diff options
Diffstat (limited to 'collections-debian-merged/ansible_collections/netapp/ontap/tests')
95 files changed, 25687 insertions, 0 deletions
diff --git a/collections-debian-merged/ansible_collections/netapp/ontap/tests/sanity/ignore-2.10.txt b/collections-debian-merged/ansible_collections/netapp/ontap/tests/sanity/ignore-2.10.txt new file mode 100644 index 00000000..e686ab44 --- /dev/null +++ b/collections-debian-merged/ansible_collections/netapp/ontap/tests/sanity/ignore-2.10.txt @@ -0,0 +1,6 @@ +plugins/modules/na_ontap_autosupport_invoke.py validate-modules:invalid-argument-name +plugins/modules/na_ontap_info.py validate-modules:parameter-state-invalid-choice +plugins/modules/na_ontap_login_messages.py validate-modules:invalid-argument-name +plugins/modules/na_ontap_motd.py validate-modules:invalid-argument-name +plugins/modules/na_ontap_nfs.py validate-modules:parameter-invalid +plugins/modules/na_ontap_rest_info.py validate-modules:parameter-state-invalid-choice diff --git a/collections-debian-merged/ansible_collections/netapp/ontap/tests/sanity/ignore-2.9.txt b/collections-debian-merged/ansible_collections/netapp/ontap/tests/sanity/ignore-2.9.txt new file mode 100644 index 00000000..5c626a03 --- /dev/null +++ b/collections-debian-merged/ansible_collections/netapp/ontap/tests/sanity/ignore-2.9.txt @@ -0,0 +1 @@ +plugins/modules/na_ontap_nfs.py validate-modules:parameter-invalid
\ No newline at end of file diff --git a/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/compat/__init__.py b/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/compat/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/compat/__init__.py diff --git a/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/compat/builtins.py b/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/compat/builtins.py new file mode 100644 index 00000000..f60ee678 --- /dev/null +++ b/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/compat/builtins.py @@ -0,0 +1,33 @@ +# (c) 2014, Toshio Kuratomi <tkuratomi@ansible.com> +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see <http://www.gnu.org/licenses/>. + +# Make coding more python3-ish +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +# +# Compat for python2.7 +# + +# One unittest needs to import builtins via __import__() so we need to have +# the string that represents it +try: + import __builtin__ +except ImportError: + BUILTINS = 'builtins' +else: + BUILTINS = '__builtin__' diff --git a/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/compat/mock.py b/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/compat/mock.py new file mode 100644 index 00000000..0972cd2e --- /dev/null +++ b/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/compat/mock.py @@ -0,0 +1,122 @@ +# (c) 2014, Toshio Kuratomi <tkuratomi@ansible.com> +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see <http://www.gnu.org/licenses/>. + +# Make coding more python3-ish +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +''' +Compat module for Python3.x's unittest.mock module +''' +import sys + +# Python 2.7 + +# Note: Could use the pypi mock library on python3.x as well as python2.x. It +# is the same as the python3 stdlib mock library + +try: + # Allow wildcard import because we really do want to import all of mock's + # symbols into this compat shim + # pylint: disable=wildcard-import,unused-wildcard-import + from unittest.mock import * +except ImportError: + # Python 2 + # pylint: disable=wildcard-import,unused-wildcard-import + try: + from mock import * + except ImportError: + print('You need the mock library installed on python2.x to run tests') + + +# Prior to 3.4.4, mock_open cannot handle binary read_data +if sys.version_info >= (3,) and sys.version_info < (3, 4, 4): + file_spec = None + + def _iterate_read_data(read_data): + # Helper for mock_open: + # Retrieve lines from read_data via a generator so that separate calls to + # readline, read, and readlines are properly interleaved + sep = b'\n' if isinstance(read_data, bytes) else '\n' + data_as_list = [l + sep for l in read_data.split(sep)] + + if data_as_list[-1] == sep: + # If the last line ended in a newline, the list comprehension will have an + # extra entry that's just a newline. Remove this. + data_as_list = data_as_list[:-1] + else: + # If there wasn't an extra newline by itself, then the file being + # emulated doesn't have a newline to end the last line remove the + # newline that our naive format() added + data_as_list[-1] = data_as_list[-1][:-1] + + for line in data_as_list: + yield line + + def mock_open(mock=None, read_data=''): + """ + A helper function to create a mock to replace the use of `open`. It works + for `open` called directly or used as a context manager. + + The `mock` argument is the mock object to configure. If `None` (the + default) then a `MagicMock` will be created for you, with the API limited + to methods or attributes available on standard file handles. + + `read_data` is a string for the `read` methoddline`, and `readlines` of the + file handle to return. This is an empty string by default. + """ + def _readlines_side_effect(*args, **kwargs): + if handle.readlines.return_value is not None: + return handle.readlines.return_value + return list(_data) + + def _read_side_effect(*args, **kwargs): + if handle.read.return_value is not None: + return handle.read.return_value + return type(read_data)().join(_data) + + def _readline_side_effect(): + if handle.readline.return_value is not None: + while True: + yield handle.readline.return_value + for line in _data: + yield line + + global file_spec + if file_spec is None: + import _io + file_spec = list(set(dir(_io.TextIOWrapper)).union(set(dir(_io.BytesIO)))) + + if mock is None: + mock = MagicMock(name='open', spec=open) + + handle = MagicMock(spec=file_spec) + handle.__enter__.return_value = handle + + _data = _iterate_read_data(read_data) + + handle.write.return_value = None + handle.read.return_value = None + handle.readline.return_value = None + handle.readlines.return_value = None + + handle.read.side_effect = _read_side_effect + handle.readline.side_effect = _readline_side_effect() + handle.readlines.side_effect = _readlines_side_effect + + mock.return_value = handle + return mock diff --git a/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/compat/unittest.py b/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/compat/unittest.py new file mode 100644 index 00000000..98f08ad6 --- /dev/null +++ b/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/compat/unittest.py @@ -0,0 +1,38 @@ +# (c) 2014, Toshio Kuratomi <tkuratomi@ansible.com> +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see <http://www.gnu.org/licenses/>. + +# Make coding more python3-ish +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +''' +Compat module for Python2.7's unittest module +''' + +import sys + +# Allow wildcard import because we really do want to import all of +# unittests's symbols into this compat shim +# pylint: disable=wildcard-import,unused-wildcard-import +if sys.version_info < (2, 7): + try: + # Need unittest2 on python2.6 + from unittest2 import * + except ImportError: + print('You need unittest2 installed on python2.6.x to run tests') +else: + from unittest import * diff --git a/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/module_utils/test_netapp.py b/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/module_utils/test_netapp.py new file mode 100644 index 00000000..be76d435 --- /dev/null +++ b/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/module_utils/test_netapp.py @@ -0,0 +1,468 @@ +# Copyright (c) 2018 NetApp +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +''' unit tests for module_utils netapp.py ''' +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import json +import os.path +import tempfile + +import pytest + +from ansible.module_utils.ansible_release import __version__ as ansible_version +from ansible.module_utils import basic +from ansible.module_utils._text import to_bytes +from ansible_collections.netapp.ontap.plugins.module_utils.netapp import COLLECTION_VERSION +from ansible_collections.netapp.ontap.tests.unit.compat.mock import patch + +import ansible_collections.netapp.ontap.plugins.module_utils.netapp as netapp_utils + +if not netapp_utils.has_netapp_lib(): + pytestmark = pytest.mark.skip("skipping as missing required netapp_lib") + + +def set_module_args(args): + """prepare arguments so that they will be picked up during module creation""" + args = json.dumps({'ANSIBLE_MODULE_ARGS': args}) + basic._ANSIBLE_ARGS = to_bytes(args) # pylint: disable=protected-access + + +class AnsibleFailJson(Exception): + """Exception class to be raised by module.fail_json and caught by the test case""" + + +def fail_json(*args, **kwargs): # pylint: disable=unused-argument + """function to patch over fail_json; package return data into an exception""" + kwargs['failed'] = True + raise AnsibleFailJson(kwargs) + + +SRR = { + # common responses + 'is_rest': (200, {}, None), + 'is_zapi': (400, {}, "Unreachable"), + 'empty_good': ({}, None), + 'end_of_sequence': (None, "Unexpected call to send_request"), + 'generic_error': (None, "Expected error"), +} + + +class MockONTAPConnection(object): + ''' mock a server connection to ONTAP host ''' + + def __init__(self, kind=None, parm1=None): + ''' save arguments ''' + self.type = kind + self.parm1 = parm1 + self.xml_in = None + self.xml_out = None + + def invoke_successfully(self, xml, enable_tunneling): # pylint: disable=unused-argument + ''' mock invoke_successfully returning xml data ''' + self.xml_in = xml + if self.type == 'vserver': + xml = self.build_vserver_info(self.parm1) + self.xml_out = xml + return xml + + @staticmethod + def build_vserver_info(vserver): + ''' 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}) + xml.add_child_elem(attributes) + return xml + + +def test_ems_log_event_version(): + ''' validate Ansible version is correctly read ''' + source = 'unittest' + server = MockONTAPConnection() + netapp_utils.ems_log_event(source, server) + xml = server.xml_in + version = xml.get_child_content('app-version') + if version == ansible_version: + assert version == ansible_version + else: + assert version == COLLECTION_VERSION + print("Ansible version: %s" % ansible_version) + + +def test_get_cserver(): + ''' validate cluster vserser name is correctly retrieved ''' + svm_name = 'svm1' + server = MockONTAPConnection('vserver', svm_name) + cserver = netapp_utils.get_cserver(server) + assert cserver == svm_name + + +def mock_args(feature_flags=None): + args = { + 'hostname': 'test', + 'username': 'test_user', + 'password': 'test_pass!' + } + if feature_flags is not None: + args.update({'feature_flags': feature_flags}) + return args + + +def cert_args(feature_flags=None): + args = { + 'hostname': 'test', + 'cert_filepath': 'test_pem.pem', + 'key_filepath': 'test_key.key' + } + if feature_flags is not None: + args.update({'feature_flags': feature_flags}) + return args + + +def create_module(args): + argument_spec = netapp_utils.na_ontap_host_argument_spec() + set_module_args(args) + module = basic.AnsibleModule(argument_spec) + return module + + +def create_restapi_object(args): + module = create_module(args) + module.fail_json = fail_json + rest_api = netapp_utils.OntapRestAPI(module) + return rest_api + + +def create_ontapzapicx_object(args, feature_flags=None): + module_args = dict(args) + if feature_flags is not None: + module_args['feature_flags'] = feature_flags + module = create_module(module_args) + module.fail_json = fail_json + my_args = dict(args) + my_args.update(dict(module=module)) + zapi_cx = netapp_utils.OntapZAPICx(**my_args) + return zapi_cx + + +def test_write_to_file(): + ''' check error and debug logs can be written to disk ''' + rest_api = create_restapi_object(mock_args()) + # logging an error also add a debug record + rest_api.log_error(404, '404 error') + print(rest_api.errors) + print(rest_api.debug_logs) + # logging a debug record only + rest_api.log_debug(501, '501 error') + print(rest_api.errors) + print(rest_api.debug_logs) + + try: + tempdir = tempfile.TemporaryDirectory() + filepath = os.path.join(tempdir.name, 'log.txt') + except AttributeError: + # python 2.7 does not support tempfile.TemporaryDirectory + # we're taking a small chance that there is a race condition + filepath = '/tmp/deleteme354.txt' + rest_api.write_debug_log_to_file(filepath=filepath, append=False) + with open(filepath, 'r') as log: + lines = log.readlines() + assert len(lines) == 4 + assert lines[0].strip() == 'Debug: 404' + assert lines[2].strip() == 'Debug: 501' + + # Idempotent, as append is False + rest_api.write_debug_log_to_file(filepath=filepath, append=False) + with open(filepath, 'r') as log: + lines = log.readlines() + assert len(lines) == 4 + assert lines[0].strip() == 'Debug: 404' + assert lines[2].strip() == 'Debug: 501' + + # Duplication, as append is True + rest_api.write_debug_log_to_file(filepath=filepath, append=True) + with open(filepath, 'r') as log: + lines = log.readlines() + assert len(lines) == 8 + assert lines[0].strip() == 'Debug: 404' + assert lines[2].strip() == 'Debug: 501' + assert lines[4].strip() == 'Debug: 404' + assert lines[6].strip() == 'Debug: 501' + + rest_api.write_errors_to_file(filepath=filepath, append=False) + with open(filepath, 'r') as log: + lines = log.readlines() + assert len(lines) == 1 + assert lines[0].strip() == 'Error: 404 error' + + # Idempotent, as append is False + rest_api.write_errors_to_file(filepath=filepath, append=False) + with open(filepath, 'r') as log: + lines = log.readlines() + assert len(lines) == 1 + assert lines[0].strip() == 'Error: 404 error' + + # Duplication, as append is True + rest_api.write_errors_to_file(filepath=filepath, append=True) + with open(filepath, 'r') as log: + lines = log.readlines() + assert len(lines) == 2 + assert lines[0].strip() == 'Error: 404 error' + assert lines[1].strip() == 'Error: 404 error' + + +@patch('ansible_collections.netapp.ontap.plugins.module_utils.netapp.OntapRestAPI.send_request') +def test_is_rest_true(mock_request): + ''' is_rest is expected to return True ''' + mock_request.side_effect = [ + SRR['is_rest'], + ] + rest_api = create_restapi_object(mock_args()) + is_rest = rest_api.is_rest() + print(rest_api.errors) + print(rest_api.debug_logs) + assert is_rest + + +@patch('ansible_collections.netapp.ontap.plugins.module_utils.netapp.OntapRestAPI.send_request') +def test_is_rest_false(mock_request): + ''' is_rest is expected to return False ''' + mock_request.side_effect = [ + SRR['is_zapi'], + ] + rest_api = create_restapi_object(mock_args()) + is_rest = rest_api.is_rest() + print(rest_api.errors) + print(rest_api.debug_logs) + assert not is_rest + assert rest_api.errors[0] == SRR['is_zapi'][2] + assert rest_api.debug_logs[0][0] == SRR['is_zapi'][0] # status_code + assert rest_api.debug_logs[0][1] == SRR['is_zapi'][2] # error + + +def test_has_feature_success_default(): + ''' existing feature_flag with default ''' + flag = 'deprecation_warning' + module = create_module(mock_args()) + value = netapp_utils.has_feature(module, flag) + assert value + + +def test_has_feature_success_user_true(): + ''' existing feature_flag with value set to True ''' + flag = 'user_deprecation_warning' + args = dict(mock_args({flag: True})) + module = create_module(args) + value = netapp_utils.has_feature(module, flag) + assert value + + +def test_has_feature_success_user_false(): + ''' existing feature_flag with value set to False ''' + flag = 'user_deprecation_warning' + args = dict(mock_args({flag: False})) + print(args) + module = create_module(args) + value = netapp_utils.has_feature(module, flag) + assert not value + + +def test_has_feature_invalid_key(): + ''' existing feature_flag with unknown key ''' + flag = 'deprecation_warning_bad_key' + module = create_module(mock_args()) + # replace ANsible fail method with ours + module.fail_json = fail_json + with pytest.raises(AnsibleFailJson) as exc: + netapp_utils.has_feature(module, flag) + msg = 'Internal error: unexpected feature flag: %s' % flag + assert exc.value.args[0]['msg'] == msg + + +def test_fail_has_username_password_and_cert(): + ''' failure case in auth_method ''' + args = mock_args() + args.update(dict(cert_filepath='dummy')) + with pytest.raises(AnsibleFailJson) as exc: + create_restapi_object(args) + msg = 'Error: cannot have both basic authentication (username/password) and certificate authentication (cert/key files)' + assert exc.value.args[0]['msg'] == msg + + +def test_fail_has_username_password_and_key(): + ''' failure case in auth_method ''' + args = mock_args() + args.update(dict(key_filepath='dummy')) + with pytest.raises(AnsibleFailJson) as exc: + create_restapi_object(args) + msg = 'Error: cannot have both basic authentication (username/password) and certificate authentication (cert/key files)' + assert exc.value.args[0]['msg'] == msg + + +def test_fail_has_username_and_cert(): + ''' failure case in auth_method ''' + args = mock_args() + args.update(dict(cert_filepath='dummy')) + del args['password'] + with pytest.raises(AnsibleFailJson) as exc: + create_restapi_object(args) + msg = 'Error: username and password have to be provided together and cannot be used with cert or key files' + assert exc.value.args[0]['msg'] == msg + + +def test_fail_has_password_and_cert(): + ''' failure case in auth_method ''' + args = mock_args() + args.update(dict(cert_filepath='dummy')) + del args['username'] + with pytest.raises(AnsibleFailJson) as exc: + create_restapi_object(args) + msg = 'Error: username and password have to be provided together and cannot be used with cert or key files' + assert exc.value.args[0]['msg'] == msg + + +def test_has_username_password(): + ''' auth_method reports expected value ''' + args = mock_args() + rest_api = create_restapi_object(args) + assert rest_api.auth_method == 'speedy_basic_auth' + + +def test_has_cert_no_key(): + ''' auth_method reports expected value ''' + args = cert_args() + del args['key_filepath'] + rest_api = create_restapi_object(args) + assert rest_api.auth_method == 'single_cert' + + +def test_has_cert_and_key(): + ''' auth_method reports expected value ''' + args = cert_args() + rest_api = create_restapi_object(args) + assert rest_api.auth_method == 'cert_key' + + +def test_certificate_method_zapi(): + ''' should fail when trying to read the certificate file ''' + args = cert_args() + zapi_cx = create_ontapzapicx_object(args) + with pytest.raises(AnsibleFailJson) as exc: + zapi_cx._create_certificate_auth_handler() + msg1 = 'Cannot load SSL certificate, check files exist.' + # for python 2,6 :( + msg2 = 'SSL certificate authentication requires python 2.7 or later.' + assert exc.value.args[0]['msg'].startswith((msg1, msg2)) + + +def test_classify_zapi_exception_cluster_only(): + ''' verify output matches expectations ''' + code = 13005 + message = 'Unable to find API: diagnosis-alert-get-iter on data vserver trident_svm' + zapi_exception = netapp_utils.zapi.NaApiError(code, message) + kind, new_message = netapp_utils.classify_zapi_exception(zapi_exception) + assert kind == 'missing_vserver_api_error' + assert new_message.endswith("%d:%s" % (code, message)) + + +def test_classify_zapi_exception_rpc_error(): + ''' verify output matches expectations ''' + code = 13001 + message = "RPC: Couldn't make connection [from mgwd on node \"laurentn-vsim1\" (VSID: -1) to mgwd at 172.32.78.223]" + error_message = 'NetApp API failed. Reason - %d:%s' % (code, message) + zapi_exception = netapp_utils.zapi.NaApiError(code, message) + kind, new_message = netapp_utils.classify_zapi_exception(zapi_exception) + assert kind == 'rpc_error' + assert new_message == error_message + + +def test_classify_zapi_exception_other_error(): + ''' verify output matches expectations ''' + code = 13008 + message = 'whatever' + error_message = 'NetApp API failed. Reason - %d:%s' % (code, message) + zapi_exception = netapp_utils.zapi.NaApiError(code, message) + kind, new_message = netapp_utils.classify_zapi_exception(zapi_exception) + assert kind == 'other_error' + assert new_message == error_message + + +def test_zapi_parse_response_sanitized(): + ''' should not fail when trying to read invalid XML characters (\x08) ''' + args = mock_args() + zapi_cx = create_ontapzapicx_object(args) + response = b"<?xml version='1.0' encoding='UTF-8' ?>\n<!DOCTYPE netapp SYSTEM 'file:/etc/netapp_gx.dtd'>\n" + response += b"<netapp version='1.180' xmlns='http://www.netapp.com/filer/admin'>\n<results status=\"passed\">" + response += b"<cli-output> (cluster log-forwarding create)\n\n" + response += b"Testing network connectivity to the destination host 10.10.10.10. \x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\n\n" + response += b"Error: command failed: Cannot contact destination host (10.10.10.10) from node\n" + response += b" "laurentn-vsim1". Verify connectivity to desired host or skip the\n" + response += b" connectivity check with the "-force" parameter.</cli-output>" + response += b"<cli-result-value>0</cli-result-value></results></netapp>\n" + # Manually extract cli-output contents + cli_output = response.split(b'<cli-output>')[1] + cli_output = cli_output.split(b'</cli-output>')[0] + cli_output = cli_output.replace(b'"', b'"') + # the XML parser would chole on \x08, zapi_cx._parse_response replaces them with '.' + cli_output = cli_output.replace(b'\x08', b'.') + # Use xml parser to extract cli-output contents + xml = zapi_cx._parse_response(response) + results = xml.get_child_by_name('results') + new_cli_output = results.get_child_content('cli-output') + assert cli_output.decode() == new_cli_output + + +def test_zapi_parse_response_unsanitized(): + ''' should fail when trying to read invalid XML characters (\x08) ''' + args = mock_args() + # use feature_flags to disable sanitization + zapi_cx = create_ontapzapicx_object(args, dict(sanitize_xml=False)) + response = b"<?xml version='1.0' encoding='UTF-8' ?>\n<!DOCTYPE netapp SYSTEM 'file:/etc/netapp_gx.dtd'>\n" + response += b"<netapp version='1.180' xmlns='http://www.netapp.com/filer/admin'>\n<results status=\"passed\">" + response += b"<cli-output> (cluster log-forwarding create)\n\n" + response += b"Testing network connectivity to the destination host 10.10.10.10. \x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\n\n" + response += b"Error: command failed: Cannot contact destination host (10.10.10.10) from node\n" + response += b" "laurentn-vsim1". Verify connectivity to desired host or skip the\n" + response += b" connectivity check with the "-force" parameter.</cli-output>" + response += b"<cli-result-value>0</cli-result-value></results></netapp>\n" + with pytest.raises(netapp_utils.zapi.etree.XMLSyntaxError) as exc: + zapi_cx._parse_response(response) + msg = 'PCDATA invalid Char value 8' + assert exc.value.msg.startswith(msg) + + +def test_zapi_cx_add_auth_header(): + ''' should add header ''' + args = mock_args() + module = create_module(args) + zapi_cx = netapp_utils.setup_na_ontap_zapi(module) + assert isinstance(zapi_cx, netapp_utils.OntapZAPICx) + assert zapi_cx.base64_creds is not None + request, dummy = zapi_cx._create_request(netapp_utils.zapi.NaElement('dummy_tag')) + assert "Authorization" in [x[0] for x in request.header_items()] + + +def test_zapi_cx_add_auth_header_explicit(): + ''' should add header ''' + args = mock_args() + args['feature_flags'] = dict(classic_basic_authorization=False) + module = create_module(args) + zapi_cx = netapp_utils.setup_na_ontap_zapi(module) + assert isinstance(zapi_cx, netapp_utils.OntapZAPICx) + assert zapi_cx.base64_creds is not None + request, dummy = zapi_cx._create_request(netapp_utils.zapi.NaElement('dummy_tag')) + assert "Authorization" in [x[0] for x in request.header_items()] + + +def test_zapi_cx_no_auth_header(): + ''' should add header ''' + args = mock_args() + args['feature_flags'] = dict(classic_basic_authorization=True) + module = create_module(args) + zapi_cx = netapp_utils.setup_na_ontap_zapi(module) + assert not isinstance(zapi_cx, netapp_utils.OntapZAPICx) + request, dummy = zapi_cx._create_request(netapp_utils.zapi.NaElement('dummy_tag')) + assert "Authorization" not in [x[0] for x in request.header_items()] diff --git a/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/module_utils/test_netapp_module.py b/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/module_utils/test_netapp_module.py new file mode 100644 index 00000000..8b31ed7f --- /dev/null +++ b/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/module_utils/test_netapp_module.py @@ -0,0 +1,400 @@ +# Copyright (c) 2018 NetApp +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +''' unit tests for module_utils netapp_module.py ''' +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import sys + +import pytest + +from ansible_collections.netapp.ontap.tests.unit.compat import unittest +from ansible_collections.netapp.ontap.plugins.module_utils.netapp_module import NetAppModule as na_helper + + +class AnsibleFailJson(Exception): + """Exception class to be raised by module.fail_json and caught by the test case""" + + +class MockModule(object): + ''' rough mock for an Ansible module class ''' + def __init__(self, required_param=None, not_required_param=None, unqualified_param=None): + self.argument_spec = dict( + required_param=dict(required=True), + not_required_param=dict(required=False), + unqualified_param=dict(), + feature_flags=dict(type='dict') + ) + self.params = dict( + required_param=required_param, + not_required_param=not_required_param, + unqualified_param=unqualified_param, + feature_flags=dict(type='dict') + ) + + def fail_json(self, *args, **kwargs): # pylint: disable=unused-argument + """function to simulate fail_json: package return data into an exception""" + kwargs['failed'] = True + raise AnsibleFailJson(kwargs) + + +class TestMyModule(unittest.TestCase): + ''' a group of related Unit Tests ''' + + def test_get_cd_action_create(self): + ''' validate cd_action for create ''' + current = None + desired = {'state': 'present'} + my_obj = na_helper() + result = my_obj.get_cd_action(current, desired) + assert result == 'create' + + def test_get_cd_action_delete(self): + ''' validate cd_action for delete ''' + current = {'state': 'absent'} + desired = {'state': 'absent'} + my_obj = na_helper() + result = my_obj.get_cd_action(current, desired) + assert result == 'delete' + + def test_get_cd_action(self): + ''' validate cd_action for returning None ''' + current = None + desired = {'state': 'absent'} + my_obj = na_helper() + result = my_obj.get_cd_action(current, desired) + assert result is None + + def test_get_modified_attributes_for_no_data(self): + ''' validate modified attributes when current is None ''' + current = None + desired = {'name': 'test'} + my_obj = na_helper() + result = my_obj.get_modified_attributes(current, desired) + assert result == {} + + def test_get_modified_attributes(self): + ''' validate modified attributes ''' + current = {'name': ['test', 'abcd', 'xyz', 'pqr'], 'state': 'present'} + desired = {'name': ['abcd', 'abc', 'xyz', 'pqr'], 'state': 'absent'} + my_obj = na_helper() + result = my_obj.get_modified_attributes(current, desired) + assert result == desired + + def test_get_modified_attributes_for_intersecting_mixed_list(self): + ''' validate modified attributes for list diff ''' + current = {'name': [2, 'four', 'six', 8]} + desired = {'name': ['a', 8, 'ab', 'four', 'abcd']} + my_obj = na_helper() + result = my_obj.get_modified_attributes(current, desired, True) + assert result == {'name': ['a', 'ab', 'abcd']} + + def test_get_modified_attributes_for_intersecting_list(self): + ''' validate modified attributes for list diff ''' + current = {'name': ['two', 'four', 'six', 'eight']} + desired = {'name': ['a', 'six', 'ab', 'four', 'abc']} + my_obj = na_helper() + result = my_obj.get_modified_attributes(current, desired, True) + assert result == {'name': ['a', 'ab', 'abc']} + + def test_get_modified_attributes_for_nonintersecting_list(self): + ''' validate modified attributes for list diff ''' + current = {'name': ['two', 'four', 'six', 'eight']} + desired = {'name': ['a', 'ab', 'abd']} + my_obj = na_helper() + result = my_obj.get_modified_attributes(current, desired, True) + assert result == {'name': ['a', 'ab', 'abd']} + + def test_get_modified_attributes_for_list_of_dicts_no_data(self): + ''' validate modified attributes for list diff ''' + current = None + desired = {'address_blocks': [{'start': '10.20.10.40', 'size': 5}]} + my_obj = na_helper() + result = my_obj.get_modified_attributes(current, desired, True) + assert result == {} + + def test_get_modified_attributes_for_intersecting_list_of_dicts(self): + ''' validate modified attributes for list diff ''' + current = {'address_blocks': [{'start': '10.10.10.23', 'size': 5}, {'start': '10.10.10.30', 'size': 5}]} + desired = {'address_blocks': [{'start': '10.10.10.23', 'size': 5}, {'start': '10.10.10.30', 'size': 5}, {'start': '10.20.10.40', 'size': 5}]} + my_obj = na_helper() + result = my_obj.get_modified_attributes(current, desired, True) + assert result == {'address_blocks': [{'start': '10.20.10.40', 'size': 5}]} + + def test_get_modified_attributes_for_nonintersecting_list_of_dicts(self): + ''' validate modified attributes for list diff ''' + current = {'address_blocks': [{'start': '10.10.10.23', 'size': 5}, {'start': '10.10.10.30', 'size': 5}]} + desired = {'address_blocks': [{'start': '10.20.10.23', 'size': 5}, {'start': '10.20.10.30', 'size': 5}, {'start': '10.20.10.40', 'size': 5}]} + my_obj = na_helper() + result = my_obj.get_modified_attributes(current, desired, True) + assert result == {'address_blocks': [{'start': '10.20.10.23', 'size': 5}, {'start': '10.20.10.30', 'size': 5}, {'start': '10.20.10.40', 'size': 5}]} + + def test_get_modified_attributes_for_list_diff(self): + ''' validate modified attributes for list diff ''' + current = {'name': ['test', 'abcd'], 'state': 'present'} + desired = {'name': ['abcd', 'abc'], 'state': 'present'} + my_obj = na_helper() + result = my_obj.get_modified_attributes(current, desired, True) + assert result == {'name': ['abc']} + + def test_get_modified_attributes_for_no_change(self): + ''' validate modified attributes for same data in current and desired ''' + current = {'name': 'test'} + desired = {'name': 'test'} + my_obj = na_helper() + result = my_obj.get_modified_attributes(current, desired) + assert result == {} + + def test_get_modified_attributes_for_an_empty_desired_list(self): + ''' validate modified attributes for an empty desired list ''' + current = {'snapmirror_label': ['daily', 'weekly', 'monthly'], 'state': 'present'} + desired = {'snapmirror_label': [], 'state': 'present'} + my_obj = na_helper() + result = my_obj.get_modified_attributes(current, desired) + assert result == {'snapmirror_label': []} + + def test_get_modified_attributes_for_an_empty_desired_list_diff(self): + ''' validate modified attributes for an empty desired list with diff''' + current = {'snapmirror_label': ['daily', 'weekly', 'monthly'], 'state': 'present'} + desired = {'snapmirror_label': [], 'state': 'present'} + my_obj = na_helper() + result = my_obj.get_modified_attributes(current, desired, True) + assert result == {'snapmirror_label': []} + + def test_get_modified_attributes_for_an_empty_current_list(self): + ''' validate modified attributes for an empty current list ''' + current = {'snapmirror_label': [], 'state': 'present'} + desired = {'snapmirror_label': ['daily', 'weekly', 'monthly'], 'state': 'present'} + my_obj = na_helper() + result = my_obj.get_modified_attributes(current, desired) + assert result == {'snapmirror_label': ['daily', 'weekly', 'monthly']} + + def test_get_modified_attributes_for_an_empty_current_list_diff(self): + ''' validate modified attributes for an empty current list with diff''' + current = {'snapmirror_label': [], 'state': 'present'} + desired = {'snapmirror_label': ['daily', 'weekly', 'monthly'], 'state': 'present'} + my_obj = na_helper() + result = my_obj.get_modified_attributes(current, desired, True) + assert result == {'snapmirror_label': ['daily', 'weekly', 'monthly']} + + def test_get_modified_attributes_for_empty_lists(self): + ''' validate modified attributes for empty lists ''' + current = {'snapmirror_label': [], 'state': 'present'} + desired = {'snapmirror_label': [], 'state': 'present'} + my_obj = na_helper() + result = my_obj.get_modified_attributes(current, desired) + assert result == {} + + def test_get_modified_attributes_for_empty_lists_diff(self): + ''' validate modified attributes for empty lists with diff ''' + current = {'snapmirror_label': [], 'state': 'present'} + desired = {'snapmirror_label': [], 'state': 'present'} + my_obj = na_helper() + result = my_obj.get_modified_attributes(current, desired, True) + assert result == {} + + def test_get_modified_attributes_equal_lists_with_duplicates(self): + ''' validate modified attributes for equal lists with duplicates ''' + current = {'schedule': ['hourly', 'daily', 'daily', 'weekly', 'monthly', 'daily'], 'state': 'present'} + desired = {'schedule': ['hourly', 'daily', 'daily', 'weekly', 'monthly', 'daily'], 'state': 'present'} + my_obj = na_helper() + result = my_obj.get_modified_attributes(current, desired, False) + assert result == {} + + def test_get_modified_attributes_equal_lists_with_duplicates_diff(self): + ''' validate modified attributes for equal lists with duplicates with diff ''' + current = {'schedule': ['hourly', 'daily', 'daily', 'weekly', 'monthly', 'daily'], 'state': 'present'} + desired = {'schedule': ['hourly', 'daily', 'daily', 'weekly', 'monthly', 'daily'], 'state': 'present'} + my_obj = na_helper() + result = my_obj.get_modified_attributes(current, desired, True) + assert result == {} + + def test_get_modified_attributes_for_current_list_with_duplicates(self): + ''' validate modified attributes for current list with duplicates ''' + current = {'schedule': ['hourly', 'daily', 'daily', 'weekly', 'monthly', 'daily'], 'state': 'present'} + desired = {'schedule': ['daily', 'daily', 'weekly', 'monthly'], 'state': 'present'} + my_obj = na_helper() + result = my_obj.get_modified_attributes(current, desired, False) + assert result == {'schedule': ['daily', 'daily', 'weekly', 'monthly']} + + def test_get_modified_attributes_for_current_list_with_duplicates_diff(self): + ''' validate modified attributes for current list with duplicates with diff ''' + current = {'schedule': ['hourly', 'daily', 'daily', 'weekly', 'monthly', 'daily'], 'state': 'present'} + desired = {'schedule': ['daily', 'daily', 'weekly', 'monthly'], 'state': 'present'} + my_obj = na_helper() + result = my_obj.get_modified_attributes(current, desired, True) + assert result == {'schedule': []} + + def test_get_modified_attributes_for_desired_list_with_duplicates(self): + ''' validate modified attributes for desired list with duplicates ''' + current = {'schedule': ['daily', 'weekly', 'monthly'], 'state': 'present'} + desired = {'schedule': ['hourly', 'daily', 'daily', 'weekly', 'monthly', 'daily'], 'state': 'present'} + my_obj = na_helper() + result = my_obj.get_modified_attributes(current, desired, False) + assert result == {'schedule': ['hourly', 'daily', 'daily', 'weekly', 'monthly', 'daily']} + + def test_get_modified_attributes_for_desired_list_with_duplicates_diff(self): + ''' validate modified attributes for desired list with duplicates with diff ''' + current = {'schedule': ['daily', 'weekly', 'monthly'], 'state': 'present'} + desired = {'schedule': ['hourly', 'daily', 'daily', 'weekly', 'monthly', 'daily'], 'state': 'present'} + my_obj = na_helper() + result = my_obj.get_modified_attributes(current, desired, True) + assert result == {'schedule': ['hourly', 'daily', 'daily']} + + def test_is_rename_action_for_empty_input(self): + ''' validate rename action for input None ''' + source = None + target = None + my_obj = na_helper() + result = my_obj.is_rename_action(source, target) + assert result == source + + def test_is_rename_action_for_no_source(self): + ''' validate rename action when source is None ''' + source = None + target = 'test2' + my_obj = na_helper() + result = my_obj.is_rename_action(source, target) + assert result is False + + def test_is_rename_action_for_no_target(self): + ''' validate rename action when target is None ''' + source = 'test2' + target = None + my_obj = na_helper() + result = my_obj.is_rename_action(source, target) + assert result is True + + def test_is_rename_action(self): + ''' validate rename action ''' + source = 'test' + target = 'test2' + my_obj = na_helper() + result = my_obj.is_rename_action(source, target) + assert result is False + + def test_required_is_not_set_to_none(self): + ''' if a key is present, without a value, Ansible sets it to None ''' + my_obj = na_helper() + my_module = MockModule() + print(my_module.argument_spec) + with pytest.raises(AnsibleFailJson) as exc: + my_obj.check_and_set_parameters(my_module) + msg = 'required_param requires a value, got: None' + assert exc.value.args[0]['msg'] == msg + + # force a value different than None + my_module.params['required_param'] = 1 + my_params = my_obj.check_and_set_parameters(my_module) + assert set(my_params.keys()) == set(['required_param', 'feature_flags']) + + def test_sanitize_wwn_no_action(self): + ''' no change ''' + initiator = 'tEsT' + expected = initiator + my_obj = na_helper() + result = my_obj.sanitize_wwn(initiator) + assert result == expected + + def test_sanitize_wwn_no_action_valid_iscsi(self): + ''' no change ''' + initiator = 'iqn.1995-08.com.eXaMpLe:StRiNg' + expected = initiator + my_obj = na_helper() + result = my_obj.sanitize_wwn(initiator) + assert result == expected + + def test_sanitize_wwn_no_action_valid_wwn(self): + ''' no change ''' + initiator = '01:02:03:04:0A:0b:0C:0d' + expected = initiator.lower() + my_obj = na_helper() + result = my_obj.sanitize_wwn(initiator) + assert result == expected + + def test_filter_empty_dict(self): + ''' empty dict return empty dict ''' + my_obj = na_helper() + arg = dict() + result = my_obj.filter_out_none_entries(arg) + assert arg == result + + def test_filter_empty_list(self): + ''' empty list return empty list ''' + my_obj = na_helper() + arg = list() + result = my_obj.filter_out_none_entries(arg) + assert arg == result + + def test_filter_typeerror_on_none(self): + ''' empty list return empty list ''' + my_obj = na_helper() + arg = None + with pytest.raises(TypeError) as exc: + my_obj.filter_out_none_entries(arg) + msg = "unexpected type <class 'NoneType'>" + if sys.version_info < (3, 0): + # the assert fails on 2.x + return + assert exc.value.args[0] == msg + + def test_filter_typeerror_on_str(self): + ''' empty list return empty list ''' + my_obj = na_helper() + arg = "" + with pytest.raises(TypeError) as exc: + my_obj.filter_out_none_entries(arg) + msg = "unexpected type <class 'str'>" + if sys.version_info < (3, 0): + # the assert fails on 2.x + return + assert exc.value.args[0] == msg + + def test_filter_simple_dict(self): + ''' simple dict return simple dict ''' + my_obj = na_helper() + arg = dict(a=None, b=1, c=None, d=2, e=3) + expected = dict(b=1, d=2, e=3) + result = my_obj.filter_out_none_entries(arg) + assert expected == result + + def test_filter_simple_list(self): + ''' simple list return simple list ''' + my_obj = na_helper() + arg = [None, 2, 3, None, 5] + expected = [2, 3, 5] + result = my_obj.filter_out_none_entries(arg) + assert expected == result + + def test_filter_dict_dict(self): + ''' simple dict return simple dict ''' + my_obj = na_helper() + arg = dict(a=None, b=dict(u=1, v=None, w=2), c=dict(), d=2, e=3) + expected = dict(b=dict(u=1, w=2), d=2, e=3) + result = my_obj.filter_out_none_entries(arg) + assert expected == result + + def test_filter_list_list(self): + ''' simple list return simple list ''' + my_obj = na_helper() + arg = [None, [1, None, 3], 3, None, 5] + expected = [[1, 3], 3, 5] + result = my_obj.filter_out_none_entries(arg) + assert expected == result + + def test_filter_dict_list_dict(self): + ''' simple dict return simple dict ''' + my_obj = na_helper() + arg = dict(a=None, b=[dict(u=1, v=None, w=2), 5, None, dict(x=6, y=None)], c=dict(), d=2, e=3) + expected = dict(b=[dict(u=1, w=2), 5, dict(x=6)], d=2, e=3) + result = my_obj.filter_out_none_entries(arg) + assert expected == result + + def test_filter_list_dict_list(self): + ''' simple list return simple list ''' + my_obj = na_helper() + arg = [None, [1, None, 3], dict(a=None, b=[7, None, 9], c=None, d=dict(u=None, v=10)), None, 5] + expected = [[1, 3], dict(b=[7, 9], d=dict(v=10)), 5] + result = my_obj.filter_out_none_entries(arg) + assert expected == result diff --git a/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_aggregate.py b/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_aggregate.py new file mode 100644 index 00000000..58d4eac1 --- /dev/null +++ b/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_aggregate.py @@ -0,0 +1,419 @@ +# (c) 2018, NetApp, Inc +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +""" unit tests for Ansible module: na_ontap_aggregate """ + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type +import json +import pytest + +from ansible.module_utils import basic +from ansible.module_utils._text import to_bytes +from ansible_collections.netapp.ontap.tests.unit.compat import unittest +from ansible_collections.netapp.ontap.tests.unit.compat.mock import patch, Mock +import ansible_collections.netapp.ontap.plugins.module_utils.netapp as netapp_utils + +from ansible_collections.netapp.ontap.plugins.modules.na_ontap_aggregate \ + import NetAppOntapAggregate as my_module # module under test + +if not netapp_utils.has_netapp_lib(): + pytestmark = pytest.mark.skip('skipping as missing required netapp_lib') + + +def set_module_args(args): + """prepare arguments so that they will be picked up during module creation""" + args = json.dumps({'ANSIBLE_MODULE_ARGS': args}) + basic._ANSIBLE_ARGS = to_bytes(args) # pylint: disable=protected-access + + +class AnsibleExitJson(Exception): + """Exception class to be raised by module.exit_json and caught by the test case""" + + +class AnsibleFailJson(Exception): + """Exception class to be raised by module.fail_json and caught by the test case""" + + +def exit_json(*args, **kwargs): # pylint: disable=unused-argument + """function to patch over exit_json; package return data into an exception""" + if 'changed' not in kwargs: + kwargs['changed'] = False + raise AnsibleExitJson(kwargs) + + +def fail_json(*args, **kwargs): # pylint: disable=unused-argument + """function to patch over fail_json; package return data into an exception""" + kwargs['failed'] = True + raise AnsibleFailJson(kwargs) + + +AGGR_NAME = 'aggr_name' +OS_NAME = 'abc' + + +class MockONTAPConnection(object): + ''' mock server connection to ONTAP host ''' + + def __init__(self, kind=None, parm1=None, parm2=None): + ''' save arguments ''' + self.type = kind + self.parm1 = parm1 + self.parm2 = parm2 + self.xml_in = None + self.xml_out = None + self.zapis = list() + + def invoke_successfully(self, xml, enable_tunneling): # pylint: disable=unused-argument + ''' mock invoke_successfully returning xml data ''' + self.xml_in = xml + print('request:', xml.to_string()) + zapi = xml.get_name() + self.zapis.append(zapi) + if zapi == 'aggr-object-store-get-iter': + if self.type in ('aggregate_no_object_store',): + xml = None + else: + xml = self.build_object_store_info() + elif self.type in ('aggregate', 'aggr_disks', 'aggr_mirrors', 'aggregate_no_object_store'): + with_os = self.type != 'aggregate_no_object_store' + xml = self.build_aggregate_info(self.parm1, self.parm2, with_object_store=with_os) + if self.type in ('aggr_disks', 'aggr_mirrors'): + self.type = 'disks' + elif self.type == 'no_aggregate': + xml = None + elif self.type == 'no_aggregate_then_aggregate': + xml = None + self.type = 'aggregate' + elif self.type == 'disks': + xml = self.build_disk_info() + elif self.type == 'aggregate_fail': + raise netapp_utils.zapi.NaApiError(code='TEST', message="This exception is from the unit test") + self.xml_out = xml + return xml + + @staticmethod + def build_aggregate_info(vserver, aggregate, with_object_store): + ''' build xml data for aggregate and vserser-info ''' + xml = netapp_utils.zapi.NaElement('xml') + data = {'num-records': 3, + 'attributes-list': + {'aggr-attributes': + {'aggregate-name': aggregate, + 'aggr-raid-attributes': {'state': 'offline'} + }, + 'object-store-information': {'object-store-name': 'abc'} + }, + 'vserver-info': + {'vserver-name': vserver + } + } + if not with_object_store: + del data['attributes-list']['object-store-information'] + xml.translate_struct(data) + print(xml.to_string()) + return xml + + @staticmethod + def build_object_store_info(): + ''' build xml data for object_store ''' + xml = netapp_utils.zapi.NaElement('xml') + data = {'num-records': 3, + 'attributes-list': + {'object-store-information': {'object-store-name': 'abc'} + } + } + xml.translate_struct(data) + print(xml.to_string()) + return xml + + @staticmethod + def build_disk_info(): + ''' build xml data for disk ''' + xml = netapp_utils.zapi.NaElement('xml') + data = {'num-records': 1, + 'attributes-list': [ + {'disk-info': + {'disk-name': '1', + 'disk-raid-info': + {'disk-aggregate-info': + {'plex-name': 'plex0'} + }}}, + {'disk-info': + {'disk-name': '2', + 'disk-raid-info': + {'disk-aggregate-info': + {'plex-name': 'plex0'} + }}}, + {'disk-info': + {'disk-name': '3', + 'disk-raid-info': + {'disk-aggregate-info': + {'plex-name': 'plexM'} + }}}, + {'disk-info': + {'disk-name': '4', + 'disk-raid-info': + {'disk-aggregate-info': + {'plex-name': 'plexM'} + }}}, + ]} + xml.translate_struct(data) + print(xml.to_string()) + return xml + + +class TestMyModule(unittest.TestCase): + ''' a group of related Unit Tests ''' + + def setUp(self): + self.mock_module_helper = patch.multiple(basic.AnsibleModule, + exit_json=exit_json, + fail_json=fail_json) + self.mock_module_helper.start() + self.addCleanup(self.mock_module_helper.stop) + self.server = MockONTAPConnection('aggregate', '12', 'name') + # whether to use a mock or a simulator + self.onbox = False + self.zapis = list() + + def set_default_args(self): + if self.onbox: + hostname = '10.193.74.78' + username = 'admin' + password = 'netapp1!' + name = 'name' + else: + hostname = 'hostname' + username = 'username' + password = 'password' + name = AGGR_NAME + return dict({ + 'hostname': hostname, + 'username': username, + 'password': password, + 'name': name + }) + + def call_command(self, module_args, what=None): + ''' utility function to call apply ''' + args = dict(self.set_default_args()) + args.update(module_args) + set_module_args(args) + my_obj = my_module() + my_obj.asup_log_for_cserver = Mock(return_value=None) + aggregate = 'aggregate' + if what == 'disks': + aggregate = 'aggr_disks' + elif what == 'mirrors': + aggregate = 'aggr_mirrors' + elif what is not None: + aggregate = what + + if not self.onbox: + # mock the connection + my_obj.server = MockONTAPConnection(aggregate, '12', AGGR_NAME) + self.zapis = my_obj.server.zapis + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + return exc.value.args[0]['changed'] + + def test_module_fail_when_required_args_missing(self): + ''' required arguments are reported as errors ''' + with pytest.raises(AnsibleFailJson) as exc: + set_module_args({}) + my_module() + print('Info: %s' % exc.value.args[0]['msg']) + + def test_create(self): + module_args = { + 'disk_count': '2', + 'is_mirrored': 'true', + } + changed = self.call_command(module_args, what='no_aggregate') + assert changed + assert 'aggr-object-store-attach' not in self.zapis + + def test_create_with_object_store(self): + module_args = { + 'disk_count': '2', + 'is_mirrored': 'true', + 'object_store_name': 'abc' + } + changed = self.call_command(module_args, what='no_aggregate') + assert changed + assert 'aggr-object-store-attach' in self.zapis + + def test_is_mirrored(self): + module_args = { + 'disk_count': '2', + 'is_mirrored': 'true', + } + changed = self.call_command(module_args) + assert not changed + + def test_disks_list(self): + module_args = { + 'disks': ['1', '2'], + } + changed = self.call_command(module_args, 'disks') + assert not changed + + def test_mirror_disks(self): + module_args = { + 'disks': ['1', '2'], + 'mirror_disks': ['3', '4'] + } + changed = self.call_command(module_args, 'mirrors') + assert not changed + + def test_spare_pool(self): + module_args = { + 'disk_count': '2', + 'spare_pool': 'Pool1' + } + changed = self.call_command(module_args) + assert not changed + + def test_rename(self): + module_args = { + 'from_name': 'test_name2' + } + changed = self.call_command(module_args, 'no_aggregate_then_aggregate') + assert changed + assert 'aggr-rename' in self.zapis + + def test_rename_error_no_from(self): + module_args = { + 'from_name': 'test_name2' + } + with pytest.raises(AnsibleFailJson) as exc: + self.call_command(module_args, 'no_aggregate') + msg = 'Error renaming: aggregate %s does not exist' % module_args['from_name'] + assert msg in exc.value.args[0]['msg'] + + def test_rename_with_add_object_store(self): + module_args = { + 'from_name': 'test_name2' + } + changed = self.call_command(module_args, 'aggregate_no_object_store') + assert not changed + + def test_object_store_present(self): + module_args = { + 'object_store_name': 'abc' + } + changed = self.call_command(module_args) + assert not changed + + def test_object_store_create(self): + module_args = { + 'object_store_name': 'abc' + } + changed = self.call_command(module_args, 'aggregate_no_object_store') + assert changed + + def test_object_store_modify(self): + ''' not supported ''' + module_args = { + 'object_store_name': 'def' + } + with pytest.raises(AnsibleFailJson) as exc: + self.call_command(module_args) + msg = 'Error: object store %s is already associated with aggregate %s.' % (OS_NAME, AGGR_NAME) + assert msg in exc.value.args[0]['msg'] + + def test_if_all_methods_catch_exception(self): + module_args = {} + module_args.update(self.set_default_args()) + module_args.update({'service_state': 'online'}) + module_args.update({'unmount_volumes': 'True'}) + module_args.update({'from_name': 'test_name2'}) + set_module_args(module_args) + my_obj = my_module() + if not self.onbox: + my_obj.server = MockONTAPConnection('aggregate_fail') + with pytest.raises(AnsibleFailJson) as exc: + my_obj.aggr_get_iter(module_args.get('name')) + assert '' in exc.value.args[0]['msg'] + with pytest.raises(AnsibleFailJson) as exc: + my_obj.aggregate_online() + assert 'Error changing the state of aggregate' in exc.value.args[0]['msg'] + with pytest.raises(AnsibleFailJson) as exc: + my_obj.aggregate_offline() + assert 'Error changing the state of aggregate' in exc.value.args[0]['msg'] + with pytest.raises(AnsibleFailJson) as exc: + my_obj.create_aggr() + assert 'Error provisioning aggregate' in exc.value.args[0]['msg'] + with pytest.raises(AnsibleFailJson) as exc: + my_obj.delete_aggr() + assert 'Error removing aggregate' in exc.value.args[0]['msg'] + with pytest.raises(AnsibleFailJson) as exc: + my_obj.rename_aggregate() + assert 'Error renaming aggregate' in exc.value.args[0]['msg'] + with pytest.raises(AnsibleFailJson) as exc: + my_obj.asup_log_for_cserver = Mock(return_value=None) + my_obj.apply() + assert 'TEST:This exception is from the unit test' in exc.value.args[0]['msg'] + + def test_disks_bad_mapping(self): + module_args = { + 'disks': ['0'], + } + with pytest.raises(AnsibleFailJson) as exc: + self.call_command(module_args, 'mirrors') + msg = "Error mapping disks for aggregate %s: cannot not match disks with current aggregate disks." % AGGR_NAME + assert exc.value.args[0]['msg'].startswith(msg) + + def test_disks_overlapping_mirror(self): + module_args = { + 'disks': ['1', '2', '3'], + } + with pytest.raises(AnsibleFailJson) as exc: + self.call_command(module_args, 'mirrors') + msg = "Error mapping disks for aggregate %s: found overlapping plexes:" % AGGR_NAME + assert exc.value.args[0]['msg'].startswith(msg) + + def test_disks_removing_disk(self): + module_args = { + 'disks': ['1'], + } + with pytest.raises(AnsibleFailJson) as exc: + self.call_command(module_args, 'mirrors') + msg = "Error removing disks is not supported. Aggregate %s: these disks cannot be removed: ['2']." % AGGR_NAME + assert exc.value.args[0]['msg'].startswith(msg) + + def test_disks_removing_mirror_disk(self): + module_args = { + 'disks': ['1', '2'], + 'mirror_disks': ['4', '6'] + } + with pytest.raises(AnsibleFailJson) as exc: + self.call_command(module_args, 'mirrors') + msg = "Error removing disks is not supported. Aggregate %s: these disks cannot be removed: ['3']." % AGGR_NAME + assert exc.value.args[0]['msg'].startswith(msg) + + def test_disks_add(self): + module_args = { + 'disks': ['1', '2', '5'], + } + changed = self.call_command(module_args, 'disks') + assert changed + + def test_mirror_disks_add(self): + module_args = { + 'disks': ['1', '2', '5'], + 'mirror_disks': ['3', '4', '6'] + } + changed = self.call_command(module_args, 'mirrors') + assert changed + + def test_mirror_disks_add_unbalanced(self): + module_args = { + 'disks': ['1', '2'], + 'mirror_disks': ['3', '4', '6'] + } + with pytest.raises(AnsibleFailJson) as exc: + self.call_command(module_args, 'mirrors') + msg = "Error cannot add mirror disks ['6'] without adding disks for aggregate %s." % AGGR_NAME + assert exc.value.args[0]['msg'].startswith(msg) diff --git a/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_autosupport.py b/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_autosupport.py new file mode 100644 index 00000000..c5c591f7 --- /dev/null +++ b/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_autosupport.py @@ -0,0 +1,245 @@ +# (c) 2018, NetApp, Inc +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +''' unit test template for ONTAP Ansible module ''' + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type +import json +import pytest + +from ansible_collections.netapp.ontap.tests.unit.compat import unittest +from ansible_collections.netapp.ontap.tests.unit.compat.mock import patch, Mock +from ansible.module_utils import basic +from ansible.module_utils._text import to_bytes +import ansible_collections.netapp.ontap.plugins.module_utils.netapp as netapp_utils + +from ansible_collections.netapp.ontap.plugins.modules.na_ontap_autosupport \ + import NetAppONTAPasup as asup_module # module under test + +if not netapp_utils.has_netapp_lib(): + pytestmark = pytest.mark.skip('skipping as missing required netapp_lib') + + +def set_module_args(args): + """prepare arguments so that they will be picked up during module creation""" + args = json.dumps({'ANSIBLE_MODULE_ARGS': args}) + basic._ANSIBLE_ARGS = to_bytes(args) # pylint: disable=protected-access + + +class AnsibleExitJson(Exception): + """Exception class to be raised by module.exit_json and caught by the test case""" + pass + + +class AnsibleFailJson(Exception): + """Exception class to be raised by module.fail_json and caught by the test case""" + pass + + +def exit_json(*args, **kwargs): # pylint: disable=unused-argument + """function to patch over exit_json; package return data into an exception""" + if 'changed' not in kwargs: + kwargs['changed'] = False + raise AnsibleExitJson(kwargs) + + +def fail_json(*args, **kwargs): # pylint: disable=unused-argument + """function to patch over fail_json; package return data into an exception""" + kwargs['failed'] = True + raise AnsibleFailJson(kwargs) + + +class MockONTAPConnection(object): + ''' mock server connection to ONTAP host ''' + + def __init__(self, kind=None, data=None): + ''' save arguments ''' + self.kind = kind + self.params = data + self.xml_in = None + self.xml_out = None + + def invoke_successfully(self, xml, enable_tunneling): # pylint: disable=unused-argument + ''' mock invoke_successfully returning xml data ''' + self.xml_in = xml + if self.kind == 'asup': + xml = self.build_asup_config_info(self.params) + self.xml_out = xml + return xml + + @staticmethod + def build_asup_config_info(asup_data): + ''' build xml data for asup-config ''' + xml = netapp_utils.zapi.NaElement('xml') + attributes = {'attributes': {'autosupport-config-info': { + 'node-name': asup_data['node_name'], + 'is-enabled': asup_data['is_enabled'], + 'is-support-enabled': asup_data['support'], + 'proxy-url': asup_data['proxy_url'], + 'post-url': asup_data['post_url'], + 'transport': asup_data['transport'], + 'is-node-in-subject': 'false', + 'from': 'test', + 'mail-hosts': [{'string': '1.2.3.4'}, {'string': '4.5.6.8'}], + 'noteto': [{'mail-address': 'abc@test.com'}, + {'mail-address': 'def@test.com'}], + }}} + xml.translate_struct(attributes) + return xml + + +class TestMyModule(unittest.TestCase): + ''' a group of related Unit Tests ''' + + def setUp(self): + self.mock_module_helper = patch.multiple(basic.AnsibleModule, + exit_json=exit_json, + fail_json=fail_json) + self.mock_module_helper.start() + self.addCleanup(self.mock_module_helper.stop) + self.server = MockONTAPConnection() + self.mock_asup = { + 'node_name': 'test-vsim1', + 'transport': 'https', + 'support': 'false', + 'post_url': 'testbed.netapp.com/asupprod/post/1.0/postAsup', + 'proxy_url': 'something.com', + } + + def mock_args(self): + return { + 'node_name': self.mock_asup['node_name'], + 'transport': self.mock_asup['transport'], + 'support': self.mock_asup['support'], + 'post_url': self.mock_asup['post_url'], + 'proxy_url': self.mock_asup['proxy_url'], + 'hostname': 'host', + 'username': 'admin', + 'password': 'password', + } + + def get_asup_mock_object(self, kind=None, enabled='false'): + """ + Helper method to return an na_ontap_volume object + :param kind: passes this param to MockONTAPConnection() + :return: na_ontap_volume object + """ + asup_obj = asup_module() + asup_obj.autosupport_log = Mock(return_value=None) + if kind is None: + asup_obj.server = MockONTAPConnection() + else: + data = self.mock_asup + data['is_enabled'] = enabled + asup_obj.server = MockONTAPConnection(kind='asup', data=data) + return asup_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({}) + asup_module() + print('Info: %s' % exc.value.args[0]['msg']) + + def test_enable_asup(self): + ''' a more interesting test ''' + data = self.mock_args() + set_module_args(data) + with pytest.raises(AnsibleExitJson) as exc: + self.get_asup_mock_object('asup').apply() + assert exc.value.args[0]['changed'] + + def test_disable_asup(self): + ''' a more interesting test ''' + # enable asup + data = self.mock_args() + data['state'] = 'absent' + set_module_args(data) + with pytest.raises(AnsibleExitJson) as exc: + self.get_asup_mock_object(kind='asup', enabled='true').apply() + assert exc.value.args[0]['changed'] + + def test_result_from_get(self): + ''' Check boolean and service_state conversion from get ''' + data = self.mock_args() + set_module_args(data) + obj = self.get_asup_mock_object(kind='asup', enabled='true') + # constructed based on valued passed in self.mock_asup and build_asup_config_info() + expected_dict = { + 'node_name': 'test-vsim1', + 'service_state': 'started', + 'support': False, + 'hostname_in_subject': False, + 'transport': self.mock_asup['transport'], + 'post_url': self.mock_asup['post_url'], + 'proxy_url': self.mock_asup['proxy_url'], + 'from_address': 'test', + 'mail_hosts': ['1.2.3.4', '4.5.6.8'], + 'partner_addresses': [], + 'to_addresses': [], + 'noteto': ['abc@test.com', 'def@test.com'] + } + result = obj.get_autosupport_config() + assert result == expected_dict + + def test_modify_config(self): + ''' Check boolean and service_state conversion from get ''' + data = self.mock_args() + data['transport'] = 'http' + data['post_url'] = 'somethingelse.com' + data['proxy_url'] = 'somethingelse.com' + set_module_args(data) + with pytest.raises(AnsibleExitJson) as exc: + self.get_asup_mock_object('asup').apply() + assert exc.value.args[0]['changed'] + + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_autosupport.NetAppONTAPasup.get_autosupport_config') + def test_get_called(self, get_asup): + data = self.mock_args() + set_module_args(data) + with pytest.raises(AnsibleExitJson) as exc: + self.get_asup_mock_object('asup').apply() + get_asup.assert_called_with() + + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_autosupport.NetAppONTAPasup.modify_autosupport_config') + def test_modify_called(self, modify_asup): + data = self.mock_args() + data['transport'] = 'http' + set_module_args(data) + with pytest.raises(AnsibleExitJson) as exc: + self.get_asup_mock_object('asup').apply() + modify_asup.assert_called_with({'transport': 'http', 'service_state': 'started'}) + + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_autosupport.NetAppONTAPasup.modify_autosupport_config') + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_autosupport.NetAppONTAPasup.get_autosupport_config') + def test_modify_not_called(self, get_asup, modify_asup): + data = self.mock_args() + set_module_args(data) + with pytest.raises(AnsibleExitJson) as exc: + self.get_asup_mock_object('asup').apply() + get_asup.assert_called_with() + modify_asup.assert_not_called() + + def test_modify_packet(self): + '''check XML construction for nested attributes like mail-hosts, noteto, partner-address, and to''' + data = self.mock_args() + set_module_args(data) + obj = self.get_asup_mock_object(kind='asup', enabled='true') + modify_dict = { + 'noteto': ['one@test.com'], + 'partner_addresses': ['firstpartner@test.com'], + 'mail_hosts': ['1.1.1.1'], + 'to_addresses': ['first@test.com'] + } + obj.modify_autosupport_config(modify_dict) + xml = obj.server.xml_in + for key in ['noteto', 'to', 'partner-address']: + assert xml[key] is not None + assert xml[key]['mail-address'] is not None + assert xml['noteto']['mail-address'] == modify_dict['noteto'][0] + assert xml['to']['mail-address'] == modify_dict['to_addresses'][0] + assert xml['partner-address']['mail-address'] == modify_dict['partner_addresses'][0] + assert xml['mail-hosts'] is not None + assert xml['mail-hosts']['string'] is not None + assert xml['mail-hosts']['string'] == modify_dict['mail_hosts'][0] diff --git a/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_autosupport_invoke.py b/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_autosupport_invoke.py new file mode 100644 index 00000000..b250bdef --- /dev/null +++ b/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_autosupport_invoke.py @@ -0,0 +1,135 @@ +# (c) 2020, NetApp, Inc +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +''' unit tests for Ansible module: na_ontap_autosupport_invoke ''' + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type +import json +import pytest + +from ansible.module_utils import basic +from ansible.module_utils._text import to_bytes +from ansible_collections.netapp.ontap.tests.unit.compat import unittest +from ansible_collections.netapp.ontap.tests.unit.compat.mock import patch, Mock +import ansible_collections.netapp.ontap.plugins.module_utils.netapp as netapp_utils + +from ansible_collections.netapp.ontap.plugins.modules.na_ontap_autosupport_invoke \ + import NetAppONTAPasupInvoke as invoke_module # module under test + +# REST API canned responses when mocking send_request +SRR = { + # common responses + 'is_rest': (200, {}, None), + 'is_zapi': (400, {}, "Unreachable"), + 'empty_good': (200, {}, None), + 'end_of_sequence': (500, None, "Unexpected call to send_request"), + 'generic_error': (400, None, "Expected error") +} + + +def set_module_args(args): + """prepare arguments so that they will be picked up during module creation""" + args = json.dumps({'ANSIBLE_MODULE_ARGS': args}) + basic._ANSIBLE_ARGS = to_bytes(args) # pylint: disable=protected-access + + +class AnsibleExitJson(Exception): + """Exception class to be raised by module.exit_json and caught by the test case""" + + +class AnsibleFailJson(Exception): + """Exception class to be raised by module.fail_json and caught by the test case""" + + +def exit_json(*args, **kwargs): # pylint: disable=unused-argument + """function to patch over exit_json; package return data into an exception""" + if 'changed' not in kwargs: + kwargs['changed'] = False + raise AnsibleExitJson(kwargs) + + +def fail_json(*args, **kwargs): # pylint: disable=unused-argument + """function to patch over fail_json; package return data into an exception""" + kwargs['failed'] = True + raise AnsibleFailJson(kwargs) + + +class MockONTAPConnection(object): + ''' mock server connection to ONTAP host ''' + + def invoke_successfully(self, xml, enable_tunneling): + raise netapp_utils.zapi.NaApiError('test', 'Expected error') + + +class TestMyModule(unittest.TestCase): + ''' Unit tests for na_ontap_wwpn_alias ''' + + def setUp(self): + self.mock_module_helper = patch.multiple(basic.AnsibleModule, + exit_json=exit_json, + fail_json=fail_json) + self.mock_module_helper.start() + self.addCleanup(self.mock_module_helper.stop) + self.mock_invoke = { + 'name': 'test_node', + 'message': 'test_message', + 'type': 'all' + } + + def mock_args(self): + return { + 'message': self.mock_invoke['message'], + 'name': self.mock_invoke['name'], + 'type': self.mock_invoke['type'], + 'hostname': 'test_host', + 'username': 'test_user', + 'password': 'test_pass!' + } + + def get_invoke_mock_object(self, use_rest=True): + invoke_obj = invoke_module() + if not use_rest: + invoke_obj.ems_log_event = Mock() + invoke_obj.server = MockONTAPConnection() + return invoke_obj + + @patch('ansible_collections.netapp.ontap.plugins.module_utils.netapp.OntapRestAPI.send_request') + def test_rest_successful_send(self, mock_request): + '''Test successful send message''' + data = self.mock_args() + set_module_args(data) + mock_request.side_effect = [ + SRR['is_rest'], + SRR['empty_good'], + SRR['end_of_sequence'] + ] + with pytest.raises(AnsibleExitJson) as exc: + self.get_invoke_mock_object().apply() + assert exc.value.args[0]['changed'] + + @patch('ansible_collections.netapp.ontap.plugins.module_utils.netapp.OntapRestAPI.send_request') + def test_rest_send_error(self, mock_request): + '''Test rest send error''' + data = self.mock_args() + set_module_args(data) + mock_request.side_effect = [ + SRR['is_rest'], + SRR['generic_error'], + SRR['empty_good'], + SRR['end_of_sequence'] + ] + with pytest.raises(AnsibleFailJson) as exc: + self.get_invoke_mock_object().apply() + msg = "Error on sending autosupport message to node %s: Expected error." % data['name'] + assert exc.value.args[0]['msg'] == msg + + def test_zapi_send_error(self): + '''Test rest send error''' + data = self.mock_args() + data['use_rest'] = 'Never' + set_module_args(data) + with pytest.raises(AnsibleFailJson) as exc: + self.get_invoke_mock_object(use_rest=False).apply() + msg = "Error on sending autosupport message to node %s: NetApp API failed. Reason - test:Expected error." % data['name'] + assert exc.value.args[0]['msg'] == msg diff --git a/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_broadcast_domain.py b/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_broadcast_domain.py new file mode 100644 index 00000000..86a0b8d2 --- /dev/null +++ b/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_broadcast_domain.py @@ -0,0 +1,309 @@ +# (c) 2018, NetApp, Inc +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +''' unit test template for ONTAP Ansible module ''' + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type +import json +import pytest + +from ansible_collections.netapp.ontap.tests.unit.compat import unittest +from ansible_collections.netapp.ontap.tests.unit.compat.mock import patch, Mock +from ansible.module_utils import basic +from ansible.module_utils._text import to_bytes +import ansible_collections.netapp.ontap.plugins.module_utils.netapp as netapp_utils + +from ansible_collections.netapp.ontap.plugins.modules.na_ontap_broadcast_domain \ + import NetAppOntapBroadcastDomain as broadcast_domain_module # module under test + +if not netapp_utils.has_netapp_lib(): + pytestmark = pytest.mark.skip('skipping as missing required netapp_lib') + + +def set_module_args(args): + """prepare arguments so that they will be picked up during module creation""" + args = json.dumps({'ANSIBLE_MODULE_ARGS': args}) + basic._ANSIBLE_ARGS = to_bytes(args) # pylint: disable=protected-access + + +class AnsibleExitJson(Exception): + """Exception class to be raised by module.exit_json and caught by the test case""" + pass + + +class AnsibleFailJson(Exception): + """Exception class to be raised by module.fail_json and caught by the test case""" + pass + + +def exit_json(*args, **kwargs): # pylint: disable=unused-argument + """function to patch over exit_json; package return data into an exception""" + if 'changed' not in kwargs: + kwargs['changed'] = False + raise AnsibleExitJson(kwargs) + + +def fail_json(*args, **kwargs): # pylint: disable=unused-argument + """function to patch over fail_json; package return data into an exception""" + kwargs['failed'] = True + raise AnsibleFailJson(kwargs) + + +class MockONTAPConnection(object): + ''' mock server connection to ONTAP host ''' + + def __init__(self, kind=None, data=None): + ''' save arguments ''' + self.type = kind + self.params = data + self.xml_in = None + self.xml_out = None + + def invoke_successfully(self, xml, enable_tunneling): # pylint: disable=unused-argument + ''' mock invoke_successfully returning xml data ''' + self.xml_in = xml + if self.type == 'broadcast_domain': + xml = self.build_broadcast_domain_info(self.params) + self.xml_out = xml + return xml + + @staticmethod + def build_broadcast_domain_info(broadcast_domain_details): + ''' build xml data for broadcast_domain info ''' + xml = netapp_utils.zapi.NaElement('xml') + attributes = { + 'num-records': 1, + 'attributes-list': { + 'net-port-broadcast-domain-info': { + 'broadcast-domain': broadcast_domain_details['name'], + 'ipspace': broadcast_domain_details['ipspace'], + 'mtu': broadcast_domain_details['mtu'], + 'ports': { + 'port-info': { + 'port': 'test_port_1' + } + } + } + + } + } + xml.translate_struct(attributes) + return xml + + +class TestMyModule(unittest.TestCase): + ''' a group of related Unit Tests ''' + + def setUp(self): + self.mock_module_helper = patch.multiple(basic.AnsibleModule, + exit_json=exit_json, + fail_json=fail_json) + self.mock_module_helper.start() + self.addCleanup(self.mock_module_helper.stop) + self.server = MockONTAPConnection() + self.mock_broadcast_domain = { + 'name': 'test_broadcast_domain', + 'mtu': '1000', + 'ipspace': 'Default', + 'ports': 'test_port_1' + } + + def mock_args(self): + return { + 'name': self.mock_broadcast_domain['name'], + 'ipspace': self.mock_broadcast_domain['ipspace'], + 'mtu': self.mock_broadcast_domain['mtu'], + 'ports': self.mock_broadcast_domain['ports'], + 'hostname': 'test', + 'username': 'test_user', + 'password': 'test_pass!' + } + + def get_broadcast_domain_mock_object(self, kind=None, data=None): + """ + Helper method to return an na_ontap_volume object + :param kind: passes this param to MockONTAPConnection() + :param data: passes this param to MockONTAPConnection() + :return: na_ontap_volume object + """ + broadcast_domain_obj = broadcast_domain_module() + broadcast_domain_obj.asup_log_for_cserver = Mock(return_value=None) + broadcast_domain_obj.cluster = Mock() + broadcast_domain_obj.cluster.invoke_successfully = Mock() + if kind is None: + broadcast_domain_obj.server = MockONTAPConnection() + else: + if data is None: + broadcast_domain_obj.server = MockONTAPConnection(kind='broadcast_domain', data=self.mock_broadcast_domain) + else: + broadcast_domain_obj.server = MockONTAPConnection(kind='broadcast_domain', data=data) + return broadcast_domain_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({}) + broadcast_domain_module() + print('Info: %s' % exc.value.args[0]['msg']) + + def test_get_nonexistent_net_route(self): + ''' Test if get_broadcast_domain returns None for non-existent broadcast_domain ''' + set_module_args(self.mock_args()) + result = self.get_broadcast_domain_mock_object().get_broadcast_domain() + assert result is None + + def test_create_error_missing_broadcast_domain(self): + ''' Test if create throws an error if broadcast_domain is not specified''' + data = self.mock_args() + del data['name'] + set_module_args(data) + with pytest.raises(AnsibleFailJson) as exc: + self.get_broadcast_domain_mock_object('broadcast_domain').create_broadcast_domain() + msg = 'missing required arguments: name' + assert exc.value.args[0]['msg'] == msg + + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_broadcast_domain.NetAppOntapBroadcastDomain.create_broadcast_domain') + def test_successful_create(self, create_broadcast_domain): + ''' Test successful create ''' + data = self.mock_args() + set_module_args(data) + with pytest.raises(AnsibleExitJson) as exc: + self.get_broadcast_domain_mock_object().apply() + assert exc.value.args[0]['changed'] + create_broadcast_domain.assert_called_with() + + def test_create_idempotency(self): + ''' Test create idempotency ''' + set_module_args(self.mock_args()) + obj = self.get_broadcast_domain_mock_object('broadcast_domain') + with pytest.raises(AnsibleExitJson) as exc: + obj.apply() + assert not exc.value.args[0]['changed'] + + def test_modify_mtu(self): + ''' Test successful modify mtu ''' + data = self.mock_args() + data['mtu'] = '1200' + set_module_args(data) + with pytest.raises(AnsibleExitJson) as exc: + self.get_broadcast_domain_mock_object('broadcast_domain').apply() + assert exc.value.args[0]['changed'] + + def test_modify_ipspace_idempotency(self): + ''' Test modify ipsapce idempotency''' + data = self.mock_args() + data['ipspace'] = 'Cluster' + set_module_args(data) + with pytest.raises(AnsibleFailJson) as exc: + self.get_broadcast_domain_mock_object('broadcast_domain').apply() + msg = 'A domain ipspace can not be modified after the domain has been created.' + assert exc.value.args[0]['msg'] == msg + + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_broadcast_domain.NetAppOntapBroadcastDomain.add_broadcast_domain_ports') + def test_add_ports(self, add_broadcast_domain_ports): + ''' Test successful modify ports ''' + data = self.mock_args() + data['ports'] = 'test_port_1,test_port_2' + set_module_args(data) + with pytest.raises(AnsibleExitJson) as exc: + self.get_broadcast_domain_mock_object('broadcast_domain').apply() + assert exc.value.args[0]['changed'] + add_broadcast_domain_ports.assert_called_with(['test_port_2']) + + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_broadcast_domain.NetAppOntapBroadcastDomain.delete_broadcast_domain_ports') + def test_delete_ports(self, delete_broadcast_domain_ports): + ''' Test successful modify ports ''' + data = self.mock_args() + data['ports'] = '' + set_module_args(data) + with pytest.raises(AnsibleExitJson) as exc: + self.get_broadcast_domain_mock_object('broadcast_domain').apply() + assert exc.value.args[0]['changed'] + delete_broadcast_domain_ports.assert_called_with(['test_port_1']) + + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_broadcast_domain.NetAppOntapBroadcastDomain.modify_broadcast_domain') + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_broadcast_domain.NetAppOntapBroadcastDomain.split_broadcast_domain') + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_broadcast_domain.NetAppOntapBroadcastDomain.get_broadcast_domain') + def test_split_broadcast_domain(self, get_broadcast_domain, split_broadcast_domain, modify_broadcast_domain): + ''' Test successful split broadcast domain ''' + data = self.mock_args() + data['from_name'] = 'test_broadcast_domain' + data['name'] = 'test_broadcast_domain_2' + data['ports'] = 'test_port_2' + set_module_args(data) + current = { + 'name': 'test_broadcast_domain', + 'mtu': '1000', + 'ipspace': 'Default', + 'ports': ['test_port_1,test_port2'] + } + get_broadcast_domain.side_effect = [ + None, + current, + current + ] + with pytest.raises(AnsibleExitJson) as exc: + self.get_broadcast_domain_mock_object().apply() + assert exc.value.args[0]['changed'] + modify_broadcast_domain.assert_not_called() + split_broadcast_domain.assert_called_with() + + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_broadcast_domain.NetAppOntapBroadcastDomain.delete_broadcast_domain') + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_broadcast_domain.NetAppOntapBroadcastDomain.modify_broadcast_domain') + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_broadcast_domain.NetAppOntapBroadcastDomain.get_broadcast_domain') + def test_split_broadcast_domain_modify_delete(self, get_broadcast_domain, modify_broadcast_domain, delete_broadcast_domain): + ''' Test successful split broadcast domain ''' + data = self.mock_args() + data['from_name'] = 'test_broadcast_domain' + data['name'] = 'test_broadcast_domain_2' + data['ports'] = ['test_port_1', 'test_port_2'] + data['mtu'] = '1200' + set_module_args(data) + + current = { + 'name': 'test_broadcast_domain', + 'mtu': '1000', + 'ipspace': 'Default', + 'ports': ['test_port_1', 'test_port2'] + } + get_broadcast_domain.side_effect = [ + None, + current, + current + ] + with pytest.raises(AnsibleExitJson) as exc: + self.get_broadcast_domain_mock_object().apply() + assert exc.value.args[0]['changed'] + delete_broadcast_domain.assert_called_with('test_broadcast_domain') + modify_broadcast_domain.assert_called_with() + + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_broadcast_domain.NetAppOntapBroadcastDomain.get_broadcast_domain') + def test_split_broadcast_domain_not_exist(self, get_broadcast_domain): + ''' Test successful split broadcast domain ''' + data = self.mock_args() + data['from_name'] = 'test_broadcast_domain' + data['name'] = 'test_broadcast_domain_2' + data['ports'] = 'test_port_2' + set_module_args(data) + + get_broadcast_domain.side_effect = [ + None, + None, + ] + with pytest.raises(AnsibleFailJson) as exc: + self.get_broadcast_domain_mock_object().apply() + msg = 'A domain can not be split if it does not exist.' + assert exc.value.args[0]['msg'], msg + + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_broadcast_domain.NetAppOntapBroadcastDomain.split_broadcast_domain') + def test_split_broadcast_domain_idempotency(self, split_broadcast_domain): + ''' Test successful split broadcast domain ''' + data = self.mock_args() + data['from_name'] = 'test_broadcast_domain' + data['ports'] = 'test_port_1' + set_module_args(data) + with pytest.raises(AnsibleExitJson) as exc: + self.get_broadcast_domain_mock_object('broadcast_domain').apply() + assert exc.value.args[0]['changed'] is False + split_broadcast_domain.assert_not_called() diff --git a/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_cg_snapshot.py b/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_cg_snapshot.py new file mode 100644 index 00000000..7bc8dfbc --- /dev/null +++ b/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_cg_snapshot.py @@ -0,0 +1,116 @@ +# (c) 2018, NetApp, Inc +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +''' unit tests for Ansible module: na_ontap_cg_snapshot''' + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type +import json +import pytest + +from ansible_collections.netapp.ontap.tests.unit.compat import unittest +from ansible_collections.netapp.ontap.tests.unit.compat.mock import patch +from ansible.module_utils import basic +from ansible.module_utils._text import to_bytes +import ansible_collections.netapp.ontap.plugins.module_utils.netapp as netapp_utils + +from ansible_collections.netapp.ontap.plugins.modules.na_ontap_cg_snapshot \ + import NetAppONTAPCGSnapshot as my_module # module under test + +if not netapp_utils.has_netapp_lib(): + pytestmark = pytest.mark.skip('skipping as missing required netapp_lib') + + +def set_module_args(args): + """prepare arguments so that they will be picked up during module creation""" + args = json.dumps({'ANSIBLE_MODULE_ARGS': args}) + basic._ANSIBLE_ARGS = to_bytes(args) # pylint: disable=protected-access + + +class AnsibleExitJson(Exception): + """Exception class to be raised by module.exit_json and caught by the test case""" + pass + + +class AnsibleFailJson(Exception): + """Exception class to be raised by module.fail_json and caught by the test case""" + pass + + +def exit_json(*args, **kwargs): # pylint: disable=unused-argument + """function to patch over exit_json; package return data into an exception""" + if 'changed' not in kwargs: + kwargs['changed'] = False + raise AnsibleExitJson(kwargs) + + +def fail_json(*args, **kwargs): # pylint: disable=unused-argument + """function to patch over fail_json; package return data into an exception""" + kwargs['failed'] = True + raise AnsibleFailJson(kwargs) + + +class MockONTAPConnection(object): + ''' mock server connection to ONTAP host ''' + + def __init__(self, kind=None, parm1=None): + ''' save arguments ''' + self.type = kind + self.parm1 = parm1 + self.xml_in = None + self.xml_out = None + + def invoke_successfully(self, xml, enable_tunneling): # pylint: disable=unused-argument + ''' mock invoke_successfully returning xml data ''' + self.xml_in = xml + if self.type == 'vserver': + xml = self.build_vserver_info(self.parm1) + self.xml_out = xml + return xml + + @staticmethod + def build_vserver_info(vserver): + ''' 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}) + xml.add_child_elem(attributes) + # print(xml.to_string()) + return xml + + +class TestMyModule(unittest.TestCase): + ''' a group of related Unit Tests ''' + + def setUp(self): + self.mock_module_helper = patch.multiple(basic.AnsibleModule, + exit_json=exit_json, + fail_json=fail_json) + self.mock_module_helper.start() + self.addCleanup(self.mock_module_helper.stop) + self.server = MockONTAPConnection() + + def test_module_fail_when_required_args_missing(self): + ''' required arguments are reported as errors ''' + with pytest.raises(AnsibleFailJson) as exc: + set_module_args({}) + my_module() + print('Info: %s' % exc.value.args[0]['msg']) + + def test_ensure_command_called(self): + ''' a more interesting test ''' + set_module_args({ + 'vserver': 'vserver', + 'volumes': 'volumes', + 'snapshot': 'snapshot', + 'hostname': 'hostname', + 'username': 'username', + 'password': 'password', + }) + my_obj = my_module() + my_obj.server = self.server + with pytest.raises(AnsibleFailJson) as exc: + my_obj.cgcreate() + msg = 'Error fetching CG ID for CG commit snapshot' + assert exc.value.args[0]['msg'] == msg diff --git a/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_cifs.py b/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_cifs.py new file mode 100644 index 00000000..d35f816c --- /dev/null +++ b/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_cifs.py @@ -0,0 +1,228 @@ +# (c) 2018, NetApp, Inc +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +''' unit tests ONTAP Ansible module: na_ontap_cifs ''' + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type +import json +import pytest + +from ansible_collections.netapp.ontap.tests.unit.compat import unittest +from ansible_collections.netapp.ontap.tests.unit.compat.mock import patch +from ansible.module_utils import basic +from ansible.module_utils._text import to_bytes +import ansible_collections.netapp.ontap.plugins.module_utils.netapp as netapp_utils + +from ansible_collections.netapp.ontap.plugins.modules.na_ontap_cifs \ + import NetAppONTAPCifsShare as my_module # module under test + +if not netapp_utils.has_netapp_lib(): + pytestmark = pytest.mark.skip('skipping as missing required netapp_lib') + + +def set_module_args(args): + """prepare arguments so that they will be picked up during module creation""" + args = json.dumps({'ANSIBLE_MODULE_ARGS': args}) + basic._ANSIBLE_ARGS = to_bytes(args) # pylint: disable=protected-access + + +class AnsibleExitJson(Exception): + """Exception class to be raised by module.exit_json and caught by the test case""" + pass + + +class AnsibleFailJson(Exception): + """Exception class to be raised by module.fail_json and caught by the test case""" + pass + + +def exit_json(*args, **kwargs): # pylint: disable=unused-argument + """function to patch over exit_json; package return data into an exception""" + if 'changed' not in kwargs: + kwargs['changed'] = False + raise AnsibleExitJson(kwargs) + + +def fail_json(*args, **kwargs): # pylint: disable=unused-argument + """function to patch over fail_json; package return data into an exception""" + kwargs['failed'] = True + raise AnsibleFailJson(kwargs) + + +class MockONTAPConnection(object): + ''' mock server connection to ONTAP host ''' + + def __init__(self, kind=None): + ''' save arguments ''' + self.type = kind + self.xml_in = None + self.xml_out = None + + def invoke_successfully(self, xml, enable_tunneling): # pylint: disable=unused-argument + ''' mock invoke_successfully returning xml data ''' + self.xml_in = xml + if self.type == 'cifs': + xml = self.build_cifs_info() + elif self.type == 'cifs_fail': + raise netapp_utils.zapi.NaApiError(code='TEST', message="This exception is from the unit test") + self.xml_out = xml + return xml + + @staticmethod + def build_cifs_info(): + ''' build xml data for cifs-info ''' + xml = netapp_utils.zapi.NaElement('xml') + data = {'num-records': 1, 'attributes-list': {'cifs-share': { + 'share-name': 'test', + 'path': '/test', + 'vscan-fileop-profile': 'standard', + 'share-properties': [{'cifs-share-properties': 'browsable'}, + {'cifs-share-properties': 'oplocks'}], + 'symlink-properties': [{'cifs-share-symlink-properties': 'enable'}, + {'cifs-share-symlink-properties': 'read_only'}], + }}} + xml.translate_struct(data) + print(xml.to_string()) + return xml + + +class TestMyModule(unittest.TestCase): + ''' a group of related Unit Tests ''' + + def setUp(self): + self.mock_module_helper = patch.multiple(basic.AnsibleModule, + exit_json=exit_json, + fail_json=fail_json) + self.mock_module_helper.start() + self.addCleanup(self.mock_module_helper.stop) + self.server = MockONTAPConnection() + self.onbox = False + + def set_default_args(self): + if self.onbox: + hostname = '10.193.77.37' + username = 'admin' + password = 'netapp1!' + share_name = 'test' + path = '/test' + share_properties = 'browsable,oplocks' + symlink_properties = 'disable' + vscan_fileop_profile = 'standard' + vserver = 'abc' + else: + hostname = '10.193.77.37' + username = 'admin' + password = 'netapp1!' + share_name = 'test' + path = '/test' + share_properties = 'show_previous_versions' + symlink_properties = 'disable' + vscan_fileop_profile = 'no_scan' + vserver = 'abc' + return dict({ + 'hostname': hostname, + 'username': username, + 'password': password, + 'share_name': share_name, + 'path': path, + 'share_properties': share_properties, + 'symlink_properties': symlink_properties, + 'vscan_fileop_profile': vscan_fileop_profile, + 'vserver': vserver + }) + + def test_module_fail_when_required_args_missing(self): + ''' required arguments are reported as errors ''' + with pytest.raises(AnsibleFailJson) as exc: + set_module_args({}) + my_module() + print('Info: %s' % exc.value.args[0]['msg']) + + def test_ensure_cifs_get_called(self): + ''' fetching details of cifs ''' + set_module_args(self.set_default_args()) + my_obj = my_module() + my_obj.server = self.server + cifs_get = my_obj.get_cifs_share() + print('Info: test_cifs_share_get: %s' % repr(cifs_get)) + assert not bool(cifs_get) + + def test_ensure_apply_for_cifs_called(self): + ''' creating cifs share and checking idempotency ''' + module_args = {} + module_args.update(self.set_default_args()) + set_module_args(module_args) + my_obj = my_module() + if not self.onbox: + my_obj.server = self.server + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + print('Info: test_cifs_apply: %s' % repr(exc.value)) + assert exc.value.args[0]['changed'] + if not self.onbox: + my_obj.server = MockONTAPConnection('cifs') + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + print('Info: test_cifs_apply: %s' % repr(exc.value)) + assert exc.value.args[0]['changed'] + + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_cifs.NetAppONTAPCifsShare.create_cifs_share') + def test_cifs_create_called(self, create_cifs_share): + ''' creating cifs''' + module_args = {} + module_args.update(self.set_default_args()) + set_module_args(module_args) + my_obj = my_module() + if not self.onbox: + my_obj.server = MockONTAPConnection() + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + print('Info: test_cifs_apply: %s' % repr(exc.value)) + create_cifs_share.assert_called_with() + + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_cifs.NetAppONTAPCifsShare.delete_cifs_share') + def test_cifs_delete_called(self, delete_cifs_share): + ''' deleting cifs''' + module_args = {} + module_args.update(self.set_default_args()) + module_args['state'] = 'absent' + set_module_args(module_args) + my_obj = my_module() + if not self.onbox: + my_obj.server = MockONTAPConnection('cifs') + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + print('Info: test_cifs_apply: %s' % repr(exc.value)) + delete_cifs_share.assert_called_with() + + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_cifs.NetAppONTAPCifsShare.modify_cifs_share') + def test_cifs_modify_called(self, modify_cifs_share): + ''' modifying cifs''' + module_args = {} + module_args.update(self.set_default_args()) + set_module_args(module_args) + my_obj = my_module() + if not self.onbox: + my_obj.server = MockONTAPConnection('cifs') + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + print('Info: test_cifs_apply: %s' % repr(exc.value)) + modify_cifs_share.assert_called_with() + + def test_if_all_methods_catch_exception(self): + module_args = {} + module_args.update(self.set_default_args()) + set_module_args(module_args) + my_obj = my_module() + if not self.onbox: + my_obj.server = MockONTAPConnection('cifs_fail') + with pytest.raises(AnsibleFailJson) as exc: + my_obj.create_cifs_share() + assert 'Error creating cifs-share' in exc.value.args[0]['msg'] + with pytest.raises(AnsibleFailJson) as exc: + my_obj.delete_cifs_share() + assert 'Error deleting cifs-share' in exc.value.args[0]['msg'] + with pytest.raises(AnsibleFailJson) as exc: + my_obj.modify_cifs_share() + assert 'Error modifying cifs-share' in exc.value.args[0]['msg'] diff --git a/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_cifs_server.py b/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_cifs_server.py new file mode 100644 index 00000000..27b368ff --- /dev/null +++ b/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_cifs_server.py @@ -0,0 +1,222 @@ +# (c) 2018, NetApp, Inc +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +''' unit tests ONTAP Ansible module: na_ontap_cifs_server ''' + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type +import json +import pytest + +from ansible_collections.netapp.ontap.tests.unit.compat import unittest +from ansible_collections.netapp.ontap.tests.unit.compat.mock import patch +from ansible.module_utils import basic +from ansible.module_utils._text import to_bytes +import ansible_collections.netapp.ontap.plugins.module_utils.netapp as netapp_utils + +from ansible_collections.netapp.ontap.plugins.modules.na_ontap_cifs_server \ + import NetAppOntapcifsServer as my_module # module under test + +if not netapp_utils.has_netapp_lib(): + pytestmark = pytest.mark.skip('skipping as missing required netapp_lib') + + +def set_module_args(args): + """prepare arguments so that they will be picked up during module creation""" + args = json.dumps({'ANSIBLE_MODULE_ARGS': args}) + basic._ANSIBLE_ARGS = to_bytes(args) # pylint: disable=protected-access + + +class AnsibleExitJson(Exception): + """Exception class to be raised by module.exit_json and caught by the test case""" + pass + + +class AnsibleFailJson(Exception): + """Exception class to be raised by module.fail_json and caught by the test case""" + pass + + +def exit_json(*args, **kwargs): # pylint: disable=unused-argument + """function to patch over exit_json; package return data into an exception""" + if 'changed' not in kwargs: + kwargs['changed'] = False + raise AnsibleExitJson(kwargs) + + +def fail_json(*args, **kwargs): # pylint: disable=unused-argument + """function to patch over fail_json; package return data into an exception""" + kwargs['failed'] = True + raise AnsibleFailJson(kwargs) + + +class MockONTAPConnection(object): + ''' mock server connection to ONTAP host ''' + + def __init__(self, kind=None, parm1=None, parm2=None): + ''' save arguments ''' + self.type = kind + self.parm1 = parm1 + self.parm2 = parm2 + self.xml_in = None + self.xml_out = None + + def invoke_successfully(self, xml, enable_tunneling): # pylint: disable=unused-argument + ''' mock invoke_successfully returning xml data ''' + self.xml_in = xml + if self.type == 'cifs_server': + xml = self.build_vserver_info(self.parm1, self.parm2) + self.xml_out = xml + return xml + + @staticmethod + def build_vserver_info(cifs_server, admin_status): + ''' build xml data for cifs-server-info ''' + xml = netapp_utils.zapi.NaElement('xml') + data = {'num-records': 1, + 'attributes-list': {'cifs-server-config': {'cifs-server': cifs_server, + 'administrative-status': admin_status}}} + xml.translate_struct(data) + print(xml.to_string()) + return xml + + +class TestMyModule(unittest.TestCase): + ''' a group of related Unit Tests ''' + + def setUp(self): + self.mock_module_helper = patch.multiple(basic.AnsibleModule, + exit_json=exit_json, + fail_json=fail_json) + self.mock_module_helper.start() + self.addCleanup(self.mock_module_helper.stop) + self.server = MockONTAPConnection() + self.use_vsim = False + + def set_default_args(self): + if self.use_vsim: + hostname = '10.193.77.154' + username = 'admin' + password = 'netapp1!' + cifs_server = 'test' + vserver = 'ansible_test' + else: + hostname = 'hostname' + username = 'username' + password = 'password' + cifs_server = 'name' + vserver = 'vserver' + return dict({ + 'hostname': hostname, + 'username': username, + 'password': password, + 'cifs_server_name': cifs_server, + 'vserver': vserver + }) + + def test_module_fail_when_required_args_missing(self): + ''' required arguments are reported as errors ''' + with pytest.raises(AnsibleFailJson) as exc: + set_module_args({}) + my_module() + print('Info: %s' % exc.value.args[0]['msg']) + + def test_ensure_cifs_server_get_called(self): + ''' a more interesting test ''' + set_module_args(self.set_default_args()) + my_obj = my_module() + my_obj.server = self.server + cifs_server = my_obj.get_cifs_server() + print('Info: test_cifs_server_get: %s' % repr(cifs_server)) + assert cifs_server is None + + def test_ensure_cifs_server_apply_for_create_called(self): + ''' creating cifs server and checking idempotency ''' + module_args = {} + module_args.update(self.set_default_args()) + module_args.update({'cifs_server_name': 'create'}) + set_module_args(module_args) + my_obj = my_module() + if not self.use_vsim: + my_obj.server = self.server + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + print('Info: test_cifs_server_apply: %s' % repr(exc.value)) + assert exc.value.args[0]['changed'] + if not self.use_vsim: + my_obj.server = MockONTAPConnection('cifs_server', 'create', 'up') + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + print('Info: test_cifs_server_apply_for_create: %s' % repr(exc.value)) + assert not exc.value.args[0]['changed'] + + def test_ensure_cifs_server_apply_for_delete_called(self): + ''' deleting cifs server and checking idempotency ''' + module_args = {} + module_args.update(self.set_default_args()) + module_args.update({'cifs_server_name': 'delete'}) + set_module_args(module_args) + my_obj = my_module() + if not self.use_vsim: + my_obj.server = MockONTAPConnection('cifs_server', 'delete', 'up') + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + print('Info: test_cifs_server_apply: %s' % repr(exc.value)) + assert not exc.value.args[0]['changed'] + module_args.update({'state': 'absent'}) + set_module_args(module_args) + my_obj = my_module() + if not self.use_vsim: + my_obj.server = MockONTAPConnection('cifs_server', 'delete', 'up') + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + print('Info: test_cifs_server_delete: %s' % repr(exc.value)) + assert exc.value.args[0]['changed'] + + def test_ensure_start_cifs_server_called(self): + ''' starting cifs server and checking idempotency ''' + module_args = {} + module_args.update(self.set_default_args()) + module_args.update({'cifs_server_name': 'delete'}) + module_args.update({'service_state': 'started'}) + set_module_args(module_args) + my_obj = my_module() + if not self.use_vsim: + my_obj.server = MockONTAPConnection('cifs_server', 'test', 'up') + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + print('Info: test_ensure_start_cifs_server: %s' % repr(exc.value)) + assert not exc.value.args[0]['changed'] + module_args.update({'service_state': 'stopped'}) + set_module_args(module_args) + my_obj = my_module() + if not self.use_vsim: + my_obj.server = MockONTAPConnection('cifs_server', 'test', 'up') + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + print('Info: test_ensure_start_cifs_server: %s' % repr(exc.value)) + assert exc.value.args[0]['changed'] + + def test_ensure_stop_cifs_server_called(self): + ''' stopping cifs server and checking idempotency ''' + module_args = {} + module_args.update(self.set_default_args()) + module_args.update({'cifs_server_name': 'delete'}) + module_args.update({'service_state': 'stopped'}) + set_module_args(module_args) + my_obj = my_module() + if not self.use_vsim: + my_obj.server = MockONTAPConnection('cifs_server', 'test', 'down') + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + print('Info: test_ensure_stop_cifs_server: %s' % repr(exc.value)) + assert not exc.value.args[0]['changed'] + module_args.update({'service_state': 'started'}) + set_module_args(module_args) + my_obj = my_module() + if not self.use_vsim: + my_obj.server = MockONTAPConnection('cifs_server', 'test', 'down') + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + print('Info: test_ensure_stop_cifs_server: %s' % repr(exc.value)) + assert exc.value.args[0]['changed'] diff --git a/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_cluster.py b/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_cluster.py new file mode 100644 index 00000000..8d90c477 --- /dev/null +++ b/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_cluster.py @@ -0,0 +1,429 @@ +# (c) 2018, NetApp, Inc +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +''' unit tests ONTAP Ansible module: na_ontap_cluster ''' + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type +import json +import pytest + +from ansible.module_utils import basic +from ansible.module_utils._text import to_bytes +from ansible_collections.netapp.ontap.tests.unit.compat import unittest +from ansible_collections.netapp.ontap.tests.unit.compat.mock import patch, Mock +import ansible_collections.netapp.ontap.plugins.module_utils.netapp as netapp_utils + +from ansible_collections.netapp.ontap.plugins.modules.na_ontap_cluster \ + import NetAppONTAPCluster as my_module # module under test + +if not netapp_utils.has_netapp_lib(): + pytestmark = pytest.mark.skip('skipping as missing required netapp_lib') + + +def set_module_args(args): + """prepare arguments so that they will be picked up during module creation""" + args = json.dumps({'ANSIBLE_MODULE_ARGS': args}) + basic._ANSIBLE_ARGS = to_bytes(args) # pylint: disable=protected-access + + +class AnsibleExitJson(Exception): + """Exception class to be raised by module.exit_json and caught by the test case""" + + +class AnsibleFailJson(Exception): + """Exception class to be raised by module.fail_json and caught by the test case""" + + +def exit_json(*args, **kwargs): # pylint: disable=unused-argument + """function to patch over exit_json; package return data into an exception""" + if 'changed' not in kwargs: + kwargs['changed'] = False + raise AnsibleExitJson(kwargs) + + +def fail_json(*args, **kwargs): # pylint: disable=unused-argument + """function to patch over fail_json; package return data into an exception""" + kwargs['failed'] = True + raise AnsibleFailJson(kwargs) + + +class MockONTAPConnection(object): + ''' mock server connection to ONTAP host ''' + + def __init__(self, kind=None): + ''' save arguments ''' + self.type = kind + self.xml_in = None + self.xml_out = None + + def invoke_successfully(self, xml, enable_tunneling): # pylint: disable=unused-argument + ''' mock invoke_successfully returning xml data ''' + self.xml_in = xml + if self.type == 'cluster': + xml = self.build_cluster_info() + if self.type == 'cluster_success': + xml = self.build_cluster_info_success() + elif self.type == 'cluster_add': + xml = self.build_add_node_info() + elif self.type == 'cluster_extra_input': + self.type = 'cluster' # success on second call + raise netapp_utils.zapi.NaApiError(code='TEST1', message="Extra input: single-node-cluster") + elif self.type == 'cluster_extra_input_loop': + raise netapp_utils.zapi.NaApiError(code='TEST2', message="Extra input: single-node-cluster") + elif self.type == 'cluster_extra_input_other': + raise netapp_utils.zapi.NaApiError(code='TEST3', message="Extra input: other-unexpected-element") + elif self.type == 'cluster_fail': + raise netapp_utils.zapi.NaApiError(code='TEST4', message="This exception is from the unit test") + self.xml_out = xml + return xml + + def autosupport_log(self): + ''' mock autosupport log''' + return None + + @staticmethod + def build_cluster_info(): + ''' build xml data for cluster-create-join-progress-info ''' + xml = netapp_utils.zapi.NaElement('xml') + attributes = { + 'attributes': { + 'cluster-create-join-progress-info': { + 'is-complete': 'true', + 'status': 'whatever' + } + } + } + xml.translate_struct(attributes) + return xml + + @staticmethod + def build_cluster_info_success(): + ''' build xml data for cluster-create-join-progress-info ''' + xml = netapp_utils.zapi.NaElement('xml') + attributes = { + 'attributes': { + 'cluster-create-join-progress-info': { + 'is-complete': 'false', + 'status': 'success' + } + } + } + xml.translate_struct(attributes) + return xml + + @staticmethod + def build_add_node_info(): + ''' build xml data for cluster-create-add-node-status-info ''' + xml = netapp_utils.zapi.NaElement('xml') + attributes = { + 'attributes-list': { + 'cluster-create-add-node-status-info': { + 'failure-msg': '', + 'status': 'success' + } + } + } + xml.translate_struct(attributes) + return xml + + +class TestMyModule(unittest.TestCase): + ''' a group of related Unit Tests ''' + + def setUp(self): + self.mock_module_helper = patch.multiple(basic.AnsibleModule, + exit_json=exit_json, + fail_json=fail_json) + self.mock_module_helper.start() + self.addCleanup(self.mock_module_helper.stop) + self.server = MockONTAPConnection() + self.use_vsim = False + + def set_default_args(self): + if self.use_vsim: + hostname = '10.10.10.10' + username = 'admin' + password = 'password' + cluster_name = 'abc' + else: + hostname = '10.10.10.10' + username = 'admin' + password = 'password' + cluster_name = 'abc' + return dict({ + 'hostname': hostname, + 'username': username, + 'password': password, + 'cluster_name': cluster_name + }) + + def test_module_fail_when_required_args_missing(self): + ''' required arguments are reported as errors ''' + with pytest.raises(AnsibleFailJson) as exc: + set_module_args({}) + my_module() + print('Info: %s' % exc.value.args[0]['msg']) + + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_cluster.NetAppONTAPCluster.get_cluster_identity') + def test_ensure_apply_for_cluster_called(self, get_cl_id): + ''' creating cluster and checking idempotency ''' + get_cl_id.return_value = None + module_args = {} + module_args.update(self.set_default_args()) + set_module_args(module_args) + my_obj = my_module() + my_obj.autosupport_log = Mock(return_value=None) + if not self.use_vsim: + my_obj.server = MockONTAPConnection('cluster') + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + print('Info: test_cluster_apply: %s' % repr(exc.value)) + assert exc.value.args[0]['changed'] + + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_cluster.NetAppONTAPCluster.get_cluster_identity') + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_cluster.NetAppONTAPCluster.create_cluster') + def test_cluster_create_called(self, cluster_create, get_cl_id): + ''' creating cluster''' + get_cl_id.return_value = None + module_args = {} + module_args.update(self.set_default_args()) + set_module_args(module_args) + my_obj = my_module() + my_obj.autosupport_log = Mock(return_value=None) + if not self.use_vsim: + my_obj.server = MockONTAPConnection('cluster_success') + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + print('Info: test_cluster_apply: %s' % repr(exc.value)) + cluster_create.assert_called_with() + + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_cluster.NetAppONTAPCluster.get_cluster_identity') + def test_cluster_create_old_api(self, get_cl_id): + ''' creating cluster''' + get_cl_id.return_value = None + module_args = {} + module_args.update(self.set_default_args()) + set_module_args(module_args) + my_obj = my_module() + my_obj.autosupport_log = Mock(return_value=None) + if not self.use_vsim: + my_obj.server = MockONTAPConnection('cluster_extra_input') + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + print('Info: test_cluster_apply: %s' % repr(exc.value)) + assert exc.value.args[0]['changed'] + + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_cluster.NetAppONTAPCluster.get_cluster_identity') + def test_cluster_create_old_api_loop(self, get_cl_id): + ''' creating cluster''' + get_cl_id.return_value = None + module_args = {} + module_args.update(self.set_default_args()) + set_module_args(module_args) + my_obj = my_module() + my_obj.autosupport_log = Mock(return_value=None) + if not self.use_vsim: + my_obj.server = MockONTAPConnection('cluster_extra_input_loop') + with pytest.raises(AnsibleFailJson) as exc: + my_obj.apply() + msg = 'TEST2:Extra input: single-node-cluster' + print('Info: test_cluster_apply: %s' % repr(exc.value)) + assert msg in exc.value.args[0]['msg'] + + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_cluster.NetAppONTAPCluster.get_cluster_identity') + def test_cluster_create_old_api_other_extra(self, get_cl_id): + ''' creating cluster''' + get_cl_id.return_value = None + module_args = {} + module_args.update(self.set_default_args()) + set_module_args(module_args) + my_obj = my_module() + my_obj.autosupport_log = Mock(return_value=None) + if not self.use_vsim: + my_obj.server = MockONTAPConnection('cluster_extra_input_other') + with pytest.raises(AnsibleFailJson) as exc: + my_obj.apply() + msg = 'TEST3:Extra input: other-unexpected-element' + print('Info: test_cluster_apply: %s' % repr(exc.value)) + assert msg in exc.value.args[0]['msg'] + + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_cluster.NetAppONTAPCluster.get_cluster_ip_addresses') + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_cluster.NetAppONTAPCluster.get_cluster_identity') + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_cluster.NetAppONTAPCluster.add_node') + def test_add_node_called(self, add_node, get_cl_id, get_cl_ips): + ''' creating add_node''' + get_cl_ips.return_value = list() + get_cl_id.return_value = None + data = self.set_default_args() + del data['cluster_name'] + data['cluster_ip_address'] = '10.10.10.10' + set_module_args(data) + my_obj = my_module() + my_obj.autosupport_log = Mock(return_value=None) + if not self.use_vsim: + my_obj.server = MockONTAPConnection('cluster_add') + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + print('Info: test_cluster_apply: %s' % repr(exc.value)) + add_node.assert_called_with() + assert exc.value.args[0]['changed'] + + def test_if_all_methods_catch_exception(self): + module_args = {} + module_args.update(self.set_default_args()) + set_module_args(module_args) + my_obj = my_module() + if not self.use_vsim: + my_obj.server = MockONTAPConnection('cluster_fail') + with pytest.raises(AnsibleFailJson) as exc: + my_obj.create_cluster() + assert 'Error creating cluster' in exc.value.args[0]['msg'] + data = self.set_default_args() + data['cluster_ip_address'] = '10.10.10.10' + set_module_args(data) + my_obj = my_module() + if not self.use_vsim: + my_obj.server = MockONTAPConnection('cluster_fail') + with pytest.raises(AnsibleFailJson) as exc: + my_obj.add_node() + assert 'Error adding node with ip' in exc.value.args[0]['msg'] + + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_cluster.NetAppONTAPCluster.get_cluster_ip_addresses') + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_cluster.NetAppONTAPCluster.get_cluster_identity') + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_cluster.NetAppONTAPCluster.add_node') + def test_add_node_idempotent(self, add_node, get_cl_id, get_cl_ips): + ''' creating add_node''' + get_cl_ips.return_value = ['10.10.10.10'] + get_cl_id.return_value = None + data = self.set_default_args() + del data['cluster_name'] + data['cluster_ip_address'] = '10.10.10.10' + set_module_args(data) + my_obj = my_module() + my_obj.autosupport_log = Mock(return_value=None) + if not self.use_vsim: + my_obj.server = MockONTAPConnection('cluster_add') + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + print('Info: test_cluster_apply: %s' % repr(exc.value)) + try: + add_node.assert_not_called() + except AttributeError: + # not supported with python <= 3.4 + pass + assert not exc.value.args[0]['changed'] + + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_cluster.NetAppONTAPCluster.get_cluster_ip_addresses') + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_cluster.NetAppONTAPCluster.get_cluster_identity') + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_cluster.NetAppONTAPCluster.remove_node') + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_cluster.NetAppONTAPCluster.node_remove_wait') + def test_remove_node_ip(self, wait, remove_node, get_cl_id, get_cl_ips): + ''' creating add_node''' + get_cl_ips.return_value = ['10.10.10.10'] + get_cl_id.return_value = None + wait.return_value = None + data = self.set_default_args() + # del data['cluster_name'] + data['cluster_ip_address'] = '10.10.10.10' + data['state'] = 'absent' + set_module_args(data) + my_obj = my_module() + my_obj.autosupport_log = Mock(return_value=None) + if not self.use_vsim: + my_obj.server = MockONTAPConnection('cluster_add') + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + print('Info: test_cluster_apply: %s' % repr(exc.value)) + remove_node.assert_called_with() + assert exc.value.args[0]['changed'] + + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_cluster.NetAppONTAPCluster.get_cluster_ip_addresses') + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_cluster.NetAppONTAPCluster.get_cluster_identity') + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_cluster.NetAppONTAPCluster.remove_node') + def test_remove_node_ip_idempotent(self, remove_node, get_cl_id, get_cl_ips): + ''' creating add_node''' + get_cl_ips.return_value = list() + get_cl_id.return_value = None + data = self.set_default_args() + # del data['cluster_name'] + data['cluster_ip_address'] = '10.10.10.10' + data['state'] = 'absent' + set_module_args(data) + my_obj = my_module() + my_obj.autosupport_log = Mock(return_value=None) + if not self.use_vsim: + my_obj.server = MockONTAPConnection('cluster_add') + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + print('Info: test_cluster_apply: %s' % repr(exc.value)) + try: + remove_node.assert_not_called() + except AttributeError: + # not supported with python <= 3.4 + pass + assert not exc.value.args[0]['changed'] + + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_cluster.NetAppONTAPCluster.get_cluster_nodes') + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_cluster.NetAppONTAPCluster.get_cluster_identity') + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_cluster.NetAppONTAPCluster.remove_node') + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_cluster.NetAppONTAPCluster.node_remove_wait') + def test_remove_node_name(self, wait, remove_node, get_cl_id, get_cl_nodes): + ''' creating add_node''' + get_cl_nodes.return_value = ['node1', 'node2'] + get_cl_id.return_value = None + wait.return_value = None + data = self.set_default_args() + # del data['cluster_name'] + data['node_name'] = 'node2' + data['state'] = 'absent' + set_module_args(data) + my_obj = my_module() + my_obj.autosupport_log = Mock(return_value=None) + if not self.use_vsim: + my_obj.server = MockONTAPConnection('cluster_add') + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + print('Info: test_cluster_apply: %s' % repr(exc.value)) + remove_node.assert_called_with() + assert exc.value.args[0]['changed'] + + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_cluster.NetAppONTAPCluster.get_cluster_nodes') + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_cluster.NetAppONTAPCluster.get_cluster_identity') + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_cluster.NetAppONTAPCluster.remove_node') + def test_remove_node_name_idempotent(self, remove_node, get_cl_id, get_cl_nodes): + ''' creating add_node''' + get_cl_nodes.return_value = ['node1', 'node2'] + get_cl_id.return_value = None + data = self.set_default_args() + # del data['cluster_name'] + data['node_name'] = 'node3' + data['state'] = 'absent' + set_module_args(data) + my_obj = my_module() + my_obj.autosupport_log = Mock(return_value=None) + if not self.use_vsim: + my_obj.server = MockONTAPConnection('cluster_add') + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + print('Info: test_cluster_apply: %s' % repr(exc.value)) + try: + remove_node.assert_not_called() + except AttributeError: + # not supported with python <= 3.4 + pass + assert not exc.value.args[0]['changed'] + + def test_remove_node_name_and_id(self): + ''' creating add_node''' + data = self.set_default_args() + # del data['cluster_name'] + data['cluster_ip_address'] = '10.10.10.10' + data['node_name'] = 'node3' + data['state'] = 'absent' + set_module_args(data) + with pytest.raises(AnsibleFailJson) as exc: + my_module() + print('Info: test_remove_node_name_and_id: %s' % repr(exc.value)) + msg = 'when state is "absent", parameters are mutually exclusive: cluster_ip_address|node_name' + assert msg in exc.value.args[0]['msg'] diff --git a/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_cluster_peer.py b/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_cluster_peer.py new file mode 100644 index 00000000..690563c6 --- /dev/null +++ b/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_cluster_peer.py @@ -0,0 +1,212 @@ +''' unit tests ONTAP Ansible module: na_ontap_cluster_peer ''' + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type +import json +import pytest + +from ansible_collections.netapp.ontap.tests.unit.compat import unittest +from ansible_collections.netapp.ontap.tests.unit.compat.mock import patch, Mock +from ansible.module_utils import basic +from ansible.module_utils._text import to_bytes +import ansible_collections.netapp.ontap.plugins.module_utils.netapp as netapp_utils + +from ansible_collections.netapp.ontap.plugins.modules.na_ontap_cluster_peer \ + import NetAppONTAPClusterPeer as my_module # module under test + +if not netapp_utils.has_netapp_lib(): + pytestmark = pytest.mark.skip('skipping as missing required netapp_lib') + + +def set_module_args(args): + """prepare arguments so that they will be picked up during module creation""" + args = json.dumps({'ANSIBLE_MODULE_ARGS': args}) + basic._ANSIBLE_ARGS = to_bytes(args) # pylint: disable=protected-access + + +class AnsibleExitJson(Exception): + """Exception class to be raised by module.exit_json and caught by the test case""" + pass + + +class AnsibleFailJson(Exception): + """Exception class to be raised by module.fail_json and caught by the test case""" + pass + + +def exit_json(*args, **kwargs): # pylint: disable=unused-argument + """function to patch over exit_json; package return data into an exception""" + if 'changed' not in kwargs: + kwargs['changed'] = False + raise AnsibleExitJson(kwargs) + + +def fail_json(*args, **kwargs): # pylint: disable=unused-argument + """function to patch over fail_json; package return data into an exception""" + kwargs['failed'] = True + raise AnsibleFailJson(kwargs) + + +class MockONTAPConnection(object): + ''' mock server connection to ONTAP host ''' + + def __init__(self, kind=None, parm1=None): + ''' save arguments ''' + self.type = kind + self.data = parm1 + self.xml_in = None + self.xml_out = None + + def invoke_successfully(self, xml, enable_tunneling): # pylint: disable=unused-argument + ''' mock invoke_successfully returning xml data ''' + self.xml_in = xml + if self.type == 'cluster_peer': + xml = self.build_cluster_peer_info(self.data) + self.xml_out = xml + return xml + + @staticmethod + def build_cluster_peer_info(parm1): + ''' build xml data for vserser-info ''' + xml = netapp_utils.zapi.NaElement('xml') + attributes = { + 'num-records': 1, + 'attributes-list': { + 'cluster-peer-info': { + 'cluster-name': parm1['dest_cluster_name'], + 'peer-addresses': parm1['dest_intercluster_lifs'] + } + } + } + xml.translate_struct(attributes) + return xml + + +class TestMyModule(unittest.TestCase): + ''' a group of related Unit Tests ''' + + def setUp(self): + self.mock_module_helper = patch.multiple(basic.AnsibleModule, + exit_json=exit_json, + fail_json=fail_json) + self.mock_module_helper.start() + self.addCleanup(self.mock_module_helper.stop) + self.server = MockONTAPConnection() + self.mock_cluster_peer = { + 'source_intercluster_lifs': '1.2.3.4,1.2.3.5', + 'dest_intercluster_lifs': '1.2.3.6,1.2.3.7', + 'passphrase': 'netapp123', + 'dest_hostname': '10.20.30.40', + 'dest_cluster_name': 'cluster2', + 'hostname': 'hostname', + 'username': 'username', + 'password': 'password', + + } + + def mock_args(self): + return { + 'source_intercluster_lifs': self.mock_cluster_peer['source_intercluster_lifs'], + 'dest_intercluster_lifs': self.mock_cluster_peer['dest_intercluster_lifs'], + 'passphrase': self.mock_cluster_peer['passphrase'], + 'dest_hostname': self.mock_cluster_peer['dest_hostname'], + 'dest_cluster_name': 'cluster2', + 'hostname': 'hostname', + 'username': 'username', + 'password': 'password', + } + + def get_cluster_peer_mock_object(self, kind=None): + """ + Helper method to return an na_ontap_cluster_peer object + :param kind: passes this param to MockONTAPConnection() + :return: na_ontap_cluster_peer object + """ + cluster_peer_obj = my_module() + cluster_peer_obj.asup_log_for_cserver = Mock(return_value=None) + cluster_peer_obj.cluster = Mock() + cluster_peer_obj.cluster.invoke_successfully = Mock() + if kind is None: + cluster_peer_obj.server = MockONTAPConnection() + cluster_peer_obj.dest_server = MockONTAPConnection() + else: + cluster_peer_obj.server = MockONTAPConnection(kind=kind, parm1=self.mock_cluster_peer) + cluster_peer_obj.dest_server = MockONTAPConnection(kind=kind, parm1=self.mock_cluster_peer) + return cluster_peer_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({}) + my_module() + print('Info: %s' % exc.value.args[0]['msg']) + + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_cluster_peer.NetAppONTAPClusterPeer.cluster_peer_get') + def test_successful_create(self, cluster_peer_get): + ''' Test successful create ''' + set_module_args(self.mock_args()) + cluster_peer_get.side_effect = [ + None, + None + ] + with pytest.raises(AnsibleExitJson) as exc: + self.get_cluster_peer_mock_object().apply() + assert exc.value.args[0]['changed'] + + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_cluster_peer.NetAppONTAPClusterPeer.cluster_peer_get') + def test_create_idempotency(self, cluster_peer_get): + ''' Test create idempotency ''' + set_module_args(self.mock_args()) + current1 = { + 'cluster_name': 'cluster1', + 'peer-addresses': '1.2.3.6,1.2.3.7' + } + current2 = { + 'cluster_name': 'cluster2', + 'peer-addresses': '1.2.3.4,1.2.3.5' + } + cluster_peer_get.side_effect = [ + current1, + current2 + ] + with pytest.raises(AnsibleExitJson) as exc: + self.get_cluster_peer_mock_object('cluster_peer').apply() + assert not exc.value.args[0]['changed'] + + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_cluster_peer.NetAppONTAPClusterPeer.cluster_peer_get') + def test_successful_delete(self, cluster_peer_get): + ''' Test delete existing cluster peer ''' + data = self.mock_args() + data['state'] = 'absent' + data['source_cluster_name'] = 'cluster1' + set_module_args(data) + current1 = { + 'cluster_name': 'cluster1', + 'peer-addresses': '1.2.3.6,1.2.3.7' + } + current2 = { + 'cluster_name': 'cluster2', + 'peer-addresses': '1.2.3.4,1.2.3.5' + } + cluster_peer_get.side_effect = [ + current1, + current2 + ] + with pytest.raises(AnsibleExitJson) as exc: + self.get_cluster_peer_mock_object('cluster_peer').apply() + assert exc.value.args[0]['changed'] + + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_cluster_peer.NetAppONTAPClusterPeer.cluster_peer_get') + def test_delete_idempotency(self, cluster_peer_get): + ''' Test delete idempotency ''' + data = self.mock_args() + data['state'] = 'absent' + data['source_cluster_name'] = 'cluster2' + set_module_args(data) + cluster_peer_get.side_effect = [ + None, + None + ] + with pytest.raises(AnsibleExitJson) as exc: + self.get_cluster_peer_mock_object().apply() + assert not exc.value.args[0]['changed'] diff --git a/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_command.py b/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_command.py new file mode 100644 index 00000000..bd13094b --- /dev/null +++ b/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_command.py @@ -0,0 +1,205 @@ +# (c) 2018, NetApp, Inc +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +''' unit test for ONTAP Command Ansible module ''' + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type +import json +import pytest + +from ansible_collections.netapp.ontap.tests.unit.compat import unittest +from ansible_collections.netapp.ontap.tests.unit.compat.mock import patch, Mock +from ansible.module_utils import basic +from ansible.module_utils._text import to_bytes +import ansible_collections.netapp.ontap.plugins.module_utils.netapp as netapp_utils + +from ansible_collections.netapp.ontap.plugins.modules.na_ontap_command \ + import NetAppONTAPCommand as my_module # module under test + +if not netapp_utils.has_netapp_lib(): + pytestmark = pytest.mark.skip('skipping as missing required netapp_lib') + + +def set_module_args(args): + """prepare arguments so that they will be picked up during module creation""" + args = json.dumps({'ANSIBLE_MODULE_ARGS': args}) + basic._ANSIBLE_ARGS = to_bytes(args) # pylint: disable=protected-access + + +class AnsibleExitJson(Exception): + """Exception class to be raised by module.exit_json and caught by the test case""" + pass + + +class AnsibleFailJson(Exception): + """Exception class to be raised by module.fail_json and caught by the test case""" + pass + + +def exit_json(*args, **kwargs): # pylint: disable=unused-argument + """function to patch over exit_json; package return data into an exception""" + if 'changed' not in kwargs: + kwargs['changed'] = False + raise AnsibleExitJson(kwargs) + + +def fail_json(*args, **kwargs): # pylint: disable=unused-argument + """function to patch over fail_json; package return data into an exception""" + kwargs['failed'] = True + raise AnsibleFailJson(kwargs) + + +class MockONTAPConnection(object): + ''' mock server connection to ONTAP host ''' + + def __init__(self, kind=None, parm1=None): + ''' save arguments ''' + self.type = kind + self.parm1 = parm1 + self.xml_in = None + self.xml_out = None + + def invoke_successfully(self, xml, enable_tunneling): # pylint: disable=unused-argument + ''' mock invoke_successfully returning xml data ''' + self.xml_in = xml + # print(xml.to_string()) + + if self.type == 'version': + priv = xml.get_child_content('priv') + xml = self.build_version(priv, self.parm1) + + self.xml_out = xml + return xml + + @staticmethod + def build_version(priv, result): + ''' build xml data for version ''' + prefix = 'NetApp Release' + if priv == 'advanced': + prefix = '\n' + prefix + xml = netapp_utils.zapi.NaElement('results') + xml.add_attr('status', 'status_ok') + xml.add_new_child('cli-output', prefix) + if result == "u'77'": + xml.add_new_child('cli-result-value', u'77') + elif result == "b'77'": + xml.add_new_child('cli-result-value', b'77') + else: + xml.add_new_child('cli-result-value', b'7' if result is None else result) + # print('XML ut:', xml.to_string()) + return xml + + +class TestMyModule(unittest.TestCase): + ''' a group of related Unit Tests ''' + + def setUp(self): + self.mock_module_helper = patch.multiple(basic.AnsibleModule, + exit_json=exit_json, + fail_json=fail_json) + self.mock_module_helper.start() + self.addCleanup(self.mock_module_helper.stop) + self.server = MockONTAPConnection(kind='version') + # whether to use a mock or a simulator + self.use_vsim = False + + def test_module_fail_when_required_args_missing(self): + ''' required arguments are reported as errors ''' + with pytest.raises(AnsibleFailJson) as exc: + set_module_args({}) + my_module() + print('Info: %s' % exc.value.args[0]['msg']) + + @staticmethod + def set_default_args(vsim=False): + ''' populate hostname/username/password ''' + if vsim: + hostname = '10.10.10.10' + username = 'admin' + password = 'admin' + else: + hostname = 'hostname' + username = 'username' + password = 'password' + return dict({ + 'hostname': hostname, + 'username': username, + 'password': password, + 'https': True, + 'validate_certs': False + }) + + def call_command(self, module_args, vsim=False): + ''' utility function to call apply ''' + module_args.update(self.set_default_args(vsim=vsim)) + set_module_args(module_args) + my_obj = my_module() + my_obj.asup_log_for_cserver = Mock(return_value=None) + if not vsim: + # mock the connection + my_obj.server = self.server + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + msg = exc.value.args[0]['msg'] + return msg + + def test_default_priv(self): + ''' make sure privilege is not required ''' + module_args = { + 'command': 'version', + } + msg = self.call_command(module_args, vsim=self.use_vsim) + needle = b'<cli-output>NetApp Release' + assert needle in msg + print('Version (raw): %s' % msg) + + def test_admin_priv(self): + ''' make sure admin is accepted ''' + module_args = { + 'command': 'version', + 'privilege': 'admin', + } + msg = self.call_command(module_args, vsim=self.use_vsim) + needle = b'<cli-output>NetApp Release' + assert needle in msg + print('Version (raw): %s' % msg) + + def test_advanced_priv(self): + ''' make sure advanced is not required ''' + module_args = { + 'command': 'version', + 'privilege': 'advanced', + } + msg = self.call_command(module_args, vsim=self.use_vsim) + # Interestingly, the ZAPI returns a slightly different response + needle = b'<cli-output>\nNetApp Release' + assert needle in msg + print('Version (raw): %s' % msg) + + def get_dict_output(self, result): + ''' get result value after calling command module ''' + print('In:', result) + module_args = { + 'command': 'version', + 'return_dict': 'true', + } + self.server = MockONTAPConnection(kind='version', parm1=result) + dict_output = self.call_command(module_args, vsim=self.use_vsim) + print('dict_output: %s' % repr(dict_output)) + return dict_output['result_value'] + + def test_dict_output_77(self): + ''' make sure correct value is returned ''' + result = '77' + assert self.get_dict_output(result) == int(result) + + def test_dict_output_b77(self): + ''' make sure correct value is returned ''' + result = b'77' + assert self.get_dict_output(result) == int(result) + + def test_dict_output_u77(self): + ''' make sure correct value is returned ''' + result = "u'77'" + assert self.get_dict_output(result) == int(eval(result)) diff --git a/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_dns.py b/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_dns.py new file mode 100644 index 00000000..b514742b --- /dev/null +++ b/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_dns.py @@ -0,0 +1,321 @@ +# (c) 2018, NetApp, Inc +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +''' unit tests for Ansible module: na_ontap_dns''' + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type +import json +import pytest + +from ansible.module_utils import basic +from ansible.module_utils._text import to_bytes +from ansible_collections.netapp.ontap.tests.unit.compat import unittest +from ansible_collections.netapp.ontap.tests.unit.compat.mock import patch +import ansible_collections.netapp.ontap.plugins.module_utils.netapp as netapp_utils + +from ansible_collections.netapp.ontap.plugins.modules.na_ontap_dns \ + import NetAppOntapDns as dns_module # module under test + +if not netapp_utils.has_netapp_lib(): + pytestmark = pytest.mark.skip('skipping as missing required netapp_lib') +HAS_NETAPP_ZAPI_MSG = "pip install netapp_lib is required" + + +# REST API canned responses when mocking send_request +SRR = { + # common responses + 'is_rest': (200, {}, None), + 'is_zapi': (400, {}, "Unreachable"), + 'empty_good': (200, {}, None), + 'end_of_sequence': (500, None, "Unexpected call to send_request"), + 'generic_error': (400, None, "Expected error"), + 'dns_record': (200, {"records": [{"domains": ['test.com'], + "servers": ['0.0.0.0'], + "svm": {"name": "svm1", "uuid": "02c9e252-41be-11e9-81d5-00a0986138f7"}}]}, None), + 'cluster_data': (200, {"dns_domains": ['test.com'], + "name_servers": ['0.0.0.0'], + "name": "cserver", + "uuid": "C2c9e252-41be-11e9-81d5-00a0986138f7"}, None), + 'cluster_name': (200, {"name": "cserver"}, None) +} + + +def set_module_args(args): + """prepare arguments so that they will be picked up during module creation""" + args = json.dumps({'ANSIBLE_MODULE_ARGS': args}) + basic._ANSIBLE_ARGS = to_bytes(args) # pylint: disable=protected-access + + +class AnsibleExitJson(Exception): + """Exception class to be raised by module.exit_json and caught by the test case""" + + +class AnsibleFailJson(Exception): + """Exception class to be raised by module.fail_json and caught by the test case""" + + +def exit_json(*args, **kwargs): # pylint: disable=unused-argument + """function to patch over exit_json; package return data into an exception""" + if 'changed' not in kwargs: + kwargs['changed'] = False + raise AnsibleExitJson(kwargs) + + +def fail_json(*args, **kwargs): # pylint: disable=unused-argument + """function to patch over fail_json; package return data into an exception""" + kwargs['failed'] = True + raise AnsibleFailJson(kwargs) + + +class MockONTAPConnection(object): + ''' mock server connection to ONTAP host ''' + + def __init__(self, kind=None, data=None): + ''' save arguments ''' + self.kind = kind + self.params = data + self.xml_in = None + self.xml_out = None + + def invoke_successfully(self, xml, enable_tunneling): # pylint: disable=unused-argument + ''' mock invoke_successfully returning xml data ''' + self.xml_in = xml + request = xml.to_string().decode('utf-8') + if request.startswith("<ems-autosupport-log>"): + xml = None # or something that may the logger happy, and you don't need @patch anymore + # or + # xml = build_ems_log_response() + elif request == "<net-dns-get/>": + if self.kind == 'create': + raise netapp_utils.zapi.NaApiError(code="15661") + else: + xml = self.build_dns_status_info() + elif request.startswith("<net-dns-create>"): + xml = self.build_dns_status_info() + if self.kind == 'enable': + xml = self.build_dns_status_info() + self.xml_out = xml + return xml + + @staticmethod + def build_dns_status_info(): + xml = netapp_utils.zapi.NaElement('xml') + nameservers = [{'ip-address': '0.0.0.0'}] + domains = [{'string': 'test.com'}] + attributes = {'num-records': 1, + 'attributes': {'net-dns-info': {'name-servers': nameservers, + 'domains': domains, + 'skip-config-validation': 'false'}}} + xml.translate_struct(attributes) + return xml + + +class TestMyModule(unittest.TestCase): + ''' Unit tests for na_ontap_job_schedule ''' + + def setUp(self): + self.mock_module_helper = patch.multiple(basic.AnsibleModule, + exit_json=exit_json, + fail_json=fail_json) + self.mock_module_helper.start() + self.addCleanup(self.mock_module_helper.stop) + + def mock_args(self): + return { + 'state': 'present', + 'vserver': 'vserver', + 'nameservers': ['0.0.0.0'], + 'domains': ['test.com'], + 'hostname': 'test', + 'username': 'test_user', + 'password': 'test_pass!' + } + + def get_dns_mock_object(self, cx_type='zapi', kind=None, status=None): + dns_obj = dns_module() + if cx_type == 'zapi': + if kind is None: + dns_obj.server = MockONTAPConnection() + else: + dns_obj.server = MockONTAPConnection(kind=kind, data=status) + return dns_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({}) + dns_module() + print('Info: %s' % exc.value.args[0]['msg']) + + def test_idempotent_modify_dns(self): + data = self.mock_args() + data['use_rest'] = 'never' + set_module_args(data) + with pytest.raises(AnsibleExitJson) as exc: + self.get_dns_mock_object('zapi', 'enable', 'false').apply() + assert not exc.value.args[0]['changed'] + + def test_successfully_modify_dns(self): + data = self.mock_args() + data['domains'] = ['new_test.com'] + data['use_rest'] = 'never' + set_module_args(data) + with pytest.raises(AnsibleExitJson) as exc: + self.get_dns_mock_object('zapi', 'enable', 'false').apply() + assert exc.value.args[0]['changed'] + + @patch('ansible_collections.netapp.ontap.plugins.module_utils.netapp.ems_log_event') + def test_idempotent_create_dns(self, mock_ems_log_event): + data = self.mock_args() + data['use_rest'] = 'never' + set_module_args(data) + with pytest.raises(AnsibleExitJson) as exc: + self.get_dns_mock_object('zapi').apply() + assert not exc.value.args[0]['changed'] + + @patch('ansible_collections.netapp.ontap.plugins.module_utils.netapp.ems_log_event') + def test_successfully_create_dns(self, mock_ems_log_event): + data = self.mock_args() + print("create dns") + data['domains'] = ['new_test.com'] + data['use_rest'] = 'never' + set_module_args(data) + with pytest.raises(AnsibleExitJson) as exc: + self.get_dns_mock_object('zapi', 'create').apply() + assert exc.value.args[0]['changed'] + + @patch('ansible_collections.netapp.ontap.plugins.module_utils.netapp.OntapRestAPI.send_request') + def test_rest_error(self, mock_request): + data = self.mock_args() + set_module_args(data) + mock_request.side_effect = [ + SRR['is_rest'], + SRR['generic_error'], + SRR['end_of_sequence'] + ] + with pytest.raises(AnsibleFailJson) as exc: + self.get_dns_mock_object(cx_type='rest').apply() + assert exc.value.args[0]['msg'] == SRR['generic_error'][2] + + @patch('ansible_collections.netapp.ontap.plugins.module_utils.netapp.OntapRestAPI.send_request') + def test_rest_successfully_create(self, mock_request): + data = self.mock_args() + set_module_args(data) + mock_request.side_effect = [ + SRR['is_rest'], + SRR['empty_good'], # get + SRR['cluster_data'], # get cluster + SRR['empty_good'], # post + SRR['end_of_sequence'] + ] + with pytest.raises(AnsibleExitJson) as exc: + self.get_dns_mock_object(cx_type='rest').apply() + assert exc.value.args[0]['changed'] + + @patch('ansible_collections.netapp.ontap.plugins.module_utils.netapp.OntapRestAPI.send_request') + def test_rest_successfully_create_is_cluster_vserver(self, mock_request): + data = self.mock_args() + data['vserver'] = 'cvserver' + set_module_args(data) + mock_request.side_effect = [ + SRR['is_rest'], + SRR['empty_good'], # get + SRR['cluster_name'], # get cluster name + SRR['empty_good'], # post + SRR['end_of_sequence'] + ] + with pytest.raises(AnsibleExitJson) as exc: + self.get_dns_mock_object(cx_type='rest').apply() + assert exc.value.args[0]['changed'] + + @patch('ansible_collections.netapp.ontap.plugins.module_utils.netapp.OntapRestAPI.send_request') + def test_rest_idempotent_create_dns(self, mock_request): + data = self.mock_args() + set_module_args(data) + mock_request.side_effect = [ + SRR['is_rest'], + SRR['dns_record'], # get + SRR['end_of_sequence'] + ] + with pytest.raises(AnsibleExitJson) as exc: + self.get_dns_mock_object(cx_type='rest').apply() + assert not exc.value.args[0]['changed'] + + @patch('ansible_collections.netapp.ontap.plugins.module_utils.netapp.OntapRestAPI.send_request') + def test_rest_successfully_destroy(self, mock_request): + data = self.mock_args() + data['state'] = 'absent' + set_module_args(data) + mock_request.side_effect = [ + SRR['is_rest'], + SRR['dns_record'], # get + SRR['empty_good'], # delete + SRR['end_of_sequence'] + ] + with pytest.raises(AnsibleExitJson) as exc: + self.get_dns_mock_object(cx_type='rest').apply() + assert exc.value.args[0]['changed'] + + @patch('ansible_collections.netapp.ontap.plugins.module_utils.netapp.OntapRestAPI.send_request') + def test_rest_idempotently_destroy(self, mock_request): + data = self.mock_args() + data['state'] = 'absent' + set_module_args(data) + mock_request.side_effect = [ + SRR['is_rest'], + SRR['empty_good'], # get + SRR['cluster_data'], # get cluster + SRR['end_of_sequence'] + ] + with pytest.raises(AnsibleExitJson) as exc: + self.get_dns_mock_object(cx_type='rest').apply() + assert not exc.value.args[0]['changed'] + + @patch('ansible_collections.netapp.ontap.plugins.module_utils.netapp.OntapRestAPI.send_request') + def test_rest_successfully_modify(self, mock_request): + data = self.mock_args() + data['state'] = 'present' + set_module_args(data) + mock_request.side_effect = [ + SRR['is_rest'], + SRR['empty_good'], # get + SRR['cluster_data'], # get cluster + SRR['empty_good'], # patch + SRR['end_of_sequence'] + ] + with pytest.raises(AnsibleExitJson) as exc: + self.get_dns_mock_object(cx_type='rest').apply() + assert exc.value.args[0]['changed'] + + @patch('ansible_collections.netapp.ontap.plugins.module_utils.netapp.OntapRestAPI.send_request') + def test_rest_successfully_modify_is_cluster_vserver(self, mock_request): + data = self.mock_args() + data['vserver'] = 'cvserver' + data['state'] = 'present' + data['domains'] = 'new_test.com' + set_module_args(data) + mock_request.side_effect = [ + SRR['is_rest'], + SRR['empty_good'], # get + SRR['cluster_data'], # get cluster data + SRR['empty_good'], # patch + SRR['end_of_sequence'] + ] + with pytest.raises(AnsibleExitJson) as exc: + self.get_dns_mock_object(cx_type='rest').apply() + assert exc.value.args[0]['changed'] + + @patch('ansible_collections.netapp.ontap.plugins.module_utils.netapp.OntapRestAPI.send_request') + def test_rest_idempotently_modify(self, mock_request): + data = self.mock_args() + data['state'] = 'present' + set_module_args(data) + mock_request.side_effect = [ + SRR['is_rest'], + SRR['dns_record'], # get + SRR['end_of_sequence'] + ] + with pytest.raises(AnsibleExitJson) as exc: + self.get_dns_mock_object(cx_type='rest').apply() + assert not exc.value.args[0]['changed'] diff --git a/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_efficiency_policy.py b/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_efficiency_policy.py new file mode 100644 index 00000000..9432ff96 --- /dev/null +++ b/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_efficiency_policy.py @@ -0,0 +1,232 @@ +# (c) 2018, NetApp, Inc +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +''' unit tests for Ansible module: na_ontap_vscan_scanner_pool ''' + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type +import json +import pytest + +from ansible_collections.netapp.ontap.tests.unit.compat import unittest +from ansible_collections.netapp.ontap.tests.unit.compat.mock import patch, Mock +from ansible.module_utils import basic +from ansible.module_utils._text import to_bytes +import ansible_collections.netapp.ontap.plugins.module_utils.netapp as netapp_utils + +from ansible_collections.netapp.ontap.plugins.modules.na_ontap_efficiency_policy \ + import NetAppOntapEfficiencyPolicy as efficiency_module # module under test + +if not netapp_utils.has_netapp_lib(): + pytestmark = pytest.mark.skip('skipping as missing required netapp_lib') + + +def set_module_args(args): + """prepare arguments so that they will be picked up during module creation""" + args = json.dumps({'ANSIBLE_MODULE_ARGS': args}) + basic._ANSIBLE_ARGS = to_bytes(args) # pylint: disable=protected-access + + +class AnsibleExitJson(Exception): + """Exception class to be raised by module.exit_json and caught by the test case""" + pass + + +class AnsibleFailJson(Exception): + """Exception class to be raised by module.fail_json and caught by the test case""" + pass + + +def exit_json(*args, **kwargs): # pylint: disable=unused-argument + """function to patch over exit_json; package return data into an exception""" + if 'changed' not in kwargs: + kwargs['changed'] = False + raise AnsibleExitJson(kwargs) + + +def fail_json(*args, **kwargs): # pylint: disable=unused-argument + """function to patch over fail_json; package return data into an exception""" + kwargs['failed'] = True + raise AnsibleFailJson(kwargs) + + +class MockONTAPConnection(object): + ''' mock server connection to ONTAP host ''' + + def __init__(self, kind=None, data=None): + ''' save arguments ''' + self.kind = kind + self.params = data + self.xml_in = None + self.xml_out = None + + def invoke_successfully(self, xml, enable_tunneling): # pylint: disable=unused-argument + ''' mock invoke_successfully returning xml data ''' + self.xml_in = xml + if self.kind == 'threshold': + xml = self.build_threshold_info(self.params) + elif self.kind == 'scheduled': + xml = self.build_schedule_info(self.params) + self.xml_out = xml + return xml + + @staticmethod + def build_threshold_info(details): + xml = netapp_utils.zapi.NaElement('xml') + attributes = {'num-records': 1, + 'attributes-list': { + 'sis-policy-info': { + 'changelog-threshold-percent': 10, + 'comment': details['comment'], + 'enabled': 'true', + 'policy-name': details['policy_name'], + 'policy-type': 'threshold', + 'qos-policy': details['qos_policy'], + 'vserver': details['vserver'] + } + } + } + xml.translate_struct(attributes) + return xml + + @staticmethod + def build_schedule_info(details): + xml = netapp_utils.zapi.NaElement('xml') + attributes = {'num-records': 1, + 'attributes-list': { + 'sis-policy-info': { + 'comment': details['comment'], + 'duration': 10, + 'enabled': 'true', + 'policy-name': details['policy_name'], + 'policy-type': 'scheduled', + 'qos-policy': details['qos_policy'], + 'vserver': details['vserver'] + } + } + } + xml.translate_struct(attributes) + return xml + + +class TestMyModule(unittest.TestCase): + ''' Unit tests for na_ontap_efficiency_policy ''' + + def setUp(self): + self.mock_module_helper = patch.multiple(basic.AnsibleModule, + exit_json=exit_json, + fail_json=fail_json) + self.mock_module_helper.start() + self.addCleanup(self.mock_module_helper.stop) + self.mock_efficiency_policy = { + 'state': 'present', + 'vserver': 'test_vserver', + 'policy_name': 'test_policy', + 'comment': 'This policy is for x and y', + 'enabled': True, + 'qos_policy': 'background' + } + + def mock_args(self): + return { + 'state': self.mock_efficiency_policy['state'], + 'vserver': self.mock_efficiency_policy['vserver'], + 'policy_name': self.mock_efficiency_policy['policy_name'], + 'comment': self.mock_efficiency_policy['comment'], + 'enabled': self.mock_efficiency_policy['enabled'], + 'qos_policy': self.mock_efficiency_policy['qos_policy'], + 'hostname': 'test', + 'username': 'test_user', + 'password': 'test_pass!' + } + + def get_efficiency_mock_object(self, kind=None): + efficiency_obj = efficiency_module() + if kind is None: + efficiency_obj.server = MockONTAPConnection() + elif kind == 'threshold': + efficiency_obj.server = MockONTAPConnection(kind='threshold', data=self.mock_efficiency_policy) + elif kind == 'scheduled': + efficiency_obj.server = MockONTAPConnection(kind='scheduled', data=self.mock_efficiency_policy) + return efficiency_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({}) + efficiency_module() + print('Info: %s' % exc.value.args[0]['msg']) + + def test_get_nonexistent_efficiency_policy(self): + set_module_args(self.mock_args()) + result = self.get_efficiency_mock_object().get_efficiency_policy() + assert not result + + def test_get_existing_scanner(self): + set_module_args(self.mock_args()) + result = self.get_efficiency_mock_object('threshold').get_efficiency_policy() + assert result + + def test_successfully_create(self): + data = self.mock_args() + data['policy_type'] = 'threshold' + set_module_args(data) + with pytest.raises(AnsibleExitJson) as exc: + self.get_efficiency_mock_object().apply() + assert exc.value.args[0]['changed'] + + def test_create_idempotency(self): + data = self.mock_args() + data['policy_type'] = 'threshold' + set_module_args(data) + with pytest.raises(AnsibleExitJson) as exc: + self.get_efficiency_mock_object('threshold').apply() + assert not exc.value.args[0]['changed'] + + def test_threshold_duration_failure(self): + data = self.mock_args() + data['duration'] = 1 + set_module_args(data) + with pytest.raises(AnsibleFailJson) as exc: + self.get_efficiency_mock_object('threshold').apply() + assert exc.value.args[0]['msg'] == "duration cannot be set if policy_type is threshold" + + def test_threshold_schedule_failure(self): + data = self.mock_args() + data['schedule'] = 'test_job_schedule' + set_module_args(data) + with pytest.raises(AnsibleFailJson) as exc: + self.get_efficiency_mock_object('threshold').apply() + assert exc.value.args[0]['msg'] == "schedule cannot be set if policy_type is threshold" + + def test_scheduled_threshold_percent_failure(self): + data = self.mock_args() + data['changelog_threshold_percent'] = 30 + set_module_args(data) + with pytest.raises(AnsibleFailJson) as exc: + self.get_efficiency_mock_object('scheduled').apply() + assert exc.value.args[0]['msg'] == "changelog_threshold_percent cannot be set if policy_type is scheduled" + + def test_successfully_delete(self): + data = self.mock_args() + data['state'] = 'absent' + set_module_args(data) + with pytest.raises(AnsibleExitJson) as exc: + self.get_efficiency_mock_object('threshold').apply() + assert exc.value.args[0]['changed'] + + def test_delete_idempotency(self): + data = self.mock_args() + data['state'] = 'absent' + set_module_args(data) + with pytest.raises(AnsibleExitJson) as exc: + self.get_efficiency_mock_object().apply() + assert not exc.value.args[0]['changed'] + + def test_successful_modify(self): + data = self.mock_args() + data['policy_type'] = 'threshold' + set_module_args(data) + with pytest.raises(AnsibleExitJson) as exc: + self.get_efficiency_mock_object('scheduled').apply() + assert exc.value.args[0]['changed'] diff --git a/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_export_policy.py b/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_export_policy.py new file mode 100644 index 00000000..04620856 --- /dev/null +++ b/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_export_policy.py @@ -0,0 +1,289 @@ +# (c) 2019, NetApp, Inc +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +''' unit tests for Ansible module: na_ontap_volume_export_policy ''' + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type +import json +import pytest + +from ansible.module_utils import basic +from ansible.module_utils._text import to_bytes +from ansible_collections.netapp.ontap.tests.unit.compat import unittest +from ansible_collections.netapp.ontap.tests.unit.compat.mock import patch +import ansible_collections.netapp.ontap.plugins.module_utils.netapp as netapp_utils + +from ansible_collections.netapp.ontap.plugins.modules.na_ontap_export_policy \ + import NetAppONTAPExportPolicy as policy_module # module under test + +if not netapp_utils.has_netapp_lib(): + pytestmark = pytest.mark.skip('skipping as missing required netapp_lib') + +# REST API canned responses when mocking send_request +SRR = { + # common responses + 'is_rest': (200, {}, None), + 'is_zapi': (400, {}, "Unreachable"), + 'empty_good': (200, {}, None), + 'end_of_sequence': (500, None, "Unexpected call to send_request"), + 'generic_error': (400, None, "Expected error"), + # module specific responses + 'get_uuid_policy_id_export_policy': ( + 200, + { + "records": [{ + "svm": { + "uuid": "uuid", + "name": "svm"}, + "id": 123, + "name": "ansible" + }], + "num_records": 1}, None), + "no_record": ( + 200, + {"num_records": 0}, + None) +} + + +def set_module_args(args): + """prepare arguments so that they will be picked up during module creation""" + args = json.dumps({'ANSIBLE_MODULE_ARGS': args}) + basic._ANSIBLE_ARGS = to_bytes(args) # pylint: disable=protected-access + + +class AnsibleExitJson(Exception): + """Exception class to be raised by module.exit_json and caught by the test case""" + + +class AnsibleFailJson(Exception): + """Exception class to be raised by module.fail_json and caught by the test case""" + + +def exit_json(*args, **kwargs): # pylint: disable=unused-argument + """function to patch over exit_json; package return data into an exception""" + if 'changed' not in kwargs: + kwargs['changed'] = False + raise AnsibleExitJson(kwargs) + + +def fail_json(*args, **kwargs): # pylint: disable=unused-argument + """function to patch over fail_json; package return data into an exception""" + kwargs['failed'] = True + raise AnsibleFailJson(kwargs) + + +class MockONTAPConnection(object): + ''' mock server connection to ONTAP host ''' + + def __init__(self, kind=None, data=None): + ''' save arguments ''' + self.kind = kind + self.params = data + self.xml_in = None + self.xml_out = None + + def invoke_successfully(self, xml, enable_tunneling): # pylint: disable=unused-argument + ''' mock invoke_successfully returning xml data ''' + self.xml_in = xml + if self.kind == 'export_policy': + xml = self.build_export_policy_info(self.params) + self.xml_out = xml + return xml + + @staticmethod + def build_export_policy_info(export_policy_details): + xml = netapp_utils.zapi.NaElement('xml') + data = {'num-records': 1, + 'attributes-list': {'export-policy-info': {'name': export_policy_details['name'] + }}} + xml.translate_struct(data) + return xml + + +class TestMyModule(unittest.TestCase): + ''' Unit tests for na_ontap_job_schedule ''' + + def setUp(self): + self.mock_module_helper = patch.multiple(basic.AnsibleModule, + exit_json=exit_json, + fail_json=fail_json) + self.mock_module_helper.start() + self.addCleanup(self.mock_module_helper.stop) + self.mock_export_policy = { + 'name': 'test_policy', + 'vserver': 'test_vserver' + } + + def mock_args(self, rest=False): + if rest: + return { + 'vserver': self.mock_export_policy['vserver'], + 'name': self.mock_export_policy['name'], + 'hostname': 'test', + 'username': 'test_user', + 'password': 'test_pass!' + } + else: + return { + 'vserver': self.mock_export_policy['vserver'], + 'name': self.mock_export_policy['name'], + 'hostname': 'test', + 'username': 'test_user', + 'password': 'test_pass!', + 'use_rest': 'never' + } + + def get_export_policy_mock_object(self, cx_type='zapi', kind=None): + policy_obj = policy_module() + if cx_type == 'zapi': + if kind is None: + policy_obj.server = MockONTAPConnection() + elif kind == 'export_policy': + policy_obj.server = MockONTAPConnection(kind='export_policy', data=self.mock_export_policy) + return policy_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({}) + policy_module() + print('Info: %s' % exc.value.args[0]['msg']) + + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_export_policy.NetAppONTAPExportPolicy.create_export_policy') + def test_successful_create(self, create_export_policy): + ''' Test successful create ''' + data = self.mock_args() + set_module_args(data) + with pytest.raises(AnsibleExitJson) as exc: + self.get_export_policy_mock_object().apply() + assert exc.value.args[0]['changed'] + create_export_policy.assert_called_with(uuid=None) + + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_export_policy.NetAppONTAPExportPolicy.get_export_policy') + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_export_policy.NetAppONTAPExportPolicy.rename_export_policy') + def test_successful_rename(self, rename_export_policy, get_export_policy): + ''' Test successful rename ''' + data = self.mock_args() + data['from_name'] = 'old_policy' + set_module_args(data) + get_export_policy.side_effect = [ + None, + {'policy-name': 'old_policy'} + ] + with pytest.raises(AnsibleExitJson) as exc: + self.get_export_policy_mock_object().apply() + assert exc.value.args[0]['changed'] + rename_export_policy.assert_called_with(policy_id=None) + + @patch('ansible_collections.netapp.ontap.plugins.module_utils.netapp.OntapRestAPI.send_request') + def test_rest_successful_create(self, mock_request): + '''Test successful rest create''' + data = self.mock_args(rest=True) + set_module_args(data) + mock_request.side_effect = [ + SRR['is_rest'], + SRR['get_uuid_policy_id_export_policy'], + SRR['get_uuid_policy_id_export_policy'], + SRR['no_record'], + SRR['empty_good'], + SRR['end_of_sequence'] + ] + with pytest.raises(AnsibleExitJson) as exc: + self.get_export_policy_mock_object(cx_type='rest').apply() + assert exc.value.args[0]['changed'] + + @patch('ansible_collections.netapp.ontap.plugins.module_utils.netapp.OntapRestAPI.send_request') + def test_rest_successful_delete(self, mock_request): + '''Test successful rest delete''' + data = self.mock_args(rest=True) + data['state'] = 'absent' + set_module_args(data) + mock_request.side_effect = [ + SRR['is_rest'], + SRR['get_uuid_policy_id_export_policy'], + SRR['get_uuid_policy_id_export_policy'], + SRR['get_uuid_policy_id_export_policy'], + SRR['empty_good'], + SRR['end_of_sequence'] + ] + with pytest.raises(AnsibleExitJson) as exc: + self.get_export_policy_mock_object(cx_type='rest').apply() + assert exc.value.args[0]['changed'] + + @patch('ansible_collections.netapp.ontap.plugins.module_utils.netapp.OntapRestAPI.send_request') + def test_rest_successful_rename(self, mock_request): + '''Test successful rest rename''' + data = self.mock_args(rest=True) + data['from_name'] = 'ansible' + set_module_args(data) + mock_request.side_effect = [ + SRR['is_rest'], + SRR['get_uuid_policy_id_export_policy'], + SRR['get_uuid_policy_id_export_policy'], + SRR['no_record'], + SRR['get_uuid_policy_id_export_policy'], + SRR['empty_good'], + SRR['end_of_sequence'] + ] + with pytest.raises(AnsibleExitJson) as exc: + self.get_export_policy_mock_object(cx_type='rest').apply() + assert exc.value.args[0]['changed'] + + @patch('ansible_collections.netapp.ontap.plugins.module_utils.netapp.OntapRestAPI.send_request') + def test_rest_error_create(self, mock_request): + '''Test error rest create''' + data = self.mock_args(rest=True) + set_module_args(data) + mock_request.side_effect = [ + SRR['is_rest'], + SRR['get_uuid_policy_id_export_policy'], + SRR['get_uuid_policy_id_export_policy'], + SRR['no_record'], + SRR['generic_error'], + SRR['empty_good'], + SRR['end_of_sequence'] + ] + with pytest.raises(AnsibleFailJson) as exc: + self.get_export_policy_mock_object(cx_type='rest').apply() + assert 'Error on creating export policy: Expected error' in exc.value.args[0]['msg'] + + @patch('ansible_collections.netapp.ontap.plugins.module_utils.netapp.OntapRestAPI.send_request') + def test_rest_error_delete(self, mock_request): + '''Test error rest delete''' + data = self.mock_args(rest=True) + data['state'] = 'absent' + set_module_args(data) + mock_request.side_effect = [ + SRR['is_rest'], + SRR['get_uuid_policy_id_export_policy'], + SRR['get_uuid_policy_id_export_policy'], + SRR['get_uuid_policy_id_export_policy'], + SRR['generic_error'], + SRR['empty_good'], + SRR['end_of_sequence'] + ] + with pytest.raises(AnsibleFailJson) as exc: + self.get_export_policy_mock_object(cx_type='rest').apply() + assert 'Error on deleting export policy: Expected error' in exc.value.args[0]['msg'] + + @patch('ansible_collections.netapp.ontap.plugins.module_utils.netapp.OntapRestAPI.send_request') + def test_rest_error_rename(self, mock_request): + '''Test error rest rename''' + data = self.mock_args(rest=True) + data['from_name'] = 'ansible' + set_module_args(data) + mock_request.side_effect = [ + SRR['is_rest'], + SRR['get_uuid_policy_id_export_policy'], + SRR['get_uuid_policy_id_export_policy'], + SRR['no_record'], + SRR['get_uuid_policy_id_export_policy'], + SRR['generic_error'], + SRR['empty_good'], + SRR['end_of_sequence'] + ] + with pytest.raises(AnsibleFailJson) as exc: + self.get_export_policy_mock_object(cx_type='rest').apply() + assert 'Error on renaming export policy: Expected error' in exc.value.args[0]['msg'] diff --git a/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_export_policy_rule.py b/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_export_policy_rule.py new file mode 100644 index 00000000..016dd68a --- /dev/null +++ b/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_export_policy_rule.py @@ -0,0 +1,269 @@ +# (c) 2018, NetApp, Inc +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +''' unit test template for ONTAP Ansible module ''' + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type +import json +import pytest + +from ansible_collections.netapp.ontap.tests.unit.compat import unittest +from ansible_collections.netapp.ontap.tests.unit.compat.mock import patch, Mock +from ansible.module_utils import basic +from ansible.module_utils._text import to_bytes +import ansible_collections.netapp.ontap.plugins.module_utils.netapp as netapp_utils + +from ansible_collections.netapp.ontap.plugins.modules.na_ontap_export_policy_rule \ + import NetAppontapExportRule as policy_rule # module under test + +if not netapp_utils.has_netapp_lib(): + pytestmark = pytest.mark.skip('skipping as missing required netapp_lib') + + +def set_module_args(args): + """prepare arguments so that they will be picked up during module creation""" + args = json.dumps({'ANSIBLE_MODULE_ARGS': args}) + basic._ANSIBLE_ARGS = to_bytes(args) # pylint: disable=protected-access + + +class AnsibleExitJson(Exception): + """Exception class to be raised by module.exit_json and caught by the test case""" + pass + + +class AnsibleFailJson(Exception): + """Exception class to be raised by module.fail_json and caught by the test case""" + pass + + +def exit_json(*args, **kwargs): # pylint: disable=unused-argument + """function to patch over exit_json; package return data into an exception""" + if 'changed' not in kwargs: + kwargs['changed'] = False + raise AnsibleExitJson(kwargs) + + +def fail_json(*args, **kwargs): # pylint: disable=unused-argument + """function to patch over fail_json; package return data into an exception""" + kwargs['failed'] = True + raise AnsibleFailJson(kwargs) + + +class MockONTAPConnection(object): + ''' mock server connection to ONTAP host ''' + + def __init__(self, kind=None, data=None): + ''' save arguments ''' + self.kind = kind + self.data = data + self.xml_in = None + self.xml_out = None + + def invoke_successfully(self, xml, enable_tunneling): # pylint: disable=unused-argument + ''' mock invoke_successfully returning xml data ''' + self.xml_in = xml + if self.kind == 'rule': + xml = self.build_policy_rule(self.data) + if self.kind == 'rules': + xml = self.build_policy_rule(self.data, multiple=True) + if self.kind == 'policy': + xml = self.build_policy() + self.xml_out = xml + return xml + + @staticmethod + def build_policy_rule(policy, multiple=False): + ''' build xml data for export-rule-info ''' + xml = netapp_utils.zapi.NaElement('xml') + attributes = {'attributes-list': { + 'export-rule-info': { + 'policy-name': policy['name'], + 'client-match': policy['client_match'], + 'ro-rule': { + 'security-flavor': 'any' + }, + 'rw-rule': { + 'security-flavor': 'any' + }, + 'protocol': { + 'access-protocol': policy['protocol'] + }, + 'super-user-security': { + 'security-flavor': 'any' + }, + 'is-allow-set-uid-enabled': 'false', + 'rule-index': policy['rule_index'], + 'anonymous-user-id': policy['anonymous_user_id'], + + } + }, 'num-records': 2 if multiple is True else 1} + xml.translate_struct(attributes) + return xml + + @staticmethod + def build_policy(): + ''' build xml data for export-policy-get-iter ''' + xml = netapp_utils.zapi.NaElement('xml') + attributes = { + 'num-records': 1, + + } + xml.translate_struct(attributes) + return xml + + +class TestMyModule(unittest.TestCase): + ''' a group of related Unit Tests ''' + + def setUp(self): + self.mock_module_helper = patch.multiple(basic.AnsibleModule, + exit_json=exit_json, + fail_json=fail_json) + self.mock_module_helper.start() + self.addCleanup(self.mock_module_helper.stop) + self.server = MockONTAPConnection() + self.mock_rule = { + 'name': 'test', + 'protocol': 'nfs', + 'client_match': '1.1.1.0', + 'rule_index': 10, + 'anonymous_user_id': '65534' + } + + def mock_rule_args(self): + return { + 'name': self.mock_rule['name'], + 'client_match': self.mock_rule['client_match'], + 'vserver': 'test', + 'protocol': self.mock_rule['protocol'], + 'rule_index': self.mock_rule['rule_index'], + 'anonymous_user_id': self.mock_rule['anonymous_user_id'], + 'ro_rule': 'any', + 'rw_rule': 'any', + 'hostname': 'test', + 'username': 'test_user', + 'password': 'test_pass!' + } + + def get_mock_object(self, kind=None): + """ + Helper method to return an na_ontap_firewall_policy object + :param kind: passes this param to MockONTAPConnection() + :return: na_ontap_firewall_policy object + """ + obj = policy_rule() + obj.autosupport_log = Mock(return_value=None) + if kind is None: + obj.server = MockONTAPConnection() + else: + obj.server = MockONTAPConnection(kind=kind, data=self.mock_rule_args()) + 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({}) + policy_rule() + print('Info: %s' % exc.value.args[0]['msg']) + + def test_get_nonexistent_rule(self): + ''' Test if get_export_policy_rule returns None for non-existent policy ''' + set_module_args(self.mock_rule_args()) + result = self.get_mock_object().get_export_policy_rule() + assert result is None + + def test_get_nonexistent_policy(self): + ''' Test if get_export_policy returns None for non-existent policy ''' + set_module_args(self.mock_rule_args()) + result = self.get_mock_object().get_export_policy() + assert result is None + + def test_get_existing_rule(self): + ''' Test if get_export_policy_rule returns rule details for existing policy ''' + data = self.mock_rule_args() + set_module_args(data) + result = self.get_mock_object('rule').get_export_policy_rule() + assert result['name'] == data['name'] + assert result['client_match'] == data['client_match'] + assert result['ro_rule'] == ['any'] # from build_rule() + + def test_get_existing_policy(self): + ''' Test if get_export_policy returns policy details for existing policy ''' + data = self.mock_rule_args() + set_module_args(data) + result = self.get_mock_object('policy').get_export_policy() + assert result is not None + + def test_create_missing_param_error(self): + ''' Test validation error from create ''' + data = self.mock_rule_args() + del data['ro_rule'] + set_module_args(data) + with pytest.raises(AnsibleFailJson) as exc: + self.get_mock_object().apply() + msg = 'Error: Missing required param for creating export policy rule ro_rule' + assert exc.value.args[0]['msg'] == msg + + def test_successful_create(self): + ''' Test successful create ''' + set_module_args(self.mock_rule_args()) + with pytest.raises(AnsibleExitJson) as exc: + self.get_mock_object().apply() + assert exc.value.args[0]['changed'] + + def test_create_idempotency(self): + ''' Test create idempotency ''' + set_module_args(self.mock_rule_args()) + with pytest.raises(AnsibleExitJson) as exc: + self.get_mock_object('rule').apply() + assert not exc.value.args[0]['changed'] + + def test_successful_delete_without_rule_index(self): + ''' Test delete existing job ''' + data = self.mock_rule_args() + data['state'] = 'absent' + del data['rule_index'] + set_module_args(data) + with pytest.raises(AnsibleExitJson) as exc: + self.get_mock_object('rule').apply() + assert exc.value.args[0]['changed'] + + def test_delete_idempotency(self): + ''' Test delete idempotency ''' + data = self.mock_rule_args() + data['state'] = 'absent' + set_module_args(data) + with pytest.raises(AnsibleExitJson) as exc: + self.get_mock_object().apply() + assert not exc.value.args[0]['changed'] + + def test_successful_modify(self): + ''' Test successful modify protocol ''' + data = self.mock_rule_args() + data['protocol'] = ['cifs'] + data['allow_suid'] = 'true' + set_module_args(data) + with pytest.raises(AnsibleExitJson) as exc: + self.get_mock_object('rule').apply() + assert exc.value.args[0]['changed'] + + def test_error_on_ambiguous_delete(self): + ''' Test error if multiple entries match for a delete ''' + data = self.mock_rule_args() + data['state'] = 'absent' + set_module_args(data) + with pytest.raises(AnsibleFailJson) as exc: + self.get_mock_object('rules').apply() + msg = "Multiple export policy rules exist.Please specify a rule_index to delete" + assert exc.value.args[0]['msg'] == msg + + def test_helper_query_parameters(self): + ''' Test helper method set_query_parameters() ''' + data = self.mock_rule_args() + set_module_args(data) + result = self.get_mock_object('rule').set_query_parameters() + print(str(result)) + assert 'query' in result + assert 'export-rule-info' in result['query'] + assert result['query']['export-rule-info']['rule-index'] == data['rule_index'] diff --git a/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_file_directory_policy.py b/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_file_directory_policy.py new file mode 100644 index 00000000..70354edc --- /dev/null +++ b/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_file_directory_policy.py @@ -0,0 +1,173 @@ +# (c) 2020, NetApp, Inc +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +''' unit tests for Ansible module: na_ontap_file_directory_policy ''' + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type +import json +import pytest + +from ansible_collections.netapp.ontap.tests.unit.compat import unittest +from ansible_collections.netapp.ontap.tests.unit.compat.mock import patch, Mock +from ansible.module_utils import basic +from ansible.module_utils._text import to_bytes +import ansible_collections.netapp.ontap.plugins.module_utils.netapp as netapp_utils + +from ansible_collections.netapp.ontap.plugins.modules.na_ontap_file_directory_policy \ + import NetAppOntapFilePolicy as policy_module # module under test + +if not netapp_utils.has_netapp_lib(): + pytestmark = pytest.mark.skip('skipping as missing required netapp_lib') + + +def set_module_args(args): + """prepare arguments so that they will be picked up during module creation""" + args = json.dumps({'ANSIBLE_MODULE_ARGS': args}) + basic._ANSIBLE_ARGS = to_bytes(args) # pylint: disable=protected-access + + +class AnsibleExitJson(Exception): + """Exception class to be raised by module.exit_json and caught by the test case""" + pass + + +class AnsibleFailJson(Exception): + """Exception class to be raised by module.fail_json and caught by the test case""" + pass + + +def exit_json(*args, **kwargs): # pylint: disable=unused-argument + """function to patch over exit_json; package return data into an exception""" + if 'changed' not in kwargs: + kwargs['changed'] = False + raise AnsibleExitJson(kwargs) + + +def fail_json(*args, **kwargs): # pylint: disable=unused-argument + """function to patch over fail_json; package return data into an exception""" + kwargs['failed'] = True + raise AnsibleFailJson(kwargs) + + +class MockONTAPConnection(object): + ''' mock server connection to ONTAP host ''' + + def __init__(self, kind=None, data=None): + ''' save arguments ''' + self.kind = kind + self.params = data + self.xml_in = None + self.xml_out = None + + def invoke_successfully(self, xml, enable_tunneling): # pylint: disable=unused-argument + ''' mock invoke_successfully returning xml data ''' + self.xml_in = xml + request = xml.to_string().decode('utf-8') + if self.kind == 'error': + raise netapp_utils.zapi.NaApiError('test', 'expect error') + elif request.startswith("<ems-autosupport-log>"): + xml = None # or something that may the logger happy, and you don't need @patch anymore + # or + # xml = build_ems_log_response() + elif request.startswith("<file-directory-security-policy-get-iter>"): + if self.kind == 'create': + xml = self.build_sd_info() + else: + xml = self.build_sd_info(self.params) + elif request.startswith("<file-directory-security-ntfs-modify>"): + xml = self.build_sd_info(self.params) + self.xml_out = xml + return xml + + @staticmethod + def build_sd_info(data=None): + xml = netapp_utils.zapi.NaElement('xml') + attributes = {} + if data is not None: + attributes = {'num-records': 1, + 'attributes-list': {'file-directory-security-policy': {'policy-name': data['policy_name']}}} + xml.translate_struct(attributes) + return xml + + +class TestMyModule(unittest.TestCase): + ''' Unit tests for na_ontap_file_directory_policy ''' + + def setUp(self): + self.mock_module_helper = patch.multiple(basic.AnsibleModule, + exit_json=exit_json, + fail_json=fail_json) + self.mock_module_helper.start() + self.addCleanup(self.mock_module_helper.stop) + + def mock_args(self): + return { + 'vserver': 'vserver', + 'hostname': 'test', + 'username': 'test_user', + 'password': 'test_pass!' + } + + def get_policy_mock_object(self, type='zapi', kind=None, status=None): + policy_obj = policy_module() + netapp_utils.ems_log_event = Mock(return_value=None) + if type == 'zapi': + if kind is None: + policy_obj.server = MockONTAPConnection() + else: + policy_obj.server = MockONTAPConnection(kind=kind, data=status) + return policy_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({}) + policy_module() + print('Info: %s' % exc.value.args[0]['msg']) + + def test_successfully_create_policy(self): + data = self.mock_args() + data['policy_name'] = 'test_policy' + set_module_args(data) + with pytest.raises(AnsibleExitJson) as exc: + self.get_policy_mock_object('zapi', 'create', data).apply() + assert exc.value.args[0]['changed'] + + def test_error(self): + data = self.mock_args() + data['policy_name'] = 'test_policy' + set_module_args(data) + with pytest.raises(AnsibleFailJson) as exc: + self.get_policy_mock_object('zapi', 'error', data).get_policy_iter() + assert exc.value.args[0]['msg'] == 'Error fetching file-directory policy test_policy: NetApp API failed. Reason - test:expect error' + + with pytest.raises(AnsibleFailJson) as exc: + self.get_policy_mock_object('zapi', 'error', data).create_policy() + assert exc.value.args[0]['msg'] == 'Error creating file-directory policy test_policy: NetApp API failed. Reason - test:expect error' + + with pytest.raises(AnsibleFailJson) as exc: + self.get_policy_mock_object('zapi', 'error', data).remove_policy() + assert exc.value.args[0]['msg'] == 'Error removing file-directory policy test_policy: NetApp API failed. Reason - test:expect error' + + data['path'] = '/vol' + set_module_args(data) + with pytest.raises(AnsibleFailJson) as exc: + self.get_policy_mock_object('zapi', 'error', data).get_task_iter() + assert exc.value.args[0]['msg'] == 'Error fetching task from file-directory policy test_policy: NetApp API failed. Reason - test:expect error' + + with pytest.raises(AnsibleFailJson) as exc: + self.get_policy_mock_object('zapi', 'error', data).add_task_to_policy() + assert exc.value.args[0]['msg'] == 'Error adding task to file-directory policy test_policy: NetApp API failed. Reason - test:expect error' + + with pytest.raises(AnsibleFailJson) as exc: + self.get_policy_mock_object('zapi', 'error', data).remove_task_from_policy() + assert exc.value.args[0]['msg'] == 'Error removing task from file-directory policy test_policy: NetApp API failed. Reason - test:expect error' + + with pytest.raises(AnsibleFailJson) as exc: + self.get_policy_mock_object('zapi', 'error', data).modify_task(dict()) + assert exc.value.args[0]['msg'] == 'Error modifying task in file-directory policy test_policy: NetApp API failed. Reason - test:expect error' + + with pytest.raises(AnsibleFailJson) as exc: + self.get_policy_mock_object('zapi', 'error', data).set_sd() + assert exc.value.args[0]['msg'] == 'Error applying file-directory policy test_policy: NetApp API failed. Reason - test:expect error' diff --git a/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_firewall_policy.py b/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_firewall_policy.py new file mode 100644 index 00000000..51e4d06d --- /dev/null +++ b/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_firewall_policy.py @@ -0,0 +1,296 @@ +# (c) 2018, NetApp, Inc +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +''' unit test template for ONTAP Ansible module ''' + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type +import json +import pytest + +from ansible_collections.netapp.ontap.tests.unit.compat import unittest +from ansible_collections.netapp.ontap.tests.unit.compat.mock import patch, Mock +from ansible.module_utils import basic +from ansible.module_utils._text import to_bytes +import ansible_collections.netapp.ontap.plugins.module_utils.netapp as netapp_utils + +from ansible_collections.netapp.ontap.plugins.modules.na_ontap_firewall_policy \ + import NetAppONTAPFirewallPolicy as fp_module # module under test + +if not netapp_utils.has_netapp_lib(): + pytestmark = pytest.mark.skip('skipping as missing required netapp_lib') + + +def set_module_args(args): + """prepare arguments so that they will be picked up during module creation""" + args = json.dumps({'ANSIBLE_MODULE_ARGS': args}) + basic._ANSIBLE_ARGS = to_bytes(args) # pylint: disable=protected-access + + +class AnsibleExitJson(Exception): + """Exception class to be raised by module.exit_json and caught by the test case""" + pass + + +class AnsibleFailJson(Exception): + """Exception class to be raised by module.fail_json and caught by the test case""" + pass + + +def exit_json(*args, **kwargs): # pylint: disable=unused-argument + """function to patch over exit_json; package return data into an exception""" + if 'changed' not in kwargs: + kwargs['changed'] = False + raise AnsibleExitJson(kwargs) + + +def fail_json(*args, **kwargs): # pylint: disable=unused-argument + """function to patch over fail_json; package return data into an exception""" + kwargs['failed'] = True + raise AnsibleFailJson(kwargs) + + +class MockONTAPConnection(object): + ''' mock server connection to ONTAP host ''' + + def __init__(self, kind=None, data=None): + ''' save arguments ''' + self.kind = kind + self.data = data + self.xml_in = None + self.xml_out = None + + def invoke_successfully(self, xml, enable_tunneling): # pylint: disable=unused-argument + ''' mock invoke_successfully returning xml data ''' + self.xml_in = xml + if self.kind == 'policy': + xml = self.build_policy_info(self.data) + if self.kind == 'config': + xml = self.build_firewall_config_info(self.data) + self.xml_out = xml + return xml + + @staticmethod + def build_policy_info(data): + ''' build xml data for net-firewall-policy-info ''' + xml = netapp_utils.zapi.NaElement('xml') + attributes = { + 'num-records': 1, + 'attributes-list': { + 'net-firewall-policy-info': { + 'policy': data['policy'], + 'service': data['service'], + 'allow-list': [ + {'ip-and-mask': '1.2.3.0/24'} + ] + } + } + } + + xml.translate_struct(attributes) + return xml + + @staticmethod + def build_firewall_config_info(data): + ''' build xml data for net-firewall-config-info ''' + xml = netapp_utils.zapi.NaElement('xml') + attributes = { + 'attributes': { + 'net-firewall-config-info': { + 'is-enabled': 'true', + 'is-logging': 'false' + } + } + } + xml.translate_struct(attributes) + return xml + + +class TestMyModule(unittest.TestCase): + ''' a group of related Unit Tests ''' + + def setUp(self): + self.mock_module_helper = patch.multiple(basic.AnsibleModule, + exit_json=exit_json, + fail_json=fail_json) + self.mock_module_helper.start() + self.addCleanup(self.mock_module_helper.stop) + self.mock_policy = { + 'policy': 'test', + 'service': 'http', + 'vserver': 'my_vserver', + 'allow_list': '1.2.3.0/24' + } + self.mock_config = { + 'node': 'test', + 'enable': 'enable', + 'logging': 'enable' + } + + def mock_policy_args(self): + return { + 'policy': self.mock_policy['policy'], + 'service': self.mock_policy['service'], + 'vserver': self.mock_policy['vserver'], + 'allow_list': [self.mock_policy['allow_list']], + 'hostname': 'test', + 'username': 'test_user', + 'password': 'test_pass!' + } + + def mock_config_args(self): + return { + 'node': self.mock_config['node'], + 'enable': self.mock_config['enable'], + 'logging': self.mock_config['logging'], + 'hostname': 'test', + 'username': 'test_user', + 'password': 'test_pass!' + } + + def get_mock_object(self, kind=None): + """ + Helper method to return an na_ontap_firewall_policy object + :param kind: passes this param to MockONTAPConnection() + :return: na_ontap_firewall_policy object + """ + obj = fp_module() + obj.autosupport_log = Mock(return_value=None) + if kind is None: + obj.server = MockONTAPConnection() + else: + mock_data = self.mock_config if kind == 'config' else self.mock_policy + obj.server = MockONTAPConnection(kind=kind, data=mock_data) + 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({}) + fp_module() + print('Info: %s' % exc.value.args[0]['msg']) + + def test_helper_firewall_policy_attributes(self): + ''' helper returns dictionary with vserver, service and policy details ''' + data = self.mock_policy + set_module_args(self.mock_policy_args()) + result = self.get_mock_object('policy').firewall_policy_attributes() + del data['allow_list'] + assert data == result + + def test_helper_validate_ip_addresses_positive(self): + ''' test if helper validates if IP is a network address ''' + data = self.mock_policy_args() + data['allow_list'] = ['1.2.0.0/16', '1.2.3.0/24'] + set_module_args(data) + result = self.get_mock_object().validate_ip_addresses() + assert result is None + + def test_helper_validate_ip_addresses_negative(self): + ''' test if helper validates if IP is a network address ''' + data = self.mock_policy_args() + data['allow_list'] = ['1.2.0.10/16', '1.2.3.0/24'] + set_module_args(data) + with pytest.raises(AnsibleFailJson) as exc: + self.get_mock_object().validate_ip_addresses() + msg = 'Error: Invalid IP address value for allow_list parameter.' \ + 'Please specify a network address without host bits set: ' \ + '1.2.0.10/16 has host bits set' + assert exc.value.args[0]['msg'] == msg + + def test_get_nonexistent_policy(self): + ''' Test if get_firewall_policy returns None for non-existent policy ''' + set_module_args(self.mock_policy_args()) + result = self.get_mock_object().get_firewall_policy() + assert result is None + + def test_get_existing_policy(self): + ''' Test if get_firewall_policy returns policy details for existing policy ''' + data = self.mock_policy_args() + set_module_args(data) + result = self.get_mock_object('policy').get_firewall_policy() + assert result['service'] == data['service'] + assert result['allow_list'] == ['1.2.3.0/24'] # from build_policy_info() + + def test_successful_create(self): + ''' Test successful create ''' + set_module_args(self.mock_policy_args()) + with pytest.raises(AnsibleExitJson) as exc: + self.get_mock_object().apply() + assert exc.value.args[0]['changed'] + + def test_create_idempotency(self): + ''' Test create idempotency ''' + set_module_args(self.mock_policy_args()) + with pytest.raises(AnsibleExitJson) as exc: + self.get_mock_object('policy').apply() + assert not exc.value.args[0]['changed'] + + def test_successful_delete(self): + ''' Test delete existing job ''' + data = self.mock_policy_args() + data['state'] = 'absent' + set_module_args(data) + with pytest.raises(AnsibleExitJson) as exc: + self.get_mock_object('policy').apply() + assert exc.value.args[0]['changed'] + + def test_delete_idempotency(self): + ''' Test delete idempotency ''' + data = self.mock_policy_args() + data['state'] = 'absent' + set_module_args(data) + with pytest.raises(AnsibleExitJson) as exc: + self.get_mock_object().apply() + assert not exc.value.args[0]['changed'] + + def test_successful_modify(self): + ''' Test successful modify allow_list ''' + data = self.mock_policy_args() + data['allow_list'] = ['1.2.0.0/16'] + set_module_args(data) + with pytest.raises(AnsibleExitJson) as exc: + self.get_mock_object('policy').apply() + assert exc.value.args[0]['changed'] + + def test_successful_modify_mutiple_ips(self): + ''' Test successful modify allow_list ''' + data = self.mock_policy_args() + data['allow_list'] = ['1.2.0.0/16', '1.0.0.0/8'] + set_module_args(data) + with pytest.raises(AnsibleExitJson) as exc: + self.get_mock_object('policy').apply() + assert exc.value.args[0]['changed'] + + def test_successful_modify_mutiple_ips_contain_existing(self): + ''' Test successful modify allow_list ''' + data = self.mock_policy_args() + data['allow_list'] = ['1.2.3.0/24', '1.0.0.0/8'] + set_module_args(data) + with pytest.raises(AnsibleExitJson) as exc: + self.get_mock_object('policy').apply() + assert exc.value.args[0]['changed'] + + def test_get_nonexistent_config(self): + ''' Test if get_firewall_config returns None for non-existent node ''' + set_module_args(self.mock_config_args()) + result = self.get_mock_object().get_firewall_config_for_node() + assert result is None + + def test_get_existing_config(self): + ''' Test if get_firewall_config returns policy details for existing node ''' + data = self.mock_config_args() + set_module_args(data) + result = self.get_mock_object('config').get_firewall_config_for_node() + assert result['enable'] == 'enable' # from build_config_info() + assert result['logging'] == 'disable' # from build_config_info() + + def test_successful_modify_config(self): + ''' Test successful modify allow_list ''' + data = self.mock_config_args() + data['enable'] = 'disable' + data['logging'] = 'enable' + set_module_args(data) + with pytest.raises(AnsibleExitJson) as exc: + self.get_mock_object('config').apply() + assert exc.value.args[0]['changed'] diff --git a/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_firmware_upgrade.py b/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_firmware_upgrade.py new file mode 100644 index 00000000..223bb5b6 --- /dev/null +++ b/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_firmware_upgrade.py @@ -0,0 +1,436 @@ +# (c) 2018, NetApp, Inc +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +''' unit tests ONTAP Ansible module: na_ontap_firmware_upgrade ''' + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type +import json +import pytest + +from ansible_collections.netapp.ontap.tests.unit.compat import unittest +from ansible_collections.netapp.ontap.tests.unit.compat.mock import patch, Mock +from ansible.module_utils import basic +from ansible.module_utils._text import to_bytes +import ansible_collections.netapp.ontap.plugins.module_utils.netapp as netapp_utils + +from ansible_collections.netapp.ontap.plugins.modules.na_ontap_firmware_upgrade\ + import NetAppONTAPFirmwareUpgrade as my_module # module under test + +if not netapp_utils.has_netapp_lib(): + pytestmark = pytest.mark.skip('skipping as missing required netapp_lib') + + +def set_module_args(args): + """prepare arguments so that they will be picked up during module creation""" + args = json.dumps({'ANSIBLE_MODULE_ARGS': args}) + basic._ANSIBLE_ARGS = to_bytes(args) # pylint: disable=protected-access + + +class AnsibleExitJson(Exception): + """Exception class to be raised by module.exit_json and caught by the test case""" + pass + + +class AnsibleFailJson(Exception): + """Exception class to be raised by module.fail_json and caught by the test case""" + pass + + +def exit_json(*args, **kwargs): # pylint: disable=unused-argument + """function to patch over exit_json; package return data into an exception""" + if 'changed' not in kwargs: + kwargs['changed'] = False + raise AnsibleExitJson(kwargs) + + +def fail_json(*args, **kwargs): # pylint: disable=unused-argument + """function to patch over fail_json; package return data into an exception""" + kwargs['failed'] = True + raise AnsibleFailJson(kwargs) + + +class MockONTAPConnection(object): + ''' mock server connection to ONTAP host ''' + + def __init__(self, kind=None, parm1=None, parm2=None, parm3=None): + ''' save arguments ''' + self.type = kind + self.parm1 = parm1 + self.parm2 = parm2 + # self.parm3 = parm3 + self.xml_in = None + self.xml_out = None + self.firmware_type = 'None' + + def invoke_successfully(self, xml, enable_tunneling): # pylint: disable=unused-argument + ''' mock invoke_successfully returning xml data ''' + self.xml_in = xml + # print('xml_in', xml.to_string()) + if self.type == 'firmware_upgrade': + xml = self.build_firmware_upgrade_info(self.parm1, self.parm2) + if self.type == 'acp': + xml = self.build_acp_firmware_info(self.firmware_type) + if self.type == 'firmware_download': + xml = self.build_system_cli_info(error=self.parm1) + if self.type == 'firmware_download_exception': + raise netapp_utils.zapi.NaApiError(self.parm1, self.parm2) + self.xml_out = xml + return xml + + def autosupport_log(self): + ''' mock autosupport log''' + return None + + @staticmethod + def build_firmware_upgrade_info(version, node): + ''' build xml data for service-processor firmware info ''' + xml = netapp_utils.zapi.NaElement('xml') + data = { + 'num-records': 1, + 'attributes-list': {'service-processor-info': {'firmware-version': '3.4'}} + } + xml.translate_struct(data) + print(xml.to_string()) + return xml + + @staticmethod + def build_acp_firmware_info(firmware_type): + ''' build xml data for acp firmware info ''' + xml = netapp_utils.zapi.NaElement('xml') + data = { + # 'num-records': 1, + 'attributes-list': {'storage-shelf-acp-module': {'state': 'firmware_update_required'}} + } + xml.translate_struct(data) + print(xml.to_string()) + return xml + + @staticmethod + def build_system_cli_info(error=None): + ''' build xml data for system-cli info ''' + if error is None: + # make it a string, to be able to compare easily + error = "" + xml = netapp_utils.zapi.NaElement('results') + if error == 'empty_output': + output = "" + else: + output = 'Download complete.' + data = { + 'cli-output': output, + 'cli-result-value': 1 + } + xml.translate_struct(data) + if error == 'status_failed': + status = "failed" + else: + status = "passed" + if error != 'no_status_attr': + xml.add_attr('status', status) + return xml + + +class TestMyModule(unittest.TestCase): + ''' a group of related Unit Tests ''' + + def setUp(self): + self.mock_module_helper = patch.multiple(basic.AnsibleModule, + exit_json=exit_json, + fail_json=fail_json) + self.mock_module_helper.start() + self.addCleanup(self.mock_module_helper.stop) + self.server = MockONTAPConnection() + self.use_vsim = False + + def set_default_args(self): + if self.use_vsim: + hostname = '10.10.10.10' + username = 'admin' + password = 'admin' + node = 'vsim1' + clear_logs = True + package = 'test1.zip' + install_baseline_image = False + update_type = 'serial_full' + force_disruptive_update = False + else: + hostname = 'hostname' + username = 'username' + password = 'password' + node = 'abc' + package = 'test1.zip' + clear_logs = True + install_baseline_image = False + update_type = 'serial_full' + force_disruptive_update = False + + return dict({ + 'hostname': hostname, + 'username': username, + 'password': password, + 'node': node, + 'package': package, + 'clear_logs': clear_logs, + 'install_baseline_image': install_baseline_image, + 'update_type': update_type, + 'https': 'true', + 'force_disruptive_update': force_disruptive_update + }) + + def test_module_fail_when_required_args_missing(self): + ''' required arguments are reported as errors ''' + with pytest.raises(AnsibleFailJson) as exc: + set_module_args({}) + my_module() + print('Info: %s' % exc.value.args[0]['msg']) + + def test_invalid_firmware_type_parameters(self): + ''' fail if firmware_type is missing ''' + module_args = {} + module_args.update(self.set_default_args()) + module_args.update({'firmware_type': 'service_test'}) + set_module_args(module_args) + with pytest.raises(AnsibleFailJson) as exc: + set_module_args(module_args) + my_module() + msg = 'value of firmware_type must be one of: service-processor, shelf, acp, disk, got: %s' % module_args['firmware_type'] + print('Info: %s' % exc.value.args[0]['msg']) + assert exc.value.args[0]['msg'] == msg + + def test_ensure_sp_firmware_get_called(self): + ''' a more interesting test ''' + module_args = {} + module_args.update(self.set_default_args()) + module_args.update({'firmware_type': 'service-processor'}) + set_module_args(module_args) + my_obj = my_module() + my_obj.server = self.server + firmware_image_get = my_obj.firmware_image_get('node') + print('Info: test_firmware_upgrade_get: %s' % repr(firmware_image_get)) + assert firmware_image_get is None + + def test_ensure_firmware_get_with_package_baseline_called(self): + ''' a more interesting test ''' + module_args = {} + module_args.update(self.set_default_args()) + module_args.update({'firmware_type': 'service-processor'}) + module_args.update({'package': 'test1.zip'}) + module_args.update({'install_baseline_image': True}) + with pytest.raises(AnsibleFailJson) as exc: + set_module_args(module_args) + my_module() + msg = 'Do not specify both package and install_baseline_image: true' + print('info: ' + exc.value.args[0]['msg']) + assert exc.value.args[0]['msg'] == msg + + def test_ensure_acp_firmware_required_get_called(self): + ''' a test tp verify acp firmware upgrade is required or not ''' + module_args = {} + module_args.update(self.set_default_args()) + module_args.update({'firmware_type': 'acp'}) + set_module_args(module_args) + my_obj = my_module() + # my_obj.server = self.server + my_obj.server = MockONTAPConnection(kind='acp') + acp_firmware_required_get = my_obj.acp_firmware_required_get() + print('Info: test_acp_firmware_upgrade_required_get: %s' % repr(acp_firmware_required_get)) + assert acp_firmware_required_get is True + + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_firmware_upgrade.NetAppONTAPFirmwareUpgrade.sp_firmware_image_update') + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_firmware_upgrade.NetAppONTAPFirmwareUpgrade.sp_firmware_image_update_progress_get') + def test_ensure_apply_for_firmware_upgrade_called(self, get_mock, update_mock): + ''' updgrading firmware and checking idempotency ''' + module_args = {} + module_args.update(self.set_default_args()) + module_args.update({'package': 'test1.zip'}) + module_args.update({'firmware_type': 'service-processor'}) + module_args.update({'force_disruptive_update': True}) + set_module_args(module_args) + my_obj = my_module() + my_obj.autosupport_log = Mock(return_value=None) + if not self.use_vsim: + my_obj.server = self.server + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + print('Info: test_firmware_upgrade_apply: %s' % repr(exc.value)) + assert not exc.value.args[0]['changed'] + if not self.use_vsim: + my_obj.server = MockONTAPConnection('firmware_upgrade', '3.5', 'true') + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + print('Info: test_firmware_upgrade_apply: %s' % repr(exc.value)) + assert exc.value.args[0]['changed'] + update_mock.assert_called_with() + + def test_shelf_firmware_upgrade(self): + ''' Test shelf firmware upgrade ''' + module_args = {} + module_args.update(self.set_default_args()) + module_args.update({'firmware_type': 'shelf'}) + set_module_args(module_args) + my_obj = my_module() + my_obj.autosupport_log = Mock(return_value=None) + if not self.use_vsim: + my_obj.server = self.server + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + print('Info: test_firmware_upgrade_apply: %s' % repr(exc.value)) + assert not exc.value.args[0]['changed'] + + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_firmware_upgrade.NetAppONTAPFirmwareUpgrade.acp_firmware_upgrade') + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_firmware_upgrade.NetAppONTAPFirmwareUpgrade.acp_firmware_required_get') + def test_acp_firmware_upgrade(self, get_mock, update_mock): + ''' Test ACP firmware upgrade ''' + module_args = {} + module_args.update(self.set_default_args()) + module_args.update({'firmware_type': 'acp'}) + set_module_args(module_args) + my_obj = my_module() + my_obj.autosupport_log = Mock(return_value=None) + if not self.use_vsim: + my_obj.server = self.server + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + print('Info: test_firmware_upgrade_apply: %s' % repr(exc.value)) + assert not exc.value.args[0]['changed'] + + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_firmware_upgrade.NetAppONTAPFirmwareUpgrade.disk_firmware_upgrade') + def test_disk_firmware_upgrade(self, get_mock): + ''' Test disk firmware upgrade ''' + module_args = {} + module_args.update(self.set_default_args()) + module_args.update({'firmware_type': 'disk'}) + set_module_args(module_args) + my_obj = my_module() + my_obj.autosupport_log = Mock(return_value=None) + if not self.use_vsim: + my_obj.server = self.server + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + print('Info: test_firmware_upgrade_apply: %s' % repr(exc.value)) + assert not exc.value.args[0]['changed'] + + def test_firmware_download(self): + ''' Test firmware download ''' + module_args = {} + module_args.update(self.set_default_args()) + module_args.update({'package_url': 'dummy_url'}) + set_module_args(module_args) + my_obj = my_module() + my_obj.autosupport_log = Mock(return_value=None) + if not self.use_vsim: + my_obj.server = MockONTAPConnection('firmware_download') + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + assert exc.value.args[0]['changed'] + msg = "Firmware download completed. Extra info: Download complete." + assert exc.value.args[0]['msg'] == msg + + def test_firmware_download_60(self): + ''' Test firmware download ''' + module_args = {} + module_args.update(self.set_default_args()) + module_args.update({'package_url': 'dummy_url'}) + set_module_args(module_args) + my_obj = my_module() + my_obj.autosupport_log = Mock(return_value=None) + if not self.use_vsim: + my_obj.server = MockONTAPConnection('firmware_download_exception', 60, 'ZAPI timeout') + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + assert exc.value.args[0]['changed'] + msg = "Firmware download completed, slowly." + assert exc.value.args[0]['msg'] == msg + + def test_firmware_download_502(self): + ''' Test firmware download ''' + module_args = {} + module_args.update(self.set_default_args()) + module_args.update({'package_url': 'dummy_url'}) + set_module_args(module_args) + my_obj = my_module() + my_obj.autosupport_log = Mock(return_value=None) + if not self.use_vsim: + my_obj.server = MockONTAPConnection('firmware_download_exception', 502, 'Bad GW') + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + assert exc.value.args[0]['changed'] + msg = "Firmware download still in progress." + assert exc.value.args[0]['msg'] == msg + + def test_firmware_download_502_as_error(self): + ''' Test firmware download ''' + module_args = {} + module_args.update(self.set_default_args()) + module_args.update({'package_url': 'dummy_url'}) + module_args.update({'fail_on_502_error': True}) + set_module_args(module_args) + my_obj = my_module() + my_obj.autosupport_log = Mock(return_value=None) + if not self.use_vsim: + my_obj.server = MockONTAPConnection('firmware_download_exception', 502, 'Bad GW') + with pytest.raises(AnsibleFailJson) as exc: + my_obj.apply() + msg = "NetApp API failed. Reason - 502:Bad GW" + assert msg in exc.value.args[0]['msg'] + + def test_firmware_download_no_num_error(self): + ''' Test firmware download ''' + module_args = {} + module_args.update(self.set_default_args()) + module_args.update({'package_url': 'dummy_url'}) + set_module_args(module_args) + my_obj = my_module() + my_obj.autosupport_log = Mock(return_value=None) + if not self.use_vsim: + my_obj.server = MockONTAPConnection('firmware_download_exception', 'some error string', 'whatever') + with pytest.raises(AnsibleFailJson) as exc: + my_obj.apply() + msg = "NetApp API failed. Reason - some error string:whatever" + assert msg in exc.value.args[0]['msg'] + + def test_firmware_download_no_status_attr(self): + ''' Test firmware download ''' + module_args = {} + module_args.update(self.set_default_args()) + module_args.update({'package_url': 'dummy_url'}) + set_module_args(module_args) + my_obj = my_module() + my_obj.autosupport_log = Mock(return_value=None) + if not self.use_vsim: + my_obj.server = MockONTAPConnection('firmware_download', 'no_status_attr') + with pytest.raises(AnsibleFailJson) as exc: + my_obj.apply() + msg = "unable to download package from dummy_url: 'status' attribute missing." + assert exc.value.args[0]['msg'].startswith(msg) + + def test_firmware_download_status_failed(self): + ''' Test firmware download ''' + module_args = {} + module_args.update(self.set_default_args()) + module_args.update({'package_url': 'dummy_url'}) + set_module_args(module_args) + my_obj = my_module() + my_obj.autosupport_log = Mock(return_value=None) + if not self.use_vsim: + my_obj.server = MockONTAPConnection('firmware_download', 'status_failed') + with pytest.raises(AnsibleFailJson) as exc: + my_obj.apply() + msg = "unable to download package from dummy_url: check 'status' value." + assert exc.value.args[0]['msg'].startswith(msg) + + def test_firmware_download_empty_output(self): + ''' Test firmware download ''' + module_args = {} + module_args.update(self.set_default_args()) + module_args.update({'package_url': 'dummy_url'}) + set_module_args(module_args) + my_obj = my_module() + my_obj.autosupport_log = Mock(return_value=None) + if not self.use_vsim: + my_obj.server = MockONTAPConnection('firmware_download', 'empty_output') + with pytest.raises(AnsibleFailJson) as exc: + my_obj.apply() + msg = "unable to download package from dummy_url: check console permissions." + assert exc.value.args[0]['msg'].startswith(msg) diff --git a/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_flexcache.py b/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_flexcache.py new file mode 100644 index 00000000..ae93f142 --- /dev/null +++ b/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_flexcache.py @@ -0,0 +1,531 @@ +# (c) 2018, NetApp, Inc +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +''' unit test for ONTAP FlexCache Ansible module ''' + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type +import json +import pytest + +from ansible_collections.netapp.ontap.tests.unit.compat import unittest +from ansible_collections.netapp.ontap.tests.unit.compat.mock import patch +from ansible.module_utils import basic +from ansible.module_utils._text import to_bytes +import ansible_collections.netapp.ontap.plugins.module_utils.netapp as netapp_utils + +from ansible_collections.netapp.ontap.plugins.modules.na_ontap_flexcache \ + import NetAppONTAPFlexCache as my_module # module under test + +if not netapp_utils.has_netapp_lib(): + pytestmark = pytest.mark.skip('skipping as missing required netapp_lib') + + +def set_module_args(args): + """prepare arguments so that they will be picked up during module creation""" + args = json.dumps({'ANSIBLE_MODULE_ARGS': args}) + basic._ANSIBLE_ARGS = to_bytes(args) # pylint: disable=protected-access + + +class AnsibleExitJson(Exception): + """Exception class to be raised by module.exit_json and caught by the test case""" + pass + + +class AnsibleFailJson(Exception): + """Exception class to be raised by module.fail_json and caught by the test case""" + pass + + +def exit_json(*args, **kwargs): # pylint: disable=unused-argument + """function to patch over exit_json; package return data into an exception""" + if 'changed' not in kwargs: + kwargs['changed'] = False + raise AnsibleExitJson(kwargs) + + +def fail_json(*args, **kwargs): # pylint: disable=unused-argument + """function to patch over fail_json; package return data into an exception""" + kwargs['failed'] = True + raise AnsibleFailJson(kwargs) + + +class MockONTAPConnection(object): + ''' mock server connection to ONTAP host ''' + + def __init__(self, kind=None, parm1=None, api_error=None, job_error=None): + ''' save arguments ''' + self.type = kind + self.parm1 = parm1 + self.api_error = api_error + self.job_error = job_error + self.xml_in = None + self.xml_out = None + + def invoke_successfully(self, xml, enable_tunneling): # pylint: disable=unused-argument + ''' mock invoke_successfully returning xml data ''' + self.xml_in = xml + tag = xml.get_name() + if tag == 'flexcache-get-iter' and self.type == 'vserver': + xml = self.build_flexcache_info(self.parm1) + elif tag == 'flexcache-create-async': + xml = self.build_flexcache_create_destroy_rsp() + elif tag == 'flexcache-destroy-async': + if self.api_error: + code, message = self.api_error.split(':', 2) + raise netapp_utils.zapi.NaApiError(code, message) + xml = self.build_flexcache_create_destroy_rsp() + elif tag == 'job-get': + xml = self.build_job_info(self.job_error) + self.xml_out = xml + return xml + + @staticmethod + def build_flexcache_info(vserver): + ''' build xml data for vserser-info ''' + xml = netapp_utils.zapi.NaElement('xml') + attributes = netapp_utils.zapi.NaElement('attributes-list') + count = 2 if vserver == 'repeats' else 1 + for dummy in range(count): + attributes.add_node_with_children('flexcache-info', **{ + 'vserver': vserver, + 'origin-vserver': 'ovserver', + 'origin-volume': 'ovolume', + 'origin-cluster': 'ocluster', + 'volume': 'volume', + }) + xml.add_child_elem(attributes) + xml.add_new_child('num-records', str(count)) + return xml + + @staticmethod + def build_flexcache_create_destroy_rsp(): + ''' build xml data for a create or destroy response ''' + xml = netapp_utils.zapi.NaElement('xml') + xml.add_new_child('result-status', 'in_progress') + xml.add_new_child('result-jobid', '1234') + return xml + + @staticmethod + def build_job_info(error): + ''' build xml data for a job ''' + xml = netapp_utils.zapi.NaElement('xml') + attributes = netapp_utils.zapi.NaElement('attributes') + if error is None: + state = 'success' + elif error == 'time_out': + state = 'running' + else: + state = 'failure' + attributes.add_node_with_children('job-info', **{ + 'job-state': state, + 'job-progress': 'dummy', + 'job-completion': error, + }) + xml.add_child_elem(attributes) + xml.add_new_child('result-status', 'in_progress') + xml.add_new_child('result-jobid', '1234') + return xml + + +class TestMyModule(unittest.TestCase): + ''' a group of related Unit Tests ''' + + def setUp(self): + self.mock_module_helper = patch.multiple(basic.AnsibleModule, + exit_json=exit_json, + fail_json=fail_json) + self.mock_module_helper.start() + self.addCleanup(self.mock_module_helper.stop) + # make sure to change this to False before submitting + self.onbox = False + self.dummy_args = dict() + for arg in ('hostname', 'username', 'password'): + self.dummy_args[arg] = arg + if self.onbox: + self.args = { + 'hostname': '10.193.78.219', + 'username': 'admin', + 'password': 'netapp1!', + } + else: + self.args = self.dummy_args + self.server = MockONTAPConnection() + + def create_flexcache(self, vserver, volume, junction_path): + ''' create flexcache ''' + if not self.onbox: + return + args = { + 'state': 'present', + 'volume': volume, + 'size': '90', # 80MB minimum + 'size_unit': 'mb', # 80MB minimum + 'vserver': vserver, + 'aggr_list': 'aggr1', + 'origin_volume': 'fc_vol_origin', + 'origin_vserver': 'ansibleSVM', + 'junction_path': junction_path, + } + args.update(self.args) + set_module_args(args) + my_obj = my_module() + try: + my_obj.apply() + except AnsibleExitJson as exc: + print('Create util: ' + repr(exc)) + except AnsibleFailJson as exc: + print('Create util: ' + repr(exc)) + + def delete_flexcache(self, vserver, volume): + ''' delete flexcache ''' + if not self.onbox: + return + args = {'volume': volume, 'vserver': vserver, 'state': 'absent', 'force_offline': 'true'} + args.update(self.args) + set_module_args(args) + my_obj = my_module() + try: + my_obj.apply() + except AnsibleExitJson as exc: + print('Delete util: ' + repr(exc)) + except AnsibleFailJson as exc: + print('Delete util: ' + repr(exc)) + + def test_module_fail_when_required_args_missing(self): + ''' required arguments are reported as errors ''' + with pytest.raises(AnsibleFailJson) as exc: + set_module_args({}) + my_module() + print('Info: %s' % exc.value.args[0]['msg']) + + def test_missing_parameters(self): + ''' fail if origin volume and origin verser are missing ''' + args = { + 'vserver': 'vserver', + 'volume': 'volume' + } + args.update(self.dummy_args) + set_module_args(args) + my_obj = my_module() + my_obj.server = self.server + with pytest.raises(AnsibleFailJson) as exc: + # It may not be a good idea to start with apply + # More atomic methods can be easier to mock + # Hint: start with get methods, as they are called first + my_obj.apply() + msg = 'Missing parameters: origin_volume, origin_vserver' + assert exc.value.args[0]['msg'] == msg + + def test_missing_parameter(self): + ''' fail if origin verser parameter is missing ''' + args = { + 'vserver': 'vserver', + 'origin_volume': 'origin_volume', + 'volume': 'volume' + } + args.update(self.dummy_args) + set_module_args(args) + my_obj = my_module() + my_obj.server = self.server + with pytest.raises(AnsibleFailJson) as exc: + my_obj.apply() + msg = 'Missing parameter: origin_vserver' + assert exc.value.args[0]['msg'] == msg + + def test_get_flexcache(self): + ''' get flexcache info ''' + args = { + 'vserver': 'ansibleSVM', + 'origin_volume': 'origin_volume', + 'volume': 'volume' + } + args.update(self.args) + set_module_args(args) + my_obj = my_module() + if not self.onbox: + my_obj.server = MockONTAPConnection('vserver') + info = my_obj.flexcache_get() + print('info: ' + repr(info)) + + def test_get_flexcache_double(self): + ''' get flexcache info returns 2 entries! ''' + args = { + 'vserver': 'ansibleSVM', + 'origin_volume': 'origin_volume', + 'volume': 'volume' + } + args.update(self.dummy_args) + set_module_args(args) + my_obj = my_module() + my_obj.server = MockONTAPConnection('vserver', 'repeats') + with pytest.raises(AnsibleFailJson) as exc: + my_obj.flexcache_get() + msg = 'Error fetching FlexCache info: Multiple records found for %s:' % args['volume'] + assert exc.value.args[0]['msg'] == msg + + def test_create_flexcache(self): + ''' create flexcache ''' + args = { + 'volume': 'volume', + 'size': '90', # 80MB minimum + 'size_unit': 'mb', # 80MB minimum + 'vserver': 'ansibleSVM', + 'aggr_list': 'aggr1', + 'origin_volume': 'fc_vol_origin', + 'origin_vserver': 'ansibleSVM', + } + self.delete_flexcache(args['vserver'], args['volume']) + args.update(self.args) + set_module_args(args) + my_obj = my_module() + if not self.onbox: + my_obj.server = MockONTAPConnection() + with patch.object(my_module, 'flexcache_create', wraps=my_obj.flexcache_create) as mock_create: + # with patch('__main__.my_module.flexcache_create', wraps=my_obj.flexcache_create) as mock_create: + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + print('Create: ' + repr(exc.value)) + assert exc.value.args[0]['changed'] + mock_create.assert_called_with() + + def test_create_flexcache_idempotent(self): + ''' create flexcache - already exists ''' + args = { + 'volume': 'volume', + 'vserver': 'ansibleSVM', + 'aggr_list': 'aggr1', + 'origin_volume': 'fc_vol_origin', + 'origin_vserver': 'ansibleSVM', + } + args.update(self.args) + set_module_args(args) + my_obj = my_module() + if not self.onbox: + my_obj.server = MockONTAPConnection('vserver') + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + print('Create: ' + repr(exc.value)) + assert exc.value.args[0]['changed'] is False + + def test_create_flexcache_autoprovision(self): + ''' create flexcache with autoprovision''' + args = { + 'volume': 'volume', + 'size': '90', # 80MB minimum + 'size_unit': 'mb', # 80MB minimum + 'vserver': 'ansibleSVM', + 'auto_provision_as': 'flexgroup', + 'origin_volume': 'fc_vol_origin', + 'origin_vserver': 'ansibleSVM', + } + self.delete_flexcache(args['vserver'], args['volume']) + args.update(self.args) + set_module_args(args) + my_obj = my_module() + if not self.onbox: + my_obj.server = MockONTAPConnection() + with patch.object(my_module, 'flexcache_create', wraps=my_obj.flexcache_create) as mock_create: + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + print('Create: ' + repr(exc.value)) + assert exc.value.args[0]['changed'] + mock_create.assert_called_with() + + def test_create_flexcache_autoprovision_idempotent(self): + ''' create flexcache with autoprovision - already exists ''' + args = { + 'volume': 'volume', + 'vserver': 'ansibleSVM', + 'origin_volume': 'fc_vol_origin', + 'origin_vserver': 'ansibleSVM', + 'auto_provision_as': 'flexgroup', + } + args.update(self.args) + set_module_args(args) + my_obj = my_module() + if not self.onbox: + my_obj.server = MockONTAPConnection('vserver') + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + print('Create: ' + repr(exc.value)) + assert exc.value.args[0]['changed'] is False + + def test_create_flexcache_multiplier(self): + ''' create flexcache with aggregate multiplier''' + args = { + 'volume': 'volume', + 'size': '90', # 80MB minimum + 'size_unit': 'mb', # 80MB minimum + 'vserver': 'ansibleSVM', + 'aggr_list': 'aggr1', + 'origin_volume': 'fc_vol_origin', + 'origin_vserver': 'ansibleSVM', + 'aggr_list_multiplier': '2', + } + self.delete_flexcache(args['vserver'], args['volume']) + args.update(self.args) + set_module_args(args) + my_obj = my_module() + if not self.onbox: + my_obj.server = MockONTAPConnection() + with patch.object(my_module, 'flexcache_create', wraps=my_obj.flexcache_create) as mock_create: + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + print('Create: ' + repr(exc.value)) + assert exc.value.args[0]['changed'] + mock_create.assert_called_with() + + def test_create_flexcache_multiplier_idempotent(self): + ''' create flexcache with aggregate multiplier - already exists ''' + args = { + 'volume': 'volume', + 'vserver': 'ansibleSVM', + 'aggr_list': 'aggr1', + 'origin_volume': 'fc_vol_origin', + 'origin_vserver': 'ansibleSVM', + 'aggr_list_multiplier': '2', + } + args.update(self.args) + set_module_args(args) + my_obj = my_module() + if not self.onbox: + my_obj.server = MockONTAPConnection('vserver') + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + print('Create: ' + repr(exc.value)) + assert exc.value.args[0]['changed'] is False + + def test_delete_flexcache_exists_no_force(self): + ''' delete flexcache ''' + args = {'volume': 'volume', 'vserver': 'ansibleSVM', 'state': 'absent'} + args.update(self.args) + set_module_args(args) + my_obj = my_module() + error = '13001:Volume volume in Vserver ansibleSVM must be offline to be deleted. ' \ + 'Use "volume offline -vserver ansibleSVM -volume volume" command to offline ' \ + 'the volume' + if not self.onbox: + my_obj.server = MockONTAPConnection('vserver', 'flex', api_error=error) + with patch.object(my_module, 'flexcache_delete', wraps=my_obj.flexcache_delete) as mock_delete: + with pytest.raises(AnsibleFailJson) as exc: + my_obj.apply() + print('Delete: ' + repr(exc.value)) + msg = 'Error deleting FlexCache : NetApp API failed. Reason - %s' % error + assert exc.value.args[0]['msg'] == msg + mock_delete.assert_called_with() + + def test_delete_flexcache_exists_with_force(self): + ''' delete flexcache ''' + args = {'volume': 'volume', 'vserver': 'ansibleSVM', 'state': 'absent', 'force_offline': 'true'} + args.update(self.args) + set_module_args(args) + my_obj = my_module() + if not self.onbox: + my_obj.server = MockONTAPConnection('vserver', 'flex') + with patch.object(my_module, 'flexcache_delete', wraps=my_obj.flexcache_delete) as mock_delete: + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + print('Delete: ' + repr(exc.value)) + assert exc.value.args[0]['changed'] + mock_delete.assert_called_with() + + def test_delete_flexcache_exists_junctionpath_no_force(self): + ''' delete flexcache ''' + args = {'volume': 'volume', 'vserver': 'ansibleSVM', 'junction_path': 'jpath', 'state': 'absent', 'force_offline': 'true'} + self.create_flexcache(args['vserver'], args['volume'], args['junction_path']) + args.update(self.args) + set_module_args(args) + my_obj = my_module() + error = '160:Volume volume on Vserver ansibleSVM must be unmounted before being taken offline or restricted.' + if not self.onbox: + my_obj.server = MockONTAPConnection('vserver', 'flex', api_error=error) + with patch.object(my_module, 'flexcache_delete', wraps=my_obj.flexcache_delete) as mock_delete: + with pytest.raises(AnsibleFailJson) as exc: + my_obj.apply() + print('Delete: ' + repr(exc.value)) + msg = 'Error deleting FlexCache : NetApp API failed. Reason - %s' % error + assert exc.value.args[0]['msg'] == msg + mock_delete.assert_called_with() + + def test_delete_flexcache_exists_junctionpath_with_force(self): + ''' delete flexcache ''' + args = {'volume': 'volume', 'vserver': 'ansibleSVM', 'junction_path': 'jpath', 'state': 'absent', 'force_offline': 'true', 'force_unmount': 'true'} + self.create_flexcache(args['vserver'], args['volume'], args['junction_path']) + args.update(self.args) + set_module_args(args) + my_obj = my_module() + if not self.onbox: + my_obj.server = MockONTAPConnection('vserver', 'flex') + with patch.object(my_module, 'flexcache_delete', wraps=my_obj.flexcache_delete) as mock_delete: + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + print('Delete: ' + repr(exc.value)) + assert exc.value.args[0]['changed'] + mock_delete.assert_called_with() + + def test_delete_flexcache_not_exist(self): + ''' delete flexcache ''' + args = {'volume': 'volume', 'vserver': 'ansibleSVM', 'state': 'absent'} + args.update(self.args) + set_module_args(args) + my_obj = my_module() + if not self.onbox: + my_obj.server = MockONTAPConnection() + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + print('Delete: ' + repr(exc.value)) + assert exc.value.args[0]['changed'] is False + + def test_create_flexcache_size_error(self): + ''' create flexcache ''' + args = { + 'volume': 'volume_err', + 'size': '50', # 80MB minimum + 'size_unit': 'mb', # 80MB minimum + 'vserver': 'ansibleSVM', + 'aggr_list': 'aggr1', + 'origin_volume': 'fc_vol_origin', + 'origin_vserver': 'ansibleSVM', + } + args.update(self.args) + set_module_args(args) + my_obj = my_module() + error = 'Size "50MB" ("52428800B") is too small. Minimum size is "80MB" ("83886080B"). ' + if not self.onbox: + my_obj.server = MockONTAPConnection(job_error=error) + with patch.object(my_module, 'flexcache_create', wraps=my_obj.flexcache_create) as mock_create: + with pytest.raises(AnsibleFailJson) as exc: + my_obj.apply() + print('Create: ' + repr(exc.value)) + msg = 'Error when creating flexcache: %s' % error + assert exc.value.args[0]['msg'] == msg + mock_create.assert_called_with() + + def test_create_flexcache_time_out(self): + ''' create flexcache ''' + args = { + 'volume': 'volume_err', + 'size': '50', # 80MB minimum + 'size_unit': 'mb', # 80MB minimum + 'vserver': 'ansibleSVM', + 'aggr_list': 'aggr1', + 'origin_volume': 'fc_vol_origin', + 'origin_vserver': 'ansibleSVM', + 'time_out': '2' + } + args.update(self.args) + set_module_args(args) + my_obj = my_module() + if not self.onbox: + my_obj.server = MockONTAPConnection(job_error='time_out') + with patch.object(my_module, 'flexcache_create', wraps=my_obj.flexcache_create) as mock_create: + # replace time.sleep with a noop + with patch('time.sleep', lambda a: None): + with pytest.raises(AnsibleFailJson) as exc: + my_obj.apply() + print('Create: ' + repr(exc.value)) + msg = 'Error when creating flexcache: job completion exceeded expected timer of: %s seconds' \ + % args['time_out'] + assert exc.value.args[0]['msg'] == msg + mock_create.assert_called_with() diff --git a/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_igroup.py b/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_igroup.py new file mode 100644 index 00000000..57609fc6 --- /dev/null +++ b/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_igroup.py @@ -0,0 +1,260 @@ +# (c) 2018, NetApp, Inc +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +''' unit test template for ONTAP Ansible module ''' + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type +import json +import pytest + +from ansible_collections.netapp.ontap.tests.unit.compat import unittest +from ansible_collections.netapp.ontap.tests.unit.compat.mock import patch, Mock +from ansible.module_utils import basic +from ansible.module_utils._text import to_bytes +import ansible_collections.netapp.ontap.plugins.module_utils.netapp as netapp_utils + +from ansible_collections.netapp.ontap.plugins.modules.na_ontap_igroup \ + import NetAppOntapIgroup as igroup # module under test + +if not netapp_utils.has_netapp_lib(): + pytestmark = pytest.mark.skip('skipping as missing required netapp_lib') + + +def set_module_args(args): + """prepare arguments so that they will be picked up during module creation""" + args = json.dumps({'ANSIBLE_MODULE_ARGS': args}) + basic._ANSIBLE_ARGS = to_bytes(args) # pylint: disable=protected-access + + +class AnsibleExitJson(Exception): + """Exception class to be raised by module.exit_json and caught by the test case""" + pass + + +class AnsibleFailJson(Exception): + """Exception class to be raised by module.fail_json and caught by the test case""" + pass + + +def exit_json(*args, **kwargs): # pylint: disable=unused-argument + """function to patch over exit_json; package return data into an exception""" + if 'changed' not in kwargs: + kwargs['changed'] = False + raise AnsibleExitJson(kwargs) + + +def fail_json(*args, **kwargs): # pylint: disable=unused-argument + """function to patch over fail_json; package return data into an exception""" + kwargs['failed'] = True + raise AnsibleFailJson(kwargs) + + +class MockONTAPConnection(object): + ''' mock server connection to ONTAP host ''' + + def __init__(self, kind=None, data=None): + ''' save arguments ''' + self.kind = kind + self.data = data + self.xml_in = None + self.xml_out = None + + def invoke_successfully(self, xml, enable_tunneling): # pylint: disable=unused-argument + ''' mock invoke_successfully returning xml data ''' + self.xml_in = xml + if self.kind == 'igroup': + xml = self.build_igroup() + if self.kind == 'igroup_no_initiators': + xml = self.build_igroup_no_initiators() + self.xml_out = xml + return xml + + @staticmethod + def build_igroup(): + ''' build xml data for initiator ''' + xml = netapp_utils.zapi.NaElement('xml') + attributes = { + 'num-records': 1, + 'attributes-list': { + 'initiator-group-info': { + 'initiators': [ + { + 'initiator-info': { + 'initiator-name': 'init1' + }}, + { + 'initiator-info': { + 'initiator-name': 'init2' + }} + ] + } + } + } + xml.translate_struct(attributes) + return xml + + @staticmethod + def build_igroup_no_initiators(): + ''' build xml data for igroup with no initiators ''' + xml = netapp_utils.zapi.NaElement('xml') + attributes = { + 'num-records': 1, + 'attributes-list': { + 'initiator-group-info': { + 'vserver': 'test' + } + } + } + xml.translate_struct(attributes) + return xml + + +class TestMyModule(unittest.TestCase): + ''' a group of related Unit Tests ''' + + def setUp(self): + self.mock_module_helper = patch.multiple(basic.AnsibleModule, + exit_json=exit_json, + fail_json=fail_json) + self.mock_module_helper.start() + self.addCleanup(self.mock_module_helper.stop) + self.server = MockONTAPConnection() + + def mock_args(self): + return { + 'vserver': 'vserver', + 'name': 'test', + 'initiators': 'init1', + 'ostype': 'linux', + 'initiator_group_type': 'fcp', + 'bind_portset': 'true', + 'hostname': 'hostname', + 'username': 'username', + 'password': 'password' + } + + def get_igroup_mock_object(self, kind=None): + """ + Helper method to return an na_ontap_igroup object + :param kind: passes this param to MockONTAPConnection() + :return: na_ontap_igroup object + """ + obj = igroup() + obj.autosupport_log = Mock(return_value=None) + if kind is None: + obj.server = MockONTAPConnection() + else: + obj.server = MockONTAPConnection(kind=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({}) + igroup() + + def test_get_nonexistent_igroup(self): + ''' Test if get_igroup returns None for non-existent igroup ''' + data = self.mock_args() + set_module_args(data) + result = self.get_igroup_mock_object().get_igroup('dummy') + assert result is None + + def test_get_existing_igroup_with_initiators(self): + ''' Test if get_igroup returns list of existing initiators ''' + data = self.mock_args() + set_module_args(data) + result = self.get_igroup_mock_object('igroup').get_igroup(data['name']) + assert data['initiators'] in result['initiators'] + assert result['initiators'] == ['init1', 'init2'] + + def test_get_existing_igroup_without_initiators(self): + ''' Test if get_igroup returns empty list() ''' + data = self.mock_args() + set_module_args(data) + result = self.get_igroup_mock_object('igroup_no_initiators').get_igroup(data['name']) + assert result['initiators'] == [] + + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_igroup.NetAppOntapIgroup.add_initiators') + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_igroup.NetAppOntapIgroup.remove_initiators') + def test_modify_initiator_calls_add_and_remove(self, remove, add): + '''Test remove_initiator() is called followed by add_initiator() on modify operation''' + data = self.mock_args() + data['initiators'] = 'replacewithme' + set_module_args(data) + obj = self.get_igroup_mock_object('igroup') + with pytest.raises(AnsibleExitJson) as exc: + current = obj.get_igroup(data['name']) + obj.apply() + remove.assert_called_with(current['initiators']) + add.assert_called_with() + + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_igroup.NetAppOntapIgroup.modify_initiator') + def test_modify_called_from_add(self, modify): + '''Test remove_initiator() and add_initiator() calls modify''' + data = self.mock_args() + data['initiators'] = 'replacewithme' + add, remove = 'igroup-add', 'igroup-remove' + set_module_args(data) + with pytest.raises(AnsibleExitJson) as exc: + self.get_igroup_mock_object('igroup_no_initiators').apply() + modify.assert_called_with('replacewithme', add) + assert modify.call_count == 1 # remove nothing, add 1 new + + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_igroup.NetAppOntapIgroup.modify_initiator') + def test_modify_called_from_remove(self, modify): + '''Test remove_initiator() and add_initiator() calls modify''' + data = self.mock_args() + data['initiators'] = '' + remove = 'igroup-remove' + set_module_args(data) + with pytest.raises(AnsibleExitJson) as exc: + self.get_igroup_mock_object('igroup').apply() + modify.assert_called_with('init2', remove) + assert modify.call_count == 2 # remove existing 2, add nothing + + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_igroup.NetAppOntapIgroup.add_initiators') + def test_successful_create(self, add): + ''' Test successful create ''' + set_module_args(self.mock_args()) + with pytest.raises(AnsibleExitJson) as exc: + self.get_igroup_mock_object().apply() + assert exc.value.args[0]['changed'] + add.assert_called_with() + + def test_successful_delete(self): + ''' Test successful delete ''' + data = self.mock_args() + data['state'] = 'absent' + set_module_args(self.mock_args()) + with pytest.raises(AnsibleExitJson) as exc: + self.get_igroup_mock_object('igroup').apply() + assert exc.value.args[0]['changed'] + + def test_successful_modify(self): + ''' Test successful modify ''' + data = self.mock_args() + data['initiators'] = 'new' + set_module_args(self.mock_args()) + with pytest.raises(AnsibleExitJson) as exc: + self.get_igroup_mock_object('igroup').apply() + assert exc.value.args[0]['changed'] + + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_igroup.NetAppOntapIgroup.get_igroup') + def test_successful_rename(self, get_vserver): + '''Test successful rename''' + data = self.mock_args() + data['from_name'] = 'test' + data['name'] = 'test_new' + set_module_args(data) + current = { + 'initiators': ['init1', 'init2'] + } + get_vserver.side_effect = [ + None, + current + ] + with pytest.raises(AnsibleExitJson) as exc: + self.get_igroup_mock_object().apply() + assert exc.value.args[0]['changed'] diff --git a/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_igroup_initiator.py b/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_igroup_initiator.py new file mode 100644 index 00000000..d7e3bc68 --- /dev/null +++ b/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_igroup_initiator.py @@ -0,0 +1,218 @@ +# (c) 2018, NetApp, Inc +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +''' unit test template for ONTAP Ansible module ''' + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type +import json +import pytest + +from ansible_collections.netapp.ontap.tests.unit.compat import unittest +from ansible_collections.netapp.ontap.tests.unit.compat.mock import patch, Mock +from ansible.module_utils import basic +from ansible.module_utils._text import to_bytes +import ansible_collections.netapp.ontap.plugins.module_utils.netapp as netapp_utils + +from ansible_collections.netapp.ontap.plugins.modules.na_ontap_igroup_initiator \ + import NetAppOntapIgroupInitiator as initiator # module under test + +if not netapp_utils.has_netapp_lib(): + pytestmark = pytest.mark.skip('skipping as missing required netapp_lib') + + +def set_module_args(args): + """prepare arguments so that they will be picked up during module creation""" + args = json.dumps({'ANSIBLE_MODULE_ARGS': args}) + basic._ANSIBLE_ARGS = to_bytes(args) # pylint: disable=protected-access + + +class AnsibleExitJson(Exception): + """Exception class to be raised by module.exit_json and caught by the test case""" + pass + + +class AnsibleFailJson(Exception): + """Exception class to be raised by module.fail_json and caught by the test case""" + pass + + +def exit_json(*args, **kwargs): # pylint: disable=unused-argument + """function to patch over exit_json; package return data into an exception""" + if 'changed' not in kwargs: + kwargs['changed'] = False + raise AnsibleExitJson(kwargs) + + +def fail_json(*args, **kwargs): # pylint: disable=unused-argument + """function to patch over fail_json; package return data into an exception""" + kwargs['failed'] = True + raise AnsibleFailJson(kwargs) + + +class MockONTAPConnection(object): + ''' mock server connection to ONTAP host ''' + + def __init__(self, kind=None, data=None): + ''' save arguments ''' + self.kind = kind + self.data = data + self.xml_in = None + self.xml_out = None + + def invoke_successfully(self, xml, enable_tunneling): # pylint: disable=unused-argument + ''' mock invoke_successfully returning xml data ''' + self.xml_in = xml + if self.kind == 'initiator': + xml = self.build_igroup_initiator() + elif self.kind == 'initiator_fail': + raise netapp_utils.zapi.NaApiError(code='TEST', message="This exception is from the unit test") + self.xml_out = xml + return xml + + @staticmethod + def build_igroup_initiator(): + ''' build xml data for initiator ''' + xml = netapp_utils.zapi.NaElement('xml') + attributes = { + 'num-records': 1, + 'attributes-list': { + 'initiator-group-info': { + 'initiators': [ + {'initiator-info': { + 'initiator-name': 'init1' + }}, + {'initiator-info': { + 'initiator-name': 'init2' + }} + ] + } + } + } + xml.translate_struct(attributes) + return xml + + +class TestMyModule(unittest.TestCase): + ''' a group of related Unit Tests ''' + + def setUp(self): + self.mock_module_helper = patch.multiple(basic.AnsibleModule, + exit_json=exit_json, + fail_json=fail_json) + self.mock_module_helper.start() + self.addCleanup(self.mock_module_helper.stop) + self.server = MockONTAPConnection() + + def mock_args(self): + return { + 'vserver': 'vserver', + 'name': 'init1', + 'initiator_group': 'test', + 'hostname': 'hostname', + 'username': 'username', + 'password': 'password' + } + + def get_initiator_mock_object(self, kind=None): + """ + Helper method to return an na_ontap_initiator object + :param kind: passes this param to MockONTAPConnection() + :return: na_ontap_initiator object + """ + obj = initiator() + obj.autosupport_log = Mock(return_value=None) + if kind is None: + obj.server = MockONTAPConnection() + else: + obj.server = MockONTAPConnection(kind=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({}) + initiator() + + def test_get_nonexistent_initiator(self): + ''' Test if get_initiators returns None for non-existent initiator ''' + data = self.mock_args() + data['name'] = 'idontexist' + set_module_args(data) + result = self.get_initiator_mock_object('initiator').get_initiators() + assert data['name'] not in result + + def test_get_nonexistent_igroup(self): + ''' Test if get_initiators returns None for non-existent igroup ''' + data = self.mock_args() + data['name'] = 'idontexist' + set_module_args(data) + result = self.get_initiator_mock_object().get_initiators() + assert result == [] + + def test_get_existing_initiator(self): + ''' Test if get_initiator returns None for existing initiator ''' + data = self.mock_args() + set_module_args(data) + result = self.get_initiator_mock_object(kind='initiator').get_initiators() + assert data['name'] in result + assert result == ['init1', 'init2'] # from build_igroup_initiators() + + def test_successful_add(self): + ''' Test successful add''' + data = self.mock_args() + data['name'] = 'iamnew' + set_module_args(data) + obj = self.get_initiator_mock_object('initiator') + with pytest.raises(AnsibleExitJson) as exc: + current = obj.get_initiators() + obj.apply() + assert data['name'] not in current + assert exc.value.args[0]['changed'] + + def test_successful_add_idempotency(self): + ''' Test successful add idempotency ''' + data = self.mock_args() + set_module_args(data) + obj = self.get_initiator_mock_object('initiator') + with pytest.raises(AnsibleExitJson) as exc: + current_list = obj.get_initiators() + obj.apply() + assert data['name'] in current_list + assert not exc.value.args[0]['changed'] + + def test_successful_remove(self): + ''' Test successful remove ''' + data = self.mock_args() + data['state'] = 'absent' + set_module_args(data) + obj = self.get_initiator_mock_object('initiator') + with pytest.raises(AnsibleExitJson) as exc: + current_list = obj.get_initiators() + obj.apply() + assert data['name'] in current_list + assert exc.value.args[0]['changed'] + + def test_successful_remove_idempotency(self): + ''' Test successful remove idempotency''' + data = self.mock_args() + data['state'] = 'absent' + data['name'] = 'alreadyremoved' + set_module_args(data) + obj = self.get_initiator_mock_object('initiator') + with pytest.raises(AnsibleExitJson) as exc: + current_list = obj.get_initiators() + obj.apply() + assert data['name'] not in current_list + assert not exc.value.args[0]['changed'] + + def test_if_all_methods_catch_exception(self): + data = self.mock_args() + set_module_args(data) + my_obj = self.get_initiator_mock_object('initiator_fail') + with pytest.raises(AnsibleFailJson) as exc: + my_obj.get_initiators() + assert 'Error fetching igroup info ' in exc.value.args[0]['msg'] + with pytest.raises(AnsibleFailJson) as exc: + my_obj.modify_initiator(data['name'], 'igroup-add') + assert 'Error modifying igroup initiator ' in exc.value.args[0]['msg'] diff --git a/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_info.py b/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_info.py new file mode 100644 index 00000000..625fbb2b --- /dev/null +++ b/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_info.py @@ -0,0 +1,557 @@ +# (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_info ''' + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type +import json +import pytest +import sys + +from ansible_collections.netapp.ontap.tests.unit.compat import unittest +from ansible_collections.netapp.ontap.tests.unit.compat.mock import patch +from ansible.module_utils import basic +from ansible.module_utils._text import to_bytes +import ansible_collections.netapp.ontap.plugins.module_utils.netapp as netapp_utils + +from ansible_collections.netapp.ontap.plugins.modules.na_ontap_info import main as info_main +from ansible_collections.netapp.ontap.plugins.modules.na_ontap_info import __finditem as info_finditem +from ansible_collections.netapp.ontap.plugins.modules.na_ontap_info \ + import NetAppONTAPGatherInfo as info_module # module under test +from ansible_collections.netapp.ontap.plugins.modules.na_ontap_info \ + import convert_keys as info_convert_keys # function under test + +if not netapp_utils.has_netapp_lib(): + pytestmark = pytest.mark.skip('skipping as missing required netapp_lib') + + +def set_module_args(args): + """prepare arguments so that they will be picked up during module creation""" + args = json.dumps({'ANSIBLE_MODULE_ARGS': args}) + basic._ANSIBLE_ARGS = to_bytes(args) # pylint: disable=protected-access + + +class AnsibleExitJson(Exception): + """Exception class to be raised by module.exit_json and caught by the test case""" + pass + + +class AnsibleFailJson(Exception): + """Exception class to be raised by module.fail_json and caught by the test case""" + pass + + +def exit_json(*args, **kwargs): # pylint: disable=unused-argument + """function to patch over exit_json; package return data into an exception""" + if 'changed' not in kwargs: + kwargs['changed'] = False + raise AnsibleExitJson(kwargs) + + +def fail_json(*args, **kwargs): # pylint: disable=unused-argument + """function to patch over fail_json; package return data into an exception""" + kwargs['failed'] = True + raise AnsibleFailJson(kwargs) + + +class MockONTAPConnection(object): + ''' mock server connection to ONTAP host ''' + + def __init__(self, kind=None): + ''' save arguments ''' + self.type = kind + self.xml_in = None + self.xml_out = None + + def invoke_successfully(self, xml, enable_tunneling): # pylint: disable=unused-argument + ''' mock invoke_successfully returning xml data ''' + self.xml_in = xml + if self.type == 'vserver': + xml = self.build_vserver_info() + elif self.type == 'net_port': + xml = self.build_net_port_info() + elif self.type == 'net_port_no_ifgrp': + xml = self.build_net_port_info('no_ifgrp') + elif self.type == 'net_port_with_ifgrp': + xml = self.build_net_port_info('with_ifgrp') + # for the next calls + self.type = 'net_ifgrp' + elif self.type == 'net_ifgrp': + xml = self.build_net_ifgrp_info() + elif self.type == 'zapi_error': + error = netapp_utils.zapi.NaApiError('test', 'error') + raise error + elif self.type == 'list_of_one': + xml = self.list_of_one() + elif self.type == 'list_of_two': + xml = self.list_of_two() + elif self.type == 'list_of_two_dups': + xml = self.list_of_two_dups() + else: + raise KeyError(self.type) + self.xml_out = xml + return xml + + @staticmethod + 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': 'test_vserver'}) + xml.add_child_elem(attributes) + return xml + + @staticmethod + def build_net_port_info(with_type=None): + ''' 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)) + if with_type == 'with_ifgrp': + net_port_info.add_new_child('port_type', 'if_group') + elif with_type == 'no_ifgrp': + net_port_info.add_new_child('port_type', 'whatever') + attributes_list.add_child_elem(net_port_info) + xml.add_child_elem(attributes_list) + return xml + + @staticmethod + def build_net_ifgrp_info(): + ''' build xml data for net-ifgrp-info ''' + xml = netapp_utils.zapi.NaElement('xml') + attributes_list = netapp_utils.zapi.NaElement('attributes') + num_net_ifgrp_info = 2 + for i in range(num_net_ifgrp_info): + net_ifgrp_info = netapp_utils.zapi.NaElement('net-ifgrp-info') + net_ifgrp_info.add_new_child('ifgrp-name', 'ifgrp_' + str(i)) + net_ifgrp_info.add_new_child('node', 'node_' + str(i)) + attributes_list.add_child_elem(net_ifgrp_info) + xml.add_child_elem(attributes_list) + return xml + + @staticmethod + def list_of_one(): + ''' build xml data for list of one info element ''' + xml = netapp_utils.zapi.NaElement('xml') + list_of_one = [{'k1': 'v1', 'k2': 'v2'}] + xml.translate_struct(list_of_one) + return xml + + @staticmethod + def list_of_two(): + ''' build xml data for list of two info elements ''' + xml = netapp_utils.zapi.NaElement('xml') + list_of_two = [{'k1': 'v1'}, {'k2': 'v2'}] + xml.translate_struct(list_of_two) + return xml + + @staticmethod + def list_of_two_dups(): + ''' build xml data for list of two info elements with same key ''' + xml = netapp_utils.zapi.NaElement('xml') + list_of_two = [{'k1': 'v1'}, {'k1': 'v2'}] + xml.translate_struct(list_of_two) + return xml + + +class TestMyModule(unittest.TestCase): + ''' a group of related Unit Tests ''' + + def setUp(self): + self.mock_module_helper = patch.multiple(basic.AnsibleModule, + exit_json=exit_json, + fail_json=fail_json) + self.mock_module_helper.start() + self.addCleanup(self.mock_module_helper.stop) + self.server = MockONTAPConnection() + + def mock_args(self): + return { + 'hostname': 'hostname', + 'username': 'username', + 'password': 'password', + 'vserver': None + } + + def get_info_mock_object(self, kind=None): + """ + Helper method to return an na_ontap_info object + """ + argument_spec = netapp_utils.na_ontap_host_argument_spec() + argument_spec.update(dict( + state=dict(type='str', default='info', choices=['info']), + gather_subset=dict(default=['all'], type='list'), + vserver=dict(type='str', default=None, required=False), + max_records=dict(type='int', default=1024, required=False), + desired_attributes=dict(type='dict', required=False), + use_native_zapi_tags=dict(type='bool', required=False, default=False), + continue_on_error=dict(type='list', required=False, default=['never']), + query=dict(type='dict', required=False), + )) + module = basic.AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True + ) + max_records = module.params['max_records'] + obj = info_module(module, max_records) + 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({}) + self.get_info_mock_object() + print('Info: %s' % exc.value.args[0]['msg']) + + @patch('ansible_collections.netapp.ontap.plugins.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(self.mock_args()) + my_obj = self.get_info_mock_object('vserver') + with pytest.raises(KeyError) as exc: + my_obj.get_all(['net_interface_info']) + if sys.version_info >= (2, 7): + msg = 'net-interface-info' + print(exc.value.args[0]) + assert exc.value.args[0] == msg + + @patch('ansible_collections.netapp.ontap.plugins.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_info_mock_object('net_port') + result = obj.get_generic_get_iter( + 'net-port-get-iter', + attribute='net-port-info', + key_fields=('node', 'port'), + query={'max-records': '1024'} + ) + assert result.get('node_0:port_0') + assert result.get('node_1:port_1') + + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_info.NetAppONTAPGatherInfo.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: + info_main() + assert exc.value.args[0]['state'] == 'info' + + def test_get_ifgrp_info_no_ifgrp(self): + '''test get_ifgrp_info with empty ifgrp_info''' + set_module_args(self.mock_args()) + obj = self.get_info_mock_object('net_port_no_ifgrp') + result = obj.get_ifgrp_info() + assert result == {} + + def test_get_ifgrp_info_with_ifgrp(self): + '''test get_ifgrp_info with empty ifgrp_info''' + set_module_args(self.mock_args()) + obj = self.get_info_mock_object('net_port_with_ifgrp') + result = obj.get_ifgrp_info() + assert result.get('node_0:ifgrp_0') + assert result.get('node_1:ifgrp_1') + + def test_ontapi_error(self): + '''test ontapi will raise zapi error''' + set_module_args(self.mock_args()) + obj = self.get_info_mock_object('zapi_error') + with pytest.raises(AnsibleFailJson) as exc: + obj.ontapi() + # The new version of nettap-lib adds a space after : + # Keep both versions to keep the pipeline happy + 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_info_mock_object('zapi_error') + with pytest.raises(AnsibleFailJson) as exc: + obj.call_api('nvme-get-iter') + # The new version of nettap-lib adds a space after : + # Keep both versions to keep the pipeline happy + 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 = info_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 = '170' # change this if new ZAPIs are supported + set_module_args(self.mock_args()) + obj = self.get_info_mock_object('vserver') + subset = obj.get_subset(['all'], version) + assert set(obj.info_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_info_mock_object('vserver') + subset = obj.get_subset(['all'], version) + all_keys = obj.info_subsets.keys() + assert set(all_keys) > subset + supported_keys = filter(lambda key: obj.info_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_info_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_info_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_info_mock_object('vserver') + with pytest.raises(AnsibleFailJson) as exc: + 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_info_mock_object('vserver') + with pytest.raises(AnsibleFailJson) as exc: + 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_info_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 = '170' # change this if new ZAPIs are supported + set_module_args(self.mock_args()) + obj = self.get_info_mock_object('vserver') + subset = obj.get_subset(['!net_interface_info'], version) + assert len(obj.info_subsets.keys()) == len(subset) + 1 + subset.add('net_interface_info') + assert set(obj.info_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 = '170' # change this if new ZAPIs are supported + set_module_args(self.mock_args()) + obj = self.get_info_mock_object('vserver') + subset = obj.get_subset(['!net_interface_info', '!nvme_info', '!ontap_version'], version) + assert len(obj.info_subsets.keys()) == len(subset) + 3 + subset.update(['net_interface_info', 'nvme_info', 'ontap_version']) + assert set(obj.info_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_info_mock_object('vserver') + with pytest.raises(AnsibleFailJson) as exc: + 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 + + @patch('ansible_collections.netapp.ontap.plugins.module_utils.netapp.ems_log_event') + def test_get_generic_get_iter_flatten_list_of_one(self, mock_ems_log): + '''calling get_generic_get_iter will return expected dict''' + set_module_args(self.mock_args()) + obj = self.get_info_mock_object('list_of_one') + result = obj.get_generic_get_iter( + 'list_of_one', + attributes_list_tag=None, + ) + assert isinstance(result, dict) + assert result.get('k1') == 'v1' + assert result.get('k2') == 'v2' + + @patch('ansible_collections.netapp.ontap.plugins.module_utils.netapp.ems_log_event') + def test_get_generic_get_iter_flatten_list_of_two(self, mock_ems_log): + '''calling get_generic_get_iter will return expected dict''' + set_module_args(self.mock_args()) + obj = self.get_info_mock_object('list_of_two') + result = obj.get_generic_get_iter( + 'list_of_two', + attributes_list_tag=None, + ) + assert isinstance(result, dict) + assert result.get('k1') == 'v1' + assert result.get('k2') == 'v2' + + @patch('ansible_collections.netapp.ontap.plugins.module_utils.netapp.ems_log_event') + def test_get_generic_get_iter_flatten_list_of_two_dups(self, mock_ems_log): + '''calling get_generic_get_iter will return expected dict''' + set_module_args(self.mock_args()) + obj = self.get_info_mock_object('list_of_two_dups') + result = obj.get_generic_get_iter( + 'list_of_two_dups', + attributes_list_tag=None, + ) + assert isinstance(result, list) + assert result[0].get('k1') == 'v1' + assert result[1].get('k1') == 'v2' + + def test_check_underscore(self): + ''' Check warning is recorded if '_' is found in key ''' + test_dict = dict( + bad_key='something' + ) + test_dict['good-key'] = [dict( + other_bad_key=dict( + yet_another_bad_key=1 + ), + somekey=dict( + more_bad_key=2 + ) + )] + set_module_args(self.mock_args()) + obj = self.get_info_mock_object('vserver') + obj.check_for___in_keys(test_dict) + print('Info: %s' % repr(obj.warnings)) + for key in ['bad_key', 'other_bad_key', 'yet_another_bad_key', 'more_bad_key']: + msg = "Underscore in ZAPI tag: %s, do you mean '-'?" % key + assert msg in obj.warnings + obj.warnings.remove(msg) + # make sure there is no extra warnings (eg we found and removed all of them) + assert obj.warnings == list() + + @staticmethod + def d2us(astr): + return str.replace(astr, '-', '_') + + def test_convert_keys_string(self): + ''' no conversion ''' + key = 'a-b-c' + assert info_convert_keys(key) == key + + def test_convert_keys_tuple(self): + ''' no conversion ''' + key = 'a-b-c' + anobject = (key, key) + assert info_convert_keys(anobject) == anobject + + def test_convert_keys_list(self): + ''' no conversion ''' + key = 'a-b-c' + anobject = [key, key] + assert info_convert_keys(anobject) == anobject + + def test_convert_keys_simple_dict(self): + ''' conversion of keys ''' + key = 'a-b-c' + anobject = {key: 1} + assert list(info_convert_keys(anobject).keys())[0] == self.d2us(key) + + def test_convert_keys_list_of_dict(self): + ''' conversion of keys ''' + key = 'a-b-c' + anobject = [{key: 1}, {key: 2}] + converted = info_convert_keys(anobject) + for adict in converted: + for akey in adict: + assert akey == self.d2us(key) + + def test_set_error_flags_error_n(self): + ''' Check set_error__flags return correct dict ''' + args = dict(self.mock_args()) + args['continue_on_error'] = ['never', 'whatever'] + set_module_args(args) + with pytest.raises(AnsibleFailJson) as exc: + obj = self.get_info_mock_object('vserver') + print('Info: %s' % exc.value.args[0]['msg']) + msg = "never needs to be the only keyword in 'continue_on_error' option." + assert exc.value.args[0]['msg'] == msg + + def test_set_error_flags_error_a(self): + ''' Check set_error__flags return correct dict ''' + args = dict(self.mock_args()) + args['continue_on_error'] = ['whatever', 'always'] + set_module_args(args) + with pytest.raises(AnsibleFailJson) as exc: + obj = self.get_info_mock_object('vserver') + print('Info: %s' % exc.value.args[0]['msg']) + msg = "always needs to be the only keyword in 'continue_on_error' option." + assert exc.value.args[0]['msg'] == msg + + def test_set_error_flags_error_u(self): + ''' Check set_error__flags return correct dict ''' + args = dict(self.mock_args()) + args['continue_on_error'] = ['whatever', 'else'] + set_module_args(args) + with pytest.raises(AnsibleFailJson) as exc: + obj = self.get_info_mock_object('vserver') + print('Info: %s' % exc.value.args[0]['msg']) + msg = "whatever is not a valid keyword in 'continue_on_error' option." + assert exc.value.args[0]['msg'] == msg + + def test_set_error_flags_1_flag(self): + ''' Check set_error__flags return correct dict ''' + args = dict(self.mock_args()) + args['continue_on_error'] = ['missing_vserver_api_error'] + set_module_args(args) + obj = self.get_info_mock_object('vserver') + assert not obj.error_flags['missing_vserver_api_error'] + assert obj.error_flags['rpc_error'] + assert obj.error_flags['other_error'] + + def test_set_error_flags_2_flags(self): + ''' Check set_error__flags return correct dict ''' + args = dict(self.mock_args()) + args['continue_on_error'] = ['missing_vserver_api_error', 'rpc_error'] + set_module_args(args) + obj = self.get_info_mock_object('vserver') + assert not obj.error_flags['missing_vserver_api_error'] + assert not obj.error_flags['rpc_error'] + assert obj.error_flags['other_error'] + + def test_set_error_flags_3_flags(self): + ''' Check set_error__flags return correct dict ''' + args = dict(self.mock_args()) + args['continue_on_error'] = ['missing_vserver_api_error', 'rpc_error', 'other_error'] + set_module_args(args) + obj = self.get_info_mock_object('vserver') + assert not obj.error_flags['missing_vserver_api_error'] + assert not obj.error_flags['rpc_error'] + assert not obj.error_flags['other_error'] diff --git a/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_interface.py b/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_interface.py new file mode 100644 index 00000000..67c04c2c --- /dev/null +++ b/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_interface.py @@ -0,0 +1,312 @@ +# (c) 2018, NetApp, Inc +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +''' unit test template for ONTAP Ansible module ''' + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type +import json +import pytest + +from ansible_collections.netapp.ontap.tests.unit.compat import unittest +from ansible_collections.netapp.ontap.tests.unit.compat.mock import patch, Mock +from ansible.module_utils import basic +from ansible.module_utils._text import to_bytes +import ansible_collections.netapp.ontap.plugins.module_utils.netapp as netapp_utils + +from ansible_collections.netapp.ontap.plugins.modules.na_ontap_interface \ + import NetAppOntapInterface as interface_module # module under test + +if not netapp_utils.has_netapp_lib(): + pytestmark = pytest.mark.skip('skipping as missing required netapp_lib') + + +def set_module_args(args): + """prepare arguments so that they will be picked up during module creation""" + args = json.dumps({'ANSIBLE_MODULE_ARGS': args}) + basic._ANSIBLE_ARGS = to_bytes(args) # pylint: disable=protected-access + + +class AnsibleExitJson(Exception): + """Exception class to be raised by module.exit_json and caught by the test case""" + pass + + +class AnsibleFailJson(Exception): + """Exception class to be raised by module.fail_json and caught by the test case""" + pass + + +def exit_json(*args, **kwargs): # pylint: disable=unused-argument + """function to patch over exit_json; package return data into an exception""" + if 'changed' not in kwargs: + kwargs['changed'] = False + raise AnsibleExitJson(kwargs) + + +def fail_json(*args, **kwargs): # pylint: disable=unused-argument + """function to patch over fail_json; package return data into an exception""" + kwargs['failed'] = True + raise AnsibleFailJson(kwargs) + + +class MockONTAPConnection(object): + ''' mock server connection to ONTAP host ''' + + def __init__(self, kind=None, data=None): + ''' save arguments ''' + self.type = kind + self.params = data + self.xml_in = None + self.xml_out = None + + def invoke_successfully(self, xml, enable_tunneling): # pylint: disable=unused-argument + ''' mock invoke_successfully returning xml data ''' + self.xml_in = xml + if self.type == 'interface': + xml = self.build_interface_info(self.params) + elif self.type == 'zapi_error': + error = netapp_utils.zapi.NaApiError('test', 'error') + raise error + self.xml_out = xml + return xml + + @staticmethod + def build_interface_info(data): + ''' build xml data for vserser-info ''' + xml = netapp_utils.zapi.NaElement('xml') + attributes = { + 'num-records': 1, + 'attributes-list': { + 'net-interface-info': { + 'interface-name': data['name'], + 'administrative-status': data['administrative-status'], + 'failover-policy': data['failover-policy'], + 'firewall-policy': data['firewall-policy'], + 'is-auto-revert': data['is-auto-revert'], + 'home-node': data['home_node'], + 'home-port': data['home_port'], + 'address': data['address'], + 'netmask': data['netmask'], + 'role': data['role'], + 'protocols': data['protocols'] if data.get('protocols') else None, + 'dns-domain-name': data['dns_domain_name'], + 'listen-for-dns_query': data['listen_for_dns_query'], + 'is-dns-update-enabled': data['is_dns_update_enabled'] + } + } + } + xml.translate_struct(attributes) + return xml + + +class TestMyModule(unittest.TestCase): + ''' a group of related Unit Tests ''' + + def setUp(self): + self.mock_module_helper = patch.multiple(basic.AnsibleModule, + exit_json=exit_json, + fail_json=fail_json) + self.mock_module_helper.start() + self.addCleanup(self.mock_module_helper.stop) + self.mock_interface = { + 'name': 'test_lif', + 'administrative-status': 'up', + 'failover-policy': 'up', + 'firewall-policy': 'up', + 'is-auto-revert': 'true', + 'home_node': 'node', + 'role': 'data', + 'home_port': 'e0c', + 'address': '2.2.2.2', + 'netmask': '1.1.1.1', + 'dns_domain_name': 'test.com', + 'listen_for_dns_query': True, + 'is_dns_update_enabled': True, + 'admin_status': 'up' + } + + def mock_args(self): + return { + 'vserver': 'vserver', + 'interface_name': self.mock_interface['name'], + 'home_node': self.mock_interface['home_node'], + 'role': self.mock_interface['role'], + 'home_port': self.mock_interface['home_port'], + 'address': self.mock_interface['address'], + 'netmask': self.mock_interface['netmask'], + 'hostname': 'hostname', + 'username': 'username', + 'password': 'password', + } + + def get_interface_mock_object(self, kind=None): + """ + Helper method to return an na_ontap_interface object + :param kind: passes this param to MockONTAPConnection() + :return: na_ontap_interface object + """ + interface_obj = interface_module() + interface_obj.autosupport_log = Mock(return_value=None) + if kind is None: + interface_obj.server = MockONTAPConnection() + else: + interface_obj.server = MockONTAPConnection(kind=kind, data=self.mock_interface) + return interface_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({}) + interface_module() + print('Info: %s' % exc.value.args[0]['msg']) + + def test_create_error_missing_param(self): + ''' Test if create throws an error if required param 'role' is not specified''' + data = self.mock_args() + del data['role'] + set_module_args(data) + with pytest.raises(AnsibleFailJson) as exc: + self.get_interface_mock_object('interface').create_interface() + msg = 'Error: Missing one or more required parameters for creating interface: ' \ + 'home_port, netmask, role, home_node, address' + expected = sorted(','.split(msg)) + received = sorted(','.split(exc.value.args[0]['msg'])) + assert expected == received + + def test_get_nonexistent_interface(self): + ''' Test if get_interface returns None for non-existent interface ''' + set_module_args(self.mock_args()) + result = self.get_interface_mock_object().get_interface() + assert result is None + + def test_get_existing_interface(self): + ''' Test if get_interface returns None for existing interface ''' + set_module_args(self.mock_args()) + result = self.get_interface_mock_object(kind='interface').get_interface() + assert result['interface_name'] == self.mock_interface['name'] + + def test_successful_create(self): + ''' Test successful create ''' + set_module_args(self.mock_args()) + with pytest.raises(AnsibleExitJson) as exc: + self.get_interface_mock_object().apply() + assert exc.value.args[0]['changed'] + + def test_successful_create_for_NVMe(self): + ''' Test successful create for NVMe protocol''' + data = self.mock_args() + data['protocols'] = 'fc-nvme' + del data['address'] + del data['netmask'] + del data['home_port'] + set_module_args(data) + with pytest.raises(AnsibleExitJson) as exc: + self.get_interface_mock_object().apply() + assert exc.value.args[0]['changed'] + + def test_create_idempotency_for_NVMe(self): + ''' Test create idempotency for NVMe protocol ''' + data = self.mock_args() + data['protocols'] = 'fc-nvme' + del data['address'] + del data['netmask'] + del data['home_port'] + set_module_args(data) + with pytest.raises(AnsibleExitJson) as exc: + self.get_interface_mock_object('interface').apply() + assert not exc.value.args[0]['changed'] + + def test_create_error_for_NVMe(self): + ''' Test if create throws an error if required param 'protocols' uses NVMe''' + data = self.mock_args() + data['protocols'] = 'fc-nvme' + set_module_args(data) + with pytest.raises(AnsibleFailJson) as exc: + self.get_interface_mock_object('interface').create_interface() + msg = 'Error: Following parameters for creating interface are not supported for data-protocol fc-nvme: ' \ + 'netmask, firewall_policy, address' + expected = sorted(','.split(msg)) + received = sorted(','.split(exc.value.args[0]['msg'])) + assert expected == received + + def test_create_idempotency(self): + ''' Test create idempotency ''' + set_module_args(self.mock_args()) + with pytest.raises(AnsibleExitJson) as exc: + self.get_interface_mock_object('interface').apply() + assert not exc.value.args[0]['changed'] + + def test_successful_delete(self): + ''' Test delete existing interface ''' + data = self.mock_args() + data['state'] = 'absent' + set_module_args(data) + with pytest.raises(AnsibleExitJson) as exc: + self.get_interface_mock_object('interface').apply() + assert exc.value.args[0]['changed'] + + def test_delete_idempotency(self): + ''' Test delete idempotency ''' + data = self.mock_args() + data['state'] = 'absent' + set_module_args(data) + with pytest.raises(AnsibleExitJson) as exc: + self.get_interface_mock_object().apply() + assert not exc.value.args[0]['changed'] + + def test_successful_modify(self): + ''' Test successful modify interface_minutes ''' + data = self.mock_args() + data['home_port'] = 'new_port' + data['dns_domain_name'] = 'test2.com' + data['listen_for_dns_query'] = False + data['is_dns_update_enabled'] = False + set_module_args(data) + with pytest.raises(AnsibleExitJson) as exc: + interface_obj = self.get_interface_mock_object('interface') + interface_obj.apply() + assert exc.value.args[0]['changed'] + + def test_modify_idempotency(self): + ''' Test modify idempotency ''' + data = self.mock_args() + set_module_args(data) + with pytest.raises(AnsibleExitJson) as exc: + self.get_interface_mock_object('interface').apply() + assert not exc.value.args[0]['changed'] + + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_interface.NetAppOntapInterface.get_interface') + def test_error_message(self, get_interface): + ''' Test modify idempotency ''' + data = self.mock_args() + set_module_args(data) + get_interface.side_effect = [None] + with pytest.raises(AnsibleFailJson) as exc: + self.get_interface_mock_object('zapi_error').apply() + assert exc.value.args[0]['msg'] == 'Error Creating interface test_lif: NetApp API failed. Reason - test:error' + + data = self.mock_args() + data['home_port'] = 'new_port' + data['dns_domain_name'] = 'test2.com' + data['listen_for_dns_query'] = False + data['is_dns_update_enabled'] = False + set_module_args(data) + get_interface.side_effect = [ + self.mock_interface + ] + with pytest.raises(AnsibleFailJson) as exc: + self.get_interface_mock_object('zapi_error').apply() + assert exc.value.args[0]['msg'] == 'Error modifying interface test_lif: NetApp API failed. Reason - test:error' + + data = self.mock_args() + data['state'] = 'absent' + set_module_args(data) + current = self.mock_interface + current['admin_status'] = 'down' + get_interface.side_effect = [ + current + ] + with pytest.raises(AnsibleFailJson) as exc: + self.get_interface_mock_object('zapi_error').apply() + assert exc.value.args[0]['msg'] == 'Error deleting interface test_lif: NetApp API failed. Reason - test:error' diff --git a/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_ipspace.py b/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_ipspace.py new file mode 100644 index 00000000..641e1414 --- /dev/null +++ b/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_ipspace.py @@ -0,0 +1,269 @@ +# (c) 2018, NTT Europe Ltd. +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +""" unit test for Ansible module: na_ontap_ipspace """ + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type +import json +import pytest + +from ansible.module_utils import basic +from ansible.module_utils._text import to_bytes +from ansible_collections.netapp.ontap.tests.unit.compat import unittest +from ansible_collections.netapp.ontap.tests.unit.compat.mock import patch +import ansible_collections.netapp.ontap.plugins.module_utils.netapp as netapp_utils + +from ansible_collections.netapp.ontap.plugins.modules.na_ontap_ipspace \ + import NetAppOntapIpspace as my_module # module under test + +if not netapp_utils.has_netapp_lib(): + pytestmark = pytest.mark.skip('skipping as missing required netapp_lib') + +# REST API canned responses when mocking send_request +SRR = { + # common responses + 'is_rest': (200, {}, None), + 'is_zapi': (400, {}, "Unreachable"), + 'empty_good': (200, {}, None), + 'end_of_sequence': (500, None, "Ooops, the UT needs one more SRR response"), + 'generic_error': (400, None, "Expected error"), + # module specific responses + 'ipspace_record': (200, {'records': [{"name": "test_ipspace", + "uuid": "1cd8a442-86d1-11e0-ae1c-123478563412"}]}, None) +} + + +def set_module_args(args): + """prepare arguments so that they will be picked up during module creation""" + args = json.dumps({'ANSIBLE_MODULE_ARGS': args}) + basic._ANSIBLE_ARGS = to_bytes(args) # pylint: disable=protected-access + + +class AnsibleExitJson(Exception): + """Exception class to be raised by module.exit_json and caught by the test case""" + + +class AnsibleFailJson(Exception): + """Exception class to be raised by module.fail_json and caught by the test case""" + + +def exit_json(*args, **kwargs): # pylint: disable=unused-argument + """function to patch over exit_json; package return data into an exception""" + if 'changed' not in kwargs: + kwargs['changed'] = False + raise AnsibleExitJson(kwargs) + + +def fail_json(*args, **kwargs): # pylint: disable=unused-argument + """function to patch over fail_json; package return data into an exception""" + kwargs['failed'] = True + raise AnsibleFailJson(kwargs) + + +class MockONTAPConnection(object): + ''' mock server connection to ONTAP host ''' + def __init__(self, kind=None, parm1=None): + ''' save arguments ''' + self.type = kind + self.parm1 = parm1 + self.xml_in = None + self.xml_out = None + + def invoke_successfully(self, xml, enable_tunneling): # pylint: disable=unused-argument + ''' mock invoke_successfully returning xml data ''' + self.xml_in = xml + if self.type == 'ipspace': + xml = self.build_ipspace_info(self.parm1) + self.xml_out = xml + return xml + + @staticmethod + def build_ipspace_info(ipspace): + ''' build xml data for ipspace ''' + xml = netapp_utils.zapi.NaElement('xml') + data = {'num-records': 1, + 'attributes-list': {'net-ipspaces-info': {'ipspace': ipspace}}} + xml.translate_struct(data) + print(xml.to_string()) + return xml + + +class TestMyModule(unittest.TestCase): + ''' a group of related Unit Tests ''' + + def setUp(self): + self.mock_module_helper = patch.multiple(basic.AnsibleModule, + exit_json=exit_json, + fail_json=fail_json) + self.mock_module_helper.start() + self.addCleanup(self.mock_module_helper.stop) + self.server = MockONTAPConnection() + + def set_default_args(self): + return dict({ + 'name': 'test_ipspace', + 'hostname': 'hostname', + 'username': 'username', + 'password': 'password' + }) + + @staticmethod + def get_ipspace_mock_object(cx_type='zapi', kind=None, status=None): + ipspace_obj = my_module() + if cx_type == 'zapi': + if kind is None: + ipspace_obj.server = MockONTAPConnection() + else: + ipspace_obj.server = MockONTAPConnection(kind=kind, parm1=status) + return ipspace_obj + + def test_fail_requiredargs_missing(self): + ''' required arguments are reported as errors ''' + with pytest.raises(AnsibleFailJson) as exc: + set_module_args({}) + my_module() + print('Info: %s' % exc.value.args[0]['msg']) + + @patch('ansible_collections.netapp.ontap.plugins.module_utils.netapp.OntapRestAPI.send_request') + def test_get_ipspace_iscalled(self, mock_request): + ''' test if get_ipspace() is called ''' + mock_request.side_effect = [ + SRR['is_zapi'], + SRR['end_of_sequence'] + ] + set_module_args(self.set_default_args()) + my_obj = my_module() + my_obj.server = self.server + ipspace = my_obj.get_ipspace() + print('Info: test_get_ipspace: %s' % repr(ipspace)) + assert ipspace is None + + @patch('ansible_collections.netapp.ontap.plugins.module_utils.netapp.OntapRestAPI.send_request') + def test_ipspace_apply_iscalled(self, mock_request): + ''' test if apply() is called ''' + mock_request.side_effect = [ + SRR['is_zapi'], + SRR['end_of_sequence'] + ] + module_args = {'name': 'test_apply_ips'} + module_args.update(self.set_default_args()) + set_module_args(module_args) + my_obj = my_module() + my_obj.server = self.server + ipspace = my_obj.get_ipspace() + print('Info: test_get_ipspace: %s' % repr(ipspace)) + assert ipspace is None + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + print('Info: test_ipspace_apply: %s' % repr(exc.value)) + assert exc.value.args[0]['changed'] + my_obj.server = MockONTAPConnection('ipspace', 'test_apply_ips') + ipspace = my_obj.get_ipspace() + print('Info: test_get_ipspace: %s' % repr(ipspace)) + assert ipspace is not None + assert ipspace['name'] == 'test_apply_ips' + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + print('Info: test_ipspace_apply: %s' % repr(exc.value)) + assert exc.value.args[0]['changed'] + ipspace = my_obj.get_ipspace() + assert ipspace['name'] == 'test_apply_ips' + + @patch('ansible_collections.netapp.ontap.plugins.module_utils.netapp.OntapRestAPI.send_request') + def test_rest_error(self, mock_request): + data = self.set_default_args() + set_module_args(data) + mock_request.side_effect = [ + SRR['is_rest'], + SRR['generic_error'], + SRR['end_of_sequence'] + ] + with pytest.raises(AnsibleFailJson) as exc: + self.get_ipspace_mock_object(cx_type='rest').apply() + assert exc.value.args[0]['msg'] == SRR['generic_error'][2] + + @patch('ansible_collections.netapp.ontap.plugins.module_utils.netapp.OntapRestAPI.send_request') + def test_successful_create_rest(self, mock_request): + data = self.set_default_args() + set_module_args(data) + mock_request.side_effect = [ + SRR['is_rest'], + SRR['empty_good'], # get + SRR['empty_good'], # post + SRR['end_of_sequence'] + ] + with pytest.raises(AnsibleExitJson) as exc: + self.get_ipspace_mock_object(cx_type='rest').apply() + assert exc.value.args[0]['changed'] + + @patch('ansible_collections.netapp.ontap.plugins.module_utils.netapp.OntapRestAPI.send_request') + def test_idempotent_create_rest(self, mock_request): + data = self.set_default_args() + set_module_args(data) + mock_request.side_effect = [ + SRR['is_rest'], + SRR['ipspace_record'], # get + SRR['end_of_sequence'] + ] + with pytest.raises(AnsibleExitJson) as exc: + self.get_ipspace_mock_object(cx_type='rest').apply() + assert not exc.value.args[0]['changed'] + + @patch('ansible_collections.netapp.ontap.plugins.module_utils.netapp.OntapRestAPI.send_request') + def test_successful_delete_rest(self, mock_request): + data = self.set_default_args() + data['state'] = 'absent' + set_module_args(data) + mock_request.side_effect = [ + SRR['is_rest'], + SRR['ipspace_record'], # get + SRR['empty_good'], # delete + SRR['end_of_sequence'] + ] + with pytest.raises(AnsibleExitJson) as exc: + self.get_ipspace_mock_object(cx_type='rest').apply() + assert exc.value.args[0]['changed'] + + @patch('ansible_collections.netapp.ontap.plugins.module_utils.netapp.OntapRestAPI.send_request') + def test_idempotent_delete_rest(self, mock_request): + data = self.set_default_args() + data['state'] = 'absent' + set_module_args(data) + mock_request.side_effect = [ + SRR['is_rest'], + SRR['empty_good'], # get + SRR['end_of_sequence'] + ] + with pytest.raises(AnsibleExitJson) as exc: + self.get_ipspace_mock_object(cx_type='rest').apply() + assert not exc.value.args[0]['changed'] + + @patch('ansible_collections.netapp.ontap.plugins.module_utils.netapp.OntapRestAPI.send_request') + def test_successful_modify_rest(self, mock_request): + data = self.set_default_args() + data['state'] = 'present' + set_module_args(data) + mock_request.side_effect = [ + SRR['is_rest'], + SRR['empty_good'], # get + SRR['empty_good'], # patch + SRR['end_of_sequence'] + ] + with pytest.raises(AnsibleExitJson) as exc: + self.get_ipspace_mock_object(cx_type='rest').apply() + assert exc.value.args[0]['changed'] + + @patch('ansible_collections.netapp.ontap.plugins.module_utils.netapp.OntapRestAPI.send_request') + def test_idempotent_modify_rest(self, mock_request): + data = self.set_default_args() + data['state'] = 'present' + set_module_args(data) + mock_request.side_effect = [ + SRR['is_rest'], + SRR['ipspace_record'], # get + SRR['end_of_sequence'] + ] + with pytest.raises(AnsibleExitJson) as exc: + self.get_ipspace_mock_object(cx_type='rest').apply() + assert not exc.value.args[0]['changed'] diff --git a/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_iscsi_security.py b/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_iscsi_security.py new file mode 100644 index 00000000..bb31fd65 --- /dev/null +++ b/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_iscsi_security.py @@ -0,0 +1,256 @@ +# (c) 2018, NetApp, Inc +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +''' unit tests for Ansible module: na_ontap_iscsi_security ''' + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type +import json +import pytest + +from ansible.module_utils import basic +from ansible.module_utils._text import to_bytes +from ansible_collections.netapp.ontap.tests.unit.compat import unittest +from ansible_collections.netapp.ontap.tests.unit.compat.mock import patch + +from ansible_collections.netapp.ontap.plugins.modules.na_ontap_iscsi_security \ + import NetAppONTAPIscsiSecurity as iscsi_module # module under test + +# REST API canned responses when mocking send_request +SRR = { + # common responses + 'is_rest': (200, {}, None), + 'is_zapi': (400, {}, "Unreachable"), + 'empty_good': (200, {}, None), + 'end_of_sequence': (500, None, "Unexpected call to send_request"), + 'generic_error': (400, None, "Expected error"), + # module specific responses + 'get_uuid': ( + 200, + { + "records": [ + { + "uuid": "e2e89ccc-db35-11e9-0000-000000000000" + } + ], + "num_records": 1 + }, None), + 'get_initiator': ( + 200, + { + "records": [ + { + "svm": { + "uuid": "e2e89ccc-db35-11e9-0000-000000000000", + "name": "test_ansible" + }, + "initiator": "eui.0123456789abcdef", + "authentication_type": "chap", + "chap": { + "inbound": { + "user": "test_user_1" + }, + "outbound": { + "user": "test_user_2" + } + }, + "initiator_address": { + "ranges": [ + { + "start": "10.125.10.0", + "end": "10.125.10.10", + "family": "ipv4" + }, + { + "start": "10.10.10.7", + "end": "10.10.10.7", + "family": "ipv4" + } + ] + } + } + ], + "num_records": 1 + }, None), + "no_record": ( + 200, + { + "num_records": 0 + }, None) +} + + +def set_module_args(args): + """prepare arguments so that they will be picked up during module creation""" + args = json.dumps({'ANSIBLE_MODULE_ARGS': args}) + basic._ANSIBLE_ARGS = to_bytes(args) # pylint: disable=protected-access + + +class AnsibleExitJson(Exception): + """Exception class to be raised by module.exit_json and caught by the test case""" + + +class AnsibleFailJson(Exception): + """Exception class to be raised by module.fail_json and caught by the test case""" + + +def exit_json(*args, **kwargs): # pylint: disable=unused-argument + """function to patch over exit_json; package return data into an exception""" + if 'changed' not in kwargs: + kwargs['changed'] = False + raise AnsibleExitJson(kwargs) + + +def fail_json(*args, **kwargs): # pylint: disable=unused-argument + """function to patch over fail_json; package return data into an exception""" + kwargs['failed'] = True + raise AnsibleFailJson(kwargs) + + +class TestMyModule(unittest.TestCase): + ''' Unit tests for na_ontap_iscsi_security ''' + + def setUp(self): + self.mock_module_helper = patch.multiple(basic.AnsibleModule, + exit_json=exit_json, + fail_json=fail_json) + self.mock_module_helper.start() + self.addCleanup(self.mock_module_helper.stop) + self.mock_iscsi = { + "initiator": "eui.0123456789abcdef", + "inbound_username": "test_user_1", + "inbound_password": "123", + "outbound_username": "test_user_2", + "outbound_password": "321", + "auth_type": "chap", + "address_ranges": ["10.125.10.0-10.125.10.10", "10.10.10.7"] + } + + def mock_args(self): + return { + 'initiator': self.mock_iscsi['initiator'], + 'inbound_username': self.mock_iscsi['inbound_username'], + 'inbound_password': self.mock_iscsi['inbound_password'], + 'outbound_username': self.mock_iscsi['outbound_username'], + 'outbound_password': self.mock_iscsi['outbound_password'], + 'auth_type': self.mock_iscsi['auth_type'], + 'address_ranges': self.mock_iscsi['address_ranges'], + 'hostname': 'test', + 'vserver': 'test_vserver', + 'username': 'test_user', + 'password': 'test_pass!' + } + + def get_iscsi_mock_object(self): + """ + Helper method to return an na_ontap_iscsi_security object + :return: na_ontap_iscsi_security object + """ + iscsi_obj = iscsi_module() + return iscsi_obj + + @patch('ansible_collections.netapp.ontap.plugins.module_utils.netapp.OntapRestAPI.send_request') + def test_rest_successful_create(self, mock_request): + '''Test successful rest create''' + data = self.mock_args() + set_module_args(data) + mock_request.side_effect = [ + SRR['get_uuid'], + SRR['no_record'], + SRR['empty_good'], + SRR['end_of_sequence'] + ] + with pytest.raises(AnsibleExitJson) as exc: + self.get_iscsi_mock_object().apply() + assert exc.value.args[0]['changed'] + + @patch('ansible_collections.netapp.ontap.plugins.module_utils.netapp.OntapRestAPI.send_request') + def test_rest_create_idempotency(self, mock_request): + '''Test rest create idempotency''' + data = self.mock_args() + set_module_args(data) + mock_request.side_effect = [ + SRR['get_uuid'], + SRR['get_initiator'], + SRR['empty_good'], + SRR['end_of_sequence'] + ] + with pytest.raises(AnsibleExitJson) as exc: + self.get_iscsi_mock_object().apply() + assert not exc.value.args[0]['changed'] + + @patch('ansible_collections.netapp.ontap.plugins.module_utils.netapp.OntapRestAPI.send_request') + def test_rest_successful_modify_address(self, mock_request): + '''Test successful rest modify''' + data = self.mock_args() + data['address_ranges'] = ['10.10.10.8'] + set_module_args(data) + mock_request.side_effect = [ + SRR['get_uuid'], + SRR['get_initiator'], + SRR['empty_good'], + SRR['end_of_sequence'] + ] + with pytest.raises(AnsibleExitJson) as exc: + self.get_iscsi_mock_object().apply() + assert exc.value.args[0]['changed'] + + @patch('ansible_collections.netapp.ontap.plugins.module_utils.netapp.OntapRestAPI.send_request') + def test_rest_successful_modify_user(self, mock_request): + '''Test successful rest modify''' + data = self.mock_args() + data['inbound_username'] = 'test_user_3' + set_module_args(data) + mock_request.side_effect = [ + SRR['get_uuid'], + SRR['get_initiator'], + SRR['empty_good'], + SRR['end_of_sequence'] + ] + with pytest.raises(AnsibleExitJson) as exc: + self.get_iscsi_mock_object().apply() + assert exc.value.args[0]['changed'] + + @patch('ansible_collections.netapp.ontap.plugins.module_utils.netapp.OntapRestAPI.send_request') + def test_rest_error(self, mock_request): + '''Test rest error''' + data = self.mock_args() + set_module_args(data) + mock_request.side_effect = [ + SRR['get_uuid'], + SRR['no_record'], + SRR['generic_error'], + SRR['empty_good'], + SRR['end_of_sequence'] + ] + with pytest.raises(AnsibleFailJson) as exc: + self.get_iscsi_mock_object().apply() + assert 'Error on creating initiator: Expected error' in exc.value.args[0]['msg'] + + data = self.mock_args() + data['inbound_username'] = 'test_user_3' + set_module_args(data) + mock_request.side_effect = [ + SRR['get_uuid'], + SRR['get_initiator'], + SRR['generic_error'], + SRR['empty_good'], + SRR['end_of_sequence'] + ] + with pytest.raises(AnsibleFailJson) as exc: + self.get_iscsi_mock_object().apply() + assert 'Error on modifying initiator: Expected error' in exc.value.args[0]['msg'] + + data = self.mock_args() + data['state'] = 'absent' + set_module_args(data) + mock_request.side_effect = [ + SRR['get_uuid'], + SRR['get_initiator'], + SRR['generic_error'], + SRR['empty_good'], + SRR['end_of_sequence'] + ] + with pytest.raises(AnsibleFailJson) as exc: + self.get_iscsi_mock_object().apply() + assert 'Error on deleting initiator: Expected error' in exc.value.args[0]['msg'] diff --git a/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_job_schedule.py b/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_job_schedule.py new file mode 100644 index 00000000..c7196677 --- /dev/null +++ b/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_job_schedule.py @@ -0,0 +1,369 @@ +# (c) 2018, NetApp, Inc +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +''' unit tests for Ansible module: na_ontap_job_schedule ''' + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type +import json +import pytest + +from ansible.module_utils import basic +from ansible.module_utils._text import to_bytes +from ansible_collections.netapp.ontap.tests.unit.compat import unittest +from ansible_collections.netapp.ontap.tests.unit.compat.mock import patch, Mock +import ansible_collections.netapp.ontap.plugins.module_utils.netapp as netapp_utils + +from ansible_collections.netapp.ontap.plugins.modules.na_ontap_job_schedule \ + import NetAppONTAPJob as job_module # module under test + +if not netapp_utils.has_netapp_lib(): + pytestmark = pytest.mark.skip('skipping as missing required netapp_lib') + +# REST API canned responses when mocking send_request +SRR = { + # common responses + 'is_rest': (200, {}, None), + 'is_zapi': (400, {}, "Unreachable"), + 'empty_good': (200, {}, None), + 'end_of_sequence': (500, None, "Unexpected call to send_request"), + 'generic_error': (400, None, "Expected error"), + # module specific responses + 'get_schedule': ( + 200, + { + "records": [ + { + "uuid": "010df156-e0a9-11e9-9f70-005056b3df08", + "name": "test_job", + "cron": { + "minutes": [ + 25 + ], + "hours": [ + 0 + ], + "weekdays": [ + 0 + ] + } + } + ], + "num_records": 1 + }, None), + "no_record": ( + 200, + {"num_records": 0}, + None) +} + + +def set_module_args(args): + """prepare arguments so that they will be picked up during module creation""" + args = json.dumps({'ANSIBLE_MODULE_ARGS': args}) + basic._ANSIBLE_ARGS = to_bytes(args) # pylint: disable=protected-access + + +class AnsibleExitJson(Exception): + """Exception class to be raised by module.exit_json and caught by the test case""" + + +class AnsibleFailJson(Exception): + """Exception class to be raised by module.fail_json and caught by the test case""" + + +def exit_json(*args, **kwargs): # pylint: disable=unused-argument + """function to patch over exit_json; package return data into an exception""" + if 'changed' not in kwargs: + kwargs['changed'] = False + raise AnsibleExitJson(kwargs) + + +def fail_json(*args, **kwargs): # pylint: disable=unused-argument + """function to patch over fail_json; package return data into an exception""" + kwargs['failed'] = True + raise AnsibleFailJson(kwargs) + + +class MockONTAPConnection(object): + ''' mock server connection to ONTAP host ''' + + def __init__(self, kind=None, data=None): + ''' save arguments ''' + self.kind = kind + self.params = data + self.xml_in = None + self.xml_out = None + + def invoke_successfully(self, xml, enable_tunneling): # pylint: disable=unused-argument + ''' mock invoke_successfully returning xml data ''' + self.xml_in = xml + if self.kind == 'job': + xml = self.build_job_schedule_cron_info(self.params) + elif self.kind == 'job_multiple': + xml = self.build_job_schedule_multiple_cron_info(self.params) + self.xml_out = xml + return xml + + def autosupport_log(self): + ''' Mock autosupport log method, returns None ''' + return None + + @staticmethod + def build_job_schedule_cron_info(job_details): + ''' build xml data for vserser-info ''' + xml = netapp_utils.zapi.NaElement('xml') + attributes = { + 'num-records': 1, + 'attributes-list': { + 'job-schedule-cron-info': { + 'job-schedule-name': job_details['name'], + 'job-schedule-cron-minute': { + 'cron-minute': job_details['minutes'] + } + } + } + } + xml.translate_struct(attributes) + return xml + + @staticmethod + def build_job_schedule_multiple_cron_info(job_details): + ''' build xml data for vserser-info ''' + print("CALLED MULTIPLE BUILD") + xml = netapp_utils.zapi.NaElement('xml') + attributes = { + 'num-records': 1, + 'attributes-list': { + 'job-schedule-cron-info': { + 'job-schedule-name': job_details['name'], + 'job-schedule-cron-minute': [ + {'cron-minute': '25'}, + {'cron-minute': '35'} + ], + 'job-schedule-cron-month': [ + {'cron-month': '5'}, + {'cron-month': '10'} + ] + } + } + } + xml.translate_struct(attributes) + return xml + + +class TestMyModule(unittest.TestCase): + ''' Unit tests for na_ontap_job_schedule ''' + + def setUp(self): + self.mock_module_helper = patch.multiple(basic.AnsibleModule, + exit_json=exit_json, + fail_json=fail_json) + self.mock_module_helper.start() + self.addCleanup(self.mock_module_helper.stop) + self.mock_job = { + 'name': 'test_job', + 'minutes': '25', + 'job_hours': ['0'], + 'weekdays': ['0'] + } + + def mock_args(self, rest=False): + if rest: + return { + 'name': self.mock_job['name'], + 'job_minutes': [self.mock_job['minutes']], + 'job_hours': self.mock_job['job_hours'], + 'job_days_of_week': self.mock_job['weekdays'], + 'hostname': 'test', + 'username': 'test_user', + 'password': 'test_pass!' + } + else: + return { + 'name': self.mock_job['name'], + 'job_minutes': [self.mock_job['minutes']], + 'hostname': 'test', + 'username': 'test_user', + 'password': 'test_pass!', + 'use_rest': 'never' + } + + def get_job_mock_object(self, kind=None, call_type='zapi'): + """ + Helper method to return an na_ontap_job_schedule object + :param kind: passes this param to MockONTAPConnection() + :param call_type: + :return: na_ontap_job_schedule object + """ + job_obj = job_module() + job_obj.autosupport_log = Mock(return_value=None) + if call_type == 'zapi': + if kind is None: + job_obj.server = MockONTAPConnection() + else: + job_obj.server = MockONTAPConnection(kind=kind, data=self.mock_job) + return job_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({}) + job_module() + print('Info: %s' % exc.value.args[0]['msg']) + + def test_get_nonexistent_job(self): + ''' Test if get_job_schedule returns None for non-existent job ''' + set_module_args(self.mock_args()) + result = self.get_job_mock_object().get_job_schedule() + assert result is None + + def test_get_existing_job(self): + ''' Test if get_job_schedule retuns job details for existing job ''' + data = self.mock_args() + set_module_args(data) + result = self.get_job_mock_object('job').get_job_schedule() + assert result['name'] == self.mock_job['name'] + assert result['job_minutes'] == data['job_minutes'] + + def test_get_existing_job_multiple_minutes(self): + ''' Test if get_job_schedule retuns job details for existing job ''' + set_module_args(self.mock_args()) + result = self.get_job_mock_object('job_multiple').get_job_schedule() + print(str(result)) + assert result['name'] == self.mock_job['name'] + assert result['job_minutes'] == ['25', '35'] + assert result['job_months'] == ['5', '10'] + + def test_create_error_missing_param(self): + ''' Test if create throws an error if job_minutes is not specified''' + data = self.mock_args() + del data['job_minutes'] + set_module_args(data) + with pytest.raises(AnsibleFailJson) as exc: + self.get_job_mock_object('job').create_job_schedule() + msg = 'Error: missing required parameter job_minutes for create' + assert exc.value.args[0]['msg'] == msg + + def test_successful_create(self): + ''' Test successful create ''' + set_module_args(self.mock_args()) + with pytest.raises(AnsibleExitJson) as exc: + self.get_job_mock_object().apply() + assert exc.value.args[0]['changed'] + + def test_create_idempotency(self): + ''' Test create idempotency ''' + set_module_args(self.mock_args()) + with pytest.raises(AnsibleExitJson) as exc: + self.get_job_mock_object('job').apply() + assert not exc.value.args[0]['changed'] + + def test_successful_delete(self): + ''' Test delete existing job ''' + data = self.mock_args() + data['state'] = 'absent' + set_module_args(data) + with pytest.raises(AnsibleExitJson) as exc: + self.get_job_mock_object('job').apply() + assert exc.value.args[0]['changed'] + + def test_delete_idempotency(self): + ''' Test delete idempotency ''' + data = self.mock_args() + data['state'] = 'absent' + set_module_args(data) + with pytest.raises(AnsibleExitJson) as exc: + self.get_job_mock_object().apply() + assert not exc.value.args[0]['changed'] + + def test_successful_modify(self): + ''' Test successful modify job_minutes ''' + data = self.mock_args() + data['job_minutes'] = ['20'] + set_module_args(data) + with pytest.raises(AnsibleExitJson) as exc: + self.get_job_mock_object('job').apply() + assert exc.value.args[0]['changed'] + + def test_modify_idempotency(self): + ''' Test modify idempotency ''' + data = self.mock_args() + set_module_args(data) + with pytest.raises(AnsibleExitJson) as exc: + self.get_job_mock_object('job').apply() + assert not exc.value.args[0]['changed'] + + @patch('ansible_collections.netapp.ontap.plugins.module_utils.netapp.OntapRestAPI.send_request') + def test_rest_successful_create(self, mock_request): + '''Test successful rest create''' + data = self.mock_args(rest=True) + set_module_args(data) + mock_request.side_effect = [ + SRR['is_rest'], + SRR['no_record'], + SRR['empty_good'], + SRR['end_of_sequence'] + ] + with pytest.raises(AnsibleExitJson) as exc: + self.get_job_mock_object(call_type='rest').apply() + assert exc.value.args[0]['changed'] + + @patch('ansible_collections.netapp.ontap.plugins.module_utils.netapp.OntapRestAPI.send_request') + def test_rest_create_idempotency(self, mock_request): + '''Test rest create idempotency''' + data = self.mock_args(rest=True) + set_module_args(data) + mock_request.side_effect = [ + SRR['is_rest'], + SRR['get_schedule'], + SRR['empty_good'], + SRR['end_of_sequence'] + ] + with pytest.raises(AnsibleExitJson) as exc: + self.get_job_mock_object(call_type='rest').apply() + assert not exc.value.args[0]['changed'] + + @patch('ansible_collections.netapp.ontap.plugins.module_utils.netapp.OntapRestAPI.send_request') + def test_rest_error(self, mock_request): + '''Test rest create idempotency''' + data = self.mock_args(rest=True) + set_module_args(data) + mock_request.side_effect = [ + SRR['is_rest'], + SRR['no_record'], + SRR['generic_error'], + SRR['empty_good'], + SRR['end_of_sequence'] + ] + with pytest.raises(AnsibleFailJson) as exc: + self.get_job_mock_object(call_type='rest').apply() + assert 'Error on creating job schedule: Expected error' in exc.value.args[0]['msg'] + + data = self.mock_args(rest=True) + data['job_minutes'] = ['20'] + set_module_args(data) + mock_request.side_effect = [ + SRR['is_rest'], + SRR['get_schedule'], + SRR['generic_error'], + SRR['empty_good'], + SRR['end_of_sequence'] + ] + with pytest.raises(AnsibleFailJson) as exc: + self.get_job_mock_object(call_type='rest').apply() + assert 'Error on modifying job schedule: Expected error' in exc.value.args[0]['msg'] + + data = self.mock_args(rest=True) + data['state'] = 'absent' + set_module_args(data) + mock_request.side_effect = [ + SRR['is_rest'], + SRR['get_schedule'], + SRR['generic_error'], + SRR['empty_good'], + SRR['end_of_sequence'] + ] + with pytest.raises(AnsibleFailJson) as exc: + self.get_job_mock_object(call_type='rest').apply() + assert 'Error on deleting job schedule: Expected error' in exc.value.args[0]['msg'] diff --git a/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_kerberos_realm.py b/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_kerberos_realm.py new file mode 100644 index 00000000..5cdcb75a --- /dev/null +++ b/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_kerberos_realm.py @@ -0,0 +1,269 @@ +# (c) 2018, NetApp, Inc +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +''' unit test for ONTAP Kerberos Realm module ''' + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import json +import pytest +import ansible_collections.netapp.ontap.plugins.module_utils.netapp as netapp_utils +from ansible.module_utils import basic +from ansible.module_utils._text import to_bytes +from ansible_collections.netapp.ontap.plugins.modules.na_ontap_kerberos_realm \ + import NetAppOntapKerberosRealm as my_module # module under test +from ansible_collections.netapp.ontap.tests.unit.compat import unittest +from ansible_collections.netapp.ontap.tests.unit.compat.mock import patch +from ansible_collections.netapp.ontap.tests.unit.compat.mock import Mock + + +if not netapp_utils.has_netapp_lib(): + pytestmark = pytest.mark.skip('skipping as missing required netapp_lib') + + +def set_module_args(args): + """prepare arguments so that they will be picked up during module creation""" + args = json.dumps({'ANSIBLE_MODULE_ARGS': args}) + basic._ANSIBLE_ARGS = to_bytes(args) # pylint: disable=protected-access + + +class AnsibleExitJson(Exception): + """Exception class to be raised by module.exit_json and caught by the test case""" + print(Exception) + # pass + + +class AnsibleFailJson(Exception): + """Exception class to be raised by module.fail_json and caught by the test case""" + print(Exception) + # pass + + +def exit_json(*args, **kwargs): # pylint: disable=unused-argument + """function to patch over exit_json; package return data into an exception""" + if 'changed' not in kwargs: + kwargs['changed'] = False + raise AnsibleExitJson(kwargs) + + +def fail_json(*args, **kwargs): # pylint: disable=unused-argument + """function to patch over fail_json; package return data into an exception""" + kwargs['failed'] = True + raise AnsibleFailJson(kwargs) + + +class MockONTAPConnection(object): + ''' mock server connection to ONTAP host ''' + + def __init__(self, kind=None): + ''' save arguments ''' + self.type = kind + self.xml_in = None + self.xml_out = None + + def invoke_successfully(self, xml, enable_tunneling): # pylint: disable=unused-argument + ''' mock invoke_successfully returning xml data ''' + self.xml_in = xml + + if self.type == 'present_realm': + xml = self.build_realm() + else: + xml = self.build_empty_realm() + + self.xml_out = xml + return xml + + @staticmethod + def build_realm(): + ''' build xml data for kerberos realm ''' + xml = netapp_utils.zapi.NaElement('xml') + attributes = { + 'num-records': "1", + 'attributes-list': { + 'kerberos-realm': { + 'admin-server-ip': "192.168.0.1", + 'admin-server-port': "749", + 'clock-skew': "5", + 'kdc-ip': "192.168.0.1", + 'kdc-port': "88", + 'kdc-vendor': "other", + 'password-server-ip': "192.168.0.1", + 'password-server-port': "464", + "permitted-enc-types": { + "string": ["des", "des3", "aes_128", "aes_256"] + }, + 'realm': "EXAMPLE.COM", + 'vserver-name': "vserver0" + } + } + } + xml.translate_struct(attributes) + return xml + + @staticmethod + def build_empty_realm(): + ''' build xml data for kerberos realm ''' + xml = netapp_utils.zapi.NaElement('xml') + attributes = { + 'num-records': "0", + } + xml.translate_struct(attributes) + return xml + + +class TestMyModule(unittest.TestCase): + ''' a group of related Unit Tests ''' + + def setUp(self): + self.mock_module_helper = patch.multiple(basic.AnsibleModule, + exit_json=exit_json, + fail_json=fail_json) + self.mock_module_helper.start() + self.addCleanup(self.mock_module_helper.stop) + self.server = MockONTAPConnection(kind='present_realm') + + @staticmethod + def get_kerberos_realm_mock_object(kind=None): + """ + Helper method to return an na_ontap_volume object + :param kind: passes this param to MockONTAPConnection() + :return: na_ontap_volume object + """ + krbrealm_obj = my_module() + krbrealm_obj.asup_log_for_cserver = Mock(return_value=None) + krbrealm_obj.cluster = Mock() + krbrealm_obj.cluster.invoke_successfully = Mock() + if kind is None: + krbrealm_obj.server = MockONTAPConnection() + else: + krbrealm_obj.server = MockONTAPConnection(kind=kind) + return krbrealm_obj + + @staticmethod + def mock_args(): + '''Set default arguments''' + return dict({ + 'hostname': 'test', + 'username': 'test_user', + 'password': 'test_pass!', + 'https': True, + 'validate_certs': False + }) + + @staticmethod + def test_module_fail_when_required_args_missing(): + ''' required arguments are reported as errors ''' + with pytest.raises(AnsibleFailJson) as exc: + set_module_args({}) + my_module() + print('Info: %s' % exc.value.args[0]['msg']) + + def test_module_fail_when_state_present_required_args_missing(self): + ''' required arguments are reported as errors ''' + data = self.mock_args() + data['state'] = 'present' + data['vserver'] = 'vserver1' + data['realm'] = 'NETAPP.COM' + with pytest.raises(AnsibleFailJson) as exc: + set_module_args(data) + my_module() + msg = "state is present but all of the following are missing: kdc_vendor, kdc_ip" + assert exc.value.args[0]['msg'] == msg + + def test_get_nonexistent_realm(self): + ''' Test if get_krbrealm returns None for non-existent kerberos realm ''' + data = self.mock_args() + data['vserver'] = 'none' + data['realm'] = 'none' + data['state'] = 'present' + data['kdc_ip'] = 'none' + data['kdc_vendor'] = 'other' + set_module_args(data) + result = self.get_kerberos_realm_mock_object().get_krbrealm() + assert result is None + + def test_get_existing_realm(self): + ''' Test if get_krbrealm returns details for existing kerberos realm ''' + data = self.mock_args() + data['vserver'] = 'vserver0' + data['realm'] = 'EXAMPLE.COM' + data['state'] = 'present' + data['kdc_ip'] = '10.0.0.1' + data['kdc_vendor'] = 'other' + set_module_args(data) + result = self.get_kerberos_realm_mock_object('present_realm').get_krbrealm() + assert result['realm'] + + def test_successfully_modify_realm(self): + ''' Test modify realm successful for modifying kdc_ip. ''' + data = self.mock_args() + data['vserver'] = 'vserver0' + data['realm'] = 'EXAMPLE.COM' + data['kdc_ip'] = '192.168.10.10' + data['state'] = 'present' + data['kdc_ip'] = '10.0.0.1' + data['kdc_vendor'] = 'other' + set_module_args(data) + with pytest.raises(AnsibleExitJson) as exc: + self.get_kerberos_realm_mock_object('present_realm').apply() + assert exc.value.args[0]['changed'] + + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_kerberos_realm.NetAppOntapKerberosRealm.delete_krbrealm') + def test_successfully_delete_realm(self, delete_krbrealm): + ''' Test successfully delete realm ''' + data = self.mock_args() + data['state'] = 'absent' + data['vserver'] = 'vserver0' + data['realm'] = 'EXAMPLE.COM' + set_module_args(data) + obj = self.get_kerberos_realm_mock_object('present_realm') + with pytest.raises(AnsibleExitJson) as exc: + obj.apply() + assert exc.value.args[0]['changed'] + delete_krbrealm.assert_called_with() + + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_kerberos_realm.NetAppOntapKerberosRealm.create_krbrealm') + def test_successfully_create_realm(self, create_krbrealm): + ''' Test successfully create realm ''' + data = self.mock_args() + data['state'] = 'present' + data['vserver'] = 'vserver1' + data['realm'] = 'NETAPP.COM' + data['kdc_ip'] = '10.0.0.1' + data['kdc_vendor'] = 'other' + set_module_args(data) + obj = self.get_kerberos_realm_mock_object() + with pytest.raises(AnsibleExitJson) as exc: + obj.apply() + assert exc.value.args[0]['changed'] + create_krbrealm.assert_called_with() + + def test_required_if(self): + ''' required arguments are reported as errors ''' + data = self.mock_args() + data['state'] = 'present' + data['vserver'] = 'vserver1' + data['realm'] = 'NETAPP.COM' + data['kdc_ip'] = '10.0.0.1' + data['kdc_vendor'] = 'microsoft' + with pytest.raises(AnsibleFailJson) as exc: + set_module_args(data) + my_module() + msg = "kdc_vendor is microsoft but all of the following are missing: ad_server_ip, ad_server_name" + assert exc.value.args[0]['msg'] == msg + + def test_required_if_single(self): + ''' required arguments are reported as errors ''' + data = self.mock_args() + data['state'] = 'present' + data['vserver'] = 'vserver1' + data['realm'] = 'NETAPP.COM' + data['kdc_ip'] = '10.0.0.1' + data['kdc_vendor'] = 'microsoft' + data['ad_server_ip'] = '10.0.0.1' + with pytest.raises(AnsibleFailJson) as exc: + set_module_args(data) + my_module() + msg = "kdc_vendor is microsoft but all of the following are missing: ad_server_name" + assert exc.value.args[0]['msg'] == msg diff --git a/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_ldap_client.py b/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_ldap_client.py new file mode 100644 index 00000000..d8502342 --- /dev/null +++ b/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_ldap_client.py @@ -0,0 +1,185 @@ +# (c) 2018, NetApp, Inc +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +''' unit tests for Ansible module: na_ontap_ldap_client ''' + +from __future__ import (absolute_import, division, print_function) + +__metaclass__ = type + +import json +import pytest + +from ansible_collections.netapp.ontap.tests.unit.compat import unittest +from ansible_collections.netapp.ontap.tests.unit.compat.mock import patch, Mock +from ansible.module_utils import basic +from ansible.module_utils._text import to_bytes +import ansible_collections.netapp.ontap.plugins.module_utils.netapp as netapp_utils + +from ansible_collections.netapp.ontap.plugins.modules.na_ontap_ldap_client \ + import NetAppOntapLDAPClient as client_module # module under test + +if not netapp_utils.has_netapp_lib(): + pytestmark = pytest.mark.skip('skipping as missing required netapp_lib') + + +def set_module_args(args): + """prepare arguments so that they will be picked up during module creation""" + args = json.dumps({'ANSIBLE_MODULE_ARGS': args}) + basic._ANSIBLE_ARGS = to_bytes(args) # pylint: disable=protected-access + + +class AnsibleExitJson(Exception): + """Exception class to be raised by module.exit_json and caught by the test case""" + pass + + +class AnsibleFailJson(Exception): + """Exception class to be raised by module.fail_json and caught by the test case""" + pass + + +def exit_json(*args, **kwargs): # pylint: disable=unused-argument + """function to patch over exit_json; package return data into an exception""" + if 'changed' not in kwargs: + kwargs['changed'] = False + raise AnsibleExitJson(kwargs) + + +def fail_json(*args, **kwargs): # pylint: disable=unused-argument + """function to patch over fail_json; package return data into an exception""" + kwargs['failed'] = True + raise AnsibleFailJson(kwargs) + + +class MockONTAPConnection(object): + ''' mock server connection to ONTAP host ''' + + def __init__(self, kind=None, data=None): + ''' save arguments ''' + self.kind = kind + self.params = data + self.xml_in = None + self.xml_out = None + + def invoke_successfully(self, xml, enable_tunneling): # pylint: disable=unused-argument + ''' mock invoke_successfully returning xml data ''' + self.xml_in = xml + if self.kind == 'client': + xml = self.build_ldap_client_info(self.params) + self.xml_out = xml + return xml + + @staticmethod + def build_ldap_client_info(client_details): + xml = netapp_utils.zapi.NaElement('xml') + attributes = { + 'num-records': 1, + 'attributes-list': { + 'ldap-client': { + 'ldap-client-config': client_details['name'], + 'schema': client_details['schema'], + 'ldap-servers': [ + {"ldap-server": client_details['ldap_servers'][0]}, + {"ldap-server": client_details['ldap_servers'][1]} + ] + } + } + } + xml.translate_struct(attributes) + return xml + + +class TestMyModule(unittest.TestCase): + ''' Unit tests for na_ontap_job_schedule ''' + + def setUp(self): + self.mock_module_helper = patch.multiple(basic.AnsibleModule, + exit_json=exit_json, + fail_json=fail_json) + self.mock_module_helper.start() + self.addCleanup(self.mock_module_helper.stop) + self.mock_client = { + 'state': 'present', + 'name': 'test_ldap', + 'ldap_servers': ['ldap1.example.company.com', 'ldap2.example.company.com'], + 'schema': 'RFC-2307', + 'vserver': 'test_vserver', + } + + def mock_args(self): + return { + 'state': self.mock_client['state'], + 'name': self.mock_client['name'], + 'ldap_servers': self.mock_client['ldap_servers'], + 'schema': self.mock_client['schema'], + 'hostname': 'test', + 'username': 'test_user', + 'password': 'test_pass!', + 'vserver': 'test_vserver', + } + + def get_client_mock_object(self, kind=None): + client_obj = client_module() + client_obj.asup_log_for_cserver = Mock(return_value=None) + if kind is None: + client_obj.server = MockONTAPConnection() + else: + client_obj.server = MockONTAPConnection(kind='client', data=self.mock_client) + return client_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({}) + client_module() + print('Info: %s' % exc.value.args[0]['msg']) + + def test_get_nonexistent_client(self): + ''' Test if get ldap client returns None for non-existent job ''' + set_module_args(self.mock_args()) + result = self.get_client_mock_object().get_ldap_client() + assert not result + + def test_get_existing_client(self): + ''' Test if get ldap client returns None for non-existent job ''' + set_module_args(self.mock_args()) + result = self.get_client_mock_object('client').get_ldap_client() + assert result + + def test_successfully_create(self): + set_module_args(self.mock_args()) + with pytest.raises(AnsibleExitJson) as exc: + self.get_client_mock_object().apply() + assert exc.value.args[0]['changed'] + + def test_create_idempotency(self): + set_module_args(self.mock_args()) + with pytest.raises(AnsibleExitJson) as exc: + self.get_client_mock_object('client').apply() + assert not exc.value.args[0]['changed'] + + def test_successfully_delete(self): + data = self.mock_args() + data['state'] = 'absent' + set_module_args(data) + with pytest.raises(AnsibleExitJson) as exc: + self.get_client_mock_object('client').apply() + assert exc.value.args[0]['changed'] + + def test_delete_idempotency(self): + data = self.mock_args() + data['state'] = 'absent' + set_module_args(data) + with pytest.raises(AnsibleExitJson) as exc: + self.get_client_mock_object().apply() + assert not exc.value.args[0]['changed'] + + def test_successfully_modify(self): + data = self.mock_args() + data['ldap_servers'] = ["ldap1.example.company.com"] + set_module_args(data) + print(self.get_client_mock_object('client').get_ldap_client()) + with pytest.raises(AnsibleExitJson) as exc: + self.get_client_mock_object('client').apply() + assert exc.value.args[0]['changed'] diff --git a/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_login_messages.py b/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_login_messages.py new file mode 100644 index 00000000..f7b81ae4 --- /dev/null +++ b/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_login_messages.py @@ -0,0 +1,287 @@ +# (c) 2019, NetApp, Inc +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +''' unit tests for Ansible module: na_ontap_login_messages''' + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type +import json +import pytest + +from ansible.module_utils import basic +from ansible.module_utils._text import to_bytes +from ansible_collections.netapp.ontap.tests.unit.compat import unittest +from ansible_collections.netapp.ontap.tests.unit.compat.mock import patch, Mock +import ansible_collections.netapp.ontap.plugins.module_utils.netapp as netapp_utils + +from ansible_collections.netapp.ontap.plugins.modules.na_ontap_login_messages \ + import NetAppOntapLoginMessages as messages_module # module under test + +if not netapp_utils.has_netapp_lib(): + pytestmark = pytest.mark.skip('skipping as missing required netapp_lib') +HAS_NETAPP_ZAPI_MSG = "pip install netapp_lib is required" + + +# REST API canned responses when mocking send_request +SRR = { + # common responses + 'is_rest': (200, {}, None), + 'is_zapi': (400, {}, "Unreachable"), + 'empty_good': (200, {}, None), + 'end_of_sequence': (500, None, "Unexpected call to send_request"), + 'generic_error': (400, None, "Expected error"), + # 'dns_record': ({"records": [{"message": "test message", + # "uuid": "02c9e252-41be-11e9-81d5-00a0986138f7"}]}, None), + 'svm_uuid': (200, {"records": [{"uuid": "test_uuid"}], "num_records": 1}, None) +} + + +def set_module_args(args): + """prepare arguments so that they will be picked up during module creation""" + args = json.dumps({'ANSIBLE_MODULE_ARGS': args}) + basic._ANSIBLE_ARGS = to_bytes(args) # pylint: disable=protected-access + + +class AnsibleExitJson(Exception): + """Exception class to be raised by module.exit_json and caught by the test case""" + + +class AnsibleFailJson(Exception): + """Exception class to be raised by module.fail_json and caught by the test case""" + + +def exit_json(*args, **kwargs): # pylint: disable=unused-argument + """function to patch over exit_json; package return data into an exception""" + if 'changed' not in kwargs: + kwargs['changed'] = False + raise AnsibleExitJson(kwargs) + + +def fail_json(*args, **kwargs): # pylint: disable=unused-argument + """function to patch over fail_json; package return data into an exception""" + kwargs['failed'] = True + raise AnsibleFailJson(kwargs) + + +class MockONTAPConnection(object): + ''' mock server connection to ONTAP host ''' + + def __init__(self, kind=None, data=None): + ''' save arguments ''' + self.kind = kind + self.params = data + self.xml_in = None + self.xml_out = None + + def invoke_successfully(self, xml, enable_tunneling): # pylint: disable=unused-argument + ''' mock invoke_successfully returning xml data ''' + self.xml_in = xml + request = xml.to_string().decode('utf-8') + print(request) + if self.kind == 'error': + raise netapp_utils.zapi.NaApiError('test', 'expect error') + elif request.startswith("<ems-autosupport-log>"): + xml = None # or something that may the logger happy, and you don't need @patch anymore + # or + # xml = build_ems_log_response() + elif request.startswith("<vserver-login-banner-get-iter>"): + if self.kind == 'create': + xml = self.build_banner_info() + # elif self.kind == 'create_idempotency': + # xml = self.build_banner_info(self.params) + else: + xml = self.build_banner_info(self.params) + elif request.startswith("<vserver-login-banner-modify-iter>"): + xml = self.build_banner_info(self.params) + elif request.startswith("<vserver-motd-modify-iter>"): + xml = self.build_motd_info(self.params) + elif request.startswith("<vserver-motd-get-iter>"): + if self.kind == 'create': + xml = self.build_motd_info() + # elif self.kind == 'create_idempotency': + # xml = self.build_banner_info(self.params) + else: + xml = self.build_motd_info(self.params) + + self.xml_out = xml + return xml + + @staticmethod + def build_banner_info(data=None): + xml = netapp_utils.zapi.NaElement('xml') + vserver = 'vserver' + attributes = {'num-records': 1, + 'attributes-list': {'vserver-login-banner-info': {'vserver': vserver}}} + if data is not None and data.get('banner'): + attributes['attributes-list']['vserver-login-banner-info']['message'] = data['banner'] + xml.translate_struct(attributes) + return xml + + @staticmethod + def build_motd_info(data=None): + xml = netapp_utils.zapi.NaElement('xml') + vserver = 'vserver' + attributes = {'num-records': 1, + 'attributes-list': {'vserver-motd-info': {'vserver': vserver}}} + if data is not None and data.get('motd_message'): + attributes['attributes-list']['vserver-motd-info']['message'] = data['motd_message'] + if data is not None and data.get('show_cluster_motd') is False: + attributes['attributes-list']['vserver-motd-info']['is-cluster-message-enabled'] = 'false' + else: + attributes['attributes-list']['vserver-motd-info']['is-cluster-message-enabled'] = 'true' + xml.translate_struct(attributes) + return xml + + +class TestMyModule(unittest.TestCase): + ''' Unit tests for na_ontap_login_banner ''' + + def setUp(self): + self.mock_module_helper = patch.multiple(basic.AnsibleModule, + exit_json=exit_json, + fail_json=fail_json) + self.mock_module_helper.start() + self.addCleanup(self.mock_module_helper.stop) + + def mock_args(self): + return { + 'vserver': 'vserver', + 'hostname': 'test', + 'username': 'test_user', + 'password': 'test_pass!' + } + + def get_login_mock_object(self, cx_type='zapi', kind=None, status=None): + banner_obj = messages_module() + netapp_utils.ems_log_event = Mock(return_value=None) + if cx_type == 'zapi': + if kind is None: + banner_obj.server = MockONTAPConnection() + else: + banner_obj.server = MockONTAPConnection(kind=kind, data=status) + return banner_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({}) + messages_module() + print('Info: %s' % exc.value.args[0]['msg']) + + def test_successfully_create_banner(self): + data = self.mock_args() + data['banner'] = 'test banner' + data['use_rest'] = 'never' + set_module_args(data) + with pytest.raises(AnsibleExitJson) as exc: + self.get_login_mock_object('zapi', 'create', data).apply() + assert exc.value.args[0]['changed'] + + def test_create_banner_idempotency(self): + data = self.mock_args() + data['banner'] = 'test banner' + data['use_rest'] = 'never' + set_module_args(data) + with pytest.raises(AnsibleExitJson) as exc: + self.get_login_mock_object('zapi', 'create_idempotency', data).apply() + assert not exc.value.args[0]['changed'] + + def test_successfully_create_motd(self): + data = self.mock_args() + data['motd_message'] = 'test message' + data['use_rest'] = 'never' + set_module_args(data) + with pytest.raises(AnsibleExitJson) as exc: + self.get_login_mock_object('zapi', 'create', data).apply() + assert exc.value.args[0]['changed'] + + def test_create_motd_idempotency(self): + data = self.mock_args() + data['motd_message'] = 'test message' + data['use_rest'] = 'never' + set_module_args(data) + with pytest.raises(AnsibleExitJson) as exc: + self.get_login_mock_object('zapi', 'create_idempotency', data).apply() + assert not exc.value.args[0]['changed'] + + def test_get_banner_error(self): + data = self.mock_args() + data['use_rest'] = 'never' + set_module_args(data) + with pytest.raises(AnsibleFailJson) as exc: + self.get_login_mock_object('zapi', 'error', data).apply() + assert exc.value.args[0]['msg'] == 'Error fetching login_banner info: NetApp API failed. Reason - test:expect error' + + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_login_messages.NetAppOntapLoginMessages.get_banner_motd') + def test_modify_banner_error(self, get_info): + data = self.mock_args() + data['banner'] = 'modify to new banner' + data['use_rest'] = 'never' + set_module_args(data) + get_info.side_effect = [ + { + 'banner': 'old banner', + 'motd': '' + } + ] + with pytest.raises(AnsibleFailJson) as exc: + self.get_login_mock_object('zapi', 'error', data).apply() + assert exc.value.args[0]['msg'] == 'Error modifying login_banner: NetApp API failed. Reason - test:expect error' + + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_login_messages.NetAppOntapLoginMessages.get_banner_motd') + def test_modify_motd_error(self, get_info): + data = self.mock_args() + data['motd_message'] = 'modify to new motd' + data['use_rest'] = 'never' + set_module_args(data) + get_info.side_effect = [ + { + 'motd': 'old motd', + 'show_cluster_motd': False + } + ] + with pytest.raises(AnsibleFailJson) as exc: + self.get_login_mock_object('zapi', 'error', data).apply() + assert exc.value.args[0]['msg'] == 'Error modifying motd: NetApp API failed. Reason - test:expect error' + + @patch('ansible_collections.netapp.ontap.plugins.module_utils.netapp.OntapRestAPI.send_request') + def test_successfully_create_banner_rest(self, mock_request): + data = self.mock_args() + data['banner'] = 'test banner' + set_module_args(data) + mock_request.side_effect = [ + SRR['is_rest'], + SRR['svm_uuid'], + SRR['empty_good'], # get + SRR['empty_good'], # patch + SRR['end_of_sequence'] + ] + with pytest.raises(AnsibleExitJson) as exc: + self.get_login_mock_object(cx_type='rest').apply() + assert exc.value.args[0]['changed'] + + @patch('ansible_collections.netapp.ontap.plugins.module_utils.netapp.OntapRestAPI.send_request') + def test_banner_error_rest(self, mock_request): + data = self.mock_args() + data['banner'] = 'test banner' + set_module_args(data) + mock_request.side_effect = [ + SRR['is_rest'], + SRR['svm_uuid'], + SRR['generic_error'], # get + SRR['end_of_sequence'] + ] + with pytest.raises(AnsibleFailJson) as exc: + self.get_login_mock_object(cx_type='rest').apply() + assert exc.value.args[0]['msg'] == 'Error when fetching login_banner info: Expected error' + + mock_request.side_effect = [ + SRR['is_rest'], + SRR['svm_uuid'], + SRR['empty_good'], # get + SRR['generic_error'], # patch + SRR['end_of_sequence'] + ] + with pytest.raises(AnsibleFailJson) as exc: + self.get_login_mock_object(cx_type='rest').apply() + assert exc.value.args[0]['msg'] == 'Error when modifying banner: Expected error' diff --git a/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_lun.py b/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_lun.py new file mode 100644 index 00000000..07d3c2f3 --- /dev/null +++ b/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_lun.py @@ -0,0 +1,177 @@ +# (c) 2020, NetApp, Inc +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +''' unit test template for ONTAP Ansible module ''' + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type +import json +import pytest + +from ansible.module_utils import basic +from ansible.module_utils._text import to_bytes +from ansible_collections.netapp.ontap.tests.unit.compat import unittest +from ansible_collections.netapp.ontap.tests.unit.compat.mock import patch, Mock +import ansible_collections.netapp.ontap.plugins.module_utils.netapp as netapp_utils + +from ansible_collections.netapp.ontap.plugins.modules.na_ontap_lun \ + import NetAppOntapLUN as my_module # module under test + +if not netapp_utils.has_netapp_lib(): + pytestmark = pytest.mark.skip('skipping as missing required netapp_lib') + + +def set_module_args(args): + """prepare arguments so that they will be picked up during module creation""" + args = json.dumps({'ANSIBLE_MODULE_ARGS': args}) + basic._ANSIBLE_ARGS = to_bytes(args) # pylint: disable=protected-access + + +class AnsibleExitJson(Exception): + """Exception class to be raised by module.exit_json and caught by the test case""" + + +class AnsibleFailJson(Exception): + """Exception class to be raised by module.fail_json and caught by the test case""" + + +def exit_json(*args, **kwargs): # pylint: disable=unused-argument + """function to patch over exit_json; package return data into an exception""" + if 'changed' not in kwargs: + kwargs['changed'] = False + raise AnsibleExitJson(kwargs) + + +def fail_json(*args, **kwargs): # pylint: disable=unused-argument + """function to patch over fail_json; package return data into an exception""" + kwargs['failed'] = True + raise AnsibleFailJson(kwargs) + + +class MockONTAPConnection(object): + ''' mock server connection to ONTAP host ''' + + def __init__(self, kind=None, parm1=None): + ''' save arguments ''' + self.type = kind + self.parm1 = parm1 + self.xml_in = None + self.xml_out = None + + def invoke_successfully(self, xml, enable_tunneling): # pylint: disable=unused-argument + ''' mock invoke_successfully returning xml data ''' + self.xml_in = xml + if self.type == 'lun': + xml = self.build_lun_info(self.parm1) + self.xml_out = xml + return xml + + @staticmethod + def build_lun_info(lun_name): + ''' build xml data for lun-info ''' + xml = netapp_utils.zapi.NaElement('xml') + lun = dict( + lun_info=dict( + path="/what/ever/%s" % lun_name, + size=10 + ) + ) + attributes = { + 'num-records': 1, + 'attributes-list': [lun] + } + xml.translate_struct(attributes) + return xml + + +class TestMyModule(unittest.TestCase): + ''' a group of related Unit Tests ''' + + def setUp(self): + self.mock_module_helper = patch.multiple(basic.AnsibleModule, + exit_json=exit_json, + fail_json=fail_json) + self.mock_module_helper.start() + self.addCleanup(self.mock_module_helper.stop) + self.mock_lun_args = { + 'vserver': 'ansible', + 'name': 'lun_name', + 'flexvol_name': 'vol_name', + 'state': 'present' + } + + def mock_args(self): + + return { + 'vserver': self.mock_lun_args['vserver'], + 'name': self.mock_lun_args['name'], + 'flexvol_name': self.mock_lun_args['flexvol_name'], + 'state': self.mock_lun_args['state'], + 'hostname': 'hostname', + 'username': 'username', + 'password': 'password', + } + # self.server = MockONTAPConnection() + + def get_lun_mock_object(self, kind=None, parm1=None): + """ + Helper method to return an na_ontap_lun object + :param kind: passes this param to MockONTAPConnection() + :return: na_ontap_interface object + """ + lun_obj = my_module() + lun_obj.autosupport_log = Mock(return_value=None) + lun_obj.server = MockONTAPConnection(kind=kind, parm1=parm1) + return lun_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({}) + my_module() + print('Info: %s' % exc.value.args[0]['msg']) + + def test_create_error_missing_param(self): + ''' Test if create throws an error if required param 'destination_vserver' is not specified''' + data = self.mock_args() + set_module_args(data) + with pytest.raises(AnsibleFailJson) as exc: + self.get_lun_mock_object().apply() + msg = 'size is a required parameter for create.' + assert msg == exc.value.args[0]['msg'] + + def test_successful_create(self): + ''' Test successful create ''' + data = dict(self.mock_args()) + data['size'] = 5 + set_module_args(data) + with pytest.raises(AnsibleExitJson) as exc: + self.get_lun_mock_object().apply() + assert exc.value.args[0]['changed'] + + def test_create_rename_idempotency(self): + ''' Test create idempotency ''' + set_module_args(self.mock_args()) + with pytest.raises(AnsibleExitJson) as exc: + self.get_lun_mock_object('lun', 'lun_name').apply() + assert not exc.value.args[0]['changed'] + + def test_successful_rename(self): + ''' Test successful create ''' + data = dict(self.mock_args()) + data['from_name'] = 'lun_from_name' + set_module_args(data) + with pytest.raises(AnsibleExitJson) as exc: + self.get_lun_mock_object('lun', 'lun_from_name').apply() + assert exc.value.args[0]['changed'] + assert 'renamed' in exc.value.args[0] + + def test_failed_rename(self): + ''' Test failed rename ''' + data = dict(self.mock_args()) + data['from_name'] = 'lun_from_name' + set_module_args(data) + with pytest.raises(AnsibleFailJson) as exc: + self.get_lun_mock_object('lun', 'other_lun_name').apply() + msg = 'Error renaming lun: lun_from_name does not exist' + assert msg == exc.value.args[0]['msg'] diff --git a/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_lun_copy.py b/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_lun_copy.py new file mode 100644 index 00000000..cafa9105 --- /dev/null +++ b/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_lun_copy.py @@ -0,0 +1,155 @@ +# (c) 2018, NetApp, Inc +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +''' unit test template for ONTAP Ansible module ''' + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type +import json +import pytest + +from ansible.module_utils import basic +from ansible.module_utils._text import to_bytes +from ansible_collections.netapp.ontap.tests.unit.compat import unittest +from ansible_collections.netapp.ontap.tests.unit.compat.mock import patch, Mock +import ansible_collections.netapp.ontap.plugins.module_utils.netapp as netapp_utils + +from ansible_collections.netapp.ontap.plugins.modules.na_ontap_lun_copy \ + import NetAppOntapLUNCopy as my_module # module under test + +if not netapp_utils.has_netapp_lib(): + pytestmark = pytest.mark.skip('skipping as missing required netapp_lib') + + +def set_module_args(args): + """prepare arguments so that they will be picked up during module creation""" + args = json.dumps({'ANSIBLE_MODULE_ARGS': args}) + basic._ANSIBLE_ARGS = to_bytes(args) # pylint: disable=protected-access + + +class AnsibleExitJson(Exception): + """Exception class to be raised by module.exit_json and caught by the test case""" + + +class AnsibleFailJson(Exception): + """Exception class to be raised by module.fail_json and caught by the test case""" + + +def exit_json(*args, **kwargs): # pylint: disable=unused-argument + """function to patch over exit_json; package return data into an exception""" + if 'changed' not in kwargs: + kwargs['changed'] = False + raise AnsibleExitJson(kwargs) + + +def fail_json(*args, **kwargs): # pylint: disable=unused-argument + """function to patch over fail_json; package return data into an exception""" + kwargs['failed'] = True + raise AnsibleFailJson(kwargs) + + +class MockONTAPConnection(object): + ''' mock server connection to ONTAP host ''' + + def __init__(self, kind=None, parm1=None): + ''' save arguments ''' + self.type = kind + self.parm1 = parm1 + self.xml_in = None + self.xml_out = None + + def invoke_successfully(self, xml, enable_tunneling): # pylint: disable=unused-argument + ''' mock invoke_successfully returning xml data ''' + self.xml_in = xml + if self.type == 'destination_vserver': + xml = self.build_lun_info() + self.xml_out = xml + return xml + + @staticmethod + def build_lun_info(): + ''' build xml data for lun-info ''' + xml = netapp_utils.zapi.NaElement('xml') + attributes = { + 'num-records': 1, + } + xml.translate_struct(attributes) + return xml + + +class TestMyModule(unittest.TestCase): + ''' a group of related Unit Tests ''' + + def setUp(self): + self.mock_module_helper = patch.multiple(basic.AnsibleModule, + exit_json=exit_json, + fail_json=fail_json) + self.mock_module_helper.start() + self.addCleanup(self.mock_module_helper.stop) + self.mock_lun_copy = { + 'source_vserver': 'ansible', + 'destination_path': '/vol/test/test_copy_dest_dest_new_reviewd_new', + 'source_path': '/vol/test/test_copy_1', + 'destination_vserver': 'ansible', + 'state': 'present' + } + + def mock_args(self): + + return { + 'source_vserver': self.mock_lun_copy['source_vserver'], + 'destination_path': self.mock_lun_copy['destination_path'], + 'source_path': self.mock_lun_copy['source_path'], + 'destination_vserver': self.mock_lun_copy['destination_vserver'], + 'state': self.mock_lun_copy['state'], + 'hostname': 'hostname', + 'username': 'username', + 'password': 'password', + } + # self.server = MockONTAPConnection() + + def get_lun_copy_mock_object(self, kind=None): + """ + Helper method to return an na_ontap_lun_copy object + :param kind: passes this param to MockONTAPConnection() + :return: na_ontap_interface object + """ + lun_copy_obj = my_module() + lun_copy_obj.autosupport_log = Mock(return_value=None) + if kind is None: + lun_copy_obj.server = MockONTAPConnection() + else: + lun_copy_obj.server = MockONTAPConnection(kind=kind) + return lun_copy_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({}) + my_module() + print('Info: %s' % exc.value.args[0]['msg']) + + def test_create_error_missing_param(self): + ''' Test if create throws an error if required param 'destination_vserver' is not specified''' + data = self.mock_args() + del data['destination_vserver'] + set_module_args(data) + with pytest.raises(AnsibleFailJson) as exc: + self.get_lun_copy_mock_object('lun_copy').copy_lun() + msg = 'missing required arguments: destination_vserver' + assert msg == exc.value.args[0]['msg'] + + def test_successful_copy(self): + ''' Test successful create ''' + # data = self.mock_args() + set_module_args(self.mock_args()) + with pytest.raises(AnsibleExitJson) as exc: + self.get_lun_copy_mock_object().apply() + assert exc.value.args[0]['changed'] + + def test_copy_idempotency(self): + ''' Test create idempotency ''' + set_module_args(self.mock_args()) + with pytest.raises(AnsibleExitJson) as exc: + self.get_lun_copy_mock_object('destination_vserver').apply() + assert not exc.value.args[0]['changed'] diff --git a/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_lun_map.py b/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_lun_map.py new file mode 100644 index 00000000..a904a516 --- /dev/null +++ b/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_lun_map.py @@ -0,0 +1,192 @@ +''' unit tests ONTAP Ansible module: na_ontap_lun_map ''' + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type +import json +import pytest + +from ansible_collections.netapp.ontap.tests.unit.compat import unittest +from ansible_collections.netapp.ontap.tests.unit.compat.mock import patch +from ansible.module_utils import basic +from ansible.module_utils._text import to_bytes +import ansible_collections.netapp.ontap.plugins.module_utils.netapp as netapp_utils + +from ansible_collections.netapp.ontap.plugins.modules.na_ontap_lun_map \ + import NetAppOntapLUNMap as my_module + +if not netapp_utils.has_netapp_lib(): + pytestmark = pytest.mark.skip('skipping as missing required netapp_lib') + + +def set_module_args(args): + """prepare arguments so that they will be picked up during module creation""" + args = json.dumps({'ANSIBLE_MODULE_ARGS': args}) + basic._ANSIBLE_ARGS = to_bytes(args) # pylint: disable=protected-access + + +class AnsibleExitJson(Exception): + """Exception class to be raised by module.exit_json and caught by the test case""" + pass + + +class AnsibleFailJson(Exception): + """Exception class to be raised by module.fail_json and caught by the test case""" + pass + + +def exit_json(*args, **kwargs): # pylint: disable=unused-argument + """function to patch over exit_json; package return data into an exception""" + if 'changed' not in kwargs: + kwargs['changed'] = False + raise AnsibleExitJson(kwargs) + + +def fail_json(*args, **kwargs): # pylint: disable=unused-argument + """function to patch over fail_json; package return data into an exception""" + kwargs['failed'] = True + raise AnsibleFailJson(kwargs) + + +class MockONTAPConnection(object): + ''' mock server connection to ONTAP host ''' + + def __init__(self, kind=None): + ''' save arguments ''' + self.type = kind + self.xml_in = None + self.xml_out = None + + def invoke_successfully(self, xml, enable_tunneling): # pylint: disable=unused-argument + ''' mock invoke_successfully returning xml data ''' + self.xml_in = xml + if self.type == 'lun_map': + xml = self.build_lun_info() + elif self.type == 'lun_map_fail': + raise netapp_utils.zapi.NaApiError(code='TEST', message="This exception is from the unit test") + self.xml_out = xml + return xml + + @staticmethod + def build_lun_info(): + ''' build xml data for lun-map-entry ''' + xml = netapp_utils.zapi.NaElement('xml') + data = {'initiator-groups': [{'initiator-group-info': {'initiator-group-name': 'ansible', 'lun-id': 2}}]} + xml.translate_struct(data) + return xml + + +class TestMyModule(unittest.TestCase): + ''' a group of related Unit Tests ''' + + def setUp(self): + self.mock_module_helper = patch.multiple(basic.AnsibleModule, + exit_json=exit_json, + fail_json=fail_json) + self.mock_module_helper.start() + self.addCleanup(self.mock_module_helper.stop) + self.server = MockONTAPConnection() + self.onbox = False + + def set_default_args(self): + if self.onbox: + hostname = '10.10.10.10' + username = 'admin' + password = 'password' + initiator_group_name = 'ansible' + vserver = 'ansible' + path = '/vol/ansible/test' + lun_id = 2 + else: + hostname = 'hostname' + username = 'username' + password = 'password' + initiator_group_name = 'ansible' + vserver = 'ansible' + path = '/vol/ansible/test' + lun_id = 2 + return dict({ + 'hostname': hostname, + 'username': username, + 'password': password, + 'initiator_group_name': initiator_group_name, + 'vserver': vserver, + 'path': path, + 'lun_id': lun_id + }) + + def test_module_fail_when_required_args_missing(self): + ''' required arguments are reported as errors ''' + with pytest.raises(AnsibleFailJson) as exc: + set_module_args({}) + my_module() + print('Info: %s' % exc.value.args[0]['msg']) + + def test_ensure_get_called(self): + ''' test get_lun_map for non-existent lun''' + set_module_args(self.set_default_args()) + my_obj = my_module() + my_obj.server = self.server + assert my_obj.get_lun_map is not None + + def test_ensure_get_called_existing(self): + ''' test get_lun_map for existing lun''' + set_module_args(self.set_default_args()) + my_obj = my_module() + my_obj.server = MockONTAPConnection(kind='lun_map') + assert my_obj.get_lun_map() + + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_lun_map.NetAppOntapLUNMap.create_lun_map') + def test_successful_create(self, create_lun_map): + ''' mapping lun and testing idempotency ''' + data = self.set_default_args() + set_module_args(data) + my_obj = my_module() + if not self.onbox: + my_obj.server = self.server + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + assert exc.value.args[0]['changed'] + create_lun_map.assert_called_with() + # to reset na_helper from remembering the previous 'changed' value + set_module_args(self.set_default_args()) + my_obj = my_module() + if not self.onbox: + my_obj.server = MockONTAPConnection('lun_map') + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + assert not exc.value.args[0]['changed'] + + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_lun_map.NetAppOntapLUNMap.delete_lun_map') + def test_successful_delete(self, delete_lun_map): + ''' unmapping lun and testing idempotency ''' + data = self.set_default_args() + data['state'] = 'absent' + set_module_args(data) + my_obj = my_module() + if not self.onbox: + my_obj.server = MockONTAPConnection('lun_map') + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + assert exc.value.args[0]['changed'] + delete_lun_map.assert_called_with() + # to reset na_helper from remembering the previous 'changed' value + my_obj = my_module() + if not self.onbox: + my_obj.server = self.server + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + assert not exc.value.args[0]['changed'] + + def test_if_all_methods_catch_exception(self): + module_args = {} + module_args.update(self.set_default_args()) + set_module_args(module_args) + my_obj = my_module() + if not self.onbox: + my_obj.server = MockONTAPConnection('lun_map_fail') + with pytest.raises(AnsibleFailJson) as exc: + my_obj.create_lun_map() + assert 'Error mapping lun' in exc.value.args[0]['msg'] + with pytest.raises(AnsibleFailJson) as exc: + my_obj.delete_lun_map() + assert 'Error unmapping lun' in exc.value.args[0]['msg'] diff --git a/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_lun_rest.py b/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_lun_rest.py new file mode 100644 index 00000000..9ed07535 --- /dev/null +++ b/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_lun_rest.py @@ -0,0 +1,277 @@ +# (c) 2020, NetApp, Inc +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +''' unit test template for ONTAP Ansible module ''' + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type +import json +import pytest + +from ansible.module_utils import basic +from ansible.module_utils._text import to_bytes +from ansible_collections.netapp.ontap.tests.unit.compat import unittest +from ansible_collections.netapp.ontap.tests.unit.compat.mock import patch, Mock +import ansible_collections.netapp.ontap.plugins.module_utils.netapp as netapp_utils + +from ansible_collections.netapp.ontap.plugins.modules.na_ontap_lun \ + import NetAppOntapLUN as my_module # module under test + +if not netapp_utils.has_netapp_lib(): + pytestmark = pytest.mark.skip('skipping as missing required netapp_lib') + + +def set_module_args(args): + """prepare arguments so that they will be picked up during module creation""" + args = json.dumps({'ANSIBLE_MODULE_ARGS': args}) + basic._ANSIBLE_ARGS = to_bytes(args) # pylint: disable=protected-access + + +class AnsibleExitJson(Exception): + """Exception class to be raised by module.exit_json and caught by the test case""" + + +class AnsibleFailJson(Exception): + """Exception class to be raised by module.fail_json and caught by the test case""" + + +def exit_json(*args, **kwargs): # pylint: disable=unused-argument + """function to patch over exit_json; package return data into an exception""" + if 'changed' not in kwargs: + kwargs['changed'] = False + raise AnsibleExitJson(kwargs) + + +def fail_json(*args, **kwargs): # pylint: disable=unused-argument + """function to patch over fail_json; package return data into an exception""" + kwargs['failed'] = True + raise AnsibleFailJson(kwargs) + + +# REST API canned responses when mocking send_request +SRR = { + # common responses + 'is_rest': (200, {}, None), + 'is_zapi': (400, {}, "Unreachable"), + 'empty_good': (200, {}, None), + 'end_of_sequence': (500, None, "Unexpected call to send_request"), + 'generic_error': (400, None, "Expected error"), + # module specific responses + 'get_apps_empty': (200, + {'records': [], + 'num_records': 0 + }, + None + ), + 'get_apps_found': (200, + {'records': [dict(name='san_appli', uuid='1234')], + 'num_records': 1 + }, + None + ), + 'get_app_components': (200, + {'records': [dict(name='san_appli', uuid='1234')], + 'num_records': 1 + }, + None + ), + 'get_app_component_details': (200, + {'backing_storage': dict(luns=[]), + }, + None + ), +} + + +class MockONTAPConnection(object): + ''' mock server connection to ONTAP host ''' + + def __init__(self, kind=None, parm1=None): + ''' save arguments ''' + self.type = kind + self.parm1 = parm1 + self.xml_in = None + self.xml_out = None + + def invoke_successfully(self, xml, enable_tunneling): # pylint: disable=unused-argument + ''' mock invoke_successfully returning xml data ''' + self.xml_in = xml + if self.type == 'lun': + xml = self.build_lun_info(self.parm1) + self.xml_out = xml + return xml + + @staticmethod + def build_lun_info(lun_name): + ''' build xml data for lun-info ''' + xml = netapp_utils.zapi.NaElement('xml') + lun = dict( + lun_info=dict( + path="/what/ever/%s" % lun_name, + size=10 + ) + ) + attributes = { + 'num-records': 1, + 'attributes-list': [lun] + } + xml.translate_struct(attributes) + return xml + + +class TestMyModule(unittest.TestCase): + ''' a group of related Unit Tests ''' + + def setUp(self): + self.mock_module_helper = patch.multiple(basic.AnsibleModule, + exit_json=exit_json, + fail_json=fail_json) + self.mock_module_helper.start() + self.addCleanup(self.mock_module_helper.stop) + self.mock_lun_args = { + 'vserver': 'ansible', + 'name': 'lun_name', + 'flexvol_name': 'vol_name', + 'state': 'present' + } + + def mock_args(self): + return { + 'vserver': self.mock_lun_args['vserver'], + 'name': self.mock_lun_args['name'], + 'flexvol_name': self.mock_lun_args['flexvol_name'], + 'state': self.mock_lun_args['state'], + 'hostname': 'hostname', + 'username': 'username', + 'password': 'password', + } + # self.server = MockONTAPConnection() + + def get_lun_mock_object(self, kind=None, parm1=None): + """ + Helper method to return an na_ontap_lun object + :param kind: passes this param to MockONTAPConnection() + :return: na_ontap_interface object + """ + lun_obj = my_module() + lun_obj.autosupport_log = Mock(return_value=None) + lun_obj.server = MockONTAPConnection(kind=kind, parm1=parm1) + return lun_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({}) + my_module() + print('Info: %s' % exc.value.args[0]['msg']) + + def test_create_error_missing_param(self): + ''' Test if create throws an error if required param 'destination_vserver' is not specified''' + data = self.mock_args() + set_module_args(data) + data.pop('flexvol_name') + data['san_application_template'] = dict(name='san_appli') + with pytest.raises(AnsibleFailJson) as exc: + self.get_lun_mock_object().apply() + msg = 'size is a required parameter for create.' + assert msg == exc.value.args[0]['msg'] + + def test_create_error_missing_param2(self): + ''' Test if create throws an error if required param 'destination_vserver' is not specified''' + data = self.mock_args() + data.pop('flexvol_name') + data['size'] = 5 + data['san_application_template'] = dict(lun_count=6) + set_module_args(data) + with pytest.raises(AnsibleFailJson) as exc: + self.get_lun_mock_object().apply() + msg = 'missing required arguments: name found in san_application_template' + assert msg == exc.value.args[0]['msg'] + + @patch('ansible_collections.netapp.ontap.plugins.module_utils.netapp.OntapRestAPI.send_request') + def test_successful_create_appli(self, mock_request): + ''' Test successful create ''' + mock_request.side_effect = [ + SRR['get_apps_empty'], # GET application/applications + SRR['empty_good'], # POST application/applications + SRR['end_of_sequence'] + ] + data = dict(self.mock_args()) + data['size'] = 5 + data.pop('flexvol_name') + data['san_application_template'] = dict(name='san_appli') + set_module_args(data) + with pytest.raises(AnsibleExitJson) as exc: + self.get_lun_mock_object().apply() + assert exc.value.args[0]['changed'] + + @patch('ansible_collections.netapp.ontap.plugins.module_utils.netapp.OntapRestAPI.send_request') + def test_successful_create_appli_idem(self, mock_request): + ''' Test successful create idempotent ''' + mock_request.side_effect = [ + SRR['get_apps_found'], # GET application/applications + SRR['get_apps_found'], # GET application/applications/<uuid>/components + SRR['get_app_component_details'], # GET application/applications/<uuid>/components/<cuuid> + SRR['end_of_sequence'] + ] + data = dict(self.mock_args()) + data['size'] = 5 + data.pop('flexvol_name') + data['san_application_template'] = dict(name='san_appli') + set_module_args(data) + with pytest.raises(AnsibleExitJson) as exc: + self.get_lun_mock_object().apply() + assert not exc.value.args[0]['changed'] + + @patch('ansible_collections.netapp.ontap.plugins.module_utils.netapp.OntapRestAPI.send_request') + def test_successful_create_appli_idem_no_comp(self, mock_request): + ''' Test successful create idempotent ''' + mock_request.side_effect = [ + SRR['get_apps_found'], # GET application/applications + SRR['get_apps_empty'], # GET application/applications/<uuid>/components + SRR['end_of_sequence'] + ] + data = dict(self.mock_args()) + data['size'] = 5 + data.pop('flexvol_name') + data['san_application_template'] = dict(name='san_appli') + set_module_args(data) + with pytest.raises(AnsibleFailJson) as exc: + self.get_lun_mock_object().apply() + msg = 'Error: no component for application san_appli' + assert msg == exc.value.args[0]['msg'] + + @patch('ansible_collections.netapp.ontap.plugins.module_utils.netapp.OntapRestAPI.send_request') + def test_successful_delete_appli(self, mock_request): + ''' Test successful create ''' + mock_request.side_effect = [ + SRR['get_apps_found'], # GET application/applications + SRR['empty_good'], # POST application/applications + SRR['end_of_sequence'] + ] + data = dict(self.mock_args()) + data['size'] = 5 + data.pop('flexvol_name') + data['san_application_template'] = dict(name='san_appli') + data['state'] = 'absent' + set_module_args(data) + with pytest.raises(AnsibleExitJson) as exc: + self.get_lun_mock_object().apply() + assert exc.value.args[0]['changed'] + + @patch('ansible_collections.netapp.ontap.plugins.module_utils.netapp.OntapRestAPI.send_request') + def test_successful_delete_appli_idem(self, mock_request): + ''' Test successful deelte idempotent ''' + mock_request.side_effect = [ + SRR['get_apps_empty'], # GET application/applications + SRR['end_of_sequence'] + ] + data = dict(self.mock_args()) + data['size'] = 5 + data.pop('flexvol_name') + data['san_application_template'] = dict(name='san_appli') + data['state'] = 'absent' + set_module_args(data) + with pytest.raises(AnsibleExitJson) as exc: + self.get_lun_mock_object().apply() + assert not exc.value.args[0]['changed'] diff --git a/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_mcc_mediator.py b/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_mcc_mediator.py new file mode 100644 index 00000000..33da1819 --- /dev/null +++ b/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_mcc_mediator.py @@ -0,0 +1,156 @@ +# (c) 2020, NetApp, Inc +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +''' unit tests for Ansible module: na_ontap_metrocluster ''' + +from __future__ import (absolute_import, division, print_function) + +__metaclass__ = type + +import json +import pytest + +from ansible_collections.netapp.ontap.tests.unit.compat import unittest +from ansible_collections.netapp.ontap.tests.unit.compat.mock import patch, Mock +from ansible.module_utils import basic +from ansible.module_utils._text import to_bytes + +from ansible_collections.netapp.ontap.plugins.modules.na_ontap_mcc_mediator \ + import NetAppOntapMccipMediator as mediator_module # module under test + +SRR = { + # common responses + 'is_rest': (200, {}, None), + 'is_zapi': (400, {}, "Unreachable"), + 'empty_good': (200, {}, None), + 'end_of_sequence': (500, None, "Unexpected call to send_request"), + 'generic_error': (400, None, "Expected error"), + # module specific responses + 'get_mediator_with_no_results': (200, {'num_records': 0}, None), + 'get_mediator_with_results': (200, { + 'num_records': 1, + 'records': [{ + 'ip_address': '10.10.10.10', + 'uuid': 'ebe27c49-1adf-4496-8335-ab862aebebf2' + }] + }, None) +} + + +def set_module_args(args): + """prepare arguments so that they will be picked up during module creation""" + args = json.dumps({'ANSIBLE_MODULE_ARGS': args}) + basic._ANSIBLE_ARGS = to_bytes(args) # pylint: disable=protected-access + + +class AnsibleExitJson(Exception): + """Exception class to be raised by module.exit_json and caught by the test case""" + + +class AnsibleFailJson(Exception): + """Exception class to be raised by module.fail_json and caught by the test case""" + + +def exit_json(*args, **kwargs): # pylint: disable=unused-argument + """function to patch over exit_json; package return data into an exception""" + if 'changed' not in kwargs: + kwargs['changed'] = False + raise AnsibleExitJson(kwargs) + + +def fail_json(*args, **kwargs): # pylint: disable=unused-argument + """function to patch over fail_json; package return data into an exception""" + kwargs['failed'] = True + raise AnsibleFailJson(kwargs) + + +class TestMyModule(unittest.TestCase): + """ Unit tests for na_ontap_metrocluster """ + + def setUp(self): + self.mock_module_helper = patch.multiple(basic.AnsibleModule, + exit_json=exit_json, + fail_json=fail_json) + self.mock_module_helper.start() + self.addCleanup(self.mock_module_helper.stop) + self.mock_mediator = { + 'mediator_address': '10.10.10.10', + 'mediator_user': 'carchi', + 'mediator_password': 'netapp1!' + } + + def mock_args(self): + return { + 'mediator_address': self.mock_mediator['mediator_address'], + 'mediator_user': self.mock_mediator['mediator_user'], + 'mediator_password': self.mock_mediator['mediator_password'], + 'hostname': 'test_host', + 'username': 'test_user', + 'password': 'test_pass!' + } + + def get_alias_mock_object(self): + alias_obj = mediator_module() + return alias_obj + + @patch('ansible_collections.netapp.ontap.plugins.module_utils.netapp.OntapRestAPI.send_request') + def test_rest_successful_create(self, mock_request): + """Test successful rest create""" + data = self.mock_args() + set_module_args(data) + mock_request.side_effect = [ + SRR['is_rest'], + SRR['get_mediator_with_no_results'], + SRR['empty_good'], + SRR['end_of_sequence'] + ] + with pytest.raises(AnsibleExitJson) as exc: + self.get_alias_mock_object().apply() + assert exc.value.args[0]['changed'] + + @patch('ansible_collections.netapp.ontap.plugins.module_utils.netapp.OntapRestAPI.send_request') + def test_rest_successful_create_idempotency(self, mock_request): + """Test successful rest create""" + data = self.mock_args() + set_module_args(data) + mock_request.side_effect = [ + SRR['is_rest'], + SRR['get_mediator_with_results'], + SRR['empty_good'], + SRR['end_of_sequence'] + ] + with pytest.raises(AnsibleExitJson) as exc: + self.get_alias_mock_object().apply() + assert not exc.value.args[0]['changed'] + + @patch('ansible_collections.netapp.ontap.plugins.module_utils.netapp.OntapRestAPI.send_request') + def test_rest_successful_delete(self, mock_request): + """Test successful rest create""" + data = self.mock_args() + data['state'] = 'absent' + set_module_args(data) + mock_request.side_effect = [ + SRR['is_rest'], + SRR['get_mediator_with_results'], + SRR['empty_good'], + SRR['end_of_sequence'] + ] + with pytest.raises(AnsibleExitJson) as exc: + self.get_alias_mock_object().apply() + assert exc.value.args[0]['changed'] + + @patch('ansible_collections.netapp.ontap.plugins.module_utils.netapp.OntapRestAPI.send_request') + def test_rest_successful_delete(self, mock_request): + """Test successful rest create""" + data = self.mock_args() + data['state'] = 'absent' + set_module_args(data) + mock_request.side_effect = [ + SRR['is_rest'], + SRR['get_mediator_with_no_results'], + SRR['empty_good'], + SRR['end_of_sequence'] + ] + with pytest.raises(AnsibleExitJson) as exc: + self.get_alias_mock_object().apply() + assert not exc.value.args[0]['changed'] diff --git a/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_metrocluster.py b/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_metrocluster.py new file mode 100644 index 00000000..169ab9c3 --- /dev/null +++ b/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_metrocluster.py @@ -0,0 +1,149 @@ +# (c) 2020, NetApp, Inc +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +''' unit tests for Ansible module: na_ontap_metrocluster ''' + +from __future__ import (absolute_import, division, print_function) + +__metaclass__ = type + +import json +import pytest + +from ansible_collections.netapp.ontap.tests.unit.compat import unittest +from ansible_collections.netapp.ontap.tests.unit.compat.mock import patch, Mock +from ansible.module_utils import basic +from ansible.module_utils._text import to_bytes + +from ansible_collections.netapp.ontap.plugins.modules.na_ontap_metrocluster \ + import NetAppONTAPMetroCluster as metrocluster_module # module under test + +# REST API canned responses when mocking send_request +SRR = { + # common responses + 'is_rest': (200, {}, None), + 'is_zapi': (400, {}, "Unreachable"), + 'empty_good': (200, {}, None), + 'end_of_sequence': (500, None, "Unexpected call to send_request"), + 'generic_error': (400, None, "Expected error"), + # module specific responses + 'get_metrocluster_with_results': (200, {"local": { + "cluster": { + 'name': 'cluster1' + }, + "configuration_state": "configuration_error", # TODO: put correct state + "partner_cluster_reachable": "true", + }}, None), + 'get_metrocluster_with_no_results': (200, None, None), + 'metrocluster_post': (200, {'job': { + 'uuid': 'fde79888-692a-11ea-80c2-005056b39fe7', + '_links': { + 'self': { + 'href': '/api/cluster/jobs/fde79888-692a-11ea-80c2-005056b39fe7'}}} + }, None), + 'job': (200, { + "uuid": "cca3d070-58c6-11ea-8c0c-005056826c14", + "description": "POST /api/cluster/metrocluster", + "state": "success", + "message": "There are not enough disks in Pool1.", + "code": 2432836, + "start_time": "2020-02-26T10:35:44-08:00", + "end_time": "2020-02-26T10:47:38-08:00", + "_links": { + "self": { + "href": "/api/cluster/jobs/cca3d070-58c6-11ea-8c0c-005056826c14" + } + } + }, None) +} + + +def set_module_args(args): + """prepare arguments so that they will be picked up during module creation""" + args = json.dumps({'ANSIBLE_MODULE_ARGS': args}) + basic._ANSIBLE_ARGS = to_bytes(args) # pylint: disable=protected-access + + +class AnsibleExitJson(Exception): + """Exception class to be raised by module.exit_json and caught by the test case""" + + +class AnsibleFailJson(Exception): + """Exception class to be raised by module.fail_json and caught by the test case""" + + +def exit_json(*args, **kwargs): # pylint: disable=unused-argument + """function to patch over exit_json; package return data into an exception""" + if 'changed' not in kwargs: + kwargs['changed'] = False + raise AnsibleExitJson(kwargs) + + +def fail_json(*args, **kwargs): # pylint: disable=unused-argument + """function to patch over fail_json; package return data into an exception""" + kwargs['failed'] = True + raise AnsibleFailJson(kwargs) + + +class TestMyModule(unittest.TestCase): + """ Unit tests for na_ontap_metrocluster """ + + def setUp(self): + self.mock_module_helper = patch.multiple(basic.AnsibleModule, + exit_json=exit_json, + fail_json=fail_json) + self.mock_module_helper.start() + self.addCleanup(self.mock_module_helper.stop) + self.mock_metrocluster = { + 'partner_cluster_name': 'cluster1', + 'node_name': 'carchi_vsim1', + 'partner_node_name': 'carchi_vsim3' + } + + def mock_args(self): + return { + 'dr_pairs': [{ + 'node_name': self.mock_metrocluster['node_name'], + 'partner_node_name': self.mock_metrocluster['partner_node_name'], + }], + 'partner_cluster_name': self.mock_metrocluster['partner_cluster_name'], + 'hostname': 'test_host', + 'username': 'test_user', + 'password': 'test_pass!' + } + + def get_alias_mock_object(self): + alias_obj = metrocluster_module() + return alias_obj + + @patch('ansible_collections.netapp.ontap.plugins.module_utils.netapp.OntapRestAPI.send_request') + def test_rest_successful_create(self, mock_request): + """Test successful rest create""" + data = self.mock_args() + set_module_args(data) + mock_request.side_effect = [ + SRR['is_rest'], + SRR['get_metrocluster_with_no_results'], + SRR['metrocluster_post'], + SRR['job'], + SRR['empty_good'], + SRR['end_of_sequence'] + ] + with pytest.raises(AnsibleExitJson) as exc: + self.get_alias_mock_object().apply() + assert exc.value.args[0]['changed'] + + @patch('ansible_collections.netapp.ontap.plugins.module_utils.netapp.OntapRestAPI.send_request') + def test_rest_create_idempotency(self, mock_request): + """Test rest create idempotency""" + data = self.mock_args() + set_module_args(data) + mock_request.side_effect = [ + SRR['is_rest'], + SRR['get_metrocluster_with_results'], + SRR['empty_good'], + SRR['end_of_sequence'] + ] + with pytest.raises(AnsibleExitJson) as exc: + self.get_alias_mock_object().apply() + assert not exc.value.args[0]['changed'] diff --git a/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_metrocluster_dr_group.py b/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_metrocluster_dr_group.py new file mode 100644 index 00000000..df349da4 --- /dev/null +++ b/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_metrocluster_dr_group.py @@ -0,0 +1,196 @@ +# (c) 2020, NetApp, Inc +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +''' unit tests for Ansible module: na_ontap_metrocluster ''' + +from __future__ import (absolute_import, division, print_function) + +__metaclass__ = type + +import json +import pytest + +from ansible_collections.netapp.ontap.tests.unit.compat import unittest +from ansible_collections.netapp.ontap.tests.unit.compat.mock import patch, Mock +from ansible.module_utils import basic +from ansible.module_utils._text import to_bytes + +from ansible_collections.netapp.ontap.plugins.modules.na_ontap_metrocluster_dr_group \ + import NetAppONTAPMetroClusterDRGroup as mcc_dr_pairs_module # module under test + +# REST API canned responses when mocking send_request +SRR = { + # common responses + 'is_rest': (200, {}, None), + 'is_zapi': (400, {}, "Unreachable"), + 'empty_good': (200, {}, None), + 'end_of_sequence': (500, None, "Unexpected call to send_request"), + 'generic_error': (400, None, "Expected error"), + # module specific responses + 'get_mcc_dr_pair_with_no_results': (200, {'records': [], 'num_records': 0}, None), + 'get_mcc_dr_pair_with_results': (200, {'records': [{'partner_cluster': {'name': 'rha2-b2b1_siteB'}, + 'dr_pairs': [{'node': {'name': 'rha17-a2'}, + 'partner': {'name': 'rha17-b2'}}, + {'node': {'name': 'rha17-b2'}, + 'partner': {'name': 'rha17-b1'}}], + 'id': '2'}], + 'num_records': 1}, None), + 'mcc_dr_pair_post': (200, {'job': { + 'uuid': 'fde79888-692a-11ea-80c2-005056b39fe7', + '_links': { + 'self': { + 'href': '/api/cluster/jobs/fde79888-692a-11ea-80c2-005056b39fe7'}}} + }, None), + 'get_mcc_dr_node': (200, {'records': [{'dr_group_id': '1'}], 'num_records': 1}, None), + 'get_mcc_dr_node_none': (200, {'records': [], 'num_records': 0}, None), + 'job': (200, { + "uuid": "cca3d070-58c6-11ea-8c0c-005056826c14", + "description": "POST /api/cluster/metrocluster", + "state": "success", + "message": "There are not enough disks in Pool1.", + "code": 2432836, + "start_time": "2020-02-26T10:35:44-08:00", + "end_time": "2020-02-26T10:47:38-08:00", + "_links": { + "self": { + "href": "/api/cluster/jobs/cca3d070-58c6-11ea-8c0c-005056826c14" + } + } + }, None) +} + + +def set_module_args(args): + """prepare arguments so that they will be picked up during module creation""" + args = json.dumps({'ANSIBLE_MODULE_ARGS': args}) + basic._ANSIBLE_ARGS = to_bytes(args) # pylint: disable=protected-access + + +class AnsibleExitJson(Exception): + """Exception class to be raised by module.exit_json and caught by the test case""" + + +class AnsibleFailJson(Exception): + """Exception class to be raised by module.fail_json and caught by the test case""" + + +def exit_json(*args, **kwargs): # pylint: disable=unused-argument + """function to patch over exit_json; package return data into an exception""" + if 'changed' not in kwargs: + kwargs['changed'] = False + raise AnsibleExitJson(kwargs) + + +def fail_json(*args, **kwargs): # pylint: disable=unused-argument + """function to patch over fail_json; package return data into an exception""" + kwargs['failed'] = True + raise AnsibleFailJson(kwargs) + + +class TestMyModule(unittest.TestCase): + """ Unit tests for na_ontap_metrocluster """ + + def setUp(self): + self.mock_module_helper = patch.multiple(basic.AnsibleModule, + exit_json=exit_json, + fail_json=fail_json) + self.mock_module_helper.start() + self.addCleanup(self.mock_module_helper.stop) + self.mock_mcc_dr_pair = { + 'partner_cluster_name': 'rha2-b2b1_siteB', + 'node_name': 'rha17-a2', + 'partner_node_name': 'rha17-b2', + 'node_name2': 'rha17-b2', + 'partner_node_name2': 'rha17-b1' + + } + + def mock_args(self): + return { + 'dr_pairs': [{ + 'node_name': self.mock_mcc_dr_pair['node_name'], + 'partner_node_name': self.mock_mcc_dr_pair['partner_node_name'], + }, { + 'node_name': self.mock_mcc_dr_pair['node_name2'], + 'partner_node_name': self.mock_mcc_dr_pair['partner_node_name2'], + }], + 'partner_cluster_name': self.mock_mcc_dr_pair['partner_cluster_name'], + 'hostname': 'test_host', + 'username': 'test_user', + 'password': 'test_pass!' + } + + def get_alias_mock_object(self): + alias_obj = mcc_dr_pairs_module() + return alias_obj + + @patch('ansible_collections.netapp.ontap.plugins.module_utils.netapp.OntapRestAPI.send_request') + def test_rest_successful_create(self, mock_request): + """Test successful rest create""" + data = self.mock_args() + set_module_args(data) + mock_request.side_effect = [ + SRR['is_rest'], + SRR['get_mcc_dr_pair_with_no_results'], + SRR['get_mcc_dr_pair_with_no_results'], + SRR['mcc_dr_pair_post'], + SRR['job'], + SRR['empty_good'], + SRR['end_of_sequence'] + ] + with pytest.raises(AnsibleExitJson) as exc: + self.get_alias_mock_object().apply() + assert exc.value.args[0]['changed'] + + @patch('ansible_collections.netapp.ontap.plugins.module_utils.netapp.OntapRestAPI.send_request') + def test_rest_create_idempotency(self, mock_request): + """Test rest create idempotency""" + data = self.mock_args() + set_module_args(data) + mock_request.side_effect = [ + SRR['is_rest'], + SRR['get_mcc_dr_pair_with_results'], + SRR['empty_good'], + SRR['end_of_sequence'] + ] + with pytest.raises(AnsibleExitJson) as exc: + self.get_alias_mock_object().apply() + assert not exc.value.args[0]['changed'] + + @patch('ansible_collections.netapp.ontap.plugins.module_utils.netapp.OntapRestAPI.send_request') + def test_rest_successful_delete(self, mock_request): + """Test successful rest delete""" + data = self.mock_args() + data['state'] = 'absent' + set_module_args(data) + mock_request.side_effect = [ + SRR['is_rest'], + SRR['get_mcc_dr_pair_with_results'], + SRR['mcc_dr_pair_post'], + SRR['job'], + SRR['empty_good'], + SRR['end_of_sequence'] + ] + with pytest.raises(AnsibleExitJson) as exc: + self.get_alias_mock_object().apply() + assert exc.value.args[0]['changed'] + + @patch('ansible_collections.netapp.ontap.plugins.module_utils.netapp.OntapRestAPI.send_request') + def test_rest_delete_idempotency(self, mock_request): + """Test rest delete idempotency""" + data = self.mock_args() + data['state'] = 'absent' + set_module_args(data) + mock_request.side_effect = [ + SRR['is_rest'], + SRR['get_mcc_dr_pair_with_no_results'], + SRR['get_mcc_dr_pair_with_no_results'], + SRR['get_mcc_dr_node_none'], + SRR['get_mcc_dr_node_none'], + SRR['job'], + SRR['empty_good'], + SRR['end_of_sequence'] + ] + with pytest.raises(AnsibleExitJson) as exc: + self.get_alias_mock_object().apply() + assert not exc.value.args[0]['changed'] diff --git a/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_motd.py b/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_motd.py new file mode 100644 index 00000000..5522986d --- /dev/null +++ b/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_motd.py @@ -0,0 +1,182 @@ +# (c) 2019, NetApp, Inc +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +""" unit tests for Ansible module: na_ontap_motd """ + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type +import json +import pytest + +from ansible_collections.netapp.ontap.tests.unit.compat import unittest +from ansible_collections.netapp.ontap.tests.unit.compat.mock import patch, Mock +from ansible.module_utils import basic +from ansible.module_utils._text import to_bytes +import ansible_collections.netapp.ontap.plugins.module_utils.netapp as netapp_utils + +from ansible_collections.netapp.ontap.plugins.modules.na_ontap_motd \ + import NetAppONTAPMotd as my_module # module under test + +if not netapp_utils.has_netapp_lib(): + pytestmark = pytest.mark.skip('skipping as missing required netapp_lib') + + +def set_module_args(args): + """prepare arguments so that they will be picked up during module creation""" + args = json.dumps({'ANSIBLE_MODULE_ARGS': args}) + basic._ANSIBLE_ARGS = to_bytes(args) # pylint: disable=protected-access + + +class AnsibleExitJson(Exception): + """Exception class to be raised by module.exit_json and caught by the test case""" + pass + + +class AnsibleFailJson(Exception): + """Exception class to be raised by module.fail_json and caught by the test case""" + pass + + +def exit_json(*args, **kwargs): # pylint: disable=unused-argument + """function to patch over exit_json; package return data into an exception""" + if 'changed' not in kwargs: + kwargs['changed'] = False + raise AnsibleExitJson(kwargs) + + +def fail_json(*args, **kwargs): # pylint: disable=unused-argument + """function to patch over fail_json; package return data into an exception""" + kwargs['failed'] = True + raise AnsibleFailJson(kwargs) + + +class MockONTAPConnection(object): + ''' mock server connection to ONTAP host ''' + + def __init__(self, kind=None): + ''' save arguments ''' + self.type = kind + self.xml_in = None + self.xml_out = None + + def invoke_successfully(self, xml, enable_tunneling): # pylint: disable=unused-argument + ''' mock invoke_successfully returning xml data ''' + self.xml_in = xml + if self.type == 'motd': + xml = self.build_motd_info() + elif self.type == 'motd_fail': + raise netapp_utils.zapi.NaApiError(code='TEST', message="This exception is from the unit test") + self.xml_out = xml + return xml + + @staticmethod + def build_motd_info(): + ''' build xml data for motd ''' + xml = netapp_utils.zapi.NaElement('xml') + data = {'num-records': 1, + 'attributes-list': {'vserver-motd-info': {'message': 'ansible', + 'vserver': 'ansible', + 'is-cluster-message-enabled': 'true'}}} + xml.translate_struct(data) + print(xml.to_string()) + return xml + + +class TestMyModule(unittest.TestCase): + ''' a group of related Unit Tests ''' + + def setUp(self): + self.mock_module_helper = patch.multiple(basic.AnsibleModule, + exit_json=exit_json, + fail_json=fail_json) + self.mock_module_helper.start() + self.addCleanup(self.mock_module_helper.stop) + self.server = MockONTAPConnection() + # whether to use a mock or a simulator + self.onbox = False + + def set_default_args(self): + if self.onbox: + hostname = '10.10.10.10' + username = 'admin' + password = 'password' + message = 'ansible' + vserver = 'ansible' + show_cluster_motd = 'true' + else: + hostname = 'hostname' + username = 'username' + password = 'password' + message = 'ansible' + vserver = 'ansible' + show_cluster_motd = 'true' + return dict({ + 'hostname': hostname, + 'username': username, + 'password': password, + 'message': message, + 'vserver': vserver, + 'show_cluster_motd': show_cluster_motd + }) + + def call_command(self, module_args): + ''' utility function to call apply ''' + module_args.update(self.set_default_args()) + set_module_args(module_args) + my_obj = my_module() + if not self.onbox: + my_obj.server = MockONTAPConnection('motd') + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + return exc.value.args[0]['changed'] + + def test_module_fail_when_required_args_missing(self): + ''' required arguments are reported as errors ''' + with pytest.raises(AnsibleFailJson) as exc: + set_module_args({}) + my_module() + print('Info: %s' % exc.value.args[0]['msg']) + + def test_ensure_motd_get_called(self): + ''' fetching details of motd ''' + set_module_args(self.set_default_args()) + my_obj = my_module() + my_obj.server = self.server + assert my_obj.motd_get() is None + + def test_ensure_get_called_existing(self): + ''' test for existing motd''' + set_module_args(self.set_default_args()) + my_obj = my_module() + my_obj.server = MockONTAPConnection(kind='motd') + assert my_obj.motd_get() + + def test_motd_create(self): + ''' test for creating motd''' + set_module_args(self.set_default_args()) + my_obj = my_module() + if not self.onbox: + my_obj.server = MockONTAPConnection(kind='motd') + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + assert not exc.value.args[0]['changed'] + + def test_motd_delete(self): + ''' test for deleting motd''' + module_args = { + 'state': 'absent', + } + changed = self.call_command(module_args) + assert changed + + def test_if_all_methods_catch_exception(self): + set_module_args(self.set_default_args()) + my_obj = my_module() + if not self.onbox: + my_obj.server = MockONTAPConnection('motd_fail') + with pytest.raises(AnsibleFailJson) as exc: + my_obj.motd_get() + assert '' in exc.value.args[0]['msg'] + with pytest.raises(AnsibleFailJson) as exc: + my_obj.modify_motd() + assert 'Error creating motd: ' in exc.value.args[0]['msg'] diff --git a/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_name_service_switch.py b/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_name_service_switch.py new file mode 100644 index 00000000..d6b0ce72 --- /dev/null +++ b/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_name_service_switch.py @@ -0,0 +1,180 @@ +# (c) 2019, NetApp, Inc +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +''' unit tests for Ansible module: na_ontap_volume_export_policy ''' + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type +import json +import pytest + +from ansible_collections.netapp.ontap.tests.unit.compat import unittest +from ansible_collections.netapp.ontap.tests.unit.compat.mock import patch, Mock +from ansible.module_utils import basic +from ansible.module_utils._text import to_bytes +import ansible_collections.netapp.ontap.plugins.module_utils.netapp as netapp_utils + +from ansible_collections.netapp.ontap.plugins.modules.na_ontap_name_service_switch \ + import NetAppONTAPNsswitch as nss_module # module under test + +if not netapp_utils.has_netapp_lib(): + pytestmark = pytest.mark.skip('skipping as missing required netapp_lib') + + +def set_module_args(args): + """prepare arguments so that they will be picked up during module creation""" + args = json.dumps({'ANSIBLE_MODULE_ARGS': args}) + basic._ANSIBLE_ARGS = to_bytes(args) # pylint: disable=protected-access + + +class AnsibleExitJson(Exception): + """Exception class to be raised by module.exit_json and caught by the test case""" + pass + + +class AnsibleFailJson(Exception): + """Exception class to be raised by module.fail_json and caught by the test case""" + pass + + +def exit_json(*args, **kwargs): # pylint: disable=unused-argument + """function to patch over exit_json; package return data into an exception""" + if 'changed' not in kwargs: + kwargs['changed'] = False + raise AnsibleExitJson(kwargs) + + +def fail_json(*args, **kwargs): # pylint: disable=unused-argument + """function to patch over fail_json; package return data into an exception""" + kwargs['failed'] = True + raise AnsibleFailJson(kwargs) + + +class MockONTAPConnection(object): + ''' mock server connection to ONTAP host ''' + + def __init__(self, kind=None, data=None): + ''' save arguments ''' + self.kind = kind + self.params = data + self.xml_in = None + self.xml_out = None + + def invoke_successfully(self, xml, enable_tunneling): # pylint: disable=unused-argument + ''' mock invoke_successfully returning xml data ''' + self.xml_in = xml + if self.kind == 'nss': + xml = self.build_nss_info(self.params) + if self.kind == 'error': + error = netapp_utils.zapi.NaApiError('test', 'error') + raise error + self.xml_out = xml + return xml + + @staticmethod + def build_nss_info(nss_details): + xml = netapp_utils.zapi.NaElement('xml') + attributes = { + 'num-records': 1, + 'attributes-list': { + 'namservice-nsswitch-config-info': { + 'nameservice-database': nss_details['database_type'], + 'nameservice-sources': { + 'nss-source-type': nss_details['sources'] + } + } + } + } + xml.translate_struct(attributes) + return xml + + +class TestMyModule(unittest.TestCase): + ''' Unit tests for na_ontap_name_service_switch ''' + + def setUp(self): + self.mock_module_helper = patch.multiple(basic.AnsibleModule, + exit_json=exit_json, + fail_json=fail_json) + self.mock_module_helper.start() + self.addCleanup(self.mock_module_helper.stop) + self.mock_nss = { + 'state': 'present', + 'vserver': 'test_vserver', + 'database_type': 'namemap', + 'sources': 'files,ldap', + } + + def mock_args(self): + return { + 'state': self.mock_nss['state'], + 'vserver': self.mock_nss['vserver'], + 'database_type': self.mock_nss['database_type'], + 'sources': self.mock_nss['sources'], + 'hostname': 'test', + 'username': 'test_user', + 'password': 'test_pass!' + } + + def get_nss_object(self, kind=None): + nss_obj = nss_module() + nss_obj.asup_log_for_cserver = Mock(return_value=None) + if kind is None: + nss_obj.server = MockONTAPConnection() + else: + nss_obj.server = MockONTAPConnection(kind=kind, data=self.mock_nss) + return nss_obj + + def test_module_fail_when_required_args_missing(self): + with pytest.raises(AnsibleFailJson) as exc: + set_module_args({}) + nss_module() + print('Info: %s' % exc.value.args[0]['msg']) + + def test_get_nonexistent_nss(self): + set_module_args(self.mock_args()) + result = self.get_nss_object().get_name_service_switch() + assert result is None + + def test_get_existing_nss(self): + set_module_args(self.mock_args()) + result = self.get_nss_object('nss').get_name_service_switch() + assert result + + def test_successfully_create(self): + set_module_args(self.mock_args()) + with pytest.raises(AnsibleExitJson) as exc: + self.get_nss_object().apply() + assert exc.value.args[0]['changed'] + + def test_successfully_modify(self): + data = self.mock_args() + data['sources'] = 'files' + set_module_args(data) + with pytest.raises(AnsibleExitJson) as exc: + self.get_nss_object('nss').apply() + assert exc.value.args[0]['changed'] + + def test_successfully_delete(self): + data = self.mock_args() + data['state'] = 'absent' + set_module_args(data) + with pytest.raises(AnsibleExitJson) as exc: + self.get_nss_object('nss').apply() + assert exc.value.args[0]['changed'] + + def test_error(self): + data = self.mock_args() + set_module_args(data) + with pytest.raises(AnsibleFailJson) as exc: + self.get_nss_object('error').create_name_service_switch() + print(exc) + assert exc.value.args[0]['msg'] == 'Error on creating name service switch config on vserver test_vserver: NetApp API failed. Reason - test:error' + with pytest.raises(AnsibleFailJson) as exc: + self.get_nss_object('error').modify_name_service_switch({}) + print(exc) + assert exc.value.args[0]['msg'] == 'Error on modifying name service switch config on vserver test_vserver: NetApp API failed. Reason - test:error' + with pytest.raises(AnsibleFailJson) as exc: + self.get_nss_object('error').delete_name_service_switch() + print(exc) + assert exc.value.args[0]['msg'] == 'Error on deleting name service switch config on vserver test_vserver: NetApp API failed. Reason - test:error' diff --git a/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_ndmp.py b/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_ndmp.py new file mode 100644 index 00000000..6fc9b89e --- /dev/null +++ b/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_ndmp.py @@ -0,0 +1,227 @@ +# (c) 2019, NetApp, Inc +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +''' unit test template for ONTAP Ansible module ''' + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type +import json +import pytest + +from ansible.module_utils import basic +from ansible.module_utils._text import to_bytes +from ansible_collections.netapp.ontap.tests.unit.compat import unittest +from ansible_collections.netapp.ontap.tests.unit.compat.mock import patch, Mock +import ansible_collections.netapp.ontap.plugins.module_utils.netapp as netapp_utils + +from ansible_collections.netapp.ontap.plugins.modules.na_ontap_ndmp \ + import NetAppONTAPNdmp as ndmp_module # module under test + +if not netapp_utils.has_netapp_lib(): + pytestmark = pytest.mark.skip('skipping as missing required netapp_lib') + +# REST API canned responses when mocking send_request +SRR = { + # common responses + 'is_rest': (200, {}, None), + 'is_zapi': (400, {}, "Unreachable"), + 'get_uuid': (200, {'records': [{'uuid': 'testuuid'}]}, None), + 'empty_good': (200, {}, None), + 'end_of_sequence': (500, None, "Unexpected call to send_request"), + 'generic_error': (400, None, 'Error fetching ndmp from ansible: NetApp API failed. Reason - Unexpected error:', + "REST API currently does not support 'backup_log_enable, ignore_ctime_enabled'"), + 'get_ndmp_uuid': (200, {"records": [{"svm": {"name": "svm1", "uuid": "02c9e252-41be-11e9-81d5-00a0986138f7"}}]}, None), + 'get_ndmp': (200, {"enabled": True, "authentication_types": ["test"], + "records": [{"svm": {"name": "svm1", "uuid": "02c9e252-41be-11e9-81d5-00a0986138f7"}}]}, None) +} + + +def set_module_args(args): + """prepare arguments so that they will be picked up during module creation""" + args = json.dumps({'ANSIBLE_MODULE_ARGS': args}) + basic._ANSIBLE_ARGS = to_bytes(args) # pylint: disable=protected-access + + +class AnsibleExitJson(Exception): + """Exception class to be raised by module.exit_json and caught by the test case""" + + +class AnsibleFailJson(Exception): + """Exception class to be raised by module.fail_json and caught by the test case""" + + +def exit_json(*args, **kwargs): # pylint: disable=unused-argument + """function to patch over exit_json; package return data into an exception""" + if 'changed' not in kwargs: + kwargs['changed'] = False + raise AnsibleExitJson(kwargs) + + +def fail_json(*args, **kwargs): # pylint: disable=unused-argument + """function to patch over fail_json; package return data into an exception""" + kwargs['failed'] = True + raise AnsibleFailJson(kwargs) + + +class MockONTAPConnection(object): + ''' mock server connection to ONTAP host ''' + + def __init__(self, kind=None, data=None): + ''' save arguments ''' + self.type = kind + self.data = data + self.xml_in = None + self.xml_out = None + + def invoke_successfully(self, xml, enable_tunneling): # pylint: disable=unused-argument + ''' mock invoke_successfully returning xml data ''' + self.xml_in = xml + if self.type == 'ndmp': + xml = self.build_ndmp_info(self.data) + if self.type == 'error': + error = netapp_utils.zapi.NaApiError('test', 'error') + raise error + self.xml_out = xml + return xml + + @staticmethod + def build_ndmp_info(ndmp_details): + ''' build xml data for ndmp ''' + xml = netapp_utils.zapi.NaElement('xml') + attributes = { + 'num-records': 1, + 'attributes-list': { + 'ndmp-vserver-attributes-info': { + 'ignore_ctime_enabled': ndmp_details['ignore_ctime_enabled'], + 'backup_log_enable': ndmp_details['backup_log_enable'], + + 'authtype': [ + {'ndmpd-authtypes': 'plaintext'}, + {'ndmpd-authtypes': 'challenge'} + ] + } + } + } + xml.translate_struct(attributes) + return xml + + +class TestMyModule(unittest.TestCase): + ''' a group of related Unit Tests ''' + + def setUp(self): + self.mock_module_helper = patch.multiple(basic.AnsibleModule, + exit_json=exit_json, + fail_json=fail_json) + self.mock_module_helper.start() + self.addCleanup(self.mock_module_helper.stop) + self.mock_ndmp = { + 'ignore_ctime_enabled': True, + 'backup_log_enable': 'false', + 'authtype': 'plaintext', + 'enable': True + } + + def mock_args(self, rest=False): + if rest: + return { + 'authtype': self.mock_ndmp['authtype'], + 'enable': True, + 'vserver': 'ansible', + 'hostname': 'test', + 'username': 'test_user', + 'password': 'test_pass!', + 'https': 'False' + } + else: + return { + 'vserver': 'ansible', + 'authtype': self.mock_ndmp['authtype'], + 'ignore_ctime_enabled': self.mock_ndmp['ignore_ctime_enabled'], + 'backup_log_enable': self.mock_ndmp['backup_log_enable'], + 'enable': self.mock_ndmp['enable'], + 'hostname': 'test', + 'username': 'test_user', + 'password': 'test_pass!' + } + + def get_ndmp_mock_object(self, kind=None, cx_type='zapi'): + """ + Helper method to return an na_ontap_ndmp object + :param kind: passes this param to MockONTAPConnection() + :return: na_ontap_ndmp object + """ + obj = ndmp_module() + if cx_type == 'zapi': + obj.asup_log_for_cserver = Mock(return_value=None) + obj.server = Mock() + obj.server.invoke_successfully = Mock() + if kind is None: + obj.server = MockONTAPConnection() + else: + obj.server = MockONTAPConnection(kind=kind, data=self.mock_ndmp) + return obj + + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_ndmp.NetAppONTAPNdmp.ndmp_get_iter') + def test_successful_modify(self, ger_ndmp): + ''' Test successful modify ndmp''' + data = self.mock_args() + set_module_args(data) + current = { + 'ignore_ctime_enabled': False, + 'backup_log_enable': True + } + ger_ndmp.side_effect = [ + current + ] + with pytest.raises(AnsibleExitJson) as exc: + self.get_ndmp_mock_object('ndmp').apply() + assert exc.value.args[0]['changed'] + + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_ndmp.NetAppONTAPNdmp.ndmp_get_iter') + def test_modify_error(self, ger_ndmp): + ''' Test modify error ''' + data = self.mock_args() + set_module_args(data) + current = { + 'ignore_ctime_enabled': False, + 'backup_log_enable': True + } + ger_ndmp.side_effect = [ + current + ] + with pytest.raises(AnsibleFailJson) as exc: + self.get_ndmp_mock_object('error').apply() + assert exc.value.args[0]['msg'] == 'Error modifying ndmp on ansible: NetApp API failed. Reason - test:error' + + @patch('ansible_collections.netapp.ontap.plugins.module_utils.netapp.OntapRestAPI.send_request') + def test_rest_error(self, mock_request): + data = self.mock_args() + data['use_rest'] = 'Always' + set_module_args(data) + mock_request.side_effect = [ + SRR['is_rest'], + SRR['generic_error'], + SRR['end_of_sequence'] + ] + with pytest.raises(AnsibleFailJson) as exc: + self.get_ndmp_mock_object(cx_type='rest').apply() + assert exc.value.args[0]['msg'] == SRR['generic_error'][3] + + @patch('ansible_collections.netapp.ontap.plugins.module_utils.netapp.OntapRestAPI.send_request') + def test_rest_successfully_modify(self, mock_request): + data = self.mock_args(rest=True) + data['use_rest'] = 'Always' + set_module_args(data) + mock_request.side_effect = [ + # SRR['is_rest'], # WHY IS IT NOT CALLED HERE? + SRR['get_ndmp_uuid'], # for get svm uuid: protocols/ndmp/svms + SRR['get_ndmp'], # for get ndmp details: '/protocols/ndmp/svms/' + uuid + SRR['get_ndmp_uuid'], # for get svm uuid: protocols/ndmp/svms (before modify) + SRR['empty_good'], # modify (patch) + SRR['end_of_sequence'], + ] + my_obj = ndmp_module() + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + assert exc.value.args[0]['changed'] diff --git a/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_net_ifgrp.py b/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_net_ifgrp.py new file mode 100644 index 00000000..f849c35a --- /dev/null +++ b/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_net_ifgrp.py @@ -0,0 +1,299 @@ +# (c) 2018, NetApp, Inc +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +''' unit test template for ONTAP Ansible module ''' + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type +import json +import pytest + +from ansible_collections.netapp.ontap.tests.unit.compat import unittest +from ansible_collections.netapp.ontap.tests.unit.compat.mock import patch, Mock +from ansible.module_utils import basic +from ansible.module_utils._text import to_bytes +import ansible_collections.netapp.ontap.plugins.module_utils.netapp as netapp_utils + +from ansible_collections.netapp.ontap.plugins.modules.na_ontap_net_ifgrp \ + import NetAppOntapIfGrp as ifgrp_module # module under test + +if not netapp_utils.has_netapp_lib(): + pytestmark = pytest.mark.skip('skipping as missing required netapp_lib') + + +def set_module_args(args): + """prepare arguments so that they will be picked up during module creation""" + args = json.dumps({'ANSIBLE_MODULE_ARGS': args}) + basic._ANSIBLE_ARGS = to_bytes(args) # pylint: disable=protected-access + + +class AnsibleExitJson(Exception): + """Exception class to be raised by module.exit_json and caught by the test case""" + pass + + +class AnsibleFailJson(Exception): + """Exception class to be raised by module.fail_json and caught by the test case""" + pass + + +def exit_json(*args, **kwargs): # pylint: disable=unused-argument + """function to patch over exit_json; package return data into an exception""" + if 'changed' not in kwargs: + kwargs['changed'] = False + raise AnsibleExitJson(kwargs) + + +def fail_json(*args, **kwargs): # pylint: disable=unused-argument + """function to patch over fail_json; package return data into an exception""" + kwargs['failed'] = True + raise AnsibleFailJson(kwargs) + + +class MockONTAPConnection(object): + ''' mock server connection to ONTAP host ''' + + def __init__(self, kind=None, data=None): + ''' save arguments ''' + self.kind = kind + self.params = data + self.xml_in = None + self.xml_out = None + + def invoke_successfully(self, xml, enable_tunneling): # pylint: disable=unused-argument + ''' mock invoke_successfully returning xml data ''' + self.xml_in = xml + if self.kind == 'ifgrp': + xml = self.build_ifgrp_info(self.params) + elif self.kind == 'ifgrp-ports': + xml = self.build_ifgrp_ports_info(self.params) + elif self.kind == 'ifgrp-fail': + raise netapp_utils.zapi.NaApiError(code='TEST', message="This exception is from the unit test") + self.xml_out = xml + return xml + + @staticmethod + def build_ifgrp_info(ifgrp_details): + ''' build xml data for ifgrp-info ''' + xml = netapp_utils.zapi.NaElement('xml') + attributes = { + 'num-records': 1, + 'attributes-list': { + 'net-port-info': { + 'port': ifgrp_details['name'], + 'ifgrp-distribution-function': 'mac', + 'ifgrp-mode': ifgrp_details['mode'], + 'node': ifgrp_details['node'] + } + } + } + xml.translate_struct(attributes) + return xml + + @staticmethod + def build_ifgrp_ports_info(data): + ''' build xml data for ifgrp-ports ''' + xml = netapp_utils.zapi.NaElement('xml') + attributes = { + 'attributes': { + 'net-ifgrp-info': { + 'ports': [ + {'lif-bindable': data['ports'][0]}, + {'lif-bindable': data['ports'][1]}, + {'lif-bindable': data['ports'][2]} + ] + } + } + } + xml.translate_struct(attributes) + return xml + + +class TestMyModule(unittest.TestCase): + ''' a group of related Unit Tests ''' + + def setUp(self): + self.mock_module_helper = patch.multiple(basic.AnsibleModule, + exit_json=exit_json, + fail_json=fail_json) + self.mock_module_helper.start() + self.addCleanup(self.mock_module_helper.stop) + self.mock_ifgrp = { + 'name': 'test', + 'port': 'a1', + 'node': 'test_vserver', + 'mode': 'something' + } + + def mock_args(self): + return { + 'name': self.mock_ifgrp['name'], + 'distribution_function': 'mac', + 'ports': [self.mock_ifgrp['port']], + 'node': self.mock_ifgrp['node'], + 'mode': self.mock_ifgrp['mode'], + 'hostname': 'test', + 'username': 'test_user', + 'password': 'test_pass!' + } + + def get_ifgrp_mock_object(self, kind=None, data=None): + """ + Helper method to return an na_ontap_net_ifgrp object + :param kind: passes this param to MockONTAPConnection() + :return: na_ontap_net_ifgrp object + """ + obj = ifgrp_module() + obj.autosupport_log = Mock(return_value=None) + if data is None: + data = self.mock_ifgrp + obj.server = MockONTAPConnection(kind=kind, data=data) + 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({}) + ifgrp_module() + print('Info: %s' % exc.value.args[0]['msg']) + + def test_get_nonexistent_ifgrp(self): + ''' Test if get_ifgrp returns None for non-existent ifgrp ''' + set_module_args(self.mock_args()) + result = self.get_ifgrp_mock_object().get_if_grp() + assert result is None + + def test_get_existing_ifgrp(self): + ''' Test if get_ifgrp returns details for existing ifgrp ''' + set_module_args(self.mock_args()) + result = self.get_ifgrp_mock_object('ifgrp').get_if_grp() + assert result['name'] == self.mock_ifgrp['name'] + + def test_successful_create(self): + ''' Test successful create ''' + data = self.mock_args() + set_module_args(data) + with pytest.raises(AnsibleExitJson) as exc: + self.get_ifgrp_mock_object().apply() + assert exc.value.args[0]['changed'] + + def test_successful_delete(self): + ''' Test delete existing volume ''' + data = self.mock_args() + data['state'] = 'absent' + set_module_args(data) + with pytest.raises(AnsibleExitJson) as exc: + self.get_ifgrp_mock_object('ifgrp').apply() + assert exc.value.args[0]['changed'] + + def test_successful_modify(self): + ''' Test delete existing volume ''' + data = self.mock_args() + data['ports'] = ['1', '2', '3'] + set_module_args(data) + with pytest.raises(AnsibleExitJson) as exc: + self.get_ifgrp_mock_object('ifgrp').apply() + assert exc.value.args[0]['changed'] + + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_net_ifgrp.NetAppOntapIfGrp.get_if_grp') + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_net_ifgrp.NetAppOntapIfGrp.create_if_grp') + def test_create_called(self, create_ifgrp, get_ifgrp): + data = self.mock_args() + set_module_args(data) + get_ifgrp.return_value = None + with pytest.raises(AnsibleExitJson) as exc: + self.get_ifgrp_mock_object().apply() + get_ifgrp.assert_called_with() + create_ifgrp.assert_called_with() + + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_net_ifgrp.NetAppOntapIfGrp.add_port_to_if_grp') + def test_if_ports_are_added_after_create(self, add_ports): + ''' Test successful create ''' + data = self.mock_args() + set_module_args(data) + self.get_ifgrp_mock_object().create_if_grp() + add_ports.assert_called_with('a1') + + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_net_ifgrp.NetAppOntapIfGrp.get_if_grp') + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_net_ifgrp.NetAppOntapIfGrp.delete_if_grp') + def test_delete_called(self, delete_ifgrp, get_ifgrp): + data = self.mock_args() + data['state'] = 'absent' + set_module_args(data) + get_ifgrp.return_value = Mock() + with pytest.raises(AnsibleExitJson) as exc: + self.get_ifgrp_mock_object().apply() + get_ifgrp.assert_called_with() + delete_ifgrp.assert_called_with() + + def test_get_return_value(self): + data = self.mock_args() + set_module_args(data) + result = self.get_ifgrp_mock_object('ifgrp').get_if_grp() + assert result['name'] == data['name'] + assert result['mode'] == data['mode'] + assert result['node'] == data['node'] + + def test_get_ports_list(self): + data = self.mock_args() + data['ports'] = ['e0a', 'e0b', 'e0c'] + set_module_args(data) + result = self.get_ifgrp_mock_object('ifgrp-ports', data).get_if_grp_ports() + assert result['ports'] == data['ports'] + + def test_add_port_packet(self): + data = self.mock_args() + set_module_args(data) + obj = self.get_ifgrp_mock_object('ifgrp') + obj.add_port_to_if_grp('addme') + assert obj.server.xml_in['port'] == 'addme' + + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_net_ifgrp.NetAppOntapIfGrp.remove_port_to_if_grp') + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_net_ifgrp.NetAppOntapIfGrp.add_port_to_if_grp') + def test_modify_ports_calls_remove_existing_ports(self, add_port, remove_port): + ''' Test if already existing ports are not being added again ''' + data = self.mock_args() + data['ports'] = ['1', '2'] + set_module_args(data) + self.get_ifgrp_mock_object('ifgrp').modify_ports(current_ports=['1', '2', '3']) + assert remove_port.call_count == 1 + assert add_port.call_count == 0 + + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_net_ifgrp.NetAppOntapIfGrp.remove_port_to_if_grp') + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_net_ifgrp.NetAppOntapIfGrp.add_port_to_if_grp') + def test_modify_ports_calls_add_new_ports(self, add_port, remove_port): + ''' Test new ports are added ''' + data = self.mock_args() + data['ports'] = ['1', '2', '3', '4'] + set_module_args(data) + self.get_ifgrp_mock_object('ifgrp').modify_ports(current_ports=['1', '2']) + assert remove_port.call_count == 0 + assert add_port.call_count == 2 + + def test_get_ports_returns_none(self): + set_module_args(self.mock_args()) + result = self.get_ifgrp_mock_object().get_if_grp_ports() + assert result['ports'] == [] + result = self.get_ifgrp_mock_object().get_if_grp() + assert result is None + + def test_if_all_methods_catch_exception(self): + set_module_args(self.mock_args()) + with pytest.raises(AnsibleFailJson) as exc: + self.get_ifgrp_mock_object('ifgrp-fail').get_if_grp() + assert 'Error getting if_group test' in exc.value.args[0]['msg'] + with pytest.raises(AnsibleFailJson) as exc: + self.get_ifgrp_mock_object('ifgrp-fail').create_if_grp() + assert 'Error creating if_group test' in exc.value.args[0]['msg'] + with pytest.raises(AnsibleFailJson) as exc: + self.get_ifgrp_mock_object('ifgrp-fail').get_if_grp_ports() + assert 'Error getting if_group ports test' in exc.value.args[0]['msg'] + with pytest.raises(AnsibleFailJson) as exc: + self.get_ifgrp_mock_object('ifgrp-fail').add_port_to_if_grp('test-port') + assert 'Error adding port test-port to if_group test' in exc.value.args[0]['msg'] + with pytest.raises(AnsibleFailJson) as exc: + self.get_ifgrp_mock_object('ifgrp-fail').remove_port_to_if_grp('test-port') + assert 'Error removing port test-port to if_group test' in exc.value.args[0]['msg'] + with pytest.raises(AnsibleFailJson) as exc: + self.get_ifgrp_mock_object('ifgrp-fail').delete_if_grp() + assert 'Error deleting if_group test' in exc.value.args[0]['msg'] diff --git a/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_net_port.py b/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_net_port.py new file mode 100644 index 00000000..7c16c243 --- /dev/null +++ b/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_net_port.py @@ -0,0 +1,180 @@ +# (c) 2018, NetApp, Inc +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +''' unit test template for ONTAP Ansible module ''' + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type +import json +import pytest + +from ansible_collections.netapp.ontap.tests.unit.compat import unittest +from ansible_collections.netapp.ontap.tests.unit.compat.mock import patch, Mock +from ansible.module_utils import basic +from ansible.module_utils._text import to_bytes +import ansible_collections.netapp.ontap.plugins.module_utils.netapp as netapp_utils + +from ansible_collections.netapp.ontap.plugins.modules.na_ontap_net_port \ + import NetAppOntapNetPort as port_module # module under test + +if not netapp_utils.has_netapp_lib(): + pytestmark = pytest.mark.skip('skipping as missing required netapp_lib') + + +def set_module_args(args): + """prepare arguments so that they will be picked up during module creation""" + args = json.dumps({'ANSIBLE_MODULE_ARGS': args}) + basic._ANSIBLE_ARGS = to_bytes(args) # pylint: disable=protected-access + + +class AnsibleExitJson(Exception): + """Exception class to be raised by module.exit_json and caught by the test case""" + pass + + +class AnsibleFailJson(Exception): + """Exception class to be raised by module.fail_json and caught by the test case""" + pass + + +def exit_json(*args, **kwargs): # pylint: disable=unused-argument + """function to patch over exit_json; package return data into an exception""" + if 'changed' not in kwargs: + kwargs['changed'] = False + raise AnsibleExitJson(kwargs) + + +def fail_json(*args, **kwargs): # pylint: disable=unused-argument + """function to patch over fail_json; package return data into an exception""" + kwargs['failed'] = True + raise AnsibleFailJson(kwargs) + + +class MockONTAPConnection(object): + ''' mock server connection to ONTAP host ''' + + def __init__(self, kind=None, data=None): + ''' save arguments ''' + self.type = kind + self.data = data + self.xml_in = None + self.xml_out = None + + def invoke_successfully(self, xml, enable_tunneling): # pylint: disable=unused-argument + ''' mock invoke_successfully returning xml data ''' + self.xml_in = xml + if self.type == 'port': + xml = self.build_port_info(self.data) + self.xml_out = xml + return xml + + @staticmethod + def build_port_info(port_details): + ''' build xml data for net-port-info ''' + xml = netapp_utils.zapi.NaElement('xml') + attributes = { + 'num-records': 1, + 'attributes-list': { + 'net-port-info': { + # 'port': port_details['port'], + 'mtu': port_details['mtu'], + 'is-administrative-auto-negotiate': 'true', + 'ipspace': 'default', + 'administrative-flowcontrol': port_details['flowcontrol_admin'], + 'node': port_details['node'] + } + } + } + xml.translate_struct(attributes) + return xml + + +class TestMyModule(unittest.TestCase): + ''' a group of related Unit Tests ''' + + def setUp(self): + self.mock_module_helper = patch.multiple(basic.AnsibleModule, + exit_json=exit_json, + fail_json=fail_json) + self.mock_module_helper.start() + self.addCleanup(self.mock_module_helper.stop) + self.server = MockONTAPConnection() + self.mock_port = { + 'node': 'test', + 'ports': 'a1', + 'flowcontrol_admin': 'something', + 'mtu': '1000' + } + + def mock_args(self): + return { + 'node': self.mock_port['node'], + 'flowcontrol_admin': self.mock_port['flowcontrol_admin'], + 'ports': [self.mock_port['ports']], + 'mtu': self.mock_port['mtu'], + 'hostname': 'test', + 'username': 'test_user', + 'password': 'test_pass!' + } + + def get_port_mock_object(self, kind=None, data=None): + """ + Helper method to return an na_ontap_net_port object + :param kind: passes this param to MockONTAPConnection() + :return: na_ontap_net_port object + """ + obj = port_module() + obj.autosupport_log = Mock(return_value=None) + if data is None: + data = self.mock_port + obj.server = MockONTAPConnection(kind=kind, data=data) + 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({}) + port_module() + print('Info: %s' % exc.value.args[0]['msg']) + + def test_get_nonexistent_port(self): + ''' Test if get_net_port returns None for non-existent port ''' + set_module_args(self.mock_args()) + result = self.get_port_mock_object().get_net_port('test') + assert result is None + + def test_get_existing_port(self): + ''' Test if get_net_port returns details for existing port ''' + set_module_args(self.mock_args()) + result = self.get_port_mock_object('port').get_net_port('test') + assert result['mtu'] == self.mock_port['mtu'] + assert result['flowcontrol_admin'] == self.mock_port['flowcontrol_admin'] + + def test_successful_modify(self): + ''' Test modify_net_port ''' + data = self.mock_args() + data['mtu'] = '2000' + set_module_args(data) + with pytest.raises(AnsibleExitJson) as exc: + self.get_port_mock_object('port').apply() + assert exc.value.args[0]['changed'] + + def test_successful_modify_multiple_ports(self): + ''' Test modify_net_port ''' + data = self.mock_args() + data['ports'] = ['a1', 'a2'] + data['mtu'] = '2000' + set_module_args(data) + with pytest.raises(AnsibleExitJson) as exc: + self.get_port_mock_object('port').apply() + assert exc.value.args[0]['changed'] + + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_net_port.NetAppOntapNetPort.get_net_port') + def test_get_called(self, get_port): + ''' Test get_net_port ''' + data = self.mock_args() + data['ports'] = ['a1', 'a2'] + set_module_args(data) + with pytest.raises(AnsibleExitJson) as exc: + self.get_port_mock_object('port').apply() + assert get_port.call_count == 2 diff --git a/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_net_routes.py b/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_net_routes.py new file mode 100644 index 00000000..ab7d57bc --- /dev/null +++ b/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_net_routes.py @@ -0,0 +1,461 @@ +# (c) 2018, NetApp, Inc +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +''' unit test template for ONTAP Ansible module ''' + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type +import json +import pytest + +from ansible.module_utils import basic +from ansible.module_utils._text import to_bytes +from ansible_collections.netapp.ontap.tests.unit.compat import unittest +from ansible_collections.netapp.ontap.tests.unit.compat.mock import patch, Mock +import ansible_collections.netapp.ontap.plugins.module_utils.netapp as netapp_utils + +from ansible_collections.netapp.ontap.plugins.modules.na_ontap_net_routes \ + import NetAppOntapNetRoutes as net_route_module # module under test + +if not netapp_utils.has_netapp_lib(): + pytestmark = pytest.mark.skip('skipping as missing required netapp_lib') + +# REST API canned responses when mocking send_request +SRR = { + # common responses + 'is_rest': (200, {}, None), + 'is_zapi': (400, {}, "Unreachable"), + 'empty_good': (200, {}, None), + 'end_of_sequence': (500, None, "Unexpected call to send_request"), + 'generic_error': (400, None, "Expected error"), + # module specific responses + 'net_routes_record': (200, + {'records': [{"destination": {"address": "176.0.0.0", + "netmask": "24", + "family": "ipv4"}, + "gateway": '10.193.72.1', + "uuid": '1cd8a442-86d1-11e0-ae1c-123478563412', + "svm": {"name": "test_vserver"}}]}, None), + 'modified_record': (200, + {'records': [{"destination": {"address": "0.0.0.0", + "netmask": "0", + "family": "ipv4"}, + "gateway": "10.193.72.1", + "uuid": '1cd8a442-86d1-11e0-ae1c-123478563412', + "svm": {"name": "test_vserver"}}]}, None) +} + + +def set_module_args(args): + """prepare arguments so that they will be picked up during module creation""" + args = json.dumps({'ANSIBLE_MODULE_ARGS': args}) + basic._ANSIBLE_ARGS = to_bytes(args) # pylint: disable=protected-access + + +class AnsibleExitJson(Exception): + """Exception class to be raised by module.exit_json and caught by the test case""" + + +class AnsibleFailJson(Exception): + """Exception class to be raised by module.fail_json and caught by the test case""" + + +def exit_json(*args, **kwargs): # pylint: disable=unused-argument + """function to patch over exit_json; package return data into an exception""" + if 'changed' not in kwargs: + kwargs['changed'] = False + raise AnsibleExitJson(kwargs) + + +def fail_json(*args, **kwargs): # pylint: disable=unused-argument + """function to patch over fail_json; package return data into an exception""" + kwargs['failed'] = True + raise AnsibleFailJson(kwargs) + + +class MockONTAPConnection(object): + ''' mock server connection to ONTAP host ''' + + def __init__(self, kind=None, data=None): + ''' save arguments ''' + self.kind = kind + self.params = data + self.xml_in = None + self.xml_out = None + + def invoke_successfully(self, xml, enable_tunneling): # pylint: disable=unused-argument + ''' mock invoke_successfully returning xml data ''' + self.xml_in = xml + if self.kind == 'net_route': + xml = self.build_net_route_info(self.params) + self.xml_out = xml + return xml + + @staticmethod + def build_net_route_info(net_route_details): + ''' build xml data for net_route-attributes ''' + xml = netapp_utils.zapi.NaElement('xml') + attributes = { + 'attributes': { + 'net-vs-routes-info': { + 'address-family': 'ipv4', + 'destination': net_route_details['destination'], + 'gateway': net_route_details['gateway'], + 'metric': net_route_details['metric'], + 'vserver': net_route_details['vserver'] + } + } + } + xml.translate_struct(attributes) + return xml + + +class TestMyModule(unittest.TestCase): + ''' a group of related Unit Tests ''' + + def setUp(self): + self.mock_module_helper = patch.multiple(basic.AnsibleModule, + exit_json=exit_json, + fail_json=fail_json) + self.mock_module_helper.start() + self.addCleanup(self.mock_module_helper.stop) + self.server = MockONTAPConnection() + self.mock_net_route = { + 'destination': '176.0.0.0/24', + 'gateway': '10.193.72.1', + 'vserver': 'test_vserver', + 'metric': 70 + } + + def mock_args(self, rest=False, modify=False): + if rest: + return { + 'vserver': self.mock_net_route['vserver'], + 'destination': self.mock_net_route['destination'], + 'gateway': self.mock_net_route['gateway'], + 'hostname': 'test', + 'username': 'test_user', + 'password': 'test_pass!' + } + elif modify: + return { + 'vserver': self.mock_net_route['vserver'], + 'destination': '0.0.0.0/0', + 'gateway': '10.193.72.1', + 'from_destination': self.mock_net_route['destination'], + 'from_gateway': self.mock_net_route['gateway'], + 'hostname': 'test', + 'username': 'test_user', + 'password': 'test_pass!' + } + else: + return { + 'vserver': self.mock_net_route['vserver'], + 'destination': self.mock_net_route['destination'], + 'gateway': self.mock_net_route['gateway'], + 'metric': self.mock_net_route['metric'], + 'hostname': 'test', + 'username': 'test_user', + 'password': 'test_pass!' + } + + def get_net_route_mock_object(self, kind=None, data=None, cx_type='zapi'): + """ + Helper method to return an na_ontap_net_route object + :param kind: passes this param to MockONTAPConnection() + :param data: passes this data to MockONTAPConnection() + :param type: differentiates zapi and rest procedure + :return: na_ontap_net_route object + """ + net_route_obj = net_route_module() + if cx_type == 'zapi': + net_route_obj.ems_log_event = Mock(return_value=None) + net_route_obj.cluster = Mock() + net_route_obj.cluster.invoke_successfully = Mock() + if kind is None: + net_route_obj.server = MockONTAPConnection() + else: + if data is None: + net_route_obj.server = MockONTAPConnection(kind='net_route', data=self.mock_net_route) + else: + net_route_obj.server = MockONTAPConnection(kind='net_route', data=data) + return net_route_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({}) + net_route_module() + print('Info: %s' % exc.value.args[0]['msg']) + + def test_get_nonexistent_net_route(self): + ''' Test if get_net_route returns None for non-existent net_route ''' + set_module_args(self.mock_args()) + result = self.get_net_route_mock_object().get_net_route() + assert result is None + + def test_get_existing_job(self): + ''' Test if get_net_route returns details for existing net_route ''' + set_module_args(self.mock_args()) + result = self.get_net_route_mock_object('net_route').get_net_route() + assert result['destination'] == self.mock_net_route['destination'] + assert result['gateway'] == self.mock_net_route['gateway'] + + def test_create_error_missing_param(self): + ''' Test if create throws an error if destination is not specified''' + data = self.mock_args() + del data['destination'] + set_module_args(data) + with pytest.raises(AnsibleFailJson) as exc: + self.get_net_route_mock_object('net_route').create_net_route() + msg = 'missing required arguments: destination' + assert exc.value.args[0]['msg'] == msg + + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_net_routes.NetAppOntapNetRoutes.create_net_route') + def test_successful_create(self, create_net_route): + ''' Test successful create ''' + data = self.mock_args() + set_module_args(data) + with pytest.raises(AnsibleExitJson) as exc: + self.get_net_route_mock_object().apply() + assert exc.value.args[0]['changed'] + create_net_route.assert_called_with() + + def test_create_idempotency(self): + ''' Test create idempotency ''' + set_module_args(self.mock_args()) + obj = self.get_net_route_mock_object('net_route') + with pytest.raises(AnsibleExitJson) as exc: + obj.apply() + assert not exc.value.args[0]['changed'] + + def test_successful_delete(self): + ''' Test successful delete ''' + data = self.mock_args() + data['state'] = 'absent' + set_module_args(data) + with pytest.raises(AnsibleExitJson) as exc: + self.get_net_route_mock_object('net_route').apply() + assert exc.value.args[0]['changed'] + + def test_delete_idempotency(self): + ''' Test delete idempotency ''' + data = self.mock_args() + data['state'] = 'absent' + set_module_args(data) + with pytest.raises(AnsibleExitJson) as exc: + self.get_net_route_mock_object().apply() + assert not exc.value.args[0]['changed'] + + def test_successful_modify_metric(self): + ''' Test successful modify metric ''' + data = self.mock_args() + del data['metric'] + data['from_metric'] = 70 + data['metric'] = 40 + set_module_args(data) + with pytest.raises(AnsibleExitJson) as exc: + self.get_net_route_mock_object('net_route').apply() + assert exc.value.args[0]['changed'] + + def test_modify_metric_idempotency(self): + ''' Test modify metric idempotency''' + data = self.mock_args() + data['metric'] = 70 + set_module_args(data) + with pytest.raises(AnsibleExitJson) as exc: + self.get_net_route_mock_object('net_route').apply() + assert not exc.value.args[0]['changed'] + + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_net_routes.NetAppOntapNetRoutes.get_net_route') + def test_successful_modify_gateway(self, get_net_route): + ''' Test successful modify gateway ''' + data = self.mock_args() + del data['gateway'] + data['from_gateway'] = '10.193.72.1' + data['gateway'] = '10.193.0.1' + set_module_args(data) + current = { + 'destination': '176.0.0.0/24', + 'gateway': '10.193.72.1', + 'metric': 70, + 'vserver': 'test_server' + } + get_net_route.side_effect = [ + None, + current + ] + with pytest.raises(AnsibleExitJson) as exc: + self.get_net_route_mock_object().apply() + assert exc.value.args[0]['changed'] + + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_net_routes.NetAppOntapNetRoutes.get_net_route') + def test__modify_gateway_idempotency(self, get_net_route): + ''' Test modify gateway idempotency ''' + data = self.mock_args() + del data['gateway'] + data['from_gateway'] = '10.193.72.1' + data['gateway'] = '10.193.0.1' + set_module_args(data) + current = { + 'destination': '178.0.0.1/24', + 'gateway': '10.193.72.1', + 'metric': 70, + 'vserver': 'test_server' + } + get_net_route.side_effect = [ + current, + None + ] + with pytest.raises(AnsibleExitJson) as exc: + self.get_net_route_mock_object('net_route', current).apply() + assert not exc.value.args[0]['changed'] + + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_net_routes.NetAppOntapNetRoutes.get_net_route') + def test_successful_modify_destination(self, get_net_route): + ''' Test successful modify destination ''' + data = self.mock_args() + del data['destination'] + data['from_destination'] = '176.0.0.0/24' + data['destination'] = '178.0.0.1/24' + set_module_args(data) + current = { + 'destination': '176.0.0.0/24', + 'gateway': '10.193.72.1', + 'metric': 70, + 'vserver': 'test_server' + } + get_net_route.side_effect = [ + None, + current + ] + with pytest.raises(AnsibleExitJson) as exc: + self.get_net_route_mock_object().apply() + assert exc.value.args[0]['changed'] + + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_net_routes.NetAppOntapNetRoutes.get_net_route') + def test__modify_destination_idempotency(self, get_net_route): + ''' Test modify destination idempotency''' + data = self.mock_args() + del data['destination'] + data['from_destination'] = '176.0.0.0/24' + data['destination'] = '178.0.0.1/24' + set_module_args(data) + current = { + 'destination': '178.0.0.1/24', + 'gateway': '10.193.72.1', + 'metric': 70, + 'vserver': 'test_server' + } + get_net_route.side_effect = [ + current, + None + ] + with pytest.raises(AnsibleExitJson) as exc: + self.get_net_route_mock_object('net_route', current).apply() + assert not exc.value.args[0]['changed'] + + @patch('ansible_collections.netapp.ontap.plugins.module_utils.netapp.OntapRestAPI.send_request') + def test_rest_error(self, mock_request): + data = self.mock_args(rest=True) + set_module_args(data) + mock_request.side_effect = [ + SRR['is_rest'], + SRR['generic_error'], + SRR['end_of_sequence'] + ] + with pytest.raises(AnsibleFailJson) as exc: + self.get_net_route_mock_object(cx_type='rest').apply() + assert exc.value.args[0]['msg'] == SRR['generic_error'][2] + + @patch('ansible_collections.netapp.ontap.plugins.module_utils.netapp.OntapRestAPI.send_request') + def test_rest_successfully_create(self, mock_request): + data = self.mock_args(rest=True) + data['state'] = 'present' + set_module_args(data) + mock_request.side_effect = [ + SRR['is_rest'], + SRR['empty_good'], # get + SRR['empty_good'], # post + SRR['end_of_sequence'] + ] + with pytest.raises(AnsibleExitJson) as exc: + self.get_net_route_mock_object(cx_type='rest').apply() + assert exc.value.args[0]['changed'] + + @patch('ansible_collections.netapp.ontap.plugins.module_utils.netapp.OntapRestAPI.send_request') + def test_rest_idempotent_create_dns(self, mock_request): + data = self.mock_args(rest=True) + data['state'] = 'present' + set_module_args(data) + mock_request.side_effect = [ + SRR['is_rest'], + SRR['net_routes_record'], # get + SRR['end_of_sequence'] + ] + with pytest.raises(AnsibleExitJson) as exc: + self.get_net_route_mock_object(cx_type='rest').apply() + assert not exc.value.args[0]['changed'] + + @patch('ansible_collections.netapp.ontap.plugins.module_utils.netapp.OntapRestAPI.send_request') + def test_rest_successfully_destroy(self, mock_request): + data = self.mock_args(rest=True) + data['state'] = 'absent' + set_module_args(data) + mock_request.side_effect = [ + SRR['is_rest'], + SRR['net_routes_record'], # get + SRR['empty_good'], # delete + SRR['end_of_sequence'] + ] + with pytest.raises(AnsibleExitJson) as exc: + self.get_net_route_mock_object(cx_type='rest').apply() + assert exc.value.args[0]['changed'] + + @patch('ansible_collections.netapp.ontap.plugins.module_utils.netapp.OntapRestAPI.send_request') + def test_rest_idempotently_destroy(self, mock_request): + data = self.mock_args(rest=True) + data['state'] = 'absent' + set_module_args(data) + mock_request.side_effect = [ + SRR['is_rest'], + SRR['empty_good'], # get + SRR['end_of_sequence'] + ] + with pytest.raises(AnsibleExitJson) as exc: + self.get_net_route_mock_object(cx_type='rest').apply() + assert not exc.value.args[0]['changed'] + + @patch('ansible_collections.netapp.ontap.plugins.module_utils.netapp.OntapRestAPI.send_request') + def test_rest_successfully_modify(self, mock_request): + data = self.mock_args(modify=True) + data['state'] = 'present' + set_module_args(data) + mock_request.side_effect = [ + SRR['is_rest'], + SRR['empty_good'], # get + SRR['net_routes_record'], # get + SRR['net_routes_record'], # get + SRR['empty_good'], # get + SRR['empty_good'], # delete + SRR['empty_good'], # post + SRR['end_of_sequence'] + ] + with pytest.raises(AnsibleExitJson) as exc: + self.get_net_route_mock_object(cx_type='rest').apply() + assert exc.value.args[0]['changed'] + + @patch('ansible_collections.netapp.ontap.plugins.module_utils.netapp.OntapRestAPI.send_request') + def test_rest_idempotently_modify(self, mock_request): + data = self.mock_args(modify=True) + data['state'] = 'present' + set_module_args(data) + mock_request.side_effect = [ + SRR['is_rest'], + SRR['modified_record'], # get + SRR['modified_record'], # get + SRR['end_of_sequence'] + ] + with pytest.raises(AnsibleExitJson) as exc: + self.get_net_route_mock_object(cx_type='rest').apply() + assert not exc.value.args[0]['changed'] diff --git a/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_net_subnet.py b/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_net_subnet.py new file mode 100644 index 00000000..80d27e1b --- /dev/null +++ b/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_net_subnet.py @@ -0,0 +1,265 @@ +# (c) 2018, NetApp, Inc +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +''' unit test template for ONTAP Ansible module ''' + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type +import json +import pytest + +from ansible_collections.netapp.ontap.tests.unit.compat import unittest +from ansible_collections.netapp.ontap.tests.unit.compat.mock import patch +from ansible.module_utils import basic +from ansible.module_utils._text import to_bytes +import ansible_collections.netapp.ontap.plugins.module_utils.netapp as netapp_utils + +from ansible_collections.netapp.ontap.plugins.modules.na_ontap_net_subnet \ + import NetAppOntapSubnet as my_module # module under test + +if not netapp_utils.has_netapp_lib(): + pytestmark = pytest.mark.skip('skipping as missing required netapp_lib') + + +def set_module_args(args): + """prepare arguments so that they will be picked up during module creation""" + args = json.dumps({'ANSIBLE_MODULE_ARGS': args}) + basic._ANSIBLE_ARGS = to_bytes(args) # pylint: disable=protected-access + + +class AnsibleExitJson(Exception): + """Exception class to be raised by module.exit_json and caught by the test case""" + pass + + +class AnsibleFailJson(Exception): + """Exception class to be raised by module.fail_json and caught by the test case""" + pass + + +def exit_json(*args, **kwargs): # pylint: disable=unused-argument + """function to patch over exit_json; package return data into an exception""" + if 'changed' not in kwargs: + kwargs['changed'] = False + raise AnsibleExitJson(kwargs) + + +def fail_json(*args, **kwargs): # pylint: disable=unused-argument + """function to patch over fail_json; package return data into an exception""" + kwargs['failed'] = True + raise AnsibleFailJson(kwargs) + + +class MockONTAPConnection(object): + ''' mock server connection to ONTAP host ''' + + def __init__(self, kind=None, data=None): + ''' save arguments ''' + self.type = kind + self.params = data + self.xml_in = None + self.xml_out = None + + def invoke_successfully(self, xml, enable_tunneling): # pylint: disable=unused-argument + ''' mock invoke_successfully returning xml data ''' + self.xml_in = xml + if xml.get_child_by_name('query') is not None and \ + xml.get_child_by_name('query').get_child_by_name('vserver-info') is not None: + # assume this a a cserver request + xml = self.build_cserver_info() + elif self.type == 'subnet': + if xml.get_child_by_name('query'): + name_obj = xml.get_child_by_name('query').get_child_by_name('net-subnet-info').get_child_by_name('subnet-name') + xml_name = name_obj.get_content() + if xml_name == self.params.get('name'): + xml = self.build_subnet_info(self.params) + elif self.type == 'subnet_fail': + raise netapp_utils.zapi.NaApiError(code='TEST', message="This exception is from the unit test") + self.xml_out = xml + return xml + + @staticmethod + def build_cserver_info(): + ''' build xml data for vserver-info ''' + xml = netapp_utils.zapi.NaElement('xml') + attributes = { + 'num-records': 1, + 'attributes-list': { + 'vserver-info': { + 'vserver-name': 'cserver', + } + } + } + xml.translate_struct(attributes) + return xml + + @staticmethod + def build_subnet_info(data): + ''' build xml data for subnet-info ''' + xml = netapp_utils.zapi.NaElement('xml') + ip_ranges = [] + for elem in data['ip_ranges']: + ip_ranges.append({'ip-range': elem}) + attributes = { + 'num-records': 1, + 'attributes-list': { + 'net-subnet-info': { + 'broadcast-domain': data['broadcast_domain'], + 'gateway': data['gateway'], + 'ip-ranges': ip_ranges, + 'ipspace': data['ipspace'], + 'subnet': data['subnet'], + 'subnet-name': data['name'], + } + } + } + xml.translate_struct(attributes) + return xml + + +class TestMyModule(unittest.TestCase): + ''' a group of related Unit Tests ''' + + def setUp(self): + self.mock_module_helper = patch.multiple(basic.AnsibleModule, + exit_json=exit_json, + fail_json=fail_json) + self.mock_module_helper.start() + self.addCleanup(self.mock_module_helper.stop) + self.server = MockONTAPConnection() + + def set_default_args(self): + return dict({ + 'name': 'test_subnet', + 'hostname': 'hostname', + 'username': 'username', + 'password': 'password', + 'broadcast_domain': 'Default', + 'gateway': '10.0.0.1', + 'ipspace': 'Default', + 'subnet': '10.0.0.0/24', + 'ip_ranges': ['10.0.0.10-10.0.0.20', '10.0.0.30'] + }) + + def test_module_fail_when_required_args_missing(self): + ''' required arguments are reported as errors ''' + with pytest.raises(AnsibleFailJson) as exc: + set_module_args({}) + my_module() + print('Info: %s' % exc.value.args[0]['msg']) + + def test_ensure_get_called(self): + ''' test get_subnet for non-existent subnet''' + set_module_args(self.set_default_args()) + my_obj = my_module() + my_obj.server = self.server + assert my_obj.get_subnet() is None + + def test_ensure_get_called_existing(self): + ''' test get_subnet for existing subnet''' + data = self.set_default_args() + set_module_args(data) + my_obj = my_module() + my_obj.server = MockONTAPConnection(kind='subnet', data=data) + assert my_obj.get_subnet() is not None + + @patch('ansible_collections.netapp.ontap.plugins.module_utils.netapp.ems_log_event') + def test_fail_broadcast_domain_modify(self, mock_ems_log): + ''' test that boradcast_domain is not alterable ''' + data = self.set_default_args() + data.update({'broadcast_domain': 'Test'}) + set_module_args(data) + my_obj = my_module() + my_obj.server = MockONTAPConnection(kind='subnet', data=self.set_default_args()) + with pytest.raises(AnsibleFailJson) as exc: + my_obj.apply() + assert 'cannot modify broadcast_domain parameter' in exc.value.args[0]['msg'] + + @patch('ansible_collections.netapp.ontap.plugins.module_utils.netapp.ems_log_event') + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_net_subnet.NetAppOntapSubnet.create_subnet') + def test_successful_create(self, create_subnet, mock_ems_log): + ''' creating subnet and testing idempotency ''' + print("Create:") + data = self.set_default_args() + set_module_args(data) + my_obj = my_module() + my_obj.server = self.server + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + assert exc.value.args[0]['changed'] + create_subnet.assert_called_with() + + # to reset na_helper from remembering the previous 'changed' value + print("reset:") + data = self.set_default_args() + set_module_args(data) + my_obj = my_module() + my_obj.server = MockONTAPConnection(kind='subnet', data=data) + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + assert not exc.value.args[0]['changed'] + + @patch('ansible_collections.netapp.ontap.plugins.module_utils.netapp.ems_log_event') + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_net_subnet.NetAppOntapSubnet.rename_subnet') + def test_successful_rename(self, rename_subnet, mock_ems_log): + ''' renaming subnet ''' + data = self.set_default_args() + data.update({'from_name': data['name'], 'name': 'new_test_subnet'}) + set_module_args(data) + my_obj = my_module() + my_obj.server = MockONTAPConnection(kind='subnet', data=self.set_default_args()) + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + assert exc.value.args[0]['changed'] + + @patch('ansible_collections.netapp.ontap.plugins.module_utils.netapp.ems_log_event') + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_net_subnet.NetAppOntapSubnet.delete_subnet') + def test_successful_delete(self, delete_subnet, mock_ems_log): + ''' deleting subnet and testing idempotency ''' + data = self.set_default_args() + data['state'] = 'absent' + set_module_args(data) + my_obj = my_module() + my_obj.server = MockONTAPConnection(kind='subnet', data=data) + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + assert exc.value.args[0]['changed'] + delete_subnet.assert_called_with() + + # to reset na_helper from remembering the previous 'changed' value + my_obj = my_module() + my_obj.server = self.server + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + assert not exc.value.args[0]['changed'] + + @patch('ansible_collections.netapp.ontap.plugins.module_utils.netapp.ems_log_event') + def test_successful_modify(self, mock_ems_log): + ''' modifying subnet and testing idempotency ''' + data = self.set_default_args() + data.update({'ip_ranges': ['10.0.0.10-10.0.0.25', '10.0.0.30']}) + set_module_args(data) + my_obj = my_module() + my_obj.server = MockONTAPConnection(kind='subnet', data=self.set_default_args()) + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + assert exc.value.args[0]['changed'] + + @patch('ansible_collections.netapp.ontap.plugins.module_utils.netapp.ems_log_event') + def test_if_all_methods_catch_exception(self, mock_ems_log): + data = self.set_default_args() + set_module_args(data) + my_obj = my_module() + my_obj.server = MockONTAPConnection(kind='subnet_fail', data=data) + with pytest.raises(AnsibleFailJson) as exc: + my_obj.create_subnet() + assert 'Error creating subnet' in exc.value.args[0]['msg'] + with pytest.raises(AnsibleFailJson) as exc: + my_obj.delete_subnet() + assert 'Error deleting subnet' in exc.value.args[0]['msg'] + with pytest.raises(AnsibleFailJson) as exc: + my_obj.modify_subnet() + assert 'Error modifying subnet' in exc.value.args[0]['msg'] + with pytest.raises(AnsibleFailJson) as exc: + my_obj.rename_subnet() + assert 'Error renaming subnet' in exc.value.args[0]['msg'] diff --git a/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_nfs.py b/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_nfs.py new file mode 100644 index 00000000..c6cd5ed8 --- /dev/null +++ b/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_nfs.py @@ -0,0 +1,309 @@ +# (c) 2018, NetApp, Inc +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +''' unit test template for ONTAP Ansible module ''' + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type +import json +import pytest + +from ansible_collections.netapp.ontap.tests.unit.compat import unittest +from ansible_collections.netapp.ontap.tests.unit.compat.mock import patch, Mock +from ansible.module_utils import basic +from ansible.module_utils._text import to_bytes +import ansible_collections.netapp.ontap.plugins.module_utils.netapp as netapp_utils + +from ansible_collections.netapp.ontap.plugins.modules.na_ontap_nfs \ + import NetAppONTAPNFS as nfs_module # module under test + +if not netapp_utils.has_netapp_lib(): + pytestmark = pytest.mark.skip('skipping as missing required netapp_lib') + + +def set_module_args(args): + """prepare arguments so that they will be picked up during module creation""" + args = json.dumps({'ANSIBLE_MODULE_ARGS': args}) + basic._ANSIBLE_ARGS = to_bytes(args) # pylint: disable=protected-access + + +class AnsibleExitJson(Exception): + """Exception class to be raised by module.exit_json and caught by the test case""" + pass + + +class AnsibleFailJson(Exception): + """Exception class to be raised by module.fail_json and caught by the test case""" + pass + + +def exit_json(*args, **kwargs): # pylint: disable=unused-argument + """function to patch over exit_json; package return data into an exception""" + if 'changed' not in kwargs: + kwargs['changed'] = False + raise AnsibleExitJson(kwargs) + + +def fail_json(*args, **kwargs): # pylint: disable=unused-argument + """function to patch over fail_json; package return data into an exception""" + kwargs['failed'] = True + raise AnsibleFailJson(kwargs) + + +class MockONTAPConnection(object): + ''' mock server connection to ONTAP host ''' + + def __init__(self, kind=None, data=None, job_error=None): + ''' save arguments ''' + self.kind = kind + self.params = data + self.xml_in = None + self.xml_out = None + + def invoke_successfully(self, xml, enable_tunneling): # pylint: disable=unused-argument + ''' mock invoke_successfully returning xml data ''' + self.xml_in = xml + if self.kind == 'nfs': + xml = self.build_nfs_info(self.params) + self.xml_out = xml + if self.kind == 'nfs_status': + xml = self.build_nfs_status_info(self.params) + return xml + + @staticmethod + def build_nfs_info(nfs_details): + ''' build xml data for volume-attributes ''' + xml = netapp_utils.zapi.NaElement('xml') + attributes = { + "attributes-list": { + "nfs-info": { + "auth-sys-extended-groups": "false", + "cached-cred-harvest-timeout": "86400000", + "cached-cred-negative-ttl": "7200000", + "cached-cred-positive-ttl": "86400000", + "cached-transient-err-ttl": "30000", + "chown-mode": "use_export_policy", + "enable-ejukebox": "true", + "extended-groups-limit": "32", + "file-session-io-grouping-count": "5000", + "file-session-io-grouping-duration": "120", + "ignore-nt-acl-for-root": "false", + "is-checksum-enabled-for-replay-cache": "true", + "is-mount-rootonly-enabled": "true", + "is-netgroup-dns-domain-search": "true", + "is-nfs-access-enabled": "false", + "is-nfs-rootonly-enabled": "false", + "is-nfsv2-enabled": "false", + "is-nfsv3-64bit-identifiers-enabled": "false", + "is-nfsv3-connection-drop-enabled": "true", + "is-nfsv3-enabled": "true", + "is-nfsv3-fsid-change-enabled": "true", + "is-nfsv4-fsid-change-enabled": "true", + "is-nfsv4-numeric-ids-enabled": "true", + "is-nfsv40-acl-enabled": "false", + "is-nfsv40-enabled": "true", + "is-nfsv40-migration-enabled": "false", + "is-nfsv40-read-delegation-enabled": "false", + "is-nfsv40-referrals-enabled": "false", + "is-nfsv40-req-open-confirm-enabled": "false", + "is-nfsv40-write-delegation-enabled": "false", + "is-nfsv41-acl-enabled": "false", + "is-nfsv41-acl-preserve-enabled": "true", + "is-nfsv41-enabled": "true", + "is-nfsv41-migration-enabled": "false", + "is-nfsv41-pnfs-enabled": "true", + "is-nfsv41-read-delegation-enabled": "false", + "is-nfsv41-referrals-enabled": "false", + "is-nfsv41-state-protection-enabled": "true", + "is-nfsv41-write-delegation-enabled": "false", + "is-qtree-export-enabled": "false", + "is-rquota-enabled": "false", + "is-tcp-enabled": "false", + "is-udp-enabled": "false", + "is-v3-ms-dos-client-enabled": "false", + "is-validate-qtree-export-enabled": "true", + "is-vstorage-enabled": "false", + "map-unknown-uid-to-default-windows-user": "true", + "mountd-port": "635", + "name-service-lookup-protocol": "udp", + "netgroup-trust-any-ns-switch-no-match": "false", + "nfsv4-acl-max-aces": "400", + "nfsv4-grace-seconds": "45", + "nfsv4-id-domain": "defaultv4iddomain.com", + "nfsv4-lease-seconds": "30", + "nfsv41-implementation-id-domain": "netapp.com", + "nfsv41-implementation-id-name": "NetApp Release Kalyaniblack__9.4.0", + "nfsv41-implementation-id-time": "1541070767", + "nfsv4x-session-num-slots": "180", + "nfsv4x-session-slot-reply-cache-size": "640", + "nlm-port": "4045", + "nsm-port": "4046", + "ntacl-display-permissive-perms": "false", + "ntfs-unix-security-ops": "use_export_policy", + "permitted-enc-types": { + "string": ["des", "des3", "aes_128", "aes_256"] + }, + "rpcsec-ctx-high": "0", + "rpcsec-ctx-idle": "0", + "rquotad-port": "4049", + "showmount": "true", + "showmount-timestamp": "1548372452", + "skip-root-owner-write-perm-check": "false", + "tcp-max-xfer-size": "1048576", + "udp-max-xfer-size": "32768", + "v3-search-unconverted-filename": "false", + "v4-inherited-acl-preserve": "false", + "vserver": "ansible" + } + }, + "num-records": "1" + } + xml.translate_struct(attributes) + return xml + + @staticmethod + def build_nfs_status_info(nfs_status_details): + ''' build xml data for volume-attributes ''' + xml = netapp_utils.zapi.NaElement('xml') + attributes = { + 'is-enabled': "true" + } + xml.translate_struct(attributes) + return xml + + +class TestMyModule(unittest.TestCase): + ''' a group of related Unit Tests ''' + + def setUp(self): + self.mock_module_helper = patch.multiple(basic.AnsibleModule, + exit_json=exit_json, + fail_json=fail_json) + self.mock_module_helper.start() + self.addCleanup(self.mock_module_helper.stop) + self.mock_nfs_group = { + 'vserver': 'nfs_vserver', + } + + def mock_args(self): + return { + 'vserver': self.mock_nfs_group['vserver'], + 'hostname': 'test', + 'username': 'test_user', + 'password': 'test_pass!', + 'https': 'False' + } + + def get_nfs_mock_object(self, kind=None): + """ + Helper method to return an na_ontap_volume object + :param kind: passes this param to MockONTAPConnection() + :return: na_ontap_volume object + """ + nfsy_obj = nfs_module() + nfsy_obj.asup_log_for_cserver = Mock(return_value=None) + nfsy_obj.cluster = Mock() + nfsy_obj.cluster.invoke_successfully = Mock() + if kind is None: + nfsy_obj.server = MockONTAPConnection() + else: + nfsy_obj.server = MockONTAPConnection(kind=kind, data=self.mock_nfs_group) + return nfsy_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({}) + nfs_module() + print('Info: %s' % exc.value.args[0]['msg']) + + def test_get_nonexistent_nfs(self): + ''' Test if get_nfs_service returns None for non-existent nfs ''' + set_module_args(self.mock_args()) + result = self.get_nfs_mock_object().get_nfs_service() + assert result is None + + def test_get_existing_nfs(self): + ''' Test if get_policy_group returns details for existing nfs ''' + set_module_args(self.mock_args()) + result = self.get_nfs_mock_object('nfs').get_nfs_service() + assert result['is_nfsv3_enabled'] + + def test_get_nonexistent_nfs_status(self): + ''' Test if get__nfs_status returns None for non-existent nfs ''' + set_module_args(self.mock_args()) + result = self.get_nfs_mock_object().get_nfs_status() + assert result is None + + def test_get_existing_nfs_status(self): + ''' Test if get__nfs_status returns details for nfs ''' + set_module_args(self.mock_args()) + result = self.get_nfs_mock_object('nfs_status').get_nfs_status() + assert result + + def test_modify_nfs(self): + ''' Test if modify_nfs runs for existing nfs ''' + data = self.mock_args() + data['nfsv3'] = 'enabled' + data['nfsv3_fsid_change'] = 'enabled' + data['nfsv4'] = 'enabled' + data['nfsv41'] = 'enabled' + data['vstorage_state'] = 'enabled' + data['tcp'] = 'enabled' + data['udp'] = 'enabled' + data['nfsv4_id_domain'] = 'nfsv4_id_domain' + data['nfsv40_acl'] = 'enabled' + data['nfsv40_read_delegation'] = 'enabled' + data['nfsv40_write_delegation'] = 'enabled' + data['nfsv41_acl'] = 'enabled' + data['nfsv41_read_delegation'] = 'enabled' + data['nfsv41_write_delegation'] = 'enabled' + data['showmount'] = 'enabled' + data['tcp_max_xfer_size'] = '1048576' + set_module_args(data) + self.get_nfs_mock_object('nfs_status').modify_nfs() + + def test_successfully_modify_nfs(self): + ''' Test modify nfs successful for modifying tcp max xfer size. ''' + data = self.mock_args() + data['tcp_max_xfer_size'] = '8192' + set_module_args(data) + with pytest.raises(AnsibleExitJson) as exc: + self.get_nfs_mock_object('nfs').apply() + assert exc.value.args[0]['changed'] + + def test_modify_nfs_idempotency(self): + ''' Test modify nfs idempotency ''' + data = self.mock_args() + data['tcp_max_xfer_size'] = '1048576' + set_module_args(data) + with pytest.raises(AnsibleExitJson) as exc: + self.get_nfs_mock_object('nfs').apply() + assert not exc.value.args[0]['changed'] + + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_nfs.NetAppONTAPNFS.delete_nfs') + def test_successfully_delete_nfs(self, delete_nfs): + ''' Test successfully delete nfs ''' + data = self.mock_args() + data['state'] = 'absent' + set_module_args(data) + obj = self.get_nfs_mock_object('nfs') + with pytest.raises(AnsibleExitJson) as exc: + obj.apply() + assert exc.value.args[0]['changed'] + delete_nfs.assert_called_with() + + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_nfs.NetAppONTAPNFS.get_nfs_service') + def test_successfully_enable_nfs(self, get_nfs_service): + ''' Test successfully enable nfs on non-existent nfs ''' + data = self.mock_args() + data['state'] = 'present' + set_module_args(data) + get_nfs_service.side_effect = [ + None, + {} + ] + obj = self.get_nfs_mock_object('nfs') + with pytest.raises(AnsibleExitJson) as exc: + obj.apply() + assert exc.value.args[0]['changed'] diff --git a/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_ntfs_dacl.py b/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_ntfs_dacl.py new file mode 100644 index 00000000..405f1e43 --- /dev/null +++ b/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_ntfs_dacl.py @@ -0,0 +1,268 @@ +# (c) 2020, NetApp, Inc +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +''' unit tests for Ansible module: na_ontap_ntfs_dacl''' + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type +import json +import pytest + +from ansible_collections.netapp.ontap.tests.unit.compat import unittest +from ansible_collections.netapp.ontap.tests.unit.compat.mock import patch, Mock +from ansible.module_utils import basic +from ansible.module_utils._text import to_bytes +import ansible_collections.netapp.ontap.plugins.module_utils.netapp as netapp_utils + +from ansible_collections.netapp.ontap.plugins.modules.na_ontap_ntfs_dacl \ + import NetAppOntapNtfsDacl as dacl_module # module under test + +if not netapp_utils.has_netapp_lib(): + pytestmark = pytest.mark.skip('skipping as missing required netapp_lib') +HAS_NETAPP_ZAPI_MSG = "pip install netapp_lib is required" + + +def set_module_args(args): + """prepare arguments so that they will be picked up during module creation""" + args = json.dumps({'ANSIBLE_MODULE_ARGS': args}) + basic._ANSIBLE_ARGS = to_bytes(args) # pylint: disable=protected-access + + +class AnsibleExitJson(Exception): + """Exception class to be raised by module.exit_json and caught by the test case""" + pass + + +class AnsibleFailJson(Exception): + """Exception class to be raised by module.fail_json and caught by the test case""" + pass + + +def exit_json(*args, **kwargs): # pylint: disable=unused-argument + """function to patch over exit_json; package return data into an exception""" + if 'changed' not in kwargs: + kwargs['changed'] = False + raise AnsibleExitJson(kwargs) + + +def fail_json(*args, **kwargs): # pylint: disable=unused-argument + """function to patch over fail_json; package return data into an exception""" + kwargs['failed'] = True + raise AnsibleFailJson(kwargs) + + +class MockONTAPConnection(object): + ''' mock server connection to ONTAP host ''' + + def __init__(self, kind=None, data=None): + ''' save arguments ''' + self.kind = kind + self.params = data + self.xml_in = None + self.xml_out = None + + def invoke_successfully(self, xml, enable_tunneling): # pylint: disable=unused-argument + ''' mock invoke_successfully returning xml data ''' + self.xml_in = xml + request = xml.to_string().decode('utf-8') + if self.kind == 'error': + raise netapp_utils.zapi.NaApiError('test', 'expect error') + elif request.startswith("<ems-autosupport-log>"): + xml = None # or something that may the logger happy, and you don't need @patch anymore + # or + # xml = build_ems_log_response() + elif request.startswith("<file-directory-security-ntfs-dacl-get-iter>"): + if self.kind == 'create': + xml = self.build_dacl_info() + else: + xml = self.build_dacl_info(self.params) + elif request.startswith("<file-directory-security-ntfs-dacl-modify>"): + xml = self.build_dacl_info(self.params) + self.xml_out = xml + return xml + + @staticmethod + def build_dacl_info(data=None): + xml = netapp_utils.zapi.NaElement('xml') + vserver = 'vserver' + attributes = {'num-records': '0', + 'attributes-list': {'file-directory-security-ntfs-dacl': {'vserver': vserver}}} + + if data is not None: + attributes['num-records'] = '1' + if data.get('access_type'): + attributes['attributes-list']['file-directory-security-ntfs-dacl']['access-type'] = data['access_type'] + if data.get('account'): + attributes['attributes-list']['file-directory-security-ntfs-dacl']['account'] = data['account'] + if data.get('rights'): + attributes['attributes-list']['file-directory-security-ntfs-dacl']['rights'] = data['rights'] + if data.get('advanced_rights'): + attributes['attributes-list']['file-directory-security-ntfs-dacl']['advanced-rights'] = data['advanced_rights'] + if data.get('apply_to'): + tmp = [] + for target in data['apply_to']: + tmp.append({'inheritance-level': target}) + attributes['attributes-list']['file-directory-security-ntfs-dacl']['apply-to'] = tmp + if data.get('security_descriptor'): + attributes['attributes-list']['file-directory-security-ntfs-dacl']['ntfs-sd'] = data['security_descriptor'] + xml.translate_struct(attributes) + return xml + + +class TestMyModule(unittest.TestCase): + ''' Unit tests for na_ontap_ntfs_dacl ''' + + def setUp(self): + self.mock_module_helper = patch.multiple(basic.AnsibleModule, + exit_json=exit_json, + fail_json=fail_json) + self.mock_module_helper.start() + self.addCleanup(self.mock_module_helper.stop) + + def mock_args(self): + return { + 'vserver': 'vserver', + 'hostname': 'test', + 'username': 'test_user', + 'password': 'test_pass!' + } + + def get_dacl_mock_object(self, type='zapi', kind=None, status=None): + dacl_obj = dacl_module() + dacl_obj.autosupport_log = Mock(return_value=None) + if type == 'zapi': + if kind is None: + dacl_obj.server = MockONTAPConnection() + else: + dacl_obj.server = MockONTAPConnection(kind=kind, data=status) + return dacl_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({}) + dacl_module() + print('Info: %s' % exc.value.args[0]['msg']) + + def test_get_dacl_error(self): + data = self.mock_args() + data['access_type'] = 'allow' + data['account'] = 'acc_test' + data['rights'] = 'full_control' + data['security_descriptor'] = 'sd_test' + data['apply_to'] = 'this_folder,files' + set_module_args(data) + with pytest.raises(AnsibleFailJson) as exc: + self.get_dacl_mock_object('zapi', 'error', data).apply() + msg = 'Error fetching allow DACL for account acc_test for security descriptor sd_test: NetApp API failed. Reason - test:expect error' + assert exc.value.args[0]['msg'] == msg + + def test_successfully_create_dacl(self): + data = self.mock_args() + data['access_type'] = 'allow' + data['account'] = 'acc_test' + data['rights'] = 'full_control' + data['security_descriptor'] = 'sd_test' + data['apply_to'] = 'this_folder,files' + set_module_args(data) + with pytest.raises(AnsibleExitJson) as exc: + self.get_dacl_mock_object('zapi', 'create', data).apply() + assert exc.value.args[0]['changed'] + + def test_create_dacl_idempotency(self): + data = self.mock_args() + data['access_type'] = 'allow' + data['account'] = 'acc_test' + data['rights'] = 'full_control' + data['security_descriptor'] = 'sd_test' + data['apply_to'] = ['this_folder', 'files'] + set_module_args(data) + with pytest.raises(AnsibleExitJson) as exc: + self.get_dacl_mock_object('zapi', 'create_idempotency', data).apply() + assert not exc.value.args[0]['changed'] + + def test_successfully_modify_dacl(self): + data = self.mock_args() + data['access_type'] = 'allow' + data['account'] = 'acc_test' + data['rights'] = 'full_control' + data['security_descriptor'] = 'sd_test' + data['apply_to'] = ['this_folder', 'files'] + set_module_args(data) + data['advanced_rights'] = 'read_data,write_data' + with pytest.raises(AnsibleExitJson) as exc: + self.get_dacl_mock_object('zapi', 'create', data).apply() + assert exc.value.args[0]['changed'] + + def test_modify_dacl_idempotency(self): + data = self.mock_args() + data['access_type'] = 'allow' + data['account'] = 'acc_test' + data['rights'] = 'full_control' + data['security_descriptor'] = 'sd_test' + data['apply_to'] = ['this_folder', 'files'] + set_module_args(data) + data['rights'] = 'full_control' + with pytest.raises(AnsibleExitJson) as exc: + self.get_dacl_mock_object('zapi', 'modify_idempotency', data).apply() + assert not exc.value.args[0]['changed'] + + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_ntfs_dacl.NetAppOntapNtfsDacl.get_dacl') + def test_modify_error(self, get_info): + data = self.mock_args() + data['access_type'] = 'allow' + data['account'] = 'acc_test' + data['rights'] = 'full_control' + data['security_descriptor'] = 'sd_test' + set_module_args(data) + get_info.side_effect = [ + { + 'access_type': 'allow', + 'account': 'acc_test', + 'security_descriptor': 'sd_test', + 'rights': 'modify', + 'apply_to': ['this_folder', 'files'] + } + ] + with pytest.raises(AnsibleFailJson) as exc: + self.get_dacl_mock_object('zapi', 'error', data).apply() + msg = 'Error modifying allow DACL for account acc_test for security descriptor sd_test: NetApp API failed. Reason - test:expect error' + assert exc.value.args[0]['msg'] == msg + + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_ntfs_dacl.NetAppOntapNtfsDacl.get_dacl') + def test_create_error(self, get_info): + data = self.mock_args() + data['access_type'] = 'allow' + data['account'] = 'acc_test' + data['rights'] = 'full_control' + data['security_descriptor'] = 'sd_test' + set_module_args(data) + get_info.side_effect = [ + None + ] + with pytest.raises(AnsibleFailJson) as exc: + self.get_dacl_mock_object('zapi', 'error', data).apply() + msg = 'Error adding allow DACL for account acc_test for security descriptor sd_test: NetApp API failed. Reason - test:expect error' + assert exc.value.args[0]['msg'] == msg + + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_ntfs_dacl.NetAppOntapNtfsDacl.get_dacl') + def test_delete_error(self, get_info): + data = self.mock_args() + data['access_type'] = 'allow' + data['account'] = 'acc_test' + data['rights'] = 'full_control' + data['security_descriptor'] = 'sd_test' + data['state'] = 'absent' + set_module_args(data) + get_info.side_effect = [ + { + 'access_type': 'allow', + 'account': 'acc_test', + 'security_descriptor': 'sd_test', + 'rights': 'modify' + } + ] + with pytest.raises(AnsibleFailJson) as exc: + self.get_dacl_mock_object('zapi', 'error', data).apply() + msg = 'Error deleting allow DACL for account acc_test for security descriptor sd_test: NetApp API failed. Reason - test:expect error' + assert exc.value.args[0]['msg'] == msg diff --git a/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_ntfs_sd.py b/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_ntfs_sd.py new file mode 100644 index 00000000..f82e3536 --- /dev/null +++ b/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_ntfs_sd.py @@ -0,0 +1,225 @@ +# (c) 2020, NetApp, Inc +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +''' unit tests for Ansible module: na_ontap_ntfs_sd''' + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type +import json +import pytest + +from ansible_collections.netapp.ontap.tests.unit.compat import unittest +from ansible_collections.netapp.ontap.tests.unit.compat.mock import patch, Mock +from ansible.module_utils import basic +from ansible.module_utils._text import to_bytes +import ansible_collections.netapp.ontap.plugins.module_utils.netapp as netapp_utils + +from ansible_collections.netapp.ontap.plugins.modules.na_ontap_ntfs_sd \ + import NetAppOntapNtfsSd as sd_module # module under test + +if not netapp_utils.has_netapp_lib(): + pytestmark = pytest.mark.skip('skipping as missing required netapp_lib') + + +def set_module_args(args): + """prepare arguments so that they will be picked up during module creation""" + args = json.dumps({'ANSIBLE_MODULE_ARGS': args}) + basic._ANSIBLE_ARGS = to_bytes(args) # pylint: disable=protected-access + + +class AnsibleExitJson(Exception): + """Exception class to be raised by module.exit_json and caught by the test case""" + pass + + +class AnsibleFailJson(Exception): + """Exception class to be raised by module.fail_json and caught by the test case""" + pass + + +def exit_json(*args, **kwargs): # pylint: disable=unused-argument + """function to patch over exit_json; package return data into an exception""" + if 'changed' not in kwargs: + kwargs['changed'] = False + raise AnsibleExitJson(kwargs) + + +def fail_json(*args, **kwargs): # pylint: disable=unused-argument + """function to patch over fail_json; package return data into an exception""" + kwargs['failed'] = True + raise AnsibleFailJson(kwargs) + + +class MockONTAPConnection(object): + ''' mock server connection to ONTAP host ''' + + def __init__(self, kind=None, data=None): + ''' save arguments ''' + self.kind = kind + self.params = data + self.xml_in = None + self.xml_out = None + + def invoke_successfully(self, xml, enable_tunneling): # pylint: disable=unused-argument + ''' mock invoke_successfully returning xml data ''' + self.xml_in = xml + request = xml.to_string().decode('utf-8') + if self.kind == 'error': + raise netapp_utils.zapi.NaApiError('test', 'expect error') + elif request.startswith("<ems-autosupport-log>"): + xml = None # or something that may the logger happy, and you don't need @patch anymore + # or + # xml = build_ems_log_response() + elif request.startswith("<file-directory-security-ntfs-get-iter>"): + if self.kind == 'create': + xml = self.build_sd_info() + else: + xml = self.build_sd_info(self.params) + elif request.startswith("<file-directory-security-ntfs-modify>"): + xml = self.build_sd_info(self.params) + self.xml_out = xml + return xml + + @staticmethod + def build_sd_info(data=None): + xml = netapp_utils.zapi.NaElement('xml') + vserver = 'vserver' + attributes = {'num-records': 1, + 'attributes-list': {'file-directory-security-ntfs': {'vserver': vserver}}} + if data is not None: + if data.get('name'): + attributes['attributes-list']['file-directory-security-ntfs']['ntfs-sd'] = data['name'] + if data.get('owner'): + attributes['attributes-list']['file-directory-security-ntfs']['owner'] = data['owner'] + if data.get('group'): + attributes['attributes-list']['file-directory-security-ntfs']['group'] = data['group'] + if data.get('control_flags_raw'): + attributes['attributes-list']['file-directory-security-ntfs']['control-flags-raw'] = str(data['control_flags_raw']) + xml.translate_struct(attributes) + return xml + + +class TestMyModule(unittest.TestCase): + ''' Unit tests for na_ontap_ntfs_sd ''' + + def setUp(self): + self.mock_module_helper = patch.multiple(basic.AnsibleModule, + exit_json=exit_json, + fail_json=fail_json) + self.mock_module_helper.start() + self.addCleanup(self.mock_module_helper.stop) + + def mock_args(self): + return { + 'vserver': 'vserver', + 'hostname': 'test', + 'username': 'test_user', + 'password': 'test_pass!' + } + + def get_sd_mock_object(self, type='zapi', kind=None, status=None): + sd_obj = sd_module() + netapp_utils.ems_log_event = Mock(return_value=None) + if type == 'zapi': + if kind is None: + sd_obj.server = MockONTAPConnection() + else: + sd_obj.server = MockONTAPConnection(kind=kind, data=status) + return sd_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({}) + sd_module() + print('Info: %s' % exc.value.args[0]['msg']) + + def test_successfully_create_sd(self): + data = self.mock_args() + data['name'] = 'sd_test' + data['owner'] = 'user_test' + set_module_args(data) + with pytest.raises(AnsibleExitJson) as exc: + self.get_sd_mock_object('zapi', 'create', data).apply() + assert exc.value.args[0]['changed'] + + def test_create_sd_idempotency(self): + data = self.mock_args() + data['name'] = 'sd_test' + data['owner'] = 'user_test' + set_module_args(data) + with pytest.raises(AnsibleExitJson) as exc: + self.get_sd_mock_object('zapi', 'create_idempotency', data).apply() + assert not exc.value.args[0]['changed'] + + def test_successfully_modify_sd(self): + data = self.mock_args() + data['name'] = 'sd_test' + data['owner'] = 'user_test' + data['control_flags_raw'] = 1 + set_module_args(data) + data['control_flags_raw'] = 2 + with pytest.raises(AnsibleExitJson) as exc: + self.get_sd_mock_object('zapi', 'create', data).apply() + assert exc.value.args[0]['changed'] + + def test_modify_sd_idempotency(self): + data = self.mock_args() + data['name'] = 'sd_test' + data['owner'] = 'user_test' + data['control_flags_raw'] = 2 + set_module_args(data) + data['control_flags_raw'] = 2 + with pytest.raises(AnsibleExitJson) as exc: + self.get_sd_mock_object('zapi', 'modify_idempotency', data).apply() + assert not exc.value.args[0]['changed'] + + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_ntfs_sd.NetAppOntapNtfsSd.get_ntfs_sd') + def test_modify_error(self, get_info): + data = self.mock_args() + data['name'] = 'sd_test' + data['owner'] = 'user_test' + data['control_flags_raw'] = 2 + set_module_args(data) + get_info.side_effect = [ + { + 'name': 'sd_test', + 'control_flags_raw': 1 + } + ] + with pytest.raises(AnsibleFailJson) as exc: + self.get_sd_mock_object('zapi', 'error', data).apply() + print(exc) + assert exc.value.args[0]['msg'] == 'Error modifying NTFS security descriptor sd_test: NetApp API failed. Reason - test:expect error' + + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_ntfs_sd.NetAppOntapNtfsSd.get_ntfs_sd') + def test_create_error(self, get_info): + data = self.mock_args() + data['name'] = 'sd_test' + data['owner'] = 'user_test' + set_module_args(data) + get_info.side_effect = [ + None + ] + with pytest.raises(AnsibleFailJson) as exc: + self.get_sd_mock_object('zapi', 'error', data).apply() + print(exc) + assert exc.value.args[0]['msg'] == 'Error creating NTFS security descriptor sd_test: NetApp API failed. Reason - test:expect error' + + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_ntfs_sd.NetAppOntapNtfsSd.get_ntfs_sd') + def test_delete_error(self, get_info): + data = self.mock_args() + data['name'] = 'sd_test' + data['owner'] = 'user_test' + data['state'] = 'absent' + set_module_args(data) + get_info.side_effect = [ + { + 'name': 'sd_test', + 'owner': 'user_test' + } + ] + with pytest.raises(AnsibleFailJson) as exc: + self.get_sd_mock_object('zapi', 'error', data).apply() + print(exc) + assert exc.value.args[0]['msg'] == 'Error deleting NTFS security descriptor sd_test: NetApp API failed. Reason - test:expect error' diff --git a/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_nvme.py b/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_nvme.py new file mode 100644 index 00000000..647a82ca --- /dev/null +++ b/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_nvme.py @@ -0,0 +1,217 @@ +# (c) 2018, NetApp, Inc +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +''' unit tests ONTAP Ansible module: na_ontap_nvme''' + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type +import json +import pytest + +from ansible_collections.netapp.ontap.tests.unit.compat import unittest +from ansible_collections.netapp.ontap.tests.unit.compat.mock import patch +from ansible.module_utils import basic +from ansible.module_utils._text import to_bytes +import ansible_collections.netapp.ontap.plugins.module_utils.netapp as netapp_utils + +from ansible_collections.netapp.ontap.plugins.modules.na_ontap_nvme \ + import NetAppONTAPNVMe as my_module + +if not netapp_utils.has_netapp_lib(): + pytestmark = pytest.mark.skip('skipping as missing required netapp_lib') + + +def set_module_args(args): + """prepare arguments so that they will be picked up during module creation""" + args = json.dumps({'ANSIBLE_MODULE_ARGS': args}) + basic._ANSIBLE_ARGS = to_bytes(args) # pylint: disable=protected-access + + +class AnsibleExitJson(Exception): + """Exception class to be raised by module.exit_json and caught by the test case""" + pass + + +class AnsibleFailJson(Exception): + """Exception class to be raised by module.fail_json and caught by the test case""" + pass + + +def exit_json(*args, **kwargs): # pylint: disable=unused-argument + """function to patch over exit_json; package return data into an exception""" + if 'changed' not in kwargs: + kwargs['changed'] = False + raise AnsibleExitJson(kwargs) + + +def fail_json(*args, **kwargs): # pylint: disable=unused-argument + """function to patch over fail_json; package return data into an exception""" + kwargs['failed'] = True + raise AnsibleFailJson(kwargs) + + +class MockONTAPConnection(object): + ''' mock server connection to ONTAP host ''' + + def __init__(self, kind=None): + ''' save arguments ''' + self.type = kind + self.xml_in = None + self.xml_out = None + + def invoke_successfully(self, xml, enable_tunneling): # pylint: disable=unused-argument + ''' mock invoke_successfully returning xml data ''' + self.xml_in = xml + if self.type == 'nvme': + xml = self.build_nvme_info() + elif self.type == 'nvme_fail': + raise netapp_utils.zapi.NaApiError(code='TEST', message="This exception is from the unit test") + self.xml_out = xml + return xml + + @staticmethod + def build_nvme_info(): + ''' build xml data for nvme-info ''' + xml = netapp_utils.zapi.NaElement('xml') + data = {'num-records': 1, + 'attributes-list': [{'nvme-target-service-info': {'is-available': 'true'}}]} + xml.translate_struct(data) + return xml + + +class TestMyModule(unittest.TestCase): + ''' a group of related Unit Tests ''' + + def setUp(self): + self.mock_module_helper = patch.multiple(basic.AnsibleModule, + exit_json=exit_json, + fail_json=fail_json) + self.mock_module_helper.start() + self.addCleanup(self.mock_module_helper.stop) + self.server = MockONTAPConnection() + self.onbox = False + + def set_default_args(self): + if self.onbox: + hostname = '10.193.75.3' + username = 'admin' + password = 'netapp1!' + vserver = 'ansible' + status_admin = True + else: + hostname = 'hostname' + username = 'username' + password = 'password' + vserver = 'vserver' + status_admin = True + return dict({ + 'hostname': hostname, + 'username': username, + 'password': password, + 'vserver': vserver, + 'status_admin': status_admin + }) + + def test_module_fail_when_required_args_missing(self): + ''' required arguments are reported as errors ''' + with pytest.raises(AnsibleFailJson) as exc: + set_module_args({}) + my_module() + print('Info: %s' % exc.value.args[0]['msg']) + + def test_ensure_get_called(self): + ''' test get_nvme() for non-existent nvme''' + set_module_args(self.set_default_args()) + my_obj = my_module() + my_obj.server = self.server + assert my_obj.get_nvme() is None + + def test_ensure_get_called_existing(self): + ''' test get_nvme() for existing nvme''' + set_module_args(self.set_default_args()) + my_obj = my_module() + my_obj.server = MockONTAPConnection(kind='nvme') + assert my_obj.get_nvme() + + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_nvme.NetAppONTAPNVMe.create_nvme') + def test_successful_create(self, create_nvme): + ''' creating nvme and testing idempotency ''' + set_module_args(self.set_default_args()) + my_obj = my_module() + if not self.onbox: + my_obj.server = self.server + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + assert exc.value.args[0]['changed'] + create_nvme.assert_called_with() + # to reset na_helper from remembering the previous 'changed' value + my_obj = my_module() + if not self.onbox: + my_obj.server = MockONTAPConnection('nvme') + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + assert not exc.value.args[0]['changed'] + + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_nvme.NetAppONTAPNVMe.delete_nvme') + def test_successful_delete(self, delete_nvme): + ''' deleting nvme and testing idempotency ''' + data = self.set_default_args() + data['state'] = 'absent' + set_module_args(data) + my_obj = my_module() + if not self.onbox: + my_obj.server = MockONTAPConnection('nvme') + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + assert exc.value.args[0]['changed'] + delete_nvme.assert_called_with() + # to reset na_helper from remembering the previous 'changed' value + my_obj = my_module() + if not self.onbox: + my_obj.server = self.server + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + assert not exc.value.args[0]['changed'] + + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_nvme.NetAppONTAPNVMe.modify_nvme') + def test_successful_modify(self, modify_nvme): + ''' modifying nvme and testing idempotency ''' + data = self.set_default_args() + data['status_admin'] = False + set_module_args(data) + my_obj = my_module() + if not self.onbox: + my_obj.server = MockONTAPConnection('nvme') + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + assert exc.value.args[0]['changed'] + modify_nvme.assert_called_with() + # to reset na_helper from remembering the previous 'changed' value + data = self.set_default_args() + set_module_args(data) + my_obj = my_module() + if not self.onbox: + my_obj.server = MockONTAPConnection('nvme') + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + assert not exc.value.args[0]['changed'] + + def test_if_all_methods_catch_exception(self): + module_args = {} + module_args.update(self.set_default_args()) + set_module_args(module_args) + my_obj = my_module() + if not self.onbox: + my_obj.server = MockONTAPConnection('nvme_fail') + with pytest.raises(AnsibleFailJson) as exc: + my_obj.get_nvme() + assert 'Error fetching nvme info:' in exc.value.args[0]['msg'] + with pytest.raises(AnsibleFailJson) as exc: + my_obj.create_nvme() + assert 'Error creating nvme' in exc.value.args[0]['msg'] + with pytest.raises(AnsibleFailJson) as exc: + my_obj.delete_nvme() + assert 'Error deleting nvme' in exc.value.args[0]['msg'] + with pytest.raises(AnsibleFailJson) as exc: + my_obj.modify_nvme() + assert 'Error modifying nvme' in exc.value.args[0]['msg'] diff --git a/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_nvme_namespace.py b/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_nvme_namespace.py new file mode 100644 index 00000000..ecfaadc3 --- /dev/null +++ b/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_nvme_namespace.py @@ -0,0 +1,201 @@ +# (c) 2018, NetApp, Inc +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +''' unit tests ONTAP Ansible module: na_ontap_nvme_namespace''' + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type +import json +import pytest + +from ansible_collections.netapp.ontap.tests.unit.compat import unittest +from ansible_collections.netapp.ontap.tests.unit.compat.mock import patch +from ansible.module_utils import basic +from ansible.module_utils._text import to_bytes +import ansible_collections.netapp.ontap.plugins.module_utils.netapp as netapp_utils + +from ansible_collections.netapp.ontap.plugins.modules.na_ontap_nvme_namespace \ + import NetAppONTAPNVMENamespace as my_module + +if not netapp_utils.has_netapp_lib(): + pytestmark = pytest.mark.skip('skipping as missing required netapp_lib') + + +def set_module_args(args): + """prepare arguments so that they will be picked up during module creation""" + args = json.dumps({'ANSIBLE_MODULE_ARGS': args}) + basic._ANSIBLE_ARGS = to_bytes(args) # pylint: disable=protected-access + + +class AnsibleExitJson(Exception): + """Exception class to be raised by module.exit_json and caught by the test case""" + pass + + +class AnsibleFailJson(Exception): + """Exception class to be raised by module.fail_json and caught by the test case""" + pass + + +def exit_json(*args, **kwargs): # pylint: disable=unused-argument + """function to patch over exit_json; package return data into an exception""" + if 'changed' not in kwargs: + kwargs['changed'] = False + raise AnsibleExitJson(kwargs) + + +def fail_json(*args, **kwargs): # pylint: disable=unused-argument + """function to patch over fail_json; package return data into an exception""" + kwargs['failed'] = True + raise AnsibleFailJson(kwargs) + + +class MockONTAPConnection(object): + ''' mock server connection to ONTAP host ''' + + def __init__(self, kind=None): + ''' save arguments ''' + self.type = kind + self.xml_in = None + self.xml_out = None + + def invoke_successfully(self, xml, enable_tunneling): # pylint: disable=unused-argument + ''' mock invoke_successfully returning xml data ''' + self.xml_in = xml + if self.type == 'namespace': + xml = self.build_namespace_info() + elif self.type == 'quota_fail': + raise netapp_utils.zapi.NaApiError(code='TEST', message="This exception is from the unit test") + self.xml_out = xml + return xml + + @staticmethod + def build_namespace_info(): + ''' build xml data for namespace-info ''' + xml = netapp_utils.zapi.NaElement('xml') + data = {'num-records': 2, + 'attributes-list': [{'nvme-namespace-info': {'path': 'abcd/vol'}}, + {'nvme-namespace-info': {'path': 'xyz/vol'}}]} + xml.translate_struct(data) + return xml + + +class TestMyModule(unittest.TestCase): + ''' a group of related Unit Tests ''' + + def setUp(self): + self.mock_module_helper = patch.multiple(basic.AnsibleModule, + exit_json=exit_json, + fail_json=fail_json) + self.mock_module_helper.start() + self.addCleanup(self.mock_module_helper.stop) + self.server = MockONTAPConnection() + self.onbox = False + + def set_default_args(self): + if self.onbox: + hostname = '10.193.75.3' + username = 'admin' + password = 'netapp1!' + vserver = 'ansible' + ostype = 'linux' + path = 'abcd/vol' + size = 20 + size_unit = 'mb' + else: + hostname = 'hostname' + username = 'username' + password = 'password' + vserver = 'vserver' + ostype = 'linux' + path = 'abcd/vol' + size = 20 + size_unit = 'mb' + return dict({ + 'hostname': hostname, + 'username': username, + 'password': password, + 'ostype': ostype, + 'vserver': vserver, + 'path': path, + 'size': size, + 'size_unit': size_unit + }) + + def test_module_fail_when_required_args_missing(self): + ''' required arguments are reported as errors ''' + with pytest.raises(AnsibleFailJson) as exc: + set_module_args({}) + my_module() + print('Info: %s' % exc.value.args[0]['msg']) + + def test_ensure_get_called(self): + ''' test get_namespace() for non-existent namespace''' + set_module_args(self.set_default_args()) + my_obj = my_module() + my_obj.server = self.server + assert my_obj.get_namespace() is None + + def test_ensure_get_called_existing(self): + ''' test get_namespace() for existing namespace''' + set_module_args(self.set_default_args()) + my_obj = my_module() + my_obj.server = MockONTAPConnection(kind='namespace') + assert my_obj.get_namespace() + + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_nvme_namespace.NetAppONTAPNVMENamespace.create_namespace') + def test_successful_create(self, create_namespace): + ''' creating namespace and testing idempotency ''' + set_module_args(self.set_default_args()) + my_obj = my_module() + if not self.onbox: + my_obj.server = self.server + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + assert exc.value.args[0]['changed'] + create_namespace.assert_called_with() + # to reset na_helper from remembering the previous 'changed' value + my_obj = my_module() + if not self.onbox: + my_obj.server = MockONTAPConnection('namespace') + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + assert not exc.value.args[0]['changed'] + + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_nvme_namespace.NetAppONTAPNVMENamespace.delete_namespace') + def test_successful_delete(self, delete_namespace): + ''' deleting namespace and testing idempotency ''' + data = self.set_default_args() + data['state'] = 'absent' + set_module_args(data) + my_obj = my_module() + if not self.onbox: + my_obj.server = MockONTAPConnection('namespace') + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + assert exc.value.args[0]['changed'] + delete_namespace.assert_called_with() + # to reset na_helper from remembering the previous 'changed' value + my_obj = my_module() + if not self.onbox: + my_obj.server = self.server + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + assert not exc.value.args[0]['changed'] + + def test_if_all_methods_catch_exception(self): + module_args = {} + module_args.update(self.set_default_args()) + set_module_args(module_args) + my_obj = my_module() + if not self.onbox: + my_obj.server = MockONTAPConnection('quota_fail') + with pytest.raises(AnsibleFailJson) as exc: + my_obj.get_namespace() + assert 'Error fetching namespace info:' in exc.value.args[0]['msg'] + with pytest.raises(AnsibleFailJson) as exc: + my_obj.create_namespace() + assert 'Error creating namespace for path' in exc.value.args[0]['msg'] + with pytest.raises(AnsibleFailJson) as exc: + my_obj.delete_namespace() + assert 'Error deleting namespace for path' in exc.value.args[0]['msg'] diff --git a/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_nvme_subsystem.py b/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_nvme_subsystem.py new file mode 100644 index 00000000..c056fc90 --- /dev/null +++ b/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_nvme_subsystem.py @@ -0,0 +1,242 @@ +# (c) 2018, NetApp, Inc +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +''' unit tests ONTAP Ansible module: na_ontap_nvme_subsystem ''' + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type +import json +import pytest + +from ansible_collections.netapp.ontap.tests.unit.compat import unittest +from ansible_collections.netapp.ontap.tests.unit.compat.mock import patch +from ansible.module_utils import basic +from ansible.module_utils._text import to_bytes +import ansible_collections.netapp.ontap.plugins.module_utils.netapp as netapp_utils + +from ansible_collections.netapp.ontap.plugins.modules.na_ontap_nvme_subsystem \ + import NetAppONTAPNVMESubsystem as my_module + +if not netapp_utils.has_netapp_lib(): + pytestmark = pytest.mark.skip('skipping as missing required netapp_lib') + + +def set_module_args(args): + """prepare arguments so that they will be picked up during module creation""" + args = json.dumps({'ANSIBLE_MODULE_ARGS': args}) + basic._ANSIBLE_ARGS = to_bytes(args) # pylint: disable=protected-access + + +class AnsibleExitJson(Exception): + """Exception class to be raised by module.exit_json and caught by the test case""" + pass + + +class AnsibleFailJson(Exception): + """Exception class to be raised by module.fail_json and caught by the test case""" + pass + + +def exit_json(*args, **kwargs): # pylint: disable=unused-argument + """function to patch over exit_json; package return data into an exception""" + if 'changed' not in kwargs: + kwargs['changed'] = False + raise AnsibleExitJson(kwargs) + + +def fail_json(*args, **kwargs): # pylint: disable=unused-argument + """function to patch over fail_json; package return data into an exception""" + kwargs['failed'] = True + raise AnsibleFailJson(kwargs) + + +class MockONTAPConnection(object): + ''' mock server connection to ONTAP host ''' + + def __init__(self, kind=None, parm1=None): + ''' save arguments ''' + self.type = kind + self.parm1 = parm1 + self.xml_in = None + self.xml_out = None + + def invoke_successfully(self, xml, enable_tunneling): # pylint: disable=unused-argument + ''' mock invoke_successfully returning xml data ''' + self.xml_in = xml + if self.type == 'subsystem': + xml = self.build_subsystem_info(self.parm1) + self.xml_out = xml + return xml + + @staticmethod + def build_subsystem_info(vserver): + ''' build xml data for vserser-info ''' + xml = netapp_utils.zapi.NaElement('xml') + data = {'num-records': 2, + 'attributes-list': [{'nvme-target-subsystem-map-info': {'path': 'abcd/vol'}}, + {'nvme-target-subsystem-map-info': {'path': 'xyz/vol'}}]} + xml.translate_struct(data) + return xml + + +class TestMyModule(unittest.TestCase): + ''' a group of related Unit Tests ''' + + def setUp(self): + self.mock_module_helper = patch.multiple(basic.AnsibleModule, + exit_json=exit_json, + fail_json=fail_json) + self.mock_module_helper.start() + self.addCleanup(self.mock_module_helper.stop) + self.server = MockONTAPConnection() + self.onbox = False + + def set_default_args(self): + if self.onbox: + hostname = '10.193.75.3' + username = 'admin' + password = 'netapp1!' + subsystem = 'test' + vserver = 'ansible' + ostype = 'linux' + paths = ['abcd/vol', 'xyz/vol'] + else: + hostname = 'hostname' + username = 'username' + password = 'password' + subsystem = 'test' + vserver = 'vserver' + ostype = 'linux' + paths = ['abcd/vol', 'xyz/vol'] + return dict({ + 'hostname': hostname, + 'username': username, + 'password': password, + 'subsystem': subsystem, + 'ostype': ostype, + 'vserver': vserver, + 'paths': paths + }) + + def test_module_fail_when_required_args_missing(self): + ''' required arguments are reported as errors ''' + with pytest.raises(AnsibleFailJson) as exc: + set_module_args({}) + my_module() + print('Info: %s' % exc.value.args[0]['msg']) + + def test_ensure_get_called(self): + ''' test get_subsystem() for non-existent subsystem''' + set_module_args(self.set_default_args()) + my_obj = my_module() + my_obj.server = self.server + assert my_obj.get_subsystem() is None + + def test_ensure_get_called_existing(self): + ''' test get_subsystem() for existing subsystem''' + set_module_args(self.set_default_args()) + my_obj = my_module() + my_obj.server = MockONTAPConnection(kind='subsystem') + assert my_obj.get_subsystem() + + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_nvme_subsystem.NetAppONTAPNVMESubsystem.create_subsystem') + def test_successful_create(self, create_subsystem): + ''' creating subsystem and testing idempotency ''' + set_module_args(self.set_default_args()) + my_obj = my_module() + if not self.onbox: + my_obj.server = self.server + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + assert exc.value.args[0]['changed'] + create_subsystem.assert_called_with() + # to reset na_helper from remembering the previous 'changed' value + my_obj = my_module() + if not self.onbox: + my_obj.server = MockONTAPConnection('subsystem') + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + assert not exc.value.args[0]['changed'] + + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_nvme_subsystem.NetAppONTAPNVMESubsystem.delete_subsystem') + def test_successful_delete(self, delete_subsystem): + ''' deleting subsystem and testing idempotency ''' + data = self.set_default_args() + data['state'] = 'absent' + set_module_args(data) + my_obj = my_module() + if not self.onbox: + my_obj.server = MockONTAPConnection('subsystem') + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + assert exc.value.args[0]['changed'] + delete_subsystem.assert_called_with() + # to reset na_helper from remembering the previous 'changed' value + my_obj = my_module() + if not self.onbox: + my_obj.server = self.server + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + assert not exc.value.args[0]['changed'] + + def test_ensure_get_called(self): + ''' test get_subsystem_host_map() for non-existent subsystem''' + set_module_args(self.set_default_args()) + my_obj = my_module() + my_obj.server = self.server + assert my_obj.get_subsystem_host_map('paths') is None + + def test_ensure_get_called_existing(self): + ''' test get_subsystem_host_map() for existing subsystem''' + set_module_args(self.set_default_args()) + my_obj = my_module() + my_obj.server = MockONTAPConnection(kind='subsystem') + assert my_obj.get_subsystem_host_map('paths') + + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_nvme_subsystem.NetAppONTAPNVMESubsystem.add_subsystem_host_map') + def test_successful_add_mock(self, add_subsystem_host_map): + ''' adding subsystem host/map and testing idempotency ''' + set_module_args(self.set_default_args()) + my_obj = my_module() + if not self.onbox: + my_obj.server = self.server + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + assert exc.value.args[0]['changed'] + add_subsystem_host_map.assert_called_with(['abcd/vol', 'xyz/vol'], 'paths') + + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_nvme_subsystem.NetAppONTAPNVMESubsystem.remove_subsystem_host_map') + def test_successful_remove_mock(self, remove_subsystem_host_map): + ''' removing subsystem host/map and testing idempotency ''' + data = self.set_default_args() + data['paths'] = ['abcd/vol'] + set_module_args(data) + my_obj = my_module() + if not self.onbox: + my_obj.server = MockONTAPConnection('subsystem') + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + assert exc.value.args[0]['changed'] + remove_subsystem_host_map.assert_called_with(['xyz/vol'], 'paths') + + def test_successful_add(self): + ''' adding subsystem host/map and testing idempotency ''' + set_module_args(self.set_default_args()) + my_obj = my_module() + if not self.onbox: + my_obj.server = self.server + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + assert exc.value.args[0]['changed'] + + def test_successful_remove(self): + ''' removing subsystem host/map and testing idempotency ''' + data = self.set_default_args() + data['paths'] = ['abcd/vol'] + set_module_args(data) + my_obj = my_module() + if not self.onbox: + my_obj.server = MockONTAPConnection('subsystem') + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + assert exc.value.args[0]['changed'] diff --git a/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_object_store.py b/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_object_store.py new file mode 100644 index 00000000..3317aed3 --- /dev/null +++ b/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_object_store.py @@ -0,0 +1,300 @@ +# (c) 2019, NetApp, Inc +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +""" unit tests for Ansible module: na_ontap_object_store """ + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type +import json +import pytest + +from ansible.module_utils import basic +from ansible.module_utils._text import to_bytes +from ansible_collections.netapp.ontap.tests.unit.compat import unittest +from ansible_collections.netapp.ontap.tests.unit.compat.mock import patch, Mock +import ansible_collections.netapp.ontap.plugins.module_utils.netapp as netapp_utils + +from ansible_collections.netapp.ontap.plugins.modules.na_ontap_object_store \ + import NetAppOntapObjectStoreConfig as my_module # module under test + +if not netapp_utils.has_netapp_lib(): + pytestmark = pytest.mark.skip('skipping as missing required netapp_lib') + + +# REST API canned responses when mocking send_request +SRR = { + # common responses + 'is_rest': (200, {}, None), + 'is_zapi': (400, {}, "Unreachable"), + 'empty_good': (200, {}, None), + 'end_of_sequence': (500, None, "Unexpected call to send_request"), + 'generic_error': (400, None, "Expected error"), + # module specific responses + 'get_uuid': (200, {'records': [{'uuid': 'ansible'}]}, None), + 'get_object_store': (200, + {'uuid': 'ansible', + 'name': 'ansible', + }, None) +} + + +def set_module_args(args): + """prepare arguments so that they will be picked up during module creation""" + args = json.dumps({'ANSIBLE_MODULE_ARGS': args}) + basic._ANSIBLE_ARGS = to_bytes(args) # pylint: disable=protected-access + + +class AnsibleExitJson(Exception): + """Exception class to be raised by module.exit_json and caught by the test case""" + + +class AnsibleFailJson(Exception): + """Exception class to be raised by module.fail_json and caught by the test case""" + + +def exit_json(*args, **kwargs): # pylint: disable=unused-argument + """function to patch over exit_json; package return data into an exception""" + if 'changed' not in kwargs: + kwargs['changed'] = False + raise AnsibleExitJson(kwargs) + + +def fail_json(*args, **kwargs): # pylint: disable=unused-argument + """function to patch over fail_json; package return data into an exception""" + kwargs['failed'] = True + raise AnsibleFailJson(kwargs) + + +class MockONTAPConnection(object): + ''' mock server connection to ONTAP host ''' + + def __init__(self, kind=None): + ''' save arguments ''' + self.type = kind + self.xml_in = None + self.xml_out = None + + def invoke_successfully(self, xml, enable_tunneling): # pylint: disable=unused-argument + ''' mock invoke_successfully returning xml data ''' + self.xml_in = xml + if self.type == 'object_store': + xml = self.build_object_store_info() + elif self.type == 'object_store_fail': + raise netapp_utils.zapi.NaApiError(code='TEST', message="This exception is from the unit test") + self.xml_out = xml + return xml + + @staticmethod + def build_object_store_info(): + ''' build xml data for object store ''' + xml = netapp_utils.zapi.NaElement('xml') + data = {'attributes': + {'aggr-object-store-config-info': + {'object-store-name': 'ansible'} + } + } + xml.translate_struct(data) + print(xml.to_string()) + return xml + + +class TestMyModule(unittest.TestCase): + ''' a group of related Unit Tests ''' + + def setUp(self): + self.mock_module_helper = patch.multiple(basic.AnsibleModule, + exit_json=exit_json, + fail_json=fail_json) + self.mock_module_helper.start() + self.addCleanup(self.mock_module_helper.stop) + self.server = MockONTAPConnection() + # whether to use a mock or a simulator + self.onbox = False + + def set_default_args(self): + if self.onbox: + hostname = '10.10.10.10' + username = 'admin' + password = 'password' + name = 'ansible' + else: + hostname = 'hostname' + username = 'username' + password = 'password' + name = 'ansible' + return dict({ + 'hostname': hostname, + 'username': username, + 'password': password, + 'name': name + }) + + def call_command(self, module_args, cx_type='zapi'): + ''' utility function to call apply ''' + module_args.update(self.set_default_args()) + set_module_args(module_args) + my_obj = my_module() + my_obj.asup_log_for_cserver = Mock(return_value=None) + if cx_type == 'zapi': + if not self.onbox: + # mock the connection + my_obj.server = MockONTAPConnection('object_store') + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + return exc.value.args[0]['changed'] + + def test_module_fail_when_required_args_missing(self): + ''' required arguments are reported as errors ''' + with pytest.raises(AnsibleFailJson) as exc: + set_module_args({}) + my_module() + print('Info: %s' % exc.value.args[0]['msg']) + + @patch('ansible_collections.netapp.ontap.plugins.module_utils.netapp.OntapRestAPI.send_request') + def test_ensure_object_store_get_called(self, mock_request): + ''' fetching details of object store ''' + mock_request.side_effect = [ + SRR['is_zapi'], + SRR['end_of_sequence'] + ] + set_module_args(self.set_default_args()) + my_obj = my_module() + my_obj.server = self.server + assert my_obj.get_aggr_object_store() is not None + + @patch('ansible_collections.netapp.ontap.plugins.module_utils.netapp.OntapRestAPI.send_request') + def test_ensure_get_called_existing(self, mock_request): + ''' test for existing object store''' + mock_request.side_effect = [ + SRR['is_zapi'], + SRR['end_of_sequence'] + ] + set_module_args(self.set_default_args()) + my_obj = my_module() + my_obj.server = MockONTAPConnection(kind='object_store') + assert my_obj.get_aggr_object_store() + + @patch('ansible_collections.netapp.ontap.plugins.module_utils.netapp.OntapRestAPI.send_request') + def test_object_store_create(self, mock_request): + ''' test for creating object store''' + mock_request.side_effect = [ + SRR['is_zapi'], + SRR['end_of_sequence'] + ] + module_args = { + 'provider_type': 'abc', + 'server': 'abc', + 'container': 'abc', + 'access_key': 'abc', + 'secret_password': 'abc' + } + module_args.update(self.set_default_args()) + set_module_args(module_args) + my_obj = my_module() + my_obj.asup_log_for_cserver = Mock(return_value=None) + if not self.onbox: + # mock the connection + my_obj.server = MockONTAPConnection(kind='object_store') + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + assert not exc.value.args[0]['changed'] + + @patch('ansible_collections.netapp.ontap.plugins.module_utils.netapp.OntapRestAPI.send_request') + def test_object_store_delete(self, mock_request): + ''' test for deleting object store''' + mock_request.side_effect = [ + SRR['is_zapi'], + SRR['end_of_sequence'] + ] + module_args = { + 'state': 'absent', + } + changed = self.call_command(module_args) + assert changed + + @patch('ansible_collections.netapp.ontap.plugins.module_utils.netapp.OntapRestAPI.send_request') + def test_rest_error(self, mock_request): + mock_request.side_effect = [ + SRR['is_zapi'], + SRR['end_of_sequence'] + ] + set_module_args(self.set_default_args()) + mock_request.side_effect = [ + SRR['is_rest'], + SRR['generic_error'], + SRR['end_of_sequence'] + ] + my_obj = my_module() + with pytest.raises(AnsibleFailJson) as exc: + my_obj.apply() + assert exc.value.args[0]['msg'] == SRR['generic_error'][2] + + @patch('ansible_collections.netapp.ontap.plugins.module_utils.netapp.OntapRestAPI.send_request') + def test_rest_successful_create(self, mock_request): + data = { + 'provider_type': 'abc', + 'server': 'abc', + 'container': 'abc', + 'access_key': 'abc', + 'secret_password': 'abc' + } + data.update(self.set_default_args()) + set_module_args(data) + mock_request.side_effect = [ + SRR['is_rest'], + SRR['get_uuid'], + SRR['get_object_store'], + SRR['empty_good'], + SRR['end_of_sequence'] + ] + my_obj = my_module() + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + assert not exc.value.args[0]['changed'] + + @patch('ansible_collections.netapp.ontap.plugins.module_utils.netapp.OntapRestAPI.send_request') + def test_rest_successful_delete(self, mock_request): + data = { + 'state': 'absent', + } + data.update(self.set_default_args()) + set_module_args(data) + mock_request.side_effect = [ + SRR['is_rest'], + SRR['get_uuid'], + SRR['get_object_store'], + SRR['empty_good'], + SRR['end_of_sequence'] + ] + my_obj = my_module() + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + assert exc.value.args[0]['changed'] + + @patch('ansible_collections.netapp.ontap.plugins.module_utils.netapp.OntapRestAPI.send_request') + def test_if_all_methods_catch_exception(self, mock_request): + mock_request.side_effect = [ + SRR['is_zapi'], + SRR['end_of_sequence'] + ] + module_args = { + 'provider_type': 'abc', + 'server': 'abc', + 'container': 'abc', + 'access_key': 'abc', + 'secret_password': 'abc' + } + module_args.update(self.set_default_args()) + set_module_args(module_args) + my_obj = my_module() + if not self.onbox: + my_obj.server = MockONTAPConnection('object_store_fail') + with pytest.raises(AnsibleFailJson) as exc: + my_obj.get_aggr_object_store() + assert '' in exc.value.args[0]['msg'] + with pytest.raises(AnsibleFailJson) as exc: + my_obj.create_aggr_object_store() + assert 'Error provisioning object store config ' in exc.value.args[0]['msg'] + with pytest.raises(AnsibleFailJson) as exc: + my_obj.delete_aggr_object_store() + assert 'Error removing object store config ' in exc.value.args[0]['msg'] diff --git a/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_ports.py b/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_ports.py new file mode 100644 index 00000000..04942486 --- /dev/null +++ b/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_ports.py @@ -0,0 +1,173 @@ +# (c) 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_port''' + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type +import json +import pytest + +from ansible_collections.netapp.ontap.tests.unit.compat import unittest +from ansible_collections.netapp.ontap.tests.unit.compat.mock import patch, Mock +from ansible.module_utils import basic +from ansible.module_utils._text import to_bytes +import ansible_collections.netapp.ontap.plugins.module_utils.netapp as netapp_utils + +from ansible_collections.netapp.ontap.plugins.modules.na_ontap_ports \ + import NetAppOntapPorts as port_module # module under test + +if not netapp_utils.has_netapp_lib(): + pytestmark = pytest.mark.skip('skipping as missing required netapp_lib') + + +def set_module_args(args): + """prepare arguments so that they will be picked up during module creation""" + args = json.dumps({'ANSIBLE_MODULE_ARGS': args}) + basic._ANSIBLE_ARGS = to_bytes(args) # pylint: disable=protected-access + + +class AnsibleExitJson(Exception): + """Exception class to be raised by module.exit_json and caught by the test case""" + pass + + +class AnsibleFailJson(Exception): + """Exception class to be raised by module.fail_json and caught by the test case""" + pass + + +def exit_json(*args, **kwargs): # pylint: disable=unused-argument + """function to patch over exit_json; package return data into an exception""" + if 'changed' not in kwargs: + kwargs['changed'] = False + raise AnsibleExitJson(kwargs) + + +def fail_json(*args, **kwargs): # pylint: disable=unused-argument + """function to patch over fail_json; package return data into an exception""" + kwargs['failed'] = True + raise AnsibleFailJson(kwargs) + + +class TestMyModule(unittest.TestCase): + ''' a group of related Unit Tests ''' + + def setUp(self): + self.mock_module_helper = patch.multiple(basic.AnsibleModule, + exit_json=exit_json, + fail_json=fail_json) + self.mock_module_helper.start() + self.addCleanup(self.mock_module_helper.stop) + + def mock_args(self, choice): + if choice == 'broadcast_domain': + return { + 'names': ['test_port_1', 'test_port_2'], + 'resource_name': 'test_domain', + 'resource_type': 'broadcast_domain', + 'hostname': 'test', + 'username': 'test_user', + 'password': 'test_pass!' + } + elif choice == 'portset': + return { + 'names': ['test_lif'], + 'resource_name': 'test_portset', + 'resource_type': 'portset', + 'hostname': 'test', + 'username': 'test_user', + 'password': 'test_pass!', + 'vserver': 'test_vserver' + } + + def get_port_mock_object(self): + """ + Helper method to return an na_ontap_port object + """ + port_obj = port_module() + port_obj.asup_log_for_cserver = Mock(return_value=None) + port_obj.server = Mock() + port_obj.server.invoke_successfully = Mock() + + return port_obj + + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_ports.NetAppOntapPorts.add_broadcast_domain_ports') + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_ports.NetAppOntapPorts.get_broadcast_domain_ports') + def test_successfully_add_broadcast_domain_ports(self, get_broadcast_domain_ports, add_broadcast_domain_ports): + ''' Test successful add broadcast domain ports ''' + data = self.mock_args('broadcast_domain') + set_module_args(data) + get_broadcast_domain_ports.side_effect = [ + [] + ] + with pytest.raises(AnsibleExitJson) as exc: + self.get_port_mock_object().apply() + assert exc.value.args[0]['changed'] + + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_ports.NetAppOntapPorts.add_broadcast_domain_ports') + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_ports.NetAppOntapPorts.get_broadcast_domain_ports') + def test_add_broadcast_domain_ports_idempotency(self, get_broadcast_domain_ports, add_broadcast_domain_ports): + ''' Test add broadcast domain ports idempotency ''' + data = self.mock_args('broadcast_domain') + set_module_args(data) + get_broadcast_domain_ports.side_effect = [ + ['test_port_1', 'test_port_2'] + ] + with pytest.raises(AnsibleExitJson) as exc: + self.get_port_mock_object().apply() + assert not exc.value.args[0]['changed'] + + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_ports.NetAppOntapPorts.add_portset_ports') + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_ports.NetAppOntapPorts.portset_get') + def test_successfully_add_portset_ports(self, portset_get, add_portset_ports): + ''' Test successful add portset ports ''' + data = self.mock_args('portset') + set_module_args(data) + portset_get.side_effect = [ + [] + ] + with pytest.raises(AnsibleExitJson) as exc: + self.get_port_mock_object().apply() + assert exc.value.args[0]['changed'] + + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_ports.NetAppOntapPorts.add_portset_ports') + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_ports.NetAppOntapPorts.portset_get') + def test_add_portset_ports_idempotency(self, portset_get, add_portset_ports): + ''' Test add portset ports idempotency ''' + data = self.mock_args('portset') + set_module_args(data) + portset_get.side_effect = [ + ['test_lif'] + ] + with pytest.raises(AnsibleExitJson) as exc: + self.get_port_mock_object().apply() + assert not exc.value.args[0]['changed'] + + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_ports.NetAppOntapPorts.add_broadcast_domain_ports') + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_ports.NetAppOntapPorts.get_broadcast_domain_ports') + def test_successfully_remove_broadcast_domain_ports(self, get_broadcast_domain_ports, add_broadcast_domain_ports): + ''' Test successful remove broadcast domain ports ''' + data = self.mock_args('broadcast_domain') + data['state'] = 'absent' + set_module_args(data) + get_broadcast_domain_ports.side_effect = [ + ['test_port_1', 'test_port_2'] + ] + with pytest.raises(AnsibleExitJson) as exc: + self.get_port_mock_object().apply() + assert exc.value.args[0]['changed'] + + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_ports.NetAppOntapPorts.add_portset_ports') + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_ports.NetAppOntapPorts.portset_get') + def test_remove_add_portset_ports(self, portset_get, add_portset_ports): + ''' Test successful remove portset ports ''' + data = self.mock_args('portset') + data['state'] = 'absent' + set_module_args(data) + portset_get.side_effect = [ + ['test_lif'] + ] + with pytest.raises(AnsibleExitJson) as exc: + self.get_port_mock_object().apply() + assert exc.value.args[0]['changed'] diff --git a/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_portset.py b/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_portset.py new file mode 100644 index 00000000..2efb2275 --- /dev/null +++ b/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_portset.py @@ -0,0 +1,190 @@ +# (c) 2018, 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_portset''' + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type +import json +import pytest + +from ansible_collections.netapp.ontap.tests.unit.compat import unittest +from ansible_collections.netapp.ontap.tests.unit.compat.mock import patch +from ansible.module_utils import basic +from ansible.module_utils._text import to_bytes +import ansible_collections.netapp.ontap.plugins.module_utils.netapp as netapp_utils + +from ansible_collections.netapp.ontap.plugins.modules.na_ontap_portset \ + import NetAppONTAPPortset as my_module # module under test + +if not netapp_utils.has_netapp_lib(): + pytestmark = pytest.mark.skip('skipping as missing required netapp_lib') + + +def set_module_args(args): + """prepare arguments so that they will be picked up during module creation""" + args = json.dumps({'ANSIBLE_MODULE_ARGS': args}) + basic._ANSIBLE_ARGS = to_bytes(args) # pylint: disable=protected-access + + +class AnsibleExitJson(Exception): + """Exception class to be raised by module.exit_json and caught by the test case""" + pass + + +class AnsibleFailJson(Exception): + """Exception class to be raised by module.fail_json and caught by the test case""" + pass + + +def exit_json(*args, **kwargs): # pylint: disable=unused-argument + """function to patch over exit_json; package return data into an exception""" + if 'changed' not in kwargs: + kwargs['changed'] = False + raise AnsibleExitJson(kwargs) + + +def fail_json(*args, **kwargs): # pylint: disable=unused-argument + """function to patch over fail_json; package return data into an exception""" + kwargs['failed'] = True + raise AnsibleFailJson(kwargs) + + +class MockONTAPConnection(object): + ''' mock server connection to ONTAP host ''' + + def __init__(self, kind=None, parm1=None, parm2=None, parm3=None): + ''' save arguments ''' + self.type = kind + self.parm1 = parm1 + self.parm2 = parm2 + self.parm3 = parm3 + self.xml_in = None + self.xml_out = None + + def invoke_successfully(self, xml, enable_tunneling): # pylint: disable=unused-argument + ''' mock invoke_successfully returning xml data ''' + self.xml_in = xml + if self.type == 'portset': + xml = self.build_portset_info(self.parm1, self.parm2, self.parm3) + self.xml_out = xml + return xml + + @staticmethod + def build_portset_info(portset, vserver, type): + ''' build xml data for vserser-info ''' + xml = netapp_utils.zapi.NaElement('xml') + data = {'num-records': 1, + 'attributes-list': {'portset-info': {'portset-name': portset, + 'vserver': vserver, 'portset-type': type, + 'portset-port-total': '0'}}} + xml.translate_struct(data) + print(xml.to_string()) + return xml + + +class TestMyModule(unittest.TestCase): + ''' a group of related Unit Tests ''' + + def setUp(self): + self.mock_module_helper = patch.multiple(basic.AnsibleModule, + exit_json=exit_json, + fail_json=fail_json) + self.mock_module_helper.start() + self.addCleanup(self.mock_module_helper.stop) + self.server = MockONTAPConnection() + self.use_vsim = False + + def set_default_args(self): + if self.use_vsim: + hostname = '10.193.77.154' + username = 'admin' + password = 'netapp1!' + name = 'test' + type = 'mixed' + vserver = 'ansible_test' + ports = ['a1', 'a2'] + else: + hostname = 'hostname' + username = 'username' + password = 'password' + name = 'name' + type = 'mixed' + vserver = 'vserver' + ports = ['a1', 'a2'] + return dict({ + 'hostname': hostname, + 'username': username, + 'password': password, + 'name': name, + 'type': type, + 'vserver': vserver, + 'ports': ports + }) + + def test_module_fail_when_required_args_missing(self): + ''' required arguments are reported as errors ''' + with pytest.raises(AnsibleFailJson) as exc: + set_module_args({}) + my_module() + print('Info: %s' % exc.value.args[0]['msg']) + + def test_ensure_portset_get_called(self): + ''' a more interesting test ''' + set_module_args(self.set_default_args()) + my_obj = my_module() + my_obj.server = self.server + portset = my_obj.portset_get() + print('Info: test_portset_get: %s' % repr(portset)) + assert portset is None + + def test_ensure_portset_apply_called(self): + ''' Test successful create ''' + module_args = {'name': 'create'} + module_args.update(self.set_default_args()) + set_module_args(module_args) + my_obj = my_module() + if not self.use_vsim: + my_obj.server = self.server + portset = my_obj.portset_get() + print('Info: test_portset_get: %s' % repr(portset)) + assert portset is None + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + print('Info: test_portset_apply: %s' % repr(exc.value)) + assert exc.value.args[0]['changed'] + if not self.use_vsim: + my_obj.server = MockONTAPConnection('portset', 'create', 'vserver', 'mixed') + portset = my_obj.portset_get() + print('Info: test_portset_get: %s' % repr(portset)) + assert portset is not None + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + print('Info: test_portset_apply: %s' % repr(exc.value)) + assert exc.value.args[0]['changed'] + + def test_modify_ports(self): + ''' Test modify_portset method ''' + module_args = {'ports': ['l1', 'l2']} + module_args.update(self.set_default_args()) + set_module_args(module_args) + my_obj = my_module() + if not self.use_vsim: + my_obj.server = MockONTAPConnection('portset', parm3='mixed') + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + print('Info: test_portset_apply: %s' % repr(exc.value)) + assert exc.value.args[0]['changed'] + + def test_delete_portset(self): + ''' Test successful delete ''' + module_args = {'state': 'absent'} + module_args.update(self.set_default_args()) + set_module_args(module_args) + my_obj = my_module() + if not self.use_vsim: + my_obj.server = MockONTAPConnection('portset') + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + print('Info: test_portset_apply: %s' % repr(exc.value)) + assert exc.value.args[0]['changed'] diff --git a/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_qos_adaptive_policy_group.py b/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_qos_adaptive_policy_group.py new file mode 100644 index 00000000..b376ba8e --- /dev/null +++ b/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_qos_adaptive_policy_group.py @@ -0,0 +1,347 @@ +# (c) 2018, NetApp, Inc +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +''' unit test template for ONTAP Ansible module ''' + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type +import json +import pytest + +from ansible_collections.netapp.ontap.tests.unit.compat import unittest +from ansible_collections.netapp.ontap.tests.unit.compat.mock import patch, Mock +from ansible.module_utils import basic +from ansible.module_utils._text import to_bytes +import ansible_collections.netapp.ontap.plugins.module_utils.netapp as netapp_utils + +from ansible_collections.netapp.ontap.plugins.modules.na_ontap_qos_adaptive_policy_group \ + import NetAppOntapAdaptiveQosPolicyGroup as qos_policy_group_module # module under test + +if not netapp_utils.has_netapp_lib(): + pytestmark = pytest.mark.skip('skipping as missing required netapp_lib') + + +def set_module_args(args): + """prepare arguments so that they will be picked up during module creation""" + args = json.dumps({'ANSIBLE_MODULE_ARGS': args}) + basic._ANSIBLE_ARGS = to_bytes(args) # pylint: disable=protected-access + + +class AnsibleExitJson(Exception): + """Exception class to be raised by module.exit_json and caught by the test case""" + pass + + +class AnsibleFailJson(Exception): + """Exception class to be raised by module.fail_json and caught by the test case""" + pass + + +def exit_json(*args, **kwargs): # pylint: disable=unused-argument + """function to patch over exit_json; package return data into an exception""" + if 'changed' not in kwargs: + kwargs['changed'] = False + raise AnsibleExitJson(kwargs) + + +def fail_json(*args, **kwargs): # pylint: disable=unused-argument + """function to patch over fail_json; package return data into an exception""" + kwargs['failed'] = True + raise AnsibleFailJson(kwargs) + + +class MockONTAPConnection(object): + ''' mock server connection to ONTAP host ''' + + def __init__(self, kind=None, data=None): + ''' save arguments ''' + self.kind = kind + self.params = data + self.xml_in = None + self.xml_out = None + + def invoke_successfully(self, xml, enable_tunneling): # pylint: disable=unused-argument + ''' mock invoke_successfully returning xml data ''' + self.xml_in = xml + if self.kind == 'policy': + xml = self.build_policy_group_info(self.params) + if self.kind == 'error': + error = netapp_utils.zapi.NaApiError('test', 'error') + raise error + self.xml_out = xml + return xml + + @staticmethod + def build_policy_group_info(vol_details): + ''' build xml data for volume-attributes ''' + xml = netapp_utils.zapi.NaElement('xml') + attributes = { + 'num-records': 1, + 'attributes-list': { + 'qos-adaptive-policy-group-info': { + 'absolute-min-iops': '50IOPS', + 'expected-iops': '150IOPS/TB', + 'peak-iops': '220IOPS/TB', + 'peak-iops-allocation': 'used_space', + 'num-workloads': 0, + 'pgid': 6941, + 'policy-group': vol_details['name'], + 'vserver': vol_details['vserver'] + } + } + } + xml.translate_struct(attributes) + return xml + + +class TestMyModule(unittest.TestCase): + ''' a group of related Unit Tests ''' + + def setUp(self): + self.mock_module_helper = patch.multiple(basic.AnsibleModule, + exit_json=exit_json, + fail_json=fail_json) + self.mock_module_helper.start() + self.addCleanup(self.mock_module_helper.stop) + self.mock_policy_group = { + 'name': 'policy_1', + 'vserver': 'policy_vserver', + 'absolute_min_iops': '50IOPS', + 'expected_iops': '150IOPS/TB', + 'peak_iops': '220IOPS/TB', + 'peak_iops_allocation': 'used_space' + } + + def mock_args(self): + return { + 'name': self.mock_policy_group['name'], + 'vserver': self.mock_policy_group['vserver'], + 'absolute_min_iops': '50IOPS', + 'expected_iops': '150IOPS/TB', + 'peak_iops': '220IOPS/TB', + 'peak_iops_allocation': 'used_space', + 'hostname': 'test', + 'username': 'test_user', + 'password': 'test_pass!', + 'https': 'False' + } + + def get_policy_group_mock_object(self, kind=None): + """ + Helper method to return an na_ontap_volume object + :param kind: passes this param to MockONTAPConnection() + :return: na_ontap_volume object + """ + policy_obj = qos_policy_group_module() + policy_obj.autosupport_log = Mock(return_value=None) + policy_obj.cluster = Mock() + policy_obj.cluster.invoke_successfully = Mock() + if kind is None: + policy_obj.server = MockONTAPConnection() + else: + policy_obj.server = MockONTAPConnection(kind=kind, data=self.mock_policy_group) + return policy_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({}) + qos_policy_group_module() + print('Info: %s' % exc.value.args[0]['msg']) + + def test_get_nonexistent_policy(self): + ''' Test if get_policy_group returns None for non-existent policy_group ''' + set_module_args(self.mock_args()) + result = self.get_policy_group_mock_object().get_policy_group() + assert result is None + + def test_get_existing_policy_group(self): + ''' Test if get_policy_group returns details for existing policy_group ''' + set_module_args(self.mock_args()) + result = self.get_policy_group_mock_object('policy').get_policy_group() + assert result['name'] == self.mock_policy_group['name'] + assert result['vserver'] == self.mock_policy_group['vserver'] + + def test_create_error_missing_param(self): + ''' Test if create throws an error if name is not specified''' + data = self.mock_args() + del data['name'] + set_module_args(data) + with pytest.raises(AnsibleFailJson) as exc: + self.get_policy_group_mock_object('policy').create_policy_group() + msg = 'missing required arguments: name' + assert exc.value.args[0]['msg'] == msg + + def test_successful_create(self): + ''' Test successful create ''' + data = self.mock_args() + set_module_args(data) + with pytest.raises(AnsibleExitJson) as exc: + self.get_policy_group_mock_object().apply() + assert exc.value.args[0]['changed'] + + def test_create_idempotency(self): + ''' Test create idempotency ''' + set_module_args(self.mock_args()) + obj = self.get_policy_group_mock_object('policy') + with pytest.raises(AnsibleExitJson) as exc: + obj.apply() + assert not exc.value.args[0]['changed'] + + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_qos_adaptive_policy_group.NetAppOntapAdaptiveQosPolicyGroup.get_policy_group') + def test_create_error(self, get_policy_group): + ''' Test create error ''' + set_module_args(self.mock_args()) + get_policy_group.side_effect = [ + None + ] + with pytest.raises(AnsibleFailJson) as exc: + self.get_policy_group_mock_object('error').apply() + assert exc.value.args[0]['msg'] == 'Error creating adaptive qos policy group policy_1: NetApp API failed. Reason - test:error' + + def test_successful_delete(self): + ''' Test delete existing volume ''' + data = self.mock_args() + data['state'] = 'absent' + set_module_args(data) + with pytest.raises(AnsibleExitJson) as exc: + self.get_policy_group_mock_object('policy').apply() + assert exc.value.args[0]['changed'] + + def test_delete_idempotency(self): + ''' Test delete idempotency ''' + data = self.mock_args() + data['state'] = 'absent' + set_module_args(data) + with pytest.raises(AnsibleExitJson) as exc: + self.get_policy_group_mock_object().apply() + assert not exc.value.args[0]['changed'] + + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_qos_adaptive_policy_group.NetAppOntapAdaptiveQosPolicyGroup.get_policy_group') + def test_delete_error(self, get_policy_group): + ''' Test create idempotency''' + data = self.mock_args() + data['state'] = 'absent' + set_module_args(data) + current = { + 'absolute_min_iops': '50IOPS', + 'expected_iops': '150IOPS/TB', + 'peak_iops': '220IOPS/TB', + 'peak_iops_allocation': 'used_space', + 'name': 'policy_1', + 'vserver': 'policy_vserver' + } + get_policy_group.side_effect = [ + current + ] + with pytest.raises(AnsibleFailJson) as exc: + self.get_policy_group_mock_object('error').apply() + assert exc.value.args[0]['msg'] == 'Error deleting adaptive qos policy group policy_1: NetApp API failed. Reason - test:error' + + def test_successful_modify_expected_iops(self): + ''' Test successful modify expected iops ''' + data = self.mock_args() + data['expected_iops'] = '175IOPS' + set_module_args(data) + with pytest.raises(AnsibleExitJson) as exc: + self.get_policy_group_mock_object('policy').apply() + assert exc.value.args[0]['changed'] + + def test_modify_expected_iops_idempotency(self): + ''' Test modify idempotency ''' + data = self.mock_args() + set_module_args(data) + with pytest.raises(AnsibleExitJson) as exc: + self.get_policy_group_mock_object('policy').apply() + assert not exc.value.args[0]['changed'] + + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_qos_adaptive_policy_group.NetAppOntapAdaptiveQosPolicyGroup.get_policy_group') + def test_modify_error(self, get_policy_group): + ''' Test create idempotency ''' + data = self.mock_args() + data['expected_iops'] = '175IOPS' + set_module_args(data) + current = { + 'absolute_min_iops': '50IOPS', + 'expected_iops': '150IOPS/TB', + 'peak_iops': '220IOPS/TB', + 'peak_iops_allocation': 'used_space', + 'name': 'policy_1', + 'vserver': 'policy_vserver' + } + get_policy_group.side_effect = [ + current + ] + with pytest.raises(AnsibleFailJson) as exc: + self.get_policy_group_mock_object('error').apply() + assert exc.value.args[0]['msg'] == 'Error modifying adaptive qos policy group policy_1: NetApp API failed. Reason - test:error' + + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_qos_adaptive_policy_group.NetAppOntapAdaptiveQosPolicyGroup.get_policy_group') + def test_rename(self, get_policy_group): + ''' Test rename idempotency ''' + data = self.mock_args() + data['name'] = 'policy_2' + data['from_name'] = 'policy_1' + set_module_args(data) + current = { + 'absolute_min_iops': '50IOPS', + 'expected_iops': '150IOPS/TB', + 'peak_iops': '220IOPS/TB', + 'peak_iops_allocation': 'used_space', + 'name': 'policy_1', + 'vserver': 'policy_vserver' + } + get_policy_group.side_effect = [ + None, + current + ] + with pytest.raises(AnsibleExitJson) as exc: + self.get_policy_group_mock_object('policy').apply() + assert exc.value.args[0]['changed'] + + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_qos_adaptive_policy_group.NetAppOntapAdaptiveQosPolicyGroup.get_policy_group') + def test_rename_idempotency(self, get_policy_group): + ''' Test rename idempotency ''' + data = self.mock_args() + data['name'] = 'policy_1' + data['from_name'] = 'policy_1' + current = { + 'absolute_min_iops': '50IOPS', + 'expected_iops': '150IOPS/TB', + 'peak_iops': '220IOPS/TB', + 'peak_iops_allocation': 'used_space', + 'name': 'policy_1', + 'vserver': 'policy_vserver' + } + get_policy_group.side_effect = [ + current, + current + ] + set_module_args(data) + with pytest.raises(AnsibleExitJson) as exc: + self.get_policy_group_mock_object('policy').apply() + assert not exc.value.args[0]['changed'] + + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_qos_adaptive_policy_group.NetAppOntapAdaptiveQosPolicyGroup.get_policy_group') + def test_rename_error(self, get_policy_group): + ''' Test create idempotency ''' + data = self.mock_args() + data['from_name'] = 'policy_1' + data['name'] = 'policy_2' + set_module_args(data) + current = { + 'absolute_min_iops': '50IOPS', + 'expected_iops': '150IOPS/TB', + 'peak_iops': '220IOPS/TB', + 'peak_iops_allocation': 'used_space', + 'is_shared': 'true', + 'name': 'policy_1', + 'vserver': 'policy_vserver' + } + get_policy_group.side_effect = [ + None, + current + ] + with pytest.raises(AnsibleFailJson) as exc: + self.get_policy_group_mock_object('error').apply() + assert exc.value.args[0]['msg'] == 'Error renaming adaptive qos policy group policy_1: NetApp API failed. Reason - test:error' diff --git a/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_qos_policy_group.py b/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_qos_policy_group.py new file mode 100644 index 00000000..295f9070 --- /dev/null +++ b/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_qos_policy_group.py @@ -0,0 +1,340 @@ +# (c) 2018, NetApp, Inc +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +''' unit test template for ONTAP Ansible module ''' + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type +import json +import pytest + +from ansible.module_utils import basic +from ansible.module_utils._text import to_bytes +from ansible_collections.netapp.ontap.tests.unit.compat import unittest +from ansible_collections.netapp.ontap.tests.unit.compat.mock import patch, Mock +import ansible_collections.netapp.ontap.plugins.module_utils.netapp as netapp_utils + +from ansible_collections.netapp.ontap.plugins.modules.na_ontap_qos_policy_group \ + import NetAppOntapQosPolicyGroup as qos_policy_group_module # module under test + +if not netapp_utils.has_netapp_lib(): + pytestmark = pytest.mark.skip('skipping as missing required netapp_lib') + + +def set_module_args(args): + """prepare arguments so that they will be picked up during module creation""" + args = json.dumps({'ANSIBLE_MODULE_ARGS': args}) + basic._ANSIBLE_ARGS = to_bytes(args) # pylint: disable=protected-access + + +class AnsibleExitJson(Exception): + """Exception class to be raised by module.exit_json and caught by the test case""" + + +class AnsibleFailJson(Exception): + """Exception class to be raised by module.fail_json and caught by the test case""" + + +def exit_json(*args, **kwargs): # pylint: disable=unused-argument + """function to patch over exit_json; package return data into an exception""" + if 'changed' not in kwargs: + kwargs['changed'] = False + raise AnsibleExitJson(kwargs) + + +def fail_json(*args, **kwargs): # pylint: disable=unused-argument + """function to patch over fail_json; package return data into an exception""" + kwargs['failed'] = True + raise AnsibleFailJson(kwargs) + + +class MockONTAPConnection(object): + ''' mock server connection to ONTAP host ''' + + def __init__(self, kind=None, data=None): + ''' save arguments ''' + self.kind = kind + self.params = data + self.xml_in = None + self.xml_out = None + + def invoke_successfully(self, xml, enable_tunneling): # pylint: disable=unused-argument + ''' mock invoke_successfully returning xml data ''' + self.xml_in = xml + if self.kind == 'policy': + xml = self.build_policy_group_info(self.params) + if self.kind == 'error': + error = netapp_utils.zapi.NaApiError('test', 'error') + raise error + self.xml_out = xml + return xml + + @staticmethod + def build_policy_group_info(vol_details): + ''' build xml data for volume-attributes ''' + xml = netapp_utils.zapi.NaElement('xml') + attributes = { + 'num-records': 1, + 'attributes-list': { + 'qos-policy-group-info': { + 'is-shared': 'true', + 'max-throughput': '800KB/s,800IOPS', + 'min-throughput': '100IOPS', + 'num-workloads': 0, + 'pgid': 8690, + 'policy-group': vol_details['name'], + 'vserver': vol_details['vserver'] + } + } + } + xml.translate_struct(attributes) + return xml + + +class TestMyModule(unittest.TestCase): + ''' a group of related Unit Tests ''' + + def setUp(self): + self.mock_module_helper = patch.multiple(basic.AnsibleModule, + exit_json=exit_json, + fail_json=fail_json) + self.mock_module_helper.start() + self.addCleanup(self.mock_module_helper.stop) + self.mock_policy_group = { + 'name': 'policy_1', + 'vserver': 'policy_vserver', + 'max_throughput': '800KB/s,800IOPS', + 'min_throughput': '100IOPS' + } + + def mock_args(self): + return { + 'name': self.mock_policy_group['name'], + 'vserver': self.mock_policy_group['vserver'], + 'max_throughput': '800KB/s,800IOPS', + 'min_throughput': '100IOPS', + 'hostname': 'test', + 'username': 'test_user', + 'password': 'test_pass!', + 'https': 'False' + } + + def get_policy_group_mock_object(self, kind=None): + """ + Helper method to return an na_ontap_volume object + :param kind: passes this param to MockONTAPConnection() + :return: na_ontap_volume object + """ + policy_obj = qos_policy_group_module() + policy_obj.asup_log_for_cserver = Mock(return_value=None) + policy_obj.cluster = Mock() + policy_obj.cluster.invoke_successfully = Mock() + if kind is None: + policy_obj.server = MockONTAPConnection() + else: + policy_obj.server = MockONTAPConnection(kind=kind, data=self.mock_policy_group) + return policy_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({}) + qos_policy_group_module() + print('Info: %s' % exc.value.args[0]['msg']) + + def test_get_nonexistent_policy(self): + ''' Test if get_policy_group returns None for non-existent policy_group ''' + set_module_args(self.mock_args()) + result = self.get_policy_group_mock_object().get_policy_group() + assert result is None + + def test_get_existing_policy_group(self): + ''' Test if get_policy_group returns details for existing policy_group ''' + set_module_args(self.mock_args()) + result = self.get_policy_group_mock_object('policy').get_policy_group() + assert result['name'] == self.mock_policy_group['name'] + assert result['vserver'] == self.mock_policy_group['vserver'] + + def test_create_error_missing_param(self): + ''' Test if create throws an error if name is not specified''' + data = self.mock_args() + del data['name'] + set_module_args(data) + with pytest.raises(AnsibleFailJson) as exc: + self.get_policy_group_mock_object('policy').create_policy_group() + msg = 'missing required arguments: name' + assert exc.value.args[0]['msg'] == msg + + def test_successful_create(self): + ''' Test successful create ''' + data = self.mock_args() + set_module_args(data) + with pytest.raises(AnsibleExitJson) as exc: + self.get_policy_group_mock_object().apply() + assert exc.value.args[0]['changed'] + + def test_create_idempotency(self): + ''' Test create idempotency ''' + set_module_args(self.mock_args()) + obj = self.get_policy_group_mock_object('policy') + with pytest.raises(AnsibleExitJson) as exc: + obj.apply() + assert not exc.value.args[0]['changed'] + + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_qos_policy_group.NetAppOntapQosPolicyGroup.get_policy_group') + def test_create_error(self, get_policy_group): + ''' Test create error ''' + set_module_args(self.mock_args()) + get_policy_group.side_effect = [ + None + ] + with pytest.raises(AnsibleFailJson) as exc: + self.get_policy_group_mock_object('error').apply() + assert exc.value.args[0]['msg'] == 'Error creating qos policy group policy_1: NetApp API failed. Reason - test:error' + + def test_successful_delete(self): + ''' Test delete existing volume ''' + data = self.mock_args() + data['state'] = 'absent' + set_module_args(data) + with pytest.raises(AnsibleExitJson) as exc: + self.get_policy_group_mock_object('policy').apply() + assert exc.value.args[0]['changed'] + + def test_delete_idempotency(self): + ''' Test delete idempotency ''' + data = self.mock_args() + data['state'] = 'absent' + set_module_args(data) + with pytest.raises(AnsibleExitJson) as exc: + self.get_policy_group_mock_object().apply() + assert not exc.value.args[0]['changed'] + + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_qos_policy_group.NetAppOntapQosPolicyGroup.get_policy_group') + def test_delete_error(self, get_policy_group): + ''' Test create idempotency ''' + data = self.mock_args() + data['state'] = 'absent' + set_module_args(data) + current = { + 'max_throughput': '800KB/s,800IOPS', + 'min_throughput': '100IOPS', + 'name': 'policy_1', + 'vserver': 'policy_vserver' + } + get_policy_group.side_effect = [ + current + ] + with pytest.raises(AnsibleFailJson) as exc: + self.get_policy_group_mock_object('error').apply() + assert exc.value.args[0]['msg'] == 'Error deleting qos policy group policy_1: NetApp API failed. Reason - test:error' + + def test_successful_modify_max_throughput(self): + ''' Test successful modify max throughput ''' + data = self.mock_args() + data['max_throughput'] = '900KB/s,800iops' + set_module_args(data) + with pytest.raises(AnsibleExitJson) as exc: + self.get_policy_group_mock_object('policy').apply() + assert exc.value.args[0]['changed'] + + def test_modify_max_throughput_idempotency(self): + ''' Test modify idempotency ''' + data = self.mock_args() + set_module_args(data) + with pytest.raises(AnsibleExitJson) as exc: + self.get_policy_group_mock_object('policy').apply() + assert not exc.value.args[0]['changed'] + + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_qos_policy_group.NetAppOntapQosPolicyGroup.get_policy_group') + def test_modify_error(self, get_policy_group): + ''' Test create idempotency ''' + data = self.mock_args() + data['max_throughput'] = '900KB/s,900IOPS' + set_module_args(data) + current = { + 'max_throughput': '800KB/s,800IOPS', + 'min_throughput': '100IOPS', + 'name': 'policy_1', + 'vserver': 'policy_vserver' + } + get_policy_group.side_effect = [ + current + ] + with pytest.raises(AnsibleFailJson) as exc: + self.get_policy_group_mock_object('error').apply() + assert exc.value.args[0]['msg'] == 'Error modifying qos policy group policy_1: NetApp API failed. Reason - test:error' + + def test_modify_is_shared_error(self): + ''' Test create idempotency ''' + data = self.mock_args() + data['max_throughput'] = '900KB/s,900IOPS' + data['is_shared'] = False + set_module_args(data) + with pytest.raises(AnsibleFailJson) as exc: + self.get_policy_group_mock_object('policy').apply() + assert exc.value.args[0]['msg'] == 'Error cannot modify is_shared attribute.' + + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_qos_policy_group.NetAppOntapQosPolicyGroup.get_policy_group') + def test_rename(self, get_policy_group): + ''' Test rename idempotency ''' + data = self.mock_args() + data['name'] = 'policy_2' + data['from_name'] = 'policy_1' + set_module_args(data) + current = { + 'max_throughput': '800KB/s,800IOPS', + 'min_throughput': '100IOPS', + 'name': 'policy_1', + 'vserver': 'policy_vserver' + } + get_policy_group.side_effect = [ + None, + current + ] + with pytest.raises(AnsibleExitJson) as exc: + self.get_policy_group_mock_object('policy').apply() + assert exc.value.args[0]['changed'] + + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_qos_policy_group.NetAppOntapQosPolicyGroup.get_policy_group') + def test_rename_idempotency(self, get_policy_group): + ''' Test rename idempotency ''' + data = self.mock_args() + data['name'] = 'policy_1' + data['from_name'] = 'policy_1' + current = { + 'max_throughput': '800KB/s,800IOPS', + 'min_throughput': '100IOPS', + 'name': 'policy_1', + 'vserver': 'policy_vserver' + } + get_policy_group.side_effect = [ + current, + current + ] + set_module_args(data) + with pytest.raises(AnsibleExitJson) as exc: + self.get_policy_group_mock_object('policy').apply() + assert not exc.value.args[0]['changed'] + + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_qos_policy_group.NetAppOntapQosPolicyGroup.get_policy_group') + def test_rename_error(self, get_policy_group): + ''' Test create idempotency ''' + data = self.mock_args() + data['from_name'] = 'policy_1' + data['name'] = 'policy_2' + set_module_args(data) + current = { + 'is_shared': 'true', + 'max_throughput': '800KB/s,800IOPS', + 'min_throughput': '100IOPS', + 'name': 'policy_1', + 'vserver': 'policy_vserver' + } + get_policy_group.side_effect = [ + None, + current + ] + with pytest.raises(AnsibleFailJson) as exc: + self.get_policy_group_mock_object('error').apply() + assert exc.value.args[0]['msg'] == 'Error renaming qos policy group policy_1: NetApp API failed. Reason - test:error' diff --git a/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_qtree.py b/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_qtree.py new file mode 100644 index 00000000..700dd251 --- /dev/null +++ b/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_qtree.py @@ -0,0 +1,464 @@ +''' unit tests ONTAP Ansible module: na_ontap_quotas ''' +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type +import json +import pytest + +from ansible.module_utils import basic +from ansible.module_utils._text import to_bytes +from ansible_collections.netapp.ontap.tests.unit.compat import unittest +from ansible_collections.netapp.ontap.tests.unit.compat.mock import patch +import ansible_collections.netapp.ontap.plugins.module_utils.netapp as netapp_utils + +from ansible_collections.netapp.ontap.plugins.modules.na_ontap_qtree \ + import NetAppOntapQTree as qtree_module # module under test + +if not netapp_utils.has_netapp_lib(): + pytestmark = pytest.mark.skip('skipping as missing required netapp_lib') + +# REST API canned responses when mocking send_request +SRR = { + # common responses + 'is_rest': (200, {}, None), + 'is_zapi': (400, {}, "Unreachable"), + 'empty_good': (200, {}, None), + 'end_of_sequence': (500, None, "Ooops, the UT needs one more SRR response"), + 'generic_error': (400, None, "Expected error"), + # module specific responses + 'qtree_record': (200, + {"records": [{"svm": {"uuid": "09e9fd5e-8ebd-11e9-b162-005056b39fe7", + "name": "ansibleSVM"}, + "id": 1, + "name": "string", + "security_style": "unix", + "unix_permissions": "abc", + "export_policy": {"name": "ansible"}, + "volume": {"name": "volume1", + "uuid": "028baa66-41bd-11e9-81d5-00a0986138f7"}}]}, None) +} + + +def set_module_args(args): + """prepare arguments so that they will be picked up during module creation""" + args = json.dumps({'ANSIBLE_MODULE_ARGS': args}) + basic._ANSIBLE_ARGS = to_bytes(args) # pylint: disable=protected-access + + +class AnsibleExitJson(Exception): + """Exception class to be raised by module.exit_json and caught by the test case""" + + +class AnsibleFailJson(Exception): + """Exception class to be raised by module.fail_json and caught by the test case""" + + +def exit_json(*args, **kwargs): # pylint: disable=unused-argument + """function to patch over exit_json; package return data into an exception""" + if 'changed' not in kwargs: + kwargs['changed'] = False + raise AnsibleExitJson(kwargs) + + +def fail_json(*args, **kwargs): # pylint: disable=unused-argument + """function to patch over fail_json; package return data into an exception""" + kwargs['failed'] = True + raise AnsibleFailJson(kwargs) + + +class MockONTAPConnection(object): + ''' mock server connection to ONTAP host ''' + + def __init__(self, kind=None): + ''' save arguments ''' + self.type = kind + self.xml_in = None + self.xml_out = None + + def invoke_successfully(self, xml, enable_tunneling): # pylint: disable=unused-argument + ''' mock invoke_successfully returning xml data ''' + self.xml_in = xml + if self.type == 'qtree': + xml = self.build_quota_info() + elif self.type == 'qtree_fail': + raise netapp_utils.zapi.NaApiError(code='TEST', message="This exception is from the unit test") + self.xml_out = xml + return xml + + @staticmethod + def build_quota_info(): + ''' build xml data for quota-entry ''' + xml = netapp_utils.zapi.NaElement('xml') + data = {'num-records': 1, + 'attributes-list': {'qtree-info': {'export-policy': 'ansible', 'vserver': 'ansible', 'qtree': 'ansible', + 'oplocks': 'enabled', 'security-style': 'unix', 'mode': 'abc', + 'volume': 'ansible'}}} + xml.translate_struct(data) + return xml + + +class TestMyModule(unittest.TestCase): + ''' a group of related Unit Tests ''' + + def setUp(self): + self.mock_module_helper = patch.multiple(basic.AnsibleModule, + exit_json=exit_json, + fail_json=fail_json) + self.mock_module_helper.start() + self.addCleanup(self.mock_module_helper.stop) + self.server = MockONTAPConnection() + self.onbox = False + + def set_default_args(self, use_rest=None): + if self.onbox: + hostname = '10.10.10.10' + username = 'username' + password = 'password' + name = 'ansible' + vserver = 'ansible' + flexvol_name = 'ansible' + export_policy = 'ansible' + security_style = 'unix' + mode = 'abc' + else: + hostname = '10.10.10.10' + username = 'username' + password = 'password' + name = 'ansible' + vserver = 'ansible' + flexvol_name = 'ansible' + export_policy = 'ansible' + security_style = 'unix' + mode = 'abc' + + args = dict({ + 'state': 'present', + 'hostname': hostname, + 'username': username, + 'password': password, + 'name': name, + 'vserver': vserver, + 'flexvol_name': flexvol_name, + 'export_policy': export_policy, + 'security_style': security_style, + 'unix_permissions': mode + }) + + if use_rest is not None: + args['use_rest'] = use_rest + + return args + + @staticmethod + def get_qtree_mock_object(cx_type='zapi', kind=None): + qtree_obj = qtree_module() + if cx_type == 'zapi': + if kind is None: + qtree_obj.server = MockONTAPConnection() + else: + qtree_obj.server = MockONTAPConnection(kind=kind) + return qtree_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({}) + qtree_module() + print('Info: %s' % exc.value.args[0]['msg']) + + def test_ensure_get_called(self): + ''' test get_qtree for non-existent qtree''' + set_module_args(self.set_default_args(use_rest='Never')) + print('starting') + my_obj = qtree_module() + print('use_rest:', my_obj.use_rest) + my_obj.server = self.server + assert my_obj.get_qtree is not None + + def test_ensure_get_called_existing(self): + ''' test get_qtree for existing qtree''' + set_module_args(self.set_default_args(use_rest='Never')) + my_obj = qtree_module() + my_obj.server = MockONTAPConnection(kind='qtree') + assert my_obj.get_qtree() + + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_qtree.NetAppOntapQTree.create_qtree') + def test_successful_create(self, create_qtree): + ''' creating qtree and testing idempotency ''' + set_module_args(self.set_default_args(use_rest='Never')) + my_obj = qtree_module() + if not self.onbox: + my_obj.server = self.server + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + assert exc.value.args[0]['changed'] + create_qtree.assert_called_with() + # to reset na_helper from remembering the previous 'changed' value + set_module_args(self.set_default_args(use_rest='Never')) + my_obj = qtree_module() + if not self.onbox: + my_obj.server = MockONTAPConnection('qtree') + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + assert not exc.value.args[0]['changed'] + + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_qtree.NetAppOntapQTree.delete_qtree') + def test_successful_delete(self, delete_qtree): + ''' deleting qtree and testing idempotency ''' + data = self.set_default_args(use_rest='Never') + data['state'] = 'absent' + set_module_args(data) + my_obj = qtree_module() + if not self.onbox: + my_obj.server = MockONTAPConnection('qtree') + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + assert exc.value.args[0]['changed'] + # delete_qtree.assert_called_with() + # to reset na_helper from remembering the previous 'changed' value + my_obj = qtree_module() + if not self.onbox: + my_obj.server = self.server + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + assert not exc.value.args[0]['changed'] + + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_qtree.NetAppOntapQTree.modify_qtree') + def test_successful_modify(self, modify_qtree): + ''' modifying qtree and testing idempotency ''' + data = self.set_default_args(use_rest='Never') + data['export_policy'] = 'test' + set_module_args(data) + my_obj = qtree_module() + if not self.onbox: + my_obj.server = MockONTAPConnection('qtree') + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + assert exc.value.args[0]['changed'] + # modify_qtree.assert_called_with() + # to reset na_helper from remembering the previous 'changed' value + data['export_policy'] = 'ansible' + set_module_args(data) + my_obj = qtree_module() + if not self.onbox: + my_obj.server = MockONTAPConnection('qtree') + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + assert not exc.value.args[0]['changed'] + + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_qtree.NetAppOntapQTree.get_qtree') + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_qtree.NetAppOntapQTree.rename_qtree') + def test_failed_rename(self, rename_qtree, get_qtree): + ''' creating qtree and testing idempotency ''' + get_qtree.side_effect = [None, None] + data = self.set_default_args(use_rest='Never') + data['from_name'] = 'ansible_old' + set_module_args(data) + my_obj = qtree_module() + if not self.onbox: + my_obj.server = self.server + with pytest.raises(AnsibleFailJson) as exc: + my_obj.apply() + msg = 'Error renaming: qtree %s does not exist' % data['from_name'] + assert msg in exc.value.args[0]['msg'] + + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_qtree.NetAppOntapQTree.get_qtree') + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_qtree.NetAppOntapQTree.rename_qtree') + def test_successful_rename(self, rename_qtree, get_qtree): + ''' creating qtree and testing idempotency ''' + data = self.set_default_args(use_rest='Never') + data['from_name'] = 'ansible_old' + qtree = dict( + security_style=data['security_style'], + unix_permissions=data['unix_permissions'], + export_policy=data['export_policy'] + ) + get_qtree.side_effect = [None, qtree] + set_module_args(data) + my_obj = qtree_module() + if not self.onbox: + my_obj.server = self.server + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + assert exc.value.args[0]['changed'] + rename_qtree.assert_called_with(qtree) + # Idempotency + get_qtree.side_effect = [qtree, 'whatever'] + my_obj = qtree_module() + if not self.onbox: + my_obj.server = MockONTAPConnection('qtree') + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + assert not exc.value.args[0]['changed'] + + def test_if_all_methods_catch_exception(self): + data = self.set_default_args(use_rest='Never') + data['from_name'] = 'ansible' + set_module_args(data) + my_obj = qtree_module() + if not self.onbox: + my_obj.server = MockONTAPConnection('qtree_fail') + with pytest.raises(AnsibleFailJson) as exc: + my_obj.create_qtree() + assert 'Error provisioning qtree ' in exc.value.args[0]['msg'] + with pytest.raises(AnsibleFailJson) as exc: + my_obj.delete_qtree(self.get_qtree_mock_object()) + assert 'Error deleting qtree ' in exc.value.args[0]['msg'] + with pytest.raises(AnsibleFailJson) as exc: + my_obj.modify_qtree(self.get_qtree_mock_object()) + assert 'Error modifying qtree ' in exc.value.args[0]['msg'] + with pytest.raises(AnsibleFailJson) as exc: + my_obj.rename_qtree(self.get_qtree_mock_object()) + assert 'Error renaming qtree ' in exc.value.args[0]['msg'] + + @patch('ansible_collections.netapp.ontap.plugins.module_utils.netapp.OntapRestAPI.send_request') + def test_rest_error(self, mock_request): + data = self.set_default_args() + set_module_args(data) + mock_request.side_effect = [ + SRR['is_rest'], + SRR['generic_error'], + SRR['end_of_sequence'] + ] + with pytest.raises(AnsibleFailJson) as exc: + self.get_qtree_mock_object(cx_type='rest').apply() + assert exc.value.args[0]['msg'] == SRR['generic_error'][2] + + @patch('ansible_collections.netapp.ontap.plugins.module_utils.netapp.OntapRestAPI.send_request') + def test_successful_create_rest(self, mock_request): + data = self.set_default_args() + set_module_args(data) + mock_request.side_effect = [ + SRR['is_rest'], + SRR['empty_good'], # get + SRR['empty_good'], # post + SRR['end_of_sequence'] + ] + with pytest.raises(AnsibleExitJson) as exc: + self.get_qtree_mock_object(cx_type='rest').apply() + assert exc.value.args[0]['changed'] + + @patch('ansible_collections.netapp.ontap.plugins.module_utils.netapp.OntapRestAPI.send_request') + def test_idempotent_create_rest(self, mock_request): + data = self.set_default_args() + set_module_args(data) + mock_request.side_effect = [ + SRR['is_rest'], + SRR['qtree_record'], # get + SRR['end_of_sequence'] + ] + with pytest.raises(AnsibleExitJson) as exc: + self.get_qtree_mock_object(cx_type='rest').apply() + assert not exc.value.args[0]['changed'] + + @patch('ansible_collections.netapp.ontap.plugins.module_utils.netapp.OntapRestAPI.send_request') + def test_successful_delete_rest(self, mock_request): + data = self.set_default_args() + data['state'] = 'absent' + set_module_args(data) + mock_request.side_effect = [ + SRR['is_rest'], + SRR['qtree_record'], # get + SRR['empty_good'], # delete + SRR['end_of_sequence'] + ] + with pytest.raises(AnsibleExitJson) as exc: + self.get_qtree_mock_object(cx_type='rest').apply() + assert exc.value.args[0]['changed'] + + @patch('ansible_collections.netapp.ontap.plugins.module_utils.netapp.OntapRestAPI.send_request') + def test_idempotent_delete_rest(self, mock_request): + data = self.set_default_args() + data['state'] = 'absent' + set_module_args(data) + mock_request.side_effect = [ + SRR['is_rest'], + SRR['empty_good'], # get + SRR['end_of_sequence'] + ] + with pytest.raises(AnsibleExitJson) as exc: + self.get_qtree_mock_object(cx_type='rest').apply() + assert not exc.value.args[0]['changed'] + + @patch('ansible_collections.netapp.ontap.plugins.module_utils.netapp.OntapRestAPI.send_request') + def test_successful_modify_rest(self, mock_request): + data = self.set_default_args() + data['state'] = 'present' + data['unix_permissions'] = 'abcde' + set_module_args(data) + mock_request.side_effect = [ + SRR['is_rest'], + SRR['qtree_record'], # get + SRR['empty_good'], # patch + SRR['end_of_sequence'] + ] + with pytest.raises(AnsibleExitJson) as exc: + self.get_qtree_mock_object(cx_type='rest').apply() + assert exc.value.args[0]['changed'] + + @patch('ansible_collections.netapp.ontap.plugins.module_utils.netapp.OntapRestAPI.send_request') + def test_idempotent_modify_rest(self, mock_request): + data = self.set_default_args() + data['state'] = 'present' + set_module_args(data) + mock_request.side_effect = [ + SRR['is_rest'], + SRR['qtree_record'], # get + SRR['end_of_sequence'] + ] + with pytest.raises(AnsibleExitJson) as exc: + self.get_qtree_mock_object(cx_type='rest').apply() + assert not exc.value.args[0]['changed'] + + @patch('ansible_collections.netapp.ontap.plugins.module_utils.netapp.OntapRestAPI.send_request') + def test_successful_rename_rest(self, mock_request): + data = self.set_default_args() + data['state'] = 'present' + data['from_name'] = 'abcde' + # data['unix_permissions'] = 'abcde' + set_module_args(data) + mock_request.side_effect = [ + SRR['is_rest'], + SRR['empty_good'], # get (current) + SRR['qtree_record'], # get (from) + SRR['empty_good'], # patch + SRR['end_of_sequence'] + ] + with pytest.raises(AnsibleExitJson) as exc: + self.get_qtree_mock_object(cx_type='rest').apply() + assert exc.value.args[0]['changed'] + + @patch('ansible_collections.netapp.ontap.plugins.module_utils.netapp.OntapRestAPI.send_request') + def test_successful_rename_rest_idempotent(self, mock_request): + data = self.set_default_args() + data['state'] = 'present' + data['from_name'] = 'abcde' + # data['unix_permissions'] = 'abcde' + set_module_args(data) + mock_request.side_effect = [ + SRR['is_rest'], + SRR['qtree_record'], # get (current exists) + SRR['empty_good'], # patch + SRR['end_of_sequence'] + ] + with pytest.raises(AnsibleExitJson) as exc: + self.get_qtree_mock_object(cx_type='rest').apply() + assert not exc.value.args[0]['changed'] + + @patch('ansible_collections.netapp.ontap.plugins.module_utils.netapp.OntapRestAPI.send_request') + def test_successful_rename_and_modify_rest(self, mock_request): + data = self.set_default_args() + data['state'] = 'present' + data['from_name'] = 'abcde' + data['unix_permissions'] = 'abcde' + set_module_args(data) + mock_request.side_effect = [ + SRR['is_rest'], + SRR['empty_good'], # get (current) + SRR['qtree_record'], # get (from) + SRR['empty_good'], # patch (rename) + SRR['empty_good'], # patch (modify) + SRR['end_of_sequence'] + ] + with pytest.raises(AnsibleExitJson) as exc: + self.get_qtree_mock_object(cx_type='rest').apply() + assert exc.value.args[0]['changed'] diff --git a/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_quota_policy.py b/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_quota_policy.py new file mode 100644 index 00000000..55f59ae9 --- /dev/null +++ b/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_quota_policy.py @@ -0,0 +1,207 @@ +# (c) 2019, NetApp, Inc +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +''' unit tests for Ansible module: na_ontap_quota_policy ''' + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type +import json +import pytest + +from ansible.module_utils import basic +from ansible.module_utils._text import to_bytes +from ansible_collections.netapp.ontap.tests.unit.compat import unittest +from ansible_collections.netapp.ontap.tests.unit.compat.mock import patch, Mock +import ansible_collections.netapp.ontap.plugins.module_utils.netapp as netapp_utils + +from ansible_collections.netapp.ontap.plugins.modules.na_ontap_quota_policy \ + import NetAppOntapQuotaPolicy as quota_policy_module # module under test + +if not netapp_utils.has_netapp_lib(): + pytestmark = pytest.mark.skip('skipping as missing required netapp_lib') + + +def set_module_args(args): + """prepare arguments so that they will be picked up during module creation""" + args = json.dumps({'ANSIBLE_MODULE_ARGS': args}) + basic._ANSIBLE_ARGS = to_bytes(args) # pylint: disable=protected-access + + +class AnsibleExitJson(Exception): + """Exception class to be raised by module.exit_json and caught by the test case""" + + +class AnsibleFailJson(Exception): + """Exception class to be raised by module.fail_json and caught by the test case""" + + +def exit_json(*args, **kwargs): # pylint: disable=unused-argument + """function to patch over exit_json; package return data into an exception""" + if 'changed' not in kwargs: + kwargs['changed'] = False + raise AnsibleExitJson(kwargs) + + +def fail_json(*args, **kwargs): # pylint: disable=unused-argument + """function to patch over fail_json; package return data into an exception""" + kwargs['failed'] = True + raise AnsibleFailJson(kwargs) + + +class MockONTAPConnection(object): + ''' mock server connection to ONTAP host ''' + + def __init__(self, kind=None, data=None): + ''' save arguments ''' + self.kind = kind + self.params = data + self.xml_in = None + self.xml_out = None + + def invoke_successfully(self, xml, enable_tunneling): # pylint: disable=unused-argument + ''' mock invoke_successfully returning xml data ''' + self.xml_in = xml + if self.kind == 'quota': + xml = self.build_quota_policy_info(self.params, True) + if self.kind == 'quota_not_assigned': + xml = self.build_quota_policy_info(self.params, False) + elif self.kind == 'zapi_error': + error = netapp_utils.zapi.NaApiError('test', 'error') + raise error + self.xml_out = xml + return xml + + @staticmethod + def build_quota_policy_info(params, assigned): + xml = netapp_utils.zapi.NaElement('xml') + attributes = {'num-records': 1, + 'attributes-list': { + 'quota-policy-info': { + 'policy-name': params['name']}, + 'vserver-info': { + 'quota-policy': params['name'] if assigned else 'default'} + }} + xml.translate_struct(attributes) + return xml + + +class TestMyModule(unittest.TestCase): + ''' Unit tests for na_ontap_quota_policy ''' + + def setUp(self): + self.mock_module_helper = patch.multiple(basic.AnsibleModule, + exit_json=exit_json, + fail_json=fail_json) + self.mock_module_helper.start() + self.addCleanup(self.mock_module_helper.stop) + self.mock_quota_policy = { + 'state': 'present', + 'vserver': 'test_vserver', + 'name': 'test_policy' + } + + def mock_args(self): + return { + 'state': self.mock_quota_policy['state'], + 'vserver': self.mock_quota_policy['vserver'], + 'name': self.mock_quota_policy['name'], + 'hostname': 'test', + 'username': 'test_user', + 'password': 'test_pass!' + } + + def get_quota_policy_mock_object(self, kind=None): + policy_obj = quota_policy_module() + netapp_utils.ems_log_event = Mock(return_value=None) + if kind is None: + policy_obj.server = MockONTAPConnection() + else: + policy_obj.server = MockONTAPConnection(kind=kind, data=self.mock_quota_policy) + return policy_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({}) + quota_policy_module() + print('Info: %s' % exc.value.args[0]['msg']) + + def test_successfully_create(self): + set_module_args(self.mock_args()) + with pytest.raises(AnsibleExitJson) as exc: + self.get_quota_policy_mock_object().apply() + assert exc.value.args[0]['changed'] + + def test_create_idempotency(self): + set_module_args(self.mock_args()) + with pytest.raises(AnsibleExitJson) as exc: + self.get_quota_policy_mock_object('quota').apply() + assert not exc.value.args[0]['changed'] + + def test_cannot_delete(self): + data = self.mock_args() + data['state'] = 'absent' + set_module_args(data) + with pytest.raises(AnsibleFailJson) as exc: + self.get_quota_policy_mock_object('quota').apply() + msg = 'Error policy test_policy cannot be deleted as it is assigned to the vserver test_vserver' + assert msg == exc.value.args[0]['msg'] + + def test_successfully_delete(self): + data = self.mock_args() + data['state'] = 'absent' + set_module_args(data) + with pytest.raises(AnsibleExitJson) as exc: + self.get_quota_policy_mock_object('quota_not_assigned').apply() + assert exc.value.args[0]['changed'] + + def test_delete_idempotency(self): + data = self.mock_args() + data['state'] = 'absent' + set_module_args(data) + with pytest.raises(AnsibleExitJson) as exc: + self.get_quota_policy_mock_object().apply() + assert not exc.value.args[0]['changed'] + + def test_successfully_assign(self): + data = self.mock_args() + set_module_args(data) + with pytest.raises(AnsibleExitJson) as exc: + self.get_quota_policy_mock_object('quota_not_assigned').apply() + assert exc.value.args[0]['changed'] + + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_quota_policy.NetAppOntapQuotaPolicy.get_quota_policy') + def test_successful_rename(self, get_volume): + data = self.mock_args() + data['name'] = 'new_policy' + data['from_name'] = 'test_policy' + set_module_args(data) + current = { + 'name': 'test_policy' + } + get_volume.side_effect = [ + None, + current + ] + with pytest.raises(AnsibleExitJson) as exc: + self.get_quota_policy_mock_object('quota').apply() + assert exc.value.args[0]['changed'] + + def test_error(self): + data = self.mock_args() + set_module_args(data) + with pytest.raises(AnsibleFailJson) as exc: + self.get_quota_policy_mock_object('zapi_error').get_quota_policy() + assert exc.value.args[0]['msg'] == 'Error fetching quota policy test_policy: NetApp API failed. Reason - test:error' + with pytest.raises(AnsibleFailJson) as exc: + self.get_quota_policy_mock_object('zapi_error').create_quota_policy() + assert exc.value.args[0]['msg'] == 'Error creating quota policy test_policy: NetApp API failed. Reason - test:error' + with pytest.raises(AnsibleFailJson) as exc: + self.get_quota_policy_mock_object('zapi_error').delete_quota_policy() + assert exc.value.args[0]['msg'] == 'Error deleting quota policy test_policy: NetApp API failed. Reason - test:error' + data['name'] = 'new_policy' + data['from_name'] = 'test_policy' + set_module_args(data) + with pytest.raises(AnsibleFailJson) as exc: + self.get_quota_policy_mock_object('zapi_error').rename_quota_policy() + assert exc.value.args[0]['msg'] == 'Error renaming quota policy test_policy: NetApp API failed. Reason - test:error' diff --git a/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_quotas.py b/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_quotas.py new file mode 100644 index 00000000..134269d7 --- /dev/null +++ b/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_quotas.py @@ -0,0 +1,243 @@ +''' unit tests ONTAP Ansible module: na_ontap_quotas ''' + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type +import json +import pytest + +from ansible_collections.netapp.ontap.tests.unit.compat import unittest +from ansible_collections.netapp.ontap.tests.unit.compat.mock import patch +from ansible.module_utils import basic +from ansible.module_utils._text import to_bytes +import ansible_collections.netapp.ontap.plugins.module_utils.netapp as netapp_utils + +from ansible_collections.netapp.ontap.plugins.modules.na_ontap_quotas \ + import NetAppONTAPQuotas as my_module + +if not netapp_utils.has_netapp_lib(): + pytestmark = pytest.mark.skip('skipping as missing required netapp_lib') + + +def set_module_args(args): + """prepare arguments so that they will be picked up during module creation""" + args = json.dumps({'ANSIBLE_MODULE_ARGS': args}) + basic._ANSIBLE_ARGS = to_bytes(args) # pylint: disable=protected-access + + +class AnsibleExitJson(Exception): + """Exception class to be raised by module.exit_json and caught by the test case""" + pass + + +class AnsibleFailJson(Exception): + """Exception class to be raised by module.fail_json and caught by the test case""" + pass + + +def exit_json(*args, **kwargs): # pylint: disable=unused-argument + """function to patch over exit_json; package return data into an exception""" + if 'changed' not in kwargs: + kwargs['changed'] = False + raise AnsibleExitJson(kwargs) + + +def fail_json(*args, **kwargs): # pylint: disable=unused-argument + """function to patch over fail_json; package return data into an exception""" + kwargs['failed'] = True + raise AnsibleFailJson(kwargs) + + +class MockONTAPConnection(object): + ''' mock server connection to ONTAP host ''' + + def __init__(self, kind=None): + ''' save arguments ''' + self.type = kind + self.xml_in = None + self.xml_out = None + + def invoke_successfully(self, xml, enable_tunneling): # pylint: disable=unused-argument + ''' mock invoke_successfully returning xml data ''' + self.xml_in = xml + if self.type == 'quotas': + xml = self.build_quota_info() + elif self.type == 'quota_fail': + raise netapp_utils.zapi.NaApiError(code='TEST', message="This exception is from the unit test") + self.xml_out = xml + return xml + + @staticmethod + def build_quota_info(): + ''' build xml data for quota-entry ''' + xml = netapp_utils.zapi.NaElement('xml') + data = {'num-records': 1, + 'attributes-list': {'quota-entry': {'volume': 'ansible', + 'file-limit': '-', 'disk-limit': '-', + 'soft-file-limit': '-', 'soft-disk-limit': '-', 'threshold': '-'}}, + 'status': 'true'} + xml.translate_struct(data) + return xml + + +class TestMyModule(unittest.TestCase): + ''' a group of related Unit Tests ''' + + def setUp(self): + self.mock_module_helper = patch.multiple(basic.AnsibleModule, + exit_json=exit_json, + fail_json=fail_json) + self.mock_module_helper.start() + self.addCleanup(self.mock_module_helper.stop) + self.server = MockONTAPConnection() + self.onbox = False + + def set_default_args(self): + if self.onbox: + hostname = '10.193.75.3' + username = 'admin' + password = 'netapp1!' + volume = 'ansible' + vserver = 'ansible' + policy = 'ansible' + quota_target = '/vol/ansible' + type = 'user' + else: + hostname = 'hostname' + username = 'username' + password = 'password' + volume = 'ansible' + vserver = 'ansible' + policy = 'ansible' + quota_target = '/vol/ansible' + type = 'user' + return dict({ + 'hostname': hostname, + 'username': username, + 'password': password, + 'volume': volume, + 'vserver': vserver, + 'policy': policy, + 'quota_target': quota_target, + 'type': type + }) + + def test_module_fail_when_required_args_missing(self): + ''' required arguments are reported as errors ''' + with pytest.raises(AnsibleFailJson) as exc: + set_module_args({}) + my_module() + print('Info: %s' % exc.value.args[0]['msg']) + + def test_ensure_get_called(self): + ''' test get_quota for non-existent quota''' + set_module_args(self.set_default_args()) + my_obj = my_module() + my_obj.server = self.server + assert my_obj.get_quotas is not None + + def test_ensure_get_called_existing(self): + ''' test get_quota for existing quota''' + set_module_args(self.set_default_args()) + my_obj = my_module() + my_obj.server = MockONTAPConnection(kind='quotas') + assert my_obj.get_quotas() + + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_quotas.NetAppONTAPQuotas.quota_entry_set') + def test_successful_create(self, quota_entry_set): + ''' creating quota and testing idempotency ''' + data = self.set_default_args() + data.update({'file_limit': '3', + 'disk_limit': '4', + 'soft_file_limit': '3', + 'soft_disk_limit': '4', + }) + # data['file_limit'] = '3' + # data['disk_limit'] = '4' + # data['threshold'] = '4' + set_module_args(data) + my_obj = my_module() + if not self.onbox: + my_obj.server = self.server + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + assert exc.value.args[0]['changed'] + quota_entry_set.assert_called_with() + # to reset na_helper from remembering the previous 'changed' value + set_module_args(self.set_default_args()) + my_obj = my_module() + if not self.onbox: + my_obj.server = MockONTAPConnection('quotas') + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + assert not exc.value.args[0]['changed'] + + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_quotas.NetAppONTAPQuotas.quota_entry_delete') + def test_successful_delete(self, quota_entry_delete): + ''' deleting quota and testing idempotency ''' + data = self.set_default_args() + data['state'] = 'absent' + set_module_args(data) + my_obj = my_module() + if not self.onbox: + my_obj.server = MockONTAPConnection('quotas') + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + assert exc.value.args[0]['changed'] + quota_entry_delete.assert_called_with() + # to reset na_helper from remembering the previous 'changed' value + my_obj = my_module() + if not self.onbox: + my_obj.server = self.server + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + assert not exc.value.args[0]['changed'] + + def test_successful_modify(self): + ''' modifying quota and testing idempotency ''' + data = self.set_default_args() + data['file_limit'] = '3' + set_module_args(data) + my_obj = my_module() + if not self.onbox: + my_obj.server = MockONTAPConnection('quotas') + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + assert exc.value.args[0]['changed'] + + def test_quota_on_off(self): + ''' quota set on or off ''' + data = self.set_default_args() + data['set_quota_status'] = 'false' + set_module_args(data) + my_obj = my_module() + if not self.onbox: + my_obj.server = MockONTAPConnection('quotas') + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + assert not exc.value.args[0]['changed'] + + def test_if_all_methods_catch_exception(self): + module_args = {} + module_args.update(self.set_default_args()) + set_module_args(module_args) + my_obj = my_module() + if not self.onbox: + my_obj.server = MockONTAPConnection('quota_fail') + with pytest.raises(AnsibleFailJson) as exc: + my_obj.get_quota_status() + assert 'Error fetching quotas status info' in exc.value.args[0]['msg'] + with pytest.raises(AnsibleFailJson) as exc: + my_obj.get_quotas() + assert 'Error fetching quotas info' in exc.value.args[0]['msg'] + with pytest.raises(AnsibleFailJson) as exc: + my_obj.quota_entry_set() + assert 'Error adding/modifying quota entry' in exc.value.args[0]['msg'] + with pytest.raises(AnsibleFailJson) as exc: + my_obj.quota_entry_delete() + assert 'Error deleting quota entry' in exc.value.args[0]['msg'] + with pytest.raises(AnsibleFailJson) as exc: + my_obj.quota_entry_modify(module_args) + assert 'Error modifying quota entry' in exc.value.args[0]['msg'] + with pytest.raises(AnsibleFailJson) as exc: + my_obj.on_or_off_quota('quota-on') + assert 'Error setting quota-on for ansible' in exc.value.args[0]['msg'] diff --git a/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_rest_cli.py b/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_rest_cli.py new file mode 100644 index 00000000..d145a53e --- /dev/null +++ b/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_rest_cli.py @@ -0,0 +1,137 @@ +# (c) 2019, NetApp, Inc +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +''' unit tests for Ansible module: na_ontap_rest_cli''' + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type +import json +import pytest + +from ansible.module_utils import basic +from ansible.module_utils._text import to_bytes +from ansible_collections.netapp.ontap.tests.unit.compat import unittest +from ansible_collections.netapp.ontap.tests.unit.compat.mock import patch +import ansible_collections.netapp.ontap.plugins.module_utils.netapp as netapp_utils + +from ansible_collections.netapp.ontap.plugins.modules.na_ontap_rest_cli \ + import NetAppONTAPCommandREST as rest_cli_module # module under test + +if not netapp_utils.has_netapp_lib(): + pytestmark = pytest.mark.skip('skipping as missing required netapp_lib') + + +# REST API canned responses when mocking send_request +SRR = { + # common responses + 'is_rest': (200, {}, None), + 'empty_good': (200, {}, None), + 'end_of_sequence': (500, None, "Ooops, the UT needs one more SRR response"), + 'generic_error': (400, None, "Expected error"), + # module specific response + 'allow': (200, {'Allow': ['GET', 'WHATEVER']}, None) +} + + +def set_module_args(args): + """prepare arguments so that they will be picked up during module creation""" + args = json.dumps({'ANSIBLE_MODULE_ARGS': args}) + basic._ANSIBLE_ARGS = to_bytes(args) # pylint: disable=protected-access + + +class AnsibleExitJson(Exception): + """Exception class to be raised by module.exit_json and caught by the test case""" + + +class AnsibleFailJson(Exception): + """Exception class to be raised by module.fail_json and caught by the test case""" + + +def exit_json(*args, **kwargs): # pylint: disable=unused-argument + """function to patch over exit_json; package return data into an exception""" + if 'changed' not in kwargs: + kwargs['changed'] = False + raise AnsibleExitJson(kwargs) + + +def fail_json(*args, **kwargs): # pylint: disable=unused-argument + """function to patch over fail_json; package return data into an exception""" + kwargs['failed'] = True + raise AnsibleFailJson(kwargs) + + +class MockONTAPConnection(object): + ''' mock server connection to ONTAP host ''' + + def __init__(self, data=None): + ''' save arguments ''' + self.params = data + self.xml_in = None + self.xml_out = None + + def invoke_successfully(self, xml, enable_tunneling): # pylint: disable=unused-argument + ''' mock invoke_successfully returning xml data ''' + self.xml_in = xml + self.xml_out = xml + return xml + + +class TestMyModule(unittest.TestCase): + ''' Unit tests for na_ontap_job_schedule ''' + + def setUp(self): + self.mock_module_helper = patch.multiple(basic.AnsibleModule, + exit_json=exit_json, + fail_json=fail_json) + self.mock_module_helper.start() + self.addCleanup(self.mock_module_helper.stop) + + def mock_args(self): + return { + 'hostname': 'test', + 'username': 'test_user', + 'password': 'test_pass!', + 'https': False, + 'command': 'volume', + 'verb': 'GET', + 'params': {'fields': 'size,percent_used'} + } + + def get_cli_mock_object(self): + # For rest, mocking is achieved through side_effect + return rest_cli_module() + + def test_module_fail_when_required_args_missing(self): + ''' required arguments are reported as errors ''' + with pytest.raises(AnsibleFailJson) as exc: + set_module_args({}) + rest_cli_module() + print('Info: %s' % exc.value.args[0]['msg']) + + @patch('ansible_collections.netapp.ontap.plugins.module_utils.netapp.OntapRestAPI.send_request') + def test_rest_cli(self, mock_request): + data = dict(self.mock_args()) + set_module_args(data) + mock_request.side_effect = [ + SRR['is_rest'], + SRR['empty_good'], + SRR['end_of_sequence'] + ] + with pytest.raises(AnsibleExitJson) as exc: + self.get_cli_mock_object().apply() + assert exc.value.args[0]['changed'] + + @patch('ansible_collections.netapp.ontap.plugins.module_utils.netapp.OntapRestAPI.send_request') + def test_rest_cli_options(self, mock_request): + data = dict(self.mock_args()) + data['verb'] = 'OPTIONS' + set_module_args(data) + mock_request.side_effect = [ + SRR['is_rest'], + SRR['allow'], + SRR['end_of_sequence'] + ] + with pytest.raises(AnsibleExitJson) as exc: + self.get_cli_mock_object().apply() + assert exc.value.args[0]['changed'] + assert 'Allow' in exc.value.args[0]['msg'] diff --git a/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_rest_info.py b/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_rest_info.py new file mode 100644 index 00000000..a7b400f9 --- /dev/null +++ b/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_rest_info.py @@ -0,0 +1,543 @@ +# (c) 2020, NetApp, Inc +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +''' Unit Tests NetApp ONTAP REST APIs Ansible module: na_ontap_rest_info ''' + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type +import json +import pytest + +from ansible.module_utils import basic +from ansible.module_utils._text import to_bytes +from ansible_collections.netapp.ontap.tests.unit.compat import unittest +from ansible_collections.netapp.ontap.tests.unit.compat.mock import patch + +from ansible_collections.netapp.ontap.plugins.modules.na_ontap_rest_info \ + import NetAppONTAPGatherInfo as ontap_rest_info_module + +# REST API canned responses when mocking send_request +SRR = { + # common responses + 'validate_ontap_version_pass': (200, {'version': 'ontap_version'}, None), + 'validate_ontap_version_fail': (200, None, 'API not found error'), + 'get_subset_info': (200, + {'_links': {'self': {'href': 'dummy_href'}}, + 'num_records': 3, + 'records': [{'name': 'dummy_vol1'}, + {'name': 'dummy_vol2'}, + {'name': 'dummy_vol3'}], + 'version': 'ontap_version'}, None), + 'get_subset_info_with_next': (200, + {'_links': {'self': {'href': 'dummy_href'}, + 'next': {'href': '/api/next_record_api'}}, + 'num_records': 3, + 'records': [{'name': 'dummy_vol1'}, + {'name': 'dummy_vol2'}, + {'name': 'dummy_vol3'}], + 'version': 'ontap_version'}, None), + 'get_next_record': (200, + {'_links': {'self': {'href': 'dummy_href'}}, + 'num_records': 2, + 'records': [{'name': 'dummy_vol1'}, + {'name': 'dummy_vol2'}], + 'version': 'ontap_version'}, None), + 'metrocluster_post': (200, + {'job': { + 'uuid': 'fde79888-692a-11ea-80c2-005056b39fe7', + '_links': { + 'self': { + 'href': '/api/cluster/jobs/fde79888-692a-11ea-80c2-005056b39fe7'}} + }}, + None), + 'metrocluster_return': (200, + {"_links": { + "self": { + "href": "/api/cluster/metrocluster/diagnostics" + } + }, "aggregate": { + "state": "ok", + "summary": { + "message": "" + }, "timestamp": "2020-07-22T16:42:51-07:00" + }}, None), + 'job': (200, + { + "uuid": "cca3d070-58c6-11ea-8c0c-005056826c14", + "description": "POST /api/cluster/metrocluster", + "state": "success", + "message": "There are not enough disks in Pool1.", + "code": 2432836, + "start_time": "2020-02-26T10:35:44-08:00", + "end_time": "2020-02-26T10:47:38-08:00", + "_links": { + "self": { + "href": "/api/cluster/jobs/cca3d070-58c6-11ea-8c0c-005056826c14" + } + } + }, None) +} + + +def set_module_args(args): + """prepare arguments so that they will be picked up during module creation""" + args = json.dumps({'ANSIBLE_MODULE_ARGS': args}) + basic._ANSIBLE_ARGS = to_bytes(args) # pylint: disable=protected-access + + +class AnsibleExitJson(Exception): + """Exception class to be raised by module.exit_json and caught by the test case""" + + +class AnsibleFailJson(Exception): + """Exception class to be raised by module.fail_json and caught by the test case""" + + +def exit_json(*args, **kwargs): # pylint: disable=unused-argument + """function to patch over exit_json; package return data into an exception""" + if 'changed' not in kwargs: + kwargs['changed'] = False + raise AnsibleExitJson(kwargs) + + +def fail_json(*args, **kwargs): # pylint: disable=unused-argument + """function to patch over fail_json; package return data into an exception""" + kwargs['failed'] = True + raise AnsibleFailJson(kwargs) + + +class TestMyModule(unittest.TestCase): + ''' A group of related Unit Tests ''' + + def setUp(self): + self.mock_module_helper = patch.multiple(basic.AnsibleModule, + exit_json=exit_json, + fail_json=fail_json) + self.mock_module_helper.start() + self.addCleanup(self.mock_module_helper.stop) + + def set_default_args(self): + return dict({ + 'hostname': 'hostname', + 'username': 'username', + 'password': 'password', + 'https': True, + 'validate_certs': False + }) + + def set_args_run_ontap_version_check(self): + return dict({ + 'hostname': 'hostname', + 'username': 'username', + 'password': 'password', + 'https': True, + 'validate_certs': False, + 'max_records': 1024, + 'gather_subset': ['volume_info'] + }) + + def set_args_run_metrocluster_diag(self): + return dict({ + 'hostname': 'hostname', + 'username': 'username', + 'password': 'password', + 'https': True, + 'validate_certs': False, + 'max_records': 1024, + 'gather_subset': ['cluster/metrocluster/diagnostics'] + }) + + def set_args_run_ontap_gather_facts_for_vserver_info(self): + return dict({ + 'hostname': 'hostname', + 'username': 'username', + 'password': 'password', + 'https': True, + 'validate_certs': False, + 'max_records': 1024, + 'gather_subset': ['vserver_info'] + }) + + def set_args_run_ontap_gather_facts_for_volume_info(self): + return dict({ + 'hostname': 'hostname', + 'username': 'username', + 'password': 'password', + 'https': True, + 'validate_certs': False, + 'max_records': 1024, + 'gather_subset': ['volume_info'] + }) + + def set_args_run_ontap_gather_facts_for_all_subsets(self): + return dict({ + 'hostname': 'hostname', + 'username': 'username', + 'password': 'password', + 'https': True, + 'validate_certs': False, + 'max_records': 1024, + 'gather_subset': ['all'] + }) + + def set_args_run_ontap_gather_facts_for_all_subsets_with_fields_section_pass(self): + return dict({ + 'hostname': 'hostname', + 'username': 'username', + 'password': 'password', + 'https': True, + 'validate_certs': False, + 'max_records': 1024, + 'fields': '*', + 'gather_subset': ['all'] + }) + + def set_args_run_ontap_gather_facts_for_all_subsets_with_fields_section_fail(self): + return dict({ + 'hostname': 'hostname', + 'username': 'username', + 'password': 'password', + 'https': True, + 'validate_certs': False, + 'max_records': 1024, + 'fields': ['uuid', 'name', 'node'], + 'gather_subset': ['all'] + }) + + def set_args_run_ontap_gather_facts_for_aggregate_info_with_fields_section_pass(self): + return dict({ + 'hostname': 'hostname', + 'username': 'username', + 'password': 'password', + 'https': True, + 'fields': ['uuid', 'name', 'node'], + 'validate_certs': False, + 'max_records': 1024, + 'gather_subset': ['aggregate_info'] + }) + + def set_args_get_all_records_for_volume_info_to_check_next_api_call_functionality_pass(self): + return dict({ + 'hostname': 'hostname', + 'username': 'username', + 'password': 'password', + 'https': True, + 'validate_certs': False, + 'max_records': 3, + 'gather_subset': ['volume_info'] + }) + + @patch('ansible_collections.netapp.ontap.plugins.module_utils.netapp.OntapRestAPI.send_request') + def test_run_ontap_version_check_for_9_6_pass(self, mock_request): + set_module_args(self.set_args_run_ontap_version_check()) + my_obj = ontap_rest_info_module() + mock_request.side_effect = [ + SRR['validate_ontap_version_pass'], + SRR['get_subset_info'], + ] + + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + assert exc.value.args[0]['changed'] + + @patch('ansible_collections.netapp.ontap.plugins.module_utils.netapp.OntapRestAPI.send_request') + def test_run_ontap_version_check_for_10_2_pass(self, mock_request): + set_module_args(self.set_args_run_ontap_version_check()) + my_obj = ontap_rest_info_module() + mock_request.side_effect = [ + SRR['validate_ontap_version_pass'], + SRR['get_subset_info'], + ] + + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + assert exc.value.args[0]['changed'] + + @patch('ansible_collections.netapp.ontap.plugins.module_utils.netapp.OntapRestAPI.send_request') + def test_run_ontap_version_check_for_9_2_fail(self, mock_request): + ''' Test for Checking the ONTAP version ''' + set_module_args(self.set_args_run_ontap_version_check()) + my_obj = ontap_rest_info_module() + mock_request.side_effect = [ + SRR['validate_ontap_version_fail'], + ] + + with pytest.raises(AnsibleFailJson) as exc: + my_obj.apply() + assert exc.value.args[0]['msg'] == SRR['validate_ontap_version_fail'][2] + + @patch('ansible_collections.netapp.ontap.plugins.module_utils.netapp.OntapRestAPI.send_request') + def test_run_metrocluster_pass(self, mock_request): + set_module_args(self.set_args_run_metrocluster_diag()) + my_obj = ontap_rest_info_module() + gather_subset = ['cluster/metrocluster/diagnostics'] + mock_request.side_effect = [ + SRR['validate_ontap_version_pass'], + SRR['metrocluster_post'], + SRR['job'], + SRR['metrocluster_return'] + ] + + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + print('Info: test_run_metrocluster_digag_pass: %s' % repr(exc.value.args)) + assert set(exc.value.args[0]['ontap_info']) == set(gather_subset) + + @patch('ansible_collections.netapp.ontap.plugins.module_utils.netapp.OntapRestAPI.send_request') + def test_run_ontap_gather_facts_for_vserver_info_pass(self, mock_request): + set_module_args(self.set_args_run_ontap_gather_facts_for_vserver_info()) + my_obj = ontap_rest_info_module() + gather_subset = ['svm/svms'] + mock_request.side_effect = [ + SRR['validate_ontap_version_pass'], + SRR['get_subset_info'], + ] + + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + print('Info: test_run_ontap_gather_facts_for_vserver_info_pass: %s' % repr(exc.value.args)) + assert set(exc.value.args[0]['ontap_info']) == set(gather_subset) + + @patch('ansible_collections.netapp.ontap.plugins.module_utils.netapp.OntapRestAPI.send_request') + def test_run_ontap_gather_facts_for_volume_info_pass(self, mock_request): + set_module_args(self.set_args_run_ontap_gather_facts_for_volume_info()) + my_obj = ontap_rest_info_module() + gather_subset = ['storage/volumes'] + mock_request.side_effect = [ + SRR['validate_ontap_version_pass'], + SRR['get_subset_info'], + ] + + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + print('Info: test_run_ontap_gather_facts_for_volume_info_pass: %s' % repr(exc.value.args)) + assert set(exc.value.args[0]['ontap_info']) == set(gather_subset) + + # Super Important, Metrocluster doesn't call get_subset_info and has 3 api calls instead of 1!!!! + # The metrocluster calls need to be in the correct place. The Module return the keys in a sorted list. + @patch('ansible_collections.netapp.ontap.plugins.module_utils.netapp.OntapRestAPI.send_request') + def test_run_ontap_gather_facts_for_all_subsets_pass(self, mock_request): + set_module_args(self.set_args_run_ontap_gather_facts_for_all_subsets()) + my_obj = ontap_rest_info_module() + gather_subset = ['application/applications', 'application/templates', 'cloud/targets', 'cluster/chassis', 'cluster/jobs', + 'cluster/metrocluster/diagnostics', 'cluster/metrics', 'cluster/nodes', 'cluster/peers', 'cluster/schedules', + 'cluster/software', 'cluster/software/download', 'cluster/software/history', 'cluster/software/packages', + 'name-services/dns', 'name-services/ldap', 'name-services/name-mappings', 'name-services/nis', + 'network/ethernet/broadcast-domains', 'network/ethernet/ports', 'network/fc/logins', 'network/fc/wwpn-aliases', + 'network/ip/interfaces', 'network/ip/routes', 'network/ip/service-policies', 'network/ipspaces', + 'protocols/cifs/home-directory/search-paths', 'protocols/cifs/services', 'protocols/cifs/shares', + 'protocols/san/fcp/services', 'protocols/san/igroups', 'protocols/san/iscsi/credentials', + 'protocols/san/iscsi/services', 'protocols/san/lun-maps', 'security/accounts', 'security/roles', 'storage/aggregates', + 'storage/disks', 'storage/flexcache/flexcaches', 'storage/flexcache/origins', 'storage/luns', 'storage/namespaces', + 'storage/ports', 'storage/qos/policies', 'storage/qtrees', 'storage/quota/reports', 'storage/quota/rules', + 'storage/shelves', 'storage/snapshot-policies', 'storage/volumes', 'support/autosupport', 'support/autosupport/messages', + 'support/ems', 'support/ems/destinations', 'support/ems/events', 'support/ems/filters', 'svm/peers', 'svm/peer-permissions', + 'svm/svms'] + mock_request.side_effect = [ + SRR['validate_ontap_version_pass'], + SRR['get_subset_info'], + SRR['get_subset_info'], + SRR['get_subset_info'], + SRR['get_subset_info'], + SRR['get_subset_info'], + SRR['get_subset_info'], + SRR['metrocluster_post'], + SRR['job'], + SRR['metrocluster_return'], + SRR['get_subset_info'], + SRR['get_subset_info'], + SRR['get_subset_info'], + SRR['get_subset_info'], + SRR['get_subset_info'], + SRR['get_subset_info'], + SRR['get_subset_info'], + SRR['get_subset_info'], + SRR['get_subset_info'], + SRR['get_subset_info'], + SRR['get_subset_info'], + SRR['get_subset_info'], + SRR['get_subset_info'], + SRR['get_subset_info'], + SRR['get_subset_info'], + SRR['get_subset_info'], + SRR['get_subset_info'], + SRR['get_subset_info'], + SRR['get_subset_info'], + SRR['get_subset_info'], + SRR['get_subset_info'], + SRR['get_subset_info'], + SRR['get_subset_info'], + SRR['get_subset_info'], + SRR['get_subset_info'], + SRR['get_subset_info'], + SRR['get_subset_info'], + SRR['get_subset_info'], + SRR['get_subset_info'], + SRR['get_subset_info'], + SRR['get_subset_info'], + SRR['get_subset_info'], + SRR['get_subset_info'], + SRR['get_subset_info'], + SRR['get_subset_info'], + SRR['get_subset_info'], + SRR['get_subset_info'], + SRR['get_subset_info'], + SRR['get_subset_info'], + SRR['get_subset_info'], + SRR['get_subset_info'], + SRR['get_subset_info'], + SRR['get_subset_info'], + SRR['get_subset_info'], + SRR['get_subset_info'], + SRR['get_subset_info'], + SRR['get_subset_info'], + SRR['get_subset_info'], + SRR['get_subset_info'], + SRR['get_subset_info'], + SRR['get_subset_info'], + SRR['get_subset_info'], + ] + + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + print('Info: test_run_ontap_gather_facts_for_all_subsets_pass: %s' % repr(exc.value.args)) + assert set(exc.value.args[0]['ontap_info']) == set(gather_subset) + + # Super Important, Metrocluster doesn't call get_subset_info and has 3 api calls instead of 1!!!! + # The metrocluster calls need to be in the correct place. The Module return the keys in a sorted list. + @patch('ansible_collections.netapp.ontap.plugins.module_utils.netapp.OntapRestAPI.send_request') + def test_run_ontap_gather_facts_for_all_subsets_with_fields_section_pass(self, mock_request): + set_module_args(self.set_args_run_ontap_gather_facts_for_all_subsets_with_fields_section_pass()) + my_obj = ontap_rest_info_module() + gather_subset = ['application/applications', 'application/templates', 'cloud/targets', 'cluster/chassis', 'cluster/jobs', + 'cluster/metrocluster/diagnostics', 'cluster/metrics', 'cluster/nodes', 'cluster/peers', 'cluster/schedules', + 'cluster/software', 'cluster/software/download', 'cluster/software/history', 'cluster/software/packages', + 'name-services/dns', 'name-services/ldap', 'name-services/name-mappings', 'name-services/nis', + 'network/ethernet/broadcast-domains', 'network/ethernet/ports', 'network/fc/logins', 'network/fc/wwpn-aliases', + 'network/ip/interfaces', 'network/ip/routes', 'network/ip/service-policies', 'network/ipspaces', + 'protocols/cifs/home-directory/search-paths', 'protocols/cifs/services', 'protocols/cifs/shares', + 'protocols/san/fcp/services', 'protocols/san/igroups', 'protocols/san/iscsi/credentials', + 'protocols/san/iscsi/services', 'protocols/san/lun-maps', 'security/accounts', 'security/roles', 'storage/aggregates', + 'storage/disks', 'storage/flexcache/flexcaches', 'storage/flexcache/origins', 'storage/luns', 'storage/namespaces', + 'storage/ports', 'storage/qos/policies', 'storage/qtrees', 'storage/quota/reports', 'storage/quota/rules', + 'storage/shelves', 'storage/snapshot-policies', 'storage/volumes', 'support/autosupport', 'support/autosupport/messages', + 'support/ems', 'support/ems/destinations', 'support/ems/events', 'support/ems/filters', 'svm/peers', 'svm/peer-permissions', + 'svm/svms'] + mock_request.side_effect = [ + SRR['validate_ontap_version_pass'], + SRR['get_subset_info'], + SRR['get_subset_info'], + SRR['get_subset_info'], + SRR['get_subset_info'], + SRR['get_subset_info'], + SRR['get_subset_info'], + SRR['metrocluster_post'], + SRR['job'], + SRR['metrocluster_return'], + SRR['get_subset_info'], + SRR['get_subset_info'], + SRR['get_subset_info'], + SRR['get_subset_info'], + SRR['get_subset_info'], + SRR['get_subset_info'], + SRR['get_subset_info'], + SRR['get_subset_info'], + SRR['get_subset_info'], + SRR['get_subset_info'], + SRR['get_subset_info'], + SRR['get_subset_info'], + SRR['get_subset_info'], + SRR['get_subset_info'], + SRR['get_subset_info'], + SRR['get_subset_info'], + SRR['get_subset_info'], + SRR['get_subset_info'], + SRR['get_subset_info'], + SRR['get_subset_info'], + SRR['get_subset_info'], + SRR['get_subset_info'], + SRR['get_subset_info'], + SRR['get_subset_info'], + SRR['get_subset_info'], + SRR['get_subset_info'], + SRR['get_subset_info'], + SRR['get_subset_info'], + SRR['get_subset_info'], + SRR['get_subset_info'], + SRR['get_subset_info'], + SRR['get_subset_info'], + SRR['get_subset_info'], + SRR['get_subset_info'], + SRR['get_subset_info'], + SRR['get_subset_info'], + SRR['get_subset_info'], + SRR['get_subset_info'], + SRR['get_subset_info'], + SRR['get_subset_info'], + SRR['get_subset_info'], + SRR['get_subset_info'], + SRR['get_subset_info'], + SRR['get_subset_info'], + SRR['get_subset_info'], + SRR['get_subset_info'], + SRR['get_subset_info'], + SRR['get_subset_info'], + SRR['get_subset_info'], + SRR['get_subset_info'], + SRR['get_subset_info'], + SRR['get_subset_info'], + ] + + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + print('Info: test_run_ontap_gather_facts_for_all_subsets_pass: %s' % repr(exc.value.args)) + assert set(exc.value.args[0]['ontap_info']) == set(gather_subset) + + @patch('ansible_collections.netapp.ontap.plugins.module_utils.netapp.OntapRestAPI.send_request') + def test_run_ontap_gather_facts_for_all_subsets_with_fields_section_fail(self, mock_request): + set_module_args(self.set_args_run_ontap_gather_facts_for_all_subsets_with_fields_section_fail()) + my_obj = ontap_rest_info_module() + error_message = "Error: fields: %s, only one subset will be allowed." \ + % self.set_args_run_ontap_gather_facts_for_aggregate_info_with_fields_section_pass()['fields'] + mock_request.side_effect = [ + SRR['validate_ontap_version_pass'], + SRR['get_subset_info'], + SRR['get_subset_info'], + SRR['get_subset_info'], + ] + + with pytest.raises(AnsibleFailJson) as exc: + my_obj.apply() + print('Info: test_run_ontap_gather_facts_for_all_subsets_pass: %s' % repr(exc.value.args)) + assert exc.value.args[0]['msg'] == error_message + + @patch('ansible_collections.netapp.ontap.plugins.module_utils.netapp.OntapRestAPI.send_request') + def test_run_ontap_gather_facts_for_aggregate_info_pass_with_fields_section_pass(self, mock_request): + set_module_args(self.set_args_run_ontap_gather_facts_for_aggregate_info_with_fields_section_pass()) + my_obj = ontap_rest_info_module() + gather_subset = ['storage/aggregates'] + mock_request.side_effect = [ + SRR['validate_ontap_version_pass'], + SRR['get_subset_info'], + ] + + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + print('Info: test_run_ontap_gather_facts_for_volume_info_pass: %s' % repr(exc.value.args)) + assert set(exc.value.args[0]['ontap_info']) == set(gather_subset) + + @patch('ansible_collections.netapp.ontap.plugins.module_utils.netapp.OntapRestAPI.send_request') + def test_get_all_records_for_volume_info_to_check_next_api_call_functionality_pass(self, mock_request): + set_module_args(self.set_args_get_all_records_for_volume_info_to_check_next_api_call_functionality_pass()) + my_obj = ontap_rest_info_module() + total_records = 5 + mock_request.side_effect = [ + SRR['validate_ontap_version_pass'], + SRR['get_subset_info_with_next'], + SRR['get_next_record'], + ] + + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + print('Info: test_get_all_records_for_volume_info_to_check_next_api_call_functionality_pass: %s' % repr(exc.value.args)) + assert exc.value.args[0]['ontap_info']['storage/volumes']['num_records'] == total_records diff --git a/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_security_certificates.py b/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_security_certificates.py new file mode 100644 index 00000000..6e298145 --- /dev/null +++ b/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_security_certificates.py @@ -0,0 +1,435 @@ +# (c) 2019, NetApp, Inc +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +""" unit tests for Ansible module: na_ontap_security_certificates """ + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type +import json +import pytest + +from ansible.module_utils import basic +from ansible.module_utils._text import to_bytes +from ansible_collections.netapp.ontap.tests.unit.compat.mock import patch + +from ansible_collections.netapp.ontap.plugins.modules.na_ontap_security_certificates \ + import NetAppOntapSecurityCertificates as my_module # module under test + + +# REST API canned responses when mocking send_request +SRR = { + # common responses + 'is_rest': (200, {}, None), + 'is_zapi': (400, {}, "Unreachable"), + 'empty_good': (200, {}, None), + 'end_of_sequence': (500, None, "Unexpected call to send_request"), + 'generic_error': (400, None, "Expected error"), + # module specific responses + 'empty_records': (200, {'records': []}, None), + 'get_uuid': (200, {'records': [{'uuid': 'ansible'}]}, None), + 'error_unexpected_name': (200, None, {'message': 'Unexpected argument "name".'}) +} + +NAME_ERROR = "Error calling API: security/certificates - ONTAP 9.6 and 9.7 do not support 'name'. Use 'common_name' and 'type' as a work-around." +TYPE_ERROR = "Error calling API: security/certificates - When using 'common_name', 'type' is required." +EXPECTED_ERROR = "Error calling API: security/certificates - Expected error" + + +def set_module_args(args): + """prepare arguments so that they will be picked up during module creation""" + args = json.dumps({'ANSIBLE_MODULE_ARGS': args}) + basic._ANSIBLE_ARGS = to_bytes(args) # pylint: disable=protected-access + + +class AnsibleExitJson(Exception): + """Exception class to be raised by module.exit_json and caught by the test case""" + + +class AnsibleFailJson(Exception): + """Exception class to be raised by module.fail_json and caught by the test case""" + + +def exit_json(*args, **kwargs): # pylint: disable=unused-argument + """function to patch over exit_json; package return data into an exception""" + if 'changed' not in kwargs: + kwargs['changed'] = False + raise AnsibleExitJson(kwargs) + + +def fail_json(*args, **kwargs): # pylint: disable=unused-argument + """function to patch over fail_json; package return data into an exception""" + kwargs['failed'] = True + raise AnsibleFailJson(kwargs) + + +def set_default_args(): + return dict({ + 'hostname': 'hostname', + 'username': 'username', + 'password': 'password', + 'name': 'name_for_certificate' + }) + + +@patch('ansible.module_utils.basic.AnsibleModule.fail_json') +def test_module_fail_when_required_args_missing(mock_fail): + ''' required arguments are reported as errors ''' + mock_fail.side_effect = fail_json + set_module_args({}) + with pytest.raises(AnsibleFailJson) as exc: + my_module() + print('Info: %s' % exc.value.args[0]['msg']) + + +@patch('ansible.module_utils.basic.AnsibleModule.fail_json') +@patch('ansible_collections.netapp.ontap.plugins.module_utils.netapp.OntapRestAPI.send_request') +def test_ensure_get_certificate_called(mock_request, mock_fail): + mock_fail.side_effect = fail_json + mock_request.side_effect = [ + SRR['is_rest'], + SRR['get_uuid'], + SRR['end_of_sequence'] + ] + set_module_args(set_default_args()) + my_obj = my_module() + assert my_obj.get_certificate() is not None + + +@patch('ansible.module_utils.basic.AnsibleModule.fail_json') +@patch('ansible_collections.netapp.ontap.plugins.module_utils.netapp.OntapRestAPI.send_request') +def test_rest_error(mock_request, mock_fail): + mock_fail.side_effect = fail_json + mock_request.side_effect = [ + SRR['is_rest'], + SRR['generic_error'], + SRR['end_of_sequence'] + ] + set_module_args(set_default_args()) + my_obj = my_module() + with pytest.raises(AnsibleFailJson) as exc: + my_obj.apply() + assert exc.value.args[0]['msg'] == EXPECTED_ERROR + + +@patch('ansible.module_utils.basic.AnsibleModule.exit_json') +@patch('ansible.module_utils.basic.AnsibleModule.fail_json') +@patch('ansible_collections.netapp.ontap.plugins.module_utils.netapp.OntapRestAPI.send_request') +def test_rest_create_failed(mock_request, mock_fail, mock_exit): + mock_exit.side_effect = exit_json + mock_fail.side_effect = fail_json + mock_request.side_effect = [ + SRR['is_rest'], + SRR['empty_records'], # get certificate -> not found + SRR['empty_good'], + SRR['end_of_sequence'] + ] + data = { + 'type': 'client_ca', + 'vserver': 'abc', + } + data.update(set_default_args()) + set_module_args(data) + my_obj = my_module() + with pytest.raises(AnsibleFailJson) as exc: + my_obj.apply() + msg = 'Error creating or installing certificate: one or more of the following options are missing:' + assert exc.value.args[0]['msg'].startswith(msg) + + +@patch('ansible.module_utils.basic.AnsibleModule.exit_json') +@patch('ansible.module_utils.basic.AnsibleModule.fail_json') +@patch('ansible_collections.netapp.ontap.plugins.module_utils.netapp.OntapRestAPI.send_request') +def test_rest_successful_create(mock_request, mock_fail, mock_exit): + mock_exit.side_effect = exit_json + mock_fail.side_effect = fail_json + mock_request.side_effect = [ + SRR['is_rest'], + SRR['empty_records'], # get certificate -> not found + SRR['empty_good'], + SRR['end_of_sequence'] + ] + data = { + 'type': 'client_ca', + 'vserver': 'abc', + 'common_name': 'cname' + } + data.update(set_default_args()) + set_module_args(data) + my_obj = my_module() + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + assert exc.value.args[0]['changed'] + + +@patch('ansible.module_utils.basic.AnsibleModule.exit_json') +@patch('ansible.module_utils.basic.AnsibleModule.fail_json') +@patch('ansible_collections.netapp.ontap.plugins.module_utils.netapp.OntapRestAPI.send_request') +def test_rest_idempotent_create(mock_request, mock_fail, mock_exit): + mock_exit.side_effect = exit_json + mock_fail.side_effect = fail_json + mock_request.side_effect = [ + SRR['is_rest'], + SRR['get_uuid'], # get certificate -> found + SRR['end_of_sequence'] + ] + data = { + 'type': 'client_ca', + 'vserver': 'abc', + } + data.update(set_default_args()) + set_module_args(data) + my_obj = my_module() + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + assert not exc.value.args[0]['changed'] + + +@patch('ansible.module_utils.basic.AnsibleModule.exit_json') +@patch('ansible.module_utils.basic.AnsibleModule.fail_json') +@patch('ansible_collections.netapp.ontap.plugins.module_utils.netapp.OntapRestAPI.send_request') +def test_rest_successful_delete(mock_request, mock_fail, mock_exit): + mock_exit.side_effect = exit_json + mock_fail.side_effect = fail_json + mock_request.side_effect = [ + SRR['is_rest'], + SRR['get_uuid'], # get certificate -> found + SRR['empty_good'], + SRR['end_of_sequence'] + ] + data = { + 'state': 'absent', + } + data.update(set_default_args()) + set_module_args(data) + my_obj = my_module() + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + assert exc.value.args[0]['changed'] + + +@patch('ansible.module_utils.basic.AnsibleModule.exit_json') +@patch('ansible.module_utils.basic.AnsibleModule.fail_json') +@patch('ansible_collections.netapp.ontap.plugins.module_utils.netapp.OntapRestAPI.send_request') +def test_rest_idempotent_delete(mock_request, mock_fail, mock_exit): + mock_exit.side_effect = exit_json + mock_fail.side_effect = fail_json + mock_request.side_effect = [ + SRR['is_rest'], + SRR['empty_records'], # get certificate -> not found + SRR['empty_good'], + SRR['end_of_sequence'] + ] + data = { + 'state': 'absent', + } + data.update(set_default_args()) + set_module_args(data) + my_obj = my_module() + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + assert not exc.value.args[0]['changed'] + + +@patch('ansible.module_utils.basic.AnsibleModule.exit_json') +@patch('ansible.module_utils.basic.AnsibleModule.fail_json') +@patch('ansible_collections.netapp.ontap.plugins.module_utils.netapp.OntapRestAPI.send_request') +def test_rest_successful_sign(mock_request, mock_fail, mock_exit): + mock_exit.side_effect = exit_json + mock_fail.side_effect = fail_json + mock_request.side_effect = [ + SRR['is_rest'], + SRR['get_uuid'], # get certificate -> found + SRR['empty_good'], + SRR['end_of_sequence'] + ] + data = { + 'vserver': 'abc', + 'signing_request': 'CSR' + } + data.update(set_default_args()) + set_module_args(data) + my_obj = my_module() + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + assert exc.value.args[0]['changed'] + + +@patch('ansible.module_utils.basic.AnsibleModule.exit_json') +@patch('ansible.module_utils.basic.AnsibleModule.fail_json') +@patch('ansible_collections.netapp.ontap.plugins.module_utils.netapp.OntapRestAPI.send_request') +def test_rest_failed_sign_missing_ca(mock_request, mock_fail, mock_exit): + mock_exit.side_effect = exit_json + mock_fail.side_effect = fail_json + mock_request.side_effect = [ + SRR['is_rest'], + SRR['empty_records'], # get certificate -> not found + SRR['empty_good'], + SRR['end_of_sequence'] + ] + data = { + 'vserver': 'abc', + 'signing_request': 'CSR' + } + data.update(set_default_args()) + set_module_args(data) + my_obj = my_module() + with pytest.raises(AnsibleFailJson) as exc: + my_obj.apply() + msg = "signing certificate with name '%s' not found on svm: %s" % (data['name'], data['vserver']) + assert exc.value.args[0]['msg'] == msg + + +@patch('ansible.module_utils.basic.AnsibleModule.exit_json') +@patch('ansible.module_utils.basic.AnsibleModule.fail_json') +@patch('ansible_collections.netapp.ontap.plugins.module_utils.netapp.OntapRestAPI.send_request') +def test_rest_failed_sign_absent(mock_request, mock_fail, mock_exit): + mock_exit.side_effect = exit_json + mock_fail.side_effect = fail_json + mock_request.side_effect = [ + SRR['is_rest'], + SRR['get_uuid'], # get certificate -> found + SRR['end_of_sequence'] + ] + data = { + 'vserver': 'abc', + 'signing_request': 'CSR', + 'state': 'absent' + } + data.update(set_default_args()) + set_module_args(data) + my_obj = my_module() + with pytest.raises(AnsibleFailJson) as exc: + my_obj.apply() + msg = "'signing_request' is not supported with 'state' set to 'absent'" + assert exc.value.args[0]['msg'] == msg + + +@patch('ansible.module_utils.basic.AnsibleModule.exit_json') +@patch('ansible.module_utils.basic.AnsibleModule.fail_json') +@patch('ansible_collections.netapp.ontap.plugins.module_utils.netapp.OntapRestAPI.send_request') +def test_rest_failed_on_name(mock_request, mock_fail, mock_exit): + mock_exit.side_effect = exit_json + mock_fail.side_effect = fail_json + mock_request.side_effect = [ + SRR['is_rest'], + SRR['error_unexpected_name'], # get certificate -> error + SRR['end_of_sequence'] + ] + data = { + 'vserver': 'abc', + 'signing_request': 'CSR', + 'state': 'absent', + 'ignore_name_if_not_supported': False, + 'common_name': 'common_name', + 'type': 'root_ca' + } + data.update(set_default_args()) + set_module_args(data) + my_obj = my_module() + with pytest.raises(AnsibleFailJson) as exc: + my_obj.apply() + assert exc.value.args[0]['msg'] == NAME_ERROR + + +@patch('ansible.module_utils.basic.AnsibleModule.exit_json') +@patch('ansible.module_utils.basic.AnsibleModule.fail_json') +@patch('ansible_collections.netapp.ontap.plugins.module_utils.netapp.OntapRestAPI.send_request') +def test_rest_cannot_ignore_name_error_no_common_name(mock_request, mock_fail, mock_exit): + mock_exit.side_effect = exit_json + mock_fail.side_effect = fail_json + mock_request.side_effect = [ + SRR['is_rest'], + SRR['error_unexpected_name'], # get certificate -> error + SRR['end_of_sequence'] + ] + data = { + 'vserver': 'abc', + 'signing_request': 'CSR', + 'state': 'absent', + } + data.update(set_default_args()) + set_module_args(data) + my_obj = my_module() + with pytest.raises(AnsibleFailJson) as exc: + my_obj.apply() + assert exc.value.args[0]['msg'] == NAME_ERROR + + +@patch('ansible.module_utils.basic.AnsibleModule.exit_json') +@patch('ansible.module_utils.basic.AnsibleModule.fail_json') +@patch('ansible_collections.netapp.ontap.plugins.module_utils.netapp.OntapRestAPI.send_request') +def test_rest_cannot_ignore_name_error_no_type(mock_request, mock_fail, mock_exit): + mock_exit.side_effect = exit_json + mock_fail.side_effect = fail_json + mock_request.side_effect = [ + SRR['is_rest'], + SRR['error_unexpected_name'], # get certificate -> error + SRR['end_of_sequence'] + ] + data = { + 'vserver': 'abc', + 'signing_request': 'CSR', + 'state': 'absent', + 'common_name': 'common_name' + } + data.update(set_default_args()) + set_module_args(data) + my_obj = my_module() + with pytest.raises(AnsibleFailJson) as exc: + my_obj.apply() + assert exc.value.args[0]['msg'] == TYPE_ERROR + + +@patch('ansible.module_utils.basic.AnsibleModule.exit_json') +@patch('ansible.module_utils.basic.AnsibleModule.fail_json') +@patch('ansible_collections.netapp.ontap.plugins.module_utils.netapp.OntapRestAPI.send_request') +def test_rest_ignore_name_error(mock_request, mock_fail, mock_exit): + mock_exit.side_effect = exit_json + mock_fail.side_effect = fail_json + mock_request.side_effect = [ + SRR['is_rest'], + SRR['error_unexpected_name'], # get certificate -> error + SRR['get_uuid'], # get certificate -> found + SRR['end_of_sequence'] + ] + data = { + 'vserver': 'abc', + 'signing_request': 'CSR', + 'state': 'absent', + 'common_name': 'common_name', + 'type': 'root_ca' + } + data.update(set_default_args()) + set_module_args(data) + my_obj = my_module() + with pytest.raises(AnsibleFailJson) as exc: + my_obj.apply() + msg = "'signing_request' is not supported with 'state' set to 'absent'" + assert exc.value.args[0]['msg'] == msg + + +@patch('ansible.module_utils.basic.AnsibleModule.exit_json') +@patch('ansible.module_utils.basic.AnsibleModule.fail_json') +@patch('ansible_collections.netapp.ontap.plugins.module_utils.netapp.OntapRestAPI.send_request') +def test_rest_successful_create_name_error(mock_request, mock_fail, mock_exit): + mock_exit.side_effect = exit_json + mock_fail.side_effect = fail_json + mock_request.side_effect = [ + SRR['is_rest'], + SRR['error_unexpected_name'], # get certificate -> error + SRR['empty_records'], # get certificate -> not found + SRR['empty_good'], + SRR['end_of_sequence'] + ] + data = { + 'common_name': 'cname', + 'type': 'client_ca', + 'vserver': 'abc', + } + data.update(set_default_args()) + set_module_args(data) + my_obj = my_module() + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + assert exc.value.args[0]['changed'] + print(mock_request.mock_calls) diff --git a/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_security_key_manager.py b/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_security_key_manager.py new file mode 100644 index 00000000..3f5e6a41 --- /dev/null +++ b/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_security_key_manager.py @@ -0,0 +1,174 @@ +# (c) 2019, NetApp, Inc +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +''' unit test template for ONTAP Ansible module ''' + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type +import json +import pytest + +from ansible_collections.netapp.ontap.tests.unit.compat import unittest +from ansible_collections.netapp.ontap.tests.unit.compat.mock import patch, Mock +from ansible.module_utils import basic +from ansible.module_utils._text import to_bytes +import ansible_collections.netapp.ontap.plugins.module_utils.netapp as netapp_utils + +from ansible_collections.netapp.ontap.plugins.modules.na_ontap_security_key_manager \ + import NetAppOntapSecurityKeyManager as key_manager_module # module under test + +if not netapp_utils.has_netapp_lib(): + pytestmark = pytest.mark.skip('skipping as missing required netapp_lib') + + +def set_module_args(args): + """prepare arguments so that they will be picked up during module creation""" + args = json.dumps({'ANSIBLE_MODULE_ARGS': args}) + basic._ANSIBLE_ARGS = to_bytes(args) # pylint: disable=protected-access + + +class AnsibleExitJson(Exception): + """Exception class to be raised by module.exit_json and caught by the test case""" + pass + + +class AnsibleFailJson(Exception): + """Exception class to be raised by module.fail_json and caught by the test case""" + pass + + +def exit_json(*args, **kwargs): # pylint: disable=unused-argument + """function to patch over exit_json; package return data into an exception""" + if 'changed' not in kwargs: + kwargs['changed'] = False + raise AnsibleExitJson(kwargs) + + +def fail_json(*args, **kwargs): # pylint: disable=unused-argument + """function to patch over fail_json; package return data into an exception""" + kwargs['failed'] = True + raise AnsibleFailJson(kwargs) + + +class MockONTAPConnection(object): + ''' mock server connection to ONTAP host ''' + + def __init__(self, kind=None, data=None): + ''' save arguments ''' + self.type = kind + self.data = data + self.xml_in = None + self.xml_out = None + + def invoke_successfully(self, xml, enable_tunneling): # pylint: disable=unused-argument + ''' mock invoke_successfully returning xml data ''' + self.xml_in = xml + if self.type == 'key_manager': + xml = self.build_port_info(self.data) + self.xml_out = xml + return xml + + @staticmethod + def build_port_info(key_manager_details): + ''' build xml data for-key-manager-info ''' + xml = netapp_utils.zapi.NaElement('xml') + attributes = { + 'num-records': 1, + 'attributes-list': { + 'key-manager-info': { + 'key-manager-ip-address': '0.0.0.0', + 'key-manager-server-status': 'available', + 'key-manager-tcp-port': '5696', + 'node-name': 'test_node' + } + } + } + xml.translate_struct(attributes) + return xml + + +class TestMyModule(unittest.TestCase): + ''' a group of related Unit Tests ''' + + def setUp(self): + self.mock_module_helper = patch.multiple(basic.AnsibleModule, + exit_json=exit_json, + fail_json=fail_json) + self.mock_module_helper.start() + self.addCleanup(self.mock_module_helper.stop) + self.mock_key_manager = { + 'node_name': 'test_node', + 'tcp_port': 5696, + 'ip_address': '0.0.0.0', + 'server_status': 'available' + } + + def mock_args(self): + return { + 'node': self.mock_key_manager['node_name'], + 'tcp_port': self.mock_key_manager['tcp_port'], + 'ip_address': self.mock_key_manager['ip_address'], + 'hostname': 'test', + 'username': 'test_user', + 'password': 'test_pass!', + 'https': 'False' + } + + def get_key_manager_mock_object(self, kind=None): + """ + Helper method to return an na_ontap_security_key_manager object + :param kind: passes this param to MockONTAPConnection() + :return: na_ontap_security_key_manager object + """ + obj = key_manager_module() + obj.asup_log_for_cserver = Mock(return_value=None) + obj.cluster = Mock() + obj.cluster.invoke_successfully = Mock() + if kind is None: + obj.cluster = MockONTAPConnection() + else: + obj.cluster = MockONTAPConnection(kind=kind, data=self.mock_key_manager) + 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({}) + key_manager_module() + print('Info: %s' % exc.value.args[0]['msg']) + + def test_get_nonexistent_key_manager(self): + ''' Test if get_key_manager() returns None for non-existent key manager ''' + set_module_args(self.mock_args()) + result = self.get_key_manager_mock_object().get_key_manager() + assert result is None + + def test_get_existing_key_manager(self): + ''' Test if get_key_manager() returns details for existing key manager ''' + set_module_args(self.mock_args()) + result = self.get_key_manager_mock_object('key_manager').get_key_manager() + assert result['ip_address'] == self.mock_key_manager['ip_address'] + + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_security_key_manager.NetAppOntapSecurityKeyManager.get_key_manager') + def test_successfully_add_key_manager(self, get_key_manager): + ''' Test successfully add key manager''' + data = self.mock_args() + data['state'] = 'present' + set_module_args(data) + get_key_manager.side_effect = [ + None + ] + obj = self.get_key_manager_mock_object('key_manager') + with pytest.raises(AnsibleExitJson) as exc: + obj.apply() + assert exc.value.args[0]['changed'] + + def test_successfully_delete_key_manager(self): + ''' Test successfully delete key manager''' + data = self.mock_args() + data['state'] = 'absent' + set_module_args(data) + obj = self.get_key_manager_mock_object('key_manager') + with pytest.raises(AnsibleExitJson) as exc: + obj.apply() + assert exc.value.args[0]['changed'] diff --git a/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_service_processor_network.py b/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_service_processor_network.py new file mode 100644 index 00000000..ed3596b6 --- /dev/null +++ b/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_service_processor_network.py @@ -0,0 +1,234 @@ +''' unit test template for ONTAP Ansible module ''' + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type +import json +import pytest + +import time +from ansible_collections.netapp.ontap.tests.unit.compat import unittest +from ansible_collections.netapp.ontap.tests.unit.compat.mock import patch, Mock +from ansible.module_utils import basic +from ansible.module_utils._text import to_bytes +import ansible_collections.netapp.ontap.plugins.module_utils.netapp as netapp_utils + +from ansible_collections.netapp.ontap.plugins.modules.na_ontap_service_processor_network \ + import NetAppOntapServiceProcessorNetwork as sp_module # module under test + +if not netapp_utils.has_netapp_lib(): + pytestmark = pytest.mark.skip('skipping as missing required netapp_lib') + + +def set_module_args(args): + """prepare arguments so that they will be picked up during module creation""" + args = json.dumps({'ANSIBLE_MODULE_ARGS': args}) + basic._ANSIBLE_ARGS = to_bytes(args) # pylint: disable=protected-access + + +class AnsibleExitJson(Exception): + """Exception class to be raised by module.exit_json and caught by the test case""" + pass + + +class AnsibleFailJson(Exception): + """Exception class to be raised by module.fail_json and caught by the test case""" + pass + + +def exit_json(*args, **kwargs): # pylint: disable=unused-argument + """function to patch over exit_json; package return data into an exception""" + if 'changed' not in kwargs: + kwargs['changed'] = False + raise AnsibleExitJson(kwargs) + + +def fail_json(*args, **kwargs): # pylint: disable=unused-argument + """function to patch over fail_json; package return data into an exception""" + kwargs['failed'] = True + raise AnsibleFailJson(kwargs) + + +class MockONTAPConnection(object): + ''' mock server connection to ONTAP host ''' + + def __init__(self, kind=None, data=None): + ''' save arguments ''' + self.kind = kind + self.data = data + self.xml_in = None + self.xml_out = None + + def invoke_successfully(self, xml, enable_tunneling): # pylint: disable=unused-argument + ''' mock invoke_successfully returning xml data ''' + self.xml_in = xml + if self.kind == 'sp-enabled': + xml = self.build_sp_info(self.data) + elif self.kind == 'sp-disabled': + xml = self.build_sp_disabled_info(self.data) + else: + xml = self.build_info() + self.xml_out = xml + return xml + + @staticmethod + def build_sp_info(sp): + ''' build xml data for vserser-info ''' + xml = netapp_utils.zapi.NaElement('xml') + attributes = { + 'num-records': 1, + 'attributes-list': + { + 'service-processor-network-info': + { + 'node-name': sp['node'], + 'is-enabled': 'true', + 'address-type': sp['address_type'], + 'dhcp': 'v4', + 'gateway-ip-address': sp['gateway_ip_address'], + 'netmask': sp['netmask'], + 'ip-address': sp['ip_address'], + 'setup-status': 'succeeded', + } + } + } + xml.translate_struct(attributes) + return xml + + @staticmethod + def build_info(): + ''' build xml data for vserser-info ''' + xml = netapp_utils.zapi.NaElement('xml') + attributes = { + 'num-records': 0 + } + xml.translate_struct(attributes) + return xml + + @staticmethod + def build_sp_disabled_info(sp): + ''' build xml data for vserser-info ''' + xml = netapp_utils.zapi.NaElement('xml') + attributes = { + 'num-records': 1, + 'attributes-list': + { + 'service-processor-network-info': + { + 'node-name': sp['node'], + 'is-enabled': 'false', + 'address-type': sp['address_type'], + 'setup-status': 'not_setup', + } + } + } + xml.translate_struct(attributes) + return xml + + +class TestMyModule(unittest.TestCase): + ''' a group of related Unit Tests ''' + + def setUp(self): + self.mock_module_helper = patch.multiple(basic.AnsibleModule, + exit_json=exit_json, + fail_json=fail_json) + self.mock_module_helper.start() + self.addCleanup(self.mock_module_helper.stop) + self.server = MockONTAPConnection() + self.mock_sp = { + 'node': 'test-vsim1', + 'gateway_ip_address': '2.2.2.2', + 'address_type': 'ipv4', + 'ip_address': '1.1.1.1', + 'netmask': '255.255.248.0', + 'dhcp': 'v4' + } + + def mock_args(self, enable=True): + data = { + 'node': self.mock_sp['node'], + 'is_enabled': enable, + 'address_type': self.mock_sp['address_type'], + 'hostname': 'host', + 'username': 'admin', + 'password': 'password', + } + if enable is True: + data['ip_address'] = self.mock_sp['ip_address'] + data['gateway_ip_address'] = self.mock_sp['gateway_ip_address'] + data['netmask'] = self.mock_sp['netmask'] + data['dhcp'] = 'v4' + return data + + def get_sp_mock_object(self, kind=None): + """ + Helper method to return an na_ontap_volume object + :param kind: passes this param to MockONTAPConnection() + :return: na_ontap_volume object + """ + sp_obj = sp_module() + sp_obj.autosupport_log = Mock(return_value=None) + if kind is None: + sp_obj.server = MockONTAPConnection() + else: + sp_obj.server = MockONTAPConnection(kind=kind, data=self.mock_sp) + return sp_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({}) + sp_module() + print('Info: %s' % exc.value.args[0]['msg']) + + def test_modify_error_on_disabled_sp(self): + ''' a more interesting test ''' + data = self.mock_args(enable=False) + data['ip_address'] = self.mock_sp['ip_address'] + set_module_args(data) + with pytest.raises(AnsibleFailJson) as exc: + self.get_sp_mock_object('sp-disabled').apply() + assert 'Error: Cannot modify a service processor network if it is disabled.' in \ + exc.value.args[0]['msg'] + + def test_modify_sp(self): + ''' a more interesting test ''' + data = self.mock_args() + data['ip_address'] = '3.3.3.3' + set_module_args(data) + with pytest.raises(AnsibleExitJson) as exc: + self.get_sp_mock_object('sp-enabled').apply() + assert exc.value.args[0]['changed'] + + def test_modify_sp_wait(self): + ''' a more interesting test ''' + data = self.mock_args() + data['ip_address'] = '3.3.3.3' + data['wait_for_completion'] = True + set_module_args(data) + with pytest.raises(AnsibleExitJson) as exc: + self.get_sp_mock_object('sp-enabled').apply() + assert exc.value.args[0]['changed'] + + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_service_processor_network.NetAppOntapServiceProcessorNetwork.' + 'get_service_processor_network') + def test_non_existing_sp(self, get_sp): + set_module_args(self.mock_args()) + get_sp.return_value = None + with pytest.raises(AnsibleFailJson) as exc: + self.get_sp_mock_object().apply() + assert 'Error No Service Processor for node: test-vsim1' in exc.value.args[0]['msg'] + + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_service_processor_network.NetAppOntapServiceProcessorNetwork.' + 'get_sp_network_status') + @patch('time.sleep') + def test_wait_on_sp_status(self, get_sp, sleep): + data = self.mock_args() + data['gateway_ip_address'] = '4.4.4.4' + data['wait_for_completion'] = True + set_module_args(data) + get_sp.side_effect = ['in_progress', 'done'] + with pytest.raises(AnsibleExitJson) as exc: + self.get_sp_mock_object('sp-enabled').apply() + sleep.assert_called_once_with() + assert exc.value.args[0]['changed'] diff --git a/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_snapmirror.py b/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_snapmirror.py new file mode 100644 index 00000000..b568e218 --- /dev/null +++ b/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_snapmirror.py @@ -0,0 +1,630 @@ +''' unit tests ONTAP Ansible module: na_ontap_snapmirror ''' + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type +import json +import pytest + +from ansible_collections.netapp.ontap.tests.unit.compat import unittest +from ansible_collections.netapp.ontap.tests.unit.compat.mock import patch, Mock +from ansible.module_utils import basic +from ansible.module_utils._text import to_bytes +import ansible_collections.netapp.ontap.plugins.module_utils.netapp as netapp_utils + +from ansible_collections.netapp.ontap.plugins.modules.na_ontap_snapmirror \ + import NetAppONTAPSnapmirror as my_module + +if not netapp_utils.has_netapp_lib(): + pytestmark = pytest.mark.skip('skipping as missing required netapp_lib') + + +def set_module_args(args): + """prepare arguments so that they will be picked up during module creation""" + args = json.dumps({'ANSIBLE_MODULE_ARGS': args}) + basic._ANSIBLE_ARGS = to_bytes(args) # pylint: disable=protected-access + + +class AnsibleExitJson(Exception): + """Exception class to be raised by module.exit_json and caught by the test case""" + pass + + +class AnsibleFailJson(Exception): + """Exception class to be raised by module.fail_json and caught by the test case""" + pass + + +def exit_json(*args, **kwargs): # pylint: disable=unused-argument + """function to patch over exit_json; package return data into an exception""" + if 'changed' not in kwargs: + kwargs['changed'] = False + raise AnsibleExitJson(kwargs) + + +def fail_json(*args, **kwargs): # pylint: disable=unused-argument + """function to patch over fail_json; package return data into an exception""" + kwargs['failed'] = True + raise AnsibleFailJson(kwargs) + + +class MockONTAPConnection(object): + ''' mock server connection to ONTAP host ''' + + def __init__(self, kind=None, parm=None, status=None, quiesce_status='passed'): + ''' save arguments ''' + self.type = kind + self.xml_in = None + self.xml_out = None + self.parm = parm + self.status = status + self.quiesce_status = quiesce_status + + def invoke_successfully(self, xml, enable_tunneling): # pylint: disable=unused-argument + ''' mock invoke_successfully returning xml data ''' + self.xml_in = xml + if self.type == 'snapmirror': + xml = self.build_snapmirror_info(self.parm, self.status, self.quiesce_status) + elif self.type == 'snapmirror_fail': + raise netapp_utils.zapi.NaApiError(code='TEST', message="This exception is from the unit test") + self.xml_out = xml + return xml + + @staticmethod + def build_snapmirror_info(mirror_state, status, quiesce_status): + ''' build xml data for snapmirror-entry ''' + xml = netapp_utils.zapi.NaElement('xml') + data = {'num-records': 1, + 'status': quiesce_status, + 'attributes-list': {'snapmirror-info': {'mirror-state': mirror_state, 'schedule': None, + 'source-location': 'ansible:ansible', + 'relationship-status': status, 'policy': 'ansible', + 'relationship-type': 'data_protection', + 'max-transfer-rate': 1000, + 'identity-preserve': 'true'}, + 'snapmirror-destination-info': {'destination-location': 'ansible'}}} + xml.translate_struct(data) + return xml + + +class TestMyModule(unittest.TestCase): + ''' a group of related Unit Tests ''' + + def setUp(self): + self.mock_module_helper = patch.multiple(basic.AnsibleModule, + exit_json=exit_json, + fail_json=fail_json) + self.mock_module_helper.start() + self.addCleanup(self.mock_module_helper.stop) + self.server = MockONTAPConnection() + self.source_server = MockONTAPConnection() + self.onbox = False + + def set_default_args(self): + if self.onbox: + hostname = '10.10.10.10' + username = 'admin' + password = 'password' + source_path = 'ansible:ansible' + destination_path = 'ansible:ansible' + policy = 'ansible' + source_vserver = 'ansible' + destination_vserver = 'ansible' + relationship_type = 'data_protection' + schedule = None + source_username = 'admin' + source_password = 'password' + relationship_state = 'active' + update = True + else: + hostname = '10.10.10.10' + username = 'admin' + password = 'password' + source_path = 'ansible:ansible' + destination_path = 'ansible:ansible' + policy = 'ansible' + source_vserver = 'ansible' + destination_vserver = 'ansible' + relationship_type = 'data_protection' + schedule = None + source_username = 'admin' + source_password = 'password' + relationship_state = 'active' + update = True + return dict({ + 'hostname': hostname, + 'username': username, + 'password': password, + 'source_path': source_path, + 'destination_path': destination_path, + 'policy': policy, + 'source_vserver': source_vserver, + 'destination_vserver': destination_vserver, + 'relationship_type': relationship_type, + 'schedule': schedule, + 'source_username': source_username, + 'source_password': source_password, + 'relationship_state': relationship_state, + 'update': update + }) + + def test_module_fail_when_required_args_missing(self): + ''' required arguments are reported as errors ''' + with pytest.raises(AnsibleFailJson) as exc: + set_module_args({}) + my_module() + print('Info: %s' % exc.value.args[0]['msg']) + + def test_ensure_get_called(self): + ''' test snapmirror_get for non-existent snapmirror''' + set_module_args(self.set_default_args()) + my_obj = my_module() + my_obj.server = self.server + assert my_obj.snapmirror_get is not None + + def test_ensure_get_called_existing(self): + ''' test snapmirror_get for existing snapmirror''' + set_module_args(self.set_default_args()) + my_obj = my_module() + my_obj.server = MockONTAPConnection(kind='snapmirror', status='idle') + assert my_obj.snapmirror_get() + + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_snapmirror.NetAppONTAPSnapmirror.snapmirror_create') + def test_successful_create(self, snapmirror_create): + ''' creating snapmirror and testing idempotency ''' + data = self.set_default_args() + data['schedule'] = 'abc' + data['identity_preserve'] = True + set_module_args(data) + my_obj = my_module() + my_obj.asup_log_for_cserver = Mock(return_value=None) + if not self.onbox: + my_obj.server = self.server + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + assert exc.value.args[0]['changed'] + snapmirror_create.assert_called_with() + # to reset na_helper from remembering the previous 'changed' value + data = self.set_default_args() + data['update'] = False + set_module_args(data) + my_obj = my_module() + my_obj.asup_log_for_cserver = Mock(return_value=None) + if not self.onbox: + my_obj.server = MockONTAPConnection('snapmirror', 'snapmirrored', status='idle') + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + assert not exc.value.args[0]['changed'] + + def test_failure_break(self): + ''' breaking snapmirror to test quiesce time-delay failure ''' + data = self.set_default_args() + data['relationship_state'] = 'broken' + set_module_args(data) + my_obj = my_module() + my_obj.asup_log_for_cserver = Mock(return_value=None) + if not self.onbox: + my_obj.server = MockONTAPConnection('snapmirror', 'snapmirrored', status='idle', quiesce_status='InProgress') + with pytest.raises(AnsibleFailJson) as exc: + # replace time.sleep with a noop + with patch('time.sleep', lambda a: None): + my_obj.apply() + assert 'Taking a long time to Quiescing SnapMirror, try again later' in exc.value.args[0]['msg'] + + def test_successful_break(self): + ''' breaking snapmirror and testing idempotency ''' + data = self.set_default_args() + data['relationship_state'] = 'broken' + set_module_args(data) + my_obj = my_module() + my_obj.asup_log_for_cserver = Mock(return_value=None) + if not self.onbox: + my_obj.server = MockONTAPConnection('snapmirror', 'snapmirrored', status='idle') + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + assert exc.value.args[0]['changed'] + # to reset na_helper from remembering the previous 'changed' value + set_module_args(self.set_default_args()) + my_obj = my_module() + my_obj.asup_log_for_cserver = Mock(return_value=None) + if not self.onbox: + my_obj.server = MockONTAPConnection('snapmirror', 'broken-off', status='idle') + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + assert exc.value.args[0]['changed'] + + def test_successful_create_without_initialize(self): + ''' creating snapmirror and testing idempotency ''' + data = self.set_default_args() + data['schedule'] = 'abc' + data['identity_preserve'] = True + data['initialize'] = False + set_module_args(data) + my_obj = my_module() + my_obj.asup_log_for_cserver = Mock(return_value=None) + my_obj.server = MockONTAPConnection('snapmirror', 'Uninitialized', status='idle') + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + assert exc.value.args[0]['changed'] + + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_snapmirror.NetAppONTAPSnapmirror.snapmirror_create') + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_snapmirror.NetAppONTAPSnapmirror.check_elementsw_parameters') + def test_successful_element_ontap_create(self, check_param, snapmirror_create): + ''' creating ElementSW to ONTAP snapmirror ''' + data = self.set_default_args() + data['schedule'] = 'abc' + data['connection_type'] = 'elementsw_ontap' + data['source_hostname'] = '10.10.10.10' + set_module_args(data) + my_obj = my_module() + my_obj.asup_log_for_cserver = Mock(return_value=None) + if not self.onbox: + my_obj.server = self.server + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + assert exc.value.args[0]['changed'] + snapmirror_create.assert_called_with() + check_param.assert_called_with() + + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_snapmirror.NetAppONTAPSnapmirror.snapmirror_create') + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_snapmirror.NetAppONTAPSnapmirror.check_elementsw_parameters') + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_snapmirror.NetAppONTAPSnapmirror.snapmirror_get') + def test_successful_ontap_element_create(self, snapmirror_get, check_param, snapmirror_create): + ''' creating ONTAP to ElementSW snapmirror ''' + data = self.set_default_args() + data['schedule'] = 'abc' + data['connection_type'] = 'ontap_elementsw' + data['source_hostname'] = '10.10.10.10' + set_module_args(data) + my_obj = my_module() + my_obj.asup_log_for_cserver = Mock(return_value=None) + snapmirror_get.side_effect = [ + Mock(), + None + ] + if not self.onbox: + my_obj.server = self.server + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + assert exc.value.args[0]['changed'] + snapmirror_create.assert_called_with() + check_param.assert_called_with('destination') + + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_snapmirror.NetAppONTAPSnapmirror.delete_snapmirror') + def test_successful_delete(self, delete_snapmirror): + ''' deleting snapmirror and testing idempotency ''' + data = self.set_default_args() + data['state'] = 'absent' + data['source_hostname'] = '10.10.10.10' + set_module_args(data) + my_obj = my_module() + my_obj.asup_log_for_cserver = Mock(return_value=None) + my_obj.get_destination = Mock(return_value=True) + if not self.onbox: + my_obj.server = MockONTAPConnection('snapmirror', status='idle') + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + assert exc.value.args[0]['changed'] + delete_snapmirror.assert_called_with(False, 'data_protection', None) + # to reset na_helper from remembering the previous 'changed' value + my_obj = my_module() + my_obj.asup_log_for_cserver = Mock(return_value=None) + if not self.onbox: + my_obj.server = self.server + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + assert not exc.value.args[0]['changed'] + + def test_successful_delete_error_check(self): + ''' check required parameter source cluster hostname deleting snapmirror''' + data = self.set_default_args() + data['state'] = 'absent' + set_module_args(data) + my_obj = my_module() + my_obj.asup_log_for_cserver = Mock(return_value=None) + if not self.onbox: + my_obj.server = MockONTAPConnection('snapmirror', status='idle') + with pytest.raises(AnsibleFailJson) as exc: + my_obj.apply() + assert 'Missing parameters for delete:' in exc.value.args[0]['msg'] + + def test_successful_delete_check_get_destination(self): + ''' check required parameter source cluster hostname deleting snapmirror''' + data = self.set_default_args() + data['state'] = 'absent' + data['source_hostname'] = '10.10.10.10' + set_module_args(data) + my_obj = my_module() + my_obj.asup_log_for_cserver = Mock(return_value=None) + if not self.onbox: + my_obj.server = MockONTAPConnection('snapmirror', status='idle') + my_obj.source_server = MockONTAPConnection('snapmirror', status='idle') + res = my_obj.get_destination() + assert res is True + + def test_snapmirror_release(self): + data = self.set_default_args() + set_module_args(data) + my_obj = my_module() + my_obj.asup_log_for_cserver = Mock(return_value=None) + if not self.onbox: + my_obj.source_server = MockONTAPConnection('snapmirror', status='idle', parm='snapmirrored') + my_obj.snapmirror_release() + assert my_obj.source_server.xml_in['destination-location'] == data['destination_path'] + + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_snapmirror.NetAppONTAPSnapmirror.snapmirror_resume') + def test_snapmirror_resume(self, snapmirror_resume): + ''' resuming snapmirror ''' + data = self.set_default_args() + set_module_args(data) + my_obj = my_module() + my_obj.asup_log_for_cserver = Mock(return_value=None) + if not self.onbox: + my_obj.server = MockONTAPConnection('snapmirror', status='quiesced', parm='snapmirrored') + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + assert exc.value.args[0]['changed'] + snapmirror_resume.assert_called_with() + + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_snapmirror.NetAppONTAPSnapmirror.snapmirror_restore') + def test_snapmirror_restore(self, snapmirror_restore): + ''' restore snapmirror ''' + data = self.set_default_args() + data['relationship_type'] = 'restore' + set_module_args(data) + my_obj = my_module() + my_obj.asup_log_for_cserver = Mock(return_value=None) + if not self.onbox: + my_obj.server = self.server + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + assert exc.value.args[0]['changed'] + snapmirror_restore.assert_called_with() + + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_snapmirror.NetAppONTAPSnapmirror.snapmirror_abort') + def test_successful_abort(self, snapmirror_abort): + ''' deleting snapmirror and testing idempotency ''' + data = self.set_default_args() + data['state'] = 'absent' + data['source_hostname'] = '10.10.10.10' + set_module_args(data) + my_obj = my_module() + my_obj.asup_log_for_cserver = Mock(return_value=None) + if not self.onbox: + my_obj.server = MockONTAPConnection('snapmirror', status='transferring') + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + assert exc.value.args[0]['changed'] + snapmirror_abort.assert_called_with() + # to reset na_helper from remembering the previous 'changed' value + my_obj = my_module() + my_obj.asup_log_for_cserver = Mock(return_value=None) + if not self.onbox: + my_obj.server = self.server + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + assert not exc.value.args[0]['changed'] + + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_snapmirror.NetAppONTAPSnapmirror.snapmirror_modify') + def test_successful_modify(self, snapmirror_modify): + ''' modifying snapmirror and testing idempotency ''' + data = self.set_default_args() + data['policy'] = 'ansible2' + data['schedule'] = 'abc2' + data['max_transfer_rate'] = 2000 + set_module_args(data) + my_obj = my_module() + my_obj.asup_log_for_cserver = Mock(return_value=None) + if not self.onbox: + my_obj.server = MockONTAPConnection('snapmirror', status='idle') + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + assert exc.value.args[0]['changed'] + snapmirror_modify.assert_called_with({'policy': 'ansible2', 'schedule': 'abc2', 'max_transfer_rate': 2000}) + # to reset na_helper from remembering the previous 'changed' value + data = self.set_default_args() + data['update'] = False + set_module_args(data) + my_obj = my_module() + my_obj.asup_log_for_cserver = Mock(return_value=None) + if not self.onbox: + my_obj.server = MockONTAPConnection('snapmirror', status='idle', parm='snapmirrored') + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + assert not exc.value.args[0]['changed'] + + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_snapmirror.NetAppONTAPSnapmirror.snapmirror_initialize') + def test_successful_initialize(self, snapmirror_initialize): + ''' initialize snapmirror and testing idempotency ''' + data = self.set_default_args() + set_module_args(data) + my_obj = my_module() + my_obj.asup_log_for_cserver = Mock(return_value=None) + if not self.onbox: + my_obj.server = MockONTAPConnection('snapmirror', status='idle', parm='uninitialized') + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + assert exc.value.args[0]['changed'] + snapmirror_initialize.assert_called_with() + # to reset na_helper from remembering the previous 'changed' value + data = self.set_default_args() + data['update'] = False + set_module_args(data) + my_obj = my_module() + my_obj.asup_log_for_cserver = Mock(return_value=None) + if not self.onbox: + my_obj.server = MockONTAPConnection('snapmirror', status='idle', parm='snapmirrored') + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + assert not exc.value.args[0]['changed'] + + def test_successful_update(self): + ''' update snapmirror and testing idempotency ''' + data = self.set_default_args() + data['policy'] = 'ansible2' + set_module_args(data) + my_obj = my_module() + my_obj.asup_log_for_cserver = Mock(return_value=None) + if not self.onbox: + my_obj.server = MockONTAPConnection('snapmirror', status='idle') + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + assert exc.value.args[0]['changed'] + + def test_elementsw_volume_exists(self): + ''' elementsw_volume_exists ''' + data = self.set_default_args() + set_module_args(data) + my_obj = my_module() + my_obj.asup_log_for_cserver = Mock(return_value=None) + mock_helper = Mock() + mock_helper.volume_id_exists.side_effect = [1000, None] + if not self.onbox: + my_obj.server = MockONTAPConnection('snapmirror', status='idle', parm='snapmirrored') + my_obj.check_if_elementsw_volume_exists('10.10.10.10:/lun/1000', mock_helper) + with pytest.raises(AnsibleFailJson) as exc: + my_obj.check_if_elementsw_volume_exists('10.10.10.10:/lun/1000', mock_helper) + assert 'Error: Source volume does not exist in the ElementSW cluster' in exc.value.args[0]['msg'] + + def test_elementsw_svip_exists(self): + ''' svip_exists ''' + data = self.set_default_args() + set_module_args(data) + my_obj = my_module() + my_obj.asup_log_for_cserver = Mock(return_value=None) + mock_helper = Mock() + mock_helper.get_cluster_info.return_value.cluster_info.svip = '10.10.10.10' + if not self.onbox: + my_obj.server = MockONTAPConnection('snapmirror', status='idle', parm='snapmirrored') + my_obj.validate_elementsw_svip('10.10.10.10:/lun/1000', mock_helper) + + def test_elementsw_svip_exists_negative(self): + ''' svip_exists negative testing''' + data = self.set_default_args() + set_module_args(data) + my_obj = my_module() + my_obj.asup_log_for_cserver = Mock(return_value=None) + mock_helper = Mock() + mock_helper.get_cluster_info.return_value.cluster_info.svip = '10.10.10.10' + if not self.onbox: + my_obj.server = MockONTAPConnection('snapmirror', status='idle', parm='snapmirrored') + with pytest.raises(AnsibleFailJson) as exc: + my_obj.validate_elementsw_svip('10.10.10.11:/lun/1000', mock_helper) + assert 'Error: Invalid SVIP' in exc.value.args[0]['msg'] + + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_snapmirror.NetAppONTAPSnapmirror.set_element_connection') + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_snapmirror.NetAppONTAPSnapmirror.validate_elementsw_svip') + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_snapmirror.NetAppONTAPSnapmirror.check_if_elementsw_volume_exists') + def test_check_elementsw_params_source(self, validate_volume, validate_svip, connection): + ''' check elementsw parameters for source ''' + data = self.set_default_args() + data['source_path'] = '10.10.10.10:/lun/1000' + set_module_args(data) + my_obj = my_module() + my_obj.asup_log_for_cserver = Mock(return_value=None) + mock_elem, mock_helper = Mock(), Mock() + connection.return_value = mock_helper, mock_elem + if not self.onbox: + my_obj.server = MockONTAPConnection('snapmirror', status='idle', parm='snapmirrored') + my_obj.check_elementsw_parameters('source') + connection.called_once_with('source') + validate_svip.called_once_with(data['source_path'], mock_elem) + validate_volume.called_once_with(data['source_path'], mock_helper) + + def test_check_elementsw_params_negative(self): + ''' check elementsw parameters for source negative testing ''' + data = self.set_default_args() + del data['source_path'] + set_module_args(data) + my_obj = my_module() + my_obj.asup_log_for_cserver = Mock(return_value=None) + if not self.onbox: + my_obj.server = MockONTAPConnection('snapmirror', status='idle', parm='snapmirrored') + with pytest.raises(AnsibleFailJson) as exc: + my_obj.check_elementsw_parameters('source') + assert 'Error: Missing required parameter source_path' in exc.value.args[0]['msg'] + + def test_check_elementsw_params_invalid(self): + ''' check elementsw parameters for source invalid testing ''' + data = self.set_default_args() + set_module_args(data) + my_obj = my_module() + my_obj.asup_log_for_cserver = Mock(return_value=None) + if not self.onbox: + my_obj.server = MockONTAPConnection('snapmirror', status='idle', parm='snapmirrored') + with pytest.raises(AnsibleFailJson) as exc: + my_obj.check_elementsw_parameters('source') + assert 'Error: invalid source_path' in exc.value.args[0]['msg'] + + def test_elementsw_source_path_format(self): + ''' test element_source_path_format_matches ''' + data = self.set_default_args() + set_module_args(data) + my_obj = my_module() + my_obj.asup_log_for_cserver = Mock(return_value=None) + if not self.onbox: + my_obj.server = MockONTAPConnection('snapmirror', status='idle', parm='snapmirrored') + match = my_obj.element_source_path_format_matches('1.1.1.1:dummy') + assert match is None + match = my_obj.element_source_path_format_matches('10.10.10.10:/lun/10') + assert match is not None + + def test_remote_volume_exists(self): + ''' test check_if_remote_volume_exists ''' + data = self.set_default_args() + data['source_volume'] = 'test_vol' + data['destination_volume'] = 'test_vol2' + set_module_args(data) + my_obj = my_module() + my_obj.set_source_cluster_connection = Mock(return_value=None) + my_obj.asup_log_for_cserver = Mock(return_value=None) + if not self.onbox: + my_obj.server = MockONTAPConnection('snapmirror', status='idle', parm='snapmirrored') + my_obj.source_server = MockONTAPConnection('snapmirror', status='idle', parm='snapmirrored') + res = my_obj.check_if_remote_volume_exists() + assert res + + def test_if_all_methods_catch_exception(self): + data = self.set_default_args() + data['source_hostname'] = '10.10.10.10' + data['source_volume'] = 'ansible' + data['destination_volume'] = 'ansible2' + set_module_args(data) + my_obj = my_module() + if not self.onbox: + my_obj.server = MockONTAPConnection('snapmirror_fail') + with pytest.raises(AnsibleFailJson) as exc: + my_obj.snapmirror_get() + assert 'Error fetching snapmirror info: ' in exc.value.args[0]['msg'] + with pytest.raises(AnsibleFailJson) as exc: + my_obj.snapmirror_abort() + assert 'Error aborting SnapMirror relationship :' in exc.value.args[0]['msg'] + with pytest.raises(AnsibleFailJson) as exc: + my_obj.snapmirror_quiesce = Mock(return_value=None) + my_obj.snapmirror_break() + assert 'Error breaking SnapMirror relationship :' in exc.value.args[0]['msg'] + with pytest.raises(AnsibleFailJson) as exc: + my_obj.snapmirror_get = Mock(return_value={'mirror_state': 'transferring'}) + my_obj.snapmirror_initialize() + assert 'Error initializing SnapMirror :' in exc.value.args[0]['msg'] + with pytest.raises(AnsibleFailJson) as exc: + my_obj.snapmirror_update() + assert 'Error updating SnapMirror :' in exc.value.args[0]['msg'] + with pytest.raises(AnsibleFailJson) as exc: + my_obj.set_source_cluster_connection = Mock(return_value=True) + my_obj.source_server = MockONTAPConnection('snapmirror_fail') + my_obj.check_if_remote_volume_exists() + assert 'Error fetching source volume details' in exc.value.args[0]['msg'] + with pytest.raises(AnsibleFailJson) as exc: + my_obj.check_if_remote_volume_exists = Mock(return_value=True) + my_obj.source_server = MockONTAPConnection() + my_obj.snapmirror_create() + assert 'Error creating SnapMirror ' in exc.value.args[0]['msg'] + with pytest.raises(AnsibleFailJson) as exc: + my_obj.snapmirror_quiesce = Mock(return_value=None) + my_obj.get_destination = Mock(return_value=None) + my_obj.snapmirror_break = Mock(return_value=None) + my_obj.delete_snapmirror(False, 'data_protection', None) + assert 'Error deleting SnapMirror :' in exc.value.args[0]['msg'] + with pytest.raises(AnsibleFailJson) as exc: + my_obj.snapmirror_modify({'policy': 'ansible2', 'schedule': 'abc2'}) + assert 'Error modifying SnapMirror schedule or policy :' in exc.value.args[0]['msg'] diff --git a/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_snapmirror_policy.py b/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_snapmirror_policy.py new file mode 100644 index 00000000..2b62e4dc --- /dev/null +++ b/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_snapmirror_policy.py @@ -0,0 +1,717 @@ +''' unit tests ONTAP Ansible module: na_ontap_snapmirror_policy ''' + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type +import json +import pytest + +from ansible_collections.netapp.ontap.tests.unit.compat import unittest +from ansible_collections.netapp.ontap.tests.unit.compat.mock import patch, Mock +from ansible.module_utils import basic +from ansible.module_utils._text import to_bytes +import ansible_collections.netapp.ontap.plugins.module_utils.netapp as netapp_utils + +from ansible_collections.netapp.ontap.plugins.modules.na_ontap_snapmirror_policy \ + import NetAppOntapSnapMirrorPolicy as my_module + +if not netapp_utils.has_netapp_lib(): + pytestmark = pytest.mark.skip('skipping as missing required netapp_lib') + +# REST API canned responses when mocking send_request +SRR = { + # common responses + 'is_rest': (200, {}, None), + 'is_zapi': (400, {}, "Unreachable"), + 'empty_good': ({}, None), + 'end_of_sequence': (None, "Unexpected call to send_request"), + 'generic_error': (None, "Expected error"), + # module specific responses + 'get_snapmirror_policy': {'vserver': 'ansible', + 'policy_name': 'ansible', + 'uuid': 'abcdef12-3456-7890-abcd-ef1234567890', + 'comment': 'created by ansible', + 'policy_type': 'async_mirror', + 'snapmirror_label': [], + 'keep': [], + 'schedule': [], + 'prefix': []}, + 'get_snapmirror_policy_with_rules': {'vserver': 'ansible', + 'policy_name': 'ansible', + 'uuid': 'abcdef12-3456-7890-abcd-ef1234567890', + 'comment': 'created by ansible', + 'policy_type': 'async_mirror', + 'snapmirror_label': ['daily', 'weekly', 'monthly'], + 'keep': [7, 5, 12], + 'schedule': ['', 'weekly', 'monthly'], + 'prefix': ['', 'weekly', 'monthly']} +} + + +def set_module_args(args): + """prepare arguments so that they will be picked up during module creation""" + args = json.dumps({'ANSIBLE_MODULE_ARGS': args}) + basic._ANSIBLE_ARGS = to_bytes(args) # pylint: disable=protected-access + + +class AnsibleExitJson(Exception): + """Exception class to be raised by module.exit_json and caught by the test case""" + pass + + +class AnsibleFailJson(Exception): + """Exception class to be raised by module.fail_json and caught by the test case""" + pass + + +def exit_json(*args, **kwargs): # pylint: disable=unused-argument + """function to patch over exit_json; package return data into an exception""" + if 'changed' not in kwargs: + kwargs['changed'] = False + raise AnsibleExitJson(kwargs) + + +def fail_json(*args, **kwargs): # pylint: disable=unused-argument + """function to patch over fail_json; package return data into an exception""" + kwargs['failed'] = True + raise AnsibleFailJson(kwargs) + + +class MockONTAPConnection(object): + ''' mock server connection to ONTAP host ''' + + def __init__(self, kind=None, parm=None): + ''' save arguments ''' + self.type = kind + self.xml_in = None + self.xml_out = None + self.parm = parm + + def invoke_successfully(self, xml, enable_tunneling): # pylint: disable=unused-argument + ''' mock invoke_successfully returning xml data ''' + self.xml_in = xml + if self.type == 'snapmirror_policy': + xml = self.build_snapmirror_policy_info(self.parm) + elif self.type == 'snapmirror_policy_fail': + raise netapp_utils.zapi.NaApiError(code='TEST', message="This exception is from the unit test") + self.xml_out = xml + return xml + + @staticmethod + def build_snapmirror_policy_info(mirror_state): + ''' build xml data for snapmirror_policy-entry ''' + xml = netapp_utils.zapi.NaElement('xml') + data = {'num-records': 1, + 'attributes-list': {'snapmirror-policy-info': {'comment': 'created by ansible', + 'policy-name': 'ansible', + 'type': 'async_mirror', + 'tries': '8', + 'transfer-priority': 'normal', + 'restart': 'always', + 'is-network-compression-enabled': False, + 'ignore-atime': False, + 'vserver-name': 'ansible'}}} + xml.translate_struct(data) + return xml + + +class TestMyModule(unittest.TestCase): + ''' a group of related Unit Tests ''' + + def setUp(self): + self.mock_module_helper = patch.multiple(basic.AnsibleModule, + exit_json=exit_json, + fail_json=fail_json) + self.mock_module_helper.start() + self.addCleanup(self.mock_module_helper.stop) + self.server = MockONTAPConnection() + self.source_server = MockONTAPConnection() + self.onbox = False + + def set_default_args(self, use_rest=None, with_rules=False): + if self.onbox: + hostname = '10.10.10.10' + username = 'admin' + password = 'password' + vserver = 'ansible' + policy_name = 'ansible' + policy_type = 'async_mirror' + comment = 'created by ansible' + snapmirror_label = ['daily', 'weekly', 'monthly'] + keep = [7, 5, 12] + schedule = ['', 'weekly', 'monthly'] + prefix = ['', 'weekly', 'monthly'] + else: + hostname = '10.10.10.10' + username = 'admin' + password = 'password' + vserver = 'ansible' + policy_name = 'ansible' + policy_type = 'async_mirror' + comment = 'created by ansible' + snapmirror_label = ['daily', 'weekly', 'monthly'] + keep = [7, 5, 12] + schedule = ['', 'weekly', 'monthly'] + prefix = ['', 'weekly', 'monthly'] + + args = dict({ + 'hostname': hostname, + 'username': username, + 'password': password, + 'vserver': vserver, + 'policy_name': policy_name, + 'policy_type': policy_type, + 'comment': comment + }) + + if with_rules: + args['snapmirror_label'] = snapmirror_label + args['keep'] = keep + args['schedule'] = schedule + args['prefix'] = prefix + + if use_rest is not None: + args['use_rest'] = use_rest + + return args + + def test_module_fail_when_required_args_missing(self): + ''' required arguments are reported as errors ''' + with pytest.raises(AnsibleFailJson) as exc: + set_module_args({}) + my_module() + print('Info: %s' % exc.value.args[0]['msg']) + + def test_ensure_get_called(self): + ''' test get_snapmirror_policy for non-existent snapmirror policy''' + set_module_args(self.set_default_args(use_rest='Never')) + my_obj = my_module() + my_obj.server = self.server + assert my_obj.get_snapmirror_policy is not None + + def test_ensure_get_called_existing(self): + ''' test get_snapmirror_policy for existing snapmirror policy''' + set_module_args(self.set_default_args(use_rest='Never')) + my_obj = my_module() + my_obj.server = MockONTAPConnection(kind='snapmirror_policy') + assert my_obj.get_snapmirror_policy() + + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_snapmirror_policy.NetAppOntapSnapMirrorPolicy.create_snapmirror_policy') + def test_successful_create(self, snapmirror_create_policy): + ''' creating snapmirror policy without rules and testing idempotency ''' + data = self.set_default_args(use_rest='Never') + set_module_args(data) + my_obj = my_module() + my_obj.asup_log_for_cserver = Mock(return_value=None) + if not self.onbox: + my_obj.server = self.server + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + assert exc.value.args[0]['changed'] + snapmirror_create_policy.assert_called_with() + # to reset na_helper from remembering the previous 'changed' value + data = self.set_default_args(use_rest='Never') + set_module_args(data) + my_obj = my_module() + my_obj.asup_log_for_cserver = Mock(return_value=None) + if not self.onbox: + my_obj.server = MockONTAPConnection('snapmirror_policy') + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + assert not exc.value.args[0]['changed'] + + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_snapmirror_policy.NetAppOntapSnapMirrorPolicy.create_snapmirror_policy') + def test_successful_create_with_rest(self, snapmirror_create_policy): + ''' creating snapmirror policy without rules via REST and testing idempotency ''' + data = self.set_default_args() + data['use_rest'] = 'Always' + set_module_args(data) + my_obj = my_module() + my_obj.asup_log_for_cserver = Mock(return_value=None) + my_obj.get_snapmirror_policy = Mock() + my_obj.get_snapmirror_policy.side_effect = [None, SRR['get_snapmirror_policy']] + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + assert exc.value.args[0]['changed'] + snapmirror_create_policy.assert_called_with() + # to reset na_helper from remembering the previous 'changed' value + data = self.set_default_args(use_rest='Never') + data['use_rest'] = 'Always' + set_module_args(data) + my_obj = my_module() + my_obj.asup_log_for_cserver = Mock(return_value=None) + my_obj.get_snapmirror_policy = Mock(return_value=SRR['get_snapmirror_policy']) + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + assert not exc.value.args[0]['changed'] + + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_snapmirror_policy.NetAppOntapSnapMirrorPolicy.create_snapmirror_policy') + def test_successful_create_with_rules(self, snapmirror_create_policy): + ''' creating snapmirror policy with rules and testing idempotency ''' + data = self.set_default_args(use_rest='Never', with_rules=True) + set_module_args(data) + my_obj = my_module() + my_obj.asup_log_for_cserver = Mock(return_value=None) + my_obj.get_snapmirror_policy = Mock(return_value=None) + if not self.onbox: + my_obj.server = self.server + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + assert exc.value.args[0]['changed'] + snapmirror_create_policy.assert_called_with() + # to reset na_helper from remembering the previous 'changed' value + data = self.set_default_args(use_rest='Never', with_rules=True) + set_module_args(data) + my_obj = my_module() + my_obj.asup_log_for_cserver = Mock(return_value=None) + my_obj.get_snapmirror_policy = Mock(return_value=SRR['get_snapmirror_policy_with_rules']) + if not self.onbox: + my_obj.server = MockONTAPConnection('snapmirror_policy') + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + assert not exc.value.args[0]['changed'] + + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_snapmirror_policy.NetAppOntapSnapMirrorPolicy.modify_snapmirror_policy_rules') + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_snapmirror_policy.NetAppOntapSnapMirrorPolicy.create_snapmirror_policy') + def test_successful_create_with_rules_via_rest(self, snapmirror_create_policy, modify_snapmirror_policy_rules): + ''' creating snapmirror policy with rules via rest and testing idempotency ''' + data = self.set_default_args(use_rest='Always', with_rules=True) + set_module_args(data) + my_obj = my_module() + my_obj.asup_log_for_cserver = Mock(return_value=None) + my_obj.get_snapmirror_policy = Mock() + my_obj.get_snapmirror_policy.side_effect = [None, SRR['get_snapmirror_policy']] + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + assert exc.value.args[0]['changed'] + snapmirror_create_policy.assert_called_with() + # to reset na_helper from remembering the previous 'changed' value + data = self.set_default_args(use_rest='Always', with_rules=True) + set_module_args(data) + my_obj = my_module() + my_obj.asup_log_for_cserver = Mock(return_value=None) + my_obj.get_snapmirror_policy = Mock(return_value=SRR['get_snapmirror_policy_with_rules']) + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + assert not exc.value.args[0]['changed'] + + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_snapmirror_policy.NetAppOntapSnapMirrorPolicy.delete_snapmirror_policy') + def test_successful_delete(self, delete_snapmirror_policy): + ''' deleting snapmirror policy and testing idempotency ''' + data = self.set_default_args(use_rest='Never') + data['state'] = 'absent' + set_module_args(data) + my_obj = my_module() + my_obj.asup_log_for_cserver = Mock(return_value=None) + if not self.onbox: + my_obj.server = MockONTAPConnection('snapmirror_policy') + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + assert exc.value.args[0]['changed'] + delete_snapmirror_policy.assert_called_with(None) + # to reset na_helper from remembering the previous 'changed' value + my_obj = my_module() + my_obj.asup_log_for_cserver = Mock(return_value=None) + if not self.onbox: + my_obj.server = self.server + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + assert not exc.value.args[0]['changed'] + + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_snapmirror_policy.NetAppOntapSnapMirrorPolicy.delete_snapmirror_policy') + def test_successful_delete_with_rest(self, delete_snapmirror_policy): + ''' deleting snapmirror policy via REST and testing idempotency ''' + data = self.set_default_args() + data['state'] = 'absent' + data['use_rest'] = 'Always' + set_module_args(data) + my_obj = my_module() + my_obj.asup_log_for_cserver = Mock(return_value=None) + my_obj.get_snapmirror_policy = Mock(return_value=SRR['get_snapmirror_policy']) + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + assert exc.value.args[0]['changed'] + delete_snapmirror_policy.assert_called_with('abcdef12-3456-7890-abcd-ef1234567890') + my_obj = my_module() + my_obj.asup_log_for_cserver = Mock(return_value=None) + my_obj.get_snapmirror_policy = Mock(return_value=None) + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + assert not exc.value.args[0]['changed'] + + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_snapmirror_policy.NetAppOntapSnapMirrorPolicy.modify_snapmirror_policy') + def test_successful_modify(self, snapmirror_policy_modify): + ''' modifying snapmirror policy without rules and testing idempotency ''' + data = self.set_default_args(use_rest='Never') + data['comment'] = 'old comment' + data['ignore_atime'] = True + data['is_network_compression_enabled'] = True + data['owner'] = 'cluster_admin' + data['restart'] = 'default' + data['tries'] = '7' + set_module_args(data) + my_obj = my_module() + my_obj.asup_log_for_cserver = Mock(return_value=None) + if not self.onbox: + my_obj.server = MockONTAPConnection('snapmirror_policy') + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + assert exc.value.args[0]['changed'] + snapmirror_policy_modify.assert_called_with() + # to reset na_helper from remembering the previous 'changed' value + data = self.set_default_args(use_rest='Never') + set_module_args(data) + my_obj = my_module() + my_obj.asup_log_for_cserver = Mock(return_value=None) + if not self.onbox: + my_obj.server = MockONTAPConnection('snapmirror_policy') + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + assert not exc.value.args[0]['changed'] + + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_snapmirror_policy.NetAppOntapSnapMirrorPolicy.modify_snapmirror_policy') + def test_successful_modify_with_rest(self, snapmirror_policy_modify): + ''' modifying snapmirror policy without rules via REST and testing idempotency ''' + data = self.set_default_args() + data['comment'] = 'old comment' + data['use_rest'] = 'Always' + set_module_args(data) + my_obj = my_module() + my_obj.asup_log_for_cserver = Mock(return_value=None) + my_obj.get_snapmirror_policy = Mock(return_value=SRR['get_snapmirror_policy']) + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + assert exc.value.args[0]['changed'] + snapmirror_policy_modify.assert_called_with('abcdef12-3456-7890-abcd-ef1234567890', 'async_mirror') + # to reset na_helper from remembering the previous 'changed' value + data = self.set_default_args() + data['use_rest'] = 'Always' + set_module_args(data) + my_obj = my_module() + my_obj.asup_log_for_cserver = Mock(return_value=None) + my_obj.get_snapmirror_policy = Mock(return_value=SRR['get_snapmirror_policy']) + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + assert not exc.value.args[0]['changed'] + + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_snapmirror_policy.NetAppOntapSnapMirrorPolicy.modify_snapmirror_policy') + def test_successful_modify_with_rules(self, snapmirror_policy_modify): + ''' modifying snapmirror policy with rules and testing idempotency ''' + data = self.set_default_args(use_rest='Never', with_rules=True) + set_module_args(data) + my_obj = my_module() + my_obj.asup_log_for_cserver = Mock(return_value=None) + my_obj.get_snapmirror_policy = Mock(return_value=SRR['get_snapmirror_policy']) + if not self.onbox: + my_obj.server = MockONTAPConnection('snapmirror_policy') + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + assert exc.value.args[0]['changed'] + snapmirror_policy_modify.assert_called_with() + # to reset na_helper from remembering the previous 'changed' value + data = self.set_default_args(use_rest='Never', with_rules=True) + set_module_args(data) + my_obj = my_module() + my_obj.asup_log_for_cserver = Mock(return_value=None) + my_obj.get_snapmirror_policy = Mock(return_value=SRR['get_snapmirror_policy_with_rules']) + if not self.onbox: + my_obj.server = MockONTAPConnection('snapmirror_policy') + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + assert not exc.value.args[0]['changed'] + + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_snapmirror_policy.NetAppOntapSnapMirrorPolicy.modify_snapmirror_policy_rules') + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_snapmirror_policy.NetAppOntapSnapMirrorPolicy.modify_snapmirror_policy') + def test_successful_modify_with_rules_via_rest(self, snapmirror_policy_modify, modify_snapmirror_policy_rules): + ''' modifying snapmirror policy with rules via rest and testing idempotency ''' + data = self.set_default_args(use_rest='Always', with_rules=True) + set_module_args(data) + my_obj = my_module() + my_obj.asup_log_for_cserver = Mock(return_value=None) + my_obj.get_snapmirror_policy = Mock(return_value=SRR['get_snapmirror_policy']) + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + assert exc.value.args[0]['changed'] + snapmirror_policy_modify.assert_called_with('abcdef12-3456-7890-abcd-ef1234567890', 'async_mirror') + # to reset na_helper from remembering the previous 'changed' value + data = self.set_default_args(use_rest='Always', with_rules=True) + set_module_args(data) + my_obj = my_module() + my_obj.asup_log_for_cserver = Mock(return_value=None) + my_obj.get_snapmirror_policy = Mock(return_value=SRR['get_snapmirror_policy_with_rules']) + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + assert not exc.value.args[0]['changed'] + + def test_if_all_methods_catch_exception(self): + data = self.set_default_args(use_rest='Never') + set_module_args(data) + my_obj = my_module() + if not self.onbox: + my_obj.server = MockONTAPConnection('snapmirror_policy_fail') + with pytest.raises(AnsibleFailJson) as exc: + my_obj.get_snapmirror_policy() + assert 'Error getting snapmirror policy ansible:' in exc.value.args[0]['msg'] + with pytest.raises(AnsibleFailJson) as exc: + my_obj.create_snapmirror_policy() + assert 'Error creating snapmirror policy ansible:' in exc.value.args[0]['msg'] + with pytest.raises(AnsibleFailJson) as exc: + my_obj.delete_snapmirror_policy() + assert 'Error deleting snapmirror policy ansible:' in exc.value.args[0]['msg'] + with pytest.raises(AnsibleFailJson) as exc: + my_obj.modify_snapmirror_policy() + assert 'Error modifying snapmirror policy ansible:' in exc.value.args[0]['msg'] + + def test_create_snapmirror_policy_retention_obj_for_rest(self): + ''' test create_snapmirror_policy_retention_obj_for_rest ''' + data = self.set_default_args(use_rest='Never') + set_module_args(data) + my_obj = my_module() + + # Test no rules + self.assertEqual(my_obj.create_snapmirror_policy_retention_obj_for_rest(), []) + + # Test one rule + rules = [{'snapmirror_label': 'daily', 'keep': 7}] + retention_obj = [{'label': 'daily', 'count': '7'}] + self.assertEqual(my_obj.create_snapmirror_policy_retention_obj_for_rest(rules), retention_obj) + + # Test two rules, with a prefix + rules = [{'snapmirror_label': 'daily', 'keep': 7}, + {'snapmirror_label': 'weekly', 'keep': 5, 'prefix': 'weekly'}] + retention_obj = [{'label': 'daily', 'count': '7'}, + {'label': 'weekly', 'count': '5', 'prefix': 'weekly'}] + self.assertEqual(my_obj.create_snapmirror_policy_retention_obj_for_rest(rules), retention_obj) + + # Test three rules, with a prefix & schedule + rules = [{'snapmirror_label': 'daily', 'keep': 7}, + {'snapmirror_label': 'weekly', 'keep': 5, 'prefix': 'weekly_sv'}, + {'snapmirror_label': 'monthly', 'keep': 12, 'prefix': 'monthly_sv', 'schedule': 'monthly'}] + retention_obj = [{'label': 'daily', 'count': '7'}, + {'label': 'weekly', 'count': '5', 'prefix': 'weekly_sv'}, + {'label': 'monthly', 'count': '12', 'prefix': 'monthly_sv', 'creation_schedule': {'name': 'monthly'}}] + self.assertEqual(my_obj.create_snapmirror_policy_retention_obj_for_rest(rules), retention_obj) + + def test_identify_snapmirror_policy_rules_with_schedule(self): + ''' test identify_snapmirror_policy_rules_with_schedule ''' + data = self.set_default_args(use_rest='Never') + set_module_args(data) + my_obj = my_module() + + # Test no rules + self.assertEqual(my_obj.identify_snapmirror_policy_rules_with_schedule(), ([], [])) + + # Test one non-schedule rule identified + rules = [{'snapmirror_label': 'daily', 'keep': 7}] + schedule_rules = [] + non_schedule_rules = [{'snapmirror_label': 'daily', 'keep': 7}] + self.assertEqual(my_obj.identify_snapmirror_policy_rules_with_schedule(rules), (schedule_rules, non_schedule_rules)) + + # Test one schedule and two non-schedule rules identified + rules = [{'snapmirror_label': 'daily', 'keep': 7}, + {'snapmirror_label': 'weekly', 'keep': 5, 'prefix': 'weekly_sv'}, + {'snapmirror_label': 'monthly', 'keep': 12, 'prefix': 'monthly_sv', 'schedule': 'monthly'}] + schedule_rules = [{'snapmirror_label': 'monthly', 'keep': 12, 'prefix': 'monthly_sv', 'schedule': 'monthly'}] + non_schedule_rules = [{'snapmirror_label': 'daily', 'keep': 7}, + {'snapmirror_label': 'weekly', 'keep': 5, 'prefix': 'weekly_sv'}] + self.assertEqual(my_obj.identify_snapmirror_policy_rules_with_schedule(rules), (schedule_rules, non_schedule_rules)) + + # Test three schedule & zero non-schedule rules identified + rules = [{'snapmirror_label': 'daily', 'keep': 7, 'schedule': 'daily'}, + {'snapmirror_label': 'weekly', 'keep': 5, 'prefix': 'weekly_sv', 'schedule': 'weekly'}, + {'snapmirror_label': 'monthly', 'keep': 12, 'prefix': 'monthly_sv', 'schedule': 'monthly'}] + schedule_rules = [{'snapmirror_label': 'daily', 'keep': 7, 'schedule': 'daily'}, + {'snapmirror_label': 'weekly', 'keep': 5, 'prefix': 'weekly_sv', 'schedule': 'weekly'}, + {'snapmirror_label': 'monthly', 'keep': 12, 'prefix': 'monthly_sv', 'schedule': 'monthly'}] + non_schedule_rules = [] + self.assertEqual(my_obj.identify_snapmirror_policy_rules_with_schedule(rules), (schedule_rules, non_schedule_rules)) + + def test_identify_new_snapmirror_policy_rules(self): + ''' test identify_new_snapmirror_policy_rules ''' + + # Test with no rules in parameters. new_rules should always be []. + data = self.set_default_args(use_rest='Never', with_rules=False) + set_module_args(data) + my_obj = my_module() + + current = None + new_rules = [] + self.assertEqual(my_obj.identify_new_snapmirror_policy_rules(current), new_rules) + + current = {'snapmirror_label': ['daily'], 'keep': [7], 'prefix': [''], 'schedule': ['']} + new_rules = [] + self.assertEqual(my_obj.identify_new_snapmirror_policy_rules(current), new_rules) + + # Test with rules in parameters. + data = self.set_default_args(use_rest='Never', with_rules=True) + set_module_args(data) + my_obj = my_module() + + # Test three new rules identified when no rules currently exist + current = None + new_rules = [{'snapmirror_label': 'daily', 'keep': 7, 'prefix': '', 'schedule': ''}, + {'snapmirror_label': 'weekly', 'keep': 5, 'prefix': 'weekly', 'schedule': 'weekly'}, + {'snapmirror_label': 'monthly', 'keep': 12, 'prefix': 'monthly', 'schedule': 'monthly'}] + self.assertEqual(my_obj.identify_new_snapmirror_policy_rules(current), new_rules) + + # Test two new rules identified and one rule already exists + current = {'snapmirror_label': ['daily'], 'keep': [7], 'prefix': [''], 'schedule': ['']} + new_rules = [{'snapmirror_label': 'weekly', 'keep': 5, 'prefix': 'weekly', 'schedule': 'weekly'}, + {'snapmirror_label': 'monthly', 'keep': 12, 'prefix': 'monthly', 'schedule': 'monthly'}] + self.assertEqual(my_obj.identify_new_snapmirror_policy_rules(current), new_rules) + + # Test one new rule identified and two rules already exist + current = {'snapmirror_label': ['daily', 'monthly'], + 'keep': [7, 12], + 'prefix': ['', 'monthly'], + 'schedule': ['', 'monthly']} + new_rules = [{'snapmirror_label': 'weekly', 'keep': 5, 'prefix': 'weekly', 'schedule': 'weekly'}] + self.assertEqual(my_obj.identify_new_snapmirror_policy_rules(current), new_rules) + + # Test no new rules identified as all rules already exist + current = {'snapmirror_label': ['daily', 'monthly', 'weekly'], + 'keep': [7, 12, 5], + 'prefix': ['', 'monthly', 'weekly'], + 'schedule': ['', 'monthly', 'weekly']} + new_rules = [] + self.assertEqual(my_obj.identify_new_snapmirror_policy_rules(current), new_rules) + + def test_identify_obsolete_snapmirror_policy_rules(self): + ''' test identify_obsolete_snapmirror_policy_rules ''' + + # Test with no rules in parameters. obsolete_rules should always be []. + data = self.set_default_args(use_rest='Never', with_rules=False) + set_module_args(data) + my_obj = my_module() + + current = None + obsolete_rules = [] + self.assertEqual(my_obj.identify_obsolete_snapmirror_policy_rules(current), obsolete_rules) + + current = {'snapmirror_label': ['daily'], 'keep': [7], 'prefix': [''], 'schedule': ['']} + obsolete_rules = [] + self.assertEqual(my_obj.identify_obsolete_snapmirror_policy_rules(current), obsolete_rules) + + # Test removing all rules. obsolete_rules should equal current. + data = self.set_default_args(use_rest='Never', with_rules=False) + data['snapmirror_label'] = [] + set_module_args(data) + my_obj = my_module() + + current = {'snapmirror_label': ['monthly', 'weekly', 'hourly', 'daily', 'yearly'], + 'keep': [12, 5, 24, 7, 7], + 'prefix': ['monthly', 'weekly', '', '', 'yearly'], + 'schedule': ['monthly', 'weekly', '', '', 'yearly']} + obsolete_rules = [{'snapmirror_label': 'monthly', 'keep': 12, 'prefix': 'monthly', 'schedule': 'monthly'}, + {'snapmirror_label': 'weekly', 'keep': 5, 'prefix': 'weekly', 'schedule': 'weekly'}, + {'snapmirror_label': 'hourly', 'keep': 24, 'prefix': '', 'schedule': ''}, + {'snapmirror_label': 'daily', 'keep': 7, 'prefix': '', 'schedule': ''}, + {'snapmirror_label': 'yearly', 'keep': 7, 'prefix': 'yearly', 'schedule': 'yearly'}] + self.assertEqual(my_obj.identify_obsolete_snapmirror_policy_rules(current), obsolete_rules) + + # Test with rules in parameters. + data = self.set_default_args(use_rest='Never', with_rules=True) + set_module_args(data) + my_obj = my_module() + + # Test no rules exist, thus no obsolete rules + current = None + obsolete_rules = [] + self.assertEqual(my_obj.identify_obsolete_snapmirror_policy_rules(current), obsolete_rules) + + # Test new rules and one obsolete rule identified + current = {'snapmirror_label': ['hourly'], 'keep': [24], 'prefix': [''], 'schedule': ['']} + obsolete_rules = [{'snapmirror_label': 'hourly', 'keep': 24, 'prefix': '', 'schedule': ''}] + self.assertEqual(my_obj.identify_obsolete_snapmirror_policy_rules(current), obsolete_rules) + + # Test new rules, with one retained and one obsolete rule identified + current = {'snapmirror_label': ['hourly', 'daily'], + 'keep': [24, 7], + 'prefix': ['', ''], + 'schedule': ['', '']} + obsolete_rules = [{'snapmirror_label': 'hourly', 'keep': 24, 'prefix': '', 'schedule': ''}] + self.assertEqual(my_obj.identify_obsolete_snapmirror_policy_rules(current), obsolete_rules) + + # Test new rules and two obsolete rules identified + current = {'snapmirror_label': ['monthly', 'weekly', 'hourly', 'daily', 'yearly'], + 'keep': [12, 5, 24, 7, 7], + 'prefix': ['monthly', 'weekly', '', '', 'yearly'], + 'schedule': ['monthly', 'weekly', '', '', 'yearly']} + obsolete_rules = [{'snapmirror_label': 'hourly', 'keep': 24, 'prefix': '', 'schedule': ''}, + {'snapmirror_label': 'yearly', 'keep': 7, 'prefix': 'yearly', 'schedule': 'yearly'}] + self.assertEqual(my_obj.identify_obsolete_snapmirror_policy_rules(current), obsolete_rules) + + def test_identify_modified_snapmirror_policy_rules(self): + ''' test identify_modified_snapmirror_policy_rules ''' + + # Test with no rules in parameters. modified_rules & unmodified_rules should always be []. + data = self.set_default_args(use_rest='Never', with_rules=False) + data.pop('snapmirror_label', None) + set_module_args(data) + my_obj = my_module() + + current = None + modified_rules, unmodified_rules = [], [] + self.assertEqual(my_obj.identify_modified_snapmirror_policy_rules(current), (modified_rules, unmodified_rules)) + + current = {'snapmirror_label': ['daily'], 'keep': [14], 'prefix': ['daily'], 'schedule': ['daily']} + modified_rules, unmodified_rules = [], [] + self.assertEqual(my_obj.identify_modified_snapmirror_policy_rules(current), (modified_rules, unmodified_rules)) + + # Test removing all rules. modified_rules & unmodified_rules should be []. + data = self.set_default_args(use_rest='Never', with_rules=False) + data['snapmirror_label'] = [] + set_module_args(data) + my_obj = my_module() + current = {'snapmirror_label': ['monthly', 'weekly', 'hourly', 'daily', 'yearly'], + 'keep': [12, 5, 24, 7, 7], + 'prefix': ['monthly', 'weekly', '', '', 'yearly'], + 'schedule': ['monthly', 'weekly', '', '', 'yearly']} + modified_rules, unmodified_rules = [], [] + self.assertEqual(my_obj.identify_modified_snapmirror_policy_rules(current), (modified_rules, unmodified_rules)) + + # Test with rules in parameters. + data = self.set_default_args(use_rest='Never', with_rules=True) + set_module_args(data) + my_obj = my_module() + + # Test no rules exist, thus no modified & unmodified rules + current = None + modified_rules, unmodified_rules = [], [] + self.assertEqual(my_obj.identify_modified_snapmirror_policy_rules(current), (modified_rules, unmodified_rules)) + + # Test new rules don't exist, thus no modified & unmodified rules + current = {'snapmirror_label': ['hourly'], 'keep': [24], 'prefix': [''], 'schedule': ['']} + modified_rules, unmodified_rules = [], [] + self.assertEqual(my_obj.identify_modified_snapmirror_policy_rules(current), (modified_rules, unmodified_rules)) + + # Test daily & monthly modified, weekly unmodified + current = {'snapmirror_label': ['hourly', 'daily', 'weekly', 'monthly'], + 'keep': [24, 14, 5, 6], + 'prefix': ['', 'daily', 'weekly', 'monthly'], + 'schedule': ['', 'daily', 'weekly', 'monthly']} + modified_rules = [{'snapmirror_label': 'daily', 'keep': 7, 'prefix': '', 'schedule': ''}, + {'snapmirror_label': 'monthly', 'keep': 12, 'prefix': 'monthly', 'schedule': 'monthly'}] + unmodified_rules = [{'snapmirror_label': 'weekly', 'keep': 5, 'prefix': 'weekly', 'schedule': 'weekly'}] + self.assertEqual(my_obj.identify_modified_snapmirror_policy_rules(current), (modified_rules, unmodified_rules)) + + # Test all rules modified + current = {'snapmirror_label': ['daily', 'weekly', 'monthly'], + 'keep': [14, 10, 6], + 'prefix': ['', '', ''], + 'schedule': ['daily', 'weekly', 'monthly']} + modified_rules = [{'snapmirror_label': 'daily', 'keep': 7, 'prefix': '', 'schedule': ''}, + {'snapmirror_label': 'weekly', 'keep': 5, 'prefix': 'weekly', 'schedule': 'weekly'}, + {'snapmirror_label': 'monthly', 'keep': 12, 'prefix': 'monthly', 'schedule': 'monthly'}] + unmodified_rules = [] + self.assertEqual(my_obj.identify_modified_snapmirror_policy_rules(current), (modified_rules, unmodified_rules)) + + # Test all rules unmodified + current = {'snapmirror_label': ['daily', 'weekly', 'monthly'], + 'keep': [7, 5, 12], + 'prefix': ['', 'weekly', 'monthly'], + 'schedule': ['', 'weekly', 'monthly']} + modified_rules = [] + unmodified_rules = [{'snapmirror_label': 'daily', 'keep': 7, 'prefix': '', 'schedule': ''}, + {'snapmirror_label': 'weekly', 'keep': 5, 'prefix': 'weekly', 'schedule': 'weekly'}, + {'snapmirror_label': 'monthly', 'keep': 12, 'prefix': 'monthly', 'schedule': 'monthly'}] + self.assertEqual(my_obj.identify_modified_snapmirror_policy_rules(current), (modified_rules, unmodified_rules)) diff --git a/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_snapshot.py b/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_snapshot.py new file mode 100644 index 00000000..74c87355 --- /dev/null +++ b/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_snapshot.py @@ -0,0 +1,227 @@ +# (c) 2018, NetApp, Inc +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +''' unit tests ONTAP Ansible module: na_ontap_nvme_snapshot''' + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type +import json +import pytest + +from ansible_collections.netapp.ontap.tests.unit.compat import unittest +from ansible_collections.netapp.ontap.tests.unit.compat.mock import patch +from ansible.module_utils import basic +from ansible.module_utils._text import to_bytes +import ansible_collections.netapp.ontap.plugins.module_utils.netapp as netapp_utils + +from ansible_collections.netapp.ontap.plugins.modules.na_ontap_snapshot \ + import NetAppOntapSnapshot as my_module + +if not netapp_utils.has_netapp_lib(): + pytestmark = pytest.mark.skip('skipping as missing required netapp_lib') + + +def set_module_args(args): + """prepare arguments so that they will be picked up during module creation""" + args = json.dumps({'ANSIBLE_MODULE_ARGS': args}) + basic._ANSIBLE_ARGS = to_bytes(args) # pylint: disable=protected-access + + +class AnsibleExitJson(Exception): + """Exception class to be raised by module.exit_json and caught by the test case""" + pass + + +class AnsibleFailJson(Exception): + """Exception class to be raised by module.fail_json and caught by the test case""" + pass + + +def exit_json(*args, **kwargs): # pylint: disable=unused-argument + """function to patch over exit_json; package return data into an exception""" + if 'changed' not in kwargs: + kwargs['changed'] = False + raise AnsibleExitJson(kwargs) + + +def fail_json(*args, **kwargs): # pylint: disable=unused-argument + """function to patch over fail_json; package return data into an exception""" + kwargs['failed'] = True + raise AnsibleFailJson(kwargs) + + +class MockONTAPConnection(object): + ''' mock server connection to ONTAP host ''' + + def __init__(self, kind=None): + ''' save arguments ''' + self.type = kind + self.xml_in = None + self.xml_out = None + + def invoke_successfully(self, xml, enable_tunneling): # pylint: disable=unused-argument + ''' mock invoke_successfully returning xml data ''' + self.xml_in = xml + if self.type == 'snapshot': + xml = self.build_snapshot_info() + elif self.type == 'snapshot_fail': + raise netapp_utils.zapi.NaApiError(code='TEST', message="This exception is from the unit test") + self.xml_out = xml + return xml + + @staticmethod + def build_snapshot_info(): + ''' build xml data for snapshot-info ''' + xml = netapp_utils.zapi.NaElement('xml') + data = {'num-records': 1, + 'attributes-list': {'snapshot-info': {'comment': 'new comment', + 'name': 'ansible', + 'snapmirror-label': 'label12'}}} + xml.translate_struct(data) + return xml + + +class TestMyModule(unittest.TestCase): + ''' a group of related Unit Tests ''' + + def setUp(self): + self.mock_module_helper = patch.multiple(basic.AnsibleModule, + exit_json=exit_json, + fail_json=fail_json) + self.mock_module_helper.start() + self.addCleanup(self.mock_module_helper.stop) + self.server = MockONTAPConnection() + self.onbox = False + + def set_default_args(self): + if self.onbox: + hostname = '10.193.75.3' + username = 'admin' + password = 'netapp1!' + vserver = 'ansible' + volume = 'ansible' + snapshot = 'ansible' + comment = 'new comment' + snapmirror_label = 'label12' + else: + hostname = 'hostname' + username = 'username' + password = 'password' + vserver = 'vserver' + volume = 'ansible' + snapshot = 'ansible' + comment = 'new comment' + snapmirror_label = 'label12' + return dict({ + 'hostname': hostname, + 'username': username, + 'password': password, + 'vserver': vserver, + 'volume': volume, + 'snapshot': snapshot, + 'comment': comment, + 'snapmirror_label': snapmirror_label + }) + + def test_module_fail_when_required_args_missing(self): + ''' required arguments are reported as errors ''' + with pytest.raises(AnsibleFailJson) as exc: + set_module_args({}) + my_module() + print('Info: %s' % exc.value.args[0]['msg']) + + def test_ensure_get_called(self): + ''' test get_snapshot() for non-existent snapshot''' + set_module_args(self.set_default_args()) + my_obj = my_module() + my_obj.server = self.server + assert my_obj.get_snapshot() is None + + def test_ensure_get_called_existing(self): + ''' test get_snapshot() for existing snapshot''' + set_module_args(self.set_default_args()) + my_obj = my_module() + my_obj.server = MockONTAPConnection(kind='snapshot') + assert my_obj.get_snapshot() + + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_snapshot.NetAppOntapSnapshot.create_snapshot') + def test_successful_create(self, create_snapshot): + ''' creating snapshot and testing idempotency ''' + set_module_args(self.set_default_args()) + my_obj = my_module() + if not self.onbox: + my_obj.server = self.server + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + assert exc.value.args[0]['changed'] + create_snapshot.assert_called_with() + # to reset na_helper from remembering the previous 'changed' value + my_obj = my_module() + if not self.onbox: + my_obj.server = MockONTAPConnection('snapshot') + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + assert not exc.value.args[0]['changed'] + + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_snapshot.NetAppOntapSnapshot.modify_snapshot') + def test_successful_modify(self, modify_snapshot): + ''' modifying snapshot and testing idempotency ''' + data = self.set_default_args() + data['comment'] = 'adding comment' + data['snapmirror_label'] = 'label22' + set_module_args(data) + my_obj = my_module() + if not self.onbox: + my_obj.server = MockONTAPConnection('snapshot') + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + assert exc.value.args[0]['changed'] + modify_snapshot.assert_called_with() + # to reset na_helper from remembering the previous 'changed' value + data['comment'] = 'new comment' + data['snapmirror_label'] = 'label12' + set_module_args(data) + my_obj = my_module() + if not self.onbox: + my_obj.server = MockONTAPConnection('snapshot') + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + assert not exc.value.args[0]['changed'] + + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_snapshot.NetAppOntapSnapshot.delete_snapshot') + def test_successful_delete(self, delete_snapshot): + ''' deleting snapshot and testing idempotency ''' + data = self.set_default_args() + data['state'] = 'absent' + set_module_args(data) + my_obj = my_module() + if not self.onbox: + my_obj.server = MockONTAPConnection('snapshot') + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + assert exc.value.args[0]['changed'] + delete_snapshot.assert_called_with() + # to reset na_helper from remembering the previous 'changed' value + my_obj = my_module() + if not self.onbox: + my_obj.server = self.server + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + assert not exc.value.args[0]['changed'] + + def test_if_all_methods_catch_exception(self): + module_args = {} + module_args.update(self.set_default_args()) + set_module_args(module_args) + my_obj = my_module() + if not self.onbox: + my_obj.server = MockONTAPConnection('snapshot_fail') + with pytest.raises(AnsibleFailJson) as exc: + my_obj.create_snapshot() + assert 'Error creating snapshot ansible:' in exc.value.args[0]['msg'] + with pytest.raises(AnsibleFailJson) as exc: + my_obj.delete_snapshot() + assert 'Error deleting snapshot ansible:' in exc.value.args[0]['msg'] + with pytest.raises(AnsibleFailJson) as exc: + my_obj.modify_snapshot() + assert 'Error modifying snapshot ansible:' in exc.value.args[0]['msg'] diff --git a/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_snapshot_policy.py b/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_snapshot_policy.py new file mode 100644 index 00000000..57bd42dc --- /dev/null +++ b/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_snapshot_policy.py @@ -0,0 +1,691 @@ +# (c) 2018, NetApp, Inc +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +''' unit tests ONTAP Ansible module: na_ontap_snapshot_policy''' + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type +import json +import pytest + +from ansible_collections.netapp.ontap.tests.unit.compat import unittest +from ansible_collections.netapp.ontap.tests.unit.compat.mock import patch, Mock +from ansible.module_utils import basic +from ansible.module_utils._text import to_bytes +import ansible_collections.netapp.ontap.plugins.module_utils.netapp as netapp_utils + +from ansible_collections.netapp.ontap.plugins.modules.na_ontap_snapshot_policy \ + import NetAppOntapSnapshotPolicy as my_module + +if not netapp_utils.has_netapp_lib(): + pytestmark = pytest.mark.skip('skipping as missing required netapp_lib') + + +def set_module_args(args): + """prepare arguments so that they will be picked up during module creation""" + args = json.dumps({'ANSIBLE_MODULE_ARGS': args}) + basic._ANSIBLE_ARGS = to_bytes(args) # pylint: disable=protected-access + + +class AnsibleExitJson(Exception): + """Exception class to be raised by module.exit_json and caught by the test case""" + pass + + +class AnsibleFailJson(Exception): + """Exception class to be raised by module.fail_json and caught by the test case""" + pass + + +def exit_json(*args, **kwargs): # pylint: disable=unused-argument + """function to patch over exit_json; package return data into an exception""" + if 'changed' not in kwargs: + kwargs['changed'] = False + raise AnsibleExitJson(kwargs) + + +def fail_json(*args, **kwargs): # pylint: disable=unused-argument + """function to patch over fail_json; package return data into an exception""" + kwargs['failed'] = True + raise AnsibleFailJson(kwargs) + + +class MockONTAPConnection(object): + ''' mock server connection to ONTAP host ''' + + def __init__(self, kind=None): + ''' save arguments ''' + self.type = kind + self.xml_in = None + self.xml_out = None + + def invoke_successfully(self, xml, enable_tunneling): # pylint: disable=unused-argument + ''' mock invoke_successfully returning xml data ''' + self.xml_in = xml + if self.type == 'policy': + xml = self.build_snapshot_policy_info() + elif self.type == 'snapshot_policy_info_policy_disabled': + xml = self.build_snapshot_policy_info_policy_disabled() + elif self.type == 'snapshot_policy_info_comment_modified': + xml = self.build_snapshot_policy_info_comment_modified() + elif self.type == 'snapshot_policy_info_schedules_added': + xml = self.build_snapshot_policy_info_schedules_added() + elif self.type == 'snapshot_policy_info_schedules_deleted': + xml = self.build_snapshot_policy_info_schedules_deleted() + elif self.type == 'snapshot_policy_info_modified_schedule_counts': + xml = self.build_snapshot_policy_info_modified_schedule_counts() + elif self.type == 'policy_fail': + raise netapp_utils.zapi.NaApiError(code='TEST', message="This exception is from the unit test") + self.xml_out = xml + return xml + + def asup_log_for_cserver(self): + ''' mock autosupport log''' + return None + + @staticmethod + def build_snapshot_policy_info(): + ''' build xml data for snapshot-policy-info ''' + xml = netapp_utils.zapi.NaElement('xml') + data = {'num-records': 1, + 'attributes-list': { + 'snapshot-policy-info': { + 'comment': 'new comment', + 'enabled': 'true', + 'policy': 'ansible', + 'snapshot-policy-schedules': { + 'snapshot-schedule-info': { + 'count': 100, + 'schedule': 'hourly', + 'prefix': 'hourly', + 'snapmirror-label': '' + } + }, + 'vserver-name': 'hostname' + } + }} + xml.translate_struct(data) + return xml + + @staticmethod + def build_snapshot_policy_info_comment_modified(): + ''' build xml data for snapshot-policy-info ''' + xml = netapp_utils.zapi.NaElement('xml') + data = {'num-records': 1, + 'attributes-list': { + 'snapshot-policy-info': { + 'comment': 'modified comment', + 'enabled': 'true', + 'policy': 'ansible', + 'snapshot-policy-schedules': { + 'snapshot-schedule-info': { + 'count': 100, + 'schedule': 'hourly', + 'prefix': 'hourly', + 'snapmirror-label': '' + } + }, + 'vserver-name': 'hostname' + } + }} + xml.translate_struct(data) + return xml + + @staticmethod + def build_snapshot_policy_info_policy_disabled(): + ''' build xml data for snapshot-policy-info ''' + xml = netapp_utils.zapi.NaElement('xml') + data = {'num-records': 1, + 'attributes-list': { + 'snapshot-policy-info': { + 'comment': 'new comment', + 'enabled': 'false', + 'policy': 'ansible', + 'snapshot-policy-schedules': { + 'snapshot-schedule-info': { + 'count': 100, + 'schedule': 'hourly', + 'prefix': 'hourly', + 'snapmirror-label': '' + } + }, + 'vserver-name': 'hostname' + } + }} + xml.translate_struct(data) + return xml + + @staticmethod + def build_snapshot_policy_info_schedules_added(): + ''' build xml data for snapshot-policy-info ''' + xml = netapp_utils.zapi.NaElement('xml') + data = {'num-records': 1, + 'attributes-list': { + 'snapshot-policy-info': { + 'comment': 'new comment', + 'enabled': 'true', + 'policy': 'ansible', + 'snapshot-policy-schedules': [ + { + 'snapshot-schedule-info': { + 'count': 100, + 'schedule': 'hourly', + 'prefix': 'hourly', + 'snapmirror-label': '' + } + }, + { + 'snapshot-schedule-info': { + 'count': 5, + 'schedule': 'daily', + 'prefix': 'daily', + 'snapmirror-label': 'daily' + } + }, + { + 'snapshot-schedule-info': { + 'count': 10, + 'schedule': 'weekly', + 'prefix': 'weekly', + 'snapmirror-label': '' + } + } + ], + 'vserver-name': 'hostname' + } + }} + xml.translate_struct(data) + return xml + + @staticmethod + def build_snapshot_policy_info_schedules_deleted(): + ''' build xml data for snapshot-policy-info ''' + xml = netapp_utils.zapi.NaElement('xml') + data = {'num-records': 1, + 'attributes-list': { + 'snapshot-policy-info': { + 'comment': 'new comment', + 'enabled': 'true', + 'policy': 'ansible', + 'snapshot-policy-schedules': [ + { + 'snapshot-schedule-info': { + 'schedule': 'daily', + 'prefix': 'daily', + 'count': 5, + 'snapmirror-label': 'daily' + } + } + ], + 'vserver-name': 'hostname' + } + }} + xml.translate_struct(data) + return xml + + @staticmethod + def build_snapshot_policy_info_modified_schedule_counts(): + ''' build xml data for snapshot-policy-info ''' + xml = netapp_utils.zapi.NaElement('xml') + data = {'num-records': 1, + 'attributes-list': { + 'snapshot-policy-info': { + 'comment': 'new comment', + 'enabled': 'true', + 'policy': 'ansible', + 'snapshot-policy-schedules': [ + { + 'snapshot-schedule-info': { + 'count': 10, + 'schedule': 'hourly', + 'prefix': 'hourly', + 'snapmirror-label': '' + } + }, + { + 'snapshot-schedule-info': { + 'count': 50, + 'schedule': 'daily', + 'prefix': 'daily', + 'snapmirror-label': 'daily' + } + }, + { + 'snapshot-schedule-info': { + 'count': 100, + 'schedule': 'weekly', + 'prefix': 'weekly', + 'snapmirror-label': '' + } + } + ], + 'vserver-name': 'hostname' + } + }} + xml.translate_struct(data) + return xml + + +class TestMyModule(unittest.TestCase): + ''' a group of related Unit Tests ''' + + def setUp(self): + self.mock_module_helper = patch.multiple(basic.AnsibleModule, + exit_json=exit_json, + fail_json=fail_json) + self.mock_module_helper.start() + self.addCleanup(self.mock_module_helper.stop) + self.server = MockONTAPConnection() + self.onbox = False + + def set_default_args(self): + if self.onbox: + hostname = '10.10.10.10' + username = 'admin' + password = '1234' + name = 'ansible' + enabled = True + count = 100 + schedule = 'hourly' + prefix = 'hourly' + comment = 'new comment' + else: + hostname = 'hostname' + username = 'username' + password = 'password' + name = 'ansible' + enabled = True + count = 100 + schedule = 'hourly' + prefix = 'hourly' + comment = 'new comment' + return dict({ + 'hostname': hostname, + 'username': username, + 'password': password, + 'name': name, + 'enabled': enabled, + 'count': count, + 'schedule': schedule, + 'prefix': prefix, + 'comment': comment + }) + + def set_default_current(self): + default_args = self.set_default_args() + return dict({ + 'name': default_args['name'], + 'enabled': default_args['enabled'], + 'count': [default_args['count']], + 'schedule': [default_args['schedule']], + 'snapmirror_label': [''], + 'prefix': [default_args['prefix']], + 'comment': default_args['comment'], + 'vserver': default_args['hostname'] + }) + + def test_module_fail_when_required_args_missing(self): + ''' required arguments are reported as errors ''' + with pytest.raises(AnsibleFailJson) as exc: + set_module_args({}) + my_module() + print('Info: %s' % exc.value.args[0]['msg']) + + def test_ensure_get_called(self): + ''' test get_snapshot_policy() for non-existent snapshot policy''' + set_module_args(self.set_default_args()) + my_obj = my_module() + my_obj.server = self.server + assert my_obj.get_snapshot_policy() is None + + def test_ensure_get_called_existing(self): + ''' test get_snapshot_policy() for existing snapshot policy''' + set_module_args(self.set_default_args()) + my_obj = my_module() + my_obj.server = MockONTAPConnection(kind='policy') + assert my_obj.get_snapshot_policy() + + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_snapshot_policy.NetAppOntapSnapshotPolicy.create_snapshot_policy') + def test_successful_create(self, create_snapshot): + ''' creating snapshot policy and testing idempotency ''' + set_module_args(self.set_default_args()) + my_obj = my_module() + my_obj.asup_log_for_cserver = Mock(return_value=None) + if not self.onbox: + my_obj.server = self.server + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + assert exc.value.args[0]['changed'] + create_snapshot.assert_called_with() + # to reset na_helper from remembering the previous 'changed' value + my_obj = my_module() + my_obj.asup_log_for_cserver = Mock(return_value=None) + if not self.onbox: + my_obj.server = MockONTAPConnection('policy') + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + assert not exc.value.args[0]['changed'] + + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_snapshot_policy.NetAppOntapSnapshotPolicy.modify_snapshot_policy') + def test_successful_modify_comment(self, modify_snapshot): + ''' modifying snapshot policy comment and testing idempotency ''' + data = self.set_default_args() + data['comment'] = 'modified comment' + set_module_args(data) + my_obj = my_module() + my_obj.asup_log_for_cserver = Mock(return_value=None) + if not self.onbox: + my_obj.server = MockONTAPConnection('policy') + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + assert exc.value.args[0]['changed'] + current = self.set_default_current() + modify_snapshot.assert_called_with(current) + # to reset na_helper from remembering the previous 'changed' value + my_obj = my_module() + my_obj.asup_log_for_cserver = Mock(return_value=None) + if not self.onbox: + my_obj.server = MockONTAPConnection('snapshot_policy_info_comment_modified') + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + assert not exc.value.args[0]['changed'] + + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_snapshot_policy.NetAppOntapSnapshotPolicy.modify_snapshot_policy') + def test_successful_disable_policy(self, modify_snapshot): + ''' disabling snapshot policy and testing idempotency ''' + data = self.set_default_args() + data['enabled'] = False + set_module_args(data) + my_obj = my_module() + my_obj.asup_log_for_cserver = Mock(return_value=None) + if not self.onbox: + my_obj.server = MockONTAPConnection('policy') + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + assert exc.value.args[0]['changed'] + current = self.set_default_current() + modify_snapshot.assert_called_with(current) + # to reset na_helper from remembering the previous 'changed' value + my_obj = my_module() + my_obj.asup_log_for_cserver = Mock(return_value=None) + if not self.onbox: + my_obj.server = MockONTAPConnection('snapshot_policy_info_policy_disabled') + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + assert not exc.value.args[0]['changed'] + + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_snapshot_policy.NetAppOntapSnapshotPolicy.modify_snapshot_policy') + def test_successful_enable_policy(self, modify_snapshot): + ''' enabling snapshot policy and testing idempotency ''' + data = self.set_default_args() + data['enabled'] = True + set_module_args(data) + my_obj = my_module() + my_obj.asup_log_for_cserver = Mock(return_value=None) + if not self.onbox: + my_obj.server = MockONTAPConnection('snapshot_policy_info_policy_disabled') + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + assert exc.value.args[0]['changed'] + current = self.set_default_current() + current['enabled'] = False + modify_snapshot.assert_called_with(current) + # to reset na_helper from remembering the previous 'changed' value + my_obj = my_module() + my_obj.asup_log_for_cserver = Mock(return_value=None) + if not self.onbox: + my_obj.server = MockONTAPConnection('policy') + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + assert not exc.value.args[0]['changed'] + + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_snapshot_policy.NetAppOntapSnapshotPolicy.modify_snapshot_policy') + def test_successful_modify_schedules_add(self, modify_snapshot): + ''' adding snapshot policy schedules and testing idempotency ''' + data = self.set_default_args() + data['schedule'] = ['hourly', 'daily', 'weekly'] + data['prefix'] = ['hourly', 'daily', 'weekly'] + data['count'] = [100, 5, 10] + data['snapmirror_label'] = ['', 'daily', ''] + set_module_args(data) + my_obj = my_module() + my_obj.asup_log_for_cserver = Mock(return_value=None) + if not self.onbox: + my_obj.server = MockONTAPConnection('policy') + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + assert exc.value.args[0]['changed'] + current = self.set_default_current() + modify_snapshot.assert_called_with(current) + # to reset na_helper from remembering the previous 'changed' value + my_obj = my_module() + my_obj.asup_log_for_cserver = Mock(return_value=None) + if not self.onbox: + my_obj.server = MockONTAPConnection('snapshot_policy_info_schedules_added') + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + assert not exc.value.args[0]['changed'] + + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_snapshot_policy.NetAppOntapSnapshotPolicy.modify_snapshot_policy') + def test_successful_modify_schedules_delete(self, modify_snapshot): + ''' deleting snapshot policy schedules and testing idempotency ''' + data = self.set_default_args() + data['schedule'] = ['daily'] + data['prefix'] = ['daily'] + data['count'] = [5] + set_module_args(data) + my_obj = my_module() + my_obj.asup_log_for_cserver = Mock(return_value=None) + if not self.onbox: + my_obj.server = MockONTAPConnection('policy') + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + assert exc.value.args[0]['changed'] + current = self.set_default_current() + modify_snapshot.assert_called_with(current) + # to reset na_helper from remembering the previous 'changed' value + my_obj = my_module() + my_obj.asup_log_for_cserver = Mock(return_value=None) + if not self.onbox: + my_obj.server = MockONTAPConnection('snapshot_policy_info_schedules_deleted') + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + assert not exc.value.args[0]['changed'] + + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_snapshot_policy.NetAppOntapSnapshotPolicy.modify_snapshot_policy') + def test_successful_modify_schedules(self, modify_snapshot): + ''' modifying snapshot policy schedule counts and testing idempotency ''' + data = self.set_default_args() + data['schedule'] = ['hourly', 'daily', 'weekly'] + data['count'] = [10, 50, 100] + data['prefix'] = ['hourly', 'daily', 'weekly'] + set_module_args(data) + my_obj = my_module() + my_obj.asup_log_for_cserver = Mock(return_value=None) + if not self.onbox: + my_obj.server = MockONTAPConnection('policy') + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + assert exc.value.args[0]['changed'] + current = self.set_default_current() + modify_snapshot.assert_called_with(current) + # to reset na_helper from remembering the previous 'changed' value + my_obj = my_module() + my_obj.asup_log_for_cserver = Mock(return_value=None) + if not self.onbox: + my_obj.server = MockONTAPConnection('snapshot_policy_info_modified_schedule_counts') + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + assert not exc.value.args[0]['changed'] + + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_snapshot_policy.NetAppOntapSnapshotPolicy.delete_snapshot_policy') + def test_successful_delete(self, delete_snapshot): + ''' deleting snapshot policy and testing idempotency ''' + data = self.set_default_args() + data['state'] = 'absent' + set_module_args(data) + my_obj = my_module() + my_obj.asup_log_for_cserver = Mock(return_value=None) + if not self.onbox: + my_obj.server = MockONTAPConnection('policy') + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + assert exc.value.args[0]['changed'] + delete_snapshot.assert_called_with() + # to reset na_helper from remembering the previous 'changed' value + my_obj = my_module() + my_obj.asup_log_for_cserver = Mock(return_value=None) + if not self.onbox: + my_obj.server = self.server + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + assert not exc.value.args[0]['changed'] + + def test_valid_schedule_count(self): + ''' validate when schedule has same number of elements ''' + data = self.set_default_args() + data['schedule'] = ['hourly', 'daily', 'weekly', 'monthly', '5min'] + data['prefix'] = ['hourly', 'daily', 'weekly', 'monthly', '5min'] + data['count'] = [1, 2, 3, 4, 5] + set_module_args(data) + my_obj = my_module() + my_obj.asup_log_for_cserver = Mock(return_value=None) + if not self.onbox: + my_obj.server = self.server + my_obj.create_snapshot_policy() + create_xml = my_obj.server.xml_in + assert data['count'][2] == int(create_xml['count3']) + assert data['schedule'][4] == create_xml['schedule5'] + + def test_valid_schedule_count_with_snapmirror_labels(self): + ''' validate when schedule has same number of elements with snapmirror labels ''' + data = self.set_default_args() + data['schedule'] = ['hourly', 'daily', 'weekly', 'monthly', '5min'] + data['prefix'] = ['hourly', 'daily', 'weekly', 'monthly', '5min'] + data['count'] = [1, 2, 3, 4, 5] + data['snapmirror_label'] = ['hourly', 'daily', 'weekly', 'monthly', '5min'] + set_module_args(data) + my_obj = my_module() + my_obj.asup_log_for_cserver = Mock(return_value=None) + if not self.onbox: + my_obj.server = self.server + my_obj.create_snapshot_policy() + create_xml = my_obj.server.xml_in + assert data['count'][2] == int(create_xml['count3']) + assert data['schedule'][4] == create_xml['schedule5'] + assert data['snapmirror_label'][3] == create_xml['snapmirror-label4'] + + def test_invalid_params(self): + ''' validate error when schedule does not have same number of elements ''' + data = self.set_default_args() + data['schedule'] = ['s1', 's2'] + data['prefix'] = ['s1', 's2'] + data['count'] = [1, 2, 3] + set_module_args(data) + my_obj = my_module() + my_obj.asup_log_for_cserver = Mock(return_value=None) + if not self.onbox: + my_obj.server = self.server + with pytest.raises(AnsibleFailJson) as exc: + my_obj.create_snapshot_policy() + msg = 'Error: A Snapshot policy must have at least 1 ' \ + 'schedule and can have up to a maximum of 5 schedules, with a count ' \ + 'representing the maximum number of Snapshot copies for each schedule' + assert exc.value.args[0]['msg'] == msg + + def test_invalid_schedule_count(self): + ''' validate error when schedule has more than 5 elements ''' + data = self.set_default_args() + data['schedule'] = ['s1', 's2', 's3', 's4', 's5', 's6'] + data['count'] = [1, 2, 3, 4, 5, 6] + set_module_args(data) + my_obj = my_module() + my_obj.asup_log_for_cserver = Mock(return_value=None) + if not self.onbox: + my_obj.server = self.server + with pytest.raises(AnsibleFailJson) as exc: + my_obj.create_snapshot_policy() + msg = 'Error: A Snapshot policy must have at least 1 ' \ + 'schedule and can have up to a maximum of 5 schedules, with a count ' \ + 'representing the maximum number of Snapshot copies for each schedule' + assert exc.value.args[0]['msg'] == msg + + def test_invalid_schedule_count_less_than_one(self): + ''' validate error when schedule has less than 1 element ''' + data = self.set_default_args() + data['schedule'] = [] + data['count'] = [] + set_module_args(data) + my_obj = my_module() + my_obj.asup_log_for_cserver = Mock(return_value=None) + if not self.onbox: + my_obj.server = self.server + with pytest.raises(AnsibleFailJson) as exc: + my_obj.create_snapshot_policy() + msg = 'Error: A Snapshot policy must have at least 1 ' \ + 'schedule and can have up to a maximum of 5 schedules, with a count ' \ + 'representing the maximum number of Snapshot copies for each schedule' + assert exc.value.args[0]['msg'] == msg + + def test_invalid_schedule_count_is_none(self): + ''' validate error when schedule is None ''' + data = self.set_default_args() + data['schedule'] = None + data['count'] = None + set_module_args(data) + my_obj = my_module() + my_obj.asup_log_for_cserver = Mock(return_value=None) + if not self.onbox: + my_obj.server = self.server + with pytest.raises(AnsibleFailJson) as exc: + my_obj.create_snapshot_policy() + msg = 'Error: A Snapshot policy must have at least 1 ' \ + 'schedule and can have up to a maximum of 5 schedules, with a count ' \ + 'representing the maximum number of Snapshot copies for each schedule' + assert exc.value.args[0]['msg'] == msg + + def test_invalid_schedule_count_with_snapmirror_labels(self): + ''' validate error when schedule with snapmirror labels does not have same number of elements ''' + data = self.set_default_args() + data['schedule'] = ['s1', 's2', 's3'] + data['count'] = [1, 2, 3] + data['snapmirror_label'] = ['sm1', 'sm2'] + set_module_args(data) + my_obj = my_module() + my_obj.asup_log_for_cserver = Mock(return_value=None) + if not self.onbox: + my_obj.server = self.server + with pytest.raises(AnsibleFailJson) as exc: + my_obj.create_snapshot_policy() + msg = 'Error: Each Snapshot Policy schedule must have an accompanying SnapMirror Label' + assert exc.value.args[0]['msg'] == msg + + def test_invalid_schedule_count_with_prefixes(self): + ''' validate error when schedule with prefixes does not have same number of elements ''' + data = self.set_default_args() + data['schedule'] = ['s1', 's2', 's3'] + data['count'] = [1, 2, 3] + data['prefix'] = ['s1', 's2'] + set_module_args(data) + my_obj = my_module() + my_obj.asup_log_for_cserver = Mock(return_value=None) + if not self.onbox: + my_obj.server = self.server + with pytest.raises(AnsibleFailJson) as exc: + my_obj.create_snapshot_policy() + msg = 'Error: Each Snapshot Policy schedule must have an accompanying prefix' + assert exc.value.args[0]['msg'] == msg + + def test_if_all_methods_catch_exception(self): + module_args = {} + module_args.update(self.set_default_args()) + set_module_args(module_args) + my_obj = my_module() + if not self.onbox: + my_obj.server = MockONTAPConnection('policy_fail') + with pytest.raises(AnsibleFailJson) as exc: + my_obj.create_snapshot_policy() + assert 'Error creating snapshot policy ansible:' in exc.value.args[0]['msg'] + with pytest.raises(AnsibleFailJson) as exc: + my_obj.delete_snapshot_policy() + assert 'Error deleting snapshot policy ansible:' in exc.value.args[0]['msg'] diff --git a/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_snmp_traphosts.py b/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_snmp_traphosts.py new file mode 100644 index 00000000..18092fe0 --- /dev/null +++ b/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_snmp_traphosts.py @@ -0,0 +1,153 @@ +# (c) 2020, NetApp, Inc +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +""" unit tests for Ansible module: na_ontap_snmp_traphosts """ + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type +import json +import pytest + +from ansible.module_utils import basic +from ansible.module_utils._text import to_bytes +from ansible_collections.netapp.ontap.tests.unit.compat import unittest +from ansible_collections.netapp.ontap.tests.unit.compat.mock import patch + +from ansible_collections.netapp.ontap.plugins.modules.na_ontap_snmp_traphosts \ + import NetAppONTAPSnmpTraphosts as traphost_module # module under test + +# REST API canned responses when mocking send_request + +SRR = { + # common responses + 'is_rest': (200, {}, None), + 'is_zapi': (400, {}, "Unreachable"), + 'empty_good': (200, {}, None), + 'end_of_sequence': (500, None, "Unexpected call to send_request"), + 'generic_error': (400, None, "Expected error"), + # module specific responses + 'no_record': (200, {"records": {}}, None), + 'get_snmp_traphosts': ( + 200, + {"records": [{ + "host": "0.0.0.0", + "ip_address": "0.0.0.0" + }] + }, None + ) +} + + +def set_module_args(args): + """prepare arguments so that they will be picked up during module creation""" + args = json.dumps({'ANSIBLE_MODULE_ARGS': args}) + basic._ANSIBLE_ARGS = to_bytes(args) # pylint: disable=protected-access + + +class AnsibleExitJson(Exception): + """Exception class to be raised by module.exit_json and caught by the test case""" + + +class AnsibleFailJson(Exception): + """Exception class to be raised by module.fail_json and caught by the test case""" + + +def exit_json(*args, **kwargs): # pylint: disable=unused-argument + """function to patch over exit_json; package return data into an exception""" + if 'changed' not in kwargs: + kwargs['changed'] = False + raise AnsibleExitJson(kwargs) + + +def fail_json(*args, **kwargs): # pylint: disable=unused-argument + """function to patch over fail_json; package return data into an exception""" + kwargs['failed'] = True + raise AnsibleFailJson(kwargs) + + +class TestMyModule(unittest.TestCase): + """ Unit tests for na_ontap_wwpn_alias """ + + def setUp(self): + self.mock_module_helper = patch.multiple(basic.AnsibleModule, + exit_json=exit_json, + fail_json=fail_json) + self.mock_module_helper.start() + self.addCleanup(self.mock_module_helper.stop) + self.mock_alias = { + 'ip_address': '0.0.0.0', + } + + def mock_args(self): + return { + 'ip_address': self.mock_alias['ip_address'], + 'hostname': 'test_host', + 'username': 'test_user', + 'password': 'test_pass!' + } + + def get_alias_mock_object(self): + alias_obj = traphost_module() + return alias_obj + + @patch('ansible_collections.netapp.ontap.plugins.module_utils.netapp.OntapRestAPI.send_request') + def test_rest_successful_create(self, mock_request): + """Test successful rest create""" + data = self.mock_args() + set_module_args(data) + mock_request.side_effect = [ + SRR['is_rest'], + SRR['no_record'], + SRR['empty_good'], + SRR['end_of_sequence'] + ] + with pytest.raises(AnsibleExitJson) as exc: + self.get_alias_mock_object().apply() + assert exc.value.args[0]['changed'] + + @patch('ansible_collections.netapp.ontap.plugins.module_utils.netapp.OntapRestAPI.send_request') + def test_rest_create_idempotency(self, mock_request): + """Test rest create idempotency""" + data = self.mock_args() + set_module_args(data) + mock_request.side_effect = [ + SRR['is_rest'], + SRR['get_snmp_traphosts'], + SRR['empty_good'], + SRR['end_of_sequence'] + ] + with pytest.raises(AnsibleExitJson) as exc: + self.get_alias_mock_object().apply() + assert not exc.value.args[0]['changed'] + + @patch('ansible_collections.netapp.ontap.plugins.module_utils.netapp.OntapRestAPI.send_request') + def test_rest_successful_delete(self, mock_request): + """Test successful rest delete""" + data = self.mock_args() + data['state'] = 'absent' + set_module_args(data) + mock_request.side_effect = [ + SRR['is_rest'], + SRR['get_snmp_traphosts'], + SRR['empty_good'], + SRR['end_of_sequence'] + ] + with pytest.raises(AnsibleExitJson) as exc: + self.get_alias_mock_object().apply() + assert exc.value.args[0]['changed'] + + @patch('ansible_collections.netapp.ontap.plugins.module_utils.netapp.OntapRestAPI.send_request') + def test_rest_delete_idempotency(self, mock_request): + """Test successful rest delete""" + data = self.mock_args() + data['state'] = 'absent' + set_module_args(data) + mock_request.side_effect = [ + SRR['is_rest'], + SRR['no_record'], + SRR['empty_good'], + SRR['end_of_sequence'] + ] + with pytest.raises(AnsibleExitJson) as exc: + self.get_alias_mock_object().apply() + assert not exc.value.args[0]['changed'] diff --git a/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_software_update.py b/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_software_update.py new file mode 100644 index 00000000..a771b276 --- /dev/null +++ b/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_software_update.py @@ -0,0 +1,190 @@ +# (c) 2018, NetApp, Inc +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +''' unit tests ONTAP Ansible module: na_ontap_software_update ''' + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type +import json +import pytest + +from ansible.module_utils import basic +from ansible.module_utils._text import to_bytes +from ansible_collections.netapp.ontap.tests.unit.compat import unittest +from ansible_collections.netapp.ontap.tests.unit.compat.mock import patch, Mock +import ansible_collections.netapp.ontap.plugins.module_utils.netapp as netapp_utils + +from ansible_collections.netapp.ontap.plugins.modules.na_ontap_software_update \ + import NetAppONTAPSoftwareUpdate as my_module # module under test + +if not netapp_utils.has_netapp_lib(): + pytestmark = pytest.mark.skip('skipping as missing required netapp_lib') + + +def set_module_args(args): + """prepare arguments so that they will be picked up during module creation""" + args = json.dumps({'ANSIBLE_MODULE_ARGS': args}) + basic._ANSIBLE_ARGS = to_bytes(args) # pylint: disable=protected-access + + +class AnsibleExitJson(Exception): + """Exception class to be raised by module.exit_json and caught by the test case""" + + +class AnsibleFailJson(Exception): + """Exception class to be raised by module.fail_json and caught by the test case""" + + +def exit_json(*args, **kwargs): # pylint: disable=unused-argument + """function to patch over exit_json; package return data into an exception""" + if 'changed' not in kwargs: + kwargs['changed'] = False + raise AnsibleExitJson(kwargs) + + +def fail_json(*args, **kwargs): # pylint: disable=unused-argument + """function to patch over fail_json; package return data into an exception""" + kwargs['failed'] = True + raise AnsibleFailJson(kwargs) + + +class MockONTAPConnection(object): + ''' mock server connection to ONTAP host ''' + + def __init__(self, kind=None, parm1=None, parm2=None): + ''' save arguments ''' + self.type = kind + self.parm1 = parm1 + self.parm2 = parm2 + self.xml_in = None + self.xml_out = None + + def invoke_successfully(self, xml, enable_tunneling): # pylint: disable=unused-argument + ''' mock invoke_successfully returning xml data ''' + self.xml_in = xml + print(xml.to_string()) + if xml.to_string().startswith(b'<cluster-image-get><node-id>'): + xml = self.build_image_info() + elif self.type == 'software_update': + xml = self.build_software_update_info(self.parm1, self.parm2) + self.xml_out = xml + return xml + + def autosupport_log(self): + ''' mock autosupport log''' + return None + + @staticmethod + def build_image_info(): + ''' build xml data for software-update-info ''' + xml = netapp_utils.zapi.NaElement('xml') + data = { + 'attributes': {'cluster-image-info': {'node-id': 'node4test', + 'current-version': 'Fattire__9.3.0'}}, + } + xml.translate_struct(data) + print(xml.to_string()) + return xml + + @staticmethod + def build_software_update_info(status, node): + ''' build xml data for software-update-info ''' + xml = netapp_utils.zapi.NaElement('xml') + data = { + 'num-records': 1, + 'attributes-list': {'cluster-image-info': {'node-id': node}}, + 'progress-status': status, + 'attributes': {'ndu-progress-info': {'overall-status': 'completed', + 'completed-node-count': '0'}}, + } + xml.translate_struct(data) + print(xml.to_string()) + return xml + + +class TestMyModule(unittest.TestCase): + ''' a group of related Unit Tests ''' + + def setUp(self): + self.mock_module_helper = patch.multiple(basic.AnsibleModule, + exit_json=exit_json, + fail_json=fail_json) + self.mock_module_helper.start() + self.addCleanup(self.mock_module_helper.stop) + self.server = MockONTAPConnection() + self.use_vsim = False + + def set_default_args(self): + if self.use_vsim: + hostname = '10.10.10.10' + username = 'admin' + password = 'admin' + node = 'vsim1' + package_version = 'Fattire__9.3.0' + package_url = 'abc.com' + stabilize_minutes = 10 + else: + hostname = 'hostname' + username = 'username' + password = 'password' + node = 'abc' + package_version = 'Fattire__9.3.0' + package_url = 'abc.com' + stabilize_minutes = 10 + return dict({ + 'hostname': hostname, + 'username': username, + 'password': password, + 'nodes': node, + 'package_version': package_version, + 'package_url': package_url, + 'https': 'true', + 'stabilize_minutes': stabilize_minutes + }) + + def test_module_fail_when_required_args_missing(self): + ''' required arguments are reported as errors ''' + with pytest.raises(AnsibleFailJson) as exc: + set_module_args({}) + my_module() + print('Info: %s' % exc.value.args[0]['msg']) + + def test_ensure_image_get_called(self): + ''' a more interesting test ''' + set_module_args(self.set_default_args()) + my_obj = my_module() + my_obj.server = self.server + cluster_image_get = my_obj.cluster_image_get() + print('Info: test_software_update_get: %s' % repr(cluster_image_get)) + assert cluster_image_get == list() + + def test_ensure_apply_for_update_called_idempotent(self): + ''' updating software and checking idempotency ''' + module_args = {} + module_args.update(self.set_default_args()) + set_module_args(module_args) + my_obj = my_module() + my_obj.autosupport_log = Mock(return_value=None) + if not self.use_vsim: + my_obj.server = self.server + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + print('Info: test_software_update_apply: %s' % repr(exc.value)) + assert not exc.value.args[0]['changed'] + + def test_ensure_apply_for_update_called(self): + ''' updating software and checking idempotency ''' + module_args = {} + module_args.update(self.set_default_args()) + module_args.update({'package_version': 'PlinyTheElder'}) + set_module_args(module_args) + my_obj = my_module() + my_obj.autosupport_log = Mock(return_value=None) + if not self.use_vsim: + my_obj.server = MockONTAPConnection('software_update', 'async_pkg_get_phase_complete', 'abc') + with pytest.raises(AnsibleExitJson) as exc: + # replace time.sleep with a noop + with patch('time.sleep', lambda a: None): + my_obj.apply() + print('Info: test_software_update_apply: %s' % repr(exc.value)) + assert exc.value.args[0]['changed'] diff --git a/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_svm.py b/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_svm.py new file mode 100644 index 00000000..9ea6785a --- /dev/null +++ b/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_svm.py @@ -0,0 +1,430 @@ +# (c) 2018, NetApp, Inc +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +''' unit test template for ONTAP Ansible module ''' + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import json +import pytest + +from ansible.module_utils import basic +from ansible.module_utils._text import to_bytes +from ansible_collections.netapp.ontap.tests.unit.compat import unittest +from ansible_collections.netapp.ontap.tests.unit.compat.mock import patch, Mock +import ansible_collections.netapp.ontap.plugins.module_utils.netapp as netapp_utils + +from ansible_collections.netapp.ontap.plugins.modules.na_ontap_svm \ + import NetAppOntapSVM as svm_module # module under test + +if not netapp_utils.has_netapp_lib(): + pytestmark = pytest.mark.skip('skipping as missing required netapp_lib') + +# REST API canned responses when mocking send_request +SRR = { + # common responses + 'is_rest': (200, {}, None), + 'is_zapi': (400, {}, "Unreachable"), + 'empty_good': (200, {}, None), + 'end_of_sequence': (500, None, "Unexpected call to send_request"), + 'generic_error': (400, None, "Expected error"), + # module specific responses + 'svm_record': (200, + {'records': [{"uuid": "09e9fd5e-8ebd-11e9-b162-005056b39fe7", + "name": "test_svm", + "subtype": "default", + "language": "c.utf_8", + "aggregates": [{"name": "aggr_1", + "uuid": "850dd65b-8811-4611-ac8c-6f6240475ff9"}, + {"name": "aggr_2", + "uuid": "850dd65b-8811-4611-ac8c-6f6240475ff9"}], + "comment": "new comment", + "ipspace": {"name": "ansible_ipspace", + "uuid": "2b760d31-8dfd-11e9-b162-005056b39fe7"}, + "snapshot_policy": {"uuid": "3b611707-8dfd-11e9-b162-005056b39fe7", + "name": "old_snapshot_policy"}, + "nfs": {"enabled": True}, + "cifs": {"enabled": False}, + "iscsi": {"enabled": False}, + "fcp": {"enabled": False}, + "nvme": {"enabled": False}}]}, None) +} + + +def set_module_args(args): + """prepare arguments so that they will be picked up during module creation""" + args = json.dumps({'ANSIBLE_MODULE_ARGS': args}) + basic._ANSIBLE_ARGS = to_bytes(args) # pylint: disable=protected-access + + +class AnsibleExitJson(Exception): + """Exception class to be raised by module.exit_json and caught by the test case""" + + +class AnsibleFailJson(Exception): + """Exception class to be raised by module.fail_json and caught by the test case""" + + +def exit_json(*args, **kwargs): # pylint: disable=unused-argument + """function to patch over exit_json; package return data into an exception""" + if 'changed' not in kwargs: + kwargs['changed'] = False + raise AnsibleExitJson(kwargs) + + +def fail_json(*args, **kwargs): # pylint: disable=unused-argument + """function to patch over fail_json; package return data into an exception""" + kwargs['failed'] = True + raise AnsibleFailJson(kwargs) + + +class MockONTAPConnection(object): + ''' mock server connection to ONTAP host ''' + + def __init__(self, kind=None, data=None): + ''' save arguments ''' + self.type = kind + self.params = data + self.xml_in = None + self.xml_out = None + + def invoke_successfully(self, xml, enable_tunneling): # pylint: disable=unused-argument + ''' mock invoke_successfully returning xml data ''' + self.xml_in = xml + if self.type == 'vserver': + xml = self.build_vserver_info(self.params) + self.xml_out = xml + return xml + + @staticmethod + def build_vserver_info(vserver): + ''' build xml data for vserser-info ''' + xml = netapp_utils.zapi.NaElement('xml') + data = {'num-records': 1, 'attributes-list': {'vserver-info': { + 'vserver-name': vserver['name'], + 'ipspace': vserver['ipspace'], + 'root-volume': vserver['root_volume'], + 'root-volume-aggregate': vserver['root_volume_aggregate'], + 'language': vserver['language'], + 'comment': vserver['comment'], + 'snapshot-policy': vserver['snapshot_policy'], + 'vserver-subtype': vserver['subtype'], + 'allowed-protocols': [{'protocol': 'nfs'}, {'protocol': 'cifs'}], + 'aggr-list': [{'aggr-name': 'aggr_1'}, {'aggr-name': 'aggr_2'}], + }}} + xml.translate_struct(data) + return xml + + +class TestMyModule(unittest.TestCase): + ''' a group of related Unit Tests ''' + + def setUp(self): + self.mock_module_helper = patch.multiple(basic.AnsibleModule, + exit_json=exit_json, + fail_json=fail_json) + self.mock_module_helper.start() + self.addCleanup(self.mock_module_helper.stop) + self.server = MockONTAPConnection() + self.mock_vserver = { + 'name': 'test_svm', + 'root_volume': 'ansible_vol', + 'root_volume_aggregate': 'ansible_aggr', + 'aggr_list': 'aggr_1,aggr_2', + 'ipspace': 'ansible_ipspace', + 'subtype': 'default', + 'language': 'c.utf_8', + 'snapshot_policy': 'old_snapshot_policy', + 'comment': 'new comment' + } + + def mock_args(self, rest=False): + if rest: + return {'name': self.mock_vserver['name'], + 'aggr_list': self.mock_vserver['aggr_list'], + 'ipspace': self.mock_vserver['ipspace'], + 'comment': self.mock_vserver['comment'], + 'subtype': 'default', + 'hostname': 'test', + 'username': 'test_user', + 'password': 'test_pass!'} + else: + return { + 'name': self.mock_vserver['name'], + 'root_volume': self.mock_vserver['root_volume'], + 'root_volume_aggregate': self.mock_vserver['root_volume_aggregate'], + 'aggr_list': self.mock_vserver['aggr_list'], + 'ipspace': self.mock_vserver['ipspace'], + 'comment': self.mock_vserver['comment'], + 'subtype': 'default', + 'hostname': 'test', + 'username': 'test_user', + 'password': 'test_pass!' + } + + def get_vserver_mock_object(self, kind=None, data=None, cx_type='zapi'): + """ + Helper method to return an na_ontap_volume object + :param kind: passes this param to MockONTAPConnection() + :param data: passes this param to MockONTAPConnection() + :return: na_ontap_volume object + """ + vserver_obj = svm_module() + if cx_type == 'zapi': + vserver_obj.asup_log_for_cserver = Mock(return_value=None) + vserver_obj.cluster = Mock() + vserver_obj.cluster.invoke_successfully = Mock() + if kind is None: + vserver_obj.server = MockONTAPConnection() + else: + if data is None: + vserver_obj.server = MockONTAPConnection(kind='vserver', data=self.mock_vserver) + else: + vserver_obj.server = MockONTAPConnection(kind='vserver', data=data) + return vserver_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({}) + svm_module() + print('Info: %s' % exc.value.args[0]['msg']) + + def test_get_nonexistent_vserver(self): + ''' test if get_vserver() throws an error if vserver is not specified ''' + data = self.mock_args() + set_module_args(data) + result = self.get_vserver_mock_object().get_vserver() + assert result is None + + def test_create_error_missing_name(self): + ''' Test if create throws an error if name is not specified''' + data = self.mock_args() + del data['name'] + set_module_args(data) + with pytest.raises(AnsibleFailJson) as exc: + self.get_vserver_mock_object('vserver').create_vserver() + msg = 'missing required arguments: name' + assert exc.value.args[0]['msg'] == msg + + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_svm.NetAppOntapSVM.create_vserver') + def test_successful_create(self, create_vserver): + '''Test successful create''' + data = self.mock_args() + set_module_args(data) + with pytest.raises(AnsibleExitJson) as exc: + self.get_vserver_mock_object().apply() + assert exc.value.args[0]['changed'] + create_vserver.assert_called_with() + + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_svm.NetAppOntapSVM.create_vserver') + def test_create_idempotency(self, create_vserver): + '''Test successful create''' + data = self.mock_args() + set_module_args(data) + with pytest.raises(AnsibleExitJson) as exc: + self.get_vserver_mock_object('vserver').apply() + assert not exc.value.args[0]['changed'] + create_vserver.assert_not_called() + + def test_successful_delete(self): + '''Test successful delete''' + data = self.mock_args() + data['state'] = 'absent' + set_module_args(data) + with pytest.raises(AnsibleExitJson) as exc: + self.get_vserver_mock_object('vserver').apply() + assert exc.value.args[0]['changed'] + + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_svm.NetAppOntapSVM.delete_vserver') + def test_delete_idempotency(self, delete_vserver): + '''Test delete idempotency''' + data = self.mock_args() + data['state'] = 'absent' + set_module_args(data) + with pytest.raises(AnsibleExitJson) as exc: + self.get_vserver_mock_object().apply() + assert not exc.value.args[0]['changed'] + delete_vserver.assert_not_called() + + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_svm.NetAppOntapSVM.get_vserver') + def test_successful_rename(self, get_vserver): + '''Test successful rename''' + data = self.mock_args() + data['from_name'] = 'test_svm' + data['name'] = 'test_new_svm' + set_module_args(data) + current = { + 'name': 'test_svm', + 'root_volume': 'ansible_vol', + 'root_volume_aggregate': 'ansible_aggr', + 'ipspace': 'ansible_ipspace', + 'subtype': 'default', + 'language': 'c.utf_8' + } + get_vserver.side_effect = [ + None, + current + ] + with pytest.raises(AnsibleExitJson) as exc: + self.get_vserver_mock_object().apply() + assert exc.value.args[0]['changed'] + + def test_successful_modify_language(self): + '''Test successful modify language''' + data = self.mock_args() + data['language'] = 'c' + set_module_args(data) + with pytest.raises(AnsibleExitJson) as exc: + self.get_vserver_mock_object('vserver').apply() + assert exc.value.args[0]['changed'] + + def test_successful_modify_snapshot_policy(self): + '''Test successful modify language''' + data = self.mock_args() + data['snapshot_policy'] = 'new_snapshot_policy' + set_module_args(data) + with pytest.raises(AnsibleExitJson) as exc: + self.get_vserver_mock_object('vserver').apply() + assert exc.value.args[0]['changed'] + + def test_successful_modify_allowed_protocols(self): + '''Test successful modify allowed protocols''' + data = self.mock_args() + data['allowed_protocols'] = 'protocol_1,protocol_2' + set_module_args(data) + with pytest.raises(AnsibleExitJson) as exc: + self.get_vserver_mock_object('vserver').apply() + assert exc.value.args[0]['changed'] + + def test_successful_modify_aggr_list(self): + '''Test successful modify aggr-list''' + data = self.mock_args() + data['aggr_list'] = 'aggr_3,aggr_4' + set_module_args(data) + with pytest.raises(AnsibleExitJson) as exc: + self.get_vserver_mock_object('vserver').apply() + assert exc.value.args[0]['changed'] + + @patch('ansible_collections.netapp.ontap.plugins.module_utils.netapp.OntapRestAPI.send_request') + def test_rest_error(self, mock_request): + data = self.mock_args(rest=True) + set_module_args(data) + mock_request.side_effect = [ + SRR['is_rest'], + SRR['generic_error'], + SRR['end_of_sequence'] + ] + with pytest.raises(AnsibleFailJson) as exc: + self.get_vserver_mock_object(cx_type='rest').apply() + assert exc.value.args[0]['msg'] == SRR['generic_error'][2] + + @patch('ansible_collections.netapp.ontap.plugins.module_utils.netapp.OntapRestAPI.send_request') + def test_rest_error_unsupported_parm(self, mock_request): + data = self.mock_args(rest=True) + data['use_rest'] = 'Always' + data['root_volume'] = 'not_supported_by_rest' + set_module_args(data) + mock_request.side_effect = [ + SRR['is_rest'], + SRR['end_of_sequence'] + ] + with pytest.raises(AnsibleFailJson) as exc: + self.get_vserver_mock_object(cx_type='rest').apply() + assert exc.value.args[0]['msg'] == "REST API currently does not support 'root_volume'" + + @patch('ansible_collections.netapp.ontap.plugins.module_utils.netapp.OntapRestAPI.send_request') + def test_rest_successfully_create(self, mock_request): + data = self.mock_args(rest=True) + data['state'] = 'present' + set_module_args(data) + mock_request.side_effect = [ + SRR['is_rest'], + SRR['empty_good'], # get + SRR['empty_good'], # post + SRR['end_of_sequence'] + ] + with pytest.raises(AnsibleExitJson) as exc: + self.get_vserver_mock_object(cx_type='rest').apply() + assert exc.value.args[0]['changed'] + + @patch('ansible_collections.netapp.ontap.plugins.module_utils.netapp.OntapRestAPI.send_request') + def test_rest_create_idempotency(self, mock_request): + data = self.mock_args(rest=True) + data['state'] = 'present' + set_module_args(data) + mock_request.side_effect = [ + SRR['is_rest'], + SRR['svm_record'], # get + SRR['end_of_sequence'] + ] + with pytest.raises(AnsibleExitJson) as exc: + self.get_vserver_mock_object(cx_type='rest').apply() + assert not exc.value.args[0]['changed'] + + @patch('ansible_collections.netapp.ontap.plugins.module_utils.netapp.OntapRestAPI.send_request') + def test_rest_successful_delete(self, mock_request): + '''Test successful delete''' + data = self.mock_args(rest=True) + data['state'] = 'absent' + set_module_args(data) + mock_request.side_effect = [ + SRR['is_rest'], + SRR['svm_record'], # get + SRR['empty_good'], # delete + SRR['end_of_sequence'] + ] + with pytest.raises(AnsibleExitJson) as exc: + self.get_vserver_mock_object(cx_type='rest').apply() + assert exc.value.args[0]['changed'] + + @patch('ansible_collections.netapp.ontap.plugins.module_utils.netapp.OntapRestAPI.send_request') + def test_rest_delete_idempotency(self, mock_request): + '''Test delete idempotency''' + data = self.mock_args(rest=True) + data['state'] = 'absent' + set_module_args(data) + mock_request.side_effect = [ + SRR['is_rest'], + SRR['empty_good'], # get + SRR['end_of_sequence'] + ] + with pytest.raises(AnsibleExitJson) as exc: + self.get_vserver_mock_object(cx_type='rest').apply() + assert not exc.value.args[0]['changed'] + + @patch('ansible_collections.netapp.ontap.plugins.module_utils.netapp.OntapRestAPI.send_request') + def test_rest_successful_rename(self, mock_request): + '''Test successful rename''' + data = self.mock_args(rest=True) + data['from_name'] = 'test_svm' + data['name'] = 'test_new_svm' + set_module_args(data) + mock_request.side_effect = [ + SRR['is_rest'], + SRR['svm_record'], # get + SRR['svm_record'], # get + SRR['empty_good'], # patch + SRR['end_of_sequence'] + ] + with pytest.raises(AnsibleExitJson) as exc: + self.get_vserver_mock_object(cx_type='rest').apply() + assert exc.value.args[0]['changed'] + + @patch('ansible_collections.netapp.ontap.plugins.module_utils.netapp.OntapRestAPI.send_request') + def test_rest_successful_modify_language(self, mock_request): + '''Test successful modify language''' + data = self.mock_args(rest=True) + data['language'] = 'c' + set_module_args(data) + mock_request.side_effect = [ + SRR['is_rest'], + SRR['svm_record'], # get + SRR['svm_record'], # get + SRR['empty_good'], # patch + SRR['end_of_sequence'] + ] + with pytest.raises(AnsibleExitJson) as exc: + self.get_vserver_mock_object(cx_type='rest').apply() + assert exc.value.args[0]['changed'] diff --git a/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_template.py b/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_template.py new file mode 100644 index 00000000..60648253 --- /dev/null +++ b/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_template.py @@ -0,0 +1,121 @@ +# (c) 2018, NetApp, Inc +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +''' unit test template for ONTAP Ansible module ''' + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type +import json +import pytest + +from ansible_collections.netapp.ontap.tests.unit.compat import unittest +from ansible_collections.netapp.ontap.tests.unit.compat.mock import patch +from ansible.module_utils import basic +from ansible.module_utils._text import to_bytes +import ansible_collections.netapp.ontap.plugins.module_utils.netapp as netapp_utils + +from ansible_collections.netapp.ontap.plugins.modules.na_ontap_cg_snapshot \ + import NetAppONTAPCGSnapshot as my_module # module under test + +if not netapp_utils.has_netapp_lib(): + pytestmark = pytest.mark.skip('skipping as missing required netapp_lib') + + +def set_module_args(args): + """prepare arguments so that they will be picked up during module creation""" + args = json.dumps({'ANSIBLE_MODULE_ARGS': args}) + basic._ANSIBLE_ARGS = to_bytes(args) # pylint: disable=protected-access + + +class AnsibleExitJson(Exception): + """Exception class to be raised by module.exit_json and caught by the test case""" + pass + + +class AnsibleFailJson(Exception): + """Exception class to be raised by module.fail_json and caught by the test case""" + pass + + +def exit_json(*args, **kwargs): # pylint: disable=unused-argument + """function to patch over exit_json; package return data into an exception""" + if 'changed' not in kwargs: + kwargs['changed'] = False + raise AnsibleExitJson(kwargs) + + +def fail_json(*args, **kwargs): # pylint: disable=unused-argument + """function to patch over fail_json; package return data into an exception""" + kwargs['failed'] = True + raise AnsibleFailJson(kwargs) + + +class MockONTAPConnection(object): + ''' mock server connection to ONTAP host ''' + + def __init__(self, kind=None, parm1=None): + ''' save arguments ''' + self.type = kind + self.parm1 = parm1 + self.xml_in = None + self.xml_out = None + + def invoke_successfully(self, xml, enable_tunneling): # pylint: disable=unused-argument + ''' mock invoke_successfully returning xml data ''' + self.xml_in = xml + if self.type == 'vserver': + xml = self.build_vserver_info(self.parm1) + self.xml_out = xml + return xml + + @staticmethod + def build_vserver_info(vserver): + ''' 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}) + xml.add_child_elem(attributes) + # print(xml.to_string()) + return xml + + +class TestMyModule(unittest.TestCase): + ''' a group of related Unit Tests ''' + + def setUp(self): + self.mock_module_helper = patch.multiple(basic.AnsibleModule, + exit_json=exit_json, + fail_json=fail_json) + self.mock_module_helper.start() + self.addCleanup(self.mock_module_helper.stop) + self.server = MockONTAPConnection() + + def test_module_fail_when_required_args_missing(self): + ''' required arguments are reported as errors ''' + with pytest.raises(AnsibleFailJson) as exc: + set_module_args({}) + my_module() + print('Info: %s' % exc.value.args[0]['msg']) + + def test_ensure_command_called(self): + ''' a more interesting test ''' +# TODO: change argument names/values + set_module_args({ + 'vserver': 'vserver', + 'volumes': 'volumes', + 'snapshot': 'snapshot', + 'hostname': 'hostname', + 'username': 'username', + 'password': 'password', + }) + my_obj = my_module() + my_obj.server = self.server + with pytest.raises(AnsibleFailJson) as exc: + # It may not be a good idea to start with apply + # More atomic methods can be easier to mock + # Hint: start with get methods, as they are called first + my_obj.apply() +# TODO: change message, and maybe test contents + msg = 'Error fetching CG ID for CG commit snapshot' + assert exc.value.args[0]['msg'] == msg diff --git a/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_ucadapter.py b/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_ucadapter.py new file mode 100644 index 00000000..6b8459a2 --- /dev/null +++ b/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_ucadapter.py @@ -0,0 +1,176 @@ +# (c) 2018, NetApp, Inc +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +''' unit tests ONTAP Ansible module: na_ontap_ucadapter ''' + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type +import json +import pytest + +from ansible_collections.netapp.ontap.tests.unit.compat import unittest +from ansible_collections.netapp.ontap.tests.unit.compat.mock import patch, Mock +from ansible.module_utils import basic +from ansible.module_utils._text import to_bytes +import ansible_collections.netapp.ontap.plugins.module_utils.netapp as netapp_utils + +from ansible_collections.netapp.ontap.plugins.modules.na_ontap_ucadapter \ + import NetAppOntapadapter as ucadapter_module # module under test + +if not netapp_utils.has_netapp_lib(): + pytestmark = pytest.mark.skip('skipping as missing required netapp_lib') + + +def set_module_args(args): + """prepare arguments so that they will be picked up during module creation""" + args = json.dumps({'ANSIBLE_MODULE_ARGS': args}) + basic._ANSIBLE_ARGS = to_bytes(args) # pylint: disable=protected-access + + +class AnsibleExitJson(Exception): + """Exception class to be raised by module.exit_json and caught by the test case""" + pass + + +class AnsibleFailJson(Exception): + """Exception class to be raised by module.fail_json and caught by the test case""" + pass + + +def exit_json(*args, **kwargs): # pylint: disable=unused-argument + """function to patch over exit_json; package return data into an exception""" + if 'changed' not in kwargs: + kwargs['changed'] = False + raise AnsibleExitJson(kwargs) + + +def fail_json(*args, **kwargs): # pylint: disable=unused-argument + """function to patch over fail_json; package return data into an exception""" + kwargs['failed'] = True + raise AnsibleFailJson(kwargs) + + +class MockONTAPConnection(object): + ''' mock server connection to ONTAP host ''' + + def __init__(self, kind=None, data=None): + ''' save arguments ''' + self.type = kind + self.parm1 = data + self.xml_in = None + self.xml_out = None + + def invoke_successfully(self, xml, enable_tunneling): # pylint: disable=unused-argument + ''' mock invoke_successfully returning xml data ''' + self.xml_in = xml + if self.type == 'ucadapter': + xml = self.build_ucadapter_info(self.parm1) + self.xml_out = xml + return xml + + def autosupport_log(self): + ''' mock autosupport log''' + return None + + @staticmethod + def build_ucadapter_info(params): + ''' build xml data for ucadapter_info ''' + xml = netapp_utils.zapi.NaElement('xml') + data = {'attributes': {'uc-adapter-info': { + 'mode': 'fc', + 'pending-mode': 'abc', + 'type': 'target', + 'pending-type': 'intitiator', + 'status': params['status'], + }}} + xml.translate_struct(data) + print(xml.to_string()) + return xml + + +class TestMyModule(unittest.TestCase): + ''' a group of related Unit Tests ''' + + def setUp(self): + self.mock_module_helper = patch.multiple(basic.AnsibleModule, + exit_json=exit_json, + fail_json=fail_json) + self.mock_module_helper.start() + self.addCleanup(self.mock_module_helper.stop) + self.server = MockONTAPConnection() + self.use_vsim = False + self.mock_ucadapter = { + 'mode': 'fc', + 'pending-mode': 'fc', + 'type': 'target', + 'pending-type': 'intitiator', + 'status': 'up', + } + + def set_default_args(self): + args = (dict({ + 'hostname': '10.0.0.0', + 'username': 'user', + 'password': 'pass', + 'node_name': 'node1', + 'adapter_name': '0f', + 'mode': self.mock_ucadapter['mode'], + 'type': self.mock_ucadapter['type'] + })) + return args + + def get_ucadapter_mock_object(self, kind=None, data=None): + """ + Helper method to return an na_ontap_unix_user object + :param kind: passes this param to MockONTAPConnection() + :return: na_ontap_unix_user object + """ + obj = ucadapter_module() + obj.autosupport_log = Mock(return_value=None) + params = self.mock_ucadapter + if data is not None: + for k, v in data.items(): + params[k] = v + obj.server = MockONTAPConnection(kind=kind, data=params) + 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({}) + ucadapter_module() + print('Info: %s' % exc.value.args[0]['msg']) + + def test_ensure_ucadapter_get_called(self): + ''' fetching ucadapter details ''' + set_module_args(self.set_default_args()) + get_adapter = self.get_ucadapter_mock_object().get_adapter() + print('Info: test_ucadapter_get: %s' % repr(get_adapter)) + assert get_adapter is None + + def test_change_mode_from_cna_to_fc(self): + ''' configuring ucadaptor and checking idempotency ''' + module_args = {} + module_args.update(self.set_default_args()) + set_module_args(module_args) + with pytest.raises(AnsibleExitJson) as exc: + self.get_ucadapter_mock_object().apply() + assert not exc.value.args[0]['changed'] + with pytest.raises(AnsibleExitJson) as exc: + self.get_ucadapter_mock_object('ucadapter', {'mode': 'cna', 'pending-mode': 'cna'}).apply() + assert exc.value.args[0]['changed'] + + module_args['type'] = 'intitiator' + set_module_args(module_args) + with pytest.raises(AnsibleExitJson) as exc: + self.get_ucadapter_mock_object('ucadapter', {'mode': 'cna', 'pending-mode': 'cna'}).apply() + assert exc.value.args[0]['changed'] + + def test_change_mode_from_fc_to_cna(self): + module_args = self.set_default_args() + module_args['mode'] = 'cna' + del module_args['type'] + set_module_args(module_args) + with pytest.raises(AnsibleExitJson) as exc: + self.get_ucadapter_mock_object('ucadapter').apply() + assert exc.value.args[0]['changed'] diff --git a/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_unix_group.py b/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_unix_group.py new file mode 100644 index 00000000..016e951b --- /dev/null +++ b/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_unix_group.py @@ -0,0 +1,289 @@ +# (c) 2018, NetApp, Inc +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +''' unit test template for ONTAP Ansible module ''' + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type +import json +import pytest + +from ansible_collections.netapp.ontap.tests.unit.compat import unittest +from ansible_collections.netapp.ontap.tests.unit.compat.mock import patch, Mock +from ansible.module_utils import basic +from ansible.module_utils._text import to_bytes +import ansible_collections.netapp.ontap.plugins.module_utils.netapp as netapp_utils + +from ansible_collections.netapp.ontap.plugins.modules.na_ontap_unix_group \ + import NetAppOntapUnixGroup as group_module # module under test + +if not netapp_utils.has_netapp_lib(): + pytestmark = pytest.mark.skip('skipping as missing required netapp_lib') + + +def set_module_args(args): + """prepare arguments so that they will be picked up during module creation""" + args = json.dumps({'ANSIBLE_MODULE_ARGS': args}) + basic._ANSIBLE_ARGS = to_bytes(args) # pylint: disable=protected-access + + +class AnsibleExitJson(Exception): + """Exception class to be raised by module.exit_json and caught by the test case""" + pass + + +class AnsibleFailJson(Exception): + """Exception class to be raised by module.fail_json and caught by the test case""" + pass + + +def exit_json(*args, **kwargs): # pylint: disable=unused-argument + """function to patch over exit_json; package return data into an exception""" + if 'changed' not in kwargs: + kwargs['changed'] = False + raise AnsibleExitJson(kwargs) + + +def fail_json(*args, **kwargs): # pylint: disable=unused-argument + """function to patch over fail_json; package return data into an exception""" + kwargs['failed'] = True + raise AnsibleFailJson(kwargs) + + +class MockONTAPConnection(object): + ''' mock server connection to ONTAP host ''' + + def __init__(self, kind=None, data=None): + ''' save arguments ''' + self.kind = kind + self.params = data + self.xml_in = None + self.xml_out = None + + def invoke_successfully(self, xml, enable_tunneling): # pylint: disable=unused-argument + ''' mock invoke_successfully returning xml data ''' + self.xml_in = xml + if self.kind == 'group': + xml = self.build_group_info(self.params) + elif self.kind == 'group-fail': + raise netapp_utils.zapi.NaApiError(code='TEST', message="This exception is from the unit test") + self.xml_out = xml + return xml + + @staticmethod + def build_group_info(data): + ''' build xml data for vserser-info ''' + xml = netapp_utils.zapi.NaElement('xml') + attributes = \ + {'attributes-list': {'unix-group-info': {'group-name': data['name'], + 'group-id': data['id']}}, + 'num-records': 1} + xml.translate_struct(attributes) + return xml + + +class TestMyModule(unittest.TestCase): + ''' a group of related Unit Tests ''' + + def setUp(self): + self.mock_module_helper = patch.multiple(basic.AnsibleModule, + exit_json=exit_json, + fail_json=fail_json) + self.mock_module_helper.start() + self.addCleanup(self.mock_module_helper.stop) + self.server = MockONTAPConnection() + self.mock_group = { + 'name': 'test', + 'id': '11', + 'vserver': 'something', + } + + def mock_args(self): + return { + 'name': self.mock_group['name'], + 'id': self.mock_group['id'], + 'vserver': self.mock_group['vserver'], + 'hostname': 'test', + 'username': 'test_user', + 'password': 'test_pass!' + } + + def get_group_mock_object(self, kind=None, data=None): + """ + Helper method to return an na_ontap_unix_group object + :param kind: passes this param to MockONTAPConnection() + :return: na_ontap_unix_group object + """ + obj = group_module() + obj.autosupport_log = Mock(return_value=None) + if data is None: + data = self.mock_group + obj.server = MockONTAPConnection(kind=kind, data=data) + 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({}) + group_module() + + def test_get_nonexistent_group(self): + ''' Test if get_unix_group returns None for non-existent group ''' + set_module_args(self.mock_args()) + result = self.get_group_mock_object().get_unix_group() + assert result is None + + def test_get_existing_group(self): + ''' Test if get_unix_group returns details for existing group ''' + set_module_args(self.mock_args()) + result = self.get_group_mock_object('group').get_unix_group() + assert result['name'] == self.mock_group['name'] + + def test_get_xml(self): + set_module_args(self.mock_args()) + obj = self.get_group_mock_object('group') + result = obj.get_unix_group() + assert obj.server.xml_in['query'] + assert obj.server.xml_in['query']['unix-group-info'] + group_info = obj.server.xml_in['query']['unix-group-info'] + assert group_info['group-name'] == self.mock_group['name'] + assert group_info['vserver'] == self.mock_group['vserver'] + + def test_create_error_missing_params(self): + data = self.mock_args() + del data['id'] + set_module_args(data) + with pytest.raises(AnsibleFailJson) as exc: + self.get_group_mock_object('group').create_unix_group() + assert 'Error: Missing a required parameter for create: (id)' == exc.value.args[0]['msg'] + + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_unix_group.NetAppOntapUnixGroup.create_unix_group') + def test_create_called(self, create_group): + set_module_args(self.mock_args()) + with pytest.raises(AnsibleExitJson) as exc: + self.get_group_mock_object().apply() + assert exc.value.args[0]['changed'] + create_group.assert_called_with() + + def test_create_xml(self): + '''Test create ZAPI element''' + set_module_args(self.mock_args()) + create = self.get_group_mock_object() + with pytest.raises(AnsibleExitJson) as exc: + create.apply() + mock_key = { + 'group-name': 'name', + 'group-id': 'id', + } + for key in ['group-name', 'group-id']: + assert create.server.xml_in[key] == self.mock_group[mock_key[key]] + + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_unix_group.NetAppOntapUnixGroup.modify_unix_group') + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_unix_group.NetAppOntapUnixGroup.delete_unix_group') + def test_delete_called(self, delete_group, modify_group): + ''' Test delete existing group ''' + data = self.mock_args() + data['state'] = 'absent' + set_module_args(data) + with pytest.raises(AnsibleExitJson) as exc: + self.get_group_mock_object('group').apply() + assert exc.value.args[0]['changed'] + delete_group.assert_called_with() + assert modify_group.call_count == 0 + + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_unix_group.NetAppOntapUnixGroup.get_unix_group') + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_unix_group.NetAppOntapUnixGroup.modify_unix_group') + def test_modify_called(self, modify_group, get_group): + ''' Test modify group group_id ''' + data = self.mock_args() + data['id'] = 20 + set_module_args(data) + get_group.return_value = {'id': 10} + obj = self.get_group_mock_object('group') + with pytest.raises(AnsibleExitJson) as exc: + obj.apply() + get_group.assert_called_with() + modify_group.assert_called_with({'id': 20}) + + def test_modify_only_id(self): + ''' Test modify group id ''' + set_module_args(self.mock_args()) + modify = self.get_group_mock_object('group') + modify.modify_unix_group({'id': 123}) + print(modify.server.xml_in.to_string()) + assert modify.server.xml_in['group-id'] == '123' + with pytest.raises(KeyError): + modify.server.xml_in['id'] + + def test_modify_xml(self): + ''' Test modify group full_name ''' + set_module_args(self.mock_args()) + modify = self.get_group_mock_object('group') + modify.modify_unix_group({'id': 25}) + assert modify.server.xml_in['group-name'] == self.mock_group['name'] + assert modify.server.xml_in['group-id'] == '25' + + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_unix_group.NetAppOntapUnixGroup.create_unix_group') + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_unix_group.NetAppOntapUnixGroup.delete_unix_group') + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_unix_group.NetAppOntapUnixGroup.modify_unix_group') + def test_do_nothing(self, modify, delete, create): + ''' changed is False and none of the opetaion methods are called''' + data = self.mock_args() + data['state'] = 'absent' + set_module_args(data) + obj = self.get_group_mock_object() + with pytest.raises(AnsibleExitJson) as exc: + obj.apply() + create.assert_not_called() + delete.assert_not_called() + modify.assert_not_called() + + def test_get_exception(self): + set_module_args(self.mock_args()) + with pytest.raises(AnsibleFailJson) as exc: + self.get_group_mock_object('group-fail').get_unix_group() + assert 'Error getting UNIX group' in exc.value.args[0]['msg'] + + def test_create_exception(self): + set_module_args(self.mock_args()) + with pytest.raises(AnsibleFailJson) as exc: + self.get_group_mock_object('group-fail').create_unix_group() + assert 'Error creating UNIX group' in exc.value.args[0]['msg'] + + def test_modify_exception(self): + set_module_args(self.mock_args()) + with pytest.raises(AnsibleFailJson) as exc: + self.get_group_mock_object('group-fail').modify_unix_group({'id': '123'}) + assert 'Error modifying UNIX group' in exc.value.args[0]['msg'] + + def test_delete_exception(self): + set_module_args(self.mock_args()) + with pytest.raises(AnsibleFailJson) as exc: + self.get_group_mock_object('group-fail').delete_unix_group() + assert 'Error removing UNIX group' in exc.value.args[0]['msg'] + + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_unix_group.NetAppOntapUnixGroup.get_unix_group') + def test_add_user_exception(self, get_unix_group): + data = self.mock_args() + data['users'] = 'test_user' + set_module_args(data) + get_unix_group.side_effect = [ + {'users': []} + ] + with pytest.raises(AnsibleFailJson) as exc: + self.get_group_mock_object('group-fail').modify_users_in_group() + print(exc.value.args[0]['msg']) + assert 'Error adding user' in exc.value.args[0]['msg'] + + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_unix_group.NetAppOntapUnixGroup.get_unix_group') + def test_delete_user_exception(self, get_unix_group): + data = self.mock_args() + data['users'] = '' + set_module_args(data) + get_unix_group.side_effect = [ + {'users': ['test_user']} + ] + with pytest.raises(AnsibleFailJson) as exc: + self.get_group_mock_object('group-fail').modify_users_in_group() + print(exc.value.args[0]['msg']) + assert 'Error deleting user' in exc.value.args[0]['msg'] diff --git a/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_unix_user.py b/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_unix_user.py new file mode 100644 index 00000000..3b9e5bce --- /dev/null +++ b/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_unix_user.py @@ -0,0 +1,283 @@ +# (c) 2018, NetApp, Inc +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +''' unit test template for ONTAP Ansible module ''' + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type +import json +import pytest + +from ansible_collections.netapp.ontap.tests.unit.compat import unittest +from ansible_collections.netapp.ontap.tests.unit.compat.mock import patch, Mock +from ansible.module_utils import basic +from ansible.module_utils._text import to_bytes +import ansible_collections.netapp.ontap.plugins.module_utils.netapp as netapp_utils + +from ansible_collections.netapp.ontap.plugins.modules.na_ontap_unix_user \ + import NetAppOntapUnixUser as user_module # module under test + +if not netapp_utils.has_netapp_lib(): + pytestmark = pytest.mark.skip('skipping as missing required netapp_lib') + + +def set_module_args(args): + """prepare arguments so that they will be picked up during module creation""" + args = json.dumps({'ANSIBLE_MODULE_ARGS': args}) + basic._ANSIBLE_ARGS = to_bytes(args) # pylint: disable=protected-access + + +class AnsibleExitJson(Exception): + """Exception class to be raised by module.exit_json and caught by the test case""" + pass + + +class AnsibleFailJson(Exception): + """Exception class to be raised by module.fail_json and caught by the test case""" + pass + + +def exit_json(*args, **kwargs): # pylint: disable=unused-argument + """function to patch over exit_json; package return data into an exception""" + if 'changed' not in kwargs: + kwargs['changed'] = False + raise AnsibleExitJson(kwargs) + + +def fail_json(*args, **kwargs): # pylint: disable=unused-argument + """function to patch over fail_json; package return data into an exception""" + kwargs['failed'] = True + raise AnsibleFailJson(kwargs) + + +class MockONTAPConnection(object): + ''' mock server connection to ONTAP host ''' + + def __init__(self, kind=None, data=None): + ''' save arguments ''' + self.kind = kind + self.params = data + self.xml_in = None + self.xml_out = None + + def invoke_successfully(self, xml, enable_tunneling): # pylint: disable=unused-argument + ''' mock invoke_successfully returning xml data ''' + self.xml_in = xml + if self.kind == 'user': + xml = self.build_user_info(self.params) + elif self.kind == 'user-fail': + raise netapp_utils.zapi.NaApiError(code='TEST', message="This exception is from the unit test") + self.xml_out = xml + return xml + + @staticmethod + def build_user_info(data): + ''' build xml data for vserser-info ''' + xml = netapp_utils.zapi.NaElement('xml') + attributes = \ + {'attributes-list': {'unix-user-info': {'user-id': data['id'], + 'group-id': data['group_id'], 'full-name': data['full_name']}}, + 'num-records': 1} + xml.translate_struct(attributes) + return xml + + +class TestMyModule(unittest.TestCase): + ''' a group of related Unit Tests ''' + + def setUp(self): + self.mock_module_helper = patch.multiple(basic.AnsibleModule, + exit_json=exit_json, + fail_json=fail_json) + self.mock_module_helper.start() + self.addCleanup(self.mock_module_helper.stop) + self.server = MockONTAPConnection() + self.mock_user = { + 'name': 'test', + 'id': '11', + 'group_id': '12', + 'vserver': 'something', + 'full_name': 'Test User' + } + + def mock_args(self): + return { + 'name': self.mock_user['name'], + 'group_id': self.mock_user['group_id'], + 'id': self.mock_user['id'], + 'vserver': self.mock_user['vserver'], + 'full_name': self.mock_user['full_name'], + 'hostname': 'test', + 'username': 'test_user', + 'password': 'test_pass!' + } + + def get_user_mock_object(self, kind=None, data=None): + """ + Helper method to return an na_ontap_unix_user object + :param kind: passes this param to MockONTAPConnection() + :return: na_ontap_unix_user object + """ + obj = user_module() + obj.autosupport_log = Mock(return_value=None) + if data is None: + data = self.mock_user + obj.server = MockONTAPConnection(kind=kind, data=data) + 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({}) + user_module() + + def test_get_nonexistent_user(self): + ''' Test if get_unix_user returns None for non-existent user ''' + set_module_args(self.mock_args()) + result = self.get_user_mock_object().get_unix_user() + assert result is None + + def test_get_existing_user(self): + ''' Test if get_unix_user returns details for existing user ''' + set_module_args(self.mock_args()) + result = self.get_user_mock_object('user').get_unix_user() + assert result['full_name'] == self.mock_user['full_name'] + + def test_get_xml(self): + set_module_args(self.mock_args()) + obj = self.get_user_mock_object('user') + result = obj.get_unix_user() + assert obj.server.xml_in['query'] + assert obj.server.xml_in['query']['unix-user-info'] + user_info = obj.server.xml_in['query']['unix-user-info'] + assert user_info['user-name'] == self.mock_user['name'] + assert user_info['vserver'] == self.mock_user['vserver'] + + def test_create_error_missing_params(self): + data = self.mock_args() + del data['group_id'] + set_module_args(data) + with pytest.raises(AnsibleFailJson) as exc: + self.get_user_mock_object('user').create_unix_user() + assert 'Error: Missing one or more required parameters for create: (group_id, id)' == exc.value.args[0]['msg'] + + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_unix_user.NetAppOntapUnixUser.create_unix_user') + def test_create_called(self, create_user): + set_module_args(self.mock_args()) + with pytest.raises(AnsibleExitJson) as exc: + self.get_user_mock_object().apply() + assert exc.value.args[0]['changed'] + create_user.assert_called_with() + + def test_create_xml(self): + '''Test create ZAPI element''' + set_module_args(self.mock_args()) + create = self.get_user_mock_object() + with pytest.raises(AnsibleExitJson) as exc: + create.apply() + mock_key = { + 'user-name': 'name', + 'group-id': 'group_id', + 'user-id': 'id', + 'full-name': 'full_name' + } + for key in ['user-name', 'user-id', 'group-id', 'full-name']: + assert create.server.xml_in[key] == self.mock_user[mock_key[key]] + + def test_create_wihtout_full_name(self): + '''Test create ZAPI element''' + data = self.mock_args() + del data['full_name'] + set_module_args(data) + create = self.get_user_mock_object() + with pytest.raises(AnsibleExitJson) as exc: + create.apply() + with pytest.raises(KeyError): + create.server.xml_in['full-name'] + + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_unix_user.NetAppOntapUnixUser.modify_unix_user') + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_unix_user.NetAppOntapUnixUser.delete_unix_user') + def test_delete_called(self, delete_user, modify_user): + ''' Test delete existing user ''' + data = self.mock_args() + data['state'] = 'absent' + set_module_args(data) + with pytest.raises(AnsibleExitJson) as exc: + self.get_user_mock_object('user').apply() + assert exc.value.args[0]['changed'] + delete_user.assert_called_with() + assert modify_user.call_count == 0 + + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_unix_user.NetAppOntapUnixUser.get_unix_user') + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_unix_user.NetAppOntapUnixUser.modify_unix_user') + def test_modify_called(self, modify_user, get_user): + ''' Test modify user group_id ''' + data = self.mock_args() + data['group_id'] = 20 + set_module_args(data) + get_user.return_value = {'group_id': 10} + obj = self.get_user_mock_object('user') + with pytest.raises(AnsibleExitJson) as exc: + obj.apply() + get_user.assert_called_with() + modify_user.assert_called_with({'group_id': 20}) + + def test_modify_only_id(self): + ''' Test modify user id ''' + set_module_args(self.mock_args()) + modify = self.get_user_mock_object('user') + modify.modify_unix_user({'id': 123}) + assert modify.server.xml_in['user-id'] == '123' + with pytest.raises(KeyError): + modify.server.xml_in['group-id'] + with pytest.raises(KeyError): + modify.server.xml_in['full-name'] + + def test_modify_xml(self): + ''' Test modify user full_name ''' + set_module_args(self.mock_args()) + modify = self.get_user_mock_object('user') + modify.modify_unix_user({'full_name': 'New Name', + 'group_id': '25'}) + assert modify.server.xml_in['user-name'] == self.mock_user['name'] + assert modify.server.xml_in['full-name'] == 'New Name' + assert modify.server.xml_in['group-id'] == '25' + + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_unix_user.NetAppOntapUnixUser.create_unix_user') + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_unix_user.NetAppOntapUnixUser.delete_unix_user') + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_unix_user.NetAppOntapUnixUser.modify_unix_user') + def test_do_nothing(self, modify, delete, create): + ''' changed is False and none of the opetaion methods are called''' + data = self.mock_args() + data['state'] = 'absent' + set_module_args(data) + obj = self.get_user_mock_object() + with pytest.raises(AnsibleExitJson) as exc: + obj.apply() + create.assert_not_called() + delete.assert_not_called() + modify.assert_not_called() + + def test_get_exception(self): + set_module_args(self.mock_args()) + with pytest.raises(AnsibleFailJson) as exc: + self.get_user_mock_object('user-fail').get_unix_user() + assert 'Error getting UNIX user' in exc.value.args[0]['msg'] + + def test_create_exception(self): + set_module_args(self.mock_args()) + with pytest.raises(AnsibleFailJson) as exc: + self.get_user_mock_object('user-fail').create_unix_user() + assert 'Error creating UNIX user' in exc.value.args[0]['msg'] + + def test_modify_exception(self): + set_module_args(self.mock_args()) + with pytest.raises(AnsibleFailJson) as exc: + self.get_user_mock_object('user-fail').modify_unix_user({'id': '123'}) + assert 'Error modifying UNIX user' in exc.value.args[0]['msg'] + + def test_delete_exception(self): + set_module_args(self.mock_args()) + with pytest.raises(AnsibleFailJson) as exc: + self.get_user_mock_object('user-fail').delete_unix_user() + assert 'Error removing UNIX user' in exc.value.args[0]['msg'] diff --git a/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_user.py b/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_user.py new file mode 100644 index 00000000..a43ac6f5 --- /dev/null +++ b/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_user.py @@ -0,0 +1,505 @@ +# (c) 2018, NetApp, Inc +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +''' unit tests ONTAP Ansible module: na_ontap_user ''' + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type +import json +import pytest + +from ansible.module_utils import basic +from ansible.module_utils._text import to_bytes +from ansible_collections.netapp.ontap.tests.unit.compat import unittest +from ansible_collections.netapp.ontap.tests.unit.compat.mock import patch +import ansible_collections.netapp.ontap.plugins.module_utils.netapp as netapp_utils + +from ansible_collections.netapp.ontap.plugins.modules.na_ontap_user \ + import NetAppOntapUser as my_module # module under test + +if not netapp_utils.has_netapp_lib(): + pytestmark = pytest.mark.skip('skipping as missing required netapp_lib') + +# REST API canned responses when mocking send_request +SRR = { + # common responses + 'is_rest': (200, {}, None), + 'is_zapi': (400, {}, "Unreachable"), + 'empty_good': (200, {}, None), + 'end_of_sequence': (500, None, "Ooops, the UT needs one more SRR response"), + 'generic_error': (400, None, "Expected error"), + 'get_uuid': (200, {'owner': {'uuid': 'ansible'}}, None), + 'get_user_rest': (200, + {'num_records': 1, + 'records': [{'owner': {'uuid': 'ansible_vserver'}, + 'name': 'abcd'}]}, None), + 'get_user_details_rest': (200, + {'role': {'name': 'vsadmin'}, + 'applications': [{'application': 'http'}], + 'locked': False}, None) +} + + +def set_module_args(args): + """prepare arguments so that they will be picked up during module creation""" + args = json.dumps({'ANSIBLE_MODULE_ARGS': args}) + basic._ANSIBLE_ARGS = to_bytes(args) # pylint: disable=protected-access + + +class AnsibleExitJson(Exception): + """Exception class to be raised by module.exit_json and caught by the test case""" + + +class AnsibleFailJson(Exception): + """Exception class to be raised by module.fail_json and caught by the test case""" + + +def exit_json(*args, **kwargs): # pylint: disable=unused-argument + """function to patch over exit_json; package return data into an exception""" + if 'changed' not in kwargs: + kwargs['changed'] = False + raise AnsibleExitJson(kwargs) + + +def fail_json(*args, **kwargs): # pylint: disable=unused-argument + """function to patch over fail_json; package return data into an exception""" + kwargs['failed'] = True + raise AnsibleFailJson(kwargs) + + +def set_default_args_rest(): + return dict({ + 'hostname': 'hostname', + 'username': 'username', + 'password': 'password', + 'name': 'user_name', + 'vserver': 'vserver', + 'applications': 'http', + 'authentication_method': 'password', + 'role_name': 'vsadmin', + 'lock_user': 'True', + }) + + +class MockONTAPConnection(object): + ''' mock server connection to ONTAP host ''' + + def __init__(self, kind=None, parm1=None, parm2=None): + ''' save arguments ''' + self.type = kind + self.parm1 = parm1 + self.parm2 = parm2 + self.xml_in = None + self.xml_out = None + + def invoke_successfully(self, xml, enable_tunneling): # pylint: disable=unused-argument + ''' mock invoke_successfully returning xml data ''' + self.xml_in = xml + if self.type == 'user': + xml = self.build_user_info(self.parm1, self.parm2) + elif self.type == 'user_fail': + raise netapp_utils.zapi.NaApiError(code='TEST', message="This exception is from the unit test") + self.xml_out = xml + return xml + + @staticmethod + def set_vserver(vserver): + '''mock set vserver''' + + @staticmethod + def build_user_info(locked, role_name): + ''' build xml data for user-info ''' + xml = netapp_utils.zapi.NaElement('xml') + data = {'num-records': 1, + 'attributes-list': {'security-login-account-info': {'is-locked': locked, 'role-name': role_name}}} + + xml.translate_struct(data) + print(xml.to_string()) + return xml + + +class TestMyModule(unittest.TestCase): + ''' a group of related Unit Tests ''' + + def setUp(self): + self.mock_module_helper = patch.multiple(basic.AnsibleModule, + exit_json=exit_json, + fail_json=fail_json) + self.mock_module_helper.start() + self.addCleanup(self.mock_module_helper.stop) + self.server = MockONTAPConnection() + self.onbox = False + + def set_default_args(self, rest=False): + if self.onbox: + hostname = '10.10.10.10' + username = 'username' + password = 'password' + user_name = 'test' + vserver = 'ansible_test' + application = 'console' + authentication_method = 'password' + else: + hostname = 'hostname' + username = 'username' + password = 'password' + user_name = 'name' + vserver = 'vserver' + application = 'console' + authentication_method = 'password' + if rest: + use_rest = 'auto' + else: + use_rest = 'never' + + return dict({ + 'hostname': hostname, + 'username': username, + 'password': password, + 'use_rest': use_rest, + 'name': user_name, + 'vserver': vserver, + 'applications': application, + 'authentication_method': authentication_method + }) + + def test_module_fail_when_required_args_missing(self): + ''' required arguments are reported as errors ''' + with pytest.raises(AnsibleFailJson) as exc: + set_module_args({}) + my_module() + print('Info: %s' % exc.value.args[0]['msg']) + + def test_ensure_user_get_called(self): + ''' a more interesting test ''' + module_args = {} + module_args.update(self.set_default_args()) + module_args.update({'role_name': 'test'}) + set_module_args(module_args) + my_obj = my_module() + my_obj.server = self.server + user_info = my_obj.get_user() + print('Info: test_user_get: %s' % repr(user_info)) + assert user_info is None + + def test_ensure_user_apply_called(self): + ''' creating user and checking idempotency ''' + module_args = {} + module_args.update(self.set_default_args()) + module_args.update({'name': 'create'}) + module_args.update({'role_name': 'test'}) + set_module_args(module_args) + my_obj = my_module() + if not self.onbox: + my_obj.server = self.server + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + print('Info: test_user_apply: %s' % repr(exc.value)) + assert exc.value.args[0]['changed'] + if not self.onbox: + my_obj.server = MockONTAPConnection('user', 'false') + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + print('Info: test_user_apply: %s' % repr(exc.value)) + assert exc.value.args[0]['changed'] + + def test_ensure_user_apply_for_delete_called(self): + ''' deleting user and checking idempotency ''' + module_args = {} + module_args.update(self.set_default_args()) + module_args.update({'name': 'create'}) + module_args.update({'role_name': 'test'}) + set_module_args(module_args) + my_obj = my_module() + if not self.onbox: + my_obj.server = MockONTAPConnection('user', 'false', 'test') + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + print('Info: test_user_apply: %s' % repr(exc.value)) + assert not exc.value.args[0]['changed'] + module_args.update({'state': 'absent'}) + set_module_args(module_args) + my_obj = my_module() + if not self.onbox: + my_obj.server = MockONTAPConnection('user', 'false', 'test') + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + print('Info: test_user_delete: %s' % repr(exc.value)) + assert exc.value.args[0]['changed'] + + def test_ensure_user_lock_called(self): + ''' changing user_lock to True and checking idempotency''' + module_args = {} + module_args.update(self.set_default_args()) + module_args.update({'name': 'create'}) + module_args.update({'role_name': 'test'}) + module_args.update({'lock_user': 'false'}) + set_module_args(module_args) + my_obj = my_module() + if not self.onbox: + my_obj.server = MockONTAPConnection('user', 'false', 'test') + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + print('Info: test_user_apply: %s' % repr(exc.value)) + assert not exc.value.args[0]['changed'] + module_args.update({'lock_user': 'true'}) + set_module_args(module_args) + my_obj = my_module() + if not self.onbox: + my_obj.server = MockONTAPConnection('user', 'false') + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + print('Info: test_user_lock: %s' % repr(exc.value)) + assert exc.value.args[0]['changed'] + + def test_ensure_user_unlock_called(self): + ''' changing user_lock to False and checking idempotency''' + module_args = {} + module_args.update(self.set_default_args()) + module_args.update({'name': 'create'}) + module_args.update({'role_name': 'test'}) + module_args.update({'lock_user': 'false'}) + set_module_args(module_args) + my_obj = my_module() + if not self.onbox: + my_obj.server = MockONTAPConnection('user', 'false', 'test') + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + print('Info: test_user_apply: %s' % repr(exc.value)) + assert not exc.value.args[0]['changed'] + module_args.update({'lock_user': 'false'}) + set_module_args(module_args) + my_obj = my_module() + if not self.onbox: + my_obj.server = MockONTAPConnection('user', 'true', 'test') + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + print('Info: test_user_unlock: %s' % repr(exc.value)) + assert exc.value.args[0]['changed'] + + def test_ensure_user_set_password_called(self): + ''' set password ''' + module_args = {} + module_args.update(self.set_default_args()) + module_args.update({'name': 'create'}) + module_args.update({'role_name': 'test'}) + module_args.update({'set_password': '123456'}) + set_module_args(module_args) + my_obj = my_module() + if not self.onbox: + my_obj.server = MockONTAPConnection('user', 'true') + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + print('Info: test_user_apply: %s' % repr(exc.value)) + assert exc.value.args[0]['changed'] + + def test_ensure_user_role_update_called(self): + ''' set password ''' + module_args = {} + module_args.update(self.set_default_args()) + module_args.update({'name': 'create'}) + module_args.update({'role_name': 'test123'}) + module_args.update({'set_password': '123456'}) + set_module_args(module_args) + my_obj = my_module() + if not self.onbox: + my_obj.server = MockONTAPConnection('user', 'true') + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + print('Info: test_user_apply: %s' % repr(exc.value)) + assert exc.value.args[0]['changed'] + + def test_ensure_user_role_update_additional_application_called(self): + ''' set password ''' + module_args = {} + module_args.update(self.set_default_args()) + module_args.update({'name': 'create'}) + module_args.update({'role_name': 'test123'}) + module_args.update({'application': 'http'}) + module_args.update({'set_password': '123456'}) + set_module_args(module_args) + my_obj = my_module() + if not self.onbox: + my_obj.server = MockONTAPConnection('user', 'true') + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + print('Info: test_user_apply: %s' % repr(exc.value)) + assert exc.value.args[0]['changed'] + + def test_if_all_methods_catch_exception(self): + data = self.set_default_args() + data.update({'role_name': 'test'}) + set_module_args(data) + my_obj = my_module() + if not self.onbox: + my_obj.server = MockONTAPConnection('user_fail') + with pytest.raises(AnsibleFailJson) as exc: + my_obj.get_user() + assert 'Error getting user ' in exc.value.args[0]['msg'] + with pytest.raises(AnsibleFailJson) as exc: + my_obj.create_user(data['applications']) + assert 'Error creating user ' in exc.value.args[0]['msg'] + with pytest.raises(AnsibleFailJson) as exc: + my_obj.lock_given_user() + assert 'Error locking user ' in exc.value.args[0]['msg'] + with pytest.raises(AnsibleFailJson) as exc: + my_obj.unlock_given_user() + assert 'Error unlocking user ' in exc.value.args[0]['msg'] + with pytest.raises(AnsibleFailJson) as exc: + my_obj.delete_user(data['applications']) + assert 'Error removing user ' in exc.value.args[0]['msg'] + with pytest.raises(AnsibleFailJson) as exc: + my_obj.change_password() + assert 'Error setting password for user ' in exc.value.args[0]['msg'] + with pytest.raises(AnsibleFailJson) as exc: + my_obj.modify_user(data['applications']) + assert 'Error modifying user ' in exc.value.args[0]['msg'] + + @patch('ansible_collections.netapp.ontap.plugins.module_utils.netapp.OntapRestAPI.send_request') + def test_rest_error_applications_snmp(self, mock_request): + data = self.set_default_args(rest=True) + data.update({'applications': 'snmp'}) + data.update({'name': 'create'}) + data.update({'role_name': 'test123'}) + data.update({'set_password': '123456'}) + set_module_args(data) + mock_request.side_effect = [ + SRR['is_rest'], + SRR['end_of_sequence'] + ] + with pytest.raises(AnsibleFailJson) as exc: + my_module() + assert exc.value.args[0]['msg'] == "Snmp as application is not supported in REST." + + +@patch('ansible.module_utils.basic.AnsibleModule.fail_json') +@patch('ansible_collections.netapp.ontap.plugins.module_utils.netapp.OntapRestAPI.send_request') +def test_ensure_user_get_rest_called(mock_request, mock_fail): + mock_fail.side_effect = fail_json + mock_request.side_effect = [ + SRR['is_rest'], + SRR['get_user_rest'], + SRR['end_of_sequence'] + ] + set_module_args(set_default_args_rest()) + my_obj = my_module() + assert my_obj.get_user_rest() is not None + + +@patch('ansible.module_utils.basic.AnsibleModule.exit_json') +@patch('ansible.module_utils.basic.AnsibleModule.fail_json') +@patch('ansible_collections.netapp.ontap.plugins.module_utils.netapp.OntapRestAPI.send_request') +def test_ensure_create_user_rest_called(mock_request, mock_fail, mock_exit): + mock_fail.side_effect = fail_json + mock_exit.side_effect = exit_json + mock_request.side_effect = [ + SRR['is_rest'], + SRR['get_user_rest'], + SRR['get_user_details_rest'], + SRR['get_user_rest'], + ] + set_module_args(set_default_args_rest()) + my_obj = my_module() + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + assert exc.value.args[0]['changed'] + + +@patch('ansible.module_utils.basic.AnsibleModule.exit_json') +@patch('ansible.module_utils.basic.AnsibleModule.fail_json') +@patch('ansible_collections.netapp.ontap.plugins.module_utils.netapp.OntapRestAPI.send_request') +def test_ensure_delete_user_rest_called(mock_request, mock_fail, mock_exit): + mock_fail.side_effect = fail_json + mock_exit.side_effect = exit_json + mock_request.side_effect = [ + SRR['is_rest'], + SRR['get_user_rest'], + SRR['get_user_details_rest'], + SRR['get_user_rest'], + SRR['empty_good'], + SRR['end_of_sequence'] + ] + data = { + 'state': 'absent', + } + data.update(set_default_args_rest()) + set_module_args(data) + my_obj = my_module() + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + assert exc.value.args[0]['changed'] + + +@patch('ansible.module_utils.basic.AnsibleModule.exit_json') +@patch('ansible.module_utils.basic.AnsibleModule.fail_json') +@patch('ansible_collections.netapp.ontap.plugins.module_utils.netapp.OntapRestAPI.send_request') +def test_ensure_modify_user_rest_called(mock_request, mock_fail, mock_exit): + mock_fail.side_effect = fail_json + mock_exit.side_effect = exit_json + mock_request.side_effect = [ + SRR['is_rest'], + SRR['get_user_rest'], + SRR['get_user_details_rest'], + SRR['get_user_rest'], + SRR['empty_good'], + SRR['end_of_sequence'] + ] + data = { + 'application': 'ssh', + } + data.update(set_default_args_rest()) + set_module_args(data) + my_obj = my_module() + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + assert exc.value.args[0]['changed'] + + +@patch('ansible.module_utils.basic.AnsibleModule.exit_json') +@patch('ansible.module_utils.basic.AnsibleModule.fail_json') +@patch('ansible_collections.netapp.ontap.plugins.module_utils.netapp.OntapRestAPI.send_request') +def test_ensure_lock_unlock_user_rest_called(mock_request, mock_fail, mock_exit): + mock_fail.side_effect = fail_json + mock_exit.side_effect = exit_json + mock_request.side_effect = [ + SRR['is_rest'], + SRR['get_user_rest'], + SRR['get_user_details_rest'], + SRR['get_user_rest'], + SRR['empty_good'], + SRR['end_of_sequence'] + ] + data = { + 'lock_user': 'newvalue', + } + data.update(set_default_args_rest()) + set_module_args(data) + my_obj = my_module() + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + assert exc.value.args[0]['changed'] + + +@patch('ansible.module_utils.basic.AnsibleModule.exit_json') +@patch('ansible.module_utils.basic.AnsibleModule.fail_json') +@patch('ansible_collections.netapp.ontap.plugins.module_utils.netapp.OntapRestAPI.send_request') +def test_ensure_change_password_user_rest_called(mock_request, mock_fail, mock_exit): + mock_fail.side_effect = fail_json + mock_exit.side_effect = exit_json + mock_request.side_effect = [ + SRR['is_rest'], + SRR['get_user_rest'], + SRR['get_user_details_rest'], + SRR['get_user_rest'], + SRR['empty_good'], + SRR['end_of_sequence'] + ] + data = { + 'password': 'newvalue', + } + data.update(set_default_args_rest()) + set_module_args(data) + my_obj = my_module() + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + assert exc.value.args[0]['changed'] diff --git a/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_user_role.py b/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_user_role.py new file mode 100644 index 00000000..f8b3ce82 --- /dev/null +++ b/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_user_role.py @@ -0,0 +1,239 @@ +# (c) 2018, NetApp, Inc +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +''' unit test template for ONTAP Ansible module ''' + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type +import json +import pytest + +from ansible_collections.netapp.ontap.tests.unit.compat import unittest +from ansible_collections.netapp.ontap.tests.unit.compat.mock import patch, Mock +from ansible.module_utils import basic +from ansible.module_utils._text import to_bytes +import ansible_collections.netapp.ontap.plugins.module_utils.netapp as netapp_utils + +from ansible_collections.netapp.ontap.plugins.modules.na_ontap_user_role \ + import NetAppOntapUserRole as role_module # module under test + +if not netapp_utils.has_netapp_lib(): + pytestmark = pytest.mark.skip('skipping as missing required netapp_lib') + + +def set_module_args(args): + """prepare arguments so that they will be picked up during module creation""" + args = json.dumps({'ANSIBLE_MODULE_ARGS': args}) + basic._ANSIBLE_ARGS = to_bytes(args) # pylint: disable=protected-access + + +class AnsibleExitJson(Exception): + """Exception class to be raised by module.exit_json and caught by the test case""" + pass + + +class AnsibleFailJson(Exception): + """Exception class to be raised by module.fail_json and caught by the test case""" + pass + + +def exit_json(*args, **kwargs): # pylint: disable=unused-argument + """function to patch over exit_json; package return data into an exception""" + if 'changed' not in kwargs: + kwargs['changed'] = False + raise AnsibleExitJson(kwargs) + + +def fail_json(*args, **kwargs): # pylint: disable=unused-argument + """function to patch over fail_json; package return data into an exception""" + kwargs['failed'] = True + raise AnsibleFailJson(kwargs) + + +class MockONTAPConnection(object): + ''' mock server connection to ONTAP host ''' + + def __init__(self, kind=None, data=None): + ''' save arguments ''' + self.kind = kind + self.params = data + self.xml_in = None + self.xml_out = None + + def invoke_successfully(self, xml, enable_tunneling): # pylint: disable=unused-argument + ''' mock invoke_successfully returning xml data ''' + self.xml_in = xml + if self.kind == 'role': + xml = self.build_role_info(self.params) + if self.kind == 'error': + error = netapp_utils.zapi.NaApiError('test', 'error') + raise error + self.xml_out = xml + return xml + + @staticmethod + def build_role_info(vol_details): + ''' build xml data for role-attributes ''' + xml = netapp_utils.zapi.NaElement('xml') + attributes = { + 'num-records': 1, + 'attributes-list': { + 'security-login-role-info': { + 'access-level': 'all', + 'command-directory-name': 'volume', + 'role-name': 'testrole', + 'role-query': 'show', + 'vserver': 'ansible' + } + } + } + xml.translate_struct(attributes) + return xml + + +class TestMyModule(unittest.TestCase): + ''' a group of related Unit Tests ''' + + def setUp(self): + self.mock_module_helper = patch.multiple(basic.AnsibleModule, + exit_json=exit_json, + fail_json=fail_json) + self.mock_module_helper.start() + self.addCleanup(self.mock_module_helper.stop) + self.mock_role = { + 'name': 'testrole', + 'access_level': 'all', + 'command_directory_name': 'volume', + 'vserver': 'ansible' + } + + def mock_args(self): + return { + 'name': self.mock_role['name'], + 'vserver': self.mock_role['vserver'], + 'command_directory_name': self.mock_role['command_directory_name'], + 'hostname': 'test', + 'username': 'test_user', + 'password': 'test_pass!', + 'https': 'False' + } + + def get_role_mock_object(self, kind=None): + """ + Helper method to return an na_ontap_user_role object + :param kind: passes this param to MockONTAPConnection() + :return: na_ontap_user_role object + """ + role_obj = role_module() + role_obj.asup_log_for_cserver = Mock(return_value=None) + role_obj.cluster = Mock() + role_obj.cluster.invoke_successfully = Mock() + if kind is None: + role_obj.server = MockONTAPConnection() + else: + role_obj.server = MockONTAPConnection(kind=kind, data=self.mock_role) + return role_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({}) + role_module() + print('Info: %s' % exc.value.args[0]['msg']) + + def test_get_nonexistent_policy(self): + ''' Test if get_role returns None for non-existent role ''' + set_module_args(self.mock_args()) + result = self.get_role_mock_object().get_role() + assert result is None + + def test_get_existing_role(self): + ''' Test if get_role returns details for existing role ''' + set_module_args(self.mock_args()) + result = self.get_role_mock_object('role').get_role() + assert result['name'] == self.mock_role['name'] + + def test_successful_create(self): + ''' Test successful create ''' + data = self.mock_args() + set_module_args(data) + with pytest.raises(AnsibleExitJson) as exc: + self.get_role_mock_object().apply() + assert exc.value.args[0]['changed'] + + def test_create_idempotency(self): + ''' Test create idempotency ''' + data = self.mock_args() + data['query'] = 'show' + set_module_args(data) + obj = self.get_role_mock_object('role') + with pytest.raises(AnsibleExitJson) as exc: + obj.apply() + assert not exc.value.args[0]['changed'] + + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_user_role.NetAppOntapUserRole.get_role') + def test_create_error(self, get_role): + ''' Test create error ''' + set_module_args(self.mock_args()) + get_role.side_effect = [ + None + ] + with pytest.raises(AnsibleFailJson) as exc: + self.get_role_mock_object('error').apply() + assert exc.value.args[0]['msg'] == 'Error creating role testrole: NetApp API failed. Reason - test:error' + + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_user_role.NetAppOntapUserRole.get_role') + def test_successful_modify(self, get_role): + ''' Test successful modify ''' + data = self.mock_args() + data['query'] = 'show' + set_module_args(data) + current = self.mock_role + current['query'] = 'show-space' + get_role.side_effect = [ + current + ] + obj = self.get_role_mock_object() + with pytest.raises(AnsibleExitJson) as exc: + obj.apply() + assert exc.value.args[0]['changed'] + + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_user_role.NetAppOntapUserRole.get_role') + def test_modify_idempotency(self, get_role): + ''' Test modify idempotency ''' + data = self.mock_args() + data['query'] = 'show' + set_module_args(data) + current = self.mock_role + current['query'] = 'show' + get_role.side_effect = [ + current + ] + obj = self.get_role_mock_object() + with pytest.raises(AnsibleExitJson) as exc: + obj.apply() + assert not exc.value.args[0]['changed'] + + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_user_role.NetAppOntapUserRole.get_role') + def test_modify_error(self, get_role): + ''' Test modify error ''' + data = self.mock_args() + data['query'] = 'show' + set_module_args(data) + current = self.mock_role + current['query'] = 'show-space' + get_role.side_effect = [ + current + ] + with pytest.raises(AnsibleFailJson) as exc: + self.get_role_mock_object('error').apply() + assert exc.value.args[0]['msg'] == 'Error modifying role testrole: NetApp API failed. Reason - test:error' + + def test_successful_delete(self): + ''' Test delete existing role ''' + data = self.mock_args() + data['state'] = 'absent' + set_module_args(data) + with pytest.raises(AnsibleExitJson) as exc: + self.get_role_mock_object('role').apply() + assert exc.value.args[0]['changed'] diff --git a/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_volume.py b/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_volume.py new file mode 100644 index 00000000..48c34856 --- /dev/null +++ b/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_volume.py @@ -0,0 +1,1183 @@ +# (c) 2018, NetApp, Inc +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +''' unit test template for ONTAP Ansible module ''' + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type +import json +import pytest + +from ansible.module_utils import basic +from ansible.module_utils._text import to_bytes +from ansible_collections.netapp.ontap.tests.unit.compat import unittest +from ansible_collections.netapp.ontap.tests.unit.compat.mock import patch, Mock +import ansible_collections.netapp.ontap.plugins.module_utils.netapp as netapp_utils + +from ansible_collections.netapp.ontap.plugins.modules.na_ontap_volume \ + import NetAppOntapVolume as vol_module # module under test + +if not netapp_utils.has_netapp_lib(): + pytestmark = pytest.mark.skip('skipping as missing required netapp_lib') + + +def set_module_args(args): + """prepare arguments so that they will be picked up during module creation""" + args = json.dumps({'ANSIBLE_MODULE_ARGS': args}) + basic._ANSIBLE_ARGS = to_bytes(args) # pylint: disable=protected-access + + +class AnsibleExitJson(Exception): + """Exception class to be raised by module.exit_json and caught by the test case""" + + +class AnsibleFailJson(Exception): + """Exception class to be raised by module.fail_json and caught by the test case""" + + +def exit_json(*args, **kwargs): # pylint: disable=unused-argument + """function to patch over exit_json; package return data into an exception""" + if 'changed' not in kwargs: + kwargs['changed'] = False + raise AnsibleExitJson(kwargs) + + +def fail_json(*args, **kwargs): # pylint: disable=unused-argument + """function to patch over fail_json; package return data into an exception""" + kwargs['failed'] = True + raise AnsibleFailJson(kwargs) + + +class MockONTAPConnection(object): + ''' mock server connection to ONTAP host ''' + + def __init__(self, kind=None, data=None, job_error=None): + ''' save arguments ''' + self.kind = kind + self.params = data + self.xml_in = None + self.xml_out = None + self.job_error = job_error + + def invoke_successfully(self, xml, enable_tunneling): # pylint: disable=unused-argument + ''' mock invoke_successfully returning xml data ''' + self.xml_in = xml + # print("request: ", xml.to_string()) + request = xml.to_string().decode('utf-8') + print(request) + if request.startswith('<sis-get-iter>'): + return self.build_sis_info() + if isinstance(self.kind, list): + kind = self.kind.pop(0) + if len(self.kind) == 0: + # loop over last element + self.kind = kind + else: + kind = self.kind + + if kind == 'volume': + xml = self.build_volume_info(self.params) + elif kind == 'job_info': + xml = self.build_job_info(self.job_error) + elif kind == 'error_modify': + xml = self.build_modify_error() + elif kind == 'failure_modify_async': + xml = self.build_failure_modify_async() + elif kind == 'missing_element_modify_async': + xml = self.build_missing_element_modify_async() + elif kind == 'success_modify_async': + xml = self.build_success_modify_async() + elif kind == 'zapi_error': + error = netapp_utils.zapi.NaApiError('test', 'error') + raise error + self.xml_out = xml + # print(xml.to_string()) + return xml + + @staticmethod + def build_volume_info(vol_details): + ''' build xml data for volume-attributes ''' + xml = netapp_utils.zapi.NaElement('xml') + attributes = { + 'num-records': 1, + 'attributes-list': { + 'volume-attributes': { + 'volume-id-attributes': { + 'containing-aggregate-name': vol_details['aggregate'], + 'junction-path': vol_details['junction_path'], + 'style-extended': 'flexvol' + }, + 'volume-language-attributes': { + 'language-code': 'en' + }, + 'volume-export-attributes': { + 'policy': 'default' + }, + 'volume-performance-attributes': { + 'is-atime-update-enabled': 'true' + }, + 'volume-state-attributes': { + 'state': "online", + 'is-nvfail-enabled': 'true' + }, + 'volume-space-attributes': { + 'space-guarantee': 'none', + 'size': vol_details['size'], + 'percentage-snapshot-reserve': vol_details['percent_snapshot_space'], + 'space-slo': 'thick' + }, + 'volume-snapshot-attributes': { + 'snapshot-policy': vol_details['snapshot_policy'] + }, + 'volume-comp-aggr-attributes': { + 'tiering-policy': 'snapshot-only' + }, + 'volume-security-attributes': { + 'style': 'unix', + 'volume-security-unix-attributes': { + 'permissions': vol_details['unix_permissions'], + 'group-id': vol_details['group_id'], + 'user-id': vol_details['user_id'] + } + }, + 'volume-vserver-dr-protection-attributes': { + 'vserver-dr-protection': vol_details['vserver_dr_protection'], + }, + 'volume-qos-attributes': { + 'policy-group-name': vol_details['qos_policy_group'], + 'adaptive-policy-group-name': vol_details['qos_adaptive_policy_group'] + }, + 'volume-snapshot-autodelete-attributes': { + 'commitment': 'try' + } + } + } + } + xml.translate_struct(attributes) + return xml + + @staticmethod + def build_flex_group_info(vol_details): + ''' build xml data for flexGroup volume-attributes ''' + xml = netapp_utils.zapi.NaElement('xml') + attributes = { + 'num-records': 1, + 'attributes-list': { + 'volume-attributes': { + 'volume-id-attributes': { + 'aggr-list': vol_details['aggregate'], + 'junction-path': vol_details['junction_path'], + 'style-extended': 'flexgroup' + }, + 'volume-language-attributes': { + 'language-code': 'en' + }, + 'volume-export-attributes': { + 'policy': 'default' + }, + 'volume-performance-attributes': { + 'is-atime-update-enabled': 'true' + }, + 'volume-state-attributes': { + 'state': "online" + }, + 'volume-space-attributes': { + 'space-guarantee': 'none', + 'size': vol_details['size'] + }, + 'volume-snapshot-attributes': { + 'snapshot-policy': vol_details['snapshot_policy'] + }, + 'volume-security-attributes': { + 'volume-security-unix-attributes': { + 'permissions': vol_details['unix_permissions'] + } + } + } + } + } + xml.translate_struct(attributes) + return xml + + @staticmethod + def build_job_info(error): + ''' build xml data for a job ''' + xml = netapp_utils.zapi.NaElement('xml') + attributes = netapp_utils.zapi.NaElement('attributes') + if error is None: + state = 'success' + elif error == 'time_out': + state = 'running' + elif error == 'failure': + state = 'failure' + else: + state = 'other' + attributes.add_node_with_children('job-info', **{ + 'job-state': state, + 'job-progress': 'dummy', + 'job-completion': error, + }) + xml.add_child_elem(attributes) + xml.add_new_child('result-status', 'in_progress') + xml.add_new_child('result-jobid', '1234') + return xml + + @staticmethod + def build_modify_error(): + ''' build xml data for modify error ''' + xml = netapp_utils.zapi.NaElement('xml') + attributes = netapp_utils.zapi.NaElement('failure-list') + info_list_obj = netapp_utils.zapi.NaElement('volume-modify-iter-info') + info_list_obj.add_new_child('error-message', 'modify error message') + attributes.add_child_elem(info_list_obj) + xml.add_child_elem(attributes) + return xml + + @staticmethod + def build_success_modify_async(): + ''' build xml data for success modify async ''' + xml = netapp_utils.zapi.NaElement('xml') + attributes = netapp_utils.zapi.NaElement('success-list') + info_list_obj = netapp_utils.zapi.NaElement('volume-modify-iter-async-info') + info_list_obj.add_new_child('status', 'in_progress') + info_list_obj.add_new_child('jobid', '1234') + attributes.add_child_elem(info_list_obj) + xml.add_child_elem(attributes) + return xml + + @staticmethod + def build_missing_element_modify_async(): + ''' build xml data for success modify async ''' + xml = netapp_utils.zapi.NaElement('xml') + attributes = netapp_utils.zapi.NaElement('success-list') + info_list_obj = netapp_utils.zapi.NaElement('volume-modify-iter-info') # no async! + info_list_obj.add_new_child('status', 'in_progress') + info_list_obj.add_new_child('jobid', '1234') + attributes.add_child_elem(info_list_obj) + xml.add_child_elem(attributes) + return xml + + @staticmethod + def build_failure_modify_async(): + ''' build xml data for failure modify async ''' + xml = netapp_utils.zapi.NaElement('xml') + attributes = netapp_utils.zapi.NaElement('failure-list') + info_list_obj = netapp_utils.zapi.NaElement('volume-modify-iter-async-info') + info_list_obj.add_new_child('status', 'failed') + info_list_obj.add_new_child('jobid', '1234') + info_list_obj.add_new_child('error-message', 'modify error message') + attributes.add_child_elem(info_list_obj) + xml.add_child_elem(attributes) + return xml + + @staticmethod + def build_sis_info(): + ''' build xml data for sis config ''' + xml = netapp_utils.zapi.NaElement('xml') + attributes = { + 'num-records': 1, + 'attributes-list': { + 'sis-status-info': { + 'policy': 'testme' + } + } + } + xml.translate_struct(attributes) + return xml + + +class TestMyModule(unittest.TestCase): + ''' a group of related Unit Tests ''' + + def setUp(self): + self.mock_module_helper = patch.multiple(basic.AnsibleModule, + exit_json=exit_json, + fail_json=fail_json) + self.mock_module_helper.start() + self.addCleanup(self.mock_module_helper.stop) + self.mock_vol = { + 'name': 'test_vol', + 'aggregate': 'test_aggr', + 'junction_path': '/test', + 'vserver': 'test_vserver', + 'size': 20971520, + 'unix_permissions': '755', + 'user_id': 100, + 'group_id': 1000, + 'snapshot_policy': 'default', + 'qos_policy_group': 'performance', + 'qos_adaptive_policy_group': 'performance', + 'percent_snapshot_space': 60, + 'language': 'en', + 'vserver_dr_protection': 'unprotected' + } + + def mock_args(self, tag=None): + args = { + 'hostname': 'test', + 'username': 'test_user', + 'password': 'test_pass!', + 'name': self.mock_vol['name'], + 'vserver': self.mock_vol['vserver'], + 'policy': 'default', + 'language': self.mock_vol['language'], + 'is_online': True, + 'unix_permissions': '---rwxr-xr-x', + 'user_id': 100, + 'group_id': 1000, + 'snapshot_policy': 'default', + 'qos_policy_group': 'performance', + 'qos_adaptive_policy_group': 'performance', + 'size': 20, + 'size_unit': 'mb', + 'junction_path': '/test', + 'percent_snapshot_space': 60, + 'type': 'type', + 'nvfail_enabled': True, + 'space_slo': 'thick' + } + if tag is None: + args['aggregate_name'] = self.mock_vol['aggregate'] + return args + + elif tag == 'flexGroup_manual': + args['aggr_list'] = 'aggr_0,aggr_1' + args['aggr_list_multiplier'] = 2 + return args + + elif tag == 'flexGroup_auto': + args['auto_provision_as'] = 'flexgroup' + return args + + def get_volume_mock_object(self, kind=None, job_error=None): + """ + Helper method to return an na_ontap_volume object + :param kind: passes this param to MockONTAPConnection(). + :param job_error: error message when getting job status. + :return: na_ontap_volume object + """ + vol_obj = vol_module() + vol_obj.ems_log_event = Mock(return_value=None) + vol_obj.get_efficiency_policy = Mock(return_value='test_efficiency') + vol_obj.volume_style = None + if kind is None: + vol_obj.server = MockONTAPConnection() + elif kind == 'job_info': + vol_obj.server = MockONTAPConnection(kind='job_info', data=self.mock_vol, job_error=job_error) + vol_obj.cluster = MockONTAPConnection(kind='job_info', data=self.mock_vol, job_error=job_error) + else: + vol_obj.server = MockONTAPConnection(kind=kind, data=self.mock_vol) + vol_obj.cluster = MockONTAPConnection(kind=kind, data=self.mock_vol) + + return vol_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({}) + vol_module() + print('Info: %s' % exc.value.args[0]['msg']) + + def test_get_nonexistent_volume(self): + ''' Test if get_volume returns None for non-existent volume ''' + set_module_args(self.mock_args()) + result = self.get_volume_mock_object().get_volume() + assert result is None + + def test_get_existing_volume(self): + ''' Test if get_volume returns details for existing volume ''' + set_module_args(self.mock_args()) + result = self.get_volume_mock_object('volume').get_volume() + assert result['name'] == self.mock_vol['name'] + assert result['size'] == self.mock_vol['size'] + + def test_create_error_missing_param(self): + ''' Test if create throws an error if aggregate_name is not specified''' + data = self.mock_args() + del data['aggregate_name'] + set_module_args(data) + with pytest.raises(AnsibleFailJson) as exc: + self.get_volume_mock_object('volume').create_volume() + msg = 'Error provisioning volume test_vol: aggregate_name is required' + assert exc.value.args[0]['msg'] == msg + + def test_successful_create(self): + ''' Test successful create ''' + data = self.mock_args() + data['size'] = 20 + data['encrypt'] = True + set_module_args(data) + with pytest.raises(AnsibleExitJson) as exc: + self.get_volume_mock_object().apply() + assert exc.value.args[0]['changed'] + + def test_create_idempotency(self): + ''' Test create idempotency ''' + set_module_args(self.mock_args()) + obj = self.get_volume_mock_object('volume') + with pytest.raises(AnsibleExitJson) as exc: + obj.apply() + assert not exc.value.args[0]['changed'] + + def test_successful_delete(self): + ''' Test delete existing volume ''' + data = self.mock_args() + data['state'] = 'absent' + set_module_args(data) + with pytest.raises(AnsibleExitJson) as exc: + self.get_volume_mock_object('volume').apply() + assert exc.value.args[0]['changed'] + + def test_delete_idempotency(self): + ''' Test delete idempotency ''' + data = self.mock_args() + data['state'] = 'absent' + set_module_args(data) + with pytest.raises(AnsibleExitJson) as exc: + self.get_volume_mock_object().apply() + assert not exc.value.args[0]['changed'] + + def test_successful_modify_size(self): + ''' Test successful modify size ''' + data = self.mock_args() + data['size'] = 200 + set_module_args(data) + with pytest.raises(AnsibleExitJson) as exc: + self.get_volume_mock_object('volume').apply() + assert exc.value.args[0]['changed'] + + def test_modify_idempotency(self): + ''' Test modify idempotency ''' + data = self.mock_args() + set_module_args(data) + with pytest.raises(AnsibleExitJson) as exc: + self.get_volume_mock_object('volume').apply() + assert not exc.value.args[0]['changed'] + + def test_modify_error(self): + ''' Test modify idempotency ''' + data = self.mock_args() + set_module_args(data) + with pytest.raises(AnsibleFailJson) as exc: + self.get_volume_mock_object('error_modify').volume_modify_attributes(dict()) + assert exc.value.args[0]['msg'] == 'Error modifying volume test_vol: modify error message' + + def test_mount_volume(self): + ''' Test mount volume ''' + data = self.mock_args() + data['junction_path'] = "/test123" + set_module_args(data) + with pytest.raises(AnsibleExitJson) as exc: + self.get_volume_mock_object('volume').apply() + assert exc.value.args[0]['changed'] + + def test_unmount_volume(self): + ''' Test unmount volume ''' + data = self.mock_args() + data['junction_path'] = "" + set_module_args(data) + with pytest.raises(AnsibleExitJson) as exc: + self.get_volume_mock_object('volume').apply() + assert exc.value.args[0]['changed'] + + def test_successful_modify_space(self): + ''' Test successful modify space ''' + data = self.mock_args() + del data['space_slo'] + data['space_guarantee'] = 'volume' + set_module_args(data) + with pytest.raises(AnsibleExitJson) as exc: + self.get_volume_mock_object('volume').apply() + assert exc.value.args[0]['changed'] + + def test_successful_modify_unix_permissions(self): + ''' Test successful modify unix_permissions ''' + data = self.mock_args() + data['unix_permissions'] = '---rw-r-xr-x' + set_module_args(data) + with pytest.raises(AnsibleExitJson) as exc: + self.get_volume_mock_object('volume').apply() + assert exc.value.args[0]['changed'] + + def test_successful_modify_snapshot_policy(self): + ''' Test successful modify snapshot_policy ''' + data = self.mock_args() + data['snapshot_policy'] = 'default-1weekly' + set_module_args(data) + with pytest.raises(AnsibleExitJson) as exc: + self.get_volume_mock_object('volume').apply() + assert exc.value.args[0]['changed'] + + def test_successful_modify_efficiency_policy(self): + ''' Test successful modify efficiency_policy ''' + data = self.mock_args() + data['efficiency_policy'] = 'test' + set_module_args(data) + with pytest.raises(AnsibleExitJson) as exc: + self.get_volume_mock_object('volume').apply() + assert exc.value.args[0]['changed'] + + def test_successful_modify_percent_snapshot_space(self): + ''' Test successful modify percent_snapshot_space ''' + data = self.mock_args() + data['percent_snapshot_space'] = '90' + set_module_args(data) + with pytest.raises(AnsibleExitJson) as exc: + self.get_volume_mock_object('volume').apply() + assert exc.value.args[0]['changed'] + + def test_successful_modify_qos_policy_group(self): + ''' Test successful modify qos_policy_group ''' + data = self.mock_args() + data['qos_policy_group'] = 'extreme' + set_module_args(data) + with pytest.raises(AnsibleExitJson) as exc: + self.get_volume_mock_object('volume').apply() + assert exc.value.args[0]['changed'] + + def test_successful_modify_qos_adaptive_policy_group(self): + ''' Test successful modify qos_adaptive_policy_group ''' + data = self.mock_args() + data['qos_adaptive_policy_group'] = 'extreme' + set_module_args(data) + with pytest.raises(AnsibleExitJson) as exc: + self.get_volume_mock_object('volume').apply() + assert exc.value.args[0]['changed'] + + def test_successful_move(self): + ''' Test successful modify aggregate ''' + data = self.mock_args() + data['aggregate_name'] = 'different_aggr' + set_module_args(data) + with pytest.raises(AnsibleExitJson) as exc: + self.get_volume_mock_object('volume').apply() + assert exc.value.args[0]['changed'] + + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_volume.NetAppOntapVolume.get_volume') + def test_successful_rename(self, get_volume): + ''' Test successful rename volume ''' + data = self.mock_args() + data['from_name'] = self.mock_vol['name'] + data['name'] = 'new_name' + set_module_args(data) + current = { + 'hostname': 'test', + 'username': 'test_user', + 'password': 'test_pass!', + 'name': self.mock_vol['name'], + 'vserver': self.mock_vol['vserver'], + } + get_volume.side_effect = [ + None, + current + ] + with pytest.raises(AnsibleExitJson) as exc: + self.get_volume_mock_object().apply() + assert exc.value.args[0]['changed'] + + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_volume.NetAppOntapVolume.get_volume') + def test_successful_rename_async(self, get_volume): + ''' Test successful rename volume ''' + data = self.mock_args() + data['from_name'] = self.mock_vol['name'] + data['name'] = 'new_name' + set_module_args(data) + current = { + 'hostname': 'test', + 'username': 'test_user', + 'password': 'test_pass!', + 'name': self.mock_vol['name'], + 'vserver': self.mock_vol['vserver'], + 'is_infinite': True + } + get_volume.side_effect = [ + None, + current + ] + obj = self.get_volume_mock_object('job_info') + with pytest.raises(AnsibleExitJson) as exc: + obj.apply() + assert exc.value.args[0]['changed'] + + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_volume.NetAppOntapVolume.change_volume_state') + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_volume.NetAppOntapVolume.volume_mount') + def test_modify_helper(self, mount_volume, change_state): + data = self.mock_args() + set_module_args(data) + modify = { + 'is_online': False, + 'junction_path': 'something' + } + obj = self.get_volume_mock_object('volume') + obj.modify_volume(modify) + change_state.assert_called_with() + mount_volume.assert_called_with() + + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_volume.NetAppOntapVolume.get_volume') + def test_compare_chmod_value_true_1(self, get_volume): + data = self.mock_args() + data['unix_permissions'] = '------------' + set_module_args(data) + current = { + 'unix_permissions': '0' + } + get_volume.side_effect = [ + current + ] + obj = self.get_volume_mock_object() + assert obj.compare_chmod_value(current) + + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_volume.NetAppOntapVolume.get_volume') + def test_compare_chmod_value_true_2(self, get_volume): + data = self.mock_args() + data['unix_permissions'] = '---rwxrwxrwx' + set_module_args(data) + current = { + 'unix_permissions': '777' + } + get_volume.side_effect = [ + current + ] + obj = self.get_volume_mock_object() + assert obj.compare_chmod_value(current) + + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_volume.NetAppOntapVolume.get_volume') + def test_compare_chmod_value_true_3(self, get_volume): + data = self.mock_args() + data['unix_permissions'] = '---rwxr-xr-x' + set_module_args(data) + current = { + 'unix_permissions': '755' + } + get_volume.side_effect = [ + current + ] + obj = self.get_volume_mock_object() + assert obj.compare_chmod_value(current) + + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_volume.NetAppOntapVolume.get_volume') + def test_compare_chmod_value_true_4(self, get_volume): + data = self.mock_args() + data['unix_permissions'] = '755' + set_module_args(data) + current = { + 'unix_permissions': '755' + } + get_volume.side_effect = [ + current + ] + obj = self.get_volume_mock_object() + assert obj.compare_chmod_value(current) + + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_volume.NetAppOntapVolume.get_volume') + def test_compare_chmod_value_false_1(self, get_volume): + data = self.mock_args() + data['unix_permissions'] = '---rwxrwxrwx' + set_module_args(data) + current = { + 'unix_permissions': '0' + } + get_volume.side_effect = [ + current + ] + obj = self.get_volume_mock_object() + assert not obj.compare_chmod_value(current) + + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_volume.NetAppOntapVolume.get_volume') + def test_compare_chmod_value_false_2(self, get_volume): + data = self.mock_args() + data['unix_permissions'] = '---rwxrwxrwx' + set_module_args(data) + current = None + get_volume.side_effect = [ + current + ] + obj = self.get_volume_mock_object() + assert not obj.compare_chmod_value(current) + + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_volume.NetAppOntapVolume.get_volume') + def test_compare_chmod_value_invalid_input_1(self, get_volume): + data = self.mock_args() + data['unix_permissions'] = '---xwrxwrxwr' + set_module_args(data) + current = { + 'unix_permissions': '777' + } + get_volume.side_effect = [ + current + ] + obj = self.get_volume_mock_object() + assert not obj.compare_chmod_value(current) + + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_volume.NetAppOntapVolume.get_volume') + def test_compare_chmod_value_invalid_input_2(self, get_volume): + data = self.mock_args() + data['unix_permissions'] = '---rwx-wx--a' + set_module_args(data) + current = { + 'unix_permissions': '0' + } + get_volume.side_effect = [ + current + ] + obj = self.get_volume_mock_object() + assert not obj.compare_chmod_value(current) + + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_volume.NetAppOntapVolume.get_volume') + def test_compare_chmod_value_invalid_input_3(self, get_volume): + data = self.mock_args() + data['unix_permissions'] = '---' + set_module_args(data) + current = { + 'unix_permissions': '0' + } + get_volume.side_effect = [ + current + ] + obj = self.get_volume_mock_object() + assert not obj.compare_chmod_value(current) + + def test_successful_create_flex_group_manually(self): + ''' Test successful create flexGroup manually ''' + data = self.mock_args('flexGroup_manual') + data['time_out'] = 20 + set_module_args(data) + obj = self.get_volume_mock_object('job_info') + with pytest.raises(AnsibleExitJson) as exc: + obj.apply() + assert exc.value.args[0]['changed'] + + def test_successful_create_flex_group_auto_provision(self): + ''' Test successful create flexGroup auto provision ''' + data = self.mock_args('flexGroup_auto') + data['time_out'] = 20 + set_module_args(data) + obj = self.get_volume_mock_object('job_info') + with pytest.raises(AnsibleExitJson) as exc: + obj.apply() + assert exc.value.args[0]['changed'] + + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_volume.NetAppOntapVolume.get_volume') + def test_successful_delete_flex_group(self, get_volume): + ''' Test successful delete flexGroup ''' + data = self.mock_args('flexGroup_manual') + data['state'] = 'absent' + set_module_args(data) + current = { + 'hostname': 'test', + 'username': 'test_user', + 'password': 'test_pass!', + 'name': self.mock_vol['name'], + 'vserver': self.mock_vol['vserver'], + 'style_extended': 'flexgroup', + 'unix_permissions': '755', + 'is_online': True + } + get_volume.side_effect = [ + current + ] + obj = self.get_volume_mock_object('job_info') + with pytest.raises(AnsibleExitJson) as exc: + obj.apply() + assert exc.value.args[0]['changed'] + + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_volume.NetAppOntapVolume.get_volume') + def test_successful_resize_flex_group(self, get_volume): + ''' Test successful reszie flexGroup ''' + data = self.mock_args('flexGroup_manual') + data['size'] = 400 + data['size_unit'] = 'mb' + set_module_args(data) + current = { + 'hostname': 'test', + 'username': 'test_user', + 'password': 'test_pass!', + 'name': self.mock_vol['name'], + 'vserver': self.mock_vol['vserver'], + 'style_extended': 'flexgroup', + 'size': 20971520, + 'unix_permissions': '755', + 'uuid': '1234' + } + get_volume.side_effect = [ + current + ] + obj = self.get_volume_mock_object('job_info') + with pytest.raises(AnsibleExitJson) as exc: + obj.apply() + assert exc.value.args[0]['changed'] + + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_volume.NetAppOntapVolume.check_job_status') + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_volume.NetAppOntapVolume.get_volume') + def test_successful_modify_unix_permissions_flex_group(self, get_volume, check_job_status): + ''' Test successful modify unix permissions flexGroup ''' + data = self.mock_args('flexGroup_manual') + data['time_out'] = 20 + data['unix_permissions'] = '---rw-r-xr-x' + set_module_args(data) + current = { + 'hostname': 'test', + 'username': 'test_user', + 'password': 'test_pass!', + 'name': self.mock_vol['name'], + 'vserver': self.mock_vol['vserver'], + 'style_extended': 'flexgroup', + 'unix_permissions': '777', + 'uuid': '1234' + } + get_volume.side_effect = [ + current + ] + check_job_status.side_effect = [ + None + ] + obj = self.get_volume_mock_object('success_modify_async') + with pytest.raises(AnsibleExitJson) as exc: + obj.apply() + print(exc) + assert exc.value.args[0]['changed'] + + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_volume.NetAppOntapVolume.get_volume') + def test_successful_modify_unix_permissions_flex_group_0_time_out(self, get_volume): + ''' Test successful modify unix permissions flexGroup ''' + data = self.mock_args('flexGroup_manual') + data['time_out'] = 0 + data['unix_permissions'] = '---rw-r-xr-x' + set_module_args(data) + current = { + 'hostname': 'test', + 'username': 'test_user', + 'password': 'test_pass!', + 'name': self.mock_vol['name'], + 'vserver': self.mock_vol['vserver'], + 'style_extended': 'flexgroup', + 'unix_permissions': '777', + 'uuid': '1234' + } + get_volume.side_effect = [ + current + ] + obj = self.get_volume_mock_object('success_modify_async') + with pytest.raises(AnsibleExitJson) as exc: + obj.apply() + assert exc.value.args[0]['changed'] + + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_volume.NetAppOntapVolume.get_volume') + def test_successful_modify_unix_permissions_flex_group_0_missing_result(self, get_volume): + ''' Test successful modify unix permissions flexGroup ''' + data = self.mock_args('flexGroup_manual') + data['time_out'] = 0 + data['unix_permissions'] = '---rw-r-xr-x' + set_module_args(data) + current = { + 'hostname': 'test', + 'username': 'test_user', + 'password': 'test_pass!', + 'name': self.mock_vol['name'], + 'vserver': self.mock_vol['vserver'], + 'style_extended': 'flexgroup', + 'unix_permissions': '777', + 'uuid': '1234' + } + get_volume.side_effect = [ + current + ] + obj = self.get_volume_mock_object('missing_element_modify_async') + with pytest.raises(AnsibleFailJson) as exc: + obj.apply() + msg = "Unexpected error when modifying volume: result is:" + assert exc.value.args[0]['msg'].startswith(msg) + + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_volume.NetAppOntapVolume.check_job_status') + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_volume.NetAppOntapVolume.get_volume') + def test_error_modify_unix_permissions_flex_group(self, get_volume, check_job_status): + ''' Test error modify unix permissions flexGroup ''' + data = self.mock_args('flexGroup_manual') + data['time_out'] = 20 + data['unix_permissions'] = '---rw-r-xr-x' + set_module_args(data) + current = { + 'hostname': 'test', + 'username': 'test_user', + 'password': 'test_pass!', + 'name': self.mock_vol['name'], + 'vserver': self.mock_vol['vserver'], + 'style_extended': 'flexgroup', + 'unix_permissions': '777', + 'uuid': '1234' + } + get_volume.side_effect = [ + current + ] + check_job_status.side_effect = ['error'] + obj = self.get_volume_mock_object('success_modify_async') + with pytest.raises(AnsibleFailJson) as exc: + obj.apply() + assert exc.value.args[0]['msg'] == 'Error when modify volume: error' + + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_volume.NetAppOntapVolume.get_volume') + def test_failure_modify_unix_permissions_flex_group(self, get_volume): + ''' Test failure modify unix permissions flexGroup ''' + data = self.mock_args('flexGroup_manual') + data['unix_permissions'] = '---rw-r-xr-x' + data['time_out'] = 20 + set_module_args(data) + current = { + 'hostname': 'test', + 'username': 'test_user', + 'password': 'test_pass!', + 'name': self.mock_vol['name'], + 'vserver': self.mock_vol['vserver'], + 'style_extended': 'flexvol', + 'unix_permissions': '777', + 'uuid': '1234' + } + get_volume.side_effect = [ + current + ] + obj = self.get_volume_mock_object('failure_modify_async') + with pytest.raises(AnsibleFailJson) as exc: + obj.apply() + assert exc.value.args[0]['msg'] == 'Error modifying volume test_vol: modify error message' + + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_volume.NetAppOntapVolume.get_volume') + def test_successful_offline_state_flex_group(self, get_volume): + ''' Test successful offline flexGroup state ''' + data = self.mock_args('flexGroup_manual') + data['is_online'] = False + set_module_args(data) + current = { + 'hostname': 'test', + 'username': 'test_user', + 'password': 'test_pass!', + 'name': self.mock_vol['name'], + 'vserver': self.mock_vol['vserver'], + 'style_extended': 'flexgroup', + 'is_online': True, + 'junction_path': 'anything', + 'unix_permissions': '755', + 'uuid': '1234' + } + get_volume.side_effect = [ + current + ] + obj = self.get_volume_mock_object('job_info') + with pytest.raises(AnsibleExitJson) as exc: + obj.apply() + assert exc.value.args[0]['changed'] + + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_volume.NetAppOntapVolume.get_volume') + def test_successful_online_state_flex_group(self, get_volume): + ''' Test successful online flexGroup state ''' + data = self.mock_args('flexGroup_manual') + set_module_args(data) + current = { + 'hostname': 'test', + 'username': 'test_user', + 'password': 'test_pass!', + 'name': self.mock_vol['name'], + 'vserver': self.mock_vol['vserver'], + 'style_extended': 'flexgroup', + 'is_online': False, + 'junction_path': 'anything', + 'unix_permissions': '755', + 'uuid': '1234' + } + get_volume.side_effect = [ + current + ] + online = 'job_info' # not correct, but works + job = 'job_info' + success = 'success_modify_async' + mount = 'job_info' # not correct, but works + kind = [online, job, job, success, mount, job, job] + obj = self.get_volume_mock_object(kind) + with pytest.raises(AnsibleExitJson) as exc: + obj.apply() + assert exc.value.args[0]['changed'] + + def test_check_job_status_error(self): + ''' Test check job status error ''' + data = self.mock_args('flexGroup_manual') + data['time_out'] = 0 + set_module_args(data) + obj = self.get_volume_mock_object('job_info', job_error='failure') + result = obj.check_job_status('123') + assert result == 'failure' + + def test_check_job_status_time_out_is_0(self): + ''' Test check job status time out is 0''' + data = self.mock_args('flexGroup_manual') + data['time_out'] = 0 + set_module_args(data) + obj = self.get_volume_mock_object('job_info', job_error='time_out') + result = obj.check_job_status('123') + assert result == 'job completion exceeded expected timer of: 0 seconds' + + def test_check_job_status_unexpected(self): + ''' Test check job status unexpected state ''' + data = self.mock_args('flexGroup_manual') + data['time_out'] = 20 + set_module_args(data) + obj = self.get_volume_mock_object('job_info', job_error='other') + with pytest.raises(AnsibleFailJson) as exc: + obj.check_job_status('123') + assert exc.value.args[0]['failed'] + + def test_error_set_efficiency_policy(self): + data = self.mock_args() + data['efficiency_policy'] = 'test_policy' + set_module_args(data) + obj = self.get_volume_mock_object('zapi_error') + with pytest.raises(AnsibleFailJson) as exc: + obj.set_efficiency_config() + assert exc.value.args[0]['msg'] == 'Error enable efficiency on volume test_vol: NetApp API failed. Reason - test:error' + + def test_error_set_efficiency_policy_async(self): + data = self.mock_args() + data['efficiency_policy'] = 'test_policy' + set_module_args(data) + obj = self.get_volume_mock_object('zapi_error') + with pytest.raises(AnsibleFailJson) as exc: + obj.set_efficiency_config_async() + assert exc.value.args[0]['msg'] == 'Error enable efficiency on volume test_vol: NetApp API failed. Reason - test:error' + + def test_successful_modify_tiering_policy(self): + ''' Test successful modify tiering policy ''' + data = self.mock_args() + data['tiering_policy'] = 'auto' + set_module_args(data) + with pytest.raises(AnsibleExitJson) as exc: + self.get_volume_mock_object('volume').apply() + assert exc.value.args[0]['changed'] + + def test_successful_modify_vserver_dr_protection(self): + ''' Test successful modify vserver_dr_protection ''' + data = self.mock_args() + data['vserver_dr_protection'] = 'protected' + set_module_args(data) + with pytest.raises(AnsibleExitJson) as exc: + self.get_volume_mock_object('volume').apply() + assert exc.value.args[0]['changed'] + + def test_successful_group_id(self): + ''' Test successful modify group_id ''' + data = self.mock_args() + data['group_id'] = 1001 + set_module_args(data) + with pytest.raises(AnsibleExitJson) as exc: + self.get_volume_mock_object('volume').apply() + assert exc.value.args[0]['changed'] + + def test_successful_user_id(self): + ''' Test successful modify user_id ''' + data = self.mock_args() + data['user_id'] = 101 + set_module_args(data) + with pytest.raises(AnsibleExitJson) as exc: + self.get_volume_mock_object('volume').apply() + assert exc.value.args[0]['changed'] + + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_volume.NetAppOntapVolume.get_volume') + def test_successful_modify_snapshot_auto_delete(self, get_volume): + ''' Test successful modify unix permissions flexGroup ''' + data = { + 'snapshot_auto_delete': {'delete_order': 'oldest_first', 'destroy_list': 'lun_clone,vol_clone', + 'target_free_space': 20, 'prefix': 'test', 'commitment': 'try', + 'state': 'on', 'trigger': 'snap_reserve', 'defer_delete': 'scheduled'}, + 'hostname': 'test', + 'username': 'test_user', + 'password': 'test_pass!', + 'name': 'test_vol', + 'vserver': 'test_vserver', + + } + set_module_args(data) + current = { + 'name': self.mock_vol['name'], + 'vserver': self.mock_vol['vserver'], + 'snapshot_auto_delete': {'delete_order': 'newest_first', 'destroy_list': 'lun_clone,vol_clone', + 'target_free_space': 30, 'prefix': 'test', 'commitment': 'try', + 'state': 'on', 'trigger': 'snap_reserve', 'defer_delete': 'scheduled'}, + 'uuid': '1234' + } + get_volume.side_effect = [ + current + ] + obj = self.get_volume_mock_object('volume') + with pytest.raises(AnsibleExitJson) as exc: + obj.apply() + assert exc.value.args[0]['changed'] + + def test_error_modify_snapshot_auto_delete(self): + data = { + 'snapshot_auto_delete': {'delete_order': 'oldest_first', 'destroy_list': 'lun_clone,vol_clone', + 'target_free_space': 20, 'prefix': 'test', 'commitment': 'try', + 'state': 'on', 'trigger': 'snap_reserve', 'defer_delete': 'scheduled'}, + 'hostname': 'test', + 'username': 'test_user', + 'password': 'test_pass!', + 'name': 'test_vol', + 'vserver': 'test_vserver', + + } + set_module_args(data) + with pytest.raises(AnsibleFailJson) as exc: + self.get_volume_mock_object('zapi_error').set_snapshot_auto_delete() + assert exc.value.args[0]['msg'] == 'Error setting snapshot auto delete options for volume test_vol: NetApp API failed. Reason - test:error' + + def test_successful_volume_rehost(self): + data = { + 'hostname': 'test', + 'username': 'test_user', + 'password': 'test_pass!', + 'name': 'test_vol', + 'vserver': 'dest_vserver', + 'from_vserver': 'source_vserver' + } + set_module_args(data) + with pytest.raises(AnsibleExitJson) as exc: + self.get_volume_mock_object('volume').apply() + assert exc.value.args[0]['changed'] + + def test_error_volume_rehost(self): + data = { + 'hostname': 'test', + 'username': 'test_user', + 'password': 'test_pass!', + 'name': 'test_vol', + 'vserver': 'dest_vserver', + 'from_vserver': 'source_vserver' + } + set_module_args(data) + with pytest.raises(AnsibleFailJson) as exc: + self.get_volume_mock_object('zapi_error').rehost_volume() + assert exc.value.args[0]['msg'] == 'Error rehosting volume test_vol: NetApp API failed. Reason - test:error' + + def test_successful_volume_restore(self): + data = { + 'hostname': 'test', + 'username': 'test_user', + 'password': 'test_pass!', + 'name': 'test_vol', + 'vserver': 'test_vserver', + 'snapshot_restore': 'snapshot_copy' + } + set_module_args(data) + with pytest.raises(AnsibleExitJson) as exc: + self.get_volume_mock_object('volume').apply() + assert exc.value.args[0]['changed'] + + def test_error_volume_restore(self): + data = { + 'hostname': 'test', + 'username': 'test_user', + 'password': 'test_pass!', + 'name': 'test_vol', + 'vserver': 'test_vserver', + 'snapshot_restore': 'snapshot_copy' + } + set_module_args(data) + with pytest.raises(AnsibleFailJson) as exc: + self.get_volume_mock_object('zapi_error').snapshot_restore_volume() + assert exc.value.args[0]['msg'] == 'Error restoring volume test_vol: NetApp API failed. Reason - test:error' diff --git a/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_volume_autosize.py b/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_volume_autosize.py new file mode 100644 index 00000000..653fddc5 --- /dev/null +++ b/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_volume_autosize.py @@ -0,0 +1,243 @@ +# (c) 2019, NetApp, Inc +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +''' unit tests for Ansible module: na_ontap_volume_autosize ''' + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type +import json +import pytest + +from ansible.module_utils import basic +from ansible.module_utils._text import to_bytes +from ansible_collections.netapp.ontap.tests.unit.compat import unittest +from ansible_collections.netapp.ontap.tests.unit.compat.mock import patch +import ansible_collections.netapp.ontap.plugins.module_utils.netapp as netapp_utils + +from ansible_collections.netapp.ontap.plugins.modules.na_ontap_volume_autosize \ + import NetAppOntapVolumeAutosize as autosize_module # module under test + +if not netapp_utils.has_netapp_lib(): + pytestmark = pytest.mark.skip('skipping as missing required netapp_lib') + + +# REST API canned responses when mocking send_request +SRR = { + # common responses + 'is_rest': (200, {}, None), + 'is_zapi': (400, {}, "Unreachable"), + 'empty_good': (200, {}, None), + 'end_of_sequence': (500, None, "Unexpected call to send_request"), + 'generic_error': (400, None, "Expected error"), + # module specific responses + 'get_uuid': (200, {'records': [{'uuid': 'testuuid'}]}, None), + 'get_autosize': (200, + {'uuid': 'testuuid', + 'name': 'testname', + 'autosize': {"maximum": 10737418240, + "minimum": 22020096, + "grow_threshold": 99, + "shrink_threshold": 40, + "mode": "grow" + } + }, None) +} + + +def set_module_args(args): + """prepare arguments so that they will be picked up during module creation""" + args = json.dumps({'ANSIBLE_MODULE_ARGS': args}) + basic._ANSIBLE_ARGS = to_bytes(args) # pylint: disable=protected-access + + +class AnsibleExitJson(Exception): + """Exception class to be raised by module.exit_json and caught by the test case""" + + +class AnsibleFailJson(Exception): + """Exception class to be raised by module.fail_json and caught by the test case""" + + +def exit_json(*args, **kwargs): # pylint: disable=unused-argument + """function to patch over exit_json; package return data into an exception""" + if 'changed' not in kwargs: + kwargs['changed'] = False + raise AnsibleExitJson(kwargs) + + +def fail_json(*args, **kwargs): # pylint: disable=unused-argument + """function to patch over fail_json; package return data into an exception""" + kwargs['failed'] = True + raise AnsibleFailJson(kwargs) + + +class MockONTAPConnection(object): + ''' mock server connection to ONTAP host ''' + + def __init__(self, kind=None, data=None): + ''' save arguments ''' + self.kind = kind + self.params = data + self.xml_in = None + self.xml_out = None + + def invoke_successfully(self, xml, enable_tunneling): # pylint: disable=unused-argument + ''' mock invoke_successfully returning xml data ''' + self.xml_in = xml + if self.kind == 'autosize': + xml = self.build_autosize_info(self.params) + self.xml_out = xml + return xml + + @staticmethod + def build_autosize_info(autosize_details): + xml = netapp_utils.zapi.NaElement('xml') + attributes = { + 'grow-threshold-percent': autosize_details['grow_threshold_percent'], + 'maximum-size': '10485760', + 'minimum-size': '21504', + 'increment_size': '10240', + 'mode': autosize_details['mode'], + 'shrink-threshold-percent': autosize_details['shrink_threshold_percent'] + } + xml.translate_struct(attributes) + return xml + + +class TestMyModule(unittest.TestCase): + ''' Unit tests for na_ontap_job_schedule ''' + + def setUp(self): + self.mock_module_helper = patch.multiple(basic.AnsibleModule, + exit_json=exit_json, + fail_json=fail_json) + self.mock_module_helper.start() + self.addCleanup(self.mock_module_helper.stop) + self.mock_autosize = { + 'grow_threshold_percent': 99, + 'maximum_size': '10g', + 'minimum_size': '21m', + 'increment_size': '10m', + 'mode': 'grow', + 'shrink_threshold_percent': 40, + 'vserver': 'test_vserver', + 'volume': 'test_volume' + } + + def mock_args(self, rest=False): + if rest: + return { + 'vserver': self.mock_autosize['vserver'], + 'volume': self.mock_autosize['volume'], + 'grow_threshold_percent': self.mock_autosize['grow_threshold_percent'], + 'maximum_size': self.mock_autosize['maximum_size'], + 'minimum_size': self.mock_autosize['minimum_size'], + 'mode': self.mock_autosize['mode'], + 'shrink_threshold_percent': self.mock_autosize['shrink_threshold_percent'], + 'hostname': 'test', + 'username': 'test_user', + 'password': 'test_pass!' + } + else: + return { + 'vserver': self.mock_autosize['vserver'], + 'volume': self.mock_autosize['volume'], + 'grow_threshold_percent': self.mock_autosize['grow_threshold_percent'], + 'maximum_size': self.mock_autosize['maximum_size'], + 'minimum_size': self.mock_autosize['minimum_size'], + 'increment_size': self.mock_autosize['increment_size'], + 'mode': self.mock_autosize['mode'], + 'shrink_threshold_percent': self.mock_autosize['shrink_threshold_percent'], + 'hostname': 'test', + 'username': 'test_user', + 'password': 'test_pass!', + 'use_rest': 'never' + } + + def get_autosize_mock_object(self, cx_type='zapi', kind=None): + autosize_obj = autosize_module() + if cx_type == 'zapi': + if kind is None: + autosize_obj.server = MockONTAPConnection() + elif kind == 'autosize': + autosize_obj.server = MockONTAPConnection(kind='autosize', data=self.mock_autosize) + return autosize_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({}) + autosize_module() + print('Info: %s' % exc.value.args[0]['msg']) + + def test_idempotent_modify(self): + set_module_args(self.mock_args()) + with pytest.raises(AnsibleExitJson) as exc: + self.get_autosize_mock_object('zapi', 'autosize').apply() + assert not exc.value.args[0]['changed'] + + def test_successful_modify(self): + data = self.mock_args() + data['maximum_size'] = '11g' + set_module_args(data) + with pytest.raises(AnsibleExitJson) as exc: + self.get_autosize_mock_object('zapi', 'autosize').apply() + assert exc.value.args[0]['changed'] + + def test_successful_reset(self): + data = {} + data['reset'] = True + data['hostname'] = 'test' + data['username'] = 'test_user' + data['password'] = 'test_pass!' + data['volume'] = 'test_vol' + data['vserver'] = 'test_vserver' + data['use_rest'] = 'never' + set_module_args(data) + with pytest.raises(AnsibleExitJson) as exc: + self.get_autosize_mock_object('zapi', 'autosize').apply() + assert exc.value.args[0]['changed'] + + @patch('ansible_collections.netapp.ontap.plugins.module_utils.netapp.OntapRestAPI.send_request') + def test_rest_error(self, mock_request): + data = self.mock_args(rest=True) + set_module_args(data) + mock_request.side_effect = [ + SRR['is_rest'], + SRR['generic_error'], + SRR['end_of_sequence'] + ] + with pytest.raises(AnsibleFailJson) as exc: + self.get_autosize_mock_object(cx_type='rest').apply() + assert exc.value.args[0]['msg'] == SRR['generic_error'][2] + + @patch('ansible_collections.netapp.ontap.plugins.module_utils.netapp.OntapRestAPI.send_request') + def test_rest_successful_modify(self, mock_request): + data = self.mock_args(rest=True) + data['maximum_size'] = '11g' + set_module_args(data) + mock_request.side_effect = [ + SRR['is_rest'], + SRR['get_uuid'], + SRR['get_autosize'], + SRR['empty_good'], + SRR['end_of_sequence'] + ] + with pytest.raises(AnsibleExitJson) as exc: + self.get_autosize_mock_object(cx_type='rest').apply() + assert exc.value.args[0]['changed'] + + @patch('ansible_collections.netapp.ontap.plugins.module_utils.netapp.OntapRestAPI.send_request') + def test_rest_idempotent_modify(self, mock_request): + data = self.mock_args(rest=True) + set_module_args(data) + mock_request.side_effect = [ + SRR['is_rest'], + SRR['get_uuid'], + SRR['get_autosize'], + SRR['empty_good'], + SRR['end_of_sequence'] + ] + with pytest.raises(AnsibleExitJson) as exc: + self.get_autosize_mock_object(cx_type='rest').apply() + assert not exc.value.args[0]['changed'] diff --git a/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_volume_clone.py b/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_volume_clone.py new file mode 100644 index 00000000..eb78e3fa --- /dev/null +++ b/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_volume_clone.py @@ -0,0 +1,257 @@ +# (c) 2018, NetApp, Inc +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +''' unit tests ONTAP Ansible module: na_ontap_volume_clone''' + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type +import json +import pytest + +from ansible_collections.netapp.ontap.tests.unit.compat import unittest +from ansible_collections.netapp.ontap.tests.unit.compat.mock import patch +from ansible.module_utils import basic +from ansible.module_utils._text import to_bytes +import ansible_collections.netapp.ontap.plugins.module_utils.netapp as netapp_utils + +from ansible_collections.netapp.ontap.plugins.modules.na_ontap_volume_clone \ + import NetAppONTAPVolumeClone as my_module + +if not netapp_utils.has_netapp_lib(): + pytestmark = pytest.mark.skip('skipping as missing required netapp_lib') + + +def set_module_args(args): + """prepare arguments so that they will be picked up during module creation""" + args = json.dumps({'ANSIBLE_MODULE_ARGS': args}) + basic._ANSIBLE_ARGS = to_bytes(args) # pylint: disable=protected-access + + +class AnsibleExitJson(Exception): + """Exception class to be raised by module.exit_json and caught by the test case""" + pass + + +class AnsibleFailJson(Exception): + """Exception class to be raised by module.fail_json and caught by the test case""" + pass + + +def exit_json(*args, **kwargs): # pylint: disable=unused-argument + """function to patch over exit_json; package return data into an exception""" + if 'changed' not in kwargs: + kwargs['changed'] = False + raise AnsibleExitJson(kwargs) + + +def fail_json(*args, **kwargs): # pylint: disable=unused-argument + """function to patch over fail_json; package return data into an exception""" + kwargs['failed'] = True + raise AnsibleFailJson(kwargs) + + +class MockONTAPConnection(object): + ''' mock server connection to ONTAP host ''' + + def __init__(self, kind=None): + ''' save arguments ''' + self.type = kind + self.xml_in = None + self.xml_out = None + + def invoke_successfully(self, xml, enable_tunneling): # pylint: disable=unused-argument + ''' mock invoke_successfully returning xml data ''' + self.xml_in = xml + if self.type == 'volume_clone': + xml = self.build_volume_clone_info() + elif self.type == 'volume_clone_split_in_progress': + xml = self.build_volume_clone_info_split_in_progress() + elif self.type == 'volume_clone_fail': + raise netapp_utils.zapi.NaApiError(code='TEST', message="This exception is from the unit test") + self.xml_out = xml + return xml + + @staticmethod + def build_volume_clone_info(): + ''' build xml data for volume-clone-info ''' + xml = netapp_utils.zapi.NaElement('xml') + data = {'attributes': {'volume-clone-info': {'volume': 'ansible', + 'parent-volume': 'ansible'}}} + xml.translate_struct(data) + return xml + + @staticmethod + def build_volume_clone_info_split_in_progress(): + ''' build xml data for volume-clone-info whilst split in progress ''' + xml = netapp_utils.zapi.NaElement('xml') + data = {'attributes': {'volume-clone-info': {'volume': 'ansible', + 'parent-volume': 'ansible', + 'block-percentage-complete': 20, + 'blocks-scanned': 56676, + 'blocks-updated': 54588}}} + xml.translate_struct(data) + return xml + + +class TestMyModule(unittest.TestCase): + ''' a group of related Unit Tests ''' + + def setUp(self): + self.mock_module_helper = patch.multiple(basic.AnsibleModule, + exit_json=exit_json, + fail_json=fail_json) + self.mock_module_helper.start() + self.addCleanup(self.mock_module_helper.stop) + self.vserver = MockONTAPConnection() + self.onbox = False + + def set_default_args(self): + if self.onbox: + hostname = '10.10.10.10' + username = 'username' + password = 'password' + vserver = 'ansible' + volume = 'ansible' + parent_volume = 'ansible' + split = None + else: + hostname = '10.10.10.10' + username = 'username' + password = 'password' + vserver = 'ansible' + volume = 'ansible' + parent_volume = 'ansible' + split = None + return dict({ + 'hostname': hostname, + 'username': username, + 'password': password, + 'vserver': vserver, + 'volume': volume, + 'parent_volume': parent_volume, + 'split': split + }) + + def set_default_current(self): + return dict({ + 'split': False + }) + + def test_module_fail_when_required_args_missing(self): + ''' test required arguments are reported as errors ''' + with pytest.raises(AnsibleFailJson) as exc: + set_module_args({}) + my_module() + print('Info: %s' % exc.value.args[0]['msg']) + + def test_ensure_get_called(self): + ''' test get_volume_clone() for non-existent volume clone''' + set_module_args(self.set_default_args()) + my_obj = my_module() + my_obj.vserver = self.vserver + assert my_obj.get_volume_clone() is None + + def test_ensure_get_called_existing(self): + ''' test get_volume_clone() for existing volume clone''' + set_module_args(self.set_default_args()) + my_obj = my_module() + my_obj.vserver = MockONTAPConnection(kind='volume_clone') + current = self.set_default_current() + assert my_obj.get_volume_clone() == current + + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_volume_clone.NetAppONTAPVolumeClone.create_volume_clone') + def test_successful_create(self, create_volume_clone): + ''' test creating volume_clone without split and testing idempotency ''' + module_args = { + 'parent_vserver': 'abc', + 'parent_snapshot': 'abc', + 'volume_type': 'dp', + 'qos_policy_group_name': 'abc', + 'junction_path': 'abc', + 'uid': '1', + 'gid': '1' + } + module_args.update(self.set_default_args()) + set_module_args(module_args) + my_obj = my_module() + if not self.onbox: + my_obj.vserver = self.vserver + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + assert exc.value.args[0]['changed'] + create_volume_clone.assert_called_with() + # to reset na_helper from remembering the previous 'changed' value + my_obj = my_module() + if not self.onbox: + my_obj.vserver = MockONTAPConnection('volume_clone') + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + assert not exc.value.args[0]['changed'] + + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_volume_clone.NetAppONTAPVolumeClone.create_volume_clone') + def test_successful_create_with_split(self, create_volume_clone): + ''' test creating volume_clone with split and testing idempotency ''' + module_args = { + 'parent_snapshot': 'abc', + 'parent_vserver': 'abc', + 'volume_type': 'dp', + 'qos_policy_group_name': 'abc', + 'junction_path': 'abc', + 'uid': '1', + 'gid': '1' + } + module_args.update(self.set_default_args()) + module_args['split'] = True + set_module_args(module_args) + my_obj = my_module() + if not self.onbox: + my_obj.vserver = self.vserver + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + assert exc.value.args[0]['changed'] + create_volume_clone.assert_called_with() + # to reset na_helper from remembering the previous 'changed' value + my_obj = my_module() + if not self.onbox: + my_obj.vserver = MockONTAPConnection('volume_clone_split_in_progress') + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + assert not exc.value.args[0]['changed'] + + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_volume_clone.NetAppONTAPVolumeClone.create_volume_clone') + def test_successful_create_with_split_in_progress(self, create_volume_clone): + ''' test creating volume_clone with split and split already in progress ''' + module_args = { + 'parent_snapshot': 'abc', + 'parent_vserver': 'abc', + 'volume_type': 'dp', + 'qos_policy_group_name': 'abc', + 'junction_path': 'abc', + 'uid': '1', + 'gid': '1' + } + module_args.update(self.set_default_args()) + module_args['split'] = True + set_module_args(module_args) + my_obj = my_module() + if not self.onbox: + my_obj.vserver = MockONTAPConnection('volume_clone_split_in_progress') + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + assert not exc.value.args[0]['changed'] + + def test_if_all_methods_catch_exception(self): + ''' test if all methods catch exception ''' + module_args = {} + module_args.update(self.set_default_args()) + set_module_args(module_args) + my_obj = my_module() + if not self.onbox: + my_obj.vserver = MockONTAPConnection('volume_clone_fail') + my_obj.create_server = my_obj.vserver + with pytest.raises(AnsibleFailJson) as exc: + my_obj.get_volume_clone() + assert 'Error fetching volume clone information ' in exc.value.args[0]['msg'] + with pytest.raises(AnsibleFailJson) as exc: + my_obj.create_volume_clone() + assert 'Error creating volume clone: ' in exc.value.args[0]['msg'] diff --git a/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_volume_rest.py b/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_volume_rest.py new file mode 100644 index 00000000..fe5016e3 --- /dev/null +++ b/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_volume_rest.py @@ -0,0 +1,333 @@ +# (c) 2020, NetApp, Inc +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +''' unit test template for ONTAP Ansible module ''' + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import json +import pytest + +from ansible.module_utils import basic +from ansible.module_utils._text import to_bytes +from ansible_collections.netapp.ontap.tests.unit.compat import unittest +from ansible_collections.netapp.ontap.tests.unit.compat.mock import patch, Mock +import ansible_collections.netapp.ontap.plugins.module_utils.netapp as netapp_utils + +from ansible_collections.netapp.ontap.plugins.modules.na_ontap_volume \ + import NetAppOntapVolume as volume_module # module under test + +# needed for get and modify/delete as they still use ZAPI +if not netapp_utils.has_netapp_lib(): + pytestmark = pytest.mark.skip('skipping as missing required netapp_lib') + +# REST API canned responses when mocking send_request +SRR = { + # common responses + 'is_rest': (200, {}, None), + 'is_zapi': (400, {}, "Unreachable"), + 'empty_good': (200, {}, None), + 'end_of_sequence': (500, None, "Unexpected call to send_request"), + 'generic_error': (400, None, "Expected error"), + # module specific responses + 'svm_record': (200, + {'records': [{"uuid": "09e9fd5e-8ebd-11e9-b162-005056b39fe7", + "name": "test_svm", + "subtype": "default", + "language": "c.utf_8", + "aggregates": [{"name": "aggr_1", + "uuid": "850dd65b-8811-4611-ac8c-6f6240475ff9"}, + {"name": "aggr_2", + "uuid": "850dd65b-8811-4611-ac8c-6f6240475ff9"}], + "comment": "new comment", + "ipspace": {"name": "ansible_ipspace", + "uuid": "2b760d31-8dfd-11e9-b162-005056b39fe7"}, + "snapshot_policy": {"uuid": "3b611707-8dfd-11e9-b162-005056b39fe7", + "name": "old_snapshot_policy"}, + "nfs": {"enabled": True}, + "cifs": {"enabled": False}, + "iscsi": {"enabled": False}, + "fcp": {"enabled": False}, + "nvme": {"enabled": False}}]}, None) +} + + +def set_module_args(args): + """prepare arguments so that they will be picked up during module creation""" + args = json.dumps({'ANSIBLE_MODULE_ARGS': args}) + basic._ANSIBLE_ARGS = to_bytes(args) # pylint: disable=protected-access + + +class AnsibleExitJson(Exception): + """Exception class to be raised by module.exit_json and caught by the test case""" + + +class AnsibleFailJson(Exception): + """Exception class to be raised by module.fail_json and caught by the test case""" + + +def exit_json(*args, **kwargs): # pylint: disable=unused-argument + """function to patch over exit_json; package return data into an exception""" + if 'changed' not in kwargs: + kwargs['changed'] = False + raise AnsibleExitJson(kwargs) + + +def fail_json(*args, **kwargs): # pylint: disable=unused-argument + """function to patch over fail_json; package return data into an exception""" + kwargs['failed'] = True + raise AnsibleFailJson(kwargs) + + +class MockONTAPConnection(object): + ''' mock server connection to ONTAP host ''' + + def __init__(self, kind=None, data=None, get_volume=None): + ''' save arguments ''' + self.type = kind + self.params = data + self.xml_in = None + self.xml_out = None + self.get_volume = get_volume + self.zapis = list() + + def invoke_successfully(self, xml, enable_tunneling): # pylint: disable=unused-argument + ''' mock invoke_successfully returning xml data ''' + self.xml_in = xml + zapi = xml.get_name() + self.zapis.append(zapi) + request = xml.to_string().decode('utf-8') + if self.type == 'error': + raise OSError('unexpected call to %s' % self.params) + print('request:', request) + if request.startswith('<volume-get-iter>'): + what = None + if self.get_volume: + what = self.get_volume.pop(0) + if what is None: + xml = self.build_empty_response() + else: + xml = self.build_get_response(what) + self.xml_out = xml + print('response:', xml.to_string()) + return xml + + @staticmethod + def build_response(data): + ''' build xml data for vserser-info ''' + xml = netapp_utils.zapi.NaElement('xml') + xml.translate_struct(data) + return xml + + def build_empty_response(self): + data = {'num-records': '0'} + return self.build_response(data) + + def build_get_response(self, name): + ''' build xml data for vserser-info ''' + if name is None: + return self.build_empty_response() + data = {'num-records': 1, + 'attributes-list': [{ + 'volume-attributes': { + 'volume-id-attributes': { + 'name': name, + 'instance-uuid': '123' + }, + 'volume-performance-attributes': { + 'is-atime-update-enabled': 'true' + }, + 'volume-security-attributes': { + 'volume-security-unix-attributes': { + 'permissions': 777 + } + }, + 'volume-snapshot-attributes': { + 'snapshot-policy': 'default' + }, + 'volume-snapshot-autodelete-attributes': { + 'is-autodelete-enabled': 'true' + }, + 'volume-space-attributes': { + 'size': 10737418240 # 10 GB + }, + 'volume-state-attributes': { + 'state': 'online' + }, + } + }]} + return self.build_response(data) + + +class TestMyModule(unittest.TestCase): + ''' a group of related Unit Tests ''' + + def setUp(self): + self.mock_module_helper = patch.multiple(basic.AnsibleModule, + exit_json=exit_json, + fail_json=fail_json) + self.mock_module_helper.start() + self.addCleanup(self.mock_module_helper.stop) + # self.server = MockONTAPConnection() + self.mock_vserver = { + 'name': 'test_svm', + 'root_volume': 'ansible_vol', + 'root_volume_aggregate': 'ansible_aggr', + 'aggr_list': 'aggr_1,aggr_2', + 'ipspace': 'ansible_ipspace', + 'subtype': 'default', + 'language': 'c.utf_8', + 'snapshot_policy': 'old_snapshot_policy', + 'comment': 'new comment' + } + + @staticmethod + def mock_args(): + return {'name': 'test_volume', + 'vserver': 'ansibleSVM', + 'nas_application_template': dict( + tiering=None + ), + # 'aggregate_name': 'whatever', # not used for create when using REST application/applications + 'size': 10, + 'size_unit': 'gb', + 'hostname': 'test', + 'username': 'test_user', + 'password': 'test_pass!'} + + def get_volume_mock_object(self, **kwargs): + volume_obj = volume_module() + netapp_utils.ems_log_event = Mock(return_value=None) + volume_obj.server = MockONTAPConnection(**kwargs) + volume_obj.cluster = MockONTAPConnection(kind='error', data='cluster ZAPI.') + return volume_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({}) + volume_module() + print('Info: %s' % exc.value.args[0]['msg']) + + @patch('ansible_collections.netapp.ontap.plugins.module_utils.netapp.OntapRestAPI.send_request') + def test_fail_if_aggr_is_set(self, mock_request): + data = dict(self.mock_args()) + data['aggregate_name'] = 'should_fail' + set_module_args(data) + mock_request.side_effect = [ + SRR['end_of_sequence'] + ] + with pytest.raises(AnsibleFailJson) as exc: + self.get_volume_mock_object().apply() + error = 'Conflict: aggregate_name is not supported when application template is enabled. Found: aggregate_name: should_fail' + assert exc.value.args[0]['msg'] == error + + @patch('ansible_collections.netapp.ontap.plugins.module_utils.netapp.OntapRestAPI.send_request') + def test_missing_size(self, mock_request): + data = dict(self.mock_args()) + data.pop('size') + set_module_args(data) + mock_request.side_effect = [ + SRR['end_of_sequence'] + ] + with pytest.raises(AnsibleFailJson) as exc: + self.get_volume_mock_object().apply() + error = 'Error: "size" is required to create nas application.' + assert exc.value.args[0]['msg'] == error + + @patch('ansible_collections.netapp.ontap.plugins.module_utils.netapp.OntapRestAPI.send_request') + def test_mismatched_tiering_policies(self, mock_request): + data = dict(self.mock_args()) + data['tiering_policy'] = 'none' + data['nas_application_template'] = dict( + tiering=dict(policy='auto') + ) + set_module_args(data) + mock_request.side_effect = [ + SRR['end_of_sequence'] + ] + with pytest.raises(AnsibleFailJson) as exc: + self.get_volume_mock_object().apply() + error = 'Conflict: if tiering_policy and nas_application_template tiering policy are both set, they must match.' + error += ' Found "none" and "auto".' + assert exc.value.args[0]['msg'] == error + + @patch('ansible_collections.netapp.ontap.plugins.module_utils.netapp.OntapRestAPI.send_request') + def test_rest_error(self, mock_request): + data = dict(self.mock_args()) + set_module_args(data) + mock_request.side_effect = [ + SRR['generic_error'], # POST application/applications + SRR['end_of_sequence'] + ] + with pytest.raises(AnsibleFailJson) as exc: + self.get_volume_mock_object().apply() + assert exc.value.args[0]['msg'] == 'Error: calling: /application/applications: got %s' % SRR['generic_error'][2] + + @patch('ansible_collections.netapp.ontap.plugins.module_utils.netapp.OntapRestAPI.send_request') + def test_rest_successfully_created(self, mock_request): + data = dict(self.mock_args()) + set_module_args(data) + mock_request.side_effect = [ + SRR['empty_good'], # POST application/applications + SRR['end_of_sequence'] + ] + with pytest.raises(AnsibleExitJson) as exc: + self.get_volume_mock_object().apply() + assert exc.value.args[0]['changed'] + + @patch('ansible_collections.netapp.ontap.plugins.module_utils.netapp.OntapRestAPI.send_request') + def test_rest_create_idempotency(self, mock_request): + data = dict(self.mock_args()) + set_module_args(data) + mock_request.side_effect = [ + SRR['end_of_sequence'] + ] + with pytest.raises(AnsibleExitJson) as exc: + self.get_volume_mock_object(get_volume=['test']).apply() + assert not exc.value.args[0]['changed'] + + @patch('ansible_collections.netapp.ontap.plugins.module_utils.netapp.OntapRestAPI.send_request') + def test_rest_successfully_created_with_modify(self, mock_request): + ''' since language is not supported in application, the module is expected to: + 1. create the volume using application REST API + 2. immediately modify the volume to update options which not available in the nas template. + ''' + data = dict(self.mock_args()) + data['language'] = 'fr' # TODO: apparently language is not supported for modify + data['unix_permissions'] = '---rw-rx-r--' + set_module_args(data) + mock_request.side_effect = [ + SRR['empty_good'], # POST application/applications + SRR['end_of_sequence'] + ] + my_volume = self.get_volume_mock_object(get_volume=[None, 'test']) + with pytest.raises(AnsibleExitJson) as exc: + my_volume.apply() + assert exc.value.args[0]['changed'] + print(exc.value.args[0]) + assert 'unix_permissions' in exc.value.args[0]['modify_after_create'] + assert 'language' not in exc.value.args[0]['modify_after_create'] # eh! + assert 'volume-modify-iter' in my_volume.server.zapis + + @patch('ansible_collections.netapp.ontap.plugins.module_utils.netapp.OntapRestAPI.send_request') + def test_rest_successfully_resized(self, mock_request): + ''' make sure resize if using RESP API if sizing_method is present + ''' + data = dict(self.mock_args()) + data['sizing_method'] = 'add_new_resources' + data['size'] = 20737418240 + set_module_args(data) + mock_request.side_effect = [ + SRR['empty_good'], # PATCH application/applications + SRR['end_of_sequence'] + ] + my_volume = self.get_volume_mock_object(get_volume=['test']) + with pytest.raises(AnsibleExitJson) as exc: + my_volume.apply() + assert exc.value.args[0]['changed'] + print(exc.value.args[0]) + assert 'volume-size' not in my_volume.server.zapis + print(mock_request.call_args) + mock_request.assert_called_with('PATCH', '/storage/volumes/123', {'sizing_method': 'add_new_resources'}, json={'size': 22266633286068469760}) diff --git a/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_volume_snaplock.py b/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_volume_snaplock.py new file mode 100644 index 00000000..45ec4b40 --- /dev/null +++ b/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_volume_snaplock.py @@ -0,0 +1,166 @@ +# (c) 2020, NetApp, Inc +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +""" unit tests for Ansible module: na_ontap_volume_snaplock """ + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type +import json +import pytest + +from ansible_collections.netapp.ontap.tests.unit.compat import unittest +from ansible_collections.netapp.ontap.tests.unit.compat.mock import patch, Mock +from ansible.module_utils import basic +from ansible.module_utils._text import to_bytes +import ansible_collections.netapp.ontap.plugins.module_utils.netapp as netapp_utils + +from ansible_collections.netapp.ontap.plugins.modules.na_ontap_volume_snaplock \ + import NetAppOntapVolumeSnaplock as snaplock_module # module under test + +if not netapp_utils.has_netapp_lib(): + pytestmark = pytest.mark.skip('skipping as missing required netapp_lib') + + +def set_module_args(args): + """prepare arguments so that they will be picked up during module creation""" + args = json.dumps({'ANSIBLE_MODULE_ARGS': args}) + basic._ANSIBLE_ARGS = to_bytes(args) # pylint: disable=protected-access + + +class AnsibleExitJson(Exception): + """Exception class to be raised by module.exit_json and caught by the test case""" + pass + + +class AnsibleFailJson(Exception): + """Exception class to be raised by module.fail_json and caught by the test case""" + pass + + +def exit_json(*args, **kwargs): # pylint: disable=unused-argument + """function to patch over exit_json; package return data into an exception""" + if 'changed' not in kwargs: + kwargs['changed'] = False + raise AnsibleExitJson(kwargs) + + +def fail_json(*args, **kwargs): # pylint: disable=unused-argument + """function to patch over fail_json; package return data into an exception""" + kwargs['failed'] = True + raise AnsibleFailJson(kwargs) + + +class MockONTAPConnection(object): + ''' mock server connection to ONTAP host ''' + + def __init__(self, kind=None, data=None): + ''' save arguments ''' + self.type = kind + self.params = data + self.xml_in = None + self.xml_out = None + + def invoke_successfully(self, xml, enable_tunneling): # pylint: disable=unused-argument + ''' mock invoke_successfully returning xml data ''' + self.xml_in = xml + if self.type == 'snaplock': + xml = self.build_snaplock_info(self.params) + elif self.type == 'zapi_error': + error = netapp_utils.zapi.NaApiError('test', 'error') + raise error + self.xml_out = xml + return xml + + @staticmethod + def build_snaplock_info(data): + ''' build xml data for vserser-info ''' + xml = netapp_utils.zapi.NaElement('xml') + attributes = {'snaplock-attrs': { + 'snaplock-attrs-info': { + 'autocommit-period': data['autocommit_period'], + 'default-retention-period': data['default_retention_period'], + 'maximum-retention-period': data['maximum_retention_period'], + 'minimum-retention-period': data['minimum_retention_period'], + 'is-volume-append-mode-enabled': data['is_volume_append_mode_enabled'] + } + }} + xml.translate_struct(attributes) + return xml + + +class TestMyModule(unittest.TestCase): + ''' a group of related Unit Tests ''' + + def setUp(self): + self.mock_module_helper = patch.multiple(basic.AnsibleModule, + exit_json=exit_json, + fail_json=fail_json) + self.mock_module_helper.start() + self.addCleanup(self.mock_module_helper.stop) + self.mock_snaplock = { + 'autocommit_period': '10days', + 'default_retention_period': '1years', + 'maximum_retention_period': '2years', + 'minimum_retention_period': '6months', + 'is_volume_append_mode_enabled': 'false' + } + + def mock_args(self): + return { + 'name': 'test_volume', + 'autocommit_period': self.mock_snaplock['autocommit_period'], + 'default_retention_period': self.mock_snaplock['default_retention_period'], + 'maximum_retention_period': self.mock_snaplock['maximum_retention_period'], + 'minimum_retention_period': self.mock_snaplock['minimum_retention_period'], + 'hostname': 'hostname', + 'username': 'username', + 'password': 'password', + 'vserver': 'test_vserver' + } + + def get_snaplock_mock_object(self, kind=None): + """ + Helper method to return an na_ontap_volume_snaplock object + :param kind: passes this param to MockONTAPConnection() + :return: na_ontap_volume_snaplock object + """ + snaplock_obj = snaplock_module() + netapp_utils.ems_log_event = Mock(return_value=None) + if kind is None: + snaplock_obj.server = MockONTAPConnection() + else: + snaplock_obj.server = MockONTAPConnection(kind=kind, data=self.mock_snaplock) + return snaplock_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({}) + snaplock_module() + print('Info: %s' % exc.value.args[0]['msg']) + + def test_get_existing_snaplock(self): + set_module_args(self.mock_args()) + result = self.get_snaplock_mock_object(kind='snaplock').get_volume_snaplock_attrs() + assert result['autocommit_period'] == self.mock_snaplock['autocommit_period'] + assert result['default_retention_period'] == self.mock_snaplock['default_retention_period'] + assert result['is_volume_append_mode_enabled'] is False + assert result['maximum_retention_period'] == self.mock_snaplock['maximum_retention_period'] + + def test_modify_snaplock(self): + data = self.mock_args() + data['maximum_retention_period'] = '5years' + set_module_args(data) + with pytest.raises(AnsibleExitJson) as exc: + self.get_snaplock_mock_object('snaplock').apply() + assert exc.value.args[0]['changed'] + + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_volume_snaplock.NetAppOntapVolumeSnaplock.get_volume_snaplock_attrs') + def test_modify_snaplock_error(self, get_volume_snaplock_attrs): + data = self.mock_args() + data['maximum_retention_period'] = '5years' + set_module_args(data) + get_volume_snaplock_attrs.side_effect = [self.mock_snaplock] + with pytest.raises(AnsibleFailJson) as exc: + self.get_snaplock_mock_object('zapi_error').apply() + assert exc.value.args[0]['msg'] == 'Error setting snaplock attributes for volume test_volume : NetApp API failed. Reason - test:error' diff --git a/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_vscan.py b/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_vscan.py new file mode 100644 index 00000000..6ea6893c --- /dev/null +++ b/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_vscan.py @@ -0,0 +1,234 @@ +# (c) 2018, NetApp, Inc +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +''' unit tests for Ansible module: na_ontap_vscan''' + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type +import json +import pytest + +from ansible.module_utils import basic +from ansible.module_utils._text import to_bytes +from ansible_collections.netapp.ontap.tests.unit.compat import unittest +from ansible_collections.netapp.ontap.tests.unit.compat.mock import patch +import ansible_collections.netapp.ontap.plugins.module_utils.netapp as netapp_utils + +from ansible_collections.netapp.ontap.plugins.modules.na_ontap_vscan \ + import NetAppOntapVscan as vscan_module # module under test + +if not netapp_utils.has_netapp_lib(): + pytestmark = pytest.mark.skip('skipping as missing required netapp_lib') +HAS_NETAPP_ZAPI_MSG = "pip install netapp_lib is required" + + +# REST API canned responses when mocking send_request +SRR = { + # common responses + 'is_rest': (200, {}, None), + 'is_zapi': (400, {}, "Unreachable"), + 'empty_good': (200, {}, None), + 'end_of_sequence': (500, None, "Ooops, the UT needs one more SRR response"), + 'generic_error': (400, None, "Expected error"), + # module specific responses + 'enabled': (200, {'records': [{'enabled': True, 'svm': {'uuid': 'testuuid'}}]}, None), + 'disabled': (200, {'records': [{'enabled': False, 'svm': {'uuid': 'testuuid'}}]}, None), +} + + +def set_module_args(args): + """prepare arguments so that they will be picked up during module creation""" + args = json.dumps({'ANSIBLE_MODULE_ARGS': args}) + basic._ANSIBLE_ARGS = to_bytes(args) # pylint: disable=protected-access + + +class AnsibleExitJson(Exception): + """Exception class to be raised by module.exit_json and caught by the test case""" + + +class AnsibleFailJson(Exception): + """Exception class to be raised by module.fail_json and caught by the test case""" + + +def exit_json(*args, **kwargs): # pylint: disable=unused-argument + """function to patch over exit_json; package return data into an exception""" + if 'changed' not in kwargs: + kwargs['changed'] = False + raise AnsibleExitJson(kwargs) + + +def fail_json(*args, **kwargs): # pylint: disable=unused-argument + """function to patch over fail_json; package return data into an exception""" + kwargs['failed'] = True + raise AnsibleFailJson(kwargs) + + +class MockONTAPConnection(object): + ''' mock server connection to ONTAP host ''' + + def __init__(self, kind=None, data=None): + ''' save arguments ''' + self.kind = kind + self.params = data + self.xml_in = None + self.xml_out = None + + def invoke_successfully(self, xml, enable_tunneling): # pylint: disable=unused-argument + ''' mock invoke_successfully returning xml data ''' + self.xml_in = xml + if self.kind == 'enable': + xml = self.build_vscan_status_info(self.params) + self.xml_out = xml + return xml + + @staticmethod + def build_vscan_status_info(status): + xml = netapp_utils.zapi.NaElement('xml') + attributes = {'num-records': 1, + 'attributes-list': {'vscan-status-info': {'is-vscan-enabled': status}}} + xml.translate_struct(attributes) + return xml + + +class TestMyModule(unittest.TestCase): + ''' Unit tests for na_ontap_job_schedule ''' + + def setUp(self): + self.mock_module_helper = patch.multiple(basic.AnsibleModule, + exit_json=exit_json, + fail_json=fail_json) + self.mock_module_helper.start() + self.addCleanup(self.mock_module_helper.stop) + + def mock_args(self): + return { + 'enable': False, + 'vserver': 'vserver', + 'hostname': 'test', + 'username': 'test_user', + 'password': 'test_pass!' + } + + def get_vscan_mock_object(self, cx_type='zapi', kind=None, status=None): + vscan_obj = vscan_module() + if cx_type == 'zapi': + if kind is None: + vscan_obj.server = MockONTAPConnection() + else: + vscan_obj.server = MockONTAPConnection(kind=kind, data=status) + # For rest, mocking is achieved through side_effect + return vscan_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({}) + vscan_module() + print('Info: %s' % exc.value.args[0]['msg']) + + def test_successfully_enable(self): + data = self.mock_args() + data['enable'] = True + data['use_rest'] = 'never' + set_module_args(data) + with pytest.raises(AnsibleExitJson) as exc: + self.get_vscan_mock_object('zapi', 'enable', 'false').apply() + assert exc.value.args[0]['changed'] + + def test_idempotently_enable(self): + data = self.mock_args() + data['enable'] = True + data['use_rest'] = 'never' + set_module_args(data) + with pytest.raises(AnsibleExitJson) as exc: + self.get_vscan_mock_object('zapi', 'enable', 'true').apply() + assert not exc.value.args[0]['changed'] + + def test_successfully_disable(self): + data = self.mock_args() + data['enable'] = False + data['use_rest'] = 'never' + set_module_args(data) + with pytest.raises(AnsibleExitJson) as exc: + self.get_vscan_mock_object('zapi', 'enable', 'true').apply() + assert exc.value.args[0]['changed'] + + def test_idempotently_disable(self): + data = self.mock_args() + data['enable'] = False + data['use_rest'] = 'never' + set_module_args(data) + with pytest.raises(AnsibleExitJson) as exc: + self.get_vscan_mock_object('zapi', 'enable', 'false').apply() + assert not exc.value.args[0]['changed'] + + @patch('ansible_collections.netapp.ontap.plugins.module_utils.netapp.OntapRestAPI.send_request') + def test_rest_error(self, mock_request): + data = self.mock_args() + set_module_args(data) + mock_request.side_effect = [ + SRR['is_rest'], + SRR['generic_error'], + SRR['end_of_sequence'] + ] + with pytest.raises(AnsibleFailJson) as exc: + self.get_vscan_mock_object(cx_type='rest').apply() + assert exc.value.args[0]['msg'] == SRR['generic_error'][2] + + @patch('ansible_collections.netapp.ontap.plugins.module_utils.netapp.OntapRestAPI.send_request') + def test_rest_successly_enable(self, mock_request): + data = self.mock_args() + data['enable'] = True + set_module_args(data) + mock_request.side_effect = [ + SRR['is_rest'], + SRR['disabled'], + SRR['empty_good'], + SRR['end_of_sequence'] + ] + with pytest.raises(AnsibleExitJson) as exc: + self.get_vscan_mock_object(cx_type='rest').apply() + assert exc.value.args[0]['changed'] + + @patch('ansible_collections.netapp.ontap.plugins.module_utils.netapp.OntapRestAPI.send_request') + def test_rest_idempotently_enable(self, mock_request): + data = self.mock_args() + data['enable'] = True + set_module_args(data) + mock_request.side_effect = [ + SRR['is_rest'], + SRR['enabled'], + SRR['end_of_sequence'] + ] + with pytest.raises(AnsibleExitJson) as exc: + self.get_vscan_mock_object(cx_type='rest').apply() + assert not exc.value.args[0]['changed'] + + @patch('ansible_collections.netapp.ontap.plugins.module_utils.netapp.OntapRestAPI.send_request') + def test_rest_successly_disable(self, mock_request): + data = self.mock_args() + data['enable'] = False + set_module_args(data) + mock_request.side_effect = [ + SRR['is_rest'], + SRR['enabled'], + SRR['empty_good'], + SRR['end_of_sequence'] + ] + with pytest.raises(AnsibleExitJson) as exc: + self.get_vscan_mock_object(cx_type='rest').apply() + assert exc.value.args[0]['changed'] + + @patch('ansible_collections.netapp.ontap.plugins.module_utils.netapp.OntapRestAPI.send_request') + def test_rest_idempotently_disable(self, mock_request): + data = self.mock_args() + data['enable'] = False + set_module_args(data) + mock_request.side_effect = [ + SRR['is_rest'], + SRR['disabled'], + SRR['end_of_sequence'] + ] + with pytest.raises(AnsibleExitJson) as exc: + self.get_vscan_mock_object(cx_type='rest').apply() + assert not exc.value.args[0]['changed'] diff --git a/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_vscan_on_access_policy.py b/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_vscan_on_access_policy.py new file mode 100644 index 00000000..c595e73e --- /dev/null +++ b/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_vscan_on_access_policy.py @@ -0,0 +1,159 @@ +# (c) 2018, NetApp, Inc +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +''' unit tests for Ansible module: na_ontap_vscan_scanner_pool ''' + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type +import json +import pytest + +from ansible_collections.netapp.ontap.tests.unit.compat import unittest +from ansible_collections.netapp.ontap.tests.unit.compat.mock import patch, Mock +from ansible.module_utils import basic +from ansible.module_utils._text import to_bytes +import ansible_collections.netapp.ontap.plugins.module_utils.netapp as netapp_utils + +from ansible_collections.netapp.ontap.plugins.modules.na_ontap_vscan_on_access_policy \ + import NetAppOntapVscanOnAccessPolicy as policy_module # module under test + +if not netapp_utils.has_netapp_lib(): + pytestmark = pytest.mark.skip('skipping as missing required netapp_lib') +HAS_NETAPP_ZAPI_MSG = "pip install netapp_lib is required" + + +def set_module_args(args): + """prepare arguments so that they will be picked up during module creation""" + args = json.dumps({'ANSIBLE_MODULE_ARGS': args}) + basic._ANSIBLE_ARGS = to_bytes(args) # pylint: disable=protected-access + + +class AnsibleExitJson(Exception): + """Exception class to be raised by module.exit_json and caught by the test case""" + pass + + +class AnsibleFailJson(Exception): + """Exception class to be raised by module.fail_json and caught by the test case""" + pass + + +def exit_json(*args, **kwargs): # pylint: disable=unused-argument + """function to patch over exit_json; package return data into an exception""" + if 'changed' not in kwargs: + kwargs['changed'] = False + raise AnsibleExitJson(kwargs) + + +def fail_json(*args, **kwargs): # pylint: disable=unused-argument + """function to patch over fail_json; package return data into an exception""" + kwargs['failed'] = True + raise AnsibleFailJson(kwargs) + + +class MockONTAPConnection(object): + ''' mock server connection to ONTAP host ''' + + def __init__(self, kind=None, data=None): + ''' save arguments ''' + self.kind = kind + self.params = data + self.xml_in = None + self.xml_out = None + + def invoke_successfully(self, xml, enable_tunneling): # pylint: disable=unused-argument + ''' mock invoke_successfully returning xml data ''' + self.xml_in = xml + if self.kind == 'policy': + xml = self.build_access_policy_info(self.params) + self.xml_out = xml + return xml + + @staticmethod + def build_access_policy_info(policy_details): + xml = netapp_utils.zapi.NaElement('xml') + attributes = {'num-records': 1, + 'attributes-list': {'vscan-on-access-policy-info': {'policy-name': policy_details['policy_name']}}} + xml.translate_struct(attributes) + return xml + + +class TestMyModule(unittest.TestCase): + ''' Unit tests for na_ontap_job_schedule ''' + + def setUp(self): + self.mock_module_helper = patch.multiple(basic.AnsibleModule, + exit_json=exit_json, + fail_json=fail_json) + self.mock_module_helper.start() + self.addCleanup(self.mock_module_helper.stop) + self.mock_access_policy = { + 'state': 'present', + 'vserver': 'test_vserver', + 'policy_name': 'test_carchi', + 'max_file_size': 2147483648 + 1 # 2GB + 1 + } + + def mock_args(self): + return { + 'state': self.mock_access_policy['state'], + 'vserver': self.mock_access_policy['vserver'], + 'policy_name': self.mock_access_policy['policy_name'], + 'max_file_size': self.mock_access_policy['max_file_size'], + 'hostname': 'test', + 'username': 'test_user', + 'password': 'test_pass!' + } + + def get_policy_mock_object(self, kind=None): + policy_obj = policy_module() + if kind is None: + policy_obj.server = MockONTAPConnection() + else: + policy_obj.server = MockONTAPConnection(kind='policy', data=self.mock_access_policy) + return policy_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({}) + policy_module() + print('Info: %s' % exc.value.args[0]['msg']) + + def test_get_nonexistent_policy(self): + set_module_args(self.mock_args()) + result = self.get_policy_mock_object().exists_access_policy() + assert not result + + def test_get_existing_scanner(self): + set_module_args(self.mock_args()) + result = self.get_policy_mock_object('policy').exists_access_policy() + assert result + + def test_successfully_create(self): + set_module_args(self.mock_args()) + with pytest.raises(AnsibleExitJson) as exc: + self.get_policy_mock_object().apply() + assert exc.value.args[0]['changed'] + + def test_create_idempotency(self): + set_module_args(self.mock_args()) + with pytest.raises(AnsibleExitJson) as exc: + self.get_policy_mock_object('policy').apply() + assert exc.value.args[0]['changed'] + + def test_successfully_delete(self): + data = self.mock_args() + data['state'] = 'absent' + set_module_args(data) + with pytest.raises(AnsibleExitJson) as exc: + self.get_policy_mock_object('policy').apply() + assert exc.value.args[0]['changed'] + + def test_delete_idempotency(self): + data = self.mock_args() + data['state'] = 'absent' + set_module_args(data) + with pytest.raises(AnsibleExitJson) as exc: + self.get_policy_mock_object().apply() + assert not exc.value.args[0]['changed'] diff --git a/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_vscan_on_demand_task.py b/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_vscan_on_demand_task.py new file mode 100644 index 00000000..a39e3732 --- /dev/null +++ b/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_vscan_on_demand_task.py @@ -0,0 +1,168 @@ +''' unit tests for Ansible module: na_ontap_vscan_on_demand_task ''' + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type +import json +import pytest + +from ansible_collections.netapp.ontap.tests.unit.compat import unittest +from ansible_collections.netapp.ontap.tests.unit.compat.mock import patch, Mock +from ansible.module_utils import basic +from ansible.module_utils._text import to_bytes +import ansible_collections.netapp.ontap.plugins.module_utils.netapp as netapp_utils + +from ansible_collections.netapp.ontap.plugins.modules.na_ontap_vscan_on_demand_task \ + import NetAppOntapVscanOnDemandTask as onDemand_module # module under test + +if not netapp_utils.has_netapp_lib(): + pytestmark = pytest.mark.skip('skipping as missing required netapp_lib') + + +def set_module_args(args): + """prepare arguments so that they will be picked up during module creation""" + args = json.dumps({'ANSIBLE_MODULE_ARGS': args}) + basic._ANSIBLE_ARGS = to_bytes(args) # pylint: disable=protected-access + + +class AnsibleExitJson(Exception): + """Exception class to be raised by module.exit_json and caught by the test case""" + pass + + +class AnsibleFailJson(Exception): + """Exception class to be raised by module.fail_json and caught by the test case""" + pass + + +def exit_json(*args, **kwargs): # pylint: disable=unused-argument + """function to patch over exit_json; package return data into an exception""" + if 'changed' not in kwargs: + kwargs['changed'] = False + raise AnsibleExitJson(kwargs) + + +def fail_json(*args, **kwargs): # pylint: disable=unused-argument + """function to patch over fail_json; package return data into an exception""" + kwargs['failed'] = True + raise AnsibleFailJson(kwargs) + + +class MockONTAPConnection(object): + ''' mock server connection to ONTAP host ''' + + def __init__(self, kind=None, data=None): + ''' save arguments ''' + self.kind = kind + self.params = data + self.xml_in = None + self.xml_out = None + + def invoke_successfully(self, xml, enable_tunneling): # pylint: disable=unused-argument + ''' mock invoke_successfully returning xml data ''' + self.xml_in = xml + if self.kind == 'task': + xml = self.build_onDemand_pool_info(self.params) + self.xml_out = xml + return xml + + @staticmethod + def build_onDemand_pool_info(onDemand_details): + xml = netapp_utils.zapi.NaElement('xml') + attributes = { + 'num-records': 1, + 'attributes-list': { + 'vscan-on-demand-task-info': { + 'task-name': onDemand_details['task_name'], + 'report-directory': onDemand_details['report_directory'], + 'scan-paths': { + 'string': onDemand_details['scan_paths'] + } + } + } + } + xml.translate_struct(attributes) + return xml + + +class TestMyModule(unittest.TestCase): + ''' Unit tests for na_ontap_job_schedule ''' + + def setUp(self): + self.mock_module_helper = patch.multiple(basic.AnsibleModule, + exit_json=exit_json, + fail_json=fail_json) + self.mock_module_helper.start() + self.addCleanup(self.mock_module_helper.stop) + self.mock_onDemand = { + 'state': 'present', + 'vserver': 'test_vserver', + 'report_directory': '/', + 'task_name': '/', + 'scan_paths': '/' + } + + def mock_args(self): + return { + 'state': self.mock_onDemand['state'], + 'vserver': self.mock_onDemand['vserver'], + 'report_directory': self.mock_onDemand['report_directory'], + 'task_name': self.mock_onDemand['task_name'], + 'scan_paths': self.mock_onDemand['scan_paths'], + 'hostname': 'test', + 'username': 'test_user', + 'password': 'test_pass!' + } + + def get_demand_mock_object(self, kind=None): + scanner_obj = onDemand_module() + scanner_obj.asup_log_for_cserver = Mock(return_value=None) + if kind is None: + scanner_obj.server = MockONTAPConnection() + else: + scanner_obj.server = MockONTAPConnection(kind='task', data=self.mock_onDemand) + return scanner_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({}) + onDemand_module() + print('Info: %s' % exc.value.args[0]['msg']) + + def test_get_nonexistent_demand_task(self): + set_module_args(self.mock_args()) + result = self.get_demand_mock_object().get_demand_task() + assert not result + + def test_get_existing_demand_task(self): + set_module_args(self.mock_args()) + result = self.get_demand_mock_object('task').get_demand_task() + assert result + + def test_successfully_create(self): + set_module_args(self.mock_args()) + with pytest.raises(AnsibleExitJson) as exc: + self.get_demand_mock_object().apply() + assert exc.value.args[0]['changed'] + + def test_create_idempotency(self): + set_module_args(self.mock_args()) + with pytest.raises(AnsibleExitJson) as exc: + self.get_demand_mock_object('task').apply() + assert not exc.value.args[0]['changed'] + + def test_successfully_delete(self): + data = self.mock_args() + data['state'] = 'absent' + set_module_args(data) + with pytest.raises(AnsibleExitJson) as exc: + self.get_demand_mock_object('task').apply() + assert exc.value.args[0]['changed'] + + def test_delete_idempotency(self): + data = self.mock_args() + data['state'] = 'absent' + set_module_args(data) + with pytest.raises(AnsibleExitJson) as exc: + self.get_demand_mock_object().apply() + assert not exc.value.args[0]['changed'] diff --git a/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_vscan_scanner_pool.py b/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_vscan_scanner_pool.py new file mode 100644 index 00000000..a1aae605 --- /dev/null +++ b/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_vscan_scanner_pool.py @@ -0,0 +1,188 @@ +# (c) 2018, NetApp, Inc +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +''' unit tests for Ansible module: na_ontap_vscan_scanner_pool ''' + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type +import json +import pytest + +from ansible_collections.netapp.ontap.tests.unit.compat import unittest +from ansible_collections.netapp.ontap.tests.unit.compat.mock import patch, Mock +from ansible.module_utils import basic +from ansible.module_utils._text import to_bytes +import ansible_collections.netapp.ontap.plugins.module_utils.netapp as netapp_utils + +from ansible_collections.netapp.ontap.plugins.modules.na_ontap_vscan_scanner_pool \ + import NetAppOntapVscanScannerPool as scanner_module # module under test + +if not netapp_utils.has_netapp_lib(): + pytestmark = pytest.mark.skip('skipping as missing required netapp_lib') + + +def set_module_args(args): + """prepare arguments so that they will be picked up during module creation""" + args = json.dumps({'ANSIBLE_MODULE_ARGS': args}) + basic._ANSIBLE_ARGS = to_bytes(args) # pylint: disable=protected-access + + +class AnsibleExitJson(Exception): + """Exception class to be raised by module.exit_json and caught by the test case""" + pass + + +class AnsibleFailJson(Exception): + """Exception class to be raised by module.fail_json and caught by the test case""" + pass + + +def exit_json(*args, **kwargs): # pylint: disable=unused-argument + """function to patch over exit_json; package return data into an exception""" + if 'changed' not in kwargs: + kwargs['changed'] = False + raise AnsibleExitJson(kwargs) + + +def fail_json(*args, **kwargs): # pylint: disable=unused-argument + """function to patch over fail_json; package return data into an exception""" + kwargs['failed'] = True + raise AnsibleFailJson(kwargs) + + +class MockONTAPConnection(object): + ''' mock server connection to ONTAP host ''' + + def __init__(self, kind=None, data=None): + ''' save arguments ''' + self.kind = kind + self.params = data + self.xml_in = None + self.xml_out = None + + def invoke_successfully(self, xml, enable_tunneling): # pylint: disable=unused-argument + ''' mock invoke_successfully returning xml data ''' + self.xml_in = xml + if self.kind == 'scanner': + xml = self.build_scanner_pool_info(self.params) + self.xml_out = xml + return xml + + @staticmethod + def build_scanner_pool_info(sanner_details): + xml = netapp_utils.zapi.NaElement('xml') + attributes = { + 'num-records': 1, + 'attributes-list': { + 'vscan-scanner-pool-info': { + 'scanner-pool': sanner_details['scanner_pool'], + 'scanner-policy': sanner_details['scanner_policy'], + 'hostnames': [ + {'hostname': sanner_details['hostnames'][0]}, + {'hostname': sanner_details['hostnames'][1]} + ], + 'privileged-users': [ + {"privileged-user": sanner_details['privileged_users'][0]}, + {"privileged-user": sanner_details['privileged_users'][1]} + ] + } + } + } + xml.translate_struct(attributes) + return xml + + +class TestMyModule(unittest.TestCase): + ''' Unit tests for na_ontap_job_schedule ''' + + def setUp(self): + self.mock_module_helper = patch.multiple(basic.AnsibleModule, + exit_json=exit_json, + fail_json=fail_json) + self.mock_module_helper.start() + self.addCleanup(self.mock_module_helper.stop) + self.mock_scanner = { + 'state': 'present', + 'scanner_pool': 'test_pool', + 'vserver': 'test_vserver', + 'hostnames': ['host1', 'host2'], + 'privileged_users': ['domain\\admin', 'domain\\carchi8py'], + 'scanner_policy': 'primary' + } + + def mock_args(self): + return { + 'state': self.mock_scanner['state'], + 'scanner_pool': self.mock_scanner['scanner_pool'], + 'vserver': self.mock_scanner['vserver'], + 'hostnames': self.mock_scanner['hostnames'], + 'privileged_users': self.mock_scanner['privileged_users'], + 'hostname': 'test', + 'username': 'test_user', + 'password': 'test_pass!', + 'scanner_policy': self.mock_scanner['scanner_policy'] + } + + def get_scanner_mock_object(self, kind=None): + scanner_obj = scanner_module() + scanner_obj.asup_log_for_cserver = Mock(return_value=None) + if kind is None: + scanner_obj.server = MockONTAPConnection() + else: + scanner_obj.server = MockONTAPConnection(kind='scanner', data=self.mock_scanner) + return scanner_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({}) + scanner_module() + print('Info: %s' % exc.value.args[0]['msg']) + + def test_get_nonexistent_scanner(self): + ''' Test if get_scanner_pool returns None for non-existent job ''' + set_module_args(self.mock_args()) + result = self.get_scanner_mock_object().get_scanner_pool() + assert not result + + def test_get_existing_scanner(self): + ''' Test if get_scanner_pool returns None for non-existent job ''' + set_module_args(self.mock_args()) + result = self.get_scanner_mock_object('scanner').get_scanner_pool() + assert result + + def test_successfully_create(self): + set_module_args(self.mock_args()) + with pytest.raises(AnsibleExitJson) as exc: + self.get_scanner_mock_object().apply() + assert exc.value.args[0]['changed'] + + def test_create_idempotency(self): + set_module_args(self.mock_args()) + with pytest.raises(AnsibleExitJson) as exc: + self.get_scanner_mock_object('scanner').apply() + assert not exc.value.args[0]['changed'] + + def test_successfully_delete(self): + data = self.mock_args() + data['state'] = 'absent' + set_module_args(data) + with pytest.raises(AnsibleExitJson) as exc: + self.get_scanner_mock_object('scanner').apply() + assert exc.value.args[0]['changed'] + + def test_delete_idempotency(self): + data = self.mock_args() + data['state'] = 'absent' + set_module_args(data) + with pytest.raises(AnsibleExitJson) as exc: + self.get_scanner_mock_object().apply() + assert not exc.value.args[0]['changed'] + + def test_successfully_modify(self): + data = self.mock_args() + data['hostnames'] = "host1" + set_module_args(data) + with pytest.raises(AnsibleExitJson) as exc: + self.get_scanner_mock_object('scanner').apply() + assert exc.value.args[0]['changed'] diff --git a/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_vserver_cifs_security.py b/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_vserver_cifs_security.py new file mode 100644 index 00000000..ccc48b24 --- /dev/null +++ b/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_vserver_cifs_security.py @@ -0,0 +1,166 @@ +# (c) 2019, NetApp, Inc +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +''' unit test template for ONTAP Ansible module ''' + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type +import json +import pytest + +from ansible_collections.netapp.ontap.tests.unit.compat import unittest +from ansible_collections.netapp.ontap.tests.unit.compat.mock import patch, Mock +from ansible.module_utils import basic +from ansible.module_utils._text import to_bytes +import ansible_collections.netapp.ontap.plugins.module_utils.netapp as netapp_utils + +from ansible_collections.netapp.ontap.plugins.modules.na_ontap_vserver_cifs_security \ + import NetAppONTAPCifsSecurity as cifs_security_module # module under test + +if not netapp_utils.has_netapp_lib(): + pytestmark = pytest.mark.skip('skipping as missing required netapp_lib') + + +def set_module_args(args): + """prepare arguments so that they will be picked up during module creation""" + args = json.dumps({'ANSIBLE_MODULE_ARGS': args}) + basic._ANSIBLE_ARGS = to_bytes(args) # pylint: disable=protected-access + + +class AnsibleExitJson(Exception): + """Exception class to be raised by module.exit_json and caught by the test case""" + pass + + +class AnsibleFailJson(Exception): + """Exception class to be raised by module.fail_json and caught by the test case""" + pass + + +def exit_json(*args, **kwargs): # pylint: disable=unused-argument + """function to patch over exit_json; package return data into an exception""" + if 'changed' not in kwargs: + kwargs['changed'] = False + raise AnsibleExitJson(kwargs) + + +def fail_json(*args, **kwargs): # pylint: disable=unused-argument + """function to patch over fail_json; package return data into an exception""" + kwargs['failed'] = True + raise AnsibleFailJson(kwargs) + + +class MockONTAPConnection(object): + ''' mock server connection to ONTAP host ''' + + def __init__(self, kind=None, data=None): + ''' save arguments ''' + self.type = kind + self.data = data + self.xml_in = None + self.xml_out = None + + def invoke_successfully(self, xml, enable_tunneling): # pylint: disable=unused-argument + ''' mock invoke_successfully returning xml data ''' + self.xml_in = xml + if self.type == 'cifs_security': + xml = self.build_security_info(self.data) + if self.type == 'error': + error = netapp_utils.zapi.NaApiError('test', 'error') + raise error + self.xml_out = xml + return xml + + @staticmethod + def build_security_info(cifs_security_details): + ''' build xml data for cifs-security ''' + xml = netapp_utils.zapi.NaElement('xml') + attributes = { + 'num-records': 1, + 'attributes-list': { + 'cifs-security': { + 'is-aes-encryption-enabled': str(cifs_security_details['is_aes_encryption_enabled']).lower(), + 'lm-compatibility-level': cifs_security_details['lm_compatibility_level'], + 'kerberos-clock-skew': str(cifs_security_details['kerberos_clock_skew']) + } + } + } + xml.translate_struct(attributes) + return xml + + +class TestMyModule(unittest.TestCase): + ''' a group of related Unit Tests ''' + + def setUp(self): + self.mock_module_helper = patch.multiple(basic.AnsibleModule, + exit_json=exit_json, + fail_json=fail_json) + self.mock_module_helper.start() + self.addCleanup(self.mock_module_helper.stop) + self.mock_cifs_security = { + 'is_aes_encryption_enabled': True, + 'lm_compatibility_level': 'krb', + 'kerberos_clock_skew': 10 + } + + def mock_args(self): + return { + 'is_aes_encryption_enabled': self.mock_cifs_security['is_aes_encryption_enabled'], + 'lm_compatibility_level': self.mock_cifs_security['lm_compatibility_level'], + 'kerberos_clock_skew': self.mock_cifs_security['kerberos_clock_skew'], + 'vserver': 'ansible', + 'hostname': 'test', + 'username': 'test_user', + 'password': 'test_pass!', + 'https': 'False' + } + + def get_cifs_security_mock_object(self, kind=None): + """ + Helper method to return an na_ontap_vserver_cifs_security object + :param kind: passes this param to MockONTAPConnection() + :return: na_ontap_vserver_cifs_security object + """ + obj = cifs_security_module() + obj.asup_log_for_cserver = Mock(return_value=None) + obj.server = Mock() + obj.server.invoke_successfully = Mock() + if kind is None: + obj.server = MockONTAPConnection() + else: + obj.server = MockONTAPConnection(kind=kind, data=self.mock_cifs_security) + return obj + + def test_successful_modify_int_option(self): + ''' Test successful modify kerberos_clock_skew ''' + data = self.mock_args() + data['kerberos_clock_skew'] = 15 + set_module_args(data) + with pytest.raises(AnsibleExitJson) as exc: + self.get_cifs_security_mock_object('cifs_security').apply() + assert exc.value.args[0]['changed'] + + def test_successful_modify_bool_option(self): + ''' Test successful modify is_aes_encryption_enabled ''' + data = self.mock_args() + data['is_aes_encryption_enabled'] = False + set_module_args(data) + with pytest.raises(AnsibleExitJson) as exc: + self.get_cifs_security_mock_object('cifs_security').apply() + assert exc.value.args[0]['changed'] + + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_vserver_cifs_security.NetAppONTAPCifsSecurity.cifs_security_get_iter') + def test_modify_error(self, get_cifs_security): + ''' Test modify error ''' + data = self.mock_args() + set_module_args(data) + current = { + 'is_aes_encryption_enabled': False + } + get_cifs_security.side_effect = [ + current + ] + with pytest.raises(AnsibleFailJson) as exc: + self.get_cifs_security_mock_object('error').apply() + assert exc.value.args[0]['msg'] == 'Error modifying cifs security on ansible: NetApp API failed. Reason - test:error' diff --git a/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_vserver_peer.py b/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_vserver_peer.py new file mode 100644 index 00000000..01523aac --- /dev/null +++ b/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_vserver_peer.py @@ -0,0 +1,250 @@ +# (c) 2018, NetApp, Inc +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +''' unit test template for ONTAP Ansible module ''' + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import json +import pytest + +from ansible_collections.netapp.ontap.tests.unit.compat import unittest +from ansible_collections.netapp.ontap.tests.unit.compat.mock import patch, Mock +from ansible.module_utils import basic +from ansible.module_utils._text import to_bytes +import ansible_collections.netapp.ontap.plugins.module_utils.netapp as netapp_utils + +from ansible_collections.netapp.ontap.plugins.modules.na_ontap_vserver_peer \ + import NetAppONTAPVserverPeer as vserver_peer # module under test + +if not netapp_utils.has_netapp_lib(): + pytestmark = pytest.mark.skip('skipping as missing required netapp_lib') + + +def set_module_args(args): + """prepare arguments so that they will be picked up during module creation""" + args = json.dumps({'ANSIBLE_MODULE_ARGS': args}) + basic._ANSIBLE_ARGS = to_bytes(args) # pylint: disable=protected-access + + +class AnsibleExitJson(Exception): + """Exception class to be raised by module.exit_json and caught by the test case""" + pass + + +class AnsibleFailJson(Exception): + """Exception class to be raised by module.fail_json and caught by the test case""" + pass + + +def exit_json(*args, **kwargs): # pylint: disable=unused-argument + """function to patch over exit_json; package return data into an exception""" + if 'changed' not in kwargs: + kwargs['changed'] = False + raise AnsibleExitJson(kwargs) + + +def fail_json(*args, **kwargs): # pylint: disable=unused-argument + """function to patch over fail_json; package return data into an exception""" + kwargs['failed'] = True + raise AnsibleFailJson(kwargs) + + +class MockONTAPConnection(object): + ''' mock server connection to ONTAP host ''' + + def __init__(self, kind=None, data=None): + ''' save arguments ''' + self.kind = kind + self.data = data + self.xml_in = None + self.xml_out = None + + def invoke_successfully(self, xml, enable_tunneling): # pylint: disable=unused-argument + ''' mock invoke_successfully returning xml data ''' + self.xml_in = xml + if self.kind == 'vserver_peer': + xml = self.build_vserver_peer_info(self.data) + if self.kind == 'cluster': + xml = self.build_cluster_info(self.data) + self.xml_out = xml + return xml + + @staticmethod + def build_vserver_peer_info(vserver): + ''' build xml data for vserser-peer-info ''' + xml = netapp_utils.zapi.NaElement('xml') + attributes = { + 'num-records': 1, + 'attributes-list': { + 'vserver-peer-info': { + 'peer-vserver': vserver['peer_vserver'], + 'vserver': vserver['vserver'], + 'peer-state': 'peered' + } + } + } + xml.translate_struct(attributes) + print(xml.to_string()) + return xml + + @staticmethod + def build_cluster_info(vserver): + ''' build xml data for cluster-identity-get ''' + xml = netapp_utils.zapi.NaElement('xml') + attributes = { + 'attributes': { + 'cluster-identity-info': { + 'cluster-name': vserver['peer_cluster'] + } + } + } + xml.translate_struct(attributes) + print(xml.to_string()) + return xml + + +class TestMyModule(unittest.TestCase): + ''' a group of related Unit Tests ''' + + def setUp(self): + self.mock_module_helper = patch.multiple(basic.AnsibleModule, + exit_json=exit_json, + fail_json=fail_json) + self.mock_module_helper.start() + self.addCleanup(self.mock_module_helper.stop) + self.mock_vserver_peer = { + 'vserver': 'test', + 'peer_vserver': 'test_peer', + 'peer_cluster': 'test_cluster_peer', + 'applications': ['snapmirror'], + 'hostname': 'hostname', + 'dest_hostname': 'hostname', + 'username': 'username', + 'password': 'password', + + } + + def get_vserver_peer_mock_object(self, kind=None): + """ + Helper method to return an na_ontap_vserver_peer object + :param kind: passes this param to MockONTAPConnection() + :return: na_ontap_vserver_peer object + """ + vserver_peer_obj = vserver_peer() + vserver_peer_obj.asup_log_for_cserver = Mock(return_value=None) + if kind is None: + vserver_peer_obj.server = MockONTAPConnection() + vserver_peer_obj.dest_server = MockONTAPConnection() + else: + vserver_peer_obj.server = MockONTAPConnection(kind=kind, data=self.mock_vserver_peer) + vserver_peer_obj.dest_server = MockONTAPConnection(kind=kind, data=self.mock_vserver_peer) + return vserver_peer_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({}) + vserver_peer() + print('Info: %s' % exc.value.args[0]['msg']) + + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_vserver_peer.NetAppONTAPVserverPeer.vserver_peer_get') + def test_successful_create(self, vserver_peer_get): + ''' Test successful create ''' + data = self.mock_vserver_peer + data['dest_hostname'] = 'test_destination' + set_module_args(self.mock_vserver_peer) + vserver_peer_get.return_value = None + with pytest.raises(AnsibleExitJson) as exc: + self.get_vserver_peer_mock_object().apply() + assert exc.value.args[0]['changed'] + + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_vserver_peer.NetAppONTAPVserverPeer.vserver_peer_get') + def test_create_idempotency(self, vserver_peer_get): + ''' Test create idempotency ''' + data = self.mock_vserver_peer + data['dest_hostname'] = 'test_destination' + set_module_args(self.mock_vserver_peer) + current = { + 'vserver': 'test', + 'peer_vserver': self.mock_vserver_peer['peer_vserver'], + 'peer_cluster': self.mock_vserver_peer['peer_cluster'] + } + vserver_peer_get.return_value = current + with pytest.raises(AnsibleExitJson) as exc: + self.get_vserver_peer_mock_object('vserver_peer').apply() + assert not exc.value.args[0]['changed'] + + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_vserver_peer.NetAppONTAPVserverPeer.vserver_peer_get') + def test_successful_delete(self, vserver_peer_get): + ''' Test successful delete peer ''' + data = self.mock_vserver_peer + data['state'] = 'absent' + set_module_args(data) + vserver_peer_get.return_value = Mock() + with pytest.raises(AnsibleExitJson) as exc: + self.get_vserver_peer_mock_object('vserver_peer').apply() + assert exc.value.args[0]['changed'] + + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_vserver_peer.NetAppONTAPVserverPeer.vserver_peer_get') + def test_delete_idempotency(self, vserver_peer_get): + ''' Test delete idempotency ''' + data = self.mock_vserver_peer + data['state'] = 'absent' + set_module_args(data) + vserver_peer_get.return_value = None + with pytest.raises(AnsibleExitJson) as exc: + self.get_vserver_peer_mock_object().apply() + assert not exc.value.args[0]['changed'] + + def test_helper_vserver_peer_get_iter(self): + ''' Test vserver_peer_get_iter method ''' + set_module_args(self.mock_vserver_peer) + obj = self.get_vserver_peer_mock_object('vserver_peer') + result = obj.vserver_peer_get_iter() + print(result.to_string(pretty=True)) + assert result['query'] is not None + assert result['query']['vserver-peer-info'] is not None + info = result['query']['vserver-peer-info'] + assert info['vserver'] == self.mock_vserver_peer['vserver'] + assert info['peer-vserver'] == self.mock_vserver_peer['peer_vserver'] + + def test_get_packet(self): + ''' Test vserver_peer_get method ''' + set_module_args(self.mock_vserver_peer) + obj = self.get_vserver_peer_mock_object('vserver_peer') + result = obj.vserver_peer_get() + assert 'vserver' in result.keys() + assert 'peer_vserver' in result.keys() + assert 'peer_state' in result.keys() + + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_vserver_peer.NetAppONTAPVserverPeer.vserver_peer_get') + def test_error_on_missing_params_create(self, vserver_peer_get): + ''' Test error thrown from vserver_peer_create ''' + data = self.mock_vserver_peer + del data['applications'] + set_module_args(data) + vserver_peer_get.return_value = None + with pytest.raises(AnsibleFailJson) as exc: + self.get_vserver_peer_mock_object().apply() + assert exc.value.args[0]['msg'] == "applications parameter is missing" + + @patch('ansible_collections.netapp.ontap.plugins.modules.na_ontap_vserver_peer.NetAppONTAPVserverPeer.get_peer_cluster_name') + def test_get_peer_cluster_called(self, cluster_peer_get): + ''' Test get_peer_cluster_name called if peer_cluster is missing ''' + data = self.mock_vserver_peer + del data['peer_cluster'] + set_module_args(data) + cluster_peer_get.return_value = 'something' + with pytest.raises(AnsibleExitJson) as exc: + self.get_vserver_peer_mock_object().apply() + assert cluster_peer_get.call_count == 1 + + def test_get_peer_cluster_packet(self): + ''' Test get_peer_cluster_name xml packet ''' + data = self.mock_vserver_peer + set_module_args(data) + obj = self.get_vserver_peer_mock_object('cluster') + result = obj.get_peer_cluster_name() + assert result == data['peer_cluster'] diff --git a/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_wwpn_alias.py b/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_wwpn_alias.py new file mode 100644 index 00000000..3dc513ff --- /dev/null +++ b/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_wwpn_alias.py @@ -0,0 +1,224 @@ +# (c) 2020, NetApp, Inc +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +''' unit tests for Ansible module: na_ontap_wwpn_alias ''' + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type +import json +import pytest + +from ansible.module_utils import basic +from ansible.module_utils._text import to_bytes +from ansible_collections.netapp.ontap.tests.unit.compat import unittest +from ansible_collections.netapp.ontap.tests.unit.compat.mock import patch + +from ansible_collections.netapp.ontap.plugins.modules.na_ontap_wwpn_alias \ + import NetAppOntapWwpnAlias as alias_module # module under test + +# REST API canned responses when mocking send_request +SRR = { + # common responses + 'is_rest': (200, {}, None), + 'is_zapi': (400, {}, "Unreachable"), + 'empty_good': (200, {}, None), + 'end_of_sequence': (500, None, "Unexpected call to send_request"), + 'generic_error': (400, None, "Expected error"), + # module specific responses + 'get_alias': ( + 200, + {"records": [{ + "svm": { + "uuid": "uuid", + "name": "svm"}, + "alias": "host1", + "wwpn": "01:02:03:04:0a:0b:0c:0d"}], + "num_records": 1}, None), + 'get_svm_uuid': ( + 200, + {"records": [{ + "uuid": "test_uuid" + }]}, None), + "no_record": ( + 200, + {"num_records": 0}, + None) +} + + +def set_module_args(args): + """prepare arguments so that they will be picked up during module creation""" + args = json.dumps({'ANSIBLE_MODULE_ARGS': args}) + basic._ANSIBLE_ARGS = to_bytes(args) # pylint: disable=protected-access + + +class AnsibleExitJson(Exception): + """Exception class to be raised by module.exit_json and caught by the test case""" + + +class AnsibleFailJson(Exception): + """Exception class to be raised by module.fail_json and caught by the test case""" + + +def exit_json(*args, **kwargs): # pylint: disable=unused-argument + """function to patch over exit_json; package return data into an exception""" + if 'changed' not in kwargs: + kwargs['changed'] = False + raise AnsibleExitJson(kwargs) + + +def fail_json(*args, **kwargs): # pylint: disable=unused-argument + """function to patch over fail_json; package return data into an exception""" + kwargs['failed'] = True + raise AnsibleFailJson(kwargs) + + +class TestMyModule(unittest.TestCase): + ''' Unit tests for na_ontap_wwpn_alias ''' + + def setUp(self): + self.mock_module_helper = patch.multiple(basic.AnsibleModule, + exit_json=exit_json, + fail_json=fail_json) + self.mock_module_helper.start() + self.addCleanup(self.mock_module_helper.stop) + self.mock_alias = { + 'name': 'host1', + 'vserver': 'test_vserver' + } + + def mock_args(self): + return { + 'vserver': self.mock_alias['vserver'], + 'name': self.mock_alias['name'], + "wwpn": "01:02:03:04:0a:0b:0c:0d", + 'hostname': 'test_host', + 'username': 'test_user', + 'password': 'test_pass!' + } + + def get_alias_mock_object(self): + alias_obj = alias_module() + return alias_obj + + @patch('ansible_collections.netapp.ontap.plugins.module_utils.netapp.OntapRestAPI.send_request') + def test_rest_successful_create(self, mock_request): + '''Test successful rest create''' + data = self.mock_args() + set_module_args(data) + mock_request.side_effect = [ + SRR['is_rest'], + SRR['get_svm_uuid'], + SRR['no_record'], + SRR['empty_good'], + SRR['end_of_sequence'] + ] + with pytest.raises(AnsibleExitJson) as exc: + self.get_alias_mock_object().apply() + assert exc.value.args[0]['changed'] + + @patch('ansible_collections.netapp.ontap.plugins.module_utils.netapp.OntapRestAPI.send_request') + def test_rest_create_idempotency(self, mock_request): + '''Test rest create idempotency''' + data = self.mock_args() + set_module_args(data) + mock_request.side_effect = [ + SRR['is_rest'], + SRR['get_svm_uuid'], + SRR['get_alias'], + SRR['no_record'], + SRR['empty_good'], + SRR['end_of_sequence'] + ] + with pytest.raises(AnsibleExitJson) as exc: + self.get_alias_mock_object().apply() + assert not exc.value.args[0]['changed'] + + @patch('ansible_collections.netapp.ontap.plugins.module_utils.netapp.OntapRestAPI.send_request') + def test_rest_create_error(self, mock_request): + '''Test rest create error''' + data = self.mock_args() + set_module_args(data) + mock_request.side_effect = [ + SRR['is_rest'], + SRR['get_svm_uuid'], + SRR['no_record'], + SRR['generic_error'], + SRR['empty_good'], + SRR['end_of_sequence'] + ] + with pytest.raises(AnsibleFailJson) as exc: + self.get_alias_mock_object().apply() + assert exc.value.args[0]['msg'] == "Error on creating wwpn alias: Expected error." + + @patch('ansible_collections.netapp.ontap.plugins.module_utils.netapp.OntapRestAPI.send_request') + def test_rest_modify(self, mock_request): + '''Test rest modify error''' + data = self.mock_args() + data['wwpn'] = "01:02:03:04:0a:0b:0c:0e" + set_module_args(data) + mock_request.side_effect = [ + SRR['is_rest'], + SRR['get_svm_uuid'], + SRR['get_alias'], + SRR['empty_good'], + SRR['empty_good'], + SRR['end_of_sequence'] + ] + with pytest.raises(AnsibleExitJson) as exc: + self.get_alias_mock_object().apply() + assert exc.value.args[0]['changed'] + + @patch('ansible_collections.netapp.ontap.plugins.module_utils.netapp.OntapRestAPI.send_request') + def test_rest_modify_error_delete(self, mock_request): + '''Test rest modify error''' + data = self.mock_args() + data['wwpn'] = "01:02:03:04:0a:0b:0c:0e" + set_module_args(data) + mock_request.side_effect = [ + SRR['is_rest'], + SRR['get_svm_uuid'], + SRR['get_alias'], + SRR['generic_error'], + SRR['empty_good'], + SRR['end_of_sequence'] + ] + with pytest.raises(AnsibleFailJson) as exc: + self.get_alias_mock_object().apply() + assert exc.value.args[0]['msg'] == "Error on modifying wwpn alias when trying to delete alias: Expected error." + + @patch('ansible_collections.netapp.ontap.plugins.module_utils.netapp.OntapRestAPI.send_request') + def test_rest_modify_error_create(self, mock_request): + '''Test rest modify error''' + data = self.mock_args() + data['wwpn'] = "01:02:03:04:0a:0b:0c:0e" + set_module_args(data) + mock_request.side_effect = [ + SRR['is_rest'], + SRR['get_svm_uuid'], + SRR['get_alias'], + SRR['empty_good'], + SRR['generic_error'], + SRR['end_of_sequence'] + ] + with pytest.raises(AnsibleFailJson) as exc: + self.get_alias_mock_object().apply() + assert exc.value.args[0]['msg'] == "Error on modifying wwpn alias when trying to re-create alias: Expected error." + + @patch('ansible_collections.netapp.ontap.plugins.module_utils.netapp.OntapRestAPI.send_request') + def test_rest_delete_error(self, mock_request): + '''Test rest delete error''' + data = self.mock_args() + data['state'] = 'absent' + set_module_args(data) + mock_request.side_effect = [ + SRR['is_rest'], + SRR['get_svm_uuid'], + SRR['get_alias'], + SRR['generic_error'], + SRR['empty_good'], + SRR['end_of_sequence'] + ] + with pytest.raises(AnsibleFailJson) as exc: + self.get_alias_mock_object().apply() + assert exc.value.args[0]['msg'] == "Error on deleting wwpn alias: Expected error." diff --git a/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/requirements.txt b/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/requirements.txt new file mode 100644 index 00000000..c7897ee3 --- /dev/null +++ b/collections-debian-merged/ansible_collections/netapp/ontap/tests/unit/requirements.txt @@ -0,0 +1,3 @@ +netapp-lib +solidfire-sdk-python +unittest2 ; python_version < '2.7' |