diff options
Diffstat (limited to 'ansible_collections/netapp/storagegrid/tests')
24 files changed, 7024 insertions, 0 deletions
diff --git a/ansible_collections/netapp/storagegrid/tests/unit/compat/__init__.py b/ansible_collections/netapp/storagegrid/tests/unit/compat/__init__.py new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/ansible_collections/netapp/storagegrid/tests/unit/compat/__init__.py diff --git a/ansible_collections/netapp/storagegrid/tests/unit/compat/builtins.py b/ansible_collections/netapp/storagegrid/tests/unit/compat/builtins.py new file mode 100644 index 000000000..bfc8adfbe --- /dev/null +++ b/ansible_collections/netapp/storagegrid/tests/unit/compat/builtins.py @@ -0,0 +1,34 @@ +# (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/ansible_collections/netapp/storagegrid/tests/unit/compat/mock.py b/ansible_collections/netapp/storagegrid/tests/unit/compat/mock.py new file mode 100644 index 000000000..ce13d07cb --- /dev/null +++ b/ansible_collections/netapp/storagegrid/tests/unit/compat/mock.py @@ -0,0 +1,125 @@ +# (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/ansible_collections/netapp/storagegrid/tests/unit/compat/unittest.py b/ansible_collections/netapp/storagegrid/tests/unit/compat/unittest.py new file mode 100644 index 000000000..73a20cf8c --- /dev/null +++ b/ansible_collections/netapp/storagegrid/tests/unit/compat/unittest.py @@ -0,0 +1,44 @@ +# (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 + +import pytest + +# 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') + + class TestCase: + """ skip everything """ + pytestmark = pytest.mark.skip('Skipping Unit Tests on 2.6 as unittest2 may not be available') +else: + from unittest import * diff --git a/ansible_collections/netapp/storagegrid/tests/unit/plugins/modules/test_na_sg_grid_account.py b/ansible_collections/netapp/storagegrid/tests/unit/plugins/modules/test_na_sg_grid_account.py new file mode 100644 index 000000000..e96697381 --- /dev/null +++ b/ansible_collections/netapp/storagegrid/tests/unit/plugins/modules/test_na_sg_grid_account.py @@ -0,0 +1,380 @@ +# (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 StorageGRID Tenant Ansible module: na_sg_grid_account""" + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type +import json +import pytest +import sys +try: + from requests import Response +except ImportError: + if sys.version_info < (2, 7): + pytestmark = pytest.mark.skip('Skipping Unit Tests on 2.6 as requests is not available') + else: + raise + +from ansible_collections.netapp.storagegrid.tests.unit.compat import unittest +from ansible_collections.netapp.storagegrid.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.storagegrid.plugins.modules.na_sg_grid_account import ( + SgGridAccount as grid_account_module, +) + +# REST API canned responses when mocking send_request +SRR = { + # common responses + "empty_good": ({"data": []}, None), + "end_of_sequence": (None, "Unexpected call to send_request"), + "generic_error": (None, "Expected error"), + "delete_good": ({"code": 204}, None), + "pw_change_good": ({"code": 204}, None), + "grid_accounts": ( + { + "data": [ + { + "name": "TestTenantAccount", + "capabilities": ["management", "s3"], + "policy": { + "useAccountIdentitySource": True, + "allowPlatformServices": False, + "quotaObjectBytes": None, + }, + "id": "12345678901234567890", + } + ] + }, + None, + ), + "grid_account_record": ( + { + "data": { + "name": "TestTenantAccount", + "capabilities": ["management", "s3"], + "policy": { + "useAccountIdentitySource": True, + "allowPlatformServices": False, + "quotaObjectBytes": None, + }, + "id": "12345678901234567890", + } + }, + None, + ), + "grid_account_record_with_quota": ( + { + "data": { + "name": "TestTenantAccount", + "capabilities": ["management", "s3"], + "policy": { + "useAccountIdentitySource": True, + "allowPlatformServices": False, + "quotaObjectBytes": 10737418240, + }, + "id": "12345678901234567890", + } + }, + None, + ), + "grid_account_record_update_quota": ( + { + "data": { + "name": "TestTenantAccount", + "capabilities": ["management", "s3"], + "policy": { + "useAccountIdentitySource": True, + "allowPlatformServices": False, + "quotaObjectBytes": 21474836480, + }, + "id": "12345678901234567890", + } + }, + 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""" + + 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 set_default_args_fail_check(self): + return dict( + { + "name": "TestTenantAccount", + "protocol": "s3", + "management": True, + "use_own_identity_source": True, + "allow_platform_services": False, + "password": "abc123", + "quota_size": 0, + "auth_token": "01234567-5678-9abc-78de-9fgabc123def", + "validate_certs": False, + } + ) + + def set_default_args_pass_check(self): + return dict( + { + "state": "present", + "name": "TestTenantAccount", + "protocol": "s3", + "management": True, + "use_own_identity_source": True, + "allow_platform_services": False, + "password": "abc123", + "quota_size": 0, + "api_url": "gmi.example.com", + "auth_token": "01234567-5678-9abc-78de-9fgabc123def", + "validate_certs": False, + } + ) + + def set_args_create_na_sg_grid_account(self): + return dict( + { + "state": "present", + "name": "TestTenantAccount", + "protocol": "s3", + "management": True, + "use_own_identity_source": True, + "allow_platform_services": False, + "password": "abc123", + "quota_size": 0, + "api_url": "gmi.example.com", + "auth_token": "01234567-5678-9abc-78de-9fgabc123def", + "validate_certs": False, + } + ) + + def set_args_delete_na_sg_grid_account(self): + return dict( + { + "state": "absent", + "name": "TestTenantAccount", + "protocol": "s3", + "api_url": "gmi.example.com", + "auth_token": "01234567-5678-9abc-78de-9fgabc123def", + "validate_certs": 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(self.set_default_args_fail_check()) + grid_account_module() + print( + "Info: test_module_fail_when_required_args_missing: %s" + % exc.value.args[0]["msg"] + ) + + def test_module_fail_when_required_args_present(self): + """ required arguments are reported as errors """ + with pytest.raises(AnsibleExitJson) as exc: + set_module_args(self.set_default_args_pass_check()) + grid_account_module() + exit_json(changed=True, msg="Induced arguments check") + print( + "Info: test_module_fail_when_required_args_present: %s" + % exc.value.args[0]["msg"] + ) + assert exc.value.args[0]["changed"] + + @patch( + "ansible_collections.netapp.storagegrid.plugins.module_utils.netapp.SGRestAPI.send_request" + ) + def test_create_na_sg_grid_account_pass(self, mock_request): + set_module_args(self.set_args_create_na_sg_grid_account()) + my_obj = grid_account_module() + mock_request.side_effect = [ + SRR["empty_good"], # get + SRR["grid_accounts"], # post + SRR["end_of_sequence"], + ] + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + print( + "Info: test_create_na_sg_tenant_account_pass: %s" + % repr(exc.value.args[0]) + ) + assert exc.value.args[0]["changed"] + + @patch( + "ansible_collections.netapp.storagegrid.plugins.module_utils.netapp.SGRestAPI.send_request" + ) + def test_idempotent_create_na_sg_grid_account_pass(self, mock_request): + set_module_args(self.set_args_create_na_sg_grid_account()) + my_obj = grid_account_module() + mock_request.side_effect = [ + SRR["grid_accounts"], # get id + SRR["grid_account_record"], # get account + SRR["end_of_sequence"], + ] + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + print( + "Info: test_idempotent_create_na_sg_tenant_account_pass: %s" + % repr(exc.value.args[0]) + ) + assert not exc.value.args[0]["changed"] + + @patch( + "ansible_collections.netapp.storagegrid.plugins.module_utils.netapp.SGRestAPI.send_request" + ) + def test_update_na_sg_grid_account_pass(self, mock_request): + args = self.set_args_create_na_sg_grid_account() + args["quota_size"] = 10 + set_module_args(args) + my_obj = grid_account_module() + mock_request.side_effect = [ + SRR["grid_accounts"], # get + SRR["grid_account_record"], # get + SRR["grid_account_record_with_quota"], # put + SRR["end_of_sequence"], + ] + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + print( + "Info: test_update_na_sg_tenant_account_pass: %s" + % repr(exc.value.args[0]) + ) + assert exc.value.args[0]["changed"] + + @patch( + "ansible_collections.netapp.storagegrid.plugins.module_utils.netapp.SGRestAPI.send_request" + ) + def test_update_na_sg_grid_account_quota_pass(self, mock_request): + args = self.set_args_create_na_sg_grid_account() + args["quota_size"] = 20480 + args["quota_size_unit"] = "mb" + set_module_args(args) + my_obj = grid_account_module() + mock_request.side_effect = [ + SRR["grid_accounts"], # get + SRR["grid_account_record_with_quota"], # get + SRR["grid_account_record_update_quota"], # put + SRR["end_of_sequence"], + ] + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + print( + "Info: test_create_na_sg_tenant_account_pass: %s" + % repr(exc.value.args[0]) + ) + assert exc.value.args[0]["changed"] + + # update Tenant Account and set pass + @patch( + "ansible_collections.netapp.storagegrid.plugins.module_utils.netapp.SGRestAPI.send_request" + ) + def test_update_na_sg_grid_account_and_set_password_pass(self, mock_request): + args = self.set_args_create_na_sg_grid_account() + args["quota_size"] = 20480 + args["quota_size_unit"] = "mb" + args["update_password"] = "always" + + set_module_args(args) + my_obj = grid_account_module() + mock_request.side_effect = [ + SRR["grid_accounts"], # get + SRR["grid_account_record_with_quota"], # get + SRR["grid_account_record_update_quota"], # put + SRR["pw_change_good"], # post + SRR["end_of_sequence"], + ] + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + print( + "Info: test_update_na_sg_grid_account_and_set_password_pass: %s" + % repr(exc.value.args[0]) + ) + assert exc.value.args[0]["changed"] + + # set pass only + + @patch( + "ansible_collections.netapp.storagegrid.plugins.module_utils.netapp.SGRestAPI.send_request" + ) + def test_set_na_sg_grid_account_root_password_pass(self, mock_request): + args = self.set_args_create_na_sg_grid_account() + args["update_password"] = "always" + + set_module_args(args) + my_obj = grid_account_module() + mock_request.side_effect = [ + SRR["grid_accounts"], # get id + SRR["grid_account_record"], # get account + SRR["pw_change_good"], # post + SRR["end_of_sequence"], + ] + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + print( + "Info: test_set_na_sg_grid_account_root_password_pass: %s" % repr(exc.value.args[0]) + ) + assert exc.value.args[0]["changed"] + + @patch( + "ansible_collections.netapp.storagegrid.plugins.module_utils.netapp.SGRestAPI.send_request" + ) + def test_delete_na_sg_grid_account_pass(self, mock_request): + set_module_args(self.set_args_delete_na_sg_grid_account()) + my_obj = grid_account_module() + mock_request.side_effect = [ + SRR["grid_accounts"], # get + SRR["grid_account_record"], # get + SRR["delete_good"], # delete + SRR["end_of_sequence"], + ] + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + print( + "Info: test_create_na_sg_tenant_account_pass: %s" + % repr(exc.value.args[0]) + ) + assert exc.value.args[0]["changed"] diff --git a/ansible_collections/netapp/storagegrid/tests/unit/plugins/modules/test_na_sg_grid_certificate.py b/ansible_collections/netapp/storagegrid/tests/unit/plugins/modules/test_na_sg_grid_certificate.py new file mode 100644 index 000000000..74974abff --- /dev/null +++ b/ansible_collections/netapp/storagegrid/tests/unit/plugins/modules/test_na_sg_grid_certificate.py @@ -0,0 +1,342 @@ +# (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 StorageGRID Grid Certificate Ansible module: na_sg_grid_certificate""" + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type +import json +import pytest +import sys +try: + from requests import Response +except ImportError: + if sys.version_info < (2, 7): + pytestmark = pytest.mark.skip('Skipping Unit Tests on 2.6 as requests is not available') + else: + raise + +from ansible_collections.netapp.storagegrid.tests.unit.compat import unittest +from ansible_collections.netapp.storagegrid.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.storagegrid.plugins.modules.na_sg_grid_certificate import ( + SgGridCertificate as grid_certificate_module, +) + +# REST API canned responses when mocking send_request +SRR = { + # common responses + "empty_good": ({"data": []}, None), + "not_found": ({"status": "error", "code": 404, "data": {}}, {"key": "error.404"},), + "end_of_sequence": (None, "Unexpected call to send_request"), + "generic_error": (None, "Expected error"), + "delete_good": (None, None), + "update_good": (None, None), + "cert_unset": ({"data": {"serverCertificateEncoded": None, "caBundleEncoded": None}}, None), + "storage_api_cert": ( + { + "data": { + "serverCertificateEncoded": ( + "-----BEGIN CERTIFICATE-----\n" + "MIICyDCCAbACCQCgFntI3q7iADANBgkqhkiG9w0BAQsFADAmMQswCQYDVQQGEwJV\n" + "UzEXMBUGA1UEAwwOczMuZXhhbXBsZS5jb20wHhcNMjEwNDI5MDQ1NTM1WhcNMjIw\n" + "NDI5MDQ1NTM1WjAmMQswCQYDVQQGEwJVUzEXMBUGA1UEAwwOczMuZXhhbXBsZS5j\n" + "b20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQD0LMcJUdWmTtxi7U7B\n" + "yldDRfyCD9W+QJ1Ygm7E9iFwvkThUCV5q+DIcgSfogoSKaQuHaImLXMZn36QC22n\n" + "+Ah2EGrQiggyny3wDzuWf5/Qg7ogqQRqiespBFLlV4RGCREHK0y5uq8mzpIaQ8l8\n" + "STa7nLS7BIc6rD15BJaNWZpDVHIzhljlnhfnqwio/ZfP++lAjk4/j8pPGPEEI5Fe\n" + "WxhOtQjr7xTHeJxKHp2VKiLEvFxniL3qk4uJ3k5fJ7IqALUEPWH92brFp2IkObUA\n" + "EGsZYB4KFV7asBVhGuspYNzUQ6NqWbEUmtTjKEXcb1TA8RK+Pc2TotOrQ2E7Z+rU\n" + "gl2fAgMBAAEwDQYJKoZIhvcNAQELBQADggEBAD5PW1WI7GCfxLQjaitnXpD1MR2O\n" + "6b5csymPYwRejMsSswd8egjs+vO2pbF9TptLjqGliE9XUoI+mWpuMzzd75F0jcjq\n" + "1DhlINgAmjUJEAg0RAqce0Kn8xQF+SofMtkOH+nZm3Q9nbTJKr1H5m2TnCq3v5TH\n" + "Qo0ASf0LLGgrwUtT0IghdSttYLS89dJprZ6c5wK7qeBzxfdHxxjiaSnvByL2Ryn5\n" + "cec9lptYKoRY42hWvkQv9Wkr3DDoyNA3xPdZJr0Hpf8/mSPnt9r/AR8E32xi0SXp\n" + "hOMTDgMicbK82ycxz0yW88gm6yhrChlJrWaEsVGod3FU+lbMAnagYZ/Vwp8=\n" + "-----END CERTIFICATE-----\n" + ), + "caBundleEncoded": None, + } + }, + None, + ), + "storage_api_cert_update": ( + { + "data": { + "serverCertificateEncoded": ( + "-----BEGIN CERTIFICATE-----\n" + "MIICzjCCAbYCCQDZVi1OT89SAjANBgkqhkiG9w0BAQsFADApMQswCQYDVQQGEwJV\n" + "UzEaMBgGA1UEAwwRczMubmV3ZXhhbXBsZS5jb20wHhcNMjEwNDI5MDQ1NzIxWhcN\n" + "MjIwNDI5MDQ1NzIxWjApMQswCQYDVQQGEwJVUzEaMBgGA1UEAwwRczMubmV3ZXhh\n" + "bXBsZS5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCmg37q2sjZ\n" + "k+HsXtai3PSMtGUiqij04JtG9ahMqIejuxy5sDCWnigh//NjdK+wPYc2VfYd6KFA\n" + "Uk9rP84M7sqdqGzIzmyEu7INyCnlbxcXlST6UZDsZnVU7Gk2GvUzk2OoO5N+G0oI\n" + "Lfc/3eKTx9j9BguOaWUy+ni+Te8j6EwK6HolGRBjLYqf1SYFBzaoVpy7pmzaFZ4R\n" + "10jFSxHbotIZ+kR8pPE5jGkP8OjOfrpbhEgmffpeq2MSCMRuhRtRiVp4ULwkMTRN\n" + "tFj89mu1gl9T3lYM/LO1SmBv3il0mNmrTL+99UJ4s2eL0zr/uHAVYJcVqFgWP7X8\n" + "WnOk+d86b0TXAgMBAAEwDQYJKoZIhvcNAQELBQADggEBAFmGV3IOuNYeM3LQxls+\n" + "/CNHznvIqvoiJOWq0S7LFy1eO7PVzCl3l/fDKjGMt2lGXeU89YKdFVPqsainNEFT\n" + "cNEWlezVut+/CWQpBXujyBqPLkYbzyGsakMImDb+MrSkBO5MCjlt38vppm5a97fB\n" + "9o/wM31e+N6gJLiHWs0XB9TK6bY9CvcutcGUOH/oxH1TEBgrJ3SoS7/HmZJSaCQA\n" + "hjZappzuEpGVXT8YDlb67PzUoE2rDWjdSFRXCk/0U6VR0xNgnN1WtfHaypU71DrB\n" + "zxbDaOIZoDp5G4OgjkFxoCoSWLant+LsqEwclIbCFgEvJPE8855UThelTHmIfivP\n" + "veI=\n-----END CERTIFICATE-----\n" + ), + "caBundleEncoded": None, + } + }, + 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""" + + 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 set_default_args_fail_check(self): + return dict( + { + "server_certificate": ( + "-----BEGIN CERTIFICATE-----\n" + "MIICyDCCAbACCQCgFntI3q7iADANBgkqhkiG9w0BAQsFADAmMQswCQYDVQQGEwJV\n" + "UzEXMBUGA1UEAwwOczMuZXhhbXBsZS5jb20wHhcNMjEwNDI5MDQ1NTM1WhcNMjIw\n" + "-----END CERTIFICATE-----\n" + ), + "private_key": ( + "-----BEGIN PRIVATE KEY-----\n" + "MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQD0LMcJUdWmTtxi\n" + "7U7ByldDRfyCD9W+QJ1Ygm7E9iFwvkThUCV5q+DIcgSfogoSKaQuHaImLXMZn36Q\n" + "C22n+Ah2EGrQiggyny3wDzuWf5/Qg7ogqQRqiespBFLlV4RGCREHK0y5uq8mzpIa\n" + "-----END PRIVATE KEY-----\n" + ), + "auth_token": "01234567-5678-9abc-78de-9fgabc123def", + "validate_certs": False, + } + ) + + def set_default_args_pass_check(self): + return dict( + { + "state": "present", + "type": "storage-api", + "server_certificate": ( + "-----BEGIN CERTIFICATE-----\n" + "MIICyDCCAbACCQCgFntI3q7iADANBgkqhkiG9w0BAQsFADAmMQswCQYDVQQGEwJV\n" + "UzEXMBUGA1UEAwwOczMuZXhhbXBsZS5jb20wHhcNMjEwNDI5MDQ1NTM1WhcNMjIw\n" + "-----END CERTIFICATE-----\n" + ), + "private_key": ( + "-----BEGIN PRIVATE KEY-----\n" + "MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQD0LMcJUdWmTtxi\n" + "7U7ByldDRfyCD9W+QJ1Ygm7E9iFwvkThUCV5q+DIcgSfogoSKaQuHaImLXMZn36Q\n" + "C22n+Ah2EGrQiggyny3wDzuWf5/Qg7ogqQRqiespBFLlV4RGCREHK0y5uq8mzpIa\n" + "-----END PRIVATE KEY-----\n" + ), + "api_url": "gmi.example.com", + "auth_token": "01234567-5678-9abc-78de-9fgabc123def", + "validate_certs": False, + } + ) + + def set_args_set_na_sg_grid_storage_api_certificate(self): + return dict( + { + "state": "present", + "type": "storage-api", + "server_certificate": ( + "-----BEGIN CERTIFICATE-----\n" + "MIICyDCCAbACCQCgFntI3q7iADANBgkqhkiG9w0BAQsFADAmMQswCQYDVQQGEwJV\n" + "UzEXMBUGA1UEAwwOczMuZXhhbXBsZS5jb20wHhcNMjEwNDI5MDQ1NTM1WhcNMjIw\n" + "NDI5MDQ1NTM1WjAmMQswCQYDVQQGEwJVUzEXMBUGA1UEAwwOczMuZXhhbXBsZS5j\n" + "b20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQD0LMcJUdWmTtxi7U7B\n" + "yldDRfyCD9W+QJ1Ygm7E9iFwvkThUCV5q+DIcgSfogoSKaQuHaImLXMZn36QC22n\n" + "+Ah2EGrQiggyny3wDzuWf5/Qg7ogqQRqiespBFLlV4RGCREHK0y5uq8mzpIaQ8l8\n" + "STa7nLS7BIc6rD15BJaNWZpDVHIzhljlnhfnqwio/ZfP++lAjk4/j8pPGPEEI5Fe\n" + "WxhOtQjr7xTHeJxKHp2VKiLEvFxniL3qk4uJ3k5fJ7IqALUEPWH92brFp2IkObUA\n" + "EGsZYB4KFV7asBVhGuspYNzUQ6NqWbEUmtTjKEXcb1TA8RK+Pc2TotOrQ2E7Z+rU\n" + "gl2fAgMBAAEwDQYJKoZIhvcNAQELBQADggEBAD5PW1WI7GCfxLQjaitnXpD1MR2O\n" + "6b5csymPYwRejMsSswd8egjs+vO2pbF9TptLjqGliE9XUoI+mWpuMzzd75F0jcjq\n" + "1DhlINgAmjUJEAg0RAqce0Kn8xQF+SofMtkOH+nZm3Q9nbTJKr1H5m2TnCq3v5TH\n" + "Qo0ASf0LLGgrwUtT0IghdSttYLS89dJprZ6c5wK7qeBzxfdHxxjiaSnvByL2Ryn5\n" + "cec9lptYKoRY42hWvkQv9Wkr3DDoyNA3xPdZJr0Hpf8/mSPnt9r/AR8E32xi0SXp\n" + "hOMTDgMicbK82ycxz0yW88gm6yhrChlJrWaEsVGod3FU+lbMAnagYZ/Vwp8=\n" + "-----END CERTIFICATE-----\n" + ), + "private_key": ( + "-----BEGIN PRIVATE KEY-----\n" + "MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQD0LMcJUdWmTtxi\n" + "7U7ByldDRfyCD9W+QJ1Ygm7E9iFwvkThUCV5q+DIcgSfogoSKaQuHaImLXMZn36Q\n" + "C22n+Ah2EGrQiggyny3wDzuWf5/Qg7ogqQRqiespBFLlV4RGCREHK0y5uq8mzpIa\n" + "Q8l8STa7nLS7BIc6rD15BJaNWZpDVHIzhljlnhfnqwio/ZfP++lAjk4/j8pPGPEE\n" + "I5FeWxhOtQjr7xTHeJxKHp2VKiLEvFxniL3qk4uJ3k5fJ7IqALUEPWH92brFp2Ik\n" + "ObUAEGsZYB4KFV7asBVhGuspYNzUQ6NqWbEUmtTjKEXcb1TA8RK+Pc2TotOrQ2E7\n" + "Z+rUgl2fAgMBAAECggEAAwSSqTDTvSx4WNiqAocnsPMqfckIUUOnLjLef5yzKRuQ\n" + "6l/9NpXDP3b5S6fLDBJrrw46tNIW/BgWjl01y7+rCxqE13L9SvLgtHjbua52ITOf\n" + "l0u/fDmcKHOfOqpsPhlaloYYeqsuAwLGl4CC+wBEpuj26uDRcw4x7E78NV8IIxDf\n" + "8kUNPQXI9ox6P3isXrFkMncDfKLWOYJ5fF5zCoVZai/SS8z3FhGjAXlMkay48RX4\n" + "4vuP7TNLZ2O2pAk2aVs54tQyBn9MOxIzOg3/ZFLiKZR4pY6H5sm+bT263TdvN+A4\n" + "C8kwML5HnsCjVkTzJ/3dYc9SeUOuqvJI332GCQ9YcQKBgQD8Ev2qhS61kZ3WGO6G\n" + "DRkZ6tDyt5vCuzWQ8uAAXcAerFDWN6XtDPfXq2UVcWnoCQOUpnjslCb/NJgCetLh\n" + "mOPeJGRWyMly+YuYb4/rnbwSbUs28PO4D9B/f5YQBnBjGDLL/i2+wnXg3WZTVogf\n" + "WfdKziOHGSxmWd6JinI+4UkpiwKBgQD3+krkFORTsUAlTgeIy8+QzXSuclwNygcX\n" + "HAe0F96hSYHBC7+1n7nzC1lwcbkU3jLIt3A90Uwew4nr5GCu4sSVwDeWrqP2I9WH\n" + "4w0zeaFPC1QKfKGBtsIf/89pDz/7iGlcKWlEg+56VVIJn7qC2lO8qbeUCoglsSwC\n" + "vr2Qld5WvQKBgQCHM2xpHHv8GPlOTxsIPVg8RW0C8iYSITVO5GXu7FnSWdwVuc0+\n" + "QtlgDObvxF/oe4U3Ir7zLVdpRH1Pvy8Cn22AxYYn4hPiniQYg6Xu2zB3tbVE56Hh\n" + "FGJhMD59o+Z90AnWziMdENIG5NkwU9Y48pknvz7hBEiDMSqiHObAATerlwKBgQCP\n" + "5LhCY3Ees3MCcqXilkmqv93eQFP0WHAG0+gQc+1m7+2QJI4pCTdwtfw/SG5akpkr\n" + "aW6DIIkoLNVCgbIsqT/jmbdoA4z3DlIg2PrXDNQytuMcdreNOoyo3trvHr9E6SIi\n" + "LZF9BYWDjTDejsY+mgwPJPh2uinInWdpbF85oA11jQKBgQCc6U2fSwpPQowOaat/\n" + "pY5bDCKxhfwrKk3Ecye5HfhbBZ0pu6Oneiq6cNhQC0X69iFn6ogTFx5qqyMQrWH0\n" + "L+kQRkyYFLnebCzUA8364lieRzc3cN+xQEn+jX8z7eDZ8JsvVnKdc6lTjPTwN1Fj\n" + "FZtaH2L1IEiA8ZZapMb/MNNozg==\n" + "-----END PRIVATE KEY-----\n" + ), + "api_url": "gmi.example.com", + "auth_token": "01234567-5678-9abc-78de-9fgabc123def", + "validate_certs": False, + } + ) + + def set_args_delete_na_sg_grid_storage_api_certificate(self): + return dict( + { + "state": "absent", + "type": "storage-api", + "server_certificate": ( + "-----BEGIN CERTIFICATE-----\n" + "MIICyDCCAbACCQCgFntI3q7iADANBgkqhkiG9w0BAQsFADAmMQswCQYDVQQGEwJV\n" + "UzEXMBUGA1UEAwwOczMuZXhhbXBsZS5jb20wHhcNMjEwNDI5MDQ1NTM1WhcNMjIw\n" + "-----END CERTIFICATE-----\n" + ), + "private_key": ( + "-----BEGIN PRIVATE KEY-----\n" + "MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQD0LMcJUdWmTtxi\n" + "7U7ByldDRfyCD9W+QJ1Ygm7E9iFwvkThUCV5q+DIcgSfogoSKaQuHaImLXMZn36Q\n" + "C22n+Ah2EGrQiggyny3wDzuWf5/Qg7ogqQRqiespBFLlV4RGCREHK0y5uq8mzpIa\n" + "-----END PRIVATE KEY-----\n" + ), + "api_url": "gmi.example.com", + "auth_token": "01234567-5678-9abc-78de-9fgabc123def", + "validate_certs": 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(self.set_default_args_fail_check()) + grid_certificate_module() + print("Info: test_module_fail_when_required_args_missing: %s" % exc.value.args[0]["msg"]) + + def test_module_pass_when_required_args_present(self): + """ required arguments are reported as errors """ + with pytest.raises(AnsibleExitJson) as exc: + set_module_args(self.set_default_args_pass_check()) + grid_certificate_module() + exit_json(changed=True, msg="Induced arguments check") + print("Info: test_module_pass_when_required_args_present: %s" % exc.value.args[0]["msg"]) + assert exc.value.args[0]["changed"] + + @patch("ansible_collections.netapp.storagegrid.plugins.module_utils.netapp.SGRestAPI.send_request") + def test_set_na_sg_grid_storage_api_certificate_pass(self, mock_request): + set_module_args(self.set_args_set_na_sg_grid_storage_api_certificate()) + my_obj = grid_certificate_module() + mock_request.side_effect = [ + SRR["cert_unset"], # get + SRR["update_good"], # post + SRR["storage_api_cert"], # get + SRR["end_of_sequence"], + ] + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + print("Info: test_set_na_sg_grid_storage_api_certificate_pass: %s" % repr(exc.value.args[0])) + assert exc.value.args[0]["changed"] + + @patch("ansible_collections.netapp.storagegrid.plugins.module_utils.netapp.SGRestAPI.send_request") + def test_idempotent_set_na_sg_grid_storage_api_certificate_pass(self, mock_request): + set_module_args(self.set_args_set_na_sg_grid_storage_api_certificate()) + my_obj = grid_certificate_module() + mock_request.side_effect = [ + SRR["storage_api_cert"], # get + SRR["end_of_sequence"], + ] + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + print("Info: test_idempotent_set_na_sg_grid_storage_api_certificate_pass: %s" % repr(exc.value.args[0])) + assert not exc.value.args[0]["changed"] + + @patch("ansible_collections.netapp.storagegrid.plugins.module_utils.netapp.SGRestAPI.send_request") + def test_update_na_sg_grid_storage_api_certificate_pass(self, mock_request): + args = self.set_args_set_na_sg_grid_storage_api_certificate() + args["server_certificate"] = "" + args["private_key"] = "" + + set_module_args(args) + my_obj = grid_certificate_module() + mock_request.side_effect = [ + SRR["storage_api_cert"], # get + SRR["update_good"], # put + SRR["storage_api_cert_update"], # get + SRR["end_of_sequence"], + ] + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + print("Info: test_update_na_sg_grid_storage_api_certificate_pass: %s" % repr(exc.value.args[0])) + assert exc.value.args[0]["changed"] + + @patch("ansible_collections.netapp.storagegrid.plugins.module_utils.netapp.SGRestAPI.send_request") + def test_delete_na_sg_storage_api_certificate_pass(self, mock_request): + set_module_args(self.set_args_delete_na_sg_grid_storage_api_certificate()) + my_obj = grid_certificate_module() + mock_request.side_effect = [ + SRR["storage_api_cert"], # get + SRR["delete_good"], # delete + SRR["cert_unset"], # get + SRR["end_of_sequence"], + ] + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + print("Info: test_delete_na_sg_storage_api_certificate_pass: %s" % repr(exc.value.args[0])) + assert exc.value.args[0]["changed"] diff --git a/ansible_collections/netapp/storagegrid/tests/unit/plugins/modules/test_na_sg_grid_client_certificate.py b/ansible_collections/netapp/storagegrid/tests/unit/plugins/modules/test_na_sg_grid_client_certificate.py new file mode 100644 index 000000000..d21f9da9c --- /dev/null +++ b/ansible_collections/netapp/storagegrid/tests/unit/plugins/modules/test_na_sg_grid_client_certificate.py @@ -0,0 +1,347 @@ +# (c) 2022, NetApp, Inc +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +""" unit tests NetApp StorageGRID Grid HA Group Ansible module: na_sg_grid_client_certificate""" + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type +import json +import pytest +import sys + +try: + from requests import Response +except ImportError: + if sys.version_info < (2, 7): + pytestmark = pytest.mark.skip("Skipping Unit Tests on 2.6 as requests is not available") + else: + raise + +from ansible_collections.netapp.storagegrid.tests.unit.compat import unittest +from ansible_collections.netapp.storagegrid.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.storagegrid.plugins.modules.na_sg_grid_client_certificate import ( + SgGridClientCertificate as grid_client_certificate_module, +) + +# REST API canned responses when mocking send_request +SRR = { + # common responses + "empty_good": ({"data": []}, None), + "not_found": ( + {"status": "error", "code": 404, "data": {}}, + {"key": "error.404"}, + ), + "end_of_sequence": (None, "Unexpected call to send_request"), + "generic_error": (None, "Expected error"), + "delete_good": (None, None), + "update_good": (None, None), + "version_114": ({"data": {"productVersion": "11.4.0-20200721.1338.d3969b3"}}, None), + "version_116": ({"data": {"productVersion": "11.6.0-20211120.0301.850531e"}}, None), + "client_cert_record": ( + { + "data": { + "id": "841ee2c7-3144-4c3c-8709-335462c5b05d", + "displayName": "testcert1", + "publicKey": ( + "-----BEGIN CERTIFICATE-----\n" + "MIIEOzCCAyOgAwIBAgIIFuVL2ktGT0MwDQYJKoZIhvcNAQELBQAwbzELMAkGA1UE\n" + "BhMCVVMxCzAJBgNVBAgMAkNBMRIwEAYDVQQHDAlTdW5ueXZhbGUxFDASBgNVBAoM\n" + "-----END CERTIFICATE-----\n" + ), + "allowPrometheus": True, + "expiryDate": "2024-01-01T00:00:00.000Z", + } + }, + None, + ), + "client_cert_record_updated": ( + { + "data": { + "id": "841ee2c7-3144-4c3c-8709-335462c5b05d", + "displayName": "testcert1", + "publicKey": ( + "-----BEGIN CERTIFICATE-----\n" + "MIICrDCCAZSgAwIBAgIUM3IQEKIypqPrXmoA/KmELXfFAz8wDQYJKoZIhvcNAQEL\n" + "BQAwADAeFw0yMjA5MDUyMzI3MTVaFw0yNDA5MDQyMzI3MTVaMAAwggEiMA0GCSqG\n" + "-----END CERTIFICATE-----\n" + ), + "allowPrometheus": True, + "expiryDate": "2024-01-01T00:00:00.000Z", + } + }, + None, + ), + "client_cert_record_rename": ( + { + "data": { + "id": "841ee2c7-3144-4c3c-8709-335462c5b05d", + "displayName": "testcert1-rename", + "publicKey": ( + "-----BEGIN CERTIFICATE-----\n" + "MIIEOzCCAyOgAwIBAgIIFuVL2ktGT0MwDQYJKoZIhvcNAQELBQAwbzELMAkGA1UE\n" + "BhMCVVMxCzAJBgNVBAgMAkNBMRIwEAYDVQQHDAlTdW5ueXZhbGUxFDASBgNVBAoM\n" + "-----END CERTIFICATE-----\n" + ), + "allowPrometheus": True, + "expiryDate": "2024-01-01T00:00:00.000Z", + } + }, + None, + ), + "client_certificates": ( + { + "data": [ + { + "id": "841ee2c7-3144-4c3c-8709-335462c5b05d", + "displayName": "testcert1", + "publicKey": ( + "-----BEGIN CERTIFICATE-----\n" + "MIIEOzCCAyOgAwIBAgIIFuVL2ktGT0MwDQYJKoZIhvcNAQELBQAwbzELMAkGA1UE\n" + "BhMCVVMxCzAJBgNVBAgMAkNBMRIwEAYDVQQHDAlTdW5ueXZhbGUxFDASBgNVBAoM\n" + "-----END CERTIFICATE-----\n" + ), + "allowPrometheus": True, + "expiryDate": "2024-01-01T00:00:00.000Z", + }, + { + "id": "869e1792-5505-42f1-a1fc-57a04e56f644", + "displayName": "testcert2", + "publicKey": ( + "-----BEGIN CERTIFICATE-----\n" + "MIIC9DCCAdygAwIBAgIUD7y+AyrSqRjQdYVflLJ9aTIJu3wwDQYJKoZIhvcNAQEL\n" + "BQAwFTETMBEGA1UEAwwKUHJvbWV0aGV1czAeFw0yMjA4MjQxMjQxNDhaFw0yNDA4\n" + "-----END CERTIFICATE-----\n" + ), + "allowPrometheus": True, + "expiryDate": "2024-01-01T00:00:00.000Z", + }, + ] + }, + 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""" + + 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 set_default_args_fail_check(self): + return dict( + { + "allow_prometheus": True, + "public_key": ( + "-----BEGIN CERTIFICATE-----\n" + "MIIEOzCCAyOgAwIBAgIIFuVL2ktGT0MwDQYJKoZIhvcNAQELBQAwbzELMAkGA1UE\n" + "BhMCVVMxCzAJBgNVBAgMAkNBMRIwEAYDVQQHDAlTdW5ueXZhbGUxFDASBgNVBAoM\n" + "-----END CERTIFICATE-----\n" + ), + "auth_token": "01234567-5678-9abc-78de-9fgabc123def", + "validate_certs": False, + } + ) + + def set_default_args_pass_check(self): + return dict( + { + "state": "present", + "display_name": "testcert1", + "allow_prometheus": True, + "public_key": ( + "-----BEGIN CERTIFICATE-----\n" + "MIIEOzCCAyOgAwIBAgIIFuVL2ktGT0MwDQYJKoZIhvcNAQELBQAwbzELMAkGA1UE\n" + "BhMCVVMxCzAJBgNVBAgMAkNBMRIwEAYDVQQHDAlTdW5ueXZhbGUxFDASBgNVBAoM\n" + "-----END CERTIFICATE-----\n" + ), + "api_url": "gmi.example.com", + "auth_token": "01234567-5678-9abc-78de-9fgabc123def", + "validate_certs": False, + } + ) + + def set_args_create_na_sg_grid_client_certificate(self): + return dict( + { + "state": "present", + "display_name": "testcert1", + "allow_prometheus": True, + "public_key": ( + "-----BEGIN CERTIFICATE-----\n" + "MIIEOzCCAyOgAwIBAgIIFuVL2ktGT0MwDQYJKoZIhvcNAQELBQAwbzELMAkGA1UE\n" + "BhMCVVMxCzAJBgNVBAgMAkNBMRIwEAYDVQQHDAlTdW5ueXZhbGUxFDASBgNVBAoM\n" + "-----END CERTIFICATE-----\n" + ), + "api_url": "gmi.example.com", + "auth_token": "01234567-5678-9abc-78de-9fgabc123def", + "validate_certs": False, + } + ) + + def set_args_delete_na_sg_grid_client_certificate(self): + return dict( + { + "state": "absent", + "display_name": "testcert1", + "api_url": "gmi.example.com", + "auth_token": "01234567-5678-9abc-78de-9fgabc123def", + "validate_certs": False, + } + ) + + @patch("ansible_collections.netapp.storagegrid.plugins.module_utils.netapp.SGRestAPI.send_request") + def test_module_fail_when_required_args_missing(self, mock_request): + """required arguments are reported as errors""" + with pytest.raises(AnsibleFailJson) as exc: + set_module_args(self.set_default_args_fail_check()) + grid_client_certificate_module() + print("Info: test_module_fail_when_required_args_missing: %s" % exc.value.args[0]["msg"]) + + @patch("ansible_collections.netapp.storagegrid.plugins.module_utils.netapp.SGRestAPI.send_request") + def test_module_pass_when_required_args_present(self, mock_request): + """required arguments are reported as errors""" + with pytest.raises(AnsibleExitJson) as exc: + set_module_args(self.set_default_args_pass_check()) + grid_client_certificate_module() + exit_json(changed=True, msg="Induced arguments check") + print("Info: test_module_pass_when_required_args_present: %s" % exc.value.args[0]["msg"]) + assert exc.value.args[0]["changed"] + + @patch("ansible_collections.netapp.storagegrid.plugins.module_utils.netapp.SGRestAPI.send_request") + def test_create_na_sg_grid_client_certificate_pass(self, mock_request): + set_module_args(self.set_args_create_na_sg_grid_client_certificate()) + mock_request.side_effect = [ + SRR["empty_good"], # get + SRR["client_cert_record"], # post + SRR["end_of_sequence"], + ] + my_obj = grid_client_certificate_module() + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + print("Info: test_create_na_sg_grid_client_certificate_pass: %s" % repr(exc.value.args[0])) + assert exc.value.args[0]["changed"] + + @patch("ansible_collections.netapp.storagegrid.plugins.module_utils.netapp.SGRestAPI.send_request") + def test_idempotent_create_na_sg_grid_client_certificate_pass(self, mock_request): + args = self.set_args_create_na_sg_grid_client_certificate() + set_module_args(args) + mock_request.side_effect = [ + SRR["client_certificates"], # get + SRR["client_cert_record"], # get + SRR["end_of_sequence"], + ] + my_obj = grid_client_certificate_module() + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + print("Info: test_idempotent_create_na_sg_grid_client_certificate_pass: %s" % repr(exc.value.args[0])) + assert not exc.value.args[0]["changed"] + + @patch("ansible_collections.netapp.storagegrid.plugins.module_utils.netapp.SGRestAPI.send_request") + def test_update_na_sg_grid_client_certificate_pass(self, mock_request): + args = self.set_args_create_na_sg_grid_client_certificate() + args["public_key"] = ( + "-----BEGIN CERTIFICATE-----\n" + "MIICrDCCAZSgAwIBAgIUM3IQEKIypqPrXmoA/KmELXfFAz8wDQYJKoZIhvcNAQEL\n" + "BQAwADAeFw0yMjA5MDUyMzI3MTVaFw0yNDA5MDQyMzI3MTVaMAAwggEiMA0GCSqG\n" + "-----END CERTIFICATE-----\n", + ) + set_module_args(args) + mock_request.side_effect = [ + SRR["client_certificates"], # get + SRR["client_cert_record"], # get + SRR["client_cert_record_updated"], # put + SRR["end_of_sequence"], + ] + my_obj = grid_client_certificate_module() + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + print("Info: test_update_na_sg_grid_client_certificate_pass: %s" % repr(exc.value.args[0])) + assert exc.value.args[0]["changed"] + + @patch("ansible_collections.netapp.storagegrid.plugins.module_utils.netapp.SGRestAPI.send_request") + def test_rename_na_sg_grid_client_certificate_pass(self, mock_request): + args = self.set_args_create_na_sg_grid_client_certificate() + args["certificate_id"] = "841ee2c7-3144-4c3c-8709-335462c5b05d" + args["display_name"] = "testcert1-rename" + set_module_args(args) + mock_request.side_effect = [ + SRR["client_cert_record"], # get + SRR["client_cert_record_rename"], # put + SRR["end_of_sequence"], + ] + my_obj = grid_client_certificate_module() + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + print("Info: test_rename_na_sg_grid_client_certificate_pass: %s" % repr(exc.value.args[0])) + assert exc.value.args[0]["changed"] + + @patch("ansible_collections.netapp.storagegrid.plugins.module_utils.netapp.SGRestAPI.send_request") + def test_delete_na_sg_grid_client_certificate_pass(self, mock_request): + args = self.set_args_delete_na_sg_grid_client_certificate() + set_module_args(args) + mock_request.side_effect = [ + SRR["client_certificates"], # get + SRR["client_cert_record"], # get + SRR["delete_good"], # delete + SRR["end_of_sequence"], + ] + my_obj = grid_client_certificate_module() + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + print("Info: test_delete_na_sg_grid_client_certificate_pass: %s" % repr(exc.value.args[0])) + assert exc.value.args[0]["changed"] + + @patch("ansible_collections.netapp.storagegrid.plugins.module_utils.netapp.SGRestAPI.send_request") + def test_update_na_sg_grid_client_certificate_bad_certificate_id_fail(self, mock_request): + args = self.set_args_create_na_sg_grid_client_certificate() + args["certificate_id"] = "ffffffff-ffff-aaaa-aaaa-000000000000" + args["display_name"] = "Bad ID" + set_module_args(args) + mock_request.side_effect = [ + SRR["not_found"], # get + ] + with pytest.raises(AnsibleFailJson) as exc: + my_obj = grid_client_certificate_module() + my_obj.apply() + print("Info: test_update_na_sg_grid_client_certificate_bad_certificate_id_fail: %s" % repr(exc.value.args[0])) + assert exc.value.args[0]["failed"] diff --git a/ansible_collections/netapp/storagegrid/tests/unit/plugins/modules/test_na_sg_grid_dns.py b/ansible_collections/netapp/storagegrid/tests/unit/plugins/modules/test_na_sg_grid_dns.py new file mode 100644 index 000000000..42abde9c8 --- /dev/null +++ b/ansible_collections/netapp/storagegrid/tests/unit/plugins/modules/test_na_sg_grid_dns.py @@ -0,0 +1,241 @@ +# (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 StorageGRID DNS Ansible module: na_sg_grid_dns""" + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type +import json +import pytest +import sys +try: + from requests import Response +except ImportError: + if sys.version_info < (2, 7): + pytestmark = pytest.mark.skip('Skipping Unit Tests on 2.6 as requests is not available') + else: + raise + +from ansible_collections.netapp.storagegrid.tests.unit.compat import unittest +from ansible_collections.netapp.storagegrid.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.storagegrid.plugins.modules.na_sg_grid_dns import ( + SgGridDns as grid_dns_module, +) + +# REST API canned responses when mocking send_request +SRR = { + # common responses + "empty_good": ({"data": []}, None), + "not_found": ( + {"status": "error", "code": 404, "data": {}}, + {"key": "error.404"}, + ), + "end_of_sequence": (None, "Unexpected call to send_request"), + "generic_error": (None, "Expected error"), + "delete_good": ({"code": 204}, None), + "no_dns_servers": ({"data": []}, None,), + "dns_servers": ({"data": ["10.11.12.5", "10.11.12.6"]}, None,), + "add_dns_servers": ( + {"data": ["10.11.12.5", "10.11.12.6", "10.11.12.7"]}, + None, + ), + "remove_dns_servers": ({"data": ["10.11.12.5"]}, 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""" + + 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 set_default_args_fail_check(self): + return dict( + { + "dns_servers": "10.11.12.8", + "auth_token": "01234567-5678-9abc-78de-9fgabc123def", + "validate_certs": False, + } + ) + + def set_default_args_pass_check(self): + return dict( + { + "state": "present", + "dns_servers": "10.11.12.8", + "api_url": "gmi.example.com", + "auth_token": "01234567-5678-9abc-78de-9fgabc123def", + "validate_certs": False, + } + ) + + def set_args_set_na_sg_grid_dns_servers(self): + return dict( + { + "state": "present", + "dns_servers": "10.11.12.5,10.11.12.6", + "api_url": "gmi.example.com", + "auth_token": "01234567-5678-9abc-78de-9fgabc123def", + "validate_certs": False, + } + ) + + def set_args_add_na_sg_grid_dns_server(self): + return dict( + { + "state": "present", + "dns_servers": "10.11.12.5,10.11.12.6,10.11.12.7", + "api_url": "gmi.example.com", + "auth_token": "01234567-5678-9abc-78de-9fgabc123def", + "validate_certs": False, + } + ) + + def set_args_remove_na_sg_grid_dns_server(self): + return dict( + { + "state": "present", + "dns_servers": "10.11.12.5", + "api_url": "gmi.example.com", + "auth_token": "01234567-5678-9abc-78de-9fgabc123def", + "validate_certs": 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(self.set_default_args_fail_check()) + grid_dns_module() + print( + "Info: test_module_fail_when_required_args_missing: %s" + % exc.value.args[0]["msg"] + ) + + def test_module_fail_when_required_args_present(self): + """ required arguments are reported as errors """ + with pytest.raises(AnsibleExitJson) as exc: + set_module_args(self.set_default_args_pass_check()) + grid_dns_module() + exit_json(changed=True, msg="Induced arguments check") + print( + "Info: test_module_fail_when_required_args_present: %s" + % exc.value.args[0]["msg"] + ) + assert exc.value.args[0]["changed"] + + @patch( + "ansible_collections.netapp.storagegrid.plugins.module_utils.netapp.SGRestAPI.send_request" + ) + def test_set_na_sg_grid_dns_servers_pass(self, mock_request): + set_module_args(self.set_args_set_na_sg_grid_dns_servers()) + my_obj = grid_dns_module() + mock_request.side_effect = [ + SRR["no_dns_servers"], # get + SRR["dns_servers"], # post + SRR["end_of_sequence"], + ] + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + print( + "Info: test_set_na_sg_grid_dns_servers_pass: %s" + % repr(exc.value.args[0]) + ) + assert exc.value.args[0]["changed"] + + @patch( + "ansible_collections.netapp.storagegrid.plugins.module_utils.netapp.SGRestAPI.send_request" + ) + def test_idempotent_set_na_sg_grid_dns_servers_pass(self, mock_request): + set_module_args(self.set_args_set_na_sg_grid_dns_servers()) + my_obj = grid_dns_module() + mock_request.side_effect = [ + SRR["dns_servers"], # get + SRR["end_of_sequence"], + ] + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + print( + "Info: test_idempotent_set_na_sg_grid_dns_servers_pass: %s" + % repr(exc.value.args[0]) + ) + assert not exc.value.args[0]["changed"] + + @patch( + "ansible_collections.netapp.storagegrid.plugins.module_utils.netapp.SGRestAPI.send_request" + ) + def test_add_na_sg_grid_dns_servers_pass(self, mock_request): + set_module_args(self.set_args_add_na_sg_grid_dns_server()) + my_obj = grid_dns_module() + mock_request.side_effect = [ + SRR["dns_servers"], # get + SRR["add_dns_servers"], # post + SRR["end_of_sequence"], + ] + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + print( + "Info: test_add_na_sg_grid_dns_servers_pass: %s" + % repr(exc.value.args[0]) + ) + assert exc.value.args[0]["changed"] + + @patch( + "ansible_collections.netapp.storagegrid.plugins.module_utils.netapp.SGRestAPI.send_request" + ) + def test_remove_na_sg_grid_dns_servers_pass(self, mock_request): + set_module_args(self.set_args_remove_na_sg_grid_dns_server()) + my_obj = grid_dns_module() + mock_request.side_effect = [ + SRR["dns_servers"], # get + SRR["remove_dns_servers"], # post + SRR["end_of_sequence"], + ] + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + print( + "Info: test_remove_na_sg_grid_dns_servers_pass: %s" + % repr(exc.value.args[0]) + ) + assert exc.value.args[0]["changed"] diff --git a/ansible_collections/netapp/storagegrid/tests/unit/plugins/modules/test_na_sg_grid_gateway.py b/ansible_collections/netapp/storagegrid/tests/unit/plugins/modules/test_na_sg_grid_gateway.py new file mode 100644 index 000000000..0a5a7e386 --- /dev/null +++ b/ansible_collections/netapp/storagegrid/tests/unit/plugins/modules/test_na_sg_grid_gateway.py @@ -0,0 +1,693 @@ +# (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 StorageGRID Grid Load Balancer Endpoint Ansible module: na_sg_grid_gateway""" + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type +import json +import pytest +import sys + +# try: +# from requests import Response +# except ImportError: +# if sys.version_info < (2, 7): +# pytestmark = pytest.mark.skip("Skipping Unit Tests on 2.6 as requests is not available") +# else: +# raise + +from ansible_collections.netapp.storagegrid.tests.unit.compat import unittest +from ansible_collections.netapp.storagegrid.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.storagegrid.plugins.modules.na_sg_grid_gateway import ( + SgGridGateway as grid_gateway_module, +) + +# REST API canned responses when mocking send_request +SRR = { + # common responses + "empty_good": ({"data": []}, None), + "not_found": ( + {"status": "error", "code": 404, "data": {}}, + {"key": "error.404"}, + ), + "end_of_sequence": (None, "Unexpected call to send_request"), + "generic_error": (None, "Expected error"), + "delete_good": (None, None), + "update_good": (None, None), + "version_114": ({"data": {"productVersion": "11.4.0-20200721.1338.d3969b3"}}, None), + "version_116": ({"data": {"productVersion": "11.6.0-20211120.0301.850531e"}}, None), + "gateway_record": ( + { + "data": { + "id": "e777d415-057f-4d37-9b0c-6d132d872ea0", + "displayName": "ansibletest-secure", + "enableIPv4": True, + "enableIPv6": True, + "port": 10443, + "secure": True, + "accountId": "0", + } + }, + None, + ), + "gateway_record_ha_group_binding": ( + { + "data": { + "id": "e777d415-057f-4d37-9b0c-6d132d872ea0", + "displayName": "ansibletest-secure", + "enableIPv4": True, + "enableIPv6": True, + "port": 10443, + "secure": True, + "accountId": "0", + "pinTargets": {"haGroups": ["c08e6dca-038d-4a05-9499-6fbd1e6a4c3e"], "nodeInterfaces": []}, + } + }, + None, + ), + "gateway_record_node_interface_binding": ( + { + "data": { + "id": "e777d415-057f-4d37-9b0c-6d132d872ea0", + "displayName": "ansibletest-secure", + "enableIPv4": True, + "enableIPv6": True, + "port": 10443, + "secure": True, + "accountId": "0", + "pinTargets": { + "haGroups": [], + "nodeInterfaces": [ + {"interface": "eth2", "nodeId": "0b1866ed-d6e7-41b4-815f-bf867348b76b"}, + {"interface": "eth2", "nodeId": "970ad050-b68b-4aae-a94d-aef73f3095c4"}, + ], + }, + } + }, + None, + ), + "gateway_record_rename": ( + { + "data": { + "id": "e777d415-057f-4d37-9b0c-6d132d872ea0", + "displayName": "ansibletest-rename", + "enableIPv4": True, + "enableIPv6": True, + "port": 10443, + "secure": True, + "accountId": "0", + "pinTargets": {"haGroups": ["c08e6dca-038d-4a05-9499-6fbd1e6a4c3e"], "nodeInterfaces": []}, + } + }, + None, + ), + "ha_groups": ( + { + "data": [ + { + "id": "c08e6dca-038d-4a05-9499-6fbd1e6a4c3e", + "name": "site1_primary", + "description": "test ha group", + "virtualIps": ["10.193.174.117"], + "interfaces": [ + { + "nodeId": "0b1866ed-d6e7-41b4-815f-bf867348b76b", + "nodeName": "SITE1-ADM1", + "interface": "eth2", + "preferredMaster": True, + }, + { + "nodeId": "970ad050-b68b-4aae-a94d-aef73f3095c4", + "nodeName": "SITE2-ADM1", + "interface": "eth2", + }, + ], + "gatewayCidr": "192.168.14.1/24", + }, + { + "id": "da9ac524-9a16-4be0-9d6e-ec9b22218e75", + "name": "site1_gw", + "description": "another test ha group", + "virtualIps": ["10.193.204.200"], + "interfaces": [ + { + "nodeId": "7bb5bf05-a04c-4344-8abd-08c5c4048666", + "nodeName": "SITE1-GW1", + "interface": "eth0", + "preferredMaster": True, + }, + ], + "gatewayCidr": "192.168.14.1/24", + } + ] + }, + None, + ), + "node_health": ( + { + "data": [ + { + "id": "0b1866ed-d6e7-41b4-815f-bf867348b76b", + "isPrimaryAdmin": True, + "name": "SITE1-ADM1", + "siteId": "ae56d06d-bd83-46bd-adce-77146b1d94bd", + "siteName": "SITE1", + "severity": "normal", + "state": "connected", + "type": "adminNode", + }, + { + "id": "970ad050-b68b-4aae-a94d-aef73f3095c4", + "isPrimaryAdmin": False, + "name": "SITE2-ADM1", + "siteId": "7c24002e-5157-43e9-83e5-02db9b265b02", + "siteName": "SITE2", + "severity": "normal", + "state": "connected", + "type": "adminNode", + }, + ] + }, + None, + ), + "present_gateways": ( + { + "data": [ + { + "id": "e777d415-057f-4d37-9b0c-6d132d872ea0", + "displayName": "ansibletest-secure", + "enableIPv4": True, + "enableIPv6": True, + "port": 10443, + "secure": True, + "accountId": "0", + } + ] + }, + None, + ), + "present_gateways_with_binding": ( + { + "data": [ + { + "id": "e777d415-057f-4d37-9b0c-6d132d872ea0", + "displayName": "ansibletest-secure", + "enableIPv4": True, + "enableIPv6": True, + "port": 10443, + "secure": True, + "accountId": "0", + "pinTargets": {"haGroups": [], "nodeInterfaces": []}, + } + ] + }, + None, + ), + "server_config": ( + { + "data": { + "defaultServiceType": "s3", + "certSource": "plaintext", + "plaintextCertData": { + "serverCertificateEncoded": ( + "-----BEGIN CERTIFICATE-----\n" + "MIICyDCCAbACCQCgFntI3q7iADANBgkqhkiG9w0BAQsFADAmMQswCQYDVQQGEwJV\n" + "UzEXMBUGA1UEAwwOczMuZXhhbXBsZS5jb20wHhcNMjEwNDI5MDQ1NTM1WhcNMjIw\n" + "NDI5MDQ1NTM1WjAmMQswCQYDVQQGEwJVUzEXMBUGA1UEAwwOczMuZXhhbXBsZS5j\n" + "b20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQD0LMcJUdWmTtxi7U7B\n" + "yldDRfyCD9W+QJ1Ygm7E9iFwvkThUCV5q+DIcgSfogoSKaQuHaImLXMZn36QC22n\n" + "+Ah2EGrQiggyny3wDzuWf5/Qg7ogqQRqiespBFLlV4RGCREHK0y5uq8mzpIaQ8l8\n" + "STa7nLS7BIc6rD15BJaNWZpDVHIzhljlnhfnqwio/ZfP++lAjk4/j8pPGPEEI5Fe\n" + "WxhOtQjr7xTHeJxKHp2VKiLEvFxniL3qk4uJ3k5fJ7IqALUEPWH92brFp2IkObUA\n" + "EGsZYB4KFV7asBVhGuspYNzUQ6NqWbEUmtTjKEXcb1TA8RK+Pc2TotOrQ2E7Z+rU\n" + "gl2fAgMBAAEwDQYJKoZIhvcNAQELBQADggEBAD5PW1WI7GCfxLQjaitnXpD1MR2O\n" + "6b5csymPYwRejMsSswd8egjs+vO2pbF9TptLjqGliE9XUoI+mWpuMzzd75F0jcjq\n" + "1DhlINgAmjUJEAg0RAqce0Kn8xQF+SofMtkOH+nZm3Q9nbTJKr1H5m2TnCq3v5TH\n" + "Qo0ASf0LLGgrwUtT0IghdSttYLS89dJprZ6c5wK7qeBzxfdHxxjiaSnvByL2Ryn5\n" + "cec9lptYKoRY42hWvkQv9Wkr3DDoyNA3xPdZJr0Hpf8/mSPnt9r/AR8E32xi0SXp\n" + "hOMTDgMicbK82ycxz0yW88gm6yhrChlJrWaEsVGod3FU+lbMAnagYZ/Vwp8=\n" + "-----END CERTIFICATE-----\n" + ), + "caBundleEncoded": None, + "metadata": { + "serverCertificateDetails": { + "subject": "/CN=test", + "issuer": "/CN=test", + "serialNumber": "32:6F:20:EB:0E:90:60:7E:07:8F:6E:CC:02:2D:7C:37:3D:AB:42:7E", + "notBefore": "2021-09-27T12:39:17.000Z", + "notAfter": "2023-09-27T12:39:17.000Z", + "fingerPrints": { + "SHA-1": "A4:F9:74:BE:E8:A2:46:C2:E1:23:DE:8F:A8:1B:F1:C4:91:51:C5:56", + "SHA-256": "7B:65:7F:CD:35:8F:33:1C:C8:2D:F0:C1:9F:58:2F:2B:3B:78:44:95:4E:23:8C:1B:2B:91:6C:94:B0:71:64:E8", + }, + "subjectAltNames": ["DNS:*.test.com"], + } + }, + }, + } + }, + None, + ), + "server_config_cert_update": ( + { + "data": { + "defaultServiceType": "s3", + "certSource": "plaintext", + "plaintextCertData": { + "serverCertificateEncoded": ( + "-----BEGIN CERTIFICATE-----\n" + "MIICzjCCAbYCCQDZVi1OT89SAjANBgkqhkiG9w0BAQsFADApMQswCQYDVQQGEwJV\n" + "UzEaMBgGA1UEAwwRczMubmV3ZXhhbXBsZS5jb20wHhcNMjEwNDI5MDQ1NzIxWhcN\n" + "MjIwNDI5MDQ1NzIxWjApMQswCQYDVQQGEwJVUzEaMBgGA1UEAwwRczMubmV3ZXhh\n" + "bXBsZS5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCmg37q2sjZ\n" + "k+HsXtai3PSMtGUiqij04JtG9ahMqIejuxy5sDCWnigh//NjdK+wPYc2VfYd6KFA\n" + "Uk9rP84M7sqdqGzIzmyEu7INyCnlbxcXlST6UZDsZnVU7Gk2GvUzk2OoO5N+G0oI\n" + "Lfc/3eKTx9j9BguOaWUy+ni+Te8j6EwK6HolGRBjLYqf1SYFBzaoVpy7pmzaFZ4R\n" + "10jFSxHbotIZ+kR8pPE5jGkP8OjOfrpbhEgmffpeq2MSCMRuhRtRiVp4ULwkMTRN\n" + "tFj89mu1gl9T3lYM/LO1SmBv3il0mNmrTL+99UJ4s2eL0zr/uHAVYJcVqFgWP7X8\n" + "WnOk+d86b0TXAgMBAAEwDQYJKoZIhvcNAQELBQADggEBAFmGV3IOuNYeM3LQxls+\n" + "/CNHznvIqvoiJOWq0S7LFy1eO7PVzCl3l/fDKjGMt2lGXeU89YKdFVPqsainNEFT\n" + "cNEWlezVut+/CWQpBXujyBqPLkYbzyGsakMImDb+MrSkBO5MCjlt38vppm5a97fB\n" + "9o/wM31e+N6gJLiHWs0XB9TK6bY9CvcutcGUOH/oxH1TEBgrJ3SoS7/HmZJSaCQA\n" + "hjZappzuEpGVXT8YDlb67PzUoE2rDWjdSFRXCk/0U6VR0xNgnN1WtfHaypU71DrB\n" + "zxbDaOIZoDp5G4OgjkFxoCoSWLant+LsqEwclIbCFgEvJPE8855UThelTHmIfivP\n" + "veI=\n-----END CERTIFICATE-----\n" + ), + "caBundleEncoded": None, + "metadata": { + "serverCertificateDetails": { + "subject": "/CN=test", + "issuer": "/CN=test", + "serialNumber": "32:6F:20:EB:0E:90:60:7E:07:8F:6E:CC:02:2D:7C:37:3D:AB:42:7E", + "notBefore": "2021-09-27T12:39:17.000Z", + "notAfter": "2023-09-27T12:39:17.000Z", + "fingerPrints": { + "SHA-1": "F2:C2:6F:A8:45:DA:86:09:91:F5:04:B0:25:43:B7:FC:FA:C1:43:F8", + "SHA-256": "99:3E:21:1A:03:25:69:C8:0A:D5:FE:E3:FB:6E:51:03:BD:A7:0E:88:6B:53:06:04:92:3B:34:17:68:43:F7:2F", + }, + "subjectAltNames": ["DNS:*.test.com"], + } + }, + }, + } + }, + 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""" + + 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 set_default_args_fail_check(self): + return dict( + { + "display_name": "ansibletest-secure", + "default_service_type": "s3", + "server_certificate": ( + "-----BEGIN CERTIFICATE-----\n" + "MIICyDCCAbACCQCgFntI3q7iADANBgkqhkiG9w0BAQsFADAmMQswCQYDVQQGEwJV\n" + "UzEXMBUGA1UEAwwOczMuZXhhbXBsZS5jb20wHhcNMjEwNDI5MDQ1NTM1WhcNMjIw\n" + "-----END CERTIFICATE-----\n" + ), + "private_key": ( + "-----BEGIN PRIVATE KEY-----\n" + "MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQD0LMcJUdWmTtxi\n" + "7U7ByldDRfyCD9W+QJ1Ygm7E9iFwvkThUCV5q+DIcgSfogoSKaQuHaImLXMZn36Q\n" + "C22n+Ah2EGrQiggyny3wDzuWf5/Qg7ogqQRqiespBFLlV4RGCREHK0y5uq8mzpIa\n" + "-----END PRIVATE KEY-----\n" + ), + "auth_token": "01234567-5678-9abc-78de-9fgabc123def", + "validate_certs": False, + } + ) + + def set_default_args_pass_check(self): + return dict( + { + "state": "present", + "display_name": "ansibletest-secure", + "default_service_type": "s3", + "port": 10443, + "server_certificate": ( + "-----BEGIN CERTIFICATE-----\n" + "MIICyDCCAbACCQCgFntI3q7iADANBgkqhkiG9w0BAQsFADAmMQswCQYDVQQGEwJV\n" + "UzEXMBUGA1UEAwwOczMuZXhhbXBsZS5jb20wHhcNMjEwNDI5MDQ1NTM1WhcNMjIw\n" + "-----END CERTIFICATE-----\n" + ), + "private_key": ( + "-----BEGIN PRIVATE KEY-----\n" + "MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQD0LMcJUdWmTtxi\n" + "7U7ByldDRfyCD9W+QJ1Ygm7E9iFwvkThUCV5q+DIcgSfogoSKaQuHaImLXMZn36Q\n" + "C22n+Ah2EGrQiggyny3wDzuWf5/Qg7ogqQRqiespBFLlV4RGCREHK0y5uq8mzpIa\n" + "-----END PRIVATE KEY-----\n" + ), + "api_url": "https://gmi.example.com", + "auth_token": "01234567-5678-9abc-78de-9fgabc123def", + "validate_certs": False, + } + ) + + def set_args_create_na_sg_grid_gateway_port(self): + return dict( + { + "state": "present", + "display_name": "ansibletest-secure", + "default_service_type": "s3", + "port": 10443, + "server_certificate": ( + "-----BEGIN CERTIFICATE-----\n" + "MIICyDCCAbACCQCgFntI3q7iADANBgkqhkiG9w0BAQsFADAmMQswCQYDVQQGEwJV\n" + "UzEXMBUGA1UEAwwOczMuZXhhbXBsZS5jb20wHhcNMjEwNDI5MDQ1NTM1WhcNMjIw\n" + "NDI5MDQ1NTM1WjAmMQswCQYDVQQGEwJVUzEXMBUGA1UEAwwOczMuZXhhbXBsZS5j\n" + "b20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQD0LMcJUdWmTtxi7U7B\n" + "yldDRfyCD9W+QJ1Ygm7E9iFwvkThUCV5q+DIcgSfogoSKaQuHaImLXMZn36QC22n\n" + "+Ah2EGrQiggyny3wDzuWf5/Qg7ogqQRqiespBFLlV4RGCREHK0y5uq8mzpIaQ8l8\n" + "STa7nLS7BIc6rD15BJaNWZpDVHIzhljlnhfnqwio/ZfP++lAjk4/j8pPGPEEI5Fe\n" + "WxhOtQjr7xTHeJxKHp2VKiLEvFxniL3qk4uJ3k5fJ7IqALUEPWH92brFp2IkObUA\n" + "EGsZYB4KFV7asBVhGuspYNzUQ6NqWbEUmtTjKEXcb1TA8RK+Pc2TotOrQ2E7Z+rU\n" + "gl2fAgMBAAEwDQYJKoZIhvcNAQELBQADggEBAD5PW1WI7GCfxLQjaitnXpD1MR2O\n" + "6b5csymPYwRejMsSswd8egjs+vO2pbF9TptLjqGliE9XUoI+mWpuMzzd75F0jcjq\n" + "1DhlINgAmjUJEAg0RAqce0Kn8xQF+SofMtkOH+nZm3Q9nbTJKr1H5m2TnCq3v5TH\n" + "Qo0ASf0LLGgrwUtT0IghdSttYLS89dJprZ6c5wK7qeBzxfdHxxjiaSnvByL2Ryn5\n" + "cec9lptYKoRY42hWvkQv9Wkr3DDoyNA3xPdZJr0Hpf8/mSPnt9r/AR8E32xi0SXp\n" + "hOMTDgMicbK82ycxz0yW88gm6yhrChlJrWaEsVGod3FU+lbMAnagYZ/Vwp8=\n" + "-----END CERTIFICATE-----\n" + ), + "private_key": ( + "-----BEGIN PRIVATE KEY-----\n" + "MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQD0LMcJUdWmTtxi\n" + "7U7ByldDRfyCD9W+QJ1Ygm7E9iFwvkThUCV5q+DIcgSfogoSKaQuHaImLXMZn36Q\n" + "C22n+Ah2EGrQiggyny3wDzuWf5/Qg7ogqQRqiespBFLlV4RGCREHK0y5uq8mzpIa\n" + "Q8l8STa7nLS7BIc6rD15BJaNWZpDVHIzhljlnhfnqwio/ZfP++lAjk4/j8pPGPEE\n" + "I5FeWxhOtQjr7xTHeJxKHp2VKiLEvFxniL3qk4uJ3k5fJ7IqALUEPWH92brFp2Ik\n" + "ObUAEGsZYB4KFV7asBVhGuspYNzUQ6NqWbEUmtTjKEXcb1TA8RK+Pc2TotOrQ2E7\n" + "Z+rUgl2fAgMBAAECggEAAwSSqTDTvSx4WNiqAocnsPMqfckIUUOnLjLef5yzKRuQ\n" + "6l/9NpXDP3b5S6fLDBJrrw46tNIW/BgWjl01y7+rCxqE13L9SvLgtHjbua52ITOf\n" + "l0u/fDmcKHOfOqpsPhlaloYYeqsuAwLGl4CC+wBEpuj26uDRcw4x7E78NV8IIxDf\n" + "8kUNPQXI9ox6P3isXrFkMncDfKLWOYJ5fF5zCoVZai/SS8z3FhGjAXlMkay48RX4\n" + "4vuP7TNLZ2O2pAk2aVs54tQyBn9MOxIzOg3/ZFLiKZR4pY6H5sm+bT263TdvN+A4\n" + "C8kwML5HnsCjVkTzJ/3dYc9SeUOuqvJI332GCQ9YcQKBgQD8Ev2qhS61kZ3WGO6G\n" + "DRkZ6tDyt5vCuzWQ8uAAXcAerFDWN6XtDPfXq2UVcWnoCQOUpnjslCb/NJgCetLh\n" + "mOPeJGRWyMly+YuYb4/rnbwSbUs28PO4D9B/f5YQBnBjGDLL/i2+wnXg3WZTVogf\n" + "WfdKziOHGSxmWd6JinI+4UkpiwKBgQD3+krkFORTsUAlTgeIy8+QzXSuclwNygcX\n" + "HAe0F96hSYHBC7+1n7nzC1lwcbkU3jLIt3A90Uwew4nr5GCu4sSVwDeWrqP2I9WH\n" + "4w0zeaFPC1QKfKGBtsIf/89pDz/7iGlcKWlEg+56VVIJn7qC2lO8qbeUCoglsSwC\n" + "vr2Qld5WvQKBgQCHM2xpHHv8GPlOTxsIPVg8RW0C8iYSITVO5GXu7FnSWdwVuc0+\n" + "QtlgDObvxF/oe4U3Ir7zLVdpRH1Pvy8Cn22AxYYn4hPiniQYg6Xu2zB3tbVE56Hh\n" + "FGJhMD59o+Z90AnWziMdENIG5NkwU9Y48pknvz7hBEiDMSqiHObAATerlwKBgQCP\n" + "5LhCY3Ees3MCcqXilkmqv93eQFP0WHAG0+gQc+1m7+2QJI4pCTdwtfw/SG5akpkr\n" + "aW6DIIkoLNVCgbIsqT/jmbdoA4z3DlIg2PrXDNQytuMcdreNOoyo3trvHr9E6SIi\n" + "LZF9BYWDjTDejsY+mgwPJPh2uinInWdpbF85oA11jQKBgQCc6U2fSwpPQowOaat/\n" + "pY5bDCKxhfwrKk3Ecye5HfhbBZ0pu6Oneiq6cNhQC0X69iFn6ogTFx5qqyMQrWH0\n" + "L+kQRkyYFLnebCzUA8364lieRzc3cN+xQEn+jX8z7eDZ8JsvVnKdc6lTjPTwN1Fj\n" + "FZtaH2L1IEiA8ZZapMb/MNNozg==\n" + "-----END PRIVATE KEY-----\n" + ), + "api_url": "https://gmi.example.com", + "auth_token": "01234567-5678-9abc-78de-9fgabc123def", + "validate_certs": False, + } + ) + + def set_args_delete_na_sg_grid_gateway_port(self): + return dict( + { + "state": "absent", + "display_name": "ansibletest-secure", + "default_service_type": "s3", + "port": 10443, + "server_certificate": ( + "-----BEGIN CERTIFICATE-----\n" + "MIICyDCCAbACCQCgFntI3q7iADANBgkqhkiG9w0BAQsFADAmMQswCQYDVQQGEwJV\n" + "UzEXMBUGA1UEAwwOczMuZXhhbXBsZS5jb20wHhcNMjEwNDI5MDQ1NTM1WhcNMjIw\n" + "-----END CERTIFICATE-----\n" + ), + "private_key": ( + "-----BEGIN PRIVATE KEY-----\n" + "MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQD0LMcJUdWmTtxi\n" + "7U7ByldDRfyCD9W+QJ1Ygm7E9iFwvkThUCV5q+DIcgSfogoSKaQuHaImLXMZn36Q\n" + "C22n+Ah2EGrQiggyny3wDzuWf5/Qg7ogqQRqiespBFLlV4RGCREHK0y5uq8mzpIa\n" + "-----END PRIVATE KEY-----\n" + ), + "api_url": "https://gmi.example.com", + "auth_token": "01234567-5678-9abc-78de-9fgabc123def", + "validate_certs": False, + } + ) + + @patch("ansible_collections.netapp.storagegrid.plugins.module_utils.netapp.SGRestAPI.send_request") + def test_module_fail_when_required_args_missing(self, mock_request): + """required arguments are reported as errors""" + mock_request.side_effect = [ + SRR["version_114"], + ] + with pytest.raises(AnsibleFailJson) as exc: + set_module_args(self.set_default_args_fail_check()) + grid_gateway_module() + print("Info: test_module_fail_when_required_args_missing: %s" % exc.value.args[0]["msg"]) + + @patch("ansible_collections.netapp.storagegrid.plugins.module_utils.netapp.SGRestAPI.send_request") + def test_module_pass_when_required_args_present(self, mock_request): + """required arguments are reported as errors""" + mock_request.side_effect = [ + SRR["version_114"], + ] + with pytest.raises(AnsibleExitJson) as exc: + set_module_args(self.set_default_args_pass_check()) + grid_gateway_module() + exit_json(changed=True, msg="Induced arguments check") + print("Info: test_module_pass_when_required_args_present: %s" % exc.value.args[0]["msg"]) + assert exc.value.args[0]["changed"] + + @patch("ansible_collections.netapp.storagegrid.plugins.module_utils.netapp.SGRestAPI.send_request") + def test_create_na_sg_grid_gateway_port_pass(self, mock_request): + set_module_args(self.set_args_create_na_sg_grid_gateway_port()) + mock_request.side_effect = [ + SRR["version_114"], # get + SRR["empty_good"], # get + SRR["gateway_record"], # post + SRR["server_config"], # post + SRR["end_of_sequence"], + ] + my_obj = grid_gateway_module() + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + print("Info: test_create_na_sg_grid_gateway_port_pass: %s" % repr(exc.value.args[0])) + assert exc.value.args[0]["changed"] + + @patch("ansible_collections.netapp.storagegrid.plugins.module_utils.netapp.SGRestAPI.send_request") + def test_idempotent_create_na_sg_grid_gateway_port_pass(self, mock_request): + args = self.set_args_create_na_sg_grid_gateway_port() + del args["private_key"] + set_module_args(args) + mock_request.side_effect = [ + SRR["version_114"], # get + SRR["present_gateways"], # get + SRR["server_config"], # get + SRR["end_of_sequence"], + ] + my_obj = grid_gateway_module() + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + print("Info: test_idempotent_create_na_sg_grid_gateway_port_pass: %s" % repr(exc.value.args[0])) + assert not exc.value.args[0]["changed"] + + @patch("ansible_collections.netapp.storagegrid.plugins.module_utils.netapp.SGRestAPI.send_request") + def test_update_na_sg_grid_gateway_certificate_pass(self, mock_request): + args = self.set_args_create_na_sg_grid_gateway_port() + args["server_certificate"] = "-----BEGIN CERTIFICATE-----\nABCDEFGABCD\n-----END CERTIFICATE-----\n" + args["private_key"] = "-----BEGIN PRIVATE KEY-----\nABCDEFGABCD\n-----END PRIVATE KEY-----\n" + + set_module_args(args) + mock_request.side_effect = [ + SRR["version_114"], # get + SRR["present_gateways"], # get + SRR["server_config"], # get + SRR["server_config_cert_update"], # put + SRR["end_of_sequence"], + ] + my_obj = grid_gateway_module() + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + print("Info: test_update_na_sg_grid_gateway_certificate_pass: %s" % repr(exc.value.args[0])) + assert exc.value.args[0]["changed"] + + @patch("ansible_collections.netapp.storagegrid.plugins.module_utils.netapp.SGRestAPI.send_request") + def test_delete_na_sg_grid_gateway_port_pass(self, mock_request): + set_module_args(self.set_args_delete_na_sg_grid_gateway_port()) + mock_request.side_effect = [ + SRR["version_114"], # get + SRR["present_gateways"], # get + SRR["server_config"], # get + SRR["delete_good"], # delete + SRR["end_of_sequence"], + ] + my_obj = grid_gateway_module() + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + print("Info: test_delete_na_sg_grid_gateway_port_pass: %s" % repr(exc.value.args[0])) + assert exc.value.args[0]["changed"] + + @patch("ansible_collections.netapp.storagegrid.plugins.module_utils.netapp.SGRestAPI.send_request") + def test_module_fail_minimum_version_not_met(self, mock_request): + args = self.set_args_create_na_sg_grid_gateway_port() + args["binding_mode"] = "ha-groups" + set_module_args(args) + mock_request.side_effect = [ + SRR["version_114"], # get + ] + with pytest.raises(AnsibleFailJson) as exc: + grid_gateway_module() + print("Info: test_module_fail_minimum_version_not_met: %s" % exc.value.args[0]["msg"]) + + # test create with ha groups + @patch("ansible_collections.netapp.storagegrid.plugins.module_utils.netapp.SGRestAPI.send_request") + def test_create_na_sg_grid_gateway_port_with_ha_group_binding_pass(self, mock_request): + args = self.set_args_create_na_sg_grid_gateway_port() + args["binding_mode"] = "ha-groups" + args["ha_groups"] = ["site1_primary", "da9ac524-9a16-4be0-9d6e-ec9b22218e75"] + set_module_args(args) + mock_request.side_effect = [ + SRR["version_116"], # get + SRR["ha_groups"], # get + SRR["empty_good"], # get + SRR["gateway_record_ha_group_binding"], # post + SRR["server_config"], # post + SRR["end_of_sequence"], + ] + my_obj = grid_gateway_module() + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + print("Info: test_create_na_sg_grid_gateway_port_with_ha_group_binding_pass: %s" % repr(exc.value.args[0])) + assert exc.value.args[0]["changed"] + + # test create with bad ha group ID + @patch("ansible_collections.netapp.storagegrid.plugins.module_utils.netapp.SGRestAPI.send_request") + def test_create_na_sg_grid_gateway_port_with_bad_ha_group_binding_fail(self, mock_request): + mock_request.side_effect = [ + SRR["version_116"], # get + SRR["ha_groups"], # get + ] + with pytest.raises(AnsibleFailJson) as exc: + args = self.set_args_create_na_sg_grid_gateway_port() + args["binding_mode"] = "ha-groups" + args["ha_groups"] = ["fffac524-9a16-4be0-9d6e-ec9b22218e75"] + set_module_args(args) + grid_gateway_module() + print("Info: test_create_na_sg_grid_gateway_port_with_bad_ha_group_binding_fail: %s" % repr(exc.value.args[0])) + + # test create with node interfaces + @patch("ansible_collections.netapp.storagegrid.plugins.module_utils.netapp.SGRestAPI.send_request") + def test_create_na_sg_grid_gateway_port_with_node_interface_binding_pass(self, mock_request): + args = self.set_args_create_na_sg_grid_gateway_port() + args["binding_mode"] = "node-interfaces" + args["node_interfaces"] = [ + {"node": "SITE1-ADM1", "interface": "eth2"}, + {"node": "SITE2-ADM1", "interface": "eth2"}, + ] + set_module_args(args) + mock_request.side_effect = [ + SRR["version_116"], # get + SRR["node_health"], # get + SRR["empty_good"], # get + SRR["gateway_record_node_interface_binding"], # post + SRR["server_config"], # post + SRR["end_of_sequence"], + ] + my_obj = grid_gateway_module() + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + print( + "Info: test_create_na_sg_grid_gateway_port_with_node_interface_binding_pass: %s" % repr(exc.value.args[0]) + ) + assert exc.value.args[0]["changed"] + + # test change from global to ha groups + @patch("ansible_collections.netapp.storagegrid.plugins.module_utils.netapp.SGRestAPI.send_request") + def test_update_na_sg_grid_gateway_binding_to_ha_groups_pass(self, mock_request): + args = self.set_args_create_na_sg_grid_gateway_port() + args["binding_mode"] = "ha-groups" + args["ha_groups"] = "site1_primary" + args["server_certificate"] = "-----BEGIN CERTIFICATE-----\nABCDEFGABCD\n-----END CERTIFICATE-----\n" + args["private_key"] = "-----BEGIN PRIVATE KEY-----\nABCDEFGABCD\n-----END PRIVATE KEY-----\n" + set_module_args(args) + mock_request.side_effect = [ + SRR["version_116"], # get + SRR["ha_groups"], # get + SRR["present_gateways_with_binding"], # get + SRR["server_config"], # get + SRR["gateway_record_ha_group_binding"], # put + SRR["server_config_cert_update"], # put + SRR["end_of_sequence"], + ] + my_obj = grid_gateway_module() + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + print("Info: test_update_na_sg_grid_gateway_binding_to_ha_groups_pass: %s" % repr(exc.value.args[0])) + assert exc.value.args[0]["changed"] + + # test rename by supplying gateway_id + @patch("ansible_collections.netapp.storagegrid.plugins.module_utils.netapp.SGRestAPI.send_request") + def test_update_na_sg_grid_gateway_rename_pass(self, mock_request): + args = self.set_args_create_na_sg_grid_gateway_port() + args["gateway_id"] = "e777d415-057f-4d37-9b0c-6d132d872ea0" + args["binding_mode"] = "ha-groups" + args["ha_groups"] = "site1_primary" + set_module_args(args) + mock_request.side_effect = [ + SRR["version_116"], # get + SRR["ha_groups"], # get + SRR["gateway_record_ha_group_binding"], # get + SRR["server_config"], # get + SRR["gateway_record_rename"], # put + SRR["end_of_sequence"], + ] + my_obj = grid_gateway_module() + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + print("Info: test_update_na_sg_grid_gateway_rename_pass: %s" % repr(exc.value.args[0])) + assert exc.value.args[0]["changed"] diff --git a/ansible_collections/netapp/storagegrid/tests/unit/plugins/modules/test_na_sg_grid_group.py b/ansible_collections/netapp/storagegrid/tests/unit/plugins/modules/test_na_sg_grid_group.py new file mode 100644 index 000000000..fd9fdf15c --- /dev/null +++ b/ansible_collections/netapp/storagegrid/tests/unit/plugins/modules/test_na_sg_grid_group.py @@ -0,0 +1,317 @@ +# (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 StorageGRID Grid Group Ansible module: na_sg_grid_group""" + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type +import json +import pytest +import sys +try: + from requests import Response +except ImportError: + if sys.version_info < (2, 7): + pytestmark = pytest.mark.skip('Skipping Unit Tests on 2.6 as requests is not available') + else: + raise + +from ansible_collections.netapp.storagegrid.tests.unit.compat import unittest +from ansible_collections.netapp.storagegrid.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.storagegrid.plugins.modules.na_sg_grid_group import ( + SgGridGroup as grid_group_module, +) + +# REST API canned responses when mocking send_request +SRR = { + # common responses + "empty_good": ({"data": []}, None), + "not_found": ( + {"status": "error", "code": 404, "data": {}}, + {"key": "error.404"}, + ), + "end_of_sequence": (None, "Unexpected call to send_request"), + "generic_error": (None, "Expected error"), + "delete_good": ({"code": 204}, None), + "grid_groups": ( + { + "data": [ + { + "displayName": "TestGridGroup", + "uniqueName": "group/testgridgroup", + "policies": { + "management": { + "tenantAccounts": True, + "metricsQuery": True, + "maintenance": True, + }, + }, + "id": "00000000-0000-0000-0000-000000000000", + "federated": False, + "groupURN": "urn:sgws:identity::12345678901234567890:group/testgridgroup", + } + ] + }, + None, + ), + "grid_group_record": ( + { + "data": { + "displayName": "TestGridGroup", + "uniqueName": "group/testgridgroup", + "policies": { + "management": { + "tenantAccounts": True, + "metricsQuery": True, + "maintenance": True, + }, + }, + "id": "00000000-0000-0000-0000-000000000000", + "federated": False, + "groupURN": "urn:sgws:identity::12345678901234567890:group/testgridgroup", + } + }, + None, + ), + "grid_group_record_update": ( + { + "data": { + "displayName": "TestGridGroup", + "uniqueName": "group/testgridgroup", + "policies": { + "management": { + "tenantAccounts": True, + "metricsQuery": False, + "maintenance": True, + "ilm": True, + }, + }, + "id": "00000000-0000-0000-0000-000000000000", + "federated": False, + "groupURN": "urn:sgws:identity::12345678901234567890:group/testgridgroup", + } + }, + 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""" + + 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 set_default_args_fail_check(self): + return dict( + { + "display_name": "TestGroup", + "management_policy": { + "maintenance": True, + "ilm": True, + "root_access": False, + }, + "auth_token": "01234567-5678-9abc-78de-9fgabc123def", + "validate_certs": False, + } + ) + + def set_default_args_pass_check(self): + return dict( + { + "state": "present", + "display_name": "TestGroup", + "unique_name": "group/testgroup", + "management_policy": { + "maintenance": True, + "ilm": True, + "root_access": False, + }, + "api_url": "gmi.example.com", + "auth_token": "01234567-5678-9abc-78de-9fgabc123def", + "validate_certs": False, + } + ) + + def set_args_create_na_sg_grid_group(self): + return dict( + { + "state": "present", + "display_name": "TestGridGroup", + "unique_name": "group/testgridgroup", + "management_policy": { + "tenant_accounts": True, + "metrics_query": True, + "maintenance": True, + }, + "api_url": "gmi.example.com", + "auth_token": "01234567-5678-9abc-78de-9fgabc123def", + "validate_certs": False, + } + ) + + def set_args_delete_na_sg_grid_group(self): + return dict( + { + "state": "absent", + "unique_name": "group/testgridgroup", + "api_url": "gmi.example.com", + "auth_token": "01234567-5678-9abc-78de-9fgabc123def", + "validate_certs": 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(self.set_default_args_fail_check()) + grid_group_module() + print( + "Info: test_module_fail_when_required_args_missing: %s" + % exc.value.args[0]["msg"] + ) + + def test_module_fail_when_required_args_present(self): + """ required arguments are reported as errors """ + with pytest.raises(AnsibleExitJson) as exc: + set_module_args(self.set_default_args_pass_check()) + grid_group_module() + exit_json(changed=True, msg="Induced arguments check") + print( + "Info: test_module_fail_when_required_args_present: %s" + % exc.value.args[0]["msg"] + ) + assert exc.value.args[0]["changed"] + + def test_module_fail_with_bad_unique_name(self): + """ error returned if unique_name doesn't start with group or federated_group """ + with pytest.raises(AnsibleFailJson) as exc: + args = self.set_default_args_pass_check() + args["unique_name"] = "noprefixgroup" + set_module_args(args) + grid_group_module() + print( + "Info: test_module_fail_with_bad_unique_name: %s" + % exc.value.args[0]["msg"] + ) + + @patch( + "ansible_collections.netapp.storagegrid.plugins.module_utils.netapp.SGRestAPI.send_request" + ) + def test_create_na_sg_grid_group_pass(self, mock_request): + set_module_args(self.set_args_create_na_sg_grid_group()) + my_obj = grid_group_module() + mock_request.side_effect = [ + SRR["not_found"], # get + SRR["grid_group_record"], # post + SRR["end_of_sequence"], + ] + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + print( + "Info: test_create_na_sg_grid_group_pass: %s" + % repr(exc.value.args[0]) + ) + assert exc.value.args[0]["changed"] + + @patch( + "ansible_collections.netapp.storagegrid.plugins.module_utils.netapp.SGRestAPI.send_request" + ) + def test_idempotent_create_na_sg_grid_group_pass(self, mock_request): + set_module_args(self.set_args_create_na_sg_grid_group()) + my_obj = grid_group_module() + mock_request.side_effect = [ + SRR["grid_group_record"], # get + SRR["end_of_sequence"], + ] + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + print( + "Info: test_idempotent_create_na_sg_grid_group_pass: %s" + % repr(exc.value.args[0]) + ) + assert not exc.value.args[0]["changed"] + + @patch( + "ansible_collections.netapp.storagegrid.plugins.module_utils.netapp.SGRestAPI.send_request" + ) + def test_update_na_sg_grid_group_pass(self, mock_request): + args = self.set_args_create_na_sg_grid_group() + args["management_policy"]["tenant_accounts"] = True + args["management_policy"]["metrics_query"] = False + args["management_policy"]["ilm"] = False + + set_module_args(args) + my_obj = grid_group_module() + mock_request.side_effect = [ + SRR["grid_group_record"], # get + SRR["grid_group_record_update"], # put + SRR["end_of_sequence"], + ] + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + print( + "Info: test_update_na_sg_grid_group_pass: %s" + % repr(exc.value.args[0]) + ) + assert exc.value.args[0]["changed"] + + @patch( + "ansible_collections.netapp.storagegrid.plugins.module_utils.netapp.SGRestAPI.send_request" + ) + def test_delete_na_sg_grid_group_pass(self, mock_request): + set_module_args(self.set_args_delete_na_sg_grid_group()) + my_obj = grid_group_module() + mock_request.side_effect = [ + SRR["grid_group_record"], # get + SRR["delete_good"], # delete + SRR["end_of_sequence"], + ] + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + print( + "Info: test_delete_na_sg_grid_group_pass: %s" + % repr(exc.value.args[0]) + ) + assert exc.value.args[0]["changed"] diff --git a/ansible_collections/netapp/storagegrid/tests/unit/plugins/modules/test_na_sg_grid_ha_group.py b/ansible_collections/netapp/storagegrid/tests/unit/plugins/modules/test_na_sg_grid_ha_group.py new file mode 100644 index 000000000..fbc8fd0ce --- /dev/null +++ b/ansible_collections/netapp/storagegrid/tests/unit/plugins/modules/test_na_sg_grid_ha_group.py @@ -0,0 +1,408 @@ +# (c) 2022, NetApp, Inc +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +""" unit tests NetApp StorageGRID Grid HA Group Ansible module: na_sg_grid_ha_group""" + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type +import json +import pytest +import sys + +try: + from requests import Response +except ImportError: + if sys.version_info < (2, 7): + pytestmark = pytest.mark.skip("Skipping Unit Tests on 2.6 as requests is not available") + else: + raise + +from ansible_collections.netapp.storagegrid.tests.unit.compat import unittest +from ansible_collections.netapp.storagegrid.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.storagegrid.plugins.modules.na_sg_grid_ha_group import ( + SgGridHaGroup as grid_ha_group_module, +) + +# REST API canned responses when mocking send_request +SRR = { + # common responses + "empty_good": ({"data": []}, None), + "not_found": ( + {"status": "error", "code": 404, "data": {}}, + {"key": "error.404"}, + ), + "end_of_sequence": (None, "Unexpected call to send_request"), + "generic_error": (None, "Expected error"), + "delete_good": (None, None), + "update_good": (None, None), + "version_114": ({"data": {"productVersion": "11.4.0-20200721.1338.d3969b3"}}, None), + "version_116": ({"data": {"productVersion": "11.6.0-20211120.0301.850531e"}}, None), + "ha_group_record": ( + { + "data": { + "id": "fbe724da-c941-439b-bb61-a536f6211ca9", + "name": "ansible-ha-group", + "description": None, + "virtualIps": ["192.168.50.5"], + "interfaces": [ + {"nodeId": "0b1866ed-d6e7-41b4-815f-bf867348b76b", "interface": "ens256"}, + {"nodeId": "7bb5bf05-a04c-4344-8abd-08c5c4048666", "interface": "ens256"}, + ], + "gatewayCidr": "192.168.50.1/24", + } + }, + None, + ), + "ha_group_record_twovip": ( + { + "data": { + "id": "fbe724da-c941-439b-bb61-a536f6211ca9", + "name": "ansible-ha-group", + "description": "2 VIP HA Group", + "virtualIps": ["192.168.50.5", "192.168.50.6"], + "interfaces": [ + {"nodeId": "0b1866ed-d6e7-41b4-815f-bf867348b76b", "interface": "ens256"}, + {"nodeId": "7bb5bf05-a04c-4344-8abd-08c5c4048666", "interface": "ens256"}, + ], + "gatewayCidr": "192.168.50.1/24", + } + }, + None, + ), + "ha_group_record_rename": ( + { + "data": { + "id": "fbe724da-c941-439b-bb61-a536f6211ca9", + "name": "ansible-ha-group-rename", + "description": None, + "virtualIps": ["192.168.50.5"], + "interfaces": [ + {"nodeId": "0b1866ed-d6e7-41b4-815f-bf867348b76b", "interface": "ens256"}, + {"nodeId": "7bb5bf05-a04c-4344-8abd-08c5c4048666", "interface": "ens256"}, + ], + "gatewayCidr": "192.168.50.1/24", + } + }, + None, + ), + "ha_groups": ( + { + "data": [ + { + "id": "c08e6dca-038d-4a05-9499-6fbd1e6a4c3e", + "name": "site1_primary", + "description": "test ha group", + "virtualIps": ["10.193.174.117"], + "interfaces": [ + { + "nodeId": "0b1866ed-d6e7-41b4-815f-bf867348b76b", + "nodeName": "SITE1-ADM1", + "interface": "eth2", + "preferredMaster": True, + }, + { + "nodeId": "970ad050-b68b-4aae-a94d-aef73f3095c4", + "nodeName": "SITE2-ADM1", + "interface": "eth2", + }, + ], + "gatewayCidr": "192.168.14.1/24", + }, + { + "id": "fbe724da-c941-439b-bb61-a536f6211ca9", + "name": "ansible-ha-group", + "description": None, + "virtualIps": ["192.168.50.5"], + "interfaces": [ + {"nodeId": "0b1866ed-d6e7-41b4-815f-bf867348b76b", "interface": "ens256"}, + {"nodeId": "7bb5bf05-a04c-4344-8abd-08c5c4048666", "interface": "ens256"}, + ], + "gatewayCidr": "192.168.50.1/24", + }, + ] + }, + None, + ), + "node_health": ( + { + "data": [ + { + "id": "0b1866ed-d6e7-41b4-815f-bf867348b76b", + "isPrimaryAdmin": True, + "name": "SITE1-ADM1", + "siteId": "ae56d06d-bd83-46bd-adce-77146b1d94bd", + "siteName": "SITE1", + "severity": "normal", + "state": "connected", + "type": "adminNode", + }, + { + "id": "7bb5bf05-a04c-4344-8abd-08c5c4048666", + "isPrimaryAdmin": None, + "name": "SITE1-G1", + "siteId": "ae56d06d-bd83-46bd-adce-77146b1d94bd", + "siteName": "SITE1", + "severity": "normal", + "state": "connected", + "type": "apiGatewayNode", + }, + { + "id": "970ad050-b68b-4aae-a94d-aef73f3095c4", + "isPrimaryAdmin": False, + "name": "SITE2-ADM1", + "siteId": "7c24002e-5157-43e9-83e5-02db9b265b02", + "siteName": "SITE2", + "severity": "normal", + "state": "connected", + "type": "adminNode", + }, + ] + }, + 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""" + + 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 set_default_args_fail_check(self): + return dict( + { + "gateway_cidr": "192.168.50.1/24", + "virtual_ips": "192.168.50.5", + "interfaces": [ + {"node": "SITE1-ADM1", "interface": "ens256"}, + {"node": "SITE1-G1", "interface": "ens256"}, + ], + "auth_token": "01234567-5678-9abc-78de-9fgabc123def", + "validate_certs": False, + } + ) + + def set_default_args_pass_check(self): + return dict( + { + "state": "present", + "name": "ansible-test-ha-group", + "gateway_cidr": "192.168.50.1/24", + "virtual_ips": "192.168.50.5", + "interfaces": [ + {"node": "SITE1-ADM1", "interface": "ens256"}, + {"node": "SITE1-G1", "interface": "ens256"}, + ], + "api_url": "https://gmi.example.com", + "auth_token": "01234567-5678-9abc-78de-9fgabc123def", + "validate_certs": False, + } + ) + + def set_args_create_na_sg_grid_ha_group(self): + return dict( + { + "state": "present", + "name": "ansible-ha-group", + "gateway_cidr": "192.168.50.1/24", + "virtual_ips": "192.168.50.5", + "interfaces": [ + {"node": "SITE1-ADM1", "interface": "ens256"}, + {"node": "SITE1-G1", "interface": "ens256"}, + ], + "api_url": "https://gmi.example.com", + "auth_token": "01234567-5678-9abc-78de-9fgabc123def", + "validate_certs": False, + } + ) + + def set_args_delete_na_sg_grid_ha_group(self): + return dict( + { + "state": "absent", + "name": "ansible-ha-group", + "gateway_cidr": "192.168.50.1/24", + "virtual_ips": "192.168.50.5", + "interfaces": [ + {"node": "SITE1-ADM1", "interface": "ens256"}, + {"node": "SITE1-G1", "interface": "ens256"}, + ], + "api_url": "https://gmi.example.com", + "auth_token": "01234567-5678-9abc-78de-9fgabc123def", + "validate_certs": False, + } + ) + + @patch("ansible_collections.netapp.storagegrid.plugins.module_utils.netapp.SGRestAPI.send_request") + def test_module_fail_when_required_args_missing(self, mock_request): + """required arguments are reported as errors""" + with pytest.raises(AnsibleFailJson) as exc: + set_module_args(self.set_default_args_fail_check()) + grid_ha_group_module() + print("Info: test_module_fail_when_required_args_missing: %s" % exc.value.args[0]["msg"]) + + @patch("ansible_collections.netapp.storagegrid.plugins.module_utils.netapp.SGRestAPI.send_request") + def test_module_pass_when_required_args_present(self, mock_request): + """required arguments are reported as errors""" + mock_request.side_effect = [ + SRR["node_health"], # get + ] + with pytest.raises(AnsibleExitJson) as exc: + set_module_args(self.set_default_args_pass_check()) + grid_ha_group_module() + exit_json(changed=True, msg="Induced arguments check") + print("Info: test_module_pass_when_required_args_present: %s" % exc.value.args[0]["msg"]) + assert exc.value.args[0]["changed"] + + @patch("ansible_collections.netapp.storagegrid.plugins.module_utils.netapp.SGRestAPI.send_request") + def test_create_na_sg_grid_ha_group_pass(self, mock_request): + set_module_args(self.set_args_create_na_sg_grid_ha_group()) + mock_request.side_effect = [ + SRR["node_health"], # get + SRR["empty_good"], # get + SRR["ha_group_record"], # post + SRR["end_of_sequence"], + ] + my_obj = grid_ha_group_module() + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + print("Info: test_create_na_sg_grid_ha_group_pass: %s" % repr(exc.value.args[0])) + assert exc.value.args[0]["changed"] + + @patch("ansible_collections.netapp.storagegrid.plugins.module_utils.netapp.SGRestAPI.send_request") + def test_idempotent_create_na_sg_grid_ha_group_pass(self, mock_request): + args = self.set_args_create_na_sg_grid_ha_group() + set_module_args(args) + mock_request.side_effect = [ + SRR["node_health"], # get + SRR["ha_groups"], # get + SRR["ha_group_record"], # get + SRR["end_of_sequence"], + ] + my_obj = grid_ha_group_module() + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + print("Info: test_idempotent_create_na_sg_grid_ha_group_pass: %s" % repr(exc.value.args[0])) + assert not exc.value.args[0]["changed"] + + @patch("ansible_collections.netapp.storagegrid.plugins.module_utils.netapp.SGRestAPI.send_request") + def test_update_na_sg_grid_ha_group_pass(self, mock_request): + args = self.set_args_create_na_sg_grid_ha_group() + args["description"] = "2 VIP HA Group" + args["virtual_ips"] = ["192.168.50.5", "192.168.50.6"] + set_module_args(args) + mock_request.side_effect = [ + SRR["node_health"], # get + SRR["ha_groups"], # get + SRR["ha_group_record"], # get + SRR["ha_group_record_twovip"], # post + SRR["end_of_sequence"], + ] + my_obj = grid_ha_group_module() + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + print("Info: test_update_na_sg_grid_ha_group_pass: %s" % repr(exc.value.args[0])) + assert exc.value.args[0]["changed"] + + @patch("ansible_collections.netapp.storagegrid.plugins.module_utils.netapp.SGRestAPI.send_request") + def test_rename_na_sg_grid_ha_group_pass(self, mock_request): + args = self.set_args_create_na_sg_grid_ha_group() + args["ha_group_id"] = "fbe724da-c941-439b-bb61-a536f6211ca9" + args["name"] = "ansible-ha-group-rename" + set_module_args(args) + mock_request.side_effect = [ + SRR["node_health"], # get + SRR["ha_group_record"], # get + SRR["ha_group_record_rename"], # post + SRR["end_of_sequence"], + ] + my_obj = grid_ha_group_module() + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + print("Info: test_rename_na_sg_grid_ha_group_pass: %s" % repr(exc.value.args[0])) + assert exc.value.args[0]["changed"] + + @patch("ansible_collections.netapp.storagegrid.plugins.module_utils.netapp.SGRestAPI.send_request") + def test_delete_na_sg_grid_ha_group_pass(self, mock_request): + args = self.set_args_delete_na_sg_grid_ha_group() + set_module_args(args) + mock_request.side_effect = [ + SRR["ha_groups"], # get + SRR["ha_group_record"], # get + SRR["delete_good"], # delete + SRR["end_of_sequence"], + ] + my_obj = grid_ha_group_module() + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + print("Info: test_delete_na_sg_grid_ha_group_pass: %s" % repr(exc.value.args[0])) + assert exc.value.args[0]["changed"] + + @patch("ansible_collections.netapp.storagegrid.plugins.module_utils.netapp.SGRestAPI.send_request") + def test_create_na_sg_grid_ha_group_bad_node_fail(self, mock_request): + args = self.set_args_create_na_sg_grid_ha_group() + args["interfaces"] = [{"node": "FakeNode", "interface": "eth0"}] + set_module_args(args) + mock_request.side_effect = [ + SRR["node_health"], # get + ] + with pytest.raises(AnsibleFailJson) as exc: + grid_ha_group_module() + print("Info: test_create_na_sg_grid_ha_group_bad_node_fail: %s" % repr(exc.value.args[0])) + assert exc.value.args[0]["failed"] + + @patch("ansible_collections.netapp.storagegrid.plugins.module_utils.netapp.SGRestAPI.send_request") + def test_update_na_sg_grid_ha_group_bad_ha_group_id_fail(self, mock_request): + args = self.set_args_create_na_sg_grid_ha_group() + args["ha_group_id"] = "ffffffff-ffff-aaaa-aaaa-000000000000" + args["virtual_ips"] = "192.168.50.10" + set_module_args(args) + mock_request.side_effect = [ + SRR["node_health"], # get + SRR["not_found"], # get + ] + with pytest.raises(AnsibleFailJson) as exc: + my_obj = grid_ha_group_module() + my_obj.apply() + print("Info: test_create_na_sg_grid_ha_group_bad_node_fail: %s" % repr(exc.value.args[0])) + assert exc.value.args[0]["failed"] diff --git a/ansible_collections/netapp/storagegrid/tests/unit/plugins/modules/test_na_sg_grid_identity_federation.py b/ansible_collections/netapp/storagegrid/tests/unit/plugins/modules/test_na_sg_grid_identity_federation.py new file mode 100644 index 000000000..058fc609e --- /dev/null +++ b/ansible_collections/netapp/storagegrid/tests/unit/plugins/modules/test_na_sg_grid_identity_federation.py @@ -0,0 +1,354 @@ +# (c) 2021, NetApp, Inc +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +""" unit tests NetApp StorageGRID Grid Identity Federation Ansible module: na_sg_grid_identity_federation""" + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type +import json +import pytest +import sys +try: + from requests import Response +except ImportError: + if sys.version_info < (2, 7): + pytestmark = pytest.mark.skip('Skipping Unit Tests on 2.6 as requests is not available') + else: + raise + +from ansible_collections.netapp.storagegrid.tests.unit.compat import unittest +from ansible_collections.netapp.storagegrid.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.storagegrid.plugins.modules.na_sg_grid_identity_federation import ( + SgGridIdentityFederation as grid_identity_federation_module, +) + +# REST API canned responses when mocking send_request +SRR = { + # common responses + "end_of_sequence": (None, "Unexpected call to send_request"), + "generic_error": (None, "Expected error"), + "check_mode_good": (None, None), + "identity_federation_unset": ( + { + "data": { + "id": "09876543-abcd-4321-abcd-0987654321ab", + "disable": True, + "type": "", + "ldapServiceType": "", + "hostname": "", + "port": 0, + "username": "", + "password": None, + "baseGroupDn": "", + "baseUserDn": "", + "disableTLS": False, + "enableLDAPS": False, + "caCert": "", + } + }, + None, + ), + "identity_federation": ( + { + "data": { + "id": "09876543-abcd-4321-abcd-0987654321ab", + "disable": False, + "type": "ldap", + "ldapServiceType": "Active Directory", + "hostname": "ad.example.com", + "port": 389, + "username": "binduser", + "password": "********", + "baseGroupDn": "DC=example,DC=com", + "baseUserDn": "DC=example,DC=com", + "disableTLS": True, + "enableLDAPS": False, + "caCert": "", + } + }, + None, + ), + "identity_federation_tls": ( + { + "data": { + "id": "09876543-abcd-4321-abcd-0987654321ab", + "disable": False, + "type": "ldap", + "ldapServiceType": "Active Directory", + "hostname": "ad.example.com", + "port": 636, + "username": "binduser", + "password": "********", + "baseGroupDn": "DC=example,DC=com", + "baseUserDn": "DC=example,DC=com", + "disableTLS": False, + "enableLDAPS": True, + "caCert": ( + "-----BEGIN CERTIFICATE-----\n" + "MIIF+DCCBOCgAwIBAgITRwAAAAIg5KzMrJo+kQAAAAAAAjANBgkqhkiG9w0BAQUF\n" + "ADBlMRIwEAYKCZImiZPyLGQBGRYCYXUxFjAUBgoJkiaJk/IsZAEZFgZuZXRhcHAx\n" + "FjAUBgoJkiaJk/IsZAEZFgZhdXNuZ3MxHzAdBgNVBAMTFmF1c25ncy1NRUxOR1NE\n" + "QzAxLUNBLTEwHhcNMjEwMjExMDkzMTIwWhcNMjMwMjExMDk0MTIwWjAAMIIBIjAN\n" + "BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAt2xPi4FS4Uc37KrDLEXXUoc4lhhT\n" + "uQmMnLc0PYZCIpzYOaosFIeGqco3woiC7wSZJ2whKE4RDcxxgE+azuGiSWVjIxIL\n" + "AimmcDhFid/T3KRN5jmkjBzUKuPBYzZBFih8iU9056rqgN7eMKQYjRwPeV0+AeiB\n" + "irw46OgkwVQu3shEUtXxZPP2Mb6Md23+4vSmcElUcW28Opt2q/M5fs7DNomG3eaG\n" + "-----END CERTIFICATE-----\n" + ), + } + }, + None, + ), + "identity_federation_disable": ( + { + "data": { + "id": "09876543-abcd-4321-abcd-0987654321ab", + "disable": True, + "type": "ldap", + "ldapServiceType": "Active Directory", + "hostname": "ad.example.com", + "port": 389, + "username": "binduser", + "password": "********", + "baseGroupDn": "DC=example,DC=com", + "baseUserDn": "DC=example,DC=com", + "disableTLS": True, + "enableLDAPS": False, + "caCert": "", + } + }, + 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""" + + 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 set_default_args_fail_check(self): + return dict( + { + "ldap_service_type": "Active Directory", + "hostname": "ad.example.com", + "port": 389, + "username": "binduser", + "password": "bindpass", + "base_group_dn": "DC=example,DC=com", + "base_user_dn": "DC=example,DC=com", + "tls": "Disabled", + "auth_token": "01234567-5678-9abc-78de-9fgabc123def", + "validate_certs": False, + } + ) + + def set_default_args_pass_check(self): + return dict( + { + "ldap_service_type": "Active Directory", + "hostname": "ad.example.com", + "port": 389, + "username": "binduser", + "password": "bindpass", + "base_group_dn": "DC=example,DC=com", + "base_user_dn": "DC=example,DC=com", + "tls": "Disabled", + "state": "present", + "api_url": "gmi.example.com", + "auth_token": "01234567-5678-9abc-78de-9fgabc123def", + "validate_certs": False, + } + ) + + def set_args_set_na_sg_grid_identity_federation(self): + return dict( + { + "ldap_service_type": "Active Directory", + "hostname": "ad.example.com", + "port": 389, + "username": "binduser", + "password": "bindpass", + "base_group_dn": "DC=example,DC=com", + "base_user_dn": "DC=example,DC=com", + "tls": "Disabled", + "state": "present", + "api_url": "gmi.example.com", + "auth_token": "01234567-5678-9abc-78de-9fgabc123def", + "validate_certs": False, + } + ) + + def set_args_set_na_sg_grid_identity_federation_tls(self): + return dict( + { + "ldap_service_type": "Active Directory", + "hostname": "ad.example.com", + "port": 636, + "username": "binduser", + "password": "bindpass", + "base_group_dn": "DC=example,DC=com", + "base_user_dn": "DC=example,DC=com", + "tls": "LDAPS", + "ca_cert": ( + "-----BEGIN CERTIFICATE-----\n" + "MIIF+DCCBOCgAwIBAgITRwAAAAIg5KzMrJo+kQAAAAAAAjANBgkqhkiG9w0BAQUF\n" + "ADBlMRIwEAYKCZImiZPyLGQBGRYCYXUxFjAUBgoJkiaJk/IsZAEZFgZuZXRhcHAx\n" + "FjAUBgoJkiaJk/IsZAEZFgZhdXNuZ3MxHzAdBgNVBAMTFmF1c25ncy1NRUxOR1NE\n" + "QzAxLUNBLTEwHhcNMjEwMjExMDkzMTIwWhcNMjMwMjExMDk0MTIwWjAAMIIBIjAN\n" + "BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAt2xPi4FS4Uc37KrDLEXXUoc4lhhT\n" + "uQmMnLc0PYZCIpzYOaosFIeGqco3woiC7wSZJ2whKE4RDcxxgE+azuGiSWVjIxIL\n" + "AimmcDhFid/T3KRN5jmkjBzUKuPBYzZBFih8iU9056rqgN7eMKQYjRwPeV0+AeiB\n" + "irw46OgkwVQu3shEUtXxZPP2Mb6Md23+4vSmcElUcW28Opt2q/M5fs7DNomG3eaG\n" + "-----END CERTIFICATE-----\n" + ), + "state": "present", + "api_url": "gmi.example.com", + "auth_token": "01234567-5678-9abc-78de-9fgabc123def", + "validate_certs": False, + } + ) + + def set_args_remove_na_sg_grid_identity_federation(self): + return dict( + { + "ldap_service_type": "Active Directory", + "hostname": "ad.example.com", + "state": "absent", + "api_url": "gmi.example.com", + "auth_token": "01234567-5678-9abc-78de-9fgabc123def", + "validate_certs": 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(self.set_default_args_fail_check()) + grid_identity_federation_module() + print("Info: test_module_fail_when_required_args_missing: %s" % exc.value.args[0]["msg"]) + + def test_module_fail_when_required_args_present(self): + """ required arguments are reported as errors """ + with pytest.raises(AnsibleExitJson) as exc: + set_module_args(self.set_default_args_pass_check()) + grid_identity_federation_module() + exit_json(changed=True, msg="Induced arguments check") + print("Info: test_module_fail_when_required_args_present: %s" % exc.value.args[0]["msg"]) + assert exc.value.args[0]["changed"] + + @patch("ansible_collections.netapp.storagegrid.plugins.module_utils.netapp.SGRestAPI.send_request") + def test_set_na_sg_grid_identity_federation_pass(self, mock_request): + set_module_args(self.set_args_set_na_sg_grid_identity_federation()) + my_obj = grid_identity_federation_module() + mock_request.side_effect = [ + SRR["identity_federation_unset"], # get + SRR["identity_federation"], # post + SRR["end_of_sequence"], + ] + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + print("Info: test_set_na_sg_grid_identity_federation_pass: %s" % repr(exc.value.args[0])) + assert exc.value.args[0]["changed"] + + @patch("ansible_collections.netapp.storagegrid.plugins.module_utils.netapp.SGRestAPI.send_request") + def test_idempotent_set_na_sg_grid_identity_federation_pass(self, mock_request): + args = self.set_args_set_na_sg_grid_identity_federation() + # remove password + del args["password"] + set_module_args(args) + my_obj = grid_identity_federation_module() + mock_request.side_effect = [ + SRR["identity_federation"], # get + SRR["end_of_sequence"], + ] + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + print("Info: test_idempotent_set_na_sg_grid_identity_federation_pass: %s" % repr(exc.value.args[0])) + assert not exc.value.args[0]["changed"] + + @patch("ansible_collections.netapp.storagegrid.plugins.module_utils.netapp.SGRestAPI.send_request") + def test_set_na_sg_grid_identity_federation_tls_pass(self, mock_request): + set_module_args(self.set_args_set_na_sg_grid_identity_federation_tls()) + my_obj = grid_identity_federation_module() + mock_request.side_effect = [ + SRR["identity_federation_unset"], # get + SRR["identity_federation_tls"], # post + SRR["end_of_sequence"], + ] + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + print("Info: test_set_na_sg_grid_identity_federation_pass: %s" % repr(exc.value.args[0])) + assert exc.value.args[0]["changed"] + + @patch("ansible_collections.netapp.storagegrid.plugins.module_utils.netapp.SGRestAPI.send_request") + def test_remove_na_sg_grid_identity_federation_pass(self, mock_request): + set_module_args(self.set_args_remove_na_sg_grid_identity_federation()) + my_obj = grid_identity_federation_module() + mock_request.side_effect = [ + SRR["identity_federation"], # get + SRR["identity_federation_disable"], # post + SRR["end_of_sequence"], + ] + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + print("Info: test_remove_na_sg_grid_identity_federation_pass: %s" % repr(exc.value.args[0])) + assert exc.value.args[0]["changed"] + + # test check mode + + @patch("ansible_collections.netapp.storagegrid.plugins.module_utils.netapp.SGRestAPI.send_request") + def test_check_mode_na_sg_grid_identity_federation_pass(self, mock_request): + set_module_args(self.set_args_set_na_sg_grid_identity_federation()) + my_obj = grid_identity_federation_module() + my_obj.module.check_mode = True + mock_request.side_effect = [ + SRR["identity_federation_unset"], # get + SRR["check_mode_good"], # post + SRR["end_of_sequence"], + ] + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + print("Info: test_check_mode_na_sg_grid_identity_federation_pass: %s" % repr(exc.value.args[0])) + assert exc.value.args[0]["changed"] + assert exc.value.args[0]["msg"] == "Connection test successful" diff --git a/ansible_collections/netapp/storagegrid/tests/unit/plugins/modules/test_na_sg_grid_info.py b/ansible_collections/netapp/storagegrid/tests/unit/plugins/modules/test_na_sg_grid_info.py new file mode 100644 index 000000000..2de26109b --- /dev/null +++ b/ansible_collections/netapp/storagegrid/tests/unit/plugins/modules/test_na_sg_grid_info.py @@ -0,0 +1,362 @@ +# (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 StorageGRID Grid Ansible module: na_sg_grid_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.storagegrid.tests.unit.compat import unittest +from ansible_collections.netapp.storagegrid.tests.unit.compat.mock import patch + +from ansible_collections.netapp.storagegrid.plugins.modules.na_sg_grid_info \ + import NetAppSgGatherInfo as sg_grid_info_module + +# REST API canned responses when mocking send_request +SRR = { + # common responses + 'empty_good': ({'data': []}, None), + 'end_of_sequence': (None, 'Unexpected call to send_request'), + 'generic_error': (None, 'Expected error'), + 'grid_accounts': ( + { + 'data': [ + { + 'name': 'TestTenantAccount1', + 'capabilities': ['management', 's3'], + 'policy': { + 'useAccountIdentitySource': True, + 'allowPlatformServices': False, + 'quotaObjectBytes': None, + }, + 'id': '12345678901234567891', + }, + { + 'name': 'TestTenantAccount2', + 'capabilities': ['management', 's3'], + 'policy': { + 'useAccountIdentitySource': True, + 'allowPlatformServices': False, + 'quotaObjectBytes': None, + }, + 'id': '12345678901234567892', + }, + { + 'name': 'TestTenantAccount3', + 'capabilities': ['management', 's3'], + 'policy': { + 'useAccountIdentitySource': True, + 'allowPlatformServices': False, + 'quotaObjectBytes': None, + }, + 'id': '12345678901234567893', + }, + ] + }, + None, + ), + 'grid_alarms': ({'data': []}, None), + 'grid_audit': ({'data': {}}, None), + 'grid_compliance_global': ({'data': {}}, None), + 'grid_config': ({'data': {}}, None), + 'grid_config_management': ({'data': {}}, None), + 'grid_config_product_version': ({'data': {}}, None), + 'grid_deactivated_features': ({'data': {}}, None), + 'grid_dns_servers': ({'data': []}, None), + 'grid_domain_names': ({'data': []}, None), + 'grid_ec_profiles': ({'data': []}, None), + 'grid_expansion': ({'data': {}}, None), + 'grid_expansion_nodes': ({'data': []}, None), + 'grid_expansion_sites': ({'data': []}, None), + 'grid_grid_networks': ({'data': []}, None), + 'grid_groups': ({'data': []}, None), + 'grid_health': ({'data': {}}, None), + 'grid_health_topology': ({'data': {}}, None), + 'grid_identity_source': ({'data': {}}, None), + 'grid_ilm_criteria': ({'data': []}, None), + 'grid_ilm_policies': ({'data': []}, None), + 'grid_ilm_rules': ({'data': []}, None), + 'grid_license': ({'data': []}, None), + 'grid_management_certificate': ({'data': {}}, None), + 'grid_ntp_servers': ({'data': []}, None), + 'grid_recovery': ({'data': {}}, None), + 'grid_recovery_available_nodes': ({'data': []}, None), + 'grid_regions': ({'data': []}, None), + 'grid_schemes': ({'data': []}, None), + 'grid_snmp': ({'data': {}}, None), + 'grid_storage_api_certificate': ({'data': {}}, None), + 'grid_untrusted_client_network': ({'data': {}}, None), + 'grid_users': ( + { + 'data': [ + { + 'accountId': '0', + 'disable': False, + 'federated': False, + 'fullName': 'Root', + 'id': '00000000-0000-0000-0000-000000000000', + 'memberOf': None, + 'uniqueName': 'root', + 'userURN': 'urn:sgws:identity::0:root' + }, + ] + }, + None + ), + 'grid_users_root': ( + { + 'data': { + 'accountId': '0', + 'disable': False, + 'federated': False, + 'fullName': 'Root', + 'id': '00000000-0000-0000-0000-000000000000', + 'memberOf': None, + 'uniqueName': 'root', + 'userURN': 'urn:sgws:identity::0:root' + }, + }, + None + ), + 'versions': ({'data': [2, 3]}, 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 ''' + 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 set_default_args_fail_check(self): + return dict( + { + 'api_url': 'sgmi.example.com', + } + ) + + def set_default_args_pass_check(self): + return dict( + { + 'api_url': 'sgmi.example.com', + 'auth_token': '01234567-5678-9abc-78de-9fgabc123def', + } + ) + + def set_default_optional_args_pass_check(self): + return dict( + { + 'api_url': 'sgmi.example.com', + 'auth_token': '01234567-5678-9abc-78de-9fgabc123def', + 'validate_certs': False, + 'gather_subset': ['all'], + 'parameters': {'limit': 5}, + } + ) + + def set_args_run_sg_gather_facts_for_all_info(self): + return dict({ + 'api_url': 'sgmi.example.com', + 'auth_token': '01234567-5678-9abc-78de-9fgabc123def', + 'validate_certs': False, + }) + + def set_args_run_sg_gather_facts_for_grid_accounts_info(self): + return dict({ + 'api_url': 'sgmi.example.com', + 'auth_token': '01234567-5678-9abc-78de-9fgabc123def', + 'validate_certs': False, + 'gather_subset': ['grid_accounts_info'], + }) + + def set_args_run_sg_gather_facts_for_grid_accounts_and_grid_users_root_info(self): + return dict({ + 'api_url': 'sgmi.example.com', + 'auth_token': '01234567-5678-9abc-78de-9fgabc123def', + 'validate_certs': False, + 'gather_subset': ['grid_accounts_info', 'grid/users/root'], + }) + + 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.set_default_args_fail_check()) + sg_grid_info_module() + print( + 'Info: test_module_fail_when_required_args_missing: %s' + % exc.value.args[0]['msg'] + ) + + def test_module_pass_when_required_args_present(self): + ''' required arguments are reported as errors ''' + with pytest.raises(AnsibleExitJson) as exc: + set_module_args(self.set_default_args_pass_check()) + sg_grid_info_module() + exit_json(changed=True, msg='Induced arguments check') + print( + 'Info: test_module_pass_when_required_args_present: %s' + % exc.value.args[0]['msg'] + ) + assert exc.value.args[0]['changed'] + + def test_module_pass_when_optional_args_present(self): + ''' Optional arguments are reported as pass ''' + with pytest.raises(AnsibleExitJson) as exc: + set_module_args(self.set_default_optional_args_pass_check()) + sg_grid_info_module() + exit_json(changed=True, msg='Induced arguments check') + print( + 'Info: test_module_pass_when_optional_args_present: %s' + % exc.value.args[0]['msg'] + ) + assert exc.value.args[0]['changed'] + + @patch('ansible_collections.netapp.storagegrid.plugins.module_utils.netapp.SGRestAPI.send_request') + def test_run_sg_gather_facts_for_all_info_pass(self, mock_request): + set_module_args(self.set_args_run_sg_gather_facts_for_all_info()) + my_obj = sg_grid_info_module() + gather_subset = [ + 'grid/accounts', + 'grid/alarms', + 'grid/audit', + 'grid/compliance-global', + 'grid/config', + 'grid/config/management', + 'grid/config/product-version', + 'grid/deactivated-features', + 'grid/dns-servers', + 'grid/domain-names', + 'grid/ec-profiles', + 'grid/expansion', + 'grid/expansion/nodes', + 'grid/expansion/sites', + 'grid/grid-networks', + 'grid/groups', + 'grid/health', + 'grid/health/topology', + 'grid/identity-source', + 'grid/ilm-criteria', + 'grid/ilm-policies', + 'grid/ilm-rules', + 'grid/license', + 'grid/management-certificate', + 'grid/ntp-servers', + 'grid/recovery/available-nodes', + 'grid/recovery', + 'grid/regions', + 'grid/schemes', + 'grid/snmp', + 'grid/storage-api-certificate', + 'grid/untrusted-client-network', + 'grid/users', + 'grid/users/root', + 'versions', + ] + mock_request.side_effect = [ + SRR['grid_accounts'], + SRR['grid_alarms'], + SRR['grid_audit'], + SRR['grid_compliance_global'], + SRR['grid_config'], + SRR['grid_config_management'], + SRR['grid_config_product_version'], + SRR['grid_deactivated_features'], + SRR['grid_dns_servers'], + SRR['grid_domain_names'], + SRR['grid_ec_profiles'], + SRR['grid_expansion'], + SRR['grid_expansion_nodes'], + SRR['grid_expansion_sites'], + SRR['grid_grid_networks'], + SRR['grid_groups'], + SRR['grid_health'], + SRR['grid_health_topology'], + SRR['grid_identity_source'], + SRR['grid_ilm_criteria'], + SRR['grid_ilm_policies'], + SRR['grid_ilm_rules'], + SRR['grid_license'], + SRR['grid_management_certificate'], + SRR['grid_ntp_servers'], + SRR['grid_recovery_available_nodes'], + SRR['grid_recovery'], + SRR['grid_regions'], + SRR['grid_schemes'], + SRR['grid_snmp'], + SRR['grid_storage_api_certificate'], + SRR['grid_untrusted_client_network'], + SRR['grid_users'], + SRR['grid_users_root'], + SRR['versions'], + SRR['end_of_sequence'], + ] + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + print('Info: test_run_sg_gather_facts_for_all_info_pass: %s' % repr(exc.value.args)) + assert set(exc.value.args[0]['sg_info']) == set(gather_subset) + + @patch('ansible_collections.netapp.storagegrid.plugins.module_utils.netapp.SGRestAPI.send_request') + def test_run_sg_gather_facts_for_grid_accounts_info_pass(self, mock_request): + set_module_args(self.set_args_run_sg_gather_facts_for_grid_accounts_info()) + my_obj = sg_grid_info_module() + gather_subset = ['grid/accounts'] + mock_request.side_effect = [ + SRR['grid_accounts'], + SRR['end_of_sequence'], + ] + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + print('Info: test_run_sg_gather_facts_for_grid_accounts_info_pass: %s' % repr(exc.value.args)) + assert set(exc.value.args[0]['sg_info']) == set(gather_subset) + + @patch('ansible_collections.netapp.storagegrid.plugins.module_utils.netapp.SGRestAPI.send_request') + def test_run_sg_gather_facts_for_grid_accounts_and_grid_users_root_info_pass(self, mock_request): + set_module_args(self.set_args_run_sg_gather_facts_for_grid_accounts_and_grid_users_root_info()) + my_obj = sg_grid_info_module() + gather_subset = ['grid/accounts', 'grid/users/root'] + mock_request.side_effect = [ + SRR['grid_accounts'], + SRR['grid_users_root'], + SRR['end_of_sequence'], + ] + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + print('Info: test_run_sg_gather_facts_for_grid_accounts_and_grid_users_root_info_pass: %s' % repr(exc.value.args)) + assert set(exc.value.args[0]['sg_info']) == set(gather_subset) diff --git a/ansible_collections/netapp/storagegrid/tests/unit/plugins/modules/test_na_sg_grid_ntp.py b/ansible_collections/netapp/storagegrid/tests/unit/plugins/modules/test_na_sg_grid_ntp.py new file mode 100644 index 000000000..eed83d49b --- /dev/null +++ b/ansible_collections/netapp/storagegrid/tests/unit/plugins/modules/test_na_sg_grid_ntp.py @@ -0,0 +1,257 @@ +# (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 StorageGRID NTP Ansible module: na_sg_grid_ntp""" + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type +import json +import pytest +import sys +try: + from requests import Response +except ImportError: + if sys.version_info < (2, 7): + pytestmark = pytest.mark.skip('Skipping Unit Tests on 2.6 as requests is not available') + else: + raise + +from ansible_collections.netapp.storagegrid.tests.unit.compat import unittest +from ansible_collections.netapp.storagegrid.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.storagegrid.plugins.modules.na_sg_grid_ntp import ( + SgGridNtp as grid_ntp_module, +) + +# REST API canned responses when mocking send_request +SRR = { + # common responses + "empty_good": ({"data": []}, None), + "not_found": ( + {"status": "error", "code": 404, "data": {}}, + {"key": "error.404"}, + ), + "end_of_sequence": (None, "Unexpected call to send_request"), + "generic_error": (None, "Expected error"), + "delete_good": ({"code": 204}, None), + "ntp_servers": ({"data": ["123.12.3.123", "123.1.23.123"]}, None,), + "update_ntp_servers": ({"data": ["123.12.3.123", "12.3.12.3"]}, None,), + "add_ntp_servers": ( + {"data": ["123.12.3.123", "123.1.23.123", "12.3.12.3"]}, + None, + ), + "remove_ntp_servers": ({"data": ["123.12.3.123"]}, 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""" + + 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 set_default_args_fail_check(self): + return dict( + { + "ntp_servers": "123.12.3.123,123.1.23.123", + "auth_token": "01234567-5678-9abc-78de-9fgabc123def", + "validate_certs": False, + } + ) + + def set_default_args_pass_check(self): + return dict( + { + "state": "present", + "passphrase": "secretstring", + "ntp_servers": "123.12.3.123,123.1.23.123", + "api_url": "gmi.example.com", + "auth_token": "01234567-5678-9abc-78de-9fgabc123def", + "validate_certs": False, + } + ) + + def set_default_args_pass_check(self): + return dict( + { + "state": "present", + "passphrase": "secretstring", + "ntp_servers": "123.12.3.123,123.1.23.123", + "api_url": "gmi.example.com", + "auth_token": "01234567-5678-9abc-78de-9fgabc123def", + "validate_certs": False, + } + ) + + def set_args_set_na_sg_grid_ntp_servers(self): + return dict( + { + "state": "present", + "passphrase": "secretstring", + "ntp_servers": "123.12.3.123,12.3.12.3", + "api_url": "gmi.example.com", + "auth_token": "01234567-5678-9abc-78de-9fgabc123def", + "validate_certs": False, + } + ) + + def set_args_add_na_sg_grid_ntp_servers(self): + return dict( + { + "state": "present", + "passphrase": "secretstring", + "ntp_servers": "123.12.3.123,123.1.23.123,12.3.12.3", + "api_url": "gmi.example.com", + "auth_token": "01234567-5678-9abc-78de-9fgabc123def", + "validate_certs": False, + } + ) + + def set_args_remove_na_sg_grid_ntp_server(self): + return dict( + { + "state": "present", + "passphrase": "secretstring", + "ntp_servers": "123.12.3.123", + "api_url": "gmi.example.com", + "auth_token": "01234567-5678-9abc-78de-9fgabc123def", + "validate_certs": 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(self.set_default_args_fail_check()) + grid_ntp_module() + print( + "Info: test_module_fail_when_required_args_missing: %s" + % exc.value.args[0]["msg"] + ) + + def test_module_fail_when_required_args_present(self): + """ required arguments are reported as errors """ + with pytest.raises(AnsibleExitJson) as exc: + set_module_args(self.set_default_args_pass_check()) + grid_ntp_module() + exit_json(changed=True, msg="Induced arguments check") + print( + "Info: test_module_fail_when_required_args_present: %s" + % exc.value.args[0]["msg"] + ) + assert exc.value.args[0]["changed"] + + @patch( + "ansible_collections.netapp.storagegrid.plugins.module_utils.netapp.SGRestAPI.send_request" + ) + def test_set_na_sg_grid_ntp_servers_pass(self, mock_request): + set_module_args(self.set_args_set_na_sg_grid_ntp_servers()) + my_obj = grid_ntp_module() + mock_request.side_effect = [ + SRR["ntp_servers"], # get + SRR["update_ntp_servers"], # post + SRR["end_of_sequence"], + ] + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + print( + "Info: test_set_na_sg_grid_ntp_servers_pass: %s" + % repr(exc.value.args[0]) + ) + assert exc.value.args[0]["changed"] + + @patch( + "ansible_collections.netapp.storagegrid.plugins.module_utils.netapp.SGRestAPI.send_request" + ) + def test_idempotent_set_na_sg_grid_ntp_servers_pass(self, mock_request): + set_module_args(self.set_args_set_na_sg_grid_ntp_servers()) + my_obj = grid_ntp_module() + mock_request.side_effect = [ + SRR["update_ntp_servers"], # get + SRR["end_of_sequence"], + ] + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + print( + "Info: test_idempotent_set_na_sg_grid_ntp_servers_pass: %s" + % repr(exc.value.args[0]) + ) + assert not exc.value.args[0]["changed"] + + @patch( + "ansible_collections.netapp.storagegrid.plugins.module_utils.netapp.SGRestAPI.send_request" + ) + def test_add_na_sg_grid_ntp_servers_pass(self, mock_request): + set_module_args(self.set_args_add_na_sg_grid_ntp_servers()) + my_obj = grid_ntp_module() + mock_request.side_effect = [ + SRR["ntp_servers"], # get + SRR["add_ntp_servers"], # post + SRR["end_of_sequence"], + ] + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + print( + "Info: test_add_na_sg_grid_ntp_servers_pass: %s" + % repr(exc.value.args[0]) + ) + assert exc.value.args[0]["changed"] + + @patch( + "ansible_collections.netapp.storagegrid.plugins.module_utils.netapp.SGRestAPI.send_request" + ) + def test_remove_na_sg_grid_ntp_servers_pass(self, mock_request): + set_module_args(self.set_args_remove_na_sg_grid_ntp_server()) + my_obj = grid_ntp_module() + mock_request.side_effect = [ + SRR["ntp_servers"], # get + SRR["remove_ntp_servers"], # post + SRR["end_of_sequence"], + ] + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + print( + "Info: test_remove_na_sg_grid_ntp_servers_pass: %s" + % repr(exc.value.args[0]) + ) + assert exc.value.args[0]["changed"] diff --git a/ansible_collections/netapp/storagegrid/tests/unit/plugins/modules/test_na_sg_grid_regions.py b/ansible_collections/netapp/storagegrid/tests/unit/plugins/modules/test_na_sg_grid_regions.py new file mode 100644 index 000000000..585ba3f45 --- /dev/null +++ b/ansible_collections/netapp/storagegrid/tests/unit/plugins/modules/test_na_sg_grid_regions.py @@ -0,0 +1,206 @@ +# (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 StorageGRID Regions Ansible module: na_sg_grid_regions""" + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type +import json +import pytest +import sys +try: + from requests import Response +except ImportError: + if sys.version_info < (2, 7): + pytestmark = pytest.mark.skip('Skipping Unit Tests on 2.6 as requests is not available') + else: + raise + +from ansible_collections.netapp.storagegrid.tests.unit.compat import unittest +from ansible_collections.netapp.storagegrid.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.storagegrid.plugins.modules.na_sg_grid_regions import ( + SgGridRegions as grid_regions_module, +) + +# REST API canned responses when mocking send_request +SRR = { + # common responses + "empty_good": ({"data": []}, None), + "not_found": ( + {"status": "error", "code": 404, "data": {}}, + {"key": "error.404"}, + ), + "end_of_sequence": (None, "Unexpected call to send_request"), + "generic_error": (None, "Expected error"), + "delete_good": ({"code": 204}, None), + "default_regions": ({"data": ["us-east-1"]}, None,), + "regions": ({"data": ["us-east-1", "us-west-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""" + + 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 set_default_args_fail_check(self): + return dict( + { + "regions": "us-east-1,us-west-1", + "auth_token": "01234567-5678-9abc-78de-9fgabc123def", + "validate_certs": False, + } + ) + + def set_default_args_pass_check(self): + return dict( + { + "state": "present", + "regions": "us-east-1,us-west-1", + "api_url": "gmi.example.com", + "auth_token": "01234567-5678-9abc-78de-9fgabc123def", + "validate_certs": False, + } + ) + + def set_args_set_na_sg_grid_regions(self): + return dict( + { + "state": "present", + "regions": "us-east-1,us-west-1", + "api_url": "gmi.example.com", + "auth_token": "01234567-5678-9abc-78de-9fgabc123def", + "validate_certs": False, + } + ) + + def set_args_remove_na_sg_grid_regions(self): + return dict( + { + "state": "present", + "regions": "us-east-1", + "api_url": "gmi.example.com", + "auth_token": "01234567-5678-9abc-78de-9fgabc123def", + "validate_certs": 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(self.set_default_args_fail_check()) + grid_regions_module() + print( + "Info: test_module_fail_when_required_args_missing: %s" + % exc.value.args[0]["msg"] + ) + + def test_module_fail_when_required_args_present(self): + """ required arguments are reported as errors """ + with pytest.raises(AnsibleExitJson) as exc: + set_module_args(self.set_default_args_pass_check()) + grid_regions_module() + exit_json(changed=True, msg="Induced arguments check") + print( + "Info: test_module_fail_when_required_args_present: %s" + % exc.value.args[0]["msg"] + ) + assert exc.value.args[0]["changed"] + + @patch( + "ansible_collections.netapp.storagegrid.plugins.module_utils.netapp.SGRestAPI.send_request" + ) + def test_set_na_sg_grid_regions_pass(self, mock_request): + set_module_args(self.set_args_set_na_sg_grid_regions()) + my_obj = grid_regions_module() + mock_request.side_effect = [ + SRR["default_regions"], # get + SRR["regions"], # post + SRR["end_of_sequence"], + ] + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + print( + "Info: test_set_na_sg_grid_regions_pass: %s" + % repr(exc.value.args[0]) + ) + assert exc.value.args[0]["changed"] + + @patch( + "ansible_collections.netapp.storagegrid.plugins.module_utils.netapp.SGRestAPI.send_request" + ) + def test_idempotent_set_na_sg_grid_regions_pass(self, mock_request): + set_module_args(self.set_args_set_na_sg_grid_regions()) + my_obj = grid_regions_module() + mock_request.side_effect = [ + SRR["regions"], # get + SRR["end_of_sequence"], + ] + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + print( + "Info: test_idempotent_set_na_sg_grid_regions_pass: %s" + % repr(exc.value.args[0]) + ) + assert not exc.value.args[0]["changed"] + + @patch( + "ansible_collections.netapp.storagegrid.plugins.module_utils.netapp.SGRestAPI.send_request" + ) + def test_remove_na_sg_grid_regions_pass(self, mock_request): + set_module_args(self.set_args_remove_na_sg_grid_regions()) + my_obj = grid_regions_module() + mock_request.side_effect = [ + SRR["regions"], # get + SRR["default_regions"], # post + SRR["end_of_sequence"], + ] + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + print( + "Info: test_remove_na_sg_grid_regions_pass: %s" + % repr(exc.value.args[0]) + ) + assert exc.value.args[0]["changed"] diff --git a/ansible_collections/netapp/storagegrid/tests/unit/plugins/modules/test_na_sg_grid_traffic_classes.py b/ansible_collections/netapp/storagegrid/tests/unit/plugins/modules/test_na_sg_grid_traffic_classes.py new file mode 100644 index 000000000..42fce0e3b --- /dev/null +++ b/ansible_collections/netapp/storagegrid/tests/unit/plugins/modules/test_na_sg_grid_traffic_classes.py @@ -0,0 +1,355 @@ +# (c) 2022, NetApp, Inc +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +""" unit tests NetApp StorageGRID Grid HA Group Ansible module: na_sg_grid_traffic_classes""" + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type +import json +import pytest +import sys + +try: + from requests import Response +except ImportError: + if sys.version_info < (2, 7): + pytestmark = pytest.mark.skip("Skipping Unit Tests on 2.6 as requests is not available") + else: + raise + +from ansible_collections.netapp.storagegrid.tests.unit.compat import unittest +from ansible_collections.netapp.storagegrid.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.storagegrid.plugins.modules.na_sg_grid_traffic_classes import ( + SgGridTrafficClasses as grid_traffic_classes_module, +) + +# REST API canned responses when mocking send_request +SRR = { + # common responses + "empty_good": ({"data": []}, None), + "not_found": ( + {"status": "error", "code": 404, "data": {}}, + {"key": "error.404"}, + ), + "end_of_sequence": (None, "Unexpected call to send_request"), + "generic_error": (None, "Expected error"), + "delete_good": (None, None), + "update_good": (None, None), + "version_114": ({"data": {"productVersion": "11.4.0-20200721.1338.d3969b3"}}, None), + "version_116": ({"data": {"productVersion": "11.6.0-20211120.0301.850531e"}}, None), + "traffic_class_record": ( + { + "data": { + "id": "6b2946e6-7fed-40d0-9262-8e922580aba7", + "name": "ansible-test-traffic-class", + "description": "Ansible Test", + "matchers": [ + {"type": "cidr", "inverse": False, "members": ["192.168.50.0/24"]}, + {"type": "bucket", "inverse": False, "members": ["ansible-test1", "ansible-test2"]}, + ], + "limits": [], + } + }, + None, + ), + "traffic_class_record_updated": ( + { + "data": { + "id": "6b2946e6-7fed-40d0-9262-8e922580aba7", + "name": "ansible-test-traffic-class", + "description": "Ansible Test", + "matchers": [ + {"type": "cidr", "inverse": False, "members": ["192.168.50.0/24"]}, + {"type": "bucket", "inverse": False, "members": ["ansible-test1", "ansible-test2"]}, + ], + "limits": [{"type": "aggregateBandwidthIn", "value": 888888}], + } + }, + None, + ), + "traffic_class_record_rename": ( + { + "data": { + "id": "6b2946e6-7fed-40d0-9262-8e922580aba7", + "name": "ansible-test-traffic-class-rename", + "description": "Ansible Test", + "matchers": [ + {"type": "cidr", "inverse": False, "members": ["192.168.50.0/24"]}, + {"type": "bucket", "inverse": False, "members": ["ansible-test1", "ansible-test2"]}, + ], + "limits": [], + } + }, + None, + ), + "traffic_classes": ( + { + "data": [ + { + "id": "6b2946e6-7fed-40d0-9262-8e922580aba7", + "name": "ansible-test-traffic-class", + "description": "Ansible Test", + }, + { + "id": "531e6be1-e9b1-4010-bb79-03437c7c13d2", + "name": "policy-test1", + "description": "First test policy", + }, + ] + }, + None, + ), + "node_health": ( + { + "data": [ + { + "id": "0b1866ed-d6e7-41b4-815f-bf867348b76b", + "isPrimaryAdmin": True, + "name": "SITE1-ADM1", + "siteId": "ae56d06d-bd83-46bd-adce-77146b1d94bd", + "siteName": "SITE1", + "severity": "normal", + "state": "connected", + "type": "adminNode", + }, + { + "id": "7bb5bf05-a04c-4344-8abd-08c5c4048666", + "isPrimaryAdmin": None, + "name": "SITE1-G1", + "siteId": "ae56d06d-bd83-46bd-adce-77146b1d94bd", + "siteName": "SITE1", + "severity": "normal", + "state": "connected", + "type": "apiGatewayNode", + }, + { + "id": "970ad050-b68b-4aae-a94d-aef73f3095c4", + "isPrimaryAdmin": False, + "name": "SITE2-ADM1", + "siteId": "7c24002e-5157-43e9-83e5-02db9b265b02", + "siteName": "SITE2", + "severity": "normal", + "state": "connected", + "type": "adminNode", + }, + ] + }, + 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""" + + 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 set_default_args_fail_check(self): + return dict( + { + "matchers": [ + {"type": "bucket", "members": ["ansible-test1", "ansible-test2"]}, + {"type": "cidr", "members": ["192.168.50.0/24"]}, + ], + "auth_token": "01234567-5678-9abc-78de-9fgabc123def", + "validate_certs": False, + } + ) + + def set_default_args_pass_check(self): + return dict( + { + "state": "present", + "name": "ansible-test-traffic-class", + "matchers": [ + {"type": "bucket", "members": ["ansible-test1", "ansible-test2"]}, + {"type": "cidr", "members": ["192.168.50.0/24"]}, + ], + "api_url": "gmi.example.com", + "auth_token": "01234567-5678-9abc-78de-9fgabc123def", + "validate_certs": False, + } + ) + + def set_args_create_na_sg_grid_traffic_class(self): + return dict( + { + "state": "present", + "name": "ansible-test-traffic-class", + "description": "Ansible Test", + "matchers": [ + {"type": "bucket", "members": ["ansible-test1", "ansible-test2"]}, + {"type": "cidr", "members": ["192.168.50.0/24"]}, + ], + "api_url": "gmi.example.com", + "auth_token": "01234567-5678-9abc-78de-9fgabc123def", + "validate_certs": False, + } + ) + + def set_args_delete_na_sg_grid_traffic_class(self): + return dict( + { + "state": "absent", + "name": "ansible-test-traffic-class", + "api_url": "gmi.example.com", + "auth_token": "01234567-5678-9abc-78de-9fgabc123def", + "validate_certs": False, + } + ) + + @patch("ansible_collections.netapp.storagegrid.plugins.module_utils.netapp.SGRestAPI.send_request") + def test_module_fail_when_required_args_missing(self, mock_request): + """required arguments are reported as errors""" + with pytest.raises(AnsibleFailJson) as exc: + set_module_args(self.set_default_args_fail_check()) + grid_traffic_classes_module() + print("Info: test_module_fail_when_required_args_missing: %s" % exc.value.args[0]["msg"]) + + @patch("ansible_collections.netapp.storagegrid.plugins.module_utils.netapp.SGRestAPI.send_request") + def test_module_pass_when_required_args_present(self, mock_request): + """required arguments are reported as errors""" + mock_request.side_effect = [ + SRR["node_health"], # get + ] + with pytest.raises(AnsibleExitJson) as exc: + set_module_args(self.set_default_args_pass_check()) + grid_traffic_classes_module() + exit_json(changed=True, msg="Induced arguments check") + print("Info: test_module_pass_when_required_args_present: %s" % exc.value.args[0]["msg"]) + assert exc.value.args[0]["changed"] + + @patch("ansible_collections.netapp.storagegrid.plugins.module_utils.netapp.SGRestAPI.send_request") + def test_create_na_sg_grid_traffic_class_pass(self, mock_request): + set_module_args(self.set_args_create_na_sg_grid_traffic_class()) + mock_request.side_effect = [ + SRR["empty_good"], # get + SRR["traffic_class_record"], # post + SRR["end_of_sequence"], + ] + my_obj = grid_traffic_classes_module() + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + print("Info: test_create_na_sg_grid_traffic_class_pass: %s" % repr(exc.value.args[0])) + assert exc.value.args[0]["changed"] + + @patch("ansible_collections.netapp.storagegrid.plugins.module_utils.netapp.SGRestAPI.send_request") + def test_idempotent_create_na_sg_grid_traffic_class_pass(self, mock_request): + args = self.set_args_create_na_sg_grid_traffic_class() + set_module_args(args) + mock_request.side_effect = [ + SRR["traffic_classes"], # get + SRR["traffic_class_record"], # get + SRR["end_of_sequence"], + ] + my_obj = grid_traffic_classes_module() + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + print("Info: test_idempotent_create_na_sg_grid_traffic_class_pass: %s" % repr(exc.value.args[0])) + assert not exc.value.args[0]["changed"] + + @patch("ansible_collections.netapp.storagegrid.plugins.module_utils.netapp.SGRestAPI.send_request") + def test_update_na_sg_grid_traffic_class_pass(self, mock_request): + args = self.set_args_create_na_sg_grid_traffic_class() + args["description"] = "Ansible Test with Limit" + args["limits"] = [{"type": "aggregateBandwidthIn", "value": 888888}] + set_module_args(args) + mock_request.side_effect = [ + SRR["traffic_classes"], # get + SRR["traffic_class_record"], # get + SRR["traffic_class_record_updated"], # put + SRR["end_of_sequence"], + ] + my_obj = grid_traffic_classes_module() + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + print("Info: test_update_na_sg_grid_traffic_class_pass: %s" % repr(exc.value.args[0])) + assert exc.value.args[0]["changed"] + + @patch("ansible_collections.netapp.storagegrid.plugins.module_utils.netapp.SGRestAPI.send_request") + def test_rename_na_sg_grid_traffic_class_pass(self, mock_request): + args = self.set_args_create_na_sg_grid_traffic_class() + args["policy_id"] = "6b2946e6-7fed-40d0-9262-8e922580aba7" + args["name"] = "ansible-test-traffic-class-rename" + set_module_args(args) + mock_request.side_effect = [ + SRR["traffic_class_record"], # get + SRR["traffic_class_record_rename"], # put + SRR["end_of_sequence"], + ] + my_obj = grid_traffic_classes_module() + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + print("Info: test_rename_na_sg_grid_traffic_class_pass: %s" % repr(exc.value.args[0])) + assert exc.value.args[0]["changed"] + + @patch("ansible_collections.netapp.storagegrid.plugins.module_utils.netapp.SGRestAPI.send_request") + def test_delete_na_sg_grid_traffic_class_pass(self, mock_request): + args = self.set_args_delete_na_sg_grid_traffic_class() + set_module_args(args) + mock_request.side_effect = [ + SRR["traffic_classes"], # get + SRR["traffic_class_record"], # get + SRR["delete_good"], # delete + SRR["end_of_sequence"], + ] + my_obj = grid_traffic_classes_module() + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + print("Info: test_delete_na_sg_grid_traffic_class_pass: %s" % repr(exc.value.args[0])) + assert exc.value.args[0]["changed"] + + @patch("ansible_collections.netapp.storagegrid.plugins.module_utils.netapp.SGRestAPI.send_request") + def test_update_na_sg_grid_traffic_class_bad_policy_id_fail(self, mock_request): + args = self.set_args_create_na_sg_grid_traffic_class() + args["policy_id"] = "ffffffff-ffff-aaaa-aaaa-000000000000" + args["description"] = "Bad ID" + set_module_args(args) + mock_request.side_effect = [ + SRR["not_found"], # get + ] + with pytest.raises(AnsibleFailJson) as exc: + my_obj = grid_traffic_classes_module() + my_obj.apply() + print("Info: test_update_na_sg_grid_traffic_class_bad_policy_id_fail: %s" % repr(exc.value.args[0])) + assert exc.value.args[0]["failed"] diff --git a/ansible_collections/netapp/storagegrid/tests/unit/plugins/modules/test_na_sg_grid_user.py b/ansible_collections/netapp/storagegrid/tests/unit/plugins/modules/test_na_sg_grid_user.py new file mode 100644 index 000000000..c8ec38c09 --- /dev/null +++ b/ansible_collections/netapp/storagegrid/tests/unit/plugins/modules/test_na_sg_grid_user.py @@ -0,0 +1,476 @@ +# (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 StorageGRID Grid User Ansible module: na_sg_grid_user""" + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type +import json +import pytest +import sys +try: + from requests import Response +except ImportError: + if sys.version_info < (2, 7): + pytestmark = pytest.mark.skip('Skipping Unit Tests on 2.6 as requests is not available') + else: + raise + +from ansible_collections.netapp.storagegrid.tests.unit.compat import unittest +from ansible_collections.netapp.storagegrid.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.storagegrid.plugins.modules.na_sg_grid_user import ( + SgGridUser as grid_user_module, +) + +# REST API canned responses when mocking send_request +SRR = { + # common responses + "empty_good": ({"data": []}, None), + "not_found": ({"status": "error", "code": 404, "data": {}}, {"key": "error.404"},), + "end_of_sequence": (None, "Unexpected call to send_request"), + "generic_error": (None, "Expected error"), + "delete_good": ({"code": 204}, None), + "pw_change_good": ({"code": 204}, None), + "grid_groups": ( + { + "data": [ + { + "displayName": "TestGridGroup1", + "uniqueName": "group/testgridgroup1", + "accountId": "12345678901234567890", + "id": "12345678-abcd-1234-abcd-1234567890ab", + "federated": False, + "groupURN": "urn:sgws:identity::12345678901234567890:group/testgridgroup1", + }, + { + "displayName": "TestGridGroup2", + "uniqueName": "group/testgridgroup2", + "accountId": "12345678901234567890", + "id": "87654321-abcd-1234-cdef-1234567890ab", + "federated": False, + "groupURN": "urn:sgws:identity::12345678901234567890:group/testgridgroup2", + }, + ] + }, + None, + ), + "grid_users": ( + { + "data": [ + { + "id": "09876543-abcd-4321-abcd-0987654321ab", + "accountId": "12345678901234567890", + "fullName": "testgriduser", + "uniqueName": "user/ansible-sg-adm-user1", + "userURN": "urn:sgws:identity::12345678901234567890:user/testgriduser", + "federated": False, + "memberOf": ["12345678-abcd-1234-abcd-1234567890ab"], + "disable": False, + } + ] + }, + None, + ), + "grid_user_record_no_group": ( + { + "data": { + "id": "09876543-abcd-4321-abcd-0987654321ab", + "accountId": "12345678901234567890", + "fullName": "testgriduser", + "uniqueName": "user/ansible-sg-adm-user1", + "userURN": "urn:sgws:identity::12345678901234567890:user/testgriduser", + "federated": False, + "disable": False, + } + }, + None, + ), + "grid_user_record": ( + { + "data": { + "id": "09876543-abcd-4321-abcd-0987654321ab", + "accountId": "12345678901234567890", + "fullName": "testgriduser", + "uniqueName": "user/ansible-sg-adm-user1", + "userURN": "urn:sgws:identity::12345678901234567890:user/testgriduser", + "federated": False, + "memberOf": ["12345678-abcd-1234-abcd-1234567890ab"], + "disable": False, + } + }, + None, + ), + "grid_user_record_update": ( + { + "data": { + "id": "09876543-abcd-4321-abcd-0987654321ab", + "accountId": "12345678901234567890", + "fullName": "testgriduser", + "uniqueName": "user/ansible-sg-adm-user1", + "userURN": "urn:sgws:identity::12345678901234567890:user/testgriduser", + "federated": False, + "memberOf": [ + "12345678-abcd-1234-abcd-1234567890ab", + "87654321-abcd-1234-cdef-1234567890ab", + ], + "disable": 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""" + + 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 set_default_args_fail_check(self): + return dict( + { + "full_name": "TestUser", + "api_url": "gmi.example.com", + "auth_token": "01234567-5678-9abc-78de-9fgabc123def", + "validate_certs": False, + } + ) + + def set_default_args_pass_check(self): + return dict( + { + "state": "present", + "full_name": "TestUser", + "unique_name": "user/testuser", + "api_url": "gmi.example.com", + "auth_token": "01234567-5678-9abc-78de-9fgabc123def", + "validate_certs": False, + } + ) + + def set_args_create_na_sg_grid_user_no_group(self): + return dict( + { + "state": "present", + "full_name": "TestUser", + "unique_name": "user/testuser", + "api_url": "gmi.example.com", + "auth_token": "01234567-5678-9abc-78de-9fgabc123def", + "validate_certs": False, + } + ) + + def set_args_create_na_sg_grid_user(self): + return dict( + { + "state": "present", + "full_name": "TestUser", + "unique_name": "user/testuser", + "member_of": ["group/testgridgroup1"], + "api_url": "gmi.example.com", + "auth_token": "01234567-5678-9abc-78de-9fgabc123def", + "validate_certs": False, + } + ) + + def set_args_delete_na_sg_grid_user(self): + return dict( + { + "state": "absent", + "unique_name": "user/testuser", + "api_url": "gmi.example.com", + "auth_token": "01234567-5678-9abc-78de-9fgabc123def", + "validate_certs": 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(self.set_default_args_fail_check()) + grid_user_module() + print( + "Info: test_module_fail_when_required_args_missing: %s" + % exc.value.args[0]["msg"] + ) + + def test_module_fail_when_required_args_present(self): + """ required arguments are reported as errors """ + with pytest.raises(AnsibleExitJson) as exc: + set_module_args(self.set_default_args_pass_check()) + grid_user_module() + exit_json(changed=True, msg="Induced arguments check") + print( + "Info: test_module_fail_when_required_args_present: %s" + % exc.value.args[0]["msg"] + ) + assert exc.value.args[0]["changed"] + + def test_module_fail_with_bad_unique_name(self): + """ error returned if unique_name doesn't start with user or federated_user """ + with pytest.raises(AnsibleFailJson) as exc: + args = self.set_default_args_pass_check() + args["unique_name"] = "noprefixuser" + set_module_args(args) + grid_user_module() + print( + "Info: test_module_fail_with_bad_unique_name: %s" % exc.value.args[0]["msg"] + ) + + def set_args_create_na_sg_grid_user_with_password(self): + return dict( + { + "state": "present", + "full_name": "TestUser", + "unique_name": "user/testuser", + "member_of": ["group/testgridgroup1"], + "password": "netapp123", + "api_url": "gmi.example.com", + "auth_token": "01234567-5678-9abc-78de-9fgabc123def", + "validate_certs": False, + } + ) + + @patch( + "ansible_collections.netapp.storagegrid.plugins.module_utils.netapp.SGRestAPI.send_request" + ) + def test_create_na_sg_grid_user_no_group_pass(self, mock_request): + set_module_args(self.set_args_create_na_sg_grid_user_no_group()) + my_obj = grid_user_module() + mock_request.side_effect = [ + SRR["not_found"], # get + SRR["grid_user_record_no_group"], # post + SRR["end_of_sequence"], + ] + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + print( + "Info: test_create_na_sg_grid_user_no_group_pass: %s" + % repr(exc.value.args[0]) + ) + assert exc.value.args[0]["changed"] + + @patch( + "ansible_collections.netapp.storagegrid.plugins.module_utils.netapp.SGRestAPI.send_request" + ) + def test_create_na_sg_grid_user_pass(self, mock_request): + set_module_args(self.set_args_create_na_sg_grid_user()) + my_obj = grid_user_module() + mock_request.side_effect = [ + SRR["not_found"], # get + SRR["grid_groups"], # get + SRR["grid_user_record"], # post + SRR["end_of_sequence"], + ] + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + print("Info: test_create_na_sg_grid_user_pass: %s" % repr(exc.value.args[0])) + assert exc.value.args[0]["changed"] + + @patch( + "ansible_collections.netapp.storagegrid.plugins.module_utils.netapp.SGRestAPI.send_request" + ) + def test_idempotent_create_na_sg_grid_user_pass(self, mock_request): + set_module_args(self.set_args_create_na_sg_grid_user()) + my_obj = grid_user_module() + mock_request.side_effect = [ + SRR["grid_user_record"], # get + SRR["grid_groups"], # get + SRR["end_of_sequence"], + ] + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + print( + "Info: test_idempotent_create_na_sg_grid_user_pass: %s" + % repr(exc.value.args[0]) + ) + assert not exc.value.args[0]["changed"] + + @patch( + "ansible_collections.netapp.storagegrid.plugins.module_utils.netapp.SGRestAPI.send_request" + ) + def test_update_na_sg_grid_user_pass(self, mock_request): + args = self.set_args_create_na_sg_grid_user() + args["member_of"] = ["group/testgridgroup1", "group/testgridgroup2"] + + set_module_args(args) + my_obj = grid_user_module() + mock_request.side_effect = [ + SRR["grid_user_record"], # get + SRR["grid_groups"], # get + SRR["grid_user_record_update"], # put + SRR["end_of_sequence"], + ] + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + print("Info: test_update_na_sg_grid_user_pass: %s" % repr(exc.value.args[0])) + assert exc.value.args[0]["changed"] + + @patch( + "ansible_collections.netapp.storagegrid.plugins.module_utils.netapp.SGRestAPI.send_request" + ) + def test_delete_na_sg_grid_user_pass(self, mock_request): + set_module_args(self.set_args_delete_na_sg_grid_user()) + my_obj = grid_user_module() + mock_request.side_effect = [ + SRR["grid_user_record"], # get + SRR["grid_groups"], # get + SRR["delete_good"], # delete + SRR["end_of_sequence"], + ] + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + print("Info: test_delete_na_sg_grid_user_pass: %s" % repr(exc.value.args[0])) + assert exc.value.args[0]["changed"] + + # create user and set pass + @patch( + "ansible_collections.netapp.storagegrid.plugins.module_utils.netapp.SGRestAPI.send_request" + ) + def test_create_na_sg_grid_user_and_set_password_pass(self, mock_request): + set_module_args(self.set_args_create_na_sg_grid_user_with_password()) + my_obj = grid_user_module() + mock_request.side_effect = [ + SRR["not_found"], # get + SRR["grid_groups"], # get + SRR["grid_user_record"], # post + SRR["pw_change_good"], # post + SRR["end_of_sequence"], + ] + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + print( + "Info: test_create_na_sg_grid_user_and_set_password_pass: %s" + % repr(exc.value.args[0]) + ) + assert exc.value.args[0]["changed"] + + # Idempotent user with password defined + @patch( + "ansible_collections.netapp.storagegrid.plugins.module_utils.netapp.SGRestAPI.send_request" + ) + def test_idempotent_create_na_sg_grid_user_and_set_password_pass( + self, mock_request + ): + set_module_args(self.set_args_create_na_sg_grid_user_with_password()) + my_obj = grid_user_module() + mock_request.side_effect = [ + SRR["grid_user_record"], # get + SRR["grid_groups"], # get + SRR["end_of_sequence"], + ] + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + print( + "Info: test_idempotent_create_na_sg_grid_user_and_set_password_pass: %s" + % repr(exc.value.args[0]) + ) + assert not exc.value.args[0]["changed"] + + # update user and set pass + @patch( + "ansible_collections.netapp.storagegrid.plugins.module_utils.netapp.SGRestAPI.send_request" + ) + def test_update_na_sg_grid_user_and_set_password_pass(self, mock_request): + args = self.set_args_create_na_sg_grid_user_with_password() + args["member_of"] = ["group/testgridgroup1", "group/testgridgroup2"] + args["update_password"] = "always" + + set_module_args(args) + my_obj = grid_user_module() + mock_request.side_effect = [ + SRR["grid_user_record"], # get + SRR["grid_groups"], # get + SRR["grid_user_record_update"], # put + SRR["pw_change_good"], # post + SRR["end_of_sequence"], + ] + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + print( + "Info: test_update_na_sg_grid_user_and_set_password_pass: %s" + % repr(exc.value.args[0]) + ) + assert exc.value.args[0]["changed"] + + # set pass only + + @patch( + "ansible_collections.netapp.storagegrid.plugins.module_utils.netapp.SGRestAPI.send_request" + ) + def test_set_na_sg_grid_user_password_pass(self, mock_request): + args = self.set_args_create_na_sg_grid_user_with_password() + args["update_password"] = "always" + + set_module_args(args) + my_obj = grid_user_module() + mock_request.side_effect = [ + SRR["grid_user_record"], # get + SRR["grid_groups"], # get + SRR["pw_change_good"], # post + SRR["end_of_sequence"], + ] + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + print( + "Info: test_set_na_sg_grid_user_password_pass: %s" % repr(exc.value.args[0]) + ) + assert exc.value.args[0]["changed"] + + # attempt to set password on federated user + + @patch( + "ansible_collections.netapp.storagegrid.plugins.module_utils.netapp.SGRestAPI.send_request" + ) + def test_fail_set_federated_user_password(self, mock_request): + with pytest.raises(AnsibleFailJson) as exc: + args = self.set_args_create_na_sg_grid_user_with_password() + args["unique_name"] = "federated-user/abc123" + args["update_password"] = "always" + set_module_args(args) + grid_user_module() + print( + "Info: test_fail_set_federated_user_password: %s" % repr(exc.value.args[0]) + ) diff --git a/ansible_collections/netapp/storagegrid/tests/unit/plugins/modules/test_na_sg_org_container.py b/ansible_collections/netapp/storagegrid/tests/unit/plugins/modules/test_na_sg_org_container.py new file mode 100644 index 000000000..21c49a556 --- /dev/null +++ b/ansible_collections/netapp/storagegrid/tests/unit/plugins/modules/test_na_sg_org_container.py @@ -0,0 +1,348 @@ +# (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 StorageGRID Org Container Ansible module: na_sg_org_container""" + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type +import json +import pytest +import sys + +try: + from requests import Response +except ImportError: + if sys.version_info < (2, 7): + pytestmark = pytest.mark.skip("Skipping Unit Tests on 2.6 as requests is not available") + else: + raise + +from ansible_collections.netapp.storagegrid.tests.unit.compat import unittest +from ansible_collections.netapp.storagegrid.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.storagegrid.plugins.modules.na_sg_org_container import ( + SgOrgContainer as org_container_module, +) + +# REST API canned responses when mocking send_request +SRR = { + # common responses + "empty_good": ({"data": []}, None), + "not_found": ( + {"status": "error", "code": 404, "data": {}}, + {"key": "error.404"}, + ), + "end_of_sequence": (None, "Unexpected call to send_request"), + "generic_error": (None, "Expected error"), + "delete_good": (None, None), + "version_114": ({"data": {"productVersion": "11.4.0-20200721.1338.d3969b3"}}, None), + "version_116": ({"data": {"productVersion": "11.6.0-20211120.0301.850531e"}}, None), + "global_compliance_disabled": ( + { + "data": { + "complianceEnabled": False, + } + }, + None, + ), + "global_compliance_enabled": ( + { + "data": { + "complianceEnabled": True, + } + }, + None, + ), + "org_containers": ( + {"data": [{"name": "testbucket", "creationTime": "2020-02-04T12:43:50.777Z", "region": "us-east-1"}]}, + None, + ), + "org_container_record": ( + {"data": {"name": "testbucket", "creationTime": "2020-02-04T12:43:50.777Z", "region": "us-east-1"}}, + None, + ), + "org_container_objectlock_record": ( + { + "data": { + "name": "testbucket", + "creationTime": "2020-02-04T12:43:50.777Z", + "region": "us-east-1", + "s3ObjectLock": {"enabled": True}, + } + }, + None, + ), + "org_container_record_update": ( + { + "data": { + "name": "testbucket", + "creationTime": "2020-02-04T12:43:50.777Z", + "region": "us-east-1", + "compliance": {"autoDelete": False, "legalHold": False}, + } + }, + None, + ), + "org_container_versioning_disabled": ({"data": {"versioningEnabled": False, "versioningSuspended": False}}, None), + "org_container_versioning_enabled": ({"data": {"versioningEnabled": True, "versioningSuspended": False}}, None), + "org_container_versioning_suspended": ({"data": {"versioningEnabled": False, "versioningSuspended": True}}, 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""" + + 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 set_default_args_fail_check(self): + return dict( + {"name": "testbucket", "auth_token": "01234567-5678-9abc-78de-9fgabc123def", "validate_certs": False} + ) + + def set_default_args_pass_check(self): + return dict( + { + "state": "present", + "name": "testbucket", + "api_url": "gmi.example.com", + "auth_token": "01234567-5678-9abc-78de-9fgabc123def", + "validate_certs": False, + } + ) + + def set_args_create_na_sg_org_container(self): + return dict( + { + "state": "present", + "name": "testbucket", + "api_url": "gmi.example.com", + "auth_token": "01234567-5678-9abc-78de-9fgabc123def", + "validate_certs": False, + } + ) + + def set_args_delete_na_sg_org_container(self): + return dict( + { + "state": "absent", + "name": "testbucket", + "api_url": "gmi.example.com", + "auth_token": "01234567-5678-9abc-78de-9fgabc123def", + "validate_certs": False, + } + ) + + @patch("ansible_collections.netapp.storagegrid.plugins.module_utils.netapp.SGRestAPI.send_request") + def test_module_fail_when_required_args_missing(self, mock_request): + """required arguments are reported as errors""" + mock_request.side_effect = [ + SRR["version_114"], + ] + with pytest.raises(AnsibleFailJson) as exc: + set_module_args(self.set_default_args_fail_check()) + org_container_module() + print("Info: test_module_fail_when_required_args_missing: %s" % exc.value.args[0]["msg"]) + + @patch("ansible_collections.netapp.storagegrid.plugins.module_utils.netapp.SGRestAPI.send_request") + def test_module_fail_when_required_args_present(self, mock_request): + """required arguments are reported as errors""" + mock_request.side_effect = [ + SRR["version_114"], + ] + with pytest.raises(AnsibleExitJson) as exc: + set_module_args(self.set_default_args_pass_check()) + org_container_module() + exit_json(changed=True, msg="Induced arguments check") + print("Info: test_module_fail_when_required_args_present: %s" % exc.value.args[0]["msg"]) + assert exc.value.args[0]["changed"] + + @patch("ansible_collections.netapp.storagegrid.plugins.module_utils.netapp.SGRestAPI.send_request") + def test_create_na_sg_org_container_pass(self, mock_request): + set_module_args(self.set_args_create_na_sg_org_container()) + mock_request.side_effect = [ + SRR["version_114"], + SRR["empty_good"], # get + SRR["org_container_record"], # post + SRR["end_of_sequence"], + ] + my_obj = org_container_module() + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + print("Info: test_create_na_sg_org_container_pass: %s" % repr(exc.value.args[0])) + assert exc.value.args[0]["changed"] + + @patch("ansible_collections.netapp.storagegrid.plugins.module_utils.netapp.SGRestAPI.send_request") + def test_idempotent_create_na_sg_org_container_pass(self, mock_request): + set_module_args(self.set_args_create_na_sg_org_container()) + mock_request.side_effect = [ + SRR["version_114"], + SRR["org_containers"], # get + SRR["end_of_sequence"], + ] + my_obj = org_container_module() + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + print("Info: test_idempotent_create_na_sg_org_container_pass: %s" % repr(exc.value.args[0])) + assert not exc.value.args[0]["changed"] + + @patch("ansible_collections.netapp.storagegrid.plugins.module_utils.netapp.SGRestAPI.send_request") + def test_update_na_sg_org_container_pass(self, mock_request): + args = self.set_args_create_na_sg_org_container() + args["compliance"] = {"auto_delete": False, "legal_hold": False} + set_module_args(args) + mock_request.side_effect = [ + SRR["version_114"], + SRR["org_containers"], # get + SRR["org_container_record_update"], # put + SRR["end_of_sequence"], + ] + my_obj = org_container_module() + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + print("Info: test_update_na_sg_org_container_pass: %s" % repr(exc.value.args[0])) + assert exc.value.args[0]["changed"] + + @patch("ansible_collections.netapp.storagegrid.plugins.module_utils.netapp.SGRestAPI.send_request") + def test_delete_na_sg_org_container_pass(self, mock_request): + set_module_args(self.set_args_delete_na_sg_org_container()) + mock_request.side_effect = [ + SRR["version_114"], + SRR["org_containers"], # get + SRR["delete_good"], # delete + SRR["end_of_sequence"], + ] + my_obj = org_container_module() + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + print("Info: test_delete_na_sg_org_container_pass: %s" % repr(exc.value.args[0])) + assert exc.value.args[0]["changed"] + + @patch("ansible_collections.netapp.storagegrid.plugins.module_utils.netapp.SGRestAPI.send_request") + def test_module_fail_minimum_version_not_met_object_lock(self, mock_request): + args = self.set_args_create_na_sg_org_container() + args["s3_object_lock_enabled"] = True + set_module_args(args) + mock_request.side_effect = [ + SRR["version_114"], # get + ] + with pytest.raises(AnsibleFailJson) as exc: + org_container_module() + print("Info: test_module_fail_minimum_version_not_met_object_lock: %s" % exc.value.args[0]["msg"]) + + @patch("ansible_collections.netapp.storagegrid.plugins.module_utils.netapp.SGRestAPI.send_request") + def test_create_na_sg_org_container_objectlock_global_compliance_fail(self, mock_request): + args = self.set_args_create_na_sg_org_container() + args["s3_object_lock_enabled"] = True + set_module_args(args) + mock_request.side_effect = [ + SRR["version_116"], + SRR["empty_good"], # get + SRR["global_compliance_disabled"], # get + ] + my_obj = org_container_module() + with pytest.raises(AnsibleFailJson) as exc: + my_obj.apply() + print("Info: test_create_na_sg_org_container_objectlock_global_compliance_fail: %s" % repr(exc.value.args[0])) + + @patch("ansible_collections.netapp.storagegrid.plugins.module_utils.netapp.SGRestAPI.send_request") + def test_create_na_sg_org_container_objectlock_pass(self, mock_request): + set_module_args(self.set_args_create_na_sg_org_container()) + mock_request.side_effect = [ + SRR["version_116"], + SRR["empty_good"], # get + SRR["global_compliance_enabled"], # get + SRR["org_container_objectlock_record"], # post + SRR["end_of_sequence"], + ] + my_obj = org_container_module() + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + print("Info: test_create_na_sg_org_container_pass: %s" % repr(exc.value.args[0])) + assert exc.value.args[0]["changed"] + + @patch("ansible_collections.netapp.storagegrid.plugins.module_utils.netapp.SGRestAPI.send_request") + def test_module_fail_minimum_version_not_met_versioning(self, mock_request): + args = self.set_args_create_na_sg_org_container() + args["bucket_versioning_enabled"] = True + set_module_args(args) + mock_request.side_effect = [ + SRR["version_114"], # get + ] + with pytest.raises(AnsibleFailJson) as exc: + org_container_module() + print("Info: test_module_fail_minimum_version_not_met_versioning: %s" % exc.value.args[0]["msg"]) + + @patch("ansible_collections.netapp.storagegrid.plugins.module_utils.netapp.SGRestAPI.send_request") + def test_create_na_sg_org_container_with_versioning_pass(self, mock_request): + args = self.set_args_create_na_sg_org_container() + args["bucket_versioning_enabled"] = True + set_module_args(args) + mock_request.side_effect = [ + SRR["version_116"], + SRR["empty_good"], # get + SRR["org_container_record"], # post + SRR["org_container_versioning_enabled"], # post + SRR["end_of_sequence"], + ] + my_obj = org_container_module() + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + print("Info: test_create_na_sg_org_container_with_versioning_pass: %s" % repr(exc.value.args[0])) + assert exc.value.args[0]["changed"] + + @patch("ansible_collections.netapp.storagegrid.plugins.module_utils.netapp.SGRestAPI.send_request") + def test_update_na_sg_org_container_enable_versioning_pass(self, mock_request): + args = self.set_args_create_na_sg_org_container() + args["bucket_versioning_enabled"] = True + set_module_args(args) + mock_request.side_effect = [ + SRR["version_116"], + SRR["org_containers"], # get + SRR["org_container_versioning_disabled"], # get + SRR["org_container_versioning_enabled"], # put + SRR["end_of_sequence"], + ] + my_obj = org_container_module() + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + print("Info: test_update_na_sg_org_container_enable_versioning_pass: %s" % repr(exc.value.args[0])) + assert exc.value.args[0]["changed"] diff --git a/ansible_collections/netapp/storagegrid/tests/unit/plugins/modules/test_na_sg_org_group.py b/ansible_collections/netapp/storagegrid/tests/unit/plugins/modules/test_na_sg_org_group.py new file mode 100644 index 000000000..c229130c2 --- /dev/null +++ b/ansible_collections/netapp/storagegrid/tests/unit/plugins/modules/test_na_sg_org_group.py @@ -0,0 +1,403 @@ +# (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 StorageGRID Org Group Ansible module: na_sg_org_group""" + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type +import json +import pytest +import sys +try: + from requests import Response +except ImportError: + if sys.version_info < (2, 7): + pytestmark = pytest.mark.skip('Skipping Unit Tests on 2.6 as requests is not available') + else: + raise + +from ansible_collections.netapp.storagegrid.tests.unit.compat import unittest +from ansible_collections.netapp.storagegrid.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.storagegrid.plugins.modules.na_sg_org_group import ( + SgOrgGroup as org_group_module, +) + +# REST API canned responses when mocking send_request +SRR = { + # common responses + "empty_good": ({"data": []}, None), + "not_found": ( + {"status": "error", "code": 404, "data": {}}, + {"key": "error.404"}, + ), + "end_of_sequence": (None, "Unexpected call to send_request"), + "generic_error": (None, "Expected error"), + "delete_good": ({"code": 204}, None), + "org_groups": ( + { + "data": [ + { + "displayName": "TestOrgGroup", + "uniqueName": "group/testorggroup", + "policies": { + "management": { + "manageAllContainers": True, + "manageEndpoints": True, + "manageOwnS3Credentials": True, + }, + "s3": { + "Statement": [ + { + "Effect": "Allow", + "Action": "s3:*", + "Resource": "arn:aws:s3:::*", + } + ] + }, + }, + "accountId": "12345678901234567890", + "id": "00000000-0000-0000-0000-000000000000", + "federated": False, + "groupURN": "urn:sgws:identity::12345678901234567890:group/testorggroup", + } + ] + }, + None, + ), + "org_group_record": ( + { + "data": { + "displayName": "TestOrgGroup", + "uniqueName": "group/testorggroup", + "policies": { + "management": { + "manageAllContainers": True, + "manageEndpoints": True, + "manageOwnS3Credentials": True, + }, + "s3": { + "Statement": [ + { + "Effect": "Allow", + "Action": "s3:*", + "Resource": "arn:aws:s3:::*", + } + ] + }, + }, + "accountId": "12345678901234567890", + "id": "00000000-0000-0000-0000-000000000000", + "federated": False, + "groupURN": "urn:sgws:identity::12345678901234567890:group/testorggroup", + } + }, + None, + ), + "org_group_record_update": ( + { + "data": { + "displayName": "TestOrgGroup", + "uniqueName": "group/testorggroup", + "policies": { + "management": { + "manageAllContainers": True, + "manageEndpoints": True, + "manageOwnS3Credentials": True, + # "rootAccess": False, + }, + "s3": { + "Statement": [ + { + "Effect": "Allow", + "Action": "s3:*", + "Resource": "arn:aws:s3:::mybucket/*", + } + ] + }, + }, + "accountId": "12345678901234567890", + "id": "00000000-0000-0000-0000-000000000000", + "federated": False, + "groupURN": "urn:sgws:identity::12345678901234567890:group/testorggroup", + } + }, + 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""" + + 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 set_default_args_fail_check(self): + return dict( + { + "display_name": "TestGroup", + "management_policy": { + "manage_all_containers": True, + "manage_endpoints": True, + "manage_own_s3_credentials": True, + "root_access": False, + }, + "s3_policy": { + "Statement": [ + { + "Effect": "Allow", + "Action": "s3:*", + "Resource": "arn:aws:s3:::*", + } + ] + }, + "auth_token": "01234567-5678-9abc-78de-9fgabc123def", + "validate_certs": False, + } + ) + + def set_default_args_pass_check(self): + return dict( + { + "state": "present", + "display_name": "TestGroup", + "unique_name": "group/testgroup", + "management_policy": { + "manage_all_containers": True, + "manage_endpoints": True, + "manage_own_s3_credentials": True, + "root_access": False, + }, + "s3_policy": { + "Statement": [ + { + "Effect": "Allow", + "Action": "s3:*", + "Resource": "arn:aws:s3:::*", + } + ] + }, + "api_url": "gmi.example.com", + "auth_token": "01234567-5678-9abc-78de-9fgabc123def", + "validate_certs": False, + } + ) + + def set_args_create_na_sg_org_group(self): + return dict( + { + "state": "present", + "display_name": "TestOrgGroup", + "unique_name": "group/testorggroup", + "management_policy": { + "manage_all_containers": True, + "manage_endpoints": True, + "manage_own_s3_credentials": True, + "root_access": False, + }, + "s3_policy": { + "Statement": [ + { + "Effect": "Allow", + "Action": "s3:*", + "Resource": "arn:aws:s3:::*", + } + ] + }, + "api_url": "gmi.example.com", + "auth_token": "01234567-5678-9abc-78de-9fgabc123def", + "validate_certs": False, + } + ) + + def set_args_delete_na_sg_org_group(self): + return dict( + { + "state": "absent", + # "display_name": "TestOrgGroup", + "unique_name": "group/testorggroup", + # "management_policy": { + # "manage_all_containers": True, + # "manage_endpoints": True, + # "manage_own_s3_credentials": True, + # "root_access": False, + # }, + # "s3_policy": { + # "Statement": [ + # { + # "Effect": "Allow", + # "Action": "s3:*", + # "Resource": "arn:aws:s3:::*", + # } + # ] + # }, + "api_url": "gmi.example.com", + "auth_token": "01234567-5678-9abc-78de-9fgabc123def", + "validate_certs": 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(self.set_default_args_fail_check()) + org_group_module() + print( + "Info: test_module_fail_when_required_args_missing: %s" + % exc.value.args[0]["msg"] + ) + + def test_module_fail_when_required_args_present(self): + """ required arguments are reported as errors """ + with pytest.raises(AnsibleExitJson) as exc: + set_module_args(self.set_default_args_pass_check()) + org_group_module() + exit_json(changed=True, msg="Induced arguments check") + print( + "Info: test_module_fail_when_required_args_present: %s" + % exc.value.args[0]["msg"] + ) + assert exc.value.args[0]["changed"] + + def test_module_fail_with_bad_unique_name(self): + """ error returned if unique_name doesn't start with group or federated_group """ + with pytest.raises(AnsibleFailJson) as exc: + args = self.set_default_args_pass_check() + args["unique_name"] = "noprefixgroup" + set_module_args(args) + org_group_module() + print( + "Info: test_module_fail_with_bad_unique_name: %s" + % exc.value.args[0]["msg"] + ) + + @patch( + "ansible_collections.netapp.storagegrid.plugins.module_utils.netapp.SGRestAPI.send_request" + ) + def test_create_na_sg_org_group_pass(self, mock_request): + set_module_args(self.set_args_create_na_sg_org_group()) + my_obj = org_group_module() + mock_request.side_effect = [ + SRR["not_found"], # get + SRR["org_group_record"], # post + SRR["end_of_sequence"], + ] + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + print( + "Info: test_create_na_sg_org_group_pass: %s" + % repr(exc.value.args[0]) + ) + assert exc.value.args[0]["changed"] + + @patch( + "ansible_collections.netapp.storagegrid.plugins.module_utils.netapp.SGRestAPI.send_request" + ) + def test_idempotent_create_na_sg_org_group_pass(self, mock_request): + set_module_args(self.set_args_create_na_sg_org_group()) + my_obj = org_group_module() + mock_request.side_effect = [ + SRR["org_group_record"], # get + SRR["end_of_sequence"], + ] + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + print( + "Info: test_idempotent_create_na_sg_org_group_pass: %s" + % repr(exc.value.args[0]) + ) + assert not exc.value.args[0]["changed"] + + @patch( + "ansible_collections.netapp.storagegrid.plugins.module_utils.netapp.SGRestAPI.send_request" + ) + def test_update_na_sg_org_group_pass(self, mock_request): + args = self.set_args_create_na_sg_org_group() + args["s3_policy"] = ( + { + "Statement": [ + { + "Effect": "Allow", + "Action": "s3:*", + "Resource": "arn:aws:s3:::mybucket/*", + } + ] + }, + ) + + args["management_policy"]["manage_endpoints"] = False + + set_module_args(args) + my_obj = org_group_module() + mock_request.side_effect = [ + SRR["org_group_record"], # get + SRR["org_group_record_update"], # put + SRR["end_of_sequence"], + ] + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + print( + "Info: test_update_na_sg_org_group_pass: %s" + % repr(exc.value.args[0]) + ) + assert exc.value.args[0]["changed"] + + @patch( + "ansible_collections.netapp.storagegrid.plugins.module_utils.netapp.SGRestAPI.send_request" + ) + def test_delete_na_sg_org_group_pass(self, mock_request): + set_module_args(self.set_args_delete_na_sg_org_group()) + my_obj = org_group_module() + mock_request.side_effect = [ + SRR["org_group_record"], # get + SRR["delete_good"], # delete + SRR["end_of_sequence"], + ] + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + print( + "Info: test_delete_na_sg_org_group_pass: %s" + % repr(exc.value.args[0]) + ) + assert exc.value.args[0]["changed"] diff --git a/ansible_collections/netapp/storagegrid/tests/unit/plugins/modules/test_na_sg_org_identity_federation.py b/ansible_collections/netapp/storagegrid/tests/unit/plugins/modules/test_na_sg_org_identity_federation.py new file mode 100644 index 000000000..b02259005 --- /dev/null +++ b/ansible_collections/netapp/storagegrid/tests/unit/plugins/modules/test_na_sg_org_identity_federation.py @@ -0,0 +1,354 @@ +# (c) 2021, NetApp, Inc +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +""" unit tests NetApp StorageGRID Tenant Identity Federation Ansible module: na_sg_org_identity_federation""" + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type +import json +import pytest +import sys +try: + from requests import Response +except ImportError: + if sys.version_info < (2, 7): + pytestmark = pytest.mark.skip('Skipping Unit Tests on 2.6 as requests is not available') + else: + raise + +from ansible_collections.netapp.storagegrid.tests.unit.compat import unittest +from ansible_collections.netapp.storagegrid.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.storagegrid.plugins.modules.na_sg_org_identity_federation import ( + SgOrgIdentityFederation as org_identity_federation_module, +) + +# REST API canned responses when mocking send_request +SRR = { + # common responses + "end_of_sequence": (None, "Unexpected call to send_request"), + "generic_error": (None, "Expected error"), + "check_mode_good": (None, None), + "identity_federation_unset": ( + { + "data": { + "id": "09876543-abcd-4321-abcd-0987654321ab", + "disable": True, + "type": "", + "ldapServiceType": "", + "hostname": "", + "port": 0, + "username": "", + "password": None, + "baseGroupDn": "", + "baseUserDn": "", + "disableTLS": False, + "enableLDAPS": False, + "caCert": "", + } + }, + None, + ), + "identity_federation": ( + { + "data": { + "id": "09876543-abcd-4321-abcd-0987654321ab", + "disable": False, + "type": "ldap", + "ldapServiceType": "Active Directory", + "hostname": "ad.example.com", + "port": 389, + "username": "binduser", + "password": "********", + "baseGroupDn": "DC=example,DC=com", + "baseUserDn": "DC=example,DC=com", + "disableTLS": True, + "enableLDAPS": False, + "caCert": "", + } + }, + None, + ), + "identity_federation_tls": ( + { + "data": { + "id": "09876543-abcd-4321-abcd-0987654321ab", + "disable": False, + "type": "ldap", + "ldapServiceType": "Active Directory", + "hostname": "ad.example.com", + "port": 636, + "username": "binduser", + "password": "********", + "baseGroupDn": "DC=example,DC=com", + "baseUserDn": "DC=example,DC=com", + "disableTLS": False, + "enableLDAPS": True, + "caCert": ( + "-----BEGIN CERTIFICATE-----\n" + "MIIF+DCCBOCgAwIBAgITRwAAAAIg5KzMrJo+kQAAAAAAAjANBgkqhkiG9w0BAQUF\n" + "ADBlMRIwEAYKCZImiZPyLGQBGRYCYXUxFjAUBgoJkiaJk/IsZAEZFgZuZXRhcHAx\n" + "FjAUBgoJkiaJk/IsZAEZFgZhdXNuZ3MxHzAdBgNVBAMTFmF1c25ncy1NRUxOR1NE\n" + "QzAxLUNBLTEwHhcNMjEwMjExMDkzMTIwWhcNMjMwMjExMDk0MTIwWjAAMIIBIjAN\n" + "BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAt2xPi4FS4Uc37KrDLEXXUoc4lhhT\n" + "uQmMnLc0PYZCIpzYOaosFIeGqco3woiC7wSZJ2whKE4RDcxxgE+azuGiSWVjIxIL\n" + "AimmcDhFid/T3KRN5jmkjBzUKuPBYzZBFih8iU9056rqgN7eMKQYjRwPeV0+AeiB\n" + "irw46OgkwVQu3shEUtXxZPP2Mb6Md23+4vSmcElUcW28Opt2q/M5fs7DNomG3eaG\n" + "-----END CERTIFICATE-----\n" + ), + } + }, + None, + ), + "identity_federation_disable": ( + { + "data": { + "id": "09876543-abcd-4321-abcd-0987654321ab", + "disable": True, + "type": "ldap", + "ldapServiceType": "Active Directory", + "hostname": "ad.example.com", + "port": 389, + "username": "binduser", + "password": "********", + "baseGroupDn": "DC=example,DC=com", + "baseUserDn": "DC=example,DC=com", + "disableTLS": True, + "enableLDAPS": False, + "caCert": "", + } + }, + 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""" + + 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 set_default_args_fail_check(self): + return dict( + { + "ldap_service_type": "Active Directory", + "hostname": "ad.example.com", + "port": 389, + "username": "binduser", + "password": "bindpass", + "base_group_dn": "DC=example,DC=com", + "base_user_dn": "DC=example,DC=com", + "tls": "Disabled", + "auth_token": "01234567-5678-9abc-78de-9fgabc123def", + "validate_certs": False, + } + ) + + def set_default_args_pass_check(self): + return dict( + { + "ldap_service_type": "Active Directory", + "hostname": "ad.example.com", + "port": 389, + "username": "binduser", + "password": "bindpass", + "base_group_dn": "DC=example,DC=com", + "base_user_dn": "DC=example,DC=com", + "tls": "Disabled", + "state": "present", + "api_url": "gmi.example.com", + "auth_token": "01234567-5678-9abc-78de-9fgabc123def", + "validate_certs": False, + } + ) + + def set_args_set_na_sg_org_identity_federation(self): + return dict( + { + "ldap_service_type": "Active Directory", + "hostname": "ad.example.com", + "port": 389, + "username": "binduser", + "password": "bindpass", + "base_group_dn": "DC=example,DC=com", + "base_user_dn": "DC=example,DC=com", + "tls": "Disabled", + "state": "present", + "api_url": "gmi.example.com", + "auth_token": "01234567-5678-9abc-78de-9fgabc123def", + "validate_certs": False, + } + ) + + def set_args_set_na_sg_org_identity_federation_tls(self): + return dict( + { + "ldap_service_type": "Active Directory", + "hostname": "ad.example.com", + "port": 636, + "username": "binduser", + "password": "bindpass", + "base_group_dn": "DC=example,DC=com", + "base_user_dn": "DC=example,DC=com", + "tls": "LDAPS", + "ca_cert": ( + "-----BEGIN CERTIFICATE-----\n" + "MIIF+DCCBOCgAwIBAgITRwAAAAIg5KzMrJo+kQAAAAAAAjANBgkqhkiG9w0BAQUF\n" + "ADBlMRIwEAYKCZImiZPyLGQBGRYCYXUxFjAUBgoJkiaJk/IsZAEZFgZuZXRhcHAx\n" + "FjAUBgoJkiaJk/IsZAEZFgZhdXNuZ3MxHzAdBgNVBAMTFmF1c25ncy1NRUxOR1NE\n" + "QzAxLUNBLTEwHhcNMjEwMjExMDkzMTIwWhcNMjMwMjExMDk0MTIwWjAAMIIBIjAN\n" + "BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAt2xPi4FS4Uc37KrDLEXXUoc4lhhT\n" + "uQmMnLc0PYZCIpzYOaosFIeGqco3woiC7wSZJ2whKE4RDcxxgE+azuGiSWVjIxIL\n" + "AimmcDhFid/T3KRN5jmkjBzUKuPBYzZBFih8iU9056rqgN7eMKQYjRwPeV0+AeiB\n" + "irw46OgkwVQu3shEUtXxZPP2Mb6Md23+4vSmcElUcW28Opt2q/M5fs7DNomG3eaG\n" + "-----END CERTIFICATE-----\n" + ), + "state": "present", + "api_url": "gmi.example.com", + "auth_token": "01234567-5678-9abc-78de-9fgabc123def", + "validate_certs": False, + } + ) + + def set_args_remove_na_sg_org_identity_federation(self): + return dict( + { + "ldap_service_type": "Active Directory", + "hostname": "ad.example.com", + "state": "absent", + "api_url": "gmi.example.com", + "auth_token": "01234567-5678-9abc-78de-9fgabc123def", + "validate_certs": 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(self.set_default_args_fail_check()) + org_identity_federation_module() + print("Info: test_module_fail_when_required_args_missing: %s" % exc.value.args[0]["msg"]) + + def test_module_fail_when_required_args_present(self): + """ required arguments are reported as errors """ + with pytest.raises(AnsibleExitJson) as exc: + set_module_args(self.set_default_args_pass_check()) + org_identity_federation_module() + exit_json(changed=True, msg="Induced arguments check") + print("Info: test_module_fail_when_required_args_present: %s" % exc.value.args[0]["msg"]) + assert exc.value.args[0]["changed"] + + @patch("ansible_collections.netapp.storagegrid.plugins.module_utils.netapp.SGRestAPI.send_request") + def test_set_na_sg_org_identity_federation_pass(self, mock_request): + set_module_args(self.set_args_set_na_sg_org_identity_federation()) + my_obj = org_identity_federation_module() + mock_request.side_effect = [ + SRR["identity_federation_unset"], # get + SRR["identity_federation"], # post + SRR["end_of_sequence"], + ] + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + print("Info: test_set_na_sg_org_identity_federation_pass: %s" % repr(exc.value.args[0])) + assert exc.value.args[0]["changed"] + + @patch("ansible_collections.netapp.storagegrid.plugins.module_utils.netapp.SGRestAPI.send_request") + def test_idempotent_set_na_sg_org_identity_federation_pass(self, mock_request): + args = self.set_args_set_na_sg_org_identity_federation() + # remove password + del args["password"] + set_module_args(args) + my_obj = org_identity_federation_module() + mock_request.side_effect = [ + SRR["identity_federation"], # get + SRR["end_of_sequence"], + ] + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + print("Info: test_idempotent_set_na_sg_org_identity_federation_pass: %s" % repr(exc.value.args[0])) + assert not exc.value.args[0]["changed"] + + @patch("ansible_collections.netapp.storagegrid.plugins.module_utils.netapp.SGRestAPI.send_request") + def test_set_na_sg_org_identity_federation_tls_pass(self, mock_request): + set_module_args(self.set_args_set_na_sg_org_identity_federation_tls()) + my_obj = org_identity_federation_module() + mock_request.side_effect = [ + SRR["identity_federation_unset"], # get + SRR["identity_federation_tls"], # post + SRR["end_of_sequence"], + ] + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + print("Info: test_set_na_sg_org_identity_federation_pass: %s" % repr(exc.value.args[0])) + assert exc.value.args[0]["changed"] + + @patch("ansible_collections.netapp.storagegrid.plugins.module_utils.netapp.SGRestAPI.send_request") + def test_remove_na_sg_org_identity_federation_pass(self, mock_request): + set_module_args(self.set_args_remove_na_sg_org_identity_federation()) + my_obj = org_identity_federation_module() + mock_request.side_effect = [ + SRR["identity_federation"], # get + SRR["identity_federation_disable"], # post + SRR["end_of_sequence"], + ] + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + print("Info: test_remove_na_sg_org_identity_federation_pass: %s" % repr(exc.value.args[0])) + assert exc.value.args[0]["changed"] + + # test check mode + + @patch("ansible_collections.netapp.storagegrid.plugins.module_utils.netapp.SGRestAPI.send_request") + def test_check_mode_na_sg_org_identity_federation_pass(self, mock_request): + set_module_args(self.set_args_set_na_sg_org_identity_federation()) + my_obj = org_identity_federation_module() + my_obj.module.check_mode = True + mock_request.side_effect = [ + SRR["identity_federation_unset"], # get + SRR["check_mode_good"], # post + SRR["end_of_sequence"], + ] + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + print("Info: test_check_mode_na_sg_org_identity_federation_pass: %s" % repr(exc.value.args[0])) + assert exc.value.args[0]["changed"] + assert exc.value.args[0]["msg"] == "Connection test successful" diff --git a/ansible_collections/netapp/storagegrid/tests/unit/plugins/modules/test_na_sg_org_info.py b/ansible_collections/netapp/storagegrid/tests/unit/plugins/modules/test_na_sg_org_info.py new file mode 100644 index 000000000..e24c7cd46 --- /dev/null +++ b/ansible_collections/netapp/storagegrid/tests/unit/plugins/modules/test_na_sg_org_info.py @@ -0,0 +1,263 @@ +# (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 StorageGRID Org Ansible module: na_sg_org_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.storagegrid.tests.unit.compat import unittest +from ansible_collections.netapp.storagegrid.tests.unit.compat.mock import patch + +from ansible_collections.netapp.storagegrid.plugins.modules.na_sg_org_info \ + import NetAppSgGatherInfo as sg_org_info_module + +# REST API canned responses when mocking send_request +SRR = { + # common responses + 'empty_good': ({'data': []}, None), + 'end_of_sequence': (None, 'Unexpected call to send_request'), + 'generic_error': (None, 'Expected error'), + 'org_compliance_global': ({'data': {}}, None), + 'org_config': ({'data': {}}, None), + 'org_config_product_version': ({'data': {}}, None), + 'org_containers': ({'data': {}}, None), + 'org_deactivated_features': ({'data': {}}, None), + 'org_endpoints': ({'data': []}, None), + 'org_groups': ({'data': []}, None), + 'org_identity_source': ({'data': {}}, None), + 'org_regions': ({'data': []}, None), + 'org_users_current_user_s3_access_keys': ({'data': []}, None), + 'org_usage': ({'data': {}}, None), + 'org_users': ( + { + 'data': [ + { + 'accountId': '99846664116007910793', + 'disable': False, + 'federated': False, + 'fullName': 'Root', + 'id': '00000000-0000-0000-0000-000000000000', + 'memberOf': None, + 'uniqueName': 'root', + 'userURN': 'urn:sgws:identity::99846664116007910793:root' + }, + ] + }, + None + ), + 'org_users_root': ( + { + 'data': { + 'accountId': '99846664116007910793', + 'disable': False, + 'federated': False, + 'fullName': 'Root', + 'id': '00000000-0000-0000-0000-000000000000', + 'memberOf': None, + 'uniqueName': 'root', + 'userURN': 'urn:sgws:identity::99846664116007910793:root' + }, + }, + None + ), + 'versions': ({'data': [2, 3]}, 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 ''' + 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 set_default_args_fail_check(self): + return dict( + { + 'api_url': 'sgmi.example.com', + } + ) + + def set_default_args_pass_check(self): + return dict( + { + 'api_url': 'sgmi.example.com', + 'auth_token': '01234567-5678-9abc-78de-9fgabc123def', + } + ) + + def set_default_optional_args_pass_check(self): + return dict( + { + 'api_url': 'sgmi.example.com', + 'auth_token': '01234567-5678-9abc-78de-9fgabc123def', + 'validate_certs': False, + 'gather_subset': ['all'], + 'parameters': {'limit': 5}, + } + ) + + def set_args_run_sg_gather_facts_for_all_info(self): + return dict({ + 'api_url': 'sgmi.example.com', + 'auth_token': '01234567-5678-9abc-78de-9fgabc123def', + 'validate_certs': False, + }) + + def set_args_run_sg_gather_facts_for_org_users_info(self): + return dict({ + 'api_url': 'sgmi.example.com', + 'auth_token': '01234567-5678-9abc-78de-9fgabc123def', + 'validate_certs': False, + 'gather_subset': ['org_users_info'], + }) + + def set_args_run_sg_gather_facts_for_org_users_and_org_users_root_info(self): + return dict({ + 'api_url': 'sgmi.example.com', + 'auth_token': '01234567-5678-9abc-78de-9fgabc123def', + 'validate_certs': False, + 'gather_subset': ['org_users_info', 'org/users/root'], + }) + + 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.set_default_args_fail_check()) + sg_org_info_module() + print( + 'Info: test_module_fail_when_required_args_missing: %s' + % exc.value.args[0]['msg'] + ) + + def test_module_pass_when_required_args_present(self): + ''' required arguments are reported as errors ''' + with pytest.raises(AnsibleExitJson) as exc: + set_module_args(self.set_default_args_pass_check()) + sg_org_info_module() + exit_json(changed=True, msg='Induced arguments check') + print( + 'Info: test_module_pass_when_required_args_present: %s' + % exc.value.args[0]['msg'] + ) + assert exc.value.args[0]['changed'] + + def test_module_pass_when_optional_args_present(self): + ''' Optional arguments are reported as pass ''' + with pytest.raises(AnsibleExitJson) as exc: + set_module_args(self.set_default_optional_args_pass_check()) + sg_org_info_module() + exit_json(changed=True, msg='Induced arguments check') + print( + 'Info: test_module_pass_when_optional_args_present: %s' + % exc.value.args[0]['msg'] + ) + assert exc.value.args[0]['changed'] + + @patch('ansible_collections.netapp.storagegrid.plugins.module_utils.netapp.SGRestAPI.send_request') + def test_run_sg_gather_facts_for_all_info_pass(self, mock_request): + set_module_args(self.set_args_run_sg_gather_facts_for_all_info()) + my_obj = sg_org_info_module() + gather_subset = [ + 'org/compliance-global', + 'org/config', + 'org/config/product-version', + 'org/containers', + 'org/deactivated-features', + 'org/endpoints', + 'org/groups', + 'org/identity-source', + 'org/regions', + 'org/users/current-user/s3-access-keys', + 'org/usage', + 'org/users', + 'org/users/root', + 'versions', + ] + mock_request.side_effect = [ + SRR['org_compliance_global'], + SRR['org_config'], + SRR['org_config_product_version'], + SRR['org_containers'], + SRR['org_deactivated_features'], + SRR['org_endpoints'], + SRR['org_groups'], + SRR['org_identity_source'], + SRR['org_regions'], + SRR['org_users_current_user_s3_access_keys'], + SRR['org_usage'], + SRR['org_users'], + SRR['org_users_root'], + SRR['versions'], + SRR['end_of_sequence'], + ] + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + print('Info: test_run_sg_gather_facts_for_all_info_pass: %s' % repr(exc.value.args)) + assert set(exc.value.args[0]['sg_info']) == set(gather_subset) + + @patch('ansible_collections.netapp.storagegrid.plugins.module_utils.netapp.SGRestAPI.send_request') + def test_run_sg_gather_facts_for_org_users_info_pass(self, mock_request): + set_module_args(self.set_args_run_sg_gather_facts_for_org_users_info()) + my_obj = sg_org_info_module() + gather_subset = ['org/users'] + mock_request.side_effect = [ + SRR['org_users'], + SRR['end_of_sequence'], + ] + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + print('Info: test_run_sg_gather_facts_for_org_users_info_pass: %s' % repr(exc.value.args)) + assert set(exc.value.args[0]['sg_info']) == set(gather_subset) + + @patch('ansible_collections.netapp.storagegrid.plugins.module_utils.netapp.SGRestAPI.send_request') + def test_run_sg_gather_facts_for_org_users_and_org_users_root_info_pass(self, mock_request): + set_module_args(self.set_args_run_sg_gather_facts_for_org_users_and_org_users_root_info()) + my_obj = sg_org_info_module() + gather_subset = ['org/users', 'org/users/root'] + mock_request.side_effect = [ + SRR['org_users'], + SRR['org_users_root'], + SRR['end_of_sequence'], + ] + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + print('Info: test_run_sg_gather_facts_for_org_users_and_org_users_root_info_pass: %s' % repr(exc.value.args)) + assert set(exc.value.args[0]['sg_info']) == set(gather_subset) diff --git a/ansible_collections/netapp/storagegrid/tests/unit/plugins/modules/test_na_sg_org_user.py b/ansible_collections/netapp/storagegrid/tests/unit/plugins/modules/test_na_sg_org_user.py new file mode 100644 index 000000000..8fcec6734 --- /dev/null +++ b/ansible_collections/netapp/storagegrid/tests/unit/plugins/modules/test_na_sg_org_user.py @@ -0,0 +1,476 @@ +# (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 StorageGRID Org Group Ansible module: na_sg_org_user""" + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type +import json +import pytest +import sys +try: + from requests import Response +except ImportError: + if sys.version_info < (2, 7): + pytestmark = pytest.mark.skip('Skipping Unit Tests on 2.6 as requests is not available') + else: + raise + +from ansible_collections.netapp.storagegrid.tests.unit.compat import unittest +from ansible_collections.netapp.storagegrid.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.storagegrid.plugins.modules.na_sg_org_user import ( + SgOrgUser as org_user_module, +) + +# REST API canned responses when mocking send_request +SRR = { + # common responses + "empty_good": ({"data": []}, None), + "not_found": ({"status": "error", "code": 404, "data": {}}, {"key": "error.404"},), + "end_of_sequence": (None, "Unexpected call to send_request"), + "generic_error": (None, "Expected error"), + "delete_good": ({"code": 204}, None), + "pw_change_good": ({"code": 204}, None), + "org_groups": ( + { + "data": [ + { + "displayName": "TestOrgGroup1", + "uniqueName": "group/testorggroup1", + "accountId": "12345678901234567890", + "id": "12345678-abcd-1234-abcd-1234567890ab", + "federated": False, + "groupURN": "urn:sgws:identity::12345678901234567890:group/testorggroup1", + }, + { + "displayName": "TestOrgGroup2", + "uniqueName": "group/testorggroup2", + "accountId": "12345678901234567890", + "id": "87654321-abcd-1234-cdef-1234567890ab", + "federated": False, + "groupURN": "urn:sgws:identity::12345678901234567890:group/testorggroup2", + }, + ] + }, + None, + ), + "org_users": ( + { + "data": [ + { + "id": "09876543-abcd-4321-abcd-0987654321ab", + "accountId": "12345678901234567890", + "fullName": "testorguser", + "uniqueName": "user/ansible-sg-demo-user1", + "userURN": "urn:sgws:identity::12345678901234567890:user/testorguser", + "federated": False, + "memberOf": ["12345678-abcd-1234-abcd-1234567890ab"], + "disable": False, + } + ] + }, + None, + ), + "org_user_record_no_group": ( + { + "data": { + "id": "09876543-abcd-4321-abcd-0987654321ab", + "accountId": "12345678901234567890", + "fullName": "testorguser", + "uniqueName": "user/ansible-sg-demo-user1", + "userURN": "urn:sgws:identity::12345678901234567890:user/testorguser", + "federated": False, + "disable": False, + } + }, + None, + ), + "org_user_record": ( + { + "data": { + "id": "09876543-abcd-4321-abcd-0987654321ab", + "accountId": "12345678901234567890", + "fullName": "testorguser", + "uniqueName": "user/ansible-sg-demo-user1", + "userURN": "urn:sgws:identity::12345678901234567890:user/testorguser", + "federated": False, + "memberOf": ["12345678-abcd-1234-abcd-1234567890ab"], + "disable": False, + } + }, + None, + ), + "org_user_record_update": ( + { + "data": { + "id": "09876543-abcd-4321-abcd-0987654321ab", + "accountId": "12345678901234567890", + "fullName": "testorguser", + "uniqueName": "user/ansible-sg-demo-user1", + "userURN": "urn:sgws:identity::12345678901234567890:user/testorguser", + "federated": False, + "memberOf": [ + "12345678-abcd-1234-abcd-1234567890ab", + "87654321-abcd-1234-cdef-1234567890ab", + ], + "disable": 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""" + + 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 set_default_args_fail_check(self): + return dict( + { + "full_name": "TestUser", + "api_url": "gmi.example.com", + "auth_token": "01234567-5678-9abc-78de-9fgabc123def", + "validate_certs": False, + } + ) + + def set_default_args_pass_check(self): + return dict( + { + "state": "present", + "full_name": "TestUser", + "unique_name": "user/testuser", + "api_url": "gmi.example.com", + "auth_token": "01234567-5678-9abc-78de-9fgabc123def", + "validate_certs": False, + } + ) + + def set_args_create_na_sg_org_user_no_group(self): + return dict( + { + "state": "present", + "full_name": "TestUser", + "unique_name": "user/testuser", + "api_url": "gmi.example.com", + "auth_token": "01234567-5678-9abc-78de-9fgabc123def", + "validate_certs": False, + } + ) + + def set_args_create_na_sg_org_user(self): + return dict( + { + "state": "present", + "full_name": "TestUser", + "unique_name": "user/testuser", + "member_of": ["group/testorggroup1"], + "api_url": "gmi.example.com", + "auth_token": "01234567-5678-9abc-78de-9fgabc123def", + "validate_certs": False, + } + ) + + def set_args_delete_na_sg_org_user(self): + return dict( + { + "state": "absent", + # "full_name": "TestUser", + "unique_name": "user/testuser", + # "member_of": ["group/testorggroup1"], + "api_url": "gmi.example.com", + "auth_token": "01234567-5678-9abc-78de-9fgabc123def", + "validate_certs": 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(self.set_default_args_fail_check()) + org_user_module() + print( + "Info: test_module_fail_when_required_args_missing: %s" + % exc.value.args[0]["msg"] + ) + + def test_module_fail_when_required_args_present(self): + """ required arguments are reported as errors """ + with pytest.raises(AnsibleExitJson) as exc: + set_module_args(self.set_default_args_pass_check()) + org_user_module() + exit_json(changed=True, msg="Induced arguments check") + print( + "Info: test_module_fail_when_required_args_present: %s" + % exc.value.args[0]["msg"] + ) + assert exc.value.args[0]["changed"] + + def test_module_fail_with_bad_unique_name(self): + """ error returned if unique_name doesn't start with user or federated_user """ + with pytest.raises(AnsibleFailJson) as exc: + args = self.set_default_args_pass_check() + args["unique_name"] = "noprefixuser" + set_module_args(args) + org_user_module() + print( + "Info: test_module_fail_with_bad_unique_name: %s" % exc.value.args[0]["msg"] + ) + + def set_args_create_na_sg_org_user_with_password(self): + return dict( + { + "state": "present", + "full_name": "TestUser", + "unique_name": "user/testuser", + "member_of": ["group/testorggroup1"], + "password": "netapp123", + "api_url": "gmi.example.com", + "auth_token": "01234567-5678-9abc-78de-9fgabc123def", + "validate_certs": False, + } + ) + + @patch( + "ansible_collections.netapp.storagegrid.plugins.module_utils.netapp.SGRestAPI.send_request" + ) + def test_create_na_sg_org_user_no_group_pass(self, mock_request): + set_module_args(self.set_args_create_na_sg_org_user_no_group()) + my_obj = org_user_module() + mock_request.side_effect = [ + SRR["not_found"], # get + SRR["org_user_record_no_group"], # post + SRR["end_of_sequence"], + ] + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + print( + "Info: test_create_na_sg_org_user_no_group_pass: %s" + % repr(exc.value.args[0]) + ) + assert exc.value.args[0]["changed"] + + @patch( + "ansible_collections.netapp.storagegrid.plugins.module_utils.netapp.SGRestAPI.send_request" + ) + def test_create_na_sg_org_user_pass(self, mock_request): + set_module_args(self.set_args_create_na_sg_org_user()) + my_obj = org_user_module() + mock_request.side_effect = [ + SRR["not_found"], # get + SRR["org_groups"], # get + SRR["org_user_record"], # post + SRR["end_of_sequence"], + ] + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + print("Info: test_create_na_sg_org_user_pass: %s" % repr(exc.value.args[0])) + assert exc.value.args[0]["changed"] + + @patch( + "ansible_collections.netapp.storagegrid.plugins.module_utils.netapp.SGRestAPI.send_request" + ) + def test_idempotent_create_na_sg_org_user_pass(self, mock_request): + set_module_args(self.set_args_create_na_sg_org_user()) + my_obj = org_user_module() + mock_request.side_effect = [ + SRR["org_user_record"], # get + SRR["org_groups"], # get + SRR["end_of_sequence"], + ] + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + print( + "Info: test_idempotent_create_na_sg_org_user_pass: %s" + % repr(exc.value.args[0]) + ) + assert not exc.value.args[0]["changed"] + + @patch( + "ansible_collections.netapp.storagegrid.plugins.module_utils.netapp.SGRestAPI.send_request" + ) + def test_update_na_sg_org_user_pass(self, mock_request): + args = self.set_args_create_na_sg_org_user() + args["member_of"] = ["group/testorggroup1", "group/testorggroup2"] + + set_module_args(args) + my_obj = org_user_module() + mock_request.side_effect = [ + SRR["org_user_record"], # get + SRR["org_groups"], # get + SRR["org_user_record_update"], # put + SRR["end_of_sequence"], + ] + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + print("Info: test_update_na_sg_org_user_pass: %s" % repr(exc.value.args[0])) + assert exc.value.args[0]["changed"] + + @patch( + "ansible_collections.netapp.storagegrid.plugins.module_utils.netapp.SGRestAPI.send_request" + ) + def test_delete_na_sg_org_user_pass(self, mock_request): + set_module_args(self.set_args_delete_na_sg_org_user()) + my_obj = org_user_module() + mock_request.side_effect = [ + SRR["org_user_record"], # get + SRR["org_groups"], # get + SRR["delete_good"], # delete + SRR["end_of_sequence"], + ] + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + print("Info: test_delete_na_sg_org_user_pass: %s" % repr(exc.value.args[0])) + assert exc.value.args[0]["changed"] + + # create user and set pass + @patch( + "ansible_collections.netapp.storagegrid.plugins.module_utils.netapp.SGRestAPI.send_request" + ) + def test_create_na_sg_org_user_and_set_password_pass(self, mock_request): + set_module_args(self.set_args_create_na_sg_org_user_with_password()) + my_obj = org_user_module() + mock_request.side_effect = [ + SRR["not_found"], # get + SRR["org_groups"], # get + SRR["org_user_record"], # post + SRR["pw_change_good"], # post + SRR["end_of_sequence"], + ] + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + print( + "Info: test_create_na_sg_org_user_and_set_password_pass: %s" + % repr(exc.value.args[0]) + ) + assert exc.value.args[0]["changed"] + + # Idempotent user with password defined + @patch( + "ansible_collections.netapp.storagegrid.plugins.module_utils.netapp.SGRestAPI.send_request" + ) + def test_idempotent_create_na_sg_org_user_and_set_password_pass(self, mock_request): + set_module_args(self.set_args_create_na_sg_org_user_with_password()) + my_obj = org_user_module() + mock_request.side_effect = [ + SRR["org_user_record"], # get + SRR["org_groups"], # get + SRR["end_of_sequence"], + ] + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + print( + "Info: test_idempotent_create_na_sg_org_user_and_set_password_pass: %s" + % repr(exc.value.args[0]) + ) + assert not exc.value.args[0]["changed"] + + # update user and set pass + @patch( + "ansible_collections.netapp.storagegrid.plugins.module_utils.netapp.SGRestAPI.send_request" + ) + def test_update_na_sg_org_user_and_set_password_pass(self, mock_request): + args = self.set_args_create_na_sg_org_user_with_password() + args["member_of"] = ["group/testorggroup1", "group/testorggroup2"] + args["update_password"] = "always" + + set_module_args(args) + my_obj = org_user_module() + mock_request.side_effect = [ + SRR["org_user_record"], # get + SRR["org_groups"], # get + SRR["org_user_record_update"], # put + SRR["pw_change_good"], # post + SRR["end_of_sequence"], + ] + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + print( + "Info: test_update_na_sg_org_user_and_set_password_pass: %s" + % repr(exc.value.args[0]) + ) + assert exc.value.args[0]["changed"] + + # set pass only + + @patch( + "ansible_collections.netapp.storagegrid.plugins.module_utils.netapp.SGRestAPI.send_request" + ) + def test_set_na_sg_org_user_password_pass(self, mock_request): + args = self.set_args_create_na_sg_org_user_with_password() + args["update_password"] = "always" + + set_module_args(args) + my_obj = org_user_module() + mock_request.side_effect = [ + SRR["org_user_record"], # get + SRR["org_groups"], # get + SRR["pw_change_good"], # post + SRR["end_of_sequence"], + ] + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + print( + "Info: test_set_na_sg_org_user_password_pass: %s" % repr(exc.value.args[0]) + ) + assert exc.value.args[0]["changed"] + + # attempt to set password on federated user + + @patch( + "ansible_collections.netapp.storagegrid.plugins.module_utils.netapp.SGRestAPI.send_request" + ) + def test_fail_set_federated_user_password(self, mock_request): + with pytest.raises(AnsibleFailJson) as exc: + args = self.set_args_create_na_sg_org_user_with_password() + args["unique_name"] = "federated-user/abc123" + args["update_password"] = "always" + set_module_args(args) + org_user_module() + print( + "Info: test_fail_set_federated_user_password: %s" % repr(exc.value.args[0]) + ) diff --git a/ansible_collections/netapp/storagegrid/tests/unit/plugins/modules/test_na_sg_org_user_s3_key.py b/ansible_collections/netapp/storagegrid/tests/unit/plugins/modules/test_na_sg_org_user_s3_key.py new file mode 100644 index 000000000..53696bdbf --- /dev/null +++ b/ansible_collections/netapp/storagegrid/tests/unit/plugins/modules/test_na_sg_org_user_s3_key.py @@ -0,0 +1,238 @@ +# (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 StorageGRID Org Group Ansible module: na_sg_org_user_s3_key""" + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type +import json +import pytest +import sys +try: + from requests import Response +except ImportError: + if sys.version_info < (2, 7): + pytestmark = pytest.mark.skip('Skipping Unit Tests on 2.6 as requests is not be available') + else: + raise + +from ansible_collections.netapp.storagegrid.tests.unit.compat import unittest +from ansible_collections.netapp.storagegrid.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.storagegrid.plugins.modules.na_sg_org_user_s3_key import ( + SgOrgUserS3Key as org_s3_key_module, +) + +# REST API canned responses when mocking send_request +SRR = { + # common responses + "empty_good": ({"data": []}, None), + "not_found": ( + {"status": "error", "code": 404, "data": {}}, + {"key": "error.404"}, + ), + "end_of_sequence": (None, "Unexpected call to send_request"), + "generic_error": (None, "Expected error"), + "delete_good": ({"code": 204}, None), + "org_user_record": ( + { + "data": { + "id": "09876543-abcd-4321-abcd-0987654321ab", + "accountId": "12345678901234567890", + "fullName": "testorguser", + "uniqueName": "user/testorguser", + "userURN": "urn:sgws:identity::12345678901234567890:user/testorguser", + "federated": False, + "memberOf": ["12345678-abcd-1234-abcd-1234567890ab"], + "disable": False, + } + }, + None, + ), + "org_s3_key": ( + { + "data": { + "id": "abcABC_01234-0123456789abcABCabc0123456789==", + "accountId": 12345678901234567000, + "displayName": "****************AB12", + "userURN": "urn:sgws:identity::12345678901234567890:root", + "userUUID": "09876543-abcd-4321-abcd-0987654321ab", + "expires": "2020-09-04T00:00:00.000Z", + "accessKey": "ABCDEFabcd1234567890", + "secretAccessKey": "abcABC+123456789012345678901234567890123", + } + }, + 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""" + + 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 set_default_args_fail_check(self): + return dict( + { + "unique_user_name": "user/testorguser", + "auth_token": "01234567-5678-9abc-78de-9fgabc123def", + "validate_certs": False, + } + ) + + def set_default_args_pass_check(self): + return dict( + { + "state": "present", + "unique_user_name": "user/testorguser", + "api_url": "gmi.example.com", + "auth_token": "01234567-5678-9abc-78de-9fgabc123def", + "validate_certs": False, + } + ) + + def set_args_create_na_sg_org_user_s3_keys(self): + return dict( + { + "state": "present", + "unique_user_name": "user/testorguser", + "api_url": "gmi.example.com", + "auth_token": "01234567-5678-9abc-78de-9fgabc123def", + "validate_certs": False, + } + ) + + def set_args_delete_na_sg_org_user_s3_keys(self): + return dict( + { + "state": "absent", + "unique_user_name": "user/testorguser", + "access_key": "ABCDEFabcd1234567890", + "api_url": "gmi.example.com", + "auth_token": "01234567-5678-9abc-78de-9fgabc123def", + "validate_certs": 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(self.set_default_args_fail_check()) + org_s3_key_module() + print( + "Info: test_module_fail_when_required_args_missing: %s" + % exc.value.args[0]["msg"] + ) + + def test_module_fail_when_required_args_present(self): + """ required arguments are reported as errors """ + with pytest.raises(AnsibleExitJson) as exc: + set_module_args(self.set_default_args_pass_check()) + org_s3_key_module() + exit_json(changed=True, msg="Induced arguments check") + print( + "Info: test_module_fail_when_required_args_present: %s" + % exc.value.args[0]["msg"] + ) + assert exc.value.args[0]["changed"] + + @patch( + "ansible_collections.netapp.storagegrid.plugins.module_utils.netapp.SGRestAPI.send_request" + ) + def test_create_na_sg_org_user_s3_key_pass(self, mock_request): + set_module_args(self.set_args_create_na_sg_org_user_s3_keys()) + my_obj = org_s3_key_module() + mock_request.side_effect = [ + SRR["org_user_record"], # get + SRR["org_s3_key"], # post + SRR["end_of_sequence"], + ] + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + print( + "Info: test_create_na_sg_org_user_s3_key_pass: %s" + % repr(exc.value.args[0]) + ) + assert exc.value.args[0]["changed"] + + @patch( + "ansible_collections.netapp.storagegrid.plugins.module_utils.netapp.SGRestAPI.send_request" + ) + def test_idempotent_create_na_sg_org_user_s3_key_pass(self, mock_request): + args = self.set_args_create_na_sg_org_user_s3_keys() + args["access_key"] = "ABCDEFabcd1234567890" + set_module_args(args) + my_obj = org_s3_key_module() + mock_request.side_effect = [ + SRR["org_user_record"], # get + SRR["org_s3_key"], # get + SRR["end_of_sequence"], + ] + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + print( + "Info: test_idempotent_create_na_sg_org_user_s3_key_pass: %s" + % repr(exc.value.args[0]) + ) + assert not exc.value.args[0]["changed"] + + @patch( + "ansible_collections.netapp.storagegrid.plugins.module_utils.netapp.SGRestAPI.send_request" + ) + def test_delete_na_sg_org_user_s3_keys_pass(self, mock_request): + set_module_args(self.set_args_delete_na_sg_org_user_s3_keys()) + my_obj = org_s3_key_module() + mock_request.side_effect = [ + SRR["org_s3_key"], # get + SRR["delete_good"], # delete + SRR["end_of_sequence"], + ] + with pytest.raises(AnsibleExitJson) as exc: + my_obj.apply() + print( + "Info: test_delete_na_sg_org_user_s3_keys_pass: %s" + % repr(exc.value.args[0]) + ) + assert exc.value.args[0]["changed"] diff --git a/ansible_collections/netapp/storagegrid/tests/unit/requirements.txt b/ansible_collections/netapp/storagegrid/tests/unit/requirements.txt new file mode 100644 index 000000000..b754473a9 --- /dev/null +++ b/ansible_collections/netapp/storagegrid/tests/unit/requirements.txt @@ -0,0 +1 @@ +requests ; python_version >= '2.7' |