diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index f783d12..f3093ce 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -15,4 +15,4 @@ # Trisha Datta (trisha-dell) # for all files: -* @kuttattz @Bhavneet-Sharma @Jennifer-John @meenakshidembi691 @Pavan-Mudunuri @trisha-dell +* @kuttattz @Bhavneet-Sharma @Jennifer-John @meenakshidembi691 @Pavan-Mudunuri @trisha-dell @felixs88 @sachin-apa diff --git a/.github/workflows/ansible-test.yml b/.github/workflows/ansible-test.yml index 988cba1..058c434 100644 --- a/.github/workflows/ansible-test.yml +++ b/.github/workflows/ansible-test.yml @@ -114,7 +114,7 @@ jobs: # Ansible-core 2.16 is supported only from Python 3.10 onwards - python-version: "3.9" ansible-version: stable-2.16 - - python-version: '3.9' + - python-version: "3.9" ansible-version: devel steps: diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 8e67089..6224280f 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -4,6 +4,28 @@ Dellemc.PowerFlex Change Logs .. contents:: Topics +v2.4.0 +====== + +Minor Changes +------------- + +- Added support for executing Ansible PowerFlex modules and roles on AWS environment. + +v2.3.0 +====== + +Minor Changes +------------- + +- Added support for PowerFlex ansible modules and roles on Azure. +- Added support for resource group provisioning to validate, deploy, edit, add nodes and delete a resource group. +- The Info module is enhanced to list the firmware repositories. + +New Modules +----------- + +- dellemc.powerflex.resource_group - Manage resource group deployments on Dell PowerFlex v2.2.0 ====== diff --git a/README.md b/README.md index a12d86b..6bb8ee2 100644 --- a/README.md +++ b/README.md @@ -6,29 +6,29 @@ The capabilities of the Ansible modules are managing SDCs, volumes, snapshots, s ## Table of contents -* [Code of conduct](https://github.com/dell/ansible-powerflex/blob/2.2.0/docs/CODE_OF_CONDUCT.md) -* [Maintainer guide](https://github.com/dell/ansible-powerflex/blob/2.2.0/docs/MAINTAINER_GUIDE.md) -* [Committer guide](https://github.com/dell/ansible-powerflex/blob/2.2.0/docs/COMMITTER_GUIDE.md) -* [Contributing guide](https://github.com/dell/ansible-powerflex/blob/2.2.0/docs/CONTRIBUTING.md) -* [Branching strategy](https://github.com/dell/ansible-powerflex/blob/2.2.0/docs/BRANCHING.md) -* [List of adopters](https://github.com/dell/ansible-powerflex/blob/2.2.0/docs/ADOPTERS.md) -* [Maintainers](https://github.com/dell/ansible-powerflex/blob/2.2.0/docs/MAINTAINERS.md) -* [Support](https://github.com/dell/ansible-powerflex/blob/2.2.0/docs/SUPPORT.md) +* [Code of conduct](https://github.com/dell/ansible-powerflex/blob/2.4.0/docs/CODE_OF_CONDUCT.md) +* [Maintainer guide](https://github.com/dell/ansible-powerflex/blob/2.4.0/docs/MAINTAINER_GUIDE.md) +* [Committer guide](https://github.com/dell/ansible-powerflex/blob/2.4.0/docs/COMMITTER_GUIDE.md) +* [Contributing guide](https://github.com/dell/ansible-powerflex/blob/2.4.0/docs/CONTRIBUTING.md) +* [Branching strategy](https://github.com/dell/ansible-powerflex/blob/2.4.0/docs/BRANCHING.md) +* [List of adopters](https://github.com/dell/ansible-powerflex/blob/2.4.0/docs/ADOPTERS.md) +* [Maintainers](https://github.com/dell/ansible-powerflex/blob/2.4.0/docs/MAINTAINERS.md) +* [Support](https://github.com/dell/ansible-powerflex/blob/2.4.0/docs/SUPPORT.md) * [License](#license) -* [Security](https://github.com/dell/ansible-powerflex/blob/2.2.0/docs/SECURITY.md) +* [Security](https://github.com/dell/ansible-powerflex/blob/2.4.0/docs/SECURITY.md) * [Prerequisites](#prerequisites) * [List of Ansible modules for Dell PowerFlex](#list-of-ansible-modules-for-dell-powerflex) * [Installation and execution of Ansible modules for Dell PowerFlex](#installation-and-execution-of-ansible-modules-for-dell-powerflex) * [Releasing, Maintenance and Deprecation](#releasing-maintenance-and-deprecation) ## License -The Ansible collection for PowerFlex is released and licensed under the GPL-3.0 license. See [LICENSE](https://github.com/dell/ansible-powerflex/blob/2.2.0/LICENSE) for the full terms. Ansible modules and modules utilities that are part of the Ansible collection for PowerFlex are released and licensed under the Apache 2.0 license. See [MODULE-LICENSE](https://github.com/dell/ansible-powerflex/blob/2.2.0/MODULE-LICENSE) for the full terms. +The Ansible collection for PowerFlex is released and licensed under the GPL-3.0 license. See [LICENSE](https://github.com/dell/ansible-powerflex/blob/2.4.0/LICENSE) for the full terms. Ansible modules and modules utilities that are part of the Ansible collection for PowerFlex are released and licensed under the Apache 2.0 license. See [MODULE-LICENSE](https://github.com/dell/ansible-powerflex/blob/2.4.0/MODULE-LICENSE) for the full terms. ## Prerequisites | **Ansible Modules** | **PowerFlex/VxFlex OS Version** | **SDK version** | **Python version** | **Ansible** | |---------------------|-----------------------|-------|--------------------|--------------------------| -| v2.2.0 |3.6
4.0
4.5 | 1.9.0 | 3.9.x
3.10.x
3.11.x | 2.14
2.15
2.16 | +| v2.4.0 |3.6
4.0
4.5 | 1.11.0 | 3.9.x
3.10.x
3.11.x | 2.14
2.15
2.16 | * Please follow PyPowerFlex installation instructions on [PyPowerFlex Documentation](https://github.com/dell/python-powerflex) @@ -36,22 +36,22 @@ The Ansible collection for PowerFlex is released and licensed under the GPL-3.0 The modules are written in such a way that all requests are idempotent and hence fault-tolerant. It essentially means that the result of a successfully performed request is independent of the number of times it is executed. ## List of Ansible modules for Dell PowerFlex - * [Info module](https://github.com/dell/ansible-powerflex/blob/2.2.0/docs/modules/info.rst) - * [Snapshot module](https://github.com/dell/ansible-powerflex/blob/2.2.0/docs/modules/snapshot.rst) - * [SDC module](https://github.com/dell/ansible-powerflex/blob/2.2.0/docs/modules/sdc.rst) - * [Storage pool module](https://github.com/dell/ansible-powerflex/blob/2.2.0/docs/modules/storagepool.rst) - * [Volume module](https://github.com/dell/ansible-powerflex/blob/2.2.0/docs/modules/volume.rst) - * [SDS module](https://github.com/dell/ansible-powerflex/blob/2.2.0/docs/modules/sds.rst) - * [Device Module](https://github.com/dell/ansible-powerflex/blob/2.2.0/docs/modules/device.rst) - * [Protection Domain Module](https://github.com/dell/ansible-powerflex/blob/2.2.0/docs/modules/protection_domain.rst) - * [MDM Cluster Module](https://github.com/dell/ansible-powerflex/blob/2.2.0/docs/modules/mdm_cluster.rst) - * [Replication Consistency Group Module](https://github.com/dell/ansible-powerflex/blob/2.2.0/docs/modules/replication_consistency_group.rst) - * [Replication Pair Module](https://github.com/dell/ansible-powerflex/blob/2.2.0/docs/modules/replication_pair.rst) - * [Snapshot Policy Module](https://github.com/dell/ansible-powerflex/blob/2.2.0/docs/modules/snapshot_policy.rst) - * [Fault Sets Module](https://github.com/dell/ansible-powerflex/blob/2.2.0/docs/modules/fault_set.rst) + * [Info module](https://github.com/dell/ansible-powerflex/blob/2.4.0/docs/modules/info.rst) + * [Snapshot module](https://github.com/dell/ansible-powerflex/blob/2.4.0/docs/modules/snapshot.rst) + * [SDC module](https://github.com/dell/ansible-powerflex/blob/2.4.0/docs/modules/sdc.rst) + * [Storage pool module](https://github.com/dell/ansible-powerflex/blob/2.4.0/docs/modules/storagepool.rst) + * [Volume module](https://github.com/dell/ansible-powerflex/blob/2.4.0/docs/modules/volume.rst) + * [SDS module](https://github.com/dell/ansible-powerflex/blob/2.4.0/docs/modules/sds.rst) + * [Device Module](https://github.com/dell/ansible-powerflex/blob/2.4.0/docs/modules/device.rst) + * [Protection Domain Module](https://github.com/dell/ansible-powerflex/blob/2.4.0/docs/modules/protection_domain.rst) + * [MDM Cluster Module](https://github.com/dell/ansible-powerflex/blob/2.4.0/docs/modules/mdm_cluster.rst) + * [Replication Consistency Group Module](https://github.com/dell/ansible-powerflex/blob/2.4.0/docs/modules/replication_consistency_group.rst) + * [Replication Pair Module](https://github.com/dell/ansible-powerflex/blob/2.4.0/docs/modules/replication_pair.rst) + * [Snapshot Policy Module](https://github.com/dell/ansible-powerflex/blob/2.4.0/docs/modules/snapshot_policy.rst) + * [Fault Sets Module](https://github.com/dell/ansible-powerflex/blob/2.4.0/docs/modules/fault_set.rst) ## Installation and execution of Ansible modules for Dell PowerFlex -The installation and execution steps of Ansible modules for Dell PowerFlex can be found [here](https://github.com/dell/ansible-powerflex/blob/2.2.0/docs/INSTALLATION.md). +The installation and execution steps of Ansible modules for Dell PowerFlex can be found [here](https://github.com/dell/ansible-powerflex/blob/2.4.0/docs/INSTALLATION.md). ## Releasing, Maintenance and Deprecation @@ -59,6 +59,6 @@ Ansible Modules for Dell Technologies PowerFlex follows [Semantic Versioning](ht New version will be release regularly if significant changes (bug fix or new feature) are made in the collection. -Released code versions are located on "release" branches with names of the form "release-x.y.z" where x.y.z corresponds to the version number. More information on branching strategy followed can be found [here](https://github.com/dell/ansible-powerflex/blob/2.2.0/docs/BRANCHING.md). +Released code versions are located on "release" branches with names of the form "release-x.y.z" where x.y.z corresponds to the version number. More information on branching strategy followed can be found [here](https://github.com/dell/ansible-powerflex/blob/2.4.0/docs/BRANCHING.md). Ansible Modules for Dell Technologies PowerFlex deprecation cycle is aligned with that of [Ansible](https://docs.ansible.com/ansible/latest/dev_guide/module_lifecycle.html). \ No newline at end of file diff --git a/changelogs/.plugin-cache.yaml b/changelogs/.plugin-cache.yaml index b2098ae..ce26c2f 100644 --- a/changelogs/.plugin-cache.yaml +++ b/changelogs/.plugin-cache.yaml @@ -16,6 +16,11 @@ plugins: name: device namespace: '' version_added: 1.1.0 + fault_set: + description: Manage Fault Sets on Dell PowerFlex + name: fault_set + namespace: '' + version_added: 2.2.0 info: description: Gathering information about Dell PowerFlex name: info @@ -41,6 +46,11 @@ plugins: name: replication_pair namespace: '' version_added: 1.6.0 + resource_group: + description: Manage resource group deployments on Dell PowerFlex. + name: resource_group + namespace: '' + version_added: 2.3.0 sdc: description: Manage SDCs on Dell PowerFlex name: sdc @@ -76,4 +86,4 @@ plugins: strategy: {} test: {} vars: {} -version: 2.1.0 +version: 2.3.0 diff --git a/changelogs/changelog.yaml b/changelogs/changelog.yaml index 8211b2b..a4fb3c6 100644 --- a/changelogs/changelog.yaml +++ b/changelogs/changelog.yaml @@ -146,3 +146,20 @@ releases: name: fault_set namespace: '' release_date: '2024-02-29' + 2.3.0: + changes: + minor_changes: + - Added support for resource group provisioning to validate, deploy, + edit, add nodes and delete a resource group. + - The Info module is enhanced to list the firmware repositories. + - Added support for PowerFlex ansible modules and roles on Azure. + modules: + - description: Manage resource group deployments on Dell PowerFlex + name: resource_group + namespace: '' + release_date: '2024-03-29' + 2.4.0: + changes: + minor_changes: + - Added support for executing Ansible PowerFlex modules and roles on AWS environment. + release_date: '2024-04-30' diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md index 726c931..8453175 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-powerflex/blob/2.2.0/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-powerflex/blob/2.4.0/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 PowerFlex 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-powerflex/blob/2.2.0/ISSUE_TRIAGE.md). +Read more about the ways you can [Triage issues](https://github.com/dell/ansible-powerflex/blob/2.4.0/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 PowerFlex](https://github.com/dell/ansible-powerflex/blob/2.2.0/BRANCHING.md) +* [Branching Strategy for Ansible modules for Dell PowerFlex](https://github.com/dell/ansible-powerflex/blob/2.4.0/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-powerflex/blob/2.2.0/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-powerflex/blob/2.4.0/SUPPORT.md). #### Code sanitization diff --git a/docs/INSTALLATION.md b/docs/INSTALLATION.md index 8686157..c2b8df3 100644 --- a/docs/INSTALLATION.md +++ b/docs/INSTALLATION.md @@ -41,7 +41,7 @@ You may obtain a copy of the License at * Download the latest tar build from any of the available distribution channel [Ansible Galaxy](https://galaxy.ansible.com/dellemc/powerflex) /[Automation Hub](https://console.redhat.com/ansible/automation-hub/repo/published/dellemc/powerflex) and use this command to install the collection anywhere in your system: - ansible-galaxy collection install dellemc-powerflex-2.2.0.tar.gz -p + ansible-galaxy collection install dellemc-powerflex-2.4.0.tar.gz -p * Set the environment variable: @@ -68,7 +68,7 @@ You may obtain a copy of the License at ## Ansible modules execution -The Ansible server must be configured with Python library for PowerFlex to run the Ansible playbooks. The [Documents](https://github.com/dell/ansible-powerflex/blob/2.2.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 needs to be configured before running the modules. +The Ansible server must be configured with Python library for PowerFlex to run the Ansible playbooks. The [Documents](https://github.com/dell/ansible-powerflex/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 needs to be configured before running the modules. ## SSL certificate validation diff --git a/docs/ISSUE_TRIAGE.md b/docs/ISSUE_TRIAGE.md index 8871da8..50d4665 100644 --- a/docs/ISSUE_TRIAGE.md +++ b/docs/ISSUE_TRIAGE.md @@ -43,8 +43,8 @@ Should explain what happened, what was expected and how to reproduce it together - Ansible Version: [e.g. 2.14] - Python Version [e.g. 3.11] - - Ansible modules for Dell PowerFlex Version: [e.g. 2.2.0] - - PowerFlex SDK version: [e.g. PyPowerFlex 1.9.0] + - Ansible modules for Dell PowerFlex Version: [e.g. 2.4.0] + - PowerFlex SDK version: [e.g. PyPowerFlex 1.11.0] - Any other additional information... #### Feature requests diff --git a/docs/MAINTAINER_GUIDE.md b/docs/MAINTAINER_GUIDE.md index e2d1d90..5f982c2 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-powerflex/blob/2.2.0/CODE_OF_CONDUCT.md) and the guidelines in the [Contributing](https://github.com/dell/ansible-powerflex/blob/2.2.0/CONTRIBUTING.md) and [Committer](https://github.com/dell/ansible-powerflex/blob/2.2.0/COMMITTER_GUIDE.md) guides +* Follow the [Code of Conduct](https://github.com/dell/ansible-powerflex/blob/2.4.0/CODE_OF_CONDUCT.md) and the guidelines in the [Contributing](https://github.com/dell/ansible-powerflex/blob/2.4.0/CONTRIBUTING.md) and [Committer](https://github.com/dell/ansible-powerflex/blob/2.4.0/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 9ce24b6..1a25523 100644 --- a/docs/Release Notes.md +++ b/docs/Release Notes.md @@ -1,6 +1,6 @@ **Ansible Modules for Dell Technologies PowerFlex** ========================================= -### Release notes 2.2.0 +### Release notes 2.4.0 > © 2024 Dell Inc. or its subsidiaries. All rights reserved. Dell > and other trademarks are trademarks of Dell Inc. or its @@ -28,7 +28,7 @@ Table 1. Revision history | Revision | Date | Description | |----------|-----------------|-------------------------------------------------------------| -| 01 | February 2024 | Current release of Ansible Modules for Dell PowerFlex 2.2.0 | +| 01 | April 2024 | Current release of Ansible Modules for Dell PowerFlex 2.4.0 | Product description ------------------- @@ -36,7 +36,7 @@ Product description The Ansible modules for Dell PowerFlex are used to automate and orchestrate the deployment, configuration, and management of Dell PowerFlex storage systems. The capabilities of Ansible modules are managing volumes, -storage pools, SDCs, snapshots, snapshot policy, SDSs, replication consistency groups, replication pairs, devices, protection domain, MDM and fault sets. +storage pools, SDCs, snapshots, snapshot policy, SDSs, replication consistency groups, replication pairs, resource group, devices, protection domain, MDM and fault sets. cluster, and obtaining high-level information about a PowerFlex system information. The modules use playbooks to list, show, create, delete, and modify each of the entities. @@ -44,15 +44,16 @@ each of the entities. New features and enhancements ----------------------------- Along with the previous release deliverables, this release supports following features - -- Fault set module is introduced to create, get details, rename and delete fault sets. -- The SDS module has been enhanced to facilitate SDS creation within a fault set. -- The Info module is enhanced to retrieve lists related to fault sets, service templates, deployments, and managed devices. +- Added support for executing Ansible PowerFlex modules and roles on AWS environment. +- Added support for resource group provisioning to validate, deploy, edit, add nodes and delete a resource group. +- The Info module is enhanced to list out all the firmware repository. +- Added support for PowerFlex ansible modules and roles on Azure. Known issues ------------ - Setting the RF cache and performance profile of the SDS during its creation fails intermittently on PowerFlex version 3.5. - The creation of replication pair fails when copy_type is specified as OfflineCopy on PowerFlex version 4.0. -- Pagination in info module with offset and limit fetches more than expected records when listing service templates or deployments. +- Pagination in info module with offset and limit fetches more than expected records when listing service templates, deployments or firmware repository. - Templates are fetched using the info module in spite of setting include_templates to false when listing deployments. Limitations @@ -62,11 +63,11 @@ Limitations Distribution ------------ The software package is available for download from the [Ansible Modules -for PowerFlex GitHub](https://github.com/dell/ansible-powerflex/tree/2.2.0) page. +for PowerFlex GitHub](https://github.com/dell/ansible-powerflex/tree/2.4.0) page. Documentation ------------- -The documentation is available on [Ansible Modules for PowerFlex GitHub](https://github.com/dell/ansible-powerflex/tree/2.2.0/docs) +The documentation is available on [Ansible Modules for PowerFlex GitHub](https://github.com/dell/ansible-powerflex/tree/2.4.0/docs) page. It includes the following: - README diff --git a/docs/SECURITY.md b/docs/SECURITY.md index 77323e4..a7eab1b 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 PowerFlex repository are 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-powerflex/blob/2.2.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-powerflex/blob/2.4.0/docs/CONTRIBUTING.md#Pull-requests) for more information. ## Reporting a vulnerability diff --git a/docs/modules/device.rst b/docs/modules/device.rst index 4fcd828..cbeb0f8 100644 --- a/docs/modules/device.rst +++ b/docs/modules/device.rst @@ -22,7 +22,7 @@ The below requirements are needed on the host that executes this module. - A Dell PowerFlex storage system version 3.6 or later. - Ansible-core 2.14 or later. -- PyPowerFlex 1.9.0. +- PyPowerFlex 1.11.0. - Python 3.9, 3.10 or 3.11. diff --git a/docs/modules/fault_set.rst b/docs/modules/fault_set.rst index 55b9972..191ab73 100644 --- a/docs/modules/fault_set.rst +++ b/docs/modules/fault_set.rst @@ -22,7 +22,7 @@ The below requirements are needed on the host that executes this module. - A Dell PowerFlex storage system version 3.6 or later. - Ansible-core 2.14 or later. -- PyPowerFlex 1.9.0. +- PyPowerFlex 1.11.0. - Python 3.9, 3.10 or 3.11. diff --git a/docs/modules/info.rst b/docs/modules/info.rst index ced8e0a..fd67480 100644 --- a/docs/modules/info.rst +++ b/docs/modules/info.rst @@ -14,7 +14,7 @@ Synopsis Gathering information about Dell PowerFlex storage system includes getting the api details, list of volumes, SDSs, SDCs, storage pools, protection domains, snapshot policies, and devices. -Gathering information about Dell PowerFlex Manager includes getting the list of managed devices, deployments and service templates. +Gathering information about Dell PowerFlex Manager includes getting the list of managed devices, deployments, service templates and firmware repository. @@ -22,9 +22,9 @@ Requirements ------------ The below requirements are needed on the host that executes this module. -- A Dell PowerFlex storage system version 3.5 or later. +- A Dell PowerFlex storage system version 3.6 or later. - Ansible-core 2.14 or later. -- PyPowerFlex 1.8.0. +- PyPowerFlex 1.11.0. - Python 3.9, 3.10 or 3.11. @@ -33,7 +33,7 @@ Parameters ---------- gather_subset (optional, list, None) - List of string variables to specify the Powerflex storage system entities for which information is required. + List of string variables to specify the PowerFlex storage system entities for which information is required. Volumes - ``vol``. @@ -61,6 +61,8 @@ Parameters Deployments - ``deployment``. + FirmwareRepository - ``firmware_repository``. + filters (optional, list, None) List of filters to support filtered output for storage entities. @@ -77,7 +79,7 @@ Parameters filter_operator (True, str, None) Operation to be performed on filter key. - Choice *'contains'* is supported for gather_subset keys *service_template*, *managed_device*, *deployment*. + Choice ``contains`` is supported for *gather_subset* keys ``service_template``, ``managed_device``, ``deployment``, ``firmware_repository``. filter_value (True, str, None) @@ -88,47 +90,65 @@ Parameters limit (optional, int, 50) Page limit. - Supported for gather_subset keys *service_template*, *managed_device*, *deployment*. + Supported for *gather_subset* keys ``service_template``, ``managed_device``, ``deployment``, ``firmware_repository``. offset (optional, int, 0) Pagination offset. - Supported for gather_subset keys *service_template*, *managed_device*, *deployment*. + Supported for *gather_subset* keys ``service_template``, ``managed_device``, ``deployment``, ``firmware_repository``. sort (optional, str, None) Sort the returned components based on specified field. - Supported for gather_subset keys *service_template*, *managed_device*, *deployment*. + Supported for *gather_subset* keys ``service_template``, ``managed_device``, ``deployment``, ``firmware_repository``. - The supported sort keys for the gather_subset can be referred from PowerFlex Manager API documentation in developer.dell.com. + The supported sort keys for the *gather_subset* can be referred from PowerFlex Manager API documentation in https://developer.dell.com. include_devices (optional, bool, True) Include devices in response. - Applicable when gather_subset is *deployment*. + Applicable when *gather_subset* is ``deployment``. include_template (optional, bool, True) Include service templates in response. - Applicable when gather_subset is *deployment*. + Applicable when *gather_subset* is ``deployment``. full (optional, bool, False) Specify if response is full or brief. - Applicable when gather_subset is *deployment*, *service_template*. + Applicable when *gather_subset* is ``deployment``, ``service_template``. - For *deployment* specify to use full templates including resources in response. + For ``deployment`` specify to use full templates including resources in response. include_attachments (optional, bool, True) Include attachments. - Applicable when gather_subset is *service_template*. + Applicable when *gather_subset* is ``service_template``. + + + include_related (optional, bool, False) + Include related entities. + + Applicable when *gather_subset* is ``firmware_repository``. + + + include_bundles (optional, bool, False) + Include software bundle entities. + + Applicable when *gather_subset* is ``firmware_repository``. + + + include_components (optional, bool, False) + Include software component entities. + + Applicable when *gather_subset* is ``firmware_repository``. hostname (True, str, None) @@ -169,8 +189,8 @@ Notes .. note:: - The *check_mode* is supported. - - The supported filter keys for the gather_subset can be referred from PowerFlex Manager API documentation in developer.dell.com. - - The *filter*, *sort*, *limit* and *offset* options will be ignored when more than one *gather_subset* is specified along with *service_template*, *managed_device* or *deployment*. + - The supported filter keys for the *gather_subset* can be referred from PowerFlex Manager API documentation in https://developer.dell.com. + - The *filter*, *sort*, *limit* and *offset* options will be ignored when more than one *gather_subset* is specified along with ``service_template``, ``managed_device``, ``deployment`` or ``firmware_repository``. - The modules present in the collection named as 'dellemc.powerflex' are built to support the Dell PowerFlex storage platform. @@ -184,10 +204,10 @@ Examples - name: Get detailed list of PowerFlex entities dellemc.powerflex.info: - hostname: "{{hostname}}" - username: "{{username}}" - password: "{{password}}" - validate_certs: "{{validate_certs}}" + hostname: "{{ hostname }}" + username: "{{ username }}" + password: "{{ password }}" + validate_certs: "{{ validate_certs }}" gather_subset: - vol - storage_pool @@ -202,10 +222,10 @@ Examples - name: Get a subset list of PowerFlex volumes dellemc.powerflex.info: - hostname: "{{hostname}}" - username: "{{username}}" - password: "{{password}}" - validate_certs: "{{validate_certs}}" + hostname: "{{ hostname }}" + username: "{{ username }}" + password: "{{ password }}" + validate_certs: "{{ validate_certs }}" gather_subset: - vol filters: @@ -215,10 +235,10 @@ Examples - name: Get deployment and resource provisioning info dellemc.powerflex.info: - hostname: "{{hostname}}" - username: "{{username}}" - password: "{{password}}" - validate_certs: "{{validate_certs}}" + hostname: "{{ hostname }}" + username: "{{ username }}" + password: "{{ password }}" + validate_certs: "{{ validate_certs }}" gather_subset: - managed_device - deployment @@ -226,10 +246,10 @@ Examples - name: Get deployment with filter, sort, pagination dellemc.powerflex.info: - hostname: "{{hostname}}" - username: "{{username}}" - password: "{{password}}" - validate_certs: "{{validate_certs}}" + hostname: "{{ hostname }}" + username: "{{ username }}" + password: "{{ password }}" + validate_certs: "{{ validate_certs }}" gather_subset: - deployment filters: @@ -242,6 +262,60 @@ Examples include_devices: true include_template: true + - name: Get the list of firmware repository. + dellemc.powerflex.info: + hostname: "{{ hostname }}" + username: "{{ username }}" + password: "{{ password }}" + validate_certs: "{{ validate_certs }}" + gather_subset: + - firmware_repository + + - name: Get the list of firmware repository + dellemc.powerflex.info: + hostname: "{{ hostname }}" + username: "{{ username }}" + password: "{{ password }}" + validate_certs: "{{ validate_certs }}" + gather_subset: + - firmware_repository + include_related: true + include_bundles: true + include_components: true + + - name: Get the list of firmware repository with filter + dellemc.powerflex.info: + hostname: "{{ hostname }}" + username: "{{ username }}" + password: "{{ password }}" + validate_certs: "{{ validate_certs }}" + gather_subset: + - firmware_repository + filters: + - filter_key: "createdBy" + filter_operator: "equal" + filter_value: "admin" + sort: createdDate + limit: 10 + include_related: true + include_bundles: true + include_components: true + register: result_repository_out + + - name: Get the list of available firmware repository + ansible.builtin.debug: + msg: "{{ result_repository_out.FirmwareRepository | selectattr('state', 'equalto', 'available') }}" + + - name: Get the list of software components in the firmware repository + ansible.builtin.debug: + msg: "{{ result_repository_out.FirmwareRepository | + selectattr('id', 'equalto', '8aaa80788b7') | map(attribute='softwareComponents') | flatten }}" + + - name: Get the list of software bundles in the firmware repository + ansible.builtin.debug: + msg: "{{ result_repository_out.FirmwareRepository | + selectattr('id', 'equalto', '8aaa80788b7') | map(attribute='softwareBundles') | flatten }}" + Return Values @@ -841,6 +915,39 @@ ServiceTemplates (when I(gather_subset) is I(service_template), list, [{'id': '2 +FirmwareRepository (when I(gather_subset) is C(firmware_repository), list, [{'id': '8aaa03a78de4b2a5018de662818d000b', 'name': 'https://192.168.0.1/artifactory/path/pfxmlogs-bvt-pfmp-swo-upgrade-402-to-451-56.tar.gz', 'sourceLocation': 'https://192.168.0.2/artifactory/path/pfxmlogs-bvt-pfmp-swo-upgrade-402-to-451-56.tar.gz', 'sourceType': None, 'diskLocation': '', 'filename': '', 'md5Hash': None, 'username': '', 'password': '', 'downloadStatus': 'error', 'createdDate': '2024-02-26T17:07:11.884+00:00', 'createdBy': 'admin', 'updatedDate': '2024-03-01T06:21:10.917+00:00', 'updatedBy': 'system', 'defaultCatalog': False, 'embedded': False, 'state': 'errors', 'softwareComponents': [], 'softwareBundles': [], 'deployments': [], 'bundleCount': 0, 'componentCount': 0, 'userBundleCount': 0, 'minimal': True, 'downloadProgress': 100, 'extractProgress': 0, 'fileSizeInGigabytes': 0.0, 'signedKeySourceLocation': None, 'signature': 'Unknown', 'custom': False, 'needsAttention': False, 'jobId': 'Job-10d75a23-d801-4fdb-a2d0-7f6389ab75cf', 'rcmapproved': False}]) + Details of all firmware repository. + + + id (, str, ) + ID of the firmware repository. + + + name (, str, ) + Name of the firmware repository. + + + sourceLocation (, str, ) + Source location of the firmware repository. + + + state (, str, ) + State of the firmware repository. + + + softwareComponents (, list, ) + Software components of the firmware repository. + + + softwareBundles (, list, ) + Software bundles of the firmware repository. + + + deployments (, list, ) + Deployments of the firmware repository. + + + @@ -857,4 +964,5 @@ Authors - Arindam Datta (@dattaarindam) - Trisha Datta (@trisha-dell) - Jennifer John (@Jennifer-John) +- Felix Stephen (@felixs88) diff --git a/docs/modules/mdm_cluster.rst b/docs/modules/mdm_cluster.rst index babb39b..fa73ae5 100644 --- a/docs/modules/mdm_cluster.rst +++ b/docs/modules/mdm_cluster.rst @@ -24,7 +24,7 @@ The below requirements are needed on the host that executes this module. - A Dell PowerFlex storage system version 3.6 or later. - Ansible-core 2.14 or later. -- PyPowerFlex 1.9.0. +- PyPowerFlex 1.11.0. - Python 3.9, 3.10 or 3.11. diff --git a/docs/modules/protection_domain.rst b/docs/modules/protection_domain.rst index 84c640e..0bd532b 100644 --- a/docs/modules/protection_domain.rst +++ b/docs/modules/protection_domain.rst @@ -22,7 +22,7 @@ The below requirements are needed on the host that executes this module. - A Dell PowerFlex storage system version 3.6 or later. - Ansible-core 2.14 or later. -- PyPowerFlex 1.9.0. +- PyPowerFlex 1.11.0. - Python 3.9, 3.10 or 3.11. diff --git a/docs/modules/replication_consistency_group.rst b/docs/modules/replication_consistency_group.rst index 76d9590..d8d1440 100644 --- a/docs/modules/replication_consistency_group.rst +++ b/docs/modules/replication_consistency_group.rst @@ -22,7 +22,7 @@ The below requirements are needed on the host that executes this module. - A Dell PowerFlex storage system version 3.6 or later. - Ansible-core 2.14 or later. -- PyPowerFlex 1.9.0. +- PyPowerFlex 1.11.0. - Python 3.9, 3.10 or 3.11. diff --git a/docs/modules/replication_pair.rst b/docs/modules/replication_pair.rst index 254a2eb..7c883c6 100644 --- a/docs/modules/replication_pair.rst +++ b/docs/modules/replication_pair.rst @@ -22,7 +22,7 @@ The below requirements are needed on the host that executes this module. - A Dell PowerFlex storage system version 3.6 or later. - Ansible-core 2.14 or later. -- PyPowerFlex 1.9.0. +- PyPowerFlex 1.11.0. - Python 3.9, 3.10 or 3.11. diff --git a/docs/modules/resource_group.rst b/docs/modules/resource_group.rst new file mode 100644 index 0000000..a72918d --- /dev/null +++ b/docs/modules/resource_group.rst @@ -0,0 +1,281 @@ +.. _resource_group_module: + + +resource_group -- Manage resource group deployments on Dell PowerFlex. +====================================================================== + +.. contents:: + :local: + :depth: 1 + + +Synopsis +-------- + +Managing resource group deployments on PowerFlex storage system includes deploying, editing, adding nodes and deleting a resource group deployment. + + + +Requirements +------------ +The below requirements are needed on the host that executes this module. + +- A Dell PowerFlex storage system version 3.6 or later. +- Ansible-core 2.14 or later. +- PyPowerFlex 1.11.0. +- Python 3.9, 3.10 or 3.11. + + + +Parameters +---------- + + resource_group_name (optional, str, None) + The name of the resource group. + + This is a required field to deploy a resource group. + + Either *resource_group_id* or *resource_group_name* must be specified to perform resource group operations. + + Mutually exclusive with *resource_group_id*. + + + resource_group_id (optional, str, None) + The ID of the resource group. + + Either *resource_group_id* or *resource_group_name* must be specified to perform resource group operations. + + Mutually exclusive with *resource_group_name*. + + + template_name (optional, str, None) + The name of the published template. + + Either *template_id* or *template_name* must be specified to deploy a resource group. + + Mutually exclusive with *template_id*. + + + template_id (optional, str, None) + The ID of the published template. + + Either *template_id* or *template_name* must be specified to deploy a resource group. + + Mutually exclusive with *template_name*. + + + firmware_repository_id (optional, str, None) + The ID of the firmware repository if not using the appliance default catalog. + + Mutually exclusive with *firmware_repository_name*. + + + firmware_repository_name (optional, str, None) + The name of the firmware repository if not using the appliance default catalog. + + Mutually exclusive with *firmware_repository_id*. + + + new_resource_group_name (optional, str, None) + New name of the resource group to rename to. + + + description (optional, str, None) + The description of the resource group. + + + scaleup (optional, bool, False) + Whether to scale up the resource group. Specify as true to add nodes to the resource group. + + + clone_node (optional, str, None) + Resource to duplicate during scaleup, if more than one nodes are available in the resource group. + + + node_count (optional, int, 1) + Number of nodes to clone during scaleup. + + + validate (optional, bool, False) + Specify as true to validate the deployment of resource group. + + + schedule_date (optional, str, None) + Scheduled date for the resource group deployment. + + Specify in YYYY-MM-DD HH:MM:SS.sss or YYYY-MM-DD format. + + + state (optional, str, present) + The state of the resource group. + + + hostname (True, str, None) + IP or FQDN of the PowerFlex host. + + + username (True, str, None) + The username of the PowerFlex host. + + + password (True, str, None) + The password of the PowerFlex host. + + + validate_certs (optional, bool, True) + Boolean variable to specify whether or not to validate SSL certificate. + + ``true`` - Indicates that the SSL certificate should be verified. + + ``false`` - Indicates that the SSL certificate should not be verified. + + + port (optional, int, 443) + Port number through which communication happens with PowerFlex host. + + + timeout (False, int, 120) + Time after which connection will get terminated. + + It is to be mentioned in seconds. + + + + + +Notes +----- + +.. note:: + - The *check_mode* is supported. + - Resource group scale up can be done only when deployment is complete. + - The modules present in the collection named as 'dellemc.powerflex' are built to support the Dell PowerFlex storage platform. + + + + +Examples +-------- + +.. code-block:: yaml+jinja + + + - name: Validate deployment of a resource group + dellemc.powerflex.resource_group: + hostname: "{{ hostname }}" + username: "{{ username }}" + password: "{{ password }}" + validate_certs: "{{ validate_certs }}" + port: "{{ port }}" + resource_group_name: "{{ resource_group_name_1 }}" + description: ans_rg + template_id: c65d0172-8666-48ab-935e-9a0bf69ed66d + firmware_repository_id: 8aaa80788b5755d1018b576126d51ba3 + validate: true + + - name: Deploy a resource group + dellemc.powerflex.resource_group: + hostname: "{{ hostname }}" + username: "{{ username }}" + password: "{{ password }}" + validate_certs: "{{ validate_certs }}" + port: "{{ port }}" + resource_group_name: "{{ resource_group_name_1 }}" + description: ans_rg + template_id: c65d0172-8666-48ab-935e-9a0bf69ed66d + firmware_repository_id: 8aaa80788b5755d1018b576126d51ba3 + + - name: Add a node to a resource group + dellemc.powerflex.resource_group: + hostname: "{{ hostname }}" + username: "{{ username }}" + password: "{{ password }}" + validate_certs: "{{ validate_certs }}" + resource_group_name: "{{ resource_group_name_1 }}" + scaleup: true + clone_node: "{{ node_1 }}" + node_count: "{{ node_count }}" + + - name: Modify a resource group + dellemc.powerflex.resource_group: + hostname: "{{ hostname }}" + username: "{{ username }}" + password: "{{ password }}" + validate_certs: "{{ validate_certs }}" + resource_group_name: "{{ resource_group_name_1 }}" + new_resource_group_name: "{{ new_resource_group_name }}" + description: "description new" + + - name: Delete a resource group + dellemc.powerflex.resource_group: + hostname: "{{ hostname }}" + username: "{{ username }}" + password: "{{ password }}" + validate_certs: "{{ validate_certs }}" + port: "{{ port }}" + resource_group_name: ans_rg + state: "absent" + + + +Return Values +------------- + +changed (always, bool, false) + Whether or not the resource has changed. + + +resource_group_details (When resource group exists., dict, {'id': '8aaa03a88de961fa018de96a88d80008', 'deploymentName': 'dep-ans-test-rg1', 'deploymentDescription': 'ans test rg', 'retry': True, 'teardown': False, 'serviceTemplate': {'id': '8aaa03a88de961fa018de96a88d80008', 'templateName': 'update-template (8aaa03a88de961fa018de96a88d80008)'}, 'scheduleDate': None, 'status': 'error', 'compliant': True, 'deploymentDevice': [{'refId': 'scaleio-block-legacy-gateway', 'refType': 'SCALEIO', 'deviceHealth': 'GREEN', 'compliantState': 'COMPLIANT', 'deviceType': 'scaleio', 'currentIpAddress': '1.3.9.2', 'componentId': '910bf934-d45a-4fe3-8ea2-dc481e063a81', 'statusMessage': 'The processing of PowerFlex is unsuccessful.', 'model': 'PowerFlex Gateway', 'brownfield': False}], 'updateServerFirmware': True, 'useDefaultCatalog': True, 'firmwareRepository': {'id': '8aaa80788b5755d1018b576126d51ba3', 'name': 'PowerFlex 4.5.0.0', 'rcmapproved': False}, 'firmwareRepositoryId': '8aaa80788b5755d1018b576126d51ba3', 'deploymentHealthStatusType': 'red', 'allUsersAllowed': False, 'owner': 'admin', 'numberOfDeployments': 0, 'lifecycleMode': False, 'vds': False, 'scaleUp': False, 'brownfield': False, 'templateValid': True, 'configurationChange': False}) + Details of the resource group deployment. + + + id (, str, ) + The ID of the deployed resource group. + + + deploymentName (, str, ) + The name of the resource group deployment. + + + deploymentDescription (, str, ) + The description of the resource group deployment. + + + serviceTemplate (, dict, ) + The service template of the resource group. + + + id (, str, ) + The ID of the service template. + + + templateName (, str, ) + The name of the service template. + + + + status (, str, ) + The status of the deployment of the resource group. + + + firmwareRepositoryId (, str, ) + The ID of the firmware repository of the resource group. + + + + + + +Status +------ + + + + + +Authors +~~~~~~~ + +- Jennifer John (@johnj9) +- Trisha Datta (@trisha-dell) + diff --git a/docs/modules/sdc.rst b/docs/modules/sdc.rst index ad375eb..7b0871b 100644 --- a/docs/modules/sdc.rst +++ b/docs/modules/sdc.rst @@ -22,7 +22,7 @@ The below requirements are needed on the host that executes this module. - A Dell PowerFlex storage system version 3.6 or later. - Ansible-core 2.14 or later. -- PyPowerFlex 1.9.0. +- PyPowerFlex 1.11.0. - Python 3.9, 3.10 or 3.11. diff --git a/docs/modules/sds.rst b/docs/modules/sds.rst index f5c2951..188fe9f 100644 --- a/docs/modules/sds.rst +++ b/docs/modules/sds.rst @@ -22,7 +22,7 @@ The below requirements are needed on the host that executes this module. - A Dell PowerFlex storage system version 3.6 or later. - Ansible-core 2.14 or later. -- PyPowerFlex 1.9.0. +- PyPowerFlex 1.11.0. - Python 3.9, 3.10 or 3.11. diff --git a/docs/modules/snapshot.rst b/docs/modules/snapshot.rst index 052453a..e09e800 100644 --- a/docs/modules/snapshot.rst +++ b/docs/modules/snapshot.rst @@ -22,7 +22,7 @@ The below requirements are needed on the host that executes this module. - A Dell PowerFlex storage system version 3.6 or later. - Ansible-core 2.14 or later. -- PyPowerFlex 1.9.0. +- PyPowerFlex 1.11.0. - Python 3.9, 3.10 or 3.11. diff --git a/docs/modules/snapshot_policy.rst b/docs/modules/snapshot_policy.rst index deab7f0..dd683c9 100644 --- a/docs/modules/snapshot_policy.rst +++ b/docs/modules/snapshot_policy.rst @@ -22,7 +22,7 @@ The below requirements are needed on the host that executes this module. - A Dell PowerFlex storage system version 3.6 or later. - Ansible-core 2.14 or later. -- PyPowerFlex 1.8.0. +- PyPowerFlex 1.11.0. - Python 3.9, 3.10 or 3.11. diff --git a/docs/modules/storagepool.rst b/docs/modules/storagepool.rst index 76d9496..f9f3f27 100644 --- a/docs/modules/storagepool.rst +++ b/docs/modules/storagepool.rst @@ -22,7 +22,7 @@ The below requirements are needed on the host that executes this module. - A Dell PowerFlex storage system version 3.6 or later. - Ansible-core 2.14 or later. -- PyPowerFlex 1.9.0. +- PyPowerFlex 1.11.0. - Python 3.9, 3.10 or 3.11. diff --git a/docs/modules/volume.rst b/docs/modules/volume.rst index f3345a6..16dbf2b 100644 --- a/docs/modules/volume.rst +++ b/docs/modules/volume.rst @@ -24,7 +24,7 @@ The below requirements are needed on the host that executes this module. - A Dell PowerFlex storage system version 3.6 or later. - Ansible-core 2.14 or later. -- PyPowerFlex 1.9.0. +- PyPowerFlex 1.11.0. - Python 3.9, 3.10 or 3.11. diff --git a/galaxy.yml b/galaxy.yml index 919ccf5..b0af3b9 100644 --- a/galaxy.yml +++ b/galaxy.yml @@ -14,7 +14,7 @@ name: powerflex # The version of the collection. # Must be compatible with semantic versioning -version: 2.2.0 +version: 2.4.0 # The path to the Markdown (.md) readme file. # This path is relative to the root of the collection. @@ -60,13 +60,13 @@ tags: [storage] dependencies: {} # The URL of the originating SCM repository -repository: https://github.com/dell/ansible-powerflex/tree/2.2.0 +repository: https://github.com/dell/ansible-powerflex/tree/2.4.0 # The URL to any online docs -documentation: https://github.com/dell/ansible-powerflex/tree/2.2.0/docs +documentation: https://github.com/dell/ansible-powerflex/tree/2.4.0/docs # The URL to the homepage of the collection/project -homepage: https://github.com/dell/ansible-powerflex/tree/2.2.0 +homepage: https://github.com/dell/ansible-powerflex/tree/2.4.0 # The URL to the collection issue tracker issues: https://www.dell.com/community/Automation/bd-p/Automation diff --git a/meta/runtime.yml b/meta/runtime.yml index 6fa701d..dd41c3e 100644 --- a/meta/runtime.yml +++ b/meta/runtime.yml @@ -3,37 +3,30 @@ requires_ansible: ">=2.14.0" plugin_routing: modules: dellemc_powerflex_gatherfacts: - redirect: dellemc.powerflex.info - deprecation: - removal_date: "2024-03-31" + tombstone: + removal_date: "2024-03-22" warning_text: Use info module instead. dellemc_powerflex_device: - redirect: dellemc.powerflex.device - deprecation: - removal_date: "2024-03-31" + tombstone: + removal_date: "2024-03-22" warning_text: Use device module instead. dellemc_powerflex_sdc: - redirect: dellemc.powerflex.sdc - deprecation: - removal_date: "2024-03-31" + tombstone: + removal_date: "2024-03-22" warning_text: Use sdc module instead. dellemc_powerflex_sds: - redirect: dellemc.powerflex.sds - deprecation: - removal_date: "2024-03-31" + tombstone: + removal_date: "2024-03-22" warning_text: Use sds module instead. dellemc_powerflex_snapshot: - redirect: dellemc.powerflex.snapshot - deprecation: - removal_date: "2024-03-31" + tombstone: + removal_date: "2024-03-22" warning_text: Use snapshot module instead. dellemc_powerflex_storagepool: - redirect: dellemc.powerflex.storagepool - deprecation: - removal_date: "2024-03-31" + tombstone: + removal_date: "2024-03-22" warning_text: Use storagepool module instead. dellemc_powerflex_volume: - redirect: dellemc.powerflex.volume - deprecation: - removal_date: "2024-03-31" + tombstone: + removal_date: "2024-03-22" warning_text: Use volume module instead. diff --git a/playbooks/modules/info.yml b/playbooks/modules/info.yml index b1a1678..fac8af8 100644 --- a/playbooks/modules/info.yml +++ b/playbooks/modules/info.yml @@ -78,3 +78,60 @@ filter_value: "partial" sort: name limit: 10 + + - name: Get the list of firmware repository + dellemc.powerflex.info: + hostname: "{{ hostname }}" + username: "{{ username }}" + password: "{{ password }}" + validate_certs: "{{ validate_certs }}" + gather_subset: + - firmware_repository + + - name: Get the list of firmware repository + dellemc.powerflex.info: + hostname: "{{ hostname }}" + username: "{{ username }}" + password: "{{ password }}" + validate_certs: "{{ validate_certs }}" + gather_subset: + - firmware_repository + include_related: true + include_bundles: true + include_components: true + + - name: Get the list of firmware repository with filter + dellemc.powerflex.info: + hostname: "{{ hostname }}" + username: "{{ username }}" + password: "{{ password }}" + validate_certs: "{{ validate_certs }}" + gather_subset: + - firmware_repository + filters: + - filter_key: "createdBy" + filter_operator: "equal" + filter_value: "admin" + sort: createdDate + limit: 10 + include_related: true + include_bundles: true + include_components: true + register: result_repository_out + + - name: Get the list of available firmware repository + ansible.builtin.debug: + msg: "{{ result_repository_out.FirmwareRepository | + selectattr('state', 'equalto', 'available') }}" + + - name: Get the list of software components in the firmware repository + ansible.builtin.debug: + msg: "{{ result_repository_out.FirmwareRepository | + selectattr('id', 'equalto', '8aaa80788b7') | + map(attribute='softwareComponents') | flatten }}" + + - name: Get the list of software bundles in the firmware repository + ansible.builtin.debug: + msg: "{{ result_repository_out.FirmwareRepository | + selectattr('id', 'equalto', '8aaa80788b7') | + map(attribute='softwareBundles') | flatten }}" diff --git a/playbooks/modules/resource_group.yml b/playbooks/modules/resource_group.yml new file mode 100644 index 0000000..2cbc22e --- /dev/null +++ b/playbooks/modules/resource_group.yml @@ -0,0 +1,71 @@ +--- +- name: Resource group operations on PowerFlex array. + hosts: localhost + connection: local + gather_facts: false + vars: + hostname: 'x.x.x.x' + username: 'admin' + password: 'Password' + validate_certs: false + host_port: 443 + resource_group_name_1: "ans_rg" + node_1: "vpi2170" + template_id: "c65d0172-8666-48ab-935e-9a0bf69ed66d" + + tasks: + - name: Validate deployment of a resource group + register: result + dellemc.powerflex.resource_group: + hostname: "{{ hostname }}" + username: "{{ username }}" + password: "{{ password }}" + validate_certs: "{{ validate_certs }}" + resource_group_name: "{{ resource_group_name_1 }}" + description: ans_rg + template_id: "{{ template_id }}" + firmware_repository_name: "PowerFlex 4.5.0.0" + validate: true + + - name: Deploy a resource group + register: result + dellemc.powerflex.resource_group: + hostname: "{{ hostname }}" + username: "{{ username }}" + password: "{{ password }}" + validate_certs: "{{ validate_certs }}" + resource_group_name: "{{ resource_group_name_1 }}" + description: ans_rg + template_id: "{{ template_id }}" + firmware_repository_name: "PowerFlex 4.5.0.0" + + - name: Add a node to a resource group + dellemc.powerflex.resource_group: + hostname: "{{ hostname }}" + username: "{{ username }}" + password: "{{ password }}" + validate_certs: "{{ validate_certs }}" + resource_group_name: "{{ resource_group_name_1 }}" + scaleup: true + clone_node: "{{ node_1 }}" + node_count: 2 + + - name: Modify a resource group + dellemc.powerflex.resource_group: + hostname: "{{ hostname }}" + username: "{{ username }}" + password: "{{ password }}" + validate_certs: "{{ validate_certs }}" + resource_group_name: "{{ resource_group_name_1 }}" + new_resource_group_name: "new_resource_group_name" + description: "description new" + + - name: Delete a resource group + register: result + dellemc.powerflex.resource_group: + hostname: "{{ hostname }}" + username: "{{ username }}" + password: "{{ password }}" + validate_certs: "{{ validate_certs }}" + resource_group_name: "{{ resource_group_name_1 }}" + state: "absent" diff --git a/playbooks/roles/group_vars/all b/playbooks/roles/group_vars/all index 1031958..5cec51f 100644 --- a/playbooks/roles/group_vars/all +++ b/playbooks/roles/group_vars/all @@ -1,6 +1,7 @@ powerflex_common_file_install_location: "/var/tmp" powerflex_common_esxi_files_location: "/tmp/" powerflex_common_win_package_location: "C:\\Windows\\Temp" +powerflex_gateway_disable_gpg_check: true # powerflex sdc params powerflex_sdc_driver_sync_repo_address: 'ftp://ftp.emc.com/' powerflex_sdc_driver_sync_repo_user: 'QNzgdxXix' diff --git a/plugins/doc_fragments/powerflex.py b/plugins/doc_fragments/powerflex.py index 0c0e0d9..32b17a4 100644 --- a/plugins/doc_fragments/powerflex.py +++ b/plugins/doc_fragments/powerflex.py @@ -53,7 +53,7 @@ class ModuleDocFragment(object): requirements: - A Dell PowerFlex storage system version 3.6 or later. - Ansible-core 2.14 or later. - - PyPowerFlex 1.9.0. + - PyPowerFlex 1.10.0. - Python 3.9, 3.10 or 3.11. notes: - The modules present in the collection named as 'dellemc.powerflex' diff --git a/plugins/module_utils/storage/dell/utils.py b/plugins/module_utils/storage/dell/utils.py index 94024d4..50f4166 100644 --- a/plugins/module_utils/storage/dell/utils.py +++ b/plugins/module_utils/storage/dell/utils.py @@ -8,11 +8,14 @@ import logging import math import re +from datetime import datetime from decimal import Decimal from ansible_collections.dellemc.powerflex.plugins.module_utils.storage.dell.logging_handler \ import CustomRotatingFileHandler import traceback from ansible.module_utils.basic import missing_required_lib +import random +import string """import PyPowerFlex lib""" try: @@ -80,10 +83,10 @@ def ensure_required_libs(module): exception=PKG_RSRC_IMP_ERR) if not HAS_POWERFLEX_SDK: - module.fail_json(msg=missing_required_lib("PyPowerFlex V 1.9.0 or above"), + module.fail_json(msg=missing_required_lib("PyPowerFlex V 1.10.0 or above"), exception=POWERFLEX_SDK_IMP_ERR) - min_ver = '1.9.0' + min_ver = '1.11.0' try: curr_version = pkg_resources.require("PyPowerFlex")[0].version supported_version = (parse_version(curr_version) >= parse_version(min_ver)) @@ -193,3 +196,34 @@ def get_time_minutes(time, time_unit): return time else: return 0 + + +def get_display_message(error_text): + match = re.search(r"displayMessage=([^']+)", error_text) + error_message = match.group(1) if match else error_text + return error_message + + +def validate_date(date): + try: + return datetime.strptime(date, '%Y-%m-%dT%H:%M:%S.%f') + except ValueError: + try: + date_obj = datetime.strptime(date, '%Y-%m-%d') + return date_obj.replace(hour=0, minute=0, second=0, microsecond=0) + except ValueError: + return None + + +def get_filter(name, id=None): + filter_type = "id" if id else "name" + filter_value = id or name + filter_query = f"eq,{filter_type},{filter_value}" + return filter_query + + +def random_uuid_generation(): + generate_uuid = ''.join( + [random.choice(string.ascii_lowercase + string.digits) for n in range(32)]) + + return generate_uuid diff --git a/plugins/modules/info.py b/plugins/modules/info.py index 33f3a8a..a114d0c 100644 --- a/plugins/modules/info.py +++ b/plugins/modules/info.py @@ -22,7 +22,7 @@ getting the api details, list of volumes, SDSs, SDCs, storage pools, protection domains, snapshot policies, and devices. - Gathering information about Dell PowerFlex Manager includes getting the - list of managed devices, deployments and service templates. + list of managed devices, deployments, service templates and firmware repository. extends_documentation_fragment: - dellemc.powerflex.powerflex @@ -31,11 +31,12 @@ - Arindam Datta (@dattaarindam) - Trisha Datta (@trisha-dell) - Jennifer John (@Jennifer-John) +- Felix Stephen (@felixs88) options: gather_subset: description: - - List of string variables to specify the Powerflex storage system + - List of string variables to specify the PowerFlex storage system entities for which information is required. - Volumes - C(vol). - Storage pools - C(storage_pool). @@ -50,9 +51,10 @@ - Service templates - C(service_template). - Managed devices - C(managed_device). - Deployments - C(deployment). + - FirmwareRepository - C(firmware_repository). choices: [vol, storage_pool, protection_domain, sdc, sds, snapshot_policy, device, rcg, replication_pair, - fault_set, service_template, managed_device, deployment] + fault_set, service_template, managed_device, deployment, firmware_repository] type: list elements: str filters: @@ -71,7 +73,8 @@ filter_operator: description: - Operation to be performed on filter key. - - Choice I('contains') is supported for gather_subset keys I(service_template), I(managed_device), I(deployment). + - Choice C(contains) is supported for I(gather_subset) keys C(service_template), C(managed_device), + C(deployment), C(firmware_repository). type: str choices: [equal, contains] required: true @@ -83,60 +86,81 @@ limit: description: - Page limit. - - Supported for gather_subset keys I(service_template), I(managed_device), I(deployment). + - Supported for I(gather_subset) keys C(service_template), C(managed_device), C(deployment), C(firmware_repository). type: int default: 50 offset: description: - Pagination offset. - - Supported for gather_subset keys I(service_template), I(managed_device), I(deployment). + - Supported for I(gather_subset) keys C(service_template), C(managed_device), C(deployment), C(firmware_repository). type: int default: 0 sort: description: - Sort the returned components based on specified field. - - Supported for gather_subset keys I(service_template), I(managed_device), I(deployment). - - The supported sort keys for the gather_subset can be referred from PowerFlex Manager API documentation in developer.dell.com. + - Supported for I(gather_subset) keys C(service_template), C(managed_device), C(deployment), C(firmware_repository). + - The supported sort keys for the I(gather_subset) can be referred from PowerFlex Manager API documentation in U(https://developer.dell.com). type: str include_devices: description: - Include devices in response. - - Applicable when gather_subset is I(deployment). + - Applicable when I(gather_subset) is C(deployment). type: bool default: true include_template: description: - Include service templates in response. - - Applicable when gather_subset is I(deployment). + - Applicable when I(gather_subset) is C(deployment). type: bool default: true full: description: - Specify if response is full or brief. - - Applicable when gather_subset is I(deployment), I(service_template). - - For I(deployment) specify to use full templates including resources in response. + - Applicable when I(gather_subset) is C(deployment), C(service_template). + - For C(deployment) specify to use full templates including resources in response. type: bool default: false include_attachments: description: - Include attachments. - - Applicable when gather_subset is I(service_template). + - Applicable when I(gather_subset) is C(service_template). type: bool default: true + include_related: + description: + - Include related entities. + - Applicable when I(gather_subset) is C(firmware_repository). + type: bool + default: false + version_added: 2.3.0 + include_bundles: + description: + - Include software bundle entities. + - Applicable when I(gather_subset) is C(firmware_repository). + type: bool + default: false + version_added: 2.3.0 + include_components: + description: + - Include software component entities. + - Applicable when I(gather_subset) is C(firmware_repository). + type: bool + default: false + version_added: 2.3.0 notes: - The I(check_mode) is supported. - - The supported filter keys for the gather_subset can be referred from PowerFlex Manager API documentation in developer.dell.com. + - The supported filter keys for the I(gather_subset) can be referred from PowerFlex Manager API documentation in U(https://developer.dell.com). - The I(filter), I(sort), I(limit) and I(offset) options will be ignored when more than one I(gather_subset) is specified along with - I(service_template), I(managed_device) or I(deployment). + C(service_template), C(managed_device), C(deployment) or C(firmware_repository). ''' EXAMPLES = r''' - name: Get detailed list of PowerFlex entities dellemc.powerflex.info: - hostname: "{{hostname}}" - username: "{{username}}" - password: "{{password}}" - validate_certs: "{{validate_certs}}" + hostname: "{{ hostname }}" + username: "{{ username }}" + password: "{{ password }}" + validate_certs: "{{ validate_certs }}" gather_subset: - vol - storage_pool @@ -151,10 +175,10 @@ - name: Get a subset list of PowerFlex volumes dellemc.powerflex.info: - hostname: "{{hostname}}" - username: "{{username}}" - password: "{{password}}" - validate_certs: "{{validate_certs}}" + hostname: "{{ hostname }}" + username: "{{ username }}" + password: "{{ password }}" + validate_certs: "{{ validate_certs }}" gather_subset: - vol filters: @@ -164,10 +188,10 @@ - name: Get deployment and resource provisioning info dellemc.powerflex.info: - hostname: "{{hostname}}" - username: "{{username}}" - password: "{{password}}" - validate_certs: "{{validate_certs}}" + hostname: "{{ hostname }}" + username: "{{ username }}" + password: "{{ password }}" + validate_certs: "{{ validate_certs }}" gather_subset: - managed_device - deployment @@ -175,10 +199,10 @@ - name: Get deployment with filter, sort, pagination dellemc.powerflex.info: - hostname: "{{hostname}}" - username: "{{username}}" - password: "{{password}}" - validate_certs: "{{validate_certs}}" + hostname: "{{ hostname }}" + username: "{{ username }}" + password: "{{ password }}" + validate_certs: "{{ validate_certs }}" gather_subset: - deployment filters: @@ -190,6 +214,60 @@ offset: 10 include_devices: true include_template: true + +- name: Get the list of firmware repository. + dellemc.powerflex.info: + hostname: "{{ hostname }}" + username: "{{ username }}" + password: "{{ password }}" + validate_certs: "{{ validate_certs }}" + gather_subset: + - firmware_repository + +- name: Get the list of firmware repository + dellemc.powerflex.info: + hostname: "{{ hostname }}" + username: "{{ username }}" + password: "{{ password }}" + validate_certs: "{{ validate_certs }}" + gather_subset: + - firmware_repository + include_related: true + include_bundles: true + include_components: true + +- name: Get the list of firmware repository with filter + dellemc.powerflex.info: + hostname: "{{ hostname }}" + username: "{{ username }}" + password: "{{ password }}" + validate_certs: "{{ validate_certs }}" + gather_subset: + - firmware_repository + filters: + - filter_key: "createdBy" + filter_operator: "equal" + filter_value: "admin" + sort: createdDate + limit: 10 + include_related: true + include_bundles: true + include_components: true + register: result_repository_out + +- name: Get the list of available firmware repository + ansible.builtin.debug: + msg: "{{ result_repository_out.FirmwareRepository | selectattr('state', 'equalto', 'available') }}" + +- name: Get the list of software components in the firmware repository + ansible.builtin.debug: + msg: "{{ result_repository_out.FirmwareRepository | + selectattr('id', 'equalto', '8aaa80788b7') | map(attribute='softwareComponents') | flatten }}" + +- name: Get the list of software bundles in the firmware repository + ansible.builtin.debug: + msg: "{{ result_repository_out.FirmwareRepository | + selectattr('id', 'equalto', '8aaa80788b7') | map(attribute='softwareBundles') | flatten }}" ''' RETURN = r''' @@ -1763,6 +1841,67 @@ ], "blockServiceOperationsMap": {} }] +FirmwareRepository: + description: Details of all firmware repository. + returned: when I(gather_subset) is C(firmware_repository) + type: list + contains: + id: + description: ID of the firmware repository. + type: str + name: + description: Name of the firmware repository. + type: str + sourceLocation: + description: Source location of the firmware repository. + type: str + state: + description: State of the firmware repository. + type: str + softwareComponents: + description: Software components of the firmware repository. + type: list + softwareBundles: + description: Software bundles of the firmware repository. + type: list + deployments: + description: Deployments of the firmware repository. + type: list + sample: [{ + "id": "8aaa03a78de4b2a5018de662818d000b", + "name": "https://192.168.0.1/artifactory/path/pfxmlogs-bvt-pfmp-swo-upgrade-402-to-451-56.tar.gz", + "sourceLocation": "https://192.168.0.2/artifactory/path/pfxmlogs-bvt-pfmp-swo-upgrade-402-to-451-56.tar.gz", + "sourceType": null, + "diskLocation": "", + "filename": "", + "md5Hash": null, + "username": "", + "password": "", + "downloadStatus": "error", + "createdDate": "2024-02-26T17:07:11.884+00:00", + "createdBy": "admin", + "updatedDate": "2024-03-01T06:21:10.917+00:00", + "updatedBy": "system", + "defaultCatalog": false, + "embedded": false, + "state": "errors", + "softwareComponents": [], + "softwareBundles": [], + "deployments": [], + "bundleCount": 0, + "componentCount": 0, + "userBundleCount": 0, + "minimal": true, + "downloadProgress": 100, + "extractProgress": 0, + "fileSizeInGigabytes": 0.0, + "signedKeySourceLocation": null, + "signature": "Unknown", + "custom": false, + "needsAttention": false, + "jobId": "Job-10d75a23-d801-4fdb-a2d0-7f6389ab75cf", + "rcmapproved": false + }] ''' from ansible.module_utils.basic import AnsibleModule @@ -2126,6 +2265,25 @@ def get_deployments_list(self): msg = f'Get deployments from PowerFlex Manager failed with error {str(e)}' return self.handle_error_exit(msg) + def get_pagination_params(self): + """ Get the pagination parameters """ + return {'limit': self.get_param_value('limit'), 'offset': self.get_param_value('offset'), + 'sort': self.get_param_value('sort'), 'filters': self.populate_filter_list()} + + def get_firmware_repository_list(self): + """ Get the list of firmware repository on a given PowerFlex Manager system """ + try: + LOG.info('Getting firmware repository list ') + firmware_repository = self.powerflex_conn.firmware_repository.get( + **self.get_pagination_params(), + related=self.get_param_value('include_related'), + bundles=self.get_param_value('include_bundles'), + components=self.get_param_value('include_components')) + return firmware_repository + except Exception as e: + msg = f'Get firmware repository from PowerFlex Manager failed with error {str(e)}' + return self.handle_error_exit(msg) + def get_service_templates_list(self): """ Get the list of service templates on a given PowerFlex Manager system """ try: @@ -2246,6 +2404,7 @@ def perform_module_operation(self): service_template = [] managed_device = [] deployment = [] + firmware_repository = [] subset = self.module.params['gather_subset'] self.validate_subset(api_version, subset) @@ -2276,6 +2435,8 @@ def perform_module_operation(self): service_template = self.get_service_templates_list() if 'deployment' in subset: deployment = self.get_deployments_list() + if 'firmware_repository' in subset: + firmware_repository = self.get_firmware_repository_list() self.module.exit_json( Array_Details=array_details, @@ -2292,7 +2453,8 @@ def perform_module_operation(self): Fault_Sets=fault_sets, ManagedDevices=managed_device, ServiceTemplates=service_template, - Deployments=deployment + Deployments=deployment, + FirmwareRepository=firmware_repository ) @@ -2319,7 +2481,7 @@ def get_powerflex_info_parameters(): choices=['vol', 'storage_pool', 'protection_domain', 'sdc', 'sds', 'snapshot_policy', 'device', 'rcg', 'replication_pair', 'fault_set', - 'service_template', 'managed_device', 'deployment']), + 'service_template', 'managed_device', 'deployment', 'firmware_repository']), filters=dict(type='list', required=False, elements='dict', options=dict(filter_key=dict(type='str', required=True, no_log=False), filter_operator=dict( @@ -2333,7 +2495,10 @@ def get_powerflex_info_parameters(): include_devices=dict(type='bool', default=True), include_template=dict(type='bool', default=True), full=dict(type='bool', default=False), - include_attachments=dict(type='bool', default=True) + include_attachments=dict(type='bool', default=True), + include_related=dict(type='bool', default=False), + include_bundles=dict(type='bool', default=False), + include_components=dict(type='bool', default=False), ) diff --git a/plugins/modules/resource_group.py b/plugins/modules/resource_group.py new file mode 100644 index 0000000..768d0c9 --- /dev/null +++ b/plugins/modules/resource_group.py @@ -0,0 +1,631 @@ +#!/usr/bin/python + +# Copyright: (c) 2024, Dell Technologies +# Apache License version 2.0 (see MODULE-LICENSE or http://www.apache.org/licenses/LICENSE-2.0.txt) + +""" Ansible module for managing resource group deployments on Dell Technologies (Dell) PowerFlex""" +from __future__ import (absolute_import, division, print_function) + +__metaclass__ = type + +DOCUMENTATION = r''' +module: resource_group +version_added: '2.3.0' +short_description: Manage resource group deployments on Dell PowerFlex. +description: +- Managing resource group deployments on PowerFlex storage system includes deploying, + editing, adding nodes and deleting a resource group deployment. +author: +- Jennifer John (@johnj9) +- Trisha Datta (@trisha-dell) +extends_documentation_fragment: + - dellemc.powerflex.powerflex +options: + resource_group_name: + description: + - The name of the resource group. + - This is a required field to deploy a resource group. + - Either I(resource_group_id) or I(resource_group_name) must be specified to perform resource group operations. + - Mutually exclusive with I(resource_group_id). + type: str + resource_group_id: + description: + - The ID of the resource group. + - Either I(resource_group_id) or I(resource_group_name) must be specified to perform resource group operations. + - Mutually exclusive with I(resource_group_name). + type: str + template_name: + description: + - The name of the published template. + - Either I(template_id) or I(template_name) must be specified to deploy a resource group. + - Mutually exclusive with I(template_id). + type: str + template_id: + description: + - The ID of the published template. + - Either I(template_id) or I(template_name) must be specified to deploy a resource group. + - Mutually exclusive with I(template_name). + type: str + firmware_repository_id: + description: + - The ID of the firmware repository if not using the appliance default catalog. + - Mutually exclusive with I(firmware_repository_name). + type: str + firmware_repository_name: + description: + - The name of the firmware repository if not using the appliance default catalog. + - Mutually exclusive with I(firmware_repository_id). + type: str + new_resource_group_name: + description: + - New name of the resource group to rename to. + type: str + description: + description: + - The description of the resource group. + type: str + scaleup: + description: + - Whether to scale up the resource group. Specify as true to add nodes to the resource group. + type: bool + default: false + clone_node: + description: + - Resource to duplicate during scaleup, if more than one nodes are available in the resource group. + type: str + node_count: + description: + - Number of nodes to clone during scaleup. + type: int + default: 1 + validate: + description: + - Specify as true to validate the deployment of resource group. + type: bool + default: false + schedule_date: + description: + - Scheduled date for the resource group deployment. + - Specify in YYYY-MM-DD HH:MM:SS.sss or YYYY-MM-DD format. + type: str + state: + description: + - The state of the resource group. + type: str + choices: ['absent', 'present'] + default: 'present' +notes: +- The I(check_mode) is supported. +- Resource group scale up can be done only when deployment is complete. +''' + +EXAMPLES = r''' +- name: Validate deployment of a resource group + dellemc.powerflex.resource_group: + hostname: "{{ hostname }}" + username: "{{ username }}" + password: "{{ password }}" + validate_certs: "{{ validate_certs }}" + port: "{{ port }}" + resource_group_name: "{{ resource_group_name_1 }}" + description: ans_rg + template_id: c65d0172-8666-48ab-935e-9a0bf69ed66d + firmware_repository_id: 8aaa80788b5755d1018b576126d51ba3 + validate: true + +- name: Deploy a resource group + dellemc.powerflex.resource_group: + hostname: "{{ hostname }}" + username: "{{ username }}" + password: "{{ password }}" + validate_certs: "{{ validate_certs }}" + port: "{{ port }}" + resource_group_name: "{{ resource_group_name_1 }}" + description: ans_rg + template_id: c65d0172-8666-48ab-935e-9a0bf69ed66d + firmware_repository_id: 8aaa80788b5755d1018b576126d51ba3 + +- name: Add a node to a resource group + dellemc.powerflex.resource_group: + hostname: "{{ hostname }}" + username: "{{ username }}" + password: "{{ password }}" + validate_certs: "{{ validate_certs }}" + resource_group_name: "{{ resource_group_name_1 }}" + scaleup: true + clone_node: "{{ node_1 }}" + node_count: "{{ node_count }}" + +- name: Modify a resource group + dellemc.powerflex.resource_group: + hostname: "{{ hostname }}" + username: "{{ username }}" + password: "{{ password }}" + validate_certs: "{{ validate_certs }}" + resource_group_name: "{{ resource_group_name_1 }}" + new_resource_group_name: "{{ new_resource_group_name }}" + description: "description new" + +- name: Delete a resource group + dellemc.powerflex.resource_group: + hostname: "{{ hostname }}" + username: "{{ username }}" + password: "{{ password }}" + validate_certs: "{{ validate_certs }}" + port: "{{ port }}" + resource_group_name: ans_rg + state: "absent" +''' + +RETURN = r''' +changed: + description: Whether or not the resource has changed. + returned: always + type: bool + sample: 'false' +resource_group_details: + description: Details of the resource group deployment. + returned: When resource group exists. + type: dict + contains: + id: + description: The ID of the deployed resource group. + type: str + deploymentName: + description: The name of the resource group deployment. + type: str + deploymentDescription: + description: The description of the resource group deployment. + type: str + serviceTemplate: + description: The service template of the resource group. + type: dict + contains: + id: + description: The ID of the service template. + type: str + templateName: + description: The name of the service template. + type: str + status: + description: The status of the deployment of the resource group. + type: str + firmwareRepositoryId: + description: The ID of the firmware repository of the resource group. + type: str + sample: { + "id": "8aaa03a88de961fa018de96a88d80008", + "deploymentName": "dep-ans-test-rg1", + "deploymentDescription": "ans test rg", + "retry": true, + "teardown": false, + "serviceTemplate": { + "id": "8aaa03a88de961fa018de96a88d80008", + "templateName": "update-template (8aaa03a88de961fa018de96a88d80008)" + }, + "scheduleDate": null, + "status": "error", + "compliant": true, + "deploymentDevice": [ + { + "refId": "scaleio-block-legacy-gateway", + "refType": "SCALEIO", + "deviceHealth": "GREEN", + "compliantState": "COMPLIANT", + "deviceType": "scaleio", + "currentIpAddress": "1.3.9.2", + "componentId": "910bf934-d45a-4fe3-8ea2-dc481e063a81", + "statusMessage": "The processing of PowerFlex is unsuccessful.", + "model": "PowerFlex Gateway", + "brownfield": false + } + ], + "updateServerFirmware": true, + "useDefaultCatalog": true, + "firmwareRepository": { + "id": "8aaa80788b5755d1018b576126d51ba3", + "name": "PowerFlex 4.5.0.0", + "rcmapproved": false + }, + "firmwareRepositoryId": "8aaa80788b5755d1018b576126d51ba3", + "deploymentHealthStatusType": "red", + "allUsersAllowed": false, + "owner": "admin", + "numberOfDeployments": 0, + "lifecycleMode": false, + "vds": false, + "scaleUp": false, + "brownfield": false, + "templateValid": true, + "configurationChange": false + } +''' + + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.dellemc.powerflex.plugins.module_utils.storage.dell \ + import utils +import json +import copy + +LOG = utils.get_logger('resource_group') + + +class PowerFlexResourceGroup: + def __init__(self): + """ Define all parameters required by this module""" + self.module_params = utils.get_powerflex_gateway_host_parameters() + self.module_params.update(get_powerflex_resource_group_parameters()) + mut_ex_args = [['resource_group_name', 'resource_group_id'], + ['template_name', 'template_id'], + ['firmware_repository_id', 'firmware_repository_name']] + + required_one_of_args = [['resource_group_name', 'resource_group_id']] + + # initialize the Ansible module + self.module = AnsibleModule( + argument_spec=self.module_params, + mutually_exclusive=mut_ex_args, + required_one_of=required_one_of_args, + supports_check_mode=True) + + utils.ensure_required_libs(self.module) + + try: + self.powerflex_conn = utils.get_powerflex_gateway_host_connection( + self.module.params) + LOG.info("Got the PowerFlex system connection object instance") + except Exception as e: + LOG.error(str(e)) + self.module.fail_json(msg=str(e)) + + def get_service_template(self, template_id=None, template_name=None, for_deployment=True): + """ + Retrieves a service template based on the provided parameters. + + Args: + template_id (str, optional): The ID of the service template. Defaults to None. + template_name (str, optional): The name of the service template. Defaults to None. + for_deployment (bool, optional): Indicates whether the template is for deployment. Defaults to True. + + Returns: + ServiceTemplate: The retrieved service template. + + Raises: + AnsibleFailJson: If either `template_id` or `template_name` is not specified for resource group deployment. + AnsibleFailJson: If the service template with the specified `template_name` is not found. + AnsibleFailJson: If the service template with the specified `template_id` is not found. + """ + if not (template_id or template_name): + self.module.fail_json(msg="Either template_id or template_name must be specified for resource group deployment") + if template_name: + filter_query = utils.get_filter(template_name) + template = self.powerflex_conn.service_template.get(filters=[filter_query]) + if not template: + error_msg = f"Service template {template_name} is not found" + self.module.fail_json(msg=error_msg) + + template_id = template_id or template[0]['id'] + return self.powerflex_conn.service_template.get_by_id(template_id, for_deployment) + + def get_firmware_repo(self, firmware_repo_id=None, firmware_repo_name=None): + """ + Retrieves the firmware repository ID based on the provided firmware repository name or ID. + + Args: + firmware_repo_id (str, optional): The ID of the firmware repository. Defaults to None. + firmware_repo_name (str, optional): The name of the firmware repository. Defaults to None. + + Returns: + str: The ID of the firmware repository. + + Raises: + ValueError: If the firmware repository is not found. + """ + firmware_repos = self.powerflex_conn.firmware_repository.get() + if firmware_repo_id: + firmware_repo = next((repo for repo in firmware_repos if repo.get('id') == firmware_repo_id), None) + else: + firmware_repo = next((repo for repo in firmware_repos if repo.get('name') == firmware_repo_name), None) + if not firmware_repo: + self.module.fail_json(msg=f"Firmware repository {firmware_repo_id or firmware_repo_name} is not found") + return firmware_repo['id'] + + def get_resource_group_name(self): + """ Retrieves the name of the resource group. """ + resource_group_name = self.module.params['resource_group_name'] + if not resource_group_name: + self.module.fail_json(msg="Specify resource_group_name for resource group deployment.") + return resource_group_name + + def is_modify_needed(self, deployment_data): + + modify_dict = False + if self.module.params["new_resource_group_name"] is not None and \ + deployment_data["deploymentName"] != self.module.params["new_resource_group_name"]: + modify_dict = True + if self.module.params["description"] is not None and \ + deployment_data["deploymentDescription"] != self.module.params["description"]: + modify_dict = True + if self.module.params["scaleup"]: + modify_dict = True + + return modify_dict + + def clone_component(self, deploy_data): + new_component = None + count_server = 0 + server_name = [] + for component in range(len(deploy_data["serviceTemplate"]["components"])): + if deploy_data["serviceTemplate"]["components"][component]["type"] == "SERVER": + count_server = count_server + 1 + server_name.append(deploy_data["serviceTemplate"]["components"][component]["name"]) + for component in range(len(deploy_data["serviceTemplate"]["components"])): + if self.module.params["clone_node"] is None: + if count_server == 1 and deploy_data["serviceTemplate"]["components"][component]["name"] == server_name[0]: + new_component = deploy_data["serviceTemplate"]["components"][component] + elif count_server != 1: + self.module.fail_json(msg="More than 1 server components exist. Provide the clone_node.") + else: + if deploy_data["serviceTemplate"]["components"][component]["name"] == self.module.params["clone_node"]: + new_component = deploy_data["serviceTemplate"]["components"][component] + + return new_component + + def prepare_add_node_payload(self, deploy_data): + + new_component = self.clone_component(deploy_data=deploy_data) + if new_component is not None: + uuid = utils.random_uuid_generation() + new_component.update({ + "identifier": None, + "asmGUID": None, + "puppetCertName": None, + "osPuppetCertName": None, + "managementIpAddress": None, + "brownfield": False, + "id": uuid, + "name": uuid}) + resource_params = ["razor_image", "scaleio_enabled", "scaleio_role", + "compression_enabled", "replication_enabled"] + + for resource in range(len(new_component["resources"])): + if new_component["resources"][resource]["id"] == "asm::server": + for param in range(len(new_component["resources"][resource]["parameters"])): + if new_component["resources"][resource]["parameters"][param]["id"] \ + not in resource_params: + new_component["resources"][resource]["parameters"][param]["guid"] = None + new_component["resources"][resource]["parameters"][param]["value"] = None + return new_component + + def modify_resource_group_details(self, deployment_data): + new_deployment_data = copy.deepcopy(deployment_data) + + # edit resource group + + if self.module.params["new_resource_group_name"]: + new_deployment_data["deploymentName"] = self.module.params["new_resource_group_name"] + if self.module.params["description"]: + new_deployment_data["deploymentDescription"] = self.module.params["description"] + + # Add nodes + + if self.module.params["scaleup"]: + new_deployment_data["scaleup"] = True + new_deployment_data["retry"] = True + node = 0 + while node < self.module.params["node_count"]: + new_deployment_data1 = copy.deepcopy(deployment_data) + new_component = self.prepare_add_node_payload(deploy_data=new_deployment_data1) + if new_component: + new_deployment_data["serviceTemplate"]["components"].append(new_component) + node = node + 1 + + try: + if not self.module.check_mode: + self.powerflex_conn.deployment.edit(deployment_id=deployment_data["id"], + rg_data=new_deployment_data) + + except Exception as e: + errmsg = f'Modifying a resource group deployment failed with error {utils.get_display_message(str(e))}' + self.module.fail_json(msg=errmsg) + + def get_deployment_data(self): + """ + Retrieves deployment data based on the provided parameters. + + :return: A JSON string representing the deployment data. + """ + template_id = self.module.params['template_id'] + template_name = self.module.params['template_name'] + resource_group_name = self.get_resource_group_name() + description = self.module.params['description'] + firmware_repo_id = self.module.params['firmware_repository_id'] + firmware_repo_name = self.module.params['firmware_repository_name'] + schedule_date = self.module.params['schedule_date'] + service_template = self.get_service_template(template_id, template_name, for_deployment=True) + deployment_data = { + "deploymentName": resource_group_name, + "deploymentDescription": description, + "serviceTemplate": service_template, + "updateServerFirmware": True, + "useDefaultCatalog": True + } + + if firmware_repo_id or firmware_repo_name: + firmware_repository_id = self.get_firmware_repo(firmware_repo_id, firmware_repo_name) + deployment_data["firmwareRepositoryId"] = firmware_repository_id + deployment_data["useDefaultCatalog"] = False + if schedule_date: + if not utils.validate_date(schedule_date): + self.module.fail_json(msg="Invalid schedule_date format. Specify the date in the format 'YYYY-MM-DDTHH:MM:SS.sss'") + deployment_data["scheduleDate"] = schedule_date + + return json.dumps(deployment_data) + + def get_deployment_details(self, deployment_name=None, deployment_id=None): + """ + Retrieves deployment details based on the provided deployment name or deployment ID. + + Args: + deployment_name (str, optional): The name of the deployment. Defaults to None. + deployment_id (str, optional): The ID of the deployment. Defaults to None. + + Returns: + list: A list of deployment details if the deployment name is provided and a matching deployment is found. + None: if the deployment ID is provided and no deployment is found with that ID. + """ + try: + if deployment_name: + filter_query = utils.get_filter(deployment_name) + resp = self.powerflex_conn.deployment.get(filters=[filter_query]) + if len(resp) > 0: + deployment_id = resp[0]["id"] + else: + return None + return self.powerflex_conn.deployment.get_by_id(deployment_id) + + except Exception as e: + if hasattr(e, 'status') and str(e.status) == '404': + return None + else: + self.module.fail_json(msg=utils.get_display_message(str(e))) + + def get_operation_mapping(self): + """ + Get the operation mapping based on the deployment details and module parameters. + + :return: The operation mapping for the given state, validate, and check_mode. + """ + if not self.deployment_details: + operation_mapping = { + ('present', True, True): ValidateDeploy, + ('present', True, False): ValidateDeploy, + ('present', False, True): ValidateDeploy, + ('present', False, False): Deploy + } + else: + operation_mapping = { + ('absent', True, True): DeleteDeploy, + ('absent', True, False): DeleteDeploy, + ('absent', False, True): DeleteDeploy, + ('absent', False, False): DeleteDeploy, + ('present', True, True): ModifyResourceGroup, + ('present', True, False): ModifyResourceGroup, + ('present', False, True): ModifyResourceGroup, + ('present', False, False): ModifyResourceGroup + } + + state = self.module.params['state'] + validate = self.module.params['validate'] + check_mode = self.module.check_mode + + return operation_mapping.get((state, validate, check_mode)) + + def perform_module_operation(self): + """ + Perform the module operation. + + :return: A dictionary containing the result of the module operation. + """ + result = dict( + changed=False, + resource_group_details=[] + ) + + self.deployment_details = self.get_deployment_details( + deployment_name=self.module.params['resource_group_name'], + deployment_id=self.module.params['resource_group_id']) + resource_group_operation = self.get_operation_mapping() + if resource_group_operation: + changed, resource_group_details = resource_group_operation.execute(self) + result['resource_group_details'] = resource_group_details + result['changed'] = changed + + self.module.exit_json(**result) + + +class Deploy: + def execute(self): + try: + rg_data = self.get_deployment_data() + response = self.powerflex_conn.deployment.create(rg_data) + return True, response + except Exception as e: + errmsg = f'Deploying a resource group failed with error {utils.get_display_message(str(e))}' + self.module.fail_json(msg=errmsg) + + +class ValidateDeploy: + def execute(self): + try: + rg_data = self.get_deployment_data() + response = self.powerflex_conn.deployment.validate(rg_data) + return False, response + except Exception as e: + errmsg = f'Validating a resource group deployment failed with error {utils.get_display_message(str(e))}' + self.module.fail_json(msg=errmsg) + + +class ModifyResourceGroup: + def execute(self): + try: + changed = False + rg_data = self.deployment_details + if self.is_modify_needed(deployment_data=rg_data): + self.modify_resource_group_details(deployment_data=rg_data) + changed = True + + response = self.get_deployment_details(deployment_id=rg_data['id']) + return changed, response + except Exception as e: + errmsg = f'Editing a resource group failed with error {utils.get_display_message(str(e))}' + self.module.fail_json(msg=errmsg) + + +class DeleteDeploy: + def execute(self): + try: + changed = False + if self.deployment_details: + if not self.module.check_mode: + self.powerflex_conn.deployment.delete(self.deployment_details['id']) + self.deployment_details = \ + self.get_deployment_details(deployment_name=self.deployment_details['deploymentName']) + changed = True + return changed, self.deployment_details + except Exception as e: + errmsg = f'Deleting a resource group deployment failed with error {utils.get_display_message(str(e))}' + self.module.fail_json(msg=errmsg) + + +def main(): + """ Create PowerFlex resource group object and perform actions on it + based on user input from playbook""" + obj = PowerFlexResourceGroup() + obj.perform_module_operation() + + +def get_powerflex_resource_group_parameters(): + """This method provides parameters required for the resource group + module on PowerFlex""" + return dict( + resource_group_name=dict(), + resource_group_id=dict(), + template_name=dict(), + template_id=dict(), + firmware_repository_id=dict(), + firmware_repository_name=dict(), + new_resource_group_name=dict(), + description=dict(), + scaleup=dict(type='bool', default=False), + clone_node=dict(), + node_count=dict(type='int', default=1), + validate=dict(type='bool', default=False), + schedule_date=dict(), + state=dict(choices=['present', 'absent'], default='present') + ) + + +if __name__ == '__main__': + main() diff --git a/roles/powerflex_config/tasks/main.yml b/roles/powerflex_config/tasks/main.yml index f9340f0..67bad80 100644 --- a/roles/powerflex_config/tasks/main.yml +++ b/roles/powerflex_config/tasks/main.yml @@ -4,6 +4,7 @@ hostname: "{{ hostname }}" username: "{{ username }}" password: "{{ password }}" + port: "{{ port }}" validate_certs: "{{ validate_certs }}" state: "present" register: powerflex_config_mdm_ip_result @@ -13,6 +14,7 @@ ansible.builtin.set_fact: powerflex_config_array_version: "{{ powerflex_config_mdm_ip_result.mdm_cluster_details.master.versionInfo[1] }}" powerflex_config_mdm_primary_hostname: "{{ hostvars[groups['mdm'][0]]['inventory_hostname'] }}" + powerflex_config_mdm_primary_ip: "{{ hostvars[groups['mdm'][0]]['ansible_host'] }}" - name: Login to primary MDM of PowerFlex 3.6 ansible.builtin.command: scli --login --username {{ username }} --password "{{ password }}" @@ -22,8 +24,30 @@ delegate_to: "{{ powerflex_config_mdm_primary_hostname }}" when: powerflex_config_array_version == '3' -- name: Login to primary MDM of PowerFlex 4.5 - ansible.builtin.command: scli --login --username {{ username }} --management_system_ip {{ hostname }} --password "{{ password }}" +- name: Generate login certificate for PowerFlex version 4.x + block: + - name: Generate login certificate using management_system_ip + ansible.builtin.command: > + scli --generate_login_certificate --management_system_ip {{ hostname }} --username {{ username }} --password {{ password }} + --p12_path /opt/emc/scaleio/mdm/cfg/cli_certificate.p12 --p12_password {{ password }} --insecure + run_once: true + register: powerflex_config_generate_login_certificate + changed_when: powerflex_config_generate_login_certificate.rc == 0 + delegate_to: "{{ powerflex_config_mdm_primary_hostname }}" + when: powerflex_config_array_version == '4' + rescue: + - name: Generate login certificate using primary_mdm_ip + ansible.builtin.command: > + scli --generate_login_certificate --management_system_ip {{ powerflex_config_mdm_primary_ip }} --username {{ username }} + --password {{ password }} --p12_path /opt/emc/scaleio/mdm/cfg/cli_certificate.p12 --p12_password {{ password }} --insecure + run_once: true + register: powerflex_config_generate_login_certificate_mdm_ip + changed_when: powerflex_config_generate_login_certificate_mdm_ip.rc == 0 + delegate_to: "{{ powerflex_config_mdm_primary_hostname }}" + when: powerflex_config_array_version == '4' + +- name: Login to MDM for PowerFlex version 4.x + ansible.builtin.command: scli --login --p12_path /opt/emc/scaleio/mdm/cfg/cli_certificate.p12 --p12_password {{ password }} run_once: true register: powerflex_config_login_output changed_when: powerflex_config_login_output.rc == 0 @@ -61,6 +85,7 @@ hostname: "{{ hostname }}" username: "{{ username }}" password: "{{ password }}" + port: "{{ port }}" validate_certs: "{{ validate_certs }}" storage_pool_name: "{{ powerflex_storage_pool_name }}" protection_domain_name: "{{ powerflex_protection_domain_name }}" diff --git a/roles/powerflex_sdc/tasks/install_sdc.yml b/roles/powerflex_sdc/tasks/install_sdc.yml index 9b75321..27c82db 100644 --- a/roles/powerflex_sdc/tasks/install_sdc.yml +++ b/roles/powerflex_sdc/tasks/install_sdc.yml @@ -4,6 +4,7 @@ hostname: "{{ hostname }}" username: "{{ username }}" password: "{{ password }}" + port: "{{ port }}" validate_certs: "{{ validate_certs }}" state: "present" register: powerflex_sdc_mdm_ip_result @@ -71,5 +72,4 @@ group: "root" notify: restart scini when: - - ansible_distribution != "VMkernel" - - " 'WindowsOS' not in ansible_distribution" + - ansible_distribution not in ['WindowsOS', 'SLES', 'VMkernel'] diff --git a/roles/powerflex_sdr/tasks/add_sdr.yml b/roles/powerflex_sdr/tasks/add_sdr.yml index 1af3452..f7cbfa3 100644 --- a/roles/powerflex_sdr/tasks/add_sdr.yml +++ b/roles/powerflex_sdr/tasks/add_sdr.yml @@ -4,6 +4,7 @@ hostname: "{{ hostname }}" username: "{{ username }}" password: "{{ password }}" + port: "{{ port }}" validate_certs: "{{ validate_certs }}" state: "present" register: powerflex_sdr_mdm_ip_result @@ -39,17 +40,34 @@ no_log: true when: powerflex_sdr_array_version == "3" -- name: Login to mdm for PowerFlex version 4.x - ansible.builtin.command: > - scli --login --management_system_ip {{ hostname }} - --username admin - --password "{{ password }}" - --approve_certificate +- name: Generate login certificate for PowerFlex version 4.x + block: + - name: Generate login certificate using management_system_ip + ansible.builtin.command: > + scli --generate_login_certificate --management_system_ip {{ hostname }} --username {{ username }} --password {{ password }} + --p12_path /opt/emc/scaleio/mdm/cfg/cli_certificate.p12 --p12_password {{ password }} --insecure + run_once: true + register: powerflex_sdr_generate_login_certificate + changed_when: powerflex_sdr_generate_login_certificate.rc == 0 + delegate_to: "{{ powerflex_sdr_mdm_primary_hostname }}" + when: powerflex_sdr_array_version != "3" + rescue: + - name: Generate login certificate using primary_mdm_ip + ansible.builtin.command: > + scli --generate_login_certificate --management_system_ip {{ powerflex_sdr_primary_mdm_ip }} --username {{ username }} + --password {{ password }} --p12_path /opt/emc/scaleio/mdm/cfg/cli_certificate.p12 --p12_password {{ password }} --insecure + run_once: true + register: powerflex_sdr_generate_login_certificate_mdm_ip + changed_when: powerflex_sdr_generate_login_certificate_mdm_ip.rc == 0 + delegate_to: "{{ powerflex_sdr_mdm_primary_hostname }}" + when: powerflex_sdr_array_version != "3" + +- name: Login to MDM for PowerFlex version 4.x + ansible.builtin.command: scli --login --p12_path /opt/emc/scaleio/mdm/cfg/cli_certificate.p12 --p12_password {{ password }} run_once: true - register: powerflex_initial_login + register: powerflex_sdr_login_output + changed_when: powerflex_sdr_login_output.rc == 0 delegate_to: "{{ powerflex_sdr_mdm_primary_hostname }}" - changed_when: powerflex_initial_login.rc == 0 - no_log: true when: powerflex_sdr_array_version != "3" - name: Output msg of previous task login to mdm diff --git a/roles/powerflex_sds/tasks/install_sds.yml b/roles/powerflex_sds/tasks/install_sds.yml index 8887ff1..010aee0 100644 --- a/roles/powerflex_sds/tasks/install_sds.yml +++ b/roles/powerflex_sds/tasks/install_sds.yml @@ -4,6 +4,7 @@ hostname: "{{ hostname }}" username: "{{ username }}" password: "{{ password }}" + port: "{{ port }}" validate_certs: "{{ validate_certs }}" state: "present" register: powerflex_sds_mdm_ip_result @@ -17,6 +18,7 @@ ansible.builtin.set_fact: powerflex_sds_mdm_ips: "{{ powerflex_sds_mdm_ip_result.mdm_cluster_details.mdmAddresses | join(',') }}" powerflex_sds_primary_mdm_hostname: "{{ hostvars[groups['mdm'][0]]['inventory_hostname'] }}" + powerflex_sds_primary_mdm_ip: "{{ hostvars[groups['mdm'][0]]['ansible_host'] }}" - name: Include install_powerflex.yml ansible.builtin.include_tasks: ../../powerflex_common/tasks/install_powerflex.yml @@ -35,8 +37,30 @@ ansible.builtin.set_fact: disks: "{{ powerflex_sds_disks }}" +- name: Generate login certificate for PowerFlex version 4.x + block: + - name: Generate login certificate using management_system_ip + ansible.builtin.command: > + scli --generate_login_certificate --management_system_ip {{ hostname }} --username {{ username }} --password {{ password }} + --p12_path /opt/emc/scaleio/mdm/cfg/cli_certificate.p12 --p12_password {{ password }} --insecure + run_once: true + register: powerflex_sds_generate_login_certificate + changed_when: powerflex_sds_generate_login_certificate.rc == 0 + delegate_to: "{{ powerflex_sds_primary_mdm_hostname }}" + when: powerflex_sds_array_version != "3" + rescue: + - name: Generate login certificate using primary_mdm_ip + ansible.builtin.command: > + scli --generate_login_certificate --management_system_ip {{ powerflex_sds_primary_mdm_ip }} --username {{ username }} + --password {{ password }} --p12_path /opt/emc/scaleio/mdm/cfg/cli_certificate.p12 --p12_password {{ password }} --insecure + run_once: true + register: powerflex_sds_generate_login_certificate_mdm_ip + changed_when: powerflex_sds_generate_login_certificate_mdm_ip.rc == 0 + delegate_to: "{{ powerflex_sds_primary_mdm_hostname }}" + when: powerflex_sds_array_version != "3" + - name: Login to MDM for PowerFlex version 4.x - ansible.builtin.command: scli --login --management_system_ip {{ hostname }} --username {{ username }} --password {{ password }} --approve_certificate + ansible.builtin.command: scli --login --p12_path /opt/emc/scaleio/mdm/cfg/cli_certificate.p12 --p12_password {{ password }} run_once: true register: powerflex_sds_login_output changed_when: powerflex_sds_login_output.rc == 0 diff --git a/roles/powerflex_tb/tasks/install_tb3x.yml b/roles/powerflex_tb/tasks/install_tb3x.yml index e602351..ebb2011 100644 --- a/roles/powerflex_tb/tasks/install_tb3x.yml +++ b/roles/powerflex_tb/tasks/install_tb3x.yml @@ -4,6 +4,7 @@ hostname: "{{ hostname }}" username: "{{ username }}" password: "{{ password }}" + port: "{{ port }}" validate_certs: "{{ validate_certs }}" state: "present" register: powerflex_tb_mdm_ip_result diff --git a/roles/powerflex_tb/tasks/uninstall_tb.yml b/roles/powerflex_tb/tasks/uninstall_tb.yml index b08bffe..82de6e0 100644 --- a/roles/powerflex_tb/tasks/uninstall_tb.yml +++ b/roles/powerflex_tb/tasks/uninstall_tb.yml @@ -29,9 +29,30 @@ when: powerflex_tb_mdm_cluster_mode[0] == "5_node" and powerflex_tb_scli_version[0] == '3' # Switch from three or five to cluster one node for PowerFlex version 4.5 -- name: Login to primary MDM node of PowerFlex version 4.5 - ansible.builtin.command: > - scli --login --management_system_ip {{ hostname }} --username {{ username }} --password {{ password }} +- name: Generate login certificate for PowerFlex version 4.x + block: + - name: Generate login certificate using management_system_ip + ansible.builtin.command: > + scli --generate_login_certificate --management_system_ip {{ hostname }} --username {{ username }} --password {{ password }} + --p12_path /opt/emc/scaleio/mdm/cfg/cli_certificate.p12 --p12_password {{ password }} --insecure + run_once: true + register: powerflex_tb_generate_login_certificate + changed_when: powerflex_tb_generate_login_certificate.rc == 0 + delegate_to: "{{ powerflex_tb_mdm_primary_hostname }}" + when: powerflex_tb_scli_version[0] >= '4' + rescue: + - name: Generate login certificate using primary_mdm_ip + ansible.builtin.command: > + scli --generate_login_certificate --management_system_ip {{ powerflex_tb_primary_ip }} --username {{ username }} + --password {{ password }} --p12_path /opt/emc/scaleio/mdm/cfg/cli_certificate.p12 --p12_password {{ password }} --insecure + run_once: true + register: powerflex_tb_generate_login_certificate_mdm_ip + changed_when: powerflex_tb_generate_login_certificate_mdm_ip.rc == 0 + delegate_to: "{{ powerflex_tb_mdm_primary_hostname }}" + when: powerflex_tb_scli_version[0] >= '4' + +- name: Login to MDM for PowerFlex version 4.x + ansible.builtin.command: scli --login --p12_path /opt/emc/scaleio/mdm/cfg/cli_certificate.p12 --p12_password {{ password }} run_once: true register: powerflex_tb_login_output changed_when: powerflex_tb_login_output.rc == 0 diff --git a/roles/powerflex_webui/tasks/install_webui.yml b/roles/powerflex_webui/tasks/install_webui.yml index 13d58ff..71886a0 100644 --- a/roles/powerflex_webui/tasks/install_webui.yml +++ b/roles/powerflex_webui/tasks/install_webui.yml @@ -10,6 +10,7 @@ hostname: "{{ hostname }}" username: "{{ username }}" password: "{{ password }}" + port: "{{ port }}" validate_certs: "{{ validate_certs }}" state: present register: powerflex_webui_result diff --git a/tests/sanity/ignore-2.14.txt b/tests/sanity/ignore-2.14.txt index cb6ef46..5714021 100644 --- a/tests/sanity/ignore-2.14.txt +++ b/tests/sanity/ignore-2.14.txt @@ -40,3 +40,6 @@ plugins/modules/replication_consistency_group.py import-2.7 plugins/modules/replication_consistency_group.py import-3.5 plugins/modules/replication_consistency_group.py compile-2.7 plugins/modules/replication_consistency_group.py compile-3.5 +plugins/modules/resource_group.py validate-modules:missing-gplv3-license +plugins/modules/resource_group.py compile-2.7 +plugins/modules/resource_group.py import-2.7 diff --git a/tests/sanity/ignore-2.15.txt b/tests/sanity/ignore-2.15.txt index cb6ef46..5714021 100644 --- a/tests/sanity/ignore-2.15.txt +++ b/tests/sanity/ignore-2.15.txt @@ -40,3 +40,6 @@ plugins/modules/replication_consistency_group.py import-2.7 plugins/modules/replication_consistency_group.py import-3.5 plugins/modules/replication_consistency_group.py compile-2.7 plugins/modules/replication_consistency_group.py compile-3.5 +plugins/modules/resource_group.py validate-modules:missing-gplv3-license +plugins/modules/resource_group.py compile-2.7 +plugins/modules/resource_group.py import-2.7 diff --git a/tests/sanity/ignore-2.16.txt b/tests/sanity/ignore-2.16.txt index 531796f..0dbde68 100644 --- a/tests/sanity/ignore-2.16.txt +++ b/tests/sanity/ignore-2.16.txt @@ -26,3 +26,6 @@ plugins/modules/replication_consistency_group.py import-2.7 plugins/modules/replication_consistency_group.py compile-2.7 plugins/modules/info.py compile-2.7 plugins/modules/info.py import-2.7 +plugins/modules/resource_group.py validate-modules:missing-gplv3-license +plugins/modules/resource_group.py compile-2.7 +plugins/modules/resource_group.py import-2.7 diff --git a/tests/sanity/ignore-2.17.txt b/tests/sanity/ignore-2.17.txt index 5406764..a464e7b 100644 --- a/tests/sanity/ignore-2.17.txt +++ b/tests/sanity/ignore-2.17.txt @@ -11,3 +11,4 @@ plugins/modules/replication_consistency_group.py validate-modules:missing-gplv3- plugins/modules/replication_pair.py validate-modules:missing-gplv3-license plugins/modules/snapshot_policy.py validate-modules:missing-gplv3-license plugins/modules/fault_set.py validate-modules:missing-gplv3-license +plugins/modules/resource_group.py validate-modules:missing-gplv3-license diff --git a/tests/sanity/ignore-2.18.txt b/tests/sanity/ignore-2.18.txt new file mode 100644 index 0000000..a464e7b --- /dev/null +++ b/tests/sanity/ignore-2.18.txt @@ -0,0 +1,14 @@ +plugins/modules/device.py validate-modules:missing-gplv3-license +plugins/modules/sdc.py validate-modules:missing-gplv3-license +plugins/modules/sds.py validate-modules:missing-gplv3-license +plugins/modules/snapshot.py validate-modules:missing-gplv3-license +plugins/modules/storagepool.py validate-modules:missing-gplv3-license +plugins/modules/volume.py validate-modules:missing-gplv3-license +plugins/modules/info.py validate-modules:missing-gplv3-license +plugins/modules/protection_domain.py validate-modules:missing-gplv3-license +plugins/modules/mdm_cluster.py validate-modules:missing-gplv3-license +plugins/modules/replication_consistency_group.py validate-modules:missing-gplv3-license +plugins/modules/replication_pair.py validate-modules:missing-gplv3-license +plugins/modules/snapshot_policy.py validate-modules:missing-gplv3-license +plugins/modules/fault_set.py validate-modules:missing-gplv3-license +plugins/modules/resource_group.py validate-modules:missing-gplv3-license diff --git a/tests/unit/plugins/module_utils/mock_info_api.py b/tests/unit/plugins/module_utils/mock_info_api.py index 20de1c1..2682cbd 100644 --- a/tests/unit/plugins/module_utils/mock_info_api.py +++ b/tests/unit/plugins/module_utils/mock_info_api.py @@ -319,7 +319,8 @@ class MockInfoApi: 'system_exception': "Get array details from Powerflex array failed with error", 'managed_device_get_error': "Get managed devices from PowerFlex Manager failed with error", 'service_template_get_error': "Get service templates from PowerFlex Manager failed with error", - 'deployment_get_error': "Get deployments from PowerFlex Manager failed with error" + 'deployment_get_error': "Get deployments from PowerFlex Manager failed with error", + 'firmware_repository_get_error': "Get firmware repository from PowerFlex Manager failed with error" } @staticmethod diff --git a/tests/unit/plugins/module_utils/mock_resource_group_api.py b/tests/unit/plugins/module_utils/mock_resource_group_api.py new file mode 100644 index 0000000..2284434 --- /dev/null +++ b/tests/unit/plugins/module_utils/mock_resource_group_api.py @@ -0,0 +1,162 @@ +# Copyright: (c) 2024, Dell Technologies + +# Apache License version 2.0 (see MODULE-LICENSE or http://www.apache.org/licenses/LICENSE-2.0.txt) + +"""Mock ApiException for Dell Technologies (Dell) PowerFlex Test modules""" + +from __future__ import (absolute_import, division, print_function) + +__metaclass__ = type + + +class MockResourceResourceGroupAPI: + + RG_COMMON_ARGS = { + "hostname": "**.***.**.***", + "username": "username", + "password": "password", + "validate_certs": False, + "port": "443", + "validate": False, + "template_name": None, + "template_id": None, + "firmware_repository_name": None, + "firmware_repository_id": None, + "resource_group_name": None, + "resource_group_id": None, + "schedule_date": None + } + + RG_RESPONSE = [{ + "id": "8aaa03a88de961fa018de96a88d80008", + "deploymentName": "ans_rg", + "deploymentDescription": "ans test rg", + "retry": True, + "teardown": False, + "serviceTemplate": { + "id": "8aaa03a88de961fa018de96a88d80008", + "templateName": "update-template (8aaa03a88de961fa018de96a88d80008)", + "components": [ + { + "id": "a7dafed9-96f2-4a55-9518-57af4f45686e", + "componentID": "component-scaleio-gateway-1", + "identifier": None, + "componentValid": { + "valid": True, + "messages": [] + }, + "puppetCertName": "scaleio-block-legacy-gateway", + "osPuppetCertName": None, + "name": "block-legacy-gateway", + "type": "SERVER", + "subType": "COMPUTEONLY", + "teardown": False, + "helpText": None, + "managementIpAddress": None, + "configFile": None, + "serialNumber": None, + "asmGUID": "scaleio-block-legacy-gateway", + "relatedComponents": { + "fc0f8f08-18c7-4fa8-bae8-67f3e6f9ccd6": "Node (Software Only)" + }, + "resources": [{ + "id": "asm::server", + "parameters": [{ + "id": "a7dafed9", + "guid": None, + "value": None}]}], + "refId": None, + "cloned": False, + "clonedFromId": None, + "manageFirmware": False, + "brownfield": False, + "instances": 1, + "clonedFromAsmGuid": None, + "ip": None + }] + }, + "scheduleDate": None, + "status": "error", + "compliant": True, + "deploymentDevice": [{ + "refId": "scaleio-block-legacy-gateway", + "refType": "SCALEIO", + "deviceHealth": "GREEN", + "compliantState": "COMPLIANT", + "deviceType": "scaleio", + "currentIpAddress": "1.3.9.2", + "componentId": "910bf934-d45a-4fe3-8ea2-dc481e063a81", + "statusMessage": "The processing of PowerFlex is unsuccessful.", + "model": "PowerFlex Gateway", + "brownfield": False}], + "updateServerFirmware": True, + "useDefaultCatalog": True, + "firmwareRepository": { + "id": "8aaa80788b5755d1018b576126d51ba3", + "name": "PowerFlex 4.5.0.0", + "rcmapproved": False}, + "firmwareRepositoryId": "8aaa80788b5755d1018b576126d51ba3", + "deploymentHealthStatusType": "red", + "allUsersAllowed": False, + "owner": "admin", + "numberOfDeployments": 0, + "lifecycleMode": False, + "vds": False, + "scaleUp": False, + "brownfield": False, + "templateValid": True, + "configurationChange": False}] + + RG_FIRMWARE_REPO = [{ + "id": "8aaa80788b5755d1018b576126d51ba3", + "name": "firmware-name", + "sourceLocation": "https://100.65.27.72/artifactory/path/pfxmlogs-bvt-pfmp-swo-upgrade-402-to-451-56.tar.gz", + "sourceType": None, + "diskLocation": "", + "filename": "", + "md5Hash": None, + "username": "", + "password": "", + "downloadStatus": "error", + "createdDate": "2024-02-26T17:07:11.884+00:00", + "createdBy": "admin", + "updatedDate": "2024-03-01T06:21:10.917+00:00", + "updatedBy": "system", + "defaultCatalog": False, + "embedded": False, + "state": "errors", + "softwareComponents": [], + "softwareBundles": [], + "deployments": [], + "bundleCount": 0, + "componentCount": 0, + "userBundleCount": 0, + "minimal": True, + "downloadProgress": 100, + "extractProgress": 0, + "fileSizeInGigabytes": 0.0, + "signedKeySourceLocation": None, + "signature": "Unknown", + "custom": False, + "needsAttention": False, + "jobId": "Job-10d75a23-d801-4fdb-a2d0-7f6389ab75cf", + "rcmapproved": False + }] + + RG_TEMPLATE_RESPONSE = [{ + "id": "8aaa03a88de961fa018de96a88d80008", + "templateName": "update-template" + }] + + @staticmethod + def resource_group_error(response_type): + return { + "get_delete_deploy_exception": "Deleting a resource group deployment failed with error ", + "get_validate_deploy_exception": "Validating a resource group deployment failed with error", + "get_create_deploy_exception": "Deploying a resource group failed with error", + "get_template_validate_error": "Deploying a resource group failed with error Either template_id", + "get_template_error": "Service template new-template is not found", + "invalid_date_format": "Deploying a resource group failed with error Invalid schedule_date format", + "resource_group_name_error": "Specify resource_group_name for resource group deployment", + "resource_group_edit_error": "Editing a resource group failed with error", + }[response_type] diff --git a/tests/unit/plugins/modules/test_info.py b/tests/unit/plugins/modules/test_info.py index ce50912..043fcd5 100644 --- a/tests/unit/plugins/modules/test_info.py +++ b/tests/unit/plugins/modules/test_info.py @@ -604,3 +604,24 @@ def test_get_with_invalid_offset_and_limit_for_subset(self, info_module_mock): info_module_mock.perform_module_operation() assert info_module_mock.get_param_value('limit') is None assert info_module_mock.get_param_value('offset') is None + + def test_get_firmware_repository_details(self, info_module_mock): + self.get_module_args.update({ + "gather_subset": ['firmware_repository'], + "limit": 20 + }) + info_module_mock.module.params = self.get_module_args + info_module_mock.powerflex_conn.firmware_repository.get = MagicMock() + info_module_mock.perform_module_operation() + info_module_mock.powerflex_conn.firmware_repository.get.assert_called() + + def test_get_deployment_details_throws_exception(self, info_module_mock): + self.get_module_args.update({ + "gather_subset": ['firmware_repository'] + }) + info_module_mock.module.params = self.get_module_args + info_module_mock.powerflex_conn.firmware_repository.get = MagicMock( + side_effect=MockApiException + ) + self.capture_fail_json_call(MockInfoApi.get_exception_response( + 'firmware_repository_get_error'), info_module_mock) diff --git a/tests/unit/plugins/modules/test_resource_group.py b/tests/unit/plugins/modules/test_resource_group.py new file mode 100644 index 0000000..1bf57f0 --- /dev/null +++ b/tests/unit/plugins/modules/test_resource_group.py @@ -0,0 +1,228 @@ +# Copyright: (c) 2024, Dell Technologies + +# Apache License version 2.0 (see MODULE-LICENSE or http://www.apache.org/licenses/LICENSE-2.0.txt) + +"""Unit Tests for Resource Group module on Dell Technologies (Dell) PowerFlex""" + +from __future__ import (absolute_import, division, print_function) + +__metaclass__ = type +import pytest +# pylint: disable=unused-import +from ansible_collections.dellemc.powerflex.tests.unit.plugins.module_utils.libraries import initial_mock +from mock.mock import MagicMock +from ansible_collections.dellemc.powerflex.tests.unit.plugins.module_utils.mock_api_exception \ + import MockApiException +from ansible_collections.dellemc.powerflex.tests.unit.plugins.module_utils.mock_resource_group_api \ + import MockResourceResourceGroupAPI +from ansible_collections.dellemc.powerflex.tests.unit.plugins.module_utils.libraries.powerflex_unit_base \ + import PowerFlexUnitBase +from ansible_collections.dellemc.powerflex.plugins.modules.resource_group \ + import PowerFlexResourceGroup + + +class TestResourceGroup(PowerFlexUnitBase): + + get_module_args = MockResourceResourceGroupAPI.RG_COMMON_ARGS + + @pytest.fixture + def module_object(self): + return PowerFlexResourceGroup + + def test_delete_deploy_check_mode(self, powerflex_module_mock): + self.set_module_params(powerflex_module_mock, self.get_module_args, + {"resource_group_name": "ans_rg", "state": "absent", "validate": True}) + powerflex_module_mock.module.check_mode = True + powerflex_module_mock.powerflex_conn.deployment.get = MagicMock( + return_value=MockResourceResourceGroupAPI.RG_RESPONSE) + powerflex_module_mock.powerflex_conn.deployment.delete = MagicMock(return_value=None) + powerflex_module_mock.perform_module_operation() + assert powerflex_module_mock.module.exit_json.call_args[1]['changed'] is True + + def test_delete_deploy(self, powerflex_module_mock): + self.set_module_params(powerflex_module_mock, self.get_module_args, + {"resource_group_name": "ans_rg", "state": "absent", "validate": True}) + powerflex_module_mock.powerflex_conn.deployment.get = MagicMock(return_value=[MagicMock()]) + powerflex_module_mock.powerflex_conn.deployment.delete = MagicMock(return_value=None) + powerflex_module_mock.perform_module_operation() + assert powerflex_module_mock.module.exit_json.call_args[1]['changed'] is True + + def test_delete_deploy_by_id(self, powerflex_module_mock): + self.set_module_params(powerflex_module_mock, self.get_module_args, + {"resource_group_id": "8aaa03a88de961fa018de96a88d80008", + "state": "absent", "validate": True, "resource_group_name": None}) + powerflex_module_mock.powerflex_conn.deployment.get_by_id = MagicMock( + return_value=MockResourceResourceGroupAPI.RG_RESPONSE[0]) + powerflex_module_mock.powerflex_conn.deployment.delete = MagicMock(return_value=None) + powerflex_module_mock.perform_module_operation() + assert powerflex_module_mock.module.exit_json.call_args[1]['changed'] is True + + def test_delete_deploy_by_id_exception(self, powerflex_module_mock): + self.set_module_params(powerflex_module_mock, self.get_module_args, + {"resource_group_id": "8aaa03a88de961fa018de96a88d80008", + "state": "absent", "validate": True, "resource_group_name": None}) + powerflex_module_mock.powerflex_conn.deployment.get_by_id = MagicMock(side_effect=MockApiException) + powerflex_module_mock.powerflex_conn.deployment.delete = MagicMock(return_value=None) + self.capture_fail_json_call("", powerflex_module_mock, invoke_perform_module=True) + + def test_delete_deploy_exception(self, powerflex_module_mock): + self.set_module_params(powerflex_module_mock, self.get_module_args, + {"resource_group_name": "ans_rg", "state": "absent", "validate": True, + "resource_group_id": None}) + powerflex_module_mock.powerflex_conn.deployment.get = MagicMock( + return_value=MockResourceResourceGroupAPI.RG_RESPONSE) + powerflex_module_mock.powerflex_conn.deployment.get_by_id = MagicMock( + return_value=MockResourceResourceGroupAPI.RG_RESPONSE[0]) + powerflex_module_mock.powerflex_conn.deployment.delete = MagicMock(side_effect=MockApiException) + self.capture_fail_json_call( + MockResourceResourceGroupAPI.resource_group_error('get_delete_deploy_exception'), + powerflex_module_mock, invoke_perform_module=True) + + def test_validate_deploy(self, powerflex_module_mock): + arguments = {"resource_group_name": "ans_rg", "description": "ans_rg", + "template_id": "c65d0172-8666-48ab-935e-9a0bf69ed66d", + "firmware_repository_id": "8aaa80788b5755d1018b576126d51ba3", + "validate": True, "resource_group_id": None, "state": "present"} + self.set_module_params(powerflex_module_mock, self.get_module_args, arguments) + powerflex_module_mock.powerflex_conn.deployment.get = MagicMock(return_value=[]) + powerflex_module_mock.powerflex_conn.deployment.get_by_id = MagicMock(return_value=[]) + powerflex_module_mock.powerflex_conn.service_template.get_by_id = MagicMock( + return_value=MockResourceResourceGroupAPI.RG_TEMPLATE_RESPONSE) + powerflex_module_mock.powerflex_conn.firmware_repository.get = MagicMock( + return_value=MockResourceResourceGroupAPI.RG_FIRMWARE_REPO) + powerflex_module_mock.powerflex_conn.deployment.validate = MagicMock( + return_value=MockResourceResourceGroupAPI.RG_RESPONSE) + powerflex_module_mock.perform_module_operation() + assert powerflex_module_mock.module.exit_json.call_args[1]['changed'] is False + + def test_validate_deploy_exception(self, powerflex_module_mock): + arguments = {"resource_group_name": "ans_rg", "description": "ans_rg", + "template_id": "c65d0172-8666-48ab-935e-9a0bf69ed66d", + "firmware_repository_id": "8aaa80788b5755d1018b576126d51ba3", + "validate": True, "resource_group_id": None, "state": "present"} + self.set_module_params(powerflex_module_mock, self.get_module_args, arguments) + powerflex_module_mock.get_deployment_data = MagicMock(side_effect=MockApiException) + self.capture_fail_json_call( + MockResourceResourceGroupAPI.resource_group_error('get_validate_deploy_exception'), + powerflex_module_mock, invoke_perform_module=True) + + def test_create_deploy(self, powerflex_module_mock): + arguments = {"resource_group_name": "ans_rg", "description": "ans_rg", + "template_id": None, "template_name": "update-template", + "firmware_repository_id": None, "firmware_repository_name": "firmware-name", + "validate": False, "state": "present", "schedule_date": "2020-01-01"} + self.set_module_params(powerflex_module_mock, self.get_module_args, arguments) + powerflex_module_mock.powerflex_conn.deployment.get = MagicMock(return_value=[]) + powerflex_module_mock.powerflex_conn.deployment.get_by_id = MagicMock(return_value=[]) + powerflex_module_mock.powerflex_conn.service_template.get = MagicMock( + return_value=MockResourceResourceGroupAPI.RG_TEMPLATE_RESPONSE) + powerflex_module_mock.powerflex_conn.service_template.get_by_id = MagicMock( + return_value=MockResourceResourceGroupAPI.RG_TEMPLATE_RESPONSE) + powerflex_module_mock.powerflex_conn.firmware_repository.get = MagicMock( + return_value=MockResourceResourceGroupAPI.RG_FIRMWARE_REPO) + powerflex_module_mock.powerflex_conn.deployment.create = MagicMock( + return_value=MockResourceResourceGroupAPI.RG_RESPONSE) + powerflex_module_mock.perform_module_operation() + assert powerflex_module_mock.module.exit_json.call_args[1]['changed'] is True + + def test_create_deploy_exception(self, powerflex_module_mock): + arguments = {"resource_group_name": "ans_rg", "description": "ans_rg", + "template_id": None, "template_name": "update-template", + "firmware_repository_id": None, "firmware_repository_name": "firmware-name", + "validate": False, "state": "present"} + self.set_module_params(powerflex_module_mock, self.get_module_args, arguments) + powerflex_module_mock.get_deployment_data = MagicMock(side_effect=MockApiException) + self.capture_fail_json_call( + MockResourceResourceGroupAPI.resource_group_error('get_create_deploy_exception'), + powerflex_module_mock, invoke_perform_module=True) + + def test_template_validation(self, powerflex_module_mock): + arguments = {"resource_group_name": "ans_rg", "description": "ans_rg", + "template_id": None, "template_name": None, + "firmware_repository_id": None, "firmware_repository_name": "firmware-name", + "validate": False, "state": "present"} + self.set_module_params(powerflex_module_mock, self.get_module_args, arguments) + powerflex_module_mock.powerflex_conn.service_template.get = MagicMock( + return_value=MockResourceResourceGroupAPI.RG_TEMPLATE_RESPONSE) + self.capture_fail_json_call( + MockResourceResourceGroupAPI.resource_group_error('get_template_validate_error'), + powerflex_module_mock, invoke_perform_module=True) + + def test_scheduled_data(self, powerflex_module_mock): + arguments = {"resource_group_name": "ans_rg", "description": "ans_rg", + "template_id": None, "template_name": "update-template", + "firmware_repository_id": None, "firmware_repository_name": "firmware-name", + "validate": False, "state": "present", "schedule_date": "01/01/2024"} + self.set_module_params(powerflex_module_mock, self.get_module_args, arguments) + powerflex_module_mock.powerflex_conn.deployment.get = MagicMock(return_value=[]) + powerflex_module_mock.powerflex_conn.deployment.get_by_id = MagicMock(return_value=[]) + powerflex_module_mock.powerflex_conn.service_template.get = MagicMock( + return_value=MockResourceResourceGroupAPI.RG_TEMPLATE_RESPONSE) + powerflex_module_mock.powerflex_conn.service_template.get_by_id = MagicMock( + return_value=MockResourceResourceGroupAPI.RG_TEMPLATE_RESPONSE) + powerflex_module_mock.powerflex_conn.firmware_repository.get = MagicMock( + return_value=MockResourceResourceGroupAPI.RG_FIRMWARE_REPO) + self.capture_fail_json_call( + MockResourceResourceGroupAPI.resource_group_error('invalid_date_format'), + powerflex_module_mock, invoke_perform_module=True) + + def test_resource_group_name_error(self, powerflex_module_mock): + arguments = {"resource_group_name": None, "description": "ans_rg", + "template_id": None, "template_name": "update-template", + "firmware_repository_id": None, "firmware_repository_name": "firmware-name", + "validate": False, "state": "present", "schedule_date": None} + self.set_module_params(powerflex_module_mock, self.get_module_args, arguments) + powerflex_module_mock.powerflex_conn.deployment.get = MagicMock(return_value=None) + powerflex_module_mock.powerflex_conn.deployment.get_by_id = MagicMock(return_value=None) + self.capture_fail_json_call( + MockResourceResourceGroupAPI.resource_group_error('resource_group_name_error'), + powerflex_module_mock, invoke_perform_module=True) + + def test_resource_group_edit(self, powerflex_module_mock): + arguments = {"resource_group_name": "ans_rg", "description": "ans_rg", + "template_id": None, "template_name": "update-template", + "firmware_repository_id": None, "firmware_repository_name": "firmware-name", + "validate": False, "state": "present", "schedule_date": "2020-01-01", + "scaleup": True, "node_count": 1, "clone_node": "block-legacy-gateway", + "new_resource_group_name": "ans_rg1"} + self.set_module_params(powerflex_module_mock, self.get_module_args, arguments) + powerflex_module_mock.powerflex_conn.deployment.get = MagicMock( + return_value=MockResourceResourceGroupAPI.RG_RESPONSE) + powerflex_module_mock.powerflex_conn.deployment.get_by_id = MagicMock( + return_value=MockResourceResourceGroupAPI.RG_RESPONSE[0]) + powerflex_module_mock.powerflex_conn.service_template.get = MagicMock( + return_value=MockResourceResourceGroupAPI.RG_TEMPLATE_RESPONSE) + powerflex_module_mock.powerflex_conn.service_template.get_by_id = MagicMock( + return_value=MockResourceResourceGroupAPI.RG_TEMPLATE_RESPONSE) + powerflex_module_mock.powerflex_conn.firmware_repository.get = MagicMock( + return_value=MockResourceResourceGroupAPI.RG_FIRMWARE_REPO) + powerflex_module_mock.powerflex_conn.deployment.edit = MagicMock( + return_value=None) + powerflex_module_mock.perform_module_operation() + assert powerflex_module_mock.module.exit_json.call_args[1]['changed'] is True + arguments.update({"clone_node": None}) + self.set_module_params(powerflex_module_mock, self.get_module_args, arguments) + powerflex_module_mock.perform_module_operation() + assert powerflex_module_mock.module.exit_json.call_args[1]['changed'] is True + + def test_modify_resource_group_exception(self, powerflex_module_mock): + arguments = {"resource_group_name": "ans_rg", "description": "ans_rg", + "template_id": None, "template_name": "update-template", + "firmware_repository_id": None, "firmware_repository_name": "firmware-name", + "validate": False, "state": "present", "schedule_date": "2020-01-01", + "scaleup": True, "node_count": 1, "clone_node": "block-legacy-gateway"} + self.set_module_params(powerflex_module_mock, self.get_module_args, arguments) + powerflex_module_mock.powerflex_conn.deployment.get = MagicMock( + return_value=MockResourceResourceGroupAPI.RG_RESPONSE) + powerflex_module_mock.powerflex_conn.deployment.get_by_id = MagicMock( + return_value=MockResourceResourceGroupAPI.RG_RESPONSE[0]) + powerflex_module_mock.powerflex_conn.service_template.get = MagicMock( + return_value=MockResourceResourceGroupAPI.RG_TEMPLATE_RESPONSE) + powerflex_module_mock.powerflex_conn.service_template.get_by_id = MagicMock( + return_value=MockResourceResourceGroupAPI.RG_TEMPLATE_RESPONSE) + powerflex_module_mock.powerflex_conn.firmware_repository.get = MagicMock( + return_value=MockResourceResourceGroupAPI.RG_FIRMWARE_REPO) + powerflex_module_mock.modify_resource_group_details = MagicMock(side_effect=MockApiException) + self.capture_fail_json_call( + MockResourceResourceGroupAPI.resource_group_error('resource_group_edit_error'), + powerflex_module_mock, invoke_perform_module=True)