summaryrefslogtreecommitdiffstats
path: root/ansible_collections/netapp/ontap
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
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 'ansible_collections/netapp/ontap')
-rw-r--r--ansible_collections/netapp/ontap/.github/ISSUE_TEMPLATE/bug_report.yml4
-rw-r--r--ansible_collections/netapp/ontap/.github/workflows/main.yml2
-rw-r--r--ansible_collections/netapp/ontap/CHANGELOG.rst105
-rw-r--r--ansible_collections/netapp/ontap/FILES.json487
-rw-r--r--ansible_collections/netapp/ontap/MANIFEST.json4
-rw-r--r--ansible_collections/netapp/ontap/README.md86
-rw-r--r--ansible_collections/netapp/ontap/changelogs/changelog.yaml157
-rw-r--r--ansible_collections/netapp/ontap/changelogs/fragments/DEVOPS-5509.yaml2
-rw-r--r--ansible_collections/netapp/ontap/changelogs/fragments/DEVOPS-5828.yaml2
-rw-r--r--ansible_collections/netapp/ontap/changelogs/fragments/DEVOPS-5920.yaml2
-rw-r--r--ansible_collections/netapp/ontap/changelogs/fragments/DEVOPS-6282.yaml2
-rw-r--r--ansible_collections/netapp/ontap/changelogs/fragments/DEVOPS-6286.yaml2
-rw-r--r--ansible_collections/netapp/ontap/changelogs/fragments/DEVOPS-6291.yaml2
-rw-r--r--ansible_collections/netapp/ontap/changelogs/fragments/DEVOPS-6309.yaml2
-rw-r--r--ansible_collections/netapp/ontap/changelogs/fragments/DEVOPS-6320.yaml2
-rw-r--r--ansible_collections/netapp/ontap/changelogs/fragments/DEVOPS-6330.yaml2
-rw-r--r--ansible_collections/netapp/ontap/changelogs/fragments/DEVOPS-6331.yaml2
-rw-r--r--ansible_collections/netapp/ontap/changelogs/fragments/DEVOPS-6341.yaml2
-rw-r--r--ansible_collections/netapp/ontap/changelogs/fragments/DEVOPS-6374.yaml2
-rw-r--r--ansible_collections/netapp/ontap/changelogs/fragments/DEVOPS-6386.yaml2
-rw-r--r--ansible_collections/netapp/ontap/changelogs/fragments/DEVOPS-6389.yaml2
-rw-r--r--ansible_collections/netapp/ontap/changelogs/fragments/DEVOPS-6395.yaml2
-rw-r--r--ansible_collections/netapp/ontap/changelogs/fragments/DEVOPS-6413.yaml5
-rw-r--r--ansible_collections/netapp/ontap/changelogs/fragments/DEVOPS-6438.yaml2
-rw-r--r--ansible_collections/netapp/ontap/changelogs/fragments/DEVOPS-6463.yaml2
-rw-r--r--ansible_collections/netapp/ontap/changelogs/fragments/DEVOPS-6481.yaml2
-rw-r--r--ansible_collections/netapp/ontap/changelogs/fragments/DEVOPS-6486.yaml2
-rw-r--r--ansible_collections/netapp/ontap/changelogs/fragments/DEVOPS-6487.yaml2
-rw-r--r--ansible_collections/netapp/ontap/changelogs/fragments/DEVOPS-6488.yaml2
-rw-r--r--ansible_collections/netapp/ontap/changelogs/fragments/DEVOPS-6495.yaml2
-rw-r--r--ansible_collections/netapp/ontap/changelogs/fragments/DEVOPS-6519.yaml2
-rw-r--r--ansible_collections/netapp/ontap/changelogs/fragments/DEVOPS-6520.yaml2
-rw-r--r--ansible_collections/netapp/ontap/changelogs/fragments/DEVOPS-6524.yaml2
-rw-r--r--ansible_collections/netapp/ontap/changelogs/fragments/DEVOPS-6525.yaml5
-rw-r--r--ansible_collections/netapp/ontap/changelogs/fragments/DEVOPS-6527.yaml2
-rw-r--r--ansible_collections/netapp/ontap/changelogs/fragments/DEVOPS-6528.yaml2
-rw-r--r--ansible_collections/netapp/ontap/changelogs/fragments/DEVOPS-6529.yaml2
-rw-r--r--ansible_collections/netapp/ontap/changelogs/fragments/DEVOPS-6551.yaml2
-rw-r--r--ansible_collections/netapp/ontap/changelogs/fragments/DEVOPS-6556.yaml2
-rw-r--r--ansible_collections/netapp/ontap/changelogs/fragments/DEVOPS-6584.yaml2
-rw-r--r--ansible_collections/netapp/ontap/changelogs/fragments/DEVOPS-6646.yaml6
-rw-r--r--ansible_collections/netapp/ontap/changelogs/fragments/DEVOPS-6658.yaml2
-rw-r--r--ansible_collections/netapp/ontap/changelogs/fragments/DEVOPS-6664.yaml2
-rw-r--r--ansible_collections/netapp/ontap/changelogs/fragments/DEVOPS-6667.yaml2
-rw-r--r--ansible_collections/netapp/ontap/changelogs/fragments/DEVOPS-6671.yaml2
-rw-r--r--ansible_collections/netapp/ontap/changelogs/fragments/DEVOPS-6680.yaml2
-rw-r--r--ansible_collections/netapp/ontap/changelogs/fragments/DEVOPS-6681.yaml2
-rw-r--r--ansible_collections/netapp/ontap/changelogs/fragments/GITHUB-174.yaml2
-rw-r--r--ansible_collections/netapp/ontap/meta/runtime.yml6
-rw-r--r--ansible_collections/netapp/ontap/plugins/module_utils/netapp.py2
-rw-r--r--ansible_collections/netapp/ontap/plugins/modules/na_ontap_broadcast_domain.py1
-rw-r--r--ansible_collections/netapp/ontap/plugins/modules/na_ontap_cg_snapshot.py256
-rw-r--r--ansible_collections/netapp/ontap/plugins/modules/na_ontap_cifs_server.py85
-rw-r--r--ansible_collections/netapp/ontap/plugins/modules/na_ontap_cifs_unix_symlink_mapping.py289
-rw-r--r--ansible_collections/netapp/ontap/plugins/modules/na_ontap_cli_timeout.py123
-rw-r--r--ansible_collections/netapp/ontap/plugins/modules/na_ontap_cluster.py76
-rw-r--r--ansible_collections/netapp/ontap/plugins/modules/na_ontap_cluster_peer.py70
-rw-r--r--ansible_collections/netapp/ontap/plugins/modules/na_ontap_dns.py10
-rw-r--r--ansible_collections/netapp/ontap/plugins/modules/na_ontap_ems_config.py186
-rw-r--r--ansible_collections/netapp/ontap/plugins/modules/na_ontap_ems_destination.py191
-rw-r--r--ansible_collections/netapp/ontap/plugins/modules/na_ontap_ems_filter.py121
-rw-r--r--ansible_collections/netapp/ontap/plugins/modules/na_ontap_export_policy_rule.py6
-rw-r--r--ansible_collections/netapp/ontap/plugins/modules/na_ontap_file_security_permissions_acl.py5
-rw-r--r--ansible_collections/netapp/ontap/plugins/modules/na_ontap_igroup_initiator.py9
-rw-r--r--ansible_collections/netapp/ontap/plugins/modules/na_ontap_info.py2
-rw-r--r--ansible_collections/netapp/ontap/plugins/modules/na_ontap_kerberos_realm.py65
-rw-r--r--ansible_collections/netapp/ontap/plugins/modules/na_ontap_login_messages.py5
-rw-r--r--ansible_collections/netapp/ontap/plugins/modules/na_ontap_lun.py21
-rw-r--r--ansible_collections/netapp/ontap/plugins/modules/na_ontap_lun_map.py16
-rw-r--r--ansible_collections/netapp/ontap/plugins/modules/na_ontap_lun_map_reporting_nodes.py22
-rw-r--r--ansible_collections/netapp/ontap/plugins/modules/na_ontap_name_mappings.py6
-rw-r--r--ansible_collections/netapp/ontap/plugins/modules/na_ontap_net_ifgrp.py13
-rw-r--r--ansible_collections/netapp/ontap/plugins/modules/na_ontap_nfs.py5
-rw-r--r--ansible_collections/netapp/ontap/plugins/modules/na_ontap_node.py15
-rw-r--r--ansible_collections/netapp/ontap/plugins/modules/na_ontap_qos_policy_group.py46
-rw-r--r--ansible_collections/netapp/ontap/plugins/modules/na_ontap_rest_info.py17
-rw-r--r--ansible_collections/netapp/ontap/plugins/modules/na_ontap_restit.py6
-rw-r--r--ansible_collections/netapp/ontap/plugins/modules/na_ontap_s3_services.py54
-rw-r--r--ansible_collections/netapp/ontap/plugins/modules/na_ontap_security_certificates.py10
-rw-r--r--ansible_collections/netapp/ontap/plugins/modules/na_ontap_service_policy.py7
-rw-r--r--ansible_collections/netapp/ontap/plugins/modules/na_ontap_snapmirror.py16
-rw-r--r--ansible_collections/netapp/ontap/plugins/modules/na_ontap_snapshot_policy.py11
-rw-r--r--ansible_collections/netapp/ontap/plugins/modules/na_ontap_snmp.py171
-rw-r--r--ansible_collections/netapp/ontap/plugins/modules/na_ontap_snmp_config.py142
-rw-r--r--ansible_collections/netapp/ontap/plugins/modules/na_ontap_storage_auto_giveback.py11
-rw-r--r--ansible_collections/netapp/ontap/plugins/modules/na_ontap_svm.py8
-rw-r--r--ansible_collections/netapp/ontap/plugins/modules/na_ontap_user.py3
-rw-r--r--ansible_collections/netapp/ontap/plugins/modules/na_ontap_volume.py180
-rw-r--r--ansible_collections/netapp/ontap/plugins/modules/na_ontap_vscan_scanner_pool.py209
-rw-r--r--ansible_collections/netapp/ontap/plugins/modules/na_ontap_vserver_peer.py19
-rw-r--r--ansible_collections/netapp/ontap/roles/na_ontap_vserver_create/README.md2
-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
118 files changed, 5115 insertions, 520 deletions
diff --git a/ansible_collections/netapp/ontap/.github/ISSUE_TEMPLATE/bug_report.yml b/ansible_collections/netapp/ontap/.github/ISSUE_TEMPLATE/bug_report.yml
index e67eb0f94..0a216967d 100644
--- a/ansible_collections/netapp/ontap/.github/ISSUE_TEMPLATE/bug_report.yml
+++ b/ansible_collections/netapp/ontap/.github/ISSUE_TEMPLATE/bug_report.yml
@@ -20,12 +20,12 @@ body:
**Tip:** If you are seeking community support, please consider
- [Join our Slack community][ML||IRC].
+ [Join our Discord community][ML||IRC].
[ML||IRC]:
- https://join.slack.com/t/netapppub/shared_invite/zt-njcjx2sh-1VR2mEDvPcJAmPutOnP~mg
+ https://discord.gg/NetApp
[issue search]: ../search?q=is%3Aissue&type=issues
diff --git a/ansible_collections/netapp/ontap/.github/workflows/main.yml b/ansible_collections/netapp/ontap/.github/workflows/main.yml
index 94353d961..b2b7e389d 100644
--- a/ansible_collections/netapp/ontap/.github/workflows/main.yml
+++ b/ansible_collections/netapp/ontap/.github/workflows/main.yml
@@ -20,6 +20,8 @@ jobs:
- stable-2.12
- stable-2.13
- stable-2.14
+ - stable-2.15
+ - stable-2.16
- devel
steps:
diff --git a/ansible_collections/netapp/ontap/CHANGELOG.rst b/ansible_collections/netapp/ontap/CHANGELOG.rst
index 0a5b2167a..9a796e0ab 100644
--- a/ansible_collections/netapp/ontap/CHANGELOG.rst
+++ b/ansible_collections/netapp/ontap/CHANGELOG.rst
@@ -5,6 +5,111 @@ NetApp ONTAP Collection Release Notes
.. contents:: Topics
+v22.10.0
+========
+
+Minor Changes
+-------------
+
+- na_ontap_cifs_server - new option `is_multichannel_enabled` added in REST, requires ONTAP 9.10 or later.
+- na_ontap_export_policy_rule - added `actions` and `modify` in module output.
+- na_ontap_file_security_permissions_acl - added `actions` and `modify` in module output.
+- na_ontap_igroup_initiator - added `actions` in module output.
+- na_ontap_lun_map - added `actions` in module output.
+- na_ontap_lun_map_reporting_nodes - added `actions` in module output.
+- na_ontap_name_mappings - added `actions` and `modify` in module output.
+- na_ontap_node - added `modify` in module output.
+- na_ontap_rest_info - added warning message if given subset doesn't support option `owning_resource`.
+- na_ontap_storage_auto_giveback - added information on modified attributes in module output.
+- na_ontap_vscan_scanner_pool - added REST support to Vscan Scanner Pools Configuration module, requires ONTAP 9.6 or later.
+
+Bugfixes
+--------
+
+- na_ontap_igroup_initiator - fixed issue with idempotency.
+
+v22.9.0
+=======
+
+Minor Changes
+-------------
+
+- na_ontap_cifs_server - new option `lm_compatibility_level` added in REST, requires ONTAP 9.8 or later.
+- na_ontap_cluster - new option `certificate.uuid` added in REST, requires ONTAP 9.10 or later.
+- na_ontap_cluster_peer - added REST only support for modifying remote intercluster addresses in cluster peer relation.
+- na_ontap_ems_destination - new options `syslog`, `port`, `transport`, `message_format`, `timestamp_format_override` and `hostname_format_override` added in REST, requires ONTAP 9.12.1 or later.
+- na_ontap_s3_services - create, modify S3 service returns `s3_service_info` in module output.
+- na_ontap_snapmirror - updated resync and resume operation for synchronous snapmirror relationship in REST.
+
+Bugfixes
+--------
+
+- na_ontap_nfs - fix error with `windows` in REST for ONTAP 9.10 or earlier.
+- na_ontap_security_certificates - fix error with ontap_info returned in module output in REST.
+- na_ontap_snapshot_policy - fix issue with modifying snapshot policy in REST.
+- na_ontap_volume - modified `type` to be case insensitive in REST.
+
+New Modules
+-----------
+
+- netapp.ontap.na_ontap_cifs_unix_symlink_mapping - NetApp ONTAP module to manage UNIX symbolic link mapping for CIFS clients.
+- netapp.ontap.na_ontap_cli_timeout - NetApp ONTAP module to set the CLI inactivity timeout value.
+- netapp.ontap.na_ontap_snmp_config - NetApp ONTAP module to modify SNMP configuration.
+
+v22.8.3
+=======
+
+Bugfixes
+--------
+
+- na_ontap_ems_destination - fix field error with `certificate.name` for ONTAP 9.11.1 or later in REST.
+- na_ontap_vserver_peer - fix issue with peering multiple clusters with same vserver name in REST.
+
+v22.8.1
+=======
+
+Bugfixes
+--------
+
+- na_ontap_dns - fix keyerror for uuid when DNS is set to vserver in REST.
+- na_ontap_volume - fix invalid field error with 'space.snapshot.autodelete' in REST.
+
+v22.8.0
+=======
+
+Minor Changes
+-------------
+
+- na_ontap_broadcast_domain - changed documentation for ipspace as it is required while using REST.
+- na_ontap_cg_snapshot - added REST support to the cg snapshot module, requires ONTAP 9.10.1 or later.
+- na_ontap_cifs_server - new option `default_site` added in REST, requires ONTAP 9.13.1 or later.
+- na_ontap_ems_destination - new option ``certificate``, ``ca`` added.
+- na_ontap_kerberos_realm - add REST support for `admin_server_ip`, `admin_server_port`, `pw_server_ip`, `pw_server_port` and `clock_skew` from ONTAP 9.13.1 or later
+- na_ontap_lun - new option `qtree_name` added in REST.
+- na_ontap_net_ifgrp - return `name` and other details of a newly created interface group in module output in REST.
+- na_ontap_qos_policy_group - added new REST only options `expected_iops_allocation` and `peak_iops_allocation`, requires ONTAP 9.10.1 or later.
+- na_ontap_rest_info - new option `hal_linking` added to enable or disable HAL links.
+- na_ontap_restit - returns changed as False for GET method.
+- na_ontap_snmp - added REST support for snmpv3 user.
+- na_ontap_user - Added warning message when password is not changed.
+- na_ontap_volume - added REST support for `atime_update` requires ONTAP 9.8 or later, `snapdir_access` and `snapshot_auto_delete` requires ONTAP 9.13.1 or later.
+- na_ontap_volume - added new REST only options `vol_nearly_full_threshold_percent` and `vol_full_threshold_percent`, requires ONTAP 9.9 or later.
+
+Bugfixes
+--------
+
+- na_ontap_dns - fix DNS not working with Cluster mode.
+- na_ontap_ems_filter - fix delete operation not idempotent for filter.
+- na_ontap_ems_filter - fix modify operation to add rule in existing filter.
+- na_ontap_login_messages - fix idempotency issue in Cluster scope in REST.
+- na_ontap_nfs - fix `default_user` under `windows` not getting modified if not set previously in REST.
+- na_ontap_svm - fix REST version warning for `ndmp` under `services`.
+
+New Modules
+-----------
+
+- netapp.ontap.na_ontap_ems_config - NetApp ONTAP module to modify EMS configuration.
+
v22.7.0
=======
diff --git a/ansible_collections/netapp/ontap/FILES.json b/ansible_collections/netapp/ontap/FILES.json
index 6d2b6162a..dde171372 100644
--- a/ansible_collections/netapp/ontap/FILES.json
+++ b/ansible_collections/netapp/ontap/FILES.json
@@ -109,7 +109,7 @@
"name": "plugins/module_utils/netapp.py",
"ftype": "file",
"chksum_type": "sha256",
- "chksum_sha256": "ec34efdb000ccbfa336b4f9f09229dae445132884bca6ab83903e20273e620d9",
+ "chksum_sha256": "6daa1820439f22b17459fe302f0d4bab7d9b55abe9a16954ba75a90a242bd01e",
"format": 1
},
{
@@ -228,7 +228,7 @@
"name": "plugins/modules/na_ontap_lun_map_reporting_nodes.py",
"ftype": "file",
"chksum_type": "sha256",
- "chksum_sha256": "c242cdda8ed0d6909df6850414f5b669d0c0f6bfa5f734c0d67d0190918db78a",
+ "chksum_sha256": "1eda5ac3890de22d0b01ad52cca1e4ebebed292d1522484320e5988aebaee6ac",
"format": 1
},
{
@@ -319,7 +319,7 @@
"name": "plugins/modules/na_ontap_dns.py",
"ftype": "file",
"chksum_type": "sha256",
- "chksum_sha256": "4fe57a0b4047c049393d27cda4e811ef882a14f6d31a1d0281fab65f68e98f23",
+ "chksum_sha256": "6ea5685b153844b586517029a15901465b587a5e8c460647e8b4672135c5fe14",
"format": 1
},
{
@@ -340,7 +340,7 @@
"name": "plugins/modules/na_ontap_volume.py",
"ftype": "file",
"chksum_type": "sha256",
- "chksum_sha256": "3ee1b2a761551734cc35fefca8ab1ae77da4030a43de2681a2665c30ed4866e2",
+ "chksum_sha256": "3c7a5ae9b747fbc66bc02ef0725b23880c3fdb121770c5f46f1964f699bb62f7",
"format": 1
},
{
@@ -382,7 +382,7 @@
"name": "plugins/modules/na_ontap_restit.py",
"ftype": "file",
"chksum_type": "sha256",
- "chksum_sha256": "e9c29e8a5dac84b2dda82f8456541055f0c2507c7d3ba160bb9f656603e2012f",
+ "chksum_sha256": "3b962cd935d284e56fbaee7b33e299078363e0ba6091e698b8941844e92fb2c7",
"format": 1
},
{
@@ -473,14 +473,14 @@
"name": "plugins/modules/na_ontap_lun_map.py",
"ftype": "file",
"chksum_type": "sha256",
- "chksum_sha256": "715e1b085b5efca021341bd7acfe60dbe6f1b1b6aa085cb1fda43a273b7137c3",
+ "chksum_sha256": "7db77cccba69ec51ce6a3ae0fea4f9b6f176b59183f0d1a51ab5c26f1b0e19ca",
"format": 1
},
{
"name": "plugins/modules/na_ontap_user.py",
"ftype": "file",
"chksum_type": "sha256",
- "chksum_sha256": "734c38050413783ac404c333cffa25012eb236e80e58c2fe12377f3833df8ab8",
+ "chksum_sha256": "327a7de29eddb2604a792a299757d6e72078db5410b1731fd570e0cdf59ebcbf",
"format": 1
},
{
@@ -494,7 +494,7 @@
"name": "plugins/modules/na_ontap_security_certificates.py",
"ftype": "file",
"chksum_type": "sha256",
- "chksum_sha256": "86b25e1b63997f6b23ed639afc080e09dd8990314b4a7aa55878d5aab77cfe4c",
+ "chksum_sha256": "57fb9f829e8c75ad7ac50be2274372806c5d43410f354d0d5da3bc0c45a8dbe0",
"format": 1
},
{
@@ -540,6 +540,13 @@
"format": 1
},
{
+ "name": "plugins/modules/na_ontap_cifs_unix_symlink_mapping.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "ab6f70acbe8e7d14b1259d6dd26e11956a57518dc70282e91878e47f465ed7a4",
+ "format": 1
+ },
+ {
"name": "plugins/modules/na_ontap_s3_users.py",
"ftype": "file",
"chksum_type": "sha256",
@@ -564,7 +571,7 @@
"name": "plugins/modules/na_ontap_snapmirror.py",
"ftype": "file",
"chksum_type": "sha256",
- "chksum_sha256": "9514d382448a13c71a3ef577a9947937261f53c4f3d26249fad0e6c169244c9e",
+ "chksum_sha256": "ec388f54cdb2cbcf61799dce69ee71faaed10b9092208dda884863a06f48afdc",
"format": 1
},
{
@@ -599,7 +606,14 @@
"name": "plugins/modules/na_ontap_snapshot_policy.py",
"ftype": "file",
"chksum_type": "sha256",
- "chksum_sha256": "d950f392c6da9173d4a222357e2f36417739c9881a7592a70ef3b9d69c28429c",
+ "chksum_sha256": "135b0efb8be0c082d51d6d955fbf39ca27e49be3faf80513cf59517ee7fa9ac4",
+ "format": 1
+ },
+ {
+ "name": "plugins/modules/na_ontap_ems_config.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "678459ebfcbc2c441fd3bd2cada52550ffb65ba97ce6eb3f738ee65f8a393179",
"format": 1
},
{
@@ -648,7 +662,7 @@
"name": "plugins/modules/na_ontap_lun.py",
"ftype": "file",
"chksum_type": "sha256",
- "chksum_sha256": "5d44070a28af9662c623e174e9699de994fda2ce6b8626bcd6d3e61e6867a705",
+ "chksum_sha256": "17260d405983a1ff389ce146d8f30082a5ea67cf5f7fb23bf13cd7280fbfe5df",
"format": 1
},
{
@@ -711,7 +725,7 @@
"name": "plugins/modules/na_ontap_cifs_server.py",
"ftype": "file",
"chksum_type": "sha256",
- "chksum_sha256": "4287377fb2c9f65556b9dbf64ff6ec467f81fc5d2cc82ebb4c2c16ade1dc1de2",
+ "chksum_sha256": "5ea181714e685520b6682a5aa46520bb475b62ab1edc5cef967743c51685da67",
"format": 1
},
{
@@ -725,7 +739,7 @@
"name": "plugins/modules/na_ontap_info.py",
"ftype": "file",
"chksum_type": "sha256",
- "chksum_sha256": "4a1b8844f0d0f5438c159b44b7d11c6d8e3f2394aa0f6e033b7757b7b9a061b2",
+ "chksum_sha256": "49fc9dbbbc53dae9daa8af45b3e212357e0caee59cb82f04100404f0d745668b",
"format": 1
},
{
@@ -753,7 +767,7 @@
"name": "plugins/modules/na_ontap_service_policy.py",
"ftype": "file",
"chksum_type": "sha256",
- "chksum_sha256": "8ac05dab425cafd7de913f81014eb10d856b5b99612bb063cdd7ca4e5a0d063d",
+ "chksum_sha256": "809f043bcbf561f0adcb74920313a3953dff31ac2eab74548abbae25681c741c",
"format": 1
},
{
@@ -774,14 +788,14 @@
"name": "plugins/modules/na_ontap_login_messages.py",
"ftype": "file",
"chksum_type": "sha256",
- "chksum_sha256": "bb1da6df48e95a15f43e615f7d36a230f7540adc96aead7dc0d132682956fba1",
+ "chksum_sha256": "2e5a603d0202b5e29710c6a52de5f449135aaa43ebb7d7cb275ca2c0a066c123",
"format": 1
},
{
"name": "plugins/modules/na_ontap_file_security_permissions_acl.py",
"ftype": "file",
"chksum_type": "sha256",
- "chksum_sha256": "ee2a8e5945e09845ad851ba4448083d9d6751f0f4ae98ff04372b15052c36ce7",
+ "chksum_sha256": "1aab00d75da338d0c475f412a76bc3e34f960a1f54a89665a4ff67ec882d30e1",
"format": 1
},
{
@@ -802,7 +816,7 @@
"name": "plugins/modules/na_ontap_s3_services.py",
"ftype": "file",
"chksum_type": "sha256",
- "chksum_sha256": "eb9b2ecd506650c6277c7db50399776c44de92a479085ded69e4d4be45fbb06b",
+ "chksum_sha256": "44a50f6d21125d571a8f115c9a4c15009c9a24aac7035da149ddc81443b6976d",
"format": 1
},
{
@@ -823,21 +837,21 @@
"name": "plugins/modules/na_ontap_storage_auto_giveback.py",
"ftype": "file",
"chksum_type": "sha256",
- "chksum_sha256": "24dffb7ebb9eacb949cd7dfbc9cb9ec35da18faa3176a239f1214da8282b66ee",
+ "chksum_sha256": "1ed26bbf1bfb4454ced206908e5256ad431093e0ea71919d5afb284d2937f3fb",
"format": 1
},
{
"name": "plugins/modules/na_ontap_name_mappings.py",
"ftype": "file",
"chksum_type": "sha256",
- "chksum_sha256": "83bf5f628ba9cdd88ba87d7af2344d97a290272b44a0acd1a658e46c86a6ce19",
+ "chksum_sha256": "e243d773c2cbc10d52e286e35771629416678be9daf28cd0fdc870870d5b620b",
"format": 1
},
{
"name": "plugins/modules/na_ontap_qos_policy_group.py",
"ftype": "file",
"chksum_type": "sha256",
- "chksum_sha256": "0b68a80bfac9ff1437326a1ac17bf296988ba6cf356d178970414623f608a9a1",
+ "chksum_sha256": "df651e1f1304ed4d27c17c9ac1332f5820a221419ba0239c3d7cb120fabef8f8",
"format": 1
},
{
@@ -851,7 +865,7 @@
"name": "plugins/modules/na_ontap_node.py",
"ftype": "file",
"chksum_type": "sha256",
- "chksum_sha256": "67b359d9c72f3768342f5ec483cf2d201e0b7c4cb02815d5f5e2e886d1411adc",
+ "chksum_sha256": "44e0b1c41de40c78af4f7b2b16bac92cb7e5214555524423750e10a69d4a37d6",
"format": 1
},
{
@@ -879,7 +893,7 @@
"name": "plugins/modules/na_ontap_cluster_peer.py",
"ftype": "file",
"chksum_type": "sha256",
- "chksum_sha256": "439ea7093e16deca7b055bb8b4577ee49fe1579c56560261a9a618a853756d37",
+ "chksum_sha256": "baffb6efea12760dbd074bd3604889b2c79dbf5255bc64628b5f3061c1cdbff1",
"format": 1
},
{
@@ -893,14 +907,14 @@
"name": "plugins/modules/na_ontap_nfs.py",
"ftype": "file",
"chksum_type": "sha256",
- "chksum_sha256": "c3f57bd199e8bb2bf6fc88de81540f4def926757d083abfafcefc3737885c674",
+ "chksum_sha256": "054ccb00eb7802c43a4e3c903f6d322b3fcac3cfbd0537c41a4edbdf783f8e57",
"format": 1
},
{
"name": "plugins/modules/na_ontap_igroup_initiator.py",
"ftype": "file",
"chksum_type": "sha256",
- "chksum_sha256": "5e0219793ff679105e7cdd12fb92ed998d7d0286fa4616881c35b48c20f93ca3",
+ "chksum_sha256": "b6e659c9d95211c61b000b1e0865cbeb478d2ffc2c365299a2716f63d0656fd7",
"format": 1
},
{
@@ -914,7 +928,7 @@
"name": "plugins/modules/na_ontap_broadcast_domain.py",
"ftype": "file",
"chksum_type": "sha256",
- "chksum_sha256": "6249740f48db9713cce0f3e639284a9cd58f589d13188161218fa36710589850",
+ "chksum_sha256": "068b01b68cb7b3de8c1c9c6a17082fed04d70ae6a4d3299854c08af1589631dc",
"format": 1
},
{
@@ -991,14 +1005,14 @@
"name": "plugins/modules/na_ontap_svm.py",
"ftype": "file",
"chksum_type": "sha256",
- "chksum_sha256": "5807c3522331a413d68c8c6a1852065ea500b4890a48e65708765431440edea1",
+ "chksum_sha256": "ffe2ffea83f07e00d9e5564a5699bb30700557f46a601fb364ff296b8af41db1",
"format": 1
},
{
"name": "plugins/modules/na_ontap_cg_snapshot.py",
"ftype": "file",
"chksum_type": "sha256",
- "chksum_sha256": "50138c1b5feda5cac7f79cfba886bb22837ee33c851f5e9975d3951df3c8927d",
+ "chksum_sha256": "a3d1673902619fa6191db5336c65fc32ad3fdc4154c694b2f9fe34357382a882",
"format": 1
},
{
@@ -1026,7 +1040,7 @@
"name": "plugins/modules/na_ontap_vscan_scanner_pool.py",
"ftype": "file",
"chksum_type": "sha256",
- "chksum_sha256": "3852ac303caf42cea6b6ff90f851fa78b51d400690edf3acc833ade94c63f365",
+ "chksum_sha256": "7d923fe9f87d5f90a0aa2155b16622974003b41e03457ed2718661aed84d66a9",
"format": 1
},
{
@@ -1037,10 +1051,17 @@
"format": 1
},
{
+ "name": "plugins/modules/na_ontap_snmp_config.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "34a3fc900f046f84fcfd9d4b669e454709a6b37cfeb07bcb98a025812a510e25",
+ "format": 1
+ },
+ {
"name": "plugins/modules/na_ontap_cluster.py",
"ftype": "file",
"chksum_type": "sha256",
- "chksum_sha256": "45e9d6197ea7263e1a211b50cf63cffe0826e098d4b262c37b8d47eb2814f05f",
+ "chksum_sha256": "011dd065b2341c49bd7d07ab424462ea1fbbe309b3b408e7e0a20b07eac79d06",
"format": 1
},
{
@@ -1082,7 +1103,7 @@
"name": "plugins/modules/na_ontap_rest_info.py",
"ftype": "file",
"chksum_type": "sha256",
- "chksum_sha256": "2856157fe4f1c49354d226279e8b48e2307e85ab7c4aa2fb06d74406f4e6ff17",
+ "chksum_sha256": "830827828d6f15dc305f41df23f1fa48caf4cf9628fe9e60786b8fc82db69b29",
"format": 1
},
{
@@ -1110,7 +1131,7 @@
"name": "plugins/modules/na_ontap_export_policy_rule.py",
"ftype": "file",
"chksum_type": "sha256",
- "chksum_sha256": "6268758ca9e6bf19ecc2ec77874a5de0c52deeeb47a278b4cf59a10aa999333f",
+ "chksum_sha256": "b81f861d458e94935077bc7c8afd0ffdef57e4ff3869067ebd1434fe5011b00d",
"format": 1
},
{
@@ -1131,7 +1152,7 @@
"name": "plugins/modules/na_ontap_net_ifgrp.py",
"ftype": "file",
"chksum_type": "sha256",
- "chksum_sha256": "700b1c21b2fceb7004f7e21525df82b9bf75e5a0b0b09b743b880ba7ae9c88ac",
+ "chksum_sha256": "b1e80c8c376745d83c3b86ba490ea5138855b67924421ef1872108683c5daac1",
"format": 1
},
{
@@ -1142,6 +1163,13 @@
"format": 1
},
{
+ "name": "plugins/modules/na_ontap_cli_timeout.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "85e78370f6dbfe3f964300540582692b371721f00d061d027dca3f7c96bbe90f",
+ "format": 1
+ },
+ {
"name": "plugins/modules/na_ontap_software_update.py",
"ftype": "file",
"chksum_type": "sha256",
@@ -1152,7 +1180,7 @@
"name": "plugins/modules/na_ontap_kerberos_realm.py",
"ftype": "file",
"chksum_type": "sha256",
- "chksum_sha256": "80e39927bb3588e6d457eb6fe30d7693ebd174c5984bc04f289a317ce727bd19",
+ "chksum_sha256": "a71dac9c8eeabd098fdacdc6a72be8099085c2ce6bae671c623b7eb6274ccec8",
"format": 1
},
{
@@ -1173,7 +1201,7 @@
"name": "plugins/modules/na_ontap_ems_filter.py",
"ftype": "file",
"chksum_type": "sha256",
- "chksum_sha256": "ab8386552e4c7a9b42cc436fb452086767d10405fbb4b1110e12b88f1d6a71d1",
+ "chksum_sha256": "1c792058420a84d67a0cc136de4a5a07fbd9ec8157eeb62d61a947b20522c70b",
"format": 1
},
{
@@ -1187,7 +1215,7 @@
"name": "plugins/modules/na_ontap_vserver_peer.py",
"ftype": "file",
"chksum_type": "sha256",
- "chksum_sha256": "47ec3d718b88fbe09abfc49a2d7c4896a11d8b1d2158ce1e55d636221f70f7d7",
+ "chksum_sha256": "2fc0315ff8bfd66b89822d4f75f779f1688df45eb5964bea58f263b48ace327a",
"format": 1
},
{
@@ -1208,7 +1236,7 @@
"name": "plugins/modules/na_ontap_snmp.py",
"ftype": "file",
"chksum_type": "sha256",
- "chksum_sha256": "bbb6b7ded15cacef91b478955e289e1dd3baa6a9723e7605872eaaf61d0fc56d",
+ "chksum_sha256": "7c09ad3d3be2a1de5e7dcc2668379dba75ab41d3295e7b1c34523de2749eb1aa",
"format": 1
},
{
@@ -1229,7 +1257,7 @@
"name": "plugins/modules/na_ontap_ems_destination.py",
"ftype": "file",
"chksum_type": "sha256",
- "chksum_sha256": "f1388814e0a0917e90b444c4e80339b90be71b0114af3c501312741cd7155b54",
+ "chksum_sha256": "5c02f2471aa67ec959883f5b57f6a83b5165eda45e17eedd25cde48860df6009",
"format": 1
},
{
@@ -1534,10 +1562,17 @@
"format": 1
},
{
+ "name": "tests/unit/plugins/modules/test_na_ontap_vscan_scanner_pool_rest.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "359f4dc18fcee6815f9d5bfcac47377d540e3206e1187e40537b1a4d95ac9f58",
+ "format": 1
+ },
+ {
"name": "tests/unit/plugins/modules/test_na_ontap_rest_info.py",
"ftype": "file",
"chksum_type": "sha256",
- "chksum_sha256": "72fa0742c19761dd2be646cdbff2b537eb8659eb4be46a13082cd422c649288d",
+ "chksum_sha256": "23dcb44c70edf87aa7ba6ef5369be372a055dd28ded864fabb766e2175501350",
"format": 1
},
{
@@ -1558,7 +1593,7 @@
"name": "tests/unit/plugins/modules/test_na_ontap_ems_destination.py",
"ftype": "file",
"chksum_type": "sha256",
- "chksum_sha256": "a70b09add9149c9ad2c74f344c0f5bb7c8e2806c2989bb722e74395637e6e20c",
+ "chksum_sha256": "17c949a72cc7a5ccb0a10962caf81ca1b70c9b6492c9e799856b8fadc493c995",
"format": 1
},
{
@@ -1572,14 +1607,14 @@
"name": "tests/unit/plugins/modules/test_na_ontap_snapshot_policy_rest.py",
"ftype": "file",
"chksum_type": "sha256",
- "chksum_sha256": "28d0650df1636d66970ee053297cdb23a3bfc6477870c7a439c21f1c28e30572",
+ "chksum_sha256": "fe8ec853b68a9f132691799cd55be43f53635096868114ec86048f5ba713fa16",
"format": 1
},
{
"name": "tests/unit/plugins/modules/test_na_ontap_net_ifgrp.py",
"ftype": "file",
"chksum_type": "sha256",
- "chksum_sha256": "fdf9b1ee67ec49c2df619de50d4e2fe1ca0068792020e1abd6a3cc5101f1f4aa",
+ "chksum_sha256": "67fc692e2c26ab9213b3773525e0d37e899417231bb0325ce5c8dfa7e2ec6abe",
"format": 1
},
{
@@ -1618,6 +1653,13 @@
"format": 1
},
{
+ "name": "tests/unit/plugins/modules/test_na_ontap_cg_snapshot_rest.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "d47ce7404e7ec493b6cae1240fc514af3d1da071dbf03e1b7b1e0cd6ac6a6cd9",
+ "format": 1
+ },
+ {
"name": "tests/unit/plugins/modules/test_na_ontap_fpolicy_ext_engine.py",
"ftype": "file",
"chksum_type": "sha256",
@@ -1663,14 +1705,14 @@
"name": "tests/unit/plugins/modules/test_na_ontap_cluster.py",
"ftype": "file",
"chksum_type": "sha256",
- "chksum_sha256": "c882a166465a3b80cf6edf4b333646a12a7fd78e5045ea59702d1c4458ecd6fa",
+ "chksum_sha256": "cc4f5c9ab06a5ab402e9c988f5ef28c21e2dcad7a1feae07bb098353086b39c7",
"format": 1
},
{
"name": "tests/unit/plugins/modules/test_na_ontap_svm.py",
"ftype": "file",
"chksum_type": "sha256",
- "chksum_sha256": "68d2fd805bd1adfbf387c926f4a7023644130be7cbd9b27a89cfa4b59456521c",
+ "chksum_sha256": "25e28f82eb025fd5b454fd61743a59e3964c09eab5edb0ccebfa31014f4b1da2",
"format": 1
},
{
@@ -1761,7 +1803,7 @@
"name": "tests/unit/plugins/modules/test_na_ontap_qos_policy_group.py",
"ftype": "file",
"chksum_type": "sha256",
- "chksum_sha256": "637d6977a9ad1258222b8201dc194e1ee712f81899fa7ed77376a2c3d524f759",
+ "chksum_sha256": "2f42e2bffa4d33b13f0aeb6b0024dcbd9a4b1ae4b1e16741d91c9808601bbcfd",
"format": 1
},
{
@@ -1789,7 +1831,7 @@
"name": "tests/unit/plugins/modules/test_na_ontap_cifs_server.py",
"ftype": "file",
"chksum_type": "sha256",
- "chksum_sha256": "20b53adc4d0ae871cd6c7431fb766bb7464d3210b79ec318dc44c9dcc3abe42e",
+ "chksum_sha256": "45279434fa84da11484d1f411788c68197321f105ae274916c92b448a4a27755",
"format": 1
},
{
@@ -1803,7 +1845,7 @@
"name": "tests/unit/plugins/modules/test_na_ontap_s3_services.py",
"ftype": "file",
"chksum_type": "sha256",
- "chksum_sha256": "eac0a91c90b250a0e5d4093d22e8837767199c4de718dc70854d94302ba85284",
+ "chksum_sha256": "e6511d83a915acf2aca33475322538f0916a188781d323f2031eecf63ad6bbaa",
"format": 1
},
{
@@ -1936,14 +1978,14 @@
"name": "tests/unit/plugins/modules/test_na_ontap_snapmirror.py",
"ftype": "file",
"chksum_type": "sha256",
- "chksum_sha256": "0f9a591c29a0560042a34c108dadd408dad7e053020f3fb79b08570fa0b28304",
+ "chksum_sha256": "f684c553b8e1fd7d00d6c1b11064077a8905f687293658b7af2b73bd013a9ff6",
"format": 1
},
{
"name": "tests/unit/plugins/modules/test_na_ontap_cluster_peer.py",
"ftype": "file",
"chksum_type": "sha256",
- "chksum_sha256": "23f42d9f923f12a2ce87fe0982d85ec2e6c663c174f6964bd89bc821db014e39",
+ "chksum_sha256": "20e0f925e07256a69fa8582075887048e84d8134eb27b5fade6fdb40893fb333",
"format": 1
},
{
@@ -2055,7 +2097,7 @@
"name": "tests/unit/plugins/modules/test_na_ontap_lun_rest.py",
"ftype": "file",
"chksum_type": "sha256",
- "chksum_sha256": "ce572f17103f660e31dfda9bc5ece970aa6cf4a4f349e080ea5c8552d1dded9b",
+ "chksum_sha256": "7624b91ca479b285e4004fd6fd466e47e465419651e3bfc56b4fce046a267ca6",
"format": 1
},
{
@@ -2195,14 +2237,14 @@
"name": "tests/unit/plugins/modules/test_na_ontap_restit.py",
"ftype": "file",
"chksum_type": "sha256",
- "chksum_sha256": "338d1436f4e001717dee8e2a7276c6f8d061c4ce5a56aa2bdac670da41642e8a",
+ "chksum_sha256": "7e6dc7ac01bfeb65d57d57ea0e3e0ecaea1af52b6930b28a6574c6f3fa9bfbaf",
"format": 1
},
{
"name": "tests/unit/plugins/modules/test_na_ontap_volume.py",
"ftype": "file",
"chksum_type": "sha256",
- "chksum_sha256": "23c30dbaacad0f9ea68d368fd88b0459ca4b7ea458fec5c4592c945aa97ebfbb",
+ "chksum_sha256": "8bb5fd6d6597cae989691f5c3de7683728ba99bc9409247e65afe248d1d26ae9",
"format": 1
},
{
@@ -2220,6 +2262,13 @@
"format": 1
},
{
+ "name": "tests/unit/plugins/modules/test_na_ontap_cli_timeout_rest.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "8c771796f7882ca87a496067a6ec08fe423b739f875112148535482ee7b80d5a",
+ "format": 1
+ },
+ {
"name": "tests/unit/plugins/modules/test_na_ontap_unix_user.py",
"ftype": "file",
"chksum_type": "sha256",
@@ -2251,7 +2300,7 @@
"name": "tests/unit/plugins/modules/test_na_ontap_kerberos_realm.py",
"ftype": "file",
"chksum_type": "sha256",
- "chksum_sha256": "45a10a5f869386b7a112d2d10da5bcfa9679537714fad526e170fbe883befb06",
+ "chksum_sha256": "194266b02394b5e2fb9dc8af45cdde6fedc4b31fa9f46229ce368a869f02f55a",
"format": 1
},
{
@@ -2314,7 +2363,7 @@
"name": "tests/unit/plugins/modules/test_na_ontap_security_certificates.py",
"ftype": "file",
"chksum_type": "sha256",
- "chksum_sha256": "cc1820aeab587ca1f867c83ec5cbbffdbe04e949434b42cb70736e21008c9638",
+ "chksum_sha256": "e773a4de1c948991b07d74ffb7d4a4db6d2eaa4cbfca2e5c5fb53699507dde14",
"format": 1
},
{
@@ -2405,7 +2454,7 @@
"name": "tests/unit/plugins/modules/test_na_ontap_info.py",
"ftype": "file",
"chksum_type": "sha256",
- "chksum_sha256": "260c1c6a7eab1af937ba0364f015209ce0a510782e8534f94a2e01c8dcdeac2e",
+ "chksum_sha256": "f28b5b1d03e66d1128b0c00bd29f19fba1ce2a9e5760417536d532c2ca50bf76",
"format": 1
},
{
@@ -2440,7 +2489,7 @@
"name": "tests/unit/plugins/modules/test_na_ontap_ems_filter.py",
"ftype": "file",
"chksum_type": "sha256",
- "chksum_sha256": "a6bc41fcd86c0af36511a2a59f3565fe8d3bffb6c6acd0308a2096d1bfcbc8ff",
+ "chksum_sha256": "d104d1c4b8e0a072d06d45f32b2acddbcb1c0f0fe108dce504e85fe4b04dbaf2",
"format": 1
},
{
@@ -2468,7 +2517,7 @@
"name": "tests/unit/plugins/modules/test_na_ontap_volume_rest.py",
"ftype": "file",
"chksum_type": "sha256",
- "chksum_sha256": "6703a6c2f9c4a9f23a33f04a03692d8ade5f6401c6eff2a71f22106a2379485a",
+ "chksum_sha256": "12fc54ccef4180bf9755e61c0396f0436aa251c321cb4742f0e7729c0165d795",
"format": 1
},
{
@@ -2489,7 +2538,7 @@
"name": "tests/unit/plugins/modules/test_na_ontap_snmp.py",
"ftype": "file",
"chksum_type": "sha256",
- "chksum_sha256": "2745af5002bd12d7f4fe61c270684cce7345e251aca858ac4307ed867febccce",
+ "chksum_sha256": "6cf653c7019017da57bade6b514480d4643fe83c76cf47f9a939262c74b742ba",
"format": 1
},
{
@@ -2605,6 +2654,13 @@
"format": 1
},
{
+ "name": "tests/unit/plugins/modules/test_na_ontap_ems_config_rest.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "8fa4366c96d911631e396a2c67d3615bb9bbae4fe6cbe803036886bdddffb04c",
+ "format": 1
+ },
+ {
"name": "tests/unit/plugins/modules/test_na_ontap_efficiency_policy.py",
"ftype": "file",
"chksum_type": "sha256",
@@ -2612,6 +2668,13 @@
"format": 1
},
{
+ "name": "tests/unit/plugins/modules/test_na_ontap_cifs_unix_symlink_mapping_rest.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "b14380254416e50cb587ac3bf0a78cd7c0e02c3f6066c2d358917212a65b78aa",
+ "format": 1
+ },
+ {
"name": "tests/unit/plugins/modules/test_na_ontap_nvme_subsystem_rest.py",
"ftype": "file",
"chksum_type": "sha256",
@@ -2619,6 +2682,13 @@
"format": 1
},
{
+ "name": "tests/unit/plugins/modules/test_na_ontap_snmp_config_rest.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "4f63c18b52b5f76ea166bd535f60b059dcba23281635f364d44498f5b29d1205",
+ "format": 1
+ },
+ {
"name": "tests/unit/plugins/modules/test_na_ontap_security_ipsec_config.py",
"ftype": "file",
"chksum_type": "sha256",
@@ -2685,7 +2755,7 @@
"name": "tests/unit/plugins/modules/test_na_ontap_dns.py",
"ftype": "file",
"chksum_type": "sha256",
- "chksum_sha256": "8d9a65c36d2f55ca4cf0f36774515f5dda2c1c1e697a9d3e5ce640710da29fb7",
+ "chksum_sha256": "bb8275c1ae29ba059dd5897cb47b174da8b59f8a5acf64b241061f219bc46bbe",
"format": 1
},
{
@@ -2811,7 +2881,7 @@
"name": "meta/runtime.yml",
"ftype": "file",
"chksum_type": "sha256",
- "chksum_sha256": "1038889fbba0e97c200632faf2549ec6194fc13ffb08af2402cbf52e3b6e6419",
+ "chksum_sha256": "f8d72f0da49c6fab626aa540d1571b47ee1846ac66613f549476a280d7b1336f",
"format": 1
},
{
@@ -3511,7 +3581,7 @@
"name": "roles/na_ontap_vserver_create/README.md",
"ftype": "file",
"chksum_type": "sha256",
- "chksum_sha256": "1df8af3622b0b165fbf5b40544e44bd9cd5cbed5ac3703cfdf75bd82780f4696",
+ "chksum_sha256": "8ca3472b8eb89ba0488c9db5483a424b73d18644cd9da6d866be9989d3243d40",
"format": 1
},
{
@@ -3844,6 +3914,13 @@
"format": 1
},
{
+ "name": "changelogs/fragments/DEVOPS-6486.yaml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "8501087711c8d0a717eae19f38765219b99347282b61ac3e2edb624dccf04455",
+ "format": 1
+ },
+ {
"name": "changelogs/fragments/DEVOPS-6193.yaml",
"ftype": "file",
"chksum_type": "sha256",
@@ -3970,6 +4047,13 @@
"format": 1
},
{
+ "name": "changelogs/fragments/DEVOPS-6664.yaml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "8cbcbe2a91705f20c30c285ba2095a54bbafe084a71c1af755fa8dde80524a6f",
+ "format": 1
+ },
+ {
"name": "changelogs/fragments/DEVOPS-3870.yaml",
"ftype": "file",
"chksum_type": "sha256",
@@ -4019,6 +4103,13 @@
"format": 1
},
{
+ "name": "changelogs/fragments/DEVOPS-6330.yaml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "201ca10262d5eab95c3fc97f99262e9a57e583abd5f7937d4542d04b7eed99a3",
+ "format": 1
+ },
+ {
"name": "changelogs/fragments/DEVOPS-5137.yaml",
"ftype": "file",
"chksum_type": "sha256",
@@ -4040,6 +4131,13 @@
"format": 1
},
{
+ "name": "changelogs/fragments/DEVOPS-6520.yaml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "335a825f4ffff4e206ff19a22cf93cff29b62b8c7aa72857ac04821ba908cc0d",
+ "format": 1
+ },
+ {
"name": "changelogs/fragments/DEVOPS-3667.yaml",
"ftype": "file",
"chksum_type": "sha256",
@@ -4257,6 +4355,13 @@
"format": 1
},
{
+ "name": "changelogs/fragments/DEVOPS-6331.yaml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "5e348511a61968bcb2c4058bf1222cc5c73a502a7cf82014469901aa3996cf87",
+ "format": 1
+ },
+ {
"name": "changelogs/fragments/DEVOPS-4863.yaml",
"ftype": "file",
"chksum_type": "sha256",
@@ -4264,6 +4369,13 @@
"format": 1
},
{
+ "name": "changelogs/fragments/DEVOPS-6389.yaml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "ec9c6bf3a6512a79921920419b89e0069040d3c21ccba337aa793c09d5a2bdd8",
+ "format": 1
+ },
+ {
"name": "changelogs/fragments/DEVOPS-5819.yaml",
"ftype": "file",
"chksum_type": "sha256",
@@ -4390,6 +4502,13 @@
"format": 1
},
{
+ "name": "changelogs/fragments/DEVOPS-6487.yaml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "40544537955d5d121a0ff78e06622def58f8ab13b9aee5704eda13390ebcc44c",
+ "format": 1
+ },
+ {
"name": "changelogs/fragments/DEVOPS-6192.yaml",
"ftype": "file",
"chksum_type": "sha256",
@@ -4488,6 +4607,13 @@
"format": 1
},
{
+ "name": "changelogs/fragments/DEVOPS-6413.yaml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "b3b104e725a081a83482888394ef8434e868fe83a42380b007bc848755e1d879",
+ "format": 1
+ },
+ {
"name": "changelogs/fragments/DEVOPS-3241.yaml",
"ftype": "file",
"chksum_type": "sha256",
@@ -4495,6 +4621,13 @@
"format": 1
},
{
+ "name": "changelogs/fragments/DEVOPS-6556.yaml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "6cc7a08f69b9c2911c0c82a0e78c6ff76065eada33086b772525d87143139eca",
+ "format": 1
+ },
+ {
"name": "changelogs/fragments/DEVOPS-3754.yaml",
"ftype": "file",
"chksum_type": "sha256",
@@ -4621,6 +4754,13 @@
"format": 1
},
{
+ "name": "changelogs/fragments/DEVOPS-6320.yaml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "adab447f548a1ce10c9ff2fd87843922b59defe7b050e27e3de9c783391fe319",
+ "format": 1
+ },
+ {
"name": "changelogs/fragments/DEVOPS-5367.yaml",
"ftype": "file",
"chksum_type": "sha256",
@@ -4705,6 +4845,20 @@
"format": 1
},
{
+ "name": "changelogs/fragments/DEVOPS-6658.yaml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "94c46027fed496fcc075c2cfad38544afbd1c9267505820e38ca805653eb7830",
+ "format": 1
+ },
+ {
+ "name": "changelogs/fragments/DEVOPS-6463.yaml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "aff00f447b5affc307da06fa4dcdabb88809c22991a37619f7f81d0903ee3fc8",
+ "format": 1
+ },
+ {
"name": "changelogs/fragments/DEVOPS-5536.yaml",
"ftype": "file",
"chksum_type": "sha256",
@@ -4712,6 +4866,13 @@
"format": 1
},
{
+ "name": "changelogs/fragments/DEVOPS-5920.yaml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "de68834e39c654fb7465a20dd205422d18f181a9e180add39e92bd0ae10e5616",
+ "format": 1
+ },
+ {
"name": "changelogs/fragments/20.5.0.yaml",
"ftype": "file",
"chksum_type": "sha256",
@@ -4796,6 +4957,13 @@
"format": 1
},
{
+ "name": "changelogs/fragments/DEVOPS-6286.yaml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "3f2ed4d85e7ad1b07891d340937dde28cd349352035618936e9dc80bc5d95e74",
+ "format": 1
+ },
+ {
"name": "changelogs/fragments/DEVOPS-6005.yaml",
"ftype": "file",
"chksum_type": "sha256",
@@ -4831,6 +4999,13 @@
"format": 1
},
{
+ "name": "changelogs/fragments/DEVOPS-6681.yaml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "a2e99c55ceb7e3a9cd2fb477fc558cbe562bcea7d43b1dffd070209fb8cd7b14",
+ "format": 1
+ },
+ {
"name": "changelogs/fragments/DEVOPS-3483.yaml",
"ftype": "file",
"chksum_type": "sha256",
@@ -4880,6 +5055,13 @@
"format": 1
},
{
+ "name": "changelogs/fragments/DEVOPS-6551.yaml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "24db9206f4b74acf6aaa5f7974bae86f09f5c593732e79292cc99a0c4d3dbd45",
+ "format": 1
+ },
+ {
"name": "changelogs/fragments/DEVOPS-4612.yaml",
"ftype": "file",
"chksum_type": "sha256",
@@ -4908,6 +5090,13 @@
"format": 1
},
{
+ "name": "changelogs/fragments/DEVOPS-6341.yaml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "9f81356bf55d57304b88dbed04f042aa6ca98a478d45912688fb415321f5b306",
+ "format": 1
+ },
+ {
"name": "changelogs/fragments/DEVOPS-4393.yaml",
"ftype": "file",
"chksum_type": "sha256",
@@ -4936,6 +5125,13 @@
"format": 1
},
{
+ "name": "changelogs/fragments/DEVOPS-6438.yaml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "9d4d2898f9c8b4d302a839a09d2eb4b22daa79f3d0830d84e821c94314aaaba6",
+ "format": 1
+ },
+ {
"name": "changelogs/fragments/DEVOPS-3952.yaml",
"ftype": "file",
"chksum_type": "sha256",
@@ -4978,6 +5174,13 @@
"format": 1
},
{
+ "name": "changelogs/fragments/GITHUB-174.yaml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "da11debbbd55d35ad98c2d91791c39cb59b4de0608bfe0c86fdeaed4bf77a38e",
+ "format": 1
+ },
+ {
"name": "changelogs/fragments/DEVOPS-4501.yaml",
"ftype": "file",
"chksum_type": "sha256",
@@ -4999,6 +5202,13 @@
"format": 1
},
{
+ "name": "changelogs/fragments/DEVOPS-5828.yaml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "42f9f947f7fbc8da5d70d7e6e8be912cead1fa4a62a6700a02cb70d752a6e4e0",
+ "format": 1
+ },
+ {
"name": "changelogs/fragments/DEVOPS-5594.yaml",
"ftype": "file",
"chksum_type": "sha256",
@@ -5006,6 +5216,13 @@
"format": 1
},
{
+ "name": "changelogs/fragments/DEVOPS-6584.yaml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "d744ee25e6289ef5a971c20a4c4565a61d4cc1c088ee383132d2032169e75e31",
+ "format": 1
+ },
+ {
"name": "changelogs/fragments/DEVOPS-4804.yaml",
"ftype": "file",
"chksum_type": "sha256",
@@ -5062,6 +5279,13 @@
"format": 1
},
{
+ "name": "changelogs/fragments/DEVOPS-6481.yaml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "8c892112bc0b8d175b908bc766a6941bdf53a9c7b010bcd810e003eb9ec97a80",
+ "format": 1
+ },
+ {
"name": "changelogs/fragments/DEVOPS-3400.yaml",
"ftype": "file",
"chksum_type": "sha256",
@@ -5097,6 +5321,13 @@
"format": 1
},
{
+ "name": "changelogs/fragments/DEVOPS-6291.yaml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "49832d5b326a49b2aa2d9fe56a98fee74611bf016841a34649950fd1a59687c4",
+ "format": 1
+ },
+ {
"name": "changelogs/fragments/DEVOPS-5844.yaml",
"ftype": "file",
"chksum_type": "sha256",
@@ -5153,6 +5384,13 @@
"format": 1
},
{
+ "name": "changelogs/fragments/DEVOPS-6395.yaml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "9d8701abe85f3af6c8cd41c29ce66a757649aca0a64b15f97f1aec85b48e81e5",
+ "format": 1
+ },
+ {
"name": "changelogs/fragments/DEVOPS-5413.yaml",
"ftype": "file",
"chksum_type": "sha256",
@@ -5160,6 +5398,13 @@
"format": 1
},
{
+ "name": "changelogs/fragments/DEVOPS-6680.yaml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "7cad34c82bfe8ac487c7e4211e0c2bb2acabb3eea4f8a14f0899a99423f39ff9",
+ "format": 1
+ },
+ {
"name": "changelogs/fragments/DEVOPS-3178.yaml",
"ftype": "file",
"chksum_type": "sha256",
@@ -5307,6 +5552,13 @@
"format": 1
},
{
+ "name": "changelogs/fragments/DEVOPS-6527.yaml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "f7ac99e8373f04d01ad44515766e01bb2ed13ce522690e8a3fc75866b121f86e",
+ "format": 1
+ },
+ {
"name": "changelogs/fragments/DEVOPS-3230.yaml",
"ftype": "file",
"chksum_type": "sha256",
@@ -5468,6 +5720,13 @@
"format": 1
},
{
+ "name": "changelogs/fragments/DEVOPS-6524.yaml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "f6653f1f9107e7a4721e6330daa02e3b79378fea7e03e644358bae328c4e0617",
+ "format": 1
+ },
+ {
"name": "changelogs/fragments/DEVOPS-5426.yaml",
"ftype": "file",
"chksum_type": "sha256",
@@ -5608,6 +5867,13 @@
"format": 1
},
{
+ "name": "changelogs/fragments/DEVOPS-6528.yaml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "d7c1b5e6f6cb4cc915546a255b948647fccbdd78cec968e51fc6a35d48ef2ace",
+ "format": 1
+ },
+ {
"name": "changelogs/fragments/DEVOPS-5304.yaml",
"ftype": "file",
"chksum_type": "sha256",
@@ -5853,6 +6119,13 @@
"format": 1
},
{
+ "name": "changelogs/fragments/DEVOPS-6529.yaml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "22a6c32bc547a62313297446b9b11efa3ae6e52ea9be36b4812af57265eb8853",
+ "format": 1
+ },
+ {
"name": "changelogs/fragments/DEVOPS-5190.yaml",
"ftype": "file",
"chksum_type": "sha256",
@@ -5881,6 +6154,13 @@
"format": 1
},
{
+ "name": "changelogs/fragments/DEVOPS-6495.yaml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "73571800ca68c39cac5dda9bb4e8158fd08e878d6bded4f764264430e6604c61",
+ "format": 1
+ },
+ {
"name": "changelogs/fragments/DEVOPS-3443.yaml",
"ftype": "file",
"chksum_type": "sha256",
@@ -5965,6 +6245,13 @@
"format": 1
},
{
+ "name": "changelogs/fragments/DEVOPS-6374.yaml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "9f844402377378d6aefbbe2156ad70fc80b5ef0c7c7757b07779094315447e6c",
+ "format": 1
+ },
+ {
"name": "changelogs/fragments/DEVOPS-4349.yaml",
"ftype": "file",
"chksum_type": "sha256",
@@ -6021,6 +6308,13 @@
"format": 1
},
{
+ "name": "changelogs/fragments/DEVOPS-6525.yaml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "a9de53d77edc2ab90ec35de395d790b32df1b7017e9421ba976b8e50336da63f",
+ "format": 1
+ },
+ {
"name": "changelogs/fragments/DEVOPS-4774.yaml",
"ftype": "file",
"chksum_type": "sha256",
@@ -6091,6 +6385,13 @@
"format": 1
},
{
+ "name": "changelogs/fragments/DEVOPS-6646.yaml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "21f63e47a2639f35a8e18feb70723d56da0877bd61b307517396ab08191ff00e",
+ "format": 1
+ },
+ {
"name": "changelogs/fragments/DEVOPS-5986.yaml",
"ftype": "file",
"chksum_type": "sha256",
@@ -6231,6 +6532,13 @@
"format": 1
},
{
+ "name": "changelogs/fragments/DEVOPS-6386.yaml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "9b8c6632ed25f15a3801442efba11713262c649d33b6168f79e21977033d7fdf",
+ "format": 1
+ },
+ {
"name": "changelogs/fragments/DEVOPS-5380.yaml",
"ftype": "file",
"chksum_type": "sha256",
@@ -6273,6 +6581,13 @@
"format": 1
},
{
+ "name": "changelogs/fragments/DEVOPS-6282.yaml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "0c029a1405ca1cd34ac7be9a6575a210c516082eaaf68cdbe6ba9684fcacf29e",
+ "format": 1
+ },
+ {
"name": "changelogs/fragments/DEVOPS-4745.yaml",
"ftype": "file",
"chksum_type": "sha256",
@@ -6406,6 +6721,13 @@
"format": 1
},
{
+ "name": "changelogs/fragments/DEVOPS-6488.yaml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "eee19d3fbc9bde48ac0ee2093b4409f5f3777b3df319533a33c6be3bc3fff42d",
+ "format": 1
+ },
+ {
"name": "changelogs/fragments/20.8.0.yaml",
"ftype": "file",
"chksum_type": "sha256",
@@ -6511,6 +6833,13 @@
"format": 1
},
{
+ "name": "changelogs/fragments/DEVOPS-6519.yaml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "83316031d7862cc7ae638f327b70e0ca8dcf989e3edb14b93183d61033efa79a",
+ "format": 1
+ },
+ {
"name": "changelogs/fragments/DEVOPS-5270.yaml",
"ftype": "file",
"chksum_type": "sha256",
@@ -6532,6 +6861,13 @@
"format": 1
},
{
+ "name": "changelogs/fragments/DEVOPS-5509.yaml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "e04b3b08214c3e5b2c6a86aabe982ec1b4982f41d5d5eefc8b0ca31ff7d4a70a",
+ "format": 1
+ },
+ {
"name": "changelogs/fragments/DEVOPS-4573.yaml",
"ftype": "file",
"chksum_type": "sha256",
@@ -6553,6 +6889,13 @@
"format": 1
},
{
+ "name": "changelogs/fragments/DEVOPS-6667.yaml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "d442b6c0e1bad182b4bca72950581ea0d54c9f04e1a13f1879904a647e4925c9",
+ "format": 1
+ },
+ {
"name": "changelogs/fragments/DEVOPS-5948.yaml",
"ftype": "file",
"chksum_type": "sha256",
@@ -6588,6 +6931,13 @@
"format": 1
},
{
+ "name": "changelogs/fragments/DEVOPS-6671.yaml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "86e8491366c89fb30b62b71a615b901ef8b640d4710165a82fe7e705168ee3bf",
+ "format": 1
+ },
+ {
"name": "changelogs/fragments/20.6.0.yaml",
"ftype": "file",
"chksum_type": "sha256",
@@ -6679,6 +7029,13 @@
"format": 1
},
{
+ "name": "changelogs/fragments/DEVOPS-6309.yaml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "47434bb4ef9be1fc80a9ca0880b6cfe17adaaa00af912c44aac704f9f4173cc6",
+ "format": 1
+ },
+ {
"name": "changelogs/fragments/DEVOPS-4764.yaml",
"ftype": "file",
"chksum_type": "sha256",
@@ -6934,14 +7291,14 @@
"name": "changelogs/changelog.yaml",
"ftype": "file",
"chksum_type": "sha256",
- "chksum_sha256": "36885965bfb781446612c16dc008aa38596609693958a42b92e4fb6b760f1a95",
+ "chksum_sha256": "4c5611a9741bbfaaa7bc6fde286865b864f1c3c183327902ad3a5558b98903a0",
"format": 1
},
{
"name": "README.md",
"ftype": "file",
"chksum_type": "sha256",
- "chksum_sha256": "8bc903a9f9e960e83709868d4e500ec990596a01b84de2ac5a2541df22497f8f",
+ "chksum_sha256": "0453a86d933c48e94a50eb91635f95a6b1b53be67f320647c7ff5e00234f6e4a",
"format": 1
},
{
@@ -6983,7 +7340,7 @@
"name": ".github/workflows/main.yml",
"ftype": "file",
"chksum_type": "sha256",
- "chksum_sha256": "a63778d527b7f382ebb71cde6c15af7fd2c08200b52166eb965e2e41ad107128",
+ "chksum_sha256": "81717f13c4cc656d1cb9ed378b8b61f0da71b488679a11bfb36b3b52251ce37a",
"format": 1
},
{
@@ -7004,14 +7361,14 @@
"name": ".github/ISSUE_TEMPLATE/bug_report.yml",
"ftype": "file",
"chksum_type": "sha256",
- "chksum_sha256": "0a52c539892ce8413167fb8a94b7c299efe4727087fa0478b76c0c92c1f18cce",
+ "chksum_sha256": "073ca54b3b4421d1f071a39ecdea38cc3fbf8b9501edce99ca15d1247b249982",
"format": 1
},
{
"name": "CHANGELOG.rst",
"ftype": "file",
"chksum_type": "sha256",
- "chksum_sha256": "50916c09a55828383e593285e59044d240349554c2a54eed9ccbe367d97b7d6a",
+ "chksum_sha256": "edab2dd435af86cb61fc7a7f112d3136ae05a9a2340e4bd7bff546764beb0f2f",
"format": 1
}
],
diff --git a/ansible_collections/netapp/ontap/MANIFEST.json b/ansible_collections/netapp/ontap/MANIFEST.json
index 333653f31..c066583ca 100644
--- a/ansible_collections/netapp/ontap/MANIFEST.json
+++ b/ansible_collections/netapp/ontap/MANIFEST.json
@@ -2,7 +2,7 @@
"collection_info": {
"namespace": "netapp",
"name": "ontap",
- "version": "22.7.0",
+ "version": "22.10.0",
"authors": [
"NetApp Ansible Team <ng-ansibleteam@netapp.com>"
],
@@ -25,7 +25,7 @@
"name": "FILES.json",
"ftype": "file",
"chksum_type": "sha256",
- "chksum_sha256": "5fcf72da57259f9f93e9dd46099622ff04e193cef08c9aacedb13253ce404b31",
+ "chksum_sha256": "3b1ef023a88302b05998107de07d042c2efd877b22839f39bfd038585e6a7e35",
"format": 1
},
"format": 1
diff --git a/ansible_collections/netapp/ontap/README.md b/ansible_collections/netapp/ontap/README.md
index f086b09e9..cfad4cbcf 100644
--- a/ansible_collections/netapp/ontap/README.md
+++ b/ansible_collections/netapp/ontap/README.md
@@ -24,7 +24,7 @@ collections:
- netapp.ontap
```
# Requirements
-- ansible version >= 2.9
+- ansible version >= 7.0
- requests >= 2.20
- netapp-lib version >= 2018.11.13
@@ -56,13 +56,95 @@ return values differently than ZAPI you will need to update your playbooks to wo
### Deprecated Modules
The following modules do not have REST equivalent APIs. They will stop working on any ONTAP release after CY22-Q4 release.
- - na_ontap_cg_snapshot
- na_ontap_file_directory_policy
- na_ontap_svm_options
- na_ontap_quota_policy
# Release Notes
+## 22.10.0
+
+### Minor Changes
+ - na_ontap_rest_info - added warning message if given subset doesn't support option `owning_resource`.
+ - na_ontap_export_policy_rule - added `actions` and `modify` in module output.
+ - na_ontap_file_security_permissions_acl - added `actions` and `modify` in module output.
+ - na_ontap_igroup_initiator - added `actions` in module output.
+ - na_ontap_name_mappings - added `actions` and `modify` in module output.
+ - na_ontap_node - added `modify` in module output.
+ - na_ontap_lun_map - added `actions` in module output.
+ - na_ontap_lun_map_reporting_nodes - added `actions` in module output.
+ - na_ontap_storage_auto_giveback - added information on modifed attributes in module output.
+ - na_ontap_vscan_scanner_pool - added REST support to Vscan Scanner Pools Configuration module, requires ONTAP 9.6 or later.
+ - na_ontap_cifs_server - new option `is_multichannel_enabled` added in REST, requires ONTAP 9.10 or later.
+
+### Bug Fixes
+ - na_ontap_igroup_initiator - fixed issue with idempotency.
+
+## 22.9.0
+
+### New Options
+ - na_ontap_cifs_server - new option `lm_compatibility_level` added in REST, requires ONTAP 9.8 or later.
+ - na_ontap_cluster - new option `certificate.uuid` added in REST, requires ONTAP 9.10 or later.
+ - na_ontap_ems_destination - new options `syslog`, `port`, `transport`, `message_format`, `timestamp_format_override` and `hostname_format_override` added in REST, requires ONTAP 9.12.1 or later.
+
+### Minor Changes
+ - na_ontap_s3_services - create, modify S3 service returns `s3_service_info` in module output.
+ - na_ontap_nfs - fix error with `windows` in REST for ONTAP 9.10 or earlier.
+ - na_ontap_cluster_peer - added REST only support for modifying remote intercluster addresses in cluster peer relation.
+ - na_ontap_snapmirror - updated resync and resume operation for synchronous snapmirror relationship in REST.
+
+### Bug Fixes
+ - na_ontap_snapshot_policy - fix issue with modifying snapshot policy in REST.
+ - na_ontap_volume - modified `type` to be case insensitive in REST.
+ - na_ontap_security_certificates - fix error with ontap_info returned in module output in REST.
+
+### New Modules
+ - na_ontap_snmp_config - REST only support for modifying SNMP configuration, requires ONTAP 9.7 or later.
+ - na_ontap_cli_timeout - REST only support for setting CLI inactivity timeout value, requires ONTAP 9.6 or later.
+ - na_ontap_cifs_unix_symlink_mapping - REST only support for managing UNIX symbolic link mapping for CIFS clients, requires ONTAP 9.6 or later.
+
+## 22.8.3
+
+### Bug Fixes
+ - na_ontap_vserver_peer - fix issue with peering multiple clusters with same vserver name in REST.
+ - na_ontap_ems_destination - fix field error with `certificate.name` for ONTAP 9.11.1 or later in REST.
+ - na_ontap_snmp - fix for getting error when `authentication_method` set to default with ZAPI.
+
+## 22.8.1
+
+### Bug Fixes
+ - na_ontap_dns - fix keyerror for uuid when DNS is set to vserver in REST.
+ - na_ontap_volume - fix invalid field error with 'space.snapshot.autodelete' in REST.
+
+## 22.8.0
+
+### New Options
+ - na_ontap_lun - new option `qtree_name` added in REST.
+ - na_ontap_rest_info - new option `hal_linking` added.
+ - na_ontap_cifs_server - new option `default_site` added in REST, requires ONTAP 9.13.1 or later.
+ - na_ontap_ems_destination - new option `certificate`, `ca` added.
+ - na_ontap_volume - added new REST only options `vol_nearly_full_threshold_percent` and `vol_full_threshold_percent`, requires ONTAP 9.9 or later.
+ - na_ontap_qos_policy_group - added new REST only options `expected_iops_allocation` and `peak_iops_allocation`, requires ONTAP 9.10.1 or later.
+
+### Minor Changes
+ - na_ontap_user - added warning message when password is not changed.
+ - na_ontap_restit - returns changed as False for GET method.
+ - na_ontap_kerberos_realm - added REST support for `admin_server_ip`, `admin_server_port`, `pw_server_ip`, `pw_server_port` and `clock_skew`, requires ONTAP 9.13.1 or later.
+ - na_ontap_volume - added REST support for `atime_update` requires ONTAP 9.8 or later, `snapdir_access` and `snapshot_auto_delete` requires ONTAP 9.13.1 or later.
+ - na_ontap_net_ifgrp - return `name` and other details of a newly created interface group in module output in REST.
+ - na_ontap_cg_snapshot - added REST support to the cg snapshot module, requires ONTAP 9.10.1 or later.
+ - na_ontap_snmp - added REST support for `snmpv3` user.
+
+### Bug Fixes
+ - na_ontap_nfs - fix `default_user` under `windows` not getting modified, if not set previously, in REST.
+ - na_ontap_dns - fix DNS not working with Cluster mode.
+ - na_ontap_ems_filter - fix modify operation to add rule in existing filter.
+ - na_ontap_svm - fix REST version warning for `ndmp` under `services`.
+ - na_ontap_login_messages - fix idempotency issue in Cluster scope in REST.
+
+### New Modules
+ - na_ontap_ems_config - REST only support for modifying EMS configuration, requires ONTAP 9.6 or later.
+
## 22.7.0
### New Options
diff --git a/ansible_collections/netapp/ontap/changelogs/changelog.yaml b/ansible_collections/netapp/ontap/changelogs/changelog.yaml
index d850c7337..8d0eb138e 100644
--- a/ansible_collections/netapp/ontap/changelogs/changelog.yaml
+++ b/ansible_collections/netapp/ontap/changelogs/changelog.yaml
@@ -2735,6 +2735,37 @@ releases:
name: na_ontap_security_ipsec_policy
namespace: ''
release_date: '2022-12-07'
+ 22.10.0:
+ changes:
+ bugfixes:
+ - na_ontap_igroup_initiator - fixed issue with idempotency.
+ minor_changes:
+ - na_ontap_cifs_server - new option `is_multichannel_enabled` added in REST,
+ requires ONTAP 9.10 or later.
+ - na_ontap_export_policy_rule - added `actions` and `modify` in module output.
+ - na_ontap_file_security_permissions_acl - added `actions` and `modify` in module
+ output.
+ - na_ontap_igroup_initiator - added `actions` in module output.
+ - na_ontap_lun_map - added `actions` in module output.
+ - na_ontap_lun_map_reporting_nodes - added `actions` in module output.
+ - na_ontap_name_mappings - added `actions` and `modify` in module output.
+ - na_ontap_node - added `modify` in module output.
+ - na_ontap_rest_info - added warning message if given subset doesn't support
+ option `owning_resource`.
+ - na_ontap_storage_auto_giveback - added information on modified attributes
+ in module output.
+ - na_ontap_vscan_scanner_pool - added REST support to Vscan Scanner Pools Configuration
+ module, requires ONTAP 9.6 or later.
+ fragments:
+ - DEVOPS-6584.yaml
+ - DEVOPS-6646.yaml
+ - DEVOPS-6658.yaml
+ - DEVOPS-6664.yaml
+ - DEVOPS-6667.yaml
+ - DEVOPS-6671.yaml
+ - DEVOPS-6680.yaml
+ - DEVOPS-6681.yaml
+ release_date: '2024-02-06'
22.2.0:
changes:
bugfixes:
@@ -3033,3 +3064,129 @@ releases:
name: na_ontap_active_directory_domain_controllers
namespace: ''
release_date: '2023-06-09'
+ 22.8.0:
+ changes:
+ bugfixes:
+ - na_ontap_dns - fix DNS not working with Cluster mode.
+ - na_ontap_ems_filter - fix delete operation not idempotent for filter.
+ - na_ontap_ems_filter - fix modify operation to add rule in existing filter.
+ - na_ontap_login_messages - fix idempotency issue in Cluster scope in REST.
+ - na_ontap_nfs - fix `default_user` under `windows` not getting modified if
+ not set previously in REST.
+ - na_ontap_svm - fix REST version warning for `ndmp` under `services`.
+ minor_changes:
+ - na_ontap_broadcast_domain - changed documentation for ipspace as it is required
+ while using REST.
+ - na_ontap_cg_snapshot - added REST support to the cg snapshot module, requires
+ ONTAP 9.10.1 or later.
+ - na_ontap_cifs_server - new option `default_site` added in REST, requires ONTAP
+ 9.13.1 or later.
+ - na_ontap_ems_destination - new option ``certificate``, ``ca`` added.
+ - na_ontap_kerberos_realm - add REST support for `admin_server_ip`, `admin_server_port`,
+ `pw_server_ip`, `pw_server_port` and `clock_skew` from ONTAP 9.13.1 or later
+ - na_ontap_lun - new option `qtree_name` added in REST.
+ - na_ontap_net_ifgrp - return `name` and other details of a newly created interface
+ group in module output in REST.
+ - na_ontap_qos_policy_group - added new REST only options `expected_iops_allocation`
+ and `peak_iops_allocation`, requires ONTAP 9.10.1 or later.
+ - na_ontap_rest_info - new option `hal_linking` added to enable or disable HAL
+ links.
+ - na_ontap_restit - returns changed as False for GET method.
+ - na_ontap_snmp - added REST support for snmpv3 user.
+ - na_ontap_user - Added warning message when password is not changed.
+ - na_ontap_volume - added REST support for `atime_update` requires ONTAP 9.8
+ or later, `snapdir_access` and `snapshot_auto_delete` requires ONTAP 9.13.1
+ or later.
+ - na_ontap_volume - added new REST only options `vol_nearly_full_threshold_percent`
+ and `vol_full_threshold_percent`, requires ONTAP 9.9 or later.
+ fragments:
+ - DEVOPS-5509.yaml
+ - DEVOPS-5828.yaml
+ - DEVOPS-6286.yaml
+ - DEVOPS-6291.yaml
+ - DEVOPS-6309.yaml
+ - DEVOPS-6320.yaml
+ - DEVOPS-6330.yaml
+ - DEVOPS-6331.yaml
+ - DEVOPS-6341.yaml
+ - DEVOPS-6374.yaml
+ - DEVOPS-6386.yaml
+ - DEVOPS-6395.yaml
+ - DEVOPS-6413.yaml
+ - DEVOPS-6438.yaml
+ - DEVOPS-6463.yaml
+ - DEVOPS-6481.yaml
+ - DEVOPS-6486.yaml
+ - DEVOPS-6488.yaml
+ - DEVOPS-6495.yaml
+ - GITHUB-174.yaml
+ modules:
+ - description: NetApp ONTAP module to modify EMS configuration.
+ name: na_ontap_ems_config
+ namespace: ''
+ release_date: '2023-11-01'
+ 22.8.1:
+ changes:
+ bugfixes:
+ - na_ontap_dns - fix keyerror for uuid when DNS is set to vserver in REST.
+ - na_ontap_volume - fix invalid field error with 'space.snapshot.autodelete'
+ in REST.
+ fragments:
+ - DEVOPS-6519.yaml
+ - DEVOPS-6520.yaml
+ release_date: '2023-11-07'
+ 22.8.3:
+ changes:
+ bugfixes:
+ - na_ontap_ems_destination - fix field error with `certificate.name` for ONTAP
+ 9.11.1 or later in REST.
+ - na_ontap_vserver_peer - fix issue with peering multiple clusters with same
+ vserver name in REST.
+ fragments:
+ - DEVOPS-6527.yaml
+ - DEVOPS-6528.yaml
+ release_date: '2023-11-16'
+ 22.9.0:
+ changes:
+ bugfixes:
+ - na_ontap_nfs - fix error with `windows` in REST for ONTAP 9.10 or earlier.
+ - na_ontap_security_certificates - fix error with ontap_info returned in module
+ output in REST.
+ - na_ontap_snapshot_policy - fix issue with modifying snapshot policy in REST.
+ - na_ontap_volume - modified `type` to be case insensitive in REST.
+ minor_changes:
+ - na_ontap_cifs_server - new option `lm_compatibility_level` added in REST,
+ requires ONTAP 9.8 or later.
+ - na_ontap_cluster - new option `certificate.uuid` added in REST, requires ONTAP
+ 9.10 or later.
+ - na_ontap_cluster_peer - added REST only support for modifying remote intercluster
+ addresses in cluster peer relation.
+ - na_ontap_ems_destination - new options `syslog`, `port`, `transport`, `message_format`,
+ `timestamp_format_override` and `hostname_format_override` added in REST,
+ requires ONTAP 9.12.1 or later.
+ - na_ontap_s3_services - create, modify S3 service returns `s3_service_info`
+ in module output.
+ - na_ontap_snapmirror - updated resync and resume operation for synchronous
+ snapmirror relationship in REST.
+ fragments:
+ - DEVOPS-5920.yaml
+ - DEVOPS-6282.yaml
+ - DEVOPS-6389.yaml
+ - DEVOPS-6487.yaml
+ - DEVOPS-6524.yaml
+ - DEVOPS-6525.yaml
+ - DEVOPS-6529.yaml
+ - DEVOPS-6551.yaml
+ - DEVOPS-6556.yaml
+ modules:
+ - description: NetApp ONTAP module to manage UNIX symbolic link mapping for CIFS
+ clients.
+ name: na_ontap_cifs_unix_symlink_mapping
+ namespace: ''
+ - description: NetApp ONTAP module to set the CLI inactivity timeout value.
+ name: na_ontap_cli_timeout
+ namespace: ''
+ - description: NetApp ONTAP module to modify SNMP configuration.
+ name: na_ontap_snmp_config
+ namespace: ''
+ release_date: '2024-01-03'
diff --git a/ansible_collections/netapp/ontap/changelogs/fragments/DEVOPS-5509.yaml b/ansible_collections/netapp/ontap/changelogs/fragments/DEVOPS-5509.yaml
new file mode 100644
index 000000000..4e3c20584
--- /dev/null
+++ b/ansible_collections/netapp/ontap/changelogs/fragments/DEVOPS-5509.yaml
@@ -0,0 +1,2 @@
+minor_changes:
+ - na_ontap_volume - added new REST only options `vol_nearly_full_threshold_percent` and `vol_full_threshold_percent`, requires ONTAP 9.9 or later. \ No newline at end of file
diff --git a/ansible_collections/netapp/ontap/changelogs/fragments/DEVOPS-5828.yaml b/ansible_collections/netapp/ontap/changelogs/fragments/DEVOPS-5828.yaml
new file mode 100644
index 000000000..c06bfa58b
--- /dev/null
+++ b/ansible_collections/netapp/ontap/changelogs/fragments/DEVOPS-5828.yaml
@@ -0,0 +1,2 @@
+minor_changes:
+ - na_ontap_cg_snapshot - added REST support to the cg snapshot module, requires ONTAP 9.10.1 or later. \ No newline at end of file
diff --git a/ansible_collections/netapp/ontap/changelogs/fragments/DEVOPS-5920.yaml b/ansible_collections/netapp/ontap/changelogs/fragments/DEVOPS-5920.yaml
new file mode 100644
index 000000000..ef47ace18
--- /dev/null
+++ b/ansible_collections/netapp/ontap/changelogs/fragments/DEVOPS-5920.yaml
@@ -0,0 +1,2 @@
+minor_changes:
+ - na_ontap_cluster_peer - added REST only support for modifying remote intercluster addresses in cluster peer relation. \ No newline at end of file
diff --git a/ansible_collections/netapp/ontap/changelogs/fragments/DEVOPS-6282.yaml b/ansible_collections/netapp/ontap/changelogs/fragments/DEVOPS-6282.yaml
new file mode 100644
index 000000000..319ec4d92
--- /dev/null
+++ b/ansible_collections/netapp/ontap/changelogs/fragments/DEVOPS-6282.yaml
@@ -0,0 +1,2 @@
+minor_changes:
+ - na_ontap_snapmirror - updated resync and resume operation for synchronous snapmirror relationship in REST. \ No newline at end of file
diff --git a/ansible_collections/netapp/ontap/changelogs/fragments/DEVOPS-6286.yaml b/ansible_collections/netapp/ontap/changelogs/fragments/DEVOPS-6286.yaml
new file mode 100644
index 000000000..74fc7e76a
--- /dev/null
+++ b/ansible_collections/netapp/ontap/changelogs/fragments/DEVOPS-6286.yaml
@@ -0,0 +1,2 @@
+minor_changes:
+ - na_ontap_user - Added warning message when password is not changed.
diff --git a/ansible_collections/netapp/ontap/changelogs/fragments/DEVOPS-6291.yaml b/ansible_collections/netapp/ontap/changelogs/fragments/DEVOPS-6291.yaml
new file mode 100644
index 000000000..b21fe8dbc
--- /dev/null
+++ b/ansible_collections/netapp/ontap/changelogs/fragments/DEVOPS-6291.yaml
@@ -0,0 +1,2 @@
+bugfixes:
+ - na_ontap_ems_filter - fix modify operation to add rule in existing filter. \ No newline at end of file
diff --git a/ansible_collections/netapp/ontap/changelogs/fragments/DEVOPS-6309.yaml b/ansible_collections/netapp/ontap/changelogs/fragments/DEVOPS-6309.yaml
new file mode 100644
index 000000000..963655759
--- /dev/null
+++ b/ansible_collections/netapp/ontap/changelogs/fragments/DEVOPS-6309.yaml
@@ -0,0 +1,2 @@
+minor_changes:
+ - na_ontap_restit - returns changed as False for GET method. \ No newline at end of file
diff --git a/ansible_collections/netapp/ontap/changelogs/fragments/DEVOPS-6320.yaml b/ansible_collections/netapp/ontap/changelogs/fragments/DEVOPS-6320.yaml
new file mode 100644
index 000000000..a0ec3ad6f
--- /dev/null
+++ b/ansible_collections/netapp/ontap/changelogs/fragments/DEVOPS-6320.yaml
@@ -0,0 +1,2 @@
+bugfixes:
+ - na_ontap_nfs - fix `default_user` under `windows` not getting modified if not set previously in REST. \ No newline at end of file
diff --git a/ansible_collections/netapp/ontap/changelogs/fragments/DEVOPS-6330.yaml b/ansible_collections/netapp/ontap/changelogs/fragments/DEVOPS-6330.yaml
new file mode 100644
index 000000000..9d2d9e0f5
--- /dev/null
+++ b/ansible_collections/netapp/ontap/changelogs/fragments/DEVOPS-6330.yaml
@@ -0,0 +1,2 @@
+minor_changes:
+ - na_ontap_lun - new option `qtree_name` added in REST. \ No newline at end of file
diff --git a/ansible_collections/netapp/ontap/changelogs/fragments/DEVOPS-6331.yaml b/ansible_collections/netapp/ontap/changelogs/fragments/DEVOPS-6331.yaml
new file mode 100644
index 000000000..18e06be95
--- /dev/null
+++ b/ansible_collections/netapp/ontap/changelogs/fragments/DEVOPS-6331.yaml
@@ -0,0 +1,2 @@
+minor_changes:
+ - na_ontap_cifs_server - new option `default_site` added in REST, requires ONTAP 9.13.1 or later. \ No newline at end of file
diff --git a/ansible_collections/netapp/ontap/changelogs/fragments/DEVOPS-6341.yaml b/ansible_collections/netapp/ontap/changelogs/fragments/DEVOPS-6341.yaml
new file mode 100644
index 000000000..821f89652
--- /dev/null
+++ b/ansible_collections/netapp/ontap/changelogs/fragments/DEVOPS-6341.yaml
@@ -0,0 +1,2 @@
+bugfixes:
+ - na_ontap_dns - fix DNS not working with Cluster mode. \ No newline at end of file
diff --git a/ansible_collections/netapp/ontap/changelogs/fragments/DEVOPS-6374.yaml b/ansible_collections/netapp/ontap/changelogs/fragments/DEVOPS-6374.yaml
new file mode 100644
index 000000000..ef79605d5
--- /dev/null
+++ b/ansible_collections/netapp/ontap/changelogs/fragments/DEVOPS-6374.yaml
@@ -0,0 +1,2 @@
+minor_changes:
+ - na_ontap_kerberos_realm - add REST support for `admin_server_ip`, `admin_server_port`, `pw_server_ip`, `pw_server_port` and `clock_skew` from ONTAP 9.13.1 or later \ No newline at end of file
diff --git a/ansible_collections/netapp/ontap/changelogs/fragments/DEVOPS-6386.yaml b/ansible_collections/netapp/ontap/changelogs/fragments/DEVOPS-6386.yaml
new file mode 100644
index 000000000..408039214
--- /dev/null
+++ b/ansible_collections/netapp/ontap/changelogs/fragments/DEVOPS-6386.yaml
@@ -0,0 +1,2 @@
+minor_changes:
+ - na_ontap_rest_info - new option `hal_linking` added to enable or disable HAL links. \ No newline at end of file
diff --git a/ansible_collections/netapp/ontap/changelogs/fragments/DEVOPS-6389.yaml b/ansible_collections/netapp/ontap/changelogs/fragments/DEVOPS-6389.yaml
new file mode 100644
index 000000000..785e06a49
--- /dev/null
+++ b/ansible_collections/netapp/ontap/changelogs/fragments/DEVOPS-6389.yaml
@@ -0,0 +1,2 @@
+bugfixes:
+ - na_ontap_snapshot_policy - fix issue with modifying snapshot policy in REST. \ No newline at end of file
diff --git a/ansible_collections/netapp/ontap/changelogs/fragments/DEVOPS-6395.yaml b/ansible_collections/netapp/ontap/changelogs/fragments/DEVOPS-6395.yaml
new file mode 100644
index 000000000..79fb05b5b
--- /dev/null
+++ b/ansible_collections/netapp/ontap/changelogs/fragments/DEVOPS-6395.yaml
@@ -0,0 +1,2 @@
+minor_changes:
+ - na_ontap_volume - added REST support for `atime_update` requires ONTAP 9.8 or later, `snapdir_access` and `snapshot_auto_delete` requires ONTAP 9.13.1 or later. \ No newline at end of file
diff --git a/ansible_collections/netapp/ontap/changelogs/fragments/DEVOPS-6413.yaml b/ansible_collections/netapp/ontap/changelogs/fragments/DEVOPS-6413.yaml
new file mode 100644
index 000000000..2a7e8b162
--- /dev/null
+++ b/ansible_collections/netapp/ontap/changelogs/fragments/DEVOPS-6413.yaml
@@ -0,0 +1,5 @@
+minor_changes:
+ - na_ontap_snmp - added REST support for snmpv3 user.
+
+bugfixes:
+ - na_ontap_snmp - fix for getting error when `authentication_method` set to default with ZAPI. \ No newline at end of file
diff --git a/ansible_collections/netapp/ontap/changelogs/fragments/DEVOPS-6438.yaml b/ansible_collections/netapp/ontap/changelogs/fragments/DEVOPS-6438.yaml
new file mode 100644
index 000000000..91a8562ff
--- /dev/null
+++ b/ansible_collections/netapp/ontap/changelogs/fragments/DEVOPS-6438.yaml
@@ -0,0 +1,2 @@
+minor_changes:
+ - na_ontap_net_ifgrp - return `name` and other details of a newly created interface group in module output in REST. \ No newline at end of file
diff --git a/ansible_collections/netapp/ontap/changelogs/fragments/DEVOPS-6463.yaml b/ansible_collections/netapp/ontap/changelogs/fragments/DEVOPS-6463.yaml
new file mode 100644
index 000000000..fb997b30f
--- /dev/null
+++ b/ansible_collections/netapp/ontap/changelogs/fragments/DEVOPS-6463.yaml
@@ -0,0 +1,2 @@
+bugfixes:
+ - na_ontap_ems_filter - fix delete operation not idempotent for filter. \ No newline at end of file
diff --git a/ansible_collections/netapp/ontap/changelogs/fragments/DEVOPS-6481.yaml b/ansible_collections/netapp/ontap/changelogs/fragments/DEVOPS-6481.yaml
new file mode 100644
index 000000000..231a5a903
--- /dev/null
+++ b/ansible_collections/netapp/ontap/changelogs/fragments/DEVOPS-6481.yaml
@@ -0,0 +1,2 @@
+minor_changes:
+ - na_ontap_broadcast_domain - changed documentation for ipspace as it is required while using REST. \ No newline at end of file
diff --git a/ansible_collections/netapp/ontap/changelogs/fragments/DEVOPS-6486.yaml b/ansible_collections/netapp/ontap/changelogs/fragments/DEVOPS-6486.yaml
new file mode 100644
index 000000000..a44334ea3
--- /dev/null
+++ b/ansible_collections/netapp/ontap/changelogs/fragments/DEVOPS-6486.yaml
@@ -0,0 +1,2 @@
+bugfixes:
+ - na_ontap_svm - fix REST version warning for `ndmp` under `services`. \ No newline at end of file
diff --git a/ansible_collections/netapp/ontap/changelogs/fragments/DEVOPS-6487.yaml b/ansible_collections/netapp/ontap/changelogs/fragments/DEVOPS-6487.yaml
new file mode 100644
index 000000000..894e1bad1
--- /dev/null
+++ b/ansible_collections/netapp/ontap/changelogs/fragments/DEVOPS-6487.yaml
@@ -0,0 +1,2 @@
+bugfixes:
+ - na_ontap_volume - modified `type` to be case insensitive in REST. \ No newline at end of file
diff --git a/ansible_collections/netapp/ontap/changelogs/fragments/DEVOPS-6488.yaml b/ansible_collections/netapp/ontap/changelogs/fragments/DEVOPS-6488.yaml
new file mode 100644
index 000000000..2060d5133
--- /dev/null
+++ b/ansible_collections/netapp/ontap/changelogs/fragments/DEVOPS-6488.yaml
@@ -0,0 +1,2 @@
+minor_changes:
+ - na_ontap_qos_policy_group - added new REST only options `expected_iops_allocation` and `peak_iops_allocation`, requires ONTAP 9.10.1 or later. \ No newline at end of file
diff --git a/ansible_collections/netapp/ontap/changelogs/fragments/DEVOPS-6495.yaml b/ansible_collections/netapp/ontap/changelogs/fragments/DEVOPS-6495.yaml
new file mode 100644
index 000000000..c5797f855
--- /dev/null
+++ b/ansible_collections/netapp/ontap/changelogs/fragments/DEVOPS-6495.yaml
@@ -0,0 +1,2 @@
+bugfixes:
+ - na_ontap_login_messages - fix idempotency issue in Cluster scope in REST. \ No newline at end of file
diff --git a/ansible_collections/netapp/ontap/changelogs/fragments/DEVOPS-6519.yaml b/ansible_collections/netapp/ontap/changelogs/fragments/DEVOPS-6519.yaml
new file mode 100644
index 000000000..f3e4c19bd
--- /dev/null
+++ b/ansible_collections/netapp/ontap/changelogs/fragments/DEVOPS-6519.yaml
@@ -0,0 +1,2 @@
+bugfixes:
+ - na_ontap_volume - fix invalid field error with 'space.snapshot.autodelete' in REST. \ No newline at end of file
diff --git a/ansible_collections/netapp/ontap/changelogs/fragments/DEVOPS-6520.yaml b/ansible_collections/netapp/ontap/changelogs/fragments/DEVOPS-6520.yaml
new file mode 100644
index 000000000..ca91b16a9
--- /dev/null
+++ b/ansible_collections/netapp/ontap/changelogs/fragments/DEVOPS-6520.yaml
@@ -0,0 +1,2 @@
+bugfixes:
+ - na_ontap_dns - fix keyerror for uuid when DNS is set to vserver in REST. \ No newline at end of file
diff --git a/ansible_collections/netapp/ontap/changelogs/fragments/DEVOPS-6524.yaml b/ansible_collections/netapp/ontap/changelogs/fragments/DEVOPS-6524.yaml
new file mode 100644
index 000000000..2d96a6530
--- /dev/null
+++ b/ansible_collections/netapp/ontap/changelogs/fragments/DEVOPS-6524.yaml
@@ -0,0 +1,2 @@
+minor_changes:
+ - na_ontap_ems_destination - new options `syslog`, `port`, `transport`, `message_format`, `timestamp_format_override` and `hostname_format_override` added in REST, requires ONTAP 9.12.1 or later. \ No newline at end of file
diff --git a/ansible_collections/netapp/ontap/changelogs/fragments/DEVOPS-6525.yaml b/ansible_collections/netapp/ontap/changelogs/fragments/DEVOPS-6525.yaml
new file mode 100644
index 000000000..0c1cc0337
--- /dev/null
+++ b/ansible_collections/netapp/ontap/changelogs/fragments/DEVOPS-6525.yaml
@@ -0,0 +1,5 @@
+minor_changes:
+ - na_ontap_cluster - new option `certificate.uuid` added in REST, requires ONTAP 9.10 or later.
+
+bugfixes:
+ - na_ontap_security_certificates - fix error with ontap_info returned in module output in REST. \ No newline at end of file
diff --git a/ansible_collections/netapp/ontap/changelogs/fragments/DEVOPS-6527.yaml b/ansible_collections/netapp/ontap/changelogs/fragments/DEVOPS-6527.yaml
new file mode 100644
index 000000000..53d0b496b
--- /dev/null
+++ b/ansible_collections/netapp/ontap/changelogs/fragments/DEVOPS-6527.yaml
@@ -0,0 +1,2 @@
+bugfixes:
+ - na_ontap_vserver_peer - fix issue with peering multiple clusters with same vserver name in REST. \ No newline at end of file
diff --git a/ansible_collections/netapp/ontap/changelogs/fragments/DEVOPS-6528.yaml b/ansible_collections/netapp/ontap/changelogs/fragments/DEVOPS-6528.yaml
new file mode 100644
index 000000000..6edc14f1d
--- /dev/null
+++ b/ansible_collections/netapp/ontap/changelogs/fragments/DEVOPS-6528.yaml
@@ -0,0 +1,2 @@
+bugfixes:
+ - na_ontap_ems_destination - fix field error with `certificate.name` for ONTAP 9.11.1 or later in REST. \ No newline at end of file
diff --git a/ansible_collections/netapp/ontap/changelogs/fragments/DEVOPS-6529.yaml b/ansible_collections/netapp/ontap/changelogs/fragments/DEVOPS-6529.yaml
new file mode 100644
index 000000000..4b276e8fa
--- /dev/null
+++ b/ansible_collections/netapp/ontap/changelogs/fragments/DEVOPS-6529.yaml
@@ -0,0 +1,2 @@
+minor_changes:
+ - na_ontap_cifs_server - new option `lm_compatibility_level` added in REST, requires ONTAP 9.8 or later. \ No newline at end of file
diff --git a/ansible_collections/netapp/ontap/changelogs/fragments/DEVOPS-6551.yaml b/ansible_collections/netapp/ontap/changelogs/fragments/DEVOPS-6551.yaml
new file mode 100644
index 000000000..f31cce5a9
--- /dev/null
+++ b/ansible_collections/netapp/ontap/changelogs/fragments/DEVOPS-6551.yaml
@@ -0,0 +1,2 @@
+minor_changes:
+ - na_ontap_s3_services - create, modify S3 service returns `s3_service_info` in module output. \ No newline at end of file
diff --git a/ansible_collections/netapp/ontap/changelogs/fragments/DEVOPS-6556.yaml b/ansible_collections/netapp/ontap/changelogs/fragments/DEVOPS-6556.yaml
new file mode 100644
index 000000000..c82759a81
--- /dev/null
+++ b/ansible_collections/netapp/ontap/changelogs/fragments/DEVOPS-6556.yaml
@@ -0,0 +1,2 @@
+bugfixes:
+ - na_ontap_nfs - fix error with `windows` in REST for ONTAP 9.10 or earlier. \ No newline at end of file
diff --git a/ansible_collections/netapp/ontap/changelogs/fragments/DEVOPS-6584.yaml b/ansible_collections/netapp/ontap/changelogs/fragments/DEVOPS-6584.yaml
new file mode 100644
index 000000000..a2cbcac5c
--- /dev/null
+++ b/ansible_collections/netapp/ontap/changelogs/fragments/DEVOPS-6584.yaml
@@ -0,0 +1,2 @@
+minor_changes:
+ - na_ontap_rest_info - added warning message if given subset doesn't support option `owning_resource`. \ No newline at end of file
diff --git a/ansible_collections/netapp/ontap/changelogs/fragments/DEVOPS-6646.yaml b/ansible_collections/netapp/ontap/changelogs/fragments/DEVOPS-6646.yaml
new file mode 100644
index 000000000..2f2103812
--- /dev/null
+++ b/ansible_collections/netapp/ontap/changelogs/fragments/DEVOPS-6646.yaml
@@ -0,0 +1,6 @@
+minor_changes:
+ - na_ontap_export_policy_rule - added `actions` and `modify` in module output.
+ - na_ontap_file_security_permissions_acl - added `actions` and `modify` in module output.
+ - na_ontap_igroup_initiator - added `actions` in module output.
+ - na_ontap_name_mappings - added `actions` and `modify` in module output.
+ - na_ontap_node - added `modify` in module output. \ No newline at end of file
diff --git a/ansible_collections/netapp/ontap/changelogs/fragments/DEVOPS-6658.yaml b/ansible_collections/netapp/ontap/changelogs/fragments/DEVOPS-6658.yaml
new file mode 100644
index 000000000..b90e520f7
--- /dev/null
+++ b/ansible_collections/netapp/ontap/changelogs/fragments/DEVOPS-6658.yaml
@@ -0,0 +1,2 @@
+minor_changes:
+ - na_ontap_lun_map - added `actions` in module output. \ No newline at end of file
diff --git a/ansible_collections/netapp/ontap/changelogs/fragments/DEVOPS-6664.yaml b/ansible_collections/netapp/ontap/changelogs/fragments/DEVOPS-6664.yaml
new file mode 100644
index 000000000..65f8ca7a7
--- /dev/null
+++ b/ansible_collections/netapp/ontap/changelogs/fragments/DEVOPS-6664.yaml
@@ -0,0 +1,2 @@
+minor_changes:
+ - na_ontap_lun_map_reporting_nodes - added `actions` in module output. \ No newline at end of file
diff --git a/ansible_collections/netapp/ontap/changelogs/fragments/DEVOPS-6667.yaml b/ansible_collections/netapp/ontap/changelogs/fragments/DEVOPS-6667.yaml
new file mode 100644
index 000000000..2e3a10b17
--- /dev/null
+++ b/ansible_collections/netapp/ontap/changelogs/fragments/DEVOPS-6667.yaml
@@ -0,0 +1,2 @@
+minor_changes:
+ - na_ontap_storage_auto_giveback - added information on modified attributes in module output. \ No newline at end of file
diff --git a/ansible_collections/netapp/ontap/changelogs/fragments/DEVOPS-6671.yaml b/ansible_collections/netapp/ontap/changelogs/fragments/DEVOPS-6671.yaml
new file mode 100644
index 000000000..5a13d9199
--- /dev/null
+++ b/ansible_collections/netapp/ontap/changelogs/fragments/DEVOPS-6671.yaml
@@ -0,0 +1,2 @@
+minor_changes:
+ - na_ontap_vscan_scanner_pool - added REST support to Vscan Scanner Pools Configuration module, requires ONTAP 9.6 or later. \ No newline at end of file
diff --git a/ansible_collections/netapp/ontap/changelogs/fragments/DEVOPS-6680.yaml b/ansible_collections/netapp/ontap/changelogs/fragments/DEVOPS-6680.yaml
new file mode 100644
index 000000000..18dd7f28c
--- /dev/null
+++ b/ansible_collections/netapp/ontap/changelogs/fragments/DEVOPS-6680.yaml
@@ -0,0 +1,2 @@
+minor_changes:
+ - na_ontap_cifs_server - new option `is_multichannel_enabled` added in REST, requires ONTAP 9.10 or later. \ No newline at end of file
diff --git a/ansible_collections/netapp/ontap/changelogs/fragments/DEVOPS-6681.yaml b/ansible_collections/netapp/ontap/changelogs/fragments/DEVOPS-6681.yaml
new file mode 100644
index 000000000..867dd6aba
--- /dev/null
+++ b/ansible_collections/netapp/ontap/changelogs/fragments/DEVOPS-6681.yaml
@@ -0,0 +1,2 @@
+bugfixes:
+ - na_ontap_igroup_initiator - fixed issue with idempotency. \ No newline at end of file
diff --git a/ansible_collections/netapp/ontap/changelogs/fragments/GITHUB-174.yaml b/ansible_collections/netapp/ontap/changelogs/fragments/GITHUB-174.yaml
new file mode 100644
index 000000000..f26c149d0
--- /dev/null
+++ b/ansible_collections/netapp/ontap/changelogs/fragments/GITHUB-174.yaml
@@ -0,0 +1,2 @@
+minor_changes:
+ - na_ontap_ems_destination - new option ``certificate``, ``ca`` added. \ No newline at end of file
diff --git a/ansible_collections/netapp/ontap/meta/runtime.yml b/ansible_collections/netapp/ontap/meta/runtime.yml
index 49dcabb60..4d8208129 100644
--- a/ansible_collections/netapp/ontap/meta/runtime.yml
+++ b/ansible_collections/netapp/ontap/meta/runtime.yml
@@ -1,5 +1,5 @@
---
-requires_ansible: ">=2.9.10"
+requires_ansible: ">=2.14"
action_groups:
netapp_ontap:
- na_ontap_active_directory_domain_controllers
@@ -19,6 +19,8 @@ action_groups:
- na_ontap_cifs_local_user_set_password
- na_ontap_cifs
- na_ontap_cifs_server
+ - na_ontap_cifs_unix_symlink_mapping
+ - na_ontap_cli_timeout
- na_ontap_cluster_ha
- na_ontap_cluster_peer
- na_ontap_cluster
@@ -29,6 +31,7 @@ action_groups:
- na_ontap_dns
- na_ontap_domain_tunnel
- na_ontap_efficiency_policy
+ - na_ontap_ems_config
- na_ontap_ems_destination
- na_ontap_ems_filter
- na_ontap_export_policy
@@ -123,6 +126,7 @@ action_groups:
- na_ontap_snapshot_policy
- na_ontap_snapshot
- na_ontap_snmp
+ - na_ontap_snmp_config
- na_ontap_snmp_traphosts
- na_ontap_software_update
- na_ontap_ssh_command
diff --git a/ansible_collections/netapp/ontap/plugins/module_utils/netapp.py b/ansible_collections/netapp/ontap/plugins/module_utils/netapp.py
index 28d9428a2..f41139423 100644
--- a/ansible_collections/netapp/ontap/plugins/module_utils/netapp.py
+++ b/ansible_collections/netapp/ontap/plugins/module_utils/netapp.py
@@ -48,7 +48,7 @@ try:
except ImportError:
ANSIBLE_VERSION = 'unknown'
-COLLECTION_VERSION = "22.7.0"
+COLLECTION_VERSION = "22.10.0"
CLIENT_APP_VERSION = "%s/%s" % ("%s", COLLECTION_VERSION)
IMPORT_EXCEPTION = None
diff --git a/ansible_collections/netapp/ontap/plugins/modules/na_ontap_broadcast_domain.py b/ansible_collections/netapp/ontap/plugins/modules/na_ontap_broadcast_domain.py
index ef74d1705..11c762d7c 100644
--- a/ansible_collections/netapp/ontap/plugins/modules/na_ontap_broadcast_domain.py
+++ b/ansible_collections/netapp/ontap/plugins/modules/na_ontap_broadcast_domain.py
@@ -47,6 +47,7 @@ options:
- Specify the required ipspace for the broadcast domain.
- With ZAPI, a domain ipspace cannot be modified after the domain has been created.
- With REST, a domain ipspace can be modified.
+ - This option is required while using REST.
type: str
from_ipspace:
description:
diff --git a/ansible_collections/netapp/ontap/plugins/modules/na_ontap_cg_snapshot.py b/ansible_collections/netapp/ontap/plugins/modules/na_ontap_cg_snapshot.py
index 313bf223e..20bca605d 100644
--- a/ansible_collections/netapp/ontap/plugins/modules/na_ontap_cg_snapshot.py
+++ b/ansible_collections/netapp/ontap/plugins/modules/na_ontap_cg_snapshot.py
@@ -1,6 +1,6 @@
#!/usr/bin/python
-# (c) 2018-2019, 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)
from __future__ import absolute_import, division, print_function
@@ -14,17 +14,17 @@ DOCUMENTATION = '''
short_description: NetApp ONTAP manage consistency group snapshot
author: NetApp Ansible Team (@carchi8py) <ng-ansibleteam@netapp.com>
description:
- - Create consistency group snapshot for ONTAP volumes.
- - This module only supports ZAPI and is deprecated.
- - The final version of ONTAP to support ZAPI is 9.12.1.
+ - Create or delete consistency group snapshot for ONTAP volumes.
extends_documentation_fragment:
- - netapp.ontap.netapp.na_ontap_zapi
+ - netapp.ontap.netapp.na_ontap
module: na_ontap_cg_snapshot
options:
state:
description:
- - If you want to create a snapshot.
+ - Specifies whether to create or delete the snapshot.
+ - Choice 'absent' is valid only with REST.
default: present
+ choices: ['present', 'absent']
type: str
vserver:
required: true
@@ -32,11 +32,19 @@ options:
description:
- Name of the vserver.
volumes:
- required: true
+ required: false
type: list
elements: str
description:
- A list of volumes in this filer that is part of this CG operation.
+ - Required with ZAPI.
+ consistency_group:
+ required: false
+ type: str
+ description:
+ - Name of the consistency group for which snapshot needs to be created or deleted.
+ - Valid only with REST.
+ version_added: 22.8.0
snapshot:
required: true
type: str
@@ -45,6 +53,7 @@ options:
timeout:
description:
- Timeout selector.
+ - Not supported with REST.
choices: ['urgent', 'medium', 'relaxed']
type: str
default: medium
@@ -52,8 +61,18 @@ options:
description:
- A human readable SnapMirror label to be attached with the consistency group snapshot copies.
type: str
+ comment:
+ description:
+ - Comment for the snapshot copy.
+ - Only supported with REST.
+ type: str
+ version_added: 22.8.0
version_added: 2.7.0
+notes:
+ - REST support requires ONTAP 9.10 or later.
+ - Delete operation is supported only with REST.
+
'''
EXAMPLES = """
@@ -66,6 +85,40 @@ EXAMPLES = """
username: "{{ netapp username }}"
password: "{{ netapp password }}"
hostname: "{{ netapp hostname }}"
+
+ - name: Create CG snapshot using CG name - REST
+ na_ontap_cg_snapshot:
+ state: present
+ vserver: vserver_name
+ snapshot: snapshot_name
+ consistency_group: cg_name
+ snapmirror_label: sm_label
+ username: "{{ netapp username }}"
+ password: "{{ netapp password }}"
+ hostname: "{{ netapp hostname }}"
+
+ - name: Create CG snapshot using volumes - REST
+ na_ontap_cg_snapshot:
+ state: present
+ vserver: vserver_name
+ snapshot: snapshot_name
+ volumes:
+ - vol1
+ - vol2
+ snapmirror_label: sm_label
+ username: "{{ netapp username }}"
+ password: "{{ netapp password }}"
+ hostname: "{{ netapp hostname }}"
+
+ - name: Delete CG snapshot - REST
+ na_ontap_cg_snapshot:
+ state: absent
+ vserver: vserver_name
+ snapshot: snapshot_name
+ consistency_group: cg_name
+ username: "{{ netapp username }}"
+ password: "{{ netapp password }}"
+ hostname: "{{ netapp hostname }}"
"""
RETURN = """
@@ -77,55 +130,56 @@ from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils._text import to_native
import ansible_collections.netapp.ontap.plugins.module_utils.netapp as netapp_utils
from ansible_collections.netapp.ontap.plugins.module_utils.netapp_module import NetAppModule
-
-HAS_NETAPP_LIB = netapp_utils.has_netapp_lib()
+from ansible_collections.netapp.ontap.plugins.module_utils import rest_generic
class NetAppONTAPCGSnapshot(object):
"""
- Methods to create CG snapshots
+ Methods to create or delete CG snapshots
"""
def __init__(self):
- self.argument_spec = netapp_utils.na_ontap_zapi_only_spec()
+ self.argument_spec = netapp_utils.na_ontap_host_argument_spec()
self.argument_spec.update(dict(
- state=dict(required=False, type='str', default='present'),
+ state=dict(required=False, choices=['present', 'absent'], default='present'),
vserver=dict(required=True, type='str'),
- volumes=dict(required=True, type='list', elements='str'),
+ volumes=dict(required=False, type='list', elements='str'),
snapshot=dict(required=True, type='str'),
timeout=dict(required=False, type='str', choices=[
'urgent', 'medium', 'relaxed'], default='medium'),
- snapmirror_label=dict(required=False, type='str')
+ snapmirror_label=dict(required=False, type='str'),
+ consistency_group=dict(required=False, type='str'),
+ comment=dict(required=False, type='str'),
))
self.module = AnsibleModule(
argument_spec=self.argument_spec,
- supports_check_mode=False
+ supports_check_mode=False,
+ mutually_exclusive=[
+ ['consistency_group', 'volumes']]
)
- parameters = self.module.params
-
- # set up variables
- self.state = parameters['state']
- self.vserver = parameters['vserver']
- self.volumes = parameters['volumes']
- self.snapshot = parameters['snapshot']
- self.timeout = parameters['timeout']
- self.snapmirror_label = parameters['snapmirror_label']
- self.cgid = None
- NetAppModule().module_deprecated(self.module)
- if HAS_NETAPP_LIB is False:
- self.module.fail_json(
- msg="the python NetApp-Lib module is required")
+ self.na_helper = NetAppModule()
+ self.parameters = self.na_helper.set_parameters(self.module.params)
+ self.rest_api = netapp_utils.OntapRestAPI(self.module)
+ self.use_rest = self.rest_api.is_rest()
+
+ if self.use_rest:
+ if not self.rest_api.meets_rest_minimum_version(self.use_rest, 9, 10, 1):
+ self.module.fail_json(msg='REST requires ONTAP 9.10.1 or later for /application/consistency-groups APIs.')
+ self.cg_uuid = None
else:
- self.server = netapp_utils.setup_na_ontap_zapi(
- module=self.module, vserver=self.vserver)
+ self.cgid = None
+ if not netapp_utils.has_netapp_lib():
+ self.module.fail_json(msg=netapp_utils.netapp_lib_is_required())
+ self.zapi_errors()
+ self.server = netapp_utils.setup_na_ontap_zapi(module=self.module, vserver=self.parameters['vserver'])
def does_snapshot_exist(self, volume):
"""
This is duplicated from na_ontap_snapshot
Checks to see if a snapshot exists or not
- :return: Return True if a snapshot exists, false if it dosn't
+ :return: Return True if a snapshot exists, false if it dosen't
"""
# TODO: Remove this method and import snapshot module and
# call get after re-factoring __init__ across all the modules
@@ -141,9 +195,9 @@ class NetAppONTAPCGSnapshot(object):
# compose query
query = netapp_utils.zapi.NaElement("query")
snapshot_info_obj = netapp_utils.zapi.NaElement("snapshot-info")
- snapshot_info_obj.add_new_child("name", self.snapshot)
+ snapshot_info_obj.add_new_child("name", self.parameters['snapshot'])
snapshot_info_obj.add_new_child("volume", volume)
- snapshot_info_obj.add_new_child("vserver", self.vserver)
+ snapshot_info_obj.add_new_child("vserver", self.parameters['vserver'])
query.add_child_elem(snapshot_info_obj)
snapshot_obj.add_child_elem(query)
result = self.server.invoke_successfully(snapshot_obj, True)
@@ -164,7 +218,7 @@ class NetAppONTAPCGSnapshot(object):
if self.cgid is not None:
self.cg_commit()
else:
- self.module.fail_json(msg="Error fetching CG ID for CG commit %s" % self.snapshot,
+ self.module.fail_json(msg="Error fetching CG ID for CG commit %s" % self.parameters['snapshot'],
exception=traceback.format_exc())
return started
@@ -174,19 +228,19 @@ class NetAppONTAPCGSnapshot(object):
"""
snapshot_started = False
cgstart = netapp_utils.zapi.NaElement("cg-start")
- cgstart.add_new_child("snapshot", self.snapshot)
- cgstart.add_new_child("timeout", self.timeout)
+ cgstart.add_new_child("snapshot", self.parameters['snapshot'])
+ cgstart.add_new_child("timeout", self.parameters['timeout'])
volume_list = netapp_utils.zapi.NaElement("volumes")
cgstart.add_child_elem(volume_list)
- for vol in self.volumes:
+ for vol in self.parameters['volumes']:
snapshot_exists = self.does_snapshot_exist(vol)
if snapshot_exists is None:
snapshot_started = True
volume_list.add_new_child("volume-name", vol)
if snapshot_started:
- if self.snapmirror_label:
+ if self.parameters.get('snapmirror_label') is not None:
cgstart.add_new_child("snapmirror-label",
- self.snapmirror_label)
+ self.parameters['snapmirror_label'])
try:
cgresult = self.server.invoke_successfully(
cgstart, enable_tunneling=True)
@@ -194,7 +248,7 @@ class NetAppONTAPCGSnapshot(object):
self.cgid = cgresult['cg-id']
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg="Error creating CG snapshot %s: %s" %
- (self.snapshot, to_native(error)),
+ (self.parameters['snapshot'], to_native(error)),
exception=traceback.format_exc())
return snapshot_started
@@ -209,18 +263,126 @@ class NetAppONTAPCGSnapshot(object):
enable_tunneling=True)
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg="Error committing CG snapshot %s: %s" %
- (self.snapshot, to_native(error)),
+ (self.parameters['snapshot'], to_native(error)),
+ exception=traceback.format_exc())
+
+ def zapi_errors(self):
+ unsupported_zapi_properties = ['consistency_group', 'comment']
+ used_unsupported_zapi_properties = [option for option in unsupported_zapi_properties if option in self.parameters]
+ if used_unsupported_zapi_properties:
+ self.module.fail_json(msg="Error: %s options supported only with REST." % " ,".join(used_unsupported_zapi_properties))
+ if self.parameters.get('volumes') is None:
+ self.module.fail_json(msg="Error: 'volumes' option is mandatory while using ZAPI.")
+ if self.parameters.get('state') == 'absent':
+ self.module.fail_json(msg="Deletion of consistency group snapshot is not supported with ZAPI.")
+
+ def get_cg_rest(self):
+ """
+ Retrieve consistency group with the given CG name or list of volumes
+ """
+ api = '/application/consistency-groups'
+ query = {
+ 'svm.name': self.parameters['vserver'],
+ 'fields': 'svm.uuid,name,uuid,'
+ }
+
+ if self.parameters.get('consistency_group') is not None:
+ query['name'] = self.parameters['consistency_group']
+ record, error = rest_generic.get_one_record(self.rest_api, api, query)
+ if error:
+ self.module.fail_json(msg='Error searching for consistency group %s: %s' % (self.parameters['consistency_group'], to_native(error)),
+ exception=traceback.format_exc())
+ if record:
+ self.cg_uuid = record.get('uuid')
+
+ if self.parameters.get('volumes') is not None:
+ query['fields'] += 'volumes.name,'
+ records, error = rest_generic.get_0_or_more_records(self.rest_api, api, query)
+ if error:
+ self.module.fail_json(msg='Error searching for consistency group having volumes %s: %s' % (self.parameters['volumes'], to_native(error)),
+ exception=traceback.format_exc())
+ if records:
+ for record in records:
+ if record.get('volumes') is not None:
+ cg_volumes = [vol_item['name'] for vol_item in record['volumes']]
+ if cg_volumes == self.parameters['volumes']:
+ self.cg_uuid = record.get('uuid')
+ break
+ return None
+
+ def get_cg_snapshot_rest(self):
+ """
+ Retrieve CG snapshots using fetched CG uuid
+ """
+ self.get_cg_rest()
+ if self.cg_uuid is None:
+ if self.parameters.get('consistency_group') is not None:
+ self.module.fail_json(msg="Consistency group named '%s' not found" % self.parameters.get('consistency_group'))
+ if self.parameters.get('volumes') is not None:
+ self.module.fail_json(msg="Consistency group having volumes '%s' not found" % self.parameters.get('volumes'))
+
+ api = '/application/consistency-groups/%s/snapshots' % self.cg_uuid
+ query = {'name': self.parameters['snapshot'],
+ 'fields': 'name,'
+ 'uuid,'
+ 'consistency_group,'
+ 'snapmirror_label,'
+ 'comment,'}
+ record, error = rest_generic.get_one_record(self.rest_api, api, query)
+ if error:
+ self.module.fail_json(msg='Error searching for consistency group snapshot %s: %s' % (self.parameters['snapshot'], to_native(error)),
+ exception=traceback.format_exc())
+ if record:
+ return {
+ 'snapshot': record.get('name'),
+ 'snapshot_uuid': record.get('uuid'),
+ 'consistency_group': self.na_helper.safe_get(record, ['consistency_group', 'name']),
+ 'snapmirror_label': record.get('snapmirror_label'),
+ 'comment': record.get('comment'),
+ }
+ return None
+
+ def create_cg_snapshot_rest(self):
+ """Create CG snapshot"""
+ api = '/application/consistency-groups/%s/snapshots' % self.cg_uuid
+ body = {'name': self.parameters['snapshot']}
+ if self.parameters.get('snapmirror_label'):
+ body['snapmirror_label'] = self.parameters['snapmirror_label']
+ if self.parameters.get('comment'):
+ body['comment'] = self.parameters['comment']
+ dummy, error = rest_generic.post_async(self.rest_api, api, body)
+ if error:
+ self.module.fail_json(msg='Error creating consistency group snapshot %s: %s' % (self.parameters['snapshot'], to_native(error)),
+ exception=traceback.format_exc())
+
+ def delete_cg_snapshot_rest(self, current):
+ """Delete CG snapshot"""
+ api = '/application/consistency-groups/%s/snapshots' % self.cg_uuid
+ dummy, error = rest_generic.delete_async(self.rest_api, api, current['snapshot_uuid'])
+ if error:
+ self.module.fail_json(msg='Error deleting consistency group snapshot %s: %s' % (self.parameters['snapshot'], to_native(error)),
exception=traceback.format_exc())
def apply(self):
- '''Applies action from playbook'''
- if not self.module.check_mode:
- changed = self.cgcreate()
- self.module.exit_json(changed=changed)
+ """Applies action from playbook"""
+ if not self.use_rest:
+ if not self.module.check_mode:
+ changed = self.cgcreate()
+ self.module.exit_json(changed=changed)
+ current = self.get_cg_snapshot_rest()
+ cd_action = self.na_helper.get_cd_action(current, self.parameters)
+
+ if self.na_helper.changed and not self.module.check_mode:
+ if cd_action == 'create':
+ self.create_cg_snapshot_rest()
+ elif cd_action == 'delete':
+ self.delete_cg_snapshot_rest(current)
+ result = netapp_utils.generate_result(self.na_helper.changed, cd_action)
+ self.module.exit_json(**result)
def main():
- '''Execute action from playbook'''
+ """Execute action from playbook"""
cg_obj = NetAppONTAPCGSnapshot()
cg_obj.apply()
diff --git a/ansible_collections/netapp/ontap/plugins/modules/na_ontap_cifs_server.py b/ansible_collections/netapp/ontap/plugins/modules/na_ontap_cifs_server.py
index 8a65dd6c5..08a69c1f7 100644
--- a/ansible_collections/netapp/ontap/plugins/modules/na_ontap_cifs_server.py
+++ b/ansible_collections/netapp/ontap/plugins/modules/na_ontap_cifs_server.py
@@ -1,7 +1,7 @@
#!/usr/bin/python
""" this is cifs_server module
- (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)
"""
@@ -70,6 +70,13 @@ options:
version_added: 2.7.0
type: str
+ default_site:
+ description:
+ - Specifies the site within the Active Directory domain to associate with the CIFS server if Data ONTAP cannot determine an appropriate site.
+ - Only supported with REST and requires ontap version 9.13.1 or later.
+ version_added: 22.8.0
+ type: str
+
force:
type: bool
description:
@@ -175,6 +182,21 @@ options:
type: str
version_added: 21.20.0
+ lm_compatibility_level:
+ description:
+ - Specifies CIFS server minimum security level, also known as the LMCompatibilityLevel.
+ - Only supported with REST and requires ontap version 9.8 or later. Use na_ontap_vserver_cifs_security with ZAPI.
+ choices: ['lm_ntlm_ntlmv2_krb', 'ntlm_ntlmv2_krb', 'ntlmv2_krb', 'krb']
+ type: str
+ version_added: 22.9.0
+
+ is_multichannel_enabled:
+ description:
+ - Specifies whether the CIFS server supports Multichannel or not.
+ - Only supported with REST and requires ontap version 9.10 or later.
+ type: bool
+ version_added: 22.10.0
+
'''
EXAMPLES = '''
@@ -239,16 +261,16 @@ EXAMPLES = '''
name: data2
vserver: svm1
service_state: stopped
- encrypt_dc_connection: True,
- smb_encryption: True,
- kdc_encryption: True,
- smb_signing: True,
- aes_netlogon_enabled: True,
- ldap_referral_enabled: True,
- session_security: seal,
- try_ldap_channel_binding: False,
- use_ldaps: True,
- use_start_tls": True
+ encrypt_dc_connection: True
+ smb_encryption: True
+ kdc_encryption: True
+ smb_signing: True
+ aes_netlogon_enabled: True
+ ldap_referral_enabled: True
+ session_security: seal
+ try_ldap_channel_binding: False
+ use_ldaps: True
+ use_start_tls: True
restrict_anonymous: no_access
domain: "{{ id_domain }}"
admin_user_name: "{{ domain_login }}"
@@ -289,6 +311,7 @@ class NetAppOntapcifsServer:
admin_user_name=dict(required=False, type='str'),
admin_password=dict(required=False, type='str', no_log=True),
ou=dict(required=False, type='str'),
+ default_site=dict(required=False, type='str'),
force=dict(required=False, type='bool'),
vserver=dict(required=True, type='str'),
from_name=dict(required=False, type='str'),
@@ -300,9 +323,11 @@ class NetAppOntapcifsServer:
aes_netlogon_enabled=dict(required=False, type='bool'),
ldap_referral_enabled=dict(required=False, type='bool'),
session_security=dict(required=False, type='str', choices=['none', 'sign', 'seal']),
+ lm_compatibility_level=dict(required=False, type='str', choices=['lm_ntlm_ntlmv2_krb', 'ntlm_ntlmv2_krb', 'ntlmv2_krb', 'krb']),
try_ldap_channel_binding=dict(required=False, type='bool'),
use_ldaps=dict(required=False, type='bool'),
- use_start_tls=dict(required=False, type='bool')
+ use_start_tls=dict(required=False, type='bool'),
+ is_multichannel_enabled=dict(required=False, type='bool'),
))
self.module = AnsibleModule(
@@ -318,15 +343,16 @@ class NetAppOntapcifsServer:
# Set up Rest API
self.rest_api = OntapRestAPI(self.module)
unsupported_rest_properties = ['workgroup']
- partially_supported_rest_properties = [['encrypt_dc_connection', (9, 8)], ['aes_netlogon_enabled', (9, 10, 1)], ['ldap_referral_enabled', (9, 10, 1)],
- ['session_security', (9, 10, 1)], ['try_ldap_channel_binding', (9, 10, 1)], ['use_ldaps', (9, 10, 1)],
- ['use_start_tls', (9, 10, 1)], ['force', (9, 11)]]
+ partially_supported_rest_properties = [['encrypt_dc_connection', (9, 8)], ['lm_compatibility_level', (9, 8)],
+ ['aes_netlogon_enabled', (9, 10, 1)], ['ldap_referral_enabled', (9, 10, 1)], ['session_security', (9, 10, 1)],
+ ['try_ldap_channel_binding', (9, 10, 1)], ['use_ldaps', (9, 10, 1)], ['use_start_tls', (9, 10, 1)],
+ ['is_multichannel_enabled', (9, 10, 1)], ['force', (9, 11)], ['default_site', (9, 13, 1)]]
self.use_rest = self.rest_api.is_rest_supported_properties(self.parameters, unsupported_rest_properties, partially_supported_rest_properties)
if not self.use_rest:
unsupported_zapi_properties = ['smb_signing', 'encrypt_dc_connection', 'kdc_encryption', 'smb_encryption', 'restrict_anonymous',
'aes_netlogon_enabled', 'ldap_referral_enabled', 'try_ldap_channel_binding', 'session_security',
- 'use_ldaps', 'use_start_tls', 'from_name']
+ 'lm_compatibility_level', 'use_ldaps', 'use_start_tls', 'from_name', 'default_site', 'is_multichannel_enabled']
used_unsupported_zapi_properties = [option for option in unsupported_zapi_properties if option in self.parameters]
if used_unsupported_zapi_properties:
self.module.fail_json(msg="Error: %s options supported only with REST." % " ,".join(used_unsupported_zapi_properties))
@@ -460,7 +486,9 @@ class NetAppOntapcifsServer:
query['name'] = from_name or self.parameters['cifs_server_name']
api = 'protocols/cifs/services'
if self.rest_api.meets_rest_minimum_version(self.use_rest, 9, 8):
- query['fields'] += 'security.encrypt_dc_connection,'
+ security_option_9_8 = ('security.encrypt_dc_connection,'
+ 'security.lm_compatibility_level,')
+ query['fields'] += security_option_9_8
if self.rest_api.meets_rest_minimum_version(self.use_rest, 9, 10, 1):
security_option_9_10 = ('security.use_ldaps,'
@@ -470,6 +498,10 @@ class NetAppOntapcifsServer:
'security.ldap_referral_enabled,'
'security.aes_netlogon_enabled,')
query['fields'] += security_option_9_10
+
+ if self.rest_api.meets_rest_minimum_version(self.use_rest, 9, 10, 1):
+ service_option_9_10 = ('options.multichannel,')
+ query['fields'] += service_option_9_10
record, error = rest_generic.get_one_record(self.rest_api, api, query)
if error:
self.module.fail_json(msg="Error on fetching cifs: %s" % error)
@@ -486,10 +518,12 @@ class NetAppOntapcifsServer:
'aes_netlogon_enabled': self.na_helper.safe_get(record, ['security', 'aes_netlogon_enabled']),
'ldap_referral_enabled': self.na_helper.safe_get(record, ['security', 'ldap_referral_enabled']),
'session_security': self.na_helper.safe_get(record, ['security', 'session_security']),
+ 'lm_compatibility_level': self.na_helper.safe_get(record, ['security', 'lm_compatibility_level']),
'try_ldap_channel_binding': self.na_helper.safe_get(record, ['security', 'try_ldap_channel_binding']),
'use_ldaps': self.na_helper.safe_get(record, ['security', 'use_ldaps']),
'use_start_tls': self.na_helper.safe_get(record, ['security', 'use_start_tls']),
- 'restrict_anonymous': self.na_helper.safe_get(record, ['security', 'restrict_anonymous'])
+ 'restrict_anonymous': self.na_helper.safe_get(record, ['security', 'restrict_anonymous']),
+ 'is_multichannel_enabled': self.na_helper.safe_get(record, ['options', 'multichannel']),
}
return record
@@ -503,17 +537,20 @@ class NetAppOntapcifsServer:
ad_domain['organizational_unit'] = self.parameters['ou']
if 'domain' in self.parameters:
ad_domain['fqdn'] = self.parameters['domain']
+ if 'default_site' in self.parameters:
+ ad_domain['default_site'] = self.parameters['default_site']
return ad_domain
def create_modify_body_rest(self, params=None):
"""
Function to define body for create and modify cifs server
"""
- body, query, security = {}, {}, {}
+ body, query, security, service_options = {}, {}, {}, {}
if params is None:
params = self.parameters
security_options = ['smb_signing', 'encrypt_dc_connection', 'kdc_encryption', 'smb_encryption', 'restrict_anonymous',
- 'aes_netlogon_enabled', 'ldap_referral_enabled', 'try_ldap_channel_binding', 'session_security', 'use_ldaps', 'use_start_tls']
+ 'aes_netlogon_enabled', 'ldap_referral_enabled', 'try_ldap_channel_binding', 'session_security',
+ 'lm_compatibility_level', 'use_ldaps', 'use_start_tls']
ad_domain = self.build_ad_domain()
if ad_domain:
body['ad_domain'] = ad_domain
@@ -524,6 +561,14 @@ class NetAppOntapcifsServer:
security[key] = params[key]
if security:
body['security'] = security
+ # for parameters having different key names in REST API and module inputs
+ for key, option in [
+ ('multichannel', 'is_multichannel_enabled'),
+ ]:
+ if option in params:
+ service_options.update({key: params[option]})
+ if service_options:
+ body['options'] = service_options
if 'vserver' in params:
body['svm.name'] = params['vserver']
if 'cifs_server_name' in params:
diff --git a/ansible_collections/netapp/ontap/plugins/modules/na_ontap_cifs_unix_symlink_mapping.py b/ansible_collections/netapp/ontap/plugins/modules/na_ontap_cifs_unix_symlink_mapping.py
new file mode 100644
index 000000000..f8f1bfacb
--- /dev/null
+++ b/ansible_collections/netapp/ontap/plugins/modules/na_ontap_cifs_unix_symlink_mapping.py
@@ -0,0 +1,289 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: 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
+__metaclass__ = type
+
+DOCUMENTATION = """
+module: na_ontap_cifs_unix_symlink_mapping
+short_description: NetApp ONTAP module to manage UNIX symbolic link mapping for CIFS clients.
+extends_documentation_fragment:
+ - netapp.ontap.netapp.na_ontap
+version_added: '22.9.0'
+author: NetApp Ansible Team (@carchi8py) <ng-ansibleteam@netapp.com>
+description:
+ - Create/ modify/ delete a UNIX symbolic link mapping for a CIFS client.
+options:
+ state:
+ description:
+ - Whether the specified symlink mapping should exist or not.
+ choices: ['present', 'absent']
+ type: str
+ default: present
+
+ vserver:
+ description:
+ - Name of the vserver to use.
+ type: str
+ required: true
+
+ unix_path:
+ description:
+ - Specifies the UNIX path prefix to be matched for the mapping.
+ - It must begin and end with a forward slash (/).
+ type: str
+ required: true
+
+ share_name:
+ description:
+ - Specifies the CIFS share name on the destination CIFS server to which the UNIX symbolic link is pointing.
+ type: str
+
+ cifs_server:
+ description:
+ - Specifies the destination CIFS server (DNS name, IP address, or NetBIOS name).
+ - This field is mandatory if the locality of the symbolic link is 'widelink'.
+ type: str
+
+ cifs_path:
+ description:
+ - Specifies the CIFS path on the destination to which the symbolic link maps.
+ - Note that this value is specified by using a UNIX-style path. It must begin and end with a forward slash (/).
+ type: str
+
+ locality:
+ description:
+ - Specifies whether the CIFS symbolic link is a local link or wide link. The default setting is local.
+ - The following values are supported
+ local - Local symbolic link maps only to the same CIFS share.
+ widelink - Wide symbolic link maps to any CIFS share on the network.
+ type: str
+ choices: ['local', 'widelink']
+ default: 'local'
+
+ home_directory:
+ description:
+ - Specify if the destination share is a home directory. The default value is false.
+ type: bool
+ default: False
+
+notes:
+ - Only supported with REST and requires ONTAP 9.6 or later.
+
+"""
+
+EXAMPLES = """
+ - name: Create a UNIX symlink mapping for CIFS share
+ netapp.ontap.na_ontap_cifs_unix_symlink_mapping:
+ state: present
+ vserver: "{{ svm }}"
+ unix_path: "/example1/"
+ share_name: "share1"
+ cifs_path: "/path1/test_dir/"
+ cifs_server: "CIFS"
+ hostname: "{{ netapp_hostname }}"
+ username: "{{ netapp_username }}"
+ password: "{{ netapp_password }}"
+ https: true
+ validate_certs: "{{ validate_certs }}"
+
+ - name: Update a specific UNIX symlink mapping for a SVM
+ netapp.ontap.na_ontap_cifs_unix_symlink_mapping:
+ state: present
+ vserver: "{{ svm }}"
+ unix_path: "/example1/"
+ share_name: "share2"
+ cifs_path: "/path2/test_dir/"
+ cifs_server: "CIFS"
+ locality: "widelink"
+ hostname: "{{ netapp_hostname }}"
+ username: "{{ netapp_username }}"
+ password: "{{ netapp_password }}"
+ https: true
+ validate_certs: "{{ validate_certs }}"
+
+ - name: Remove a specific UNIX symlink mapping for a SVM
+ netapp.ontap.na_ontap_cifs_unix_symlink_mapping:
+ state: absent
+ vserver: "{{ svm }}"
+ unix_path: "/example1/"
+ hostname: "{{ netapp_hostname }}"
+ username: "{{ netapp_username }}"
+ password: "{{ netapp_password }}"
+ https: true
+ validate_certs: "{{ validate_certs }}"
+
+"""
+
+RETURN = """
+"""
+
+import traceback
+from ansible.module_utils.basic import AnsibleModule
+from ansible.module_utils._text import to_native
+import ansible_collections.netapp.ontap.plugins.module_utils.netapp as netapp_utils
+from ansible_collections.netapp.ontap.plugins.module_utils.netapp_module import NetAppModule
+from ansible_collections.netapp.ontap.plugins.module_utils import rest_generic
+
+
+class NetAppOntapCifsUnixSymlink:
+ def __init__(self):
+ self.argument_spec = netapp_utils.na_ontap_host_argument_spec()
+ self.argument_spec.update(dict(
+ state=dict(required=False, type='str', choices=['present', 'absent'], default='present'),
+ vserver=dict(required=True, type='str'),
+ unix_path=dict(required=True, type='str'),
+ share_name=dict(required=False, type='str'),
+ cifs_path=dict(required=False, type='str'),
+ cifs_server=dict(required=False, type='str'),
+ locality=dict(required=False, type='str', choices=['local', 'widelink'], default='local'),
+ home_directory=dict(required=False, type='bool', default=False)
+ ))
+ self.module = AnsibleModule(
+ argument_spec=self.argument_spec,
+ required_if=[
+ ('state', 'present', ['share_name', 'cifs_path']),
+ ('locality', 'widelink', ['cifs_server']),
+ ],
+ supports_check_mode=True
+ )
+ self.svm_uuid = None
+ self.na_helper = NetAppModule(self.module)
+ self.parameters = self.na_helper.check_and_set_parameters(self.module)
+ self.rest_api = netapp_utils.OntapRestAPI(self.module)
+ self.rest_api.fail_if_not_rest_minimum_version('na_ontap_cifs_unix_symlink_mapping:', 9, 6)
+
+ @staticmethod
+ def validate_path(path):
+ if not path.startswith('/'):
+ path = "/%s" % path
+ if not path.endswith('/'):
+ path = "%s/" % path
+ return path
+
+ @staticmethod
+ def encode_path(path):
+ return path.replace('/', '%2F')
+
+ def get_symlink_mapping_rest(self):
+ """
+ Retrieves a specific UNIX symbolink mapping for a SVM
+ """
+ api = 'protocols/cifs/unix-symlink-mapping'
+ query = {'svm.name': self.parameters.get('vserver'),
+ 'unix_path': self.parameters['unix_path'],
+ 'fields': 'svm.uuid,'
+ 'unix_path,'
+ 'target.share,'
+ 'target.path,'}
+ if self.parameters.get('cifs_server') is not None:
+ query['fields'] += 'target.server,'
+ if self.parameters.get('locality') is not None:
+ query['fields'] += 'target.locality,'
+ if self.parameters.get('home_directory') is not None:
+ query['fields'] += 'target.home_directory,'
+
+ record, error = rest_generic.get_one_record(self.rest_api, api, query)
+ if error:
+ self.module.fail_json(msg='Error while fetching cifs unix symlink mapping: %s' % to_native(error),
+ exception=traceback.format_exc())
+ if record:
+ self.svm_uuid = self.na_helper.safe_get(record, ['svm', 'uuid'])
+ return self.format_record(record)
+ return None
+
+ def format_record(self, record):
+ return {
+ 'unix_path': record.get('unix_path'),
+ 'share_name': self.na_helper.safe_get(record, ['target', 'share']),
+ 'cifs_path': self.na_helper.safe_get(record, ['target', 'path']),
+ 'cifs_server': self.na_helper.safe_get(record, ['target', 'server']),
+ 'locality': self.na_helper.safe_get(record, ['target', 'locality']),
+ 'home_directory': self.na_helper.safe_get(record, ['target', 'home_directory'])
+ }
+
+ def create_symlink_mapping_rest(self):
+ """
+ Creates a UNIX symbolink mapping for CIFS share
+ """
+ api = 'protocols/cifs/unix-symlink-mapping'
+ body = {
+ 'svm.name': self.parameters['vserver'],
+ 'unix_path': self.parameters['unix_path'],
+ 'target': {
+ 'share': self.parameters['share_name'],
+ 'path': self.parameters['cifs_path']
+ }
+ }
+ if 'cifs_server' in self.parameters:
+ body['target.server'] = self.parameters['cifs_server']
+ if 'locality' in self.parameters:
+ body['target.locality'] = self.parameters['locality']
+ if 'home_directory' in self.parameters:
+ body['target.home_directory'] = self.parameters['home_directory']
+
+ dummy, error = rest_generic.post_async(self.rest_api, api, body)
+ if error is not None:
+ self.module.fail_json(msg='Error while creating cifs unix symlink mapping: %s' % to_native(error),
+ exception=traceback.format_exc())
+
+ def modify_symlink_mapping_rest(self, modify):
+ """
+ Updates a specific UNIX symbolink mapping for a SVM
+ """
+ api = 'protocols/cifs/unix-symlink-mapping/%s/%s' % (self.svm_uuid, self.encode_path(self.parameters['unix_path']))
+ body = {'target': {}}
+ for key, option in [
+ ('share', 'share_name'),
+ ('path', 'cifs_path'),
+ ('server', 'cifs_server'),
+ ('locality', 'locality'),
+ ('home_directory', 'home_directory'),
+ ]:
+ if modify.get(option) is not None:
+ body['target'][key] = modify[option]
+
+ dummy, error = rest_generic.patch_async(self.rest_api, api, uuid_or_name=None, body=body)
+ if error:
+ self.module.fail_json(msg='Error while modifying cifs unix symlink mapping: %s.' % to_native(error),
+ exception=traceback.format_exc())
+
+ def delete_symlink_mapping_rest(self):
+ """
+ Removes a specific UNIX symbolink mapping for a SVM
+ """
+ api = 'protocols/cifs/unix-symlink-mapping/%s/%s' % (self.svm_uuid, self.encode_path(self.parameters['unix_path']))
+ dummy, error = rest_generic.delete_async(self.rest_api, api, uuid=None)
+ if error is not None:
+ self.module.fail_json(msg='Error while deleting cifs unix symlink mapping: %s' % to_native(error))
+
+ def apply(self):
+ # validate leading and trailing forward slashes in unix_path & cifs_path
+ for option in ['unix_path', 'cifs_path']:
+ if self.parameters.get(option) is not None:
+ self.parameters[option] = self.validate_path(self.parameters[option])
+
+ current = self.get_symlink_mapping_rest()
+ cd_action = self.na_helper.get_cd_action(current, self.parameters)
+ modify = self.na_helper.get_modified_attributes(current, self.parameters)
+ if self.na_helper.changed and not self.module.check_mode:
+ if cd_action == 'create':
+ self.create_symlink_mapping_rest()
+ elif cd_action == 'delete':
+ self.delete_symlink_mapping_rest()
+ elif modify:
+ self.modify_symlink_mapping_rest(modify)
+ result = netapp_utils.generate_result(self.na_helper.changed, cd_action, modify)
+ self.module.exit_json(**result)
+
+
+def main():
+ symlink_mapping = NetAppOntapCifsUnixSymlink()
+ symlink_mapping.apply()
+
+
+if __name__ == '__main__':
+ main()
diff --git a/ansible_collections/netapp/ontap/plugins/modules/na_ontap_cli_timeout.py b/ansible_collections/netapp/ontap/plugins/modules/na_ontap_cli_timeout.py
new file mode 100644
index 000000000..02ff00a32
--- /dev/null
+++ b/ansible_collections/netapp/ontap/plugins/modules/na_ontap_cli_timeout.py
@@ -0,0 +1,123 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: 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
+__metaclass__ = type
+
+DOCUMENTATION = """
+module: na_ontap_cli_timeout
+short_description: NetApp ONTAP module to set the CLI inactivity timeout value.
+extends_documentation_fragment:
+ - netapp.ontap.netapp.na_ontap
+version_added: '22.9.0'
+author: NetApp Ansible Team (@carchi8py) <ng-ansibleteam@netapp.com>
+description:
+ - Modify the timeout value for CLI sessions.
+options:
+ state:
+ description:
+ - Modify timeout value, only present is supported.
+ choices: ['present']
+ type: str
+ default: present
+ timeout:
+ description:
+ - Specifies the timeout value, in minutes.
+ - To prevent CLI sessions from timing out, specify a value of 0 (zero).
+ type: int
+ required: true
+
+notes:
+ - Only supported with REST and requires ONTAP 9.6 or later.
+"""
+
+EXAMPLES = """
+ - name: Modify the timeout value for CLI sessions to be 15 minutes
+ netapp.ontap.na_ontap_cli_timeout:
+ state: present
+ timeout: 15
+ hostname: "{{ netapp_hostname }}"
+ username: "{{ netapp_username }}"
+ password: "{{ netapp_password }}"
+ https: true
+ validate_certs: "{{ validate_certs }}"
+
+ - name: Prevent CLI sessions from timing out
+ netapp.ontap.na_ontap_cli_timeout:
+ state: present
+ timeout: 0
+ hostname: "{{ netapp_hostname }}"
+ username: "{{ netapp_username }}"
+ password: "{{ netapp_password }}"
+ https: true
+ validate_certs: "{{ validate_certs }}"
+"""
+
+RETURN = """
+"""
+
+import traceback
+from ansible.module_utils.basic import AnsibleModule
+from ansible.module_utils._text import to_native
+import ansible_collections.netapp.ontap.plugins.module_utils.netapp as netapp_utils
+from ansible_collections.netapp.ontap.plugins.module_utils.netapp_module import NetAppModule
+from ansible_collections.netapp.ontap.plugins.module_utils import rest_generic
+
+
+class NetAppOntapCliTimeout:
+ def __init__(self):
+ self.argument_spec = netapp_utils.na_ontap_host_argument_spec()
+ self.argument_spec.update(dict(
+ state=dict(required=False, type='str', choices=['present'], default='present'),
+ timeout=dict(required=True, type='int')
+ ))
+ self.module = AnsibleModule(
+ argument_spec=self.argument_spec,
+ supports_check_mode=True
+ )
+ self.na_helper = NetAppModule(self.module)
+ self.parameters = self.na_helper.check_and_set_parameters(self.module)
+ self.rest_api = netapp_utils.OntapRestAPI(self.module)
+ self.rest_api.fail_if_not_rest_minimum_version('na_ontap_cli_timeout:', 9, 6)
+
+ def get_timeout_value_rest(self):
+ """ Get CLI inactivity timeout value """
+ fields = 'timeout'
+ api = 'private/cli/system/timeout'
+ record, error = rest_generic.get_one_record(self.rest_api, api, query=None, fields=fields)
+ if error:
+ self.module.fail_json(msg="Error fetching CLI sessions timeout value: %s" % to_native(error),
+ exception=traceback.format_exc())
+ if record:
+ return {
+ 'timeout': record.get('timeout')
+ }
+ return None
+
+ def modify_timeout_value_rest(self, modify):
+ """ Modify CLI inactivity timeout value """
+ api = 'private/cli/system/timeout'
+ dummy, error = rest_generic.patch_async(self.rest_api, api, uuid_or_name=None, body=modify)
+ if error:
+ self.module.fail_json(msg='Error modifying CLI sessions timeout value: %s.' % to_native(error),
+ exception=traceback.format_exc())
+
+ def apply(self):
+ current = self.get_timeout_value_rest()
+ modify = self.na_helper.get_modified_attributes(current, self.parameters)
+ if self.na_helper.changed and not self.module.check_mode:
+ self.modify_timeout_value_rest(modify)
+ result = netapp_utils.generate_result(self.na_helper.changed, modify=modify)
+ self.module.exit_json(**result)
+
+
+def main():
+ cli_timeout = NetAppOntapCliTimeout()
+ cli_timeout.apply()
+
+
+if __name__ == '__main__':
+ main()
diff --git a/ansible_collections/netapp/ontap/plugins/modules/na_ontap_cluster.py b/ansible_collections/netapp/ontap/plugins/modules/na_ontap_cluster.py
index fb0f507fc..bac9fd261 100644
--- a/ansible_collections/netapp/ontap/plugins/modules/na_ontap_cluster.py
+++ b/ansible_collections/netapp/ontap/plugins/modules/na_ontap_cluster.py
@@ -1,6 +1,6 @@
#!/usr/bin/python
-# (c) 2017-2022, NetApp, Inc
+# (c) 2017-2023, NetApp, Inc
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
'''
@@ -94,7 +94,17 @@ options:
- A system-specific or other term not associated with a geographic region or GMT
- "full list of supported alias can be found here: https://library.netapp.com/ecmdocs/ECMP1155590/html/GUID-D3B8A525-67A2-4BEE-99DB-EF52D6744B5F.html"
- Only supported by REST
-
+ certificate:
+ description:
+ - Certificate used by cluster and node management interfaces for TLS connection requests.
+ - Only supported with REST and requires ONTAP 9.10 or later.
+ type: dict
+ version_added: 22.9.0
+ suboptions:
+ uuid:
+ type: str
+ description:
+ - Certificate UUID.
notes:
- supports REST and ZAPI
'''
@@ -108,6 +118,7 @@ EXAMPLES = """
hostname: "{{ netapp_hostname }}"
username: "{{ netapp_username }}"
password: "{{ netapp_password }}"
+
- name: Add node to cluster (Join cluster)
netapp.ontap.na_ontap_cluster:
state: present
@@ -115,6 +126,7 @@ EXAMPLES = """
hostname: "{{ netapp_hostname }}"
username: "{{ netapp_username }}"
password: "{{ netapp_password }}"
+
- name: Add node to cluster (Join cluster)
netapp.ontap.na_ontap_cluster:
state: present
@@ -123,6 +135,7 @@ EXAMPLES = """
hostname: "{{ netapp_hostname }}"
username: "{{ netapp_username }}"
password: "{{ netapp_password }}"
+
- name: Create a 2 node cluster in one call
netapp.ontap.na_ontap_cluster:
state: present
@@ -131,6 +144,7 @@ EXAMPLES = """
hostname: "{{ netapp_hostname }}"
username: "{{ netapp_username }}"
password: "{{ netapp_password }}"
+
- name: Remove node from cluster
netapp.ontap.na_ontap_cluster:
state: absent
@@ -138,6 +152,7 @@ EXAMPLES = """
hostname: "{{ netapp_hostname }}"
username: "{{ netapp_username }}"
password: "{{ netapp_password }}"
+
- name: Remove node from cluster
netapp.ontap.na_ontap_cluster:
state: absent
@@ -145,6 +160,7 @@ EXAMPLES = """
hostname: "{{ netapp_hostname }}"
username: "{{ netapp_username }}"
password: "{{ netapp_password }}"
+
- name: modify cluster
netapp.ontap.na_ontap_cluster:
state: present
@@ -154,6 +170,19 @@ EXAMPLES = """
hostname: "{{ netapp_hostname }}"
username: "{{ netapp_username }}"
password: "{{ netapp_password }}"
+
+ - name: updating the cluster-wide web services configuration
+ netapp.ontap.na_ontap_cluster:
+ state: present
+ cluster_contact: testing
+ cluster_location: testing
+ certificate:
+ uuid: 7f2f332c-933e-11ee-ab1c-005056b397ff
+ cluster_name: "{{ netapp_cluster}}"
+ hostname: "{{ netapp_hostname }}"
+ username: "{{ netapp_username }}"
+ password: "{{ netapp_password }}"
+
"""
RETURN = """
@@ -182,6 +211,9 @@ class NetAppONTAPCluster:
cluster_ip_address=dict(required=False, type='str'),
cluster_location=dict(required=False, type='str'),
cluster_contact=dict(required=False, type='str'),
+ certificate=dict(required=False, type='dict', options=dict(
+ uuid=dict(required=False, type='str')
+ )),
force=dict(required=False, type='bool', default=False),
single_node_cluster=dict(required=False, type='bool'),
node_name=dict(required=False, type='str'),
@@ -202,6 +234,10 @@ class NetAppONTAPCluster:
# cached, so that we don't call the REST API more than once
self.node_records = None
+ self.rest_api = OntapRestAPI(self.module)
+ partially_supported_rest_properties = [['certificate', (9, 10, 1)]]
+ self.use_rest = self.rest_api.is_rest_supported_properties(self.parameters, None, partially_supported_rest_properties)
+
if self.parameters['state'] == 'absent' and self.parameters.get('node_name') is not None and self.parameters.get('cluster_ip_address') is not None:
msg = 'when state is "absent", parameters are mutually exclusive: cluster_ip_address|node_name'
self.module.fail_json(msg=msg)
@@ -209,14 +245,14 @@ class NetAppONTAPCluster:
if self.parameters.get('node_name') is not None and '-' in self.parameters.get('node_name'):
self.warnings.append('ONTAP ZAPI converts "-" to "_", node_name: %s may be changed or not matched' % self.parameters.get('node_name'))
- self.rest_api = OntapRestAPI(self.module)
- self.use_rest = self.rest_api.is_rest()
if self.use_rest and self.parameters['state'] == 'absent' and not self.rest_api.meets_rest_minimum_version(True, 9, 7, 0):
self.module.warn('switching back to ZAPI as DELETE is not supported on 9.6')
self.use_rest = False
if not self.use_rest:
if self.na_helper.safe_get(self.parameters, ['timezone', 'name']):
self.module.fail_json(msg='Timezone is only supported with REST')
+ if self.na_helper.safe_get(self.parameters, ['certificate', 'uuid']):
+ self.module.fail_json(msg='Certificate is only supported with REST')
if not netapp_utils.has_netapp_lib():
self.module.fail_json(msg="the python NetApp-Lib module is required")
self.server = netapp_utils.setup_na_ontap_zapi(module=self.module)
@@ -235,12 +271,17 @@ class NetAppONTAPCluster:
self.module.fail_json(msg='Error fetching cluster identity info: %s' % to_native(error),
exception=traceback.format_exc())
if record:
- return {
+ cluster_info = {
'cluster_contact': record.get('contact'),
'cluster_location': record.get('location'),
'cluster_name': record.get('name'),
'timezone': self.na_helper.safe_get(record, ['timezone'])
}
+ if self.parameters.get('certificate') is not None:
+ web_service_record = self.get_web_services()
+ cluster_info.update(web_service_record)
+ if cluster_info:
+ return cluster_info
return None
def get_cluster_identity(self, ignore_error=True):
@@ -526,6 +567,27 @@ class NetAppONTAPCluster:
exception=traceback.format_exc())
return uuid, from_node
+ def get_web_services(self):
+ record, error = rest_generic.get_one_record(self.rest_api, 'cluster/web', fields='certificate')
+ if error:
+ self.module.fail_json(msg='Error fetching cluster web service config: %s' % to_native(error),
+ exception=traceback.format_exc())
+ if record:
+ return record
+ return None
+
+ def modify_web_services(self):
+ body = {
+ 'certificate': {
+ 'uuid': self.parameters['certificate']['uuid']
+ }
+ }
+ dummy, error = rest_generic.patch_async(self.rest_api, 'cluster/web', None, body)
+ if error:
+ self.module.fail_json(msg='Error modifying cluster web service config for %s: %s'
+ % (self.parameters['cluster_name'], to_native(error)),
+ exception=traceback.format_exc())
+
def remove_node_rest(self):
"""
Remove a node from an existing cluster
@@ -570,10 +632,12 @@ class NetAppONTAPCluster:
"""
Modifies the cluster identity
"""
+ if 'certificate' in modify:
+ self.modify_web_services()
body = self.create_cluster_body(modify)
dummy, error = rest_generic.patch_async(self.rest_api, 'cluster', None, body)
if error:
- self.module.fail_json(msg='Error modifying cluster idetity details %s: %s'
+ self.module.fail_json(msg='Error modifying cluster identity details %s: %s'
% (self.parameters['cluster_name'], to_native(error)),
exception=traceback.format_exc())
diff --git a/ansible_collections/netapp/ontap/plugins/modules/na_ontap_cluster_peer.py b/ansible_collections/netapp/ontap/plugins/modules/na_ontap_cluster_peer.py
index 820001cc4..92c2c419d 100644
--- a/ansible_collections/netapp/ontap/plugins/modules/na_ontap_cluster_peer.py
+++ b/ansible_collections/netapp/ontap/plugins/modules/na_ontap_cluster_peer.py
@@ -1,6 +1,6 @@
#!/usr/bin/python
-# (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)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
@@ -9,6 +9,7 @@ DOCUMENTATION = '''
author: NetApp Ansible Team (@carchi8py) <ng-ansibleteam@netapp.com>
description:
- Create/Delete cluster peer relations on ONTAP
+ - Modify remote intercluster addresses in cluster peer relation on ONTAP
extends_documentation_fragment:
- netapp.ontap.netapp.na_ontap
- netapp.ontap.netapp.na_ontap_peer
@@ -46,12 +47,13 @@ options:
type: str
source_cluster_name:
description:
- - The name of the source cluster name in the peer relation to be deleted.
+ - The name of the source cluster name in the peer relation to be modified or deleted.
+ - Required for deleting peer relation and for modifying source_intercluster_lifs.
type: str
dest_cluster_name:
description:
- - The name of the destination cluster name in the peer relation to be deleted.
- - Required for delete
+ - The name of the destination cluster name in the peer relation to be modified or deleted.
+ - Required for deleting peer relation and for modifying dest_intercluster_lifs.
type: str
dest_hostname:
description:
@@ -86,6 +88,9 @@ options:
version_added: '20.5.0'
short_description: NetApp ONTAP Manage Cluster peering
version_added: 2.7.0
+
+notes:
+ - Modify remote intercluster addresses operation is supported only with REST.
'''
EXAMPLES = """
@@ -129,6 +134,18 @@ EXAMPLES = """
key_filepath: "{{ key_filepath }}"
encryption_protocol_proposed: tls_psk
+ - name: Modify cluster peer - destination intercluster addresses
+ netapp.ontap.na_ontap_cluster_peer:
+ state: present
+ source_intercluster_lifs: 1.2.3.4,1.2.3.5
+ dest_intercluster_lifs: 1.2.3.8
+ dest_cluster_name: test-dest-cluster
+ hostname: "{{ netapp_hostname }}"
+ username: "{{ netapp_username }}"
+ password: "{{ netapp_password }}"
+ peer_options:
+ hostname: "{{ dest_netapp_hostname }}"
+
"""
RETURN = """
@@ -279,7 +296,7 @@ class NetAppONTAPClusterPeer:
# if peer-lifs not present in parameters, use peer_cluster to filter desired cluster peer in current.
if self.parameters.get(peer_lifs) is not None:
peer_addresses_exist = set(self.parameters[peer_lifs]) == set(record['remote']['ip_addresses'])
- else:
+ if self.parameters.get(peer_cluster) is not None:
peer_cluster_exist = self.parameters[peer_cluster] == record['remote']['name']
if peer_addresses_exist or peer_cluster_exist:
cluster_info['cluster_name'] = record['remote']['name']
@@ -379,23 +396,56 @@ class NetAppONTAPClusterPeer:
for record in response['records']:
self.generated_passphrase = record['authentication']['passphrase']
+ def cluster_peer_modify_rest(self, cluster, uuid, modified_peer_addresses):
+ api = 'cluster/peers'
+ body = {'remote.ip_addresses': modified_peer_addresses}
+ server = self.rest_api if cluster == 'source' else self.dst_rest_api
+ dummy, error = rest_generic.patch_async(server, api, uuid, body)
+ if error:
+ self.module.fail_json(msg=error)
+
def apply(self):
"""
Apply action to cluster peer
:return: None
"""
+ modify = {}
source = self.cluster_peer_get('source')
destination = self.cluster_peer_get('destination')
source_action = self.na_helper.get_cd_action(source, self.parameters)
destination_action = self.na_helper.get_cd_action(destination, self.parameters)
self.na_helper.changed = False
+
# create only if expected cluster peer relation is not present on both source and destination clusters
# will error out with appropriate message if peer relationship already exists on either cluster
- if source_action == 'create' or destination_action == 'create':
+ if source_action == 'create' and destination_action == 'create':
if not self.module.check_mode:
self.cluster_peer_create('source')
self.cluster_peer_create('destination')
self.na_helper.changed = True
+ # check and modify IP addresses of the logical interfaces used in peer relation
+ # on either source or destination cluster
+ elif self.use_rest and (source_action is None or destination_action is None):
+ source_changed, destination_changed = False, False
+ if source_action is None:
+ if destination_action == 'create' and self.parameters.get('source_cluster_name') is None:
+ self.module.fail_json(msg='Following option is missing: source_cluster_name')
+ if not self.module.check_mode:
+ if source and (source.get('peer-addresses') != self.parameters.get('dest_intercluster_lifs')):
+ source_changed = True
+ uuid = source['uuid']
+ self.cluster_peer_modify_rest('source', uuid, self.parameters['dest_intercluster_lifs'])
+ modify['dest_intercluster_lifs'] = self.parameters['dest_intercluster_lifs']
+ if destination_action is None:
+ if source_action == 'create' and self.parameters.get('dest_cluster_name') is None:
+ self.module.fail_json(msg='Following option is missing: dest_cluster_name')
+ if not self.module.check_mode:
+ if destination and (destination.get('peer-addresses') != self.parameters.get('source_intercluster_lifs')):
+ destination_changed = True
+ uuid = destination['uuid']
+ self.cluster_peer_modify_rest('destination', uuid, self.parameters['source_intercluster_lifs'])
+ modify['source_intercluster_lifs'] = self.parameters['source_intercluster_lifs']
+ self.na_helper.changed = source_changed | destination_changed
# delete peer relation in cluster where relation is present
else:
if source_action == 'delete':
@@ -409,8 +459,8 @@ class NetAppONTAPClusterPeer:
self.cluster_peer_delete('destination', uuid)
self.na_helper.changed = True
- result = netapp_utils.generate_result(self.na_helper.changed, extra_responses={'source_action': source_action,
- 'destination_action': destination_action})
+ result = netapp_utils.generate_result(self.na_helper.changed, modify=modify, extra_responses={'source_action': source_action,
+ 'destination_action': destination_action})
self.module.exit_json(**result)
@@ -419,8 +469,8 @@ def main():
Execute action
:return: None
"""
- community_obj = NetAppONTAPClusterPeer()
- community_obj.apply()
+ cluster_peer_obj = NetAppONTAPClusterPeer()
+ cluster_peer_obj.apply()
if __name__ == '__main__':
diff --git a/ansible_collections/netapp/ontap/plugins/modules/na_ontap_dns.py b/ansible_collections/netapp/ontap/plugins/modules/na_ontap_dns.py
index 67d23cffd..3c46b0084 100644
--- a/ansible_collections/netapp/ontap/plugins/modules/na_ontap_dns.py
+++ b/ansible_collections/netapp/ontap/plugins/modules/na_ontap_dns.py
@@ -259,10 +259,14 @@ class NetAppOntapDns:
if error:
self.module.fail_json(msg="Error getting DNS service: %s" % error)
if record:
+ if params.get('scope') == 'cluster':
+ uuid = record.get('uuid')
+ else:
+ uuid = self.na_helper.safe_get(record, ['svm', 'uuid'])
return {
- 'domains': record['domains'],
- 'nameservers': record['servers'],
- 'uuid': record['svm']['uuid']
+ 'domains': record.get('domains'),
+ 'nameservers': record.get('servers'),
+ 'uuid': uuid
}
if self.parameters.get('vserver') and not self.rest_api.meets_rest_minimum_version(self.use_rest, 9, 9, 1):
# There is a chance we are working at the cluster level
diff --git a/ansible_collections/netapp/ontap/plugins/modules/na_ontap_ems_config.py b/ansible_collections/netapp/ontap/plugins/modules/na_ontap_ems_config.py
new file mode 100644
index 000000000..30a80e574
--- /dev/null
+++ b/ansible_collections/netapp/ontap/plugins/modules/na_ontap_ems_config.py
@@ -0,0 +1,186 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: 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
+__metaclass__ = type
+
+DOCUMENTATION = """
+module: na_ontap_ems_config
+short_description: NetApp ONTAP module to modify EMS configuration.
+extends_documentation_fragment:
+ - netapp.ontap.netapp.na_ontap
+version_added: '22.8.0'
+author: NetApp Ansible Team (@carchi8py) <ng-ansibleteam@netapp.com>
+description:
+ - Configure event notification and logging for the cluster.
+options:
+ state:
+ description:
+ - modify EMS configuration, only present is supported.
+ choices: ['present']
+ type: str
+ default: present
+ mail_from:
+ description:
+ - The email address that the event notification system uses as the "From" address for email notifications.
+ type: str
+ required: false
+ mail_server:
+ description:
+ - The name or IP address of the SMTP server that the event notification system uses to send email notification of events.
+ type: str
+ required: false
+ proxy_url:
+ description:
+ - HTTP or HTTPS proxy server URL used by rest-api type EMS notification destinations if your organization uses a proxy.
+ type: str
+ required: false
+ proxy_user:
+ description:
+ - User name for the HTTP or HTTPS proxy server if authentication is required.
+ type: str
+ required: false
+ proxy_password:
+ description:
+ - Password for HTTP or HTTPS proxy.
+ type: str
+ required: false
+ pubsub_enabled:
+ description:
+ - Indicates whether or not events are published to the Publish/Subscribe messaging broker.
+ - Requires ONTAP 9.10 or later.
+ type: bool
+ required: false
+
+notes:
+ - Only supported with REST and requires ONTAP 9.6 or later.
+ - Module is not idempotent when proxy_password is set.
+"""
+
+EXAMPLES = """
+ - name: Modify EMS mail config
+ netapp.ontap.na_ontap_ems_config:
+ state: present
+ mail_from: administrator@mycompany.com
+ mail_server: mail.mycompany.com
+ pubsub_enabled: true
+ hostname: "{{ netapp_hostname }}"
+ username: "{{ netapp_username }}"
+ password: "{{ netapp_password }}"
+ https: true
+ validate_certs: "{{ validate_certs }}"
+
+ - name: Modify EMS proxy config
+ netapp.ontap.na_ontap_ems_config:
+ state: present
+ proxy_url: http://proxy.example.com:8080
+ pubsub_enabled: true
+ proxy_user: admin
+ proxy_password: password
+ hostname: "{{ netapp_hostname }}"
+ username: "{{ netapp_username }}"
+ password: "{{ netapp_password }}"
+ https: true
+ validate_certs: "{{ validate_certs }}"
+"""
+
+RETURN = """
+"""
+
+import traceback
+from ansible.module_utils.basic import AnsibleModule
+from ansible.module_utils._text import to_native
+import ansible_collections.netapp.ontap.plugins.module_utils.netapp as netapp_utils
+from ansible_collections.netapp.ontap.plugins.module_utils.netapp_module import NetAppModule
+from ansible_collections.netapp.ontap.plugins.module_utils import rest_generic
+
+
+class NetAppOntapEmsConfig:
+ def __init__(self):
+ self.argument_spec = netapp_utils.na_ontap_host_argument_spec()
+ self.argument_spec.update(dict(
+ state=dict(required=False, type='str', choices=['present'], default='present'),
+ mail_from=dict(required=False, type='str'),
+ mail_server=dict(required=False, type='str'),
+ proxy_url=dict(required=False, type='str'),
+ proxy_user=dict(required=False, type='str'),
+ proxy_password=dict(required=False, type='str', no_log=True),
+ pubsub_enabled=dict(required=False, type='bool')
+ ))
+ self.module = AnsibleModule(
+ argument_spec=self.argument_spec,
+ supports_check_mode=False
+ )
+ self.uuid = None
+ self.na_helper = NetAppModule(self.module)
+ self.parameters = self.na_helper.check_and_set_parameters(self.module)
+ self.rest_api = netapp_utils.OntapRestAPI(self.module)
+ self.rest_api.fail_if_not_rest_minimum_version('na_ontap_ems_config:', 9, 6)
+ self.use_rest = self.rest_api.is_rest_supported_properties(self.parameters, None, [['pubsub_enabled', (9, 10, 1)]])
+
+ def get_ems_config_rest(self):
+ """Get EMS config details"""
+ fields = 'mail_from,mail_server,proxy_url,proxy_user'
+ if 'pubsub_enabled' in self.parameters and self.rest_api.meets_rest_minimum_version(self.use_rest, 9, 10, 1):
+ fields += ',pubsub_enabled'
+ record, error = rest_generic.get_one_record(self.rest_api, 'support/ems', None, fields)
+ if error:
+ self.module.fail_json(msg="Error fetching EMS config: %s" % to_native(error), exception=traceback.format_exc())
+ if record:
+ return {
+ 'mail_from': record.get('mail_from'),
+ 'mail_server': record.get('mail_server'),
+ 'proxy_url': record.get('proxy_url'),
+ 'proxy_user': record.get('proxy_user'),
+ 'pubsub_enabled': record.get('pubsub_enabled')
+ }
+ return None
+
+ def modify_ems_config_rest(self, modify):
+ """Modify EMS config"""
+ dummy, error = rest_generic.patch_async(self.rest_api, 'support/ems', None, modify)
+ if error:
+ self.module.fail_json(msg='Error modifying EMS config: %s.' % to_native(error), exception=traceback.format_exc())
+
+ def check_proxy_url(self, current):
+ # GET return the proxy url, if configured, along with port number
+ # based on the existing config, append port numnber to input url to
+ # maintain idempotency while modifying config
+ port = None
+ if current.get('proxy_url') is not None:
+ # strip trailing '/' and extract the port no
+ port = current['proxy_url'].rstrip('/').split(':')[-1]
+ pos = self.parameters['proxy_url'].rstrip('/').rfind(':')
+ if self.parameters['proxy_url'][pos + 1] == '/':
+ # port is not mentioned in input proxy URL
+ # if port is present in current url configured then add to the input url
+ if port is not None and port != '':
+ self.parameters['proxy_url'] = "%s:%s" % (self.parameters['proxy_url'].rstrip('/'), port)
+
+ def apply(self):
+ current = self.get_ems_config_rest()
+ if self.parameters.get('proxy_url') not in [None, '']:
+ self.check_proxy_url(current)
+ modify = self.na_helper.get_modified_attributes(current, self.parameters)
+
+ password_changed = False
+ if self.parameters.get('proxy_password') not in [None, '']:
+ modify['proxy_password'] = self.parameters['proxy_password']
+ self.module.warn('Module is not idempotent when proxy_password is set.')
+ password_changed = True
+ if (self.na_helper.changed or password_changed) and not self.module.check_mode:
+ self.modify_ems_config_rest(modify)
+ result = netapp_utils.generate_result(changed=self.na_helper.changed | password_changed, modify=modify)
+ self.module.exit_json(**result)
+
+
+def main():
+ ems_config = NetAppOntapEmsConfig()
+ ems_config.apply()
+
+
+if __name__ == '__main__':
+ main()
diff --git a/ansible_collections/netapp/ontap/plugins/modules/na_ontap_ems_destination.py b/ansible_collections/netapp/ontap/plugins/modules/na_ontap_ems_destination.py
index 76ddfa31b..599c86c74 100644
--- a/ansible_collections/netapp/ontap/plugins/modules/na_ontap_ems_destination.py
+++ b/ansible_collections/netapp/ontap/plugins/modules/na_ontap_ems_destination.py
@@ -1,6 +1,6 @@
#!/usr/bin/python
-# (c) 2022, NetApp, Inc
+# (c) 2023, NetApp, Inc
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
'''
@@ -18,7 +18,7 @@ extends_documentation_fragment:
version_added: 21.23.0
author: Bartosz Bielawski (@bielawb) <bartek.bielawski@live.com>
description:
- - Configure EMS destination. Currently certificate authentication for REST is not supported.
+ - Configure EMS destination.
options:
state:
description:
@@ -48,6 +48,57 @@ options:
required: true
type: list
elements: str
+ certificate:
+ description:
+ - Name of the certificate
+ required: false
+ type: str
+ version_added: 22.8.0
+ ca:
+ description:
+ - Name of the CA certificate
+ required: false
+ type: str
+ version_added: 22.8.0
+ syslog:
+ description:
+ - The parameter is specified when the EMS destination type is C(syslog).
+ required: false
+ version_added: 22.9.0
+ type: dict
+ suboptions:
+ transport:
+ choices: [udp_unencrypted, tcp_unencrypted, tcp_encrypted]
+ description:
+ - Syslog Transport Protocol.
+ type: str
+ default: 'udp_unencrypted'
+ timestamp_format_override:
+ choices: [no_override, rfc_3164, iso_8601_local_time, iso_8601_utc]
+ description:
+ - Syslog Timestamp Format Override.
+ type: str
+ default: 'no_override'
+ hostname_format_override:
+ choices: [no_override, fqdn, hostname_only]
+ description:
+ - Syslog Hostname Format Override.
+ type: str
+ default: 'no_override'
+ message_format:
+ choices: [legacy_netapp, rfc_5424]
+ description:
+ - Syslog Message Format.
+ type: str
+ default: 'legacy_netapp'
+ port:
+ description:
+ - Syslog Port.
+ type: int
+ default: 514
+notes:
+ - Supports check_mode.
+ - This module only supports REST.
'''
EXAMPLES = """
@@ -62,6 +113,38 @@ EXAMPLES = """
username: "{{username}}"
password: "{{password}}"
+ - name: Configure REST EMS destination with a certificate
+ netapp.ontap.na_ontap_ems_destination:
+ state: present
+ name: rest
+ type: rest_api
+ filters: ['important_events']
+ destination: http://my.rest.api/address
+ certificate: my_cert
+ ca: my_cert_ca
+ hostname: "{{hostname}}"
+ username: "{{username}}"
+ password: "{{password}}"
+
+ - name: Configure REST EMS destination with type syslog
+ netapp.ontap.na_ontap_ems_destination:
+ state: present
+ name: syslog_destination
+ type: syslog
+ filters: ['important_events']
+ destination: http://my.rest.api/address
+ certificate: my_cert
+ ca: my_cert_ca
+ syslog:
+ transport: udp_unencrypted
+ port: 514
+ message_format: legacy_netapp
+ hostname_format_override: no_override
+ timestamp_format_override: no_override
+ hostname: "{{hostname}}"
+ username: "{{username}}"
+ password: "{{password}}"
+
- name: Remove email EMS destination
netapp.ontap.na_ontap_ems_destination:
state: absent
@@ -91,17 +174,31 @@ class NetAppOntapEmsDestination:
state=dict(required=False, type='str', choices=['present', 'absent'], default='present'),
name=dict(required=True, type='str'),
type=dict(required=True, type='str', choices=['email', 'syslog', 'rest_api']),
+ syslog=dict(required=False, type='dict',
+ options=dict(
+ transport=dict(required=False, type='str', choices=['udp_unencrypted', 'tcp_unencrypted', 'tcp_encrypted'],
+ default='udp_unencrypted'),
+ port=dict(required=False, type='int', default=514),
+ message_format=dict(required=False, type='str', choices=['legacy_netapp', 'rfc_5424'], default='legacy_netapp'),
+ timestamp_format_override=dict(required=False, type='str',
+ choices=['no_override', 'rfc_3164', 'iso_8601_local_time', 'iso_8601_utc'], default='no_override'),
+ hostname_format_override=dict(required=False, type='str', choices=['no_override', 'fqdn', 'hostname_only'], default='no_override')
+ )),
destination=dict(required=True, type='str'),
- filters=dict(required=True, type='list', elements='str')
+ filters=dict(required=True, type='list', elements='str'),
+ certificate=dict(required=False, type='str'),
+ ca=dict(required=False, type='str'),
))
self.module = AnsibleModule(
argument_spec=self.argument_spec,
+ required_together=[('certificate', 'ca')],
supports_check_mode=True
)
self.na_helper = NetAppModule()
self.parameters = self.na_helper.set_parameters(self.module.params)
self.rest_api = netapp_utils.OntapRestAPI(self.module)
- self.use_rest = self.rest_api.is_rest()
+ partially_supported_rest_properties = [['certificate', (9, 11, 1)], ['syslog', (9, 12, 1)]]
+ self.use_rest = self.rest_api.is_rest_supported_properties(self.parameters, partially_supported_rest_properties=partially_supported_rest_properties)
if not self.use_rest:
self.module.fail_json(msg='na_ontap_ems_destination is only supported with REST API')
@@ -116,8 +213,20 @@ class NetAppOntapEmsDestination:
def get_ems_destination(self, name):
api = 'support/ems/destinations'
- fields = 'name,type,destination,filters.name'
- query = dict(name=name, fields=fields)
+ query = {'name': name,
+ 'fields': 'type,'
+ 'destination,'
+ 'filters.name,'
+ 'certificate.ca,'}
+ if self.rest_api.meets_rest_minimum_version(self.use_rest, 9, 11, 1):
+ query['fields'] += 'certificate.name,'
+ if self.rest_api.meets_rest_minimum_version(self.use_rest, 9, 12, 1):
+ syslog_option_9_12 = ('syslog.transport,'
+ 'syslog.port,'
+ 'syslog.format.message,'
+ 'syslog.format.timestamp_override,'
+ 'syslog.format.hostname_override,')
+ query['fields'] += syslog_option_9_12
record, error = rest_generic.get_one_record(self.rest_api, api, query)
self.fail_on_error(error, 'fetching EMS destination for %s' % name)
if record:
@@ -125,8 +234,18 @@ class NetAppOntapEmsDestination:
'name': self.na_helper.safe_get(record, ['name']),
'type': self.na_helper.safe_get(record, ['type']),
'destination': self.na_helper.safe_get(record, ['destination']),
- 'filters': None
+ 'filters': None,
+ 'certificate': self.na_helper.safe_get(record, ['certificate', 'name']),
+ 'ca': self.na_helper.safe_get(record, ['certificate', 'ca']),
}
+ if record.get('syslog') is not None:
+ current['syslog'] = {
+ 'port': self.na_helper.safe_get(record, ['syslog', 'port']),
+ 'transport': self.na_helper.safe_get(record, ['syslog', 'transport']),
+ 'timestamp_format_override': self.na_helper.safe_get(record, ['syslog', 'format', 'timestamp_override']),
+ 'hostname_format_override': self.na_helper.safe_get(record, ['syslog', 'format', 'hostname_override']),
+ 'message_format': self.na_helper.safe_get(record, ['syslog', 'format', 'message']),
+ }
# 9.9.0 and earlier versions returns rest-api, convert it to rest_api.
if current['type'] and '-' in current['type']:
current['type'] = current['type'].replace('-', '_')
@@ -135,6 +254,24 @@ class NetAppOntapEmsDestination:
return current
return None
+ def get_certificate_serial(self, cert_name):
+ """Retrieve the serial of a certificate"""
+ api = 'security/certificates'
+ query = {
+ 'scope': "cluster",
+ 'type': "client",
+ 'name': cert_name
+ }
+ fields = 'serial_number'
+ record, error = rest_generic.get_one_record(self.rest_api, api, query, fields)
+ if error:
+ self.module.fail_json(msg='Error retrieving certificates: %s' % error)
+
+ if not record:
+ self.module.fail_json(msg='Error certificate not found: %s.'
+ % (self.parameters['certificate']))
+ return record['serial_number']
+
def create_ems_destination(self):
api = 'support/ems/destinations'
name = self.parameters['name']
@@ -144,6 +281,25 @@ class NetAppOntapEmsDestination:
'destination': self.parameters['destination'],
'filters': self.generate_filters_list(self.parameters['filters'])
}
+
+ if self.rest_api.meets_rest_minimum_version(self.use_rest, 9, 11, 1):
+ if self.parameters.get('certificate') and self.parameters.get('ca') is not None:
+ body['certificate'] = {
+ 'serial_number': self.get_certificate_serial(self.parameters['certificate']),
+ 'ca': self.parameters['ca'],
+ }
+ if self.rest_api.meets_rest_minimum_version(self.use_rest, 9, 12, 1):
+ if self.parameters.get('syslog') is not None:
+ body['syslog'] = {}
+ for key, option in [
+ ('syslog.port', 'port'),
+ ('syslog.transport', 'transport'),
+ ('syslog.format.message', 'message_format'),
+ ('syslog.format.timestamp_override', 'timestamp_format_override'),
+ ('syslog.format.hostname_override', 'hostname_format_override')
+ ]:
+ if self.parameters['syslog'].get(option) is not None:
+ body[key] = self.parameters['syslog'][option]
dummy, error = rest_generic.post_async(self.rest_api, api, body)
self.fail_on_error(error, 'creating EMS destinations for %s' % name)
@@ -159,9 +315,25 @@ class NetAppOntapEmsDestination:
self.create_ems_destination()
else:
body = {}
+ if any(item in modify for item in ['certificate', 'ca']):
+ body['certificate'] = {}
for option in modify:
if option == 'filters':
body[option] = self.generate_filters_list(modify[option])
+ elif option == 'certificate':
+ body[option]['serial_number'] = self.get_certificate_serial(modify[option])
+ elif option == 'ca':
+ body['certificate']['ca'] = modify[option]
+ elif option == 'syslog':
+ for key, option in [
+ ('syslog.port', 'port'),
+ ('syslog.transport', 'transport'),
+ ('syslog.format.message', 'message_format'),
+ ('syslog.format.timestamp_override', 'timestamp_format_override'),
+ ('syslog.format.hostname_override', 'hostname_format_override')
+ ]:
+ if option in modify['syslog']:
+ body[key] = modify['syslog'][option]
else:
body[option] = modify[option]
if body:
@@ -170,10 +342,9 @@ class NetAppOntapEmsDestination:
self.fail_on_error(error, 'modifying EMS destination for %s' % name)
def apply(self):
- name = None
- modify = None
- current = self.get_ems_destination(self.parameters['name'])
name = self.parameters['name']
+ modify = None
+ current = self.get_ems_destination(name)
cd_action = self.na_helper.get_cd_action(current, self.parameters)
if cd_action is None and self.parameters['state'] == 'present':
modify = self.na_helper.get_modified_attributes(current, self.parameters)
diff --git a/ansible_collections/netapp/ontap/plugins/modules/na_ontap_ems_filter.py b/ansible_collections/netapp/ontap/plugins/modules/na_ontap_ems_filter.py
index bdd3a73c3..d6ea223d9 100644
--- a/ansible_collections/netapp/ontap/plugins/modules/na_ontap_ems_filter.py
+++ b/ansible_collections/netapp/ontap/plugins/modules/na_ontap_ems_filter.py
@@ -166,67 +166,90 @@ class NetAppOntapEMSFilters:
self.module.fail_json(msg='Error deleting EMS filter %s: %s' % (self.parameters['name'], to_native(error)),
exception=traceback.format_exc())
- def modify_ems_filter(self):
- # only variable other than name is rules, so if we hit this we know rules has been changed
+ def modify_ems_filter(self, desired_rules):
+ post_api = 'support/ems/filters/%s/rules' % self.parameters['name']
api = 'support/ems/filters'
- body = {'rules': self.na_helper.filter_out_none_entries(self.parameters['rules'])}
- dummy, error = rest_generic.patch_async(self.rest_api, api, self.parameters['name'], body)
- if error:
- self.module.fail_json(msg='Error modifying EMS filter %s: %s' % (self.parameters['name'], to_native(error)),
- exception=traceback.format_exc())
+ if desired_rules['patch_rules'] != []:
+ patch_body = {'rules': desired_rules['patch_rules']}
+ dummy, error = rest_generic.patch_async(self.rest_api, api, self.parameters['name'], patch_body)
+ if error:
+ self.module.fail_json(msg='Error modifying EMS filter %s: %s' % (self.parameters['name'], to_native(error)),
+ exception=traceback.format_exc())
+ if desired_rules['post_rules'] != []:
+ for rule in desired_rules['post_rules']:
+ dummy, error = rest_generic.post_async(self.rest_api, post_api, rule)
+ if error:
+ self.module.fail_json(msg='Error modifying EMS filter %s: %s' % (self.parameters['name'], to_native(error)),
+ exception=traceback.format_exc())
+
+ def desired_ems_rules(self, current_rules):
+ # Modify current filter to remove auto added rule of type exclude, from testing it always appears to be the last element
+ current_rules['rules'] = current_rules['rules'][:-1]
+ if self.parameters.get('rules'):
+ input_rules = self.na_helper.filter_out_none_entries(self.parameters['rules'])
+ for i in range(len(input_rules)):
+ input_rules[i]['message_criteria']['severities'] = input_rules[i]['message_criteria']['severities'].lower()
+ matched_idx = []
+ patch_rules = []
+ post_rules = []
+ for rule_dict in current_rules['rules']:
+ for i in range(len(input_rules)):
+ if input_rules[i]['index'] == rule_dict['index']:
+ matched_idx.append(int(input_rules[i]['index']))
+ patch_rules.append(input_rules[i])
+ break
+ else:
+ rule = {'index': rule_dict['index']}
+ rule['type'] = rule_dict.get('type')
+ if 'message_criteria' in rule_dict:
+ rule['message_criteria'] = {}
+ rule['message_criteria']['severities'] = rule_dict.get('message_criteria').get('severities')
+ rule['message_criteria']['name_pattern'] = rule_dict.get('message_criteria').get('name_pattern')
+ patch_rules.append(rule)
+ for i in range(len(input_rules)):
+ if int(input_rules[i]['index']) not in matched_idx:
+ post_rules.append(input_rules[i])
+ desired_rules = {'patch_rules': patch_rules, 'post_rules': post_rules}
+ return desired_rules
+ return None
- def find_modify(self, current):
- # The normal modify will not work for 2 reasons
- # First ems filter will add a new rule at the end that excludes everything that there isn't a rule for
- # Second Options that are not given are returned as '*' in rest
+ def find_modify(self, current, desired_rules):
if not current:
return False
- # Modify Current to remove auto added rule, from testing it always appears to be the last element
- if current.get('rules'):
- current['rules'].pop()
- # Next check if both have no rules
- if current.get('rules') is None and self.parameters.get('rules') is None:
+ # Next check if either one has no rules
+ if current.get('rules') is None or desired_rules is None:
return False
+ modify = False
+ merge_rules = desired_rules['patch_rules'] + desired_rules['post_rules']
# Next let check if rules is the same size if not we need to modify
- if len(current.get('rules')) != len(self.parameters.get('rules')):
+ if len(current.get('rules')) != len(merge_rules):
return True
- # Next let put the current rules in a dictionary by rule number
- current_rules = self.dic_of_rules(current)
- # Now we need to compare each field to see if there is a match
- modify = False
- for rule in self.parameters['rules']:
- # allow modify if a desired rule index may not exist in current rules.
- # when testing found only index 1, 2 are allowed, if try to set index other than this, let REST throw error.
- if current_rules.get(rule['index']) is None:
- modify = True
- break
- # Check if types are the same
- if rule['type'].lower() != current_rules[rule['index']]['type'].lower():
- modify = True
- break
- if rule.get('message_criteria'):
- if rule['message_criteria'].get('severities') and rule['message_criteria']['severities'].lower() != \
- current_rules[rule['index']]['message_criteria']['severities'].lower():
- modify = True
- break
- if rule['message_criteria'].get('name_pattern') and rule['message_criteria']['name_pattern'] != \
- current_rules[rule['index']]['message_criteria']['name_pattern']:
- modify = True
- break
- return modify
+ for i in range(len(current['rules'])):
+ # compare each field to see if there is a mismatch
+ if current['rules'][i]['index'] != merge_rules[i]['index'] or current['rules'][i]['type'] != merge_rules[i]['type']:
+ return True
+ else:
+ # adding default values for fields under message_criteria
+ if merge_rules[i].get('message_criteria') is None:
+ merge_rules[i]['message_criteria'] = {'severities': '*', 'name_pattern': '*'}
+ elif merge_rules[i]['message_criteria'].get('severities') is None:
+ merge_rules[i]['message_criteria']['severities'] = '*'
+ elif merge_rules[i]['message_criteria'].get('name_pattern') is None:
+ merge_rules[i]['message_criteria']['name_pattern'] = '*'
- def dic_of_rules(self, current):
- rules = {}
- for rule in current['rules']:
- rules[rule['index']] = rule
- return rules
+ if current['rules'][i].get('message_criteria').get('name_pattern') != merge_rules[i].get('message_criteria').get('name_pattern'):
+ return True
+ if current['rules'][i].get('message_criteria').get('severities') != merge_rules[i].get('message_criteria').get('severities'):
+ return True
+ return modify
def apply(self):
current = self.get_ems_filter()
cd_action, modify = None, False
cd_action = self.na_helper.get_cd_action(current, self.parameters)
- if cd_action is None:
- modify = self.find_modify(current)
+ if cd_action is None and self.parameters['state'] == 'present':
+ desired_rules = self.desired_ems_rules(current)
+ modify = self.find_modify(current, desired_rules)
if modify:
self.na_helper.changed = True
if self.na_helper.changed and not self.module.check_mode:
@@ -235,7 +258,7 @@ class NetAppOntapEMSFilters:
if cd_action == 'delete':
self.delete_ems_filter()
if modify:
- self.modify_ems_filter()
+ self.modify_ems_filter(desired_rules)
result = netapp_utils.generate_result(self.na_helper.changed, cd_action, modify)
self.module.exit_json(**result)
diff --git a/ansible_collections/netapp/ontap/plugins/modules/na_ontap_export_policy_rule.py b/ansible_collections/netapp/ontap/plugins/modules/na_ontap_export_policy_rule.py
index 8b9414074..660ef6825 100644
--- a/ansible_collections/netapp/ontap/plugins/modules/na_ontap_export_policy_rule.py
+++ b/ansible_collections/netapp/ontap/plugins/modules/na_ontap_export_policy_rule.py
@@ -1,6 +1,6 @@
#!/usr/bin/python
-# (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)
'''
@@ -169,6 +169,7 @@ EXAMPLES = """
state: present
name: default123
rule_index: 100
+ vserver: ci_dev
client_match: 0.0.0.0/0
anonymous_user_id: 65521
ro_rule: ntlm
@@ -732,7 +733,8 @@ class NetAppontapExportRule:
elif modify:
self.modify_export_policy_rule(modify, current['rule_index'])
- self.module.exit_json(changed=self.na_helper.changed)
+ result = netapp_utils.generate_result(self.na_helper.changed, cd_action, modify)
+ self.module.exit_json(**result)
def main():
diff --git a/ansible_collections/netapp/ontap/plugins/modules/na_ontap_file_security_permissions_acl.py b/ansible_collections/netapp/ontap/plugins/modules/na_ontap_file_security_permissions_acl.py
index 277986466..92514d994 100644
--- a/ansible_collections/netapp/ontap/plugins/modules/na_ontap_file_security_permissions_acl.py
+++ b/ansible_collections/netapp/ontap/plugins/modules/na_ontap_file_security_permissions_acl.py
@@ -209,7 +209,7 @@ EXAMPLES = """
path: "{{ file_mount_path }}"
validate_changes: warn
access: access_allow
- # Note, wihout quotes, use a single backslash in AD user names
+ # Note, without quotes, use a single backslash in AD user names
# with quotes, it needs to be escaped as a double backslash
# user: "ANSIBLE_CIFS\\user1"
# we can't show an example with a single backslash as this is a python file, but it works in YAML.
@@ -466,7 +466,8 @@ class NetAppOntapFileSecurityPermissionsACL:
if modify:
self.modify_file_security_permissions_acl()
self.validate_changes(cd_action, modify)
- self.module.exit_json(changed=self.na_helper.changed)
+ result = netapp_utils.generate_result(self.na_helper.changed, cd_action, modify)
+ self.module.exit_json(**result)
def validate_changes(self, cd_action, modify):
if self.parameters['validate_changes'] == 'ignore':
diff --git a/ansible_collections/netapp/ontap/plugins/modules/na_ontap_igroup_initiator.py b/ansible_collections/netapp/ontap/plugins/modules/na_ontap_igroup_initiator.py
index 7280eb181..18d25f4dd 100644
--- a/ansible_collections/netapp/ontap/plugins/modules/na_ontap_igroup_initiator.py
+++ b/ansible_collections/netapp/ontap/plugins/modules/na_ontap_igroup_initiator.py
@@ -1,7 +1,7 @@
#!/usr/bin/python
''' This is an Ansible module for ONTAP, to manage initiators in an Igroup
- (c) 2019-2022, NetApp, Inc
+ (c) 2019-2023, NetApp, Inc
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
'''
@@ -192,7 +192,7 @@ class NetAppOntapIgroupInitiator(object):
dummy, error = rest_generic.post_async(self.rest_api, api, body)
else:
query = {'allow_delete_while_mapped': self.parameters['force_remove']}
- dummy, error = rest_generic.delete_async(self.rest_api, api, initiator_name, query)
+ dummy, error = rest_generic.delete_async(self.rest_api, api, initiator_name.lower(), query)
if error:
self.module.fail_json(msg="Error modifying igroup initiator %s: %s" % (initiator_name, error))
@@ -201,7 +201,7 @@ class NetAppOntapIgroupInitiator(object):
for initiator in self.parameters['names']:
present = None
initiator = self.na_helper.sanitize_wwn(initiator)
- if initiator in initiators:
+ if initiator.lower() in initiators:
present = True
cd_action = self.na_helper.get_cd_action(present, self.parameters)
if self.na_helper.changed and not self.module.check_mode:
@@ -209,7 +209,8 @@ class NetAppOntapIgroupInitiator(object):
self.modify_initiator(initiator, 'igroup-add')
elif cd_action == 'delete':
self.modify_initiator(initiator, 'igroup-remove')
- self.module.exit_json(changed=self.na_helper.changed)
+ result = netapp_utils.generate_result(self.na_helper.changed, cd_action)
+ self.module.exit_json(**result)
def main():
diff --git a/ansible_collections/netapp/ontap/plugins/modules/na_ontap_info.py b/ansible_collections/netapp/ontap/plugins/modules/na_ontap_info.py
index 6591cc9cd..f9060ffc9 100644
--- a/ansible_collections/netapp/ontap/plugins/modules/na_ontap_info.py
+++ b/ansible_collections/netapp/ontap/plugins/modules/na_ontap_info.py
@@ -213,7 +213,7 @@ options:
- missing_vserver_api_error - most likely the API is available at cluster level but not vserver level.
- rpc_error - some queries are failing because the node cannot reach another node in the cluster.
- key_error - a query is failing because the returned data does not contain an expected key.
- - for key errors, make sure to report this in Slack. It may be a change in a new ONTAP version.
+ - for key errors, make sure to report this in Discord. It may be a change in a new ONTAP version.
- other_error - anything not in the above list.
- always will continue on any error, never will fail on any error, they cannot be used with any other keyword.
type: list
diff --git a/ansible_collections/netapp/ontap/plugins/modules/na_ontap_kerberos_realm.py b/ansible_collections/netapp/ontap/plugins/modules/na_ontap_kerberos_realm.py
index 9cb4c346b..27362f220 100644
--- a/ansible_collections/netapp/ontap/plugins/modules/na_ontap_kerberos_realm.py
+++ b/ansible_collections/netapp/ontap/plugins/modules/na_ontap_kerberos_realm.py
@@ -1,7 +1,7 @@
#!/usr/bin/python
'''
(c) 2019, Red Hat, Inc
-(c) 2019-2022, NetApp, Inc
+(c) 2019-2023, NetApp, Inc
GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
'''
@@ -65,7 +65,7 @@ options:
description:
- The clock skew in minutes is the tolerance for accepting tickets with time stamps that do not exactly match the host's system clock.
- The default for this parameter is '5' minutes.
- - This option is not supported with REST.
+ - Supported from ONTAP 9.13.1 in REST.
type: str
comment:
@@ -77,14 +77,14 @@ options:
description:
- IP address of the host where the Kerberos administration daemon is running. This is usually the master KDC.
- If this parameter is omitted, the address specified in kdc_ip is used.
- - This option is not supported with REST.
+ - Supported from ONTAP 9.13.1 in REST.
type: str
admin_server_port:
description:
- The TCP port on the Kerberos administration server where the Kerberos administration service is running.
- The default for this parmater is '749'.
- - This option is not supported with REST.
+ - Supported from ONTAP 9.13.1 in REST.
type: str
pw_server_ip:
@@ -92,14 +92,14 @@ options:
- IP address of the host where the Kerberos password-changing server is running.
- Typically, this is the same as the host indicated in the adminserver-ip.
- If this parameter is omitted, the IP address in kdc-ip is used.
- - This option is not supported with REST.
+ - Supported from ONTAP 9.13.1 in REST.
type: str
pw_server_port:
description:
- The TCP port on the Kerberos password-changing server where the Kerberos password-changing service is running.
- The default for this parameter is '464'.
- - This option is not supported with REST.
+ - Supported from ONTAP 9.13.1 in REST.
type: str
ad_server_ip:
@@ -145,6 +145,21 @@ EXAMPLES = '''
username: "{{ netapp_username }}"
password: "{{ netapp_password }}"
+ - name: Create kerberos realm other kdc vendor - REST
+ netapp.ontap.na_ontap_kerberos_realm:
+ state: present
+ realm: 'EXAMPLE.COM'
+ vserver: 'vserver1'
+ kdc_ip: '1.2.3.4'
+ kdc_vendor: 'other'
+ pw_server_ip: '0.0.0.0'
+ pw_server_port: '5'
+ admin_server_ip: '1.2.3.4'
+ admin_server_port: '2'
+ hostname: "{{ netapp_hostname }}"
+ username: "{{ netapp_username }}"
+ password: "{{ netapp_password }}"
+
'''
RETURN = '''
@@ -195,8 +210,9 @@ class NetAppOntapKerberosRealm:
self.parameters = self.na_helper.set_parameters(self.module.params)
# Set up Rest API
self.rest_api = netapp_utils.OntapRestAPI(self.module)
- unsupported_rest_properties = ['admin_server_ip', 'admin_server_port', 'clock_skew', 'pw_server_ip', 'pw_server_port']
- self.use_rest = self.rest_api.is_rest_supported_properties(self.parameters, unsupported_rest_properties)
+ partially_supported_rest_properties = [['admin_server_ip', (9, 13, 1)], ['admin_server_port', (9, 13, 1)], ['clock_skew', (9, 13, 1)],
+ ['pw_server_ip', (9, 13, 1)], ['pw_server_port', (9, 13, 1)]]
+ self.use_rest = self.rest_api.is_rest_supported_properties(self.parameters, None, partially_supported_rest_properties)
self.svm_uuid = None
if not self.use_rest:
@@ -350,7 +366,7 @@ class NetAppOntapKerberosRealm:
params = {
'name': self.parameters['realm'],
'svm.name': self.parameters['vserver'],
- 'fields': 'kdc,ad_server,svm,comment'
+ 'fields': 'kdc,ad_server,svm,comment,password_server,admin_server,clock_skew'
}
record, error = rest_generic.get_one_record(self.rest_api, api, params)
if error:
@@ -363,7 +379,12 @@ class NetAppOntapKerberosRealm:
'kdc_vendor': self.na_helper.safe_get(record, ['kdc', 'vendor']),
'ad_server_ip': self.na_helper.safe_get(record, ['ad_server', 'address']),
'ad_server_name': self.na_helper.safe_get(record, ['ad_server', 'name']),
- 'comment': self.na_helper.safe_get(record, ['comment'])
+ 'comment': self.na_helper.safe_get(record, ['comment']),
+ 'pw_server_ip': self.na_helper.safe_get(record, ['password_server', 'address']),
+ 'pw_server_port': str(self.na_helper.safe_get(record, ['password_server', 'port'])),
+ 'admin_server_ip': self.na_helper.safe_get(record, ['admin_server', 'address']),
+ 'admin_server_port': str(self.na_helper.safe_get(record, ['admin_server', 'port'])),
+ 'clock_skew': str(self.na_helper.safe_get(record, ['clock_skew']))
}
return None
@@ -379,9 +400,20 @@ class NetAppOntapKerberosRealm:
body['kdc.port'] = self.parameters['kdc_port']
if self.parameters.get('comment'):
body['comment'] = self.parameters['comment']
- if self.parameters['kdc_vendor'] == 'microsoft':
+ if self.parameters.get('ad_server_ip'):
body['ad_server.address'] = self.parameters['ad_server_ip']
+ if self.parameters.get('ad_server_name'):
body['ad_server.name'] = self.parameters['ad_server_name']
+ if self.parameters.get('admin_server_port'):
+ body['admin_server.port'] = self.parameters['admin_server_port']
+ if self.parameters.get('pw_server_port'):
+ body['password_server.port'] = self.parameters['pw_server_port']
+ if self.parameters.get('clock_skew'):
+ body['clock_skew'] = self.parameters['clock_skew']
+ if self.parameters.get('admin_server_ip'):
+ body['admin_server.address'] = self.parameters['admin_server_ip']
+ if self.parameters.get('pw_server_ip'):
+ body['password_server.address'] = self.parameters['pw_server_ip']
dummy, error = rest_generic.post_async(self.rest_api, api, body)
if error:
self.module.fail_json(msg='Error creating Kerberos Realm configuration %s: %s' % (self.parameters['realm'], to_native(error)))
@@ -401,6 +433,16 @@ class NetAppOntapKerberosRealm:
body['ad_server.address'] = modify['ad_server_ip']
if modify.get('ad_server_name'):
body['ad_server.name'] = modify['ad_server_name']
+ if modify.get('admin_server_ip'):
+ body['admin_server.address'] = modify['admin_server_ip']
+ if modify.get('admin_server_port'):
+ body['admin_server.port'] = modify['admin_server_port']
+ if modify.get('pw_server_ip'):
+ body['password_server.address'] = modify['pw_server_ip']
+ if modify.get('pw_server_port'):
+ body['password_server.port'] = modify['pw_server_port']
+ if modify.get('clock_skew'):
+ body['clock_skew'] = modify['clock_skew']
dummy, error = rest_generic.patch_async(self.rest_api, api, self.parameters['realm'], body)
if error:
self.module.fail_json(msg='Error modifying Kerberos Realm %s: %s' % (self.parameters['realm'], to_native(error)))
@@ -416,7 +458,6 @@ class NetAppOntapKerberosRealm:
current = self.get_krbrealm()
cd_action = self.na_helper.get_cd_action(current, self.parameters)
modify = self.na_helper.get_modified_attributes(current, self.parameters)
-
if self.na_helper.changed and not self.module.check_mode:
if cd_action == 'create':
self.create_krbrealm()
diff --git a/ansible_collections/netapp/ontap/plugins/modules/na_ontap_login_messages.py b/ansible_collections/netapp/ontap/plugins/modules/na_ontap_login_messages.py
index 099cea8b9..49ad2080a 100644
--- a/ansible_collections/netapp/ontap/plugins/modules/na_ontap_login_messages.py
+++ b/ansible_collections/netapp/ontap/plugins/modules/na_ontap_login_messages.py
@@ -154,12 +154,15 @@ class NetAppOntapLoginMessages:
}
def form_current(self, record):
+ show_cluster_motd = True
+ if record and record.get('show_cluster_message') is not None:
+ show_cluster_motd = record.get('show_cluster_message')
return_result = {
'banner': '',
'motd_message': '',
# we need the SVM UUID to add banner or motd if they are not present
'uuid': record['uuid'] if record else self.get_svm_uuid(self.parameters.get('vserver')),
- 'show_cluster_motd': record.get('show_cluster_message') if record else None
+ 'show_cluster_motd': show_cluster_motd
}
# by default REST adds a trailing \n if no trailing \n set in desired message/banner.
# rstip \n only when desired message/banner does not have trailing \n to preserve idempotency.
diff --git a/ansible_collections/netapp/ontap/plugins/modules/na_ontap_lun.py b/ansible_collections/netapp/ontap/plugins/modules/na_ontap_lun.py
index c0fb796f7..03ea4f592 100644
--- a/ansible_collections/netapp/ontap/plugins/modules/na_ontap_lun.py
+++ b/ansible_collections/netapp/ontap/plugins/modules/na_ontap_lun.py
@@ -1,6 +1,6 @@
#!/usr/bin/python
-# (c) 2017-2022, NetApp, Inc
+# (c) 2017-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
@@ -48,6 +48,14 @@ options:
- Not allowed if san_application_template is present.
type: str
+ qtree_name:
+ description:
+ - Specifies the name of the Qtree that contains the new LUN.
+ - Not allowed if san_application_template is present.
+ - Only supported with REST.
+ version_added: 22.8.0
+ type: str
+
size:
description:
- The size of the LUN in C(size_unit).
@@ -336,6 +344,7 @@ class NetAppOntapLUN:
force_remove=dict(required=False, type='bool', default=False),
force_remove_fenced=dict(type='bool'),
flexvol_name=dict(type='str'),
+ qtree_name=dict(type='str'),
vserver=dict(required=True, type='str'),
os_type=dict(required=False, type='str', aliases=['ostype']),
qos_policy_group=dict(required=False, type='str'),
@@ -418,6 +427,8 @@ class NetAppOntapLUN:
if use_application_template:
if self.parameters.get('flexvol_name') is not None:
self.module.fail_json(msg="'flexvol_name' option is not supported when san_application_template is present")
+ if self.parameters.get('qtree_name') is not None:
+ self.module.fail_json(msg="'qtree_name' option is not supported when san_application_template is present")
name = self.na_helper.safe_get(self.parameters, ['san_application_template', 'name'], allow_sparse_dict=False)
rest_app = RestApplication(self.rest_api, self.parameters['vserver'], name)
elif self.parameters.get('flexvol_name') is None:
@@ -914,6 +925,8 @@ class NetAppOntapLUN:
query['name'] = lun_path
else:
query['location.volume.name'] = self.parameters['flexvol_name']
+ if self.parameters.get('qtree_name') is not None:
+ query['location.qtree.name'] = self.parameters['qtree_name']
record, error = rest_generic.get_0_or_more_records(self.rest_api, api, query)
if error:
if lun_path is not None:
@@ -956,6 +969,8 @@ class NetAppOntapLUN:
}
if self.parameters.get('flexvol_name') is not None:
body['location.volume.name'] = self.parameters['flexvol_name']
+ if self.parameters.get('qtree_name') is not None:
+ body['location.qtree.name'] = self.parameters['qtree_name']
if self.parameters.get('os_type') is not None:
body['os_type'] = self.parameters['os_type']
if self.parameters.get('size') is not None:
@@ -978,7 +993,9 @@ class NetAppOntapLUN:
If the name start with a slash we will assume it a path and use it as the name
"""
if not self.parameters['name'].startswith('/') and self.parameters.get('flexvol_name') is not None:
- # if it dosn't start with a slash and we have a flexvol name we will use it to build the path
+ # if it dosn't start with a slash we will use flexvol name and/or qtree name to build the path
+ if self.parameters.get('qtree_name') is not None:
+ return '/vol/%s/%s/%s' % (self.parameters['flexvol_name'], self.parameters['qtree_name'], self.parameters['name'])
return '/vol/%s/%s' % (self.parameters['flexvol_name'], self.parameters['name'])
return self.parameters['name']
diff --git a/ansible_collections/netapp/ontap/plugins/modules/na_ontap_lun_map.py b/ansible_collections/netapp/ontap/plugins/modules/na_ontap_lun_map.py
index 5bdbc17c8..67acb4b38 100644
--- a/ansible_collections/netapp/ontap/plugins/modules/na_ontap_lun_map.py
+++ b/ansible_collections/netapp/ontap/plugins/modules/na_ontap_lun_map.py
@@ -2,7 +2,7 @@
""" this is lun mapping module
- (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)
"""
@@ -146,9 +146,7 @@ class NetAppOntapLUNMap:
],
supports_check_mode=True
)
- self.result = dict(
- changed=False,
- )
+ self.lun_info = dict()
self.na_helper = NetAppModule()
self.parameters = self.na_helper.set_parameters(self.module.params)
@@ -337,19 +335,19 @@ class NetAppOntapLUNMap:
if modify:
self.module.fail_json(msg="Modification of lun_map not allowed")
if self.parameters['state'] == 'present' and lun_details:
- self.result.update(lun_details)
- self.result['changed'] = self.na_helper.changed
+ self.lun_info.update(lun_details)
if self.na_helper.changed and not self.module.check_mode:
if cd_action == 'create':
self.create_lun_map()
if cd_action == 'delete':
self.delete_lun_map()
- self.module.exit_json(**self.result)
+ result = netapp_utils.generate_result(self.na_helper.changed, cd_action, extra_responses=self.lun_info)
+ self.module.exit_json(**result)
def main():
- v = NetAppOntapLUNMap()
- v.apply()
+ lun_mapping = NetAppOntapLUNMap()
+ lun_mapping.apply()
if __name__ == '__main__':
diff --git a/ansible_collections/netapp/ontap/plugins/modules/na_ontap_lun_map_reporting_nodes.py b/ansible_collections/netapp/ontap/plugins/modules/na_ontap_lun_map_reporting_nodes.py
index 607c8c430..5763a3d66 100644
--- a/ansible_collections/netapp/ontap/plugins/modules/na_ontap_lun_map_reporting_nodes.py
+++ b/ansible_collections/netapp/ontap/plugins/modules/na_ontap_lun_map_reporting_nodes.py
@@ -1,7 +1,7 @@
#!/usr/bin/python
"""
- (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)
"""
@@ -62,9 +62,9 @@ notes:
EXAMPLES = """
- name: Create Lun Map reporting nodes
netapp.ontap.na_ontap_lun_map_reporting_nodes:
- hostname: 172.21.121.82
- username: admin
- password: netapp1!
+ hostname: "{{ netapp_hostname }}"
+ username: "{{ netapp_username }}"
+ password: "{{ netapp_password }}"
https: true
validate_certs: false
vserver: vs1
@@ -75,9 +75,9 @@ EXAMPLES = """
- name: Delete Lun Map reporting nodes
netapp.ontap.na_ontap_lun_map_reporting_nodes:
- hostname: 172.21.121.82
- username: admin
- password: netapp1!
+ hostname: "{{ netapp_hostname }}"
+ username: "{{ netapp_username }}"
+ password: "{{ netapp_password }}"
https: true
validate_certs: false
vserver: vs1
@@ -248,21 +248,27 @@ class NetAppOntapLUNMapReportingNodes:
else:
nodes_to_add = list()
nodes_to_delete = [node for node in self.parameters['nodes'] if node in reporting_nodes]
+ cd_action = None
changed = len(nodes_to_add) > 0 or len(nodes_to_delete) > 0
if changed and not self.module.check_mode:
if nodes_to_add:
+ cd_action = 'add_node'
if self.use_rest:
for node in nodes_to_add:
self.add_lun_map_reporting_nodes_rest(node)
else:
self.add_lun_map_reporting_nodes(nodes_to_add)
if nodes_to_delete:
+ cd_action = 'remove_node'
if self.use_rest:
for node in nodes_to_delete:
self.remove_lun_map_reporting_nodes_rest(node)
else:
self.remove_lun_map_reporting_nodes(nodes_to_delete)
- self.module.exit_json(changed=changed, reporting_nodes=reporting_nodes, nodes_to_add=nodes_to_add, nodes_to_delete=nodes_to_delete)
+ result = netapp_utils.generate_result(changed, cd_action, extra_responses={'reporting_nodes': reporting_nodes,
+ 'nodes_to_add': nodes_to_add,
+ 'nodes_to_delete': nodes_to_delete})
+ self.module.exit_json(**result)
def main():
diff --git a/ansible_collections/netapp/ontap/plugins/modules/na_ontap_name_mappings.py b/ansible_collections/netapp/ontap/plugins/modules/na_ontap_name_mappings.py
index 3aa4f2df5..f6afb1546 100644
--- a/ansible_collections/netapp/ontap/plugins/modules/na_ontap_name_mappings.py
+++ b/ansible_collections/netapp/ontap/plugins/modules/na_ontap_name_mappings.py
@@ -273,7 +273,11 @@ class NetAppOntapNameMappings:
self.delete_name_mappings_rest()
elif modify or reindex:
self.modify_name_mappings_rest(modify, reindex)
- self.module.exit_json(changed=self.na_helper.changed)
+ if reindex:
+ modify['new_index'] = self.parameters.get('index')
+ modify['from_index'] = self.parameters['from_index']
+ result = netapp_utils.generate_result(self.na_helper.changed, cd_action, modify)
+ self.module.exit_json(**result)
def main():
diff --git a/ansible_collections/netapp/ontap/plugins/modules/na_ontap_net_ifgrp.py b/ansible_collections/netapp/ontap/plugins/modules/na_ontap_net_ifgrp.py
index 6ba4083e5..45035ed64 100644
--- a/ansible_collections/netapp/ontap/plugins/modules/na_ontap_net_ifgrp.py
+++ b/ansible_collections/netapp/ontap/plugins/modules/na_ontap_net_ifgrp.py
@@ -1,6 +1,6 @@
#!/usr/bin/python
-# (c) 2018-2021, 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)
'''
@@ -295,7 +295,7 @@ class NetAppOntapIfGrp:
}
return return_value
- def get_if_grp_rest(self, ports, allow_partial_match):
+ def get_if_grp_rest(self, ports, allow_partial_match, force=False):
api = 'network/ethernet/ports'
query = {
'type': 'lag',
@@ -303,7 +303,7 @@ class NetAppOntapIfGrp:
}
fields = 'name,node,uuid,broadcast_domain,lag'
error = None
- if not self.current_records:
+ if not self.current_records or force:
self.current_records, error = rest_generic.get_0_or_more_records(self.rest_api, api, query, fields)
if error:
self.module.fail_json(msg=error)
@@ -342,6 +342,7 @@ class NetAppOntapIfGrp:
current = {
'node': record['node']['name'],
'uuid': record['uuid'],
+ 'name': record['name'],
'ports': current_port_list
}
if record.get('broadcast_domain'):
@@ -496,6 +497,7 @@ class NetAppOntapIfGrp:
def apply(self):
# for a LAG, rename is equivalent to adding/removing ports from an existing LAG.
current, exact_match, modify, rename = None, True, None, None
+ response = None
if not self.use_rest:
current = self.get_if_grp()
elif self.use_rest:
@@ -523,6 +525,9 @@ class NetAppOntapIfGrp:
uuid = current['uuid'] if current and self.use_rest else None
if cd_action == 'create':
self.create_if_grp()
+ # While using REST, fetch the name of the created LAG and return as response in result
+ if self.use_rest:
+ response, exact_match = self.get_if_grp_rest(self.parameters.get('ports'), allow_partial_match=True, force=True)
elif cd_action == 'delete':
self.delete_if_grp(uuid)
elif modify:
@@ -530,7 +535,7 @@ class NetAppOntapIfGrp:
self.modify_ports_rest(modify, uuid)
else:
self.modify_ports(current_ports['ports'])
- result = netapp_utils.generate_result(self.na_helper.changed, cd_action, modify)
+ result = netapp_utils.generate_result(self.na_helper.changed, cd_action, modify, response=response)
self.module.exit_json(**result)
diff --git a/ansible_collections/netapp/ontap/plugins/modules/na_ontap_nfs.py b/ansible_collections/netapp/ontap/plugins/modules/na_ontap_nfs.py
index a1315df1b..9c4c5911f 100644
--- a/ansible_collections/netapp/ontap/plugins/modules/na_ontap_nfs.py
+++ b/ansible_collections/netapp/ontap/plugins/modules/na_ontap_nfs.py
@@ -1,6 +1,6 @@
#!/usr/bin/python
-# (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)
from __future__ import absolute_import, division, print_function
@@ -555,6 +555,9 @@ class NetAppONTAPNFS:
if error:
self.module.fail_json(msg='Error getting nfs services for SVM %s: %s' % (self.parameters['vserver'], to_native(error)),
exception=traceback.format_exc())
+ if self.rest_api.meets_rest_minimum_version(self.use_rest, 9, 11, 0):
+ if record and 'default_user' not in record.get('windows'):
+ record['windows']['default_user'] = None
return self.format_get_nfs_service_rest(record) if record else record
def format_get_nfs_service_rest(self, record):
diff --git a/ansible_collections/netapp/ontap/plugins/modules/na_ontap_node.py b/ansible_collections/netapp/ontap/plugins/modules/na_ontap_node.py
index ced6f44be..f88a8827b 100644
--- a/ansible_collections/netapp/ontap/plugins/modules/na_ontap_node.py
+++ b/ansible_collections/netapp/ontap/plugins/modules/na_ontap_node.py
@@ -1,6 +1,6 @@
#!/usr/bin/python
-# (c) 2018-2019, 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)
from __future__ import absolute_import, division, print_function
@@ -83,6 +83,7 @@ import ansible_collections.netapp.ontap.plugins.module_utils.netapp as netapp_ut
from ansible_collections.netapp.ontap.plugins.module_utils.netapp import OntapRestAPI
from ansible_collections.netapp.ontap.plugins.module_utils.netapp_module import NetAppModule
import ansible_collections.netapp.ontap.plugins.module_utils.rest_response_helpers as rrh
+import copy
HAS_NETAPP_LIB = netapp_utils.has_netapp_lib()
@@ -218,7 +219,7 @@ class NetAppOntapNode(object):
def apply(self):
from_exists = None
- modify = None
+ modify, modify_dict = None, None
uuid = None
current = self.get_node(self.parameters['name'])
if current is None and 'from_name' in self.parameters:
@@ -235,8 +236,10 @@ class NetAppOntapNode(object):
allowed_options = ['name', 'location']
if not self.use_rest:
allowed_options.append('asset_tag')
- if modify and any(x not in allowed_options for x in modify):
- self.module.fail_json(msg='Too many modified attributes found: %s, allowed: %s' % (modify, allowed_options))
+ if modify:
+ if any(x not in allowed_options for x in modify):
+ self.module.fail_json(msg='Too many modified attributes found: %s, allowed: %s' % (modify, allowed_options))
+ modify_dict = copy.deepcopy(modify)
if current is None and from_exists is None:
msg = 'from_name: %s' % self.parameters.get('from_name') if 'from_name' in self.parameters \
else 'name: %s' % self.parameters['name']
@@ -249,8 +252,8 @@ class NetAppOntapNode(object):
modify.pop('name')
if modify:
self.modify_node(modify, uuid)
-
- self.module.exit_json(changed=self.na_helper.changed)
+ result = netapp_utils.generate_result(self.na_helper.changed, modify=modify_dict)
+ self.module.exit_json(**result)
def main():
diff --git a/ansible_collections/netapp/ontap/plugins/modules/na_ontap_qos_policy_group.py b/ansible_collections/netapp/ontap/plugins/modules/na_ontap_qos_policy_group.py
index 8628efd46..b092848ac 100644
--- a/ansible_collections/netapp/ontap/plugins/modules/na_ontap_qos_policy_group.py
+++ b/ansible_collections/netapp/ontap/plugins/modules/na_ontap_qos_policy_group.py
@@ -145,6 +145,22 @@ options:
required: false
choices: ['any', '4k', '8k', '16k', '32k', '64k', '128k']
version_added: 22.6.0
+ expected_iops_allocation:
+ description:
+ - Specifies the size to be used to calculate expected IOPS per TB.
+ - Supported only with REST; requires ONTAP 9.10.1 or later.
+ type: str
+ required: false
+ choices: ['used_space', 'allocated_space']
+ version_added: 22.8.0
+ peak_iops_allocation:
+ description:
+ - Specifies the size to be used to calculate peak IOPS per TB.
+ - Supported only with REST; requires ONTAP 9.10.1 or later.
+ type: str
+ required: false
+ choices: ['used_space', 'allocated_space']
+ version_added: 22.8.0
'''
EXAMPLES = """
@@ -224,6 +240,18 @@ EXAMPLES = """
expected_iops: 200
peak_iops: 500
+ - name: modify adaptive qos policy group in REST.
+ netapp.ontap.na_ontap_qos_policy_group:
+ state: present
+ name: adaptive_policy
+ vserver: policy_vserver
+ hostname: 10.193.78.30
+ username: admin
+ password: netapp1!
+ use_rest: always
+ adaptive_qos_options:
+ expected_iops_allocation: used_space
+ peak_iops_allocation: allocated_space
"""
RETURN = """
@@ -268,7 +296,9 @@ class NetAppOntapQosPolicyGroup:
absolute_min_iops=dict(required=True, type='int'),
expected_iops=dict(required=True, type='int'),
peak_iops=dict(required=True, type='int'),
- block_size=dict(required=False, type='str', choices=['any', '4k', '8k', '16k', '32k', '64k', '128k'])
+ block_size=dict(required=False, type='str', choices=['any', '4k', '8k', '16k', '32k', '64k', '128k']),
+ expected_iops_allocation=dict(required=False, type='str', choices=['used_space', 'allocated_space']),
+ peak_iops_allocation=dict(required=False, type='str', choices=['used_space', 'allocated_space'])
))
))
@@ -297,9 +327,11 @@ class NetAppOntapQosPolicyGroup:
if not self.rest_api.meets_rest_minimum_version(self.use_rest, 9, 8) and \
self.na_helper.safe_get(self.parameters, ['fixed_qos_options', 'min_throughput_mbps']):
self.module.fail_json(msg="Minimum version of ONTAP for 'fixed_qos_options.min_throughput_mbps' is (9, 8, 0)")
+
+ ontap_9_10_adaptive_options = ['block_size', 'expected_iops_allocation', 'peak_iops_allocation']
if not self.rest_api.meets_rest_minimum_version(self.use_rest, 9, 10, 1) and \
- self.na_helper.safe_get(self.parameters, ['adaptive_qos_options', 'block_size']):
- self.module.fail_json(msg="Minimum version of ONTAP for 'adaptive_qos_options.block_size' is (9, 10, 1)")
+ any(self.na_helper.safe_get(self.parameters, ['adaptive_qos_options', option]) for option in ontap_9_10_adaptive_options):
+ self.module.fail_json(msg='Error: %s' % self.rest_api.options_require_ontap_version(ontap_9_10_adaptive_options, version='9.10.1'))
self.uuid = None
if not self.use_rest:
@@ -383,7 +415,8 @@ class NetAppOntapQosPolicyGroup:
if 'adaptive' in record:
current['adaptive_qos_options'] = {}
- for adaptive_qos_option in ['absolute_min_iops', 'expected_iops', 'peak_iops', 'block_size']:
+ for adaptive_qos_option in ['absolute_min_iops', 'expected_iops', 'peak_iops', 'block_size',
+ 'expected_iops_allocation', 'peak_iops_allocation']:
current['adaptive_qos_options'][adaptive_qos_option] = record['adaptive'].get(adaptive_qos_option)
return current
@@ -479,6 +512,11 @@ class NetAppOntapQosPolicyGroup:
if 'fixed_qos_options' in modify:
body['fixed'] = modify['fixed_qos_options']
else:
+ if 'block_size' not in self.na_helper.safe_get(modify, ['adaptive_qos_options']) and \
+ self.na_helper.safe_get(self.parameters, ['adaptive_qos_options', 'block_size']) is None:
+ # if block_size is not to be modified then remove it from the params
+ # to avoid error with block_size option during modification of other adaptive qos options
+ del self.parameters['adaptive_qos_options']['block_size']
body['adaptive'] = self.parameters['adaptive_qos_options']
dummy, error = rest_generic.patch_async(self.rest_api, api, self.uuid, body)
if error:
diff --git a/ansible_collections/netapp/ontap/plugins/modules/na_ontap_rest_info.py b/ansible_collections/netapp/ontap/plugins/modules/na_ontap_rest_info.py
index b1b5b6dae..029cf40d0 100644
--- a/ansible_collections/netapp/ontap/plugins/modules/na_ontap_rest_info.py
+++ b/ansible_collections/netapp/ontap/plugins/modules/na_ontap_rest_info.py
@@ -293,7 +293,7 @@ options:
version_added: '21.9.0'
owning_resource:
description:
- - Some resources cannot be accessed directly. You need to select them based on the owner or parent. For instance, volume for a snaphot.
+ - Some resources cannot be accessed directly. You need to select them based on the owner or parent. For instance, volume for a snapshot.
- The following subsets require an owning resource, and the following suboptions when uuid is not present.
- <storage/volumes/snapshots> B(volume_name) is the volume name, B(svm_name) is the owning vserver name for the volume.
- <protocols/nfs/export-policies/rules> B(policy_name) is the name of the policy, B(svm_name) is the owning vserver name for the policy,
@@ -310,6 +310,11 @@ options:
type: list
elements: str
version_added: '21.23.0'
+ hal_linking:
+ description:
+ - if false, HAL-encoded links are disabled in the REST calls.
+ default: true
+ type: bool
'''
EXAMPLES = '''
@@ -488,6 +493,7 @@ class NetAppONTAPGatherInfo(object):
use_python_keys=dict(type='bool', default=False),
owning_resource=dict(type='dict', required=False),
ignore_api_errors=dict(type='list', elements='str', required=False),
+ hal_linking=dict(required=False, type='bool', default=True),
))
self.module = AnsibleModule(
@@ -532,7 +538,9 @@ class NetAppONTAPGatherInfo(object):
for each in self.parameters['parameters']:
data[each] = self.parameters['parameters'][each]
- gathered_ontap_info, error = self.rest_api.get(api, data)
+ accept_header = 'application/hal+json' if self.parameters.get('hal_linking') else 'application/json'
+ headers = self.rest_api.build_headers(accept=accept_header)
+ gathered_ontap_info, error = self.rest_api.get(api, data, headers=headers)
if not error:
return gathered_ontap_info
@@ -1091,6 +1099,8 @@ class NetAppONTAPGatherInfo(object):
def add_uuid_subsets(self, get_ontap_subset_info):
params = self.parameters.get('owning_resource')
+ owning_resource_supported_subsets = ['storage/volumes/snapshots', 'protocols/nfs/export-policies/rules',
+ 'protocols/vscan/on-access-policies', 'protocols/vscan/on-demand-policies', 'protocols/vscan/scanner-pools']
if 'gather_subset' in self.parameters:
if 'storage/volumes/snapshots' in self.parameters['gather_subset']:
self.check_error_values('storage/volumes/snapshots', params, ['volume_name', 'svm_name'])
@@ -1112,6 +1122,9 @@ class NetAppONTAPGatherInfo(object):
self.add_vserver_owning_resource('protocols/vscan/on-demand-policies', params, 'protocols/vscan/%s/on-demand-policies', get_ontap_subset_info)
if 'protocols/vscan/scanner-pools' in self.parameters['gather_subset']:
self.add_vserver_owning_resource('protocols/vscan/scanner-pools', params, 'protocols/vscan/%s/scanner-pools', get_ontap_subset_info)
+ owning_resource_warning = any(subset not in owning_resource_supported_subsets for subset in self.parameters['gather_subset'])
+ if owning_resource_warning and params is not None:
+ self.module.warn("Kindly refer to Ansible documentation to check the subsets that support option 'owning_resource'.")
return get_ontap_subset_info
def add_vserver_owning_resource(self, subset, params, api, get_ontap_subset_info):
diff --git a/ansible_collections/netapp/ontap/plugins/modules/na_ontap_restit.py b/ansible_collections/netapp/ontap/plugins/modules/na_ontap_restit.py
index 7bfd63b71..fd56307f3 100644
--- a/ansible_collections/netapp/ontap/plugins/modules/na_ontap_restit.py
+++ b/ansible_collections/netapp/ontap/plugins/modules/na_ontap_restit.py
@@ -1,6 +1,6 @@
#!/usr/bin/python
'''
-# (c) 2020, 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)
'''
@@ -372,13 +372,15 @@ class NetAppONTAPRestAPI(object):
def apply(self):
''' calls the api and returns json output '''
+ changed_status = False if self.method.upper() == 'GET' else True
+
if self.module.check_mode:
status_code, response = None, {'check_mode': 'would run %s %s' % (self.method, self.api)}
elif self.wait_for_completion:
status_code, response = self.run_api_async()
else:
status_code, response = self.run_api()
- self.module.exit_json(changed=True, status_code=status_code, response=response)
+ self.module.exit_json(changed=changed_status, status_code=status_code, response=response)
def main():
diff --git a/ansible_collections/netapp/ontap/plugins/modules/na_ontap_s3_services.py b/ansible_collections/netapp/ontap/plugins/modules/na_ontap_s3_services.py
index ff5feb722..e8d8ed994 100644
--- a/ansible_collections/netapp/ontap/plugins/modules/na_ontap_s3_services.py
+++ b/ansible_collections/netapp/ontap/plugins/modules/na_ontap_s3_services.py
@@ -1,6 +1,6 @@
#!/usr/bin/python
-# (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)
@@ -86,6 +86,19 @@ EXAMPLES = """
RETURN = """
+s3_service_info:
+ description: Returns S3 service response.
+ returned: on creation or modification of S3 service
+ type: dict
+ sample: '{
+ "s3_service_info": {
+ "name": "Service1",
+ "enabled": false,
+ "certificate_name": "testSVM_177966509ABA4EC6",
+ "users": [{"name": "root"}, {"name": "user1", "access_key": "IWE711019OW02ZB3WH6Q"}],
+ "svm": {"name": "testSVM", "uuid": "39c2a5a0-35e2-11ee-b8da-005056b37403"}}
+ }
+ }'
"""
import traceback
@@ -116,21 +129,19 @@ class NetAppOntapS3Services:
self.svm_uuid = None
self.na_helper = NetAppModule(self.module)
self.parameters = self.na_helper.check_and_set_parameters(self.module)
-
self.rest_api = OntapRestAPI(self.module)
- partially_supported_rest_properties = [] # TODO: Remove if there nothing here
- self.use_rest = self.rest_api.is_rest(partially_supported_rest_properties=partially_supported_rest_properties,
- parameters=self.parameters)
-
+ self.use_rest = self.rest_api.is_rest()
self.rest_api.fail_if_not_rest_minimum_version('na_ontap_s3_services', 9, 8)
- def get_s3_service(self):
+ def get_s3_service(self, extra_field=False):
api = 'protocols/s3/services'
fields = ','.join(('name',
'enabled',
'svm.uuid',
'comment',
'certificate.name'))
+ if extra_field:
+ fields += ',users'
params = {
'name': self.parameters['name'],
@@ -192,25 +203,48 @@ class NetAppOntapS3Services:
self.svm_uuid = record['svm']['uuid']
return record
+ def parse_response(self, response):
+ if response is not None:
+ users_info = []
+ options = ['name', 'access_key', 'secret_key']
+ for user_info in response.get('users'):
+ info = {}
+ for option in options:
+ if user_info.get(option) is not None:
+ info[option] = user_info.get(option)
+ users_info.append(info)
+ return {
+ 'name': response.get('name'),
+ 'enabled': response.get('enabled'),
+ 'certificate_name': response.get('certificate_name'),
+ 'users': users_info,
+ 'svm': {'name': self.na_helper.safe_get(response, ['svm', 'name']),
+ 'uuid': self.na_helper.safe_get(response, ['svm', 'uuid'])}
+ }
+ return None
+
def apply(self):
current = self.get_s3_service()
- cd_action, modify = None, None
+ cd_action, modify, response = None, None, None
cd_action = self.na_helper.get_cd_action(current, self.parameters)
if cd_action is None:
modify = self.na_helper.get_modified_attributes(current, self.parameters)
if self.na_helper.changed and not self.module.check_mode:
if cd_action == 'create':
self.create_s3_service()
+ response = self.get_s3_service(True)
if cd_action == 'delete':
self.delete_s3_service()
if modify:
self.modify_s3_service(modify)
- result = netapp_utils.generate_result(self.na_helper.changed, cd_action, modify)
+ response = self.get_s3_service(True)
+ message = self.parse_response(response)
+ result = netapp_utils.generate_result(self.na_helper.changed, cd_action, modify, extra_responses={'s3_service_info': message})
self.module.exit_json(**result)
def main():
- '''Apply volume operations from playbook'''
+ '''Apply S3 service operations from playbook'''
obj = NetAppOntapS3Services()
obj.apply()
diff --git a/ansible_collections/netapp/ontap/plugins/modules/na_ontap_security_certificates.py b/ansible_collections/netapp/ontap/plugins/modules/na_ontap_security_certificates.py
index c7131fe5e..e6fc74e92 100644
--- a/ansible_collections/netapp/ontap/plugins/modules/na_ontap_security_certificates.py
+++ b/ansible_collections/netapp/ontap/plugins/modules/na_ontap_security_certificates.py
@@ -378,8 +378,11 @@ class NetAppOntapSecurityCertificates:
for key in required_keys + optional_keys:
if self.parameters.get(key) is not None:
body[key] = self.parameters[key]
+ params = {
+ "return_records": "true"
+ }
api = "security/certificates"
- message, error = self.rest_api.post(api, body)
+ message, error = self.rest_api.post(api, body, params)
if error:
if self.parameters.get('svm') is None and error.get('target') == 'uuid':
error['target'] = 'cluster'
@@ -399,7 +402,10 @@ class NetAppOntapSecurityCertificates:
for key in optional_keys:
if self.parameters.get(key) is not None:
body[key] = self.parameters[key]
- message, error = self.rest_api.post(api, body)
+ params = {
+ "return_records": "true"
+ }
+ message, error = self.rest_api.post(api, body, params)
if error:
self.module.fail_json(msg="Error signing certificate: %s" % error)
return message
diff --git a/ansible_collections/netapp/ontap/plugins/modules/na_ontap_service_policy.py b/ansible_collections/netapp/ontap/plugins/modules/na_ontap_service_policy.py
index f2969f720..395bbb695 100644
--- a/ansible_collections/netapp/ontap/plugins/modules/na_ontap_service_policy.py
+++ b/ansible_collections/netapp/ontap/plugins/modules/na_ontap_service_policy.py
@@ -64,13 +64,14 @@ options:
choices: ['cluster', 'svm']
known_services:
description:
- - List of known services in 9.11.1
+ - List of known services in 9.12.1
- An error is raised if any service in C(services) is not in this list or C(new_services).
- Modify this list to restrict the services you want to support if needed.
default: [cluster_core, intercluster_core, management_core, management_autosupport, management_bgp, management_ems, management_https, management_http,
management_ssh, management_portmap, data_core, data_nfs, data_cifs, data_flexcache, data_iscsi, data_s3_server, data_dns_server,
data_fpolicy_client, management_ntp_client, management_dns_client, management_ad_client, management_ldap_client, management_nis_client,
- management_snmp_server, management_rsh_server, management_telnet_server, management_ntp_server, data_nvme_tcp, backup_ndmp_control]
+ management_snmp_server, management_rsh_server, management_telnet_server, management_ntp_server, data_nvme_tcp, backup_ndmp_control,
+ management_log_forwarding]
type: list
elements: str
version_added: 22.0.0
@@ -184,7 +185,7 @@ class NetAppOntapServicePolicy:
'data_flexcache', 'data_iscsi', 'data_s3_server', 'data_dns_server', 'data_fpolicy_client', 'management_ntp_client',
'management_dns_client', 'management_ad_client', 'management_ldap_client', 'management_nis_client',
'management_snmp_server', 'management_rsh_server', 'management_telnet_server', 'management_ntp_server',
- 'data_nvme_tcp', 'backup_ndmp_control']),
+ 'data_nvme_tcp', 'backup_ndmp_control', 'management_log_forwarding']),
additional_services=dict(type='list', elements='str')
))
diff --git a/ansible_collections/netapp/ontap/plugins/modules/na_ontap_snapmirror.py b/ansible_collections/netapp/ontap/plugins/modules/na_ontap_snapmirror.py
index 26254e03b..e53358041 100644
--- a/ansible_collections/netapp/ontap/plugins/modules/na_ontap_snapmirror.py
+++ b/ansible_collections/netapp/ontap/plugins/modules/na_ontap_snapmirror.py
@@ -595,6 +595,7 @@ class NetAppONTAPSnapmirror(object):
self.na_helper = NetAppModule()
self.parameters = self.na_helper.set_parameters(self.module.params)
+ self.policy_type = None
self.new_style = False
# when deleting, ignore previous errors, but report them if delete fails
self.previous_errors = []
@@ -1051,7 +1052,8 @@ class NetAppONTAPSnapmirror(object):
resync SnapMirror based on relationship state
"""
if self.use_rest:
- self.snapmirror_mod_init_resync_break_quiesce_resume_rest(state="snapmirrored")
+ state = 'in_sync' if self.policy_type == 'sync' else 'snapmirrored'
+ self.snapmirror_mod_init_resync_break_quiesce_resume_rest(state=state)
else:
options = {'destination-location': self.parameters['destination_path']}
snapmirror_resync = netapp_utils.zapi.NaElement.create_node_with_children('snapmirror-resync', **options)
@@ -1067,7 +1069,8 @@ class NetAppONTAPSnapmirror(object):
resume SnapMirror based on relationship state
"""
if self.use_rest:
- return self.snapmirror_mod_init_resync_break_quiesce_resume_rest(state="snapmirrored")
+ state = 'in_sync' if self.policy_type == 'sync' else 'snapmirrored'
+ return self.snapmirror_mod_init_resync_break_quiesce_resume_rest(state=state)
options = {'destination-location': self.parameters['destination_path']}
snapmirror_resume = netapp_utils.zapi.NaElement.create_node_with_children('snapmirror-resume', **options)
@@ -1482,7 +1485,7 @@ class NetAppONTAPSnapmirror(object):
destination = self.parameters['destination_path']
api = 'snapmirror/relationships'
- fields = 'uuid,state,transfer.state,transfer.uuid,policy.name,unhealthy_reason.message,healthy,source'
+ fields = 'uuid,state,transfer.state,transfer.uuid,policy.name,policy.type,unhealthy_reason.message,healthy,source'
if 'schedule' in self.parameters:
fields += ',transfer_schedule'
options = {'destination.path': destination, 'fields': fields}
@@ -1499,9 +1502,10 @@ class NetAppONTAPSnapmirror(object):
snap_info['status'] = self.na_helper.safe_get(record, ['transfer', 'state'])
self.parameters['current_transfer_status'] = self.na_helper.safe_get(record, ['transfer', 'state'])
snap_info['policy'] = self.na_helper.safe_get(record, ['policy', 'name'])
+ self.policy_type = self.na_helper.safe_get(record, ['policy', 'type'])
# REST API supports only Extended Data Protection (XDP) SnapMirror relationship
snap_info['relationship_type'] = 'extended_data_protection'
- # initilized to avoid name keyerror
+ # initialized to avoid name keyerror
snap_info['current_transfer_type'] = ""
snap_info['max_transfer_rate'] = ""
if 'unhealthy_reason' in record:
@@ -1741,8 +1745,8 @@ class NetAppONTAPSnapmirror(object):
def main():
"""Execute action"""
- community_obj = NetAppONTAPSnapmirror()
- community_obj.apply()
+ snapmirror_obj = NetAppONTAPSnapmirror()
+ snapmirror_obj.apply()
if __name__ == '__main__':
diff --git a/ansible_collections/netapp/ontap/plugins/modules/na_ontap_snapshot_policy.py b/ansible_collections/netapp/ontap/plugins/modules/na_ontap_snapshot_policy.py
index 1d271657a..ee1a63be5 100644
--- a/ansible_collections/netapp/ontap/plugins/modules/na_ontap_snapshot_policy.py
+++ b/ansible_collections/netapp/ontap/plugins/modules/na_ontap_snapshot_policy.py
@@ -1,6 +1,6 @@
#!/usr/bin/python
-# (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)
@@ -611,6 +611,7 @@ class NetAppOntapSnapshotPolicy(object):
api = 'storage/snapshot-policies/%s/schedules' % current['uuid']
schedule_info = self.get_snapshot_schedule_rest(current)
delete_schedules, modify_schedules, add_schedules = [], [], []
+ retain_schedules_count = 0
if 'snapmirror_label' in self.parameters:
snapmirror_labels = self.parameters['snapmirror_label']
@@ -629,6 +630,8 @@ class NetAppOntapSnapshotPolicy(object):
schedule_name = self.safe_strip(schedule_name)
if schedule_name not in [item.strip() for item in self.parameters['schedule']]:
delete_schedules.append(schedule_uuid)
+ else:
+ retain_schedules_count += 1
# Identify schedules to be modified or added
for schedule_name, count, snapmirror_label, prefix in zip(self.parameters['schedule'], self.parameters['count'], snapmirror_labels, prefixes):
@@ -668,9 +671,11 @@ class NetAppOntapSnapshotPolicy(object):
body['prefix'] = prefix
add_schedules.append(body)
- # Delete N-1 schedules no longer required. Must leave 1 schedule in policy
+ # Delete N schedules no longer required if there is at least 1 schedule is to be retained
+ # Otherwise, delete N-1 schedules no longer required as policy must have at least 1 schedule
# at any one time. Delete last one afterwards.
- while len(delete_schedules) > 1:
+ count = 0 if retain_schedules_count > 0 else 1
+ while len(delete_schedules) > count:
schedule_uuid = delete_schedules.pop()
record, error = rest_generic.delete_async(self.rest_api, api, schedule_uuid)
if error is not None:
diff --git a/ansible_collections/netapp/ontap/plugins/modules/na_ontap_snmp.py b/ansible_collections/netapp/ontap/plugins/modules/na_ontap_snmp.py
index c1f278e0d..acde02da2 100644
--- a/ansible_collections/netapp/ontap/plugins/modules/na_ontap_snmp.py
+++ b/ansible_collections/netapp/ontap/plugins/modules/na_ontap_snmp.py
@@ -3,7 +3,7 @@
create SNMP module to add/delete/modify SNMP user
"""
-# (c) 2018-2021, 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)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
@@ -12,7 +12,7 @@ __metaclass__ = type
DOCUMENTATION = '''
author: NetApp Ansible Team (@carchi8py) <ng-ansibleteam@netapp.com>
description:
- - "Create/Delete SNMP community"
+ - Create/Delete SNMP user.
extends_documentation_fragment:
- netapp.ontap.netapp.na_ontap
module: na_ontap_snmp
@@ -20,21 +20,60 @@ options:
access_control:
choices: ['ro']
description:
- - "Access control for the community. The only supported value is 'ro' (read-only). Ignored with REST"
+ - Access control for the community. The only supported value is 'ro' (read-only).
+ - Ignored with REST.
default: 'ro'
type: str
- community_name:
+ snmp_username:
description:
- - "The name of the SNMP community to manage."
+ - The name of the SNMP user to manage.
required: true
type: str
+ version_added: 22.8.0
state:
choices: ['present', 'absent']
description:
- - "Whether the specified SNMP community should exist or not."
+ - Whether the specified SNMP user should exist or not.
default: 'present'
type: str
-short_description: NetApp ONTAP SNMP community
+ authentication_method:
+ choices: ['community', 'usm', 'both']
+ description:
+ - Authentication method for SNMP user.
+ - Only supported with REST. The default value is community.
+ type: str
+ version_added: 22.8.0
+ snmpv3:
+ description:
+ - Specify only when C(authentication_method) is either C(usm) or C(both).
+ - This option defines the SNMPv3 credentials for an SNMPv3 user or also called usm user.
+ - Only supported with REST.
+ type: dict
+ version_added: 22.8.0
+ suboptions:
+ authentication_password:
+ description:
+ - Authentication protocol password.
+ type: str
+ required: true
+ authentication_protocol:
+ choices: ['none', 'md5', 'sha', 'sha2_256']
+ description:
+ - Authentication protocol for SNMPv3.
+ default: 'none'
+ type: str
+ privacy_password:
+ description:
+ - Privacy protocol password.
+ type: str
+ required: true
+ privacy_protocol:
+ choices: ['none', 'des', 'aes128']
+ description:
+ - Privacy protocol for SNMPv3.
+ default: 'none'
+ type: str
+short_description: NetApp ONTAP SNMP user
version_added: 2.6.0
'''
@@ -42,34 +81,60 @@ EXAMPLES = """
- name: Create SNMP community (ZAPI only)
netapp.ontap.na_ontap_snmp:
state: present
- community_name: communityName
+ snmp_username: communityName
access_control: 'ro'
use_rest: never
hostname: "{{ netapp_hostname }}"
username: "{{ netapp_username }}"
password: "{{ netapp_password }}"
+
- name: Create SNMP community (snmpv1 or snmpv2) (REST only)
netapp.ontap.na_ontap_snmp:
state: present
- community_name: communityName
+ snmp_username: communityName
use_rest: always
hostname: "{{ netapp_hostname }}"
username: "{{ netapp_username }}"
password: "{{ netapp_password }}"
+ - name: Create SNMP user (snmpv3) (REST only)
+ netapp.ontap.na_ontap_snmp:
+ state: present
+ snmp_username: username
+ use_rest: always
+ authentication_method: usm
+ snmpv3:
+ authentication_protocol: sha
+ authentication_password: humTdumt*@t0nAwa21
+ privacy_protocol: aes128
+ privacy_password: p@**GOandCLCt*300
+ hostname: "{{ netapp_hostname }}"
+ username: "{{ netapp_username }}"
+ password: "{{ netapp_password }}"
+
- name: Delete SNMP community (ZAPI only)
netapp.ontap.na_ontap_snmp:
state: absent
- community_name: communityName
+ snmp_username: communityName
access_control: 'ro'
use_rest: never
hostname: "{{ netapp_hostname }}"
username: "{{ netapp_username }}"
password: "{{ netapp_password }}"
+
- name: Delete SNMP community (snmpv1 or snmpv2) (REST only)
netapp.ontap.na_ontap_snmp:
state: absent
- community_name: communityName
+ snmp_username: communityName
+ use_rest: always
+ hostname: "{{ netapp_hostname }}"
+ username: "{{ netapp_username }}"
+ password: "{{ netapp_password }}"
+
+ - name: Delete SNMP user (snmpv3) (REST only)
+ netapp.ontap.na_ontap_snmp:
+ state: absent
+ snmp_username: username
use_rest: always
hostname: "{{ netapp_hostname }}"
username: "{{ netapp_username }}"
@@ -96,8 +161,17 @@ class NetAppONTAPSnmp(object):
self.argument_spec = netapp_utils.na_ontap_host_argument_spec()
self.argument_spec.update(dict(
state=dict(required=False, type='str', choices=['present', 'absent'], default='present'),
- community_name=dict(required=True, type='str'),
+ snmp_username=dict(required=True, type='str'),
access_control=dict(required=False, type='str', choices=['ro'], default='ro'),
+ authentication_method=dict(required=False, type='str', choices=['community', 'usm', 'both']),
+ snmpv3=dict(required=False, type='dict',
+ options=dict(
+ authentication_password=dict(required=True, type='str', no_log=True),
+ privacy_protocol=dict(required=False, type='str', choices=['none', 'des', 'aes128'], default='none'),
+ authentication_protocol=dict(required=False, type='str', choices=['none', 'md5', 'sha', 'sha2_256'], default='none'),
+ privacy_password=dict(required=True, type='str', no_log=True),
+ )
+ )
))
self.module = AnsibleModule(
@@ -113,18 +187,29 @@ class NetAppONTAPSnmp(object):
self.rest_api = OntapRestAPI(self.module)
self.use_rest = self.rest_api.is_rest()
+ self.unsupported_zapi_properties = ['authentication_method', 'snmpv3', 'authentication_protocol', 'authentication_password', 'privacy_protocol',
+ 'privacy_password']
+
+ if self.use_rest:
+ if self.parameters.get('authentication_method') == 'community' and 'snmpv3' in self.parameters:
+ self.module.fail_json("SNMPv3 user can be created when 'authentication_method' is either 'usm' or 'both'")
+
if not self.use_rest:
if HAS_NETAPP_LIB is False:
self.module.fail_json(msg="the python NetApp-Lib module is required")
- else:
- self.server = netapp_utils.setup_na_ontap_zapi(module=self.module)
+
+ for unsupported_zapi_property in self.unsupported_zapi_properties:
+ if self.parameters.get(unsupported_zapi_property) is not None:
+ msg = "Error: %s option is not supported with ZAPI. It can only be used with REST." % unsupported_zapi_property
+ self.module.fail_json(msg=msg)
+ self.server = netapp_utils.setup_na_ontap_zapi(module=self.module)
def invoke_snmp_community(self, zapi):
"""
Invoke zapi - add/delete take the same NaElement structure
"""
snmp_community = netapp_utils.zapi.NaElement.create_node_with_children(
- zapi, **{'community': self.parameters['community_name'],
+ zapi, **{'community': self.parameters['snmp_username'],
'access-control': self.parameters['access_control']})
try:
self.server.invoke_successfully(snmp_community, enable_tunneling=True)
@@ -135,7 +220,7 @@ class NetAppONTAPSnmp(object):
action = 'deleting'
else:
action = 'unexpected'
- self.module.fail_json(msg='Error %s community %s: %s' % (action, self.parameters['community_name'], to_native(error)),
+ self.module.fail_json(msg='Error %s community %s: %s' % (action, self.parameters['snmp_username'], to_native(error)),
exception=traceback.format_exc())
def get_snmp(self):
@@ -151,19 +236,19 @@ class NetAppONTAPSnmp(object):
self.module.fail_json(msg=to_native(error), exception=traceback.format_exc())
if result.get_child_by_name('communities') is not None:
for snmp_entry in result.get_child_by_name('communities').get_children():
- community_name = snmp_entry.get_child_content('community')
- if community_name == self.parameters['community_name']:
+ snmp_username = snmp_entry.get_child_content('community')
+ if snmp_username == self.parameters['snmp_username']:
return {
- 'community_name': snmp_entry.get_child_content('community'),
+ 'snmp_username': snmp_entry.get_child_content('community'),
'access_control': snmp_entry.get_child_content('access-control'),
}
return None
def get_snmp_rest(self):
# There can be SNMPv1, SNMPv2 (called community) or
- # SNMPv3 local or SNMPv3 remote (called users)
+ # SNMPv3 (called usm users)
api = 'support/snmp/users'
- params = {'name': self.parameters['community_name'],
+ params = {'name': self.parameters['snmp_username'],
'fields': 'name,engine_id'}
message, error = self.rest_api.get(api, params)
record, error = rrh.check_for_0_or_1_records(api, message, error)
@@ -171,56 +256,56 @@ class NetAppONTAPSnmp(object):
self.module.fail_json(msg=error)
if record:
# access control does not exist in rest
- return dict(community_name=record['name'], engine_id=record['engine_id'], access_control='ro')
+ return dict(snmp_username=record['name'], engine_id=record['engine_id'], access_control='ro')
return None
- def add_snmp_community(self):
+ def add_snmp_user(self):
"""
- Adds a SNMP community
+ Add a SNMP user
"""
if self.use_rest:
- self.add_snmp_community_rest()
+ self.add_snmp_rest()
else:
self.invoke_snmp_community('snmp-community-add')
- def add_snmp_community_rest(self):
+ def add_snmp_rest(self):
api = 'support/snmp/users'
- params = {'name': self.parameters['community_name'],
- 'authentication_method': 'community'}
- message, error = self.rest_api.post(api, params)
+ self.parameters['authentication_method'] = self.parameters.get('authentication_method', 'community')
+ body = {
+ 'name': self.parameters['snmp_username'],
+ 'authentication_method': self.parameters['authentication_method']
+ }
+ if self.parameters.get('authentication_method') == 'usm' or self.parameters.get('authentication_method') == 'both':
+ if self.parameters.get('snmpv3'):
+ body['snmpv3'] = self.parameters['snmpv3']
+ message, error = self.rest_api.post(api, body)
if error:
self.module.fail_json(msg=error)
- def delete_snmp_community(self, current=None):
+ def delete_snmp_user(self, current=None):
"""
- Delete a SNMP community
+ Delete a SNMP user
"""
if self.use_rest:
- self.delete_snmp_community_rest(current)
+ self.delete_snmp_rest(current)
else:
self.invoke_snmp_community('snmp-community-delete')
- def delete_snmp_community_rest(self, current):
- api = 'support/snmp/users/' + current['engine_id'] + '/' + self.parameters["community_name"]
+ def delete_snmp_rest(self, current):
+ api = 'support/snmp/users/' + current['engine_id'] + '/' + self.parameters["snmp_username"]
dummy, error = self.rest_api.delete(api)
if error:
self.module.fail_json(msg=error)
def apply(self):
- """
- Apply action to SNMP community
- This module is not idempotent:
- Add doesn't fail the playbook if user is trying
- to add an already existing snmp community
- """
- # TODO: This module should of been called snmp_community has it only deals with community and not snmp
+ # TODO: This module should have been called snmp_community as it only deals with community and not snmp
current = self.get_snmp()
cd_action = self.na_helper.get_cd_action(current, self.parameters)
if self.na_helper.changed and not self.module.check_mode:
if cd_action == 'create':
- self.add_snmp_community()
+ self.add_snmp_user()
elif cd_action == 'delete':
- self.delete_snmp_community(current)
+ self.delete_snmp_user(current)
result = netapp_utils.generate_result(self.na_helper.changed, cd_action)
self.module.exit_json(**result)
diff --git a/ansible_collections/netapp/ontap/plugins/modules/na_ontap_snmp_config.py b/ansible_collections/netapp/ontap/plugins/modules/na_ontap_snmp_config.py
new file mode 100644
index 000000000..83fed1655
--- /dev/null
+++ b/ansible_collections/netapp/ontap/plugins/modules/na_ontap_snmp_config.py
@@ -0,0 +1,142 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: 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
+__metaclass__ = type
+
+DOCUMENTATION = """
+module: na_ontap_snmp_config
+short_description: NetApp ONTAP module to modify SNMP configuration.
+extends_documentation_fragment:
+ - netapp.ontap.netapp.na_ontap
+version_added: '22.9.0'
+author: NetApp Ansible Team (@carchi8py) <ng-ansibleteam@netapp.com>
+description:
+ - Modify cluster wide SNMP configuration.
+ - Enable or disable SNMP on a cluster.
+options:
+ state:
+ description:
+ - Modify SNMP configuration, only present is supported.
+ choices: ['present']
+ type: str
+ default: present
+ enabled:
+ description:
+ - Specifies whether to enable or disable SNMP.
+ type: bool
+ required: false
+ auth_traps_enabled:
+ description:
+ - Specifies whether to enable or disable SNMP authentication traps.
+ type: bool
+ required: false
+ traps_enabled:
+ description:
+ - Specifies whether to enable or disable SNMP traps.
+ - Requires ONTAP 9.10.1 or later.
+ type: bool
+ required: false
+
+notes:
+ - Only supported with REST and requires ONTAP 9.7 or later.
+"""
+
+EXAMPLES = """
+ - name: Disable SNMP on cluster
+ netapp.ontap.na_ontap_snmp_config:
+ state: present
+ enabled: false
+ hostname: "{{ netapp_hostname }}"
+ username: "{{ netapp_username }}"
+ password: "{{ netapp_password }}"
+ https: true
+ validate_certs: "{{ validate_certs }}"
+
+ - name: Modify SNMP configuration
+ netapp.ontap.na_ontap_snmp_config:
+ state: present
+ auth_traps_enabled: true
+ traps_enabled: true
+ hostname: "{{ netapp_hostname }}"
+ username: "{{ netapp_username }}"
+ password: "{{ netapp_password }}"
+ https: true
+ validate_certs: "{{ validate_certs }}"
+"""
+
+RETURN = """
+"""
+
+import traceback
+from ansible.module_utils.basic import AnsibleModule
+from ansible.module_utils._text import to_native
+import ansible_collections.netapp.ontap.plugins.module_utils.netapp as netapp_utils
+from ansible_collections.netapp.ontap.plugins.module_utils.netapp_module import NetAppModule
+from ansible_collections.netapp.ontap.plugins.module_utils import rest_generic
+
+
+class NetAppOntapSNMPConfig:
+ def __init__(self):
+ self.argument_spec = netapp_utils.na_ontap_host_argument_spec()
+ self.argument_spec.update(dict(
+ state=dict(required=False, type='str', choices=['present'], default='present'),
+ enabled=dict(required=False, type='bool'),
+ auth_traps_enabled=dict(required=False, type='bool'),
+ traps_enabled=dict(required=False, type='bool')
+ ))
+ self.module = AnsibleModule(
+ argument_spec=self.argument_spec,
+ supports_check_mode=True
+ )
+ self.uuid = None
+ self.na_helper = NetAppModule(self.module)
+ self.parameters = self.na_helper.check_and_set_parameters(self.module)
+ self.rest_api = netapp_utils.OntapRestAPI(self.module)
+ self.rest_api.fail_if_not_rest_minimum_version('na_ontap_snmp_config:', 9, 7)
+ self.use_rest = self.rest_api.is_rest_supported_properties(self.parameters, None, [['traps_enabled', (9, 10, 1)]])
+
+ def get_snmp_config_rest(self):
+ """Retrieve cluster wide SNMP configuration"""
+ fields = 'enabled'
+ if self.parameters.get('auth_traps_enabled') is not None:
+ fields += ',auth_traps_enabled'
+ if 'traps_enabled' in self.parameters and self.rest_api.meets_rest_minimum_version(self.use_rest, 9, 10, 1):
+ fields += ',traps_enabled'
+ record, error = rest_generic.get_one_record(self.rest_api, 'support/snmp', None, fields)
+ if error:
+ self.module.fail_json(msg="Error fetching SNMP configuration: %s" % to_native(error), exception=traceback.format_exc())
+ if record:
+ return {
+ 'enabled': record.get('enabled'),
+ 'auth_traps_enabled': record.get('auth_traps_enabled'),
+ 'traps_enabled': record.get('traps_enabled')
+ }
+ return None
+
+ def modify_snmp_config_rest(self, modify):
+ """Update cluster wide SNMP configuration"""
+ dummy, error = rest_generic.patch_async(self.rest_api, 'support/snmp', None, modify)
+ if error:
+ self.module.fail_json(msg='Error modifying SNMP configuration: %s.' % to_native(error), exception=traceback.format_exc())
+
+ def apply(self):
+ current = self.get_snmp_config_rest()
+ modify = self.na_helper.get_modified_attributes(current, self.parameters)
+
+ if self.na_helper.changed and not self.module.check_mode:
+ self.modify_snmp_config_rest(modify)
+ result = netapp_utils.generate_result(changed=self.na_helper.changed, modify=modify)
+ self.module.exit_json(**result)
+
+
+def main():
+ snmp_config = NetAppOntapSNMPConfig()
+ snmp_config.apply()
+
+
+if __name__ == '__main__':
+ main()
diff --git a/ansible_collections/netapp/ontap/plugins/modules/na_ontap_storage_auto_giveback.py b/ansible_collections/netapp/ontap/plugins/modules/na_ontap_storage_auto_giveback.py
index 4446371d1..298b07c4b 100644
--- a/ansible_collections/netapp/ontap/plugins/modules/na_ontap_storage_auto_giveback.py
+++ b/ansible_collections/netapp/ontap/plugins/modules/na_ontap_storage_auto_giveback.py
@@ -1,6 +1,6 @@
#!/usr/bin/python
-# (c) 2021, NetApp, Inc
+# (c) 2021-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
@@ -128,8 +128,8 @@ class NetAppOntapStorageAutoGiveback(object):
if error is None and records is not None:
return_value = {
'name': message['records'][0]['node'],
- 'auto_giveback_enabled': message['records'][0]['auto_giveback'],
- 'auto_giveback_after_panic_enabled': message['records'][0]['auto_giveback_after_panic']
+ 'auto_giveback_enabled': message['records'][0].get('auto_giveback'),
+ 'auto_giveback_after_panic_enabled': message['records'][0].get('auto_giveback_after_panic')
}
if error:
@@ -228,12 +228,13 @@ class NetAppOntapStorageAutoGiveback(object):
def apply(self):
current = self.get_storage_auto_giveback()
- self.na_helper.get_modified_attributes(current, self.parameters)
+ modify = self.na_helper.get_modified_attributes(current, self.parameters)
if self.na_helper.changed:
if not self.module.check_mode:
self.modify_storage_auto_giveback()
- self.module.exit_json(changed=self.na_helper.changed)
+ result = netapp_utils.generate_result(self.na_helper.changed, modify=modify)
+ self.module.exit_json(**result)
def main():
diff --git a/ansible_collections/netapp/ontap/plugins/modules/na_ontap_svm.py b/ansible_collections/netapp/ontap/plugins/modules/na_ontap_svm.py
index 9d5fc6c66..28a10252c 100644
--- a/ansible_collections/netapp/ontap/plugins/modules/na_ontap_svm.py
+++ b/ansible_collections/netapp/ontap/plugins/modules/na_ontap_svm.py
@@ -170,7 +170,7 @@ options:
description:
- If this is set to true, an SVM administrator can manage the NDMP service
- If it is false, only the cluster administrator can manage the service.
- - Requires ONTAP 9.7 or later.
+ - Requires ONTAP 9.10.1 or later.
type: bool
version_added: 21.24.0
aggr_list:
@@ -469,9 +469,9 @@ class NetAppOntapSVM():
]
if errors:
self.module.fail_json(msg='Error - %s' % ' '.join(errors))
- if use_rest and self.parameters.get('services') and not self.parameters.get('allowed_protocols') and self.parameters['services'].get('ndmp')\
- and not self.rest_api.meets_rest_minimum_version(use_rest, 9, 7):
- self.module.fail_json(msg=self.rest_api.options_require_ontap_version('ndmp', '9.7', use_rest=use_rest))
+ if use_rest and self.parameters.get('services') and not self.parameters.get('allowed_protocols'):
+ if self.parameters['services'].get('ndmp') and not self.rest_api.meets_rest_minimum_version(use_rest, 9, 10, 1):
+ self.module.fail_json(msg=self.rest_api.options_require_ontap_version('ndmp', '9.10.1', use_rest=use_rest))
if self.parameters.get('services') and not use_rest:
self.module.fail_json(msg=self.rest_api.options_require_ontap_version('services', use_rest=use_rest))
if self.parameters.get('web'):
diff --git a/ansible_collections/netapp/ontap/plugins/modules/na_ontap_user.py b/ansible_collections/netapp/ontap/plugins/modules/na_ontap_user.py
index 7fada8ac6..e95ab492a 100644
--- a/ansible_collections/netapp/ontap/plugins/modules/na_ontap_user.py
+++ b/ansible_collections/netapp/ontap/plugins/modules/na_ontap_user.py
@@ -654,7 +654,8 @@ class NetAppOntapUser:
error = self.patch_account(owner_uuid, username, body)
if error:
if 'message' in error and self.is_repeated_password(error['message']):
- # if the password is reused, assume idempotency
+ # if the password is reused, assume idempotency but show a warning
+ self.module.warn('Password was not changed: %s' % error['message'])
return False
self.module.fail_json(msg='Error while updating user password: %s' % error)
return True
diff --git a/ansible_collections/netapp/ontap/plugins/modules/na_ontap_volume.py b/ansible_collections/netapp/ontap/plugins/modules/na_ontap_volume.py
index 7ca007c29..8f5d827e7 100644
--- a/ansible_collections/netapp/ontap/plugins/modules/na_ontap_volume.py
+++ b/ansible_collections/netapp/ontap/plugins/modules/na_ontap_volume.py
@@ -4,17 +4,11 @@
# GNU General Public License v3.0+
# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
-'''
-na_ontap_volume
-'''
-
from __future__ import absolute_import, division, print_function
__metaclass__ = type
DOCUMENTATION = '''
-
module: na_ontap_volume
-
short_description: NetApp ONTAP manage volumes.
extends_documentation_fragment:
- netapp.ontap.netapp.na_ontap
@@ -307,6 +301,7 @@ options:
- This is an advanced option, the default is False.
- Enable the visible '.snapshot' directory that is normally present at system internal mount points.
- This value also turns on access to all other '.snapshot' directories in the volume.
+ - This option is supported in REST for ONTAP 9.13.1 or later with ONTAP collection version 22.8.0 or later.
type: bool
version_added: 2.8.0
@@ -318,9 +313,28 @@ options:
since it prevents writes to the inode file for the volume from contending with reads from other files.
- This field should be used carefully.
- That is, use this field when you know in advance that the correct access time for inodes will not be needed for files on that volume.
+ - This option is supported in REST for ONTAP 9.8 or later with ONTAP collection version 22.8.0 or later.
type: bool
version_added: 2.8.0
+ vol_nearly_full_threshold_percent:
+ description:
+ - Specifies the percentage at which the volume is considered nearly full, and above which an EMS warning will be generated.
+ - The default value is 95%. The maximum value for this option is 99%.
+ - Setting this threshold to 0 disables the volume nearly full space alerts.
+ - Supported only with in REST for ONTAP 9.9 or later.
+ type: int
+ version_added: 22.8.0
+
+ vol_full_threshold_percent:
+ description:
+ - Specifies the percentage at which the volume is considered full, and above which a critical EMS error will be generated.
+ - The default value is 98%. The maximum value for this option is 100%.
+ - Setting this threshold to 0 disables the volume full space alerts.
+ - Supported only with in REST for ONTAP 9.9 or later.
+ type: int
+ version_added: 22.8.0
+
wait_for_completion:
description:
- Set this parameter to 'true' for synchronous execution during create (wait until volume status is online)
@@ -346,7 +360,7 @@ options:
description:
- Volume move and encryption operations might take longer time to complete.
- With C(wait_for_completion) set, module will wait for time set in this option for volume move and encryption to complete.
- - If time exipres, module exit and the operation may still running.
+ - If time exipres, module exit and the operation may still be running.
- Default is set to 10 minutes.
default: 600
type: int
@@ -459,6 +473,7 @@ options:
- A dictionary for the auto delete options and values.
- Supported options include 'state', 'commitment', 'trigger', 'target_free_space', 'delete_order', 'defer_delete',
'prefix', 'destroy_list'.
+ - All the above mentioned options except 'destroy_list' are supported in REST for ONTAP 9.13.1 or later with ONTAP collection version 22.8.0 or later.
- Option 'state' determines if the snapshot autodelete is currently enabled for the volume. Possible values are 'on' and 'off'.
- Option 'commitment' determines the snapshots which snapshot autodelete is allowed to delete to get back space.
Possible values are 'try', 'disrupt' and 'destroy'.
@@ -880,6 +895,33 @@ EXAMPLES = """
retention:
default: "{{ 60 | netapp.ontap.iso8601_duration_from_seconds }}"
+ - name: Create volume with snapshot-auto-delete options - REST
+ netapp.ontap.na_ontap_volume:
+ state: present
+ name: test_vol
+ aggregate_name: "{{ aggr }}"
+ size: 20
+ size_unit: mb
+ snapshot_auto_delete:
+ state: 'on'
+ trigger: volume
+ delete_order: "oldest_first"
+ defer_delete: "user_created"
+ commitment: "try"
+ target_free_space: 30
+ prefix: "my_prefix"
+ wait_for_completion: true
+
+ - name: Modify volume - REST
+ netapp.ontap.na_ontap_volume:
+ state: present
+ name: test_vol
+ aggregate_name: "{{ aggr }}"
+ snapdir_access: false
+ snapshot_auto_delete:
+ state: 'on'
+ target_free_space: 25
+
"""
RETURN = """
@@ -929,6 +971,8 @@ class NetAppOntapVolume:
aggr_list_multiplier=dict(required=False, type='int'),
snapdir_access=dict(required=False, type='bool'),
atime_update=dict(required=False, type='bool'),
+ vol_nearly_full_threshold_percent=dict(required=False, type='int'),
+ vol_full_threshold_percent=dict(required=False, type='int'),
auto_provision_as=dict(choices=['flexgroup'], required=False, type='str'),
wait_for_completion=dict(required=False, type='bool', default=False),
time_out=dict(required=False, type='int', default=180),
@@ -1021,19 +1065,20 @@ class NetAppOntapVolume:
netapp_utils.POW2_BYTE_MAP[self.parameters['size_unit']]
self.validate_snapshot_auto_delete()
self.rest_api = netapp_utils.OntapRestAPI(self.module)
- unsupported_rest_properties = ['atime_update',
- 'cutover_action',
+ unsupported_rest_properties = ['cutover_action',
'encrypt-destination',
'force_restore',
'nvfail_enabled',
'preserve_lun_ids',
- 'snapdir_access',
- 'snapshot_auto_delete',
+ 'destroy_list',
'space_slo',
'vserver_dr_protection']
- partially_supported_rest_properties = [['efficiency_policy', (9, 7)], ['tiering_minimum_cooling_days', (9, 8)], ['analytics', (9, 8)],
- ['tags', (9, 13, 1)]]
- self.unsupported_zapi_properties = ['sizing_method', 'logical_space_enforcement', 'logical_space_reporting', 'snaplock', 'analytics', 'tags']
+ partially_supported_rest_properties = [['efficiency_policy', (9, 7)], ['tiering_minimum_cooling_days', (9, 8)],
+ ['analytics', (9, 8)], ['atime_update', (9, 8)],
+ ['vol_nearly_full_threshold_percent', (9, 9)], ['vol_full_threshold_percent', (9, 9)],
+ ['tags', (9, 13, 1)], ['snapdir_access', (9, 13, 1)], ['snapshot_auto_delete', (9, 13, 1)]]
+ self.unsupported_zapi_properties = ['sizing_method', 'logical_space_enforcement', 'logical_space_reporting', 'snaplock',
+ 'analytics', 'tags', 'vol_nearly_full_threshold_percent', 'vol_full_threshold_percent']
self.use_rest = self.rest_api.is_rest_supported_properties(self.parameters, unsupported_rest_properties, partially_supported_rest_properties)
if not self.use_rest:
@@ -1313,6 +1358,25 @@ class NetAppOntapVolume:
self.na_helper.fail_on_error(error)
return response
+ def wait_for_volume_online(self, sleep_time=10):
+ # round off time_out
+ retries = (self.parameters['time_out'] + 5) // 10
+ is_online = None
+ errors = []
+ while not is_online and retries > 0:
+ try:
+ current = self.get_volume()
+ is_online = None if current is None else current['is_online']
+ except KeyError as err:
+ # get_volume may receive incomplete data as the volume is being created
+ errors.append(repr(err))
+ if not is_online:
+ time.sleep(sleep_time)
+ retries -= 1
+ if not is_online:
+ errors.append("Timeout after %s seconds" % self.parameters['time_out'])
+ self.module.fail_json(msg='Error waiting for volume %s to come online: %s' % (self.parameters['name'], str(errors)))
+
def create_volume(self):
'''Create ONTAP volume'''
if self.rest_app:
@@ -1333,24 +1397,7 @@ class NetAppOntapVolume:
exception=traceback.format_exc())
if self.parameters.get('wait_for_completion'):
- # round off time_out
- retries = (self.parameters['time_out'] + 5) // 10
- is_online = None
- errors = []
- while not is_online and retries > 0:
- try:
- current = self.get_volume()
- is_online = None if current is None else current['is_online']
- except KeyError as err:
- # get_volume may receive incomplete data as the volume is being created
- errors.append(repr(err))
- if not is_online:
- time.sleep(10)
- retries -= 1
- if not is_online:
- errors.append("Timeout after %s seconds" % self.parameters['time_out'])
- self.module.fail_json(msg='Error waiting for volume %s to come online: %s'
- % (self.parameters['name'], str(errors)))
+ self.wait_for_volume_online()
return None
def create_volume_async(self):
@@ -1948,12 +1995,13 @@ class NetAppOntapVolume:
'snapshot_policy', 'percent_snapshot_space', 'snapdir_access', 'atime_update', 'volume_security_style',
'nvfail_enabled', 'space_slo', 'qos_policy_group', 'qos_adaptive_policy_group', 'vserver_dr_protection',
'comment', 'logical_space_enforcement', 'logical_space_reporting', 'tiering_minimum_cooling_days',
- 'snaplock', 'max_files', 'analytics', 'tags']:
+ 'snaplock', 'max_files', 'analytics', 'tags', 'snapshot_auto_delete', 'vol_nearly_full_threshold_percent',
+ 'vol_full_threshold_percent']:
self.volume_modify_attributes(modify)
break
if 'snapshot_auto_delete' in attributes and not self.use_rest:
- # Rest doesn't support any snapshot_auto_delete option other than is_autodelete_enabled. For now i've completely
- # disabled this in rest
+ # Rest didn't support snapshot_auto_delete prior to ONTAP 9.13.1; for supported ONTAP versions,
+ # modification for this parameter is handled by calling volume_modify_attributes function.
self.set_snapshot_auto_delete()
# don't mount or unmount when offline
if modify.get('junction_path'):
@@ -2265,6 +2313,8 @@ class NetAppOntapVolume:
auto_delete_info = current.pop('snapshot_auto_delete', None)
# ignore small changes in volume size or inode maximum by adjusting self.parameters['size'] or self.parameters['max_files']
self.adjust_sizes(current, after_create)
+ if 'type' in self.parameters:
+ self.parameters['type'] = self.parameters['type'].lower()
modify = self.na_helper.get_modified_attributes(current, self.parameters)
if modify is not None and 'type' in modify:
msg = "Error: volume type was not set properly at creation time." if after_create else \
@@ -2393,6 +2443,16 @@ class NetAppOntapVolume:
params['fields'] += 'analytics,'
if self.parameters.get('tags'):
params['fields'] += '_tags,'
+ if self.parameters.get('atime_update') is not None:
+ params['fields'] += 'access_time_enabled,'
+ if self.parameters.get('snapdir_access') is not None:
+ params['fields'] += 'snapshot_directory_access_enabled,'
+ if self.parameters.get('snapshot_auto_delete') is not None:
+ params['fields'] += 'space.snapshot.autodelete,'
+ if self.parameters.get('vol_nearly_full_threshold_percent') is not None:
+ params['fields'] += 'space.nearly_full_threshold_percent,'
+ if self.parameters.get('vol_full_threshold_percent') is not None:
+ params['fields'] += 'space.full_threshold_percent,'
record, error = rest_generic.get_one_record(self.rest_api, api, params)
if error:
@@ -2432,6 +2492,8 @@ class NetAppOntapVolume:
if error:
self.module.fail_json(msg='Error creating volume %s: %s' % (self.parameters['name'], to_native(error)),
exception=traceback.format_exc())
+ if self.parameters.get('wait_for_completion'):
+ self.wait_for_volume_online(sleep_time=5)
def create_volume_body_rest(self):
body = {
@@ -2464,7 +2526,7 @@ class NetAppOntapVolume:
if self.parameters.get('comment') is not None:
body['comment'] = self.parameters['comment']
if self.parameters.get('type') is not None:
- body['type'] = self.parameters['type']
+ body['type'] = self.parameters['type'].lower()
if self.parameters.get('percent_snapshot_space') is not None:
body['space.snapshot.reserve_percent'] = self.parameters['percent_snapshot_space']
if self.parameters.get('language') is not None:
@@ -2514,6 +2576,13 @@ class NetAppOntapVolume:
def bool_to_online(item):
return 'online' if item else 'offline'
+ @staticmethod
+ def enabled_to_bool(item, reverse=False):
+ """ convertes on/off to true/false or vice versa """
+ if reverse:
+ return 'on' if item else 'off'
+ return True if item == 'on' else False
+
def modify_volume_body_rest(self, params):
body = {}
for key, option, transform in [
@@ -2533,7 +2602,11 @@ class NetAppOntapVolume:
('space.logical_space.reporting', 'logical_space_reporting', None),
('tiering.min_cooling_days', 'tiering_minimum_cooling_days', None),
('state', 'is_online', self.bool_to_online),
- ('_tags', 'tags', None)
+ ('_tags', 'tags', None),
+ ('snapshot_directory_access_enabled', 'snapdir_access', None),
+ ('access_time_enabled', 'atime_update', None),
+ ('space.nearly_full_threshold_percent', 'vol_nearly_full_threshold_percent', None),
+ ('space.full_threshold_percent', 'vol_full_threshold_percent', None),
]:
value = self.parameters.get(option)
if value is not None and transform:
@@ -2557,6 +2630,22 @@ class NetAppOntapVolume:
sl_dict.pop('type', None)
if sl_dict:
body['snaplock'] = sl_dict
+
+ if params and params.get('snapshot_auto_delete') is not None:
+ for key, option, transform in [
+ ('space.snapshot.autodelete.trigger', 'trigger', None),
+ ('space.snapshot.autodelete.target_free_space', 'target_free_space', None),
+ ('space.snapshot.autodelete.delete_order', 'delete_order', None),
+ ('space.snapshot.autodelete.commitment', 'commitment', None),
+ ('space.snapshot.autodelete.defer_delete', 'defer_delete', None),
+ ('space.snapshot.autodelete.prefix', 'prefix', None),
+ ('space.snapshot.autodelete.enabled', 'state', self.enabled_to_bool),
+ ]:
+ if params and params['snapshot_auto_delete'].get(option) is not None:
+ if transform:
+ body[key] = transform(self.parameters['snapshot_auto_delete'][option])
+ else:
+ body[key] = self.parameters['snapshot_auto_delete'][option]
return body
def change_volume_state_rest(self):
@@ -2683,6 +2772,10 @@ class NetAppOntapVolume:
self.na_helper.safe_get(self.parameters, ['nas_application_template', 'flexcache', 'dr_cache']) is not None:
self.module.fail_json(msg='Error: %s' % self.rest_api.options_require_ontap_version('flexcache: dr_cache', version='9.9'))
+ if 'snapshot_auto_delete' in self.parameters:
+ if 'destroy_list' in self.parameters['snapshot_auto_delete']:
+ self.module.fail_json(msg="snapshot_auto_delete option 'destroy_list' is currently not supported with REST.")
+
def format_get_volume_rest(self, record):
is_online = record.get('state') == 'online'
# TODO FIX THIS!!!! ZAPI would only return a single aggr, REST can return more than 1.
@@ -2696,6 +2789,10 @@ class NetAppOntapVolume:
# if analytics.state is initializing it will be ON once completed.
state = self.na_helper.safe_get(record, ['analytics', 'state'])
analytics = 'on' if state == 'initializing' else state
+ auto_delete_info = self.na_helper.safe_get(record, ['space', 'snapshot', 'autodelete'])
+ if auto_delete_info is not None:
+ auto_delete_info['state'] = self.enabled_to_bool(self.na_helper.safe_get(record, ['space', 'snapshot', 'autodelete', 'enabled']), reverse=True)
+ del auto_delete_info['enabled']
return {
'tags': record.get('_tags', []),
'name': record.get('name', None),
@@ -2732,7 +2829,12 @@ class NetAppOntapVolume:
'tiering_minimum_cooling_days': self.na_helper.safe_get(record, ['tiering', 'min_cooling_days']),
'snaplock': self.na_helper.safe_get(record, ['snaplock']),
'max_files': self.na_helper.safe_get(record, ['files', 'maximum']),
-
+ # The default setting for access_time_enabled and snapshot_directory_access_enabled is true
+ 'atime_update': record.get('access_time_enabled', True),
+ 'snapdir_access': record.get('snapshot_directory_access_enabled', True),
+ 'snapshot_auto_delete': auto_delete_info,
+ 'vol_nearly_full_threshold_percent': self.na_helper.safe_get(record, ['space', 'nearly_full_threshold_percent']),
+ 'vol_full_threshold_percent': self.na_helper.safe_get(record, ['space', 'full_threshold_percent']),
}
def is_fabricpool(self, name, aggregate_uuid):
@@ -2868,6 +2970,8 @@ class NetAppOntapVolume:
# if we create using ZAPI and modify only options are set (snapdir_access or atime_update), we need to run a modify.
# The modify also takes care of efficiency (sis) parameters and snapshot_auto_delete.
# If we create using REST application, some options are not available, we may need to run a modify.
+ # If we create using REST and modify only options are set (snapdir_access or atime_update or snapshot_auto_delete), we need to run a modify.
+ # For modify only options to be set after creation wait_for_completion needs to be set.
# volume should be online for modify.
current = self.get_volume()
if current:
diff --git a/ansible_collections/netapp/ontap/plugins/modules/na_ontap_vscan_scanner_pool.py b/ansible_collections/netapp/ontap/plugins/modules/na_ontap_vscan_scanner_pool.py
index 20e480637..831ae7253 100644
--- a/ansible_collections/netapp/ontap/plugins/modules/na_ontap_vscan_scanner_pool.py
+++ b/ansible_collections/netapp/ontap/plugins/modules/na_ontap_vscan_scanner_pool.py
@@ -1,6 +1,6 @@
#!/usr/bin/python
-# (c) 2018-2019, 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)
'''
@@ -108,6 +108,8 @@ from ansible.module_utils._text import to_native
import ansible_collections.netapp.ontap.plugins.module_utils.netapp as netapp_utils
from ansible_collections.netapp.ontap.plugins.module_utils.netapp import OntapRestAPI
from ansible_collections.netapp.ontap.plugins.module_utils.netapp_module import NetAppModule
+from ansible_collections.netapp.ontap.plugins.module_utils import rest_generic
+from ansible_collections.netapp.ontap.plugins.module_utils import rest_vserver
HAS_NETAPP_LIB = netapp_utils.has_netapp_lib()
@@ -129,13 +131,18 @@ class NetAppOntapVscanScannerPool(object):
argument_spec=self.argument_spec,
supports_check_mode=True
)
+ self.svm_uuid = None
self.na_helper = NetAppModule()
self.parameters = self.na_helper.set_parameters(self.module.params)
self.rest_api = OntapRestAPI(self.module)
- if HAS_NETAPP_LIB is False:
- self.module.fail_json(msg="the python NetApp-Lib module is required")
- else:
+ self.use_rest = self.rest_api.is_rest()
+ if self.use_rest and not self.rest_api.meets_rest_minimum_version(self.use_rest, 9, 6):
+ msg = 'REST requires ONTAP 9.6 or later for /protocols/vscan/{{svm.uuid}}/scanner-pools APIs'
+ self.use_rest = self.na_helper.fall_back_to_zapi(self.module, msg, self.parameters)
+ if not self.use_rest:
+ if HAS_NETAPP_LIB is False:
+ self.module.fail_json(msg=netapp_utils.netapp_lib_is_required())
self.server = netapp_utils.setup_na_ontap_zapi(module=self.module, vserver=self.parameters['vserver'])
def create_scanner_pool(self):
@@ -143,6 +150,8 @@ class NetAppOntapVscanScannerPool(object):
Create a Vscan Scanner Pool
:return: nothing
"""
+ if self.use_rest:
+ return self.create_scanner_pool_rest()
scanner_pool_obj = netapp_utils.zapi.NaElement('vscan-scanner-pool-create')
if self.parameters['hostnames']:
string_obj = netapp_utils.zapi.NaElement('hostnames')
@@ -182,37 +191,36 @@ class NetAppOntapVscanScannerPool(object):
Check to see if a scanner pool exist or not
:return: True if it exist, False if it does not
"""
- return_value = None
if self.use_rest:
- pass
- else:
- scanner_pool_obj = netapp_utils.zapi.NaElement('vscan-scanner-pool-get-iter')
- scanner_pool_info = netapp_utils.zapi.NaElement('vscan-scanner-pool-info')
- scanner_pool_info.add_new_child('scanner-pool', self.parameters['scanner_pool'])
- scanner_pool_info.add_new_child('vserver', self.parameters['vserver'])
- query = netapp_utils.zapi.NaElement('query')
- query.add_child_elem(scanner_pool_info)
- scanner_pool_obj.add_child_elem(query)
- try:
- result = self.server.invoke_successfully(scanner_pool_obj, True)
- except netapp_utils.zapi.NaApiError as error:
- self.module.fail_json(msg='Error searching for Vscan Scanner Pool %s: %s' %
- (self.parameters['scanner_pool'], to_native(error)), exception=traceback.format_exc())
- if result.get_child_by_name('num-records') and int(result.get_child_content('num-records')) >= 1:
- if result.get_child_by_name('attributes-list').get_child_by_name('vscan-scanner-pool-info').get_child_content(
- 'scanner-pool') == self.parameters['scanner_pool']:
- scanner_pool_obj = result.get_child_by_name('attributes-list').get_child_by_name('vscan-scanner-pool-info')
- hostname = [host.get_content() for host in
- scanner_pool_obj.get_child_by_name('hostnames').get_children()]
- privileged_users = [user.get_content() for user in
- scanner_pool_obj.get_child_by_name('privileged-users').get_children()]
- return_value = {
- 'hostnames': hostname,
- 'enable': scanner_pool_obj.get_child_content('is-currently-active'),
- 'privileged_users': privileged_users,
- 'scanner_pool': scanner_pool_obj.get_child_content('scanner-pool'),
- 'scanner_policy': scanner_pool_obj.get_child_content('scanner-policy')
- }
+ return self.get_scanner_pool_rest()
+ return_value = None
+ scanner_pool_obj = netapp_utils.zapi.NaElement('vscan-scanner-pool-get-iter')
+ scanner_pool_info = netapp_utils.zapi.NaElement('vscan-scanner-pool-info')
+ scanner_pool_info.add_new_child('scanner-pool', self.parameters['scanner_pool'])
+ scanner_pool_info.add_new_child('vserver', self.parameters['vserver'])
+ query = netapp_utils.zapi.NaElement('query')
+ query.add_child_elem(scanner_pool_info)
+ scanner_pool_obj.add_child_elem(query)
+ try:
+ result = self.server.invoke_successfully(scanner_pool_obj, True)
+ except netapp_utils.zapi.NaApiError as error:
+ self.module.fail_json(msg='Error searching for Vscan Scanner Pool %s: %s' %
+ (self.parameters['scanner_pool'], to_native(error)), exception=traceback.format_exc())
+ if result.get_child_by_name('num-records') and int(result.get_child_content('num-records')) >= 1:
+ if result.get_child_by_name('attributes-list').get_child_by_name('vscan-scanner-pool-info').get_child_content(
+ 'scanner-pool') == self.parameters['scanner_pool']:
+ scanner_pool_obj = result.get_child_by_name('attributes-list').get_child_by_name('vscan-scanner-pool-info')
+ hostname = [host.get_content() for host in
+ scanner_pool_obj.get_child_by_name('hostnames').get_children()]
+ privileged_users = [user.get_content() for user in
+ scanner_pool_obj.get_child_by_name('privileged-users').get_children()]
+ return_value = {
+ 'hostnames': hostname,
+ 'enable': scanner_pool_obj.get_child_content('is-currently-active'),
+ 'privileged_users': privileged_users,
+ 'scanner_pool': scanner_pool_obj.get_child_content('scanner-pool'),
+ 'scanner_policy': scanner_pool_obj.get_child_content('scanner-policy')
+ }
return return_value
def delete_scanner_pool(self):
@@ -220,6 +228,8 @@ class NetAppOntapVscanScannerPool(object):
Delete a Scanner pool
:return: nothing
"""
+ if self.use_rest:
+ return self.delete_scanner_pool_rest()
scanner_pool_obj = netapp_utils.zapi.NaElement('vscan-scanner-pool-delete')
scanner_pool_obj.add_new_child('scanner-pool', self.parameters['scanner_pool'])
try:
@@ -234,6 +244,8 @@ class NetAppOntapVscanScannerPool(object):
Modify a scanner pool
:return: nothing
"""
+ if self.use_rest:
+ return self.modify_scanner_pool_rest(modify)
vscan_pool_modify = netapp_utils.zapi.NaElement('vscan-scanner-pool-modify')
vscan_pool_modify.add_new_child('scanner-pool', self.parameters['scanner_pool'])
for key in modify:
@@ -261,26 +273,117 @@ class NetAppOntapVscanScannerPool(object):
def attribute_to_name(attribute):
return str.replace(attribute, '_', '-')
+ def get_svm_uuid(self):
+ """
+ Get a vserver's uuid
+ :return: nothing
+ """
+ record, error = rest_vserver.get_vserver_uuid(self.rest_api, self.parameters['vserver'])
+ if error is not None:
+ self.module.fail_json(msg="Error fetching vserver %s: %s" % (self.parameters['vserver'], to_native(error)),
+ exception=traceback.format_exc())
+ if record is None:
+ self.module.fail_json(msg="Error fetching vserver %s. Please make sure vserver name is correct."
+ % self.parameters['vserver'], exception=traceback.format_exc())
+ self.svm_uuid = record
+
+ def get_scanner_pool_rest(self):
+ """
+ Check to see if a scanner pool exist or not using REST
+ :return: record if it exist, None if it does not
+ """
+ self.get_svm_uuid()
+ api = 'protocols/vscan/%s/scanner-pools' % self.svm_uuid
+ query = {'name': self.parameters.get('scanner_pool'),
+ 'fields': 'servers,'
+ 'privileged_users,'}
+ if self.parameters.get('scanner_policy') is not None:
+ query['fields'] += 'role,'
+
+ record, error = rest_generic.get_one_record(self.rest_api, api, query)
+ if error:
+ self.module.fail_json(msg='Error searching for Vscan Scanner Pool %s: %s' %
+ (self.parameters['scanner_pool'], to_native(error)),
+ exception=traceback.format_exc())
+ if record:
+ return {
+ 'scanner_pool': record.get('name'),
+ 'hostnames': record.get('servers'),
+ 'privileged_users': record.get('privileged_users'),
+ 'scanner_policy': record.get('role'),
+ }
+ return None
+
+ def create_scanner_pool_rest(self):
+ """
+ Create a Vscan Scanner Pool using REST
+ :return: nothing
+ """
+ api = 'protocols/vscan/%s/scanner-pools' % self.svm_uuid
+ body = {
+ 'name': self.parameters['scanner_pool'],
+ 'servers': self.parameters['hostnames'],
+ 'privileged_users': self.parameters['privileged_users'],
+ }
+ if 'scanner_policy' in self.parameters:
+ body['role'] = self.parameters['scanner_policy']
+
+ dummy, error = rest_generic.post_async(self.rest_api, api, body)
+ if error is not None:
+ self.module.fail_json(msg='Error creating Vscan Scanner Pool %s: %s' %
+ (self.parameters['scanner_pool'], to_native(error)),
+ exception=traceback.format_exc())
+
+ def delete_scanner_pool_rest(self):
+ """
+ Delete a Scanner pool using REST
+ :return: nothing
+ """
+ api = 'protocols/vscan/%s/scanner-pools/%s' % (self.svm_uuid, self.parameters['scanner_pool'])
+ dummy, error = rest_generic.delete_async(self.rest_api, api, uuid=None)
+ if error is not None:
+ self.module.fail_json(msg='Error deleting Vscan Scanner Pool %s: %s' %
+ (self.parameters['scanner_pool'], to_native(error)),
+ exception=traceback.format_exc())
+
+ def modify_scanner_pool_rest(self, modify):
+ """
+ Modify a scanner pool using REST
+ :return: nothing
+ """
+ api = 'protocols/vscan/%s/scanner-pools/%s' % (self.svm_uuid, self.parameters['scanner_pool'])
+ body = {}
+ for key, option in [
+ ('servers', 'hostnames'),
+ ('privileged_users', 'privileged_users'),
+ ('role', 'scanner_policy'),
+ ]:
+ if modify.get(option) is not None:
+ body[key] = modify[option]
+
+ dummy, error = rest_generic.patch_async(self.rest_api, api, uuid_or_name=None, body=body)
+ if error:
+ self.module.fail_json(msg='Error modifying Vscan Scanner Pool %s: %s.' %
+ (self.parameters['scanner_pool'], to_native(error)),
+ exception=traceback.format_exc())
+
def apply(self):
- scanner_pool_obj = self.get_scanner_pool()
- cd_action = self.na_helper.get_cd_action(scanner_pool_obj, self.parameters)
+ current = self.get_scanner_pool()
+ cd_action = self.na_helper.get_cd_action(current, self.parameters)
modify = None
if self.parameters['state'] == 'present' and cd_action is None:
- modify = self.na_helper.get_modified_attributes(scanner_pool_obj, self.parameters)
- if self.na_helper.changed:
- if self.module.check_mode:
- pass
- else:
- if cd_action == 'create':
- self.create_scanner_pool()
- if self.parameters.get('scanner_policy') is not None:
- self.apply_policy()
- elif cd_action == 'delete':
- self.delete_scanner_pool()
- elif modify:
- self.modify_scanner_pool(modify)
- if self.parameters.get('scanner_policy') is not None:
- self.apply_policy()
+ modify = self.na_helper.get_modified_attributes(current, self.parameters)
+ if self.na_helper.changed and not self.module.check_mode:
+ if cd_action == 'create':
+ self.create_scanner_pool()
+ if not self.use_rest and self.parameters.get('scanner_policy') is not None:
+ self.apply_policy()
+ elif cd_action == 'delete':
+ self.delete_scanner_pool()
+ elif modify:
+ self.modify_scanner_pool(modify)
+ if not self.use_rest and self.parameters.get('scanner_policy') is not None:
+ self.apply_policy()
result = netapp_utils.generate_result(self.na_helper.changed, cd_action, modify)
self.module.exit_json(**result)
@@ -289,8 +392,8 @@ def main():
"""
Execute action from playbook
"""
- command = NetAppOntapVscanScannerPool()
- command.apply()
+ scanner_pool = NetAppOntapVscanScannerPool()
+ scanner_pool.apply()
if __name__ == '__main__':
diff --git a/ansible_collections/netapp/ontap/plugins/modules/na_ontap_vserver_peer.py b/ansible_collections/netapp/ontap/plugins/modules/na_ontap_vserver_peer.py
index 3c34ccf08..5f7c7260d 100644
--- a/ansible_collections/netapp/ontap/plugins/modules/na_ontap_vserver_peer.py
+++ b/ansible_collections/netapp/ontap/plugins/modules/na_ontap_vserver_peer.py
@@ -1,6 +1,6 @@
#!/usr/bin/python
-# (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)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
@@ -185,7 +185,9 @@ class NetAppONTAPVserverPeer:
self.dst_rest_api = OntapRestAPI(self.module, host_options=self.parameters['peer_options'])
self.dst_use_rest = self.dst_rest_api.is_rest()
self.use_rest = bool(self.src_use_rest and self.dst_use_rest)
- if not self.use_rest:
+ if self.use_rest:
+ self.peer_relation_uuid = None
+ else:
if not netapp_utils.has_netapp_lib():
self.module.fail_json(msg=netapp_utils.netapp_lib_is_required())
self.server = netapp_utils.setup_na_ontap_zapi(module=self.module)
@@ -365,6 +367,10 @@ class NetAppONTAPVserverPeer:
vserver, remote_vserver = self.get_local_and_peer_vserver(target)
restapi = self.rest_api if target == 'source' else self.dst_rest_api
options = {'svm.name': vserver, 'peer.svm.name': remote_vserver, 'fields': 'name,svm.name,peer.svm.name,state,uuid'}
+ # peer cluster may have multiple peer relationships
+ # filter by the created relationship uuid
+ if target == 'peer' and self.peer_relation_uuid is not None:
+ options['uuid'] = self.peer_relation_uuid
record, error = rest_generic.get_one_record(restapi, api, options)
if error:
self.module.fail_json(msg='Error fetching vserver peer %s: %s' % (self.parameters['vserver'], error))
@@ -407,16 +413,19 @@ class NetAppONTAPVserverPeer:
Create a vserver peer using rest
"""
api = 'svm/peers'
- params = {
+ query = {'return_records': 'true'}
+ body = {
'svm.name': self.parameters['vserver'],
'peer.cluster.name': self.parameters['peer_cluster'],
'peer.svm.name': self.parameters['peer_vserver'],
'applications': self.parameters['applications']
}
if 'local_name_for_peer' in self.parameters:
- params['name'] = self.parameters['local_name_for_peer']
- dummy, error = rest_generic.post_async(self.rest_api, api, params)
+ body['name'] = self.parameters['local_name_for_peer']
+ record, error = rest_generic.post_async(self.rest_api, api, body, query)
self.check_and_report_rest_error(error, 'creating', self.parameters['vserver'])
+ if record.get('records') is not None:
+ self.peer_relation_uuid = record['records'][0].get('uuid')
def apply(self):
"""
diff --git a/ansible_collections/netapp/ontap/roles/na_ontap_vserver_create/README.md b/ansible_collections/netapp/ontap/roles/na_ontap_vserver_create/README.md
index e146107d2..ec8e055b9 100644
--- a/ansible_collections/netapp/ontap/roles/na_ontap_vserver_create/README.md
+++ b/ansible_collections/netapp/ontap/roles/na_ontap_vserver_create/README.md
@@ -72,7 +72,7 @@ Example Playbook
prompt: domain admin password (enter if skipped)
vars_files:
- globals.yml
- roles
+ roles:
- na_ontap_vserver_create
```
I use a globals file to hold my variables.
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