summaryrefslogtreecommitdiffstats
path: root/ansible_collections/netapp/ontap/tests
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-18 05:52:22 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-18 05:52:22 +0000
commit38b7c80217c4e72b1d8988eb1e60bb6e77334114 (patch)
tree356e9fd3762877d07cde52d21e77070aeff7e789 /ansible_collections/netapp/ontap/tests
parentAdding upstream version 7.7.0+dfsg. (diff)
downloadansible-38b7c80217c4e72b1d8988eb1e60bb6e77334114.tar.xz
ansible-38b7c80217c4e72b1d8988eb1e60bb6e77334114.zip
Adding upstream version 9.4.0+dfsg.upstream/9.4.0+dfsg
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r--ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_cg_snapshot_rest.py331
-rw-r--r--ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_cifs_server.py81
-rw-r--r--ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_cifs_unix_symlink_mapping_rest.py252
-rw-r--r--ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_cli_timeout_rest.py104
-rw-r--r--ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_cluster.py24
-rw-r--r--ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_cluster_peer.py48
-rw-r--r--ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_dns.py1
-rw-r--r--ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_ems_config_rest.py107
-rw-r--r--ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_ems_destination.py237
-rw-r--r--ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_ems_filter.py139
-rw-r--r--ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_info.py4
-rw-r--r--ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_kerberos_realm.py36
-rw-r--r--ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_lun_rest.py14
-rw-r--r--ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_net_ifgrp.py3
-rw-r--r--ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_qos_policy_group.py22
-rw-r--r--ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_rest_info.py42
-rw-r--r--ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_restit.py2
-rw-r--r--ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_s3_services.py39
-rw-r--r--ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_security_certificates.py24
-rw-r--r--ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_snapmirror.py41
-rw-r--r--ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_snapshot_policy_rest.py8
-rw-r--r--ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_snmp.py128
-rw-r--r--ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_snmp_config_rest.py123
-rw-r--r--ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_svm.py2
-rw-r--r--ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_volume.py1
-rw-r--r--ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_volume_rest.py111
-rw-r--r--ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_vscan_scanner_pool_rest.py256
27 files changed, 2113 insertions, 67 deletions
diff --git a/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_cg_snapshot_rest.py b/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_cg_snapshot_rest.py
new file mode 100644
index 000000000..52264b43e
--- /dev/null
+++ b/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_cg_snapshot_rest.py
@@ -0,0 +1,331 @@
+# (c) 2023, NetApp, Inc
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+""" unit tests for Ansible module: na_ontap_cg_snapshot while using REST """
+
+from __future__ import (absolute_import, division, print_function)
+__metaclass__ = type
+import pytest
+import sys
+
+import ansible_collections.netapp.ontap.plugins.module_utils.netapp as netapp_utils
+# pylint: disable=unused-import
+from ansible_collections.netapp.ontap.tests.unit.plugins.module_utils.ansible_mocks import patch_ansible, \
+ create_and_apply, create_module
+from ansible_collections.netapp.ontap.tests.unit.framework.mock_rest_and_zapi_requests import get_mock_record, \
+ patch_request_and_invoke, register_responses
+from ansible_collections.netapp.ontap.tests.unit.framework.rest_factory import rest_responses
+
+from ansible_collections.netapp.ontap.plugins.modules.na_ontap_cg_snapshot \
+ import NetAppONTAPCGSnapshot as my_module # module under test
+
+if not netapp_utils.HAS_REQUESTS and sys.version_info < (2, 7):
+ pytestmark = pytest.mark.skip('Skipping Unit Tests on 2.6 as requests is not available')
+
+
+DEFAULT_ARGS = {
+ 'hostname': 'hostname',
+ 'username': 'username',
+ 'password': 'password',
+ 'use_rest': 'always',
+ 'vserver': 'ansibleSVM'
+}
+
+
+# REST API canned responses when mocking send_request.
+# The rest_factory provides default responses shared across testcases.
+SRR = rest_responses({
+ # module specific responses
+ 'cg_info_by_cg_name': (200, {"records": [
+ {
+ "uuid": "af37131d-5dd2-11ee-b8da-005056b37403",
+ "name": "cg1",
+ "svm": {
+ "uuid": "39c2a5a0-35e2-11ee-b8da-005056b37403",
+ "name": "ansibleSVM"
+ }
+ }
+ ],
+ "num_records": 1
+ }, None),
+ 'cg_info_by_volumes': (200, {"records": [
+ {
+ "uuid": "af37131d-5dd2-11ee-b8da-005056b37403",
+ "name": "cg1",
+ "svm": {
+ "uuid": "39c2a5a0-35e2-11ee-b8da-005056b37403",
+ "name": "ansibleSVM"
+ },
+ "volumes": [
+ {
+ "name": "vol1"
+ },
+ {
+ "name": "vol2"
+ }
+ ]
+ }
+ ],
+ "num_records": 1
+ }, None),
+ 'cg_snapshot_info': (200, {"records": [
+ {
+ "consistency_group": {
+ "name": "cg1"
+ },
+ "uuid": "695a3306-6361-11ee-b8da-005056b37403",
+ "name": "snapshot1",
+ "comment": "dummy comment",
+ "snapmirror_label": "sm_label1"
+ }
+ ],
+ "num_records": 1
+ }, None),
+})
+
+
+cg_uuid = SRR['cg_info_by_cg_name'][1]['records'][0]['uuid']
+snapshot_uuid = SRR['cg_snapshot_info'][1]['records'][0]['uuid']
+
+
+def test_rest_successful_create_snapshot_given_consistency_group():
+ '''Test successful rest create snapshot given consistency_group'''
+ register_responses([
+ ('GET', 'cluster', SRR['is_rest_9_10_1']),
+ ('GET', '/application/consistency-groups', SRR['cg_info_by_cg_name']), # retrieve CG, given consistency_group
+ ('GET', '/application/consistency-groups/%s/snapshots' % (cg_uuid), SRR['empty_records']), # retrieve snapshots for the CG
+ ('POST', '/application/consistency-groups/%s/snapshots' % (cg_uuid), SRR['success']), # create CG snapshot
+ ])
+ module_args = {
+ 'state': 'present',
+ 'consistency_group': 'cg1',
+ 'snapshot': 'snap1',
+ 'snapmirror_label': 'sm_label1',
+ 'comment': 'dummy comment'
+ }
+ assert create_and_apply(my_module, DEFAULT_ARGS, module_args)['changed']
+
+
+def test_rest_successful_create_snapshot_idempotency():
+ '''Test successful rest create snapshot idempotency'''
+ register_responses([
+ ('GET', 'cluster', SRR['is_rest_9_10_1']),
+ ('GET', '/application/consistency-groups', SRR['cg_info_by_cg_name']), # retrieve CG, given consistency_group
+ ('GET', '/application/consistency-groups/%s/snapshots' % (cg_uuid), SRR['cg_snapshot_info']), # retrieve snapshots for the CG
+ ])
+ module_args = {
+ 'state': 'present',
+ 'consistency_group': 'cg1',
+ 'snapshot': 'snap1',
+ 'snapmirror_label': 'sm_label1',
+ 'comment': 'dummy comment'
+ }
+ assert create_and_apply(my_module, DEFAULT_ARGS, module_args)['changed'] is False
+
+
+def test_rest_successful_create_snapshot_given_volumes():
+ '''Test successful rest create snapshot given volumes'''
+ register_responses([
+ ('GET', 'cluster', SRR['is_rest_9_10_1']),
+ ('GET', '/application/consistency-groups', SRR['cg_info_by_volumes']), # retrieve CG, given volumes
+ ('GET', '/application/consistency-groups/%s/snapshots' % (cg_uuid), SRR['empty_records']), # retrieve snapshots for the CG
+ ('POST', '/application/consistency-groups/%s/snapshots' % (cg_uuid), SRR['success']), # create CG snapshot
+ ])
+ module_args = {
+ 'state': 'present',
+ 'volumes': ['vol1', 'vol2'],
+ 'snapshot': 'snap1',
+ 'snapmirror_label': 'sm_label1',
+ 'comment': 'dummy comment'
+ }
+ assert create_and_apply(my_module, DEFAULT_ARGS, module_args)['changed']
+
+
+def test_rest_error_create_snapshot():
+ '''Test error rest create snapshot'''
+ register_responses([
+ ('GET', 'cluster', SRR['is_rest_9_10_1']),
+ ('GET', '/application/consistency-groups', SRR['cg_info_by_cg_name']), # retrieve CG, given consistency_group
+ ('GET', '/application/consistency-groups/%s/snapshots' % (cg_uuid), SRR['empty_records']), # retrieve snapshots for the CG
+ ('POST', '/application/consistency-groups/%s/snapshots' % (cg_uuid), SRR['generic_error']),
+ ])
+ module_args = {
+ 'state': 'present',
+ 'consistency_group': 'cg1',
+ 'snapshot': 'snap1',
+ 'snapmirror_label': 'sm_label1',
+ 'comment': 'dummy comment'
+ }
+ error = create_and_apply(my_module, DEFAULT_ARGS, module_args, fail=True)['msg']
+ assert 'Error creating consistency group snapshot' in error
+
+
+def test_rest_successful_delete_snapshot_given_consistency_group():
+ '''Test successful rest delete snapshot given consistency_group'''
+ register_responses([
+ ('GET', 'cluster', SRR['is_rest_9_10_1']),
+ ('GET', '/application/consistency-groups', SRR['cg_info_by_cg_name']), # retrieve CG, given consistency_group
+ ('GET', '/application/consistency-groups/%s/snapshots' % (cg_uuid), SRR['cg_snapshot_info']), # retrieve snapshots for the CG
+ ('DELETE', '/application/consistency-groups/%s/snapshots/%s' % (cg_uuid, snapshot_uuid), SRR['success']), # delete CG snapshot
+ ])
+ module_args = {
+ 'state': 'absent',
+ 'consistency_group': 'cg1',
+ 'snapshot': 'snap1',
+ 'snapmirror_label': 'sm_label1',
+ 'comment': 'dummy comment'
+ }
+ assert create_and_apply(my_module, DEFAULT_ARGS, module_args)['changed']
+
+
+def test_rest_successful_delete_snapshot_idempotency():
+ '''Test successful rest delete snapshot idempotency'''
+ register_responses([
+ ('GET', 'cluster', SRR['is_rest_9_10_1']),
+ ('GET', '/application/consistency-groups', SRR['cg_info_by_cg_name']), # retrieve CG, given consistency_group
+ ('GET', '/application/consistency-groups/%s/snapshots' % (cg_uuid), SRR['empty_records']), # retrieve snapshots for the CG
+ ])
+ module_args = {
+ 'state': 'absent',
+ 'consistency_group': 'cg1',
+ 'snapshot': 'snap1',
+ 'snapmirror_label': 'sm_label1',
+ 'comment': 'dummy comment'
+ }
+ assert create_and_apply(my_module, DEFAULT_ARGS, module_args)['changed'] is False
+
+
+def test_rest_successful_delete_snapshot_given_volumes():
+ '''Test successful rest delete snapshot given volumes'''
+ register_responses([
+ ('GET', 'cluster', SRR['is_rest_9_10_1']),
+ ('GET', '/application/consistency-groups', SRR['cg_info_by_volumes']), # retrieve CG, given volumes
+ ('GET', '/application/consistency-groups/%s/snapshots' % (cg_uuid), SRR['cg_snapshot_info']), # retrieve snapshots for the CG
+ ('DELETE', '/application/consistency-groups/%s/snapshots/%s' % (cg_uuid, snapshot_uuid), SRR['success']), # delete CG snapshot
+ ])
+ module_args = {
+ 'state': 'absent',
+ 'volumes': ['vol1', 'vol2'],
+ 'snapshot': 'snap1',
+ 'snapmirror_label': 'sm_label1',
+ 'comment': 'dummy comment'
+ }
+ assert create_and_apply(my_module, DEFAULT_ARGS, module_args)['changed']
+
+
+def test_rest_error_delete_snapshot():
+ '''Test error rest delete snapshot'''
+ register_responses([
+ ('GET', 'cluster', SRR['is_rest_9_10_1']),
+ ('GET', '/application/consistency-groups', SRR['cg_info_by_cg_name']), # retrieve CG, given consistency_group
+ ('GET', '/application/consistency-groups/%s/snapshots' % (cg_uuid), SRR['cg_snapshot_info']), # retrieve snapshots for the CG
+ ('DELETE', '/application/consistency-groups/%s/snapshots/%s' % (cg_uuid, snapshot_uuid), SRR['generic_error']),
+ ])
+ module_args = {
+ 'state': 'absent',
+ 'consistency_group': 'cg1',
+ 'snapshot': 'snap1',
+ 'snapmirror_label': 'sm_label1',
+ 'comment': 'dummy comment'
+ }
+ error = create_and_apply(my_module, DEFAULT_ARGS, module_args, fail=True)['msg']
+ assert 'Error deleting consistency group snapshot' in error
+
+
+def test_error_ontap_version():
+ ''' Test module supported from 9.10 '''
+ register_responses([
+ ('GET', 'cluster', SRR['is_rest'])
+ ])
+ module_args = {
+ 'state': 'present',
+ 'consistency_group': 'cg1',
+ 'snapshot': 'snap1'
+ }
+ error = create_module(my_module, DEFAULT_ARGS, module_args, fail=True)['msg']
+ assert 'requires ONTAP 9.10.1 or later' in error
+
+
+def test_rest_error_mutually_exclusive_params():
+ '''Test error rest mutually exclusive parameters'''
+ register_responses([
+ ])
+ module_args = {
+ 'state': 'present',
+ 'consistency_group': 'cg1',
+ 'volumes': ['vol1', 'vol2'],
+ 'snapshot': 'snap1',
+ 'snapmirror_label': 'sm_label1',
+ 'comment': 'dummy comment'
+ }
+ error = create_module(my_module, DEFAULT_ARGS, module_args, fail=True)['msg']
+ assert "parameters are mutually exclusive: consistency_group|volumes" in error
+
+
+def test_rest_error_cg_not_found_given_consistency_group():
+ '''Test error rest consistency group not found, given consistency_group'''
+ register_responses([
+ ('GET', 'cluster', SRR['is_rest_9_10_1']),
+ ('GET', '/application/consistency-groups', SRR['empty_records']), # retrieve CG, given consistency_group
+ ])
+ module_args = {
+ 'state': 'present',
+ 'consistency_group': 'cg1',
+ 'snapshot': 'snap1',
+ 'snapmirror_label': 'sm_label1',
+ 'comment': 'dummy comment'
+ }
+ error = create_and_apply(my_module, DEFAULT_ARGS, module_args, fail=True)['msg']
+ assert "Consistency group named 'cg1' not found" in error
+
+
+def test_rest_error_cg_not_found_given_volumes():
+ '''Test error rest consistency group not found, given volumes'''
+ register_responses([
+ ('GET', 'cluster', SRR['is_rest_9_10_1']),
+ ('GET', '/application/consistency-groups', SRR['empty_records']), # retrieve CG, given volumes
+ ])
+ module_args = {
+ 'state': 'present',
+ 'volumes': ['vol3'],
+ 'snapshot': 'snap1',
+ 'snapmirror_label': 'sm_label1',
+ 'comment': 'dummy comment'
+ }
+ error = create_and_apply(my_module, DEFAULT_ARGS, module_args, fail=True)['msg']
+ assert "Consistency group having volumes '['vol3']' not found" in error
+
+
+def test_rest_error_retrieve_cg():
+ '''Test error rest retrieve consistency group'''
+ register_responses([
+ ('GET', 'cluster', SRR['is_rest_9_10_1']),
+ ('GET', '/application/consistency-groups', SRR['generic_error']), # retrieve CG, given consistency_group
+ ])
+ module_args = {
+ 'state': 'absent',
+ 'consistency_group': 'cg1',
+ 'snapshot': 'snap1',
+ 'snapmirror_label': 'sm_label1',
+ 'comment': 'dummy comment'
+ }
+ error = create_and_apply(my_module, DEFAULT_ARGS, module_args, fail=True)['msg']
+ assert 'Error searching for consistency group' in error
+
+
+def test_rest_error_retrieve_cg_snapshot():
+ '''Test error rest retrieve consistency group snapshot'''
+ register_responses([
+ ('GET', 'cluster', SRR['is_rest_9_10_1']),
+ ('GET', '/application/consistency-groups', SRR['cg_info_by_cg_name']), # retrieve CG, given consistency_group
+ ('GET', '/application/consistency-groups/%s/snapshots' % (cg_uuid), SRR['generic_error']), # retrieve snapshots for the CG
+ ])
+ module_args = {
+ 'state': 'absent',
+ 'consistency_group': 'cg1',
+ 'snapshot': 'snap1',
+ 'snapmirror_label': 'sm_label1',
+ 'comment': 'dummy comment'
+ }
+ error = create_and_apply(my_module, DEFAULT_ARGS, module_args, fail=True)['msg']
+ assert 'Error searching for consistency group snapshot' in error
diff --git a/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_cifs_server.py b/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_cifs_server.py
index 820c33d17..1ec919657 100644
--- a/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_cifs_server.py
+++ b/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_cifs_server.py
@@ -1,4 +1,4 @@
-# (c) 2018-2022, NetApp, Inc
+# (c) 2018-2023, NetApp, Inc
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
''' unit tests ONTAP Ansible module: na_ontap_cifs_server '''
@@ -36,6 +36,7 @@ SRR = rest_responses({
"enabled": True,
"security": {
"encrypt_dc_connection": False,
+ "lm_compatibility_level": "lm_ntlm_ntlmv2_krb",
"smb_encryption": False,
"kdc_encryption": False,
"smb_signing": False,
@@ -47,6 +48,9 @@ SRR = rest_responses({
"use_ldaps": False,
"use_start_tls": False
},
+ "options": {
+ "multichannel": True
+ },
"target": {
"name": "20:05:00:50:56:b3:0c:fa"
},
@@ -68,6 +72,7 @@ SRR = rest_responses({
"enabled": False,
"security": {
"encrypt_dc_connection": False,
+ "lm_compatibility_level": "lm_ntlm_ntlmv2_krb",
"smb_encryption": False,
"kdc_encryption": False,
"smb_signing": False,
@@ -79,8 +84,11 @@ SRR = rest_responses({
"use_ldaps": False,
"use_start_tls": False
},
+ "options": {
+ "multichannel": True
+ },
"target": {
- "nam,e": "20:05:00:50:56:b3:0c:fa"
+ "name": "20:05:00:50:56:b3:0c:fa"
},
"name": "cifs_server_name"
}
@@ -100,6 +108,7 @@ SRR = rest_responses({
"enabled": True,
"security": {
"encrypt_dc_connection": False,
+ "lm_compatibility_level": "lm_ntlm_ntlmv2_krb",
"smb_encryption": False,
"kdc_encryption": False,
"smb_signing": False,
@@ -457,6 +466,19 @@ def test_rest_successful_create_with_domain():
assert create_and_apply(my_module, ARGS_REST, module_args)['changed']
+def test_rest_successful_create_with_default_site():
+ '''Test successful rest create'''
+ register_responses([
+ ('GET', 'cluster', SRR['is_rest_9_13_1']),
+ ('GET', 'protocols/cifs/services', SRR['empty_records']),
+ ('POST', 'protocols/cifs/services', SRR['empty_good']),
+ ])
+ module_args = {
+ 'default_site': 'default_site'
+ }
+ assert create_and_apply(my_module, ARGS_REST, module_args)['changed']
+
+
def test_rest_successful_create_with_security():
'''Test successful rest create'''
register_responses([
@@ -469,11 +491,48 @@ def test_rest_successful_create_with_security():
'smb_signing': True,
'kdc_encryption': True,
'encrypt_dc_connection': True,
- 'restrict_anonymous': 'no_enumeration'
+ 'restrict_anonymous': 'no_enumeration',
+ 'lm_compatibility_level': 'lm_ntlm_ntlmv2_krb'
}
assert create_and_apply(my_module, ARGS_REST, module_args)['changed']
+def test_rest_version_error_with_security_options_9_8():
+ register_responses([
+ ('GET', 'cluster', SRR['is_rest'])
+ ])
+ module_args = {
+ 'use_rest': 'always',
+ 'lm_compatibility_level': 'ntlm_ntlmv2_krb',
+ }
+ error = create_module(my_module, ARGS_REST, module_args, fail=True)['msg']
+ assert 'Minimum version of ONTAP for lm_compatibility_level is (9, 8)' in error
+
+
+def test_rest_version_error_with_service_options_9_10():
+ register_responses([
+ ('GET', 'cluster', SRR['is_rest'])
+ ])
+ module_args = {
+ 'use_rest': 'always',
+ 'is_multichannel_enabled': False
+ }
+ error = create_module(my_module, ARGS_REST, module_args, fail=True)['msg']
+ assert 'Minimum version of ONTAP for is_multichannel_enabled is (9, 10, 1)' in error
+
+
+def test_rest_version_error_with_default_site():
+ register_responses([
+ ('GET', 'cluster', SRR['is_rest_9_12_1'])
+ ])
+ module_args = {
+ 'use_rest': 'always',
+ 'default_site': 'default_site',
+ }
+ error = create_module(my_module, ARGS_REST, module_args, fail=True)['msg']
+ assert 'Minimum version of ONTAP for default_site is (9, 13, 1)' in error
+
+
def test_rest_version_error_with_security_encryption():
register_responses([
('GET', 'cluster', SRR['is_rest_96'])
@@ -625,7 +684,7 @@ def test_rest_negative_security_options_modify():
def test_rest_successful_security_options_modify():
- '''Test successful rest enable'''
+ '''Test successful rest security options modify'''
register_responses([
('GET', 'cluster', SRR['is_rest_9_10_1']),
('GET', 'protocols/cifs/services', SRR['cifs_record_disabled']),
@@ -635,12 +694,26 @@ def test_rest_successful_security_options_modify():
"aes_netlogon_enabled": True,
"ldap_referral_enabled": True,
"session_security": "seal",
+ "lm_compatibility_level": "ntlm_ntlmv2_krb",
"try_ldap_channel_binding": False,
"use_ldaps": True
}
assert create_and_apply(my_module, ARGS_REST, module_args)['changed']
+def test_rest_successful_service_options_modify():
+ '''Test successful rest service options modify'''
+ register_responses([
+ ('GET', 'cluster', SRR['is_rest_9_10_1']),
+ ('GET', 'protocols/cifs/services', SRR['cifs_record_disabled']),
+ ('PATCH', 'protocols/cifs/services/671aa46e-11ad-11ec-a267-005056b30cfa', SRR['empty_good']),
+ ])
+ module_args = {
+ "is_multichannel_enabled": False
+ }
+ assert create_and_apply(my_module, ARGS_REST, module_args)['changed']
+
+
def test_rest_successful_rename_cifs():
'''Test successful rest rename'''
register_responses([
diff --git a/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_cifs_unix_symlink_mapping_rest.py b/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_cifs_unix_symlink_mapping_rest.py
new file mode 100644
index 000000000..28e62f6f0
--- /dev/null
+++ b/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_cifs_unix_symlink_mapping_rest.py
@@ -0,0 +1,252 @@
+# Copyright: NetApp, Inc
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+""" unit tests for Ansible module: na_ontap_cifs_unix_symlink_mapping """
+
+from __future__ import (absolute_import, division, print_function)
+__metaclass__ = type
+import pytest
+import sys
+
+import ansible_collections.netapp.ontap.plugins.module_utils.netapp as netapp_utils
+# pylint: disable=unused-import
+from ansible_collections.netapp.ontap.tests.unit.plugins.module_utils.ansible_mocks import patch_ansible, \
+ create_and_apply, create_module, call_main, expect_and_capture_ansible_exception
+from ansible_collections.netapp.ontap.tests.unit.framework.mock_rest_and_zapi_requests import get_mock_record, \
+ patch_request_and_invoke, register_responses
+from ansible_collections.netapp.ontap.tests.unit.framework.rest_factory import rest_responses
+
+from ansible_collections.netapp.ontap.plugins.modules.na_ontap_cifs_unix_symlink_mapping \
+ import NetAppOntapCifsUnixSymlink as my_module, main as my_main # module under test
+
+if not netapp_utils.HAS_REQUESTS and sys.version_info < (2, 7):
+ pytestmark = pytest.mark.skip(
+ 'Skipping Unit Tests on 2.6 as requests is not available')
+
+
+DEFAULT_ARGS = {
+ 'hostname': 'hostname',
+ 'username': 'username',
+ 'password': 'password',
+ 'use_rest': 'always'
+}
+
+
+# REST API canned responses when mocking send_request.
+# The rest_factory provides default responses shared across testcases.
+SRR = rest_responses({
+ # module specific responses
+ 'symlink_mapping': (200, {"records": [
+ {
+ "svm": {
+ "uuid": "a7d278fb-2d2d-11ee-b8da-005056b37403",
+ "name": "ansibleSVM"
+ },
+ "unix_path": "/example1/",
+ "target": {
+ "share": "share1",
+ "path": "/path1/test_dir/",
+ "server": "CIFS",
+ "locality": "local",
+ "home_directory": False
+ }
+ }
+ ]
+ }, None),
+})
+
+
+svm_uuid = 'a7d278fb-2d2d-11ee-b8da-005056b37403'
+unix_path = '/example1/'
+unix_path_encoded = unix_path.replace('/', '%2F')
+
+
+def test_successful_create():
+ ''' Test successful rest create '''
+ register_responses([
+ ('GET', 'cluster', SRR['is_rest_96']),
+ ('GET', 'protocols/cifs/unix-symlink-mapping', SRR['empty_records']),
+ ('POST', 'protocols/cifs/unix-symlink-mapping', SRR['empty_good']),
+ ])
+ args = {
+ 'state': 'present',
+ 'vserver': 'ansibleSVM',
+ 'unix_path': '/example1/',
+ 'share_name': 'share1',
+ 'cifs_path': '/path1/test_dir/'
+ }
+ assert create_and_apply(my_module, DEFAULT_ARGS, args)['changed']
+
+
+def test_successful_create_idempotency():
+ ''' Test successful rest create idempotency '''
+ register_responses([
+ ('GET', 'cluster', SRR['is_rest_96']),
+ ('GET', 'protocols/cifs/unix-symlink-mapping', SRR['symlink_mapping']),
+ ])
+ args = {
+ 'state': 'present',
+ 'vserver': 'ansibleSVM',
+ 'unix_path': '/example1/',
+ 'share_name': 'share1',
+ 'cifs_path': '/path1/test_dir/'
+ }
+ assert create_and_apply(my_module, DEFAULT_ARGS, args)['changed'] is False
+
+
+def test_successful_delete():
+ ''' Test successful rest delete '''
+ unix_path_encoded = '%2Fexample1%2F'
+ register_responses([
+ ('GET', 'cluster', SRR['is_rest_96']),
+ ('GET', 'protocols/cifs/unix-symlink-mapping', SRR['symlink_mapping']),
+ ('DELETE', 'protocols/cifs/unix-symlink-mapping/%s/%s' % (svm_uuid, unix_path_encoded), SRR['success']),
+ ])
+ args = {
+ 'state': 'absent',
+ 'vserver': 'ansibleSVM',
+ 'unix_path': '/example1/'
+ }
+ assert create_and_apply(my_module, DEFAULT_ARGS, args)['changed']
+
+
+def test_successful_delete_idempotency():
+ ''' Test successful rest delete idempotency '''
+ register_responses([
+ ('GET', 'cluster', SRR['is_rest_96']),
+ ('GET', 'protocols/cifs/unix-symlink-mapping', SRR['empty_records']),
+ ])
+ args = {
+ 'state': 'absent',
+ 'vserver': 'ansibleSVM',
+ 'unix_path': '/example1/'
+ }
+ assert create_and_apply(my_module, DEFAULT_ARGS, args)['changed'] is False
+
+
+def test_successful_modify():
+ ''' Test successful rest modify '''
+ register_responses([
+ ('GET', 'cluster', SRR['is_rest_96']),
+ ('GET', 'protocols/cifs/unix-symlink-mapping', SRR['symlink_mapping']),
+ ('PATCH', 'protocols/cifs/unix-symlink-mapping/%s/%s' % (svm_uuid, unix_path_encoded), SRR['success']),
+ ])
+ args = {
+ 'state': 'present',
+ 'vserver': 'ansibleSVM',
+ 'unix_path': '/example1/',
+ 'share_name': 'share2',
+ 'cifs_path': '/path2/test_dir/'
+ }
+ assert create_and_apply(my_module, DEFAULT_ARGS, args)['changed']
+
+
+def test_error_get():
+ ''' Test error rest get '''
+ register_responses([
+ ('GET', 'cluster', SRR['is_rest_96']),
+ ('GET', 'protocols/cifs/unix-symlink-mapping', SRR['generic_error']),
+ ]),
+ args = {
+ 'state': 'present',
+ 'vserver': 'ansibleSVM',
+ 'unix_path': '/example1/',
+ 'share_name': 'share1',
+ 'cifs_path': '/path1/test_dir/'
+ }
+ error = create_and_apply(my_module, DEFAULT_ARGS, args, fail=True)['msg']
+ assert 'Error while fetching cifs unix symlink mapping' in error
+
+
+def test_error_create():
+ ''' Test error rest create '''
+ register_responses([
+ ('GET', 'cluster', SRR['is_rest_96']),
+ ('GET', 'protocols/cifs/unix-symlink-mapping', SRR['empty_records']),
+ ('POST', 'protocols/cifs/unix-symlink-mapping', SRR['generic_error']),
+ ]),
+ args = {
+ 'state': 'present',
+ 'vserver': 'ansibleSVM',
+ 'unix_path': '/example1/',
+ 'share_name': 'share1',
+ 'cifs_path': '/path1/test_dir/'
+ }
+ error = create_and_apply(my_module, DEFAULT_ARGS, args, fail=True)['msg']
+ assert 'Error while creating cifs unix symlink mapping' in error
+
+
+def test_error_modify():
+ ''' Test error rest modify '''
+ register_responses([
+ ('GET', 'cluster', SRR['is_rest_96']),
+ ('GET', 'protocols/cifs/unix-symlink-mapping', SRR['symlink_mapping']),
+ ('PATCH', 'protocols/cifs/unix-symlink-mapping/%s/%s' % (svm_uuid, unix_path_encoded), SRR['generic_error']),
+ ])
+ args = {
+ 'state': 'present',
+ 'vserver': 'ansibleSVM',
+ 'unix_path': '/example1/',
+ 'share_name': 'share2',
+ 'cifs_path': '/path2/test_dir/'
+ }
+ error = create_and_apply(my_module, DEFAULT_ARGS, args, fail=True)['msg']
+ assert 'Error while modifying cifs unix symlink mapping' in error
+
+
+def test_error_delete():
+ ''' Test error rest delete '''
+ register_responses([
+ ('GET', 'cluster', SRR['is_rest_96']),
+ ('GET', 'protocols/cifs/unix-symlink-mapping', SRR['symlink_mapping']),
+ ('DELETE', 'protocols/cifs/unix-symlink-mapping/%s/%s' % (svm_uuid, unix_path_encoded), SRR['generic_error']),
+ ])
+ args = {
+ 'state': 'absent',
+ 'vserver': 'ansibleSVM',
+ 'unix_path': '/example1/'
+ }
+ error = create_and_apply(my_module, DEFAULT_ARGS, args, fail=True)['msg']
+ assert 'Error while deleting cifs unix symlink mapping' in error
+
+
+def test_error_ontap96():
+ ''' Test error module supported from 9.6 '''
+ register_responses([
+ ('GET', 'cluster', SRR['is_rest'])
+ ])
+ args = {
+ 'state': 'present',
+ 'vserver': 'ansibleSVM',
+ 'unix_path': 'example1',
+ 'share_name': 'share1',
+ 'cifs_path': '/path1/test_dir/'
+ }
+ assert 'requires ONTAP 9.6.0 or later' in call_main(my_main, DEFAULT_ARGS, args, fail=True)['msg']
+
+
+def test_missing_options_state_present():
+ ''' Test error missing options with state=present '''
+ register_responses([])
+ args = {
+ 'state': 'present',
+ 'vserver': 'ansibleSVM',
+ 'unix_path': '/example1/'
+ }
+ error = create_module(my_module, DEFAULT_ARGS, args, fail=True)['msg']
+ assert 'state is present but all of the following are missing: share_name, cifs_path' in error
+
+
+def test_missing_options_locality_widelink():
+ ''' Test error missing cifs_server with locality=widelink '''
+ register_responses([])
+ args = {
+ 'state': 'present',
+ 'vserver': 'ansibleSVM',
+ 'unix_path': '/example1/',
+ 'share_name': 'share1',
+ 'cifs_path': '/path1/test_dir/',
+ 'locality': 'widelink'
+ }
+ error = create_module(my_module, DEFAULT_ARGS, args, fail=True)['msg']
+ assert 'locality is widelink but all of the following are missing: cifs_server' in error
diff --git a/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_cli_timeout_rest.py b/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_cli_timeout_rest.py
new file mode 100644
index 000000000..86ec7640e
--- /dev/null
+++ b/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_cli_timeout_rest.py
@@ -0,0 +1,104 @@
+# Copyright: NetApp, Inc
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+""" unit tests for Ansible module: na_ontap_cli_timeout """
+
+from __future__ import (absolute_import, division, print_function)
+__metaclass__ = type
+import pytest
+import sys
+
+import ansible_collections.netapp.ontap.plugins.module_utils.netapp as netapp_utils
+# pylint: disable=unused-import
+from ansible_collections.netapp.ontap.tests.unit.plugins.module_utils.ansible_mocks import patch_ansible, \
+ create_and_apply, create_module, call_main, expect_and_capture_ansible_exception
+from ansible_collections.netapp.ontap.tests.unit.framework.mock_rest_and_zapi_requests import get_mock_record, \
+ patch_request_and_invoke, register_responses
+from ansible_collections.netapp.ontap.tests.unit.framework.rest_factory import rest_responses
+
+from ansible_collections.netapp.ontap.plugins.modules.na_ontap_cli_timeout \
+ import NetAppOntapCliTimeout as my_module, main as my_main # module under test
+
+if not netapp_utils.HAS_REQUESTS and sys.version_info < (2, 7):
+ pytestmark = pytest.mark.skip(
+ 'Skipping Unit Tests on 2.6 as requests is not available')
+
+
+DEFAULT_ARGS = {
+ 'hostname': 'hostname',
+ 'username': 'username',
+ 'password': 'password',
+ 'use_rest': 'always',
+ 'state': 'present'
+}
+
+
+# REST API canned responses when mocking send_request.
+# The rest_factory provides default responses shared across testcases.
+SRR = rest_responses({
+ # module specific responses
+ 'cli_timeout': (200, {
+ 'timeout': 30
+ }, None),
+})
+
+
+def test_successful_modify():
+ ''' Test successful modify timeout value '''
+ register_responses([
+ ('GET', 'cluster', SRR['is_rest_96']),
+ ('GET', 'private/cli/system/timeout', SRR['cli_timeout']), # get timeout value
+ ('PATCH', 'private/cli/system/timeout', SRR['success']), # modify timeout value
+ ])
+ args = {
+ 'timeout': 0
+ }
+ assert create_and_apply(my_module, DEFAULT_ARGS, args)['changed']
+
+
+def test_successful_modify_idempotency():
+ ''' Test successful modify timeout value idempotency '''
+ register_responses([
+ ('GET', 'cluster', SRR['is_rest_96']),
+ ('GET', 'private/cli/system/timeout', SRR['cli_timeout']), # get timeout value
+ ])
+ args = {
+ 'timeout': 30
+ }
+ assert not create_and_apply(my_module, DEFAULT_ARGS, args)['changed']
+
+
+def test_all_methods_catch_exception():
+ ''' Test exception in get/modify timeout value '''
+ register_responses([
+ ('GET', 'cluster', SRR['is_rest_96']),
+ # GET/PATCH error
+ ('GET', 'private/cli/system/timeout', SRR['generic_error']),
+ ('PATCH', 'private/cli/system/timeout', SRR['generic_error'])
+ ])
+ args = {
+ 'timeout': 15
+ }
+ cli_timeout = create_module(my_module, DEFAULT_ARGS, args)
+ error = 'Error fetching CLI sessions timeout value'
+ assert error in expect_and_capture_ansible_exception(cli_timeout.get_timeout_value_rest, 'fail')['msg']
+ error = 'Error modifying CLI sessions timeout value'
+ assert error in expect_and_capture_ansible_exception(cli_timeout.modify_timeout_value_rest, 'fail', args)['msg']
+
+
+def test_missing_options():
+ ''' Test error missing required option timeout '''
+ register_responses([])
+ error = create_module(my_module, DEFAULT_ARGS, fail=True)['msg']
+ assert 'missing required arguments: timeout' in error
+
+
+def test_error_ontap96():
+ ''' Test error module supported from 9.6 '''
+ register_responses([
+ ('GET', 'cluster', SRR['is_rest'])
+ ])
+ args = {
+ 'timeout': 15
+ }
+ assert 'requires ONTAP 9.6.0 or later' in call_main(my_main, DEFAULT_ARGS, args, fail=True)['msg']
diff --git a/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_cluster.py b/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_cluster.py
index 89fe069a3..bfd5ca4ef 100644
--- a/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_cluster.py
+++ b/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_cluster.py
@@ -404,11 +404,13 @@ SRR = {
'is_rest_95': (200, dict(version=dict(generation=9, major=5, minor=0, full='dummy_9_5_0')), None),
'is_rest_96': (200, dict(version=dict(generation=9, major=6, minor=0, full='dummy_9_6_0')), None),
'is_rest_97': (200, dict(version=dict(generation=9, major=7, minor=0, full='dummy_9_7_0')), None),
+ 'is_rest_910': (200, dict(version=dict(generation=9, major=10, minor=1, full='dummy_9_10_1')), None),
'is_zapi': (400, {}, "Unreachable"),
'empty_good': ({}, None, None),
'zero_record': (200, {'records': []}, None),
'precluster': (500, None, {'message': 'are available in precluster.'}),
'cluster_identity': (200, {'location': 'Oz', 'name': 'abc'}, None),
+ 'cluster_web_service': (200, {'certificate': {'uuid': 'abcd12'}}, None),
'nodes': (200, {'records': [
{'name': 'node2', 'uuid': 'uuid2', 'cluster_interfaces': [{'ip': {'address': '10.10.10.2'}}]}
]}, None),
@@ -471,6 +473,28 @@ def test_rest_create_timezone(mock_request, patch_ansible):
@patch('ansible_collections.netapp.ontap.plugins.module_utils.netapp.OntapRestAPI.send_request')
+def test_rest_modify_certificate(mock_request, patch_ansible):
+ ''' modify cluster certificate '''
+ args = dict(set_default_args())
+ args['certificate'] = {'uuid': 'abcd123'}
+ set_module_args(args)
+ mock_request.side_effect = [
+ SRR['is_rest_910'],
+ SRR['cluster_identity'], # get /cluster
+ SRR['cluster_web_service'], # get /cluster/web
+ SRR['empty_good'], # patch /cluster
+ SRR['empty_good'], # patch /cluster/web
+ SRR['end_of_sequence']
+ ]
+ my_obj = my_module()
+ with pytest.raises(AnsibleExitJson) as exc:
+ my_obj.apply()
+ print(mock_request.mock_calls)
+ assert exc.value.args[0]['changed'] is True
+ assert len(mock_request.mock_calls) == 5
+
+
+@patch('ansible_collections.netapp.ontap.plugins.module_utils.netapp.OntapRestAPI.send_request')
def test_rest_create_single(mock_request, patch_ansible):
''' create cluster '''
args = dict(set_default_args())
diff --git a/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_cluster_peer.py b/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_cluster_peer.py
index 7551a619e..190169cee 100644
--- a/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_cluster_peer.py
+++ b/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_cluster_peer.py
@@ -267,6 +267,38 @@ def test_delete_idempotency_rest():
assert create_and_apply(my_module, DEFAULT_ARGS, module_args)
+def test_successful_modify_rest():
+ ''' Test successful modify '''
+ module_args = DEFAULT_ARGS
+ module_args['dest_intercluster_lifs'] = ['10.193.179.58']
+ module_args['source_intercluster_lifs'] = ['10.193.179.181']
+ register_responses([
+ ('GET', 'cluster', SRR['is_rest']),
+ ('GET', 'cluster', SRR['is_rest']),
+ ('GET', 'cluster/peers', SRR['cluster_peer_src']),
+ ('GET', 'cluster/peers', SRR['cluster_peer_dst']),
+ ('PATCH', 'cluster/peers/1fg98aba-2aa6-11ec-b7be-005fgvb366e1', SRR['empty_good']),
+ ('PATCH', 'cluster/peers/1e698aba-2aa6-11ec-b7be-005056b366e1', SRR['empty_good'])
+ ])
+ assert create_and_apply(my_module, module_args)
+
+
+def test_modify_idempotency_rest():
+ ''' Test successful modify idempotency '''
+ module_args = DEFAULT_ARGS
+ module_args['dest_intercluster_lifs'] = ['10.193.179.58']
+ module_args['source_intercluster_lifs'] = ['10.193.179.181']
+ SRR['cluster_peer_src'][1]['records'][0]['remote']['ip_addresses'] = ['10.193.179.58']
+ SRR['cluster_peer_dst'][1]['records'][0]['remote']['ip_addresses'] = ['10.193.179.181']
+ register_responses([
+ ('GET', 'cluster', SRR['is_rest']),
+ ('GET', 'cluster', SRR['is_rest']),
+ ('GET', 'cluster/peers', SRR['cluster_peer_src']),
+ ('GET', 'cluster/peers', SRR['cluster_peer_dst'])
+ ])
+ assert create_and_apply(my_module, module_args)
+
+
def test_error_get_cluster_peer_rest():
''' Test get error '''
register_responses([
@@ -303,3 +335,19 @@ def test_error_create_cluster_peer_rest():
])
error = create_and_apply(my_module, DEFAULT_ARGS, fail=True)['msg']
assert 'calling: cluster/peers: got Expected error.' == error
+
+
+def test_error_modify_cluster_peer_rest():
+ ''' Test modify error '''
+ module_args = DEFAULT_ARGS
+ module_args['dest_intercluster_lifs'] = ['10.193.179.59']
+ module_args['source_intercluster_lifs'] = ['10.193.179.180']
+ register_responses([
+ ('GET', 'cluster', SRR['is_rest']),
+ ('GET', 'cluster', SRR['is_rest']),
+ ('GET', 'cluster/peers', SRR['cluster_peer_src']),
+ ('GET', 'cluster/peers', SRR['cluster_peer_dst']),
+ ('PATCH', 'cluster/peers/1fg98aba-2aa6-11ec-b7be-005fgvb366e1', SRR['generic_error']),
+ ])
+ error = create_and_apply(my_module, DEFAULT_ARGS, module_args, fail=True)['msg']
+ assert 'calling: cluster/peers/1fg98aba-2aa6-11ec-b7be-005fgvb366e1: got Expected error.' == error
diff --git a/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_dns.py b/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_dns.py
index c592f5c88..bb88ee5fc 100644
--- a/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_dns.py
+++ b/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_dns.py
@@ -33,6 +33,7 @@ SRR = rest_responses({
'end_of_sequence': (500, None, "Unexpected call to send_request"),
'generic_error': (400, None, "Expected error"),
'dns_record': (200, {"records": [{"domains": ['test.com'],
+ "uuid": "02c9e252-41be-11e9-81d5-00a0986138f7",
"servers": ['0.0.0.0'],
"svm": {"name": "svm1", "uuid": "02c9e252-41be-11e9-81d5-00a0986138f7"}}]}, None),
'cluster_data': (200, {"dns_domains": ['test.com'],
diff --git a/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_ems_config_rest.py b/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_ems_config_rest.py
new file mode 100644
index 000000000..71efac389
--- /dev/null
+++ b/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_ems_config_rest.py
@@ -0,0 +1,107 @@
+# Copyright: NetApp, Inc
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+""" unit tests for Ansible module: na_ontap_ems_config """
+
+from __future__ import (absolute_import, division, print_function)
+__metaclass__ = type
+import pytest
+import sys
+
+import ansible_collections.netapp.ontap.plugins.module_utils.netapp as netapp_utils
+# pylint: disable=unused-import
+from ansible_collections.netapp.ontap.tests.unit.plugins.module_utils.ansible_mocks import patch_ansible, \
+ create_and_apply, create_module, call_main, expect_and_capture_ansible_exception
+from ansible_collections.netapp.ontap.tests.unit.framework.mock_rest_and_zapi_requests import get_mock_record, \
+ patch_request_and_invoke, register_responses
+from ansible_collections.netapp.ontap.tests.unit.framework.rest_factory import rest_responses
+
+from ansible_collections.netapp.ontap.plugins.modules.na_ontap_ems_config \
+ import NetAppOntapEmsConfig as my_module, main as my_main # module under test
+
+if not netapp_utils.HAS_REQUESTS and sys.version_info < (2, 7):
+ pytestmark = pytest.mark.skip(
+ 'Skipping Unit Tests on 2.6 as requests is not available')
+
+
+DEFAULT_ARGS = {
+ 'hostname': 'hostname',
+ 'username': 'username',
+ 'password': 'password',
+ 'use_rest': 'always',
+ 'state': 'present'
+}
+
+
+# REST API canned responses when mocking send_request.
+# The rest_factory provides default responses shared across testcases.
+SRR = rest_responses({
+ # module specific responses
+ 'ems_config': (200, {
+ 'mail_from': 'administrator@mycompany.com',
+ 'mail_server': 'mail.mycompany.com',
+ 'pubsub_enabled': True
+ }, None),
+ 'ems_config_modified': (200, {
+ 'mail_from': 'admin@mycompany.com',
+ 'mail_server': 'mail.mycompany.com',
+ 'proxy_url': 'http://proxyserver.mycompany.com',
+ 'proxy_user': 'proxy_user',
+ 'pubsub_enabled': False
+ }, None),
+})
+
+
+def test_successful_modify():
+ ''' Test successful rest modify ems config with idempotency check'''
+ register_responses([
+ ('GET', 'cluster', SRR['is_rest_9_10_1']),
+ ('GET', 'support/ems', SRR['ems_config']), # get ems config
+ ('PATCH', 'support/ems', SRR['success']), # modify ems config
+
+ ('GET', 'cluster', SRR['is_rest_9_10_1']), # get ems config
+ ('GET', 'support/ems', SRR['ems_config_modified']), # modify ems config
+ ])
+ args = {
+ 'mail_from': 'admin@mycompany.com',
+ 'mail_server': 'mail.mycompany.com',
+ 'pubsub_enabled': 'false'
+ }
+ assert create_and_apply(my_module, DEFAULT_ARGS, args)['changed']
+ assert not create_and_apply(my_module, DEFAULT_ARGS, args)['changed']
+
+
+def test_all_methods_catch_exception():
+ ''' Test exception in get/modify ems config '''
+ register_responses([
+ ('GET', 'cluster', SRR['is_rest_9_10_1']),
+ # GET/PATCH error
+ ('GET', 'support/ems', SRR['generic_error']),
+ ('PATCH', 'support/ems', SRR['generic_error'])
+ ])
+ modify_args = {
+ 'pubsub_enabled': 'false'
+ }
+ ems_config = create_module(my_module, DEFAULT_ARGS)
+ assert 'Error fetching EMS config' in expect_and_capture_ansible_exception(ems_config.get_ems_config_rest, 'fail')['msg']
+ assert 'Error modifying EMS config' in expect_and_capture_ansible_exception(ems_config.modify_ems_config_rest, 'fail', modify_args)['msg']
+
+
+def test_error_ontap96():
+ ''' Test module supported from 9.6 '''
+ register_responses([
+ ('GET', 'cluster', SRR['is_rest'])
+ ])
+ assert 'requires ONTAP 9.6.0 or later' in call_main(my_main, DEFAULT_ARGS, fail=True)['msg']
+
+
+def test_version_error_with_pubsub_enabled():
+ ''' Test version error for pubsub_enabled '''
+ register_responses([
+ ('GET', 'cluster', SRR['is_rest_96'])
+ ])
+ args = {
+ 'pubsub_enabled': 'false'
+ }
+ error = create_module(my_module, DEFAULT_ARGS, args, fail=True)['msg']
+ assert 'Minimum version of ONTAP for pubsub_enabled is (9, 10, 1)' in error
diff --git a/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_ems_destination.py b/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_ems_destination.py
index ca951ba58..dacc2cb97 100644
--- a/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_ems_destination.py
+++ b/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_ems_destination.py
@@ -48,7 +48,50 @@ SRR = rest_responses({
"destination": "https://test.destination"
}],
"num_records": 1
- }, None)
+ }, None),
+ 'certificate_record_1': (200,
+ {'records': [{"name": "cert_1",
+ "uuid": "cert_uuid_1",
+ "serial_number": "cert_serial"}]}, None),
+ 'ems_destination_with_cert': (200, {
+ "records": [
+ {
+ "name": "test",
+ "type": "rest-api",
+ "destination": "https://test.destination",
+ "certificate": {
+ "ca": "cert_ca",
+ "name": "cert1"
+ },
+ "filters": [
+ {
+ "name": "test-filter"
+ }
+ ]
+ }],
+ "num_records": 1
+ }, None),
+ 'ems_destination_type_syslog': (200, {
+ "records": [
+ {
+ "name": "test",
+ "type": "syslog",
+ "destination": "https://test.destination",
+ "filters": [
+ {
+ "name": "test-filter"
+ }
+ ],
+ "syslog": {
+ "port": 514,
+ "transport": "udp_unencrypted",
+ "message_format": "legacy_netapp",
+ "timestamp_format_override": "no_override",
+ "hostname_format_override": "no_override"
+ }
+ }],
+ "num_records": 1
+ }, None),
})
DEFAULT_ARGS = {
@@ -90,6 +133,40 @@ def test_create_ems_destination():
assert create_and_apply(my_module, DEFAULT_ARGS, module_args)['changed']
+def test_create_ems_destination_with_cert():
+ register_responses([
+ ('GET', 'cluster', SRR['is_rest_9_11_1']),
+ ('GET', 'support/ems/destinations', SRR['empty_records']),
+ ('GET', 'security/certificates', SRR['certificate_record_1']),
+ ('POST', 'support/ems/destinations', SRR['empty_good'])
+ ])
+ module_args = {
+ 'name': 'test',
+ 'type': 'rest_api',
+ 'destination': 'https://test.destination',
+ 'filters': ['test-filter'],
+ 'certificate': 'cert1',
+ 'ca': 'cert_ca',
+ }
+ assert create_and_apply(my_module, DEFAULT_ARGS, module_args)['changed']
+
+
+def test_error_create_ems_destination_with_cert_unsupported_rest():
+ register_responses([
+ ('GET', 'cluster', SRR['is_rest_9_10_1']),
+ ])
+ module_args = {
+ 'name': 'test',
+ 'type': 'rest_api',
+ 'destination': 'https://test.destination',
+ 'filters': ['test-filter'],
+ 'certificate': 'cert1',
+ 'ca': 'cert_ca',
+ }
+ error = call_main(my_main, DEFAULT_ARGS, module_args, fail=True)['msg']
+ assert 'na_ontap_ems_destination is only supported with REST API' == error
+
+
def test_create_ems_destination_error():
register_responses([
('GET', 'cluster', SRR['is_rest_9_10_1']),
@@ -224,3 +301,161 @@ def test_empty_modify_skips_patch():
module_args = {'name': 'test', 'type': 'rest_api', 'destination': 'https://test.destination', 'filters': ['test-filter']}
my_obj = create_module(my_module, DEFAULT_ARGS, module_args)
my_obj.modify_ems_destination('test', {})
+
+
+def test_module_error_missing_required_together_param():
+ module_args = {
+ 'name': 'test',
+ 'type': 'rest_api',
+ 'destination': 'https://test.destination',
+ 'filters': ['test-filter'],
+ 'certificate': 'cert1',
+ }
+ error = call_main(my_main, DEFAULT_ARGS, module_args, fail=True)['msg']
+ assert 'parameters are required together: certificate, ca' == error
+
+
+def test_module_error_cert_not_found():
+ register_responses([
+ ('GET', 'cluster', SRR['is_rest_9_11_1']),
+ ('GET', 'support/ems/destinations', SRR['ems_destination']),
+ ('GET', 'security/certificates', SRR['empty_records']),
+ ])
+ module_args = {
+ 'name': 'test',
+ 'type': 'rest_api',
+ 'destination': 'https://test.destination',
+ 'filters': ['test-filter'],
+ 'certificate': 'cert1',
+ 'ca': 'my_cert_ca',
+ }
+ error = call_main(my_main, DEFAULT_ARGS, module_args, fail=True)['msg']
+ assert 'Error certificate not found: cert1.' == error
+
+
+def test_module_error_rest_get_cert():
+ register_responses([
+ ('GET', 'cluster', SRR['is_rest_9_11_1']),
+ ('GET', 'support/ems/destinations', SRR['ems_destination']),
+ ('GET', 'security/certificates', SRR['generic_error']),
+ ])
+ module_args = {
+ 'name': 'test',
+ 'type': 'rest_api',
+ 'destination': 'https://test.destination',
+ 'filters': ['test-filter'],
+ 'certificate': 'cert1',
+ 'ca': 'my_cert_ca',
+ }
+ error = call_main(my_main, DEFAULT_ARGS, module_args, fail=True)['msg']
+ assert 'Error retrieving certificates: calling: security/certificates: got Expected error.' == error
+
+
+def test_modify_ems_cert():
+ register_responses([
+ ('GET', 'cluster', SRR['is_rest_9_11_1']),
+ ('GET', 'support/ems/destinations', SRR['ems_destination']),
+ ('GET', 'security/certificates', SRR['certificate_record_1']),
+ ('PATCH', 'support/ems/destinations/test', SRR['empty_good'])
+ ])
+ module_args = {
+ 'name': 'test',
+ 'type': 'rest_api',
+ 'destination': 'https://test.destination',
+ 'filters': ['test-filter'],
+ 'certificate': 'cert1',
+ 'ca': 'cert_ca',
+ }
+ assert create_and_apply(my_module, DEFAULT_ARGS, module_args)['changed']
+
+
+def test_create_ems_destination_with_type_syslog():
+ register_responses([
+ ('GET', 'cluster', SRR['is_rest_9_12_1']),
+ ('GET', 'support/ems/destinations', SRR['empty_records']),
+ ('POST', 'support/ems/destinations', SRR['empty_good'])
+ ])
+ module_args = {
+ 'name': 'test',
+ 'type': 'syslog',
+ 'destination': 'https://test.destination',
+ 'filters': ['test-filter'],
+ 'syslog': {
+ 'port': 514,
+ 'transport': 'udp_unencrypted',
+ 'message_format': 'legacy_netapp',
+ 'timestamp_format_override': 'no_override',
+ 'hostname_format_override': 'no_override'
+ }
+ }
+ assert create_and_apply(my_module, DEFAULT_ARGS, module_args)['changed']
+
+
+def test_error_ontap_9_12_1():
+ ''' syslog option supported from 9.12.1 '''
+ register_responses([
+ ('GET', 'cluster', SRR['is_rest_9_10_1'])
+ ])
+ module_args = {
+ 'name': 'test',
+ 'type': 'syslog',
+ 'use_rest': 'always',
+ 'destination': 'https://test.destination',
+ 'filters': ['test-filter'],
+ 'syslog': {
+ 'port': 514,
+ 'transport': 'udp_unencrypted',
+ 'message_format': 'legacy_netapp',
+ 'timestamp_format_override': 'no_override',
+ 'hostname_format_override': 'no_override'
+ }
+ }
+ assert 'Error: Minimum version of ONTAP for syslog is (9, 12, 1). Current version: (9, 10, 1).' in call_main(my_main, DEFAULT_ARGS,
+ module_args, fail=True)['msg']
+
+
+def test_create_ems_destination_with_type_syslog_and_add_certs():
+ register_responses([
+ ('GET', 'cluster', SRR['is_rest_9_12_1']),
+ ('GET', 'support/ems/destinations', SRR['empty_records']),
+ ('GET', 'security/certificates', SRR['certificate_record_1']),
+ ('POST', 'support/ems/destinations', SRR['empty_good'])
+ ])
+ module_args = {
+ 'name': 'test',
+ 'type': 'syslog',
+ 'destination': 'https://test.destination',
+ 'filters': ['test-filter'],
+ 'certificate': 'cert1',
+ 'ca': 'cert_ca',
+ 'syslog': {
+ 'port': 514,
+ 'transport': 'udp_unencrypted',
+ 'message_format': 'legacy_netapp',
+ 'timestamp_format_override': 'no_override',
+ 'hostname_format_override': 'no_override'
+ }
+ }
+ assert create_and_apply(my_module, DEFAULT_ARGS, module_args)['changed']
+
+
+def test_modify_ems_destination_with_type_syslog():
+ register_responses([
+ ('GET', 'cluster', SRR['is_rest_9_12_1']),
+ ('GET', 'support/ems/destinations', SRR['ems_destination_type_syslog']),
+ ('PATCH', 'support/ems/destinations/test', SRR['empty_good'])
+ ])
+ module_args = {
+ 'name': 'test',
+ 'type': 'syslog',
+ 'destination': 'https://test.destination',
+ 'filters': ['test-filter'],
+ 'syslog': {
+ 'port': 614,
+ 'transport': 'tcp_unencrypted',
+ 'message_format': 'rfc_5424',
+ 'timestamp_format_override': 'no_override',
+ 'hostname_format_override': 'fqdn'
+ }
+ }
+ assert create_and_apply(my_module, DEFAULT_ARGS, module_args)['changed']
diff --git a/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_ems_filter.py b/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_ems_filter.py
index f7f0a1feb..8223944c9 100644
--- a/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_ems_filter.py
+++ b/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_ems_filter.py
@@ -25,6 +25,18 @@ if not netapp_utils.HAS_REQUESTS and sys.version_info < (2, 7):
pytestmark = pytest.mark.skip('Skipping Unit Tests on 2.6 as requests is not available')
SRR = rest_responses({
+ 'default_ems_filter': (200, {
+ "name": "snmp-traphost",
+ "rules": [{
+ "index": "1",
+ "type": "exclude",
+ "message_criteria": {
+ "severities": "*",
+ "name_pattern": "*",
+ "snmp_trap_types": "*",
+ }
+ }]
+ }, None),
'ems_filter': (200, {
"name": "snmp-traphost",
"rules": [{
@@ -44,7 +56,8 @@ SRR = rest_responses({
}
}]
}, None),
- 'ems_filter_2_riles': (200, {
+ 'post_empty_good': (201, {}, None),
+ 'ems_filter_2_rules': (200, {
"name": "snmp-traphost",
"rules": [{
"index": "1",
@@ -70,6 +83,39 @@ SRR = rest_responses({
}
}]
}, None),
+ 'ems_filter_3_rules': (200, {
+ "name": "snmp-traphost",
+ "rules": [{
+ "index": "1",
+ "type": "include",
+ "message_criteria": {
+ "severities": "error",
+ "name_pattern": "*",
+ }
+ }, {
+ "index": "2",
+ "type": "include",
+ "message_criteria": {
+ "severities": "alert",
+ "name_pattern": "callhome.*",
+ }
+ }, {
+ "index": "3",
+ "type": "include",
+ "message_criteria": {
+ "severities": "emergency",
+ "name_pattern": "callhome.*",
+ }
+ }, {
+ "index": "4",
+ "type": "exclude",
+ "message_criteria": {
+ "severities": "*",
+ "name_pattern": "*",
+ "snmp_trap_types": "*",
+ }
+ }]
+ }, None),
'ems_filter_no_rules': (200, {
"name": "snmp-traphost",
}, None)
@@ -94,17 +140,19 @@ DEFAULT_RULE = [{
DEFAULT_RULE_2_RULES = [{
"index": "1",
- "type": "include",
+ "type": "exclude",
"message_criteria": {
"severities": "error,informational",
"name_pattern": "callhome.*",
- }}, {
+ }
+}, {
"index": "2",
- "type": "include",
+ "type": "exclude",
"message_criteria": {
- "severities": "alert",
- "name_pattern": "callhome.*",
- }}]
+ "severities": "*",
+ "name_pattern": "*",
+ }
+}]
DEFAULT_RULE_MODIFY_TYPE_2_RULES = [{
"index": "1",
@@ -126,7 +174,7 @@ DEFAULT_RULE_MODIFY_SEVERITIES_2_RULES = [{
"index": "1",
"type": "include",
"message_criteria": {
- "severities": "informational",
+ "severities": "notice",
"name_pattern": "callhome.*",
}
}, {
@@ -150,6 +198,29 @@ DEFAULT_RULE_MODIFY_NAME_PATTERN_2_RULES = [{
"type": "include",
"message_criteria": {
"severities": "alert",
+ "name_pattern": "*",
+ }
+}]
+
+DEFAULT_RULE_MODIFY_SEVERITIES_3_RULES = [{
+ "index": "1",
+ "type": "include",
+ "message_criteria": {
+ "severities": "error, informational",
+ "name_pattern": "*",
+ }
+}, {
+ "index": "2",
+ "type": "include",
+ "message_criteria": {
+ "severities": "alert",
+ "name_pattern": "callhome.*",
+ }
+}, {
+ "index": "3",
+ "type": "include",
+ "message_criteria": {
+ "severities": "emergency",
"name_pattern": "callhome.*",
}
}]
@@ -241,28 +312,32 @@ def test_delete_ems_filter_error():
def test_modify_ems_filter_add_rule():
register_responses([
('GET', 'cluster', SRR['is_rest_9_10_1']),
- ('GET', 'support/ems/filters', SRR['ems_filter']),
- ('PATCH', 'support/ems/filters/snmp-traphost', SRR['empty_good'])
+ ('GET', 'support/ems/filters', SRR['default_ems_filter']),
+ ('POST', 'support/ems/filters/snmp-traphost/rules', SRR['post_empty_good']),
])
- module_args = {'rules': DEFAULT_RULE_2_RULES}
+ module_args = {'rules': DEFAULT_RULE}
assert create_and_apply(my_module, DEFAULT_ARGS, module_args)['changed']
def test_modify_ems_filter_change_type():
register_responses([
('GET', 'cluster', SRR['is_rest_9_10_1']),
- ('GET', 'support/ems/filters', SRR['ems_filter_2_riles']),
- ('PATCH', 'support/ems/filters/snmp-traphost', SRR['empty_good'])
+ ('GET', 'support/ems/filters', SRR['ems_filter']),
+ ('PATCH', 'support/ems/filters/snmp-traphost', SRR['empty_good']),
+ ('POST', 'support/ems/filters/snmp-traphost/rules', SRR['post_empty_good']),
+ ('POST', 'support/ems/filters/snmp-traphost/rules', SRR['post_empty_good'])
])
- module_args = {'rules': DEFAULT_RULE_MODIFY_TYPE_2_RULES}
+ module_args = {'rules': DEFAULT_RULE_2_RULES}
assert create_and_apply(my_module, DEFAULT_ARGS, module_args)['changed']
def test_modify_ems_filter_change_severities():
register_responses([
('GET', 'cluster', SRR['is_rest_9_10_1']),
- ('GET', 'support/ems/filters', SRR['ems_filter_2_riles']),
- ('PATCH', 'support/ems/filters/snmp-traphost', SRR['empty_good'])
+ ('GET', 'support/ems/filters', SRR['ems_filter_2_rules']),
+ ('PATCH', 'support/ems/filters/snmp-traphost', SRR['empty_good']),
+ ('POST', 'support/ems/filters/snmp-traphost/rules', SRR['post_empty_good']),
+ ('POST', 'support/ems/filters/snmp-traphost/rules', SRR['post_empty_good'])
])
module_args = {'rules': DEFAULT_RULE_MODIFY_SEVERITIES_2_RULES}
assert create_and_apply(my_module, DEFAULT_ARGS, module_args)['changed']
@@ -271,21 +346,38 @@ def test_modify_ems_filter_change_severities():
def test_modify_ems_filter_change_name_pattern():
register_responses([
('GET', 'cluster', SRR['is_rest_9_10_1']),
- ('GET', 'support/ems/filters', SRR['ems_filter_2_riles']),
- ('PATCH', 'support/ems/filters/snmp-traphost', SRR['empty_good'])
+ ('GET', 'support/ems/filters', SRR['ems_filter_2_rules']),
+ ('PATCH', 'support/ems/filters/snmp-traphost', SRR['empty_good']),
+ ('POST', 'support/ems/filters/snmp-traphost/rules', SRR['post_empty_good']),
+ ('POST', 'support/ems/filters/snmp-traphost/rules', SRR['post_empty_good'])
])
module_args = {'rules': DEFAULT_RULE_MODIFY_NAME_PATTERN_2_RULES}
assert create_and_apply(my_module, DEFAULT_ARGS, module_args)['changed']
+def test_modify_ems_filter_add_rule_and_change_severities():
+ register_responses([
+ ('GET', 'cluster', SRR['is_rest_9_10_1']),
+ ('GET', 'support/ems/filters', SRR['ems_filter_2_rules']),
+ ('PATCH', 'support/ems/filters/snmp-traphost', SRR['empty_good']),
+ ('POST', 'support/ems/filters/snmp-traphost/rules', SRR['post_empty_good']),
+ ('POST', 'support/ems/filters/snmp-traphost/rules', SRR['post_empty_good']),
+ ('POST', 'support/ems/filters/snmp-traphost/rules', SRR['post_empty_good'])
+ ])
+ module_args = {'rules': DEFAULT_RULE_MODIFY_SEVERITIES_3_RULES}
+ assert create_and_apply(my_module, DEFAULT_ARGS, module_args)['changed']
+
+
def test_modify_ems_filter_error():
register_responses([
('GET', 'cluster', SRR['is_rest_9_10_1']),
- ('PATCH', 'support/ems/filters/snmp-traphost', SRR['generic_error'])
+ ('PATCH', 'support/ems/filters/snmp-traphost', SRR['generic_error']),
])
my_obj = create_module(my_module, DEFAULT_ARGS)
- my_obj.parameters['rules'] = DEFAULT_RULE_2_RULES
- error = expect_and_capture_ansible_exception(my_obj.modify_ems_filter, 'fail')['msg']
+ patch_rules = [{'index': 1, 'type': 'include', 'message_criteria': {'severities': 'error', 'name_pattern': '*'}}]
+ post_rules = [{'index': 2, 'type': 'include', 'message_criteria': {'severities': 'notice', 'name_pattern': '*'}}]
+ desired_rules = {'patch_rules': patch_rules, 'post_rules': post_rules}
+ error = expect_and_capture_ansible_exception(my_obj.modify_ems_filter, 'fail', desired_rules)['msg']
print('Info: %s' % error)
assert 'Error modifying EMS filter snmp-traphost: calling: support/ems/filters/snmp-traphost: got Expected error.' == error
@@ -293,7 +385,7 @@ def test_modify_ems_filter_error():
def test_modify_ems_filter_no_rules():
register_responses([
('GET', 'cluster', SRR['is_rest_9_10_1']),
- ('GET', 'support/ems/filters', SRR['ems_filter_no_rules']),
+ ('GET', 'support/ems/filters', SRR['default_ems_filter']),
])
assert not create_and_apply(my_module, DEFAULT_ARGS, {})['changed']
@@ -302,7 +394,8 @@ def test_modify_star_test():
register_responses([
('GET', 'cluster', SRR['is_rest_9_10_1']),
('GET', 'support/ems/filters', SRR['ems_filter']),
- ('PATCH', 'support/ems/filters/snmp-traphost', SRR['empty_good'])
+ ('PATCH', 'support/ems/filters/snmp-traphost', SRR['empty_good']),
+ ('POST', 'support/ems/filters/snmp-traphost/rules', SRR['post_empty_good'])
])
module_args = {'rules': DEFAULT_RULE_STARS}
assert create_and_apply(my_module, DEFAULT_ARGS, module_args)['changed']
diff --git a/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_info.py b/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_info.py
index 18c35c910..c3c845b16 100644
--- a/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_info.py
+++ b/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_info.py
@@ -7,6 +7,7 @@ from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
import pytest
+import sys
from ansible_collections.netapp.ontap.tests.unit.compat.mock import patch
import ansible_collections.netapp.ontap.plugins.module_utils.netapp as netapp_utils
@@ -21,6 +22,9 @@ from ansible_collections.netapp.ontap.plugins.modules.na_ontap_info import conve
if not netapp_utils.has_netapp_lib():
pytestmark = pytest.mark.skip('skipping as missing required netapp_lib')
+if sys.version_info < (2, 8):
+ pytestmark = pytest.mark.skip('Skipping Unit Tests on python 2')
+
DEFAULT_ARGS = {
'hostname': 'hostname',
diff --git a/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_kerberos_realm.py b/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_kerberos_realm.py
index 30f577d4c..c1db9d26a 100644
--- a/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_kerberos_realm.py
+++ b/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_kerberos_realm.py
@@ -1,4 +1,4 @@
-# (c) 2018-2022, NetApp, Inc
+# (c) 2018-2023, NetApp, Inc
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
''' unit test for ONTAP Kerberos Realm module '''
@@ -36,7 +36,8 @@ DEFAULT_ARGS = {
'realm': 'NETAPP.COM',
'vserver': 'vserver1',
'kdc_ip': '192.168.0.1',
- 'kdc_vendor': 'other'
+ 'kdc_vendor': 'other',
+
}
kerberos_info = {
@@ -78,6 +79,15 @@ SRR = rest_responses({
"ip": "10.193.115.116",
"port": 88
},
+ "admin_server": {
+ "address": "10.193.115.116",
+ "port": 126
+ },
+ "password_server": {
+ "address": "1.2.3.4",
+ "port": 0
+ },
+ "clock_skew": 10,
"comment": "mohan",
"ad_server": {
"name": "netapp",
@@ -157,7 +167,7 @@ def test_if_all_methods_catch_exception():
('kerberos-realm-create', ZRR['error']),
('kerberos-realm-modify', ZRR['error']),
('kerberos-realm-delete', ZRR['error']),
- ('GET', 'cluster', SRR['is_rest_9_9_0']),
+ ('GET', 'cluster', SRR['is_rest_9_13_1']),
('GET', 'protocols/nfs/kerberos/realms', SRR['generic_error']),
('POST', 'protocols/nfs/kerberos/realms', SRR['generic_error']),
('PATCH', 'protocols/nfs/kerberos/realms/89368b07/NETAPP.COM', SRR['generic_error']),
@@ -180,7 +190,7 @@ def test_if_all_methods_catch_exception():
def test_successfully_create_realm_rest():
''' Test successfully create realm '''
register_responses([
- ('GET', 'cluster', SRR['is_rest_9_9_0']),
+ ('GET', 'cluster', SRR['is_rest_9_13_1']),
('GET', 'protocols/nfs/kerberos/realms', SRR['empty_records']),
('POST', 'protocols/nfs/kerberos/realms', SRR['success']),
])
@@ -191,11 +201,11 @@ def test_successfully_modify_realm_rest():
''' Test modify realm successful for modifying kdc_ip. '''
register_responses([
# modify ip.
- ('GET', 'cluster', SRR['is_rest_9_9_0']),
+ ('GET', 'cluster', SRR['is_rest_9_13_1']),
('GET', 'protocols/nfs/kerberos/realms', SRR['kerberos_info']),
('PATCH', 'protocols/nfs/kerberos/realms/89368b07/NETAPP.COM', SRR['success']),
# modify port.
- ('GET', 'cluster', SRR['is_rest_9_9_0']),
+ ('GET', 'cluster', SRR['is_rest_9_13_1']),
('GET', 'protocols/nfs/kerberos/realms', SRR['kerberos_info']),
('PATCH', 'protocols/nfs/kerberos/realms/89368b07/NETAPP.COM', SRR['success']),
])
@@ -206,8 +216,20 @@ def test_successfully_modify_realm_rest():
def test_successfully_delete_realm_rest():
''' Test successfully delete realm '''
register_responses([
- ('GET', 'cluster', SRR['is_rest_9_9_0']),
+ ('GET', 'cluster', SRR['is_rest_9_13_1']),
('GET', 'protocols/nfs/kerberos/realms', SRR['kerberos_info']),
('DELETE', 'protocols/nfs/kerberos/realms/89368b07/NETAPP.COM', SRR['success'])
])
assert create_and_apply(my_module, DEFAULT_ARGS, {'use_rest': 'always', 'state': 'absent'})
+
+
+def test_error_with_params_supported_before_9_13_1_rest():
+ register_responses([
+ ('GET', 'cluster', SRR['is_rest_9_9_0']),
+ ])
+ args = {'use_rest': 'always', 'admin_server_ip': '10.193.115.116', 'admin_server_port': 126, 'clock_skew': 10, 'pw_server_ip': '1.2.3.4',
+ 'pw_server_port': 0}
+ error = create_module(my_module, DEFAULT_ARGS, args, fail=True)['msg']
+ unsupported_param_before_9_13_1 = ['admin_server_ip', 'admin_server_port', 'clock_skew', 'pw_server_ip', 'pw_server_port']
+ msg = 'Error: Minimum version of ONTAP for ' + ' is (9, 13, 1).\nMinimum version of ONTAP for '.join(unsupported_param_before_9_13_1) + ' is (9, 13, 1).'
+ assert msg in error
diff --git a/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_lun_rest.py b/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_lun_rest.py
index fd65062d0..36d07815f 100644
--- a/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_lun_rest.py
+++ b/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_lun_rest.py
@@ -1,4 +1,4 @@
-# (c) 2022, NetApp, Inc
+# (c) 2022-2023, NetApp, Inc
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import (absolute_import, division, print_function)
@@ -36,6 +36,10 @@ SRR = rest_responses({
"name": "volume1",
"uuid": "028baa66-41bd-11e9-81d5-00a0986138f7"
},
+ "qtree": {
+ "name": "qtree1",
+ "id": 1,
+ },
},
"name": "/vol/volume1/qtree1/lun1",
"space": {
@@ -77,6 +81,10 @@ SRR = rest_responses({
"name": "volume1",
"uuid": "028baa66-41bd-11e9-81d5-00a0986138f7"
},
+ "qtree": {
+ "name": "qtree1",
+ "id": 1,
+ },
},
"name": "/vol/volume1/qtree1/lun1",
"space": {
@@ -114,6 +122,10 @@ SRR = rest_responses({
"name": "volume2",
"uuid": "028baa66-41bd-11e9-81d5-00a0986138f3"
},
+ "qtree": {
+ "name": "qtree1",
+ "id": 1,
+ },
},
"name": "/vol/volume1/qtree1/lun2",
"space": {
diff --git a/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_net_ifgrp.py b/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_net_ifgrp.py
index 7e3e58783..011d53083 100644
--- a/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_net_ifgrp.py
+++ b/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_net_ifgrp.py
@@ -1,4 +1,4 @@
-# (c) 2018, NetApp, Inc
+# (c) 2018-2023, NetApp, Inc
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
''' unit test template for ONTAP Ansible module '''
@@ -629,6 +629,7 @@ def test_create_ifgrp_port(mock_request, patch_ansible):
SRR['is_rest_9_8'], # get version
SRR['ifgrp_record_create'], # get
SRR['empty_good'], # create
+ SRR['ifgrp_record_create'], # get details of created lag
SRR['end_of_sequence']
]
my_obj = ifgrp_module()
diff --git a/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_qos_policy_group.py b/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_qos_policy_group.py
index c14b13151..44388fd54 100644
--- a/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_qos_policy_group.py
+++ b/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_qos_policy_group.py
@@ -1,7 +1,7 @@
# (c) 2018-2023, NetApp, Inc
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
-''' unit test template for ONTAP Ansible module '''
+''' unit test cases for ONTAP Ansible module: na_ontap_qos_policy_group '''
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
@@ -349,6 +349,10 @@ def test_successful_create_adaptive_rest():
('GET', 'cluster', SRR['is_rest_9_10_1']),
('GET', 'storage/qos/policies', SRR['empty_records']),
('POST', 'storage/qos/policies', SRR['success']),
+ # with expected and peak IOPS per TB
+ ('GET', 'cluster', SRR['is_rest_9_10_1']),
+ ('GET', 'storage/qos/policies', SRR['empty_records']),
+ ('POST', 'storage/qos/policies', SRR['success']),
])
DEFAULT_ARGS_COPY = DEFAULT_ARGS_REST.copy()
del DEFAULT_ARGS_COPY['fixed_qos_options']
@@ -358,9 +362,14 @@ def test_successful_create_adaptive_rest():
"peak_iops": 500
}
assert create_and_apply(qos_policy_group_module, DEFAULT_ARGS_COPY)['changed']
+
DEFAULT_ARGS_COPY['adaptive_qos_options']['block_size'] = '4k'
assert create_and_apply(qos_policy_group_module, DEFAULT_ARGS_COPY)['changed']
+ DEFAULT_ARGS_COPY['adaptive_qos_options']['expected_iops_allocation'] = 'used_space'
+ DEFAULT_ARGS_COPY['adaptive_qos_options']['peak_iops_allocation'] = 'allocated_space'
+ assert create_and_apply(qos_policy_group_module, DEFAULT_ARGS_COPY)['changed']
+
def test_partially_supported_option_rest():
''' Test delete error '''
@@ -370,16 +379,19 @@ def test_partially_supported_option_rest():
])
error = create_module(qos_policy_group_module, DEFAULT_ARGS_REST, fail=True)['msg']
assert "Minimum version of ONTAP for 'fixed_qos_options.min_throughput_mbps' is (9, 8, 0)" in error
+
DEFAULT_ARGS_COPY = DEFAULT_ARGS_REST.copy()
del DEFAULT_ARGS_COPY['fixed_qos_options']
DEFAULT_ARGS_COPY['adaptive_qos_options'] = {
"absolute_min_iops": 100,
"expected_iops": 200,
"peak_iops": 500,
- "block_size": "4k"
+ "block_size": "4k",
+ "expected_iops_allocation": "used_space",
+ "peak_iops_allocation": "allocated_space"
}
error = create_module(qos_policy_group_module, DEFAULT_ARGS_COPY, fail=True)['msg']
- assert "Minimum version of ONTAP for 'adaptive_qos_options.block_size' is (9, 10, 1)" in error
+ assert "using any of ['block_size', 'expected_iops_allocation', 'peak_iops_allocation'] requires ONTAP 9.10.1 or later and REST must be enabled" in error
def test_error_create_adaptive_rest():
@@ -486,7 +498,9 @@ def test_successful_modify_adaptive_qos_options_rest():
'expected_iops': 300,
'peak_iops': 600,
'absolute_min_iops': 200,
- 'block_size': '4k'
+ 'block_size': '4k',
+ 'expected_iops_allocation': 'used_space',
+ 'peak_iops_allocation': 'allocated_space'
}
}
assert create_and_apply(qos_policy_group_module, DEFAULT_ARGS_REST_COPY, args)['changed']
diff --git a/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_rest_info.py b/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_rest_info.py
index bf678e3ac..08ea9f2bc 100644
--- a/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_rest_info.py
+++ b/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_rest_info.py
@@ -1,4 +1,4 @@
-# (c) 2020-2022, NetApp, Inc
+# (c) 2020-2023, NetApp, Inc
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
''' Unit Tests NetApp ONTAP REST APIs Ansible module: na_ontap_rest_info '''
@@ -56,6 +56,12 @@ SRR = rest_responses({
'records': [{'name': 'dummy_vol1'},
{'name': 'dummy_vol2'}],
'version': 'ontap_version'}, None),
+ 'get_subset_info_without_hal_links': (200,
+ {'num_records': 3,
+ 'records': [{'name': 'dummy_vol1'},
+ {'name': 'dummy_vol2'},
+ {'name': 'dummy_vol3'}],
+ 'version': 'ontap_version'}, None),
'metrocluster_post': (200,
{'job': {
'uuid': 'fde79888-692a-11ea-80c2-005056b39fe7',
@@ -617,6 +623,17 @@ def set_default_args():
})
+def set_args_run_ontap_gather_facts_disable_hal_links():
+ return dict({
+ 'hostname': 'hostname',
+ 'username': 'username',
+ 'password': 'password',
+ 'https': True,
+ 'validate_certs': False,
+ 'hal_linking': False
+ })
+
+
def set_args_run_ontap_version_check():
return dict({
'hostname': 'hostname',
@@ -763,6 +780,19 @@ def test_version_warning_message():
'your version of ONTAP cluster/metrocluster/diagnostics requires (9, 8), ')
+def test_owning_resource_warning_message():
+ gather_subset = ['cluster/nodes']
+ register_responses([
+ ('GET', 'cluster', SRR['is_rest_96']),
+ ('GET', 'storage/volumes', SRR['get_subset_info']),
+ ])
+ extra_args = {
+ 'owning_resource': {'svm_name': 'testSVM'}
+ }
+ create_and_apply(ontap_rest_info_module, set_args_run_ontap_version_check(), extra_args)
+ assert_warning_was_raised("Kindly refer to Ansible documentation to check the subsets that support option 'owning_resource'.")
+
+
# metrocluster/diagnostics doesn't call get_subset_info and has 3 api calls instead of 1
def test_run_metrocluster_pass():
gather_subset = ['cluster/metrocluster/diagnostics']
@@ -981,6 +1011,16 @@ def test_demo_subset():
assert 'cluster/nodes' in call_main(my_main, set_default_args(), {'gather_subset': 'demo'})['ontap_info']
+def test_demo_subset_without_hal_links():
+ register_responses([
+ ('GET', 'cluster', SRR['validate_ontap_version_pass']),
+ ('GET', 'cluster/software', SRR['get_subset_info_without_hal_links']),
+ ('GET', 'svm/svms', SRR['get_subset_info_without_hal_links']),
+ ('GET', 'cluster/nodes', SRR['get_subset_info_without_hal_links']),
+ ])
+ assert 'cluster/nodes' in call_main(my_main, set_args_run_ontap_gather_facts_disable_hal_links(), {'gather_subset': 'demo'})['ontap_info']
+
+
def test_subset_with_default_fields():
register_responses([
('GET', 'cluster', SRR['validate_ontap_version_pass']),
diff --git a/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_restit.py b/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_restit.py
index 89289386a..6b15d9087 100644
--- a/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_restit.py
+++ b/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_restit.py
@@ -97,7 +97,7 @@ def test_rest_run_default_get(mock_request, patch_ansible):
my_obj = my_module()
with pytest.raises(AnsibleExitJson) as exc:
my_obj.apply()
- assert exc.value.args[0]['changed'] is True
+ assert exc.value.args[0]['changed'] is False
print(mock_request.mock_calls)
assert len(mock_request.mock_calls) == 1
diff --git a/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_s3_services.py b/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_s3_services.py
index fce59093a..4bbd43f82 100644
--- a/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_s3_services.py
+++ b/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_s3_services.py
@@ -1,4 +1,4 @@
-# (c) 2022, NetApp, Inc
+# (c) 2022-2023, NetApp, Inc
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import (absolute_import, division, print_function)
@@ -97,7 +97,8 @@ def test_create_s3_service():
('GET', 'cluster', SRR['is_rest_9_10_1']),
('GET', 'cluster', SRR['is_rest_9_10_1']),
('GET', 'protocols/s3/services', SRR['empty_records']),
- ('POST', 'protocols/s3/services', SRR['empty_good'])
+ ('POST', 'protocols/s3/services', SRR['empty_good']),
+ ('GET', 'protocols/s3/services', SRR['s3_service']),
])
module_args = {
'enabled': True,
@@ -107,6 +108,22 @@ def test_create_s3_service():
assert create_and_apply(my_module, DEFAULT_ARGS, module_args)['changed']
+def test_create_s3_service_response():
+ register_responses([
+ ('GET', 'cluster', SRR['is_rest_9_10_1']),
+ ('GET', 'cluster', SRR['is_rest_9_10_1']),
+ ('GET', 'protocols/s3/services', SRR['empty_records']),
+ ('POST', 'protocols/s3/services', SRR['empty_good']),
+ ('GET', 'protocols/s3/services', SRR['s3_service']),
+ ])
+ module_args = {
+ 'enabled': True,
+ 'comment': 'this is a s3 service',
+ 'certificate_name': 'cert1',
+ }
+ assert create_and_apply(my_module, DEFAULT_ARGS, module_args)['s3_service_info'] is not None
+
+
def test_create_s3_service_error():
register_responses([
('GET', 'cluster', SRR['is_rest_9_8_0']),
@@ -152,7 +169,8 @@ def test_modify_s3_service():
('GET', 'cluster', SRR['is_rest_9_10_1']),
('GET', 'cluster', SRR['is_rest_9_10_1']),
('GET', 'protocols/s3/services', SRR['s3_service']),
- ('PATCH', 'protocols/s3/services/08c8a385-b1ac-11ec-bd2e-005056b3b297', SRR['empty_good'])
+ ('PATCH', 'protocols/s3/services/08c8a385-b1ac-11ec-bd2e-005056b3b297', SRR['empty_good']),
+ ('GET', 'protocols/s3/services', SRR['s3_service']),
])
module_args = {'comment': 'this is a modified s3 service',
'enabled': False,
@@ -161,6 +179,21 @@ def test_modify_s3_service():
assert create_and_apply(my_module, DEFAULT_ARGS, module_args)['changed']
+def test_modify_s3_service_response():
+ register_responses([
+ ('GET', 'cluster', SRR['is_rest_9_10_1']),
+ ('GET', 'cluster', SRR['is_rest_9_10_1']),
+ ('GET', 'protocols/s3/services', SRR['s3_service']),
+ ('PATCH', 'protocols/s3/services/08c8a385-b1ac-11ec-bd2e-005056b3b297', SRR['empty_good']),
+ ('GET', 'protocols/s3/services', SRR['s3_service']),
+ ])
+ module_args = {'comment': 'this is a modified s3 service',
+ 'enabled': False,
+ 'certificate_name': 'cert2',
+ }
+ assert create_and_apply(my_module, DEFAULT_ARGS, module_args)['s3_service_info'] is not None
+
+
def test_modify_s3_service_error():
register_responses([
('GET', 'cluster', SRR['is_rest_9_8_0']),
diff --git a/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_security_certificates.py b/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_security_certificates.py
index 866dd3a58..636e826ab 100644
--- a/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_security_certificates.py
+++ b/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_security_certificates.py
@@ -113,7 +113,7 @@ def test_rest_create_failed(mock_request):
def test_rest_successful_create(mock_request):
mock_request.side_effect = [
SRR['is_rest'],
- SRR['get_uuid'], # validate data vserver exist.
+ SRR['get_uuid'], # validate data vserver exists.
SRR['empty_records'], # get certificate -> not found
SRR['empty_good'],
SRR['end_of_sequence']
@@ -132,6 +132,28 @@ def test_rest_successful_create(mock_request):
@patch('ansible_collections.netapp.ontap.plugins.module_utils.netapp.OntapRestAPI.send_request')
+def test_rest_check_module_output(mock_request):
+ mock_request.side_effect = [
+ SRR['is_rest'],
+ SRR['get_uuid'], # validate data vserver exists.
+ SRR['empty_records'], # get certificate -> not found
+ SRR['empty_good'],
+ SRR['end_of_sequence']
+ ]
+ data = {
+ 'type': 'server',
+ 'vserver': 'abc',
+ 'common_name': 'cname'
+ }
+ data.update(set_default_args())
+ set_module_args(data)
+ my_obj = my_module()
+ with pytest.raises(AnsibleExitJson) as exc:
+ my_obj.apply()
+ assert exc.value.args[0]['ontap_info'] is not None
+
+
+@patch('ansible_collections.netapp.ontap.plugins.module_utils.netapp.OntapRestAPI.send_request')
def test_rest_idempotent_create(mock_request):
mock_request.side_effect = [
SRR['is_rest'],
diff --git a/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_snapmirror.py b/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_snapmirror.py
index 9ba179279..0227c4e44 100644
--- a/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_snapmirror.py
+++ b/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_snapmirror.py
@@ -45,7 +45,7 @@ DEFAULT_ARGS = {
}
-def sm_rest_info(state, healthy, transfer_state=None, destination_path=DEFAULT_ARGS['destination_path']):
+def sm_rest_info(state, healthy, transfer_state=None, policy_type=None, destination_path=DEFAULT_ARGS['destination_path']):
record = {
'uuid': 'b5ee4571-5429-11ec-9779-005056b39a06',
'destination': {
@@ -63,6 +63,8 @@ def sm_rest_info(state, healthy, transfer_state=None, destination_path=DEFAULT_A
record['transfer']['uuid'] = 'xfer_uuid'
if healthy is False:
record['unhealthy_reason'] = 'this is why the relationship is not healthy.'
+ if policy_type:
+ record['policy']['type'] = policy_type
record['transfer_schedule'] = {'name': 'abc'}
return {
@@ -110,9 +112,12 @@ SRR = rest_responses({
'sm_get_uninitialized': (200, sm_rest_info('uninitialized', True), None),
'sm_get_uninitialized_xfering': (200, sm_rest_info('uninitialized', True, 'transferring'), None),
'sm_get_mirrored': (200, sm_rest_info('snapmirrored', True, 'success'), None),
+ 'sm_sync_get_mirrored': (200, sm_rest_info('in_sync', True, 'success', 'sync'), None),
'sm_get_restore': (200, sm_rest_info('snapmirrored', True, 'success', destination_path=DEFAULT_ARGS['source_path']), None),
'sm_get_paused': (200, sm_rest_info('paused', True, 'success'), None),
+ 'sm_sync_get_paused': (200, sm_rest_info('paused', True, 'success', 'sync'), None),
'sm_get_broken': (200, sm_rest_info('broken_off', True, 'success'), None),
+ 'sm_sync_get_broken': (200, sm_rest_info('broken_off', True, 'success', 'sync'), None),
'sm_get_data_transferring': (200, sm_rest_info('transferring', True, 'transferring'), None),
'sm_get_abort': (200, sm_rest_info('sm_get_abort', False, 'failed'), None),
'sm_get_resync': (200, {
@@ -1181,12 +1186,28 @@ def test_rest_resync_when_state_is_broken(dont_sleep):
assert call_main(my_main, DEFAULT_ARGS, module_args)['changed']
+@patch('time.sleep')
+def test_rest_synchronous_sm_resync_when_state_is_broken(dont_sleep):
+ ''' resync when snapmirror state is broken and relationship_state active '''
+ register_responses([
+ ('GET', 'cluster', SRR['is_rest_9_8_0']),
+ ('GET', 'snapmirror/relationships', SRR['sm_sync_get_broken']), # apply first sm_get with state broken_off
+ ('PATCH', 'snapmirror/relationships/b5ee4571-5429-11ec-9779-005056b39a06', SRR['success']), # sm resync response
+ ('GET', 'snapmirror/relationships', SRR['sm_sync_get_mirrored']), # check for idle
+ ('GET', 'snapmirror/relationships', SRR['sm_sync_get_mirrored']), # check_health calls sm_get
+ ])
+ module_args = {
+ "use_rest": "always",
+ }
+ assert call_main(my_main, DEFAULT_ARGS, module_args)['changed']
+
+
def test_rest_resume_when_state_quiesced():
''' resync when snapmirror state is broken and relationship_state active '''
register_responses([
('GET', 'cluster', SRR['is_rest_9_8_0']),
('GET', 'snapmirror/relationships', SRR['sm_get_paused']), # apply first sm_get with state quiesced
- ('PATCH', 'snapmirror/relationships/b5ee4571-5429-11ec-9779-005056b39a06', SRR['success']), # sm resync response
+ ('PATCH', 'snapmirror/relationships/b5ee4571-5429-11ec-9779-005056b39a06', SRR['success']), # sm resume response
('GET', 'snapmirror/relationships', SRR['sm_get_mirrored']), # sm update calls sm_get
('POST', 'snapmirror/relationships/b5ee4571-5429-11ec-9779-005056b39a06/transfers', SRR['success']), # sm update response
('GET', 'snapmirror/relationships', SRR['sm_get_mirrored']), # check_health calls sm_get
@@ -1197,6 +1218,22 @@ def test_rest_resume_when_state_quiesced():
assert call_main(my_main, DEFAULT_ARGS, module_args)['changed']
+def test_rest_synchronous_sm_resume_when_state_quiesced():
+ ''' resync when snapmirror state is broken and relationship_state active '''
+ register_responses([
+ ('GET', 'cluster', SRR['is_rest_9_8_0']),
+ ('GET', 'snapmirror/relationships', SRR['sm_sync_get_paused']), # apply first sm_get with state quiesced
+ ('PATCH', 'snapmirror/relationships/b5ee4571-5429-11ec-9779-005056b39a06', SRR['success']), # sm resume response
+ ('GET', 'snapmirror/relationships', SRR['sm_sync_get_mirrored']), # sm update calls sm_get
+ # ('POST', 'snapmirror/relationships/b5ee4571-5429-11ec-9779-005056b39a06/transfers', SRR['success']), # sm update response
+ ('GET', 'snapmirror/relationships', SRR['sm_sync_get_mirrored']), # check_health calls sm_get
+ ])
+ module_args = {
+ "use_rest": "always",
+ }
+ assert call_main(my_main, DEFAULT_ARGS, module_args)['changed']
+
+
@patch('time.sleep')
def test_rest_snapmirror_delete(dont_sleep):
''' snapmirror delete '''
diff --git a/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_snapshot_policy_rest.py b/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_snapshot_policy_rest.py
index b79507759..5533451af 100644
--- a/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_snapshot_policy_rest.py
+++ b/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_snapshot_policy_rest.py
@@ -156,7 +156,7 @@ def test_module_error_ontap_version():
assert 'Error: REST requires ONTAP 9.8 or later for snapshot schedules.' == msg
-def test_create_snapshot_polciy_rest():
+def test_create_snapshot_policy_rest():
''' Test create with rest API'''
register_responses([
('GET', 'cluster', SRR['is_rest_9_9_0']),
@@ -166,7 +166,7 @@ def test_create_snapshot_polciy_rest():
assert create_and_apply(my_module, ARGS_REST)
-def test_create_snapshot_polciy_with_snapmirror_label_rest():
+def test_create_snapshot_policy_with_snapmirror_label_rest():
''' Test create with rest API'''
register_responses([
('GET', 'cluster', SRR['is_rest_9_9_0']),
@@ -179,7 +179,7 @@ def test_create_snapshot_polciy_with_snapmirror_label_rest():
assert create_and_apply(my_module, ARGS_REST, module_args)
-def test_create_snapshot_polciy_with_prefix_rest():
+def test_create_snapshot_policy_with_prefix_rest():
''' Test create with rest API'''
register_responses([
('GET', 'cluster', SRR['is_rest_9_9_0']),
@@ -192,7 +192,7 @@ def test_create_snapshot_polciy_with_prefix_rest():
assert create_and_apply(my_module, ARGS_REST, module_args)
-def test_error_create_snapshot_polciy_rest():
+def test_error_create_snapshot_policy_rest():
''' Test error create with rest API'''
register_responses([
('GET', 'cluster', SRR['is_rest_9_9_0']),
diff --git a/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_snmp.py b/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_snmp.py
index 24d8c5da4..48cbc1107 100644
--- a/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_snmp.py
+++ b/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_snmp.py
@@ -47,6 +47,7 @@ SRR = {
'zero_record': (200, dict(records=[], num_records=0), None),
'one_record_uuid': (200, dict(records=[dict(uuid='a1b2c3')], num_records=1), None),
'end_of_sequence': (500, None, "Unexpected call to send_request"),
+ 'server_error': (500, None, "Internal Server error"),
'generic_error': (400, None, "Expected error"),
'community_user_record': (200, {
'records': [{
@@ -56,11 +57,17 @@ SRR = {
}],
'num_records': 1
}, None),
- 'snmp_user_record': (200, {
+ 'snmp_usm_user_record': (200, {
'records': [{
"name": "snmpv3user3",
"authentication_method": "usm",
- 'engine_id': "80000315058e02057c0fb8e911bc9f005056bb942e"
+ 'engine_id': "80000315058e02057c0fb8e911bc9f005056bb942e",
+ 'snmpv3': {
+ 'privacy_protocol': 'aes128',
+ 'authentication_password': 'humTdumt*@t0nAwa11',
+ 'authentication_protocol': 'sha',
+ 'privacy_password': 'p@**GOandCLCt*200'
+ }
}],
'num_records': 1
}, None),
@@ -73,15 +80,15 @@ def test_module_fail_when_required_args_missing(patch_ansible):
set_module_args(dict(hostname=''))
my_module()
print('Info: %s' % exc.value.args[0]['msg'])
- msg = 'missing required arguments: community_name'
+ msg = 'missing required arguments: snmp_username'
assert msg == exc.value.args[0]['msg']
@patch('ansible_collections.netapp.ontap.plugins.module_utils.netapp.OntapRestAPI.send_request')
-def test_ensure_get_community_called(mock_request, patch_ansible):
+def test_ensure_get_snmp_community_called(mock_request, patch_ansible):
''' test get'''
args = dict(default_args())
- args['community_name'] = 'snmpv3user2'
+ args['snmp_username'] = 'snmpv3user2'
set_module_args(args)
mock_request.side_effect = [
SRR['is_rest_9_8'], # get version
@@ -97,10 +104,29 @@ def test_ensure_get_community_called(mock_request, patch_ansible):
@patch('ansible_collections.netapp.ontap.plugins.module_utils.netapp.OntapRestAPI.send_request')
-def test_ensure_create_community_called(mock_request, patch_ansible):
+def test_ensure_get_snmp_usm_called(mock_request, patch_ansible):
+ ''' test get'''
+ args = dict(default_args())
+ args['snmp_username'] = 'snmpv3user3'
+ set_module_args(args)
+ mock_request.side_effect = [
+ SRR['is_rest_9_8'], # get version
+ SRR['snmp_usm_user_record'], # get
+ SRR['end_of_sequence']
+ ]
+ my_obj = my_module()
+ with pytest.raises(AnsibleExitJson) as exc:
+ my_obj.apply()
+ print('Info: %s' % exc.value.args[0])
+ assert exc.value.args[0]['changed'] is False
+ assert_no_warnings()
+
+
+@patch('ansible_collections.netapp.ontap.plugins.module_utils.netapp.OntapRestAPI.send_request')
+def test_ensure_create_snmp_community_called(mock_request, patch_ansible):
''' test get'''
args = dict(default_args())
- args['community_name'] = 'snmpv3user2'
+ args['snmp_username'] = 'snmpv3user2'
set_module_args(args)
mock_request.side_effect = [
SRR['is_rest_9_8'], # get version
@@ -117,10 +143,48 @@ def test_ensure_create_community_called(mock_request, patch_ansible):
@patch('ansible_collections.netapp.ontap.plugins.module_utils.netapp.OntapRestAPI.send_request')
-def test_ensure_delete_community_called(mock_request, patch_ansible):
+def test_ensure_create_snmp_usm_called(mock_request, patch_ansible):
+ ''' test get'''
+ args = dict(default_args())
+ args['snmp_username'] = 'snmpv3user3'
+ set_module_args(args)
+ mock_request.side_effect = [
+ SRR['is_rest_9_8'], # get version
+ SRR['zero_record'], # get
+ SRR['empty_good'], # create
+ SRR['end_of_sequence']
+ ]
+ my_obj = my_module()
+ with pytest.raises(AnsibleExitJson) as exc:
+ my_obj.apply()
+ print('Info: %s' % exc.value.args[0])
+ assert exc.value.args[0]['changed'] is True
+ assert_no_warnings()
+
+
+def test_fail_to_create_snmp_usm_without_snmpv3_passwords(patch_ansible):
+ ''' required arguments are reported as errors '''
+ usm_record = {
+ 'snmp_username': 'usm19',
+ 'authentication_method': 'usm',
+ 'snmpv3': {
+ 'privacy_protocol': 'aes128',
+ 'authentication_protocol': 'sha'
+ }
+ }
+ with pytest.raises(AnsibleFailJson) as exc:
+ set_module_args(usm_record)
+ my_module()
+ print('Info: %s' % exc.value.args[0]['msg'])
+ msg = 'missing required arguments:'
+ assert msg in exc.value.args[0]['msg']
+
+
+@patch('ansible_collections.netapp.ontap.plugins.module_utils.netapp.OntapRestAPI.send_request')
+def test_ensure_delete_snmp_community_called(mock_request, patch_ansible):
''' test get'''
args = dict(default_args())
- args['community_name'] = 'snmpv3user2'
+ args['snmp_username'] = 'snmpv3user2'
args['state'] = 'absent'
set_module_args(args)
mock_request.side_effect = [
@@ -139,10 +203,52 @@ def test_ensure_delete_community_called(mock_request, patch_ansible):
@patch('ansible_collections.netapp.ontap.plugins.module_utils.netapp.OntapRestAPI.send_request')
-def test_ensure_delete_community_idempotent(mock_request, patch_ansible):
+def test_ensure_delete_snmp_usm_called(mock_request, patch_ansible):
+ ''' test get'''
+ args = dict(default_args())
+ args['snmp_username'] = 'snmpv3user3'
+ args['state'] = 'absent'
+ set_module_args(args)
+ mock_request.side_effect = [
+ SRR['is_rest_9_8'], # get version
+ SRR['snmp_usm_user_record'], # get
+ SRR['snmp_usm_user_record'],
+ SRR['empty_good'], # delete
+ SRR['end_of_sequence']
+ ]
+ my_obj = my_module()
+ with pytest.raises(AnsibleExitJson) as exc:
+ my_obj.apply()
+ print('Info: %s' % exc.value.args[0])
+ assert exc.value.args[0]['changed'] is True
+ assert_no_warnings()
+
+
+@patch('ansible_collections.netapp.ontap.plugins.module_utils.netapp.OntapRestAPI.send_request')
+def test_ensure_delete_snmp_community_idempotent(mock_request, patch_ansible):
+ ''' test get'''
+ args = dict(default_args())
+ args['snmp_username'] = 'snmpv3user2'
+ args['state'] = 'absent'
+ set_module_args(args)
+ mock_request.side_effect = [
+ SRR['is_rest_9_8'], # get version
+ SRR['zero_record'], # get
+ SRR['end_of_sequence']
+ ]
+ my_obj = my_module()
+ with pytest.raises(AnsibleExitJson) as exc:
+ my_obj.apply()
+ print('Info: %s' % exc.value.args[0])
+ assert exc.value.args[0]['changed'] is False
+ assert_no_warnings()
+
+
+@patch('ansible_collections.netapp.ontap.plugins.module_utils.netapp.OntapRestAPI.send_request')
+def test_ensure_delete_snmp_usm_idempotent(mock_request, patch_ansible):
''' test get'''
args = dict(default_args())
- args['community_name'] = 'snmpv3user2'
+ args['snmp_username'] = 'snmpv3user3'
args['state'] = 'absent'
set_module_args(args)
mock_request.side_effect = [
diff --git a/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_snmp_config_rest.py b/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_snmp_config_rest.py
new file mode 100644
index 000000000..bf6236825
--- /dev/null
+++ b/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_snmp_config_rest.py
@@ -0,0 +1,123 @@
+# Copyright: NetApp, Inc
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+""" unit tests for Ansible module: na_ontap_snmp_config """
+
+from __future__ import (absolute_import, division, print_function)
+__metaclass__ = type
+import pytest
+import sys
+
+import ansible_collections.netapp.ontap.plugins.module_utils.netapp as netapp_utils
+# pylint: disable=unused-import
+from ansible_collections.netapp.ontap.tests.unit.plugins.module_utils.ansible_mocks import patch_ansible, \
+ create_and_apply, create_module, call_main, expect_and_capture_ansible_exception
+from ansible_collections.netapp.ontap.tests.unit.framework.mock_rest_and_zapi_requests import get_mock_record, \
+ patch_request_and_invoke, register_responses
+from ansible_collections.netapp.ontap.tests.unit.framework.rest_factory import rest_responses
+
+from ansible_collections.netapp.ontap.plugins.modules.na_ontap_snmp_config \
+ import NetAppOntapSNMPConfig as my_module, main as my_main # module under test
+
+if not netapp_utils.HAS_REQUESTS and sys.version_info < (2, 7):
+ pytestmark = pytest.mark.skip(
+ 'Skipping Unit Tests on 2.6 as requests is not available')
+
+
+DEFAULT_ARGS = {
+ 'hostname': 'hostname',
+ 'username': 'username',
+ 'password': 'password',
+ 'use_rest': 'always',
+ 'state': 'present'
+}
+
+
+# REST API canned responses when mocking send_request.
+# The rest_factory provides default responses shared across testcases.
+SRR = rest_responses({
+ # module specific responses
+ 'snmp_config': (200, {
+ 'auth_traps_enabled': False,
+ 'enabled': True,
+ 'traps_enabled': False,
+ }, None),
+ 'snmp_config_disabled': (200, {
+ 'enabled': False
+ }, None),
+ 'snmp_config_modified': (200, {
+ 'auth_traps_enabled': True,
+ 'traps_enabled': True
+ }, None),
+})
+
+
+def test_successful_disable_snmp():
+ ''' Test successful rest modify SNMP config with idempotency check'''
+ register_responses([
+ ('GET', 'cluster', SRR['is_rest_97']),
+ ('GET', 'support/snmp', SRR['snmp_config']), # get SNMP config
+ ('PATCH', 'support/snmp', SRR['success']), # update SNMP config
+
+ ('GET', 'cluster', SRR['is_rest_97']),
+ ('GET', 'support/snmp', SRR['snmp_config_disabled']), # get SNMP config
+ ])
+ args = {
+ 'enabled': 'false'
+ }
+ assert create_and_apply(my_module, DEFAULT_ARGS, args)['changed']
+ assert not create_and_apply(my_module, DEFAULT_ARGS, args)['changed']
+
+
+def test_successful_modify_snmp_config():
+ ''' Test successful rest modify SNMP config with idempotency check'''
+ register_responses([
+ ('GET', 'cluster', SRR['is_rest_9_10_1']),
+ ('GET', 'support/snmp', SRR['snmp_config']), # get SNMP config
+ ('PATCH', 'support/snmp', SRR['success']), # update SNMP config
+
+ ('GET', 'cluster', SRR['is_rest_9_10_1']),
+ ('GET', 'support/snmp', SRR['snmp_config_modified']), # get SNMP config
+ ])
+ args = {
+ 'auth_traps_enabled': True,
+ 'traps_enabled': True
+ }
+ assert create_and_apply(my_module, DEFAULT_ARGS, args)['changed']
+ assert not create_and_apply(my_module, DEFAULT_ARGS, args)['changed']
+
+
+def test_all_methods_catch_exception():
+ ''' Test exception in get/modify SNMP config '''
+ register_responses([
+ ('GET', 'cluster', SRR['is_rest_9_10_1']),
+ # GET/PATCH error
+ ('GET', 'support/snmp', SRR['generic_error']),
+ ('PATCH', 'support/snmp', SRR['generic_error'])
+ ])
+ modify_args = {
+ 'enabled': 'false'
+ }
+ snmp_config = create_module(my_module, DEFAULT_ARGS)
+ assert 'Error fetching SNMP config' in expect_and_capture_ansible_exception(snmp_config.get_snmp_config_rest, 'fail')['msg']
+ assert 'Error modifying SNMP config' in expect_and_capture_ansible_exception(snmp_config.modify_snmp_config_rest, 'fail', modify_args)['msg']
+
+
+def test_error_ontap97():
+ ''' Test module supported from 9.7 '''
+ register_responses([
+ ('GET', 'cluster', SRR['is_rest'])
+ ])
+ assert 'requires ONTAP 9.7.0 or later' in call_main(my_main, DEFAULT_ARGS, fail=True)['msg']
+
+
+def test_partially_supported_options_rest():
+ ''' Test REST version error for parameters '''
+ register_responses([
+ ('GET', 'cluster', SRR['is_rest_97']),
+ ])
+ args = {
+ 'traps_enabled': 'true'
+ }
+ error = create_module(my_module, DEFAULT_ARGS, args, fail=True)['msg']
+ assert 'Minimum version of ONTAP for traps_enabled is (9, 10, 1)' in error
diff --git a/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_svm.py b/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_svm.py
index d18d32a57..757eca2e9 100644
--- a/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_svm.py
+++ b/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_svm.py
@@ -282,7 +282,7 @@ def test_init_error():
'services': {'ndmp': {'allowed': True}},
}
error = create_module(svm_module, DEFAULT_ARGS, module_args, fail=True)['msg']
- assert error == 'using ndmp requires ONTAP 9.7 or later and REST must be enabled - ONTAP version: 9.6.0 - using REST.'
+ assert error == 'using ndmp requires ONTAP 9.10.1 or later and REST must be enabled - ONTAP version: 9.6.0 - using REST.'
def test_successful_rename():
diff --git a/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_volume.py b/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_volume.py
index 3161ead04..aae0f49a3 100644
--- a/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_volume.py
+++ b/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_volume.py
@@ -1544,6 +1544,7 @@ def test_successful_modify_snapshot_auto_delete(get_volume):
''' Test successful modify unix permissions flexGroup '''
register_responses([
# One ZAPI call for each option!
+ ('ZAPI', 'volume-modify-iter', ZRR['success']),
('ZAPI', 'snapshot-autodelete-set-option', ZRR['success']),
('ZAPI', 'snapshot-autodelete-set-option', ZRR['success']),
('ZAPI', 'snapshot-autodelete-set-option', ZRR['success']),
diff --git a/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_volume_rest.py b/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_volume_rest.py
index 47525beec..20e3ba0f7 100644
--- a/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_volume_rest.py
+++ b/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_volume_rest.py
@@ -78,7 +78,16 @@ volume_info = {
},
"size": 10737418240,
"snapshot": {
- "reserve_percent": 5
+ "reserve_percent": 5,
+ "autodelete": {
+ "enabled": False,
+ "trigger": "volume",
+ "delete_order": "oldest_first",
+ "defer_delete": "user_created",
+ "commitment": "try",
+ "target_free_space": 20,
+ "prefix": "(not specified)"
+ }
}
},
"guarantee": {
@@ -89,7 +98,9 @@ volume_info = {
},
"analytics": {
"state": "on"
- }
+ },
+ "access_time_enabled": True,
+ "snapshot_directory_access_enabled": True
}
volume_info_mount = copy.deepcopy(volume_info)
@@ -114,6 +125,8 @@ SRR = rest_responses({
# common responses
'is_rest': (200, dict(version=dict(generation=9, major=8, minor=0, full='dummy')), None),
'is_rest_96': (200, dict(version=dict(generation=9, major=6, minor=0, full='dummy_9_6_0')), None),
+ 'is_rest_9_8_0': (200, dict(version=dict(generation=9, major=8, minor=0, full='dummy_9_8_0')), None),
+ 'is_rest_9_13_1': (200, dict(version=dict(generation=9, major=13, minor=1, full='dummy_9_13_1')), None),
'is_zapi': (400, {}, "Unreachable"),
'empty_good': (200, {}, None),
'no_record': (200, {'num_records': 0, 'records': []}, None),
@@ -507,6 +520,100 @@ def test_rest_error_modify_attributes():
assert create_and_apply(volume_module, DEFAULT_VOLUME_ARGS, module_args, fail=True)['msg'] == msg
+def test_rest_version_error_with_atime_update():
+ register_responses([
+ ('GET', 'cluster', SRR['is_rest_96'])
+ ])
+ module_args = {
+ 'atime_update': False
+ }
+ error = create_module(volume_module, DEFAULT_VOLUME_ARGS, module_args, fail=True)['msg']
+ print('error', error)
+ assert 'Minimum version of ONTAP for atime_update is (9, 8)' in error
+
+
+def test_rest_version_error_with_snapdir_access():
+ register_responses([
+ ('GET', 'cluster', SRR['is_rest_9_12_1'])
+ ])
+ module_args = {
+ 'snapdir_access': False
+ }
+ error = create_module(volume_module, DEFAULT_VOLUME_ARGS, module_args, fail=True)['msg']
+ print('error', error)
+ assert 'Minimum version of ONTAP for snapdir_access is (9, 13, 1)' in error
+
+
+def test_rest_version_error_with_snapshot_auto_delete():
+ register_responses([
+ ('GET', 'cluster', SRR['is_rest_9_12_1'])
+ ])
+ module_args = {
+ 'snapshot_auto_delete': {'state': 'on'}
+ }
+ error = create_module(volume_module, DEFAULT_VOLUME_ARGS, module_args, fail=True)['msg']
+ print('error', error)
+ assert 'Minimum version of ONTAP for snapshot_auto_delete is (9, 13, 1)' in error
+
+
+def test_rest_version_error_with_vol_nearly_full_threshold_percent():
+ register_responses([
+ ('GET', 'cluster', SRR['is_rest_9_8_0'])
+ ])
+ module_args = {
+ 'vol_nearly_full_threshold_percent': 96
+ }
+ error = create_module(volume_module, DEFAULT_VOLUME_ARGS, module_args, fail=True)['msg']
+ print('error', error)
+ assert 'Minimum version of ONTAP for vol_nearly_full_threshold_percent is (9, 9)' in error
+
+
+def test_rest_successfully_modify_attributes_atime_update():
+ register_responses([
+ ('GET', 'cluster', SRR['is_rest_9_8_0']),
+ ('GET', 'storage/volumes', SRR['get_volume']), # Get Volume
+ ('PATCH', 'storage/volumes/7882901a-1aef-11ec-a267-005056b30cfa', SRR['no_record']), # Modify
+ ])
+ module_args = {
+ 'atime_update': False,
+ }
+ assert create_and_apply(volume_module, DEFAULT_VOLUME_ARGS, module_args)['changed']
+
+
+def test_rest_successfully_modify_attributes_snapdir_access_and_snapshot_auto_delete():
+ register_responses([
+ ('GET', 'cluster', SRR['is_rest_9_13_1']),
+ ('GET', 'storage/volumes', SRR['get_volume']), # Get Volume
+ ('PATCH', 'storage/volumes/7882901a-1aef-11ec-a267-005056b30cfa', SRR['no_record']), # Modify
+ ])
+ module_args = {
+ 'snapdir_access': False,
+ 'snapshot_auto_delete': {
+ 'state': 'on',
+ 'trigger': 'volume',
+ 'delete_order': 'oldest_first',
+ 'defer_delete': 'user_created',
+ 'commitment': 'try',
+ 'target_free_space': 25,
+ 'prefix': 'prefix1'
+ }
+ }
+ assert create_and_apply(volume_module, DEFAULT_VOLUME_ARGS, module_args)['changed']
+
+
+def test_rest_successfully_modify_vol_threshold_percent_params():
+ register_responses([
+ ('GET', 'cluster', SRR['is_rest_9_9_0']),
+ ('GET', 'storage/volumes', SRR['get_volume']), # Get Volume
+ ('PATCH', 'storage/volumes/7882901a-1aef-11ec-a267-005056b30cfa', SRR['no_record']), # Modify
+ ])
+ module_args = {
+ 'vol_nearly_full_threshold_percent': 98,
+ 'vol_full_threshold_percent': 99
+ }
+ assert create_and_apply(volume_module, DEFAULT_VOLUME_ARGS, module_args)['changed']
+
+
def test_rest_successfully_create_volume():
register_responses([
('GET', 'cluster', SRR['is_rest']),
diff --git a/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_vscan_scanner_pool_rest.py b/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_vscan_scanner_pool_rest.py
new file mode 100644
index 000000000..2cdc97a59
--- /dev/null
+++ b/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_vscan_scanner_pool_rest.py
@@ -0,0 +1,256 @@
+# Copyright: NetApp, Inc
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+""" unit tests for Ansible module: na_ontap_vscan_scanner_pool """
+
+from __future__ import (absolute_import, division, print_function)
+__metaclass__ = type
+import pytest
+import sys
+
+import ansible_collections.netapp.ontap.plugins.module_utils.netapp as netapp_utils
+# pylint: disable=unused-import
+from ansible_collections.netapp.ontap.tests.unit.plugins.module_utils.ansible_mocks import patch_ansible, \
+ create_and_apply, create_module, call_main, expect_and_capture_ansible_exception
+from ansible_collections.netapp.ontap.tests.unit.framework.mock_rest_and_zapi_requests import get_mock_record, \
+ patch_request_and_invoke, register_responses
+from ansible_collections.netapp.ontap.tests.unit.framework.rest_factory import rest_responses
+
+from ansible_collections.netapp.ontap.plugins.modules.na_ontap_vscan_scanner_pool \
+ import NetAppOntapVscanScannerPool as my_module, main as my_main # module under test
+
+if not netapp_utils.HAS_REQUESTS and sys.version_info < (2, 7):
+ pytestmark = pytest.mark.skip(
+ 'Skipping Unit Tests on 2.6 as requests is not available')
+
+
+DEFAULT_ARGS = {
+ 'hostname': 'hostname',
+ 'username': 'username',
+ 'password': 'password',
+ 'use_rest': 'always'
+}
+
+
+# REST API canned responses when mocking send_request.
+# The rest_factory provides default responses shared across testcases.
+SRR = rest_responses({
+ # module specific responses
+ 'scanner_pool_info': (200, {"records": [
+ {
+ "name": "Scanner1",
+ "servers": [
+ "10.193.78.219",
+ "10.193.78.221"
+ ],
+ "privileged_users": [
+ "cifs\\user1",
+ "cifs\\user2"
+ ],
+ "role": "primary"
+ }
+ ]}, None),
+ 'svm_uuid': (200, {"records": [
+ {
+ 'uuid': '5ec77839-b9b9-11ee-8084-005056b3d69a'
+ }
+ ], "num_records": 1}, None)
+})
+
+
+svm_uuid = '5ec77839-b9b9-11ee-8084-005056b3d69a'
+scanner_pool_name = 'Scanner1'
+
+
+def test_successful_create():
+ ''' Test successful rest create '''
+ register_responses([
+ ('GET', 'cluster', SRR['is_rest_96']),
+ ('GET', 'svm/svms', SRR['svm_uuid']),
+ ('GET', 'protocols/vscan/%s/scanner-pools' % (svm_uuid), SRR['empty_records']),
+ ('POST', 'protocols/vscan/%s/scanner-pools' % (svm_uuid), SRR['empty_good']),
+ ])
+ args = {
+ 'state': 'present',
+ 'vserver': 'ansibleSVM',
+ 'hostnames': ['10.193.78.219', '10.193.78.221'],
+ 'scanner_policy': 'primary',
+ 'privileged_users': ['cifs\\user1', 'cifs\\user2'],
+ 'scanner_pool': 'Scanner1'
+ }
+ assert create_and_apply(my_module, DEFAULT_ARGS, args)['changed']
+
+
+def test_successful_create_idempotency():
+ ''' Test successful rest create idempotency '''
+ register_responses([
+ ('GET', 'cluster', SRR['is_rest_96']),
+ ('GET', 'svm/svms', SRR['svm_uuid']),
+ ('GET', 'protocols/vscan/%s/scanner-pools' % (svm_uuid), SRR['scanner_pool_info']),
+ ])
+ args = {
+ 'state': 'present',
+ 'vserver': 'ansibleSVM',
+ 'hostnames': ['10.193.78.219', '10.193.78.221'],
+ 'scanner_policy': 'primary',
+ 'privileged_users': ['cifs\\user1', 'cifs\\user2'],
+ 'scanner_pool': 'Scanner1'
+ }
+ assert create_and_apply(my_module, DEFAULT_ARGS, args)['changed'] is False
+
+
+def test_successful_delete():
+ ''' Test successful rest delete '''
+ register_responses([
+ ('GET', 'cluster', SRR['is_rest_96']),
+ ('GET', 'svm/svms', SRR['svm_uuid']),
+ ('GET', 'protocols/vscan/%s/scanner-pools' % (svm_uuid), SRR['scanner_pool_info']),
+ ('DELETE', 'protocols/vscan/%s/scanner-pools/%s' % (svm_uuid, scanner_pool_name), SRR['success']),
+ ])
+ args = {
+ 'state': 'absent',
+ 'vserver': 'ansibleSVM',
+ 'scanner_pool': 'Scanner1'
+ }
+ assert create_and_apply(my_module, DEFAULT_ARGS, args)['changed']
+
+
+def test_successful_delete_idempotency():
+ ''' Test successful rest delete idempotency '''
+ register_responses([
+ ('GET', 'cluster', SRR['is_rest_96']),
+ ('GET', 'svm/svms', SRR['svm_uuid']),
+ ('GET', 'protocols/vscan/%s/scanner-pools' % (svm_uuid), SRR['empty_records']),
+ ])
+ args = {
+ 'state': 'absent',
+ 'vserver': 'ansibleSVM',
+ 'scanner_pool': 'Scanner1'
+ }
+ assert create_and_apply(my_module, DEFAULT_ARGS, args)['changed'] is False
+
+
+def test_successful_modify():
+ ''' Test successful rest modify '''
+ register_responses([
+ ('GET', 'cluster', SRR['is_rest_96']),
+ ('GET', 'svm/svms', SRR['svm_uuid']),
+ ('GET', 'protocols/vscan/%s/scanner-pools' % (svm_uuid), SRR['scanner_pool_info']),
+ ('PATCH', 'protocols/vscan/%s/scanner-pools/%s' % (svm_uuid, scanner_pool_name), SRR['success']),
+ ])
+ args = {
+ 'state': 'present',
+ 'vserver': 'ansibleSVM',
+ 'hostnames': ['10.193.78.219'],
+ 'scanner_policy': 'idle',
+ 'privileged_users': ['cifs\\user1'],
+ 'scanner_pool': 'Scanner1'
+ }
+ assert create_and_apply(my_module, DEFAULT_ARGS, args)['changed']
+
+
+def test_error_get():
+ ''' Test error rest get '''
+ register_responses([
+ ('GET', 'cluster', SRR['is_rest_96']),
+ ('GET', 'svm/svms', SRR['svm_uuid']),
+ ('GET', 'protocols/vscan/%s/scanner-pools' % (svm_uuid), SRR['generic_error']),
+ ]),
+ args = {
+ 'state': 'present',
+ 'vserver': 'ansibleSVM',
+ 'hostnames': ['10.193.78.219', '10.193.78.221'],
+ 'scanner_policy': 'primary',
+ 'privileged_users': ['cifs\\user1', 'cifs\\user2'],
+ 'scanner_pool': 'Scanner1'
+ }
+ error = create_and_apply(my_module, DEFAULT_ARGS, args, fail=True)['msg']
+ assert 'Error searching for Vscan Scanner Pool' in error
+
+
+def test_error_create():
+ ''' Test error rest create '''
+ register_responses([
+ ('GET', 'cluster', SRR['is_rest_96']),
+ ('GET', 'svm/svms', SRR['svm_uuid']),
+ ('GET', 'protocols/vscan/%s/scanner-pools' % (svm_uuid), SRR['empty_records']),
+ ('POST', 'protocols/vscan/%s/scanner-pools' % (svm_uuid), SRR['generic_error']),
+ ]),
+ args = {
+ 'state': 'present',
+ 'vserver': 'ansibleSVM',
+ 'hostnames': ['10.193.78.219', '10.193.78.221'],
+ 'scanner_policy': 'primary',
+ 'privileged_users': ['cifs\\user1', 'cifs\\user2'],
+ 'scanner_pool': 'Scanner1'
+ }
+ error = create_and_apply(my_module, DEFAULT_ARGS, args, fail=True)['msg']
+ assert 'Error creating Vscan Scanner Pool' in error
+
+
+def test_error_modify():
+ ''' Test error rest modify '''
+ register_responses([
+ ('GET', 'cluster', SRR['is_rest_96']),
+ ('GET', 'svm/svms', SRR['svm_uuid']),
+ ('GET', 'protocols/vscan/%s/scanner-pools' % (svm_uuid), SRR['scanner_pool_info']),
+ ('PATCH', 'protocols/vscan/%s/scanner-pools/%s' % (svm_uuid, scanner_pool_name), SRR['generic_error']),
+ ])
+ args = {
+ 'state': 'present',
+ 'vserver': 'ansibleSVM',
+ 'hostnames': ['10.193.78.219'],
+ 'scanner_policy': 'idle',
+ 'privileged_users': ['cifs\\user1'],
+ 'scanner_pool': 'Scanner1'
+ }
+ error = create_and_apply(my_module, DEFAULT_ARGS, args, fail=True)['msg']
+ assert 'Error modifying Vscan Scanner Pool' in error
+
+
+def test_error_delete():
+ ''' Test error rest delete '''
+ register_responses([
+ ('GET', 'cluster', SRR['is_rest_96']),
+ ('GET', 'svm/svms', SRR['svm_uuid']),
+ ('GET', 'protocols/vscan/%s/scanner-pools' % (svm_uuid), SRR['scanner_pool_info']),
+ ('DELETE', 'protocols/vscan/%s/scanner-pools/%s' % (svm_uuid, scanner_pool_name), SRR['generic_error']),
+ ])
+ args = {
+ 'state': 'absent',
+ 'vserver': 'ansibleSVM',
+ 'scanner_pool': 'Scanner1'
+ }
+ error = create_and_apply(my_module, DEFAULT_ARGS, args, fail=True)['msg']
+ assert 'Error deleting Vscan Scanner Pool' in error
+
+
+def test_error_ontap96():
+ ''' Test error module supported from 9.6 '''
+ register_responses([
+ ('GET', 'cluster', SRR['is_rest'])
+ ])
+ args = {
+ 'state': 'present',
+ 'vserver': 'ansibleSVM',
+ 'hostnames': ['10.193.78.219', '10.193.78.221'],
+ 'scanner_policy': 'primary',
+ 'privileged_users': ['cifs\\user1', 'cifs\\user2'],
+ 'scanner_pool': 'Scanner1'
+ }
+ msg = 'REST requires ONTAP 9.6 or later for /protocols/vscan/{{svm.uuid}}/scanner-pools APIs'
+ assert msg in call_main(my_main, DEFAULT_ARGS, args, fail=True)['msg']
+
+
+def test_missing_options_scanner_pool():
+ ''' Test error missing option scanner_pool '''
+ register_responses([])
+ args = {
+ 'state': 'present',
+ 'vserver': 'ansibleSVM',
+ 'hostnames': ['10.193.78.219', '10.193.78.221'],
+ 'scanner_policy': 'primary',
+ 'privileged_users': ['cifs\\user1', 'cifs\\user2'],
+ }
+ error = create_module(my_module, DEFAULT_ARGS, args, fail=True)['msg']
+ assert 'missing required arguments: scanner_pool' in error