From 6b8e628b3496dbd272ca13b18fe8bc2ce80b2686 Mon Sep 17 00:00:00 2001 From: Allen Robel Date: Wed, 27 Mar 2024 09:24:03 -1000 Subject: [PATCH] dcnm_image_policy (ready to merge) (#272) * dcnm_image_policy: work in progress initial commit contains a sort-of working merged state * Rename Payload to Config2Payload, more... 1. Add Payload2Config 2. Make Payload a base class for both Config2Payload and Payload2Config 3. Shorten filenames in module_utils/image_policy/*.py so as not to duplicate the directory name. 4. Add unit tests for ApiEndpoints, Config2Payload, and Payload2Config * remove unused file * merged state: differing policy parameters are now updated We merge want into have with want taking precedence. * saving work in progress (not working currently) * Committing changes for today for backup * Rename Policy* to ImagePolicy*, refactor, more... 1. Rename classes from Policy* to ImagePolicy* to differentiate them from NDFC Policies 2. Refactor ImagePolicyUpdate* and ImagePolicyCreate* to deduplicate common code and simplify the commit() method in each. * Run thru black/isort * Add state property to MockAnsibleModule, more... Run thru black/isort * Align functionality and docstring * Make subclass of ImagePolicyCommon, more... 1. Modify fail_json() calls to include self.failed_result from ImagePolicyCommon() 2. Run thru black / isort * Add handler for query state, more... Also: 1. Add logging to all classes 2. Run thru linters 3. Rename Task() to ImagePolicyTask() to distinguish from other task classses in the log 4. Move result.py to dcnm_image_upgrade module_utils/common. We'll commit it there. 5. ImagePolicyReplaceBulk().payloads() remove unused var 6. Remove unused method_name vars from all methods that don't call fail_json(), since the logger now provides the method name. 7. Config2Payload().commit() added query state to conditional to avoid building unrelated payload. 8. ParamsSpec() - added _build_params_spec_for_query_state() method. * Standardize result processing, more... ImagePolicyTask: - update_diff_and_response() - new method - handle_replaced_state() - standardize result processing - handle_deleted_state() - rename delete to instance - handle_delete_state() - leverage update_diff_and_response() - leverage ImagePolicyQuery() - Use ImagePolicyTaskResult() rather than Result() - delete_policies_not_in_want() - leverage update_diff_and_response() - handle_query_state() - leverage ImagePolicyQuery() - handle_query_state() - leverage update_diff_and_response() - send_need_create() - leverage update_diff_and_response() - send_need_update() - leverage update_diff_and_response() ImagePolicyUpdate: - Standardize diff, result, reponse handling - Inject self.action into diff elements ImagePolicyReplaceBulk: - Standardize diff, result, reponse handling - Inject self.action into diff elements - Remove ImagePolicyUpdate() class ParamsSpec: - _build_params_spec_for_overridden_state() - new method ImagePolicyDelete: - Standardize diff, result, reponse handling - Inject self.action into diff elements ImagePolicyCreate: - Standardize diff, result, reponse handling - Inject self.action into diff elements ImagePolicyCommon: - Add properties for failed, response, response_current, result, result_current - Remove properties for send_interval, timeout, and unit_test - Leveage ImagePolicyTaskResult.failed_result for failed_result property * Remove parameters dict This was used in early development with MockAnsibleModule, but is no longer needed. * Initial unit tests and fixtures, more... Adding some initial unit tests. As a result of these, found several issues which are address in the tested code for this commit. Within tests/unit/modules/dcnm/dcnm_image_policy: - utils.py: Add fixtures, add comments for unused fixtures to remove later - test_image_policy_create.py: initial unit tests for ImagePolicyCreate() - test_image_policy_create_bulk.py: initial unit tests for ImagePolicyCreateBulk() - Add json data for above test cases Within module_utils/image_policy - update.py: refactor, run through linters, test changes - replace.py: refactor, run through linters, test changes - params_spec.py: run though linters - delete.py: run through linters - create.py: run through linters, refactor - common.py: self.module should be self.ansible_module * Forgot to commit this with initial commit. * Remove unused imports, add query.py, add image_policies.py For now, copy ImagePolicies() class (image_policies.py) from dcnm_image_upgrade, since the imports for this class within dcnm_image_policy won't work until dcnm_image_upgrade PR is merged. Need to rethink the location of common image_mgmt classes... * Forgot to add copied ImagePolicies() class, more... Rename test_image_policy_api_endpoints.py to test_image_policy_endpoints.py to reflect the filename being tested, rather than the class name. Fix ImagePolicies() import in create.py * Fix import issues, more... Several files that are shared with dcnm_image_upgrade (and will be committed with dcnm_image_upgrade) are needed for sanity and unit tests to pass for dcnm_image_policy. I've added a README_MERGE_TODO.txt file in module_utils/common that lists these files. We need to remove these files from this branch after committing dcnm_image_upgrade. * Add a documentation string * Fix yaml linter issues * Fix one more yaml linter issue * Fix argument_spec to define config as a list * Fix yaml linter errors and run through black/isort * Add dcnm_image_policy.py to tests/sanity/ignore* for missing-gplv3-license Also, fix module name in documentation. * Fix PEP8 issues * Remove unused file * Fix lazy formatting, remove unused file * Fix pylint bad-option-value * fail_json if image policy ref_count is non-zero For delete, update operations, an image policy cannot be attached to any devices. We now verify that this is the case, and fail_json with a message to use the dcnm_image_upgrade module to detach the image policies before trying to delete or update them. * Fix pylint issue * Add initial integration tests for merged and deleted state * Remove one extra blank line at the end of file to appease yamllint * Add integration tests for overridden state * Initial integration tests for replaced state * Initial integration tests for query state * 81% unit test coverage for ImagePolicyCreateBulk() Also: 1. Add more debug logs to module_utils/image_policy/create.py 2. Consistent var names in utils.py * Refactor tests to use global MockImagePolicies() * Fix linter objection over too few blank lines * 100% unit test coverage for create.py * Fix pylint issues, more... ImagePolicyCreateCommon(): set self.response_current and self.result_current directly from dcnm_send() and _handle_response() respectively. * 74% coverage for delete.py, more... ImagePolicyDelete.policy_names: add verification that policy_name is a string. ImagePolicyDelete: make image_policies private (self._image_policies) ImagePolicyDelete._get_policies_to_delete: Modify to set self._policies_to_delete instead of returning policies_to_delete (easier for writing unit tests). utils.py, MockImagePolicies: Add ref_count property utils.py, MockImagePolicies: remove *args from all_policies property ImagePolicyCommon._verify_image_policy_ref_count: skip ref_count == None ImagePolicyCommon._verify_image_policy_ref_count: use .items() to simplify for loop. * 100% unit test coverage for delete.py * 100% unit test coverage for query.py, more... Also: 1. Add TEST_NOTES for all json fixture files 2. Modify TEST_NOTES for consistency between fixture files * Forgot to commit image_policies.py and query.py Changes include: - ImagePolicies: remove response, response_data, and ressult properties so that these are inherited from ImagePolicyCommon - ImagePolicyQuery: Set _policies_to_query in __init__ - ImagePolicyQuery: Set failed to False in __init__ - ImagePolicyQuery: make image_policies private (i.e. self._image_policies) - ImagePolicyQuery: policy_names setter. Add checks for empty list, and list containing other than string values. - ImagePolicyQuery._get_policies_to_query: set self._policies_to_query rather than returning _policies_to_query (easier for unit tests). - ImagePolicyQuery.commit: Don't re-instantiate ImagePolicies, use instance from __init__(). * 100% unit test coverage for replace.py Also: ImagePolicyReplaceBulk: initialize verb/path in __init__() ImagePolicyReplaceBulk: add _verify_payload to check that mandatory keys are present in all payloads ImagePolicyReplaceBulk.payloads: refactor to leverage _verify_payloads() ImagePolicyReplaceBulk.default_policy: privatize method name -> _default_policy ImagePolicyReplaceBulk.image_policies: privatize instance name -> _image_policies ImagePolicyReplaceBulk.image_policies: Add comments to clarify some code ImagePolicyReplaceBulk.image_policies: Add debug statements ImagePolicyReplaceBulk._send_payloads: directly set self._result_current and self._response_current from the calls to _handle_response and dcnm_send respectively ImagePolicyReplaceBulk._send_payloads: out of paranoia, use copy.deepcopy() in assignments ImagePolicyReplaceBulk._process_responses: set self.failed as appropriate ImagePolicyCommon: add some debug logs ImagePolicyCreateCommon: out of paranoia, use copy.deepcopy() in assignments ImagePolicyDelete: Remove TODO from commit() docstring * Update comments and fix task name * 99% unit test coverage for update.py Also: - MockImagePolicies: modified to correctly handle ref_count and name when the policy does not exist in all_policies - delete.py: Added Summary to all unit tests - update.py: make self.image_policies private (self._image_policies) - update.py: fix typo in fail_json message - update.py: fix docstring for _build_payloads_to_commit() - update.py: in _build_payloads_to_commit() remove ref_count, imageName, and platformPolicies from the merged payload since these are not valid parameters for the edit-policy endpoint (these ARE returned by the controller, hence we have to remove them). - update.py: _send_payloads(): directly set self.response_current, and self.result_current from the return values of their respective calls. - update.py: _send_payloads(): out of paranoia, deepcopy the response, result, payload when assigning values to diff_*, response_* and result_*. - update.py, ImagePolicyUpdate.payload setter: we needed to set payloads as well here. fixed. - replace.py: move _default_policy() into ImagePolicyCommon() * Fix pylint issues * 100% unit test coverage for update.py * Improve docstrings and comments * Add unit test to bring create.py to 100% coverage * Add unit tests for payload and config setter error handling 90% unit test coverage for payload.py * payload.py: Unit tests for empty config and payload, more... This brings unit test coverage for payload.py to 98% Also: Rename unit test fixture data keys from "...00XXX" to "---00XXXa" for consistency with other unit test fixture data. Fix test_image_policy_payload_00221 to not to use the fixture data from test_image_policy_payload_00220. test_image_policy_payload_00221: Fix fixture data to include the "type" key. * payload.py: add unit test for ansible states query and deleted This brings unit test coverage for payload.py to 100% * ImagePolicyCommon: add unit tests for _get_response(), more... This brings unit test coverage for image_policy_common.py to 79% Also: Update docstring for _handle_post_put_delete_response to indicate that RETURN_CODE is not considered when determining the result and to better describe the logic. * ImagePolicyCommon: add unit tests, more... This brings coverage for image_policy_common.py to 89% Add unit tests for: - make_boolean() - make_none() - @changed - @diff - @failed - @failed_result * Fix PEP8 errors. * 100% unit test coverage for image_policy_common.py Add unit tests for: - ImagePolicyCommon._handle_unknown_request_verbs() - ImagePolicyCommon.response_current - ImagePolicyCommon.response - ImagePolicyCommon.response_data - ImagePolicyCommon.result - ImagePolicyCommon.result_current For consistency, update fail_json messages and match values for: - ImagePolicyCommon.changed - ImagePolicyCommon.diff - ImagePolicyCommon.failed * Modifications based on Mike's initial review of dcnm_image_policy.py * Fix ImagePolicyTaskResult() docstring (Usage section) The original example code was overwriting the result instance. * ImagePolicyTaskResult: 100% unit test coverage * Update integration tests with new include syntax 'include' is deprecated in Ansible roles and is now 'include_tasks' * First attempt at documentation * antsibull-docs generated doc fails rstcheck Need to ping Mike to remember how we generate docs... ERROR: docs/cisco.dcnm.dcnm_image_policy_module.rst:37:0: Unknown directive type "rst-class". * Implement check_mode * Fix PEP8 errors and add check_mode to MockAnsibleModule * testing potential fix for image_policy unit test failures * Modifications to fix (hopefully) unit tests after implementing check_mode * Fix PEP8 too many black lines * Update docs * Forgot to commit dcnm_image_policy.py with the updated DOCUMENTATION section. * Fix validate-modules invalid-documentation * 2nd try: fixing invalid-documentation format. * Remove redundant REST request query.py: ImagePolicyQuery.commit(): remove redundant refresh() call create.py: remove commented line * Align rseults output with existing modules, more... Leverage Results() class for results generation Leveage RestSend() class in all other classes Update all unit and integration tests * Forgot to commit the unit tests * Fix PEP8 errors * Remove unused library as it's causing an import error * Fix python 3.8 TypeError * Update docstring to reflect current results output. * Fix blank line with whitespace in docstring. * For deleted state, delete all policies if config is None Also: dcnm_image_policy.py: Common().get_want() was referencing a non-existant property (Results().module_result) in exit_json(). Added an "ok_result" property to Results to avoid an error. * Fix documentation to remove required from config --------- Co-authored-by: Mike Wiebe --- __init__.py | 0 docs/cisco.dcnm.dcnm_image_policy_module.rst | 416 +++++++++ .../module_utils/common/README_MERGE_TODO.txt | 9 + plugins/module_utils/image_policy/__init__.py | 0 plugins/module_utils/image_policy/common.py | 228 +++++ plugins/module_utils/image_policy/create.py | 336 +++++++ plugins/module_utils/image_policy/delete.py | 204 +++++ .../module_utils/image_policy/endpoints.py | 138 +++ .../image_policy/image_policies.py | 300 ++++++ .../module_utils/image_policy/params_spec.py | 214 +++++ plugins/module_utils/image_policy/payload.py | 200 ++++ plugins/module_utils/image_policy/query.py | 140 +++ plugins/module_utils/image_policy/replace.py | 262 ++++++ plugins/module_utils/image_policy/update.py | 372 ++++++++ plugins/modules/dcnm_image_policy.py | 858 +++++++++++++++++ .../dcnm_image_policy/defaults/main.yaml | 2 + .../targets/dcnm_image_policy/meta/main.yaml | 1 + .../targets/dcnm_image_policy/tasks/dcnm.yaml | 20 + .../targets/dcnm_image_policy/tasks/main.yaml | 2 + .../tests/dcnm_image_policy_deleted.yaml | 457 +++++++++ .../tests/dcnm_image_policy_merged.yaml | 356 +++++++ .../tests/dcnm_image_policy_overridden.yaml | 563 ++++++++++++ .../tests/dcnm_image_policy_query.yaml | 566 ++++++++++++ .../tests/dcnm_image_policy_replaced.yaml | 512 +++++++++++ tests/sanity/ignore-2.10.txt | 1 + tests/sanity/ignore-2.11.txt | 1 + tests/sanity/ignore-2.12.txt | 1 + tests/sanity/ignore-2.13.txt | 1 + tests/sanity/ignore-2.14.txt | 1 + tests/sanity/ignore-2.9.txt | 1 + .../modules/dcnm/dcnm_image_policy/fixture.py | 50 + .../fixtures/all_policies_ImagePolicies.json | 865 ++++++++++++++++++ .../fixtures/data_payload.json | 138 +++ .../fixtures/payloads_ImagePolicyCreate.json | 92 ++ .../payloads_ImagePolicyCreateBulk.json | 206 +++++ .../payloads_ImagePolicyReplaceBulk.json | 209 +++++ .../fixtures/payloads_ImagePolicyUpdate.json | 126 +++ .../payloads_ImagePolicyUpdateBulk.json | 218 +++++ .../fixtures/response_current_RestSend.json | 108 +++ .../fixtures/responses_ImagePolicies.json | 24 + .../fixtures/responses_ImagePolicyCommon.json | 49 + .../fixtures/responses_ImagePolicyCreate.json | 38 + .../responses_ImagePolicyCreateBulk.json | 82 ++ .../fixtures/responses_ImagePolicyDelete.json | 24 + .../responses_ImagePolicyReplaceBulk.json | 81 ++ .../fixtures/responses_ImagePolicyUpdate.json | 19 + .../responses_ImagePolicyUpdateBulk.json | 81 ++ .../fixtures/result_current_RestSend.json | 30 + .../fixtures/results_ImagePolicies.json | 20 + .../fixtures/results_ImagePolicyCommon.json | 19 + .../results_ImagePolicyCreateBulk.json | 18 + .../fixtures/results_ImagePolicyDelete.json | 17 + .../results_ImagePolicyReplaceBulk.json | 18 + .../results_ImagePolicyTaskResult.json | 110 +++ .../fixtures/results_ImagePolicyUpdate.json | 6 + .../results_ImagePolicyUpdateBulk.json | 18 + .../test_image_policy_common.py | 726 +++++++++++++++ .../test_image_policy_create.py | 380 ++++++++ .../test_image_policy_create_bulk.py | 534 +++++++++++ .../test_image_policy_delete.py | 469 ++++++++++ .../test_image_policy_endpoints.py | 110 +++ .../test_image_policy_payload.py | 475 ++++++++++ .../test_image_policy_query.py | 439 +++++++++ .../test_image_policy_replace_bulk.py | 603 ++++++++++++ .../test_image_policy_update.py | 516 +++++++++++ .../test_image_policy_update_bulk.py | 642 +++++++++++++ .../modules/dcnm/dcnm_image_policy/utils.py | 574 ++++++++++++ 67 files changed, 14296 insertions(+) create mode 100644 __init__.py create mode 100644 docs/cisco.dcnm.dcnm_image_policy_module.rst create mode 100644 plugins/module_utils/common/README_MERGE_TODO.txt create mode 100644 plugins/module_utils/image_policy/__init__.py create mode 100644 plugins/module_utils/image_policy/common.py create mode 100644 plugins/module_utils/image_policy/create.py create mode 100644 plugins/module_utils/image_policy/delete.py create mode 100644 plugins/module_utils/image_policy/endpoints.py create mode 100644 plugins/module_utils/image_policy/image_policies.py create mode 100644 plugins/module_utils/image_policy/params_spec.py create mode 100644 plugins/module_utils/image_policy/payload.py create mode 100644 plugins/module_utils/image_policy/query.py create mode 100644 plugins/module_utils/image_policy/replace.py create mode 100644 plugins/module_utils/image_policy/update.py create mode 100644 plugins/modules/dcnm_image_policy.py create mode 100644 tests/integration/targets/dcnm_image_policy/defaults/main.yaml create mode 100644 tests/integration/targets/dcnm_image_policy/meta/main.yaml create mode 100644 tests/integration/targets/dcnm_image_policy/tasks/dcnm.yaml create mode 100644 tests/integration/targets/dcnm_image_policy/tasks/main.yaml create mode 100644 tests/integration/targets/dcnm_image_policy/tests/dcnm_image_policy_deleted.yaml create mode 100644 tests/integration/targets/dcnm_image_policy/tests/dcnm_image_policy_merged.yaml create mode 100644 tests/integration/targets/dcnm_image_policy/tests/dcnm_image_policy_overridden.yaml create mode 100644 tests/integration/targets/dcnm_image_policy/tests/dcnm_image_policy_query.yaml create mode 100644 tests/integration/targets/dcnm_image_policy/tests/dcnm_image_policy_replaced.yaml create mode 100644 tests/unit/modules/dcnm/dcnm_image_policy/fixture.py create mode 100644 tests/unit/modules/dcnm/dcnm_image_policy/fixtures/all_policies_ImagePolicies.json create mode 100644 tests/unit/modules/dcnm/dcnm_image_policy/fixtures/data_payload.json create mode 100644 tests/unit/modules/dcnm/dcnm_image_policy/fixtures/payloads_ImagePolicyCreate.json create mode 100644 tests/unit/modules/dcnm/dcnm_image_policy/fixtures/payloads_ImagePolicyCreateBulk.json create mode 100644 tests/unit/modules/dcnm/dcnm_image_policy/fixtures/payloads_ImagePolicyReplaceBulk.json create mode 100644 tests/unit/modules/dcnm/dcnm_image_policy/fixtures/payloads_ImagePolicyUpdate.json create mode 100644 tests/unit/modules/dcnm/dcnm_image_policy/fixtures/payloads_ImagePolicyUpdateBulk.json create mode 100644 tests/unit/modules/dcnm/dcnm_image_policy/fixtures/response_current_RestSend.json create mode 100644 tests/unit/modules/dcnm/dcnm_image_policy/fixtures/responses_ImagePolicies.json create mode 100644 tests/unit/modules/dcnm/dcnm_image_policy/fixtures/responses_ImagePolicyCommon.json create mode 100644 tests/unit/modules/dcnm/dcnm_image_policy/fixtures/responses_ImagePolicyCreate.json create mode 100644 tests/unit/modules/dcnm/dcnm_image_policy/fixtures/responses_ImagePolicyCreateBulk.json create mode 100644 tests/unit/modules/dcnm/dcnm_image_policy/fixtures/responses_ImagePolicyDelete.json create mode 100644 tests/unit/modules/dcnm/dcnm_image_policy/fixtures/responses_ImagePolicyReplaceBulk.json create mode 100644 tests/unit/modules/dcnm/dcnm_image_policy/fixtures/responses_ImagePolicyUpdate.json create mode 100644 tests/unit/modules/dcnm/dcnm_image_policy/fixtures/responses_ImagePolicyUpdateBulk.json create mode 100644 tests/unit/modules/dcnm/dcnm_image_policy/fixtures/result_current_RestSend.json create mode 100644 tests/unit/modules/dcnm/dcnm_image_policy/fixtures/results_ImagePolicies.json create mode 100644 tests/unit/modules/dcnm/dcnm_image_policy/fixtures/results_ImagePolicyCommon.json create mode 100644 tests/unit/modules/dcnm/dcnm_image_policy/fixtures/results_ImagePolicyCreateBulk.json create mode 100644 tests/unit/modules/dcnm/dcnm_image_policy/fixtures/results_ImagePolicyDelete.json create mode 100644 tests/unit/modules/dcnm/dcnm_image_policy/fixtures/results_ImagePolicyReplaceBulk.json create mode 100644 tests/unit/modules/dcnm/dcnm_image_policy/fixtures/results_ImagePolicyTaskResult.json create mode 100644 tests/unit/modules/dcnm/dcnm_image_policy/fixtures/results_ImagePolicyUpdate.json create mode 100644 tests/unit/modules/dcnm/dcnm_image_policy/fixtures/results_ImagePolicyUpdateBulk.json create mode 100644 tests/unit/modules/dcnm/dcnm_image_policy/test_image_policy_common.py create mode 100644 tests/unit/modules/dcnm/dcnm_image_policy/test_image_policy_create.py create mode 100644 tests/unit/modules/dcnm/dcnm_image_policy/test_image_policy_create_bulk.py create mode 100644 tests/unit/modules/dcnm/dcnm_image_policy/test_image_policy_delete.py create mode 100644 tests/unit/modules/dcnm/dcnm_image_policy/test_image_policy_endpoints.py create mode 100644 tests/unit/modules/dcnm/dcnm_image_policy/test_image_policy_payload.py create mode 100644 tests/unit/modules/dcnm/dcnm_image_policy/test_image_policy_query.py create mode 100644 tests/unit/modules/dcnm/dcnm_image_policy/test_image_policy_replace_bulk.py create mode 100644 tests/unit/modules/dcnm/dcnm_image_policy/test_image_policy_update.py create mode 100644 tests/unit/modules/dcnm/dcnm_image_policy/test_image_policy_update_bulk.py create mode 100644 tests/unit/modules/dcnm/dcnm_image_policy/utils.py diff --git a/__init__.py b/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/docs/cisco.dcnm.dcnm_image_policy_module.rst b/docs/cisco.dcnm.dcnm_image_policy_module.rst new file mode 100644 index 000000000..4ce6ebaaa --- /dev/null +++ b/docs/cisco.dcnm.dcnm_image_policy_module.rst @@ -0,0 +1,416 @@ +.. _cisco.dcnm.dcnm_image_policy_module: + + +**************************** +cisco.dcnm.dcnm_image_policy +**************************** + +**Image policy management for Nexus Dashboard Fabric Controller** + + +Version added: 3.5.0 + +.. contents:: + :local: + :depth: 1 + + +Synopsis +-------- +- Create, delete, modify image policies. + + + + +Parameters +---------- + +.. raw:: html + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ParameterChoices/DefaultsComments
+
+ config + +
+ list + / elements=dictionary + / required +
+
+ +
List of dictionaries containing image policy parameters
+
+
+ agnostic + +
+ boolean +
+
+
    Choices: +
  • no ←
  • +
  • yes
  • +
+
+
The agnostic flag.
+
+
+ description + +
+ string +
+
+ Default:
""
+
+
The image policy description.
+
+
+ epld_image + +
+ string +
+
+ Default:
""
+
+
The epld image name.
+
+
+ name + +
+ string + / required +
+
+ +
The image policy name.
+
+
+ packages + +
+ dictionary +
+
+ +
A dictionary containing two keys, install and uninstall.
+
+
+ install + +
+ list + / elements=string +
+
+ +
A list of packages to install.
+
+
+ uninstall + +
+ list + / elements=string +
+
+ +
A list of packages to uninstall.
+
+
+ platform + +
+ string + / required +
+
+ +
The platform to which the image policy applies e.g. N9K.
+
+
+ release + +
+ string + / required +
+
+ +
The release associated with the image policy.
+
This is derived from the image name as follows.
+
From image name nxos64-cs.10.2.5.M.bin
+
we need to extract version (10.2.5), platform (nxos64-cs), and bits (64bit).
+
The release string conforms to format (version)_(platform)_(bits)
+
so the resulting release string will be 10.2.5_nxos64-cs_64bit
+
+
+ type + +
+ string +
+
+ Default:
"PLATFORM"
+
+
The type of the image policy e.g. PLATFORM.
+
+
+ state + +
+ string +
+
+
    Choices: +
  • deleted
  • +
  • merged ←
  • +
  • overridden
  • +
  • query
  • +
  • replaced
  • +
+
+
The state of the feature or object after module completion
+
+
+ + + + +Examples +-------- + +.. code-block:: yaml + + # This module supports the following states: + # + # deleted: + # Delete image policies from the controller. + # + # If an image policy has references (i.e. it is attached to a device), + # the module will fail. Use dcnm_image_upgrade module, state deleted, + # to detach the image policy from all devices before deleting it. + # + # merged: + # Create (or update) one or more image policies. + # + # If an image policy does not exist on the controller, create it. + # If an image policy already exists on the controller, edit it. + # + # overridden: + # Create/delete one or more image policies. + # + # If an image policy already exists on the controller, delete it and update + # it with the configuration in the playbook task. + # + # Remove any image policies from the controller that are not in the + # playbook task. + # + # query: + # + # Return the configuration for one or more image policies. + # + # replaced: + # + # Replace image policies on the controller with policies in the playbook task. + # + # If an image policy exists on the controller, but not in the playbook task, + # do not delete it or modify it. + # + # Delete two image policies from the controller. + + - name: Delete Image policies + cisco.dcnm.dcnm_image_policy: + state: deleted + config: + - name: KR5M + - name: NR3F + register: result + - name: print result + ansible.builtin.debug: + var: result + + # Merge two image policies into the controller. + + - name: Merge Image policies + cisco.dcnm.dcnm_image_policy: + state: merged + config: + - name: KR5M + agnostic: false + description: KR5M + epld_image: n9000-epld.10.2.5.M.img + packages: + install: + - mtx-openconfig-all-2.0.0.0-10.4.1.src.rpm + uninstall: + - mtx-grpctunnel-2.1.0.0-10.4.1.lib32_64_n9000 + platform: N9K + release: 10.2.5_nxos64-cs_64bit + type: PLATFORM + - name: NR3F + description: NR3F + platform: N9K + epld_image: n9000-epld.10.3.1.F.img + release: 10.3.1_nxos64-cs_64bit + register: result + - name: print result + ansible.builtin.debug: + var: result + + # Override all policies on the controller and replace them with + # the policies in the playbook task. Any policies other than + # KR5M and NR3F are deleted from the controller. + + - name: Override Image policies + cisco.dcnm.dcnm_image_policy: + state: overridden + config: + - name: KR5M + agnostic: false + description: KR5M + epld_image: n9000-epld.10.2.5.M.img + platform: N9K + release: 10.2.5_nxos64-cs_64bit + type: PLATFORM + - name: NR3F + description: NR3F + platform: N9K + epld_image: n9000-epld.10.2.5.M.img + release: 10.3.1_nxos64-cs_64bit + register: result + - name: print result + ansible.builtin.debug: + var: result + + # Query the controller for the policies in the playbook task. + + - name: Query Image policies + cisco.dcnm.dcnm_image_policy: + state: query + config: + - name: NR3F + - name: KR5M + register: result + - name: print result + ansible.builtin.debug: + var: result + + # Replace any policies on the controller that are in the playbook task with + # the configuration given in the playbook task. Policies not listed in the + # playbook task are not modified and are not deleted. + + - name: Replace Image policies + cisco.dcnm.dcnm_image_policy: + state: replaced + config: + - name: KR5M + agnostic: false + description: KR5M + epld_image: n9000-epld.10.2.5.M.img + platform: N9K + release: 10.2.5_nxos64-cs_64bit + type: PLATFORM + - name: NR3F + description: Replaced NR3F + platform: N9K + epld_image: n9000-epld.10.3.1.F.img + release: 10.3.1_nxos64-cs_64bit + register: result + - name: print result + ansible.builtin.debug: + var: result + + + + +Status +------ + + +Authors +~~~~~~~ + +- Allen Robel (@quantumonion) diff --git a/plugins/module_utils/common/README_MERGE_TODO.txt b/plugins/module_utils/common/README_MERGE_TODO.txt new file mode 100644 index 000000000..3a3edd29e --- /dev/null +++ b/plugins/module_utils/common/README_MERGE_TODO.txt @@ -0,0 +1,9 @@ +The following files in this directory need to be deleted prior to merging since these are being committed with dcnm_image_upgrade. + +The single source of truth for these is dcnm_image_upgrade. + +log.py +logging_config.json +merge_dicts.py +params_merge_defaults.py +params_validate.py diff --git a/plugins/module_utils/image_policy/__init__.py b/plugins/module_utils/image_policy/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/plugins/module_utils/image_policy/common.py b/plugins/module_utils/image_policy/common.py new file mode 100644 index 000000000..3ce87f785 --- /dev/null +++ b/plugins/module_utils/image_policy/common.py @@ -0,0 +1,228 @@ +# Copyright (c) 2024 Cisco and/or its affiliates. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type +__author__ = "Allen Robel" + +import inspect +import logging +from typing import Any, Dict + + +class ImagePolicyCommon: + """ + Common methods used by the other classes supporting + dcnm_image_policy module + + Usage (where ansible_module is an instance of + AnsibleModule or MockAnsibleModule): + + class MyClass(ImagePolicyCommon): + def __init__(self, module): + super().__init__(module) + ... + """ + + def __init__(self, ansible_module): + self.class_name = self.__class__.__name__ + + self.log = logging.getLogger(f"dcnm.{self.class_name}") + self.log.debug("ENTERED ImagePolicyCommon()") + + self.ansible_module = ansible_module + self.check_mode = self.ansible_module.check_mode + self.state = ansible_module.params["state"] + + self.params = ansible_module.params + + self.properties: Dict[str, Any] = {} + self.properties["results"] = None + + def _verify_image_policy_ref_count(self, instance, policy_names): + """ + instance: ImagePolicies() instance + policy_names: list of policy names + + Verify that all image policies in policy_names have a + ref_count of 0 (i.e. no devices are using the policy). + + If the ref_count is greater than 0, fail_json with a message + indicating that the policy, or policies, must be detached from + all devices before it/they can be deleted. + """ + method_name = inspect.stack()[0][3] + _non_zero_ref_counts = {} + for policy_name in policy_names: + instance.policy_name = policy_name + msg = f"instance.policy_name: {instance.policy_name}, " + msg += f"instance.ref_count: {instance.ref_count}." + self.log.debug(msg) + # If the policy does not exist on the controller, the ref_count + # will be None. We skip these too. + if instance.ref_count in [0, None]: + continue + _non_zero_ref_counts[policy_name] = instance.ref_count + if len(_non_zero_ref_counts) == 0: + return + msg = f"{self.class_name}.{method_name}: " + msg += "One or more policies have devices attached. " + msg += "Detach these policies from all devices first using " + msg += "the dcnm_image_upgrade module, with state == deleted. " + for policy_name, ref_count in _non_zero_ref_counts.items(): + msg += f"policy_name: {policy_name}, " + msg += f"ref_count: {ref_count}. " + self.ansible_module.fail_json(msg, **self.results.failed_result) + + def _default_policy(self, policy_name): + """ + Return a default policy payload for policy name. + """ + method_name = inspect.stack()[0][3] + if not isinstance(policy_name, str): + msg = f"{self.class_name}.{method_name}: " + msg += "policy_name must be a string. " + msg += f"Got type {type(policy_name).__name__} for " + msg += f"value {policy_name}." + self.log.debug(msg) + self.ansible_module.fail_json(msg, **self.results.failed_result) + + policy = { + "agnostic": False, + "epldImgName": "", + "nxosVersion": "", + "packageName": "", + "platform": "", + "policyDescr": "", + "policyName": policy_name, + "policyType": "PLATFORM", + "rpmimages": "", + } + return policy + + def _handle_response(self, response, verb): + """ + Call the appropriate handler for response based on verb + """ + if verb == "GET": + return self._handle_get_response(response) + if verb in {"POST", "PUT", "DELETE"}: + return self._handle_post_put_delete_response(response) + return self._handle_unknown_request_verbs(response, verb) + + def _handle_unknown_request_verbs(self, response, verb): + method_name = inspect.stack()[0][3] + + msg = f"{self.class_name}.{method_name}: " + msg += f"Unknown request verb ({verb}) for response {response}." + self.ansible_module.fail_json(msg) + + def _handle_get_response(self, response): + """ + Caller: + - self._handle_response() + Handle controller responses to GET requests + Returns: dict() with the following keys: + - found: + - False, if request error was "Not found" and RETURN_CODE == 404 + - True otherwise + - success: + - False if RETURN_CODE != 200 or MESSAGE != "OK" + - True otherwise + """ + result = {} + success_return_codes = {200, 404} + if ( + response.get("RETURN_CODE") == 404 + and response.get("MESSAGE") == "Not Found" + ): + result["found"] = False + result["success"] = True + return result + if ( + response.get("RETURN_CODE") not in success_return_codes + or response.get("MESSAGE") != "OK" + ): + result["found"] = False + result["success"] = False + return result + result["found"] = True + result["success"] = True + return result + + def _handle_post_put_delete_response(self, response): + """ + Caller: + - self.self._handle_response() + + Handle POST, PUT, DELETE responses from the controller. + + Returns: dict() with the following keys: + - changed: + - True if changes were made to by the controller + - ERROR key is not present + - MESSAGE == "OK" + - False otherwise + - success: + - False if MESSAGE != "OK" or ERROR key is present + - True otherwise + """ + result = {} + if response.get("ERROR") is not None: + result["success"] = False + result["changed"] = False + return result + if response.get("MESSAGE") != "OK" and response.get("MESSAGE") is not None: + result["success"] = False + result["changed"] = False + return result + result["success"] = True + result["changed"] = True + return result + + def make_boolean(self, value): + """ + Return value converted to boolean, if possible. + Return value, if value cannot be converted. + """ + if isinstance(value, bool): + return value + if isinstance(value, str): + if value.lower() in ["true", "yes"]: + return True + if value.lower() in ["false", "no"]: + return False + return value + + def make_none(self, value): + """ + Return None if value is an empty string, or a string + representation of a None type + Return value otherwise + """ + if value in ["", "none", "None", "NONE", "null", "Null", "NULL"]: + return None + return value + + @property + def results(self): + """ + An instance of the Results class. + """ + return self.properties["results"] + + @results.setter + def results(self, value): + self.properties["results"] = value diff --git a/plugins/module_utils/image_policy/create.py b/plugins/module_utils/image_policy/create.py new file mode 100644 index 000000000..37f1503c3 --- /dev/null +++ b/plugins/module_utils/image_policy/create.py @@ -0,0 +1,336 @@ +# Copyright (c) 2024 Cisco and/or its affiliates. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type +__author__ = "Allen Robel" + +import copy +import inspect +import json +import logging + +from ansible_collections.cisco.dcnm.plugins.module_utils.common.rest_send import \ + RestSend +from ansible_collections.cisco.dcnm.plugins.module_utils.image_policy.common import \ + ImagePolicyCommon +from ansible_collections.cisco.dcnm.plugins.module_utils.image_policy.endpoints import \ + ApiEndpoints +from ansible_collections.cisco.dcnm.plugins.module_utils.image_policy.image_policies import \ + ImagePolicies +from ansible_collections.cisco.dcnm.plugins.module_utils.common.results import \ + Results + + +class ImagePolicyCreateCommon(ImagePolicyCommon): + """ + Common methods and properties for: + - ImagePolicyCreate + - ImagePolicyCreateBulk + """ + + def __init__(self, ansible_module): + super().__init__(ansible_module) + self.class_name = self.__class__.__name__ + self.action = "create" + + self.log = logging.getLogger(f"dcnm.{self.class_name}") + + self._image_policies = ImagePolicies(self.ansible_module) + self._image_policies.results = Results() + + self.endpoints = ApiEndpoints() + self.rest_send = RestSend(self.ansible_module) + + self.path = self.endpoints.policy_create.get("path") + self.verb = self.endpoints.policy_create.get("verb") + + self._payloads_to_commit = [] + + self._mandatory_payload_keys = set() + self._mandatory_payload_keys.add("nxosVersion") + self._mandatory_payload_keys.add("policyName") + self._mandatory_payload_keys.add("policyType") + + msg = "ENTERED ImagePolicyCreateCommon(): " + msg += f"action: {self.action}, " + msg += f"check_mode: {self.check_mode}, " + msg += f"state: {self.state}" + self.log.debug(msg) + + def _verify_payload(self, payload): + """ + Verify that the payload is a dict and contains all mandatory keys + """ + method_name = inspect.stack()[0][3] + if not isinstance(payload, dict): + msg = f"{self.class_name}.{method_name}: " + msg += "payload must be a dict. " + msg += f"Got type {type(payload).__name__}, " + msg += f"value {payload}" + self.ansible_module.fail_json(msg, **self.results.failed_result) + + missing_keys = [] + for key in self._mandatory_payload_keys: + if key not in payload: + missing_keys.append(key) + if len(missing_keys) == 0: + return + + msg = f"{self.class_name}.{method_name}: " + msg += "payload is missing mandatory keys: " + msg += f"{sorted(missing_keys)}" + self.ansible_module.fail_json(msg, **self.results.failed_result) + + def _build_payloads_to_commit(self): + """ + Build a list of payloads to commit. Skip any payloads that + already exist on the controller. + + Expects self.payloads to be a list of dict, with each dict + being a payload for the image policy create API endpoint. + + Populates self._payloads_to_commit with a list of payloads + to commit. + """ + self._image_policies.refresh() + + self._payloads_to_commit = [] + for payload in self.payloads: + if payload.get("policyName", None) in self._image_policies.all_policies: + continue + self._payloads_to_commit.append(copy.deepcopy(payload)) + msg = f"self._payloads_to_commit: {json.dumps(self._payloads_to_commit, indent=4, sort_keys=True)}" + self.log.debug(msg) + + def _send_payloads(self): + """ + If check_mode is False, send the payloads to the controller + If check_mode is True, do not send the payloads to the controller + + In both cases, update results + """ + self.rest_send.check_mode = self.check_mode + + for payload in self._payloads_to_commit: + + # We don't want RestSend to retry on errors since the likelihood of a + # timeout error when creating an image policy is low, and there are + # cases of permanent errors for which we don't want to retry. + self.rest_send.timeout = 1 + + self.rest_send.path = self.path + self.rest_send.verb = self.verb + self.rest_send.payload = payload + self.rest_send.commit() + + msg = f"rest_send.result_current: {json.dumps(self.rest_send.result_current, indent=4, sort_keys=True)}" + self.log.debug(msg) + + if self.rest_send.result_current["success"] is False: + self.results.diff_current = {} + else: + self.results.diff_current = copy.deepcopy(payload) + + self.results.action = self.action + self.results.state = self.state + self.results.check_mode = self.check_mode + self.results.response_current = copy.deepcopy(self.rest_send.response_current) + self.results.result_current = copy.deepcopy(self.rest_send.result_current) + self.results.register_task_result() + + @property + def payloads(self): + """ + Return the image policy payloads + + Payloads must be a list of dict. Each dict is a + payload for the image policy create API endpoint. + """ + return self.properties["payloads"] + + @payloads.setter + def payloads(self, value): + method_name = inspect.stack()[0][3] + if not isinstance(value, list): + msg = f"{self.class_name}.{method_name}: " + msg += "payloads must be a list of dict. " + msg += f"got {type(value).__name__} for " + msg += f"value {value}" + self.ansible_module.fail_json(msg, **self.results.failed_result) + for item in value: + self._verify_payload(item) + self.properties["payloads"] = value + + +class ImagePolicyCreateBulk(ImagePolicyCreateCommon): + """ + Given a properly-constructed list of payloads, bulk-create the + image policies therein. The payload format is given below. + + Payload format: + agnostic bool(), optional. true or false + epldImgName str(), optional. name of an EPLD image to install. + nxosVersion str(), required. NX-OS version as version_type_arch + packageName: str(), optional, A comma-separated list of packages + platform: str(), optional, one of N9K, N6K, N5K, N3K + policyDesc str(), optional, description for the image policy + policyName: str(), required. Name of the image policy. + policyType str(), required. PLATFORM or UMBRELLA + rpmimages: str(), optional. A comma-separated list of packages to uninstall + + Example list of payloads: + + [ + { + "agnostic": False, + "epldImgName": "n9000-epld.10.3.2.F.img", + "nxosVersion": "10.3.1_nxos64-cs_64bit", + "packageName": "mtx-openconfig-all-2.0.0.0-10.4.1.src.rpm", + "platform": "N9K", + "policyDescr": "image policy of 10.3(3)F", + "policyName": "FOO", + "policyType": "PLATFORM", + "rpmimages": "mtx-grpctunnel-2.1.0.0-10.4.1.lib32_64_n9000" + }, + { + "nxosVersion": "10.3.1_nxos64-cs_64bit", + "policyName": "FOO", + "policyType": "PLATFORM" + } + ] + """ + + def __init__(self, ansible_module): + super().__init__(ansible_module) + self.class_name = self.__class__.__name__ + + self.log = logging.getLogger(f"dcnm.{self.class_name}") + + msg = "ENTERED ImagePolicyCreateBulk():" + self.log.debug(msg) + + self._build_properties() + + def _build_properties(self): + """ + Add properties specific to this class + """ + # properties dict is already initialized in the parent class + self.properties["payloads"] = None + + def commit(self): + """ + create policies. Skip any policies that already exist + on the controller, + """ + method_name = inspect.stack()[0][3] + + if self.payloads is None: + msg = f"{self.class_name}.{method_name}: " + msg += "payloads must be set prior to calling commit." + self.ansible_module.fail_json(msg, **self.results.failed_result) + + self._build_payloads_to_commit() + if len(self._payloads_to_commit) == 0: + return + self._send_payloads() + + +class ImagePolicyCreate(ImagePolicyCreateCommon): + """ + NOTE: This class is not being used currently. + + Given a properly-constructed image policy payload (python dict), + send an image policy create request to the controller. The payload + format is given below. + + agnostic bool(), optional. true or false + epldImgName str(), optional. name of an EPLD image to install. + nxosVersion str(), required. NX-OS version as version_type_arch + packageName: str(), optional, A comma-separated list of packages + platform: str(), optional, one of N9K, N6K, N5K, N3K + policyDesc str(), optional, description for the image policy + policyName: str(), required. Name of the image policy. + policyType str(), required. PLATFORM or UMBRELLA + rpmimages: str(), optional. A comma-separated list of packages to uninstall + + Example: + + { + "agnostic": false, + "epldImgName": "n9000-epld.10.3.2.F.img", + "nxosVersion": "10.3.1_nxos64-cs_64bit", + "packageName": "mtx-openconfig-all-2.0.0.0-10.4.1.src.rpm", + "platform": "N9K", + "policyDescr": "image policy of 10.3(3)F", + "policyName": "FOO", + "policyType": "PLATFORM", + "rpmimages": "mtx-grpctunnel-2.1.0.0-10.4.1.lib32_64_n9000" + } + + """ + + def __init__(self, ansible_module): + super().__init__(ansible_module) + self.class_name = self.__class__.__name__ + + self.log = logging.getLogger(f"dcnm.{self.class_name}") + + msg = "ENTERED ImagePolicyCreate(): " + self.log.debug(msg) + + self.data = {} + self.rest_send = RestSend(self.ansible_module) + + self._init_properties() + + def _init_properties(self): + """ + Add properties specific to this class + """ + # properties is already initialized in the parent class + self.properties["payload"] = None + + @property + def payload(self): + """ + This class expects a properly-defined image policy payload. + See class docstring for the payload structure. + """ + return self.properties["payload"] + + @payload.setter + def payload(self, value): + self._verify_payload(value) + self.properties["payloads"] = [value] + self.properties["payload"] = value + + def commit(self): + """ + Create policy. + If policy already exists on the controller, do nothing. + """ + method_name = inspect.stack()[0][3] + if self.payload is None: + msg = f"{self.class_name}.{method_name}: " + msg += "payload must be set prior to calling commit." + self.ansible_module.fail_json(msg, **self.results.failed_result) + + self._build_payloads_to_commit() + + if len(self._payloads_to_commit) == 0: + return + self._send_payloads() diff --git a/plugins/module_utils/image_policy/delete.py b/plugins/module_utils/image_policy/delete.py new file mode 100644 index 000000000..9596f6a83 --- /dev/null +++ b/plugins/module_utils/image_policy/delete.py @@ -0,0 +1,204 @@ +# Copyright (c) 2024 Cisco and/or its affiliates. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type +__author__ = "Allen Robel" + +import copy +import inspect +import json +import logging + +from ansible_collections.cisco.dcnm.plugins.module_utils.common.rest_send import \ + RestSend +from ansible_collections.cisco.dcnm.plugins.module_utils.image_policy.common import \ + ImagePolicyCommon +from ansible_collections.cisco.dcnm.plugins.module_utils.image_policy.endpoints import \ + ApiEndpoints +from ansible_collections.cisco.dcnm.plugins.module_utils.image_policy.image_policies import \ + ImagePolicies +from ansible_collections.cisco.dcnm.plugins.module_utils.common.results import \ + Results + + +class ImagePolicyDelete(ImagePolicyCommon): + """ + Delete image policies + + Usage: + + instance = ImagePolicyDelete(ansible_module) + instance.policy_names = ["IMAGE_POLICY_1", "IMAGE_POLICY_2"] + instance.commit() + """ + + def __init__(self, ansible_module): + super().__init__(ansible_module) + self.class_name = self.__class__.__name__ + self.action = "delete" + + self.log = logging.getLogger(f"dcnm.{self.class_name}") + + self._policies_to_delete = [] + self._build_properties() + self.endpoints = ApiEndpoints() + self._image_policies = ImagePolicies(self.ansible_module) + self._image_policies.results = Results() + self.rest_send = RestSend(self.ansible_module) + + self.path = self.endpoints.policy_delete["path"] + self.verb = self.endpoints.policy_delete["verb"] + + msg = "ENTERED ImagePolicyDelete(): " + msg += f"action: {self.action}, " + msg += f"check_mode: {self.check_mode}, " + msg += f"state: {self.state}" + self.log.debug(msg) + + def _build_properties(self): + """ + self.properties holds property values for the class + """ + # self.properties is already set in the parent class + self.properties["policy_names"] = None + + def _get_policies_to_delete(self) -> None: + """ + Retrieve policies from the controller and return the list of + controller policies that are in our policy_names list. + """ + self._image_policies.refresh() + self._verify_image_policy_ref_count(self._image_policies, self.policy_names) + + self._policies_to_delete = [] + for policy_name in self.policy_names: + if policy_name in self._image_policies.all_policies: + msg = f"Policy {policy_name} exists on the controller. " + msg += f"Appending {policy_name} to _policies_to_delete." + self.log.debug(msg) + self._policies_to_delete.append(policy_name) + + def _validate_commit_parameters(self): + """ + validate the parameters for commit + """ + method_name = inspect.stack()[0][3] # pylint: disable=unused-variable + if self.policy_names is None: + msg = f"{self.class_name}.{method_name}: " + msg += "policy_names must be set prior to calling commit." + self.ansible_module.fail_json(msg, **self.results.failed_result) + + def commit(self): + """ + delete each of the image policies in self.policy_names + """ + method_name = inspect.stack()[0][3] # pylint: disable=unused-variable + self._validate_commit_parameters() + + self._get_policies_to_delete() + + msg = f"self._policies_to_delete: {self._policies_to_delete}" + self.log.debug(msg) + if len(self._policies_to_delete) != 0: + self._send_requests() + else: + self.results.action = self.action + self.results.check_mode = self.check_mode + self.results.state = self.state + self.results.diff_current = {} + self.results.result_current = {"success": True, "changed": False} + msg = "No image policies to delete" + self.results.changed = False + self.results.failed = False + self.results.response_current = {"RETURN_CODE": 200, "MESSAGE": msg} + self.log.debug(msg) + + def _send_requests(self): + """ + If check_mode is False, send the requests to the controller + If check_mode is True, do not send the requests to the controller + + In both cases, populate the following lists: + + - self.response_ok : list of controller responses associated with success result + - self.result_ok : list of results where success is True + - self.diff_ok : list of payloads for which the request succeeded + - self.response_nok : list of controller responses associated with failed result + - self.result_nok : list of results where success is False + - self.diff_nok : list of payloads for which the request failed + """ + self.rest_send.check_mode = self.check_mode + + # We don't want RestSend to retry on errors since the likelihood of a + # timeout error when deleting image policies is low, and there + # are cases of permanent errors for which we don't want to retry. + self.rest_send.timeout = 1 + + msg = f"Deleting policies {self._policies_to_delete}" + self.log.debug(msg) + + self.payload = {"policyNames": self._policies_to_delete} + self.rest_send.path = self.path + self.rest_send.verb = self.verb + self.rest_send.payload = copy.deepcopy(self.payload) + self.rest_send.commit() + + self.register_result() + + def register_result(self): + """ + Register the result of the fabric create request + """ + msg = f"self.rest_send.result_current: {self.rest_send.result_current}" + self.log.debug(msg) + if self.rest_send.result_current["success"]: + self.results.failed = False + self.results.diff_current = self.payload + else: + self.results.diff_current = {} + self.results.failed = True + + self.results.action = self.action + self.results.check_mode = self.check_mode + self.results.state = self.state + self.results.result_current = self.rest_send.result_current + self.results.response_current = self.rest_send.response_current + self.results.register_task_result() + + @property + def policy_names(self): + """ + return the policy names + """ + return self.properties["policy_names"] + + @policy_names.setter + def policy_names(self, value): + method_name = inspect.stack()[0][3] + if not isinstance(value, list): + msg = f"{self.class_name}.{method_name}: " + msg += "policy_names must be a list. " + msg += f"got {type(value).__name__} for " + msg += f"value {value}" + self.ansible_module.fail_json(msg) + for item in value: + if not isinstance(item, str): + msg = f"{self.class_name}.{method_name}: " + msg += "policy_names must be a list of strings. " + msg += f"got {type(item).__name__} for " + msg += f"value {item}" + self.ansible_module.fail_json(msg) + self.properties["policy_names"] = value diff --git a/plugins/module_utils/image_policy/endpoints.py b/plugins/module_utils/image_policy/endpoints.py new file mode 100644 index 000000000..ff41dca7e --- /dev/null +++ b/plugins/module_utils/image_policy/endpoints.py @@ -0,0 +1,138 @@ +# Copyright (c) 2024 Cisco and/or its affiliates. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type +__author__ = "Allen Robel" + +import logging + + +class ApiEndpoints: + """ + Endpoints for image policy API calls + """ + + def __init__(self): + self.class_name = self.__class__.__name__ + + self.log = logging.getLogger(f"dcnm.{self.class_name}") + self.log.debug("ENTERED ApiEndpoints()") + + self.endpoint_api_v1 = "/appcenter/cisco/ndfc/api/v1" + + self.endpoint_image_management = f"{self.endpoint_api_v1}" + self.endpoint_image_management += "/imagemanagement" + + self.endpoint_policy_mgnt = f"{self.endpoint_image_management}" + self.endpoint_policy_mgnt += "/rest/policymgnt" + + @property + def policies_attached_info(self): + """ + return endpoint GET /rest/policymgnt/all-attached-policies + """ + path = f"{self.endpoint_policy_mgnt}/all-attached-policies" + endpoint = {} + endpoint["path"] = path + endpoint["verb"] = "GET" + return endpoint + + @property + def policies_info(self): + """ + return endpoint GET /rest/policymgnt/policies + """ + path = f"{self.endpoint_policy_mgnt}/policies" + endpoint = {} + endpoint["path"] = path + endpoint["verb"] = "GET" + return endpoint + + @property + def policy_attach(self): + """ + return endpoint POST /rest/policymgnt/attach-policy + """ + path = f"{self.endpoint_policy_mgnt}/attach-policy" + endpoint = {} + endpoint["path"] = path + endpoint["verb"] = "POST" + return endpoint + + @property + def policy_create(self): + """ + return endpoint POST /rest/policymgnt/platform-policy + """ + path = f"{self.endpoint_policy_mgnt}/platform-policy" + endpoint = {} + endpoint["path"] = path + endpoint["verb"] = "POST" + return endpoint + + @property + def policy_delete(self): + """ + return endpoint DELETE /rest/policymgnt/policy + This expects a request body with the following: + + policyNames: comma separated list of policy names to delete. + + { + "policyNames": "policyA,policyB,etc" + } + """ + path = f"{self.endpoint_policy_mgnt}/policy" + endpoint = {} + endpoint["path"] = path + endpoint["verb"] = "DELETE" + return endpoint + + @property + def policy_detach(self): + """ + return endpoint DELETE /rest/policymgnt/detach-policy + """ + path = f"{self.endpoint_policy_mgnt}/detach-policy" + endpoint = {} + endpoint["path"] = path + endpoint["verb"] = "DELETE" + return endpoint + + @property + def policy_edit(self): + """ + return endpoint POST /rest/policymgnt/edit-policy + """ + path = f"{self.endpoint_policy_mgnt}/edit-policy" + endpoint = {} + endpoint["path"] = path + endpoint["verb"] = "POST" + return endpoint + + @property + def policy_info(self): + """ + return endpoint GET /rest/policymgnt/image-policy/__POLICY_NAME__ + + Replace __POLICY_NAME__ with the policy_name to query + e.g. path.replace("__POLICY_NAME__", "NR1F") + """ + path = f"{self.endpoint_policy_mgnt}/image-policy/__POLICY_NAME__" + endpoint = {} + endpoint["path"] = path + endpoint["verb"] = "GET" + return endpoint diff --git a/plugins/module_utils/image_policy/image_policies.py b/plugins/module_utils/image_policy/image_policies.py new file mode 100644 index 000000000..946fd6e23 --- /dev/null +++ b/plugins/module_utils/image_policy/image_policies.py @@ -0,0 +1,300 @@ +# Copyright (c) 2024 Cisco and/or its affiliates. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type +__author__ = "Allen Robel" + +import copy +import inspect +import logging +from typing import Any, AnyStr, Dict + +from ansible_collections.cisco.dcnm.plugins.module_utils.common.rest_send import \ + RestSend +from ansible_collections.cisco.dcnm.plugins.module_utils.image_policy.common import \ + ImagePolicyCommon +from ansible_collections.cisco.dcnm.plugins.module_utils.image_policy.endpoints import \ + ApiEndpoints + + +class ImagePolicies(ImagePolicyCommon): + """ + Retrieve image policy details from the controller and provide + property accessors for the policy attributes. + + Usage (where module is an instance of AnsibleModule): + + instance = ImagePolicies(module).refresh() + instance.policy_name = "NR3F" + if instance.name is None: + print("policy NR3F does not exist on the controller") + exit(1) + policy_name = instance.name + platform = instance.platform + epd_image_name = instance.epld_image_name + etc... + + Policies can be refreshed by calling instance.refresh(). + + Endpoint: + /appcenter/cisco/ndfc/api/v1/imagemanagement/rest/policymgnt/policies + """ + + def __init__(self, ansible_module): + super().__init__(ansible_module) + self.class_name = self.__class__.__name__ + + self.log = logging.getLogger(f"dcnm.{self.class_name}") + self.log.debug("ENTERED ImagePolicies()") + + self.method_name = inspect.stack()[0][3] # pylint: disable=unused-variable + self.endpoints = ApiEndpoints() + self.rest_send = RestSend(self.ansible_module) + + # We always want to get the controller's current image policy + # state so we set check_mode to False here so the request will be + # sent to the controller + self.rest_send.check_mode = False + + self._init_properties() + + def _init_properties(self): + self.method_name = inspect.stack()[0][3] # pylint: disable=unused-variable + # self.properties is already initialized in the parent class + self.properties["all_policies"] = None + self.properties["response_data"] = None + self.properties["policy_name"] = None + + def refresh(self): + """ + Refresh the image policy details from the controller and + populate self.data with the results. + + self.data is a dictionary of image policy details, keyed on + image policy name. + """ + method_name = inspect.stack()[0][3] # pylint: disable=unused-variable + + self.rest_send.path = self.endpoints.policies_info.get("path") + self.rest_send.verb = self.endpoints.policies_info.get("verb") + self.rest_send.commit() + + data = self.rest_send.response_current.get("DATA", {}).get("lastOperDataObject") + + if data is None: + msg = f"{self.class_name}.{self.method_name}: " + msg += "Bad response when retrieving image policy " + msg += "information from the controller." + self.ansible_module.fail_json(msg, **self.results.failed_result) + + if len(data) == 0: + msg = "the controller has no defined image policies." + self.log.debug(msg) + + self.properties["response_data"] = {} + self.properties["all_policies"] = {} + self.data = {} + + for policy in data: + policy_name = policy.get("policyName") + if policy_name is None: + msg = f"{self.class_name}.{self.method_name}: " + msg += "Cannot parse policy information from the controller." + self.ansible_module.fail_json(msg, **self.results.failed_result) + self.data[policy_name] = policy + self.properties["response_data"][policy_name] = policy + + self.properties["all_policies"] = copy.deepcopy( + self.properties["response_data"] + ) + + self.results.response_current = self.rest_send.response_current + self.results.response = self.rest_send.response_current + self.results.result_current = self.rest_send.result_current + self.results.result = self.rest_send.result_current + + def _get(self, item): + self.method_name = inspect.stack()[0][3] + + if self.policy_name is None: + msg = f"{self.class_name}.{self.method_name}: " + msg += "instance.policy_name must be set before " + msg += f"accessing property {item}." + self.ansible_module.fail_json(msg, **self.failed_result) + + if self.policy_name not in self.properties["response_data"]: + return None + + if item == "policy": + return self.properties["response_data"][self.policy_name] + + if item not in self.properties["response_data"][self.policy_name]: + msg = f"{self.class_name}.{self.method_name}: " + msg += f"{self.policy_name} does not have a key named {item}." + self.ansible_module.fail_json(msg, **self.failed_result) + + return self.make_boolean( + self.make_none(self.properties["response_data"][self.policy_name][item]) + ) + + @property + def all_policies(self) -> Dict[AnyStr, Any]: + """ + Return dict containing all policies, keyed on policy_name + """ + if self.properties["all_policies"] is None: + return {} + return self.properties["all_policies"] + + @property + def description(self): + """ + Return the policyDescr of the policy matching self.policy_name, + if it exists. + Return None otherwise + """ + return self._get("policyDescr") + + @property + def epld_image_name(self): + """ + Return the epldImgName of the policy matching self.policy_name, + if it exists. + Return None otherwise + """ + return self._get("epldImgName") + + @property + def name(self): + """ + Return the name of the policy matching self.policy_name, + if it exists. + Return None otherwise + """ + return self._get("policyName") + + @property + def policy_name(self): + """ + Set the name of the policy to query. + + This must be set prior to accessing any other properties + """ + return self.properties.get("policy_name") + + @policy_name.setter + def policy_name(self, value): + self.properties["policy_name"] = value + + @property + def policy(self): + """ + Return the policy data of the policy matching self.policy_name, + if it exists. + Return None otherwise + """ + return self._get("policy") + + @property + def policy_type(self): + """ + Return the policyType of the policy matching self.policy_name, + if it exists. + Return None otherwise + """ + return self._get("policyType") + + @property + def response_data(self) -> Dict[AnyStr, Any]: + """ + Return dict containing the DATA portion of a controller response, keyed on policy_name + """ + if self.properties["response_data"] is None: + return {} + return self.properties["response_data"] + + @property + def nxos_version(self): + """ + Return the nxosVersion of the policy matching self.policy_name, + if it exists. + Return None otherwise + """ + return self._get("nxosVersion") + + @property + def package_name(self): + """ + Return the packageName of the policy matching self.policy_name, + if it exists. + Return None otherwise + """ + return self._get("packageName") + + @property + def platform(self): + """ + Return the platform of the policy matching self.policy_name, + if it exists. + Return None otherwise + """ + return self._get("platform") + + @property + def platform_policies(self): + """ + Return the platformPolicies of the policy matching self.policy_name, + if it exists. + Return None otherwise + """ + return self._get("platformPolicies") + + @property + def ref_count(self): + """ + Return the reference count of the policy matching self.policy_name, + if it exists. The reference count is the number of switches using + this policy. + Return None otherwise + """ + return self._get("ref_count") + + @property + def rpm_images(self): + """ + Return the rpmimages of the policy matching self.policy_name, + if it exists. + Return None otherwise + """ + return self._get("rpmimages") + + @property + def image_name(self): + """ + Return the imageName of the policy matching self.policy_name, + if it exists. + Return None otherwise + """ + return self._get("imageName") + + @property + def agnostic(self): + """ + Return the value of agnostic for the policy matching self.policy_name, + if it exists. + Return None otherwise + """ + return self._get("agnostic") diff --git a/plugins/module_utils/image_policy/params_spec.py b/plugins/module_utils/image_policy/params_spec.py new file mode 100644 index 000000000..a4494292c --- /dev/null +++ b/plugins/module_utils/image_policy/params_spec.py @@ -0,0 +1,214 @@ +# Copyright (c) 2024 Cisco and/or its affiliates. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type +__author__ = "Allen Robel" + +import inspect +import logging +from typing import Any, Dict + + +class ParamsSpec: + """ + Parameter specifications for the dcnm_image_policy module. + """ + + def __init__(self, ansible_module): + self.class_name = self.__class__.__name__ + self.ansible_module = ansible_module + + self.log = logging.getLogger(f"dcnm.{self.class_name}") + self.log.debug("ENTERED ParamsSpec()") + + self._params_spec: Dict[str, Any] = {} + + def commit(self): + """ + build the parameter specification based on the state + """ + method_name = inspect.stack()[0][3] + + if self.ansible_module.params["state"] is None: + self.ansible_module.fail_json(msg="state is None") + + if self.ansible_module.params["state"] == "merged": + # self._build_params_spec_for_merged_state() + self._build_params_spec_for_merged_state_proposed() + elif self.ansible_module.params["state"] == "replaced": + self._build_params_spec_for_replaced_state() + elif self.ansible_module.params["state"] == "overridden": + self._build_params_spec_for_overridden_state() + elif self.ansible_module.params["state"] == "deleted": + self._build_params_spec_for_deleted_state() + elif self.ansible_module.params["state"] == "query": + self._build_params_spec_for_query_state() + else: + msg = f"{self.class_name}.{method_name}: " + msg += f"Invalid state {self.ansible_module.params['state']}" + self.ansible_module.fail_json(msg) + + def _build_params_spec_for_merged_state(self) -> None: + """ + Build the specs for the parameters expected when state == merged. + + Caller: _validate_configs() + Return: params_spec, a dictionary containing playbook + parameter specifications. + """ + # print("Building params spec for merged state") + self._params_spec: Dict[str, Any] = {} + + self._params_spec["agnostic"] = {} + self._params_spec["agnostic"]["required"] = False + self._params_spec["agnostic"]["type"] = "bool" + self._params_spec["agnostic"]["default"] = False + + self._params_spec["description"] = {} + self._params_spec["description"]["default"] = "" + self._params_spec["description"]["required"] = False + self._params_spec["description"]["type"] = "str" + + self._params_spec["disabled_rpm"] = {} + self._params_spec["disabled_rpm"]["default"] = "" + self._params_spec["disabled_rpm"]["required"] = False + self._params_spec["disabled_rpm"]["type"] = "str" + + self._params_spec["epld_image"] = {} + self._params_spec["epld_image"]["default"] = "" + self._params_spec["epld_image"]["required"] = False + self._params_spec["epld_image"]["type"] = "str" + + self._params_spec["name"] = {} + self._params_spec["name"]["required"] = True + self._params_spec["name"]["type"] = "str" + + self._params_spec["platform"] = {} + self._params_spec["platform"]["required"] = True + self._params_spec["platform"]["type"] = "str" + self._params_spec["platform"]["choices"] = ["N9K", "N7K", "N77", "N6K", "N5K"] + + self._params_spec["release"] = {} + self._params_spec["release"]["required"] = True + self._params_spec["release"]["type"] = "str" + + self._params_spec["packages"] = {} + self._params_spec["packages"]["default"] = [] + self._params_spec["packages"]["required"] = False + self._params_spec["packages"]["type"] = "list" + + def _build_params_spec_for_merged_state_proposed(self) -> None: + """ + Build the specs for the parameters expected when state == merged. + + Caller: _validate_configs() + Return: params_spec, a dictionary containing playbook + parameter specifications. + """ + # print("Building params spec for merged state PROPOSED") + self._params_spec: Dict[str, Any] = {} + + self._params_spec["agnostic"] = {} + self._params_spec["agnostic"]["default"] = False + self._params_spec["agnostic"]["required"] = False + self._params_spec["agnostic"]["type"] = "bool" + + self._params_spec["description"] = {} + self._params_spec["description"]["default"] = "" + self._params_spec["description"]["required"] = False + self._params_spec["description"]["type"] = "str" + + self._params_spec["epld_image"] = {} + self._params_spec["epld_image"]["default"] = "" + self._params_spec["epld_image"]["required"] = False + self._params_spec["epld_image"]["type"] = "str" + + self._params_spec["name"] = {} + self._params_spec["name"]["required"] = True + self._params_spec["name"]["type"] = "str" + + self._params_spec["platform"] = {} + self._params_spec["platform"]["required"] = True + self._params_spec["platform"]["type"] = "str" + self._params_spec["platform"]["choices"] = ["N9K", "N7K", "N77", "N6K", "N5K"] + + self._params_spec["packages"] = {} + self._params_spec["packages"]["default"] = {} + self._params_spec["packages"]["required"] = False + self._params_spec["packages"]["type"] = "dict" + + self._params_spec["packages"]["install"] = {} + self._params_spec["packages"]["install"]["default"] = [] + self._params_spec["packages"]["install"]["required"] = False + self._params_spec["packages"]["install"]["type"] = "list" + + self._params_spec["packages"]["uninstall"] = {} + self._params_spec["packages"]["uninstall"]["default"] = [] + self._params_spec["packages"]["uninstall"]["required"] = False + self._params_spec["packages"]["uninstall"]["type"] = "list" + + self._params_spec["release"] = {} + self._params_spec["release"]["required"] = True + self._params_spec["release"]["type"] = "str" + + self._params_spec["type"] = {} + self._params_spec["type"]["default"] = "PLATFORM" + self._params_spec["type"]["required"] = False + self._params_spec["type"]["type"] = "str" + + def _build_params_spec_for_overridden_state(self) -> None: + self._build_params_spec_for_merged_state_proposed() + + def _build_params_spec_for_replaced_state(self) -> None: + self._build_params_spec_for_merged_state_proposed() + + def _build_params_spec_for_deleted_state(self) -> None: + """ + Build the specs for the parameters expected when state == deleted. + + Caller: _validate_configs() + Return: params_spec, a dictionary containing playbook + parameter specifications. + """ + self._params_spec: Dict[str, Any] = {} + + self._params_spec["name"] = {} + self._params_spec["name"]["required"] = True + self._params_spec["name"]["type"] = "str" + + def _build_params_spec_for_query_state(self) -> None: + """ + Build the specs for the parameters expected when state == query. + + Caller: _validate_configs() + Return: params_spec, a dictionary containing playbook + parameter specifications. + """ + self._params_spec: Dict[str, Any] = {} + + self._params_spec["name"] = {} + self._params_spec["name"]["required"] = True + self._params_spec["name"]["type"] = "str" + + def _build_params_spec_for_replaced_state(self) -> None: + self._build_params_spec_for_merged_state_proposed() + + @property + def params_spec(self) -> Dict[str, Any]: + """ + return the parameter specification + """ + return self._params_spec diff --git a/plugins/module_utils/image_policy/payload.py b/plugins/module_utils/image_policy/payload.py new file mode 100644 index 000000000..129d92686 --- /dev/null +++ b/plugins/module_utils/image_policy/payload.py @@ -0,0 +1,200 @@ +# Copyright (c) 2024 Cisco and/or its affiliates. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type +__author__ = "Allen Robel" + +import inspect +import json +import logging +from typing import Any, Dict + +from ansible_collections.cisco.dcnm.plugins.module_utils.image_policy.common import \ + ImagePolicyCommon + + +class Payload(ImagePolicyCommon): + """ + Base class for Config2Payload and Payload2Config + """ + + def __init__(self, ansible_module): + super().__init__(ansible_module) + self.class_name = self.__class__.__name__ + self.ansible_module = ansible_module + + self.log = logging.getLogger(f"dcnm.{self.class_name}") + self.log.debug("ENTERED Payload()") + + self._build_properties() + + def _build_properties(self): + """ + self.properties holds property values for the class + """ + # self.properties is instantiated in ImagePolicyCommon + self.properties["payload"] = {} + self.properties["config"] = {} + + @property + def payload(self): + """ + return the payload + """ + return self.properties["payload"] + + @payload.setter + def payload(self, value): + method_name = inspect.stack()[0][3] + if not isinstance(value, dict): + msg = f"{self.class_name}.{method_name}: " + msg += "payload must be a dictionary. " + msg += f"got {type(value).__name__} for " + msg += f"value {value}" + self.ansible_module.fail_json(msg) + self.properties["payload"] = value + + @property + def config(self): + """ + return the playbook configuration + """ + return self.properties["config"] + + @config.setter + def config(self, value): + method_name = inspect.stack()[0][3] + if not isinstance(value, dict): + msg = f"{self.class_name}.{method_name}: " + msg += "config must be a dictionary. " + msg += f"got {type(value).__name__} for " + msg += f"value {value}" + self.ansible_module.fail_json(msg) + self.properties["config"] = value + + +class Config2Payload(Payload): + """ + Convert an image_policy configuration into a payload + for a POST request to the image-policy API endpoint. + """ + + def __init__(self, ansible_module): + super().__init__(ansible_module) + self.class_name = self.__class__.__name__ + + self.log = logging.getLogger(f"dcnm.{self.class_name}") + self.log.debug("ENTERED Config2Payload()") + + def commit(self): + """ + Convert self_payload into a playbook configuration + """ + method_name = inspect.stack()[0][3] + + msg = f"{self.class_name}.{method_name}: " + msg += f"properties[config] {json.dumps(self.properties['config'], indent=4, sort_keys=True)}" + self.log.debug(msg) + + if self.properties["config"] == {}: + msg = f"{self.class_name}.{method_name}: " + msg += "config is empty" + self.ansible_module.fail_json(msg, **self.results.failed_result) + + msg = f"{self.class_name}.{method_name}: " + msg += f"HERE 1 STATE: {self.ansible_module.params['state']}" + self.log.debug(msg) + + if self.ansible_module.params["state"] in ["deleted", "query"]: + self.properties["payload"]["policyName"] = self.properties["config"]["name"] + return + self.properties["payload"]["agnostic"] = self.properties["config"]["agnostic"] + self.properties["payload"]["epldImgName"] = self.properties["config"][ + "epld_image" + ] + self.properties["payload"]["nxosVersion"] = self.properties["config"]["release"] + self.properties["payload"]["platform"] = self.properties["config"]["platform"] + self.properties["payload"]["policyDescr"] = self.properties["config"][ + "description" + ] + self.properties["payload"]["policyName"] = self.properties["config"]["name"] + self.properties["payload"]["policyType"] = self.properties["config"].get( + "type", "PLATFORM" + ) + + if len(self.properties["config"].get("packages", {}).get("install", [])) != 0: + self.properties["payload"]["packageName"] = ",".join( + self.properties["config"]["packages"]["install"] + ) + if len(self.properties["config"].get("packages", {}).get("uninstall", [])) != 0: + self.properties["payload"]["rpmimages"] = ",".join( + self.properties["config"]["packages"]["uninstall"] + ) + + msg = f"{self.class_name}.{method_name}: " + msg += f"properties[payload] {json.dumps(self.properties['payload'], indent=4, sort_keys=True)}" + self.log.debug(msg) + + +class Payload2Config(Payload): + """ + Convert an image-policy endpoint payload into a playbook + configuration. + """ + + def __init__(self, ansible_module): + super().__init__(ansible_module) + self.class_name = self.__class__.__name__ + + self.log = logging.getLogger(f"dcnm.{self.class_name}") + self.log.debug("ENTERED Payload2Config()") + + def commit(self): + """ + build the config from the payload + """ + method_name = inspect.stack()[0][3] + + if self.properties["payload"] == {}: + msg = f"{self.class_name}.{method_name}: " + msg += "payload is empty" + self.ansible_module.fail_json(msg) + + self.properties["config"]["agnostic"] = self.properties["payload"]["agnostic"] + self.properties["config"]["epld_image"] = self.properties["payload"][ + "epldImgName" + ] + self.properties["config"]["release"] = self.properties["payload"]["nxosVersion"] + self.properties["config"]["platform"] = self.properties["payload"]["platform"] + self.properties["config"]["description"] = self.properties["payload"][ + "policyDescr" + ] + self.properties["config"]["name"] = self.properties["payload"]["policyName"] + self.properties["config"]["type"] = self.properties["payload"]["policyType"] + + self.properties["config"]["packages"] = {} + if self.properties["payload"].get("packageName", "") != "": + self.properties["config"]["packages"]["install"] = self.properties[ + "payload" + ]["packageName"].split(",") + else: + self.properties["config"]["packages"]["install"] = [] + if self.properties["payload"].get("rpmimages", "") != "": + self.properties["config"]["packages"]["uninstall"] = self.properties[ + "payload" + ]["rpmimages"].split(",") + else: + self.properties["config"]["packages"]["uninstall"] = [] diff --git a/plugins/module_utils/image_policy/query.py b/plugins/module_utils/image_policy/query.py new file mode 100644 index 000000000..1db296d69 --- /dev/null +++ b/plugins/module_utils/image_policy/query.py @@ -0,0 +1,140 @@ +# Copyright (c) 2024 Cisco and/or its affiliates. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +from __future__ import absolute_import, division, print_function + +__metaclass__ = type +__author__ = "Allen Robel" + +import copy +import inspect +import logging + +from ansible_collections.cisco.dcnm.plugins.module_utils.image_policy.common import \ + ImagePolicyCommon +from ansible_collections.cisco.dcnm.plugins.module_utils.image_policy.image_policies import \ + ImagePolicies +from ansible_collections.cisco.dcnm.plugins.module_utils.common.results import \ + Results + + +class ImagePolicyQuery(ImagePolicyCommon): + """ + Query image policies + + Usage: + + instance = ImagePolicyQuery(ansible_module) + instance.policy_names = ["IMAGE_POLICY_1", "IMAGE_POLICY_2"] + instance.commit() + diff = instance.diff # contains the image policy information + result = instance.result # contains the result(s) of the query + response = instance.response # contains the response(s) from the controller + """ + + def __init__(self, ansible_module): + super().__init__(ansible_module) + self.class_name = self.__class__.__name__ + + self._policies_to_query = [] + self._build_properties() + self._image_policies = ImagePolicies(self.ansible_module) + self._image_policies.results = Results() + + self.action = "query" + + self.log = logging.getLogger(f"dcnm.{self.class_name}") + msg = "ENTERED ImagePolicyQuery(): " + msg += f"action {self.action}, " + msg += f"check_mode {self.check_mode}, " + msg += f"state {self.state}" + self.log.debug(msg) + + def _build_properties(self): + """ + self.properties holds property values for the class + """ + # self.properties is already set in the parent class + self.properties["policy_names"] = None + + @property + def policy_names(self): + """ + return the policy names + """ + return self.properties["policy_names"] + + @policy_names.setter + def policy_names(self, value): + method_name = inspect.stack()[0][3] + if not isinstance(value, list): + msg = f"{self.class_name}.{method_name}: " + msg += "policy_names must be a list. " + msg += f"got {type(value).__name__} for " + msg += f"value {value}" + self.ansible_module.fail_json(msg) + if len(value) == 0: + msg = f"{self.class_name}.{method_name}: " + msg += "policy_names must be a list of at least one string. " + msg += f"got {value}." + self.ansible_module.fail_json(msg) + for item in value: + if not isinstance(item, str): + msg = f"{self.class_name}.{method_name}: " + msg += "policy_names must be a list of strings. " + msg += f"got {type(item).__name__} for " + msg += f"value {item}" + self.ansible_module.fail_json(msg) + self.properties["policy_names"] = value + + def commit(self): + """ + query each of the image policies in self.policy_names + """ + method_name = inspect.stack()[0][3] + if self.policy_names is None: + msg = f"{self.class_name}.{method_name}: " + msg += "policy_names must be set prior to calling commit." + self.ansible_module.fail_json(msg, **self.results.failed_result) + + self._image_policies.refresh() + + self.results.action = self.action + self.results.check_mode = self.check_mode + self.results.state = self.state + + if self._image_policies.results.result_current.get("success") is False: + self.results.diff_current = {} + self.results.failed = True + self.results.response_current = copy.deepcopy(self._image_policies.results.response_current) + self.results.result_current = copy.deepcopy(self._image_policies.results.result_current) + self.results.register_task_result() + return + + self.results.failed = False + registered_a_result = False + for policy_name in self.policy_names: + if policy_name not in self._image_policies.all_policies: + continue + self.results.diff_current = copy.deepcopy(self._image_policies.all_policies[policy_name]) + self.results.response_current = copy.deepcopy(self._image_policies.results.response_current) + self.results.result_current = copy.deepcopy(self._image_policies.results.result_current) + self.results.register_task_result() + registered_a_result = True + + if registered_a_result is False: + self.results.failed = False + self.results.diff_current = {} + # Avoid a failed result if none of the policies were found + self.results.result_current = {"success": True} + self.results.register_task_result() diff --git a/plugins/module_utils/image_policy/replace.py b/plugins/module_utils/image_policy/replace.py new file mode 100644 index 000000000..5f6469bfb --- /dev/null +++ b/plugins/module_utils/image_policy/replace.py @@ -0,0 +1,262 @@ +# Copyright (c) 2024 Cisco and/or its affiliates. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type +__author__ = "Allen Robel" + +import copy +import inspect +import json +import logging + +from ansible_collections.cisco.dcnm.plugins.module_utils.common.rest_send import \ + RestSend +from ansible_collections.cisco.dcnm.plugins.module_utils.common.merge_dicts import \ + MergeDicts +from ansible_collections.cisco.dcnm.plugins.module_utils.image_policy.common import \ + ImagePolicyCommon +from ansible_collections.cisco.dcnm.plugins.module_utils.image_policy.endpoints import \ + ApiEndpoints +from ansible_collections.cisco.dcnm.plugins.module_utils.image_policy.image_policies import \ + ImagePolicies +from ansible_collections.cisco.dcnm.plugins.module_utils.common.results import \ + Results + + +class ImagePolicyReplaceBulk(ImagePolicyCommon): + + """ + Handle Ansible replaced state for image policies + + Given a list of payloads, bulk-replace the image policies therein. + The payload format is given below. + + agnostic bool(), optional. true or false + epldImgName str(), optional. name of an EPLD image to install. + nxosVersion str(), required. NX-OS version as version_type_arch + packageName: str(), optional, A comma-separated list of packages + platform: str(), optional, one of N9K, N6K, N5K, N3K + policyDesc str(), optional, description for the image policy + policyName: str(), required. Name of the image policy. + policyType str(), required. PLATFORM or UMBRELLA + rpmimages: str(), optional. A comma-separated list of packages to uninstall + + Example (replacing two policies)): + + policies = [ + { + "agnostic": false, + "epldImgName": "n9000-epld.10.3.2.F.img", + "nxosVersion": "10.3.1_nxos64-cs_64bit", + "packageName": "mtx-openconfig-all-2.0.0.0-10.4.1.src.rpm", + "platform": "N9K", + "policyDescr": "10.3(3)F", + "policyName": "FOO", + "policyType": "PLATFORM", + "rpmimages": "mtx-grpctunnel-2.1.0.0-10.4.1.lib32_64_n9000" + }, + { + "policyDescr": "new policy description for BAR", + "policyName": "BAR, + }, + ] + bulk_replace = ImagePolicyReplaceBulk(ansible_module) + bulk_replace.payloads = policies + bulk_replace.commit() + """ + + def __init__(self, ansible_module): + super().__init__(ansible_module) + self.class_name = self.__class__.__name__ + self.action = "replace" + + self.log = logging.getLogger(f"dcnm.{self.class_name}") + msg = "ENTERED ImagePolicyReplaceBulk(): " + msg += f"action: {self.action}, " + msg += f"check_mode: {self.check_mode}" + msg += f"state: {self.state}" + self.log.debug(msg) + + self.endpoints = ApiEndpoints() + self._image_policies = ImagePolicies(self.ansible_module) + self._image_policies.results = Results() + + self.rest_send = RestSend(self.ansible_module) + + self._payloads_to_commit = [] + + self.path = self.endpoints.policy_edit.get("path") + self.verb = self.endpoints.policy_edit.get("verb") + + self._mandatory_payload_keys = set() + self._mandatory_payload_keys.add("nxosVersion") + self._mandatory_payload_keys.add("policyName") + self._mandatory_payload_keys.add("policyType") + + self._build_properties() + + def _build_properties(self): + """ + self.properties holds property values for the class + """ + # self.properties is already set in the parent class + self.properties["payloads"] = None + + def _verify_payload(self, payload): + """ + Verify that the payload is a dict and contains all mandatory keys + """ + method_name = inspect.stack()[0][3] + if not isinstance(payload, dict): + msg = f"{self.class_name}.{method_name}: " + msg += "payload must be a dict. " + msg += f"Got type {type(payload).__name__}, " + msg += f"value {payload}" + self.ansible_module.fail_json(msg, **self.results.failed_result) + + missing_keys = [] + for key in self._mandatory_payload_keys: + if key not in payload: + missing_keys.append(key) + if len(missing_keys) == 0: + return + + msg = f"{self.class_name}.{method_name}: " + msg += "payload is missing mandatory keys: " + msg += f"{sorted(missing_keys)}" + self.ansible_module.fail_json(msg, **self.results.failed_result) + + def _build_payloads_to_commit(self): + """ + Build the payloads to commit to the controller. + Populates the list self._payloads_to_commit + + Caller: commit() + """ + method_name = inspect.stack()[0][3] + if self.payloads is None: + msg = f"{self.class_name}.{method_name}: " + msg += "payloads must be set prior to calling commit." + self.ansible_module.fail_json(msg) + + self._image_policies.refresh() + + msg = f"self.payloads: {json.dumps(self.payloads, indent=4, sort_keys=True)}" + self.log.debug(msg) + # Populate a list of policies on the contoller that match our payloads + controller_policies = [] + policy_names = [] + for payload in self.payloads: + if payload.get("policyName", None) not in self._image_policies.all_policies: + continue + controller_policies.append(payload) + policy_names.append(payload["policyName"]) + + msg = f"controller_policies: {json.dumps(controller_policies, indent=4, sort_keys=True)}" + self.log.debug(msg) + + # fail_json if the ref_count for any policy is not 0 (i.e. the policy is in use + # and cannot be replaced) + self._verify_image_policy_ref_count(self._image_policies, policy_names) + + # If we made it this far, the ref_counts for all policies are 0 + # Merge the default image policy with the user's payload to create a + # complete playload and add it to self._payloads_to_commit + self._payloads_to_commit = [] + for payload in controller_policies: + merge = MergeDicts(self.ansible_module) + merge.dict1 = copy.deepcopy(self._default_policy(payload["policyName"])) + merge.dict2 = payload + msg = f"merge.dict1: {json.dumps(merge.dict1, indent=4, sort_keys=True)}" + self.log.debug(msg) + msg = f"merge.dict2: {json.dumps(merge.dict2, indent=4, sort_keys=True)}" + self.log.debug(msg) + merge.commit() + self._payloads_to_commit.append(copy.deepcopy(merge.dict_merged)) + msg = "self._payloads_to_commit: " + msg += f"{json.dumps(self._payloads_to_commit, indent=4, sort_keys=True)}" + self.log.debug(msg) + + def _send_payloads(self): + """ + Send the payloads in self._payloads_to_commit to the controller + + Caller: commit() + """ + self.rest_send.check_mode = self.check_mode + + for payload in self._payloads_to_commit: + self._send_payload(payload) + + def _send_payload(self, payload): + """ + Send one payload to the controller + """ + method_name = inspect.stack()[0][3] # pylint: disable=unused-variable + msg = f"{self.class_name}.{method_name}: " + msg += f"verb: {self.verb}, path: {self.path}, " + msg += f"payload: {json.dumps(payload, indent=4, sort_keys=True)}" + self.log.debug(msg) + + # We don't want RestSend to retry on errors since the likelihood of a + # timeout error when updating image policies is low, and there are + # many cases of permanent errors for which we don't want to retry. + self.rest_send.timeout = 1 + + self.rest_send.path = self.path + self.rest_send.verb = self.verb + self.rest_send.payload = payload + self.rest_send.commit() + + if self.rest_send.result_current["success"] is False: + self.results.diff_current = {} + else: + self.results.diff_current = copy.deepcopy(payload) + + # self.send_payload_result[payload["FABRIC_NAME"]] = self.rest_send.result_current["success"] + self.results.action = self.action + self.results.check_mode = self.check_mode + self.results.state = self.state + self.results.response_current = copy.deepcopy(self.rest_send.response_current) + self.results.result_current = copy.deepcopy(self.rest_send.result_current) + self.results.register_task_result() + + def commit(self): + """ + Commit the payloads to the controller + """ + self._build_payloads_to_commit() + self._send_payloads() + + @property + def payloads(self): + """ + return the policy payloads + """ + return self.properties["payloads"] + + @payloads.setter + def payloads(self, value): + method_name = inspect.stack()[0][3] + if not isinstance(value, list): + msg = f"{self.class_name}.{method_name}: " + msg += "payloads must be a list of dict. " + msg += f"got {type(value).__name__} for " + msg += f"value {value}" + self.ansible_module.fail_json(msg) + for item in value: + self._verify_payload(item) + self.properties["payloads"] = value diff --git a/plugins/module_utils/image_policy/update.py b/plugins/module_utils/image_policy/update.py new file mode 100644 index 000000000..022939810 --- /dev/null +++ b/plugins/module_utils/image_policy/update.py @@ -0,0 +1,372 @@ +# Copyright (c) 2024 Cisco and/or its affiliates. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type +__author__ = "Allen Robel" + +import copy +import inspect +import json +import logging + +from ansible_collections.cisco.dcnm.plugins.module_utils.common.rest_send import \ + RestSend +from ansible_collections.cisco.dcnm.plugins.module_utils.common.merge_dicts import \ + MergeDicts +from ansible_collections.cisco.dcnm.plugins.module_utils.image_policy.common import \ + ImagePolicyCommon +from ansible_collections.cisco.dcnm.plugins.module_utils.image_policy.endpoints import \ + ApiEndpoints +from ansible_collections.cisco.dcnm.plugins.module_utils.image_policy.image_policies import \ + ImagePolicies +from ansible_collections.cisco.dcnm.plugins.module_utils.common.results import \ + Results + + +class ImagePolicyUpdateCommon(ImagePolicyCommon): + """ + Common methods and properties for: + - ImagePolicyUpdate + - ImagePolicyUpdateBulk + """ + + def __init__(self, ansible_module): + super().__init__(ansible_module) + self.class_name = self.__class__.__name__ + self.action = "update" + + self.log = logging.getLogger(f"dcnm.{self.class_name}") + + self._image_policies = ImagePolicies(self.ansible_module) + self._image_policies.results = Results() + + self.endpoints = ApiEndpoints() + self.rest_send = RestSend(self.ansible_module) + + self.path = self.endpoints.policy_edit.get("path") + self.verb = self.endpoints.policy_edit.get("verb") + + self._payloads_to_commit = [] + + self._mandatory_payload_keys = set() + self._mandatory_payload_keys.add("nxosVersion") + self._mandatory_payload_keys.add("policyName") + self._mandatory_payload_keys.add("policyType") + + msg = "ENTERED ImagePolicyUpdateCommon(): " + msg += f"action: {self.action}, " + msg += f"check_mode: {self.check_mode}, " + msg += f"state: {self.state}" + self.log.debug(msg) + + def _verify_payload(self, payload): + """ + Verify that the payload is a dict and contains all mandatory keys + """ + method_name = inspect.stack()[0][3] + if not isinstance(payload, dict): + msg = f"{self.class_name}.{method_name}: " + msg += "payload must be a dict. " + msg += f"Got type {type(payload).__name__}, " + msg += f"value {payload}" + self.ansible_module.fail_json(msg, **self.results.failed_result) + + missing_keys = [] + for key in self._mandatory_payload_keys: + if key not in payload: + missing_keys.append(key) + if len(missing_keys) == 0: + return + + msg = f"{self.class_name}.{method_name}: " + msg += "payload is missing mandatory keys: " + msg += f"{sorted(missing_keys)}" + self.ansible_module.fail_json(msg, **self.results.failed_result) + + def _build_payloads_to_commit(self): + """ + Build a list of payloads to commit. Skip any payloads that + do not exist on the controller. + + Expects self.payloads to be a list of dict, with each dict + being a payload for the image policy edit API endpoint. + + Populates self._payloads_to_commit with a list of payloads + to commit. + """ + self._image_policies.refresh() + + _payloads = [] + _policy_names = [] + for payload in self.payloads: + if payload.get("policyName", None) not in self._image_policies.all_policies: + continue + _payloads.append(payload) + _policy_names.append(payload["policyName"]) + + self._verify_image_policy_ref_count(self._image_policies, _policy_names) + + # build self._payloads_to_commit by merging each _payload with the + # corresponding policy payload on the controller. The parameters + # in _payloads take precedence. + self._payloads_to_commit = [] + for payload in _payloads: + merge = MergeDicts(self.ansible_module) + merge.dict1 = self._image_policies.all_policies.get(payload["policyName"]) + merge.dict2 = payload + merge.commit() + updated_payload = copy.deepcopy(merge.dict_merged) + # ref_count, imageName, and platformPolicies are returned + # by the controller, but are not valid parameters for the + # edit-policy endpoint. + updated_payload.pop("ref_count", None) + updated_payload.pop("imageName", None) + updated_payload.pop("platformPolicies", None) + self._payloads_to_commit.append(copy.deepcopy(updated_payload)) + msg = f"self._payloads_to_commit: {json.dumps(self._payloads_to_commit, indent=4, sort_keys=True)}" + self.log.debug(msg) + + def _send_payloads(self): + """ + If check_mode is False, send the payloads to the controller + If check_mode is True, do not send the payloads to the controller + + In both cases, update results + """ + self.rest_send.check_mode = self.check_mode + + for payload in self._payloads_to_commit: + self._send_payload(payload) + + def _send_payload(self, payload): + """ + Send one image policy update payload + """ + method_name = inspect.stack()[0][3] + + msg = f"{self.class_name}.{method_name}: " + msg += f"verb: {self.verb}, path: {self.path}, " + msg += f"payload: {json.dumps(payload, indent=4, sort_keys=True)}" + self.log.debug(msg) + + # We don't want RestSend to retry on errors since the likelihood of a + # timeout error when updating an image policy is low, and there are + # cases of permanent errors for which we don't want to retry. + self.rest_send.timeout = 1 + + self.rest_send.path = self.path + self.rest_send.verb = self.verb + self.rest_send.payload = payload + self.rest_send.commit() + + if self.rest_send.result_current["success"] is False: + self.results.diff_current = {} + else: + self.results.diff_current = copy.deepcopy(payload) + + self.results.action = self.action + self.results.check_mode = self.check_mode + self.results.state = self.state + self.results.response_current = copy.deepcopy(self.rest_send.response_current) + self.results.result_current = copy.deepcopy(self.rest_send.result_current) + self.results.register_task_result() + + @property + def payloads(self): + """ + Return the image policy payloads + + Payloads must be a list of dict. Each dict is a + payload for the image policy update API endpoint. + """ + return self.properties["payloads"] + + @payloads.setter + def payloads(self, value): + method_name = inspect.stack()[0][3] + if not isinstance(value, list): + msg = f"{self.class_name}.{method_name}: " + msg += "payloads must be a list of dict. " + msg += f"got {type(value).__name__} for " + msg += f"value {value}" + self.ansible_module.fail_json(msg) + for item in value: + self._verify_payload(item) + self.properties["payloads"] = value + + +class ImagePolicyUpdateBulk(ImagePolicyUpdateCommon): + """ + Given a list of payloads, bulk-update the image policies therein. + The payload format is given below. + + agnostic bool(), optional. true or false + epldImgName str(), optional. name of an EPLD image to install. + nxosVersion str(), required. NX-OS version as version_type_arch + packageName: str(), optional, A comma-separated list of packages + platform: str(), optional, one of N9K, N6K, N5K, N3K + policyDesc str(), optional, description for the image policy + policyName: str(), required. Name of the image policy. + policyType str(), required. PLATFORM or UMBRELLA + rpmimages: str(), optional. A comma-separated list of packages to uninstall + + Example (updating two policies)): + + policies = [ + { + "agnostic": false, + "epldImgName": "n9000-epld.10.3.2.F.img", + "nxosVersion": "10.3.1_nxos64-cs_64bit", + "packageName": "mtx-openconfig-all-2.0.0.0-10.4.1.src.rpm", + "platform": "N9K", + "policyDescr": "10.3(3)F", + "policyName": "FOO", + "policyType": "PLATFORM", + "rpmimages": "mtx-grpctunnel-2.1.0.0-10.4.1.lib32_64_n9000" + }, + { + "policyDescr": "new policy description for BAR", + "policyName": "BAR, + }, + ] + bulk_update = ImagePolicyUpdateBulk(ansible_module) + bulk_update.payloads = policies + bulk_update.commit() + """ + + def __init__(self, ansible_module): + super().__init__(ansible_module) + self.class_name = self.__class__.__name__ + + self.log = logging.getLogger(f"dcnm.{self.class_name}") + + msg = "ENTERED ImagePolicyUpdateBulk(): " + self.log.debug(msg) + + self._build_properties() + + def _build_properties(self): + """ + self.properties holds property values for the class + """ + # self.properties is already set in the parent class + self.properties["payloads"] = None + + def commit(self): + """ + Update policies. Skip any policies that do not exist + on the controller. + """ + method_name = inspect.stack()[0][3] + if self.payloads is None: + msg = f"{self.class_name}.{method_name}: " + msg += "payloads must be set prior to calling commit." + self.ansible_module.fail_json(msg, **self.results.failed_result) + + self._build_payloads_to_commit() + if len(self._payloads_to_commit) == 0: + return + self._send_payloads() + + +class ImagePolicyUpdate(ImagePolicyUpdateCommon): + """ + Given a properly-constructed image policy payload (python dict), + send an image policy update request to the controller. The payload + format is given below. + + agnostic bool(), optional. true or false + epldImgName str(), optional. name of an EPLD image to install. + nxosVersion str(), required. NX-OS version as version_type_arch + packageName: str(), optional, A comma-separated list of packages + platform: str(), optional, one of N9K, N6K, N5K, N3K + policyDesc str(), optional, description for the image policy + policyName: str(), required. Name of the image policy. + policyType str(), required. PLATFORM or UMBRELLA + rpmimages: str(), optional. A comma-separated list of packages to uninstall + + Example (update one policy): + + image_policy_payload = { + "agnostic": false, + "epldImgName": "n9000-epld.10.3.2.F.img", + "nxosVersion": "10.3.1_nxos64-cs_64bit", + "packageName": "mtx-openconfig-all-2.0.0.0-10.4.1.src.rpm", + "platform": "N9K", + "policyDescr": "image policy of 10.3(3)F", + "policyName": "FOO", + "policyType": "PLATFORM", + "rpmimages": "mtx-grpctunnel-2.1.0.0-10.4.1.lib32_64_n9000" + } + update = ImagePolicyUpdate(ansible_module) + update.payload = image_policy_payload + update.commit() + """ + + def __init__(self, ansible_module): + super().__init__(ansible_module) + self.class_name = self.__class__.__name__ + + self.log = logging.getLogger(f"dcnm.{self.class_name}") + + msg = "ENTERED ImagePolicyUpdate(): " + self.log.debug(msg) + + self._mandatory_keys = set() + self._mandatory_keys.add("policyName") + + self.rest_send = RestSend(self.ansible_module) + + self._init_properties() + + def _init_properties(self): + """ + Add properties specific to this class + """ + # properties is already initialized in the parent class + self.properties["payload"] = None + + @property + def payload(self): + """ + This class expects a properly-defined image policy payload. + See class docstring for the payload structure and example usage. + """ + return self.properties["payload"] + + @payload.setter + def payload(self, value): + self._verify_payload(value) + self.properties["payload"] = value + # ImagePolicyUpdateCommon expects a list of payloads + self.properties["payloads"] = [value] + + def commit(self): + """ + Update policy. + If policy does not exist on the controller, do nothing. + """ + method_name = inspect.stack()[0][3] + if self.payload is None: + msg = f"{self.class_name}.{method_name}: " + msg += "payload must be set prior to calling commit." + self.ansible_module.fail_json(msg, **self.results.failed_result) + + self._build_payloads_to_commit() + + if len(self._payloads_to_commit) == 0: + return + self._send_payloads() diff --git a/plugins/modules/dcnm_image_policy.py b/plugins/modules/dcnm_image_policy.py new file mode 100644 index 000000000..ab5afac24 --- /dev/null +++ b/plugins/modules/dcnm_image_policy.py @@ -0,0 +1,858 @@ +#!/usr/bin/python +# +# Copyright (c) 2024 Cisco and/or its affiliates. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# pylint: disable=wrong-import-position +from __future__ import absolute_import, division, print_function + +__metaclass__ = type +__author__ = "Allen Robel" + +DOCUMENTATION = """ +--- +module: dcnm_image_policy +short_description: Image policy management for Nexus Dashboard Fabric Controller +version_added: "3.5.0" +description: + - Create, delete, modify image policies. +author: Allen Robel (@quantumonion) +options: + state: + description: + - The state of the feature or object after module completion + type: str + choices: + - deleted + - merged + - overridden + - query + - replaced + default: merged + + config: + description: + - List of dictionaries containing image policy parameters + type: list + elements: dict + suboptions: + name: + description: + - The image policy name. + type: str + required: true + agnostic: + description: + - The agnostic flag. + type: bool + default: false + required: false + description: + description: + - The image policy description. + type: str + default: "" + required: false + epld_image: + description: + - The epld image name. + type: str + default: "" + required: false + packages: + description: + - A dictionary containing two keys, install and uninstall. + type: dict + required: false + suboptions: + install: + description: + - A list of packages to install. + type: list + elements: str + required: false + uninstall: + description: + - A list of packages to uninstall. + type: list + elements: str + required: false + platform: + description: + - The platform to which the image policy applies e.g. N9K. + type: str + required: true + release: + description: + - The release associated with the image policy. + - This is derived from the image name as follows. + - From image name nxos64-cs.10.2.5.M.bin + - we need to extract version (10.2.5), platform (nxos64-cs), and bits (64bit). + - The release string conforms to format (version)_(platform)_(bits) + - so the resulting release string will be 10.2.5_nxos64-cs_64bit + type: str + required: true + type: + description: + - The type of the image policy e.g. PLATFORM. + type: str + default: PLATFORM + required: false +""" + +EXAMPLES = """ +# This module supports the following states: +# +# deleted: +# Delete image policies from the controller. +# +# If an image policy has references (i.e. it is attached to a device), +# the module will fail. Use dcnm_image_upgrade module, state deleted, +# to detach the image policy from all devices before deleting it. +# +# merged: +# Create (or update) one or more image policies. +# +# If an image policy does not exist on the controller, create it. +# If an image policy already exists on the controller, edit it. +# +# overridden: +# Create/delete one or more image policies. +# +# If an image policy already exists on the controller, delete it and update +# it with the configuration in the playbook task. +# +# Remove any image policies from the controller that are not in the +# playbook task. +# +# query: +# +# Return the configuration for one or more image policies. +# +# replaced: +# +# Replace image policies on the controller with policies in the playbook task. +# +# If an image policy exists on the controller, but not in the playbook task, +# do not delete it or modify it. +# +# Delete two image policies from the controller. + + - name: Delete Image policies + cisco.dcnm.dcnm_image_policy: + state: deleted + config: + - name: KR5M + - name: NR3F + register: result + - name: print result + ansible.builtin.debug: + var: result + +# Merge two image policies into the controller. + + - name: Merge Image policies + cisco.dcnm.dcnm_image_policy: + state: merged + config: + - name: KR5M + agnostic: false + description: KR5M + epld_image: n9000-epld.10.2.5.M.img + packages: + install: + - mtx-openconfig-all-2.0.0.0-10.4.1.src.rpm + uninstall: + - mtx-grpctunnel-2.1.0.0-10.4.1.lib32_64_n9000 + platform: N9K + release: 10.2.5_nxos64-cs_64bit + type: PLATFORM + - name: NR3F + description: NR3F + platform: N9K + epld_image: n9000-epld.10.3.1.F.img + release: 10.3.1_nxos64-cs_64bit + register: result + - name: print result + ansible.builtin.debug: + var: result + +# Override all policies on the controller and replace them with +# the policies in the playbook task. Any policies other than +# KR5M and NR3F are deleted from the controller. + + - name: Override Image policies + cisco.dcnm.dcnm_image_policy: + state: overridden + config: + - name: KR5M + agnostic: false + description: KR5M + epld_image: n9000-epld.10.2.5.M.img + platform: N9K + release: 10.2.5_nxos64-cs_64bit + type: PLATFORM + - name: NR3F + description: NR3F + platform: N9K + epld_image: n9000-epld.10.2.5.M.img + release: 10.3.1_nxos64-cs_64bit + register: result + - name: print result + ansible.builtin.debug: + var: result + +# Query the controller for the policies in the playbook task. + + - name: Query Image policies + cisco.dcnm.dcnm_image_policy: + state: query + config: + - name: NR3F + - name: KR5M + register: result + - name: print result + ansible.builtin.debug: + var: result + +# Replace any policies on the controller that are in the playbook task with +# the configuration given in the playbook task. Policies not listed in the +# playbook task are not modified and are not deleted. + + - name: Replace Image policies + cisco.dcnm.dcnm_image_policy: + state: replaced + config: + - name: KR5M + agnostic: false + description: KR5M + epld_image: n9000-epld.10.2.5.M.img + platform: N9K + release: 10.2.5_nxos64-cs_64bit + type: PLATFORM + - name: NR3F + description: Replaced NR3F + platform: N9K + epld_image: n9000-epld.10.3.1.F.img + release: 10.3.1_nxos64-cs_64bit + register: result + - name: print result + ansible.builtin.debug: + var: result +""" + +import copy +import inspect +import json +import logging +from typing import Dict, List + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.cisco.dcnm.plugins.module_utils.common.log import Log +from ansible_collections.cisco.dcnm.plugins.module_utils.common.merge_dicts import \ + MergeDicts +from ansible_collections.cisco.dcnm.plugins.module_utils.common.params_merge_defaults import \ + ParamsMergeDefaults +from ansible_collections.cisco.dcnm.plugins.module_utils.common.params_validate import \ + ParamsValidate +from ansible_collections.cisco.dcnm.plugins.module_utils.common.rest_send import \ + RestSend +from ansible_collections.cisco.dcnm.plugins.module_utils.common.results import \ + Results +from ansible_collections.cisco.dcnm.plugins.module_utils.image_policy.common import \ + ImagePolicyCommon +from ansible_collections.cisco.dcnm.plugins.module_utils.image_policy.create import \ + ImagePolicyCreateBulk +from ansible_collections.cisco.dcnm.plugins.module_utils.image_policy.delete import \ + ImagePolicyDelete +from ansible_collections.cisco.dcnm.plugins.module_utils.image_policy.endpoints import \ + ApiEndpoints +from ansible_collections.cisco.dcnm.plugins.module_utils.image_policy.image_policies import \ + ImagePolicies +from ansible_collections.cisco.dcnm.plugins.module_utils.image_policy.params_spec import \ + ParamsSpec +from ansible_collections.cisco.dcnm.plugins.module_utils.image_policy.payload import \ + Config2Payload +from ansible_collections.cisco.dcnm.plugins.module_utils.image_policy.query import \ + ImagePolicyQuery +from ansible_collections.cisco.dcnm.plugins.module_utils.image_policy.replace import \ + ImagePolicyReplaceBulk +from ansible_collections.cisco.dcnm.plugins.module_utils.image_policy.update import \ + ImagePolicyUpdateBulk + + +def json_pretty(msg): + """ + Return a pretty-printed JSON string for logging messages + """ + return json.dumps(msg, indent=4, sort_keys=True) + + +class Common(ImagePolicyCommon): + """ + Common methods for all states + """ + + def __init__(self, ansible_module): + self.class_name = self.__class__.__name__ + super().__init__(ansible_module) + method_name = inspect.stack()[0][3] # pylint: disable=unused-variable + self.state = self.ansible_module.params.get("state") + if self.ansible_module.params.get("check_mode") is True: + self.check_mode = True + + self.log = logging.getLogger(f"dcnm.{self.class_name}") + + msg = "ENTERED Common(): " + msg += f"state: {self.state}, " + msg += f"check_mode: {self.check_mode}" + self.log.debug(msg) + + self.endpoints = ApiEndpoints() + + self._implemented_states = set() + self._valid_states = ["deleted", "merged", "overridden", "query", "replaced"] + self._states_require_config = {"merged", "overridden", "replaced", "query"} + + self.params = ansible_module.params + self.rest_send = RestSend(self.ansible_module) + + self.config = ansible_module.params.get("config") + + if self.state in self._states_require_config and not self.config: + msg = f"'config' parameter is required for state {self.state}" + self.ansible_module.fail_json(msg, **self.rest_send.failed_result) + + self.validated = [] + self.have = {} + self.want = [] + self.query = [] + self.idempotent_want = None + + # policies to created + self.need_create = [] + # policies to updated + self.need_delete = [] + # policies to deleted + self.need_update = [] + # policies to query + self.need_query = [] + self.validated_configs = [] + + self.build_properties() + + def build_properties(self): + """ + self.properties holds property values for the class + """ + self.properties["results"] = None + + def get_have(self) -> None: + """ + Caller: main() + + self.have consists of the current image policies on the controller + """ + self.log.debug("ENTERED") + self.have = ImagePolicies(self.ansible_module) + self.have.results = self.results + self.have.refresh() + + def get_want(self) -> None: + """ + Caller: main() + + 1. Validate the playbook configs + 2. Convert the validated configs to payloads + 3. Update self.want with this list of payloads + """ + msg = "ENTERED" + self.log.debug(msg) + # Generate the params_spec used to validate the configs + params_spec = ParamsSpec(self.ansible_module) + params_spec.commit() + + # If a parameter is missing from the config, and it has a default + # value, add it to the config. + merged_configs = [] + merge_defaults = ParamsMergeDefaults(self.ansible_module) + merge_defaults.params_spec = params_spec.params_spec + for config in self.config: + merge_defaults.parameters = config + merge_defaults.commit() + merged_configs.append(merge_defaults.merged_parameters) + + # validate the merged configs + self.validated_configs = [] + validator = ParamsValidate(self.ansible_module) + validator.params_spec = params_spec.params_spec + for config in merged_configs: + validator.parameters = config + validator.commit() + self.validated_configs.append(copy.deepcopy(validator.parameters)) + + # convert the validated configs to payloads to more easily compare them + # to self.have (the current image policies on the controller). + for config in self.validated_configs: + payload = Config2Payload(self.ansible_module) + payload.config = config + payload.commit() + self.want.append(payload.payload) + + # Exit if there's nothing to do + if len(self.want) == 0: + self.ansible_module.exit_json(**self.results.ok_result) + + @property + def results(self): + return self.properties["results"] + + @results.setter + def results(self, value): + self.properties["results"] = value + + +class Replaced(Common): + """ + Handle replaced state + """ + + def __init__(self, ansible_module): + self.class_name = self.__class__.__name__ + super().__init__(ansible_module) + method_name = inspect.stack()[0][3] # pylint: disable=unused-variable + + self.log = logging.getLogger(f"dcnm.{self.class_name}") + + msg = "ENTERED Replaced(): " + msg += f"state: {self.state}, " + msg += f"check_mode: {self.check_mode}" + self.log.debug(msg) + + self._implemented_states.add("replaced") + + def commit(self) -> None: + """ + Replace all policies on the controller that are in want + """ + self.results.state = self.state + self.results.check_mode = self.check_mode + + self.get_want() + self.get_have() + + image_policy_replace = ImagePolicyReplaceBulk(self.ansible_module) + image_policy_replace.results = self.results + image_policy_replace.payloads = self.want + image_policy_replace.commit() + + +class Deleted(Common): + """ + Handle deleted state + """ + + def __init__(self, ansible_module): + self.class_name = self.__class__.__name__ + super().__init__(ansible_module) + method_name = inspect.stack()[0][3] # pylint: disable=unused-variable + + self.log = logging.getLogger(f"dcnm.{self.class_name}") + + self.image_policy_delete = ImagePolicyDelete(self.ansible_module) + + msg = "ENTERED Deleted(): " + msg += f"state: {self.state}, " + msg += f"check_mode: {self.check_mode}" + self.log.debug(msg) + + self._implemented_states.add("deleted") + + def commit(self) -> None: + """ + If config is present, delete all policies in self.want that exist on the controller + If config is not present, delete all policies on the controller + """ + self.results.state = self.state + self.results.check_mode = self.check_mode + self.image_policy_delete.policy_names = self.get_policies_to_delete() + self.image_policy_delete.results = self.results + self.image_policy_delete.commit() + + def get_policies_to_delete(self) -> List[str]: + """ + Return a list of policy names to delete + + - In config is present, return list of image policy names + in self.want that exist on the controller + - If config is not present, return list of all image policy + names on the controller + """ + if not self.config: + self.get_have() + return list(self.have.all_policies.keys()) + self.get_want() + self.get_have() + policy_names_to_delete = [] + for want in self.want: + if want["policyName"] in self.have.all_policies: + policy_names_to_delete.append(want["policyName"]) + return policy_names_to_delete + + +class Query(Common): + """ + Handle query state + """ + + def __init__(self, ansible_module): + self.class_name = self.__class__.__name__ + super().__init__(ansible_module) + method_name = inspect.stack()[0][3] # pylint: disable=unused-variable + + self.log = logging.getLogger(f"dcnm.{self.class_name}") + + msg = "ENTERED Query(): " + msg += f"state: {self.state}, " + msg += f"check_mode: {self.check_mode}" + self.log.debug(msg) + + self._implemented_states.add("query") + + def commit(self) -> None: + """ + 1. query the fabrics in self.want that exist on the controller + """ + method_name = inspect.stack()[0][3] # pylint: disable=unused-variable + + self.results.state = self.state + self.results.check_mode = self.check_mode + + self.get_want() + + image_policy_query = ImagePolicyQuery(self.ansible_module) + image_policy_query.results = self.results + policy_names_to_query = [] + for want in self.want: + policy_names_to_query.append(want["policyName"]) + image_policy_query.policy_names = policy_names_to_query + image_policy_query.commit() + + +class Overridden(Common): + """ + Handle overridden state + """ + + def __init__(self, ansible_module): + self.class_name = self.__class__.__name__ + super().__init__(ansible_module) + method_name = inspect.stack()[0][3] # pylint: disable=unused-variable + + self.log = logging.getLogger(f"dcnm.{self.class_name}") + + msg = "ENTERED Overridden(): " + msg += f"state: {self.state}, " + msg += f"check_mode: {self.check_mode}" + self.log.debug(msg) + + self._implemented_states.add("overridden") + + def commit(self) -> None: + """ + 1. Delete all policies on the controller that are not in self.want + 2. Instantiate Merged() and call Merged().commit() + """ + method_name = inspect.stack()[0][3] # pylint: disable=unused-variable + + self.results.state = self.state + self.results.check_mode = self.check_mode + + self.get_want() + self.get_have() + + msg = f"{self.class_name}.{method_name}: " + msg += f"self.want: {json_pretty(self.want)}" + self.log.debug(msg) + + msg = f"{self.class_name}.{method_name}: " + msg += f"self.have.all_policies: {json_pretty(self.have.all_policies)}" + self.log.debug(msg) + + self._delete_policies_not_in_want() + task = Merged(self.ansible_module) + task.results = self.results + task.commit() + + def _delete_policies_not_in_want(self) -> None: + """ + Delete all policies on the controller that are not in self.want + + Caller: handle_overridden_state() + """ + method_name = inspect.stack()[0][3] # pylint: disable=unused-variable + want_policy_names = set() + for want in self.want: + want_policy_names.add(want["policyName"]) + + policy_names_to_delete = [] + for policy_name in self.have.all_policies: + msg = f"{self.class_name}.{method_name}: " + msg += f"policy_name: {policy_name}" + self.log.debug(msg) + if policy_name not in want_policy_names: + msg = f"{self.class_name}.{method_name}: " + msg += f"Appending to policy_names_to_delete: {policy_name}" + self.log.debug(msg) + policy_names_to_delete.append(policy_name) + + msg = f"{self.class_name}.{method_name}: " + msg += f"policy_names_to_delete: {policy_names_to_delete}" + self.log.debug(msg) + + instance = ImagePolicyDelete(self.ansible_module) + instance.results = self.results + instance.policy_names = policy_names_to_delete + instance.commit() + + +class Merged(Common): + """ + Handle merged state + """ + + def __init__(self, ansible_module): + self.class_name = self.__class__.__name__ + super().__init__(ansible_module) + method_name = inspect.stack()[0][3] # pylint: disable=unused-variable + + self.log = logging.getLogger(f"dcnm.{self.class_name}") + + msg = f"params: {json_pretty(self.ansible_module.params)}" + self.log.debug(msg) + if not ansible_module.params.get("config"): + msg = f"playbook config is required for {self.state}" + ansible_module.fail_json(msg, **self.results.failed_result) + + self.image_policy_create = ImagePolicyCreateBulk(self.ansible_module) + self.image_policy_update = ImagePolicyUpdateBulk(self.ansible_module) + + msg = f"ENTERED {self.class_name}.{method_name}: " + msg += f"state: {self.state}, " + msg += f"check_mode: {self.check_mode}" + self.log.debug(msg) + + # new policies to be created + self.need_create: List[Dict] = [] + # existing policies to be updated + self.need_update: List[Dict] = [] + + self._implemented_states.add("merged") + + def get_need(self): + """ + Caller: commit() + + Build self.need for merged state + 1. Populate self.need_create with items from self.want that are + not in self.have + 2. Populate self.need_update with updated policies. We update + policies as follows: + a. If a policy is in both self.want amd self.have, and they + contain differences, merge self.want into self.have, + with self.want keys taking precedence and append the + merged policy to self.need_update. + b. If a policy is in both self.want and self.have, and they + are identical, do not append the policy to self.need_update + (i.e. do nothing). + """ + for want in self.want: + self.have.policy_name = want.get("policyName") + + # Policy does not exist on the controller so needs to be created. + if self.have.policy is None: + self.need_create.append(copy.deepcopy(want)) + continue + + # The policy exists on the controller. Merge want parameters with + # the controller's parameters and add the merged parameters to the + # need_update list if they differ from the want parameters. + have = copy.deepcopy(self.have.policy) + merged, needs_update = self._merge_policies(have, want) + + if needs_update is True: + self.need_update.append(copy.deepcopy(merged)) + + def commit(self) -> None: + """ + Commit the merged state requests + """ + self.results.state = self.state + self.results.check_mode = self.check_mode + + self.get_want() + self.get_have() + self.get_need() + self.send_need_create() + self.send_need_update() + + def _prepare_for_merge(self, have: Dict, want: Dict): + """ + 1. Remove fields in "have" that are not part of a request payload i.e. + imageName and ref_count. + 2. The controller returns "N9K/N3K" for the platform, but it expects + "N9K" in the payload. We change "N9K/N3K" to "N9K" in have so that + the compare works. + 3. Remove all fields that are not set in both "have" and "want" + + Caller: self._merge_policies() + """ + # Remove keys that the controller adds which are not part + # of a request payload. + for key in ["imageName", "ref_count", "platformPolicies"]: + have.pop(key, None) + + # Change "N9K/N3K" to "N9K" in have to match the request payload. + if have.get("platform", None) == "N9K/N3K": + have["platform"] = "N9K" + + # If keys are not set in both have and want, remove them. + for key in ["agnostic", "epldImgName", "packageName", "rpmimages"]: + if have.get(key, None) is None and want.get(key, None) is None: + have.pop(key, None) + want.pop(key, None) + + if have.get(key, None) == "" and want.get(key, None) == "": + have.pop(key, None) + want.pop(key, None) + return (have, want) + + def _merge_policies(self, have: Dict, want: Dict) -> Dict: + """ + Merge the parameters in want with the parameters in have. + + Caller: self.commit() + """ + (have, want) = self._prepare_for_merge(have, want) + + # Merge the parameters in want with the parameters in have. + # The parameters in want take precedence. + merge = MergeDicts(self.ansible_module) + merge.dict1 = have + merge.dict2 = want + merge.commit() + merged = copy.deepcopy(merge.dict_merged) + + needs_update = False + + if have != merged: + needs_update = True + + return (merged, needs_update) + + def send_need_create(self) -> None: + """ + Create the policies in self.need_create + + Callers: + - self.handle_merged_state() + """ + self.image_policy_create.results = self.results + self.image_policy_create.payloads = self.need_create + self.image_policy_create.commit() + + def send_need_update(self) -> None: + """ + Update the policies in self.need_update + + Callers: + - self.handle_merged_state() + """ + self.image_policy_update.results = self.results + self.image_policy_update.payloads = self.need_update + self.image_policy_update.commit() + + +def main(): + """ + main entry point for module execution + """ + + element_spec = { + "config": { + "required": False, + "type": "list", + "elements": "dict", + "default": [], + }, + "state": { + "default": "merged", + "choices": ["deleted", "merged", "overridden", "query", "replaced"], + }, + } + ansible_module = AnsibleModule(argument_spec=element_spec, supports_check_mode=True) + + # Create the base/parent logger for the dcnm collection. + # To enable logging, set enable_logging to True. + # log.config can be either a dictionary, or a path to a JSON file + # Both dictionary and JSON file formats must be conformant with + # logging.config.dictConfig and must not log to the console. + # For an example configuration, see: + # $ANSIBLE_COLLECTIONS_PATH/cisco/dcnm/plugins/module_utils/common/logging_config.json + enable_logging = False + log = Log(ansible_module) + if enable_logging is True: + collection_path = ( + "/Users/arobel/repos/collections/ansible_collections/cisco/dcnm" + ) + config_file = ( + f"{collection_path}/plugins/module_utils/common/logging_config.json" + ) + log.config = config_file + log.commit() + + results = Results() + if ansible_module.params["state"] == "deleted": + task = Deleted(ansible_module) + task.results = results + task.commit() + elif ansible_module.params["state"] == "merged": + task = Merged(ansible_module) + task.results = results + task.commit() + elif ansible_module.params["state"] == "overridden": + task = Overridden(ansible_module) + task.results = results + task.commit() + elif ansible_module.params["state"] == "query": + task = Query(ansible_module) + task.results = results + task.commit() + elif ansible_module.params["state"] == "replaced": + task = Replaced(ansible_module) + task.results = results + task.commit() + else: + msg = f"Unknown state {task.ansible_module.params['state']}" + ansible_module.fail_json(msg) + + results.build_final_result() + + if True in results.failed: + msg = "Module failed." + ansible_module.fail_json(msg, **results.final_result) + ansible_module.exit_json(**results.final_result) + + +if __name__ == "__main__": + main() diff --git a/tests/integration/targets/dcnm_image_policy/defaults/main.yaml b/tests/integration/targets/dcnm_image_policy/defaults/main.yaml new file mode 100644 index 000000000..55a93fc23 --- /dev/null +++ b/tests/integration/targets/dcnm_image_policy/defaults/main.yaml @@ -0,0 +1,2 @@ +--- +testcase: "*" \ No newline at end of file diff --git a/tests/integration/targets/dcnm_image_policy/meta/main.yaml b/tests/integration/targets/dcnm_image_policy/meta/main.yaml new file mode 100644 index 000000000..32cf5dda7 --- /dev/null +++ b/tests/integration/targets/dcnm_image_policy/meta/main.yaml @@ -0,0 +1 @@ +dependencies: [] diff --git a/tests/integration/targets/dcnm_image_policy/tasks/dcnm.yaml b/tests/integration/targets/dcnm_image_policy/tasks/dcnm.yaml new file mode 100644 index 000000000..e419fc865 --- /dev/null +++ b/tests/integration/targets/dcnm_image_policy/tasks/dcnm.yaml @@ -0,0 +1,20 @@ +--- +- name: collect dcnm test cases + find: + paths: "{{ role_path }}/tests" + patterns: "{{ testcase }}.yaml" + connection: local + register: dcnm_cases + +- set_fact: + test_cases: + files: "{{ dcnm_cases.files }}" + +- name: set test_items + set_fact: test_items="{{ test_cases.files | map(attribute='path') | list }}" + +- name: run test cases (connection=httpapi) + include_tasks: "{{ test_case_to_run }}" + with_items: "{{ test_items }}" + loop_control: + loop_var: test_case_to_run diff --git a/tests/integration/targets/dcnm_image_policy/tasks/main.yaml b/tests/integration/targets/dcnm_image_policy/tasks/main.yaml new file mode 100644 index 000000000..fbcfa5803 --- /dev/null +++ b/tests/integration/targets/dcnm_image_policy/tasks/main.yaml @@ -0,0 +1,2 @@ +--- +- { include_tasks: dcnm.yaml, tags: ['dcnm'] } diff --git a/tests/integration/targets/dcnm_image_policy/tests/dcnm_image_policy_deleted.yaml b/tests/integration/targets/dcnm_image_policy/tests/dcnm_image_policy_deleted.yaml new file mode 100644 index 000000000..00b48c06b --- /dev/null +++ b/tests/integration/targets/dcnm_image_policy/tests/dcnm_image_policy_deleted.yaml @@ -0,0 +1,457 @@ +################################################################################ +# RUNTIME +################################################################################ + +# Recent run times (MM:SS.ms): +# 00:30.937 +# 00:24.057 +################################################################################ +# STEPS +################################################################################ + +# SETUP +# 1. The following images must already be uploaded to the controller +# See vars: section in cisco/dcnm/playbooks/dcnm_tests.yaml +# - nxos_image_1 +# - nxos_image_2 +# - epld_image_1 +# - epld_image_2 +# 2. No need for fabric or switches +# 3. Delete image policies under test, if they exist +# - image_policy_1 +# - image_policy_2 +# TEST +# 4. Create image policies and verify result +# - image_policy_1 +# - image_policy_2 +# 5. Delete image_policy_1 and verify result +# 6. Delete image_policy_2 and verify result +# CLEANUP +# 7. No cleanup required + +################################################################################ +# REQUIREMENTS +################################################################################ + +# 1. The following images must already be uploaded to the controller +# See vars: section below +# - nxos_image_1 +# - nxos_image_2 +# - epld_image_1 +# - epld_image_2 +# 2. No need for fabric or switches +# +# Example vars for dcnm_image_policy integration tests +# Add to cisco/dcnm/playbooks/dcnm_tests.yaml) +# +# vars: +# # This testcase field can run any test in the tests directory for the role +# testcase: deleted +# fabric_name: f1 +# username: admin +# password: "foobar" +# switch_username: admin +# switch_password: "foobar" +# spine1: 172.22.150.114 +# spine2: 172.22.150.115 +# leaf1: 172.22.150.106 +# leaf2: 172.22.150.107 +# leaf3: 172.22.150.108 +# leaf4: 172.22.150.109 +# # for dcnm_image_policy role +# image_policy_1: "KR5M" +# image_policy_2: "NR3F" +# epld_image_1: n9000-epld.10.2.5.M.img +# epld_image_2: n9000-epld.10.3.1.F.img +# nxos_image_1: n9000-dk9.10.2.5.M.bin +# nxos_image_2: n9000-dk9.10.3.1.F.bin +# nxos_release_1: 10.2.5_nxos64-cs_64bit +# nxos_release_2: 10.3.1_nxos64-cs_64bit + +################################################################################ +# SETUP +################################################################################ + +- name: DELETED - SETUP - Delete image policies + cisco.dcnm.dcnm_image_policy: + state: deleted + config: + - name: "{{ image_policy_1 }}" + - name: "{{ image_policy_2 }}" + register: result + +- debug: + var: result +################################################################################ +# DELETED - TEST - Create two image policies and verify +################################################################################ +# Expected result +# ok: [dcnm] => { +# "result": { +# "changed": true, +# "diff": [ +# { +# "agnostic": false, +# "epldImgName": "n9000-epld.10.2.5.M.img", +# "nxosVersion": "10.2.5_nxos64-cs_64bit", +# "platform": "N9K", +# "policyDescr": "KR5M", +# "policyName": "KR5M", +# "policyType": "PLATFORM", +# "sequence_number": 1 +# }, +# { +# "agnostic": false, +# "epldImgName": "n9000-epld.10.3.1.F.img", +# "nxosVersion": "10.3.1_nxos64-cs_64bit", +# "platform": "N9K", +# "policyDescr": "NR3F", +# "policyName": "NR3F", +# "policyType": "PLATFORM", +# "sequence_number": 2 +# } +# ], +# "failed": false, +# "metadata": [ +# { +# "action": "create", +# "check_mode": false, +# "sequence_number": 1, +# "state": "merged" +# }, +# { +# "action": "create", +# "check_mode": false, +# "sequence_number": 2, +# "state": "merged" +# } +# ], +# "response": [ +# { +# "DATA": { +# "lastOperDataObject": [], +# "message": "", +# "status": "SUCCESS" +# }, +# "MESSAGE": "OK", +# "METHOD": "GET", +# "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/policymgnt/policies", +# "RETURN_CODE": 200, +# "sequence_number": 0 +# }, +# { +# "DATA": "Policy created successfully.", +# "MESSAGE": "OK", +# "METHOD": "POST", +# "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/policymgnt/platform-policy", +# "RETURN_CODE": 200, +# "sequence_number": 1 +# }, +# { +# "DATA": "Policy created successfully.", +# "MESSAGE": "OK", +# "METHOD": "POST", +# "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/policymgnt/platform-policy", +# "RETURN_CODE": 200, +# "sequence_number": 2 +# } +# ], +# "result": [ +# { +# "found": true, +# "sequence_number": 0, +# "success": true +# }, +# { +# "changed": true, +# "sequence_number": 1, +# "success": true +# }, +# { +# "changed": true, +# "sequence_number": 2, +# "success": true +# } +# ] +# } +# } + +- name: DELETED - TEST - Create two image policies and verify + cisco.dcnm.dcnm_image_policy: + state: merged + config: + - name: "{{ image_policy_1 }}" + agnostic: false + description: "{{ image_policy_1 }}" + epld_image: "{{ epld_image_1 }}" + platform: N9K + release: "{{ nxos_release_1 }}" + type: PLATFORM + - name: "{{ image_policy_2 }}" + description: "{{ image_policy_2 }}" + platform: N9K + epld_image: "{{ epld_image_2 }}" + release: "{{ nxos_release_2 }}" + register: result + +- debug: + var: result + +- assert: + that: + - result.changed == true + - result.failed == false + - (result.diff | length) == 2 + - result.diff[0].policyName == image_policy_1 + - result.diff[0].policyDescr == image_policy_1 + - result.diff[0].epldImgName == epld_image_1 + - result.diff[0].nxosVersion == nxos_release_1 + - result.diff[1].policyName == image_policy_2 + - result.diff[1].policyDescr == image_policy_2 + - result.diff[1].epldImgName == epld_image_2 + - result.diff[1].nxosVersion == nxos_release_2 + - (result.response | length) == 3 + - result.response[0].MESSAGE == "OK" + - result.response[0].METHOD == "GET" + - result.response[0].RETURN_CODE == 200 + - result.response[1].MESSAGE == "OK" + - result.response[1].METHOD == "POST" + - result.response[1].RETURN_CODE == 200 + - result.response[2].MESSAGE == "OK" + - result.response[2].METHOD == "POST" + - result.response[2].RETURN_CODE == 200 + +################################################################################ +# DELETED - TEST - Delete first image policy (image_policy_1) and verify +################################################################################ +# Expected result +# ok: [dcnm] => { +# "result": { +# "changed": true, +# "diff": [ +# { +# "policyNames": [ +# "KR5M" +# ], +# "sequence_number": 1 +# } +# ], +# "failed": false, +# "metadata": [ +# { +# "action": "delete", +# "check_mode": false, +# "sequence_number": 1, +# "state": "deleted" +# } +# ], +# "response": [ +# { +# "DATA": { +# "lastOperDataObject": [ +# { +# "agnostic": false, +# "epldImgName": "n9000-epld.10.2.5.M.img", +# "fabricPolicyName": null, +# "imageName": "nxos64-cs.10.2.5.M.bin", +# "imagePresent": "Present", +# "nxosVersion": "10.2.5_nxos64-cs_64bit", +# "packageName": "", +# "platform": "N9K/N3K", +# "platformPolicies": "", +# "policyDescr": "KR5M", +# "policyName": "KR5M", +# "policyType": "PLATFORM", +# "ref_count": 0, +# "role": null, +# "rpmimages": null, +# "unInstall": false +# }, +# { +# "agnostic": false, +# "epldImgName": "n9000-epld.10.3.1.F.img", +# "fabricPolicyName": null, +# "imageName": "nxos64-cs.10.3.1.F.bin", +# "imagePresent": "Present", +# "nxosVersion": "10.3.1_nxos64-cs_64bit", +# "packageName": "", +# "platform": "N9K/N3K", +# "platformPolicies": "", +# "policyDescr": "NR3F", +# "policyName": "NR3F", +# "policyType": "PLATFORM", +# "ref_count": 0, +# "role": null, +# "rpmimages": null, +# "unInstall": false +# } +# ], +# "message": "", +# "status": "SUCCESS" +# }, +# "MESSAGE": "OK", +# "METHOD": "GET", +# "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/policymgnt/policies", +# "RETURN_CODE": 200, +# "sequence_number": 0 +# }, +# { +# "DATA": "Selected policy(s) deleted successfully.", +# "MESSAGE": "OK", +# "METHOD": "DELETE", +# "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/policymgnt/policy", +# "RETURN_CODE": 200, +# "sequence_number": 1 +# } +# ], +# "result": [ +# { +# "found": true, +# "sequence_number": 0, +# "success": true +# }, +# { +# "changed": true, +# "sequence_number": 1, +# "success": true +# } +# ] +# } +# } +- name: DELETED - TEST - Delete first image policy (image_policy_1) and verify + cisco.dcnm.dcnm_image_policy: + state: deleted + config: + - name: KR5M + register: result + +- debug: + var: result + +- assert: + that: + - result.changed == true + - result.failed == false + - (result.diff | length) == 1 + - image_policy_1 in result.diff[0].policyNames + - (result.response | length) == 2 + - result.response[0].MESSAGE == "OK" + - result.response[0].METHOD == "GET" + - result.response[0].RETURN_CODE == 200 + - result.response[1].MESSAGE == "OK" + - result.response[1].METHOD == "DELETE" + - result.response[1].RETURN_CODE == 200 + - (result.metadata | length) == 1 + - result.metadata[0].action == "delete" + - result.metadata[0].check_mode == False + - result.metadata[0].sequence_number == 1 + - result.metadata[0].state == "deleted" + +################################################################################ +# DELETED - TEST - Delete remaining policy (image_policy_2) and verify +################################################################################ +# Expected result +# ok: [dcnm] => { +# "result": { +# "changed": true, +# "diff": [ +# { +# "policyNames": [ +# "NR3F" +# ], +# "sequence_number": 1 +# } +# ], +# "failed": false, +# "metadata": [ +# { +# "action": "delete", +# "check_mode": false, +# "sequence_number": 1, +# "state": "deleted" +# } +# ], +# "response": [ +# { +# "DATA": { +# "lastOperDataObject": [ +# { +# "agnostic": false, +# "epldImgName": "n9000-epld.10.3.1.F.img", +# "fabricPolicyName": null, +# "imageName": "nxos64-cs.10.3.1.F.bin", +# "imagePresent": "Present", +# "nxosVersion": "10.3.1_nxos64-cs_64bit", +# "packageName": "", +# "platform": "N9K/N3K", +# "platformPolicies": "", +# "policyDescr": "NR3F", +# "policyName": "NR3F", +# "policyType": "PLATFORM", +# "ref_count": 0, +# "role": null, +# "rpmimages": null, +# "unInstall": false +# } +# ], +# "message": "", +# "status": "SUCCESS" +# }, +# "MESSAGE": "OK", +# "METHOD": "GET", +# "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/policymgnt/policies", +# "RETURN_CODE": 200, +# "sequence_number": 0 +# }, +# { +# "DATA": "Selected policy(s) deleted successfully.", +# "MESSAGE": "OK", +# "METHOD": "DELETE", +# "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/policymgnt/policy", +# "RETURN_CODE": 200, +# "sequence_number": 1 +# } +# ], +# "result": [ +# { +# "found": true, +# "sequence_number": 0, +# "success": true +# }, +# { +# "changed": true, +# "sequence_number": 1, +# "success": true +# } +# ] +# } +# } + +- name: DELETED - TEST - Delete remaining image policy (image_policy_2) and verify + cisco.dcnm.dcnm_image_policy: + state: deleted + config: + - name: NR3F + register: result + +- debug: + var: result + +- assert: + that: + - result.changed == true + - result.failed == false + - (result.diff | length) == 1 + - image_policy_2 in result.diff[0].policyNames + - (result.response | length) == 2 + - result.response[0].MESSAGE == "OK" + - result.response[0].METHOD == "GET" + - result.response[0].RETURN_CODE == 200 + - result.response[1].MESSAGE == "OK" + - result.response[1].METHOD == "DELETE" + - result.response[1].RETURN_CODE == 200 + - (result.metadata | length) == 1 + - result.metadata[0].action == "delete" + - result.metadata[0].check_mode == False + - result.metadata[0].sequence_number == 1 + - result.metadata[0].state == "deleted" diff --git a/tests/integration/targets/dcnm_image_policy/tests/dcnm_image_policy_merged.yaml b/tests/integration/targets/dcnm_image_policy/tests/dcnm_image_policy_merged.yaml new file mode 100644 index 000000000..65bb02c1f --- /dev/null +++ b/tests/integration/targets/dcnm_image_policy/tests/dcnm_image_policy_merged.yaml @@ -0,0 +1,356 @@ +################################################################################ +# RUNTIME +################################################################################ + +# Recent run times (MM:SS.ms): +################################################################################ +# STEPS +################################################################################ + +# SETUP +# 1. The following images must already be uploaded to the controller +# See vars: section in cisco/dcnm/playbooks/dcnm_tests.yaml +# - nxos_image_1 +# - nxos_image_2 +# - epld_image_1 +# - epld_image_2 +# 2. No need for fabric or switches +# 3. Delete image policies under test, if they exist +# - image_policy_1 +# - image_policy_2 +# TEST +# 4. Create image policies using merged state and verify result +# - image_policy_1 +# - image_policy_2 +# CLEANUP +# 7. Delete the image policies created in the test + +################################################################################ +# REQUIREMENTS +################################################################################ + +# 1. The following images must already be uploaded to the controller +# See vars: section below +# - nxos_image_1 +# - nxos_image_2 +# - epld_image_1 +# - epld_image_2 +# 2. No need for fabric or switches +# +# Example vars for dcnm_image_policy integration tests +# Add to cisco/dcnm/playbooks/dcnm_tests.yaml) +# +# vars: +# # This testcase field can run any test in the tests directory for the role +# testcase: deleted +# fabric_name: f1 +# username: admin +# password: "foobar" +# switch_username: admin +# switch_password: "foobar" +# spine1: 172.22.150.114 +# spine2: 172.22.150.115 +# leaf1: 172.22.150.106 +# leaf2: 172.22.150.107 +# leaf3: 172.22.150.108 +# leaf4: 172.22.150.109 +# # for dcnm_image_policy role +# image_policy_1: "KR5M" +# image_policy_2: "NR3F" +# epld_image_1: n9000-epld.10.2.5.M.img +# epld_image_2: n9000-epld.10.3.1.F.img +# nxos_image_1: n9000-dk9.10.2.5.M.bin +# nxos_image_2: n9000-dk9.10.3.1.F.bin +# nxos_release_1: 10.2.5_nxos64-cs_64bit +# nxos_release_2: 10.3.1_nxos64-cs_64bit + +################################################################################ +# SETUP +################################################################################ + +- name: MERGED - SETUP - Delete image policies + cisco.dcnm.dcnm_image_policy: + state: deleted + config: + - name: "{{ image_policy_1 }}" + - name: "{{ image_policy_2 }}" + register: result + +- debug: + var: result +################################################################################ +# MERGED - TEST - Create two image policies using merged state +################################################################################ +# Expected result +# +# ok: [dcnm] => { +# "result": { +# "changed": true, +# "diff": [ +# { +# "agnostic": false, +# "epldImgName": "n9000-epld.10.2.5.M.img", +# "nxosVersion": "10.2.5_nxos64-cs_64bit", +# "platform": "N9K", +# "policyDescr": "KR5M", +# "policyName": "KR5M", +# "policyType": "PLATFORM", +# "sequence_number": 1 +# }, +# { +# "agnostic": false, +# "epldImgName": "n9000-epld.10.3.1.F.img", +# "nxosVersion": "10.3.1_nxos64-cs_64bit", +# "platform": "N9K", +# "policyDescr": "NR3F", +# "policyName": "NR3F", +# "policyType": "PLATFORM", +# "sequence_number": 2 +# } +# ], +# "failed": false, +# "metadata": [ +# { +# "action": "create", +# "check_mode": false, +# "sequence_number": 1, +# "state": "merged" +# }, +# { +# "action": "create", +# "check_mode": false, +# "sequence_number": 2, +# "state": "merged" +# } +# ], +# "response": [ +# { +# "DATA": { +# "lastOperDataObject": [], +# "message": "", +# "status": "SUCCESS" +# }, +# "MESSAGE": "OK", +# "METHOD": "GET", +# "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/policymgnt/policies", +# "RETURN_CODE": 200, +# "sequence_number": 0 +# }, +# { +# "DATA": "Policy created successfully.", +# "MESSAGE": "OK", +# "METHOD": "POST", +# "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/policymgnt/platform-policy", +# "RETURN_CODE": 200, +# "sequence_number": 1 +# }, +# { +# "DATA": "Policy created successfully.", +# "MESSAGE": "OK", +# "METHOD": "POST", +# "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/policymgnt/platform-policy", +# "RETURN_CODE": 200, +# "sequence_number": 2 +# } +# ], +# "result": [ +# { +# "found": true, +# "sequence_number": 0, +# "success": true +# }, +# { +# "changed": true, +# "sequence_number": 1, +# "success": true +# }, +# { +# "changed": true, +# "sequence_number": 2, +# "success": true +# } +# ] +# } +# } +################################################################################ + + +- name: MERGED - TEST - Create two image policies + cisco.dcnm.dcnm_image_policy: + state: merged + config: + - name: "{{ image_policy_1 }}" + agnostic: false + description: "{{ image_policy_1 }}" + epld_image: "{{ epld_image_1 }}" + platform: N9K + release: "{{ nxos_release_1 }}" + type: PLATFORM + - name: "{{ image_policy_2 }}" + description: "{{ image_policy_2 }}" + epld_image: "{{ epld_image_2 }}" + platform: N9K + release: "{{ nxos_release_2 }}" + register: result + +- debug: + var: result + +- assert: + that: + - result.changed == true + - result.failed == false + - (result.diff | length) == 2 + - result.diff[0].policyName == image_policy_1 + - result.diff[1].policyName == image_policy_2 + - result.diff[0].policyDescr == image_policy_1 + - result.diff[1].policyDescr == image_policy_2 + - result.diff[0].agnostic == false + - result.diff[1].agnostic == false + - result.diff[0].epldImgName == epld_image_1 + - result.diff[1].epldImgName == epld_image_2 + - result.diff[0].nxosVersion == nxos_release_1 + - result.diff[1].nxosVersion == nxos_release_2 + - result.diff[0].platform == "N9K" + - result.diff[1].platform == "N9K" + - result.diff[0].policyType == "PLATFORM" + - result.diff[1].policyType == "PLATFORM" + - (result.metadata | length) == 2 + - result.metadata[0].action == "create" + - result.metadata[0].check_mode == False + - result.metadata[0].sequence_number == 1 + - result.metadata[0].state == "merged" + - result.metadata[1].action == "create" + - result.metadata[1].check_mode == False + - result.metadata[1].sequence_number == 2 + - result.metadata[1].state == "merged" + +################################################################################ +# MERGED - CLEANUP - Delete image policies +################################################################################ +# Expected result +# ok: [dcnm] => { +# "result": { +# "changed": true, +# "diff": [ +# { +# "policyNames": [ +# "NR3F", +# "KR5M" +# ], +# "sequence_number": 1 +# } +# ], +# "failed": false, +# "metadata": [ +# { +# "action": "delete", +# "check_mode": false, +# "sequence_number": 1, +# "state": "deleted" +# } +# ], +# "response": [ +# { +# "DATA": { +# "lastOperDataObject": [ +# { +# "agnostic": false, +# "epldImgName": "n9000-epld.10.2.5.M.img", +# "fabricPolicyName": null, +# "imageName": "nxos64-cs.10.2.5.M.bin", +# "imagePresent": "Present", +# "nxosVersion": "10.2.5_nxos64-cs_64bit", +# "packageName": "", +# "platform": "N9K/N3K", +# "platformPolicies": "", +# "policyDescr": "KR5M", +# "policyName": "KR5M", +# "policyType": "PLATFORM", +# "ref_count": 0, +# "role": null, +# "rpmimages": null, +# "unInstall": false +# }, +# { +# "agnostic": false, +# "epldImgName": "n9000-epld.10.3.1.F.img", +# "fabricPolicyName": null, +# "imageName": "nxos64-cs.10.3.1.F.bin", +# "imagePresent": "Present", +# "nxosVersion": "10.3.1_nxos64-cs_64bit", +# "packageName": "", +# "platform": "N9K/N3K", +# "platformPolicies": "", +# "policyDescr": "NR3F", +# "policyName": "NR3F", +# "policyType": "PLATFORM", +# "ref_count": 0, +# "role": null, +# "rpmimages": null, +# "unInstall": false +# } +# ], +# "message": "", +# "status": "SUCCESS" +# }, +# "MESSAGE": "OK", +# "METHOD": "GET", +# "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/policymgnt/policies", +# "RETURN_CODE": 200, +# "sequence_number": 0 +# }, +# { +# "DATA": "Selected policy(s) deleted successfully.", +# "MESSAGE": "OK", +# "METHOD": "DELETE", +# "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/policymgnt/policy", +# "RETURN_CODE": 200, +# "sequence_number": 1 +# } +# ], +# "result": [ +# { +# "found": true, +# "sequence_number": 0, +# "success": true +# }, +# { +# "changed": true, +# "sequence_number": 1, +# "success": true +# } +# ] +# } +# } + +- name: MERGED - CLEANUP - Delete image policies + cisco.dcnm.dcnm_image_policy: + state: deleted + config: + - name: NR3F + - name: KR5M + register: result + +- debug: + var: result + +- assert: + that: + - result.changed == true + - result.failed == false + - (result.diff | length) == 1 + - image_policy_1 in result.diff[0].policyNames + - image_policy_2 in result.diff[0].policyNames + - (result.response | length) == 2 + - result.response[0].MESSAGE == "OK" + - result.response[0].METHOD == "GET" + - result.response[0].RETURN_CODE == 200 + - result.response[1].MESSAGE == "OK" + - result.response[1].METHOD == "DELETE" + - result.response[1].RETURN_CODE == 200 + - (result.metadata | length) == 1 + - result.metadata[0].action == "delete" + - result.metadata[0].check_mode == False + - result.metadata[0].sequence_number == 1 + - result.metadata[0].state == "deleted" diff --git a/tests/integration/targets/dcnm_image_policy/tests/dcnm_image_policy_overridden.yaml b/tests/integration/targets/dcnm_image_policy/tests/dcnm_image_policy_overridden.yaml new file mode 100644 index 000000000..a6d44130a --- /dev/null +++ b/tests/integration/targets/dcnm_image_policy/tests/dcnm_image_policy_overridden.yaml @@ -0,0 +1,563 @@ +################################################################################ +# RUNTIME +################################################################################ +# +# Recent run times (MM:SS.ms): +# 00.27.549 +# 00.27.943 + +################################################################################ +# STEPS +################################################################################ + +# SETUP +# 1. The following images must already be uploaded to the controller +# See vars: section in cisco/dcnm/playbooks/dcnm_tests.yaml +# - nxos_image_1 +# - nxos_image_2 +# - epld_image_1 +# - epld_image_2 +# 2. No need for fabric or switches +# 3. Delete image policies under test, if they exist +# - image_policy_1 +# - image_policy_2 +# 4. Create image policies using merged state and verify result +# - image_policy_1 +# - image_policy_2 +# +# TEST +# +# 5. Use overridden state to delete/modify image_policy_1 and verify that image_policy_2 is deleted. +# +# CLEANUP +# +# 6. Delete the image policies created in the test + +################################################################################ +# REQUIREMENTS +################################################################################ + +# 1. The following images must already be uploaded to the controller +# See vars: section below +# - nxos_image_1 +# - nxos_image_2 +# - epld_image_1 +# - epld_image_2 +# 2. No need for fabric or switches +# +# Example vars for dcnm_image_policy integration tests +# Add to cisco/dcnm/playbooks/dcnm_tests.yaml) +# +# vars: +# # This testcase field can run any test in the tests directory for the role +# testcase: deleted +# fabric_name: f1 +# username: admin +# password: "foobar" +# switch_username: admin +# switch_password: "foobar" +# spine1: 172.22.150.114 +# spine2: 172.22.150.115 +# leaf1: 172.22.150.106 +# leaf2: 172.22.150.107 +# leaf3: 172.22.150.108 +# leaf4: 172.22.150.109 +# # for dcnm_image_policy role +# image_policy_1: "KR5M" +# image_policy_2: "NR3F" +# epld_image_1: n9000-epld.10.2.5.M.img +# epld_image_2: n9000-epld.10.3.1.F.img +# nxos_image_1: n9000-dk9.10.2.5.M.bin +# nxos_image_2: n9000-dk9.10.3.1.F.bin +# nxos_release_1: 10.2.5_nxos64-cs_64bit +# nxos_release_2: 10.3.1_nxos64-cs_64bit + +################################################################################ +# SETUP +################################################################################ + +- name: OVERRIDDEN - SETUP - Delete image policies + cisco.dcnm.dcnm_image_policy: + state: deleted + config: + - name: "{{ image_policy_1 }}" + - name: "{{ image_policy_2 }}" + register: result + +- debug: + var: result + +################################################################################ +# OVERRIDDEN - TEST - Create two image policies using merged state +################################################################################ +# Expected result +# ok: [dcnm] => { +# "result": { +# "changed": true, +# "diff": [ +# { +# "agnostic": false, +# "epldImgName": "n9000-epld.10.2.5.M.img", +# "nxosVersion": "10.2.5_nxos64-cs_64bit", +# "platform": "N9K", +# "policyDescr": "KR5M", +# "policyName": "KR5M", +# "policyType": "PLATFORM", +# "sequence_number": 1 +# }, +# { +# "agnostic": false, +# "epldImgName": "n9000-epld.10.3.1.F.img", +# "nxosVersion": "10.3.1_nxos64-cs_64bit", +# "platform": "N9K", +# "policyDescr": "NR3F", +# "policyName": "NR3F", +# "policyType": "PLATFORM", +# "sequence_number": 2 +# } +# ], +# "failed": false, +# "metadata": [ +# { +# "action": "create", +# "check_mode": false, +# "sequence_number": 1, +# "state": "merged" +# }, +# { +# "action": "create", +# "check_mode": false, +# "sequence_number": 2, +# "state": "merged" +# } +# ], +# "response": [ +# { +# "DATA": { +# "lastOperDataObject": [], +# "message": "", +# "status": "SUCCESS" +# }, +# "MESSAGE": "OK", +# "METHOD": "GET", +# "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/policymgnt/policies", +# "RETURN_CODE": 200, +# "sequence_number": 0 +# }, +# { +# "DATA": "Policy created successfully.", +# "MESSAGE": "OK", +# "METHOD": "POST", +# "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/policymgnt/platform-policy", +# "RETURN_CODE": 200, +# "sequence_number": 1 +# }, +# { +# "DATA": "Policy created successfully.", +# "MESSAGE": "OK", +# "METHOD": "POST", +# "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/policymgnt/platform-policy", +# "RETURN_CODE": 200, +# "sequence_number": 2 +# } +# ], +# "result": [ +# { +# "found": true, +# "sequence_number": 0, +# "success": true +# }, +# { +# "changed": true, +# "sequence_number": 1, +# "success": true +# }, +# { +# "changed": true, +# "sequence_number": 2, +# "success": true +# } +# ] +# } +# }################################################################################ + +- name: OVERRIDDEN - TEST - Create two image policies using merged state + cisco.dcnm.dcnm_image_policy: + state: merged + config: + - name: "{{ image_policy_1 }}" + agnostic: false + description: "{{ image_policy_1 }}" + epld_image: "{{ epld_image_1 }}" + platform: N9K + release: "{{ nxos_release_1 }}" + type: PLATFORM + - name: "{{ image_policy_2 }}" + description: "{{ image_policy_2 }}" + epld_image: "{{ epld_image_2 }}" + platform: N9K + release: "{{ nxos_release_2 }}" + register: result + +- debug: + var: result + +- assert: + that: + - result.changed == true + - result.failed == false + - (result.diff | length) == 2 + - result.diff[0].policyName == image_policy_1 + - result.diff[1].policyName == image_policy_2 + - result.diff[0].policyDescr == image_policy_1 + - result.diff[1].policyDescr == image_policy_2 + - result.diff[0].agnostic == false + - result.diff[1].agnostic == false + - result.diff[0].epldImgName == epld_image_1 + - result.diff[1].epldImgName == epld_image_2 + - result.diff[0].nxosVersion == nxos_release_1 + - result.diff[1].nxosVersion == nxos_release_2 + - result.diff[0].platform == "N9K" + - result.diff[1].platform == "N9K" + - result.diff[0].policyType == "PLATFORM" + - result.diff[1].policyType == "PLATFORM" + - (result.response | length) == 3 + - result.response[0].MESSAGE == "OK" + - result.response[0].METHOD == "GET" + - result.response[0].RETURN_CODE == 200 + - result.response[1].MESSAGE == "OK" + - result.response[1].METHOD == "POST" + - result.response[1].RETURN_CODE == 200 + - result.response[2].MESSAGE == "OK" + - result.response[2].METHOD == "POST" + - result.response[2].RETURN_CODE == 200 + +################################################################################ +# OVERRIDDEN - TEST - override image_policy_1 which will delete image_policy_2 +################################################################################ +# Expected result +# ok: [dcnm] => { +# "result": { +# "changed": true, +# "diff": [ +# { +# "policyNames": [ +# "NR3F" +# ], +# "sequence_number": 1 +# }, +# { +# "agnostic": false, +# "epldImgName": "n9000-epld.10.2.5.M.img", +# "fabricPolicyName": null, +# "imagePresent": "Present", +# "nxosVersion": "10.2.5_nxos64-cs_64bit", +# "packageName": "", +# "platform": "N9K", +# "policyDescr": "KR5M overridden", +# "policyName": "KR5M", +# "policyType": "PLATFORM", +# "role": null, +# "rpmimages": null, +# "sequence_number": 2, +# "unInstall": false +# } +# ], +# "failed": false, +# "metadata": [ +# { +# "action": "delete", +# "check_mode": false, +# "sequence_number": 1, +# "state": "overridden" +# }, +# { +# "action": "update", +# "check_mode": false, +# "sequence_number": 2, +# "state": "overridden" +# } +# ], +# "response": [ +# { +# "DATA": { +# "lastOperDataObject": [ +# { +# "agnostic": false, +# "epldImgName": "n9000-epld.10.2.5.M.img", +# "fabricPolicyName": null, +# "imageName": "nxos64-cs.10.2.5.M.bin", +# "imagePresent": "Present", +# "nxosVersion": "10.2.5_nxos64-cs_64bit", +# "packageName": "", +# "platform": "N9K/N3K", +# "platformPolicies": "", +# "policyDescr": "KR5M", +# "policyName": "KR5M", +# "policyType": "PLATFORM", +# "ref_count": 0, +# "role": null, +# "rpmimages": null, +# "unInstall": false +# }, +# { +# "agnostic": false, +# "epldImgName": "n9000-epld.10.3.1.F.img", +# "fabricPolicyName": null, +# "imageName": "nxos64-cs.10.3.1.F.bin", +# "imagePresent": "Present", +# "nxosVersion": "10.3.1_nxos64-cs_64bit", +# "packageName": "", +# "platform": "N9K/N3K", +# "platformPolicies": "", +# "policyDescr": "NR3F", +# "policyName": "NR3F", +# "policyType": "PLATFORM", +# "ref_count": 0, +# "role": null, +# "rpmimages": null, +# "unInstall": false +# } +# ], +# "message": "", +# "status": "SUCCESS" +# }, +# "MESSAGE": "OK", +# "METHOD": "GET", +# "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/policymgnt/policies", +# "RETURN_CODE": 200, +# "sequence_number": 0 +# }, +# { +# "DATA": "Selected policy(s) deleted successfully.", +# "MESSAGE": "OK", +# "METHOD": "DELETE", +# "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/policymgnt/policy", +# "RETURN_CODE": 200, +# "sequence_number": 1 +# }, +# { +# "DATA": { +# "lastOperDataObject": [ +# { +# "agnostic": false, +# "epldImgName": "n9000-epld.10.2.5.M.img", +# "fabricPolicyName": null, +# "imageName": "nxos64-cs.10.2.5.M.bin", +# "imagePresent": "Present", +# "nxosVersion": "10.2.5_nxos64-cs_64bit", +# "packageName": "", +# "platform": "N9K/N3K", +# "platformPolicies": "", +# "policyDescr": "KR5M", +# "policyName": "KR5M", +# "policyType": "PLATFORM", +# "ref_count": 0, +# "role": null, +# "rpmimages": null, +# "unInstall": false +# } +# ], +# "message": "", +# "status": "SUCCESS" +# }, +# "MESSAGE": "OK", +# "METHOD": "GET", +# "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/policymgnt/policies", +# "RETURN_CODE": 200, +# "sequence_number": 1 +# }, +# { +# "DATA": "Policy updated successfully.", +# "MESSAGE": "OK", +# "METHOD": "POST", +# "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/policymgnt/edit-policy", +# "RETURN_CODE": 200, +# "sequence_number": 2 +# } +# ], +# "result": [ +# { +# "found": true, +# "sequence_number": 0, +# "success": true +# }, +# { +# "changed": true, +# "sequence_number": 1, +# "success": true +# }, +# { +# "found": true, +# "sequence_number": 1, +# "success": true +# }, +# { +# "changed": true, +# "sequence_number": 2, +# "success": true +# } +# ] +# } +# } +- name: OVERRIDDEN - TEST - override image_policy_1 which will delete image_policy_2 + cisco.dcnm.dcnm_image_policy: + state: overridden + config: + - name: "{{ image_policy_1 }}" + agnostic: false + description: "{{ image_policy_1 }} overridden" + epld_image: "{{ epld_image_1 }}" + platform: N9K + release: "{{ nxos_release_1 }}" + type: PLATFORM + register: result + +- debug: + var: result + +- assert: + that: + - result.changed == true + - result.failed == false + - (result.diff | length) == 2 + - image_policy_2 in result.diff[0].policyNames + - result.diff[1].agnostic == false + - result.diff[1].policyName == image_policy_1 + - result.diff[1].policyDescr == image_policy_1 + " overridden" + - result.diff[1].epldImgName == epld_image_1 + - result.diff[1].nxosVersion == nxos_release_1 + - result.diff[1].platform == "N9K" + - result.diff[1].policyType == "PLATFORM" + - (result.metadata | length) == 2 + - result.metadata[0].action == "delete" + - result.metadata[1].action == "update" + - result.metadata[0].state == "overridden" + - result.metadata[1].state == "overridden" + - result.metadata[0].check_mode == False + - result.metadata[1].check_mode == False + - result.metadata[0].sequence_number == 1 + - result.metadata[1].sequence_number == 2 + - (result.response | length) == 4 + - result.response[0].MESSAGE == "OK" + - result.response[0].METHOD == "GET" + - result.response[0].RETURN_CODE == 200 + - result.response[1].MESSAGE == "OK" + - result.response[1].METHOD == "DELETE" + - result.response[1].RETURN_CODE == 200 + - result.response[2].MESSAGE == "OK" + - result.response[2].METHOD == "GET" + - result.response[2].RETURN_CODE == 200 + - result.response[3].MESSAGE == "OK" + - result.response[3].METHOD == "POST" + - result.response[3].RETURN_CODE == 200 + +################################################################################ +# OVERRIDDEN - CLEANUP - Delete image policies and verify +################################################################################ +# Expected result +# ok: [dcnm] => { +# "result": { +# "changed": true, +# "diff": [ +# { +# "policyNames": [ +# "KR5M" +# ], +# "sequence_number": 1 +# } +# ], +# "failed": false, +# "metadata": [ +# { +# "action": "delete", +# "check_mode": false, +# "sequence_number": 1, +# "state": "deleted" +# } +# ], +# "response": [ +# { +# "DATA": { +# "lastOperDataObject": [ +# { +# "agnostic": false, +# "epldImgName": "n9000-epld.10.2.5.M.img", +# "fabricPolicyName": null, +# "imageName": "nxos64-cs.10.2.5.M.bin", +# "imagePresent": "Present", +# "nxosVersion": "10.2.5_nxos64-cs_64bit", +# "packageName": "", +# "platform": "N9K/N3K", +# "platformPolicies": "", +# "policyDescr": "KR5M overridden", +# "policyName": "KR5M", +# "policyType": "PLATFORM", +# "ref_count": 0, +# "role": null, +# "rpmimages": null, +# "unInstall": false +# } +# ], +# "message": "", +# "status": "SUCCESS" +# }, +# "MESSAGE": "OK", +# "METHOD": "GET", +# "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/policymgnt/policies", +# "RETURN_CODE": 200, +# "sequence_number": 0 +# }, +# { +# "DATA": "Selected policy(s) deleted successfully.", +# "MESSAGE": "OK", +# "METHOD": "DELETE", +# "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/policymgnt/policy", +# "RETURN_CODE": 200, +# "sequence_number": 1 +# } +# ], +# "result": [ +# { +# "found": true, +# "sequence_number": 0, +# "success": true +# }, +# { +# "changed": true, +# "sequence_number": 1, +# "success": true +# } +# ] +# } +# } +- name: OVERRIDDEN - CLEANUP - Delete image policies and verify + cisco.dcnm.dcnm_image_policy: + state: deleted + config: + - name: "{{ image_policy_1 }}" + - name: "{{ image_policy_2 }}" + register: result + +- debug: + var: result + +- assert: + that: + - result.changed == true + - result.failed == false + - (result.diff | length) == 1 + - image_policy_1 in result.diff[0].policyNames + - image_policy_2 not in result.diff[0].policyNames + - (result.response | length) == 2 + - result.response[0].MESSAGE == "OK" + - result.response[0].METHOD == "GET" + - result.response[0].RETURN_CODE == 200 + - result.response[1].MESSAGE == "OK" + - result.response[1].METHOD == "DELETE" + - result.response[1].RETURN_CODE == 200 + - (result.metadata | length) == 1 + - result.metadata[0].action == "delete" + - result.metadata[0].check_mode == False + - result.metadata[0].sequence_number == 1 + - result.metadata[0].state == "deleted" diff --git a/tests/integration/targets/dcnm_image_policy/tests/dcnm_image_policy_query.yaml b/tests/integration/targets/dcnm_image_policy/tests/dcnm_image_policy_query.yaml new file mode 100644 index 000000000..08bcc330e --- /dev/null +++ b/tests/integration/targets/dcnm_image_policy/tests/dcnm_image_policy_query.yaml @@ -0,0 +1,566 @@ +################################################################################ +# RUNTIME +################################################################################ +# +# Recent run times (MM:SS.ms): +# 00.26.844 +# 00.25.253 + +################################################################################ +# STEPS +################################################################################ + +# SETUP +# 1. The following images must already be uploaded to the controller +# See vars: section in cisco/dcnm/playbooks/dcnm_tests.yaml +# - nxos_image_1 +# - nxos_image_2 +# - epld_image_1 +# - epld_image_2 +# 2. No need for fabric or switches +# 3. Delete image policies under test, if they exist +# - image_policy_1 +# - image_policy_2 +# 4. Create image policies using merged state and verify result +# - image_policy_1 +# - image_policy_2 +# +# TEST +# +# 5. Use query state to verify both policies: +# +# CLEANUP +# +# 6. Delete the image policies created in the test + +################################################################################ +# REQUIREMENTS +################################################################################ + +# 1. The following images must already be uploaded to the controller +# See vars: section below +# - nxos_image_1 +# - nxos_image_2 +# - epld_image_1 +# - epld_image_2 +# 2. No need for fabric or switches +# +# Example vars for dcnm_image_policy integration tests +# Add to cisco/dcnm/playbooks/dcnm_tests.yaml) +# +# vars: +# # This testcase field can run any test in the tests directory for the role +# testcase: deleted +# fabric_name: f1 +# username: admin +# password: "foobar" +# switch_username: admin +# switch_password: "foobar" +# spine1: 172.22.150.114 +# spine2: 172.22.150.115 +# leaf1: 172.22.150.106 +# leaf2: 172.22.150.107 +# leaf3: 172.22.150.108 +# leaf4: 172.22.150.109 +# # for dcnm_image_policy role +# image_policy_1: "KR5M" +# image_policy_2: "NR3F" +# epld_image_1: n9000-epld.10.2.5.M.img +# epld_image_2: n9000-epld.10.3.1.F.img +# nxos_image_1: n9000-dk9.10.2.5.M.bin +# nxos_image_2: n9000-dk9.10.3.1.F.bin +# nxos_release_1: 10.2.5_nxos64-cs_64bit +# nxos_release_2: 10.3.1_nxos64-cs_64bit + +################################################################################ +# QUERY - SETUP - Delete image policies if they exist +################################################################################ + +- name: QUERY - SETUP - Delete image policies + cisco.dcnm.dcnm_image_policy: + state: deleted + config: + - name: "{{ image_policy_1 }}" + - name: "{{ image_policy_2 }}" + register: result + +- debug: + var: result + +################################################################################ +# QUERY - SETUP - Create two image policies using merged state +################################################################################ +# Expected result +# +# ok: [dcnm] => { +# "result": { +# "changed": true, +# "diff": [ +# { +# "agnostic": false, +# "epldImgName": "n9000-epld.10.2.5.M.img", +# "nxosVersion": "10.2.5_nxos64-cs_64bit", +# "platform": "N9K", +# "policyDescr": "KR5M", +# "policyName": "KR5M", +# "policyType": "PLATFORM", +# "sequence_number": 1 +# }, +# { +# "agnostic": false, +# "epldImgName": "n9000-epld.10.3.1.F.img", +# "nxosVersion": "10.3.1_nxos64-cs_64bit", +# "platform": "N9K", +# "policyDescr": "NR3F", +# "policyName": "NR3F", +# "policyType": "PLATFORM", +# "sequence_number": 2 +# } +# ], +# "failed": false, +# "metadata": [ +# { +# "action": "create", +# "check_mode": false, +# "sequence_number": 1, +# "state": "merged" +# }, +# { +# "action": "create", +# "check_mode": false, +# "sequence_number": 2, +# "state": "merged" +# } +# ], +# "response": [ +# { +# "DATA": { +# "lastOperDataObject": [], +# "message": "", +# "status": "SUCCESS" +# }, +# "MESSAGE": "OK", +# "METHOD": "GET", +# "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/policymgnt/policies", +# "RETURN_CODE": 200, +# "sequence_number": 0 +# }, +# { +# "DATA": "Policy created successfully.", +# "MESSAGE": "OK", +# "METHOD": "POST", +# "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/policymgnt/platform-policy", +# "RETURN_CODE": 200, +# "sequence_number": 1 +# }, +# { +# "DATA": "Policy created successfully.", +# "MESSAGE": "OK", +# "METHOD": "POST", +# "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/policymgnt/platform-policy", +# "RETURN_CODE": 200, +# "sequence_number": 2 +# } +# ], +# "result": [ +# { +# "found": true, +# "sequence_number": 0, +# "success": true +# }, +# { +# "changed": true, +# "sequence_number": 1, +# "success": true +# }, +# { +# "changed": true, +# "sequence_number": 2, +# "success": true +# } +# ] +# } +# }################################################################################ + +- name: QUERY - SETUP - Create two image policies using merged state + cisco.dcnm.dcnm_image_policy: + state: merged + config: + - name: "{{ image_policy_1 }}" + agnostic: false + description: "{{ image_policy_1 }}" + epld_image: "{{ epld_image_1 }}" + platform: N9K + release: "{{ nxos_release_1 }}" + type: PLATFORM + - name: "{{ image_policy_2 }}" + description: "{{ image_policy_2 }}" + epld_image: "{{ epld_image_2 }}" + platform: N9K + release: "{{ nxos_release_2 }}" + register: result + +- debug: + var: result + +- assert: + that: + - result.changed == true + - result.failed == false + - (result.diff | length) == 2 + - result.diff[0].policyName == image_policy_1 + - result.diff[1].policyName == image_policy_2 + - result.diff[0].policyDescr == image_policy_1 + - result.diff[1].policyDescr == image_policy_2 + - result.diff[0].agnostic == false + - result.diff[1].agnostic == false + - result.diff[0].epldImgName == epld_image_1 + - result.diff[1].epldImgName == epld_image_2 + - result.diff[0].nxosVersion == nxos_release_1 + - result.diff[1].nxosVersion == nxos_release_2 + - result.diff[0].platform == "N9K" + - result.diff[1].platform == "N9K" + - result.diff[0].policyType == "PLATFORM" + - result.diff[1].policyType == "PLATFORM" + - (result.response | length) == 3 + +################################################################################ +# QUERY - TEST - query image policies and verify results +################################################################################ +# Expected result +# ok: [dcnm] => { +# "result": { +# "changed": false, +# "diff": [ +# { +# "agnostic": false, +# "epldImgName": "n9000-epld.10.2.5.M.img", +# "fabricPolicyName": null, +# "imageName": "nxos64-cs.10.2.5.M.bin", +# "imagePresent": "Present", +# "nxosVersion": "10.2.5_nxos64-cs_64bit", +# "packageName": "", +# "platform": "N9K/N3K", +# "platformPolicies": "", +# "policyDescr": "KR5M", +# "policyName": "KR5M", +# "policyType": "PLATFORM", +# "ref_count": 0, +# "role": null, +# "rpmimages": null, +# "sequence_number": 1, +# "unInstall": false +# }, +# { +# "agnostic": false, +# "epldImgName": "n9000-epld.10.3.1.F.img", +# "fabricPolicyName": null, +# "imageName": "nxos64-cs.10.3.1.F.bin", +# "imagePresent": "Present", +# "nxosVersion": "10.3.1_nxos64-cs_64bit", +# "packageName": "", +# "platform": "N9K/N3K", +# "platformPolicies": "", +# "policyDescr": "NR3F", +# "policyName": "NR3F", +# "policyType": "PLATFORM", +# "ref_count": 0, +# "role": null, +# "rpmimages": null, +# "sequence_number": 2, +# "unInstall": false +# } +# ], +# "failed": false, +# "metadata": [ +# { +# "action": "query", +# "check_mode": false, +# "sequence_number": 1, +# "state": "query" +# }, +# { +# "action": "query", +# "check_mode": false, +# "sequence_number": 2, +# "state": "query" +# } +# ], +# "response": [ +# { +# "DATA": { +# "lastOperDataObject": [ +# { +# "agnostic": false, +# "epldImgName": "n9000-epld.10.2.5.M.img", +# "fabricPolicyName": null, +# "imageName": "nxos64-cs.10.2.5.M.bin", +# "imagePresent": "Present", +# "nxosVersion": "10.2.5_nxos64-cs_64bit", +# "packageName": "", +# "platform": "N9K/N3K", +# "platformPolicies": "", +# "policyDescr": "KR5M", +# "policyName": "KR5M", +# "policyType": "PLATFORM", +# "ref_count": 0, +# "role": null, +# "rpmimages": null, +# "unInstall": false +# }, +# { +# "agnostic": false, +# "epldImgName": "n9000-epld.10.3.1.F.img", +# "fabricPolicyName": null, +# "imageName": "nxos64-cs.10.3.1.F.bin", +# "imagePresent": "Present", +# "nxosVersion": "10.3.1_nxos64-cs_64bit", +# "packageName": "", +# "platform": "N9K/N3K", +# "platformPolicies": "", +# "policyDescr": "NR3F", +# "policyName": "NR3F", +# "policyType": "PLATFORM", +# "ref_count": 0, +# "role": null, +# "rpmimages": null, +# "unInstall": false +# } +# ], +# "message": "", +# "status": "SUCCESS" +# }, +# "MESSAGE": "OK", +# "METHOD": "GET", +# "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/policymgnt/policies", +# "RETURN_CODE": 200, +# "sequence_number": 1 +# }, +# { +# "DATA": { +# "lastOperDataObject": [ +# { +# "agnostic": false, +# "epldImgName": "n9000-epld.10.2.5.M.img", +# "fabricPolicyName": null, +# "imageName": "nxos64-cs.10.2.5.M.bin", +# "imagePresent": "Present", +# "nxosVersion": "10.2.5_nxos64-cs_64bit", +# "packageName": "", +# "platform": "N9K/N3K", +# "platformPolicies": "", +# "policyDescr": "KR5M", +# "policyName": "KR5M", +# "policyType": "PLATFORM", +# "ref_count": 0, +# "role": null, +# "rpmimages": null, +# "unInstall": false +# }, +# { +# "agnostic": false, +# "epldImgName": "n9000-epld.10.3.1.F.img", +# "fabricPolicyName": null, +# "imageName": "nxos64-cs.10.3.1.F.bin", +# "imagePresent": "Present", +# "nxosVersion": "10.3.1_nxos64-cs_64bit", +# "packageName": "", +# "platform": "N9K/N3K", +# "platformPolicies": "", +# "policyDescr": "NR3F", +# "policyName": "NR3F", +# "policyType": "PLATFORM", +# "ref_count": 0, +# "role": null, +# "rpmimages": null, +# "unInstall": false +# } +# ], +# "message": "", +# "status": "SUCCESS" +# }, +# "MESSAGE": "OK", +# "METHOD": "GET", +# "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/policymgnt/policies", +# "RETURN_CODE": 200, +# "sequence_number": 2 +# } +# ], +# "result": [ +# { +# "found": true, +# "sequence_number": 1, +# "success": true +# }, +# { +# "found": true, +# "sequence_number": 2, +# "success": true +# } +# ] +# } +# } + +- name: QUERY - TEST - query image policies and verify results + cisco.dcnm.dcnm_image_policy: + state: query + config: + - name: "{{ image_policy_1 }}" + - name: "{{ image_policy_2 }}" + register: result + +- debug: + var: result + +- assert: + that: + - result.changed == false + - result.failed == false + - (result.diff | length) == 2 + - result.diff[0].agnostic == false + - result.diff[1].agnostic == false + - result.diff[0].policyName == image_policy_1 + - result.diff[1].policyName == image_policy_2 + - result.diff[0].policyDescr == image_policy_1 + - result.diff[1].policyDescr == image_policy_2 + - result.diff[0].epldImgName == epld_image_1 + - result.diff[0].nxosVersion == nxos_release_1 + - result.diff[0].platform == "N9K/N3K" + - result.diff[1].platform == "N9K/N3K" + - result.diff[0].policyType == "PLATFORM" + - result.diff[1].policyType == "PLATFORM" + - result.diff[0].ref_count == 0 + - result.diff[1].ref_count == 0 + - result.response[0].MESSAGE == "OK" + - result.response[0].METHOD == "GET" + - result.response[0].RETURN_CODE == 200 + - result.response[1].MESSAGE == "OK" + - result.response[1].METHOD == "GET" + - result.response[1].RETURN_CODE == 200 + - (result.response | length) == 2 + - result.metadata[0].action == "query" + - result.metadata[1].action == "query" + +################################################################################ +# QUERY - CLEANUP - Delete image policies and verify +################################################################################ +# Expected result +# ok: [dcnm] => { +# "result": { +# "changed": true, +# "diff": [ +# { +# "policyNames": [ +# "KR5M", +# "NR3F" +# ], +# "sequence_number": 1 +# } +# ], +# "failed": false, +# "metadata": [ +# { +# "action": "delete", +# "check_mode": false, +# "sequence_number": 1, +# "state": "deleted" +# } +# ], +# "response": [ +# { +# "DATA": { +# "lastOperDataObject": [ +# { +# "agnostic": false, +# "epldImgName": "n9000-epld.10.2.5.M.img", +# "fabricPolicyName": null, +# "imageName": "nxos64-cs.10.2.5.M.bin", +# "imagePresent": "Present", +# "nxosVersion": "10.2.5_nxos64-cs_64bit", +# "packageName": "", +# "platform": "N9K/N3K", +# "platformPolicies": "", +# "policyDescr": "KR5M", +# "policyName": "KR5M", +# "policyType": "PLATFORM", +# "ref_count": 0, +# "role": null, +# "rpmimages": null, +# "unInstall": false +# }, +# { +# "agnostic": false, +# "epldImgName": "n9000-epld.10.3.1.F.img", +# "fabricPolicyName": null, +# "imageName": "nxos64-cs.10.3.1.F.bin", +# "imagePresent": "Present", +# "nxosVersion": "10.3.1_nxos64-cs_64bit", +# "packageName": "", +# "platform": "N9K/N3K", +# "platformPolicies": "", +# "policyDescr": "NR3F", +# "policyName": "NR3F", +# "policyType": "PLATFORM", +# "ref_count": 0, +# "role": null, +# "rpmimages": null, +# "unInstall": false +# } +# ], +# "message": "", +# "status": "SUCCESS" +# }, +# "MESSAGE": "OK", +# "METHOD": "GET", +# "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/policymgnt/policies", +# "RETURN_CODE": 200, +# "sequence_number": 0 +# }, +# { +# "DATA": "Selected policy(s) deleted successfully.", +# "MESSAGE": "OK", +# "METHOD": "DELETE", +# "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/policymgnt/policy", +# "RETURN_CODE": 200, +# "sequence_number": 1 +# } +# ], +# "result": [ +# { +# "found": true, +# "sequence_number": 0, +# "success": true +# }, +# { +# "changed": true, +# "sequence_number": 1, +# "success": true +# } +# ] +# } +# } +- name: QUERY - CLEANUP - Delete image policies and verify + cisco.dcnm.dcnm_image_policy: + state: deleted + config: + - name: "{{ image_policy_1 }}" + - name: "{{ image_policy_2 }}" + register: result + +- debug: + var: result + +- assert: + that: + - result.changed == true + - result.failed == false + - (result.diff | length) == 1 + - image_policy_1 in result.diff[0].policyNames + - image_policy_2 in result.diff[0].policyNames + - result.response[0].MESSAGE == "OK" + - result.response[0].METHOD == "GET" + - result.response[0].RETURN_CODE == 200 + - result.response[1].MESSAGE == "OK" + - result.response[1].METHOD == "DELETE" + - result.response[1].RETURN_CODE == 200 + - (result.response | length) == 2 diff --git a/tests/integration/targets/dcnm_image_policy/tests/dcnm_image_policy_replaced.yaml b/tests/integration/targets/dcnm_image_policy/tests/dcnm_image_policy_replaced.yaml new file mode 100644 index 000000000..39f629a25 --- /dev/null +++ b/tests/integration/targets/dcnm_image_policy/tests/dcnm_image_policy_replaced.yaml @@ -0,0 +1,512 @@ +################################################################################ +# RUNTIME +################################################################################ +# +# Recent run times (MM:SS.ms): +# 00.25.904 +# 00.25.215 + +################################################################################ +# STEPS +################################################################################ + +# SETUP +# 1. The following images must already be uploaded to the controller +# See vars: section in cisco/dcnm/playbooks/dcnm_tests.yaml +# - nxos_image_1 +# - nxos_image_2 +# - epld_image_1 +# - epld_image_2 +# 2. No need for fabric or switches +# 3. Delete image policies under test, if they exist +# - image_policy_1 +# - image_policy_2 +# 4. Create image policies using merged state and verify result +# - image_policy_1 +# - image_policy_2 +# +# TEST +# +# 5. Use replaced state to update image_policy_1 and verify that: +# - image_policy_1 is updated +# - image_policy_2 is untouched +# +# CLEANUP +# +# 6. Delete the image policies created in the test + +################################################################################ +# REQUIREMENTS +################################################################################ + +# 1. The following images must already be uploaded to the controller +# See vars: section below +# - nxos_image_1 +# - nxos_image_2 +# - epld_image_1 +# - epld_image_2 +# 2. No need for fabric or switches +# +# Example vars for dcnm_image_policy integration tests +# Add to cisco/dcnm/playbooks/dcnm_tests.yaml) +# +# vars: +# # This testcase field can run any test in the tests directory for the role +# testcase: deleted +# fabric_name: f1 +# username: admin +# password: "foobar" +# switch_username: admin +# switch_password: "foobar" +# spine1: 172.22.150.114 +# spine2: 172.22.150.115 +# leaf1: 172.22.150.106 +# leaf2: 172.22.150.107 +# leaf3: 172.22.150.108 +# leaf4: 172.22.150.109 +# # for dcnm_image_policy role +# image_policy_1: "KR5M" +# image_policy_2: "NR3F" +# epld_image_1: n9000-epld.10.2.5.M.img +# epld_image_2: n9000-epld.10.3.1.F.img +# nxos_image_1: n9000-dk9.10.2.5.M.bin +# nxos_image_2: n9000-dk9.10.3.1.F.bin +# nxos_release_1: 10.2.5_nxos64-cs_64bit +# nxos_release_2: 10.3.1_nxos64-cs_64bit + +################################################################################ +# SETUP +################################################################################ + +- name: REPLACED - SETUP - Delete image policies + cisco.dcnm.dcnm_image_policy: + state: deleted + config: + - name: "{{ image_policy_1 }}" + - name: "{{ image_policy_2 }}" + register: result + +- debug: + var: result + +################################################################################ +# REPLACED - TEST - Create two image policies using merged state +################################################################################ +# Expected result +# +# ok: [dcnm] => { +# "result": { +# "changed": true, +# "diff": [ +# { +# "agnostic": false, +# "epldImgName": "n9000-epld.10.2.5.M.img", +# "nxosVersion": "10.2.5_nxos64-cs_64bit", +# "platform": "N9K", +# "policyDescr": "KR5M", +# "policyName": "KR5M", +# "policyType": "PLATFORM", +# "sequence_number": 1 +# }, +# { +# "agnostic": false, +# "epldImgName": "n9000-epld.10.3.1.F.img", +# "nxosVersion": "10.3.1_nxos64-cs_64bit", +# "platform": "N9K", +# "policyDescr": "NR3F", +# "policyName": "NR3F", +# "policyType": "PLATFORM", +# "sequence_number": 2 +# } +# ], +# "failed": false, +# "metadata": [ +# { +# "action": "create", +# "check_mode": false, +# "sequence_number": 1, +# "state": "merged" +# }, +# { +# "action": "create", +# "check_mode": false, +# "sequence_number": 2, +# "state": "merged" +# } +# ], +# "response": [ +# { +# "DATA": { +# "lastOperDataObject": [], +# "message": "", +# "status": "SUCCESS" +# }, +# "MESSAGE": "OK", +# "METHOD": "GET", +# "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/policymgnt/policies", +# "RETURN_CODE": 200, +# "sequence_number": 0 +# }, +# { +# "DATA": "Policy created successfully.", +# "MESSAGE": "OK", +# "METHOD": "POST", +# "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/policymgnt/platform-policy", +# "RETURN_CODE": 200, +# "sequence_number": 1 +# }, +# { +# "DATA": "Policy created successfully.", +# "MESSAGE": "OK", +# "METHOD": "POST", +# "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/policymgnt/platform-policy", +# "RETURN_CODE": 200, +# "sequence_number": 2 +# } +# ], +# "result": [ +# { +# "found": true, +# "sequence_number": 0, +# "success": true +# }, +# { +# "changed": true, +# "sequence_number": 1, +# "success": true +# }, +# { +# "changed": true, +# "sequence_number": 2, +# "success": true +# } +# ] +# } +# } +################################################################################ + +- name: REPLACED - TEST - Create two image policies using merged state + cisco.dcnm.dcnm_image_policy: + state: merged + config: + - name: "{{ image_policy_1 }}" + agnostic: false + description: "{{ image_policy_1 }}" + epld_image: "{{ epld_image_1 }}" + platform: N9K + release: "{{ nxos_release_1 }}" + type: PLATFORM + - name: "{{ image_policy_2 }}" + description: "{{ image_policy_2 }}" + epld_image: "{{ epld_image_2 }}" + platform: N9K + release: "{{ nxos_release_2 }}" + register: result + +- debug: + var: result + +- assert: + that: + - result.changed == true + - result.failed == false + - (result.diff | length) == 2 + - result.diff[0].policyName == image_policy_1 + - result.diff[1].policyName == image_policy_2 + - result.diff[0].policyDescr == image_policy_1 + - result.diff[1].policyDescr == image_policy_2 + - result.diff[0].agnostic == false + - result.diff[1].agnostic == false + - result.diff[0].epldImgName == epld_image_1 + - result.diff[1].epldImgName == epld_image_2 + - result.diff[0].nxosVersion == nxos_release_1 + - result.diff[1].nxosVersion == nxos_release_2 + - result.diff[0].platform == "N9K" + - result.diff[1].platform == "N9K" + - result.diff[0].policyType == "PLATFORM" + - result.diff[1].policyType == "PLATFORM" + - (result.metadata | length) == 2 + - result.metadata[0].action == "create" + - result.metadata[0].check_mode == False + - result.metadata[0].sequence_number == 1 + - result.metadata[0].state == "merged" + - result.metadata[1].action == "create" + - result.metadata[1].check_mode == False + - result.metadata[1].sequence_number == 2 + - result.metadata[1].state == "merged" + +################################################################################ +# REPLACED - TEST - replace image_policy_1, will leave image_policy_2 untouched +################################################################################ +# Expected result +# ok: [dcnm] => { +# "result": { +# "changed": true, +# "diff": [ +# { +# "agnostic": false, +# "epldImgName": "n9000-epld.10.2.5.M.img", +# "nxosVersion": "10.2.5_nxos64-cs_64bit", +# "packageName": "", +# "platform": "N9K", +# "policyDescr": "KR5M replaced", +# "policyName": "KR5M", +# "policyType": "PLATFORM", +# "rpmimages": "", +# "sequence_number": 1 +# } +# ], +# "failed": false, +# "metadata": [ +# { +# "action": "replace", +# "check_mode": false, +# "sequence_number": 1, +# "state": "replaced" +# } +# ], +# "response": [ +# { +# "DATA": { +# "lastOperDataObject": [ +# { +# "agnostic": false, +# "epldImgName": "n9000-epld.10.2.5.M.img", +# "fabricPolicyName": null, +# "imageName": "nxos64-cs.10.2.5.M.bin", +# "imagePresent": "Present", +# "nxosVersion": "10.2.5_nxos64-cs_64bit", +# "packageName": "", +# "platform": "N9K/N3K", +# "platformPolicies": "", +# "policyDescr": "KR5M", +# "policyName": "KR5M", +# "policyType": "PLATFORM", +# "ref_count": 0, +# "role": null, +# "rpmimages": null, +# "unInstall": false +# }, +# { +# "agnostic": false, +# "epldImgName": "n9000-epld.10.3.1.F.img", +# "fabricPolicyName": null, +# "imageName": "nxos64-cs.10.3.1.F.bin", +# "imagePresent": "Present", +# "nxosVersion": "10.3.1_nxos64-cs_64bit", +# "packageName": "", +# "platform": "N9K/N3K", +# "platformPolicies": "", +# "policyDescr": "NR3F", +# "policyName": "NR3F", +# "policyType": "PLATFORM", +# "ref_count": 0, +# "role": null, +# "rpmimages": null, +# "unInstall": false +# } +# ], +# "message": "", +# "status": "SUCCESS" +# }, +# "MESSAGE": "OK", +# "METHOD": "GET", +# "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/policymgnt/policies", +# "RETURN_CODE": 200, +# "sequence_number": 0 +# }, +# { +# "DATA": "Policy updated successfully.", +# "MESSAGE": "OK", +# "METHOD": "POST", +# "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/policymgnt/edit-policy", +# "RETURN_CODE": 200, +# "sequence_number": 1 +# } +# ], +# "result": [ +# { +# "found": true, +# "sequence_number": 0, +# "success": true +# }, +# { +# "changed": true, +# "sequence_number": 1, +# "success": true +# } +# ] +# } +# } +################################################################################ + +- name: REPLACED - TEST - replace image_policy_1, will leave image_policy_2 untouched + cisco.dcnm.dcnm_image_policy: + state: replaced + config: + - name: "{{ image_policy_1 }}" + agnostic: false + description: "{{ image_policy_1 }} replaced" + epld_image: "{{ epld_image_1 }}" + platform: N9K + release: "{{ nxos_release_1 }}" + type: PLATFORM + register: result + +- debug: + var: result + +- assert: + that: + - result.changed == true + - result.failed == false + - (result.diff | length) == 1 + - result.diff[0].agnostic == false + - result.diff[0].policyName == image_policy_1 + - result.diff[0].policyDescr == image_policy_1 + " replaced" + - result.diff[0].epldImgName == epld_image_1 + - result.diff[0].nxosVersion == nxos_release_1 + - result.diff[0].platform == "N9K" + - result.diff[0].policyType == "PLATFORM" + - (result.response | length) == 2 + - result.response[0].MESSAGE == "OK" + - result.response[0].METHOD == "GET" + - result.response[0].RETURN_CODE == 200 + - result.response[1].MESSAGE == "OK" + - result.response[1].METHOD == "POST" + - result.response[1].RETURN_CODE == 200 + - (result.metadata | length) == 1 + - result.metadata[0].action == "replace" + - result.metadata[0].check_mode == False + - result.metadata[0].sequence_number == 1 + - result.metadata[0].state == "replaced" + +################################################################################ +# REPLACED - CLEANUP - Delete image policies and verify +################################################################################ +# Expected result +# ok: [dcnm] => { +# "result": { +# "changed": true, +# "diff": [ +# { +# "policyNames": [ +# "KR5M", +# "NR3F" +# ], +# "sequence_number": 1 +# } +# ], +# "failed": false, +# "metadata": [ +# { +# "action": "delete", +# "check_mode": false, +# "sequence_number": 1, +# "state": "deleted" +# } +# ], +# "response": [ +# { +# "DATA": { +# "lastOperDataObject": [ +# { +# "agnostic": false, +# "epldImgName": "n9000-epld.10.2.5.M.img", +# "fabricPolicyName": null, +# "imageName": "nxos64-cs.10.2.5.M.bin", +# "imagePresent": "Present", +# "nxosVersion": "10.2.5_nxos64-cs_64bit", +# "packageName": "", +# "platform": "N9K/N3K", +# "platformPolicies": "", +# "policyDescr": "KR5M replaced", +# "policyName": "KR5M", +# "policyType": "PLATFORM", +# "ref_count": 0, +# "role": null, +# "rpmimages": "", +# "unInstall": false +# }, +# { +# "agnostic": false, +# "epldImgName": "n9000-epld.10.3.1.F.img", +# "fabricPolicyName": null, +# "imageName": "nxos64-cs.10.3.1.F.bin", +# "imagePresent": "Present", +# "nxosVersion": "10.3.1_nxos64-cs_64bit", +# "packageName": "", +# "platform": "N9K/N3K", +# "platformPolicies": "", +# "policyDescr": "NR3F", +# "policyName": "NR3F", +# "policyType": "PLATFORM", +# "ref_count": 0, +# "role": null, +# "rpmimages": null, +# "unInstall": false +# } +# ], +# "message": "", +# "status": "SUCCESS" +# }, +# "MESSAGE": "OK", +# "METHOD": "GET", +# "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/policymgnt/policies", +# "RETURN_CODE": 200, +# "sequence_number": 0 +# }, +# { +# "DATA": "Selected policy(s) deleted successfully.", +# "MESSAGE": "OK", +# "METHOD": "DELETE", +# "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/policymgnt/policy", +# "RETURN_CODE": 200, +# "sequence_number": 1 +# } +# ], +# "result": [ +# { +# "found": true, +# "sequence_number": 0, +# "success": true +# }, +# { +# "changed": true, +# "sequence_number": 1, +# "success": true +# } +# ] +# } +# } +################################################################################ +- name: REPLACED - CLEANUP - Delete image policies and verify + cisco.dcnm.dcnm_image_policy: + state: deleted + config: + - name: "{{ image_policy_1 }}" + - name: "{{ image_policy_2 }}" + register: result + +- debug: + var: result + +- assert: + that: + - result.changed == true + - result.failed == false + - (result.diff | length) == 1 + - image_policy_1 in result.diff[0].policyNames + - image_policy_2 in result.diff[0].policyNames + - (result.response | length) == 2 + - result.response[0].MESSAGE == "OK" + - result.response[0].METHOD == "GET" + - result.response[0].RETURN_CODE == 200 + - result.response[1].MESSAGE == "OK" + - result.response[1].METHOD == "DELETE" + - result.response[1].RETURN_CODE == 200 + - (result.metadata | length) == 1 + - result.metadata[0].action == "delete" + - result.metadata[0].check_mode == False + - result.metadata[0].sequence_number == 1 + - result.metadata[0].state == "deleted" diff --git a/tests/sanity/ignore-2.10.txt b/tests/sanity/ignore-2.10.txt index 00e618d18..5437aea07 100644 --- a/tests/sanity/ignore-2.10.txt +++ b/tests/sanity/ignore-2.10.txt @@ -1,4 +1,5 @@ plugins/modules/dcnm_vrf.py validate-modules:missing-gplv3-license # GPLv3 license header not found in the first 20 lines of the module +plugins/modules/dcnm_image_policy.py validate-modules:missing-gplv3-license # GPLv3 license header not found in the first 20 lines of the module plugins/modules/dcnm_network.py validate-modules:missing-gplv3-license # GPLv3 license header not found in the first 20 lines of the module plugins/modules/dcnm_image_upgrade.py validate-modules:missing-gplv3-license # GPLv3 license header not found in the first 20 lines of the module plugins/modules/dcnm_interface.py validate-modules:missing-gplv3-license # GPLv3 license header not found in the first 20 lines of the module diff --git a/tests/sanity/ignore-2.11.txt b/tests/sanity/ignore-2.11.txt index 5304e1273..9b352f22a 100644 --- a/tests/sanity/ignore-2.11.txt +++ b/tests/sanity/ignore-2.11.txt @@ -1,4 +1,5 @@ plugins/modules/dcnm_vrf.py validate-modules:missing-gplv3-license # GPLv3 license header not found in the first 20 lines of the module +plugins/modules/dcnm_image_policy.py validate-modules:missing-gplv3-license # GPLv3 license header not found in the first 20 lines of the module plugins/modules/dcnm_network.py validate-modules:missing-gplv3-license # GPLv3 license header not found in the first 20 lines of the module plugins/modules/dcnm_image_upgrade.py validate-modules:missing-gplv3-license # GPLv3 license header not found in the first 20 lines of the module plugins/modules/dcnm_interface.py validate-modules:missing-gplv3-license # GPLv3 license header not found in the first 20 lines of the module diff --git a/tests/sanity/ignore-2.12.txt b/tests/sanity/ignore-2.12.txt index e0566313c..a3fadaac9 100644 --- a/tests/sanity/ignore-2.12.txt +++ b/tests/sanity/ignore-2.12.txt @@ -1,4 +1,5 @@ plugins/modules/dcnm_vrf.py validate-modules:missing-gplv3-license # GPLv3 license header not found in the first 20 lines of the module +plugins/modules/dcnm_image_policy.py validate-modules:missing-gplv3-license # GPLv3 license header not found in the first 20 lines of the module plugins/modules/dcnm_network.py validate-modules:missing-gplv3-license # GPLv3 license header not found in the first 20 lines of the module plugins/modules/dcnm_image_upgrade.py validate-modules:missing-gplv3-license # GPLv3 license header not found in the first 20 lines of the module plugins/modules/dcnm_interface.py validate-modules:missing-gplv3-license # GPLv3 license header not found in the first 20 lines of the module diff --git a/tests/sanity/ignore-2.13.txt b/tests/sanity/ignore-2.13.txt index 6e059a8b0..a2474edfc 100644 --- a/tests/sanity/ignore-2.13.txt +++ b/tests/sanity/ignore-2.13.txt @@ -1,4 +1,5 @@ plugins/modules/dcnm_vrf.py validate-modules:missing-gplv3-license # GPLv3 license header not found in the first 20 lines of the module +plugins/modules/dcnm_image_policy.py validate-modules:missing-gplv3-license # GPLv3 license header not found in the first 20 lines of the module plugins/modules/dcnm_network.py validate-modules:missing-gplv3-license # GPLv3 license header not found in the first 20 lines of the module plugins/modules/dcnm_image_upgrade.py validate-modules:missing-gplv3-license # GPLv3 license header not found in the first 20 lines of the module plugins/modules/dcnm_interface.py validate-modules:missing-gplv3-license # GPLv3 license header not found in the first 20 lines of the module diff --git a/tests/sanity/ignore-2.14.txt b/tests/sanity/ignore-2.14.txt index bd64c646c..80c99ad45 100644 --- a/tests/sanity/ignore-2.14.txt +++ b/tests/sanity/ignore-2.14.txt @@ -1,4 +1,5 @@ plugins/modules/dcnm_vrf.py validate-modules:missing-gplv3-license # GPLv3 license header not found in the first 20 lines of the module +plugins/modules/dcnm_image_policy.py validate-modules:missing-gplv3-license # GPLv3 license header not found in the first 20 lines of the module plugins/modules/dcnm_network.py validate-modules:missing-gplv3-license # GPLv3 license header not found in the first 20 lines of the module plugins/modules/dcnm_image_upgrade.py validate-modules:missing-gplv3-license # GPLv3 license header not found in the first 20 lines of the module plugins/modules/dcnm_interface.py validate-modules:missing-gplv3-license # GPLv3 license header not found in the first 20 lines of the module diff --git a/tests/sanity/ignore-2.9.txt b/tests/sanity/ignore-2.9.txt index 00e618d18..5437aea07 100644 --- a/tests/sanity/ignore-2.9.txt +++ b/tests/sanity/ignore-2.9.txt @@ -1,4 +1,5 @@ plugins/modules/dcnm_vrf.py validate-modules:missing-gplv3-license # GPLv3 license header not found in the first 20 lines of the module +plugins/modules/dcnm_image_policy.py validate-modules:missing-gplv3-license # GPLv3 license header not found in the first 20 lines of the module plugins/modules/dcnm_network.py validate-modules:missing-gplv3-license # GPLv3 license header not found in the first 20 lines of the module plugins/modules/dcnm_image_upgrade.py validate-modules:missing-gplv3-license # GPLv3 license header not found in the first 20 lines of the module plugins/modules/dcnm_interface.py validate-modules:missing-gplv3-license # GPLv3 license header not found in the first 20 lines of the module diff --git a/tests/unit/modules/dcnm/dcnm_image_policy/fixture.py b/tests/unit/modules/dcnm/dcnm_image_policy/fixture.py new file mode 100644 index 000000000..bb3730787 --- /dev/null +++ b/tests/unit/modules/dcnm/dcnm_image_policy/fixture.py @@ -0,0 +1,50 @@ +# Copyright (c) 2024 Cisco and/or its affiliates. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +import json +import os +import sys + +fixture_path = os.path.join(os.path.dirname(__file__), "fixtures") + + +def load_fixture(filename): + """ + load test inputs from json files + """ + path = os.path.join(fixture_path, f"{filename}.json") + + try: + with open(path, encoding="utf-8") as file_handle: + data = file_handle.read() + except IOError as exception: + msg = f"Exception opening test input file {filename}.json : " + msg += f"Exception detail: {exception}" + print(msg) + sys.exit(1) + + try: + fixture = json.loads(data) + except json.JSONDecodeError as exception: + msg = "Exception reading JSON contents in " + msg += f"test input file {filename}.json : " + msg += f"Exception detail: {exception}" + print(msg) + sys.exit(1) + + return fixture diff --git a/tests/unit/modules/dcnm/dcnm_image_policy/fixtures/all_policies_ImagePolicies.json b/tests/unit/modules/dcnm/dcnm_image_policy/fixtures/all_policies_ImagePolicies.json new file mode 100644 index 000000000..7bf1bdddc --- /dev/null +++ b/tests/unit/modules/dcnm/dcnm_image_policy/fixtures/all_policies_ImagePolicies.json @@ -0,0 +1,865 @@ +{ + "TEST_NOTES": [ + "Mock return values for the ImagePolicies().all_policies property", + "i.e. the controller response consisting of all image policies that", + "exist on the controller, keyed on policy_name." + ], + "test_image_policy_create_00030a": { + "KR5M": { + "agnostic": false, + "epldImgName": "n9000-epld.10.2.5.M.img", + "imageName": "nxos64-cs.10.2.5.M.bin", + "nxosVersion": "10.2.5_nxos64-cs_64bit", + "packageName": "mtx-openconfig-all-2.0.0.0-10.4.1.src.rpm", + "platform": "N9K/N3K", + "platformPolicies": "", + "policyDescr": "KR5M", + "policyName": "KR5M", + "policyType": "PLATFORM", + "ref_count": 0, + "rpmimages": "mtx-grpctunnel-2.1.0.0-10.4.1.lib32_64_n9000" + }, + "NR3F": { + "agnostic": false, + "epldImgName": "n9000-epld.10.3.1.F.img", + "imageName": "nxos64-cs.10.3.1.F.bin", + "nxosVersion": "10.3.1_nxos64-cs_64bit", + "packageName": "", + "platform": "N9K/N3K", + "platformPolicies": "", + "policyDescr": "NR3F", + "policyName": "NR3F", + "policyType": "PLATFORM", + "ref_count": 0, + "rpmimages": null + } + }, + "test_image_policy_create_00031a": { + "KR5M": { + "agnostic": false, + "epldImgName": "n9000-epld.10.2.5.M.img", + "imageName": "nxos64-cs.10.2.5.M.bin", + "nxosVersion": "10.2.5_nxos64-cs_64bit", + "packageName": "mtx-openconfig-all-2.0.0.0-10.4.1.src.rpm", + "platform": "N9K/N3K", + "platformPolicies": "", + "policyDescr": "KR5M", + "policyName": "KR5M", + "policyType": "PLATFORM", + "ref_count": 0, + "rpmimages": "mtx-grpctunnel-2.1.0.0-10.4.1.lib32_64_n9000" + }, + "NR3F": { + "agnostic": false, + "epldImgName": "n9000-epld.10.3.1.F.img", + "imageName": "nxos64-cs.10.3.1.F.bin", + "nxosVersion": "10.3.1_nxos64-cs_64bit", + "packageName": "", + "platform": "N9K/N3K", + "platformPolicies": "", + "policyDescr": "NR3F", + "policyName": "NR3F", + "policyType": "PLATFORM", + "ref_count": 0, + "rpmimages": null + } + }, + "test_image_policy_create_00034a": { + "KR5M": { + "agnostic": false, + "epldImgName": "n9000-epld.10.2.5.M.img", + "imageName": "nxos64-cs.10.2.5.M.bin", + "nxosVersion": "10.2.5_nxos64-cs_64bit", + "packageName": "mtx-openconfig-all-2.0.0.0-10.4.1.src.rpm", + "platform": "N9K/N3K", + "platformPolicies": "", + "policyDescr": "KR5M", + "policyName": "KR5M", + "policyType": "PLATFORM", + "ref_count": 0, + "rpmimages": "mtx-grpctunnel-2.1.0.0-10.4.1.lib32_64_n9000" + }, + "NR3F": { + "agnostic": false, + "epldImgName": "n9000-epld.10.3.1.F.img", + "imageName": "nxos64-cs.10.3.1.F.bin", + "nxosVersion": "10.3.1_nxos64-cs_64bit", + "packageName": "", + "platform": "N9K/N3K", + "platformPolicies": "", + "policyDescr": "NR3F", + "policyName": "NR3F", + "policyType": "PLATFORM", + "ref_count": 0, + "rpmimages": null + } + }, + "test_image_policy_create_00035a": {}, + "test_image_policy_create_bulk_00030a": { + "KR5M": { + "agnostic": false, + "epldImgName": "n9000-epld.10.2.5.M.img", + "imageName": "nxos64-cs.10.2.5.M.bin", + "nxosVersion": "10.2.5_nxos64-cs_64bit", + "packageName": "mtx-openconfig-all-2.0.0.0-10.4.1.src.rpm", + "platform": "N9K/N3K", + "platformPolicies": "", + "policyDescr": "KR5M", + "policyName": "KR5M", + "policyType": "PLATFORM", + "ref_count": 0, + "rpmimages": "mtx-grpctunnel-2.1.0.0-10.4.1.lib32_64_n9000" + }, + "NR3F": { + "agnostic": false, + "epldImgName": "n9000-epld.10.3.1.F.img", + "imageName": "nxos64-cs.10.3.1.F.bin", + "nxosVersion": "10.3.1_nxos64-cs_64bit", + "packageName": "", + "platform": "N9K/N3K", + "platformPolicies": "", + "policyDescr": "NR3F", + "policyName": "NR3F", + "policyType": "PLATFORM", + "ref_count": 0, + "rpmimages": null + } + }, + "test_image_policy_create_bulk_00031a": { + "KR5M": { + "agnostic": false, + "epldImgName": "n9000-epld.10.2.5.M.img", + "imageName": "nxos64-cs.10.2.5.M.bin", + "nxosVersion": "10.2.5_nxos64-cs_64bit", + "packageName": "mtx-openconfig-all-2.0.0.0-10.4.1.src.rpm", + "platform": "N9K/N3K", + "platformPolicies": "", + "policyDescr": "KR5M", + "policyName": "KR5M", + "policyType": "PLATFORM", + "ref_count": 0, + "rpmimages": "mtx-grpctunnel-2.1.0.0-10.4.1.lib32_64_n9000" + }, + "NR3F": { + "agnostic": false, + "epldImgName": "n9000-epld.10.3.1.F.img", + "imageName": "nxos64-cs.10.3.1.F.bin", + "nxosVersion": "10.3.1_nxos64-cs_64bit", + "packageName": "", + "platform": "N9K/N3K", + "platformPolicies": "", + "policyDescr": "NR3F", + "policyName": "NR3F", + "policyType": "PLATFORM", + "ref_count": 0, + "rpmimages": null + } + }, + "test_image_policy_create_bulk_00032a": { + "KR5M": { + "agnostic": false, + "epldImgName": "n9000-epld.10.2.5.M.img", + "imageName": "nxos64-cs.10.2.5.M.bin", + "nxosVersion": "10.2.5_nxos64-cs_64bit", + "packageName": "mtx-openconfig-all-2.0.0.0-10.4.1.src.rpm", + "platform": "N9K/N3K", + "platformPolicies": "", + "policyDescr": "KR5M", + "policyName": "KR5M", + "policyType": "PLATFORM", + "ref_count": 0, + "rpmimages": "mtx-grpctunnel-2.1.0.0-10.4.1.lib32_64_n9000" + }, + "NR3F": { + "agnostic": false, + "epldImgName": "n9000-epld.10.3.1.F.img", + "imageName": "nxos64-cs.10.3.1.F.bin", + "nxosVersion": "10.3.1_nxos64-cs_64bit", + "packageName": "", + "platform": "N9K/N3K", + "platformPolicies": "", + "policyDescr": "NR3F", + "policyName": "NR3F", + "policyType": "PLATFORM", + "ref_count": 0, + "rpmimages": null + } + }, + "test_image_policy_create_bulk_00034a": { + "KR5M": { + "agnostic": false, + "epldImgName": "n9000-epld.10.2.5.M.img", + "imageName": "nxos64-cs.10.2.5.M.bin", + "nxosVersion": "10.2.5_nxos64-cs_64bit", + "packageName": "mtx-openconfig-all-2.0.0.0-10.4.1.src.rpm", + "platform": "N9K/N3K", + "platformPolicies": "", + "policyDescr": "KR5M", + "policyName": "KR5M", + "policyType": "PLATFORM", + "ref_count": 0, + "rpmimages": "mtx-grpctunnel-2.1.0.0-10.4.1.lib32_64_n9000" + } + }, + "test_image_policy_create_bulk_00035a": {}, + "test_image_policy_create_bulk_00036a": {}, + "test_image_policy_create_bulk_00037a": {}, + "test_image_policy_delete_00030a": { + "KR5M": { + "agnostic": false, + "epldImgName": "n9000-epld.10.2.5.M.img", + "imageName": "nxos64-cs.10.2.5.M.bin", + "nxosVersion": "10.2.5_nxos64-cs_64bit", + "packageName": "mtx-openconfig-all-2.0.0.0-10.4.1.src.rpm", + "platform": "N9K/N3K", + "platformPolicies": "", + "policyDescr": "KR5M", + "policyName": "KR5M", + "policyType": "PLATFORM", + "ref_count": 0, + "rpmimages": "mtx-grpctunnel-2.1.0.0-10.4.1.lib32_64_n9000" + }, + "NR3F": { + "agnostic": false, + "epldImgName": "n9000-epld.10.3.1.F.img", + "imageName": "nxos64-cs.10.3.1.F.bin", + "nxosVersion": "10.3.1_nxos64-cs_64bit", + "packageName": "", + "platform": "N9K/N3K", + "platformPolicies": "", + "policyDescr": "NR3F", + "policyName": "NR3F", + "policyType": "PLATFORM", + "ref_count": 0, + "rpmimages": null + } + }, + "test_image_policy_delete_00031a": { + "KR5M": { + "agnostic": false, + "epldImgName": "n9000-epld.10.2.5.M.img", + "imageName": "nxos64-cs.10.2.5.M.bin", + "nxosVersion": "10.2.5_nxos64-cs_64bit", + "packageName": "mtx-openconfig-all-2.0.0.0-10.4.1.src.rpm", + "platform": "N9K/N3K", + "platformPolicies": "", + "policyDescr": "KR5M", + "policyName": "KR5M", + "policyType": "PLATFORM", + "ref_count": 0, + "rpmimages": "mtx-grpctunnel-2.1.0.0-10.4.1.lib32_64_n9000" + }, + "NR3F": { + "agnostic": false, + "epldImgName": "n9000-epld.10.3.1.F.img", + "imageName": "nxos64-cs.10.3.1.F.bin", + "nxosVersion": "10.3.1_nxos64-cs_64bit", + "packageName": "", + "platform": "N9K/N3K", + "platformPolicies": "", + "policyDescr": "NR3F", + "policyName": "NR3F", + "policyType": "PLATFORM", + "ref_count": 0, + "rpmimages": null + } + }, + "test_image_policy_delete_00032a": { + "KR5M": { + "agnostic": false, + "epldImgName": "n9000-epld.10.2.5.M.img", + "imageName": "nxos64-cs.10.2.5.M.bin", + "nxosVersion": "10.2.5_nxos64-cs_64bit", + "packageName": "mtx-openconfig-all-2.0.0.0-10.4.1.src.rpm", + "platform": "N9K/N3K", + "platformPolicies": "", + "policyDescr": "KR5M", + "policyName": "KR5M", + "policyType": "PLATFORM", + "ref_count": 0, + "rpmimages": "mtx-grpctunnel-2.1.0.0-10.4.1.lib32_64_n9000" + }, + "NR3F": { + "agnostic": false, + "epldImgName": "n9000-epld.10.3.1.F.img", + "imageName": "nxos64-cs.10.3.1.F.bin", + "nxosVersion": "10.3.1_nxos64-cs_64bit", + "packageName": "", + "platform": "N9K/N3K", + "platformPolicies": "", + "policyDescr": "NR3F", + "policyName": "NR3F", + "policyType": "PLATFORM", + "ref_count": 0, + "rpmimages": null + } + }, + "test_image_policy_delete_00034a": {}, + "test_image_policy_delete_00036a": {}, + "test_image_policy_delete_00037a": { + "KR5M": { + "agnostic": false, + "epldImgName": "n9000-epld.10.2.5.M.img", + "imageName": "nxos64-cs.10.2.5.M.bin", + "nxosVersion": "10.2.5_nxos64-cs_64bit", + "packageName": "", + "platform": "N9K/N3K", + "platformPolicies": "", + "policyDescr": "KR5M", + "policyName": "KR5M", + "policyType": "PLATFORM", + "ref_count": 0, + "rpmimages": "" + } + }, + "test_image_policy_delete_00038a": { + "KR5M": { + "agnostic": false, + "epldImgName": "n9000-epld.10.2.5.M.img", + "imageName": "nxos64-cs.10.2.5.M.bin", + "nxosVersion": "10.2.5_nxos64-cs_64bit", + "packageName": "", + "platform": "N9K/N3K", + "platformPolicies": "", + "policyDescr": "KR5M", + "policyName": "KR5M", + "policyType": "PLATFORM", + "ref_count": 0, + "rpmimages": "" + } + }, + "test_image_policy_query_00030a": { + "KR5M": { + "agnostic": false, + "epldImgName": "n9000-epld.10.2.5.M.img", + "imageName": "nxos64-cs.10.2.5.M.bin", + "nxosVersion": "10.2.5_nxos64-cs_64bit", + "packageName": "mtx-openconfig-all-2.0.0.0-10.4.1.src.rpm", + "platform": "N9K/N3K", + "platformPolicies": "", + "policyDescr": "KR5M", + "policyName": "KR5M", + "policyType": "PLATFORM", + "ref_count": 0, + "rpmimages": "mtx-grpctunnel-2.1.0.0-10.4.1.lib32_64_n9000" + }, + "NR3F": { + "agnostic": false, + "epldImgName": "n9000-epld.10.3.1.F.img", + "imageName": "nxos64-cs.10.3.1.F.bin", + "nxosVersion": "10.3.1_nxos64-cs_64bit", + "packageName": "", + "platform": "N9K/N3K", + "platformPolicies": "", + "policyDescr": "NR3F", + "policyName": "NR3F", + "policyType": "PLATFORM", + "ref_count": 0, + "rpmimages": null + } + }, + "test_image_policy_query_00031a": { + "KR5M": { + "agnostic": false, + "epldImgName": "n9000-epld.10.2.5.M.img", + "imageName": "nxos64-cs.10.2.5.M.bin", + "nxosVersion": "10.2.5_nxos64-cs_64bit", + "packageName": "mtx-openconfig-all-2.0.0.0-10.4.1.src.rpm", + "platform": "N9K/N3K", + "platformPolicies": "", + "policyDescr": "KR5M", + "policyName": "KR5M", + "policyType": "PLATFORM", + "ref_count": 0, + "rpmimages": "mtx-grpctunnel-2.1.0.0-10.4.1.lib32_64_n9000" + }, + "NR3F": { + "agnostic": false, + "epldImgName": "n9000-epld.10.3.1.F.img", + "imageName": "nxos64-cs.10.3.1.F.bin", + "nxosVersion": "10.3.1_nxos64-cs_64bit", + "packageName": "", + "platform": "N9K/N3K", + "platformPolicies": "", + "policyDescr": "NR3F", + "policyName": "NR3F", + "policyType": "PLATFORM", + "ref_count": 0, + "rpmimages": null + } + }, + "test_image_policy_query_00032a": { + "KR5M": { + "agnostic": false, + "epldImgName": "n9000-epld.10.2.5.M.img", + "imageName": "nxos64-cs.10.2.5.M.bin", + "nxosVersion": "10.2.5_nxos64-cs_64bit", + "packageName": "mtx-openconfig-all-2.0.0.0-10.4.1.src.rpm", + "platform": "N9K/N3K", + "platformPolicies": "", + "policyDescr": "KR5M", + "policyName": "KR5M", + "policyType": "PLATFORM", + "ref_count": 0, + "rpmimages": "mtx-grpctunnel-2.1.0.0-10.4.1.lib32_64_n9000" + }, + "NR3F": { + "agnostic": false, + "epldImgName": "n9000-epld.10.3.1.F.img", + "imageName": "nxos64-cs.10.3.1.F.bin", + "nxosVersion": "10.3.1_nxos64-cs_64bit", + "packageName": "", + "platform": "N9K/N3K", + "platformPolicies": "", + "policyDescr": "NR3F", + "policyName": "NR3F", + "policyType": "PLATFORM", + "ref_count": 0, + "rpmimages": null + } + }, + "test_image_policy_query_00033a": {}, + "test_image_policy_replace_bulk_00030a": { + "KR5M": { + "agnostic": false, + "epldImgName": "n9000-epld.10.2.5.M.img", + "imageName": "nxos64-cs.10.2.5.M.bin", + "nxosVersion": "10.2.5_nxos64-cs_64bit", + "packageName": "mtx-openconfig-all-2.0.0.0-10.4.1.src.rpm", + "platform": "N9K/N3K", + "platformPolicies": "", + "policyDescr": "KR5M", + "policyName": "KR5M", + "policyType": "PLATFORM", + "ref_count": 0, + "rpmimages": "mtx-grpctunnel-2.1.0.0-10.4.1.lib32_64_n9000" + }, + "NR3F": { + "agnostic": false, + "epldImgName": "n9000-epld.10.3.1.F.img", + "imageName": "nxos64-cs.10.3.1.F.bin", + "nxosVersion": "10.3.1_nxos64-cs_64bit", + "packageName": "", + "platform": "N9K/N3K", + "platformPolicies": "", + "policyDescr": "NR3F", + "policyName": "NR3F", + "policyType": "PLATFORM", + "ref_count": 0, + "rpmimages": null + } + }, + "test_image_policy_replace_bulk_00031a": { + "KR5M": { + "agnostic": false, + "epldImgName": "n9000-epld.10.2.5.M.img", + "imageName": "nxos64-cs.10.2.5.M.bin", + "nxosVersion": "10.2.5_nxos64-cs_64bit", + "packageName": "mtx-openconfig-all-2.0.0.0-10.4.1.src.rpm", + "platform": "N9K/N3K", + "platformPolicies": "", + "policyDescr": "KR5M", + "policyName": "KR5M", + "policyType": "PLATFORM", + "ref_count": 0, + "rpmimages": "mtx-grpctunnel-2.1.0.0-10.4.1.lib32_64_n9000" + }, + "NR3F": { + "agnostic": false, + "epldImgName": "n9000-epld.10.3.1.F.img", + "imageName": "nxos64-cs.10.3.1.F.bin", + "nxosVersion": "10.3.1_nxos64-cs_64bit", + "packageName": "", + "platform": "N9K/N3K", + "platformPolicies": "", + "policyDescr": "NR3F", + "policyName": "NR3F", + "policyType": "PLATFORM", + "ref_count": 0, + "rpmimages": null + } + }, + "test_image_policy_replace_bulk_00032a": { + "KR5M": { + "agnostic": false, + "epldImgName": "n9000-epld.10.2.5.M.img", + "imageName": "nxos64-cs.10.2.5.M.bin", + "nxosVersion": "10.2.5_nxos64-cs_64bit", + "packageName": "mtx-openconfig-all-2.0.0.0-10.4.1.src.rpm", + "platform": "N9K/N3K", + "platformPolicies": "", + "policyDescr": "KR5M", + "policyName": "KR5M", + "policyType": "PLATFORM", + "ref_count": 0, + "rpmimages": "mtx-grpctunnel-2.1.0.0-10.4.1.lib32_64_n9000" + }, + "NR3F": { + "agnostic": false, + "epldImgName": "n9000-epld.10.3.1.F.img", + "imageName": "nxos64-cs.10.3.1.F.bin", + "nxosVersion": "10.3.1_nxos64-cs_64bit", + "packageName": "", + "platform": "N9K/N3K", + "platformPolicies": "", + "policyDescr": "NR3F", + "policyName": "NR3F", + "policyType": "PLATFORM", + "ref_count": 0, + "rpmimages": null + } + }, + "test_image_policy_replace_bulk_00034a": { + "KR5M": { + "agnostic": false, + "epldImgName": "n9000-epld.10.2.5.M.img", + "imageName": "nxos64-cs.10.2.5.M.bin", + "nxosVersion": "10.2.5_nxos64-cs_64bit", + "packageName": "mtx-openconfig-all-2.0.0.0-10.4.1.src.rpm", + "platform": "N9K/N3K", + "platformPolicies": "", + "policyDescr": "KR5M", + "policyName": "KR5M", + "policyType": "PLATFORM", + "ref_count": 0, + "rpmimages": "mtx-grpctunnel-2.1.0.0-10.4.1.lib32_64_n9000" + } + }, + "test_image_policy_replace_bulk_00035a": { + "KR5M": { + "agnostic": false, + "epldImgName": "n9000-epld.10.2.5.M.img", + "imageName": "nxos64-cs.10.2.5.M.bin", + "nxosVersion": "10.2.5_nxos64-cs_64bit", + "packageName": "mtx-openconfig-all-2.0.0.0-10.4.1.src.rpm", + "platform": "N9K/N3K", + "platformPolicies": "", + "policyDescr": "KR5M", + "policyName": "KR5M", + "policyType": "PLATFORM", + "ref_count": 0, + "rpmimages": "mtx-grpctunnel-2.1.0.0-10.4.1.lib32_64_n9000" + }, + "NR3F": { + "agnostic": false, + "epldImgName": "n9000-epld.10.3.1.F.img", + "imageName": "nxos64-cs.10.3.1.F.bin", + "nxosVersion": "10.3.1_nxos64-cs_64bit", + "packageName": "", + "platform": "N9K/N3K", + "platformPolicies": "", + "policyDescr": "NR3F", + "policyName": "NR3F", + "policyType": "PLATFORM", + "ref_count": 0, + "rpmimages": null + } + }, + "test_image_policy_replace_bulk_00036a": { + "KR5M": { + "agnostic": false, + "epldImgName": "n9000-epld.10.2.5.M.img", + "imageName": "nxos64-cs.10.2.5.M.bin", + "nxosVersion": "10.2.5_nxos64-cs_64bit", + "packageName": "mtx-openconfig-all-2.0.0.0-10.4.1.src.rpm", + "platform": "N9K/N3K", + "platformPolicies": "", + "policyDescr": "KR5M", + "policyName": "KR5M", + "policyType": "PLATFORM", + "ref_count": 0, + "rpmimages": "mtx-grpctunnel-2.1.0.0-10.4.1.lib32_64_n9000" + } + }, + "test_image_policy_update_00030a": { + "KR5M": { + "agnostic": false, + "epldImgName": "n9000-epld.10.2.5.M.img", + "imageName": "nxos64-cs.10.2.5.M.bin", + "nxosVersion": "10.2.5_nxos64-cs_64bit", + "packageName": "mtx-openconfig-all-2.0.0.0-10.4.1.src.rpm", + "platform": "N9K/N3K", + "platformPolicies": "", + "policyDescr": "KR5M", + "policyName": "KR5M", + "policyType": "PLATFORM", + "ref_count": 0, + "rpmimages": "mtx-grpctunnel-2.1.0.0-10.4.1.lib32_64_n9000" + }, + "NR3F": { + "agnostic": false, + "epldImgName": "n9000-epld.10.3.1.F.img", + "imageName": "nxos64-cs.10.3.1.F.bin", + "nxosVersion": "10.3.1_nxos64-cs_64bit", + "packageName": "", + "platform": "N9K/N3K", + "platformPolicies": "", + "policyDescr": "NR3F", + "policyName": "NR3F", + "policyType": "PLATFORM", + "ref_count": 0, + "rpmimages": null + } + }, + "test_image_policy_update_00031a": { + "KR5M": { + "agnostic": false, + "epldImgName": "n9000-epld.10.2.5.M.img", + "imageName": "nxos64-cs.10.2.5.M.bin", + "nxosVersion": "10.2.5_nxos64-cs_64bit", + "packageName": "mtx-openconfig-all-2.0.0.0-10.4.1.src.rpm", + "platform": "N9K/N3K", + "platformPolicies": "", + "policyDescr": "KR5M", + "policyName": "KR5M", + "policyType": "PLATFORM", + "ref_count": 0, + "rpmimages": "mtx-grpctunnel-2.1.0.0-10.4.1.lib32_64_n9000" + }, + "NR3F": { + "agnostic": false, + "epldImgName": "n9000-epld.10.3.1.F.img", + "imageName": "nxos64-cs.10.3.1.F.bin", + "nxosVersion": "10.3.1_nxos64-cs_64bit", + "packageName": "", + "platform": "N9K/N3K", + "platformPolicies": "", + "policyDescr": "NR3F", + "policyName": "NR3F", + "policyType": "PLATFORM", + "ref_count": 0, + "rpmimages": null + } + }, + "test_image_policy_update_00034a": {}, + "test_image_policy_update_00035a": { + "KR5M": { + "agnostic": false, + "epldImgName": "n9000-epld.10.2.5.M.img", + "imageName": "nxos64-cs.10.2.5.M.bin", + "nxosVersion": "10.2.5_nxos64-cs_64bit", + "packageName": "mtx-openconfig-all-2.0.0.0-10.4.1.src.rpm", + "platform": "N9K/N3K", + "platformPolicies": "", + "policyDescr": "KR5M", + "policyName": "KR5M", + "policyType": "PLATFORM", + "ref_count": 0, + "rpmimages": "mtx-grpctunnel-2.1.0.0-10.4.1.lib32_64_n9000" + }, + "NR3F": { + "agnostic": false, + "epldImgName": "n9000-epld.10.3.1.F.img", + "imageName": "nxos64-cs.10.3.1.F.bin", + "nxosVersion": "10.3.1_nxos64-cs_64bit", + "packageName": "", + "platform": "N9K/N3K", + "platformPolicies": "", + "policyDescr": "NR3F", + "policyName": "NR3F", + "policyType": "PLATFORM", + "ref_count": 0, + "rpmimages": null + } + }, + "test_image_policy_update_00036a": { + "KR5M": { + "agnostic": false, + "epldImgName": "n9000-epld.10.2.5.M.img", + "imageName": "nxos64-cs.10.2.5.M.bin", + "nxosVersion": "10.2.5_nxos64-cs_64bit", + "packageName": "mtx-openconfig-all-2.0.0.0-10.4.1.src.rpm", + "platform": "N9K/N3K", + "platformPolicies": "", + "policyDescr": "KR5M", + "policyName": "KR5M", + "policyType": "PLATFORM", + "ref_count": 0, + "rpmimages": "mtx-grpctunnel-2.1.0.0-10.4.1.lib32_64_n9000" + } + }, + "test_image_policy_update_00050a": { + "KR5M": { + "agnostic": false, + "epldImgName": "n9000-epld.10.2.5.M.img", + "imageName": "nxos64-cs.10.2.5.M.bin", + "nxosVersion": "10.2.5_nxos64-cs_64bit", + "packageName": "mtx-openconfig-all-2.0.0.0-10.4.1.src.rpm", + "platform": "N9K/N3K", + "platformPolicies": "", + "policyDescr": "KR5M", + "policyName": "KR5M", + "policyType": "PLATFORM", + "ref_count": 2, + "rpmimages": "mtx-grpctunnel-2.1.0.0-10.4.1.lib32_64_n9000" + } + }, + "test_image_policy_update_bulk_00030a": { + "KR5M": { + "agnostic": false, + "epldImgName": "n9000-epld.10.2.5.M.img", + "imageName": "nxos64-cs.10.2.5.M.bin", + "nxosVersion": "10.2.5_nxos64-cs_64bit", + "packageName": "mtx-openconfig-all-2.0.0.0-10.4.1.src.rpm", + "platform": "N9K/N3K", + "platformPolicies": "", + "policyDescr": "KR5M", + "policyName": "KR5M", + "policyType": "PLATFORM", + "ref_count": 0, + "rpmimages": "mtx-grpctunnel-2.1.0.0-10.4.1.lib32_64_n9000" + }, + "NR3F": { + "agnostic": false, + "epldImgName": "n9000-epld.10.3.1.F.img", + "imageName": "nxos64-cs.10.3.1.F.bin", + "nxosVersion": "10.3.1_nxos64-cs_64bit", + "packageName": "", + "platform": "N9K/N3K", + "platformPolicies": "", + "policyDescr": "NR3F", + "policyName": "NR3F", + "policyType": "PLATFORM", + "ref_count": 0, + "rpmimages": null + } + }, + "test_image_policy_update_bulk_00031a": { + "KR5M": { + "agnostic": false, + "epldImgName": "n9000-epld.10.2.5.M.img", + "imageName": "nxos64-cs.10.2.5.M.bin", + "nxosVersion": "10.2.5_nxos64-cs_64bit", + "packageName": "mtx-openconfig-all-2.0.0.0-10.4.1.src.rpm", + "platform": "N9K/N3K", + "platformPolicies": "", + "policyDescr": "KR5M", + "policyName": "KR5M", + "policyType": "PLATFORM", + "ref_count": 0, + "rpmimages": "mtx-grpctunnel-2.1.0.0-10.4.1.lib32_64_n9000" + }, + "NR3F": { + "agnostic": false, + "epldImgName": "n9000-epld.10.3.1.F.img", + "imageName": "nxos64-cs.10.3.1.F.bin", + "nxosVersion": "10.3.1_nxos64-cs_64bit", + "packageName": "", + "platform": "N9K/N3K", + "platformPolicies": "", + "policyDescr": "NR3F", + "policyName": "NR3F", + "policyType": "PLATFORM", + "ref_count": 0, + "rpmimages": null + } + }, + "test_image_policy_update_bulk_00032a": { + "KR5M": { + "agnostic": false, + "epldImgName": "n9000-epld.10.2.5.M.img", + "imageName": "nxos64-cs.10.2.5.M.bin", + "nxosVersion": "10.2.5_nxos64-cs_64bit", + "packageName": "mtx-openconfig-all-2.0.0.0-10.4.1.src.rpm", + "platform": "N9K/N3K", + "platformPolicies": "", + "policyDescr": "KR5M", + "policyName": "KR5M", + "policyType": "PLATFORM", + "ref_count": 0, + "rpmimages": "mtx-grpctunnel-2.1.0.0-10.4.1.lib32_64_n9000" + }, + "NR3F": { + "agnostic": false, + "epldImgName": "n9000-epld.10.3.1.F.img", + "imageName": "nxos64-cs.10.3.1.F.bin", + "nxosVersion": "10.3.1_nxos64-cs_64bit", + "packageName": "", + "platform": "N9K/N3K", + "platformPolicies": "", + "policyDescr": "NR3F", + "policyName": "NR3F", + "policyType": "PLATFORM", + "ref_count": 0, + "rpmimages": null + } + }, + "test_image_policy_update_bulk_00034a": { + "KR5M": { + "agnostic": false, + "epldImgName": "n9000-epld.10.2.5.M.img", + "imageName": "nxos64-cs.10.2.5.M.bin", + "nxosVersion": "10.2.5_nxos64-cs_64bit", + "packageName": "mtx-openconfig-all-2.0.0.0-10.4.1.src.rpm", + "platform": "N9K/N3K", + "platformPolicies": "", + "policyDescr": "KR5M", + "policyName": "KR5M", + "policyType": "PLATFORM", + "ref_count": 0, + "rpmimages": "mtx-grpctunnel-2.1.0.0-10.4.1.lib32_64_n9000" + } + }, + "test_image_policy_update_bulk_00035a": { + "KR5M": { + "agnostic": false, + "epldImgName": "n9000-epld.10.2.5.M.img", + "imageName": "nxos64-cs.10.2.5.M.bin", + "nxosVersion": "10.2.5_nxos64-cs_64bit", + "packageName": "mtx-openconfig-all-2.0.0.0-10.4.1.src.rpm", + "platform": "N9K/N3K", + "platformPolicies": "", + "policyDescr": "KR5M", + "policyName": "KR5M", + "policyType": "PLATFORM", + "ref_count": 0, + "rpmimages": "mtx-grpctunnel-2.1.0.0-10.4.1.lib32_64_n9000" + }, + "NR3F": { + "agnostic": false, + "epldImgName": "n9000-epld.10.3.1.F.img", + "imageName": "nxos64-cs.10.3.1.F.bin", + "nxosVersion": "10.3.1_nxos64-cs_64bit", + "packageName": "", + "platform": "N9K/N3K", + "platformPolicies": "", + "policyDescr": "NR3F", + "policyName": "NR3F", + "policyType": "PLATFORM", + "ref_count": 0, + "rpmimages": null + } + }, + "test_image_policy_update_bulk_00036a": { + "KR5M": { + "agnostic": false, + "epldImgName": "n9000-epld.10.2.5.M.img", + "imageName": "nxos64-cs.10.2.5.M.bin", + "nxosVersion": "10.2.5_nxos64-cs_64bit", + "packageName": "mtx-openconfig-all-2.0.0.0-10.4.1.src.rpm", + "platform": "N9K/N3K", + "platformPolicies": "", + "policyDescr": "KR5M", + "policyName": "KR5M", + "policyType": "PLATFORM", + "ref_count": 0, + "rpmimages": "mtx-grpctunnel-2.1.0.0-10.4.1.lib32_64_n9000" + } + }, + "test_image_policy_update_bulk_00050a": { + "KR5M": { + "agnostic": false, + "epldImgName": "n9000-epld.10.2.5.M.img", + "imageName": "nxos64-cs.10.2.5.M.bin", + "nxosVersion": "10.2.5_nxos64-cs_64bit", + "packageName": "mtx-openconfig-all-2.0.0.0-10.4.1.src.rpm", + "platform": "N9K/N3K", + "platformPolicies": "", + "policyDescr": "KR5M", + "policyName": "KR5M", + "policyType": "PLATFORM", + "ref_count": 2, + "rpmimages": "mtx-grpctunnel-2.1.0.0-10.4.1.lib32_64_n9000" + } + } +} \ No newline at end of file diff --git a/tests/unit/modules/dcnm/dcnm_image_policy/fixtures/data_payload.json b/tests/unit/modules/dcnm/dcnm_image_policy/fixtures/data_payload.json new file mode 100644 index 000000000..77f60cb3a --- /dev/null +++ b/tests/unit/modules/dcnm/dcnm_image_policy/fixtures/data_payload.json @@ -0,0 +1,138 @@ +{ + "test_image_policy_payload_00120a": { + "config": { + "agnostic": false, + "description": "image policy of 10.3(3)F", + "epld_image": "n9000-epld.10.3.2.F.img", + "name": "FOO", + "packages": { + "install": [ + "mtx-openconfig-all-2.0.0.0-10.4.1.src.rpm" + ], + "uninstall": [ + "mtx-grpctunnel-2.1.0.0-10.4.1.lib32_64_n9000" + ] + }, + "platform": "N9K", + "release": "10.3.1_nxos64-cs_64bit", + "type": "PLATFORM" + }, + "payload": { + "agnostic": false, + "epldImgName": "n9000-epld.10.3.2.F.img", + "nxosVersion": "10.3.1_nxos64-cs_64bit", + "packageName": "mtx-openconfig-all-2.0.0.0-10.4.1.src.rpm", + "platform": "N9K", + "policyDescr": "image policy of 10.3(3)F", + "policyName": "FOO", + "policyType": "PLATFORM", + "rpmimages": "mtx-grpctunnel-2.1.0.0-10.4.1.lib32_64_n9000" + } + }, + "test_image_policy_payload_00121a": { + "config": { + "agnostic": false, + "description": "BAR", + "epld_image": "", + "name": "BAR", + "packages": { + "install": [], + "uninstall": [] + }, + "platform": "N9K", + "release": "10.3.1_nxos64-cs_64bit" + }, + "payload": { + "agnostic": false, + "epldImgName": "", + "nxosVersion": "10.3.1_nxos64-cs_64bit", + "platform": "N9K", + "policyDescr": "BAR", + "policyName": "BAR", + "policyType": "PLATFORM" + } + }, + "test_image_policy_payload_00122a": { + "config": {} + }, + "test_image_policy_payload_00123a": { + "config": { + "agnostic": false, + "description": "BAR", + "epld_image": "", + "name": "BAR", + "packages": { + "install": [], + "uninstall": [] + }, + "platform": "N9K", + "release": "10.3.1_nxos64-cs_64bit" + }, + "payload": { + "agnostic": false, + "epldImgName": "", + "nxosVersion": "10.3.1_nxos64-cs_64bit", + "platform": "N9K", + "policyDescr": "BAR", + "policyName": "BAR", + "policyType": "PLATFORM" + } + }, + "test_image_policy_payload_00220a": { + "config": { + "agnostic": false, + "description": "image policy of 10.3(3)F", + "epld_image": "n9000-epld.10.3.2.F.img", + "name": "FOO", + "packages": { + "install": [ + "mtx-openconfig-all-2.0.0.0-10.4.1.src.rpm" + ], + "uninstall": [ + "mtx-grpctunnel-2.1.0.0-10.4.1.lib32_64_n9000" + ] + }, + "platform": "N9K", + "release": "10.3.1_nxos64-cs_64bit", + "type": "PLATFORM" + }, + "payload": { + "agnostic": false, + "epldImgName": "n9000-epld.10.3.2.F.img", + "nxosVersion": "10.3.1_nxos64-cs_64bit", + "packageName": "mtx-openconfig-all-2.0.0.0-10.4.1.src.rpm", + "platform": "N9K", + "policyDescr": "image policy of 10.3(3)F", + "policyName": "FOO", + "policyType": "PLATFORM", + "rpmimages": "mtx-grpctunnel-2.1.0.0-10.4.1.lib32_64_n9000" + } + }, + "test_image_policy_payload_00221a": { + "config": { + "agnostic": false, + "description": "BAR", + "epld_image": "", + "name": "BAR", + "packages": { + "install": [], + "uninstall": [] + }, + "platform": "N9K", + "release": "10.3.1_nxos64-cs_64bit", + "type": "PLATFORM" + }, + "payload": { + "agnostic": false, + "epldImgName": "", + "nxosVersion": "10.3.1_nxos64-cs_64bit", + "platform": "N9K", + "policyDescr": "BAR", + "policyName": "BAR", + "policyType": "PLATFORM" + } + }, + "test_image_policy_payload_00222a": { + "payload": {} + } +} \ No newline at end of file diff --git a/tests/unit/modules/dcnm/dcnm_image_policy/fixtures/payloads_ImagePolicyCreate.json b/tests/unit/modules/dcnm/dcnm_image_policy/fixtures/payloads_ImagePolicyCreate.json new file mode 100644 index 000000000..aa7f05db5 --- /dev/null +++ b/tests/unit/modules/dcnm/dcnm_image_policy/fixtures/payloads_ImagePolicyCreate.json @@ -0,0 +1,92 @@ +{ + "TEST_NOTES": [ + "Mocked payloads for ImagePolicyCreate unit tests.", + "tests/unit/modules/dcnm/dcnm_image_policy/test_image_policy_create.py" + ], + "test_image_policy_create_00020a": { + "agnostic": false, + "epldImgName": "n9000-epld.10.3.2.F.img", + "nxosVersion": "10.3.1_nxos64-cs_64bit", + "packageName": "mtx-openconfig-all-2.0.0.0-10.4.1.src.rpm", + "platform": "N9K", + "policyDescr": "image policy of 10.3(3)F", + "policyName": "FOO", + "policyType": "PLATFORM", + "rpmimages": "mtx-grpctunnel-2.1.0.0-10.4.1.lib32_64_n9000" + }, + "test_image_policy_create_00021a": [], + "test_image_policy_create_00022a": { + "agnostic": false, + "epldImgName": "n9000-epld.10.3.2.F.img", + "packageName": "mtx-openconfig-all-2.0.0.0-10.4.1.src.rpm", + "platform": "N9K", + "policyDescr": "image policy of 10.3(3)F", + "policyName": "FOO", + "policyType": "PLATFORM", + "rpmimages": "mtx-grpctunnel-2.1.0.0-10.4.1.lib32_64_n9000" + }, + "test_image_policy_create_00022b": { + "agnostic": false, + "epldImgName": "n9000-epld.10.3.2.F.img", + "nxosVersion": "10.3.1_nxos64-cs_64bit", + "packageName": "mtx-openconfig-all-2.0.0.0-10.4.1.src.rpm", + "platform": "N9K", + "policyDescr": "image policy of 10.3(3)F", + "policyType": "PLATFORM", + "rpmimages": "mtx-grpctunnel-2.1.0.0-10.4.1.lib32_64_n9000" + }, + "test_image_policy_create_00022c": { + "agnostic": false, + "epldImgName": "n9000-epld.10.3.2.F.img", + "nxosVersion": "10.3.1_nxos64-cs_64bit", + "packageName": "mtx-openconfig-all-2.0.0.0-10.4.1.src.rpm", + "platform": "N9K", + "policyDescr": "image policy of 10.3(3)F", + "policyName": "FOO", + "rpmimages": "mtx-grpctunnel-2.1.0.0-10.4.1.lib32_64_n9000" + }, + "test_image_policy_create_00030a": { + "agnostic": false, + "epldImgName": "n9000-epld.10.3.2.F.img", + "nxosVersion": "10.3.1_nxos64-cs_64bit", + "packageName": "mtx-openconfig-all-2.0.0.0-10.4.1.src.rpm", + "platform": "N9K", + "policyDescr": "image policy of 10.3(3)F", + "policyName": "KR5M", + "policyType": "PLATFORM", + "rpmimages": "mtx-grpctunnel-2.1.0.0-10.4.1.lib32_64_n9000" + }, + "test_image_policy_create_00031a": { + "agnostic": false, + "epldImgName": "n9000-epld.10.3.2.F.img", + "nxosVersion": "10.3.1_nxos64-cs_64bit", + "packageName": "mtx-openconfig-all-2.0.0.0-10.4.1.src.rpm", + "platform": "N9K", + "policyDescr": "image policy of 10.3(3)F", + "policyName": "FOO", + "policyType": "PLATFORM", + "rpmimages": "mtx-grpctunnel-2.1.0.0-10.4.1.lib32_64_n9000" + }, + "test_image_policy_create_00034a": { + "agnostic": false, + "epldImgName": "n9000-epld.10.3.2.F.img", + "nxosVersion": "10.3.1_nxos64-cs_64bit", + "packageName": "mtx-openconfig-all-2.0.0.0-10.4.1.src.rpm", + "platform": "N9K", + "policyDescr": "image policy of 10.3(3)F", + "policyName": "KR5M", + "policyType": "PLATFORM", + "rpmimages": "mtx-grpctunnel-2.1.0.0-10.4.1.lib32_64_n9000" + }, + "test_image_policy_create_00035a": { + "agnostic": false, + "epldImgName": "n9000-epld.10.3.2.F.img", + "nxosVersion": "10.3.1_nxos64-cs_64bit", + "packageName": "mtx-openconfig-all-2.0.0.0-10.4.1.src.rpm", + "platform": "N9K", + "policyDescr": "image policy of 10.3(3)F", + "policyName": "FOO", + "policyType": "PLATFORM", + "rpmimages": "mtx-grpctunnel-2.1.0.0-10.4.1.lib32_64_n9000" + } +} \ No newline at end of file diff --git a/tests/unit/modules/dcnm/dcnm_image_policy/fixtures/payloads_ImagePolicyCreateBulk.json b/tests/unit/modules/dcnm/dcnm_image_policy/fixtures/payloads_ImagePolicyCreateBulk.json new file mode 100644 index 000000000..54ad7669f --- /dev/null +++ b/tests/unit/modules/dcnm/dcnm_image_policy/fixtures/payloads_ImagePolicyCreateBulk.json @@ -0,0 +1,206 @@ +{ + "TEST_NOTES": [ + "Mocked payloads for ImagePolicyCreateBulk unit tests.", + "tests/unit/modules/dcnm/dcnm_image_policy/test_image_policy_create_bulk.py" + ], + "test_image_policy_create_bulk_00020a": [ + { + "agnostic": false, + "epldImgName": "n9000-epld.10.3.2.F.img", + "nxosVersion": "10.3.1_nxos64-cs_64bit", + "packageName": "mtx-openconfig-all-2.0.0.0-10.4.1.src.rpm", + "platform": "N9K", + "policyDescr": "image policy of 10.3(3)F", + "policyName": "FOO", + "policyType": "PLATFORM", + "rpmimages": "mtx-grpctunnel-2.1.0.0-10.4.1.lib32_64_n9000" + } + ], + "test_image_policy_create_bulk_00021a": { + "agnostic": false, + "epldImgName": "n9000-epld.10.3.2.F.img", + "nxosVersion": "10.3.1_nxos64-cs_64bit", + "packageName": "mtx-openconfig-all-2.0.0.0-10.4.1.src.rpm", + "platform": "N9K", + "policyDescr": "image policy of 10.3(3)F", + "policyName": "FOO", + "policyType": "PLATFORM", + "rpmimages": "mtx-grpctunnel-2.1.0.0-10.4.1.lib32_64_n9000" + }, + "test_image_policy_create_bulk_00022a": [ + { + "agnostic": false, + "epldImgName": "n9000-epld.10.3.2.F.img", + "packageName": "mtx-openconfig-all-2.0.0.0-10.4.1.src.rpm", + "platform": "N9K", + "policyDescr": "image policy of 10.3(3)F", + "policyName": "FOO", + "policyType": "PLATFORM", + "rpmimages": "mtx-grpctunnel-2.1.0.0-10.4.1.lib32_64_n9000" + } + ], + "test_image_policy_create_bulk_00022b": [ + { + "agnostic": false, + "epldImgName": "n9000-epld.10.3.2.F.img", + "nxosVersion": "10.3.1_nxos64-cs_64bit", + "packageName": "mtx-openconfig-all-2.0.0.0-10.4.1.src.rpm", + "platform": "N9K", + "policyDescr": "image policy of 10.3(3)F", + "policyType": "PLATFORM", + "rpmimages": "mtx-grpctunnel-2.1.0.0-10.4.1.lib32_64_n9000" + } + ], + "test_image_policy_create_bulk_00022c": [ + { + "agnostic": false, + "epldImgName": "n9000-epld.10.3.2.F.img", + "nxosVersion": "10.3.1_nxos64-cs_64bit", + "packageName": "mtx-openconfig-all-2.0.0.0-10.4.1.src.rpm", + "platform": "N9K", + "policyDescr": "image policy of 10.3(3)F", + "policyName": "FOO", + "rpmimages": "mtx-grpctunnel-2.1.0.0-10.4.1.lib32_64_n9000" + } + ], + "test_image_policy_create_bulk_00030a": [ + { + "agnostic": false, + "epldImgName": "n9000-epld.10.3.2.F.img", + "nxosVersion": "10.3.1_nxos64-cs_64bit", + "packageName": "mtx-openconfig-all-2.0.0.0-10.4.1.src.rpm", + "platform": "N9K", + "policyDescr": "image policy of 10.3(3)F", + "policyName": "KR5M", + "policyType": "PLATFORM", + "rpmimages": "mtx-grpctunnel-2.1.0.0-10.4.1.lib32_64_n9000" + } + ], + "test_image_policy_create_bulk_00031a": [ + { + "agnostic": false, + "epldImgName": "n9000-epld.10.3.2.F.img", + "nxosVersion": "10.3.1_nxos64-cs_64bit", + "packageName": "mtx-openconfig-all-2.0.0.0-10.4.1.src.rpm", + "platform": "N9K", + "policyDescr": "image policy of 10.3(3)F", + "policyName": "FOO", + "policyType": "PLATFORM", + "rpmimages": "mtx-grpctunnel-2.1.0.0-10.4.1.lib32_64_n9000" + } + ], + "test_image_policy_create_bulk_00032a": [ + { + "agnostic": false, + "epldImgName": "n9000-epld.10.3.2.F.img", + "nxosVersion": "10.3.1_nxos64-cs_64bit", + "packageName": "mtx-openconfig-all-2.0.0.0-10.4.1.src.rpm", + "platform": "N9K", + "policyDescr": "image policy of 10.3(3)F", + "policyName": "FOO", + "policyType": "PLATFORM", + "rpmimages": "mtx-grpctunnel-2.1.0.0-10.4.1.lib32_64_n9000" + }, + { + "agnostic": false, + "epldImgName": "n9000-epld.10.2.5.M.img", + "imageName": "nxos64-cs.10.2.5.M.bin", + "nxosVersion": "10.2.5_nxos64-cs_64bit", + "packageName": "", + "platform": "N9K", + "policyDescr": "KR5M", + "policyName": "KR5M", + "policyType": "PLATFORM", + "rpmimages": "" + } + ], + "test_image_policy_create_bulk_00034a": [ + { + "agnostic": false, + "epldImgName": "n9000-epld.10.3.2.F.img", + "nxosVersion": "10.3.1_nxos64-cs_64bit", + "packageName": "mtx-openconfig-all-2.0.0.0-10.4.1.src.rpm", + "platform": "N9K", + "policyDescr": "image policy of 10.3(3)F", + "policyName": "FOO", + "policyType": "PLATFORM", + "rpmimages": "mtx-grpctunnel-2.1.0.0-10.4.1.lib32_64_n9000" + } + ], + "test_image_policy_create_bulk_00035a": [ + { + "agnostic": false, + "epldImgName": "n9000-epld.10.3.2.F.img", + "nxosVersion": "10.3.1_nxos64-cs_64bit", + "packageName": "mtx-openconfig-all-2.0.0.0-10.4.1.src.rpm", + "platform": "N9K", + "policyDescr": "image policy of 10.3(3)F", + "policyName": "FOO", + "policyType": "PLATFORM", + "rpmimages": "mtx-grpctunnel-2.1.0.0-10.4.1.lib32_64_n9000" + } + ], + "test_image_policy_create_bulk_00036a": [ + { + "agnostic": false, + "epldImgName": "n9000-epld.10.3.2.F.img", + "nxosVersion": "10.3.1_nxos64-cs_64bit", + "packageName": "mtx-openconfig-all-2.0.0.0-10.4.1.src.rpm", + "platform": "N9K", + "policyDescr": "image policy of 10.3(3)F", + "policyName": "FOO", + "policyType": "PLATFORM", + "rpmimages": "mtx-grpctunnel-2.1.0.0-10.4.1.lib32_64_n9000" + } + ], + "test_image_policy_create_bulk_00037a": [ + { + "agnostic": false, + "epldImgName": "n9000-epld.10.3.2.F.img", + "nxosVersion": "10.3.1_nxos64-cs_64bit", + "packageName": "mtx-openconfig-all-2.0.0.0-10.4.1.src.rpm", + "platform": "N9K", + "policyDescr": "image policy of 10.3(3)F", + "policyName": "FOO", + "policyType": "PLATFORM", + "rpmimages": "mtx-grpctunnel-2.1.0.0-10.4.1.lib32_64_n9000" + } + ], + "test_image_policy_create_bulk_00037b": [ + { + "agnostic": false, + "epldImgName": "n9000-epld.10.3.2.F.img", + "nxosVersion": "10.3.1_nxos64-cs_64bit", + "packageName": "", + "platform": "N9K", + "policyDescr": "image policy of 10.3(3)F", + "policyName": "BAR", + "policyType": "PLATFORM", + "rpmimages": "" + } + ], + "test_image_policy_create_bulk_00037d": [ + { + "agnostic": false, + "epldImgName": "n9000-epld.10.3.2.F.img", + "nxosVersion": "10.3.1_nxos64-cs_64bit", + "packageName": "mtx-openconfig-all-2.0.0.0-10.4.1.src.rpm", + "platform": "N9K", + "policyDescr": "image policy of 10.3(3)F", + "policyName": "FOO", + "policyType": "PLATFORM", + "rpmimages": "mtx-grpctunnel-2.1.0.0-10.4.1.lib32_64_n9000" + }, + { + "agnostic": false, + "epldImgName": "n9000-epld.10.3.2.F.img", + "nxosVersion": "10.3.1_nxos64-cs_64bit", + "packageName": "", + "platform": "N9K", + "policyDescr": "image policy of 10.3(3)F", + "policyName": "BAR", + "policyType": "PLATFORM", + "rpmimages": "" + } + ] +} \ No newline at end of file diff --git a/tests/unit/modules/dcnm/dcnm_image_policy/fixtures/payloads_ImagePolicyReplaceBulk.json b/tests/unit/modules/dcnm/dcnm_image_policy/fixtures/payloads_ImagePolicyReplaceBulk.json new file mode 100644 index 000000000..667b810ac --- /dev/null +++ b/tests/unit/modules/dcnm/dcnm_image_policy/fixtures/payloads_ImagePolicyReplaceBulk.json @@ -0,0 +1,209 @@ +{ + "TEST_NOTES": [ + "Mocked payloads for ImagePolicyReplaceBulk unit tests.", + "tests/unit/modules/dcnm/dcnm_image_policy/test_image_policy_replace_bulk.py" + ], + "test_image_policy_replace_bulk_00020a": [ + { + "agnostic": false, + "epldImgName": "n9000-epld.10.3.2.F.img", + "nxosVersion": "10.3.1_nxos64-cs_64bit", + "packageName": "mtx-openconfig-all-2.0.0.0-10.4.1.src.rpm", + "platform": "N9K", + "policyDescr": "image policy of 10.3(3)F", + "policyName": "FOO", + "policyType": "PLATFORM", + "rpmimages": "mtx-grpctunnel-2.1.0.0-10.4.1.lib32_64_n9000" + } + ], + "test_image_policy_replace_bulk_00021a": { + "agnostic": false, + "epldImgName": "n9000-epld.10.3.2.F.img", + "nxosVersion": "10.3.1_nxos64-cs_64bit", + "packageName": "mtx-openconfig-all-2.0.0.0-10.4.1.src.rpm", + "platform": "N9K", + "policyDescr": "image policy of 10.3(3)F", + "policyName": "FOO", + "policyType": "PLATFORM", + "rpmimages": "mtx-grpctunnel-2.1.0.0-10.4.1.lib32_64_n9000" + }, + "test_image_policy_replace_bulk_00022a": [ + { + "agnostic": false, + "epldImgName": "n9000-epld.10.3.2.F.img", + "packageName": "mtx-openconfig-all-2.0.0.0-10.4.1.src.rpm", + "platform": "N9K", + "policyDescr": "image policy of 10.3(3)F", + "policyName": "FOO", + "policyType": "PLATFORM", + "rpmimages": "mtx-grpctunnel-2.1.0.0-10.4.1.lib32_64_n9000" + } + ], + "test_image_policy_replace_bulk_00022b": [ + { + "agnostic": false, + "epldImgName": "n9000-epld.10.3.2.F.img", + "nxosVersion": "10.3.1_nxos64-cs_64bit", + "packageName": "mtx-openconfig-all-2.0.0.0-10.4.1.src.rpm", + "platform": "N9K", + "policyDescr": "image policy of 10.3(3)F", + "policyType": "PLATFORM", + "rpmimages": "mtx-grpctunnel-2.1.0.0-10.4.1.lib32_64_n9000" + } + ], + "test_image_policy_replace_bulk_00022c": [ + { + "agnostic": false, + "epldImgName": "n9000-epld.10.3.2.F.img", + "nxosVersion": "10.3.1_nxos64-cs_64bit", + "packageName": "mtx-openconfig-all-2.0.0.0-10.4.1.src.rpm", + "platform": "N9K", + "policyDescr": "image policy of 10.3(3)F", + "policyName": "FOO", + "rpmimages": "mtx-grpctunnel-2.1.0.0-10.4.1.lib32_64_n9000" + } + ], + "test_image_policy_replace_bulk_00023a": ["IM_A_STRING_BUT_SHOULD_BE_A_DICT"], + "test_image_policy_replace_bulk_00030a": [ + { + "agnostic": false, + "epldImgName": "n9000-epld.10.3.2.F.img", + "nxosVersion": "10.3.1_nxos64-cs_64bit", + "packageName": "mtx-openconfig-all-2.0.0.0-10.4.1.src.rpm", + "platform": "N9K", + "policyDescr": "KR5M Replaced", + "policyName": "KR5M", + "policyType": "PLATFORM", + "rpmimages": "mtx-grpctunnel-2.1.0.0-10.4.1.lib32_64_n9000" + } + ], + "test_image_policy_replace_bulk_00031a": [ + { + "agnostic": false, + "epldImgName": "n9000-epld.10.3.2.F.img", + "nxosVersion": "10.3.1_nxos64-cs_64bit", + "packageName": "mtx-openconfig-all-2.0.0.0-10.4.1.src.rpm", + "platform": "N9K", + "policyDescr": "image policy of 10.3(3)F", + "policyName": "FOO", + "policyType": "PLATFORM", + "rpmimages": "mtx-grpctunnel-2.1.0.0-10.4.1.lib32_64_n9000" + } + ], + "test_image_policy_replace_bulk_00032a": [ + { + "agnostic": false, + "epldImgName": "n9000-epld.10.3.2.F.img", + "nxosVersion": "10.3.1_nxos64-cs_64bit", + "packageName": "mtx-openconfig-all-2.0.0.0-10.4.1.src.rpm", + "platform": "N9K", + "policyDescr": "image policy of 10.3(3)F", + "policyName": "FOO", + "policyType": "PLATFORM", + "rpmimages": "mtx-grpctunnel-2.1.0.0-10.4.1.lib32_64_n9000" + }, + { + "agnostic": false, + "epldImgName": "n9000-epld.10.2.5.M.img", + "imageName": "nxos64-cs.10.2.5.M.bin", + "nxosVersion": "10.2.5_nxos64-cs_64bit", + "packageName": "", + "platform": "N9K", + "policyDescr": "KR5M", + "policyName": "KR5M", + "policyType": "PLATFORM", + "rpmimages": "" + } + ], + "test_image_policy_replace_bulk_00035a": [ + { + "agnostic": false, + "epldImgName": "n9000-epld.10.2.5.M.img", + "imageName": "nxos64-cs.10.2.5.M.bin", + "nxosVersion": "10.2.5_nxos64-cs_64bit", + "packageName": "mtx-openconfig-all-2.0.0.0-10.4.1.src.rpm", + "platform": "N9K/N3K", + "platformPolicies": "", + "policyDescr": "KR5M replaced", + "policyName": "KR5M", + "policyType": "PLATFORM", + "rpmimages": "mtx-grpctunnel-2.1.0.0-10.4.1.lib32_64_n9000" + }, + { + "agnostic": false, + "epldImgName": "n9000-epld.10.3.1.F.img", + "imageName": "nxos64-cs.10.3.1.F.bin", + "nxosVersion": "10.3.1_nxos64-cs_64bit", + "packageName": "", + "platform": "N9K/N3K", + "platformPolicies": "", + "policyDescr": "NR3F replaced", + "policyName": "NR3F", + "policyType": "PLATFORM", + "rpmimages": "" + } + ], + "test_image_policy_replace_bulk_00036a": [ + { + "agnostic": false, + "epldImgName": "n9000-epld.10.3.2.F.img", + "nxosVersion": "10.3.1_nxos64-cs_64bit", + "packageName": "mtx-openconfig-all-2.0.0.0-10.4.1.src.rpm", + "platform": "N9K", + "policyDescr": "KR5M replaced", + "policyName": "KR5M", + "policyType": "PLATFORM", + "rpmimages": "mtx-grpctunnel-2.1.0.0-10.4.1.lib32_64_n9000" + } + ], + "test_image_policy_replace_bulk_00037a": [ + { + "agnostic": false, + "epldImgName": "n9000-epld.10.3.2.F.img", + "nxosVersion": "10.3.1_nxos64-cs_64bit", + "packageName": "mtx-openconfig-all-2.0.0.0-10.4.1.src.rpm", + "platform": "N9K", + "policyDescr": "image policy of 10.3(3)F", + "policyName": "FOO", + "policyType": "PLATFORM", + "rpmimages": "mtx-grpctunnel-2.1.0.0-10.4.1.lib32_64_n9000" + } + ], + "test_image_policy_replace_bulk_00037b": [ + { + "agnostic": false, + "epldImgName": "n9000-epld.10.3.2.F.img", + "nxosVersion": "10.3.1_nxos64-cs_64bit", + "packageName": "", + "platform": "N9K", + "policyDescr": "image policy of 10.3(3)F", + "policyName": "BAR", + "policyType": "PLATFORM", + "rpmimages": "" + } + ], + "test_image_policy_replace_bulk_00037d": [ + { + "agnostic": false, + "epldImgName": "n9000-epld.10.3.2.F.img", + "nxosVersion": "10.3.1_nxos64-cs_64bit", + "packageName": "mtx-openconfig-all-2.0.0.0-10.4.1.src.rpm", + "platform": "N9K", + "policyDescr": "image policy of 10.3(3)F", + "policyName": "FOO", + "policyType": "PLATFORM", + "rpmimages": "mtx-grpctunnel-2.1.0.0-10.4.1.lib32_64_n9000" + }, + { + "agnostic": false, + "epldImgName": "n9000-epld.10.3.2.F.img", + "nxosVersion": "10.3.1_nxos64-cs_64bit", + "packageName": "", + "platform": "N9K", + "policyDescr": "image policy of 10.3(3)F", + "policyName": "BAR", + "policyType": "PLATFORM", + "rpmimages": "" + } + ] +} \ No newline at end of file diff --git a/tests/unit/modules/dcnm/dcnm_image_policy/fixtures/payloads_ImagePolicyUpdate.json b/tests/unit/modules/dcnm/dcnm_image_policy/fixtures/payloads_ImagePolicyUpdate.json new file mode 100644 index 000000000..8472be256 --- /dev/null +++ b/tests/unit/modules/dcnm/dcnm_image_policy/fixtures/payloads_ImagePolicyUpdate.json @@ -0,0 +1,126 @@ +{ + "TEST_NOTES": [ + "Mocked payloads for ImagePolicyUpdateBulk unit tests.", + "tests/unit/modules/dcnm/dcnm_image_policy/test_image_policy_update.py" + ], + "test_image_policy_update_00020a":{ + "agnostic": false, + "epldImgName": "n9000-epld.10.3.2.F.img", + "nxosVersion": "10.3.1_nxos64-cs_64bit", + "packageName": "mtx-openconfig-all-2.0.0.0-10.4.1.src.rpm", + "platform": "N9K", + "policyDescr": "image policy of 10.3(3)F", + "policyName": "FOO", + "policyType": "PLATFORM", + "rpmimages": "mtx-grpctunnel-2.1.0.0-10.4.1.lib32_64_n9000" + }, + "test_image_policy_update_00021a": [ + { + "agnostic": false, + "epldImgName": "n9000-epld.10.3.2.F.img", + "nxosVersion": "10.3.1_nxos64-cs_64bit", + "packageName": "mtx-openconfig-all-2.0.0.0-10.4.1.src.rpm", + "platform": "N9K", + "policyDescr": "image policy of 10.3(3)F", + "policyName": "FOO", + "policyType": "PLATFORM", + "rpmimages": "mtx-grpctunnel-2.1.0.0-10.4.1.lib32_64_n9000" + } + ], + "test_image_policy_update_00022a": { + "agnostic": false, + "epldImgName": "n9000-epld.10.3.2.F.img", + "packageName": "mtx-openconfig-all-2.0.0.0-10.4.1.src.rpm", + "platform": "N9K", + "policyDescr": "image policy of 10.3(3)F", + "policyName": "FOO", + "policyType": "PLATFORM", + "rpmimages": "mtx-grpctunnel-2.1.0.0-10.4.1.lib32_64_n9000" + }, + "test_image_policy_update_00022b": { + "agnostic": false, + "epldImgName": "n9000-epld.10.3.2.F.img", + "nxosVersion": "10.3.1_nxos64-cs_64bit", + "packageName": "mtx-openconfig-all-2.0.0.0-10.4.1.src.rpm", + "platform": "N9K", + "policyDescr": "image policy of 10.3(3)F", + "policyType": "PLATFORM", + "rpmimages": "mtx-grpctunnel-2.1.0.0-10.4.1.lib32_64_n9000" + }, + "test_image_policy_update_00022c": { + "agnostic": false, + "epldImgName": "n9000-epld.10.3.2.F.img", + "nxosVersion": "10.3.1_nxos64-cs_64bit", + "packageName": "mtx-openconfig-all-2.0.0.0-10.4.1.src.rpm", + "platform": "N9K", + "policyDescr": "image policy of 10.3(3)F", + "policyName": "FOO", + "rpmimages": "mtx-grpctunnel-2.1.0.0-10.4.1.lib32_64_n9000" + }, + "test_image_policy_update_00030a": { + "agnostic": false, + "epldImgName": "n9000-epld.10.2.5.M.img", + "nxosVersion": "10.2.5_nxos64-cs_64bit", + "packageName": "mtx-openconfig-all-2.0.0.0-10.4.1.src.rpm", + "platform": "N9K", + "policyDescr": "KR5M updated", + "policyName": "KR5M", + "policyType": "PLATFORM", + "rpmimages": "mtx-grpctunnel-2.1.0.0-10.4.1.lib32_64_n9000" + }, + "test_image_policy_update_00031a": { + "agnostic": false, + "epldImgName": "n9000-epld.10.3.2.F.img", + "nxosVersion": "10.3.1_nxos64-cs_64bit", + "packageName": "mtx-openconfig-all-2.0.0.0-10.4.1.src.rpm", + "platform": "N9K", + "policyDescr": "image policy of 10.3(3)F", + "policyName": "FOO", + "policyType": "PLATFORM", + "rpmimages": "mtx-grpctunnel-2.1.0.0-10.4.1.lib32_64_n9000" + }, + "test_image_policy_update_00034a": { + "agnostic": false, + "epldImgName": "n9000-epld.10.3.2.F.img", + "nxosVersion": "10.3.1_nxos64-cs_64bit", + "packageName": "mtx-openconfig-all-2.0.0.0-10.4.1.src.rpm", + "platform": "N9K", + "policyDescr": "image policy of 10.3(3)F", + "policyName": "FOO", + "policyType": "PLATFORM", + "rpmimages": "mtx-grpctunnel-2.1.0.0-10.4.1.lib32_64_n9000" + }, + "test_image_policy_update_00035a": { + "agnostic": false, + "epldImgName": "n9000-epld.10.2.5.M.img", + "nxosVersion": "10.2.5_nxos64-cs_64bit", + "packageName": "mtx-openconfig-all-2.0.0.0-10.4.1.src.rpm", + "platform": "N9K/N3K", + "policyDescr": "KR5M updated", + "policyName": "KR5M", + "policyType": "PLATFORM", + "rpmimages": "mtx-grpctunnel-2.1.0.0-10.4.1.lib32_64_n9000" + }, + "test_image_policy_update_00036a": { + "agnostic": false, + "epldImgName": "n9000-epld.10.3.2.F.img", + "nxosVersion": "10.3.1_nxos64-cs_64bit", + "packageName": "mtx-openconfig-all-2.0.0.0-10.4.1.src.rpm", + "platform": "N9K", + "policyDescr": "KR5M updated", + "policyName": "KR5M", + "policyType": "PLATFORM", + "rpmimages": "mtx-grpctunnel-2.1.0.0-10.4.1.lib32_64_n9000" + }, + "test_image_policy_update_00050a": { + "agnostic": false, + "epldImgName": "n9000-epld.10.3.2.F.img", + "nxosVersion": "10.3.1_nxos64-cs_64bit", + "packageName": "mtx-openconfig-all-2.0.0.0-10.4.1.src.rpm", + "platform": "N9K", + "policyDescr": "KR5M updated", + "policyName": "KR5M", + "policyType": "PLATFORM", + "rpmimages": "mtx-grpctunnel-2.1.0.0-10.4.1.lib32_64_n9000" + } +} \ No newline at end of file diff --git a/tests/unit/modules/dcnm/dcnm_image_policy/fixtures/payloads_ImagePolicyUpdateBulk.json b/tests/unit/modules/dcnm/dcnm_image_policy/fixtures/payloads_ImagePolicyUpdateBulk.json new file mode 100644 index 000000000..ddda1c2dd --- /dev/null +++ b/tests/unit/modules/dcnm/dcnm_image_policy/fixtures/payloads_ImagePolicyUpdateBulk.json @@ -0,0 +1,218 @@ +{ + "TEST_NOTES": [ + "Mocked payloads for ImagePolicyUpdate unit tests.", + "tests/unit/modules/dcnm/dcnm_image_policy/test_image_policy_update_bulk.py" + ], + "test_image_policy_update_bulk_00020a": [ + { + "agnostic": false, + "epldImgName": "n9000-epld.10.3.2.F.img", + "nxosVersion": "10.3.1_nxos64-cs_64bit", + "packageName": "mtx-openconfig-all-2.0.0.0-10.4.1.src.rpm", + "platform": "N9K", + "policyDescr": "image policy of 10.3(3)F", + "policyName": "FOO", + "policyType": "PLATFORM", + "rpmimages": "mtx-grpctunnel-2.1.0.0-10.4.1.lib32_64_n9000" + } + ], + "test_image_policy_update_bulk_00021a": { + "agnostic": false, + "epldImgName": "n9000-epld.10.3.2.F.img", + "nxosVersion": "10.3.1_nxos64-cs_64bit", + "packageName": "mtx-openconfig-all-2.0.0.0-10.4.1.src.rpm", + "platform": "N9K", + "policyDescr": "image policy of 10.3(3)F", + "policyName": "FOO", + "policyType": "PLATFORM", + "rpmimages": "mtx-grpctunnel-2.1.0.0-10.4.1.lib32_64_n9000" + }, + "test_image_policy_update_bulk_00022a": [ + { + "agnostic": false, + "epldImgName": "n9000-epld.10.3.2.F.img", + "packageName": "mtx-openconfig-all-2.0.0.0-10.4.1.src.rpm", + "platform": "N9K", + "policyDescr": "image policy of 10.3(3)F", + "policyName": "FOO", + "policyType": "PLATFORM", + "rpmimages": "mtx-grpctunnel-2.1.0.0-10.4.1.lib32_64_n9000" + } + ], + "test_image_policy_update_bulk_00022b": [ + { + "agnostic": false, + "epldImgName": "n9000-epld.10.3.2.F.img", + "nxosVersion": "10.3.1_nxos64-cs_64bit", + "packageName": "mtx-openconfig-all-2.0.0.0-10.4.1.src.rpm", + "platform": "N9K", + "policyDescr": "image policy of 10.3(3)F", + "policyType": "PLATFORM", + "rpmimages": "mtx-grpctunnel-2.1.0.0-10.4.1.lib32_64_n9000" + } + ], + "test_image_policy_update_bulk_00022c": [ + { + "agnostic": false, + "epldImgName": "n9000-epld.10.3.2.F.img", + "nxosVersion": "10.3.1_nxos64-cs_64bit", + "packageName": "mtx-openconfig-all-2.0.0.0-10.4.1.src.rpm", + "platform": "N9K", + "policyDescr": "image policy of 10.3(3)F", + "policyName": "FOO", + "rpmimages": "mtx-grpctunnel-2.1.0.0-10.4.1.lib32_64_n9000" + } + ], + "test_image_policy_update_bulk_00023a": ["IM_A_STRING_BUT_SHOULD_BE_A_DICT"], + "test_image_policy_update_bulk_00030a": [ + { + "agnostic": false, + "epldImgName": "n9000-epld.10.2.5.M.img", + "nxosVersion": "10.2.5_nxos64-cs_64bit", + "packageName": "mtx-openconfig-all-2.0.0.0-10.4.1.src.rpm", + "platform": "N9K", + "policyDescr": "KR5M updated", + "policyName": "KR5M", + "policyType": "PLATFORM", + "rpmimages": "mtx-grpctunnel-2.1.0.0-10.4.1.lib32_64_n9000" + } + ], + "test_image_policy_update_bulk_00031a": [ + { + "agnostic": false, + "epldImgName": "n9000-epld.10.3.2.F.img", + "nxosVersion": "10.3.1_nxos64-cs_64bit", + "packageName": "mtx-openconfig-all-2.0.0.0-10.4.1.src.rpm", + "platform": "N9K", + "policyDescr": "image policy of 10.3(3)F", + "policyName": "FOO", + "policyType": "PLATFORM", + "rpmimages": "mtx-grpctunnel-2.1.0.0-10.4.1.lib32_64_n9000" + } + ], + "test_image_policy_update_bulk_00032a": [ + { + "agnostic": false, + "epldImgName": "n9000-epld.10.3.2.F.img", + "nxosVersion": "10.3.1_nxos64-cs_64bit", + "packageName": "mtx-openconfig-all-2.0.0.0-10.4.1.src.rpm", + "platform": "N9K", + "policyDescr": "image policy of 10.3(3)F", + "policyName": "FOO", + "policyType": "PLATFORM", + "rpmimages": "mtx-grpctunnel-2.1.0.0-10.4.1.lib32_64_n9000" + }, + { + "agnostic": false, + "epldImgName": "n9000-epld.10.2.5.M.img", + "imageName": "nxos64-cs.10.2.5.M.bin", + "nxosVersion": "10.2.5_nxos64-cs_64bit", + "packageName": "", + "platform": "N9K", + "policyDescr": "KR5M updated", + "policyName": "KR5M", + "policyType": "PLATFORM", + "rpmimages": "" + } + ], + "test_image_policy_update_bulk_00035a": [ + { + "agnostic": false, + "epldImgName": "n9000-epld.10.2.5.M.img", + "nxosVersion": "10.2.5_nxos64-cs_64bit", + "packageName": "mtx-openconfig-all-2.0.0.0-10.4.1.src.rpm", + "platform": "N9K/N3K", + "policyDescr": "KR5M updated", + "policyName": "KR5M", + "policyType": "PLATFORM", + "rpmimages": "mtx-grpctunnel-2.1.0.0-10.4.1.lib32_64_n9000" + }, + { + "agnostic": false, + "epldImgName": "n9000-epld.10.3.1.F.img", + "nxosVersion": "10.3.1_nxos64-cs_64bit", + "packageName": "", + "platform": "N9K/N3K", + "policyDescr": "NR3F updated", + "policyName": "NR3F", + "policyType": "PLATFORM", + "rpmimages": "" + } + ], + "test_image_policy_update_bulk_00036a": [ + { + "agnostic": false, + "epldImgName": "n9000-epld.10.3.2.F.img", + "nxosVersion": "10.3.1_nxos64-cs_64bit", + "packageName": "mtx-openconfig-all-2.0.0.0-10.4.1.src.rpm", + "platform": "N9K", + "policyDescr": "KR5M updated", + "policyName": "KR5M", + "policyType": "PLATFORM", + "rpmimages": "mtx-grpctunnel-2.1.0.0-10.4.1.lib32_64_n9000" + } + ], + "test_image_policy_update_bulk_00037a": [ + { + "agnostic": false, + "epldImgName": "n9000-epld.10.3.2.F.img", + "nxosVersion": "10.3.1_nxos64-cs_64bit", + "packageName": "mtx-openconfig-all-2.0.0.0-10.4.1.src.rpm", + "platform": "N9K", + "policyDescr": "image policy of 10.3(3)F", + "policyName": "FOO", + "policyType": "PLATFORM", + "rpmimages": "mtx-grpctunnel-2.1.0.0-10.4.1.lib32_64_n9000" + } + ], + "test_image_policy_update_bulk_00037b": [ + { + "agnostic": false, + "epldImgName": "n9000-epld.10.3.2.F.img", + "nxosVersion": "10.3.1_nxos64-cs_64bit", + "packageName": "", + "platform": "N9K", + "policyDescr": "image policy of 10.3(3)F", + "policyName": "BAR", + "policyType": "PLATFORM", + "rpmimages": "" + } + ], + "test_image_policy_update_bulk_00037d": [ + { + "agnostic": false, + "epldImgName": "n9000-epld.10.3.2.F.img", + "nxosVersion": "10.3.1_nxos64-cs_64bit", + "packageName": "mtx-openconfig-all-2.0.0.0-10.4.1.src.rpm", + "platform": "N9K", + "policyDescr": "image policy of 10.3(3)F", + "policyName": "FOO", + "policyType": "PLATFORM", + "rpmimages": "mtx-grpctunnel-2.1.0.0-10.4.1.lib32_64_n9000" + }, + { + "agnostic": false, + "epldImgName": "n9000-epld.10.3.2.F.img", + "nxosVersion": "10.3.1_nxos64-cs_64bit", + "packageName": "", + "platform": "N9K", + "policyDescr": "image policy of 10.3(3)F", + "policyName": "BAR", + "policyType": "PLATFORM", + "rpmimages": "" + } + ], + "test_image_policy_update_bulk_00050a": [ + { + "agnostic": false, + "epldImgName": "n9000-epld.10.2.5.M.img", + "nxosVersion": "10.2.5_nxos64-cs_64bit", + "packageName": "mtx-openconfig-all-2.0.0.0-10.4.1.src.rpm", + "platform": "N9K", + "policyDescr": "KR5M updated", + "policyName": "KR5M", + "policyType": "PLATFORM", + "rpmimages": "mtx-grpctunnel-2.1.0.0-10.4.1.lib32_64_n9000" + } + ] +} \ No newline at end of file diff --git a/tests/unit/modules/dcnm/dcnm_image_policy/fixtures/response_current_RestSend.json b/tests/unit/modules/dcnm/dcnm_image_policy/fixtures/response_current_RestSend.json new file mode 100644 index 000000000..e229fe58e --- /dev/null +++ b/tests/unit/modules/dcnm/dcnm_image_policy/fixtures/response_current_RestSend.json @@ -0,0 +1,108 @@ +{ + "TEST_NOTES": [ + "Mocked response_current value." + ], + "test_image_policy_create_bulk_00035a": { + "agnostic": false, + "epldImgName": "n9000-epld.10.3.2.F.img", + "nxosVersion": "10.3.1_nxos64-cs_64bit", + "packageName": "mtx-openconfig-all-2.0.0.0-10.4.1.src.rpm", + "platform": "N9K", + "policyDescr": "image policy of 10.3(3)F", + "policyName": "FOO", + "policyType": "PLATFORM", + "rpmimages": "mtx-grpctunnel-2.1.0.0-10.4.1.lib32_64_n9000", + "sequence_number": 1 + }, + "test_image_policy_query_00030a": { + "MESSAGE": "OK", + "METHOD": "GET", + "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/policymgnt/policy", + "RETURN_CODE": 200, + "DATA": { + "lastOperDataObject": [ + { + "agnostic": false, + "epldImgName": "n9000-epld.10.2.5.M.img", + "nxosVersion": "10.2.5_nxos64-cs_64bit", + "packageName": "mtx-openconfig-all-2.0.0.0-10.4.1.src.rpm", + "platform": "N9K", + "policyDescr": "KR5M image policy", + "policyName": "KR5M", + "policyType": "PLATFORM", + "rpmimages": "mtx-grpctunnel-2.1.0.0-10.4.1.lib32_64_n9000" + } + ] + } + }, + "test_image_policy_query_00031a": { + "MESSAGE": "OK", + "METHOD": "GET", + "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/policymgnt/policy", + "RETURN_CODE": 200, + "DATA": { + "lastOperDataObject": [ + { + "agnostic": false, + "epldImgName": "n9000-epld.10.2.5.M.img", + "nxosVersion": "10.2.5_nxos64-cs_64bit", + "packageName": "mtx-openconfig-all-2.0.0.0-10.4.1.src.rpm", + "platform": "N9K", + "policyDescr": "KR5M image policy", + "policyName": "KR5M", + "policyType": "PLATFORM", + "rpmimages": "mtx-grpctunnel-2.1.0.0-10.4.1.lib32_64_n9000" + } + ] + } + }, + "test_image_policy_query_00032a": { + "MESSAGE": "OK", + "METHOD": "GET", + "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/policymgnt/policy", + "RETURN_CODE": 200, + "DATA": { + "lastOperDataObject": [ + { + "agnostic": false, + "epldImgName": "n9000-epld.10.2.5.M.img", + "imageName": "nxos64-cs.10.2.5.M.bin", + "nxosVersion": "10.2.5_nxos64-cs_64bit", + "packageName": "mtx-openconfig-all-2.0.0.0-10.4.1.src.rpm", + "platform": "N9K/N3K", + "platformPolicies": "", + "policyDescr": "KR5M", + "policyName": "KR5M", + "policyType": "PLATFORM", + "ref_count": 0, + "rpmimages": "mtx-grpctunnel-2.1.0.0-10.4.1.lib32_64_n9000" + }, + { + "agnostic": false, + "epldImgName": "n9000-epld.10.3.1.F.img", + "imageName": "nxos64-cs.10.3.1.F.bin", + "nxosVersion": "10.3.1_nxos64-cs_64bit", + "packageName": "", + "platform": "N9K/N3K", + "platformPolicies": "", + "policyDescr": "NR3F", + "policyName": "NR3F", + "policyType": "PLATFORM", + "ref_count": 0, + "rpmimages": null + } + ] + } + }, + "test_image_policy_query_00033a": { + "DATA": { + "lastOperDataObject": [], + "message": "", + "status": "SUCCESS" + }, + "MESSAGE": "OK", + "METHOD": "GET", + "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/policymgnt/policies", + "RETURN_CODE": 200 + } +} \ No newline at end of file diff --git a/tests/unit/modules/dcnm/dcnm_image_policy/fixtures/responses_ImagePolicies.json b/tests/unit/modules/dcnm/dcnm_image_policy/fixtures/responses_ImagePolicies.json new file mode 100644 index 000000000..4f9163b42 --- /dev/null +++ b/tests/unit/modules/dcnm/dcnm_image_policy/fixtures/responses_ImagePolicies.json @@ -0,0 +1,24 @@ +{ + "TEST_NOTES": [ + "Mocked responses for ImagePolicies class used in the following unit tests.", + "tests/unit/modules/dcnm/dcnm_image_policy/test_image_policy_query.py" + ], + "test_image_policy_query_00030a": { + "MESSAGE": "OK", + "METHOD": "GET", + "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/policymgnt/policy", + "RETURN_CODE": 200 + }, + "test_image_policy_query_00031a": { + "MESSAGE": "OK", + "METHOD": "GET", + "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/policymgnt/policy", + "RETURN_CODE": 200 + }, + "test_image_policy_query_00032a": { + "MESSAGE": "OK", + "METHOD": "DELETE", + "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/policymgnt/policy", + "RETURN_CODE": 200 + } +} \ No newline at end of file diff --git a/tests/unit/modules/dcnm/dcnm_image_policy/fixtures/responses_ImagePolicyCommon.json b/tests/unit/modules/dcnm/dcnm_image_policy/fixtures/responses_ImagePolicyCommon.json new file mode 100644 index 000000000..6c8c395f8 --- /dev/null +++ b/tests/unit/modules/dcnm/dcnm_image_policy/fixtures/responses_ImagePolicyCommon.json @@ -0,0 +1,49 @@ +{ + "TEST_NOTES": [ + "Mocked responses for ImagePolicyCreate unit tests.", + "tests/unit/modules/dcnm/dcnm_image_policy/test_image_policy_create.py" + ], + "test_image_policy_common_00020a": { + "DATA": "NA", + "MESSAGE": "OK", + "METHOD": "GET", + "REQUEST_PATH": "https://foo/bar/endpoint", + "RETURN_CODE": 200 + }, + "test_image_policy_common_00021a": { + "DATA": "NA", + "MESSAGE": "Not Found", + "METHOD": "GET", + "REQUEST_PATH": "https://foo/bar/endpoint", + "RETURN_CODE": 404 + }, + "test_image_policy_common_00022a": { + "DATA": "NA", + "MESSAGE": "Internal Server Error", + "METHOD": "GET", + "REQUEST_PATH": "https://foo/bar/endpoint", + "RETURN_CODE": 500 + }, + "test_image_policy_common_00030a": { + "DATA": "NA", + "MESSAGE": "OK", + "METHOD": "POST", + "REQUEST_PATH": "https://foo/bar/endpoint", + "RETURN_CODE": 200 + }, + "test_image_policy_common_00031a": { + "DATA": "NA", + "MESSAGE": "NOK", + "METHOD": "POST", + "REQUEST_PATH": "https://foo/bar/endpoint", + "RETURN_CODE": 200 + }, + "test_image_policy_common_00032a": { + "DATA": "NA", + "ERROR": "Oh no!", + "MESSAGE": "OK", + "METHOD": "POST", + "REQUEST_PATH": "https://foo/bar/endpoint", + "RETURN_CODE": 200 + } +} \ No newline at end of file diff --git a/tests/unit/modules/dcnm/dcnm_image_policy/fixtures/responses_ImagePolicyCreate.json b/tests/unit/modules/dcnm/dcnm_image_policy/fixtures/responses_ImagePolicyCreate.json new file mode 100644 index 000000000..c71bd572e --- /dev/null +++ b/tests/unit/modules/dcnm/dcnm_image_policy/fixtures/responses_ImagePolicyCreate.json @@ -0,0 +1,38 @@ +{ + "TEST_NOTES": [ + "Mocked responses for ImagePolicyCreate unit tests.", + "tests/unit/modules/dcnm/dcnm_image_policy/test_image_policy_create.py" + ], + "test_image_policy_create_00035a": { + "DATA": "Policy created successfully.", + "MESSAGE": "OK", + "METHOD": "POST", + "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/policymgnt/platform-policy", + "RETURN_CODE": 200 + }, + "test_image_policy_create_00036a": { + "DATA": "Internal server error.", + "MESSAGE": "NOK", + "METHOD": "POST", + "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/policymgnt/platform-policy", + "RETURN_CODE": 500 + }, + "test_image_policy_create_00037a": [ + { + "DATA": "Policy created successfully.", + "MESSAGE": "OK", + "METHOD": "POST", + "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/policymgnt/platform-policy", + "RETURN_CODE": 200 + } + ], + "test_image_policy_create_00037b": [ + { + "DATA": "Internal server error.", + "MESSAGE": "NOK", + "METHOD": "POST", + "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/policymgnt/platform-policy", + "RETURN_CODE": 500 + } + ] +} \ No newline at end of file diff --git a/tests/unit/modules/dcnm/dcnm_image_policy/fixtures/responses_ImagePolicyCreateBulk.json b/tests/unit/modules/dcnm/dcnm_image_policy/fixtures/responses_ImagePolicyCreateBulk.json new file mode 100644 index 000000000..83f044c55 --- /dev/null +++ b/tests/unit/modules/dcnm/dcnm_image_policy/fixtures/responses_ImagePolicyCreateBulk.json @@ -0,0 +1,82 @@ +{ + "TEST_NOTES": [ + "Mocked responses for ImagePolicyCreateBulk unit tests.", + "tests/unit/modules/dcnm/dcnm_image_policy/test_image_policy_create_bulk.py" + ], + "test_image_policy_create_bulk_00035a": { + "DATA": "Policy created successfully.", + "MESSAGE": "OK", + "METHOD": "POST", + "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/policymgnt/platform-policy", + "RETURN_CODE": 200 + }, + "test_image_policy_create_bulk_00036a": { + "DATA": "Internal server error.", + "MESSAGE": "NOK", + "METHOD": "POST", + "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/policymgnt/platform-policy", + "RETURN_CODE": 500 + }, + "test_image_policy_create_bulk_00037a": { + "RETURN_CODE": 200, + "METHOD": "GET", + "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/policymgnt/policies", + "MESSAGE": "OK", + "DATA": { + "status": "SUCCESS", + "lastOperDataObject": [ + { + "policyName": "KR5M", + "policyType": "PLATFORM", + "nxosVersion": "10.2.5_nxos64-cs_64bit", + "packageName": "", + "platform": "N9K/N3K", + "policyDescr": "KR5M", + "platformPolicies": "", + "epldImgName": "n9000-epld.10.2.5.M.img", + "rpmimages": "", + "imageName": "nxos64-cs.10.2.5.M.bin", + "agnostic": "false", + "role": "", + "fabricPolicyName": "", + "unInstall": "false", + "ref_count": 0, + "imagePresent": "Present" + }, + { + "policyName": "NR3F", + "policyType": "PLATFORM", + "nxosVersion": "10.3.1_nxos64-cs_64bit", + "packageName": "", + "platform": "N9K/N3K", + "policyDescr": "NR3F", + "platformPolicies": "", + "epldImgName": "n9000-epld.10.3.1.F.img", + "rpmimages": "", + "imageName": "nxos64-cs.10.3.1.F.bin", + "agnostic": "false", + "role": "", + "fabricPolicyName": "", + "unInstall": "false", + "ref_count": 0, + "imagePresent": "Present" + } + ], + "message": "" + } + }, + "test_image_policy_create_bulk_00037b": { + "DATA": "Policy created successfully.", + "MESSAGE": "OK", + "METHOD": "POST", + "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/policymgnt/platform-policy", + "RETURN_CODE": 200 + }, + "test_image_policy_create_bulk_00037c": { + "DATA": "Internal server error.", + "MESSAGE": "NOK", + "METHOD": "POST", + "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/policymgnt/platform-policy", + "RETURN_CODE": 500 + } +} \ No newline at end of file diff --git a/tests/unit/modules/dcnm/dcnm_image_policy/fixtures/responses_ImagePolicyDelete.json b/tests/unit/modules/dcnm/dcnm_image_policy/fixtures/responses_ImagePolicyDelete.json new file mode 100644 index 000000000..f5024082c --- /dev/null +++ b/tests/unit/modules/dcnm/dcnm_image_policy/fixtures/responses_ImagePolicyDelete.json @@ -0,0 +1,24 @@ +{ + "TEST_NOTES": [ + "Mocked responses for ImagePolicyDelete unit tests.", + "tests/unit/modules/dcnm/dcnm_image_policy/test_image_policy_delete.py" + ], + "test_image_policy_delete_00034a": { + "MESSAGE": "OK", + "METHOD": "DELETE", + "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/policymgnt/policy", + "RETURN_CODE": 200 + }, + "test_image_policy_delete_00037a": { + "MESSAGE": "OK", + "METHOD": "DELETE", + "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/policymgnt/policy", + "RETURN_CODE": 200 + }, + "test_image_policy_delete_00038a": { + "MESSAGE": "NOK", + "METHOD": "DELETE", + "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/policymgnt/policy", + "RETURN_CODE": 500 + } +} \ No newline at end of file diff --git a/tests/unit/modules/dcnm/dcnm_image_policy/fixtures/responses_ImagePolicyReplaceBulk.json b/tests/unit/modules/dcnm/dcnm_image_policy/fixtures/responses_ImagePolicyReplaceBulk.json new file mode 100644 index 000000000..595cb390b --- /dev/null +++ b/tests/unit/modules/dcnm/dcnm_image_policy/fixtures/responses_ImagePolicyReplaceBulk.json @@ -0,0 +1,81 @@ +{ + "TEST_NOTES": [ + "Mocked responses for ImagePolicyReplaceBulk unit tests.", + "tests/unit/modules/dcnm/dcnm_image_policy/test_image_policy_replace_bulk.py" + ], + "test_image_policy_replace_bulk_00035a": { + "MESSAGE": "OK", + "METHOD": "POST", + "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/policymgnt/edit-policy", + "RETURN_CODE": 200 + }, + "test_image_policy_replace_bulk_00036a": { + "DATA": "Internal server error.", + "MESSAGE": "NOK", + "METHOD": "POST", + "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/policymgnt/edit-policy", + "RETURN_CODE": 500 + }, + "test_image_policy_replace_bulk_00037a": { + "RETURN_CODE": 200, + "METHOD": "GET", + "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/policymgnt/policies", + "MESSAGE": "OK", + "DATA": { + "status": "SUCCESS", + "lastOperDataObject": [ + { + "policyName": "FOO", + "policyType": "PLATFORM", + "nxosVersion": "10.2.5_nxos64-cs_64bit", + "packageName": "", + "platform": "N9K/N3K", + "policyDescr": "FOO", + "platformPolicies": "", + "epldImgName": "n9000-epld.10.2.5.M.img", + "rpmimages": "", + "imageName": "nxos64-cs.10.2.5.M.bin", + "agnostic": "false", + "role": "", + "fabricPolicyName": "", + "unInstall": "false", + "ref_count": 0, + "imagePresent": "Present" + }, + { + "policyName": "BAR", + "policyType": "PLATFORM", + "nxosVersion": "10.3.1_nxos64-cs_64bit", + "packageName": "", + "platform": "N9K/N3K", + "policyDescr": "BAR", + "platformPolicies": "", + "epldImgName": "n9000-epld.10.3.1.F.img", + "rpmimages": "", + "imageName": "nxos64-cs.10.3.1.F.bin", + "agnostic": "false", + "role": "", + "fabricPolicyName": "", + "unInstall": "false", + "ref_count": 0, + "imagePresent": "Present" + } + ], + "message": "" + } + }, + "test_image_policy_replace_bulk_00037b": { + "DATA": "Policy edited successfully.", + "MESSAGE": "OK", + "METHOD": "POST", + "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/policymgnt/edit-policy", + "RETURN_CODE": 200 + }, + "test_image_policy_replace_bulk_00037c": { + "DATA": "Internal server error.", + "MESSAGE": "NOK", + "METHOD": "POST", + "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/policymgnt/edit-policy", + "RETURN_CODE": 500 + } +} \ No newline at end of file diff --git a/tests/unit/modules/dcnm/dcnm_image_policy/fixtures/responses_ImagePolicyUpdate.json b/tests/unit/modules/dcnm/dcnm_image_policy/fixtures/responses_ImagePolicyUpdate.json new file mode 100644 index 000000000..c97c29aab --- /dev/null +++ b/tests/unit/modules/dcnm/dcnm_image_policy/fixtures/responses_ImagePolicyUpdate.json @@ -0,0 +1,19 @@ +{ + "TEST_NOTES": [ + "Mocked responses for ImagePolicyUpdate unit tests.", + "tests/unit/modules/dcnm/dcnm_image_policy/test_image_policy_update.py" + ], + "test_image_policy_update_00035a": { + "MESSAGE": "OK", + "METHOD": "POST", + "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/policymgnt/edit-policy", + "RETURN_CODE": 200 + }, + "test_image_policy_update_00036a": { + "DATA": "Internal server error.", + "MESSAGE": "NOK", + "METHOD": "POST", + "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/policymgnt/edit-policy", + "RETURN_CODE": 500 + } +} \ No newline at end of file diff --git a/tests/unit/modules/dcnm/dcnm_image_policy/fixtures/responses_ImagePolicyUpdateBulk.json b/tests/unit/modules/dcnm/dcnm_image_policy/fixtures/responses_ImagePolicyUpdateBulk.json new file mode 100644 index 000000000..79cde1066 --- /dev/null +++ b/tests/unit/modules/dcnm/dcnm_image_policy/fixtures/responses_ImagePolicyUpdateBulk.json @@ -0,0 +1,81 @@ +{ + "TEST_NOTES": [ + "Mocked responses for ImagePolicyUpdateBulk unit tests.", + "tests/unit/modules/dcnm/dcnm_image_policy/test_image_policy_update_bulk.py" + ], + "test_image_policy_update_bulk_00035a": { + "MESSAGE": "OK", + "METHOD": "POST", + "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/policymgnt/edit-policy", + "RETURN_CODE": 200 + }, + "test_image_policy_update_bulk_00036a": { + "DATA": "Internal server error.", + "MESSAGE": "NOK", + "METHOD": "POST", + "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/policymgnt/edit-policy", + "RETURN_CODE": 500 + }, + "test_image_policy_update_bulk_00037a": { + "RETURN_CODE": 200, + "METHOD": "GET", + "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/policymgnt/policies", + "MESSAGE": "OK", + "DATA": { + "status": "SUCCESS", + "lastOperDataObject": [ + { + "policyName": "FOO", + "policyType": "PLATFORM", + "nxosVersion": "10.2.5_nxos64-cs_64bit", + "packageName": "", + "platform": "N9K/N3K", + "policyDescr": "FOO", + "platformPolicies": "", + "epldImgName": "n9000-epld.10.2.5.M.img", + "rpmimages": "", + "imageName": "nxos64-cs.10.2.5.M.bin", + "agnostic": "false", + "role": "", + "fabricPolicyName": "", + "unInstall": "false", + "ref_count": 0, + "imagePresent": "Present" + }, + { + "policyName": "BAR", + "policyType": "PLATFORM", + "nxosVersion": "10.3.1_nxos64-cs_64bit", + "packageName": "", + "platform": "N9K/N3K", + "policyDescr": "BAR", + "platformPolicies": "", + "epldImgName": "n9000-epld.10.3.1.F.img", + "rpmimages": "", + "imageName": "nxos64-cs.10.3.1.F.bin", + "agnostic": "false", + "role": "", + "fabricPolicyName": "", + "unInstall": "false", + "ref_count": 0, + "imagePresent": "Present" + } + ], + "message": "" + } + }, + "test_image_policy_update_bulk_00037b": { + "DATA": "Policy edited successfully.", + "MESSAGE": "OK", + "METHOD": "POST", + "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/policymgnt/edit-policy", + "RETURN_CODE": 200 + }, + "test_image_policy_update_bulk_00037c": { + "DATA": "Internal server error.", + "MESSAGE": "NOK", + "METHOD": "POST", + "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/policymgnt/edit-policy", + "RETURN_CODE": 500 + } +} \ No newline at end of file diff --git a/tests/unit/modules/dcnm/dcnm_image_policy/fixtures/result_current_RestSend.json b/tests/unit/modules/dcnm/dcnm_image_policy/fixtures/result_current_RestSend.json new file mode 100644 index 000000000..1de070597 --- /dev/null +++ b/tests/unit/modules/dcnm/dcnm_image_policy/fixtures/result_current_RestSend.json @@ -0,0 +1,30 @@ +{ + + "TEST_NOTES": [ + "Mocked result_current value." + ], + "test_image_policy_create_00035a": { + "changed": true, + "success": true + }, + "test_image_policy_create_bulk_00035a": { + "changed": true, + "success": true + }, + "test_image_policy_replace_bulk_00035a": { + "changed": true, + "success": true + }, + "test_image_policy_update_00035a": { + "changed": true, + "success": true + }, + "test_image_policy_update_00036a": { + "changed": false, + "success": false + }, + "test_image_policy_update_bulk_00035a": { + "changed": true, + "success": true + } +} \ No newline at end of file diff --git a/tests/unit/modules/dcnm/dcnm_image_policy/fixtures/results_ImagePolicies.json b/tests/unit/modules/dcnm/dcnm_image_policy/fixtures/results_ImagePolicies.json new file mode 100644 index 000000000..6e80b0648 --- /dev/null +++ b/tests/unit/modules/dcnm/dcnm_image_policy/fixtures/results_ImagePolicies.json @@ -0,0 +1,20 @@ +{ + + "TEST_NOTES": [ + "Mocked results used by tests/unit/dcnm_image_policy/utils.py MockImagePolicies.", + "These are used in the following unit tests.", + "tests/unit/modules/dcnm/dcnm_image_policy/test_image_policy_query.py" + ], + "test_image_policy_query_00031a": { + "changed": true, + "success": true + }, + "test_image_policy_query_00032a": { + "changed": true, + "success": true + }, + "test_image_policy_query_00038a": { + "changed": false, + "success": false + } +} \ No newline at end of file diff --git a/tests/unit/modules/dcnm/dcnm_image_policy/fixtures/results_ImagePolicyCommon.json b/tests/unit/modules/dcnm/dcnm_image_policy/fixtures/results_ImagePolicyCommon.json new file mode 100644 index 000000000..c35c40995 --- /dev/null +++ b/tests/unit/modules/dcnm/dcnm_image_policy/fixtures/results_ImagePolicyCommon.json @@ -0,0 +1,19 @@ +{ + "TEST_NOTES": [ + "Mocked results for ImagePolicyCommon unit tests.", + "tests/unit/modules/dcnm/dcnm_image_policy/test_image_policy_common.py" + ], + "test_image_policy_common_00090a": { + "changed": false, + "failed": true, + "diff": [ + {} + ], + "response": [ + {} + ], + "result": [ + {} + ] + } +} \ No newline at end of file diff --git a/tests/unit/modules/dcnm/dcnm_image_policy/fixtures/results_ImagePolicyCreateBulk.json b/tests/unit/modules/dcnm/dcnm_image_policy/fixtures/results_ImagePolicyCreateBulk.json new file mode 100644 index 000000000..64c1e8929 --- /dev/null +++ b/tests/unit/modules/dcnm/dcnm_image_policy/fixtures/results_ImagePolicyCreateBulk.json @@ -0,0 +1,18 @@ +{ + "TEST_NOTES": [ + "Mocked results for ImagePolicyCreateBulk unit tests.", + "tests/unit/modules/dcnm/dcnm_image_policy/test_image_policy_create_bulk.py" + ], + "test_image_policy_create_bulk_00037a": [ + { + "changed": true, + "success": true + } + ], + "test_image_policy_create_bulk_00037b": [ + { + "changed": false, + "success": false + } + ] +} \ No newline at end of file diff --git a/tests/unit/modules/dcnm/dcnm_image_policy/fixtures/results_ImagePolicyDelete.json b/tests/unit/modules/dcnm/dcnm_image_policy/fixtures/results_ImagePolicyDelete.json new file mode 100644 index 000000000..5e6de5f0f --- /dev/null +++ b/tests/unit/modules/dcnm/dcnm_image_policy/fixtures/results_ImagePolicyDelete.json @@ -0,0 +1,17 @@ +{ + + "TEST_NOTES": [ + "Mocked results for ImagePolicyDelete unit tests.", + "tests/unit/modules/dcnm/dcnm_image_policy/test_image_policy_delete.py" + ], + "test_image_policy_delete_00037a": { + "sequence_number": 1, + "changed": true, + "success": true + }, + "test_image_policy_delete_00038a": { + "sequence_number": 1, + "changed": false, + "success": false + } +} \ No newline at end of file diff --git a/tests/unit/modules/dcnm/dcnm_image_policy/fixtures/results_ImagePolicyReplaceBulk.json b/tests/unit/modules/dcnm/dcnm_image_policy/fixtures/results_ImagePolicyReplaceBulk.json new file mode 100644 index 000000000..d751acee3 --- /dev/null +++ b/tests/unit/modules/dcnm/dcnm_image_policy/fixtures/results_ImagePolicyReplaceBulk.json @@ -0,0 +1,18 @@ +{ + "TEST_NOTES": [ + "Mocked results for ImagePolicyReplaceBulk unit tests.", + "tests/unit/modules/dcnm/dcnm_image_policy/test_image_policy_replace_bulk.py" + ], + "test_image_policy_replace_bulk_00037a": [ + { + "changed": true, + "success": true + } + ], + "test_image_policy_replace_bulk_00037b": [ + { + "changed": false, + "success": false + } + ] +} \ No newline at end of file diff --git a/tests/unit/modules/dcnm/dcnm_image_policy/fixtures/results_ImagePolicyTaskResult.json b/tests/unit/modules/dcnm/dcnm_image_policy/fixtures/results_ImagePolicyTaskResult.json new file mode 100644 index 000000000..6b56dd915 --- /dev/null +++ b/tests/unit/modules/dcnm/dcnm_image_policy/fixtures/results_ImagePolicyTaskResult.json @@ -0,0 +1,110 @@ +{ + + "TEST_NOTES": [ + "Mocked results for ImagePolicyTaskResult unit tests.", + "tests/unit/modules/dcnm/dcnm_image_policy/test_image_policy_task_result.py" + ], + "test_image_policy_task_result_00030a": { + "changed": false, + "failed": true, + "diff": { + "diff_deleted": [], + "diff_merged": [], + "diff_overridden": [], + "diff_query": [], + "diff_replaced": [] + }, + "response": { + "response_deleted": [], + "response_merged": [], + "response_overridden": [], + "response_query": [], + "response_replaced": [] + } + }, + "test_image_policy_task_result_00040a": { + "changed": true, + "diff": { + "deleted": [{"foo": "bar"}], + "merged": [], + "overridden": [], + "query": [], + "replaced": [] + }, + "response": { + "deleted": [{"foo": "bar"}], + "merged": [], + "overridden": [], + "query": [], + "replaced": [] + } + }, + "test_image_policy_task_result_00040b": { + "changed": true, + "diff": { + "deleted": [], + "merged": [{"foo": "bar"}], + "overridden": [], + "query": [], + "replaced": [] + }, + "response": { + "deleted": [], + "merged": [{"foo": "bar"}], + "overridden": [], + "query": [], + "replaced": [] + } + }, + "test_image_policy_task_result_00040c": { + "changed": true, + "diff": { + "deleted": [], + "merged": [], + "overridden": [{"foo": "bar"}], + "query": [], + "replaced": [] + }, + "response": { + "deleted": [], + "merged": [], + "overridden": [{"foo": "bar"}], + "query": [], + "replaced": [] + } + }, + "test_image_policy_task_result_00040d": { + "changed": false, + "diff": { + "deleted": [], + "merged": [], + "overridden": [], + "query": [{"foo": "bar"}], + "replaced": [] + }, + "response": { + "deleted": [], + "merged": [], + "overridden": [], + "query": [{"foo": "bar"}], + "replaced": [] + } + }, + "test_image_policy_task_result_00040e": { + "changed": true, + "diff": { + "deleted": [], + "merged": [], + "overridden": [], + "query": [], + "replaced": [{"foo": "bar"}] + }, + "response": { + "deleted": [], + "merged": [], + "overridden": [], + "query": [], + "replaced": [{"foo": "bar"}] + } + } +} diff --git a/tests/unit/modules/dcnm/dcnm_image_policy/fixtures/results_ImagePolicyUpdate.json b/tests/unit/modules/dcnm/dcnm_image_policy/fixtures/results_ImagePolicyUpdate.json new file mode 100644 index 000000000..69bf2dba9 --- /dev/null +++ b/tests/unit/modules/dcnm/dcnm_image_policy/fixtures/results_ImagePolicyUpdate.json @@ -0,0 +1,6 @@ +{ + "TEST_NOTES": [ + "Mocked results for ImagePolicyUpdate unit tests.", + "tests/unit/modules/dcnm/dcnm_image_policy/test_image_policy_update.py" + ] +} \ No newline at end of file diff --git a/tests/unit/modules/dcnm/dcnm_image_policy/fixtures/results_ImagePolicyUpdateBulk.json b/tests/unit/modules/dcnm/dcnm_image_policy/fixtures/results_ImagePolicyUpdateBulk.json new file mode 100644 index 000000000..758305ac4 --- /dev/null +++ b/tests/unit/modules/dcnm/dcnm_image_policy/fixtures/results_ImagePolicyUpdateBulk.json @@ -0,0 +1,18 @@ +{ + "TEST_NOTES": [ + "Mocked results for ImagePolicyUpdateBulk unit tests.", + "tests/unit/modules/dcnm/dcnm_image_policy/test_image_policy_update_bulk.py" + ], + "test_image_policy_update_bulk_00037a": [ + { + "changed": true, + "success": true + } + ], + "test_image_policy_update_bulk_00037b": [ + { + "changed": false, + "success": false + } + ] +} \ No newline at end of file diff --git a/tests/unit/modules/dcnm/dcnm_image_policy/test_image_policy_common.py b/tests/unit/modules/dcnm/dcnm_image_policy/test_image_policy_common.py new file mode 100644 index 000000000..d67f81ba3 --- /dev/null +++ b/tests/unit/modules/dcnm/dcnm_image_policy/test_image_policy_common.py @@ -0,0 +1,726 @@ +# Copyright (c) 2024 Cisco and/or its affiliates. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# See the following regarding *_fixture imports +# https://pylint.pycqa.org/en/latest/user_guide/messages/warning/redefined-outer-name.html +# Due to the above, we also need to disable unused-import +# Also, fixtures need to use *args to match the signature of the function they are mocking +# pylint: disable=unused-import +# pylint: disable=redefined-outer-name +# pylint: disable=protected-access +# pylint: disable=unused-argument +# pylint: disable=invalid-name + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +__copyright__ = "Copyright (c) 2024 Cisco and/or its affiliates." +__author__ = "Allen Robel" + +import pytest +from ansible_collections.ansible.netcommon.tests.unit.modules.utils import \ + AnsibleFailJson +from ansible_collections.cisco.dcnm.plugins.module_utils.common.results import \ + Results +from ansible_collections.cisco.dcnm.tests.unit.modules.dcnm.dcnm_image_policy.utils import ( + does_not_raise, image_policy_common_fixture, responses_image_policy_common, + results_image_policy_common) + + +def test_image_policy_common_00010(image_policy_common) -> None: + """ + Classes and Methods + - ImagePolicyCommon + - __init__() + + Summary + Verify that the class attributes are initialized to expected values + and that fail_json is not called. + + Test + - Class attributes are initialized to expected values + - fail_json is not called + """ + with does_not_raise(): + instance = image_policy_common + instance.results = Results() + assert instance.class_name == "ImagePolicyCommon" + assert len(instance.results.changed) == 0 + assert len(instance.results.failed) == 0 + assert instance.results.response == [] + assert instance.results.response_current == {"sequence_number": 0} + assert instance.results.result == [] + assert instance.results.result_current == {"sequence_number": 0} + assert instance.results.diff_current == {"sequence_number": 0} + assert instance.results.diff == [] + assert instance.results.response_data == [] + + +def test_image_policy_common_00020(image_policy_common) -> None: + """ + Classes and Methods + - ImagePolicyCommon + - __init__() + - _handle_response() + - _handle_get_response() + + Summary + Verify that _handle_response() calls the appropriate methods when verb == GET + and response is successful (RETURN_CODE == 200) and that a proper result is + returned. + + Setup + - verb is set to GET + - response RETURN_CODE == 200 + - response MESSAGE == "OK" + + Test + - _handle_response() calls _handle_response_get() + - _handle_response_get() returns a proper result + - fail_json is not called + """ + key = "test_image_policy_common_00020a" + verb = "GET" + + with does_not_raise(): + instance = image_policy_common + result = instance._handle_response(responses_image_policy_common(key), verb) + assert result == {"success": True, "found": True} + + +def test_image_policy_common_00021(image_policy_common) -> None: + """ + Classes and Methods + - ImagePolicyCommon + - __init__() + - _handle_response() + - _handle_get_response() + + Summary + Verify that _handle_response() returns a proper result when verb == GET + and response is unsuccessful (RETURN_CODE == 404 and MESSAGE == "Not Found"). + + Setup + - verb is set to GET + - response RETURN_CODE == 404 + - response MESSAGE == "Not Found" + + Test + - _handle_response() calls _handle_response_get() + - _handle_response_get() returns a proper result + - fail_json is not called + """ + key = "test_image_policy_common_00021a" + verb = "GET" + + with does_not_raise(): + instance = image_policy_common + result = instance._handle_response(responses_image_policy_common(key), verb) + assert result == {"success": True, "found": False} + + +def test_image_policy_common_00022(image_policy_common) -> None: + """ + Classes and Methods + - ImagePolicyCommon + - __init__() + - _handle_response() + - _handle_get_response() + + Summary + Verify that _handle_response() returns a proper result when verb == GET + and response is unsuccessful (RETURN_CODE == 500 and MESSAGE == "Internal Server Error"). + + Setup + - verb is set to GET + - response RETURN_CODE == 500 + - response MESSAGE == "Internal Server Error" + + Test + - _handle_response() calls _handle_response_get() + - _handle_response_get() returns a proper result + - fail_json is not called + """ + key = "test_image_policy_common_00022a" + verb = "GET" + + with does_not_raise(): + instance = image_policy_common + result = instance._handle_response(responses_image_policy_common(key), verb) + assert result == {"success": False, "found": False} + + +def test_image_policy_common_00023(image_policy_common) -> None: + """ + Classes and Methods + - ImagePolicyCommon + - __init__() + - _handle_response() + - _handle_unknown_request_verbs() + + Summary + Verify that _handle_response() calls _handle_unknown_request_verbs() when verb + is unknown and that _handle_unknown_request_verbs() calls fail_json. + + Setup + - verb is set to FOOBAR + + Test + - _handle_response() calls _handle_unknown_request_verbs() + - _handle_unknown_request_verbs() calls fail_json + - instance.result is unchanged from initialized value + """ + key = "test_image_policy_common_00023a" + verb = "FOOBAR" + match = r"ImagePolicyCommon\._handle_unknown_request_verbs: Unknown request verb \(FOOBAR\)" + with does_not_raise(): + instance = image_policy_common + instance.results = Results() + with pytest.raises(AnsibleFailJson, match=match): + instance._handle_response(responses_image_policy_common(key), verb) + assert instance.results.result == [] + + +@pytest.mark.parametrize("verb", ["POST", "PUT", "DELETE"]) +def test_image_policy_common_00030(image_policy_common, verb) -> None: + """ + Classes and Methods + - ImagePolicyCommon + - __init__() + - _handle_response() + - _handle_post_put_delete_response() + + Summary + Verify that _handle_response() calls the appropriate methods when verb == POST + and response is successful (MESSAGE = "OK") and that a proper result is + returned. + + Setup + - verb == POST + - response MESSAGE == "OK" + + Test + - _handle_response() calls _handle_post_put_delete_response() + - _handle_post_put_delete_response() returns a proper result + - fail_json is not called + + Discussion + RESULT_CODE is not checked or used in the code, so it is not tested. + """ + key = "test_image_policy_common_00030a" + + with does_not_raise(): + instance = image_policy_common + result = instance._handle_response(responses_image_policy_common(key), verb) + assert result == {"success": True, "changed": True} + + +@pytest.mark.parametrize("verb", ["POST", "PUT", "DELETE"]) +def test_image_policy_common_00031(image_policy_common, verb) -> None: + """ + Classes and Methods + - ImagePolicyCommon + - __init__() + - _handle_response() + - _handle_post_put_delete_response() + + Summary + Verify that _handle_response() calls the appropriate methods when verb == POST + and response is unsuccessful (MESSAGE != "OK") and that a proper result is + returned. + + Setup + - verb == POST + - response MESSAGE == "NOK" + + Test + - _handle_response() calls _handle_post_put_delete_response() + - _handle_post_put_delete_response() returns a proper result + - fail_json is not called + + Discussion + RESULT_CODE is not checked or used in the code, so it is not tested. + """ + key = "test_image_policy_common_00031a" + + with does_not_raise(): + instance = image_policy_common + result = instance._handle_response(responses_image_policy_common(key), verb) + assert result == {"success": False, "changed": False} + + +@pytest.mark.parametrize("verb", ["POST", "PUT", "DELETE"]) +def test_image_policy_common_00032(image_policy_common, verb) -> None: + """ + Classes and Methods + - ImagePolicyCommon + - __init__() + - _handle_response() + - _handle_post_put_delete_response() + + Summary + Verify that _handle_response() calls the appropriate methods when verb == POST + and response is unsuccessful (ERROR key is present) and that a proper result is + returned. + + Setup + - verb == POST + - response ERROR == "Oh no!" + + Test + - _handle_response() calls _handle_post_put_delete_response() + - _handle_post_put_delete_response() returns a proper result + - fail_json is not called + + Discussion + RESULT_CODE is not checked or used in the code, so it is not tested. + """ + key = "test_image_policy_common_00032a" + + with does_not_raise(): + instance = image_policy_common + result = instance._handle_response(responses_image_policy_common(key), verb) + assert result == {"success": False, "changed": False} + + +@pytest.mark.parametrize( + "arg, return_value", + [ + (True, True), + (False, False), + ("True", True), + ("False", False), + ("true", True), + ("false", False), + (1, 1), + ("tru", "tru"), + ("fals", "fals"), + (None, None), + ({"foo"}, {"foo"}), + ], +) +def test_image_policy_common_00040(image_policy_common, arg, return_value) -> None: + """ + Classes and Methods + - ImagePolicyCommon + - __init__() + - make_boolean() + + Summary + Verify that make_boolean() returns expected values for various inputs. + + Test + - make_boolean() returns expected values + - fail_json is not called + """ + with does_not_raise(): + instance = image_policy_common + value = instance.make_boolean(arg) + assert value == return_value + + +@pytest.mark.parametrize( + "arg, return_value", + [ + ("", None), + ("none", None), + ("None", None), + ("NONE", None), + ("null", None), + ("Null", None), + ("NULL", None), + (None, None), + ("False", "False"), + ("true", "true"), + (1, 1), + ({"foo"}, {"foo"}), + (True, True), + (False, False), + ], +) +def test_image_policy_common_00050(image_policy_common, arg, return_value) -> None: + """ + Classes and Methods + - ImagePolicyCommon + - __init__() + - make_none() + + Summary + Verify that make_none() returns expected values for various inputs. + + Test + - make_none() returns expected values + - fail_json is not called + """ + with does_not_raise(): + instance = image_policy_common + value = instance.make_none(arg) + assert value == return_value + + +MATCH_00060 = r"Results\.changed: instance\.changed must be a bool\." + + +@pytest.mark.parametrize( + "arg, expected, flag", + [ + (True, does_not_raise(), True), + (False, does_not_raise(), True), + (None, pytest.raises(ValueError, match=MATCH_00060), False), + ("FOO", pytest.raises(ValueError, match=MATCH_00060), False), + ], +) +def test_image_policy_common_00060(image_policy_common, arg, expected, flag) -> None: + """ + Classes and Methods + - ImagePolicyCommon + - __init__() + - instance.results.changed getter/setter + + Summary + Verify that instance.changed returns expected values and + calls fail_json appropriately. + + Test + - instance.results.changed returns expected values + - fail_json is called when unexpected values are passed + - fail_json is not called when expected values are passed + """ + with does_not_raise(): + instance = image_policy_common + instance.results = Results() + with expected: + instance.results.changed = arg + if flag is True: + assert arg in instance.results.changed + else: + assert len(instance.results.changed) == 0 + + +MATCH_00070 = r"Results\.diff: instance\.diff must be a dict\." + + +@pytest.mark.parametrize( + "arg, return_value, expected, flag", + [ + ({}, [{"sequence_number": 0}], does_not_raise(), True), + ( + {"foo": "bar"}, + [{"foo": "bar", "sequence_number": 0}], + does_not_raise(), + True, + ), + (None, None, pytest.raises(ValueError, match=MATCH_00070), False), + ("FOO", None, pytest.raises(ValueError, match=MATCH_00070), False), + ], +) +def test_image_policy_common_00070( + image_policy_common, arg, return_value, expected, flag +) -> None: + """ + Classes and Methods + - ImagePolicyCommon + - __init__() + - @diff getter/setter + + Summary + Verify that instance.diff returns expected values and + calls fail_json appropriately. + + Test + - @diff returns expected values + - fail_json is called when unexpected values are passed + - fail_json is not called when expected values are passed + """ + with does_not_raise(): + instance = image_policy_common + instance.results = Results() + with expected: + instance.results.diff = arg + if flag is True: + assert instance.results.diff == return_value + else: + assert instance.results.diff == [] + + +MATCH_00080 = r"Results\.failed: instance\.failed must be a bool\." + + +@pytest.mark.parametrize( + "arg, expected, flag", + [ + (True, does_not_raise(), True), + (False, does_not_raise(), True), + (None, pytest.raises(ValueError, match=MATCH_00080), False), + ("FOO", pytest.raises(ValueError, match=MATCH_00080), False), + ], +) +def test_image_policy_common_00080(image_policy_common, arg, expected, flag) -> None: + """ + Classes and Methods + - ImagePolicyCommon + - __init__() + - @failed getter/setter + + Summary + Verify that instance.failed returns expected values and + calls fail_json appropriately. + + Test + - @failed returns expected values + - fail_json is called when unexpected values are passed + - fail_json is not called when expected values are passed + """ + with does_not_raise(): + instance = image_policy_common + instance.results = Results() + with expected: + instance.results.failed = arg + if flag is True: + assert arg in instance.results.failed + else: + assert True in instance.results.failed + + +def test_image_policy_common_00090(image_policy_common) -> None: + """ + Classes and Methods + - ImagePolicyCommon + - __init__() + - @failed_result getter + + Summary + Verify that failed_result returns expected value. + + Test + - @failed_result returns expected value + - fail_json is not called + """ + key = "test_image_policy_common_00090a" + with does_not_raise(): + instance = image_policy_common + instance.results = Results() + value = instance.results.failed_result + assert value == results_image_policy_common(key) + + +MATCH_00100 = r"Results\.response_current: instance\.response_current must be a dict\." + + +@pytest.mark.parametrize( + "arg, return_value, expected, flag", + [ + ({}, {"sequence_number": 0}, does_not_raise(), True), + ({"foo": "bar"}, {"foo": "bar", "sequence_number": 0}, does_not_raise(), True), + (None, None, pytest.raises(ValueError, match=MATCH_00100), False), + ("FOO", None, pytest.raises(ValueError, match=MATCH_00100), False), + ], +) +def test_image_policy_common_00100( + image_policy_common, arg, return_value, expected, flag +) -> None: + """ + Classes and Methods + - ImagePolicyCommon + - __init__() + - @response_current getter/setter + + Summary + Verify that instance.results.response_current returns expected values and + raises ValueError appropriately. + + Test + - instance.results.response_current returns expected values + - ValueError is raised when unexpected values are passed + - ValueError is not raised when expected values are passed + """ + with does_not_raise(): + instance = image_policy_common + instance.results = Results() + with expected: + instance.results.response_current = arg + if flag is True: + assert instance.results.response_current == return_value + else: + assert instance.results.response_current == {"sequence_number": 0} + + +MATCH_00110 = r"Results\.response: instance\.response must be a dict\." + + +@pytest.mark.parametrize( + "arg, return_value, expected, flag", + [ + ({}, [{"sequence_number": 0}], does_not_raise(), True), + ( + {"foo": "bar"}, + [{"foo": "bar", "sequence_number": 0}], + does_not_raise(), + True, + ), + (None, None, pytest.raises(ValueError, match=MATCH_00110), False), + ("FOO", None, pytest.raises(ValueError, match=MATCH_00110), False), + ], +) +def test_image_policy_common_00110( + image_policy_common, arg, return_value, expected, flag +) -> None: + """ + Classes and Methods + - ImagePolicyCommon + - __init__() + - @response getter/setter + + Summary + Verify that instance.results.response returns expected values and + raises ValueError appropriately. + + Test + - instance.results.response returns expected value + - ValueError is raised when unexpected values are passed + - ValueError is not raised when expected values are passed + """ + with does_not_raise(): + instance = image_policy_common + instance.results = Results() + with expected: + instance.results.response = arg + if flag is True: + assert instance.results.response == return_value + else: + assert instance.results.response == [] + + +@pytest.mark.parametrize( + "arg, return_value", + [ + ({}, [{}]), + ({"foo": "bar"}, [{"foo": "bar"}]), + (None, [None]), + ("FOO", ["FOO"]), + (1, [1]), + (True, [True]), + (False, [False]), + ([], [[]]), + ([1, 2, 3], [[1, 2, 3]]), + ], +) +def test_image_policy_common_00120(image_policy_common, arg, return_value) -> None: + """ + Classes and Methods + - ImagePolicyCommon + - __init__() + - @results + + Summary + Verify that instance.results.response_data returns expected values and + never calls fail_json. + + Test + - instance.results.response_datea returns expected values + - fail_json is not called + """ + with does_not_raise(): + instance = image_policy_common + instance.results = Results() + instance.results.response_data = arg + assert instance.results.response_data == return_value + + +MATCH_00130 = r"Results\.result: instance\.result must be a dict\." + + +@pytest.mark.parametrize( + "arg, return_value, expected, flag", + [ + ({}, [{"sequence_number": 0}], does_not_raise(), True), + ( + {"foo": "bar"}, + [{"foo": "bar", "sequence_number": 0}], + does_not_raise(), + True, + ), + (None, None, pytest.raises(ValueError, match=MATCH_00130), False), + ("FOO", None, pytest.raises(ValueError, match=MATCH_00130), False), + ], +) +def test_image_policy_common_00130( + image_policy_common, arg, return_value, expected, flag +) -> None: + """ + Classes and Methods + - ImagePolicyCommon + - __init__() + - @result getter/setter + + Summary + Verify that instance.results.result returns expected values and + raises ValueError appropriately. + + Test + - instance.results.result returns expected values + - ValueError is raised when unexpected values are passed + - ValueError is not raised when expected values are passed + """ + with does_not_raise(): + instance = image_policy_common + instance.results = Results() + with expected: + instance.results.result = arg + if flag is True: + assert instance.results.result == return_value + else: + assert instance.results.result == [] + + +MATCH_00140 = r"Results\.result_current: instance\.result_current must be a dict\." + + +@pytest.mark.parametrize( + "arg, return_value, expected, flag", + [ + ({}, {"sequence_number": 0}, does_not_raise(), True), + ({"foo": "bar"}, {"foo": "bar", "sequence_number": 0}, does_not_raise(), True), + (None, None, pytest.raises(ValueError, match=MATCH_00140), False), + ("FOO", None, pytest.raises(ValueError, match=MATCH_00140), False), + ], +) +def test_image_policy_common_00140( + image_policy_common, arg, return_value, expected, flag +) -> None: + """ + Classes and Methods + - ImagePolicyCommon + - __init__() + - instance.results.result_current getter/setter + + Summary + Verify that instance.result_current returns expected values and + calls fail_json appropriately. + + Test + - instance.results.result_current returns expected values + - ValueError is raised when unexpected values are passed + - ValueError is not raised when expected values are passed + """ + with does_not_raise(): + instance = image_policy_common + instance.results = Results() + with expected: + instance.results.result_current = arg + if flag is True: + assert instance.results.result_current == return_value + else: + assert instance.results.result_current == {"sequence_number": 0} diff --git a/tests/unit/modules/dcnm/dcnm_image_policy/test_image_policy_create.py b/tests/unit/modules/dcnm/dcnm_image_policy/test_image_policy_create.py new file mode 100644 index 000000000..804cb9546 --- /dev/null +++ b/tests/unit/modules/dcnm/dcnm_image_policy/test_image_policy_create.py @@ -0,0 +1,380 @@ +# Copyright (c) 2024 Cisco and/or its affiliates. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# See the following regarding *_fixture imports +# https://pylint.pycqa.org/en/latest/user_guide/messages/warning/redefined-outer-name.html +# Due to the above, we also need to disable unused-import +# Also, fixtures need to use *args to match the signature of the function they are mocking +# pylint: disable=unused-import +# pylint: disable=redefined-outer-name +# pylint: disable=protected-access +# pylint: disable=unused-argument +# pylint: disable=invalid-name + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +__copyright__ = "Copyright (c) 2024 Cisco and/or its affiliates." +__author__ = "Allen Robel" + +from typing import Any, Dict + +import pytest +from ansible_collections.ansible.netcommon.tests.unit.modules.utils import \ + AnsibleFailJson +from ansible_collections.cisco.dcnm.plugins.module_utils.common.results import \ + Results +from ansible_collections.cisco.dcnm.plugins.module_utils.image_policy.endpoints import \ + ApiEndpoints +from ansible_collections.cisco.dcnm.tests.unit.modules.dcnm.dcnm_image_policy.utils import ( + MockImagePolicies, does_not_raise, image_policy_create_fixture, + payloads_image_policy_create, responses_image_policy_create, + rest_send_result_current) + + +def test_image_policy_create_00010(image_policy_create) -> None: + """ + Classes and Methods + - ImagePolicyCreateCommon + - __init__ + - ImagePolicyCreate + - __init__ + + Summary + Verify that __init__() sets class attributes to the expected values. + + Test + - Class attributes initialized to expected values + - fail_json is not called + """ + with does_not_raise(): + instance = image_policy_create + assert instance.class_name == "ImagePolicyCreate" + assert instance.action == "create" + assert instance.state == "merged" + assert instance.check_mode is False + assert isinstance(instance.endpoints, ApiEndpoints) + assert instance.path == ApiEndpoints().policy_create["path"] + assert instance.verb == ApiEndpoints().policy_create["verb"] + assert instance._mandatory_payload_keys == { + "nxosVersion", + "policyName", + "policyType", + } + assert instance.payload is None + assert instance._payloads_to_commit == [] + + +def test_image_policy_create_00020(image_policy_create) -> None: + """ + Classes and Methods + - ImagePolicyCreateCommon + - __init__ + - ImagePolicyCreate + - __init__ + - payload setter + + Summary + Verify that the payloads setter sets the payloads attribute + to the expected value. + + Test + - payload is set to expected value + - fail_json is not called + """ + key = "test_image_policy_create_00020a" + + with does_not_raise(): + instance = image_policy_create + instance.payload = payloads_image_policy_create(key) + assert instance.payload == payloads_image_policy_create(key) + + +def test_image_policy_create_00021(image_policy_create) -> None: + """ + Classes and Methods + - ImagePolicyCreateCommon + - __init__ + - ImagePolicyCreate + - __init__ + - payload setter + + Summary + Verify that the payload setter calls fail_json when payload is not a dict + + Setup + - payload is set to a list + + Test + - fail_json is called because payload is not a dict + """ + key = "test_image_policy_create_00021a" + match = "ImagePolicyCreate._verify_payload: " + match += "payload must be a dict. Got type list" + + with does_not_raise(): + instance = image_policy_create + instance.results = Results() + with pytest.raises(AnsibleFailJson, match=match): + instance.payload = payloads_image_policy_create(key) + assert instance.payload is None + + +@pytest.mark.parametrize( + "key, match", + [ + ("test_image_policy_create_00022a", "nxosVersion"), + ("test_image_policy_create_00022b", "policyName"), + ("test_image_policy_create_00022c", "policyType"), + ], +) +def test_image_policy_create_00022(image_policy_create, key, match) -> None: + """ + Classes and Methods + - ImagePolicyCreateCommon + - __init__ + - ImagePolicyCreate + - __init__ + - payload setter + + Summary + Verify that the payload setter calls fail_json when a payload is missing + a mandatory key + + Test + - fail_json is called because payload is missing a mandatory key + - instance.payload is not modified, hence it retains its initial value of None + """ + with does_not_raise(): + instance = image_policy_create + instance.results = Results() + with pytest.raises(AnsibleFailJson, match=match): + instance.payload = payloads_image_policy_create(key) + assert instance.payload is None + + +def test_image_policy_create_00030(monkeypatch, image_policy_create) -> None: + """ + Classes and Methods + - ImagePolicyCreateCommon + - __init__() + - _build_payloads_to_commit() + - ImagePolicyCreate + - __init__() + - payload setter + + Summary + Verify behavior when the user sends an image create payload for an + image policy that already exists on the controller. + + Setup + - ImagePolicies().all_policies, called from instance._build_payloads_to_commit(), + is mocked to indicate that two image policies (KR5M, NR3F) exist on the + controller. + - ImagePolicyCreate().payload is set to contain one payload (KR5M) + that is present in all_policies. + + Test + - payloads_to_commit will an empty list because the payload in + instance.payload exists on the controller. + """ + key = "test_image_policy_create_00030a" + + with does_not_raise(): + instance = image_policy_create + instance.results = Results() + instance.payload = payloads_image_policy_create(key) + monkeypatch.setattr(instance, "_image_policies", MockImagePolicies(key)) + instance._build_payloads_to_commit() + assert instance._payloads_to_commit == [] + + +def test_image_policy_create_00031(monkeypatch, image_policy_create) -> None: + """ + Classes and Methods + - ImagePolicyCreateCommon + - __init__() + - _build_payloads_to_commit() + - ImagePolicyCreate + - __init__() + - payload setter + + Summary + Verify that instance._build_payloads_to_commit() adds a payload to the + payloads_to_commit list when a request is made to create an image policy + that does not exist on the controller. + + Setup + - ImagePolicies().all_policies, called from instance._build_payloads_to_commit(), + is mocked to indicate that two image policies (KR5M, NR3F) exist on the + controller. + - ImagePolicyCreate().payload is set to contain one payload containing + an image policy (FOO) that is not present in all_policies. + + Test + - _payloads_to_commit will equal list(instance.payload) since none of the + image policies in instance.payloads exist on the controller. + """ + key = "test_image_policy_create_00031a" + + with does_not_raise(): + instance = image_policy_create + instance.results = Results() + instance.payload = payloads_image_policy_create(key) + monkeypatch.setattr(instance, "_image_policies", MockImagePolicies(key)) + instance._build_payloads_to_commit() + assert len(instance._payloads_to_commit) == 1 + assert instance._payloads_to_commit == [payloads_image_policy_create(key)] + + +def test_image_policy_create_00033(image_policy_create) -> None: + """ + Classes and Methods + - ImagePolicyCreate + - commit() + - fail_json + + Summary + Verify that ImagePolicyCreate.commit() calls fail_json when + payload is None. + + Setup + - ImagePolicyCreate().payload is not set + + Test + - fail_json is called because payload is None + """ + with does_not_raise(): + instance = image_policy_create + instance.results = Results() + + match = "ImagePolicyCreate.commit: payload must be set prior to calling commit." + with pytest.raises(AnsibleFailJson, match=match): + instance.commit() + + +def test_image_policy_create_00034(monkeypatch, image_policy_create) -> None: + """ + Classes and Methods + - ImagePolicyCreateCommon + - __init__() + - _build_payloads_to_commit() + - ImagePolicyCreate + - __init__() + - payload setter + - commit() + + Summary + Verify that ImagePolicyCreate.commit() works as expected when the image policy + already exists on the controller. This is similar to test_image_policy_create_00030 + but tests that the commit method returns when _payloads_to_commit is empty. + + Setup + - ImagePolicies().all_policies, called from instance._build_payloads_to_commit(), + is mocked to indicate that two image policies (KR5M, NR3F) exist on the + controller. + - ImagePolicyCreate().payload is set to contain one payload (KR5M) + that is present in all_policies. + + Test + - payloads_to_commit will an empty list because all payloads in + instance.payloads exist on the controller. + - commit will return without calling _send_payloads + - fail_json is not called + """ + key = "test_image_policy_create_00034a" + + with does_not_raise(): + instance = image_policy_create + instance.results = Results() + instance.payload = payloads_image_policy_create(key) + monkeypatch.setattr(instance, "_image_policies", MockImagePolicies(key)) + instance.commit() + assert instance._payloads_to_commit == [] + + +def test_image_policy_create_00035(monkeypatch, image_policy_create) -> None: + """ + Classes and Methods + - ImagePolicyCreateCommon + - _build_payloads_to_commit() + - _send_payloads() + - ImagePolicyCreate + - payload setter + - commit() + + Summary + Verify that ImagePolicyCreate.commit() behaves as expected when the + controller responds to an image policy create request with a 200 response. + + Setup + - ImagePolicies().all_policies, called from instance._build_payloads_to_commit(), + is mocked to indicate that no policies exist on the controller. + - ImagePolicyCreate().payload is set to contain one payload that + contains an image policy (FOO) which does not exist on the controller. + - dcnm_send is mocked to return a successful (200) response. + + Test + - commit calls _build_payloads_to_commit which returns one payload. + - commit calls _send_payloads, which calls rest_send, which populates + diff_current with the payload due to result_current indicating + success. + - results.result_current is set to the expected value + - results.diff_current is set to the expected value + - results.response_current is set to the expected value + - results.action is set to "create" + """ + key = "test_image_policy_create_00035a" + + def mock_dcnm_send(*args, **kwargs): + return responses_image_policy_create(key) + + PATCH_DCNM_SEND = "ansible_collections.cisco.dcnm.plugins." + PATCH_DCNM_SEND += "module_utils.common.rest_send.dcnm_send" + + monkeypatch.setattr(PATCH_DCNM_SEND, mock_dcnm_send) + + with does_not_raise(): + instance = image_policy_create + instance.results = Results() + + monkeypatch.setattr(instance, "_image_policies", MockImagePolicies(key)) + + with does_not_raise(): + instance.payload = payloads_image_policy_create(key) + instance.commit() + + response_current = responses_image_policy_create(key) + response_current["sequence_number"] = 1 + + result_current = rest_send_result_current(key) + result_current["sequence_number"] = 1 + + payload = payloads_image_policy_create(key) + payload["sequence_number"] = 1 + + assert instance.results.action == "create" + assert instance.rest_send.result_current == rest_send_result_current(key) + assert instance.results.result_current == result_current + assert instance.results.response_current == response_current + assert instance.results.diff_current == payload + assert False in instance.results.failed + assert True not in instance.results.failed + assert False not in instance.results.changed + assert True in instance.results.changed + assert len(instance.results.metadata) == 1 + assert instance.results.metadata[0]["action"] == "create" + assert instance.results.metadata[0]["state"] == "merged" + assert instance.results.metadata[0]["sequence_number"] == 1 diff --git a/tests/unit/modules/dcnm/dcnm_image_policy/test_image_policy_create_bulk.py b/tests/unit/modules/dcnm/dcnm_image_policy/test_image_policy_create_bulk.py new file mode 100644 index 000000000..5e2c0d277 --- /dev/null +++ b/tests/unit/modules/dcnm/dcnm_image_policy/test_image_policy_create_bulk.py @@ -0,0 +1,534 @@ +# Copyright (c) 2024 Cisco and/or its affiliates. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# See the following regarding *_fixture imports +# https://pylint.pycqa.org/en/latest/user_guide/messages/warning/redefined-outer-name.html +# Due to the above, we also need to disable unused-import +# Also, fixtures need to use *args to match the signature of the function they are mocking +# pylint: disable=unused-import +# pylint: disable=redefined-outer-name +# pylint: disable=protected-access +# pylint: disable=unused-argument +# pylint: disable=invalid-name + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +__copyright__ = "Copyright (c) 2024 Cisco and/or its affiliates." +__author__ = "Allen Robel" + +from typing import Any, Dict + +import pytest +from ansible_collections.ansible.netcommon.tests.unit.modules.utils import \ + AnsibleFailJson +from ansible_collections.cisco.dcnm.plugins.module_utils.common.results import \ + Results +from ansible_collections.cisco.dcnm.plugins.module_utils.image_policy.endpoints import \ + ApiEndpoints +from ansible_collections.cisco.dcnm.tests.unit.modules.dcnm.dcnm_image_policy.utils import ( + GenerateResponses, MockImagePolicies, does_not_raise, + image_policies_all_policies, image_policy_create_bulk_fixture, + payloads_image_policy_create_bulk, responses_image_policy_create_bulk, + rest_send_result_current, results_image_policy_create_bulk) + + +def test_image_policy_create_bulk_00010(image_policy_create_bulk) -> None: + """ + Classes and Methods + - ImagePolicyCreateCommon + - __init__() + - ImagePolicyCreateBulk + - __init__() + + Summary + Verify that __init__() sets class attributes to the expected values. + + Test + - Class attributes are initialized to expected values + - fail_json is not called + """ + with does_not_raise(): + instance = image_policy_create_bulk + assert instance.class_name == "ImagePolicyCreateBulk" + assert instance.action == "create" + assert instance.state == "merged" + assert instance.check_mode is False + assert isinstance(instance.endpoints, ApiEndpoints) + assert instance.path == ApiEndpoints().policy_create["path"] + assert instance.verb == ApiEndpoints().policy_create["verb"] + assert instance._mandatory_payload_keys == { + "nxosVersion", + "policyName", + "policyType", + } + assert instance.payloads is None + assert instance._payloads_to_commit == [] + + +def test_image_policy_create_bulk_00020(image_policy_create_bulk) -> None: + """ + Classes and Methods + - ImagePolicyCreateCommon + - __init__() + - payloads setter + - ImagePolicyCreateBulk + - __init__() + + Summary + Verify that the payloads setter sets the payloads attribute + to the expected value. + + Test + - payloads is set to expected value + - fail_json is not called + """ + key = "test_image_policy_create_bulk_00020a" + + with does_not_raise(): + instance = image_policy_create_bulk + instance.payloads = payloads_image_policy_create_bulk(key) + assert instance.payloads == payloads_image_policy_create_bulk(key) + + +def test_image_policy_create_bulk_00021(image_policy_create_bulk) -> None: + """ + Classes and Methods + - ImagePolicyCreateCommon + - __init__() + - payloads setter + - ImagePolicyCreateBulk + - __init__() + + Summary + Verify that the payloads setter calls fail_json when payloads is not a list of dict + + Test + - fail_json is called because payloads is not a list of dict + - instance.payloads is not modified, hence it retains its initial value of None + """ + key = "test_image_policy_create_bulk_00021a" + match = "ImagePolicyCreateBulk.payloads: " + match += "payloads must be a list of dict. got dict for value" + + with does_not_raise(): + instance = image_policy_create_bulk + instance.results = Results() + with pytest.raises(AnsibleFailJson, match=match): + instance.payloads = payloads_image_policy_create_bulk(key) + assert instance.payloads is None + + +@pytest.mark.parametrize( + "key, match", + [ + ("test_image_policy_create_bulk_00022a", "nxosVersion"), + ("test_image_policy_create_bulk_00022b", "policyName"), + ("test_image_policy_create_bulk_00022c", "policyType"), + ], +) +def test_image_policy_create_bulk_00022(image_policy_create_bulk, key, match) -> None: + """ + Classes and Methods + - ImagePolicyCreateCommon + - __init__() + - payloads setter + - ImagePolicyCreateBulk + - __init__() + + Summary + Verify that the payloads setter calls fail_json when a payload in the payloads list + is missing a mandatory key + + Test + - fail_json is called because a payload in the payloads list is missing a mandatory key + - instance.payloads is not modified, hence it retains its initial value of None + """ + with does_not_raise(): + instance = image_policy_create_bulk + instance.results = Results() + with pytest.raises(AnsibleFailJson, match=match): + instance.payloads = payloads_image_policy_create_bulk(key) + assert instance.payloads is None + + +def test_image_policy_create_bulk_00030(monkeypatch, image_policy_create_bulk) -> None: + """ + Classes and Methods + - ImagePolicyCreateCommon + - __init__() + - payloads setter + - _build_payloads_to_commit() + - ImagePolicyCreateBulk + - __init__() + + Summary + Verify behavior when the user sends an image create payload for an + image policy that already exists on the controller. + + Setup + - ImagePolicies().all_policies, called from instance._build_payloads_to_commit(), + is mocked to indicate that two image policies (KR5M, NR3F) exist on the + controller. + - ImagePolicyCreateCommon().payloads is set to contain one payload (KR5M) + that is present in all_policies. + + Test + - payloads_to_commit will an empty list because all payloads in + instance.payloads exist on the controller. + """ + key = "test_image_policy_create_bulk_00030a" + + instance = image_policy_create_bulk + instance.results = Results() + instance.payloads = payloads_image_policy_create_bulk(key) + monkeypatch.setattr(instance, "_image_policies", MockImagePolicies(key)) + instance._build_payloads_to_commit() + assert instance._payloads_to_commit == [] + assert len(instance.results.failed) == 0 + assert len(instance.results.changed) == 0 + + +def test_image_policy_create_bulk_00031(monkeypatch, image_policy_create_bulk) -> None: + """ + Classes and Methods + - ImagePolicyCreateCommon + - __init__() + - payloads setter + - _build_payloads_to_commit() + - ImagePolicyCreateBulk + - __init__() + + Summary + Verify that instance._build_payloads_to_commit() adds a payload to the + payloads_to_commit list when a request is made to create an image policy + that does not exist on the controller. + + Setup + - ImagePolicies().all_policies, called from instance._build_payloads_to_commit(), + is mocked to indicate that two image policies (KR5M, NR3F) exist on the + controller. + - ImagePolicyCreateCommon().payloads is set to contain one payload containing + an image policy (FOO) that is not present in all_policies. + + Test + - _payloads_to_commit will equal instance.payloads since none of the + image policies in instance.payloads exist on the controller. + """ + key = "test_image_policy_create_bulk_00031a" + + with does_not_raise(): + instance = image_policy_create_bulk + instance.results = Results() + instance.payloads = payloads_image_policy_create_bulk(key) + monkeypatch.setattr(instance, "_image_policies", MockImagePolicies(key)) + instance._build_payloads_to_commit() + assert len(instance._payloads_to_commit) == 1 + assert instance._payloads_to_commit == payloads_image_policy_create_bulk(key) + + +def test_image_policy_create_bulk_00032(monkeypatch, image_policy_create_bulk) -> None: + """ + Classes and Methods + - ImagePolicyCreateCommon + - payloads setter + - _build_payloads_to_commit() + - ImagePolicyCreateBulk + - __init__() + + Setup + - ImagePolicies().all_policies, called from instance._build_payloads_to_commit(), + is mocked to indicate that two image policies (KR5M, NR3F) exist on the + controller. + - ImagePolicyCreateCommon().payloads is set to contain one payload containing + an image policy (FOO) that is not present in all_policies and one payload + containing an image policy (KR5M) that does exist on the controller. + + Test + - _payloads_to_commit will contain one payload + - The policyName for this payload will be "FOO", which is the image policy that + does not exist on the controller + """ + key = "test_image_policy_create_bulk_00032a" + + instance = image_policy_create_bulk + instance.payloads = payloads_image_policy_create_bulk(key) + monkeypatch.setattr(instance, "_image_policies", MockImagePolicies(key)) + instance._build_payloads_to_commit() + assert len(instance._payloads_to_commit) == 1 + assert instance._payloads_to_commit[0]["policyName"] == "FOO" + + +def test_image_policy_create_bulk_00033(image_policy_create_bulk) -> None: + """ + Classes and Methods + - ImagePolicyCreateBulk + - commit() + - fail_json + + Summary + Verify that ImagePolicyCreateBulk.commit() calls fail_json when + payloads is None. + + Setup + - ImagePolicyCreateCommon().payloads is not set + + Test + - fail_json is called because payloads is None + """ + with does_not_raise(): + results = Results() + instance = image_policy_create_bulk + instance.results = results + + match = ( + "ImagePolicyCreateBulk.commit: payloads must be set prior to calling commit." + ) + with pytest.raises(AnsibleFailJson, match=match): + instance.commit() + + +def test_image_policy_create_bulk_00034(monkeypatch, image_policy_create_bulk) -> None: + """ + Classes and Methods + - ImagePolicyCreateCommon + - payloads setter + - ImagePolicyCreateBulk + - commit() + + Setup + - ImagePolicyCreateCommon().payloads is set to an empty list + + Test + - ImagePolicyCreateBulk().commit returns without doing anything + """ + key = "test_image_policy_create_bulk_00034a" + + with does_not_raise(): + instance = image_policy_create_bulk + instance.payloads = [] + + monkeypatch.setattr(instance, "_image_policies", MockImagePolicies(key)) + + with does_not_raise(): + instance.commit() + + +def test_image_policy_create_bulk_00035(monkeypatch, image_policy_create_bulk) -> None: + """ + Classes and Methods + - ImagePolicyCreateCommon + - _build_payloads_to_commit() + - _send_payloads() + - ImagePolicyCreateBulk + - payloads setter + - commit() + + Summary + Verify that ImagePolicyCreateBulk.commit() behaves as expected when the + controller responds to an image create request with a 200 response. + + Setup + - ImagePolicies().all_policies, called from instance._build_payloads_to_commit(), + is mocked to indicate that no policies exist on the controller. + - ImagePolicyCreateCommon().payloads is set to contain one payload that + contains an image policy (FOO) which does not exist on the controller. + - RestSend.dcnm_send is mocked to return a successful (200) response. + + Test + - commit calls _build_payloads_to_commit which returns one payload. + - commit calls _send_payloads, which calls rest_send, which populates + diff_current with the payload due to result_current indicating + success. + - results.result_current is set to the expected value + - results.diff_current is set to the expected value + - results.response_current is set to the expected value + - results.action is set to "create" + """ + key = "test_image_policy_create_bulk_00035a" + + def mock_dcnm_send(*args, **kwargs): + return responses_image_policy_create_bulk(key) + + PATCH_DCNM_SEND = "ansible_collections.cisco.dcnm.plugins." + PATCH_DCNM_SEND += "module_utils.common.rest_send.dcnm_send" + + monkeypatch.setattr(PATCH_DCNM_SEND, mock_dcnm_send) + + with does_not_raise(): + instance = image_policy_create_bulk + instance.results = Results() + + monkeypatch.setattr(instance, "_image_policies", MockImagePolicies(key)) + + with does_not_raise(): + instance.payloads = payloads_image_policy_create_bulk(key) + instance.commit() + + response_current = responses_image_policy_create_bulk(key) + response_current["sequence_number"] = 1 + + result_current = rest_send_result_current(key) + result_current["sequence_number"] = 1 + + payload = payloads_image_policy_create_bulk(key)[0] + payload["sequence_number"] = 1 + + assert instance.results.action == "create" + assert instance.rest_send.result_current == rest_send_result_current(key) + assert instance.results.result_current == result_current + assert instance.results.response_current == response_current + assert instance.results.diff_current == payload + assert False in instance.results.failed + assert True not in instance.results.failed + assert False not in instance.results.changed + assert True in instance.results.changed + assert len(instance.results.metadata) == 1 + assert instance.results.metadata[0]["action"] == "create" + assert instance.results.metadata[0]["state"] == "merged" + assert instance.results.metadata[0]["sequence_number"] == 1 + + +def test_image_policy_create_bulk_00036(monkeypatch, image_policy_create_bulk) -> None: + """ + Classes and Methods + - ImagePolicyCreateCommon + - payloads setter + - _build_payloads_to_commit() + - _send_payloads() + - ImagePolicyCreateBulk + - commit() + + Summary + Verify behavior when the controller returns a 500 response to an + image policy create request + + Setup + - ImagePolicies().all_policies, called from instance._build_payloads_to_commit(), + is mocked to indicate that no policies exist on the controller. + - ImagePolicyCreateBulk().payloads is set to contain one payload that + contains an image policy (FOO) which does not exist on the controller. + - dcnm_send is mocked to return a failure (500) response. + + Test + - A sequence_number key is added to instance.results.response_current + - instance.results.diff_current is set to a dict with only + the key "sequence_number", since no changes were made + - instance.results.failed set() contains True and does not contain False + - instance.results.changed set() contains False and does not contain True + - instance.results.metadata contains one dict + - The value of instance.results.metadata "action" is "create" + - The value of instance.results.metadata "state" is "merged" + - The value of instance.results.metadata "sequence_number" is 1 + """ + key = "test_image_policy_create_bulk_00036a" + + PATCH_DCNM_SEND = "ansible_collections.cisco.dcnm.plugins." + PATCH_DCNM_SEND += "module_utils.common.rest_send.dcnm_send" + + def mock_dcnm_send(*args, **kwargs): + return responses_image_policy_create_bulk(key) + + with does_not_raise(): + instance = image_policy_create_bulk + instance.rest_send.unit_test = True + instance.results = Results() + instance.payloads = payloads_image_policy_create_bulk(key) + + monkeypatch.setattr(instance, "_image_policies", MockImagePolicies(key)) + monkeypatch.setattr(PATCH_DCNM_SEND, mock_dcnm_send) + + with does_not_raise(): + instance.commit() + + response_current = responses_image_policy_create_bulk(key) + response_current["sequence_number"] = 1 + assert instance.results.response_current == response_current + assert instance.results.diff_current == {"sequence_number": 1} + assert True in instance.results.failed + assert False not in instance.results.failed + assert True not in instance.results.changed + assert False in instance.results.changed + assert len(instance.results.metadata) == 1 + assert len(instance.results.diff) == 1 + assert instance.results.diff[0] == {"sequence_number": 1} + assert instance.results.metadata[0]["action"] == "create" + assert instance.results.metadata[0]["state"] == "merged" + assert instance.results.metadata[0]["sequence_number"] == 1 + + +def test_image_policy_create_bulk_00037(monkeypatch, image_policy_create_bulk) -> None: + """ + Classes and Methods + - ImagePolicyCreateCommon + - _process_responses() + - ImagePolicyCreateBulk + - __init__() + + Summary + Simulate a succussful response from the controller, followed by a bad response + from the controller during policy create. + + Setup + - instance.payloads is set to contain two payloads + + Test + - Both successful and bad responses are recorded with separate sequence_numbers. + - instance.results.failed will be a set() containing both True and False + - instance.results.changed will be a set() containing both True and False + - instance.results.response contains two responses + - instance.results.result contains two results + - instance.results.diff contains two diffs + """ + PATCH_DCNM_SEND = "ansible_collections.cisco.dcnm.plugins." + PATCH_DCNM_SEND += "module_utils.common.rest_send.dcnm_send" + + key_policies = "test_image_policy_create_bulk_00037a" + key_ok = "test_image_policy_create_bulk_00037b" + key_nok = "test_image_policy_create_bulk_00037c" + key_payloads = "test_image_policy_create_bulk_00037d" + + def responses(): + yield responses_image_policy_create_bulk(key_policies) + yield responses_image_policy_create_bulk(key_ok) + yield responses_image_policy_create_bulk(key_nok) + + gen = GenerateResponses(responses()) + + def mock_dcnm_send(*args, **kwargs): + item = gen.next + return item + + with does_not_raise(): + instance = image_policy_create_bulk + instance.rest_send.unit_test = True + instance.results = Results() + instance.payloads = payloads_image_policy_create_bulk(key_payloads) + + monkeypatch.setattr(PATCH_DCNM_SEND, mock_dcnm_send) + + with does_not_raise(): + instance.commit() + + assert len(instance.results.diff) == 2 + assert len(instance.results.result) == 2 + assert len(instance.results.response) == 2 + assert len(instance.results.metadata) == 2 + assert instance.results.response[0]["RETURN_CODE"] == 200 + assert instance.results.response[1]["RETURN_CODE"] == 500 + assert False in instance.results.changed + assert True in instance.results.changed + assert False in instance.results.failed + assert True in instance.results.failed diff --git a/tests/unit/modules/dcnm/dcnm_image_policy/test_image_policy_delete.py b/tests/unit/modules/dcnm/dcnm_image_policy/test_image_policy_delete.py new file mode 100644 index 000000000..a0d369ccc --- /dev/null +++ b/tests/unit/modules/dcnm/dcnm_image_policy/test_image_policy_delete.py @@ -0,0 +1,469 @@ +# Copyright (c) 2024 Cisco and/or its affiliates. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# See the following regarding *_fixture imports +# https://pylint.pycqa.org/en/latest/user_guide/messages/warning/redefined-outer-name.html +# Due to the above, we also need to disable unused-import +# Also, fixtures need to use *args to match the signature of the function they are mocking +# pylint: disable=unused-import +# pylint: disable=redefined-outer-name +# pylint: disable=protected-access +# pylint: disable=unused-argument +# pylint: disable=invalid-name + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +__copyright__ = "Copyright (c) 2024 Cisco and/or its affiliates." +__author__ = "Allen Robel" + +import pytest +from ansible_collections.ansible.netcommon.tests.unit.modules.utils import \ + AnsibleFailJson +from ansible_collections.cisco.dcnm.plugins.module_utils.common.results import \ + Results +from ansible_collections.cisco.dcnm.plugins.module_utils.image_policy.endpoints import \ + ApiEndpoints +from ansible_collections.cisco.dcnm.tests.unit.modules.dcnm.dcnm_image_policy.utils import ( + MockImagePolicies, does_not_raise, image_policy_delete_fixture, + responses_image_policy_delete, results_image_policy_delete) + + +def test_image_policy_delete_00010(image_policy_delete) -> None: + """ + Classes and Methods + - ImagePolicyCommon + - __init__() + - ImagePolicyDelete + - __init__() + + Summary + Verify that the class attributes are initialized to expected values + and that fail_json is not called. + + Test + - Class attributes are initialized to expected values + - fail_json is not called + """ + with does_not_raise(): + instance = image_policy_delete + assert instance.class_name == "ImagePolicyDelete" + assert instance.action == "delete" + assert instance.state == "deleted" + assert isinstance(instance.endpoints, ApiEndpoints) + assert instance.verb == "DELETE" + assert ( + instance.path + == "/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/policymgnt/policy" + ) + assert instance.policy_names is None + + +def test_image_policy_delete_00020(image_policy_delete) -> None: + """ + Classes and Methods + - ImagePolicyCommon + - __init__() + - ImagePolicyDelete + - __init__() + - policy_names setter + + Summary + policy_names is set correctly to a list of strings. + Verify that instance.policy_names is set to the expected value + and that fail_json is not called. + + Test + - policy_names is set to expected value + - fail_json is not called + """ + policy_names = ["FOO", "BAR"] + with does_not_raise(): + instance = image_policy_delete + instance.policy_names = policy_names + assert instance.policy_names == policy_names + + +def test_image_policy_delete_00021(image_policy_delete) -> None: + """ + Classes and Methods + - ImagePolicyCommon + - __init__() + - ImagePolicyDelete + - __init__() + - policy_names setter + + Summary + policy_names should be a list of strings, but it set to a string. + Verify that fail_json is called with appropriate message. + + Test + - fail_json is called because policy_names is not a list + - instance.policy_names is not modified, hence it retains its initial value of None + """ + match = "ImagePolicyDelete.policy_names: " + match += "policy_names must be a list." + + with does_not_raise(): + instance = image_policy_delete + with pytest.raises(AnsibleFailJson, match=match): + instance.policy_names = "NOT_A_LIST" + assert instance.policy_names is None + + +def test_image_policy_delete_00022(image_policy_delete) -> None: + """ + Classes and Methods + - ImagePolicyCommon + - __init__() + - ImagePolicyDelete + - __init__() + - policy_names setter + + Summary + policy_names is set to a list of non-strings. + Verify that fail_json is called with appropriate message. + + Test + - fail_json is called because policy_names is a list with a non-string element + - instance.policy_names is not modified, hence it retains its initial value of None + """ + match = "ImagePolicyDelete.policy_names: " + match += "policy_names must be a list of strings." + + with does_not_raise(): + instance = image_policy_delete + with pytest.raises(AnsibleFailJson, match=match): + instance.policy_names = [1, 2, 3] + assert instance.policy_names is None + + +def test_image_policy_delete_00030(monkeypatch, image_policy_delete) -> None: + """ + Classes and Methods + - ImagePolicyCommon + - __init__() + - _verify_image_policy_ref_count() + - ImagePolicyDelete + - __init__() + - policy_names setter + - _get_policies_to_delete() + + Summary + The requested policy to delete does not exist on the controller. + Verify that instance._policies_to_delete is an empty list. + + Setup + - ImagePolicies().all_policies, is mocked to indicate that two image policies + (KR5M, NR3F) exist on the controller. + - ImagePolicyDelete.policy_names is set to contain one policy_name (FOO) + that does not exist on the controller. + + Test + - instance._policies_to_delete will an empty list because all of the + policy_names in instance.policy_names do not exist on the controller + and, hence, nothing needs to be deleted. + """ + key = "test_image_policy_delete_00030a" + + instance = image_policy_delete + instance.policy_names = ["FOO"] + monkeypatch.setattr(instance, "_image_policies", MockImagePolicies(key)) + instance._get_policies_to_delete() + assert instance._policies_to_delete == [] + + +def test_image_policy_delete_00031(monkeypatch, image_policy_delete) -> None: + """ + Classes and Methods + - ImagePolicyDelete + - __init__() + - policy_names setter + - _get_policies_to_delete() + + Summary + One policy (KR5M) is requested to be deleted and it exists on the controller. + Verify that instance._policies_to_delete contains the policy name KR5M. + + Setup + - ImagePolicies().all_policies is mocked to indicate that two image policies + (KR5M, NR3F) exist on the controller. + - ImagePolicyDelete.policy_names is set to contain one policy_name (KR5M) + that exists on the controller. + + Test + - instance._policies_to_delete will contain one policy name (KR5M) + """ + key = "test_image_policy_delete_00031a" + + instance = image_policy_delete + instance.policy_names = ["KR5M"] + monkeypatch.setattr(instance, "_image_policies", MockImagePolicies(key)) + instance._get_policies_to_delete() + assert instance._policies_to_delete == ["KR5M"] + + +def test_image_policy_delete_00032(monkeypatch, image_policy_delete) -> None: + """ + Classes and Methods + - ImagePolicyDelete + - policy_names setter + - _get_policies_to_delete() + + Summary + Of two policies being requested to delete, one policy exists on the controller + and one policy does not exist on the controller. Verify that only the policy + that exists on the controller is added to instance._policies_to_delete. + + Setup + - ImagePolicies().all_policies, is mocked to indicate that two image policies + (KR5M, NR3F) exist on the controller. + - ImagePolicyDelete().policy_names is set to contain one image policy name (FOO) + that does not exist on the controller and one image policy name (KR5M) that + does exist on the controller. + + Test + - instance._policies_to_delete will contain one policy name (KR5M) + """ + key = "test_image_policy_delete_00032a" + + instance = image_policy_delete + instance.policy_names = ["FOO", "KR5M"] + monkeypatch.setattr(instance, "_image_policies", MockImagePolicies(key)) + instance._get_policies_to_delete() + assert instance._policies_to_delete == ["KR5M"] + + +def test_image_policy_delete_00033(image_policy_delete) -> None: + """ + Classes and Methods + - ImagePolicyDelete + - commit() + - fail_json + + Summary + commit() is called without first setting policy_names. + + Setup + - ImagePolicyDelete().policy_names is not set + + Test + - fail_json is called because policy_names is None + """ + with does_not_raise(): + instance = image_policy_delete + instance.results = Results() + + match = r"ImagePolicyDelete\._validate_commit_parameters: " + match += r"policy_names must be set prior to calling commit\." + with pytest.raises(AnsibleFailJson, match=match): + instance.commit() + + +def test_image_policy_delete_00034(monkeypatch, image_policy_delete) -> None: + """ + Classes and Methods + - ImagePolicyDelete + - policy_names setter + - commit() + + Summary + commit() is called with policy_names set to an empty list. + + Setup + - ImagePolicyDelete().policy_names is set to an empty list + - ImagePolicies.all_policies is mocked to indicate that no policies + exist on the controller. + - RestSend.dcnm_send is mocked to return a successful (200) response. + + Test + - ImagePolicyDelete().commit returns without doing anything + - fail_json is not called + - instance.results.changed set() contains False + - instance.results.failed set() contains False + """ + key = "test_image_policy_delete_00034a" + + def mock_dcnm_send(*args, **kwargs): + return responses_image_policy_delete(key) + + PATCH_DCNM_SEND = "ansible_collections.cisco.dcnm.plugins." + PATCH_DCNM_SEND += "module_utils.common.rest_send.dcnm_send" + + monkeypatch.setattr(PATCH_DCNM_SEND, mock_dcnm_send) + + with does_not_raise(): + instance = image_policy_delete + instance.results = Results() + instance.policy_names = [] + + monkeypatch.setattr(instance, "_image_policies", MockImagePolicies(key)) + with does_not_raise(): + instance.commit() + assert False in instance.results.changed + assert False in instance.results.failed + + +def test_image_policy_delete_00036(monkeypatch, image_policy_delete) -> None: + """ + Classes and Methods + - ImagePolicyDelete + - policy_names setter + - _get_policies_to_delete() + - commit() + + Summary + commit() is called with policy_names set to a policy_name that does not exist on the controller. + + Setup + - ImagePolicies().all_policies is mocked to indicate that no policies exist on the controller. + - ImagePolicyDelete().policy_names is set a policy_name that is not on the controller. + + Test + - ImagePolicyDelete()._get_policies_to_delete return an empty list + - ImagePolicyDelete().commit returns without doing anything + - instance.results.changed set() contains False + - instance.results.failed set() contains False + - fail_json is not called + """ + key = "test_image_policy_delete_00036a" + with does_not_raise(): + instance = image_policy_delete + instance.results = Results() + instance.policy_names = ["FOO"] + monkeypatch.setattr(instance, "_image_policies", MockImagePolicies(key)) + with does_not_raise(): + instance.commit() + assert len(instance._policies_to_delete) == 0 + assert False in instance.results.changed + assert False in instance.results.failed + + +def test_image_policy_delete_00037(monkeypatch, image_policy_delete) -> None: + """ + Classes and Methods + - ImagePolicyCommon: + - __init__() + - _handle_response() + - ImagePolicyDelete + - _get_policies_to_delete() + - policy_names setter + - commit() + + Summary + commit() is called with policy_names set to a policy_name that exists on + the controller, and the controller returns a success (200) response. + + Setup + - ImagePolicies().all_policies is mocked to indicate policy (KR5M) exists + on the controller. + - ImagePolicyDelete().policy_names is set to contain policy_name KR5M. + - dcnm_send is mocked to return a successful (200) response. + + Test + - fail_json is not called + - commit calls _get_policies_to_delete which returns a list containing policy_name (KR5M) + - commit calls the mocked dcnm_send, which populates instance.response_current + with a successful (200) response + - instance.result_current is populated by instance._handle_response() + - instance.result_current contains expected values + - instance.changed is set to True + - instance.diff contains expected values + """ + key = "test_image_policy_delete_00037a" + + PATCH_DCNM_SEND = "ansible_collections.cisco.dcnm.plugins." + PATCH_DCNM_SEND += "module_utils.common.rest_send.dcnm_send" + + def mock_dcnm_send(*args, **kwargs): + return responses_image_policy_delete(key) + + with does_not_raise(): + instance = image_policy_delete + instance.results = Results() + instance.policy_names = ["KR5M"] + + monkeypatch.setattr(instance, "_image_policies", MockImagePolicies(key)) + monkeypatch.setattr(PATCH_DCNM_SEND, mock_dcnm_send) + + with does_not_raise(): + instance.commit() + assert instance._policies_to_delete == ["KR5M"] + assert instance.results.result_current == results_image_policy_delete(key) + assert True in instance.results.changed + assert False in instance.results.failed + assert instance.results.diff == [{"policyNames": ["KR5M"], "sequence_number": 1}] + + +def test_image_policy_delete_00038(monkeypatch, image_policy_delete) -> None: + """ + Classes and Methods + - ImagePolicyCommon: + - __init__() + - _handle_response() + - ImagePolicyDelete + - _get_policies_to_delete() + - policy_names setter + - commit() + + Summary + commit() is called with policy_names set to a policy_name that exists on + the controller, and the controller returns a failure (500) response. + + Setup + - ImagePolicies().all_policies is mocked to indicate policy (KR5M) exists on + the controller. + - ImagePolicyDelete().policy_names is set to contain one payload (KR5M). + - dcnm_send is mocked to return a failure (500) response. + + Test + - fail_json is called + - commit calls _get_policies_to_delete which returns a list containing + policy_name (KR5M) + - commit calls the mocked dcnm_send, which populates + instance.response_current with a failure (500) response + - instance.result_current is populated by instance._handle_response() + - instance.result_current contains expected values + - instance.changed is set to False + - instance.diff is an empty list + """ + key = "test_image_policy_delete_00038a" + + def mock_dcnm_send(*args, **kwargs): + return responses_image_policy_delete(key) + + PATCH_DCNM_SEND = "ansible_collections.cisco.dcnm.plugins." + PATCH_DCNM_SEND += "module_utils.common.rest_send.dcnm_send" + + monkeypatch.setattr(PATCH_DCNM_SEND, mock_dcnm_send) + + with does_not_raise(): + instance = image_policy_delete + instance.rest_send.unit_test = True + instance.results = Results() + instance.policy_names = ["KR5M"] + + monkeypatch.setattr(instance, "_image_policies", MockImagePolicies(key)) + + # match = r"ImagePolicyDelete.commit: Bad response during policies delete\. " + # match += r"policy_names \['KR5M'\]\." + with does_not_raise(): + instance.commit() + + assert instance._policies_to_delete == ["KR5M"] + assert instance.results.result_current == results_image_policy_delete(key) + assert True in instance.results.failed + assert False in instance.results.changed + # assert instance.diff == [] diff --git a/tests/unit/modules/dcnm/dcnm_image_policy/test_image_policy_endpoints.py b/tests/unit/modules/dcnm/dcnm_image_policy/test_image_policy_endpoints.py new file mode 100644 index 000000000..aa9603dd7 --- /dev/null +++ b/tests/unit/modules/dcnm/dcnm_image_policy/test_image_policy_endpoints.py @@ -0,0 +1,110 @@ +# Copyright (c) 2024 Cisco and/or its affiliates. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +__copyright__ = "Copyright (c) 2024 Cisco and/or its affiliates." +__author__ = "Allen Robel" + +from ansible_collections.cisco.dcnm.plugins.module_utils.image_policy.endpoints import \ + ApiEndpoints + + +def test_image_policy_endpoints_00001() -> None: + """ + Endpoints.__init__ + """ + endpoints = ApiEndpoints() + assert endpoints.endpoint_api_v1 == "/appcenter/cisco/ndfc/api/v1" + assert ( + endpoints.endpoint_image_management + == "/appcenter/cisco/ndfc/api/v1/imagemanagement" + ) + assert ( + endpoints.endpoint_policy_mgnt + == "/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/policymgnt" + ) + + +def test_image_policy_endpoints_00010() -> None: + """ + Endpoints.policies_attached_info + """ + endpoints = ApiEndpoints() + assert endpoints.policies_attached_info.get("verb") == "GET" + assert ( + endpoints.policies_attached_info.get("path") + == "/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/policymgnt/all-attached-policies" + ) + + +def test_image_policy_endpoints_00020() -> None: + """ + Endpoints.policies_info + """ + endpoints = ApiEndpoints() + assert endpoints.policies_info.get("verb") == "GET" + assert ( + endpoints.policies_info.get("path") + == "/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/policymgnt/policies" + ) + + +def test_image_policy_endpoints_00030() -> None: + """ + Endpoints.policy_attach + """ + endpoints = ApiEndpoints() + assert endpoints.policy_attach.get("verb") == "POST" + assert ( + endpoints.policy_attach.get("path") + == "/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/policymgnt/attach-policy" + ) + + +def test_image_policy_endpoints_00040() -> None: + """ + Endpoints.policy_create + """ + endpoints = ApiEndpoints() + assert endpoints.policy_create.get("verb") == "POST" + assert ( + endpoints.policy_create.get("path") + == "/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/policymgnt/platform-policy" + ) + + +def test_image_policy_endpoints_00050() -> None: + """ + Endpoints.policy_detach + """ + endpoints = ApiEndpoints() + assert endpoints.policy_detach.get("verb") == "DELETE" + assert ( + endpoints.policy_detach.get("path") + == "/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/policymgnt/detach-policy" + ) + + +def test_image_policy_endpoints_00060() -> None: + """ + Endpoints.policy_info + """ + path = "/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/policymgnt/" + path += "image-policy/__POLICY_NAME__" + endpoints = ApiEndpoints() + assert endpoints.policy_info.get("verb") == "GET" + assert endpoints.policy_info.get("path") == path diff --git a/tests/unit/modules/dcnm/dcnm_image_policy/test_image_policy_payload.py b/tests/unit/modules/dcnm/dcnm_image_policy/test_image_policy_payload.py new file mode 100644 index 000000000..a17d209fe --- /dev/null +++ b/tests/unit/modules/dcnm/dcnm_image_policy/test_image_policy_payload.py @@ -0,0 +1,475 @@ +# Copyright (c) 2024 Cisco and/or its affiliates. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# See the following regarding *_fixture imports +# https://pylint.pycqa.org/en/latest/user_guide/messages/warning/redefined-outer-name.html +# Due to the above, we also need to disable unused-import +# pylint: disable=unused-import +# Some fixtures need to use *args to match the signature of the function they are mocking +# pylint: disable=unused-argument + +from __future__ import absolute_import, division, print_function + +import json + +import pytest + +__metaclass__ = type + +__copyright__ = "Copyright (c) 2024 Cisco and/or its affiliates." +__author__ = "Allen Robel" + + +from ansible_collections.cisco.dcnm.plugins.module_utils.common.results import \ + Results +from ansible_collections.cisco.dcnm.plugins.module_utils.image_policy.payload import ( + Config2Payload, Payload2Config) +from ansible_collections.cisco.dcnm.tests.unit.modules.dcnm.dcnm_image_policy.fixture import \ + load_fixture +from ansible_collections.cisco.dcnm.tests.unit.modules.dcnm.dcnm_image_policy.utils import ( + AnsibleFailJson, MockAnsibleModule, config2payload_fixture, does_not_raise, + payload2config_fixture) + + +def test_image_policy_payload_00110(config2payload: Config2Payload) -> None: + """ + Class + - Payload + - Config2Payload + Function + - __init__ + + Summary + Verify Config2Payload is initialized properly + + Test + - Class attributes initialized to expected values + - fail_json is not called + """ + with does_not_raise(): + instance = config2payload + assert instance.class_name == "Config2Payload" + assert isinstance(instance.properties, dict) + assert instance.properties.get("config") == {} + assert instance.properties.get("payload") == {} + + +def test_image_policy_payload_00120(config2payload: Config2Payload) -> None: + """ + Class + - Payload + - Config2Payload + Function + - commit + + Summary + Verify Config2Payload coverts a configuration to a proper payload. + + Test + - fail_json is not called + - commit converts config to a proper payload + """ + key = "test_image_policy_payload_00120a" + data = load_fixture("data_payload") + + config = data.get(key, {}).get("config") + payload = data.get(key, {}).get("payload") + + print(f"config: {json.dumps(config, indent=4, sort_keys=True)}") + print(f"payload: {json.dumps(payload, indent=4, sort_keys=True)}") + + with does_not_raise(): + instance = config2payload + instance.config = config + instance.log.debug( + f"00120: config: {json.dumps(config, indent=4, sort_keys=True)}" + ) + instance.log.debug( + f"00120: payload: {json.dumps(payload, indent=4, sort_keys=True)}" + ) + instance.commit() + assert payload is not None + assert instance.payload == payload + + +def test_image_policy_payload_00121(config2payload: Config2Payload) -> None: + """ + Class + - Payload + - Config2Payload + Function + - commit + + Summary + Verify Config2Payload coverts a configuration to a proper payload when + the packages.install and packages.uninstall keys are empty lists. + + Test + - config packages.install is an empty list + - config packages.ininstall is an empty list + - commit converts config to a proper payload + """ + key = "test_image_policy_payload_00121a" + data = load_fixture("data_payload") + + config = data.get(key, {}).get("config") + payload = data.get(key, {}).get("payload") + with does_not_raise(): + ansible_module = MockAnsibleModule() + ansible_module.state = "merged" + instance = config2payload + instance.config = config + instance.commit() + assert payload is not None + assert instance.payload == payload + + +def test_image_policy_payload_00122(config2payload: Config2Payload) -> None: + """ + Class + - Payload + - Config2Payload + Function + - commit + + Summary + Verify Config2Payload.commit() calls fail_json when config is an empty dict + + Test + - config is set to an empty dict + - commit calls fail_json + """ + key = "test_image_policy_payload_00122a" + data = load_fixture("data_payload") + + config = data.get(key, {}).get("config") + + with does_not_raise(): + ansible_module = MockAnsibleModule() + ansible_module.state = "merged" + instance = config2payload + instance.results = Results() + instance.config = config + match = "Config2Payload.commit: config is empty" + with pytest.raises(AnsibleFailJson, match=match): + instance.commit() + + +@pytest.mark.parametrize("state", ["deleted", "query"]) +def test_image_policy_payload_00123(config2payload: Config2Payload, state) -> None: + """ + Class + - Payload + - Config2Payload + Function + - commit + + Summary + Verify Config2Payload.commit() behavior for Ansible states + "query" and "deleted". + + Test + - payload contains only the policyName key + - The value of the policyName key == value of the name key in instance.config + - fail_json is not called + """ + key = "test_image_policy_payload_00123a" + data = load_fixture("data_payload") + + config = data.get(key, {}).get("config") + with does_not_raise(): + ansible_module = MockAnsibleModule() + ansible_module.state = state + instance = config2payload + instance.config = config + instance.commit() + assert instance.payload == {"policyName": config["name"]} + + +MATCH_00130 = ( + r"Config2Payload.payload: payload must be a dictionary\. got .* for value .*" +) + + +@pytest.mark.parametrize( + "value, expected", + [ + ({}, does_not_raise()), + ([], pytest.raises(AnsibleFailJson, match=MATCH_00130)), + ((), pytest.raises(AnsibleFailJson, match=MATCH_00130)), + (None, pytest.raises(AnsibleFailJson, match=MATCH_00130)), + (1, pytest.raises(AnsibleFailJson, match=MATCH_00130)), + (1.1, pytest.raises(AnsibleFailJson, match=MATCH_00130)), + ("foo", pytest.raises(AnsibleFailJson, match=MATCH_00130)), + (True, pytest.raises(AnsibleFailJson, match=MATCH_00130)), + (False, pytest.raises(AnsibleFailJson, match=MATCH_00130)), + ], +) +def test_image_policy_payload_00130( + config2payload: Config2Payload, value, expected +) -> None: + """ + Class + - Payload + - Config2Payload + Function + - payload setter + + Summary + Verify payload setter error handling + + Test + - payload accepts a dictionary + - payload calls fail_json for non-dictionary values + """ + with does_not_raise(): + instance = config2payload + with expected: + instance.payload = value + + +MATCH_00140 = ( + r"Config2Payload.config: config must be a dictionary\. got .* for value .*" +) + + +@pytest.mark.parametrize( + "value, expected", + [ + ({}, does_not_raise()), + ([], pytest.raises(AnsibleFailJson, match=MATCH_00140)), + ((), pytest.raises(AnsibleFailJson, match=MATCH_00140)), + (None, pytest.raises(AnsibleFailJson, match=MATCH_00140)), + (1, pytest.raises(AnsibleFailJson, match=MATCH_00140)), + (1.1, pytest.raises(AnsibleFailJson, match=MATCH_00140)), + ("foo", pytest.raises(AnsibleFailJson, match=MATCH_00140)), + (True, pytest.raises(AnsibleFailJson, match=MATCH_00140)), + (False, pytest.raises(AnsibleFailJson, match=MATCH_00140)), + ], +) +def test_image_policy_payload_00140( + config2payload: Config2Payload, value, expected +) -> None: + """ + Class + - Payload + - Payload2Config + Function + - config setter + + Summary + Verify config setter error handling + + Test + - config accepts a dictionary + - config calls fail_json for non-dictionary values + """ + with does_not_raise(): + instance = config2payload + with expected: + instance.config = value + + +def test_image_policy_payload_00210(payload2config: Payload2Config) -> None: + """ + Class + - Payload + - Payload2Config + Function + - __init__ + + Summary + Verify Payload2Config is initialized properly + + Test + - fail_json is not called + - Class attributes initialized to expected values + """ + with does_not_raise(): + instance = payload2config + assert instance.class_name == "Payload2Config" + assert isinstance(instance.properties, dict) + assert instance.properties.get("config") == {} + assert instance.properties.get("payload") == {} + + +def test_image_policy_payload_00220(payload2config: Payload2Config) -> None: + """ + Class + - Payload + - Payload2Config + Function + - commit + + Summary + Verify Payload2Config coverts a payload to a proper configuration. + + Test + - fail_json is not called + - commit converts the payload to a proper config + """ + key = "test_image_policy_payload_00220a" + data = load_fixture("data_payload") + + config = data.get(key, {}).get("config") + payload = data.get(key, {}).get("payload") + with does_not_raise(): + instance = payload2config + instance.payload = payload + instance.commit() + assert config is not None + assert instance.config == config + + +def test_image_policy_payload_00221(payload2config: Payload2Config) -> None: + """ + Class + - Payload + - Payload2Config + Function + - commit + + Summary + Verify Payload2Config coverts a payload to a proper configuration when + the payload is missing the rpmimages and packageName keys. + + Test + - payload is missing rpmimages and packageName keys + - commit converts the payload to a proper config + - missing mandatory key "type" is added to the config + """ + key = "test_image_policy_payload_00221a" + data = load_fixture("data_payload") + + config = data.get(key, {}).get("config") + payload = data.get(key, {}).get("payload") + with does_not_raise(): + instance = payload2config + instance.payload = payload + instance.commit() + assert config is not None + assert instance.config == config + + +def test_image_policy_payload_00222(payload2config: Payload2Config) -> None: + """ + Class + - Payload + - Payload2Config + Function + - commit + + Summary + Verify Payload2Config.commit() calls fail_json when payload is an empty dict + + Test + - config is set to an empty dict + - commit calls fail_json + """ + key = "test_image_policy_payload_00222a" + data = load_fixture("data_payload") + + payload = data.get(key, {}).get("payload") + with does_not_raise(): + ansible_module = MockAnsibleModule() + ansible_module.state = "merged" + instance = payload2config + instance.payload = payload + match = "Payload2Config.commit: payload is empty" + with pytest.raises(AnsibleFailJson, match=match): + instance.commit() + + +MATCH_00230 = ( + r"Payload2Config.payload: payload must be a dictionary\. got .* for value .*" +) + + +@pytest.mark.parametrize( + "value, expected", + [ + ({}, does_not_raise()), + ([], pytest.raises(AnsibleFailJson, match=MATCH_00230)), + ((), pytest.raises(AnsibleFailJson, match=MATCH_00230)), + (None, pytest.raises(AnsibleFailJson, match=MATCH_00230)), + (1, pytest.raises(AnsibleFailJson, match=MATCH_00230)), + (1.1, pytest.raises(AnsibleFailJson, match=MATCH_00230)), + ("foo", pytest.raises(AnsibleFailJson, match=MATCH_00230)), + (True, pytest.raises(AnsibleFailJson, match=MATCH_00230)), + (False, pytest.raises(AnsibleFailJson, match=MATCH_00230)), + ], +) +def test_image_policy_payload_00230( + payload2config: Payload2Config, value, expected +) -> None: + """ + Class + - Payload + - Payload2Config + Function + - payload setter + + Summary + Verify payload setter error handling + + Test + - payload accepts a dictionary + - payload calls fail_json for non-dictionary values + """ + with does_not_raise(): + instance = payload2config + with expected: + instance.payload = value + + +MATCH_00240 = ( + r"Payload2Config.config: config must be a dictionary\. got .* for value .*" +) + + +@pytest.mark.parametrize( + "value, expected", + [ + ({}, does_not_raise()), + ([], pytest.raises(AnsibleFailJson, match=MATCH_00240)), + ((), pytest.raises(AnsibleFailJson, match=MATCH_00240)), + (None, pytest.raises(AnsibleFailJson, match=MATCH_00240)), + (1, pytest.raises(AnsibleFailJson, match=MATCH_00240)), + (1.1, pytest.raises(AnsibleFailJson, match=MATCH_00240)), + ("foo", pytest.raises(AnsibleFailJson, match=MATCH_00240)), + (True, pytest.raises(AnsibleFailJson, match=MATCH_00240)), + (False, pytest.raises(AnsibleFailJson, match=MATCH_00240)), + ], +) +def test_image_policy_payload_00240( + payload2config: Payload2Config, value, expected +) -> None: + """ + Class + - Payload + - Payload2Config + Function + - config setter + + Summary + Verify config setter error handling + + Test + - config accepts a dictionary + - config calls fail_json for non-dictionary values + """ + with does_not_raise(): + instance = payload2config + with expected: + instance.config = value diff --git a/tests/unit/modules/dcnm/dcnm_image_policy/test_image_policy_query.py b/tests/unit/modules/dcnm/dcnm_image_policy/test_image_policy_query.py new file mode 100644 index 000000000..3edbc9061 --- /dev/null +++ b/tests/unit/modules/dcnm/dcnm_image_policy/test_image_policy_query.py @@ -0,0 +1,439 @@ +# Copyright (c) 2024 Cisco and/or its affiliates. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# See the following regarding *_fixture imports +# https://pylint.pycqa.org/en/latest/user_guide/messages/warning/redefined-outer-name.html +# Due to the above, we also need to disable unused-import +# Also, fixtures need to use *args to match the signature of the function they are mocking +# pylint: disable=unused-import +# pylint: disable=redefined-outer-name +# pylint: disable=protected-access +# pylint: disable=unused-argument +# pylint: disable=invalid-name + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +__copyright__ = "Copyright (c) 2024 Cisco and/or its affiliates." +__author__ = "Allen Robel" + +import pytest +from ansible_collections.ansible.netcommon.tests.unit.modules.utils import \ + AnsibleFailJson +from ansible_collections.cisco.dcnm.plugins.module_utils.common.results import \ + Results +from ansible_collections.cisco.dcnm.plugins.module_utils.image_policy.image_policies import \ + ImagePolicies +from ansible_collections.cisco.dcnm.tests.unit.modules.dcnm.dcnm_image_policy.utils import ( + GenerateResponses, MockImagePolicies, does_not_raise, + image_policies_all_policies, image_policy_query_fixture, + rest_send_response_current) + + +def test_image_policy_query_00010(image_policy_query) -> None: + """ + Classes and Methods + - ImagePolicyCommon + - __init__() + - ImagePolicyQuery + - __init__() + + Test + - Class attributes are initialized to expected values + - fail_json is not called + """ + with does_not_raise(): + instance = image_policy_query + assert instance.class_name == "ImagePolicyQuery" + assert instance.action == "query" + assert instance.state == "query" + assert isinstance(instance._image_policies, ImagePolicies) + assert instance.policy_names is None + assert instance._policies_to_query == [] + + +def test_image_policy_query_00020(image_policy_query) -> None: + """ + Classes and Methods + - ImagePolicyCommon + - __init__() + - ImagePolicyQuery + - __init__() + - policy_names setter + + Test + - policy_names is set to expected value + - fail_json is not called + """ + policy_names = ["FOO", "BAR"] + with does_not_raise(): + instance = image_policy_query + instance.policy_names = policy_names + assert instance.policy_names == policy_names + + +def test_image_policy_query_00021(image_policy_query) -> None: + """ + Classes and Methods + - ImagePolicyCommon + - __init__() + - ImagePolicyQuery + - __init__() + - policy_names setter + + Test + - fail_json is called because policy_names is not a list + - instance.policy_names is not modified, hence it retains its initial value of None + """ + match = "ImagePolicyQuery.policy_names: " + match += "policy_names must be a list." + + with does_not_raise(): + instance = image_policy_query + with pytest.raises(AnsibleFailJson, match=match): + instance.policy_names = "NOT_A_LIST" + assert instance.policy_names is None + + +def test_image_policy_query_00022(image_policy_query) -> None: + """ + Classes and Methods + - ImagePolicyCommon + - __init__() + - ImagePolicyQuery + - __init__() + - policy_names setter + + Test + - fail_json is called because policy_names is a list with a non-string element + - instance.policy_names is not modified, hence it retains its initial value of None + """ + match = "ImagePolicyQuery.policy_names: " + match += "policy_names must be a list of strings." + + with does_not_raise(): + instance = image_policy_query + with pytest.raises(AnsibleFailJson, match=match): + instance.policy_names = [1, 2, 3] + assert instance.policy_names is None + + +def test_image_policy_query_00023(image_policy_query) -> None: + """ + Classes and Methods + - ImagePolicyCommon + - __init__() + - ImagePolicyQuery + - __init__() + - policy_names setter + + Summary + Verify behavior when policy_names is not set prior to calling commit + + Test + - fail_json is called because policy_names is not set prior to calling commit + - instance.policy_names is not modified, hence it retains its initial value of None + """ + match = "ImagePolicyQuery.commit: " + match += "policy_names must be set prior to calling commit." + + with does_not_raise(): + instance = image_policy_query + instance.results = Results() + with pytest.raises(AnsibleFailJson, match=match): + instance.commit() + assert instance.policy_names is None + + +def test_image_policy_query_00024(image_policy_query) -> None: + """ + Classes and Methods + - ImagePolicyQuery + - policy_names setter + + Summary + Verify behavior when policy_names is set to an empty list + + Setup + - ImagePolicyQuery().policy_names is set to an empty list + + Test + - fail_json is called from policy_names setter + """ + match = "ImagePolicyQuery.policy_names: policy_names must be a list of " + match += "at least one string." + with pytest.raises(AnsibleFailJson, match=match): + instance = image_policy_query + instance.policy_names = [] + + +def test_image_policy_query_00030(monkeypatch, image_policy_query) -> None: + """ + Classes and Methods + - ImagePolicyCommon + - __init__() + - _verify_image_policy_ref_count() + - ImagePolicyQuery + - __init__() + - policy_names setter + - _get_policies_to_query() + - commit() + + Summary + Verify behavior when user queries a policy that does not exist on the controller + + Setup + - ImagePolicies().all_policies, is mocked to indicate that one image policy + (KR5M) exist on the controller. + - ImagePolicyQuery.policy_names is set to contain one policy_name (FOO) + that does not exist on the controller. + + Test + - ImagePolicyQuery.commit() calls _get_policies_to_query() which sets + instance._policies_to_query to an empty list. + - instance.results.changed set() contains False + - instance.results.failed set() contains False + - commit() returns without doing anything else + - fail_json is not called + """ + key = "test_image_policy_query_00030a" + + PATCH_DCNM_SEND = "ansible_collections.cisco.dcnm.plugins." + PATCH_DCNM_SEND += "module_utils.common.rest_send.dcnm_send" + + def responses(): + yield rest_send_response_current(key) + + gen = GenerateResponses(responses()) + + def mock_dcnm_send(*args, **kwargs): + item = gen.next + return item + + with does_not_raise(): + instance = image_policy_query + instance.results = Results() + instance.policy_names = ["FOO"] + monkeypatch.setattr(PATCH_DCNM_SEND, mock_dcnm_send) + instance._image_policies.results = Results() + with does_not_raise(): + instance.commit() + assert isinstance(instance.results.diff, list) + assert isinstance(instance.results.result, list) + assert isinstance(instance.results.response, list) + assert len(instance.results.diff) == 1 + assert len(instance.results.result) == 1 + assert len(instance.results.response) == 1 + assert instance.results.diff[0].get("policyName", None) is None + assert instance.results.diff[0].get("sequence_number", None) == 1 + assert False in instance.results.failed + assert True not in instance.results.failed + assert False in instance.results.changed + assert True not in instance.results.changed + + +def test_image_policy_query_00031(monkeypatch, image_policy_query) -> None: + """ + Classes and Methods + - ImagePolicyCommon + - __init__() + - ImagePolicyQuery + - __init__() + - policy_names setter + - _get_policies_to_query() + - commit() + + Summary + Verify behavior when user queries a policy that exists on the controller + + Setup + - ImagePolicies().all_policies is mocked to indicate that one image policy + (KR5M) exists on the controller. + - ImagePolicyQuery.policy_names is set to contain one policy_name (KR5M) + that exists on the controller. + + Test + - instance.diff is a list containing one dict with keys action == "query" + and policyName == "KR5M" + - instance.response is a list with one element + - instance.response_current is a dict with key RETURN_CODE == 200 + - instance.result is a list with one element + - instance.result_current is a dict with key success == True + """ + key = "test_image_policy_query_00031a" + + PATCH_DCNM_SEND = "ansible_collections.cisco.dcnm.plugins." + PATCH_DCNM_SEND += "module_utils.common.rest_send.dcnm_send" + + def responses(): + yield rest_send_response_current(key) + + gen = GenerateResponses(responses()) + + def mock_dcnm_send(*args, **kwargs): + item = gen.next + return item + + with does_not_raise(): + instance = image_policy_query + instance.results = Results() + instance.policy_names = ["KR5M"] + monkeypatch.setattr(PATCH_DCNM_SEND, mock_dcnm_send) + instance._image_policies.results = Results() + with does_not_raise(): + instance.commit() + assert isinstance(instance.results.diff, list) + assert isinstance(instance.results.result, list) + assert isinstance(instance.results.response, list) + assert len(instance.results.diff) == 1 + assert len(instance.results.result) == 1 + assert len(instance.results.response) == 1 + assert instance.results.diff[0].get("policyName", None) == "KR5M" + assert instance.results.diff[0].get("sequence_number", None) == 1 + assert False in instance.results.failed + assert True not in instance.results.failed + assert False in instance.results.changed + assert True not in instance.results.changed + + +def test_image_policy_query_00032(monkeypatch, image_policy_query) -> None: + """ + Classes and Methods + - ImagePolicyQuery + - policy_names setter + - _get_policies_to_query() + - commit() + + Summary + Verify behavior when user queries multiple policies, some of which exist + on the controller and some of which do not exist on the controller. + + Setup + - ImagePolicies().all_policies, is mocked to indicate that two image policies + (KR5M, NR3F) exist on the controller. + - ImagePolicyQuery().policy_names is set to contain one image policy name (FOO) + that does not exist on the controller and two image policy names (KR5M, NR3F) + that do exist on the controller. + + Test + - instance.diff is a list containing two elements + - instance.diff[0] contains keys action == "query" and policyName == "KR5M" + - instance.diff[1] contains keys action == "query" and policyName == "NR3F" + - instance.response is a list with one element + - instance.response_current is a dict with key RETURN_CODE == 200 + - instance.result is a list with one element + - instance.result_current is a dict with key success == True + """ + key = "test_image_policy_query_00032a" + + PATCH_DCNM_SEND = "ansible_collections.cisco.dcnm.plugins." + PATCH_DCNM_SEND += "module_utils.common.rest_send.dcnm_send" + + def responses(): + yield rest_send_response_current(key) + + gen = GenerateResponses(responses()) + + def mock_dcnm_send(*args, **kwargs): + item = gen.next + return item + + with does_not_raise(): + instance = image_policy_query + instance.results = Results() + instance.policy_names = ["KR5M", "NR3F", "FOO"] + monkeypatch.setattr(PATCH_DCNM_SEND, mock_dcnm_send) + instance._image_policies.results = Results() + with does_not_raise(): + instance.commit() + assert isinstance(instance.results.diff, list) + assert isinstance(instance.results.result, list) + assert isinstance(instance.results.response, list) + assert len(instance.results.diff) == 2 + assert len(instance.results.result) == 2 + assert len(instance.results.response) == 2 + assert instance.results.diff[0].get("policyName", None) == "KR5M" + assert instance.results.diff[1].get("policyName", None) == "NR3F" + assert instance.results.diff[0].get("sequence_number", None) == 1 + assert instance.results.diff[1].get("sequence_number", None) == 2 + assert False in instance.results.failed + assert True not in instance.results.failed + assert False in instance.results.changed + assert True not in instance.results.changed + + +def test_image_policy_query_00033(monkeypatch, image_policy_query) -> None: + """ + Classes and Methods + - ImagePolicyCommon + - __init__() + - ImagePolicyQuery + - __init__() + - policy_names setter + - _get_policies_to_query() + - commit() + + Summary + Verify behavior when no image policies exist on the controller and the user + queries for an image policy that, of course, does not exist. + + Setup + - ImagePolicies().all_policies, is mocked to indicate that no image policies + exist on the controller. + - ImagePolicyQuery.policy_names is set to contain one policy_name (FOO) + that does not exist on the controller. + + Test + - commit() calls _get_policies_to_query() which sets instance._policies_to_query + to an empty list. + - commit() sets instance.changed to False + - commit() sets instance.failed to False + - commit() returns without doing anything else + - fail_json is not called + """ + key = "test_image_policy_query_00033a" + + PATCH_DCNM_SEND = "ansible_collections.cisco.dcnm.plugins." + PATCH_DCNM_SEND += "module_utils.common.rest_send.dcnm_send" + + def responses(): + yield rest_send_response_current(key) + + gen = GenerateResponses(responses()) + + def mock_dcnm_send(*args, **kwargs): + item = gen.next + return item + + with does_not_raise(): + instance = image_policy_query + instance.results = Results() + instance.policy_names = ["FOO"] + monkeypatch.setattr(PATCH_DCNM_SEND, mock_dcnm_send) + instance._image_policies.results = Results() + with does_not_raise(): + instance.commit() + assert isinstance(instance.results.diff, list) + assert isinstance(instance.results.result, list) + assert isinstance(instance.results.response, list) + assert len(instance.results.diff) == 1 + assert len(instance.results.result) == 1 + assert len(instance.results.response) == 1 + assert instance.results.diff[0].get("policyName", None) is None + assert instance.results.diff[0].get("sequence_number", None) == 1 + assert False in instance.results.failed + assert True not in instance.results.failed + assert False in instance.results.changed + assert True not in instance.results.changed diff --git a/tests/unit/modules/dcnm/dcnm_image_policy/test_image_policy_replace_bulk.py b/tests/unit/modules/dcnm/dcnm_image_policy/test_image_policy_replace_bulk.py new file mode 100644 index 000000000..be7a3e90b --- /dev/null +++ b/tests/unit/modules/dcnm/dcnm_image_policy/test_image_policy_replace_bulk.py @@ -0,0 +1,603 @@ +# Copyright (c) 2024 Cisco and/or its affiliates. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# See the following regarding *_fixture imports +# https://pylint.pycqa.org/en/latest/user_guide/messages/warning/redefined-outer-name.html +# Due to the above, we also need to disable unused-import +# Also, fixtures need to use *args to match the signature of the function they are mocking +# pylint: disable=unused-import +# pylint: disable=redefined-outer-name +# pylint: disable=protected-access +# pylint: disable=unused-argument +# pylint: disable=invalid-name + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +__copyright__ = "Copyright (c) 2024 Cisco and/or its affiliates." +__author__ = "Allen Robel" + +from typing import Any, Dict + +import pytest +from ansible_collections.ansible.netcommon.tests.unit.modules.utils import \ + AnsibleFailJson +from ansible_collections.cisco.dcnm.plugins.module_utils.common.results import \ + Results +from ansible_collections.cisco.dcnm.plugins.module_utils.image_policy.endpoints import \ + ApiEndpoints +from ansible_collections.cisco.dcnm.plugins.module_utils.image_policy.image_policies import \ + ImagePolicies +from ansible_collections.cisco.dcnm.tests.unit.modules.dcnm.dcnm_image_policy.utils import ( + GenerateResponses, MockImagePolicies, does_not_raise, + image_policy_replace_bulk_fixture, payloads_image_policy_replace_bulk, + responses_image_policy_replace_bulk, rest_send_result_current, + results_image_policy_replace_bulk) + + +def test_image_policy_replace_bulk_00010(image_policy_replace_bulk) -> None: + """ + Classes and Methods + - ImagePolicyReplaceBulk + - __init__ + + Summary + Verify that __init__() sets class attributes to the expected values. + + Test + - Class attributes initialized to expected values + - fail_json is not called + """ + with does_not_raise(): + instance = image_policy_replace_bulk + assert instance.class_name == "ImagePolicyReplaceBulk" + assert instance.action == "replace" + assert instance.state == "replaced" + assert instance.check_mode is False + assert isinstance(instance.endpoints, ApiEndpoints) + assert instance.path == ApiEndpoints().policy_edit["path"] + assert instance.verb == ApiEndpoints().policy_edit["verb"] + assert instance._mandatory_payload_keys == { + "nxosVersion", + "policyName", + "policyType", + } + assert instance.payloads is None + assert instance._payloads_to_commit == [] + assert isinstance(instance._image_policies, ImagePolicies) + + +def test_image_policy_replace_bulk_00020(image_policy_replace_bulk) -> None: + """ + Classes and Methods + - ImagePolicyReplaceBulk + - __init__ + - payloads setter + + Summary + Verify that the payloads setter sets the payloads attribute + to the expected value. + + Test + - payloads is set to expected value + - fail_json is not called + """ + key = "test_image_policy_replace_bulk_00020a" + + with does_not_raise(): + instance = image_policy_replace_bulk + instance.payloads = payloads_image_policy_replace_bulk(key) + assert instance.payloads == payloads_image_policy_replace_bulk(key) + + +def test_image_policy_replace_bulk_00021(image_policy_replace_bulk) -> None: + """ + Classes and Methods + - ImagePolicyReplaceBulk + - __init__ + - payload setter + + Summary + Verify that the payloads setter calls fail_json when payloads is not a list of dict + + Test + - fail_json is called because payloads is not a list + - instance.payloads is not modified, hence it retains its initial value of None + """ + key = "test_image_policy_replace_bulk_00021a" + match = "ImagePolicyReplaceBulk.payloads: " + match += "payloads must be a list of dict. got dict for value" + + with does_not_raise(): + instance = image_policy_replace_bulk + with pytest.raises(AnsibleFailJson, match=match): + instance.payloads = payloads_image_policy_replace_bulk(key) + assert instance.payloads is None + + +@pytest.mark.parametrize( + "key, match", + [ + ("test_image_policy_replace_bulk_00022a", "nxosVersion"), + ("test_image_policy_replace_bulk_00022b", "policyName"), + ("test_image_policy_replace_bulk_00022c", "policyType"), + ], +) +def test_image_policy_replace_bulk_00022(image_policy_replace_bulk, key, match) -> None: + """ + Classes and Methods + - ImagePolicyReplaceBulk + - __init__ + - payloads setter + + Test + - fail_json is called because a payload in the payloads list is missing a mandatory key + - instance.payloads is not modified, hence it retains its initial value of None + """ + with does_not_raise(): + instance = image_policy_replace_bulk + instance.results = Results() + with pytest.raises(AnsibleFailJson, match=match): + instance.payloads = payloads_image_policy_replace_bulk(key) + assert instance.payloads is None + + +def test_image_policy_replace_bulk_00023(image_policy_replace_bulk) -> None: + """ + Classes and Methods + - ImagePolicyReplaceBulk + - __init__ + - payload setter + + Summary + Verify that the payloads setter calls fail_json when payloads is a list + but contains an element that is not a dict. + + Test + - fail_json is called because payloads is a list, but contains a non-dict element + - instance.payloads is not modified, hence it retains its initial value of None + """ + key = "test_image_policy_replace_bulk_00023a" + match = "ImagePolicyReplaceBulk._verify_payload: " + match += "payload must be a dict. Got type str, value " + match += "IM_A_STRING_BUT_SHOULD_BE_A_DICT" + + with does_not_raise(): + instance = image_policy_replace_bulk + instance.results = Results() + with pytest.raises(AnsibleFailJson, match=match): + instance.payloads = payloads_image_policy_replace_bulk(key) + assert instance.payloads is None + + +def test_image_policy_replace_bulk_00030( + monkeypatch, image_policy_replace_bulk +) -> None: + """ + Classes and Methods + - ImagePolicyReplaceBulk + - __init__() + - _build_payloads_to_commit() + - _verify_image_policy_ref_count() + - payloads setter + + Summary + Verify _build_payloads_to_commit() behavior when a request contains one + image policy that exists on the controller and the caller has requested to + replace it. The replaced image policy contains a different policyDescr. + + Setup + - ImagePolicies().all_policies, called from instance._build_payloads_to_commit(), + is mocked to indicate that two image policies (KR5M, NR3F) exist on the + controller. + - ImagePolicyReplaceBulk().payloads is set to contain one payload (KR5M) + that is present in all_policies. + + Test + - payloads_to_commit will contain payload for KR5M since it exists on the controller + and the caller has requested to replace it. + - Since this is a full payload, MergeDicts doesn't apply any defaults to it. + """ + key = "test_image_policy_replace_bulk_00030a" + + with does_not_raise(): + instance = image_policy_replace_bulk + instance.results = Results() + instance.payloads = payloads_image_policy_replace_bulk(key) + monkeypatch.setattr(instance, "_image_policies", MockImagePolicies(key)) + instance._build_payloads_to_commit() + assert instance._payloads_to_commit == payloads_image_policy_replace_bulk(key) + assert instance._payloads_to_commit[0]["policyName"] == "KR5M" + assert instance._payloads_to_commit[0]["policyDescr"] == "KR5M Replaced" + + +def test_image_policy_replace_bulk_00031( + monkeypatch, image_policy_replace_bulk +) -> None: + """ + Classes and Methods + - ImagePolicyReplaceBulk + - __init__() + - _build_payloads_to_commit() + - _verify_image_policy_ref_count() + - payloads setter + + Summary + Verify behavior when a request to replace an image policy is sent for + an image policy that does not exist on the controller + + Setup + - ImagePolicies().all_policies, called from instance._build_payloads_to_commit(), + is mocked to indicate that two image policies (KR5M, NR3F) exist on the + controller. + - ImagePolicyReplaceBulk().payloads is set to contain one payload containing + an image policy (FOO) that is not present on the controller. + + Test + - fail_json is not called + - _payloads_to_commit be an empty list since policy FOO does not + exist on the controller. + """ + key = "test_image_policy_replace_bulk_00031a" + + with does_not_raise(): + instance = image_policy_replace_bulk + instance.results = Results() + instance.payloads = payloads_image_policy_replace_bulk(key) + monkeypatch.setattr(instance, "_image_policies", MockImagePolicies(key)) + instance._build_payloads_to_commit() + assert instance._payloads_to_commit == [] + + +def test_image_policy_replace_bulk_00032( + monkeypatch, image_policy_replace_bulk +) -> None: + """ + Classes and Methods + - ImagePolicyReplaceBulk + - __init__() + - _build_payloads_to_commit() + - _verify_image_policy_ref_count() + - payloads setter + + Summary + Verify _build_payloads_to_commit() behavior when a request contains one + image policy that does not exist on the controller and one image policy + that exists on the controller. + + Setup + - ImagePolicies().all_policies, called from instance._build_payloads_to_commit(), + is mocked to indicate that two image policies (KR5M, NR3F) exist on the + controller. + - ImagePolicyReplaceBulk().payloads is set to contain one payload containing + an image policy (FOO) that does not exist on the controller and one payload + containing an image policy (KR5M) that exists on the controller. + + Test + - _payloads_to_commit will contain one payload + - The policyName for this payload will be "KR5M", which is the image policy that + exists on the controller + """ + key = "test_image_policy_replace_bulk_00032a" + + with does_not_raise(): + instance = image_policy_replace_bulk + instance.results = Results() + instance.payloads = payloads_image_policy_replace_bulk(key) + monkeypatch.setattr(instance, "_image_policies", MockImagePolicies(key)) + instance._build_payloads_to_commit() + assert len(instance._payloads_to_commit) == 1 + assert instance._payloads_to_commit[0]["policyName"] == "KR5M" + + +def test_image_policy_replace_bulk_00033(image_policy_replace_bulk) -> None: + """ + Classes and Methods + - ImagePolicyReplaceBulk + - commit() + - _build_payloads_to_commit + - fail_json + + Summary + Verify that _build_payloads_to_commit() calls fail_json when + payloads is not set. + + Setup + - ImagePolicyReplaceBulk().payloads is not set + + Test + - fail_json is called because payloads is None + """ + with does_not_raise(): + instance = image_policy_replace_bulk + + match = ( + "ImagePolicyReplaceBulk._build_payloads_to_commit: payloads must be " + "set prior to calling commit." + ) + with pytest.raises(AnsibleFailJson, match=match): + instance.commit() + + +def test_image_policy_replace_bulk_00034( + monkeypatch, image_policy_replace_bulk +) -> None: + """ + Classes and Methods + - ImagePolicyReplaceBulk + - payloads setter + - commit() + - _build_payloads_to_commit() + + Summary + Verify that commit() returns without doing anything when payloads + is set to an empty list. + + Setup + - ImagePolicyReplaceBulk().payloads is set to an empty list + + Test + - ImagePolicyReplaceBulk().commit returns without doing anything + """ + key = "test_image_policy_replace_bulk_00034a" + + with does_not_raise(): + instance = image_policy_replace_bulk + instance.results = Results() + instance.payloads = [] + + monkeypatch.setattr(instance, "_image_policies", MockImagePolicies(key)) + + with does_not_raise(): + instance.commit() + + +def test_image_policy_replace_bulk_00035( + monkeypatch, image_policy_replace_bulk +) -> None: + """ + Classes and Methods + - ImagePolicyReplaceBulk + - _build_payloads_to_commit() + - _send_payloads() + - payloads setter + - commit() + + Summary + Verify behavior when a request is made to replace two image policies + that exist on the controller. + + Setup + - ImagePolicies().all_policies, called from instance._build_payloads_to_commit(), + is mocked to indicate that two policies (KR5M, NR3F) exist on the controller. + - ImagePolicyReplaceBulk().payloads is set to contain payloads for KR5M and NR3F + in which policyDescr is different from the existing policyDescr. + - dcnm_send is mocked to return a successful (200) response. + + Test + - commit calls _build_payloads_to_commit which returns two payloads + - commit calls _send_payloads, which calls results.register_task_result() + to update the results. + - results.* are set to the expected values + """ + key = "test_image_policy_replace_bulk_00035a" + + def mock_dcnm_send(*args, **kwargs): + return responses_image_policy_replace_bulk(key) + + PATCH_DCNM_SEND = "ansible_collections.cisco.dcnm.plugins." + PATCH_DCNM_SEND += "module_utils.common.rest_send.dcnm_send" + + monkeypatch.setattr(PATCH_DCNM_SEND, mock_dcnm_send) + + with does_not_raise(): + instance = image_policy_replace_bulk + instance.results = Results() + + monkeypatch.setattr(instance, "_image_policies", MockImagePolicies(key)) + monkeypatch.setattr(PATCH_DCNM_SEND, mock_dcnm_send) + + with does_not_raise(): + instance.payloads = payloads_image_policy_replace_bulk(key) + instance.commit() + + payload_0 = payloads_image_policy_replace_bulk(key)[0] + # sequence_number is added by the Results class + payload_0["sequence_number"] = 1 + + payload_1 = payloads_image_policy_replace_bulk(key)[1] + payload_1["sequence_number"] = 2 + + assert instance.results.action == "replace" + assert instance.rest_send.result_current == rest_send_result_current(key) + assert len(instance.results.diff) == 2 + assert len(instance.results.result) == 2 + assert len(instance.results.response) == 2 + assert instance.results.result[0].get("sequence_number") == 1 + assert instance.results.result[1].get("sequence_number") == 2 + assert instance.results.diff[0] == payload_0 + assert instance.results.diff[1] == payload_1 + assert instance.results.diff[0].get("policyDescr") == "KR5M replaced" + assert instance.results.diff[1].get("policyDescr") == "NR3F replaced" + assert False in instance.results.failed + assert True not in instance.results.failed + assert False not in instance.results.changed + assert True in instance.results.changed + assert len(instance.results.metadata) == 2 + assert instance.results.metadata[0]["action"] == "replace" + assert instance.results.metadata[0]["state"] == "replaced" + assert instance.results.metadata[0]["sequence_number"] == 1 + assert instance.results.metadata[1]["action"] == "replace" + assert instance.results.metadata[1]["state"] == "replaced" + assert instance.results.metadata[1]["sequence_number"] == 2 + + +def test_image_policy_replace_bulk_00036( + monkeypatch, image_policy_replace_bulk +) -> None: + """ + Classes and Methods + - ImagePolicyReplaceBulk + - _build_payloads_to_commit() + - _send_payloads() + - payloads setter + - commit() + + Summary + Verify behavior when the controller returns a 500 response to an + image policy replace request + + Setup + - ImagePolicies().all_policies, is mocked to indicate that one policy + (KR5M) exists on the controller. + - ImagePolicyReplaceBulk().payloads is set to contain the payload for + image policy KR5M with policyDescr changed. + - dcnm_send is mocked to return a failure (500) response. + + Test + - commit calls _build_payloads_to_commit which returns one payload + - commit calls _send_payloads, which populates response_ok, result_ok, + diff_ok, response_nok, result_nok, and diff_nok based on the payload + returned from _build_payloads_to_commit and the failure response + - response_ok, result_ok, and diff_ok are set to empty lists + - response_nok, result_nok, and diff_nok are set to expected values + """ + key = "test_image_policy_replace_bulk_00036a" + + PATCH_DCNM_SEND = "ansible_collections.cisco.dcnm.plugins." + PATCH_DCNM_SEND += "module_utils.common.rest_send.dcnm_send" + + def mock_dcnm_send(*args, **kwargs): + return responses_image_policy_replace_bulk(key) + + with does_not_raise(): + instance = image_policy_replace_bulk + instance.rest_send.unit_test = True + instance.results = Results() + instance.payloads = payloads_image_policy_replace_bulk(key) + + monkeypatch.setattr(instance, "_image_policies", MockImagePolicies(key)) + monkeypatch.setattr(PATCH_DCNM_SEND, mock_dcnm_send) + + with does_not_raise(): + instance.commit() + + response_current = responses_image_policy_replace_bulk(key) + response_current["sequence_number"] = 1 + assert instance.results.response_current == response_current + assert instance.results.diff_current == {"sequence_number": 1} + assert True in instance.results.failed + assert False not in instance.results.failed + assert True not in instance.results.changed + assert False in instance.results.changed + assert len(instance.results.metadata) == 1 + assert len(instance.results.diff) == 1 + assert instance.results.diff[0] == {"sequence_number": 1} + assert instance.results.metadata[0]["action"] == "replace" + assert instance.results.metadata[0]["state"] == "replaced" + assert instance.results.metadata[0]["sequence_number"] == 1 + + +def test_image_policy_replace_bulk_00037( + monkeypatch, image_policy_replace_bulk +) -> None: + """ + Classes and Methods + - ImagePolicyCreateCommon + - _process_responses() + - ImagePolicyCreateBulk + - __init__() + + Summary + Verify behavior when the controller returns a 200 response to an image policy + replace request, followed by a 500 response to a subsequent image policy replace + request. + + Setup + - instance.payloads is set to contain two payloads + + Test + - Both successful and bad responses are recorded with separate sequence_numbers. + - instance.results.failed will be a set() containing both True and False + - instance.results.changed will be a set() containing both True and False + - instance.results.response contains two responses + - instance.results.result contains two results + - instance.results.diff contains two diffs + """ + PATCH_DCNM_SEND = "ansible_collections.cisco.dcnm.plugins." + PATCH_DCNM_SEND += "module_utils.common.rest_send.dcnm_send" + + key_policies = "test_image_policy_replace_bulk_00037a" + key_ok = "test_image_policy_replace_bulk_00037b" + key_nok = "test_image_policy_replace_bulk_00037c" + key_payloads = "test_image_policy_replace_bulk_00037d" + + def responses(): + yield responses_image_policy_replace_bulk(key_policies) + yield responses_image_policy_replace_bulk(key_ok) + yield responses_image_policy_replace_bulk(key_nok) + + gen = GenerateResponses(responses()) + + def mock_dcnm_send(*args, **kwargs): + item = gen.next + return item + + with does_not_raise(): + instance = image_policy_replace_bulk + instance.rest_send.unit_test = True + instance.results = Results() + instance.payloads = payloads_image_policy_replace_bulk(key_payloads) + + monkeypatch.setattr(PATCH_DCNM_SEND, mock_dcnm_send) + + with does_not_raise(): + instance.commit() + + assert len(instance.results.diff) == 2 + assert len(instance.results.result) == 2 + assert len(instance.results.response) == 2 + assert len(instance.results.metadata) == 2 + assert instance.results.response[0]["RETURN_CODE"] == 200 + assert instance.results.response[1]["RETURN_CODE"] == 500 + assert False in instance.results.changed + assert True in instance.results.changed + assert False in instance.results.failed + assert True in instance.results.failed + + +def test_image_policy_replace_bulk_00040(image_policy_replace_bulk) -> None: + """ + Classes and Methods + - ImagePolicyReplaceBulk + - __init__ + - _default_policy + + Summary + Verify that instance._default_policy setter calls fail_json when + passed a policy_name that is not a string. + + Test + - fail_json is called because policy_name is a list + """ + match = "ImagePolicyReplaceBulk._default_policy: " + match += "policy_name must be a string. " + match += r"Got type list for value \[\]" + + with does_not_raise(): + instance = image_policy_replace_bulk + instance.results = Results() + with pytest.raises(AnsibleFailJson, match=match): + instance._default_policy([]) diff --git a/tests/unit/modules/dcnm/dcnm_image_policy/test_image_policy_update.py b/tests/unit/modules/dcnm/dcnm_image_policy/test_image_policy_update.py new file mode 100644 index 000000000..bd2548b43 --- /dev/null +++ b/tests/unit/modules/dcnm/dcnm_image_policy/test_image_policy_update.py @@ -0,0 +1,516 @@ +# Copyright (c) 2024 Cisco and/or its affiliates. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# See the following regarding *_fixture imports +# https://pylint.pycqa.org/en/latest/user_guide/messages/warning/redefined-outer-name.html +# Due to the above, we also need to disable unused-import +# Also, fixtures need to use *args to match the signature of the function they are mocking +# pylint: disable=unused-import +# pylint: disable=redefined-outer-name +# pylint: disable=protected-access +# pylint: disable=unused-argument +# pylint: disable=invalid-name + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +__copyright__ = "Copyright (c) 2024 Cisco and/or its affiliates." +__author__ = "Allen Robel" + +from typing import Any, Dict + +import pytest +from ansible_collections.ansible.netcommon.tests.unit.modules.utils import \ + AnsibleFailJson +from ansible_collections.cisco.dcnm.plugins.module_utils.common.results import \ + Results +from ansible_collections.cisco.dcnm.plugins.module_utils.image_policy.endpoints import \ + ApiEndpoints +from ansible_collections.cisco.dcnm.tests.unit.modules.dcnm.dcnm_image_policy.utils import ( + MockImagePolicies, does_not_raise, image_policy_update_fixture, + payloads_image_policy_update, responses_image_policy_update, + rest_send_result_current, results_image_policy_update) + + +def test_image_policy_update_00010(image_policy_update) -> None: + """ + Classes and Methods + - ImagePolicyUpdate + - __init__ + + Summary + Verify that __init__() sets class attributes to the expected values. + + Test + - Class attributes initialized to expected values + - fail_json is not called + """ + with does_not_raise(): + instance = image_policy_update + assert instance.class_name == "ImagePolicyUpdate" + assert instance.action == "update" + assert instance.state == "merged" + assert instance.check_mode is False + assert isinstance(instance.endpoints, ApiEndpoints) + assert instance.path == ApiEndpoints().policy_edit["path"] + assert instance.verb == ApiEndpoints().policy_edit["verb"] + assert instance._mandatory_payload_keys == { + "nxosVersion", + "policyName", + "policyType", + } + assert instance.payload is None + assert instance._payloads_to_commit == [] + + +def test_image_policy_update_00020(image_policy_update) -> None: + """ + Classes and Methods + - ImagePolicyUpdate + - __init__ + - payload setter + + Summary + Verify that the payload setter sets the payload attribute + to the expected value. + + Test + - payload is set to expected value + - fail_json is not called + """ + key = "test_image_policy_update_00020a" + + with does_not_raise(): + instance = image_policy_update + instance.payload = payloads_image_policy_update(key) + assert instance.payload == payloads_image_policy_update(key) + + +def test_image_policy_update_00021(image_policy_update) -> None: + """ + Classes and Methods + - ImagePolicyUpdate + - __init__ + - payload setter + + Summary + Verify that the payload setter calls fail_json when payload is not a dict + + Setup + - payload is set to a list + + Test + - fail_json is called because payload is not a dict + - instance.payload is not modified, hence it retains its initial value of None + """ + key = "test_image_policy_update_00021a" + match = "ImagePolicyUpdate._verify_payload: " + match += "payload must be a dict. Got type list, value" + + with does_not_raise(): + instance = image_policy_update + instance.results = Results() + with pytest.raises(AnsibleFailJson, match=match): + instance.payload = payloads_image_policy_update(key) + assert instance.payload is None + + +@pytest.mark.parametrize( + "key, match", + [ + ("test_image_policy_update_00022a", "nxosVersion"), + ("test_image_policy_update_00022b", "policyName"), + ("test_image_policy_update_00022c", "policyType"), + ], +) +def test_image_policy_update_00022(image_policy_update, key, match) -> None: + """ + Classes and Methods + - ImagePolicyUpdate + - __init__ + - payload setter + + Summary + Verify that the payload setter calls fail_json when a payload is missing + a mandatory key + + Test + - fail_json is called because payload is missing a mandatory key + - instance.payload is not modified, hence it retains its initial value of None + """ + with does_not_raise(): + instance = image_policy_update + instance.results = Results() + with pytest.raises(AnsibleFailJson, match=match): + instance.payload = payloads_image_policy_update(key) + assert instance.payload is None + + +def test_image_policy_update_00030(monkeypatch, image_policy_update) -> None: + """ + Classes and Methods + - ImagePolicyUpdate + - __init__() + - _build_payloads_to_commit() + - _verify_image_policy_ref_count() + - payload setter + + Summary + Verify _build_payloads_to_commit() behavior when a request contains one + image policy that exists on the controller and the caller has requested to + update it. The update consists of changing the policyDescr. + + Setup + - ImagePolicies().all_policies, is mocked to indicate that two image + policies (KR5M, NR3F) exist on the controller. + - ImagePolicyUpdate().payload is set to contain a payload (KR5M) + that is present on the controller. + + Test + - payloads_to_commit will contain the payload for KR5M since it exists + on the controller and the caller has requested to update it. + - The policyName for this payload will be "KR5M" + - The policyDescr for this payload will be "KR5M updated" + - fail_json is not called + """ + key = "test_image_policy_update_00030a" + + with does_not_raise(): + instance = image_policy_update + instance.results = Results() + instance.payload = payloads_image_policy_update(key) + + monkeypatch.setattr(instance, "_image_policies", MockImagePolicies(key)) + + with does_not_raise(): + instance._build_payloads_to_commit() + + assert instance._payloads_to_commit == [payloads_image_policy_update(key)] + assert instance._payloads_to_commit[0]["policyName"] == "KR5M" + assert instance._payloads_to_commit[0]["policyDescr"] == "KR5M updated" + + +def test_image_policy_update_00031(monkeypatch, image_policy_update) -> None: + """ + Classes and Methods + - ImagePolicyUpdate + - __init__() + - _build_payloads_to_commit() + - _verify_image_policy_ref_count() + - payload setter + + Summary + Verify that instance._build_payloads_to_commit() does not add a payload + to the payloads_to_commit list when a request is made to update an + image policy that does not exist on the controller. + + Setup + - ImagePolicies().all_policies, is mocked to indicate that two image policies + (KR5M, NR3F) exist on the controller. + - ImagePolicyUpdate().payload is set to contain a payload containing + an image policy (FOO) that does not exist on the controller. + + Test + - fail_json is not called + - _payloads_to_commit will be an empty list since policy FOO does not + exist on the controller. + """ + key = "test_image_policy_update_00031a" + + with does_not_raise(): + instance = image_policy_update + instance.results = Results() + instance.payload = payloads_image_policy_update(key) + monkeypatch.setattr(instance, "_image_policies", MockImagePolicies(key)) + instance._build_payloads_to_commit() + assert instance._payloads_to_commit == [] + + +def test_image_policy_update_00033(image_policy_update) -> None: + """ + Classes and Methods + - ImagePolicyUpdate + - commit() + - _build_payloads_to_commit + - fail_json + + Summary + Verify that _build_payloads_to_commit() calls fail_json when + payload is not set. + + Setup + - ImagePolicyUpdate().payload is not set + + Test + - fail_json is called because payload is None + """ + with does_not_raise(): + instance = image_policy_update + instance.results = Results() + + match = "ImagePolicyUpdate.commit: payload must be set prior to calling commit." + with pytest.raises(AnsibleFailJson, match=match): + instance.commit() + + +def test_image_policy_update_00034(monkeypatch, image_policy_update) -> None: + """ + Classes and Methods + - ImagePolicyUpdateCommon + - _build_payloads_to_commit() + - ImagePolicyUpdate + - payload setter + - commit() + + Summary + Verify that commit() returns without doing anything when payloads + is set to a policy that does not exist on the controller. + + Setup + - ImagePolicies().all_policies, is mocked to indicate that no policies + exist on the controller. + - ImagePolicyUpdate().payload is set to a policy (FOO) that does not + exist on the controller + + Test + - ImagePolicyUpdate().commit returns without doing anything + - ImagePolicyUpdate()._payloads_to_commit is an empty list + - ImagePolicyUpdate().results.changed is empty + - ImagePolicyUpdate().results.failed is empty + """ + key = "test_image_policy_update_00034a" + + with does_not_raise(): + instance = image_policy_update + instance.results = Results() + instance.payload = payloads_image_policy_update(key) + + monkeypatch.setattr(instance, "_image_policies", MockImagePolicies(key)) + + with does_not_raise(): + instance.commit() + assert instance._payloads_to_commit == [] + assert len(instance.results.changed) == 0 + assert len(instance.results.failed) == 0 + + +def test_image_policy_update_00035(monkeypatch, image_policy_update) -> None: + """ + Classes and Methods + - ImagePolicyUpdate + - _build_payloads_to_commit() + - _send_payloads() + - payload setter + - commit() + + Summary + Verify that ImagePolicyUpdate.commit() behaves as expected when the + controller responds to an image policy update request with a 200 response. + + Setup + - ImagePolicies().all_policies, is mocked to indicate that two policies + (KR5M, NR3F) exist on the controller. + - ImagePolicyUpdate().payload is set to contain a payload for KR5M + in which policyDescr is different from the existing policyDescr. + - dcnm_send is mocked to return a successful (200) response. + + Test + Test + - commit calls _build_payloads_to_commit which returns one payload. + - commit calls _send_payloads, which calls rest_send, which populates + diff_current with the payload due to result_current indicating + success. + - results.result_current is set to the expected value + - results.diff_current is set to the expected value + - results.response_current is set to the expected value + - results.action is set to "update" + """ + key = "test_image_policy_update_00035a" + + def mock_dcnm_send(*args, **kwargs): + return responses_image_policy_update(key) + + PATCH_DCNM_SEND = "ansible_collections.cisco.dcnm.plugins." + PATCH_DCNM_SEND += "module_utils.common.rest_send.dcnm_send" + + monkeypatch.setattr(PATCH_DCNM_SEND, mock_dcnm_send) + + with does_not_raise(): + instance = image_policy_update + instance.results = Results() + + monkeypatch.setattr(instance, "_image_policies", MockImagePolicies(key)) + + with does_not_raise(): + instance.payload = payloads_image_policy_update(key) + instance.commit() + + response_current = responses_image_policy_update(key) + response_current["sequence_number"] = 1 + + result_current = rest_send_result_current(key) + result_current["sequence_number"] = 1 + + payload = payloads_image_policy_update(key) + payload["sequence_number"] = 1 + + assert instance.results.action == "update" + assert instance.rest_send.result_current == rest_send_result_current(key) + assert instance.results.result_current == result_current + assert instance.results.response_current == response_current + assert instance.results.diff_current == payload + assert False in instance.results.failed + assert True not in instance.results.failed + assert False not in instance.results.changed + assert True in instance.results.changed + assert len(instance.results.metadata) == 1 + assert instance.results.metadata[0]["action"] == "update" + assert instance.results.metadata[0]["state"] == "merged" + assert instance.results.metadata[0]["sequence_number"] == 1 + + +def test_image_policy_update_00036(monkeypatch, image_policy_update) -> None: + """ + Classes and Methods + - ImagePolicyUpdate + - _build_payloads_to_commit() + - _send_payloads() + - payload setter + - commit() + + Summary + Verify that ImagePolicyUpdate.commit() behaves as expected when the + controller responds to an image policy update request with a 500 response. + + Setup + - ImagePolicies().all_policies, is mocked to indicate that one policy + (KR5M) exists on the controller. + - ImagePolicyUpdate().payloads is set to contain the payload for + image policy KR5M with policyDescr changed. + - dcnm_send is mocked to return a failure (500) response. + + Test + - commit calls _build_payloads_to_commit which returns one payload + - commit calls _send_payloads, which populates response_ok, result_ok, + diff_ok, response_nok, result_nok, and diff_nok based on the payload + returned from _build_payloads_to_commit and the failure response + - response_ok, result_ok, and diff_ok are set to empty lists + - response_nok, result_nok, and diff_nok are set to expected values + """ + key = "test_image_policy_update_00036a" + + def mock_dcnm_send(*args, **kwargs): + return responses_image_policy_update(key) + + PATCH_DCNM_SEND = "ansible_collections.cisco.dcnm.plugins." + PATCH_DCNM_SEND += "module_utils.common.rest_send.dcnm_send" + + monkeypatch.setattr(PATCH_DCNM_SEND, mock_dcnm_send) + + with does_not_raise(): + instance = image_policy_update + instance.rest_send.unit_test = True + instance.results = Results() + + monkeypatch.setattr(instance, "_image_policies", MockImagePolicies(key)) + + with does_not_raise(): + instance.payload = payloads_image_policy_update(key) + instance.commit() + + response_current = responses_image_policy_update(key) + response_current["sequence_number"] = 1 + + result_current = rest_send_result_current(key) + result_current["sequence_number"] = 1 + + assert instance.results.action == "update" + assert instance.rest_send.result_current == rest_send_result_current(key) + assert instance.results.result_current == result_current + assert instance.results.response_current == response_current + assert instance.results.diff_current == {"sequence_number": 1} + assert True in instance.results.failed + assert False not in instance.results.failed + assert True not in instance.results.changed + assert False in instance.results.changed + assert len(instance.results.metadata) == 1 + assert instance.results.metadata[0]["action"] == "update" + assert instance.results.metadata[0]["state"] == "merged" + assert instance.results.metadata[0]["sequence_number"] == 1 + + +def test_image_policy_update_00040(image_policy_update) -> None: + """ + Classes and Methods + - ImagePolicyUpdate + - __init__ + - _default_policy + + Summary + Verify that instance._default_policy setter calls fail_json when + passed a policy_name that is not a string. + + Test + - fail_json is called because policy_name is a list + """ + match = "ImagePolicyUpdate._default_policy: " + match += "policy_name must be a string. " + match += r"Got type list for value \[\]" + + with does_not_raise(): + instance = image_policy_update + instance.results = Results() + with pytest.raises(AnsibleFailJson, match=match): + instance._default_policy([]) + + +def test_image_policy_update_00050(monkeypatch, image_policy_update) -> None: + """ + Classes and Methods + - ImagePolicyUpdate + - _build_payloads_to_commit() + - _send_payloads() + - payload setter + - commit() + + Summary + Verify that fail_json is called when an image policy update request is made + for an image policy which has a ref_count != 0 on the controller, i.e. + switches are attached to the image policy. + + Setup + - ImagePolicies().all_policies, is mocked to indicate that one policy + (KR5M) exists on the controller with ref_count == 2. + - ImagePolicyUpdate().payloads is set to contain the payload for + image policy KR5M with policyDescr changed. + + Test + - commit calls _build_payloads_to_commit + - _build_payloads_to_commit calls _verify_image_policy_ref_count + - _verify_image_policy_ref_count calls fail_json with the expected message + """ + key = "test_image_policy_update_00050a" + + with does_not_raise(): + instance = image_policy_update + instance.results = Results() + instance.payload = payloads_image_policy_update(key) + + monkeypatch.setattr(instance, "_image_policies", MockImagePolicies(key)) + + match = "ImagePolicyUpdate._verify_image_policy_ref_count: " + match += "One or more policies have devices attached." + with pytest.raises(AnsibleFailJson, match=match): + instance.commit() diff --git a/tests/unit/modules/dcnm/dcnm_image_policy/test_image_policy_update_bulk.py b/tests/unit/modules/dcnm/dcnm_image_policy/test_image_policy_update_bulk.py new file mode 100644 index 000000000..aed364f06 --- /dev/null +++ b/tests/unit/modules/dcnm/dcnm_image_policy/test_image_policy_update_bulk.py @@ -0,0 +1,642 @@ +# Copyright (c) 2024 Cisco and/or its affiliates. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# See the following regarding *_fixture imports +# https://pylint.pycqa.org/en/latest/user_guide/messages/warning/redefined-outer-name.html +# Due to the above, we also need to disable unused-import +# Also, fixtures need to use *args to match the signature of the function they are mocking +# pylint: disable=unused-import +# pylint: disable=redefined-outer-name +# pylint: disable=protected-access +# pylint: disable=unused-argument +# pylint: disable=invalid-name + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +__copyright__ = "Copyright (c) 2024 Cisco and/or its affiliates." +__author__ = "Allen Robel" + +from typing import Any, Dict + +import pytest +from ansible_collections.ansible.netcommon.tests.unit.modules.utils import \ + AnsibleFailJson +from ansible_collections.cisco.dcnm.plugins.module_utils.common.results import \ + Results +from ansible_collections.cisco.dcnm.plugins.module_utils.image_policy.endpoints import \ + ApiEndpoints +from ansible_collections.cisco.dcnm.tests.unit.modules.dcnm.dcnm_image_policy.utils import ( + GenerateResponses, MockImagePolicies, does_not_raise, + image_policy_update_bulk_fixture, payloads_image_policy_update_bulk, + responses_image_policy_update_bulk, rest_send_result_current, + results_image_policy_update_bulk) + + +def test_image_policy_update_bulk_00010(image_policy_update_bulk) -> None: + """ + Classes and Methods + - ImagePolicyUpdateBulk + - __init__ + + Summary + Verify that __init__() sets class attributes to the expected values. + + Test + - Class attributes initialized to expected values + - fail_json is not called + """ + with does_not_raise(): + instance = image_policy_update_bulk + assert instance.class_name == "ImagePolicyUpdateBulk" + assert instance.action == "update" + assert instance.state == "merged" + assert instance.check_mode is False + assert isinstance(instance.endpoints, ApiEndpoints) + assert instance.path == ApiEndpoints().policy_edit["path"] + assert instance.verb == ApiEndpoints().policy_edit["verb"] + assert instance._mandatory_payload_keys == { + "nxosVersion", + "policyName", + "policyType", + } + assert instance.payloads is None + assert instance._payloads_to_commit == [] + + +def test_image_policy_update_bulk_00020(image_policy_update_bulk) -> None: + """ + Classes and Methods + - ImagePolicyUpdateCommon + - __init__ + - payloads setter + - ImagePolicyUpdateBulk + - __init__() + + Summary + Verify that the payloads setter sets the payloads attribute + to the expected value. + + Test + - payloads is set to expected value + - fail_json is not called + """ + key = "test_image_policy_update_bulk_00020a" + + with does_not_raise(): + instance = image_policy_update_bulk + instance.payloads = payloads_image_policy_update_bulk(key) + assert instance.payloads == payloads_image_policy_update_bulk(key) + + +def test_image_policy_update_bulk_00021(image_policy_update_bulk) -> None: + """ + Classes and Methods + - ImagePolicyUpdateCommon + - __init__() + - payloads setter + - ImagePolicyUpdateBulk + - __init__() + + Summary + Verify that the payloads setter calls fail_json when payloads is not a list of dict + + Test + - fail_json is called because payloads is not a list + - instance.payloads is not modified, hence it retains its initial value of None + """ + key = "test_image_policy_update_bulk_00021a" + match = "ImagePolicyUpdateBulk.payloads: " + match += "payloads must be a list of dict. got dict for value" + + with does_not_raise(): + instance = image_policy_update_bulk + instance.results = Results() + with pytest.raises(AnsibleFailJson, match=match): + instance.payloads = payloads_image_policy_update_bulk(key) + assert instance.payloads is None + + +@pytest.mark.parametrize( + "key, match", + [ + ("test_image_policy_update_bulk_00022a", "nxosVersion"), + ("test_image_policy_update_bulk_00022b", "policyName"), + ("test_image_policy_update_bulk_00022c", "policyType"), + ], +) +def test_image_policy_update_bulk_00022(image_policy_update_bulk, key, match) -> None: + """ + Classes and Methods + - ImagePolicyCreateCommon + - __init__() + - payloads setter + - ImagePolicyUpdateBulk + - __init__() + + Summary + Verify that the payloads setter calls fail_json when a payload in the payloads list + is missing a mandatory key + + Test + - fail_json is called because a payload in the payloads list is missing a mandatory key + - instance.payloads is not modified, hence it retains its initial value of None + """ + with does_not_raise(): + instance = image_policy_update_bulk + instance.results = Results() + with pytest.raises(AnsibleFailJson, match=match): + instance.payloads = payloads_image_policy_update_bulk(key) + assert instance.payloads is None + + +def test_image_policy_update_bulk_00023(image_policy_update_bulk) -> None: + """ + Classes and Methods + - ImagePolicyUpdateBulk + - __init__ + - payload setter + + Summary + Verify that the payloads setter calls fail_json when payloads is a list + but contains an element that is not a dict. + + Test + - fail_json is called because payloads is a list, but contains a non-dict element + - instance.payloads is not modified, hence it retains its initial value of None + """ + key = "test_image_policy_update_bulk_00023a" + match = "ImagePolicyUpdateBulk._verify_payload: " + match += "payload must be a dict. Got type str, value " + match += "IM_A_STRING_BUT_SHOULD_BE_A_DICT" + + with does_not_raise(): + instance = image_policy_update_bulk + instance.results = Results() + with pytest.raises(AnsibleFailJson, match=match): + instance.payloads = payloads_image_policy_update_bulk(key) + assert instance.payloads is None + + +def test_image_policy_update_bulk_00030(monkeypatch, image_policy_update_bulk) -> None: + """ + Classes and Methods + - ImagePolicyUpdateBulk + - __init__() + - _build_payloads_to_commit() + - _verify_image_policy_ref_count() + - payloads setter + + Summary + Verify _build_payloads_to_commit() behavior when a request contains one + image policy that exists on the controller and the caller has requested to + update it. The update consists of changing the policyDescr. + + Setup + - ImagePolicies().all_policies, is mocked to indicate that two image + policies (KR5M, NR3F) exist on the controller. + - ImagePolicyUpdateBulk().payloads is set to contain one payload (KR5M) + that is present on the controller. + + Test + - payloads_to_commit will contain payload for KR5M since it exists on the controller + and the caller has requested to update it. + """ + key = "test_image_policy_update_bulk_00030a" + + with does_not_raise(): + instance = image_policy_update_bulk + instance.results = Results() + instance.payloads = payloads_image_policy_update_bulk(key) + monkeypatch.setattr(instance, "_image_policies", MockImagePolicies(key)) + instance._build_payloads_to_commit() + assert instance._payloads_to_commit == payloads_image_policy_update_bulk(key) + assert instance._payloads_to_commit[0]["policyName"] == "KR5M" + assert instance._payloads_to_commit[0]["policyDescr"] == "KR5M updated" + + +def test_image_policy_update_bulk_00031(monkeypatch, image_policy_update_bulk) -> None: + """ + Classes and Methods + - ImagePolicyUpdateBulk + - __init__() + - _build_payloads_to_commit() + - _verify_image_policy_ref_count() + - payloads setter + + Summary + Verify behavior when a request is sent to update a policy that does + not exist on the controller + + Setup + - ImagePolicies().all_policies, called from instance._build_payloads_to_commit(), + is mocked to indicate that two image policies (KR5M, NR3F) exist on the + controller. + - ImagePolicyUpdateBulk().payloads is set to contain one payload containing + an image policy (FOO) that is not present on the controller. + + Test + - fail_json is not called + - _payloads_to_commit will be an empty list since policy FOO does not + exist on the controller. + """ + key = "test_image_policy_update_bulk_00031a" + + with does_not_raise(): + instance = image_policy_update_bulk + instance.results = Results() + instance.payloads = payloads_image_policy_update_bulk(key) + monkeypatch.setattr(instance, "_image_policies", MockImagePolicies(key)) + instance._build_payloads_to_commit() + assert instance._payloads_to_commit == [] + assert len(instance.results.failed) == 0 + assert len(instance.results.changed) == 0 + + +def test_image_policy_update_bulk_00032(monkeypatch, image_policy_update_bulk) -> None: + """ + Classes and Methods + - ImagePolicyUpdateBulk + - __init__() + - _build_payloads_to_commit() + - _verify_image_policy_ref_count() + - payloads setter + + Summary + Verify _build_payloads_to_commit() behavior when a request contains one + image policy that does not exist on the controller and one image policy + that exists on the controller. + + Setup + - ImagePolicies().all_policies, called from instance._build_payloads_to_commit(), + is mocked to indicate that two image policies (KR5M, NR3F) exist on the + controller. + - ImagePolicyUpdateBulk().payloads is set to contain one payload containing + an image policy (FOO) that does not exist on the controller and one payload + containing an image policy (KR5M) that exists on the controller. + + Test + - _payloads_to_commit will contain one payload + - The policyName for this payload will be "KR5M", which is the image policy that + exists on the controller + """ + key = "test_image_policy_update_bulk_00032a" + + with does_not_raise(): + instance = image_policy_update_bulk + instance.results = Results() + instance.payloads = payloads_image_policy_update_bulk(key) + monkeypatch.setattr(instance, "_image_policies", MockImagePolicies(key)) + instance._build_payloads_to_commit() + assert len(instance._payloads_to_commit) == 1 + assert instance._payloads_to_commit[0]["policyName"] == "KR5M" + assert instance._payloads_to_commit[0]["policyDescr"] == "KR5M updated" + + +def test_image_policy_update_bulk_00033(image_policy_update_bulk) -> None: + """ + Classes and Methods + - ImagePolicyUpdateBulk + - commit() + - _build_payloads_to_commit + - fail_json + + Summary + Verify that _build_payloads_to_commit() calls fail_json when + payloads is not set. + + Setup + - ImagePolicyUpdateBulk().payloads is not set + + Test + - fail_json is called because payloads is None + """ + with does_not_raise(): + instance = image_policy_update_bulk + instance.results = Results() + + match = ( + "ImagePolicyUpdateBulk.commit: payloads must be set prior to calling commit." + ) + with pytest.raises(AnsibleFailJson, match=match): + instance.commit() + + +def test_image_policy_update_bulk_00034(monkeypatch, image_policy_update_bulk) -> None: + """ + Classes and Methods + - ImagePolicyUpdateBulk + - payloads setter + - commit() + - _build_payloads_to_commit() + + Summary + Verify that commit() returns without doing anything when payloads + is set to an empty list. + + Setup + - ImagePolicyUpdateBulk().payloads is set to an empty list + + Test + - ImagePolicyUpdateBulk().commit returns without doing anything + """ + key = "test_image_policy_update_bulk_00034a" + + with does_not_raise(): + instance = image_policy_update_bulk + instance.results = Results() + instance.payloads = [] + + monkeypatch.setattr(instance, "_image_policies", MockImagePolicies(key)) + + with does_not_raise(): + instance.commit() + + +def test_image_policy_update_bulk_00035(monkeypatch, image_policy_update_bulk) -> None: + """ + Classes and Methods + - ImagePolicyUpdateBulk + - _build_payloads_to_commit() + - _send_payloads() + - payloads setter + - commit() + + Summary + Verify behavior when a request is made to update two image policies + that exist on the controller. + + Setup + - ImagePolicies().all_policies, is mocked to indicate that two policies + (KR5M, NR3F) exist on the controller. + - ImagePolicyUpdateBulk().payloads is set to contain payloads for KR5M and NR3F + in which policyDescr is different from the existing policyDescr. + - dcnm_send is mocked to return a successful (200) response. + + Test + - commit calls _build_payloads_to_commit which returns two payloads + - commit calls _send_payloads, which calls results.register_task_result() + to update the results. + - results.* are set to the expected values + """ + key = "test_image_policy_update_bulk_00035a" + + def mock_dcnm_send(*args, **kwargs): + return responses_image_policy_update_bulk(key) + + PATCH_DCNM_SEND = "ansible_collections.cisco.dcnm.plugins." + PATCH_DCNM_SEND += "module_utils.common.rest_send.dcnm_send" + + monkeypatch.setattr(PATCH_DCNM_SEND, mock_dcnm_send) + + with does_not_raise(): + instance = image_policy_update_bulk + instance.results = Results() + + monkeypatch.setattr(instance, "_image_policies", MockImagePolicies(key)) + monkeypatch.setattr(PATCH_DCNM_SEND, mock_dcnm_send) + + with does_not_raise(): + instance.payloads = payloads_image_policy_update_bulk(key) + instance.commit() + + payload_0 = payloads_image_policy_update_bulk(key)[0] + # sequence_number is added by the Results class + payload_0["sequence_number"] = 1 + + payload_1 = payloads_image_policy_update_bulk(key)[1] + payload_1["sequence_number"] = 2 + + assert instance.results.action == "update" + assert instance.rest_send.result_current == rest_send_result_current(key) + assert len(instance.results.diff) == 2 + assert len(instance.results.result) == 2 + assert len(instance.results.response) == 2 + assert instance.results.result[0].get("sequence_number") == 1 + assert instance.results.result[1].get("sequence_number") == 2 + assert instance.results.diff[0] == payload_0 + assert instance.results.diff[1] == payload_1 + assert instance.results.diff[0].get("policyDescr") == "KR5M updated" + assert instance.results.diff[1].get("policyDescr") == "NR3F updated" + assert False in instance.results.failed + assert True not in instance.results.failed + assert False not in instance.results.changed + assert True in instance.results.changed + assert len(instance.results.metadata) == 2 + assert instance.results.metadata[0]["action"] == "update" + assert instance.results.metadata[0]["state"] == "merged" + assert instance.results.metadata[0]["sequence_number"] == 1 + assert instance.results.metadata[1]["action"] == "update" + assert instance.results.metadata[1]["state"] == "merged" + assert instance.results.metadata[1]["sequence_number"] == 2 + + +def test_image_policy_update_bulk_00036(monkeypatch, image_policy_update_bulk) -> None: + """ + Classes and Methods + - ImagePolicyUpdateCommon + - payloads setter + - _build_payloads_to_commit() + - _send_payloads() + - ImagePolicyUpdateBulk + - commit() + + Summary + Verify behavior when the controller returns a 500 response to an + image policy update request + + Setup + - ImagePolicies().all_policies, is mocked to indicate that one policy + (KR5M) exists on the controller. + - ImagePolicyUpdateBulk().payloads is set to contain the payload for + image policy KR5M with policyDescr changed. + - dcnm_send is mocked to return a failure (500) response. + + Test + - A sequence_number key is added to instance.results.response_current + - instance.results.diff_current is set to a dict with only + the key "sequence_number", since no changes were made + - instance.results.failed set() contains True and does not contain False + - instance.results.changed set() contains False and does not contain True + - instance.results.metadata contains one dict + - The value of instance.results.metadata "action" is "create" + - The value of instance.results.metadata "state" is "merged" + - The value of instance.results.metadata "sequence_number" is 1 + """ + key = "test_image_policy_update_bulk_00036a" + + PATCH_DCNM_SEND = "ansible_collections.cisco.dcnm.plugins." + PATCH_DCNM_SEND += "module_utils.common.rest_send.dcnm_send" + + def mock_dcnm_send(*args, **kwargs): + return responses_image_policy_update_bulk(key) + + with does_not_raise(): + instance = image_policy_update_bulk + instance.rest_send.unit_test = True + instance.results = Results() + instance.payloads = payloads_image_policy_update_bulk(key) + + monkeypatch.setattr(instance, "_image_policies", MockImagePolicies(key)) + monkeypatch.setattr(PATCH_DCNM_SEND, mock_dcnm_send) + + with does_not_raise(): + instance.commit() + + response_current = responses_image_policy_update_bulk(key) + response_current["sequence_number"] = 1 + assert instance.results.response_current == response_current + assert instance.results.diff_current == {"sequence_number": 1} + assert True in instance.results.failed + assert False not in instance.results.failed + assert True not in instance.results.changed + assert False in instance.results.changed + assert len(instance.results.metadata) == 1 + assert len(instance.results.diff) == 1 + assert instance.results.diff[0] == {"sequence_number": 1} + assert instance.results.metadata[0]["action"] == "update" + assert instance.results.metadata[0]["state"] == "merged" + assert instance.results.metadata[0]["sequence_number"] == 1 + + +def test_image_policy_update_bulk_00037(monkeypatch, image_policy_update_bulk) -> None: + """ + Classes and Methods + - ImagePolicyCreateCommon + - _process_responses() + - ImagePolicyCreateBulk + - __init__() + + Summary + Verify behavior when the controller returns a 200 response to an image policy + create request, followed by a 500 response to a subsequent image policy create + request. + + Setup + - instance.payloads is set to contain two payloads + + Test + - Both successful and bad responses are recorded with separate sequence_numbers. + - instance.results.failed will be a set() containing both True and False + - instance.results.changed will be a set() containing both True and False + - instance.results.response contains two responses + - instance.results.result contains two results + - instance.results.diff contains two diffs + """ + PATCH_DCNM_SEND = "ansible_collections.cisco.dcnm.plugins." + PATCH_DCNM_SEND += "module_utils.common.rest_send.dcnm_send" + + key_policies = "test_image_policy_update_bulk_00037a" + key_ok = "test_image_policy_update_bulk_00037b" + key_nok = "test_image_policy_update_bulk_00037c" + key_payloads = "test_image_policy_update_bulk_00037d" + + def responses(): + yield responses_image_policy_update_bulk(key_policies) + yield responses_image_policy_update_bulk(key_ok) + yield responses_image_policy_update_bulk(key_nok) + + gen = GenerateResponses(responses()) + + def mock_dcnm_send(*args, **kwargs): + item = gen.next + return item + + with does_not_raise(): + instance = image_policy_update_bulk + instance.rest_send.unit_test = True + instance.results = Results() + instance.payloads = payloads_image_policy_update_bulk(key_payloads) + + monkeypatch.setattr(PATCH_DCNM_SEND, mock_dcnm_send) + + with does_not_raise(): + instance.commit() + + assert len(instance.results.diff) == 2 + assert len(instance.results.result) == 2 + assert len(instance.results.response) == 2 + assert len(instance.results.metadata) == 2 + assert instance.results.response[0]["RETURN_CODE"] == 200 + assert instance.results.response[1]["RETURN_CODE"] == 500 + assert False in instance.results.changed + assert True in instance.results.changed + assert False in instance.results.failed + assert True in instance.results.failed + + +def test_image_policy_update_bulk_00040(image_policy_update_bulk) -> None: + """ + Classes and Methods + - ImagePolicyUpdateBulk + - __init__ + - _default_policy + + Summary + Verify that instance._default_policy setter calls fail_json when + passed a policy_name that is not a string. + + Test + - fail_json is called because policy_name is a list + """ + match = "ImagePolicyUpdateBulk._default_policy: " + match += "policy_name must be a string. " + match += r"Got type list for value \[\]" + + with does_not_raise(): + instance = image_policy_update_bulk + instance.results = Results() + with pytest.raises(AnsibleFailJson, match=match): + instance._default_policy([]) + + +def test_image_policy_update_bulk_00050(monkeypatch, image_policy_update_bulk) -> None: + """ + Classes and Methods + - ImagePolicyUpdateCommon + - _build_payloads_to_commit() + - ImagePolicyUpdateBulk + - payloads setter + - commit() + + Summary + Verify that fail_json is called when an image policy update request is made + for an image policy which has a ref_count != 0 on the controller, i.e. + switches are attached to the image policy. + + Setup + - ImagePolicies().all_policies, is mocked to indicate that one policy + (KR5M) exists on the controller with ref_count == 2. + - ImagePolicyUpdateBulk().payloads is set to contain a payload for + image policy KR5M with policyDescr changed. + + Test + - commit calls _build_payloads_to_commit + - _build_payloads_to_commit calls _verify_image_policy_ref_count + - _verify_image_policy_ref_count calls fail_json with the expected message + """ + key = "test_image_policy_update_bulk_00050a" + + with does_not_raise(): + instance = image_policy_update_bulk + instance.results = Results() + instance.payloads = payloads_image_policy_update_bulk(key) + + monkeypatch.setattr(instance, "_image_policies", MockImagePolicies(key)) + + match = "ImagePolicyUpdateBulk._verify_image_policy_ref_count: " + match += "One or more policies have devices attached." + with pytest.raises(AnsibleFailJson, match=match): + instance.commit() diff --git a/tests/unit/modules/dcnm/dcnm_image_policy/utils.py b/tests/unit/modules/dcnm/dcnm_image_policy/utils.py new file mode 100644 index 000000000..093de7318 --- /dev/null +++ b/tests/unit/modules/dcnm/dcnm_image_policy/utils.py @@ -0,0 +1,574 @@ +# Copyright (c) 2024 Cisco and/or its affiliates. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + + +from contextlib import contextmanager +from typing import Any, Dict + +import pytest +from ansible_collections.ansible.netcommon.tests.unit.modules.utils import \ + AnsibleFailJson +from ansible_collections.cisco.dcnm.plugins.module_utils.image_policy.common import \ + ImagePolicyCommon +from ansible_collections.cisco.dcnm.plugins.module_utils.image_policy.create import ( + ImagePolicyCreate, ImagePolicyCreateBulk) +from ansible_collections.cisco.dcnm.plugins.module_utils.image_policy.delete import \ + ImagePolicyDelete +from ansible_collections.cisco.dcnm.plugins.module_utils.image_policy.payload import ( + Config2Payload, Payload2Config) +from ansible_collections.cisco.dcnm.plugins.module_utils.image_policy.query import \ + ImagePolicyQuery +from ansible_collections.cisco.dcnm.plugins.module_utils.image_policy.replace import \ + ImagePolicyReplaceBulk +from ansible_collections.cisco.dcnm.plugins.module_utils.image_policy.update import ( + ImagePolicyUpdate, ImagePolicyUpdateBulk) +from ansible_collections.cisco.dcnm.tests.unit.modules.dcnm.dcnm_image_policy.fixture import \ + load_fixture + + +class GenerateResponses: + """ + Given a generator, return the items in the generator with + each call to the next property + + For usage in the context of dcnm_image_policy unit tests, see: + test: test_image_policy_create_bulk_00037 + file: tests/unit/modules/dcnm/dcnm_image_policy/test_image_policy_create_bulk.py + + Simplified usage example below. + + def responses(): + yield {"key1": "value1"} + yield {"key2": "value2"} + + gen = GenerateResponses(responses()) + + print(gen.next) # {"key1": "value1"} + print(gen.next) # {"key2": "value2"} + """ + + def __init__(self, gen): + self.gen = gen + + @property + def next(self): + """ + Return the next item in the generator + """ + return next(self.gen) + + def public_method_for_pylint(self) -> Any: + """ + Add one public method to appease pylint + """ + + +class MockAnsibleModule: + """ + Mock the AnsibleModule class + """ + + check_mode = False + + params = { + "state": "merged", + "config": {"switches": [{"ip_address": "172.22.150.105"}]}, + "check_mode": False, + } + argument_spec = { + "config": {"required": True, "type": "dict"}, + "state": { + "default": "merged", + "choices": ["deleted", "overridden", "merged", "query", "replaced"], + }, + } + supports_check_mode = True + + @property + def state(self): + """ + return the state + """ + return self.params["state"] + + @state.setter + def state(self, value): + """ + set the state + """ + self.params["state"] = value + + @staticmethod + def fail_json(msg, **kwargs) -> AnsibleFailJson: + """ + mock the fail_json method + """ + raise AnsibleFailJson(msg, kwargs) + + def public_method_for_pylint(self) -> Any: + """ + Add one public method to appease pylint + """ + + +class MockImagePolicies: + """ + Mock the ImagePolicies class to return various values for all_policies + """ + + def __init__(self, key: str) -> None: + self.key = key + self.properties = {} + self.properties["policy_name"] = None + self.properties["results"] = None + + def refresh(self) -> None: + """ + bypass dcnm_send + """ + + @property + def all_policies(self): + """ + Mock the return value of all_policies + all_policies contains all image policies that exist on the controller + """ + return image_policies_all_policies(self.key) + + @property + def name(self): + """ + Return the name of the policy matching self.policy_name, + if it exists. + Return None otherwise + """ + try: + return ( + image_policies_all_policies(self.key) + .get(self.policy_name, None) + .get("policyName") + ) + except AttributeError: + return None + + @property + def policy_name(self): + """ + Set the name of the policy to query. + + This must be set prior to accessing any other properties + """ + return self.properties.get("policy_name") + + @policy_name.setter + def policy_name(self, value): + self.properties["policy_name"] = value + + @property + def ref_count(self): + """ + Return the reference count of the policy matching self.policy_name, + if it exists. The reference count is the number of switches using + this policy. + Return None otherwise + """ + try: + return ( + image_policies_all_policies(self.key) + .get(self.policy_name, None) + .get("ref_count") + ) + except AttributeError: + return None + + @property + def results(self): + """ + An instance of the Results class. + """ + return self.properties["results"] + + @results.setter + def results(self, value): + self.properties["results"] = value + + +# See the following for explanation of why fixtures are explicitely named +# https://pylint.pycqa.org/en/latest/user_guide/messages/warning/redefined-outer-name.html + + +@pytest.fixture(name="image_policy_common") +def image_policy_common_fixture(): + """ + mock ImagePolicyCommon + """ + instance = MockAnsibleModule() + instance.state = "merged" + return ImagePolicyCommon(instance) + + +@pytest.fixture(name="image_policy_create") +def image_policy_create_fixture(): + """ + mock ImagePolicyCreate + """ + instance = MockAnsibleModule() + instance.state = "merged" + return ImagePolicyCreate(instance) + + +@pytest.fixture(name="image_policy_create_bulk") +def image_policy_create_bulk_fixture(): + """ + mock ImagePolicyCreateBulk + """ + instance = MockAnsibleModule() + instance.state = "merged" + return ImagePolicyCreateBulk(instance) + + +@pytest.fixture(name="image_policy_delete") +def image_policy_delete_fixture(): + """ + mock ImagePolicyDelete + """ + instance = MockAnsibleModule() + instance.state = "deleted" + return ImagePolicyDelete(instance) + + +@pytest.fixture(name="image_policy_query") +def image_policy_query_fixture(): + """ + mock ImagePolicyQuery + """ + instance = MockAnsibleModule() + instance.state = "query" + return ImagePolicyQuery(instance) + + +@pytest.fixture(name="image_policy_replace_bulk") +def image_policy_replace_bulk_fixture(): + """ + mock ImagePolicyReplaceBulk + """ + instance = MockAnsibleModule() + instance.state = "replaced" + return ImagePolicyReplaceBulk(instance) + + +@pytest.fixture(name="image_policy_update") +def image_policy_update_fixture(): + """ + mock ImagePolicyUpdate + """ + instance = MockAnsibleModule() + instance.state = "merged" + return ImagePolicyUpdate(instance) + + +@pytest.fixture(name="image_policy_update_bulk") +def image_policy_update_bulk_fixture(): + """ + mock ImagePolicyUpdateBulk + """ + instance = MockAnsibleModule() + instance.state = "merged" + return ImagePolicyUpdateBulk(instance) + + +@pytest.fixture(name="config2payload") +def config2payload_fixture(): + """ + mock Config2Payload + Used in test_image_policy_payload.py + """ + instance = MockAnsibleModule() + instance.state = "merged" + return Config2Payload(instance) + + +@pytest.fixture(name="payload2config") +def payload2config_fixture(): + """ + mock Payload2Config + Used in test_image_policy_payload.py + """ + instance = MockAnsibleModule() + instance.state = "merged" + return Payload2Config(instance) + + +@contextmanager +def does_not_raise(): + """ + A context manager that does not raise an exception. + """ + yield + + +def data_payload(key: str) -> Dict[str, str]: + """ + Return data for unit tests of the Payload() class + """ + data_file = "data_payload" + data = load_fixture(data_file).get(key) + print(f"data_payload: {key} : {data}") + return data + + +def payloads_image_policy_create(key: str) -> Dict[str, str]: + """ + Return payloads for ImagePolicyCreate + """ + data_file = "payloads_ImagePolicyCreate" + data = load_fixture(data_file).get(key) + print(f"{data_file}: {key} : {data}") + return data + + +def payloads_image_policy_create_bulk(key: str) -> Dict[str, str]: + """ + Return payloads for ImagePolicyCreateBulk + """ + data_file = "payloads_ImagePolicyCreateBulk" + data = load_fixture(data_file).get(key) + print(f"{data_file}: {key} : {data}") + return data + + +def payloads_image_policy_replace_bulk(key: str) -> Dict[str, str]: + """ + Return payloads for ImagePolicyReplaceBulk + """ + data_file = "payloads_ImagePolicyReplaceBulk" + data = load_fixture(data_file).get(key) + print(f"{data_file}: {key} : {data}") + return data + + +def payloads_image_policy_update(key: str) -> Dict[str, str]: + """ + Return payloads for ImagePolicyUpdate + """ + data_file = "payloads_ImagePolicyUpdate" + data = load_fixture(data_file).get(key) + print(f"{data_file}: {key} : {data}") + return data + + +def payloads_image_policy_update_bulk(key: str) -> Dict[str, str]: + """ + Return payloads for ImagePolicyUpdateBulk + """ + data_file = "payloads_ImagePolicyUpdateBulk" + data = load_fixture(data_file).get(key) + print(f"{data_file}: {key} : {data}") + return data + + +def responses_image_policies(key: str) -> Dict[str, str]: + """ + Return responses for ImagePolicies + Used in MockImagePolicies + """ + data_file = "responses_ImagePolicies" + data = load_fixture(data_file).get(key) + print(f"{data_file}: {key} : {data}") + return data + + +def responses_image_policy_common(key: str) -> Dict[str, str]: + """ + Return responses for ImagePolicyCommon + """ + data_file = "responses_ImagePolicyCommon" + data = load_fixture(data_file).get(key) + print(f"{data_file}: {key} : {data}") + return data + + +def responses_image_policy_create(key: str) -> Dict[str, str]: + """ + Return responses for ImagePolicyCreate + """ + data_file = "responses_ImagePolicyCreate" + data = load_fixture(data_file).get(key) + print(f"{data_file}: {key} : {data}") + return data + + +def responses_image_policy_create_bulk(key: str) -> Dict[str, str]: + """ + Return responses for ImagePolicyCreateBulk + """ + data_file = "responses_ImagePolicyCreateBulk" + data = load_fixture(data_file).get(key) + print(f"{data_file}: {key} : {data}") + return data + + +def responses_image_policy_delete(key: str) -> Dict[str, str]: + """ + Return responses for ImagePolicyDelete + """ + data_file = "responses_ImagePolicyDelete" + data = load_fixture(data_file).get(key) + print(f"{data_file}: {key} : {data}") + return data + + +def responses_image_policy_replace_bulk(key: str) -> Dict[str, str]: + """ + Return responses for ImagePolicyReplaceBulk + """ + data_file = "responses_ImagePolicyReplaceBulk" + data = load_fixture(data_file).get(key) + print(f"{data_file}: {key} : {data}") + return data + + +def responses_image_policy_update(key: str) -> Dict[str, str]: + """ + Return responses for ImagePolicyUpdate + """ + data_file = "responses_ImagePolicyUpdate" + data = load_fixture(data_file).get(key) + print(f"{data_file}: {key} : {data}") + return data + + +def responses_image_policy_update_bulk(key: str) -> Dict[str, str]: + """ + Return responses for ImagePolicyUpdateBulk + """ + data_file = "responses_ImagePolicyUpdateBulk" + data = load_fixture(data_file).get(key) + print(f"{data_file}: {key} : {data}") + return data + + +def results_image_policies(key: str) -> Dict[str, str]: + """ + Return results for ImagePolicies + Used in MockImagePolicies + """ + data_file = "results_ImagePolicies" + data = load_fixture(data_file).get(key) + print(f"{data_file}: {key} : {data}") + return data + + +def results_image_policy_common(key: str) -> Dict[str, str]: + """ + Return results for ImagePolicyCommon + """ + data_file = "results_ImagePolicyCommon" + data = load_fixture(data_file).get(key) + print(f"{data_file}: {key} : {data}") + return data + + +def results_image_policy_create_bulk(key: str) -> Dict[str, str]: + """ + Return results for ImagePolicyCreateBulk + """ + data_file = "results_ImagePolicyCreateBulk" + data = load_fixture(data_file).get(key) + print(f"{data_file}: {key} : {data}") + return data + + +def results_image_policy_delete(key: str) -> Dict[str, str]: + """ + Return results for ImagePolicyDelete + """ + data_file = "results_ImagePolicyDelete" + data = load_fixture(data_file).get(key) + print(f"{data_file}: {key} : {data}") + return data + + +def results_image_policy_replace_bulk(key: str) -> Dict[str, str]: + """ + Return results for ImagePolicyReplaceBulk + """ + data_file = "results_ImagePolicyReplaceBulk" + data = load_fixture(data_file).get(key) + print(f"{data_file}: {key} : {data}") + return data + + +def results_image_policy_task_result(key: str) -> Dict[str, str]: + """ + Return results for ImagePolicyTaskResult + """ + data_file = "results_ImagePolicyTaskResult" + data = load_fixture(data_file).get(key) + print(f"{data_file}: {key} : {data}") + return data + + +def results_image_policy_update(key: str) -> Dict[str, str]: + """ + Return results for ImagePolicyUpdate + """ + data_file = "results_ImagePolicyUpdate" + data = load_fixture(data_file).get(key) + print(f"{data_file}: {key} : {data}") + return data + + +def results_image_policy_update_bulk(key: str) -> Dict[str, str]: + """ + Return results for ImagePolicyUpdateBulk + """ + data_file = "results_ImagePolicyUpdateBulk" + data = load_fixture(data_file).get(key) + print(f"{data_file}: {key} : {data}") + return data + + +def image_policies_all_policies(key: str) -> Dict[str, str]: + """ + Return mocked return values for ImagePolicies().all_policies property + """ + data_file = "all_policies_ImagePolicies" + data = load_fixture(data_file).get(key) + print(f"{data_file}: {key} : {data}") + return data + + +def rest_send_response_current(key: str) -> Dict[str, str]: + """ + Mocked return values for RestSend().response_current property + """ + data_file = "response_current_RestSend" + data = load_fixture(data_file).get(key) + print(f"{data_file}: {key} : {data}") + return data + + +def rest_send_result_current(key: str) -> Dict[str, str]: + """ + Mocked return values for RestSend().result_current property + """ + data_file = "result_current_RestSend" + data = load_fixture(data_file).get(key) + print(f"{data_file}: {key} : {data}") + return data