diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 166b9454..79932598 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -4,6 +4,24 @@ Dellemc.Powerscale Change Logs .. contents:: Topics +v2.4.0 +====== + +Minor Changes +------------- + +- Added support for listing SMB global settings, detailed network interfaces, NTP servers, email settings, + cluster identity, cluster owner and SNMP settings through info module. +- Added support for getting and modifying cluster owner information and cluster identity information + through settings module. +- Added support for removing the static route for IP address pool through network pool module. + +New Modules +----------- + +- dellemc.powerscale.smb_global_settings - Manage SMB global settings on a PowerScale Storage System. +- dellemc.powerscale.snmp_settings - Manage SNMP settings on a PowerScale Storage System. + v2.3.0 ====== diff --git a/changelogs/changelog.yaml b/changelogs/changelog.yaml index b3751d3d..4b878cc1 100644 --- a/changelogs/changelog.yaml +++ b/changelogs/changelog.yaml @@ -234,3 +234,19 @@ releases: name: synciq_global_settings namespace: '' release_date: '2023-11-30' + 2.4.0: + changes: + minor_changes: + - Added support for listing SMB global settings, detailed network interfaces, NTP servers, email settings, + cluster identity, cluster owner and SNMP settings through info module. + - Added support for getting and modifying cluster owner information and cluster identity information + through settings module. + - Added support for removing the static route for IP address pool through network pool module. + modules: + - description: Manage SMB global settings on a PowerScale Storage System. + name: smb_global_settings + namespace: '' + - description: Manage SNMP settings on a PowerScale Storage System. + name: snmp_settings + namespace: '' + release_date: '2023-12-29' diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md index 3595b688..bf53fb34 100644 --- a/docs/CONTRIBUTING.md +++ b/docs/CONTRIBUTING.md @@ -10,7 +10,7 @@ You may obtain a copy of the License at # How to contribute -Become one of the contributors to this project! We thrive to build a welcoming and open community for anyone who wants to use the project or contribute to it. There are just a few small guidelines you need to follow. To help us create a safe and positive community experience for all, we require all participants to adhere to the [Code of Conduct](https://github.com/dell/ansible-powerscale/blob/2.3.0/docs/CODE_OF_CONDUCT.md). +Become one of the contributors to this project! We thrive to build a welcoming and open community for anyone who wants to use the project or contribute to it. There are just a few small guidelines you need to follow. To help us create a safe and positive community experience for all, we require all participants to adhere to the [Code of Conduct](https://github.com/dell/ansible-powerscale/blob/2.4.0/docs/CODE_OF_CONDUCT.md). ## Table of contents @@ -76,7 +76,7 @@ Triage helps ensure that issues resolve quickly by: If you don't have the knowledge or time to code, consider helping with _issue triage_. The Ansible modules for Dell PowerScale community will thank you for saving them time by spending some of yours. -Read more about the ways you can [Triage issues](https://github.com/dell/ansible-powerscale/blob/2.3.0/docs/ISSUE_TRIAGE.md). +Read more about the ways you can [Triage issues](https://github.com/dell/ansible-powerscale/blob/2.4.0/docs/ISSUE_TRIAGE.md). ## Your first contribution @@ -89,7 +89,7 @@ When you're ready to contribute, it's time to create a pull request. ## Branching -* [Branching Strategy for Ansible modules for Dell PowerScale](https://github.com/dell/ansible-powerscale/blob/2.3.0/docs/BRANCHING.md) +* [Branching Strategy for Ansible modules for Dell PowerScale](https://github.com/dell/ansible-powerscale/blob/2.4.0/docs/BRANCHING.md) ## Signing your commits @@ -144,7 +144,7 @@ Make sure that the title for your pull request uses the same format as the subje ### Quality gates for pull requests -GitHub Actions are used to enforce quality gates when a pull request is created or when any commit is made to the pull request. These GitHub Actions enforce our minimum code quality requirement for any code that get checked into the repository. If any of the quality gates fail, it is expected that the contributor will look into the check log, understand the problem and resolve the issue. If help is needed, please feel free to reach out the maintainers of the project for [support](https://github.com/dell/ansible-powerscale/blob/2.3.0/docs/SUPPORT.md). +GitHub Actions are used to enforce quality gates when a pull request is created or when any commit is made to the pull request. These GitHub Actions enforce our minimum code quality requirement for any code that get checked into the repository. If any of the quality gates fail, it is expected that the contributor will look into the check log, understand the problem and resolve the issue. If help is needed, please feel free to reach out the maintainers of the project for [support](https://github.com/dell/ansible-powerscale/blob/2.4.0/docs/SUPPORT.md). #### Code sanitization diff --git a/docs/INSTALLATION.md b/docs/INSTALLATION.md index cc05f51d..183fbed9 100644 --- a/docs/INSTALLATION.md +++ b/docs/INSTALLATION.md @@ -32,7 +32,7 @@ Use this procedure to install SDK: #### Offline installation of collections 1. Download the latest tar build from either of the available distribution channels [Ansible Galaxy](https://galaxy.ansible.com/dellemc/powerscale) /[Automation Hub](https://console.redhat.com/ansible/automation-hub/repo/published/dellemc/powerscale) and use this command to install the collection anywhere in your system: - ansible-galaxy collection install dellemc-powerscale-2.3.0.tar.gz -p + ansible-galaxy collection install dellemc-powerscale-2.4.0.tar.gz -p 2. Set the environment variable: @@ -59,7 +59,7 @@ Use this procedure to install SDK: ## Ansible modules execution -The Ansible server must be configured with Python library for OneFS to run the Ansible playbooks. The [Documents](https://github.com/dell/ansible-powerscale/blob/2.3.0/docs) provide information on different Ansible modules along with their functions and syntax. The parameters table in the Product Guide provides information on various parameters which need to be configured before running the modules. +The Ansible server must be configured with Python library for OneFS to run the Ansible playbooks. The [Documents](https://github.com/dell/ansible-powerscale/blob/2.4.0/docs) provide information on different Ansible modules along with their functions and syntax. The parameters table in the Product Guide provides information on various parameters which need to be configured before running the modules. ## SSL certificate validation diff --git a/docs/ISSUE_TRIAGE.md b/docs/ISSUE_TRIAGE.md index 88bb29ce..aab8a5d7 100644 --- a/docs/ISSUE_TRIAGE.md +++ b/docs/ISSUE_TRIAGE.md @@ -43,7 +43,7 @@ Should explain what happened, what was expected and how to reproduce it together - Ansible Version: [e.g. 2.15] - Python Version [e.g. 3.11] - - Ansible modules for Dell PowerScale Version: [e.g. 2.3.0] + - Ansible modules for Dell PowerScale Version: [e.g. 2.4.0] - PowerScale SDK version: [e.g. isilon-sdk] - Any other additional information... diff --git a/docs/MAINTAINER_GUIDE.md b/docs/MAINTAINER_GUIDE.md index c78b993c..f5e22d45 100644 --- a/docs/MAINTAINER_GUIDE.md +++ b/docs/MAINTAINER_GUIDE.md @@ -27,7 +27,7 @@ If a candidate is approved, a Maintainer contacts the candidate to invite them t ## Maintainer policies * Lead by example -* Follow the [Code of Conduct](https://github.com/dell/ansible-powerscale/blob/2.3.0/docs/CODE_OF_CONDUCT.md) and the guidelines in the [Contributing](https://github.com/dell/ansible-powerscale/blob/2.3.0/docs/CONTRIBUTING.md) and [Committer](https://github.com/dell/ansible-powerscale/blob/2.3.0/docs/COMMITTER_GUIDE.md) guides +* Follow the [Code of Conduct](https://github.com/dell/ansible-powerscale/blob/2.4.0/docs/CODE_OF_CONDUCT.md) and the guidelines in the [Contributing](https://github.com/dell/ansible-powerscale/blob/2.4.0/docs/CONTRIBUTING.md) and [Committer](https://github.com/dell/ansible-powerscale/blob/2.4.0/docs/COMMITTER_GUIDE.md) guides * Promote a friendly and collaborative environment within our community * Be actively engaged in discussions, answering questions, updating defects, and reviewing pull requests * Criticize code, not people. Ideally, tell the contributor a better way to do what they need. diff --git a/docs/Release Notes.md b/docs/Release Notes.md index 8a971080..4f9fef1a 100644 --- a/docs/Release Notes.md +++ b/docs/Release Notes.md @@ -1,6 +1,6 @@ **Ansible Modules for Dell Technologies PowerScale** ========================================= -### Release notes 2.3.0 +### Release notes 2.4.0 > © 2022 Dell Inc. or its subsidiaries. All rights reserved. Dell > and other trademarks are trademarks of Dell Inc. or its @@ -27,7 +27,7 @@ Table 1. Revision history | Revision | Date | Description | |----------|---------------|-----------------------------------------------------------| -| 01 | November 2023 | Ansible Modules for Dell PowerScale 2.3.0 | +| 01 | December 2023 | Ansible Modules for Dell PowerScale 2.4.0 | Product description @@ -63,6 +63,8 @@ The Ansible Modules for Dell PowerScale support the following features: - Create, modify, get details and delete an S3 bucket. - Get details and modify SyncIQ global settings. - Get details, modify, import, and delete SyncIQ certificates. +- Get details and modify SMB global settings. +- Get details and modify SNMP settings. The Ansible modules use playbooks, written in yaml syntax, to list, show, create, delete, and modify each of these entities. @@ -70,16 +72,18 @@ New Features and Enhancements --------------------------- This section describes the features of the Ansible Modules for Dell PowerScale for this release. -The Ansible Modules for Dell PowerScale release 2.3.0 supports the following features: +The Ansible Modules for Dell PowerScale release 2.4.0 supports the following features: -- The SyncIQ global settings module supports this functionality: - - Added support for getting and modifying SyncIQ global settings. -- The SyncIQ target cluster certificate module supports this functionality: - - Added support for getting, importing, modifying and deleting SyncIQ target cluster certificates. -- The Info module supports this functionality. - - Added support for listing SyncIQ global settings and S3 buckets in Info module. -- The SyncIQ policy module supports this functionality. - - Added support for manually running a SyncIQ policy. +- The SMB global settings module supports this functionality: + - Added support for getting and modifying SMB global settings. +- The SNMP settings module supports this functionality: + - Added support for getting and modifying SNMP settings. +- The network pool module supports this functionality: + - Added support for removing the static route for IP address pool. +- The settings module has been enhanced to support this functionality: + - Added support for getting and modifying cluster owner information and cluster identity information. +- The Info module has been enhanced to support this functionality. + - Added support for listing SMB global settings, detailed network interfaces, NTP servers, email settings, cluster identity, cluster owner and SNMP settings through info module. Known issues ------------ @@ -87,7 +91,6 @@ Known problems in this release are listed. | **Issue** | **Description** | **Resolution** | | ------------- |-------------| -----| -| Snapshot schedule | If the playbook has a desired_retention field, running the same playbook again returns the changed as True (Idempotency does not work). | This is an issue in the supported OneFS versions. | | Filesystem creation | Creation of a filesystem can fail when api_user: "admin" is used because it is possible that the admin user may not have privileges to set an ACLs. | Assigning privileges ISI_PRIV_IFS_RESTORE and ISI_PRIV_NS_TRAVERSE to the user should enable the creation of filesystem with ACL permissions. | | Snapshot creation with alias name | Alias name attribute remains null in spite of creating snapshot with alias name | This is an issue with the PowerScale rest API. Alias name is not getting appended to the attribute in response. | | SyncIQ Job creation/modification/retrieval | When SyncIQ policy has any job of the type "resync_prep/allow_write/allow_write_revert" then creation, modification or retrieval of SyncIQ job will fail with an error saying "Invalid value for 'action', must be one of ['copy', 'sync']". | This is an issue in the supported OneFS versions. | @@ -105,9 +108,6 @@ This section lists the limitations in this release of Ansible Modules for Dell P - Only local users and groups can be created. - Operations on users and groups with very long names may fail. - Modification of user password fails for OneFS version 9.5. - -- Access Zone - - Deletion of access zones is not supported. - Filesystems - Only directory quotas are supported but not user or group quotas. @@ -129,7 +129,7 @@ This section lists the limitations in this release of Ansible Modules for Dell P Software media, organization, and files ----------- The software package is available for download from the [Ansible Modules -for PowerScale GitHub](https://github.com/dell/ansible-powerscale/tree/2.3.0) page. +for PowerScale GitHub](https://github.com/dell/ansible-powerscale/tree/2.4.0) page. Additional resources -------------------- diff --git a/docs/SECURITY.md b/docs/SECURITY.md index 4e069a64..57648827 100644 --- a/docs/SECURITY.md +++ b/docs/SECURITY.md @@ -12,7 +12,7 @@ You may obtain a copy of the License at The Ansible modules for Dell PowerScale repository is inspected for security vulnerabilities via blackduck scans and static code analysis. -In addition to this, there are various security checks that get executed against a branch when a pull request is created/updated. Please refer to [pull request](https://github.com/dell/ansible-powerscale/blob/2.3.0/docs/CONTRIBUTING.md#Pull-requests) for more information. +In addition to this, there are various security checks that get executed against a branch when a pull request is created/updated. Please refer to [pull request](https://github.com/dell/ansible-powerscale/blob/2.4.0/docs/CONTRIBUTING.md#Pull-requests) for more information. ## Reporting a vulnerability diff --git a/docs/modules/info.rst b/docs/modules/info.rst index 40a99c19..808b3d45 100644 --- a/docs/modules/info.rst +++ b/docs/modules/info.rst @@ -16,7 +16,7 @@ Gathering information about Specified PowerScale Storage entities, includes attr Get list of smb_shares, nfs_exports, nfs_aliases, active clients, SyncIQ reports, SyncIQ target reports, SyncIQ target cluster certificates, SyncIQ policies, SyncIQ performance rules. -Get list of network groupnets, network pools for all access zones or a specific access zone, network rules, network subnets, network interfaces, node pools, storage pool tiers, smb open files. +Get list of network groupnets, network pools for all access zones or a specific access zone, network rules, network subnets, network interfaces, node pools, storage pool tiers, smb open files, s3 buckets, ntp_servers. Get list of user mapping rules, ldap providers of the PowerScale cluster. @@ -28,6 +28,12 @@ Get NFS global settings details of the PowerScale cluster. Get SyncIQ global settings details of the PowerScale cluster. +Get SMB Global Settings details of the PowerScale cluster. + +Get cluster owner, cluster identity and email settings details of the PowerScale cluster. + +Get SNMP settings details of the PowerScale cluster. + Requirements @@ -124,7 +130,7 @@ Parameters SyncIQ global settings - ``synciq_global_settings``. - S3 buckets - ``s3_buckets`` + S3 buckets - ``s3_buckets``. The list of *attributes*, *access_zones* and *nodes* is for the entire PowerScale cluster. @@ -146,7 +152,17 @@ Parameters The list of ldap providers of PowerScale cluster. - The list of S3 bucket for the entire PowerScale cluster. + SMB global settings - ``smb_global_settings``. + + NTP servers ``ntp_servers`` + + Email settings ``email_settings`` + + Cluster identity ``cluster_identity`` + + Cluster owner ``cluster_owner`` + + SNMP settings - ``snmp_settings``. onefs_host (True, str, None) @@ -487,6 +503,60 @@ Examples gather_subset: - s3_buckets + - name: Get SMB global settings from PowerScale cluster + dellemc.powerscale.info: + onefs_host: "{{ onefs_host }}" + verify_ssl: "{{ verify_ssl }}" + api_user: "{{ api_user }}" + api_password: "{{ api_password }}" + gather_subset: + - smb_global_settings + + - name: Get NTP servers from PowerScale cluster + dellemc.powerscale.info: + onefs_host: "{{ onefs_host }}" + verify_ssl: "{{ verify_ssl }}" + api_user: "{{ api_user }}" + api_password: "{{ api_password }}" + gather_subset: + - ntp_servers + + - name: Get SNMP settings from PowerScale cluster + dellemc.powerscale.info: + onefs_host: "{{ onefs_host }}" + verify_ssl: "{{ verify_ssl }}" + api_user: "{{ api_user }}" + api_password: "{{ api_password }}" + gather_subset: + - snmp_settings + + - name: Get email settings details from PowerScale cluster + dellemc.powerscale.info: + onefs_host: "{{ onefs_host }}" + verify_ssl: "{{ verify_ssl }}" + api_user: "{{ api_user }}" + api_password: "{{ api_password }}" + gather_subset: + - email_settings + + - name: Get cluster identity details from PowerScale cluster + dellemc.powerscale.info: + onefs_host: "{{ onefs_host }}" + verify_ssl: "{{ verify_ssl }}" + api_user: "{{ api_user }}" + api_password: "{{ api_password }}" + gather_subset: + - cluster_identity + + - name: Get cluster owner details from PowerScale cluster + dellemc.powerscale.info: + onefs_host: "{{ onefs_host }}" + verify_ssl: "{{ verify_ssl }}" + api_user: "{{ api_user }}" + api_password: "{{ api_password }}" + gather_subset: + - cluster_owner + Return Values @@ -627,22 +697,66 @@ NetworkGroupnets (When C(network_groupnets) is in a given I(gather_subset), list -NetworkInterfaces (When C(network_interfaces) is in a given I(gather_subset), list, [{'id': '110gig1', 'lnn': 1, 'name': '10gig1'}]) +NetworkInterfaces (When C(network_interfaces) is in a given I(gather_subset), list, [{'flags': [], 'id': '3:ext-agg', 'ip_addrs': [], 'ipv4_gateway': None, 'ipv6_gateway': None, 'lnn': 3, 'mtu': 0, 'name': 'ext-agg', 'nic_name': 'lagg0', 'owners': [], 'speed': None, 'status': 'inactive', 'type': 'aggregated', 'vlans': []}]) List of Network interfaces. + flags (, list, ) + List of interface flags. + + id (, str, ) ID of the interface. + ip_addrs (, list, ) + List of IP addresses. + + + ipv4_gateway (, str, ) + Address of the default IPv4 gateway. + + + ipv6_gateway (, str, ) + Address of the default IPv6 gateway. + + lnn (, int, ) Interface's lnn. + mtu (, int, ) + The mtu the interface. + + name (, str, ) Name of the interface. + nic_name (, str, ) + NIC name. + + + owners (, list, ) + List of owners. + + + speed (, int, ) + Interface's speed. + + + status (, str, ) + Status of the interface. + + + type (, str, ) + Type of the interface. + + + vlans (, list, ) + List of VLANs. + + NetworkPools (When C(network_pools) is in a given I(gather_subset), list, [{'id': 'groupnet0.subnet0.pool0', 'name': 'pool0'}]) List of Network Pools. @@ -1363,6 +1477,295 @@ S3_bucket_details (When C(s3_buckets) is in a given I(gather_subset), dict, {'ac +SmbGlobalSettings (always, dict, {'access_based_share_enum': False, 'audit_fileshare': None, 'audit_logon': None, 'dot_snap_accessible_child': True, 'dot_snap_accessible_root': True, 'dot_snap_visible_child': False, 'dot_snap_visible_root': True, 'enable_security_signatures': False, 'guest_user': 'nobody', 'ignore_eas': False, 'onefs_cpu_multiplier': 4, 'onefs_num_workers': 0, 'reject_unencrypted_access': False, 'require_security_signatures': False, 'server_side_copy': False, 'server_string': 'PowerScale Server', 'service': True, 'srv_cpu_multiplier': None, 'srv_num_workers': None, 'support_multichannel': True, 'support_netbios': False, 'support_smb2': True, 'support_smb3_encryption': True}) + The updated SMB global settings details. + + + access_based_share_enum (, bool, ) + Only enumerate files and folders the requesting user has access to. + + + audit_fileshare (, str, ) + Specify level of file share audit events to log. + + + audit_logon (, str, ) + Specify the level of logon audit events to log. + + + dot_snap_accessible_child (, bool, ) + Allow access to .snapshot directories in share subdirectories. + + + dot_snap_accessible_root (, bool, ) + Allow access to the .snapshot directory in the root of the share. + + + dot_snap_visible_child (, bool, ) + Show .snapshot directories in share subdirectories. + + + dot_snap_visible_root (, bool, ) + Show the .snapshot directory in the root of a share. + + + enable_security_signatures (, bool, ) + Indicates whether the server supports signed SMB packets. + + + guest_user (, str, ) + Specifies the fully-qualified user to use for guest access. + + + ignore_eas (, bool, ) + Specify whether to ignore EAs on files. + + + onefs_cpu_multiplier (, int, ) + Specify the number of OneFS driver worker threads per CPU. + + + onefs_num_workers (, int, ) + Set the maximum number of OneFS driver worker threads. + + + reject_unencrypted_access (, bool, ) + If SMB3 encryption is enabled, reject unencrypted access from clients. + + + require_security_signatures (, bool, ) + Indicates whether the server requires signed SMB packets. + + + server_side_copy (, bool, ) + Enable Server Side Copy. + + + server_string (, str, ) + Provides a description of the server. + + + service (, bool, ) + Specify whether service is enabled. + + + srv_cpu_multiplier (, int, ) + Specify the number of SRV service worker threads per CPU. + + + srv_num_workers (, int, ) + Set the maximum number of SRV service worker threads. + + + support_multichannel (, bool, ) + Support multichannel. + + + support_netbios (, bool, ) + Support NetBIOS. + + + support_smb2 (, bool, ) + The support SMB2 attribute. + + + support_smb3_encryption (, bool, ) + Support the SMB3 encryption on the server. + + + +email_settings (Always, dict, {'settings': {'batch_mode': 'none', 'mail_relay': '10.**.**.**', 'mail_sender': 'powerscale@dell.com', 'mail_subject': 'Powerscale Cluster notifications', 'smtp_auth_passwd_set': False, 'smtp_auth_security': 'none', 'smtp_auth_username': '', 'smtp_port': 25, 'use_smtp_auth': False, 'user_template': ''}}) + Details of the email settings. + + + settings (Always, dict, ) + Details of the settings. + + + batch_mode (, str, ) + This setting determines how notifications will be batched together to be sent by email. + + + mail_relay (, str, ) + The address of the SMTP server to be used for relaying the notification messages. + + + mail_sender (, str, ) + The full email address that will appear as the sender of notification messages. + + + mail_subject (, str, ) + The subject line for notification messages from this cluster. + + + smtp_auth_passwd_set (, bool, ) + Indicates if an SMTP authentication password is set. + + + smtp_auth_security (, str, ) + The type of secure communication protocol to use if SMTP is being used. + + + smtp_auth_username (, str, ) + Username to authenticate with if SMTP authentication is being used. + + + smtp_port (, int, ) + The port on the SMTP server to be used for relaying the notification messages. + + + use_smtp_auth (, bool, ) + If true, this cluster will send SMTP authentication credentials to the SMTP relay server in order to send its notification emails. + + + user_template (, str, ) + Location of a custom template file that can be used to specify the layout of the notification emails. + + + + +ntp_servers (Always, dict, {'servers': [{'id': '10.**.**.**', 'key': None, 'name': '10.**.**.**'}]}) + List of NTP servers. + + + servers (, list, ) + List of servers. + + + id (, str, ) + Field id. + + + key (, str, ) + Key value from *key_file* that maps to this server. + + + name (, str, ) + NTP server name. + + + + +cluster_identity (Always, dict, {'cluster_identity': {'description': 'asdadasdasdasdadadadds', 'logon': {'motd': 'This is new description', 'motd_header': 'This is the new title'}, 'mttdl_level_msg': 'none', 'name': 'PIE-IsilonS-24241-Clusterwrerwerwrewr'}}) + Details related to cluster identity. + + + description (, str, ) + Description of PowerScale cluster. + + + logon (, dict, ) + Details of logon message shown on Powerscale login screen. + + + motd (, str, ) + Details of logon message. + + + motd_header (, str, ) + Details of logon message title. + + + + mttdl_level_msg (, str, ) + mttdl_level_msg. + + + name (, str, ) + Name of PowerScale cluster. + + + +cluster_owner (Always, dict, {'cluster_owner': {'company': 'Test company', 'location': 'Test location', 'primary_email': 'primary_email@email.com', 'primary_name': 'primary_name', 'primary_phone1': 'primary_phone1', 'primary_phone2': 'primary_phone2', 'secondary_email': 'secondary_email@email.com', 'secondary_name': 'secondary_name', 'secondary_phone1': 'secondary_phone1', 'secondary_phone2': 'secondary_phone2'}}) + Details related to cluster identity. + + + company (, str, ) + Name of the company. + + + location (, str, ) + Location of the company. + + + primary_email (, str, ) + Email of primary system admin. + + + primary_name (, str, ) + Name of primary system admin. + + + primary_phone1 (, str, ) + Phone1 of primary system admin. + + + primary_phone2 (, str, ) + Phone2 of primary system admin. + + + secondary_email (, str, ) + Email of secondary system admin. + + + secondary_name (, str, ) + Name of secondary system admin. + + + secondary_phone1 (, str, ) + Phone1 of secondary system admin. + + + secondary_phone2 (, str, ) + Phone2 of secondary system admin. + + + +SnmpSettings (When C(snmp_settings) is in a given I(gather_subset), dict, {'read_only_community': 'public', 'service': True, 'snmp_v1_v2c_access': True, 'snmp_v3_access': True, 'snmp_v3_auth_protocol': 'MD5', 'snmp_v3_priv_protocol': 'DES', 'snmp_v3_security_level': 'authPriv', 'snmp_v3_read_only_user': 'general', 'system_contact': 'system', 'system_location': 'cluster'}) + The SNMP settings details. + + + read_only_community (, str, ) + SNMP Read-only community name. + + + service (, bool, ) + Whether the SNMP Service is enabled. + + + snmp_v1_v2c_access (, bool, ) + Whether the SNMP v2c access is enabled. + + + snmp_v3_access (, bool, ) + Whether the SNMP v3 access is enabled. + + + snmp_v3_auth_protocol (, str, ) + SNMP v3 authentication protocol. + + + snmp_v3_priv_protocol (, str, ) + SNMP v3 privacy protocol. + + + snmp_v3_security_level (, str, ) + SNMP v3 security level. + + + snmp_v3_read_only_user (, str, ) + SNMP v3 read-only user. + + + system_contact (, str, ) + SNMP system owner contact information. + + + system_location (, str, ) + The cluster description of the SNMP system. + + + @@ -1383,4 +1786,5 @@ Authors - Bhavneet Sharma(@Bhavneet-Sharma) - Trisha Datta(@trisha-dell) - Meenakshi Dembi(@dembim) +- Sachin Apagundi(@sachin-apa) diff --git a/docs/modules/networkpool.rst b/docs/modules/networkpool.rst index eff69570..32e44a47 100644 --- a/docs/modules/networkpool.rst +++ b/docs/modules/networkpool.rst @@ -116,6 +116,10 @@ Parameters Network address in the format xxx.xxx.xxx.xxx. + route_state (optional, str, add) + This signifies if route needs to be added or removed. + + sc_dns_zone (optional, str, None) SmartConnect zone name for the pool. @@ -190,7 +194,7 @@ Notes .. note:: - The *check_mode* is not supported. - - Removal of static routes and *sc_dns_zone_aliases* is not supported. + - Removal of *sc_dns_zone_aliases* is not supported. - The modules present in this collection named as 'dellemc.powerscale' are built to support the Dell PowerScale storage platform. @@ -220,19 +224,20 @@ Examples lnn: 1 iface_state: "add" sc_params: - sc_dns_zone: "10.230.**.***" - sc_connect_policy: "throughput" - sc_failover_policy: "throughput" - rebalance_policy: "auto" - alloc_method: "static" - sc_auto_unsuspend_delay: 200 - sc_ttl: 200 - sc_dns_zone_aliases: - - "Test" - static_routes: - - gateway: "10.**.**.**" - prefix_len: 21 - subnet: "10.**.**.**" + sc_dns_zone: "10.230.**.***" + sc_connect_policy: "throughput" + sc_failover_policy: "throughput" + rebalance_policy: "auto" + alloc_method: "static" + sc_auto_unsuspend_delay: 200 + sc_ttl: 200 + sc_dns_zone_aliases: + - "Test" + static_routes: + - gateway: "10.**.**.**" + prefix_len: 21 + subnet: "10.**.**.**" + route_state: "add" pool: "Test_Pool_2" access_zone: "system" state: "present" @@ -258,29 +263,34 @@ Examples subnet: "subnet0" pool: "Test_Pool_2" additional_pool_params: - ranges: - - low: "10.230.**.***" - high: "10.230.**.***" - range_state: "add" - ifaces: - - iface: "ext-1" - lnn: 1 - iface_state: "add" + ranges: + - low: "10.230.**.***" + high: "10.230.**.***" + range_state: "add" + ifaces: + - iface: "ext-1" + lnn: 1 + iface_state: "add" sc_params: - sc_dns_zone: "10.230.**.***" - sc_connect_policy: "throughput" - sc_failover_policy: "throughput" - rebalance_policy: "auto" - alloc_method: "static" - sc_auto_unsuspend_delay: 200 - sc_ttl: 200 - sc_dns_zone_aliases: - - "Test" - static_routes: - - gateway: "10.**.**.**" - prefix_len: 21 - subnet: "10.**.**.**" - aggregation_mode: "fec" + sc_dns_zone: "10.230.**.***" + sc_connect_policy: "throughput" + sc_failover_policy: "throughput" + rebalance_policy: "auto" + alloc_method: "static" + sc_auto_unsuspend_delay: 200 + sc_ttl: 200 + sc_dns_zone_aliases: + - "Test" + static_routes: + - gateway: "10.**.**.**" + prefix_len: 21 + subnet: "10.**.**.**" + route_state: "remove" + - gateway: "10.**.**.**" + prefix_len: 24 + subnet: "10.**.**.**" + route_state: "add" + aggregation_mode: "fec" description: "Pool Created by Ansible Modify" state: "present" @@ -419,5 +429,6 @@ Authors ~~~~~~~ - Meenakshi Dembi (@dembim) -- Pavan Mudunuri(@Pavan-Mudunuri) +- Pavan Mudunuri (@Pavan-Mudunuri) +- Bhavneet Sharma (@Bhavneet-Sharma) diff --git a/docs/modules/settings.rst b/docs/modules/settings.rst index 07f99ff4..51cb3eda 100644 --- a/docs/modules/settings.rst +++ b/docs/modules/settings.rst @@ -12,7 +12,15 @@ settings -- Manages general settings for PowerScale storage system Synopsis -------- -Managing general settings on the PowerScale storage system which includes get and update operations for email settings and add, remove and get operations for NTP servers. +Managing general settings on the PowerScale storage system which includes the following. + +Get and update operations for email settings. + +Add remove and get operations for NTP servers. + +Get and update operation for cluster identity. + +Get and update operation for cluster owner. @@ -46,14 +54,16 @@ Parameters email_settings (optional, bool, None) - This is an addition flag to view the email settings. + (deprecated) This is an addition flag to view the email settings. + + This option is deprecated and will be removed in the later version. ntp_servers (optional, list, None) List of NTP servers which need to be configured. - state (True, str, None) + state (optional, str, present) The state option is used to mention the existence of pool. @@ -61,6 +71,77 @@ Parameters ID of NTP server. + name (optional, str, None) + Name of PowerScale Cluster. + + + description (optional, str, None) + Description of PowerScale Cluster. + + + logon_details (optional, dict, None) + Details related to login to the Powerscale Cluster. + + + message_title (optional, str, None) + Message to be shown on the login screen. + + + description (optional, str, None) + Message description to be shown on the login screen. + + + + company (optional, str, None) + Name of the company. + + + location (optional, str, None) + Location of the company. + + + primary_contact (optional, dict, None) + Contact details of primary system admin. + + + name (optional, str, None) + Name of primary system admin. + + + phone1 (optional, str, None) + Phone1 of primary system admin. + + + phone2 (optional, str, None) + Phone2 of primary system admin. + + + email (optional, str, None) + Email of primary system admin. + + + + secondary_contact (optional, dict, None) + Contact details of secondary system admin. + + + name (optional, str, None) + Name of secondary system admin. + + + phone1 (optional, str, None) + Phone1 of secondary system admin. + + + phone2 (optional, str, None) + Phone2 of secondary system admin. + + + email (optional, str, None) + Email of secondary system admin. + + + onefs_host (True, str, None) IP address or FQDN of the PowerScale cluster. @@ -92,7 +173,7 @@ Notes ----- .. note:: - - The *check_mode* is not supported. + - The *check_mode* is supported. - The modules present in this collection named as 'dellemc.powerscale' are built to support the Dell PowerScale storage platform. @@ -110,8 +191,6 @@ Examples api_user: "{{api_user}}" api_password: "{{api_password}}" verify_ssl: "{{verify_ssl}}" - email_settings: "{{email_settings}}" - state: "{{state_present}}" - name: Update email settings dellemc.powerscale.settings: @@ -135,17 +214,6 @@ Examples - "10.106.**.***" state: "{{state_present}}" - - name: Add NTP server - Idempotency - dellemc.powerscale.settings: - onefs_host: "{{onefs_host}}" - api_user: "{{api_user}}" - api_password: "{{api_password}}" - verify_ssl: "{{verify_ssl}}" - ntp_servers: - - "10.106.**.***" - - "10.106.**.***" - state: "{{state_present}}" - - name: Get NTP server dellemc.powerscale.settings: onefs_host: "{{onefs_host}}" @@ -166,24 +234,77 @@ Examples - "10.106.**.***" state: "{{state_absent}}" - - name: Remove NTP server - Idempotency + - name: Update email settings and add NTP server dellemc.powerscale.settings: onefs_host: "{{onefs_host}}" api_user: "{{api_user}}" api_password: "{{api_password}}" verify_ssl: "{{verify_ssl}}" + state: "{{state_present}}" + mail_relay: "mailrelay.itp.dell.com" + mail_sender: "lab-a2@dell.com" + mail_subject: "lab-a2-alerts" ntp_servers: - "10.106.**.***" - "10.106.**.***" - state: "{{state_absent}}" - - name: Update email settings and add NTP server + - name: Update cluster owner details + dellemc.powerscale.settings: + onefs_host: "{{onefs_host}}" + api_user: "{{api_user}}" + api_password: "{{api_password}}" + verify_ssl: "{{verify_ssl}}" + state: "{{state_present}}" + company: "Test company" + location: "Test location" + primary_contact: + name: "primary_name11" + phone1: "primary_phone11" + phone2: "primary_phone21" + email: "primary_email1@email.com" + secondary_contact: + name: "secondary_name11" + phone1: "secondary_phone11" + phone2: "secondary_phone21" + email: "secondary_email1@email.com" + + - name: Update cluster identity details + dellemc.powerscale.settings: + onefs_host: "{{onefs_host}}" + api_user: "{{api_user}}" + api_password: "{{api_password}}" + verify_ssl: "{{verify_ssl}}" + state: "{{state_present}}" + name: "PIE-IsilonS-24241-Cluster" + description: "This is new description for the cluster" + logon_details: + message_title: "This is the new title" + description: "This is new description" + + - name: Update all settings dellemc.powerscale.settings: onefs_host: "{{onefs_host}}" api_user: "{{api_user}}" api_password: "{{api_password}}" verify_ssl: "{{verify_ssl}}" state: "{{state_present}}" + name: "PIE-IsilonS-24241-Cluster" + description: "This is new description for the cluster" + logon_details: + message_title: "This is the new title" + description: "This is new description" + company: "Test company" + location: "Test location" + primary_contact: + name: "primary_name11" + phone1: "primary_phone11" + phone2: "primary_phone21" + email: "primary_email1@email.com" + secondary_contact: + name: "secondary_name11" + phone1: "secondary_phone11" + phone2: "secondary_phone21" + email: "secondary_email1@email.com" mail_relay: "mailrelay.itp.dell.com" mail_sender: "lab-a2@dell.com" mail_subject: "lab-a2-alerts" @@ -250,7 +371,7 @@ email_settings (Always, dict, {'settings': {'batch_mode': 'none', 'mail_relay': -ntp_server (Always, dict, {'servers': [{'id': '10.**.**.**', 'key': None, 'name': '10.**.**.**'}]}) +ntp_servers (Always, dict, {'servers': [{'id': '10.**.**.**', 'key': None, 'name': '10.**.**.**'}]}) List of NTP servers. @@ -272,6 +393,81 @@ ntp_server (Always, dict, {'servers': [{'id': '10.**.**.**', 'key': None, 'name' +cluster_identity (Always, dict, {'cluster_identity': {'description': 'asdadasdasdasdadadadds', 'logon': {'motd': 'This is new description', 'motd_header': 'This is the new title'}, 'mttdl_level_msg': 'none', 'name': 'PIE-IsilonS-24241-Clusterwrerwerwrewr'}}) + Details related to cluster identity. + + + description (, str, ) + Description of PowerScale cluster. + + + logon (, dict, ) + Details of logon message shown on Powerscale login screen. + + + motd (, str, ) + Details of logon message. + + + motd_header (, str, ) + Details of logon message title. + + + + mttdl_level_msg (, str, ) + mttdl_level_msg. + + + name (, str, ) + Name of PowerScale cluster. + + + +cluster_owner (Always, dict, {'cluster_owner': {'company': 'Test company', 'location': 'Test location', 'primary_email': 'primary_email@email.com', 'primary_name': 'primary_name', 'primary_phone1': 'primary_phone1', 'primary_phone2': 'primary_phone2', 'secondary_email': 'secondary_email@email.com', 'secondary_name': 'secondary_name', 'secondary_phone1': 'secondary_phone1', 'secondary_phone2': 'secondary_phone2'}}) + Details related to cluster identity. + + + company (, str, ) + Name of the company. + + + location (, str, ) + Location of the company. + + + primary_email (, str, ) + Email of primary system admin. + + + primary_name (, str, ) + Name of primary system admin. + + + primary_phone1 (, str, ) + Phone1 of primary system admin. + + + primary_phone2 (, str, ) + Phone2 of primary system admin. + + + secondary_email (, str, ) + Email of secondary system admin. + + + secondary_name (, str, ) + Name of secondary system admin. + + + secondary_phone1 (, str, ) + Phone1 of secondary system admin. + + + secondary_phone2 (, str, ) + Phone2 of secondary system admin. + + + diff --git a/docs/modules/smb_global_settings.rst b/docs/modules/smb_global_settings.rst new file mode 100644 index 00000000..01731b90 --- /dev/null +++ b/docs/modules/smb_global_settings.rst @@ -0,0 +1,306 @@ +.. _smb_global_settings_module: + + +smb_global_settings -- Manage SMB global settings on a PowerScale Storage System +================================================================================ + +.. contents:: + :local: + :depth: 1 + + +Synopsis +-------- + +Managing SMB global settings on a PowerScale system includes retrieving details of SMB global settings and modifying SMB global settings. + + + +Requirements +------------ +The below requirements are needed on the host that executes this module. + +- A Dell PowerScale Storage system. +- Ansible-core 2.14 or later. +- Python 3.9, 3.10 or 3.11. + + + +Parameters +---------- + + access_based_share_enum (optional, bool, None) + Only enumerate files and folders the requesting user has access to. + + + dot_snap_accessible_child (optional, bool, None) + Allow access to .snapshot directories in share subdirectories. + + + dot_snap_accessible_root (optional, bool, None) + Allow access to the .snapshot directory in the root of the share. + + + dot_snap_visible_child (optional, bool, None) + Show .snapshot directories in share subdirectories. + + + dot_snap_visible_root (optional, bool, None) + Show the .snapshot directory in the root of a share. + + + enable_security_signatures (optional, bool, None) + Indicates whether the server supports signed SMB packets. + + + guest_user (optional, str, None) + Specifies the fully-qualified user to use for guest access. + + + ignore_eas (optional, bool, None) + Specify whether to ignore EAs on files. + + + onefs_cpu_multiplier (optional, int, None) + Specify the number of OneFS driver worker threads per CPU. + + + onefs_num_workers (optional, int, None) + Set the maximum number of OneFS driver worker threads. + + + reject_unencrypted_access (optional, bool, None) + If SMB3 encryption is enabled, reject unencrypted access from clients. + + + require_security_signatures (optional, bool, None) + Indicates whether the server requires signed SMB packets. + + + server_side_copy (optional, bool, None) + Enable Server Side Copy. + + + server_string (optional, str, None) + Provides a description of the server. + + + service (optional, bool, None) + Specify whether service is enabled. + + + support_multichannel (optional, bool, None) + Support multichannel. + + + support_netbios (optional, bool, None) + Support NetBIOS. + + + support_smb2 (optional, bool, None) + The support SMB2 attribute. + + + support_smb3_encryption (optional, bool, None) + Support the SMB3 encryption on the server. + + + onefs_host (True, str, None) + IP address or FQDN of the PowerScale cluster. + + + port_no (False, str, 8080) + Port number of the PowerScale cluster.It defaults to 8080 if not specified. + + + verify_ssl (True, bool, None) + boolean variable to specify whether to validate SSL certificate or not. + + ``true`` - indicates that the SSL certificate should be verified. + + ``false`` - indicates that the SSL certificate should not be verified. + + + api_user (True, str, None) + username of the PowerScale cluster. + + + api_password (True, str, None) + the password of the PowerScale cluster. + + + + + +Notes +----- + +.. note:: + - The *check_mode* is supported. + - The modules present in this collection named as 'dellemc.powerscale' are built to support the Dell PowerScale storage platform. + + + + +Examples +-------- + +.. code-block:: yaml+jinja + + + - name: Get SMB global settings + dellemc.powerscale.smb_global_settings: + onefs_host: "{{ onefs_host }}" + port_no: "{{ port_no }}" + api_user: "{{ api_user }}" + api_password: "{{ api_password }}" + verify_ssl: "{{ verify_ssl }}" + + - name: Update SMB global settings + dellemc.powerscale.smb_global_settings: + onefs_host: "{{ onefs_host }}" + port_no: "{{ port_no }}" + api_user: "{{ api_user }}" + api_password: "{{ api_password }}" + verify_ssl: "{{ verify_ssl }}" + access_based_share_enum: true + dot_snap_accessible_child: true + dot_snap_accessible_root: false + dot_snap_visible_child: false + dot_snap_visible_root: true + enable_security_signatures: true + guest_user: user + ignore_eas: false + onefs_cpu_multiplier: 2 + onefs_num_workers: 4 + reject_unencrypted_access: true + require_security_signatures: true + server_side_copy: true + server_string: 'PowerScale Server' + service: true + support_multichannel: true + support_netbios: true + support_smb2: true + support_smb3_encryption: true + + + +Return Values +------------- + +changed (always, bool, false) + A boolean indicating if the task had to make changes. + + +smb_global_settings_details (always, dict, {'access_based_share_enum': False, 'audit_fileshare': None, 'audit_logon': None, 'dot_snap_accessible_child': True, 'dot_snap_accessible_root': True, 'dot_snap_visible_child': False, 'dot_snap_visible_root': True, 'enable_security_signatures': False, 'guest_user': 'nobody', 'ignore_eas': False, 'onefs_cpu_multiplier': 4, 'onefs_num_workers': 0, 'reject_unencrypted_access': False, 'require_security_signatures': False, 'server_side_copy': False, 'server_string': 'PowerScale Server', 'service': True, 'srv_cpu_multiplier': None, 'srv_num_workers': None, 'support_multichannel': True, 'support_netbios': False, 'support_smb2': True, 'support_smb3_encryption': True}) + The updated SMB global settings details. + + + access_based_share_enum (, bool, ) + Only enumerate files and folders the requesting user has access to. + + + audit_fileshare (, str, ) + Specify level of file share audit events to log. + + + audit_logon (, str, ) + Specify the level of logon audit events to log. + + + dot_snap_accessible_child (, bool, ) + Allow access to .snapshot directories in share subdirectories. + + + dot_snap_accessible_root (, bool, ) + Allow access to the .snapshot directory in the root of the share. + + + dot_snap_visible_child (, bool, ) + Show .snapshot directories in share subdirectories. + + + dot_snap_visible_root (, bool, ) + Show the .snapshot directory in the root of a share. + + + enable_security_signatures (, bool, ) + Indicates whether the server supports signed SMB packets. + + + guest_user (, str, ) + Specifies the fully-qualified user to use for guest access. + + + ignore_eas (, bool, ) + Specify whether to ignore EAs on files. + + + onefs_cpu_multiplier (, int, ) + Specify the number of OneFS driver worker threads per CPU. + + + onefs_num_workers (, int, ) + Set the maximum number of OneFS driver worker threads. + + + reject_unencrypted_access (, bool, ) + If SMB3 encryption is enabled, reject unencrypted access from clients. + + + require_security_signatures (, bool, ) + Indicates whether the server requires signed SMB packets. + + + server_side_copy (, bool, ) + Enable Server Side Copy. + + + server_string (, str, ) + Provides a description of the server. + + + service (, bool, ) + Specify whether service is enabled. + + + srv_cpu_multiplier (, int, ) + Specify the number of SRV service worker threads per CPU. + + + srv_num_workers (, int, ) + Set the maximum number of SRV service worker threads. + + + support_multichannel (, bool, ) + Support multichannel. + + + support_netbios (, bool, ) + Support NetBIOS. + + + support_smb2 (, bool, ) + The support SMB2 attribute. + + + support_smb3_encryption (, bool, ) + Support the SMB3 encryption on the server. + + + + + + +Status +------ + + + + + +Authors +~~~~~~~ + +- Sachin Apagundi (@sachin-apa) + diff --git a/docs/modules/snmp_settings.rst b/docs/modules/snmp_settings.rst new file mode 100644 index 00000000..c0bed8e1 --- /dev/null +++ b/docs/modules/snmp_settings.rst @@ -0,0 +1,237 @@ +.. _snmp_settings_module: + + +snmp_settings -- Manage SNMP settings on PowerScale storage systems +=================================================================== + +.. contents:: + :local: + :depth: 1 + + +Synopsis +-------- + +Manage SNMP settings on PowerScale storage systems includes retrieving, and updating SNMP settings. + + + +Requirements +------------ +The below requirements are needed on the host that executes this module. + +- A Dell PowerScale Storage system. +- Ansible-core 2.14 or later. +- Python 3.9, 3.10 or 3.11. + + + +Parameters +---------- + + read_only_community (optional, str, None) + SNMP read-only community name. + + The system default value of the read-only community name is ``I$ilonpublic``. + + Update the read-only community name while enabling SNMP v2c. + + + service (optional, bool, None) + Whether the SNMP service is enabled. + + + snmp_v2c_access (optional, bool, None) + Whether the SNMP v2c is enabled. + + OneFS support SNMP v2c and later. + + + snmp_v3 (optional, dict, None) + Specify the access, privacy, and security level for SNMP v3. + + + access (optional, bool, None) + Whether SNMP v3 is enabled. + + + auth_protocol (optional, str, None) + SNMP v3 authentication protocol. + + + privacy_password (optional, str, None) + SNMP v3 privacy password. + + + password (optional, str, None) + SNMP v3 authentication password. + + + privacy_protocol (optional, str, None) + SNMP v3 privacy protocol. + + + security_level (optional, str, None) + SNMP v3 security level. + + + read_only_user (optional, str, None) + The read-only user for SNMP v3 requests. + + The system default value of read-only user is ``general``. + + + + system_contact (optional, str, None) + SNMP system owner contact information. + + This must be a valid email address. + + The contact information is set for the reporting purpose. + + + system_location (optional, str, None) + The cluster description for SNMP system. + + The cluster description is set for the reporting purpose. + + + onefs_host (True, str, None) + IP address or FQDN of the PowerScale cluster. + + + port_no (False, str, 8080) + Port number of the PowerScale cluster.It defaults to 8080 if not specified. + + + verify_ssl (True, bool, None) + boolean variable to specify whether to validate SSL certificate or not. + + ``true`` - indicates that the SSL certificate should be verified. + + ``false`` - indicates that the SSL certificate should not be verified. + + + api_user (True, str, None) + username of the PowerScale cluster. + + + api_password (True, str, None) + the password of the PowerScale cluster. + + + + + +Notes +----- + +.. note:: + - The *check_mode* is supported. + - Users can configure SNMP version 3 alone or in combination with version 2c. + - Idempotency is not supported for SNMP v3's password and privacy password. + - The modules present in this collection named as 'dellemc.powerscale' are built to support the Dell PowerScale storage platform. + + + + +Examples +-------- + +.. code-block:: yaml+jinja + + + - name: Get SNMP settings + dellemc.powerscale.snmp_settings: + onefs_host: "{{ onefs_host }}" + api_user: "{{ api_user }}" + api_password: "{{ api_password }}" + verify_ssl: "{{ verify_ssl }}" + + - name: Update SNMP settings + dellemc.powerscale.snmp_settings: + onefs_host: "{{ onefs_host }}" + api_user: "{{ api_user }}" + api_password: "{{ api_password }}" + verify_ssl: "{{ verify_ssl }}" + read_only_community: "community-name" + snmp_v3: + access: true + auth_protocol: "SHA" + privacy_password: "password" + password: "auth_password" + privacy_protocol: "AES" + security_level: "noAuthNoPriv" + read_only_user: "user" + system_contact: "contact@domain.com" + system_location: "Enabled SNMP" + + + +Return Values +------------- + +changed (always, bool, true) + A Boolean value indicating if task had to make changes. + + +snmp_settings (always, dict, {'read_only_community': 'community-name', 'service': True, 'snmp_v1_v2c_access': True, 'snmp_v3_access': True, 'snmp_v3_auth_protocol': 'SHA', 'snmp_v3_priv_protocol': 'AES', 'snmp_v3_read_only_user': 'user', 'snmp_v3_security_level': 'noAuthNoPriv', 'system_contact': 'contact@domain.com', 'system_location': 'Enabled SNMP'}) + The details of SNMP settings. + + + read_only_community (, str, ) + SNMP read-only community name. + + + service (, bool, ) + Whether the SNMP service is enabled. + + + snmp_v1_v2c_access (, bool, ) + Whether the SNMP v2c access is enabled. + + + snmp_v3_access (, bool, ) + Whether the SNMP v3 is enabled. + + + snmp_v3_auth_protocol (, str, ) + SNMP v3 authentication protocol. + + + snmp_v3_priv_protocol (, str, ) + SNMP v3 privacy protocol. + + + smnmp_v3_read_only_user (, str, ) + SNMP v3 read-only user. + + + snmp_v3_security_level (, str, ) + SNMP v3 security level. + + + system_contact (, str, ) + SNMP system owner contact information. + + + system_location (, str, ) + The cluster description for SNMP system. + + + + + + +Status +------ + + + + + +Authors +~~~~~~~ + +- Bhavneet Sharma(@Bhavneet-Sharma) + diff --git a/galaxy.yml b/galaxy.yml index b7a599c8..cf7eeeea 100644 --- a/galaxy.yml +++ b/galaxy.yml @@ -14,7 +14,7 @@ name: powerscale # The version of the collection. # Must be compatible with semantic versioning -version: 2.3.0 +version: 2.4.0 # The path to the Markdown (.md) readme file. # This path is relative to the root of the collection. @@ -38,6 +38,7 @@ authors: - Trisha Datta - Bhavneet Sharma - Pavan Mudunuri + - Sachin Apagundi ### OPTIONAL but strongly recommended @@ -65,13 +66,13 @@ tags: [storage] dependencies: {} # The URL of the originating SCM repository -repository: https://github.com/dell/ansible-powerscale/tree/2.3.0 +repository: https://github.com/dell/ansible-powerscale/tree/2.4.0 # The URL to any online docs -documentation: https://github.com/dell/ansible-powerscale/tree/2.3.0/docs +documentation: https://github.com/dell/ansible-powerscale/tree/2.4.0/docs # The URL to the homepage of the collection/project -homepage: https://github.com/dell/ansible-powerscale/tree/2.3.0 +homepage: https://github.com/dell/ansible-powerscale/tree/2.4.0 # The URL to the collection issue tracker issues: https://www.dell.com/community/Automation/bd-p/Automation diff --git a/playbooks/modules/info.yml b/playbooks/modules/info.yml index c90315c7..d3ff650e 100644 --- a/playbooks/modules/info.yml +++ b/playbooks/modules/info.yml @@ -209,3 +209,24 @@ api_password: "{{ api_password }}" gather_subset: - s3_buckets + + - name: Get SMB global settings from PowerScale cluster + dellemc.powerscale.info: + onefs_host: "{{ onefs_host }}" + verify_ssl: "{{ verify_ssl }}" + api_user: "{{ api_user }}" + api_password: "{{ api_password }}" + gather_subset: + - smb_global_settings + + - name: Get attributes, access_zones and nodes of the PowerScale cluster + dellemc.powerscale.info: + onefs_host: "{{ onefs_host }}" + verify_ssl: "{{ verify_ssl }}" + api_user: "{{ api_user }}" + api_password: "{{ api_password }}" + gather_subset: + - ntp_servers + - email_settings + - cluster_identity + - cluster_owner diff --git a/playbooks/modules/networkpool.yml b/playbooks/modules/networkpool.yml index 9a888ac3..4712da2c 100644 --- a/playbooks/modules/networkpool.yml +++ b/playbooks/modules/networkpool.yml @@ -15,32 +15,6 @@ subnet_name: 'subnet0' description: "pool Created by Ansible" new_pool_name: "rename_Test_pool_1" - additional_pool_params_mod: - ranges: - - low: "10.**.**.176" - high: "10.**.**.178" - range_state: "add" - ifaces: - - iface: "ext-1" - lnn: 1 - - iface: "ext-2" - lnn: 1 - iface_state: "add" - static_routes: - - gateway: "10.**.**.**" - prefixlen: 21 - subnet: "10.**.**.**" - sc_params_mod: - sc_dns_zone: "10.**.**.169" - sc_connect_policy: "round_robin" - sc_failover_policy: "round_robin" - rebalance_policy: "auto" - alloc_method: "static" - sc_auto_unsuspend_delay: 0 - sc_ttl: 0 - aggregation_mode: "roundrobin" - sc_dns_zone_aliases: - - "Test" tasks: - name: Create Network pool @@ -55,18 +29,6 @@ pool_name: "Test_pool_1" state: "{{ state_present }}" - - name: Create Network pool Idempotency case - dellemc.powerscale.networkpool: - onefs_host: "{{ onefs_host }}" - api_user: "{{ api_user }}" - api_password: "{{ api_password }}" - verify_ssl: "{{ verify_ssl }}" - access_zone: "{{ access_zone }}" - groupnet_name: "{{ groupnet_name }}" - subnet_name: "{{ subnet_name }}" - pool_name: "Test_pool_1" - state: "{{ state_present }}" - - name: Get Network pool dellemc.powerscale.networkpool: onefs_host: "{{ onefs_host }}" @@ -87,9 +49,38 @@ groupnet_name: "{{ groupnet_name }}" subnet_name: "{{ subnet_name }}" pool_name: "Test_pool_1" - sc_params: "{{ sc_params_mod }}" + sc_params: + sc_dns_zone: "10.**.**.169" + sc_connect_policy: "round_robin" + sc_failover_policy: "round_robin" + rebalance_policy: "auto" + alloc_method: "static" + sc_auto_unsuspend_delay: 0 + sc_ttl: 0 + aggregation_mode: "roundrobin" + sc_dns_zone_aliases: + - "Test" + static_routes: + - gateway: "10.**.**.**" + prefix_len: 21 + subnet: "10.**.**.**" + route_state: "add" + - gateway: "11.**.**.**" + prefix_len: 24 + subnet: "11.**.**.**" + route_state: "add" description: "pool_name Created by Ansible Modify" - additional_pool_params: "{{ additional_pool_params_mod }}" + additional_pool_params: + ranges: + - low: "10.**.**.176" + high: "10.**.**.178" + range_state: "add" + ifaces: + - iface: "ext-1" + lnn: 1 + - iface: "ext-2" + lnn: 1 + iface_state: "add" state: "{{ state_present }}" - name: Rename a network Pool @@ -114,14 +105,3 @@ subnet_name: "{{ subnet_name }}" pool_name: "Test_pool_1" state: "{{ state_absent }}" - - - name: Delete Network pool Idempotency case - dellemc.powerscale.networkpool: - onefs_host: "{{ onefs_host }}" - api_user: "{{ api_user }}" - api_password: "{{ api_password }}" - verify_ssl: "{{ verify_ssl }}" - groupnet_name: "{{ groupnet_name }}" - subnet_name: "{{ subnet_name }}" - pool_name: "Test_pool_1" - state: "{{ state_absent }}" diff --git a/playbooks/modules/settings.yml b/playbooks/modules/settings.yml index d07ceaa9..13633c6f 100644 --- a/playbooks/modules/settings.yml +++ b/playbooks/modules/settings.yml @@ -3,58 +3,59 @@ hosts: localhost connection: local vars: - onefs_host: '10.**.**.**' + onefs_host: '10.**.**.242' verify_ssl: false - api_user: 'user' - api_password: 'Password' + api_user: '***' + api_password: '***' state_present: 'present' state_absent: 'absent' - mail_relay: 'mailrelay.itp.dell.com' - mail_sender: 'lab-a2@dell.com' - mail_subject: 'alerts' + mail_relay: 'mailrelay.itp11.dell.com' + mail_sender: 'lab-a21@dell.com' + mail_subject: 'alerts11' ntp_servers_add: - - '10.**.**.27' - - '10.**.**.28' - - '10.**.**.29' + - '10.**.**.21' + - '10.**.**.22' + - '10.**.**.23' ntp_servers_remove: - - '10.**.**.27' - ntp_server_id: "10.**.**.27" - email_settings: true + - '10.**.**.29' + ntp_server_id: "10.10.230.21" tasks: - - name: Update email settings and add NTP server + - name: Update cluster settings dellemc.powerscale.settings: onefs_host: "{{ onefs_host }}" api_user: "{{ api_user }}" api_password: "{{ api_password }}" verify_ssl: "{{ verify_ssl }}" state: "{{ state_present }}" - mail_relay: "{{ mail_relay }}" - mail_sender: "{{ mail_sender }}" - mail_subject: "{{ mail_subject }}" ntp_servers: "{{ ntp_servers_add }}" - - - name: Update email settings and add NTP server - Idempotency - dellemc.powerscale.settings: - onefs_host: "{{ onefs_host }}" - api_user: "{{ api_user }}" - api_password: "{{ api_password }}" - verify_ssl: "{{ verify_ssl }}" - state: "{{ state_present }}" mail_relay: "{{ mail_relay }}" mail_sender: "{{ mail_sender }}" mail_subject: "{{ mail_subject }}" - ntp_servers: "{{ ntp_servers_add }}" + name: "PIE-IsilonS-24241-Cluster" + description: "This is new description for the cluster" + logon_details: + message_title: "This is the new title" + description: "This is new description" + company: "Test company" + location: "Test location" + primary_contact: + name: "primary_name11" + phone1: "primary_phone11" + phone2: "primary_phone21" + email: "primary_email1@email.com" + secondary_contact: + name: "secondary_name11" + phone1: "secondary_phone11" + phone2: "secondary_phone21" + email: "secondary_email1@email.com" - - name: Get email settings and NTP Server Details + - name: Get cluster settings dellemc.powerscale.settings: onefs_host: "{{ onefs_host }}" api_user: "{{ api_user }}" api_password: "{{ api_password }}" verify_ssl: "{{ verify_ssl }}" - email_settings: "{{ email_settings }}" - ntp_server_id: "{{ ntp_server_id }}" - state: "{{ state_present }}" - name: Remove NTP server dellemc.powerscale.settings: @@ -64,12 +65,3 @@ verify_ssl: "{{ verify_ssl }}" ntp_servers: "{{ ntp_servers_remove }}" state: "{{ state_absent }}" - - - name: Remove NTP server - Idempotency - dellemc.powerscale.settings: - onefs_host: "{{ onefs_host }}" - api_user: "{{ api_user }}" - api_password: "{{ api_password }}" - verify_ssl: "{{ verify_ssl }}" - ntp_servers: "{{ ntp_servers_remove }}" - state: "{{ state_absent }}" diff --git a/playbooks/modules/smb_global_settings.yml b/playbooks/modules/smb_global_settings.yml new file mode 100644 index 00000000..6aa86a3b --- /dev/null +++ b/playbooks/modules/smb_global_settings.yml @@ -0,0 +1,46 @@ +--- +- name: SMB Global Settings Module Operations on PowerScale Storage + hosts: localhost + connection: local + vars: + onefs_host: "10.**.**.**" + port_no: "1234" + api_user: "user" + api_password: "password" + verify_ssl: false + + tasks: + - name: Get SMB global settings + dellemc.powerscale.smb_global_settings: + onefs_host: "{{ onefs_host }}" + port_no: "{{ port_no }}" + api_user: "{{ api_user }}" + api_password: "{{ api_password }}" + verify_ssl: "{{ verify_ssl }}" + + - name: Update SMB global settings + dellemc.powerscale.smb_global_settings: + onefs_host: "{{ onefs_host }}" + port_no: "{{ port_no }}" + api_user: "{{ api_user }}" + api_password: "{{ api_password }}" + verify_ssl: "{{ verify_ssl }}" + access_based_share_enum: true + dot_snap_accessible_child: true + dot_snap_accessible_root: false + dot_snap_visible_child: false + dot_snap_visible_root: true + enable_security_signatures: true + guest_user: user + ignore_eas: false + onefs_cpu_multiplier: 2 + onefs_num_workers: 4 + reject_unencrypted_access: true + require_security_signatures: true + server_side_copy: true + server_string: 'PowerScale Server' + service: false + support_multichannel: true + support_netbios: true + support_smb2: true + support_smb3_encryption: false diff --git a/playbooks/modules/snmp_settings.yml b/playbooks/modules/snmp_settings.yml new file mode 100644 index 00000000..63a14288 --- /dev/null +++ b/playbooks/modules/snmp_settings.yml @@ -0,0 +1,55 @@ +--- +- name: SNMP Settings Module Operations on PowerScale Storage + hosts: localhost + connection: local + vars: + onefs_host: "**.**.**.**" + port_no: "0000" + api_user: "user" + api_password: "pass" + verify_ssl: false + + tasks: + - name: Get SNMP settings + dellemc.powerscale.snmp_settings: + onefs_host: "{{ onefs_host }}" + api_user: "{{ api_user }}" + api_password: "{{ api_password }}" + verify_ssl: "{{ verify_ssl }}" + + - name: Update SNMP settings - Check_mode + dellemc.powerscale.snmp_settings: + onefs_host: "{{ onefs_host }}" + api_user: "{{ api_user }}" + api_password: "{{ api_password }}" + verify_ssl: "{{ verify_ssl }}" + read_only_community: "community-name" + snmp_v3: + access: true + auth_protocol: "SHA" + privacy_password: "password" + password: "auth_password" + privacy_protocol: "AES" + security_level: "noAuthNoPriv" + read_only_user: "user" + system_contact: "contact@domain.com" + system_location: "Enabled SNMP" + check_mode: true + + - name: Update SNMP settings + dellemc.powerscale.snmp_settings: + onefs_host: "{{ onefs_host }}" + api_user: "{{ api_user }}" + api_password: "{{ api_password }}" + verify_ssl: "{{ verify_ssl }}" + read_only_community: "community-name" + snmp_v3: + access: true + auth_protocol: "SHA" + privacy_password: "password" + password: "auth_password" + privacy_protocol: "AES" + security_level: "noAuthNoPriv" + read_only_user: "user" + system_contact: "contact@domain.com" + system_location: "Enabled SNMP" diff --git a/plugins/module_utils/storage/dell/shared_library/cluster.py b/plugins/module_utils/storage/dell/shared_library/cluster.py new file mode 100644 index 00000000..f8fbbe1a --- /dev/null +++ b/plugins/module_utils/storage/dell/shared_library/cluster.py @@ -0,0 +1,65 @@ +# Copyright: (c) 2023, Dell Technologies + +# Apache License version 2.0 (see MODULE-LICENSE or http://www.apache.org/licenses/LICENSE-2.0.txt) + +from __future__ import (absolute_import, division, print_function) + +__metaclass__ = type + +from ansible_collections.dellemc.powerscale.plugins.module_utils.storage.dell \ + import utils + +LOG = utils.get_logger('cluster') + + +class Cluster: + + '''Class with shared Cluster operations''' + + def __init__(self, cluster_api, module): + """ + Initialize the cluster class + :param cluster_api: The cluster sdk instance + :param module: Ansible module object + """ + self.cluster_api = cluster_api + self.module = module + + def get_email_settings(self): + """ + Get cluster email settings + :return: Cluster email settings. + """ + try: + email_details = self.cluster_api.get_cluster_email() + return email_details.to_dict() + except Exception as e: + error_message = f"Failed to get the details of email settings with error: {utils.determine_error(e)}" + LOG.error(error_message) + self.module.fail_json(msg=error_message) + + def get_cluster_identity_details(self): + """ + Get cluster email settings + :return: Cluster identity details. + """ + try: + cluster_identity = self.cluster_api.get_cluster_identity() + return cluster_identity.to_dict() + except Exception as e: + error_message = f"Failed to get the details of cluster identity details with error: {utils.determine_error(e)}" + LOG.error(error_message) + self.module.fail_json(msg=error_message) + + def get_cluster_owner_details(self): + """ + Get cluster email settings + :return: Cluster owner details. + """ + try: + cluster_owner = self.cluster_api.get_cluster_owner() + return cluster_owner.to_dict() + except Exception as e: + error_message = f"Failed to get the details of cluster owner details with error: {utils.determine_error(e)}" + LOG.error(error_message) + self.module.fail_json(msg=error_message) diff --git a/plugins/module_utils/storage/dell/shared_library/powerscale_base.py b/plugins/module_utils/storage/dell/shared_library/powerscale_base.py index d1e6dfb7..b58c69b1 100644 --- a/plugins/module_utils/storage/dell/shared_library/powerscale_base.py +++ b/plugins/module_utils/storage/dell/shared_library/powerscale_base.py @@ -49,6 +49,7 @@ def __init__(self, ansible_module, ansible_module_params): self._protocol_api = None self._auth_api = None self._synciq_api = None + self._cluster_api = None @property def protocol_api(self): @@ -85,3 +86,15 @@ def synciq_api(self): if self._synciq_api is None: self._synciq_api = self.isi_sdk.SyncApi(self.api_client) return self._synciq_api + + @property + def cluster_api(self): + """ + Returns the cluster API object. + + :return: The cluster API object. + :rtype: isi_sdk.ClusterApi + """ + if self._cluster_api is None: + self._cluster_api = self.isi_sdk.ClusterApi(self.api_client) + return self._cluster_api diff --git a/plugins/module_utils/storage/dell/shared_library/protocol.py b/plugins/module_utils/storage/dell/shared_library/protocol.py index 20686b1f..46f271c0 100644 --- a/plugins/module_utils/storage/dell/shared_library/protocol.py +++ b/plugins/module_utils/storage/dell/shared_library/protocol.py @@ -55,7 +55,6 @@ def get_s3_bucket_list(self): :rtype: dict """ try: - s3_bucket_list = [] s3_bucket_details = (self.protocol_api.list_s3_buckets()).to_dict() if s3_bucket_details: return s3_bucket_details @@ -64,3 +63,56 @@ def get_s3_bucket_list(self): error_message = f'Fetching S3 bucket list failed with error: {error_msg}' LOG.error(error_message) self.module.fail_json(msg=error_message) + + def get_smb_global_settings(self): + """ + Get details of SMB global settings + """ + msg = "Getting SMB global settings details" + LOG.info(msg) + try: + smb_global_obj = self.protocol_api.get_smb_settings_global().to_dict() + if smb_global_obj: + msg = f"SMB global settings details are: {smb_global_obj}" + LOG.info(msg) + return smb_global_obj['settings'] + + except Exception as e: + error_msg = f"Got error {utils.determine_error(e)} while getting" \ + f" SMB global setings details " + LOG.error(error_msg) + self.module.fail_json(msg=error_msg) + + def get_ntp_server_list(self): + """ + Get the list of NTP Servers configured a given PowerScale Storage + :return: NTP server list + :rtype: dict + """ + try: + ntp_server_list = self.protocol_api.list_ntp_servers() + return ntp_server_list.to_dict() + except Exception as e: + error_message = f"Failed to get NTP server list: {utils.determine_error(e)}" + LOG.error(error_message) + self.module.fail_json(msg=error_message) + + def get_snmp_settings(self): + """ + Get details of SNMP settings + :return: SNMP settings + :rtype: dict + """ + try: + snmp_settings = self.protocol_api.get_snmp_settings().to_dict() + if snmp_settings: + snmp_setting = snmp_settings['settings'] + msg = f"SNMP settings are: {snmp_setting}" + LOG.info(msg) + return snmp_setting + except Exception as e: + error_msg = utils.determine_error(error_obj=e) + error_message = f'Fetching SNMP settings failed with ' \ + f'error: {error_msg}' + LOG.error(error_message) + self.module.fail_json(msg=error_message) diff --git a/plugins/module_utils/storage/dell/utils.py b/plugins/module_utils/storage/dell/utils.py index 5cc23af6..e95594a0 100644 --- a/plugins/module_utils/storage/dell/utils.py +++ b/plugins/module_utils/storage/dell/utils.py @@ -541,3 +541,8 @@ def get_nfs_map_object(): return import_obj.NfsExportMapAll() except ImportError: return None + + +def is_email_address_valid(address): + if address is not None and re.search(r'^\w+([\.-]?\w+)*@\w+([\.-]?\w+)*(\.\w{2,3})+$', address) is None: + return True diff --git a/plugins/modules/info.py b/plugins/modules/info.py index ad330be1..9cb715c8 100644 --- a/plugins/modules/info.py +++ b/plugins/modules/info.py @@ -26,12 +26,15 @@ SyncIQ policies, SyncIQ performance rules. - Get list of network groupnets, network pools for all access zones or a specific access zone, network rules, network subnets, network interfaces, - node pools, storage pool tiers, smb open files. + node pools, storage pool tiers, smb open files, s3 buckets, ntp_servers. - Get list of user mapping rules, ldap providers of the PowerScale cluster. - Get NFS zone settings details of the PowerScale cluster. - Get NFS default settings details of the PowerScale cluster. - Get NFS global settings details of the PowerScale cluster. - Get SyncIQ global settings details of the PowerScale cluster. +- Get SMB Global Settings details of the PowerScale cluster. +- Get cluster owner, cluster identity and email settings details of the PowerScale cluster. +- Get SNMP settings details of the PowerScale cluster. extends_documentation_fragment: - dellemc.powerscale.powerscale @@ -44,6 +47,7 @@ - Bhavneet Sharma(@Bhavneet-Sharma) - Trisha Datta(@trisha-dell) - Meenakshi Dembi(@dembim) +- Sachin Apagundi(@sachin-apa) options: include_all_access_zones: @@ -101,7 +105,7 @@ - NFS zone settings - C(nfs_zone_settings). - NFS default settings - C(nfs_default_settings). - SyncIQ global settings - C(synciq_global_settings). - - S3 buckets - C(s3_buckets) + - S3 buckets - C(s3_buckets). - The list of I(attributes), I(access_zones) and I(nodes) is for the entire PowerScale cluster. - The list of providers for the entire PowerScale cluster. @@ -118,14 +122,20 @@ - The list of smb open files for the entire PowerScale cluster. - The list of user mapping rules of PowerScale cluster. - The list of ldap providers of PowerScale cluster. - - The list of S3 bucket for the entire PowerScale cluster. + - SMB global settings - C(smb_global_settings). + - NTP servers C(ntp_servers) + - Email settings C(email_settings) + - Cluster identity C(cluster_identity) + - Cluster owner C(cluster_owner) + - SNMP settings - C(snmp_settings). required: true choices: [attributes, access_zones, nodes, providers, users, groups, smb_shares, nfs_exports, nfs_aliases, clients, synciq_reports, synciq_target_reports, synciq_policies, synciq_target_cluster_certificates, synciq_performance_rules, network_groupnets, network_subnets, network_pools, network_rules, network_interfaces, node_pools, storagepool_tiers, smb_files, user_mapping_rules, ldap, - nfs_zone_settings, nfs_default_settings, nfs_global_settings, synciq_global_settings, s3_buckets] + nfs_zone_settings, nfs_default_settings, nfs_global_settings, synciq_global_settings, s3_buckets, + smb_global_settings, ntp_servers, email_settings, cluster_identity, cluster_owner, snmp_settings] type: list elements: str notes: @@ -427,6 +437,60 @@ api_password: "{{ api_password }}" gather_subset: - s3_buckets + +- name: Get SMB global settings from PowerScale cluster + dellemc.powerscale.info: + onefs_host: "{{ onefs_host }}" + verify_ssl: "{{ verify_ssl }}" + api_user: "{{ api_user }}" + api_password: "{{ api_password }}" + gather_subset: + - smb_global_settings + +- name: Get NTP servers from PowerScale cluster + dellemc.powerscale.info: + onefs_host: "{{ onefs_host }}" + verify_ssl: "{{ verify_ssl }}" + api_user: "{{ api_user }}" + api_password: "{{ api_password }}" + gather_subset: + - ntp_servers + +- name: Get SNMP settings from PowerScale cluster + dellemc.powerscale.info: + onefs_host: "{{ onefs_host }}" + verify_ssl: "{{ verify_ssl }}" + api_user: "{{ api_user }}" + api_password: "{{ api_password }}" + gather_subset: + - snmp_settings + +- name: Get email settings details from PowerScale cluster + dellemc.powerscale.info: + onefs_host: "{{ onefs_host }}" + verify_ssl: "{{ verify_ssl }}" + api_user: "{{ api_user }}" + api_password: "{{ api_password }}" + gather_subset: + - email_settings + +- name: Get cluster identity details from PowerScale cluster + dellemc.powerscale.info: + onefs_host: "{{ onefs_host }}" + verify_ssl: "{{ verify_ssl }}" + api_user: "{{ api_user }}" + api_password: "{{ api_password }}" + gather_subset: + - cluster_identity + +- name: Get cluster owner details from PowerScale cluster + dellemc.powerscale.info: + onefs_host: "{{ onefs_host }}" + verify_ssl: "{{ verify_ssl }}" + api_user: "{{ api_user }}" + api_password: "{{ api_password }}" + gather_subset: + - cluster_owner ''' RETURN = r''' @@ -686,20 +750,64 @@ type: list returned: When C(network_interfaces) is in a given I(gather_subset) contains: + flags: + description: List of interface flags. + type: list id: description: ID of the interface. type: str + ip_addrs: + description: List of IP addresses. + type: list + ipv4_gateway: + description: Address of the default IPv4 gateway. + type: str + ipv6_gateway: + description: Address of the default IPv6 gateway. + type: str lnn: description: Interface's lnn. type: int + mtu: + description: The mtu the interface. + type: int name: description: Name of the interface. type: str + nic_name: + description: NIC name. + type: str + owners: + description: List of owners. + type: list + speed: + description: Interface's speed. + type: int + status: + description: Status of the interface. + type: str + type: + description: Type of the interface. + type: str + vlans: + description: List of VLANs. + type: list sample: [ - { - "id": "110gig1", - "lnn": 1, - "name": "10gig1" + { + "flags": [], + "id": "3:ext-agg", + "ip_addrs": [], + "ipv4_gateway": null, + "ipv6_gateway": null, + "lnn": 3, + "mtu": 0, + "name": "ext-agg", + "nic_name": "lagg0", + "owners": [], + "speed": null, + "status": "inactive", + "type": "aggregated", + "vlans": [] } ] NetworkPools: @@ -1646,6 +1754,326 @@ "path": "/ifs/", "zid": 1 } +SmbGlobalSettings: + description: The updated SMB global settings details. + type: dict + returned: always + contains: + access_based_share_enum: + description: Only enumerate files and folders the requesting user has access to. + type: bool + audit_fileshare: + description: Specify level of file share audit events to log. + type: str + audit_logon: + description: Specify the level of logon audit events to log. + type: str + dot_snap_accessible_child: + description: Allow access to .snapshot directories in share subdirectories. + type: bool + dot_snap_accessible_root: + description: Allow access to the .snapshot directory in the root of the share. + type: bool + dot_snap_visible_child: + description: Show .snapshot directories in share subdirectories. + type: bool + dot_snap_visible_root: + description: Show the .snapshot directory in the root of a share. + type: bool + enable_security_signatures: + description: Indicates whether the server supports signed SMB packets. + type: bool + guest_user: + description: Specifies the fully-qualified user to use for guest access. + type: str + ignore_eas: + description: Specify whether to ignore EAs on files. + type: bool + onefs_cpu_multiplier: + description: Specify the number of OneFS driver worker threads per CPU. + type: int + onefs_num_workers: + description: Set the maximum number of OneFS driver worker threads. + type: int + reject_unencrypted_access: + description: If SMB3 encryption is enabled, reject unencrypted access from clients. + type: bool + require_security_signatures: + description: Indicates whether the server requires signed SMB packets. + type: bool + server_side_copy: + description: Enable Server Side Copy. + type: bool + server_string: + description: Provides a description of the server. + type: str + service: + description: Specify whether service is enabled. + type: bool + srv_cpu_multiplier: + description: Specify the number of SRV service worker threads per CPU. + type: int + srv_num_workers: + description: Set the maximum number of SRV service worker threads. + type: int + support_multichannel: + description: Support multichannel. + type: bool + support_netbios: + description: Support NetBIOS. + type: bool + support_smb2: + description: The support SMB2 attribute. + type: bool + support_smb3_encryption: + description: Support the SMB3 encryption on the server. + type: bool + sample: { + "access_based_share_enum": false, + "audit_fileshare": null, + "audit_logon": null, + "dot_snap_accessible_child": true, + "dot_snap_accessible_root": true, + "dot_snap_visible_child": false, + "dot_snap_visible_root": true, + "enable_security_signatures": false, + "guest_user": "nobody", + "ignore_eas": false, + "onefs_cpu_multiplier": 4, + "onefs_num_workers": 0, + "reject_unencrypted_access": false, + "require_security_signatures": false, + "server_side_copy": false, + "server_string": "PowerScale Server", + "service": true, + "srv_cpu_multiplier": null, + "srv_num_workers": null, + "support_multichannel": true, + "support_netbios": false, + "support_smb2": true, + "support_smb3_encryption": true + } +email_settings: + description: Details of the email settings. + type: dict + returned: Always + contains: + settings: + description: Details of the settings. + returned: Always + type: dict + contains: + batch_mode: + description: This setting determines how notifications will be batched together to be sent by email. + type: str + mail_relay: + description: The address of the SMTP server to be used for relaying the notification messages. + type: str + mail_sender: + description: The full email address that will appear as the sender of notification messages. + type: str + mail_subject: + description: The subject line for notification messages from this cluster. + type: str + smtp_auth_passwd_set: + description: Indicates if an SMTP authentication password is set. + type: bool + smtp_auth_security: + description: The type of secure communication protocol to use if SMTP is being used. + type: str + smtp_auth_username: + description: Username to authenticate with if SMTP authentication is being used. + type: str + smtp_port: + description: The port on the SMTP server to be used for relaying the notification messages. + type: int + use_smtp_auth: + description: If true, this cluster will send SMTP authentication credentials to the + SMTP relay server in order to send its notification emails. + type: bool + user_template: + description: Location of a custom template file that can be used to specify the layout of the notification emails. + type: str + sample: + { + "settings": { + "batch_mode": "none", + "mail_relay": "10.**.**.**", + "mail_sender": "powerscale@dell.com", + "mail_subject": "Powerscale Cluster notifications", + "smtp_auth_passwd_set": false, + "smtp_auth_security": "none", + "smtp_auth_username": "", + "smtp_port": 25, + "use_smtp_auth": false, + "user_template": "" + } + } + +ntp_servers: + description: List of NTP servers. + type: dict + returned: Always + contains: + servers: + description: List of servers. + type: list + contains: + id: + description: Field id. + type: str + key: + description: Key value from I(key_file) that maps to this server. + type: str + name: + description: NTP server name. + type: str + sample: + { + "servers": [ + { + "id": "10.**.**.**", + "key": null, + "name": "10.**.**.**" + } + ] + } +cluster_identity: + description: Details related to cluster identity. + type: dict + returned: Always + contains: + description: + description: Description of PowerScale cluster. + type: str + logon: + description: Details of logon message shown on Powerscale login screen. + type: dict + contains: + motd: + description: Details of logon message. + type: str + motd_header: + description: Details of logon message title. + type: str + mttdl_level_msg: + description: mttdl_level_msg. + type: str + name: + description: Name of PowerScale cluster. + type: str + sample: + { + "cluster_identity": + { + "description": "asdadasdasdasdadadadds", + "logon": + { + "motd": "This is new description", + "motd_header": "This is the new title" + }, + "mttdl_level_msg": "none", + "name": "PIE-IsilonS-24241-Clusterwrerwerwrewr" + } + } +cluster_owner: + description: Details related to cluster identity. + type: dict + returned: Always + contains: + company: + description: Name of the company. + type: str + location: + description: Location of the company. + type: str + primary_email: + description: Email of primary system admin. + type: str + primary_name: + description: Name of primary system admin. + type: str + primary_phone1: + description: Phone1 of primary system admin. + type: str + primary_phone2: + description: Phone2 of primary system admin. + type: str + secondary_email: + description: Email of secondary system admin. + type: str + secondary_name: + description: Name of secondary system admin. + type: str + secondary_phone1: + description: Phone1 of secondary system admin. + type: str + secondary_phone2: + description: Phone2 of secondary system admin. + type: str + sample: + { + "cluster_owner": + { + "company": "Test company", + "location": "Test location", + "primary_email": "primary_email@email.com", + "primary_name": "primary_name", + "primary_phone1": "primary_phone1", + "primary_phone2": "primary_phone2", + "secondary_email": "secondary_email@email.com", + "secondary_name": "secondary_name", + "secondary_phone1": "secondary_phone1", + "secondary_phone2": "secondary_phone2" + } + } +SnmpSettings: + description: The SNMP settings details. + type: dict + returned: When C(snmp_settings) is in a given I(gather_subset) + contains: + read_only_community: + description: SNMP Read-only community name. + type: str + service: + description: Whether the SNMP Service is enabled. + type: bool + snmp_v1_v2c_access: + description: Whether the SNMP v2c access is enabled. + type: bool + snmp_v3_access: + description: Whether the SNMP v3 access is enabled. + type: bool + snmp_v3_auth_protocol: + description: SNMP v3 authentication protocol. + type: str + snmp_v3_priv_protocol: + description: SNMP v3 privacy protocol. + type: str + snmp_v3_security_level: + description: SNMP v3 security level. + type: str + snmp_v3_read_only_user: + description: SNMP v3 read-only user. + type: str + system_contact: + description: SNMP system owner contact information. + type: str + system_location: + description: The cluster description of the SNMP system. + type: str + sample: { + "read_only_community": "public", + "service": true, + "snmp_v1_v2c_access": true, + "snmp_v3_access": true, + "snmp_v3_auth_protocol": "MD5", + "snmp_v3_priv_protocol": "DES", + "snmp_v3_security_level": "authPriv", + "snmp_v3_read_only_user": "general", + "system_contact": "system", + "system_location": "cluster" + } ''' from ansible.module_utils.basic import AnsibleModule @@ -1653,6 +2081,8 @@ import Protocol from ansible_collections.dellemc.powerscale.plugins.module_utils.storage.dell.shared_library.synciq \ import SyncIQ +from ansible_collections.dellemc.powerscale.plugins.module_utils.storage.dell.shared_library.cluster \ + import Cluster from ansible_collections.dellemc.powerscale.plugins.module_utils.storage.dell \ import utils @@ -2067,23 +2497,12 @@ def get_network_rules(self): def get_network_interfaces(self): """Get the list of network interfaces of a given PowerScale Storage""" try: - network_interfaces_list = [] network_interfaces_details = (self.network_api.get_network_interfaces()).to_dict() - interfaces = network_interfaces_details['interfaces'] - if interfaces: - for interface in interfaces: - network_interfaces_list.append({ - "id": interface["id"], - "name": interface["name"], - "lnn": interface["lnn"] - }) - return network_interfaces_list + return network_interfaces_details['interfaces'] except Exception as e: - error_msg = ( - 'Getting list of network interfaces for PowerScale: %s failed with ' - 'error: %s' % ( - self.module.params['onefs_host'], - utils.determine_error(e))) + error_msg = (f"Getting list of network interfaces for PowerScale: " + f"{self.module.params['onefs_host']} failed with " + f"error: {utils.determine_error(e)}") LOG.error(error_msg) self.module.fail_json(msg=error_msg) @@ -2267,6 +2686,12 @@ def perform_module_operation(self): nfs_global_settings = {} synciq_global_settings = {} s3_buckets = {} + smb_global_settings = {} + ntp_servers = {} + email_settings = {} + cluster_identity = {} + cluster_owner = {} + snmp_settings = {} if 'attributes' in str(subset): attributes = self.get_attributes_list() @@ -2328,6 +2753,20 @@ def perform_module_operation(self): synciq_global_settings = SyncIQ(self.synciq_api, self.module).get_synciq_global_settings() if 's3_buckets' in str(subset): s3_buckets = Protocol(self.protocol_api, self.module).get_s3_bucket_list() + if 'smb_global_settings' in str(subset): + smb_global_settings = Protocol( + self.protocol_api, self.module).get_smb_global_settings() + if 'ntp_servers' in str(subset): + ntp_servers = Protocol(self.protocol_api, self.module).get_ntp_server_list() + if 'email_settings' in str(subset): + email_settings = Cluster(self.cluster_api, self.module).get_email_settings() + if 'cluster_identity' in str(subset): + cluster_identity = Cluster(self.cluster_api, self.module).get_cluster_identity_details() + if 'cluster_owner' in str(subset): + cluster_owner = Cluster(self.cluster_api, self.module).get_cluster_owner_details() + if 'snmp_settings' in str(subset): + snmp_settings = Protocol( + self.protocol_api, self.module).get_snmp_settings() result = dict( Attributes=attributes, @@ -2358,7 +2797,13 @@ def perform_module_operation(self): NfsDefaultSettings=nfs_default_settings, NfsGlobalSettings=nfs_global_settings, SynciqGlobalSettings=synciq_global_settings, - s3Buckets=s3_buckets + s3Buckets=s3_buckets, + SmbGlobalSettings=smb_global_settings, + NTPServers=ntp_servers, + EmailSettings=email_settings, + ClusterIdentity=cluster_identity, + ClusterOwner=cluster_owner, + SnmpSettings=snmp_settings ) result.update(SynciqTargetClusterCertificate=synciq_target_cluster_certificates) @@ -2420,7 +2865,13 @@ def get_info_parameters(): 'nfs_default_settings', 'nfs_global_settings', 'synciq_global_settings', - 's3_buckets' + 's3_buckets', + 'smb_global_settings', + 'ntp_servers', + 'email_settings', + 'cluster_identity', + 'cluster_owner', + 'snmp_settings', ]), ) diff --git a/plugins/modules/networkpool.py b/plugins/modules/networkpool.py index 2253f6e2..89e5cb05 100644 --- a/plugins/modules/networkpool.py +++ b/plugins/modules/networkpool.py @@ -24,7 +24,8 @@ author: - Meenakshi Dembi (@dembim) -- Pavan Mudunuri(@Pavan-Mudunuri) +- Pavan Mudunuri (@Pavan-Mudunuri) +- Bhavneet Sharma (@Bhavneet-Sharma) options: pool_name: @@ -129,6 +130,12 @@ - Network address in the format xxx.xxx.xxx.xxx. type: str required: true + route_state: + description: + - This signifies if route needs to be added or removed. + type: str + choices: ['add', 'remove'] + default: 'add' sc_dns_zone: description: - SmartConnect zone name for the pool. @@ -178,7 +185,7 @@ type: str notes: - The I(check_mode) is not supported. -- Removal of static routes and I(sc_dns_zone_aliases) is not supported. +- Removal of I(sc_dns_zone_aliases) is not supported. ''' EXAMPLES = r''' @@ -200,19 +207,20 @@ lnn: 1 iface_state: "add" sc_params: - sc_dns_zone: "10.230.**.***" - sc_connect_policy: "throughput" - sc_failover_policy: "throughput" - rebalance_policy: "auto" - alloc_method: "static" - sc_auto_unsuspend_delay: 200 - sc_ttl: 200 - sc_dns_zone_aliases: - - "Test" - static_routes: - - gateway: "10.**.**.**" - prefix_len: 21 - subnet: "10.**.**.**" + sc_dns_zone: "10.230.**.***" + sc_connect_policy: "throughput" + sc_failover_policy: "throughput" + rebalance_policy: "auto" + alloc_method: "static" + sc_auto_unsuspend_delay: 200 + sc_ttl: 200 + sc_dns_zone_aliases: + - "Test" + static_routes: + - gateway: "10.**.**.**" + prefix_len: 21 + subnet: "10.**.**.**" + route_state: "add" pool: "Test_Pool_2" access_zone: "system" state: "present" @@ -238,29 +246,34 @@ subnet: "subnet0" pool: "Test_Pool_2" additional_pool_params: - ranges: - - low: "10.230.**.***" - high: "10.230.**.***" - range_state: "add" - ifaces: - - iface: "ext-1" - lnn: 1 - iface_state: "add" + ranges: + - low: "10.230.**.***" + high: "10.230.**.***" + range_state: "add" + ifaces: + - iface: "ext-1" + lnn: 1 + iface_state: "add" sc_params: - sc_dns_zone: "10.230.**.***" - sc_connect_policy: "throughput" - sc_failover_policy: "throughput" - rebalance_policy: "auto" - alloc_method: "static" - sc_auto_unsuspend_delay: 200 - sc_ttl: 200 - sc_dns_zone_aliases: - - "Test" - static_routes: - - gateway: "10.**.**.**" - prefix_len: 21 - subnet: "10.**.**.**" - aggregation_mode: "fec" + sc_dns_zone: "10.230.**.***" + sc_connect_policy: "throughput" + sc_failover_policy: "throughput" + rebalance_policy: "auto" + alloc_method: "static" + sc_auto_unsuspend_delay: 200 + sc_ttl: 200 + sc_dns_zone_aliases: + - "Test" + static_routes: + - gateway: "10.**.**.**" + prefix_len: 21 + subnet: "10.**.**.**" + route_state: "remove" + - gateway: "10.**.**.**" + prefix_len: 24 + subnet: "10.**.**.**" + route_state: "add" + aggregation_mode: "fec" description: "Pool Created by Ansible Modify" state: "present" @@ -514,6 +527,43 @@ def get_modify_list(self, source, target, key, state): elif state == 'remove': return [item for item in target[key] if item not in source[key]] + def check_modify_sc_params(self, pool_details, sc_params): + """ + Check whether SC params are modified + :param pool_details: Network pool object of existing pool + :param sc_params: Dictionary of parameters input from playbook + :return :Dictionary of parameters that needs to be modified + """ + if sc_params.get('sc_dns_zone_aliases'): + sc_params['sc_dns_zone_aliases'] = self.remove_duplicate_aliases( + sc_params['sc_dns_zone_aliases']) + + if sc_params.get('static_routes'): + updated_routes = self.replace_prefix_len(sc_params['static_routes']) + routes = from_routes_unique_routes(updated_routes, pool_details) + sc_params['static_routes'] = self.remove_duplicate_static_routes(routes) + + return sc_params + + def check_modify_for_common_params(self, pool_details, pool_params): + """ + Check whether network pool is modified for access zone, new pool name + and description + :param pool_details: Network pool object of existing pool + :param pool_params: Dictionary of parameters input from playbook + :return :Dictionary of parameters that needs to be modified + """ + modify_pool_dict = {} + network_pool_keys = ['description', 'access_zone', 'new_pool_name'] + for keys in network_pool_keys: + if keys == 'new_pool_name' and pool_params[keys] and \ + self.do_update(pool_details['pools'][0]['name'], pool_params[keys]): + modify_pool_dict['name'] = pool_params[keys] + elif pool_params[keys] and \ + self.do_update(pool_details['pools'][0][keys], pool_params[keys]): + modify_pool_dict[keys] = pool_params[keys] + return modify_pool_dict + def is_pool_modifiable(self, pool_details, pool_params): """ Check whether network pool is modifiable @@ -522,48 +572,37 @@ def is_pool_modifiable(self, pool_details, pool_params): :return :Dictionary of parameters that needs to be modified, if no parameters are to be modified dict is empty """ - modify_pool_dict = {} - network_pool_keys = ['description', 'access_zone'] - - for keys in network_pool_keys: - if pool_params[keys] and self.do_update(pool_details['pools'][0][keys], pool_params[keys]): - modify_pool_dict[keys] = pool_params[keys] - - if pool_params['new_pool_name'] and pool_params['new_pool_name'] != pool_details['pools'][0]['name']: - modify_pool_dict['name'] = pool_params['new_pool_name'] + modify_pool_dict = self.check_modify_for_common_params(pool_details, + pool_params) - if pool_params['additional_pool_params']: - ifaces = self.get_modify_list(pool_params['additional_pool_params'], + if pool_params.get('additional_pool_params'): + additional_pool_params = pool_params.get('additional_pool_params') + ifaces = self.get_modify_list(additional_pool_params, pool_details['pools'][0], 'ifaces', - pool_params['additional_pool_params']['iface_state']) - if pool_params['additional_pool_params']['iface_state'] == "remove" or \ - pool_params['additional_pool_params']['iface_state'] == "add" and ifaces: + additional_pool_params['iface_state']) + if additional_pool_params['iface_state'] in ["remove", "add"] and \ + ifaces: modify_pool_dict['ifaces'] = ifaces - ranges = self.get_modify_list(pool_params['additional_pool_params'], + ranges = self.get_modify_list(additional_pool_params, pool_details['pools'][0], 'ranges', - pool_params['additional_pool_params']['range_state']) - if pool_params['additional_pool_params']['range_state'] == "remove" or \ - pool_params['additional_pool_params']['range_state'] == "add" and ranges: + additional_pool_params['range_state']) + if additional_pool_params['range_state'] in ["remove", "add"] and \ + ranges: modify_pool_dict['ranges'] = ranges - if pool_params['sc_params']: + if pool_params.get('sc_params'): sc_params_modify = pool_params['sc_params'] - if sc_params_modify['sc_dns_zone_aliases']: - sc_params_modify['sc_dns_zone_aliases'] = \ - self.remove_duplicate_aliases(sc_params_modify['sc_dns_zone_aliases']) - if sc_params_modify['static_routes']: - sc_params_modify['static_routes'] = \ - self.remove_duplicate_static_routes(sc_params_modify['static_routes']) - self.replace_prefix_len(sc_params_modify['static_routes']) + sc_params_modify = self.check_modify_sc_params( + pool_details, sc_params_modify) for key in sc_params_modify: if sc_params_modify[key] is not None and sc_params_modify[key] != pool_details['pools'][0][key]: modify_pool_dict[key] = sc_params_modify[key] + return modify_pool_dict def remove_duplicate_static_routes(self, routes): - - route_dict = {(route["gateway"], route["prefix_len"], route["subnet"]): route for route in routes} + route_dict = {(route["gateway"], route["prefixlen"], route["subnet"]): route for route in routes} route_list = list(route_dict.values()) return route_list @@ -591,40 +630,67 @@ def remove_duplicate_aliases(self, aliases): [aliases_list.append(item) for item in aliases if item not in aliases_list] return aliases_list + def prepare_create_routes(self, routes): + """ + Prepare routes for network pool creation. + Filter routes to only include those with 'route_state' == 'add'. + :param routes: List of routes + :return: List of routes + """ + update_routes = [] + for route in routes: + if route.get('route_state') == 'add': + del route['route_state'] + update_routes.append(route) + + return update_routes + + def create_sc_params(self, sc_params): + """ + Create sc_params dictionary for creating new pool. + :param sc_params: Dictionary of parameters to be set for create network pool + :return: Dictionary of parameters to be set for create network pool + """ + sc_dns_zone_aliases = sc_params.get('sc_dns_zone_aliases', []) + static_routes = sc_params.get('static_routes', []) + + if sc_dns_zone_aliases: + sc_params['sc_dns_zone_aliases'] = self.remove_duplicate_aliases(sc_dns_zone_aliases) + + if static_routes: + modified_routes = self.replace_prefix_len(static_routes) + routes = self.prepare_create_routes(modified_routes) + sc_params["static_routes"] = self.remove_duplicate_static_routes(routes) + + return sc_params + def construct_pool_parameters(self, input_param): """ Construct dictionary of parameters to be set for create network pool :param input_param: Dictionary of parameters provided from playbook - :param network_pool_param: Network pool object of existing pool :return: Dictionary of parameters to be set for create network pool """ network_pool_param = {} - if input_param['pool_name']: - network_pool_param['name'] = input_param['pool_name'] - if input_param['access_zone']: - network_pool_param['access_zone'] = input_param['access_zone'] - if input_param['description']: - network_pool_param['description'] = input_param['description'] - if input_param['sc_params']: - sc_params = input_param['sc_params'] - if sc_params['sc_dns_zone_aliases']: - sc_params['sc_dns_zone_aliases'] = self.remove_duplicate_aliases(sc_params['sc_dns_zone_aliases']) - if sc_params['static_routes']: - sc_params['static_routes'] = self.remove_duplicate_static_routes(sc_params['static_routes']) - network_pool_param["static_routes"] = self.replace_prefix_len(sc_params['static_routes']) - for key in sc_params: - if sc_params[key]: - network_pool_param[key] = sc_params[key] - if input_param['additional_pool_params']: + keys = ['pool_name', 'description', 'access_zone'] + for key in keys: + if key == 'pool_name' and input_param.get(key): + network_pool_param['name'] = input_param.get(key) + elif input_param.get(key): + network_pool_param[key] = input_param.get(key) + + sc_params = input_param.get('sc_params', {}) + if sc_params: + sc_params = self.create_sc_params(sc_params) + network_pool_param.update(dict(sc_params.items())) + + if input_param.get('additional_pool_params'): additional_params = input_param['additional_pool_params'] - if additional_params['ifaces'] and additional_params['iface_state'] == "add": - network_pool_param['ifaces'] = [] - for ifaces in additional_params['ifaces']: - network_pool_param['ifaces'].append(ifaces) - if additional_params['ranges'] and additional_params['range_state'] == "add": - network_pool_param['ranges'] = [] - for ranges in additional_params['ranges']: - network_pool_param['ranges'].append(ranges) + if additional_params.get('ifaces') and additional_params.get('iface_state') == "add": + network_pool_param['ifaces'] = additional_params['ifaces'] + + if additional_params.get('ranges') and additional_params.get('range_state') == "add": + network_pool_param['ranges'] = additional_params['ranges'] + return network_pool_param def replace_prefix_len(self, routes): @@ -634,6 +700,47 @@ def replace_prefix_len(self, routes): route["prefixlen"] = route.pop("prefix_len") return routes + def validate_sc_params(self, pool_params): + """ + Validate sc_params + :param pool_params: Dictionary of parameters input from playbook + :return :Dictionary of parameters that needs to be modified + """ + static_routes = pool_params['sc_params'].get('static_routes', []) + if static_routes: + for routes in static_routes: + if any(value == "" or value is None for value in routes.values()): + msg = 'Invalid static route value' + self.module.fail_json(msg=msg) + + def validate_ifaces(self, ifaces): + """Validate ifaces + :param ifaces: Dictionary of parameters input from playbook + :return :Dictionary of parameters that needs to be modified + """ + if ifaces: + for index in ifaces: + for value in index.values(): + if value == "" or value is None: + self.module.fail_json( + msg='Please enter valid value for iface') + + def validate_additional_pool_params(self, pool_params): + """Validate additional_pool_params + :param pool_params: Dictionary of parameters input from playbook + :return :Dictionary of parameters that needs to be modified + """ + ranges = pool_params['additional_pool_params'].get('ranges', []) + if ranges: + for index in ranges: + for value in index.values(): + if not is_valid_ip(value): + self.module.fail_json( + msg='The value for IP range is invalid') + + ifaces = pool_params['additional_pool_params'].get('ifaces', []) + self.validate_ifaces(ifaces) + def validate_input(self, pool_params): error_msg = utils.is_invalid_name(pool_params['pool_name'], 'pool_name') @@ -644,30 +751,16 @@ def validate_input(self, pool_params): self.module.fail_json(msg="The maximum length for description is 128") if pool_params['new_pool_name']: - error_msg = utils.is_invalid_name(pool_params['new_pool_name'], 'new_pool_name') + error_msg = utils.is_invalid_name( + pool_params['new_pool_name'], 'new_pool_name') if error_msg: self.module.fail_json(msg=error_msg) - if pool_params['sc_params']: - if pool_params['sc_params']['static_routes']: - for index in pool_params['sc_params']['static_routes']: - for value in index.values(): - if value == "" or value is None: - msg = 'Invalid static route value' - self.module.fail_json(msg=f'{msg}') - - if pool_params['additional_pool_params']: - if pool_params['additional_pool_params']['ranges']: - for index in pool_params['additional_pool_params']['ranges']: - for value in index.values(): - if not is_valid_ip(value): - self.module.fail_json(msg='The value for IP range is invalid') - - if pool_params['additional_pool_params']['ifaces']: - for index in pool_params['additional_pool_params']['ifaces']: - for value in index.values(): - if value == "" or value is None: - self.module.fail_json(msg='Please enter valid value for iface') + if pool_params.get('sc_params'): + self.validate_sc_params(pool_params) + + if pool_params.get('additional_pool_params'): + self.validate_additional_pool_params(pool_params) def perform_module_operation(self): """ @@ -681,9 +774,6 @@ def perform_module_operation(self): subnet_name = self.module.params['subnet_name'] pool_name = self.module.params['pool_name'] state = self.module.params['state'] - access_zone = self.module.params['access_zone'] - sc_params = self.module.params['sc_params'] - additional_pool_params = self.module.params['additional_pool_params'] pool_params = self.module.params modify_pool_param = None new_pool_name = self.module.params['new_pool_name'] @@ -694,10 +784,12 @@ def perform_module_operation(self): for param in self.module.params: input_param[param] = self.module.params[param] - pool_details = self.get_network_pool(groupnet_name, subnet_name, pool_name) + pool_details = self.get_network_pool( + groupnet_name, subnet_name, pool_name) if state == "absent" and pool_details: - result['changed'] = self.delete_network_pool(groupnet_name, subnet_name, pool_name) + result['changed'] = self.delete_network_pool( + groupnet_name, subnet_name, pool_name) result['network_pool'] = [] if state == "present" and not pool_details: @@ -725,6 +817,30 @@ def perform_module_operation(self): self.module.exit_json(**result) +def from_routes_unique_routes(routes, pool_details): + """ + Convert list of routes to list of unique routes + :param routes: List of routes + :param pool_details: Network pool object + :return: List of unique routes + """ + existing_routes = pool_details['pools'][0]['static_routes'].copy() + add_existing_routes = [] + + for route in routes: + state = route.get('route_state') + del route['route_state'] + if state == 'add': + add_existing_routes.append(route) + elif state == 'remove' and route in existing_routes: + existing_routes.remove(route) + + exit_route = [dict(s) for s in set(frozenset(each_route.items()) for each_route in add_existing_routes)] + exit_route.extend(existing_routes) + exit_route.sort(key=lambda x: x['prefixlen']) + return exit_route + + def get_network_pool_parameters(): """This method provide parameter required for the ansible Network Pool modules on PowerScale""" @@ -736,17 +852,25 @@ def get_network_pool_parameters(): new_pool_name=dict(type='str'), sc_params=dict(type='dict', options=dict( sc_dns_zone=dict(type='str'), - sc_connect_policy=dict(type='str', choices=['round_robin', 'conn_count', 'throughput', 'cpu_usage']), - sc_failover_policy=dict(type='str', choices=['round_robin', 'conn_count', 'throughput', 'cpu_usage']), + sc_connect_policy=dict( + type='str', choices=['round_robin', 'conn_count', 'throughput', + 'cpu_usage']), + sc_failover_policy=dict( + type='str', choices=['round_robin', 'conn_count', 'throughput', + 'cpu_usage']), rebalance_policy=dict(type='str', choices=['auto', 'manual']), alloc_method=dict(type='str', choices=['dynamic', 'static']), - aggregation_mode=dict(type='str', choices=['roundrobin', 'failover', 'lacp', 'fec']), + aggregation_mode=dict( + type='str', choices=['roundrobin', 'failover', 'lacp', 'fec']), sc_subnet=dict(type='str'), sc_auto_unsuspend_delay=dict(type='int'), static_routes=dict(type='list', elements='dict', options=dict( gateway=dict(type='str', required=True), prefix_len=dict(type='int', required=True), - subnet=dict(type='str', required=True))), + subnet=dict(type='str', required=True), + route_state=dict( + type='str', choices=['add', 'remove'], + default='add'))), sc_dns_zone_aliases=dict(type='list', elements='str'), sc_ttl=dict(type='int'))), state=dict(required=True, type='str', choices=['absent', 'present']), diff --git a/plugins/modules/settings.py b/plugins/modules/settings.py index c4732071..0c8bcdb4 100644 --- a/plugins/modules/settings.py +++ b/plugins/modules/settings.py @@ -16,8 +16,11 @@ short_description: Manages general settings for PowerScale storage system description: -- Managing general settings on the PowerScale storage system which includes get and update operations for - email settings and add, remove and get operations for NTP servers. +- Managing general settings on the PowerScale storage system which includes the following. +- Get and update operations for email settings. +- Add remove and get operations for NTP servers. +- Get and update operation for cluster identity. +- Get and update operation for cluster owner. extends_documentation_fragment: - dellemc.powerscale.powerscale @@ -42,7 +45,8 @@ type: str email_settings: description: - - This is an addition flag to view the email settings. + - (deprecated) This is an addition flag to view the email settings. + - This option is deprecated and will be removed in the later version. type: bool ntp_servers: description: @@ -53,14 +57,85 @@ description: - The state option is used to mention the existence of pool. type: str - required: true choices: [absent, present] + default: present ntp_server_id: description: - ID of NTP server. type: str + name: + description: + - Name of PowerScale Cluster. + type: str + description: + description: + - Description of PowerScale Cluster. + type: str + logon_details: + description: + - Details related to login to the Powerscale Cluster. + type: dict + suboptions: + message_title: + description: + - Message to be shown on the login screen. + type: str + description: + description: + - Message description to be shown on the login screen. + type: str + company: + description: + - Name of the company. + type: str + location: + description: + - Location of the company. + type: str + primary_contact: + description: + - Contact details of primary system admin. + type: dict + suboptions: + name: + description: + - Name of primary system admin. + type: str + phone1: + description: + - Phone1 of primary system admin. + type: str + phone2: + description: + - Phone2 of primary system admin. + type: str + email: + description: + - Email of primary system admin. + type: str + secondary_contact: + description: + - Contact details of secondary system admin. + type: dict + suboptions: + name: + description: + - Name of secondary system admin. + type: str + phone1: + description: + - Phone1 of secondary system admin. + type: str + phone2: + description: + - Phone2 of secondary system admin. + type: str + email: + description: + - Email of secondary system admin. + type: str notes: -- The I(check_mode) is not supported. +- The I(check_mode) is supported. ''' EXAMPLES = r''' @@ -70,8 +145,6 @@ api_user: "{{api_user}}" api_password: "{{api_password}}" verify_ssl: "{{verify_ssl}}" - email_settings: "{{email_settings}}" - state: "{{state_present}}" - name: Update email settings dellemc.powerscale.settings: @@ -95,17 +168,6 @@ - "10.106.**.***" state: "{{state_present}}" -- name: Add NTP server - Idempotency - dellemc.powerscale.settings: - onefs_host: "{{onefs_host}}" - api_user: "{{api_user}}" - api_password: "{{api_password}}" - verify_ssl: "{{verify_ssl}}" - ntp_servers: - - "10.106.**.***" - - "10.106.**.***" - state: "{{state_present}}" - - name: Get NTP server dellemc.powerscale.settings: onefs_host: "{{onefs_host}}" @@ -126,24 +188,77 @@ - "10.106.**.***" state: "{{state_absent}}" -- name: Remove NTP server - Idempotency +- name: Update email settings and add NTP server dellemc.powerscale.settings: onefs_host: "{{onefs_host}}" api_user: "{{api_user}}" api_password: "{{api_password}}" verify_ssl: "{{verify_ssl}}" + state: "{{state_present}}" + mail_relay: "mailrelay.itp.dell.com" + mail_sender: "lab-a2@dell.com" + mail_subject: "lab-a2-alerts" ntp_servers: - "10.106.**.***" - "10.106.**.***" - state: "{{state_absent}}" -- name: Update email settings and add NTP server +- name: Update cluster owner details + dellemc.powerscale.settings: + onefs_host: "{{onefs_host}}" + api_user: "{{api_user}}" + api_password: "{{api_password}}" + verify_ssl: "{{verify_ssl}}" + state: "{{state_present}}" + company: "Test company" + location: "Test location" + primary_contact: + name: "primary_name11" + phone1: "primary_phone11" + phone2: "primary_phone21" + email: "primary_email1@email.com" + secondary_contact: + name: "secondary_name11" + phone1: "secondary_phone11" + phone2: "secondary_phone21" + email: "secondary_email1@email.com" + +- name: Update cluster identity details dellemc.powerscale.settings: onefs_host: "{{onefs_host}}" api_user: "{{api_user}}" api_password: "{{api_password}}" verify_ssl: "{{verify_ssl}}" state: "{{state_present}}" + name: "PIE-IsilonS-24241-Cluster" + description: "This is new description for the cluster" + logon_details: + message_title: "This is the new title" + description: "This is new description" + +- name: Update all settings + dellemc.powerscale.settings: + onefs_host: "{{onefs_host}}" + api_user: "{{api_user}}" + api_password: "{{api_password}}" + verify_ssl: "{{verify_ssl}}" + state: "{{state_present}}" + name: "PIE-IsilonS-24241-Cluster" + description: "This is new description for the cluster" + logon_details: + message_title: "This is the new title" + description: "This is new description" + company: "Test company" + location: "Test location" + primary_contact: + name: "primary_name11" + phone1: "primary_phone11" + phone2: "primary_phone21" + email: "primary_email1@email.com" + secondary_contact: + name: "secondary_name11" + phone1: "secondary_phone11" + phone2: "secondary_phone21" + email: "secondary_email1@email.com" mail_relay: "mailrelay.itp.dell.com" mail_sender: "lab-a2@dell.com" mail_subject: "lab-a2-alerts" @@ -216,7 +331,7 @@ } } -ntp_server: +ntp_servers: description: List of NTP servers. type: dict returned: Always @@ -244,51 +359,152 @@ } ] } +cluster_identity: + description: Details related to cluster identity. + type: dict + returned: Always + contains: + description: + description: Description of PowerScale cluster. + type: str + logon: + description: Details of logon message shown on Powerscale login screen. + type: dict + contains: + motd: + description: Details of logon message. + type: str + motd_header: + description: Details of logon message title. + type: str + mttdl_level_msg: + description: mttdl_level_msg. + type: str + name: + description: Name of PowerScale cluster. + type: str + sample: + { + "cluster_identity": + { + "description": "asdadasdasdasdadadadds", + "logon": + { + "motd": "This is new description", + "motd_header": "This is the new title" + }, + "mttdl_level_msg": "none", + "name": "PIE-IsilonS-24241-Clusterwrerwerwrewr" + } + } +cluster_owner: + description: Details related to cluster identity. + type: dict + returned: Always + contains: + company: + description: Name of the company. + type: str + location: + description: Location of the company. + type: str + primary_email: + description: Email of primary system admin. + type: str + primary_name: + description: Name of primary system admin. + type: str + primary_phone1: + description: Phone1 of primary system admin. + type: str + primary_phone2: + description: Phone2 of primary system admin. + type: str + secondary_email: + description: Email of secondary system admin. + type: str + secondary_name: + description: Name of secondary system admin. + type: str + secondary_phone1: + description: Phone1 of secondary system admin. + type: str + secondary_phone2: + description: Phone2 of secondary system admin. + type: str + sample: + { + "cluster_owner": + { + "company": "Test company", + "location": "Test location", + "primary_email": "primary_email@email.com", + "primary_name": "primary_name", + "primary_phone1": "primary_phone1", + "primary_phone2": "primary_phone2", + "secondary_email": "secondary_email@email.com", + "secondary_name": "secondary_name", + "secondary_phone1": "secondary_phone1", + "secondary_phone2": "secondary_phone2" + } + } ''' from ansible.module_utils.basic import AnsibleModule +from ansible_collections.dellemc.powerscale.plugins.module_utils.storage.dell.shared_library.powerscale_base \ + import PowerScaleBase +from ansible_collections.dellemc.powerscale.plugins.module_utils.storage.dell.shared_library.protocol \ + import Protocol +from ansible_collections.dellemc.powerscale.plugins.module_utils.storage.dell.shared_library.cluster \ + import Cluster from ansible_collections.dellemc.powerscale.plugins.module_utils.storage.dell \ import utils LOG = utils.get_logger('settings') -class Settings(object): - """Class with general setting operations""" +class Settings(PowerScaleBase): - def __init__(self): - """ Define all parameters required by this module""" + '''Class to manage Settings of Powerscale Cluster''' - self.module_params = utils.get_powerscale_management_host_parameters() - self.module_params.update(get_setting_parameters()) + def __init__(self): + ''' Define all parameters required by this module''' - # initialize the ansible module - self.module = AnsibleModule(argument_spec=self.module_params) + ansible_module_params = { + 'argument_spec': self.get_setting_parameters(), + 'supports_check_mode': True + } - PREREQS_VALIDATE = utils.validate_module_pre_reqs(self.module.params) - if PREREQS_VALIDATE \ - and not PREREQS_VALIDATE["all_packages_found"]: - self.module.fail_json( - msg=PREREQS_VALIDATE["error_message"]) + super().__init__(AnsibleModule, ansible_module_params) - self.api_client = utils.get_powerscale_connection(self.module.params) - self.isi_sdk = utils.get_powerscale_sdk() - LOG.info('Got python SDK instance for provisioning on PowerScale') - self.cluster_api = self.isi_sdk.ClusterApi(self.api_client) - self.protocol_api = self.isi_sdk.ProtocolsApi(self.api_client) + self.result = { + "changed": False, + "email_settings": {}, + "ntp_servers": {}, + "cluster_owner": {}, + "cluster_identity": {} + } def get_email_settings(self): """ Get cluster email settings :return: Cluster email settings. """ - try: - email_details = self.cluster_api.get_cluster_email() - return email_details.to_dict() - except Exception as e: - error_message = 'Failed to get the details of email settings %s,' % (str(e)) - LOG.error(error_message) - self.module.fail_json(msg=error_message) + return Cluster(self.cluster_api, self.module).get_email_settings() + + def get_cluster_identity_details(self): + """ + Get cluster identity details + :return: Cluster identity details. + """ + return Cluster(self.cluster_api, self.module).get_cluster_identity_details() + + def get_cluster_owner_details(self): + """ + Get cluster owner details + :return: Cluster owner details. + """ + return Cluster(self.cluster_api, self.module).get_cluster_owner_details() def update_email_settings(self, email_params): """ @@ -297,10 +513,11 @@ def update_email_settings(self, email_params): :return: True when email settings are updated successfully """ try: - self.cluster_api.update_cluster_email(email_params) - return True + if not self.module.check_mode: + self.cluster_api.update_cluster_email(email_params) + return self.get_email_settings() except Exception as e: - error_message = 'Modifying email setting failed with error %s,' % (str(e)) + error_message = f"Modifying email setting failed with error: {utils.determine_error(e)}" LOG.error(error_message) self.module.fail_json(msg=error_message) @@ -311,9 +528,10 @@ def add_ntp_server(self, server): :return: None """ try: - self.protocol_api.create_ntp_server(server) + if not self.module.check_mode: + self.protocol_api.create_ntp_server(server) except Exception as e: - error_message = 'Failed to add NTP server %s %s' % (server, str(e)) + error_message = f"Failed to add NTP server: {utils.determine_error(e)}" LOG.error(error_message) self.module.fail_json(msg=error_message) @@ -324,10 +542,11 @@ def get_ntp_server(self, server_id): :return: Details of the NTP Server """ try: - server_details = self.protocol_api.get_ntp_server(server_id) - return server_details.to_dict() + if server_id: + server_details = self.protocol_api.get_ntp_server(server_id) + return server_details.to_dict() except Exception as e: - error_message = 'Failed to get NTP server %s %s' % (server_id, str(e)) + error_message = f"Failed to get NTP server: {utils.determine_error(e)}" LOG.error(error_message) self.module.fail_json(msg=error_message) @@ -336,13 +555,7 @@ def get_ntp_servers(self): Get NTP servers :return: List of all the NTP Servers """ - try: - server_details = self.protocol_api.list_ntp_servers() - return server_details.to_dict() - except Exception as e: - error_message = 'Failed to get NTP servers %s' % (str(e)) - LOG.error(error_message) - self.module.fail_json(msg=error_message) + return Protocol(self.protocol_api, self.module).get_ntp_server_list() def delete_ntp_server(self, server_id): """ @@ -351,28 +564,92 @@ def delete_ntp_server(self, server_id): :return: True when delete operation is successful """ try: - self.protocol_api.delete_ntp_server(server_id) + if not self.module.check_mode: + self.protocol_api.delete_ntp_server(server_id) return True except Exception as e: - error_message = 'Deleting NTP server %s failed with error %s' % (server_id, str(e)) + error_message = f"Deleting NTP server failed with error {utils.determine_error(e)}" LOG.error(error_message) self.module.fail_json(msg=error_message) - def construct_ntp_server_body(self, ntp_server): + def get_setting_parameters(self): + return dict( + state=dict(type='str', choices=['present', 'absent'], default='present'), + mail_relay=dict(type='str'), + mail_sender=dict(type='str'), + mail_subject=dict(type='str'), + email_settings=dict(type='bool'), + ntp_servers=dict(type='list', elements='str'), + ntp_server_id=dict(type='str'), + name=dict(type='str'), + description=dict(type='str'), + logon_details=dict(type='dict', options=dict( + message_title=dict(type='str'), + description=dict(type='str'))), + company=dict(type='str'), + location=dict(type='str'), + primary_contact=dict(type='dict', options=dict( + name=dict(type='str'), + phone1=dict(type='str'), + phone2=dict(type='str'), + email=dict(type='str'))), + secondary_contact=dict(type='dict', options=dict( + name=dict(type='str'), + phone1=dict(type='str'), + phone2=dict(type='str'), + email=dict(type='str'))) + ) + + def getting_configured_ntp_server(self): + ntp_server_list_system = [] + ntp_servers = self.get_ntp_servers() + if ntp_servers: + for index in range(len(ntp_servers['servers'])): + ntp_server_list_system.append(ntp_servers['servers'][index]['name']) + return ntp_server_list_system + + def construct_ntp_server_payload(self, ntp_server_details, ntp_server): """ Constructs NTP server body :param ntp_server: ID or Name of NTP server which is to be added. :return: Dictionary which has the details of the NTP server. """ + ntp_servers_list = self.getting_configured_ntp_server() + ntp_servers_final = (list(set(ntp_server) - set(ntp_servers_list))) + return ntp_servers_final + + def get_ntp_server_dict(self, ntp_server): ntp_server_dict = dict(name=ntp_server) return ntp_server_dict - def validate_input(self, ntp_servers): + def validate_input(self, settings_params): """ Validate input. - :params ntp_servers: ID or Name of NTP server + :params settings_params: All the input params """ - for server in ntp_servers: + if settings_params["ntp_servers"]: + self.validate_ntp_server(settings_params) + + if settings_params['name'] and len(settings_params['name']) > 40: + self.module.fail_json(msg="The maximum length for name is 40") + + if settings_params['description'] and len(settings_params['description']) > 255: + self.module.fail_json(msg="The maximum length for description is 255") + + keys = ["primary_contact", "secondary_contact"] + for item in keys: + if settings_params[item] and settings_params[item]["email"] and utils.is_email_address_valid(settings_params[item]["email"]): + error_message = item + ' email is not in the correct format' + LOG.error(error_message) + self.module.fail_json(msg=error_message) + + if settings_params["mail_sender"] and utils.is_email_address_valid(settings_params["mail_sender"]): + error_message = 'Email address of mail_sender is not in the correct format' + LOG.error(error_message) + self.module.fail_json(msg=error_message) + + def validate_ntp_server(self, settings_params): + for server in settings_params["ntp_servers"]: if utils.is_input_empty(server): error_message = 'Please provide valid value for NTP server' LOG.error(error_message) @@ -381,94 +658,190 @@ def validate_input(self, ntp_servers): def do_update(self, source, target): return source and source != target - def perform_module_operation(self): + def form_modify_email_dict(self, settings_params, email_settings): + email_setting_keys = ['mail_relay', 'mail_sender', 'mail_subject'] + email_params = {} + for setting in email_setting_keys: + if setting in email_setting_keys and \ + self.do_update(settings_params[setting], email_settings['settings'][setting]): + email_params[setting] = settings_params[setting] + return email_params + + def form_modify_cluster_identity_dict(self, settings_params, cluster_identity_details): + cluster_identity_keys = ['name', 'description'] + cluster_identity = {} + if 'logon' not in cluster_identity: + cluster_identity['logon'] = dict() + + for items in cluster_identity_keys: + if items in cluster_identity_keys and \ + self.do_update(settings_params[items], cluster_identity_details[items]): + cluster_identity[items] = settings_params[items] + + test_dict = {'description': 'motd', 'message_title': 'motd_header'} + + if settings_params['logon_details']: + for key in test_dict.keys(): + if key in settings_params['logon_details'] and settings_params['logon_details'][key] != cluster_identity_details['logon'][test_dict[key]]: + cluster_identity['logon'][test_dict[key]] = settings_params['logon_details'][key] + + if cluster_identity['logon'] == dict(): + del cluster_identity['logon'] + return cluster_identity + + def modify_cluster_identity(self, modify_dict): """ - Perform different actions based on parameters chosen in playbook + Update cluster identity + :param modify_dict: cluster identity dict + :return: True when delete operation is successful """ - result = dict( - changed=False, - email_settings='', - ntp_server='', - ) - state = self.module.params['state'] - mail_relay = self.module.params['mail_relay'] - mail_sender = self.module.params['mail_sender'] - mail_subject = self.module.params['mail_subject'] - ntp_servers = self.module.params['ntp_servers'] - ntp_server_id = self.module.params['ntp_server_id'] - email_settings = self.module.params['email_settings'] + try: + if not self.module.check_mode: + self.cluster_api.update_cluster_identity(modify_dict) + return self.get_cluster_identity_details() + except Exception as e: + error_message = f"Updating cluster identity failed with error {utils.determine_error(e)}" + LOG.error(error_message) + self.module.fail_json(msg=error_message) - email_setting_keys = ['mail_relay', 'mail_sender', 'mail_subject'] - ntp_server_list_system = [] - ntp_servers_final = None - ntp_server_details = self.get_ntp_servers() - existing_email_settings = self.get_email_settings() + def form_modify_cluster_owner_dict(self, settings_params, cluster_owner_details): + cluster_owner_keys = ['company', 'location'] + cluster_owner = {} + contact = ['primary_contact', 'secondary_contact'] - email_params = {} - if existing_email_settings: - for setting in email_setting_keys: - if setting in self.module.params.keys() and \ - self.do_update(self.module.params[setting], existing_email_settings['settings'][setting]): - email_params[setting] = self.module.params[setting] - - if ntp_server_details: - for index in range(len(ntp_server_details['servers'])): - ntp_server_list_system.append(ntp_server_details['servers'][index]['name']) - - if state == "absent" and (email_settings or mail_relay or mail_sender or mail_subject): - error_message = 'Deletion of email settings is not valid operation' + contact_dict_keys = ['name', 'phone1', 'phone2', 'email'] + + for item in contact: + if item not in cluster_owner: + cluster_owner[item] = dict() + + for items in cluster_owner_keys: + if items in cluster_owner_keys and \ + self.do_update(settings_params[items], cluster_owner_details[items]): + cluster_owner[items] = settings_params[items] + + for key in contact_dict_keys: + self.get_modified_value(settings_params, key, cluster_owner, cluster_owner_details) + + for item in contact: + if cluster_owner[item] == dict(): + del cluster_owner[item] + + return cluster_owner + + def get_modified_value(self, settings_params, key, cluster_owner, cluster_owner_details): + if settings_params['primary_contact'] and settings_params['primary_contact'][key] \ + and settings_params['primary_contact'][key] != cluster_owner_details['primary_' + key]: + cluster_owner['primary_' + key] = settings_params['primary_contact'][key] + + if settings_params['secondary_contact'] and settings_params['secondary_contact'][key] \ + and settings_params['secondary_contact'][key] != cluster_owner_details['secondary_' + key]: + cluster_owner['secondary_' + key] = settings_params['secondary_contact'][key] + + def modify_cluster_owner(self, modify_dict): + """ + Update cluster owner + :param modify_dict: cluster owner dict + :return: True when delete operation is successful + """ + try: + if not self.module.check_mode: + self.cluster_api.update_cluster_owner(modify_dict) + return self.get_cluster_owner_details() + except Exception as e: + error_message = f"Updating cluster owner failed with error {utils.determine_error(e)}" LOG.error(error_message) self.module.fail_json(msg=error_message) - if email_params and state == 'present': - result['changed'] = self.update_email_settings(email_params) - result['email_settings'] = self.get_email_settings() - - if ntp_servers and state == 'present': - self.validate_input(ntp_servers) - ntp_servers_final = (list(set(ntp_servers) - set(ntp_server_list_system))) - for ntp_server in ntp_servers_final: - server_details_to_update = self.construct_ntp_server_body(ntp_server) - self.add_ntp_server(server_details_to_update) - result['changed'] = True - result['ntp_server'] = self.get_ntp_servers() - - if (ntp_server_details['total'] != 0) and state == 'absent' and ntp_servers: - ntp_servers_to_be_removed = (list(set(ntp_servers).intersection(ntp_server_list_system))) - if ntp_servers_to_be_removed: - for ntp_server in ntp_servers_to_be_removed: - self.delete_ntp_server(ntp_server) - result['changed'] = True - result['ntp_server'] = self.get_ntp_servers() - - if email_settings and state == 'present': - result['email_settings'] = existing_email_settings - - if ntp_server_id and state == 'present': - result['ntp_server'] = self.get_ntp_server(ntp_server_id) - - self.module.exit_json(**result) - - -def get_setting_parameters(): - """This method provide parameter required for the managing genaral - settings on PowerScale""" - return dict( - state=dict(required=True, type='str', choices=['absent', 'present']), - mail_relay=dict(type='str'), - mail_sender=dict(type='str'), - mail_subject=dict(type='str'), - ntp_servers=dict(type='list', elements='str'), - ntp_server_id=dict(type='str'), - email_settings=dict(type='bool') - ) + +class SettingsExitHandler(): + def handle(self, settings_obj, settings_details): + settings_obj.result["cluster_identity"] = settings_details['cluster_identity_details'] + settings_obj.result["cluster_owner"] = settings_details['cluster_owner_details'] + settings_obj.result["email_settings"] = settings_details['email_settings'] + settings_obj.result["ntp_servers"] = settings_details['ntp_server_details'] + settings_obj.module.exit_json(**settings_obj.result) + + +class SettingsDeleteHandler(): + def handle(self, settings_obj, settings_params, settings_details): + if settings_params['state'] == 'absent' and settings_details['ntp_server_details']: + existing_ntp_servers = settings_obj.getting_configured_ntp_server() + ntp_servers_to_be_removed = (list(set(settings_params['ntp_servers']).intersection(existing_ntp_servers))) + for ntp_server in ntp_servers_to_be_removed: + settings_obj.delete_ntp_server(ntp_server) + settings_obj.result['changed'] = True + settings_details['ntp_server_details'] = settings_obj.get_ntp_servers() + + SettingsExitHandler().handle(settings_obj, settings_details) + + +class SettingsModifyHandler(): + def handle_email_settings(self, settings_obj, settings_params, settings_details): + if settings_params['state'] == 'present' and settings_details['email_settings']: + modify_email_dict = settings_obj.form_modify_email_dict(settings_params, settings_details['email_settings']) + if modify_email_dict: + settings_params['email_settings'] = settings_obj.update_email_settings(modify_email_dict) + settings_obj.result['changed'] = True + + def handle_cluster_identity(self, settings_obj, settings_params, settings_details): + if settings_params['state'] == 'present' and settings_details['cluster_identity_details']: + modify_cluster_identity_dict = settings_obj.form_modify_cluster_identity_dict(settings_params, settings_details['cluster_identity_details']) + if modify_cluster_identity_dict: + settings_details['cluster_identity_details'] = settings_obj.modify_cluster_identity(modify_cluster_identity_dict) + settings_obj.result['changed'] = True + + def handle_cluster_owner(self, settings_obj, settings_params, settings_details): + if settings_params['state'] == 'present' and settings_details['cluster_owner_details']: + modify_cluster_owner_dict = settings_obj.form_modify_cluster_owner_dict(settings_params, settings_details['cluster_owner_details']) + if modify_cluster_owner_dict: + settings_details['cluster_owner_details'] = settings_obj.modify_cluster_owner(modify_cluster_owner_dict) + settings_obj.result['changed'] = True + + def handle(self, settings_obj, settings_params, settings_details): + self.handle_email_settings(settings_obj, settings_params, settings_details) + self.handle_cluster_identity(settings_obj, settings_params, settings_details) + self.handle_cluster_owner(settings_obj, settings_params, settings_details) + + SettingsDeleteHandler().handle(settings_obj, settings_params, settings_details) + + +class SettingsCreateHandler(): + def handle(self, settings_obj, settings_params, settings_details): + if settings_params['state'] == 'present' and settings_params['ntp_servers']: + add_ntp_server_payload = settings_obj.construct_ntp_server_payload(settings_details['ntp_server_details'], settings_params['ntp_servers']) + for item in add_ntp_server_payload: + list_to_be = settings_obj.get_ntp_server_dict(item) + settings_obj.add_ntp_server(list_to_be) + settings_obj.result['changed'] = True + settings_details['ntp_server_details'] = settings_obj.get_ntp_servers() + + SettingsModifyHandler().handle(settings_obj, settings_params, settings_details) + + +class SettingsHandler(): + def set_initial_data(self, settings_obj): + data_dict = {'email_settings': None, + 'ntp_server_details': None, + 'cluster_identity_details': None, + 'cluster_owner_details': None} + data_dict['email_settings'] = settings_obj.get_email_settings() + data_dict['ntp_server_details'] = settings_obj.get_ntp_servers() + data_dict['cluster_identity_details'] = settings_obj.get_cluster_identity_details() + data_dict['cluster_owner_details'] = settings_obj.get_cluster_owner_details() + return data_dict + + def handle(self, settings_obj, settings_params): + settings_obj.validate_input(settings_params) + settings_details = self.set_initial_data(settings_obj) + SettingsCreateHandler().handle(settings_obj, settings_params, settings_details) def main(): - """ Create PowerScale Setting object and perform actions on it - based on user input from playbook""" + """ Create PowerScale Settings object and perform action on it + based on user input from playbook.""" obj = Settings() - obj.perform_module_operation() + SettingsHandler().handle(obj, obj.module.params) if __name__ == '__main__': diff --git a/plugins/modules/smb_global_settings.py b/plugins/modules/smb_global_settings.py new file mode 100644 index 00000000..2bbfbf9b --- /dev/null +++ b/plugins/modules/smb_global_settings.py @@ -0,0 +1,371 @@ +#!/usr/bin/python +# Copyright: (c) 2023, Dell Technologies + +# Apache License version 2.0 (see MODULE-LICENSE or http://www.apache.org/licenses/LICENSE-2.0.txt) + +"""Ansible module for managing SMB global settings on PowerScale""" + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +DOCUMENTATION = r''' +--- +module: smb_global_settings +version_added: '2.4.0' +short_description: Manage SMB global settings on a PowerScale Storage System +description: +- Managing SMB global settings on a PowerScale system includes retrieving details of + SMB global settings and modifying SMB global settings. + +extends_documentation_fragment: + - dellemc.powerscale.powerscale + +author: + - Sachin Apagundi (@sachin-apa) +options: + access_based_share_enum: + description: Only enumerate files and folders the requesting user has access to. + type: bool + dot_snap_accessible_child: + description: Allow access to .snapshot directories in share subdirectories. + type: bool + dot_snap_accessible_root: + description: Allow access to the .snapshot directory in the root of the share. + type: bool + dot_snap_visible_child: + description: Show .snapshot directories in share subdirectories. + type: bool + dot_snap_visible_root: + description: Show the .snapshot directory in the root of a share. + type: bool + enable_security_signatures: + description: Indicates whether the server supports signed SMB packets. + type: bool + guest_user: + description: Specifies the fully-qualified user to use for guest access. + type: str + ignore_eas: + description: Specify whether to ignore EAs on files. + type: bool + onefs_cpu_multiplier: + description: Specify the number of OneFS driver worker threads per CPU. + type: int + onefs_num_workers: + description: Set the maximum number of OneFS driver worker threads. + type: int + reject_unencrypted_access: + description: If SMB3 encryption is enabled, reject unencrypted access from clients. + type: bool + require_security_signatures: + description: Indicates whether the server requires signed SMB packets. + type: bool + server_side_copy: + description: Enable Server Side Copy. + type: bool + server_string: + description: Provides a description of the server. + type: str + service: + description: Specify whether service is enabled. + type: bool + support_multichannel: + description: Support multichannel. + type: bool + support_netbios: + description: Support NetBIOS. + type: bool + support_smb2: + description: The support SMB2 attribute. + type: bool + support_smb3_encryption: + description: Support the SMB3 encryption on the server. + type: bool +notes: +- The I(check_mode) is supported. +''' + +EXAMPLES = r''' +- name: Get SMB global settings + dellemc.powerscale.smb_global_settings: + onefs_host: "{{ onefs_host }}" + port_no: "{{ port_no }}" + api_user: "{{ api_user }}" + api_password: "{{ api_password }}" + verify_ssl: "{{ verify_ssl }}" + +- name: Update SMB global settings + dellemc.powerscale.smb_global_settings: + onefs_host: "{{ onefs_host }}" + port_no: "{{ port_no }}" + api_user: "{{ api_user }}" + api_password: "{{ api_password }}" + verify_ssl: "{{ verify_ssl }}" + access_based_share_enum: true + dot_snap_accessible_child: true + dot_snap_accessible_root: false + dot_snap_visible_child: false + dot_snap_visible_root: true + enable_security_signatures: true + guest_user: user + ignore_eas: false + onefs_cpu_multiplier: 2 + onefs_num_workers: 4 + reject_unencrypted_access: true + require_security_signatures: true + server_side_copy: true + server_string: 'PowerScale Server' + service: true + support_multichannel: true + support_netbios: true + support_smb2: true + support_smb3_encryption: true +''' + +RETURN = r''' +changed: + description: A boolean indicating if the task had to make changes. + returned: always + type: bool + sample: "false" +smb_global_settings_details: + description: The updated SMB global settings details. + type: dict + returned: always + contains: + access_based_share_enum: + description: Only enumerate files and folders the requesting user has access to. + type: bool + audit_fileshare: + description: Specify level of file share audit events to log. + type: str + audit_logon: + description: Specify the level of logon audit events to log. + type: str + dot_snap_accessible_child: + description: Allow access to .snapshot directories in share subdirectories. + type: bool + dot_snap_accessible_root: + description: Allow access to the .snapshot directory in the root of the share. + type: bool + dot_snap_visible_child: + description: Show .snapshot directories in share subdirectories. + type: bool + dot_snap_visible_root: + description: Show the .snapshot directory in the root of a share. + type: bool + enable_security_signatures: + description: Indicates whether the server supports signed SMB packets. + type: bool + guest_user: + description: Specifies the fully-qualified user to use for guest access. + type: str + ignore_eas: + description: Specify whether to ignore EAs on files. + type: bool + onefs_cpu_multiplier: + description: Specify the number of OneFS driver worker threads per CPU. + type: int + onefs_num_workers: + description: Set the maximum number of OneFS driver worker threads. + type: int + reject_unencrypted_access: + description: If SMB3 encryption is enabled, reject unencrypted access from clients. + type: bool + require_security_signatures: + description: Indicates whether the server requires signed SMB packets. + type: bool + server_side_copy: + description: Enable Server Side Copy. + type: bool + server_string: + description: Provides a description of the server. + type: str + service: + description: Specify whether service is enabled. + type: bool + srv_cpu_multiplier: + description: Specify the number of SRV service worker threads per CPU. + type: int + srv_num_workers: + description: Set the maximum number of SRV service worker threads. + type: int + support_multichannel: + description: Support multichannel. + type: bool + support_netbios: + description: Support NetBIOS. + type: bool + support_smb2: + description: The support SMB2 attribute. + type: bool + support_smb3_encryption: + description: Support the SMB3 encryption on the server. + type: bool + sample: { + "access_based_share_enum": false, + "audit_fileshare": null, + "audit_logon": null, + "dot_snap_accessible_child": true, + "dot_snap_accessible_root": true, + "dot_snap_visible_child": false, + "dot_snap_visible_root": true, + "enable_security_signatures": false, + "guest_user": "nobody", + "ignore_eas": false, + "onefs_cpu_multiplier": 4, + "onefs_num_workers": 0, + "reject_unencrypted_access": false, + "require_security_signatures": false, + "server_side_copy": false, + "server_string": "PowerScale Server", + "service": true, + "srv_cpu_multiplier": null, + "srv_num_workers": null, + "support_multichannel": true, + "support_netbios": false, + "support_smb2": true, + "support_smb3_encryption": true + } +''' + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.dellemc.powerscale.plugins.module_utils.storage.dell.shared_library.powerscale_base \ + import PowerScaleBase +from ansible_collections.dellemc.powerscale.plugins.module_utils.storage.dell \ + import utils +from ansible_collections.dellemc.powerscale.plugins.module_utils.storage.dell.shared_library.protocol \ + import Protocol + +LOG = utils.get_logger('smb_global_settings') + + +class SMBGlobalSettings(PowerScaleBase): + """Class with SMB global settings operations""" + + def __init__(self): + """ Define all parameters required by the SMB global settings module""" + + ansible_module_params = { + 'argument_spec': self.get_smb_global_settings_parameters(), + 'supports_check_mode': True, + } + super().__init__(AnsibleModule, ansible_module_params) + + # Result is a dictionary that contains changed status and SMB global + # settings details + self.result.update({ + "smb_global_settings_details": {} + }) + + def get_smb_global_settings_details(self): + """ + Get details of SMB global settings + """ + return Protocol(self.protocol_api, self.module).get_smb_global_settings() + + def modify_smb_global_settings(self, modify_dict): + """ + Modify the SMB global settings based on modify dict + :param modify_dict: dict containing parameters to be modfied + """ + try: + msg = "Modify SMB global settings with parameters" + LOG.info(msg) + if not self.module.check_mode: + self.protocol_api.update_smb_settings_global( + smb_settings_global=modify_dict) + LOG.info("Successfully modified the SMB global settings.") + return True + + except Exception as e: + error_msg = f"Modify SMB global settings failed with " \ + f"error: {utils.determine_error(e)}" + LOG.error(error_msg) + self.module.fail_json(msg=error_msg) + + def is_smb_global_modify_required(self, settings_params, settings_details): + """ + Check whether modification is required in SMB global settings + """ + modify_dict = {} + for key in self.get_smb_keys(): + if settings_params.get(key) is not None and \ + settings_details[key] != settings_params[key]: + modify_dict[key] = settings_params[key] + return modify_dict + + def get_smb_keys(self): + """ + Returns a list of SMB keys. + """ + return ["access_based_share_enum", "dot_snap_accessible_child", + "dot_snap_accessible_root", "dot_snap_visible_child", + "dot_snap_visible_root", "enable_security_signatures" + "guest_user", "ignore_eas", "onefs_cpu_multiplier", + "onefs_num_workers", "reject_unencrypted_access", + "require_security_signatures", "server_side_copy", + "server_string", "service", "support_multichannel", + "support_netbios", "support_smb2", "support_smb3_encryption"] + + def get_smb_global_settings_parameters(self): + return dict( + access_based_share_enum=dict(type='bool'), + dot_snap_accessible_child=dict(type='bool'), + dot_snap_accessible_root=dict(type='bool'), + dot_snap_visible_child=dict(type='bool'), + dot_snap_visible_root=dict(type='bool'), + enable_security_signatures=dict(type='bool'), + guest_user=dict(type='str'), + ignore_eas=dict(type='bool'), + onefs_cpu_multiplier=dict(type='int'), + onefs_num_workers=dict(type='int'), + reject_unencrypted_access=dict(type='bool'), + require_security_signatures=dict(type='bool'), + server_side_copy=dict(type='bool'), + server_string=dict(type='str'), + service=dict(type='bool'), + support_multichannel=dict(type='bool'), + support_netbios=dict(type='bool'), + support_smb2=dict(type='bool'), + support_smb3_encryption=dict(type='bool') + ) + + +class SMBGlobalSettingsExitHandler: + def handle(self, smb_global_obj, smb_global_details): + smb_global_obj.result["smb_global_settings_details"] = smb_global_details + smb_global_obj.module.exit_json(**smb_global_obj.result) + + +class SMBGlobalSettingsModifyHandler: + def handle(self, smb_global_obj, smb_global_params, smb_global_details): + modify_params = smb_global_obj.is_smb_global_modify_required(smb_global_params, + smb_global_details) + if modify_params: + changed = smb_global_obj.modify_smb_global_settings( + modify_dict=modify_params) + smb_global_details = smb_global_obj.get_smb_global_settings_details() + smb_global_obj.result["changed"] = changed + smb_global_obj.result["smb_global_settings_details"] = smb_global_details + + SMBGlobalSettingsExitHandler().handle(smb_global_obj, smb_global_details) + + +class SMBGlobalSettingsHandler: + def handle(self, smb_global_obj, smb_global_params): + smb_global_details = smb_global_obj.get_smb_global_settings_details() + SMBGlobalSettingsModifyHandler().handle( + smb_global_obj=smb_global_obj, smb_global_params=smb_global_params, + smb_global_details=smb_global_details) + + +def main(): + """ perform action on PowerScale SMB Global settings and perform action on it + based on user input from playbook.""" + obj = SMBGlobalSettings() + SMBGlobalSettingsHandler().handle(obj, obj.module.params) + + +if __name__ == '__main__': + main() diff --git a/plugins/modules/snmp_settings.py b/plugins/modules/snmp_settings.py new file mode 100644 index 00000000..17980b9e --- /dev/null +++ b/plugins/modules/snmp_settings.py @@ -0,0 +1,367 @@ +#!/usr/bin/python +# Copyright: (c) 2023, Dell Technologies + +# Apache License version 2.0 (see MODULE-LICENSE or http://www.apache.org/licenses/LICENSE-2.0.txt) + +"""Ansible module for managing SNMP settings on PowerScale""" + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +DOCUMENTATION = r''' +--- +module: snmp_settings +version_added: '2.4.0' + +short_description: Manage SNMP settings on PowerScale storage systems + +description: +- Manage SNMP settings on PowerScale storage systems includes + retrieving, and updating SNMP settings. + +extends_documentation_fragment: + - dellemc.powerscale.powerscale + +author: +- Bhavneet Sharma(@Bhavneet-Sharma) + +options: + read_only_community: + description: + - SNMP read-only community name. + - The system default value of the read-only community name is + C(I$ilonpublic). + - Update the read-only community name while enabling SNMP v2c. + type: str + service: + description: + - Whether the SNMP service is enabled. + type: bool + snmp_v2c_access: + description: + - Whether the SNMP v2c is enabled. + - OneFS support SNMP v2c and later. + type: bool + snmp_v3: + description: + - Specify the access, privacy, and security level for SNMP v3. + type: dict + suboptions: + access: + description: + - Whether SNMP v3 is enabled. + type: bool + auth_protocol: + description: + - SNMP v3 authentication protocol. + type: str + choices: ['SHA', 'MD5'] + privacy_password: + description: + - SNMP v3 privacy password. + type: str + password: + description: + - SNMP v3 authentication password. + type: str + privacy_protocol: + description: + - SNMP v3 privacy protocol. + type: str + choices: ['AES', 'DES'] + security_level: + description: + - SNMP v3 security level. + type: str + choices: ['noAuthNoPriv', 'authNoPriv', 'authPriv'] + read_only_user: + description: + - The read-only user for SNMP v3 requests. + - The system default value of read-only user is C(general). + type: str + system_contact: + description: + - SNMP system owner contact information. + - This must be a valid email address. + - The contact information is set for the reporting purpose. + type: str + system_location: + description: + - The cluster description for SNMP system. + - The cluster description is set for the reporting purpose. + type: str +notes: +- The I(check_mode) is supported. +- Users can configure SNMP version 3 alone or in combination with version 2c. +- Idempotency is not supported for SNMP v3's password and privacy password. +''' + +EXAMPLES = r''' +- name: Get SNMP settings + dellemc.powerscale.snmp_settings: + onefs_host: "{{ onefs_host }}" + api_user: "{{ api_user }}" + api_password: "{{ api_password }}" + verify_ssl: "{{ verify_ssl }}" + +- name: Update SNMP settings + dellemc.powerscale.snmp_settings: + onefs_host: "{{ onefs_host }}" + api_user: "{{ api_user }}" + api_password: "{{ api_password }}" + verify_ssl: "{{ verify_ssl }}" + read_only_community: "community-name" + snmp_v3: + access: true + auth_protocol: "SHA" + privacy_password: "password" + password: "auth_password" + privacy_protocol: "AES" + security_level: "noAuthNoPriv" + read_only_user: "user" + system_contact: "contact@domain.com" + system_location: "Enabled SNMP" +''' + +RETURN = r''' +changed: + description: A Boolean value indicating if task had to make changes. + returned: always + type: bool + sample: "true" +snmp_settings: + description: The details of SNMP settings. + returned: always + type: dict + contains: + read_only_community: + description: SNMP read-only community name. + type: str + service: + description: Whether the SNMP service is enabled. + type: bool + snmp_v1_v2c_access: + description: Whether the SNMP v2c access is enabled. + type: bool + snmp_v3_access: + description: Whether the SNMP v3 is enabled. + type: bool + snmp_v3_auth_protocol: + description: SNMP v3 authentication protocol. + type: str + snmp_v3_priv_protocol: + description: SNMP v3 privacy protocol. + type: str + smnmp_v3_read_only_user: + description: SNMP v3 read-only user. + type: str + snmp_v3_security_level: + description: SNMP v3 security level. + type: str + system_contact: + description: SNMP system owner contact information. + type: str + system_location: + description: The cluster description for SNMP system. + type: str + sample: { + "read_only_community": "community-name", + "service": true, + "snmp_v1_v2c_access": true, + "snmp_v3_access": true, + "snmp_v3_auth_protocol": "SHA", + "snmp_v3_priv_protocol": "AES", + "snmp_v3_read_only_user": "user", + "snmp_v3_security_level": "noAuthNoPriv", + "system_contact": "contact@domain.com", + "system_location": "Enabled SNMP" + } +''' + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.dellemc.powerscale.plugins.module_utils.storage.dell.shared_library.powerscale_base \ + import PowerScaleBase +from ansible_collections.dellemc.powerscale.plugins.module_utils.storage.dell.shared_library.protocol \ + import Protocol +from ansible_collections.dellemc.powerscale.plugins.module_utils.storage.dell \ + import utils + +LOG = utils.get_logger('snmp_settings') + + +class SNMPSettings(PowerScaleBase): + + """SNMP Settings operations.""" + + def __init__(self): + """Define all parameters for this module.""" + + ansible_module_params = { + 'argument_spec': self.get_snmp_settings_parameters(), + 'supports_check_mode': True + } + + super().__init__(AnsibleModule, ansible_module_params) + + self.result = { + "changed": False, + "snmp_settings_details": {} + } + + def get_snmp_settings(self): + """ + Get SNMP settings. + returns: Details of SNMP settings + rtype: dict + """ + return Protocol(self.protocol_api, self.module).get_snmp_settings() + + def get_snmp_settings_parameters(self): + """ + Returns a dictionary containing the SNMP settings parameters. + :rtype: dict + """ + return dict( + read_only_community=dict(type='str'), service=dict(type='bool'), + snmp_v2c_access=dict(type='bool'), snmp_v3=dict( + type='dict', options=dict( + access=dict(type='bool'), + auth_protocol=dict(type='str', choices=['SHA', 'MD5']), + privacy_password=dict(type='str', no_log=True), + password=dict(type='str', no_log=True), + privacy_protocol=dict(type='str', choices=['AES', 'DES']), + security_level=dict(type='str', choices=[ + 'noAuthNoPriv', 'authNoPriv', 'authPriv']), + read_only_user=dict(type='str') + ) + ), system_contact=dict(type='str'), + system_location=dict(type='str')) + + def is_snmp_v3_required(self, v3_params, snmp_details): + """ + Check if SNMP settings need to be updated. + :param v3_params: SNMP v3 parameters + :type v3_params: dict + :param snmp_details: SNMP details + :type snmp_details: dict + :rtype: dict + """ + modify_dict = {} + v3_pbs_keys = ['access', 'auth_protocol', 'read_only_user', + 'security_level'] + + for key in v3_pbs_keys: + if v3_params.get(key) is not None and v3_params.get(key) != snmp_details.get(f'snmp_v3_{key}'): + modify_dict[f'snmp_v3_{key}'] = v3_params.get(key) + if v3_params.get('password') is not None and v3_params.get('password'): + modify_dict['snmp_v3_password'] = v3_params.get('password') + if v3_params.get('privacy_password') is not None and v3_params.get('privacy_password'): + modify_dict['snmp_v3_priv_password'] = v3_params.get( + 'privacy_password') + if v3_params.get('privacy_protocol') is not None and v3_params.get('privacy_protocol') and \ + v3_params.get('privacy_protocol') != snmp_details.get('snmp_v3_priv_protocol'): + modify_dict['snmp_v3_priv_protocol'] = v3_params.get( + 'privacy_protocol') + + return modify_dict + + def is_snmp_modify_required(self, snmp_params, snmp_details): + """ + Check if SNMP settings need to be updated. + :param snmp_params: SNMP parameters + :type snmp_params: dict + :param snmp_details: SNMP details + :type snmp_details: dict + :rtype: dict + """ + modify_dict = {} + settings_keys = ['read_only_community', 'service', 'system_contact', + 'system_location'] + + for key in settings_keys: + if snmp_params.get(key) is not None and snmp_params.get(key) != snmp_details.get(key): + modify_dict[key] = snmp_params.get(key) + + if snmp_params.get('snmp_v2c_access') is not None and snmp_params.get('snmp_v2c_access') != snmp_details.get('snmp_v1_v2c_access'): + modify_dict['snmp_v1_v2c_access'] = snmp_params.get( + 'snmp_v2c_access') + + if snmp_params.get('snmp_v3') is not None: + v3_params = snmp_params.get('snmp_v3') + modify_dict.update(self.is_snmp_v3_required(v3_params, snmp_details)) + + return modify_dict + + def update_snmp_settings(self, modify_dict): + """Update SNMP settings. + :param kwargs: Params to update. + :type kwargs: dict + :rtype: bool + """ + try: + LOG.info("Modifying SNMP settings") + snmp_settings = self.isi_sdk.SnmpSettingsExtended(**modify_dict) + if not self.module.check_mode: + self.protocol_api.update_snmp_settings(snmp_settings) + LOG.info("Successfully modified the SNMP settings.") + return True + except Exception as e: + error_msg = f"Modifying SNMP settings failed with " \ + f"error: {utils.determine_error(e)}" + LOG.error(error_msg) + self.module.fail_json(msg=error_msg) + + def validate_snmp_params(self, module_params): + """Validate SNMP parameters. + :param module_params: SNMP parameters. + :type module_params: dict + :rtype: bool + """ + params = ['read_only_community', 'system_contact'] + for param in params: + if utils.is_param_empty_spaces(module_params.get(param)): + err_msg = f"Provide valid {param} parameter." + self.module.fail_json(msg=err_msg) + if module_params.get('system_location') and \ + utils.is_input_empty(module_params.get('system_location')): + err_msg = "Provide valid system_location parameter." + self.module.fail_json(msg=err_msg) + + +class SNMPSettingsExitHandler: + def handle(self, settings_obj): + settings_obj.result['snmp_settings_details'] = settings_obj.get_snmp_settings() + settings_obj.module.exit_json(**settings_obj.result) + + +class SNMPSettingsUpdateHandler: + def handle(self, settings_obj, module_params, snmp_details): + settings_obj.validate_snmp_params(module_params) + modify_dict = settings_obj.is_snmp_modify_required( + snmp_params=module_params, snmp_details=snmp_details) + + if modify_dict: + changed = settings_obj.update_snmp_settings(modify_dict) + settings_obj.result['changed'] = changed + SNMPSettingsExitHandler().handle(settings_obj) + + +class SNMPSettingsHandler: + def handle(self, settings_obj, module_params): + snmp_details = settings_obj.get_snmp_settings() + SNMPSettingsUpdateHandler().handle( + settings_obj=settings_obj, module_params=module_params, + snmp_details=snmp_details) + + +def main(): + """ Create the PowerScale SNMP settings object and perform + action on it based on the user input from playbook. + """ + obj = SNMPSettings() + SNMPSettingsHandler().handle(obj, obj.module.params) + + +if __name__ == '__main__': + main() diff --git a/tests/sanity/ignore-2.14.txt b/tests/sanity/ignore-2.14.txt index 602527fc..d3d41a9d 100644 --- a/tests/sanity/ignore-2.14.txt +++ b/tests/sanity/ignore-2.14.txt @@ -49,6 +49,14 @@ plugins/modules/groupnet.py import-2.7 plugins/modules/networkrule.py validate-modules:missing-gplv3-license plugins/modules/nfs_alias.py validate-modules:missing-gplv3-license plugins/modules/settings.py validate-modules:missing-gplv3-license +plugins/modules/settings.py import-2.7 +plugins/modules/settings.py import-3.5 +plugins/modules/settings.py compile-2.7 +plugins/modules/settings.py compile-3.5 +plugins/module_utils/storage/dell/shared_library/cluster.py compile-2.7 +plugins/module_utils/storage/dell/shared_library/cluster.py import-2.7 +plugins/module_utils/storage/dell/shared_library/cluster.py compile-3.5 +plugins/module_utils/storage/dell/shared_library/cluster.py import-3.5 plugins/modules/networksettings.py validate-modules:missing-gplv3-license plugins/modules/smartpoolsettings.py validate-modules:missing-gplv3-license plugins/modules/filepoolpolicy.py import-2.7 @@ -76,9 +84,6 @@ plugins/modules/user_mapping_rule.py compile-2.7 plugins/modules/user_mapping_rule.py compile-3.5 plugins/modules/user_mapping_rule.py validate-modules:missing-gplv3-license plugins/modules/networkpool.py import-2.7 -plugins/modules/networkpool.py import-3.5 -plugins/modules/networkpool.py compile-2.7 -plugins/modules/networkpool.py compile-3.5 plugins/modules/networkpool.py validate-modules:missing-gplv3-license plugins/modules/nfs_global_settings.py import-2.7 plugins/modules/nfs_global_settings.py import-3.5 @@ -111,4 +116,14 @@ plugins/modules/synciqcertificate.py validate-modules:missing-gplv3-license plugins/modules/synciqcertificate.py import-2.7 plugins/modules/synciqcertificate.py import-3.5 plugins/modules/synciqcertificate.py compile-2.7 -plugins/modules/synciqcertificate.py compile-3.5 \ No newline at end of file +plugins/modules/synciqcertificate.py compile-3.5 +plugins/modules/smb_global_settings.py import-2.7 +plugins/modules/smb_global_settings.py compile-2.7 +plugins/modules/smb_global_settings.py import-3.5 +plugins/modules/smb_global_settings.py compile-3.5 +plugins/modules/smb_global_settings.py validate-modules:missing-gplv3-license +plugins/modules/snmp_settings.py validate-modules:missing-gplv3-license +plugins/modules/snmp_settings.py import-2.7 +plugins/modules/snmp_settings.py compile-2.7 +plugins/modules/snmp_settings.py import-3.5 +plugins/modules/snmp_settings.py compile-3.5 \ No newline at end of file diff --git a/tests/sanity/ignore-2.15.txt b/tests/sanity/ignore-2.15.txt index 602527fc..d3d41a9d 100644 --- a/tests/sanity/ignore-2.15.txt +++ b/tests/sanity/ignore-2.15.txt @@ -49,6 +49,14 @@ plugins/modules/groupnet.py import-2.7 plugins/modules/networkrule.py validate-modules:missing-gplv3-license plugins/modules/nfs_alias.py validate-modules:missing-gplv3-license plugins/modules/settings.py validate-modules:missing-gplv3-license +plugins/modules/settings.py import-2.7 +plugins/modules/settings.py import-3.5 +plugins/modules/settings.py compile-2.7 +plugins/modules/settings.py compile-3.5 +plugins/module_utils/storage/dell/shared_library/cluster.py compile-2.7 +plugins/module_utils/storage/dell/shared_library/cluster.py import-2.7 +plugins/module_utils/storage/dell/shared_library/cluster.py compile-3.5 +plugins/module_utils/storage/dell/shared_library/cluster.py import-3.5 plugins/modules/networksettings.py validate-modules:missing-gplv3-license plugins/modules/smartpoolsettings.py validate-modules:missing-gplv3-license plugins/modules/filepoolpolicy.py import-2.7 @@ -76,9 +84,6 @@ plugins/modules/user_mapping_rule.py compile-2.7 plugins/modules/user_mapping_rule.py compile-3.5 plugins/modules/user_mapping_rule.py validate-modules:missing-gplv3-license plugins/modules/networkpool.py import-2.7 -plugins/modules/networkpool.py import-3.5 -plugins/modules/networkpool.py compile-2.7 -plugins/modules/networkpool.py compile-3.5 plugins/modules/networkpool.py validate-modules:missing-gplv3-license plugins/modules/nfs_global_settings.py import-2.7 plugins/modules/nfs_global_settings.py import-3.5 @@ -111,4 +116,14 @@ plugins/modules/synciqcertificate.py validate-modules:missing-gplv3-license plugins/modules/synciqcertificate.py import-2.7 plugins/modules/synciqcertificate.py import-3.5 plugins/modules/synciqcertificate.py compile-2.7 -plugins/modules/synciqcertificate.py compile-3.5 \ No newline at end of file +plugins/modules/synciqcertificate.py compile-3.5 +plugins/modules/smb_global_settings.py import-2.7 +plugins/modules/smb_global_settings.py compile-2.7 +plugins/modules/smb_global_settings.py import-3.5 +plugins/modules/smb_global_settings.py compile-3.5 +plugins/modules/smb_global_settings.py validate-modules:missing-gplv3-license +plugins/modules/snmp_settings.py validate-modules:missing-gplv3-license +plugins/modules/snmp_settings.py import-2.7 +plugins/modules/snmp_settings.py compile-2.7 +plugins/modules/snmp_settings.py import-3.5 +plugins/modules/snmp_settings.py compile-3.5 \ No newline at end of file diff --git a/tests/sanity/ignore-2.16.txt b/tests/sanity/ignore-2.16.txt index 33b78777..cf9bf4ca 100644 --- a/tests/sanity/ignore-2.16.txt +++ b/tests/sanity/ignore-2.16.txt @@ -37,6 +37,10 @@ plugins/modules/groupnet.py import-2.7 plugins/modules/networkrule.py validate-modules:missing-gplv3-license plugins/modules/nfs_alias.py validate-modules:missing-gplv3-license plugins/modules/settings.py validate-modules:missing-gplv3-license +plugins/modules/settings.py import-2.7 +plugins/modules/settings.py compile-2.7 +plugins/module_utils/storage/dell/shared_library/cluster.py compile-2.7 +plugins/module_utils/storage/dell/shared_library/cluster.py import-2.7 plugins/modules/networksettings.py validate-modules:missing-gplv3-license plugins/modules/smartpoolsettings.py validate-modules:missing-gplv3-license plugins/modules/filepoolpolicy.py import-2.7 @@ -54,7 +58,6 @@ plugins/modules/user_mapping_rule.py import-2.7 plugins/modules/user_mapping_rule.py compile-2.7 plugins/modules/user_mapping_rule.py validate-modules:missing-gplv3-license plugins/modules/networkpool.py import-2.7 -plugins/modules/networkpool.py compile-2.7 plugins/modules/networkpool.py validate-modules:missing-gplv3-license plugins/modules/nfs_global_settings.py import-2.7 plugins/modules/nfs_global_settings.py compile-2.7 @@ -73,4 +76,10 @@ plugins/module_utils/storage/dell/shared_library/synciq.py compile-2.7 plugins/module_utils/storage/dell/shared_library/synciq.py import-2.7 plugins/modules/synciqcertificate.py validate-modules:missing-gplv3-license plugins/modules/synciqcertificate.py import-2.7 -plugins/modules/synciqcertificate.py compile-2.7 \ No newline at end of file +plugins/modules/synciqcertificate.py compile-2.7 +plugins/modules/smb_global_settings.py import-2.7 +plugins/modules/smb_global_settings.py compile-2.7 +plugins/modules/smb_global_settings.py validate-modules:missing-gplv3-license +plugins/modules/snmp_settings.py validate-modules:missing-gplv3-license +plugins/modules/snmp_settings.py import-2.7 +plugins/modules/snmp_settings.py compile-2.7 \ No newline at end of file diff --git a/tests/unit/plugins/module_utils/mock_info_api.py b/tests/unit/plugins/module_utils/mock_info_api.py index 53f2765b..002e3371 100644 --- a/tests/unit/plugins/module_utils/mock_info_api.py +++ b/tests/unit/plugins/module_utils/mock_info_api.py @@ -74,8 +74,16 @@ class MockGatherfactsApi: 'NfsDefaultSettings': {}, 'NfsGlobalSettings': {}, 'SynciqGlobalSettings': {}, - 's3Buckets': {} + 's3Buckets': {}, + 'SmbGlobalSettings': {}, + 'SnmpSettings': {}, + 'NTPServers': {}, + 'EmailSettings': {}, + 'ClusterIdentity': {}, + 'ClusterOwner': {} } + API = "api" + MODULE = "module" @staticmethod def get_network_groupnets_response(response_type): @@ -224,135 +232,40 @@ def get_network_interfaces_response(response_type): return { "interfaces": [ { - "id": "1.ext-1", - "ip_addrs": [ - "VALUE_SPECIFIED_IN_NO_LOG_PARAMETER" - ], - "lnn": 1, - "name": "ext-1", - "nic_name": "em1", - "owners": [ - { - "groupnet": "groupnet0", - "ip_addrs": [ - "VALUE_SPECIFIED_IN_NO_LOG_PARAMETER" - ], - "pool": "pool0", - "subnet": "subnet0" - } - ], - "status": "up", - "type": "gige" - }, - { - "id": "1.ext-2", - "ip_addrs": [], - "lnn": 1, - "name": "ext-2", - "nic_name": "em3", - "owners": [], - "status": "inactive", - "type": "gige" - }, - { - "id": "1.ext-3", + "flags": [], + "id": "3:ext-agg", "ip_addrs": [], - "lnn": 1, - "name": "ext-3", - "nic_name": "em4", + "ipv4_gateway": None, + "ipv6_gateway": None, + "lnn": 3, + "mtu": 0, + "name": "ext-agg", + "nic_name": "lagg0", "owners": [], + "speed": None, "status": "inactive", - "type": "gige" - }, - { - "id": "1.ext-4", - "ip_addrs": [], - "lnn": 1, - "name": "ext-4", - "nic_name": "em5", - "owners": [], - "status": "inactive", - "type": "gige" - }, - { - "id": "1.ext-5", - "ip_addrs": [], - "lnn": 1, - "name": "ext-5", - "nic_name": "em6", - "owners": [], - "status": "inactive", - "type": "gige" - }, - { - "id": "1.ext-6", - "ip_addrs": [], - "lnn": 1, - "name": "ext-6", - "nic_name": "em7", - "owners": [], - "status": "inactive", - "type": "gige" - }, - { - "id": "1.int-a", - "ip_addrs": [ - "1.1.1.31" - ], - "lnn": 1, - "name": "int-a", - "nic_name": "em0", - "owners": [ - { - "groupnet": "internal", - "ip_addrs": [ - "1.1.1.1" - ], - "pool": "int-a-pool", - "subnet": "int-a-subnet" - } - ], - "status": "up", - "type": "gige" + "type": "aggregated", + "vlans": [] } ] } elif response_type == 'module': return [ { - 'id': '1.ext-1', - 'name': 'ext-1', - 'lnn': 1 - }, - { - 'id': '1.ext-2', - 'name': 'ext-2', - 'lnn': 1 - }, - { - 'id': '1.ext-3', - 'name': 'ext-3', - 'lnn': 1 - }, - { - 'id': '1.ext-4', - 'name': 'ext-4', - 'lnn': 1 - }, - { - 'id': '1.ext-5', - 'name': 'ext-5', - 'lnn': 1 - }, - { - 'id': '1.ext-6', - 'name': 'ext-6', - 'lnn': 1 - }, - { - 'id': '1.int-a', - 'name': 'int-a', - 'lnn': 1 + "flags": [], + "id": "3:ext-agg", + "ip_addrs": [], + "ipv4_gateway": None, + "ipv6_gateway": None, + "lnn": 3, + "mtu": 0, + "name": "ext-agg", + "nic_name": "lagg0", + "owners": [], + "speed": None, + "status": "inactive", + "type": "aggregated", + "vlans": [] } ] else: @@ -956,13 +869,580 @@ def get_ldap_details_response(response_type): MockGatherfactsApi.GATHERFACTS_COMMON_ARGS['onefs_host']) @staticmethod - def get_nfs_zone_setting_response(response_type): + def get_smb_global_settings(response_type): + if response_type == "api": + return { + "settings": { + 'access_based_share_enum': False, + 'dot_snap_accessible_child': True, + 'dot_snap_accessible_root': True, + 'dot_snap_visible_child': False, + 'dot_snap_visible_root': True, + } + } + elif response_type == "module": + return { + 'access_based_share_enum': False, + 'dot_snap_accessible_child': True, + 'dot_snap_accessible_root': True, + 'dot_snap_visible_child': False, + 'dot_snap_visible_root': True, + } + else: + return "Got error SDK Error message while getting SMB global setings details " + + @staticmethod + def get_nfsglobal_settings(response_type): + if response_type == "api": + return { + "settings": { + "service": True + } + } + elif response_type == "module": + return { + "service": True + } + else: + return "Getting NFS global settings for PowerScale: **.***.**.*** failed with error: SDK Error message" + + @staticmethod + def get_snmp_settings_response(response_type): + if response_type == "api": + return { + "settings": { + "service": True, + "snmp_v1_v2c_access": True, + "snmp_v3_access": True, + "snmp_v3_auth_protocol": "MD5", + "snmp_v3_priv_protocol": "DES", + "read_only_community": "readonly" + } + } + elif response_type == "module": + return { + "service": True, + "snmp_v1_v2c_access": True, + "snmp_v3_access": True, + "snmp_v3_auth_protocol": "MD5", + "snmp_v3_priv_protocol": "DES", + "read_only_community": "readonly" + } + else: + return "Fetching SNMP settings failed with error: SDK Error message" + + @staticmethod + def get_nfs_zone_settings(response_type): + if response_type == "api": + return { + "settings": { + "nfsv4_replace_domain": False, + "zone": "System" + } + } + elif response_type == "module": + return { + "nfsv4_replace_domain": False, + "zone": "System" + } + else: + return "Getting zone settings for PowerScale: **.***.**.*** failed with error: SDK Error message" + + @staticmethod + def get_nfs_default_settings_response(response_type): + if response_type == "api": + return { + "settings": { + 'write_datasync_action': 'DATASYNC', + 'write_datasync_reply': 'DATASYNC', + } + } + elif response_type == "module": + return { + 'write_datasync_action': 'DATASYNC', + 'write_datasync_reply': 'DATASYNC', + } + else: + return "Fetching NFS default settings failed with error: SDK Error message" + + @staticmethod + def get_providers_response(response_type): + resp = [{ + "name": "ansildap1", + }, { + "name": "ansildap2", + }] + if response_type == "error": + return "Get authentication Providers List for PowerScale cluster: **.***.**.*** failed with error: SDK Error message" + else: + return resp + + @staticmethod + def get_users_response(response_type): + resp = [{ + "name": "testuser", + }, { + "name": "testuser1", + }] + if response_type == "error": + return "Get Users List for PowerScale cluster: **.***.**.*** and access zone: System failed with error: SDK Error message" + else: + return resp + + @staticmethod + def get_groups_response(response_type): + resp = [{ + "name": "testgroup", + }, { + "name": "testgroup1", + }] + if response_type == "error": + return "Get Group List for PowerScale cluster: **.***.**.*** andaccess zone: System failed with error: SDK Error message" + else: + return resp + + @staticmethod + def get_s3_buckets_response(response_type): + resp = [{ + "name": "testuser", + }, { + "name": "testuser1", + }] + if response_type == "error": + return "Fetching S3 bucket list failed with error: SDK Error message" + else: + return resp + + @staticmethod + def get_smb_shares_response(response_type): + if response_type == "module": + return [{"id": "1", "name": "testuser"}, { + "id": "2", "name": "testuser1", + }] + elif response_type == "api": + return {"shares": [{"id": "1", "name": "testuser"}, { + "id": "2", "name": "testuser1", + }]} + else: + return "Get smb_shares list for PowerScale cluster: **.***.**.*** failed witherror: SDK Error message" + + @staticmethod + def get_nfs_exports_response(response_type): + if response_type == "module": + return [{"id": "1", "paths": "testuser"}, { + "id": "2", "paths": "testuser1", + }] + elif response_type == "api": + return {"exports": [{"id": "1", "paths": "testuser"}, { + "id": "2", "paths": "testuser1", + }]} + else: + return "Get nfs_exports list for PowerScale cluster: **.***.**.*** failed witherror: SDK Error message" + + @staticmethod + def get_attributes_response(response_type): + cluster_config = {"name": "cluster"} + external_ips = ["*.**.***.*", "*.**.***.*"] + logon_msg = "logon msg" + contact_info = "Contact Info" + cluster_version = "9.5" + if response_type == "error": + return "Get Attributes List for PowerScale cluster: **.***.**.*** failed with error: SDK Error message" + elif response_type == "module": + return cluster_config, external_ips, logon_msg, contact_info, cluster_version + else: + return {"Config": cluster_config, "Contact_Info": contact_info, + "External_IP": {"External IPs": ','.join(external_ips)}, + "Logon_msg": logon_msg, + "Cluster_Version": cluster_version} + + @staticmethod + def get_nodes_response(response_type): + if response_type == "error": + return "Get Nodes List for PowerScale cluster: **.***.**.*** failed witherror: SDK Error message" + return [{"node_name": "node1"}, {"node_name": "node2"}] + + @staticmethod + def get_synciq_reports_response(response_type): + if response_type == "error": + return "Get SyncIQ Report list for PowerScale cluster: **.***.**.*** failed witherror: SDK Error message" + elif response_type == "api": + return {"total": 2, + "reports": [ + { + "id": "rep1", + "policy_name": "Policy1" + }, + { + "id": "rep2", + "policy_name": "Policy2" + }, + ] + } + elif response_type == "module": + return [ + { + "id": "rep1", + "name": "Policy1" + }, + { + "id": "rep2", + "name": "Policy2" + }, + ] + + @staticmethod + def get_synciq_target_reports_response(response_type): + if response_type == "error": + return "Get SyncIQ Target Report list for PowerScale cluster: **.***.**.*** failed witherror: SDK Error message" + elif response_type == "api": + return {"total": 2, + "reports": [ + { + "id": "rep1", + "policy_name": "Policy1" + }, + { + "id": "rep2", + "policy_name": "Policy2" + }, + ] + } + elif response_type == "module": + return [ + { + "id": "rep1", + "name": "Policy1" + }, + { + "id": "rep2", + "name": "Policy2" + }, + ] + + @staticmethod + def get_synciq_performance_rules_response(response_type): + if response_type == "error": + return "Get SyncIQ performance rules list for PowerScale cluster: **.***.**.*** failed witherror: SDK Error message" + elif response_type == "api": + return { + "rules": [ + { + "id": "rep1", + "schedule": "schedule", + "enabled": True, + "type": "bandwidth", + "limit": 2 + }, + { + "id": "rep1", + "schedule": "schedule", + "enabled": True, + "type": "cpu", + "limit": 4 + }, + { + "id": "rep1", + "schedule": "schedule", + "enabled": True, + "type": "file_count", + "limit": 1 + }, + { + "id": "rep1", + "schedule": "schedule", + "enabled": True, + "type": "worker", + "limit": 3 + }, + ] + } + elif response_type == "module": + return [ + { + "id": "rep1", + "schedule": "schedule", + "enabled": True, + "type": "bandwidth", + "limit": "2kb/s" + }, + { + "id": "rep1", + "schedule": "schedule", + "enabled": True, + "type": "cpu", + "limit": "4%" + }, + { + "id": "rep1", + "schedule": "schedule", + "enabled": True, + "type": "file_count", + "limit": "1files/sec" + }, + { + "id": "rep1", + "schedule": "schedule", + "enabled": True, + "type": "worker", + "limit": "3%" + }, + ] + + @staticmethod + def get_synciq_policies_response(response_type): + resp = [ + { + "id": "p1", + "name": "Policy1", + "source_root_path": "path", + "target_path": "target_path", + "action": "action", + "schedule": "schedule", + "enabled": True + }, + { + "id": "p2", + "name": "Policy2", + "source_root_path": "path", + "target_path": "target_path", + "action": "action", + "schedule": "schedule", + "enabled": False + }, + ] if response_type == "error": - return "Getting zone settings for PowerScale: %s failed with " \ - "error: SDK Error message" % (MockGatherfactsApi.GATHERFACTS_COMMON_ARGS['onefs_host']) + return "Get list of SyncIQ Policies for PowerScale: **.***.**.*** failed witherror: SDK Error message" + if response_type == "api": + return { + "policies": resp + } + elif response_type == "module": + return resp @staticmethod - def get_nfs_global_setting_response(response_type): + def get_synciq_target_cluster_certificates_response(response_type): + resp = [ + { + "id": "c1", + "name": "cert1", + }, + { + "id": "c2", + "name": "cert2", + } + ] if response_type == "error": - return "Getting NFS global settings for PowerScale: %s failed with " \ - "error: SDK Error message" % (MockGatherfactsApi.GATHERFACTS_COMMON_ARGS['onefs_host']) + return "Get list of SyncIQ target cluster certificates for PowerScale: **.***.**.*** failed witherror: SDK Error message" + elif response_type == "api": + return { + "certificates": resp + } + else: + return resp + + @staticmethod + def get_access_zones_response(response_type): + if response_type == "error": + return "Get Access zone List for PowerScale cluster: **.***.**.*** failedwith error: SDK Error message" + return [{"id": "1"}, {"id": "2"}] + + @staticmethod + def get_clients_response(response_type): + if response_type == "error": + return "Get active clients list for PowerScale cluster: **.***.**.*** failed witherror: SDK Error message" + elif response_type == "api": + return { + "client": [ + { + "local_addr": "local_address1", + "local_name": "local_name1", + "remote_addr": "remote_address1", + "remote_name": "remote_name1", + "node": "node1", + "protocol": "protocol1", + }, + { + "local_addr": "local_address2", + "local_name": "local_name2", + "remote_addr": "remote_address2", + "remote_name": "remote_name2", + "node": "node2", + "protocol": "protocol2", + } + ] + } + elif response_type == "module": + return [ + { + "local_address": "local_address1", + "local_name": "local_name1", + "remote_address": "remote_address1", + "remote_name": "remote_name1", + "node": "node1", + "protocol": "protocol1", + }, + { + "local_address": "local_address2", + "local_name": "local_name2", + "remote_address": "remote_address2", + "remote_name": "remote_name2", + "node": "node2", + "protocol": "protocol2", + } + ] + + @staticmethod + def get_gather_facts_module_response(gather_subset): + param = "module" + subset_error_dict = { + "nfs_global_settings": MockGatherfactsApi.get_nfsglobal_settings(param), + "smb_global_settings": MockGatherfactsApi.get_smb_global_settings(param), + "nfs_zone_settings": MockGatherfactsApi.get_nfs_zone_settings(param), + "ldap": MockGatherfactsApi.get_ldap_details_response(param), + "user_mapping_rules": MockGatherfactsApi.get_user_mapping_rules_response(param), + "smb_files": MockGatherfactsApi.get_smb_files_response(param), + "storagepool_tiers": MockGatherfactsApi.get_storage_tier_response('api')['tiers'], + "node_pools": MockGatherfactsApi.get_node_pool_response('api')['nodepools'], + "network_subnets": MockGatherfactsApi.get_network_subnets_response(param), + "nfs_aliases": MockGatherfactsApi.get_nfs_aliases_response(param), + "network_interfaces": MockGatherfactsApi.get_network_interfaces_response(param), + "network_rules": MockGatherfactsApi.get_network_rules_response(param), + "network_pools": MockGatherfactsApi.get_network_pools_response(param), + "network_groupnets": MockGatherfactsApi.get_network_groupnets_response(param), + "providers": MockGatherfactsApi.get_providers_response(param), + "users": MockGatherfactsApi.get_users_response(param), + "groups": MockGatherfactsApi.get_groups_response(param), + "smb_shares": MockGatherfactsApi.get_smb_shares_response(param), + "nfs_exports": MockGatherfactsApi.get_nfs_exports_response(param), + "nfs_default_settings": MockGatherfactsApi.get_nfs_default_settings_response(param), + "s3_buckets": MockGatherfactsApi.get_s3_buckets_response(param), + "nodes": MockGatherfactsApi.get_nodes_response(param), + "synciq_reports": MockGatherfactsApi.get_synciq_reports_response(param), + "synciq_target_reports": MockGatherfactsApi.get_synciq_target_reports_response(param), + "synciq_policies": MockGatherfactsApi.get_synciq_policies_response(param), + "synciq_performance_rules": MockGatherfactsApi.get_synciq_performance_rules_response(param), + "synciq_target_cluster_certificates": MockGatherfactsApi.get_synciq_target_cluster_certificates_response(param), + "access_zones": MockGatherfactsApi.get_access_zones_response(param), + "clients": MockGatherfactsApi.get_clients_response(param), + "snmp_settings": MockGatherfactsApi.get_snmp_settings_response(param), + } + return subset_error_dict.get(gather_subset) + + @staticmethod + def get_gather_facts_api_response(gather_subset): + param = "api" + subset_error_dict = { + "nfs_global_settings": MockGatherfactsApi.get_nfsglobal_settings(param), + "smb_global_settings": MockGatherfactsApi.get_smb_global_settings(param), + "nfs_zone_settings": MockGatherfactsApi.get_nfs_zone_settings(param), + "ldap": MockGatherfactsApi.get_ldap_details_response(param), + "user_mapping_rules": MockGatherfactsApi.get_user_mapping_rules_response(param), + "smb_files": MockGatherfactsApi.get_smb_files_response(param), + "storagepool_tiers": MockGatherfactsApi.get_storage_tier_response(param), + "node_pools": MockGatherfactsApi.get_node_pool_response(param), + "network_subnets": MockGatherfactsApi.get_network_subnets_response(param), + "nfs_aliases": MockGatherfactsApi.get_nfs_aliases_response(param), + "network_interfaces": MockGatherfactsApi.get_network_interfaces_response(param), + "network_rules": MockGatherfactsApi.get_network_rules_response(param), + "network_pools": MockGatherfactsApi.get_network_pools_response(param), + "network_groupnets": MockGatherfactsApi.get_network_groupnets_response(param), + "providers": MockGatherfactsApi.get_providers_response(param), + "users": MockGatherfactsApi.get_users_response(param), + "groups": MockGatherfactsApi.get_groups_response(param), + "smb_shares": MockGatherfactsApi.get_smb_shares_response(param), + "nfs_exports": MockGatherfactsApi.get_nfs_exports_response(param), + "nfs_default_settings": MockGatherfactsApi.get_nfs_default_settings_response(param), + "s3_buckets": MockGatherfactsApi.get_s3_buckets_response(param), + "nodes": MockGatherfactsApi.get_nodes_response(param), + "synciq_reports": MockGatherfactsApi.get_synciq_reports_response(param), + "synciq_target_reports": MockGatherfactsApi.get_synciq_target_reports_response(param), + "synciq_policies": MockGatherfactsApi.get_synciq_policies_response(param), + "synciq_performance_rules": MockGatherfactsApi.get_synciq_performance_rules_response(param), + "synciq_target_cluster_certificates": MockGatherfactsApi.get_synciq_target_cluster_certificates_response(param), + "access_zones": MockGatherfactsApi.get_access_zones_response(param), + "clients": MockGatherfactsApi.get_clients_response(param), + "snmp_settings": MockGatherfactsApi.get_snmp_settings_response(param), + } + return subset_error_dict.get(gather_subset) + + @staticmethod + def get_gather_facts_error_response(gather_subset): + param = "error" + subset_error_dict = { + "nfs_global_settings": MockGatherfactsApi.get_nfsglobal_settings(param), + "smb_global_settings": MockGatherfactsApi.get_smb_global_settings(param), + "nfs_zone_settings": MockGatherfactsApi.get_nfs_zone_settings(param), + "ldap": MockGatherfactsApi.get_ldap_details_response(param), + "user_mapping_rules": MockGatherfactsApi.get_user_mapping_rules_response(param), + "smb_files": MockGatherfactsApi.get_smb_files_response(param), + "storagepool_tiers": MockGatherfactsApi.get_storage_tier_response(param), + "node_pools": MockGatherfactsApi.get_node_pool_response(param), + "network_subnets": MockGatherfactsApi.get_network_subnets_response(param), + "nfs_aliases": MockGatherfactsApi.get_nfs_aliases_response(param), + "network_interfaces": MockGatherfactsApi.get_network_interfaces_response(param), + "network_rules": MockGatherfactsApi.get_network_rules_response(param), + "network_pools": MockGatherfactsApi.get_network_pools_response(param), + "network_groupnets": MockGatherfactsApi.get_network_groupnets_response(param), + "providers": MockGatherfactsApi.get_providers_response(param), + "users": MockGatherfactsApi.get_users_response(param), + "groups": MockGatherfactsApi.get_groups_response(param), + "smb_shares": MockGatherfactsApi.get_smb_shares_response(param), + "nfs_exports": MockGatherfactsApi.get_nfs_exports_response(param), + "nfs_default_settings": MockGatherfactsApi.get_nfs_default_settings_response(param), + "s3_buckets": MockGatherfactsApi.get_s3_buckets_response(param), + "attributes": MockGatherfactsApi.get_attributes_response(param), + "nodes": MockGatherfactsApi.get_nodes_response(param), + "synciq_reports": MockGatherfactsApi.get_synciq_reports_response(param), + "synciq_target_reports": MockGatherfactsApi.get_synciq_target_reports_response(param), + "synciq_policies": MockGatherfactsApi.get_synciq_policies_response(param), + "synciq_performance_rules": MockGatherfactsApi.get_synciq_performance_rules_response(param), + "synciq_target_cluster_certificates": MockGatherfactsApi.get_synciq_target_cluster_certificates_response(param), + "access_zones": MockGatherfactsApi.get_access_zones_response(param), + "clients": MockGatherfactsApi.get_clients_response(param), + "snmp_settings": MockGatherfactsApi.get_snmp_settings_response(param), + } + return subset_error_dict.get(gather_subset) + + @staticmethod + def get_gather_facts_error_method(gather_subset): + subset_method_dict = { + "nfs_global_settings": "get_nfs_settings_global", + "smb_global_settings": "get_smb_settings_global", + "nfs_zone_settings": "get_nfs_settings_zone", + "nfs_aliases": "list_nfs_aliases", + "smb_shares": "list_smb_shares", + "nfs_exports": "list_nfs_exports", + "nfs_default_settings": "get_nfs_settings_export", + "s3_buckets": "list_s3_buckets", + "snmp_settings": "get_snmp_settings", + + "ldap": "list_providers_ldap", + "user_mapping_rules": "get_mapping_users_rules", + "smb_files": "get_smb_openfiles", + "providers": "get_providers_summary", + "users": "list_auth_users", + "groups": "list_auth_groups", + + "storagepool_tiers": "list_storagepool_tiers", + "node_pools": "list_storagepool_nodepools", + + "network_subnets": "get_network_subnets", + "network_interfaces": "get_network_interfaces", + "network_rules": "get_network_rules", + "network_pools": "get_network_pools", + "network_groupnets": "list_network_groupnets", + + "attributes": "get_cluster_config", + "nodes": "get_cluster_nodes", + + "synciq_reports": "get_sync_reports", + "synciq_target_reports": "get_target_reports", + "synciq_policies": "list_sync_policies", + "synciq_performance_rules": "list_sync_rules", + "synciq_target_cluster_certificates": "list_certificates_peer", + + "access_zones": "list_zones", + "clients": "get_summary_client" + } + return subset_method_dict.get(gather_subset) diff --git a/tests/unit/plugins/module_utils/mock_networkpool_api.py b/tests/unit/plugins/module_utils/mock_networkpool_api.py index fde5e79e..24f4f09c 100644 --- a/tests/unit/plugins/module_utils/mock_networkpool_api.py +++ b/tests/unit/plugins/module_utils/mock_networkpool_api.py @@ -10,6 +10,9 @@ MODULE_UTILS_PATH = 'ansible_collections.dellemc.powerscale.plugins.modules.networkpool.utils' +RANGE1 = "1.1.1.1" +RANGE2 = "2.2.2.2" + GET_NETWORK_POOLS = {"pools": [{"access_zone": "ansible-neo", "groupnet": "groupnet0", "name": "Test_pool1", @@ -24,14 +27,14 @@ "name": "Test_pool1", "subnet": "subnet0", "description": "Test_pool1", - "ranges": [{"low": "1.*.*.*", - "high": "1.*.*.*"}], + "ranges": [{"low": RANGE1, + "high": RANGE1}], "ifaces": [{"iface": "ext-1", "lnn": 4}], - "static_routes": [{"gateway": "1.*.*.*", + "static_routes": [{"gateway": RANGE1, "prefixlen": 4, - "subnetdict": "1.*.*.*"}], - "sc_dns_zone": "1.*.*.*", + "subnet": RANGE1}], + "sc_dns_zone": RANGE1, "sc_connect_policy": "throughput", "sc_failover_policy": "throughput", "rebalance_policy": "auto", @@ -59,5 +62,14 @@ def delete_networkpool_failed_msg(pool_name): return 'Failed to delete network pool: ' + pool_name + ' with error' -def get_networkpool_invalid_id_msg(pool_name): - return "Invalid value for 'id', must not be 'None'" +def network_pool_failed_msg(response_type): + if response_type == 'invalid_pool_name': + return 'The value for pool_name is invalid' + elif response_type == 'invalid_pool_description': + return 'The maximum length for description is 128' + elif response_type == 'invalid_ip_range': + return 'The value for IP range is invalid' + elif response_type == 'invalid_iface': + return 'Please enter valid value for iface' + elif response_type == 'invalid_route': + return 'Invalid static route value' diff --git a/tests/unit/plugins/module_utils/mock_settings_api.py b/tests/unit/plugins/module_utils/mock_settings_api.py index 9f55edc5..388d2254 100644 --- a/tests/unit/plugins/module_utils/mock_settings_api.py +++ b/tests/unit/plugins/module_utils/mock_settings_api.py @@ -8,43 +8,80 @@ __metaclass__ = type -MODULE_UTILS_PATH = 'ansible_collections.dellemc.powerscale.plugins.modules.settings.utils' -SETTINGS = {'email_settings': [{'mail_relay': 'mailrelay.itp.xyz.net', - 'mail_sender': 'lab-a2@dell.com', - 'mail_subject': 'lab-alerts'}], - 'email_settings_mod': [{'mail_relay': 'mailrelaymod.itp.xyz.net', - 'mail_sender': 'lab-a2_mod@dell.com', - 'mail_subject': 'lab_mod-alerts'}], - 'NTP_server': [{"id": "1.1.1.1", - "key": None, - "name": "1.1.1.1"}], - 'NTP_servers': [{"resume": None, - "servers": [{"id": "1.1.1.1", - "key": None, - "name": "1.1.1.1"}], - "total": 1}]} - -GET_SETTINGS = {'settings': {'mail_relay': 'mailrelay.itp.xyz.net', - 'mail_sender': 'lab-a2@dell.com', - 'mail_subject': 'lab-alerts'}} - - -def get_ntp_server_failed_msg(ntp_server): - return 'Failed to get NTP server ' + ntp_server - - -def add_blank_ntp_server_msg(ntp_server): - return 'Failed to add NTP server' - - -def delete_ntp_server_failed_msg(ntp_server): - return 'Deleting NTP server ' + ntp_server[0] + ' failed with error' - - -def get_email_setting_failed_msg(): - return 'Failed to get the details of email settings' - - -def update_email_setting_failed_msg(): - return 'Modifying email setting failed with error' +class MockSettingsApi: + + MODULE_UTILS_PATH = 'ansible_collections.dellemc.powerscale.plugins.modules.settings.utils' + + IP_ADDRESS = "**.***.**.***" + + SETTINGS_COMMON_ARGS = { + "onefs_host": IP_ADDRESS, + "ntp_servers": IP_ADDRESS, + "mail_relay": "mailrelay.itp.xyz.net", + "mail_sender": "lab-a2@dell.com", + "mail_subject": "lab-alerts", + "name": "PIE-IsilonS-24241-Cluster", + "description": "description", + "logon_details": {"message_title": "This is the new title", + "description": "This is new description"}, + "company": "Test company", + "location": "Test location", + "primary_contact": {"name": "primary_name", + "phone1": "primary_phone1", + "phone2": "primary_phone2", + "email": "primary_email@email.com"}, + "secondary_contact": {"name": "secondary_name", + "phone1": "secondary_phone1", + "phone2": "secondary_phone2", + "email": "secondary_email@email.com"}, + "state": "present"} + + SETTINGS = {'NTP_server': [{"id": IP_ADDRESS, + "key": None, + "name": IP_ADDRESS}], + 'NTP_servers': [{"resume": None, + "servers": [{"id": IP_ADDRESS, + "key": None, + "name": IP_ADDRESS}], + "total": 1}], + "cluster_owner": {"company": "Test company", + "location": "Test location", + "primary_email": "primary_email@email.com", + "primary_name": "primary_name", + "primary_phone1": "primary_phone1", + "primary_phone2": "primary_phone2", + "secondary_email": "secondary_email@email.com", + "secondary_name": "secondary_name", + "secondary_phone1": "secondary_phone1", + "secondary_phone2": "secondary_phone2"}, + "cluster_identity": {"description": "asdadasdasdasdadadadds", + "logon": {"motd": "This is new description", + "motd_header": "This is the new title"}, + "mttdl_level_msg": "none", + "name": "PIE-IsilonS-24241-Clusterwrerwerwrewr"}} + + GET_SETTINGS = {'settings': {'mail_relay': 'mailrelay.itp.xyz.net', + 'mail_sender': 'lab-a2@dell.com', + 'mail_subject': 'lab-alerts'}} + + @staticmethod + def get_settings_exception_response(response_type): + if response_type == 'update_email_exception': + return "Modifying email setting failed with error: SDK Error message" + elif response_type == 'update_cluster_identity': + return "Updating cluster identity failed with error SDK Error message" + elif response_type == 'update_cluster_owner': + return "Updating cluster owner failed with error SDK Error message" + elif response_type == 'adding_invalid_server': + return "Failed to add NTP server:" + elif response_type == 'removing_invalid_server': + return "Deleting NTP server failed with error" + elif response_type == 'invalid_NTP_value': + return "Please provide valid value for NTP server" + elif response_type == 'update_invalid_cluster_name': + return "The maximum length for name is 40" + elif response_type == 'update_invalid_cluster_description': + return "The maximum length for description is 255" + elif response_type == 'update_invalid_mail_sender_email': + return "Email address of mail_sender is not in the correct format" diff --git a/tests/unit/plugins/module_utils/mock_smb_global_settings_api.py b/tests/unit/plugins/module_utils/mock_smb_global_settings_api.py new file mode 100644 index 00000000..aa7ed9c6 --- /dev/null +++ b/tests/unit/plugins/module_utils/mock_smb_global_settings_api.py @@ -0,0 +1,48 @@ +# Copyright: (c) 2023, Dell Technologies + +# Apache License version 2.0 (see MODULE-LICENSE or http://www.apache.org/licenses/LICENSE-2.0.txt) + +"""Mock Api response for Unit tests of SMB global settings module on PowerScale""" + +from __future__ import (absolute_import, division, print_function) + +__metaclass__ = type + + +class MockSMBGlobalSettingsApi: + MODULE_UTILS_PATH = 'ansible_collections.dellemc.powerscale.plugins.module_utils.storage.dell.utils' + + SMB_GLOBAL_COMMON_ARGS = { + "onefs_host": "**.***.**.***", + "verify_ssl": False + } + GET_SMB_GLOBAL_RESPONSE = { + "access_based_share_enum": False, + "dot_snap_accessible_child": True, + "dot_snap_accessible_root": True, + "dot_snap_visible_child": False, + "dot_snap_visible_root": True, + "enable_security_signatures": False, + "guest_user": "nobody", + "ignore_eas": False, + "onefs_cpu_multiplier": 4, + "onefs_num_workers": 0, + "reject_unencrypted_access": False, + "require_security_signatures": False, + "server_side_copy": False, + "server_string": "PowerScale Server", + "service": False, + "support_multichannel": True, + "support_netbios": False, + "support_smb2": True, + "support_smb3_encryption": True + } + + @staticmethod + def get_smb_global_settings_exception_response(response_type): + if response_type == 'get_details_exception': + return "Got error SDK Error message while getting SMB global setings details " + elif response_type == 'update_exception': + return "Modify SMB global settings failed with error: SDK Error message" + elif response_type == 'prereq_exception': + return 'Prerequisite validation failed' diff --git a/tests/unit/plugins/module_utils/mock_snmp_settings_api.py b/tests/unit/plugins/module_utils/mock_snmp_settings_api.py new file mode 100644 index 00000000..0dc09b6b --- /dev/null +++ b/tests/unit/plugins/module_utils/mock_snmp_settings_api.py @@ -0,0 +1,45 @@ +# Copyright: (c) 2023, Dell Technologies + +# Apache License version 2.0 (see MODULE-LICENSE or http://www.apache.org/licenses/LICENSE-2.0.txt) + +"""Mock Api response for Unit tests of SNMP settings module on PowerScale""" + +from __future__ import (absolute_import, division, print_function) + +__metaclass__ = type + + +class MockSNMPSettingsApi: + MODULE_UTILS_PATH = 'ansible_collections.dellemc.powerscale.plugins.module_utils.storage.dell.utils' + SNMP_SETTINGS_COMMON_ARGS = { + 'onefs_host': '**.***.**.***', + 'read_only_community': None, + 'service': None, + 'snmp_v2c_access': None, + 'snmp_v3': None, + 'system_contact': None, + 'system_location': None + } + GET_SNMP_SETTINGS_RESPONSE = { + "read_only_community": "I$ilonpublic", + "service": True, + "snmp_v1_v2c_access": True, + "snmp_v3_access": True, + "snmp_v3_auth_protocol": "SHA", + "snmp_v3_priv_protocol": "AES", + "snmp_v3_read_only_user": "general", + "snmp_v3_security_level": "authNoPriv", + "system_contact": "unset@unset.none", + "system_location": "unset" + } + + @staticmethod + def get_snmpsettings_exception_response(response_type): + if response_type == 'update_exception': + return "Modifying SNMP settings failed with error: SDK Error message" + elif response_type == 'get_resp_exception': + return "Fetching SNMP settings failed with error:" + elif response_type == 'system_contact_exception': + return "Provide valid system_contact parameter." + elif response_type == 'system_location_exception': + return "Provide valid system_location parameter." diff --git a/tests/unit/plugins/modules/test_info.py b/tests/unit/plugins/modules/test_info.py index c4ed07f9..201959fd 100644 --- a/tests/unit/plugins/modules/test_info.py +++ b/tests/unit/plugins/modules/test_info.py @@ -9,6 +9,7 @@ __metaclass__ = type import pytest +from unittest.mock import patch from mock.mock import MagicMock from ansible_collections.dellemc.powerscale.tests.unit.plugins.module_utils.mock_info_api \ import MockGatherfactsApi @@ -18,11 +19,10 @@ import MockApiException from ansible_collections.dellemc.powerscale.tests.unit.plugins.module_utils.shared_library.initial_mock \ import utils +from ansible_collections.dellemc.powerscale.plugins.modules.info import Info utils.get_logger = MagicMock() -from ansible_collections.dellemc.powerscale.plugins.modules.info import Info - class TestInfo(): @@ -30,272 +30,385 @@ class TestInfo(): @pytest.fixture def gatherfacts_module_mock(self, mocker): - mocker.patch(MockGatherfactsApi.MODULE_PATH + '__init__', return_value=None) - mocker.patch(MockGatherfactsApi.MODULE_UTILS_PATH + '.ApiException', new=MockApiException) + mocker.patch(MockGatherfactsApi.MODULE_PATH + + '__init__', return_value=None) + mocker.patch(MockGatherfactsApi.MODULE_UTILS_PATH + + '.ApiException', new=MockApiException) gatherfacts_module_mock = Info() gatherfacts_module_mock.module = MagicMock() gatherfacts_module_mock.network_api = MagicMock() gatherfacts_module_mock.protocol_api = MagicMock() gatherfacts_module_mock.auth_api = MagicMock() + gatherfacts_module_mock.storagepool_api = MagicMock() + gatherfacts_module_mock.cluster_api = MagicMock() + gatherfacts_module_mock.zone_api = MagicMock() + gatherfacts_module_mock.synciq_api = MagicMock() + gatherfacts_module_mock.statistics_api = MagicMock() utils.ISI_SDK_VERSION_9 = MagicMock(return_value=True) return gatherfacts_module_mock - def test_get_network_groupnets(self, gatherfacts_module_mock): - network_groupnets = MockGatherfactsApi.get_network_groupnets_response('api') - self.get_module_args.update({ - 'gather_subset': ['network_groupnets'] - }) - gatherfacts_module_mock.module.params = self.get_module_args - gatherfacts_module_mock.network_api.list_network_groupnets = MagicMock(return_value=MockSDKResponse(network_groupnets)) - gatherfacts_module_mock.perform_module_operation() - assert MockGatherfactsApi.get_network_groupnets_response('module') == gatherfacts_module_mock.module.exit_json.call_args[1]['NetworkGroupnets'] - - def test_get_network_groupnets_api_exception(self, gatherfacts_module_mock): - self.get_module_args.update({ - 'gather_subset': ['network_groupnets'] - }) - gatherfacts_module_mock.module.params = self.get_module_args - gatherfacts_module_mock.network_api.list_network_groupnets = MagicMock(side_effect=MockApiException) - gatherfacts_module_mock.perform_module_operation() - assert MockGatherfactsApi.get_network_groupnets_response('error') == gatherfacts_module_mock.module.fail_json.call_args[1]['msg'] - - def test_get_network_pools(self, gatherfacts_module_mock): - network_pools = MockGatherfactsApi.get_network_pools_response('api') - self.get_module_args.update({ - 'gather_subset': ['network_pools'] - }) - gatherfacts_module_mock.module.params = self.get_module_args - gatherfacts_module_mock.network_api.get_network_pools = MagicMock(return_value=MockSDKResponse(network_pools)) - gatherfacts_module_mock.perform_module_operation() - assert MockGatherfactsApi.get_network_pools_response('module') == gatherfacts_module_mock.module.exit_json.call_args[1]['NetworkPools'] - - def test_get_network_pools_api_exception(self, gatherfacts_module_mock): - self.get_module_args.update({ - 'gather_subset': ['network_pools'] - }) - gatherfacts_module_mock.module.params = self.get_module_args - gatherfacts_module_mock.network_api.get_network_pools = MagicMock(side_effect=MockApiException) - gatherfacts_module_mock.perform_module_operation() - assert MockGatherfactsApi.get_network_pools_response('error') == gatherfacts_module_mock.module.fail_json.call_args[1]['msg'] - - def test_get_network_rules(self, gatherfacts_module_mock): - network_rules = MockGatherfactsApi.get_network_rules_response('api') - self.get_module_args.update({ - 'gather_subset': ['network_rules'] - }) - gatherfacts_module_mock.module.params = self.get_module_args - gatherfacts_module_mock.network_api.get_network_rules = MagicMock(return_value=MockSDKResponse(network_rules)) - gatherfacts_module_mock.perform_module_operation() - assert MockGatherfactsApi.get_network_rules_response('module') == gatherfacts_module_mock.module.exit_json.call_args[1]['NetworkRules'] - - def test_get_network_rules_api_exception(self, gatherfacts_module_mock): - self.get_module_args.update({ - 'gather_subset': ['network_rules'] - }) - gatherfacts_module_mock.module.params = self.get_module_args - gatherfacts_module_mock.network_api.get_network_rules = MagicMock(side_effect=MockApiException) - gatherfacts_module_mock.perform_module_operation() - assert MockGatherfactsApi.get_network_rules_response('error') == gatherfacts_module_mock.module.fail_json.call_args[1]['msg'] - def test_empty_gather_subset(self, gatherfacts_module_mock): self.get_module_args.update({ 'gather_subset': [] }) gatherfacts_module_mock.module.params = self.get_module_args gatherfacts_module_mock.perform_module_operation() - assert MockGatherfactsApi.EMPTY_GATHERSUBSET_ERROR_MSG == gatherfacts_module_mock.module.fail_json.call_args[1]['msg'] + assert MockGatherfactsApi.EMPTY_GATHERSUBSET_ERROR_MSG == gatherfacts_module_mock.module.fail_json.call_args[ + 1]['msg'] def test_input_none(self, gatherfacts_module_mock): self.get_module_args.update({}) gatherfacts_module_mock.perform_module_operation() - assert MockGatherfactsApi.EMPTY_RESULT == gatherfacts_module_mock.module.exit_json.call_args[1] - - def test_get_network_interfaces(self, gatherfacts_module_mock): - network_interfaces = MockGatherfactsApi.get_network_interfaces_response('api') + assert MockGatherfactsApi.EMPTY_RESULT == gatherfacts_module_mock.module.exit_json.call_args[ + 1] + + @pytest.mark.parametrize("input_params", [ + {"gather_subset": "clients", "return_key": "Clients"}, + ] + ) + def test_get_facts_statistics_api_module(self, gatherfacts_module_mock, input_params): + """Test the get_facts that uses the cluster api endpoint to get the module response""" + gather_subset = input_params.get('gather_subset') + return_key = input_params.get('return_key') + api_response = MockGatherfactsApi.get_gather_facts_api_response( + gather_subset) self.get_module_args.update({ - 'gather_subset': ['network_interfaces'] + 'gather_subset': [gather_subset] }) gatherfacts_module_mock.module.params = self.get_module_args - gatherfacts_module_mock.network_api = MagicMock() - gatherfacts_module_mock.network_api.get_network_interfaces = MagicMock(return_value=MockSDKResponse(network_interfaces)) - gatherfacts_module_mock.perform_module_operation() - assert MockGatherfactsApi.get_network_interfaces_response('module') == gatherfacts_module_mock.module.exit_json.call_args[1]['NetworkInterfaces'] - - def test_get_network_interfaces_api_exception(self, gatherfacts_module_mock): + with patch.object(gatherfacts_module_mock.statistics_api, MockGatherfactsApi.get_gather_facts_error_method(gather_subset)) as mock_method: + mock_method.return_value = MockSDKResponse(api_response) + gatherfacts_module_mock.perform_module_operation() + assert MockGatherfactsApi.get_gather_facts_module_response( + gather_subset) == gatherfacts_module_mock.module.exit_json.call_args[1][return_key] + + @pytest.mark.parametrize("gather_subset", [ + "clients", + ] + ) + def test_get_facts_statistics_api_exception(self, gatherfacts_module_mock, gather_subset): + """Test the get_facts that uses the cluster api endpoint to get the exception""" self.get_module_args.update({ - 'gather_subset': ['network_interfaces'] + 'gather_subset': [gather_subset] }) gatherfacts_module_mock.module.params = self.get_module_args - gatherfacts_module_mock.network_api = MagicMock() - gatherfacts_module_mock.network_api.get_network_interfaces = MagicMock(side_effect=MockApiException) - gatherfacts_module_mock.perform_module_operation() - assert MockGatherfactsApi.get_network_interfaces_response('error') == gatherfacts_module_mock.module.fail_json.call_args[1]['msg'] - - def test_get_nfs_aliases(self, gatherfacts_module_mock): - nfs_aliases = MockGatherfactsApi.get_nfs_aliases_response('api') + with patch.object(gatherfacts_module_mock.statistics_api, MockGatherfactsApi.get_gather_facts_error_method(gather_subset)) as mock_method: + mock_method.side_effect = MagicMock(side_effect=MockApiException) + gatherfacts_module_mock.perform_module_operation() + assert MockGatherfactsApi.get_gather_facts_error_response( + gather_subset) == gatherfacts_module_mock.module.fail_json.call_args[1]['msg'] + + @pytest.mark.parametrize("input_params", [ + {"gather_subset": "access_zones", "return_key": "AccessZones"}, + ] + ) + def test_get_facts_zone_api_module(self, gatherfacts_module_mock, input_params): + """Test the get_facts that uses the cluster api endpoint to get the module response""" + gather_subset = input_params.get('gather_subset') + return_key = input_params.get('return_key') + api_response = MockGatherfactsApi.get_gather_facts_api_response( + gather_subset) self.get_module_args.update({ - 'zone': "System", - 'gather_subset': ['nfs_aliases'] + 'gather_subset': [gather_subset] }) gatherfacts_module_mock.module.params = self.get_module_args - gatherfacts_module_mock.protocol_api = MagicMock() - gatherfacts_module_mock.protocol_api.list_nfs_aliases = MagicMock(return_value=MockSDKResponse(nfs_aliases)) - gatherfacts_module_mock.perform_module_operation() - assert MockGatherfactsApi.get_nfs_aliases_response('module') == gatherfacts_module_mock.module.exit_json.call_args[1]['NfsAliases'] - - def test_get_nfs_aliases_api_exception(self, gatherfacts_module_mock): + with patch.object(gatherfacts_module_mock.zone_api, MockGatherfactsApi.get_gather_facts_error_method(gather_subset)) as mock_method: + mock_method.return_value = MockSDKResponse(api_response) + gatherfacts_module_mock.perform_module_operation() + assert MockGatherfactsApi.get_gather_facts_module_response( + gather_subset) == gatherfacts_module_mock.module.exit_json.call_args[1][return_key] + + @pytest.mark.parametrize("gather_subset", [ + "access_zones", + ] + ) + def test_get_facts_zone_api_exception(self, gatherfacts_module_mock, gather_subset): + """Test the get_facts that uses the cluster api endpoint to get the exception""" self.get_module_args.update({ - 'zone': "System", - 'gather_subset': ['nfs_aliases'] + 'gather_subset': [gather_subset] }) gatherfacts_module_mock.module.params = self.get_module_args - gatherfacts_module_mock.protocol_api = MagicMock() - gatherfacts_module_mock.protocol_api.list_nfs_aliases = MagicMock(side_effect=MockApiException) - gatherfacts_module_mock.perform_module_operation() - assert MockGatherfactsApi.get_nfs_aliases_response('error') == gatherfacts_module_mock.module.fail_json.call_args[1]['msg'] - - def test_get_network_subnets(self, gatherfacts_module_mock): - network_subnets = MockGatherfactsApi.get_network_subnets_response('api') + with patch.object(gatherfacts_module_mock.zone_api, MockGatherfactsApi.get_gather_facts_error_method(gather_subset)) as mock_method: + mock_method.side_effect = MagicMock(side_effect=MockApiException) + gatherfacts_module_mock.perform_module_operation() + assert MockGatherfactsApi.get_gather_facts_error_response( + gather_subset) == gatherfacts_module_mock.module.fail_json.call_args[1]['msg'] + + @pytest.mark.parametrize("input_params", [ + {"gather_subset": "synciq_reports", "return_key": "SynciqReports"}, + {"gather_subset": "synciq_target_reports", "return_key": "SynciqTargetReports"}, + {"gather_subset": "synciq_policies", "return_key": "SynciqPolicies"}, + {"gather_subset": "synciq_performance_rules", "return_key": "SynciqPerformanceRules"}, + {"gather_subset": "synciq_target_cluster_certificates", "return_key": "SynciqTargetClusterCertificate"}, + ] + ) + def test_get_facts_synciq_api_module(self, gatherfacts_module_mock, input_params): + """Test the get_facts that uses the cluster api endpoint to get the module response""" + gather_subset = input_params.get('gather_subset') + return_key = input_params.get('return_key') + api_response = MockGatherfactsApi.get_gather_facts_api_response( + gather_subset) self.get_module_args.update({ - 'gather_subset': ['network_subnets'] + 'gather_subset': [gather_subset] }) gatherfacts_module_mock.module.params = self.get_module_args - gatherfacts_module_mock.network_api = MagicMock() - gatherfacts_module_mock.network_api.get_network_subnets = MagicMock(return_value=MockSDKResponse(network_subnets)) - gatherfacts_module_mock.perform_module_operation() - assert MockGatherfactsApi.get_network_subnets_response('module') == gatherfacts_module_mock.module.exit_json.call_args[1]['NetworkSubnets'] - - def test_get_network_subnets_api_exception(self, gatherfacts_module_mock): + with patch.object(gatherfacts_module_mock.synciq_api, MockGatherfactsApi.get_gather_facts_error_method(gather_subset)) as mock_method: + mock_method.return_value = MockSDKResponse(api_response) + gatherfacts_module_mock.perform_module_operation() + assert MockGatherfactsApi.get_gather_facts_module_response( + gather_subset) == gatherfacts_module_mock.module.exit_json.call_args[1][return_key] + + @pytest.mark.parametrize("gather_subset", [ + "synciq_reports", + "synciq_target_reports", + "synciq_policies", + "synciq_performance_rules", + "synciq_target_cluster_certificates" + ] + ) + def test_get_facts_synciq_api_exception(self, gatherfacts_module_mock, gather_subset): + """Test the get_facts that uses the cluster api endpoint to get the exception""" self.get_module_args.update({ - 'gather_subset': ['network_subnets'] + 'gather_subset': [gather_subset] }) gatherfacts_module_mock.module.params = self.get_module_args - gatherfacts_module_mock.network_api = MagicMock() - gatherfacts_module_mock.network_api.get_network_subnets = MagicMock(side_effect=MockApiException) - gatherfacts_module_mock.perform_module_operation() - assert MockGatherfactsApi.get_network_subnets_response('error') == gatherfacts_module_mock.module.fail_json.call_args[1]['msg'] - - def test_get_node_pools(self, gatherfacts_module_mock): - node_pools = MockGatherfactsApi.get_node_pool_response('api') + with patch.object(gatherfacts_module_mock.synciq_api, MockGatherfactsApi.get_gather_facts_error_method(gather_subset)) as mock_method: + mock_method.side_effect = MagicMock(side_effect=MockApiException) + gatherfacts_module_mock.perform_module_operation() + assert MockGatherfactsApi.get_gather_facts_error_response( + gather_subset) == gatherfacts_module_mock.module.fail_json.call_args[1]['msg'] + + def test_get_facts_attributes_module(self, gatherfacts_module_mock): + """Test the get_facts of attributes response""" + gather_subset = "attributes" + return_key = "Attributes" self.get_module_args.update({ - 'gather_subset': ['node_pools'] + 'gather_subset': [gather_subset] }) + # Mocking + cluster_config, external_ips, logon_msg, contact_info, cluster_version = MockGatherfactsApi.get_attributes_response("module") gatherfacts_module_mock.module.params = self.get_module_args - gatherfacts_module_mock.storagepool_api = MagicMock() - gatherfacts_module_mock.storagepool_api.list_storagepool_nodepools = MagicMock(return_value=MockSDKResponse(node_pools)) + gatherfacts_module_mock.cluster_api.get_cluster_config = MagicMock(return_value=MockSDKResponse(cluster_config)) + gatherfacts_module_mock.cluster_api.get_cluster_external_ips = MagicMock(return_value=external_ips) + gatherfacts_module_mock.cluster_api.get_cluster_identity = MagicMock(return_value=MockSDKResponse(logon_msg)) + gatherfacts_module_mock.cluster_api.get_cluster_owner = MagicMock(return_value=MockSDKResponse(contact_info)) + gatherfacts_module_mock.cluster_api.get_cluster_version = MagicMock(return_value=MockSDKResponse(cluster_version)) gatherfacts_module_mock.perform_module_operation() - assert node_pools['nodepools'] == gatherfacts_module_mock.module.exit_json.call_args[1]['NodePools'] - - def test_get_node_pools_api_exception(self, gatherfacts_module_mock): + return_resp = MockGatherfactsApi.get_attributes_response("api") + assert return_resp == gatherfacts_module_mock.module.exit_json.call_args[1][return_key] + + @pytest.mark.parametrize("input_params", [ + {"gather_subset": "nodes", "return_key": "Nodes"}, + ] + ) + def test_get_facts_cluster_api_module(self, gatherfacts_module_mock, input_params): + """Test the get_facts that uses the cluster api endpoint to get the module response""" + gather_subset = input_params.get('gather_subset') + return_key = input_params.get('return_key') + api_response = MockGatherfactsApi.get_gather_facts_api_response( + gather_subset) self.get_module_args.update({ - 'gather_subset': ['node_pools'] + 'gather_subset': [gather_subset] }) gatherfacts_module_mock.module.params = self.get_module_args - gatherfacts_module_mock.storagepool_api = MagicMock() - gatherfacts_module_mock.storagepool_api.list_storagepool_nodepools = MagicMock(side_effect=MockApiException) - gatherfacts_module_mock.perform_module_operation() - assert MockGatherfactsApi.get_node_pool_response('error') == gatherfacts_module_mock.module.fail_json.call_args[1]['msg'] - - def test_get_storagepool_tiers(self, gatherfacts_module_mock): - storage_tiers = MockGatherfactsApi.get_storage_tier_response('api') + with patch.object(gatherfacts_module_mock.cluster_api, MockGatherfactsApi.get_gather_facts_error_method(gather_subset)) as mock_method: + mock_method.return_value = MockSDKResponse(api_response) + gatherfacts_module_mock.perform_module_operation() + assert MockGatherfactsApi.get_gather_facts_module_response( + gather_subset) == gatherfacts_module_mock.module.exit_json.call_args[1][return_key] + + @pytest.mark.parametrize("gather_subset", [ + "attributes", + "nodes" + ] + ) + def test_get_facts_cluster_api_exception(self, gatherfacts_module_mock, gather_subset): + """Test the get_facts that uses the cluster api endpoint to get the exception""" self.get_module_args.update({ - 'gather_subset': ['storagepool_tiers'] + 'gather_subset': [gather_subset] }) gatherfacts_module_mock.module.params = self.get_module_args - gatherfacts_module_mock.storagepool_api = MagicMock() - gatherfacts_module_mock.storagepool_api.list_storagepool_tiers = MagicMock(return_value=MockSDKResponse(storage_tiers)) - gatherfacts_module_mock.perform_module_operation() - assert storage_tiers['tiers'] == gatherfacts_module_mock.module.exit_json.call_args[1]['StoragePoolTiers'] - - def test_get_storage_tiers_api_exception(self, gatherfacts_module_mock): + with patch.object(gatherfacts_module_mock.cluster_api, MockGatherfactsApi.get_gather_facts_error_method(gather_subset)) as mock_method: + mock_method.side_effect = MagicMock(side_effect=MockApiException) + gatherfacts_module_mock.perform_module_operation() + assert MockGatherfactsApi.get_gather_facts_error_response( + gather_subset) == gatherfacts_module_mock.module.fail_json.call_args[1]['msg'] + + @pytest.mark.parametrize("input_params", [ + {"gather_subset": "network_subnets", + "return_key": "NetworkSubnets"}, + {"gather_subset": "network_interfaces", + "return_key": "NetworkInterfaces"}, + {"gather_subset": "network_rules", + "return_key": "NetworkRules"}, + {"gather_subset": "network_groupnets", + "return_key": "NetworkGroupnets"}, + {"gather_subset": "network_pools", + "return_key": "NetworkPools"}, + ] + ) + def test_get_facts_network_api_module(self, gatherfacts_module_mock, input_params): + """Test the get_facts that uses the network api endpoint to get the module response""" + gather_subset = input_params.get('gather_subset') + return_key = input_params.get('return_key') + api_response = MockGatherfactsApi.get_gather_facts_api_response( + gather_subset) self.get_module_args.update({ - 'gather_subset': ['storagepool_tiers'] + 'gather_subset': [gather_subset] }) gatherfacts_module_mock.module.params = self.get_module_args - gatherfacts_module_mock.storagepool_api = MagicMock() - gatherfacts_module_mock.storagepool_api.list_storagepool_tiers = MagicMock(side_effect=MockApiException) - gatherfacts_module_mock.perform_module_operation() - assert MockGatherfactsApi.get_storage_tier_response('error') == gatherfacts_module_mock.module.fail_json.call_args[1]['msg'] - - def test_get_smb_files(self, gatherfacts_module_mock): - smb_files = MockGatherfactsApi.get_smb_files_response('api') + with patch.object(gatherfacts_module_mock.network_api, MockGatherfactsApi.get_gather_facts_error_method(gather_subset)) as mock_method: + mock_method.return_value = MockSDKResponse(api_response) + gatherfacts_module_mock.perform_module_operation() + assert MockGatherfactsApi.get_gather_facts_module_response( + gather_subset) == gatherfacts_module_mock.module.exit_json.call_args[1][return_key] + + @pytest.mark.parametrize("gather_subset", [ + "network_subnets", + "network_interfaces", + "network_rules", + "network_pools", + "network_groupnets" + ] + ) + def test_get_facts_network_api_exception(self, gatherfacts_module_mock, gather_subset): + """Test the get_facts that uses the network api endpoint to get the exception""" self.get_module_args.update({ - 'gather_subset': ['smb_files'] + 'gather_subset': [gather_subset] }) gatherfacts_module_mock.module.params = self.get_module_args - gatherfacts_module_mock.protocol_api = MagicMock() - gatherfacts_module_mock.protocol_api.get_smb_openfiles = MagicMock(return_value=MockSDKResponse(smb_files)) - gatherfacts_module_mock.perform_module_operation() - assert MockGatherfactsApi.get_smb_files_response('module') == gatherfacts_module_mock.module.exit_json.call_args[1]['SmbOpenFiles'] - - def test_get_smb_files_api_exception(self, gatherfacts_module_mock): + with patch.object(gatherfacts_module_mock.network_api, MockGatherfactsApi.get_gather_facts_error_method(gather_subset)) as mock_method: + mock_method.side_effect = MagicMock(side_effect=MockApiException) + gatherfacts_module_mock.perform_module_operation() + assert MockGatherfactsApi.get_gather_facts_error_response( + gather_subset) == gatherfacts_module_mock.module.fail_json.call_args[1]['msg'] + + @pytest.mark.parametrize("input_params", [ + {"gather_subset": "storagepool_tiers", + "return_key": "StoragePoolTiers"}, + {"gather_subset": "node_pools", + "return_key": "NodePools"}, + ] + ) + def test_get_facts_stooragepool_api_module(self, gatherfacts_module_mock, input_params): + """Test the get_facts that uses the storageapi api endpoint to get the module response""" + gather_subset = input_params.get('gather_subset') + return_key = input_params.get('return_key') + api_response = MockGatherfactsApi.get_gather_facts_api_response( + gather_subset) self.get_module_args.update({ - 'gather_subset': ['smb_files'] + 'gather_subset': [gather_subset] }) gatherfacts_module_mock.module.params = self.get_module_args - gatherfacts_module_mock.protocol_api = MagicMock() - gatherfacts_module_mock.protocol_api.get_smb_openfiles = MagicMock(side_effect=MockApiException) - gatherfacts_module_mock.perform_module_operation() - assert MockGatherfactsApi.get_smb_files_response('error') == gatherfacts_module_mock.module.fail_json.call_args[1]['msg'] - - def test_get_user_mapping_rule_api(self, gatherfacts_module_mock): - smb_files = MockGatherfactsApi.get_user_mapping_rules_response('api') + with patch.object(gatherfacts_module_mock.storagepool_api, MockGatherfactsApi.get_gather_facts_error_method(gather_subset)) as mock_method: + mock_method.return_value = MockSDKResponse(api_response) + gatherfacts_module_mock.perform_module_operation() + assert MockGatherfactsApi.get_gather_facts_module_response( + gather_subset) == gatherfacts_module_mock.module.exit_json.call_args[1][return_key] + + @pytest.mark.parametrize("gather_subset", ["storagepool_tiers", "node_pools"]) + def test_get_facts_storagepool_api_exception(self, gatherfacts_module_mock, gather_subset): + """Test the get_facts that uses the storageapi api endpoint to get the exception""" self.get_module_args.update({ - 'gather_subset': ['user_mapping_rules'] + 'gather_subset': [gather_subset] }) gatherfacts_module_mock.module.params = self.get_module_args - gatherfacts_module_mock.auth_api = MagicMock() - gatherfacts_module_mock.auth_api.get_mapping_users_rules = MagicMock(return_value=MockSDKResponse(smb_files)) - gatherfacts_module_mock.perform_module_operation() - assert MockGatherfactsApi.get_user_mapping_rules_response('module') == gatherfacts_module_mock.module.exit_json.call_args[1]['UserMappingRules'] - - def test_get_user_mapping_rule_api_exception(self, gatherfacts_module_mock): + with patch.object(gatherfacts_module_mock.storagepool_api, MockGatherfactsApi.get_gather_facts_error_method(gather_subset)) as mock_method: + mock_method.side_effect = MagicMock(side_effect=MockApiException) + gatherfacts_module_mock.perform_module_operation() + assert MockGatherfactsApi.get_gather_facts_error_response( + gather_subset) == gatherfacts_module_mock.module.fail_json.call_args[1]['msg'] + + @pytest.mark.parametrize("input_params", [ + {"gather_subset": "ldap", "return_key": "LdapProviders"}, + {"gather_subset": "user_mapping_rules", "return_key": "UserMappingRules"}, + {"gather_subset": "providers", "return_key": "Providers"}, + {"gather_subset": "users", "return_key": "Users"}, + {"gather_subset": "groups", "return_key": "Groups"}, + ] + ) + def test_get_facts_auth_api_module(self, gatherfacts_module_mock, input_params): + """Test the get_facts that uses the auth api endpoint to get the module response""" + + gather_subset = input_params.get('gather_subset') + return_key = input_params.get('return_key') + api_response = MockGatherfactsApi.get_gather_facts_api_response( + gather_subset) self.get_module_args.update({ - 'gather_subset': ['user_mapping_rules'] + 'gather_subset': [gather_subset] }) gatherfacts_module_mock.module.params = self.get_module_args - gatherfacts_module_mock.auth_api = MagicMock() - gatherfacts_module_mock.auth_api.get_mapping_users_rules = MagicMock(side_effect=MockApiException) - gatherfacts_module_mock.perform_module_operation() - assert MockGatherfactsApi.get_user_mapping_rules_response('error') == gatherfacts_module_mock.module.fail_json.call_args[1]['msg'] + with patch.object(gatherfacts_module_mock.auth_api, MockGatherfactsApi.get_gather_facts_error_method(gather_subset)) as mock_method: + mock_method.return_value = MockSDKResponse(api_response) + gatherfacts_module_mock.perform_module_operation() + assert MockGatherfactsApi.get_gather_facts_module_response( + gather_subset) == gatherfacts_module_mock.module.exit_json.call_args[1][return_key] - def test_get_ldap_details_api(self, gatherfacts_module_mock): - ldap = MockGatherfactsApi.get_ldap_details_response('api') - self.get_module_args.update({ - 'gather_subset': ['ldap'] - }) - gatherfacts_module_mock.module.params = self.get_module_args - gatherfacts_module_mock.auth_api = MagicMock() - gatherfacts_module_mock.auth_api.list_providers_ldap = MagicMock(return_value=MockSDKResponse(ldap)) - gatherfacts_module_mock.perform_module_operation() - assert MockGatherfactsApi.get_ldap_details_response('module') == gatherfacts_module_mock.module.exit_json.call_args[1]['LdapProviders'] + @pytest.mark.parametrize("gather_subset", ["ldap", "user_mapping_rules", "providers", "users", "groups"]) + def test_get_facts_auth_api_exception(self, gatherfacts_module_mock, gather_subset): + """Test the get_facts that uses the auth api endpoint to get the exception""" - def test_get_ldap_details_api_exception(self, gatherfacts_module_mock): self.get_module_args.update({ - 'gather_subset': ['ldap'] + 'gather_subset': [gather_subset] }) gatherfacts_module_mock.module.params = self.get_module_args - gatherfacts_module_mock.auth_api = MagicMock() - gatherfacts_module_mock.auth_api.list_providers_ldap = MagicMock(side_effect=MockApiException) - gatherfacts_module_mock.perform_module_operation() - assert MockGatherfactsApi.get_ldap_details_response('error') == gatherfacts_module_mock.module.fail_json.call_args[1]['msg'] - - def test_get_nfs_zone_setting_exception(self, gatherfacts_module_mock): + with patch.object(gatherfacts_module_mock.auth_api, MockGatherfactsApi.get_gather_facts_error_method(gather_subset)) as mock_method: + mock_method.side_effect = MagicMock(side_effect=MockApiException) + gatherfacts_module_mock.perform_module_operation() + assert MockGatherfactsApi.get_gather_facts_error_response( + gather_subset) == gatherfacts_module_mock.module.fail_json.call_args[1]['msg'] + + @pytest.mark.parametrize("input_params", [ + {"gather_subset": "smb_files", "return_key": "SmbOpenFiles"}, + {"gather_subset": "nfs_aliases", "return_key": "NfsAliases"}, + {"gather_subset": "smb_global_settings", "return_key": "SmbGlobalSettings"}, + {"gather_subset": "nfs_global_settings", "return_key": "NfsGlobalSettings"}, + {"gather_subset": "nfs_zone_settings", "return_key": "NfsZoneSettings"}, + {"gather_subset": "smb_shares", "return_key": "SmbShares"}, + {"gather_subset": "nfs_exports", "return_key": "NfsExports"}, + {"gather_subset": "nfs_default_settings", "return_key": "NfsDefaultSettings"}, + {"gather_subset": "s3_buckets", "return_key": "s3Buckets"}, + {"gather_subset": "snmp_settings", "return_key": "SnmpSettings"}, + ] + ) + def test_get_facts_protocols_api_module(self, gatherfacts_module_mock, input_params): + """Test the get_facts that uses the protocols api endpoint to get the module response""" + + gather_subset = input_params.get('gather_subset') + return_key = input_params.get('return_key') + api_response = MockGatherfactsApi.get_gather_facts_api_response( + gather_subset) self.get_module_args.update({ - 'gather_subset': ['nfs_zone_settings'] + 'gather_subset': [gather_subset], + 'zone': "System", }) gatherfacts_module_mock.module.params = self.get_module_args - gatherfacts_module_mock.protocol_api = MagicMock() - gatherfacts_module_mock.protocol_api.get_nfs_settings_zone = MagicMock(side_effect=MockApiException) - gatherfacts_module_mock.perform_module_operation() - assert MockGatherfactsApi.get_nfs_zone_setting_response('error') == gatherfacts_module_mock.module.fail_json.call_args[1]['msg'] - - def test_get_nfs_global_setting_exception(self, gatherfacts_module_mock): + with patch.object(gatherfacts_module_mock.protocol_api, MockGatherfactsApi.get_gather_facts_error_method(gather_subset)) as mock_method: + mock_method.return_value = MockSDKResponse(api_response) + gatherfacts_module_mock.perform_module_operation() + assert MockGatherfactsApi.get_gather_facts_module_response( + gather_subset) == gatherfacts_module_mock.module.exit_json.call_args[1][return_key] + + @pytest.mark.parametrize("gather_subset", [ + "nfs_global_settings", + "smb_global_settings", + "nfs_zone_settings", + "smb_files", + "nfs_aliases", + "smb_shares", + "nfs_exports", + "nfs_default_settings", + "s3_buckets", + "snmp_settings"]) + def test_get_facts_protocols_api_exception(self, gatherfacts_module_mock, gather_subset): + """Test the get_facts that uses the protocols api endpoint to get the exception""" self.get_module_args.update({ - 'gather_subset': ['nfs_global_settings'] + 'gather_subset': [gather_subset], + 'zone': "System", }) gatherfacts_module_mock.module.params = self.get_module_args - gatherfacts_module_mock.protocol_api = MagicMock() - gatherfacts_module_mock.protocol_api.get_nfs_settings_global = MagicMock(side_effect=MockApiException) - gatherfacts_module_mock.perform_module_operation() - assert MockGatherfactsApi.get_nfs_global_setting_response('error') == gatherfacts_module_mock.module.fail_json.call_args[1]['msg'] + with patch.object(gatherfacts_module_mock.protocol_api, MockGatherfactsApi.get_gather_facts_error_method(gather_subset)) as mock_method: + mock_method.side_effect = MagicMock(side_effect=MockApiException) + gatherfacts_module_mock.perform_module_operation() + assert MockGatherfactsApi.get_gather_facts_error_response( + gather_subset) == gatherfacts_module_mock.module.fail_json.call_args[1]['msg'] diff --git a/tests/unit/plugins/modules/test_networkpool.py b/tests/unit/plugins/modules/test_networkpool.py index 9d5f1566..2a2f2285 100644 --- a/tests/unit/plugins/modules/test_networkpool.py +++ b/tests/unit/plugins/modules/test_networkpool.py @@ -9,21 +9,25 @@ __metaclass__ = type import pytest +# pylint: disable=unused-import +from ansible_collections.dellemc.powerscale.tests.unit.plugins.module_utils.shared_library \ + import initial_mock from mock.mock import MagicMock from ansible_collections.dellemc.powerscale.tests.unit.plugins.module_utils.shared_library.initial_mock \ import utils - from ansible_collections.dellemc.powerscale.plugins.modules.networkpool import NetworkPool from ansible_collections.dellemc.powerscale.tests.unit.plugins.\ module_utils import mock_networkpool_api as MockNetworkPoolApi from ansible_collections.dellemc.powerscale.tests.unit.plugins.module_utils.mock_sdk_response \ import MockSDKResponse + from ansible_collections.dellemc.powerscale.tests.unit.plugins.module_utils.mock_api_exception \ import MockApiException +from ansible_collections.dellemc.powerscale.tests.unit.plugins.module_utils.shared_library.powerscale_unit_base import PowerScaleUnitBase -class TestNetworkPool(): +class TestNetworkPool(PowerScaleUnitBase): get_network_pool_args = {"groupnet_name": "groupnet0", "subnet_name": "subnet0", "pool_name": "Test_pool1", @@ -39,50 +43,47 @@ class TestNetworkPool(): "sc_dns_zone_aliases": []}} @pytest.fixture - def network_pool_module_mock(self, mocker): - mocker.patch(MockNetworkPoolApi.MODULE_UTILS_PATH + '.ApiException', new=MockApiException) - networkpool_module_mock = NetworkPool() - networkpool_module_mock.module = MagicMock() - return networkpool_module_mock + def module_object(self): + return NetworkPool - def test_get_network_pool(self, network_pool_module_mock): + def test_get_network_pool(self, powerscale_module_mock): network_pool = MockNetworkPoolApi.GET_NETWORK_POOLS self.get_network_pool_args.update({"state": "present"}) - network_pool_module_mock.module.params = self.get_network_pool_args - network_pool_module_mock.network_groupnet_api.get_subnets_subnet_pool = MagicMock( + powerscale_module_mock.module.params = self.get_network_pool_args + powerscale_module_mock.network_groupnet_api.get_subnets_subnet_pool = MagicMock( return_value=MockSDKResponse(MockNetworkPoolApi.GET_NETWORK_POOLS)) - network_pool_module_mock.perform_module_operation() - assert network_pool_module_mock.module.exit_json.call_args[1]['changed'] is False - assert network_pool == network_pool_module_mock.module.exit_json.call_args[1]['network_pool'] + powerscale_module_mock.perform_module_operation() + assert powerscale_module_mock.module.exit_json.call_args[1]['changed'] is False + assert network_pool == powerscale_module_mock.module.exit_json.call_args[1]['network_pool'] - def test_get_network_pool_with_exception(self, network_pool_module_mock): + def test_get_network_pool_with_exception(self, powerscale_module_mock): MockNetworkPoolApi.GET_NETWORK_POOLS self.get_network_pool_args.update({"state": "present"}) - network_pool_module_mock.module.params = self.get_network_pool_args - network_pool_module_mock.network_groupnet_api.get_subnets_subnet_pool = MagicMock(side_effect=utils.ApiException) - network_pool_module_mock.perform_module_operation() - assert MockNetworkPoolApi.get_networkpool_failed_msg(MockNetworkPoolApi.GET_NETWORK_POOLS['pools'][0]['name']) in \ - network_pool_module_mock.module.fail_json.call_args[1]['msg'] + powerscale_module_mock.module.params = self.get_network_pool_args + powerscale_module_mock.network_groupnet_api.get_subnets_subnet_pool = MagicMock(side_effect=utils.ApiException) + self.capture_fail_json_call(MockNetworkPoolApi.get_networkpool_failed_msg( + MockNetworkPoolApi.GET_NETWORK_POOLS['pools'][0]['name']), + powerscale_module_mock, invoke_perform_module=True) - def test_get_network_pool_with_404_exception(self, network_pool_module_mock): + def test_get_network_pool_with_404_exception(self, powerscale_module_mock): MockNetworkPoolApi.GET_NETWORK_POOLS self.get_network_pool_args.update({"state": "present"}) MockApiException.status = '404' - network_pool_module_mock.module.params = self.get_network_pool_args - network_pool_module_mock.network_groupnet_api.get_subnets_subnet_pool = MagicMock(side_effect=utils.ApiException) - network_pool_module_mock.perform_module_operation() - assert network_pool_module_mock.module.exit_json.call_args[1]['changed'] is True + powerscale_module_mock.module.params = self.get_network_pool_args + powerscale_module_mock.network_groupnet_api.get_subnets_subnet_pool = MagicMock(side_effect=utils.ApiException) + powerscale_module_mock.perform_module_operation() + assert powerscale_module_mock.module.exit_json.call_args[1]['changed'] is True - def test_create_network_pool(self, network_pool_module_mock): + def common_create_pool_params(self, powerscale_module_mock): self.get_network_pool_args.update({"state": "present", "description": "Test_pool1", - "additional_pool_params": {"ranges": [{"low": "1.*.*.*", - "high": "1.*.*.*"}], + "additional_pool_params": {"ranges": [{"low": MockNetworkPoolApi.RANGE1, + "high": MockNetworkPoolApi.RANGE1}], "range_state": "add", "ifaces": [{"iface": "ext-1", "lnn": 4}], "iface_state": "add"}, - "sc_params": {"sc_dns_zone": "1.*.*.*", + "sc_params": {"sc_dns_zone": MockNetworkPoolApi.RANGE1, "sc_connect_policy": "throughput", "sc_failover_policy": "throughput", "rebalance_policy": "auto", @@ -92,145 +93,142 @@ def test_create_network_pool(self, network_pool_module_mock): "aggregation_mode": "lacp", "sc_dns_zone_aliases": ["smartconn-zone"], "sc_subnet": "subnet_test", - "static_routes": [{"gateway": "1.*.*.*", + "static_routes": [{"gateway": MockNetworkPoolApi.RANGE1, "prefix_len": 4, - "subnet": "1.*.*.*"}]}}) - network_pool_module_mock.module.params = self.get_network_pool_args - network_pool_module_mock.get_network_pool = MagicMock(return_value=None) - network_pool_module_mock.network_groupnet_api.create_subnets_subnet_pool = MagicMock( + "subnet": MockNetworkPoolApi.RANGE1, + "route_state": "add"}]}}) + powerscale_module_mock.module.params = self.get_network_pool_args + powerscale_module_mock.get_network_pool = MagicMock(return_value=None) + + def test_create_network_pool(self, powerscale_module_mock): + self.common_create_pool_params(powerscale_module_mock) + powerscale_module_mock.network_groupnet_api.create_subnets_subnet_pool = MagicMock( return_value=MockSDKResponse(MockNetworkPoolApi.CREATE_NETWORK_POOL)) - network_pool_module_mock.perform_module_operation() - assert network_pool_module_mock.module.exit_json.call_args[1]['changed'] is True - - def test_create_network_pool_with_exception(self, network_pool_module_mock): - self.get_network_pool_args.update({"state": "present", - "description": "Test_pool1", - "additional_pool_params": {"ranges": [{"low": "1.*.*.*", - "high": "1.*.*.*"}], - "range_state": "add", - "ifaces": [{"iface": "ext-1", - "lnn": 4}], - "iface_state": "add"}, - "sc_params": {"sc_dns_zone": "1.*.*.*", - "sc_connect_policy": "throughput", - "sc_failover_policy": "throughput", - "rebalance_policy": "auto", - "alloc_method": "dynamic", - "sc_auto_unsuspend_delay": 200, - "sc_ttl": 300, - "aggregation_mode": "lacp", - "sc_dns_zone_aliases": ["smartconn-zone"], - "sc_subnet": "subnet_test", - "static_routes": [{"gateway": "1.*.*.*", - "prefix_len": 4, - "subnet": "1.*.*.*"}]}}) - network_pool_module_mock.module.params = self.get_network_pool_args - network_pool_module_mock.get_network_pool = MagicMock(return_value=None) - network_pool_module_mock.network_groupnet_api.create_subnets_subnet_pool = MagicMock(side_effect=utils.ApiException) - network_pool_module_mock.perform_module_operation() - assert MockNetworkPoolApi.create_networkpool_failed_msg(MockNetworkPoolApi.GET_NETWORK_POOLS['pools'][0]['name']) in \ - network_pool_module_mock.module.fail_json.call_args[1]['msg'] - - def test_create_network_pool_with_blank_values(self, network_pool_module_mock): - self.get_network_pool_args.update({"state": "present", - "pool_name": "", - "description": "", - "access_zone": None, - "additional_pool_params": {"ranges": [], - "range_state": None, - "ifaces": [], - "iface_state": None}, - "sc_params": {"sc_dns_zone": "", - "sc_connect_policy": "", - "sc_failover_policy": "", - "rebalance_policy": "", - "alloc_method": "", - "sc_auto_unsuspend_delay": None, - "sc_ttl": None, - "aggregation_mode": "", - "sc_dns_zone_aliases": "", - "sc_subnet": "", - "static_routes": None}}) - network_pool_module_mock.module.params = self.get_network_pool_args - network_pool_module_mock.get_network_pool = MagicMock(return_value=None) - network_pool_module_mock.network_groupnet_api.create_subnets_subnet_pool = MagicMock(side_effect=utils.ApiException) - network_pool_module_mock.perform_module_operation() - assert network_pool_module_mock.module.exit_json.call_args[1]['changed'] is False - - def test_delete_network_pool(self, network_pool_module_mock): + powerscale_module_mock.perform_module_operation() + assert powerscale_module_mock.module.exit_json.call_args[1]['changed'] is True + powerscale_module_mock.network_groupnet_api.create_subnets_subnet_pool.assert_called() + + def test_create_network_pool_with_exception(self, powerscale_module_mock): + self.common_create_pool_params(powerscale_module_mock) + powerscale_module_mock.network_groupnet_api.create_subnets_subnet_pool = MagicMock(side_effect=utils.ApiException) + self.capture_fail_json_call(MockNetworkPoolApi.create_networkpool_failed_msg( + MockNetworkPoolApi.GET_NETWORK_POOLS['pools'][0]['name']), + powerscale_module_mock, invoke_perform_module=True) + + def test_delete_network_pool(self, powerscale_module_mock): self.get_network_pool_args.update({"state": "absent"}) - network_pool_module_mock.module.params = self.get_network_pool_args - network_pool_module_mock.network_groupnet_api.get_subnets_subnet_pool = MagicMock( + powerscale_module_mock.module.params = self.get_network_pool_args + powerscale_module_mock.network_groupnet_api.get_subnets_subnet_pool = MagicMock( return_value=MockSDKResponse(MockNetworkPoolApi.GET_NETWORK_POOLS)) - network_pool_module_mock.network_groupnet_api.delete_subnets_subnet_pool = MagicMock(return_value=None) - network_pool_module_mock.perform_module_operation() - assert (network_pool_module_mock.module.exit_json.call_args[1]['changed']) + powerscale_module_mock.network_groupnet_api.delete_subnets_subnet_pool = MagicMock(return_value=None) + powerscale_module_mock.perform_module_operation() + assert (powerscale_module_mock.module.exit_json.call_args[1]['changed']) + powerscale_module_mock.network_groupnet_api.delete_subnets_subnet_pool.assert_called() - def test_delete_network_pool_with_exception(self, network_pool_module_mock): + def test_delete_network_pool_with_exception(self, powerscale_module_mock): self.get_network_pool_args.update({"state": "absent"}) - network_pool_module_mock.module.params = self.get_network_pool_args - network_pool_module_mock.network_groupnet_api.get_subnets_subnet_pool = MagicMock( + powerscale_module_mock.module.params = self.get_network_pool_args + powerscale_module_mock.network_groupnet_api.get_subnets_subnet_pool = MagicMock( return_value=MockSDKResponse(MockNetworkPoolApi.GET_NETWORK_POOLS)) - network_pool_module_mock.network_groupnet_api.delete_subnets_subnet_pool = MagicMock(side_effect=utils.ApiException) - network_pool_module_mock.perform_module_operation() - assert MockNetworkPoolApi.delete_networkpool_failed_msg(MockNetworkPoolApi.GET_NETWORK_POOLS['pools'][0]['name']) in \ - network_pool_module_mock.module.fail_json.call_args[1]['msg'] + powerscale_module_mock.network_groupnet_api.delete_subnets_subnet_pool = MagicMock(side_effect=utils.ApiException) + self.capture_fail_json_call(MockNetworkPoolApi.delete_networkpool_failed_msg( + MockNetworkPoolApi.GET_NETWORK_POOLS['pools'][0]['name']), + powerscale_module_mock, invoke_perform_module=True) - def test_modify_network_pool(self, network_pool_module_mock): + def common_modify_pool_params(self, powerscale_module_mock): self.get_network_pool_args.update({"state": "present", - "description": "Test_pool1", - "additional_pool_params": {"ranges": [{"low": "1.*.*.*", - "high": "1.*.*.*"}], - "range_state": "add", - "ifaces": [{"iface": "ext-1", - "lnn": 4}], + "description": "Test_pool2", + "new_pool_name": "Test_pool2", + "additional_pool_params": {"ranges": [{"low": MockNetworkPoolApi.RANGE1, + "high": MockNetworkPoolApi.RANGE1}], + "range_state": "remove", + "ifaces": [{"iface": "ext-2", + "lnn": 5}], "iface_state": "add"}, - "sc_params": {"sc_dns_zone": "1.*.*.*", - "sc_connect_policy": "throughput", - "sc_failover_policy": "throughput", - "rebalance_policy": "auto", - "alloc_method": "dynamic", - "sc_auto_unsuspend_delay": 200, - "sc_ttl": 300, - "aggregation_mode": "lacp", - "sc_dns_zone_aliases": ["smartconn-zone"], - "sc_subnet": "subnet_test", - "static_routes": [{"gateway": "1.*.*.*", + "sc_params": {"sc_dns_zone": MockNetworkPoolApi.RANGE2, + "sc_connect_policy": "round_robin", + "sc_failover_policy": "round_robin", + "rebalance_policy": "manual", + "alloc_method": "static", + "sc_auto_unsuspend_delay": 100, + "sc_ttl": 200, + "aggregation_mode": "failover", + "sc_dns_zone_aliases": ["smartconn-zone2"], + "sc_subnet": "subnet_test2", + "static_routes": [{"gateway": MockNetworkPoolApi.RANGE1, "prefix_len": 4, - "subnet": "1.*.*.*"}]}}) - network_pool_module_mock.module.params = self.get_network_pool_args - network_pool_module_mock.network_groupnet_api.get_subnets_subnet_pool = MagicMock( + "subnet": MockNetworkPoolApi.RANGE1, + "route_state": "remove"}, + {"gateway": MockNetworkPoolApi.RANGE2, + "prefix_len": 2, + "subnet": MockNetworkPoolApi.RANGE2, + "route_state": "add"}]}}) + powerscale_module_mock.module.params = self.get_network_pool_args + powerscale_module_mock.network_groupnet_api.get_subnets_subnet_pool = MagicMock( return_value=MockSDKResponse(MockNetworkPoolApi.CREATE_NETWORK_POOL)) - network_pool_module_mock.network_groupnet_api.update_subnets_subnet_pool = MagicMock(return_value=None) - network_pool_module_mock.perform_module_operation() - assert (network_pool_module_mock.module.exit_json.call_args[1]['changed']) - def test_modify_network_pool_with_exception(self, network_pool_module_mock): + def test_modify_network_pool(self, powerscale_module_mock): + self.common_modify_pool_params(powerscale_module_mock) + powerscale_module_mock.network_groupnet_api.update_subnets_subnet_pool = MagicMock(return_value=None) + powerscale_module_mock.perform_module_operation() + assert (powerscale_module_mock.module.exit_json.call_args[1]['changed']) + powerscale_module_mock.network_groupnet_api.update_subnets_subnet_pool.assert_called() + + def test_modify_network_pool_with_exception(self, powerscale_module_mock): + self.common_modify_pool_params(powerscale_module_mock) + powerscale_module_mock.network_groupnet_api.update_subnets_subnet_pool = MagicMock(side_effect=utils.ApiException) + self.capture_fail_json_call(MockNetworkPoolApi.modify_networkpool_failed_msg( + MockNetworkPoolApi.GET_NETWORK_POOLS['pools'][0]['name']), + powerscale_module_mock, invoke_perform_module=True) + + def test_create_network_pool_with_blank_name(self, powerscale_module_mock): self.get_network_pool_args.update({"state": "present", - "description": "Test_pool1", - "additional_pool_params": {"ranges": [{"low": "1.*.*.*", - "high": "1.*.*.*"}], - "range_state": "add", - "ifaces": [{"iface": "ext-1", - "lnn": 4}], - "iface_state": "add"}, - "sc_params": {"sc_dns_zone": "1.*.*.*", - "sc_connect_policy": "throughput", - "sc_failover_policy": "throughput", - "rebalance_policy": "auto", - "alloc_method": "dynamic", - "sc_auto_unsuspend_delay": 200, - "sc_ttl": 300, - "aggregation_mode": "lacp", - "sc_dns_zone_aliases": ["smartconn-zone"], - "sc_subnet": "subnet_test", - "static_routes": [{"gateway": "1.*.*.*", + "pool_name": ""}) + powerscale_module_mock.module.params = self.get_network_pool_args + powerscale_module_mock.network_groupnet_api.get_subnets_subnet_pool = MagicMock( + return_value=MockSDKResponse(MockNetworkPoolApi.GET_NETWORK_POOLS)) + self.capture_fail_json_call(MockNetworkPoolApi.network_pool_failed_msg('invalid_pool_name'), + powerscale_module_mock, invoke_perform_module=True) + + def test_modify_description_morethan_128(self, powerscale_module_mock): + self.get_network_pool_args.update({"state": "present", + "description": "a" * 129}) + powerscale_module_mock.module.params = self.get_network_pool_args + powerscale_module_mock.network_groupnet_api.get_subnets_subnet_pool = MagicMock( + return_value=MockSDKResponse(MockNetworkPoolApi.GET_NETWORK_POOLS)) + self.capture_fail_json_call(MockNetworkPoolApi.network_pool_failed_msg('invalid_pool_description'), + powerscale_module_mock, invoke_perform_module=True) + + def test_modify_invalid_ip_range(self, powerscale_module_mock): + self.get_network_pool_args.update({"state": "present", + "additional_pool_params": {"ranges": [{"low": "x.x.x.1", + "high": "x.x.x.*"}], + "range_state": "add"}}) + powerscale_module_mock.module.params = self.get_network_pool_args + powerscale_module_mock.network_groupnet_api.get_subnets_subnet_pool = MagicMock( + return_value=MockSDKResponse(MockNetworkPoolApi.GET_NETWORK_POOLS)) + self.capture_fail_json_call(MockNetworkPoolApi.network_pool_failed_msg('invalid_ip_range'), + powerscale_module_mock, invoke_perform_module=True) + + def test_modify_invalid_iface(self, powerscale_module_mock): + self.get_network_pool_args.update({"state": "present", + "additional_pool_params": {"ifaces": [{"iface": "", + "lnn": ""}], + "iface_state": "add"}}) + powerscale_module_mock.module.params = self.get_network_pool_args + powerscale_module_mock.network_groupnet_api.get_subnets_subnet_pool = MagicMock( + return_value=MockSDKResponse(MockNetworkPoolApi.GET_NETWORK_POOLS)) + self.capture_fail_json_call(MockNetworkPoolApi.network_pool_failed_msg('invalid_iface'), + powerscale_module_mock, invoke_perform_module=True) + + def test_modify_invalid_route(self, powerscale_module_mock): + self.get_network_pool_args.update({"state": "present", + "sc_params": {"static_routes": [{"gateway": "", "prefix_len": 4, - "subnet": "1.*.*.*"}]}}) - network_pool_module_mock.module.params = self.get_network_pool_args - network_pool_module_mock.network_groupnet_api.get_subnets_subnet_pool = MagicMock( - return_value=MockSDKResponse(MockNetworkPoolApi.CREATE_NETWORK_POOL)) - network_pool_module_mock.network_groupnet_api.update_subnets_subnet_pool = MagicMock(side_effect=utils.ApiException) - network_pool_module_mock.perform_module_operation() - assert MockNetworkPoolApi.modify_networkpool_failed_msg(MockNetworkPoolApi.GET_NETWORK_POOLS['pools'][0]['name']) in \ - network_pool_module_mock.module.fail_json.call_args[1]['msg'] + "subnet": "", + "route_state": "remove"}]}}) + powerscale_module_mock.module.params = self.get_network_pool_args + powerscale_module_mock.network_groupnet_api.get_subnets_subnet_pool = MagicMock( + return_value=MockSDKResponse(MockNetworkPoolApi.GET_NETWORK_POOLS)) + self.capture_fail_json_call(MockNetworkPoolApi.network_pool_failed_msg('invalid_route'), + powerscale_module_mock, invoke_perform_module=True) diff --git a/tests/unit/plugins/modules/test_settings.py b/tests/unit/plugins/modules/test_settings.py index e66124a6..ed55842a 100644 --- a/tests/unit/plugins/modules/test_settings.py +++ b/tests/unit/plugins/modules/test_settings.py @@ -10,142 +10,191 @@ import pytest from mock.mock import MagicMock +# pylint: disable=unused-import from ansible_collections.dellemc.powerscale.tests.unit.plugins.module_utils.shared_library.initial_mock \ import utils -from ansible_collections.dellemc.powerscale.plugins.modules.settings import Settings -from ansible_collections.dellemc.powerscale.tests.unit.plugins.\ - module_utils import mock_settings_api as MockSettingApi from ansible_collections.dellemc.powerscale.tests.unit.plugins.module_utils.mock_sdk_response \ import MockSDKResponse from ansible_collections.dellemc.powerscale.tests.unit.plugins.module_utils.mock_api_exception \ import MockApiException +from ansible_collections.dellemc.powerscale.tests.unit.plugins.module_utils.shared_library.powerscale_unit_base \ + import PowerScaleUnitBase +from ansible_collections.dellemc.powerscale.plugins.modules.settings import Settings +from ansible_collections.dellemc.powerscale.plugins.modules.settings import SettingsHandler +from ansible_collections.dellemc.powerscale.tests.unit.plugins.module_utils.mock_settings_api \ + import MockSettingsApi -class TestSettings(): - get_settings_args = {"mail_relay": None, - "mail_sender": None, - "mail_subject": None, - "state": None, - "email_settings": None, - "ntp_servers": None, - "ntp_server_id": None} +class TestSettings(PowerScaleUnitBase): + settings_args = MockSettingsApi.SETTINGS_COMMON_ARGS @pytest.fixture - def setting_module_mock(self, mocker): - mocker.patch(MockSettingApi.MODULE_UTILS_PATH + '.ApiException', new=MockApiException) - setting_module_mock = Settings() - setting_module_mock.module = MagicMock() - return setting_module_mock - - def test_get_email_settings(self, setting_module_mock): - setting_params = MockSettingApi.GET_SETTINGS - self.get_settings_args.update({"state": "present", "email_settings": True}) - setting_module_mock.module.params = self.get_settings_args - setting_module_mock.cluster_api.get_cluster_email = MagicMock( - return_value=MockSDKResponse(MockSettingApi.GET_SETTINGS)) - setting_module_mock.perform_module_operation() - assert setting_module_mock.module.exit_json.call_args[1]['changed'] is False - assert setting_params == setting_module_mock.module.exit_json.call_args[1]['email_settings'] - - def test_get_email_settings_with_exception(self, setting_module_mock): - MockSettingApi.GET_SETTINGS - self.get_settings_args.update({"state": "present", "email_settings": True}) - setting_module_mock.module.params = self.get_settings_args - setting_module_mock.cluster_api.get_email_settings = MagicMock(return_value=None) - setting_module_mock.cluster_api.get_cluster_email = MagicMock(side_effect=utils.ApiException) - setting_module_mock.perform_module_operation() - assert MockSettingApi.get_email_setting_failed_msg() in \ - setting_module_mock.module.fail_json.call_args[1]['msg'] - - def test_update_email_settings(self, setting_module_mock): - self.get_settings_args.update({"mail_relay": "mailrelaymod.itp.xyz.net", "mail_sender": "lab-a2_mod@dell.com", - "mail_subject": "lab_mod-alerts", "state": "present", "email_settings": False}) - setting_module_mock.module.params = self.get_settings_args - setting_module_mock.cluster_api.update_cluster_email = MagicMock( - return_value=MockSDKResponse(MockSettingApi.SETTINGS['email_settings_mod'][0])) - setting_module_mock.perform_module_operation() - assert setting_module_mock.module.exit_json.call_args[1]['changed'] is True - assert setting_module_mock.module.exit_json.call_args[1]['email_settings'] - - def test_update_email_settings_with_some_params(self, setting_module_mock): - self.get_settings_args.update({"mail_relay": "mailrelaymod.itp.xyz.net", "mail_sender": "lab-a2_mod@dell.com", - "state": "present", "email_settings": False}) - setting_module_mock.module.params = self.get_settings_args - setting_module_mock.cluster_api.update_cluster_email = MagicMock( - return_value=MockSDKResponse(MockSettingApi.SETTINGS['email_settings_mod'][0])) - setting_module_mock.perform_module_operation() - assert setting_module_mock.module.exit_json.call_args[1]['changed'] is True - - def test_update_email_settings_with_exception(self, setting_module_mock): - self.get_settings_args.update({"mail_relay": "mailrelaymod.itp.xyz.net", "mail_sender": "lab-a2_mod@dell.com", - "mail_subject": "lab_mod-alerts", "state": "present", "email_settings": False}) - setting_module_mock.module.params = self.get_settings_args - setting_module_mock.cluster_api.update_cluster_email = MagicMock(side_effect=utils.ApiException) - setting_module_mock.perform_module_operation() - assert MockSettingApi.update_email_setting_failed_msg() in \ - setting_module_mock.module.fail_json.call_args[1]['msg'] - - def test_get_ntp_server(self, setting_module_mock): - ntp_details = MockSettingApi.SETTINGS['NTP_server'][0] - self.get_settings_args.update({"state": "present", "email_settings": False, "ntp_server_id": "1.1.1.1"}) - setting_module_mock.module.params = self.get_settings_args - setting_module_mock.protocol_api.get_ntp_server = MagicMock( - return_value=MockSDKResponse(MockSettingApi.SETTINGS['NTP_server'][0])) - setting_module_mock.perform_module_operation() - assert setting_module_mock.module.exit_json.call_args[1]['changed'] is False - assert ntp_details == setting_module_mock.module.exit_json.call_args[1]['ntp_server'] - - def test_delete_ntp_server(self, setting_module_mock): - self.get_settings_args.update({"state": "absent", "email_settings": False, "ntp_servers": ['1.1.1.1']}) - setting_module_mock.module.params = self.get_settings_args - setting_module_mock.protocol_api.list_ntp_servers = MagicMock( - return_value=MockSDKResponse(MockSettingApi.SETTINGS['NTP_servers'][0])) - setting_module_mock.protocol_api.delete_ntp_server = MagicMock( - return_value=MockSDKResponse(MockSettingApi.SETTINGS['NTP_server'][0])) - setting_module_mock.perform_module_operation() - assert setting_module_mock.module.exit_json.call_args[1]['changed'] is True - - def test_add_ntp_server(self, setting_module_mock): - self.get_settings_args.update({"mail_relay": "mailrelaymod.itp.xyz.net", "mail_sender": "lab-a2_mod@dell.com", - "mail_subject": "lab_mod-alerts", "state": "present", "email_settings": False, - "ntp_servers": ['1.1.1.1', '2.2.2.2']}) - setting_module_mock.module.params = self.get_settings_args - setting_module_mock.protocol_api.list_ntp_servers = MagicMock( - return_value=MockSDKResponse(MockSettingApi.SETTINGS['NTP_servers'][0])) - setting_module_mock.protocol_api.create_ntp_server = MagicMock( - return_value=MockSDKResponse(MockSettingApi.SETTINGS['NTP_server'][0])) - setting_module_mock.perform_module_operation() - assert setting_module_mock.module.exit_json.call_args[1]['changed'] is True - - def test_get_ntp_server_with_invalid_value(self, setting_module_mock): - server = "asdasdasd" - self.get_settings_args.update({"state": "present", "email_settings": False, "ntp_server_id": server}) - setting_module_mock.module.params = self.get_settings_args - setting_module_mock.protocol_api.get_ntp_server = MagicMock(side_effect=utils.ApiException) - setting_module_mock.perform_module_operation() - assert MockSettingApi.get_ntp_server_failed_msg(server) in \ - setting_module_mock.module.fail_json.call_args[1]['msg'] - - def test_add_ntp_server_with_blank_value(self, setting_module_mock): - server_list = ['1.1.1.1', '2.2.2.2'] - self.get_settings_args.update({"state": "present", "email_settings": False, "ntp_servers": server_list}) - setting_module_mock.module.params = self.get_settings_args - setting_module_mock.protocol_api.list_ntp_servers = MagicMock( - return_value=MockSDKResponse(MockSettingApi.SETTINGS['NTP_servers'][0])) - setting_module_mock.protocol_api.create_ntp_server = MagicMock(side_effect=utils.ApiException) - setting_module_mock.perform_module_operation() - assert MockSettingApi.add_blank_ntp_server_msg(server_list) in \ - setting_module_mock.module.fail_json.call_args[1]['msg'] - - def test_delete_ntp_server_with_invalid_value(self, setting_module_mock): - server = ["1.1.1.1"] - self.get_settings_args.update({"state": "absent", "email_settings": False, "ntp_servers": server}) - setting_module_mock.module.params = self.get_settings_args - setting_module_mock.protocol_api.list_ntp_servers = MagicMock( - return_value=MockSDKResponse(MockSettingApi.SETTINGS['NTP_servers'][0])) - setting_module_mock.protocol_api.delete_ntp_server = MagicMock(side_effect=utils.ApiException) - setting_module_mock.perform_module_operation() - assert MockSettingApi.delete_ntp_server_failed_msg(server) in \ - setting_module_mock.module.fail_json.call_args[1]['msg'] + def module_object(self): + return Settings + + def test_get_email_settings(self, powerscale_module_mock): + self.set_module_params(powerscale_module_mock, self.settings_args, {}) + powerscale_module_mock.get_email_settings = MagicMock(return_value=MockSettingsApi.GET_SETTINGS) + SettingsHandler().handle(powerscale_module_mock, powerscale_module_mock.module.params) + powerscale_module_mock.get_email_settings.assert_called() + + def test_get_ntp_servers(self, powerscale_module_mock): + self.set_module_params(powerscale_module_mock, self.settings_args, {}) + powerscale_module_mock.get_ntp_servers = MagicMock(return_value=MockSettingsApi.SETTINGS['NTP_servers'][0]) + SettingsHandler().handle(powerscale_module_mock, powerscale_module_mock.module.params) + powerscale_module_mock.get_ntp_servers.assert_called() + + def test_get_cluster_identity_settings(self, powerscale_module_mock): + self.set_module_params(powerscale_module_mock, self.settings_args, {}) + powerscale_module_mock.get_cluster_identity_details = MagicMock(return_value=MockSettingsApi.SETTINGS['cluster_identity']) + SettingsHandler().handle(powerscale_module_mock, powerscale_module_mock.module.params) + powerscale_module_mock.get_cluster_identity_details.assert_called() + + def test_get_cluster_owner_details(self, powerscale_module_mock): + self.set_module_params(powerscale_module_mock, self.settings_args, {}) + powerscale_module_mock.get_cluster_owner_details = MagicMock(return_value=MockSettingsApi.SETTINGS['cluster_owner']) + SettingsHandler().handle(powerscale_module_mock, powerscale_module_mock.module.params) + powerscale_module_mock.get_cluster_owner_details.assert_called() + + def test_update_email_settings(self, powerscale_module_mock): + self.set_module_params(powerscale_module_mock, self.settings_args, + {"mail_relay": "mailrelaymod.itp.xyz.net", + "mail_sender": "lab-a2_mod@dell.com", + "mail_subject": "lab_mod-alerts"}) + powerscale_module_mock.get_email_settings = MagicMock(return_value=MockSettingsApi.GET_SETTINGS) + powerscale_module_mock.update_cluster_email = MagicMock(return_value=True) + SettingsHandler().handle(powerscale_module_mock, powerscale_module_mock.module.params) + assert powerscale_module_mock.module.exit_json.call_args[1]['changed'] is True + + def test_update_email_settings_exception(self, powerscale_module_mock): + self.set_module_params(powerscale_module_mock, self.settings_args, + {"mail_relay": "mailrelaymod.itp.xyz.net", + "mail_sender": "lab-a2_mod@dell.com", + "mail_subject": "lab_mod-alerts"}) + powerscale_module_mock.get_email_settings = MagicMock(return_value=MockSettingsApi.GET_SETTINGS) + powerscale_module_mock.cluster_api.update_cluster_email = MagicMock(side_effect=MockApiException) + self.capture_fail_json_call( + MockSettingsApi.get_settings_exception_response('update_email_exception'), + powerscale_module_mock, SettingsHandler) + + def test_update_cluster_identity_settings(self, powerscale_module_mock): + self.set_module_params(powerscale_module_mock, self.settings_args, + {"description": "This is a new description", + "logon": {"motd": "This is a new description", + "motd_header": "This is the a new title"}, + "mttdl_level_msg": "none", + "name": "PIE-IsilonS-24241-Cluster"}) + powerscale_module_mock.get_cluster_identity_details = MagicMock(return_value=MockSettingsApi.SETTINGS['cluster_identity']) + powerscale_module_mock.update_cluster_identity = MagicMock(return_value=True) + SettingsHandler().handle(powerscale_module_mock, powerscale_module_mock.module.params) + assert powerscale_module_mock.module.exit_json.call_args[1]['changed'] is True + + def test_update_cluster_identity_exception(self, powerscale_module_mock): + self.set_module_params(powerscale_module_mock, self.settings_args, + {"description": "This is a test description", + "logon": {"motd": "This is a test description", + "motd_header": "This is a test title"}, + "mttdl_level_msg": "none", + "name": "PIE-IsilonS-24241-Cluster"}) + powerscale_module_mock.get_cluster_identity_details = MagicMock(return_value=MockSettingsApi.SETTINGS['cluster_identity']) + powerscale_module_mock.cluster_api.update_cluster_identity = MagicMock(side_effect=MockApiException) + self.capture_fail_json_call( + MockSettingsApi.get_settings_exception_response('update_cluster_identity'), + powerscale_module_mock, SettingsHandler) + + def test_update_cluster_owner_settings(self, powerscale_module_mock): + self.set_module_params(powerscale_module_mock, self.settings_args, + {"company": "Test company 1", + "location": "Test location 1", + "primary_email": "primary_email1@email.com", + "primary_name": "primary_name1", + "primary_phone1": "primary_phone11", + "primary_phone2": "primary_phone21", + "secondary_email": "secondary_email1@email.com", + "secondary_name": "secondary_name1", + "secondary_phone1": "secondary_phone11", + "secondary_phone2": "secondary_phone21"}) + powerscale_module_mock.get_cluster_owner_details = MagicMock(return_value=MockSettingsApi.SETTINGS['cluster_owner']) + powerscale_module_mock.update_owner_identity = MagicMock(return_value=True) + SettingsHandler().handle(powerscale_module_mock, powerscale_module_mock.module.params) + assert powerscale_module_mock.module.exit_json.call_args[1]['changed'] is True + + def test_update_cluster_owner_exception(self, powerscale_module_mock): + self.set_module_params(powerscale_module_mock, self.settings_args, + {"company": "Test company 1", + "location": "Test location 1", + "primary_email": "primary_email1@email.com", + "primary_name": "primary_name1", + "primary_phone1": "primary_phone11", + "primary_phone2": "primary_phone21", + "secondary_email": "secondary_email1@email.com", + "secondary_name": "secondary_name1", + "secondary_phone1": "secondary_phone11", + "secondary_phone2": "secondary_phone21"}) + powerscale_module_mock.get_cluster_owner_details = MagicMock(return_value=MockSettingsApi.SETTINGS['cluster_owner']) + powerscale_module_mock.cluster_api.update_cluster_owner = MagicMock(side_effect=MockApiException) + self.capture_fail_json_call( + MockSettingsApi.get_settings_exception_response('update_cluster_owner'), + powerscale_module_mock, SettingsHandler) + + def test_delete_ntp_server(self, powerscale_module_mock): + self.set_module_params(powerscale_module_mock, self.settings_args, {"state": "absent", "ntp_servers": [MockSettingsApi.IP_ADDRESS]}) + powerscale_module_mock.get_ntp_servers = MagicMock(return_value=MockSettingsApi.SETTINGS['NTP_servers'][0]) + powerscale_module_mock.protocol_api.delete_ntp_server = MagicMock(return_value=True) + SettingsHandler().handle(powerscale_module_mock, powerscale_module_mock.module.params) + assert powerscale_module_mock.module.exit_json.call_args[1]['changed'] is True + + def test_add_ntp_server(self, powerscale_module_mock): + self.set_module_params(powerscale_module_mock, self.settings_args, {"ntp_servers": [MockSettingsApi.IP_ADDRESS]}) + powerscale_module_mock.get_ntp_servers = MagicMock(return_value=MockSettingsApi.SETTINGS['NTP_servers'][0]) + powerscale_module_mock.protocol_api.create_ntp_server = MagicMock(return_value=(MockSettingsApi.SETTINGS['NTP_server'][0])) + SettingsHandler().handle(powerscale_module_mock, powerscale_module_mock.module.params) + assert powerscale_module_mock.module.exit_json.call_args[1]['changed'] is True + + def test_add_ntp_server_with_invalid_value(self, powerscale_module_mock): + self.set_module_params(powerscale_module_mock, self.settings_args, {"ntp_servers": [MockSettingsApi.IP_ADDRESS]}) + powerscale_module_mock.get_ntp_servers = MagicMock(return_value=MockSettingsApi.SETTINGS['NTP_servers'][0]) + powerscale_module_mock.protocol_api.create_ntp_server = MagicMock(side_effect=MockApiException) + self.capture_fail_json_call( + MockSettingsApi.get_settings_exception_response('adding_invalid_server'), + powerscale_module_mock, SettingsHandler) + + def test_delete_ntp_server_with_invalid_value(self, powerscale_module_mock): + self.set_module_params(powerscale_module_mock, self.settings_args, {"state": "absent", "ntp_servers": [MockSettingsApi.IP_ADDRESS]}) + powerscale_module_mock.get_ntp_servers = MagicMock(return_value=MockSettingsApi.SETTINGS['NTP_servers'][0]) + powerscale_module_mock.protocol_api.delete_ntp_server = MagicMock(side_effect=MockApiException) + self.capture_fail_json_call( + MockSettingsApi.get_settings_exception_response('removing_invalid_server'), + powerscale_module_mock, SettingsHandler) + + def test_add_ntp_server_with_blank_value(self, powerscale_module_mock): + self.set_module_params(powerscale_module_mock, self.settings_args, {"ntp_servers": ['']}) + self.capture_fail_json_call( + MockSettingsApi.get_settings_exception_response('invalid_NTP_value'), + powerscale_module_mock, SettingsHandler) + + def test_cluster_name_invalid_length(self, powerscale_module_mock): + self.set_module_params(powerscale_module_mock, self.settings_args, {"name": 'PIE-IsilonS-24241-ClusterPIE-IsilonS-242PIE-IsilonS-24241-' + 'ClusterPIE-IsilonS-242PIE-IsilonS-24241-ClusterPIE-IsilonS-' + '242PIE-IsilonS-24241'}) + self.capture_fail_json_call( + MockSettingsApi.get_settings_exception_response('update_invalid_cluster_name'), + powerscale_module_mock, SettingsHandler) + + def test_cluster_description_invalid_length(self, powerscale_module_mock): + self.set_module_params(powerscale_module_mock, self.settings_args, {"description": 'PIE-IsilonS-24241-ClusterPIE-IsilonS-242PIE-IsilonS-' + '24241-ClusterPIE-IsilonS-242PIE-IsilonS-24241-Cluster' + 'PIE-IsilonS-242PIE-IsilonS-24241-ClusterPIE-IsilonS-24' + '2PIE-IsilonS-24241-ClusterPIE-IsilonS-242 PIE-IsilonS-' + '24241-ClusterPIE-IsilonS-242PIE-IsilonS-242sdfsdfsfsdf'}) + self.capture_fail_json_call( + MockSettingsApi.get_settings_exception_response('update_invalid_cluster_description'), + powerscale_module_mock, SettingsHandler) + + def test_cluster_owner_invalid_email(self, powerscale_module_mock): + self.set_module_params(powerscale_module_mock, self.settings_args, {"mail_sender": "primary_email123"}) + self.capture_fail_json_call( + MockSettingsApi.get_settings_exception_response('update_invalid_mail_sender_email'), + powerscale_module_mock, SettingsHandler) diff --git a/tests/unit/plugins/modules/test_smb_global_settings.py b/tests/unit/plugins/modules/test_smb_global_settings.py new file mode 100644 index 00000000..21294e16 --- /dev/null +++ b/tests/unit/plugins/modules/test_smb_global_settings.py @@ -0,0 +1,108 @@ +# Copyright: (c) 2023, Dell Technologies + +# Apache License version 2.0 (see MODULE-LICENSE or http://www.apache.org/licenses/LICENSE-2.0.txt) + +"""Unit Tests for SMB global settings module on PowerScale""" + +from __future__ import (absolute_import, division, print_function) + +__metaclass__ = type + +import pytest +from mock.mock import MagicMock +# pylint: disable=unused-import +from ansible_collections.dellemc.powerscale.tests.unit.plugins.module_utils.shared_library.initial_mock \ + import utils +from ansible_collections.dellemc.powerscale.plugins.modules.smb_global_settings import SMBGlobalSettings +from ansible_collections.dellemc.powerscale.plugins.modules.smb_global_settings import SMBGlobalSettingsHandler +from ansible_collections.dellemc.powerscale.plugins.modules.smb_global_settings import main +from ansible_collections.dellemc.powerscale.tests.unit.plugins.module_utils.mock_smb_global_settings_api \ + import MockSMBGlobalSettingsApi +from ansible_collections.dellemc.powerscale.tests.unit.plugins.module_utils.mock_api_exception \ + import MockApiException +from ansible_collections.dellemc.powerscale.tests.unit.plugins.module_utils.shared_library.powerscale_unit_base \ + import PowerScaleUnitBase +from ansible_collections.dellemc.powerscale.tests.unit.plugins.module_utils.mock_sdk_response \ + import MockSDKResponse + + +class TestSMBGlobalSettings(PowerScaleUnitBase): + smb_global_args = MockSMBGlobalSettingsApi.SMB_GLOBAL_COMMON_ARGS + + @pytest.fixture + def module_object(self): + return SMBGlobalSettings + + def test_get_smb_global_details(self, powerscale_module_mock): + self.set_module_params(powerscale_module_mock, + self.smb_global_args, {}) + SMBGlobalSettingsHandler().handle(powerscale_module_mock, + powerscale_module_mock.module.params) + powerscale_module_mock.protocol_api.get_smb_settings_global.assert_called() + + def test_get_smb_global_details_empty(self, powerscale_module_mock): + self.set_module_params(powerscale_module_mock, + self.smb_global_args, {}) + powerscale_module_mock.protocol_api.get_smb_settings_global.return_value = MockSDKResponse(None) + SMBGlobalSettingsHandler().handle(powerscale_module_mock, + powerscale_module_mock.module.params) + assert powerscale_module_mock.module.exit_json.call_args[1]['changed'] is False + assert powerscale_module_mock.module.exit_json.call_args[ + 1]['smb_global_settings_details'] is None + + def test_get_smb_global_details_exception(self, powerscale_module_mock): + self.set_module_params(powerscale_module_mock, + self.smb_global_args, {}) + powerscale_module_mock.protocol_api.get_smb_settings_global = MagicMock( + side_effect=MockApiException) + self.capture_fail_json_call( + MockSMBGlobalSettingsApi.get_smb_global_settings_exception_response('get_details_exception'), + powerscale_module_mock, SMBGlobalSettingsHandler) + + @pytest.mark.parametrize("service_val", [True, False]) + def test_modify_smb_global_response(self, powerscale_module_mock, service_val): + self.smb_global_args.update({ + "service": service_val + }) + self.set_module_params(powerscale_module_mock, + self.smb_global_args, {}) + powerscale_module_mock.get_smb_global_settings_details = MagicMock( + return_value=MockSMBGlobalSettingsApi.GET_SMB_GLOBAL_RESPONSE) + SMBGlobalSettingsHandler().handle(powerscale_module_mock, + powerscale_module_mock.module.params) + assert powerscale_module_mock.module.exit_json.call_args[1]['changed'] is service_val + if service_val: + powerscale_module_mock.protocol_api.update_smb_settings_global.assert_called() + + def test_modify_smb_global_response_check_mode(self, powerscale_module_mock): + self.smb_global_args.update({ + "service": True + }) + powerscale_module_mock.module.check_mode = True + self.set_module_params(powerscale_module_mock, + self.smb_global_args, {}) + powerscale_module_mock.get_smb_global_settings_details = MagicMock( + return_value=MockSMBGlobalSettingsApi.GET_SMB_GLOBAL_RESPONSE) + SMBGlobalSettingsHandler().handle(powerscale_module_mock, + powerscale_module_mock.module.params) + assert powerscale_module_mock.module.exit_json.call_args[1]['changed'] is True + + def test_modify_smb_global_exception(self, powerscale_module_mock): + self.smb_global_args.update({"service": True}) + self.set_module_params(powerscale_module_mock, + self.smb_global_args, {}) + powerscale_module_mock.get_smb_global_settings_details = MagicMock( + return_value=MockSMBGlobalSettingsApi.GET_SMB_GLOBAL_RESPONSE) + powerscale_module_mock.protocol_api.update_smb_settings_global = MagicMock( + side_effect=MockApiException) + self.capture_fail_json_call( + MockSMBGlobalSettingsApi.get_smb_global_settings_exception_response('update_exception'), + powerscale_module_mock, SMBGlobalSettingsHandler) + + def test_main(self, powerscale_module_mock): + self.set_module_params(powerscale_module_mock, + self.smb_global_args, {}) + powerscale_module_mock.get_smb_global_settings_details = MagicMock( + return_value=MockSMBGlobalSettingsApi.GET_SMB_GLOBAL_RESPONSE) + main() + powerscale_module_mock.protocol_api.get_smb_settings_global.assert_called() diff --git a/tests/unit/plugins/modules/test_snmp_settings.py b/tests/unit/plugins/modules/test_snmp_settings.py new file mode 100644 index 00000000..b1c4e620 --- /dev/null +++ b/tests/unit/plugins/modules/test_snmp_settings.py @@ -0,0 +1,100 @@ +# Copyright: (c) 2023, Dell Technologies + +# Apache License version 2.0 (see MODULE-LICENSE or http://www.apache.org/licenses/LICENSE-2.0.txt) + +"""Unit Tests for user mapping rules module on PowerScale""" + +from __future__ import (absolute_import, division, print_function) + +__metaclass__ = type + +import pytest +from mock.mock import MagicMock +# pylint: disable=unused-import +from ansible_collections.dellemc.powerscale.tests.unit.plugins.module_utils.shared_library.initial_mock \ + import utils + +from ansible_collections.dellemc.powerscale.plugins.modules.snmp_settings import SNMPSettings +from ansible_collections.dellemc.powerscale.plugins.modules.snmp_settings import SNMPSettingsHandler +from ansible_collections.dellemc.powerscale.plugins.module_utils.storage.dell.shared_library.protocol \ + import Protocol +from ansible_collections.dellemc.powerscale.tests.unit.plugins.module_utils.mock_snmp_settings_api \ + import MockSNMPSettingsApi +from ansible_collections.dellemc.powerscale.tests.unit.plugins.module_utils.mock_api_exception \ + import MockApiException +from ansible_collections.dellemc.powerscale.tests.unit.plugins.module_utils.shared_library.powerscale_unit_base \ + import PowerScaleUnitBase + + +class TestSNMPSettings(PowerScaleUnitBase): + snmp_settings_args = MockSNMPSettingsApi.SNMP_SETTINGS_COMMON_ARGS + + @pytest.fixture + def module_object(self): + return SNMPSettings + + def test_get_snmp_settings(self, powerscale_module_mock): + self.set_module_params(powerscale_module_mock, + self.snmp_settings_args, {}) + SNMPSettingsHandler().handle(powerscale_module_mock, powerscale_module_mock.module.params) + powerscale_module_mock.protocol_api.get_snmp_settings.assert_called() + + def test_get_snmp_settings_exception(self, powerscale_module_mock): + self.set_module_params(powerscale_module_mock, + self.snmp_settings_args, {}) + powerscale_module_mock.protocol_api.get_snmp_settings = MagicMock(side_effect=MockApiException) + self.capture_fail_json_call( + MockSNMPSettingsApi.get_snmpsettings_exception_response('get_resp_exception'), + powerscale_module_mock, SNMPSettingsHandler) + + def test_modify_snmp_settings(self, powerscale_module_mock): + self.set_module_params(powerscale_module_mock, + self.snmp_settings_args, + {"system_contact": "contact@set.set", + "system_location": "location_set", + "snmp_v2c_access": False, + "snmp_v3": { + "access": False, + "auth_protocol": "MD5", + "privacy_password": "password", + "privacy_protocol": "DES", + "security_level": "noAuthNoPriv", + "read_only_user": "user", + "password": "password_set" + }, + "read_only_community": "community_set"}) + powerscale_module_mock.protocol_api.get_snmp_settings.to_dict = MagicMock( + return_value=MockSNMPSettingsApi.GET_SNMP_SETTINGS_RESPONSE) + SNMPSettingsHandler().handle(powerscale_module_mock, powerscale_module_mock.module.params) + assert powerscale_module_mock.module.exit_json.call_args[1]['changed'] is True + powerscale_module_mock.protocol_api.update_snmp_settings.assert_called() + + def test_modify_snmp_settings_exception(self, powerscale_module_mock): + self.set_module_params(powerscale_module_mock, + self.snmp_settings_args, + {"service": False}) + powerscale_module_mock.protocol_api.get_snmp_settings.to_dict = MagicMock( + return_value=MockSNMPSettingsApi.GET_SNMP_SETTINGS_RESPONSE) + powerscale_module_mock.protocol_api.update_snmp_settings = MagicMock( + side_effect=MockApiException) + self.capture_fail_json_call( + MockSNMPSettingsApi.get_snmpsettings_exception_response('update_exception'), + powerscale_module_mock, SNMPSettingsHandler) + + def test_validate_params_exception(self, powerscale_module_mock): + self.set_module_params(powerscale_module_mock, + self.snmp_settings_args, + {"system_contact": "set .set"}) + powerscale_module_mock.protocol_api.get_snmp_settings.to_dict = MagicMock( + return_value=MockSNMPSettingsApi.GET_SNMP_SETTINGS_RESPONSE) + self.capture_fail_json_call( + MockSNMPSettingsApi.get_snmpsettings_exception_response('system_contact_exception'), powerscale_module_mock, SNMPSettingsHandler) + + def test_valdiate_sysetm_location_exception(self, powerscale_module_mock): + self.set_module_params(powerscale_module_mock, + self.snmp_settings_args, + {"system_location": " "}) + powerscale_module_mock.protocol_api.get_snmp_settings.to_dict = MagicMock( + return_value=MockSNMPSettingsApi.GET_SNMP_SETTINGS_RESPONSE) + self.capture_fail_json_call( + MockSNMPSettingsApi.get_snmpsettings_exception_response('system_location_exception'), powerscale_module_mock, SNMPSettingsHandler)