diff --git a/playbooks/roles/dcnm_fabric/dcnm_tests.yaml b/playbooks/roles/dcnm_fabric/dcnm_tests.yaml index 32e02f670..e125309b3 100644 --- a/playbooks/roles/dcnm_fabric/dcnm_tests.yaml +++ b/playbooks/roles/dcnm_fabric/dcnm_tests.yaml @@ -1,8 +1,10 @@ --- # This playbook can be used to execute integration tests for -# the role located in: +# the roles located in: # -# Modify the vars section with details for testing setup. +# ./tests/integration/targets/dcnm_fabric/tests/*.yaml +# +# Modify the vars section with details for your testing setup. # # NOTES: # 1. For the IPFM test cases (dcnm_*_ipfm), ensure that the controller @@ -17,17 +19,23 @@ connection: ansible.netcommon.httpapi vars: - # This testcase field can run any test in the tests directory for the role + # Uncomment ONE of the following testcases # testcase: dcnm_fabric_deleted_basic # testcase: dcnm_fabric_deleted_basic_ipfm + # testcase: dcnm_fabric_deleted_basic_lan_classic + # testcase: dcnm_fabric_deleted_basic_msd + # testcase: dcnm_fabric_deleted_basic_vxlan # testcase: dcnm_fabric_merged_basic # testcase: dcnm_fabric_merged_basic_ipfm # testcase: dcnm_fabric_merged_save_deploy # testcase: dcnm_fabric_merged_save_deploy_ipfm # testcase: dcnm_fabric_replaced_basic + # testcase: dcnm_fabric_replaced_basic_vxlan + # testcase: dcnm_fabric_replaced_basic_vxlan_site_id # testcase: dcnm_fabric_replaced_basic_ipfm # testcase: dcnm_fabric_replaced_save_deploy # testcase: dcnm_fabric_replaced_save_deploy_ipfm + # testcase: dcnm_fabric_query_basic.yaml fabric_name_1: VXLAN_EVPN_Fabric fabric_type_1: VXLAN_EVPN fabric_name_2: VXLAN_EVPN_MSD_Fabric diff --git a/playbooks/roles/dcnm_image_upgrade/dcnm_hosts.yaml b/playbooks/roles/dcnm_image_upgrade/dcnm_hosts.yaml new file mode 100644 index 000000000..109612797 --- /dev/null +++ b/playbooks/roles/dcnm_image_upgrade/dcnm_hosts.yaml @@ -0,0 +1,15 @@ +all: + vars: + ansible_user: "admin" + ansible_password: "password-ndfc" + switch_password: "password-switch" + ansible_python_interpreter: python + ansible_httpapi_validate_certs: False + ansible_httpapi_use_ssl: True + children: + ndfc: + vars: + ansible_connection: ansible.netcommon.httpapi + ansible_network_os: cisco.dcnm.dcnm + hosts: + 192.168.1.1: diff --git a/playbooks/roles/dcnm_image_upgrade/dcnm_tests.yaml b/playbooks/roles/dcnm_image_upgrade/dcnm_tests.yaml new file mode 100644 index 000000000..47baf696c --- /dev/null +++ b/playbooks/roles/dcnm_image_upgrade/dcnm_tests.yaml @@ -0,0 +1,46 @@ +--- +# This playbook can be used to execute integration tests for +# the role located in: +# +# tests/integration/targets/dcnm_image_upgrade +# +# Modify the hosts and vars sections with details for your testing +# setup and uncomment the testcase you want to run. +# +- hosts: dcnm + gather_facts: no + connection: ansible.netcommon.httpapi + + vars: + # testcase: 00_setup_create_fabric + # testcase: 01_setup_add_switches_to_fabric + # testcase: 02_setup_replace_image_policies + # testcase: deleted + # testcase: deleted_1x_switch + # testcase: merged_global_config + # testcase: merged_override_global_config + # testcase: query + fabric_name: LAN_Classic_Fabric + switch_username: admin + switch_password: "{{ switch_password }}" + leaf1: 192.168.1.2 + leaf2: 192.168.1.3 + spine1: 192.168.1.4 + # for dcnm_image_policy and dcnm_image_upgrade roles + image_policy_1: NR1F + image_policy_2: NR2F + # for dcnm_image_policy role + epld_image_1: n9000-epld.10.3.1.F.img + epld_image_2: n9000-epld.10.3.1.F.img + nxos_image_1: nxos64-cs.10.3.1.F.bin + nxos_image_2: nxos64-cs.10.3.2.F.bin + nxos_release_1: 10.3.1_nxos64-cs_64bit + nxos_release_2: 10.3.2_nxos64-cs_64bit + # for dcnm_image_upgrade role + fabric_name_1: "{{ fabric_name }}" + ansible_switch_1: "{{ leaf1 }}" + ansible_switch_2: "{{ leaf2 }}" + ansible_switch_3: "{{ spine1 }}" + + roles: + - dcnm_image_upgrade diff --git a/plugins/module_utils/common/api/v1/imagemanagement/rest/packagemgnt/packagemgnt.py b/plugins/module_utils/common/api/v1/imagemanagement/rest/packagemgnt/packagemgnt.py new file mode 100644 index 000000000..1426ff0fb --- /dev/null +++ b/plugins/module_utils/common/api/v1/imagemanagement/rest/packagemgnt/packagemgnt.py @@ -0,0 +1,87 @@ +# 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 + +from ansible_collections.cisco.dcnm.plugins.module_utils.common.api.v1.imagemanagement.rest.rest import \ + Rest + + +class PackageMgnt(Rest): + """ + ## api.v1.imagemanagement.rest.packagemgnt.PackageMgnt() + + ### Description + Common methods and properties for PackageMgnt() subclasses + + ### Path + ``/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/packagemgnt`` + """ + + def __init__(self): + super().__init__() + self.class_name = self.__class__.__name__ + self.log = logging.getLogger(f"dcnm.{self.class_name}") + self.packagemgnt = f"{self.rest}/packagemgnt" + self.log.debug("ENTERED api.v1.PackageMgnt()") + + +class EpIssu(PackageMgnt): + """ + ## api.v1.imagemanagement.rest.packagemgnt.EpIssu() + + ### Description + Return endpoint information. + + ### Raises + - None + + ### Path + - ``/api/v1/imagemanagement/rest/packagemgnt/issu`` + + ### Verb + - GET + + ### Parameters + - path: retrieve the path for the endpoint + - verb: retrieve the verb for the endpoint + + ### Usage + ```python + instance = EpIssu() + path = instance.path + verb = instance.verb + ``` + """ + + def __init__(self): + super().__init__() + self.class_name = self.__class__.__name__ + self.log = logging.getLogger(f"dcnm.{self.class_name}") + msg = "ENTERED api.v1.imagemanagement.rest." + msg += f"packagemgnt.{self.class_name}" + self.log.debug(msg) + + @property + def path(self): + return f"{self.packagemgnt}/issu" + + @property + def verb(self): + return "GET" diff --git a/plugins/module_utils/common/api/v1/imagemanagement/rest/policymgnt/policymgnt.py b/plugins/module_utils/common/api/v1/imagemanagement/rest/policymgnt/policymgnt.py index aff504d0f..f01da95de 100644 --- a/plugins/module_utils/common/api/v1/imagemanagement/rest/policymgnt/policymgnt.py +++ b/plugins/module_utils/common/api/v1/imagemanagement/rest/policymgnt/policymgnt.py @@ -299,18 +299,52 @@ def __init__(self): super().__init__() self.class_name = self.__class__.__name__ self.log = logging.getLogger(f"dcnm.{self.class_name}") + self._serial_numbers = None msg = "ENTERED api.v1.imagemanagement.rest." msg += f"policymgnt.{self.class_name}" self.log.debug(msg) @property def path(self): - return f"{self.policymgnt}/detach-policy" + """ + ### Summary + The endpoint path. + + ### Raises + - ``ValueError`` if: + - ``path`` is accessed before setting ``serial_numbers``. + """ + if self.serial_numbers is None: + msg = f"{self.class_name}.serial_numbers must be set before " + msg += f"accessing {self.class_name}.path." + raise ValueError(msg) + query_param = ",".join(self.serial_numbers) + return f"{self.policymgnt}/detach-policy?serialNumber={query_param}" @property def verb(self): return "DELETE" + @property + def serial_numbers(self): + """ + ### Summary + A ``list`` of switch serial numbers. + + ### Raises + - ``TypeError`` if: + - ``serial_numbers`` is not a ``list``. + """ + return self._serial_numbers + + @serial_numbers.setter + def serial_numbers(self, value): + if not isinstance(value, list): + msg = f"{self.class_name}.serial_numbers must be a list " + msg += "of switch serial numbers." + raise TypeError(msg) + self._serial_numbers = value + class EpPolicyEdit(PolicyMgnt): """ diff --git a/plugins/module_utils/common/controller_features.py b/plugins/module_utils/common/controller_features.py index ba87a9c59..3963e44e2 100644 --- a/plugins/module_utils/common/controller_features.py +++ b/plugins/module_utils/common/controller_features.py @@ -23,7 +23,6 @@ __copyright__ = "Copyright (c) 2024 Cisco and/or its affiliates." __author__ = "Allen Robel" -import copy import inspect import logging @@ -33,73 +32,105 @@ ConversionUtils from ansible_collections.cisco.dcnm.plugins.module_utils.common.exceptions import \ ControllerResponseError +from ansible_collections.cisco.dcnm.plugins.module_utils.common.properties import \ + Properties +@Properties.add_rest_send class ControllerFeatures: """ - - Return feature information from the Controller - - Endpoint: /appcenter/cisco/ndfc/api/v1/fm/features - - Usage (where params is AnsibleModule.params): + ### Summary + Return feature information from the Controller + + ### Usage ```python - instance = ControllerFeatures(params) - instance.rest_send = RestSend(AnsibleModule) - # retrieves all feature information - try: - instance.refresh() - except ControllerResponseError as error: - # handle error - # filters the feature information - instance.filter = "pmn" - # retrieves the admin_state for feature pmn - pmn_admin_state = instance.admin_state - # retrieves the operational state for feature pmn - pmn_oper_state = instance.oper_state - # etc... + sender = Sender() + sender.ansible_module = ansible_module + rest_send = RestSend(ansible_module.params) + rest_send.response_handler = ResponseHandler() + rest_send.sender = sender + + instance = ControllerFeatures() + instance.rest_send = rest_send + # retrieves all feature information + try: + instance.refresh() + except ControllerResponseError as error: + # handle error + # filters the feature information + instance.filter = "pmn" + # retrieves the admin_state for feature pmn + pmn_admin_state = instance.admin_state + # retrieves the operational state for feature pmn + pmn_oper_state = instance.oper_state + # etc... ``` - - Retrievable properties for the filtered feature - - admin_state - str - - "enabled" - - "disabled" - - apidoc - list of dict - - [ - { - "url": "https://path/to/api-docs", - "subpath": "pmn", - "schema": null - } - ] - - description - str - - "Media Controller for IP Fabrics" - - healthz - str - - "https://path/to/healthz" - - hidden - bool - - True - - False - - featureset - dict - - { "lan": { "default": false }} - - name - str - - "IP Fabric for Media" - - oper_state - str - - "started" - - "stopped" - - "" - - predisablecheck - str - - "https://path/to/predisablecheck" - - installed - str - - "2024-05-08 18:02:45.626691263 +0000 UTC" - - kind - str - - "feature" - - requires - list - - ["pmn-telemetry-mgmt", "pmn-telemetry-data"] - - spec - str - - "" - - ui - bool - - True - - False - - Response: + ### Retrievable properties for the filtered feature + + - admin_state - str + - "enabled" + - "disabled" + - apidoc, list of dict + - ```json + [ + { + "url": "https://path/to/api-docs", + "subpath": "pmn", + "schema": null + } + ] + ``` + - description + - "Media Controller for IP Fabrics" + - str + - healthz + - "https://path/to/healthz" + - str + - hidden + - True + - False + - bool + - featureset + - ```json + { + "lan": { + "default": false + } + } + ``` + - name + - "IP Fabric for Media" + - str + - oper_state + - "started" + - "stopped" + - "" + - str + - predisablecheck + - "https://path/to/predisablecheck" + - str + - installed + - "2024-05-08 18:02:45.626691263 +0000 UTC" + - str + - kind + - "feature" + - str + - requires + - ```json + ["pmn-telemetry-mgmt", "pmn-telemetry-data"] + ``` + - spec + - "" + - str + - ui + - True + - False + - bool + + ### Response + ```json { "status": "success", "data": { @@ -125,32 +156,25 @@ class ControllerFeatures: } } } + ``` + + ### Endpoint + /appcenter/cisco/ndfc/api/v1/fm/features + """ - def __init__(self, params): + def __init__(self): self.class_name = self.__class__.__name__ - self.params = params self.log = logging.getLogger(f"dcnm.{self.class_name}") self.log.debug("ENTERED ControllerFeatures()") - self.check_mode = self.params.get("check_mode", None) - if self.check_mode is None: - msg = f"{self.class_name}.__init__(): " - msg += "check_mode is required." - raise ValueError(msg) - self.conversion = ConversionUtils() - self.api_features = EpFeatures() - self._init_properties() + self.ep_features = EpFeatures() - def _init_properties(self): - self.properties = {} - self.properties["filter"] = None - self.properties["rest_send"] = None - self.properties["result"] = None - self.properties["response"] = None - self.properties["response_data"] = None + self._filter = None + self._rest_send = None + self._response_data = None def refresh(self): """ @@ -160,33 +184,31 @@ def refresh(self): """ method_name = inspect.stack()[0][3] + # pylint: disable=no-member if self.rest_send is None: msg = f"{self.class_name}.{method_name}: " msg += f"{self.class_name}.rest_send must be set " msg += "before calling refresh()." raise ValueError(msg) - self.rest_send.path = self.api_features.path - self.rest_send.verb = self.api_features.verb + self.rest_send.path = self.ep_features.path + self.rest_send.verb = self.ep_features.verb # Store the current value of check_mode, then disable # check_mode since ControllerFeatures() only reads data # from the controller. # Restore the value of check_mode after the commit. - current_check_mode = self.rest_send.check_mode + self.rest_send.save_settings() self.rest_send.check_mode = False self.rest_send.commit() - self.rest_send.check_mode = current_check_mode + self.rest_send.restore_settings() - self.properties["result"] = copy.deepcopy(self.rest_send.result_current) - if self.result["success"] is False: + if self.rest_send.result_current["success"] is False: msg = f"{self.class_name}.{method_name}: " msg += f"Bad controller response: {self.rest_send.response_current}" raise ControllerResponseError(msg) - self.properties["response"] = copy.deepcopy(self.rest_send.response_current) - - self.properties["response_data"] = ( + self._response_data = ( self.rest_send.response_current.get("DATA", {}) .get("data", {}) .get("features", {}) @@ -247,11 +269,11 @@ def filter(self): NX-OS and Other devices """ - return self.properties.get("filter") + return self._filter @filter.setter def filter(self, value): - self.properties["filter"] = value + self._filter = value @property def oper_state(self): @@ -265,50 +287,12 @@ def oper_state(self): """ return self._get("oper_state") - @property - def response(self): - """ - Return the GET response from the Controller - """ - return self.properties.get("response") - @property def response_data(self): """ Return the data retrieved from the request """ - return self.properties.get("response_data") - - @property - def rest_send(self): - """ - - An instance of the RestSend class. - - Raise ``TypeError`` if the value is not an instance of RestSend. - """ - return self.properties.get("rest_send") - - @rest_send.setter - def rest_send(self, value): - method_name = inspect.stack()[0][3] - _class_name = None - msg = f"{self.class_name}.{method_name}: " - msg += "value must be an instance of RestSend. " - try: - _class_name = value.class_name - except AttributeError as error: - msg += f"Error detail: {error}." - raise TypeError(msg) from error - if _class_name != "RestSend": - self.log.debug(msg) - raise TypeError(msg) - self.properties["rest_send"] = value - - @property - def result(self): - """ - Return the GET result from the Controller - """ - return self.properties.get("result") + return self._response_data @property def started(self): diff --git a/plugins/module_utils/common/controller_version.py b/plugins/module_utils/common/controller_version.py index 7ae26652d..0ef48104e 100644 --- a/plugins/module_utils/common/controller_version.py +++ b/plugins/module_utils/common/controller_version.py @@ -19,17 +19,21 @@ __copyright__ = "Copyright (c) 2024 Cisco and/or its affiliates." __author__ = "Allen Robel" +import inspect import logging from ansible_collections.cisco.dcnm.plugins.module_utils.common.api.v1.fm.fm import \ EpVersion -from ansible_collections.cisco.dcnm.plugins.module_utils.image_upgrade.image_upgrade_common import \ - ImageUpgradeCommon -from ansible_collections.cisco.dcnm.plugins.module_utils.network.dcnm.dcnm import \ - dcnm_send +from ansible_collections.cisco.dcnm.plugins.module_utils.common.conversion import \ + ConversionUtils +from ansible_collections.cisco.dcnm.plugins.module_utils.common.exceptions import \ + ControllerResponseError +from ansible_collections.cisco.dcnm.plugins.module_utils.common.properties import \ + Properties -class ControllerVersion(ImageUpgradeCommon): +@Properties.add_rest_send +class ControllerVersion: """ Return image version information from the Controller @@ -38,7 +42,14 @@ class ControllerVersion(ImageUpgradeCommon): ### Usage (where module is an instance of AnsibleModule): ```python - instance = ControllerVersion(module) + sender = Sender() + sender.ansible_module = ansible_module + rest_send = RestSend(ansible_module.params) + rest_send.response_handler = ResponseHandler() + rest_send.sender = sender + + instance = ControllerVersion() + instance.rest_send = rest_send instance.refresh() if instance.version == "12.1.2e": # do 12.1.2e stuff @@ -61,44 +72,51 @@ class ControllerVersion(ImageUpgradeCommon): ``` """ - def __init__(self, ansible_module): - super().__init__(ansible_module) + def __init__(self): self.class_name = self.__class__.__name__ + method_name = inspect.stack()[0][3] self.log = logging.getLogger(f"dcnm.{self.class_name}") - self.log.debug("ENTERED ControllerVersion()") + self.conversion = ConversionUtils() self.ep_version = EpVersion() - self._init_properties() + self._response_data = None + self._rest_send = None - def _init_properties(self): - self.properties = {} - self.properties["data"] = None - self.properties["result"] = None - self.properties["response"] = None + msg = f"ENTERED {self.class_name}().{method_name}" + self.log.debug(msg) def refresh(self): """ Refresh self.response_data with current version info from the Controller """ - path = self.ep_version.path - verb = self.ep_version.verb - self.properties["response"] = dcnm_send(self.ansible_module, verb, path) - self.properties["result"] = self._handle_response(self.response, verb) - - if self.result["success"] is False or self.result["found"] is False: - msg = f"{self.class_name}.refresh() failed: {self.result}" - self.ansible_module.fail_json(msg) - - self.properties["response_data"] = self.response.get("DATA") + # pylint: disable=no-member + method_name = inspect.stack()[0][3] + self.rest_send.path = self.ep_version.path + self.rest_send.verb = self.ep_version.verb + self.rest_send.commit() + + if self.rest_send.result_current["success"] is False: + msg = f"{self.class_name}.{method_name}: " + msg += f"failed: {self.rest_send.result_current}" + raise ControllerResponseError(msg) + + if self.rest_send.result_current["found"] is False: + msg = f"{self.class_name}.{method_name}: " + msg += f"failed: {self.rest_send.result_current}" + raise ControllerResponseError(msg) + + self._response_data = self.rest_send.response_current.get("DATA") if self.response_data is None: msg = f"{self.class_name}.refresh() failed: response " msg += "does not contain DATA key. Controller response: " - msg += f"{self.response}" - self.ansible_module.fail_json(msg) + msg += f"{self.rest_send.response_current}" + raise ValueError(msg) def _get(self, item): - return self.make_boolean(self.make_none(self.response_data.get(item))) + return self.conversion.make_none( + self.conversion.make_boolean(self.response_data.get(item)) + ) @property def dev(self): @@ -139,7 +157,7 @@ def is_ha_enabled(self): False None """ - return self.make_boolean(self._get("isHaEnabled")) + return self._get("isHaEnabled") @property def is_media_controller(self): @@ -153,7 +171,7 @@ def is_media_controller(self): False None """ - return self.make_boolean(self._get("isMediaController")) + return self._get("isMediaController") @property def is_upgrade_inprogress(self): @@ -167,28 +185,14 @@ def is_upgrade_inprogress(self): False None """ - return self.make_boolean(self._get("is_upgrade_inprogress")) + return self._get("is_upgrade_inprogress") @property def response_data(self): """ Return the data retrieved from the request """ - return self.properties.get("response_data") - - @property - def result(self): - """ - Return the GET result from the Controller - """ - return self.properties.get("result") - - @property - def response(self): - """ - Return the GET response from the Controller - """ - return self.properties.get("response") + return self._response_data @property def mode(self): diff --git a/plugins/module_utils/common/image_policies.py b/plugins/module_utils/common/image_policies.py new file mode 100644 index 000000000..4ea3b28a7 --- /dev/null +++ b/plugins/module_utils/common/image_policies.py @@ -0,0 +1,368 @@ +# 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.common.api.v1.imagemanagement.rest.policymgnt.policymgnt import \ + EpPolicies +from ansible_collections.cisco.dcnm.plugins.module_utils.common.conversion import \ + ConversionUtils +from ansible_collections.cisco.dcnm.plugins.module_utils.common.exceptions import \ + ControllerResponseError +from ansible_collections.cisco.dcnm.plugins.module_utils.common.properties import \ + Properties + + +@Properties.add_rest_send +@Properties.add_results +class ImagePolicies: + """ + ### Summary + Retrieve image policy details from the controller and provide + property accessors for the policy attributes. + + ### Usage + + ```python + sender = Sender() + sender.ansible_module = ansible_module + rest_send = RestSend(ansible_module.params) + rest_send.response_handler = ResponseHandler() + rest_send.sender = sender + + instance = ImagePolicies() + instance.rest_send = rest_send + instance.results = Results() + instance.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): + self.class_name = self.__class__.__name__ + method_name = inspect.stack()[0][3] # pylint: disable=unused-variable + + self.log = logging.getLogger(f"dcnm.{self.class_name}") + + self._all_policies = None + self.conversion = ConversionUtils() + self.ep_policies = EpPolicies() + self.data = {} + self._policy_name = None + self._response_data = None + self._results = None + self._rest_send = None + + msg = f"ENTERED {self.class_name}.{method_name}" + self.log.debug(msg) + + # pylint: disable=no-member + def refresh(self): + """ + ### Summary + 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. + + ### Raises + - ``ControllerResponseError`` if: + - The controller response is missing the expected data. + - ``ValueError`` if: + - ``rest_send`` is not set. + - ``results`` is not set. + - The controller response cannot be parsed. + + ### Notes + - pylint: disable=no-member is needed because the rest_send, results, + and params properties are dynamically created by the + @Properties class decorators. + """ + method_name = inspect.stack()[0][3] + msg = f"ENTERED {self.class_name}.{method_name}" + self.log.debug(msg) + + if self.rest_send is None: + msg = f"{self.class_name}.{method_name}: " + msg += "instance.rest_send must be set before calling refresh." + raise ValueError(msg) + if self.results is None: + msg = f"{self.class_name}.{method_name}: " + msg += "instance.results must be set before calling refresh." + raise ValueError(msg) + + # We always want to get the controller's current image policy + # state. We set check_mode to False here so the request will be + # sent to the controller. + self.rest_send.save_settings() + self.rest_send.check_mode = False + self.rest_send.path = self.ep_policies.path + self.rest_send.verb = self.ep_policies.verb + self.rest_send.commit() + self.rest_send.restore_settings() + + data = self.rest_send.response_current.get("DATA", {}).get("lastOperDataObject") + + if data is None: + msg = f"{self.class_name}.{method_name}: " + msg += "Bad response when retrieving image policy " + msg += "information from the controller." + raise ControllerResponseError(msg) + + if len(data) == 0: + msg = "the controller has no defined image policies." + self.log.debug(msg) + + if self.rest_send.result_current["success"] is not True: + msg = f"{self.class_name}.{method_name}: " + msg += "Failed to retrieve image policy information from " + msg += "the controller. " + msg += f"Controller response: {self.rest_send.response_current}" + raise ControllerResponseError(msg) + + self._response_data = {} + self._all_policies = {} + self.data = {} + + for policy in data: + policy_name = policy.get("policyName") + if policy_name is None: + msg = f"{self.class_name}.{method_name}: " + msg += "Cannot parse policy information from the controller." + raise ValueError(msg) + self.data[policy_name] = policy + self._response_data[policy_name] = policy + + self._all_policies = copy.deepcopy(self._response_data) + + self.results.response_current = self.rest_send.response_current + self.results.result_current = self.rest_send.result_current + + def _get(self, item): + """ + ### Summary + Return the value of item from the policy matching self.policy_name. + + ### Raises + - ``ValueError`` if ``policy_name`` is not set.. + """ + method_name = inspect.stack()[0][3] + + if self.policy_name is None: + msg = f"{self.class_name}.{method_name}: " + msg += "instance.policy_name must be set before " + msg += f"accessing property {item}." + raise ValueError(msg) + + if self.policy_name not in self._response_data: + return None + + if item == "policy": + return self._response_data[self.policy_name] + + if item not in self._response_data[self.policy_name]: + msg = f"{self.class_name}.{method_name}: " + msg += f"{self.policy_name} does not have a key named {item}." + raise ValueError(msg) + + return self.conversion.make_boolean( + self.conversion.make_none(self._response_data[self.policy_name][item]) + ) + + @property + def all_policies(self) -> dict: + """ + ### Summary + Return dict containing all policies, keyed on policy_name. + """ + if self._all_policies is None: + return {} + return self._all_policies + + @property + def description(self): + """ + ### Summary + - Return the ``policyDescr`` of the policy matching ``policy_name``, + if it exists. + - Return None otherwise. + """ + return self._get("policyDescr") + + @property + def epld_image_name(self): + """ + ### Summary + - Return the ``epldImgName`` of the policy matching ``policy_name``, + if it exists. + - Return None otherwise. + """ + return self._get("epldImgName") + + @property + def name(self): + """ + ### Summary + - Return the ``name`` of the policy matching ``policy_name``, + if it exists. + - Return None otherwise. + """ + return self._get("policyName") + + @property + def policy_name(self): + """ + ### Summary + Set the name of the policy to query. + + This must be set prior to accessing any other properties + """ + return self._policy_name + + @policy_name.setter + def policy_name(self, value): + self._policy_name = value + + @property + def policy(self): + """ + ### Summary + - Return the policy data of the policy matching ``policy_name``, + if it exists. + - Return None otherwise. + """ + return self._get("policy") + + @property + def policy_type(self): + """ + ### Summary + - Return the ``policyType`` of the policy matching ``policy_name``, + if it exists. + - Return None otherwise. + """ + return self._get("policyType") + + @property + def response_data(self) -> dict: + """ + ### Summary + - Return dict containing the DATA portion of a controller response, + keyed on ``policy_name``. + - Return an empty dict otherwise. + """ + if self._response_data is None: + return {} + return self._response_data + + @property + def nxos_version(self): + """ + ### Summary + - Return the ``nxosVersion`` of the policy matching ``policy_name``, + if it exists. + - Return None otherwise. + """ + return self._get("nxosVersion") + + @property + def package_name(self): + """ + ### Summary + - Return the ``packageName`` of the policy matching ``policy_name``, + if it exists. + - Return None otherwise. + """ + return self._get("packageName") + + @property + def platform(self): + """ + ### Summary + - Return the ``platform`` of the policy matching ``policy_name``, + if it exists. + - Return None otherwise. + """ + return self._get("platform") + + @property + def platform_policies(self): + """ + ### Summary + - Return the ``platformPolicies`` of the policy matching + ``policy_name``, if it exists. + - Return None otherwise. + """ + return self._get("platformPolicies") + + @property + def ref_count(self): + """ + ### Summary + - Return the reference count of the policy matching ``policy_name``, + if it exists. The reference count indicates the number of + switches using this policy. + - Return None otherwise. + """ + return self._get("ref_count") + + @property + def rpm_images(self): + """ + ### Summary + - Return the ``rpmimages`` of the policy matching ``policy_name``, + if it exists. + - Return None otherwise. + """ + return self._get("rpmimages") + + @property + def image_name(self): + """ + ### Summary + - Return the ``imageName`` of the policy matching ``policy_name``, + if it exists. + - Return None otherwise. + """ + return self._get("imageName") + + @property + def agnostic(self): + """ + ### Summary + - Return the value of agnostic for the policy matching + ``policy_name``, if it exists. + - Return None otherwise. + """ + return self._get("agnostic") diff --git a/plugins/module_utils/common/maintenance_mode_info.py b/plugins/module_utils/common/maintenance_mode_info.py index a59c8d4e8..fa239a539 100644 --- a/plugins/module_utils/common/maintenance_mode_info.py +++ b/plugins/module_utils/common/maintenance_mode_info.py @@ -130,7 +130,7 @@ def __init__(self, params): self.params = params self.conversion = ConversionUtils() - self.fabric_details = FabricDetailsByName(self.params) + self.fabric_details = FabricDetailsByName() self.switch_details = SwitchDetails() self._config = None diff --git a/plugins/module_utils/common/merge_dicts.py b/plugins/module_utils/common/merge_dicts.py index f9102f9eb..7630f877c 100644 --- a/plugins/module_utils/common/merge_dicts.py +++ b/plugins/module_utils/common/merge_dicts.py @@ -22,7 +22,6 @@ import inspect import logging from collections.abc import MutableMapping as Map -from typing import Any, Dict class MergeDicts: @@ -78,9 +77,7 @@ def commit(self) -> None: self.properties["dict_merged"] = self.merge_dicts(self.dict1, self.dict2) - def merge_dicts( - self, dict1: Dict[Any, Any], dict2: Dict[Any, Any] - ) -> Dict[Any, Any]: + def merge_dicts(self, dict1: dict, dict2: dict) -> dict: """ Merge dict2 into dict1 and return dict1. Keys in dict2 have precedence over keys in dict1. diff --git a/plugins/module_utils/common/merge_dicts_v2.py b/plugins/module_utils/common/merge_dicts_v2.py index 5f7009519..bc1ef6838 100644 --- a/plugins/module_utils/common/merge_dicts_v2.py +++ b/plugins/module_utils/common/merge_dicts_v2.py @@ -22,7 +22,6 @@ import inspect import logging from collections.abc import MutableMapping as Map -from typing import Any, Dict class MergeDicts: @@ -90,9 +89,7 @@ def commit(self) -> None: self.properties["dict_merged"] = self.merge_dicts(self.dict1, self.dict2) - def merge_dicts( - self, dict1: Dict[Any, Any], dict2: Dict[Any, Any] - ) -> Dict[Any, Any]: + def merge_dicts(self, dict1: dict, dict2: dict) -> dict: """ Merge dict2 into dict1 and return dict1. Keys in dict2 have precedence over keys in dict1. diff --git a/plugins/module_utils/common/params_merge_defaults.py b/plugins/module_utils/common/params_merge_defaults.py index 24c3e0bd7..e0c598f8d 100644 --- a/plugins/module_utils/common/params_merge_defaults.py +++ b/plugins/module_utils/common/params_merge_defaults.py @@ -22,7 +22,6 @@ import inspect import logging from collections.abc import MutableMapping as Map -from typing import Any, Dict class ParamsMergeDefaults: @@ -75,9 +74,7 @@ def _build_reserved_params(self): self.reserved_params.add("type") self.reserved_params.add("preferred_type") - def _merge_default_params( - self, spec: Dict[str, Any], params: Dict[str, Any] - ) -> Dict[str, Any]: + def _merge_default_params(self, spec: dict, params: dict) -> dict: """ Merge default parameters into parameters. diff --git a/plugins/module_utils/common/params_merge_defaults_v2.py b/plugins/module_utils/common/params_merge_defaults_v2.py index f26ce2e08..bbc5528f3 100644 --- a/plugins/module_utils/common/params_merge_defaults_v2.py +++ b/plugins/module_utils/common/params_merge_defaults_v2.py @@ -22,7 +22,6 @@ import inspect import logging from collections.abc import MutableMapping as Map -from typing import Any, Dict class ParamsMergeDefaults: @@ -84,9 +83,7 @@ def _build_reserved_params(self): self.reserved_params.add("type") self.reserved_params.add("preferred_type") - def _merge_default_params( - self, spec: Dict[str, Any], params: Dict[str, Any] - ) -> Dict[str, Any]: + def _merge_default_params(self, spec: dict, params: dict) -> dict: """ ### Summary Merge default parameters into parameters. diff --git a/plugins/module_utils/common/params_validate.py b/plugins/module_utils/common/params_validate.py index 4e18dd472..8b3b9f35e 100644 --- a/plugins/module_utils/common/params_validate.py +++ b/plugins/module_utils/common/params_validate.py @@ -22,7 +22,6 @@ import ipaddress import logging from collections.abc import MutableMapping as Map -from typing import Any, List from ansible.module_utils.common import validation @@ -53,7 +52,7 @@ class ParamsValidate: - ``bar`` can be assigned one of three values: bingo, bango, or bongo. ```python - params_spec: Dict[str, Any] = {} + params_spec: dict = {} params_spec["ip_address"] = {} params_spec["ip_address"]["required"] = False params_spec["ip_address"]["type"] = "ipv4" @@ -270,7 +269,7 @@ def _validate_parameters(self, spec, parameters): param, ) - def _verify_choices(self, choices: List[Any], value: Any, param: str) -> None: + def _verify_choices(self, choices: list, value, param: str) -> None: """ Verify that value is one of the choices """ @@ -309,7 +308,7 @@ def _verify_integer_range( msg += f"Got {value}" self.ansible_module.fail_json(msg) - def _verify_type(self, expected_type: str, params: Any, param: str) -> Any: + def _verify_type(self, expected_type: str, params, param: str): """ Verify that value's type matches the expected type """ @@ -330,7 +329,7 @@ def _verify_type(self, expected_type: str, params: Any, param: str) -> Any: self._invalid_type(expected_type, value, param, err) return value - def _ipaddress_guard(self, expected_type, value: Any, param: str) -> None: + def _ipaddress_guard(self, expected_type, value, param: str) -> None: """ Guard against int and bool types for ipv4, ipv6, ipv4_subnet, and ipv6_subnet type. @@ -357,7 +356,7 @@ def _ipaddress_guard(self, expected_type, value: Any, param: str) -> None: raise TypeError(f"{msg}") def _invalid_type( - self, expected_type: str, value: Any, param: str, error: str = "" + self, expected_type: str, value, param: str, error: str = "" ) -> None: """ Calls fail_json when value's type does not match expected_type @@ -371,8 +370,8 @@ def _invalid_type( self.ansible_module.fail_json(msg) def _verify_multitype( # pylint: disable=inconsistent-return-statements - self, spec: Any, params: Any, param: str - ) -> Any: + self, spec, params, param: str + ): """ Verify that value's type matches one of the types in expected_types @@ -427,7 +426,7 @@ def _verify_multitype( # pylint: disable=inconsistent-return-statements self.ansible_module.fail_json(msg) def _verify_preferred_type_param_spec_is_present( - self, spec: Any, param: str + self, spec, param: str ) -> None: """ verify that spec contains the key 'preferred_type' @@ -440,7 +439,7 @@ def _verify_preferred_type_param_spec_is_present( self.ansible_module.fail_json(msg) def _verify_preferred_type_for_standard_types( - self, preferred_type: str, value: Any + self, preferred_type: str, value ) -> tuple: """ If preferred_type is one of the standard python types @@ -461,7 +460,7 @@ def _verify_preferred_type_for_standard_types( return (False, value) def _verify_preferred_type_for_ipaddress_types( - self, preferred_type: str, value: Any + self, preferred_type: str, value ) -> tuple: """ We can't use isinstance() to verify ipaddress types. @@ -479,7 +478,7 @@ def _verify_preferred_type_for_ipaddress_types( return (False, value) @staticmethod - def _validate_ipv4_address(value: Any) -> Any: + def _validate_ipv4_address(value): """ verify that value is an IPv4 address """ @@ -490,7 +489,7 @@ def _validate_ipv4_address(value: Any) -> Any: raise ValueError(f"invalid IPv4 address: {err}") from err @staticmethod - def _validate_ipv4_subnet(value: Any) -> Any: + def _validate_ipv4_subnet(value): """ verify that value is an IPv4 network """ @@ -501,7 +500,7 @@ def _validate_ipv4_subnet(value: Any) -> Any: raise ValueError(f"invalid IPv4 network: {err}") from err @staticmethod - def _validate_ipv6_address(value: Any) -> Any: + def _validate_ipv6_address(value): """ verify that value is an IPv6 address """ @@ -512,7 +511,7 @@ def _validate_ipv6_address(value: Any) -> Any: raise ValueError(f"invalid IPv6 address: {err}") from err @staticmethod - def _validate_ipv6_subnet(value: Any) -> Any: + def _validate_ipv6_subnet(value): """ verify that value is an IPv6 network """ @@ -523,7 +522,7 @@ def _validate_ipv6_subnet(value: Any) -> Any: raise ValueError(f"invalid IPv6 network: {err}") from err @staticmethod - def _validate_set(value: Any) -> Any: + def _validate_set(value): """ verify that value is a set """ @@ -532,7 +531,7 @@ def _validate_set(value: Any) -> Any: return value @staticmethod - def _validate_tuple(value: Any) -> Any: + def _validate_tuple(value): """ verify that value is a tuple """ diff --git a/plugins/module_utils/common/params_validate_v2.py b/plugins/module_utils/common/params_validate_v2.py index 71300cd01..1bc30a4ef 100644 --- a/plugins/module_utils/common/params_validate_v2.py +++ b/plugins/module_utils/common/params_validate_v2.py @@ -22,7 +22,6 @@ import ipaddress import logging from collections.abc import MutableMapping as Map -from typing import Any, List from ansible.module_utils.common import validation @@ -48,7 +47,7 @@ class ParamsValidate: - ``bar`` can be assigned one of three values: bingo, bango, or bongo. ```python - params_spec: Dict[str, Any] = {} + params_spec: dict = {} params_spec["ip_address"] = {} params_spec["ip_address"]["required"] = False params_spec["ip_address"]["type"] = "ipv4" @@ -300,7 +299,7 @@ def _validate_parameters(self, spec, parameters): except (TypeError, ValueError) as error: raise ValueError(error) from error - def _verify_choices(self, choices: List[Any], value: Any, param: str) -> None: + def _verify_choices(self, choices: list, value, param: str) -> None: """ ### Summary Verify that value is one of the choices @@ -351,7 +350,7 @@ def _verify_integer_range( msg += f"Got {value}" raise ValueError(msg) - def _verify_type(self, expected_type: str, params: Any, param: str): + def _verify_type(self, expected_type: str, params, param: str): """ ### Summary Verify that value's type matches the expected type @@ -384,7 +383,7 @@ def _verify_type(self, expected_type: str, params: Any, param: str): return return_value - def _ipaddress_guard(self, expected_type, value: Any, param: str) -> None: + def _ipaddress_guard(self, expected_type, value, param: str) -> None: """ ### Summary Guard against int and bool types for ipv4, ipv6, ipv4_subnet, @@ -414,7 +413,7 @@ def _ipaddress_guard(self, expected_type, value: Any, param: str) -> None: raise TypeError(msg) def _invalid_type( - self, expected_type: str, value: Any, param: str, error: str = "" + self, expected_type: str, value, param: str, error: str = "" ) -> None: """ ### Summary @@ -431,9 +430,7 @@ def _invalid_type( msg += f"Error detail: {error}" raise TypeError(msg) - def _verify_multitype( # pylint: disable=inconsistent-return-statements - self, spec: Any, params: Any, param: str - ) -> Any: + def _verify_multitype(self, spec, params, param: str): """ ### Summary Verify that value's type matches one of the types in expected_types @@ -448,6 +445,7 @@ def _verify_multitype( # pylint: disable=inconsistent-return-statements 1. We've disabled inconsistent-return-statements. We're pretty sure this method is correct. """ + # pylint: disable=inconsistent-return-statements method_name = inspect.stack()[0][3] # preferred_type is mandatory for multitype @@ -497,9 +495,7 @@ def _verify_multitype( # pylint: disable=inconsistent-return-statements msg += f"Got '{value}'." raise TypeError(msg) - def _verify_preferred_type_param_spec_is_present( - self, spec: Any, param: str - ) -> None: + def _verify_preferred_type_param_spec_is_present(self, spec, param: str) -> None: """ ### Summary Verify that spec contains the key 'preferred_type' @@ -515,7 +511,7 @@ def _verify_preferred_type_param_spec_is_present( raise KeyError(msg) def _verify_preferred_type_for_standard_types( - self, preferred_type: str, value: Any + self, preferred_type: str, value ) -> tuple: """ If preferred_type is one of the standard python types @@ -536,7 +532,7 @@ def _verify_preferred_type_for_standard_types( return (False, value) def _verify_preferred_type_for_ipaddress_types( - self, preferred_type: str, value: Any + self, preferred_type: str, value ) -> tuple: """ We can't use isinstance() to verify ipaddress types. @@ -554,7 +550,7 @@ def _verify_preferred_type_for_ipaddress_types( return (False, value) @staticmethod - def _validate_ipv4_address(value: Any) -> Any: + def _validate_ipv4_address(value): """ verify that value is an IPv4 address """ @@ -565,7 +561,7 @@ def _validate_ipv4_address(value: Any) -> Any: raise ValueError(f"invalid IPv4 address: {err}") from err @staticmethod - def _validate_ipv4_subnet(value: Any) -> Any: + def _validate_ipv4_subnet(value): """ verify that value is an IPv4 network """ @@ -576,7 +572,7 @@ def _validate_ipv4_subnet(value: Any) -> Any: raise ValueError(f"invalid IPv4 network: {err}") from err @staticmethod - def _validate_ipv6_address(value: Any) -> Any: + def _validate_ipv6_address(value): """ verify that value is an IPv6 address """ @@ -587,7 +583,7 @@ def _validate_ipv6_address(value: Any) -> Any: raise ValueError(f"invalid IPv6 address: {err}") from err @staticmethod - def _validate_ipv6_subnet(value: Any) -> Any: + def _validate_ipv6_subnet(value): """ verify that value is an IPv6 network """ @@ -598,7 +594,7 @@ def _validate_ipv6_subnet(value: Any) -> Any: raise ValueError(f"invalid IPv6 network: {err}") from err @staticmethod - def _validate_set(value: Any) -> Any: + def _validate_set(value): """ verify that value is a set """ @@ -607,7 +603,7 @@ def _validate_set(value: Any) -> Any: return value @staticmethod - def _validate_tuple(value: Any) -> Any: + def _validate_tuple(value): """ verify that value is a tuple """ diff --git a/plugins/module_utils/common/rest_send_v2.py b/plugins/module_utils/common/rest_send_v2.py index 19404230f..0d19e6083 100644 --- a/plugins/module_utils/common/rest_send_v2.py +++ b/plugins/module_utils/common/rest_send_v2.py @@ -254,7 +254,9 @@ def commit(self): """ method_name = inspect.stack()[0][3] msg = f"{self.class_name}.{method_name}: " - msg += f"check_mode: {self.check_mode}." + msg += f"check_mode: {self.check_mode}, " + msg += f"verb: {self.verb}, " + msg += f"path: {self.path}." self.log.debug(msg) try: @@ -352,7 +354,6 @@ def commit_normal_mode(self): timeout = copy.copy(self.timeout) - success = False msg = f"{caller}: Entering commit loop. " msg += f"timeout: {timeout}, unit_test: {self.unit_test}." self.log.debug(msg) @@ -361,10 +362,22 @@ def commit_normal_mode(self): self.sender.verb = self.verb if self.payload is not None: self.sender.payload = self.payload + success = False while timeout > 0 and success is False: + timeout -= self.send_interval + msg = f"{self.class_name}.{method_name}: " + msg += f"caller: {caller}. " + msg += f"unit_test: {self.unit_test}. " + msg += f"Subtracted {self.send_interval} from timeout. " + msg += f"timeout: {timeout}, " + msg += f"success: {success}." + self.log.debug(msg) + msg = f"{self.class_name}.{method_name}: " msg += f"caller: {caller}. " - msg += f"Calling sender.commit(): verb {self.verb}, path {self.path}" + msg += "Calling sender.commit(): " + msg += f"timeout {timeout}, success {success}, verb {self.verb}, path {self.path}." + self.log.debug(msg) try: self.sender.commit() @@ -382,26 +395,29 @@ def commit_normal_mode(self): msg = f"{self.class_name}.{method_name}: " msg += "Error building response/result. " msg += f"Error detail: {error}" + self.log.debug(msg) raise ValueError(msg) from error msg = f"{self.class_name}.{method_name}: " - msg += f"caller: {caller}. " + msg += f"caller: {caller}. " + msg += f"timeout: {timeout}. " msg += f"result_current: {json.dumps(self.result_current, indent=4, sort_keys=True)}." self.log.debug(msg) - success = self.result_current["success"] - if success is False and self.unit_test is False: - sleep(self.send_interval) - timeout -= self.send_interval - msg = f"{self.class_name}.{method_name}: " - msg += f"caller: {caller}. " + msg += f"caller: {caller}. " + msg += f"timeout: {timeout}. " msg += "response_current: " msg += f"{json.dumps(self.response_current, indent=4, sort_keys=True)}." self.log.debug(msg) + success = self.result_current["success"] + if success is False and self.unit_test is False: + sleep(self.send_interval) + self.response = copy.deepcopy(self.response_current) self.result = copy.deepcopy(self.result_current) + self._payload = None @property def check_mode(self): diff --git a/plugins/module_utils/common/results.py b/plugins/module_utils/common/results.py index 79505e26d..067a8001f 100644 --- a/plugins/module_utils/common/results.py +++ b/plugins/module_utils/common/results.py @@ -22,7 +22,6 @@ import inspect import json import logging -from typing import Any, Dict class Results: @@ -205,7 +204,7 @@ def __init__(self): self._build_properties() def _build_properties(self): - self.properties: Dict[str, Any] = {} + self.properties: dict = {} self.properties["action"] = None self.properties["changed"] = set() self.properties["check_mode"] = False @@ -233,11 +232,14 @@ def did_anything_change(self) -> bool: Return True if there were any changes Otherwise, return False """ - msg = f"{self.class_name}.did_anything_change(): ENTERED: " + method_name = inspect.stack()[0][3] + + msg = f"{self.class_name}.{method_name}: ENTERED: " msg += f"self.action: {self.action}, " msg += f"self.state: {self.state}, " msg += f"self.result_current: {self.result_current}, " - msg += f"self.diff: {self.diff}" + msg += f"self.diff: {self.diff}, " + msg += f"self.failed: {self.failed}" self.log.debug(msg) if self.check_mode is True: @@ -248,12 +250,17 @@ def did_anything_change(self) -> bool: return True if self.result_current.get("changed", None) is False: return False + if "changed" not in self.result_current: + return False for diff in self.diff: something_changed = False test_diff = copy.deepcopy(diff) test_diff.pop("sequence_number", None) if len(test_diff) != 0: something_changed = True + msg = f"{self.class_name}.{method_name}: " + msg += f"something_changed: {something_changed}" + self.log.debug(msg) return something_changed def register_task_result(self): @@ -297,8 +304,15 @@ def register_task_result(self): if self.result_current.get("success") is True: self.failed = False - else: + elif self.result_current.get("success") is False: self.failed = True + else: + msg = f"{self.class_name}.{method_name}: " + msg += "self.result_current['success'] is not a boolean. " + msg += f"self.result_current: {self.result_current}. " + msg += "Setting self.failed to False." + self.log.debug(msg) + self.failed = False msg = f"{self.class_name}.{method_name}: " msg += f"self.diff: {json.dumps(self.diff, indent=4, sort_keys=True)}, " @@ -360,7 +374,7 @@ def build_final_result(self): self.final_result["metadata"] = self.metadata @property - def failed_result(self) -> Dict[str, Any]: + def failed_result(self) -> dict: """ return a result for a failed task with no changes """ @@ -373,7 +387,7 @@ def failed_result(self) -> Dict[str, Any]: return result @property - def ok_result(self) -> Dict[str, Any]: + def ok_result(self) -> dict: """ return a result for a successful task with no changes """ diff --git a/plugins/module_utils/common/switch_details.py b/plugins/module_utils/common/switch_details.py index 33f410be5..b7973e85f 100644 --- a/plugins/module_utils/common/switch_details.py +++ b/plugins/module_utils/common/switch_details.py @@ -83,9 +83,6 @@ def __init__(self): self.action = "switch_details" self.conversion = ConversionUtils() self.ep_all_switches = EpAllSwitches() - self.path = self.ep_all_switches.path - self.verb = self.ep_all_switches.verb - self._filter = None self._info = None self._rest_send = None @@ -128,8 +125,8 @@ def send_request(self) -> None: # Regardless of ansible_module.check_mode, we need to get the # switch details. So, set check_mode to False. self.rest_send.check_mode = False - self.rest_send.verb = self.verb - self.rest_send.path = self.path + self.rest_send.path = self.ep_all_switches.path + self.rest_send.verb = self.ep_all_switches.verb self.rest_send.commit() self.rest_send.restore_settings() except (TypeError, ValueError) as error: diff --git a/plugins/module_utils/fabric/common.py b/plugins/module_utils/fabric/common.py index 104d68d42..23f92521f 100644 --- a/plugins/module_utils/fabric/common.py +++ b/plugins/module_utils/fabric/common.py @@ -19,10 +19,11 @@ import inspect import logging -from typing import Any, Dict from ansible_collections.cisco.dcnm.plugins.module_utils.common.conversion import \ ConversionUtils +from ansible_collections.cisco.dcnm.plugins.module_utils.common.properties import \ + Properties from ansible_collections.cisco.dcnm.plugins.module_utils.fabric.config_deploy import \ FabricConfigDeploy from ansible_collections.cisco.dcnm.plugins.module_utils.fabric.config_save import \ @@ -31,45 +32,34 @@ FabricTypes +@Properties.add_rest_send +@Properties.add_results class FabricCommon: """ + ### Summary Common methods used by the other classes supporting the dcnm_fabric module - Usage (where params is AnsibleModule.params) + ### Usage class MyClass(FabricCommon): - def __init__(self, params): - super().__init__(params) + def __init__(self): + super().__init__() ... """ - def __init__(self, params): + def __init__(self): self.class_name = self.__class__.__name__ - self.params = params + self.action = None self.log = logging.getLogger(f"dcnm.{self.class_name}") - self.check_mode = self.params.get("check_mode", None) - if self.check_mode is None: - msg = f"{self.class_name}.__init__(): " - msg += "check_mode is required" - raise ValueError(msg) - - self.state = self.params.get("state", None) - if self.state is None: - msg = f"{self.class_name}.__init__(): " - msg += "state is required" - raise ValueError(msg) - self.conversion = ConversionUtils() - self.config_save = FabricConfigSave(params) - self.config_deploy = FabricConfigDeploy(params) + self.config_save = FabricConfigSave() + self.config_deploy = FabricConfigDeploy() self.fabric_types = FabricTypes() - msg = "ENTERED FabricCommon(): " - msg += f"check_mode: {self.check_mode}, " - msg += f"state: {self.state}" + msg = "ENTERED FabricCommon()" self.log.debug(msg) # key: fabric_name, value: boolean @@ -106,19 +96,13 @@ def __init__(self, params): self.path = None self.verb = None - self._init_properties() - self._init_key_translations() + self._fabric_details = None + self._fabric_summary = None + self._fabric_type = "VXLAN_EVPN" + self._rest_send = None + self._results = None - def _init_properties(self) -> None: - """ - Initialize the properties dictionary. - """ - self._properties: Dict[str, Any] = {} - self._properties["fabric_details"] = None - self._properties["fabric_summary"] = None - self._properties["fabric_type"] = "VXLAN_EVPN" - self._properties["rest_send"] = None - self._properties["results"] = None + self._init_key_translations() def _init_key_translations(self): """ @@ -167,6 +151,7 @@ def _config_save(self, payload): return self.config_save.payload = payload + # pylint: disable=no-member self.config_save.rest_send = self.rest_send self.config_save.results = self.results try: @@ -197,6 +182,7 @@ def _config_deploy(self, payload): self.config_deploy.fabric_details = self.fabric_details self.config_deploy.payload = payload self.config_deploy.fabric_summary = self.fabric_summary + # pylint: disable=no-member self.config_deploy.rest_send = self.rest_send self.config_deploy.results = self.results except TypeError as error: @@ -241,6 +227,7 @@ def translate_anycast_gw_mac(self, fabric_name, mac_address): try: mac_address = self.conversion.translate_mac_address(mac_address) except ValueError as error: + # pylint: disable=no-member self.results.failed = True self.results.changed = False self.results.register_task_result() @@ -271,6 +258,7 @@ def _fixup_payloads_to_commit(self) -> None: self._fixup_anycast_gw_mac() self._fixup_bgp_as() except ValueError as error: + # pylint: disable=no-member self.results.failed = True self.results.changed = False self.results.register_task_result() @@ -325,7 +313,7 @@ def _verify_payload(self, payload) -> None: - raise ``ValueError`` if the payload is missing mandatory keys """ method_name = inspect.stack()[0][3] - if self.state not in {"merged", "replaced"}: + if self.action not in {"fabric_create", "fabric_replace", "fabric_update"}: return msg = f"{self.class_name}.{method_name}: " msg += f"payload: {payload}" @@ -394,22 +382,22 @@ def fabric_details(self): """ An instance of the FabricDetails class. """ - return self._properties["fabric_details"] + return self._fabric_details @fabric_details.setter def fabric_details(self, value): - self._properties["fabric_details"] = value + self._fabric_details = value @property def fabric_summary(self): """ An instance of the FabricSummary class. """ - return self._properties["fabric_summary"] + return self._fabric_summary @fabric_summary.setter def fabric_summary(self, value): - self._properties["fabric_summary"] = value + self._fabric_summary = value @property def fabric_type(self): @@ -420,7 +408,7 @@ def fabric_type(self): See ``FabricTypes().valid_fabric_types`` for valid values """ - return self._properties["fabric_type"] + return self._fabric_type @fabric_type.setter def fabric_type(self, value): @@ -431,26 +419,4 @@ def fabric_type(self, value): msg += f"{self.fabric_types.valid_fabric_types}. " msg += f"Got {value}" raise ValueError(msg) - self._properties["fabric_type"] = value - - @property - def rest_send(self): - """ - An instance of the RestSend class. - """ - return self._properties["rest_send"] - - @rest_send.setter - def rest_send(self, value): - self._properties["rest_send"] = 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 + self._fabric_type = value diff --git a/plugins/module_utils/fabric/config_deploy.py b/plugins/module_utils/fabric/config_deploy.py index 7f0bd6e30..a8679f05c 100644 --- a/plugins/module_utils/fabric/config_deploy.py +++ b/plugins/module_utils/fabric/config_deploy.py @@ -20,16 +20,21 @@ import copy import inspect import logging -from typing import Dict from ansible_collections.cisco.dcnm.plugins.module_utils.common.api.v1.lan_fabric.rest.control.fabrics.fabrics import \ EpFabricConfigDeploy from ansible_collections.cisco.dcnm.plugins.module_utils.common.conversion import \ ConversionUtils +from ansible_collections.cisco.dcnm.plugins.module_utils.common.results import \ + Results from ansible_collections.cisco.dcnm.plugins.module_utils.common.exceptions import \ ControllerResponseError +from ansible_collections.cisco.dcnm.plugins.module_utils.common.properties import \ + Properties +@Properties.add_rest_send +@Properties.add_results class FabricConfigDeploy: """ # Initiate a fabric config-deploy operation on the controller. @@ -39,15 +44,25 @@ class FabricConfigDeploy: - Update FabricConfigDeploy().results to reflect success/failure of the operation on the controller. - ## Usage (where params is AnsibleModule.params) + ## Usage ```python - config_deploy = FabricConfigDeploy(params) - config_deploy.rest_send = RestSend() + # params is typically obtained from ansible_module.params + # but can also be specified manually, like below. + params = {"check_mode": False, "state": "merged"} + sender = Sender() + sender.ansible_module = ansible_module + rest_send = RestSend(params) + rest_send.response_handler = ResponseHandler() + rest_send.sender = sender + results = Results() + + config_deploy = FabricConfigDeploy() + config_deploy.rest_send = rest_send config_deploy.payload = payload # a valid payload dictionary - config_deploy.fabric_details = FabricDetailsByName(params) + config_deploy.fabric_details = FabricDetailsByName() config_deploy.fabric_summary = FabricSummary(params) - config_deploy.results = Results() + config_deploy.results = results try: config_deploy.commit() except ValueError as error: @@ -55,54 +70,33 @@ class FabricConfigDeploy: ``` """ - def __init__(self, params): + def __init__(self): self.class_name = self.__class__.__name__ self.log = logging.getLogger(f"dcnm.{self.class_name}") - self.params = params self.action = "config_deploy" self.cannot_deploy_fabric_reason = "" self.config_deploy_failed = False - self.fabric_can_be_deployed = False - - self.check_mode = self.params.get("check_mode", None) - if self.check_mode is None: - msg = f"{self.class_name}.__init__(): " - msg += "params is missing mandatory check_mode parameter." - raise ValueError(msg) - - self.state = self.params.get("state", None) - if self.state is None: - msg = f"{self.class_name}.__init__(): " - msg += "params is missing mandatory state parameter." - raise ValueError(msg) - - self.config_deploy_result: Dict[str, bool] = {} - - self.path = None - self.verb = None - self._init_properties() + self.config_deploy_result: dict[str, bool] = {} self.conversion = ConversionUtils() self.ep_config_deploy = EpFabricConfigDeploy() - msg = "ENTERED FabricConfigDeploy(): " - msg += f"check_mode: {self.check_mode}, " - msg += f"state: {self.state}" + self.fabric_can_be_deployed = False + self._fabric_details = None + self._fabric_name = None + self._fabric_summary = None + self._payload = None + self._rest_send = None + self._results = None + + msg = "ENTERED FabricConfigDeploy():" self.log.debug(msg) - def _init_properties(self): - self._properties = {} - self._properties["fabric_details"] = None - self._properties["fabric_name"] = None - self._properties["fabric_summary"] = None - self._properties["payload"] = None - self._properties["rest_send"] = None - self._properties["results"] = None - def _can_fabric_be_deployed(self) -> None: """ + ### Summary - Set self.fabric_can_be_deployed to True if the fabric configuration can be deployed. - Set self.fabric_can_be_deployed to False otherwise. @@ -113,7 +107,8 @@ def _can_fabric_be_deployed(self) -> None: deploy = self.payload.get("DEPLOY", None) if deploy is False or deploy is None: - msg = f"Fabric {self.fabric_name} DEPLOY is False or None. " + msg = f"{self.class_name}.{method_name}: " + msg += f"Fabric {self.fabric_name} DEPLOY is False or None. " msg += "Skipping config-deploy." self.log.debug(msg) self.cannot_deploy_fabric_reason = msg @@ -124,7 +119,8 @@ def _can_fabric_be_deployed(self) -> None: try: self.fabric_summary.fabric_name = self.fabric_name except ValueError as error: - msg = f"Fabric {self.fabric_name} is invalid. " + msg = f"{self.class_name}.{method_name}: " + msg += f"Fabric {self.fabric_name} is invalid. " msg += "Cannot deploy fabric. " msg += f"Error detail: {error}" self.log.debug(msg) @@ -139,6 +135,7 @@ def _can_fabric_be_deployed(self) -> None: msg = f"{self.class_name}.{method_name}: " msg += "Error during FabricSummary().refresh(). " msg += f"Error detail: {error}" + self.log.debug(msg) self.cannot_deploy_fabric_reason = msg self.fabric_can_be_deployed = False self.config_deploy_failed = True @@ -154,11 +151,13 @@ def _can_fabric_be_deployed(self) -> None: return try: + self.fabric_details.results = Results() self.fabric_details.refresh() except ValueError as error: msg = f"{self.class_name}.{method_name}: " msg += "Error during FabricDetailsByName().refresh(). " msg += f"Error detail: {error}" + self.log.debug(msg) self.cannot_deploy_fabric_reason = msg self.fabric_can_be_deployed = False self.config_deploy_failed = True @@ -167,7 +166,8 @@ def _can_fabric_be_deployed(self) -> None: self.fabric_details.filter = self.fabric_name if self.fabric_details.deployment_freeze is True: - msg = f"Fabric {self.fabric_name} DEPLOYMENT_FREEZE == True. " + msg = f"{self.class_name}.{method_name}: " + msg += f"Fabric {self.fabric_name} DEPLOYMENT_FREEZE == True. " msg += "Cannot deploy a fabric with deployment freeze enabled." self.log.debug(msg) self.cannot_deploy_fabric_reason = msg @@ -176,7 +176,8 @@ def _can_fabric_be_deployed(self) -> None: return if self.fabric_details.is_read_only is True: - msg = f"Fabric {self.fabric_name} IS_READ_ONLY == True. " + msg = f"{self.class_name}.{method_name}: " + msg += f"Fabric {self.fabric_name} IS_READ_ONLY == True. " msg += "Cannot deploy a read only fabric." self.log.debug(msg) self.cannot_deploy_fabric_reason = msg @@ -194,6 +195,7 @@ def commit(self): - Raise ``ValueError`` if FabricConfigDeploy().results is not set. - Raise ``ValueError`` if the endpoint assignment fails. """ + # pylint: disable=no-member method_name = inspect.stack()[0][3] if self.fabric_details is None: @@ -234,8 +236,8 @@ def commit(self): if self.fabric_can_be_deployed is False: self.results.diff_current = {} self.results.action = self.action - self.results.check_mode = self.check_mode - self.results.state = self.state + self.results.check_mode = self.rest_send.check_mode + self.results.state = self.rest_send.state self.results.response_current = { "RETURN_CODE": 200, "MESSAGE": self.cannot_deploy_fabric_reason, @@ -249,16 +251,13 @@ def commit(self): try: self.ep_config_deploy.fabric_name = self.fabric_name - self.path = self.ep_config_deploy.path - self.verb = self.ep_config_deploy.verb + self.rest_send.path = self.ep_config_deploy.path + self.rest_send.verb = self.ep_config_deploy.verb + self.rest_send.payload = None + self.rest_send.commit() except ValueError as error: raise ValueError(error) from error - self.rest_send.path = self.path - self.rest_send.verb = self.verb - self.rest_send.payload = None - self.rest_send.commit() - result = self.rest_send.result_current["success"] self.config_deploy_result[self.fabric_name] = result if self.config_deploy_result[self.fabric_name] is False: @@ -270,8 +269,8 @@ def commit(self): } self.results.action = self.action - self.results.check_mode = self.check_mode - self.results.state = self.state + self.results.check_mode = self.rest_send.check_mode + self.results.state = self.rest_send.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() @@ -281,7 +280,7 @@ def fabric_name(self): """ The name of the fabric to config-save. """ - return self._properties["fabric_name"] + return self._fabric_name @fabric_name.setter def fabric_name(self, value): @@ -289,7 +288,7 @@ def fabric_name(self, value): self.conversion.validate_fabric_name(value) except (TypeError, ValueError) as error: raise ValueError(error) from error - self._properties["fabric_name"] = value + self._fabric_name = value @property def fabric_details(self): @@ -299,7 +298,7 @@ def fabric_details(self): - setter: Raise ``TypeError`` if the value is not an instance of FabricDetailsByName. """ - return self._properties["fabric_details"] + return self._fabric_details @fabric_details.setter def fabric_details(self, value): @@ -315,7 +314,7 @@ def fabric_details(self, value): msg += f"Got {class_name}." self.log.debug(msg) raise TypeError(msg) - self._properties["fabric_details"] = value + self._fabric_details = value @property def fabric_summary(self): @@ -325,7 +324,7 @@ def fabric_summary(self): - setter: Raise ``TypeError`` if the value is not an instance of FabricSummary. """ - return self._properties["fabric_summary"] + return self._fabric_summary @fabric_summary.setter def fabric_summary(self, value): @@ -341,7 +340,7 @@ def fabric_summary(self, value): msg += f"Got {class_name}." self.log.debug(msg) raise TypeError(msg) - self._properties["fabric_summary"] = value + self._fabric_summary = value @property def payload(self): @@ -350,7 +349,7 @@ def payload(self): - Raise ``ValueError`` if the value is not a dictionary. - Raise ``ValueError`` the payload is missing FABRIC_NAME key. """ - return self._properties["payload"] + return self._payload @payload.setter def payload(self, value): @@ -370,58 +369,4 @@ def payload(self, value): self.fabric_name = value["FABRIC_NAME"] except ValueError as error: raise ValueError(error) from error - self._properties["payload"] = value - - @property - def rest_send(self): - """ - - getter: Return an instance of the RestSend class. - - setter: Set an instance of the RestSend class. - - setter: Raise ``TypeError`` if the value is not an - instance of RestSend. - """ - return self._properties["rest_send"] - - @rest_send.setter - def rest_send(self, value): - method_name = inspect.stack()[0][3] - _class_name = None - msg = f"{self.class_name}.{method_name}: " - msg += "value must be an instance of RestSend. " - try: - _class_name = value.class_name - except AttributeError as error: - msg += f"Error detail: {error}." - raise TypeError(msg) from error - if _class_name != "RestSend": - self.log.debug(msg) - raise TypeError(msg) - self._properties["rest_send"] = value - - @property - def results(self): - """ - - getter: Return an instance of the Results class. - - setter: Set an instance of the Results class. - - setter: Raise ``TypeError`` if the value is not an - instance of Results. - """ - return self._properties["results"] - - @results.setter - def results(self, value): - method_name = inspect.stack()[0][3] - msg = f"{self.class_name}.{method_name}: " - msg += "value must be an instance of Results. " - msg += f"Got value {value} of type {type(value).__name__}." - _class_name = None - try: - _class_name = value.class_name - except AttributeError as error: - msg += f" Error detail: {error}." - self.log.debug(msg) - raise TypeError(msg) from error - if _class_name != "Results": - self.log.debug(msg) - raise TypeError(msg) - self._properties["results"] = value + self._payload = value diff --git a/plugins/module_utils/fabric/config_save.py b/plugins/module_utils/fabric/config_save.py index 6e4b232ea..08629a1e4 100644 --- a/plugins/module_utils/fabric/config_save.py +++ b/plugins/module_utils/fabric/config_save.py @@ -20,14 +20,17 @@ import copy import inspect import logging -from typing import Dict from ansible_collections.cisco.dcnm.plugins.module_utils.common.api.v1.lan_fabric.rest.control.fabrics.fabrics import \ EpFabricConfigSave from ansible_collections.cisco.dcnm.plugins.module_utils.common.conversion import \ ConversionUtils +from ansible_collections.cisco.dcnm.plugins.module_utils.common.properties import \ + Properties +@Properties.add_rest_send +@Properties.add_results class FabricConfigSave: """ # Initiate a fabric config-save operation on the controller. @@ -37,13 +40,23 @@ class FabricConfigSave: - Update FabricConfigSave().results to reflect success/failure of the operation on the controller. - ## Usage (where params is AnsibleModule.params) + ## Usage: ```python - config_save = FabricConfigSave(params) - config_save.rest_send = RestSend() + # params is typically obtained from ansible_module.params + # but can also be specified manually, like below. + params = {"check_mode": False, "state": "merged"} + sender = Sender() + sender.ansible_module = ansible_module + rest_send = RestSend(params) + rest_send.response_handler = ResponseHandler() + rest_send.sender = sender + results = Results() + + config_save = FabricConfigSave() + config_save.rest_send = rest_send config_deploy.payload = payload # a valid payload dictionary - config_save.results = Results() + config_save.results = results try: config_save.commit() except ValueError as error: @@ -51,50 +64,28 @@ class FabricConfigSave: ``` """ - def __init__(self, params): + def __init__(self): self.class_name = self.__class__.__name__ self.log = logging.getLogger(f"dcnm.{self.class_name}") - self.params = params self.action = "config_save" self.cannot_save_fabric_reason = "" self.config_save_failed = False self.fabric_can_be_saved = False - self.check_mode = self.params.get("check_mode", None) - if self.check_mode is None: - msg = f"{self.class_name}.__init__(): " - msg += "params is missing mandatory check_mode parameter." - raise ValueError(msg) - - self.state = self.params.get("state", None) - if self.state is None: - msg = f"{self.class_name}.__init__(): " - msg += "params is missing mandatory state parameter." - raise ValueError(msg) - - self.config_save_result: Dict[str, bool] = {} - - self.path = None - self.verb = None - self._init_properties() + self.config_save_result: dict[str, bool] = {} self.conversion = ConversionUtils() self.ep_config_save = EpFabricConfigSave() + self._fabric_name = None + self._payload = None + self._rest_send = None + self._results = None - msg = "ENTERED FabricConfigSave(): " - msg += f"check_mode: {self.check_mode}, " - msg += f"state: {self.state}" + msg = "ENTERED FabricConfigSave()" self.log.debug(msg) - def _init_properties(self): - self._properties = {} - self._properties["fabric_name"] = None - self._properties["payload"] = None - self._properties["rest_send"] = None - self._properties["results"] = None - def _can_fabric_be_saved(self) -> None: """ - Set self.fabric_can_be_saved to True if the fabric configuration @@ -120,6 +111,7 @@ def commit(self): - Raise ``ValueError`` if the endpoint assignment fails. """ method_name = inspect.stack()[0][3] + # pylint: disable=no-member if self.payload is None: msg = f"{self.class_name}.{method_name}: " @@ -142,8 +134,8 @@ def commit(self): if self.fabric_can_be_saved is False: self.results.diff_current = {} self.results.action = self.action - self.results.check_mode = self.check_mode - self.results.state = self.state + self.results.check_mode = self.rest_send.check_mode + self.results.state = self.rest_send.state self.results.response_current = { "RETURN_CODE": 200, "MESSAGE": self.cannot_save_fabric_reason, @@ -157,16 +149,13 @@ def commit(self): try: self.ep_config_save.fabric_name = self.fabric_name - self.path = self.ep_config_save.path - self.verb = self.ep_config_save.verb + self.rest_send.path = self.ep_config_save.path + self.rest_send.verb = self.ep_config_save.verb + self.rest_send.payload = None + self.rest_send.commit() except ValueError as error: raise ValueError(error) from error - self.rest_send.path = self.path - self.rest_send.verb = self.verb - self.rest_send.payload = None - self.rest_send.commit() - result = self.rest_send.result_current["success"] self.config_save_result[self.fabric_name] = result if self.config_save_result[self.fabric_name] is False: @@ -178,8 +167,8 @@ def commit(self): } self.results.action = self.action - self.results.check_mode = self.check_mode - self.results.state = self.state + self.results.check_mode = self.rest_send.check_mode + self.results.state = self.rest_send.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() @@ -189,7 +178,7 @@ def fabric_name(self): """ The name of the fabric to config-save. """ - return self._properties["fabric_name"] + return self._fabric_name @fabric_name.setter def fabric_name(self, value): @@ -197,7 +186,7 @@ def fabric_name(self, value): self.conversion.validate_fabric_name(value) except (TypeError, ValueError) as error: raise ValueError(error) from error - self._properties["fabric_name"] = value + self._fabric_name = value @property def payload(self): @@ -206,7 +195,7 @@ def payload(self): - Raise ``ValueError`` if the value is not a dictionary. - Raise ``ValueError`` the payload is missing FABRIC_NAME key. """ - return self._properties["payload"] + return self._payload @payload.setter def payload(self, value): @@ -226,58 +215,4 @@ def payload(self, value): self.fabric_name = value["FABRIC_NAME"] except ValueError as error: raise ValueError(error) from error - self._properties["payload"] = value - - @property - def rest_send(self): - """ - - getter: Return an instance of the RestSend class. - - setter: Set an instance of the RestSend class. - - setter: Raise ``TypeError`` if the value is not an - instance of RestSend. - """ - return self._properties["rest_send"] - - @rest_send.setter - def rest_send(self, value): - method_name = inspect.stack()[0][3] - _class_name = None - msg = f"{self.class_name}.{method_name}: " - msg += "value must be an instance of RestSend. " - try: - _class_name = value.class_name - except AttributeError as error: - msg += f"Error detail: {error}." - raise TypeError(msg) from error - if _class_name != "RestSend": - self.log.debug(msg) - raise TypeError(msg) - self._properties["rest_send"] = value - - @property - def results(self): - """ - - getter: Return an instance of the Results class. - - setter: Set an instance of the Results class. - - setter: Raise ``TypeError`` if the value is not an - instance of Results. - """ - return self._properties["results"] - - @results.setter - def results(self, value): - method_name = inspect.stack()[0][3] - msg = f"{self.class_name}.{method_name}: " - msg += "value must be an instance of Results. " - msg += f"Got value {value} of type {type(value).__name__}." - _class_name = None - try: - _class_name = value.class_name - except AttributeError as error: - msg += f" Error detail: {error}." - self.log.debug(msg) - raise TypeError(msg) from error - if _class_name != "Results": - self.log.debug(msg) - raise TypeError(msg) - self._properties["results"] = value + self._payload = value diff --git a/plugins/module_utils/fabric/create.py b/plugins/module_utils/fabric/create.py index cdf4cb43f..8c404908f 100644 --- a/plugins/module_utils/fabric/create.py +++ b/plugins/module_utils/fabric/create.py @@ -38,10 +38,10 @@ class FabricCreateCommon(FabricCommon): - FabricCreateBulk """ - def __init__(self, params): - super().__init__(params) + def __init__(self): + super().__init__() self.class_name = self.__class__.__name__ - self.action: str = "create" + self.action = "fabric_create" self.log = logging.getLogger(f"dcnm.{self.class_name}") @@ -57,20 +57,9 @@ def __init__(self, params): self._payloads_to_commit: list = [] - self._build_properties() - - msg = "ENTERED FabricCreateCommon(): " - msg += f"action: {self.action}, " - msg += f"check_mode: {self.check_mode}, " - msg += f"state: {self.state}" + msg = "ENTERED FabricCreateCommon()" self.log.debug(msg) - def _build_properties(self): - """ - - Add properties specific to this class - - self._properties is initialized in FabricCommon - """ - def _build_payloads_to_commit(self) -> None: """ Build a list of payloads to commit. Skip any payloads that @@ -84,7 +73,7 @@ def _build_payloads_to_commit(self) -> None: """ self.fabric_details.refresh() - self._payloads_to_commit = [] + self._payloads_to_commit: list = [] for payload in self.payloads: if payload.get("FABRIC_NAME", None) in self.fabric_details.all_data: continue @@ -135,8 +124,6 @@ def _send_payloads(self): NOTES: - This overrides the parent class method. """ - self.rest_send.check_mode = self.check_mode - for payload in self._payloads_to_commit: try: self._set_fabric_create_endpoint(payload) @@ -151,6 +138,7 @@ def _send_payloads(self): # We don't want RestSend to retry on errors since the likelihood of a # timeout error when creating a fabric is low, and there are many cases # of permanent errors for which we don't want to retry. + # pylint: disable=no-member self.rest_send.timeout = 1 self.rest_send.path = self.path @@ -163,8 +151,8 @@ def _send_payloads(self): 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.state = self.rest_send.state + self.results.check_mode = self.rest_send.check_mode self.results.response_current = copy.deepcopy( self.rest_send.response_current ) @@ -185,7 +173,7 @@ def payloads(self): - setter: raise ``ValueError`` if ``payloads`` is not a ``list`` of ``dict`` - setter: raise ``ValueError`` if any payload is missing mandatory keys """ - return self._properties["payloads"] + return self._payloads @payloads.setter def payloads(self, value): @@ -206,7 +194,7 @@ def payloads(self, value): self._verify_payload(item) except ValueError as error: raise ValueError(error) from error - self._properties["payloads"] = value + self._payloads = value class FabricCreateBulk(FabricCreateCommon): @@ -250,22 +238,14 @@ class FabricCreateBulk(FabricCreateCommon): ``` """ - def __init__(self, params): - super().__init__(params) + def __init__(self): + super().__init__() self.class_name = self.__class__.__name__ self.log = logging.getLogger(f"dcnm.{self.class_name}") + self._payloads = None self.log.debug("ENTERED FabricCreateBulk()") - 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 fabrics. @@ -277,6 +257,7 @@ def commit(self): """ method_name = inspect.stack()[0][3] + # pylint: disable=no-member if self.rest_send is None: msg = f"{self.class_name}.{method_name}: " msg += "rest_send must be set prior to calling commit. " @@ -315,22 +296,14 @@ class FabricCreate(FabricCreateCommon): - FabricCreateBulk is used instead. """ - def __init__(self, params): - super().__init__(params) + def __init__(self): + super().__init__() self.class_name = self.__class__.__name__ self.log = logging.getLogger(f"dcnm.{self.class_name}") + self._payload = None self.log.debug("ENTERED FabricCreate()") - self._build_properties() - - def _build_properties(self): - """ - Add properties specific to this class - """ - # self._properties is already initialized in the parent class - self._properties["payload"] = None - def commit(self): """ - Send the fabric create request to the controller. @@ -347,7 +320,7 @@ def commit(self): in FabricCreateCommom() """ method_name = inspect.stack()[0][3] - if self.rest_send is None: + if self.rest_send is None: # pylint: disable=no-member msg = f"{self.class_name}.{method_name}: " msg += "rest_send must be set prior to calling commit. " raise ValueError(msg) @@ -376,7 +349,7 @@ def payload(self): """ Return a fabric create payload. """ - return self._properties["payload"] + return self._payload @payload.setter def payload(self, value): @@ -395,10 +368,10 @@ def payload(self, value): self._verify_payload(value) except ValueError as error: raise ValueError(error) from error - self._properties["payload"] = value + self._payload = value # payloads is also set to a list containing one payload. # commit() calls FabricCreateCommon()._build_payloads_to_commit(), # which expects a list of payloads. # FabricCreateCommon()._build_payloads_to_commit() verifies that # the fabric does not already exist on the controller. - self._properties["payloads"] = [value] + self._payloads = [value] diff --git a/plugins/module_utils/fabric/delete.py b/plugins/module_utils/fabric/delete.py index 8958720ee..2ded53e33 100644 --- a/plugins/module_utils/fabric/delete.py +++ b/plugins/module_utils/fabric/delete.py @@ -69,38 +69,22 @@ class FabricDelete(FabricCommon): ansible_module.exit_json(**task.results.final_result) """ - def __init__(self, params): - super().__init__(params) + def __init__(self): + super().__init__() self.class_name = self.__class__.__name__ - self.action = "delete" + self.action = "fabric_delete" self.log = logging.getLogger(f"dcnm.{self.class_name}") self._fabrics_to_delete = [] - self._build_properties() self.ep_fabric_delete = EpFabricDelete() + self._fabric_names = None self._cannot_delete_fabric_reason = None - # path and verb cannot be defined here because endpoints.fabric name - # must be set first. Set these to None here and define them later in - # the commit() method. - self.path = None - self.verb = None - - msg = "ENTERED FabricDelete(): " - msg += f"action: {self.action}, " - msg += f"check_mode: {self.check_mode}, " - msg += f"state: {self.state}" + msg = "ENTERED FabricDelete()" 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["fabric_names"] = None - def _get_fabrics_to_delete(self) -> None: """ - Retrieve fabric info from the controller and set the list of @@ -139,19 +123,6 @@ def _verify_fabric_can_be_deleted(self, fabric_name): msg += "empty. Remove all devices from the fabric and try again." raise ValueError(msg) - def _set_fabric_delete_endpoint(self, fabric_name) -> None: - """ - - Set the fabric delete endpoint for fabric_name - - Raise ``ValueError`` if the endpoint assignment fails - """ - try: - self.ep_fabric_delete.fabric_name = fabric_name - except (ValueError, TypeError) as error: - raise ValueError(error) from error - - self.path = self.ep_fabric_delete.path - self.verb = self.ep_fabric_delete.verb - def _validate_commit_parameters(self): """ - validate the parameters for commit @@ -159,22 +130,32 @@ def _validate_commit_parameters(self): """ method_name = inspect.stack()[0][3] # pylint: disable=unused-variable + if self.fabric_details is None: + msg = f"{self.class_name}.{method_name}: " + msg += "fabric_details must be set prior to calling commit." + raise ValueError(msg) + if self.fabric_names is None: msg = f"{self.class_name}.{method_name}: " msg += "fabric_names must be set prior to calling commit." raise ValueError(msg) + # pylint: disable=no-member if self.rest_send is None: msg = f"{self.class_name}.{method_name}: " msg += "rest_send must be set prior to calling commit." raise ValueError(msg) + # pylint: disable=access-member-before-definition + # pylint: disable=attribute-defined-outside-init if self.results is None: # Instantiate Results() only to register the failure self.results = Results() msg = f"{self.class_name}.{method_name}: " msg += "results must be set prior to calling commit." raise ValueError(msg) + # pylint: enable=access-member-before-definition + # pylint: enable=attribute-defined-outside-init def commit(self): """ @@ -191,9 +172,10 @@ def commit(self): self.register_result(None) raise ValueError(error) from error + # pylint: disable=no-member self.results.action = self.action - self.results.check_mode = self.check_mode - self.results.state = self.state + self.results.check_mode = self.rest_send.check_mode + self.results.state = self.rest_send.state self.results.diff_current = {} try: @@ -235,9 +217,11 @@ def _send_requests(self): - We don't want RestSend to retry on errors since the likelihood of a timeout error when deleting a fabric is low, and there are cases of permanent errors for which we don't want to retry. Hence, we set - timeout to 1 second. + timeout to 1 second and restore the original timeout after the + requests are sent. """ - self.rest_send.check_mode = self.check_mode + # pylint: disable=no-member + self.rest_send.save_settings() self.rest_send.timeout = 1 for fabric_name in self._fabrics_to_delete: @@ -248,20 +232,32 @@ def _send_requests(self): self.results.failed = True self.register_result(fabric_name) raise ValueError(error) from error + self.rest_send.restore_settings() + + def _set_fabric_delete_endpoint(self, fabric_name): + try: + self.ep_fabric_delete.fabric_name = fabric_name + # pylint: disable=no-member + self.rest_send.path = self.ep_fabric_delete.path + self.rest_send.verb = self.ep_fabric_delete.verb + except (ValueError, TypeError) as error: + raise ValueError(error) from error def _send_request(self, fabric_name): """ - - Send a delete request to the controller and register the result. - - Raise ``ValueError`` if the fabric delete endpoint cannot be set + ### Summary + Send a delete request to the controller and register the result. + + ### Raises + - ``ValueError`` if the fabric delete endpoint cannot be set. """ + # pylint: disable=no-member try: self._set_fabric_delete_endpoint(fabric_name) - except ValueError as error: + self.rest_send.commit() + except (ValueError, TypeError) as error: raise ValueError(error) from error - self.rest_send.path = self.path - self.rest_send.verb = self.verb - self.rest_send.commit() self.register_result(fabric_name) def register_result(self, fabric_name): @@ -272,11 +268,16 @@ def register_result(self, fabric_name): - If ``fabric_name`` is not ``None``, set the result to indicate the success or failure of the request. """ + # pylint: disable=no-member self.results.action = self.action - self.results.check_mode = self.check_mode - self.results.state = self.state + if self.rest_send is not None: + self.results.check_mode = self.rest_send.check_mode + self.results.state = self.rest_send.state + else: + self.results.check_mode = False + self.results.state = "unknown" - if fabric_name is None: + if fabric_name is None or self.rest_send is None: self.results.diff_current = {} self.results.response_current = {} self.results.result_current = {"success": False, "changed": False} @@ -309,7 +310,7 @@ def fabric_names(self): - setter: set list of fabric_names - setter: raise ``ValueError`` if ``value`` is not a ``list`` of ``str`` """ - return self._properties["fabric_names"] + return self._fabric_names @fabric_names.setter def fabric_names(self, value): @@ -332,4 +333,4 @@ def fabric_names(self, value): msg += f"got {type(item).__name__} for " msg += f"value {item}" raise ValueError(msg) - self._properties["fabric_names"] = value + self._fabric_names = value diff --git a/plugins/module_utils/fabric/fabric_details.py b/plugins/module_utils/fabric/fabric_details.py deleted file mode 100644 index f7cfc6007..000000000 --- a/plugins/module_utils/fabric/fabric_details.py +++ /dev/null @@ -1,538 +0,0 @@ -# -# 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.common.api.v1.lan_fabric.rest.control.fabrics.fabrics import \ - EpFabrics -from ansible_collections.cisco.dcnm.plugins.module_utils.common.conversion import \ - ConversionUtils -from ansible_collections.cisco.dcnm.plugins.module_utils.common.results import \ - Results -from ansible_collections.cisco.dcnm.plugins.module_utils.fabric.common import \ - FabricCommon - - -class FabricDetails(FabricCommon): - """ - # Parent class for *FabricDetails() subclasses. - - See subclass docstrings for details. - - params is AnsibleModule.params - """ - - def __init__(self, params): - super().__init__(params) - self.class_name = self.__class__.__name__ - - self.log = logging.getLogger(f"dcnm.{self.class_name}") - msg = "ENTERED FabricDetails()" - msg += f"state: {self.state}, " - msg += f"check_mode: {self.check_mode}" - self.log.debug(msg) - - self.data = {} - self.results = Results() - self.conversion = ConversionUtils() - self.ep_fabrics = EpFabrics() - - def _update_results(self): - """ - Update the results object with the current state of the fabric - details. - """ - 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 - if self.results.response_current.get("RETURN_CODE") == 200: - self.results.failed = False - else: - self.results.failed = True - # FabricDetails never changes the controller state - self.results.changed = False - - def refresh_super(self): - """ - Refresh the fabric details from the controller and - populate self.data with the results. - - self.data is a dictionary of fabric details, keyed on - fabric name. - """ - method_name = inspect.stack()[0][3] # pylint: disable=unused-variable - - self.rest_send.path = self.ep_fabrics.path - self.rest_send.verb = self.ep_fabrics.verb - - # We always want to get the controller's current fabric state, - # regardless of the current value of check_mode. - # We save the current check_mode value, set rest_send.check_mode - # to False so the request will be sent to the controller, and then - # restore the original check_mode value. - msg = f"{self.class_name}.{method_name}: calling self.rest_send.commit()" - self.log.debug(msg) - save_check_mode = self.rest_send.check_mode - self.rest_send.check_mode = False - self.rest_send.timeout = 1 - self.rest_send.commit() - self.rest_send.check_mode = save_check_mode - - self.data = {} - if self.rest_send.response_current.get("DATA") is None: - # The DATA key should always be present. We should never hit this. - self._update_results() - return - for item in self.rest_send.response_current.get("DATA"): - fabric_name = item.get("nvPairs", {}).get("FABRIC_NAME", None) - if fabric_name is None: - self._update_results() - return - self.data[fabric_name] = item - - msg = f"{self.class_name}.{method_name}: calling self.rest_send.commit() DONE" - self.log.debug(msg) - - self._update_results() - - def _get(self, item): - """ - overridden in subclasses - """ - - def _get_nv_pair(self, item): - """ - overridden in subclasses - """ - - @property - def all_data(self): - """ - Return all fabric details from the controller (i.e. self.data) - """ - return self.data - - @property - def asn(self): - """ - Return the BGP asn of the fabric specified with filter, if it exists. - Return None otherwise - - Type: string - Possible values: - - e.g. 65000 - - None - """ - try: - return self._get("asn") - except ValueError as error: - msg = f"Failed to retrieve asn: Error detail: {error}" - self.log.debug(msg) - return None - - @property - def deployment_freeze(self): - """ - Return the nvPairs.DEPLOYMENT_FREEZE of the fabric specified with filter, - if it exists. - Return None otherwise - - Type: string - Possible values: - - true - - false - """ - try: - return self._get_nv_pair("DEPLOYMENT_FREEZE") - except ValueError as error: - msg = f"Failed to retrieve deployment_freeze: Error detail: {error}" - self.log.debug(msg) - return None - - @property - def enable_pbr(self): - """ - Return the PBR enable state of the fabric specified with filter, - if it exists. - Return None otherwise - - Type: boolean - Possible values: - - True - - False - - None - """ - try: - return self._get_nv_pair("ENABLE_PBR") - except ValueError as error: - msg = f"Failed to retrieve enable_pbr: Error detail: {error}" - self.log.debug(msg) - return None - - @property - def fabric_id(self): - """ - Return the fabricId of the fabric specified with filter, - if it exists. - Return None otherwise - - Type: string - Possible values: - - e.g. FABRIC-5 - - None - """ - try: - return self._get("fabricId") - except ValueError as error: - msg = f"Failed to retrieve fabric_id: Error detail: {error}" - self.log.debug(msg) - return None - - @property - def fabric_type(self): - """ - Return the nvPairs.FABRIC_TYPE of the fabric specified with filter, - if it exists. - Return None otherwise - - Type: string - Possible values: - - Switch_Fabric - - None - """ - try: - return self._get_nv_pair("FABRIC_TYPE") - except ValueError as error: - msg = f"Failed to retrieve fabric_type: Error detail: {error}" - self.log.debug(msg) - return None - - @property - def is_read_only(self): - """ - Return the nvPairs.IS_READ_ONLY of the fabric specified with filter, - if it exists. - Return None otherwise - - Type: string - Possible values: - - true - - false - """ - try: - return self._get_nv_pair("IS_READ_ONLY") - except ValueError as error: - msg = f"Failed to retrieve is_read_only: Error detail: {error}" - self.log.debug(msg) - return None - - @property - def replication_mode(self): - """ - Return the nvPairs.REPLICATION_MODE of the fabric specified with filter, - if it exists. - Return None otherwise - - Type: string - Possible values: - - Ingress - - Multicast - - None - """ - try: - return self._get_nv_pair("REPLICATION_MODE") - except ValueError as error: - msg = f"Failed to retrieve replication_mode: Error detail: {error}" - self.log.debug(msg) - return None - - @property - def template_name(self): - """ - Return the templateName of the fabric specified with filter, - if it exists. - Return None otherwise - - Type: string - Possible values: - - Easy_Fabric - - TODO - add other values - - None - """ - try: - return self._get("templateName") - except ValueError as error: - msg = f"Failed to retrieve template_name: Error detail: {error}" - self.log.debug(msg) - return None - - -class FabricDetailsByName(FabricDetails): - """ - Retrieve fabric details from the controller and provide - property accessors for the fabric attributes. - - Usage (where params is AnsibleModule.params): - - ```python - instance = FabricDetailsByName(params) - instance.rest_send = RestSend(ansible_module) - instance.refresh() - instance.filter = "MyFabric" - # BGP AS for fabric "MyFabric" - bgp_as = instance.asn - - # all fabric details for "MyFabric" - fabric_dict = instance.filtered_data - if fabric_dict is None: - # fabric does not exist on the controller - # etc... - ``` - - Or: - - ```python - instance.FabricDetailsByName(module) - instance.rest_send = RestSend(ansible_module) - instance.refresh() - all_fabrics = instance.all_data - ``` - - - Where ``all_fabrics`` will be a dictionary of all fabrics - on the controller, keyed on fabric name. - """ - - def __init__(self, params): - super().__init__(params) - self.class_name = self.__class__.__name__ - - self.log = logging.getLogger(f"dcnm.{self.class_name}") - self.log.debug("ENTERED FabricDetailsByName()") - - self.data_subclass = {} - self._properties["filter"] = None - - def refresh(self): - """ - Refresh fabric_name current details from the controller - """ - self.refresh_super() - self.data_subclass = copy.deepcopy(self.data) - - def _get(self, item): - """ - Retrieve the value of the top-level (non-nvPair) item for fabric_name - (anything not in the nvPairs dictionary). - - - raise ``ValueError`` if ``self.filter`` has not been set. - - raise ``ValueError`` if ``self.filter`` (fabric_name) does not exist - on the controller. - - raise ``ValueError`` if item is not a valid property name for the fabric. - - See also: ``_get_nv_pair()`` - """ - method_name = inspect.stack()[0][3] - - msg = f"{self.class_name}.{method_name}: " - msg += f"instance.filter {self.filter} " - self.log.debug(msg) - - if self.filter is None: - msg = f"{self.class_name}.{method_name}: " - msg += "set instance.filter to a fabric name " - msg += f"before accessing property {item}." - raise ValueError(msg) - - if self.data_subclass.get(self.filter) is None: - msg = f"{self.class_name}.{method_name}: " - msg += f"fabric_name {self.filter} does not exist on the controller." - raise ValueError(msg) - - if self.data_subclass[self.filter].get(item) is None: - msg = f"{self.class_name}.{method_name}: " - msg += f"{self.filter} unknown property name: {item}." - raise ValueError(msg) - - return self.conversion.make_none( - self.conversion.make_boolean(self.data_subclass[self.filter].get(item)) - ) - - def _get_nv_pair(self, item): - """ - # Retrieve the value of the nvPair item for fabric_name. - - - raise ``ValueError`` if ``self.filter`` has not been set. - - raise ``ValueError`` if ``self.filter`` (fabric_name) does not exist on the controller. - - raise ``ValueError`` if item is not a valid property name for the fabric. - - See also: ``self._get()`` - """ - method_name = inspect.stack()[0][3] - - msg = f"{self.class_name}.{method_name}: " - msg += f"instance.filter {self.filter} " - self.log.debug(msg) - - if self.filter is None: - msg = f"{self.class_name}.{method_name}: " - msg += "set instance.filter to a fabric name " - msg += f"before accessing property {item}." - raise ValueError(msg) - - if self.data_subclass.get(self.filter) is None: - msg = f"{self.class_name}.{method_name}: " - msg += f"fabric_name {self.filter} " - msg += "does not exist on the controller." - raise ValueError(msg) - - if self.data_subclass[self.filter].get("nvPairs", {}).get(item) is None: - msg = f"{self.class_name}.{method_name}: " - msg += f"fabric_name {self.filter} " - msg += f"unknown property name: {item}." - raise ValueError(msg) - - return self.conversion.make_none( - self.conversion.make_boolean( - self.data_subclass[self.filter].get("nvPairs").get(item) - ) - ) - - @property - def filtered_data(self): - """ - - Return a dictionary of the fabric matching self.filter. - - Return None if the fabric does not exist on the controller. - - raise ``ValueError`` if self.filter has not been set. - """ - method_name = inspect.stack()[0][3] - if self.filter is None: - msg = f"{self.class_name}.{method_name}: " - msg += f"{self.class_name}.filter must be set before calling " - msg += f"{self.class_name}.filtered_data" - raise ValueError(msg) - return self.data_subclass.get(self.filter, None) - - @property - def filter(self): - """ - Set the fabric_name of the fabric to query. - - This needs to be set before accessing this class's properties. - """ - return self._properties.get("filter") - - @filter.setter - def filter(self, value): - self._properties["filter"] = value - - -class FabricDetailsByNvPair(FabricDetails): - """ - Retrieve fabric details from the controller filtered - by nvPair key and value. This sets the filtered_data - property to a dictionary of all fabrics on the controller - that match filter_key and filter_value. - - Usage (where params is AnsibleModule.params): - - instance = FabricDetailsNvPair(params) - instance.refresh() - instance.filter_key = "DCI_SUBNET_RANGE" - instance.filter_value = "10.33.0.0/16" - fabrics = instance.filtered_data - """ - - def __init__(self, params): - super().__init__(params) - self.class_name = self.__class__.__name__ - - self.log = logging.getLogger(f"dcnm.{self.class_name}") - self.log.debug("ENTERED FabricDetailsByNvPair()") - - self.data_subclass = {} - self._properties["filter_key"] = None - self._properties["filter_value"] = None - - def refresh(self): - """ - Refresh fabric_name current details from the controller. - - - raise ValueError if self.filter_key has not been set. - """ - method_name = inspect.stack()[0][3] - - if self.filter_key is None: - msg = f"{self.class_name}.{method_name}: " - msg += f"set {self.class_name}.filter_key to a nvPair key " - msg += f"before calling {self.class_name}.refresh()." - raise ValueError(msg) - if self.filter_value is None: - msg = f"{self.class_name}.{method_name}: " - msg += f"set {self.class_name}.filter_value to a nvPair value " - msg += f"before calling {self.class_name}.refresh()." - raise ValueError(msg) - - self.refresh_super() - for item, value in self.data.items(): - if value.get("nvPairs", {}).get(self.filter_key) == self.filter_value: - self.data_subclass[item] = value - - @property - def filtered_data(self): - """ - - Return a ``dict`` of the fabric(s) matching ``self.filter_key`` - and ``self.filter_value``. - - Return an empty ``dict`` if the fabric does not exist on - the controller. - """ - return self.data_subclass - - @property - def filter_key(self): - """ - - getter: Return the nvPairs key to filter on. - - setter: Set the nvPairs key to filter on. - - This should be an exact match for the key in the nvPairs - dictionary for the fabric. - """ - return self._properties.get("filter_key") - - @filter_key.setter - def filter_key(self, value): - self._properties["filter_key"] = value - - @property - def filter_value(self): - """ - - getter: Return the nvPairs value to filter on. - - setter: Set the nvPairs value to filter on. - - This should be an exact match for the value in the nvPairs - dictionary for the fabric. - """ - return self._properties.get("filter_value") - - @filter_value.setter - def filter_value(self, value): - self._properties["filter_value"] = value diff --git a/plugins/module_utils/fabric/fabric_details_v2.py b/plugins/module_utils/fabric/fabric_details_v2.py index 04d596a43..7b75c6cbe 100644 --- a/plugins/module_utils/fabric/fabric_details_v2.py +++ b/plugins/module_utils/fabric/fabric_details_v2.py @@ -42,39 +42,16 @@ class FabricDetails: See subclass docstrings for details. ### Raises - - ``ValueError`` if: - - Mandatory properties are not set. - - RestSend object raises ``TypeError`` or ``ValueError``. - - ``params`` is missing ``check_mode`` key. - - ``params`` is missing ``state`` key. - - params is AnsibleModule.params + None """ - def __init__(self, params): + def __init__(self): self.class_name = self.__class__.__name__ - method_name = inspect.stack()[0][3] - self.params = params - self.check_mode = self.params.get("check_mode", None) - if self.check_mode is None: - msg = f"{self.class_name}.{method_name}: " - msg += "check_mode is missing from params. " - msg += f"params: {params}." - raise ValueError(msg) - - self.state = self.params.get("state", None) - if self.state is None: - msg = f"{self.class_name}.{method_name}: " - msg += "state is missing from params. " - msg += f"params: {params}." - raise ValueError(msg) self.action = "fabric_details" self.log = logging.getLogger(f"dcnm.{self.class_name}") msg = "ENTERED FabricDetails() (v2)" - msg += f"state: {self.state}, " - msg += f"check_mode: {self.check_mode}" self.log.debug(msg) self.data = {} @@ -467,7 +444,7 @@ class FabricDetailsByName(FabricDetails): rest_send.sender = sender rest_send.response_handler = ResponseHandler() - instance = FabricDetailsByName(params) + instance = FabricDetailsByName() instance.rest_send = rest_send instance.results = Results() instance.refresh() @@ -497,7 +474,7 @@ class FabricDetailsByName(FabricDetails): rest_send.sender = sender rest_send.response_handler = ResponseHandler() - instance = FabricDetailsByName(params) + instance = FabricDetailsByName() instance.rest_send = rest_send instance.results = Results() instance.refresh() @@ -508,19 +485,12 @@ class FabricDetailsByName(FabricDetails): controller, keyed on fabric name. """ - def __init__(self, params): + def __init__(self): self.class_name = self.__class__.__name__ - try: - super().__init__(params) - except ValueError as error: - msg = "FabricDetailsByName.__init__: " - msg += "Failed in super().__init__(). " - msg += f"Error detail: {error}" - raise ValueError(msg) from error + super().__init__() self.log = logging.getLogger(f"dcnm.{self.class_name}") - msg = "ENTERED FabricDetailsByName() " - msg += f"params {params}." + msg = "ENTERED FabricDetailsByName()" self.log.debug(msg) self.data_subclass = {} @@ -698,7 +668,7 @@ class FabricDetailsByNvPair(FabricDetails): rest_send.sender = sender rest_send.response_handler = ResponseHandler() - instance = FabricDetailsNvPair(params) + instance = FabricDetailsNvPair() instance.filter_key = "DCI_SUBNET_RANGE" instance.filter_value = "10.33.0.0/16" instance.refresh() @@ -706,15 +676,9 @@ class FabricDetailsByNvPair(FabricDetails): ``` """ - def __init__(self, params): + def __init__(self): self.class_name = self.__class__.__name__ - try: - super().__init__(params) - except ValueError as error: - msg = "FabricDetailsByNvPair.__init__: " - msg += "Failed in super().__init__(). " - msg += f"Error detail: {error}" - raise ValueError(msg) from error + super().__init__() self.log = logging.getLogger(f"dcnm.{self.class_name}") diff --git a/plugins/module_utils/fabric/fabric_summary.py b/plugins/module_utils/fabric/fabric_summary.py index 7d8ae01c1..b720ae63f 100644 --- a/plugins/module_utils/fabric/fabric_summary.py +++ b/plugins/module_utils/fabric/fabric_summary.py @@ -78,9 +78,22 @@ class FabricSummary(FabricCommon): Usage: ```python - params = ansible_module.params - instance = FabricSummary(params) - instance.rest_send = RestSend(ansible_module) + # params is typically obtained from ansible_module.params + # but can also be specified manually, like below. + params = { + "check_mode": False, + "state": "merged" + } + + sender = Sender() + sender.ansible_module = ansible_module + + rest_send = RestSend(params) + rest_send.sender = sender + rest_send.response_handler = ResponseHandler() + + instance = FabricSummary() + instance.rest_send = rest_send instance.fabric_name = "MyFabric" instance.refresh() fabric_summary = instance.data @@ -89,8 +102,8 @@ class FabricSummary(FabricCommon): etc... """ - def __init__(self, params): - super().__init__(params) + def __init__(self): + super().__init__() self.class_name = self.__class__.__name__ self.log = logging.getLogger(f"dcnm.{self.class_name}") @@ -106,24 +119,15 @@ def __init__(self, params): self.results = Results() - self._build_properties() + self._border_gateway_count = 0 + self._device_count = 0 + self._fabric_name = None + self._leaf_count = 0 + self._spine_count = 0 - msg = "ENTERED FabricSummary(): " - msg += f"state: {self.state}, " - msg += f"check_mode: {self.check_mode} " + msg = "ENTERED FabricSummary()" self.log.debug(msg) - def _build_properties(self): - """ - Initialize properties specific to this class. - """ - # self._properties is already initialized in the parent class - self._properties["border_gateway_count"] = 0 - self._properties["device_count"] = 0 - self._properties["fabric_name"] = None - self._properties["leaf_count"] = 0 - self._properties["spine_count"] = 0 - def _update_device_counts(self): """ - From the controller response, update class properties @@ -137,14 +141,14 @@ def _update_device_counts(self): msg = f"self.data: {json.dumps(self.data, indent=4, sort_keys=True)}" self.log.debug(msg) - self._properties["border_gateway_count"] = self.data.get("switchRoles", {}).get( + self._border_gateway_count = self.data.get("switchRoles", {}).get( "border gateway", 0 ) - self._properties["leaf_count"] = self.data.get("switchRoles", {}).get("leaf", 0) - self._properties["spine_count"] = self.data.get("switchRoles", {}).get( + self._leaf_count = self.data.get("switchRoles", {}).get("leaf", 0) + self._spine_count = self.data.get("switchRoles", {}).get( "spine", 0 ) - self._properties["device_count"] = ( + self._device_count = ( self.leaf_count + self.spine_count + self.border_gateway_count ) @@ -155,6 +159,7 @@ def _set_fabric_summary_endpoint(self): """ try: self.ep_fabric_summary.fabric_name = self.fabric_name + # pylint: disable=no-member self.rest_send.path = self.ep_fabric_summary.path self.rest_send.verb = self.ep_fabric_summary.verb except ValueError as error: @@ -170,6 +175,7 @@ def _verify_controller_response(self): """ method_name = inspect.stack()[0][3] + # pylint: disable=no-member controller_return_code = self.rest_send.response_current.get( "RETURN_CODE", None ) @@ -208,6 +214,7 @@ def refresh(self): msg += f"{self.class_name}.refresh()." raise ValueError(msg) + # pylint: disable=no-member if self.rest_send is None: msg = f"{self.class_name}.{method_name}: " msg += f"Set {self.class_name}.rest_send prior to calling " @@ -239,6 +246,7 @@ def refresh(self): self.results.result = self.rest_send.result_current self.results.register_task_result() + # pylint: enable=no-member try: self._verify_controller_response() except ControllerResponseError as error: @@ -283,7 +291,7 @@ def border_gateway_count(self) -> int: self.verify_refresh_has_been_called(method_name) except ValueError as error: raise ValueError(error) from error - return self._properties["border_gateway_count"] + return self._border_gateway_count @property def device_count(self) -> int: @@ -296,7 +304,7 @@ def device_count(self) -> int: self.verify_refresh_has_been_called(method_name) except ValueError as error: raise ValueError(error) from error - return self._properties["device_count"] + return self._device_count @property def fabric_is_empty(self) -> bool: @@ -322,7 +330,7 @@ def fabric_name(self) -> str: - setter: Raise ``ValueError`` if fabric_name is invalid (i.e. the controller would return an error due to invalid characters). """ - return self._properties.get("fabric_name") + return self._fabric_name @fabric_name.setter def fabric_name(self, value: str): @@ -330,7 +338,7 @@ def fabric_name(self, value: str): self.conversion.validate_fabric_name(value) except ValueError as error: raise ValueError(error) from error - self._properties["fabric_name"] = value + self._fabric_name = value @property def leaf_count(self) -> int: @@ -343,7 +351,7 @@ def leaf_count(self) -> int: self.verify_refresh_has_been_called(method_name) except ValueError as error: raise ValueError(error) from error - return self._properties["leaf_count"] + return self._leaf_count @property def spine_count(self) -> int: @@ -356,4 +364,4 @@ def spine_count(self) -> int: self.verify_refresh_has_been_called(method_name) except ValueError as error: raise ValueError(error) from error - return self._properties["spine_count"] + return self._spine_count diff --git a/plugins/module_utils/fabric/query.py b/plugins/module_utils/fabric/query.py index 738110e73..eacea2676 100644 --- a/plugins/module_utils/fabric/query.py +++ b/plugins/module_utils/fabric/query.py @@ -20,26 +20,41 @@ import inspect import logging +from ansible_collections.cisco.dcnm.plugins.module_utils.common.results import \ + Results from ansible_collections.cisco.dcnm.plugins.module_utils.fabric.common import \ FabricCommon class FabricQuery(FabricCommon): """ - Query fabrics + ### Summary + Query fabrics. - Usage: + ### Raises + - ``ValueError`` if: + - ``fabric_details`` is not set. + - ``fabric_names`` is not set. + - ``rest_send`` is not set. + - ``results`` is not set. + + ### Usage ```python from ansible_collections.cisco.dcnm.plugins.module_utils.fabric.query import FabricQuery from ansible_collections.cisco.dcnm.plugins.module_utils.common.results import Results - fabric_details = FabricDetailsByName(params) - fabric_details.rest_send = RestSend(ansible_module) - + params = {"state": "query", "check_mode": False} + rest_send = RestSend(params) results = Results() - params = ansible_module.params - instance = FabricQuery(params) + + fabric_details = FabricDetailsByName() + fabric_details.rest_send = rest_send + fabric_details.results = results # or Results() if you don't want + # fabric_details results to be separate + # from FabricQuery results. + + instance = FabricQuery() instance.fabric_details = fabric_details instance.fabric_names = ["FABRIC_1", "FABRIC_2"] instance.results = results @@ -64,40 +79,34 @@ class FabricQuery(FabricCommon): ``` """ - def __init__(self, params): - super().__init__(params) + def __init__(self): + super().__init__() self.class_name = self.__class__.__name__ + self.action = "fabric_query" self.log = logging.getLogger(f"dcnm.{self.class_name}") - msg = "ENTERED FabricQuery(): " - msg += f"state: {self.state}" - self.log.debug(msg) self._fabrics_to_query = [] - self._build_properties() - - self.action = "query" - self.changed = False - self.failed = False + self._fabric_names = None - def _build_properties(self): - """ - self._properties holds property values for the class - """ - # self._properties is already set in the parent class - self._properties["fabric_names"] = None + msg = "ENTERED FabricQuery()" + self.log.debug(msg) @property def fabric_names(self): """ - - setter: return the fabric names - - getter: set the fable_names - - getter: raise ``ValueError`` if ``value`` is not a ``list`` - - getter: raise ``ValueError`` if ``value`` is an empty list - - getter: raise ``ValueError`` if ``value`` is not a list of strings + ### Summary + - setter: return the fabric names + - getter: set the fabric_names + + ### Raises + - ``ValueError`` if: + - ``value`` is not a list. + - ``value`` is an empty list. + - ``value`` is not a list of strings. """ - return self._properties["fabric_names"] + return self._fabric_names @fabric_names.setter def fabric_names(self, value): @@ -120,31 +129,80 @@ def fabric_names(self, value): msg += f"got {type(item).__name__} for " msg += f"value {item}" raise ValueError(msg) - self._properties["fabric_names"] = value + self._fabric_names = value - def commit(self): + def _validate_commit_parameters(self): """ - - query each of the fabrics in self.fabric_names - - raise ``ValueError`` if ``fabric_names`` is not set - - raise ``ValueError`` if ``fabric_details`` is not set - + ### Summary + - validate the parameters for commit. + + ### Raises + - ``ValueError`` if: + - ``fabric_details`` is not set. + - ``fabric_names`` is not set. + - ``rest_send`` is not set. + - ``results`` is not set. """ - method_name = inspect.stack()[0][3] + method_name = inspect.stack()[0][3] # pylint: disable=unused-variable + + if self.fabric_details is None: + msg = f"{self.class_name}.{method_name}: " + msg += "fabric_details must be set before calling commit." + raise ValueError(msg) + if self.fabric_names is None: msg = f"{self.class_name}.{method_name}: " - msg += "fabric_names must be set prior to calling commit." + msg += "fabric_names must be set before calling commit." raise ValueError(msg) - if self.fabric_details is None: + # pylint: disable=no-member + if self.rest_send is None: + msg = f"{self.class_name}.{method_name}: " + msg += "rest_send must be set before calling commit." + raise ValueError(msg) + + # pylint: disable=access-member-before-definition + if self.results is None: + # Instantiate Results() to register the failure + self.results = Results() msg = f"{self.class_name}.{method_name}: " - msg += "fabric_details must be set prior to calling commit." + msg += "results must be set before calling commit." raise ValueError(msg) + def commit(self): + """ + ### Summary + - query each of the fabrics in ``fabric_names``. + + ### Raises + - ``ValueError`` if: + - ``_validate_commit_parameters`` raises ``ValueError``. + + """ + try: + self._validate_commit_parameters() + except ValueError as error: + # pylint: disable=no-member + self.results.action = self.action + self.results.changed = False + self.results.failed = True + if self.rest_send is not None: + self.results.check_mode = self.rest_send.check_mode + self.results.state = self.rest_send.state + else: + self.results.check_mode = False + self.results.state = "query" + self.results.register_task_result() + raise ValueError(error) from error + # pylint: enable=no-member + self.fabric_details.refresh() + # pylint: disable=no-member self.results.action = self.action - self.results.check_mode = self.check_mode - self.results.state = self.state + self.results.check_mode = self.rest_send.check_mode + self.results.state = self.rest_send.state + # pylint: enable=no-member msg = f"self.fabric_names: {self.fabric_names}" self.log.debug(msg) @@ -155,6 +213,7 @@ def commit(self): self.fabric_details.all_data[fabric_name] ) + # pylint: disable=no-member self.results.diff_current = add_to_diff self.results.response_current = copy.deepcopy( self.fabric_details.results.response_current diff --git a/plugins/module_utils/fabric/replaced.py b/plugins/module_utils/fabric/replaced.py index c317df080..4e3bea2a9 100644 --- a/plugins/module_utils/fabric/replaced.py +++ b/plugins/module_utils/fabric/replaced.py @@ -47,10 +47,10 @@ class FabricReplacedCommon(FabricCommon): - FabricReplacedBulk """ - def __init__(self, params): - super().__init__(params) + def __init__(self): + super().__init__() self.class_name = self.__class__.__name__ - self.action = "replace" + self.action = "fabric_replace" self.log = logging.getLogger(f"dcnm.{self.class_name}") @@ -72,10 +72,7 @@ def __init__(self, params): # Populated in _fabric_needs_update_for_replaced_state() self._controller_config = {} - msg = "ENTERED FabricReplacedCommon(): " - msg += f"action: {self.action}, " - msg += f"check_mode: {self.check_mode}, " - msg += f"state: {self.state}" + msg = "ENTERED FabricReplacedCommon()" self.log.debug(msg) def _translate_payload_for_comparison(self, payload: dict) -> dict: @@ -124,19 +121,52 @@ def _translate_payload_for_comparison(self, payload: dict) -> dict: translated_payload[user_parameter] = user_value return copy.deepcopy(translated_payload) + def update_site_id(self, playbook, controller): + """ + ### Summary + Special-case handling for fabric SITE_ID parameter update. + + ### Raises + None + + ### Discussion + - If playbook.SITE_ID == controller.SITE_ID, no change is needed. + Return None. + - If playbook.SITE_ID == controller.BGP_AS, no change is needed. + Return None. + - If playbook.SITE_ID is not None and playbook.SITE_ID != BGP_AS, + update payload with playbook.SITE_ID. + - If playbook.SITE_ID is None, and controller.SITE_ID != controller.BGP_AS, + update the payload with controller.BGP_AS. + - Default return is None (don't add SITE_ID to payload). + """ + bgp_as = self._controller_config.get("BGP_AS", None) + if playbook == controller: + return None + if playbook == bgp_as: + return None + if playbook is not None and playbook != bgp_as: + return {"SITE_ID": playbook} + if playbook is None and controller != bgp_as: + return {"SITE_ID": bgp_as} + return None + def update_replaced_payload(self, parameter, playbook, controller, default): """ + ### Summary Given a parameter, and the parameter's values from: + - playbook config - controller fabric config - default value from the template Return either: + - None if the parameter does not need to be updated. - A dict with the parameter and playbook value if the parameter needs to be updated. - Usage: + ### Usage: ```python payload_to_send_to_controller = {} for parameter, controller in _controller_config.items(): @@ -147,9 +177,21 @@ def update_replaced_payload(self, parameter, playbook, controller, default): continue payload_to_send_to_controller.update(result) ``` + + ### NOTES + - Special-case SITE_ID. + - The template default value is "", but the actual default value + is BGP_AS. + - Explicitely skip ANYCAST_RP_IP_RANGE_INTERNAL. + - It is an internal parameter, but is not specified as such in + the fabric template. """ + if parameter == "ANYCAST_RP_IP_RANGE_INTERNAL": + return None + if parameter == "SITE_ID": + return self.update_site_id(playbook, controller) if playbook is None: - if controller != default: + if controller not in {default, ""}: if default is None: # The controller prefers empty string over null. return {parameter: ""} @@ -426,8 +468,6 @@ def _send_payloads(self): - ``FabricReplacedCommon()._config_save()`` - ``FabricReplacedCommon()._config_deploy()`` """ - self.rest_send.check_mode = self.check_mode - try: self._fixup_payloads_to_commit() except ValueError as error: @@ -442,6 +482,7 @@ def _send_payloads(self): except ValueError as error: raise ValueError(error) from error + # pylint: disable=no-member # Skip config-save if prior actions encountered errors. if True in self.results.failed: return @@ -507,6 +548,7 @@ def _send_payload(self, payload): # We don't want RestSend to retry on errors since the likelihood of a # timeout error when updating a fabric is low, and there are many cases # of permanent errors for which we don't want to retry. + # pylint: disable=no-member self.rest_send.timeout = 1 self.rest_send.path = self.path self.rest_send.verb = self.verb @@ -522,8 +564,8 @@ def _send_payload(self, payload): 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.check_mode = self.rest_send.check_mode + self.results.state = self.rest_send.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() @@ -539,7 +581,7 @@ def payloads(self): - setter: raise ``ValueError`` if ``payloads`` is not a ``list`` of ``dict`` - setter: raise ``ValueError`` if any payload is missing mandatory keys """ - return self._properties["payloads"] + return self._payloads @payloads.setter def payloads(self, value): @@ -555,7 +597,7 @@ def payloads(self, value): self._verify_payload(item) except ValueError as error: raise ValueError(error) from error - self._properties["payloads"] = value + self._payloads = value class FabricReplacedBulk(FabricReplacedCommon): @@ -597,21 +639,14 @@ class FabricReplacedBulk(FabricReplacedCommon): ``` """ - def __init__(self, params): - super().__init__(params) + def __init__(self): + super().__init__() self.class_name = self.__class__.__name__ self.log = logging.getLogger(f"dcnm.{self.class_name}") - self.log.debug("ENTERED FabricReplacedBulk()") + self._payloads = None - self._build_properties() - - def _build_properties(self): - """ - Add properties specific to this class - """ - # properties dict is already initialized in FabricCommon - self._properties["payloads"] = None + self.log.debug("ENTERED FabricReplacedBulk()") def commit(self): """ @@ -640,14 +675,15 @@ def commit(self): msg += "payloads must be set prior to calling commit." raise ValueError(msg) + # pylint: disable=no-member if self.rest_send is None: msg = f"{self.class_name}.{method_name}: " msg += "rest_send must be set prior to calling commit." raise ValueError(msg) self.results.action = self.action - self.results.check_mode = self.check_mode - self.results.state = self.state + self.results.check_mode = self.rest_send.check_mode + self.results.state = self.rest_send.state self.template_get.rest_send = self.rest_send try: diff --git a/plugins/module_utils/fabric/template_get.py b/plugins/module_utils/fabric/template_get.py index 058f02ab5..edc6e9d0b 100644 --- a/plugins/module_utils/fabric/template_get.py +++ b/plugins/module_utils/fabric/template_get.py @@ -21,14 +21,17 @@ import copy import inspect import logging -from typing import Any, Dict from ansible_collections.cisco.dcnm.plugins.module_utils.common.api.v1.configtemplate.rest.config.templates.templates import \ EpTemplate from ansible_collections.cisco.dcnm.plugins.module_utils.common.exceptions import \ ControllerResponseError +from ansible_collections.cisco.dcnm.plugins.module_utils.common.properties import \ + Properties +@Properties.add_rest_send +@Properties.add_results class TemplateGet: """ - Retrieve a template from the controller. @@ -64,14 +67,10 @@ def __init__(self): self.result = [] self.result_current = {} - self._init_properties() - - def _init_properties(self) -> None: - self._properties = {} - self._properties["rest_send"] = None - self._properties["results"] = None - self._properties["template"] = None - self._properties["template_name"] = None + self._rest_send = None + self._results = None + self._template = None + self._template_name = None def _set_template_endpoint(self) -> None: """ @@ -99,7 +98,9 @@ def refresh(self): - raise ``ControllerResponseError`` if the controller ``RETURN_CODE`` != 200 """ + # pylint: disable=no-member method_name = inspect.stack()[0][3] + try: self._set_template_endpoint() except ValueError as error: @@ -138,62 +139,6 @@ def refresh(self): "parameters", [] ) - @property - def rest_send(self): - """ - - getter: Return an instance of the RestSend class. - - setter: Set an instance of the RestSend class. - - setter: Raise ``TypeError`` if the value is not an - instance of RestSend. - """ - return self._properties["rest_send"] - - @rest_send.setter - def rest_send(self, value): - method_name = inspect.stack()[0][3] - msg = f"{self.class_name}.{method_name}: " - msg += "value must be an instance of RestSend. " - msg += f"Got value {value} of type {type(value).__name__}." - _class_name = None - try: - _class_name = value.class_name - except AttributeError as error: - msg += f" Error detail: {error}." - self.log.debug(msg) - raise TypeError(msg) from error - if _class_name != "RestSend": - self.log.debug(msg) - raise TypeError(msg) - self._properties["rest_send"] = value - - @property - def results(self): - """ - - getter: Return an instance of the Results class. - - setter: Set an instance of the Results class. - - setter: Raise ``TypeError`` if the value is not an - instance of Results. - """ - return self._properties["results"] - - @results.setter - def results(self, value): - method_name = inspect.stack()[0][3] - msg = f"{self.class_name}.{method_name}: " - msg += "value must be an instance of Results. " - msg += f"Got value {value} of type {type(value).__name__}." - _class_name = None - try: - _class_name = value.class_name - except AttributeError as error: - msg += f" Error detail: {error}." - self.log.debug(msg) - raise TypeError(msg) from error - if _class_name != "Results": - self.log.debug(msg) - raise TypeError(msg) - self._properties["results"] = value - @property def template(self): """ @@ -202,17 +147,17 @@ def template(self): - The template must be a template retrieved from the controller. - setter: Raise ``TypeError`` if the value is not a dict. """ - return self._properties["template"] + return self._template @template.setter - def template(self, value: Dict[str, Any]) -> None: + def template(self, value) -> None: method_name = inspect.stack()[0][3] if not isinstance(value, dict): msg = f"{self.class_name}.{method_name}: " msg += "template must be an instance of dict." self.log.debug(msg) raise TypeError(msg) - self._properties["template"] = value + self._template = value @property def template_name(self) -> str: @@ -223,7 +168,7 @@ def template_name(self) -> str: from the controller. - setter: Raise ``TypeError`` if the value is not a str. """ - return self._properties["template_name"] + return self._template_name @template_name.setter def template_name(self, value: str) -> None: @@ -234,4 +179,4 @@ def template_name(self, value: str) -> None: msg += f"Got type: {type(value)} for value: {value}." self.log.debug(msg) raise TypeError(msg) - self._properties["template_name"] = value + self._template_name = value diff --git a/plugins/module_utils/fabric/template_get_all.py b/plugins/module_utils/fabric/template_get_all.py index a147a1650..67385beab 100644 --- a/plugins/module_utils/fabric/template_get_all.py +++ b/plugins/module_utils/fabric/template_get_all.py @@ -21,14 +21,17 @@ import copy import inspect import logging -from typing import Any, Dict from ansible_collections.cisco.dcnm.plugins.module_utils.common.api.v1.configtemplate.rest.config.templates.templates import \ EpTemplates from ansible_collections.cisco.dcnm.plugins.module_utils.common.exceptions import \ ControllerResponseError +from ansible_collections.cisco.dcnm.plugins.module_utils.common.properties import \ + Properties +@Properties.add_rest_send +@Properties.add_results class TemplateGetAll: """ Retrieve a list of all templates from the controller. @@ -63,13 +66,10 @@ def __init__(self): self.result = [] self.result_current = {} - self._init_properties() + self._rest_send = None + self._results = None - def _init_properties(self) -> None: - self._properties = {} - self._properties["rest_send"] = None - self._properties["results"] = None - self._properties["templates"] = None + self._templates = None def refresh(self): """ @@ -78,6 +78,7 @@ def refresh(self): - raise ``ValueError`` if self.rest_send is not set. - raise ``ControllerResponseError`` if RETURN_CODE != 200. """ + # pylint: disable=no-member method_name = inspect.stack()[0][3] if self.rest_send is None: @@ -115,74 +116,18 @@ def refresh(self): self.templates = copy.deepcopy(templates) @property - def rest_send(self): - """ - - getter: Return an instance of the RestSend class. - - setter: Set an instance of the RestSend class. - - setter: Raise ``TypeError`` if the value is not an - instance of RestSend. - """ - return self._properties["rest_send"] - - @rest_send.setter - def rest_send(self, value): - method_name = inspect.stack()[0][3] - msg = f"{self.class_name}.{method_name}: " - msg += "value must be an instance of RestSend. " - msg += f"Got value {value} of type {type(value).__name__}." - _class_name = None - try: - _class_name = value.class_name - except AttributeError as error: - msg += f" Error detail: {error}." - self.log.debug(msg) - raise TypeError(msg) from error - if _class_name != "RestSend": - self.log.debug(msg) - raise TypeError(msg) - self._properties["rest_send"] = value - - @property - def results(self): - """ - - getter: Return an instance of the Results class. - - setter: Set an instance of the Results class. - - setter: Raise ``TypeError`` if the value is not an - instance of Results. - """ - return self._properties["results"] - - @results.setter - def results(self, value): - method_name = inspect.stack()[0][3] - msg = f"{self.class_name}.{method_name}: " - msg += "value must be an instance of Results. " - msg += f"Got value {value} of type {type(value).__name__}." - _class_name = None - try: - _class_name = value.class_name - except AttributeError as error: - msg += f" Error detail: {error}." - self.log.debug(msg) - raise TypeError(msg) from error - if _class_name != "Results": - self.log.debug(msg) - raise TypeError(msg) - self._properties["results"] = value - - @property - def templates(self) -> Dict[str, Any]: + def templates(self) -> dict: """ Return the templates retrieved from the controller. """ - return self._properties["templates"] + return self._templates @templates.setter - def templates(self, value: Dict[str, Any]) -> None: + def templates(self, value) -> None: method_name = inspect.stack()[0][3] if not isinstance(value, dict): msg = f"{self.class_name}.{method_name}: " msg += "templates must be an instance of dict." self.log.debug(msg) raise TypeError(msg) - self._properties["templates"] = value + self._templates = value diff --git a/plugins/module_utils/fabric/update.py b/plugins/module_utils/fabric/update.py index 6689d92be..0ce861802 100644 --- a/plugins/module_utils/fabric/update.py +++ b/plugins/module_utils/fabric/update.py @@ -40,20 +40,17 @@ class FabricUpdateCommon(FabricCommon): - FabricUpdateBulk """ - def __init__(self, params): - super().__init__(params) + def __init__(self): + super().__init__() self.class_name = self.__class__.__name__ - self.action = "update" + self.action = "fabric_update" self.log = logging.getLogger(f"dcnm.{self.class_name}") self.ep_fabric_update = EpFabricUpdate() self.fabric_types = FabricTypes() - msg = "ENTERED FabricUpdateCommon(): " - msg += f"action: {self.action}, " - msg += f"check_mode: {self.check_mode}, " - msg += f"state: {self.state}" + msg = "ENTERED FabricUpdateCommon()" self.log.debug(msg) def _fabric_needs_update_for_merged_state(self, payload): @@ -108,6 +105,7 @@ def _fabric_needs_update_for_merged_state(self, payload): # configuration on the controller: # - Update Results() # - raise ValueError + # pylint: disable=no-member if nv_pairs.get(key) is None: self.results.diff_current = {} self.results.result_current = {"success": False, "changed": False} @@ -123,7 +121,7 @@ def _fabric_needs_update_for_merged_state(self, payload): msg += f"fabric {fabric_name}" self.log.debug(msg) raise ValueError(msg) - + # pylint: enable=no-member msg = f"{self.class_name}.{method_name}: " msg += f"key: {key}, payload_value: {payload_value}, " msg += f"fabric_value: {nv_pairs.get(key)}" @@ -212,8 +210,6 @@ def _send_payloads(self): - ``FabricUpdateCommon()._config_save()`` - ``FabricUpdateCommon()._config_deploy()`` """ - self.rest_send.check_mode = self.check_mode - try: self._fixup_payloads_to_commit() except ValueError as error: @@ -229,6 +225,7 @@ def _send_payloads(self): raise ValueError(error) from error # Skip config-save if prior actions encountered errors. + # pylint: disable=no-member if True in self.results.failed: return @@ -241,6 +238,7 @@ def _send_payloads(self): # Skip config-deploy if prior actions encountered errors. if True in self.results.failed: return + # pylint: enable=no-member for payload in self._payloads_to_commit: try: @@ -294,6 +292,7 @@ def _send_payload(self, payload): # We don't want RestSend to retry on errors since the likelihood of a # timeout error when updating a fabric is low, and there are many cases # of permanent errors for which we don't want to retry. + # pylint: disable=no-member self.rest_send.timeout = 1 self.rest_send.path = self.path self.rest_send.verb = self.verb @@ -309,8 +308,8 @@ def _send_payload(self, payload): 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.check_mode = self.rest_send.check_mode + self.results.state = self.rest_send.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() @@ -326,7 +325,7 @@ def payloads(self): - setter: raise ``ValueError`` if ``payloads`` is not a ``list`` of ``dict`` - setter: raise ``ValueError`` if any payload is missing mandatory keys """ - return self._properties["payloads"] + return self._payloads @payloads.setter def payloads(self, value): @@ -342,7 +341,7 @@ def payloads(self, value): self._verify_payload(item) except ValueError as error: raise ValueError(error) from error - self._properties["payloads"] = value + self._payloads = value class FabricUpdateBulk(FabricUpdateCommon): @@ -382,21 +381,15 @@ class FabricUpdateBulk(FabricUpdateCommon): ansible_module.exit_json(**task.results.final_result) """ - def __init__(self, params): - super().__init__(params) + def __init__(self): + super().__init__() self.class_name = self.__class__.__name__ self.log = logging.getLogger(f"dcnm.{self.class_name}") - self.log.debug("ENTERED FabricUpdateBulk()") - - self._build_properties() + self._payloads = None - def _build_properties(self): - """ - Add properties specific to this class - """ - # properties dict is already initialized in FabricCommon - self._properties["payloads"] = None + msg = "ENTERED FabricUpdateBulk()" + self.log.debug(msg) def commit(self): """ @@ -425,14 +418,15 @@ def commit(self): msg += "payloads must be set prior to calling commit." raise ValueError(msg) + # pylint: disable=no-member if self.rest_send is None: msg = f"{self.class_name}.{method_name}: " msg += "rest_send must be set prior to calling commit." raise ValueError(msg) self.results.action = self.action - self.results.check_mode = self.check_mode - self.results.state = self.state + self.results.check_mode = self.rest_send.check_mode + self.results.state = self.rest_send.state try: self._build_payloads_for_merged_state() diff --git a/plugins/module_utils/image_policy/params_spec.py b/plugins/module_utils/image_policy/params_spec.py index f6db51a05..4e58c810e 100644 --- a/plugins/module_utils/image_policy/params_spec.py +++ b/plugins/module_utils/image_policy/params_spec.py @@ -19,7 +19,6 @@ import inspect import logging -from typing import Any, Dict class ParamsSpec: @@ -239,7 +238,7 @@ def params(self, value: dict) -> None: self._params = value @property - def params_spec(self) -> Dict[str, Any]: + def params_spec(self) -> dict: """ ### Summary Return the parameter specification diff --git a/plugins/module_utils/image_upgrade/api_endpoints.py b/plugins/module_utils/image_upgrade/api_endpoints.py deleted file mode 100644 index 33462b7c7..000000000 --- a/plugins/module_utils/image_upgrade/api_endpoints.py +++ /dev/null @@ -1,222 +0,0 @@ -# -# 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 management 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_feature_manager = f"{self.endpoint_api_v1}/fm" - self.endpoint_lan_fabric = f"{self.endpoint_api_v1}/lan-fabric" - - self.endpoint_image_management = f"{self.endpoint_api_v1}" - self.endpoint_image_management += "/imagemanagement" - - self.endpoint_image_upgrade = f"{self.endpoint_image_management}" - self.endpoint_image_upgrade += "/rest/imageupgrade" - - self.endpoint_package_mgnt = f"{self.endpoint_image_management}" - self.endpoint_package_mgnt += "/rest/packagemgnt" - - self.endpoint_policy_mgnt = f"{self.endpoint_image_management}" - self.endpoint_policy_mgnt += "/rest/policymgnt" - - self.endpoint_staging_management = f"{self.endpoint_image_management}" - self.endpoint_staging_management += "/rest/stagingmanagement" - - @property - def bootflash_info(self): - """ - return endpoint GET /rest/imagemgnt/bootFlash - """ - path = f"{self.endpoint_image_management}/rest/imagemgnt/bootFlash" - path += "/bootflash-info" - endpoint = {} - endpoint["path"] = path - endpoint["verb"] = "GET" - return endpoint - - @property - def install_options(self): - """ - return endpoint POST /rest/imageupgrade/install-options - """ - path = f"{self.endpoint_image_upgrade}/install-options" - endpoint = {} - endpoint["path"] = path - endpoint["verb"] = "POST" - return endpoint - - @property - def image_stage(self): - """ - return endpoint POST /rest/stagingmanagement/stage-image - """ - path = f"{self.endpoint_staging_management}/stage-image" - endpoint = {} - endpoint["path"] = path - endpoint["verb"] = "POST" - return endpoint - - @property - def image_upgrade(self): - """ - return endpoint POST /rest/imageupgrade/upgrade-image - """ - path = f"{self.endpoint_image_upgrade}/upgrade-image" - endpoint = {} - endpoint["path"] = path - endpoint["verb"] = "POST" - return endpoint - - @property - def image_validate(self): - """ - return endpoint POST /rest/stagingmanagement/validate-image - """ - path = f"{self.endpoint_staging_management}/validate-image" - endpoint = {} - endpoint["path"] = path - endpoint["verb"] = "POST" - return endpoint - - @property - def issu_info(self): - """ - return endpoint GET /rest/packagemgnt/issu - """ - path = f"{self.endpoint_package_mgnt}/issu" - endpoint = {} - endpoint["path"] = path - endpoint["verb"] = "GET" - return endpoint - - @property - def controller_version(self): - """ - return endpoint GET /appcenter/cisco/ndfc/api/v1/fm/about/version - """ - path = f"{self.endpoint_feature_manager}/about/version" - endpoint = {} - endpoint["path"] = path - endpoint["verb"] = "GET" - return endpoint - - @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_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_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 - - @property - def stage_info(self): - """ - return endpoint GET /rest/stagingmanagement/stage-info - """ - path = f"{self.endpoint_staging_management}/stage-info" - endpoint = {} - endpoint["path"] = path - endpoint["verb"] = "GET" - return endpoint - - @property - def switches_info(self): - """ - return endpoint GET /rest/inventory/allswitches - """ - path = f"{self.endpoint_lan_fabric}/rest/inventory/allswitches" - endpoint = {} - endpoint["path"] = path - endpoint["verb"] = "GET" - return endpoint diff --git a/plugins/module_utils/image_upgrade/image_policies.py b/plugins/module_utils/image_upgrade/image_policies.py deleted file mode 100644 index d2b744caa..000000000 --- a/plugins/module_utils/image_upgrade/image_policies.py +++ /dev/null @@ -1,291 +0,0 @@ -# -# 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.image_upgrade.api_endpoints import \ - ApiEndpoints -from ansible_collections.cisco.dcnm.plugins.module_utils.image_upgrade.image_upgrade_common import \ - ImageUpgradeCommon -from ansible_collections.cisco.dcnm.plugins.module_utils.network.dcnm.dcnm import \ - dcnm_send - - -class ImagePolicies(ImageUpgradeCommon): - """ - 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) - instance.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... - - 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._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["policy_name"] = None - self.properties["response_data"] = {} - self.properties["response"] = None - self.properties["result"] = None - - def refresh(self): - """ - Refresh self.image_policies with current image policies from the controller - """ - self.method_name = inspect.stack()[0][3] - - path = self.endpoints.policies_info.get("path") - verb = self.endpoints.policies_info.get("verb") - - self.properties["response"] = dcnm_send(self.ansible_module, verb, path) - self.properties["result"] = self._handle_response(self.response, verb) - - if not self.result["success"]: - msg = f"{self.class_name}.{self.method_name}: " - msg += "Bad result when retrieving image policy " - msg += "information from the controller." - self.ansible_module.fail_json(msg, **self.failed_result) - - data = self.response.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.failed_result) - - # We cannot fail_json here since dcnm_image_policy merged - # state will fail if there are no policies defined. - # if len(data) == 0: - # msg = f"{self.class_name}.{self.method_name}: " - # msg += "the controller has no defined image policies." - # self.ansible_module.fail_json(msg, **self.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"] = {} - 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.failed_result) - - self.properties["response_data"][policy_name] = policy - - self.properties["all_policies"] = copy.deepcopy( - self.properties["response_data"] - ) - - 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 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_upgrade/image_policy_action.py b/plugins/module_utils/image_upgrade/image_policy_action.py deleted file mode 100644 index d992a97a8..000000000 --- a/plugins/module_utils/image_upgrade/image_policy_action.py +++ /dev/null @@ -1,473 +0,0 @@ -# -# 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 typing import Any, Dict - -from ansible_collections.cisco.dcnm.plugins.module_utils.image_upgrade.api_endpoints import \ - ApiEndpoints -from ansible_collections.cisco.dcnm.plugins.module_utils.image_upgrade.image_policies import \ - ImagePolicies -from ansible_collections.cisco.dcnm.plugins.module_utils.image_upgrade.image_upgrade_common import \ - ImageUpgradeCommon -from ansible_collections.cisco.dcnm.plugins.module_utils.image_upgrade.switch_issu_details import \ - SwitchIssuDetailsBySerialNumber - - -class ImagePolicyAction(ImageUpgradeCommon): - """ - Perform image policy actions on the controller for one or more switches. - - Support for the following actions: - - attach - - detach - - query - - Usage (where module is an instance of AnsibleModule): - - instance = ImagePolicyAction(module) - instance.policy_name = "NR3F" - instance.action = "attach" # or detach, or query - instance.serial_numbers = ["FDO211218GC", "FDO211218HH"] - instance.commit() - # for query only - query_result = instance.query_result - - Endpoints: - For action == attach: - /appcenter/cisco/ndfc/api/v1/imagemanagement/rest/policymgnt/attach-policy - For action == detach: - /appcenter/cisco/ndfc/api/v1/imagemanagement/rest/policymgnt/detach-policy - For action == query: - /appcenter/cisco/ndfc/api/v1/imagemanagement/rest/policymgnt/image-policy/__POLICY_NAME__ - """ - - 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 ImagePolicyAction()") - - self.endpoints = ApiEndpoints() - self._init_properties() - self.image_policies = ImagePolicies(self.ansible_module) - self.path = None - self.payloads = [] - self.switch_issu_details = SwitchIssuDetailsBySerialNumber(self.ansible_module) - self.valid_actions = {"attach", "detach", "query"} - self.verb = None - - def _init_properties(self): - # self.properties is already initialized in the parent class - self.properties["action"] = None - self.properties["policy_name"] = None - self.properties["query_result"] = None - self.properties["serial_numbers"] = None - - def build_payload(self): - """ - build the payload to send in the POST request - to attach policies to devices - - caller _attach_policy() - """ - method_name = inspect.stack()[0][3] - - msg = "ENTERED" - self.log.debug(msg) - - self.payloads = [] - - self.switch_issu_details.refresh() - for serial_number in self.serial_numbers: - self.switch_issu_details.filter = serial_number - payload: Dict[str, Any] = {} - payload["policyName"] = self.policy_name - payload["hostName"] = self.switch_issu_details.device_name - payload["ipAddr"] = self.switch_issu_details.ip_address - payload["platform"] = self.switch_issu_details.platform - payload["serialNumber"] = self.switch_issu_details.serial_number - msg = f"payload: {json.dumps(payload, indent=4)}" - self.log.debug(msg) - for key, value in payload.items(): - if value is None: - msg = f"{self.class_name}.{method_name}: " - msg += f" Unable to determine {key} for switch " - msg += f"{self.switch_issu_details.ip_address}, " - msg += f"{self.switch_issu_details.serial_number}, " - msg += f"{self.switch_issu_details.device_name}. " - msg += "Please verify that the switch is managed by " - msg += "the controller." - self.ansible_module.fail_json(msg, **self.failed_result) - self.payloads.append(payload) - - def validate_request(self): - """ - validations prior to commit() should be added here. - """ - method_name = inspect.stack()[0][3] - - msg = "ENTERED" - self.log.debug(msg) - - if self.action is None: - msg = f"{self.class_name}.{method_name}: " - msg += "instance.action must be set before " - msg += "calling commit()" - self.ansible_module.fail_json(msg, **self.failed_result) - - if self.policy_name is None: - msg = f"{self.class_name}.{method_name}: " - msg += "instance.policy_name must be set before " - msg += "calling commit()" - self.ansible_module.fail_json(msg, **self.failed_result) - - if self.action == "query": - return - - if self.serial_numbers is None: - msg = f"{self.class_name}.{method_name}: " - msg += "instance.serial_numbers must be set before " - msg += "calling commit()" - self.ansible_module.fail_json(msg, **self.failed_result) - - self.image_policies.refresh() - self.switch_issu_details.refresh() - - self.image_policies.policy_name = self.policy_name - # Fail if the image policy does not exist. - # Image policy creation is handled by a different module. - if self.image_policies.name is None: - msg = f"{self.class_name}.{method_name}: " - msg += f"policy {self.policy_name} does not exist on " - msg += "the controller." - self.ansible_module.fail_json(msg) - - for serial_number in self.serial_numbers: - self.switch_issu_details.filter = serial_number - # Fail if the image policy does not support the switch platform - if self.switch_issu_details.platform not in self.image_policies.platform: - msg = f"{self.class_name}.{method_name}: " - msg += f"policy {self.policy_name} does not support platform " - msg += f"{self.switch_issu_details.platform}. {self.policy_name} " - msg += "supports the following platform(s): " - msg += f"{self.image_policies.platform}" - self.ansible_module.fail_json(msg, **self.failed_result) - - def commit(self): - """ - Call one of the following methods to commit the action to the controller: - - _attach_policy - - _detach_policy - - _query_policy - """ - method_name = inspect.stack()[0][3] - - msg = "ENTERED" - self.log.debug(msg) - - self.validate_request() - if self.action == "attach": - self._attach_policy() - elif self.action == "detach": - self._detach_policy() - elif self.action == "query": - self._query_policy() - else: - msg = f"{self.class_name}.{method_name}: " - msg += f"Unknown action {self.action}." - self.ansible_module.fail_json(msg, **self.failed_result) - - def _attach_policy(self): - if self.check_mode is True: - self._attach_policy_check_mode() - else: - self._attach_policy_normal_mode() - - def _attach_policy_check_mode(self): - """ - Simulate _attach_policy() - """ - self.build_payload() - - self.path = self.endpoints.policy_attach.get("path") - self.verb = self.endpoints.policy_attach.get("verb") - - payload: Dict[str, Any] = {} - payload["mappingList"] = self.payloads - - self.response_current = {} - self.response_current["RETURN_CODE"] = 200 - self.response_current["METHOD"] = self.verb - self.response_current["REQUEST_PATH"] = self.path - self.response_current["MESSAGE"] = "OK" - self.response_current["DATA"] = "[simulated-check-mode-response:Success] " - self.result_current = self._handle_response(self.response_current, self.verb) - - for payload in self.payloads: - diff: Dict[str, Any] = {} - diff["action"] = self.action - diff["ip_address"] = payload["ipAddr"] - diff["logical_name"] = payload["hostName"] - diff["policy_name"] = payload["policyName"] - diff["serial_number"] = payload["serialNumber"] - self.diff = copy.deepcopy(diff) - - def _attach_policy_normal_mode(self): - """ - Attach policy_name to the switch(es) associated with serial_numbers - - This method creates a list of diffs, one result, and one response. - These are accessable via: - self.diff = List[Dict[str, Any]] - self.result = result from the controller - self.response = response from the controller - """ - method_name = inspect.stack()[0][3] - - msg = "ENTERED" - self.log.debug(msg) - - self.build_payload() - - self.path = self.endpoints.policy_attach.get("path") - self.verb = self.endpoints.policy_attach.get("verb") - - payload: Dict[str, Any] = {} - payload["mappingList"] = self.payloads - self.dcnm_send_with_retry(self.verb, self.path, payload) - - msg = f"result_current: {json.dumps(self.result_current, indent=4)}" - self.log.debug(msg) - msg = f"response_current: {json.dumps(self.response_current, indent=4)}" - self.log.debug(msg) - - if not self.result_current["success"]: - msg = f"{self.class_name}.{method_name}: " - msg += f"Bad result when attaching policy {self.policy_name} " - msg += f"to switch. Payload: {payload}." - self.ansible_module.fail_json(msg, **self.failed_result) - - for payload in self.payloads: - diff: Dict[str, Any] = {} - diff["action"] = self.action - diff["ip_address"] = payload["ipAddr"] - diff["logical_name"] = payload["hostName"] - diff["policy_name"] = payload["policyName"] - diff["serial_number"] = payload["serialNumber"] - self.diff = copy.deepcopy(diff) - - def _detach_policy(self): - if self.check_mode is True: - self._detach_policy_check_mode() - else: - self._detach_policy_normal_mode() - - def _detach_policy_check_mode(self): - """ - Simulate self._detach_policy_normal_mode() - verb: DELETE - endpoint: /appcenter/cisco/ndfc/api/v1/imagemanagement/rest/policymgnt/detach-policy - query_params(example): ?serialNumber=FDO211218GC,FDO21120U5D - """ - method_name = inspect.stack()[0][3] - - msg = "ENTERED" - self.log.debug(msg) - - self.path = self.endpoints.policy_detach.get("path") - self.verb = self.endpoints.policy_detach.get("verb") - - query_params = ",".join(self.serial_numbers) - self.path += f"?serialNumber={query_params}" - - self.response_current = {} - self.response_current["RETURN_CODE"] = 200 - self.response_current["METHOD"] = self.verb - self.response_current["REQUEST_PATH"] = self.path - self.response_current["MESSAGE"] = "OK" - self.response_current["DATA"] = "[simulated-response:Success] " - self.result_current = self._handle_response(self.response_current, self.verb) - - if not self.result_current["success"]: - msg = f"{self.class_name}.{method_name}: " - msg += f"Bad result when detaching policy {self.policy_name} " - msg += f"from the following device(s): {','.join(sorted(self.serial_numbers))}." - self.ansible_module.fail_json(msg, **self.failed_result) - - for serial_number in self.serial_numbers: - self.switch_issu_details.filter = serial_number - diff: Dict[str, Any] = {} - diff["action"] = self.action - diff["ip_address"] = self.switch_issu_details.ip_address - diff["logical_name"] = self.switch_issu_details.device_name - diff["policy_name"] = self.policy_name - diff["serial_number"] = serial_number - self.diff = copy.deepcopy(diff) - self.changed = False - - def _detach_policy_normal_mode(self): - """ - Detach policy_name from the switch(es) associated with serial_numbers - verb: DELETE - endpoint: /appcenter/cisco/ndfc/api/v1/imagemanagement/rest/policymgnt/detach-policy - query_params(example): ?serialNumber=FDO211218GC,FDO21120U5D - """ - method_name = inspect.stack()[0][3] - - msg = "ENTERED" - self.log.debug(msg) - - self.path = self.endpoints.policy_detach.get("path") - self.verb = self.endpoints.policy_detach.get("verb") - - query_params = ",".join(self.serial_numbers) - self.path += f"?serialNumber={query_params}" - - self.dcnm_send_with_retry(self.verb, self.path) - - if not self.result_current["success"]: - msg = f"{self.class_name}.{method_name}: " - msg += f"Bad result when detaching policy {self.policy_name} " - msg += f"from the following device(s): {','.join(sorted(self.serial_numbers))}." - self.ansible_module.fail_json(msg, **self.failed_result) - - for serial_number in self.serial_numbers: - self.switch_issu_details.filter = serial_number - diff: Dict[str, Any] = {} - diff["action"] = self.action - diff["ip_address"] = self.switch_issu_details.ip_address - diff["logical_name"] = self.switch_issu_details.device_name - diff["policy_name"] = self.policy_name - diff["serial_number"] = serial_number - self.diff = copy.deepcopy(diff) - self.changed = True - - def _query_policy(self): - """ - Query the image policy - verb: GET - endpoint: /appcenter/cisco/ndfc/api/v1/imagemanagement/rest/policymgnt/image-policy - """ - method_name = inspect.stack()[0][3] - - self.path = self.endpoints.policy_info.get("path") - self.verb = self.endpoints.policy_info.get("verb") - - self.path = self.path.replace("__POLICY_NAME__", self.policy_name) - - self.dcnm_send_with_retry(self.verb, self.path) - - if not self.result_current["success"]: - msg = f"{self.class_name}.{method_name}: " - msg += f"Bad result when querying image policy {self.policy_name}." - self.ansible_module.fail_json(msg, **self.failed_result) - - self.query_result = self.response_current.get("DATA") - self.diff = self.response_current - - @property - def diff_null(self): - """ - Convenience property to return a null diff when no action is taken. - """ - diff: Dict[str, Any] = {} - diff["action"] = None - diff["ip_address"] = None - diff["logical_name"] = None - diff["policy"] = None - diff["serial_number"] = None - return diff - - @property - def query_result(self): - """ - Return the value of properties["query_result"]. - """ - return self.properties.get("query_result") - - @query_result.setter - def query_result(self, value): - self.properties["query_result"] = value - - @property - def action(self): - """ - Set the action to take. - - One of "attach", "detach", "query" - - Must be set prior to calling instance.commit() - """ - return self.properties.get("action") - - @action.setter - def action(self, value): - method_name = inspect.stack()[0][3] - if value not in self.valid_actions: - msg = f"{self.class_name}.{method_name}: " - msg += "instance.action must be one of " - msg += f"{','.join(sorted(self.valid_actions))}. " - msg += f"Got {value}." - self.ansible_module.fail_json(msg, **self.failed_result) - self.properties["action"] = value - - @property - def policy_name(self): - """ - Set the name of the policy to attach, detach, query. - - Must be set prior to calling instance.commit() - """ - return self.properties.get("policy_name") - - @policy_name.setter - def policy_name(self, value): - self.properties["policy_name"] = value - - @property - def serial_numbers(self): - """ - Set the serial numbers of the switches to/from which - policy_name will be attached or detached. - - Must be set prior to calling instance.commit() - """ - return self.properties.get("serial_numbers") - - @serial_numbers.setter - def serial_numbers(self, value): - method_name = inspect.stack()[0][3] - if not isinstance(value, list): - msg = f"{self.class_name}.{method_name}: " - msg += "instance.serial_numbers must be a " - msg += "python list of switch serial numbers. " - msg += f"Got {value}." - self.ansible_module.fail_json(msg, **self.failed_result) - if len(value) == 0: - msg = f"{self.class_name}.{method_name}: " - msg += "instance.serial_numbers must contain at least one " - msg += "switch serial number." - self.ansible_module.fail_json(msg, **self.failed_result) - self.properties["serial_numbers"] = value diff --git a/plugins/module_utils/image_upgrade/image_policy_attach.py b/plugins/module_utils/image_upgrade/image_policy_attach.py new file mode 100644 index 000000000..2b35895d8 --- /dev/null +++ b/plugins/module_utils/image_upgrade/image_policy_attach.py @@ -0,0 +1,455 @@ +# +# 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.api.v1.imagemanagement.rest.policymgnt.policymgnt import \ + EpPolicyAttach +from ansible_collections.cisco.dcnm.plugins.module_utils.common.image_policies import \ + ImagePolicies +from ansible_collections.cisco.dcnm.plugins.module_utils.common.properties import \ + Properties +from ansible_collections.cisco.dcnm.plugins.module_utils.common.results import \ + Results +from ansible_collections.cisco.dcnm.plugins.module_utils.image_upgrade.switch_issu_details import \ + SwitchIssuDetailsBySerialNumber +from ansible_collections.cisco.dcnm.plugins.module_utils.image_upgrade.wait_for_controller_done import \ + WaitForControllerDone + + +@Properties.add_rest_send +@Properties.add_results +class ImagePolicyAttach: + """ + ### Summary + Attach image policies to one or more switches. + + ### Raises + - ValueError: if: + - ``policy_name`` is not set before calling commit. + - ``serial_numbers`` is not set before calling commit. + - ``serial_numbers`` is an empty list. + - ``policy_name`` does not exist on the controller. + - ``policy_name`` does not support the switch platform. + - TypeError: if: + - ``serial_numbers`` is not a list. + + ### Usage + + ```python + # params is typically obtained from ansible_module.params + # but can also be specified manually, like below. + params = { + "check_mode": False, + "state": "merged" + } + + sender = Sender() + sender.ansible_module = ansible_module + + rest_send = RestSend(params) + rest_send.sender = sender + rest_send.response_handler = ResponseHandler() + + instance = ImagePolicyAttach() + instance.rest_send = rest_send + instance.results = results + instance.policy_name = "NR3F" + instance.serial_numbers = ["FDO211218GC", "FDO211218HH"] + instance.commit() + ``` + + ### Endpoint + /appcenter/cisco/ndfc/api/v1/imagemanagement/rest/policymgnt/attach-policy + """ + + def __init__(self): + self.class_name = self.__class__.__name__ + method_name = inspect.stack()[0][3] + + self.action = "image_policy_attach" + self.diff: dict = {} + self.payloads = [] + self.saved_response_current: dict = {} + self.saved_result_current: dict = {} + + self.ep_policy_attach = EpPolicyAttach() + self.image_policies = ImagePolicies() + self.switch_issu_details = SwitchIssuDetailsBySerialNumber() + self.wait_for_controller_done = WaitForControllerDone() + + self._check_interval = 10 # seconds + self._check_timeout = 1800 # seconds + self._rest_send = None + self._results = None + + self.log = logging.getLogger(f"dcnm.{self.class_name}") + msg = f"ENTERED {self.class_name}().{method_name}" + self.log.debug(msg) + + def build_payload(self): + """ + build the payload to send in the POST request + to attach policies to devices + + caller _attach_policy() + """ + method_name = inspect.stack()[0][3] + + msg = f"ENTERED {self.class_name}.{method_name}" + self.log.debug(msg) + + self.payloads = [] + + self.switch_issu_details.refresh() + for serial_number in self.serial_numbers: + self.switch_issu_details.filter = serial_number + payload: dict = {} + payload["policyName"] = self.policy_name + payload["hostName"] = self.switch_issu_details.device_name + payload["ipAddr"] = self.switch_issu_details.ip_address + payload["platform"] = self.switch_issu_details.platform + payload["serialNumber"] = self.switch_issu_details.serial_number + msg = f"payload: {json.dumps(payload, indent=4)}" + self.log.debug(msg) + for key, value in payload.items(): + if value is None: + msg = f"{self.class_name}.{method_name}: " + msg += f" Unable to determine {key} for switch " + msg += f"{self.switch_issu_details.ip_address}, " + msg += f"{self.switch_issu_details.serial_number}, " + msg += f"{self.switch_issu_details.device_name}. " + msg += "Please verify that the switch is managed by " + msg += "the controller." + raise ValueError(msg) + self.payloads.append(payload) + + def validate_commit_parameters(self): + """ + ### Summary + Validations prior to commit() should be added here. + + ### Raises + - ValueError: if: + - ``policy_name`` is not set. + - ``serial_numbers`` is not set. + - ``policy_name`` does not exist on the controller. + - ``policy_name`` does not support the switch platform. + """ + method_name = inspect.stack()[0][3] + + msg = "ENTERED" + self.log.debug(msg) + + if self.policy_name is None: + msg = f"{self.class_name}.{method_name}: " + msg += "instance.policy_name must be set before " + msg += "calling commit()" + raise ValueError(msg) + + if self.rest_send is None: + msg = f"{self.class_name}.{method_name}: " + msg += "rest_send must be set before calling commit()." + raise ValueError(msg) + + if self.results is None: + msg = f"{self.class_name}.{method_name}: " + msg += "results must be set before calling commit()." + raise ValueError(msg) + + if self.serial_numbers is None: + msg = f"{self.class_name}.{method_name}: " + msg += "instance.serial_numbers must be set before " + msg += "calling commit()" + raise ValueError(msg) + + def validate_image_policies(self): + """ + ### Summary + Validate that the image policy exists on the controller + and supports the switch platform. + + ### Raises + - ValueError: if: + - ``policy_name`` does not exist on the controller. + - ``policy_name`` does not support the switch platform. + """ + method_name = inspect.stack()[0][3] + + msg = f"ENTERED {self.class_name}.{method_name}" + self.log.debug(msg) + + self.image_policies.refresh() + self.switch_issu_details.refresh() + + self.image_policies.policy_name = self.policy_name + # Fail if the image policy does not exist. + # Image policy creation is handled by a different module. + if self.image_policies.name is None: + msg = f"{self.class_name}.{method_name}: " + msg += f"policy {self.policy_name} does not exist on " + msg += "the controller." + raise ValueError(msg) + + for serial_number in self.serial_numbers: + self.switch_issu_details.filter = serial_number + # Fail if the image policy does not support the switch platform + if self.switch_issu_details.platform not in self.image_policies.platform: + msg = f"{self.class_name}.{method_name}: " + msg += f"policy {self.policy_name} does not support platform " + msg += f"{self.switch_issu_details.platform}. {self.policy_name} " + msg += "supports the following platform(s): " + msg += f"{self.image_policies.platform}" + raise ValueError(msg) + + def commit(self): + """ + ### Summary + Attach image policy to switches. + + ### Raises + - ValueError: if: + - ``policy_name`` is not set. + - ``serial_numbers`` is not set. + - ``policy_name`` does not exist on the controller. + - ``policy_name`` does not support the switch platform. + """ + method_name = inspect.stack()[0][3] + + msg = f"ENTERED {self.class_name}.{method_name}" + self.log.debug(msg) + + try: + self.validate_commit_parameters() + except ValueError as error: + msg = f"{self.class_name}.{method_name}: " + msg += "Error while validating commit parameters. " + msg += f"Error detail: {error}" + raise ValueError(msg) from error + + self.switch_issu_details.rest_send = self.rest_send + # Don't include results in user output. + self.switch_issu_details.results = Results() + + self.image_policies.results = Results() + self.image_policies.rest_send = self.rest_send # pylint: disable=no-member + + try: + self.validate_image_policies() + except ValueError as error: + msg = f"{self.class_name}.{method_name}: " + msg += "Error while validating image policies. " + msg += f"Error detail: {error}" + raise ValueError(msg) from error + + self.wait_for_controller() + self.attach_policy() + + def wait_for_controller(self): + """ + ### Summary + Wait for any actions on the controller to complete. + + ### Raises + - ValueError: if: + - ``items`` is not a set. + - ``item_type`` is not a valid item type. + - The action times out. + """ + method_name = inspect.stack()[0][3] + try: + self.wait_for_controller_done.items = set(copy.copy(self.serial_numbers)) + self.wait_for_controller_done.item_type = "serial_number" + self.wait_for_controller_done.rest_send = ( + self.rest_send # pylint: disable=no-member + ) + self.wait_for_controller_done.commit() + except (TypeError, ValueError) as error: + msg = f"{self.class_name}.{method_name}: " + msg += f"Error {error}." + raise ValueError(msg) from error + + def build_diff(self): + """ + ### Summary + Build the diff for the task result. + """ + method_name = inspect.stack()[0][3] + + msg = f"ENTERED {self.class_name}.{method_name}" + self.log.debug(msg) + + self.diff: dict = {} + for payload in self.payloads: + ipv4 = payload["ipAddr"] + if ipv4 not in self.diff: + self.diff[ipv4] = {} + self.diff[ipv4]["action"] = self.action + self.diff[ipv4]["ip_address"] = payload["ipAddr"] + self.diff[ipv4]["logical_name"] = payload["hostName"] + self.diff[ipv4]["policy_name"] = payload["policyName"] + self.diff[ipv4]["serial_number"] = payload["serialNumber"] + + def attach_policy(self): + """ + ### Summary + Attach policy_name to the switch(es) associated with serial_numbers. + + ### Raises + - ValueError: if the result of the POST request is not successful. + """ + method_name = inspect.stack()[0][3] + + msg = f"ENTERED {self.class_name}.{method_name}" + self.log.debug(msg) + + self.build_payload() + + msg = f"{self.class_name}.{method_name}: " + msg += f"rest_send.check_mode: {self.rest_send.check_mode}" + self.log.debug(msg) + + payload: dict = {} + payload["mappingList"] = self.payloads + self.rest_send.payload = payload + self.rest_send.path = self.ep_policy_attach.path + self.rest_send.verb = self.ep_policy_attach.verb + self.rest_send.commit() + + msg = f"result_current: {json.dumps(self.rest_send.result_current, indent=4)}" + self.log.debug(msg) + msg = ( + f"response_current: {json.dumps(self.rest_send.response_current, indent=4)}" + ) + self.log.debug(msg) + + self.build_diff() + self.results.action = self.action + self.results.diff_current = copy.deepcopy(self.diff) + self.results.result_current = self.rest_send.result_current + self.results.response_current = self.rest_send.response_current + self.results.register_task_result() + + if not self.rest_send.result_current["success"]: + msg = f"{self.class_name}.{method_name}: " + msg += f"Bad result when attaching policy {self.policy_name} " + msg += f"to switch. Payload: {payload}." + raise ValueError(msg) + + @property + def policy_name(self): + """ + Set the name of the policy to attach, detach, query. + + Must be set prior to calling instance.commit() + """ + return self._policy_name + + @policy_name.setter + def policy_name(self, value): + self._policy_name = value + + @property + def serial_numbers(self): + """ + ### Summary + Set the serial numbers of the switches to/ which + policy_name will be attached. + + Must be set prior to calling commit() + + ### Raises + - TypeError: if value is not a list. + - ValueError: if value is an empty list. + """ + return self._serial_numbers + + @serial_numbers.setter + def serial_numbers(self, value): + method_name = inspect.stack()[0][3] + if not isinstance(value, list): + msg = f"{self.class_name}.{method_name}: " + msg += "instance.serial_numbers must be a " + msg += "python list of switch serial numbers. " + msg += f"Got {value}." + raise TypeError(msg) + if len(value) == 0: + msg = f"{self.class_name}.{method_name}: " + msg += "instance.serial_numbers must contain at least one " + msg += "switch serial number." + raise ValueError(msg) + self._serial_numbers = value + + @property + def check_interval(self): + """ + ### Summary + The validate check interval, in seconds. + + ### Raises + - ``TypeError`` if the value is not an integer. + - ``ValueError`` if the value is less than zero. + """ + return self._check_interval + + @check_interval.setter + def check_interval(self, value): + method_name = inspect.stack()[0][3] + msg = f"{self.class_name}.{method_name}: " + msg += "must be a positive integer or zero. " + msg += f"Got value {value} of type {type(value)}." + # isinstance(True, int) is True so we need to check for bool first + if isinstance(value, bool): + raise TypeError(msg) + if not isinstance(value, int): + raise TypeError(msg) + if value < 0: + raise ValueError(msg) + self._check_interval = value + + @property + def check_timeout(self): + """ + ### Summary + The validate check timeout, in seconds. + + ### Raises + - ``TypeError`` if the value is not an integer. + - ``ValueError`` if the value is less than zero. + """ + return self._check_timeout + + @check_timeout.setter + def check_timeout(self, value): + method_name = inspect.stack()[0][3] + msg = f"{self.class_name}.{method_name}: " + msg += "must be a positive integer or zero. " + msg += f"Got value {value} of type {type(value)}." + # isinstance(True, int) is True so we need to check for bool first + if isinstance(value, bool): + raise TypeError(msg) + if not isinstance(value, int): + raise TypeError(msg) + if value < 0: + raise ValueError(msg) + self._check_timeout = value diff --git a/plugins/module_utils/image_upgrade/image_policy_detach.py b/plugins/module_utils/image_upgrade/image_policy_detach.py new file mode 100644 index 000000000..250081707 --- /dev/null +++ b/plugins/module_utils/image_upgrade/image_policy_detach.py @@ -0,0 +1,375 @@ +# +# 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.api.v1.imagemanagement.rest.policymgnt.policymgnt import \ + EpPolicyDetach +from ansible_collections.cisco.dcnm.plugins.module_utils.common.exceptions import \ + ControllerResponseError +from ansible_collections.cisco.dcnm.plugins.module_utils.common.image_policies import \ + ImagePolicies +from ansible_collections.cisco.dcnm.plugins.module_utils.common.properties import \ + Properties +from ansible_collections.cisco.dcnm.plugins.module_utils.common.results import \ + Results +from ansible_collections.cisco.dcnm.plugins.module_utils.image_upgrade.switch_issu_details import \ + SwitchIssuDetailsBySerialNumber +from ansible_collections.cisco.dcnm.plugins.module_utils.image_upgrade.wait_for_controller_done import \ + WaitForControllerDone + + +@Properties.add_rest_send +@Properties.add_results +class ImagePolicyDetach: + """ + ### Summary + Detach image policies from one or more switches. + + ### Raises + - ValueError: if: + - ``serial_numbers`` is not set before calling commit. + - ``serial_numbers`` is an empty list. + - The result of the DELETE request is not successful. + - TypeError: if: + - ``check_interval`` is not an integer. + - ``check_timeout`` is not an integer. + - ``serial_numbers`` is not a list. + + ### Usage + + ```python + # params is typically obtained from ansible_module.params + # but can also be specified manually, like below. + params = { + "check_mode": False, + "state": "merged" + } + + sender = Sender() + sender.ansible_module = ansible_module + + rest_send = RestSend(params) + rest_send.sender = sender + rest_send.response_handler = ResponseHandler() + + instance = ImagePolicyDetach() + instance.rest_send = rest_send + instance.results = results + instance.serial_numbers = ["FDO211218GC", "FDO211218HH"] + instance.commit() + ``` + + ### Endpoint + /appcenter/cisco/ndfc/api/v1/imagemanagement/rest/policymgnt/detach-policy + """ + + def __init__(self): + self.class_name = self.__class__.__name__ + method_name = inspect.stack()[0][3] + + self.log = logging.getLogger(f"dcnm.{self.class_name}") + + self.action = "image_policy_detach" + self.diff: dict = {} + self.saved_response_current: dict = {} + self.saved_result_current: dict = {} + + self.ep_policy_detach = EpPolicyDetach() + self.image_policies = ImagePolicies() + self.switch_issu_details = SwitchIssuDetailsBySerialNumber() + self.wait_for_controller_done = WaitForControllerDone() + + self._check_interval = 10 # seconds + self._check_timeout = 1800 # seconds + self._rest_send = None + self._results = None + + msg = f"ENTERED {self.class_name}().{method_name}" + self.log.debug(msg) + + def build_diff(self): + """ + ### Summary + Build the diff of the detach policy operation. + + ### Raises + - ValueError: if the switch is not managed by the controller. + """ + method_name = inspect.stack()[0][3] + + msg = f"ENTERED {self.class_name}.{method_name}" + self.log.debug(msg) + + self.diff: dict = {} + + self.switch_issu_details.refresh() + for serial_number in self.serial_numbers: + self.switch_issu_details.filter = serial_number + ipv4 = self.switch_issu_details.ip_address + + if ipv4 not in self.diff: + self.diff[ipv4] = {} + + self.diff[ipv4]["action"] = self.action + self.diff[ipv4]["policy_name"] = self.switch_issu_details.policy + self.diff[ipv4]["device_name"] = self.switch_issu_details.device_name + self.diff[ipv4]["ipv4_address"] = self.switch_issu_details.ip_address + self.diff[ipv4]["platform"] = self.switch_issu_details.platform + self.diff[ipv4]["serial_number"] = self.switch_issu_details.serial_number + msg = f"{self.class_name}.{method_name}: " + msg += f"self.diff[{ipv4}]: {json.dumps(self.diff[ipv4], indent=4)}" + self.log.debug(msg) + + def validate_commit_parameters(self): + """ + ### Summary + Validations prior to commit() should be added here. + + ### Raises + - ValueError: if: + - ``serial_numbers`` is not set. + """ + method_name = inspect.stack()[0][3] + + msg = "ENTERED" + self.log.debug(msg) + + if self.rest_send is None: + msg = f"{self.class_name}.{method_name}: " + msg += "rest_send must be set before calling commit()." + raise ValueError(msg) + + if self.results is None: + msg = f"{self.class_name}.{method_name}: " + msg += "results must be set before calling commit()." + raise ValueError(msg) + + if self.serial_numbers is None: + msg = f"{self.class_name}.{method_name}: " + msg += "instance.serial_numbers must be set before " + msg += "calling commit()" + raise ValueError(msg) + + def commit(self): + """ + ### Summary + Attach image policy to switches. + + ### Raises + - ValueError: if: + - ``serial_numbers`` is not set. + - ``results`` is not set. + - ``rest_send`` is not set. + - Error encountered while waiting for controller actions + to complete. + - The result of the DELETE request is not successful. + """ + method_name = inspect.stack()[0][3] + + msg = f"ENTERED {self.class_name}.{method_name}" + self.log.debug(msg) + + try: + self.validate_commit_parameters() + except ValueError as error: + msg = f"{self.class_name}.{method_name}: " + msg += "Error while validating commit parameters. " + msg += f"Error detail: {error}" + raise ValueError(msg) from error + + self.switch_issu_details.rest_send = self.rest_send + # Don't include results in user output. + self.switch_issu_details.results = Results() + + try: + self.wait_for_controller() + except (TypeError, ValueError) as error: + raise ValueError(error) from error + + try: + self.detach_policy() + except (ControllerResponseError, TypeError, ValueError) as error: + msg = f"{self.class_name}.{method_name}: " + msg += "Error while detaching image policies from switches. " + msg += f"Error detail: {error}" + raise ValueError(msg) from error + + def wait_for_controller(self): + """ + ### Summary + Wait for any actions on the controller to complete. + + ### Raises + - ValueError: if: + - ``items`` is not a set. + - ``item_type`` is not a valid item type. + - The action times out. + """ + try: + self.wait_for_controller_done.items = set(copy.copy(self.serial_numbers)) + self.wait_for_controller_done.item_type = "serial_number" + self.wait_for_controller_done.rest_send = ( + self.rest_send # pylint: disable=no-member + ) + self.wait_for_controller_done.commit() + except (TypeError, ValueError) as error: + msg = f"{self.class_name}.wait_for_controller: " + msg += "Error while waiting for controller actions to complete. " + msg += f"Error detail: {error}" + raise ValueError(msg) from error + + def detach_policy(self): + """ + ### Summary + Detach image policy from the switch(es) associated with + ``serial_numbers``. + + ### Raises + - ``ControllerResponseError`` if: + - The result of the DELETE request is not successful. + """ + method_name = inspect.stack()[0][3] + + msg = f"ENTERED {self.class_name}.{method_name}" + self.log.debug(msg) + + self.ep_policy_detach.serial_numbers = self.serial_numbers + + msg = f"{self.class_name}.{method_name}: " + msg += "ep_policy_detach: " + msg += f"verb: {self.ep_policy_detach.verb}, " + msg += f"path: {self.ep_policy_detach.path}" + self.log.debug(msg) + + # Build the diff before sending the request so that + # we can include the policy names in the diff. + self.build_diff() + + self.rest_send.path = self.ep_policy_detach.path + self.rest_send.verb = self.ep_policy_detach.verb + self.rest_send.commit() + + msg = f"result_current: {json.dumps(self.rest_send.result_current, indent=4)}" + self.log.debug(msg) + + msg = "response_current: " + msg += f"{json.dumps(self.rest_send.response_current, indent=4)}" + self.log.debug(msg) + + self.results.action = self.action + self.results.diff_current = self.diff + self.results.response_current = self.rest_send.response_current + self.results.result_current = self.rest_send.result_current + self.results.register_task_result() + + if not self.rest_send.result_current["success"]: + msg = f"{self.class_name}.{method_name}: " + msg += "Bad result when detaching image polices from switches: " + msg += f"{','.join(self.serial_numbers)}." + raise ControllerResponseError(msg) + + @property + def serial_numbers(self): + """ + ### Summary + Set the serial numbers of the switches from which + image policies will be detached. + + Must be set prior to calling ``commit``. + + ### Raises + - ``TypeError`` if value is not a list. + - ``ValueError`` if value is an empty list. + """ + return self._serial_numbers + + @serial_numbers.setter + def serial_numbers(self, value): + method_name = inspect.stack()[0][3] + if not isinstance(value, list): + msg = f"{self.class_name}.{method_name}: " + msg += "instance.serial_numbers must be a " + msg += "python list of switch serial numbers. " + msg += f"Got {value}." + raise TypeError(msg) + if len(value) == 0: + msg = f"{self.class_name}.{method_name}: " + msg += "instance.serial_numbers must contain at least one " + msg += "switch serial number." + raise ValueError(msg) + self._serial_numbers = value + + @property + def check_interval(self): + """ + ### Summary + The validate check interval, in seconds. + + ### Raises + - ``TypeError`` if the value is not an integer. + - ``ValueError`` if the value is less than zero. + """ + return self._check_interval + + @check_interval.setter + def check_interval(self, value): + method_name = inspect.stack()[0][3] + msg = f"{self.class_name}.{method_name}: " + msg += "must be a positive integer or zero. " + msg += f"Got value {value} of type {type(value)}." + # isinstance(True, int) is True so we need to check for bool first + if isinstance(value, bool): + raise TypeError(msg) + if not isinstance(value, int): + raise TypeError(msg) + if value < 0: + raise ValueError(msg) + self._check_interval = value + + @property + def check_timeout(self): + """ + ### Summary + The validate check timeout, in seconds. + + ### Raises + - ``TypeError`` if the value is not an integer. + - ``ValueError`` if the value is less than zero. + """ + return self._check_timeout + + @check_timeout.setter + def check_timeout(self, value): + method_name = inspect.stack()[0][3] + msg = f"{self.class_name}.{method_name}: " + msg += "must be a positive integer or zero. " + msg += f"Got value {value} of type {type(value)}." + # isinstance(True, int) is True so we need to check for bool first + if isinstance(value, bool): + raise TypeError(msg) + if not isinstance(value, int): + raise TypeError(msg) + if value < 0: + raise ValueError(msg) + self._check_timeout = value diff --git a/plugins/module_utils/image_upgrade/image_stage.py b/plugins/module_utils/image_upgrade/image_stage.py index 9a1dada87..4ad7f83d4 100644 --- a/plugins/module_utils/image_upgrade/image_stage.py +++ b/plugins/module_utils/image_upgrade/image_stage.py @@ -24,68 +24,97 @@ import logging from time import sleep +from ansible_collections.cisco.dcnm.plugins.module_utils.common.api.v1.imagemanagement.rest.stagingmanagement.stagingmanagement import \ + EpImageStage from ansible_collections.cisco.dcnm.plugins.module_utils.common.controller_version import \ ControllerVersion -from ansible_collections.cisco.dcnm.plugins.module_utils.common.rest_send import \ - RestSend -from ansible_collections.cisco.dcnm.plugins.module_utils.image_upgrade.api_endpoints import \ - ApiEndpoints -from ansible_collections.cisco.dcnm.plugins.module_utils.image_upgrade.image_upgrade_common import \ - ImageUpgradeCommon +from ansible_collections.cisco.dcnm.plugins.module_utils.common.exceptions import \ + ControllerResponseError +from ansible_collections.cisco.dcnm.plugins.module_utils.common.properties import \ + Properties +from ansible_collections.cisco.dcnm.plugins.module_utils.common.results import \ + Results from ansible_collections.cisco.dcnm.plugins.module_utils.image_upgrade.switch_issu_details import \ SwitchIssuDetailsBySerialNumber +from ansible_collections.cisco.dcnm.plugins.module_utils.image_upgrade.wait_for_controller_done import \ + WaitForControllerDone -class ImageStage(ImageUpgradeCommon): +@Properties.add_rest_send +@Properties.add_results +class ImageStage: """ - Endpoint: - /appcenter/cisco/ndfc/api/v1/imagemanagement/rest/stagingmanagement/stage-image - - Verb: POST - - Usage (where module is an instance of AnsibleModule): - - stage = ImageStage(module) - stage.serial_numbers = ["FDO211218HH", "FDO211218GC"] - stage.commit() - - Request body (12.1.2e) (yes, serialNum is misspelled): + ### Summary + Stage an image on a set of switches. + + ### Usage example + + ```python + # params is typically obtained from ansible_module.params + # but can also be specified manually, like below. + params = {"check_mode": False, "state": "merged"} + sender = Sender() + sender.ansible_module = ansible_module + rest_send = RestSend(params) + rest_send.response_handler = ResponseHandler() + rest_send.sender = sender + results = Results() + + instance = ImageStage() + # mandatory parameters + instance.rest_send = rest_send + instance.results = results + instance.serial_numbers = ["FDO211218HH", "FDO211218GC"] + instance.commit() + ``` + + ### Request body (12.1.2e) (yes, serialNum is misspelled) + + ```json { "sereialNum": [ "FDO211218HH", "FDO211218GC" ] } - Request body (12.1.3b): + ``` + + ### Request body (12.1.3b): + ```json { "serialNumbers": [ "FDO211218HH", "FDO211218GC" ] } + ``` + + ### Response + Unfortunately, the response does not contain consistent data. + Would be better if all responses contained serial numbers as keys so that + we could verify against a set() of serial numbers. - Response: - Unfortunately, the response does not contain consistent data. - Would be better if all responses contained serial numbers as keys so that - we could verify against a set() of serial numbers. + ```json { - 'RETURN_CODE': 200, - 'METHOD': 'POST', - 'REQUEST_PATH': '.../api/v1/imagemanagement/rest/stagingmanagement/stage-image', - 'MESSAGE': 'OK', - 'DATA': [ + "RETURN_CODE": 200, + "METHOD": "POST", + "REQUEST_PATH": ".../api/v1/imagemanagement/rest/stagingmanagement/stage-image", + "MESSAGE": "OK", + "DATA": [ { - 'key': 'success', - 'value': '' + "key": "success", + "value": "" }, { - 'key': 'success', - 'value': '' + "key": "success", + "value": "" } ] } + ``` - Response when there are no files to stage: + ### Response when there are no files to stage + ```json [ { "key": "FDO211218GC", @@ -96,47 +125,102 @@ class ImageStage(ImageUpgradeCommon): "value": "No files to stage" } ] + ``` + + ### Endpoint Path + ``` + /appcenter/cisco/ndfc/api/v1/imagemanagement/rest/stagingmanagement/stage-image + ``` + + ### Endpoint Verb + ``POST`` + """ - def __init__(self, ansible_module): - super().__init__(ansible_module) + def __init__(self): self.class_name = self.__class__.__name__ + method_name = inspect.stack()[0][3] self.log = logging.getLogger(f"dcnm.{self.class_name}") - msg = "ENTERED ImageStage() " - msg += f"check_mode {self.check_mode}" - self.log.debug(msg) - self.endpoints = ApiEndpoints() - self.path = self.endpoints.image_stage.get("path") - self.verb = self.endpoints.image_stage.get("verb") + self.action = "image_stage" + self.controller_version = None + self.diff: dict = {} self.payload = None - - self.rest_send = RestSend(self.ansible_module) - self._init_properties() + self.saved_response_current: dict = {} + self.saved_result_current: dict = {} + # _wait_for_image_stage_to_complete() populates these self.serial_numbers_done = set() - self.controller_version = None - self.issu_detail = SwitchIssuDetailsBySerialNumber(self.ansible_module) + self.serial_numbers_todo = set() + + self.controller_version_instance = ControllerVersion() + self.ep_image_stage = EpImageStage() + self.issu_detail = SwitchIssuDetailsBySerialNumber() + self.wait_for_controller_done = WaitForControllerDone() - def _init_properties(self): - # self.properties is already initialized in the parent class - self.properties["serial_numbers"] = None - self.properties["check_interval"] = 10 # seconds - self.properties["check_timeout"] = 1800 # seconds + self._check_interval = 10 # seconds + self._check_timeout = 1800 # seconds + self._rest_send = None + self._results = None + self._serial_numbers = None - def _populate_controller_version(self): + msg = f"ENTERED {self.class_name}().{method_name}" + self.log.debug(msg) + + def build_diff(self) -> None: + """ + ### Summary + Build the diff of the image stage operation. + + ### Raises + None + """ + method_name = inspect.stack()[0][3] + + msg = f"ENTERED {self.class_name}.{method_name}" + self.log.debug(msg) + + self.diff: dict = {} + for serial_number in self.serial_numbers_done: + self.issu_detail.filter = serial_number + ipv4 = self.issu_detail.ip_address + + if ipv4 not in self.diff: + self.diff[ipv4] = {} + + self.diff[ipv4]["action"] = self.action + self.diff[ipv4]["ip_address"] = self.issu_detail.ip_address + self.diff[ipv4]["logical_name"] = self.issu_detail.device_name + self.diff[ipv4]["policy_name"] = self.issu_detail.policy + self.diff[ipv4]["serial_number"] = serial_number + msg = f"{self.class_name}.{method_name}: " + msg += f"self.diff[{ipv4}]: " + msg += f"{json.dumps(self.diff[ipv4], indent=4)}" + self.log.debug(msg) + + def _populate_controller_version(self) -> None: """ Populate self.controller_version with the running controller version. """ - instance = ControllerVersion(self.ansible_module) - instance.refresh() - self.controller_version = instance.version + method_name = inspect.stack()[0][3] - def prune_serial_numbers(self): + msg = f"ENTERED {self.class_name}().{method_name}" + self.log.debug(msg) + + self.controller_version_instance.refresh() + self.controller_version = self.controller_version_instance.version + + def prune_serial_numbers(self) -> None: """ If the image is already staged on a switch, remove that switch's serial number from the list of serial numbers to stage. """ + method_name = inspect.stack()[0][3] + + msg = f"ENTERED: {self.class_name}.{method_name}: " + msg += f"self.serial_numbers {self.serial_numbers}" + self.log.debug(msg) + serial_numbers = copy.copy(self.serial_numbers) self.issu_detail.refresh() for serial_number in serial_numbers: @@ -144,12 +228,43 @@ def prune_serial_numbers(self): if self.issu_detail.image_staged == "Success": self.serial_numbers.remove(serial_number) - def validate_serial_numbers(self): + def register_unchanged_result(self, response_message) -> None: """ - Fail if the image_staged state for any serial_number - is Failed. + ### Summary + Register a successful unchanged result with the results object. """ + # pylint: disable=no-member method_name = inspect.stack()[0][3] + + msg = f"ENTERED {self.class_name}().{method_name}" + self.log.debug(msg) + + self.results.action = self.action + self.results.check_mode = self.rest_send.check_mode + self.results.diff_current = {} + self.results.response_current = { + "DATA": [{"key": "ALL", "value": response_message}] + } + self.results.result_current = {"success": True, "changed": False} + self.results.response_data = {"response": response_message} + self.results.state = self.rest_send.state + self.results.register_task_result() + + def validate_serial_numbers(self) -> None: + """ + ### Summary + Fail if "imageStaged" is "Failed" for any serial number. + + ### Raises + - ``ControllerResponseError`` if: + - "imageStaged" is "Failed" for any serial_number. + """ + method_name = inspect.stack()[0][3] + + msg = f"ENTERED {self.class_name}.{method_name}: " + msg += f"self.serial_numbers: {self.serial_numbers}" + self.log.debug(msg) + self.issu_detail.refresh() for serial_number in self.serial_numbers: self.issu_detail.filter = serial_number @@ -162,45 +277,41 @@ def validate_serial_numbers(self): msg += f"{self.issu_detail.serial_number}. " msg += "Check the switch connectivity to the controller " msg += "and try again." - self.ansible_module.fail_json(msg, **self.failed_result) + raise ControllerResponseError(msg) - def commit(self) -> None: - if self.check_mode is True: - self.commit_check_mode() - else: - self.commit_normal_mode() - - def commit_check_mode(self) -> None: + def validate_commit_parameters(self) -> None: """ - Simulate a commit of the image staging request to the - controller. + Verify mandatory parameters are set before calling commit. """ method_name = inspect.stack()[0][3] - msg = f"ENTERED {self.class_name}.{method_name}" - self.log.debug(msg) - - msg = f"self.serial_numbers: {self.serial_numbers}" + msg = f"ENTERED {self.class_name}().{method_name}" self.log.debug(msg) + # pylint: disable=no-member + if self.rest_send is None: + msg = f"{self.class_name}.{method_name}: " + msg += "rest_send must be set before calling commit()." + raise ValueError(msg) + if self.results is None: + msg = f"{self.class_name}.{method_name}: " + msg += "results must be set before calling commit()." + raise ValueError(msg) + # pylint: enable=no-member if self.serial_numbers is None: msg = f"{self.class_name}.{method_name}: " - msg += "call instance.serial_numbers " - msg += "before calling commit." - self.ansible_module.fail_json(msg, **self.failed_result) + msg += "serial_numbers must be set before calling commit()." + raise ValueError(msg) - if len(self.serial_numbers) == 0: - msg = "No files to stage." - response_current = {"DATA": [{"key": "ALL", "value": msg}]} - self.response_current = response_current - self.response = response_current - self.response_data = response_current.get("DATA", "No Stage DATA") - self.result = {"changed": False, "success": True} - self.result_current = {"changed": False, "success": True} - return + def build_payload(self) -> None: + """ + ### Summary + Build the payload for the image stage request. + """ + method_name = inspect.stack()[0][3] - self.prune_serial_numbers() - self.validate_serial_numbers() + msg = f"ENTERED {self.class_name}().{method_name}" + self.log.debug(msg) self.payload = {} self._populate_controller_version() @@ -211,224 +322,152 @@ def commit_check_mode(self) -> None: else: self.payload["serialNumbers"] = self.serial_numbers - self.rest_send.verb = self.verb - self.rest_send.path = self.path - self.rest_send.payload = self.payload - - self.rest_send.check_mode = True - - self.rest_send.commit() - - self.response_current = {} - self.response_current["DATA"] = "[simulated-check-mode-response:Success]" - self.response_current["MESSAGE"] = "OK" - self.response_current["METHOD"] = self.verb - self.response_current["REQUEST_PATH"] = self.path - self.response_current["RETURN_CODE"] = 200 - self.response = copy.deepcopy(self.response_current) - - self.response_data = self.response_current.get("DATA") - - self.result_current = self.rest_send._handle_response(self.response_current) - self.result = copy.deepcopy(self.result_current) - - msg = "payload: " - msg += f"{json.dumps(self.payload, indent=4, sort_keys=True)}" - self.log.debug(msg) - - msg = "self.response: " - msg += f"{json.dumps(self.response, indent=4, sort_keys=True)}" - self.log.debug(msg) - - msg = "self.response_current: " - msg += f"{json.dumps(self.response_current, indent=4, sort_keys=True)}" - self.log.debug(msg) - - msg = "self.response_data: " - msg += f"{self.response_data}" - self.log.debug(msg) - - msg = "self.result: " - msg += f"{json.dumps(self.result, indent=4, sort_keys=True)}" - self.log.debug(msg) - - msg = "self.result_current: " - msg += f"{json.dumps(self.result_current, indent=4, sort_keys=True)}" - self.log.debug(msg) - - if not self.result_current["success"]: - msg = f"{self.class_name}.{method_name}: " - msg += f"failed: {self.result_current}. " - msg += f"Controller response: {self.response_current}" - self.ansible_module.fail_json(msg, **self.failed_result) - - for serial_number in self.serial_numbers: - self.issu_detail.filter = serial_number - diff = {} - diff["action"] = "stage" - diff["ip_address"] = self.issu_detail.ip_address - diff["logical_name"] = self.issu_detail.device_name - diff["policy"] = self.issu_detail.policy - diff["serial_number"] = serial_number - # See image_upgrade_common.py for the definition of self.diff - self.diff = copy.deepcopy(diff) - - msg = "self.diff: " - msg += f"{json.dumps(self.diff, indent=4, sort_keys=True)}" - self.log.debug(msg) - - def commit_normal_mode(self) -> None: + def commit(self) -> None: """ + ### Summary Commit the image staging request to the controller and wait for the images to be staged. + + ### Raises + - ``ValueError`` if: + - ``rest_send`` is not set. + - ``results`` is not set. + - ``serial_numbers`` is not set. + - ``ControllerResponseError`` if: + - The controller response is unsuccessful. """ method_name = inspect.stack()[0][3] msg = f"ENTERED {self.class_name}.{method_name}" self.log.debug(msg) - msg = f"self.serial_numbers: {self.serial_numbers}" + msg = f"{self.class_name}.{method_name}: " + msg += f"self.serial_numbers: {self.serial_numbers}" self.log.debug(msg) - if self.serial_numbers is None: - msg = f"{self.class_name}.{method_name}: " - msg += "call instance.serial_numbers " - msg += "before calling commit." - self.ansible_module.fail_json(msg, **self.failed_result) + self.validate_commit_parameters() if len(self.serial_numbers) == 0: - msg = "No files to stage." - response_current = {"DATA": [{"key": "ALL", "value": msg}]} - self.response_current = response_current - self.response = response_current - self.response_data = response_current.get("DATA", "No Stage DATA") - self.result = {"changed": False, "success": True} - self.result_current = {"changed": False, "success": True} + msg = "No images to stage." + self.register_unchanged_result(msg) return + # pylint: disable=no-member + self.issu_detail.rest_send = self.rest_send + self.controller_version_instance.rest_send = self.rest_send + # pylint: enable=no-member + # We don't want the results to show up in the user's result output. + self.issu_detail.results = Results() + self.prune_serial_numbers() self.validate_serial_numbers() - self._wait_for_current_actions_to_complete() - - self.payload = {} - self._populate_controller_version() + self.wait_for_controller() + self.build_payload() - if self.controller_version == "12.1.2e": - # Yes, version 12.1.2e wants serialNum to be misspelled - self.payload["sereialNum"] = self.serial_numbers - else: - self.payload["serialNumbers"] = self.serial_numbers - - self.rest_send.verb = self.verb - self.rest_send.path = self.path - self.rest_send.payload = self.payload - - self.rest_send.check_mode = False - - self.rest_send.commit() - - self.response = self.rest_send.response_current - self.response_current = self.rest_send.response_current - self.response_data = self.response_current.get("DATA", "No Stage DATA") - - self.result = self.rest_send.result_current - self.result_current = self.rest_send.result_current - - msg = "payload: " - msg += f"{json.dumps(self.payload, indent=4, sort_keys=True)}" - self.log.debug(msg) - - msg = "self.response: " - msg += f"{json.dumps(self.response, indent=4, sort_keys=True)}" - self.log.debug(msg) - - msg = "self.response_current: " - msg += f"{json.dumps(self.response_current, indent=4, sort_keys=True)}" - self.log.debug(msg) - - msg = "self.response_data: " - msg += f"{self.response_data}" - self.log.debug(msg) - - msg = "self.result: " - msg += f"{json.dumps(self.result, indent=4, sort_keys=True)}" - self.log.debug(msg) - - msg = "self.result_current: " - msg += f"{json.dumps(self.result_current, indent=4, sort_keys=True)}" + msg = f"{self.class_name}.{method_name}: " + msg += "Calling RestSend().commit()" self.log.debug(msg) - if not self.result_current["success"]: + # pylint: disable=no-member + try: + self.rest_send.verb = self.ep_image_stage.verb + self.rest_send.path = self.ep_image_stage.path + self.rest_send.payload = self.payload + self.rest_send.commit() + except (TypeError, ValueError) as error: + self.results.diff_current = {} + self.results.action = self.action + 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() msg = f"{self.class_name}.{method_name}: " - msg += f"failed: {self.result_current}. " - msg += f"Controller response: {self.response_current}" - self.ansible_module.fail_json(msg, **self.failed_result) + msg += "Error while sending request. " + msg += f"Error detail: {error}" + raise ValueError(msg) from error + + if not self.rest_send.result_current["success"]: + self.results.diff_current = {} + self.results.action = self.action + 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() + msg = f"{self.class_name}.{method_name}: " + msg += "failed. " + msg += f"Controller response: {self.rest_send.response_current}" + raise ControllerResponseError(msg) + + # Save response_current and result_current so they aren't overwritten + # by _wait_for_image_stage_to_complete(), which needs to run + # before we can build the diff, since the diff is based on the + # serial_numbers_done set, which isn't populated until image + # stage is complete. + self.saved_response_current = copy.deepcopy(self.rest_send.response_current) + self.saved_result_current = copy.deepcopy(self.rest_send.result_current) self._wait_for_image_stage_to_complete() + self.build_diff() - for serial_number in self.serial_numbers_done: - self.issu_detail.filter = serial_number - diff = {} - diff["action"] = "stage" - diff["ip_address"] = self.issu_detail.ip_address - diff["logical_name"] = self.issu_detail.device_name - diff["policy"] = self.issu_detail.policy - diff["serial_number"] = serial_number - # See image_upgrade_common.py for the definition of self.diff - self.diff = copy.deepcopy(diff) - - msg = "self.diff: " - msg += f"{json.dumps(self.diff, indent=4, sort_keys=True)}" - self.log.debug(msg) + self.results.action = self.action + self.results.diff_current = copy.deepcopy(self.diff) + self.results.response_current = copy.deepcopy(self.saved_response_current) + self.results.result_current = copy.deepcopy(self.saved_result_current) + self.results.register_task_result() - def _wait_for_current_actions_to_complete(self): + def wait_for_controller(self) -> None: """ - The controller will not stage an image if there are any actions in - progress. Wait for all actions to complete before staging image. - Actions include image staging, image upgrade, and image validation. + ### Summary + Wait for any actions on the controller to complete. + + ### Raises + - ValueError: if: + - ``items`` is not a set. + - ``item_type`` is not a valid item type. + - The action times out. """ method_name = inspect.stack()[0][3] - self.serial_numbers_done = set() - serial_numbers_todo = set(copy.copy(self.serial_numbers)) - timeout = self.check_timeout - - while self.serial_numbers_done != serial_numbers_todo and timeout > 0: - if self.unit_test is False: - sleep(self.check_interval) - timeout -= self.check_interval - self.issu_detail.refresh() - - for serial_number in self.serial_numbers: - if serial_number in self.serial_numbers_done: - continue - - self.issu_detail.filter = serial_number - - if self.issu_detail.actions_in_progress is False: - self.serial_numbers_done.add(serial_number) - - if self.serial_numbers_done != serial_numbers_todo: - msg = f"{self.class_name}.{method_name}: " - msg += "Timed out waiting for actions to complete. " - msg += "serial_numbers_done: " - msg += f"{','.join(sorted(self.serial_numbers_done))}, " - msg += "serial_numbers_todo: " - msg += f"{','.join(sorted(serial_numbers_todo))}" - self.ansible_module.fail_json(msg, **self.failed_result) + msg = f"ENTERED {self.class_name}().{method_name}" + self.log.debug(msg) - def _wait_for_image_stage_to_complete(self): + try: + self.wait_for_controller_done.items = set(copy.copy(self.serial_numbers)) + self.wait_for_controller_done.item_type = "serial_number" + self.wait_for_controller_done.rest_send = ( + self.rest_send # pylint: disable=no-member + ) + self.wait_for_controller_done.commit() + except (TypeError, ValueError) as error: + msg = f"{self.class_name}.wait_for_controller: " + msg += "Error while waiting for controller actions to complete. " + msg += f"Error detail: {error}" + raise ValueError(msg) from error + + def _wait_for_image_stage_to_complete(self) -> None: """ - # Wait for image stage to complete + ### Summary + Wait for image stage to complete + + ### Raises + - ``ValueError`` if: + - Image stage does not complete within ``check_timeout`` + seconds. + - Image stage fails for any switch. """ method_name = inspect.stack()[0][3] + msg = f"ENTERED {self.class_name}().{method_name}" + self.log.debug(msg) + self.serial_numbers_done = set() timeout = self.check_timeout - serial_numbers_todo = set(copy.copy(self.serial_numbers)) + self.serial_numbers_todo = set(copy.copy(self.serial_numbers)) - while self.serial_numbers_done != serial_numbers_todo and timeout > 0: - if self.unit_test is False: + while self.serial_numbers_done != self.serial_numbers_todo and timeout > 0: + if self.rest_send.unit_test is False: # pylint: disable=no-member sleep(self.check_interval) timeout -= self.check_interval self.issu_detail.refresh() @@ -448,85 +487,108 @@ def _wait_for_image_stage_to_complete(self): msg += f"Seconds remaining {timeout}: stage image failed " msg += f"for {device_name}, {serial_number}, {ip_address}. " msg += f"image staged percent: {staged_percent}" - self.ansible_module.fail_json(msg, **self.failed_result) - + raise ValueError(msg) if staged_status == "Success": self.serial_numbers_done.add(serial_number) - msg = f"seconds remaining {timeout}" - self.log.debug(msg) - msg = f"serial_numbers_todo: {sorted(serial_numbers_todo)}" - self.log.debug(msg) - msg = f"serial_numbers_done: {sorted(self.serial_numbers_done)}" - self.log.debug(msg) + msg = f"seconds remaining {timeout}" + self.log.debug(msg) + msg = f"serial_numbers_todo: {sorted(self.serial_numbers_todo)}" + self.log.debug(msg) + msg = f"serial_numbers_done: {sorted(self.serial_numbers_done)}" + self.log.debug(msg) - if self.serial_numbers_done != serial_numbers_todo: + if self.serial_numbers_done != self.serial_numbers_todo: msg = f"{self.class_name}.{method_name}: " msg += "Timed out waiting for image stage to complete. " msg += "serial_numbers_done: " msg += f"{','.join(sorted(self.serial_numbers_done))}, " msg += "serial_numbers_todo: " - msg += f"{','.join(sorted(serial_numbers_todo))}" - self.ansible_module.fail_json(msg, **self.failed_result) + msg += f"{','.join(sorted(self.serial_numbers_todo))}" + raise ValueError(msg) @property - def serial_numbers(self): + def serial_numbers(self) -> list: """ + ### Summary Set the serial numbers of the switches to stage. This must be set before calling instance.commit() + + ### Raises + - ``TypeError`` if: + - value is not a list of switch serial numbers. """ - return self.properties.get("serial_numbers") + return self._serial_numbers @serial_numbers.setter - def serial_numbers(self, value): + def serial_numbers(self, value) -> None: method_name = inspect.stack()[0][3] if not isinstance(value, list): msg = f"{self.class_name}.{method_name}: " msg += "must be a python list of switch serial numbers." - self.ansible_module.fail_json(msg, **self.failed_result) - self.properties["serial_numbers"] = value + raise TypeError(msg) + for item in value: + if not isinstance(item, str): + msg = f"{self.class_name}.{method_name}: " + msg += "must be a python list of switch serial numbers." + raise TypeError(msg) + self._serial_numbers = value @property - def check_interval(self): + def check_interval(self) -> int: """ - Return the stage check interval in seconds + ### Summary + The interval, in seconds, used to check the status of the image stage + operation. Used by ``_wait_for_image_stage_to_complete()``. + + ### Raises + - ``TypeError`` if: + - value is not a positive integer. + - ``ValueError`` if: + - value is an integer less than zero. """ - return self.properties.get("check_interval") + return self._check_interval @check_interval.setter - def check_interval(self, value): + def check_interval(self, value) -> None: method_name = inspect.stack()[0][3] msg = f"{self.class_name}.{method_name}: " msg += "must be a positive integer or zero. " msg += f"Got value {value} of type {type(value)}." # isinstance(True, int) is True so we need to check for bool first if isinstance(value, bool): - self.ansible_module.fail_json(msg, **self.failed_result) + raise TypeError(msg) if not isinstance(value, int): - self.ansible_module.fail_json(msg, **self.failed_result) + raise TypeError(msg) if value < 0: - self.ansible_module.fail_json(msg, **self.failed_result) - self.properties["check_interval"] = value + raise ValueError(msg) + self._check_interval = value @property - def check_timeout(self): + def check_timeout(self) -> int: """ - Return the stage check timeout in seconds + ### Summary + The interval, in seconds, used to check the status of the image stage + operation. Used by ``_wait_for_image_stage_to_complete()``. + + ### Raises + - ``TypeError`` if: + - value is not a positive integer. """ - return self.properties.get("check_timeout") + return self._check_timeout @check_timeout.setter - def check_timeout(self, value): + def check_timeout(self, value) -> None: method_name = inspect.stack()[0][3] msg = f"{self.class_name}.{method_name}: " msg += "must be a positive integer or zero. " msg += f"Got value {value} of type {type(value)}." # isinstance(True, int) is True so we need to check for bool first if isinstance(value, bool): - self.ansible_module.fail_json(msg, **self.failed_result) + raise TypeError(msg) if not isinstance(value, int): - self.ansible_module.fail_json(msg, **self.failed_result) + raise TypeError(msg) if value < 0: - self.ansible_module.fail_json(msg, **self.failed_result) - self.properties["check_timeout"] = value + raise ValueError(msg) + self._check_timeout = value diff --git a/plugins/module_utils/image_upgrade/image_upgrade.py b/plugins/module_utils/image_upgrade/image_upgrade.py index 65de2f9ee..9a359112f 100644 --- a/plugins/module_utils/image_upgrade/image_upgrade.py +++ b/plugins/module_utils/image_upgrade/image_upgrade.py @@ -23,73 +23,103 @@ import json import logging from time import sleep -from typing import Any, Dict, List, Set - -from ansible_collections.cisco.dcnm.plugins.module_utils.common.rest_send import \ - RestSend -from ansible_collections.cisco.dcnm.plugins.module_utils.image_upgrade.api_endpoints import \ - ApiEndpoints -from ansible_collections.cisco.dcnm.plugins.module_utils.image_upgrade.image_upgrade_common import \ - ImageUpgradeCommon + +from ansible_collections.cisco.dcnm.plugins.module_utils.common.api.v1.imagemanagement.rest.imageupgrade.imageupgrade import \ + EpUpgradeImage +from ansible_collections.cisco.dcnm.plugins.module_utils.common.conversion import \ + ConversionUtils +from ansible_collections.cisco.dcnm.plugins.module_utils.common.exceptions import \ + ControllerResponseError +from ansible_collections.cisco.dcnm.plugins.module_utils.common.properties import \ + Properties +from ansible_collections.cisco.dcnm.plugins.module_utils.common.results import \ + Results from ansible_collections.cisco.dcnm.plugins.module_utils.image_upgrade.install_options import \ ImageInstallOptions from ansible_collections.cisco.dcnm.plugins.module_utils.image_upgrade.switch_issu_details import \ SwitchIssuDetailsByIpAddress +from ansible_collections.cisco.dcnm.plugins.module_utils.image_upgrade.wait_for_controller_done import \ + WaitForControllerDone -class ImageUpgrade(ImageUpgradeCommon): +@Properties.add_rest_send +@Properties.add_results +class ImageUpgrade: """ - Endpoint: - /appcenter/cisco/ndfc/api/v1/imagemanagement/rest/imageupgrade/upgrade-image - Verb: POST - - Usage (where module is an instance of AnsibleModule): - - upgrade = ImageUpgrade(module) - upgrade.devices = devices + ### Summary + Upgrade the image on one or more switches. + + + ### Usage example + ```python + # params is typically obtained from ansible_module.params + # but can also be specified manually, like below. + params = {"check_mode": False, "state": "merged"} + sender = Sender() + sender.ansible_module = ansible_module + rest_send = RestSend(params) + rest_send.response_handler = ResponseHandler() + rest_send.sender = sender + results = Results() + + upgrade = ImageUpgrade() + upgrade.rest_send = rest_send + upgrade.results = results + upgrade.devices = devices # see Example devices structure below upgrade.commit() data = upgrade.data + ``` - Where devices is a list of dict. Example structure: + ### Endpoint: + - path: /appcenter/cisco/ndfc/api/v1/imagemanagement/rest/imageupgrade/upgrade-image + - verb: POST - [ - { - 'policy': 'KR3F', - 'ip_address': '172.22.150.102', - 'policy_changed': False - 'stage': False, - 'validate': True, - 'upgrade': { - 'nxos': True, - 'epld': False + ### Example devices structure + + ```python + devices = [ + { + 'policy': 'KR3F', + 'ip_address': '172.22.150.102', + 'policy_changed': False + 'stage': False, + 'validate': True, + 'upgrade': { + 'nxos': True, + 'epld': False + }, + 'options': { + 'nxos': { + 'mode': 'non_disruptive' + 'bios_force': False }, - 'options': { - 'nxos': { - 'mode': 'non_disruptive' - 'bios_force': False - }, - 'epld': { - 'module': 'ALL', - 'golden': False - }, - 'reboot': { - 'config_reload': False, - 'write_erase': False - }, - 'package': { - 'install': False, - 'uninstall': False - } + 'epld': { + 'module': 'ALL', + 'golden': False }, + 'reboot': { + 'config_reload': False, + 'write_erase': False + }, + 'package': { + 'install': False, + 'uninstall': False + } }, - etc... - ] + }, + { + "etc...": "etc..." + } + ] + ``` - Request body: - Yes, the keys below are misspelled in the request body: - pacakgeInstall - pacakgeUnInstall + ### Example request body + - Yes, the keys below are misspelled in the request body: + - ``pacakgeInstall`` + - ``pacakgeUnInstall`` + + ```json { "devices": [ { @@ -119,39 +149,50 @@ class ImageUpgrade(ImageUpgradeCommon): "pacakgeInstall": false, "pacakgeUnInstall": false } - Response bodies: - Responses are text, not JSON, and are returned immediately. - They do not contain useful information. We need to poll the controller - to determine when the upgrade is complete. Basically, we ignore - these responses in favor of the poll responses. - - If an action is in progress, text is returned: - "Action in progress for some of selected device(s). - Please try again after completing current action." - - If an action is not in progress, text is returned: - "3" + ``` + + ### Response bodies + - Responses are text, not JSON, and are returned immediately. + - Responses do not contain useful information. We need to poll + the controller to determine when the upgrade is complete. + Basically, we ignore these responses in favor of the poll + responses. + - If an action is in progress, text is returned: + ``Action in progress for some of selected device(s). + Please try again after completing current action.`` + - If an action is not in progress, text is returned: + ``3`` """ - def __init__(self, ansible_module): - super().__init__(ansible_module) + def __init__(self): self.class_name = self.__class__.__name__ + method_name = inspect.stack()[0][3] self.log = logging.getLogger(f"dcnm.{self.class_name}") - msg = "ENTERED ImageUpgrade(): " - msg += f"check_mode: {self.check_mode}" - self.log.debug(msg) - self.endpoints = ApiEndpoints() - self.install_options = ImageInstallOptions(self.ansible_module) - self.rest_send = RestSend(self.ansible_module) - self.issu_detail = SwitchIssuDetailsByIpAddress(self.ansible_module) + self.action = "image_upgrade" + self.diff: dict = {} + # Used in _wait_for_upgrade_to_complete() self.ipv4_done = set() self.ipv4_todo = set() - self.payload: Dict[str, Any] = {} - self.path = self.endpoints.image_upgrade.get("path") - self.verb = self.endpoints.image_upgrade.get("verb") + self.payload = None + self.saved_response_current: dict = {} + self.saved_result_current: dict = {} + + self.conversion = ConversionUtils() + self.ep_upgrade_image = EpUpgradeImage() + self.install_options = ImageInstallOptions() + self.issu_detail = SwitchIssuDetailsByIpAddress() + self.wait_for_controller_done = WaitForControllerDone() + + self._rest_send = None + self._results = None self._init_properties() + msg = f"ENTERED {self.class_name}().{method_name}" + self.log.debug(msg) + def _init_properties(self) -> None: """ Initialize properties used by this class. @@ -163,26 +204,26 @@ def _init_properties(self) -> None: # self.ip_addresses is used in: # self._wait_for_current_actions_to_complete() # self._wait_for_image_upgrade_to_complete() - self.ip_addresses: Set[str] = set() - # self.properties is already initialized in the parent class - self.properties["bios_force"] = False - self.properties["check_interval"] = 10 # seconds - self.properties["check_timeout"] = 1800 # seconds - self.properties["config_reload"] = False - self.properties["devices"] = None - self.properties["disruptive"] = True - self.properties["epld_golden"] = False - self.properties["epld_module"] = "ALL" - self.properties["epld_upgrade"] = False - self.properties["force_non_disruptive"] = False - self.properties["response_data"] = [] - self.properties["non_disruptive"] = False - self.properties["package_install"] = False - self.properties["package_uninstall"] = False - self.properties["reboot"] = False - self.properties["write_erase"] = False - - self.valid_nxos_mode: Set[str] = set() + self.ip_addresses: set = set() + + self.properties = {} + self._bios_force = False + self._check_interval = 10 # seconds + self._check_timeout = 1800 # seconds + self._config_reload = False + self._devices = None + self._disruptive = True + self._epld_golden = False + self._epld_module = "ALL" + self._epld_upgrade = False + self._force_non_disruptive = False + self._non_disruptive = False + self._package_install = False + self._package_uninstall = False + self._reboot = False + self._write_erase = False + + self.valid_nxos_mode: set = set() self.valid_nxos_mode.add("disruptive") self.valid_nxos_mode.add("non_disruptive") self.valid_nxos_mode.add("force_non_disruptive") @@ -191,6 +232,37 @@ def _init_properties(self) -> None: # is now done in dcnm_image_upgrade.py. Consider moving # that code here later. + def build_diff(self) -> None: + """ + ### Summary + Build the diff of the image validate operation. + + ### Raises + None + """ + method_name = inspect.stack()[0][3] + + msg = f"ENTERED {self.class_name}.{method_name}" + self.log.debug(msg) + + self.diff: dict = {} + + for ipv4 in self.ipv4_done: + self.issu_detail.filter = ipv4 + + if ipv4 not in self.diff: + self.diff[ipv4] = {} + + self.diff[ipv4]["action"] = self.action + self.diff[ipv4]["ip_address"] = self.issu_detail.ip_address + self.diff[ipv4]["logical_name"] = self.issu_detail.device_name + self.diff[ipv4]["policy_name"] = self.issu_detail.policy + self.diff[ipv4]["serial_number"] = self.issu_detail.serial_number + msg = f"{self.class_name}.{method_name}: " + msg += f"self.diff[{ipv4}]: " + msg += f"{json.dumps(self.diff[ipv4], indent=4)}" + self.log.debug(msg) + def _validate_devices(self) -> None: """ 1. Perform any pre-upgrade validations @@ -201,13 +273,14 @@ def _validate_devices(self) -> None: """ method_name = inspect.stack()[0][3] + msg = f"ENTERED: {self.class_name}.{method_name}: " msg = f"self.devices: {json.dumps(self.devices, indent=4, sort_keys=True)}" self.log.debug(msg) if self.devices is None: msg = f"{self.class_name}.{method_name}: " msg += "call instance.devices before calling commit." - self.ansible_module.fail_json(msg, **self.failed_result) + raise ValueError(msg) self.issu_detail.refresh() for device in self.devices: @@ -227,11 +300,14 @@ def _build_payload(self, device) -> None: """ Build the request payload to upgrade the switches. """ - # issu_detail.refresh() has already been called in _validate_devices() - # so no need to call it here. - msg = f"ENTERED _build_payload: device {device}" + method_name = inspect.stack()[0][3] + + msg = f"ENTERED {self.class_name}.{method_name}: " + msg += f"device {device}" self.log.debug(msg) + # issu_detail.refresh() has already been called in _validate_devices() + # so no need to call it here. self.issu_detail.filter = device.get("ip_address") self.install_options.serial_number = self.issu_detail.serial_number @@ -247,14 +323,14 @@ def _build_payload(self, device) -> None: self.install_options.refresh() # devices_to_upgrade must currently be a single device - devices_to_upgrade: List[dict] = [] + devices_to_upgrade: list = [] - payload_device: Dict[str, Any] = {} + payload_device: dict = {} payload_device["serialNumber"] = self.issu_detail.serial_number payload_device["policyName"] = device.get("policy") devices_to_upgrade.append(payload_device) - self.payload: Dict[str, Any] = {} + self.payload: dict = {} self.payload["devices"] = devices_to_upgrade self._build_payload_issu_upgrade(device) @@ -272,15 +348,18 @@ def _build_payload_issu_upgrade(self, device) -> None: """ Build the issuUpgrade portion of the payload. """ - method_name = inspect.stack()[0][3] # pylint: disable=unused-variable + method_name = inspect.stack()[0][3] + + msg = f"ENTERED: {self.class_name}.{method_name}." + self.log.debug(msg) nxos_upgrade = device.get("upgrade").get("nxos") - nxos_upgrade = self.make_boolean(nxos_upgrade) + nxos_upgrade = self.conversion.make_boolean(nxos_upgrade) if not isinstance(nxos_upgrade, bool): msg = f"{self.class_name}.{method_name}: " msg += "upgrade.nxos must be a boolean. " msg += f"Got {nxos_upgrade}." - self.ansible_module.fail_json(msg, **self.failed_result) + raise TypeError(msg) self.payload["issuUpgrade"] = nxos_upgrade def _build_payload_issu_options_1(self, device) -> None: @@ -289,6 +368,9 @@ def _build_payload_issu_options_1(self, device) -> None: """ method_name = inspect.stack()[0][3] + msg = f"ENTERED: {self.class_name}.{method_name}." + self.log.debug(msg) + # nxos_mode: The choices for nxos_mode are mutually-exclusive. # If one is set to True, the others must be False. # nonDisruptive corresponds to Allow Non-Disruptive GUI option @@ -303,7 +385,7 @@ def _build_payload_issu_options_1(self, device) -> None: msg += "options.nxos.mode must be one of " msg += f"{sorted(self.valid_nxos_mode)}. " msg += f"Got {nxos_mode}." - self.ansible_module.fail_json(msg, **self.failed_result) + raise ValueError(msg) verify_nxos_mode_list = [] if nxos_mode == "non_disruptive": @@ -322,13 +404,19 @@ def _build_payload_issu_options_2(self, device) -> None: """ method_name = inspect.stack()[0][3] + msg = f"ENTERED: {self.class_name}.{method_name}." + self.log.debug(msg) + + msg = f"ENTERED: {self.class_name}.{method_name}." + self.log.debug(msg) + bios_force = device.get("options").get("nxos").get("bios_force") - bios_force = self.make_boolean(bios_force) + bios_force = self.conversion.make_boolean(bios_force) if not isinstance(bios_force, bool): msg = f"{self.class_name}.{method_name}: " msg += "options.nxos.bios_force must be a boolean. " msg += f"Got {bios_force}." - self.ansible_module.fail_json(msg, **self.failed_result) + raise TypeError(msg) self.payload["issuUpgradeOptions2"] = {} self.payload["issuUpgradeOptions2"]["biosForce"] = bios_force @@ -339,23 +427,26 @@ def _build_payload_epld(self, device) -> None: """ method_name = inspect.stack()[0][3] + msg = f"ENTERED: {self.class_name}.{method_name}." + self.log.debug(msg) + epld_upgrade = device.get("upgrade").get("epld") - epld_upgrade = self.make_boolean(epld_upgrade) + epld_upgrade = self.conversion.make_boolean(epld_upgrade) if not isinstance(epld_upgrade, bool): msg = f"{self.class_name}.{method_name}: " msg += "upgrade.epld must be a boolean. " msg += f"Got {epld_upgrade}." - self.ansible_module.fail_json(msg, **self.failed_result) + raise TypeError(msg) epld_module = device.get("options").get("epld").get("module") epld_golden = device.get("options").get("epld").get("golden") - epld_golden = self.make_boolean(epld_golden) + epld_golden = self.conversion.make_boolean(epld_golden) if not isinstance(epld_golden, bool): msg = f"{self.class_name}.{method_name}: " msg += "options.epld.golden must be a boolean. " msg += f"Got {epld_golden}." - self.ansible_module.fail_json(msg, **self.failed_result) + raise TypeError(msg) if epld_golden is True and device.get("upgrade").get("nxos") is True: msg = f"{self.class_name}.{method_name}: " @@ -364,16 +455,16 @@ def _build_payload_epld(self, device) -> None: msg += "If options.epld.golden is True " msg += "all other upgrade options, e.g. upgrade.nxos, " msg += "must be False." - self.ansible_module.fail_json(msg, **self.failed_result) + raise ValueError(msg) if epld_module != "ALL": try: epld_module = int(epld_module) - except ValueError: + except ValueError as error: msg = f"{self.class_name}.{method_name}: " msg += "options.epld.module must either be 'ALL' " msg += f"or an integer. Got {epld_module}." - self.ansible_module.fail_json(msg, **self.failed_result) + raise ValueError(msg) from error self.payload["epldUpgrade"] = epld_upgrade self.payload["epldOptions"] = {} @@ -385,14 +476,18 @@ def _build_payload_reboot(self, device) -> None: Build the reboot portion of the payload. """ method_name = inspect.stack()[0][3] + + msg = f"ENTERED: {self.class_name}.{method_name}." + self.log.debug(msg) + reboot = device.get("reboot") - reboot = self.make_boolean(reboot) + reboot = self.conversion.make_boolean(reboot) if not isinstance(reboot, bool): msg = f"{self.class_name}.{method_name}: " msg += "reboot must be a boolean. " msg += f"Got {reboot}." - self.ansible_module.fail_json(msg, **self.failed_result) + raise TypeError(msg) self.payload["reboot"] = reboot def _build_payload_reboot_options(self, device) -> None: @@ -401,22 +496,25 @@ def _build_payload_reboot_options(self, device) -> None: """ method_name = inspect.stack()[0][3] + msg = f"ENTERED: {self.class_name}.{method_name}." + self.log.debug(msg) + config_reload = device.get("options").get("reboot").get("config_reload") write_erase = device.get("options").get("reboot").get("write_erase") - config_reload = self.make_boolean(config_reload) + config_reload = self.conversion.make_boolean(config_reload) if not isinstance(config_reload, bool): msg = f"{self.class_name}.{method_name}: " msg += "options.reboot.config_reload must be a boolean. " msg += f"Got {config_reload}." - self.ansible_module.fail_json(msg, **self.failed_result) + raise TypeError(msg) - write_erase = self.make_boolean(write_erase) + write_erase = self.conversion.make_boolean(write_erase) if not isinstance(write_erase, bool): msg = f"{self.class_name}.{method_name}: " msg += "options.reboot.write_erase must be a boolean. " msg += f"Got {write_erase}." - self.ansible_module.fail_json(msg, **self.failed_result) + raise TypeError(msg) self.payload["rebootOptions"] = {} self.payload["rebootOptions"]["configReload"] = config_reload @@ -428,10 +526,13 @@ def _build_payload_package(self, device) -> None: """ method_name = inspect.stack()[0][3] + msg = f"ENTERED: {self.class_name}.{method_name}." + self.log.debug(msg) + package_install = device.get("options").get("package").get("install") package_uninstall = device.get("options").get("package").get("uninstall") - package_install = self.make_boolean(package_install) + package_install = self.conversion.make_boolean(package_install) if not isinstance(package_install, bool): # This code is never hit since ImageInstallOptions calls # fail_json on invalid options.package.install. @@ -440,14 +541,14 @@ def _build_payload_package(self, device) -> None: msg = f"{self.class_name}.{method_name}: " msg += "options.package.install must be a boolean. " msg += f"Got {package_install}." - self.ansible_module.fail_json(msg, **self.failed_result) + raise TypeError(msg) - package_uninstall = self.make_boolean(package_uninstall) + package_uninstall = self.conversion.make_boolean(package_uninstall) if not isinstance(package_uninstall, bool): msg = f"{self.class_name}.{method_name}: " msg += "options.package.uninstall must be a boolean. " msg += f"Got {package_uninstall}." - self.ansible_module.fail_json(msg, **self.failed_result) + raise TypeError(msg) # Yes, these keys are misspelled. The controller # wants them to be misspelled. Need to keep an @@ -455,219 +556,176 @@ def _build_payload_package(self, device) -> None: self.payload["pacakgeInstall"] = package_install self.payload["pacakgeUnInstall"] = package_uninstall - def commit(self) -> None: - if self.check_mode is True: - self.commit_check_mode() - else: - self.commit_normal_mode() - - def commit_check_mode(self) -> None: + def validate_commit_parameters(self): """ - Simulate a commit of the image upgrade request to - the controller. + Verify mandatory parameters are set before calling commit. """ + # pylint: disable=no-member method_name = inspect.stack()[0][3] - self._validate_devices() - - self.rest_send.verb = self.verb - self.rest_send.path = self.path - - self.rest_send.check_mode = True - - for device in self.devices: - msg = f"device: {json.dumps(device, indent=4, sort_keys=True)}" - self.log.debug(msg) - - self._build_payload(device) - - msg = "Calling rest_send.commit(): " - msg += f"verb {self.verb}, path: {self.path} " - msg += f"payload: {json.dumps(self.payload, indent=4, sort_keys=True)}" - self.log.debug(msg) - - self.rest_send.payload = self.payload - self.rest_send.commit() - - msg = "DONE rest_send.commit()" - self.log.debug(msg) - - self.response_current = {} - self.response_current["DATA"] = "[simulated-check-mode-response:Success]" - self.response_current["MESSAGE"] = "OK" - self.response_current["METHOD"] = self.verb - self.response_current["REQUEST_PATH"] = self.path - self.response_current["RETURN_CODE"] = 200 - self.response = copy.deepcopy(self.response_current) - - self.response_data = self.response_current.get("DATA") - - self.result_current = self.rest_send._handle_response(self.response_current) - self.result = copy.deepcopy(self.result_current) - - msg = "payload: " - msg += f"{json.dumps(self.payload, indent=4, sort_keys=True)}" - self.log.debug(msg) - - msg = "self.response: " - msg += f"{json.dumps(self.response, indent=4, sort_keys=True)}" - self.log.debug(msg) - - msg = "self.response_current: " - msg += f"{json.dumps(self.response_current, indent=4, sort_keys=True)}" - self.log.debug(msg) - - msg = "self.response_data: " - msg += f"{self.response_data}" - self.log.debug(msg) - - msg = "self.result: " - msg += f"{json.dumps(self.result, indent=4, sort_keys=True)}" - self.log.debug(msg) - - msg = "self.result_current: " - msg += f"{json.dumps(self.result_current, indent=4, sort_keys=True)}" - self.log.debug(msg) - - if not self.result_current["success"]: - msg = f"{self.class_name}.{method_name}: " - msg += f"failed: {self.result_current}. " - msg += f"Controller response: {self.response_current}" - self.ansible_module.fail_json(msg, **self.failed_result) + msg = f"ENTERED: {self.class_name}.{method_name}." + self.log.debug(msg) - # See image_upgrade_common.py for the definition of self.diff - self.diff = copy.deepcopy(self.payload) + if self.rest_send is None: + msg = f"{self.class_name}.{method_name}: " + msg += "rest_send must be set before calling commit()." + raise ValueError(msg) + if self.results is None: + msg = f"{self.class_name}.{method_name}: " + msg += "results must be set before calling commit()." + raise ValueError(msg) - def commit_normal_mode(self) -> None: + def commit(self) -> None: """ + ### Summary Commit the image upgrade request to the controller and wait for the images to be upgraded. + + ### Raises + - ``ControllerResponseError`` if the controller returns a non-200 + response. + - ``ValueError`` if: + - ``RestSend()`` raises a ``TypeError`` or ``ValueError``. """ method_name = inspect.stack()[0][3] - self._validate_devices() - self._wait_for_current_actions_to_complete() - - self.rest_send.verb = self.verb - self.rest_send.path = self.path - - if self.check_mode is True: - self.rest_send.check_mode = True - else: - self.rest_send.check_mode = False - - for device in self.devices: - msg = f"device: {json.dumps(device, indent=4, sort_keys=True)}" - self.log.debug(msg) - - self._build_payload(device) - - msg = "Calling rest_send.commit(): " - msg += f"verb {self.verb}, path: {self.path} " - msg += f"payload: {json.dumps(self.payload, indent=4, sort_keys=True)}" - self.log.debug(msg) + msg = f"ENTERED: {self.class_name}.{method_name}." + self.log.debug(msg) - self.rest_send.payload = self.payload - self.rest_send.commit() + self.validate_commit_parameters() - msg = "DONE rest_send.commit()" - self.log.debug(msg) + # pylint: disable=no-member + self.issu_detail.rest_send = self.rest_send + self.install_options.rest_send = self.rest_send - self.response = self.rest_send.response_current - self.response_current = self.rest_send.response_current - self.response_data = self.rest_send.response_current.get("DATA") + self.install_options.results = self.results + # pylint: enable=no-member + # We don't want issu_detail results to show up in the user's result output. + self.issu_detail.results = Results() - self.result = self.rest_send.result_current - self.result_current = self.rest_send.result_current - - msg = "payload: " - msg += f"{json.dumps(self.payload, indent=4, sort_keys=True)}" - self.log.debug(msg) - - msg = "self.response: " - msg += f"{json.dumps(self.response, indent=4, sort_keys=True)}" - self.log.debug(msg) + self._validate_devices() + self.wait_for_controller() - msg = "self.response_current: " - msg += f"{json.dumps(self.response_current, indent=4, sort_keys=True)}" - self.log.debug(msg) + self.saved_response_current = {} + self.saved_result_current = {} + for device in self.devices: + ipv4 = device.get("ip_address") + if ipv4 not in self.saved_response_current: + self.saved_response_current[ipv4] = {} + if ipv4 not in self.saved_result_current: + self.saved_result_current[ipv4] = {} - msg = "self.response_data: " - msg += f"{self.response_data}" + msg = f"{self.class_name}.{method_name}: " + msg += f"device: {json.dumps(device, indent=4, sort_keys=True)}." self.log.debug(msg) - msg = "self.result: " - msg += f"{json.dumps(self.result, indent=4, sort_keys=True)}" - self.log.debug(msg) + self._build_payload(device) - msg = "self.result_current: " - msg += f"{json.dumps(self.result_current, indent=4, sort_keys=True)}" + msg = f"{self.class_name}.{method_name}: " + msg += "Calling RestSend.commit(). " + msg += f"verb: {self.ep_upgrade_image.verb}, " + msg += f"path: {self.ep_upgrade_image.path}." self.log.debug(msg) - if not self.result_current["success"]: + # pylint: disable=no-member + try: + self.rest_send.path = self.ep_upgrade_image.path + self.rest_send.verb = self.ep_upgrade_image.verb + self.rest_send.payload = self.payload + self.rest_send.commit() + except (TypeError, ValueError) as error: + self.results.diff_current = {} + self.results.action = self.action + 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() msg = f"{self.class_name}.{method_name}: " - msg += f"failed: {self.result_current}. " - msg += f"Controller response: {self.response_current}" - self.ansible_module.fail_json(msg, **self.failed_result) - - # See image_upgrade_common.py for the definition of self.diff - self.diff = copy.deepcopy(self.payload) + msg += "Error while sending request. " + msg += f"Error detail: {error}" + raise ValueError(msg) from error + + self.saved_response_current[ipv4] = copy.deepcopy( + self.rest_send.response_current + ) + self.saved_result_current[ipv4] = copy.deepcopy( + self.rest_send.result_current + ) + + if not self.rest_send.result_current["success"]: + msg = f"{self.class_name}.{method_name}: " + msg += f"failed: {self.rest_send.result_current}. " + msg += f"Controller response: {self.rest_send.response_current}" + self.results.register_task_result() + raise ControllerResponseError(msg) self._wait_for_image_upgrade_to_complete() - def _wait_for_current_actions_to_complete(self): + self.build_diff() + # pylint: disable=no-member + self.results.action = self.action + self.results.diff_current = copy.deepcopy(self.diff) + self.results.response_current = copy.deepcopy(self.saved_response_current) + self.results.result_current = copy.deepcopy(self.saved_result_current) + self.results.register_task_result() + + def wait_for_controller(self): """ - The controller will not upgrade an image if there are any actions - in progress. Wait for all actions to complete before upgrading image. - Actions include image staging, image upgrade, and image validation. + ### Summary + Wait for any actions on the controller to complete. + + ### Raises + - ValueError: if: + - ``items`` is not a set. + - ``item_type`` is not a valid item type. + - The action times out. """ method_name = inspect.stack()[0][3] - if self.unit_test is False: - # See unit test test_image_upgrade_upgrade_00205 - self.ipv4_done = set() - self.ipv4_todo = set(copy.copy(self.ip_addresses)) - timeout = self.check_timeout - - while self.ipv4_done != self.ipv4_todo and timeout > 0: - sleep(self.check_interval) - timeout -= self.check_interval - self.issu_detail.refresh() - - for ipv4 in self.ip_addresses: - if ipv4 in self.ipv4_done: - continue - self.issu_detail.filter = ipv4 - - if self.issu_detail.actions_in_progress is False: - self.ipv4_done.add(ipv4) - continue + msg = f"ENTERED: {self.class_name}.{method_name}." + self.log.debug(msg) - if self.ipv4_done != self.ipv4_todo: + try: + self.wait_for_controller_done.items = set(copy.copy(self.ip_addresses)) + self.wait_for_controller_done.item_type = "ipv4_address" + self.wait_for_controller_done.rest_send = ( + self.rest_send # pylint: disable=no-member + ) + self.wait_for_controller_done.commit() + except (TypeError, ValueError) as error: msg = f"{self.class_name}.{method_name}: " - msg += "Timed out waiting for actions to complete. " - msg += "ipv4_done: " - msg += f"{','.join(sorted(self.ipv4_done))}, " - msg += "ipv4_todo: " - msg += f"{','.join(sorted(self.ipv4_todo))}. " - msg += "check the device(s) to determine the cause " - msg += "(e.g. show install all status)." - self.ansible_module.fail_json(msg, **self.failed_result) + msg += f"Error {error}." + raise ValueError(msg) from error def _wait_for_image_upgrade_to_complete(self): """ + ### Summary Wait for image upgrade to complete + + ### Raises + - ``ValueError`` if: + - The upgrade does not complete within ``check_timeout`` + seconds. + - The upgrade fails for any device. + """ method_name = inspect.stack()[0][3] + msg = f"ENTERED: {self.class_name}.{method_name}." + self.log.debug(msg) + self.ipv4_todo = set(copy.copy(self.ip_addresses)) - if self.unit_test is False: + if self.rest_send.unit_test is False: # pylint: disable=no-member # See unit test test_image_upgrade_upgrade_00240 self.ipv4_done = set() timeout = self.check_timeout while self.ipv4_done != self.ipv4_todo and timeout > 0: - sleep(self.check_interval) + if self.rest_send.unit_test is False: # pylint: disable=no-member + sleep(self.check_interval) timeout -= self.check_interval self.issu_detail.refresh() @@ -692,16 +750,16 @@ def _wait_for_image_upgrade_to_complete(self): msg += "Operations > Image Management > Devices > View Details. " msg += "And/or check the devices " msg += "(e.g. show install all status)." - self.ansible_module.fail_json(msg, **self.failed_result) - + raise ValueError(msg) if upgrade_status == "Success": self.ipv4_done.add(ipv4) - msg = f"seconds remaining {timeout}" - self.log.debug(msg) - msg = f"ipv4_done: {sorted(self.ipv4_done)}" - self.log.debug(msg) - msg = f"ipv4_todo: {sorted(self.ipv4_todo)}" - self.log.debug(msg) + + msg = f"seconds remaining {timeout}" + self.log.debug(msg) + msg = f"ipv4_done: {sorted(self.ipv4_done)}" + self.log.debug(msg) + msg = f"ipv4_todo: {sorted(self.ipv4_todo)}" + self.log.debug(msg) if self.ipv4_done != self.ipv4_todo: msg = f"{self.class_name}.{method_name}: " @@ -711,7 +769,7 @@ def _wait_for_image_upgrade_to_complete(self): msg += "Operations > Image Management > Devices > View Details. " msg += "And/or check the device(s) " msg += "(e.g. show install all status)." - self.ansible_module.fail_json(msg, **self.failed_result) + raise ValueError(msg) # setter properties @property @@ -721,7 +779,7 @@ def bios_force(self): Default: False """ - return self.properties.get("bios_force") + return self._bios_force @bios_force.setter def bios_force(self, value): @@ -729,8 +787,8 @@ def bios_force(self, value): if not isinstance(value, bool): msg = f"{self.class_name}.{method_name}: " msg += "instance.bios_force must be a boolean." - self.ansible_module.fail_json(msg, **self.failed_result) - self.properties["bios_force"] = value + raise TypeError(msg) + self._bios_force = value @property def config_reload(self): @@ -739,7 +797,7 @@ def config_reload(self): Default: False """ - return self.properties.get("config_reload") + return self._config_reload @config_reload.setter def config_reload(self, value): @@ -747,11 +805,11 @@ def config_reload(self, value): if not isinstance(value, bool): msg = f"{self.class_name}.{method_name}: " msg += "instance.config_reload must be a boolean." - self.ansible_module.fail_json(msg, **self.failed_result) - self.properties["config_reload"] = value + raise TypeError(msg) + self._config_reload = value @property - def devices(self) -> List[Dict]: + def devices(self) -> list: """ Set the devices to upgrade. @@ -763,30 +821,30 @@ def devices(self) -> List[Dict]: ] Must be set before calling instance.commit() """ - return self.properties.get("devices", [{}]) + return self._devices @devices.setter - def devices(self, value: List[Dict]): + def devices(self, value: list): method_name = inspect.stack()[0][3] if not isinstance(value, list): msg = f"{self.class_name}.{method_name}: " msg += "instance.devices must be a python list of dict. " msg += f"Got {value}." - self.ansible_module.fail_json(msg, **self.failed_result) + raise TypeError(msg) for device in value: if not isinstance(device, dict): msg = f"{self.class_name}.{method_name}: " msg += "instance.devices must be a python list of dict. " msg += f"Got {value}." - self.ansible_module.fail_json(msg, **self.failed_result) + raise TypeError(msg) if "ip_address" not in device: msg = f"{self.class_name}.{method_name}: " msg += "instance.devices must be a python list of dict, " msg += "where each dict contains the following keys: " msg += "ip_address. " msg += f"Got {value}." - self.ansible_module.fail_json(msg, **self.failed_result) - self.properties["devices"] = value + raise ValueError(msg) + self._devices = value @property def disruptive(self): @@ -795,7 +853,7 @@ def disruptive(self): Default: False """ - return self.properties.get("disruptive") + return self._disruptive @disruptive.setter def disruptive(self, value): @@ -803,8 +861,8 @@ def disruptive(self, value): if not isinstance(value, bool): msg = f"{self.class_name}.{method_name}: " msg += "instance.disruptive must be a boolean." - self.ansible_module.fail_json(msg, **self.failed_result) - self.properties["disruptive"] = value + raise TypeError(msg) + self._disruptive = value @property def epld_golden(self): @@ -813,7 +871,7 @@ def epld_golden(self): Default: False """ - return self.properties.get("epld_golden") + return self._epld_golden @epld_golden.setter def epld_golden(self, value): @@ -821,8 +879,8 @@ def epld_golden(self, value): if not isinstance(value, bool): msg = f"{self.class_name}.{method_name}: " msg += "instance.epld_golden must be a boolean." - self.ansible_module.fail_json(msg, **self.failed_result) - self.properties["epld_golden"] = value + raise TypeError(msg) + self._epld_golden = value @property def epld_upgrade(self): @@ -831,7 +889,7 @@ def epld_upgrade(self): Default: False """ - return self.properties.get("epld_upgrade") + return self._epld_upgrade @epld_upgrade.setter def epld_upgrade(self, value): @@ -839,8 +897,8 @@ def epld_upgrade(self, value): if not isinstance(value, bool): msg = f"{self.class_name}.{method_name}: " msg += "instance.epld_upgrade must be a boolean." - self.ansible_module.fail_json(msg, **self.failed_result) - self.properties["epld_upgrade"] = value + raise TypeError(msg) + self._epld_upgrade = value @property def epld_module(self): @@ -851,7 +909,7 @@ def epld_module(self): Valid values: integer or "ALL" Default: "ALL" """ - return self.properties.get("epld_module") + return self._epld_module @epld_module.setter def epld_module(self, value): @@ -867,8 +925,8 @@ def epld_module(self, value): if not isinstance(value, int) and value != "ALL": msg = f"{self.class_name}.{method_name}: " msg += "instance.epld_module must be an integer or 'ALL'" - self.ansible_module.fail_json(msg, **self.failed_result) - self.properties["epld_module"] = value + raise TypeError(msg) + self._epld_module = value @property def force_non_disruptive(self): @@ -877,7 +935,7 @@ def force_non_disruptive(self): Default: False """ - return self.properties.get("force_non_disruptive") + return self._force_non_disruptive @force_non_disruptive.setter def force_non_disruptive(self, value): @@ -885,8 +943,8 @@ def force_non_disruptive(self, value): if not isinstance(value, bool): msg = f"{self.class_name}.{method_name}: " msg += "instance.force_non_disruptive must be a boolean." - self.ansible_module.fail_json(msg, **self.failed_result) - self.properties["force_non_disruptive"] = value + raise TypeError(msg) + self._force_non_disruptive = value @property def non_disruptive(self): @@ -895,7 +953,7 @@ def non_disruptive(self): Default: True """ - return self.properties.get("non_disruptive") + return self._non_disruptive @non_disruptive.setter def non_disruptive(self, value): @@ -903,8 +961,8 @@ def non_disruptive(self, value): if not isinstance(value, bool): msg = f"{self.class_name}.{method_name}: " msg += "instance.non_disruptive must be a boolean." - self.ansible_module.fail_json(msg, **self.failed_result) - self.properties["non_disruptive"] = value + raise TypeError(msg) + self._non_disruptive = value @property def package_install(self): @@ -913,7 +971,7 @@ def package_install(self): Default: False """ - return self.properties.get("package_install") + return self._package_install @package_install.setter def package_install(self, value): @@ -921,8 +979,8 @@ def package_install(self, value): if not isinstance(value, bool): msg = f"{self.class_name}.{method_name}: " msg += "instance.package_install must be a boolean." - self.ansible_module.fail_json(msg, **self.failed_result) - self.properties["package_install"] = value + raise TypeError(msg) + self._package_install = value @property def package_uninstall(self): @@ -931,7 +989,7 @@ def package_uninstall(self): Default: False """ - return self.properties.get("package_uninstall") + return self._package_uninstall @package_uninstall.setter def package_uninstall(self, value): @@ -939,8 +997,8 @@ def package_uninstall(self, value): if not isinstance(value, bool): msg = f"{self.class_name}.{method_name}: " msg += "instance.package_uninstall must be a boolean." - self.ansible_module.fail_json(msg, **self.failed_result) - self.properties["package_uninstall"] = value + raise TypeError(msg) + self._package_uninstall = value @property def reboot(self): @@ -949,7 +1007,7 @@ def reboot(self): Default: False """ - return self.properties.get("reboot") + return self._reboot @reboot.setter def reboot(self, value): @@ -957,8 +1015,8 @@ def reboot(self, value): if not isinstance(value, bool): msg = f"{self.class_name}.{method_name}: " msg += "instance.reboot must be a boolean." - self.ansible_module.fail_json(msg, **self.failed_result) - self.properties["reboot"] = value + raise TypeError(msg) + self._reboot = value @property def write_erase(self): @@ -967,7 +1025,7 @@ def write_erase(self): Default: False """ - return self.properties.get("write_erase") + return self._write_erase @write_erase.setter def write_erase(self, value): @@ -975,15 +1033,15 @@ def write_erase(self, value): if not isinstance(value, bool): msg = f"{self.class_name}.{method_name}: " msg += "instance.write_erase must be a boolean." - self.ansible_module.fail_json(msg, **self.failed_result) - self.properties["write_erase"] = value + raise TypeError(msg) + self._write_erase = value @property def check_interval(self): """ Return the image upgrade check interval in seconds """ - return self.properties.get("check_interval") + return self._check_interval @check_interval.setter def check_interval(self, value): @@ -993,17 +1051,17 @@ def check_interval(self, value): # isinstance(False, int) returns True, so we need first # to test for this and fail_json specifically for bool values. if isinstance(value, bool): - self.ansible_module.fail_json(msg, **self.failed_result) + raise TypeError(msg) if not isinstance(value, int): - self.ansible_module.fail_json(msg, **self.failed_result) - self.properties["check_interval"] = value + raise TypeError(msg) + self._check_interval = value @property def check_timeout(self): """ Return the image upgrade check timeout in seconds """ - return self.properties.get("check_timeout") + return self._check_timeout @check_timeout.setter def check_timeout(self, value): @@ -1013,7 +1071,7 @@ def check_timeout(self, value): # isinstance(False, int) returns True, so we need first # to test for this and fail_json specifically for bool values. if isinstance(value, bool): - self.ansible_module.fail_json(msg, **self.failed_result) + raise TypeError(msg) if not isinstance(value, int): - self.ansible_module.fail_json(msg, **self.failed_result) - self.properties["check_timeout"] = value + raise TypeError(msg) + self._check_timeout = value diff --git a/plugins/module_utils/image_upgrade/image_upgrade_common.py b/plugins/module_utils/image_upgrade/image_upgrade_common.py deleted file mode 100644 index d306bace7..000000000 --- a/plugins/module_utils/image_upgrade/image_upgrade_common.py +++ /dev/null @@ -1,458 +0,0 @@ -# -# 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 time import sleep - -# Using only for its failed_result property -from ansible_collections.cisco.dcnm.plugins.module_utils.image_upgrade.image_upgrade_task_result import \ - ImageUpgradeTaskResult -from ansible_collections.cisco.dcnm.plugins.module_utils.network.dcnm.dcnm import \ - dcnm_send - - -class ImageUpgradeCommon: - """ - Common methods used by the other image upgrade classes - - Usage (where module is an instance of AnsibleModule): - - class MyClass(ImageUpgradeCommon): - def __init__(self, module): - super().__init__(module) - ... - """ - - def __init__(self, ansible_module): - self.class_name = self.__class__.__name__ - self.ansible_module = ansible_module - self.check_mode = ansible_module.check_mode - - self.log = logging.getLogger(f"dcnm.{self.class_name}") - - msg = "ENTERED ImageUpgradeCommon() " - msg += f"check_mode: {self.check_mode}" - self.log.debug(msg) - - self.params = ansible_module.params - - self.properties = {} - self.properties["changed"] = False - self.properties["diff"] = [] - self.properties["failed"] = False - self.properties["response"] = [] - self.properties["response_current"] = {} - self.properties["response_data"] = [] - self.properties["result"] = [] - self.properties["result_current"] = {} - self.properties["send_interval"] = 5 - self.properties["timeout"] = 300 - self.properties["unit_test"] = False - - self.dcnm_send = dcnm_send - - def dcnm_send_with_retry(self, verb: str, path: str, payload=None): - """ - Call dcnm_send() with retries until successful response or timeout is exceeded. - - Properties read: - self.send_interval: interval between retries (set in ImageUpgradeCommon) - self.timeout: timeout in seconds (set in ImageUpgradeCommon) - verb: HTTP verb (set in the calling class's commit() method) - path: HTTP path (set in the calling class's commit() method) - payload: - - (optionally) passed directly to this function. - - Normally only used when verb is POST or PUT. - - Properties written: - self.properties["response"]: raw response from the controller - self.properties["result"]: result from self._handle_response() method - """ - caller = inspect.stack()[1][3] - try: - timeout = self.timeout - except AttributeError: - timeout = 300 - - success = False - msg = f"{caller}: Entering dcnm_send_with_retry loop. " - msg += f"timeout {timeout}, send_interval {self.send_interval}, " - msg += f"verb {verb}, path {path}" - self.log.debug(msg) - - # self.dcnm_send = dcnm_send - while timeout > 0 and success is False: - if payload is None: - msg = f"{caller}: Calling dcnm_send: verb {verb}, path {path}" - self.log.debug(msg) - response = self.dcnm_send(self.ansible_module, verb, path) - else: - msg = ( - f"{caller}: Calling dcnm_send: verb {verb}, path {path}, payload: " - ) - msg += f"{json.dumps(payload, indent=4, sort_keys=True)}" - self.log.debug(msg) - response = self.dcnm_send( - self.ansible_module, verb, path, data=json.dumps(payload) - ) - - self.response_current = copy.deepcopy(response) - self.result_current = self._handle_response(response, verb) - - success = self.result_current["success"] - - if success is False and self.unit_test is False: - sleep(self.send_interval) - timeout -= self.send_interval - - self.response = copy.deepcopy(response) - self.result = copy.deepcopy(self.result_current) - - msg = f"{caller}: Exiting dcnm_send_with_retry loop. success {success}. verb {verb}, path {path}." - self.log.debug(msg) - - msg = f"{caller}: self.response_current {json.dumps(self.response_current, indent=4, sort_keys=True)}" - self.log.debug(msg) - - msg = f"{caller}: self.response {json.dumps(self.response, indent=4, sort_keys=True)}" - self.log.debug(msg) - - msg = f"{caller}: self.result_current {json.dumps(self.result_current, indent=4, sort_keys=True)}" - self.log.debug(msg) - - msg = ( - f"{caller}: self.result {json.dumps(self.result, indent=4, sort_keys=True)}" - ) - self.log.debug(msg) - - 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 responses from the controller. - - Returns: dict() with the following keys: - - changed: - - True if changes were made to by the controller - - False otherwise - - success: - - False if RETURN_CODE != 200 or MESSAGE != "OK" - - 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 failed_result(self): - """ - Return a result for a failed task with no changes - """ - return ImageUpgradeTaskResult(self.ansible_module).failed_result - - @property - def changed(self): - """ - bool = whether we changed anything - """ - return self.properties["changed"] - - @changed.setter - def changed(self, value): - method_name = inspect.stack()[0][3] - if not isinstance(value, bool): - msg = f"{self.class_name}.{method_name}: " - msg += f"{method_name} must be a bool. Got {value}" - self.ansible_module.fail_json(msg) - self.properties["changed"] = value - - @property - def diff(self): - """ - List of dicts representing the changes made - """ - return self.properties["diff"] - - @diff.setter - def diff(self, value): - method_name = inspect.stack()[0][3] - if not isinstance(value, dict): - msg = f"{self.class_name}.{method_name}: " - msg += f"{method_name} must be a dict. Got {value}" - self.ansible_module.fail_json(msg) - self.properties["diff"].append(value) - - @property - def failed(self): - """ - bool = whether we failed or not - If True, this means we failed to make a change - If False, this means we succeeded in making a change - """ - return self.properties["failed"] - - @failed.setter - def failed(self, value): - method_name = inspect.stack()[0][3] - if not isinstance(value, bool): - msg = f"{self.class_name}.{method_name}: " - msg += f"{method_name} must be a bool. Got {value}" - self.ansible_module.fail_json(msg) - self.properties["failed"] = value - - @property - def response_current(self): - """ - Return the current POST response from the controller - instance.commit() must be called first. - - This is a dict of the current response from the controller. - """ - return self.properties.get("response_current") - - @response_current.setter - def response_current(self, value): - method_name = inspect.stack()[0][3] - if not isinstance(value, dict): - msg = f"{self.class_name}.{method_name}: " - msg += f"{method_name} must be a dict. " - msg += f"Got {value}." - self.ansible_module.fail_json(msg, **self.failed_result) - self.properties["response_current"] = value - - @property - def response(self): - """ - Return the aggregated POST response from the controller - instance.commit() must be called first. - - This is a list of responses from the controller. - """ - return self.properties.get("response") - - @response.setter - def response(self, value): - method_name = inspect.stack()[0][3] - if not isinstance(value, dict): - msg = f"{self.class_name}.{method_name}: " - msg += f"{method_name} must be a dict. " - msg += f"Got {value}." - self.ansible_module.fail_json(msg, **self.failed_result) - self.properties["response"].append(value) - - @property - def response_data(self): - """ - Return the contents of the DATA key within current_response. - """ - return self.properties.get("response_data") - - @response_data.setter - def response_data(self, value): - self.properties["response_data"].append(value) - - @property - def result(self): - """ - Return the aggregated result from the controller - instance.commit() must be called first. - - This is a list of results from the controller. - """ - return self.properties.get("result") - - @result.setter - def result(self, value): - method_name = inspect.stack()[0][3] - if not isinstance(value, dict): - msg = f"{self.class_name}.{method_name}: " - msg += f"{method_name} must be a dict. " - msg += f"Got {value}." - self.ansible_module.fail_json(msg, **self.failed_result) - self.properties["result"].append(value) - - @property - def result_current(self): - """ - Return the current result from the controller - instance.commit() must be called first. - - This is a dict containing the current result. - """ - return self.properties.get("result_current") - - @result_current.setter - def result_current(self, value): - method_name = inspect.stack()[0][3] - if not isinstance(value, dict): - msg = f"{self.class_name}.{method_name}: " - msg += f"{method_name} must be a dict. " - msg += f"Got {value}." - self.ansible_module.fail_json(msg, **self.failed_result) - self.properties["result_current"] = value - - @property - def send_interval(self): - """ - Send interval, in seconds, for retrying responses from the controller. - Valid values: int() - Default: 5 - """ - return self.properties.get("send_interval") - - @send_interval.setter - def send_interval(self, value): - method_name = inspect.stack()[0][3] - msg = f"{self.class_name}.{method_name}: " - msg += f"{method_name} must be an integer. Got {value}." - # isinstance(False, int) returns True, so we need first - # to test for this and fail_json specifically for bool values. - if isinstance(value, bool): - self.ansible_module.fail_json(msg, **self.failed_result) - if not isinstance(value, int): - self.ansible_module.fail_json(msg, **self.failed_result) - self.properties["send_interval"] = value - - @property - def timeout(self): - """ - Timeout, in seconds, for retrieving responses from the controller. - Valid values: int() - Default: 300 - """ - return self.properties.get("timeout") - - @timeout.setter - def timeout(self, value): - method_name = inspect.stack()[0][3] - msg = f"{self.class_name}.{method_name}: " - msg += f"{method_name} must be an integer. Got {value}." - # isinstance(False, int) returns True, so we need first - # to test for this and fail_json specifically for bool values. - if isinstance(value, bool): - self.ansible_module.fail_json(msg, **self.failed_result) - if not isinstance(value, int): - self.ansible_module.fail_json(msg, **self.failed_result) - self.properties["timeout"] = value - - @property - def unit_test(self): - """ - Is the class running under a unit test. - Set this to True in unit tests to speed the test up. - Default: False - """ - return self.properties.get("unit_test") - - @unit_test.setter - def unit_test(self, value): - method_name = inspect.stack()[0][3] - if not isinstance(value, bool): - msg = f"{self.class_name}.{method_name}: " - msg += f"{method_name} must be a bool(). Got {value}." - self.ansible_module.fail_json(msg, **self.failed_result) - self.properties["unit_test"] = value diff --git a/plugins/module_utils/image_upgrade/image_upgrade_task_result.py b/plugins/module_utils/image_upgrade/image_upgrade_task_result.py deleted file mode 100644 index f4ea1da32..000000000 --- a/plugins/module_utils/image_upgrade/image_upgrade_task_result.py +++ /dev/null @@ -1,382 +0,0 @@ -# -# 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" - -import copy -import inspect -import logging - - -class ImageUpgradeTaskResult: - """ - Storage for ImageUpgradeTask result - """ - - def __init__(self, ansible_module): - self.class_name = self.__class__.__name__ - self.ansible_module = ansible_module - self.check_mode = self.ansible_module.check_mode - - self.log = logging.getLogger(f"dcnm.{self.class_name}") - - msg = "ENTERED ImageUpgradeTaskResult(): " - msg += f"check_mode: {self.check_mode}" - self.log.debug(msg) - - # Used in did_anything_change() to determine if any diffs have been - # appended to the diff lists. - self.diff_properties = {} - self.diff_properties["diff_attach_policy"] = "attach_policy" - self.diff_properties["diff_detach_policy"] = "detach_policy" - self.diff_properties["diff_issu_status"] = "issu_status" - self.diff_properties["diff_stage"] = "stage" - self.diff_properties["diff_upgrade"] = "upgrade" - self.diff_properties["diff_validate"] = "validate" - # Used in failed_result() and module_result() to build the result dict() - self.response_properties = {} - self.response_properties["response_attach_policy"] = "attach_policy" - self.response_properties["response_detach_policy"] = "detach_policy" - self.response_properties["response_issu_status"] = "issu_status" - self.response_properties["response_stage"] = "stage" - self.response_properties["response_upgrade"] = "upgrade" - self.response_properties["response_validate"] = "validate" - - self._build_properties() - - def _build_properties(self): - """ - Build the properties dict() with default values - """ - self.properties = {} - self.properties["diff"] = [] - self.properties["diff_attach_policy"] = [] - self.properties["diff_detach_policy"] = [] - self.properties["diff_issu_status"] = [] - self.properties["diff_stage"] = [] - self.properties["diff_upgrade"] = [] - self.properties["diff_validate"] = [] - - self.properties["response"] = [] - self.properties["response_attach_policy"] = [] - self.properties["response_issu_status"] = [] - self.properties["response_detach_policy"] = [] - self.properties["response_stage"] = [] - self.properties["response_upgrade"] = [] - self.properties["response_validate"] = [] - - def did_anything_change(self): - """ - return True if diffs have been appended to any of the diff lists. - """ - if self.check_mode is True: - self.log.debug("check_mode is True. No changes made.") - return False - for key in self.diff_properties: - # skip query state diffs - if key == "diff_issu_status": - continue - if len(self.properties[key]) != 0: - return True - return False - - def _verify_is_dict(self, value): - method_name = inspect.stack()[0][3] - if not isinstance(value, dict): - msg = f"{self.class_name}.{method_name}: " - msg += "value must be a dict. " - msg += f"got {type(value).__name__} for " - msg += f"value {value}" - self.ansible_module.fail_json(msg, **self.failed_result) - - @property - def failed_result(self): - """ - return a result for a failed task with no changes - """ - result = {} - result["changed"] = False - result["failed"] = True - result["diff"] = {} - result["response"] = {} - for key in self.diff_properties: - result["diff"][key] = [] - for key in self.response_properties: - result["response"][key] = [] - return result - - @property - def module_result_orig(self): - """ - return a result that AnsibleModule can use - """ - result = {} - result["changed"] = self.did_anything_change() - result["diff"] = {} - result["response"] = {} - for key, diff_key in self.diff_properties.items(): - result["diff"][diff_key] = self.properties[key] - for key, response_key in self.response_properties.items(): - result["response"][response_key] = self.properties[key] - return result - - @property - def module_result(self): - """ - return a result that AnsibleModule can use - """ - result = {} - result["changed"] = self.did_anything_change() - result["diff"] = {} - result["response"] = {} - result["diff"] = copy.deepcopy(self.diff) - result["response"] = copy.deepcopy(self.response) - return result - - # diff properties - @property - def diff(self): - """ - Getter for diff property - - Used for all diffs - """ - return self.properties["diff"] - - @diff.setter - def diff(self, value): - """ - Setter for diff property - """ - self._verify_is_dict(value) - self.properties["diff"].append(value) - - @property - def diff_attach_policy(self): - """ - Getter for diff_attach_policy property - - Used for merged state where we attach image policies - to devices. - """ - return self.properties["diff_attach_policy"] - - @diff_attach_policy.setter - def diff_attach_policy(self, value): - """ - Setter for diff_attach_policy property - """ - self._verify_is_dict(value) - self.properties["diff_attach_policy"].append(value) - - @property - def diff_detach_policy(self): - """ - Getter for diff_detach_policy property - - This is used for deleted state where we detach image policies - from devices. - """ - return self.properties["diff_detach_policy"] - - @diff_detach_policy.setter - def diff_detach_policy(self, value): - """ - Setter for diff_detach_policy property - """ - self._verify_is_dict(value) - self.properties["diff_detach_policy"].append(value) - - @property - def diff_issu_status(self): - """ - Getter for diff_issu_status property - - This is used query state diffs of switch issu state - """ - return self.properties["diff_issu_status"] - - @diff_issu_status.setter - def diff_issu_status(self, value): - """ - Setter for diff_issu_status property - """ - self._verify_is_dict(value) - self.properties["diff_issu_status"].append(value) - - @property - def diff_stage(self): - """ - Getter for diff_stage property - """ - return self.properties["diff_stage"] - - @diff_stage.setter - def diff_stage(self, value): - """ - Setter for diff_stage property - """ - self._verify_is_dict(value) - self.properties["diff_stage"].append(value) - - @property - def diff_upgrade(self): - """ - Getter for diff_upgrade property - """ - return self.properties["diff_upgrade"] - - @diff_upgrade.setter - def diff_upgrade(self, value): - """ - Setter for diff_upgrade property - """ - self._verify_is_dict(value) - self.properties["diff_upgrade"].append(value) - - @property - def diff_validate(self): - """ - Getter for diff_validate property - """ - return self.properties["diff_validate"] - - @diff_validate.setter - def diff_validate(self, value): - """ - Setter for diff_validate property - """ - self._verify_is_dict(value) - self.properties["diff_validate"].append(value) - - # response properties - @property - def response(self): - """ - Getter for response property - - Used for all responses - """ - return self.properties["response"] - - @response.setter - def response(self, value): - """ - Setter for response_attach_policy property - """ - self._verify_is_dict(value) - self.properties["response"].append(value) - - @property - def response_attach_policy(self): - """ - Getter for response_attach_policy property - - Used for merged state where we attach image policies - to devices. - """ - return self.properties["response_attach_policy"] - - @response_attach_policy.setter - def response_attach_policy(self, value): - """ - Setter for response_attach_policy property - """ - self._verify_is_dict(value) - self.properties["response_attach_policy"].append(value) - - @property - def response_detach_policy(self): - """ - Getter for response_detach_policy property - - This is used for deleted state where we detach image policies - from devices. - """ - return self.properties["response_detach_policy"] - - @response_detach_policy.setter - def response_detach_policy(self, value): - """ - Setter for response_detach_policy property - """ - self._verify_is_dict(value) - self.properties["response_detach_policy"].append(value) - - @property - def response_issu_status(self): - """ - Getter for response_issu_status property - - This is used for deleted state where we detach image policies - from devices. - """ - return self.properties["response_issu_status"] - - @response_issu_status.setter - def response_issu_status(self, value): - """ - Setter for response_issu_status property - """ - self._verify_is_dict(value) - self.properties["response_issu_status"].append(value) - - @property - def response_stage(self): - """ - Getter for response_stage property - """ - return self.properties["response_stage"] - - @response_stage.setter - def response_stage(self, value): - """ - Setter for response_stage property - """ - self._verify_is_dict(value) - self.properties["response_stage"].append(value) - - @property - def response_upgrade(self): - """ - Getter for response_upgrade property - """ - return self.properties["response_upgrade"] - - @response_upgrade.setter - def response_upgrade(self, value): - """ - Setter for response_upgrade property - """ - self._verify_is_dict(value) - self.properties["response_upgrade"].append(value) - - @property - def response_validate(self): - """ - Getter for response_validate property - """ - return self.properties["response_validate"] - - @response_validate.setter - def response_validate(self, value): - """ - Setter for response_validate property - """ - self._verify_is_dict(value) - self.properties["response_validate"].append(value) diff --git a/plugins/module_utils/image_upgrade/image_validate.py b/plugins/module_utils/image_upgrade/image_validate.py index 908d6a2ac..00f9fe2f9 100644 --- a/plugins/module_utils/image_upgrade/image_validate.py +++ b/plugins/module_utils/image_upgrade/image_validate.py @@ -23,88 +23,161 @@ import json import logging from time import sleep -from typing import List, Set - -from ansible_collections.cisco.dcnm.plugins.module_utils.common.rest_send import \ - RestSend -from ansible_collections.cisco.dcnm.plugins.module_utils.image_upgrade.api_endpoints import \ - ApiEndpoints -from ansible_collections.cisco.dcnm.plugins.module_utils.image_upgrade.image_upgrade_common import \ - ImageUpgradeCommon + +from ansible_collections.cisco.dcnm.plugins.module_utils.common.api.v1.imagemanagement.rest.stagingmanagement.stagingmanagement import \ + EpImageValidate +from ansible_collections.cisco.dcnm.plugins.module_utils.common.conversion import \ + ConversionUtils +from ansible_collections.cisco.dcnm.plugins.module_utils.common.exceptions import \ + ControllerResponseError +from ansible_collections.cisco.dcnm.plugins.module_utils.common.properties import \ + Properties +from ansible_collections.cisco.dcnm.plugins.module_utils.common.results import \ + Results from ansible_collections.cisco.dcnm.plugins.module_utils.image_upgrade.switch_issu_details import \ SwitchIssuDetailsBySerialNumber +from ansible_collections.cisco.dcnm.plugins.module_utils.image_upgrade.wait_for_controller_done import \ + WaitForControllerDone -class ImageValidate(ImageUpgradeCommon): +@Properties.add_rest_send +@Properties.add_results +class ImageValidate: """ - Endpoint: - /appcenter/cisco/ndfc/api/v1/imagemanagement/rest/stagingmanagement/validate-image - - Verb: POST - - Usage (where module is an instance of AnsibleModule): - - instance = ImageValidate(module) + ### Summary + Validate an image on a switch. + + ### Endpoint + - path: /appcenter/cisco/ndfc/api/v1/imagemanagement/rest/stagingmanagement/validate-image + - verb: POST + + ### Usage example + + ```python + # params is typically obtained from ansible_module.params + # but can also be specified manually, like below. + params = {"check_mode": False, "state": "merged"} + sender = Sender() + sender.ansible_module = ansible_module + rest_send = RestSend(params) + rest_send.response_handler = ResponseHandler() + rest_send.sender = sender + results = Results() + + instance = ImageValidate() + # mandatory parameters + instance.rest_send = rest_send + instance.results = results instance.serial_numbers = ["FDO211218HH", "FDO211218GC"] - # non_disruptive is optional - instance.non_disruptive = True instance.commit() - data = instance.response_data + ``` - Request body: + ### Request body + ```json { "serialNum": ["FDO21120U5D"], "nonDisruptive":"true" } + ``` - Response body when nonDisruptive is True: - [StageResponse [key=success, value=]] + ### Response body when nonDisruptive is True: + ``` + [StageResponse [key=success, value=]] + ``` - Response body when nonDisruptive is False: - [StageResponse [key=success, value=]] + ### Response body when nonDisruptive is False: + ``` + [StageResponse [key=success, value=]] + ``` - The response is not JSON, nor is it very useful. - Instead, we poll for validation status using - SwitchIssuDetailsBySerialNumber. + The response is not JSON, nor is it very useful. Instead, we poll for + validation status using ``SwitchIssuDetailsBySerialNumber``. """ - def __init__(self, ansible_module): - super().__init__(ansible_module) + def __init__(self): self.class_name = self.__class__.__name__ + method_name = inspect.stack()[0][3] self.log = logging.getLogger(f"dcnm.{self.class_name}") - msg = "ENTERED ImageValidate() " - msg += f"check_mode {self.check_mode}" + + self.action = "image_validate" + self.diff: dict = {} + self.payload = None + self.saved_response_current: dict = {} + self.saved_result_current: dict = {} + # _wait_for_image_validate_to_complete() populates these + self.serial_numbers_done: set = set() + self.serial_numbers_todo = set() + + self.conversion = ConversionUtils() + self.ep_image_validate = EpImageValidate() + self.issu_detail = SwitchIssuDetailsBySerialNumber() + self.wait_for_controller_done = WaitForControllerDone() + + self._check_interval = 10 # seconds + self._check_timeout = 1800 # seconds + self._non_disruptive = False + self._rest_send = None + self._results = None + self._serial_numbers = None + + msg = f"ENTERED {self.class_name}().{method_name}" self.log.debug(msg) - self.endpoints = ApiEndpoints() - self.rest_send = RestSend(self.ansible_module) + def build_diff(self) -> None: + """ + ### Summary + Build the diff of the image validate operation. - self.path = self.endpoints.image_validate.get("path") - self.verb = self.endpoints.image_validate.get("verb") - self.payload = {} - self.serial_numbers_done: Set[str] = set() + ### Raises + None + """ + method_name = inspect.stack()[0][3] - self._init_properties() - self.issu_detail = SwitchIssuDetailsBySerialNumber(self.ansible_module) + msg = f"ENTERED {self.class_name}.{method_name}" + self.log.debug(msg) + + self.diff: dict = {} + + for serial_number in self.serial_numbers_done: + self.issu_detail.filter = serial_number + ipv4 = self.issu_detail.ip_address - def _init_properties(self) -> None: + if ipv4 not in self.diff: + self.diff[ipv4] = {} + + self.diff[ipv4]["action"] = self.action + self.diff[ipv4]["ip_address"] = self.issu_detail.ip_address + self.diff[ipv4]["logical_name"] = self.issu_detail.device_name + self.diff[ipv4]["policy_name"] = self.issu_detail.policy + self.diff[ipv4]["serial_number"] = serial_number + msg = f"{self.class_name}.{method_name}: " + msg += f"self.diff[{ipv4}]: " + msg += f"{json.dumps(self.diff[ipv4], indent=4)}" + self.log.debug(msg) + + def build_payload(self) -> None: """ - Initialize the properties dictionary + Build the payload for the image validation request """ + method_name = inspect.stack()[0][3] + msg = f"ENTERED {self.class_name}.{method_name}: " + msg += f"self.serial_numbers: {self.serial_numbers}" + self.log.debug(msg) - # self.properties is already initialized in the parent class - self.properties["check_interval"] = 10 # seconds - self.properties["check_timeout"] = 1800 # seconds - self.properties["non_disruptive"] = False - self.properties["serial_numbers"] = [] + self.payload = {} + self.payload["serialNum"] = self.serial_numbers + self.payload["nonDisruptive"] = self.non_disruptive def prune_serial_numbers(self) -> None: """ If the image is already validated on a switch, remove that switch's serial number from the list of serial numbers to validate. """ - msg = f"ENTERED: self.serial_numbers {self.serial_numbers}" + method_name = inspect.stack()[0][3] + + msg = f"ENTERED: {self.class_name}.{method_name}: " + msg += f"self.serial_numbers {self.serial_numbers}" self.log.debug(msg) self.issu_detail.refresh() @@ -117,279 +190,222 @@ def prune_serial_numbers(self) -> None: msg = f"DONE: self.serial_numbers {self.serial_numbers}" self.log.debug(msg) + def register_unchanged_result(self, response_message) -> None: + """ + ### Summary + Register a successful unchanged result with the results object. + """ + # pylint: disable=no-member + method_name = inspect.stack()[0][3] + + msg = f"ENTERED {self.class_name}().{method_name}" + self.log.debug(msg) + + self.results.action = self.action + self.results.diff_current = {} + self.results.response_current = {"response": response_message} + self.results.result_current = {"success": True, "changed": False} + self.results.response_data = {"response": response_message} + self.results.register_task_result() + def validate_serial_numbers(self) -> None: """ - Log a warning if the validated state for any serial_number - is Failed. - - TODO:1 Need a way to compare current image_policy with the image - policy in the response - TODO:3 If validate == Failed, it may have been from the last operation. - TODO:3 We can't fail here based on this until we can verify the failure - is happening for the current image_policy. - TODO:3 Change this to a log message and update the unit test if we can't - verify the failure is happening for the current image_policy. + ### Summary + Fail if "validated" is "Failed" for any serial number. + + ### Raises + - ``ControllerResponseError`` if: + - "validated" is "Failed" for any serial_number. """ - self.method_name = inspect.stack()[0][3] + method_name = inspect.stack()[0][3] + + msg = f"ENTERED {self.class_name}.{method_name}: " + msg += f"self.serial_numbers: {self.serial_numbers}" + self.log.debug(msg) self.issu_detail.refresh() for serial_number in self.serial_numbers: self.issu_detail.filter = serial_number if self.issu_detail.validated == "Failed": - msg = f"{self.class_name}.{self.method_name}: " + msg = f"{self.class_name}.{method_name}: " msg += "image validation is failing for the following switch: " msg += f"{self.issu_detail.device_name}, " msg += f"{self.issu_detail.ip_address}, " msg += f"{self.issu_detail.serial_number}. " msg += "If this persists, check the switch connectivity to " msg += "the controller and try again." - self.ansible_module.fail_json(msg, **self.failed_result) + raise ControllerResponseError(msg) - def build_payload(self) -> None: + def validate_commit_parameters(self) -> None: """ - Build the payload for the image validation request - """ - self.method_name = inspect.stack()[0][3] - - self.payload = {} - self.payload["serialNum"] = self.serial_numbers - self.payload["nonDisruptive"] = self.non_disruptive - - def commit(self) -> None: - if self.check_mode is True: - self.commit_check_mode() - else: - self.commit_normal_mode() - - def commit_check_mode(self) -> None: - """ - Simulate a commit of the image validation request to the - controller. + ### Summary + Verify mandatory parameters are set before calling commit. + + ### Raises + - ``ValueError`` if: + - ``rest_send`` is not set. + - ``results`` is not set. + - ``serial_numbers`` is not set. """ method_name = inspect.stack()[0][3] - - msg = f"ENTERED: self.serial_numbers: {self.serial_numbers}" - self.log.debug(msg) - - if len(self.serial_numbers) == 0: - msg = "No serial numbers to validate." - self.response_current = {"response": msg} - self.result_current = {"success": True} - self.response_data = {"response": msg} - self.response = self.response_current - self.result = self.result_current - return - - self.prune_serial_numbers() - self.validate_serial_numbers() - - self.build_payload() - self.rest_send.verb = self.verb - self.rest_send.path = self.path - self.rest_send.payload = self.payload - - self.rest_send.check_mode = True - - self.rest_send.commit() - - self.response_current = {} - self.response_current["DATA"] = "[simulated-check-mode-response:Success]" - self.response_current["MESSAGE"] = "OK" - self.response_current["METHOD"] = self.verb - self.response_current["REQUEST_PATH"] = self.path - self.response_current["RETURN_CODE"] = 200 - self.response = copy.deepcopy(self.response_current) - - self.response_data = self.response_current.get("DATA") - - self.result_current = self.rest_send._handle_response(self.response_current) - self.result = copy.deepcopy(self.result_current) - - msg = "self.payload: " - msg += f"{json.dumps(self.payload, indent=4, sort_keys=True)}" + msg = f"ENTERED {self.class_name}.{method_name}" self.log.debug(msg) - msg = "self.response: " - msg += f"{json.dumps(self.response, indent=4, sort_keys=True)}" - self.log.debug(msg) - - msg = "self.response_current: " - msg += f"{json.dumps(self.response_current, indent=4, sort_keys=True)}" - self.log.debug(msg) - - msg = "self.response_data: " - msg += f"{self.response_data}" - self.log.debug(msg) - - msg = "self.result: " - msg += f"{json.dumps(self.result, indent=4, sort_keys=True)}" - self.log.debug(msg) - - msg = "self.result_current: " - msg += f"{json.dumps(self.result_current, indent=4, sort_keys=True)}" - self.log.debug(msg) - - if not self.result_current["success"]: + # pylint: disable=no-member + if self.rest_send is None: msg = f"{self.class_name}.{method_name}: " - msg += f"failed: {self.result_current}. " - msg += f"Controller response: {self.response_current}" - self.ansible_module.fail_json(msg, **self.failed_result) - - for serial_number in self.serial_numbers: - self.issu_detail.filter = serial_number - diff = {} - diff["action"] = "validate" - diff["ip_address"] = self.issu_detail.ip_address - diff["logical_name"] = self.issu_detail.device_name - diff["policy"] = self.issu_detail.policy - diff["serial_number"] = serial_number - # See image_upgrade_common.py for the definition of self.diff - self.diff = copy.deepcopy(diff) - - msg = "self.diff: " - msg += f"{json.dumps(self.diff, indent=4, sort_keys=True)}" - self.log.debug(msg) + msg += "rest_send must be set before calling commit()." + raise ValueError(msg) + if self.results is None: + msg = f"{self.class_name}.{method_name}: " + msg += "results must be set before calling commit()." + raise ValueError(msg) + # pylint: enable=no-member + if self.serial_numbers is None: + msg = f"{self.class_name}.{method_name}: " + msg += "serial_numbers must be set before calling commit()." + raise ValueError(msg) - def commit_normal_mode(self) -> None: + def commit(self) -> None: """ + ### Summary Commit the image validation request to the controller and wait for the images to be validated. + + ### Raises + - ``ValueError`` if: + - ``rest_send`` is not set. + - ``results`` is not set. + - ``serial_numbers`` is not set. + - ``ControllerResponseError`` if: + - The controller response is unsuccessful. """ method_name = inspect.stack()[0][3] - msg = f"ENTERED: self.serial_numbers: {self.serial_numbers}" + msg = f"{self.class_name}.{method_name}: " + msg += f"self.serial_numbers: {self.serial_numbers}" self.log.debug(msg) + self.validate_commit_parameters() + if len(self.serial_numbers) == 0: - msg = "No serial numbers to validate." - self.response_current = {"response": msg} - self.result_current = {"success": True} - self.response_data = {"response": msg} - self.response = self.response_current - self.result = self.result_current + msg = "No images to validate." + self.register_unchanged_result(msg) return + # pylint: disable=no-member + self.issu_detail.rest_send = self.rest_send + # pylint: enable=no-member + # We don't want the results to show up in the user's result output. + self.issu_detail.results = Results() + self.prune_serial_numbers() self.validate_serial_numbers() - self._wait_for_current_actions_to_complete() - + self.wait_for_controller() self.build_payload() - self.rest_send.verb = self.verb - self.rest_send.path = self.path - self.rest_send.payload = self.payload - - if self.check_mode is True: - self.rest_send.check_mode = True - else: - self.rest_send.check_mode = False - - self.rest_send.commit() - - msg = f"self.rest_send.response_current: {self.rest_send.response_current}" - self.log.debug(msg) - - self.response_current = copy.deepcopy(self.rest_send.response_current) - self.response = copy.deepcopy(self.rest_send.response_current) - self.response_data = self.response_current.get("DATA", "No Stage DATA") - - self.result_current = copy.deepcopy(self.rest_send.result_current) - self.result = copy.deepcopy(self.rest_send.result_current) - - msg = "self.payload: " - msg += f"{json.dumps(self.payload, indent=4, sort_keys=True)}" - self.log.debug(msg) - - msg = "self.response: " - msg += f"{json.dumps(self.response, indent=4, sort_keys=True)}" - self.log.debug(msg) - msg = "self.response_current: " - msg += f"{json.dumps(self.response_current, indent=4, sort_keys=True)}" - self.log.debug(msg) - - msg = "self.response_data: " - msg += f"{self.response_data}" - self.log.debug(msg) - - msg = "self.result: " - msg += f"{json.dumps(self.result, indent=4, sort_keys=True)}" - self.log.debug(msg) - - msg = "self.result_current: " - msg += f"{json.dumps(self.result_current, indent=4, sort_keys=True)}" + msg = f"{self.class_name}.{method_name}: " + msg += "Calling RestSend().commit()" self.log.debug(msg) - if not self.result_current["success"]: + # pylint: disable=no-member + try: + self.rest_send.verb = self.ep_image_validate.verb + self.rest_send.path = self.ep_image_validate.path + self.rest_send.payload = self.payload + self.rest_send.commit() + except (TypeError, ValueError) as error: + self.results.diff_current = {} + self.results.action = self.action + 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() + msg = f"{self.class_name}.{method_name}: " + msg += "Error while sending request. " + msg += f"Error detail: {error}" + raise ValueError(msg) from error + + if not self.rest_send.result_current["success"]: + self.results.diff_current = {} + self.results.action = self.action + 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() msg = f"{self.class_name}.{method_name}: " - msg += f"failed: {self.result_current}. " - msg += f"Controller response: {self.response_current}" - self.ansible_module.fail_json(msg, **self.failed_result) + msg += "failed. " + msg += f"Controller response: {self.rest_send.response_current}" + raise ControllerResponseError(msg) + + # Save response_current and result_current so they aren't overwritten + # by _wait_for_image_validate_to_complete(), which needs to run + # before we can build the diff, since the diff is based on the + # serial_numbers_done set, which isn't populated until image + # validate is complete. + self.saved_response_current = copy.deepcopy(self.rest_send.response_current) + self.saved_result_current = copy.deepcopy(self.rest_send.result_current) - self.properties["response_data"] = self.response self._wait_for_image_validate_to_complete() - for serial_number in self.serial_numbers_done: - self.issu_detail.filter = serial_number - diff = {} - diff["action"] = "validate" - diff["ip_address"] = self.issu_detail.ip_address - diff["logical_name"] = self.issu_detail.device_name - diff["policy"] = self.issu_detail.policy - diff["serial_number"] = serial_number - # See image_upgrade_common.py for the definition of self.diff - self.diff = copy.deepcopy(diff) - msg = f"self.diff: {json.dumps(self.diff, indent=4, sort_keys=True)}" - self.log.debug(msg) + self.build_diff() + self.results.action = self.action + self.results.diff_current = copy.deepcopy(self.diff) + self.results.response_current = copy.deepcopy(self.saved_response_current) + self.results.result_current = copy.deepcopy(self.saved_result_current) + self.results.register_task_result() - def _wait_for_current_actions_to_complete(self) -> None: + def wait_for_controller(self): """ - The controller will not validate an image if there are any actions in - progress. Wait for all actions to complete before validating image. - Actions include image staging, image upgrade, and image validation. + ### Summary + Wait for any actions on the controller to complete. + + ### Raises + - ValueError: if: + - ``items`` is not a set. + - ``item_type`` is not a valid item type. + - The action times out. """ - self.method_name = inspect.stack()[0][3] - - if self.unit_test is False: - self.serial_numbers_done: Set[str] = set() - serial_numbers_todo = set(copy.copy(self.serial_numbers)) - timeout = self.check_timeout - - while self.serial_numbers_done != serial_numbers_todo and timeout > 0: - if self.unit_test is False: - sleep(self.check_interval) - timeout -= self.check_interval - self.issu_detail.refresh() - - for serial_number in self.serial_numbers: - if serial_number in self.serial_numbers_done: - continue - - self.issu_detail.filter = serial_number + method_name = inspect.stack()[0][3] - if self.issu_detail.actions_in_progress is False: - self.serial_numbers_done.add(serial_number) + msg = f"ENTERED {self.class_name}().{method_name}" + self.log.debug(msg) - if self.serial_numbers_done != serial_numbers_todo: - msg = f"{self.class_name}.{self.method_name}: " - msg += "Timed out waiting for actions to complete. " - msg += "serial_numbers_done: " - msg += f"{','.join(sorted(self.serial_numbers_done))}, " - msg += "serial_numbers_todo: " - msg += f"{','.join(sorted(serial_numbers_todo))}" - self.ansible_module.fail_json(msg, **self.failed_result) + try: + self.wait_for_controller_done.items = set(copy.copy(self.serial_numbers)) + self.wait_for_controller_done.item_type = "serial_number" + self.wait_for_controller_done.rest_send = ( + self.rest_send # pylint: disable=no-member + ) + self.wait_for_controller_done.commit() + except (TypeError, ValueError) as error: + msg = f"{self.class_name}.{method_name}: " + msg += f"Error {error}." + raise ValueError(msg) from error def _wait_for_image_validate_to_complete(self) -> None: """ - Wait for image validation to complete + ### Summary + Wait for image validation to complete. + + ### Raises + - ``ValueError`` if: + - The image validation does not complete within the timeout. + - The image validation fails. """ - self.method_name = inspect.stack()[0][3] + method_name = inspect.stack()[0][3] + + msg = f"ENTERED {self.class_name}.{method_name}" + self.log.debug(msg) self.serial_numbers_done = set() timeout = self.check_timeout - serial_numbers_todo = set(copy.copy(self.serial_numbers)) + self.serial_numbers_todo = set(copy.copy(self.serial_numbers)) - while self.serial_numbers_done != serial_numbers_todo and timeout > 0: - if self.unit_test is False: + while self.serial_numbers_done != self.serial_numbers_todo and timeout > 0: + if self.rest_send.unit_test is False: # pylint: disable=no-member sleep(self.check_interval) timeout -= self.check_interval self.issu_detail.refresh() @@ -406,7 +422,7 @@ def _wait_for_image_validate_to_complete(self) -> None: validated_status = self.issu_detail.validated if validated_status == "Failed": - msg = f"{self.class_name}.{self.method_name}: " + msg = f"{self.class_name}.{method_name}: " msg = f"Seconds remaining {timeout}: validate image " msg += f"{validated_status} for " msg += f"{device_name}, {ip_address}, {serial_number}, " @@ -416,108 +432,145 @@ def _wait_for_image_validate_to_complete(self) -> None: msg += "check Operations > Image Management > " msg += "Devices > View Details > Validate on the " msg += "controller GUI for more details." - self.ansible_module.fail_json(msg, **self.failed_result) - + raise ValueError(msg) if validated_status == "Success": self.serial_numbers_done.add(serial_number) - msg = f"seconds remaining {timeout}" - self.log.debug(msg) - msg = f"serial_numbers_todo: {sorted(serial_numbers_todo)}" - self.log.debug(msg) - msg = f"serial_numbers_done: {sorted(self.serial_numbers_done)}" - self.log.debug(msg) - - if self.serial_numbers_done != serial_numbers_todo: - msg = f"{self.class_name}.{self.method_name}: " + + msg = f"seconds remaining {timeout}" + self.log.debug(msg) + msg = f"serial_numbers_todo: {sorted(self.serial_numbers_todo)}" + self.log.debug(msg) + msg = f"serial_numbers_done: {sorted(self.serial_numbers_done)}" + self.log.debug(msg) + + msg = f"{self.class_name}.{method_name}: " + msg += "Completed. " + msg += f"serial_numbers_done: {sorted(self.serial_numbers_done)}." + self.log.debug(msg) + + if self.serial_numbers_done != self.serial_numbers_todo: + msg = f"{self.class_name}.{method_name}: " msg += "Timed out waiting for image validation to complete. " msg += "serial_numbers_done: " msg += f"{','.join(sorted(self.serial_numbers_done))}, " msg += "serial_numbers_todo: " - msg += f"{','.join(sorted(serial_numbers_todo))}" - self.ansible_module.fail_json(msg, **self.failed_result) + msg += f"{','.join(sorted(self.serial_numbers_todo))}" + raise ValueError(msg) @property - def serial_numbers(self) -> List[str]: + def response_data(self) -> dict: """ - Set the serial numbers of the switches to stage. + ### Summary + Return the DATA key of the controller response. + Obtained from self.rest_send.response_current. - This must be set before calling instance.commit() + commit must be called before accessing this property. """ - return self.properties.get("serial_numbers", []) + # pylint: disable=no-member + return self.rest_send.response_current.get("DATA") - @serial_numbers.setter - def serial_numbers(self, value: List[str]): - self.method_name = inspect.stack()[0][3] + @property + def serial_numbers(self) -> list: + """ + ### Summary + A ``list`` of switch serial numbers. The image will be validated on + each switch in the list. - if not isinstance(value, list): - msg = f"{self.class_name}.{self.method_name}: " - msg += "instance.serial_numbers must be a " - msg += "python list of switch serial numbers. " - msg += f"Got {value}." - self.ansible_module.fail_json(msg, **self.failed_result) + ``serial_numbers`` must be set before calling commit. - self.properties["serial_numbers"] = value + ### Raises + - ``TypeError`` if value is not a list of serial numbers. + """ + return self._serial_numbers + + @serial_numbers.setter + def serial_numbers(self, value) -> None: + method_name = inspect.stack()[0][3] + if not isinstance(value, list): + msg = f"{self.class_name}.{method_name}: " + msg += "must be a python list of switch serial numbers." + raise TypeError(msg) + for item in value: + if not isinstance(item, str): + msg = f"{self.class_name}.{method_name}: " + msg += "must be a python list of switch serial numbers." + raise TypeError(msg) + self._serial_numbers = value @property - def non_disruptive(self): + def non_disruptive(self) -> bool: """ + ### Summary Set the non_disruptive flag to True or False. + + ### Raises + - ``TypeError`` if the value is not a boolean. """ - return self.properties.get("non_disruptive") + return self._non_disruptive @non_disruptive.setter - def non_disruptive(self, value): - self.method_name = inspect.stack()[0][3] + def non_disruptive(self, value) -> None: + method_name = inspect.stack()[0][3] - value = self.make_boolean(value) + value = self.conversion.make_boolean(value) if not isinstance(value, bool): - msg = f"{self.class_name}.{self.method_name}: " + msg = f"{self.class_name}.{method_name}: " msg += "instance.non_disruptive must be a boolean. " msg += f"Got {value}." - self.ansible_module.fail_json(msg, **self.failed_result) + raise TypeError(msg) - self.properties["non_disruptive"] = value + self._non_disruptive = value @property - def check_interval(self): + def check_interval(self) -> int: """ - Return the validate check interval in seconds + ### Summary + The validate check interval, in seconds. + + ### Raises + - ``TypeError`` if the value is not an integer. + - ``ValueError`` if the value is less than zero. """ - return self.properties.get("check_interval") + return self._check_interval @check_interval.setter - def check_interval(self, value): + def check_interval(self, value) -> None: method_name = inspect.stack()[0][3] msg = f"{self.class_name}.{method_name}: " msg += "must be a positive integer or zero. " msg += f"Got value {value} of type {type(value)}." # isinstance(True, int) is True so we need to check for bool first if isinstance(value, bool): - self.ansible_module.fail_json(msg, **self.failed_result) + raise TypeError(msg) if not isinstance(value, int): - self.ansible_module.fail_json(msg, **self.failed_result) + raise TypeError(msg) if value < 0: - self.ansible_module.fail_json(msg, **self.failed_result) - self.properties["check_interval"] = value + raise ValueError(msg) + self._check_interval = value @property - def check_timeout(self): + def check_timeout(self) -> int: """ - Return the validate check timeout in seconds + ### Summary + The validate check timeout, in seconds. + + ### Raises + - ``TypeError`` if the value is not an integer. + - ``ValueError`` if the value is less than zero. """ - return self.properties.get("check_timeout") + return self._check_timeout @check_timeout.setter - def check_timeout(self, value): + def check_timeout(self, value) -> None: method_name = inspect.stack()[0][3] msg = f"{self.class_name}.{method_name}: " msg += "must be a positive integer or zero. " msg += f"Got value {value} of type {type(value)}." # isinstance(True, int) is True so we need to check for bool first if isinstance(value, bool): - self.ansible_module.fail_json(msg, **self.failed_result) + raise TypeError(msg) if not isinstance(value, int): - self.ansible_module.fail_json(msg, **self.failed_result) + raise TypeError(msg) if value < 0: - self.ansible_module.fail_json(msg, **self.failed_result) - self.properties["check_timeout"] = value + raise ValueError(msg) + self._check_timeout = value diff --git a/plugins/module_utils/image_upgrade/install_options.py b/plugins/module_utils/image_upgrade/install_options.py index b129bac69..3f620fd43 100644 --- a/plugins/module_utils/image_upgrade/install_options.py +++ b/plugins/module_utils/image_upgrade/install_options.py @@ -18,35 +18,40 @@ __metaclass__ = type __author__ = "Allen Robel" -import copy import inspect import json import logging -import time -from typing import Any, Dict -from ansible_collections.cisco.dcnm.plugins.module_utils.image_upgrade.api_endpoints import \ - ApiEndpoints -from ansible_collections.cisco.dcnm.plugins.module_utils.image_upgrade.image_upgrade_common import \ - ImageUpgradeCommon -from ansible_collections.cisco.dcnm.plugins.module_utils.network.dcnm.dcnm import \ - dcnm_send +from ansible_collections.cisco.dcnm.plugins.module_utils.common.api.v1.imagemanagement.rest.imageupgrade.imageupgrade import \ + EpInstallOptions +from ansible_collections.cisco.dcnm.plugins.module_utils.common.conversion import \ + ConversionUtils +from ansible_collections.cisco.dcnm.plugins.module_utils.common.exceptions import \ + ControllerResponseError +from ansible_collections.cisco.dcnm.plugins.module_utils.common.properties import \ + Properties -class ImageInstallOptions(ImageUpgradeCommon): +@Properties.add_rest_send +@Properties.add_results +class ImageInstallOptions: """ + ### Summary Retrieve install-options details for ONE switch from the controller and provide property accessors for the policy attributes. - Caveats: - - This retrieves for a SINGLE switch only. - - Set serial_number and policy_name and call refresh() for - each switch separately. + ### Caveats - Usage (where module is an instance of AnsibleModule): + - This retrieves for a SINGLE switch only. + - Set serial_number and policy_name and call refresh() for + each switch separately. - instance = ImageInstallOptions(module) + ### Usage + + ```python + instance = ImageInstallOptions() # Mandatory + instance.rest_send = rest_send instance.policy_name = "NR3F" instance.serial_number = "FDO211218GC" # Optional @@ -62,13 +67,18 @@ class ImageInstallOptions(ImageUpgradeCommon): exit(1) status = instance.status platform = instance.platform - etc... + ### etc... + ``` + + install-options are retrieved by calling ``refresh()``. - install-options are retrieved by calling instance.refresh(). + ### Endpoint - Endpoint: /appcenter/cisco/ndfc/api/v1/imagemanagement/rest/imageupgrade/install-options - Request body: + + ### Payload + + ```json { "devices": [ { @@ -84,11 +94,15 @@ class ImageInstallOptions(ImageUpgradeCommon): "epld": false, "packageInstall": false } - Response body: - NOTES: - 1. epldModules will be null if epld is false in the request body. - This class converts this to None (python NoneType) in this case. + ``` + ### Response body + + - NOTES + 1. epldModules will be null if epld is false in the request body. + This class converts this to None (python NoneType) in this case. + + ```json { "compatibilityStatusList": [ { @@ -138,66 +152,91 @@ class ImageInstallOptions(ImageUpgradeCommon): "installPacakges": null, "errMessage": "" } + ``` """ - def __init__(self, ansible_module) -> None: - super().__init__(ansible_module) + def __init__(self) -> None: self.class_name = self.__class__.__name__ + method_name = inspect.stack()[0][3] self.log = logging.getLogger(f"dcnm.{self.class_name}") - self.log.debug("ENTERED ImageInstallOptions()") - - self.endpoints = ApiEndpoints() - self.path = self.endpoints.install_options.get("path") - self.verb = self.endpoints.install_options.get("verb") + self.compatibility_status = {} + self.payload: dict = {} - self.payload: Dict[str, Any] = {} + self.conversion = ConversionUtils() + self.ep_install_options = EpInstallOptions() - self.compatibility_status = {} + self._response_data = None self._init_properties() + msg = f"ENTERED {self.class_name}().{method_name}" + self.log.debug(msg) def _init_properties(self): - # self.properties is already initialized in the parent class - self.properties["epld"] = False - self.properties["epld_modules"] = None - self.properties["issu"] = True - self.properties["package_install"] = False - self.properties["policy_name"] = None - self.properties["response_data"] = None - self.properties["serial_number"] = None - self.properties["timeout"] = 300 - self.properties["unit_test"] = False + """ + ### Summary + Initialize class properties. + + ### Raises + None + """ + self._epld = False + self._issu = True + self._package_install = False + self._policy_name = None + self._rest_send = None + self._results = None + self._serial_number = None + self._timeout = 300 def _validate_refresh_parameters(self) -> None: """ - Ensure parameters are set correctly for a refresh() call. + ### Summary + - Ensure parameters are set correctly for a refresh() call. - fail_json if not. + ### Raises + ``ValueError`` if parameters are not set correctly. """ + # pylint: disable=no-member method_name = inspect.stack()[0][3] + if self.policy_name is None: msg = f"{self.class_name}.{method_name}: " - msg += "instance.policy_name must be set before " - msg += "calling refresh()" - self.ansible_module.fail_json(msg, **self.failed_result) + msg += "policy_name must be set before calling refresh()." + raise ValueError(msg) + + if self.rest_send is None: + msg = f"{self.class_name}.{method_name}: " + msg += "rest_send must be set before calling refresh()." + raise ValueError(msg) + + if self.results is None: + msg = f"{self.class_name}.{method_name}: " + msg += "results must be set before calling refresh()." + raise ValueError(msg) if self.serial_number is None: msg = f"{self.class_name}.{method_name}: " - msg += "instance.serial_number must be set before " - msg += "calling refresh()" - self.ansible_module.fail_json(msg, **self.failed_result) + msg += "serial_number must be set before calling refresh()." + raise ValueError(msg) def refresh(self) -> None: """ - Refresh self.response_data with current install-options from the controller + ### Summary + Refresh ``self.response_data`` with current install-options from + the controller. + + ### Raises + - ``ControllerResponseError``: if the controller response is bad. + e.g. 401, 500 error, etc. """ method_name = inspect.stack()[0][3] self._validate_refresh_parameters() - msg = f"self.epld {self.epld}, " + msg = f"{self.class_name}.{method_name}: " + msg += f"self.epld {self.epld}, " msg += f"self.issu {self.issu}, " msg += f"self.package_install {self.package_install}" self.log.debug(msg) @@ -210,9 +249,10 @@ def refresh(self) -> None: msg += "must be True before calling refresh(). Skipping." self.log.debug(msg) self.compatibility_status = {} - self.properties["response_data"] = { + # Yes, installPackages is intentionally misspelled below. + self._response_data = { "compatibilityStatusList": [], - "epldModules": None, + "epldModules": {}, "installPacakges": None, "errMessage": "", } @@ -220,51 +260,53 @@ def refresh(self) -> None: self._build_payload() - timeout = self.timeout - sleep_time = 5 - self.result_current["success"] = False + # pylint: disable=no-member + self.rest_send.path = self.ep_install_options.path + self.rest_send.verb = self.ep_install_options.verb + self.rest_send.payload = self.payload + self.rest_send.commit() - while timeout > 0 and self.result_current.get("success") is False: - msg = f"Calling dcnm_send: verb {self.verb} path {self.path} payload: " - msg += f"{json.dumps(self.payload, indent=4, sort_keys=True)}" - self.log.debug(msg) - - response = dcnm_send( - self.ansible_module, self.verb, self.path, data=json.dumps(self.payload) - ) + self._response_data = self.rest_send.response_current.get("DATA", {}) + # pylint: enable=no-member - self.properties["response_data"] = response.get("DATA", {}) - self.result_current = self._handle_response(response, self.verb) - self.response_current = copy.deepcopy(response) - - if self.result_current.get("success") is False and self.unit_test is False: - time.sleep(sleep_time) - timeout -= sleep_time + msg = f"{self.class_name}.{method_name}: " + msg += f"self.response_data: {json.dumps(self.response_data, indent=4, sort_keys=True)}" + self.log.debug(msg) - if self.result_current["success"] is False: + # pylint: disable=no-member + if self.rest_send.result_current["success"] is False: msg = f"{self.class_name}.{method_name}: " msg += "Bad result when retrieving install-options from " - msg += f"the controller. Controller response: {self.response_current}. " + msg += f"the controller. Controller response: {self.rest_send.response_current}. " if self.response_data.get("error", None) is None: - self.ansible_module.fail_json(msg, **self.failed_result) + raise ControllerResponseError(msg) if "does not have package to continue" in self.response_data.get( "error", "" ): msg += f"Possible cause: Image policy {self.policy_name} does not have " msg += "a package defined, and package_install is set to " msg += f"True in the playbook for device {self.serial_number}." - self.ansible_module.fail_json(msg, **self.failed_result) + raise ControllerResponseError(msg) + # pylint: enable=no-member - self.response = copy.deepcopy(self.response_current) if self.response_data.get("compatibilityStatusList") is None: self.compatibility_status = {} else: self.compatibility_status = self.response_data.get( "compatibilityStatusList", [{}] )[0] + # epldModules is handled in the epld_modules.getter property def _build_payload(self) -> None: """ + ### Summary + Build the payload for the install-options request. + + ### Raises + None + + ### Payload structure + ```json { "devices": [ { @@ -276,8 +318,9 @@ def _build_payload(self) -> None: "epld": false, "packageInstall": false } + ``` """ - self.payload: Dict[str, Any] = {} + self.payload: dict = {} self.payload["devices"] = [] devices = {} devices["serialNumber"] = self.serial_number @@ -291,15 +334,28 @@ def _build_payload(self) -> None: self.log.debug(msg) def _get(self, item): - return self.make_boolean(self.make_none(self.response_data.get(item))) + """ + ### Summary + Return items from self.response_data. + + ### Raises + None + """ + return self.conversion.make_boolean( + self.conversion.make_none(self.response_data.get(item)) + ) # Mandatory properties @property def policy_name(self): """ + ### Summary Set the policy_name of the policy to query. + + ### Raises + ``TypeError``: if value is not a string. """ - return self.properties.get("policy_name") + return self._policy_name @policy_name.setter def policy_name(self, value): @@ -307,249 +363,317 @@ def policy_name(self, value): if not isinstance(value, str): msg = f"{self.class_name}.{method_name}: " msg += f"instance.policy_name must be a string. Got {value}." - self.ansible_module.fail_json(msg, **self.failed_result) - self.properties["policy_name"] = value + raise TypeError(msg) + self._policy_name = value @property def serial_number(self): """ + ### Summary Set the serial_number of the device to query. + + ### Raises + None """ - return self.properties.get("serial_number") + return self._serial_number @serial_number.setter def serial_number(self, value): - self.properties["serial_number"] = value + self._serial_number = value # Optional properties - @property def issu(self): """ + ### Summary Enable (True) or disable (False) issu compatibility check. - Valid values: - True - Enable issu compatibility check - False - Disable issu compatibility check - Default: True + + ### Raises + ``TypeError``: if value is not a boolean. + + ### Valid values + + - True - Enable issu compatibility check + - False - Disable issu compatibility check + + ### Default value + True """ - return self.properties.get("issu") + return self._issu @issu.setter def issu(self, value): method_name = inspect.stack()[0][3] - value = self.make_boolean(value) + value = self.conversion.make_boolean(value) if not isinstance(value, bool): msg = f"{self.class_name}.{method_name}: " msg += f"issu must be a boolean value. Got {value}." - self.ansible_module.fail_json(msg, **self.failed_result) - self.properties["issu"] = value + raise TypeError(msg) + self._issu = value @property def epld(self): """ + ### Summary Enable (True) or disable (False) epld compatibility check. - Valid values: - True - Enable epld compatibility check - False - Disable epld compatibility check - Default: False + ### Raises + ``TypeError`` if value is not a boolean. + + ### Valid values + + - True - Enable epld compatibility check + - False - Disable epld compatibility check + + ### Default value + False """ - return self.properties.get("epld") + return self._epld @epld.setter def epld(self, value): method_name = inspect.stack()[0][3] - value = self.make_boolean(value) + value = self.conversion.make_boolean(value) if not isinstance(value, bool): msg = f"{self.class_name}.{method_name}: " msg += f"epld must be a boolean value. Got {value}." - self.ansible_module.fail_json(msg, **self.failed_result) - self.properties["epld"] = value + raise TypeError(msg) + self._epld = value @property def package_install(self): """ + ### Summary Enable (True) or disable (False) package_install compatibility check. - Valid values: - True - Enable package_install compatibility check - False - Disable package_install compatibility check - Default: False + + ### Raises + ``TypeError`` if value is not a boolean. + + ### Valid values + + - True - Enable package_install compatibility check + - False - Disable package_install compatibility check + + ### Default value + False """ - return self.properties.get("package_install") + return self._package_install @package_install.setter def package_install(self, value): method_name = inspect.stack()[0][3] - value = self.make_boolean(value) + value = self.conversion.make_boolean(value) if not isinstance(value, bool): msg = f"{self.class_name}.{method_name}: " msg += "package_install must be a boolean value. " msg += f"Got {value}." - self.ansible_module.fail_json(msg, **self.failed_result) - self.properties["package_install"] = value + raise TypeError(msg) + self._package_install = value # Getter properties @property def comp_disp(self): """ - Return the compDisp (CLI output from show install all status) - of the install-options response, if it exists. - Return None otherwise + ### Summary + + - Return the compDisp (CLI output from show install all status) + of the install-options response, if it exists. + - Return None otherwise """ return self.compatibility_status.get("compDisp") @property def device_name(self): """ - Return the deviceName of the install-options response, - if it exists. - Return None otherwise + ### Summary + + - Return the deviceName of the install-options response, + if it exists. + - Return None otherwise """ return self.compatibility_status.get("deviceName") @property def epld_modules(self): """ - Return the epldModules of the install-options response, - if it exists. - Return None otherwise + ### Summary - epldModules will be "null" if self.epld is False. - _get will convert to NoneType in this case. + - Return the epldModules of the install-options response, + if it exists. + - Return None otherwise. + + ### Notes + - epldModules will be "null" if self.epld is False. + - _get() will convert to NoneType in this case. """ return self._get("epldModules") @property def err_message(self): """ - Return the errMessage of the install-options response, - if it exists. - Return None otherwise + ### Summary + + - Return the errMessage of the install-options response, + if it exists. + - Return None otherwise """ return self._get("errMessage") @property def install_option(self): """ - Return the installOption of the install-options response, - if it exists. - Return None otherwise + ### Summary + + - Return the installOption of the install-options response, + if it exists. + - Return None otherwise """ return self.compatibility_status.get("installOption") @property def install_packages(self): """ - Return the installPackages of the install-options response, - if it exists. - Return None otherwise + ### Summary + + - Return the installPackages of the install-options response, + if it exists. + - Return None otherwise - NOTE: yes, installPacakges is misspelled in the response in the - following versions (at least): - 12.1.2e - 12.1.3b + ### NOTE + Yes, installPacakges is misspelled in the response in the following + controller versions (at least): + + - 12.1.2e + - 12.1.3b """ return self._get("installPacakges") @property def ip_address(self): """ - Return the ipAddress of the install-options response, - if it exists. - Return None otherwise + ### Summary + + - Return the ipAddress of the install-options response, + if it exists. + - Return None otherwise """ return self.compatibility_status.get("ipAddress") @property - def response_data(self) -> Dict[str, Any]: + def response_data(self) -> dict: """ - Return the DATA portion of the controller response. - Return empty dict otherwise + ### Summary + + - Return the DATA portion of the controller response. + - Return empty dict otherwise. """ - return self.properties.get("response_data", {}) + return self._response_data @property def os_type(self): """ - Return the osType of the install-options response, - if it exists. - Return None otherwise + ### Summary + + - Return the osType of the install-options response, + if it exists. + - Return None otherwise """ return self.compatibility_status.get("osType") @property def platform(self): """ - Return the platform of the install-options response, - if it exists. - Return None otherwise + ### Summary + + - Return the platform of the install-options response, + if it exists. + - Return None otherwise """ return self.compatibility_status.get("platform") @property def pre_issu_link(self): """ - Return the preIssuLink of the install-options response, if it exists. - Return None otherwise + ### Summary + + - Return the ``preIssuLink`` of the install-options response, + if it exists. + - Return ``None`` otherwise. """ return self.compatibility_status.get("preIssuLink") @property def raw_data(self): """ - Return the raw data of the install-options response, if it exists. - Alias for self.response_data + ### Summary + + - Return the raw data of the install-options response, + if it exists. + - Return ``None`` otherwise. """ return self.response_data @property def raw_response(self): """ - Return the raw response, if it exists. - Alias for self.response_current + ### Summary + + - Return the raw install-options response, if it exists. + - Alias for self.rest_send.response_current """ - return self.response_current + return self.rest_send.response_current # pylint: disable=no-member @property def rep_status(self): """ - Return the repStatus of the install-options response, if it exists. - Return None otherwise + ### Summary + + - Return the ``repStatus`` of the install-options response, + if it exists. + - Return ``None`` otherwise. """ return self.compatibility_status.get("repStatus") @property def status(self): """ - Return the status of the install-options response, - if it exists. - Return None otherwise + ### Summary + + - Return the ``status`` of the install-options response, + if it exists. + - Return ``None`` otherwise. """ return self.compatibility_status.get("status") @property def timestamp(self): """ - Return the timestamp of the install-options response, - if it exists. - Return None otherwise + ### Summary + + - Return the ``timestamp`` of the install-options response, + if it exists. + - Return ``None`` otherwise. """ return self.compatibility_status.get("timestamp") @property def version(self): """ - Return the version of the install-options response, - if it exists. - Return None otherwise + ### Summary + + - Return the ``version`` of the install-options response, + if it exists. + - Return ``None`` otherwise. """ return self.compatibility_status.get("version") @property def version_check(self): """ - Return the versionCheck (version check CLI output) - of the install-options response, if it exists. - Return None otherwise + ### Summary + + - Return the ``versionCheck`` (version check CLI output) + of the install-options response, if it exists. + - Return ``None`` otherwise. """ return self.compatibility_status.get("versionCheck") diff --git a/plugins/module_utils/image_upgrade/params_spec.py b/plugins/module_utils/image_upgrade/params_spec.py new file mode 100644 index 000000000..8e3991a7c --- /dev/null +++ b/plugins/module_utils/image_upgrade/params_spec.py @@ -0,0 +1,389 @@ +# 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 + + +class ParamsSpec: + """ + ### Summary + Parameter specifications for the dcnm_image_upgrade module. + + ## Raises + - ``ValueError`` if: + - ``params["state"]`` is missing. + - ``params["state"]`` is not a valid state. + - ``params`` is not set before calling ``commit``. + - ``TypeError`` if: + - ``params`` is not a dict. + """ + + def __init__(self): + self.class_name = self.__class__.__name__ + self.log = logging.getLogger(f"dcnm.{self.class_name}") + + self._params_spec: dict = {} + self.valid_states = set() + self.valid_states.add("deleted") + self.valid_states.add("merged") + self.valid_states.add("query") + + self.log.debug("ENTERED ParamsSpec() v2") + + def commit(self): + """ + ### Summary + Build the parameter specification based on the state. + + ## Raises + - ``ValueError`` if: + - ``params`` is not set. + """ + method_name = inspect.stack()[0][3] + + if self._params is None: + msg = f"{self.class_name}.{method_name}: " + msg += f"params must be set before calling {method_name}." + raise ValueError(msg) + + if self.params["state"] == "deleted": + self._build_params_spec_for_deleted_state() + if self.params["state"] == "merged": + self._build_params_spec_for_merged_state() + if self.params["state"] == "query": + self._build_params_spec_for_query_state() + + def build_ip_address(self): + """ + ### Summary + Build the parameter specification for the ``ip_address`` parameter. + + ### Raises + None + """ + self._params_spec["ip_address"] = {} + self._params_spec["ip_address"]["required"] = True + self._params_spec["ip_address"]["type"] = "ipv4" + + def build_policy(self): + """ + ### Summary + Build the parameter specification for the ``policy`` parameter. + + ### Raises + None + """ + self._params_spec["policy"] = {} + self._params_spec["policy"]["required"] = False + self._params_spec["policy"]["type"] = "str" + + def build_reboot(self): + """ + ### Summary + Build the parameter specification for the ``reboot`` parameter. + + ### Raises + None + """ + self._params_spec["reboot"] = {} + self._params_spec["reboot"]["required"] = False + self._params_spec["reboot"]["type"] = "bool" + self._params_spec["reboot"]["default"] = False + + def build_stage(self): + """ + ### Summary + Build the parameter specification for the ``stage`` parameter. + + ### Raises + None + """ + self._params_spec["stage"] = {} + self._params_spec["stage"]["required"] = False + self._params_spec["stage"]["type"] = "bool" + self._params_spec["stage"]["default"] = True + + def build_validate(self): + """ + ### Summary + Build the parameter specification for the ``validate`` parameter. + + ### Raises + None + """ + self._params_spec["validate"] = {} + self._params_spec["validate"]["required"] = False + self._params_spec["validate"]["type"] = "bool" + self._params_spec["validate"]["default"] = True + + def build_upgrade(self): + """ + ### Summary + Build the parameter specification for the ``upgrade`` parameter. + + ### Raises + None + """ + self._params_spec["upgrade"] = {} + self._params_spec["upgrade"]["required"] = False + self._params_spec["upgrade"]["type"] = "dict" + self._params_spec["upgrade"]["default"] = {} + self._params_spec["upgrade"]["epld"] = {} + self._params_spec["upgrade"]["epld"]["required"] = False + self._params_spec["upgrade"]["epld"]["type"] = "bool" + self._params_spec["upgrade"]["epld"]["default"] = False + self._params_spec["upgrade"]["nxos"] = {} + self._params_spec["upgrade"]["nxos"]["required"] = False + self._params_spec["upgrade"]["nxos"]["type"] = "bool" + self._params_spec["upgrade"]["nxos"]["default"] = True + + def build_options(self): + """ + ### Summary + Build the parameter specification for the ``options`` parameter. + + ### Raises + None + """ + section = "options" + self._params_spec[section] = {} + self._params_spec[section]["required"] = False + self._params_spec[section]["type"] = "dict" + self._params_spec[section]["default"] = {} + + def build_options_nxos(self): + """ + ### Summary + Build the parameter specification for the ``options.nxos`` parameter. + + ### Raises + None + """ + section = "options" + sub_section = "nxos" + self._params_spec[section][sub_section] = {} + self._params_spec[section][sub_section]["required"] = False + self._params_spec[section][sub_section]["type"] = "dict" + self._params_spec[section][sub_section]["default"] = {} + + self._params_spec[section][sub_section]["mode"] = {} + self._params_spec[section][sub_section]["mode"]["required"] = False + self._params_spec[section][sub_section]["mode"]["type"] = "str" + self._params_spec[section][sub_section]["mode"]["default"] = "disruptive" + self._params_spec[section][sub_section]["mode"]["choices"] = [ + "disruptive", + "non_disruptive", + "force_non_disruptive", + ] + + self._params_spec[section][sub_section]["bios_force"] = {} + self._params_spec[section][sub_section]["bios_force"]["required"] = False + self._params_spec[section][sub_section]["bios_force"]["type"] = "bool" + self._params_spec[section][sub_section]["bios_force"]["default"] = False + + def build_options_epld(self): + """ + ### Summary + Build the parameter specification for the ``options.epld`` parameter. + + ### Raises + None + """ + section = "options" + sub_section = "epld" + self._params_spec[section][sub_section] = {} + self._params_spec[section][sub_section]["required"] = False + self._params_spec[section][sub_section]["type"] = "dict" + self._params_spec[section][sub_section]["default"] = {} + + self._params_spec[section][sub_section]["module"] = {} + self._params_spec[section][sub_section]["module"]["required"] = False + self._params_spec[section][sub_section]["module"]["type"] = ["str", "int"] + self._params_spec[section][sub_section]["module"]["preferred_type"] = "str" + self._params_spec[section][sub_section]["module"]["default"] = "ALL" + self._params_spec[section][sub_section]["module"]["choices"] = [ + str(x) for x in range(1, 33) + ] + self._params_spec[section][sub_section]["module"]["choices"].extend( + list(range(1, 33)) + ) + self._params_spec[section][sub_section]["module"]["choices"].append("ALL") + + self._params_spec[section][sub_section]["golden"] = {} + self._params_spec[section][sub_section]["golden"]["required"] = False + self._params_spec[section][sub_section]["golden"]["type"] = "bool" + self._params_spec[section][sub_section]["golden"]["default"] = False + + def build_options_reboot(self): + """ + ### Summary + Build the parameter specification for the ``options.reboot`` parameter. + + ### Raises + None + """ + section = "options" + sub_section = "reboot" + self._params_spec[section][sub_section] = {} + self._params_spec[section][sub_section]["required"] = False + self._params_spec[section][sub_section]["type"] = "dict" + self._params_spec[section][sub_section]["default"] = {} + + self._params_spec[section][sub_section]["config_reload"] = {} + self._params_spec[section][sub_section]["config_reload"]["required"] = False + self._params_spec[section][sub_section]["config_reload"]["type"] = "bool" + self._params_spec[section][sub_section]["config_reload"]["default"] = False + + self._params_spec[section][sub_section]["write_erase"] = {} + self._params_spec[section][sub_section]["write_erase"]["required"] = False + self._params_spec[section][sub_section]["write_erase"]["type"] = "bool" + self._params_spec[section][sub_section]["write_erase"]["default"] = False + + def build_options_package(self): + """ + ### Summary + Build the parameter specification for the ``options.package`` parameter. + + ### Raises + None + """ + section = "options" + sub_section = "package" + self._params_spec[section][sub_section] = {} + self._params_spec[section][sub_section]["required"] = False + self._params_spec[section][sub_section]["type"] = "dict" + self._params_spec[section][sub_section]["default"] = {} + + self._params_spec[section][sub_section]["install"] = {} + self._params_spec[section][sub_section]["install"]["required"] = False + self._params_spec[section][sub_section]["install"]["type"] = "bool" + self._params_spec[section][sub_section]["install"]["default"] = False + + self._params_spec[section][sub_section]["uninstall"] = {} + self._params_spec[section][sub_section]["uninstall"]["required"] = False + self._params_spec[section][sub_section]["uninstall"]["type"] = "bool" + self._params_spec[section][sub_section]["uninstall"]["default"] = False + + def _build_params_spec_for_merged_state(self) -> None: + """ + ### Summary + Build the specs for the parameters expected when state is + ``merged``. + + ### Raises + None + """ + self.build_ip_address() + self.build_policy() + self.build_reboot() + self.build_stage() + self.build_validate() + self.build_upgrade() + self.build_options() + self.build_options_nxos() + self.build_options_epld() + self.build_options_reboot() + self.build_options_package() + + def _build_params_spec_for_deleted_state(self) -> None: + """ + ### Summary + Build the specs for the parameters expected when state is + ``deleted``. + + ### Raises + None + + ### Notes + - Parameters for ``deleted`` state are the same as ``merged`` state. + """ + self._build_params_spec_for_merged_state() + + def _build_params_spec_for_query_state(self) -> None: + """ + ### Summary + Build the specs for the parameters expected when state is + ``query``. + + ### Raises + None + """ + self.build_ip_address() + + @property + def params(self) -> dict: + """ + ### Summary + Expects value to be a dictionary containing, at mimimum, + the key ``state`` with value being one of: + - deleted + - merged + - query + + ### Raises + - ``TypeError`` if: + - ``value`` is not a dict. + - ``ValueError`` if: + - ``value["state"]`` is missing. + - ``value["state"]`` is not a valid state. + + ### Details + - Valid params: + - ``{"state": "deleted"}`` + - ``{"state": "merged"}`` + - ``{"state": "query"}`` + - getter: return the params + - setter: set the params + """ + return self._params + + @params.setter + def params(self, value) -> None: + method_name = inspect.stack()[0][3] + if not isinstance(value, dict): + msg = f"{self.class_name}.{method_name}.setter: " + msg += "Invalid type. Expected dict but " + msg += f"got type {type(value).__name__}, " + msg += f"value {value}." + raise TypeError(msg) + + if value.get("state", None) is None: + msg = f"{self.class_name}.{method_name}.setter: " + msg += "params.state is required but missing." + raise ValueError(msg) + + if value["state"] not in self.valid_states: + msg = f"{self.class_name}.{method_name}.setter: " + msg += f"params.state is invalid: {value['state']}. " + msg += f"Expected one of {', '.join(self.valid_states)}." + raise ValueError(msg) + + self._params = value + + @property + def params_spec(self) -> dict: + """ + ### Summary + Return the parameter specification + + ### Raises + None + """ + return self._params_spec diff --git a/plugins/module_utils/image_upgrade/switch_details.py b/plugins/module_utils/image_upgrade/switch_details.py deleted file mode 100644 index 40ce33373..000000000 --- a/plugins/module_utils/image_upgrade/switch_details.py +++ /dev/null @@ -1,230 +0,0 @@ -# -# 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 ansible_collections.cisco.dcnm.plugins.module_utils.common.rest_send import \ - RestSend -from ansible_collections.cisco.dcnm.plugins.module_utils.image_upgrade.api_endpoints import \ - ApiEndpoints -from ansible_collections.cisco.dcnm.plugins.module_utils.image_upgrade.image_upgrade_common import \ - ImageUpgradeCommon - - -class SwitchDetails(ImageUpgradeCommon): - """ - Retrieve switch details from the controller and provide property accessors - for the switch attributes. - - Usage (where module is an instance of AnsibleModule): - - instance = SwitchDetails(module) - instance.refresh() - instance.ip_address = 10.1.1.1 - fabric_name = instance.fabric_name - serial_number = instance.serial_number - etc... - - Switch details are retrieved by calling instance.refresh(). - - Endpoint: - /appcenter/cisco/ndfc/api/v1/lan-fabric/rest/inventory/allswitches - """ - - 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 SwitchDetails()") - - self.endpoints = ApiEndpoints() - self.path = self.endpoints.switches_info.get("path") - self.verb = self.endpoints.switches_info.get("verb") - - self.rest_send = RestSend(self.ansible_module) - - self._init_properties() - - def _init_properties(self): - # self.properties is already initialized in the parent class - self.properties["ip_address"] = None - self.properties["info"] = {} - - def refresh(self): - """ - Caller: __init__() - - Refresh switch_details with current switch details from - the controller. - """ - method_name = inspect.stack()[0][3] - - # Regardless of ansible_module.check_mode, we need to get the switch details - # So, set check_mode to False - self.rest_send.check_mode = False - self.rest_send.verb = self.verb - self.rest_send.path = self.path - self.rest_send.commit() - - msg = "self.rest_send.response_current: " - msg += ( - f"{json.dumps(self.rest_send.response_current, indent=4, sort_keys=True)}" - ) - self.log.debug(msg) - - msg = "self.rest_send.result_current: " - msg += f"{json.dumps(self.rest_send.result_current, indent=4, sort_keys=True)}" - self.log.debug(msg) - - self.response = self.rest_send.response_current - self.response_current = self.rest_send.response_current - self.response_data = self.response_current.get("DATA", "No_DATA_SwitchDetails") - - self.result = self.rest_send.result_current - self.result_current = self.rest_send.result_current - - if self.response_current["RETURN_CODE"] != 200: - msg = f"{self.class_name}.{method_name}: " - msg += "Unable to retrieve switch information from the controller. " - msg += f"Got response {self.response_current}" - self.ansible_module.fail_json(msg, **self.failed_result) - - data = self.response_current.get("DATA") - self.properties["info"] = {} - for switch in data: - self.properties["info"][switch["ipAddress"]] = switch - - msg = "self.properties[info]: " - msg += f"{json.dumps(self.properties['info'], indent=4, sort_keys=True)}" - self.log.debug(msg) - - def _get(self, item): - method_name = inspect.stack()[0][3] - - if self.ip_address is None: - msg = f"{self.class_name}.{method_name}: " - msg += "set instance.ip_address before accessing " - msg += f"property {item}." - self.ansible_module.fail_json(msg, **self.failed_result) - - if self.ip_address not in self.properties["info"]: - msg = f"{self.class_name}.{method_name}: " - msg += f"{self.ip_address} does not exist on the controller." - self.ansible_module.fail_json(msg, **self.failed_result) - - if item not in self.properties["info"][self.ip_address]: - msg = f"{self.class_name}.{method_name}: " - msg += f"{self.ip_address} 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["info"][self.ip_address].get(item)) - ) - - @property - def ip_address(self): - """ - Set the ip_address of the switch to query. - - This needs to be set before accessing this class's properties. - """ - return self.properties.get("ip_address") - - @ip_address.setter - def ip_address(self, value): - self.properties["ip_address"] = value - - @property - def fabric_name(self): - """ - Return the fabricName of the switch with ip_address, if it exists. - Return None otherwise - """ - return self._get("fabricName") - - @property - def hostname(self): - """ - Return the hostName of the switch with ip_address, if it exists. - Return None otherwise - - NOTES: - 1. This is None for 12.1.2e - 2. Better to use logical_name which is populated in both 12.1.2e and 12.1.3b - """ - return self._get("hostName") - - @property - def logical_name(self): - """ - Return the logicalName of the switch with ip_address, if it exists. - Return None otherwise - """ - return self._get("logicalName") - - @property - def model(self): - """ - Return the model of the switch with ip_address, if it exists. - Return None otherwise - """ - return self._get("model") - - @property - def info(self): - """ - Return parsed data from the GET request. - Return None otherwise - - NOTE: Keyed on ip_address - """ - return self.properties["info"] - - @property - def platform(self): - """ - Return the platform of the switch with ip_address, if it exists. - Return None otherwise - - NOTE: This is derived from "model". Is not in the controller response. - """ - model = self._get("model") - if model is None: - return None - return model.split("-")[0] - - @property - def role(self): - """ - Return the switchRole of the switch with ip_address, if it exists. - Return None otherwise - """ - return self._get("switchRole") - - @property - def serial_number(self): - """ - Return the serialNumber of the switch with ip_address, if it exists. - Return None otherwise - """ - return self._get("serialNumber") diff --git a/plugins/module_utils/image_upgrade/switch_issu_details.py b/plugins/module_utils/image_upgrade/switch_issu_details.py index 95bb6b88f..f96c70a18 100644 --- a/plugins/module_utils/image_upgrade/switch_issu_details.py +++ b/plugins/module_utils/image_upgrade/switch_issu_details.py @@ -19,28 +19,34 @@ __author__ = "Allen Robel" import inspect -import json import logging -from ansible_collections.cisco.dcnm.plugins.module_utils.image_upgrade.api_endpoints import \ - ApiEndpoints -from ansible_collections.cisco.dcnm.plugins.module_utils.image_upgrade.image_upgrade_common import \ - ImageUpgradeCommon -from ansible_collections.cisco.dcnm.plugins.module_utils.network.dcnm.dcnm import \ - dcnm_send +from ansible_collections.cisco.dcnm.plugins.module_utils.common.api.v1.imagemanagement.rest.packagemgnt.packagemgnt import \ + EpIssu +from ansible_collections.cisco.dcnm.plugins.module_utils.common.conversion import \ + ConversionUtils +from ansible_collections.cisco.dcnm.plugins.module_utils.common.properties import \ + Properties -class SwitchIssuDetails(ImageUpgradeCommon): +@Properties.add_rest_send +@Properties.add_results +class SwitchIssuDetails: """ + ### Summary Retrieve switch issu details from the controller and provide - property accessors for the switch attributes. + property getters for the switch attributes. - Usage: See subclasses. + ### Usage + See subclasses. - Endpoint: + ### Endpoint + ``` /appcenter/cisco/ndfc/api/v1/imagemanagement/rest/packagemgnt/issu + ``` - Response body: + ### Response body + ```json { "status": "SUCCESS", "lastOperDataObject": [ @@ -85,146 +91,208 @@ class SwitchIssuDetails(ImageUpgradeCommon): }, {etc...} ] + } + ``` """ - def __init__(self, ansible_module): - super().__init__(ansible_module) + def __init__(self): self.class_name = self.__class__.__name__ + method_name = inspect.stack()[0][3] self.log = logging.getLogger(f"dcnm.{self.class_name}") - self.log.debug("ENTERED SwitchIssuDetails()") - self.endpoints = ApiEndpoints() - self._init_properties() + self.action = "switch_issu_details" + self.conversion = ConversionUtils() + self.ep_issu = EpIssu() + self.data = {} + self._action_keys = set() + self._action_keys.add("imageStaged") + self._action_keys.add("upgrade") + self._action_keys.add("validated") + + self._rest_send = None + self._results = None - def _init_properties(self): - # self.properties is already initialized in the parent class - # action_keys is used in subclasses to determine if any actions - # are in progress. - # Property actions_in_progress returns True if so, False otherwise - self.properties["action_keys"] = set() - self.properties["action_keys"].add("imageStaged") - self.properties["action_keys"].add("upgrade") - self.properties["action_keys"].add("validated") + msg = f"ENTERED {self.class_name}().{method_name}" + self.log.debug(msg) + + def validate_refresh_parameters(self) -> None: + """ + ### Summary + Validate that mandatory parameters are set before calling refresh(). + + ### Raises + - ``ValueError``if: + - ``rest_send`` is not set. + - ``results`` is not set. + """ + method_name = inspect.stack()[0][3] + if self.rest_send is None: + msg = f"{self.class_name}.{method_name}: " + msg += f"{self.class_name}.rest_send must be set before calling " + msg += f"{self.class_name}.refresh()." + raise ValueError(msg) + if self.results is None: + msg = f"{self.class_name}.{method_name}: " + msg += f"{self.class_name}.results must be set before calling " + msg += f"{self.class_name}.refresh()." + raise ValueError(msg) def refresh_super(self) -> None: """ + ### Summary Refresh current issu details from the controller. """ method_name = inspect.stack()[0][3] - path = self.endpoints.issu_info.get("path") - verb = self.endpoints.issu_info.get("verb") - - msg = f"verb: {verb}, path {path}" + msg = f"ENTERED {self.class_name}.{method_name}" self.log.debug(msg) - self.response_current = dcnm_send(self.ansible_module, verb, path) - self.result_current = self._handle_response(self.response_current, verb) - - msg = f"self.response_current: {json.dumps(self.response_current, indent=4, sort_keys=True)}" - self.log.debug(msg) + try: + self.validate_refresh_parameters() + except ValueError as error: + raise ValueError(error) from error + + try: + self.rest_send.path = self.ep_issu.path + self.rest_send.verb = self.ep_issu.verb + + # We always want to get the issu details from the controller, + # regardless of the current value of check_mode. + # We save the current check_mode and timeout settings, set + # rest_send.check_mode to False so the request will be sent + # to the controller, and then restore the original settings. + + self.rest_send.save_settings() + self.rest_send.check_mode = False + self.rest_send.timeout = 1 + self.rest_send.commit() + self.rest_send.restore_settings() + except (TypeError, ValueError) as error: + raise ValueError(error) from error + + self.data = self.rest_send.response_current.get("DATA", {}).get( + "lastOperDataObject", {} + ) - msg = f"self.result_current: {json.dumps(self.result_current, indent=4, sort_keys=True)}" - self.log.debug(msg) + diff = {} + for item in self.data: + ip_address = item.get("ipAddress") + if ip_address is None: + continue + diff[ip_address] = item + + self.results.action = self.action + self.results.state = self.rest_send.state + # Set check_mode to True so that results.changed will be set to False + # (since we didn't make any changes). + self.results.check_mode = True + self.results.diff_current = diff + self.results.response_current = self.rest_send.response_current + self.results.result_current = self.rest_send.result_current + self.results.register_task_result() if ( - self.result_current["success"] is False - or self.result_current["found"] is False + self.rest_send.result_current["success"] is False + or self.rest_send.result_current["found"] is False ): msg = f"{self.class_name}.{method_name}: " msg += "Bad result when retriving switch " - msg += "information from the controller" - self.ansible_module.fail_json(msg, **self.failed_result) + msg += "ISSU details from the controller." + raise ValueError(msg) - data = self.response_current.get("DATA").get("lastOperDataObject") - - if data is None: + if self.data is None: msg = f"{self.class_name}.{method_name}: " msg += "The controller has no switch ISSU information." - self.ansible_module.fail_json(msg, **self.failed_result) + raise ValueError(msg) - if len(data) == 0: + if len(self.data) == 0: msg = f"{self.class_name}.{method_name}: " msg += "The controller has no switch ISSU information." - self.ansible_module.fail_json(msg, **self.failed_result) + raise ValueError(msg) @property def actions_in_progress(self): """ - Return True if any actions are in progress - Return False otherwise + ### Summary + - Return ``True`` if any actions are in progress. + - Return ``False`` otherwise. """ - for action_key in self.properties["action_keys"]: + for action_key in self._action_keys: if self._get(action_key) == "In-Progress": return True return False def _get(self, item): """ + ### Summary overridden in subclasses """ @property def device_name(self): """ - Return the deviceName of the switch with ip_address, if it exists. - Return None otherwise + ### Summary + - Return the ``deviceName`` of the switch with ``ip_address``, + if it exists. + - Return ``None`` otherwise. - Possible values: - device name, e.g. "cvd-1312-leaf" - None + ### Possible values + ``device name``, e.g. "cvd-1312-leaf" + - ``None`` """ return self._get("deviceName") @property def eth_switch_id(self): """ - Return the ethswitchid of the switch with - ip_address, if it exists. - Return None otherwise + - Return the ``ethswitchid`` of the switch with + ``ip_address``, if it exists. + - Return ``None`` otherwise. - Possible values: - integer - None + ### Possible values + - ``int()`` + - ``None`` """ return self._get("ethswitchid") @property def fabric(self): """ - Return the fabric of the switch with ip_address, if it exists. - Return None otherwise + - Return the ``fabric`` name of the switch with ``ip_address``, + if it exists. + - Return ``None`` otherwise. - Possible values: - fabric name, e.g. "myfabric" - None + ### Possible values + - fabric name, e.g. ``myfabric`` + - ``None`` """ return self._get("fabric") @property def fcoe_enabled(self): """ - Return whether FCOE is enabled on the switch with - ip_address, if it exists. - Return None otherwise + - Return whether FCOE is enabled on the switch with + ``ip_address``, if it exists. + - Return ``None`` otherwise. - Possible values: - boolean (true/false) - None + ### Possible values + - ``bool()`` (true/false) + - ``None`` """ - return self.make_boolean(self._get("fcoEEnabled")) + return self.conversion.make_boolean(self._get("fcoEEnabled")) @property def group(self): """ - Return the group of the switch with ip_address, if it exists. - Return None otherwise + - Return the ``group`` of the switch with ``ip_address``, + if it exists. + - Return ``None`` otherwise. - Possible values: - group name, e.g. "mygroup" - None + ### Possible values + - group name, e.g. ``mygroup`` + - ``None`` """ return self._get("group") @@ -233,673 +301,820 @@ def group(self): # so we use switch_id instead def switch_id(self): """ - Return the switch ID of the switch with ip_address, if it exists. - Return None otherwise + - Return the switch ``id`` of the switch with ``ip_address``, + if it exists. + - Return ``None`` otherwise. - Possible values: - Integer - None + ### Possible values + - ``int()`` + - ``None`` """ return self._get("id") @property def image_staged(self): """ - Return the imageStaged of the switch with ip_address, if it exists. - Return None otherwise + - Return the ``imageStaged`` of the switch with ``ip_address``, + if it exists. + - Return ``None`` otherwise. - Possible values: - Success - Failed - None + ### Possible values + - ``Success`` + - ``Failed`` + - ``None`` """ return self._get("imageStaged") @property def image_staged_percent(self): """ - Return the imageStagedPercent of the switch with - ip_address, if it exists. - Return None otherwise + - Return the ``imageStagedPercent`` of the switch with + ``ip_address``, if it exists. + - Return ``None`` otherwise. - Possible values: - Integer in range 0-100 - None + ### Possible values + - ``int()`` in range ``0-100`` + - ``None`` """ return self._get("imageStagedPercent") @property def ip_address(self): """ - Return the ipAddress of the switch, if it exists. - Return None otherwise + - Return the ``ipAddress`` of the switch, if it exists. + - Return ``None`` otherwise. - Possible values: - switch IP address - None + ### Possible values + - switch IP address, e.g. ``192.168.1.1`` + - ``None`` """ return self._get("ipAddress") @property def issu_allowed(self): """ - Return the issuAllowed value of the switch with - ip_address, if it exists. - Return None otherwise + - Return the ``issuAllowed`` value of the switch with + ``ip_address``, if it exists. + - Return ``None`` otherwise. - Possible values: - ?? TODO:3 check this - "" - None + ### Possible values + - ?? TODO:3 check this + - ``None`` """ return self._get("issuAllowed") @property def last_upg_action(self): """ - Return the last upgrade action performed on the switch - with ip_address, if it exists. - Return None otherwise + - Return the last upgrade action performed on the switch + with ``ip_address``, if it exists. + - Return ``None`` otherwise. - Possible values: - ?? TODO:3 check this - Never - None + ### Possible values + - ?? TODO:3 check this + - ``Never`` + - ``None`` """ return self._get("lastUpgAction") @property def mds(self): """ - Return whether the switch with ip_address is an MSD, if it exists. - Return None otherwise + - Return whether the switch with ``ip_address`` is an MDS, + if it exists. + - Return ``None`` otherwise. - Possible values: - Boolean (True or False) - None + ### Possible values + - ``bool()`` (True or False) + - ``None`` """ - return self.make_boolean(self._get("mds")) + return self.conversion.make_boolean(self._get("mds")) @property def mode(self): """ - Return the ISSU mode of the switch with ip_address, if it exists. - Return None otherwise + - Return the ISSU mode of the switch with ``ip_address``, + if it exists. + - Return ``None`` otherwise. - Possible values: - "Normal" - None + ### Possible values + - ``Normal`` + - ``None`` """ return self._get("mode") @property def model(self): """ - Return the model of the switch with ip_address, if it exists. - Return None otherwise + - Return the `model` of the switch with ``ip_address``, + if it exists. + - Return ``None`` otherwise. - Possible values: - model number e.g. "N9K-C93180YC-EX" - None + ### Possible values + - model number e.g. ``N9K-C93180YC-EX``. + - ``None`` """ return self._get("model") @property def model_type(self): """ - Return the model type of the switch with - ip_address, if it exists. - Return None otherwise + - Return the ``modelType`` of the switch with ``ip_address``, + if it exists. + - Return ``None`` otherwise. - Possible values: - Integer - None + ### Possible values + - ``int()`` + - ``None`` """ return self._get("modelType") @property def peer(self): """ - Return the peer of the switch with ip_address, if it exists. - Return None otherwise + - Return the ``peer`` of the switch with ``ip_address``, + if it exists. + - Return ``None`` otherwise. - Possible values: - ?? TODO:3 check this - None + ### Possible values + - ?? TODO:3 check this + - ``None`` """ return self._get("peer") @property def platform(self): """ - Return the platform of the switch with ip_address, if it exists. - Return None otherwise + - Return the ``platform`` of the switch with ``ip_address``, + if it exists. + - Return ``None`` otherwise. - Possible values: - platform, e.g. "N9K" - None + ### Possible values + - ``platform``, e.g. ``N9K`` + - ``None`` """ return self._get("platform") @property def policy(self): """ - Return the policy attached to the switch with ip_address, if it exists. - Return None otherwise + - Return the image ``policy`` attached to the switch with + ``ip_address``, if it exists. + - Return ``None`` otherwise. - Possible values: - policy name, e.g. "NR3F" - None + ### Possible values + - ``policy``, e.g. ``NR3F`` + - ``None`` """ return self._get("policy") @property def reason(self): """ - Return the reason (?) of the switch with ip_address, if it exists. - Return None otherwise + ### Summary + - Return the ``reason`` (?) of the switch with ``ip_address``, + if it exists. + - Return ``None`` otherwise. - Possible values: - Compliance - Validate - Upgrade - None + ### Possible values + - ``Compliance`` + - ``Validate`` + - ``Upgrade`` + - ``None`` """ return self._get("reason") @property def role(self): """ - Return the role of the switch with ip_address, if it exists. - Return None otherwise + ### Summary + - Return the ``role`` of the switch with ``ip_address``, + if it exists. + - Return ``None`` otherwise. - Possible values: - switch role, e.g. "leaf" - None + ### Possible values + - switch role, e.g. ``leaf`` + - ``None`` """ return self._get("role") @property def serial_number(self): """ - Return the serialNumber of the switch with ip_address, if it exists. - Return None otherwise + ### Summary + - Return the ``serialNumber`` of the switch with ``ip_address``, + if it exists. + - Return ``None`` otherwise. - Possible values: - switch serial number, e.g. "AB1234567CD" - None + ### Possible values + - switch serial number, e.g. ``AB1234567CD`` + - ``None`` """ return self._get("serialNumber") @property def status(self): """ - Return the sync status of the switch with ip_address, if it exists. - Return None otherwise + ### Summary + - Return the sync ``status`` of the switch with ``ip_address``, + if it exists. + - Return ``None`` otherwise. - Details: The sync status is the status of the switch with respect - to the image policy. If the switch is in sync with the image policy, - the status is "In-Sync". If the switch is out of sync with the image - policy, the status is "Out-Of-Sync". + ### Details + The sync status is the status of the switch with respect to the + image policy. If the switch is in sync with the image policy, + the status is ``In-Sync``. If the switch is out of sync with + the image policy, the status is ``Out-Of-Sync``. - Possible values: - "In-Sync" - "Out-Of-Sync" - None + ### Possible values + - ``In-Sync`` + - ``Out-Of-Sync`` + - ``None`` """ return self._get("status") @property def status_percent(self): """ - Return the upgrade (TODO:3 verify this) percentage completion - of the switch with ip_address, if it exists. - Return None otherwise + ### Summary + - Return the upgrade (TODO:3 verify this) percentage completion + of the switch with ``ip_address``, if it exists. + - Return ``None`` otherwise. - Possible values: - Integer in range 0-100 - None + ### Possible values + - ``int()`` in range ``0-100`` + - ``None`` """ return self._get("statusPercent") @property def sys_name(self): """ - Return the system name of the switch with ip_address, if it exists. - Return None otherwise + ### Summary + - Return the system name of the switch with ``ip_address``, + if it exists. + - Return ``None`` otherwise. - Possible values: - system name, e.g. "cvd-1312-leaf" - None + ### Possible values + - ``system name``, e.g. ``cvd-1312-leaf`` + - ``None`` """ return self._get("sys_name") @property def system_mode(self): """ - Return the system mode of the switch with ip_address, if it exists. - Return None otherwise + ### Summary + - Return the system mode of the switch with ``ip_address``, + if it exists. + - Return ``None`` otherwise. - Possible values: - "Maintenance" (TODO:3 verify this) - "Normal" - None + ### Possible values + - ``Maintenance`` (TODO:3 verify this) + - ``Normal`` + - ``None`` """ return self._get("systemMode") @property def upgrade(self): """ - Return the upgrade status of the switch with ip_address, - if it exists. - Return None otherwise + ### Summary + - Return the ``upgrade`` status of the switch with ``ip_address``, + if it exists. + - Return ``None`` otherwise. - Possible values: - Success - In-Progress - None + ### Possible values + - ``Success`` + - ``In-Progress`` + - ``None`` """ return self._get("upgrade") @property def upg_groups(self): """ - Return the upgGroups (upgrade groups) of the switch with ip_address, - if it exists. - Return None otherwise + ### Summary + - Return the ``upgGroups`` (upgrade groups) of the switch with + ``ip_address``, if it exists. + - Return ``None`` otherwise. - Possible values: - upgrade group to which the switch belongs e.g. "LEAFS" - None + ### Possible values + - upgrade group to which the switch belongs e.g. ``LEAFS`` + - ``None`` """ return self._get("upgGroups") @property def upgrade_percent(self): """ - Return the upgrade percent complete of the switch - with ip_address, if it exists. - Return None otherwise + ### Summary + - Return the upgrade percent complete of the switch with + ``ip_address``, if it exists. + - Return ``None`` otherwise. - Possible values: - Integer in range 0-100 - None + ### Possible values + - ``int()`` in range 0-100 + - ``None`` """ return self._get("upgradePercent") @property def validated(self): """ - Return the validation status of the switch with ip_address, - if it exists. - Return None otherwise + ### Summary + - Return the ``validated`` status of the switch with ``ip_address``, + if it exists. + - Return ``None`` otherwise. - Possible values: - Failed - Success - None + ### Possible values + - ``Failed`` + - ``Success`` + - ``None`` """ return self._get("validated") @property def validated_percent(self): """ - Return the validation percent complete of the switch - with ip_address, if it exists. - Return None otherwise + ### Summary + - Return the ``validatedPercent`` complete of the switch + with ``ip_address``, if it exists. + - Return ``None`` otherwise. - Possible values: - Integer in range 0-100 - None + ### Possible values + - ``int()`` in range 0-100 + - ``None`` """ return self._get("validatedPercent") @property def vdc_id(self): """ - Return the vdcId of the switch with ip_address, if it exists. - Return None otherwise + ### Summary + - Return the ``vdcId`` of the switch with ``ip_address``, + if it exists. + - Return ``None`` otherwise. - Possible values: - Integer - None + ### Possible values + - ''int()'' + - ``None`` """ return self._get("vdcId") @property def vdc_id2(self): """ - Return the vdc_id of the switch with ip_address, if it exists. - Return None otherwise + ### Summary + - Return the ``vdc_id`` of the switch with ``ip_address``, + if it exists. + - Return ``None`` otherwise. - Possible values: - Integer (negative values are valid) - None + ### Possible values + - ``int()`` (negative values are valid) + - ``None`` """ return self._get("vdc_id") @property def version(self): """ - Return the version of the switch with ip_address, if it exists. - Return None otherwise + ### Summary + - Return the ``version`` of the switch with ``ip_address``, + if it exists. + - Return ``None`` otherwise. - Possible values: - version, e.g. "10.3(2)" - None + ### Possible values + - version, e.g. ``10.3(2)`` + - ``None`` """ return self._get("version") @property def vpc_peer(self): """ - Return the vpcPeer of the switch with ip_address, if it exists. - Return None otherwise + ### Summary + - Return the ``vpcPeer`` of the switch with ``ip_address``, + if it exists. ``vpcPeer`` is the IP address of the switch's + VPC peer. + - Return ``None`` otherwise. - Possible values: - vpc peer e.g.: 10.1.1.1 - None + ### Possible values + - vpc peer e.g.: ``10.1.1.1`` + - ``None`` """ return self._get("vpcPeer") @property def vpc_role(self): """ - Return the vpcRole of the switch with ip_address, if it exists. - Return None otherwise - - NOTE: Two properties exist for vpc_role in the controller response. - vpc_role corresponds to vpcRole. - Possible values: - vpc role e.g.: - "primary" - "secondary" - "none" -> This will be translated to None - "none established" (TODO:3 verify this) - "primary, operational secondary" (TODO:3 verify this) - None + ### Summary + - Return the ``vpcRole`` of the switch with ``ip_address``, + if it exists. + - Return ``None`` otherwise. + + ### NOTES + - Two properties exist for vpc_role in the controller response. + ``vpc_role`` corresponds to vpcRole. + ### Possible values + - ``primary`` + - ``secondary`` + - ``none` + - This will be translated to ``None`` + - ``none established`` + - TODO:3 verify this + - ``primary, operational secondary`` + - TODO:3 verify this + - ``None`` + - python NoneType """ return self._get("vpcRole") @property def vpc_role2(self): """ - Return the vpc_role of the switch with ip_address, if it exists. - Return None otherwise - - NOTE: Two properties exist for vpc_role in the controller response. - vpc_role2 corresponds to vpc_role. - Possible values: - vpc role e.g.: - "primary" - "secondary" - "none" -> This will be translated to None - "none established" (TODO:3 verify this) - "primary, operational secondary" (TODO:3 verify this) - None + ### Summary + Return the ``vpc_role`` of the switch with ``ip_address``, + if it exists. + - Return ``None`` otherwise. + + ### Notes + - Two properties exist for vpc_role in the controller response. + vpc_role2 corresponds to vpc_role. + + ### Possible values + - ``primary`` + - ``secondary`` + - ``none` + - This will be translated to ``None`` + - ``none established`` + - TODO:3 verify this + - ``primary, operational secondary`` + - TODO:3 verify this + - ``None`` + - python NoneType """ return self._get("vpc_role") class SwitchIssuDetailsByIpAddress(SwitchIssuDetails): """ - Retrieve switch issu details from the controller and provide - property accessors for the switch attributes retrieved by ip address. + ### Summary + Retrieve switch issu details from the controller and provide property + getters for the switch attributes retrieved by ip_address. + + ### Raises + - ``ValueError`` if: + - ``filter`` is not set before accessing properties. + + ### Usage + + ```python + params = {"check_mode": False, "state": "merged"} + sender = Sender() + sender.ansible_module = ansible_module - Usage (where module is an instance of AnsibleModule): + rest_send = RestSend(params) + rest_send.sender = sender + rest_send.response_handler = ResponseHandler() - instance = SwitchIssuDetailsByIpAddress(module) + instance = SwitchIssuDetailsByIpAddress() + instance.rest_send = rest_send + instance.results = Results() instance.refresh() instance.filter = "10.1.1.1" image_staged = instance.image_staged image_upgraded = instance.image_upgraded serial_number = instance.serial_number - etc... + ### etc... + ``` See SwitchIssuDetails for more details. """ - def __init__(self, ansible_module): - super().__init__(ansible_module) + def __init__(self): + super().__init__() self.class_name = self.__class__.__name__ + method_name = inspect.stack()[0][3] self.log = logging.getLogger(f"dcnm.{self.class_name}") - self.log.debug("ENTERED SwitchIssuDetailsByIpAddress()") self.data_subclass = {} - self.properties["filter"] = None + self._filter = None + + msg = f"ENTERED {self.class_name}().{method_name}" + self.log.debug(msg) def refresh(self): """ - Refresh ip_address current issu details from the controller + ### Summary + Refresh ip_address current issu details from the controller. + + ### Raises + None """ self.refresh_super() + method_name = inspect.stack()[0][3] + self.action = "switch_issu_details_by_ip_address" + self.data_subclass = {} - for switch in self.response_current["DATA"]["lastOperDataObject"]: + for switch in self.rest_send.response_current["DATA"]["lastOperDataObject"]: self.data_subclass[switch["ipAddress"]] = switch - msg = f"{self.class_name}.refresh(): self.data_subclass: " - msg += f"{json.dumps(self.data_subclass, indent=4, sort_keys=True)}" - self.log.debug(msg) - def _get(self, item): + """ + ### Summary + Return the value of the switch property matching self.filter. + + ### Raises + - ``ValueError`` if: + - ``filter`` is not set before accessing properties. + - ``filter`` does not exist on the controller. + - ``filter`` references an unknown property name. + """ method_name = inspect.stack()[0][3] if self.filter is None: msg = f"{self.class_name}.{method_name}: " msg += "set instance.filter to a switch ipAddress " msg += f"before accessing property {item}." - self.ansible_module.fail_json(msg, **self.failed_result) + raise ValueError(msg) if self.data_subclass.get(self.filter) is None: msg = f"{self.class_name}.{method_name}: " msg += f"{self.filter} does not exist on the controller." - self.ansible_module.fail_json(msg, **self.failed_result) + raise ValueError(msg) if self.data_subclass[self.filter].get(item) is None: msg = f"{self.class_name}.{method_name}: " msg += f"{self.filter} unknown property name: {item}." - self.ansible_module.fail_json(msg, **self.failed_result) + raise ValueError(msg) - return self.make_none( - self.make_boolean(self.data_subclass[self.filter].get(item)) + return self.conversion.make_none( + self.conversion.make_boolean(self.data_subclass[self.filter].get(item)) ) @property def filtered_data(self): """ - Return a dictionary of the switch matching self.filter. - Return None if the switch does not exist on the controller. + ### Summary + - Return a dictionary of the switch matching ``filter``. + - Return ``None`` if the switch does not exist on the controller. """ return self.data_subclass.get(self.filter) @property def filter(self): """ - Set the ip_address of the switch to query. + ### Summary + Set the ``ipv4_address`` of the switch to query. - This needs to be set before accessing this class's properties. + ``filter`` needs to be set before accessing this class's properties. """ - return self.properties.get("filter") + return self._filter @filter.setter def filter(self, value): - self.properties["filter"] = value + self._filter = value class SwitchIssuDetailsBySerialNumber(SwitchIssuDetails): """ - Retrieve switch issu details from NDFC and provide property accessors - for the switch attributes retrieved by serial_number. + ### Summary + Retrieve switch issu details from the controller and provide property + getters for the switch attributes retrieved by serial_number. + + ### Usage - Usage (where module is an instance of AnsibleModule): + ```python + params = {"check_mode": False, "state": "merged"} + sender = Sender() + sender.ansible_module = ansible_module - instance = SwitchIssuDetailsBySerialNumber(module) + rest_send = RestSend(params) + rest_send.sender = sender + rest_send.response_handler = ResponseHandler() + + instance = SwitchIssuDetailsBySerialNumber() + instance.rest_send = rest_send + instance.results = Results() instance.refresh() instance.filter = "FDO211218GC" - instance.refresh() image_staged = instance.image_staged image_upgraded = instance.image_upgraded ip_address = instance.ip_address - etc... - + # etc... + ``` See SwitchIssuDetails for more details. - """ - def __init__(self, ansible_module): - super().__init__(ansible_module) + def __init__(self): + super().__init__() self.class_name = self.__class__.__name__ + method_name = inspect.stack()[0][3] + self.action = "switch_issu_details_by_serial_number" self.log = logging.getLogger(f"dcnm.{self.class_name}") - self.log.debug("ENTERED SwitchIssuDetailsBySerialNumber()") self.data_subclass = {} - self.properties["filter"] = None + self._filter = None + + msg = f"ENTERED {self.class_name}().{method_name}" + self.log.debug(msg) def refresh(self): """ - Refresh serial_number current issu details from NDFC + ### Summary + Refresh serial_number current issu details from the controller. + + ### Raises + None """ self.refresh_super() + method_name = inspect.stack()[0][3] self.data_subclass = {} - for switch in self.response_current["DATA"]["lastOperDataObject"]: + for switch in self.rest_send.response_current["DATA"]["lastOperDataObject"]: self.data_subclass[switch["serialNumber"]] = switch - msg = f"{self.class_name}.refresh(): self.data_subclass: " - msg += f"{json.dumps(self.data_subclass, indent=4, sort_keys=True)}" - self.log.debug(msg) - def _get(self, item): + """ + ### Summary + Return the value of the switch property matching self.filter. + + ### Raises + - ``ValueError`` if: + - ``filter`` is not set before accessing properties. + - ``filter`` does not exist on the controller. + - ``filter`` references an unknown property name. + """ method_name = inspect.stack()[0][3] if self.filter is None: msg = f"{self.class_name}.{method_name}: " msg += "set instance.filter to a switch serialNumber " msg += f"before accessing property {item}." - self.ansible_module.fail_json(msg, **self.failed_result) + raise ValueError(msg) if self.data_subclass.get(self.filter) is None: msg = f"{self.class_name}.{method_name}: " msg += f"{self.filter} does not exist " msg += "on the controller." - self.ansible_module.fail_json(msg, **self.failed_result) + raise ValueError(msg) if self.data_subclass[self.filter].get(item) is None: msg = f"{self.class_name}.{method_name}: " msg += f"{self.filter} unknown property name: {item}." - self.ansible_module.fail_json(msg, **self.failed_result) + raise ValueError(msg) - return self.make_none( - self.make_boolean(self.data_subclass[self.filter].get(item)) + return self.conversion.make_none( + self.conversion.make_boolean(self.data_subclass[self.filter].get(item)) ) @property def filtered_data(self): """ - Return a dictionary of the switch matching self.serial_number. - Return None if the switch does not exist on the controller. + ### Summary + - Return a dictionary of the switch matching self.serial_number. + - Return ``None`` if the switch does not exist on the controller. + + ### Raises + None """ return self.data_subclass.get(self.filter) @property def filter(self): """ + ### Summary Set the serial_number of the switch to query. - This needs to be set before accessing this class's properties. + ``filter`` needs to be set before accessing this class's properties. + + ### Raises + None """ - return self.properties.get("filter") + return self._filter @filter.setter def filter(self, value): - self.properties["filter"] = value + self._filter = value class SwitchIssuDetailsByDeviceName(SwitchIssuDetails): """ - Retrieve switch issu details from NDFC and provide property accessors - for the switch attributes retrieved by device_name. + ### Summary + Retrieve switch issu details from the controller and provide property + getters for the switch attributes retrieved by ``device_name``. + + ### Raises + - ``ValueError`` if: + - ``filter`` is not set before calling refresh(). - Usage (where module is an instance of AnsibleModule): + ### Usage - instance = SwitchIssuDetailsByDeviceName(module) + ```python + params = {"check_mode": False, "state": "merged"} + sender = Sender() + sender.ansible_module = ansible_module + + rest_send = RestSend(params) + rest_send.sender = sender + rest_send.response_handler = ResponseHandler() + + instance = SwitchIssuDetailsByDeviceName() instance.refresh() instance.filter = "leaf_1" image_staged = instance.image_staged image_upgraded = instance.image_upgraded ip_address = instance.ip_address - etc... + # etc... + ``` See SwitchIssuDetails for more details. - """ - def __init__(self, ansible_module): - super().__init__(ansible_module) + def __init__(self): + super().__init__() self.class_name = self.__class__.__name__ - - self.log = logging.getLogger(f"dcnm.{self.class_name}") - self.log.debug("ENTERED SwitchIssuDetailsByDeviceName()") + method_name = inspect.stack()[0][3] + self.action = "switch_issu_details_by_device_name" self.data_subclass = {} - self.properties["filter"] = None + self._filter = None + + msg = f"ENTERED {self.class_name}().{method_name}" + self.log.debug(msg) def refresh(self): """ - Refresh device_name current issu details from NDFC + ### Summary + Refresh device_name current issu details from the controller. + + ### Raises + None """ self.refresh_super() + method_name = inspect.stack()[0][3] + self.data_subclass = {} - for switch in self.response_current["DATA"]["lastOperDataObject"]: + for switch in self.rest_send.response_current["DATA"]["lastOperDataObject"]: self.data_subclass[switch["deviceName"]] = switch - msg = f"{self.class_name}.refresh(): self.data_subclass: " - msg += f"{json.dumps(self.data_subclass, indent=4, sort_keys=True)}" - self.log.debug(msg) - def _get(self, item): + """ + ### Summary + Return the value of the switch property matching self.filter. + + ### Raises + - ``ValueError`` if: + - ``filter`` is not set before accessing properties. + - ``filter`` does not exist on the controller. + - ``filter`` references an unknown property name. + """ method_name = inspect.stack()[0][3] if self.filter is None: msg = f"{self.class_name}.{method_name}: " msg += "set instance.filter to a switch deviceName " msg += f"before accessing property {item}." - self.ansible_module.fail_json(msg, **self.failed_result) + raise ValueError(msg) if self.data_subclass.get(self.filter) is None: msg = f"{self.class_name}.{method_name}: " msg += f"{self.filter} does not exist " msg += "on the controller." - self.ansible_module.fail_json(msg, **self.failed_result) + raise ValueError(msg) if self.data_subclass[self.filter].get(item) is None: msg = f"{self.class_name}.{method_name}: " msg += f"{self.filter} unknown property name: {item}." - self.ansible_module.fail_json(msg, **self.failed_result) + raise ValueError(msg) - return self.make_none( - self.make_boolean(self.data_subclass[self.filter].get(item)) + return self.conversion.make_none( + self.conversion.make_boolean(self.data_subclass[self.filter].get(item)) ) @property def filtered_data(self): """ - Return a dictionary of the switch matching self.filter. - Return None of the switch does not exist in NDFC. + ### Summary + - Return a dictionary of the switch matching ``filter``. + - Return ``None`` if the switch does not exist on the + controller. """ return self.data_subclass.get(self.filter) @property def filter(self): """ + ### Summary Set the device_name of the switch to query. - This needs to be set before accessing this class's properties. + ``filter`` needs to be set before accessing this class's properties. """ - return self.properties.get("filter") + return self._filter @filter.setter def filter(self, value): - self.properties["filter"] = value + self._filter = value diff --git a/plugins/module_utils/image_upgrade/wait_for_controller_done.py b/plugins/module_utils/image_upgrade/wait_for_controller_done.py new file mode 100644 index 000000000..629b8d26d --- /dev/null +++ b/plugins/module_utils/image_upgrade/wait_for_controller_done.py @@ -0,0 +1,219 @@ +# +# 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 time import sleep + +from ansible_collections.cisco.dcnm.plugins.module_utils.common.properties import \ + Properties +from ansible_collections.cisco.dcnm.plugins.module_utils.common.results import \ + Results +from ansible_collections.cisco.dcnm.plugins.module_utils.image_upgrade.switch_issu_details import ( + SwitchIssuDetailsByDeviceName, SwitchIssuDetailsByIpAddress, + SwitchIssuDetailsBySerialNumber) + + +@Properties.add_rest_send +class WaitForControllerDone: + """ + ### Summary + Wait for actions to complete on the controller. + + Actions include image staging, image upgrade, and image validation. + + ### Raises + - ``ValueError`` if: + - Controller actions do not complete within ``rest_send.timeout`` seconds. + - ``items`` is not a set prior to calling ``commit()``. + - ``item_type`` is not set prior to calling ``commit()``. + - ``rest_send`` is not set prior to calling ``commit()``. + """ + + def __init__(self): + self.class_name = self.__class__.__name__ + method_name = inspect.stack()[0][3] + + self.log = logging.getLogger(f"dcnm.{self.class_name}") + + self.action = "wait_for_controller" + self.done = set() + self.todo = set() + self.issu_details = None + + self._items = None + self._item_type = None + self._rest_send = None + self._valid_item_types = ["device_name", "ipv4_address", "serial_number"] + + msg = f"ENTERED {self.class_name}().{method_name}" + self.log.debug(msg) + + def get_filter_class(self) -> None: + """ + ### Summary + Set the appropriate ''SwitchIssuDetails'' subclass based on + ``item_type``. + + The subclass is used to filter the issu_details controller data + by item_type. + + ### Raises + None + """ + _select = {} + _select["device_name"] = SwitchIssuDetailsByDeviceName + _select["ipv4_address"] = SwitchIssuDetailsByIpAddress + _select["serial_number"] = SwitchIssuDetailsBySerialNumber + self.issu_details = _select[self.item_type]() + self.issu_details.rest_send = self.rest_send # pylint: disable=no-member + self.issu_details.results = Results() + self.issu_details.results.action = self.action + + def verify_commit_parameters(self): + """ + ### Summary + Verify that mandatory parameters are set before calling commit(). + + ### Raises + - ``ValueError`` if: + - ``items`` is not set. + - ``item_type`` is not set. + - ``rest_send`` is not set. + """ + method_name = inspect.stack()[0][3] + msg = f"{self.class_name}.{method_name}: " + + if self.items is None: + msg += "items must be set before calling commit()." + raise ValueError(msg) + + if self.item_type is None: + msg += "item_type must be set before calling commit()." + raise ValueError(msg) + + if self.rest_send is None: # pylint: disable=no-member + msg = f"{self.class_name}.{method_name}: " + msg += "rest_send must be set before calling commit()." + raise ValueError(msg) + + def commit(self): + """ + ### Summary + The controller will not proceed with certain operations if there + are any actions in progress. Wait for all actions to complete + and then return. + + Actions include image staging, image upgrade, and image validation. + + ### Raises + - ``ValueError`` if: + - Actions do not complete within ``rest_send.timeout`` seconds. + - ``items`` is not a set. + - ``item_type`` is not set. + - ``rest_send`` is not set. + """ + # pylint: disable=no-member + method_name = inspect.stack()[0][3] + + self.verify_commit_parameters() + + if len(self.items) == 0: + return + self.get_filter_class() + self.todo = copy.copy(self.items) + timeout = self.rest_send.timeout + + while self.done != self.todo and timeout > 0: + if self.rest_send.unit_test is False: # pylint: disable=no-member + sleep(self.rest_send.send_interval) + timeout -= self.rest_send.send_interval + + self.issu_details.refresh() + + for item in self.todo: + if item in self.done: + continue + self.issu_details.filter = item + if self.issu_details.actions_in_progress is False: + self.done.add(item) + + if self.done != self.todo: + msg = f"{self.class_name}.{method_name}: " + msg += f"Timed out after {self.rest_send.timeout} seconds " + msg += "waiting for controller actions to complete on items: " + msg += f"{sorted(self.todo)}. " + if len(self.done) > 0: + msg += "The following items did complete: " + msg += f"{','.join(sorted(self.done))}." + raise ValueError(msg) + + @property + def items(self): + """ + ### Summary + A set of serial_number, ipv4_address, or device_name to wait for. + + ### Raises + ValueError: If ``items`` is not a set. + + ### Example + ```python + instance.items = {"192.168.1.1", "192.168.1.2"} + ``` + """ + return self._items + + @items.setter + def items(self, value): + if not isinstance(value, set): + raise TypeError("items must be a set") + self._items = value + + @property + def item_type(self): + """ + ### Summary + The type of items to wait for. + + ### Raises + ValueError: If ``item_type`` is not one of the valid values. + + ### Valid Values + - ``serial_number`` + - ``ipv4_address`` + - ``device_name`` + + ### Example + ```python + instance.item_type = "ipv4_address" + ``` + """ + return self._item_type + + @item_type.setter + def item_type(self, value): + if value not in self._valid_item_types: + msg = f"{self.class_name}.item_type: " + msg = "item_type must be one of " + msg += f"{','.join(self._valid_item_types)}." + raise ValueError(msg) + self._item_type = value diff --git a/plugins/modules/dcnm_fabric.py b/plugins/modules/dcnm_fabric.py index 30997ed50..4204986e0 100644 --- a/plugins/modules/dcnm_fabric.py +++ b/plugins/modules/dcnm_fabric.py @@ -2313,25 +2313,31 @@ import inspect import json import logging -from os import environ from ansible.module_utils.basic import AnsibleModule from ansible_collections.cisco.dcnm.plugins.module_utils.common.controller_features import \ ControllerFeatures from ansible_collections.cisco.dcnm.plugins.module_utils.common.exceptions import \ ControllerResponseError -from ansible_collections.cisco.dcnm.plugins.module_utils.common.log import Log -from ansible_collections.cisco.dcnm.plugins.module_utils.common.rest_send import \ +from ansible_collections.cisco.dcnm.plugins.module_utils.common.log_v2 import \ + Log +from ansible_collections.cisco.dcnm.plugins.module_utils.common.properties import \ + Properties +from ansible_collections.cisco.dcnm.plugins.module_utils.common.response_handler import \ + ResponseHandler +from ansible_collections.cisco.dcnm.plugins.module_utils.common.rest_send_v2 import \ RestSend from ansible_collections.cisco.dcnm.plugins.module_utils.common.results import \ Results +from ansible_collections.cisco.dcnm.plugins.module_utils.common.sender_dcnm import \ + Sender from ansible_collections.cisco.dcnm.plugins.module_utils.fabric.common import \ FabricCommon from ansible_collections.cisco.dcnm.plugins.module_utils.fabric.create import \ FabricCreateBulk from ansible_collections.cisco.dcnm.plugins.module_utils.fabric.delete import \ FabricDelete -from ansible_collections.cisco.dcnm.plugins.module_utils.fabric.fabric_details import \ +from ansible_collections.cisco.dcnm.plugins.module_utils.fabric.fabric_details_v2 import \ FabricDetailsByName from ansible_collections.cisco.dcnm.plugins.module_utils.fabric.fabric_summary import \ FabricSummary @@ -2356,6 +2362,7 @@ def json_pretty(msg): return json.dumps(msg, indent=4, sort_keys=True) +@Properties.add_rest_send class Common(FabricCommon): """ Common methods, properties, and resources for all states. @@ -2364,74 +2371,149 @@ class Common(FabricCommon): def __init__(self, params): self.class_name = self.__class__.__name__ self.log = logging.getLogger(f"dcnm.{self.class_name}") - super().__init__(params) + super().__init__() method_name = inspect.stack()[0][3] # pylint: disable=unused-variable + self.controller_features = ControllerFeatures() + self.features = {} + self._implemented_states = set() + + self.params = params + # populated in self.validate_input() + self.payloads = {} + + self.populate_check_mode() + self.populate_state() + self.populate_config() + self.results = Results() self.results.state = self.state self.results.check_mode = self.check_mode + self._rest_send = None + self._verify_playbook_params = VerifyPlaybookParams() + + self.have = {} + self.query = [] + self.validated = [] + self.want = [] msg = "ENTERED Common(): " msg += f"state: {self.state}, " msg += f"check_mode: {self.check_mode}" self.log.debug(msg) - self.controller_features = ControllerFeatures(params) - self.features = {} + def populate_check_mode(self): + """ + ### Summary + Populate ``check_mode`` with the playbook check_mode. - self._implemented_states = set() + ### Raises + - ValueError if check_mode is not provided. + """ + method_name = inspect.stack()[0][3] + self.check_mode = self.params.get("check_mode", None) + if self.check_mode is None: + msg = f"{self.class_name}.{method_name}: " + msg += "check_mode is required." + raise ValueError(msg) - self._verify_playbook_params = VerifyPlaybookParams() + def populate_config(self): + """ + ### Summary + Populate ``config`` with the playbook config. - # populated in self.validate_input() - self.payloads = {} + ### Raises + - ValueError if: + - ``state`` is "merged" or "replaced" and ``config`` is None. + - ``config`` is not a list. + """ + method_name = inspect.stack()[0][3] + states_requiring_config = {"merged", "replaced"} + self.config = self.params.get("config", None) + if self.state in states_requiring_config: + if self.config is None: + msg = f"{self.class_name}.{method_name}: " + msg += "params is missing config parameter." + raise ValueError(msg) + if not isinstance(self.config, list): + msg = f"{self.class_name}.{method_name}: " + msg += "expected list type for self.config. " + msg += f"got {type(self.config).__name__}" + raise ValueError(msg) - self.config = params.get("config") - if not isinstance(self.config, list): - msg = "expected list type for self.config. " - msg += f"got {type(self.config).__name__}" - raise ValueError(msg) + def populate_state(self): + """ + ### Summary + Populate ``state`` with the playbook state. - self.validated = [] - self.have = {} - self.want = [] - self.query = [] + ### Raises + - ValueError if: + - ``state`` is not provided. + - ``state`` is not a valid state. + """ + method_name = inspect.stack()[0][3] - self._build_properties() + valid_states = ["deleted", "merged", "query", "replaced"] - def _build_properties(self): - self._properties["ansible_module"] = None + self.state = self.params.get("state", None) + if self.state is None: + msg = f"{self.class_name}.{method_name}: " + msg += "params is missing state parameter." + raise ValueError(msg) + if self.state not in valid_states: + msg = f"{self.class_name}.{method_name}: " + msg += f"Invalid state: {self.state}. " + msg += f"Expected one of: {','.join(valid_states)}." + raise ValueError(msg) def get_have(self): """ - Caller: main() - - Build self.have, which is a dict containing the current controller + ### Summary + Build ``self.have``, which is a dict containing the current controller fabrics and their details. - Have is a dict, keyed on fabric_name, where each element is a dict - with the following structure: + ### Raises + - ``ValueError`` if the controller returns an error when attempting to + retrieve the fabric details. + + ### have structure + + ``have`` is a dict, keyed on fabric_name, where each element is a dict + with the following structure. - { + ```python + have = { "fabric_name": "fabric_name", "fabric_config": { "fabricName": "fabric_name", "fabricType": "VXLAN EVPN", - etc... + "etc...": "etc..." } } + ``` + """ method_name = inspect.stack()[0][3] # pylint: disable=unused-variable - self.have = FabricDetailsByName(self.params) - self.have.rest_send = RestSend(self.ansible_module) - self.have.refresh() + try: + self.have = FabricDetailsByName() + self.have.rest_send = self.rest_send + self.have.results = Results() + self.have.refresh() + except ValueError as error: + msg = f"{self.class_name}.{method_name}: " + msg += "Controller returned error when attempting to retrieve " + msg += "fabric details. " + msg += f"Error detail: {error}" + raise ValueError(msg) from error def get_want(self) -> None: """ - Caller: main() + ### Summary + - Validate the playbook configs. + - Update self.want with the playbook configs. - 1. Validate the playbook configs - 2. Update self.want with the playbook configs + ### Raises + - ``ValueError`` if the playbook configs are invalid. """ method_name = inspect.stack()[0][3] # pylint: disable=unused-variable merged_configs = [] @@ -2439,7 +2521,7 @@ def get_want(self) -> None: try: self._verify_payload(config) except ValueError as error: - self.ansible_module.fail_json(f"{error}", **self.results.failed_result) + raise ValueError(f"{error}") from error merged_configs.append(copy.deepcopy(config)) self.want = [] @@ -2448,16 +2530,23 @@ def get_want(self) -> None: def get_controller_features(self) -> None: """ + ### Summary + - Retrieve the state of relevant controller features - Populate self.features - key: FABRIC_TYPE - value: True or False - True if feature is started for this fabric type - False otherwise + + ### Raises + + - ``ValueError`` if the controller returns an error when attempting to + retrieve the controller features. """ method_name = inspect.stack()[0][3] self.features = {} - self.controller_features.rest_send = RestSend(self.ansible_module) + self.controller_features.rest_send = self.rest_send try: self.controller_features.refresh() except ControllerResponseError as error: @@ -2465,98 +2554,98 @@ def get_controller_features(self) -> None: msg += "Controller returned error when attempting to retrieve " msg += "controller features. " msg += f"Error detail: {error}" - self.ansible_module.fail_json(f"{msg}", **self.results.failed_result) + raise ValueError(msg) from error + for fabric_type in self.fabric_types.valid_fabric_types: self.fabric_types.fabric_type = fabric_type self.controller_features.filter = self.fabric_types.feature_name self.features[fabric_type] = self.controller_features.started - @property - def ansible_module(self): - """ - getter: return an instance of AnsibleModule - setter: set an instance of AnsibleModule - """ - return self._properties["ansible_module"] - - @ansible_module.setter - def ansible_module(self, value): - method_name = inspect.stack()[0][3] # pylint: disable=unused-variable - if not isinstance(value, AnsibleModule): - msg = f"{self.class_name}.{method_name}: " - msg += "expected AnsibleModule instance. " - msg += f"got {type(value).__name__}." - raise ValueError(msg) - self._properties["ansible_module"] = value - class Deleted(Common): """ + ### Summary Handle deleted state """ def __init__(self, params): self.class_name = self.__class__.__name__ super().__init__(params) - method_name = inspect.stack()[0][3] # pylint: disable=unused-variable - self.log = logging.getLogger(f"dcnm.{self.class_name}") + self.action = "fabric_delete" + self.delete = FabricDelete() + self._implemented_states.add("deleted") - self.fabric_delete = FabricDelete(self.params) + self.log = logging.getLogger(f"dcnm.{self.class_name}") msg = "ENTERED Deleted(): " - msg += f"state: {self.state}, " - msg += f"check_mode: {self.check_mode}" + msg += f"state: {self.results.state}, " + msg += f"check_mode: {self.results.check_mode}" self.log.debug(msg) - self._implemented_states.add("deleted") - def commit(self) -> None: """ - delete the fabrics in self.want that exist on the controller + ### Summary + delete the fabrics in ``self.want`` that exist on the controller. + + ### Raises + + - ``ValueError`` if the controller returns an error when attempting to + delete the fabrics. """ self.get_want() - method_name = inspect.stack()[0][3] # pylint: disable=unused-variable + method_name = inspect.stack()[0][3] - msg = f"{self.class_name}.{method_name}: " - msg += "entered" + msg = f"ENTERED: {self.class_name}.{method_name}" self.log.debug(msg) - self.rest_send = RestSend(self.ansible_module) - - self.fabric_details = FabricDetailsByName(self.params) + self.fabric_details = FabricDetailsByName() self.fabric_details.rest_send = self.rest_send + self.fabric_details.results = Results() - self.fabric_summary = FabricSummary(self.params) + self.fabric_summary = FabricSummary() self.fabric_summary.rest_send = self.rest_send + self.fabric_summary.results = Results() - self.fabric_delete.rest_send = self.rest_send - self.fabric_delete.fabric_details = self.fabric_details - self.fabric_delete.fabric_summary = self.fabric_summary - self.fabric_delete.results = self.results + self.delete.rest_send = self.rest_send + self.delete.fabric_details = self.fabric_details + self.delete.fabric_summary = self.fabric_summary + self.delete.results = self.results fabric_names_to_delete = [] for want in self.want: fabric_names_to_delete.append(want["FABRIC_NAME"]) try: - self.fabric_delete.fabric_names = fabric_names_to_delete + self.delete.fabric_names = fabric_names_to_delete except ValueError as error: - self.ansible_module.fail_json( - f"{error}", **self.fabric_delete.results.failed_result - ) + raise ValueError(f"{error}") from error try: - self.fabric_delete.commit() + self.delete.commit() except ValueError as error: - self.ansible_module.fail_json( - f"{error}", **self.fabric_delete.results.failed_result - ) + raise ValueError(f"{error}") from error class Merged(Common): """ - Handle merged state + ### Summary + Handle merged state. + + ### Raises + + - ``ValueError`` if: + - The controller features required for the fabric type are not + running on the controller. + - The playbook parameters are invalid. + - The controller returns an error when attempting to retrieve + the template. + - The controller returns an error when attempting to retrieve + the fabric details. + - The controller returns an error when attempting to create + the fabric. + - The controller returns an error when attempting to update + the fabric. """ def __init__(self, params): @@ -2564,13 +2653,14 @@ def __init__(self, params): super().__init__(params) method_name = inspect.stack()[0][3] # pylint: disable=unused-variable + self.action = "fabric_create" self.log = logging.getLogger(f"dcnm.{self.class_name}") - self.fabric_details = FabricDetailsByName(self.params) - self.fabric_summary = FabricSummary(self.params) - self.fabric_create = FabricCreateBulk(self.params) + self.fabric_details = FabricDetailsByName() + self.fabric_summary = FabricSummary() + self.fabric_create = FabricCreateBulk() self.fabric_types = FabricTypes() - self.fabric_update = FabricUpdateBulk(self.params) + self.fabric_update = FabricUpdateBulk() self.template = TemplateGet() msg = f"ENTERED Merged.{method_name}: " @@ -2585,9 +2675,18 @@ def __init__(self, params): def get_need(self): """ - Caller: commit() - - Build self.need for merged state + ### Summary + Build ``self.need`` for merged state. + + ### Raises + - ``ValueError`` if: + - The controller features required for the fabric type are not + running on the controller. + - The playbook parameters are invalid. + - The controller returns an error when attempting to retrieve + the template. + - The controller returns an error when attempting to retrieve + the fabric details. """ method_name = inspect.stack()[0][3] self.payloads = {} @@ -2603,22 +2702,22 @@ def get_need(self): msg += "controller. Review controller settings at " msg += "Fabric Controller -> Admin -> System Settings -> " msg += "Feature Management" - self.ansible_module.fail_json(f"{msg}", **self.results.failed_result) + raise ValueError(msg) try: self._verify_playbook_params.config_playbook = want except TypeError as error: - self.ansible_module.fail_json(f"{error}", **self.results.failed_result) + raise ValueError(f"{error}") from error try: self.fabric_types.fabric_type = fabric_type except ValueError as error: - self.ansible_module.fail_json(f"{error}", **self.results.failed_result) + raise ValueError(f"{error}") from error try: template_name = self.fabric_types.template_name except ValueError as error: - self.ansible_module.fail_json(f"{error}", **self.results.failed_result) + raise ValueError(f"{error}") from error self.template.rest_send = self.rest_send self.template.template_name = template_name @@ -2626,18 +2725,18 @@ def get_need(self): try: self.template.refresh() except ValueError as error: - self.ansible_module.fail_json(f"{error}", **self.results.failed_result) + raise ValueError(f"{error}") from error except ControllerResponseError as error: msg = f"{self.class_name}.{method_name}: " msg += "Controller returned error when attempting to retrieve " msg += f"template: {template_name}. " msg += f"Error detail: {error}" - self.ansible_module.fail_json(f"{msg}", **self.results.failed_result) + raise ValueError(msg) from error try: self._verify_playbook_params.template = self.template.template except TypeError as error: - self.ansible_module.fail_json(f"{error}", **self.results.failed_result) + raise ValueError(f"{error}") from error # Append to need_create if the fabric does not exist. # Otherwise, append to need_update. @@ -2645,16 +2744,12 @@ def get_need(self): try: self._verify_playbook_params.config_controller = None except TypeError as error: - self.ansible_module.fail_json( - f"{error}", **self.results.failed_result - ) + raise ValueError(f"{error}") from error try: self._verify_playbook_params.commit() except ValueError as error: - self.ansible_module.fail_json( - f"{error}", **self.results.failed_result - ) + raise ValueError(f"{error}") from error self.need_create.append(want) @@ -2664,30 +2759,42 @@ def get_need(self): try: self._verify_playbook_params.config_controller = nv_pairs except TypeError as error: - self.ansible_module.fail_json( - f"{error}", **self.results.failed_result - ) + raise ValueError(f"{error}") from error try: self._verify_playbook_params.commit() except (ValueError, KeyError) as error: - self.ansible_module.fail_json( - f"{error}", **self.results.failed_result - ) + raise ValueError(f"{error}") from error self.need_update.append(want) def commit(self): """ - Commit the merged state request + ### Summary + Commit the merged state request. + + ### Raises + - ``ValueError`` if: + - The controller features required for the fabric type are not + running on the controller. + - The playbook parameters are invalid. + - The controller returns an error when attempting to retrieve + the template. + - The controller returns an error when attempting to retrieve + the fabric details. + - The controller returns an error when attempting to create + the fabric. + - The controller returns an error when attempting to update """ method_name = inspect.stack()[0][3] # pylint: disable=unused-variable msg = f"{self.class_name}.{method_name}: entered" self.log.debug(msg) - self.rest_send = RestSend(self.ansible_module) self.fabric_details.rest_send = self.rest_send self.fabric_summary.rest_send = self.rest_send + self.fabric_details.results = Results() + self.fabric_summary.results = Results() + self.get_controller_features() self.get_want() self.get_have() @@ -2697,9 +2804,15 @@ def commit(self): def send_need_create(self) -> None: """ - Caller: commit() - + ### Summary Build and send the payload to create fabrics specified in the playbook. + + ### Raises + + - ``ValueError`` if: + - Any payload is invalid. + - The controller returns an error when attempting to create + the fabric. """ method_name = inspect.stack()[0][3] # pylint: disable=unused-variable msg = f"{self.class_name}.{method_name}: entered. " @@ -2719,22 +2832,24 @@ def send_need_create(self) -> None: try: self.fabric_create.payloads = self.need_create except ValueError as error: - self.ansible_module.fail_json( - f"{error}", **self.fabric_create.results.failed_result - ) + raise ValueError(f"{error}") from error try: self.fabric_create.commit() except ValueError as error: - self.ansible_module.fail_json( - f"{error}", **self.fabric_create.results.failed_result - ) + raise ValueError(f"{error}") from error def send_need_update(self) -> None: """ - Caller: commit() - + ### Summary Build and send the payload to create fabrics specified in the playbook. + + ### Raises + + - ``ValueError`` if: + - Any payload is invalid. + - The controller returns an error when attempting to update + the fabric. """ method_name = inspect.stack()[0][3] # pylint: disable=unused-variable msg = f"{self.class_name}.{method_name}: entered. " @@ -2750,33 +2865,39 @@ def send_need_update(self) -> None: self.fabric_update.fabric_details = self.fabric_details self.fabric_update.fabric_summary = self.fabric_summary - self.fabric_update.rest_send = RestSend(self.ansible_module) + self.fabric_update.rest_send = self.rest_send self.fabric_update.results = self.results try: self.fabric_update.payloads = self.need_update except ValueError as error: - self.ansible_module.fail_json( - f"{error}", **self.fabric_update.results.failed_result - ) + raise ValueError(f"{error}") from error try: self.fabric_update.commit() except ValueError as error: - self.ansible_module.fail_json( - f"{error}", **self.fabric_update.results.failed_result - ) + raise ValueError(f"{error}") from error class Query(Common): """ - Handle query state + ### Summary + Handle query state. + + ### Raises + + - ``ValueError`` if: + - The playbook parameters are invalid. + - The controller returns an error when attempting to retrieve + the fabric details. """ - def __init__(self, ansible_module): + def __init__(self, params): self.class_name = self.__class__.__name__ - super().__init__(ansible_module) - method_name = inspect.stack()[0][3] # pylint: disable=unused-variable + super().__init__(params) + + self.action = "fabric_query" + self._implemented_states.add("query") self.log = logging.getLogger(f"dcnm.{self.class_name}") @@ -2785,44 +2906,61 @@ def __init__(self, ansible_module): 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 + ### Summary + query the fabrics in ``self.want`` that exist on the controller. + + ### Raises - self.fabric_details = FabricDetailsByName(self.params) - self.fabric_details.rest_send = RestSend(self.ansible_module) + - ``ValueError`` if: + - Any fabric names are invalid. + - The controller returns an error when attempting to + query the fabrics. + """ + self.fabric_details = FabricDetailsByName() + self.fabric_details.rest_send = self.rest_send + self.fabric_details.results = Results() self.get_want() - fabric_query = FabricQuery(self.params) + fabric_query = FabricQuery() fabric_query.fabric_details = self.fabric_details - + fabric_query.rest_send = self.rest_send fabric_query.results = self.results + fabric_names_to_query = [] for want in self.want: fabric_names_to_query.append(want["FABRIC_NAME"]) try: fabric_query.fabric_names = copy.copy(fabric_names_to_query) except ValueError as error: - self.ansible_module.fail_json( - f"{error}", **fabric_query.results.failed_result - ) + raise ValueError(f"{error}") from error try: fabric_query.commit() except ValueError as error: - self.ansible_module.fail_json( - f"{error}", **fabric_query.results.failed_result - ) + raise ValueError(f"{error}") from error class Replaced(Common): """ - Handle replaced state + ### Summary + Handle replaced state. + + ### Raises + + - ``ValueError`` if: + - The controller features required for the fabric type are not + running on the controller. + - The playbook parameters are invalid. + - The controller returns an error when attempting to retrieve + the template. + - The controller returns an error when attempting to retrieve + the fabric details. + - The controller returns an error when attempting to create + the fabric. + - The controller returns an error when attempting to update """ def __init__(self, params): @@ -2830,11 +2968,12 @@ def __init__(self, params): super().__init__(params) method_name = inspect.stack()[0][3] # pylint: disable=unused-variable + self.action = "fabric_replaced" self.log = logging.getLogger(f"dcnm.{self.class_name}") - self.fabric_details = FabricDetailsByName(self.params) - self.fabric_replaced = FabricReplacedBulk(self.params) - self.fabric_summary = FabricSummary(self.params) + self.fabric_details = FabricDetailsByName() + self.fabric_replaced = FabricReplacedBulk() + self.fabric_summary = FabricSummary() self.fabric_types = FabricTypes() self.merged = None self.need_create = [] @@ -2849,9 +2988,13 @@ def __init__(self, params): def get_need(self): """ - Caller: commit() + ### Summary + Build ``self.need`` for replaced state. - Build self.need for replaced state + ### Raises + - ``ValueError`` if: + - The controller features required for the fabric type are not + running on the controller. """ method_name = inspect.stack()[0][3] self.payloads = {} @@ -2874,22 +3017,31 @@ def get_need(self): msg += "controller. Review controller settings at " msg += "Fabric Controller -> Admin -> System Settings -> " msg += "Feature Management" - self.ansible_module.fail_json(f"{msg}", **self.results.failed_result) + raise ValueError(msg) self.need_replaced.append(want) def commit(self): """ - Commit the replaced state request + ### Summary + Commit the replaced state request. + + ### Raises + + - ``ValueError`` if: + - The controller features required for the fabric type are not + running on the controller. """ - method_name = inspect.stack()[0][3] # pylint: disable=unused-variable + method_name = inspect.stack()[0][3] msg = f"{self.class_name}.{method_name}: entered" self.log.debug(msg) - self.rest_send = RestSend(self.ansible_module) self.fabric_details.rest_send = self.rest_send self.fabric_summary.rest_send = self.rest_send + self.fabric_details.results = Results() + self.fabric_summary.results = Results() + self.get_controller_features() self.get_want() self.get_have() @@ -2898,11 +3050,16 @@ def commit(self): def send_need_replaced(self) -> None: """ - Caller: commit() - + ### Summary Build and send the payload to modify fabrics specified in the playbook per replaced state handling. + ### Raises + + - ``ValueError`` if: + - Any payload is invalid. + - The controller returns an error when attempting to + update the fabric. """ method_name = inspect.stack()[0][3] # pylint: disable=unused-variable msg = f"{self.class_name}.{method_name}: entered. " @@ -2912,7 +3069,6 @@ def send_need_replaced(self) -> None: if len(self.need_create) != 0: self.merged = Merged(self.params) - self.merged.ansible_module = self.ansible_module self.merged.rest_send = self.rest_send self.merged.fabric_details.rest_send = self.rest_send self.merged.fabric_summary.rest_send = self.rest_send @@ -2928,26 +3084,35 @@ def send_need_replaced(self) -> None: self.fabric_replaced.fabric_details = self.fabric_details self.fabric_replaced.fabric_summary = self.fabric_summary - self.fabric_replaced.rest_send = RestSend(self.ansible_module) + self.fabric_replaced.rest_send = self.rest_send self.fabric_replaced.results = self.results try: self.fabric_replaced.payloads = self.need_replaced except ValueError as error: - self.ansible_module.fail_json( - f"{error}", **self.fabric_replaced.results.failed_result - ) + raise ValueError(f"{error}") from error try: self.fabric_replaced.commit() except ValueError as error: - self.ansible_module.fail_json( - f"{error}", **self.fabric_replaced.results.failed_result - ) + raise ValueError(f"{error}") from error def main(): - """main entry point for module execution""" + """ + ### Summary + main entry point for module execution. + + - In the event that ``ValueError`` is raised, ``AnsibleModule.fail_json`` + is called with the error message. + - Else, ``AnsibleModule.exit_json`` is called with the final result. + + ### Raises + - ``ValueError`` if: + - The playbook parameters are invalid. + - The controller returns an error when attempting to + delete, create, query, or update the fabrics. + """ argument_spec = {} argument_spec["config"] = {"required": False, "type": "list", "elements": "dict"} @@ -2959,51 +3124,38 @@ def main(): ansible_module = AnsibleModule( argument_spec=argument_spec, supports_check_mode=True ) - log = Log(ansible_module) - - # Create the base/parent logger for the dcnm collection. - # Set the following environment variable to enable logging: - # - NDFC_LOGGING_CONFIG= - # logging_config.json must be must be conformant with logging.config.dictConfig - # and must not log to the console. - # For an example logging_config.json configuration, see: - # $ANSIBLE_COLLECTIONS_PATH/cisco/dcnm/plugins/module_utils/common/logging_config.json - config_file = environ.get("NDFC_LOGGING_CONFIG", None) - if config_file is not None: - log.config = config_file + params = copy.deepcopy(ansible_module.params) + params["check_mode"] = ansible_module.check_mode + + # Logging setup try: + log = Log() log.commit() - except json.decoder.JSONDecodeError as error: - msg = f"Invalid logging configuration file: {log.config}. " - msg += f"Error detail: {error}" - ansible_module.fail_json(msg) except ValueError as error: - msg = f"Invalid logging configuration file: {log.config}. " - msg += f"Error detail: {error}" - ansible_module.fail_json(msg) - - ansible_module.params["check_mode"] = ansible_module.check_mode - if ansible_module.params["state"] == "merged": - task = Merged(ansible_module.params) - task.ansible_module = ansible_module - task.commit() - elif ansible_module.params["state"] == "deleted": - task = Deleted(ansible_module.params) - task.ansible_module = ansible_module - task.commit() - elif ansible_module.params["state"] == "query": - task = Query(ansible_module.params) - task.ansible_module = ansible_module - task.commit() - elif ansible_module.params["state"] == "replaced": - task = Replaced(ansible_module.params) - task.ansible_module = ansible_module + ansible_module.fail_json(str(error)) + + sender = Sender() + sender.ansible_module = ansible_module + rest_send = RestSend(params) + rest_send.response_handler = ResponseHandler() + rest_send.sender = sender + + try: + task = None + if params["state"] == "merged": + task = Merged(params) + elif params["state"] == "deleted": + task = Deleted(params) + elif params["state"] == "query": + task = Query(params) + elif params["state"] == "replaced": + task = Replaced(params) + if task is None: + ansible_module.fail_json(f"Invalid state: {params['state']}") + task.rest_send = rest_send task.commit() - else: - # We should never get here since the state parameter has - # already been validated. - msg = f"Unknown state {task.ansible_module.params['state']}" - ansible_module.fail_json(msg) + except ValueError as error: + ansible_module.fail_json(f"{error}", **task.results.failed_result) task.results.build_final_result() diff --git a/plugins/modules/dcnm_image_upgrade.py b/plugins/modules/dcnm_image_upgrade.py index 7bd6de004..527a05aa0 100644 --- a/plugins/modules/dcnm_image_upgrade.py +++ b/plugins/modules/dcnm_image_upgrade.py @@ -13,6 +13,7 @@ # 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 @@ -404,88 +405,153 @@ import inspect import json import logging -from typing import Any, 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 \ +from ansible_collections.cisco.dcnm.plugins.module_utils.common.image_policies import \ + ImagePolicies +from ansible_collections.cisco.dcnm.plugins.module_utils.common.log_v2 import \ + Log +from ansible_collections.cisco.dcnm.plugins.module_utils.common.merge_dicts_v2 import \ MergeDicts -from ansible_collections.cisco.dcnm.plugins.module_utils.common.params_merge_defaults import \ +from ansible_collections.cisco.dcnm.plugins.module_utils.common.params_merge_defaults_v2 import \ ParamsMergeDefaults -from ansible_collections.cisco.dcnm.plugins.module_utils.common.params_validate import \ +from ansible_collections.cisco.dcnm.plugins.module_utils.common.params_validate_v2 import \ ParamsValidate -from ansible_collections.cisco.dcnm.plugins.module_utils.image_upgrade.api_endpoints import \ - ApiEndpoints -from ansible_collections.cisco.dcnm.plugins.module_utils.image_upgrade.image_policies import \ - ImagePolicies -from ansible_collections.cisco.dcnm.plugins.module_utils.image_upgrade.image_policy_action import \ - ImagePolicyAction +from ansible_collections.cisco.dcnm.plugins.module_utils.common.properties import \ + Properties +from ansible_collections.cisco.dcnm.plugins.module_utils.common.response_handler import \ + ResponseHandler +from ansible_collections.cisco.dcnm.plugins.module_utils.common.rest_send_v2 import \ + RestSend +from ansible_collections.cisco.dcnm.plugins.module_utils.common.results import \ + Results +from ansible_collections.cisco.dcnm.plugins.module_utils.common.sender_dcnm import \ + Sender +from ansible_collections.cisco.dcnm.plugins.module_utils.common.switch_details import \ + SwitchDetails +from ansible_collections.cisco.dcnm.plugins.module_utils.image_upgrade.image_policy_attach import \ + ImagePolicyAttach +from ansible_collections.cisco.dcnm.plugins.module_utils.image_upgrade.image_policy_detach import \ + ImagePolicyDetach from ansible_collections.cisco.dcnm.plugins.module_utils.image_upgrade.image_stage import \ ImageStage from ansible_collections.cisco.dcnm.plugins.module_utils.image_upgrade.image_upgrade import \ ImageUpgrade -from ansible_collections.cisco.dcnm.plugins.module_utils.image_upgrade.image_upgrade_common import \ - ImageUpgradeCommon -from ansible_collections.cisco.dcnm.plugins.module_utils.image_upgrade.image_upgrade_task_result import \ - ImageUpgradeTaskResult from ansible_collections.cisco.dcnm.plugins.module_utils.image_upgrade.image_validate import \ ImageValidate from ansible_collections.cisco.dcnm.plugins.module_utils.image_upgrade.install_options import \ ImageInstallOptions -from ansible_collections.cisco.dcnm.plugins.module_utils.image_upgrade.switch_details import \ - SwitchDetails +from ansible_collections.cisco.dcnm.plugins.module_utils.image_upgrade.params_spec import \ + ParamsSpec from ansible_collections.cisco.dcnm.plugins.module_utils.image_upgrade.switch_issu_details import \ SwitchIssuDetailsByIpAddress -class ImageUpgradeTask(ImageUpgradeCommon): +def json_pretty(msg): """ - Classes and methods for Ansible support of Nexus image upgrade. + Return a pretty-printed JSON string for logging messages + """ + return json.dumps(msg, indent=4, sort_keys=True) - Ansible states "merged", "deleted", and "query" are implemented. - merged: stage, validate, upgrade image for one or more devices - deleted: delete image policy from one or more devices - query: return switch issu details for one or more devices +@Properties.add_rest_send +class Common: + """ + ### Summary + Common methods for Ansible support of Nexus image upgrade. + + ### Raises + - ``TypeError`` if + - ``params`` is not a dict. + - ``ValueError`` if + - params.check_mode is missing. + - params.state is missing. + - params.state is not one of + - ``deleted`` + - ``merged`` + - ``query`` """ - def __init__(self, ansible_module): - super().__init__(ansible_module) + def __init__(self, params): self.class_name = self.__class__.__name__ method_name = inspect.stack()[0][3] self.log = logging.getLogger(f"dcnm.{self.class_name}") - self.log.debug("ENTERED ImageUpgradeTask()") - self.endpoints = ApiEndpoints() + self.valid_states = ["deleted", "merged", "query"] + self.check_mode = None + self.config = None + self.state = None + self.params = params + self.validate_params() + self.results = Results() + self.results.state = self.state + self.results.check_mode = self.check_mode + + self._rest_send = None self.have = None self.idempotent_want = None # populated in self._merge_global_and_switch_configs() self.switch_configs = [] - self.path = None - self.verb = None + self.want = [] + self.need = [] + + self.switch_details = SwitchDetails() + self.image_policies = ImagePolicies() + self.install_options = ImageInstallOptions() + self.image_policy_attach = ImagePolicyAttach() + self.params_spec = ParamsSpec() - self.config = ansible_module.params.get("config", {}) + self.image_policies.results = self.results + self.install_options.results = self.results + self.image_policy_attach.results = self.results + + msg = f"ENTERED Common().{method_name}: " + msg += f"state: {self.state}, " + msg += f"check_mode: {self.check_mode}" + self.log.debug(msg) + def validate_params(self): + """ + ### Summary + Validate ``params`` passed to __init__(). + + ### Raises + - ``TypeError`` if + - ``params`` is not a dict. + - ``ValueError`` if + - params.check_mode is missing. + - params.state is missing. + - params.state is not one of + - ``deleted`` + - ``merged`` + - ``query`` + """ + method_name = inspect.stack()[0][3] + + self.check_mode = self.params.get("check_mode", None) + if self.check_mode is None: + msg = f"{self.class_name}.{method_name}: " + msg += "check_mode is required." + raise ValueError(msg) + self.config = self.params.get("config", None) if not isinstance(self.config, dict): msg = f"{self.class_name}.{method_name}: " msg += "expected dict type for self.config. " msg += f"got {type(self.config).__name__}" - self.ansible_module.fail_json(msg) - - self.check_mode = False - - self.validated = {} - self.want = [] - self.need = [] - - self.task_result = ImageUpgradeTaskResult(self.ansible_module) - self.task_result.changed = False - - self.switch_details = SwitchDetails(self.ansible_module) - self.image_policies = ImagePolicies(self.ansible_module) + raise TypeError(msg) + self.state = self.params.get("state", None) + if self.state is None: + msg = f"{self.class_name}.{method_name}: " + msg += "params is missing state parameter." + raise ValueError(msg) + if self.state not in self.valid_states: + msg = f"{self.class_name}.{method_name}: " + msg += f"Invalid state: {self.state}. " + msg += f"Expected one of: {','.join(self.valid_states)}." + raise ValueError(msg) def get_have(self) -> None: """ @@ -493,51 +559,49 @@ def get_have(self) -> None: Determine current switch ISSU state on the controller """ - method_name = inspect.stack()[0][3] # pylint: disable=unused-variable + method_name = inspect.stack()[0][3] - self.have = SwitchIssuDetailsByIpAddress(self.ansible_module) + msg = f"ENTERED {self.class_name}.{method_name}" + self.log.debug(msg) + self.have = SwitchIssuDetailsByIpAddress() + self.have.rest_send = self.rest_send # pylint: disable=no-member + # Set to Results() instead of self.results so as not to clutter + # the playbook results. + self.have.results = Results() self.have.refresh() def get_want(self) -> None: """ - Caller: main() - - Update self.want for all switches defined in the playbook + ### Summary + Update self.want for all switches defined in the playbook. """ - msg = "Calling _merge_global_and_switch_configs with " + method_name = inspect.stack()[0][3] + + msg = f"{self.class_name}.{method_name}: " + msg += "Calling _merge_global_and_switch_configs with " msg += f"self.config: {json.dumps(self.config, indent=4, sort_keys=True)}" self.log.debug(msg) self._merge_global_and_switch_configs(self.config) - self._merge_defaults_to_switch_configs() - - msg = "Calling _validate_switch_configs with self.switch_configs: " - msg += f"{json.dumps(self.switch_configs, indent=4, sort_keys=True)}" - self.log.debug(msg) - self._validate_switch_configs() self.want = self.switch_configs - msg = f"self.want: {json.dumps(self.want, indent=4, sort_keys=True)}" + msg = f"{self.class_name}.{method_name}: " + msg += f"self.want: {json.dumps(self.want, indent=4, sort_keys=True)}" self.log.debug(msg) - if len(self.want) == 0: - self.task_result.result["changed"] = False - self.ansible_module.exit_json(**self.task_result.module_result) - def _build_idempotent_want(self, want) -> None: """ + ### Summary Build an itempotent want item based on the have item contents. The have item is obtained from an instance of SwitchIssuDetails - created in self.get_have(). - - Caller: self.get_need_merged() - - want structure passed to this method: + created in get_have(). + ### want structure + ```json { 'policy': 'KR3F', 'stage': True, @@ -558,37 +622,40 @@ def _build_idempotent_want(self, want) -> None: 'validate': True, 'ip_address': '172.22.150.102' } + ``` The returned idempotent_want structure is identical to the above structure, except that the policy_changed key is added, and values are modified based on results from the have item, and the information returned by ImageInstallOptions. - """ - msg = f"want: {json.dumps(want, indent=4, sort_keys=True)}" + method_name = inspect.stack()[0][3] + + msg = f"ENTERED {self.class_name}.{method_name}: " + msg += f"want: {json.dumps(want, indent=4, sort_keys=True)}" self.log.debug(msg) + # start with a copy of the want item with policy_changed = True + want["policy_changed"] = True + self.idempotent_want = copy.deepcopy(want) + self.have.filter = want["ip_address"] - want["policy_changed"] = True # The switch does not have an image policy attached. # idempotent_want == want with policy_changed = True if self.have.serial_number is None: - self.idempotent_want = copy.deepcopy(want) return # The switch has an image policy attached which is # different from the want policy. # idempotent_want == want with policy_changed = True if want["policy"] != self.have.policy: - self.idempotent_want = copy.deepcopy(want) return - # start with a copy of the want item - self.idempotent_want = copy.deepcopy(want) - # Give an indication to the caller that the policy has not changed - # We can use this later to determine if we need to do anything in - # the case where the image is already staged and/or upgraded. + # Give an indication to the caller that the image policy has not + # changed. This can be used later to determine if we need to do + # anything in the case where the image is already staged and/or + # upgraded. self.idempotent_want["policy_changed"] = False # if the image is already staged, don't stage it again @@ -598,295 +665,101 @@ def _build_idempotent_want(self, want) -> None: if self.have.validated == "Success": self.idempotent_want["validate"] = False - msg = f"self.have.reason: {self.have.reason}, " + msg = f"{self.class_name}.{method_name}: " + msg += f"self.have.reason: {self.have.reason}, " msg += f"self.have.policy: {self.have.policy}, " msg += f"idempotent_want[policy]: {self.idempotent_want['policy']}, " msg += f"self.have.upgrade: {self.have.upgrade}" self.log.debug(msg) - # if the image is already upgraded, don't upgrade it again - if ( - self.have.reason == "Upgrade" - and self.have.policy == self.idempotent_want["policy"] - # If upgrade is other than Success, we need to try to upgrade - # again. So only change upgrade.nxos if upgrade is Success. - and self.have.upgrade == "Success" - ): + # if the image is already upgraded, don't upgrade it again. + # if the upgrade was previously unsuccessful, we need to try + # to upgrade again. + if self.have.reason == "Upgrade" and self.have.upgrade == "Success": msg = "Set upgrade nxos to False" self.log.debug(msg) self.idempotent_want["upgrade"]["nxos"] = False # Get relevant install options from the controller # based on the options in our idempotent_want item - instance = ImageInstallOptions(self.ansible_module) - instance.policy_name = self.idempotent_want["policy"] - instance.serial_number = self.have.serial_number - - instance.epld = want.get("upgrade", {}).get("epld", False) - instance.issu = self.idempotent_want.get("upgrade", {}).get("nxos", False) - instance.package_install = ( + self.install_options.policy_name = self.idempotent_want["policy"] + self.install_options.serial_number = self.have.serial_number + self.install_options.epld = want.get("upgrade", {}).get("epld", False) + self.install_options.issu = self.idempotent_want.get("upgrade", {}).get( + "nxos", False + ) + self.install_options.package_install = ( want.get("options", {}).get("package", {}).get("install", False) ) - instance.refresh() + self.install_options.refresh() - msg = "ImageInstallOptions.response: " - msg += f"{json.dumps(instance.response_data, indent=4, sort_keys=True)}" + msg = f"{self.class_name}.{method_name}: " + msg += "ImageInstallOptions.response: " + msg += f"{json.dumps(self.install_options.response_data, indent=4, sort_keys=True)}" self.log.debug(msg) - msg = "self.idempotent_want PRE EPLD CHECK: " + msg = f"{self.class_name}.{method_name}: " + msg += "self.idempotent_want PRE EPLD CHECK: " msg += f"{json.dumps(self.idempotent_want, indent=4, sort_keys=True)}" self.log.debug(msg) + msg = f"{self.class_name}.{method_name}" + msg += f"self.install_options.epld_modules: {self.install_options.epld_modules}" + self.log.debug(msg) + # if InstallOptions indicates that EPLD is already upgraded, # don't upgrade it again. - if self.needs_epld_upgrade(instance.epld_modules) is False: + if self.needs_epld_upgrade(self.install_options.epld_modules) is False: self.idempotent_want["upgrade"]["epld"] = False msg = "self.idempotent_want POST EPLD CHECK: " msg += f"{json.dumps(self.idempotent_want, indent=4, sort_keys=True)}" self.log.debug(msg) - def get_need_merged(self) -> None: - """ - Caller: main() - - For merged state, populate self.need list() with items from - our want list that are not in our have list. These items will - be sent to the controller. - """ - need: List[Dict] = [] - - msg = "self.want: " - msg += f"{json.dumps(self.want, indent=4, sort_keys=True)}" - self.log.debug(msg) - - for want in self.want: - self.have.filter = want["ip_address"] - - msg = f"self.have.serial_number: {self.have.serial_number}" - self.log.debug(msg) - - if self.have.serial_number is not None: - self._build_idempotent_want(want) - - msg = "self.idempotent_want: " - msg += f"{json.dumps(self.idempotent_want, indent=4, sort_keys=True)}" - self.log.debug(msg) - - test_idempotence = set() - test_idempotence.add(self.idempotent_want["policy_changed"]) - test_idempotence.add(self.idempotent_want["stage"]) - test_idempotence.add(self.idempotent_want["upgrade"]["nxos"]) - test_idempotence.add(self.idempotent_want["upgrade"]["epld"]) - test_idempotence.add( - self.idempotent_want["options"]["package"]["install"] - ) - # NOTE: InstallOptions doesn't seem to have a way to determine package uninstall. - # NOTE: For now, we'll comment this out so that it doesn't muck up idempotence. - # test_idempotence.add(self.idempotent_want["options"]["package"]["uninstall"]) - if True not in test_idempotence: - continue - need.append(self.idempotent_want) - self.need = copy.copy(need) - - def get_need_deleted(self) -> None: - """ - Caller: main() - - For deleted state, populate self.need list() with items from our want - list that are not in our have list. These items will be sent to - the controller. - - Policies are detached only if the policy name matches. + def needs_epld_upgrade(self, epld_modules) -> bool: """ - need = [] - for want in self.want: - self.have.filter = want["ip_address"] - if self.have.serial_number is None: - continue - if self.have.policy is None: - continue - if self.have.policy != want["policy"]: - continue - need.append(want) - self.need = copy.copy(need) + ### Summary + Determine if the switch needs an EPLD upgrade. - def get_need_query(self) -> None: - """ - Caller: main() + For all modules, compare EPLD oldVersion and newVersion. - For query state, populate self.need list() with all items from - our want list. These items will be sent to the controller. + ### Raises + None - policy name is ignored for query state. + ### Returns + - ``True`` if newVersion > oldVersion for any module. + - ``False`` otherwise. """ - need = [] - for want in self.want: - need.append(want) - self.need = copy.copy(need) - - def _build_params_spec(self) -> Dict[str, Any]: method_name = inspect.stack()[0][3] - if self.ansible_module.params["state"] == "merged": - return self._build_params_spec_for_merged_state() - if self.ansible_module.params["state"] == "deleted": - return self._build_params_spec_for_merged_state() - if self.ansible_module.params["state"] == "query": - return self._build_params_spec_for_query_state() msg = f"{self.class_name}.{method_name}: " - msg += f"Unsupported state: {self.ansible_module.params['state']}" - self.ansible_module.fail_json(msg) - - @staticmethod - def _build_params_spec_for_merged_state() -> Dict[str, Any]: - """ - Build the specs for the parameters expected when state == merged. - - Caller: _validate_switch_configs() - Return: params_spec, a dictionary containing playbook - parameter specifications. - """ - params_spec: Dict[str, Any] = {} - params_spec["ip_address"] = {} - params_spec["ip_address"]["required"] = True - params_spec["ip_address"]["type"] = "ipv4" - - params_spec["policy"] = {} - params_spec["policy"]["required"] = False - params_spec["policy"]["type"] = "str" - - params_spec["reboot"] = {} - params_spec["reboot"]["required"] = False - params_spec["reboot"]["type"] = "bool" - params_spec["reboot"]["default"] = False - - params_spec["stage"] = {} - params_spec["stage"]["required"] = False - params_spec["stage"]["type"] = "bool" - params_spec["stage"]["default"] = True - - params_spec["validate"] = {} - params_spec["validate"]["required"] = False - params_spec["validate"]["type"] = "bool" - params_spec["validate"]["default"] = True - - params_spec["upgrade"] = {} - params_spec["upgrade"]["required"] = False - params_spec["upgrade"]["type"] = "dict" - params_spec["upgrade"]["default"] = {} - params_spec["upgrade"]["epld"] = {} - params_spec["upgrade"]["epld"]["required"] = False - params_spec["upgrade"]["epld"]["type"] = "bool" - params_spec["upgrade"]["epld"]["default"] = False - params_spec["upgrade"]["nxos"] = {} - params_spec["upgrade"]["nxos"]["required"] = False - params_spec["upgrade"]["nxos"]["type"] = "bool" - params_spec["upgrade"]["nxos"]["default"] = True - - section = "options" - params_spec[section] = {} - params_spec[section]["required"] = False - params_spec[section]["type"] = "dict" - params_spec[section]["default"] = {} - - sub_section = "nxos" - params_spec[section][sub_section] = {} - params_spec[section][sub_section]["required"] = False - params_spec[section][sub_section]["type"] = "dict" - params_spec[section][sub_section]["default"] = {} - - params_spec[section][sub_section]["mode"] = {} - params_spec[section][sub_section]["mode"]["required"] = False - params_spec[section][sub_section]["mode"]["type"] = "str" - params_spec[section][sub_section]["mode"]["default"] = "disruptive" - params_spec[section][sub_section]["mode"]["choices"] = [ - "disruptive", - "non_disruptive", - "force_non_disruptive", - ] - - params_spec[section][sub_section]["bios_force"] = {} - params_spec[section][sub_section]["bios_force"]["required"] = False - params_spec[section][sub_section]["bios_force"]["type"] = "bool" - params_spec[section][sub_section]["bios_force"]["default"] = False - - sub_section = "epld" - params_spec[section][sub_section] = {} - params_spec[section][sub_section]["required"] = False - params_spec[section][sub_section]["type"] = "dict" - params_spec[section][sub_section]["default"] = {} - - params_spec[section][sub_section]["module"] = {} - params_spec[section][sub_section]["module"]["required"] = False - params_spec[section][sub_section]["module"]["type"] = ["str", "int"] - params_spec[section][sub_section]["module"]["preferred_type"] = "str" - params_spec[section][sub_section]["module"]["default"] = "ALL" - params_spec[section][sub_section]["module"]["choices"] = [ - str(x) for x in range(1, 33) - ] - params_spec[section][sub_section]["module"]["choices"].extend( - list(range(1, 33)) - ) - params_spec[section][sub_section]["module"]["choices"].append("ALL") - - params_spec[section][sub_section]["golden"] = {} - params_spec[section][sub_section]["golden"]["required"] = False - params_spec[section][sub_section]["golden"]["type"] = "bool" - params_spec[section][sub_section]["golden"]["default"] = False - - sub_section = "reboot" - params_spec[section][sub_section] = {} - params_spec[section][sub_section]["required"] = False - params_spec[section][sub_section]["type"] = "dict" - params_spec[section][sub_section]["default"] = {} - - params_spec[section][sub_section]["config_reload"] = {} - params_spec[section][sub_section]["config_reload"]["required"] = False - params_spec[section][sub_section]["config_reload"]["type"] = "bool" - params_spec[section][sub_section]["config_reload"]["default"] = False - - params_spec[section][sub_section]["write_erase"] = {} - params_spec[section][sub_section]["write_erase"]["required"] = False - params_spec[section][sub_section]["write_erase"]["type"] = "bool" - params_spec[section][sub_section]["write_erase"]["default"] = False - - sub_section = "package" - params_spec[section][sub_section] = {} - params_spec[section][sub_section]["required"] = False - params_spec[section][sub_section]["type"] = "dict" - params_spec[section][sub_section]["default"] = {} - - params_spec[section][sub_section]["install"] = {} - params_spec[section][sub_section]["install"]["required"] = False - params_spec[section][sub_section]["install"]["type"] = "bool" - params_spec[section][sub_section]["install"]["default"] = False - - params_spec[section][sub_section]["uninstall"] = {} - params_spec[section][sub_section]["uninstall"]["required"] = False - params_spec[section][sub_section]["uninstall"]["type"] = "bool" - params_spec[section][sub_section]["uninstall"]["default"] = False - - return copy.deepcopy(params_spec) - - @staticmethod - def _build_params_spec_for_query_state() -> Dict[str, Any]: - """ - Build the specs for the parameters expected when state == query. - - Caller: _validate_switch_configs() - Return: params_spec, a dictionary containing playbook - parameter specifications. - """ - params_spec: Dict[str, Any] = {} - params_spec["ip_address"] = {} - params_spec["ip_address"]["required"] = True - params_spec["ip_address"]["type"] = "ipv4" + msg += f"epld_modules: {epld_modules}" + self.log.debug(msg) - return copy.deepcopy(params_spec) + if epld_modules is None: + return False + if epld_modules.get("moduleList") is None: + return False + for module in epld_modules["moduleList"]: + new_version = module.get("newVersion", "0x0") + old_version = module.get("oldVersion", "0x0") + # int(str, 0) enables python to guess the base + # of the str when converting to int. An + # error is thrown without this. + if int(new_version, 0) > int(old_version, 0): + msg = f"(device: {module.get('deviceName')}), " + msg += f"(IP: {module.get('ipAddress')}), " + msg += f"(module#: {module.get('module')}), " + msg += f"(module: {module.get('moduleType')}), " + msg += f"new_version {new_version} > old_version {old_version}, " + msg += "returning True" + self.log.debug(msg) + return True + return False def _merge_global_and_switch_configs(self, config) -> None: """ + ### Summary Merge the global config with each switch config and populate list of merged configs self.switch_configs. @@ -900,13 +773,18 @@ def _merge_global_and_switch_configs(self, config) -> None: is one (see self._merge_defaults_to_switch_configs) 5. If global_config and switch_config are both missing a mandatory parameter, fail (see self._validate_switch_configs) + + ### Raises + - ``ValueError`` if: + - Playbook is missing list of switches. + - ``MergedDicts()`` raises an error. """ method_name = inspect.stack()[0][3] if not config.get("switches"): msg = f"{self.class_name}.{method_name}: " msg += "playbook is missing list of switches" - self.ansible_module.fail_json(msg) + raise ValueError(msg) self.switch_configs = [] merged_configs = [] @@ -923,11 +801,17 @@ def _merge_global_and_switch_configs(self, config) -> None: msg = f"switch PRE_MERGE : {json.dumps(switch, indent=4, sort_keys=True)}" self.log.debug(msg) - merge_dicts = MergeDicts(self.ansible_module) - merge_dicts.dict1 = global_config - merge_dicts.dict2 = switch - merge_dicts.commit() - switch_config = merge_dicts.dict_merged + try: + merge = MergeDicts() + merge.dict1 = global_config + merge.dict2 = switch + merge.commit() + except (TypeError, ValueError) as error: + msg = f"{self.class_name}.{method_name}: " + msg += "Error during MergeDicts(). " + msg += f"Error detail: {error}" + raise ValueError(msg) from error + switch_config = merge.dict_merged msg = f"switch POST_MERGE: {json.dumps(switch_config, indent=4, sort_keys=True)}" self.log.debug(msg) @@ -940,108 +824,234 @@ def _merge_defaults_to_switch_configs(self) -> None: For any items in config which are not set, apply the default value from params_spec (if a default value exists). """ + method_name = inspect.stack()[0][3] + + msg = f"ENTERED {self.class_name}.{method_name}." + self.log.debug(msg) + + self.params_spec.params = self.params + self.params_spec.commit() + configs_to_merge = copy.copy(self.switch_configs) merged_configs = [] - merge = ParamsMergeDefaults(self.ansible_module) - merge.params_spec = self._build_params_spec() + merge_defaults = ParamsMergeDefaults() + merge_defaults.params_spec = self.params_spec.params_spec for switch_config in configs_to_merge: - merge.parameters = switch_config - merge.commit() - merged_configs.append(merge.merged_parameters) + merge_defaults.parameters = switch_config + merge_defaults.commit() + merged_configs.append(merge_defaults.merged_parameters) self.switch_configs = copy.copy(merged_configs) def _validate_switch_configs(self) -> None: """ - Verify parameters for each switch - - fail_json if any parameters are not valid - - fail_json if any mandatory parameters are missing - - Callers: - - self.get_want + ### Summary + Verify parameters for each switch. + + ### Raises + - ``ValueError`` if: + - Any parameter is not valid. + - Mandatory parameters are missing. + - params is not a dict. + - params is missing ``state`` key. + - params ``state`` is not one of: + - ``deleted`` + - ``merged`` + - ``query`` """ - validator = ParamsValidate(self.ansible_module) - validator.params_spec = self._build_params_spec() + method_name = inspect.stack()[0][3] + + try: + self.params_spec.params = self.params + self.params_spec.commit() + except (TypeError, ValueError) as error: + msg = f"{self.class_name}.{method_name}: " + msg += "Error during ParamsSpec(). " + msg += f"Error detail: {error}" + raise ValueError(msg) from error + + validator = ParamsValidate() + try: + validator.params_spec = self.params_spec.params_spec + for switch in self.switch_configs: + validator.parameters = switch + validator.commit() + except (TypeError, ValueError) as error: + msg = f"{self.class_name}.{method_name}: " + msg += "Error during ParamsValidate(). " + msg += f"Error detail: {error}" + raise ValueError(msg) from error + + +class Merged(Common): + """ + ### Summary + Handle merged state + + ### Raises + - ``ValueError`` if: + - ``params`` is missing ``config`` key. + - ``commit()`` is issued before setting mandatory properties + """ + + def __init__(self, params): + self.class_name = self.__class__.__name__ + method_name = inspect.stack()[0][3] + try: + super().__init__(params) + except (TypeError, ValueError) as error: + msg = f"{self.class_name}.{method_name}: " + msg += "Error during super().__init__(). " + msg += f"Error detail: {error}" + raise ValueError(msg) from error + + msg = f"params: {json_pretty(self.params)}" + self.log.debug(msg) + if not params.get("config"): + msg = f"playbook config is required for {self.state}" + raise ValueError(msg) - for switch in self.switch_configs: - validator.parameters = switch - validator.commit() + msg = f"ENTERED {self.class_name}().{method_name}: " + msg += f"state: {self.state}, " + msg += f"check_mode: {self.check_mode}" + self.log.debug(msg) - def _attach_or_detach_image_policy(self, action=None) -> None: + def validate_commit_parameters(self) -> None: """ - Attach or detach image policies to/from switches - action valid values: attach, detach + ### Summary + Verify mandatory parameters are set before calling commit. - Caller: - - self.handle_merged_state - - self.handle_deleted_state + ### Raises + - ``ValueError`` if: + - ``rest_send`` is not set. + - ``results`` is not set. + """ + method_name = inspect.stack()[0][3] + msg = f"ENTERED {self.class_name}.{method_name}" + self.log.debug(msg) - NOTES: - - Sanity checking for action is done in ImagePolicyAction + if self.rest_send is None: + msg = f"{self.class_name}.{method_name}: " + msg += "rest_send must be set before calling commit()." + raise ValueError(msg) + if self.results is None: + msg = f"{self.class_name}.{method_name}: " + msg += "results must be set before calling commit()." + raise ValueError(msg) + + def commit(self) -> None: + """ + ### Summary + - Update the switch policy if it has changed. + - Stage the image if requested. + - Validate the image if requested. + - Upgrade the image if requested. """ - msg = f"ENTERED: action: {action}" + method_name = inspect.stack()[0][3] + msg = f"ENTERED {self.class_name}.{method_name}" self.log.debug(msg) - serial_numbers_to_update: Dict[str, Any] = {} + self.validate_commit_parameters() + + self.install_options.rest_send = self.rest_send + self.image_policies.rest_send = self.rest_send + self.image_policy_attach.rest_send = self.rest_send + self.switch_details.rest_send = self.rest_send + # We don't want switch_details results to be saved in self.results + self.switch_details.results = Results() + + self.get_have() + self.get_want() + if len(self.want) == 0: + return + self.get_need() + self.attach_image_policy() + + stage_devices: list[str] = [] + validate_devices: list[str] = [] + upgrade_devices: list[dict] = [] + self.switch_details.refresh() - self.image_policies.refresh() for switch in self.need: - self.switch_details.ip_address = switch.get("ip_address") - self.image_policies.policy_name = switch.get("policy") - # ImagePolicyAction wants a policy name and a list of serial_number - # Build dictionary, serial_numbers_to_udate, keyed on policy name - # whose value is the list of serial numbers to attach/detach. - if self.image_policies.name not in serial_numbers_to_update: - serial_numbers_to_update[self.image_policies.policy_name] = [] + msg = f"{self.class_name}.{method_name}: " + msg = f"switch: {json.dumps(switch, indent=4, sort_keys=True)}" + self.log.debug(msg) - serial_numbers_to_update[self.image_policies.policy_name].append( - self.switch_details.serial_number - ) + device = {} + self.have.filter = switch.get("ip_address") + device["serial_number"] = self.have.serial_number + device["policy_name"] = switch.get("policy") + device["ip_address"] = self.have.ip_address - instance = ImagePolicyAction(self.ansible_module) - if len(serial_numbers_to_update) == 0: - msg = f"No policies to {action}" - self.log.debug(msg) + if switch.get("stage") is not False: + stage_devices.append(device["serial_number"]) + if switch.get("validate") is not False: + validate_devices.append(device["serial_number"]) + if ( + switch.get("upgrade").get("nxos") is not False + or switch.get("upgrade").get("epld") is not False + ): + upgrade_devices.append(switch) - if action == "attach": - self.task_result.diff_attach_policy = instance.diff_null - self.task_result.diff = instance.diff_null - if action == "detach": - self.task_result.diff_detach_policy = instance.diff_null - self.task_result.diff = instance.diff_null - return + msg = f"{self.class_name}.{method_name}: " + msg += f"stage_devices: {stage_devices}" + self.log.debug(msg) - for key, value in serial_numbers_to_update.items(): - instance.policy_name = key - instance.action = action - instance.serial_numbers = value - instance.commit() - if action == "attach": - self.task_result.response_attach_policy = copy.deepcopy( - instance.response_current - ) - self.task_result.response = copy.deepcopy( - instance.response_current - ) - if action == "detach": - self.task_result.response_detach_policy = copy.deepcopy( - instance.response_current - ) - self.task_result.response = copy.deepcopy( - instance.response_current - ) + msg = f"{self.class_name}.{method_name}: " + msg += f"validate_devices: {validate_devices}" + self.log.debug(msg) - for diff in instance.diff: - msg = ( - f"{instance.action} diff: {json.dumps(diff, indent=4, sort_keys=True)}" - ) + self._stage_images(stage_devices) + self._validate_images(validate_devices) + + self._verify_install_options(upgrade_devices) + self._upgrade_images(upgrade_devices) + + def get_need(self) -> None: + """ + ### Summary + For merged state, populate self.need list() with items from + our want list that are not in our have list. These items will + be sent to the controller. + """ + method_name = inspect.stack()[0][3] + need: list[dict] = [] + + msg = f"{self.class_name}.{method_name}: " + msg += "self.want: " + msg += f"{json.dumps(self.want, indent=4, sort_keys=True)}" + self.log.debug(msg) + + for want in self.want: + self.have.filter = want["ip_address"] + + msg = f"{self.class_name}.{method_name}: " + msg += f"self.have.serial_number: {self.have.serial_number}" self.log.debug(msg) - if action == "attach": - self.task_result.diff_attach_policy = copy.deepcopy(diff) - self.task_result.diff = copy.deepcopy(diff) - elif action == "detach": - self.task_result.diff_detach_policy = copy.deepcopy(diff) - self.task_result.diff = copy.deepcopy(diff) + + if self.have.serial_number is not None: + self._build_idempotent_want(want) + + msg = f"{self.class_name}.{method_name}: " + msg += "self.idempotent_want: " + msg += f"{json.dumps(self.idempotent_want, indent=4, sort_keys=True)}" + self.log.debug(msg) + + test_idempotence = set() + test_idempotence.add(self.idempotent_want["policy_changed"]) + test_idempotence.add(self.idempotent_want["stage"]) + test_idempotence.add(self.idempotent_want["upgrade"]["nxos"]) + test_idempotence.add(self.idempotent_want["upgrade"]["epld"]) + test_idempotence.add( + self.idempotent_want["options"]["package"]["install"] + ) + # NOTE: InstallOptions doesn't seem to have a way to determine package uninstall. + # NOTE: For now, we'll comment this out so that it doesn't muck up idempotence. + # test_idempotence.add(self.idempotent_want["options"]["package"]["uninstall"]) + if True not in test_idempotence: + continue + need.append(copy.deepcopy(self.idempotent_want)) + self.need = copy.copy(need) def _stage_images(self, serial_numbers) -> None: """ @@ -1051,24 +1061,16 @@ def _stage_images(self, serial_numbers) -> None: Callers: - handle_merged_state """ - msg = f"serial_numbers: {serial_numbers}" + method_name = inspect.stack()[0][3] + msg = f"{self.class_name}.{method_name}: " + msg += f"serial_numbers: {serial_numbers}" self.log.debug(msg) - instance = ImageStage(self.ansible_module) - instance.serial_numbers = serial_numbers - instance.commit() - for diff in instance.diff: - msg = "adding diff to task_result.diff_stage: " - msg += f"{json.dumps(diff, indent=4, sort_keys=True)}" - self.log.debug(msg) - self.task_result.diff_stage = copy.deepcopy(diff) - self.task_result.diff = copy.deepcopy(diff) - for response in instance.response: - msg = "adding response to task_result.response_stage: " - msg += f"{json.dumps(response, indent=4, sort_keys=True)}" - self.log.debug(msg) - self.task_result.response_stage = copy.deepcopy(response) - self.task_result.response = copy.deepcopy(response) + stage = ImageStage() + stage.rest_send = self.rest_send + stage.results = self.results + stage.serial_numbers = serial_numbers + stage.commit() def _validate_images(self, serial_numbers) -> None: """ @@ -1077,24 +1079,29 @@ def _validate_images(self, serial_numbers) -> None: Callers: - handle_merged_state """ - msg = f"serial_numbers: {serial_numbers}" + method_name = inspect.stack()[0][3] + msg = f"{self.class_name}.{method_name}: " + msg += f"serial_numbers: {serial_numbers}" self.log.debug(msg) - instance = ImageValidate(self.ansible_module) - instance.serial_numbers = serial_numbers - instance.commit() - for diff in instance.diff: - msg = "adding diff to task_result.diff_validate: " - msg += f"{json.dumps(diff, indent=4, sort_keys=True)}" - self.log.debug(msg) - self.task_result.diff_validate = copy.deepcopy(diff) - self.task_result.diff = copy.deepcopy(diff) - for response in instance.response: - msg = "adding response to task_result.response_validate: " - msg += f"{json.dumps(response, indent=4, sort_keys=True)}" - self.log.debug(msg) - self.task_result.response_validate = copy.deepcopy(response) - self.task_result.response = copy.deepcopy(response) + validate = ImageValidate() + validate.serial_numbers = serial_numbers + validate.rest_send = self.rest_send + validate.results = self.results + validate.commit() + + def _upgrade_images(self, devices) -> None: + """ + Upgrade the switch(es) to the specified image + + Callers: + - handle_merged_state + """ + upgrade = ImageUpgrade() + upgrade.rest_send = self.rest_send + upgrade.results = self.results + upgrade.devices = devices + upgrade.commit() def _verify_install_options(self, devices) -> None: """ @@ -1131,10 +1138,14 @@ def _verify_install_options(self, devices) -> None: """ method_name = inspect.stack()[0][3] + msg = f"{self.class_name}.{method_name}: " + msg += f"len(devices): {len(devices)}, " + msg += f"self.results: {self.results}" + self.log.debug(msg) + if len(devices) == 0: return - install_options = ImageInstallOptions(self.ansible_module) self.switch_details.refresh() verify_devices = copy.deepcopy(devices) @@ -1144,256 +1155,354 @@ def _verify_install_options(self, devices) -> None: self.log.debug(msg) self.switch_details.ip_address = device.get("ip_address") - install_options.serial_number = self.switch_details.serial_number - install_options.policy_name = device.get("policy") - install_options.epld = device.get("upgrade", {}).get("epld", False) - install_options.issu = device.get("upgrade", {}).get("nxos", False) - install_options.refresh() + self.install_options.serial_number = self.switch_details.serial_number + self.install_options.policy_name = device.get("policy") + self.install_options.epld = device.get("upgrade", {}).get("epld", False) + self.install_options.issu = device.get("upgrade", {}).get("nxos", False) + self.install_options.refresh() msg = "install_options.response_data: " - msg += ( - f"{json.dumps(install_options.response_data, indent=4, sort_keys=True)}" - ) + msg += f"{json.dumps(self.install_options.response_data, indent=4, sort_keys=True)}" self.log.debug(msg) if ( - install_options.status not in ["Success", "Skipped"] + self.install_options.status not in ["Success", "Skipped"] and device["upgrade"]["nxos"] is True ): msg = f"{self.class_name}.{method_name}: " msg += "NXOS upgrade is set to True for switch " msg += f"{device['ip_address']}, but the image policy " - msg += f"{install_options.policy_name} does not contain an " + msg += f"{self.install_options.policy_name} does not contain an " msg += "NX-OS image" - self.ansible_module.fail_json(msg) + raise ValueError(msg) - msg = f"install_options.epld: {install_options.epld}" + msg = f"install_options.epld: {self.install_options.epld}" self.log.debug(msg) msg = "install_options.epld_modules: " - msg += ( - f"{json.dumps(install_options.epld_modules, indent=4, sort_keys=True)}" - ) + msg += f"{json.dumps(self.install_options.epld_modules, indent=4, sort_keys=True)}" self.log.debug(msg) - if install_options.epld_modules is None and install_options.epld is True: + if ( + self.install_options.epld_modules is None + and self.install_options.epld is True + ): msg = f"{self.class_name}.{method_name}: " msg += "EPLD upgrade is set to True for switch " msg += f"{device['ip_address']}, but the image policy " - msg += f"{install_options.policy_name} does not contain an " + msg += f"{self.install_options.policy_name} does not contain an " msg += "EPLD image." - self.ansible_module.fail_json(msg) + raise ValueError(msg) - def needs_epld_upgrade(self, epld_modules) -> bool: + def attach_image_policy(self) -> None: + """ + ### Summary + Attach image policies to switches. """ - Determine if the switch needs an EPLD upgrade + method_name = inspect.stack()[0][3] + msg = f"ENTERED {self.class_name}.{method_name}." + self.log.debug(msg) - For all modules, compare EPLD oldVersion and newVersion. - Returns: - - True if newVersion > oldVersion for any module - - False otherwise + serial_numbers_to_update: dict = {} + self.switch_details.refresh() + self.image_policies.refresh() - Callers: - - self._build_idempotent_want + msg = f"{self.class_name}.{method_name}: " + msg += f"self.need: {json.dumps(self.need, indent=4, sort_keys=True)}" + self.log.debug(msg) + + for switch in self.need: + self.switch_details.filter = switch.get("ip_address") + self.image_policies.policy_name = switch.get("policy") + # ImagePolicyAttach wants a policy name and a list of serial_number. + # Build dictionary, serial_numbers_to_update, keyed on policy name, + # whose value is the list of serial numbers to attach. + if self.image_policies.name not in serial_numbers_to_update: + serial_numbers_to_update[self.image_policies.policy_name] = [] + + serial_numbers_to_update[self.image_policies.policy_name].append( + self.switch_details.serial_number + ) + + if len(serial_numbers_to_update) == 0: + msg = "No policies to attach." + self.log.debug(msg) + return + + for key, value in serial_numbers_to_update.items(): + self.image_policy_attach.policy_name = key + self.image_policy_attach.serial_numbers = value + self.image_policy_attach.commit() + + +class Deleted(Common): + """ + ### Summary + Handle deleted state. + + ### Raises + - ``ValueError`` if: + - ``params`` is missing ``config`` key. + - ``commit()`` is issued before setting mandatory properties + """ + + def __init__(self, params): + self.class_name = self.__class__.__name__ + method_name = inspect.stack()[0][3] + try: + super().__init__(params) + except (TypeError, ValueError) as error: + msg = f"{self.class_name}.{method_name}: " + msg += "Error during super().__init__(). " + msg += f"Error detail: {error}" + raise ValueError(msg) from error + + self.image_policy_detach = ImagePolicyDetach() + self.switch_issu_details = SwitchIssuDetailsByIpAddress() + + msg = f"ENTERED {self.class_name}().{method_name}: " + msg += f"state: {self.state}, " + msg += f"check_mode: {self.check_mode}" + self.log.debug(msg) + + def get_need(self) -> None: """ - if epld_modules is None: - return False - if epld_modules.get("moduleList") is None: - return False - for module in epld_modules["moduleList"]: - new_version = module.get("newVersion", "0x0") - old_version = module.get("oldVersion", "0x0") - # int(str, 0) enables python to guess the base - # of the str when converting to int. An - # error is thrown without this. - if int(new_version, 0) > int(old_version, 0): - msg = f"(device: {module.get('deviceName')}), " - msg += f"(IP: {module.get('ipAddress')}), " - msg += f"(module#: {module.get('module')}), " - msg += f"(module: {module.get('moduleType')}), " - msg += f"new_version {new_version} > old_version {old_version}, " - msg += "returning True" - self.log.debug(msg) - return True - return False + ### Summary + For deleted state, populate self.need list() with items from our want + list that are not in our have list. These items will be sent to + the controller. - def _upgrade_images(self, devices) -> None: + Policies are detached only if the policy name matches. """ - Upgrade the switch(es) to the specified image + need = [] + for want in self.want: + self.have.filter = want["ip_address"] + if self.have.serial_number is None: + continue + if self.have.policy is None: + continue + if self.have.policy != want["policy"]: + continue + need.append(want) + self.need = copy.copy(need) - Callers: - - handle_merged_state + def validate_commit_parameters(self) -> None: """ - upgrade = ImageUpgrade(self.ansible_module) - upgrade.devices = devices - upgrade.commit() - for diff in upgrade.diff: - msg = "adding diff to diff_upgrade: " - msg += f"{json.dumps(diff, indent=4, sort_keys=True)}" - self.log.debug(msg) - self.task_result.diff_upgrade = copy.deepcopy(diff) - self.task_result.diff = copy.deepcopy(diff) - for response in upgrade.response: - msg = "adding response to response_upgrade: " - msg += f"{json.dumps(response, indent=4, sort_keys=True)}" - self.log.debug(msg) - self.task_result.response_upgrade = copy.deepcopy(response) - self.task_result.response = copy.deepcopy(response) + ### Summary + Verify mandatory parameters are set before calling commit. - def handle_merged_state(self) -> None: + ### Raises + - ``ValueError`` if: + - ``rest_send`` is not set. + - ``results`` is not set. """ - Update the switch policy if it has changed. - Stage the image if requested. - Validate the image if requested. - Upgrade the image if requested. + method_name = inspect.stack()[0][3] + msg = f"ENTERED {self.class_name}.{method_name}" + self.log.debug(msg) - Caller: main() + if self.rest_send is None: + msg = f"{self.class_name}.{method_name}: " + msg += "rest_send must be set before calling commit()." + raise ValueError(msg) + if self.results is None: + msg = f"{self.class_name}.{method_name}: " + msg += "results must be set before calling commit()." + raise ValueError(msg) + + def commit(self) -> None: """ - msg = "ENTERED" + ### Summary + Detach image policies from switches. + """ + method_name = inspect.stack()[0][3] + msg = f"ENTERED {self.class_name}.{method_name}." self.log.debug(msg) - self._attach_or_detach_image_policy(action="attach") + self.validate_commit_parameters() + + self.get_have() + self.get_want() + if len(self.want) == 0: + return + self.get_need() - stage_devices: List[str] = [] - validate_devices: List[str] = [] - upgrade_devices: List[Dict[str, Any]] = [] + self.results.state = self.state + self.results.check_mode = self.check_mode - self.switch_details.refresh() + self.image_policy_detach.rest_send = self.rest_send + self.switch_issu_details.rest_send = self.rest_send + + self.image_policy_detach.results = self.results + # We don't want switch_issu_details results + # to clutter the results returned to the playbook. + self.switch_issu_details.results = Results() + + self.detach_image_policy() + + def detach_image_policy(self) -> None: + """ + ### Summary + Detach image policies from switches. + """ + method_name = inspect.stack()[0][3] + msg = f"ENTERED {self.class_name}.{method_name}." + self.log.debug(msg) + + self.switch_issu_details.refresh() + serial_numbers_to_detach: list = [] for switch in self.need: - msg = f"switch: {json.dumps(switch, indent=4, sort_keys=True)}" + self.switch_issu_details.filter = switch.get("ip_address") + if self.switch_issu_details.policy is None: + continue + serial_numbers_to_detach.append(self.switch_issu_details.serial_number) + + if len(serial_numbers_to_detach) == 0: + msg = "No policies to detach." self.log.debug(msg) + return - self.switch_details.ip_address = switch.get("ip_address") - device = {} - device["serial_number"] = self.switch_details.serial_number - self.have.filter = self.switch_details.ip_address - device["policy_name"] = switch.get("policy") - device["ip_address"] = self.switch_details.ip_address + self.image_policy_detach.serial_numbers = serial_numbers_to_detach + self.image_policy_detach.commit() - if switch.get("stage") is not False: - stage_devices.append(device["serial_number"]) - if switch.get("validate") is not False: - validate_devices.append(device["serial_number"]) - if ( - switch.get("upgrade").get("nxos") is not False - or switch.get("upgrade").get("epld") is not False - ): - upgrade_devices.append(switch) - self._stage_images(stage_devices) - self._validate_images(validate_devices) +class Query(Common): + """ + ### Summary + Handle query state. - self._verify_install_options(upgrade_devices) - self._upgrade_images(upgrade_devices) + ### Raises + - ``ValueError`` if: + - ``params`` is missing ``config`` key. + - ``commit()`` is issued before setting mandatory properties + """ + + def __init__(self, params): + self.class_name = self.__class__.__name__ + method_name = inspect.stack()[0][3] + try: + super().__init__(params) + except (TypeError, ValueError) as error: + msg = f"{self.class_name}.{method_name}: " + msg += "Error during super().__init__(). " + msg += f"Error detail: {error}" + raise ValueError(msg) from error - def handle_deleted_state(self) -> None: + self.issu_detail = SwitchIssuDetailsByIpAddress() + + msg = f"ENTERED {self.class_name}().{method_name}: " + msg += f"state: {self.state}, " + msg += f"check_mode: {self.check_mode}" + self.log.debug(msg) + + def validate_commit_parameters(self) -> None: """ - Delete the image policy from the switch(es) + ### Summary + Verify mandatory parameters are set before calling commit. - Caller: main() + ### Raises + - ``ValueError`` if: + - ``rest_send`` is not set. + - ``results`` is not set. """ - msg = "ENTERED" + method_name = inspect.stack()[0][3] + msg = f"ENTERED {self.class_name}.{method_name}" self.log.debug(msg) - self._attach_or_detach_image_policy("detach") + if self.rest_send is None: + msg = f"{self.class_name}.{method_name}: " + msg += "rest_send must be set before calling commit()." + raise ValueError(msg) + if self.results is None: + msg = f"{self.class_name}.{method_name}: " + msg += "results must be set before calling commit()." + raise ValueError(msg) - def handle_query_state(self) -> None: + def get_need(self) -> None: """ - Return the ISSU state of the switch(es) listed in the playbook + ### Summary + For query state, populate self.need list() with all items from + our want list. These items will be sent to the controller. - Caller: main() + ``policy`` name is ignored for query state. """ - instance = SwitchIssuDetailsByIpAddress(self.ansible_module) - instance.refresh() - response_current = copy.deepcopy(instance.response_current) - if "DATA" in response_current: - response_current.pop("DATA") - self.task_result.response_issu_status = copy.deepcopy(response_current) - self.task_result.response = copy.deepcopy(response_current) - for switch in self.need: - instance.filter = switch.get("ip_address") - msg = f"SwitchIssuDetailsByIpAddress.filter: {instance.filter}, " - msg += f"SwitchIssuDetailsByIpAddress.filtered_data: {json.dumps(instance.filtered_data, indent=4, sort_keys=True)}" - self.log.debug(msg) - if instance.filtered_data is None: - continue - self.task_result.diff_issu_status = instance.filtered_data - self.task_result.diff = instance.filtered_data + need = [] + for want in self.want: + need.append(want) + self.need = copy.copy(need) - def _failure(self, resp) -> None: + def commit(self) -> None: """ - Caller: self.attach_policies() + Return the ISSU state of the switch(es) listed in the playbook + + Caller: main() """ - res = copy.deepcopy(resp) + method_name = inspect.stack()[0][3] + msg = f"ENTERED {self.class_name}.{method_name}." + self.log.debug(msg) - if resp.get("DATA"): - data = copy.deepcopy(resp.get("DATA")) - if data.get("stackTrace"): - data.update( - {"stackTrace": "Stack trace is hidden, use '-vvvvv' to print it"} - ) - res.update({"DATA": data}) + self.validate_commit_parameters() + self.get_want() - self.ansible_module.fail_json(msg=res) + self.issu_detail.rest_send = self.rest_send + self.issu_detail.results = self.results + self.issu_detail.refresh() + msg = f"{self.class_name}.{method_name}: " + msg += "self.results.metadata: " + msg += f"{json.dumps(self.results.metadata, indent=4, sort_keys=True)}" + self.log.debug(msg) def main(): """main entry point for module execution""" - element_spec = { + argument_spec = { "config": {"required": True, "type": "dict"}, "state": {"default": "merged", "choices": ["merged", "deleted", "query"]}, } - 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() - - task_module = ImageUpgradeTask(ansible_module) - - task_module.get_want() - task_module.get_have() - - if ansible_module.params["state"] == "merged": - task_module.get_need_merged() - elif ansible_module.params["state"] == "deleted": - task_module.get_need_deleted() - elif ansible_module.params["state"] == "query": - task_module.get_need_query() - - task_module.task_result.changed = False - if len(task_module.need) == 0: - ansible_module.exit_json(**task_module.task_result.module_result) - - if ansible_module.params["state"] in ["merged", "deleted"]: - task_module.task_result.changed = True - - if ansible_module.params["state"] == "merged": - task_module.handle_merged_state() - elif ansible_module.params["state"] == "deleted": - task_module.handle_deleted_state() - elif ansible_module.params["state"] == "query": - task_module.handle_query_state() - - ansible_module.exit_json(**task_module.task_result.module_result) + ansible_module = AnsibleModule( + argument_spec=argument_spec, supports_check_mode=True + ) + + params = copy.deepcopy(ansible_module.params) + params["check_mode"] = ansible_module.check_mode + + # Logging setup + try: + log = Log() + log.commit() + except ValueError as error: + ansible_module.fail_json(str(error)) + + sender = Sender() + sender.ansible_module = ansible_module + rest_send = RestSend(params) + rest_send.response_handler = ResponseHandler() + rest_send.sender = sender + + # pylint: disable=attribute-defined-outside-init + try: + task = None + if params["state"] == "deleted": + task = Deleted(params) + if params["state"] == "merged": + task = Merged(params) + if params["state"] == "query": + task = Query(params) + if task is None: + ansible_module.fail_json(f"Invalid state: {params['state']}") + task.rest_send = rest_send + task.commit() + except ValueError as error: + ansible_module.fail_json(f"{error}", **task.results.failed_result) + + task.results.build_final_result() + + if True in task.results.failed: # pylint: disable=unsupported-membership-test + msg = "Module failed." + ansible_module.fail_json(msg, **task.results.final_result) + ansible_module.exit_json(**task.results.final_result) if __name__ == "__main__": diff --git a/tests/integration/targets/dcnm_fabric/tests/dcnm_fabric_deleted_basic.yaml b/tests/integration/targets/dcnm_fabric/tests/dcnm_fabric_deleted_basic.yaml index 24ce5c1ce..0ded0a927 100644 --- a/tests/integration/targets/dcnm_fabric/tests/dcnm_fabric_deleted_basic.yaml +++ b/tests/integration/targets/dcnm_fabric/tests/dcnm_fabric_deleted_basic.yaml @@ -39,12 +39,17 @@ ################################################################################ # REQUIREMENTS ################################################################################ -# Example vars for dcnm_image_policy integration tests -# Add to cisco/dcnm/playbooks/dcnm_tests.yaml) +# Inventory: +# ./playbooks/roles/dcnm_fabric/dcnm_hosts.yaml +# Playbook: +# ./playbooks/roles/dcnm_fabric/dcnm_tests.yaml +# Roles: +# ./tests/integration/targets/dcnm_fabric/tests/*.yaml +# +# Example vars: # # vars: -# # This testcase field can run any test in the tests directory for the role -# testcase: deleted +# testcase: dcnm_fabric_deleted_basic # fabric_name_1: VXLAN_EVPN_Fabric # fabric_type_1: VXLAN_EVPN # fabric_name_2: VXLAN_EVPN_MSD_Fabric @@ -302,11 +307,11 @@ - result.diff[0].sequence_number == 1 - result.diff[1].sequence_number == 2 - (result.metadata | length) == 2 - - result.metadata[0].action == "delete" + - result.metadata[0].action == "fabric_delete" - result.metadata[0].check_mode == False - result.metadata[0].sequence_number == 1 - result.metadata[0].state == "deleted" - - result.metadata[1].action == "delete" + - result.metadata[1].action == "fabric_delete" - result.metadata[1].check_mode == False - result.metadata[1].sequence_number == 2 - result.metadata[1].state == "deleted" @@ -383,7 +388,7 @@ - result.diff[0].FABRIC_NAME == fabric_name_3 - result.diff[0].sequence_number == 1 - (result.metadata | length) == 1 - - result.metadata[0].action == "delete" + - result.metadata[0].action == "fabric_delete" - result.metadata[0].check_mode == False - result.metadata[0].sequence_number == 1 - result.metadata[0].state == "deleted" @@ -445,7 +450,7 @@ - (result.diff | length) == 1 - result.diff[0].sequence_number == 1 - (result.metadata | length) == 1 - - result.metadata[0].action == "delete" + - result.metadata[0].action == "fabric_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_fabric/tests/dcnm_fabric_deleted_basic_ipfm.yaml b/tests/integration/targets/dcnm_fabric/tests/dcnm_fabric_deleted_basic_ipfm.yaml index f023b992b..2e95c2dec 100644 --- a/tests/integration/targets/dcnm_fabric/tests/dcnm_fabric_deleted_basic_ipfm.yaml +++ b/tests/integration/targets/dcnm_fabric/tests/dcnm_fabric_deleted_basic_ipfm.yaml @@ -29,14 +29,19 @@ ################################################################################ # REQUIREMENTS ################################################################################ -# Example vars for dcnm_image_policy integration tests -# Add to cisco/dcnm/playbooks/dcnm_tests.yaml) +# Inventory: +# ./playbooks/roles/dcnm_fabric/dcnm_hosts.yaml +# Playbook: +# ./playbooks/roles/dcnm_fabric/dcnm_tests.yaml +# Roles: +# ./tests/integration/targets/dcnm_fabric/tests/*.yaml +# +# Example vars: # # vars: -# # This testcase field can run any test in the tests directory for the role # testcase: dcnm_fabric_deleted_basic_ipfm # fabric_name_4: IPFM_Fabric -# fabric_type_4: VXLAN_EVPN_IPFM +# fabric_type_4: IPFM ################################################################################ # SETUP ################################################################################ @@ -175,7 +180,7 @@ - result.diff[0].FABRIC_NAME == fabric_name_4 - result.diff[0].sequence_number == 1 - (result.metadata | length) == 1 - - result.metadata[0].action == "delete" + - result.metadata[0].action == "fabric_delete" - result.metadata[0].check_mode == False - result.metadata[0].sequence_number == 1 - result.metadata[0].state == "deleted" @@ -237,7 +242,7 @@ - (result.diff | length) == 1 - result.diff[0].sequence_number == 1 - (result.metadata | length) == 1 - - result.metadata[0].action == "delete" + - result.metadata[0].action == "fabric_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_fabric/tests/dcnm_fabric_deleted_basic_lan_classic.yaml b/tests/integration/targets/dcnm_fabric/tests/dcnm_fabric_deleted_basic_lan_classic.yaml new file mode 100644 index 000000000..443af166b --- /dev/null +++ b/tests/integration/targets/dcnm_fabric/tests/dcnm_fabric_deleted_basic_lan_classic.yaml @@ -0,0 +1,326 @@ +################################################################################ +# RUNTIME +################################################################################ +# Recent run times (MM:SS.ms): +# 00:49.83 +################################################################################ +# DESCRIPTION - BASIC FABRIC DELETED STATE TEST +# +# Test basic deletion of fabrics verify results. +# - Deletion of populated fabrics not tested here. +# - See dcnm_fabric_deleted_populated.yaml instead. +################################################################################ +################################################################################ +# STEPS +################################################################################ +# SETUP +# 1. The following fabrics must be empty on the controller +# See vars: section in cisco/dcnm/playbooks/dcnm_tests.yaml +# - fabric_name_3 +# - fabric_type_3 # VXLAN_EVPN_MSD +# 2. Delete fabrics under test, if they exist +# - fabric_name_3 +# TEST +# 3. Create fabrics and verify result +# - fabric_name_3 +# 4. Delete fabric_name_3. Verify result +# CLEANUP +# 7. No cleanup required +################################################################################ +# REQUIREMENTS +################################################################################ +# Inventory: +# ./playbooks/roles/dcnm_fabric/dcnm_hosts.yaml +# Playbook: +# ./playbooks/roles/dcnm_fabric/dcnm_tests.yaml +# Roles: +# ./tests/integration/targets/dcnm_fabric/tests/*.yaml +# +# Example vars: +# vars: +# testcase: dcnm_fabric_deleted_basic_lan_classic +# fabric_name_3: LAN_CLASSIC_Fabric +# fabric_type_3: LAN_CLASSIC +################################################################################ +# SETUP +################################################################################ +- name: DELETED - SETUP - Delete fabrics + cisco.dcnm.dcnm_fabric: + state: deleted + config: + - FABRIC_NAME: "{{ fabric_name_3 }}" + register: result +- debug: + var: result +################################################################################ +# DELETED - TEST - Create fabric_name_3 and verify +################################################################################ +# Expected result +# - All untested nvPairs removed for brevity. +# - Fabric global keys in DATA removed for brevity. +# ok: [172.22.150.244] => { +# "result": { +# "changed": true, +# "diff": [ +# { +# "BOOTSTRAP_ENABLE": false, +# "FABRIC_NAME": "LAN_CLASSIC_Fabric", +# "IS_READ_ONLY": false, +# "sequence_number": 1 +# } +# ], +# "failed": false, +# "metadata": [ +# { +# "action": "fabric_create", +# "check_mode": false, +# "sequence_number": 1, +# "state": "merged" +# } +# ], +# "response": [ +# { +# "DATA": { +# "nvPairs": { +# "AAA_REMOTE_IP_ENABLED": "false", +# "AAA_SERVER_CONF": "", +# "ALLOW_NXC": "true", +# "ALLOW_NXC_PREV": "", +# "BOOTSTRAP_CONF": "", +# "BOOTSTRAP_ENABLE": "false", +# "BOOTSTRAP_MULTISUBNET": "", +# "BOOTSTRAP_MULTISUBNET_INTERNAL": "", +# "CDP_ENABLE": "false", +# "DCI_SUBNET_RANGE": "10.10.1.0/24", +# "DCI_SUBNET_TARGET_MASK": "30", +# "DEPLOYMENT_FREEZE": "false", +# "DHCP_ENABLE": "", +# "DHCP_END": "", +# "DHCP_END_INTERNAL": "", +# "DHCP_IPV6_ENABLE": "", +# "DHCP_IPV6_ENABLE_INTERNAL": "", +# "DHCP_START": "", +# "DHCP_START_INTERNAL": "", +# "ENABLE_AAA": "", +# "ENABLE_NETFLOW": "false", +# "ENABLE_NETFLOW_PREV": "", +# "ENABLE_NXAPI": "false", +# "ENABLE_NXAPI_HTTP": "", +# "EXT_FABRIC_TYPE": "", +# "FABRIC_FREEFORM": "", +# "FABRIC_NAME": "LAN_CLASSIC_Fabric", +# "FABRIC_TECHNOLOGY": "LANClassic", +# "FABRIC_TYPE": "External", +# "FEATURE_PTP": "false", +# "FEATURE_PTP_INTERNAL": "false", +# "FF": "LANClassic", +# "INBAND_ENABLE": "", +# "INBAND_ENABLE_PREV": "false", +# "INBAND_MGMT": "false", +# "INBAND_MGMT_PREV": "false", +# "IS_READ_ONLY": "false", +# "LOOPBACK0_IP_RANGE": "10.1.0.0/22", +# "MGMT_GW": "", +# "MGMT_GW_INTERNAL": "", +# "MGMT_PREFIX": "", +# "MGMT_PREFIX_INTERNAL": "", +# "MGMT_V6PREFIX": "", +# "MGMT_V6PREFIX_INTERNAL": "", +# "MPLS_HANDOFF": "false", +# "MPLS_LB_ID": "", +# "MPLS_LOOPBACK_IP_RANGE": "", +# "NETFLOW_EXPORTER_LIST": "", +# "NETFLOW_MONITOR_LIST": "", +# "NETFLOW_RECORD_LIST": "", +# "NETFLOW_SAMPLER_LIST": "", +# "NXAPI_HTTPS_PORT": "", +# "NXAPI_HTTP_PORT": "", +# "NXC_DEST_VRF": "", +# "NXC_PROXY_PORT": "8080", +# "NXC_PROXY_SERVER": "", +# "NXC_SRC_INTF": "", +# "OVERWRITE_GLOBAL_NXC": "false", +# "PM_ENABLE": "false", +# "PM_ENABLE_PREV": "false", +# "POWER_REDUNDANCY_MODE": "ps-redundant", +# "PTP_DOMAIN_ID": "", +# "PTP_LB_ID": "", +# "SNMP_SERVER_HOST_TRAP": "true", +# "SUBINTERFACE_RANGE": "2-511", +# "enableRealTimeBackup": "false", +# "enableScheduledBackup": "false", +# "scheduledTime": "" +# } +# }, +# "MESSAGE": "OK", +# "METHOD": "POST", +# "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/lan-fabric/rest/control/fabrics/LAN_CLASSIC_Fabric/LAN_Classic", +# "RETURN_CODE": 200, +# "sequence_number": 1 +# } +# ], +# "result": [ +# { +# "changed": true, +# "sequence_number": 1, +# "success": true +# } +# ] +# } +# } +################################################################################ +- name: DELETED - SETUP - Create fabric_name_3 and verify + cisco.dcnm.dcnm_fabric: + state: merged + config: + - FABRIC_NAME: "{{ fabric_name_3 }}" + FABRIC_TYPE: "{{ fabric_type_3 }}" + BOOTSTRAP_ENABLE: false + IS_READ_ONLY: false + DEPLOY: true + register: result +- debug: + var: result +- assert: + that: + - result.changed == true + - result.failed == false + - (result.diff | length) == 1 + - result.diff[0].FABRIC_NAME == fabric_name_3 + - result.diff[0].sequence_number == 1 + - (result.response | length) == 1 + - result.response[0].sequence_number == 1 + - result.response[0].MESSAGE == "OK" + - result.response[0].METHOD == "POST" + - result.response[0].RETURN_CODE == 200 +############################################################################################### +# DELETED - TEST - Delete fabric_name_3 and verify +############################################################################################### +# Expected result +# ok: [172.22.150.244] => { +# "result": { +# "changed": true, +# "diff": [ +# { +# "FABRIC_NAME": "LAN_CLASSIC_Fabric", +# "sequence_number": 1 +# } +# ], +# "failed": false, +# "metadata": [ +# { +# "action": "fabric_delete", +# "check_mode": false, +# "sequence_number": 1, +# "state": "deleted" +# } +# ], +# "response": [ +# { +# "DATA": "Invalid JSON response: Fabric 'LAN_CLASSIC_Fabric' is deleted successfully!", +# "MESSAGE": "OK", +# "METHOD": "DELETE", +# "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/lan-fabric/rest/control/fabrics/LAN_CLASSIC_Fabric", +# "RETURN_CODE": 200, +# "sequence_number": 1 +# } +# ], +# "result": [ +# { +# "changed": true, +# "sequence_number": 1, +# "success": true +# } +# ] +# } +# } +############################################################################################### +- name: DELETED - TEST - Delete fabric_name_3 and verify + cisco.dcnm.dcnm_fabric: &fabric_deleted + state: deleted + config: + - FABRIC_NAME: "{{ fabric_name_3 }}" + register: result +- debug: + var: result +- assert: + that: + - result.changed == true + - result.failed == false + - (result.diff | length) == 1 + - result.diff[0].FABRIC_NAME == fabric_name_3 + - result.diff[0].sequence_number == 1 + - (result.metadata | length) == 1 + - result.metadata[0].action == "fabric_delete" + - result.metadata[0].check_mode == False + - result.metadata[0].sequence_number == 1 + - result.metadata[0].state == "deleted" + - (result.response | length) == 1 + - result.response[0].MESSAGE == "OK" + - result.response[0].METHOD == "DELETE" + - result.response[0].RETURN_CODE == 200 + - result.response[0].sequence_number == 1 + - result.result[0].changed == true + - result.result[0].success == true + - result.result[0].sequence_number == 1 +################################################################################ +# DELETED - TEST - Delete fabric_name_3 idempotence +################################################################################ +# Expected result +# ok: [ndfc1] => { +# "result": { +# "changed": false, +# "diff": [ +# { +# "sequence_number": 1 +# } +# ], +# "failed": false, +# "metadata": [ +# { +# "action": "fabric_delete", +# "check_mode": false, +# "sequence_number": 1, +# "state": "deleted" +# } +# ], +# "response": [ +# { +# "MESSAGE": "No fabrics to delete", +# "RETURN_CODE": 200, +# "sequence_number": 1 +# } +# ], +# "result": [ +# { +# "changed": false, +# "sequence_number": 1, +# "success": true +# } +# ] +# } +# } +################################################################################ +- name: DELETED - TEST - Delete fabric_name_3 idempotence + cisco.dcnm.dcnm_fabric: *fabric_deleted + register: result +- debug: + var: result +- assert: + that: + - result.changed == false + - result.failed == false + - (result.diff | length) == 1 + - result.diff[0].sequence_number == 1 + - (result.metadata | length) == 1 + - result.metadata[0].action == "fabric_delete" + - result.metadata[0].check_mode == False + - result.metadata[0].sequence_number == 1 + - result.metadata[0].state == "deleted" + - (result.response | length) == 1 + - result.response[0].MESSAGE == "No fabrics to delete" + - result.response[0].RETURN_CODE == 200 + - result.response[0].sequence_number == 1 + - result.result[0].changed == false + - result.result[0].success == true + - result.result[0].sequence_number == 1 diff --git a/tests/integration/targets/dcnm_fabric/tests/dcnm_fabric_deleted_basic_msd.yaml b/tests/integration/targets/dcnm_fabric/tests/dcnm_fabric_deleted_basic_msd.yaml new file mode 100644 index 000000000..59200f2e3 --- /dev/null +++ b/tests/integration/targets/dcnm_fabric/tests/dcnm_fabric_deleted_basic_msd.yaml @@ -0,0 +1,303 @@ +################################################################################ +# RUNTIME +################################################################################ +# Recent run times (MM:SS.ms): +# 00:49.83 +################################################################################ +# DESCRIPTION - BASIC FABRIC DELETED STATE TEST +# +# Test basic deletion of fabrics verify results. +# - Deletion of populated fabrics not tested here. +# - See dcnm_fabric_deleted_populated.yaml instead. +################################################################################ +################################################################################ +# STEPS +################################################################################ +# SETUP +# 1. The following fabrics must be empty on the controller +# See vars: section in cisco/dcnm/playbooks/dcnm_tests.yaml +# - fabric_name_2 +# - fabric_type_2 # VXLAN_EVPN_MSD +# 2. Delete fabrics under test, if they exist +# - fabric_name_2 +# TEST +# 3. Create fabrics and verify result +# - fabric_name_2 +# 4. Delete fabric_name_2. Verify result +# CLEANUP +# 7. No cleanup required +################################################################################ +# REQUIREMENTS +################################################################################ +# Inventory: +# ./playbooks/roles/dcnm_fabric/dcnm_hosts.yaml +# Playbook: +# ./playbooks/roles/dcnm_fabric/dcnm_tests.yaml +# Roles: +# ./tests/integration/targets/dcnm_fabric/tests/*.yaml +# +# Example vars: +# +# vars: +# testcase: dcnm_fabric_deleted_basic_msd +# fabric_name_2: VXLAN_EVPN_MSD_Fabric +# fabric_type_2: VXLAN_EVPN_MSD +################################################################################ +# SETUP +################################################################################ +- name: DELETED - SETUP - Delete fabrics + cisco.dcnm.dcnm_fabric: + state: deleted + config: + - FABRIC_NAME: "{{ fabric_name_2 }}" + register: result +- debug: + var: result +################################################################################ +# DELETED - TEST - Create all supported fabric types +################################################################################ +# Expected result +# - All untested nvPairs removed for brevity. +# - Fabric global keys in DATA removed for brevity. +# ok: [172.22.150.244] => { +# "result": { +# "changed": true, +# "diff": [ +# { +# "FABRIC_NAME": "VXLAN_EVPN_MSD_Fabric", +# "sequence_number": 1 +# } +# ], +# "failed": false, +# "metadata": [ +# { +# "action": "fabric_create", +# "check_mode": false, +# "sequence_number": 1, +# "state": "merged" +# } +# ], +# "response": [ +# { +# "DATA": { +# "nvPairs": { +# "ANYCAST_GW_MAC": "2020.0000.00aa", +# "BGP_RP_ASN": "", +# "BGW_ROUTING_TAG": "54321", +# "BGW_ROUTING_TAG_PREV": "54321", +# "BORDER_GWY_CONNECTIONS": "Manual", +# "CLOUDSEC_ALGORITHM": "", +# "CLOUDSEC_AUTOCONFIG": "false", +# "CLOUDSEC_ENFORCEMENT": "", +# "CLOUDSEC_KEY_STRING": "", +# "CLOUDSEC_REPORT_TIMER": "", +# "DCI_SUBNET_RANGE": "10.10.1.0/24", +# "DCI_SUBNET_TARGET_MASK": "30", +# "DCNM_ID": "", +# "DELAY_RESTORE": "300", +# "ENABLE_BGP_BFD": "", +# "ENABLE_BGP_LOG_NEIGHBOR_CHANGE": "", +# "ENABLE_BGP_SEND_COMM": "", +# "ENABLE_PVLAN": "false", +# "ENABLE_PVLAN_PREV": "", +# "ENABLE_RS_REDIST_DIRECT": "", +# "EXT_FABRIC_TYPE": "", +# "FABRIC_NAME": "VXLAN_EVPN_MSD_Fabric", +# "FABRIC_TYPE": "MFD", +# "FF": "MSD", +# "L2_SEGMENT_ID_RANGE": "30000-49000", +# "L3_PARTITION_ID_RANGE": "50000-59000", +# "LOOPBACK100_IP_RANGE": "10.10.0.0/24", +# "MSO_CONTROLER_ID": "", +# "MSO_SITE_GROUP_NAME": "", +# "MS_IFC_BGP_AUTH_KEY_TYPE": "", +# "MS_IFC_BGP_AUTH_KEY_TYPE_PREV": "", +# "MS_IFC_BGP_PASSWORD": "", +# "MS_IFC_BGP_PASSWORD_ENABLE": "false", +# "MS_IFC_BGP_PASSWORD_ENABLE_PREV": "", +# "MS_IFC_BGP_PASSWORD_PREV": "", +# "MS_LOOPBACK_ID": "100", +# "MS_UNDERLAY_AUTOCONFIG": "false", +# "PREMSO_PARENT_FABRIC": "", +# "RP_SERVER_IP": "", +# "RS_ROUTING_TAG": "", +# "TOR_AUTO_DEPLOY": "false", +# "default_network": "Default_Network_Universal", +# "default_pvlan_sec_network": "", +# "default_vrf": "Default_VRF_Universal", +# "enableScheduledBackup": "", +# "network_extension_template": "Default_Network_Extension_Universal", +# "scheduledTime": "", +# "vrf_extension_template": "Default_VRF_Extension_Universal" +# }, +# }, +# "MESSAGE": "OK", +# "METHOD": "POST", +# "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/lan-fabric/rest/control/fabrics/VXLAN_EVPN_MSD_Fabric/MSD_Fabric", +# "RETURN_CODE": 200, +# "sequence_number": 1 +# } +# ], +# "result": [ +# { +# "changed": true, +# "sequence_number": 1, +# "success": true +# } +# ] +# } +# } +################################################################################ +- name: DELETED - SETUP - Create all supported fabric types + cisco.dcnm.dcnm_fabric: + state: merged + config: + - FABRIC_NAME: "{{ fabric_name_2 }}" + FABRIC_TYPE: "{{ fabric_type_2 }}" + DEPLOY: true + register: result +- debug: + var: result +- assert: + that: + - result.changed == true + - result.failed == false + - (result.diff | length) == 1 + - result.diff[0].FABRIC_NAME == fabric_name_2 + - result.diff[0].sequence_number == 1 + - (result.response | length) == 1 + - result.response[0].sequence_number == 1 + - result.response[0].MESSAGE == "OK" + - result.response[0].METHOD == "POST" + - result.response[0].RETURN_CODE == 200 +############################################################################################### +# DELETED - TEST - Delete fabric_name_2 and verify +############################################################################################### +# Expected result +# ok: [ndfc1] => { +# "result": { +# "changed": true, +# "diff": [ +# { +# "FABRIC_NAME": "VXLAN_EVPN_MSD_Fabric", +# "sequence_number": 1 +# } +# ], +# "failed": false, +# "metadata": [ +# { +# "action": "fabric_delete", +# "check_mode": false, +# "sequence_number": 1, +# "state": "deleted" +# } +# ], +# "response": [ +# { +# "DATA": "Fabric 'VXLAN_EVPN_MSD_Fabric' is deleted successfully!", +# "MESSAGE": "OK", +# "METHOD": "DELETE", +# "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/rest/control/fabrics/VXLAN_EVPN_MSD_Fabric", +# "RETURN_CODE": 200, +# "sequence_number": 1 +# } +# ], +# "result": [ +# { +# "changed": true, +# "sequence_number": 1, +# "success": true +# } +# ] +# } +# } +############################################################################################### +- name: DELETED - TEST - Delete fabric_name_2 and verify + cisco.dcnm.dcnm_fabric: &fabric_deleted + state: deleted + config: + - FABRIC_NAME: "{{ fabric_name_2 }}" + register: result +- debug: + var: result +- assert: + that: + - result.changed == true + - result.failed == false + - (result.diff | length) == 1 + - result.diff[0].FABRIC_NAME == fabric_name_2 + - result.diff[0].sequence_number == 1 + - (result.metadata | length) == 1 + - result.metadata[0].action == "fabric_delete" + - result.metadata[0].check_mode == False + - result.metadata[0].sequence_number == 1 + - result.metadata[0].state == "deleted" + - (result.response | length) == 1 + - result.response[0].MESSAGE == "OK" + - result.response[0].METHOD == "DELETE" + - result.response[0].RETURN_CODE == 200 + - result.response[0].sequence_number == 1 + - result.result[0].changed == true + - result.result[0].success == true + - result.result[0].sequence_number == 1 +################################################################################ +# DELETED - TEST - Delete fabric_name_2 idempotence +################################################################################ +# Expected result +# ok: [ndfc1] => { +# "result": { +# "changed": false, +# "diff": [ +# { +# "sequence_number": 1 +# } +# ], +# "failed": false, +# "metadata": [ +# { +# "action": "fabric_delete", +# "check_mode": false, +# "sequence_number": 1, +# "state": "deleted" +# } +# ], +# "response": [ +# { +# "MESSAGE": "No fabrics to delete", +# "RETURN_CODE": 200, +# "sequence_number": 1 +# } +# ], +# "result": [ +# { +# "changed": false, +# "sequence_number": 1, +# "success": true +# } +# ] +# } +# } +################################################################################ +- name: DELETED - TEST - Delete fabric_name_2 idempotence + cisco.dcnm.dcnm_fabric: *fabric_deleted + register: result +- debug: + var: result +- assert: + that: + - result.changed == false + - result.failed == false + - (result.diff | length) == 1 + - result.diff[0].sequence_number == 1 + - (result.metadata | length) == 1 + - result.metadata[0].action == "fabric_delete" + - result.metadata[0].check_mode == False + - result.metadata[0].sequence_number == 1 + - result.metadata[0].state == "deleted" + - (result.response | length) == 1 + - result.response[0].MESSAGE == "No fabrics to delete" + - result.response[0].RETURN_CODE == 200 + - result.response[0].sequence_number == 1 + - result.result[0].changed == false + - result.result[0].success == true + - result.result[0].sequence_number == 1 diff --git a/tests/integration/targets/dcnm_fabric/tests/dcnm_fabric_deleted_basic_vxlan.yaml b/tests/integration/targets/dcnm_fabric/tests/dcnm_fabric_deleted_basic_vxlan.yaml new file mode 100644 index 000000000..a9844b96e --- /dev/null +++ b/tests/integration/targets/dcnm_fabric/tests/dcnm_fabric_deleted_basic_vxlan.yaml @@ -0,0 +1,260 @@ +################################################################################ +# RUNTIME +################################################################################ +# Recent run times (MM:SS.ms): +# 00:49.83 +################################################################################ +# DESCRIPTION - BASIC FABRIC DELETED STATE TEST +# +# Test basic deletion of fabrics verify results. +# - Deletion of populated fabrics not tested here. +# - See dcnm_fabric_deleted_populated.yaml instead. +################################################################################ +################################################################################ +# STEPS +################################################################################ +# SETUP +# 1. The following fabrics must be empty on the controller +# See vars: section in cisco/dcnm/playbooks/dcnm_tests.yaml +# - fabric_name_1 +# - fabric_type_1 # VXLAN_EVPN +# 2. Delete fabrics under test, if they exist +# - fabric_name_1 +# TEST +# 3. Create fabrics and verify result +# - fabric_name_1 +# 4. Delete fabric_name_1. Verify result +# CLEANUP +# 7. No cleanup required +################################################################################ +# REQUIREMENTS +################################################################################ +# Inventory: +# ./playbooks/roles/dcnm_fabric/dcnm_hosts.yaml +# Playbook: +# ./playbooks/roles/dcnm_fabric/dcnm_tests.yaml +# Roles: +# ./tests/integration/targets/dcnm_fabric/tests/*.yaml +# +# Example vars: +# +# vars: +# testcase: dcnm_fabric_deleted_basic_vxlan +# fabric_name_1: VXLAN_EVPN_Fabric +# fabric_type_1: VXLAN_EVPN +################################################################################ +# SETUP +################################################################################ +- name: DELETED - SETUP - Delete fabrics + cisco.dcnm.dcnm_fabric: + state: deleted + config: + - FABRIC_NAME: "{{ fabric_name_1 }}" + register: result +- debug: + var: result +################################################################################ +# DELETED - TEST - Create all supported fabric types +################################################################################ +# Expected result +# - All untested nvPairs removed for brevity. +# - Fabric global keys in DATA removed for brevity. +# ok: [ndfc1] => { +# "result": { +# "changed": true, +# "diff": [ +# { +# "BGP_AS": "65535.65534", +# "FABRIC_NAME": "VXLAN_EVPN_Fabric", +# "sequence_number": 1 +# } +# ], +# "failed": false, +# "metadata": [ +# { +# "action": "fabric_create", +# "check_mode": false, +# "sequence_number": 1, +# "state": "merged" +# } +# ], +# "response": [ +# { +# "DATA": { +# "nvPairs": { +# "BGP_AS": "65535.65534", +# "FABRIC_NAME": "VXLAN_EVPN_Fabric" +# } +# }, +# "MESSAGE": "OK", +# "METHOD": "POST", +# "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/rest/control/fabrics/VXLAN_EVPN_Fabric/Easy_Fabric", +# "RETURN_CODE": 200, +# "sequence_number": 1 +# } +# ], +# "result": [ +# { +# "changed": true, +# "sequence_number": 1, +# "success": true +# } +# ] +# } +# } +################################################################################ +- name: DELETED - SETUP - Create all supported fabric types + cisco.dcnm.dcnm_fabric: + state: merged + config: + - FABRIC_NAME: "{{ fabric_name_1 }}" + FABRIC_TYPE: "{{ fabric_type_1 }}" + BGP_AS: "65535.65534" + DEPLOY: true + register: result +- debug: + var: result +- assert: + that: + - result.changed == true + - result.failed == false + - (result.diff | length) == 1 + - result.diff[0].BGP_AS == "65535.65534" + - result.diff[0].FABRIC_NAME == fabric_name_1 + - result.diff[0].sequence_number == 1 + - (result.response | length) == 1 + - result.response[0].sequence_number == 1 + - result.response[0].MESSAGE == "OK" + - result.response[0].METHOD == "POST" + - result.response[0].RETURN_CODE == 200 +############################################################################################### +# DELETED - TEST - Delete fabric_name_1 and verify +############################################################################################### +# Expected result +# ok: [ndfc1] => { +# "result": { +# "changed": true, +# "diff": [ +# { +# "FABRIC_NAME": "VXLAN_EVPN_Fabric", +# "sequence_number": 1 +# } +# ], +# "failed": false, +# "metadata": [ +# { +# "action": "fabric_delete", +# "check_mode": false, +# "sequence_number": 1, +# "state": "deleted" +# } +# ], +# "response": [ +# { +# "DATA": "Fabric 'VXLAN_EVPN_Fabric' is deleted successfully!", +# "MESSAGE": "OK", +# "METHOD": "DELETE", +# "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/rest/control/fabrics/VXLAN_EVPN_Fabric", +# "RETURN_CODE": 200, +# "sequence_number": 1 +# } +# ], +# "result": [ +# { +# "changed": true, +# "sequence_number": 1, +# "success": true +# } +# ] +# } +# } +############################################################################################### +- name: DELETED - TEST - Delete fabric_name_1 and verify + cisco.dcnm.dcnm_fabric: &fabric_deleted + state: deleted + config: + - FABRIC_NAME: "{{ fabric_name_1 }}" + register: result +- debug: + var: result +- assert: + that: + - result.changed == true + - result.failed == false + - (result.diff | length) == 1 + - result.diff[0].FABRIC_NAME == fabric_name_1 + - result.diff[0].sequence_number == 1 + - (result.metadata | length) == 1 + - result.metadata[0].action == "fabric_delete" + - result.metadata[0].check_mode == False + - result.metadata[0].sequence_number == 1 + - result.metadata[0].state == "deleted" + - (result.response | length) == 1 + - result.response[0].MESSAGE == "OK" + - result.response[0].METHOD == "DELETE" + - result.response[0].RETURN_CODE == 200 + - result.response[0].sequence_number == 1 + - result.result[0].changed == true + - result.result[0].success == true + - result.result[0].sequence_number == 1 +################################################################################ +# DELETED - TEST - Delete fabric_name_1 idempotence +################################################################################ +# Expected result +# ok: [ndfc1] => { +# "result": { +# "changed": false, +# "diff": [ +# { +# "sequence_number": 1 +# } +# ], +# "failed": false, +# "metadata": [ +# { +# "action": "fabric_delete", +# "check_mode": false, +# "sequence_number": 1, +# "state": "deleted" +# } +# ], +# "response": [ +# { +# "MESSAGE": "No fabrics to delete", +# "RETURN_CODE": 200, +# "sequence_number": 1 +# } +# ], +# "result": [ +# { +# "changed": false, +# "sequence_number": 1, +# "success": true +# } +# ] +# } +# } +################################################################################ +- name: DELETED - TEST - Delete fabric_name_1 idempotence + cisco.dcnm.dcnm_fabric: *fabric_deleted + register: result +- debug: + var: result +- assert: + that: + - result.changed == false + - result.failed == false + - (result.diff | length) == 1 + - result.diff[0].sequence_number == 1 + - (result.metadata | length) == 1 + - result.metadata[0].action == "fabric_delete" + - result.metadata[0].check_mode == False + - result.metadata[0].sequence_number == 1 + - result.metadata[0].state == "deleted" + - (result.response | length) == 1 + - result.response[0].MESSAGE == "No fabrics to delete" + - result.response[0].RETURN_CODE == 200 + - result.response[0].sequence_number == 1 + - result.result[0].changed == false + - result.result[0].success == true + - result.result[0].sequence_number == 1 diff --git a/tests/integration/targets/dcnm_fabric/tests/dcnm_fabric_merged_basic.yaml b/tests/integration/targets/dcnm_fabric/tests/dcnm_fabric_merged_basic.yaml index 37112ef17..af54d0a88 100644 --- a/tests/integration/targets/dcnm_fabric/tests/dcnm_fabric_merged_basic.yaml +++ b/tests/integration/targets/dcnm_fabric/tests/dcnm_fabric_merged_basic.yaml @@ -22,35 +22,40 @@ # - fabric_type_2 # VXLAN_EVPN_MSD # - fabric_name_3 # - fabric_type_3 # LAN_CLASSIC -# 3. Delete fabrics under test, if they exist +# 2. Delete fabrics under test, if they exist # - fabric_name_1 # - fabric_name_2 # - fabric_name_3 ################################################################################ # TEST ################################################################################ -# 4. Create fabrics and verify result +# 3. Create fabrics and verify result # - fabric_name_1 # - fabric_name_1 # - fabric_name_1 -# 5. Merge additional configs into fabric_1 and fabric_2 and verify result -# 6. Merge additional config into fabric_3 and verify result +# 4. Merge additional configs into fabric_1 and fabric_2 and verify result +# 5. Merge additional config into fabric_3 and verify result ################################################################################ # CLEANUP ################################################################################ -# 7. Delete fabrics under test +# 6. Delete fabrics under test # - fabric_name_1 # - fabric_name_1 # - fabric_name_1 ################################################################################ # REQUIREMENTS ################################################################################ -# Example vars for dcnm_image_policy integration tests -# Add to cisco/dcnm/playbooks/dcnm_tests.yaml) +# Inventory: +# ./playbooks/roles/dcnm_fabric/dcnm_hosts.yaml +# Playbook: +# ./playbooks/roles/dcnm_fabric/dcnm_tests.yaml +# Roles: +# ./tests/integration/targets/dcnm_fabric/tests/*.yaml +# +# Example vars: # # vars: -# # This testcase field can run any test in the tests directory for the role -# testcase: deleted +# testcase: dcnm_fabric_merged_basic # fabric_name_1: VXLAN_EVPN_Fabric # fabric_type_1: VXLAN_EVPN # fabric_name_2: VXLAN_EVPN_MSD_Fabric @@ -99,19 +104,19 @@ # "failed": false, # "metadata": [ # { -# "action": "create", +# "action": "fabric_create", # "check_mode": false, # "sequence_number": 1, # "state": "merged" # }, # { -# "action": "create", +# "action": "fabric_create", # "check_mode": false, # "sequence_number": 2, # "state": "merged" # }, # { -# "action": "create", +# "action": "fabric_create", # "check_mode": false, # "sequence_number": 3, # "state": "merged" @@ -211,11 +216,11 @@ - result.diff[2].sequence_number == 3 - result.diff[2].BOOTSTRAP_ENABLE == false - result.diff[2].IS_READ_ONLY == false - - result.metadata[0].action == "create" + - result.metadata[0].action == "fabric_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].action == "fabric_create" - result.metadata[1].check_mode == False - result.metadata[1].sequence_number == 2 - result.metadata[1].state == "merged" @@ -246,6 +251,7 @@ # "ANYCAST_GW_MAC": "aaaa.bbbb.cccc", # "FABRIC_NAME": "VXLAN_EVPN_Fabric", # "REPLICATION_MODE": "Ingress", +# "SITE_ID": "65000", # "sequence_number": 1 # }, # { @@ -269,13 +275,13 @@ # "failed": false, # "metadata": [ # { -# "action": "update", +# "action": "fabric_update", # "check_mode": false, # "sequence_number": 1, # "state": "merged" # }, # { -# "action": "update", +# "action": "fabric_update", # "check_mode": false, # "sequence_number": 2, # "state": "merged" @@ -312,6 +318,7 @@ # "ANYCAST_GW_MAC": "aaaa.bbbb.cccc", # "FABRIC_NAME": "VXLAN_EVPN_Fabric", # "REPLICATION_MODE": "Ingress", +# "SITE_ID": "65000", # "UNDERLAY_IS_V6": "false", # } # }, @@ -396,9 +403,10 @@ config: - FABRIC_NAME: "{{ fabric_name_1 }}" FABRIC_TYPE: "{{ fabric_type_1 }}" - BGP_AS: 65535 ANYCAST_GW_MAC: aaaabbbbcccc + BGP_AS: 65535 REPLICATION_MODE: Ingress + SITE_ID: 65000 UNDERLAY_IS_V6: false DEPLOY: false - FABRIC_NAME: "{{ fabric_name_2 }}" @@ -415,16 +423,17 @@ - (result.diff | length) == 6 - result.diff[0].FABRIC_NAME == fabric_name_1 - result.diff[0].ANYCAST_GW_MAC == "aaaa.bbbb.cccc" + - result.diff[0].SITE_ID == "65000" - result.diff[0].sequence_number == 1 - result.diff[1].FABRIC_NAME == fabric_name_2 - result.diff[1].ANYCAST_GW_MAC == "aaaa.bbbb.cccc" - result.diff[1].sequence_number == 2 - (result.metadata | length) == 6 - - result.metadata[0].action == "update" + - result.metadata[0].action == "fabric_update" - result.metadata[0].check_mode == False - result.metadata[0].sequence_number == 1 - result.metadata[0].state == "merged" - - result.metadata[1].action == "update" + - result.metadata[1].action == "fabric_update" - result.metadata[1].check_mode == False - result.metadata[1].sequence_number == 2 - result.metadata[1].state == "merged" @@ -450,7 +459,9 @@ - result.response[0].METHOD == "PUT" - result.response[0].RETURN_CODE == 200 - result.response[0].DATA.nvPairs.ANYCAST_GW_MAC == "aaaa.bbbb.cccc" + - result.response[0].DATA.nvPairs.BGP_AS == "65535" - result.response[0].DATA.nvPairs.REPLICATION_MODE == "Ingress" + - result.response[0].DATA.nvPairs.SITE_ID == "65000" - result.response[0].DATA.nvPairs.UNDERLAY_IS_V6 == "false" - result.response[0].DATA.nvPairs.FABRIC_NAME == "VXLAN_EVPN_Fabric" - result.response[1].sequence_number == 2 @@ -515,7 +526,7 @@ # "failed": false, # "metadata": [ # { -# "action": "update", +# "action": "fabric_update", # "check_mode": false, # "sequence_number": 1, # "state": "merged" @@ -602,7 +613,7 @@ - result.diff[1].sequence_number == 2 - result.diff[2].sequence_number == 3 - (result.metadata | length) == 3 - - result.metadata[0].action == "update" + - result.metadata[0].action == "fabric_update" - result.metadata[0].check_mode == False - result.metadata[0].sequence_number == 1 - result.metadata[0].state == "merged" @@ -644,7 +655,7 @@ # "failed": false, # "metadata": [ # { -# "action": "update", +# "action": "fabric_update", # "check_mode": false, # "sequence_number": 1, # "state": "merged" @@ -679,7 +690,7 @@ - (result.diff | length) == 1 - result.diff[0].sequence_number == 1 - (result.metadata | length) == 1 - - result.metadata[0].action == "update" + - result.metadata[0].action == "fabric_update" - result.metadata[0].check_mode == False - result.metadata[0].sequence_number == 1 - result.metadata[0].state == "merged" @@ -715,19 +726,19 @@ # "failed": false, # "metadata": [ # { -# "action": "delete", +# "action": "fabric_delete", # "check_mode": false, # "sequence_number": 1, # "state": "deleted" # }, # { -# "action": "delete", +# "action": "fabric_delete", # "check_mode": false, # "sequence_number": 2, # "state": "deleted" # }, # { -# "action": "delete", +# "action": "fabric_delete", # "check_mode": false, # "sequence_number": 3, # "state": "deleted" @@ -801,15 +812,15 @@ - result.diff[2].FABRIC_NAME == fabric_name_3 - result.diff[2].sequence_number == 3 - (result.metadata | length) == 3 - - result.metadata[0].action == "delete" + - result.metadata[0].action == "fabric_delete" - result.metadata[0].check_mode == False - result.metadata[0].sequence_number == 1 - result.metadata[0].state == "deleted" - - result.metadata[1].action == "delete" + - result.metadata[1].action == "fabric_delete" - result.metadata[1].check_mode == False - result.metadata[1].sequence_number == 2 - result.metadata[1].state == "deleted" - - result.metadata[2].action == "delete" + - result.metadata[2].action == "fabric_delete" - result.metadata[2].check_mode == False - result.metadata[2].sequence_number == 3 - result.metadata[2].state == "deleted" diff --git a/tests/integration/targets/dcnm_fabric/tests/dcnm_fabric_merged_basic_ipfm.yaml b/tests/integration/targets/dcnm_fabric/tests/dcnm_fabric_merged_basic_ipfm.yaml index 0c0638c95..9eefa76e7 100644 --- a/tests/integration/targets/dcnm_fabric/tests/dcnm_fabric_merged_basic_ipfm.yaml +++ b/tests/integration/targets/dcnm_fabric/tests/dcnm_fabric_merged_basic_ipfm.yaml @@ -34,12 +34,17 @@ ################################################################################ # REQUIREMENTS ################################################################################ -# Example vars for dcnm_image_policy integration tests -# Add to cisco/dcnm/playbooks/dcnm_tests.yaml) +# Inventory: +# ./playbooks/roles/dcnm_fabric/dcnm_hosts.yaml +# Playbook: +# ./playbooks/roles/dcnm_fabric/dcnm_tests.yaml +# Roles: +# ./tests/integration/targets/dcnm_fabric/tests/*.yaml +# +# Example vars: # # vars: -# # This testcase field can run any test in the tests directory for the role -# testcase: deleted +# testcase: dcnm_fabric_merged_basic_ipfm # fabric_name_4: IPFM_Fabric # fabric_type_4: IPFM ################################################################################ @@ -118,7 +123,7 @@ - (result.diff | length) == 1 - result.diff[0].FABRIC_NAME == fabric_name_4 - result.diff[0].sequence_number == 1 - - result.metadata[0].action == "create" + - result.metadata[0].action == "fabric_create" - result.metadata[0].check_mode == False - result.metadata[0].sequence_number == 1 - result.metadata[0].state == "merged" @@ -237,7 +242,7 @@ - result.diff[1].sequence_number == 2 - result.diff[2].sequence_number == 3 - (result.metadata | length) == 3 - - result.metadata[0].action == "update" + - result.metadata[0].action == "fabric_update" - result.metadata[0].check_mode == False - result.metadata[0].sequence_number == 1 - result.metadata[0].state == "merged" @@ -323,7 +328,7 @@ - (result.diff | length) == 1 - result.diff[0].sequence_number == 1 - (result.metadata | length) == 1 - - result.metadata[0].action == "update" + - result.metadata[0].action == "fabric_update" - result.metadata[0].check_mode == False - result.metadata[0].sequence_number == 1 - result.metadata[0].state == "merged" @@ -393,7 +398,7 @@ - result.diff[0].FABRIC_NAME == fabric_name_4 - result.diff[0].sequence_number == 1 - (result.metadata | length) == 1 - - result.metadata[0].action == "delete" + - result.metadata[0].action == "fabric_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_fabric/tests/dcnm_fabric_merged_save_deploy.yaml b/tests/integration/targets/dcnm_fabric/tests/dcnm_fabric_merged_save_deploy.yaml index b53516dd6..ee9ac706f 100644 --- a/tests/integration/targets/dcnm_fabric/tests/dcnm_fabric_merged_save_deploy.yaml +++ b/tests/integration/targets/dcnm_fabric/tests/dcnm_fabric_merged_save_deploy.yaml @@ -44,13 +44,17 @@ ################################################################################ # REQUIREMENTS ################################################################################ -# Example vars for dcnm_fabric integration tests -# Add fabric and leaf vars to cisco/dcnm/playbooks/dcnm_tests.yaml -# Add nxos_username and nxos_password vars to cisco/dcnm/playbooks/dcnm_hosts.yaml +# Inventory: +# ./playbooks/roles/dcnm_fabric/dcnm_hosts.yaml +# Playbook: +# ./playbooks/roles/dcnm_fabric/dcnm_tests.yaml +# Roles: +# ./tests/integration/targets/dcnm_fabric/tests/*.yaml +# +# Example vars: # # vars: -# # This testcase field can run any test in the tests directory for the role -# testcase: deleted +# testcase: dcnm_fabric_merged_save_deploy # fabric_name_1: VXLAN_EVPN_Fabric # fabric_type_1: VXLAN_EVPN # fabric_name_3: LAN_CLASSIC_Fabric @@ -184,11 +188,11 @@ - result.diff[1].BOOTSTRAP_ENABLE == false - result.diff[1].IS_READ_ONLY == false - (result.metadata | length) == 2 - - result.metadata[0].action == "create" + - result.metadata[0].action == "fabric_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].action == "fabric_create" - result.metadata[1].check_mode == False - result.metadata[1].sequence_number == 2 - result.metadata[1].state == "merged" @@ -462,11 +466,11 @@ - result.diff[1].SUBINTERFACE_RANGE == "2-101" - result.diff[1].sequence_number == 2 - (result.metadata | length) == 6 - - result.metadata[0].action == "update" + - result.metadata[0].action == "fabric_update" - result.metadata[0].check_mode == False - result.metadata[0].sequence_number == 1 - result.metadata[0].state == "merged" - - result.metadata[1].action == "update" + - result.metadata[1].action == "fabric_update" - result.metadata[1].check_mode == False - result.metadata[1].sequence_number == 2 - result.metadata[1].state == "merged" @@ -592,7 +596,7 @@ - (result.diff | length) == 1 - result.diff[0].sequence_number == 1 - (result.metadata | length) == 1 - - result.metadata[0].action == "update" + - result.metadata[0].action == "fabric_update" - result.metadata[0].check_mode == False - result.metadata[0].sequence_number == 1 - result.metadata[0].state == "merged" @@ -723,11 +727,11 @@ - result.diff[1].FABRIC_NAME == fabric_name_3 - result.diff[1].sequence_number == 2 - (result.metadata | length) == 2 - - result.metadata[0].action == "delete" + - result.metadata[0].action == "fabric_delete" - result.metadata[0].check_mode == False - result.metadata[0].sequence_number == 1 - result.metadata[0].state == "deleted" - - result.metadata[1].action == "delete" + - result.metadata[1].action == "fabric_delete" - result.metadata[1].check_mode == False - result.metadata[1].sequence_number == 2 - result.metadata[1].state == "deleted" diff --git a/tests/integration/targets/dcnm_fabric/tests/dcnm_fabric_merged_save_deploy_ipfm.yaml b/tests/integration/targets/dcnm_fabric/tests/dcnm_fabric_merged_save_deploy_ipfm.yaml index d799f900c..eae4f6274 100644 --- a/tests/integration/targets/dcnm_fabric/tests/dcnm_fabric_merged_save_deploy_ipfm.yaml +++ b/tests/integration/targets/dcnm_fabric/tests/dcnm_fabric_merged_save_deploy_ipfm.yaml @@ -34,13 +34,17 @@ ################################################################################ # REQUIREMENTS ################################################################################ -# Example vars for dcnm_image_policy integration tests -# Add fabric and leaf vars to cisco/dcnm/playbooks/dcnm_tests.yaml -# Add nxos_username and nxos_password vars to cisco/dcnm/playbooks/dcnm_hosts.yaml +# Inventory: +# ./playbooks/roles/dcnm_fabric/dcnm_hosts.yaml +# Playbook: +# ./playbooks/roles/dcnm_fabric/dcnm_tests.yaml +# Roles: +# ./tests/integration/targets/dcnm_fabric/tests/*.yaml +# +# Example vars: # # vars: -# # This testcase field can run any test in the tests directory for the role -# testcase: deleted +# testcase: dcnm_fabric_merged_save_deploy_ipfm # fabric_name_4: IPFM_Fabric # fabric_type_4: IPFM # leaf_1: 172.22.150.103 @@ -122,7 +126,7 @@ - (result.diff | length) == 1 - result.diff[0].FABRIC_NAME == fabric_name_4 - result.diff[0].sequence_number == 1 - - result.metadata[0].action == "create" + - result.metadata[0].action == "fabric_create" - result.metadata[0].check_mode == False - result.metadata[0].sequence_number == 1 - result.metadata[0].state == "merged" @@ -277,7 +281,7 @@ - result.diff[2].config_deploy == "OK" - result.diff[2].sequence_number == 3 - (result.metadata | length) == 3 - - result.metadata[0].action == "update" + - result.metadata[0].action == "fabric_update" - result.metadata[0].check_mode == False - result.metadata[0].sequence_number == 1 - result.metadata[0].state == "merged" @@ -365,7 +369,7 @@ - (result.diff | length) == 1 - result.diff[0].sequence_number == 1 - (result.metadata | length) == 1 - - result.metadata[0].action == "update" + - result.metadata[0].action == "fabric_update" - result.metadata[0].check_mode == False - result.metadata[0].sequence_number == 1 - result.metadata[0].state == "merged" @@ -454,7 +458,7 @@ - result.diff[0].FABRIC_NAME == fabric_name_4 - result.diff[0].sequence_number == 1 - (result.metadata | length) == 1 - - result.metadata[0].action == "delete" + - result.metadata[0].action == "fabric_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_fabric/tests/dcnm_fabric_query_basic.yaml b/tests/integration/targets/dcnm_fabric/tests/dcnm_fabric_query_basic.yaml new file mode 100644 index 000000000..46577fefc --- /dev/null +++ b/tests/integration/targets/dcnm_fabric/tests/dcnm_fabric_query_basic.yaml @@ -0,0 +1,1385 @@ +################################################################################ +# RUNTIME +################################################################################ +# Recent run times (MM:SS.ms): +# 00:77.09 +################################################################################ +# DESCRIPTION - BASIC FABRIC QUERY STATE TEST +# +# Test basic query of fabric configurations and verify results. +################################################################################ +# STEPS +################################################################################ +# SETUP +################################################################################ +# 1. The following fabrics must be empty on the controller +# See vars: section in cisco/dcnm/playbooks/dcnm_tests.yaml +# - fabric_name_1 +# - fabric_type_1 # VXLAN_EVPN +# - fabric_name_2 +# - fabric_type_2 # VXLAN_EVPN_MSD +# - fabric_name_3 +# - fabric_type_3 # LAN_CLASSIC +# 2. Delete fabrics under test, if they exist +# - fabric_name_1 +# - fabric_name_2 +# - fabric_name_3 +################################################################################ +# TEST +################################################################################ +# 3. Create fabrics and verify result +# - fabric_name_1 +# - fabric_name_1 +# - fabric_name_1 +# +# 4. Query fabric configurations and verify results +################################################################################ +# CLEANUP +################################################################################ +# 6. Delete fabrics under test +# - fabric_name_1 +# - fabric_name_1 +# - fabric_name_1 +################################################################################ +# REQUIREMENTS +################################################################################ +# Inventory: +# ./playbooks/roles/dcnm_fabric/dcnm_hosts.yaml +# Playbook: +# ./playbooks/roles/dcnm_fabric/dcnm_tests.yaml +# Roles: +# ./tests/integration/targets/dcnm_fabric/tests/*.yaml +# +# Example vars: +# +# vars: +# testcase: dcnm_fabric_query_basic +# fabric_name_1: VXLAN_EVPN_Fabric +# fabric_type_1: VXLAN_EVPN +# fabric_name_2: VXLAN_EVPN_MSD_Fabric +# fabric_type_2: VXLAN_EVPN_MSD +# fabric_name_3: LAN_CLASSIC_Fabric +# fabric_type_3: LAN_CLASSIC +################################################################################ +# QUERY - SETUP - Delete fabrics +################################################################################ +- name: QUERY - SETUP - Delete fabrics + cisco.dcnm.dcnm_fabric: + state: deleted + config: + - FABRIC_NAME: "{{ fabric_name_1 }}" + - FABRIC_NAME: "{{ fabric_name_2 }}" + - FABRIC_NAME: "{{ fabric_name_3 }}" + register: result +- debug: + var: result +################################################################################ +# QUERY - TEST - Create all supported fabric types with basic config +################################################################################ +# Expected result +# - All untested nvPairs removed for brevity. +# - Fabric global keys in DATA removed for brevity. +# ok: [ndfc1] => { +# "result": { +# "changed": true, +# "diff": [ +# { +# "BGP_AS": 65535, +# "FABRIC_NAME": "VXLAN_EVPN_Fabric", +# "sequence_number": 1 +# }, +# { +# "FABRIC_NAME": "VXLAN_EVPN_MSD_Fabric", +# "sequence_number": 2 +# }, +# { +# "BOOTSTRAP_ENABLE": false, +# "FABRIC_NAME": "LAN_CLASSIC_Fabric", +# "IS_READ_ONLY": false, +# "sequence_number": 3 +# } +# ], +# "failed": false, +# "metadata": [ +# { +# "action": "fabric_create", +# "check_mode": false, +# "sequence_number": 1, +# "state": "merged" +# }, +# { +# "action": "fabric_create", +# "check_mode": false, +# "sequence_number": 2, +# "state": "merged" +# }, +# { +# "action": "fabric_create", +# "check_mode": false, +# "sequence_number": 3, +# "state": "merged" +# } +# ], +# "response": [ +# { +# "DATA": { +# "nvPairs": { +# "BGP_AS": "65535", +# "FABRIC_NAME": "VXLAN_EVPN_Fabric" +# } +# }, +# "MESSAGE": "OK", +# "METHOD": "POST", +# "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/rest/control/fabrics/VXLAN_EVPN_Fabric/Easy_Fabric", +# "RETURN_CODE": 200, +# "sequence_number": 1 +# }, +# { +# "DATA": { +# "nvPairs": { +# "FABRIC_NAME": "VXLAN_EVPN_MSD_Fabric", +# } +# }, +# "MESSAGE": "OK", +# "METHOD": "POST", +# "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/rest/control/fabrics/VXLAN_EVPN_MSD_Fabric/MSD_Fabric", +# "RETURN_CODE": 200, +# "sequence_number": 2 +# }, +# { +# "DATA": { +# "nvPairs": { +# "BOOTSTRAP_ENABLE": "false", +# "FABRIC_NAME": "LAN_CLASSIC_Fabric", +# "IS_READ_ONLY": "false", +# } +# }, +# "MESSAGE": "OK", +# "METHOD": "POST", +# "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/rest/control/fabrics/LAN_CLASSIC_Fabric/LAN_Classic", +# "RETURN_CODE": 200, +# "sequence_number": 3 +# } +# ], +# "result": [ +# { +# "changed": true, +# "sequence_number": 1, +# "success": true +# }, +# { +# "changed": true, +# "sequence_number": 2, +# "success": true +# }, +# { +# "changed": true, +# "sequence_number": 3, +# "success": true +# } +# ] +# } +# } +################################################################################ +- name: QUERY - TEST - Create all supported fabric types with minimal config + cisco.dcnm.dcnm_fabric: + state: merged + config: + - FABRIC_NAME: "{{ fabric_name_1 }}" + FABRIC_TYPE: "{{ fabric_type_1 }}" + BGP_AS: 65535 + DEPLOY: true + - FABRIC_NAME: "{{ fabric_name_2 }}" + FABRIC_TYPE: "{{ fabric_type_2 }}" + DEPLOY: true + - FABRIC_NAME: "{{ fabric_name_3 }}" + FABRIC_TYPE: "{{ fabric_type_3 }}" + BOOTSTRAP_ENABLE: false + IS_READ_ONLY: false + DEPLOY: true + register: result +- debug: + var: result +- assert: + that: + - result.changed == true + - result.failed == false + - (result.diff | length) == 3 + - result.diff[0].BGP_AS == 65535 + - result.diff[0].FABRIC_NAME == fabric_name_1 + - result.diff[0].sequence_number == 1 + - result.diff[1].FABRIC_NAME == fabric_name_2 + - result.diff[1].sequence_number == 2 + - result.diff[2].FABRIC_NAME == fabric_name_3 + - result.diff[2].sequence_number == 3 + - result.diff[2].BOOTSTRAP_ENABLE == false + - result.diff[2].IS_READ_ONLY == false + - result.metadata[0].action == "fabric_create" + - result.metadata[0].check_mode == False + - result.metadata[0].sequence_number == 1 + - result.metadata[0].state == "merged" + - result.metadata[1].action == "fabric_create" + - result.metadata[1].check_mode == False + - result.metadata[1].sequence_number == 2 + - result.metadata[1].state == "merged" + - (result.response | length) == 3 + - result.response[0].sequence_number == 1 + - result.response[0].MESSAGE == "OK" + - result.response[0].METHOD == "POST" + - result.response[0].RETURN_CODE == 200 + - result.response[1].sequence_number == 2 + - result.response[1].MESSAGE == "OK" + - result.response[1].METHOD == "POST" + - result.response[1].RETURN_CODE == 200 + - result.response[2].sequence_number == 3 + - result.response[2].MESSAGE == "OK" + - result.response[2].METHOD == "POST" + - result.response[2].RETURN_CODE == 200 +################################################################################ +# QUERY - Query the fabrics +################################################################################ +# Expected result +# ok: [172.22.150.244] => { +# "result": { +# "changed": false, +# "diff": [ +# { +# "LAN_CLASSIC_Fabric": { +# "createdOn": 1721954060733, +# "deviceType": "n9k", +# "fabricId": "FABRIC-4", +# "fabricName": "LAN_CLASSIC_Fabric", +# "fabricTechnology": "LANClassic", +# "fabricTechnologyFriendly": "Classic LAN", +# "fabricType": "External", +# "fabricTypeFriendly": "External", +# "id": 4, +# "modifiedOn": 1721954061501, +# "nvPairs": { +# "AAA_REMOTE_IP_ENABLED": "false", +# "AAA_SERVER_CONF": "", +# "ALLOW_NXC": "true", +# "ALLOW_NXC_PREV": "true", +# "BOOTSTRAP_CONF": "", +# "BOOTSTRAP_ENABLE": "false", +# "BOOTSTRAP_MULTISUBNET": "#Scope_Start_IP, Scope_End_IP, Scope_Default_Gateway, Scope_Subnet_Prefix", +# "BOOTSTRAP_MULTISUBNET_INTERNAL": "", +# "CDP_ENABLE": "false", +# "DCI_SUBNET_RANGE": "10.10.1.0/24", +# "DCI_SUBNET_TARGET_MASK": "30", +# "DEPLOYMENT_FREEZE": "false", +# "DHCP_ENABLE": "false", +# "DHCP_END": "", +# "DHCP_END_INTERNAL": "", +# "DHCP_IPV6_ENABLE": "", +# "DHCP_IPV6_ENABLE_INTERNAL": "", +# "DHCP_START": "", +# "DHCP_START_INTERNAL": "", +# "DOMAIN_NAME_INTERNAL": "", +# "ENABLE_AAA": "", +# "ENABLE_NETFLOW": "false", +# "ENABLE_NETFLOW_PREV": "false", +# "ENABLE_NXAPI": "false", +# "ENABLE_NXAPI_HTTP": "false", +# "EXT_FABRIC_TYPE": "", +# "FABRIC_FREEFORM": "", +# "FABRIC_NAME": "LAN_CLASSIC_Fabric", +# "FABRIC_TECHNOLOGY": "LANClassic", +# "FABRIC_TYPE": "External", +# "FEATURE_PTP": "false", +# "FEATURE_PTP_INTERNAL": "false", +# "FF": "LANClassic", +# "INBAND_ENABLE": "", +# "INBAND_ENABLE_PREV": "", +# "INBAND_MGMT": "false", +# "INBAND_MGMT_PREV": "false", +# "IS_READ_ONLY": "false", +# "LOOPBACK0_IP_RANGE": "10.1.0.0/22", +# "MGMT_GW": "", +# "MGMT_GW_INTERNAL": "", +# "MGMT_PREFIX": "", +# "MGMT_PREFIX_INTERNAL": "", +# "MGMT_V6PREFIX": "", +# "MGMT_V6PREFIX_INTERNAL": "", +# "MPLS_HANDOFF": "false", +# "MPLS_LB_ID": "", +# "MPLS_LOOPBACK_IP_RANGE": "", +# "NETFLOW_EXPORTER_LIST": "", +# "NETFLOW_MONITOR_LIST": "", +# "NETFLOW_RECORD_LIST": "", +# "NETFLOW_SAMPLER_LIST": "", +# "NXAPI_HTTPS_PORT": "443", +# "NXAPI_HTTP_PORT": "80", +# "NXC_DEST_VRF": "", +# "NXC_PROXY_PORT": "8080", +# "NXC_PROXY_SERVER": "", +# "NXC_SRC_INTF": "", +# "OVERWRITE_GLOBAL_NXC": "false", +# "PM_ENABLE": "false", +# "PM_ENABLE_PREV": "false", +# "POWER_REDUNDANCY_MODE": "ps-redundant", +# "PTP_DOMAIN_ID": "", +# "PTP_LB_ID": "", +# "SNMP_SERVER_HOST_TRAP": "true", +# "SUBINTERFACE_RANGE": "2-511", +# "enableRealTimeBackup": "false", +# "enableScheduledBackup": "false", +# "scheduledTime": "" +# }, +# "operStatus": "HEALTHY", +# "provisionMode": "DCNMTopDown", +# "replicationMode": "IngressReplication", +# "templateName": "LAN_Classic" +# }, +# "VXLAN_EVPN_Fabric": { +# "asn": "65535", +# "createdOn": 1721954053773, +# "deviceType": "n9k", +# "fabricId": "FABRIC-2", +# "fabricName": "VXLAN_EVPN_Fabric", +# "fabricTechnology": "VXLANFabric", +# "fabricTechnologyFriendly": "VXLAN EVPN", +# "fabricType": "Switch_Fabric", +# "fabricTypeFriendly": "Switch Fabric", +# "id": 2, +# "modifiedOn": 1721954057503, +# "networkExtensionTemplate": "Default_Network_Extension_Universal", +# "networkTemplate": "Default_Network_Universal", +# "nvPairs": { +# "AAA_REMOTE_IP_ENABLED": "false", +# "AAA_SERVER_CONF": "", +# "ACTIVE_MIGRATION": "false", +# "ADVERTISE_PIP_BGP": "false", +# "ADVERTISE_PIP_ON_BORDER": "true", +# "AGENT_INTF": "eth0", +# "AI_ML_QOS_POLICY": "AI_Fabric_QOS_400G", +# "ALLOW_NXC": "true", +# "ALLOW_NXC_PREV": "true", +# "ANYCAST_BGW_ADVERTISE_PIP": "false", +# "ANYCAST_GW_MAC": "2020.0000.00aa", +# "ANYCAST_LB_ID": "", +# "ANYCAST_RP_IP_RANGE": "10.254.254.0/24", +# "ANYCAST_RP_IP_RANGE_INTERNAL": "", +# "AUTO_SYMMETRIC_DEFAULT_VRF": "false", +# "AUTO_SYMMETRIC_VRF_LITE": "false", +# "AUTO_UNIQUE_VRF_LITE_IP_PREFIX": "false", +# "AUTO_UNIQUE_VRF_LITE_IP_PREFIX_PREV": "false", +# "AUTO_VRFLITE_IFC_DEFAULT_VRF": "false", +# "BANNER": "", +# "BFD_AUTH_ENABLE": "false", +# "BFD_AUTH_KEY": "", +# "BFD_AUTH_KEY_ID": "", +# "BFD_ENABLE": "false", +# "BFD_ENABLE_PREV": "", +# "BFD_IBGP_ENABLE": "false", +# "BFD_ISIS_ENABLE": "false", +# "BFD_OSPF_ENABLE": "false", +# "BFD_PIM_ENABLE": "false", +# "BGP_AS": "65535", +# "BGP_AS_PREV": "65535", +# "BGP_AUTH_ENABLE": "false", +# "BGP_AUTH_KEY": "", +# "BGP_AUTH_KEY_TYPE": "3", +# "BGP_LB_ID": "0", +# "BOOTSTRAP_CONF": "", +# "BOOTSTRAP_ENABLE": "false", +# "BOOTSTRAP_ENABLE_PREV": "false", +# "BOOTSTRAP_MULTISUBNET": "#Scope_Start_IP, Scope_End_IP, Scope_Default_Gateway, Scope_Subnet_Prefix", +# "BOOTSTRAP_MULTISUBNET_INTERNAL": "", +# "BRFIELD_DEBUG_FLAG": "Disable", +# "BROWNFIELD_NETWORK_NAME_FORMAT": "Auto_Net_VNI$$VNI$$_VLAN$$VLAN_ID$$", +# "BROWNFIELD_SKIP_OVERLAY_NETWORK_ATTACHMENTS": "false", +# "CDP_ENABLE": "false", +# "COPP_POLICY": "strict", +# "DCI_SUBNET_RANGE": "10.33.0.0/16", +# "DCI_SUBNET_TARGET_MASK": "30", +# "DEAFULT_QUEUING_POLICY_CLOUDSCALE": "queuing_policy_default_8q_cloudscale", +# "DEAFULT_QUEUING_POLICY_OTHER": "queuing_policy_default_other", +# "DEAFULT_QUEUING_POLICY_R_SERIES": "queuing_policy_default_r_series", +# "DEFAULT_VRF_REDIS_BGP_RMAP": "", +# "DEPLOYMENT_FREEZE": "false", +# "DHCP_ENABLE": "false", +# "DHCP_END": "", +# "DHCP_END_INTERNAL": "", +# "DHCP_IPV6_ENABLE": "", +# "DHCP_IPV6_ENABLE_INTERNAL": "", +# "DHCP_START": "", +# "DHCP_START_INTERNAL": "", +# "DNS_SERVER_IP_LIST": "", +# "DNS_SERVER_VRF": "", +# "DOMAIN_NAME_INTERNAL": "", +# "ENABLE_AAA": "false", +# "ENABLE_AGENT": "false", +# "ENABLE_AI_ML_QOS_POLICY": "false", +# "ENABLE_AI_ML_QOS_POLICY_FLAP": "false", +# "ENABLE_DEFAULT_QUEUING_POLICY": "false", +# "ENABLE_EVPN": "true", +# "ENABLE_FABRIC_VPC_DOMAIN_ID": "false", +# "ENABLE_FABRIC_VPC_DOMAIN_ID_PREV": "false", +# "ENABLE_L3VNI_NO_VLAN": "false", +# "ENABLE_MACSEC": "false", +# "ENABLE_NETFLOW": "false", +# "ENABLE_NETFLOW_PREV": "false", +# "ENABLE_NGOAM": "true", +# "ENABLE_NXAPI": "true", +# "ENABLE_NXAPI_HTTP": "true", +# "ENABLE_PBR": "false", +# "ENABLE_PVLAN": "false", +# "ENABLE_PVLAN_PREV": "false", +# "ENABLE_SGT": "false", +# "ENABLE_SGT_PREV": "false", +# "ENABLE_TENANT_DHCP": "true", +# "ENABLE_TRM": "false", +# "ENABLE_VPC_PEER_LINK_NATIVE_VLAN": "false", +# "ESR_OPTION": "PBR", +# "EXTRA_CONF_INTRA_LINKS": "", +# "EXTRA_CONF_LEAF": "", +# "EXTRA_CONF_SPINE": "", +# "EXTRA_CONF_TOR": "", +# "EXT_FABRIC_TYPE": "", +# "FABRIC_INTERFACE_TYPE": "p2p", +# "FABRIC_MTU": "9216", +# "FABRIC_MTU_PREV": "9216", +# "FABRIC_NAME": "VXLAN_EVPN_Fabric", +# "FABRIC_TYPE": "Switch_Fabric", +# "FABRIC_VPC_DOMAIN_ID": "", +# "FABRIC_VPC_DOMAIN_ID_PREV": "", +# "FABRIC_VPC_QOS": "false", +# "FABRIC_VPC_QOS_POLICY_NAME": "spine_qos_for_fabric_vpc_peering", +# "FEATURE_PTP": "false", +# "FEATURE_PTP_INTERNAL": "false", +# "FF": "Easy_Fabric", +# "GRFIELD_DEBUG_FLAG": "Disable", +# "HD_TIME": "180", +# "HOST_INTF_ADMIN_STATE": "true", +# "IBGP_PEER_TEMPLATE": "", +# "IBGP_PEER_TEMPLATE_LEAF": "", +# "INBAND_DHCP_SERVERS": "", +# "INBAND_MGMT": "false", +# "INBAND_MGMT_PREV": "false", +# "ISIS_AREA_NUM": "0001", +# "ISIS_AREA_NUM_PREV": "", +# "ISIS_AUTH_ENABLE": "false", +# "ISIS_AUTH_KEY": "", +# "ISIS_AUTH_KEYCHAIN_KEY_ID": "", +# "ISIS_AUTH_KEYCHAIN_NAME": "", +# "ISIS_LEVEL": "level-2", +# "ISIS_OVERLOAD_ELAPSE_TIME": "", +# "ISIS_OVERLOAD_ENABLE": "", +# "ISIS_P2P_ENABLE": "", +# "L2_HOST_INTF_MTU": "9216", +# "L2_HOST_INTF_MTU_PREV": "9216", +# "L2_SEGMENT_ID_RANGE": "30000-49000", +# "L3VNI_MCAST_GROUP": "", +# "L3_PARTITION_ID_RANGE": "50000-59000", +# "LINK_STATE_ROUTING": "ospf", +# "LINK_STATE_ROUTING_TAG": "UNDERLAY", +# "LINK_STATE_ROUTING_TAG_PREV": "UNDERLAY", +# "LOOPBACK0_IPV6_RANGE": "", +# "LOOPBACK0_IP_RANGE": "10.2.0.0/22", +# "LOOPBACK1_IPV6_RANGE": "", +# "LOOPBACK1_IP_RANGE": "10.3.0.0/22", +# "MACSEC_ALGORITHM": "", +# "MACSEC_CIPHER_SUITE": "", +# "MACSEC_FALLBACK_ALGORITHM": "", +# "MACSEC_FALLBACK_KEY_STRING": "", +# "MACSEC_KEY_STRING": "", +# "MACSEC_REPORT_TIMER": "", +# "MGMT_GW": "", +# "MGMT_GW_INTERNAL": "", +# "MGMT_PREFIX": "", +# "MGMT_PREFIX_INTERNAL": "", +# "MGMT_V6PREFIX": "", +# "MGMT_V6PREFIX_INTERNAL": "", +# "MPLS_HANDOFF": "false", +# "MPLS_ISIS_AREA_NUM": "0001", +# "MPLS_ISIS_AREA_NUM_PREV": "", +# "MPLS_LB_ID": "", +# "MPLS_LOOPBACK_IP_RANGE": "", +# "MSO_CONNECTIVITY_DEPLOYED": "", +# "MSO_CONTROLER_ID": "", +# "MSO_SITE_GROUP_NAME": "", +# "MSO_SITE_ID": "", +# "MST_INSTANCE_RANGE": "", +# "MULTICAST_GROUP_SUBNET": "239.1.1.0/25", +# "NETFLOW_EXPORTER_LIST": "", +# "NETFLOW_MONITOR_LIST": "", +# "NETFLOW_RECORD_LIST": "", +# "NETWORK_VLAN_RANGE": "2300-2999", +# "NTP_SERVER_IP_LIST": "", +# "NTP_SERVER_VRF": "", +# "NVE_LB_ID": "1", +# "NXAPI_HTTPS_PORT": "443", +# "NXAPI_HTTP_PORT": "80", +# "NXC_DEST_VRF": "", +# "NXC_PROXY_PORT": "8080", +# "NXC_PROXY_SERVER": "", +# "NXC_SRC_INTF": "", +# "OBJECT_TRACKING_NUMBER_RANGE": "100-299", +# "OSPF_AREA_ID": "0.0.0.0", +# "OSPF_AUTH_ENABLE": "false", +# "OSPF_AUTH_KEY": "", +# "OSPF_AUTH_KEY_ID": "", +# "OVERLAY_MODE": "cli", +# "OVERLAY_MODE_PREV": "cli", +# "OVERWRITE_GLOBAL_NXC": "false", +# "PER_VRF_LOOPBACK_AUTO_PROVISION": "false", +# "PER_VRF_LOOPBACK_AUTO_PROVISION_PREV": "false", +# "PER_VRF_LOOPBACK_AUTO_PROVISION_V6": "false", +# "PER_VRF_LOOPBACK_AUTO_PROVISION_V6_PREV": "false", +# "PER_VRF_LOOPBACK_IP_RANGE": "", +# "PER_VRF_LOOPBACK_IP_RANGE_V6": "", +# "PHANTOM_RP_LB_ID1": "", +# "PHANTOM_RP_LB_ID2": "", +# "PHANTOM_RP_LB_ID3": "", +# "PHANTOM_RP_LB_ID4": "", +# "PIM_HELLO_AUTH_ENABLE": "false", +# "PIM_HELLO_AUTH_KEY": "", +# "PM_ENABLE": "false", +# "PM_ENABLE_PREV": "false", +# "POWER_REDUNDANCY_MODE": "ps-redundant", +# "PREMSO_PARENT_FABRIC": "", +# "PTP_DOMAIN_ID": "", +# "PTP_LB_ID": "", +# "REPLICATION_MODE": "Multicast", +# "ROUTER_ID_RANGE": "", +# "ROUTE_MAP_SEQUENCE_NUMBER_RANGE": "1-65534", +# "RP_COUNT": "2", +# "RP_LB_ID": "254", +# "RP_MODE": "asm", +# "RR_COUNT": "2", +# "SEED_SWITCH_CORE_INTERFACES": "", +# "SERVICE_NETWORK_VLAN_RANGE": "3000-3199", +# "SGT_ID_RANGE": "", +# "SGT_NAME_PREFIX": "", +# "SGT_PREPROVISION": "", +# "SITE_ID": "65535", +# "SLA_ID_RANGE": "10000-19999", +# "SNMP_SERVER_HOST_TRAP": "true", +# "SPINE_COUNT": "0", +# "SPINE_SWITCH_CORE_INTERFACES": "", +# "SSPINE_ADD_DEL_DEBUG_FLAG": "Disable", +# "SSPINE_COUNT": "0", +# "STATIC_UNDERLAY_IP_ALLOC": "false", +# "STP_BRIDGE_PRIORITY": "", +# "STP_ROOT_OPTION": "unmanaged", +# "STP_VLAN_RANGE": "", +# "STRICT_CC_MODE": "false", +# "SUBINTERFACE_RANGE": "2-511", +# "SUBNET_RANGE": "10.4.0.0/16", +# "SUBNET_TARGET_MASK": "30", +# "SYSLOG_SERVER_IP_LIST": "", +# "SYSLOG_SERVER_VRF": "", +# "SYSLOG_SEV": "", +# "TCAM_ALLOCATION": "true", +# "TOPDOWN_CONFIG_RM_TRACKING": "notstarted", +# "UNDERLAY_IS_V6": "false", +# "UNNUM_BOOTSTRAP_LB_ID": "", +# "UNNUM_DHCP_END": "", +# "UNNUM_DHCP_END_INTERNAL": "", +# "UNNUM_DHCP_START": "", +# "UNNUM_DHCP_START_INTERNAL": "", +# "UPGRADE_FROM_VERSION": "", +# "USE_LINK_LOCAL": "true", +# "V6_SUBNET_RANGE": "", +# "V6_SUBNET_TARGET_MASK": "126", +# "VPC_AUTO_RECOVERY_TIME": "360", +# "VPC_DELAY_RESTORE": "150", +# "VPC_DELAY_RESTORE_TIME": "60", +# "VPC_DOMAIN_ID_RANGE": "1-1000", +# "VPC_ENABLE_IPv6_ND_SYNC": "true", +# "VPC_PEER_KEEP_ALIVE_OPTION": "management", +# "VPC_PEER_LINK_PO": "500", +# "VPC_PEER_LINK_VLAN": "3600", +# "VRF_LITE_AUTOCONFIG": "Manual", +# "VRF_VLAN_RANGE": "2000-2299", +# "abstract_anycast_rp": "anycast_rp", +# "abstract_bgp": "base_bgp", +# "abstract_bgp_neighbor": "evpn_bgp_rr_neighbor", +# "abstract_bgp_rr": "evpn_bgp_rr", +# "abstract_dhcp": "base_dhcp", +# "abstract_extra_config_bootstrap": "extra_config_bootstrap_11_1", +# "abstract_extra_config_leaf": "extra_config_leaf", +# "abstract_extra_config_spine": "extra_config_spine", +# "abstract_extra_config_tor": "extra_config_tor", +# "abstract_feature_leaf": "base_feature_leaf_upg", +# "abstract_feature_spine": "base_feature_spine_upg", +# "abstract_isis": "base_isis_level2", +# "abstract_isis_interface": "isis_interface", +# "abstract_loopback_interface": "int_fabric_loopback_11_1", +# "abstract_multicast": "base_multicast_11_1", +# "abstract_ospf": "base_ospf", +# "abstract_ospf_interface": "ospf_interface_11_1", +# "abstract_pim_interface": "pim_interface", +# "abstract_route_map": "route_map", +# "abstract_routed_host": "int_routed_host", +# "abstract_trunk_host": "int_trunk_host", +# "abstract_vlan_interface": "int_fabric_vlan_11_1", +# "abstract_vpc_domain": "base_vpc_domain_11_1", +# "default_network": "Default_Network_Universal", +# "default_pvlan_sec_network": "", +# "default_vrf": "Default_VRF_Universal", +# "enableRealTimeBackup": "", +# "enableScheduledBackup": "", +# "network_extension_template": "Default_Network_Extension_Universal", +# "scheduledTime": "", +# "temp_anycast_gateway": "anycast_gateway", +# "temp_vpc_domain_mgmt": "vpc_domain_mgmt", +# "temp_vpc_peer_link": "int_vpc_peer_link_po", +# "vrf_extension_template": "Default_VRF_Extension_Universal" +# }, +# "operStatus": "HEALTHY", +# "provisionMode": "DCNMTopDown", +# "replicationMode": "Multicast", +# "siteId": "65535", +# "templateName": "Easy_Fabric", +# "vrfExtensionTemplate": "Default_VRF_Extension_Universal", +# "vrfTemplate": "Default_VRF_Universal" +# }, +# "VXLAN_EVPN_MSD_Fabric": { +# "createdOn": 1721954059451, +# "deviceType": "n9k", +# "fabricId": "FABRIC-3", +# "fabricName": "VXLAN_EVPN_MSD_Fabric", +# "fabricTechnology": "VXLANFabric", +# "fabricTechnologyFriendly": "VXLAN EVPN", +# "fabricType": "MFD", +# "fabricTypeFriendly": "VXLAN EVPN Multi-Site", +# "id": 3, +# "modifiedOn": 1721954059701, +# "networkExtensionTemplate": "Default_Network_Extension_Universal", +# "networkTemplate": "Default_Network_Universal", +# "nvPairs": { +# "ANYCAST_GW_MAC": "2020.0000.00aa", +# "BGP_RP_ASN": "", +# "BGW_ROUTING_TAG": "54321", +# "BGW_ROUTING_TAG_PREV": "54321", +# "BORDER_GWY_CONNECTIONS": "Manual", +# "CLOUDSEC_ALGORITHM": "", +# "CLOUDSEC_AUTOCONFIG": "false", +# "CLOUDSEC_ENFORCEMENT": "", +# "CLOUDSEC_KEY_STRING": "", +# "CLOUDSEC_REPORT_TIMER": "", +# "DCI_SUBNET_RANGE": "10.10.1.0/24", +# "DCI_SUBNET_TARGET_MASK": "30", +# "DCNM_ID": "", +# "DELAY_RESTORE": "300", +# "ENABLE_BGP_BFD": "", +# "ENABLE_BGP_LOG_NEIGHBOR_CHANGE": "", +# "ENABLE_BGP_SEND_COMM": "", +# "ENABLE_PVLAN": "false", +# "ENABLE_PVLAN_PREV": "false", +# "ENABLE_RS_REDIST_DIRECT": "", +# "EXT_FABRIC_TYPE": "", +# "FABRIC_NAME": "VXLAN_EVPN_MSD_Fabric", +# "FABRIC_TYPE": "MFD", +# "FF": "MSD", +# "L2_SEGMENT_ID_RANGE": "30000-49000", +# "L3_PARTITION_ID_RANGE": "50000-59000", +# "LOOPBACK100_IP_RANGE": "10.10.0.0/24", +# "MSO_CONTROLER_ID": "", +# "MSO_SITE_GROUP_NAME": "", +# "MS_IFC_BGP_AUTH_KEY_TYPE": "", +# "MS_IFC_BGP_AUTH_KEY_TYPE_PREV": "", +# "MS_IFC_BGP_PASSWORD": "", +# "MS_IFC_BGP_PASSWORD_ENABLE": "false", +# "MS_IFC_BGP_PASSWORD_ENABLE_PREV": "", +# "MS_IFC_BGP_PASSWORD_PREV": "", +# "MS_LOOPBACK_ID": "100", +# "MS_UNDERLAY_AUTOCONFIG": "false", +# "PREMSO_PARENT_FABRIC": "", +# "RP_SERVER_IP": "", +# "RS_ROUTING_TAG": "", +# "TOR_AUTO_DEPLOY": "false", +# "default_network": "Default_Network_Universal", +# "default_pvlan_sec_network": "", +# "default_vrf": "Default_VRF_Universal", +# "enableScheduledBackup": "", +# "network_extension_template": "Default_Network_Extension_Universal", +# "scheduledTime": "", +# "vrf_extension_template": "Default_VRF_Extension_Universal" +# }, +# "operStatus": "HEALTHY", +# "provisionMode": "DCNMTopDown", +# "replicationMode": "IngressReplication", +# "templateName": "MSD_Fabric", +# "vrfExtensionTemplate": "Default_VRF_Extension_Universal", +# "vrfTemplate": "Default_VRF_Universal" +# }, +# "sequence_number": 1 +# } +# ], +# "failed": false, +# "metadata": [ +# { +# "action": "fabric_query", +# "check_mode": false, +# "sequence_number": 1, +# "state": "query" +# } +# ], +# "response": [ +# { +# "DATA": [ +# { +# "createdOn": 1721954059451, +# "deviceType": "n9k", +# "fabricId": "FABRIC-3", +# "fabricName": "VXLAN_EVPN_MSD_Fabric", +# "fabricTechnology": "VXLANFabric", +# "fabricTechnologyFriendly": "VXLAN EVPN", +# "fabricType": "MFD", +# "fabricTypeFriendly": "VXLAN EVPN Multi-Site", +# "id": 3, +# "modifiedOn": 1721954059701, +# "networkExtensionTemplate": "Default_Network_Extension_Universal", +# "networkTemplate": "Default_Network_Universal", +# "nvPairs": { +# "ANYCAST_GW_MAC": "2020.0000.00aa", +# "BGP_RP_ASN": "", +# "BGW_ROUTING_TAG": "54321", +# "BGW_ROUTING_TAG_PREV": "54321", +# "BORDER_GWY_CONNECTIONS": "Manual", +# "CLOUDSEC_ALGORITHM": "", +# "CLOUDSEC_AUTOCONFIG": "false", +# "CLOUDSEC_ENFORCEMENT": "", +# "CLOUDSEC_KEY_STRING": "", +# "CLOUDSEC_REPORT_TIMER": "", +# "DCI_SUBNET_RANGE": "10.10.1.0/24", +# "DCI_SUBNET_TARGET_MASK": "30", +# "DCNM_ID": "", +# "DELAY_RESTORE": "300", +# "ENABLE_BGP_BFD": "", +# "ENABLE_BGP_LOG_NEIGHBOR_CHANGE": "", +# "ENABLE_BGP_SEND_COMM": "", +# "ENABLE_PVLAN": "false", +# "ENABLE_PVLAN_PREV": "false", +# "ENABLE_RS_REDIST_DIRECT": "", +# "EXT_FABRIC_TYPE": "", +# "FABRIC_NAME": "VXLAN_EVPN_MSD_Fabric", +# "FABRIC_TYPE": "MFD", +# "FF": "MSD", +# "L2_SEGMENT_ID_RANGE": "30000-49000", +# "L3_PARTITION_ID_RANGE": "50000-59000", +# "LOOPBACK100_IP_RANGE": "10.10.0.0/24", +# "MSO_CONTROLER_ID": "", +# "MSO_SITE_GROUP_NAME": "", +# "MS_IFC_BGP_AUTH_KEY_TYPE": "", +# "MS_IFC_BGP_AUTH_KEY_TYPE_PREV": "", +# "MS_IFC_BGP_PASSWORD": "", +# "MS_IFC_BGP_PASSWORD_ENABLE": "false", +# "MS_IFC_BGP_PASSWORD_ENABLE_PREV": "", +# "MS_IFC_BGP_PASSWORD_PREV": "", +# "MS_LOOPBACK_ID": "100", +# "MS_UNDERLAY_AUTOCONFIG": "false", +# "PREMSO_PARENT_FABRIC": "", +# "RP_SERVER_IP": "", +# "RS_ROUTING_TAG": "", +# "TOR_AUTO_DEPLOY": "false", +# "default_network": "Default_Network_Universal", +# "default_pvlan_sec_network": "", +# "default_vrf": "Default_VRF_Universal", +# "enableScheduledBackup": "", +# "network_extension_template": "Default_Network_Extension_Universal", +# "scheduledTime": "", +# "vrf_extension_template": "Default_VRF_Extension_Universal" +# }, +# "operStatus": "HEALTHY", +# "provisionMode": "DCNMTopDown", +# "replicationMode": "IngressReplication", +# "templateName": "MSD_Fabric", +# "vrfExtensionTemplate": "Default_VRF_Extension_Universal", +# "vrfTemplate": "Default_VRF_Universal" +# }, +# { +# "createdOn": 1721954060733, +# "deviceType": "n9k", +# "fabricId": "FABRIC-4", +# "fabricName": "LAN_CLASSIC_Fabric", +# "fabricTechnology": "LANClassic", +# "fabricTechnologyFriendly": "Classic LAN", +# "fabricType": "External", +# "fabricTypeFriendly": "External", +# "id": 4, +# "modifiedOn": 1721954061501, +# "nvPairs": { +# "AAA_REMOTE_IP_ENABLED": "false", +# "AAA_SERVER_CONF": "", +# "ALLOW_NXC": "true", +# "ALLOW_NXC_PREV": "true", +# "BOOTSTRAP_CONF": "", +# "BOOTSTRAP_ENABLE": "false", +# "BOOTSTRAP_MULTISUBNET": "#Scope_Start_IP, Scope_End_IP, Scope_Default_Gateway, Scope_Subnet_Prefix", +# "BOOTSTRAP_MULTISUBNET_INTERNAL": "", +# "CDP_ENABLE": "false", +# "DCI_SUBNET_RANGE": "10.10.1.0/24", +# "DCI_SUBNET_TARGET_MASK": "30", +# "DEPLOYMENT_FREEZE": "false", +# "DHCP_ENABLE": "false", +# "DHCP_END": "", +# "DHCP_END_INTERNAL": "", +# "DHCP_IPV6_ENABLE": "", +# "DHCP_IPV6_ENABLE_INTERNAL": "", +# "DHCP_START": "", +# "DHCP_START_INTERNAL": "", +# "DOMAIN_NAME_INTERNAL": "", +# "ENABLE_AAA": "", +# "ENABLE_NETFLOW": "false", +# "ENABLE_NETFLOW_PREV": "false", +# "ENABLE_NXAPI": "false", +# "ENABLE_NXAPI_HTTP": "false", +# "EXT_FABRIC_TYPE": "", +# "FABRIC_FREEFORM": "", +# "FABRIC_NAME": "LAN_CLASSIC_Fabric", +# "FABRIC_TECHNOLOGY": "LANClassic", +# "FABRIC_TYPE": "External", +# "FEATURE_PTP": "false", +# "FEATURE_PTP_INTERNAL": "false", +# "FF": "LANClassic", +# "INBAND_ENABLE": "", +# "INBAND_ENABLE_PREV": "", +# "INBAND_MGMT": "false", +# "INBAND_MGMT_PREV": "false", +# "IS_READ_ONLY": "false", +# "LOOPBACK0_IP_RANGE": "10.1.0.0/22", +# "MGMT_GW": "", +# "MGMT_GW_INTERNAL": "", +# "MGMT_PREFIX": "", +# "MGMT_PREFIX_INTERNAL": "", +# "MGMT_V6PREFIX": "", +# "MGMT_V6PREFIX_INTERNAL": "", +# "MPLS_HANDOFF": "false", +# "MPLS_LB_ID": "", +# "MPLS_LOOPBACK_IP_RANGE": "", +# "NETFLOW_EXPORTER_LIST": "", +# "NETFLOW_MONITOR_LIST": "", +# "NETFLOW_RECORD_LIST": "", +# "NETFLOW_SAMPLER_LIST": "", +# "NXAPI_HTTPS_PORT": "443", +# "NXAPI_HTTP_PORT": "80", +# "NXC_DEST_VRF": "", +# "NXC_PROXY_PORT": "8080", +# "NXC_PROXY_SERVER": "", +# "NXC_SRC_INTF": "", +# "OVERWRITE_GLOBAL_NXC": "false", +# "PM_ENABLE": "false", +# "PM_ENABLE_PREV": "false", +# "POWER_REDUNDANCY_MODE": "ps-redundant", +# "PTP_DOMAIN_ID": "", +# "PTP_LB_ID": "", +# "SNMP_SERVER_HOST_TRAP": "true", +# "SUBINTERFACE_RANGE": "2-511", +# "enableRealTimeBackup": "false", +# "enableScheduledBackup": "false", +# "scheduledTime": "" +# }, +# "operStatus": "HEALTHY", +# "provisionMode": "DCNMTopDown", +# "replicationMode": "IngressReplication", +# "templateName": "LAN_Classic" +# }, +# { +# "asn": "65535", +# "createdOn": 1721954053773, +# "deviceType": "n9k", +# "fabricId": "FABRIC-2", +# "fabricName": "VXLAN_EVPN_Fabric", +# "fabricTechnology": "VXLANFabric", +# "fabricTechnologyFriendly": "VXLAN EVPN", +# "fabricType": "Switch_Fabric", +# "fabricTypeFriendly": "Switch Fabric", +# "id": 2, +# "modifiedOn": 1721954057503, +# "networkExtensionTemplate": "Default_Network_Extension_Universal", +# "networkTemplate": "Default_Network_Universal", +# "nvPairs": { +# "AAA_REMOTE_IP_ENABLED": "false", +# "AAA_SERVER_CONF": "", +# "ACTIVE_MIGRATION": "false", +# "ADVERTISE_PIP_BGP": "false", +# "ADVERTISE_PIP_ON_BORDER": "true", +# "AGENT_INTF": "eth0", +# "AI_ML_QOS_POLICY": "AI_Fabric_QOS_400G", +# "ALLOW_NXC": "true", +# "ALLOW_NXC_PREV": "true", +# "ANYCAST_BGW_ADVERTISE_PIP": "false", +# "ANYCAST_GW_MAC": "2020.0000.00aa", +# "ANYCAST_LB_ID": "", +# "ANYCAST_RP_IP_RANGE": "10.254.254.0/24", +# "ANYCAST_RP_IP_RANGE_INTERNAL": "", +# "AUTO_SYMMETRIC_DEFAULT_VRF": "false", +# "AUTO_SYMMETRIC_VRF_LITE": "false", +# "AUTO_UNIQUE_VRF_LITE_IP_PREFIX": "false", +# "AUTO_UNIQUE_VRF_LITE_IP_PREFIX_PREV": "false", +# "AUTO_VRFLITE_IFC_DEFAULT_VRF": "false", +# "BANNER": "", +# "BFD_AUTH_ENABLE": "false", +# "BFD_AUTH_KEY": "", +# "BFD_AUTH_KEY_ID": "", +# "BFD_ENABLE": "false", +# "BFD_ENABLE_PREV": "", +# "BFD_IBGP_ENABLE": "false", +# "BFD_ISIS_ENABLE": "false", +# "BFD_OSPF_ENABLE": "false", +# "BFD_PIM_ENABLE": "false", +# "BGP_AS": "65535", +# "BGP_AS_PREV": "65535", +# "BGP_AUTH_ENABLE": "false", +# "BGP_AUTH_KEY": "", +# "BGP_AUTH_KEY_TYPE": "3", +# "BGP_LB_ID": "0", +# "BOOTSTRAP_CONF": "", +# "BOOTSTRAP_ENABLE": "false", +# "BOOTSTRAP_ENABLE_PREV": "false", +# "BOOTSTRAP_MULTISUBNET": "#Scope_Start_IP, Scope_End_IP, Scope_Default_Gateway, Scope_Subnet_Prefix", +# "BOOTSTRAP_MULTISUBNET_INTERNAL": "", +# "BRFIELD_DEBUG_FLAG": "Disable", +# "BROWNFIELD_NETWORK_NAME_FORMAT": "Auto_Net_VNI$$VNI$$_VLAN$$VLAN_ID$$", +# "BROWNFIELD_SKIP_OVERLAY_NETWORK_ATTACHMENTS": "false", +# "CDP_ENABLE": "false", +# "COPP_POLICY": "strict", +# "DCI_SUBNET_RANGE": "10.33.0.0/16", +# "DCI_SUBNET_TARGET_MASK": "30", +# "DEAFULT_QUEUING_POLICY_CLOUDSCALE": "queuing_policy_default_8q_cloudscale", +# "DEAFULT_QUEUING_POLICY_OTHER": "queuing_policy_default_other", +# "DEAFULT_QUEUING_POLICY_R_SERIES": "queuing_policy_default_r_series", +# "DEFAULT_VRF_REDIS_BGP_RMAP": "", +# "DEPLOYMENT_FREEZE": "false", +# "DHCP_ENABLE": "false", +# "DHCP_END": "", +# "DHCP_END_INTERNAL": "", +# "DHCP_IPV6_ENABLE": "", +# "DHCP_IPV6_ENABLE_INTERNAL": "", +# "DHCP_START": "", +# "DHCP_START_INTERNAL": "", +# "DNS_SERVER_IP_LIST": "", +# "DNS_SERVER_VRF": "", +# "DOMAIN_NAME_INTERNAL": "", +# "ENABLE_AAA": "false", +# "ENABLE_AGENT": "false", +# "ENABLE_AI_ML_QOS_POLICY": "false", +# "ENABLE_AI_ML_QOS_POLICY_FLAP": "false", +# "ENABLE_DEFAULT_QUEUING_POLICY": "false", +# "ENABLE_EVPN": "true", +# "ENABLE_FABRIC_VPC_DOMAIN_ID": "false", +# "ENABLE_FABRIC_VPC_DOMAIN_ID_PREV": "false", +# "ENABLE_L3VNI_NO_VLAN": "false", +# "ENABLE_MACSEC": "false", +# "ENABLE_NETFLOW": "false", +# "ENABLE_NETFLOW_PREV": "false", +# "ENABLE_NGOAM": "true", +# "ENABLE_NXAPI": "true", +# "ENABLE_NXAPI_HTTP": "true", +# "ENABLE_PBR": "false", +# "ENABLE_PVLAN": "false", +# "ENABLE_PVLAN_PREV": "false", +# "ENABLE_SGT": "false", +# "ENABLE_SGT_PREV": "false", +# "ENABLE_TENANT_DHCP": "true", +# "ENABLE_TRM": "false", +# "ENABLE_VPC_PEER_LINK_NATIVE_VLAN": "false", +# "ESR_OPTION": "PBR", +# "EXTRA_CONF_INTRA_LINKS": "", +# "EXTRA_CONF_LEAF": "", +# "EXTRA_CONF_SPINE": "", +# "EXTRA_CONF_TOR": "", +# "EXT_FABRIC_TYPE": "", +# "FABRIC_INTERFACE_TYPE": "p2p", +# "FABRIC_MTU": "9216", +# "FABRIC_MTU_PREV": "9216", +# "FABRIC_NAME": "VXLAN_EVPN_Fabric", +# "FABRIC_TYPE": "Switch_Fabric", +# "FABRIC_VPC_DOMAIN_ID": "", +# "FABRIC_VPC_DOMAIN_ID_PREV": "", +# "FABRIC_VPC_QOS": "false", +# "FABRIC_VPC_QOS_POLICY_NAME": "spine_qos_for_fabric_vpc_peering", +# "FEATURE_PTP": "false", +# "FEATURE_PTP_INTERNAL": "false", +# "FF": "Easy_Fabric", +# "GRFIELD_DEBUG_FLAG": "Disable", +# "HD_TIME": "180", +# "HOST_INTF_ADMIN_STATE": "true", +# "IBGP_PEER_TEMPLATE": "", +# "IBGP_PEER_TEMPLATE_LEAF": "", +# "INBAND_DHCP_SERVERS": "", +# "INBAND_MGMT": "false", +# "INBAND_MGMT_PREV": "false", +# "ISIS_AREA_NUM": "0001", +# "ISIS_AREA_NUM_PREV": "", +# "ISIS_AUTH_ENABLE": "false", +# "ISIS_AUTH_KEY": "", +# "ISIS_AUTH_KEYCHAIN_KEY_ID": "", +# "ISIS_AUTH_KEYCHAIN_NAME": "", +# "ISIS_LEVEL": "level-2", +# "ISIS_OVERLOAD_ELAPSE_TIME": "", +# "ISIS_OVERLOAD_ENABLE": "", +# "ISIS_P2P_ENABLE": "", +# "L2_HOST_INTF_MTU": "9216", +# "L2_HOST_INTF_MTU_PREV": "9216", +# "L2_SEGMENT_ID_RANGE": "30000-49000", +# "L3VNI_MCAST_GROUP": "", +# "L3_PARTITION_ID_RANGE": "50000-59000", +# "LINK_STATE_ROUTING": "ospf", +# "LINK_STATE_ROUTING_TAG": "UNDERLAY", +# "LINK_STATE_ROUTING_TAG_PREV": "UNDERLAY", +# "LOOPBACK0_IPV6_RANGE": "", +# "LOOPBACK0_IP_RANGE": "10.2.0.0/22", +# "LOOPBACK1_IPV6_RANGE": "", +# "LOOPBACK1_IP_RANGE": "10.3.0.0/22", +# "MACSEC_ALGORITHM": "", +# "MACSEC_CIPHER_SUITE": "", +# "MACSEC_FALLBACK_ALGORITHM": "", +# "MACSEC_FALLBACK_KEY_STRING": "", +# "MACSEC_KEY_STRING": "", +# "MACSEC_REPORT_TIMER": "", +# "MGMT_GW": "", +# "MGMT_GW_INTERNAL": "", +# "MGMT_PREFIX": "", +# "MGMT_PREFIX_INTERNAL": "", +# "MGMT_V6PREFIX": "", +# "MGMT_V6PREFIX_INTERNAL": "", +# "MPLS_HANDOFF": "false", +# "MPLS_ISIS_AREA_NUM": "0001", +# "MPLS_ISIS_AREA_NUM_PREV": "", +# "MPLS_LB_ID": "", +# "MPLS_LOOPBACK_IP_RANGE": "", +# "MSO_CONNECTIVITY_DEPLOYED": "", +# "MSO_CONTROLER_ID": "", +# "MSO_SITE_GROUP_NAME": "", +# "MSO_SITE_ID": "", +# "MST_INSTANCE_RANGE": "", +# "MULTICAST_GROUP_SUBNET": "239.1.1.0/25", +# "NETFLOW_EXPORTER_LIST": "", +# "NETFLOW_MONITOR_LIST": "", +# "NETFLOW_RECORD_LIST": "", +# "NETWORK_VLAN_RANGE": "2300-2999", +# "NTP_SERVER_IP_LIST": "", +# "NTP_SERVER_VRF": "", +# "NVE_LB_ID": "1", +# "NXAPI_HTTPS_PORT": "443", +# "NXAPI_HTTP_PORT": "80", +# "NXC_DEST_VRF": "", +# "NXC_PROXY_PORT": "8080", +# "NXC_PROXY_SERVER": "", +# "NXC_SRC_INTF": "", +# "OBJECT_TRACKING_NUMBER_RANGE": "100-299", +# "OSPF_AREA_ID": "0.0.0.0", +# "OSPF_AUTH_ENABLE": "false", +# "OSPF_AUTH_KEY": "", +# "OSPF_AUTH_KEY_ID": "", +# "OVERLAY_MODE": "cli", +# "OVERLAY_MODE_PREV": "cli", +# "OVERWRITE_GLOBAL_NXC": "false", +# "PER_VRF_LOOPBACK_AUTO_PROVISION": "false", +# "PER_VRF_LOOPBACK_AUTO_PROVISION_PREV": "false", +# "PER_VRF_LOOPBACK_AUTO_PROVISION_V6": "false", +# "PER_VRF_LOOPBACK_AUTO_PROVISION_V6_PREV": "false", +# "PER_VRF_LOOPBACK_IP_RANGE": "", +# "PER_VRF_LOOPBACK_IP_RANGE_V6": "", +# "PHANTOM_RP_LB_ID1": "", +# "PHANTOM_RP_LB_ID2": "", +# "PHANTOM_RP_LB_ID3": "", +# "PHANTOM_RP_LB_ID4": "", +# "PIM_HELLO_AUTH_ENABLE": "false", +# "PIM_HELLO_AUTH_KEY": "", +# "PM_ENABLE": "false", +# "PM_ENABLE_PREV": "false", +# "POWER_REDUNDANCY_MODE": "ps-redundant", +# "PREMSO_PARENT_FABRIC": "", +# "PTP_DOMAIN_ID": "", +# "PTP_LB_ID": "", +# "REPLICATION_MODE": "Multicast", +# "ROUTER_ID_RANGE": "", +# "ROUTE_MAP_SEQUENCE_NUMBER_RANGE": "1-65534", +# "RP_COUNT": "2", +# "RP_LB_ID": "254", +# "RP_MODE": "asm", +# "RR_COUNT": "2", +# "SEED_SWITCH_CORE_INTERFACES": "", +# "SERVICE_NETWORK_VLAN_RANGE": "3000-3199", +# "SGT_ID_RANGE": "", +# "SGT_NAME_PREFIX": "", +# "SGT_PREPROVISION": "", +# "SITE_ID": "65535", +# "SLA_ID_RANGE": "10000-19999", +# "SNMP_SERVER_HOST_TRAP": "true", +# "SPINE_COUNT": "0", +# "SPINE_SWITCH_CORE_INTERFACES": "", +# "SSPINE_ADD_DEL_DEBUG_FLAG": "Disable", +# "SSPINE_COUNT": "0", +# "STATIC_UNDERLAY_IP_ALLOC": "false", +# "STP_BRIDGE_PRIORITY": "", +# "STP_ROOT_OPTION": "unmanaged", +# "STP_VLAN_RANGE": "", +# "STRICT_CC_MODE": "false", +# "SUBINTERFACE_RANGE": "2-511", +# "SUBNET_RANGE": "10.4.0.0/16", +# "SUBNET_TARGET_MASK": "30", +# "SYSLOG_SERVER_IP_LIST": "", +# "SYSLOG_SERVER_VRF": "", +# "SYSLOG_SEV": "", +# "TCAM_ALLOCATION": "true", +# "TOPDOWN_CONFIG_RM_TRACKING": "notstarted", +# "UNDERLAY_IS_V6": "false", +# "UNNUM_BOOTSTRAP_LB_ID": "", +# "UNNUM_DHCP_END": "", +# "UNNUM_DHCP_END_INTERNAL": "", +# "UNNUM_DHCP_START": "", +# "UNNUM_DHCP_START_INTERNAL": "", +# "UPGRADE_FROM_VERSION": "", +# "USE_LINK_LOCAL": "true", +# "V6_SUBNET_RANGE": "", +# "V6_SUBNET_TARGET_MASK": "126", +# "VPC_AUTO_RECOVERY_TIME": "360", +# "VPC_DELAY_RESTORE": "150", +# "VPC_DELAY_RESTORE_TIME": "60", +# "VPC_DOMAIN_ID_RANGE": "1-1000", +# "VPC_ENABLE_IPv6_ND_SYNC": "true", +# "VPC_PEER_KEEP_ALIVE_OPTION": "management", +# "VPC_PEER_LINK_PO": "500", +# "VPC_PEER_LINK_VLAN": "3600", +# "VRF_LITE_AUTOCONFIG": "Manual", +# "VRF_VLAN_RANGE": "2000-2299", +# "abstract_anycast_rp": "anycast_rp", +# "abstract_bgp": "base_bgp", +# "abstract_bgp_neighbor": "evpn_bgp_rr_neighbor", +# "abstract_bgp_rr": "evpn_bgp_rr", +# "abstract_dhcp": "base_dhcp", +# "abstract_extra_config_bootstrap": "extra_config_bootstrap_11_1", +# "abstract_extra_config_leaf": "extra_config_leaf", +# "abstract_extra_config_spine": "extra_config_spine", +# "abstract_extra_config_tor": "extra_config_tor", +# "abstract_feature_leaf": "base_feature_leaf_upg", +# "abstract_feature_spine": "base_feature_spine_upg", +# "abstract_isis": "base_isis_level2", +# "abstract_isis_interface": "isis_interface", +# "abstract_loopback_interface": "int_fabric_loopback_11_1", +# "abstract_multicast": "base_multicast_11_1", +# "abstract_ospf": "base_ospf", +# "abstract_ospf_interface": "ospf_interface_11_1", +# "abstract_pim_interface": "pim_interface", +# "abstract_route_map": "route_map", +# "abstract_routed_host": "int_routed_host", +# "abstract_trunk_host": "int_trunk_host", +# "abstract_vlan_interface": "int_fabric_vlan_11_1", +# "abstract_vpc_domain": "base_vpc_domain_11_1", +# "default_network": "Default_Network_Universal", +# "default_pvlan_sec_network": "", +# "default_vrf": "Default_VRF_Universal", +# "enableRealTimeBackup": "", +# "enableScheduledBackup": "", +# "network_extension_template": "Default_Network_Extension_Universal", +# "scheduledTime": "", +# "temp_anycast_gateway": "anycast_gateway", +# "temp_vpc_domain_mgmt": "vpc_domain_mgmt", +# "temp_vpc_peer_link": "int_vpc_peer_link_po", +# "vrf_extension_template": "Default_VRF_Extension_Universal" +# }, +# "operStatus": "HEALTHY", +# "provisionMode": "DCNMTopDown", +# "replicationMode": "Multicast", +# "siteId": "65535", +# "templateName": "Easy_Fabric", +# "vrfExtensionTemplate": "Default_VRF_Extension_Universal", +# "vrfTemplate": "Default_VRF_Universal" +# } +# ], +# "MESSAGE": "OK", +# "METHOD": "GET", +# "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/lan-fabric/rest/control/fabrics", +# "RETURN_CODE": 200, +# "sequence_number": 1 +# } +# ], +# "result": [ +# { +# "found": true, +# "sequence_number": 1, +# "success": true +# } +# ] +# } +# } +################################################################################ +- name: QUERY - TEST - Query the fabrics + cisco.dcnm.dcnm_fabric: + state: query + config: + - FABRIC_NAME: "{{ fabric_name_1 }}" + - FABRIC_NAME: "{{ fabric_name_2 }}" + - FABRIC_NAME: "{{ fabric_name_3 }}" + register: result +- debug: + var: result +- assert: + that: + - result.changed == false + - result.failed == false + - (result.diff | length) == 1 + - fabric_name_1 in result.diff[0] + - fabric_name_2 in result.diff[0] + - fabric_name_3 in result.diff[0] + - result.diff[0].sequence_number == 1 + - (result.metadata | length) == 1 + - result.metadata[0].action == "fabric_query" + - result.metadata[0].check_mode == false + - result.metadata[0].sequence_number == 1 + - result.metadata[0].state == "query" + - (result.response | length) == 1 + - result.response[0].MESSAGE == "OK" + - result.response[0].METHOD == "GET" + - result.response[0].RETURN_CODE == 200 + - result.response[0].sequence_number == 1 + - (result.response[0].DATA | length) == 3 + - result.result[0].found == true + - result.result[0].sequence_number == 1 + - result.result[0].success == true + +################################################################################ +# QUERY - CLEANUP - Delete the fabrics +################################################################################ +# Expected result +# ok: [ndfc1] => { +# "result": { +# "changed": true, +# "diff": [ +# { +# "FABRIC_NAME": "VXLAN_EVPN_Fabric", +# "sequence_number": 1 +# }, +# { +# "FABRIC_NAME": "VXLAN_EVPN_MSD_Fabric", +# "sequence_number": 2 +# }, +# { +# "FABRIC_NAME": "LAN_CLASSIC_Fabric", +# "sequence_number": 3 +# } +# ], +# "failed": false, +# "metadata": [ +# { +# "action": "fabric_delete", +# "check_mode": false, +# "sequence_number": 1, +# "state": "deleted" +# }, +# { +# "action": "fabric_delete", +# "check_mode": false, +# "sequence_number": 2, +# "state": "deleted" +# }, +# { +# "action": "fabric_delete", +# "check_mode": false, +# "sequence_number": 3, +# "state": "deleted" +# } +# ], +# "response": [ +# { +# "DATA": "Fabric 'VXLAN_EVPN_Fabric' is deleted successfully!", +# "MESSAGE": "OK", +# "METHOD": "DELETE", +# "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/rest/control/fabrics/VXLAN_EVPN_Fabric", +# "RETURN_CODE": 200, +# "sequence_number": 1 +# }, +# { +# "DATA": "Fabric 'VXLAN_EVPN_MSD_Fabric' is deleted successfully!", +# "MESSAGE": "OK", +# "METHOD": "DELETE", +# "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/rest/control/fabrics/VXLAN_EVPN_MSD_Fabric", +# "RETURN_CODE": 200, +# "sequence_number": 2 +# }, +# { +# "DATA": "Fabric 'LAN_CLASSIC_Fabric' is deleted successfully!", +# "MESSAGE": "OK", +# "METHOD": "DELETE", +# "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/rest/control/fabrics/LAN_CLASSIC_Fabric", +# "RETURN_CODE": 200, +# "sequence_number": 3 +# } +# ], +# "result": [ +# { +# "changed": true, +# "sequence_number": 1, +# "success": true +# }, +# { +# "changed": true, +# "sequence_number": 2, +# "success": true +# }, +# { +# "changed": true, +# "sequence_number": 3, +# "success": true +# } +# ] +# } +# } +################################################################################ +- name: QUERY - CLEANUP - Delete the fabrics + cisco.dcnm.dcnm_fabric: + state: deleted + config: + - FABRIC_NAME: "{{ fabric_name_1 }}" + - FABRIC_NAME: "{{ fabric_name_2 }}" + - FABRIC_NAME: "{{ fabric_name_3 }}" + register: result +- debug: + var: result +- assert: + that: + - result.changed == true + - result.failed == false + - (result.diff | length) == 3 + - result.diff[0].FABRIC_NAME == fabric_name_1 + - result.diff[0].sequence_number == 1 + - result.diff[1].FABRIC_NAME == fabric_name_2 + - result.diff[1].sequence_number == 2 + - result.diff[2].FABRIC_NAME == fabric_name_3 + - result.diff[2].sequence_number == 3 + - (result.metadata | length) == 3 + - result.metadata[0].action == "fabric_delete" + - result.metadata[0].check_mode == False + - result.metadata[0].sequence_number == 1 + - result.metadata[0].state == "deleted" + - result.metadata[1].action == "fabric_delete" + - result.metadata[1].check_mode == False + - result.metadata[1].sequence_number == 2 + - result.metadata[1].state == "deleted" + - result.metadata[2].action == "fabric_delete" + - result.metadata[2].check_mode == False + - result.metadata[2].sequence_number == 3 + - result.metadata[2].state == "deleted" + - (result.response | length) == 3 + - result.response[0].DATA is match '.*deleted successfully.*' + - result.response[0].MESSAGE == "OK" + - result.response[0].METHOD == "DELETE" + - result.response[0].RETURN_CODE == 200 + - result.response[0].sequence_number == 1 + - result.response[1].DATA is match '.*deleted successfully.*' + - result.response[1].MESSAGE == "OK" + - result.response[1].METHOD == "DELETE" + - result.response[1].RETURN_CODE == 200 + - result.response[1].sequence_number == 2 + - result.response[2].DATA is match '.*deleted successfully.*' + - result.response[2].MESSAGE == "OK" + - result.response[2].METHOD == "DELETE" + - result.response[2].RETURN_CODE == 200 + - result.response[2].sequence_number == 3 + - (result.result | length) == 3 + - result.result[0].changed == true + - result.result[0].success == true + - result.result[0].sequence_number == 1 + - result.result[1].changed == true + - result.result[1].success == true + - result.result[1].sequence_number == 2 + - result.result[2].changed == true + - result.result[2].success == true + - result.result[2].sequence_number == 3 diff --git a/tests/integration/targets/dcnm_fabric/tests/dcnm_fabric_replaced_basic.yaml b/tests/integration/targets/dcnm_fabric/tests/dcnm_fabric_replaced_basic.yaml index 445b8092c..ae31a43c6 100644 --- a/tests/integration/targets/dcnm_fabric/tests/dcnm_fabric_replaced_basic.yaml +++ b/tests/integration/targets/dcnm_fabric/tests/dcnm_fabric_replaced_basic.yaml @@ -28,24 +28,29 @@ # TEST # 4. Create fabrics with non-default configs and verify result # - fabric_name_1 -# - fabric_name_1 -# - fabric_name_1 +# - fabric_name_2 +# - fabric_name_3 # 5. Replace configs for fabric_1 and fabric_2 and verify result # 6. Replace config for fabric_3 and verify result # CLEANUP # 7. Delete fabrics under test # - fabric_name_1 -# - fabric_name_1 -# - fabric_name_1 +# - fabric_name_2 +# - fabric_name_3 ################################################################################ # REQUIREMENTS ################################################################################ -# Example vars for dcnm_image_policy integration tests -# Add to cisco/dcnm/playbooks/dcnm_tests.yaml +# Inventory: +# ./playbooks/roles/dcnm_fabric/dcnm_hosts.yaml +# Playbook: +# ./playbooks/roles/dcnm_fabric/dcnm_tests.yaml +# Roles: +# ./tests/integration/targets/dcnm_fabric/tests/*.yaml +# +# Example vars: # # vars: -# # This testcase field can run any test in the tests directory for the role -# testcase: deleted +# testcase: dcnm_fabric_replaced_basic # fabric_name_1: VXLAN_EVPN_Fabric # fabric_type_1: VXLAN_EVPN # fabric_name_2: VXLAN_EVPN_MSD_Fabric @@ -235,15 +240,15 @@ - result.diff[2].IS_READ_ONLY == false - result.diff[2].NXC_PROXY_PORT == 8088 - result.diff[2].sequence_number == 3 - - result.metadata[0].action == "create" + - result.metadata[0].action == "fabric_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].action == "fabric_create" - result.metadata[1].check_mode == False - result.metadata[1].sequence_number == 2 - result.metadata[1].state == "merged" - - result.metadata[2].action == "create" + - result.metadata[2].action == "fabric_create" - result.metadata[2].check_mode == False - result.metadata[2].sequence_number == 3 - result.metadata[2].state == "merged" @@ -465,11 +470,11 @@ - result.diff[4].sequence_number == 5 - result.diff[5].sequence_number == 6 - (result.metadata | length) == 6 - - result.metadata[0].action == "replace" + - result.metadata[0].action == "fabric_replace" - result.metadata[0].check_mode == False - result.metadata[0].sequence_number == 1 - result.metadata[0].state == "replaced" - - result.metadata[1].action == "replace" + - result.metadata[1].action == "fabric_replace" - result.metadata[1].check_mode == False - result.metadata[1].sequence_number == 2 - result.metadata[1].state == "replaced" @@ -653,7 +658,7 @@ - result.diff[1].sequence_number == 2 - result.diff[2].sequence_number == 3 - (result.metadata | length) == 3 - - result.metadata[0].action == "replace" + - result.metadata[0].action == "fabric_replace" - result.metadata[0].check_mode == False - result.metadata[0].sequence_number == 1 - result.metadata[0].state == "replaced" @@ -749,7 +754,7 @@ - (result.diff | length) == 1 - result.diff[0].sequence_number == 1 - (result.metadata | length) == 1 - - result.metadata[0].action == "replace" + - result.metadata[0].action == "fabric_replace" - result.metadata[0].check_mode == False - result.metadata[0].sequence_number == 1 - result.metadata[0].state == "replaced" @@ -871,15 +876,15 @@ - result.diff[2].FABRIC_NAME == fabric_name_3 - result.diff[2].sequence_number == 3 - (result.metadata | length) == 3 - - result.metadata[0].action == "delete" + - result.metadata[0].action == "fabric_delete" - result.metadata[0].check_mode == False - result.metadata[0].sequence_number == 1 - result.metadata[0].state == "deleted" - - result.metadata[1].action == "delete" + - result.metadata[1].action == "fabric_delete" - result.metadata[1].check_mode == False - result.metadata[1].sequence_number == 2 - result.metadata[1].state == "deleted" - - result.metadata[2].action == "delete" + - result.metadata[2].action == "fabric_delete" - result.metadata[2].check_mode == False - result.metadata[2].sequence_number == 3 - result.metadata[2].state == "deleted" diff --git a/tests/integration/targets/dcnm_fabric/tests/dcnm_fabric_replaced_basic_ipfm.yaml b/tests/integration/targets/dcnm_fabric/tests/dcnm_fabric_replaced_basic_ipfm.yaml index 5b0c84a15..607da2e25 100644 --- a/tests/integration/targets/dcnm_fabric/tests/dcnm_fabric_replaced_basic_ipfm.yaml +++ b/tests/integration/targets/dcnm_fabric/tests/dcnm_fabric_replaced_basic_ipfm.yaml @@ -29,12 +29,17 @@ ################################################################################ # REQUIREMENTS ################################################################################ -# Example vars for dcnm_image_policy integration tests -# Add to cisco/dcnm/playbooks/dcnm_tests.yaml +# Inventory: +# ./playbooks/roles/dcnm_fabric/dcnm_hosts.yaml +# Playbook: +# ./playbooks/roles/dcnm_fabric/dcnm_tests.yaml +# Roles: +# ./tests/integration/targets/dcnm_fabric/tests/*.yaml +# +# Example vars: # # vars: -# # This testcase field can run any test in the tests directory for the role -# testcase: deleted +# testcase: dcnm_fabric_replaced_basic_ipfm # fabric_name_4: IPFM_Fabric # fabric_type_4: IPFM ################################################################################ @@ -117,7 +122,7 @@ - result.diff[0].sequence_number == 1 - result.diff[0].FABRIC_MTU == 1500 - (result.metadata | length) == 1 - - result.metadata[0].action == "create" + - result.metadata[0].action == "fabric_create" - result.metadata[0].check_mode == False - result.metadata[0].sequence_number == 1 - result.metadata[0].state == "merged" @@ -240,7 +245,7 @@ - result.diff[1].sequence_number == 2 - result.diff[2].sequence_number == 3 - (result.metadata | length) == 3 - - result.metadata[0].action == "replace" + - result.metadata[0].action == "fabric_replace" - result.metadata[0].check_mode == False - result.metadata[0].sequence_number == 1 - result.metadata[0].state == "replaced" @@ -327,7 +332,7 @@ - (result.diff | length) == 1 - result.diff[0].sequence_number == 1 - (result.metadata | length) == 1 - - result.metadata[0].action == "replace" + - result.metadata[0].action == "fabric_replace" - result.metadata[0].check_mode == False - result.metadata[0].sequence_number == 1 - result.metadata[0].state == "replaced" @@ -397,7 +402,7 @@ - result.diff[0].FABRIC_NAME == fabric_name_4 - result.diff[0].sequence_number == 1 - (result.metadata | length) == 1 - - result.metadata[0].action == "delete" + - result.metadata[0].action == "fabric_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_fabric/tests/dcnm_fabric_replaced_basic_vxlan.yaml b/tests/integration/targets/dcnm_fabric/tests/dcnm_fabric_replaced_basic_vxlan.yaml new file mode 100644 index 000000000..1b55b8a69 --- /dev/null +++ b/tests/integration/targets/dcnm_fabric/tests/dcnm_fabric_replaced_basic_vxlan.yaml @@ -0,0 +1,447 @@ +################################################################################ +# RUNTIME +################################################################################ +# Recent run times (MM:SS.ms): +# 00:78.47 +################################################################################ +# DESCRIPTION - BASIC FABRIC REPLACED STATE TEST +# +# Test basic replace of new fabric configurations and verify results. +# - config-save and config-deploy not tested here. +# - See dcnm_fabric_replaced_save_deploy.yaml instead. +################################################################################ +# STEPS +################################################################################ +# SETUP +# 1. The following fabrics must be empty on the controller +# See vars: section in cisco/dcnm/playbooks/dcnm_tests.yaml +# - fabric_name_1 +# - fabric_type_1 # VXLAN_EVPN +# 3. Delete fabrics under test, if they exist +# - fabric_name_1 +# TEST +# 4. Create fabrics with non-default configs and verify result +# - fabric_name_1 +# 5. Replace configs for fabric_1 and fabric_2 and verify result +# 6. Replace config for fabric_3 and verify result +# CLEANUP +# 7. Delete fabrics under test +# - fabric_name_1 +################################################################################ +# REQUIREMENTS +################################################################################ +# Inventory: +# ./playbooks/roles/dcnm_fabric/dcnm_hosts.yaml +# Playbook: +# ./playbooks/roles/dcnm_fabric/dcnm_tests.yaml +# Roles: +# ./tests/integration/targets/dcnm_fabric/tests/*.yaml +# +# Example vars: +# +# vars: +# testcase: dcnm_fabric_replaced_basic_vxlan +# fabric_name_1: VXLAN_EVPN_Fabric +# fabric_type_1: VXLAN_EVPN +################################################################################ +# REPLACED - SETUP - Delete fabrics +################################################################################ +- name: REPLACED - SETUP - Delete fabrics + cisco.dcnm.dcnm_fabric: + state: deleted + config: + - FABRIC_NAME: "{{ fabric_name_1 }}" + register: result +- debug: + var: result +################################################################################ +# REPLACED - TEST - Create fabric_name_1 with non-default config +################################################################################ +# Expected result +# - All untested nvPairs removed for brevity. +# - Fabric global keys in DATA removed for brevity. +# ok: [ndfc1] => { +# "result": { +# "changed": true, +# "diff": [ +# { +# "ADVERTISE_PIP_BGP": true, +# "ANYCAST_GW_MAC": "00aa.bbcc.ddee", +# "BGP_AS": 65535, +# "FABRIC_NAME": "VXLAN_EVPN_Fabric", +# "REPLICATION_MODE": "Ingress", +# "UNDERLAY_IS_V6": false, +# "sequence_number": 1 +# } +# ], +# "failed": false, +# "metadata": [ +# { +# "action": "fabric_create", +# "check_mode": false, +# "sequence_number": 1, +# "state": "merged" +# }, +# ], +# "response": [ +# { +# "DATA": { +# "nvPairs": { +# "ADVERTISE_PIP_BGP": "true", +# "ANYCAST_GW_MAC": "00aa.bbcc.ddee", +# "BGP_AS": "65535", +# "FABRIC_NAME": "VXLAN_EVPN_Fabric", +# "REPLICATION_MODE": "Ingress", +# "UNDERLAY_IS_V6": "false", +# } +# }, +# "MESSAGE": "OK", +# "METHOD": "POST", +# "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/rest/control/fabrics/VXLAN_EVPN_Fabric/Easy_Fabric", +# "RETURN_CODE": 200, +# "sequence_number": 1 +# } +# ], +# "result": [ +# { +# "changed": true, +# "sequence_number": 1, +# "success": true +# } +# ] +# } +# } +################################################################################ +- name: REPLACED - TEST - Create fabric_name_1 with non-default config + cisco.dcnm.dcnm_fabric: + state: merged + config: + - FABRIC_NAME: "{{ fabric_name_1 }}" + FABRIC_TYPE: "{{ fabric_type_1 }}" + ADVERTISE_PIP_BGP: true + ANYCAST_GW_MAC: 00:aa:bb:cc:dd:ee + BGP_AS: 65535 + REPLICATION_MODE: Ingress + UNDERLAY_IS_V6: false + DEPLOY: false + register: result +- debug: + var: result +- assert: + that: + - result.changed == true + - result.failed == false + - (result.diff | length) == 1 + - result.diff[0].BGP_AS == 65535 + - result.diff[0].FABRIC_NAME == fabric_name_1 + - result.diff[0].sequence_number == 1 + - result.diff[0].ADVERTISE_PIP_BGP == true + - result.diff[0].ANYCAST_GW_MAC == "00aa.bbcc.ddee" + - result.diff[0].REPLICATION_MODE == "Ingress" + - result.diff[0].UNDERLAY_IS_V6 == false + - (result.metadata | length) == 1 + - result.metadata[0].action == "fabric_create" + - result.metadata[0].check_mode == False + - result.metadata[0].sequence_number == 1 + - result.metadata[0].state == "merged" + - (result.response | length) == 1 + - result.response[0].sequence_number == 1 + - result.response[0].MESSAGE == "OK" + - result.response[0].METHOD == "POST" + - result.response[0].RETURN_CODE == 200 + - result.response[0].DATA.nvPairs.ADVERTISE_PIP_BGP == "true" + - result.response[0].DATA.nvPairs.ANYCAST_GW_MAC == "00aa.bbcc.ddee" + - result.response[0].DATA.nvPairs.BGP_AS == "65535" + - result.response[0].DATA.nvPairs.FABRIC_NAME == "VXLAN_EVPN_Fabric" + - result.response[0].DATA.nvPairs.REPLICATION_MODE == "Ingress" + - result.response[0].DATA.nvPairs.UNDERLAY_IS_V6 == "false" +################################################################################ +# REPLACED - TEST - Replace config for fabric_1 with default config +################################################################################ +# Expected result +# - All untested nvPairs removed for brevity. +# - Fabric global keys in DATA removed for brevity. +# ok: [172.22.150.244] => { +# "result": { +# "changed": true, +# "diff": [ +# { +# "ADVERTISE_PIP_BGP": "false", +# "ANYCAST_GW_MAC": "2020.0000.00aa", +# "FABRIC_NAME": "VXLAN_EVPN_Fabric", +# "REPLICATION_MODE": "Multicast", +# "sequence_number": 1 +# }, +# { +# "sequence_number": 2 +# }, +# { +# "sequence_number": 3 +# } +# ], +# "failed": false, +# "metadata": [ +# { +# "action": "fabric_replace", +# "check_mode": false, +# "sequence_number": 1, +# "state": "replaced" +# }, +# { +# "action": "config_save", +# "check_mode": false, +# "sequence_number": 2, +# "state": "replaced" +# }, +# { +# "action": "config_deploy", +# "check_mode": false, +# "sequence_number": 3, +# "state": "replaced" +# } +# ], +# "response": [ +# { +# "DATA": { +# "nvPairs": { +# "ADVERTISE_PIP_BGP": "false", +# "ANYCAST_GW_MAC": "2020.0000.00aa", +# "FABRIC_NAME": "VXLAN_EVPN_Fabric", +# "REPLICATION_MODE": "Multicast", +# } +# }, +# "MESSAGE": "OK", +# "METHOD": "PUT", +# "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/lan-fabric/rest/control/fabrics/VXLAN_EVPN_Fabric/Easy_Fabric", +# "RETURN_CODE": 200, +# "sequence_number": 1 +# }, +# { +# "MESSAGE": "Fabric VXLAN_EVPN_Fabric DEPLOY is False or None. Skipping config-save.", +# "RETURN_CODE": 200, +# "sequence_number": 2 +# }, +# { +# "MESSAGE": "FabricConfigDeploy._can_fabric_be_deployed: Fabric VXLAN_EVPN_Fabric DEPLOY is False or None. Skipping config-deploy.", +# "RETURN_CODE": 200, +# "sequence_number": 3 +# } +# ], +# "result": [ +# { +# "changed": true, +# "sequence_number": 1, +# "success": true +# }, +# { +# "changed": true, +# "sequence_number": 2, +# "success": true +# }, +# { +# "changed": true, +# "sequence_number": 3, +# "success": true +# } +# ] +# } +# } +################################################################################ +- name: REPLACED - TEST - Replace config for fabric_1 with default config + cisco.dcnm.dcnm_fabric: &replace_fabric_1 + state: replaced + config: + - FABRIC_NAME: "{{ fabric_name_1 }}" + FABRIC_TYPE: "{{ fabric_type_1 }}" + BGP_AS: 65535 + DEPLOY: false + register: result +- debug: + var: result +- assert: + that: + - result.changed == true + - result.failed == false + - (result.diff | length) == 3 + - result.diff[0].ADVERTISE_PIP_BGP == "false" + - result.diff[0].ANYCAST_GW_MAC == "2020.0000.00aa" + - result.diff[0].FABRIC_NAME == fabric_name_1 + - result.diff[0].REPLICATION_MODE == "Multicast" + - result.diff[0].sequence_number == 1 + - result.diff[1].sequence_number == 2 + - result.diff[2].sequence_number == 3 + - (result.metadata | length) == 3 + - result.metadata[0].action == "fabric_replace" + - result.metadata[0].check_mode == False + - result.metadata[0].sequence_number == 1 + - result.metadata[0].state == "replaced" + - result.metadata[1].action == "config_save" + - result.metadata[1].check_mode == False + - result.metadata[1].sequence_number == 2 + - result.metadata[1].state == "replaced" + - result.metadata[2].action == "config_deploy" + - result.metadata[2].check_mode == False + - result.metadata[2].sequence_number == 3 + - result.metadata[2].state == "replaced" + - (result.response | length) == 3 + - result.response[0].sequence_number == 1 + - result.response[0].MESSAGE == "OK" + - result.response[0].METHOD == "PUT" + - result.response[0].RETURN_CODE == 200 + - result.response[0].DATA.nvPairs.ADVERTISE_PIP_BGP == "false" + - result.response[0].DATA.nvPairs.ANYCAST_GW_MAC == "2020.0000.00aa" + - result.response[0].DATA.nvPairs.FABRIC_NAME == "VXLAN_EVPN_Fabric" + - result.response[0].DATA.nvPairs.REPLICATION_MODE == "Multicast" + - result.response[1].sequence_number == 2 + - result.response[1].RETURN_CODE == 200 + - result.response[1].MESSAGE is match '.*Skipping config-save.*' + - result.response[2].sequence_number == 3 + - result.response[2].RETURN_CODE == 200 + - result.response[2].MESSAGE is match '.*Skipping config-deploy.*' + - (result.result | length) == 3 + - result.result[0].changed == true + - result.result[0].success == true + - result.result[0].sequence_number == 1 + - result.result[1].changed == true + - result.result[1].success == true + - result.result[1].sequence_number == 2 + - result.result[2].changed == true + - result.result[2].success == true + - result.result[2].sequence_number == 3 + +################################################################################ +# REPLACED - TEST - Replace config for fabric_1 with default config omnipotence +################################################################################ +# Expected result +# - All untested nvPairs removed for brevity. +# - Fabric global keys in DATA removed for brevity. +# ok: [ndfc1] => { +# "result": { +# "changed": false, +# "diff": [ +# { +# "sequence_number": 1 +# } +# ], +# "failed": false, +# "metadata": [ +# { +# "action": "fabric_replace", +# "check_mode": false, +# "sequence_number": 1, +# "state": "replaced" +# } +# ], +# "response": [ +# { +# "MESSAGE": "No fabrics to update for replaced state.", +# "RETURN_CODE": 200, +# "sequence_number": 1 +# } +# ], +# "result": [ +# { +# "changed": false, +# "sequence_number": 1, +# "success": true +# } +# ] +# } +# } +################################################################################ +- name: REPLACED - TEST - Replace config for fabric_1 with default config - idempotence + cisco.dcnm.dcnm_fabric: *replace_fabric_1 + register: result +- debug: + var: result +- assert: + that: + - result.changed == false + - result.failed == false + - (result.diff | length) == 1 + - result.diff[0].sequence_number == 1 + - (result.metadata | length) == 1 + - result.metadata[0].action == "fabric_replace" + - result.metadata[0].check_mode == False + - result.metadata[0].sequence_number == 1 + - result.metadata[0].state == "replaced" + - (result.response | length) == 1 + - result.response[0].sequence_number == 1 + - result.response[0].MESSAGE == "No fabrics to update for replaced state." + - result.response[0].RETURN_CODE == 200 + - (result.result | length) == 1 + - result.result[0].changed == false + - result.result[0].success == true + - result.result[0].sequence_number == 1 +################################################################################ +# REPLACED - CLEANUP - Delete the fabrics +################################################################################ +# Expected result +# ok: [ndfc1] => { +# "result": { +# "changed": true, +# "diff": [ +# { +# "FABRIC_NAME": "VXLAN_EVPN_Fabric", +# "sequence_number": 1 +# } +# ], +# "failed": false, +# "metadata": [ +# { +# "action": "fabric_delete", +# "check_mode": false, +# "sequence_number": 1, +# "state": "deleted" +# } +# ], +# "response": [ +# { +# "DATA": "Fabric 'VXLAN_EVPN_Fabric' is deleted successfully!", +# "MESSAGE": "OK", +# "METHOD": "DELETE", +# "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/rest/control/fabrics/VXLAN_EVPN_Fabric", +# "RETURN_CODE": 200, +# "sequence_number": 1 +# } +# ], +# "result": [ +# { +# "changed": true, +# "sequence_number": 1, +# "success": true +# } +# ] +# } +# } +################################################################################ +- name: MERGED - CLEANUP - Delete the fabrics + cisco.dcnm.dcnm_fabric: + state: deleted + config: + - FABRIC_NAME: "{{ fabric_name_1 }}" + register: result +- debug: + var: result +- assert: + that: + - result.changed == true + - result.failed == false + - (result.diff | length) == 1 + - result.diff[0].FABRIC_NAME == fabric_name_1 + - result.diff[0].sequence_number == 1 + - (result.metadata | length) == 1 + - result.metadata[0].action == "fabric_delete" + - result.metadata[0].check_mode == False + - result.metadata[0].sequence_number == 1 + - result.metadata[0].state == "deleted" + - (result.response | length) == 1 + - result.response[0].DATA is match '.*deleted successfully.*' + - result.response[0].MESSAGE == "OK" + - result.response[0].METHOD == "DELETE" + - result.response[0].RETURN_CODE == 200 + - result.response[0].sequence_number == 1 + - (result.result | length) == 1 + - result.result[0].changed == true + - result.result[0].success == true + - result.result[0].sequence_number == 1 diff --git a/tests/integration/targets/dcnm_fabric/tests/dcnm_fabric_replaced_basic_vxlan_site_id.yaml b/tests/integration/targets/dcnm_fabric/tests/dcnm_fabric_replaced_basic_vxlan_site_id.yaml new file mode 100644 index 000000000..980e42660 --- /dev/null +++ b/tests/integration/targets/dcnm_fabric/tests/dcnm_fabric_replaced_basic_vxlan_site_id.yaml @@ -0,0 +1,455 @@ +################################################################################ +# RUNTIME +################################################################################ +# Recent run times (MM:SS.ms): +# 00:78.47 +################################################################################ +# DESCRIPTION - BASIC FABRIC REPLACED STATE TEST WITH SITE_ID +# +# Test basic replace of new fabric configurations and verify results. +# - config-save and config-deploy not tested here. +# - See dcnm_fabric_replaced_save_deploy.yaml instead. +################################################################################ +# STEPS +################################################################################ +# SETUP +# 1. The following fabrics must be empty on the controller +# See vars: section in cisco/dcnm/playbooks/dcnm_tests.yaml +# - fabric_name_1 +# - fabric_type_1 # VXLAN_EVPN +# 3. Delete fabrics under test, if they exist +# - fabric_name_1 +# TEST +# 4. Create fabrics with non-default configs and verify result +# - fabric_name_1 +# 5. Replace configs for fabric_1 and fabric_2 and verify result +# 6. Replace config for fabric_3 and verify result +# CLEANUP +# 7. Delete fabrics under test +# - fabric_name_1 +################################################################################ +# REQUIREMENTS +################################################################################ +# Inventory: +# ./playbooks/roles/dcnm_fabric/dcnm_hosts.yaml +# Playbook: +# ./playbooks/roles/dcnm_fabric/dcnm_tests.yaml +# Roles: +# ./tests/integration/targets/dcnm_fabric/tests/*.yaml +# +# Example vars: +# +# vars: +# testcase: dcnm_fabric_replaced_basic_vxlan +# fabric_name_1: VXLAN_EVPN_Fabric +# fabric_type_1: VXLAN_EVPN +################################################################################ +# REPLACED - SETUP - Delete fabrics +################################################################################ +- name: REPLACED - SETUP - Delete fabric_1 + cisco.dcnm.dcnm_fabric: + state: deleted + config: + - FABRIC_NAME: "{{ fabric_name_1 }}" + register: result +- debug: + var: result +################################################################################ +# REPLACED - TEST - Create fabric_name_1 with non-default config +################################################################################ +# Expected result +# - All untested nvPairs removed for brevity. +# - Fabric global keys in DATA removed for brevity. +# ok: [172.22.150.244] => { +# "result": { +# "changed": true, +# "diff": [ +# { +# "ADVERTISE_PIP_BGP": true, +# "ANYCAST_GW_MAC": "00aa.bbcc.ddee", +# "BGP_AS": 65535, +# "FABRIC_NAME": "VXLAN_EVPN_Fabric", +# "REPLICATION_MODE": "Ingress", +# "SITE_ID": 65000, +# "UNDERLAY_IS_V6": false, +# "sequence_number": 1 +# } +# ], +# "failed": false, +# "metadata": [ +# { +# "action": "fabric_create", +# "check_mode": false, +# "sequence_number": 1, +# "state": "merged" +# } +# ], +# "response": [ +# { +# "DATA": { +# "nvPairs": { +# "ADVERTISE_PIP_BGP": "true", +# "ANYCAST_GW_MAC": "00aa.bbcc.ddee", +# "BGP_AS": "65535", +# "FABRIC_NAME": "VXLAN_EVPN_Fabric", +# "REPLICATION_MODE": "Ingress", +# "SITE_ID": "65000", +# "UNDERLAY_IS_V6": "false", +# } +# }, +# "MESSAGE": "OK", +# "METHOD": "POST", +# "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/lan-fabric/rest/control/fabrics/VXLAN_EVPN_Fabric/Easy_Fabric", +# "RETURN_CODE": 200, +# "sequence_number": 1 +# } +# ], +# "result": [ +# { +# "changed": true, +# "sequence_number": 1, +# "success": true +# } +# ] +# } +# } +################################################################################ +- name: REPLACED - TEST - Create fabric_name_1 with non-default config + SITE_ID + cisco.dcnm.dcnm_fabric: + state: merged + config: + - FABRIC_NAME: "{{ fabric_name_1 }}" + FABRIC_TYPE: "{{ fabric_type_1 }}" + ADVERTISE_PIP_BGP: true + ANYCAST_GW_MAC: 00:aa:bb:cc:dd:ee + BGP_AS: 65535 + REPLICATION_MODE: Ingress + SITE_ID: 65000 + UNDERLAY_IS_V6: false + DEPLOY: false + register: result +- debug: + var: result +- assert: + that: + - result.changed == true + - result.failed == false + - (result.diff | length) == 1 + - result.diff[0].BGP_AS == 65535 + - result.diff[0].FABRIC_NAME == fabric_name_1 + - result.diff[0].sequence_number == 1 + - result.diff[0].ADVERTISE_PIP_BGP == true + - result.diff[0].ANYCAST_GW_MAC == "00aa.bbcc.ddee" + - result.diff[0].REPLICATION_MODE == "Ingress" + - result.diff[0].SITE_ID == 65000 + - result.diff[0].UNDERLAY_IS_V6 == false + - (result.metadata | length) == 1 + - result.metadata[0].action == "fabric_create" + - result.metadata[0].check_mode == False + - result.metadata[0].sequence_number == 1 + - result.metadata[0].state == "merged" + - (result.response | length) == 1 + - result.response[0].sequence_number == 1 + - result.response[0].MESSAGE == "OK" + - result.response[0].METHOD == "POST" + - result.response[0].RETURN_CODE == 200 + - result.response[0].DATA.nvPairs.ADVERTISE_PIP_BGP == "true" + - result.response[0].DATA.nvPairs.ANYCAST_GW_MAC == "00aa.bbcc.ddee" + - result.response[0].DATA.nvPairs.BGP_AS == "65535" + - result.response[0].DATA.nvPairs.FABRIC_NAME == "VXLAN_EVPN_Fabric" + - result.response[0].DATA.nvPairs.REPLICATION_MODE == "Ingress" + - result.response[0].DATA.nvPairs.SITE_ID == "65000" + - result.response[0].DATA.nvPairs.UNDERLAY_IS_V6 == "false" +################################################################################ +# REPLACED - TEST - Replace config for fabric_1 with default config +################################################################################ +# Expected result +# - All untested nvPairs removed for brevity. +# - Fabric global keys in DATA removed for brevity. +# ok: [172.22.150.244] => { +# "result": { +# "changed": true, +# "diff": [ +# { +# "ADVERTISE_PIP_BGP": "false", +# "ANYCAST_GW_MAC": "2020.0000.00aa", +# "FABRIC_NAME": "VXLAN_EVPN_Fabric", +# "REPLICATION_MODE": "Multicast", +# "SITE_ID": 65535, +# "sequence_number": 1 +# }, +# { +# "sequence_number": 2 +# }, +# { +# "sequence_number": 3 +# } +# ], +# "failed": false, +# "metadata": [ +# { +# "action": "fabric_replace", +# "check_mode": false, +# "sequence_number": 1, +# "state": "replaced" +# }, +# { +# "action": "config_save", +# "check_mode": false, +# "sequence_number": 2, +# "state": "replaced" +# }, +# { +# "action": "config_deploy", +# "check_mode": false, +# "sequence_number": 3, +# "state": "replaced" +# } +# ], +# "response": [ +# { +# "DATA": { +# "nvPairs": { +# "ADVERTISE_PIP_BGP": "false", +# "ANYCAST_GW_MAC": "2020.0000.00aa", +# "FABRIC_NAME": "VXLAN_EVPN_Fabric", +# "REPLICATION_MODE": "Multicast", +# "SITE_ID": "65535" +# } +# }, +# "MESSAGE": "OK", +# "METHOD": "PUT", +# "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/lan-fabric/rest/control/fabrics/VXLAN_EVPN_Fabric/Easy_Fabric", +# "RETURN_CODE": 200, +# "sequence_number": 1 +# }, +# { +# "MESSAGE": "Fabric VXLAN_EVPN_Fabric DEPLOY is False or None. Skipping config-save.", +# "RETURN_CODE": 200, +# "sequence_number": 2 +# }, +# { +# "MESSAGE": "FabricConfigDeploy._can_fabric_be_deployed: Fabric VXLAN_EVPN_Fabric DEPLOY is False or None. Skipping config-deploy.", +# "RETURN_CODE": 200, +# "sequence_number": 3 +# } +# ], +# "result": [ +# { +# "changed": true, +# "sequence_number": 1, +# "success": true +# }, +# { +# "changed": true, +# "sequence_number": 2, +# "success": true +# }, +# { +# "changed": true, +# "sequence_number": 3, +# "success": true +# } +# ] +# } +# } +################################################################################ +- name: REPLACED - TEST - Replace config for fabric_1 with default config + cisco.dcnm.dcnm_fabric: &replace_fabric_1 + state: replaced + config: + - FABRIC_NAME: "{{ fabric_name_1 }}" + FABRIC_TYPE: "{{ fabric_type_1 }}" + BGP_AS: 65535 + DEPLOY: false + register: result +- debug: + var: result +- assert: + that: + - result.changed == true + - result.failed == false + - (result.diff | length) == 3 + - result.diff[0].ADVERTISE_PIP_BGP == "false" + - result.diff[0].ANYCAST_GW_MAC == "2020.0000.00aa" + - result.diff[0].FABRIC_NAME == fabric_name_1 + - result.diff[0].REPLICATION_MODE == "Multicast" + - result.diff[0].sequence_number == 1 + - result.diff[1].sequence_number == 2 + - result.diff[2].sequence_number == 3 + - (result.metadata | length) == 3 + - result.metadata[0].action == "fabric_replace" + - result.metadata[0].check_mode == False + - result.metadata[0].sequence_number == 1 + - result.metadata[0].state == "replaced" + - result.metadata[1].action == "config_save" + - result.metadata[1].check_mode == False + - result.metadata[1].sequence_number == 2 + - result.metadata[1].state == "replaced" + - result.metadata[2].action == "config_deploy" + - result.metadata[2].check_mode == False + - result.metadata[2].sequence_number == 3 + - result.metadata[2].state == "replaced" + - (result.response | length) == 3 + - result.response[0].sequence_number == 1 + - result.response[0].MESSAGE == "OK" + - result.response[0].METHOD == "PUT" + - result.response[0].RETURN_CODE == 200 + - result.response[0].DATA.nvPairs.ADVERTISE_PIP_BGP == "false" + - result.response[0].DATA.nvPairs.ANYCAST_GW_MAC == "2020.0000.00aa" + - result.response[0].DATA.nvPairs.FABRIC_NAME == "VXLAN_EVPN_Fabric" + - result.response[0].DATA.nvPairs.REPLICATION_MODE == "Multicast" + - result.response[0].DATA.nvPairs.SITE_ID == "65535" + - result.response[1].sequence_number == 2 + - result.response[1].RETURN_CODE == 200 + - result.response[1].MESSAGE is match '.*Skipping config-save.*' + - result.response[2].sequence_number == 3 + - result.response[2].RETURN_CODE == 200 + - result.response[2].MESSAGE is match '.*Skipping config-deploy.*' + - (result.result | length) == 3 + - result.result[0].changed == true + - result.result[0].success == true + - result.result[0].sequence_number == 1 + - result.result[1].changed == true + - result.result[1].success == true + - result.result[1].sequence_number == 2 + - result.result[2].changed == true + - result.result[2].success == true + - result.result[2].sequence_number == 3 + +################################################################################ +# REPLACED - TEST - Replace config for fabric_1 with default config omnipotence +################################################################################ +# Expected result +# - All untested nvPairs removed for brevity. +# - Fabric global keys in DATA removed for brevity. +# ok: [ndfc1] => { +# "result": { +# "changed": false, +# "diff": [ +# { +# "sequence_number": 1 +# } +# ], +# "failed": false, +# "metadata": [ +# { +# "action": "fabric_replace", +# "check_mode": false, +# "sequence_number": 1, +# "state": "replaced" +# } +# ], +# "response": [ +# { +# "MESSAGE": "No fabrics to update for replaced state.", +# "RETURN_CODE": 200, +# "sequence_number": 1 +# } +# ], +# "result": [ +# { +# "changed": false, +# "sequence_number": 1, +# "success": true +# } +# ] +# } +# } +################################################################################ +- name: REPLACED - TEST - Replace config for fabric_1 with default config - idempotence + cisco.dcnm.dcnm_fabric: *replace_fabric_1 + register: result +- debug: + var: result +- assert: + that: + - result.changed == false + - result.failed == false + - (result.diff | length) == 1 + - result.diff[0].sequence_number == 1 + - (result.metadata | length) == 1 + - result.metadata[0].action == "fabric_replace" + - result.metadata[0].check_mode == False + - result.metadata[0].sequence_number == 1 + - result.metadata[0].state == "replaced" + - (result.response | length) == 1 + - result.response[0].sequence_number == 1 + - result.response[0].MESSAGE == "No fabrics to update for replaced state." + - result.response[0].RETURN_CODE == 200 + - (result.result | length) == 1 + - result.result[0].changed == false + - result.result[0].success == true + - result.result[0].sequence_number == 1 +################################################################################ +# REPLACED - CLEANUP - Delete the fabrics +################################################################################ +# Expected result +# ok: [ndfc1] => { +# "result": { +# "changed": true, +# "diff": [ +# { +# "FABRIC_NAME": "VXLAN_EVPN_Fabric", +# "sequence_number": 1 +# } +# ], +# "failed": false, +# "metadata": [ +# { +# "action": "fabric_delete", +# "check_mode": false, +# "sequence_number": 1, +# "state": "deleted" +# } +# ], +# "response": [ +# { +# "DATA": "Fabric 'VXLAN_EVPN_Fabric' is deleted successfully!", +# "MESSAGE": "OK", +# "METHOD": "DELETE", +# "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/rest/control/fabrics/VXLAN_EVPN_Fabric", +# "RETURN_CODE": 200, +# "sequence_number": 1 +# } +# ], +# "result": [ +# { +# "changed": true, +# "sequence_number": 1, +# "success": true +# } +# ] +# } +# } +################################################################################ +- name: MERGED - CLEANUP - Delete the fabrics + cisco.dcnm.dcnm_fabric: + state: deleted + config: + - FABRIC_NAME: "{{ fabric_name_1 }}" + register: result +- debug: + var: result +- assert: + that: + - result.changed == true + - result.failed == false + - (result.diff | length) == 1 + - result.diff[0].FABRIC_NAME == fabric_name_1 + - result.diff[0].sequence_number == 1 + - (result.metadata | length) == 1 + - result.metadata[0].action == "fabric_delete" + - result.metadata[0].check_mode == False + - result.metadata[0].sequence_number == 1 + - result.metadata[0].state == "deleted" + - (result.response | length) == 1 + - result.response[0].DATA is match '.*deleted successfully.*' + - result.response[0].MESSAGE == "OK" + - result.response[0].METHOD == "DELETE" + - result.response[0].RETURN_CODE == 200 + - result.response[0].sequence_number == 1 + - (result.result | length) == 1 + - result.result[0].changed == true + - result.result[0].success == true + - result.result[0].sequence_number == 1 diff --git a/tests/integration/targets/dcnm_fabric/tests/dcnm_fabric_replaced_save_deploy.yaml b/tests/integration/targets/dcnm_fabric/tests/dcnm_fabric_replaced_save_deploy.yaml index 2de009239..e39c7caa1 100644 --- a/tests/integration/targets/dcnm_fabric/tests/dcnm_fabric_replaced_save_deploy.yaml +++ b/tests/integration/targets/dcnm_fabric/tests/dcnm_fabric_replaced_save_deploy.yaml @@ -44,13 +44,17 @@ ################################################################################ # REQUIREMENTS ################################################################################ -# Example vars for dcnm_fabric integration tests -# Add fabric and leaf vars to cisco/dcnm/playbooks/dcnm_tests.yaml -# Add nxos_username and nxos_password vars to cisco/dcnm/playbooks/dcnm_hosts.yaml +# Inventory: +# ./playbooks/roles/dcnm_fabric/dcnm_hosts.yaml +# Playbook: +# ./playbooks/roles/dcnm_fabric/dcnm_tests.yaml +# Roles: +# ./tests/integration/targets/dcnm_fabric/tests/*.yaml +# +# Example vars: # # vars: -# # This testcase field can run any test in the tests directory for the role -# testcase: deleted +# testcase: dcnm_fabric_replaced_save_deploy # fabric_name_1: VXLAN_EVPN_Fabric # fabric_type_1: VXLAN_EVPN # fabric_name_3: LAN_CLASSIC_Fabric @@ -171,6 +175,7 @@ BGP_AS: 65535 ANYCAST_GW_MAC: aaaabbbbcccc REPLICATION_MODE: Ingress + SITE_ID: 65000 UNDERLAY_IS_V6: false DEPLOY: true - FABRIC_NAME: "{{ fabric_name_3 }}" @@ -192,6 +197,7 @@ - result.diff[0].FABRIC_NAME == fabric_name_1 - result.diff[0].ANYCAST_GW_MAC == "aaaa.bbbb.cccc" - result.diff[0].REPLICATION_MODE == "Ingress" + - result.diff[0].SITE_ID == 65000 - result.diff[0].UNDERLAY_IS_V6 == false - result.diff[1].FABRIC_NAME == fabric_name_3 - result.diff[1].sequence_number == 2 @@ -199,11 +205,11 @@ - result.diff[1].IS_READ_ONLY == false - result.diff[1].SUBINTERFACE_RANGE == "2-100" - (result.metadata | length) == 2 - - result.metadata[0].action == "create" + - result.metadata[0].action == "fabric_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].action == "fabric_create" - result.metadata[1].check_mode == False - result.metadata[1].sequence_number == 2 - result.metadata[1].state == "merged" @@ -212,10 +218,20 @@ - result.response[0].MESSAGE == "OK" - result.response[0].METHOD == "POST" - result.response[0].RETURN_CODE == 200 + - result.response[0].DATA.nvPairs.BGP_AS == "65535" + - result.response[0].DATA.nvPairs.FABRIC_NAME == fabric_name_1 + - result.response[0].DATA.nvPairs.ANYCAST_GW_MAC == "aaaa.bbbb.cccc" + - result.response[0].DATA.nvPairs.REPLICATION_MODE == "Ingress" + - result.response[0].DATA.nvPairs.SITE_ID == "65000" + - result.response[0].DATA.nvPairs.UNDERLAY_IS_V6 == "false" - result.response[1].sequence_number == 2 - result.response[1].MESSAGE == "OK" - result.response[1].METHOD == "POST" - result.response[1].RETURN_CODE == 200 + - result.response[1].DATA.nvPairs.BOOTSTRAP_ENABLE == "false" + - result.response[1].DATA.nvPairs.FABRIC_NAME == fabric_name_3 + - result.response[1].DATA.nvPairs.IS_READ_ONLY == "false" + - result.response[1].DATA.nvPairs.SUBINTERFACE_RANGE == "2-100" - (result.result | length) == 2 - result.result[0].changed == true - result.result[0].success == true @@ -394,7 +410,7 @@ - result.diff[2].FABRIC_NAME == fabric_name_1 - result.diff[2].config_deploy == "OK" - (result.metadata | length) == 3 - - result.metadata[0].action == "replace" + - result.metadata[0].action == "fabric_replace" - result.metadata[0].check_mode == False - result.metadata[0].sequence_number == 1 - result.metadata[0].state == "replaced" @@ -412,8 +428,10 @@ - result.response[0].METHOD == "PUT" - result.response[0].RETURN_CODE == 200 - result.response[0].DATA.nvPairs.ANYCAST_GW_MAC == "2020.0000.00aa" + - result.response[0].DATA.nvPairs.BGP_AS == "65535" - result.response[0].DATA.nvPairs.FABRIC_NAME == fabric_name_1 - result.response[0].DATA.nvPairs.REPLICATION_MODE == "Multicast" + - result.response[0].DATA.nvPairs.SITE_ID == "65535" - result.response[0].DATA.nvPairs.UNDERLAY_IS_V6 == "false" - result.response[1].sequence_number == 2 - result.response[1].DATA.status == 'Config save is completed' @@ -581,7 +599,7 @@ - result.diff[2].FABRIC_NAME == fabric_name_3 - result.diff[2].config_deploy == "OK" - (result.metadata | length) == 3 - - result.metadata[0].action == "replace" + - result.metadata[0].action == "fabric_replace" - result.metadata[0].check_mode == False - result.metadata[0].sequence_number == 1 - result.metadata[0].state == "replaced" @@ -675,7 +693,7 @@ - (result.diff | length) == 1 - result.diff[0].sequence_number == 1 - (result.metadata | length) == 1 - - result.metadata[0].action == "replace" + - result.metadata[0].action == "fabric_replace" - result.metadata[0].check_mode == False - result.metadata[0].sequence_number == 1 - result.metadata[0].state == "replaced" @@ -740,7 +758,7 @@ - (result.diff | length) == 1 - result.diff[0].sequence_number == 1 - (result.metadata | length) == 1 - - result.metadata[0].action == "replace" + - result.metadata[0].action == "fabric_replace" - result.metadata[0].check_mode == False - result.metadata[0].sequence_number == 1 - result.metadata[0].state == "replaced" @@ -873,11 +891,11 @@ - result.diff[1].FABRIC_NAME == fabric_name_3 - result.diff[1].sequence_number == 2 - (result.metadata | length) == 2 - - result.metadata[0].action == "delete" + - result.metadata[0].action == "fabric_delete" - result.metadata[0].check_mode == False - result.metadata[0].sequence_number == 1 - result.metadata[0].state == "deleted" - - result.metadata[1].action == "delete" + - result.metadata[1].action == "fabric_delete" - result.metadata[1].check_mode == False - result.metadata[1].sequence_number == 2 - result.metadata[1].state == "deleted" diff --git a/tests/integration/targets/dcnm_fabric/tests/dcnm_fabric_replaced_save_deploy_ipfm.yaml b/tests/integration/targets/dcnm_fabric/tests/dcnm_fabric_replaced_save_deploy_ipfm.yaml index 2ae7c8415..023151cf7 100644 --- a/tests/integration/targets/dcnm_fabric/tests/dcnm_fabric_replaced_save_deploy_ipfm.yaml +++ b/tests/integration/targets/dcnm_fabric/tests/dcnm_fabric_replaced_save_deploy_ipfm.yaml @@ -41,13 +41,17 @@ ################################################################################ # REQUIREMENTS ################################################################################ -# Example vars for dcnm_fabric integration tests -# Add fabric and leaf vars to cisco/dcnm/playbooks/dcnm_tests.yaml -# Add nxos_username and nxos_password vars to cisco/dcnm/playbooks/dcnm_hosts.yaml +# Inventory: +# ./playbooks/roles/dcnm_fabric/dcnm_hosts.yaml +# Playbook: +# ./playbooks/roles/dcnm_fabric/dcnm_tests.yaml +# Roles: +# ./tests/integration/targets/dcnm_fabric/tests/*.yaml +# +# Example vars: # # vars: -# # This testcase field can run any test in the tests directory for the role -# testcase: deleted +# testcase: dcnm_fabric_replaced_save_deploy_ipfm # fabric_name_4: IPFM_Fabric # fabric_type_4: IPFM # leaf_1: 172.22.150.103 @@ -138,7 +142,7 @@ - result.diff[0].sequence_number == 1 - result.diff[0].FABRIC_MTU == 1500 - (result.metadata | length) == 1 - - result.metadata[0].action == "create" + - result.metadata[0].action == "fabric_create" - result.metadata[0].check_mode == False - result.metadata[0].sequence_number == 1 - result.metadata[0].state == "merged" @@ -273,7 +277,7 @@ - result.diff[1].sequence_number == 2 - result.diff[2].sequence_number == 3 - (result.metadata | length) == 3 - - result.metadata[0].action == "replace" + - result.metadata[0].action == "fabric_replace" - result.metadata[0].check_mode == False - result.metadata[0].sequence_number == 1 - result.metadata[0].state == "replaced" @@ -365,7 +369,7 @@ - (result.diff | length) == 1 - result.diff[0].sequence_number == 1 - (result.metadata | length) == 1 - - result.metadata[0].action == "replace" + - result.metadata[0].action == "fabric_replace" - result.metadata[0].check_mode == False - result.metadata[0].sequence_number == 1 - result.metadata[0].state == "replaced" @@ -455,7 +459,7 @@ - result.diff[0].FABRIC_NAME == fabric_name_4 - result.diff[0].sequence_number == 1 - (result.metadata | length) == 1 - - result.metadata[0].action == "delete" + - result.metadata[0].action == "fabric_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_upgrade/tests/00_setup_create_fabric.yaml b/tests/integration/targets/dcnm_image_upgrade/tests/00_setup_create_fabric.yaml new file mode 100644 index 000000000..5bb1ffe05 --- /dev/null +++ b/tests/integration/targets/dcnm_image_upgrade/tests/00_setup_create_fabric.yaml @@ -0,0 +1,111 @@ +################################################################################ +# TESTCASE: +# +# 00_setup_create_fabric +# +# Description: +# +# Create a VXLAN EVPN Fabric. +# +################################################################################ +# +################################################################################ +# RUNTIME +################################################################################ +# +# Recent run times (MM:SS.ms): +# 28:57.34 +# +################################################################################ +# STEPS +################################################################################ +# +# SETUP +# 1. Create LAN_Classic fabric with basic config. +################################################################################ +# SETUP - Create VXLAN_EVPN_Fabric with basic config +################################################################################ +# Expected result +# - All untested nvPairs removed for brevity. +# - Fabric global keys in DATA removed for brevity. +# ok: [172.22.150.244] => { +# "result": { +# "changed": true, +# "diff": [ +# { +# "BOOTSTRAP_ENABLE": false, +# "FABRIC_NAME": "LAN_Classic_Fabric", +# "IS_READ_ONLY": false, +# "sequence_number": 1 +# } +# ], +# "failed": false, +# "metadata": [ +# { +# "action": "create", +# "check_mode": false, +# "sequence_number": 1, +# "state": "merged" +# } +# ], +# "response": [ +# { +# "DATA": { +# "nvPairs": { +# "BOOTSTRAP_ENABLE": "false", +# "FABRIC_NAME": "LAN_Classic_Fabric", +# "IS_READ_ONLY": "false", +# }, +# }, +# "MESSAGE": "OK", +# "METHOD": "POST", +# "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/lan-fabric/rest/control/fabrics/LAN_Classic_Fabric/LAN_Classic", +# "RETURN_CODE": 200, +# "sequence_number": 1 +# } +# ], +# "result": [ +# { +# "changed": true, +# "sequence_number": 1, +# "success": true +# } +# ] +# } +# } +################################################################################ +- name: SETUP - Create LAN_Classic fabric with basic config. + cisco.dcnm.dcnm_fabric: + state: merged + config: + - FABRIC_NAME: "{{ fabric_name_1 }}" + FABRIC_TYPE: LAN_CLASSIC + BOOTSTRAP_ENABLE: false + IS_READ_ONLY: false + DEPLOY: true + register: result +- debug: + var: result +- assert: + that: + - result.changed == true + - result.failed == false + - (result.diff | length) == 1 + - result.diff[0].FABRIC_NAME == fabric_name_1 + - result.diff[0].BOOTSTRAP_ENABLE == False + - result.diff[0].IS_READ_ONLY == False + - result.diff[0].sequence_number == 1 + - (result.metadata | length) == 1 + - result.metadata[0].action == "create" + - result.metadata[0].check_mode == False + - result.metadata[0].sequence_number == 1 + - result.metadata[0].state == "merged" + - (result.response | length) == 1 + - result.response[0].sequence_number == 1 + - result.response[0].MESSAGE == "OK" + - result.response[0].METHOD == "POST" + - result.response[0].RETURN_CODE == 200 + - (result.result | length) == 1 + - result.result[0].changed == true + - result.result[0].sequence_number == 1 + - result.result[0].success == true diff --git a/tests/integration/targets/dcnm_image_upgrade/tests/01_setup_add_switches_to_fabric.yaml b/tests/integration/targets/dcnm_image_upgrade/tests/01_setup_add_switches_to_fabric.yaml new file mode 100644 index 000000000..6e75edc65 --- /dev/null +++ b/tests/integration/targets/dcnm_image_upgrade/tests/01_setup_add_switches_to_fabric.yaml @@ -0,0 +1,58 @@ +################################################################################ +# TESTCASE: +# +# 01_add_switches_to_fabric +# +# Description: +# +# Add 1x Spine and 2x Leafs to Fabric. +# +################################################################################ +# +################################################################################ +# RUNTIME +################################################################################ +# +# Recent run times (MM:SS.ms): +# 02:20.4434 +# +################################################################################ +# STEPS +################################################################################ +# +- name: SETUP - Add 1x Spine and 2x Leafs to Fabric. + cisco.dcnm.dcnm_inventory: + fabric: "{{ fabric_name_1 }}" + state: merged + config: + - seed_ip: "{{ ansible_switch_1 }}" + auth_proto: MD5 + user_name: "{{ switch_username }}" + password: "{{ switch_password }}" + max_hops: 0 + role: leaf + preserve_config: True + - seed_ip: "{{ ansible_switch_2 }}" + auth_proto: MD5 + user_name: "{{ switch_username }}" + password: "{{ switch_password }}" + max_hops: 0 + role: leaf + preserve_config: True + - seed_ip: "{{ ansible_switch_3 }}" + auth_proto: MD5 + user_name: "{{ switch_username }}" + password: "{{ switch_password }}" + max_hops: 0 + role: spine + preserve_config: True + register: result + +- assert: + that: + - result.changed == true + +- assert: + that: + - item["RETURN_CODE"] == 200 + loop: '{{ result.response }}' diff --git a/tests/integration/targets/dcnm_image_upgrade/tests/02_setup_replace_image_policies.yaml b/tests/integration/targets/dcnm_image_upgrade/tests/02_setup_replace_image_policies.yaml new file mode 100644 index 000000000..f16e1c1cf --- /dev/null +++ b/tests/integration/targets/dcnm_image_upgrade/tests/02_setup_replace_image_policies.yaml @@ -0,0 +1,90 @@ +################################################################################ +# TESTCASE: +# +# 02_setup_create_image_policies +# +# Description: +# +# Replace image policies. +# +# This will replace all image policies on the controller. +# +################################################################################ +# +################################################################################ +# RUNTIME +################################################################################ +# +# Recent run times (MM:SS.ms): +# 00:07.565 +# 00:07.552 +# +################################################################################ +# STEPS +################################################################################ +# +- name: SETUP - Replace image policies. + cisco.dcnm.dcnm_image_policy: + state: replaced + 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 == "replace" + - result.metadata[0].check_mode == False + - result.metadata[0].sequence_number == 1 + - result.metadata[0].state == "replaced" + - result.metadata[1].action == "replace" + - result.metadata[1].check_mode == False + - result.metadata[1].sequence_number == 2 + - result.metadata[1].state == "replaced" + - (result.response | length) == 2 + - result.response[0].MESSAGE == "OK" + - result.response[0].METHOD == "POST" + - result.response[0].RETURN_CODE == 200 + - result.response[0].sequence_number == 1 + - result.response[1].MESSAGE == "OK" + - result.response[1].METHOD == "POST" + - result.response[1].RETURN_CODE == 200 + - result.response[1].sequence_number == 2 + - (result.result | length) == 2 + - result.result[0].changed == true + - result.result[0].sequence_number == 1 + - result.result[0].success == true + - result.result[1].changed == true + - result.result[1].sequence_number == 2 + - result.result[1].success == true diff --git a/tests/integration/targets/dcnm_image_upgrade/tests/deleted.yaml b/tests/integration/targets/dcnm_image_upgrade/tests/deleted.yaml index ef6973d17..6f9f95935 100644 --- a/tests/integration/targets/dcnm_image_upgrade/tests/deleted.yaml +++ b/tests/integration/targets/dcnm_image_upgrade/tests/deleted.yaml @@ -3,347 +3,67 @@ ################################################################################ # Recent run times (MM:SS.ms): -# 32:06.27 -# 29:10.63 -# 30:39.32 -# 32:36.36 -# 28:58.81 - +# 13:32.03 +# ################################################################################ # STEPS ################################################################################ - -# SETUP -# 1. Create a fabric -# 2. Merge switches into fabric -# 3. Upgrade switches using global config -# 4. Wait for all switches to complete ISSU -# TEST -# 5. Detach policies from two switches and verify -# 6. Detach policy from remaining switch and verify +# +# SETUP (these should be run prior to running this playbook) +# 1. Run 00_setup_create_fabric.yaml +# 2. Run 01_setup_add_switches_to_fabric +# 3. Run 02_setup_replace_image_policies +# PRE_TEST (this playbook) +# 4. DELETED - PRE_TEST - Upgrade all switches using global config. +# 5. DELETED - PRE_TEST - Wait for controller response for all three switches. +# 6. DELETED - TEST - Detach policies from two switches and verify. +# 7. DELETED - TEST - Detach policies from remaining switch and verify. # CLEANUP -# 7. Delete devices from fabric - +# 8. Run 03_cleanup_remove_devices_from_fabric.yaml +# 9. Run 04_cleanup_delete_image_policies.yaml +# 10. Run 05_cleanup_delete_fabric.yaml +# ################################################################################ # REQUIREMENTS ################################################################################ - -# 1. image policies are already configured on the controller: -# - KR5M (Kerry release maintenance 5) -# - NR3F (Niles release maintenance 3) -# The above include both NX-OS and EPLD images. -# -# TODO: Once dcnm_image_policy module is accepted, use that to -# configure the above policies. # # Example vars for dcnm_image_upgrade 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_upgrade role -# test_fabric: "{{ fabric_name }}" -# ansible_switch_1: "{{ leaf1 }}" -# ansible_switch_2: "{{ leaf2 }}" -# ansible_switch_3: "{{ spine1 }}" -# image_policy_1: "KR5M" -# image_policy_2: "NR3F" - +# testcase: deleted +# fabric_name: LAN_Classic_Fabric +# switch_username: admin +# switch_password: "Cisco!2345" +# leaf1: 172.22.150.103 +# leaf2: 172.22.150.104 +# spine1: 172.22.150.113 +# # for dcnm_image_policy and dcnm_image_upgrade roles +# image_policy_1: "KR5M" +# image_policy_2: "NR3F" +# # for dcnm_image_policy role +# 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 +# # for dcnm_image_upgrade role +# fabric_name_1: "{{ fabric_name }}" +# ansible_switch_1: "{{ leaf1 }}" +# ansible_switch_2: "{{ leaf2 }}" +# ansible_switch_3: "{{ spine1 }}" +# ################################################################################ -# SETUP +# DELETED - PRE_TEST - Upgrade all switches using global_config +# +# NOTES: +# 1. Depending on whether the switches are already at the desired version, the +# upgrade may not be performed. Hence, we do not check for the upgrade +# status in this test. ################################################################################ -- set_fact: - rest_fabric_create: "/appcenter/cisco/ndfc/api/v1/lan-fabric/rest/control/fabrics/{{ fabric_name }}" - -- name: DELETED - SETUP - Verify if fabric is deployed. - cisco.dcnm.dcnm_rest: - method: GET - path: "{{ rest_fabric_create }}" - register: result - -- debug: - var: result - -- assert: - that: - - result.response.DATA != None - -- name: DELETED - SETUP - Clean up any existing devices - cisco.dcnm.dcnm_inventory: - fabric: "{{ fabric_name }}" - state: deleted - -- name: DELETED - SETUP - Merge switches - cisco.dcnm.dcnm_inventory: - fabric: "{{ fabric_name }}" - state: merged - config: - - seed_ip: "{{ ansible_switch_1 }}" - auth_proto: MD5 - user_name: "{{ switch_username }}" - password: "{{ switch_password }}" - max_hops: 0 - role: leaf - preserve_config: False - - seed_ip: "{{ ansible_switch_2 }}" - auth_proto: MD5 - user_name: "{{ switch_username }}" - password: "{{ switch_password }}" - max_hops: 0 - role: leaf - preserve_config: False - - seed_ip: "{{ ansible_switch_3 }}" - auth_proto: MD5 - user_name: "{{ switch_username }}" - password: "{{ switch_password }}" - max_hops: 0 - role: spine - preserve_config: False - register: result - -- assert: - that: - - result.changed == true - -- assert: - that: - - item["RETURN_CODE"] == 200 - loop: '{{ result.response }}' - -################################################################################ -# DELETED - SETUP - Upgrade all switches using global_config -################################################################################ -# Expected result -# ok: [dcnm] => { -# "result": { -# "changed": true, -# "diff": [ -# { -# "action": "attach", -# "ip_address": "172.22.150.106", -# "logical_name": "cvd-2311-leaf", -# "policy_name": "KR5M", -# "serial_number": "FDO211218HB" -# }, -# { -# "action": "attach", -# "ip_address": "172.22.150.107", -# "logical_name": "cvd-2312-leaf", -# "policy_name": "KR5M", -# "serial_number": "FDO211218AX" -# }, -# { -# "action": "attach", -# "ip_address": "172.22.150.114", -# "logical_name": "cvd-2211-spine", -# "policy_name": "KR5M", -# "serial_number": "FOX2109PHDD" -# }, -# { -# "action": "stage", -# "ip_address": "172.22.150.106", -# "logical_name": "cvd-2311-leaf", -# "policy": "KR5M", -# "serial_number": "FDO211218HB" -# }, -# { -# "action": "stage", -# "ip_address": "172.22.150.114", -# "logical_name": "cvd-2211-spine", -# "policy": "KR5M", -# "serial_number": "FOX2109PHDD" -# }, -# { -# "action": "stage", -# "ip_address": "172.22.150.107", -# "logical_name": "cvd-2312-leaf", -# "policy": "KR5M", -# "serial_number": "FDO211218AX" -# }, -# { -# "action": "validate", -# "ip_address": "172.22.150.106", -# "logical_name": "cvd-2311-leaf", -# "policy": "KR5M", -# "serial_number": "FDO211218HB" -# }, -# { -# "action": "validate", -# "ip_address": "172.22.150.114", -# "logical_name": "cvd-2211-spine", -# "policy": "KR5M", -# "serial_number": "FOX2109PHDD" -# }, -# { -# "action": "validate", -# "ip_address": "172.22.150.107", -# "logical_name": "cvd-2312-leaf", -# "policy": "KR5M", -# "serial_number": "FDO211218AX" -# }, -# { -# "devices": [ -# { -# "policyName": "KR5M", -# "serialNumber": "FDO211218HB" -# } -# ], -# "epldOptions": { -# "golden": false, -# "moduleNumber": "ALL" -# }, -# "epldUpgrade": false, -# "issuUpgrade": true, -# "issuUpgradeOptions1": { -# "disruptive": true, -# "forceNonDisruptive": false, -# "nonDisruptive": false -# }, -# "issuUpgradeOptions2": { -# "biosForce": false -# }, -# "pacakgeInstall": false, -# "pacakgeUnInstall": false, -# "reboot": false, -# "rebootOptions": { -# "configReload": false, -# "writeErase": false -# } -# }, -# { -# "devices": [ -# { -# "policyName": "KR5M", -# "serialNumber": "FDO211218AX" -# } -# ], -# "epldOptions": { -# "golden": false, -# "moduleNumber": "ALL" -# }, -# "epldUpgrade": false, -# "issuUpgrade": true, -# "issuUpgradeOptions1": { -# "disruptive": true, -# "forceNonDisruptive": false, -# "nonDisruptive": false -# }, -# "issuUpgradeOptions2": { -# "biosForce": false -# }, -# "pacakgeInstall": false, -# "pacakgeUnInstall": false, -# "reboot": false, -# "rebootOptions": { -# "configReload": false, -# "writeErase": false -# } -# }, -# { -# "devices": [ -# { -# "policyName": "KR5M", -# "serialNumber": "FOX2109PHDD" -# } -# ], -# "epldOptions": { -# "golden": false, -# "moduleNumber": "ALL" -# }, -# "epldUpgrade": false, -# "issuUpgrade": true, -# "issuUpgradeOptions1": { -# "disruptive": true, -# "forceNonDisruptive": false, -# "nonDisruptive": false -# }, -# "issuUpgradeOptions2": { -# "biosForce": false -# }, -# "pacakgeInstall": false, -# "pacakgeUnInstall": false, -# "reboot": false, -# "rebootOptions": { -# "configReload": false, -# "writeErase": false -# } -# } -# ], -# "failed": false, -# "response": [ -# { -# "DATA": "[cvd-2311-leaf:Success] [cvd-2312-leaf:Success] [cvd-2211-spine:Success] ", -# "MESSAGE": "OK", -# "METHOD": "POST", -# "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/policymgnt/attach-policy", -# "RETURN_CODE": 200 -# }, -# { -# "DATA": [ -# { -# "key": "FDO211218AX", -# "value": "No files to stage" -# }, -# { -# "key": "FDO211218HB", -# "value": "No files to stage" -# }, -# { -# "key": "FOX2109PHDD", -# "value": "No files to stage" -# } -# ], -# "MESSAGE": "OK", -# "METHOD": "POST", -# "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/stagingmanagement/stage-image", -# "RETURN_CODE": 200 -# }, -# { -# "DATA": "[StageResponse [key=success, value=]]", -# "MESSAGE": "OK", -# "METHOD": "POST", -# "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/stagingmanagement/validate-image", -# "RETURN_CODE": 200 -# }, -# { -# "DATA": 63, -# "MESSAGE": "OK", -# "METHOD": "POST", -# "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/imageupgrade/upgrade-image", -# "RETURN_CODE": 200 -# }, -# { -# "DATA": 64, -# "MESSAGE": "OK", -# "METHOD": "POST", -# "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/imageupgrade/upgrade-image", -# "RETURN_CODE": 200 -# }, -# { -# "DATA": 65, -# "MESSAGE": "OK", -# "METHOD": "POST", -# "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/imageupgrade/upgrade-image", -# "RETURN_CODE": 200 -# } -# ] -# } -# } -################################################################################ -- name: DELETED - SETUP - Upgrade all switches using global config +- name: DELETED - PRE_TEST - Upgrade all switches using global config. cisco.dcnm.dcnm_image_upgrade: &global_config state: merged config: @@ -376,38 +96,11 @@ - debug: var: result -- assert: - that: - - result.changed == true - - result.failed == false - - result.diff[0].action == "attach" - - result.diff[1].action == "attach" - - result.diff[2].action == "attach" - - result.diff[0].policy_name == image_policy_1 - - result.diff[1].policy_name == image_policy_1 - - result.diff[2].policy_name == image_policy_1 - - result.diff[3].action == "stage" - - result.diff[4].action == "stage" - - result.diff[5].action == "stage" - - result.diff[3].policy == image_policy_1 - - result.diff[4].policy == image_policy_1 - - result.diff[5].policy == image_policy_1 - - result.diff[6].action == "validate" - - result.diff[7].action == "validate" - - result.diff[8].action == "validate" - - result.diff[6].policy == image_policy_1 - - result.diff[7].policy == image_policy_1 - - result.diff[8].policy == image_policy_1 - - result.diff[9].devices[0].policyName == image_policy_1 - - result.diff[10].devices[0].policyName == image_policy_1 - - result.diff[11].devices[0].policyName == image_policy_1 - - result.response[0].RETURN_CODE == 200 - - result.response[1].RETURN_CODE == 200 - - result.response[3].RETURN_CODE == 200 - - result.response[4].RETURN_CODE == 200 - - result.response[5].RETURN_CODE == 200 +################################################################################ +# DELETED - PRE_TEST - Wait for controller response for all three switches. +################################################################################ -- name: DELETED - SETUP - Wait for controller response for all three switches +- name: DELETED - PRE_TEST - Wait for controller response for all three switches. cisco.dcnm.dcnm_image_upgrade: state: query config: @@ -417,50 +110,71 @@ - ip_address: "{{ ansible_switch_3 }}" register: result until: - - result.diff[0].ipAddress == ansible_switch_1 - - result.diff[1].ipAddress == ansible_switch_2 - - result.diff[2].ipAddress == ansible_switch_3 + - ansible_switch_1 in result.diff[0] + - ansible_switch_2 in result.diff[0] + - ansible_switch_3 in result.diff[0] retries: 60 delay: 5 ignore_errors: yes ################################################################################ -# DELETED - TEST - Detach policies from two switches and verify +# DELETED - TEST - Detach policies from two switches and verify. ################################################################################ -# Expected result -# ok: [dcnm] => { +# Expected output +# ok: [172.22.150.244] => { # "result": { # "changed": true, # "diff": [ # { -# "action": "detach", -# "ip_address": "172.22.150.106", -# "logical_name": "cvd-2311-leaf", -# "policy_name": "KR5M", -# "serial_number": "FDO211218HB" -# }, -# { -# "action": "detach", -# "ip_address": "172.22.150.107", -# "logical_name": "cvd-2312-leaf", -# "policy_name": "KR5M", -# "serial_number": "FDO211218AX" +# "172.22.150.103": { +# "action": "image_policy_detach", +# "device_name": "cvd-1312-leaf", +# "ipv4_address": "172.22.150.103", +# "platform": "N9K", +# "policy_name": "NR1F", +# "serial_number": "FDO211218GC" +# }, +# "172.22.150.104": { +# "action": "image_policy_detach", +# "device_name": "cvd-1313-leaf", +# "ipv4_address": "172.22.150.104", +# "platform": "N9K", +# "policy_name": "NR1F", +# "serial_number": "FDO211218HH" +# }, +# "sequence_number": 1 # } # ], # "failed": false, +# "metadata": [ +# { +# "action": "image_policy_detach", +# "check_mode": false, +# "sequence_number": 1, +# "state": "deleted" +# } +# ], # "response": [ # { # "DATA": "Successfully detach the policy from device.", # "MESSAGE": "OK", # "METHOD": "DELETE", -# "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/policymgnt/detach-policy?serialNumber=FDO211218HB,FDO211218AX", -# "RETURN_CODE": 200 +# "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/policymgnt/detach-policy?serialNumber=FDO211218GC,FDO211218HH", +# "RETURN_CODE": 200, +# "sequence_number": 1 +# } +# ], +# "result": [ +# { +# "changed": true, +# "sequence_number": 1, +# "success": true # } # ] # } # } ################################################################################ -- name: DELETED - TEST - Detach policies from two switches +- name: DELETED - TEST - Detach policies from two switches and verify. cisco.dcnm.dcnm_image_upgrade: state: deleted config: @@ -477,44 +191,75 @@ that: - result.changed == true - result.failed == false - - (result.diff | length) == 2 + - (result.diff | length) == 1 + - result.diff[0][ansible_switch_1]["action"] == "image_policy_detach" + - result.diff[0][ansible_switch_2]["action"] == "image_policy_detach" + - result.diff[0].sequence_number == 1 + - (result.metadata | length) == 1 + - result.metadata[0]["action"] == "image_policy_detach" + - result.metadata[0]["check_mode"] == false + - result.metadata[0]["state"] == "deleted" + - result.metadata[0]["sequence_number"] == 1 - (result.response | length) == 1 - - result.diff[0]["action"] == "detach" - - result.diff[1]["action"] == "detach" - - response[0].RETURN_CODE == 200 - - response[0].DATA == "Successfully detach the policy from device." - - response[0].METHOD == "DELETE" + - result.response[0].RETURN_CODE == 200 + - result.response[0].DATA == "Successfully detach the policy from device." + - result.response[0].METHOD == "DELETE" + - result.response[0].sequence_number == 1 + - (result.result | length) == 1 + - result.result[0].changed == true + - result.result[0].success == true + - result.result[0].sequence_number == 1 ################################################################################ -# DELETED - TEST - Detach policies from remaining switch and verify +# DELETED - TEST - Detach policies from remaining switch and verify. ################################################################################ -# Expected result -# ok: [dcnm] => { +# Expected output +# ok: [172.22.150.244] => { # "result": { # "changed": true, # "diff": [ # { -# "action": "detach", -# "ip_address": "172.22.150.114", -# "logical_name": "cvd-2211-spine", -# "policy_name": "KR5M", -# "serial_number": "FOX2109PHDD" +# "172.22.150.113": { +# "action": "image_policy_detach", +# "device_name": "cvd-1212-spine", +# "ipv4_address": "172.22.150.113", +# "platform": "N9K", +# "policy_name": "NR1F", +# "serial_number": "FOX2109PGD0" +# }, +# "sequence_number": 1 # } # ], # "failed": false, +# "metadata": [ +# { +# "action": "image_policy_detach", +# "check_mode": false, +# "sequence_number": 1, +# "state": "deleted" +# } +# ], # "response": [ # { # "DATA": "Successfully detach the policy from device.", # "MESSAGE": "OK", # "METHOD": "DELETE", -# "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/policymgnt/detach-policy?serialNumber=FOX2109PHDD", -# "RETURN_CODE": 200 +# "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/policymgnt/detach-policy?serialNumber=FOX2109PGD0", +# "RETURN_CODE": 200, +# "sequence_number": 1 +# } +# ], +# "result": [ +# { +# "changed": true, +# "sequence_number": 1, +# "success": true # } # ] # } # } ################################################################################ -- name: DELETED - TEST - Detach policy from remaining switch +- name: DELETED - TEST - Detach policies from remaining switch and verify. cisco.dcnm.dcnm_image_upgrade: state: deleted config: @@ -531,16 +276,27 @@ - result.changed == true - result.failed == false - (result.diff | length) == 1 + - result.diff[0][ansible_switch_3]["action"] == "image_policy_detach" + - result.diff[0].sequence_number == 1 + - (result.metadata | length) == 1 + - result.metadata[0]["action"] == "image_policy_detach" + - result.metadata[0]["check_mode"] == false + - result.metadata[0]["state"] == "deleted" + - result.metadata[0]["sequence_number"] == 1 - (result.response | length) == 1 - - result.diff[0]["action"] == "detach" - - result.diff[0]["policy_name"] == image_policy_1 - - response[0].RETURN_CODE == 200 + - result.response[0].RETURN_CODE == 200 + - result.response[0].DATA == "Successfully detach the policy from device." + - result.response[0].METHOD == "DELETE" + - result.response[0].sequence_number == 1 + - (result.result | length) == 1 + - result.result[0].changed == true + - result.result[0].success == true + - result.result[0].sequence_number == 1 ################################################################################ -# CLEAN-UP +# CLEANUP ################################################################################ - -- name: DELETED - CLEANUP - Remove devices from fabric - cisco.dcnm.dcnm_inventory: - fabric: "{{ fabric_name }}" - state: deleted +# Run 03_cleanup_remove_devices_from_fabric.yaml +# Run 04_cleanup_delete_image_policies.yaml +# Run 05_cleanup_delete_fabric.yaml +################################################################################ \ No newline at end of file diff --git a/tests/integration/targets/dcnm_image_upgrade/tests/deleted_1x_switch.yaml b/tests/integration/targets/dcnm_image_upgrade/tests/deleted_1x_switch.yaml new file mode 100644 index 000000000..b8ec0ecd8 --- /dev/null +++ b/tests/integration/targets/dcnm_image_upgrade/tests/deleted_1x_switch.yaml @@ -0,0 +1,204 @@ +################################################################################ +# RUNTIME +################################################################################ + +# Recent run times (MM:SS.ms): +# 13:32.03 +# +################################################################################ +# STEPS +################################################################################ +# +# SETUP (these should be run prior to running this playbook) +# 1. Run 00_setup_create_fabric.yaml +# 2. Run 01_setup_add_switches_to_fabric +# 3. Run 02_setup_replace_image_policies +# PRE_TEST (this playbook) +# 4. DELETED - PRE_TEST - Upgrade all switches using global config. +# 5. DELETED - PRE_TEST - Wait for controller response for all three switches. +# 6. DELETED - TEST - Detach policies from two switches and verify. +# 7. DELETED - TEST - Detach policies from remaining switch and verify. +# CLEANUP +# 8. Run 03_cleanup_remove_devices_from_fabric.yaml +# 9. Run 04_cleanup_delete_image_policies.yaml +# 10. Run 05_cleanup_delete_fabric.yaml +# +################################################################################ +# REQUIREMENTS +################################################################################ +# +# Example vars for dcnm_image_upgrade integration tests +# Add to cisco/dcnm/playbooks/dcnm_tests.yaml) +# +# vars: +# testcase: deleted_1x_switch +# fabric_name: LAN_Classic_Fabric +# switch_username: admin +# switch_password: "Cisco!2345" +# leaf1: 172.22.150.103 +# leaf2: 172.22.150.104 +# spine1: 172.22.150.113 +# # for dcnm_image_policy and dcnm_image_upgrade roles +# image_policy_1: "KR5M" +# image_policy_2: "NR3F" +# # for dcnm_image_policy role +# 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 +# # for dcnm_image_upgrade role +# fabric_name_1: "{{ fabric_name }}" +# ansible_switch_1: "{{ leaf1 }}" +# ansible_switch_2: "{{ leaf2 }}" +# ansible_switch_3: "{{ spine1 }}" +# +################################################################################ +# DELETED - PRE_TEST - Upgrade all switches using global_config +# +# NOTES: +# 1. Depending on whether the switches are already at the desired version, the +# upgrade may not be performed. Hence, we do not check for the upgrade +# status in this test. +################################################################################ + +- name: DELETED - PRE_TEST - Upgrade all switches using global config. + cisco.dcnm.dcnm_image_upgrade: &global_config + state: merged + config: + policy: "{{ image_policy_1 }}" + reboot: false + stage: true + validate: true + upgrade: + nxos: true + epld: false + options: + nxos: + mode: disruptive + bios_force: false + epld: + module: ALL + golden: false + reboot: + config_reload: false + write_erase: false + package: + install: false + uninstall: false + switches: + - ip_address: "{{ ansible_switch_1 }}" + register: result + +- debug: + var: result + +################################################################################ +# DELETED - PRE_TEST - Wait for controller response for switch. +################################################################################ + +- name: DELETED - PRE_TEST - Wait for controller response for switch. + cisco.dcnm.dcnm_image_upgrade: + state: query + config: + switches: + - ip_address: "{{ ansible_switch_1 }}" + register: result + until: + - ansible_switch_1 in result.diff[0] + retries: 60 + delay: 5 + ignore_errors: yes + +################################################################################ +# DELETED - TEST - Detach policy from switch and verify. +################################################################################ +# Expected result +# ok: [172.22.150.244] => { +# "result": { +# "changed": true, +# "diff": [ +# { +# "172.22.150.103": { +# "action": "image_policy_detach", +# "device_name": "cvd-1312-leaf", +# "ipv4_address": "172.22.150.103", +# "platform": "N9K", +# "policy_name": "NR2F", +# "serial_number": "FDO211218GC" +# }, +# "sequence_number": 1 +# } +# ], +# "failed": false, +# "metadata": [ +# { +# "action": "image_policy_detach", +# "check_mode": false, +# "sequence_number": 1, +# "state": "deleted" +# } +# ], +# "response": [ +# { +# "DATA": "Successfully detach the policy from device.", +# "MESSAGE": "OK", +# "METHOD": "DELETE", +# "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/policymgnt/detach-policy?serialNumber=FDO211218GC", +# "RETURN_CODE": 200, +# "sequence_number": 1 +# } +# ], +# "result": [ +# { +# "changed": true, +# "sequence_number": 1, +# "success": true +# } +# ] +# } +# } +################################################################################ +- name: DELETED - TEST - Detach policy from switch and verify. + cisco.dcnm.dcnm_image_upgrade: + state: deleted + config: + policy: "{{ image_policy_1 }}" + switches: + - ip_address: "{{ ansible_switch_1 }}" + register: result + +- debug: + var: result + +- assert: + that: + - result.changed == true + - result.failed == false + - (result.diff | length) == 1 + - result.diff[0][ansible_switch_1]["action"] == "image_policy_detach" + - result.diff[0][ansible_switch_1]["policy_name"] == image_policy_1 + - result.diff[0].sequence_number == 1 + - (result.metadata | length) == 1 + - result.metadata[0]["action"] == "image_policy_detach" + - result.metadata[0]["check_mode"] == false + - result.metadata[0]["state"] == "deleted" + - result.metadata[0]["sequence_number"] == 1 + - (result.response | length) == 1 + - result.response[0].RETURN_CODE == 200 + - result.response[0].DATA == "Successfully detach the policy from device." + - result.response[0].METHOD == "DELETE" + - result.response[0].sequence_number == 1 + - (result.result | length) == 1 + - result.result[0].changed == true + - result.result[0].success == true + - result.result[0].sequence_number == 1 + +################################################################################ +# CLEANUP +################################################################################ +# Run 03_cleanup_remove_devices_from_fabric.yaml +# Run 04_cleanup_delete_image_policies.yaml +# Run 05_cleanup_delete_fabric.yaml +################################################################################ \ No newline at end of file diff --git a/tests/integration/targets/dcnm_image_upgrade/tests/merged_global_config.yaml b/tests/integration/targets/dcnm_image_upgrade/tests/merged_global_config.yaml index bd016b213..19fbbb71b 100644 --- a/tests/integration/targets/dcnm_image_upgrade/tests/merged_global_config.yaml +++ b/tests/integration/targets/dcnm_image_upgrade/tests/merged_global_config.yaml @@ -11,45 +11,37 @@ # # To minimize runtime, we use preserve_config: True during SETUP ################################################################################ - +# ################################################################################ # RUNTIME ################################################################################ - +# # Recent run times (MM:SS.ms): -# 29:49.15 -# 33:40.57 -# 28:58.18 -# 31:41.42 -# 31:28.12 -# 29:18.47 -# 28:57.34 - +# 13:07.88 +# 14:02.90 +# 13:12.97 +# ################################################################################ # STEPS ################################################################################ - -# SETUP -# 1. Create a fabric -# 2. Merge switches into fabric -# TEST -# 3. Upgrade switches using global config and verify -# 4. Wait for all switches to complete ISSU -# 5. Test idempotence +# +# SETUP (these should be run prior to running this playbook) +# 1. Run 00_setup_create_fabric.yaml +# 2. Run 01_setup_add_switches_to_fabric +# 3. Run 02_setup_replace_image_policies +# PRE_TEST (this playbook) +# 4. MERGED - PRE_TEST - Upgrade all switches using global config. +# 5. MERGED - PRE_TEST - Wait for controller response for all three switches. +# TEST (this playbook) +# 6. MERGED - TEST - global_config - test idempotence. # CLEANUP -# 6. Remove devices from fabric - +# 7. Run 03_cleanup_remove_devices_from_fabric.yaml +# 8. Run 04_cleanup_delete_image_policies.yaml +# 9. Run 05_cleanup_delete_fabric.yaml +# ################################################################################ # REQUIREMENTS ################################################################################ - -# 1. image policies are already configured on the controller: -# - KR5M (Kerry release maintenance 5) -# - NR3F (Niles release maintenance 3) -# The above include both NX-OS and EPLD images. -# -# TODO: Once dcnm_image_policy module is accepted, use that to -# configure the above policies. # # Example vars for dcnm_image_upgrade integration tests # Add to cisco/dcnm/playbooks/dcnm_tests.yaml) @@ -57,447 +49,228 @@ # vars: # # This testcase field can run any test in the tests directory for the role # testcase: merged_global_config -# 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_upgrade role -# test_fabric: "{{ fabric_name }}" -# ansible_switch_1: "{{ leaf1 }}" -# ansible_switch_2: "{{ leaf2 }}" -# ansible_switch_3: "{{ spine1 }}" -# image_policy_1: "KR5M" -# image_policy_2: "NR3F" - +# fabric_name: LAN_Classic_Fabric +# switch_username: admin +# switch_password: "Cisco!2345" +# leaf1: 192.168.1.2 +# leaf2: 192.168.1.3 +# spine1: 192.168.1.4 +# # for dcnm_image_policy and dcnm_image_upgrade roles +# image_policy_1: "KR5M" +# image_policy_2: "NR3F" +# # for dcnm_image_policy role +# 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 +# # for dcnm_image_upgrade role +# fabric_name_1: "{{ fabric_name }}" +# ansible_switch_1: "{{ leaf1 }}" +# ansible_switch_2: "{{ leaf2 }}" +# ansible_switch_3: "{{ spine1 }}" +# ################################################################################ -# SETUP +# MERGED - PRE_TEST - Detach image policies from all switches. +# NOTES: +# 1. Depending on whether the switches have policies attached, the +# detach operation may not be performed. Hence, we simply print the +# result and do not verify it. ################################################################################ -- set_fact: - rest_fabric_create: "/appcenter/cisco/ndfc/api/v1/lan-fabric/rest/control/fabrics/{{ fabric_name }}" - -- name: MERGED - SETUP - Verify if fabric is deployed. - cisco.dcnm.dcnm_rest: - method: GET - path: "{{ rest_fabric_create }}" +- name: MERGED - PRE_TEST - Detach image policies from all switches. + cisco.dcnm.dcnm_image_upgrade: + state: deleted + config: + policy: "{{ image_policy_1 }}" + switches: + - ip_address: "{{ ansible_switch_1 }}" + - ip_address: "{{ ansible_switch_2 }}" + - ip_address: "{{ ansible_switch_3 }}" register: result - - debug: var: result -- assert: - that: - - result.response.DATA != None - -- name: MERGED - SETUP - Clean up any existing devices - cisco.dcnm.dcnm_inventory: - fabric: "{{ fabric_name }}" - state: deleted +################################################################################ +# MERGED - TEST - Upgrade all switches using global config. +# NOTES: +# 1. Images may or may not be staged depending on the current state of the +# switches. Test only that the upgrade operation is successful. +# 2. Since we detached the image policies, image validation will be +# performed, so we do test for this. +################################################################################ -- name: MERGED - SETUP - Merge switches - cisco.dcnm.dcnm_inventory: - fabric: "{{ fabric_name }}" +- name: MERGED - TEST - Upgrade all switches using global config. + cisco.dcnm.dcnm_image_upgrade: state: merged config: - - seed_ip: "{{ ansible_switch_1 }}" - auth_proto: MD5 - user_name: "{{ switch_username }}" - password: "{{ switch_password }}" - max_hops: 0 - role: leaf - preserve_config: False - - seed_ip: "{{ ansible_switch_2 }}" - auth_proto: MD5 - user_name: "{{ switch_username }}" - password: "{{ switch_password }}" - max_hops: 0 - role: leaf - preserve_config: False - - seed_ip: "{{ ansible_switch_3 }}" - auth_proto: MD5 - user_name: "{{ switch_username }}" - password: "{{ switch_password }}" - max_hops: 0 - role: spine - preserve_config: False + policy: "{{ image_policy_1 }}" + reboot: false + stage: true + validate: true + upgrade: + nxos: true + epld: false + options: + nxos: + mode: disruptive + bios_force: false + epld: + module: ALL + golden: false + reboot: + config_reload: false + write_erase: false + package: + install: false + uninstall: false + switches: + - ip_address: "{{ ansible_switch_1 }}" + - ip_address: "{{ ansible_switch_2 }}" + - ip_address: "{{ ansible_switch_3 }}" register: result -- assert: - that: - - result.changed == true +- debug: + var: result -- assert: - that: - - item["RETURN_CODE"] == 200 - loop: '{{ result.response }}' +################################################################################ +# MERGED - TEST - Wait for controller response for all three switches. +################################################################################ + +- name: MERGED - TEST - Wait for controller response for all three switches. + cisco.dcnm.dcnm_image_upgrade: + state: query + config: + switches: + - ip_address: "{{ ansible_switch_1 }}" + - ip_address: "{{ ansible_switch_2 }}" + - ip_address: "{{ ansible_switch_3 }}" + register: result + until: + - ansible_switch_1 in result.diff[0] + - ansible_switch_2 in result.diff[0] + - ansible_switch_3 in result.diff[0] + retries: 60 + delay: 5 + ignore_errors: yes ################################################################################ -# MERGED - TEST - Upgrade all switches using global config +# MERGED - TEST - global_config - test idempotence. ################################################################################ # Expected result -# ok: [dcnm] => { +# ok: [172.22.150.244] => { # "result": { -# "changed": true, +# "changed": false, # "diff": [ # { -# "action": "attach", -# "ip_address": "172.22.150.106", -# "logical_name": "cvd-2311-leaf", -# "policy_name": "KR5M", -# "serial_number": "FDO211218HB" -# }, -# { -# "action": "attach", -# "ip_address": "172.22.150.107", -# "logical_name": "cvd-2312-leaf", -# "policy_name": "KR5M", -# "serial_number": "FDO211218AX" -# }, -# { -# "action": "attach", -# "ip_address": "172.22.150.114", -# "logical_name": "cvd-2211-spine", -# "policy_name": "KR5M", -# "serial_number": "FOX2109PHDD" -# }, -# { -# "action": "stage", -# "ip_address": "172.22.150.106", -# "logical_name": "cvd-2311-leaf", -# "policy": "KR5M", -# "serial_number": "FDO211218HB" -# }, -# { -# "action": "stage", -# "ip_address": "172.22.150.114", -# "logical_name": "cvd-2211-spine", -# "policy": "KR5M", -# "serial_number": "FOX2109PHDD" +# "sequence_number": 1 # }, # { -# "action": "stage", -# "ip_address": "172.22.150.107", -# "logical_name": "cvd-2312-leaf", -# "policy": "KR5M", -# "serial_number": "FDO211218AX" +# "sequence_number": 2 # }, # { -# "action": "validate", -# "ip_address": "172.22.150.106", -# "logical_name": "cvd-2311-leaf", -# "policy": "KR5M", -# "serial_number": "FDO211218HB" -# }, -# { -# "action": "validate", -# "ip_address": "172.22.150.114", -# "logical_name": "cvd-2211-spine", -# "policy": "KR5M", -# "serial_number": "FOX2109PHDD" -# }, -# { -# "action": "validate", -# "ip_address": "172.22.150.107", -# "logical_name": "cvd-2312-leaf", -# "policy": "KR5M", -# "serial_number": "FDO211218AX" -# }, +# "sequence_number": 3 +# } +# ], +# "failed": false, +# "metadata": [ # { -# "devices": [ -# { -# "policyName": "KR5M", -# "serialNumber": "FDO211218HB" -# } -# ], -# "epldOptions": { -# "golden": false, -# "moduleNumber": "ALL" -# }, -# "epldUpgrade": false, -# "issuUpgrade": true, -# "issuUpgradeOptions1": { -# "disruptive": true, -# "forceNonDisruptive": false, -# "nonDisruptive": false -# }, -# "issuUpgradeOptions2": { -# "biosForce": false -# }, -# "pacakgeInstall": false, -# "pacakgeUnInstall": false, -# "reboot": false, -# "rebootOptions": { -# "configReload": false, -# "writeErase": false -# } +# "action": "image_stage", +# "check_mode": false, +# "sequence_number": 1, +# "state": "merged" # }, # { -# "devices": [ -# { -# "policyName": "KR5M", -# "serialNumber": "FDO211218AX" -# } -# ], -# "epldOptions": { -# "golden": false, -# "moduleNumber": "ALL" -# }, -# "epldUpgrade": false, -# "issuUpgrade": true, -# "issuUpgradeOptions1": { -# "disruptive": true, -# "forceNonDisruptive": false, -# "nonDisruptive": false -# }, -# "issuUpgradeOptions2": { -# "biosForce": false -# }, -# "pacakgeInstall": false, -# "pacakgeUnInstall": false, -# "reboot": false, -# "rebootOptions": { -# "configReload": false, -# "writeErase": false -# } +# "action": "image_validate", +# "check_mode": false, +# "sequence_number": 2, +# "state": "merged" # }, # { -# "devices": [ -# { -# "policyName": "KR5M", -# "serialNumber": "FOX2109PHDD" -# } -# ], -# "epldOptions": { -# "golden": false, -# "moduleNumber": "ALL" -# }, -# "epldUpgrade": false, -# "issuUpgrade": true, -# "issuUpgradeOptions1": { -# "disruptive": true, -# "forceNonDisruptive": false, -# "nonDisruptive": false -# }, -# "issuUpgradeOptions2": { -# "biosForce": false -# }, -# "pacakgeInstall": false, -# "pacakgeUnInstall": false, -# "reboot": false, -# "rebootOptions": { -# "configReload": false, -# "writeErase": false -# } +# "action": "image_upgrade", +# "check_mode": false, +# "sequence_number": 3, +# "state": "merged" # } # ], -# "failed": false, # "response": [ # { -# "DATA": "[cvd-2311-leaf:Success] [cvd-2312-leaf:Success] [cvd-2211-spine:Success] ", -# "MESSAGE": "OK", -# "METHOD": "POST", -# "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/policymgnt/attach-policy", -# "RETURN_CODE": 200 -# }, -# { # "DATA": [ # { -# "key": "FDO211218AX", -# "value": "No files to stage" -# }, -# { -# "key": "FDO211218HB", -# "value": "No files to stage" -# }, -# { -# "key": "FOX2109PHDD", -# "value": "No files to stage" +# "key": "ALL", +# "value": "No images to stage." # } # ], -# "MESSAGE": "OK", -# "METHOD": "POST", -# "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/stagingmanagement/stage-image", -# "RETURN_CODE": 200 +# "sequence_number": 1 # }, # { -# "DATA": "[StageResponse [key=success, value=]]", -# "MESSAGE": "OK", -# "METHOD": "POST", -# "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/stagingmanagement/validate-image", -# "RETURN_CODE": 200 +# "response": "No images to validate.", +# "sequence_number": 2 # }, # { -# "DATA": 63, -# "MESSAGE": "OK", -# "METHOD": "POST", -# "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/imageupgrade/upgrade-image", -# "RETURN_CODE": 200 +# "sequence_number": 3 +# } +# ], +# "result": [ +# { +# "changed": false, +# "sequence_number": 1, +# "success": true # }, # { -# "DATA": 64, -# "MESSAGE": "OK", -# "METHOD": "POST", -# "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/imageupgrade/upgrade-image", -# "RETURN_CODE": 200 +# "changed": false, +# "sequence_number": 2, +# "success": true # }, # { -# "DATA": 65, -# "MESSAGE": "OK", -# "METHOD": "POST", -# "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/imageupgrade/upgrade-image", -# "RETURN_CODE": 200 +# "sequence_number": 3 # } # ] # } # } ################################################################################ -- name: MERGED - SETUP - Upgrade all switches using global config - cisco.dcnm.dcnm_image_upgrade: &global_config - state: merged - config: - policy: "{{ image_policy_1 }}" - reboot: false - stage: true - validate: true - upgrade: - nxos: true - epld: false - options: - nxos: - mode: disruptive - bios_force: false - epld: - module: ALL - golden: false - reboot: - config_reload: false - write_erase: false - package: - install: false - uninstall: false - switches: - - ip_address: "{{ ansible_switch_1 }}" - - ip_address: "{{ ansible_switch_2 }}" - - ip_address: "{{ ansible_switch_3 }}" - register: result - -- debug: - var: result -- assert: - that: - - result.changed == true - - result.failed == false - - result.diff[0].action == "attach" - - result.diff[1].action == "attach" - - result.diff[2].action == "attach" - - result.diff[0].policy_name == image_policy_1 - - result.diff[1].policy_name == image_policy_1 - - result.diff[2].policy_name == image_policy_1 - - result.diff[3].action == "stage" - - result.diff[4].action == "stage" - - result.diff[5].action == "stage" - - result.diff[3].policy == image_policy_1 - - result.diff[4].policy == image_policy_1 - - result.diff[5].policy == image_policy_1 - - result.diff[6].action == "validate" - - result.diff[7].action == "validate" - - result.diff[8].action == "validate" - - result.diff[6].policy == image_policy_1 - - result.diff[7].policy == image_policy_1 - - result.diff[8].policy == image_policy_1 - - result.diff[9].devices[0].policyName == image_policy_1 - - result.diff[10].devices[0].policyName == image_policy_1 - - result.diff[11].devices[0].policyName == image_policy_1 - - result.response[0].RETURN_CODE == 200 - - result.response[1].RETURN_CODE == 200 - - result.response[3].RETURN_CODE == 200 - - result.response[4].RETURN_CODE == 200 - - result.response[5].RETURN_CODE == 200 - -- name: MERGED - TEST - Wait for controller response for all three switches +- name: MERGED - TEST - global_config - test idempotence. cisco.dcnm.dcnm_image_upgrade: - state: query + state: merged config: - switches: + policy: "{{ image_policy_1}}" + reboot: false + stage: true + validate: true + upgrade: + nxos: true + epld: false + options: + nxos: + mode: disruptive + bios_force: false + epld: + module: ALL + golden: false + reboot: + config_reload: false + write_erase: false + package: + install: false + uninstall: false + switches: - ip_address: "{{ ansible_switch_1 }}" - ip_address: "{{ ansible_switch_2 }}" - ip_address: "{{ ansible_switch_3 }}" register: result - until: - - result.diff[0].ipAddress == ansible_switch_1 - - result.diff[1].ipAddress == ansible_switch_2 - - result.diff[2].ipAddress == ansible_switch_3 - retries: 60 - delay: 5 - ignore_errors: yes - -################################################################################ -# MERGED - TEST - global_config - IDEMPOTENCE -################################################################################ -# Expected result -# ok: [dcnm] => { -# "result": { -# "changed": false, -# "diff": [], -# "failed": false, -# "response": [] -# } -# } -################################################################################ - -- name: MERGED - TEST - global_config - IDEMPOTENCE - cisco.dcnm.dcnm_image_upgrade: - state: merged - config: - policy: "{{ image_policy_1}}" - reboot: false - stage: true - validate: true - upgrade: - nxos: true - epld: false - options: - nxos: - mode: disruptive - bios_force: false - epld: - module: ALL - golden: false - reboot: - config_reload: false - write_erase: false - package: - install: false - uninstall: false - switches: - - ip_address: "{{ ansible_switch_1 }}" - - ip_address: "{{ ansible_switch_2 }}" - - ip_address: "{{ ansible_switch_3 }}" - register: result - - debug: var: result - - assert: that: - - result.changed == false - - result.failed == false - - (result.diff | length) == 0 - - (result.response | length) == 0 + - result.changed == false + - result.failed == false ################################################################################ -# CLEAN-UP +# CLEANUP +################################################################################ +# Run 03_cleanup_remove_devices_from_fabric.yaml +# Run 04_cleanup_delete_image_policies.yaml +# Run 05_cleanup_delete_fabric.yaml ################################################################################ - -- name: MERGED - CLEANUP - Remove devices from fabric - cisco.dcnm.dcnm_inventory: - fabric: "{{ fabric_name }}" - state: deleted diff --git a/tests/integration/targets/dcnm_image_upgrade/tests/merged_override_global_config.yaml b/tests/integration/targets/dcnm_image_upgrade/tests/merged_override_global_config.yaml index 3113bea7c..a9a280b9b 100644 --- a/tests/integration/targets/dcnm_image_upgrade/tests/merged_override_global_config.yaml +++ b/tests/integration/targets/dcnm_image_upgrade/tests/merged_override_global_config.yaml @@ -10,351 +10,149 @@ # switch config stanzas. # All other upgrade options are specified in the global config stanza. ################################################################################ - +# ################################################################################ # RUNTIME ################################################################################ - +# # Recent run times (MM:SS.ms): -# 33:18.99 -# 27:36.11 -# 36:10.94 -# 34:14.59 -# 34:17.54 -# 30:40.84 - +# 19:29.43 +# ################################################################################ # STEPS ################################################################################ - -# SETUP -# 1. Create a fabric -# 2. Merge switches into fabric -# TEST -# 3. Upgrade switches using global config, overriding image policy in switch config -# 4. Verify the upgrade is successful. -# 5. Wait for all switches to complete ISSU -# 6. Test idempotence +# +# SETUP (these should be run prior to running this playbook) +# 1. Run 00_setup_create_fabric.yaml +# 2. Run 01_setup_add_switches_to_fabric +# 3. Run 02_setup_replace_image_policies +# PRE_TEST (this playbook) +# 4. MERGED - PRE_TEST - Upgrade all switches using switch config to override global config. +# 5. MERGED - PRE_TEST - Wait for controller response for all three switches. +# TEST (this playbook) +# 6. MERGED - TEST - switch_config - test idempotence. # CLEANUP -# 7. Remove devices from fabric - +# 7. Run 03_cleanup_remove_devices_from_fabric.yaml +# 8. Run 04_cleanup_delete_image_policies.yaml +# 9. Run 05_cleanup_delete_fabric.yaml +# ################################################################################ # REQUIREMENTS ################################################################################ - -# 1. Recommended to use a simple fabric type -# e.g. LAN Classic or Enhanced LAN Classic -# 2. image policies are already configured on the controller: -# - KR5M (Kerry release maintenance 5) -# - NR3F (Niles release maintenance 3) -# The above include both NX-OS and EPLD images. -# -# TODO: Once dcnm_image_policy module is accepted, use that to -# configure the above policies. # # Example vars for dcnm_image_upgrade 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: merged_override_global_config -# 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_upgrade role -# test_fabric: "{{ fabric_name }}" -# ansible_switch_1: "{{ leaf1 }}" -# ansible_switch_2: "{{ leaf2 }}" -# ansible_switch_3: "{{ spine1 }}" -# image_policy_1: "KR5M" -# image_policy_2: "NR3F" - +# testcase: merged_global_config +# fabric_name: LAN_Classic_Fabric +# switch_username: admin +# switch_password: "Cisco!2345" +# leaf1: 192.168.1.2 +# leaf2: 192.168.1.3 +# spine1: 192.168.1.4 +# # for dcnm_image_policy and dcnm_image_upgrade roles +# image_policy_1: "KR5M" +# image_policy_2: "NR3F" +# # for dcnm_image_policy role +# 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 +# # for dcnm_image_upgrade role +# fabric_name_1: "{{ fabric_name }}" +# ansible_switch_1: "{{ leaf1 }}" +# ansible_switch_2: "{{ leaf2 }}" +# ansible_switch_3: "{{ spine1 }}" +# ################################################################################ -# SETUP +# MERGED - PRE_TEST - Upgrade all switches using switch config to override global config. +# NOTES: +# 1. Depending on whether the switches are already at the desired version, the +# upgrade may not be performed. Hence, we do not check for the upgrade +# status in this test. ################################################################################ -- set_fact: - rest_fabric_create: "/appcenter/cisco/ndfc/api/v1/lan-fabric/rest/control/fabrics/{{ fabric_name }}" - -- name: MERGED - SETUP - Verify if fabric is deployed. - cisco.dcnm.dcnm_rest: - method: GET - path: "{{ rest_fabric_create }}" +- name: MERGED - PRE_TEST - Upgrade all switches to image_policy_2 using switch config to override global config. + cisco.dcnm.dcnm_image_upgrade: + state: merged + config: + policy: "{{ image_policy_1}}" + reboot: false + stage: true + validate: true + upgrade: + nxos: true + epld: false + options: + nxos: + mode: disruptive + bios_force: false + epld: + module: ALL + golden: false + reboot: + config_reload: false + write_erase: false + package: + install: false + uninstall: false + switches: + - ip_address: "{{ ansible_switch_1 }}" + policy: "{{ image_policy_2 }}" + - ip_address: "{{ ansible_switch_2 }}" + policy: "{{ image_policy_2 }}" + - ip_address: "{{ ansible_switch_3 }}" + policy: "{{ image_policy_2 }}" register: result - debug: var: result -- assert: - that: - - result.response.DATA != None - -- name: MERGED - SETUP - Clean up any existing devices - cisco.dcnm.dcnm_inventory: - fabric: "{{ fabric_name }}" - state: deleted +################################################################################ +# MERGED - PRE_TEST - Wait for controller response for all three switches. +################################################################################ -- name: MERGED - SETUP - Merge switches - cisco.dcnm.dcnm_inventory: - fabric: "{{ fabric_name }}" - state: merged +- name: MERGED - PRE_TEST - Wait for controller response for all three switches. + cisco.dcnm.dcnm_image_upgrade: + state: query config: - - seed_ip: "{{ ansible_switch_1 }}" - auth_proto: MD5 - user_name: "{{ switch_username }}" - password: "{{ switch_password }}" - max_hops: 0 - role: leaf - preserve_config: False - - seed_ip: "{{ ansible_switch_2 }}" - auth_proto: MD5 - user_name: "{{ switch_username }}" - password: "{{ switch_password }}" - max_hops: 0 - role: leaf - preserve_config: False - - seed_ip: "{{ ansible_switch_3 }}" - auth_proto: MD5 - user_name: "{{ switch_username }}" - password: "{{ switch_password }}" - max_hops: 0 - role: spine - preserve_config: False + switches: + - ip_address: "{{ ansible_switch_1 }}" + - ip_address: "{{ ansible_switch_2 }}" + - ip_address: "{{ ansible_switch_3 }}" register: result + until: + - ansible_switch_1 in result.diff[0] + - ansible_switch_2 in result.diff[0] + - ansible_switch_3 in result.diff[0] + retries: 60 + delay: 5 + ignore_errors: yes -- assert: - that: - - item["RETURN_CODE"] == 200 - loop: '{{ result.response }}' +- debug: + var: result ################################################################################ -# MERGED - TEST - Override global image policy in switch configs +# MERGED - TEST - switch_config - test idempotence. +# +# Anchor and Alias didn't work for this. I copied the entire config from above ################################################################################ # Expected result # ok: [dcnm] => { # "result": { -# "changed": true, -# "diff": [ -# { -# "action": "attach", -# "ip_address": "172.22.150.106", -# "logical_name": "cvd-2311-leaf", -# "policy_name": "NR3F", -# "serial_number": "FDO211218HB" -# }, -# { -# "action": "attach", -# "ip_address": "172.22.150.107", -# "logical_name": "cvd-2312-leaf", -# "policy_name": "NR3F", -# "serial_number": "FDO211218AX" -# }, -# { -# "action": "attach", -# "ip_address": "172.22.150.114", -# "logical_name": "cvd-2211-spine", -# "policy_name": "NR3F", -# "serial_number": "FOX2109PHDD" -# }, -# { -# "action": "stage", -# "ip_address": "172.22.150.106", -# "logical_name": "cvd-2311-leaf", -# "policy": "NR3F", -# "serial_number": "FDO211218HB" -# }, -# { -# "action": "stage", -# "ip_address": "172.22.150.114", -# "logical_name": "cvd-2211-spine", -# "policy": "NR3F", -# "serial_number": "FOX2109PHDD" -# }, -# { -# "action": "stage", -# "ip_address": "172.22.150.107", -# "logical_name": "cvd-2312-leaf", -# "policy": "NR3F", -# "serial_number": "FDO211218AX" -# }, -# { -# "action": "validate", -# "ip_address": "172.22.150.106", -# "logical_name": "cvd-2311-leaf", -# "policy": "NR3F", -# "serial_number": "FDO211218HB" -# }, -# { -# "action": "validate", -# "ip_address": "172.22.150.114", -# "logical_name": "cvd-2211-spine", -# "policy": "NR3F", -# "serial_number": "FOX2109PHDD" -# }, -# { -# "action": "validate", -# "ip_address": "172.22.150.107", -# "logical_name": "cvd-2312-leaf", -# "policy": "NR3F", -# "serial_number": "FDO211218AX" -# }, -# { -# "devices": [ -# { -# "policyName": "NR3F", -# "serialNumber": "FDO211218HB" -# } -# ], -# "epldOptions": { -# "golden": false, -# "moduleNumber": "ALL" -# }, -# "epldUpgrade": false, -# "issuUpgrade": true, -# "issuUpgradeOptions1": { -# "disruptive": true, -# "forceNonDisruptive": false, -# "nonDisruptive": false -# }, -# "issuUpgradeOptions2": { -# "biosForce": false -# }, -# "pacakgeInstall": false, -# "pacakgeUnInstall": false, -# "reboot": false, -# "rebootOptions": { -# "configReload": false, -# "writeErase": false -# } -# }, -# { -# "devices": [ -# { -# "policyName": "NR3F", -# "serialNumber": "FDO211218AX" -# } -# ], -# "epldOptions": { -# "golden": false, -# "moduleNumber": "ALL" -# }, -# "epldUpgrade": false, -# "issuUpgrade": true, -# "issuUpgradeOptions1": { -# "disruptive": true, -# "forceNonDisruptive": false, -# "nonDisruptive": false -# }, -# "issuUpgradeOptions2": { -# "biosForce": false -# }, -# "pacakgeInstall": false, -# "pacakgeUnInstall": false, -# "reboot": false, -# "rebootOptions": { -# "configReload": false, -# "writeErase": false -# } -# }, -# { -# "devices": [ -# { -# "policyName": "NR3F", -# "serialNumber": "FOX2109PHDD" -# } -# ], -# "epldOptions": { -# "golden": false, -# "moduleNumber": "ALL" -# }, -# "epldUpgrade": false, -# "issuUpgrade": true, -# "issuUpgradeOptions1": { -# "disruptive": true, -# "forceNonDisruptive": false, -# "nonDisruptive": false -# }, -# "issuUpgradeOptions2": { -# "biosForce": false -# }, -# "pacakgeInstall": false, -# "pacakgeUnInstall": false, -# "reboot": false, -# "rebootOptions": { -# "configReload": false, -# "writeErase": false -# } -# } -# ], +# "changed": false, +# "diff": [], # "failed": false, -# "response": [ -# { -# "DATA": "[cvd-2311-leaf:Success] [cvd-2312-leaf:Success] [cvd-2211-spine:Success] ", -# "MESSAGE": "OK", -# "METHOD": "POST", -# "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/policymgnt/attach-policy", -# "RETURN_CODE": 200 -# }, -# { -# "DATA": [ -# { -# "key": "FDO211218AX", -# "value": "No files to stage" -# }, -# { -# "key": "FDO211218HB", -# "value": "No files to stage" -# }, -# { -# "key": "FOX2109PHDD", -# "value": "No files to stage" -# } -# ], -# "MESSAGE": "OK", -# "METHOD": "POST", -# "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/stagingmanagement/stage-image", -# "RETURN_CODE": 200 -# }, -# { -# "DATA": "[StageResponse [key=success, value=]]", -# "MESSAGE": "OK", -# "METHOD": "POST", -# "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/stagingmanagement/validate-image", -# "RETURN_CODE": 200 -# }, -# { -# "DATA": 71, -# "MESSAGE": "OK", -# "METHOD": "POST", -# "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/imageupgrade/upgrade-image", -# "RETURN_CODE": 200 -# }, -# { -# "DATA": 72, -# "MESSAGE": "OK", -# "METHOD": "POST", -# "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/imageupgrade/upgrade-image", -# "RETURN_CODE": 200 -# }, -# { -# "DATA": 73, -# "MESSAGE": "OK", -# "METHOD": "POST", -# "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/imageupgrade/upgrade-image", -# "RETURN_CODE": 200 -# } -# ] +# "response": [] # } # } -- name: MERGED - TEST - Upgrade all switches using global config. Override policy in switch configs. +################################################################################ + +- name: MERGED - TEST - switch_config - test idempotence. cisco.dcnm.dcnm_image_upgrade: state: merged config: @@ -387,41 +185,60 @@ policy: "{{ image_policy_2 }}" register: result -- debug: - var: result - - assert: that: - - result.changed == true + - result.changed == false - result.failed == false - - result.diff[0].action == "attach" - - result.diff[1].action == "attach" - - result.diff[2].action == "attach" - - result.diff[0].policy_name == image_policy_2 - - result.diff[1].policy_name == image_policy_2 - - result.diff[2].policy_name == image_policy_2 - - result.diff[3].action == "stage" - - result.diff[4].action == "stage" - - result.diff[5].action == "stage" - - result.diff[3].policy == image_policy_2 - - result.diff[4].policy == image_policy_2 - - result.diff[5].policy == image_policy_2 - - result.diff[6].action == "validate" - - result.diff[7].action == "validate" - - result.diff[8].action == "validate" - - result.diff[6].policy == image_policy_2 - - result.diff[7].policy == image_policy_2 - - result.diff[8].policy == image_policy_2 - - result.diff[9].devices[0].policyName == image_policy_2 - - result.diff[10].devices[0].policyName == image_policy_2 - - result.diff[11].devices[0].policyName == image_policy_2 - - result.response[0].RETURN_CODE == 200 - - result.response[1].RETURN_CODE == 200 - - result.response[3].RETURN_CODE == 200 - - result.response[4].RETURN_CODE == 200 - - result.response[5].RETURN_CODE == 200 + - (result.diff | length) == 6 + - (result.metadata | length) == 6 + - (result.response | length) == 6 + - (result.result | length) == 6 + +################################################################################ +# MERGED - TEST - Upgrade all switches to image_policy_1 using switch config to override global config. +################################################################################ + +- name: MERGED - TEST - Upgrade all switches to image_policy_1 using switch config to override global config. + cisco.dcnm.dcnm_image_upgrade: + state: merged + config: + policy: "{{ image_policy_2}}" + reboot: false + stage: true + validate: true + upgrade: + nxos: true + epld: false + options: + nxos: + mode: disruptive + bios_force: false + epld: + module: ALL + golden: false + reboot: + config_reload: false + write_erase: false + package: + install: false + uninstall: false + switches: + - ip_address: "{{ ansible_switch_1 }}" + policy: "{{ image_policy_1 }}" + - ip_address: "{{ ansible_switch_2 }}" + policy: "{{ image_policy_1 }}" + - ip_address: "{{ ansible_switch_3 }}" + policy: "{{ image_policy_1 }}" + register: result -- name: MERGED - TEST - Wait for controller response for all three switches +- debug: + var: result + +################################################################################ +# MERGED - TEST - Wait for controller response for all three switches. +################################################################################ + +- name: MERGED - PRE_TEST - Wait for controller response for all three switches. cisco.dcnm.dcnm_image_upgrade: state: query config: @@ -431,17 +248,18 @@ - ip_address: "{{ ansible_switch_3 }}" register: result until: - - result.diff[0].ipAddress == ansible_switch_1 - - result.diff[1].ipAddress == ansible_switch_2 - - result.diff[2].ipAddress == ansible_switch_3 + - ansible_switch_1 in result.diff[0] + - ansible_switch_2 in result.diff[0] + - ansible_switch_3 in result.diff[0] retries: 60 delay: 5 ignore_errors: yes +- debug: + var: result + ################################################################################ -# MERGED - TEST - IDEMPOTENCE switch_config -# -# Anchor and Alias didn't work for this. I copied the entire config from above +# MERGED - TEST - switch_config - test idempotence. ################################################################################ # Expected result # ok: [dcnm] => { @@ -454,11 +272,11 @@ # } ################################################################################ -- name: MERGED - TEST - switch_config - Idempotence +- name: MERGED - TEST - switch_config - test idempotence. cisco.dcnm.dcnm_image_upgrade: state: merged config: - policy: "{{ image_policy_1}}" + policy: "{{ image_policy_2}}" reboot: false stage: true validate: true @@ -480,25 +298,26 @@ uninstall: false switches: - ip_address: "{{ ansible_switch_1 }}" - policy: "{{ image_policy_2 }}" + policy: "{{ image_policy_1 }}" - ip_address: "{{ ansible_switch_2 }}" - policy: "{{ image_policy_2 }}" + policy: "{{ image_policy_1 }}" - ip_address: "{{ ansible_switch_3 }}" - policy: "{{ image_policy_2 }}" + policy: "{{ image_policy_1 }}" register: result - assert: that: - result.changed == false - result.failed == false - - (result.diff | length) == 0 - - (result.response | length) == 0 + - (result.diff | length) == 6 + - (result.metadata | length) == 6 + - (result.response | length) == 6 + - (result.result | length) == 6 ################################################################################ -# CLEAN-UP +# CLEANUP ################################################################################ - -- name: MERGED - CLEANUP - Remove devices from fabric - cisco.dcnm.dcnm_inventory: - fabric: "{{ fabric_name }}" - state: deleted +# Run 03_cleanup_remove_devices_from_fabric.yaml +# Run 04_cleanup_delete_image_policies.yaml +# Run 05_cleanup_delete_fabric.yaml +################################################################################ \ No newline at end of file diff --git a/tests/integration/targets/dcnm_image_upgrade/tests/query.yaml b/tests/integration/targets/dcnm_image_upgrade/tests/query.yaml index 2e20ede77..095051531 100644 --- a/tests/integration/targets/dcnm_image_upgrade/tests/query.yaml +++ b/tests/integration/targets/dcnm_image_upgrade/tests/query.yaml @@ -1,134 +1,74 @@ ################################################################################ # RUNTIME ################################################################################ - +# # Recent run times (MM:SS.ms): -# 26:19.11 -# 26:32.97 -# 28:16.01 -# 38:33.19 - +# 12:43.37 +# ################################################################################ # STEPS ################################################################################ - -# SETUP -# 1. Verify fabric is deployed -# 2. Merge switches into fabric -# 3. Upgrade switches using global config -# TEST -# 4. Query and verify ISSU status image_policy_1 attached to all switches -# 5. Detach image policies from two of the three switches -# 6. Query and verify ISSU status image_policy_1 removed from two switches -# 7. Detach image policy from remaining switch -# 8. Query and verify ISSU status image_policy_1 removed from all switches +# +# SETUP (these should be run prior to running this playbook) +# 1. Run 00_setup_create_fabric.yaml +# 2. Run 01_setup_add_switches_to_fabric +# 3. Run 02_setup_replace_image_policies +# PRE_TEST (this playbook) +# 4. QUERY - PRE_TEST - Upgrade all switches using global_config. +# 5. QUERY - PRE_TEST - Wait for controller response for all three switches. +# TEST (this playbook) +# 5. QUERY - TEST - Verify image_policy_1 is attached to all switches. +# 6. QUERY - TEST - Detach policies from two switches and verify. +# 7. QUERY - TEST - Verify image_policy_1 was removed from two switches. +# 8. QUERY - TEST - Detach policies from remaining switch and verify. +# 9. QUERY - TEST - Verify image_policy_1 was removed from all switches. # CLEANUP -# 9. Delete devices from fabric - +# 10. Run 03_cleanup_remove_devices_from_fabric.yaml +# 11. Run 04_cleanup_delete_image_policies.yaml +# 12. Run 05_cleanup_delete_fabric.yaml +# ################################################################################ # REQUIREMENTS ################################################################################ - -# 1. image policies are already configured on the controller: -# - KR5M (Kerry release maintenance 5) -# - NR3F (Niles release maintenance 3) -# The above include both NX-OS and EPLD images. -# -# TODO: Once dcnm_image_policy module is accepted, use that to -# configure the above policies. # # Example vars for dcnm_image_upgrade 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: query -# 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_upgrade role -# test_fabric: "{{ fabric_name }}" -# ansible_switch_1: "{{ leaf1 }}" -# ansible_switch_2: "{{ leaf2 }}" -# ansible_switch_3: "{{ spine1 }}" -# image_policy_1: "KR5M" -# image_policy_2: "NR3F" - -################################################################################ -# SETUP -################################################################################ - -- set_fact: - rest_fabric_create: "/appcenter/cisco/ndfc/api/v1/lan-fabric/rest/control/fabrics/{{ fabric_name }}" - -- name: QUERY - SETUP - Verify if fabric is deployed. - cisco.dcnm.dcnm_rest: - method: GET - path: "{{ rest_fabric_create }}" - register: result - -- debug: - var: result - -- assert: - that: - - result.response.DATA != None - -- name: QUERY - SETUP - Clean up any existing devices - cisco.dcnm.dcnm_inventory: - fabric: "{{ fabric_name }}" - state: deleted - -- name: QUERY - SETUP - Merge switches - cisco.dcnm.dcnm_inventory: - fabric: "{{ fabric_name }}" - state: merged - config: - - seed_ip: "{{ ansible_switch_1 }}" - auth_proto: MD5 - user_name: "{{ switch_username }}" - password: "{{ switch_password }}" - max_hops: 0 - role: leaf - preserve_config: False - - seed_ip: "{{ ansible_switch_2 }}" - auth_proto: MD5 - user_name: "{{ switch_username }}" - password: "{{ switch_password }}" - max_hops: 0 - role: leaf - preserve_config: False - - seed_ip: "{{ ansible_switch_3 }}" - auth_proto: MD5 - user_name: "{{ switch_username }}" - password: "{{ switch_password }}" - max_hops: 0 - role: spine - preserve_config: False - register: result - -- assert: - that: - - result.changed == true - -- assert: - that: - - item["RETURN_CODE"] == 200 - loop: '{{ result.response }}' - +# testcase: merged_global_config +# fabric_name: LAN_Classic_Fabric +# switch_username: admin +# switch_password: "Cisco!2345" +# leaf1: 192.168.1.2 +# leaf2: 192.168.1.3 +# spine1: 192.168.1.4 +# # for dcnm_image_policy and dcnm_image_upgrade roles +# image_policy_1: "KR5M" +# image_policy_2: "NR3F" +# # for dcnm_image_policy role +# 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 +# # for dcnm_image_upgrade role +# fabric_name_1: "{{ fabric_name }}" +# ansible_switch_1: "{{ leaf1 }}" +# ansible_switch_2: "{{ leaf2 }}" +# ansible_switch_3: "{{ spine1 }}" +# ################################################################################ -# QUERY - SETUP - Upgrade all switches using global_config +# QUERY - PRE_TEST - Upgrade all switches using global_config. +# +# NOTES: +# 1. Depending on whether the switches are already at the desired version, the +# upgrade may not be performed. Hence, we do not check for the upgrade +# status in this test. ################################################################################ -- name: QUERY - SETUP - Upgrade all switches using global config +- name: QUERY - PRE_TEST - Upgrade all switches using global_config. cisco.dcnm.dcnm_image_upgrade: state: merged config: @@ -161,38 +101,11 @@ - debug: var: result -- assert: - that: - - result.changed == true - - result.failed == false - - result.diff[0].action == "attach" - - result.diff[1].action == "attach" - - result.diff[2].action == "attach" - - result.diff[0].policy_name == image_policy_1 - - result.diff[1].policy_name == image_policy_1 - - result.diff[2].policy_name == image_policy_1 - - result.diff[3].action == "stage" - - result.diff[4].action == "stage" - - result.diff[5].action == "stage" - - result.diff[3].policy == image_policy_1 - - result.diff[4].policy == image_policy_1 - - result.diff[5].policy == image_policy_1 - - result.diff[6].action == "validate" - - result.diff[7].action == "validate" - - result.diff[8].action == "validate" - - result.diff[6].policy == image_policy_1 - - result.diff[7].policy == image_policy_1 - - result.diff[8].policy == image_policy_1 - - result.diff[9].devices[0].policyName == image_policy_1 - - result.diff[10].devices[0].policyName == image_policy_1 - - result.diff[11].devices[0].policyName == image_policy_1 - - result.response[0].RETURN_CODE == 200 - - result.response[1].RETURN_CODE == 200 - - result.response[3].RETURN_CODE == 200 - - result.response[4].RETURN_CODE == 200 - - result.response[5].RETURN_CODE == 200 +################################################################################ +# QUERY - PRE_TEST - Wait for controller response for all three switches. +################################################################################ -- name: QUERY - SETUP - Wait for controller response for all three switches +- name: QUERY - PRE_TEST - Wait for controller response for all three switches cisco.dcnm.dcnm_image_upgrade: state: query config: @@ -202,18 +115,18 @@ - ip_address: "{{ ansible_switch_3 }}" register: result until: - - result.diff[0].ipAddress == ansible_switch_1 - - result.diff[1].ipAddress == ansible_switch_2 - - result.diff[2].ipAddress == ansible_switch_3 + - ansible_switch_1 in result.diff[0] + - ansible_switch_2 in result.diff[0] + - ansible_switch_3 in result.diff[0] retries: 60 delay: 5 ignore_errors: yes ################################################################################ -# QUERY - TEST - Verify image_policy_1 attached to all switches +# QUERY - TEST - Verify image_policy_1 is attached to all switches. ################################################################################ -- name: QUERY - TEST - Verify image_policy_1 attached to all switches +- name: QUERY - TEST - Verify image_policy_1 is attached to all switches. cisco.dcnm.dcnm_image_upgrade: state: query config: @@ -230,23 +143,77 @@ that: - result.changed == false - result.failed == false - - (result.diff | length) == 3 + - (result.diff | length) == 1 - (result.response | length) == 1 - - (result.diff[0].ipAddress) == ansible_switch_1 - - (result.diff[1].ipAddress) == ansible_switch_2 - - (result.diff[2].ipAddress) == ansible_switch_3 - - (result.diff[0].policy) == image_policy_1 - - (result.diff[1].policy) == image_policy_1 - - (result.diff[2].policy) == image_policy_1 - - (result.diff[0].statusPercent) == 100 - - (result.diff[1].statusPercent) == 100 - - (result.diff[2].statusPercent) == 100 + - (result.diff[0][ansible_switch_1].ipAddress) == ansible_switch_1 + - (result.diff[0][ansible_switch_2].ipAddress) == ansible_switch_2 + - (result.diff[0][ansible_switch_3].ipAddress) == ansible_switch_3 + - (result.diff[0][ansible_switch_1].policy) == image_policy_1 + - (result.diff[0][ansible_switch_2].policy) == image_policy_1 + - (result.diff[0][ansible_switch_3].policy) == image_policy_1 + - (result.diff[0][ansible_switch_1].statusPercent) == 100 + - (result.diff[0][ansible_switch_2].statusPercent) == 100 + - (result.diff[0][ansible_switch_3].statusPercent) == 100 ################################################################################ -# QUERY - TEST - Detach policies from two switches and verify +# QUERY - TEST - Detach policies from two switches and verify. +################################################################################ +# Expected result +# ok: [172.22.150.244] => { +# "result": { +# "changed": true, +# "diff": [ +# { +# "172.22.150.103": { +# "action": "image_policy_detach", +# "device_name": "cvd-1312-leaf", +# "ipv4_address": "172.22.150.103", +# "platform": "N9K", +# "policy_name": "NR1F", +# "serial_number": "FDO211218GC" +# }, +# "172.22.150.104": { +# "action": "image_policy_detach", +# "device_name": "cvd-1313-leaf", +# "ipv4_address": "172.22.150.104", +# "platform": "N9K", +# "policy_name": "NR1F", +# "serial_number": "FDO211218HH" +# }, +# "sequence_number": 1 +# } +# ], +# "failed": false, +# "metadata": [ +# { +# "action": "image_policy_detach", +# "check_mode": false, +# "sequence_number": 1, +# "state": "deleted" +# } +# ], +# "response": [ +# { +# "DATA": "Successfully detach the policy from device.", +# "MESSAGE": "OK", +# "METHOD": "DELETE", +# "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/policymgnt/detach-policy?serialNumber=FDO211218GC,FDO211218HH", +# "RETURN_CODE": 200, +# "sequence_number": 1 +# } +# ], +# "result": [ +# { +# "changed": true, +# "sequence_number": 1, +# "success": true +# } +# ] +# } +# } ################################################################################ -- name: QUERY - TEST - Detach policies from two switches and verify +- name: QUERY - TEST - Detach policies from two switches and verify. cisco.dcnm.dcnm_image_upgrade: state: deleted config: @@ -263,21 +230,81 @@ that: - result.changed == true - result.failed == false - - (result.diff | length) == 2 + + - (result.diff | length) == 1 + - result.diff[0][ansible_switch_1]["action"] == "image_policy_detach" + - result.diff[0][ansible_switch_2]["action"] == "image_policy_detach" + - result.diff[0][ansible_switch_1].policy_name == image_policy_1 + - result.diff[0][ansible_switch_2].policy_name == image_policy_1 + - result.diff[0].sequence_number == 1 + - (result.response | length) == 1 - - result.diff[0]["action"] == "detach" - - result.diff[1]["action"] == "detach" - result.response[0].RETURN_CODE == 200 - result.response[0].DATA == "Successfully detach the policy from device." + - result.response[0].MESSAGE == "OK" - result.response[0].METHOD == "DELETE" + - result.response[0].sequence_number == 1 - + - (result.result | length) == 1 + - result.result[0].changed == true + - result.result[0].success == true + - result.result[0].sequence_number == 1 ################################################################################ -# QUERY - TEST - Verify image_policy_1 removed from two switches +# QUERY - TEST - Verify image_policy_1 was removed from two switches. +################################################################################ +# Expected result (most untested fields removed) +# ok: [172.22.150.244] => { +# "result": { +# "changed": false, +# "diff": [ +# { +# "172.22.150.103": { +# "ipAddress": "172.22.150.103", +# "policy": "None", +# }, +# "172.22.150.104": { +# "ipAddress": "172.22.150.104", +# "policy": "None", +# }, +# "172.22.150.113": { +# "ipAddress": "172.22.150.113", +# "policy": "NR1F", +# }, +# "sequence_number": 1 +# } +# ], +# "failed": false, +# "metadata": [ +# { +# "action": "switch_issu_details", +# "check_mode": true, +# "sequence_number": 1, +# "state": "query" +# } +# ], +# "response": [ +# { +# "DATA": { "removed since not tested...": "..."}, +# "MESSAGE": "OK", +# "METHOD": "GET", +# "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/packagemgnt/issu", +# "RETURN_CODE": 200, +# "sequence_number": 1 +# } +# ], +# "result": [ +# { +# "found": true, +# "sequence_number": 1, +# "success": true +# } +# ] +# } +# } ################################################################################ -- name: QUERY - TEST - Verify image_policy_1 removed from two switches +- name: QUERY - TEST - Verify image_policy_1 was removed from two switches. cisco.dcnm.dcnm_image_upgrade: state: query config: @@ -294,23 +321,86 @@ that: - result.changed == false - result.failed == false - - (result.diff | length) == 3 + - (result.diff | length) == 1 + - result.diff[0][ansible_switch_1].ipAddress == ansible_switch_1 + - result.diff[0][ansible_switch_2].ipAddress == ansible_switch_2 + - result.diff[0][ansible_switch_3].ipAddress == ansible_switch_3 + - result.diff[0][ansible_switch_1].policy == "None" + - result.diff[0][ansible_switch_2].policy == "None" + - result.diff[0][ansible_switch_3].policy == image_policy_1 + - result.diff[0][ansible_switch_1].statusPercent == 0 + - result.diff[0][ansible_switch_2].statusPercent == 0 + - result.diff[0][ansible_switch_3].statusPercent == 100 + - result.diff[0].sequence_number == 1 + + - (result.metadata | length) == 1 + - result.metadata[0].action == "switch_issu_details" + - result.metadata[0].check_mode == true + - result.metadata[0].sequence_number == 1 + - result.metadata[0].state == "query" + - (result.response | length) == 1 - - (result.diff[0].ipAddress) == ansible_switch_1 - - (result.diff[1].ipAddress) == ansible_switch_2 - - (result.diff[2].ipAddress) == ansible_switch_3 - - (result.diff[0].policy) == "None" - - (result.diff[1].policy) == "None" - - (result.diff[2].policy) == image_policy_1 - - (result.diff[0].statusPercent) == 0 - - (result.diff[1].statusPercent) == 0 - - (result.diff[2].statusPercent) == 100 + - result.response[0].MESSAGE == "OK" + - result.response[0].METHOD == "GET" + - result.response[0].RETURN_CODE == 200 + - result.response[0].sequence_number == 1 + - (result.result | length) == 1 + - result.result[0].found == true + - result.result[0].sequence_number == 1 + - result.result[0].success == true + +################################################################################ +# QUERY - TEST - Detach policies from remaining switch and verify. ################################################################################ -# QUERY - TEST - Detach policies from remaining switch and verify +# Expected result (most untested fields removed) +# ok: [172.22.150.244] => { +# "result": { +# "changed": true, +# "diff": [ +# { +# "172.22.150.113": { +# "action": "image_policy_detach", +# "device_name": "cvd-1212-spine", +# "ipv4_address": "172.22.150.113", +# "platform": "N9K", +# "policy_name": "NR1F", +# "serial_number": "FOX2109PGD0" +# }, +# "sequence_number": 1 +# } +# ], +# "failed": false, +# "metadata": [ +# { +# "action": "image_policy_detach", +# "check_mode": false, +# "sequence_number": 1, +# "state": "deleted" +# } +# ], +# "response": [ +# { +# "DATA": "Successfully detach the policy from device.", +# "MESSAGE": "OK", +# "METHOD": "DELETE", +# "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/policymgnt/detach-policy?serialNumber=FOX2109PGD0", +# "RETURN_CODE": 200, +# "sequence_number": 1 +# } +# ], +# "result": [ +# { +# "changed": true, +# "sequence_number": 1, +# "success": true +# } +# ] +# } +# } ################################################################################ -- name: QUERY - TEST - Detach policy from remaining switch +- name: QUERY - TEST - Detach policies from remaining switch and verify. cisco.dcnm.dcnm_image_upgrade: state: deleted config: @@ -326,14 +416,91 @@ that: - result.changed == true - result.failed == false + - (result.diff | length) == 1 + - result.diff[0][ansible_switch_3]["action"] == "image_policy_detach" + - result.diff[0][ansible_switch_3]["policy_name"] == image_policy_1 + - result.diff[0].sequence_number == 1 + + - (result.metadata | length) == 1 + - result.metadata[0].action == "image_policy_detach" + - result.metadata[0].check_mode == false + - result.metadata[0].state == "deleted" + - result.metadata[0].sequence_number == 1 + - (result.response | length) == 1 + - result.response[0].MESSAGE == "OK" + - result.response[0].RETURN_CODE == 200 + - result.response[0].DATA == "Successfully detach the policy from device." + - result.response[0].METHOD == "DELETE" + - result.response[0].sequence_number == 1 + + - (result.result | length) == 1 + - result.result[0].changed == true + - result.result[0].success == true + - result.result[0].sequence_number == 1 ################################################################################ -# QUERY - TEST - Verify image_policy_1 removed from all switches +# QUERY - TEST - Verify image_policy_1 was removed from all switches. +################################################################################ +# Expected result (most untested fields removed) +# ok: [172.22.150.244] => { +# "result": { +# "changed": false, +# "diff": [ +# { +# "172.22.150.103": { +# "ipAddress": "172.22.150.103", +# "policy": "None", +# "statusPercent": 0, +# }, +# "172.22.150.104": { +# "ipAddress": "172.22.150.104", +# "ip_address": "172.22.150.104", +# "policy": "None", +# "statusPercent": 0, +# }, +# "172.22.150.113": { +# "ipAddress": "172.22.150.113", +# "policy": "None", +# "statusPercent": 0, +# }, +# "sequence_number": 1 +# } +# ], +# "failed": false, +# "metadata": [ +# { +# "action": "switch_issu_details", +# "check_mode": true, +# "sequence_number": 1, +# "state": "query" +# } +# ], +# "response": [ +# { +# "DATA": { +# "status": "SUCCESS" +# }, +# "MESSAGE": "OK", +# "METHOD": "GET", +# "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/packagemgnt/issu", +# "RETURN_CODE": 200, +# "sequence_number": 1 +# } +# ], +# "result": [ +# { +# "found": true, +# "sequence_number": 1, +# "success": true +# } +# ] +# } +# } ################################################################################ -- name: QUERY - TEST - Verify image_policy_1 removed from all switches +- name: QUERY - TEST - Verify image_policy_1 was removed from all switches. cisco.dcnm.dcnm_image_upgrade: state: query config: @@ -350,23 +517,38 @@ that: - result.changed == false - result.failed == false - - (result.diff | length) == 3 + + - (result.diff | length) == 1 + - result.diff[0][ansible_switch_1].ipAddress == ansible_switch_1 + - result.diff[0][ansible_switch_2].ipAddress == ansible_switch_2 + - result.diff[0][ansible_switch_3].ipAddress == ansible_switch_3 + - result.diff[0][ansible_switch_1].policy == "None" + - result.diff[0][ansible_switch_2].policy == "None" + - result.diff[0][ansible_switch_3].policy == "None" + - result.diff[0][ansible_switch_1].statusPercent == 0 + - result.diff[0][ansible_switch_2].statusPercent == 0 + - result.diff[0][ansible_switch_3].statusPercent == 0 + + - (result.metadata | length) == 1 + - result.metadata[0].action == "switch_issu_details" + - result.metadata[0].check_mode == true + - result.metadata[0].state == "query" + - result.metadata[0].sequence_number == 1 + - (result.response | length) == 1 - - (result.diff[0].ipAddress) == ansible_switch_1 - - (result.diff[1].ipAddress) == ansible_switch_2 - - (result.diff[2].ipAddress) == ansible_switch_3 - - (result.diff[0].policy) == "None" - - (result.diff[1].policy) == "None" - - (result.diff[2].policy) == "None" - - (result.diff[0].statusPercent) == 0 - - (result.diff[1].statusPercent) == 0 - - (result.diff[2].statusPercent) == 0 + - result.response[0].RETURN_CODE == 200 + - result.response[0].MESSAGE == "OK" + - result.response[0].DATA.status == "SUCCESS" + - (result.result | length) == 1 + - result.result[0].found == true + - result.result[0].success == true + - result.result[0].sequence_number == 1 + +################################################################################ +# CLEANUP ################################################################################ -# CLEAN-UP +# Run 03_cleanup_remove_devices_from_fabric.yaml +# Run 04_cleanup_delete_image_policies.yaml +# Run 05_cleanup_delete_fabric.yaml ################################################################################ - -- name: QUERY - CLEANUP - Remove devices from fabric - cisco.dcnm.dcnm_inventory: - fabric: "{{ fabric_name }}" - state: deleted \ No newline at end of file diff --git a/tests/unit/module_utils/common/api/test_api_v1_imagemanagement_rest_policymgnt.py b/tests/unit/module_utils/common/api/test_api_v1_imagemanagement_rest_policymgnt.py index ff66de1b3..c64599054 100644 --- a/tests/unit/module_utils/common/api/test_api_v1_imagemanagement_rest_policymgnt.py +++ b/tests/unit/module_utils/common/api/test_api_v1_imagemanagement_rest_policymgnt.py @@ -111,7 +111,8 @@ def test_ep_policy_mgnt_00050(): """ with does_not_raise(): instance = EpPolicyDetach() - assert instance.path == f"{PATH_PREFIX}/detach-policy" + instance.serial_numbers = ["AB12345CD"] + assert instance.path == f"{PATH_PREFIX}/detach-policy?serialNumber=AB12345CD" assert instance.verb == "DELETE" diff --git a/tests/unit/module_utils/common/api/test_v1_api_fabrics.py b/tests/unit/module_utils/common/api/test_v1_api_fabrics.py deleted file mode 100644 index 5ed96bd84..000000000 --- a/tests/unit/module_utils/common/api/test_v1_api_fabrics.py +++ /dev/null @@ -1,609 +0,0 @@ -# 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 pytest -from ansible_collections.cisco.dcnm.plugins.module_utils.common.api.v1.lan_fabric.rest.control.fabrics.fabrics import ( - EpFabricConfigDeploy, EpFabricConfigSave, EpFabricCreate, EpFabricDelete, - EpFabricDetails, EpFabricFreezeMode, EpFabricUpdate) -from ansible_collections.cisco.dcnm.tests.unit.module_utils.common.common_utils import \ - does_not_raise - -PATH_PREFIX = "/appcenter/cisco/ndfc/api/v1/lan-fabric/rest/control/fabrics" -FABRIC_NAME = "MyFabric" -TEMPLATE_NAME = "Easy_Fabric" - - -def test_ep_fabrics_00010(): - """ - ### Class - - EpFabricConfigDeploy - - ### Summary - - Verify path and verb - - Verify default value for ``force_show_run`` - - Verify default value for ``include_all_msd_switches`` - """ - with does_not_raise(): - instance = EpFabricConfigDeploy() - instance.fabric_name = FABRIC_NAME - assert f"{PATH_PREFIX}/{FABRIC_NAME}/config-deploy" in instance.path - assert "forceShowRun=False" in instance.path - assert "inclAllMSDSwitches=False" in instance.path - assert instance.verb == "POST" - - -def test_ep_fabrics_00020(): - """ - ### Class - - EpFabricConfigDeploy - - ### Summary - - Verify setting ``force_show_run`` results in change to path. - """ - with does_not_raise(): - instance = EpFabricConfigDeploy() - instance.fabric_name = FABRIC_NAME - instance.force_show_run = True - assert f"{PATH_PREFIX}/{FABRIC_NAME}/config-deploy" in instance.path - assert "forceShowRun=True" in instance.path - assert "inclAllMSDSwitches=False" in instance.path - assert instance.verb == "POST" - - -def test_ep_fabrics_00030(): - """ - ### Class - - EpFabricConfigDeploy - - ### Summary - - Verify setting ``include_all_msd_switches`` results in change to path. - """ - with does_not_raise(): - instance = EpFabricConfigDeploy() - instance.fabric_name = FABRIC_NAME - instance.include_all_msd_switches = True - assert f"{PATH_PREFIX}/{FABRIC_NAME}/config-deploy" in instance.path - assert "forceShowRun=False" in instance.path - assert "inclAllMSDSwitches=True" in instance.path - assert instance.verb == "POST" - - -def test_ep_fabrics_00040(): - """ - ### Class - - EpFabricConfigDeploy - - ### Summary - - Verify ``ValueError`` is raised if path is accessed - before setting ``fabric_name``. - - """ - with does_not_raise(): - instance = EpFabricConfigDeploy() - match = r"EpFabricConfigDeploy.path_fabric_name:\s+" - match += r"fabric_name must be set prior to accessing path\." - with pytest.raises(ValueError, match=match): - instance.path # pylint: disable=pointless-statement - - -def test_ep_fabrics_00050(): - """ - ### Class - - EpFabricConfigDeploy - - ### Summary - - Verify ``ValueError`` is raised if ``fabric_name`` - is invalid. - """ - fabric_name = "1_InvalidFabricName" - with does_not_raise(): - instance = EpFabricConfigDeploy() - match = r"EpFabricConfigDeploy.fabric_name:\s+" - match += r"ConversionUtils\.validate_fabric_name:\s+" - match += rf"Invalid fabric name: {fabric_name}\." - with pytest.raises(ValueError, match=match): - instance.fabric_name = fabric_name # pylint: disable=pointless-statement - - -def test_ep_fabrics_00060(): - """ - ### Class - - EpFabricConfigDeploy - - ### Summary - - Verify ``ValueError`` is raised if ``force_show_run`` - is not a boolean. - """ - with does_not_raise(): - instance = EpFabricConfigDeploy() - match = r"EpFabricConfigDeploy.force_show_run:\s+" - match += r"Expected boolean for force_show_run\.\s+" - match += r"Got NOT_BOOLEAN with type str\." - with pytest.raises(ValueError, match=match): - instance.force_show_run = "NOT_BOOLEAN" # pylint: disable=pointless-statement - - -def test_ep_fabrics_00070(): - """ - ### Class - - EpFabricConfigDeploy - - ### Summary - - Verify ``ValueError`` is raised if ``include_all_msd_switches`` - is not a boolean. - """ - with does_not_raise(): - instance = EpFabricConfigDeploy() - match = r"EpFabricConfigDeploy.include_all_msd_switches:\s+" - match += r"Expected boolean for include_all_msd_switches\.\s+" - match += r"Got NOT_BOOLEAN with type str\." - with pytest.raises(ValueError, match=match): - instance.include_all_msd_switches = ( - "NOT_BOOLEAN" # pylint: disable=pointless-statement - ) - - -def test_ep_fabrics_00100(): - """ - ### Class - - EpFabricConfigSave - - ### Summary - - Verify path and verb - """ - with does_not_raise(): - instance = EpFabricConfigSave() - instance.fabric_name = FABRIC_NAME - assert instance.path == f"{PATH_PREFIX}/{FABRIC_NAME}/config-save" - assert instance.verb == "POST" - - -def test_ep_fabrics_00110(): - """ - ### Class - - EpFabricConfigSave - - ### Summary - - Verify ticket_id is added to path when set. - """ - with does_not_raise(): - instance = EpFabricConfigSave() - instance.fabric_name = FABRIC_NAME - instance.ticket_id = "MyTicket1234" - ticket_id_path = f"{PATH_PREFIX}/{FABRIC_NAME}/config-save" - ticket_id_path += "?ticketId=MyTicket1234" - assert instance.path == ticket_id_path - assert instance.verb == "POST" - - -def test_ep_fabrics_00120(): - """ - ### Class - - EpFabricConfigSave - - ### Summary - - Verify ticket_id is added to path when set. - """ - with does_not_raise(): - instance = EpFabricConfigSave() - instance.fabric_name = FABRIC_NAME - instance.ticket_id = "MyTicket1234" - ticket_id_path = f"{PATH_PREFIX}/{FABRIC_NAME}/config-save" - ticket_id_path += "?ticketId=MyTicket1234" - assert instance.path == ticket_id_path - assert instance.verb == "POST" - - -def test_ep_fabrics_00130(): - """ - ### Class - - EpFabricConfigSave - - ### Summary - - Verify ``ValueError`` is raised if ``ticket_id`` - is not a string. - """ - with does_not_raise(): - instance = EpFabricConfigSave() - instance.fabric_name = FABRIC_NAME - match = r"EpFabricConfigSave.ticket_id:\s+" - match += r"Expected string for ticket_id\.\s+" - match += r"Got 10 with type int\." - with pytest.raises(ValueError, match=match): - instance.ticket_id = 10 # pylint: disable=pointless-statement - - -def test_ep_fabrics_00140(): - """ - ### Class - - EpFabricConfigSave - - ### Summary - - Verify ``ValueError`` is raised if path is accessed - before setting ``fabric_name``. - - """ - with does_not_raise(): - instance = EpFabricConfigSave() - match = r"EpFabricConfigSave.path_fabric_name:\s+" - match += r"fabric_name must be set prior to accessing path\." - with pytest.raises(ValueError, match=match): - instance.path # pylint: disable=pointless-statement - - -def test_ep_fabrics_00150(): - """ - ### Class - - EpFabricConfigSave - - ### Summary - - Verify ``ValueError`` is raised if ``fabric_name`` - is invalid. - """ - fabric_name = "1_InvalidFabricName" - with does_not_raise(): - instance = EpFabricConfigSave() - match = r"EpFabricConfigSave.fabric_name:\s+" - match += r"ConversionUtils\.validate_fabric_name:\s+" - match += rf"Invalid fabric name: {fabric_name}\." - with pytest.raises(ValueError, match=match): - instance.fabric_name = fabric_name # pylint: disable=pointless-statement - - -def test_ep_fabrics_00200(): - """ - ### Class - - EpFabricCreate - - ### Summary - - Verify path and verb - """ - with does_not_raise(): - instance = EpFabricCreate() - instance.fabric_name = FABRIC_NAME - instance.template_name = TEMPLATE_NAME - assert instance.path == f"{PATH_PREFIX}/{FABRIC_NAME}/{TEMPLATE_NAME}" - assert instance.verb == "POST" - - -def test_ep_fabrics_00240(): - """ - ### Class - - EpFabricCreate - - ### Summary - - Verify ``ValueError`` is raised if path is accessed - before setting ``fabric_name``. - - """ - with does_not_raise(): - instance = EpFabricCreate() - match = r"EpFabricCreate\.path_fabric_name_template_name:\s+" - match += r"fabric_name must be set prior to accessing path\." - with pytest.raises(ValueError, match=match): - instance.path # pylint: disable=pointless-statement - - -def test_ep_fabrics_00250(): - """ - ### Class - - EpFabricCreate - - ### Summary - - Verify ``ValueError`` is raised if ``fabric_name`` - is invalid. - """ - fabric_name = "1_InvalidFabricName" - with does_not_raise(): - instance = EpFabricCreate() - match = r"EpFabricCreate.fabric_name:\s+" - match += r"ConversionUtils\.validate_fabric_name:\s+" - match += rf"Invalid fabric name: {fabric_name}\." - with pytest.raises(ValueError, match=match): - instance.fabric_name = fabric_name # pylint: disable=pointless-statement - - -def test_ep_fabrics_00260(): - """ - ### Class - - EpFabricCreate - - ### Summary - - Verify ``ValueError`` is raised if path is accessed - before setting ``template_name``. - - """ - with does_not_raise(): - instance = EpFabricCreate() - instance.fabric_name = FABRIC_NAME - match = r"EpFabricCreate\.path_fabric_name_template_name:\s+" - match += r"template_name must be set prior to accessing path\." - with pytest.raises(ValueError, match=match): - instance.path # pylint: disable=pointless-statement - - -def test_ep_fabrics_00270(): - """ - ### Class - - EpFabricCreate - - ### Summary - - Verify ``ValueError`` is raised if ``template_name`` - is invalid. - """ - template_name = "Invalid_Template_Name" - with does_not_raise(): - instance = EpFabricCreate() - instance.fabric_name = FABRIC_NAME - match = r"EpFabricCreate.template_name:\s+" - match += r"Invalid template_name: Invalid_Template_Name\.\s+" - match += r"Expected one of:.*\." - with pytest.raises(ValueError, match=match): - instance.template_name = template_name # pylint: disable=pointless-statement - - -def test_ep_fabrics_00400(): - """ - ### Class - - EpFabricDelete - - ### Summary - - Verify path and verb - """ - with does_not_raise(): - instance = EpFabricDelete() - instance.fabric_name = FABRIC_NAME - assert instance.path == f"{PATH_PREFIX}/{FABRIC_NAME}" - assert instance.verb == "DELETE" - - -def test_ep_fabrics_00440(): - """ - ### Class - - EpFabricDelete - - ### Summary - - Verify ``ValueError`` is raised if path is accessed - before setting ``fabric_name``. - - """ - with does_not_raise(): - instance = EpFabricDelete() - match = r"EpFabricDelete.path_fabric_name:\s+" - match += r"fabric_name must be set prior to accessing path\." - with pytest.raises(ValueError, match=match): - instance.path # pylint: disable=pointless-statement - - -def test_ep_fabrics_00450(): - """ - ### Class - - EpFabricDelete - - ### Summary - - Verify ``ValueError`` is raised if ``fabric_name`` - is invalid. - """ - fabric_name = "1_InvalidFabricName" - with does_not_raise(): - instance = EpFabricDelete() - match = r"EpFabricDelete.fabric_name:\s+" - match += r"ConversionUtils\.validate_fabric_name:\s+" - match += rf"Invalid fabric name: {fabric_name}\." - with pytest.raises(ValueError, match=match): - instance.fabric_name = fabric_name # pylint: disable=pointless-statement - - -def test_ep_fabrics_00500(): - """ - ### Class - - EpFabricDetails - - ### Summary - - Verify path and verb - """ - with does_not_raise(): - instance = EpFabricDetails() - instance.fabric_name = FABRIC_NAME - assert instance.path == f"{PATH_PREFIX}/{FABRIC_NAME}" - assert instance.verb == "GET" - - -def test_ep_fabrics_00540(): - """ - ### Class - - EpFabricDetails - - ### Summary - - Verify ``ValueError`` is raised if path is accessed - before setting ``fabric_name``. - - """ - with does_not_raise(): - instance = EpFabricDetails() - match = r"EpFabricDetails.path_fabric_name:\s+" - match += r"fabric_name must be set prior to accessing path\." - with pytest.raises(ValueError, match=match): - instance.path # pylint: disable=pointless-statement - - -def test_ep_fabrics_00550(): - """ - ### Class - - EpFabricDetails - - ### Summary - - Verify ``ValueError`` is raised if ``fabric_name`` - is invalid. - """ - fabric_name = "1_InvalidFabricName" - with does_not_raise(): - instance = EpFabricDetails() - match = r"EpFabricDetails.fabric_name:\s+" - match += r"ConversionUtils\.validate_fabric_name:\s+" - match += rf"Invalid fabric name: {fabric_name}\." - with pytest.raises(ValueError, match=match): - instance.fabric_name = fabric_name # pylint: disable=pointless-statement - - -def test_ep_fabrics_00600(): - """ - ### Class - - EpFabricFreezeMode - - ### Summary - - Verify path and verb - """ - with does_not_raise(): - instance = EpFabricFreezeMode() - instance.fabric_name = FABRIC_NAME - assert instance.path == f"{PATH_PREFIX}/{FABRIC_NAME}/freezemode" - assert instance.verb == "GET" - - -def test_ep_fabrics_00640(): - """ - ### Class - - EpFabricFreezeMode - - ### Summary - - Verify ``ValueError`` is raised if path is accessed - before setting ``fabric_name``. - - """ - with does_not_raise(): - instance = EpFabricFreezeMode() - match = r"EpFabricFreezeMode.path_fabric_name:\s+" - match += r"fabric_name must be set prior to accessing path\." - with pytest.raises(ValueError, match=match): - instance.path # pylint: disable=pointless-statement - - -def test_ep_fabrics_00650(): - """ - ### Class - - EpFabricFreezeMode - - ### Summary - - Verify ``ValueError`` is raised if ``fabric_name`` - is invalid. - """ - fabric_name = "1_InvalidFabricName" - with does_not_raise(): - instance = EpFabricFreezeMode() - match = r"EpFabricFreezeMode.fabric_name:\s+" - match += r"ConversionUtils\.validate_fabric_name:\s+" - match += rf"Invalid fabric name: {fabric_name}\." - with pytest.raises(ValueError, match=match): - instance.fabric_name = fabric_name # pylint: disable=pointless-statement - - -# NOTE: EpFabricSummary tests are in test_v1_api_switches.py - - -def test_ep_fabrics_00700(): - """ - ### Class - - EpFabricUpdate - - ### Summary - - Verify path and verb - """ - with does_not_raise(): - instance = EpFabricUpdate() - instance.fabric_name = FABRIC_NAME - instance.template_name = TEMPLATE_NAME - assert instance.path == f"{PATH_PREFIX}/{FABRIC_NAME}/{TEMPLATE_NAME}" - assert instance.verb == "PUT" - - -def test_ep_fabrics_00740(): - """ - ### Class - - EpFabricUpdate - - ### Summary - - Verify ``ValueError`` is raised if path is accessed - before setting ``fabric_name``. - - """ - with does_not_raise(): - instance = EpFabricUpdate() - match = r"EpFabricUpdate\.path_fabric_name_template_name:\s+" - match += r"fabric_name must be set prior to accessing path\." - with pytest.raises(ValueError, match=match): - instance.path # pylint: disable=pointless-statement - - -def test_ep_fabrics_00750(): - """ - ### Class - - EpFabricUpdate - - ### Summary - - Verify ``ValueError`` is raised if ``fabric_name`` - is invalid. - """ - fabric_name = "1_InvalidFabricName" - with does_not_raise(): - instance = EpFabricUpdate() - match = r"EpFabricUpdate.fabric_name:\s+" - match += r"ConversionUtils\.validate_fabric_name:\s+" - match += rf"Invalid fabric name: {fabric_name}\." - with pytest.raises(ValueError, match=match): - instance.fabric_name = fabric_name # pylint: disable=pointless-statement - - -def test_ep_fabrics_00760(): - """ - ### Class - - EpFabricUpdate - - ### Summary - - Verify ``ValueError`` is raised if path is accessed - before setting ``template_name``. - - """ - with does_not_raise(): - instance = EpFabricUpdate() - instance.fabric_name = FABRIC_NAME - match = r"EpFabricUpdate\.path_fabric_name_template_name:\s+" - match += r"template_name must be set prior to accessing path\." - with pytest.raises(ValueError, match=match): - instance.path # pylint: disable=pointless-statement - - -def test_ep_fabrics_00770(): - """ - ### Class - - EpFabricUpdate - - ### Summary - - Verify ``ValueError`` is raised if ``template_name`` - is invalid. - """ - template_name = "Invalid_Template_Name" - with does_not_raise(): - instance = EpFabricUpdate() - instance.fabric_name = FABRIC_NAME - match = r"EpFabricUpdate.template_name:\s+" - match += r"Invalid template_name: Invalid_Template_Name\.\s+" - match += r"Expected one of:.*\." - with pytest.raises(ValueError, match=match): - instance.template_name = template_name # pylint: disable=pointless-statement diff --git a/tests/unit/module_utils/common/api/test_v1_api_image_mgnt.py b/tests/unit/module_utils/common/api/test_v1_api_image_mgnt.py deleted file mode 100644 index ab0785d15..000000000 --- a/tests/unit/module_utils/common/api/test_v1_api_image_mgnt.py +++ /dev/null @@ -1,39 +0,0 @@ -# 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 ansible_collections.cisco.dcnm.plugins.module_utils.common.api.v1.imagemanagement.rest.imagemgnt.imagemgnt import \ - EpBootFlashInfo -from ansible_collections.cisco.dcnm.tests.unit.module_utils.common.common_utils import \ - does_not_raise - -PATH_PREFIX = "/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/imagemgnt" - - -def test_ep_image_mgnt_00010(): - """ - ### Class - - EpBootFlashInfo - - ### Summary - - Verify path and verb - """ - with does_not_raise(): - instance = EpBootFlashInfo() - assert instance.path == f"{PATH_PREFIX}/bootFlash/bootflash-info" - assert instance.verb == "GET" diff --git a/tests/unit/module_utils/common/api/test_v1_api_image_upgrade_ep.py b/tests/unit/module_utils/common/api/test_v1_api_image_upgrade_ep.py deleted file mode 100644 index 1e49fd61f..000000000 --- a/tests/unit/module_utils/common/api/test_v1_api_image_upgrade_ep.py +++ /dev/null @@ -1,53 +0,0 @@ -# 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 ansible_collections.cisco.dcnm.plugins.module_utils.common.api.v1.imagemanagement.rest.imageupgrade.imageupgrade import ( - EpInstallOptions, EpUpgradeImage) -from ansible_collections.cisco.dcnm.tests.unit.module_utils.common.common_utils import \ - does_not_raise - -PATH_PREFIX = "/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/imageupgrade" - - -def test_ep_install_options_00010(): - """ - ### Class - - EpInstallOptions - - ### Summary - - Verify path and verb - """ - with does_not_raise(): - instance = EpInstallOptions() - assert instance.path == f"{PATH_PREFIX}/install-options" - assert instance.verb == "POST" - - -def test_ep_upgrade_image_00010(): - """ - ### Class - - EpUpgradeImage - - ### Summary - - Verify path and verb - """ - with does_not_raise(): - instance = EpUpgradeImage() - assert instance.path == f"{PATH_PREFIX}/upgrade-image" - assert instance.verb == "POST" diff --git a/tests/unit/module_utils/common/api/test_v1_api_policy_mgnt.py b/tests/unit/module_utils/common/api/test_v1_api_policy_mgnt.py deleted file mode 100644 index ff66de1b3..000000000 --- a/tests/unit/module_utils/common/api/test_v1_api_policy_mgnt.py +++ /dev/null @@ -1,129 +0,0 @@ -# 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 pytest -from ansible_collections.cisco.dcnm.plugins.module_utils.common.api.v1.imagemanagement.rest.policymgnt.policymgnt import ( - EpPolicies, EpPoliciesAllAttached, EpPolicyAttach, EpPolicyCreate, - EpPolicyDetach, EpPolicyInfo) -from ansible_collections.cisco.dcnm.tests.unit.module_utils.common.common_utils import \ - does_not_raise - -PATH_PREFIX = "/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/policymgnt" - - -def test_ep_policy_mgnt_00010(): - """ - ### Class - - EpPolicies - - ### Summary - - Verify path and verb - """ - with does_not_raise(): - instance = EpPolicies() - assert instance.path == f"{PATH_PREFIX}/policies" - assert instance.verb == "GET" - - -def test_ep_policy_mgnt_00020(): - """ - ### Class - - EpPolicyInfo - - ### Summary - - Verify path and verb - """ - with does_not_raise(): - instance = EpPolicyInfo() - instance.policy_name = "MyPolicy" - assert instance.path == f"{PATH_PREFIX}/image-policy/MyPolicy" - assert instance.verb == "GET" - - -def test_ep_policy_mgnt_00021(): - """ - ### Class - - EpPolicyInfo - - ### Summary - - Verify ``ValueError`` is raised if path is accessed before - setting policy_name. - """ - with does_not_raise(): - instance = EpPolicyInfo() - match = r"EpPolicyInfo\.path:\s+" - match += r"EpPolicyInfo\.policy_name must be set before accessing path\." - with pytest.raises(ValueError, match=match): - instance.path # pylint: disable=pointless-statement - - -def test_ep_policy_mgnt_00030(): - """ - ### Class - - EpPoliciesAllAttached - - ### Summary - - Verify path and verb - """ - with does_not_raise(): - instance = EpPoliciesAllAttached() - assert instance.path == f"{PATH_PREFIX}/all-attached-policies" - assert instance.verb == "GET" - - -def test_ep_policy_mgnt_00040(): - """ - ### Class - - EpPolicyAttach - - ### Summary - - Verify path and verb - """ - with does_not_raise(): - instance = EpPolicyAttach() - assert instance.path == f"{PATH_PREFIX}/attach-policy" - assert instance.verb == "POST" - - -def test_ep_policy_mgnt_00050(): - """ - ### Class - - EpPolicyDetach - - ### Summary - - Verify path and verb - """ - with does_not_raise(): - instance = EpPolicyDetach() - assert instance.path == f"{PATH_PREFIX}/detach-policy" - assert instance.verb == "DELETE" - - -def test_ep_policy_mgnt_00060(): - """ - ### Class - - EpPolicyCreate - - ### Summary - - Verify path and verb - """ - with does_not_raise(): - instance = EpPolicyCreate() - assert instance.path == f"{PATH_PREFIX}/platform-policy" - assert instance.verb == "POST" diff --git a/tests/unit/module_utils/common/api/test_v1_api_staging_management.py b/tests/unit/module_utils/common/api/test_v1_api_staging_management.py deleted file mode 100644 index 8bb951c05..000000000 --- a/tests/unit/module_utils/common/api/test_v1_api_staging_management.py +++ /dev/null @@ -1,67 +0,0 @@ -# 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 ansible_collections.cisco.dcnm.plugins.module_utils.common.api.v1.imagemanagement.rest.stagingmanagement.stagingmanagement import ( - EpImageStage, EpImageValidate, EpStageInfo) -from ansible_collections.cisco.dcnm.tests.unit.module_utils.common.common_utils import \ - does_not_raise - -PATH_PREFIX = "/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/stagingmanagement" - - -def test_ep_staging_management_00010(): - """ - ### Class - - EpImageStage - - ### Summary - - Verify path and verb - """ - with does_not_raise(): - instance = EpImageStage() - assert instance.path == f"{PATH_PREFIX}/stage-image" - assert instance.verb == "POST" - - -def test_ep_staging_management_00020(): - """ - ### Class - - EpImageValidate - - ### Summary - - Verify path and verb - """ - with does_not_raise(): - instance = EpImageValidate() - assert instance.path == f"{PATH_PREFIX}/validate-image" - assert instance.verb == "POST" - - -def test_ep_staging_management_00030(): - """ - ### Class - - EpStageInfo - - ### Summary - - Verify path and verb - """ - with does_not_raise(): - instance = EpStageInfo() - assert instance.path == f"{PATH_PREFIX}/stage-info" - assert instance.verb == "GET" diff --git a/tests/unit/module_utils/common/api/test_v1_api_switches.py b/tests/unit/module_utils/common/api/test_v1_api_switches.py deleted file mode 100644 index a654f846d..000000000 --- a/tests/unit/module_utils/common/api/test_v1_api_switches.py +++ /dev/null @@ -1,79 +0,0 @@ -# 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 pytest -from ansible_collections.cisco.dcnm.plugins.module_utils.common.api.v1.lan_fabric.rest.control.switches.switches import \ - EpFabricSummary -from ansible_collections.cisco.dcnm.tests.unit.module_utils.common.common_utils import \ - does_not_raise - -PATH_PREFIX = "/appcenter/cisco/ndfc/api/v1/lan-fabric/rest/control/switches" -FABRIC_NAME = "MyFabric" - - -def test_ep_switches_00010(): - """ - ### Class - - EpFabricSummary - - ### Summary - - Verify path and verb - """ - with does_not_raise(): - instance = EpFabricSummary() - instance.fabric_name = FABRIC_NAME - assert f"{PATH_PREFIX}/{FABRIC_NAME}/overview" in instance.path - assert instance.verb == "GET" - - -def test_ep_switches_00040(): - """ - ### Class - - EpFabricSummary - - ### Summary - - Verify ``ValueError`` is raised if path is accessed - before setting ``fabric_name``. - - """ - with does_not_raise(): - instance = EpFabricSummary() - match = r"EpFabricSummary.path_fabric_name:\s+" - match += r"fabric_name must be set prior to accessing path\." - with pytest.raises(ValueError, match=match): - instance.path # pylint: disable=pointless-statement - - -def test_ep_switches_00050(): - """ - ### Class - - EpFabricSummary - - ### Summary - - Verify ``ValueError`` is raised if ``fabric_name`` - is invalid. - """ - fabric_name = "1_InvalidFabricName" - with does_not_raise(): - instance = EpFabricSummary() - match = r"EpFabricSummary.fabric_name:\s+" - match += r"ConversionUtils\.validate_fabric_name:\s+" - match += rf"Invalid fabric name: {fabric_name}\." - with pytest.raises(ValueError, match=match): - instance.fabric_name = fabric_name # pylint: disable=pointless-statement diff --git a/tests/unit/module_utils/common/api/test_v1_api_templates.py b/tests/unit/module_utils/common/api/test_v1_api_templates.py deleted file mode 100644 index bdedf18f9..000000000 --- a/tests/unit/module_utils/common/api/test_v1_api_templates.py +++ /dev/null @@ -1,93 +0,0 @@ -# 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 pytest -from ansible_collections.cisco.dcnm.plugins.module_utils.common.api.v1.configtemplate.rest.config.templates.templates import ( - EpTemplate, EpTemplates) -from ansible_collections.cisco.dcnm.tests.unit.module_utils.common.common_utils import \ - does_not_raise - -PATH_PREFIX = "/appcenter/cisco/ndfc/api/v1/configtemplate/rest/config/templates" -TEMPLATE_NAME = "Easy_Fabric" - - -def test_ep_templates_00010(): - """ - ### Class - - EpTemplate - - ### Summary - - Verify path and verb - """ - with does_not_raise(): - instance = EpTemplate() - instance.template_name = TEMPLATE_NAME - assert f"{PATH_PREFIX}/{TEMPLATE_NAME}" in instance.path - assert instance.verb == "GET" - - -def test_ep_templates_00040(): - """ - ### Class - - EpTemplate - - ### Summary - - Verify ``ValueError`` is raised if path is accessed - before setting ``template_name``. - - """ - with does_not_raise(): - instance = EpTemplate() - match = r"EpTemplate.path_template_name:\s+" - match += r"template_name must be set prior to accessing path\." - with pytest.raises(ValueError, match=match): - instance.path # pylint: disable=pointless-statement - - -def test_ep_templates_00050(): - """ - ### Class - - EpFabricConfigDeploy - - ### Summary - - Verify ``ValueError`` is raised if ``template_name`` - is invalid. - """ - template_name = "Invalid_Template_Name" - with does_not_raise(): - instance = EpTemplate() - match = r"EpTemplate.template_name:\s+" - match += r"Invalid template_name: Invalid_Template_Name.\s+" - match += r"Expected one of:\s+" - with pytest.raises(ValueError, match=match): - instance.template_name = template_name # pylint: disable=pointless-statement - - -def test_ep_templates_00100(): - """ - ### Class - - EpTemplates - - ### Summary - - Verify path and verb - """ - with does_not_raise(): - instance = EpTemplates() - assert instance.path == PATH_PREFIX - assert instance.verb == "GET" diff --git a/tests/unit/module_utils/common/common_utils.py b/tests/unit/module_utils/common/common_utils.py index 56c28d2fe..ede1e1817 100644 --- a/tests/unit/module_utils/common/common_utils.py +++ b/tests/unit/module_utils/common/common_utils.py @@ -18,7 +18,6 @@ from contextlib import contextmanager -from typing import Any, Dict import pytest from ansible_collections.ansible.netcommon.tests.unit.modules.utils import \ @@ -27,6 +26,8 @@ ControllerFeatures from ansible_collections.cisco.dcnm.plugins.module_utils.common.controller_version import \ ControllerVersion +from ansible_collections.cisco.dcnm.plugins.module_utils.common.image_policies import \ + ImagePolicies from ansible_collections.cisco.dcnm.plugins.module_utils.common.log import Log from ansible_collections.cisco.dcnm.plugins.module_utils.common.maintenance_mode import \ MaintenanceMode @@ -95,7 +96,7 @@ def implements(self): """ return "response_generator" - def public_method_for_pylint(self) -> Any: + def public_method_for_pylint(self): """ Add one public method to appease pylint """ @@ -123,7 +124,7 @@ def fail_json(msg, **kwargs) -> AnsibleFailJson: """ raise AnsibleFailJson(msg) - def public_method_for_pylint(self) -> Any: + def public_method_for_pylint(self): """ Add one public method to appease pylint """ @@ -136,17 +137,25 @@ def public_method_for_pylint(self) -> Any: @pytest.fixture(name="controller_features") def controller_features_fixture(): """ - return ControllerFeatures + return ControllerFeatures instance. """ - return ControllerFeatures(params) + return ControllerFeatures() @pytest.fixture(name="controller_version") def controller_version_fixture(): """ - return ControllerVersion with mocked AnsibleModule + return ControllerVersion instance. """ - return ControllerVersion(MockAnsibleModule) + return ControllerVersion() + + +@pytest.fixture(name="image_policies") +def image_policies_fixture(): + """ + Return ImagePolicies instance. + """ + return ImagePolicies() @pytest.fixture(name="sender_dcnm") @@ -237,7 +246,7 @@ def does_not_raise(): yield -def merge_dicts_data(key: str) -> Dict[str, str]: +def merge_dicts_data(key: str) -> dict[str, str]: """ Return data from merge_dicts.json for merge_dicts unit tests. """ @@ -247,7 +256,7 @@ def merge_dicts_data(key: str) -> Dict[str, str]: return data -def merge_dicts_v2_data(key: str) -> Dict[str, str]: +def merge_dicts_v2_data(key: str) -> dict[str, str]: """ Return data from merge_dicts_v2.json for merge_dicts_v2 unit tests. """ @@ -257,7 +266,7 @@ def merge_dicts_v2_data(key: str) -> Dict[str, str]: return data -def responses_deploy_maintenance_mode(key: str) -> Dict[str, str]: +def responses_deploy_maintenance_mode(key: str) -> dict[str, str]: """ Return data in responses_DeployMaintenanceMode.json """ @@ -267,7 +276,7 @@ def responses_deploy_maintenance_mode(key: str) -> Dict[str, str]: return response -def responses_controller_features(key: str) -> Dict[str, str]: +def responses_controller_features(key: str) -> dict[str, str]: """ Return data in responses_ControllerFeatures.json """ @@ -276,17 +285,17 @@ def responses_controller_features(key: str) -> Dict[str, str]: return response -def responses_controller_version(key: str) -> Dict[str, str]: +def responses_ep_version(key: str) -> dict[str, str]: """ - Return data in responses_ControllerVersion.json + Return responses for endpoint EpVersion. """ - response_file = "responses_ControllerVersion" + response_file = "responses_ep_version" response = load_fixture(response_file).get(key) - print(f"responses_controller_version: {key} : {response}") + print(f"responses_ep_version: {key} : {response}") return response -def responses_fabric_details_by_name(key: str) -> Dict[str, str]: +def responses_fabric_details_by_name(key: str) -> dict[str, str]: """ Return data in responses_FabricDetailsByName.json """ @@ -296,7 +305,17 @@ def responses_fabric_details_by_name(key: str) -> Dict[str, str]: return response -def responses_maintenance_mode(key: str) -> Dict[str, str]: +def responses_ep_policies(key: str) -> dict[str, str]: + """ + Return controller responses for the EpPolicies() endpoint. + """ + response_file = "responses_ep_policies" + response = load_fixture(response_file).get(key) + print(f"responses_ep_policies: {key} : {response}") + return response + + +def responses_maintenance_mode(key: str) -> dict[str, str]: """ Return data in responses_MaintenanceMode.json """ @@ -306,7 +325,7 @@ def responses_maintenance_mode(key: str) -> Dict[str, str]: return response -def responses_sender_dcnm(key: str) -> Dict[str, str]: +def responses_sender_dcnm(key: str) -> dict[str, str]: """ Return data in responses_SenderDcnm.json """ @@ -316,7 +335,7 @@ def responses_sender_dcnm(key: str) -> Dict[str, str]: return response -def responses_sender_file(key: str) -> Dict[str, str]: +def responses_sender_file(key: str) -> dict[str, str]: """ Return data in responses_SenderFile.json """ @@ -326,7 +345,7 @@ def responses_sender_file(key: str) -> Dict[str, str]: return response -def responses_switch_details(key: str) -> Dict[str, str]: +def responses_switch_details(key: str) -> dict[str, str]: """ Return data in responses_SwitchDetails.json """ diff --git a/tests/unit/module_utils/common/fixtures/responses_SwitchDetails.json b/tests/unit/module_utils/common/fixtures/responses_SwitchDetails.json index 0212edebb..f91cb4e7e 100644 --- a/tests/unit/module_utils/common/fixtures/responses_SwitchDetails.json +++ b/tests/unit/module_utils/common/fixtures/responses_SwitchDetails.json @@ -532,6 +532,28 @@ "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/lan-fabric/rest/inventory/allswitches", "RETURN_CODE": 200 }, + "test_switch_details_00550a": { + "TEST_NOTES": [ + "DATA[0] contains valid content", + "RETURN_CODE: 500", + "MESSAGE: NOK" + ], + "DATA": [ + { + "fabricName": "VXLAN_Fabric", + "freezeMode": null, + "ipAddress": "192.168.1.2", + "mode": "Normal", + "serialNumber": "FDO123456FV", + "switchRole": "leaf", + "systemMode": "Maintenance" + } + ], + "MESSAGE": "NOK", + "METHOD": "GET", + "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/lan-fabric/rest/inventory/allswitches", + "RETURN_CODE": 500 + }, "test_switch_details_00700a": { "TEST_NOTES": [ "DATA[0].mode is null", diff --git a/tests/unit/modules/dcnm/dcnm_image_upgrade/fixtures/image_upgrade_responses_ImagePolicies.json b/tests/unit/module_utils/common/fixtures/responses_ep_policies.json similarity index 96% rename from tests/unit/modules/dcnm/dcnm_image_upgrade/fixtures/image_upgrade_responses_ImagePolicies.json rename to tests/unit/module_utils/common/fixtures/responses_ep_policies.json index 3fe92bb5b..1064e2e27 100644 --- a/tests/unit/modules/dcnm/dcnm_image_upgrade/fixtures/image_upgrade_responses_ImagePolicies.json +++ b/tests/unit/module_utils/common/fixtures/responses_ep_policies.json @@ -1,5 +1,5 @@ { - "test_image_upgrade_image_policies_00010a": { + "test_image_policies_00100a": { "RETURN_CODE": 200, "METHOD": "GET", "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/policymgnt/policies", @@ -39,7 +39,7 @@ "message": "" } }, - "test_image_upgrade_image_policies_00020a": { + "test_image_policies_00200a": { "RETURN_CODE": 200, "METHOD": "GET", "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/policymgnt/policies", @@ -79,7 +79,7 @@ "message": "" } }, - "test_image_upgrade_image_policies_00021a": { + "test_image_policies_00300a": { "TEST_NOTES": [ "404 RETURN_CODE" ], @@ -94,7 +94,7 @@ "path": "/rest/policymgnt/policiess" } }, - "test_image_upgrade_image_policies_00022a": { + "test_image_policies_00400a": { "TEST_NOTES": [ "DATA field is empty" ], @@ -104,7 +104,7 @@ "MESSAGE": "OK", "DATA": {} }, - "test_image_upgrade_image_policies_00023a": { + "test_image_policies_00500a": { "TEST_NOTES": [ "DATA has no defined image policies" ], @@ -118,7 +118,7 @@ "message": "" } }, - "test_image_upgrade_image_policies_00024a": { + "test_image_policies_00600a": { "TEST_NOTES": [ "RETURN_CODE 200", "policyName FOO is missing in response" @@ -148,7 +148,7 @@ "message": "" } }, - "test_image_upgrade_image_policies_00025a": { + "test_image_policies_00700a": { "TEST_NOTES": [ "RETURN_CODE 200", "policyName key is missing" @@ -177,7 +177,7 @@ "message": "" } }, - "test_image_upgrade_image_policies_00026a": { + "test_image_policies_00800a": { "TEST_NOTES": [ "RETURN_CODE 500", "MESSAGE == NOK" @@ -193,7 +193,7 @@ "message": "" } }, - "test_image_upgrade_image_policy_action_00013a": { + "test_image_policies_02100a": { "RETURN_CODE": 200, "METHOD": "GET", "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/policymgnt/policies", @@ -219,19 +219,7 @@ "message": "" } }, - "test_image_upgrade_image_policy_action_00014a": { - "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": [ - ], - "message": "" - } - }, - "test_image_upgrade_image_policy_action_00020a": { + "test_image_policies_02200a": { "RETURN_CODE": 200, "METHOD": "GET", "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/policymgnt/policies", @@ -257,7 +245,18 @@ "message": "" } }, - "test_image_upgrade_image_policy_action_00030a": { + "test_image_policies_03000a": { + "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": [], + "message": "" + } + }, + "test_image_policies_03100a": { "RETURN_CODE": 200, "METHOD": "GET", "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/policymgnt/policies", @@ -278,12 +277,26 @@ "imageName": "nxos64-cs.10.2.5.M.bin", "agnostic": "false", "ref_count": 10 + }, + { + "policyName": "OR1F", + "policyType": "PLATFORM", + "nxosVersion": "10.4.1_nxos64-cs_64bit", + "packageName": "", + "platform": "N9K/N3K", + "policyDescr": "OR1F EPLD", + "platformPolicies": "", + "epldImgName": "n9000-epld.10.4.1.F.img", + "rpmimages": "", + "imageName": "nxos64-cs.10.4.1.F.bin", + "agnostic": "false", + "ref_count": 0 } ], "message": "" } }, - "test_image_upgrade_image_policy_action_00031a": { + "test_image_upgrade_image_policy_action_00013a": { "RETURN_CODE": 200, "METHOD": "GET", "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/policymgnt/policies", @@ -309,7 +322,19 @@ "message": "" } }, - "test_image_upgrade_image_policy_action_00040a": { + "test_image_upgrade_image_policy_action_00014a": { + "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": [ + ], + "message": "" + } + }, + "test_image_upgrade_image_policy_action_00020a": { "RETURN_CODE": 200, "METHOD": "GET", "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/policymgnt/policies", @@ -335,7 +360,7 @@ "message": "" } }, - "test_image_upgrade_image_policy_action_00041a": { + "test_image_upgrade_image_policy_action_00030a": { "RETURN_CODE": 200, "METHOD": "GET", "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/policymgnt/policies", @@ -361,7 +386,7 @@ "message": "" } }, - "test_image_upgrade_image_policy_action_00050a": { + "test_image_upgrade_image_policy_action_00031a": { "RETURN_CODE": 200, "METHOD": "GET", "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/policymgnt/policies", @@ -387,7 +412,7 @@ "message": "" } }, - "test_image_upgrade_image_policy_action_00051a": { + "test_image_upgrade_image_policy_action_00040a": { "RETURN_CODE": 200, "METHOD": "GET", "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/policymgnt/policies", @@ -413,7 +438,7 @@ "message": "" } }, - "test_image_upgrade_image_policies_00041a": { + "test_image_upgrade_image_policy_action_00041a": { "RETURN_CODE": 200, "METHOD": "GET", "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/policymgnt/policies", @@ -439,7 +464,7 @@ "message": "" } }, - "test_image_upgrade_image_policies_00042a": { + "test_image_upgrade_image_policy_action_00050a": { "RETURN_CODE": 200, "METHOD": "GET", "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/policymgnt/policies", @@ -465,7 +490,7 @@ "message": "" } }, - "test_image_upgrade_image_policies_00051a": { + "test_image_upgrade_image_policy_action_00051a": { "RETURN_CODE": 200, "METHOD": "GET", "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/policymgnt/policies", @@ -486,20 +511,6 @@ "imageName": "nxos64-cs.10.2.5.M.bin", "agnostic": "false", "ref_count": 10 - }, - { - "policyName": "OR1F", - "policyType": "PLATFORM", - "nxosVersion": "10.4.1_nxos64-cs_64bit", - "packageName": "", - "platform": "N9K/N3K", - "policyDescr": "OR1F EPLD", - "platformPolicies": "", - "epldImgName": "n9000-epld.10.4.1.F.img", - "rpmimages": "", - "imageName": "nxos64-cs.10.4.1.F.bin", - "agnostic": "false", - "ref_count": 0 } ], "message": "" diff --git a/tests/unit/module_utils/common/fixtures/responses_ControllerVersion.json b/tests/unit/module_utils/common/fixtures/responses_ep_version.json similarity index 91% rename from tests/unit/module_utils/common/fixtures/responses_ControllerVersion.json rename to tests/unit/module_utils/common/fixtures/responses_ep_version.json index e14e07e28..d4472ef77 100644 --- a/tests/unit/module_utils/common/fixtures/responses_ControllerVersion.json +++ b/tests/unit/module_utils/common/fixtures/responses_ep_version.json @@ -1,5 +1,5 @@ { - "test_common_version_00009a": { + "test_controller_version_00100a": { "RETURN_CODE": 200, "METHOD": "GET", "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/fm/about/version", @@ -15,21 +15,7 @@ "is_upgrade_inprogress": "false" } }, - "test_common_version_00010a": { - "RETURN_CODE": 404, - "METHOD": "GET", - "REQUEST_PATH": "https://foo/noop", - "MESSAGE": "Not Found", - "DATA": {} - }, - "test_common_version_00011a": { - "RETURN_CODE": 500, - "METHOD": "GET", - "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/fm/about/version", - "MESSAGE": "Internal Server Error", - "DATA": {} - }, - "test_common_version_00002a": { + "test_controller_version_00100b": { "RETURN_CODE": 200, "METHOD": "GET", "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/fm/about/version", @@ -38,14 +24,14 @@ "version": "12.1.3b", "mode": "LAN", "isMediaController": "false", - "dev": "false", + "dev": "true", "isHaEnabled": "false", "install": "EASYFABRIC", "uuid": "", "is_upgrade_inprogress": "false" } }, - "test_common_version_00002b": { + "test_controller_version_00100c": { "RETURN_CODE": 200, "METHOD": "GET", "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/fm/about/version", @@ -54,14 +40,13 @@ "version": "12.1.3b", "mode": "LAN", "isMediaController": "false", - "dev": "true", "isHaEnabled": "false", "install": "EASYFABRIC", "uuid": "", "is_upgrade_inprogress": "false" } }, - "test_common_version_00002c": { + "test_controller_version_00110a": { "RETURN_CODE": 200, "METHOD": "GET", "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/fm/about/version", @@ -70,13 +55,14 @@ "version": "12.1.3b", "mode": "LAN", "isMediaController": "false", + "dev": "false", "isHaEnabled": "false", "install": "EASYFABRIC", "uuid": "", "is_upgrade_inprogress": "false" } }, - "test_common_version_00003a": { + "test_controller_version_00110b": { "RETURN_CODE": 200, "METHOD": "GET", "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/fm/about/version", @@ -87,12 +73,11 @@ "isMediaController": "false", "dev": "false", "isHaEnabled": "false", - "install": "EASYFABRIC", "uuid": "", "is_upgrade_inprogress": "false" } }, - "test_common_version_00003b": { + "test_controller_version_00120a": { "RETURN_CODE": 200, "METHOD": "GET", "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/fm/about/version", @@ -102,12 +87,13 @@ "mode": "LAN", "isMediaController": "false", "dev": "false", - "isHaEnabled": "false", + "isHaEnabled": "true", + "install": "EASYFABRIC", "uuid": "", "is_upgrade_inprogress": "false" } }, - "test_common_version_00004b": { + "test_controller_version_00120b": { "RETURN_CODE": 200, "METHOD": "GET", "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/fm/about/version", @@ -123,7 +109,7 @@ "is_upgrade_inprogress": "false" } }, - "test_common_version_00004a": { + "test_controller_version_00120c": { "RETURN_CODE": 200, "METHOD": "GET", "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/fm/about/version", @@ -133,13 +119,12 @@ "mode": "LAN", "isMediaController": "false", "dev": "false", - "isHaEnabled": "true", "install": "EASYFABRIC", "uuid": "", "is_upgrade_inprogress": "false" } }, - "test_common_version_00004c": { + "test_controller_version_00130a": { "RETURN_CODE": 200, "METHOD": "GET", "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/fm/about/version", @@ -147,14 +132,15 @@ "DATA": { "version": "12.1.3b", "mode": "LAN", - "isMediaController": "false", + "isMediaController": "true", "dev": "false", + "isHaEnabled": "false", "install": "EASYFABRIC", "uuid": "", "is_upgrade_inprogress": "false" } }, - "test_common_version_00006b": { + "test_controller_version_00130b": { "RETURN_CODE": 200, "METHOD": "GET", "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/fm/about/version", @@ -170,7 +156,7 @@ "is_upgrade_inprogress": "false" } }, - "test_common_version_00006a": { + "test_controller_version_00130c": { "RETURN_CODE": 200, "METHOD": "GET", "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/fm/about/version", @@ -178,15 +164,14 @@ "DATA": { "version": "12.1.3b", "mode": "LAN", - "isMediaController": "false", "dev": "false", "isHaEnabled": "false", "install": "EASYFABRIC", "uuid": "", - "is_upgrade_inprogress": "true" + "is_upgrade_inprogress": "false" } }, - "test_common_version_00006c": { + "test_controller_version_00140a": { "RETURN_CODE": 200, "METHOD": "GET", "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/fm/about/version", @@ -198,10 +183,11 @@ "dev": "false", "isHaEnabled": "false", "install": "EASYFABRIC", - "uuid": "" + "uuid": "", + "is_upgrade_inprogress": "true" } }, - "test_common_version_00005b": { + "test_controller_version_00140b": { "RETURN_CODE": 200, "METHOD": "GET", "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/fm/about/version", @@ -217,7 +203,7 @@ "is_upgrade_inprogress": "false" } }, - "test_common_version_00005a": { + "test_controller_version_00140c": { "RETURN_CODE": 200, "METHOD": "GET", "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/fm/about/version", @@ -225,15 +211,14 @@ "DATA": { "version": "12.1.3b", "mode": "LAN", - "isMediaController": "true", + "isMediaController": "false", "dev": "false", "isHaEnabled": "false", "install": "EASYFABRIC", - "uuid": "", - "is_upgrade_inprogress": "false" + "uuid": "" } }, - "test_common_version_00005c": { + "test_controller_version_00150a": { "RETURN_CODE": 200, "METHOD": "GET", "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/fm/about/version", @@ -241,6 +226,7 @@ "DATA": { "version": "12.1.3b", "mode": "LAN", + "isMediaController": "false", "dev": "false", "isHaEnabled": "false", "install": "EASYFABRIC", @@ -248,7 +234,13 @@ "is_upgrade_inprogress": "false" } }, - "test_common_version_00007a": { + "test_controller_version_00160a": { + "RETURN_CODE": 200, + "METHOD": "GET", + "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/fm/about/version", + "MESSAGE": "OK" + }, + "test_controller_version_00170a": { "RETURN_CODE": 200, "METHOD": "GET", "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/fm/about/version", @@ -264,13 +256,21 @@ "is_upgrade_inprogress": "false" } }, - "test_common_version_00008a": { - "RETURN_CODE": 200, + "test_controller_version_00180a": { + "RETURN_CODE": 404, + "METHOD": "GET", + "REQUEST_PATH": "https://foo/noop", + "MESSAGE": "Not Found", + "DATA": {} + }, + "test_controller_version_00190a": { + "RETURN_CODE": 500, "METHOD": "GET", "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/fm/about/version", - "MESSAGE": "OK" + "MESSAGE": "Internal Server Error", + "DATA": {} }, - "test_common_version_00012a": { + "test_controller_version_00200a": { "RETURN_CODE": 200, "METHOD": "GET", "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/fm/about/version", @@ -286,7 +286,7 @@ "is_upgrade_inprogress": "false" } }, - "test_common_version_00012b": { + "test_controller_version_00200b": { "RETURN_CODE": 200, "METHOD": "GET", "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/fm/about/version", @@ -300,7 +300,7 @@ "is_upgrade_inprogress": "false" } }, - "test_common_version_00013a": { + "test_controller_version_00210a": { "RETURN_CODE": 200, "METHOD": "GET", "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/fm/about/version", @@ -316,7 +316,7 @@ "is_upgrade_inprogress": "false" } }, - "test_common_version_00013b": { + "test_controller_version_00210b": { "RETURN_CODE": 200, "METHOD": "GET", "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/fm/about/version", @@ -330,7 +330,7 @@ "is_upgrade_inprogress": "false" } }, - "test_common_version_00014a": { + "test_controller_version_00220a": { "RETURN_CODE": 200, "METHOD": "GET", "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/fm/about/version", @@ -346,7 +346,7 @@ "is_upgrade_inprogress": "false" } }, - "test_common_version_00014b": { + "test_controller_version_00220b": { "RETURN_CODE": 200, "METHOD": "GET", "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/fm/about/version", @@ -360,7 +360,7 @@ "is_upgrade_inprogress": "false" } }, - "test_common_version_00015a": { + "test_controller_version_00230a": { "RETURN_CODE": 200, "METHOD": "GET", "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/fm/about/version", @@ -376,7 +376,7 @@ "is_upgrade_inprogress": "false" } }, - "test_common_version_00015b": { + "test_controller_version_00230b": { "RETURN_CODE": 200, "METHOD": "GET", "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/fm/about/version", @@ -390,7 +390,7 @@ "is_upgrade_inprogress": "false" } }, - "test_common_version_00016a": { + "test_controller_version_00240a": { "RETURN_CODE": 200, "METHOD": "GET", "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/fm/about/version", @@ -406,7 +406,7 @@ "is_upgrade_inprogress": "false" } }, - "test_common_version_00016b": { + "test_controller_version_00240b": { "RETURN_CODE": 200, "METHOD": "GET", "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/fm/about/version", @@ -420,7 +420,7 @@ "is_upgrade_inprogress": "false" } }, - "test_common_version_00017a": { + "test_controller_version_00250a": { "RETURN_CODE": 200, "METHOD": "GET", "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/fm/about/version", @@ -436,7 +436,7 @@ "is_upgrade_inprogress": "false" } }, - "test_common_version_00017b": { + "test_controller_version_00250b": { "RETURN_CODE": 200, "METHOD": "GET", "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/fm/about/version", diff --git a/tests/unit/module_utils/common/test_controller_features.py b/tests/unit/module_utils/common/test_controller_features.py index 0a932aba4..6b1baf560 100644 --- a/tests/unit/module_utils/common/test_controller_features.py +++ b/tests/unit/module_utils/common/test_controller_features.py @@ -32,22 +32,22 @@ import inspect import pytest -from ansible_collections.cisco.dcnm.plugins.module_utils.common.api.v1.fm.fm import \ - EpFeatures from ansible_collections.cisco.dcnm.plugins.module_utils.common.controller_features import \ ControllerFeatures -from ansible_collections.cisco.dcnm.plugins.module_utils.common.conversion import \ - ConversionUtils from ansible_collections.cisco.dcnm.plugins.module_utils.common.exceptions import \ ControllerResponseError -from ansible_collections.cisco.dcnm.plugins.module_utils.common.rest_send import \ +from ansible_collections.cisco.dcnm.plugins.module_utils.common.response_handler import \ + ResponseHandler +from ansible_collections.cisco.dcnm.plugins.module_utils.common.rest_send_v2 import \ RestSend +from ansible_collections.cisco.dcnm.plugins.module_utils.common.sender_file import \ + Sender from ansible_collections.cisco.dcnm.tests.unit.module_utils.common.common_utils import ( MockAnsibleModule, ResponseGenerator, controller_features_fixture, does_not_raise, params, responses_controller_features) -def test_controller_features_00010(controller_features) -> None: +def test_controller_features_00000(controller_features) -> None: """ Classes and Methods - ControllerFeatures @@ -60,53 +60,35 @@ def test_controller_features_00010(controller_features) -> None: with does_not_raise(): instance = controller_features assert instance.class_name == "ControllerFeatures" - assert isinstance(instance.api_features, EpFeatures) - assert isinstance(instance.conversion, ConversionUtils) - assert instance.check_mode is False + assert instance.ep_features.class_name == "EpFeatures" + assert instance.conversion.class_name == "ConversionUtils" assert instance.filter is None - assert instance.response is None assert instance.response_data is None assert instance.rest_send is None - assert instance.result is None - - -def test_controller_features_00020(controller_features) -> None: - """ - Classes and Methods - - ControllerFeatures - - __init__() - - Test - - ``ValueError`` is raised when params is missing check_mode - """ - params = {} - match = r"ControllerFeatures\.__init__\(\):\s+" - match += r"check_mode is required\." - with pytest.raises(ValueError, match=match): - instance = ControllerFeatures(params) # pylint: disable=unused-variable def test_controller_features_00030(controller_features) -> None: """ - Classes and Methods + ### Classes and Methods + - ControllerFeatures() - __init__() - refresh() - Summary - - Verify ControllerFeatures().refresh() raises ``ValueError`` - when ``ControllerFeatures().rest_send`` is not set. + ### Summary + Verify ``refresh`` raises ``ValueError`` when ``rest_send`` is not set. + + ### Setup - Code Flow - Setup - ControllerFeatures() is instantiated - Code Flow - Test - - ControllerFeatures().refresh() is called without having - first set ControllerFeatures().rest_send + ### Test - Expected Result + - ``refresh`` is called without having first set ``rest_send``. + + ### Expected Result - ``ValueError`` is raised - - Exception message matches expected + - Exception message matches expected. """ with does_not_raise(): instance = controller_features @@ -120,29 +102,33 @@ def test_controller_features_00030(controller_features) -> None: def test_controller_features_00040(monkeypatch, controller_features) -> None: """ - Classes and Methods + ### Classes and Methods + - ControllerFeatures() - __init__() - refresh() - Summary + ### Summary + - Verify refresh() success case: - RETURN_CODE is 200. - Controller response contains expected structure and values. - Code Flow - Setup + ### Setup + - ControllerFeatures() is instantiated - - dcnm_send() is patched to return the mocked controller response - ControllerFeatures().RestSend() is instantiated - ControllerFeatures().refresh() is called - responses_ControllerFeatures contains a dict with: - RETURN_CODE == 200 - DATA == [] - Code Flow - Test + ### Test + - ControllerFeatures().refresh() is called - Expected Result + ### Expected Result + - Exception is not raised - instance.response_data returns expected controller features data - ControllerFeatures()._properties are updated @@ -150,27 +136,23 @@ def test_controller_features_00040(monkeypatch, controller_features) -> None: method_name = inspect.stack()[0][3] key = f"{method_name}a" - PATCH_DCNM_SEND = "ansible_collections.cisco.dcnm.plugins." - PATCH_DCNM_SEND += "module_utils.common.rest_send.dcnm_send" - def responses(): yield responses_controller_features(key) - gen = ResponseGenerator(responses()) + gen_responses = ResponseGenerator(responses()) - def mock_dcnm_send(*args, **kwargs): - item = gen.next - return item + sender = Sender() + sender.ansible_module = MockAnsibleModule() + sender.gen = gen_responses + rest_send = RestSend(params) + rest_send.unit_test = True + rest_send.timeout = 1 + rest_send.response_handler = ResponseHandler() + rest_send.sender = sender with does_not_raise(): instance = controller_features - instance.rest_send = RestSend(MockAnsibleModule()) - instance.rest_send.unit_test = True - instance.rest_send.timeout = 1 - - monkeypatch.setattr(PATCH_DCNM_SEND, mock_dcnm_send) - - with does_not_raise(): + instance.rest_send = rest_send instance.refresh() instance.filter = "pmn" @@ -179,13 +161,11 @@ def mock_dcnm_send(*args, **kwargs): assert instance.oper_state == "started" assert instance.enabled is True assert instance.started is True - assert isinstance(instance.response, dict) assert isinstance(instance.response_data, dict) - assert isinstance(instance.result, dict) - assert instance.response.get("MESSAGE", None) == "OK" - assert instance.response.get("RETURN_CODE", None) == 200 - assert instance.result.get("success", None) is True - assert instance.result.get("found", None) is True + assert instance.rest_send.response_current.get("MESSAGE", None) == "OK" + assert instance.rest_send.response_current.get("RETURN_CODE", None) == 200 + assert instance.rest_send.result_current.get("success", None) is True + assert instance.rest_send.result_current.get("found", None) is True with does_not_raise(): instance.filter = "vxlan" @@ -199,52 +179,52 @@ def mock_dcnm_send(*args, **kwargs): def test_controller_features_00050(monkeypatch, controller_features) -> None: """ - Classes and Methods + ### Classes and Methods + - ControllerFeatures() - __init__() - refresh() - Summary - - Verify refresh() failure behavior: - - RETURN_CODE is 500. + ### Summary + Verify refresh() failure behavior. RETURN_CODE is 500. + + ### Setup - Code Flow - Setup - ControllerFeatures() is instantiated - - dcnm_send() is patched to return the mocked controller response - ControllerFeatures().RestSend() is instantiated - ControllerFeatures().refresh() is called - responses_ControllerFeatures contains a dict with: - RETURN_CODE == 500 - Code Flow - Test + ### Test + - ControllerFeatures().refresh() is called - Expected Result + ### Expected Result + - ``ControllerResponseError`` is raised - Exception message matches expected """ method_name = inspect.stack()[0][3] key = f"{method_name}a" - PATCH_DCNM_SEND = "ansible_collections.cisco.dcnm.plugins." - PATCH_DCNM_SEND += "module_utils.common.rest_send.dcnm_send" - def responses(): yield responses_controller_features(key) - gen = ResponseGenerator(responses()) + gen_responses = ResponseGenerator(responses()) - def mock_dcnm_send(*args, **kwargs): - item = gen.next - return item + sender = Sender() + sender.ansible_module = MockAnsibleModule() + sender.gen = gen_responses + rest_send = RestSend(params) + rest_send.unit_test = True + rest_send.timeout = 1 + rest_send.response_handler = ResponseHandler() + rest_send.sender = sender with does_not_raise(): instance = controller_features - instance.rest_send = RestSend(MockAnsibleModule()) - instance.rest_send.unit_test = True - instance.rest_send.timeout = 1 - - monkeypatch.setattr(PATCH_DCNM_SEND, mock_dcnm_send) + instance.rest_send = rest_send match = r"ControllerFeatures\.refresh: Bad controller response:" with pytest.raises(ControllerResponseError, match=match): @@ -253,54 +233,57 @@ def mock_dcnm_send(*args, **kwargs): def test_controller_features_00060(monkeypatch, controller_features) -> None: """ - Classes and Methods + ### Classes and Methods + - ControllerFeatures() - __init__() - refresh() - Summary - - Verify refresh() failure due to unexpected controller response structure.: + ### Summary + Verify refresh() failure due to unexpected controller response structure. + - RETURN_CODE is 200. - DATA is missing. - Code Flow - Setup + ### Setup + - ControllerFeatures() is instantiated - - dcnm_send() is patched to return the mocked controller response - ControllerFeatures().RestSend() is instantiated - ControllerFeatures().refresh() is called - responses_ControllerFeatures contains a dict with: - RETURN_CODE == 200 - DATA is missing - Code Flow - Test + ### Test - ControllerFeatures().refresh() is called - Expected Result + ### Expected Result - ``ControllerResponseError`` is raised - Exception message matches expected """ method_name = inspect.stack()[0][3] key = f"{method_name}a" - PATCH_DCNM_SEND = "ansible_collections.cisco.dcnm.plugins." - PATCH_DCNM_SEND += "module_utils.common.rest_send.dcnm_send" + method_name = inspect.stack()[0][3] + key = f"{method_name}a" def responses(): yield responses_controller_features(key) - gen = ResponseGenerator(responses()) + gen_responses = ResponseGenerator(responses()) - def mock_dcnm_send(*args, **kwargs): - item = gen.next - return item + sender = Sender() + sender.ansible_module = MockAnsibleModule() + sender.gen = gen_responses + rest_send = RestSend(params) + rest_send.unit_test = True + rest_send.timeout = 1 + rest_send.response_handler = ResponseHandler() + rest_send.sender = sender with does_not_raise(): instance = controller_features - instance.rest_send = RestSend(MockAnsibleModule()) - instance.rest_send.unit_test = True - instance.rest_send.timeout = 1 - - monkeypatch.setattr(PATCH_DCNM_SEND, mock_dcnm_send) + instance.rest_send = rest_send match = r"ControllerFeatures\.refresh: " match += r"Controller response does not match expected structure:" @@ -315,8 +298,8 @@ def mock_dcnm_send(*args, **kwargs): @pytest.mark.parametrize( "value, does_raise, expected", [ - (RestSend(MockAnsibleModule()), False, does_not_raise()), - (ControllerFeatures(params), True, pytest.raises(TypeError, match=MATCH_00070)), + (RestSend(params), False, does_not_raise()), + (ControllerFeatures(), True, pytest.raises(TypeError, match=MATCH_00070)), (None, True, pytest.raises(TypeError, match=MATCH_00070)), ("foo", True, pytest.raises(TypeError, match=MATCH_00070)), (10, True, pytest.raises(TypeError, match=MATCH_00070)), diff --git a/tests/unit/module_utils/common/test_controller_version.py b/tests/unit/module_utils/common/test_controller_version.py index 3bae3c9bd..896cbb0c2 100644 --- a/tests/unit/module_utils/common/test_controller_version.py +++ b/tests/unit/module_utils/common/test_controller_version.py @@ -26,583 +26,804 @@ __copyright__ = "Copyright (c) 2024 Cisco and/or its affiliates." __author__ = "Allen Robel" -from typing import Any, Dict +import inspect import pytest -from ansible_collections.ansible.netcommon.tests.unit.modules.utils import \ - AnsibleFailJson +from ansible_collections.cisco.dcnm.plugins.module_utils.common.exceptions import \ + ControllerResponseError +from ansible_collections.cisco.dcnm.plugins.module_utils.common.response_handler import \ + ResponseHandler +from ansible_collections.cisco.dcnm.plugins.module_utils.common.rest_send_v2 import \ + RestSend +from ansible_collections.cisco.dcnm.plugins.module_utils.common.sender_file import \ + Sender from ansible_collections.cisco.dcnm.tests.unit.module_utils.common.common_utils import ( - controller_version_fixture, responses_controller_version) + MockAnsibleModule, ResponseGenerator, controller_version_fixture, + does_not_raise, params, responses_ep_version) -PATCH_MODULE_UTILS = "ansible_collections.cisco.dcnm.plugins.module_utils." -PATCH_COMMON = PATCH_MODULE_UTILS + "common." -DCNM_SEND_VERSION = PATCH_COMMON + "controller_version.dcnm_send" - -def test_common_version_00001(controller_version) -> None: +def test_controller_version_00000(controller_version) -> None: """ - Function - - __init__ + ### Classes and Methods + + - ``ControllerVersion()`` + - ``__init__`` - Test - - Class properties are initialized to expected values + ### Test + - Class properties are initialized to expected values. """ instance = controller_version - assert isinstance(instance.properties, dict) - assert instance.properties.get("response_data") is None - assert instance.properties.get("response") is None - assert instance.properties.get("result") is None + assert instance.class_name == "ControllerVersion" + assert instance.conversion.class_name == "ConversionUtils" + assert instance.ep_version.class_name == "EpVersion" + assert instance.response_data is None + assert instance.rest_send is None @pytest.mark.parametrize( "key, expected", [ - ("test_common_version_00002a", False), - ("test_common_version_00002b", True), - ("test_common_version_00002c", None), + ("test_controller_version_00100a", False), + ("test_controller_version_00100b", True), + ("test_controller_version_00100c", None), ], ) -def test_common_version_00002(monkeypatch, controller_version, key, expected) -> None: +def test_controller_version_00100(controller_version, key, expected) -> None: """ - Function - - refresh - - dev + ### Classes and Methods + + - ``ControllerVersion()`` + - ``refresh`` + - ``dev`` - Test - - dev returns True when the controller is a development version - - dev returns False when the controller is not a development version - - dev returns None otherwise + ### Test + + - ``dev`` returns True when the controller is a development version. + - ``dev`` returns False when the controller is not a development version. + - ``dev`` returns None otherwise. """ - def mock_dcnm_send_version(*args) -> Dict[str, Any]: - return responses_controller_version(key) + def responses(): + yield responses_ep_version(key) - monkeypatch.setattr(DCNM_SEND_VERSION, mock_dcnm_send_version) + gen_responses = ResponseGenerator(responses()) - instance = controller_version - instance.refresh() + sender = Sender() + sender.ansible_module = MockAnsibleModule() + sender.gen = gen_responses + rest_send = RestSend(params) + rest_send.response_handler = ResponseHandler() + rest_send.sender = sender + + with does_not_raise(): + instance = controller_version + instance.rest_send = rest_send + instance.refresh() assert instance.dev == expected @pytest.mark.parametrize( "key, expected", [ - ("test_common_version_00003a", "EASYFABRIC"), - ("test_common_version_00003b", None), + ("test_controller_version_00110a", "EASYFABRIC"), + ("test_controller_version_00110b", None), ], ) -def test_common_version_00003(monkeypatch, controller_version, key, expected) -> None: +def test_controller_version_00110(controller_version, key, expected) -> None: """ - Function - - refresh - - install + ### Classes and Methods + + - ``ControllerVersion()`` + - ``refresh`` + - ``install`` + + ### Test - Test - install returns expected values - Description + ### Description install returns: - - Value of the "install" key in the controller response, if present - - None, if the "install" key is absent from the controller response - Expected results: + - Value of the "install" key in the controller response, if present. + - None, if the "install" key is absent from the controller response. - 1. test_common_version_00003a == "EASYFABRIC" - 2. test_common_version_00003b is None + ### Expected result + + 1. test_controller_version_00110a == "EASYFABRIC" + 2. test_controller_version_00110b is None """ - def mock_dcnm_send_version(*args) -> Dict[str, Any]: - return responses_controller_version(key) + def responses(): + yield responses_ep_version(key) - monkeypatch.setattr(DCNM_SEND_VERSION, mock_dcnm_send_version) + gen_responses = ResponseGenerator(responses()) - instance = controller_version - instance.refresh() + sender = Sender() + sender.ansible_module = MockAnsibleModule() + sender.gen = gen_responses + rest_send = RestSend(params) + rest_send.response_handler = ResponseHandler() + rest_send.sender = sender + + with does_not_raise(): + instance = controller_version + instance.rest_send = rest_send + instance.refresh() assert instance.install == expected @pytest.mark.parametrize( "key, expected", [ - ("test_common_version_00004a", True), - ("test_common_version_00004b", False), - ("test_common_version_00004c", None), + ("test_controller_version_00120a", True), + ("test_controller_version_00120b", False), + ("test_controller_version_00120c", None), ], ) -def test_common_version_00004(monkeypatch, controller_version, key, expected) -> None: +def test_controller_version_00120(controller_version, key, expected) -> None: """ - Function - - refresh - - is_ha_enabled + ### Classes and Methods + + - ``ControllerVersion()`` + - ``refresh`` + - ``is_ha_enabled`` + + ### Test - Test - is_ha_enabled returns expected values - Description - is_ha_enabled returns: - - True, if "isHaEnabled" key in the controller response == "true" - - False, if "isHaEnabled" key in the controller response == "false" - - None, if "isHaEnabled" key is absent from the controller response + ### Description - Expected results: + ``is_ha_enabled`` returns: - 1. test_common_version_00004a is True - 2. test_common_version_00004b is False - 3. test_common_version_00004c is None + - True, if "isHaEnabled" key in the controller response == "true". + - False, if "isHaEnabled" key in the controller response == "false". + - None, if "isHaEnabled" key is absent from the controller response. + + Expected result + + 1. test_controller_version_00120a is True + 2. test_controller_version_00120b is False + 3. test_controller_version_00120c is None """ - def mock_dcnm_send_version(*args) -> Dict[str, Any]: - return responses_controller_version(key) + def responses(): + yield responses_ep_version(key) - monkeypatch.setattr(DCNM_SEND_VERSION, mock_dcnm_send_version) + gen_responses = ResponseGenerator(responses()) - instance = controller_version - instance.refresh() + sender = Sender() + sender.ansible_module = MockAnsibleModule() + sender.gen = gen_responses + rest_send = RestSend(params) + rest_send.response_handler = ResponseHandler() + rest_send.sender = sender + + with does_not_raise(): + instance = controller_version + instance.rest_send = rest_send + instance.refresh() assert instance.is_ha_enabled == expected @pytest.mark.parametrize( "key, expected", [ - ("test_common_version_00005a", True), - ("test_common_version_00005b", False), - ("test_common_version_00005c", None), + ("test_controller_version_00130a", True), + ("test_controller_version_00130b", False), + ("test_controller_version_00130c", None), ], ) -def test_common_version_00005(monkeypatch, controller_version, key, expected) -> None: +def test_controller_version_00130(controller_version, key, expected) -> None: """ - Function - - refresh - - is_media_controller + ### Classes and Methods + + - ``ControllerVersion()`` + - ``refresh`` + - ``is_media_controller`` + + ### Test + - ``is_media_controller`` returns expected values. + + ### Description - Test - - is_media_controller returns expected values + ``is_media_controller`` returns: - Description - is_media_controller returns: - - True, if "isMediaController" key in the controller response == "true" - - False, if "isMediaController" key in the controller response == "false" - - None, if "isMediaController" key is absent from the controller response + - True, if "isMediaController" key in the controller response == "true". + - False, if "isMediaController" key in the controller response == "false". + - None, if "isMediaController" key is absent from the controller response. - Expected results: + ### Expected result - 1. test_common_version_00005a is True - 2. test_common_version_00005b is False - 3. test_common_version_00005c is None + 1. test_controller_version_00130a is True. + 2. test_controller_version_00130b is False. + 3. test_controller_version_00130c is None. """ - def mock_dcnm_send_version(*args) -> Dict[str, Any]: - return responses_controller_version(key) + def responses(): + yield responses_ep_version(key) - monkeypatch.setattr(DCNM_SEND_VERSION, mock_dcnm_send_version) + gen_responses = ResponseGenerator(responses()) - instance = controller_version - instance.refresh() + sender = Sender() + sender.ansible_module = MockAnsibleModule() + sender.gen = gen_responses + rest_send = RestSend(params) + rest_send.response_handler = ResponseHandler() + rest_send.sender = sender + + with does_not_raise(): + instance = controller_version + instance.rest_send = rest_send + instance.refresh() assert instance.is_media_controller == expected @pytest.mark.parametrize( "key, expected", [ - ("test_common_version_00006a", True), - ("test_common_version_00006b", False), - ("test_common_version_00006c", None), + ("test_controller_version_00140a", True), + ("test_controller_version_00140b", False), + ("test_controller_version_00140c", None), ], ) -def test_common_version_00006(monkeypatch, controller_version, key, expected) -> None: +def test_controller_version_00140(controller_version, key, expected) -> None: """ - Function - - refresh - - is_upgrade_inprogress + ### Classes and Methods - Test - - is_upgrade_inprogress returns expected values + - ``ControllerVersion()`` + - ``refresh`` + - ``is_upgrade_inprogress`` - Description - is_upgrade_inprogress returns: - - True, if "is_upgrade_inprogress" key in the controller response == "true" - - False, if "is_upgrade_inprogress" key in the controller response == "false" - - None, if "is_upgrade_inprogress" key is absent from the controller response + ### Test + - ``is_upgrade_inprogress`` returns expected values. - Expected results: + ### Description - 1. test_common_version_00006a is True - 2. test_common_version_00006b is False - 3. test_common_version_00006c is None + ``is_upgrade_inprogress`` returns: + - True, if "is_upgrade_inprogress" key in the controller + response == "true". + - False, if "is_upgrade_inprogress" key in the controller + response == "false". + - None, if "is_upgrade_inprogress" key is absent from the + controller response. + + ### Expected results + + 1. test_controller_version_00140a is True. + 2. test_controller_version_00140b is False. + 3. test_controller_version_00140c is None. """ - def mock_dcnm_send_version(*args) -> Dict[str, Any]: - return responses_controller_version(key) + def responses(): + yield responses_ep_version(key) - monkeypatch.setattr(DCNM_SEND_VERSION, mock_dcnm_send_version) + gen_responses = ResponseGenerator(responses()) - instance = controller_version - instance.refresh() + sender = Sender() + sender.ansible_module = MockAnsibleModule() + sender.gen = gen_responses + rest_send = RestSend(params) + rest_send.response_handler = ResponseHandler() + rest_send.sender = sender + + with does_not_raise(): + instance = controller_version + instance.rest_send = rest_send + instance.refresh() assert instance.is_upgrade_inprogress == expected -def test_common_version_00007(monkeypatch, controller_version) -> None: +def test_controller_version_00150(controller_version) -> None: """ - Function - - refresh - - response_data + ### Classes and Methods + + - ``ControllerVersion()`` + - ``refresh`` + - ``response_data`` + + ### Test + + - ``response_data`` returns the "DATA" key in the controller response. - Test - - response_data returns the "DATA" key in the controller response + ### Description - Description - response_data returns the "DATA" key in the controller response, - which is a dictionary of key-value pairs. - fail_json is called if the "DATA" key is absent. + - ``response_data`` returns the "DATA" key in the controller response, + which is a dictionary of key-value pairs. + - ``ValueError`` is raised if the "DATA" key is absent. - Expected results: + ### Expected results - 1. test_common_version_00007a, ControllerVersion.response_data == type(dict) + 1. test_controller_version_00150a + ControllerVersion.response_data == type(dict) """ + method_name = inspect.stack()[0][3] + key = f"{method_name}a" - def mock_dcnm_send_version(*args) -> Dict[str, Any]: - key = "test_common_version_00007a" - return responses_controller_version(key) + def responses(): + yield responses_ep_version(key) - monkeypatch.setattr(DCNM_SEND_VERSION, mock_dcnm_send_version) + gen_responses = ResponseGenerator(responses()) - instance = controller_version - instance.refresh() + sender = Sender() + sender.ansible_module = MockAnsibleModule() + sender.gen = gen_responses + rest_send = RestSend(params) + rest_send.response_handler = ResponseHandler() + rest_send.sender = sender + + with does_not_raise(): + instance = controller_version + instance.rest_send = rest_send + instance.refresh() assert isinstance(instance.response_data, dict) -def test_common_version_00008(monkeypatch, controller_version) -> None: +def test_controller_version_00160(controller_version) -> None: """ - Function - - refresh + ### Classes and Methods - Test - - fail_json is called because the "DATA" key is absent + - ``ControllerVersion()`` + - ``refresh`` + - ``response_data`` - Description - response_data returns the "DATA" key in the controller response, - which is a dictionary of key-value pairs. - fail_json is called if the "DATA" key is absent. + ### Test + + - ValueError is raised because the "DATA" key is absent + + ### Description + - ``response_data`` returns the "DATA" key in the controller response, + which is a dictionary of key-value pairs. + - ``ValueError`` is raised if the "DATA" key is absent. """ + method_name = inspect.stack()[0][3] + key = f"{method_name}a" - def mock_dcnm_send_version(*args) -> Dict[str, Any]: - key = "test_common_version_00008a" - return responses_controller_version(key) + def responses(): + yield responses_ep_version(key) - monkeypatch.setattr(DCNM_SEND_VERSION, mock_dcnm_send_version) + gen_responses = ResponseGenerator(responses()) - instance = controller_version - with pytest.raises(AnsibleFailJson): + sender = Sender() + sender.ansible_module = MockAnsibleModule() + sender.gen = gen_responses + rest_send = RestSend(params) + rest_send.response_handler = ResponseHandler() + rest_send.sender = sender + + with does_not_raise(): + instance = controller_version + instance.rest_send = rest_send + + match = r"ControllerVersion\.refresh\(\) failed:\s+" + match += r"response does not contain DATA key\.\s+" + match += r"Controller response:.*" + + with pytest.raises(ValueError, match=match): instance.refresh() -def test_common_version_00009(monkeypatch, controller_version) -> None: +def test_controller_version_00170(controller_version) -> None: """ - Function - - refresh - - result + ### Classes and Methods - Test - - result returns expected values + - ``ControllerVersion()`` + - ``refresh`` + - ``RestSend`` + - ``result_current`` - Description - result returns the result of its superclass - method ImageUpgradeCommon._handle_response() + ### Test + - RestSend.result_current returns expected values. - Expected results: + ### Expected results - Since a 200 response with "message" key == "OK" is received we expect result to return {'found': True, 'success': True} """ + method_name = inspect.stack()[0][3] + key = f"{method_name}a" - def mock_dcnm_send_version(*args) -> Dict[str, Any]: - key = "test_common_version_00009a" - return responses_controller_version(key) + def responses(): + yield responses_ep_version(key) - monkeypatch.setattr(DCNM_SEND_VERSION, mock_dcnm_send_version) + gen_responses = ResponseGenerator(responses()) - instance = controller_version - instance.refresh() - assert instance.result == {"found": True, "success": True} + sender = Sender() + sender.ansible_module = MockAnsibleModule() + sender.gen = gen_responses + rest_send = RestSend(params) + rest_send.response_handler = ResponseHandler() + rest_send.sender = sender + with does_not_raise(): + instance = controller_version + instance.rest_send = rest_send + instance.refresh() + assert instance.rest_send.result_current == {"found": True, "success": True} -def test_common_version_00010(monkeypatch, controller_version) -> None: + +def test_controller_version_00180(controller_version) -> None: """ - Function - - refresh - - result + ### Classes and Methods - Test - - result returns expected values + - ``ControllerVersion()`` + - ``refresh`` + - ``RestSend`` + - ``result_current`` - Description - result returns the result of its superclass - method ImageUpgradeCommon._handle_response() + ### Test + - RestSend.result_current returns expected values. - Expected results: + ### Expected results - Since a 404 response with "message" key == "Not Found" is received - we expect result to return {'found': False, 'success': True} + ``ControllerResponseError`` is raised. """ + method_name = inspect.stack()[0][3] + key = f"{method_name}a" - def mock_dcnm_send_version(*args) -> Dict[str, Any]: - key = "test_common_version_00010a" - return responses_controller_version(key) + def responses(): + yield responses_ep_version(key) - monkeypatch.setattr(DCNM_SEND_VERSION, mock_dcnm_send_version) + gen_responses = ResponseGenerator(responses()) - instance = controller_version - with pytest.raises(AnsibleFailJson): + sender = Sender() + sender.ansible_module = MockAnsibleModule() + sender.gen = gen_responses + rest_send = RestSend(params) + rest_send.response_handler = ResponseHandler() + rest_send.sender = sender + + with does_not_raise(): + instance = controller_version + instance.rest_send = rest_send + + match = r"ControllerVersion\.refresh:\s+" + match += r"failed:.*" + + with pytest.raises(ControllerResponseError, match=match): instance.refresh() - assert instance.result == {"found": False, "success": True} + assert instance.rest_send.result_current == {"found": False, "success": True} -def test_common_version_00011(monkeypatch, controller_version) -> None: +def test_controller_version_00190(controller_version) -> None: """ - Function - - refresh - - result + ### Classes and Methods - Test - - result returns expected values - - fail_json is called + - ``ControllerVersion()`` + - ``refresh`` + - ``RestSend`` + - ``result_current`` - Description - result returns the result of its superclass - method ImageUpgradeCommon._handle_response() + ### Test + - RestSend.result_current returns expected values. - Expected results: + ### Expected results - Since a 500 response is received (MESSAGE key ignored) we expect result to return {'found': False, 'success': False} """ + method_name = inspect.stack()[0][3] + key = f"{method_name}a" - def mock_dcnm_send_version(*args) -> Dict[str, Any]: - key = "test_common_version_00011a" - return responses_controller_version(key) + def responses(): + yield responses_ep_version(key) - monkeypatch.setattr(DCNM_SEND_VERSION, mock_dcnm_send_version) + gen_responses = ResponseGenerator(responses()) - instance = controller_version - with pytest.raises(AnsibleFailJson): + sender = Sender() + sender.ansible_module = MockAnsibleModule() + sender.gen = gen_responses + rest_send = RestSend(params) + rest_send.unit_test = True + rest_send.timeout = 1 + rest_send.response_handler = ResponseHandler() + rest_send.sender = sender + + with does_not_raise(): + instance = controller_version + instance.rest_send = rest_send + + match = r"ControllerVersion\.refresh:\s+" + match += r"failed:.*" + + with pytest.raises(ControllerResponseError, match=match): instance.refresh() - assert instance.result == {"found": False, "success": False} + assert instance.rest_send.result_current == {"found": False, "success": False} @pytest.mark.parametrize( "key, expected", - [("test_common_version_00012a", "LAN"), ("test_common_version_00012b", None)], + [ + ("test_controller_version_00200a", "LAN"), + ("test_controller_version_00200b", None), + ], ) -def test_common_version_00012(monkeypatch, controller_version, key, expected) -> None: +def test_controller_version_00200(controller_version, key, expected) -> None: """ - Function - - refresh - - mode + ### Classes and Methods - Test - - mode returns expected values + - ``ControllerVersion()`` + - ``refresh`` + - ``mode`` - Description - mode returns: - - its value, if the "mode" key is present in the controller response - - None, if the "mode" key is absent from the controller response + ### Test - Expected results: + - ``mode`` returns expected values. - 1. test_common_version_00012a == "LAN" - 2. test_common_version_00012b is None + ### Description + ``mode`` returns: + + - Its value, if the "mode" key is present in the controller response. + - None, if the "mode" key is absent from the controller response. + + ### Expected results + + 1. test_controller_version_00200a == "LAN" + 2. test_controller_version_00200b is None """ - def mock_dcnm_send_version(*args) -> Dict[str, Any]: - return responses_controller_version(key) + def responses(): + yield responses_ep_version(key) - monkeypatch.setattr(DCNM_SEND_VERSION, mock_dcnm_send_version) + gen_responses = ResponseGenerator(responses()) - instance = controller_version - instance.refresh() + sender = Sender() + sender.ansible_module = MockAnsibleModule() + sender.gen = gen_responses + rest_send = RestSend(params) + rest_send.response_handler = ResponseHandler() + rest_send.sender = sender + + with does_not_raise(): + instance = controller_version + instance.rest_send = rest_send + instance.refresh() assert instance.mode == expected @pytest.mark.parametrize( "key, expected", [ - ("test_common_version_00013a", "foo-uuid"), - ("test_common_version_00013b", None), + ("test_controller_version_00210a", "foo-uuid"), + ("test_controller_version_00210b", None), ], ) -def test_common_version_00013(monkeypatch, controller_version, key, expected) -> None: +def test_controller_version_00210(controller_version, key, expected) -> None: """ - Function - - refresh - - uuid + ### Classes and Methods - Test - - uuid returns expected values + - ``ControllerVersion()`` + - ``refresh`` + - ``uuid`` + + ### Test + + - ``uuid`` returns expected values. + + ### Description - Description uuid returns: - - its value, if the "uuid" key is present in the controller response - - None, if the "uuid" key is absent from the controller response - Expected results: + - Its value, if the "uuid" key is present in the controller response. + - None, if the "uuid" key is absent from the controller response. - 1. test_common_version_00013a == "foo-uuid" - 2. test_common_version_00013b is None + ### Expected result + + 1. test_controller_version_00210a == "foo-uuid" + 2. test_controller_version_00210b is None """ - def mock_dcnm_send_version(*args) -> Dict[str, Any]: - return responses_controller_version(key) + def responses(): + yield responses_ep_version(key) - monkeypatch.setattr(DCNM_SEND_VERSION, mock_dcnm_send_version) + gen_responses = ResponseGenerator(responses()) - instance = controller_version - instance.refresh() + sender = Sender() + sender.ansible_module = MockAnsibleModule() + sender.gen = gen_responses + rest_send = RestSend(params) + rest_send.response_handler = ResponseHandler() + rest_send.sender = sender + + with does_not_raise(): + instance = controller_version + instance.rest_send = rest_send + instance.refresh() assert instance.uuid == expected @pytest.mark.parametrize( "key, expected", [ - ("test_common_version_00014a", "12.1.3b"), - ("test_common_version_00014b", None), + ("test_controller_version_00220a", "12.1.3b"), + ("test_controller_version_00220b", None), ], ) -def test_common_version_00014(monkeypatch, controller_version, key, expected) -> None: +def test_controller_version_00220(controller_version, key, expected) -> None: """ - Function - - refresh - - version + ### Classes and Methods + + - ``ControllerVersion()`` + - ``refresh`` + - ``version`` - Test - - version returns expected values + ### Test - Description - mode returns: - - its value, if the "version" key is present in the controller response - - None, if the "version" key is absent from the controller response + - ``version`` returns expected values. - Expected results: + ### Description + ``version`` returns: - 1. test_common_version_00014a == "12.1.3b" - 2. test_common_version_00014b is None + - Its value, if the "version" key is present in the controller response. + - None, if the "version" key is absent from the controller response. + + ### Expected result + + 1. test_controller_version_00220a == "12.1.3b" + 2. test_controller_version_00220b is None """ - def mock_dcnm_send_version(*args) -> Dict[str, Any]: - return responses_controller_version(key) + def responses(): + yield responses_ep_version(key) - monkeypatch.setattr(DCNM_SEND_VERSION, mock_dcnm_send_version) + gen_responses = ResponseGenerator(responses()) - instance = controller_version - instance.refresh() + sender = Sender() + sender.ansible_module = MockAnsibleModule() + sender.gen = gen_responses + rest_send = RestSend(params) + rest_send.response_handler = ResponseHandler() + rest_send.sender = sender + + with does_not_raise(): + instance = controller_version + instance.rest_send = rest_send + instance.refresh() assert instance.version == expected @pytest.mark.parametrize( "key, expected", [ - ("test_common_version_00015a", "12"), - ("test_common_version_00015b", None), + ("test_controller_version_00230a", "12"), + ("test_controller_version_00230b", None), ], ) -def test_common_version_00015(monkeypatch, controller_version, key, expected) -> None: +def test_controller_version_00230(controller_version, key, expected) -> None: """ - Function - - refresh - - version_major + ### Classes and Methods + + - ``ControllerVersion()`` + - ``refresh`` + - ``version_major`` - Test - - version_major returns expected values + ### Test + - ``version_major`` returns expected values. - Description - version_major returns the major version of the controller + ### Description + ``version_major`` returns the major version of the controller. It derives this from the "version" key in the controller response - by splitting the string on "." and returning the first element + by splitting the string on "." and returning the first element. - Expected results: + ### Expected result - 1. test_common_version_00015a == "12" - 2. test_common_version_00015b is None + 1. test_controller_version_00230a == "12" + 2. test_controller_version_00230b is None """ - def mock_dcnm_send_version(*args) -> Dict[str, Any]: - return responses_controller_version(key) + def responses(): + yield responses_ep_version(key) - monkeypatch.setattr(DCNM_SEND_VERSION, mock_dcnm_send_version) + gen_responses = ResponseGenerator(responses()) - instance = controller_version - instance.refresh() + sender = Sender() + sender.ansible_module = MockAnsibleModule() + sender.gen = gen_responses + rest_send = RestSend(params) + rest_send.response_handler = ResponseHandler() + rest_send.sender = sender + + with does_not_raise(): + instance = controller_version + instance.rest_send = rest_send + instance.refresh() assert instance.version_major == expected @pytest.mark.parametrize( "key, expected", [ - ("test_common_version_00016a", "1"), - ("test_common_version_00016b", None), + ("test_controller_version_00240a", "1"), + ("test_controller_version_00240b", None), ], ) -def test_common_version_00016(monkeypatch, controller_version, key, expected) -> None: +def test_controller_version_00240(controller_version, key, expected) -> None: """ - Function - - refresh - - version_minor + ### Classes and Methods + + - ``ControllerVersion()`` + - ``refresh`` + - ``version_minor`` + + ### Test - Test - - version_minor returns expected values + - ``version_minor`` returns expected values. - Description - version_minor returns the minor version of the controller + ### Description + ``version_minor`` returns the minor version of the controller. It derives this from the "version" key in the controller response - by splitting the string on "." and returning the second element + by splitting the string on "." and returning the second element. - Expected results: + ### Expected result - 1. test_common_version_00016a == "1" - 2. test_common_version_00016b is None + 1. test_controller_version_00240a == "1" + 2. test_controller_version_00240b is None """ - def mock_dcnm_send_version(*args) -> Dict[str, Any]: - return responses_controller_version(key) + def responses(): + yield responses_ep_version(key) - monkeypatch.setattr(DCNM_SEND_VERSION, mock_dcnm_send_version) + gen_responses = ResponseGenerator(responses()) - instance = controller_version - instance.refresh() + sender = Sender() + sender.ansible_module = MockAnsibleModule() + sender.gen = gen_responses + rest_send = RestSend(params) + rest_send.response_handler = ResponseHandler() + rest_send.sender = sender + + with does_not_raise(): + instance = controller_version + instance.rest_send = rest_send + instance.refresh() assert instance.version_minor == expected @pytest.mark.parametrize( "key, expected", [ - ("test_common_version_00017a", "3b"), - ("test_common_version_00017b", None), + ("test_controller_version_00250a", "3b"), + ("test_controller_version_00250b", None), ], ) -def test_common_version_00017(monkeypatch, controller_version, key, expected) -> None: +def test_controller_version_00250(controller_version, key, expected) -> None: """ - Function - - refresh - - version_patch + ### Classes and Methods - Test - - version_patch returns expected values + - ``ControllerVersion()`` + - ``refresh`` + - ``version_patch`` - Description - version_patch returns the patch version of the controller + ### Test + + - ``version_patch`` returns expected values. + + ### Description + ``version_patch`` returns the patch version of the controller. It derives this from the "version" key in the controller response - by splitting the string on "." and returning the third element + by splitting the string on "." and returning the third element. - Expected results: + ### Expected result - 1. test_common_version_00017a == "3b" - 2. test_common_version_00017b is None + 1. test_controller_version_00250a == "3b" + 2. test_controller_version_00250b is None """ - def mock_dcnm_send_version(*args) -> Dict[str, Any]: - return responses_controller_version(key) + def responses(): + yield responses_ep_version(key) - monkeypatch.setattr(DCNM_SEND_VERSION, mock_dcnm_send_version) + gen_responses = ResponseGenerator(responses()) - instance = controller_version - instance.refresh() + sender = Sender() + sender.ansible_module = MockAnsibleModule() + sender.gen = gen_responses + rest_send = RestSend(params) + rest_send.response_handler = ResponseHandler() + rest_send.sender = sender + + with does_not_raise(): + instance = controller_version + instance.rest_send = rest_send + instance.refresh() assert instance.version_patch == expected diff --git a/tests/unit/module_utils/common/test_image_policies.py b/tests/unit/module_utils/common/test_image_policies.py new file mode 100644 index 000000000..b7a8820cd --- /dev/null +++ b/tests/unit/module_utils/common/test_image_policies.py @@ -0,0 +1,710 @@ +# 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 + +__metaclass__ = type + +__copyright__ = "Copyright (c) 2024 Cisco and/or its affiliates." +__author__ = "Allen Robel" + +import inspect + +import pytest +from ansible_collections.cisco.dcnm.plugins.module_utils.common.api.v1.imagemanagement.rest.policymgnt.policymgnt import \ + EpPolicies +from ansible_collections.cisco.dcnm.plugins.module_utils.common.exceptions import \ + ControllerResponseError +from ansible_collections.cisco.dcnm.plugins.module_utils.common.response_handler import \ + ResponseHandler +from ansible_collections.cisco.dcnm.plugins.module_utils.common.rest_send_v2 import \ + RestSend +from ansible_collections.cisco.dcnm.plugins.module_utils.common.results import \ + Results +from ansible_collections.cisco.dcnm.plugins.module_utils.common.sender_file import \ + Sender +from ansible_collections.cisco.dcnm.tests.unit.module_utils.common.common_utils import ( + MockAnsibleModule, ResponseGenerator, does_not_raise, + image_policies_fixture, params, responses_ep_policies) + + +def test_image_policies_00000(image_policies) -> None: + """ + ### Classes and Methods + + - ``ImagePolicies()`` + - ``__init__`` + + ### Test + + - Class attributes and properties are initialized to expected values. + """ + with does_not_raise(): + instance = image_policies + assert instance.all_policies == {} + assert instance.class_name == "ImagePolicies" + assert instance.conversion.class_name == "ConversionUtils" + assert instance.data == {} + assert instance.ep_policies.class_name == "EpPolicies" + assert instance.policy_name is None + assert instance.response_data == {} + assert instance.results is None + assert instance.rest_send is None + + +def test_image_policies_00100(image_policies) -> None: + """ + ### Classes and Methods + + - ``ImagePolicies()`` + - ``refresh`` + - ``policy_name`` + + ### Summary + Verify that ``refresh`` returns image policy info and that the filtered + properties associated with ``policy_name`` are the expected values. + + ### Test + + - properties for ``policy_name`` are set to reflect the response from + the controller. + - 200 RETURN_CODE. + - Exception is not raised. + """ + method_name = inspect.stack()[0][3] + key = f"{method_name}a" + + def responses(): + yield responses_ep_policies(key) + + gen_responses = ResponseGenerator(responses()) + + sender = Sender() + sender.ansible_module = MockAnsibleModule() + sender.gen = gen_responses + rest_send = RestSend(params) + rest_send.response_handler = ResponseHandler() + rest_send.sender = sender + + with does_not_raise(): + instance = image_policies + instance.rest_send = rest_send + instance.results = Results() + instance.refresh() + + instance.policy_name = "KR5M" + assert instance.agnostic is False + assert instance.description == "10.2.(5) with EPLD" + assert instance.epld_image_name == "n9000-epld.10.2.5.M.img" + assert instance.image_name == "nxos64-cs.10.2.5.M.bin" + assert instance.nxos_version == "10.2.5_nxos64-cs_64bit" + assert instance.package_name is None + assert instance.platform == "N9K/N3K" + assert instance.platform_policies is None + assert instance.policy_name == "KR5M" + assert instance.policy_type == "PLATFORM" + assert instance.ref_count == 10 + assert instance.rpm_images is None + + +def test_image_policies_00200(image_policies) -> None: + """ + ### Classes and Methods + + - ``ImagePolicies()`` + - ``refresh`` + - ``rest_send.result_current`` + + ### Summary + - ``Imagepolicies.rest_send.result`` contains expected key/values on 200 + response from endpoint. + """ + method_name = inspect.stack()[0][3] + key = f"{method_name}a" + + def responses(): + yield responses_ep_policies(key) + + gen_responses = ResponseGenerator(responses()) + + sender = Sender() + sender.ansible_module = MockAnsibleModule() + sender.gen = gen_responses + rest_send = RestSend(params) + rest_send.response_handler = ResponseHandler() + rest_send.sender = sender + + with does_not_raise(): + instance = image_policies + instance.rest_send = rest_send + instance.results = Results() + instance.refresh() + + assert isinstance(instance.rest_send.result_current, dict) + assert instance.rest_send.result_current.get("found") is True + assert instance.rest_send.result_current.get("success") is True + + +def test_image_policies_00300(image_policies) -> None: + """ + ### Classes and Methods + + - ``ImagePolicies()`` + - ``refresh`` + + ### Summary + Verify that ``ControllerResponseError`` is raised when the controller + response RETURN_CODE == 404. + + ### Test + + - ``ControllerResponseError`` is called on response with RETURN_CODE == 404. + """ + method_name = inspect.stack()[0][3] + key = f"{method_name}a" + + def responses(): + yield responses_ep_policies(key) + + gen_responses = ResponseGenerator(responses()) + + sender = Sender() + sender.ansible_module = MockAnsibleModule() + sender.gen = gen_responses + rest_send = RestSend(params) + rest_send.response_handler = ResponseHandler() + rest_send.sender = sender + + with does_not_raise(): + instance = image_policies + instance.rest_send = rest_send + instance.results = Results() + + match = r"ImagePolicies\.refresh:\s+" + match += r"Bad response when retrieving image policy information\s+" + match += r"from the controller\." + + with pytest.raises(ControllerResponseError, match=match): + instance.refresh() + + +def test_image_policies_00400(image_policies) -> None: + """ + ### Classes and Methods + + - ``ImagePolicies()`` + - ``refresh`` + + ### Summary + Verify that ``ControllerResponseError`` is raised when the controller + response contains an empty DATA key. + + ### Test + + - ``ControllerResponseError`` is raised on RETURN_CODE == 200 with empty + DATA key. + """ + method_name = inspect.stack()[0][3] + key = f"{method_name}a" + + def responses(): + yield responses_ep_policies(key) + + gen_responses = ResponseGenerator(responses()) + + sender = Sender() + sender.ansible_module = MockAnsibleModule() + sender.gen = gen_responses + rest_send = RestSend(params) + rest_send.response_handler = ResponseHandler() + rest_send.sender = sender + + with does_not_raise(): + instance = image_policies + instance.rest_send = rest_send + instance.results = Results() + + match = r"ImagePolicies\.refresh:\s+" + match += r"Bad response when retrieving image policy information\s+" + match += r"from the controller\." + + with pytest.raises(ControllerResponseError, match=match): + instance.refresh() + + +def test_image_policies_00500(image_policies) -> None: + """ + ### Classes and Methods + + - ``ImagePolicies()`` + - ``refresh`` + + ### Summary + Verify that exceptions are not raised on controller response with + RETURN_CODE == 200 containing ``DATA.lastOperDataObject`` with + length == 0. + + ### Test + + - Exception is not raised for ``DATA.lastOperDataObject`` length == 0. + - RETURN_CODE == 200. + + ### Discussion + dcnm_image_policy classes ``ImagePolicyCreate`` and + ``ImagePolicyCreateBulk`` both call ``ImagePolicies.refresh()`` when + checking if the image policies they are creating already exist on the + controller. Hence, we cannot raise an exception when the length of + ``DATA.lastOperDataObject`` is zero. + """ + method_name = inspect.stack()[0][3] + key = f"{method_name}a" + + def responses(): + yield responses_ep_policies(key) + + gen_responses = ResponseGenerator(responses()) + + sender = Sender() + sender.ansible_module = MockAnsibleModule() + sender.gen = gen_responses + rest_send = RestSend(params) + rest_send.response_handler = ResponseHandler() + rest_send.sender = sender + + with does_not_raise(): + instance = image_policies + instance.rest_send = rest_send + instance.results = Results() + instance.refresh() + + assert ( + instance.rest_send.response_current.get("DATA").get("lastOperDataObject") == [] + ) + assert instance.rest_send.response_current.get("RETURN_CODE") == 200 + + +def test_image_policies_00600(image_policies) -> None: + """ + ### Classes and Methods + + - ``ImagePolicies()`` + - ``refresh`` + - ``policy_name`` + + ### Summary + Verify when ``policy_name`` is set to a policy that does not exist on the + controller, ``policy`` returns None. + + ### Setup + + - ``policy_name`` is set to a policy that does not exist on + the controller. + + ### Test + + - ``policy`` returns None. + + """ + method_name = inspect.stack()[0][3] + key = f"{method_name}a" + + def responses(): + yield responses_ep_policies(key) + + gen_responses = ResponseGenerator(responses()) + + sender = Sender() + sender.ansible_module = MockAnsibleModule() + sender.gen = gen_responses + rest_send = RestSend(params) + rest_send.response_handler = ResponseHandler() + rest_send.sender = sender + + with does_not_raise(): + instance = image_policies + instance.rest_send = rest_send + instance.results = Results() + instance.refresh() + image_policies.policy_name = "FOO" + + assert image_policies.policy is None + + +def test_image_policies_00700(image_policies) -> None: + """ + ### Classes and Methods + + - ``ImagePolicies()`` + - ``refresh`` + + ### Summary + Verify that ``ValueError`` is raised when the controller response + is missing the "policyName" key. + + ### Test + + - ``ValueError`` is raised on response with missing "policyName" key. + + ### NOTES + + - This is to cover a check in ``ImagePolicies.refresh()``. + - This scenario should happen only with a controller bug or API change. + """ + method_name = inspect.stack()[0][3] + key = f"{method_name}a" + + def responses(): + yield responses_ep_policies(key) + + gen_responses = ResponseGenerator(responses()) + + sender = Sender() + sender.ansible_module = MockAnsibleModule() + sender.gen = gen_responses + rest_send = RestSend(params) + rest_send.response_handler = ResponseHandler() + rest_send.sender = sender + + with does_not_raise(): + instance = image_policies + instance.rest_send = rest_send + instance.results = Results() + + match = r"ImagePolicies\.refresh:\s+" + match += r"Cannot parse policy information from the controller\." + + instance = image_policies + with pytest.raises(ValueError, match=match): + instance.refresh() + + +def test_image_policies_00800(image_policies) -> None: + """ + ### Classes and Methods + + - ``ImagePolicies()`` + - ``refresh`` + + ### Summary + Verify that ``ControllerResponseError`` is raised when + ``RestSend().result_current`` indicates an unsuccessful response. + + ### Test + + - ``ControllerResponseError`` is raised when + ``RestSend().result_current["success"]`` is False. + + """ + method_name = inspect.stack()[0][3] + key = f"{method_name}a" + + def responses(): + yield responses_ep_policies(key) + + gen_responses = ResponseGenerator(responses()) + + sender = Sender() + sender.ansible_module = MockAnsibleModule() + sender.gen = gen_responses + rest_send = RestSend(params) + rest_send.unit_test = True + rest_send.timeout = 1 + rest_send.response_handler = ResponseHandler() + rest_send.sender = sender + + with does_not_raise(): + instance = image_policies + instance.rest_send = rest_send + instance.results = Results() + + match = r"ImagePolicies\.refresh:\s+" + match += r"Failed to retrieve image policy information\s+" + match += r"from the controller\.\s+" + match += r"Controller response:.*" + + with pytest.raises(ControllerResponseError, match=match): + instance.refresh() + + +def test_image_policies_02000(image_policies) -> None: + """ + ### Classes and Methods + + - ``ImagePolicies()`` + - ``_get()`` + + ### Summary + Verify that ``ValueError`` is raised when ``_get()`` is called prior to + setting ``policy_name``. + + ### Test + + - ``ValueError`` is raised when _get() is called prior to setting + ``policy_name``. + - Error messages matches expectation. + """ + + def responses(): + yield None + + gen_responses = ResponseGenerator(responses()) + + sender = Sender() + sender.ansible_module = MockAnsibleModule() + sender.gen = gen_responses + rest_send = RestSend(params) + rest_send.response_handler = ResponseHandler() + rest_send.sender = sender + + with does_not_raise(): + instance = image_policies + instance.rest_send = rest_send + instance.results = Results() + + match = "ImagePolicies._get: instance.policy_name must be " + match += "set before accessing property imageName." + + with pytest.raises(ValueError, match=match): + instance._get("imageName") # pylint: disable=protected-access + + +def test_image_policies_02100(image_policies) -> None: + """ + ### Classes and Methods + + - ``ImagePolicies()`` + - ``_get()`` + + ### Summary + Verify that ``ValueError`` is raised when ``_get`` is called + with an argument that does not match an item in the response data + for the policy_name returned by the controller. + + ### Setup + + - ``refresh`` is called and retrieves a response from the + controller containing information for image policy KR5M. + - ``policy_name`` is set to KR5M. + + ### Test + + - ``ValueError`` is raised when ``_get()`` is called with a + parameter name "FOO" that does not match any key in the + response data for the ``policy_name``. + - Error message matches expectation. + """ + method_name = inspect.stack()[0][3] + key = f"{method_name}a" + + def responses(): + yield responses_ep_policies(key) + + gen_responses = ResponseGenerator(responses()) + + sender = Sender() + sender.ansible_module = MockAnsibleModule() + sender.gen = gen_responses + rest_send = RestSend(params) + rest_send.unit_test = True + rest_send.timeout = 1 + rest_send.response_handler = ResponseHandler() + rest_send.sender = sender + + with does_not_raise(): + instance = image_policies + instance.rest_send = rest_send + instance.results = Results() + instance.refresh() + instance.policy_name = "KR5M" + + match = r"ImagePolicies\._get: KR5M does not have a key named FOO\." + + with pytest.raises(ValueError, match=match): + instance._get("FOO") # pylint: disable=protected-access + + +def test_image_policies_02200(image_policies) -> None: + """ + ### Classes and Methods + + - ``ImagePolicies()`` + - ``_get()`` + + ### Summary + Verify that the correct image policy information is returned when + ``ImagePolicies._get()`` is called with the "policy" arguement. + + ### Setup + + - ``refresh`` is called and retrieves a response from the + controller containing information for image policy KR5M. + - ``policy_name`` is set to KR5M. + - _get("policy") is called. + + ### Test + + - Exception is not raised. + - The expected policy information is returned. + """ + method_name = inspect.stack()[0][3] + key = f"{method_name}a" + + def responses(): + yield responses_ep_policies(key) + + gen_responses = ResponseGenerator(responses()) + + sender = Sender() + sender.ansible_module = MockAnsibleModule() + sender.gen = gen_responses + rest_send = RestSend(params) + rest_send.unit_test = True + rest_send.timeout = 1 + rest_send.response_handler = ResponseHandler() + rest_send.sender = sender + + with does_not_raise(): + instance = image_policies + instance.rest_send = rest_send + instance.results = Results() + instance.refresh() + instance.policy_name = "KR5M" + value = instance._get("policy") # pylint: disable=protected-access + assert value["agnostic"] == "false" + assert value["epldImgName"] == "n9000-epld.10.2.5.M.img" + assert value["imageName"] == "nxos64-cs.10.2.5.M.bin" + assert value["nxosVersion"] == "10.2.5_nxos64-cs_64bit" + assert value["packageName"] == "" + assert value["platform"] == "N9K/N3K" + assert value["platformPolicies"] == "" + assert value["policyDescr"] == "10.2.(5) with EPLD" + assert value["policyName"] == "KR5M" + assert value["policyType"] == "PLATFORM" + assert value["ref_count"] == 10 + assert value["rpmimages"] == "" + + +def test_image_policies_03000(image_policies) -> None: + """ + ### Classes and Methods + + - ``ImagePolicies()`` + - ``all_policies`` + + ### Summary + Verify that ``all_policies`` returns an empty dict when no policies exist + on the controller. + + Test + - Exception is not raised. + - ``all_policies`` returns an empty dict. + """ + method_name = inspect.stack()[0][3] + key = f"{method_name}a" + + def responses(): + yield responses_ep_policies(key) + + gen_responses = ResponseGenerator(responses()) + + sender = Sender() + sender.ansible_module = MockAnsibleModule() + sender.gen = gen_responses + rest_send = RestSend(params) + rest_send.unit_test = True + rest_send.timeout = 1 + rest_send.response_handler = ResponseHandler() + rest_send.sender = sender + + with does_not_raise(): + instance = image_policies + instance.rest_send = rest_send + instance.results = Results() + instance.refresh() + value = instance.all_policies + assert value == {} + + +def test_image_policies_03100(image_policies) -> None: + """ + ### Classes and Methods + + - ``ImagePolicies()`` + - ``all_policies`` + + ### Summary + Verify that, when policies exist on the controller, all_policies returns a dict + containing these policies. + + ### Test + + - Exception is not raised. + - ``all_policies`` returns a dict containing the controller's image policies. + """ + key = "test_image_policies_00051a" + + method_name = inspect.stack()[0][3] + key = f"{method_name}a" + + def responses(): + yield responses_ep_policies(key) + + gen_responses = ResponseGenerator(responses()) + + sender = Sender() + sender.ansible_module = MockAnsibleModule() + sender.gen = gen_responses + rest_send = RestSend(params) + rest_send.unit_test = True + rest_send.timeout = 1 + rest_send.response_handler = ResponseHandler() + rest_send.sender = sender + + with does_not_raise(): + instance = image_policies + instance.rest_send = rest_send + instance.results = Results() + instance.refresh() + value = instance.all_policies + assert value["KR5M"]["agnostic"] == "false" + assert value["KR5M"]["epldImgName"] == "n9000-epld.10.2.5.M.img" + assert value["KR5M"]["imageName"] == "nxos64-cs.10.2.5.M.bin" + assert value["KR5M"]["nxosVersion"] == "10.2.5_nxos64-cs_64bit" + assert value["KR5M"]["packageName"] == "" + assert value["KR5M"]["platform"] == "N9K/N3K" + assert value["KR5M"]["platformPolicies"] == "" + assert value["KR5M"]["policyDescr"] == "10.2.(5) with EPLD" + assert value["KR5M"]["policyName"] == "KR5M" + assert value["KR5M"]["policyType"] == "PLATFORM" + assert value["KR5M"]["ref_count"] == 10 + assert value["KR5M"]["rpmimages"] == "" + assert value["OR1F"]["agnostic"] == "false" + assert value["OR1F"]["epldImgName"] == "n9000-epld.10.4.1.F.img" + assert value["OR1F"]["imageName"] == "nxos64-cs.10.4.1.F.bin" + assert value["OR1F"]["nxosVersion"] == "10.4.1_nxos64-cs_64bit" + assert value["OR1F"]["packageName"] == "" + assert value["OR1F"]["platform"] == "N9K/N3K" + assert value["OR1F"]["platformPolicies"] == "" + assert value["OR1F"]["policyDescr"] == "OR1F EPLD" + assert value["OR1F"]["policyName"] == "OR1F" + assert value["OR1F"]["policyType"] == "PLATFORM" + assert value["OR1F"]["ref_count"] == 0 + assert value["OR1F"]["rpmimages"] == "" diff --git a/tests/unit/module_utils/common/test_log.py b/tests/unit/module_utils/common/test_log.py index c3c771109..c631f6eef 100644 --- a/tests/unit/module_utils/common/test_log.py +++ b/tests/unit/module_utils/common/test_log.py @@ -29,7 +29,6 @@ __author__ = "Allen Robel" import logging -from typing import Any, Dict import pytest from ansible_collections.ansible.netcommon.tests.unit.modules.utils import \ diff --git a/tests/unit/module_utils/common/test_merge_dicts.py b/tests/unit/module_utils/common/test_merge_dicts.py index e089f526c..76f673bce 100644 --- a/tests/unit/module_utils/common/test_merge_dicts.py +++ b/tests/unit/module_utils/common/test_merge_dicts.py @@ -28,8 +28,6 @@ __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 diff --git a/tests/unit/module_utils/common/test_merge_dicts_v2.py b/tests/unit/module_utils/common/test_merge_dicts_v2.py index 1734f88d0..562ab3e6f 100644 --- a/tests/unit/module_utils/common/test_merge_dicts_v2.py +++ b/tests/unit/module_utils/common/test_merge_dicts_v2.py @@ -28,8 +28,6 @@ __copyright__ = "Copyright (c) 2024 Cisco and/or its affiliates." __author__ = "Allen Robel" -from typing import Any, Dict - import pytest from ansible_collections.cisco.dcnm.plugins.module_utils.common.merge_dicts_v2 import \ MergeDicts diff --git a/tests/unit/module_utils/common/test_params_validate.py b/tests/unit/module_utils/common/test_params_validate.py index c5361b3d1..947da3a21 100644 --- a/tests/unit/module_utils/common/test_params_validate.py +++ b/tests/unit/module_utils/common/test_params_validate.py @@ -28,8 +28,6 @@ __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 diff --git a/tests/unit/module_utils/common/test_params_validate_v2.py b/tests/unit/module_utils/common/test_params_validate_v2.py index db2de0786..4f1ae5d5a 100644 --- a/tests/unit/module_utils/common/test_params_validate_v2.py +++ b/tests/unit/module_utils/common/test_params_validate_v2.py @@ -28,8 +28,6 @@ __copyright__ = "Copyright (c) 2024 Cisco and/or its affiliates." __author__ = "Allen Robel" -from typing import Any, Dict - import pytest from ansible_collections.cisco.dcnm.plugins.module_utils.common.params_validate_v2 import \ ParamsValidate diff --git a/tests/unit/module_utils/common/test_sender_dcnm.py b/tests/unit/module_utils/common/test_sender_dcnm.py index f563fe07d..453b70405 100644 --- a/tests/unit/module_utils/common/test_sender_dcnm.py +++ b/tests/unit/module_utils/common/test_sender_dcnm.py @@ -27,7 +27,6 @@ __author__ = "Allen Robel" import inspect -from typing import Any, Dict import pytest from ansible_collections.cisco.dcnm.plugins.module_utils.common.api.v1.lan_fabric.rest.control.fabrics.fabrics import ( diff --git a/tests/unit/module_utils/common/test_sender_file.py b/tests/unit/module_utils/common/test_sender_file.py index 894f45295..f53cb383c 100644 --- a/tests/unit/module_utils/common/test_sender_file.py +++ b/tests/unit/module_utils/common/test_sender_file.py @@ -26,9 +26,6 @@ __copyright__ = "Copyright (c) 2024 Cisco and/or its affiliates." __author__ = "Allen Robel" -import inspect -from typing import Any, Dict - import pytest from ansible_collections.cisco.dcnm.plugins.module_utils.common.sender_file import \ Sender diff --git a/tests/unit/module_utils/common/test_switch_details.py b/tests/unit/module_utils/common/test_switch_details.py index 4a9da7b39..e594e1c60 100644 --- a/tests/unit/module_utils/common/test_switch_details.py +++ b/tests/unit/module_utils/common/test_switch_details.py @@ -26,16 +26,9 @@ __copyright__ = "Copyright (c) 2024 Cisco and/or its affiliates." __author__ = "Allen Robel" -import copy import inspect import pytest -from ansible_collections.cisco.dcnm.plugins.module_utils.common.api.v1.lan_fabric.rest.inventory.inventory import \ - EpAllSwitches -from ansible_collections.cisco.dcnm.plugins.module_utils.common.conversion import \ - ConversionUtils -from ansible_collections.cisco.dcnm.plugins.module_utils.common.exceptions import \ - ControllerResponseError from ansible_collections.cisco.dcnm.plugins.module_utils.common.response_handler import \ ResponseHandler from ansible_collections.cisco.dcnm.plugins.module_utils.common.rest_send_v2 import \ @@ -65,10 +58,8 @@ def test_switch_details_00000() -> None: instance = SwitchDetails() assert instance.action == "switch_details" assert instance.class_name == "SwitchDetails" - assert isinstance(instance.conversion, ConversionUtils) - assert isinstance(instance.ep_all_switches, EpAllSwitches) - assert instance.path == EpAllSwitches().path - assert instance.verb == EpAllSwitches().verb + assert instance.conversion.class_name == "ConversionUtils" + assert instance.ep_all_switches.class_name == "EpAllSwitches" assert instance._filter is None assert instance._info is None assert instance._rest_send is None @@ -463,6 +454,64 @@ def responses(): instance.refresh() +def test_switch_details_00550() -> None: + """ + ### Classes and Methods + - SwitchDetails() + - update_results() + - refresh() + + ### Summary + Verify ``refresh()`` raises ``ValueError`` when ``update_results`` + raises ``ControllerResponseError``. + + ### Setup - Code + - Sender() is initialized and configured. + - RestSend() is initialized and configured. + - SwitchDetails() is initialized and configured. + + ### Setup - Data + responses_switch_details() returns a response with: + - RETURN_CODE: 500 + - MESSAGE: "NOK". + + ### Trigger + - SwitchDetails().refresh() is called. + + ### Expected Result + - ``update_results`` raises ``ControllerResponseError``. + - ``refresh`` re-raises ``ControllerResponseError`` as ``ValueError``. + """ + + method_name = inspect.stack()[0][3] + key = f"{method_name}a" + + def responses(): + yield responses_switch_details(key) + + sender = Sender() + sender.gen = ResponseGenerator(responses()) + rest_send = RestSend(PARAMS) + rest_send.response_handler = ResponseHandler() + rest_send.sender = sender + rest_send.unit_test = True + rest_send.timeout = 1 + + with does_not_raise(): + instance = SwitchDetails() + instance.rest_send = rest_send + instance.results = Results() + + match = r"SwitchDetails\.refresh:\s+" + match += r"Error updating results\.\s+" + match += r"Error detail:\s+" + match += r"SwitchDetails\.update_results:\s+" + match += r"Unable to retrieve switch information from the controller\.\s+" + match += r"Got response.*" + with pytest.raises(ValueError, match=match): + instance.refresh() + + def test_switch_details_00600() -> None: """ ### Classes and Methods diff --git a/tests/unit/modules/dcnm/dcnm_fabric/fixtures/payloads_FabricReplacedBulk.json b/tests/unit/modules/dcnm/dcnm_fabric/fixtures/payloads_FabricReplacedBulk.json index 939f9f94c..350165068 100644 --- a/tests/unit/modules/dcnm/dcnm_fabric/fixtures/payloads_FabricReplacedBulk.json +++ b/tests/unit/modules/dcnm/dcnm_fabric/fixtures/payloads_FabricReplacedBulk.json @@ -11,7 +11,7 @@ "FABRIC_TYPE": "VXLAN_EVPN" } ], - "test_fabric_replaced_bulk_00025a": [ + "test_fabric_replaced_bulk_00024a": [ { "BGP_AS": 65001, "DEPLOY": true, diff --git a/tests/unit/modules/dcnm/dcnm_fabric/fixtures/responses_FabricDetails.json b/tests/unit/modules/dcnm/dcnm_fabric/fixtures/responses_FabricDetails.json deleted file mode 100644 index f6c52b8d4..000000000 --- a/tests/unit/modules/dcnm/dcnm_fabric/fixtures/responses_FabricDetails.json +++ /dev/null @@ -1,344 +0,0 @@ -{ - "test_notes": [ - "Mocked responses for FabricDetails() class" - ], - "test_fabric_details_00030a": { - "TEST_NOTES": [ - "DATA is an empty list", - "RETURN_CODE is 200" - ], - "DATA": [], - "MESSAGE": "OK", - "METHOD": "GET", - "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/rest/control/fabrics", - "RETURN_CODE": 200 - }, - "test_fabric_details_00031a": { - "TEST_NOTES": [ - "DATA key is missing", - "Negative test case", - "RETURN_CODE is 200" - ], - "MESSAGE": "OK", - "METHOD": "GET", - "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/rest/control/fabrics", - "RETURN_CODE": 200 - }, - "test_fabric_details_00032a": { - "TEST_NOTES": [ - "DATA contains one fabric dict", - "RETURN_CODE is 200" - ], - "DATA": [ - { - "asn": "65001", - "createdOn": 1711411093680, - "deviceType": "n9k", - "fabricId": "FABRIC-2", - "fabricName": "f1", - "fabricTechnology": "VXLANFabric", - "fabricTechnologyFriendly": "VXLAN EVPN", - "fabricType": "Switch_Fabric", - "fabricTypeFriendly": "Switch Fabric", - "id": 2, - "modifiedOn": 1711411096857, - "networkExtensionTemplate": "Default_Network_Extension_Universal", - "networkTemplate": "Default_Network_Universal", - "nvPairs": { - "AAA_REMOTE_IP_ENABLED": "false", - "AAA_SERVER_CONF": "", - "ACTIVE_MIGRATION": "false", - "ADVERTISE_PIP_BGP": "false", - "ADVERTISE_PIP_ON_BORDER": "true", - "AGENT_INTF": "eth0", - "AI_ML_QOS_POLICY": "AI_Fabric_QOS_400G", - "ALLOW_NXC": "true", - "ALLOW_NXC_PREV": "true", - "ANYCAST_BGW_ADVERTISE_PIP": "false", - "ANYCAST_GW_MAC": "2020.0000.00aa", - "ANYCAST_LB_ID": "", - "ANYCAST_RP_IP_RANGE": "10.254.254.0/24", - "ANYCAST_RP_IP_RANGE_INTERNAL": "", - "AUTO_SYMMETRIC_DEFAULT_VRF": "false", - "AUTO_SYMMETRIC_VRF_LITE": "false", - "AUTO_UNIQUE_VRF_LITE_IP_PREFIX": "false", - "AUTO_UNIQUE_VRF_LITE_IP_PREFIX_PREV": "false", - "AUTO_VRFLITE_IFC_DEFAULT_VRF": "false", - "BANNER": "", - "BFD_AUTH_ENABLE": "false", - "BFD_AUTH_KEY": "", - "BFD_AUTH_KEY_ID": "", - "BFD_ENABLE": "false", - "BFD_ENABLE_PREV": "", - "BFD_IBGP_ENABLE": "false", - "BFD_ISIS_ENABLE": "false", - "BFD_OSPF_ENABLE": "false", - "BFD_PIM_ENABLE": "false", - "BGP_AS": "65001", - "BGP_AS_PREV": "65001", - "BGP_AUTH_ENABLE": "false", - "BGP_AUTH_KEY": "", - "BGP_AUTH_KEY_TYPE": "3", - "BGP_LB_ID": "0", - "BOOTSTRAP_CONF": "", - "BOOTSTRAP_ENABLE": "false", - "BOOTSTRAP_ENABLE_PREV": "false", - "BOOTSTRAP_MULTISUBNET": "#Scope_Start_IP, Scope_End_IP, Scope_Default_Gateway, Scope_Subnet_Prefix", - "BOOTSTRAP_MULTISUBNET_INTERNAL": "", - "BRFIELD_DEBUG_FLAG": "Disable", - "BROWNFIELD_NETWORK_NAME_FORMAT": "Auto_Net_VNI$$VNI$$_VLAN$$VLAN_ID$$", - "BROWNFIELD_SKIP_OVERLAY_NETWORK_ATTACHMENTS": "false", - "CDP_ENABLE": "false", - "COPP_POLICY": "strict", - "DCI_SUBNET_RANGE": "10.33.0.0/16", - "DCI_SUBNET_TARGET_MASK": "30", - "DEAFULT_QUEUING_POLICY_CLOUDSCALE": "queuing_policy_default_8q_cloudscale", - "DEAFULT_QUEUING_POLICY_OTHER": "queuing_policy_default_other", - "DEAFULT_QUEUING_POLICY_R_SERIES": "queuing_policy_default_r_series", - "DEFAULT_VRF_REDIS_BGP_RMAP": "", - "DEPLOYMENT_FREEZE": "false", - "DHCP_ENABLE": "false", - "DHCP_END": "", - "DHCP_END_INTERNAL": "", - "DHCP_IPV6_ENABLE": "", - "DHCP_IPV6_ENABLE_INTERNAL": "", - "DHCP_START": "", - "DHCP_START_INTERNAL": "", - "DNS_SERVER_IP_LIST": "", - "DNS_SERVER_VRF": "", - "DOMAIN_NAME_INTERNAL": "", - "ENABLE_AAA": "false", - "ENABLE_AGENT": "false", - "ENABLE_AI_ML_QOS_POLICY": "false", - "ENABLE_AI_ML_QOS_POLICY_FLAP": "false", - "ENABLE_DEFAULT_QUEUING_POLICY": "false", - "ENABLE_EVPN": "true", - "ENABLE_FABRIC_VPC_DOMAIN_ID": "false", - "ENABLE_FABRIC_VPC_DOMAIN_ID_PREV": "false", - "ENABLE_L3VNI_NO_VLAN": "false", - "ENABLE_MACSEC": "false", - "ENABLE_NETFLOW": "false", - "ENABLE_NETFLOW_PREV": "false", - "ENABLE_NGOAM": "true", - "ENABLE_NXAPI": "true", - "ENABLE_NXAPI_HTTP": "true", - "ENABLE_PBR": "false", - "ENABLE_PVLAN": "false", - "ENABLE_PVLAN_PREV": "false", - "ENABLE_SGT": "false", - "ENABLE_SGT_PREV": "false", - "ENABLE_TENANT_DHCP": "true", - "ENABLE_TRM": "false", - "ENABLE_VPC_PEER_LINK_NATIVE_VLAN": "false", - "ESR_OPTION": "PBR", - "EXTRA_CONF_INTRA_LINKS": "", - "EXTRA_CONF_LEAF": "", - "EXTRA_CONF_SPINE": "", - "EXTRA_CONF_TOR": "", - "EXT_FABRIC_TYPE": "", - "FABRIC_INTERFACE_TYPE": "p2p", - "FABRIC_MTU": "9216", - "FABRIC_MTU_PREV": "9216", - "FABRIC_NAME": "f1", - "FABRIC_TYPE": "Switch_Fabric", - "FABRIC_VPC_DOMAIN_ID": "", - "FABRIC_VPC_DOMAIN_ID_PREV": "", - "FABRIC_VPC_QOS": "false", - "FABRIC_VPC_QOS_POLICY_NAME": "spine_qos_for_fabric_vpc_peering", - "FEATURE_PTP": "false", - "FEATURE_PTP_INTERNAL": "false", - "FF": "Easy_Fabric", - "GRFIELD_DEBUG_FLAG": "Disable", - "HD_TIME": "180", - "HOST_INTF_ADMIN_STATE": "true", - "IBGP_PEER_TEMPLATE": "", - "IBGP_PEER_TEMPLATE_LEAF": "", - "INBAND_DHCP_SERVERS": "", - "INBAND_MGMT": "false", - "INBAND_MGMT_PREV": "false", - "ISIS_AREA_NUM": "0001", - "ISIS_AREA_NUM_PREV": "", - "ISIS_AUTH_ENABLE": "false", - "ISIS_AUTH_KEY": "", - "ISIS_AUTH_KEYCHAIN_KEY_ID": "", - "ISIS_AUTH_KEYCHAIN_NAME": "", - "ISIS_LEVEL": "level-2", - "ISIS_OVERLOAD_ELAPSE_TIME": "", - "ISIS_OVERLOAD_ENABLE": "", - "ISIS_P2P_ENABLE": "", - "L2_HOST_INTF_MTU": "9216", - "L2_HOST_INTF_MTU_PREV": "9216", - "L2_SEGMENT_ID_RANGE": "30000-49000", - "L3VNI_MCAST_GROUP": "", - "L3_PARTITION_ID_RANGE": "50000-59000", - "LINK_STATE_ROUTING": "ospf", - "LINK_STATE_ROUTING_TAG": "UNDERLAY", - "LINK_STATE_ROUTING_TAG_PREV": "UNDERLAY", - "LOOPBACK0_IPV6_RANGE": "", - "LOOPBACK0_IP_RANGE": "10.2.0.0/22", - "LOOPBACK1_IPV6_RANGE": "", - "LOOPBACK1_IP_RANGE": "10.3.0.0/22", - "MACSEC_ALGORITHM": "", - "MACSEC_CIPHER_SUITE": "", - "MACSEC_FALLBACK_ALGORITHM": "", - "MACSEC_FALLBACK_KEY_STRING": "", - "MACSEC_KEY_STRING": "", - "MACSEC_REPORT_TIMER": "", - "MGMT_GW": "", - "MGMT_GW_INTERNAL": "", - "MGMT_PREFIX": "", - "MGMT_PREFIX_INTERNAL": "", - "MGMT_V6PREFIX": "", - "MGMT_V6PREFIX_INTERNAL": "", - "MPLS_HANDOFF": "false", - "MPLS_ISIS_AREA_NUM": "0001", - "MPLS_ISIS_AREA_NUM_PREV": "", - "MPLS_LB_ID": "", - "MPLS_LOOPBACK_IP_RANGE": "", - "MSO_CONNECTIVITY_DEPLOYED": "", - "MSO_CONTROLER_ID": "", - "MSO_SITE_GROUP_NAME": "", - "MSO_SITE_ID": "", - "MST_INSTANCE_RANGE": "", - "MULTICAST_GROUP_SUBNET": "239.1.1.0/25", - "NETFLOW_EXPORTER_LIST": "", - "NETFLOW_MONITOR_LIST": "", - "NETFLOW_RECORD_LIST": "", - "NETWORK_VLAN_RANGE": "2300-2999", - "NTP_SERVER_IP_LIST": "", - "NTP_SERVER_VRF": "", - "NVE_LB_ID": "1", - "NXAPI_HTTPS_PORT": "443", - "NXAPI_HTTP_PORT": "80", - "NXC_DEST_VRF": "", - "NXC_PROXY_PORT": "8080", - "NXC_PROXY_SERVER": "", - "NXC_SRC_INTF": "", - "OBJECT_TRACKING_NUMBER_RANGE": "100-299", - "OSPF_AREA_ID": "0.0.0.0", - "OSPF_AUTH_ENABLE": "false", - "OSPF_AUTH_KEY": "", - "OSPF_AUTH_KEY_ID": "", - "OVERLAY_MODE": "cli", - "OVERLAY_MODE_PREV": "cli", - "OVERWRITE_GLOBAL_NXC": "false", - "PER_VRF_LOOPBACK_AUTO_PROVISION": "false", - "PER_VRF_LOOPBACK_AUTO_PROVISION_PREV": "false", - "PER_VRF_LOOPBACK_AUTO_PROVISION_V6": "false", - "PER_VRF_LOOPBACK_AUTO_PROVISION_V6_PREV": "false", - "PER_VRF_LOOPBACK_IP_RANGE": "", - "PER_VRF_LOOPBACK_IP_RANGE_V6": "", - "PHANTOM_RP_LB_ID1": "", - "PHANTOM_RP_LB_ID2": "", - "PHANTOM_RP_LB_ID3": "", - "PHANTOM_RP_LB_ID4": "", - "PIM_HELLO_AUTH_ENABLE": "false", - "PIM_HELLO_AUTH_KEY": "", - "PM_ENABLE": "false", - "PM_ENABLE_PREV": "false", - "POWER_REDUNDANCY_MODE": "ps-redundant", - "PREMSO_PARENT_FABRIC": "", - "PTP_DOMAIN_ID": "", - "PTP_LB_ID": "", - "REPLICATION_MODE": "Multicast", - "ROUTER_ID_RANGE": "", - "ROUTE_MAP_SEQUENCE_NUMBER_RANGE": "1-65534", - "RP_COUNT": "2", - "RP_LB_ID": "254", - "RP_MODE": "asm", - "RR_COUNT": "2", - "SEED_SWITCH_CORE_INTERFACES": "", - "SERVICE_NETWORK_VLAN_RANGE": "3000-3199", - "SGT_ID_RANGE": "", - "SGT_NAME_PREFIX": "", - "SGT_PREPROVISION": "", - "SITE_ID": "65001", - "SLA_ID_RANGE": "10000-19999", - "SNMP_SERVER_HOST_TRAP": "true", - "SPINE_COUNT": "0", - "SPINE_SWITCH_CORE_INTERFACES": "", - "SSPINE_ADD_DEL_DEBUG_FLAG": "Disable", - "SSPINE_COUNT": "0", - "STATIC_UNDERLAY_IP_ALLOC": "false", - "STP_BRIDGE_PRIORITY": "", - "STP_ROOT_OPTION": "unmanaged", - "STP_VLAN_RANGE": "", - "STRICT_CC_MODE": "false", - "SUBINTERFACE_RANGE": "2-511", - "SUBNET_RANGE": "10.4.0.0/16", - "SUBNET_TARGET_MASK": "30", - "SYSLOG_SERVER_IP_LIST": "", - "SYSLOG_SERVER_VRF": "", - "SYSLOG_SEV": "", - "TCAM_ALLOCATION": "true", - "TOPDOWN_CONFIG_RM_TRACKING": "notstarted", - "UNDERLAY_IS_V6": "false", - "UNNUM_BOOTSTRAP_LB_ID": "", - "UNNUM_DHCP_END": "", - "UNNUM_DHCP_END_INTERNAL": "", - "UNNUM_DHCP_START": "", - "UNNUM_DHCP_START_INTERNAL": "", - "UPGRADE_FROM_VERSION": "", - "USE_LINK_LOCAL": "true", - "V6_SUBNET_RANGE": "", - "V6_SUBNET_TARGET_MASK": "126", - "VPC_AUTO_RECOVERY_TIME": "360", - "VPC_DELAY_RESTORE": "150", - "VPC_DELAY_RESTORE_TIME": "60", - "VPC_DOMAIN_ID_RANGE": "1-1000", - "VPC_ENABLE_IPv6_ND_SYNC": "true", - "VPC_PEER_KEEP_ALIVE_OPTION": "management", - "VPC_PEER_LINK_PO": "500", - "VPC_PEER_LINK_VLAN": "3600", - "VRF_LITE_AUTOCONFIG": "Manual", - "VRF_VLAN_RANGE": "2000-2299", - "abstract_anycast_rp": "anycast_rp", - "abstract_bgp": "base_bgp", - "abstract_bgp_neighbor": "evpn_bgp_rr_neighbor", - "abstract_bgp_rr": "evpn_bgp_rr", - "abstract_dhcp": "base_dhcp", - "abstract_extra_config_bootstrap": "extra_config_bootstrap_11_1", - "abstract_extra_config_leaf": "extra_config_leaf", - "abstract_extra_config_spine": "extra_config_spine", - "abstract_extra_config_tor": "extra_config_tor", - "abstract_feature_leaf": "base_feature_leaf_upg", - "abstract_feature_spine": "base_feature_spine_upg", - "abstract_isis": "base_isis_level2", - "abstract_isis_interface": "isis_interface", - "abstract_loopback_interface": "int_fabric_loopback_11_1", - "abstract_multicast": "base_multicast_11_1", - "abstract_ospf": "base_ospf", - "abstract_ospf_interface": "ospf_interface_11_1", - "abstract_pim_interface": "pim_interface", - "abstract_route_map": "route_map", - "abstract_routed_host": "int_routed_host", - "abstract_trunk_host": "int_trunk_host", - "abstract_vlan_interface": "int_fabric_vlan_11_1", - "abstract_vpc_domain": "base_vpc_domain_11_1", - "default_network": "Default_Network_Universal", - "default_pvlan_sec_network": "", - "default_vrf": "Default_VRF_Universal", - "enableRealTimeBackup": "", - "enableScheduledBackup": "", - "network_extension_template": "Default_Network_Extension_Universal", - "scheduledTime": "", - "temp_anycast_gateway": "anycast_gateway", - "temp_vpc_domain_mgmt": "vpc_domain_mgmt", - "temp_vpc_peer_link": "int_vpc_peer_link_po", - "vrf_extension_template": "Default_VRF_Extension_Universal" - }, - "operStatus": "HEALTHY", - "provisionMode": "DCNMTopDown", - "replicationMode": "Multicast", - "siteId": "65001", - "templateName": "Easy_Fabric", - "vrfExtensionTemplate": "Default_VRF_Extension_Universal", - "vrfTemplate": "Default_VRF_Universal" - } - ], - "MESSAGE": "OK", - "METHOD": "GET", - "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/rest/control/fabrics", - "RETURN_CODE": 200 - } -} diff --git a/tests/unit/modules/dcnm/dcnm_fabric/fixtures/responses_FabricDetailsByName.json b/tests/unit/modules/dcnm/dcnm_fabric/fixtures/responses_FabricDetailsByName.json deleted file mode 100644 index ceb778e67..000000000 --- a/tests/unit/modules/dcnm/dcnm_fabric/fixtures/responses_FabricDetailsByName.json +++ /dev/null @@ -1,5186 +0,0 @@ -{ - "test_notes": [ - "Mocked responses for FabricDetails() class" - ], - "test_fabric_config_deploy_00200a": { - "DATA": [ - { - "nvPairs": { - "DEPLOYMENT_FREEZE": "false", - "IS_READ_ONLY": "false" - } - } - ], - "MESSAGE": "OK", - "METHOD": "PUT", - "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/rest/control/fabrics/f1/LAN_Classic", - "RETURN_CODE": 200, - "sequence_number": 1 - }, - "test_fabric_config_deploy_00210a": { - "DATA": [ - { - "nvPairs": { - "DEPLOYMENT_FREEZE": "false", - "IS_READ_ONLY": "false" - } - } - ], - "MESSAGE": "OK", - "METHOD": "PUT", - "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/rest/control/fabrics/f1/LAN_Classic", - "RETURN_CODE": 200, - "sequence_number": 1 - }, - "test_fabric_config_deploy_00220a": { - "DATA": [ - { - "nvPairs": { - "DEPLOYMENT_FREEZE": "false", - "IS_READ_ONLY": "false" - } - } - ], - "MESSAGE": "OK", - "METHOD": "PUT", - "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/rest/control/fabrics/f1/LAN_Classic", - "RETURN_CODE": 200, - "sequence_number": 1 - }, - "test_fabric_create_00030a": { - "DATA": [], - "MESSAGE": "OK", - "METHOD": "GET", - "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/rest/control/fabrics", - "RETURN_CODE": 200 - }, - "test_fabric_create_00031a": { - "DATA": [ - { - "asn": "65001", - "createdOn": 1711411093680, - "deviceType": "n9k", - "fabricId": "FABRIC-2", - "fabricName": "f1", - "fabricTechnology": "VXLANFabric", - "fabricTechnologyFriendly": "VXLAN EVPN", - "fabricType": "Switch_Fabric", - "fabricTypeFriendly": "Switch Fabric", - "id": 2, - "modifiedOn": 1711411096857, - "networkExtensionTemplate": "Default_Network_Extension_Universal", - "networkTemplate": "Default_Network_Universal", - "nvPairs": { - "AAA_REMOTE_IP_ENABLED": "false", - "AAA_SERVER_CONF": "", - "ACTIVE_MIGRATION": "false", - "ADVERTISE_PIP_BGP": "false", - "ADVERTISE_PIP_ON_BORDER": "true", - "AGENT_INTF": "eth0", - "AI_ML_QOS_POLICY": "AI_Fabric_QOS_400G", - "ALLOW_NXC": "true", - "ALLOW_NXC_PREV": "true", - "ANYCAST_BGW_ADVERTISE_PIP": "false", - "ANYCAST_GW_MAC": "2020.0000.00aa", - "ANYCAST_LB_ID": "", - "ANYCAST_RP_IP_RANGE": "10.254.254.0/24", - "ANYCAST_RP_IP_RANGE_INTERNAL": "", - "AUTO_SYMMETRIC_DEFAULT_VRF": "false", - "AUTO_SYMMETRIC_VRF_LITE": "false", - "AUTO_UNIQUE_VRF_LITE_IP_PREFIX": "false", - "AUTO_UNIQUE_VRF_LITE_IP_PREFIX_PREV": "false", - "AUTO_VRFLITE_IFC_DEFAULT_VRF": "false", - "BANNER": "", - "BFD_AUTH_ENABLE": "false", - "BFD_AUTH_KEY": "", - "BFD_AUTH_KEY_ID": "", - "BFD_ENABLE": "false", - "BFD_ENABLE_PREV": "", - "BFD_IBGP_ENABLE": "false", - "BFD_ISIS_ENABLE": "false", - "BFD_OSPF_ENABLE": "false", - "BFD_PIM_ENABLE": "false", - "BGP_AS": "65001", - "BGP_AS_PREV": "65001", - "BGP_AUTH_ENABLE": "false", - "BGP_AUTH_KEY": "", - "BGP_AUTH_KEY_TYPE": "3", - "BGP_LB_ID": "0", - "BOOTSTRAP_CONF": "", - "BOOTSTRAP_ENABLE": "false", - "BOOTSTRAP_ENABLE_PREV": "false", - "BOOTSTRAP_MULTISUBNET": "#Scope_Start_IP, Scope_End_IP, Scope_Default_Gateway, Scope_Subnet_Prefix", - "BOOTSTRAP_MULTISUBNET_INTERNAL": "", - "BRFIELD_DEBUG_FLAG": "Disable", - "BROWNFIELD_NETWORK_NAME_FORMAT": "Auto_Net_VNI$$VNI$$_VLAN$$VLAN_ID$$", - "BROWNFIELD_SKIP_OVERLAY_NETWORK_ATTACHMENTS": "false", - "CDP_ENABLE": "false", - "COPP_POLICY": "strict", - "DCI_SUBNET_RANGE": "10.33.0.0/16", - "DCI_SUBNET_TARGET_MASK": "30", - "DEAFULT_QUEUING_POLICY_CLOUDSCALE": "queuing_policy_default_8q_cloudscale", - "DEAFULT_QUEUING_POLICY_OTHER": "queuing_policy_default_other", - "DEAFULT_QUEUING_POLICY_R_SERIES": "queuing_policy_default_r_series", - "DEFAULT_VRF_REDIS_BGP_RMAP": "", - "DEPLOYMENT_FREEZE": "false", - "DHCP_ENABLE": "false", - "DHCP_END": "", - "DHCP_END_INTERNAL": "", - "DHCP_IPV6_ENABLE": "", - "DHCP_IPV6_ENABLE_INTERNAL": "", - "DHCP_START": "", - "DHCP_START_INTERNAL": "", - "DNS_SERVER_IP_LIST": "", - "DNS_SERVER_VRF": "", - "DOMAIN_NAME_INTERNAL": "", - "ENABLE_AAA": "false", - "ENABLE_AGENT": "false", - "ENABLE_AI_ML_QOS_POLICY": "false", - "ENABLE_AI_ML_QOS_POLICY_FLAP": "false", - "ENABLE_DEFAULT_QUEUING_POLICY": "false", - "ENABLE_EVPN": "true", - "ENABLE_FABRIC_VPC_DOMAIN_ID": "false", - "ENABLE_FABRIC_VPC_DOMAIN_ID_PREV": "false", - "ENABLE_L3VNI_NO_VLAN": "false", - "ENABLE_MACSEC": "false", - "ENABLE_NETFLOW": "false", - "ENABLE_NETFLOW_PREV": "false", - "ENABLE_NGOAM": "true", - "ENABLE_NXAPI": "true", - "ENABLE_NXAPI_HTTP": "true", - "ENABLE_PBR": "false", - "ENABLE_PVLAN": "false", - "ENABLE_PVLAN_PREV": "false", - "ENABLE_SGT": "false", - "ENABLE_SGT_PREV": "false", - "ENABLE_TENANT_DHCP": "true", - "ENABLE_TRM": "false", - "ENABLE_VPC_PEER_LINK_NATIVE_VLAN": "false", - "ESR_OPTION": "PBR", - "EXTRA_CONF_INTRA_LINKS": "", - "EXTRA_CONF_LEAF": "", - "EXTRA_CONF_SPINE": "", - "EXTRA_CONF_TOR": "", - "EXT_FABRIC_TYPE": "", - "FABRIC_INTERFACE_TYPE": "p2p", - "FABRIC_MTU": "9216", - "FABRIC_MTU_PREV": "9216", - "FABRIC_NAME": "f1", - "FABRIC_TYPE": "Switch_Fabric", - "FABRIC_VPC_DOMAIN_ID": "", - "FABRIC_VPC_DOMAIN_ID_PREV": "", - "FABRIC_VPC_QOS": "false", - "FABRIC_VPC_QOS_POLICY_NAME": "spine_qos_for_fabric_vpc_peering", - "FEATURE_PTP": "false", - "FEATURE_PTP_INTERNAL": "false", - "FF": "Easy_Fabric", - "GRFIELD_DEBUG_FLAG": "Disable", - "HD_TIME": "180", - "HOST_INTF_ADMIN_STATE": "true", - "IBGP_PEER_TEMPLATE": "", - "IBGP_PEER_TEMPLATE_LEAF": "", - "INBAND_DHCP_SERVERS": "", - "INBAND_MGMT": "false", - "INBAND_MGMT_PREV": "false", - "ISIS_AREA_NUM": "0001", - "ISIS_AREA_NUM_PREV": "", - "ISIS_AUTH_ENABLE": "false", - "ISIS_AUTH_KEY": "", - "ISIS_AUTH_KEYCHAIN_KEY_ID": "", - "ISIS_AUTH_KEYCHAIN_NAME": "", - "ISIS_LEVEL": "level-2", - "ISIS_OVERLOAD_ELAPSE_TIME": "", - "ISIS_OVERLOAD_ENABLE": "", - "ISIS_P2P_ENABLE": "", - "L2_HOST_INTF_MTU": "9216", - "L2_HOST_INTF_MTU_PREV": "9216", - "L2_SEGMENT_ID_RANGE": "30000-49000", - "L3VNI_MCAST_GROUP": "", - "L3_PARTITION_ID_RANGE": "50000-59000", - "LINK_STATE_ROUTING": "ospf", - "LINK_STATE_ROUTING_TAG": "UNDERLAY", - "LINK_STATE_ROUTING_TAG_PREV": "UNDERLAY", - "LOOPBACK0_IPV6_RANGE": "", - "LOOPBACK0_IP_RANGE": "10.2.0.0/22", - "LOOPBACK1_IPV6_RANGE": "", - "LOOPBACK1_IP_RANGE": "10.3.0.0/22", - "MACSEC_ALGORITHM": "", - "MACSEC_CIPHER_SUITE": "", - "MACSEC_FALLBACK_ALGORITHM": "", - "MACSEC_FALLBACK_KEY_STRING": "", - "MACSEC_KEY_STRING": "", - "MACSEC_REPORT_TIMER": "", - "MGMT_GW": "", - "MGMT_GW_INTERNAL": "", - "MGMT_PREFIX": "", - "MGMT_PREFIX_INTERNAL": "", - "MGMT_V6PREFIX": "", - "MGMT_V6PREFIX_INTERNAL": "", - "MPLS_HANDOFF": "false", - "MPLS_ISIS_AREA_NUM": "0001", - "MPLS_ISIS_AREA_NUM_PREV": "", - "MPLS_LB_ID": "", - "MPLS_LOOPBACK_IP_RANGE": "", - "MSO_CONNECTIVITY_DEPLOYED": "", - "MSO_CONTROLER_ID": "", - "MSO_SITE_GROUP_NAME": "", - "MSO_SITE_ID": "", - "MST_INSTANCE_RANGE": "", - "MULTICAST_GROUP_SUBNET": "239.1.1.0/25", - "NETFLOW_EXPORTER_LIST": "", - "NETFLOW_MONITOR_LIST": "", - "NETFLOW_RECORD_LIST": "", - "NETWORK_VLAN_RANGE": "2300-2999", - "NTP_SERVER_IP_LIST": "", - "NTP_SERVER_VRF": "", - "NVE_LB_ID": "1", - "NXAPI_HTTPS_PORT": "443", - "NXAPI_HTTP_PORT": "80", - "NXC_DEST_VRF": "", - "NXC_PROXY_PORT": "8080", - "NXC_PROXY_SERVER": "", - "NXC_SRC_INTF": "", - "OBJECT_TRACKING_NUMBER_RANGE": "100-299", - "OSPF_AREA_ID": "0.0.0.0", - "OSPF_AUTH_ENABLE": "false", - "OSPF_AUTH_KEY": "", - "OSPF_AUTH_KEY_ID": "", - "OVERLAY_MODE": "cli", - "OVERLAY_MODE_PREV": "cli", - "OVERWRITE_GLOBAL_NXC": "false", - "PER_VRF_LOOPBACK_AUTO_PROVISION": "false", - "PER_VRF_LOOPBACK_AUTO_PROVISION_PREV": "false", - "PER_VRF_LOOPBACK_AUTO_PROVISION_V6": "false", - "PER_VRF_LOOPBACK_AUTO_PROVISION_V6_PREV": "false", - "PER_VRF_LOOPBACK_IP_RANGE": "", - "PER_VRF_LOOPBACK_IP_RANGE_V6": "", - "PHANTOM_RP_LB_ID1": "", - "PHANTOM_RP_LB_ID2": "", - "PHANTOM_RP_LB_ID3": "", - "PHANTOM_RP_LB_ID4": "", - "PIM_HELLO_AUTH_ENABLE": "false", - "PIM_HELLO_AUTH_KEY": "", - "PM_ENABLE": "false", - "PM_ENABLE_PREV": "false", - "POWER_REDUNDANCY_MODE": "ps-redundant", - "PREMSO_PARENT_FABRIC": "", - "PTP_DOMAIN_ID": "", - "PTP_LB_ID": "", - "REPLICATION_MODE": "Multicast", - "ROUTER_ID_RANGE": "", - "ROUTE_MAP_SEQUENCE_NUMBER_RANGE": "1-65534", - "RP_COUNT": "2", - "RP_LB_ID": "254", - "RP_MODE": "asm", - "RR_COUNT": "2", - "SEED_SWITCH_CORE_INTERFACES": "", - "SERVICE_NETWORK_VLAN_RANGE": "3000-3199", - "SGT_ID_RANGE": "", - "SGT_NAME_PREFIX": "", - "SGT_PREPROVISION": "", - "SITE_ID": "65001", - "SLA_ID_RANGE": "10000-19999", - "SNMP_SERVER_HOST_TRAP": "true", - "SPINE_COUNT": "0", - "SPINE_SWITCH_CORE_INTERFACES": "", - "SSPINE_ADD_DEL_DEBUG_FLAG": "Disable", - "SSPINE_COUNT": "0", - "STATIC_UNDERLAY_IP_ALLOC": "false", - "STP_BRIDGE_PRIORITY": "", - "STP_ROOT_OPTION": "unmanaged", - "STP_VLAN_RANGE": "", - "STRICT_CC_MODE": "false", - "SUBINTERFACE_RANGE": "2-511", - "SUBNET_RANGE": "10.4.0.0/16", - "SUBNET_TARGET_MASK": "30", - "SYSLOG_SERVER_IP_LIST": "", - "SYSLOG_SERVER_VRF": "", - "SYSLOG_SEV": "", - "TCAM_ALLOCATION": "true", - "TOPDOWN_CONFIG_RM_TRACKING": "notstarted", - "UNDERLAY_IS_V6": "false", - "UNNUM_BOOTSTRAP_LB_ID": "", - "UNNUM_DHCP_END": "", - "UNNUM_DHCP_END_INTERNAL": "", - "UNNUM_DHCP_START": "", - "UNNUM_DHCP_START_INTERNAL": "", - "UPGRADE_FROM_VERSION": "", - "USE_LINK_LOCAL": "true", - "V6_SUBNET_RANGE": "", - "V6_SUBNET_TARGET_MASK": "126", - "VPC_AUTO_RECOVERY_TIME": "360", - "VPC_DELAY_RESTORE": "150", - "VPC_DELAY_RESTORE_TIME": "60", - "VPC_DOMAIN_ID_RANGE": "1-1000", - "VPC_ENABLE_IPv6_ND_SYNC": "true", - "VPC_PEER_KEEP_ALIVE_OPTION": "management", - "VPC_PEER_LINK_PO": "500", - "VPC_PEER_LINK_VLAN": "3600", - "VRF_LITE_AUTOCONFIG": "Manual", - "VRF_VLAN_RANGE": "2000-2299", - "abstract_anycast_rp": "anycast_rp", - "abstract_bgp": "base_bgp", - "abstract_bgp_neighbor": "evpn_bgp_rr_neighbor", - "abstract_bgp_rr": "evpn_bgp_rr", - "abstract_dhcp": "base_dhcp", - "abstract_extra_config_bootstrap": "extra_config_bootstrap_11_1", - "abstract_extra_config_leaf": "extra_config_leaf", - "abstract_extra_config_spine": "extra_config_spine", - "abstract_extra_config_tor": "extra_config_tor", - "abstract_feature_leaf": "base_feature_leaf_upg", - "abstract_feature_spine": "base_feature_spine_upg", - "abstract_isis": "base_isis_level2", - "abstract_isis_interface": "isis_interface", - "abstract_loopback_interface": "int_fabric_loopback_11_1", - "abstract_multicast": "base_multicast_11_1", - "abstract_ospf": "base_ospf", - "abstract_ospf_interface": "ospf_interface_11_1", - "abstract_pim_interface": "pim_interface", - "abstract_route_map": "route_map", - "abstract_routed_host": "int_routed_host", - "abstract_trunk_host": "int_trunk_host", - "abstract_vlan_interface": "int_fabric_vlan_11_1", - "abstract_vpc_domain": "base_vpc_domain_11_1", - "default_network": "Default_Network_Universal", - "default_pvlan_sec_network": "", - "default_vrf": "Default_VRF_Universal", - "enableRealTimeBackup": "", - "enableScheduledBackup": "", - "network_extension_template": "Default_Network_Extension_Universal", - "scheduledTime": "", - "temp_anycast_gateway": "anycast_gateway", - "temp_vpc_domain_mgmt": "vpc_domain_mgmt", - "temp_vpc_peer_link": "int_vpc_peer_link_po", - "vrf_extension_template": "Default_VRF_Extension_Universal" - }, - "operStatus": "HEALTHY", - "provisionMode": "DCNMTopDown", - "replicationMode": "Multicast", - "siteId": "65001", - "templateName": "Easy_Fabric", - "vrfExtensionTemplate": "Default_VRF_Extension_Universal", - "vrfTemplate": "Default_VRF_Universal" - } - ], - "MESSAGE": "OK", - "METHOD": "GET", - "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/rest/control/fabrics", - "RETURN_CODE": 200 - }, - "test_fabric_create_00032a": { - "DATA": [], - "MESSAGE": "OK", - "METHOD": "GET", - "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/rest/control/fabrics", - "RETURN_CODE": 200 - }, - "test_fabric_create_00033a": { - "DATA": [], - "MESSAGE": "OK", - "METHOD": "GET", - "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/rest/control/fabrics", - "RETURN_CODE": 200 - }, - "test_fabric_create_bulk_00030a": { - "DATA": [], - "MESSAGE": "OK", - "METHOD": "GET", - "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/rest/control/fabrics", - "RETURN_CODE": 200 - }, - "test_fabric_create_bulk_00031a": { - "DATA": [ - { - "asn": "65001", - "createdOn": 1711411093680, - "deviceType": "n9k", - "fabricId": "FABRIC-2", - "fabricName": "f1", - "fabricTechnology": "VXLANFabric", - "fabricTechnologyFriendly": "VXLAN EVPN", - "fabricType": "Switch_Fabric", - "fabricTypeFriendly": "Switch Fabric", - "id": 2, - "modifiedOn": 1711411096857, - "networkExtensionTemplate": "Default_Network_Extension_Universal", - "networkTemplate": "Default_Network_Universal", - "nvPairs": { - "AAA_REMOTE_IP_ENABLED": "false", - "AAA_SERVER_CONF": "", - "ACTIVE_MIGRATION": "false", - "ADVERTISE_PIP_BGP": "false", - "ADVERTISE_PIP_ON_BORDER": "true", - "AGENT_INTF": "eth0", - "AI_ML_QOS_POLICY": "AI_Fabric_QOS_400G", - "ALLOW_NXC": "true", - "ALLOW_NXC_PREV": "true", - "ANYCAST_BGW_ADVERTISE_PIP": "false", - "ANYCAST_GW_MAC": "2020.0000.00aa", - "ANYCAST_LB_ID": "", - "ANYCAST_RP_IP_RANGE": "10.254.254.0/24", - "ANYCAST_RP_IP_RANGE_INTERNAL": "", - "AUTO_SYMMETRIC_DEFAULT_VRF": "false", - "AUTO_SYMMETRIC_VRF_LITE": "false", - "AUTO_UNIQUE_VRF_LITE_IP_PREFIX": "false", - "AUTO_UNIQUE_VRF_LITE_IP_PREFIX_PREV": "false", - "AUTO_VRFLITE_IFC_DEFAULT_VRF": "false", - "BANNER": "", - "BFD_AUTH_ENABLE": "false", - "BFD_AUTH_KEY": "", - "BFD_AUTH_KEY_ID": "", - "BFD_ENABLE": "false", - "BFD_ENABLE_PREV": "", - "BFD_IBGP_ENABLE": "false", - "BFD_ISIS_ENABLE": "false", - "BFD_OSPF_ENABLE": "false", - "BFD_PIM_ENABLE": "false", - "BGP_AS": "65001", - "BGP_AS_PREV": "65001", - "BGP_AUTH_ENABLE": "false", - "BGP_AUTH_KEY": "", - "BGP_AUTH_KEY_TYPE": "3", - "BGP_LB_ID": "0", - "BOOTSTRAP_CONF": "", - "BOOTSTRAP_ENABLE": "false", - "BOOTSTRAP_ENABLE_PREV": "false", - "BOOTSTRAP_MULTISUBNET": "#Scope_Start_IP, Scope_End_IP, Scope_Default_Gateway, Scope_Subnet_Prefix", - "BOOTSTRAP_MULTISUBNET_INTERNAL": "", - "BRFIELD_DEBUG_FLAG": "Disable", - "BROWNFIELD_NETWORK_NAME_FORMAT": "Auto_Net_VNI$$VNI$$_VLAN$$VLAN_ID$$", - "BROWNFIELD_SKIP_OVERLAY_NETWORK_ATTACHMENTS": "false", - "CDP_ENABLE": "false", - "COPP_POLICY": "strict", - "DCI_SUBNET_RANGE": "10.33.0.0/16", - "DCI_SUBNET_TARGET_MASK": "30", - "DEAFULT_QUEUING_POLICY_CLOUDSCALE": "queuing_policy_default_8q_cloudscale", - "DEAFULT_QUEUING_POLICY_OTHER": "queuing_policy_default_other", - "DEAFULT_QUEUING_POLICY_R_SERIES": "queuing_policy_default_r_series", - "DEFAULT_VRF_REDIS_BGP_RMAP": "", - "DEPLOYMENT_FREEZE": "false", - "DHCP_ENABLE": "false", - "DHCP_END": "", - "DHCP_END_INTERNAL": "", - "DHCP_IPV6_ENABLE": "", - "DHCP_IPV6_ENABLE_INTERNAL": "", - "DHCP_START": "", - "DHCP_START_INTERNAL": "", - "DNS_SERVER_IP_LIST": "", - "DNS_SERVER_VRF": "", - "DOMAIN_NAME_INTERNAL": "", - "ENABLE_AAA": "false", - "ENABLE_AGENT": "false", - "ENABLE_AI_ML_QOS_POLICY": "false", - "ENABLE_AI_ML_QOS_POLICY_FLAP": "false", - "ENABLE_DEFAULT_QUEUING_POLICY": "false", - "ENABLE_EVPN": "true", - "ENABLE_FABRIC_VPC_DOMAIN_ID": "false", - "ENABLE_FABRIC_VPC_DOMAIN_ID_PREV": "false", - "ENABLE_L3VNI_NO_VLAN": "false", - "ENABLE_MACSEC": "false", - "ENABLE_NETFLOW": "false", - "ENABLE_NETFLOW_PREV": "false", - "ENABLE_NGOAM": "true", - "ENABLE_NXAPI": "true", - "ENABLE_NXAPI_HTTP": "true", - "ENABLE_PBR": "false", - "ENABLE_PVLAN": "false", - "ENABLE_PVLAN_PREV": "false", - "ENABLE_SGT": "false", - "ENABLE_SGT_PREV": "false", - "ENABLE_TENANT_DHCP": "true", - "ENABLE_TRM": "false", - "ENABLE_VPC_PEER_LINK_NATIVE_VLAN": "false", - "ESR_OPTION": "PBR", - "EXTRA_CONF_INTRA_LINKS": "", - "EXTRA_CONF_LEAF": "", - "EXTRA_CONF_SPINE": "", - "EXTRA_CONF_TOR": "", - "EXT_FABRIC_TYPE": "", - "FABRIC_INTERFACE_TYPE": "p2p", - "FABRIC_MTU": "9216", - "FABRIC_MTU_PREV": "9216", - "FABRIC_NAME": "f1", - "FABRIC_TYPE": "Switch_Fabric", - "FABRIC_VPC_DOMAIN_ID": "", - "FABRIC_VPC_DOMAIN_ID_PREV": "", - "FABRIC_VPC_QOS": "false", - "FABRIC_VPC_QOS_POLICY_NAME": "spine_qos_for_fabric_vpc_peering", - "FEATURE_PTP": "false", - "FEATURE_PTP_INTERNAL": "false", - "FF": "Easy_Fabric", - "GRFIELD_DEBUG_FLAG": "Disable", - "HD_TIME": "180", - "HOST_INTF_ADMIN_STATE": "true", - "IBGP_PEER_TEMPLATE": "", - "IBGP_PEER_TEMPLATE_LEAF": "", - "INBAND_DHCP_SERVERS": "", - "INBAND_MGMT": "false", - "INBAND_MGMT_PREV": "false", - "ISIS_AREA_NUM": "0001", - "ISIS_AREA_NUM_PREV": "", - "ISIS_AUTH_ENABLE": "false", - "ISIS_AUTH_KEY": "", - "ISIS_AUTH_KEYCHAIN_KEY_ID": "", - "ISIS_AUTH_KEYCHAIN_NAME": "", - "ISIS_LEVEL": "level-2", - "ISIS_OVERLOAD_ELAPSE_TIME": "", - "ISIS_OVERLOAD_ENABLE": "", - "ISIS_P2P_ENABLE": "", - "L2_HOST_INTF_MTU": "9216", - "L2_HOST_INTF_MTU_PREV": "9216", - "L2_SEGMENT_ID_RANGE": "30000-49000", - "L3VNI_MCAST_GROUP": "", - "L3_PARTITION_ID_RANGE": "50000-59000", - "LINK_STATE_ROUTING": "ospf", - "LINK_STATE_ROUTING_TAG": "UNDERLAY", - "LINK_STATE_ROUTING_TAG_PREV": "UNDERLAY", - "LOOPBACK0_IPV6_RANGE": "", - "LOOPBACK0_IP_RANGE": "10.2.0.0/22", - "LOOPBACK1_IPV6_RANGE": "", - "LOOPBACK1_IP_RANGE": "10.3.0.0/22", - "MACSEC_ALGORITHM": "", - "MACSEC_CIPHER_SUITE": "", - "MACSEC_FALLBACK_ALGORITHM": "", - "MACSEC_FALLBACK_KEY_STRING": "", - "MACSEC_KEY_STRING": "", - "MACSEC_REPORT_TIMER": "", - "MGMT_GW": "", - "MGMT_GW_INTERNAL": "", - "MGMT_PREFIX": "", - "MGMT_PREFIX_INTERNAL": "", - "MGMT_V6PREFIX": "", - "MGMT_V6PREFIX_INTERNAL": "", - "MPLS_HANDOFF": "false", - "MPLS_ISIS_AREA_NUM": "0001", - "MPLS_ISIS_AREA_NUM_PREV": "", - "MPLS_LB_ID": "", - "MPLS_LOOPBACK_IP_RANGE": "", - "MSO_CONNECTIVITY_DEPLOYED": "", - "MSO_CONTROLER_ID": "", - "MSO_SITE_GROUP_NAME": "", - "MSO_SITE_ID": "", - "MST_INSTANCE_RANGE": "", - "MULTICAST_GROUP_SUBNET": "239.1.1.0/25", - "NETFLOW_EXPORTER_LIST": "", - "NETFLOW_MONITOR_LIST": "", - "NETFLOW_RECORD_LIST": "", - "NETWORK_VLAN_RANGE": "2300-2999", - "NTP_SERVER_IP_LIST": "", - "NTP_SERVER_VRF": "", - "NVE_LB_ID": "1", - "NXAPI_HTTPS_PORT": "443", - "NXAPI_HTTP_PORT": "80", - "NXC_DEST_VRF": "", - "NXC_PROXY_PORT": "8080", - "NXC_PROXY_SERVER": "", - "NXC_SRC_INTF": "", - "OBJECT_TRACKING_NUMBER_RANGE": "100-299", - "OSPF_AREA_ID": "0.0.0.0", - "OSPF_AUTH_ENABLE": "false", - "OSPF_AUTH_KEY": "", - "OSPF_AUTH_KEY_ID": "", - "OVERLAY_MODE": "cli", - "OVERLAY_MODE_PREV": "cli", - "OVERWRITE_GLOBAL_NXC": "false", - "PER_VRF_LOOPBACK_AUTO_PROVISION": "false", - "PER_VRF_LOOPBACK_AUTO_PROVISION_PREV": "false", - "PER_VRF_LOOPBACK_AUTO_PROVISION_V6": "false", - "PER_VRF_LOOPBACK_AUTO_PROVISION_V6_PREV": "false", - "PER_VRF_LOOPBACK_IP_RANGE": "", - "PER_VRF_LOOPBACK_IP_RANGE_V6": "", - "PHANTOM_RP_LB_ID1": "", - "PHANTOM_RP_LB_ID2": "", - "PHANTOM_RP_LB_ID3": "", - "PHANTOM_RP_LB_ID4": "", - "PIM_HELLO_AUTH_ENABLE": "false", - "PIM_HELLO_AUTH_KEY": "", - "PM_ENABLE": "false", - "PM_ENABLE_PREV": "false", - "POWER_REDUNDANCY_MODE": "ps-redundant", - "PREMSO_PARENT_FABRIC": "", - "PTP_DOMAIN_ID": "", - "PTP_LB_ID": "", - "REPLICATION_MODE": "Multicast", - "ROUTER_ID_RANGE": "", - "ROUTE_MAP_SEQUENCE_NUMBER_RANGE": "1-65534", - "RP_COUNT": "2", - "RP_LB_ID": "254", - "RP_MODE": "asm", - "RR_COUNT": "2", - "SEED_SWITCH_CORE_INTERFACES": "", - "SERVICE_NETWORK_VLAN_RANGE": "3000-3199", - "SGT_ID_RANGE": "", - "SGT_NAME_PREFIX": "", - "SGT_PREPROVISION": "", - "SITE_ID": "65001", - "SLA_ID_RANGE": "10000-19999", - "SNMP_SERVER_HOST_TRAP": "true", - "SPINE_COUNT": "0", - "SPINE_SWITCH_CORE_INTERFACES": "", - "SSPINE_ADD_DEL_DEBUG_FLAG": "Disable", - "SSPINE_COUNT": "0", - "STATIC_UNDERLAY_IP_ALLOC": "false", - "STP_BRIDGE_PRIORITY": "", - "STP_ROOT_OPTION": "unmanaged", - "STP_VLAN_RANGE": "", - "STRICT_CC_MODE": "false", - "SUBINTERFACE_RANGE": "2-511", - "SUBNET_RANGE": "10.4.0.0/16", - "SUBNET_TARGET_MASK": "30", - "SYSLOG_SERVER_IP_LIST": "", - "SYSLOG_SERVER_VRF": "", - "SYSLOG_SEV": "", - "TCAM_ALLOCATION": "true", - "TOPDOWN_CONFIG_RM_TRACKING": "notstarted", - "UNDERLAY_IS_V6": "false", - "UNNUM_BOOTSTRAP_LB_ID": "", - "UNNUM_DHCP_END": "", - "UNNUM_DHCP_END_INTERNAL": "", - "UNNUM_DHCP_START": "", - "UNNUM_DHCP_START_INTERNAL": "", - "UPGRADE_FROM_VERSION": "", - "USE_LINK_LOCAL": "true", - "V6_SUBNET_RANGE": "", - "V6_SUBNET_TARGET_MASK": "126", - "VPC_AUTO_RECOVERY_TIME": "360", - "VPC_DELAY_RESTORE": "150", - "VPC_DELAY_RESTORE_TIME": "60", - "VPC_DOMAIN_ID_RANGE": "1-1000", - "VPC_ENABLE_IPv6_ND_SYNC": "true", - "VPC_PEER_KEEP_ALIVE_OPTION": "management", - "VPC_PEER_LINK_PO": "500", - "VPC_PEER_LINK_VLAN": "3600", - "VRF_LITE_AUTOCONFIG": "Manual", - "VRF_VLAN_RANGE": "2000-2299", - "abstract_anycast_rp": "anycast_rp", - "abstract_bgp": "base_bgp", - "abstract_bgp_neighbor": "evpn_bgp_rr_neighbor", - "abstract_bgp_rr": "evpn_bgp_rr", - "abstract_dhcp": "base_dhcp", - "abstract_extra_config_bootstrap": "extra_config_bootstrap_11_1", - "abstract_extra_config_leaf": "extra_config_leaf", - "abstract_extra_config_spine": "extra_config_spine", - "abstract_extra_config_tor": "extra_config_tor", - "abstract_feature_leaf": "base_feature_leaf_upg", - "abstract_feature_spine": "base_feature_spine_upg", - "abstract_isis": "base_isis_level2", - "abstract_isis_interface": "isis_interface", - "abstract_loopback_interface": "int_fabric_loopback_11_1", - "abstract_multicast": "base_multicast_11_1", - "abstract_ospf": "base_ospf", - "abstract_ospf_interface": "ospf_interface_11_1", - "abstract_pim_interface": "pim_interface", - "abstract_route_map": "route_map", - "abstract_routed_host": "int_routed_host", - "abstract_trunk_host": "int_trunk_host", - "abstract_vlan_interface": "int_fabric_vlan_11_1", - "abstract_vpc_domain": "base_vpc_domain_11_1", - "default_network": "Default_Network_Universal", - "default_pvlan_sec_network": "", - "default_vrf": "Default_VRF_Universal", - "enableRealTimeBackup": "", - "enableScheduledBackup": "", - "network_extension_template": "Default_Network_Extension_Universal", - "scheduledTime": "", - "temp_anycast_gateway": "anycast_gateway", - "temp_vpc_domain_mgmt": "vpc_domain_mgmt", - "temp_vpc_peer_link": "int_vpc_peer_link_po", - "vrf_extension_template": "Default_VRF_Extension_Universal" - }, - "operStatus": "HEALTHY", - "provisionMode": "DCNMTopDown", - "replicationMode": "Multicast", - "siteId": "65001", - "templateName": "Easy_Fabric", - "vrfExtensionTemplate": "Default_VRF_Extension_Universal", - "vrfTemplate": "Default_VRF_Universal" - } - ], - "MESSAGE": "OK", - "METHOD": "GET", - "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/rest/control/fabrics", - "RETURN_CODE": 200 - }, - "test_fabric_create_bulk_00032a": { - "DATA": [], - "MESSAGE": "OK", - "METHOD": "GET", - "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/rest/control/fabrics", - "RETURN_CODE": 200 - }, - "test_fabric_create_bulk_00033a": { - "DATA": [], - "MESSAGE": "OK", - "METHOD": "GET", - "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/rest/control/fabrics", - "RETURN_CODE": 200 - }, - "test_fabric_delete_00040a": { - "DATA": [ - { - "asn": "65001", - "createdOn": 1711411093680, - "deviceType": "n9k", - "fabricId": "FABRIC-2", - "fabricName": "f1", - "fabricTechnology": "VXLANFabric", - "fabricTechnologyFriendly": "VXLAN EVPN", - "fabricType": "Switch_Fabric", - "fabricTypeFriendly": "Switch Fabric", - "id": 2, - "modifiedOn": 1711411096857, - "networkExtensionTemplate": "Default_Network_Extension_Universal", - "networkTemplate": "Default_Network_Universal", - "nvPairs": { - "AAA_REMOTE_IP_ENABLED": "false", - "AAA_SERVER_CONF": "", - "ACTIVE_MIGRATION": "false", - "ADVERTISE_PIP_BGP": "false", - "ADVERTISE_PIP_ON_BORDER": "true", - "AGENT_INTF": "eth0", - "AI_ML_QOS_POLICY": "AI_Fabric_QOS_400G", - "ALLOW_NXC": "true", - "ALLOW_NXC_PREV": "true", - "ANYCAST_BGW_ADVERTISE_PIP": "false", - "ANYCAST_GW_MAC": "2020.0000.00aa", - "ANYCAST_LB_ID": "", - "ANYCAST_RP_IP_RANGE": "10.254.254.0/24", - "ANYCAST_RP_IP_RANGE_INTERNAL": "", - "AUTO_SYMMETRIC_DEFAULT_VRF": "false", - "AUTO_SYMMETRIC_VRF_LITE": "false", - "AUTO_UNIQUE_VRF_LITE_IP_PREFIX": "false", - "AUTO_UNIQUE_VRF_LITE_IP_PREFIX_PREV": "false", - "AUTO_VRFLITE_IFC_DEFAULT_VRF": "false", - "BANNER": "", - "BFD_AUTH_ENABLE": "false", - "BFD_AUTH_KEY": "", - "BFD_AUTH_KEY_ID": "", - "BFD_ENABLE": "false", - "BFD_ENABLE_PREV": "", - "BFD_IBGP_ENABLE": "false", - "BFD_ISIS_ENABLE": "false", - "BFD_OSPF_ENABLE": "false", - "BFD_PIM_ENABLE": "false", - "BGP_AS": "65001", - "BGP_AS_PREV": "65001", - "BGP_AUTH_ENABLE": "false", - "BGP_AUTH_KEY": "", - "BGP_AUTH_KEY_TYPE": "3", - "BGP_LB_ID": "0", - "BOOTSTRAP_CONF": "", - "BOOTSTRAP_ENABLE": "false", - "BOOTSTRAP_ENABLE_PREV": "false", - "BOOTSTRAP_MULTISUBNET": "#Scope_Start_IP, Scope_End_IP, Scope_Default_Gateway, Scope_Subnet_Prefix", - "BOOTSTRAP_MULTISUBNET_INTERNAL": "", - "BRFIELD_DEBUG_FLAG": "Disable", - "BROWNFIELD_NETWORK_NAME_FORMAT": "Auto_Net_VNI$$VNI$$_VLAN$$VLAN_ID$$", - "BROWNFIELD_SKIP_OVERLAY_NETWORK_ATTACHMENTS": "false", - "CDP_ENABLE": "false", - "COPP_POLICY": "strict", - "DCI_SUBNET_RANGE": "10.33.0.0/16", - "DCI_SUBNET_TARGET_MASK": "30", - "DEAFULT_QUEUING_POLICY_CLOUDSCALE": "queuing_policy_default_8q_cloudscale", - "DEAFULT_QUEUING_POLICY_OTHER": "queuing_policy_default_other", - "DEAFULT_QUEUING_POLICY_R_SERIES": "queuing_policy_default_r_series", - "DEFAULT_VRF_REDIS_BGP_RMAP": "", - "DEPLOYMENT_FREEZE": "false", - "DHCP_ENABLE": "false", - "DHCP_END": "", - "DHCP_END_INTERNAL": "", - "DHCP_IPV6_ENABLE": "", - "DHCP_IPV6_ENABLE_INTERNAL": "", - "DHCP_START": "", - "DHCP_START_INTERNAL": "", - "DNS_SERVER_IP_LIST": "", - "DNS_SERVER_VRF": "", - "DOMAIN_NAME_INTERNAL": "", - "ENABLE_AAA": "false", - "ENABLE_AGENT": "false", - "ENABLE_AI_ML_QOS_POLICY": "false", - "ENABLE_AI_ML_QOS_POLICY_FLAP": "false", - "ENABLE_DEFAULT_QUEUING_POLICY": "false", - "ENABLE_EVPN": "true", - "ENABLE_FABRIC_VPC_DOMAIN_ID": "false", - "ENABLE_FABRIC_VPC_DOMAIN_ID_PREV": "false", - "ENABLE_L3VNI_NO_VLAN": "false", - "ENABLE_MACSEC": "false", - "ENABLE_NETFLOW": "false", - "ENABLE_NETFLOW_PREV": "false", - "ENABLE_NGOAM": "true", - "ENABLE_NXAPI": "true", - "ENABLE_NXAPI_HTTP": "true", - "ENABLE_PBR": "false", - "ENABLE_PVLAN": "false", - "ENABLE_PVLAN_PREV": "false", - "ENABLE_SGT": "false", - "ENABLE_SGT_PREV": "false", - "ENABLE_TENANT_DHCP": "true", - "ENABLE_TRM": "false", - "ENABLE_VPC_PEER_LINK_NATIVE_VLAN": "false", - "ESR_OPTION": "PBR", - "EXTRA_CONF_INTRA_LINKS": "", - "EXTRA_CONF_LEAF": "", - "EXTRA_CONF_SPINE": "", - "EXTRA_CONF_TOR": "", - "EXT_FABRIC_TYPE": "", - "FABRIC_INTERFACE_TYPE": "p2p", - "FABRIC_MTU": "9216", - "FABRIC_MTU_PREV": "9216", - "FABRIC_NAME": "f1", - "FABRIC_TYPE": "Switch_Fabric", - "FABRIC_VPC_DOMAIN_ID": "", - "FABRIC_VPC_DOMAIN_ID_PREV": "", - "FABRIC_VPC_QOS": "false", - "FABRIC_VPC_QOS_POLICY_NAME": "spine_qos_for_fabric_vpc_peering", - "FEATURE_PTP": "false", - "FEATURE_PTP_INTERNAL": "false", - "FF": "Easy_Fabric", - "GRFIELD_DEBUG_FLAG": "Disable", - "HD_TIME": "180", - "HOST_INTF_ADMIN_STATE": "true", - "IBGP_PEER_TEMPLATE": "", - "IBGP_PEER_TEMPLATE_LEAF": "", - "INBAND_DHCP_SERVERS": "", - "INBAND_MGMT": "false", - "INBAND_MGMT_PREV": "false", - "ISIS_AREA_NUM": "0001", - "ISIS_AREA_NUM_PREV": "", - "ISIS_AUTH_ENABLE": "false", - "ISIS_AUTH_KEY": "", - "ISIS_AUTH_KEYCHAIN_KEY_ID": "", - "ISIS_AUTH_KEYCHAIN_NAME": "", - "ISIS_LEVEL": "level-2", - "ISIS_OVERLOAD_ELAPSE_TIME": "", - "ISIS_OVERLOAD_ENABLE": "", - "ISIS_P2P_ENABLE": "", - "L2_HOST_INTF_MTU": "9216", - "L2_HOST_INTF_MTU_PREV": "9216", - "L2_SEGMENT_ID_RANGE": "30000-49000", - "L3VNI_MCAST_GROUP": "", - "L3_PARTITION_ID_RANGE": "50000-59000", - "LINK_STATE_ROUTING": "ospf", - "LINK_STATE_ROUTING_TAG": "UNDERLAY", - "LINK_STATE_ROUTING_TAG_PREV": "UNDERLAY", - "LOOPBACK0_IPV6_RANGE": "", - "LOOPBACK0_IP_RANGE": "10.2.0.0/22", - "LOOPBACK1_IPV6_RANGE": "", - "LOOPBACK1_IP_RANGE": "10.3.0.0/22", - "MACSEC_ALGORITHM": "", - "MACSEC_CIPHER_SUITE": "", - "MACSEC_FALLBACK_ALGORITHM": "", - "MACSEC_FALLBACK_KEY_STRING": "", - "MACSEC_KEY_STRING": "", - "MACSEC_REPORT_TIMER": "", - "MGMT_GW": "", - "MGMT_GW_INTERNAL": "", - "MGMT_PREFIX": "", - "MGMT_PREFIX_INTERNAL": "", - "MGMT_V6PREFIX": "", - "MGMT_V6PREFIX_INTERNAL": "", - "MPLS_HANDOFF": "false", - "MPLS_ISIS_AREA_NUM": "0001", - "MPLS_ISIS_AREA_NUM_PREV": "", - "MPLS_LB_ID": "", - "MPLS_LOOPBACK_IP_RANGE": "", - "MSO_CONNECTIVITY_DEPLOYED": "", - "MSO_CONTROLER_ID": "", - "MSO_SITE_GROUP_NAME": "", - "MSO_SITE_ID": "", - "MST_INSTANCE_RANGE": "", - "MULTICAST_GROUP_SUBNET": "239.1.1.0/25", - "NETFLOW_EXPORTER_LIST": "", - "NETFLOW_MONITOR_LIST": "", - "NETFLOW_RECORD_LIST": "", - "NETWORK_VLAN_RANGE": "2300-2999", - "NTP_SERVER_IP_LIST": "", - "NTP_SERVER_VRF": "", - "NVE_LB_ID": "1", - "NXAPI_HTTPS_PORT": "443", - "NXAPI_HTTP_PORT": "80", - "NXC_DEST_VRF": "", - "NXC_PROXY_PORT": "8080", - "NXC_PROXY_SERVER": "", - "NXC_SRC_INTF": "", - "OBJECT_TRACKING_NUMBER_RANGE": "100-299", - "OSPF_AREA_ID": "0.0.0.0", - "OSPF_AUTH_ENABLE": "false", - "OSPF_AUTH_KEY": "", - "OSPF_AUTH_KEY_ID": "", - "OVERLAY_MODE": "cli", - "OVERLAY_MODE_PREV": "cli", - "OVERWRITE_GLOBAL_NXC": "false", - "PER_VRF_LOOPBACK_AUTO_PROVISION": "false", - "PER_VRF_LOOPBACK_AUTO_PROVISION_PREV": "false", - "PER_VRF_LOOPBACK_AUTO_PROVISION_V6": "false", - "PER_VRF_LOOPBACK_AUTO_PROVISION_V6_PREV": "false", - "PER_VRF_LOOPBACK_IP_RANGE": "", - "PER_VRF_LOOPBACK_IP_RANGE_V6": "", - "PHANTOM_RP_LB_ID1": "", - "PHANTOM_RP_LB_ID2": "", - "PHANTOM_RP_LB_ID3": "", - "PHANTOM_RP_LB_ID4": "", - "PIM_HELLO_AUTH_ENABLE": "false", - "PIM_HELLO_AUTH_KEY": "", - "PM_ENABLE": "false", - "PM_ENABLE_PREV": "false", - "POWER_REDUNDANCY_MODE": "ps-redundant", - "PREMSO_PARENT_FABRIC": "", - "PTP_DOMAIN_ID": "", - "PTP_LB_ID": "", - "REPLICATION_MODE": "Multicast", - "ROUTER_ID_RANGE": "", - "ROUTE_MAP_SEQUENCE_NUMBER_RANGE": "1-65534", - "RP_COUNT": "2", - "RP_LB_ID": "254", - "RP_MODE": "asm", - "RR_COUNT": "2", - "SEED_SWITCH_CORE_INTERFACES": "", - "SERVICE_NETWORK_VLAN_RANGE": "3000-3199", - "SGT_ID_RANGE": "", - "SGT_NAME_PREFIX": "", - "SGT_PREPROVISION": "", - "SITE_ID": "65001", - "SLA_ID_RANGE": "10000-19999", - "SNMP_SERVER_HOST_TRAP": "true", - "SPINE_COUNT": "0", - "SPINE_SWITCH_CORE_INTERFACES": "", - "SSPINE_ADD_DEL_DEBUG_FLAG": "Disable", - "SSPINE_COUNT": "0", - "STATIC_UNDERLAY_IP_ALLOC": "false", - "STP_BRIDGE_PRIORITY": "", - "STP_ROOT_OPTION": "unmanaged", - "STP_VLAN_RANGE": "", - "STRICT_CC_MODE": "false", - "SUBINTERFACE_RANGE": "2-511", - "SUBNET_RANGE": "10.4.0.0/16", - "SUBNET_TARGET_MASK": "30", - "SYSLOG_SERVER_IP_LIST": "", - "SYSLOG_SERVER_VRF": "", - "SYSLOG_SEV": "", - "TCAM_ALLOCATION": "true", - "TOPDOWN_CONFIG_RM_TRACKING": "notstarted", - "UNDERLAY_IS_V6": "false", - "UNNUM_BOOTSTRAP_LB_ID": "", - "UNNUM_DHCP_END": "", - "UNNUM_DHCP_END_INTERNAL": "", - "UNNUM_DHCP_START": "", - "UNNUM_DHCP_START_INTERNAL": "", - "UPGRADE_FROM_VERSION": "", - "USE_LINK_LOCAL": "true", - "V6_SUBNET_RANGE": "", - "V6_SUBNET_TARGET_MASK": "126", - "VPC_AUTO_RECOVERY_TIME": "360", - "VPC_DELAY_RESTORE": "150", - "VPC_DELAY_RESTORE_TIME": "60", - "VPC_DOMAIN_ID_RANGE": "1-1000", - "VPC_ENABLE_IPv6_ND_SYNC": "true", - "VPC_PEER_KEEP_ALIVE_OPTION": "management", - "VPC_PEER_LINK_PO": "500", - "VPC_PEER_LINK_VLAN": "3600", - "VRF_LITE_AUTOCONFIG": "Manual", - "VRF_VLAN_RANGE": "2000-2299", - "abstract_anycast_rp": "anycast_rp", - "abstract_bgp": "base_bgp", - "abstract_bgp_neighbor": "evpn_bgp_rr_neighbor", - "abstract_bgp_rr": "evpn_bgp_rr", - "abstract_dhcp": "base_dhcp", - "abstract_extra_config_bootstrap": "extra_config_bootstrap_11_1", - "abstract_extra_config_leaf": "extra_config_leaf", - "abstract_extra_config_spine": "extra_config_spine", - "abstract_extra_config_tor": "extra_config_tor", - "abstract_feature_leaf": "base_feature_leaf_upg", - "abstract_feature_spine": "base_feature_spine_upg", - "abstract_isis": "base_isis_level2", - "abstract_isis_interface": "isis_interface", - "abstract_loopback_interface": "int_fabric_loopback_11_1", - "abstract_multicast": "base_multicast_11_1", - "abstract_ospf": "base_ospf", - "abstract_ospf_interface": "ospf_interface_11_1", - "abstract_pim_interface": "pim_interface", - "abstract_route_map": "route_map", - "abstract_routed_host": "int_routed_host", - "abstract_trunk_host": "int_trunk_host", - "abstract_vlan_interface": "int_fabric_vlan_11_1", - "abstract_vpc_domain": "base_vpc_domain_11_1", - "default_network": "Default_Network_Universal", - "default_pvlan_sec_network": "", - "default_vrf": "Default_VRF_Universal", - "enableRealTimeBackup": "", - "enableScheduledBackup": "", - "network_extension_template": "Default_Network_Extension_Universal", - "scheduledTime": "", - "temp_anycast_gateway": "anycast_gateway", - "temp_vpc_domain_mgmt": "vpc_domain_mgmt", - "temp_vpc_peer_link": "int_vpc_peer_link_po", - "vrf_extension_template": "Default_VRF_Extension_Universal" - }, - "operStatus": "HEALTHY", - "provisionMode": "DCNMTopDown", - "replicationMode": "Multicast", - "siteId": "65001", - "templateName": "Easy_Fabric", - "vrfExtensionTemplate": "Default_VRF_Extension_Universal", - "vrfTemplate": "Default_VRF_Universal" - } - ], - "MESSAGE": "OK", - "METHOD": "GET", - "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/rest/control/fabrics", - "RETURN_CODE": 200 - }, - "test_fabric_delete_00042a": { - "DATA": [ - { - "asn": "65001", - "createdOn": 1711411093680, - "deviceType": "n9k", - "fabricId": "FABRIC-2", - "fabricName": "f1", - "fabricTechnology": "VXLANFabric", - "fabricTechnologyFriendly": "VXLAN EVPN", - "fabricType": "Switch_Fabric", - "fabricTypeFriendly": "Switch Fabric", - "id": 2, - "modifiedOn": 1711411096857, - "networkExtensionTemplate": "Default_Network_Extension_Universal", - "networkTemplate": "Default_Network_Universal", - "nvPairs": { - "AAA_REMOTE_IP_ENABLED": "false", - "AAA_SERVER_CONF": "", - "ACTIVE_MIGRATION": "false", - "ADVERTISE_PIP_BGP": "false", - "ADVERTISE_PIP_ON_BORDER": "true", - "AGENT_INTF": "eth0", - "AI_ML_QOS_POLICY": "AI_Fabric_QOS_400G", - "ALLOW_NXC": "true", - "ALLOW_NXC_PREV": "true", - "ANYCAST_BGW_ADVERTISE_PIP": "false", - "ANYCAST_GW_MAC": "2020.0000.00aa", - "ANYCAST_LB_ID": "", - "ANYCAST_RP_IP_RANGE": "10.254.254.0/24", - "ANYCAST_RP_IP_RANGE_INTERNAL": "", - "AUTO_SYMMETRIC_DEFAULT_VRF": "false", - "AUTO_SYMMETRIC_VRF_LITE": "false", - "AUTO_UNIQUE_VRF_LITE_IP_PREFIX": "false", - "AUTO_UNIQUE_VRF_LITE_IP_PREFIX_PREV": "false", - "AUTO_VRFLITE_IFC_DEFAULT_VRF": "false", - "BANNER": "", - "BFD_AUTH_ENABLE": "false", - "BFD_AUTH_KEY": "", - "BFD_AUTH_KEY_ID": "", - "BFD_ENABLE": "false", - "BFD_ENABLE_PREV": "", - "BFD_IBGP_ENABLE": "false", - "BFD_ISIS_ENABLE": "false", - "BFD_OSPF_ENABLE": "false", - "BFD_PIM_ENABLE": "false", - "BGP_AS": "65001", - "BGP_AS_PREV": "65001", - "BGP_AUTH_ENABLE": "false", - "BGP_AUTH_KEY": "", - "BGP_AUTH_KEY_TYPE": "3", - "BGP_LB_ID": "0", - "BOOTSTRAP_CONF": "", - "BOOTSTRAP_ENABLE": "false", - "BOOTSTRAP_ENABLE_PREV": "false", - "BOOTSTRAP_MULTISUBNET": "#Scope_Start_IP, Scope_End_IP, Scope_Default_Gateway, Scope_Subnet_Prefix", - "BOOTSTRAP_MULTISUBNET_INTERNAL": "", - "BRFIELD_DEBUG_FLAG": "Disable", - "BROWNFIELD_NETWORK_NAME_FORMAT": "Auto_Net_VNI$$VNI$$_VLAN$$VLAN_ID$$", - "BROWNFIELD_SKIP_OVERLAY_NETWORK_ATTACHMENTS": "false", - "CDP_ENABLE": "false", - "COPP_POLICY": "strict", - "DCI_SUBNET_RANGE": "10.33.0.0/16", - "DCI_SUBNET_TARGET_MASK": "30", - "DEAFULT_QUEUING_POLICY_CLOUDSCALE": "queuing_policy_default_8q_cloudscale", - "DEAFULT_QUEUING_POLICY_OTHER": "queuing_policy_default_other", - "DEAFULT_QUEUING_POLICY_R_SERIES": "queuing_policy_default_r_series", - "DEFAULT_VRF_REDIS_BGP_RMAP": "", - "DEPLOYMENT_FREEZE": "false", - "DHCP_ENABLE": "false", - "DHCP_END": "", - "DHCP_END_INTERNAL": "", - "DHCP_IPV6_ENABLE": "", - "DHCP_IPV6_ENABLE_INTERNAL": "", - "DHCP_START": "", - "DHCP_START_INTERNAL": "", - "DNS_SERVER_IP_LIST": "", - "DNS_SERVER_VRF": "", - "DOMAIN_NAME_INTERNAL": "", - "ENABLE_AAA": "false", - "ENABLE_AGENT": "false", - "ENABLE_AI_ML_QOS_POLICY": "false", - "ENABLE_AI_ML_QOS_POLICY_FLAP": "false", - "ENABLE_DEFAULT_QUEUING_POLICY": "false", - "ENABLE_EVPN": "true", - "ENABLE_FABRIC_VPC_DOMAIN_ID": "false", - "ENABLE_FABRIC_VPC_DOMAIN_ID_PREV": "false", - "ENABLE_L3VNI_NO_VLAN": "false", - "ENABLE_MACSEC": "false", - "ENABLE_NETFLOW": "false", - "ENABLE_NETFLOW_PREV": "false", - "ENABLE_NGOAM": "true", - "ENABLE_NXAPI": "true", - "ENABLE_NXAPI_HTTP": "true", - "ENABLE_PBR": "false", - "ENABLE_PVLAN": "false", - "ENABLE_PVLAN_PREV": "false", - "ENABLE_SGT": "false", - "ENABLE_SGT_PREV": "false", - "ENABLE_TENANT_DHCP": "true", - "ENABLE_TRM": "false", - "ENABLE_VPC_PEER_LINK_NATIVE_VLAN": "false", - "ESR_OPTION": "PBR", - "EXTRA_CONF_INTRA_LINKS": "", - "EXTRA_CONF_LEAF": "", - "EXTRA_CONF_SPINE": "", - "EXTRA_CONF_TOR": "", - "EXT_FABRIC_TYPE": "", - "FABRIC_INTERFACE_TYPE": "p2p", - "FABRIC_MTU": "9216", - "FABRIC_MTU_PREV": "9216", - "FABRIC_NAME": "f1", - "FABRIC_TYPE": "Switch_Fabric", - "FABRIC_VPC_DOMAIN_ID": "", - "FABRIC_VPC_DOMAIN_ID_PREV": "", - "FABRIC_VPC_QOS": "false", - "FABRIC_VPC_QOS_POLICY_NAME": "spine_qos_for_fabric_vpc_peering", - "FEATURE_PTP": "false", - "FEATURE_PTP_INTERNAL": "false", - "FF": "Easy_Fabric", - "GRFIELD_DEBUG_FLAG": "Disable", - "HD_TIME": "180", - "HOST_INTF_ADMIN_STATE": "true", - "IBGP_PEER_TEMPLATE": "", - "IBGP_PEER_TEMPLATE_LEAF": "", - "INBAND_DHCP_SERVERS": "", - "INBAND_MGMT": "false", - "INBAND_MGMT_PREV": "false", - "ISIS_AREA_NUM": "0001", - "ISIS_AREA_NUM_PREV": "", - "ISIS_AUTH_ENABLE": "false", - "ISIS_AUTH_KEY": "", - "ISIS_AUTH_KEYCHAIN_KEY_ID": "", - "ISIS_AUTH_KEYCHAIN_NAME": "", - "ISIS_LEVEL": "level-2", - "ISIS_OVERLOAD_ELAPSE_TIME": "", - "ISIS_OVERLOAD_ENABLE": "", - "ISIS_P2P_ENABLE": "", - "L2_HOST_INTF_MTU": "9216", - "L2_HOST_INTF_MTU_PREV": "9216", - "L2_SEGMENT_ID_RANGE": "30000-49000", - "L3VNI_MCAST_GROUP": "", - "L3_PARTITION_ID_RANGE": "50000-59000", - "LINK_STATE_ROUTING": "ospf", - "LINK_STATE_ROUTING_TAG": "UNDERLAY", - "LINK_STATE_ROUTING_TAG_PREV": "UNDERLAY", - "LOOPBACK0_IPV6_RANGE": "", - "LOOPBACK0_IP_RANGE": "10.2.0.0/22", - "LOOPBACK1_IPV6_RANGE": "", - "LOOPBACK1_IP_RANGE": "10.3.0.0/22", - "MACSEC_ALGORITHM": "", - "MACSEC_CIPHER_SUITE": "", - "MACSEC_FALLBACK_ALGORITHM": "", - "MACSEC_FALLBACK_KEY_STRING": "", - "MACSEC_KEY_STRING": "", - "MACSEC_REPORT_TIMER": "", - "MGMT_GW": "", - "MGMT_GW_INTERNAL": "", - "MGMT_PREFIX": "", - "MGMT_PREFIX_INTERNAL": "", - "MGMT_V6PREFIX": "", - "MGMT_V6PREFIX_INTERNAL": "", - "MPLS_HANDOFF": "false", - "MPLS_ISIS_AREA_NUM": "0001", - "MPLS_ISIS_AREA_NUM_PREV": "", - "MPLS_LB_ID": "", - "MPLS_LOOPBACK_IP_RANGE": "", - "MSO_CONNECTIVITY_DEPLOYED": "", - "MSO_CONTROLER_ID": "", - "MSO_SITE_GROUP_NAME": "", - "MSO_SITE_ID": "", - "MST_INSTANCE_RANGE": "", - "MULTICAST_GROUP_SUBNET": "239.1.1.0/25", - "NETFLOW_EXPORTER_LIST": "", - "NETFLOW_MONITOR_LIST": "", - "NETFLOW_RECORD_LIST": "", - "NETWORK_VLAN_RANGE": "2300-2999", - "NTP_SERVER_IP_LIST": "", - "NTP_SERVER_VRF": "", - "NVE_LB_ID": "1", - "NXAPI_HTTPS_PORT": "443", - "NXAPI_HTTP_PORT": "80", - "NXC_DEST_VRF": "", - "NXC_PROXY_PORT": "8080", - "NXC_PROXY_SERVER": "", - "NXC_SRC_INTF": "", - "OBJECT_TRACKING_NUMBER_RANGE": "100-299", - "OSPF_AREA_ID": "0.0.0.0", - "OSPF_AUTH_ENABLE": "false", - "OSPF_AUTH_KEY": "", - "OSPF_AUTH_KEY_ID": "", - "OVERLAY_MODE": "cli", - "OVERLAY_MODE_PREV": "cli", - "OVERWRITE_GLOBAL_NXC": "false", - "PER_VRF_LOOPBACK_AUTO_PROVISION": "false", - "PER_VRF_LOOPBACK_AUTO_PROVISION_PREV": "false", - "PER_VRF_LOOPBACK_AUTO_PROVISION_V6": "false", - "PER_VRF_LOOPBACK_AUTO_PROVISION_V6_PREV": "false", - "PER_VRF_LOOPBACK_IP_RANGE": "", - "PER_VRF_LOOPBACK_IP_RANGE_V6": "", - "PHANTOM_RP_LB_ID1": "", - "PHANTOM_RP_LB_ID2": "", - "PHANTOM_RP_LB_ID3": "", - "PHANTOM_RP_LB_ID4": "", - "PIM_HELLO_AUTH_ENABLE": "false", - "PIM_HELLO_AUTH_KEY": "", - "PM_ENABLE": "false", - "PM_ENABLE_PREV": "false", - "POWER_REDUNDANCY_MODE": "ps-redundant", - "PREMSO_PARENT_FABRIC": "", - "PTP_DOMAIN_ID": "", - "PTP_LB_ID": "", - "REPLICATION_MODE": "Multicast", - "ROUTER_ID_RANGE": "", - "ROUTE_MAP_SEQUENCE_NUMBER_RANGE": "1-65534", - "RP_COUNT": "2", - "RP_LB_ID": "254", - "RP_MODE": "asm", - "RR_COUNT": "2", - "SEED_SWITCH_CORE_INTERFACES": "", - "SERVICE_NETWORK_VLAN_RANGE": "3000-3199", - "SGT_ID_RANGE": "", - "SGT_NAME_PREFIX": "", - "SGT_PREPROVISION": "", - "SITE_ID": "65001", - "SLA_ID_RANGE": "10000-19999", - "SNMP_SERVER_HOST_TRAP": "true", - "SPINE_COUNT": "0", - "SPINE_SWITCH_CORE_INTERFACES": "", - "SSPINE_ADD_DEL_DEBUG_FLAG": "Disable", - "SSPINE_COUNT": "0", - "STATIC_UNDERLAY_IP_ALLOC": "false", - "STP_BRIDGE_PRIORITY": "", - "STP_ROOT_OPTION": "unmanaged", - "STP_VLAN_RANGE": "", - "STRICT_CC_MODE": "false", - "SUBINTERFACE_RANGE": "2-511", - "SUBNET_RANGE": "10.4.0.0/16", - "SUBNET_TARGET_MASK": "30", - "SYSLOG_SERVER_IP_LIST": "", - "SYSLOG_SERVER_VRF": "", - "SYSLOG_SEV": "", - "TCAM_ALLOCATION": "true", - "TOPDOWN_CONFIG_RM_TRACKING": "notstarted", - "UNDERLAY_IS_V6": "false", - "UNNUM_BOOTSTRAP_LB_ID": "", - "UNNUM_DHCP_END": "", - "UNNUM_DHCP_END_INTERNAL": "", - "UNNUM_DHCP_START": "", - "UNNUM_DHCP_START_INTERNAL": "", - "UPGRADE_FROM_VERSION": "", - "USE_LINK_LOCAL": "true", - "V6_SUBNET_RANGE": "", - "V6_SUBNET_TARGET_MASK": "126", - "VPC_AUTO_RECOVERY_TIME": "360", - "VPC_DELAY_RESTORE": "150", - "VPC_DELAY_RESTORE_TIME": "60", - "VPC_DOMAIN_ID_RANGE": "1-1000", - "VPC_ENABLE_IPv6_ND_SYNC": "true", - "VPC_PEER_KEEP_ALIVE_OPTION": "management", - "VPC_PEER_LINK_PO": "500", - "VPC_PEER_LINK_VLAN": "3600", - "VRF_LITE_AUTOCONFIG": "Manual", - "VRF_VLAN_RANGE": "2000-2299", - "abstract_anycast_rp": "anycast_rp", - "abstract_bgp": "base_bgp", - "abstract_bgp_neighbor": "evpn_bgp_rr_neighbor", - "abstract_bgp_rr": "evpn_bgp_rr", - "abstract_dhcp": "base_dhcp", - "abstract_extra_config_bootstrap": "extra_config_bootstrap_11_1", - "abstract_extra_config_leaf": "extra_config_leaf", - "abstract_extra_config_spine": "extra_config_spine", - "abstract_extra_config_tor": "extra_config_tor", - "abstract_feature_leaf": "base_feature_leaf_upg", - "abstract_feature_spine": "base_feature_spine_upg", - "abstract_isis": "base_isis_level2", - "abstract_isis_interface": "isis_interface", - "abstract_loopback_interface": "int_fabric_loopback_11_1", - "abstract_multicast": "base_multicast_11_1", - "abstract_ospf": "base_ospf", - "abstract_ospf_interface": "ospf_interface_11_1", - "abstract_pim_interface": "pim_interface", - "abstract_route_map": "route_map", - "abstract_routed_host": "int_routed_host", - "abstract_trunk_host": "int_trunk_host", - "abstract_vlan_interface": "int_fabric_vlan_11_1", - "abstract_vpc_domain": "base_vpc_domain_11_1", - "default_network": "Default_Network_Universal", - "default_pvlan_sec_network": "", - "default_vrf": "Default_VRF_Universal", - "enableRealTimeBackup": "", - "enableScheduledBackup": "", - "network_extension_template": "Default_Network_Extension_Universal", - "scheduledTime": "", - "temp_anycast_gateway": "anycast_gateway", - "temp_vpc_domain_mgmt": "vpc_domain_mgmt", - "temp_vpc_peer_link": "int_vpc_peer_link_po", - "vrf_extension_template": "Default_VRF_Extension_Universal" - }, - "operStatus": "HEALTHY", - "provisionMode": "DCNMTopDown", - "replicationMode": "Multicast", - "siteId": "65001", - "templateName": "Easy_Fabric", - "vrfExtensionTemplate": "Default_VRF_Extension_Universal", - "vrfTemplate": "Default_VRF_Universal" - } - ], - "MESSAGE": "OK", - "METHOD": "GET", - "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/rest/control/fabrics", - "RETURN_CODE": 200 - }, - "test_fabric_delete_00043a": { - "DATA": [], - "MESSAGE": "OK", - "METHOD": "GET", - "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/rest/control/fabrics", - "RETURN_CODE": 200 - }, - "test_fabric_delete_00044a": { - "DATA": [ - { - "asn": "65001", - "createdOn": 1711411093680, - "deviceType": "n9k", - "fabricId": "FABRIC-2", - "fabricName": "f1", - "fabricTechnology": "VXLANFabric", - "fabricTechnologyFriendly": "VXLAN EVPN", - "fabricType": "Switch_Fabric", - "fabricTypeFriendly": "Switch Fabric", - "id": 2, - "modifiedOn": 1711411096857, - "networkExtensionTemplate": "Default_Network_Extension_Universal", - "networkTemplate": "Default_Network_Universal", - "nvPairs": { - "AAA_REMOTE_IP_ENABLED": "false", - "AAA_SERVER_CONF": "", - "ACTIVE_MIGRATION": "false", - "ADVERTISE_PIP_BGP": "false", - "ADVERTISE_PIP_ON_BORDER": "true", - "AGENT_INTF": "eth0", - "AI_ML_QOS_POLICY": "AI_Fabric_QOS_400G", - "ALLOW_NXC": "true", - "ALLOW_NXC_PREV": "true", - "ANYCAST_BGW_ADVERTISE_PIP": "false", - "ANYCAST_GW_MAC": "2020.0000.00aa", - "ANYCAST_LB_ID": "", - "ANYCAST_RP_IP_RANGE": "10.254.254.0/24", - "ANYCAST_RP_IP_RANGE_INTERNAL": "", - "AUTO_SYMMETRIC_DEFAULT_VRF": "false", - "AUTO_SYMMETRIC_VRF_LITE": "false", - "AUTO_UNIQUE_VRF_LITE_IP_PREFIX": "false", - "AUTO_UNIQUE_VRF_LITE_IP_PREFIX_PREV": "false", - "AUTO_VRFLITE_IFC_DEFAULT_VRF": "false", - "BANNER": "", - "BFD_AUTH_ENABLE": "false", - "BFD_AUTH_KEY": "", - "BFD_AUTH_KEY_ID": "", - "BFD_ENABLE": "false", - "BFD_ENABLE_PREV": "", - "BFD_IBGP_ENABLE": "false", - "BFD_ISIS_ENABLE": "false", - "BFD_OSPF_ENABLE": "false", - "BFD_PIM_ENABLE": "false", - "BGP_AS": "65001", - "BGP_AS_PREV": "65001", - "BGP_AUTH_ENABLE": "false", - "BGP_AUTH_KEY": "", - "BGP_AUTH_KEY_TYPE": "3", - "BGP_LB_ID": "0", - "BOOTSTRAP_CONF": "", - "BOOTSTRAP_ENABLE": "false", - "BOOTSTRAP_ENABLE_PREV": "false", - "BOOTSTRAP_MULTISUBNET": "#Scope_Start_IP, Scope_End_IP, Scope_Default_Gateway, Scope_Subnet_Prefix", - "BOOTSTRAP_MULTISUBNET_INTERNAL": "", - "BRFIELD_DEBUG_FLAG": "Disable", - "BROWNFIELD_NETWORK_NAME_FORMAT": "Auto_Net_VNI$$VNI$$_VLAN$$VLAN_ID$$", - "BROWNFIELD_SKIP_OVERLAY_NETWORK_ATTACHMENTS": "false", - "CDP_ENABLE": "false", - "COPP_POLICY": "strict", - "DCI_SUBNET_RANGE": "10.33.0.0/16", - "DCI_SUBNET_TARGET_MASK": "30", - "DEAFULT_QUEUING_POLICY_CLOUDSCALE": "queuing_policy_default_8q_cloudscale", - "DEAFULT_QUEUING_POLICY_OTHER": "queuing_policy_default_other", - "DEAFULT_QUEUING_POLICY_R_SERIES": "queuing_policy_default_r_series", - "DEFAULT_VRF_REDIS_BGP_RMAP": "", - "DEPLOYMENT_FREEZE": "false", - "DHCP_ENABLE": "false", - "DHCP_END": "", - "DHCP_END_INTERNAL": "", - "DHCP_IPV6_ENABLE": "", - "DHCP_IPV6_ENABLE_INTERNAL": "", - "DHCP_START": "", - "DHCP_START_INTERNAL": "", - "DNS_SERVER_IP_LIST": "", - "DNS_SERVER_VRF": "", - "DOMAIN_NAME_INTERNAL": "", - "ENABLE_AAA": "false", - "ENABLE_AGENT": "false", - "ENABLE_AI_ML_QOS_POLICY": "false", - "ENABLE_AI_ML_QOS_POLICY_FLAP": "false", - "ENABLE_DEFAULT_QUEUING_POLICY": "false", - "ENABLE_EVPN": "true", - "ENABLE_FABRIC_VPC_DOMAIN_ID": "false", - "ENABLE_FABRIC_VPC_DOMAIN_ID_PREV": "false", - "ENABLE_L3VNI_NO_VLAN": "false", - "ENABLE_MACSEC": "false", - "ENABLE_NETFLOW": "false", - "ENABLE_NETFLOW_PREV": "false", - "ENABLE_NGOAM": "true", - "ENABLE_NXAPI": "true", - "ENABLE_NXAPI_HTTP": "true", - "ENABLE_PBR": "false", - "ENABLE_PVLAN": "false", - "ENABLE_PVLAN_PREV": "false", - "ENABLE_SGT": "false", - "ENABLE_SGT_PREV": "false", - "ENABLE_TENANT_DHCP": "true", - "ENABLE_TRM": "false", - "ENABLE_VPC_PEER_LINK_NATIVE_VLAN": "false", - "ESR_OPTION": "PBR", - "EXTRA_CONF_INTRA_LINKS": "", - "EXTRA_CONF_LEAF": "", - "EXTRA_CONF_SPINE": "", - "EXTRA_CONF_TOR": "", - "EXT_FABRIC_TYPE": "", - "FABRIC_INTERFACE_TYPE": "p2p", - "FABRIC_MTU": "9216", - "FABRIC_MTU_PREV": "9216", - "FABRIC_NAME": "f1", - "FABRIC_TYPE": "Switch_Fabric", - "FABRIC_VPC_DOMAIN_ID": "", - "FABRIC_VPC_DOMAIN_ID_PREV": "", - "FABRIC_VPC_QOS": "false", - "FABRIC_VPC_QOS_POLICY_NAME": "spine_qos_for_fabric_vpc_peering", - "FEATURE_PTP": "false", - "FEATURE_PTP_INTERNAL": "false", - "FF": "Easy_Fabric", - "GRFIELD_DEBUG_FLAG": "Disable", - "HD_TIME": "180", - "HOST_INTF_ADMIN_STATE": "true", - "IBGP_PEER_TEMPLATE": "", - "IBGP_PEER_TEMPLATE_LEAF": "", - "INBAND_DHCP_SERVERS": "", - "INBAND_MGMT": "false", - "INBAND_MGMT_PREV": "false", - "ISIS_AREA_NUM": "0001", - "ISIS_AREA_NUM_PREV": "", - "ISIS_AUTH_ENABLE": "false", - "ISIS_AUTH_KEY": "", - "ISIS_AUTH_KEYCHAIN_KEY_ID": "", - "ISIS_AUTH_KEYCHAIN_NAME": "", - "ISIS_LEVEL": "level-2", - "ISIS_OVERLOAD_ELAPSE_TIME": "", - "ISIS_OVERLOAD_ENABLE": "", - "ISIS_P2P_ENABLE": "", - "L2_HOST_INTF_MTU": "9216", - "L2_HOST_INTF_MTU_PREV": "9216", - "L2_SEGMENT_ID_RANGE": "30000-49000", - "L3VNI_MCAST_GROUP": "", - "L3_PARTITION_ID_RANGE": "50000-59000", - "LINK_STATE_ROUTING": "ospf", - "LINK_STATE_ROUTING_TAG": "UNDERLAY", - "LINK_STATE_ROUTING_TAG_PREV": "UNDERLAY", - "LOOPBACK0_IPV6_RANGE": "", - "LOOPBACK0_IP_RANGE": "10.2.0.0/22", - "LOOPBACK1_IPV6_RANGE": "", - "LOOPBACK1_IP_RANGE": "10.3.0.0/22", - "MACSEC_ALGORITHM": "", - "MACSEC_CIPHER_SUITE": "", - "MACSEC_FALLBACK_ALGORITHM": "", - "MACSEC_FALLBACK_KEY_STRING": "", - "MACSEC_KEY_STRING": "", - "MACSEC_REPORT_TIMER": "", - "MGMT_GW": "", - "MGMT_GW_INTERNAL": "", - "MGMT_PREFIX": "", - "MGMT_PREFIX_INTERNAL": "", - "MGMT_V6PREFIX": "", - "MGMT_V6PREFIX_INTERNAL": "", - "MPLS_HANDOFF": "false", - "MPLS_ISIS_AREA_NUM": "0001", - "MPLS_ISIS_AREA_NUM_PREV": "", - "MPLS_LB_ID": "", - "MPLS_LOOPBACK_IP_RANGE": "", - "MSO_CONNECTIVITY_DEPLOYED": "", - "MSO_CONTROLER_ID": "", - "MSO_SITE_GROUP_NAME": "", - "MSO_SITE_ID": "", - "MST_INSTANCE_RANGE": "", - "MULTICAST_GROUP_SUBNET": "239.1.1.0/25", - "NETFLOW_EXPORTER_LIST": "", - "NETFLOW_MONITOR_LIST": "", - "NETFLOW_RECORD_LIST": "", - "NETWORK_VLAN_RANGE": "2300-2999", - "NTP_SERVER_IP_LIST": "", - "NTP_SERVER_VRF": "", - "NVE_LB_ID": "1", - "NXAPI_HTTPS_PORT": "443", - "NXAPI_HTTP_PORT": "80", - "NXC_DEST_VRF": "", - "NXC_PROXY_PORT": "8080", - "NXC_PROXY_SERVER": "", - "NXC_SRC_INTF": "", - "OBJECT_TRACKING_NUMBER_RANGE": "100-299", - "OSPF_AREA_ID": "0.0.0.0", - "OSPF_AUTH_ENABLE": "false", - "OSPF_AUTH_KEY": "", - "OSPF_AUTH_KEY_ID": "", - "OVERLAY_MODE": "cli", - "OVERLAY_MODE_PREV": "cli", - "OVERWRITE_GLOBAL_NXC": "false", - "PER_VRF_LOOPBACK_AUTO_PROVISION": "false", - "PER_VRF_LOOPBACK_AUTO_PROVISION_PREV": "false", - "PER_VRF_LOOPBACK_AUTO_PROVISION_V6": "false", - "PER_VRF_LOOPBACK_AUTO_PROVISION_V6_PREV": "false", - "PER_VRF_LOOPBACK_IP_RANGE": "", - "PER_VRF_LOOPBACK_IP_RANGE_V6": "", - "PHANTOM_RP_LB_ID1": "", - "PHANTOM_RP_LB_ID2": "", - "PHANTOM_RP_LB_ID3": "", - "PHANTOM_RP_LB_ID4": "", - "PIM_HELLO_AUTH_ENABLE": "false", - "PIM_HELLO_AUTH_KEY": "", - "PM_ENABLE": "false", - "PM_ENABLE_PREV": "false", - "POWER_REDUNDANCY_MODE": "ps-redundant", - "PREMSO_PARENT_FABRIC": "", - "PTP_DOMAIN_ID": "", - "PTP_LB_ID": "", - "REPLICATION_MODE": "Multicast", - "ROUTER_ID_RANGE": "", - "ROUTE_MAP_SEQUENCE_NUMBER_RANGE": "1-65534", - "RP_COUNT": "2", - "RP_LB_ID": "254", - "RP_MODE": "asm", - "RR_COUNT": "2", - "SEED_SWITCH_CORE_INTERFACES": "", - "SERVICE_NETWORK_VLAN_RANGE": "3000-3199", - "SGT_ID_RANGE": "", - "SGT_NAME_PREFIX": "", - "SGT_PREPROVISION": "", - "SITE_ID": "65001", - "SLA_ID_RANGE": "10000-19999", - "SNMP_SERVER_HOST_TRAP": "true", - "SPINE_COUNT": "0", - "SPINE_SWITCH_CORE_INTERFACES": "", - "SSPINE_ADD_DEL_DEBUG_FLAG": "Disable", - "SSPINE_COUNT": "0", - "STATIC_UNDERLAY_IP_ALLOC": "false", - "STP_BRIDGE_PRIORITY": "", - "STP_ROOT_OPTION": "unmanaged", - "STP_VLAN_RANGE": "", - "STRICT_CC_MODE": "false", - "SUBINTERFACE_RANGE": "2-511", - "SUBNET_RANGE": "10.4.0.0/16", - "SUBNET_TARGET_MASK": "30", - "SYSLOG_SERVER_IP_LIST": "", - "SYSLOG_SERVER_VRF": "", - "SYSLOG_SEV": "", - "TCAM_ALLOCATION": "true", - "TOPDOWN_CONFIG_RM_TRACKING": "notstarted", - "UNDERLAY_IS_V6": "false", - "UNNUM_BOOTSTRAP_LB_ID": "", - "UNNUM_DHCP_END": "", - "UNNUM_DHCP_END_INTERNAL": "", - "UNNUM_DHCP_START": "", - "UNNUM_DHCP_START_INTERNAL": "", - "UPGRADE_FROM_VERSION": "", - "USE_LINK_LOCAL": "true", - "V6_SUBNET_RANGE": "", - "V6_SUBNET_TARGET_MASK": "126", - "VPC_AUTO_RECOVERY_TIME": "360", - "VPC_DELAY_RESTORE": "150", - "VPC_DELAY_RESTORE_TIME": "60", - "VPC_DOMAIN_ID_RANGE": "1-1000", - "VPC_ENABLE_IPv6_ND_SYNC": "true", - "VPC_PEER_KEEP_ALIVE_OPTION": "management", - "VPC_PEER_LINK_PO": "500", - "VPC_PEER_LINK_VLAN": "3600", - "VRF_LITE_AUTOCONFIG": "Manual", - "VRF_VLAN_RANGE": "2000-2299", - "abstract_anycast_rp": "anycast_rp", - "abstract_bgp": "base_bgp", - "abstract_bgp_neighbor": "evpn_bgp_rr_neighbor", - "abstract_bgp_rr": "evpn_bgp_rr", - "abstract_dhcp": "base_dhcp", - "abstract_extra_config_bootstrap": "extra_config_bootstrap_11_1", - "abstract_extra_config_leaf": "extra_config_leaf", - "abstract_extra_config_spine": "extra_config_spine", - "abstract_extra_config_tor": "extra_config_tor", - "abstract_feature_leaf": "base_feature_leaf_upg", - "abstract_feature_spine": "base_feature_spine_upg", - "abstract_isis": "base_isis_level2", - "abstract_isis_interface": "isis_interface", - "abstract_loopback_interface": "int_fabric_loopback_11_1", - "abstract_multicast": "base_multicast_11_1", - "abstract_ospf": "base_ospf", - "abstract_ospf_interface": "ospf_interface_11_1", - "abstract_pim_interface": "pim_interface", - "abstract_route_map": "route_map", - "abstract_routed_host": "int_routed_host", - "abstract_trunk_host": "int_trunk_host", - "abstract_vlan_interface": "int_fabric_vlan_11_1", - "abstract_vpc_domain": "base_vpc_domain_11_1", - "default_network": "Default_Network_Universal", - "default_pvlan_sec_network": "", - "default_vrf": "Default_VRF_Universal", - "enableRealTimeBackup": "", - "enableScheduledBackup": "", - "network_extension_template": "Default_Network_Extension_Universal", - "scheduledTime": "", - "temp_anycast_gateway": "anycast_gateway", - "temp_vpc_domain_mgmt": "vpc_domain_mgmt", - "temp_vpc_peer_link": "int_vpc_peer_link_po", - "vrf_extension_template": "Default_VRF_Extension_Universal" - }, - "operStatus": "HEALTHY", - "provisionMode": "DCNMTopDown", - "replicationMode": "Multicast", - "siteId": "65001", - "templateName": "Easy_Fabric", - "vrfExtensionTemplate": "Default_VRF_Extension_Universal", - "vrfTemplate": "Default_VRF_Universal" - } - ], - "MESSAGE": "OK", - "METHOD": "GET", - "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/rest/control/fabrics", - "RETURN_CODE": 200 - }, - "test_fabric_delete_00050a": { - "DATA": [ - { - "asn": "65001", - "createdOn": 1711411093680, - "deviceType": "n9k", - "fabricId": "FABRIC-2", - "fabricName": "f1", - "fabricTechnology": "VXLANFabric", - "fabricTechnologyFriendly": "VXLAN EVPN", - "fabricType": "Switch_Fabric", - "fabricTypeFriendly": "Switch Fabric", - "id": 2, - "modifiedOn": 1711411096857, - "networkExtensionTemplate": "Default_Network_Extension_Universal", - "networkTemplate": "Default_Network_Universal", - "nvPairs": { - "AAA_REMOTE_IP_ENABLED": "false", - "AAA_SERVER_CONF": "", - "ACTIVE_MIGRATION": "false", - "ADVERTISE_PIP_BGP": "false", - "ADVERTISE_PIP_ON_BORDER": "true", - "AGENT_INTF": "eth0", - "AI_ML_QOS_POLICY": "AI_Fabric_QOS_400G", - "ALLOW_NXC": "true", - "ALLOW_NXC_PREV": "true", - "ANYCAST_BGW_ADVERTISE_PIP": "false", - "ANYCAST_GW_MAC": "2020.0000.00aa", - "ANYCAST_LB_ID": "", - "ANYCAST_RP_IP_RANGE": "10.254.254.0/24", - "ANYCAST_RP_IP_RANGE_INTERNAL": "", - "AUTO_SYMMETRIC_DEFAULT_VRF": "false", - "AUTO_SYMMETRIC_VRF_LITE": "false", - "AUTO_UNIQUE_VRF_LITE_IP_PREFIX": "false", - "AUTO_UNIQUE_VRF_LITE_IP_PREFIX_PREV": "false", - "AUTO_VRFLITE_IFC_DEFAULT_VRF": "false", - "BANNER": "", - "BFD_AUTH_ENABLE": "false", - "BFD_AUTH_KEY": "", - "BFD_AUTH_KEY_ID": "", - "BFD_ENABLE": "false", - "BFD_ENABLE_PREV": "", - "BFD_IBGP_ENABLE": "false", - "BFD_ISIS_ENABLE": "false", - "BFD_OSPF_ENABLE": "false", - "BFD_PIM_ENABLE": "false", - "BGP_AS": "65001", - "BGP_AS_PREV": "65001", - "BGP_AUTH_ENABLE": "false", - "BGP_AUTH_KEY": "", - "BGP_AUTH_KEY_TYPE": "3", - "BGP_LB_ID": "0", - "BOOTSTRAP_CONF": "", - "BOOTSTRAP_ENABLE": "false", - "BOOTSTRAP_ENABLE_PREV": "false", - "BOOTSTRAP_MULTISUBNET": "#Scope_Start_IP, Scope_End_IP, Scope_Default_Gateway, Scope_Subnet_Prefix", - "BOOTSTRAP_MULTISUBNET_INTERNAL": "", - "BRFIELD_DEBUG_FLAG": "Disable", - "BROWNFIELD_NETWORK_NAME_FORMAT": "Auto_Net_VNI$$VNI$$_VLAN$$VLAN_ID$$", - "BROWNFIELD_SKIP_OVERLAY_NETWORK_ATTACHMENTS": "false", - "CDP_ENABLE": "false", - "COPP_POLICY": "strict", - "DCI_SUBNET_RANGE": "10.33.0.0/16", - "DCI_SUBNET_TARGET_MASK": "30", - "DEAFULT_QUEUING_POLICY_CLOUDSCALE": "queuing_policy_default_8q_cloudscale", - "DEAFULT_QUEUING_POLICY_OTHER": "queuing_policy_default_other", - "DEAFULT_QUEUING_POLICY_R_SERIES": "queuing_policy_default_r_series", - "DEFAULT_VRF_REDIS_BGP_RMAP": "", - "DEPLOYMENT_FREEZE": "false", - "DHCP_ENABLE": "false", - "DHCP_END": "", - "DHCP_END_INTERNAL": "", - "DHCP_IPV6_ENABLE": "", - "DHCP_IPV6_ENABLE_INTERNAL": "", - "DHCP_START": "", - "DHCP_START_INTERNAL": "", - "DNS_SERVER_IP_LIST": "", - "DNS_SERVER_VRF": "", - "DOMAIN_NAME_INTERNAL": "", - "ENABLE_AAA": "false", - "ENABLE_AGENT": "false", - "ENABLE_AI_ML_QOS_POLICY": "false", - "ENABLE_AI_ML_QOS_POLICY_FLAP": "false", - "ENABLE_DEFAULT_QUEUING_POLICY": "false", - "ENABLE_EVPN": "true", - "ENABLE_FABRIC_VPC_DOMAIN_ID": "false", - "ENABLE_FABRIC_VPC_DOMAIN_ID_PREV": "false", - "ENABLE_L3VNI_NO_VLAN": "false", - "ENABLE_MACSEC": "false", - "ENABLE_NETFLOW": "false", - "ENABLE_NETFLOW_PREV": "false", - "ENABLE_NGOAM": "true", - "ENABLE_NXAPI": "true", - "ENABLE_NXAPI_HTTP": "true", - "ENABLE_PBR": "false", - "ENABLE_PVLAN": "false", - "ENABLE_PVLAN_PREV": "false", - "ENABLE_SGT": "false", - "ENABLE_SGT_PREV": "false", - "ENABLE_TENANT_DHCP": "true", - "ENABLE_TRM": "false", - "ENABLE_VPC_PEER_LINK_NATIVE_VLAN": "false", - "ESR_OPTION": "PBR", - "EXTRA_CONF_INTRA_LINKS": "", - "EXTRA_CONF_LEAF": "", - "EXTRA_CONF_SPINE": "", - "EXTRA_CONF_TOR": "", - "EXT_FABRIC_TYPE": "", - "FABRIC_INTERFACE_TYPE": "p2p", - "FABRIC_MTU": "9216", - "FABRIC_MTU_PREV": "9216", - "FABRIC_NAME": "f1", - "FABRIC_TYPE": "Switch_Fabric", - "FABRIC_VPC_DOMAIN_ID": "", - "FABRIC_VPC_DOMAIN_ID_PREV": "", - "FABRIC_VPC_QOS": "false", - "FABRIC_VPC_QOS_POLICY_NAME": "spine_qos_for_fabric_vpc_peering", - "FEATURE_PTP": "false", - "FEATURE_PTP_INTERNAL": "false", - "FF": "Easy_Fabric", - "GRFIELD_DEBUG_FLAG": "Disable", - "HD_TIME": "180", - "HOST_INTF_ADMIN_STATE": "true", - "IBGP_PEER_TEMPLATE": "", - "IBGP_PEER_TEMPLATE_LEAF": "", - "INBAND_DHCP_SERVERS": "", - "INBAND_MGMT": "false", - "INBAND_MGMT_PREV": "false", - "ISIS_AREA_NUM": "0001", - "ISIS_AREA_NUM_PREV": "", - "ISIS_AUTH_ENABLE": "false", - "ISIS_AUTH_KEY": "", - "ISIS_AUTH_KEYCHAIN_KEY_ID": "", - "ISIS_AUTH_KEYCHAIN_NAME": "", - "ISIS_LEVEL": "level-2", - "ISIS_OVERLOAD_ELAPSE_TIME": "", - "ISIS_OVERLOAD_ENABLE": "", - "ISIS_P2P_ENABLE": "", - "L2_HOST_INTF_MTU": "9216", - "L2_HOST_INTF_MTU_PREV": "9216", - "L2_SEGMENT_ID_RANGE": "30000-49000", - "L3VNI_MCAST_GROUP": "", - "L3_PARTITION_ID_RANGE": "50000-59000", - "LINK_STATE_ROUTING": "ospf", - "LINK_STATE_ROUTING_TAG": "UNDERLAY", - "LINK_STATE_ROUTING_TAG_PREV": "UNDERLAY", - "LOOPBACK0_IPV6_RANGE": "", - "LOOPBACK0_IP_RANGE": "10.2.0.0/22", - "LOOPBACK1_IPV6_RANGE": "", - "LOOPBACK1_IP_RANGE": "10.3.0.0/22", - "MACSEC_ALGORITHM": "", - "MACSEC_CIPHER_SUITE": "", - "MACSEC_FALLBACK_ALGORITHM": "", - "MACSEC_FALLBACK_KEY_STRING": "", - "MACSEC_KEY_STRING": "", - "MACSEC_REPORT_TIMER": "", - "MGMT_GW": "", - "MGMT_GW_INTERNAL": "", - "MGMT_PREFIX": "", - "MGMT_PREFIX_INTERNAL": "", - "MGMT_V6PREFIX": "", - "MGMT_V6PREFIX_INTERNAL": "", - "MPLS_HANDOFF": "false", - "MPLS_ISIS_AREA_NUM": "0001", - "MPLS_ISIS_AREA_NUM_PREV": "", - "MPLS_LB_ID": "", - "MPLS_LOOPBACK_IP_RANGE": "", - "MSO_CONNECTIVITY_DEPLOYED": "", - "MSO_CONTROLER_ID": "", - "MSO_SITE_GROUP_NAME": "", - "MSO_SITE_ID": "", - "MST_INSTANCE_RANGE": "", - "MULTICAST_GROUP_SUBNET": "239.1.1.0/25", - "NETFLOW_EXPORTER_LIST": "", - "NETFLOW_MONITOR_LIST": "", - "NETFLOW_RECORD_LIST": "", - "NETWORK_VLAN_RANGE": "2300-2999", - "NTP_SERVER_IP_LIST": "", - "NTP_SERVER_VRF": "", - "NVE_LB_ID": "1", - "NXAPI_HTTPS_PORT": "443", - "NXAPI_HTTP_PORT": "80", - "NXC_DEST_VRF": "", - "NXC_PROXY_PORT": "8080", - "NXC_PROXY_SERVER": "", - "NXC_SRC_INTF": "", - "OBJECT_TRACKING_NUMBER_RANGE": "100-299", - "OSPF_AREA_ID": "0.0.0.0", - "OSPF_AUTH_ENABLE": "false", - "OSPF_AUTH_KEY": "", - "OSPF_AUTH_KEY_ID": "", - "OVERLAY_MODE": "cli", - "OVERLAY_MODE_PREV": "cli", - "OVERWRITE_GLOBAL_NXC": "false", - "PER_VRF_LOOPBACK_AUTO_PROVISION": "false", - "PER_VRF_LOOPBACK_AUTO_PROVISION_PREV": "false", - "PER_VRF_LOOPBACK_AUTO_PROVISION_V6": "false", - "PER_VRF_LOOPBACK_AUTO_PROVISION_V6_PREV": "false", - "PER_VRF_LOOPBACK_IP_RANGE": "", - "PER_VRF_LOOPBACK_IP_RANGE_V6": "", - "PHANTOM_RP_LB_ID1": "", - "PHANTOM_RP_LB_ID2": "", - "PHANTOM_RP_LB_ID3": "", - "PHANTOM_RP_LB_ID4": "", - "PIM_HELLO_AUTH_ENABLE": "false", - "PIM_HELLO_AUTH_KEY": "", - "PM_ENABLE": "false", - "PM_ENABLE_PREV": "false", - "POWER_REDUNDANCY_MODE": "ps-redundant", - "PREMSO_PARENT_FABRIC": "", - "PTP_DOMAIN_ID": "", - "PTP_LB_ID": "", - "REPLICATION_MODE": "Multicast", - "ROUTER_ID_RANGE": "", - "ROUTE_MAP_SEQUENCE_NUMBER_RANGE": "1-65534", - "RP_COUNT": "2", - "RP_LB_ID": "254", - "RP_MODE": "asm", - "RR_COUNT": "2", - "SEED_SWITCH_CORE_INTERFACES": "", - "SERVICE_NETWORK_VLAN_RANGE": "3000-3199", - "SGT_ID_RANGE": "", - "SGT_NAME_PREFIX": "", - "SGT_PREPROVISION": "", - "SITE_ID": "65001", - "SLA_ID_RANGE": "10000-19999", - "SNMP_SERVER_HOST_TRAP": "true", - "SPINE_COUNT": "0", - "SPINE_SWITCH_CORE_INTERFACES": "", - "SSPINE_ADD_DEL_DEBUG_FLAG": "Disable", - "SSPINE_COUNT": "0", - "STATIC_UNDERLAY_IP_ALLOC": "false", - "STP_BRIDGE_PRIORITY": "", - "STP_ROOT_OPTION": "unmanaged", - "STP_VLAN_RANGE": "", - "STRICT_CC_MODE": "false", - "SUBINTERFACE_RANGE": "2-511", - "SUBNET_RANGE": "10.4.0.0/16", - "SUBNET_TARGET_MASK": "30", - "SYSLOG_SERVER_IP_LIST": "", - "SYSLOG_SERVER_VRF": "", - "SYSLOG_SEV": "", - "TCAM_ALLOCATION": "true", - "TOPDOWN_CONFIG_RM_TRACKING": "notstarted", - "UNDERLAY_IS_V6": "false", - "UNNUM_BOOTSTRAP_LB_ID": "", - "UNNUM_DHCP_END": "", - "UNNUM_DHCP_END_INTERNAL": "", - "UNNUM_DHCP_START": "", - "UNNUM_DHCP_START_INTERNAL": "", - "UPGRADE_FROM_VERSION": "", - "USE_LINK_LOCAL": "true", - "V6_SUBNET_RANGE": "", - "V6_SUBNET_TARGET_MASK": "126", - "VPC_AUTO_RECOVERY_TIME": "360", - "VPC_DELAY_RESTORE": "150", - "VPC_DELAY_RESTORE_TIME": "60", - "VPC_DOMAIN_ID_RANGE": "1-1000", - "VPC_ENABLE_IPv6_ND_SYNC": "true", - "VPC_PEER_KEEP_ALIVE_OPTION": "management", - "VPC_PEER_LINK_PO": "500", - "VPC_PEER_LINK_VLAN": "3600", - "VRF_LITE_AUTOCONFIG": "Manual", - "VRF_VLAN_RANGE": "2000-2299", - "abstract_anycast_rp": "anycast_rp", - "abstract_bgp": "base_bgp", - "abstract_bgp_neighbor": "evpn_bgp_rr_neighbor", - "abstract_bgp_rr": "evpn_bgp_rr", - "abstract_dhcp": "base_dhcp", - "abstract_extra_config_bootstrap": "extra_config_bootstrap_11_1", - "abstract_extra_config_leaf": "extra_config_leaf", - "abstract_extra_config_spine": "extra_config_spine", - "abstract_extra_config_tor": "extra_config_tor", - "abstract_feature_leaf": "base_feature_leaf_upg", - "abstract_feature_spine": "base_feature_spine_upg", - "abstract_isis": "base_isis_level2", - "abstract_isis_interface": "isis_interface", - "abstract_loopback_interface": "int_fabric_loopback_11_1", - "abstract_multicast": "base_multicast_11_1", - "abstract_ospf": "base_ospf", - "abstract_ospf_interface": "ospf_interface_11_1", - "abstract_pim_interface": "pim_interface", - "abstract_route_map": "route_map", - "abstract_routed_host": "int_routed_host", - "abstract_trunk_host": "int_trunk_host", - "abstract_vlan_interface": "int_fabric_vlan_11_1", - "abstract_vpc_domain": "base_vpc_domain_11_1", - "default_network": "Default_Network_Universal", - "default_pvlan_sec_network": "", - "default_vrf": "Default_VRF_Universal", - "enableRealTimeBackup": "", - "enableScheduledBackup": "", - "network_extension_template": "Default_Network_Extension_Universal", - "scheduledTime": "", - "temp_anycast_gateway": "anycast_gateway", - "temp_vpc_domain_mgmt": "vpc_domain_mgmt", - "temp_vpc_peer_link": "int_vpc_peer_link_po", - "vrf_extension_template": "Default_VRF_Extension_Universal" - }, - "operStatus": "HEALTHY", - "provisionMode": "DCNMTopDown", - "replicationMode": "Multicast", - "siteId": "65001", - "templateName": "Easy_Fabric", - "vrfExtensionTemplate": "Default_VRF_Extension_Universal", - "vrfTemplate": "Default_VRF_Universal" - } - ], - "MESSAGE": "OK", - "METHOD": "GET", - "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/rest/control/fabrics", - "RETURN_CODE": 200 - }, - "test_fabric_delete_00051a": { - "DATA": [ - { - "asn": "65001", - "createdOn": 1711411093680, - "deviceType": "n9k", - "fabricId": "FABRIC-2", - "fabricName": "f1", - "fabricTechnology": "VXLANFabric", - "fabricTechnologyFriendly": "VXLAN EVPN", - "fabricType": "Switch_Fabric", - "fabricTypeFriendly": "Switch Fabric", - "id": 2, - "modifiedOn": 1711411096857, - "networkExtensionTemplate": "Default_Network_Extension_Universal", - "networkTemplate": "Default_Network_Universal", - "nvPairs": { - "AAA_REMOTE_IP_ENABLED": "false", - "AAA_SERVER_CONF": "", - "ACTIVE_MIGRATION": "false", - "ADVERTISE_PIP_BGP": "false", - "ADVERTISE_PIP_ON_BORDER": "true", - "AGENT_INTF": "eth0", - "AI_ML_QOS_POLICY": "AI_Fabric_QOS_400G", - "ALLOW_NXC": "true", - "ALLOW_NXC_PREV": "true", - "ANYCAST_BGW_ADVERTISE_PIP": "false", - "ANYCAST_GW_MAC": "2020.0000.00aa", - "ANYCAST_LB_ID": "", - "ANYCAST_RP_IP_RANGE": "10.254.254.0/24", - "ANYCAST_RP_IP_RANGE_INTERNAL": "", - "AUTO_SYMMETRIC_DEFAULT_VRF": "false", - "AUTO_SYMMETRIC_VRF_LITE": "false", - "AUTO_UNIQUE_VRF_LITE_IP_PREFIX": "false", - "AUTO_UNIQUE_VRF_LITE_IP_PREFIX_PREV": "false", - "AUTO_VRFLITE_IFC_DEFAULT_VRF": "false", - "BANNER": "", - "BFD_AUTH_ENABLE": "false", - "BFD_AUTH_KEY": "", - "BFD_AUTH_KEY_ID": "", - "BFD_ENABLE": "false", - "BFD_ENABLE_PREV": "", - "BFD_IBGP_ENABLE": "false", - "BFD_ISIS_ENABLE": "false", - "BFD_OSPF_ENABLE": "false", - "BFD_PIM_ENABLE": "false", - "BGP_AS": "65001", - "BGP_AS_PREV": "65001", - "BGP_AUTH_ENABLE": "false", - "BGP_AUTH_KEY": "", - "BGP_AUTH_KEY_TYPE": "3", - "BGP_LB_ID": "0", - "BOOTSTRAP_CONF": "", - "BOOTSTRAP_ENABLE": "false", - "BOOTSTRAP_ENABLE_PREV": "false", - "BOOTSTRAP_MULTISUBNET": "#Scope_Start_IP, Scope_End_IP, Scope_Default_Gateway, Scope_Subnet_Prefix", - "BOOTSTRAP_MULTISUBNET_INTERNAL": "", - "BRFIELD_DEBUG_FLAG": "Disable", - "BROWNFIELD_NETWORK_NAME_FORMAT": "Auto_Net_VNI$$VNI$$_VLAN$$VLAN_ID$$", - "BROWNFIELD_SKIP_OVERLAY_NETWORK_ATTACHMENTS": "false", - "CDP_ENABLE": "false", - "COPP_POLICY": "strict", - "DCI_SUBNET_RANGE": "10.33.0.0/16", - "DCI_SUBNET_TARGET_MASK": "30", - "DEAFULT_QUEUING_POLICY_CLOUDSCALE": "queuing_policy_default_8q_cloudscale", - "DEAFULT_QUEUING_POLICY_OTHER": "queuing_policy_default_other", - "DEAFULT_QUEUING_POLICY_R_SERIES": "queuing_policy_default_r_series", - "DEFAULT_VRF_REDIS_BGP_RMAP": "", - "DEPLOYMENT_FREEZE": "false", - "DHCP_ENABLE": "false", - "DHCP_END": "", - "DHCP_END_INTERNAL": "", - "DHCP_IPV6_ENABLE": "", - "DHCP_IPV6_ENABLE_INTERNAL": "", - "DHCP_START": "", - "DHCP_START_INTERNAL": "", - "DNS_SERVER_IP_LIST": "", - "DNS_SERVER_VRF": "", - "DOMAIN_NAME_INTERNAL": "", - "ENABLE_AAA": "false", - "ENABLE_AGENT": "false", - "ENABLE_AI_ML_QOS_POLICY": "false", - "ENABLE_AI_ML_QOS_POLICY_FLAP": "false", - "ENABLE_DEFAULT_QUEUING_POLICY": "false", - "ENABLE_EVPN": "true", - "ENABLE_FABRIC_VPC_DOMAIN_ID": "false", - "ENABLE_FABRIC_VPC_DOMAIN_ID_PREV": "false", - "ENABLE_L3VNI_NO_VLAN": "false", - "ENABLE_MACSEC": "false", - "ENABLE_NETFLOW": "false", - "ENABLE_NETFLOW_PREV": "false", - "ENABLE_NGOAM": "true", - "ENABLE_NXAPI": "true", - "ENABLE_NXAPI_HTTP": "true", - "ENABLE_PBR": "false", - "ENABLE_PVLAN": "false", - "ENABLE_PVLAN_PREV": "false", - "ENABLE_SGT": "false", - "ENABLE_SGT_PREV": "false", - "ENABLE_TENANT_DHCP": "true", - "ENABLE_TRM": "false", - "ENABLE_VPC_PEER_LINK_NATIVE_VLAN": "false", - "ESR_OPTION": "PBR", - "EXTRA_CONF_INTRA_LINKS": "", - "EXTRA_CONF_LEAF": "", - "EXTRA_CONF_SPINE": "", - "EXTRA_CONF_TOR": "", - "EXT_FABRIC_TYPE": "", - "FABRIC_INTERFACE_TYPE": "p2p", - "FABRIC_MTU": "9216", - "FABRIC_MTU_PREV": "9216", - "FABRIC_NAME": "f1", - "FABRIC_TYPE": "Switch_Fabric", - "FABRIC_VPC_DOMAIN_ID": "", - "FABRIC_VPC_DOMAIN_ID_PREV": "", - "FABRIC_VPC_QOS": "false", - "FABRIC_VPC_QOS_POLICY_NAME": "spine_qos_for_fabric_vpc_peering", - "FEATURE_PTP": "false", - "FEATURE_PTP_INTERNAL": "false", - "FF": "Easy_Fabric", - "GRFIELD_DEBUG_FLAG": "Disable", - "HD_TIME": "180", - "HOST_INTF_ADMIN_STATE": "true", - "IBGP_PEER_TEMPLATE": "", - "IBGP_PEER_TEMPLATE_LEAF": "", - "INBAND_DHCP_SERVERS": "", - "INBAND_MGMT": "false", - "INBAND_MGMT_PREV": "false", - "ISIS_AREA_NUM": "0001", - "ISIS_AREA_NUM_PREV": "", - "ISIS_AUTH_ENABLE": "false", - "ISIS_AUTH_KEY": "", - "ISIS_AUTH_KEYCHAIN_KEY_ID": "", - "ISIS_AUTH_KEYCHAIN_NAME": "", - "ISIS_LEVEL": "level-2", - "ISIS_OVERLOAD_ELAPSE_TIME": "", - "ISIS_OVERLOAD_ENABLE": "", - "ISIS_P2P_ENABLE": "", - "L2_HOST_INTF_MTU": "9216", - "L2_HOST_INTF_MTU_PREV": "9216", - "L2_SEGMENT_ID_RANGE": "30000-49000", - "L3VNI_MCAST_GROUP": "", - "L3_PARTITION_ID_RANGE": "50000-59000", - "LINK_STATE_ROUTING": "ospf", - "LINK_STATE_ROUTING_TAG": "UNDERLAY", - "LINK_STATE_ROUTING_TAG_PREV": "UNDERLAY", - "LOOPBACK0_IPV6_RANGE": "", - "LOOPBACK0_IP_RANGE": "10.2.0.0/22", - "LOOPBACK1_IPV6_RANGE": "", - "LOOPBACK1_IP_RANGE": "10.3.0.0/22", - "MACSEC_ALGORITHM": "", - "MACSEC_CIPHER_SUITE": "", - "MACSEC_FALLBACK_ALGORITHM": "", - "MACSEC_FALLBACK_KEY_STRING": "", - "MACSEC_KEY_STRING": "", - "MACSEC_REPORT_TIMER": "", - "MGMT_GW": "", - "MGMT_GW_INTERNAL": "", - "MGMT_PREFIX": "", - "MGMT_PREFIX_INTERNAL": "", - "MGMT_V6PREFIX": "", - "MGMT_V6PREFIX_INTERNAL": "", - "MPLS_HANDOFF": "false", - "MPLS_ISIS_AREA_NUM": "0001", - "MPLS_ISIS_AREA_NUM_PREV": "", - "MPLS_LB_ID": "", - "MPLS_LOOPBACK_IP_RANGE": "", - "MSO_CONNECTIVITY_DEPLOYED": "", - "MSO_CONTROLER_ID": "", - "MSO_SITE_GROUP_NAME": "", - "MSO_SITE_ID": "", - "MST_INSTANCE_RANGE": "", - "MULTICAST_GROUP_SUBNET": "239.1.1.0/25", - "NETFLOW_EXPORTER_LIST": "", - "NETFLOW_MONITOR_LIST": "", - "NETFLOW_RECORD_LIST": "", - "NETWORK_VLAN_RANGE": "2300-2999", - "NTP_SERVER_IP_LIST": "", - "NTP_SERVER_VRF": "", - "NVE_LB_ID": "1", - "NXAPI_HTTPS_PORT": "443", - "NXAPI_HTTP_PORT": "80", - "NXC_DEST_VRF": "", - "NXC_PROXY_PORT": "8080", - "NXC_PROXY_SERVER": "", - "NXC_SRC_INTF": "", - "OBJECT_TRACKING_NUMBER_RANGE": "100-299", - "OSPF_AREA_ID": "0.0.0.0", - "OSPF_AUTH_ENABLE": "false", - "OSPF_AUTH_KEY": "", - "OSPF_AUTH_KEY_ID": "", - "OVERLAY_MODE": "cli", - "OVERLAY_MODE_PREV": "cli", - "OVERWRITE_GLOBAL_NXC": "false", - "PER_VRF_LOOPBACK_AUTO_PROVISION": "false", - "PER_VRF_LOOPBACK_AUTO_PROVISION_PREV": "false", - "PER_VRF_LOOPBACK_AUTO_PROVISION_V6": "false", - "PER_VRF_LOOPBACK_AUTO_PROVISION_V6_PREV": "false", - "PER_VRF_LOOPBACK_IP_RANGE": "", - "PER_VRF_LOOPBACK_IP_RANGE_V6": "", - "PHANTOM_RP_LB_ID1": "", - "PHANTOM_RP_LB_ID2": "", - "PHANTOM_RP_LB_ID3": "", - "PHANTOM_RP_LB_ID4": "", - "PIM_HELLO_AUTH_ENABLE": "false", - "PIM_HELLO_AUTH_KEY": "", - "PM_ENABLE": "false", - "PM_ENABLE_PREV": "false", - "POWER_REDUNDANCY_MODE": "ps-redundant", - "PREMSO_PARENT_FABRIC": "", - "PTP_DOMAIN_ID": "", - "PTP_LB_ID": "", - "REPLICATION_MODE": "Multicast", - "ROUTER_ID_RANGE": "", - "ROUTE_MAP_SEQUENCE_NUMBER_RANGE": "1-65534", - "RP_COUNT": "2", - "RP_LB_ID": "254", - "RP_MODE": "asm", - "RR_COUNT": "2", - "SEED_SWITCH_CORE_INTERFACES": "", - "SERVICE_NETWORK_VLAN_RANGE": "3000-3199", - "SGT_ID_RANGE": "", - "SGT_NAME_PREFIX": "", - "SGT_PREPROVISION": "", - "SITE_ID": "65001", - "SLA_ID_RANGE": "10000-19999", - "SNMP_SERVER_HOST_TRAP": "true", - "SPINE_COUNT": "0", - "SPINE_SWITCH_CORE_INTERFACES": "", - "SSPINE_ADD_DEL_DEBUG_FLAG": "Disable", - "SSPINE_COUNT": "0", - "STATIC_UNDERLAY_IP_ALLOC": "false", - "STP_BRIDGE_PRIORITY": "", - "STP_ROOT_OPTION": "unmanaged", - "STP_VLAN_RANGE": "", - "STRICT_CC_MODE": "false", - "SUBINTERFACE_RANGE": "2-511", - "SUBNET_RANGE": "10.4.0.0/16", - "SUBNET_TARGET_MASK": "30", - "SYSLOG_SERVER_IP_LIST": "", - "SYSLOG_SERVER_VRF": "", - "SYSLOG_SEV": "", - "TCAM_ALLOCATION": "true", - "TOPDOWN_CONFIG_RM_TRACKING": "notstarted", - "UNDERLAY_IS_V6": "false", - "UNNUM_BOOTSTRAP_LB_ID": "", - "UNNUM_DHCP_END": "", - "UNNUM_DHCP_END_INTERNAL": "", - "UNNUM_DHCP_START": "", - "UNNUM_DHCP_START_INTERNAL": "", - "UPGRADE_FROM_VERSION": "", - "USE_LINK_LOCAL": "true", - "V6_SUBNET_RANGE": "", - "V6_SUBNET_TARGET_MASK": "126", - "VPC_AUTO_RECOVERY_TIME": "360", - "VPC_DELAY_RESTORE": "150", - "VPC_DELAY_RESTORE_TIME": "60", - "VPC_DOMAIN_ID_RANGE": "1-1000", - "VPC_ENABLE_IPv6_ND_SYNC": "true", - "VPC_PEER_KEEP_ALIVE_OPTION": "management", - "VPC_PEER_LINK_PO": "500", - "VPC_PEER_LINK_VLAN": "3600", - "VRF_LITE_AUTOCONFIG": "Manual", - "VRF_VLAN_RANGE": "2000-2299", - "abstract_anycast_rp": "anycast_rp", - "abstract_bgp": "base_bgp", - "abstract_bgp_neighbor": "evpn_bgp_rr_neighbor", - "abstract_bgp_rr": "evpn_bgp_rr", - "abstract_dhcp": "base_dhcp", - "abstract_extra_config_bootstrap": "extra_config_bootstrap_11_1", - "abstract_extra_config_leaf": "extra_config_leaf", - "abstract_extra_config_spine": "extra_config_spine", - "abstract_extra_config_tor": "extra_config_tor", - "abstract_feature_leaf": "base_feature_leaf_upg", - "abstract_feature_spine": "base_feature_spine_upg", - "abstract_isis": "base_isis_level2", - "abstract_isis_interface": "isis_interface", - "abstract_loopback_interface": "int_fabric_loopback_11_1", - "abstract_multicast": "base_multicast_11_1", - "abstract_ospf": "base_ospf", - "abstract_ospf_interface": "ospf_interface_11_1", - "abstract_pim_interface": "pim_interface", - "abstract_route_map": "route_map", - "abstract_routed_host": "int_routed_host", - "abstract_trunk_host": "int_trunk_host", - "abstract_vlan_interface": "int_fabric_vlan_11_1", - "abstract_vpc_domain": "base_vpc_domain_11_1", - "default_network": "Default_Network_Universal", - "default_pvlan_sec_network": "", - "default_vrf": "Default_VRF_Universal", - "enableRealTimeBackup": "", - "enableScheduledBackup": "", - "network_extension_template": "Default_Network_Extension_Universal", - "scheduledTime": "", - "temp_anycast_gateway": "anycast_gateway", - "temp_vpc_domain_mgmt": "vpc_domain_mgmt", - "temp_vpc_peer_link": "int_vpc_peer_link_po", - "vrf_extension_template": "Default_VRF_Extension_Universal" - }, - "operStatus": "HEALTHY", - "provisionMode": "DCNMTopDown", - "replicationMode": "Multicast", - "siteId": "65001", - "templateName": "Easy_Fabric", - "vrfExtensionTemplate": "Default_VRF_Extension_Universal", - "vrfTemplate": "Default_VRF_Universal" - } - ], - "MESSAGE": "OK", - "METHOD": "GET", - "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/rest/control/fabrics", - "RETURN_CODE": 200 - }, - "test_fabric_details_00030a": { - "TEST_NOTES": [ - "DATA is an empty list", - "RETURN_CODE is 200" - ], - "DATA": [], - "MESSAGE": "OK", - "METHOD": "GET", - "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/rest/control/fabrics", - "RETURN_CODE": 200 - }, - "test_fabric_details_00031a": { - "TEST_NOTES": [ - "DATA key is missing", - "Negative test case", - "RETURN_CODE is 200" - ], - "MESSAGE": "OK", - "METHOD": "GET", - "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/rest/control/fabrics", - "RETURN_CODE": 200 - }, - "test_fabric_details_00032a": { - "TEST_NOTES": [ - "DATA contains one fabric dict", - "RETURN_CODE is 200" - ], - "DATA": [ - { - "asn": "65001", - "createdOn": 1711411093680, - "deviceType": "n9k", - "fabricId": "FABRIC-2", - "fabricName": "f1", - "fabricTechnology": "VXLANFabric", - "fabricTechnologyFriendly": "VXLAN EVPN", - "fabricType": "Switch_Fabric", - "fabricTypeFriendly": "Switch Fabric", - "id": 2, - "modifiedOn": 1711411096857, - "networkExtensionTemplate": "Default_Network_Extension_Universal", - "networkTemplate": "Default_Network_Universal", - "nvPairs": { - "AAA_REMOTE_IP_ENABLED": "false", - "AAA_SERVER_CONF": "", - "ACTIVE_MIGRATION": "false", - "ADVERTISE_PIP_BGP": "false", - "ADVERTISE_PIP_ON_BORDER": "true", - "AGENT_INTF": "eth0", - "AI_ML_QOS_POLICY": "AI_Fabric_QOS_400G", - "ALLOW_NXC": "true", - "ALLOW_NXC_PREV": "true", - "ANYCAST_BGW_ADVERTISE_PIP": "false", - "ANYCAST_GW_MAC": "2020.0000.00aa", - "ANYCAST_LB_ID": "", - "ANYCAST_RP_IP_RANGE": "10.254.254.0/24", - "ANYCAST_RP_IP_RANGE_INTERNAL": "", - "AUTO_SYMMETRIC_DEFAULT_VRF": "false", - "AUTO_SYMMETRIC_VRF_LITE": "false", - "AUTO_UNIQUE_VRF_LITE_IP_PREFIX": "false", - "AUTO_UNIQUE_VRF_LITE_IP_PREFIX_PREV": "false", - "AUTO_VRFLITE_IFC_DEFAULT_VRF": "false", - "BANNER": "", - "BFD_AUTH_ENABLE": "false", - "BFD_AUTH_KEY": "", - "BFD_AUTH_KEY_ID": "", - "BFD_ENABLE": "false", - "BFD_ENABLE_PREV": "", - "BFD_IBGP_ENABLE": "false", - "BFD_ISIS_ENABLE": "false", - "BFD_OSPF_ENABLE": "false", - "BFD_PIM_ENABLE": "false", - "BGP_AS": "65001", - "BGP_AS_PREV": "65001", - "BGP_AUTH_ENABLE": "false", - "BGP_AUTH_KEY": "", - "BGP_AUTH_KEY_TYPE": "3", - "BGP_LB_ID": "0", - "BOOTSTRAP_CONF": "", - "BOOTSTRAP_ENABLE": "false", - "BOOTSTRAP_ENABLE_PREV": "false", - "BOOTSTRAP_MULTISUBNET": "#Scope_Start_IP, Scope_End_IP, Scope_Default_Gateway, Scope_Subnet_Prefix", - "BOOTSTRAP_MULTISUBNET_INTERNAL": "", - "BRFIELD_DEBUG_FLAG": "Disable", - "BROWNFIELD_NETWORK_NAME_FORMAT": "Auto_Net_VNI$$VNI$$_VLAN$$VLAN_ID$$", - "BROWNFIELD_SKIP_OVERLAY_NETWORK_ATTACHMENTS": "false", - "CDP_ENABLE": "false", - "COPP_POLICY": "strict", - "DCI_SUBNET_RANGE": "10.33.0.0/16", - "DCI_SUBNET_TARGET_MASK": "30", - "DEAFULT_QUEUING_POLICY_CLOUDSCALE": "queuing_policy_default_8q_cloudscale", - "DEAFULT_QUEUING_POLICY_OTHER": "queuing_policy_default_other", - "DEAFULT_QUEUING_POLICY_R_SERIES": "queuing_policy_default_r_series", - "DEFAULT_VRF_REDIS_BGP_RMAP": "", - "DEPLOYMENT_FREEZE": "false", - "DHCP_ENABLE": "false", - "DHCP_END": "", - "DHCP_END_INTERNAL": "", - "DHCP_IPV6_ENABLE": "", - "DHCP_IPV6_ENABLE_INTERNAL": "", - "DHCP_START": "", - "DHCP_START_INTERNAL": "", - "DNS_SERVER_IP_LIST": "", - "DNS_SERVER_VRF": "", - "DOMAIN_NAME_INTERNAL": "", - "ENABLE_AAA": "false", - "ENABLE_AGENT": "false", - "ENABLE_AI_ML_QOS_POLICY": "false", - "ENABLE_AI_ML_QOS_POLICY_FLAP": "false", - "ENABLE_DEFAULT_QUEUING_POLICY": "false", - "ENABLE_EVPN": "true", - "ENABLE_FABRIC_VPC_DOMAIN_ID": "false", - "ENABLE_FABRIC_VPC_DOMAIN_ID_PREV": "false", - "ENABLE_L3VNI_NO_VLAN": "false", - "ENABLE_MACSEC": "false", - "ENABLE_NETFLOW": "false", - "ENABLE_NETFLOW_PREV": "false", - "ENABLE_NGOAM": "true", - "ENABLE_NXAPI": "true", - "ENABLE_NXAPI_HTTP": "true", - "ENABLE_PBR": "false", - "ENABLE_PVLAN": "false", - "ENABLE_PVLAN_PREV": "false", - "ENABLE_SGT": "false", - "ENABLE_SGT_PREV": "false", - "ENABLE_TENANT_DHCP": "true", - "ENABLE_TRM": "false", - "ENABLE_VPC_PEER_LINK_NATIVE_VLAN": "false", - "ESR_OPTION": "PBR", - "EXTRA_CONF_INTRA_LINKS": "", - "EXTRA_CONF_LEAF": "", - "EXTRA_CONF_SPINE": "", - "EXTRA_CONF_TOR": "", - "EXT_FABRIC_TYPE": "", - "FABRIC_INTERFACE_TYPE": "p2p", - "FABRIC_MTU": "9216", - "FABRIC_MTU_PREV": "9216", - "FABRIC_NAME": "f1", - "FABRIC_TYPE": "Switch_Fabric", - "FABRIC_VPC_DOMAIN_ID": "", - "FABRIC_VPC_DOMAIN_ID_PREV": "", - "FABRIC_VPC_QOS": "false", - "FABRIC_VPC_QOS_POLICY_NAME": "spine_qos_for_fabric_vpc_peering", - "FEATURE_PTP": "false", - "FEATURE_PTP_INTERNAL": "false", - "FF": "Easy_Fabric", - "GRFIELD_DEBUG_FLAG": "Disable", - "HD_TIME": "180", - "HOST_INTF_ADMIN_STATE": "true", - "IBGP_PEER_TEMPLATE": "", - "IBGP_PEER_TEMPLATE_LEAF": "", - "INBAND_DHCP_SERVERS": "", - "INBAND_MGMT": "false", - "INBAND_MGMT_PREV": "false", - "ISIS_AREA_NUM": "0001", - "ISIS_AREA_NUM_PREV": "", - "ISIS_AUTH_ENABLE": "false", - "ISIS_AUTH_KEY": "", - "ISIS_AUTH_KEYCHAIN_KEY_ID": "", - "ISIS_AUTH_KEYCHAIN_NAME": "", - "ISIS_LEVEL": "level-2", - "ISIS_OVERLOAD_ELAPSE_TIME": "", - "ISIS_OVERLOAD_ENABLE": "", - "ISIS_P2P_ENABLE": "", - "L2_HOST_INTF_MTU": "9216", - "L2_HOST_INTF_MTU_PREV": "9216", - "L2_SEGMENT_ID_RANGE": "30000-49000", - "L3VNI_MCAST_GROUP": "", - "L3_PARTITION_ID_RANGE": "50000-59000", - "LINK_STATE_ROUTING": "ospf", - "LINK_STATE_ROUTING_TAG": "UNDERLAY", - "LINK_STATE_ROUTING_TAG_PREV": "UNDERLAY", - "LOOPBACK0_IPV6_RANGE": "", - "LOOPBACK0_IP_RANGE": "10.2.0.0/22", - "LOOPBACK1_IPV6_RANGE": "", - "LOOPBACK1_IP_RANGE": "10.3.0.0/22", - "MACSEC_ALGORITHM": "", - "MACSEC_CIPHER_SUITE": "", - "MACSEC_FALLBACK_ALGORITHM": "", - "MACSEC_FALLBACK_KEY_STRING": "", - "MACSEC_KEY_STRING": "", - "MACSEC_REPORT_TIMER": "", - "MGMT_GW": "", - "MGMT_GW_INTERNAL": "", - "MGMT_PREFIX": "", - "MGMT_PREFIX_INTERNAL": "", - "MGMT_V6PREFIX": "", - "MGMT_V6PREFIX_INTERNAL": "", - "MPLS_HANDOFF": "false", - "MPLS_ISIS_AREA_NUM": "0001", - "MPLS_ISIS_AREA_NUM_PREV": "", - "MPLS_LB_ID": "", - "MPLS_LOOPBACK_IP_RANGE": "", - "MSO_CONNECTIVITY_DEPLOYED": "", - "MSO_CONTROLER_ID": "", - "MSO_SITE_GROUP_NAME": "", - "MSO_SITE_ID": "", - "MST_INSTANCE_RANGE": "", - "MULTICAST_GROUP_SUBNET": "239.1.1.0/25", - "NETFLOW_EXPORTER_LIST": "", - "NETFLOW_MONITOR_LIST": "", - "NETFLOW_RECORD_LIST": "", - "NETWORK_VLAN_RANGE": "2300-2999", - "NTP_SERVER_IP_LIST": "", - "NTP_SERVER_VRF": "", - "NVE_LB_ID": "1", - "NXAPI_HTTPS_PORT": "443", - "NXAPI_HTTP_PORT": "80", - "NXC_DEST_VRF": "", - "NXC_PROXY_PORT": "8080", - "NXC_PROXY_SERVER": "", - "NXC_SRC_INTF": "", - "OBJECT_TRACKING_NUMBER_RANGE": "100-299", - "OSPF_AREA_ID": "0.0.0.0", - "OSPF_AUTH_ENABLE": "false", - "OSPF_AUTH_KEY": "", - "OSPF_AUTH_KEY_ID": "", - "OVERLAY_MODE": "cli", - "OVERLAY_MODE_PREV": "cli", - "OVERWRITE_GLOBAL_NXC": "false", - "PER_VRF_LOOPBACK_AUTO_PROVISION": "false", - "PER_VRF_LOOPBACK_AUTO_PROVISION_PREV": "false", - "PER_VRF_LOOPBACK_AUTO_PROVISION_V6": "false", - "PER_VRF_LOOPBACK_AUTO_PROVISION_V6_PREV": "false", - "PER_VRF_LOOPBACK_IP_RANGE": "", - "PER_VRF_LOOPBACK_IP_RANGE_V6": "", - "PHANTOM_RP_LB_ID1": "", - "PHANTOM_RP_LB_ID2": "", - "PHANTOM_RP_LB_ID3": "", - "PHANTOM_RP_LB_ID4": "", - "PIM_HELLO_AUTH_ENABLE": "false", - "PIM_HELLO_AUTH_KEY": "", - "PM_ENABLE": "false", - "PM_ENABLE_PREV": "false", - "POWER_REDUNDANCY_MODE": "ps-redundant", - "PREMSO_PARENT_FABRIC": "", - "PTP_DOMAIN_ID": "", - "PTP_LB_ID": "", - "REPLICATION_MODE": "Multicast", - "ROUTER_ID_RANGE": "", - "ROUTE_MAP_SEQUENCE_NUMBER_RANGE": "1-65534", - "RP_COUNT": "2", - "RP_LB_ID": "254", - "RP_MODE": "asm", - "RR_COUNT": "2", - "SEED_SWITCH_CORE_INTERFACES": "", - "SERVICE_NETWORK_VLAN_RANGE": "3000-3199", - "SGT_ID_RANGE": "", - "SGT_NAME_PREFIX": "", - "SGT_PREPROVISION": "", - "SITE_ID": "65001", - "SLA_ID_RANGE": "10000-19999", - "SNMP_SERVER_HOST_TRAP": "true", - "SPINE_COUNT": "0", - "SPINE_SWITCH_CORE_INTERFACES": "", - "SSPINE_ADD_DEL_DEBUG_FLAG": "Disable", - "SSPINE_COUNT": "0", - "STATIC_UNDERLAY_IP_ALLOC": "false", - "STP_BRIDGE_PRIORITY": "", - "STP_ROOT_OPTION": "unmanaged", - "STP_VLAN_RANGE": "", - "STRICT_CC_MODE": "false", - "SUBINTERFACE_RANGE": "2-511", - "SUBNET_RANGE": "10.4.0.0/16", - "SUBNET_TARGET_MASK": "30", - "SYSLOG_SERVER_IP_LIST": "", - "SYSLOG_SERVER_VRF": "", - "SYSLOG_SEV": "", - "TCAM_ALLOCATION": "true", - "TOPDOWN_CONFIG_RM_TRACKING": "notstarted", - "UNDERLAY_IS_V6": "false", - "UNNUM_BOOTSTRAP_LB_ID": "", - "UNNUM_DHCP_END": "", - "UNNUM_DHCP_END_INTERNAL": "", - "UNNUM_DHCP_START": "", - "UNNUM_DHCP_START_INTERNAL": "", - "UPGRADE_FROM_VERSION": "", - "USE_LINK_LOCAL": "true", - "V6_SUBNET_RANGE": "", - "V6_SUBNET_TARGET_MASK": "126", - "VPC_AUTO_RECOVERY_TIME": "360", - "VPC_DELAY_RESTORE": "150", - "VPC_DELAY_RESTORE_TIME": "60", - "VPC_DOMAIN_ID_RANGE": "1-1000", - "VPC_ENABLE_IPv6_ND_SYNC": "true", - "VPC_PEER_KEEP_ALIVE_OPTION": "management", - "VPC_PEER_LINK_PO": "500", - "VPC_PEER_LINK_VLAN": "3600", - "VRF_LITE_AUTOCONFIG": "Manual", - "VRF_VLAN_RANGE": "2000-2299", - "abstract_anycast_rp": "anycast_rp", - "abstract_bgp": "base_bgp", - "abstract_bgp_neighbor": "evpn_bgp_rr_neighbor", - "abstract_bgp_rr": "evpn_bgp_rr", - "abstract_dhcp": "base_dhcp", - "abstract_extra_config_bootstrap": "extra_config_bootstrap_11_1", - "abstract_extra_config_leaf": "extra_config_leaf", - "abstract_extra_config_spine": "extra_config_spine", - "abstract_extra_config_tor": "extra_config_tor", - "abstract_feature_leaf": "base_feature_leaf_upg", - "abstract_feature_spine": "base_feature_spine_upg", - "abstract_isis": "base_isis_level2", - "abstract_isis_interface": "isis_interface", - "abstract_loopback_interface": "int_fabric_loopback_11_1", - "abstract_multicast": "base_multicast_11_1", - "abstract_ospf": "base_ospf", - "abstract_ospf_interface": "ospf_interface_11_1", - "abstract_pim_interface": "pim_interface", - "abstract_route_map": "route_map", - "abstract_routed_host": "int_routed_host", - "abstract_trunk_host": "int_trunk_host", - "abstract_vlan_interface": "int_fabric_vlan_11_1", - "abstract_vpc_domain": "base_vpc_domain_11_1", - "default_network": "Default_Network_Universal", - "default_pvlan_sec_network": "", - "default_vrf": "Default_VRF_Universal", - "enableRealTimeBackup": "", - "enableScheduledBackup": "", - "network_extension_template": "Default_Network_Extension_Universal", - "scheduledTime": "", - "temp_anycast_gateway": "anycast_gateway", - "temp_vpc_domain_mgmt": "vpc_domain_mgmt", - "temp_vpc_peer_link": "int_vpc_peer_link_po", - "vrf_extension_template": "Default_VRF_Extension_Universal" - }, - "operStatus": "HEALTHY", - "provisionMode": "DCNMTopDown", - "replicationMode": "Multicast", - "siteId": "65001", - "templateName": "Easy_Fabric", - "vrfExtensionTemplate": "Default_VRF_Extension_Universal", - "vrfTemplate": "Default_VRF_Universal" - } - ], - "MESSAGE": "OK", - "METHOD": "GET", - "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/rest/control/fabrics", - "RETURN_CODE": 200 - }, - "test_fabric_details_by_name_00030a": { - "TEST_NOTES": [ - "DATA is an empty list", - "RETURN_CODE is 200" - ], - "DATA": [], - "MESSAGE": "OK", - "METHOD": "GET", - "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/rest/control/fabrics", - "RETURN_CODE": 200 - }, - "test_fabric_details_by_name_00031a": { - "TEST_NOTES": [ - "DATA key is missing", - "Negative test case", - "RETURN_CODE is 200" - ], - "MESSAGE": "OK", - "METHOD": "GET", - "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/rest/control/fabrics", - "RETURN_CODE": 200 - }, - "test_fabric_details_by_name_00032a": { - "TEST_NOTES": [ - "DATA contains one fabric dict", - "RETURN_CODE is 200" - ], - "DATA": [ - { - "asn": "65001", - "createdOn": 1711411093680, - "deviceType": "n9k", - "fabricId": "FABRIC-2", - "fabricName": "f1", - "fabricTechnology": "VXLANFabric", - "fabricTechnologyFriendly": "VXLAN EVPN", - "fabricType": "Switch_Fabric", - "fabricTypeFriendly": "Switch Fabric", - "id": 2, - "modifiedOn": 1711411096857, - "networkExtensionTemplate": "Default_Network_Extension_Universal", - "networkTemplate": "Default_Network_Universal", - "nvPairs": { - "AAA_REMOTE_IP_ENABLED": "false", - "AAA_SERVER_CONF": "", - "ACTIVE_MIGRATION": "false", - "ADVERTISE_PIP_BGP": "false", - "ADVERTISE_PIP_ON_BORDER": "true", - "AGENT_INTF": "eth0", - "AI_ML_QOS_POLICY": "AI_Fabric_QOS_400G", - "ALLOW_NXC": "true", - "ALLOW_NXC_PREV": "true", - "ANYCAST_BGW_ADVERTISE_PIP": "false", - "ANYCAST_GW_MAC": "2020.0000.00aa", - "ANYCAST_LB_ID": "", - "ANYCAST_RP_IP_RANGE": "10.254.254.0/24", - "ANYCAST_RP_IP_RANGE_INTERNAL": "", - "AUTO_SYMMETRIC_DEFAULT_VRF": "false", - "AUTO_SYMMETRIC_VRF_LITE": "false", - "AUTO_UNIQUE_VRF_LITE_IP_PREFIX": "false", - "AUTO_UNIQUE_VRF_LITE_IP_PREFIX_PREV": "false", - "AUTO_VRFLITE_IFC_DEFAULT_VRF": "false", - "BANNER": "", - "BFD_AUTH_ENABLE": "false", - "BFD_AUTH_KEY": "", - "BFD_AUTH_KEY_ID": "", - "BFD_ENABLE": "false", - "BFD_ENABLE_PREV": "", - "BFD_IBGP_ENABLE": "false", - "BFD_ISIS_ENABLE": "false", - "BFD_OSPF_ENABLE": "false", - "BFD_PIM_ENABLE": "false", - "BGP_AS": "65001", - "BGP_AS_PREV": "65001", - "BGP_AUTH_ENABLE": "false", - "BGP_AUTH_KEY": "", - "BGP_AUTH_KEY_TYPE": "3", - "BGP_LB_ID": "0", - "BOOTSTRAP_CONF": "", - "BOOTSTRAP_ENABLE": "false", - "BOOTSTRAP_ENABLE_PREV": "false", - "BOOTSTRAP_MULTISUBNET": "#Scope_Start_IP, Scope_End_IP, Scope_Default_Gateway, Scope_Subnet_Prefix", - "BOOTSTRAP_MULTISUBNET_INTERNAL": "", - "BRFIELD_DEBUG_FLAG": "Disable", - "BROWNFIELD_NETWORK_NAME_FORMAT": "Auto_Net_VNI$$VNI$$_VLAN$$VLAN_ID$$", - "BROWNFIELD_SKIP_OVERLAY_NETWORK_ATTACHMENTS": "false", - "CDP_ENABLE": "false", - "COPP_POLICY": "strict", - "DCI_SUBNET_RANGE": "10.33.0.0/16", - "DCI_SUBNET_TARGET_MASK": "30", - "DEAFULT_QUEUING_POLICY_CLOUDSCALE": "queuing_policy_default_8q_cloudscale", - "DEAFULT_QUEUING_POLICY_OTHER": "queuing_policy_default_other", - "DEAFULT_QUEUING_POLICY_R_SERIES": "queuing_policy_default_r_series", - "DEFAULT_VRF_REDIS_BGP_RMAP": "", - "DEPLOYMENT_FREEZE": "false", - "DHCP_ENABLE": "false", - "DHCP_END": "", - "DHCP_END_INTERNAL": "", - "DHCP_IPV6_ENABLE": "", - "DHCP_IPV6_ENABLE_INTERNAL": "", - "DHCP_START": "", - "DHCP_START_INTERNAL": "", - "DNS_SERVER_IP_LIST": "", - "DNS_SERVER_VRF": "", - "DOMAIN_NAME_INTERNAL": "", - "ENABLE_AAA": "false", - "ENABLE_AGENT": "false", - "ENABLE_AI_ML_QOS_POLICY": "false", - "ENABLE_AI_ML_QOS_POLICY_FLAP": "false", - "ENABLE_DEFAULT_QUEUING_POLICY": "false", - "ENABLE_EVPN": "true", - "ENABLE_FABRIC_VPC_DOMAIN_ID": "false", - "ENABLE_FABRIC_VPC_DOMAIN_ID_PREV": "false", - "ENABLE_L3VNI_NO_VLAN": "false", - "ENABLE_MACSEC": "false", - "ENABLE_NETFLOW": "false", - "ENABLE_NETFLOW_PREV": "false", - "ENABLE_NGOAM": "true", - "ENABLE_NXAPI": "true", - "ENABLE_NXAPI_HTTP": "true", - "ENABLE_PBR": "false", - "ENABLE_PVLAN": "false", - "ENABLE_PVLAN_PREV": "false", - "ENABLE_SGT": "false", - "ENABLE_SGT_PREV": "false", - "ENABLE_TENANT_DHCP": "true", - "ENABLE_TRM": "false", - "ENABLE_VPC_PEER_LINK_NATIVE_VLAN": "false", - "ESR_OPTION": "PBR", - "EXTRA_CONF_INTRA_LINKS": "", - "EXTRA_CONF_LEAF": "", - "EXTRA_CONF_SPINE": "", - "EXTRA_CONF_TOR": "", - "EXT_FABRIC_TYPE": "", - "FABRIC_INTERFACE_TYPE": "p2p", - "FABRIC_MTU": "9216", - "FABRIC_MTU_PREV": "9216", - "FABRIC_NAME": "f1", - "FABRIC_TYPE": "Switch_Fabric", - "FABRIC_VPC_DOMAIN_ID": "", - "FABRIC_VPC_DOMAIN_ID_PREV": "", - "FABRIC_VPC_QOS": "false", - "FABRIC_VPC_QOS_POLICY_NAME": "spine_qos_for_fabric_vpc_peering", - "FEATURE_PTP": "false", - "FEATURE_PTP_INTERNAL": "false", - "FF": "Easy_Fabric", - "GRFIELD_DEBUG_FLAG": "Disable", - "HD_TIME": "180", - "HOST_INTF_ADMIN_STATE": "true", - "IBGP_PEER_TEMPLATE": "", - "IBGP_PEER_TEMPLATE_LEAF": "", - "INBAND_DHCP_SERVERS": "", - "INBAND_MGMT": "false", - "INBAND_MGMT_PREV": "false", - "ISIS_AREA_NUM": "0001", - "ISIS_AREA_NUM_PREV": "", - "ISIS_AUTH_ENABLE": "false", - "ISIS_AUTH_KEY": "", - "ISIS_AUTH_KEYCHAIN_KEY_ID": "", - "ISIS_AUTH_KEYCHAIN_NAME": "", - "ISIS_LEVEL": "level-2", - "ISIS_OVERLOAD_ELAPSE_TIME": "", - "ISIS_OVERLOAD_ENABLE": "", - "ISIS_P2P_ENABLE": "", - "L2_HOST_INTF_MTU": "9216", - "L2_HOST_INTF_MTU_PREV": "9216", - "L2_SEGMENT_ID_RANGE": "30000-49000", - "L3VNI_MCAST_GROUP": "", - "L3_PARTITION_ID_RANGE": "50000-59000", - "LINK_STATE_ROUTING": "ospf", - "LINK_STATE_ROUTING_TAG": "UNDERLAY", - "LINK_STATE_ROUTING_TAG_PREV": "UNDERLAY", - "LOOPBACK0_IPV6_RANGE": "", - "LOOPBACK0_IP_RANGE": "10.2.0.0/22", - "LOOPBACK1_IPV6_RANGE": "", - "LOOPBACK1_IP_RANGE": "10.3.0.0/22", - "MACSEC_ALGORITHM": "", - "MACSEC_CIPHER_SUITE": "", - "MACSEC_FALLBACK_ALGORITHM": "", - "MACSEC_FALLBACK_KEY_STRING": "", - "MACSEC_KEY_STRING": "", - "MACSEC_REPORT_TIMER": "", - "MGMT_GW": "", - "MGMT_GW_INTERNAL": "", - "MGMT_PREFIX": "", - "MGMT_PREFIX_INTERNAL": "", - "MGMT_V6PREFIX": "", - "MGMT_V6PREFIX_INTERNAL": "", - "MPLS_HANDOFF": "false", - "MPLS_ISIS_AREA_NUM": "0001", - "MPLS_ISIS_AREA_NUM_PREV": "", - "MPLS_LB_ID": "", - "MPLS_LOOPBACK_IP_RANGE": "", - "MSO_CONNECTIVITY_DEPLOYED": "", - "MSO_CONTROLER_ID": "", - "MSO_SITE_GROUP_NAME": "", - "MSO_SITE_ID": "", - "MST_INSTANCE_RANGE": "", - "MULTICAST_GROUP_SUBNET": "239.1.1.0/25", - "NETFLOW_EXPORTER_LIST": "", - "NETFLOW_MONITOR_LIST": "", - "NETFLOW_RECORD_LIST": "", - "NETWORK_VLAN_RANGE": "2300-2999", - "NTP_SERVER_IP_LIST": "", - "NTP_SERVER_VRF": "", - "NVE_LB_ID": "1", - "NXAPI_HTTPS_PORT": "443", - "NXAPI_HTTP_PORT": "80", - "NXC_DEST_VRF": "", - "NXC_PROXY_PORT": "8080", - "NXC_PROXY_SERVER": "", - "NXC_SRC_INTF": "", - "OBJECT_TRACKING_NUMBER_RANGE": "100-299", - "OSPF_AREA_ID": "0.0.0.0", - "OSPF_AUTH_ENABLE": "false", - "OSPF_AUTH_KEY": "", - "OSPF_AUTH_KEY_ID": "", - "OVERLAY_MODE": "cli", - "OVERLAY_MODE_PREV": "cli", - "OVERWRITE_GLOBAL_NXC": "false", - "PER_VRF_LOOPBACK_AUTO_PROVISION": "false", - "PER_VRF_LOOPBACK_AUTO_PROVISION_PREV": "false", - "PER_VRF_LOOPBACK_AUTO_PROVISION_V6": "false", - "PER_VRF_LOOPBACK_AUTO_PROVISION_V6_PREV": "false", - "PER_VRF_LOOPBACK_IP_RANGE": "", - "PER_VRF_LOOPBACK_IP_RANGE_V6": "", - "PHANTOM_RP_LB_ID1": "", - "PHANTOM_RP_LB_ID2": "", - "PHANTOM_RP_LB_ID3": "", - "PHANTOM_RP_LB_ID4": "", - "PIM_HELLO_AUTH_ENABLE": "false", - "PIM_HELLO_AUTH_KEY": "", - "PM_ENABLE": "false", - "PM_ENABLE_PREV": "false", - "POWER_REDUNDANCY_MODE": "ps-redundant", - "PREMSO_PARENT_FABRIC": "", - "PTP_DOMAIN_ID": "", - "PTP_LB_ID": "", - "REPLICATION_MODE": "Multicast", - "ROUTER_ID_RANGE": "", - "ROUTE_MAP_SEQUENCE_NUMBER_RANGE": "1-65534", - "RP_COUNT": "2", - "RP_LB_ID": "254", - "RP_MODE": "asm", - "RR_COUNT": "2", - "SEED_SWITCH_CORE_INTERFACES": "", - "SERVICE_NETWORK_VLAN_RANGE": "3000-3199", - "SGT_ID_RANGE": "", - "SGT_NAME_PREFIX": "", - "SGT_PREPROVISION": "", - "SITE_ID": "65001", - "SLA_ID_RANGE": "10000-19999", - "SNMP_SERVER_HOST_TRAP": "true", - "SPINE_COUNT": "0", - "SPINE_SWITCH_CORE_INTERFACES": "", - "SSPINE_ADD_DEL_DEBUG_FLAG": "Disable", - "SSPINE_COUNT": "0", - "STATIC_UNDERLAY_IP_ALLOC": "false", - "STP_BRIDGE_PRIORITY": "", - "STP_ROOT_OPTION": "unmanaged", - "STP_VLAN_RANGE": "", - "STRICT_CC_MODE": "false", - "SUBINTERFACE_RANGE": "2-511", - "SUBNET_RANGE": "10.4.0.0/16", - "SUBNET_TARGET_MASK": "30", - "SYSLOG_SERVER_IP_LIST": "", - "SYSLOG_SERVER_VRF": "", - "SYSLOG_SEV": "", - "TCAM_ALLOCATION": "true", - "TOPDOWN_CONFIG_RM_TRACKING": "notstarted", - "UNDERLAY_IS_V6": "false", - "UNNUM_BOOTSTRAP_LB_ID": "", - "UNNUM_DHCP_END": "", - "UNNUM_DHCP_END_INTERNAL": "", - "UNNUM_DHCP_START": "", - "UNNUM_DHCP_START_INTERNAL": "", - "UPGRADE_FROM_VERSION": "", - "USE_LINK_LOCAL": "true", - "V6_SUBNET_RANGE": "", - "V6_SUBNET_TARGET_MASK": "126", - "VPC_AUTO_RECOVERY_TIME": "360", - "VPC_DELAY_RESTORE": "150", - "VPC_DELAY_RESTORE_TIME": "60", - "VPC_DOMAIN_ID_RANGE": "1-1000", - "VPC_ENABLE_IPv6_ND_SYNC": "true", - "VPC_PEER_KEEP_ALIVE_OPTION": "management", - "VPC_PEER_LINK_PO": "500", - "VPC_PEER_LINK_VLAN": "3600", - "VRF_LITE_AUTOCONFIG": "Manual", - "VRF_VLAN_RANGE": "2000-2299", - "abstract_anycast_rp": "anycast_rp", - "abstract_bgp": "base_bgp", - "abstract_bgp_neighbor": "evpn_bgp_rr_neighbor", - "abstract_bgp_rr": "evpn_bgp_rr", - "abstract_dhcp": "base_dhcp", - "abstract_extra_config_bootstrap": "extra_config_bootstrap_11_1", - "abstract_extra_config_leaf": "extra_config_leaf", - "abstract_extra_config_spine": "extra_config_spine", - "abstract_extra_config_tor": "extra_config_tor", - "abstract_feature_leaf": "base_feature_leaf_upg", - "abstract_feature_spine": "base_feature_spine_upg", - "abstract_isis": "base_isis_level2", - "abstract_isis_interface": "isis_interface", - "abstract_loopback_interface": "int_fabric_loopback_11_1", - "abstract_multicast": "base_multicast_11_1", - "abstract_ospf": "base_ospf", - "abstract_ospf_interface": "ospf_interface_11_1", - "abstract_pim_interface": "pim_interface", - "abstract_route_map": "route_map", - "abstract_routed_host": "int_routed_host", - "abstract_trunk_host": "int_trunk_host", - "abstract_vlan_interface": "int_fabric_vlan_11_1", - "abstract_vpc_domain": "base_vpc_domain_11_1", - "default_network": "Default_Network_Universal", - "default_pvlan_sec_network": "", - "default_vrf": "Default_VRF_Universal", - "enableRealTimeBackup": "", - "enableScheduledBackup": "", - "network_extension_template": "Default_Network_Extension_Universal", - "scheduledTime": "", - "temp_anycast_gateway": "anycast_gateway", - "temp_vpc_domain_mgmt": "vpc_domain_mgmt", - "temp_vpc_peer_link": "int_vpc_peer_link_po", - "vrf_extension_template": "Default_VRF_Extension_Universal" - }, - "operStatus": "HEALTHY", - "provisionMode": "DCNMTopDown", - "replicationMode": "Multicast", - "siteId": "65001", - "templateName": "Easy_Fabric", - "vrfExtensionTemplate": "Default_VRF_Extension_Universal", - "vrfTemplate": "Default_VRF_Universal" - } - ], - "MESSAGE": "OK", - "METHOD": "GET", - "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/rest/control/fabrics", - "RETURN_CODE": 200 - }, - "test_fabric_update_bulk_00030a": { - "DATA": [], - "MESSAGE": "OK", - "METHOD": "GET", - "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/rest/control/fabrics", - "RETURN_CODE": 200 - }, - "test_fabric_update_bulk_00031a": { - "DATA": [ - { - "asn": "65001", - "createdOn": 1711411093680, - "deviceType": "n9k", - "fabricId": "FABRIC-2", - "fabricName": "f1", - "fabricTechnology": "VXLANFabric", - "fabricTechnologyFriendly": "VXLAN EVPN", - "fabricType": "Switch_Fabric", - "fabricTypeFriendly": "Switch Fabric", - "id": 2, - "modifiedOn": 1711411096857, - "networkExtensionTemplate": "Default_Network_Extension_Universal", - "networkTemplate": "Default_Network_Universal", - "nvPairs": { - "AAA_REMOTE_IP_ENABLED": "false", - "AAA_SERVER_CONF": "", - "ACTIVE_MIGRATION": "false", - "ADVERTISE_PIP_BGP": "false", - "ADVERTISE_PIP_ON_BORDER": "true", - "AGENT_INTF": "eth0", - "AI_ML_QOS_POLICY": "AI_Fabric_QOS_400G", - "ALLOW_NXC": "true", - "ALLOW_NXC_PREV": "true", - "ANYCAST_BGW_ADVERTISE_PIP": "false", - "ANYCAST_GW_MAC": "2020.0000.00aa", - "ANYCAST_LB_ID": "", - "ANYCAST_RP_IP_RANGE": "10.254.254.0/24", - "ANYCAST_RP_IP_RANGE_INTERNAL": "", - "AUTO_SYMMETRIC_DEFAULT_VRF": "false", - "AUTO_SYMMETRIC_VRF_LITE": "false", - "AUTO_UNIQUE_VRF_LITE_IP_PREFIX": "false", - "AUTO_UNIQUE_VRF_LITE_IP_PREFIX_PREV": "false", - "AUTO_VRFLITE_IFC_DEFAULT_VRF": "false", - "BANNER": "", - "BFD_AUTH_ENABLE": "false", - "BFD_AUTH_KEY": "", - "BFD_AUTH_KEY_ID": "", - "BFD_ENABLE": "false", - "BFD_ENABLE_PREV": "", - "BFD_IBGP_ENABLE": "false", - "BFD_ISIS_ENABLE": "false", - "BFD_OSPF_ENABLE": "false", - "BFD_PIM_ENABLE": "false", - "BGP_AS": "65001", - "BGP_AS_PREV": "65001", - "BGP_AUTH_ENABLE": "false", - "BGP_AUTH_KEY": "", - "BGP_AUTH_KEY_TYPE": "3", - "BGP_LB_ID": "0", - "BOOTSTRAP_CONF": "", - "BOOTSTRAP_ENABLE": "false", - "BOOTSTRAP_ENABLE_PREV": "false", - "BOOTSTRAP_MULTISUBNET": "#Scope_Start_IP, Scope_End_IP, Scope_Default_Gateway, Scope_Subnet_Prefix", - "BOOTSTRAP_MULTISUBNET_INTERNAL": "", - "BRFIELD_DEBUG_FLAG": "Disable", - "BROWNFIELD_NETWORK_NAME_FORMAT": "Auto_Net_VNI$$VNI$$_VLAN$$VLAN_ID$$", - "BROWNFIELD_SKIP_OVERLAY_NETWORK_ATTACHMENTS": "false", - "CDP_ENABLE": "false", - "COPP_POLICY": "strict", - "DCI_SUBNET_RANGE": "10.33.0.0/16", - "DCI_SUBNET_TARGET_MASK": "30", - "DEAFULT_QUEUING_POLICY_CLOUDSCALE": "queuing_policy_default_8q_cloudscale", - "DEAFULT_QUEUING_POLICY_OTHER": "queuing_policy_default_other", - "DEAFULT_QUEUING_POLICY_R_SERIES": "queuing_policy_default_r_series", - "DEFAULT_VRF_REDIS_BGP_RMAP": "", - "DEPLOYMENT_FREEZE": "false", - "DHCP_ENABLE": "false", - "DHCP_END": "", - "DHCP_END_INTERNAL": "", - "DHCP_IPV6_ENABLE": "", - "DHCP_IPV6_ENABLE_INTERNAL": "", - "DHCP_START": "", - "DHCP_START_INTERNAL": "", - "DNS_SERVER_IP_LIST": "", - "DNS_SERVER_VRF": "", - "DOMAIN_NAME_INTERNAL": "", - "ENABLE_AAA": "false", - "ENABLE_AGENT": "false", - "ENABLE_AI_ML_QOS_POLICY": "false", - "ENABLE_AI_ML_QOS_POLICY_FLAP": "false", - "ENABLE_DEFAULT_QUEUING_POLICY": "false", - "ENABLE_EVPN": "true", - "ENABLE_FABRIC_VPC_DOMAIN_ID": "false", - "ENABLE_FABRIC_VPC_DOMAIN_ID_PREV": "false", - "ENABLE_L3VNI_NO_VLAN": "false", - "ENABLE_MACSEC": "false", - "ENABLE_NETFLOW": "false", - "ENABLE_NETFLOW_PREV": "false", - "ENABLE_NGOAM": "true", - "ENABLE_NXAPI": "true", - "ENABLE_NXAPI_HTTP": "true", - "ENABLE_PBR": "false", - "ENABLE_PVLAN": "false", - "ENABLE_PVLAN_PREV": "false", - "ENABLE_SGT": "false", - "ENABLE_SGT_PREV": "false", - "ENABLE_TENANT_DHCP": "true", - "ENABLE_TRM": "false", - "ENABLE_VPC_PEER_LINK_NATIVE_VLAN": "false", - "ESR_OPTION": "PBR", - "EXTRA_CONF_INTRA_LINKS": "", - "EXTRA_CONF_LEAF": "", - "EXTRA_CONF_SPINE": "", - "EXTRA_CONF_TOR": "", - "EXT_FABRIC_TYPE": "", - "FABRIC_INTERFACE_TYPE": "p2p", - "FABRIC_MTU": "9216", - "FABRIC_MTU_PREV": "9216", - "FABRIC_NAME": "f1", - "FABRIC_TYPE": "Switch_Fabric", - "FABRIC_VPC_DOMAIN_ID": "", - "FABRIC_VPC_DOMAIN_ID_PREV": "", - "FABRIC_VPC_QOS": "false", - "FABRIC_VPC_QOS_POLICY_NAME": "spine_qos_for_fabric_vpc_peering", - "FEATURE_PTP": "false", - "FEATURE_PTP_INTERNAL": "false", - "FF": "Easy_Fabric", - "GRFIELD_DEBUG_FLAG": "Disable", - "HD_TIME": "180", - "HOST_INTF_ADMIN_STATE": "true", - "IBGP_PEER_TEMPLATE": "", - "IBGP_PEER_TEMPLATE_LEAF": "", - "INBAND_DHCP_SERVERS": "", - "INBAND_MGMT": "false", - "INBAND_MGMT_PREV": "false", - "ISIS_AREA_NUM": "0001", - "ISIS_AREA_NUM_PREV": "", - "ISIS_AUTH_ENABLE": "false", - "ISIS_AUTH_KEY": "", - "ISIS_AUTH_KEYCHAIN_KEY_ID": "", - "ISIS_AUTH_KEYCHAIN_NAME": "", - "ISIS_LEVEL": "level-2", - "ISIS_OVERLOAD_ELAPSE_TIME": "", - "ISIS_OVERLOAD_ENABLE": "", - "ISIS_P2P_ENABLE": "", - "L2_HOST_INTF_MTU": "9216", - "L2_HOST_INTF_MTU_PREV": "9216", - "L2_SEGMENT_ID_RANGE": "30000-49000", - "L3VNI_MCAST_GROUP": "", - "L3_PARTITION_ID_RANGE": "50000-59000", - "LINK_STATE_ROUTING": "ospf", - "LINK_STATE_ROUTING_TAG": "UNDERLAY", - "LINK_STATE_ROUTING_TAG_PREV": "UNDERLAY", - "LOOPBACK0_IPV6_RANGE": "", - "LOOPBACK0_IP_RANGE": "10.2.0.0/22", - "LOOPBACK1_IPV6_RANGE": "", - "LOOPBACK1_IP_RANGE": "10.3.0.0/22", - "MACSEC_ALGORITHM": "", - "MACSEC_CIPHER_SUITE": "", - "MACSEC_FALLBACK_ALGORITHM": "", - "MACSEC_FALLBACK_KEY_STRING": "", - "MACSEC_KEY_STRING": "", - "MACSEC_REPORT_TIMER": "", - "MGMT_GW": "", - "MGMT_GW_INTERNAL": "", - "MGMT_PREFIX": "", - "MGMT_PREFIX_INTERNAL": "", - "MGMT_V6PREFIX": "", - "MGMT_V6PREFIX_INTERNAL": "", - "MPLS_HANDOFF": "false", - "MPLS_ISIS_AREA_NUM": "0001", - "MPLS_ISIS_AREA_NUM_PREV": "", - "MPLS_LB_ID": "", - "MPLS_LOOPBACK_IP_RANGE": "", - "MSO_CONNECTIVITY_DEPLOYED": "", - "MSO_CONTROLER_ID": "", - "MSO_SITE_GROUP_NAME": "", - "MSO_SITE_ID": "", - "MST_INSTANCE_RANGE": "", - "MULTICAST_GROUP_SUBNET": "239.1.1.0/25", - "NETFLOW_EXPORTER_LIST": "", - "NETFLOW_MONITOR_LIST": "", - "NETFLOW_RECORD_LIST": "", - "NETWORK_VLAN_RANGE": "2300-2999", - "NTP_SERVER_IP_LIST": "", - "NTP_SERVER_VRF": "", - "NVE_LB_ID": "1", - "NXAPI_HTTPS_PORT": "443", - "NXAPI_HTTP_PORT": "80", - "NXC_DEST_VRF": "", - "NXC_PROXY_PORT": "8080", - "NXC_PROXY_SERVER": "", - "NXC_SRC_INTF": "", - "OBJECT_TRACKING_NUMBER_RANGE": "100-299", - "OSPF_AREA_ID": "0.0.0.0", - "OSPF_AUTH_ENABLE": "false", - "OSPF_AUTH_KEY": "", - "OSPF_AUTH_KEY_ID": "", - "OVERLAY_MODE": "cli", - "OVERLAY_MODE_PREV": "cli", - "OVERWRITE_GLOBAL_NXC": "false", - "PER_VRF_LOOPBACK_AUTO_PROVISION": "false", - "PER_VRF_LOOPBACK_AUTO_PROVISION_PREV": "false", - "PER_VRF_LOOPBACK_AUTO_PROVISION_V6": "false", - "PER_VRF_LOOPBACK_AUTO_PROVISION_V6_PREV": "false", - "PER_VRF_LOOPBACK_IP_RANGE": "", - "PER_VRF_LOOPBACK_IP_RANGE_V6": "", - "PHANTOM_RP_LB_ID1": "", - "PHANTOM_RP_LB_ID2": "", - "PHANTOM_RP_LB_ID3": "", - "PHANTOM_RP_LB_ID4": "", - "PIM_HELLO_AUTH_ENABLE": "false", - "PIM_HELLO_AUTH_KEY": "", - "PM_ENABLE": "false", - "PM_ENABLE_PREV": "false", - "POWER_REDUNDANCY_MODE": "ps-redundant", - "PREMSO_PARENT_FABRIC": "", - "PTP_DOMAIN_ID": "", - "PTP_LB_ID": "", - "REPLICATION_MODE": "Multicast", - "ROUTER_ID_RANGE": "", - "ROUTE_MAP_SEQUENCE_NUMBER_RANGE": "1-65534", - "RP_COUNT": "2", - "RP_LB_ID": "254", - "RP_MODE": "asm", - "RR_COUNT": "2", - "SEED_SWITCH_CORE_INTERFACES": "", - "SERVICE_NETWORK_VLAN_RANGE": "3000-3199", - "SGT_ID_RANGE": "", - "SGT_NAME_PREFIX": "", - "SGT_PREPROVISION": "", - "SITE_ID": "65001", - "SLA_ID_RANGE": "10000-19999", - "SNMP_SERVER_HOST_TRAP": "true", - "SPINE_COUNT": "0", - "SPINE_SWITCH_CORE_INTERFACES": "", - "SSPINE_ADD_DEL_DEBUG_FLAG": "Disable", - "SSPINE_COUNT": "0", - "STATIC_UNDERLAY_IP_ALLOC": "false", - "STP_BRIDGE_PRIORITY": "", - "STP_ROOT_OPTION": "unmanaged", - "STP_VLAN_RANGE": "", - "STRICT_CC_MODE": "false", - "SUBINTERFACE_RANGE": "2-511", - "SUBNET_RANGE": "10.4.0.0/16", - "SUBNET_TARGET_MASK": "30", - "SYSLOG_SERVER_IP_LIST": "", - "SYSLOG_SERVER_VRF": "", - "SYSLOG_SEV": "", - "TCAM_ALLOCATION": "true", - "TOPDOWN_CONFIG_RM_TRACKING": "notstarted", - "UNDERLAY_IS_V6": "false", - "UNNUM_BOOTSTRAP_LB_ID": "", - "UNNUM_DHCP_END": "", - "UNNUM_DHCP_END_INTERNAL": "", - "UNNUM_DHCP_START": "", - "UNNUM_DHCP_START_INTERNAL": "", - "UPGRADE_FROM_VERSION": "", - "USE_LINK_LOCAL": "true", - "V6_SUBNET_RANGE": "", - "V6_SUBNET_TARGET_MASK": "126", - "VPC_AUTO_RECOVERY_TIME": "360", - "VPC_DELAY_RESTORE": "150", - "VPC_DELAY_RESTORE_TIME": "60", - "VPC_DOMAIN_ID_RANGE": "1-1000", - "VPC_ENABLE_IPv6_ND_SYNC": "true", - "VPC_PEER_KEEP_ALIVE_OPTION": "management", - "VPC_PEER_LINK_PO": "500", - "VPC_PEER_LINK_VLAN": "3600", - "VRF_LITE_AUTOCONFIG": "Manual", - "VRF_VLAN_RANGE": "2000-2299", - "abstract_anycast_rp": "anycast_rp", - "abstract_bgp": "base_bgp", - "abstract_bgp_neighbor": "evpn_bgp_rr_neighbor", - "abstract_bgp_rr": "evpn_bgp_rr", - "abstract_dhcp": "base_dhcp", - "abstract_extra_config_bootstrap": "extra_config_bootstrap_11_1", - "abstract_extra_config_leaf": "extra_config_leaf", - "abstract_extra_config_spine": "extra_config_spine", - "abstract_extra_config_tor": "extra_config_tor", - "abstract_feature_leaf": "base_feature_leaf_upg", - "abstract_feature_spine": "base_feature_spine_upg", - "abstract_isis": "base_isis_level2", - "abstract_isis_interface": "isis_interface", - "abstract_loopback_interface": "int_fabric_loopback_11_1", - "abstract_multicast": "base_multicast_11_1", - "abstract_ospf": "base_ospf", - "abstract_ospf_interface": "ospf_interface_11_1", - "abstract_pim_interface": "pim_interface", - "abstract_route_map": "route_map", - "abstract_routed_host": "int_routed_host", - "abstract_trunk_host": "int_trunk_host", - "abstract_vlan_interface": "int_fabric_vlan_11_1", - "abstract_vpc_domain": "base_vpc_domain_11_1", - "default_network": "Default_Network_Universal", - "default_pvlan_sec_network": "", - "default_vrf": "Default_VRF_Universal", - "enableRealTimeBackup": "", - "enableScheduledBackup": "", - "network_extension_template": "Default_Network_Extension_Universal", - "scheduledTime": "", - "temp_anycast_gateway": "anycast_gateway", - "temp_vpc_domain_mgmt": "vpc_domain_mgmt", - "temp_vpc_peer_link": "int_vpc_peer_link_po", - "vrf_extension_template": "Default_VRF_Extension_Universal" - }, - "operStatus": "HEALTHY", - "provisionMode": "DCNMTopDown", - "replicationMode": "Multicast", - "siteId": "65001", - "templateName": "Easy_Fabric", - "vrfExtensionTemplate": "Default_VRF_Extension_Universal", - "vrfTemplate": "Default_VRF_Universal" - } - ], - "MESSAGE": "OK", - "METHOD": "GET", - "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/rest/control/fabrics", - "RETURN_CODE": 200 - }, - "test_fabric_update_bulk_00032a": { - "DATA": [ - { - "asn": "65001", - "createdOn": 1711411093680, - "deviceType": "n9k", - "fabricId": "FABRIC-2", - "fabricName": "f1", - "fabricTechnology": "VXLANFabric", - "fabricTechnologyFriendly": "VXLAN EVPN", - "fabricType": "Switch_Fabric", - "fabricTypeFriendly": "Switch Fabric", - "id": 2, - "modifiedOn": 1711411096857, - "networkExtensionTemplate": "Default_Network_Extension_Universal", - "networkTemplate": "Default_Network_Universal", - "nvPairs": { - "AAA_REMOTE_IP_ENABLED": "false", - "AAA_SERVER_CONF": "", - "ACTIVE_MIGRATION": "false", - "ADVERTISE_PIP_BGP": "false", - "ADVERTISE_PIP_ON_BORDER": "true", - "AGENT_INTF": "eth0", - "AI_ML_QOS_POLICY": "AI_Fabric_QOS_400G", - "ALLOW_NXC": "true", - "ALLOW_NXC_PREV": "true", - "ANYCAST_BGW_ADVERTISE_PIP": "false", - "ANYCAST_GW_MAC": "2020.0000.00aa", - "ANYCAST_LB_ID": "", - "ANYCAST_RP_IP_RANGE": "10.254.254.0/24", - "ANYCAST_RP_IP_RANGE_INTERNAL": "", - "AUTO_SYMMETRIC_DEFAULT_VRF": "false", - "AUTO_SYMMETRIC_VRF_LITE": "false", - "AUTO_UNIQUE_VRF_LITE_IP_PREFIX": "false", - "AUTO_UNIQUE_VRF_LITE_IP_PREFIX_PREV": "false", - "AUTO_VRFLITE_IFC_DEFAULT_VRF": "false", - "BANNER": "", - "BFD_AUTH_ENABLE": "false", - "BFD_AUTH_KEY": "", - "BFD_AUTH_KEY_ID": "", - "BFD_ENABLE": "false", - "BFD_ENABLE_PREV": "", - "BFD_IBGP_ENABLE": "false", - "BFD_ISIS_ENABLE": "false", - "BFD_OSPF_ENABLE": "false", - "BFD_PIM_ENABLE": "false", - "BGP_AS": "65001", - "BGP_AS_PREV": "65001", - "BGP_AUTH_ENABLE": "false", - "BGP_AUTH_KEY": "", - "BGP_AUTH_KEY_TYPE": "3", - "BGP_LB_ID": "0", - "BOOTSTRAP_CONF": "", - "BOOTSTRAP_ENABLE": "false", - "BOOTSTRAP_ENABLE_PREV": "false", - "BOOTSTRAP_MULTISUBNET": "#Scope_Start_IP, Scope_End_IP, Scope_Default_Gateway, Scope_Subnet_Prefix", - "BOOTSTRAP_MULTISUBNET_INTERNAL": "", - "BRFIELD_DEBUG_FLAG": "Disable", - "BROWNFIELD_NETWORK_NAME_FORMAT": "Auto_Net_VNI$$VNI$$_VLAN$$VLAN_ID$$", - "BROWNFIELD_SKIP_OVERLAY_NETWORK_ATTACHMENTS": "false", - "CDP_ENABLE": "false", - "COPP_POLICY": "strict", - "DCI_SUBNET_RANGE": "10.33.0.0/16", - "DCI_SUBNET_TARGET_MASK": "30", - "DEAFULT_QUEUING_POLICY_CLOUDSCALE": "queuing_policy_default_8q_cloudscale", - "DEAFULT_QUEUING_POLICY_OTHER": "queuing_policy_default_other", - "DEAFULT_QUEUING_POLICY_R_SERIES": "queuing_policy_default_r_series", - "DEFAULT_VRF_REDIS_BGP_RMAP": "", - "DEPLOYMENT_FREEZE": "false", - "DHCP_ENABLE": "false", - "DHCP_END": "", - "DHCP_END_INTERNAL": "", - "DHCP_IPV6_ENABLE": "", - "DHCP_IPV6_ENABLE_INTERNAL": "", - "DHCP_START": "", - "DHCP_START_INTERNAL": "", - "DNS_SERVER_IP_LIST": "", - "DNS_SERVER_VRF": "", - "DOMAIN_NAME_INTERNAL": "", - "ENABLE_AAA": "false", - "ENABLE_AGENT": "false", - "ENABLE_AI_ML_QOS_POLICY": "false", - "ENABLE_AI_ML_QOS_POLICY_FLAP": "false", - "ENABLE_DEFAULT_QUEUING_POLICY": "false", - "ENABLE_EVPN": "true", - "ENABLE_FABRIC_VPC_DOMAIN_ID": "false", - "ENABLE_FABRIC_VPC_DOMAIN_ID_PREV": "false", - "ENABLE_L3VNI_NO_VLAN": "false", - "ENABLE_MACSEC": "false", - "ENABLE_NETFLOW": "false", - "ENABLE_NETFLOW_PREV": "false", - "ENABLE_NGOAM": "true", - "ENABLE_NXAPI": "true", - "ENABLE_NXAPI_HTTP": "true", - "ENABLE_PBR": "false", - "ENABLE_PVLAN": "false", - "ENABLE_PVLAN_PREV": "false", - "ENABLE_SGT": "false", - "ENABLE_SGT_PREV": "false", - "ENABLE_TENANT_DHCP": "true", - "ENABLE_TRM": "false", - "ENABLE_VPC_PEER_LINK_NATIVE_VLAN": "false", - "ESR_OPTION": "PBR", - "EXTRA_CONF_INTRA_LINKS": "", - "EXTRA_CONF_LEAF": "", - "EXTRA_CONF_SPINE": "", - "EXTRA_CONF_TOR": "", - "EXT_FABRIC_TYPE": "", - "FABRIC_INTERFACE_TYPE": "p2p", - "FABRIC_MTU": "9216", - "FABRIC_MTU_PREV": "9216", - "FABRIC_NAME": "f1", - "FABRIC_TYPE": "Switch_Fabric", - "FABRIC_VPC_DOMAIN_ID": "", - "FABRIC_VPC_DOMAIN_ID_PREV": "", - "FABRIC_VPC_QOS": "false", - "FABRIC_VPC_QOS_POLICY_NAME": "spine_qos_for_fabric_vpc_peering", - "FEATURE_PTP": "false", - "FEATURE_PTP_INTERNAL": "false", - "FF": "Easy_Fabric", - "GRFIELD_DEBUG_FLAG": "Disable", - "HD_TIME": "180", - "HOST_INTF_ADMIN_STATE": "true", - "IBGP_PEER_TEMPLATE": "", - "IBGP_PEER_TEMPLATE_LEAF": "", - "INBAND_DHCP_SERVERS": "", - "INBAND_MGMT": "false", - "INBAND_MGMT_PREV": "false", - "ISIS_AREA_NUM": "0001", - "ISIS_AREA_NUM_PREV": "", - "ISIS_AUTH_ENABLE": "false", - "ISIS_AUTH_KEY": "", - "ISIS_AUTH_KEYCHAIN_KEY_ID": "", - "ISIS_AUTH_KEYCHAIN_NAME": "", - "ISIS_LEVEL": "level-2", - "ISIS_OVERLOAD_ELAPSE_TIME": "", - "ISIS_OVERLOAD_ENABLE": "", - "ISIS_P2P_ENABLE": "", - "L2_HOST_INTF_MTU": "9216", - "L2_HOST_INTF_MTU_PREV": "9216", - "L2_SEGMENT_ID_RANGE": "30000-49000", - "L3VNI_MCAST_GROUP": "", - "L3_PARTITION_ID_RANGE": "50000-59000", - "LINK_STATE_ROUTING": "ospf", - "LINK_STATE_ROUTING_TAG": "UNDERLAY", - "LINK_STATE_ROUTING_TAG_PREV": "UNDERLAY", - "LOOPBACK0_IPV6_RANGE": "", - "LOOPBACK0_IP_RANGE": "10.2.0.0/22", - "LOOPBACK1_IPV6_RANGE": "", - "LOOPBACK1_IP_RANGE": "10.3.0.0/22", - "MACSEC_ALGORITHM": "", - "MACSEC_CIPHER_SUITE": "", - "MACSEC_FALLBACK_ALGORITHM": "", - "MACSEC_FALLBACK_KEY_STRING": "", - "MACSEC_KEY_STRING": "", - "MACSEC_REPORT_TIMER": "", - "MGMT_GW": "", - "MGMT_GW_INTERNAL": "", - "MGMT_PREFIX": "", - "MGMT_PREFIX_INTERNAL": "", - "MGMT_V6PREFIX": "", - "MGMT_V6PREFIX_INTERNAL": "", - "MPLS_HANDOFF": "false", - "MPLS_ISIS_AREA_NUM": "0001", - "MPLS_ISIS_AREA_NUM_PREV": "", - "MPLS_LB_ID": "", - "MPLS_LOOPBACK_IP_RANGE": "", - "MSO_CONNECTIVITY_DEPLOYED": "", - "MSO_CONTROLER_ID": "", - "MSO_SITE_GROUP_NAME": "", - "MSO_SITE_ID": "", - "MST_INSTANCE_RANGE": "", - "MULTICAST_GROUP_SUBNET": "239.1.1.0/25", - "NETFLOW_EXPORTER_LIST": "", - "NETFLOW_MONITOR_LIST": "", - "NETFLOW_RECORD_LIST": "", - "NETWORK_VLAN_RANGE": "2300-2999", - "NTP_SERVER_IP_LIST": "", - "NTP_SERVER_VRF": "", - "NVE_LB_ID": "1", - "NXAPI_HTTPS_PORT": "443", - "NXAPI_HTTP_PORT": "80", - "NXC_DEST_VRF": "", - "NXC_PROXY_PORT": "8080", - "NXC_PROXY_SERVER": "", - "NXC_SRC_INTF": "", - "OBJECT_TRACKING_NUMBER_RANGE": "100-299", - "OSPF_AREA_ID": "0.0.0.0", - "OSPF_AUTH_ENABLE": "false", - "OSPF_AUTH_KEY": "", - "OSPF_AUTH_KEY_ID": "", - "OVERLAY_MODE": "cli", - "OVERLAY_MODE_PREV": "cli", - "OVERWRITE_GLOBAL_NXC": "false", - "PER_VRF_LOOPBACK_AUTO_PROVISION": "false", - "PER_VRF_LOOPBACK_AUTO_PROVISION_PREV": "false", - "PER_VRF_LOOPBACK_AUTO_PROVISION_V6": "false", - "PER_VRF_LOOPBACK_AUTO_PROVISION_V6_PREV": "false", - "PER_VRF_LOOPBACK_IP_RANGE": "", - "PER_VRF_LOOPBACK_IP_RANGE_V6": "", - "PHANTOM_RP_LB_ID1": "", - "PHANTOM_RP_LB_ID2": "", - "PHANTOM_RP_LB_ID3": "", - "PHANTOM_RP_LB_ID4": "", - "PIM_HELLO_AUTH_ENABLE": "false", - "PIM_HELLO_AUTH_KEY": "", - "PM_ENABLE": "false", - "PM_ENABLE_PREV": "false", - "POWER_REDUNDANCY_MODE": "ps-redundant", - "PREMSO_PARENT_FABRIC": "", - "PTP_DOMAIN_ID": "", - "PTP_LB_ID": "", - "REPLICATION_MODE": "Multicast", - "ROUTER_ID_RANGE": "", - "ROUTE_MAP_SEQUENCE_NUMBER_RANGE": "1-65534", - "RP_COUNT": "2", - "RP_LB_ID": "254", - "RP_MODE": "asm", - "RR_COUNT": "2", - "SEED_SWITCH_CORE_INTERFACES": "", - "SERVICE_NETWORK_VLAN_RANGE": "3000-3199", - "SGT_ID_RANGE": "", - "SGT_NAME_PREFIX": "", - "SGT_PREPROVISION": "", - "SITE_ID": "65001", - "SLA_ID_RANGE": "10000-19999", - "SNMP_SERVER_HOST_TRAP": "true", - "SPINE_COUNT": "0", - "SPINE_SWITCH_CORE_INTERFACES": "", - "SSPINE_ADD_DEL_DEBUG_FLAG": "Disable", - "SSPINE_COUNT": "0", - "STATIC_UNDERLAY_IP_ALLOC": "false", - "STP_BRIDGE_PRIORITY": "", - "STP_ROOT_OPTION": "unmanaged", - "STP_VLAN_RANGE": "", - "STRICT_CC_MODE": "false", - "SUBINTERFACE_RANGE": "2-511", - "SUBNET_RANGE": "10.4.0.0/16", - "SUBNET_TARGET_MASK": "30", - "SYSLOG_SERVER_IP_LIST": "", - "SYSLOG_SERVER_VRF": "", - "SYSLOG_SEV": "", - "TCAM_ALLOCATION": "true", - "TOPDOWN_CONFIG_RM_TRACKING": "notstarted", - "UNDERLAY_IS_V6": "false", - "UNNUM_BOOTSTRAP_LB_ID": "", - "UNNUM_DHCP_END": "", - "UNNUM_DHCP_END_INTERNAL": "", - "UNNUM_DHCP_START": "", - "UNNUM_DHCP_START_INTERNAL": "", - "UPGRADE_FROM_VERSION": "", - "USE_LINK_LOCAL": "true", - "V6_SUBNET_RANGE": "", - "V6_SUBNET_TARGET_MASK": "126", - "VPC_AUTO_RECOVERY_TIME": "360", - "VPC_DELAY_RESTORE": "150", - "VPC_DELAY_RESTORE_TIME": "60", - "VPC_DOMAIN_ID_RANGE": "1-1000", - "VPC_ENABLE_IPv6_ND_SYNC": "true", - "VPC_PEER_KEEP_ALIVE_OPTION": "management", - "VPC_PEER_LINK_PO": "500", - "VPC_PEER_LINK_VLAN": "3600", - "VRF_LITE_AUTOCONFIG": "Manual", - "VRF_VLAN_RANGE": "2000-2299", - "abstract_anycast_rp": "anycast_rp", - "abstract_bgp": "base_bgp", - "abstract_bgp_neighbor": "evpn_bgp_rr_neighbor", - "abstract_bgp_rr": "evpn_bgp_rr", - "abstract_dhcp": "base_dhcp", - "abstract_extra_config_bootstrap": "extra_config_bootstrap_11_1", - "abstract_extra_config_leaf": "extra_config_leaf", - "abstract_extra_config_spine": "extra_config_spine", - "abstract_extra_config_tor": "extra_config_tor", - "abstract_feature_leaf": "base_feature_leaf_upg", - "abstract_feature_spine": "base_feature_spine_upg", - "abstract_isis": "base_isis_level2", - "abstract_isis_interface": "isis_interface", - "abstract_loopback_interface": "int_fabric_loopback_11_1", - "abstract_multicast": "base_multicast_11_1", - "abstract_ospf": "base_ospf", - "abstract_ospf_interface": "ospf_interface_11_1", - "abstract_pim_interface": "pim_interface", - "abstract_route_map": "route_map", - "abstract_routed_host": "int_routed_host", - "abstract_trunk_host": "int_trunk_host", - "abstract_vlan_interface": "int_fabric_vlan_11_1", - "abstract_vpc_domain": "base_vpc_domain_11_1", - "default_network": "Default_Network_Universal", - "default_pvlan_sec_network": "", - "default_vrf": "Default_VRF_Universal", - "enableRealTimeBackup": "", - "enableScheduledBackup": "", - "network_extension_template": "Default_Network_Extension_Universal", - "scheduledTime": "", - "temp_anycast_gateway": "anycast_gateway", - "temp_vpc_domain_mgmt": "vpc_domain_mgmt", - "temp_vpc_peer_link": "int_vpc_peer_link_po", - "vrf_extension_template": "Default_VRF_Extension_Universal" - }, - "operStatus": "HEALTHY", - "provisionMode": "DCNMTopDown", - "replicationMode": "Multicast", - "siteId": "65001", - "templateName": "Easy_Fabric", - "vrfExtensionTemplate": "Default_VRF_Extension_Universal", - "vrfTemplate": "Default_VRF_Universal" - } - ], - "MESSAGE": "OK", - "METHOD": "GET", - "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/rest/control/fabrics", - "RETURN_CODE": 200 - }, - "test_fabric_update_bulk_00033a": { - "DATA": [ - { - "asn": "65001", - "createdOn": 1711411093680, - "deviceType": "n9k", - "fabricId": "FABRIC-2", - "fabricName": "f1", - "fabricTechnology": "VXLANFabric", - "fabricTechnologyFriendly": "VXLAN EVPN", - "fabricType": "Switch_Fabric", - "fabricTypeFriendly": "Switch Fabric", - "id": 2, - "modifiedOn": 1711411096857, - "networkExtensionTemplate": "Default_Network_Extension_Universal", - "networkTemplate": "Default_Network_Universal", - "nvPairs": { - "AAA_REMOTE_IP_ENABLED": "false", - "AAA_SERVER_CONF": "", - "ACTIVE_MIGRATION": "false", - "ADVERTISE_PIP_BGP": "false", - "ADVERTISE_PIP_ON_BORDER": "true", - "AGENT_INTF": "eth0", - "AI_ML_QOS_POLICY": "AI_Fabric_QOS_400G", - "ALLOW_NXC": "true", - "ALLOW_NXC_PREV": "true", - "ANYCAST_BGW_ADVERTISE_PIP": "false", - "ANYCAST_GW_MAC": "2020.0000.00aa", - "ANYCAST_LB_ID": "", - "ANYCAST_RP_IP_RANGE": "10.254.254.0/24", - "ANYCAST_RP_IP_RANGE_INTERNAL": "", - "AUTO_SYMMETRIC_DEFAULT_VRF": "false", - "AUTO_SYMMETRIC_VRF_LITE": "false", - "AUTO_UNIQUE_VRF_LITE_IP_PREFIX": "false", - "AUTO_UNIQUE_VRF_LITE_IP_PREFIX_PREV": "false", - "AUTO_VRFLITE_IFC_DEFAULT_VRF": "false", - "BANNER": "", - "BFD_AUTH_ENABLE": "false", - "BFD_AUTH_KEY": "", - "BFD_AUTH_KEY_ID": "", - "BFD_ENABLE": "false", - "BFD_ENABLE_PREV": "", - "BFD_IBGP_ENABLE": "false", - "BFD_ISIS_ENABLE": "false", - "BFD_OSPF_ENABLE": "false", - "BFD_PIM_ENABLE": "false", - "BGP_AS": "65001", - "BGP_AS_PREV": "65001", - "BGP_AUTH_ENABLE": "false", - "BGP_AUTH_KEY": "", - "BGP_AUTH_KEY_TYPE": "3", - "BGP_LB_ID": "0", - "BOOTSTRAP_CONF": "", - "BOOTSTRAP_ENABLE": "false", - "BOOTSTRAP_ENABLE_PREV": "false", - "BOOTSTRAP_MULTISUBNET": "#Scope_Start_IP, Scope_End_IP, Scope_Default_Gateway, Scope_Subnet_Prefix", - "BOOTSTRAP_MULTISUBNET_INTERNAL": "", - "BRFIELD_DEBUG_FLAG": "Disable", - "BROWNFIELD_NETWORK_NAME_FORMAT": "Auto_Net_VNI$$VNI$$_VLAN$$VLAN_ID$$", - "BROWNFIELD_SKIP_OVERLAY_NETWORK_ATTACHMENTS": "false", - "CDP_ENABLE": "false", - "COPP_POLICY": "strict", - "DCI_SUBNET_RANGE": "10.33.0.0/16", - "DCI_SUBNET_TARGET_MASK": "30", - "DEAFULT_QUEUING_POLICY_CLOUDSCALE": "queuing_policy_default_8q_cloudscale", - "DEAFULT_QUEUING_POLICY_OTHER": "queuing_policy_default_other", - "DEAFULT_QUEUING_POLICY_R_SERIES": "queuing_policy_default_r_series", - "DEFAULT_VRF_REDIS_BGP_RMAP": "", - "DEPLOYMENT_FREEZE": "false", - "DHCP_ENABLE": "false", - "DHCP_END": "", - "DHCP_END_INTERNAL": "", - "DHCP_IPV6_ENABLE": "", - "DHCP_IPV6_ENABLE_INTERNAL": "", - "DHCP_START": "", - "DHCP_START_INTERNAL": "", - "DNS_SERVER_IP_LIST": "", - "DNS_SERVER_VRF": "", - "DOMAIN_NAME_INTERNAL": "", - "ENABLE_AAA": "false", - "ENABLE_AGENT": "false", - "ENABLE_AI_ML_QOS_POLICY": "false", - "ENABLE_AI_ML_QOS_POLICY_FLAP": "false", - "ENABLE_DEFAULT_QUEUING_POLICY": "false", - "ENABLE_EVPN": "true", - "ENABLE_FABRIC_VPC_DOMAIN_ID": "false", - "ENABLE_FABRIC_VPC_DOMAIN_ID_PREV": "false", - "ENABLE_L3VNI_NO_VLAN": "false", - "ENABLE_MACSEC": "false", - "ENABLE_NETFLOW": "false", - "ENABLE_NETFLOW_PREV": "false", - "ENABLE_NGOAM": "true", - "ENABLE_NXAPI": "true", - "ENABLE_NXAPI_HTTP": "true", - "ENABLE_PBR": "false", - "ENABLE_PVLAN": "false", - "ENABLE_PVLAN_PREV": "false", - "ENABLE_SGT": "false", - "ENABLE_SGT_PREV": "false", - "ENABLE_TENANT_DHCP": "true", - "ENABLE_TRM": "false", - "ENABLE_VPC_PEER_LINK_NATIVE_VLAN": "false", - "ESR_OPTION": "PBR", - "EXTRA_CONF_INTRA_LINKS": "", - "EXTRA_CONF_LEAF": "", - "EXTRA_CONF_SPINE": "", - "EXTRA_CONF_TOR": "", - "EXT_FABRIC_TYPE": "", - "FABRIC_INTERFACE_TYPE": "p2p", - "FABRIC_MTU": "9216", - "FABRIC_MTU_PREV": "9216", - "FABRIC_NAME": "f1", - "FABRIC_TYPE": "Switch_Fabric", - "FABRIC_VPC_DOMAIN_ID": "", - "FABRIC_VPC_DOMAIN_ID_PREV": "", - "FABRIC_VPC_QOS": "false", - "FABRIC_VPC_QOS_POLICY_NAME": "spine_qos_for_fabric_vpc_peering", - "FEATURE_PTP": "false", - "FEATURE_PTP_INTERNAL": "false", - "FF": "Easy_Fabric", - "GRFIELD_DEBUG_FLAG": "Disable", - "HD_TIME": "180", - "HOST_INTF_ADMIN_STATE": "true", - "IBGP_PEER_TEMPLATE": "", - "IBGP_PEER_TEMPLATE_LEAF": "", - "INBAND_DHCP_SERVERS": "", - "INBAND_MGMT": "false", - "INBAND_MGMT_PREV": "false", - "ISIS_AREA_NUM": "0001", - "ISIS_AREA_NUM_PREV": "", - "ISIS_AUTH_ENABLE": "false", - "ISIS_AUTH_KEY": "", - "ISIS_AUTH_KEYCHAIN_KEY_ID": "", - "ISIS_AUTH_KEYCHAIN_NAME": "", - "ISIS_LEVEL": "level-2", - "ISIS_OVERLOAD_ELAPSE_TIME": "", - "ISIS_OVERLOAD_ENABLE": "", - "ISIS_P2P_ENABLE": "", - "L2_HOST_INTF_MTU": "9216", - "L2_HOST_INTF_MTU_PREV": "9216", - "L2_SEGMENT_ID_RANGE": "30000-49000", - "L3VNI_MCAST_GROUP": "", - "L3_PARTITION_ID_RANGE": "50000-59000", - "LINK_STATE_ROUTING": "ospf", - "LINK_STATE_ROUTING_TAG": "UNDERLAY", - "LINK_STATE_ROUTING_TAG_PREV": "UNDERLAY", - "LOOPBACK0_IPV6_RANGE": "", - "LOOPBACK0_IP_RANGE": "10.2.0.0/22", - "LOOPBACK1_IPV6_RANGE": "", - "LOOPBACK1_IP_RANGE": "10.3.0.0/22", - "MACSEC_ALGORITHM": "", - "MACSEC_CIPHER_SUITE": "", - "MACSEC_FALLBACK_ALGORITHM": "", - "MACSEC_FALLBACK_KEY_STRING": "", - "MACSEC_KEY_STRING": "", - "MACSEC_REPORT_TIMER": "", - "MGMT_GW": "", - "MGMT_GW_INTERNAL": "", - "MGMT_PREFIX": "", - "MGMT_PREFIX_INTERNAL": "", - "MGMT_V6PREFIX": "", - "MGMT_V6PREFIX_INTERNAL": "", - "MPLS_HANDOFF": "false", - "MPLS_ISIS_AREA_NUM": "0001", - "MPLS_ISIS_AREA_NUM_PREV": "", - "MPLS_LB_ID": "", - "MPLS_LOOPBACK_IP_RANGE": "", - "MSO_CONNECTIVITY_DEPLOYED": "", - "MSO_CONTROLER_ID": "", - "MSO_SITE_GROUP_NAME": "", - "MSO_SITE_ID": "", - "MST_INSTANCE_RANGE": "", - "MULTICAST_GROUP_SUBNET": "239.1.1.0/25", - "NETFLOW_EXPORTER_LIST": "", - "NETFLOW_MONITOR_LIST": "", - "NETFLOW_RECORD_LIST": "", - "NETWORK_VLAN_RANGE": "2300-2999", - "NTP_SERVER_IP_LIST": "", - "NTP_SERVER_VRF": "", - "NVE_LB_ID": "1", - "NXAPI_HTTPS_PORT": "443", - "NXAPI_HTTP_PORT": "80", - "NXC_DEST_VRF": "", - "NXC_PROXY_PORT": "8080", - "NXC_PROXY_SERVER": "", - "NXC_SRC_INTF": "", - "OBJECT_TRACKING_NUMBER_RANGE": "100-299", - "OSPF_AREA_ID": "0.0.0.0", - "OSPF_AUTH_ENABLE": "false", - "OSPF_AUTH_KEY": "", - "OSPF_AUTH_KEY_ID": "", - "OVERLAY_MODE": "cli", - "OVERLAY_MODE_PREV": "cli", - "OVERWRITE_GLOBAL_NXC": "false", - "PER_VRF_LOOPBACK_AUTO_PROVISION": "false", - "PER_VRF_LOOPBACK_AUTO_PROVISION_PREV": "false", - "PER_VRF_LOOPBACK_AUTO_PROVISION_V6": "false", - "PER_VRF_LOOPBACK_AUTO_PROVISION_V6_PREV": "false", - "PER_VRF_LOOPBACK_IP_RANGE": "", - "PER_VRF_LOOPBACK_IP_RANGE_V6": "", - "PHANTOM_RP_LB_ID1": "", - "PHANTOM_RP_LB_ID2": "", - "PHANTOM_RP_LB_ID3": "", - "PHANTOM_RP_LB_ID4": "", - "PIM_HELLO_AUTH_ENABLE": "false", - "PIM_HELLO_AUTH_KEY": "", - "PM_ENABLE": "false", - "PM_ENABLE_PREV": "false", - "POWER_REDUNDANCY_MODE": "ps-redundant", - "PREMSO_PARENT_FABRIC": "", - "PTP_DOMAIN_ID": "", - "PTP_LB_ID": "", - "REPLICATION_MODE": "Multicast", - "ROUTER_ID_RANGE": "", - "ROUTE_MAP_SEQUENCE_NUMBER_RANGE": "1-65534", - "RP_COUNT": "2", - "RP_LB_ID": "254", - "RP_MODE": "asm", - "RR_COUNT": "2", - "SEED_SWITCH_CORE_INTERFACES": "", - "SERVICE_NETWORK_VLAN_RANGE": "3000-3199", - "SGT_ID_RANGE": "", - "SGT_NAME_PREFIX": "", - "SGT_PREPROVISION": "", - "SITE_ID": "65001", - "SLA_ID_RANGE": "10000-19999", - "SNMP_SERVER_HOST_TRAP": "true", - "SPINE_COUNT": "0", - "SPINE_SWITCH_CORE_INTERFACES": "", - "SSPINE_ADD_DEL_DEBUG_FLAG": "Disable", - "SSPINE_COUNT": "0", - "STATIC_UNDERLAY_IP_ALLOC": "false", - "STP_BRIDGE_PRIORITY": "", - "STP_ROOT_OPTION": "unmanaged", - "STP_VLAN_RANGE": "", - "STRICT_CC_MODE": "false", - "SUBINTERFACE_RANGE": "2-511", - "SUBNET_RANGE": "10.4.0.0/16", - "SUBNET_TARGET_MASK": "30", - "SYSLOG_SERVER_IP_LIST": "", - "SYSLOG_SERVER_VRF": "", - "SYSLOG_SEV": "", - "TCAM_ALLOCATION": "true", - "TOPDOWN_CONFIG_RM_TRACKING": "notstarted", - "UNDERLAY_IS_V6": "false", - "UNNUM_BOOTSTRAP_LB_ID": "", - "UNNUM_DHCP_END": "", - "UNNUM_DHCP_END_INTERNAL": "", - "UNNUM_DHCP_START": "", - "UNNUM_DHCP_START_INTERNAL": "", - "UPGRADE_FROM_VERSION": "", - "USE_LINK_LOCAL": "true", - "V6_SUBNET_RANGE": "", - "V6_SUBNET_TARGET_MASK": "126", - "VPC_AUTO_RECOVERY_TIME": "360", - "VPC_DELAY_RESTORE": "150", - "VPC_DELAY_RESTORE_TIME": "60", - "VPC_DOMAIN_ID_RANGE": "1-1000", - "VPC_ENABLE_IPv6_ND_SYNC": "true", - "VPC_PEER_KEEP_ALIVE_OPTION": "management", - "VPC_PEER_LINK_PO": "500", - "VPC_PEER_LINK_VLAN": "3600", - "VRF_LITE_AUTOCONFIG": "Manual", - "VRF_VLAN_RANGE": "2000-2299", - "abstract_anycast_rp": "anycast_rp", - "abstract_bgp": "base_bgp", - "abstract_bgp_neighbor": "evpn_bgp_rr_neighbor", - "abstract_bgp_rr": "evpn_bgp_rr", - "abstract_dhcp": "base_dhcp", - "abstract_extra_config_bootstrap": "extra_config_bootstrap_11_1", - "abstract_extra_config_leaf": "extra_config_leaf", - "abstract_extra_config_spine": "extra_config_spine", - "abstract_extra_config_tor": "extra_config_tor", - "abstract_feature_leaf": "base_feature_leaf_upg", - "abstract_feature_spine": "base_feature_spine_upg", - "abstract_isis": "base_isis_level2", - "abstract_isis_interface": "isis_interface", - "abstract_loopback_interface": "int_fabric_loopback_11_1", - "abstract_multicast": "base_multicast_11_1", - "abstract_ospf": "base_ospf", - "abstract_ospf_interface": "ospf_interface_11_1", - "abstract_pim_interface": "pim_interface", - "abstract_route_map": "route_map", - "abstract_routed_host": "int_routed_host", - "abstract_trunk_host": "int_trunk_host", - "abstract_vlan_interface": "int_fabric_vlan_11_1", - "abstract_vpc_domain": "base_vpc_domain_11_1", - "default_network": "Default_Network_Universal", - "default_pvlan_sec_network": "", - "default_vrf": "Default_VRF_Universal", - "enableRealTimeBackup": "", - "enableScheduledBackup": "", - "network_extension_template": "Default_Network_Extension_Universal", - "scheduledTime": "", - "temp_anycast_gateway": "anycast_gateway", - "temp_vpc_domain_mgmt": "vpc_domain_mgmt", - "temp_vpc_peer_link": "int_vpc_peer_link_po", - "vrf_extension_template": "Default_VRF_Extension_Universal" - }, - "operStatus": "HEALTHY", - "provisionMode": "DCNMTopDown", - "replicationMode": "Multicast", - "siteId": "65001", - "templateName": "Easy_Fabric", - "vrfExtensionTemplate": "Default_VRF_Extension_Universal", - "vrfTemplate": "Default_VRF_Universal" - } - ], - "MESSAGE": "OK", - "METHOD": "GET", - "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/rest/control/fabrics", - "RETURN_CODE": 200 - }, - "test_fabric_update_bulk_00034a": { - "DATA": [ - { - "asn": "65001", - "createdOn": 1711411093680, - "deviceType": "n9k", - "fabricId": "FABRIC-2", - "fabricName": "f1", - "fabricTechnology": "VXLANFabric", - "fabricTechnologyFriendly": "VXLAN EVPN", - "fabricType": "Switch_Fabric", - "fabricTypeFriendly": "Switch Fabric", - "id": 2, - "modifiedOn": 1711411096857, - "networkExtensionTemplate": "Default_Network_Extension_Universal", - "networkTemplate": "Default_Network_Universal", - "nvPairs": { - "AAA_REMOTE_IP_ENABLED": "false", - "AAA_SERVER_CONF": "", - "ACTIVE_MIGRATION": "false", - "ADVERTISE_PIP_BGP": "false", - "ADVERTISE_PIP_ON_BORDER": "true", - "AGENT_INTF": "eth0", - "AI_ML_QOS_POLICY": "AI_Fabric_QOS_400G", - "ALLOW_NXC": "true", - "ALLOW_NXC_PREV": "true", - "ANYCAST_BGW_ADVERTISE_PIP": "false", - "ANYCAST_GW_MAC": "2020.0000.00aa", - "ANYCAST_LB_ID": "", - "ANYCAST_RP_IP_RANGE": "10.254.254.0/24", - "ANYCAST_RP_IP_RANGE_INTERNAL": "", - "AUTO_SYMMETRIC_DEFAULT_VRF": "false", - "AUTO_SYMMETRIC_VRF_LITE": "false", - "AUTO_UNIQUE_VRF_LITE_IP_PREFIX": "false", - "AUTO_UNIQUE_VRF_LITE_IP_PREFIX_PREV": "false", - "AUTO_VRFLITE_IFC_DEFAULT_VRF": "false", - "BANNER": "", - "BFD_AUTH_ENABLE": "false", - "BFD_AUTH_KEY": "", - "BFD_AUTH_KEY_ID": "", - "BFD_ENABLE": "false", - "BFD_ENABLE_PREV": "", - "BFD_IBGP_ENABLE": "false", - "BFD_ISIS_ENABLE": "false", - "BFD_OSPF_ENABLE": "false", - "BFD_PIM_ENABLE": "false", - "BGP_AS": "65001", - "BGP_AS_PREV": "65001", - "BGP_AUTH_ENABLE": "false", - "BGP_AUTH_KEY": "", - "BGP_AUTH_KEY_TYPE": "3", - "BGP_LB_ID": "0", - "BOOTSTRAP_CONF": "", - "BOOTSTRAP_ENABLE": "false", - "BOOTSTRAP_ENABLE_PREV": "false", - "BOOTSTRAP_MULTISUBNET": "#Scope_Start_IP, Scope_End_IP, Scope_Default_Gateway, Scope_Subnet_Prefix", - "BOOTSTRAP_MULTISUBNET_INTERNAL": "", - "BRFIELD_DEBUG_FLAG": "Disable", - "BROWNFIELD_NETWORK_NAME_FORMAT": "Auto_Net_VNI$$VNI$$_VLAN$$VLAN_ID$$", - "BROWNFIELD_SKIP_OVERLAY_NETWORK_ATTACHMENTS": "false", - "CDP_ENABLE": "false", - "COPP_POLICY": "strict", - "DCI_SUBNET_RANGE": "10.33.0.0/16", - "DCI_SUBNET_TARGET_MASK": "30", - "DEAFULT_QUEUING_POLICY_CLOUDSCALE": "queuing_policy_default_8q_cloudscale", - "DEAFULT_QUEUING_POLICY_OTHER": "queuing_policy_default_other", - "DEAFULT_QUEUING_POLICY_R_SERIES": "queuing_policy_default_r_series", - "DEFAULT_VRF_REDIS_BGP_RMAP": "", - "DEPLOYMENT_FREEZE": "false", - "DHCP_ENABLE": "false", - "DHCP_END": "", - "DHCP_END_INTERNAL": "", - "DHCP_IPV6_ENABLE": "", - "DHCP_IPV6_ENABLE_INTERNAL": "", - "DHCP_START": "", - "DHCP_START_INTERNAL": "", - "DNS_SERVER_IP_LIST": "", - "DNS_SERVER_VRF": "", - "DOMAIN_NAME_INTERNAL": "", - "ENABLE_AAA": "false", - "ENABLE_AGENT": "false", - "ENABLE_AI_ML_QOS_POLICY": "false", - "ENABLE_AI_ML_QOS_POLICY_FLAP": "false", - "ENABLE_DEFAULT_QUEUING_POLICY": "false", - "ENABLE_EVPN": "true", - "ENABLE_FABRIC_VPC_DOMAIN_ID": "false", - "ENABLE_FABRIC_VPC_DOMAIN_ID_PREV": "false", - "ENABLE_L3VNI_NO_VLAN": "false", - "ENABLE_MACSEC": "false", - "ENABLE_NETFLOW": "false", - "ENABLE_NETFLOW_PREV": "false", - "ENABLE_NGOAM": "true", - "ENABLE_NXAPI": "true", - "ENABLE_NXAPI_HTTP": "true", - "ENABLE_PBR": "false", - "ENABLE_PVLAN": "false", - "ENABLE_PVLAN_PREV": "false", - "ENABLE_SGT": "false", - "ENABLE_SGT_PREV": "false", - "ENABLE_TENANT_DHCP": "true", - "ENABLE_TRM": "false", - "ENABLE_VPC_PEER_LINK_NATIVE_VLAN": "false", - "ESR_OPTION": "PBR", - "EXTRA_CONF_INTRA_LINKS": "", - "EXTRA_CONF_LEAF": "", - "EXTRA_CONF_SPINE": "", - "EXTRA_CONF_TOR": "", - "EXT_FABRIC_TYPE": "", - "FABRIC_INTERFACE_TYPE": "p2p", - "FABRIC_MTU": "9216", - "FABRIC_MTU_PREV": "9216", - "FABRIC_NAME": "f1", - "FABRIC_TYPE": "Switch_Fabric", - "FABRIC_VPC_DOMAIN_ID": "", - "FABRIC_VPC_DOMAIN_ID_PREV": "", - "FABRIC_VPC_QOS": "false", - "FABRIC_VPC_QOS_POLICY_NAME": "spine_qos_for_fabric_vpc_peering", - "FEATURE_PTP": "false", - "FEATURE_PTP_INTERNAL": "false", - "FF": "Easy_Fabric", - "GRFIELD_DEBUG_FLAG": "Disable", - "HD_TIME": "180", - "HOST_INTF_ADMIN_STATE": "true", - "IBGP_PEER_TEMPLATE": "", - "IBGP_PEER_TEMPLATE_LEAF": "", - "INBAND_DHCP_SERVERS": "", - "INBAND_MGMT": "false", - "INBAND_MGMT_PREV": "false", - "ISIS_AREA_NUM": "0001", - "ISIS_AREA_NUM_PREV": "", - "ISIS_AUTH_ENABLE": "false", - "ISIS_AUTH_KEY": "", - "ISIS_AUTH_KEYCHAIN_KEY_ID": "", - "ISIS_AUTH_KEYCHAIN_NAME": "", - "ISIS_LEVEL": "level-2", - "ISIS_OVERLOAD_ELAPSE_TIME": "", - "ISIS_OVERLOAD_ENABLE": "", - "ISIS_P2P_ENABLE": "", - "L2_HOST_INTF_MTU": "9216", - "L2_HOST_INTF_MTU_PREV": "9216", - "L2_SEGMENT_ID_RANGE": "30000-49000", - "L3VNI_MCAST_GROUP": "", - "L3_PARTITION_ID_RANGE": "50000-59000", - "LINK_STATE_ROUTING": "ospf", - "LINK_STATE_ROUTING_TAG": "UNDERLAY", - "LINK_STATE_ROUTING_TAG_PREV": "UNDERLAY", - "LOOPBACK0_IPV6_RANGE": "", - "LOOPBACK0_IP_RANGE": "10.2.0.0/22", - "LOOPBACK1_IPV6_RANGE": "", - "LOOPBACK1_IP_RANGE": "10.3.0.0/22", - "MACSEC_ALGORITHM": "", - "MACSEC_CIPHER_SUITE": "", - "MACSEC_FALLBACK_ALGORITHM": "", - "MACSEC_FALLBACK_KEY_STRING": "", - "MACSEC_KEY_STRING": "", - "MACSEC_REPORT_TIMER": "", - "MGMT_GW": "", - "MGMT_GW_INTERNAL": "", - "MGMT_PREFIX": "", - "MGMT_PREFIX_INTERNAL": "", - "MGMT_V6PREFIX": "", - "MGMT_V6PREFIX_INTERNAL": "", - "MPLS_HANDOFF": "false", - "MPLS_ISIS_AREA_NUM": "0001", - "MPLS_ISIS_AREA_NUM_PREV": "", - "MPLS_LB_ID": "", - "MPLS_LOOPBACK_IP_RANGE": "", - "MSO_CONNECTIVITY_DEPLOYED": "", - "MSO_CONTROLER_ID": "", - "MSO_SITE_GROUP_NAME": "", - "MSO_SITE_ID": "", - "MST_INSTANCE_RANGE": "", - "MULTICAST_GROUP_SUBNET": "239.1.1.0/25", - "NETFLOW_EXPORTER_LIST": "", - "NETFLOW_MONITOR_LIST": "", - "NETFLOW_RECORD_LIST": "", - "NETWORK_VLAN_RANGE": "2300-2999", - "NTP_SERVER_IP_LIST": "", - "NTP_SERVER_VRF": "", - "NVE_LB_ID": "1", - "NXAPI_HTTPS_PORT": "443", - "NXAPI_HTTP_PORT": "80", - "NXC_DEST_VRF": "", - "NXC_PROXY_PORT": "8080", - "NXC_PROXY_SERVER": "", - "NXC_SRC_INTF": "", - "OBJECT_TRACKING_NUMBER_RANGE": "100-299", - "OSPF_AREA_ID": "0.0.0.0", - "OSPF_AUTH_ENABLE": "false", - "OSPF_AUTH_KEY": "", - "OSPF_AUTH_KEY_ID": "", - "OVERLAY_MODE": "cli", - "OVERLAY_MODE_PREV": "cli", - "OVERWRITE_GLOBAL_NXC": "false", - "PER_VRF_LOOPBACK_AUTO_PROVISION": "false", - "PER_VRF_LOOPBACK_AUTO_PROVISION_PREV": "false", - "PER_VRF_LOOPBACK_AUTO_PROVISION_V6": "false", - "PER_VRF_LOOPBACK_AUTO_PROVISION_V6_PREV": "false", - "PER_VRF_LOOPBACK_IP_RANGE": "", - "PER_VRF_LOOPBACK_IP_RANGE_V6": "", - "PHANTOM_RP_LB_ID1": "", - "PHANTOM_RP_LB_ID2": "", - "PHANTOM_RP_LB_ID3": "", - "PHANTOM_RP_LB_ID4": "", - "PIM_HELLO_AUTH_ENABLE": "false", - "PIM_HELLO_AUTH_KEY": "", - "PM_ENABLE": "false", - "PM_ENABLE_PREV": "false", - "POWER_REDUNDANCY_MODE": "ps-redundant", - "PREMSO_PARENT_FABRIC": "", - "PTP_DOMAIN_ID": "", - "PTP_LB_ID": "", - "REPLICATION_MODE": "Multicast", - "ROUTER_ID_RANGE": "", - "ROUTE_MAP_SEQUENCE_NUMBER_RANGE": "1-65534", - "RP_COUNT": "2", - "RP_LB_ID": "254", - "RP_MODE": "asm", - "RR_COUNT": "2", - "SEED_SWITCH_CORE_INTERFACES": "", - "SERVICE_NETWORK_VLAN_RANGE": "3000-3199", - "SGT_ID_RANGE": "", - "SGT_NAME_PREFIX": "", - "SGT_PREPROVISION": "", - "SITE_ID": "65001", - "SLA_ID_RANGE": "10000-19999", - "SNMP_SERVER_HOST_TRAP": "true", - "SPINE_COUNT": "0", - "SPINE_SWITCH_CORE_INTERFACES": "", - "SSPINE_ADD_DEL_DEBUG_FLAG": "Disable", - "SSPINE_COUNT": "0", - "STATIC_UNDERLAY_IP_ALLOC": "false", - "STP_BRIDGE_PRIORITY": "", - "STP_ROOT_OPTION": "unmanaged", - "STP_VLAN_RANGE": "", - "STRICT_CC_MODE": "false", - "SUBINTERFACE_RANGE": "2-511", - "SUBNET_RANGE": "10.4.0.0/16", - "SUBNET_TARGET_MASK": "30", - "SYSLOG_SERVER_IP_LIST": "", - "SYSLOG_SERVER_VRF": "", - "SYSLOG_SEV": "", - "TCAM_ALLOCATION": "true", - "TOPDOWN_CONFIG_RM_TRACKING": "notstarted", - "UNDERLAY_IS_V6": "false", - "UNNUM_BOOTSTRAP_LB_ID": "", - "UNNUM_DHCP_END": "", - "UNNUM_DHCP_END_INTERNAL": "", - "UNNUM_DHCP_START": "", - "UNNUM_DHCP_START_INTERNAL": "", - "UPGRADE_FROM_VERSION": "", - "USE_LINK_LOCAL": "true", - "V6_SUBNET_RANGE": "", - "V6_SUBNET_TARGET_MASK": "126", - "VPC_AUTO_RECOVERY_TIME": "360", - "VPC_DELAY_RESTORE": "150", - "VPC_DELAY_RESTORE_TIME": "60", - "VPC_DOMAIN_ID_RANGE": "1-1000", - "VPC_ENABLE_IPv6_ND_SYNC": "true", - "VPC_PEER_KEEP_ALIVE_OPTION": "management", - "VPC_PEER_LINK_PO": "500", - "VPC_PEER_LINK_VLAN": "3600", - "VRF_LITE_AUTOCONFIG": "Manual", - "VRF_VLAN_RANGE": "2000-2299", - "abstract_anycast_rp": "anycast_rp", - "abstract_bgp": "base_bgp", - "abstract_bgp_neighbor": "evpn_bgp_rr_neighbor", - "abstract_bgp_rr": "evpn_bgp_rr", - "abstract_dhcp": "base_dhcp", - "abstract_extra_config_bootstrap": "extra_config_bootstrap_11_1", - "abstract_extra_config_leaf": "extra_config_leaf", - "abstract_extra_config_spine": "extra_config_spine", - "abstract_extra_config_tor": "extra_config_tor", - "abstract_feature_leaf": "base_feature_leaf_upg", - "abstract_feature_spine": "base_feature_spine_upg", - "abstract_isis": "base_isis_level2", - "abstract_isis_interface": "isis_interface", - "abstract_loopback_interface": "int_fabric_loopback_11_1", - "abstract_multicast": "base_multicast_11_1", - "abstract_ospf": "base_ospf", - "abstract_ospf_interface": "ospf_interface_11_1", - "abstract_pim_interface": "pim_interface", - "abstract_route_map": "route_map", - "abstract_routed_host": "int_routed_host", - "abstract_trunk_host": "int_trunk_host", - "abstract_vlan_interface": "int_fabric_vlan_11_1", - "abstract_vpc_domain": "base_vpc_domain_11_1", - "default_network": "Default_Network_Universal", - "default_pvlan_sec_network": "", - "default_vrf": "Default_VRF_Universal", - "enableRealTimeBackup": "", - "enableScheduledBackup": "", - "network_extension_template": "Default_Network_Extension_Universal", - "scheduledTime": "", - "temp_anycast_gateway": "anycast_gateway", - "temp_vpc_domain_mgmt": "vpc_domain_mgmt", - "temp_vpc_peer_link": "int_vpc_peer_link_po", - "vrf_extension_template": "Default_VRF_Extension_Universal" - }, - "operStatus": "HEALTHY", - "provisionMode": "DCNMTopDown", - "replicationMode": "Multicast", - "siteId": "65001", - "templateName": "Easy_Fabric", - "vrfExtensionTemplate": "Default_VRF_Extension_Universal", - "vrfTemplate": "Default_VRF_Universal" - } - ], - "MESSAGE": "OK", - "METHOD": "GET", - "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/rest/control/fabrics", - "RETURN_CODE": 200 - }, - "test_fabric_update_bulk_00035a": { - "DATA": [ - { - "asn": "65001", - "createdOn": 1711411093680, - "deviceType": "n9k", - "fabricId": "FABRIC-2", - "fabricName": "f1", - "fabricTechnology": "VXLANFabric", - "fabricTechnologyFriendly": "VXLAN EVPN", - "fabricType": "Switch_Fabric", - "fabricTypeFriendly": "Switch Fabric", - "id": 2, - "modifiedOn": 1711411096857, - "networkExtensionTemplate": "Default_Network_Extension_Universal", - "networkTemplate": "Default_Network_Universal", - "nvPairs": { - "AAA_REMOTE_IP_ENABLED": "false", - "AAA_SERVER_CONF": "", - "ACTIVE_MIGRATION": "false", - "ADVERTISE_PIP_BGP": "false", - "ADVERTISE_PIP_ON_BORDER": "true", - "AGENT_INTF": "eth0", - "AI_ML_QOS_POLICY": "AI_Fabric_QOS_400G", - "ALLOW_NXC": "true", - "ALLOW_NXC_PREV": "true", - "ANYCAST_BGW_ADVERTISE_PIP": "false", - "ANYCAST_GW_MAC": "2020.0000.00aa", - "ANYCAST_LB_ID": "", - "ANYCAST_RP_IP_RANGE": "10.254.254.0/24", - "ANYCAST_RP_IP_RANGE_INTERNAL": "", - "AUTO_SYMMETRIC_DEFAULT_VRF": "false", - "AUTO_SYMMETRIC_VRF_LITE": "false", - "AUTO_UNIQUE_VRF_LITE_IP_PREFIX": "false", - "AUTO_UNIQUE_VRF_LITE_IP_PREFIX_PREV": "false", - "AUTO_VRFLITE_IFC_DEFAULT_VRF": "false", - "BANNER": "", - "BFD_AUTH_ENABLE": "false", - "BFD_AUTH_KEY": "", - "BFD_AUTH_KEY_ID": "", - "BFD_ENABLE": "false", - "BFD_ENABLE_PREV": "", - "BFD_IBGP_ENABLE": "false", - "BFD_ISIS_ENABLE": "false", - "BFD_OSPF_ENABLE": "false", - "BFD_PIM_ENABLE": "false", - "BGP_AS": "65001", - "BGP_AS_PREV": "65001", - "BGP_AUTH_ENABLE": "false", - "BGP_AUTH_KEY": "", - "BGP_AUTH_KEY_TYPE": "3", - "BGP_LB_ID": "0", - "BOOTSTRAP_CONF": "", - "BOOTSTRAP_ENABLE": "false", - "BOOTSTRAP_ENABLE_PREV": "false", - "BOOTSTRAP_MULTISUBNET": "#Scope_Start_IP, Scope_End_IP, Scope_Default_Gateway, Scope_Subnet_Prefix", - "BOOTSTRAP_MULTISUBNET_INTERNAL": "", - "BRFIELD_DEBUG_FLAG": "Disable", - "BROWNFIELD_NETWORK_NAME_FORMAT": "Auto_Net_VNI$$VNI$$_VLAN$$VLAN_ID$$", - "BROWNFIELD_SKIP_OVERLAY_NETWORK_ATTACHMENTS": "false", - "CDP_ENABLE": "false", - "COPP_POLICY": "strict", - "DCI_SUBNET_RANGE": "10.33.0.0/16", - "DCI_SUBNET_TARGET_MASK": "30", - "DEAFULT_QUEUING_POLICY_CLOUDSCALE": "queuing_policy_default_8q_cloudscale", - "DEAFULT_QUEUING_POLICY_OTHER": "queuing_policy_default_other", - "DEAFULT_QUEUING_POLICY_R_SERIES": "queuing_policy_default_r_series", - "DEFAULT_VRF_REDIS_BGP_RMAP": "", - "DEPLOYMENT_FREEZE": "false", - "DHCP_ENABLE": "false", - "DHCP_END": "", - "DHCP_END_INTERNAL": "", - "DHCP_IPV6_ENABLE": "", - "DHCP_IPV6_ENABLE_INTERNAL": "", - "DHCP_START": "", - "DHCP_START_INTERNAL": "", - "DNS_SERVER_IP_LIST": "", - "DNS_SERVER_VRF": "", - "DOMAIN_NAME_INTERNAL": "", - "ENABLE_AAA": "false", - "ENABLE_AGENT": "false", - "ENABLE_AI_ML_QOS_POLICY": "false", - "ENABLE_AI_ML_QOS_POLICY_FLAP": "false", - "ENABLE_DEFAULT_QUEUING_POLICY": "false", - "ENABLE_EVPN": "true", - "ENABLE_FABRIC_VPC_DOMAIN_ID": "false", - "ENABLE_FABRIC_VPC_DOMAIN_ID_PREV": "false", - "ENABLE_L3VNI_NO_VLAN": "false", - "ENABLE_MACSEC": "false", - "ENABLE_NETFLOW": "false", - "ENABLE_NETFLOW_PREV": "false", - "ENABLE_NGOAM": "true", - "ENABLE_NXAPI": "true", - "ENABLE_NXAPI_HTTP": "true", - "ENABLE_PBR": "false", - "ENABLE_PVLAN": "false", - "ENABLE_PVLAN_PREV": "false", - "ENABLE_SGT": "false", - "ENABLE_SGT_PREV": "false", - "ENABLE_TENANT_DHCP": "true", - "ENABLE_TRM": "false", - "ENABLE_VPC_PEER_LINK_NATIVE_VLAN": "false", - "ESR_OPTION": "PBR", - "EXTRA_CONF_INTRA_LINKS": "", - "EXTRA_CONF_LEAF": "", - "EXTRA_CONF_SPINE": "", - "EXTRA_CONF_TOR": "", - "EXT_FABRIC_TYPE": "", - "FABRIC_INTERFACE_TYPE": "p2p", - "FABRIC_MTU": "9216", - "FABRIC_MTU_PREV": "9216", - "FABRIC_NAME": "f1", - "FABRIC_TYPE": "Switch_Fabric", - "FABRIC_VPC_DOMAIN_ID": "", - "FABRIC_VPC_DOMAIN_ID_PREV": "", - "FABRIC_VPC_QOS": "false", - "FABRIC_VPC_QOS_POLICY_NAME": "spine_qos_for_fabric_vpc_peering", - "FEATURE_PTP": "false", - "FEATURE_PTP_INTERNAL": "false", - "FF": "Easy_Fabric", - "GRFIELD_DEBUG_FLAG": "Disable", - "HD_TIME": "180", - "HOST_INTF_ADMIN_STATE": "true", - "IBGP_PEER_TEMPLATE": "", - "IBGP_PEER_TEMPLATE_LEAF": "", - "INBAND_DHCP_SERVERS": "", - "INBAND_MGMT": "false", - "INBAND_MGMT_PREV": "false", - "ISIS_AREA_NUM": "0001", - "ISIS_AREA_NUM_PREV": "", - "ISIS_AUTH_ENABLE": "false", - "ISIS_AUTH_KEY": "", - "ISIS_AUTH_KEYCHAIN_KEY_ID": "", - "ISIS_AUTH_KEYCHAIN_NAME": "", - "ISIS_LEVEL": "level-2", - "ISIS_OVERLOAD_ELAPSE_TIME": "", - "ISIS_OVERLOAD_ENABLE": "", - "ISIS_P2P_ENABLE": "", - "L2_HOST_INTF_MTU": "9216", - "L2_HOST_INTF_MTU_PREV": "9216", - "L2_SEGMENT_ID_RANGE": "30000-49000", - "L3VNI_MCAST_GROUP": "", - "L3_PARTITION_ID_RANGE": "50000-59000", - "LINK_STATE_ROUTING": "ospf", - "LINK_STATE_ROUTING_TAG": "UNDERLAY", - "LINK_STATE_ROUTING_TAG_PREV": "UNDERLAY", - "LOOPBACK0_IPV6_RANGE": "", - "LOOPBACK0_IP_RANGE": "10.2.0.0/22", - "LOOPBACK1_IPV6_RANGE": "", - "LOOPBACK1_IP_RANGE": "10.3.0.0/22", - "MACSEC_ALGORITHM": "", - "MACSEC_CIPHER_SUITE": "", - "MACSEC_FALLBACK_ALGORITHM": "", - "MACSEC_FALLBACK_KEY_STRING": "", - "MACSEC_KEY_STRING": "", - "MACSEC_REPORT_TIMER": "", - "MGMT_GW": "", - "MGMT_GW_INTERNAL": "", - "MGMT_PREFIX": "", - "MGMT_PREFIX_INTERNAL": "", - "MGMT_V6PREFIX": "", - "MGMT_V6PREFIX_INTERNAL": "", - "MPLS_HANDOFF": "false", - "MPLS_ISIS_AREA_NUM": "0001", - "MPLS_ISIS_AREA_NUM_PREV": "", - "MPLS_LB_ID": "", - "MPLS_LOOPBACK_IP_RANGE": "", - "MSO_CONNECTIVITY_DEPLOYED": "", - "MSO_CONTROLER_ID": "", - "MSO_SITE_GROUP_NAME": "", - "MSO_SITE_ID": "", - "MST_INSTANCE_RANGE": "", - "MULTICAST_GROUP_SUBNET": "239.1.1.0/25", - "NETFLOW_EXPORTER_LIST": "", - "NETFLOW_MONITOR_LIST": "", - "NETFLOW_RECORD_LIST": "", - "NETWORK_VLAN_RANGE": "2300-2999", - "NTP_SERVER_IP_LIST": "", - "NTP_SERVER_VRF": "", - "NVE_LB_ID": "1", - "NXAPI_HTTPS_PORT": "443", - "NXAPI_HTTP_PORT": "80", - "NXC_DEST_VRF": "", - "NXC_PROXY_PORT": "8080", - "NXC_PROXY_SERVER": "", - "NXC_SRC_INTF": "", - "OBJECT_TRACKING_NUMBER_RANGE": "100-299", - "OSPF_AREA_ID": "0.0.0.0", - "OSPF_AUTH_ENABLE": "false", - "OSPF_AUTH_KEY": "", - "OSPF_AUTH_KEY_ID": "", - "OVERLAY_MODE": "cli", - "OVERLAY_MODE_PREV": "cli", - "OVERWRITE_GLOBAL_NXC": "false", - "PER_VRF_LOOPBACK_AUTO_PROVISION": "false", - "PER_VRF_LOOPBACK_AUTO_PROVISION_PREV": "false", - "PER_VRF_LOOPBACK_AUTO_PROVISION_V6": "false", - "PER_VRF_LOOPBACK_AUTO_PROVISION_V6_PREV": "false", - "PER_VRF_LOOPBACK_IP_RANGE": "", - "PER_VRF_LOOPBACK_IP_RANGE_V6": "", - "PHANTOM_RP_LB_ID1": "", - "PHANTOM_RP_LB_ID2": "", - "PHANTOM_RP_LB_ID3": "", - "PHANTOM_RP_LB_ID4": "", - "PIM_HELLO_AUTH_ENABLE": "false", - "PIM_HELLO_AUTH_KEY": "", - "PM_ENABLE": "false", - "PM_ENABLE_PREV": "false", - "POWER_REDUNDANCY_MODE": "ps-redundant", - "PREMSO_PARENT_FABRIC": "", - "PTP_DOMAIN_ID": "", - "PTP_LB_ID": "", - "REPLICATION_MODE": "Multicast", - "ROUTER_ID_RANGE": "", - "ROUTE_MAP_SEQUENCE_NUMBER_RANGE": "1-65534", - "RP_COUNT": "2", - "RP_LB_ID": "254", - "RP_MODE": "asm", - "RR_COUNT": "2", - "SEED_SWITCH_CORE_INTERFACES": "", - "SERVICE_NETWORK_VLAN_RANGE": "3000-3199", - "SGT_ID_RANGE": "", - "SGT_NAME_PREFIX": "", - "SGT_PREPROVISION": "", - "SITE_ID": "65001", - "SLA_ID_RANGE": "10000-19999", - "SNMP_SERVER_HOST_TRAP": "true", - "SPINE_COUNT": "0", - "SPINE_SWITCH_CORE_INTERFACES": "", - "SSPINE_ADD_DEL_DEBUG_FLAG": "Disable", - "SSPINE_COUNT": "0", - "STATIC_UNDERLAY_IP_ALLOC": "false", - "STP_BRIDGE_PRIORITY": "", - "STP_ROOT_OPTION": "unmanaged", - "STP_VLAN_RANGE": "", - "STRICT_CC_MODE": "false", - "SUBINTERFACE_RANGE": "2-511", - "SUBNET_RANGE": "10.4.0.0/16", - "SUBNET_TARGET_MASK": "30", - "SYSLOG_SERVER_IP_LIST": "", - "SYSLOG_SERVER_VRF": "", - "SYSLOG_SEV": "", - "TCAM_ALLOCATION": "true", - "TOPDOWN_CONFIG_RM_TRACKING": "notstarted", - "UNDERLAY_IS_V6": "false", - "UNNUM_BOOTSTRAP_LB_ID": "", - "UNNUM_DHCP_END": "", - "UNNUM_DHCP_END_INTERNAL": "", - "UNNUM_DHCP_START": "", - "UNNUM_DHCP_START_INTERNAL": "", - "UPGRADE_FROM_VERSION": "", - "USE_LINK_LOCAL": "true", - "V6_SUBNET_RANGE": "", - "V6_SUBNET_TARGET_MASK": "126", - "VPC_AUTO_RECOVERY_TIME": "360", - "VPC_DELAY_RESTORE": "150", - "VPC_DELAY_RESTORE_TIME": "60", - "VPC_DOMAIN_ID_RANGE": "1-1000", - "VPC_ENABLE_IPv6_ND_SYNC": "true", - "VPC_PEER_KEEP_ALIVE_OPTION": "management", - "VPC_PEER_LINK_PO": "500", - "VPC_PEER_LINK_VLAN": "3600", - "VRF_LITE_AUTOCONFIG": "Manual", - "VRF_VLAN_RANGE": "2000-2299", - "abstract_anycast_rp": "anycast_rp", - "abstract_bgp": "base_bgp", - "abstract_bgp_neighbor": "evpn_bgp_rr_neighbor", - "abstract_bgp_rr": "evpn_bgp_rr", - "abstract_dhcp": "base_dhcp", - "abstract_extra_config_bootstrap": "extra_config_bootstrap_11_1", - "abstract_extra_config_leaf": "extra_config_leaf", - "abstract_extra_config_spine": "extra_config_spine", - "abstract_extra_config_tor": "extra_config_tor", - "abstract_feature_leaf": "base_feature_leaf_upg", - "abstract_feature_spine": "base_feature_spine_upg", - "abstract_isis": "base_isis_level2", - "abstract_isis_interface": "isis_interface", - "abstract_loopback_interface": "int_fabric_loopback_11_1", - "abstract_multicast": "base_multicast_11_1", - "abstract_ospf": "base_ospf", - "abstract_ospf_interface": "ospf_interface_11_1", - "abstract_pim_interface": "pim_interface", - "abstract_route_map": "route_map", - "abstract_routed_host": "int_routed_host", - "abstract_trunk_host": "int_trunk_host", - "abstract_vlan_interface": "int_fabric_vlan_11_1", - "abstract_vpc_domain": "base_vpc_domain_11_1", - "default_network": "Default_Network_Universal", - "default_pvlan_sec_network": "", - "default_vrf": "Default_VRF_Universal", - "enableRealTimeBackup": "", - "enableScheduledBackup": "", - "network_extension_template": "Default_Network_Extension_Universal", - "scheduledTime": "", - "temp_anycast_gateway": "anycast_gateway", - "temp_vpc_domain_mgmt": "vpc_domain_mgmt", - "temp_vpc_peer_link": "int_vpc_peer_link_po", - "vrf_extension_template": "Default_VRF_Extension_Universal" - }, - "operStatus": "HEALTHY", - "provisionMode": "DCNMTopDown", - "replicationMode": "Multicast", - "siteId": "65001", - "templateName": "Easy_Fabric", - "vrfExtensionTemplate": "Default_VRF_Extension_Universal", - "vrfTemplate": "Default_VRF_Universal" - } - ], - "MESSAGE": "OK", - "METHOD": "GET", - "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/rest/control/fabrics", - "RETURN_CODE": 200 - }, - "test_fabric_update_bulk_00036a": { - "DATA": [ - { - "asn": "65001", - "createdOn": 1711411093680, - "deviceType": "n9k", - "fabricId": "FABRIC-2", - "fabricName": "f1", - "fabricTechnology": "VXLANFabric", - "fabricTechnologyFriendly": "VXLAN EVPN", - "fabricType": "Switch_Fabric", - "fabricTypeFriendly": "Switch Fabric", - "id": 2, - "modifiedOn": 1711411096857, - "networkExtensionTemplate": "Default_Network_Extension_Universal", - "networkTemplate": "Default_Network_Universal", - "nvPairs": { - "AAA_REMOTE_IP_ENABLED": "false", - "AAA_SERVER_CONF": "", - "ACTIVE_MIGRATION": "false", - "ADVERTISE_PIP_BGP": "false", - "ADVERTISE_PIP_ON_BORDER": "true", - "AGENT_INTF": "eth0", - "AI_ML_QOS_POLICY": "AI_Fabric_QOS_400G", - "ALLOW_NXC": "true", - "ALLOW_NXC_PREV": "true", - "ANYCAST_BGW_ADVERTISE_PIP": "false", - "ANYCAST_GW_MAC": "2020.0000.00aa", - "ANYCAST_LB_ID": "", - "ANYCAST_RP_IP_RANGE": "10.254.254.0/24", - "ANYCAST_RP_IP_RANGE_INTERNAL": "", - "AUTO_SYMMETRIC_DEFAULT_VRF": "false", - "AUTO_SYMMETRIC_VRF_LITE": "false", - "AUTO_UNIQUE_VRF_LITE_IP_PREFIX": "false", - "AUTO_UNIQUE_VRF_LITE_IP_PREFIX_PREV": "false", - "AUTO_VRFLITE_IFC_DEFAULT_VRF": "false", - "BANNER": "", - "BFD_AUTH_ENABLE": "false", - "BFD_AUTH_KEY": "", - "BFD_AUTH_KEY_ID": "", - "BFD_ENABLE": "false", - "BFD_ENABLE_PREV": "", - "BFD_IBGP_ENABLE": "false", - "BFD_ISIS_ENABLE": "false", - "BFD_OSPF_ENABLE": "false", - "BFD_PIM_ENABLE": "false", - "BGP_AS": "65001", - "BGP_AS_PREV": "65001", - "BGP_AUTH_ENABLE": "false", - "BGP_AUTH_KEY": "", - "BGP_AUTH_KEY_TYPE": "3", - "BGP_LB_ID": "0", - "BOOTSTRAP_CONF": "", - "BOOTSTRAP_ENABLE": "false", - "BOOTSTRAP_ENABLE_PREV": "false", - "BOOTSTRAP_MULTISUBNET": "#Scope_Start_IP, Scope_End_IP, Scope_Default_Gateway, Scope_Subnet_Prefix", - "BOOTSTRAP_MULTISUBNET_INTERNAL": "", - "BRFIELD_DEBUG_FLAG": "Disable", - "BROWNFIELD_NETWORK_NAME_FORMAT": "Auto_Net_VNI$$VNI$$_VLAN$$VLAN_ID$$", - "BROWNFIELD_SKIP_OVERLAY_NETWORK_ATTACHMENTS": "false", - "CDP_ENABLE": "false", - "COPP_POLICY": "strict", - "DCI_SUBNET_RANGE": "10.33.0.0/16", - "DCI_SUBNET_TARGET_MASK": "30", - "DEAFULT_QUEUING_POLICY_CLOUDSCALE": "queuing_policy_default_8q_cloudscale", - "DEAFULT_QUEUING_POLICY_OTHER": "queuing_policy_default_other", - "DEAFULT_QUEUING_POLICY_R_SERIES": "queuing_policy_default_r_series", - "DEFAULT_VRF_REDIS_BGP_RMAP": "", - "DEPLOYMENT_FREEZE": "false", - "DHCP_ENABLE": "false", - "DHCP_END": "", - "DHCP_END_INTERNAL": "", - "DHCP_IPV6_ENABLE": "", - "DHCP_IPV6_ENABLE_INTERNAL": "", - "DHCP_START": "", - "DHCP_START_INTERNAL": "", - "DNS_SERVER_IP_LIST": "", - "DNS_SERVER_VRF": "", - "DOMAIN_NAME_INTERNAL": "", - "ENABLE_AAA": "false", - "ENABLE_AGENT": "false", - "ENABLE_AI_ML_QOS_POLICY": "false", - "ENABLE_AI_ML_QOS_POLICY_FLAP": "false", - "ENABLE_DEFAULT_QUEUING_POLICY": "false", - "ENABLE_EVPN": "true", - "ENABLE_FABRIC_VPC_DOMAIN_ID": "false", - "ENABLE_FABRIC_VPC_DOMAIN_ID_PREV": "false", - "ENABLE_L3VNI_NO_VLAN": "false", - "ENABLE_MACSEC": "false", - "ENABLE_NETFLOW": "false", - "ENABLE_NETFLOW_PREV": "false", - "ENABLE_NGOAM": "true", - "ENABLE_NXAPI": "true", - "ENABLE_NXAPI_HTTP": "true", - "ENABLE_PBR": "false", - "ENABLE_PVLAN": "false", - "ENABLE_PVLAN_PREV": "false", - "ENABLE_SGT": "false", - "ENABLE_SGT_PREV": "false", - "ENABLE_TENANT_DHCP": "true", - "ENABLE_TRM": "false", - "ENABLE_VPC_PEER_LINK_NATIVE_VLAN": "false", - "ESR_OPTION": "PBR", - "EXTRA_CONF_INTRA_LINKS": "", - "EXTRA_CONF_LEAF": "", - "EXTRA_CONF_SPINE": "", - "EXTRA_CONF_TOR": "", - "EXT_FABRIC_TYPE": "", - "FABRIC_INTERFACE_TYPE": "p2p", - "FABRIC_MTU": "9216", - "FABRIC_MTU_PREV": "9216", - "FABRIC_NAME": "f1", - "FABRIC_TYPE": "Switch_Fabric", - "FABRIC_VPC_DOMAIN_ID": "", - "FABRIC_VPC_DOMAIN_ID_PREV": "", - "FABRIC_VPC_QOS": "false", - "FABRIC_VPC_QOS_POLICY_NAME": "spine_qos_for_fabric_vpc_peering", - "FEATURE_PTP": "false", - "FEATURE_PTP_INTERNAL": "false", - "FF": "Easy_Fabric", - "GRFIELD_DEBUG_FLAG": "Disable", - "HD_TIME": "180", - "HOST_INTF_ADMIN_STATE": "true", - "IBGP_PEER_TEMPLATE": "", - "IBGP_PEER_TEMPLATE_LEAF": "", - "INBAND_DHCP_SERVERS": "", - "INBAND_MGMT": "false", - "INBAND_MGMT_PREV": "false", - "ISIS_AREA_NUM": "0001", - "ISIS_AREA_NUM_PREV": "", - "ISIS_AUTH_ENABLE": "false", - "ISIS_AUTH_KEY": "", - "ISIS_AUTH_KEYCHAIN_KEY_ID": "", - "ISIS_AUTH_KEYCHAIN_NAME": "", - "ISIS_LEVEL": "level-2", - "ISIS_OVERLOAD_ELAPSE_TIME": "", - "ISIS_OVERLOAD_ENABLE": "", - "ISIS_P2P_ENABLE": "", - "L2_HOST_INTF_MTU": "9216", - "L2_HOST_INTF_MTU_PREV": "9216", - "L2_SEGMENT_ID_RANGE": "30000-49000", - "L3VNI_MCAST_GROUP": "", - "L3_PARTITION_ID_RANGE": "50000-59000", - "LINK_STATE_ROUTING": "ospf", - "LINK_STATE_ROUTING_TAG": "UNDERLAY", - "LINK_STATE_ROUTING_TAG_PREV": "UNDERLAY", - "LOOPBACK0_IPV6_RANGE": "", - "LOOPBACK0_IP_RANGE": "10.2.0.0/22", - "LOOPBACK1_IPV6_RANGE": "", - "LOOPBACK1_IP_RANGE": "10.3.0.0/22", - "MACSEC_ALGORITHM": "", - "MACSEC_CIPHER_SUITE": "", - "MACSEC_FALLBACK_ALGORITHM": "", - "MACSEC_FALLBACK_KEY_STRING": "", - "MACSEC_KEY_STRING": "", - "MACSEC_REPORT_TIMER": "", - "MGMT_GW": "", - "MGMT_GW_INTERNAL": "", - "MGMT_PREFIX": "", - "MGMT_PREFIX_INTERNAL": "", - "MGMT_V6PREFIX": "", - "MGMT_V6PREFIX_INTERNAL": "", - "MPLS_HANDOFF": "false", - "MPLS_ISIS_AREA_NUM": "0001", - "MPLS_ISIS_AREA_NUM_PREV": "", - "MPLS_LB_ID": "", - "MPLS_LOOPBACK_IP_RANGE": "", - "MSO_CONNECTIVITY_DEPLOYED": "", - "MSO_CONTROLER_ID": "", - "MSO_SITE_GROUP_NAME": "", - "MSO_SITE_ID": "", - "MST_INSTANCE_RANGE": "", - "MULTICAST_GROUP_SUBNET": "239.1.1.0/25", - "NETFLOW_EXPORTER_LIST": "", - "NETFLOW_MONITOR_LIST": "", - "NETFLOW_RECORD_LIST": "", - "NETWORK_VLAN_RANGE": "2300-2999", - "NTP_SERVER_IP_LIST": "", - "NTP_SERVER_VRF": "", - "NVE_LB_ID": "1", - "NXAPI_HTTPS_PORT": "443", - "NXAPI_HTTP_PORT": "80", - "NXC_DEST_VRF": "", - "NXC_PROXY_PORT": "8080", - "NXC_PROXY_SERVER": "", - "NXC_SRC_INTF": "", - "OBJECT_TRACKING_NUMBER_RANGE": "100-299", - "OSPF_AREA_ID": "0.0.0.0", - "OSPF_AUTH_ENABLE": "false", - "OSPF_AUTH_KEY": "", - "OSPF_AUTH_KEY_ID": "", - "OVERLAY_MODE": "cli", - "OVERLAY_MODE_PREV": "cli", - "OVERWRITE_GLOBAL_NXC": "false", - "PER_VRF_LOOPBACK_AUTO_PROVISION": "false", - "PER_VRF_LOOPBACK_AUTO_PROVISION_PREV": "false", - "PER_VRF_LOOPBACK_AUTO_PROVISION_V6": "false", - "PER_VRF_LOOPBACK_AUTO_PROVISION_V6_PREV": "false", - "PER_VRF_LOOPBACK_IP_RANGE": "", - "PER_VRF_LOOPBACK_IP_RANGE_V6": "", - "PHANTOM_RP_LB_ID1": "", - "PHANTOM_RP_LB_ID2": "", - "PHANTOM_RP_LB_ID3": "", - "PHANTOM_RP_LB_ID4": "", - "PIM_HELLO_AUTH_ENABLE": "false", - "PIM_HELLO_AUTH_KEY": "", - "PM_ENABLE": "false", - "PM_ENABLE_PREV": "false", - "POWER_REDUNDANCY_MODE": "ps-redundant", - "PREMSO_PARENT_FABRIC": "", - "PTP_DOMAIN_ID": "", - "PTP_LB_ID": "", - "REPLICATION_MODE": "Multicast", - "ROUTER_ID_RANGE": "", - "ROUTE_MAP_SEQUENCE_NUMBER_RANGE": "1-65534", - "RP_COUNT": "2", - "RP_LB_ID": "254", - "RP_MODE": "asm", - "RR_COUNT": "2", - "SEED_SWITCH_CORE_INTERFACES": "", - "SERVICE_NETWORK_VLAN_RANGE": "3000-3199", - "SGT_ID_RANGE": "", - "SGT_NAME_PREFIX": "", - "SGT_PREPROVISION": "", - "SITE_ID": "65001", - "SLA_ID_RANGE": "10000-19999", - "SNMP_SERVER_HOST_TRAP": "true", - "SPINE_COUNT": "0", - "SPINE_SWITCH_CORE_INTERFACES": "", - "SSPINE_ADD_DEL_DEBUG_FLAG": "Disable", - "SSPINE_COUNT": "0", - "STATIC_UNDERLAY_IP_ALLOC": "false", - "STP_BRIDGE_PRIORITY": "", - "STP_ROOT_OPTION": "unmanaged", - "STP_VLAN_RANGE": "", - "STRICT_CC_MODE": "false", - "SUBINTERFACE_RANGE": "2-511", - "SUBNET_RANGE": "10.4.0.0/16", - "SUBNET_TARGET_MASK": "30", - "SYSLOG_SERVER_IP_LIST": "", - "SYSLOG_SERVER_VRF": "", - "SYSLOG_SEV": "", - "TCAM_ALLOCATION": "true", - "TOPDOWN_CONFIG_RM_TRACKING": "notstarted", - "UNDERLAY_IS_V6": "false", - "UNNUM_BOOTSTRAP_LB_ID": "", - "UNNUM_DHCP_END": "", - "UNNUM_DHCP_END_INTERNAL": "", - "UNNUM_DHCP_START": "", - "UNNUM_DHCP_START_INTERNAL": "", - "UPGRADE_FROM_VERSION": "", - "USE_LINK_LOCAL": "true", - "V6_SUBNET_RANGE": "", - "V6_SUBNET_TARGET_MASK": "126", - "VPC_AUTO_RECOVERY_TIME": "360", - "VPC_DELAY_RESTORE": "150", - "VPC_DELAY_RESTORE_TIME": "60", - "VPC_DOMAIN_ID_RANGE": "1-1000", - "VPC_ENABLE_IPv6_ND_SYNC": "true", - "VPC_PEER_KEEP_ALIVE_OPTION": "management", - "VPC_PEER_LINK_PO": "500", - "VPC_PEER_LINK_VLAN": "3600", - "VRF_LITE_AUTOCONFIG": "Manual", - "VRF_VLAN_RANGE": "2000-2299", - "abstract_anycast_rp": "anycast_rp", - "abstract_bgp": "base_bgp", - "abstract_bgp_neighbor": "evpn_bgp_rr_neighbor", - "abstract_bgp_rr": "evpn_bgp_rr", - "abstract_dhcp": "base_dhcp", - "abstract_extra_config_bootstrap": "extra_config_bootstrap_11_1", - "abstract_extra_config_leaf": "extra_config_leaf", - "abstract_extra_config_spine": "extra_config_spine", - "abstract_extra_config_tor": "extra_config_tor", - "abstract_feature_leaf": "base_feature_leaf_upg", - "abstract_feature_spine": "base_feature_spine_upg", - "abstract_isis": "base_isis_level2", - "abstract_isis_interface": "isis_interface", - "abstract_loopback_interface": "int_fabric_loopback_11_1", - "abstract_multicast": "base_multicast_11_1", - "abstract_ospf": "base_ospf", - "abstract_ospf_interface": "ospf_interface_11_1", - "abstract_pim_interface": "pim_interface", - "abstract_route_map": "route_map", - "abstract_routed_host": "int_routed_host", - "abstract_trunk_host": "int_trunk_host", - "abstract_vlan_interface": "int_fabric_vlan_11_1", - "abstract_vpc_domain": "base_vpc_domain_11_1", - "default_network": "Default_Network_Universal", - "default_pvlan_sec_network": "", - "default_vrf": "Default_VRF_Universal", - "enableRealTimeBackup": "", - "enableScheduledBackup": "", - "network_extension_template": "Default_Network_Extension_Universal", - "scheduledTime": "", - "temp_anycast_gateway": "anycast_gateway", - "temp_vpc_domain_mgmt": "vpc_domain_mgmt", - "temp_vpc_peer_link": "int_vpc_peer_link_po", - "vrf_extension_template": "Default_VRF_Extension_Universal" - }, - "operStatus": "HEALTHY", - "provisionMode": "DCNMTopDown", - "replicationMode": "Multicast", - "siteId": "65001", - "templateName": "Easy_Fabric", - "vrfExtensionTemplate": "Default_VRF_Extension_Universal", - "vrfTemplate": "Default_VRF_Universal" - } - ], - "MESSAGE": "OK", - "METHOD": "GET", - "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/rest/control/fabrics", - "RETURN_CODE": 200 - }, - "test_fabric_update_bulk_00040a": { - "DATA": [ - { - "asn": "65001", - "createdOn": 1711411093680, - "deviceType": "n9k", - "fabricId": "FABRIC-2", - "fabricName": "f1", - "fabricTechnology": "VXLANFabric", - "fabricTechnologyFriendly": "VXLAN EVPN", - "fabricType": "Switch_Fabric", - "fabricTypeFriendly": "Switch Fabric", - "id": 2, - "modifiedOn": 1711411096857, - "networkExtensionTemplate": "Default_Network_Extension_Universal", - "networkTemplate": "Default_Network_Universal", - "nvPairs": { - "AAA_REMOTE_IP_ENABLED": "false", - "AAA_SERVER_CONF": "", - "ACTIVE_MIGRATION": "false", - "ADVERTISE_PIP_BGP": "false", - "ADVERTISE_PIP_ON_BORDER": "true", - "AGENT_INTF": "eth0", - "AI_ML_QOS_POLICY": "AI_Fabric_QOS_400G", - "ALLOW_NXC": "true", - "ALLOW_NXC_PREV": "true", - "ANYCAST_BGW_ADVERTISE_PIP": "false", - "ANYCAST_GW_MAC": "2020.0000.00aa", - "ANYCAST_LB_ID": "", - "ANYCAST_RP_IP_RANGE": "10.254.254.0/24", - "ANYCAST_RP_IP_RANGE_INTERNAL": "", - "AUTO_SYMMETRIC_DEFAULT_VRF": "false", - "AUTO_SYMMETRIC_VRF_LITE": "false", - "AUTO_UNIQUE_VRF_LITE_IP_PREFIX": "false", - "AUTO_UNIQUE_VRF_LITE_IP_PREFIX_PREV": "false", - "AUTO_VRFLITE_IFC_DEFAULT_VRF": "false", - "BANNER": "", - "BFD_AUTH_ENABLE": "false", - "BFD_AUTH_KEY": "", - "BFD_AUTH_KEY_ID": "", - "BFD_ENABLE": "false", - "BFD_ENABLE_PREV": "", - "BFD_IBGP_ENABLE": "false", - "BFD_ISIS_ENABLE": "false", - "BFD_OSPF_ENABLE": "false", - "BFD_PIM_ENABLE": "false", - "BGP_AS": "65002", - "BGP_AS_PREV": "65002", - "BGP_AUTH_ENABLE": "false", - "BGP_AUTH_KEY": "", - "BGP_AUTH_KEY_TYPE": "3", - "BGP_LB_ID": "0", - "BOOTSTRAP_CONF": "", - "BOOTSTRAP_ENABLE": "false", - "BOOTSTRAP_ENABLE_PREV": "false", - "BOOTSTRAP_MULTISUBNET": "#Scope_Start_IP, Scope_End_IP, Scope_Default_Gateway, Scope_Subnet_Prefix", - "BOOTSTRAP_MULTISUBNET_INTERNAL": "", - "BRFIELD_DEBUG_FLAG": "Disable", - "BROWNFIELD_NETWORK_NAME_FORMAT": "Auto_Net_VNI$$VNI$$_VLAN$$VLAN_ID$$", - "BROWNFIELD_SKIP_OVERLAY_NETWORK_ATTACHMENTS": "false", - "CDP_ENABLE": "false", - "COPP_POLICY": "strict", - "DCI_SUBNET_RANGE": "10.33.0.0/16", - "DCI_SUBNET_TARGET_MASK": "30", - "DEAFULT_QUEUING_POLICY_CLOUDSCALE": "queuing_policy_default_8q_cloudscale", - "DEAFULT_QUEUING_POLICY_OTHER": "queuing_policy_default_other", - "DEAFULT_QUEUING_POLICY_R_SERIES": "queuing_policy_default_r_series", - "DEFAULT_VRF_REDIS_BGP_RMAP": "", - "DEPLOYMENT_FREEZE": "false", - "DHCP_ENABLE": "false", - "DHCP_END": "", - "DHCP_END_INTERNAL": "", - "DHCP_IPV6_ENABLE": "", - "DHCP_IPV6_ENABLE_INTERNAL": "", - "DHCP_START": "", - "DHCP_START_INTERNAL": "", - "DNS_SERVER_IP_LIST": "", - "DNS_SERVER_VRF": "", - "DOMAIN_NAME_INTERNAL": "", - "ENABLE_AAA": "false", - "ENABLE_AGENT": "false", - "ENABLE_AI_ML_QOS_POLICY": "false", - "ENABLE_AI_ML_QOS_POLICY_FLAP": "false", - "ENABLE_DEFAULT_QUEUING_POLICY": "false", - "ENABLE_EVPN": "true", - "ENABLE_FABRIC_VPC_DOMAIN_ID": "false", - "ENABLE_FABRIC_VPC_DOMAIN_ID_PREV": "false", - "ENABLE_L3VNI_NO_VLAN": "false", - "ENABLE_MACSEC": "false", - "ENABLE_NETFLOW": "false", - "ENABLE_NETFLOW_PREV": "false", - "ENABLE_NGOAM": "true", - "ENABLE_NXAPI": "true", - "ENABLE_NXAPI_HTTP": "true", - "ENABLE_PBR": "false", - "ENABLE_PVLAN": "false", - "ENABLE_PVLAN_PREV": "false", - "ENABLE_SGT": "false", - "ENABLE_SGT_PREV": "false", - "ENABLE_TENANT_DHCP": "true", - "ENABLE_TRM": "false", - "ENABLE_VPC_PEER_LINK_NATIVE_VLAN": "false", - "ESR_OPTION": "PBR", - "EXTRA_CONF_INTRA_LINKS": "", - "EXTRA_CONF_LEAF": "", - "EXTRA_CONF_SPINE": "", - "EXTRA_CONF_TOR": "", - "EXT_FABRIC_TYPE": "", - "FABRIC_INTERFACE_TYPE": "p2p", - "FABRIC_MTU": "9216", - "FABRIC_MTU_PREV": "9216", - "FABRIC_NAME": "f1", - "FABRIC_TYPE": "Switch_Fabric", - "FABRIC_VPC_DOMAIN_ID": "", - "FABRIC_VPC_DOMAIN_ID_PREV": "", - "FABRIC_VPC_QOS": "false", - "FABRIC_VPC_QOS_POLICY_NAME": "spine_qos_for_fabric_vpc_peering", - "FEATURE_PTP": "false", - "FEATURE_PTP_INTERNAL": "false", - "FF": "Easy_Fabric", - "GRFIELD_DEBUG_FLAG": "Disable", - "HD_TIME": "180", - "HOST_INTF_ADMIN_STATE": "true", - "IBGP_PEER_TEMPLATE": "", - "IBGP_PEER_TEMPLATE_LEAF": "", - "INBAND_DHCP_SERVERS": "", - "INBAND_MGMT": "false", - "INBAND_MGMT_PREV": "false", - "ISIS_AREA_NUM": "0001", - "ISIS_AREA_NUM_PREV": "", - "ISIS_AUTH_ENABLE": "false", - "ISIS_AUTH_KEY": "", - "ISIS_AUTH_KEYCHAIN_KEY_ID": "", - "ISIS_AUTH_KEYCHAIN_NAME": "", - "ISIS_LEVEL": "level-2", - "ISIS_OVERLOAD_ELAPSE_TIME": "", - "ISIS_OVERLOAD_ENABLE": "", - "ISIS_P2P_ENABLE": "", - "L2_HOST_INTF_MTU": "9216", - "L2_HOST_INTF_MTU_PREV": "9216", - "L2_SEGMENT_ID_RANGE": "30000-49000", - "L3VNI_MCAST_GROUP": "", - "L3_PARTITION_ID_RANGE": "50000-59000", - "LINK_STATE_ROUTING": "ospf", - "LINK_STATE_ROUTING_TAG": "UNDERLAY", - "LINK_STATE_ROUTING_TAG_PREV": "UNDERLAY", - "LOOPBACK0_IPV6_RANGE": "", - "LOOPBACK0_IP_RANGE": "10.2.0.0/22", - "LOOPBACK1_IPV6_RANGE": "", - "LOOPBACK1_IP_RANGE": "10.3.0.0/22", - "MACSEC_ALGORITHM": "", - "MACSEC_CIPHER_SUITE": "", - "MACSEC_FALLBACK_ALGORITHM": "", - "MACSEC_FALLBACK_KEY_STRING": "", - "MACSEC_KEY_STRING": "", - "MACSEC_REPORT_TIMER": "", - "MGMT_GW": "", - "MGMT_GW_INTERNAL": "", - "MGMT_PREFIX": "", - "MGMT_PREFIX_INTERNAL": "", - "MGMT_V6PREFIX": "", - "MGMT_V6PREFIX_INTERNAL": "", - "MPLS_HANDOFF": "false", - "MPLS_ISIS_AREA_NUM": "0001", - "MPLS_ISIS_AREA_NUM_PREV": "", - "MPLS_LB_ID": "", - "MPLS_LOOPBACK_IP_RANGE": "", - "MSO_CONNECTIVITY_DEPLOYED": "", - "MSO_CONTROLER_ID": "", - "MSO_SITE_GROUP_NAME": "", - "MSO_SITE_ID": "", - "MST_INSTANCE_RANGE": "", - "MULTICAST_GROUP_SUBNET": "239.1.1.0/25", - "NETFLOW_EXPORTER_LIST": "", - "NETFLOW_MONITOR_LIST": "", - "NETFLOW_RECORD_LIST": "", - "NETWORK_VLAN_RANGE": "2300-2999", - "NTP_SERVER_IP_LIST": "", - "NTP_SERVER_VRF": "", - "NVE_LB_ID": "1", - "NXAPI_HTTPS_PORT": "443", - "NXAPI_HTTP_PORT": "80", - "NXC_DEST_VRF": "", - "NXC_PROXY_PORT": "8080", - "NXC_PROXY_SERVER": "", - "NXC_SRC_INTF": "", - "OBJECT_TRACKING_NUMBER_RANGE": "100-299", - "OSPF_AREA_ID": "0.0.0.0", - "OSPF_AUTH_ENABLE": "false", - "OSPF_AUTH_KEY": "", - "OSPF_AUTH_KEY_ID": "", - "OVERLAY_MODE": "cli", - "OVERLAY_MODE_PREV": "cli", - "OVERWRITE_GLOBAL_NXC": "false", - "PER_VRF_LOOPBACK_AUTO_PROVISION": "false", - "PER_VRF_LOOPBACK_AUTO_PROVISION_PREV": "false", - "PER_VRF_LOOPBACK_AUTO_PROVISION_V6": "false", - "PER_VRF_LOOPBACK_AUTO_PROVISION_V6_PREV": "false", - "PER_VRF_LOOPBACK_IP_RANGE": "", - "PER_VRF_LOOPBACK_IP_RANGE_V6": "", - "PHANTOM_RP_LB_ID1": "", - "PHANTOM_RP_LB_ID2": "", - "PHANTOM_RP_LB_ID3": "", - "PHANTOM_RP_LB_ID4": "", - "PIM_HELLO_AUTH_ENABLE": "false", - "PIM_HELLO_AUTH_KEY": "", - "PM_ENABLE": "false", - "PM_ENABLE_PREV": "false", - "POWER_REDUNDANCY_MODE": "ps-redundant", - "PREMSO_PARENT_FABRIC": "", - "PTP_DOMAIN_ID": "", - "PTP_LB_ID": "", - "REPLICATION_MODE": "Multicast", - "ROUTER_ID_RANGE": "", - "ROUTE_MAP_SEQUENCE_NUMBER_RANGE": "1-65534", - "RP_COUNT": "2", - "RP_LB_ID": "254", - "RP_MODE": "asm", - "RR_COUNT": "2", - "SEED_SWITCH_CORE_INTERFACES": "", - "SERVICE_NETWORK_VLAN_RANGE": "3000-3199", - "SGT_ID_RANGE": "", - "SGT_NAME_PREFIX": "", - "SGT_PREPROVISION": "", - "SITE_ID": "65001", - "SLA_ID_RANGE": "10000-19999", - "SNMP_SERVER_HOST_TRAP": "true", - "SPINE_COUNT": "0", - "SPINE_SWITCH_CORE_INTERFACES": "", - "SSPINE_ADD_DEL_DEBUG_FLAG": "Disable", - "SSPINE_COUNT": "0", - "STATIC_UNDERLAY_IP_ALLOC": "false", - "STP_BRIDGE_PRIORITY": "", - "STP_ROOT_OPTION": "unmanaged", - "STP_VLAN_RANGE": "", - "STRICT_CC_MODE": "false", - "SUBINTERFACE_RANGE": "2-511", - "SUBNET_RANGE": "10.4.0.0/16", - "SUBNET_TARGET_MASK": "30", - "SYSLOG_SERVER_IP_LIST": "", - "SYSLOG_SERVER_VRF": "", - "SYSLOG_SEV": "", - "TCAM_ALLOCATION": "true", - "TOPDOWN_CONFIG_RM_TRACKING": "notstarted", - "UNDERLAY_IS_V6": "false", - "UNNUM_BOOTSTRAP_LB_ID": "", - "UNNUM_DHCP_END": "", - "UNNUM_DHCP_END_INTERNAL": "", - "UNNUM_DHCP_START": "", - "UNNUM_DHCP_START_INTERNAL": "", - "UPGRADE_FROM_VERSION": "", - "USE_LINK_LOCAL": "true", - "V6_SUBNET_RANGE": "", - "V6_SUBNET_TARGET_MASK": "126", - "VPC_AUTO_RECOVERY_TIME": "360", - "VPC_DELAY_RESTORE": "150", - "VPC_DELAY_RESTORE_TIME": "60", - "VPC_DOMAIN_ID_RANGE": "1-1000", - "VPC_ENABLE_IPv6_ND_SYNC": "true", - "VPC_PEER_KEEP_ALIVE_OPTION": "management", - "VPC_PEER_LINK_PO": "500", - "VPC_PEER_LINK_VLAN": "3600", - "VRF_LITE_AUTOCONFIG": "Manual", - "VRF_VLAN_RANGE": "2000-2299", - "abstract_anycast_rp": "anycast_rp", - "abstract_bgp": "base_bgp", - "abstract_bgp_neighbor": "evpn_bgp_rr_neighbor", - "abstract_bgp_rr": "evpn_bgp_rr", - "abstract_dhcp": "base_dhcp", - "abstract_extra_config_bootstrap": "extra_config_bootstrap_11_1", - "abstract_extra_config_leaf": "extra_config_leaf", - "abstract_extra_config_spine": "extra_config_spine", - "abstract_extra_config_tor": "extra_config_tor", - "abstract_feature_leaf": "base_feature_leaf_upg", - "abstract_feature_spine": "base_feature_spine_upg", - "abstract_isis": "base_isis_level2", - "abstract_isis_interface": "isis_interface", - "abstract_loopback_interface": "int_fabric_loopback_11_1", - "abstract_multicast": "base_multicast_11_1", - "abstract_ospf": "base_ospf", - "abstract_ospf_interface": "ospf_interface_11_1", - "abstract_pim_interface": "pim_interface", - "abstract_route_map": "route_map", - "abstract_routed_host": "int_routed_host", - "abstract_trunk_host": "int_trunk_host", - "abstract_vlan_interface": "int_fabric_vlan_11_1", - "abstract_vpc_domain": "base_vpc_domain_11_1", - "default_network": "Default_Network_Universal", - "default_pvlan_sec_network": "", - "default_vrf": "Default_VRF_Universal", - "enableRealTimeBackup": "", - "enableScheduledBackup": "", - "network_extension_template": "Default_Network_Extension_Universal", - "scheduledTime": "", - "temp_anycast_gateway": "anycast_gateway", - "temp_vpc_domain_mgmt": "vpc_domain_mgmt", - "temp_vpc_peer_link": "int_vpc_peer_link_po", - "vrf_extension_template": "Default_VRF_Extension_Universal" - }, - "operStatus": "HEALTHY", - "provisionMode": "DCNMTopDown", - "replicationMode": "Multicast", - "siteId": "65001", - "templateName": "Easy_Fabric", - "vrfExtensionTemplate": "Default_VRF_Extension_Universal", - "vrfTemplate": "Default_VRF_Universal" - } - ], - "MESSAGE": "OK", - "METHOD": "GET", - "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/rest/control/fabrics", - "RETURN_CODE": 200 - }, - "test_fabric_update_bulk_00130a": { - "TEST_NOTES": [ - "RETURN_CODE 200", - "MESSAGE OK" - ], - "MESSAGE": "OK", - "METHOD": "GET", - "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/rest/control/fabrics", - "RETURN_CODE": 200 - }, - "test_fabric_update_bulk_00140a": { - "TEST_NOTES": [ - "RETURN_CODE 200", - "MESSAGE OK" - ], - "MESSAGE": "OK", - "METHOD": "GET", - "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/rest/control/fabrics", - "RETURN_CODE": 200 - } -} diff --git a/tests/unit/modules/dcnm/dcnm_fabric/fixtures/responses_FabricDetailsByName_V2.json b/tests/unit/modules/dcnm/dcnm_fabric/fixtures/responses_FabricDetailsByName_V2.json index ed53309b9..eee3a5028 100644 --- a/tests/unit/modules/dcnm/dcnm_fabric/fixtures/responses_FabricDetailsByName_V2.json +++ b/tests/unit/modules/dcnm/dcnm_fabric/fixtures/responses_FabricDetailsByName_V2.json @@ -2,12 +2,59 @@ "test_notes": [ "Mocked responses for FabricDetails() class" ], - "test_fabric_details_by_name_v2_00200a": { - "TEST_NOTES": [ - "Verify property return values.", - "DATA contains one fabric dict.", - "RETURN_CODE == 200." + "test_fabric_config_deploy_00200a": { + "DATA": [ + { + "nvPairs": { + "DEPLOYMENT_FREEZE": "false", + "IS_READ_ONLY": "false" + } + } + ], + "MESSAGE": "OK", + "METHOD": "PUT", + "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/rest/control/fabrics/f1/LAN_Classic", + "RETURN_CODE": 200, + "sequence_number": 1 + }, + "test_fabric_config_deploy_00210a": { + "DATA": [ + { + "nvPairs": { + "DEPLOYMENT_FREEZE": "false", + "IS_READ_ONLY": "false" + } + } + ], + "MESSAGE": "OK", + "METHOD": "PUT", + "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/rest/control/fabrics/f1/LAN_Classic", + "RETURN_CODE": 200, + "sequence_number": 1 + }, + "test_fabric_config_deploy_00220a": { + "DATA": [ + { + "nvPairs": { + "DEPLOYMENT_FREEZE": "false", + "IS_READ_ONLY": "false" + } + } ], + "MESSAGE": "OK", + "METHOD": "PUT", + "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/rest/control/fabrics/f1/LAN_Classic", + "RETURN_CODE": 200, + "sequence_number": 1 + }, + "test_fabric_create_00030a": { + "DATA": [], + "MESSAGE": "OK", + "METHOD": "GET", + "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/rest/control/fabrics", + "RETURN_CODE": 200 + }, + "test_fabric_create_00031a": { "DATA": [ { "asn": "65001", @@ -320,125 +367,4581 @@ "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/rest/control/fabrics", "RETURN_CODE": 200 }, - "test_fabric_details_by_name_v2_00300a": { - "TEST_NOTES": [ - "Verify properties missing in the controller response return None.", - "DATA contains one fabric dict.", - "DATA[0].nvPairs.FABRIC_NAME == f1", - "DATA[0].nvPairs contains no other items.", - "RETURN_CODE == 200." - ], - "DATA": [ - { - "nvPairs": { - "FABRIC_NAME": "f1" - } - } - ], - "MESSAGE": "OK", - "METHOD": "GET", - "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/rest/control/fabrics", - "RETURN_CODE": 200 - }, - "test_fabric_details_by_name_v2_00500a": { - "TEST_NOTES": [ - "RETURN_CODE == 200.", - "MESSAGE == OK." - ], - "DATA": [ - { - "nvPairs": { - "FABRIC_NAME": "f1" - } - } - ], + "test_fabric_create_00032a": { + "DATA": [], "MESSAGE": "OK", "METHOD": "GET", "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/rest/control/fabrics", "RETURN_CODE": 200 }, - "test_fabric_details_by_name_v2_00510a": { - "TEST_NOTES": [ - "RETURN_CODE == 200.", - "MESSAGE == OK." - ], - "DATA": [ - { - "nvPairs": { - "FABRIC_NAME": "WRONG_FABRIC" - } - } - ], + "test_fabric_create_00033a": { + "DATA": [], "MESSAGE": "OK", "METHOD": "GET", "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/rest/control/fabrics", "RETURN_CODE": 200 }, - "test_fabric_details_by_name_v2_00600a": { - "TEST_NOTES": [ - "RETURN_CODE == 200.", - "MESSAGE == OK." - ], - "DATA": [ - { - "nvPairs": { - "FABRIC_NAME": "SOME_FABRIC" - } - } - ], + "test_fabric_create_bulk_00030a": { + "DATA": [], "MESSAGE": "OK", "METHOD": "GET", "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/rest/control/fabrics", "RETURN_CODE": 200 }, - "test_fabric_details_by_name_v2_00610a": { - "TEST_NOTES": [ - "FABRIC_NAME matches filter.", - "RETURN_CODE == 200.", - "MESSAGE == OK." - ], + "test_fabric_create_bulk_00031a": { "DATA": [ { + "asn": "65001", + "createdOn": 1711411093680, + "deviceType": "n9k", + "fabricId": "FABRIC-2", + "fabricName": "f1", + "fabricTechnology": "VXLANFabric", + "fabricTechnologyFriendly": "VXLAN EVPN", + "fabricType": "Switch_Fabric", + "fabricTypeFriendly": "Switch Fabric", + "id": 2, + "modifiedOn": 1711411096857, + "networkExtensionTemplate": "Default_Network_Extension_Universal", + "networkTemplate": "Default_Network_Universal", "nvPairs": { + "AAA_REMOTE_IP_ENABLED": "false", + "AAA_SERVER_CONF": "", + "ACTIVE_MIGRATION": "false", + "ADVERTISE_PIP_BGP": "false", + "ADVERTISE_PIP_ON_BORDER": "true", + "AGENT_INTF": "eth0", + "AI_ML_QOS_POLICY": "AI_Fabric_QOS_400G", + "ALLOW_NXC": "true", + "ALLOW_NXC_PREV": "true", + "ANYCAST_BGW_ADVERTISE_PIP": "false", + "ANYCAST_GW_MAC": "2020.0000.00aa", + "ANYCAST_LB_ID": "", + "ANYCAST_RP_IP_RANGE": "10.254.254.0/24", + "ANYCAST_RP_IP_RANGE_INTERNAL": "", + "AUTO_SYMMETRIC_DEFAULT_VRF": "false", + "AUTO_SYMMETRIC_VRF_LITE": "false", + "AUTO_UNIQUE_VRF_LITE_IP_PREFIX": "false", + "AUTO_UNIQUE_VRF_LITE_IP_PREFIX_PREV": "false", + "AUTO_VRFLITE_IFC_DEFAULT_VRF": "false", + "BANNER": "", + "BFD_AUTH_ENABLE": "false", + "BFD_AUTH_KEY": "", + "BFD_AUTH_KEY_ID": "", + "BFD_ENABLE": "false", + "BFD_ENABLE_PREV": "", + "BFD_IBGP_ENABLE": "false", + "BFD_ISIS_ENABLE": "false", + "BFD_OSPF_ENABLE": "false", + "BFD_PIM_ENABLE": "false", "BGP_AS": "65001", - "FABRIC_NAME": "MATCHING_FABRIC", - "ENABLE_NETFLOW": "false" - } - } - ], - "MESSAGE": "OK", - "METHOD": "GET", - "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/rest/control/fabrics", - "RETURN_CODE": 200 - }, - "test_fabric_details_by_name_v2_00700a": { - "TEST_NOTES": [ - "RETURN_CODE == 200.", - "MESSAGE == OK." - ], - "DATA": [ - { - "nvPairs": { - "FABRIC_NAME": "f1" - } - } - ], - "MESSAGE": "OK", - "METHOD": "GET", - "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/rest/control/fabrics", - "RETURN_CODE": 200 - }, - "test_fabric_details_by_name_v2_00710a": { - "TEST_NOTES": [ - "RETURN_CODE == 200.", - "MESSAGE == OK." - ], - "DATA": [ - { - "nvPairs": { - "FABRIC_NAME": "WRONG_FABRIC" - } - } + "BGP_AS_PREV": "65001", + "BGP_AUTH_ENABLE": "false", + "BGP_AUTH_KEY": "", + "BGP_AUTH_KEY_TYPE": "3", + "BGP_LB_ID": "0", + "BOOTSTRAP_CONF": "", + "BOOTSTRAP_ENABLE": "false", + "BOOTSTRAP_ENABLE_PREV": "false", + "BOOTSTRAP_MULTISUBNET": "#Scope_Start_IP, Scope_End_IP, Scope_Default_Gateway, Scope_Subnet_Prefix", + "BOOTSTRAP_MULTISUBNET_INTERNAL": "", + "BRFIELD_DEBUG_FLAG": "Disable", + "BROWNFIELD_NETWORK_NAME_FORMAT": "Auto_Net_VNI$$VNI$$_VLAN$$VLAN_ID$$", + "BROWNFIELD_SKIP_OVERLAY_NETWORK_ATTACHMENTS": "false", + "CDP_ENABLE": "false", + "COPP_POLICY": "strict", + "DCI_SUBNET_RANGE": "10.33.0.0/16", + "DCI_SUBNET_TARGET_MASK": "30", + "DEAFULT_QUEUING_POLICY_CLOUDSCALE": "queuing_policy_default_8q_cloudscale", + "DEAFULT_QUEUING_POLICY_OTHER": "queuing_policy_default_other", + "DEAFULT_QUEUING_POLICY_R_SERIES": "queuing_policy_default_r_series", + "DEFAULT_VRF_REDIS_BGP_RMAP": "", + "DEPLOYMENT_FREEZE": "false", + "DHCP_ENABLE": "false", + "DHCP_END": "", + "DHCP_END_INTERNAL": "", + "DHCP_IPV6_ENABLE": "", + "DHCP_IPV6_ENABLE_INTERNAL": "", + "DHCP_START": "", + "DHCP_START_INTERNAL": "", + "DNS_SERVER_IP_LIST": "", + "DNS_SERVER_VRF": "", + "DOMAIN_NAME_INTERNAL": "", + "ENABLE_AAA": "false", + "ENABLE_AGENT": "false", + "ENABLE_AI_ML_QOS_POLICY": "false", + "ENABLE_AI_ML_QOS_POLICY_FLAP": "false", + "ENABLE_DEFAULT_QUEUING_POLICY": "false", + "ENABLE_EVPN": "true", + "ENABLE_FABRIC_VPC_DOMAIN_ID": "false", + "ENABLE_FABRIC_VPC_DOMAIN_ID_PREV": "false", + "ENABLE_L3VNI_NO_VLAN": "false", + "ENABLE_MACSEC": "false", + "ENABLE_NETFLOW": "false", + "ENABLE_NETFLOW_PREV": "false", + "ENABLE_NGOAM": "true", + "ENABLE_NXAPI": "true", + "ENABLE_NXAPI_HTTP": "true", + "ENABLE_PBR": "false", + "ENABLE_PVLAN": "false", + "ENABLE_PVLAN_PREV": "false", + "ENABLE_SGT": "false", + "ENABLE_SGT_PREV": "false", + "ENABLE_TENANT_DHCP": "true", + "ENABLE_TRM": "false", + "ENABLE_VPC_PEER_LINK_NATIVE_VLAN": "false", + "ESR_OPTION": "PBR", + "EXTRA_CONF_INTRA_LINKS": "", + "EXTRA_CONF_LEAF": "", + "EXTRA_CONF_SPINE": "", + "EXTRA_CONF_TOR": "", + "EXT_FABRIC_TYPE": "", + "FABRIC_INTERFACE_TYPE": "p2p", + "FABRIC_MTU": "9216", + "FABRIC_MTU_PREV": "9216", + "FABRIC_NAME": "f1", + "FABRIC_TYPE": "Switch_Fabric", + "FABRIC_VPC_DOMAIN_ID": "", + "FABRIC_VPC_DOMAIN_ID_PREV": "", + "FABRIC_VPC_QOS": "false", + "FABRIC_VPC_QOS_POLICY_NAME": "spine_qos_for_fabric_vpc_peering", + "FEATURE_PTP": "false", + "FEATURE_PTP_INTERNAL": "false", + "FF": "Easy_Fabric", + "GRFIELD_DEBUG_FLAG": "Disable", + "HD_TIME": "180", + "HOST_INTF_ADMIN_STATE": "true", + "IBGP_PEER_TEMPLATE": "", + "IBGP_PEER_TEMPLATE_LEAF": "", + "INBAND_DHCP_SERVERS": "", + "INBAND_MGMT": "false", + "INBAND_MGMT_PREV": "false", + "ISIS_AREA_NUM": "0001", + "ISIS_AREA_NUM_PREV": "", + "ISIS_AUTH_ENABLE": "false", + "ISIS_AUTH_KEY": "", + "ISIS_AUTH_KEYCHAIN_KEY_ID": "", + "ISIS_AUTH_KEYCHAIN_NAME": "", + "ISIS_LEVEL": "level-2", + "ISIS_OVERLOAD_ELAPSE_TIME": "", + "ISIS_OVERLOAD_ENABLE": "", + "ISIS_P2P_ENABLE": "", + "L2_HOST_INTF_MTU": "9216", + "L2_HOST_INTF_MTU_PREV": "9216", + "L2_SEGMENT_ID_RANGE": "30000-49000", + "L3VNI_MCAST_GROUP": "", + "L3_PARTITION_ID_RANGE": "50000-59000", + "LINK_STATE_ROUTING": "ospf", + "LINK_STATE_ROUTING_TAG": "UNDERLAY", + "LINK_STATE_ROUTING_TAG_PREV": "UNDERLAY", + "LOOPBACK0_IPV6_RANGE": "", + "LOOPBACK0_IP_RANGE": "10.2.0.0/22", + "LOOPBACK1_IPV6_RANGE": "", + "LOOPBACK1_IP_RANGE": "10.3.0.0/22", + "MACSEC_ALGORITHM": "", + "MACSEC_CIPHER_SUITE": "", + "MACSEC_FALLBACK_ALGORITHM": "", + "MACSEC_FALLBACK_KEY_STRING": "", + "MACSEC_KEY_STRING": "", + "MACSEC_REPORT_TIMER": "", + "MGMT_GW": "", + "MGMT_GW_INTERNAL": "", + "MGMT_PREFIX": "", + "MGMT_PREFIX_INTERNAL": "", + "MGMT_V6PREFIX": "", + "MGMT_V6PREFIX_INTERNAL": "", + "MPLS_HANDOFF": "false", + "MPLS_ISIS_AREA_NUM": "0001", + "MPLS_ISIS_AREA_NUM_PREV": "", + "MPLS_LB_ID": "", + "MPLS_LOOPBACK_IP_RANGE": "", + "MSO_CONNECTIVITY_DEPLOYED": "", + "MSO_CONTROLER_ID": "", + "MSO_SITE_GROUP_NAME": "", + "MSO_SITE_ID": "", + "MST_INSTANCE_RANGE": "", + "MULTICAST_GROUP_SUBNET": "239.1.1.0/25", + "NETFLOW_EXPORTER_LIST": "", + "NETFLOW_MONITOR_LIST": "", + "NETFLOW_RECORD_LIST": "", + "NETWORK_VLAN_RANGE": "2300-2999", + "NTP_SERVER_IP_LIST": "", + "NTP_SERVER_VRF": "", + "NVE_LB_ID": "1", + "NXAPI_HTTPS_PORT": "443", + "NXAPI_HTTP_PORT": "80", + "NXC_DEST_VRF": "", + "NXC_PROXY_PORT": "8080", + "NXC_PROXY_SERVER": "", + "NXC_SRC_INTF": "", + "OBJECT_TRACKING_NUMBER_RANGE": "100-299", + "OSPF_AREA_ID": "0.0.0.0", + "OSPF_AUTH_ENABLE": "false", + "OSPF_AUTH_KEY": "", + "OSPF_AUTH_KEY_ID": "", + "OVERLAY_MODE": "cli", + "OVERLAY_MODE_PREV": "cli", + "OVERWRITE_GLOBAL_NXC": "false", + "PER_VRF_LOOPBACK_AUTO_PROVISION": "false", + "PER_VRF_LOOPBACK_AUTO_PROVISION_PREV": "false", + "PER_VRF_LOOPBACK_AUTO_PROVISION_V6": "false", + "PER_VRF_LOOPBACK_AUTO_PROVISION_V6_PREV": "false", + "PER_VRF_LOOPBACK_IP_RANGE": "", + "PER_VRF_LOOPBACK_IP_RANGE_V6": "", + "PHANTOM_RP_LB_ID1": "", + "PHANTOM_RP_LB_ID2": "", + "PHANTOM_RP_LB_ID3": "", + "PHANTOM_RP_LB_ID4": "", + "PIM_HELLO_AUTH_ENABLE": "false", + "PIM_HELLO_AUTH_KEY": "", + "PM_ENABLE": "false", + "PM_ENABLE_PREV": "false", + "POWER_REDUNDANCY_MODE": "ps-redundant", + "PREMSO_PARENT_FABRIC": "", + "PTP_DOMAIN_ID": "", + "PTP_LB_ID": "", + "REPLICATION_MODE": "Multicast", + "ROUTER_ID_RANGE": "", + "ROUTE_MAP_SEQUENCE_NUMBER_RANGE": "1-65534", + "RP_COUNT": "2", + "RP_LB_ID": "254", + "RP_MODE": "asm", + "RR_COUNT": "2", + "SEED_SWITCH_CORE_INTERFACES": "", + "SERVICE_NETWORK_VLAN_RANGE": "3000-3199", + "SGT_ID_RANGE": "", + "SGT_NAME_PREFIX": "", + "SGT_PREPROVISION": "", + "SITE_ID": "65001", + "SLA_ID_RANGE": "10000-19999", + "SNMP_SERVER_HOST_TRAP": "true", + "SPINE_COUNT": "0", + "SPINE_SWITCH_CORE_INTERFACES": "", + "SSPINE_ADD_DEL_DEBUG_FLAG": "Disable", + "SSPINE_COUNT": "0", + "STATIC_UNDERLAY_IP_ALLOC": "false", + "STP_BRIDGE_PRIORITY": "", + "STP_ROOT_OPTION": "unmanaged", + "STP_VLAN_RANGE": "", + "STRICT_CC_MODE": "false", + "SUBINTERFACE_RANGE": "2-511", + "SUBNET_RANGE": "10.4.0.0/16", + "SUBNET_TARGET_MASK": "30", + "SYSLOG_SERVER_IP_LIST": "", + "SYSLOG_SERVER_VRF": "", + "SYSLOG_SEV": "", + "TCAM_ALLOCATION": "true", + "TOPDOWN_CONFIG_RM_TRACKING": "notstarted", + "UNDERLAY_IS_V6": "false", + "UNNUM_BOOTSTRAP_LB_ID": "", + "UNNUM_DHCP_END": "", + "UNNUM_DHCP_END_INTERNAL": "", + "UNNUM_DHCP_START": "", + "UNNUM_DHCP_START_INTERNAL": "", + "UPGRADE_FROM_VERSION": "", + "USE_LINK_LOCAL": "true", + "V6_SUBNET_RANGE": "", + "V6_SUBNET_TARGET_MASK": "126", + "VPC_AUTO_RECOVERY_TIME": "360", + "VPC_DELAY_RESTORE": "150", + "VPC_DELAY_RESTORE_TIME": "60", + "VPC_DOMAIN_ID_RANGE": "1-1000", + "VPC_ENABLE_IPv6_ND_SYNC": "true", + "VPC_PEER_KEEP_ALIVE_OPTION": "management", + "VPC_PEER_LINK_PO": "500", + "VPC_PEER_LINK_VLAN": "3600", + "VRF_LITE_AUTOCONFIG": "Manual", + "VRF_VLAN_RANGE": "2000-2299", + "abstract_anycast_rp": "anycast_rp", + "abstract_bgp": "base_bgp", + "abstract_bgp_neighbor": "evpn_bgp_rr_neighbor", + "abstract_bgp_rr": "evpn_bgp_rr", + "abstract_dhcp": "base_dhcp", + "abstract_extra_config_bootstrap": "extra_config_bootstrap_11_1", + "abstract_extra_config_leaf": "extra_config_leaf", + "abstract_extra_config_spine": "extra_config_spine", + "abstract_extra_config_tor": "extra_config_tor", + "abstract_feature_leaf": "base_feature_leaf_upg", + "abstract_feature_spine": "base_feature_spine_upg", + "abstract_isis": "base_isis_level2", + "abstract_isis_interface": "isis_interface", + "abstract_loopback_interface": "int_fabric_loopback_11_1", + "abstract_multicast": "base_multicast_11_1", + "abstract_ospf": "base_ospf", + "abstract_ospf_interface": "ospf_interface_11_1", + "abstract_pim_interface": "pim_interface", + "abstract_route_map": "route_map", + "abstract_routed_host": "int_routed_host", + "abstract_trunk_host": "int_trunk_host", + "abstract_vlan_interface": "int_fabric_vlan_11_1", + "abstract_vpc_domain": "base_vpc_domain_11_1", + "default_network": "Default_Network_Universal", + "default_pvlan_sec_network": "", + "default_vrf": "Default_VRF_Universal", + "enableRealTimeBackup": "", + "enableScheduledBackup": "", + "network_extension_template": "Default_Network_Extension_Universal", + "scheduledTime": "", + "temp_anycast_gateway": "anycast_gateway", + "temp_vpc_domain_mgmt": "vpc_domain_mgmt", + "temp_vpc_peer_link": "int_vpc_peer_link_po", + "vrf_extension_template": "Default_VRF_Extension_Universal" + }, + "operStatus": "HEALTHY", + "provisionMode": "DCNMTopDown", + "replicationMode": "Multicast", + "siteId": "65001", + "templateName": "Easy_Fabric", + "vrfExtensionTemplate": "Default_VRF_Extension_Universal", + "vrfTemplate": "Default_VRF_Universal" + } + ], + "MESSAGE": "OK", + "METHOD": "GET", + "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/rest/control/fabrics", + "RETURN_CODE": 200 + }, + "test_fabric_create_bulk_00032a": { + "DATA": [], + "MESSAGE": "OK", + "METHOD": "GET", + "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/rest/control/fabrics", + "RETURN_CODE": 200 + }, + "test_fabric_create_bulk_00033a": { + "DATA": [], + "MESSAGE": "OK", + "METHOD": "GET", + "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/rest/control/fabrics", + "RETURN_CODE": 200 + }, + "test_fabric_delete_00040a": { + "DATA": [ + { + "asn": "65001", + "createdOn": 1711411093680, + "deviceType": "n9k", + "fabricId": "FABRIC-2", + "fabricName": "f1", + "fabricTechnology": "VXLANFabric", + "fabricTechnologyFriendly": "VXLAN EVPN", + "fabricType": "Switch_Fabric", + "fabricTypeFriendly": "Switch Fabric", + "id": 2, + "modifiedOn": 1711411096857, + "networkExtensionTemplate": "Default_Network_Extension_Universal", + "networkTemplate": "Default_Network_Universal", + "nvPairs": { + "AAA_REMOTE_IP_ENABLED": "false", + "AAA_SERVER_CONF": "", + "ACTIVE_MIGRATION": "false", + "ADVERTISE_PIP_BGP": "false", + "ADVERTISE_PIP_ON_BORDER": "true", + "AGENT_INTF": "eth0", + "AI_ML_QOS_POLICY": "AI_Fabric_QOS_400G", + "ALLOW_NXC": "true", + "ALLOW_NXC_PREV": "true", + "ANYCAST_BGW_ADVERTISE_PIP": "false", + "ANYCAST_GW_MAC": "2020.0000.00aa", + "ANYCAST_LB_ID": "", + "ANYCAST_RP_IP_RANGE": "10.254.254.0/24", + "ANYCAST_RP_IP_RANGE_INTERNAL": "", + "AUTO_SYMMETRIC_DEFAULT_VRF": "false", + "AUTO_SYMMETRIC_VRF_LITE": "false", + "AUTO_UNIQUE_VRF_LITE_IP_PREFIX": "false", + "AUTO_UNIQUE_VRF_LITE_IP_PREFIX_PREV": "false", + "AUTO_VRFLITE_IFC_DEFAULT_VRF": "false", + "BANNER": "", + "BFD_AUTH_ENABLE": "false", + "BFD_AUTH_KEY": "", + "BFD_AUTH_KEY_ID": "", + "BFD_ENABLE": "false", + "BFD_ENABLE_PREV": "", + "BFD_IBGP_ENABLE": "false", + "BFD_ISIS_ENABLE": "false", + "BFD_OSPF_ENABLE": "false", + "BFD_PIM_ENABLE": "false", + "BGP_AS": "65001", + "BGP_AS_PREV": "65001", + "BGP_AUTH_ENABLE": "false", + "BGP_AUTH_KEY": "", + "BGP_AUTH_KEY_TYPE": "3", + "BGP_LB_ID": "0", + "BOOTSTRAP_CONF": "", + "BOOTSTRAP_ENABLE": "false", + "BOOTSTRAP_ENABLE_PREV": "false", + "BOOTSTRAP_MULTISUBNET": "#Scope_Start_IP, Scope_End_IP, Scope_Default_Gateway, Scope_Subnet_Prefix", + "BOOTSTRAP_MULTISUBNET_INTERNAL": "", + "BRFIELD_DEBUG_FLAG": "Disable", + "BROWNFIELD_NETWORK_NAME_FORMAT": "Auto_Net_VNI$$VNI$$_VLAN$$VLAN_ID$$", + "BROWNFIELD_SKIP_OVERLAY_NETWORK_ATTACHMENTS": "false", + "CDP_ENABLE": "false", + "COPP_POLICY": "strict", + "DCI_SUBNET_RANGE": "10.33.0.0/16", + "DCI_SUBNET_TARGET_MASK": "30", + "DEAFULT_QUEUING_POLICY_CLOUDSCALE": "queuing_policy_default_8q_cloudscale", + "DEAFULT_QUEUING_POLICY_OTHER": "queuing_policy_default_other", + "DEAFULT_QUEUING_POLICY_R_SERIES": "queuing_policy_default_r_series", + "DEFAULT_VRF_REDIS_BGP_RMAP": "", + "DEPLOYMENT_FREEZE": "false", + "DHCP_ENABLE": "false", + "DHCP_END": "", + "DHCP_END_INTERNAL": "", + "DHCP_IPV6_ENABLE": "", + "DHCP_IPV6_ENABLE_INTERNAL": "", + "DHCP_START": "", + "DHCP_START_INTERNAL": "", + "DNS_SERVER_IP_LIST": "", + "DNS_SERVER_VRF": "", + "DOMAIN_NAME_INTERNAL": "", + "ENABLE_AAA": "false", + "ENABLE_AGENT": "false", + "ENABLE_AI_ML_QOS_POLICY": "false", + "ENABLE_AI_ML_QOS_POLICY_FLAP": "false", + "ENABLE_DEFAULT_QUEUING_POLICY": "false", + "ENABLE_EVPN": "true", + "ENABLE_FABRIC_VPC_DOMAIN_ID": "false", + "ENABLE_FABRIC_VPC_DOMAIN_ID_PREV": "false", + "ENABLE_L3VNI_NO_VLAN": "false", + "ENABLE_MACSEC": "false", + "ENABLE_NETFLOW": "false", + "ENABLE_NETFLOW_PREV": "false", + "ENABLE_NGOAM": "true", + "ENABLE_NXAPI": "true", + "ENABLE_NXAPI_HTTP": "true", + "ENABLE_PBR": "false", + "ENABLE_PVLAN": "false", + "ENABLE_PVLAN_PREV": "false", + "ENABLE_SGT": "false", + "ENABLE_SGT_PREV": "false", + "ENABLE_TENANT_DHCP": "true", + "ENABLE_TRM": "false", + "ENABLE_VPC_PEER_LINK_NATIVE_VLAN": "false", + "ESR_OPTION": "PBR", + "EXTRA_CONF_INTRA_LINKS": "", + "EXTRA_CONF_LEAF": "", + "EXTRA_CONF_SPINE": "", + "EXTRA_CONF_TOR": "", + "EXT_FABRIC_TYPE": "", + "FABRIC_INTERFACE_TYPE": "p2p", + "FABRIC_MTU": "9216", + "FABRIC_MTU_PREV": "9216", + "FABRIC_NAME": "f1", + "FABRIC_TYPE": "Switch_Fabric", + "FABRIC_VPC_DOMAIN_ID": "", + "FABRIC_VPC_DOMAIN_ID_PREV": "", + "FABRIC_VPC_QOS": "false", + "FABRIC_VPC_QOS_POLICY_NAME": "spine_qos_for_fabric_vpc_peering", + "FEATURE_PTP": "false", + "FEATURE_PTP_INTERNAL": "false", + "FF": "Easy_Fabric", + "GRFIELD_DEBUG_FLAG": "Disable", + "HD_TIME": "180", + "HOST_INTF_ADMIN_STATE": "true", + "IBGP_PEER_TEMPLATE": "", + "IBGP_PEER_TEMPLATE_LEAF": "", + "INBAND_DHCP_SERVERS": "", + "INBAND_MGMT": "false", + "INBAND_MGMT_PREV": "false", + "ISIS_AREA_NUM": "0001", + "ISIS_AREA_NUM_PREV": "", + "ISIS_AUTH_ENABLE": "false", + "ISIS_AUTH_KEY": "", + "ISIS_AUTH_KEYCHAIN_KEY_ID": "", + "ISIS_AUTH_KEYCHAIN_NAME": "", + "ISIS_LEVEL": "level-2", + "ISIS_OVERLOAD_ELAPSE_TIME": "", + "ISIS_OVERLOAD_ENABLE": "", + "ISIS_P2P_ENABLE": "", + "L2_HOST_INTF_MTU": "9216", + "L2_HOST_INTF_MTU_PREV": "9216", + "L2_SEGMENT_ID_RANGE": "30000-49000", + "L3VNI_MCAST_GROUP": "", + "L3_PARTITION_ID_RANGE": "50000-59000", + "LINK_STATE_ROUTING": "ospf", + "LINK_STATE_ROUTING_TAG": "UNDERLAY", + "LINK_STATE_ROUTING_TAG_PREV": "UNDERLAY", + "LOOPBACK0_IPV6_RANGE": "", + "LOOPBACK0_IP_RANGE": "10.2.0.0/22", + "LOOPBACK1_IPV6_RANGE": "", + "LOOPBACK1_IP_RANGE": "10.3.0.0/22", + "MACSEC_ALGORITHM": "", + "MACSEC_CIPHER_SUITE": "", + "MACSEC_FALLBACK_ALGORITHM": "", + "MACSEC_FALLBACK_KEY_STRING": "", + "MACSEC_KEY_STRING": "", + "MACSEC_REPORT_TIMER": "", + "MGMT_GW": "", + "MGMT_GW_INTERNAL": "", + "MGMT_PREFIX": "", + "MGMT_PREFIX_INTERNAL": "", + "MGMT_V6PREFIX": "", + "MGMT_V6PREFIX_INTERNAL": "", + "MPLS_HANDOFF": "false", + "MPLS_ISIS_AREA_NUM": "0001", + "MPLS_ISIS_AREA_NUM_PREV": "", + "MPLS_LB_ID": "", + "MPLS_LOOPBACK_IP_RANGE": "", + "MSO_CONNECTIVITY_DEPLOYED": "", + "MSO_CONTROLER_ID": "", + "MSO_SITE_GROUP_NAME": "", + "MSO_SITE_ID": "", + "MST_INSTANCE_RANGE": "", + "MULTICAST_GROUP_SUBNET": "239.1.1.0/25", + "NETFLOW_EXPORTER_LIST": "", + "NETFLOW_MONITOR_LIST": "", + "NETFLOW_RECORD_LIST": "", + "NETWORK_VLAN_RANGE": "2300-2999", + "NTP_SERVER_IP_LIST": "", + "NTP_SERVER_VRF": "", + "NVE_LB_ID": "1", + "NXAPI_HTTPS_PORT": "443", + "NXAPI_HTTP_PORT": "80", + "NXC_DEST_VRF": "", + "NXC_PROXY_PORT": "8080", + "NXC_PROXY_SERVER": "", + "NXC_SRC_INTF": "", + "OBJECT_TRACKING_NUMBER_RANGE": "100-299", + "OSPF_AREA_ID": "0.0.0.0", + "OSPF_AUTH_ENABLE": "false", + "OSPF_AUTH_KEY": "", + "OSPF_AUTH_KEY_ID": "", + "OVERLAY_MODE": "cli", + "OVERLAY_MODE_PREV": "cli", + "OVERWRITE_GLOBAL_NXC": "false", + "PER_VRF_LOOPBACK_AUTO_PROVISION": "false", + "PER_VRF_LOOPBACK_AUTO_PROVISION_PREV": "false", + "PER_VRF_LOOPBACK_AUTO_PROVISION_V6": "false", + "PER_VRF_LOOPBACK_AUTO_PROVISION_V6_PREV": "false", + "PER_VRF_LOOPBACK_IP_RANGE": "", + "PER_VRF_LOOPBACK_IP_RANGE_V6": "", + "PHANTOM_RP_LB_ID1": "", + "PHANTOM_RP_LB_ID2": "", + "PHANTOM_RP_LB_ID3": "", + "PHANTOM_RP_LB_ID4": "", + "PIM_HELLO_AUTH_ENABLE": "false", + "PIM_HELLO_AUTH_KEY": "", + "PM_ENABLE": "false", + "PM_ENABLE_PREV": "false", + "POWER_REDUNDANCY_MODE": "ps-redundant", + "PREMSO_PARENT_FABRIC": "", + "PTP_DOMAIN_ID": "", + "PTP_LB_ID": "", + "REPLICATION_MODE": "Multicast", + "ROUTER_ID_RANGE": "", + "ROUTE_MAP_SEQUENCE_NUMBER_RANGE": "1-65534", + "RP_COUNT": "2", + "RP_LB_ID": "254", + "RP_MODE": "asm", + "RR_COUNT": "2", + "SEED_SWITCH_CORE_INTERFACES": "", + "SERVICE_NETWORK_VLAN_RANGE": "3000-3199", + "SGT_ID_RANGE": "", + "SGT_NAME_PREFIX": "", + "SGT_PREPROVISION": "", + "SITE_ID": "65001", + "SLA_ID_RANGE": "10000-19999", + "SNMP_SERVER_HOST_TRAP": "true", + "SPINE_COUNT": "0", + "SPINE_SWITCH_CORE_INTERFACES": "", + "SSPINE_ADD_DEL_DEBUG_FLAG": "Disable", + "SSPINE_COUNT": "0", + "STATIC_UNDERLAY_IP_ALLOC": "false", + "STP_BRIDGE_PRIORITY": "", + "STP_ROOT_OPTION": "unmanaged", + "STP_VLAN_RANGE": "", + "STRICT_CC_MODE": "false", + "SUBINTERFACE_RANGE": "2-511", + "SUBNET_RANGE": "10.4.0.0/16", + "SUBNET_TARGET_MASK": "30", + "SYSLOG_SERVER_IP_LIST": "", + "SYSLOG_SERVER_VRF": "", + "SYSLOG_SEV": "", + "TCAM_ALLOCATION": "true", + "TOPDOWN_CONFIG_RM_TRACKING": "notstarted", + "UNDERLAY_IS_V6": "false", + "UNNUM_BOOTSTRAP_LB_ID": "", + "UNNUM_DHCP_END": "", + "UNNUM_DHCP_END_INTERNAL": "", + "UNNUM_DHCP_START": "", + "UNNUM_DHCP_START_INTERNAL": "", + "UPGRADE_FROM_VERSION": "", + "USE_LINK_LOCAL": "true", + "V6_SUBNET_RANGE": "", + "V6_SUBNET_TARGET_MASK": "126", + "VPC_AUTO_RECOVERY_TIME": "360", + "VPC_DELAY_RESTORE": "150", + "VPC_DELAY_RESTORE_TIME": "60", + "VPC_DOMAIN_ID_RANGE": "1-1000", + "VPC_ENABLE_IPv6_ND_SYNC": "true", + "VPC_PEER_KEEP_ALIVE_OPTION": "management", + "VPC_PEER_LINK_PO": "500", + "VPC_PEER_LINK_VLAN": "3600", + "VRF_LITE_AUTOCONFIG": "Manual", + "VRF_VLAN_RANGE": "2000-2299", + "abstract_anycast_rp": "anycast_rp", + "abstract_bgp": "base_bgp", + "abstract_bgp_neighbor": "evpn_bgp_rr_neighbor", + "abstract_bgp_rr": "evpn_bgp_rr", + "abstract_dhcp": "base_dhcp", + "abstract_extra_config_bootstrap": "extra_config_bootstrap_11_1", + "abstract_extra_config_leaf": "extra_config_leaf", + "abstract_extra_config_spine": "extra_config_spine", + "abstract_extra_config_tor": "extra_config_tor", + "abstract_feature_leaf": "base_feature_leaf_upg", + "abstract_feature_spine": "base_feature_spine_upg", + "abstract_isis": "base_isis_level2", + "abstract_isis_interface": "isis_interface", + "abstract_loopback_interface": "int_fabric_loopback_11_1", + "abstract_multicast": "base_multicast_11_1", + "abstract_ospf": "base_ospf", + "abstract_ospf_interface": "ospf_interface_11_1", + "abstract_pim_interface": "pim_interface", + "abstract_route_map": "route_map", + "abstract_routed_host": "int_routed_host", + "abstract_trunk_host": "int_trunk_host", + "abstract_vlan_interface": "int_fabric_vlan_11_1", + "abstract_vpc_domain": "base_vpc_domain_11_1", + "default_network": "Default_Network_Universal", + "default_pvlan_sec_network": "", + "default_vrf": "Default_VRF_Universal", + "enableRealTimeBackup": "", + "enableScheduledBackup": "", + "network_extension_template": "Default_Network_Extension_Universal", + "scheduledTime": "", + "temp_anycast_gateway": "anycast_gateway", + "temp_vpc_domain_mgmt": "vpc_domain_mgmt", + "temp_vpc_peer_link": "int_vpc_peer_link_po", + "vrf_extension_template": "Default_VRF_Extension_Universal" + }, + "operStatus": "HEALTHY", + "provisionMode": "DCNMTopDown", + "replicationMode": "Multicast", + "siteId": "65001", + "templateName": "Easy_Fabric", + "vrfExtensionTemplate": "Default_VRF_Extension_Universal", + "vrfTemplate": "Default_VRF_Universal" + } + ], + "MESSAGE": "OK", + "METHOD": "GET", + "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/rest/control/fabrics", + "RETURN_CODE": 200 + }, + "test_fabric_delete_00042a": { + "DATA": [ + { + "asn": "65001", + "createdOn": 1711411093680, + "deviceType": "n9k", + "fabricId": "FABRIC-2", + "fabricName": "f1", + "fabricTechnology": "VXLANFabric", + "fabricTechnologyFriendly": "VXLAN EVPN", + "fabricType": "Switch_Fabric", + "fabricTypeFriendly": "Switch Fabric", + "id": 2, + "modifiedOn": 1711411096857, + "networkExtensionTemplate": "Default_Network_Extension_Universal", + "networkTemplate": "Default_Network_Universal", + "nvPairs": { + "AAA_REMOTE_IP_ENABLED": "false", + "AAA_SERVER_CONF": "", + "ACTIVE_MIGRATION": "false", + "ADVERTISE_PIP_BGP": "false", + "ADVERTISE_PIP_ON_BORDER": "true", + "AGENT_INTF": "eth0", + "AI_ML_QOS_POLICY": "AI_Fabric_QOS_400G", + "ALLOW_NXC": "true", + "ALLOW_NXC_PREV": "true", + "ANYCAST_BGW_ADVERTISE_PIP": "false", + "ANYCAST_GW_MAC": "2020.0000.00aa", + "ANYCAST_LB_ID": "", + "ANYCAST_RP_IP_RANGE": "10.254.254.0/24", + "ANYCAST_RP_IP_RANGE_INTERNAL": "", + "AUTO_SYMMETRIC_DEFAULT_VRF": "false", + "AUTO_SYMMETRIC_VRF_LITE": "false", + "AUTO_UNIQUE_VRF_LITE_IP_PREFIX": "false", + "AUTO_UNIQUE_VRF_LITE_IP_PREFIX_PREV": "false", + "AUTO_VRFLITE_IFC_DEFAULT_VRF": "false", + "BANNER": "", + "BFD_AUTH_ENABLE": "false", + "BFD_AUTH_KEY": "", + "BFD_AUTH_KEY_ID": "", + "BFD_ENABLE": "false", + "BFD_ENABLE_PREV": "", + "BFD_IBGP_ENABLE": "false", + "BFD_ISIS_ENABLE": "false", + "BFD_OSPF_ENABLE": "false", + "BFD_PIM_ENABLE": "false", + "BGP_AS": "65001", + "BGP_AS_PREV": "65001", + "BGP_AUTH_ENABLE": "false", + "BGP_AUTH_KEY": "", + "BGP_AUTH_KEY_TYPE": "3", + "BGP_LB_ID": "0", + "BOOTSTRAP_CONF": "", + "BOOTSTRAP_ENABLE": "false", + "BOOTSTRAP_ENABLE_PREV": "false", + "BOOTSTRAP_MULTISUBNET": "#Scope_Start_IP, Scope_End_IP, Scope_Default_Gateway, Scope_Subnet_Prefix", + "BOOTSTRAP_MULTISUBNET_INTERNAL": "", + "BRFIELD_DEBUG_FLAG": "Disable", + "BROWNFIELD_NETWORK_NAME_FORMAT": "Auto_Net_VNI$$VNI$$_VLAN$$VLAN_ID$$", + "BROWNFIELD_SKIP_OVERLAY_NETWORK_ATTACHMENTS": "false", + "CDP_ENABLE": "false", + "COPP_POLICY": "strict", + "DCI_SUBNET_RANGE": "10.33.0.0/16", + "DCI_SUBNET_TARGET_MASK": "30", + "DEAFULT_QUEUING_POLICY_CLOUDSCALE": "queuing_policy_default_8q_cloudscale", + "DEAFULT_QUEUING_POLICY_OTHER": "queuing_policy_default_other", + "DEAFULT_QUEUING_POLICY_R_SERIES": "queuing_policy_default_r_series", + "DEFAULT_VRF_REDIS_BGP_RMAP": "", + "DEPLOYMENT_FREEZE": "false", + "DHCP_ENABLE": "false", + "DHCP_END": "", + "DHCP_END_INTERNAL": "", + "DHCP_IPV6_ENABLE": "", + "DHCP_IPV6_ENABLE_INTERNAL": "", + "DHCP_START": "", + "DHCP_START_INTERNAL": "", + "DNS_SERVER_IP_LIST": "", + "DNS_SERVER_VRF": "", + "DOMAIN_NAME_INTERNAL": "", + "ENABLE_AAA": "false", + "ENABLE_AGENT": "false", + "ENABLE_AI_ML_QOS_POLICY": "false", + "ENABLE_AI_ML_QOS_POLICY_FLAP": "false", + "ENABLE_DEFAULT_QUEUING_POLICY": "false", + "ENABLE_EVPN": "true", + "ENABLE_FABRIC_VPC_DOMAIN_ID": "false", + "ENABLE_FABRIC_VPC_DOMAIN_ID_PREV": "false", + "ENABLE_L3VNI_NO_VLAN": "false", + "ENABLE_MACSEC": "false", + "ENABLE_NETFLOW": "false", + "ENABLE_NETFLOW_PREV": "false", + "ENABLE_NGOAM": "true", + "ENABLE_NXAPI": "true", + "ENABLE_NXAPI_HTTP": "true", + "ENABLE_PBR": "false", + "ENABLE_PVLAN": "false", + "ENABLE_PVLAN_PREV": "false", + "ENABLE_SGT": "false", + "ENABLE_SGT_PREV": "false", + "ENABLE_TENANT_DHCP": "true", + "ENABLE_TRM": "false", + "ENABLE_VPC_PEER_LINK_NATIVE_VLAN": "false", + "ESR_OPTION": "PBR", + "EXTRA_CONF_INTRA_LINKS": "", + "EXTRA_CONF_LEAF": "", + "EXTRA_CONF_SPINE": "", + "EXTRA_CONF_TOR": "", + "EXT_FABRIC_TYPE": "", + "FABRIC_INTERFACE_TYPE": "p2p", + "FABRIC_MTU": "9216", + "FABRIC_MTU_PREV": "9216", + "FABRIC_NAME": "f1", + "FABRIC_TYPE": "Switch_Fabric", + "FABRIC_VPC_DOMAIN_ID": "", + "FABRIC_VPC_DOMAIN_ID_PREV": "", + "FABRIC_VPC_QOS": "false", + "FABRIC_VPC_QOS_POLICY_NAME": "spine_qos_for_fabric_vpc_peering", + "FEATURE_PTP": "false", + "FEATURE_PTP_INTERNAL": "false", + "FF": "Easy_Fabric", + "GRFIELD_DEBUG_FLAG": "Disable", + "HD_TIME": "180", + "HOST_INTF_ADMIN_STATE": "true", + "IBGP_PEER_TEMPLATE": "", + "IBGP_PEER_TEMPLATE_LEAF": "", + "INBAND_DHCP_SERVERS": "", + "INBAND_MGMT": "false", + "INBAND_MGMT_PREV": "false", + "ISIS_AREA_NUM": "0001", + "ISIS_AREA_NUM_PREV": "", + "ISIS_AUTH_ENABLE": "false", + "ISIS_AUTH_KEY": "", + "ISIS_AUTH_KEYCHAIN_KEY_ID": "", + "ISIS_AUTH_KEYCHAIN_NAME": "", + "ISIS_LEVEL": "level-2", + "ISIS_OVERLOAD_ELAPSE_TIME": "", + "ISIS_OVERLOAD_ENABLE": "", + "ISIS_P2P_ENABLE": "", + "L2_HOST_INTF_MTU": "9216", + "L2_HOST_INTF_MTU_PREV": "9216", + "L2_SEGMENT_ID_RANGE": "30000-49000", + "L3VNI_MCAST_GROUP": "", + "L3_PARTITION_ID_RANGE": "50000-59000", + "LINK_STATE_ROUTING": "ospf", + "LINK_STATE_ROUTING_TAG": "UNDERLAY", + "LINK_STATE_ROUTING_TAG_PREV": "UNDERLAY", + "LOOPBACK0_IPV6_RANGE": "", + "LOOPBACK0_IP_RANGE": "10.2.0.0/22", + "LOOPBACK1_IPV6_RANGE": "", + "LOOPBACK1_IP_RANGE": "10.3.0.0/22", + "MACSEC_ALGORITHM": "", + "MACSEC_CIPHER_SUITE": "", + "MACSEC_FALLBACK_ALGORITHM": "", + "MACSEC_FALLBACK_KEY_STRING": "", + "MACSEC_KEY_STRING": "", + "MACSEC_REPORT_TIMER": "", + "MGMT_GW": "", + "MGMT_GW_INTERNAL": "", + "MGMT_PREFIX": "", + "MGMT_PREFIX_INTERNAL": "", + "MGMT_V6PREFIX": "", + "MGMT_V6PREFIX_INTERNAL": "", + "MPLS_HANDOFF": "false", + "MPLS_ISIS_AREA_NUM": "0001", + "MPLS_ISIS_AREA_NUM_PREV": "", + "MPLS_LB_ID": "", + "MPLS_LOOPBACK_IP_RANGE": "", + "MSO_CONNECTIVITY_DEPLOYED": "", + "MSO_CONTROLER_ID": "", + "MSO_SITE_GROUP_NAME": "", + "MSO_SITE_ID": "", + "MST_INSTANCE_RANGE": "", + "MULTICAST_GROUP_SUBNET": "239.1.1.0/25", + "NETFLOW_EXPORTER_LIST": "", + "NETFLOW_MONITOR_LIST": "", + "NETFLOW_RECORD_LIST": "", + "NETWORK_VLAN_RANGE": "2300-2999", + "NTP_SERVER_IP_LIST": "", + "NTP_SERVER_VRF": "", + "NVE_LB_ID": "1", + "NXAPI_HTTPS_PORT": "443", + "NXAPI_HTTP_PORT": "80", + "NXC_DEST_VRF": "", + "NXC_PROXY_PORT": "8080", + "NXC_PROXY_SERVER": "", + "NXC_SRC_INTF": "", + "OBJECT_TRACKING_NUMBER_RANGE": "100-299", + "OSPF_AREA_ID": "0.0.0.0", + "OSPF_AUTH_ENABLE": "false", + "OSPF_AUTH_KEY": "", + "OSPF_AUTH_KEY_ID": "", + "OVERLAY_MODE": "cli", + "OVERLAY_MODE_PREV": "cli", + "OVERWRITE_GLOBAL_NXC": "false", + "PER_VRF_LOOPBACK_AUTO_PROVISION": "false", + "PER_VRF_LOOPBACK_AUTO_PROVISION_PREV": "false", + "PER_VRF_LOOPBACK_AUTO_PROVISION_V6": "false", + "PER_VRF_LOOPBACK_AUTO_PROVISION_V6_PREV": "false", + "PER_VRF_LOOPBACK_IP_RANGE": "", + "PER_VRF_LOOPBACK_IP_RANGE_V6": "", + "PHANTOM_RP_LB_ID1": "", + "PHANTOM_RP_LB_ID2": "", + "PHANTOM_RP_LB_ID3": "", + "PHANTOM_RP_LB_ID4": "", + "PIM_HELLO_AUTH_ENABLE": "false", + "PIM_HELLO_AUTH_KEY": "", + "PM_ENABLE": "false", + "PM_ENABLE_PREV": "false", + "POWER_REDUNDANCY_MODE": "ps-redundant", + "PREMSO_PARENT_FABRIC": "", + "PTP_DOMAIN_ID": "", + "PTP_LB_ID": "", + "REPLICATION_MODE": "Multicast", + "ROUTER_ID_RANGE": "", + "ROUTE_MAP_SEQUENCE_NUMBER_RANGE": "1-65534", + "RP_COUNT": "2", + "RP_LB_ID": "254", + "RP_MODE": "asm", + "RR_COUNT": "2", + "SEED_SWITCH_CORE_INTERFACES": "", + "SERVICE_NETWORK_VLAN_RANGE": "3000-3199", + "SGT_ID_RANGE": "", + "SGT_NAME_PREFIX": "", + "SGT_PREPROVISION": "", + "SITE_ID": "65001", + "SLA_ID_RANGE": "10000-19999", + "SNMP_SERVER_HOST_TRAP": "true", + "SPINE_COUNT": "0", + "SPINE_SWITCH_CORE_INTERFACES": "", + "SSPINE_ADD_DEL_DEBUG_FLAG": "Disable", + "SSPINE_COUNT": "0", + "STATIC_UNDERLAY_IP_ALLOC": "false", + "STP_BRIDGE_PRIORITY": "", + "STP_ROOT_OPTION": "unmanaged", + "STP_VLAN_RANGE": "", + "STRICT_CC_MODE": "false", + "SUBINTERFACE_RANGE": "2-511", + "SUBNET_RANGE": "10.4.0.0/16", + "SUBNET_TARGET_MASK": "30", + "SYSLOG_SERVER_IP_LIST": "", + "SYSLOG_SERVER_VRF": "", + "SYSLOG_SEV": "", + "TCAM_ALLOCATION": "true", + "TOPDOWN_CONFIG_RM_TRACKING": "notstarted", + "UNDERLAY_IS_V6": "false", + "UNNUM_BOOTSTRAP_LB_ID": "", + "UNNUM_DHCP_END": "", + "UNNUM_DHCP_END_INTERNAL": "", + "UNNUM_DHCP_START": "", + "UNNUM_DHCP_START_INTERNAL": "", + "UPGRADE_FROM_VERSION": "", + "USE_LINK_LOCAL": "true", + "V6_SUBNET_RANGE": "", + "V6_SUBNET_TARGET_MASK": "126", + "VPC_AUTO_RECOVERY_TIME": "360", + "VPC_DELAY_RESTORE": "150", + "VPC_DELAY_RESTORE_TIME": "60", + "VPC_DOMAIN_ID_RANGE": "1-1000", + "VPC_ENABLE_IPv6_ND_SYNC": "true", + "VPC_PEER_KEEP_ALIVE_OPTION": "management", + "VPC_PEER_LINK_PO": "500", + "VPC_PEER_LINK_VLAN": "3600", + "VRF_LITE_AUTOCONFIG": "Manual", + "VRF_VLAN_RANGE": "2000-2299", + "abstract_anycast_rp": "anycast_rp", + "abstract_bgp": "base_bgp", + "abstract_bgp_neighbor": "evpn_bgp_rr_neighbor", + "abstract_bgp_rr": "evpn_bgp_rr", + "abstract_dhcp": "base_dhcp", + "abstract_extra_config_bootstrap": "extra_config_bootstrap_11_1", + "abstract_extra_config_leaf": "extra_config_leaf", + "abstract_extra_config_spine": "extra_config_spine", + "abstract_extra_config_tor": "extra_config_tor", + "abstract_feature_leaf": "base_feature_leaf_upg", + "abstract_feature_spine": "base_feature_spine_upg", + "abstract_isis": "base_isis_level2", + "abstract_isis_interface": "isis_interface", + "abstract_loopback_interface": "int_fabric_loopback_11_1", + "abstract_multicast": "base_multicast_11_1", + "abstract_ospf": "base_ospf", + "abstract_ospf_interface": "ospf_interface_11_1", + "abstract_pim_interface": "pim_interface", + "abstract_route_map": "route_map", + "abstract_routed_host": "int_routed_host", + "abstract_trunk_host": "int_trunk_host", + "abstract_vlan_interface": "int_fabric_vlan_11_1", + "abstract_vpc_domain": "base_vpc_domain_11_1", + "default_network": "Default_Network_Universal", + "default_pvlan_sec_network": "", + "default_vrf": "Default_VRF_Universal", + "enableRealTimeBackup": "", + "enableScheduledBackup": "", + "network_extension_template": "Default_Network_Extension_Universal", + "scheduledTime": "", + "temp_anycast_gateway": "anycast_gateway", + "temp_vpc_domain_mgmt": "vpc_domain_mgmt", + "temp_vpc_peer_link": "int_vpc_peer_link_po", + "vrf_extension_template": "Default_VRF_Extension_Universal" + }, + "operStatus": "HEALTHY", + "provisionMode": "DCNMTopDown", + "replicationMode": "Multicast", + "siteId": "65001", + "templateName": "Easy_Fabric", + "vrfExtensionTemplate": "Default_VRF_Extension_Universal", + "vrfTemplate": "Default_VRF_Universal" + } + ], + "MESSAGE": "OK", + "METHOD": "GET", + "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/rest/control/fabrics", + "RETURN_CODE": 200 + }, + "test_fabric_delete_00043a": { + "DATA": [], + "MESSAGE": "OK", + "METHOD": "GET", + "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/rest/control/fabrics", + "RETURN_CODE": 200 + }, + "test_fabric_delete_00044a": { + "DATA": [ + { + "asn": "65001", + "createdOn": 1711411093680, + "deviceType": "n9k", + "fabricId": "FABRIC-2", + "fabricName": "f1", + "fabricTechnology": "VXLANFabric", + "fabricTechnologyFriendly": "VXLAN EVPN", + "fabricType": "Switch_Fabric", + "fabricTypeFriendly": "Switch Fabric", + "id": 2, + "modifiedOn": 1711411096857, + "networkExtensionTemplate": "Default_Network_Extension_Universal", + "networkTemplate": "Default_Network_Universal", + "nvPairs": { + "AAA_REMOTE_IP_ENABLED": "false", + "AAA_SERVER_CONF": "", + "ACTIVE_MIGRATION": "false", + "ADVERTISE_PIP_BGP": "false", + "ADVERTISE_PIP_ON_BORDER": "true", + "AGENT_INTF": "eth0", + "AI_ML_QOS_POLICY": "AI_Fabric_QOS_400G", + "ALLOW_NXC": "true", + "ALLOW_NXC_PREV": "true", + "ANYCAST_BGW_ADVERTISE_PIP": "false", + "ANYCAST_GW_MAC": "2020.0000.00aa", + "ANYCAST_LB_ID": "", + "ANYCAST_RP_IP_RANGE": "10.254.254.0/24", + "ANYCAST_RP_IP_RANGE_INTERNAL": "", + "AUTO_SYMMETRIC_DEFAULT_VRF": "false", + "AUTO_SYMMETRIC_VRF_LITE": "false", + "AUTO_UNIQUE_VRF_LITE_IP_PREFIX": "false", + "AUTO_UNIQUE_VRF_LITE_IP_PREFIX_PREV": "false", + "AUTO_VRFLITE_IFC_DEFAULT_VRF": "false", + "BANNER": "", + "BFD_AUTH_ENABLE": "false", + "BFD_AUTH_KEY": "", + "BFD_AUTH_KEY_ID": "", + "BFD_ENABLE": "false", + "BFD_ENABLE_PREV": "", + "BFD_IBGP_ENABLE": "false", + "BFD_ISIS_ENABLE": "false", + "BFD_OSPF_ENABLE": "false", + "BFD_PIM_ENABLE": "false", + "BGP_AS": "65001", + "BGP_AS_PREV": "65001", + "BGP_AUTH_ENABLE": "false", + "BGP_AUTH_KEY": "", + "BGP_AUTH_KEY_TYPE": "3", + "BGP_LB_ID": "0", + "BOOTSTRAP_CONF": "", + "BOOTSTRAP_ENABLE": "false", + "BOOTSTRAP_ENABLE_PREV": "false", + "BOOTSTRAP_MULTISUBNET": "#Scope_Start_IP, Scope_End_IP, Scope_Default_Gateway, Scope_Subnet_Prefix", + "BOOTSTRAP_MULTISUBNET_INTERNAL": "", + "BRFIELD_DEBUG_FLAG": "Disable", + "BROWNFIELD_NETWORK_NAME_FORMAT": "Auto_Net_VNI$$VNI$$_VLAN$$VLAN_ID$$", + "BROWNFIELD_SKIP_OVERLAY_NETWORK_ATTACHMENTS": "false", + "CDP_ENABLE": "false", + "COPP_POLICY": "strict", + "DCI_SUBNET_RANGE": "10.33.0.0/16", + "DCI_SUBNET_TARGET_MASK": "30", + "DEAFULT_QUEUING_POLICY_CLOUDSCALE": "queuing_policy_default_8q_cloudscale", + "DEAFULT_QUEUING_POLICY_OTHER": "queuing_policy_default_other", + "DEAFULT_QUEUING_POLICY_R_SERIES": "queuing_policy_default_r_series", + "DEFAULT_VRF_REDIS_BGP_RMAP": "", + "DEPLOYMENT_FREEZE": "false", + "DHCP_ENABLE": "false", + "DHCP_END": "", + "DHCP_END_INTERNAL": "", + "DHCP_IPV6_ENABLE": "", + "DHCP_IPV6_ENABLE_INTERNAL": "", + "DHCP_START": "", + "DHCP_START_INTERNAL": "", + "DNS_SERVER_IP_LIST": "", + "DNS_SERVER_VRF": "", + "DOMAIN_NAME_INTERNAL": "", + "ENABLE_AAA": "false", + "ENABLE_AGENT": "false", + "ENABLE_AI_ML_QOS_POLICY": "false", + "ENABLE_AI_ML_QOS_POLICY_FLAP": "false", + "ENABLE_DEFAULT_QUEUING_POLICY": "false", + "ENABLE_EVPN": "true", + "ENABLE_FABRIC_VPC_DOMAIN_ID": "false", + "ENABLE_FABRIC_VPC_DOMAIN_ID_PREV": "false", + "ENABLE_L3VNI_NO_VLAN": "false", + "ENABLE_MACSEC": "false", + "ENABLE_NETFLOW": "false", + "ENABLE_NETFLOW_PREV": "false", + "ENABLE_NGOAM": "true", + "ENABLE_NXAPI": "true", + "ENABLE_NXAPI_HTTP": "true", + "ENABLE_PBR": "false", + "ENABLE_PVLAN": "false", + "ENABLE_PVLAN_PREV": "false", + "ENABLE_SGT": "false", + "ENABLE_SGT_PREV": "false", + "ENABLE_TENANT_DHCP": "true", + "ENABLE_TRM": "false", + "ENABLE_VPC_PEER_LINK_NATIVE_VLAN": "false", + "ESR_OPTION": "PBR", + "EXTRA_CONF_INTRA_LINKS": "", + "EXTRA_CONF_LEAF": "", + "EXTRA_CONF_SPINE": "", + "EXTRA_CONF_TOR": "", + "EXT_FABRIC_TYPE": "", + "FABRIC_INTERFACE_TYPE": "p2p", + "FABRIC_MTU": "9216", + "FABRIC_MTU_PREV": "9216", + "FABRIC_NAME": "f1", + "FABRIC_TYPE": "Switch_Fabric", + "FABRIC_VPC_DOMAIN_ID": "", + "FABRIC_VPC_DOMAIN_ID_PREV": "", + "FABRIC_VPC_QOS": "false", + "FABRIC_VPC_QOS_POLICY_NAME": "spine_qos_for_fabric_vpc_peering", + "FEATURE_PTP": "false", + "FEATURE_PTP_INTERNAL": "false", + "FF": "Easy_Fabric", + "GRFIELD_DEBUG_FLAG": "Disable", + "HD_TIME": "180", + "HOST_INTF_ADMIN_STATE": "true", + "IBGP_PEER_TEMPLATE": "", + "IBGP_PEER_TEMPLATE_LEAF": "", + "INBAND_DHCP_SERVERS": "", + "INBAND_MGMT": "false", + "INBAND_MGMT_PREV": "false", + "ISIS_AREA_NUM": "0001", + "ISIS_AREA_NUM_PREV": "", + "ISIS_AUTH_ENABLE": "false", + "ISIS_AUTH_KEY": "", + "ISIS_AUTH_KEYCHAIN_KEY_ID": "", + "ISIS_AUTH_KEYCHAIN_NAME": "", + "ISIS_LEVEL": "level-2", + "ISIS_OVERLOAD_ELAPSE_TIME": "", + "ISIS_OVERLOAD_ENABLE": "", + "ISIS_P2P_ENABLE": "", + "L2_HOST_INTF_MTU": "9216", + "L2_HOST_INTF_MTU_PREV": "9216", + "L2_SEGMENT_ID_RANGE": "30000-49000", + "L3VNI_MCAST_GROUP": "", + "L3_PARTITION_ID_RANGE": "50000-59000", + "LINK_STATE_ROUTING": "ospf", + "LINK_STATE_ROUTING_TAG": "UNDERLAY", + "LINK_STATE_ROUTING_TAG_PREV": "UNDERLAY", + "LOOPBACK0_IPV6_RANGE": "", + "LOOPBACK0_IP_RANGE": "10.2.0.0/22", + "LOOPBACK1_IPV6_RANGE": "", + "LOOPBACK1_IP_RANGE": "10.3.0.0/22", + "MACSEC_ALGORITHM": "", + "MACSEC_CIPHER_SUITE": "", + "MACSEC_FALLBACK_ALGORITHM": "", + "MACSEC_FALLBACK_KEY_STRING": "", + "MACSEC_KEY_STRING": "", + "MACSEC_REPORT_TIMER": "", + "MGMT_GW": "", + "MGMT_GW_INTERNAL": "", + "MGMT_PREFIX": "", + "MGMT_PREFIX_INTERNAL": "", + "MGMT_V6PREFIX": "", + "MGMT_V6PREFIX_INTERNAL": "", + "MPLS_HANDOFF": "false", + "MPLS_ISIS_AREA_NUM": "0001", + "MPLS_ISIS_AREA_NUM_PREV": "", + "MPLS_LB_ID": "", + "MPLS_LOOPBACK_IP_RANGE": "", + "MSO_CONNECTIVITY_DEPLOYED": "", + "MSO_CONTROLER_ID": "", + "MSO_SITE_GROUP_NAME": "", + "MSO_SITE_ID": "", + "MST_INSTANCE_RANGE": "", + "MULTICAST_GROUP_SUBNET": "239.1.1.0/25", + "NETFLOW_EXPORTER_LIST": "", + "NETFLOW_MONITOR_LIST": "", + "NETFLOW_RECORD_LIST": "", + "NETWORK_VLAN_RANGE": "2300-2999", + "NTP_SERVER_IP_LIST": "", + "NTP_SERVER_VRF": "", + "NVE_LB_ID": "1", + "NXAPI_HTTPS_PORT": "443", + "NXAPI_HTTP_PORT": "80", + "NXC_DEST_VRF": "", + "NXC_PROXY_PORT": "8080", + "NXC_PROXY_SERVER": "", + "NXC_SRC_INTF": "", + "OBJECT_TRACKING_NUMBER_RANGE": "100-299", + "OSPF_AREA_ID": "0.0.0.0", + "OSPF_AUTH_ENABLE": "false", + "OSPF_AUTH_KEY": "", + "OSPF_AUTH_KEY_ID": "", + "OVERLAY_MODE": "cli", + "OVERLAY_MODE_PREV": "cli", + "OVERWRITE_GLOBAL_NXC": "false", + "PER_VRF_LOOPBACK_AUTO_PROVISION": "false", + "PER_VRF_LOOPBACK_AUTO_PROVISION_PREV": "false", + "PER_VRF_LOOPBACK_AUTO_PROVISION_V6": "false", + "PER_VRF_LOOPBACK_AUTO_PROVISION_V6_PREV": "false", + "PER_VRF_LOOPBACK_IP_RANGE": "", + "PER_VRF_LOOPBACK_IP_RANGE_V6": "", + "PHANTOM_RP_LB_ID1": "", + "PHANTOM_RP_LB_ID2": "", + "PHANTOM_RP_LB_ID3": "", + "PHANTOM_RP_LB_ID4": "", + "PIM_HELLO_AUTH_ENABLE": "false", + "PIM_HELLO_AUTH_KEY": "", + "PM_ENABLE": "false", + "PM_ENABLE_PREV": "false", + "POWER_REDUNDANCY_MODE": "ps-redundant", + "PREMSO_PARENT_FABRIC": "", + "PTP_DOMAIN_ID": "", + "PTP_LB_ID": "", + "REPLICATION_MODE": "Multicast", + "ROUTER_ID_RANGE": "", + "ROUTE_MAP_SEQUENCE_NUMBER_RANGE": "1-65534", + "RP_COUNT": "2", + "RP_LB_ID": "254", + "RP_MODE": "asm", + "RR_COUNT": "2", + "SEED_SWITCH_CORE_INTERFACES": "", + "SERVICE_NETWORK_VLAN_RANGE": "3000-3199", + "SGT_ID_RANGE": "", + "SGT_NAME_PREFIX": "", + "SGT_PREPROVISION": "", + "SITE_ID": "65001", + "SLA_ID_RANGE": "10000-19999", + "SNMP_SERVER_HOST_TRAP": "true", + "SPINE_COUNT": "0", + "SPINE_SWITCH_CORE_INTERFACES": "", + "SSPINE_ADD_DEL_DEBUG_FLAG": "Disable", + "SSPINE_COUNT": "0", + "STATIC_UNDERLAY_IP_ALLOC": "false", + "STP_BRIDGE_PRIORITY": "", + "STP_ROOT_OPTION": "unmanaged", + "STP_VLAN_RANGE": "", + "STRICT_CC_MODE": "false", + "SUBINTERFACE_RANGE": "2-511", + "SUBNET_RANGE": "10.4.0.0/16", + "SUBNET_TARGET_MASK": "30", + "SYSLOG_SERVER_IP_LIST": "", + "SYSLOG_SERVER_VRF": "", + "SYSLOG_SEV": "", + "TCAM_ALLOCATION": "true", + "TOPDOWN_CONFIG_RM_TRACKING": "notstarted", + "UNDERLAY_IS_V6": "false", + "UNNUM_BOOTSTRAP_LB_ID": "", + "UNNUM_DHCP_END": "", + "UNNUM_DHCP_END_INTERNAL": "", + "UNNUM_DHCP_START": "", + "UNNUM_DHCP_START_INTERNAL": "", + "UPGRADE_FROM_VERSION": "", + "USE_LINK_LOCAL": "true", + "V6_SUBNET_RANGE": "", + "V6_SUBNET_TARGET_MASK": "126", + "VPC_AUTO_RECOVERY_TIME": "360", + "VPC_DELAY_RESTORE": "150", + "VPC_DELAY_RESTORE_TIME": "60", + "VPC_DOMAIN_ID_RANGE": "1-1000", + "VPC_ENABLE_IPv6_ND_SYNC": "true", + "VPC_PEER_KEEP_ALIVE_OPTION": "management", + "VPC_PEER_LINK_PO": "500", + "VPC_PEER_LINK_VLAN": "3600", + "VRF_LITE_AUTOCONFIG": "Manual", + "VRF_VLAN_RANGE": "2000-2299", + "abstract_anycast_rp": "anycast_rp", + "abstract_bgp": "base_bgp", + "abstract_bgp_neighbor": "evpn_bgp_rr_neighbor", + "abstract_bgp_rr": "evpn_bgp_rr", + "abstract_dhcp": "base_dhcp", + "abstract_extra_config_bootstrap": "extra_config_bootstrap_11_1", + "abstract_extra_config_leaf": "extra_config_leaf", + "abstract_extra_config_spine": "extra_config_spine", + "abstract_extra_config_tor": "extra_config_tor", + "abstract_feature_leaf": "base_feature_leaf_upg", + "abstract_feature_spine": "base_feature_spine_upg", + "abstract_isis": "base_isis_level2", + "abstract_isis_interface": "isis_interface", + "abstract_loopback_interface": "int_fabric_loopback_11_1", + "abstract_multicast": "base_multicast_11_1", + "abstract_ospf": "base_ospf", + "abstract_ospf_interface": "ospf_interface_11_1", + "abstract_pim_interface": "pim_interface", + "abstract_route_map": "route_map", + "abstract_routed_host": "int_routed_host", + "abstract_trunk_host": "int_trunk_host", + "abstract_vlan_interface": "int_fabric_vlan_11_1", + "abstract_vpc_domain": "base_vpc_domain_11_1", + "default_network": "Default_Network_Universal", + "default_pvlan_sec_network": "", + "default_vrf": "Default_VRF_Universal", + "enableRealTimeBackup": "", + "enableScheduledBackup": "", + "network_extension_template": "Default_Network_Extension_Universal", + "scheduledTime": "", + "temp_anycast_gateway": "anycast_gateway", + "temp_vpc_domain_mgmt": "vpc_domain_mgmt", + "temp_vpc_peer_link": "int_vpc_peer_link_po", + "vrf_extension_template": "Default_VRF_Extension_Universal" + }, + "operStatus": "HEALTHY", + "provisionMode": "DCNMTopDown", + "replicationMode": "Multicast", + "siteId": "65001", + "templateName": "Easy_Fabric", + "vrfExtensionTemplate": "Default_VRF_Extension_Universal", + "vrfTemplate": "Default_VRF_Universal" + } + ], + "MESSAGE": "OK", + "METHOD": "GET", + "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/rest/control/fabrics", + "RETURN_CODE": 200 + }, + "test_fabric_delete_00050a": { + "DATA": [ + { + "asn": "65001", + "createdOn": 1711411093680, + "deviceType": "n9k", + "fabricId": "FABRIC-2", + "fabricName": "f1", + "fabricTechnology": "VXLANFabric", + "fabricTechnologyFriendly": "VXLAN EVPN", + "fabricType": "Switch_Fabric", + "fabricTypeFriendly": "Switch Fabric", + "id": 2, + "modifiedOn": 1711411096857, + "networkExtensionTemplate": "Default_Network_Extension_Universal", + "networkTemplate": "Default_Network_Universal", + "nvPairs": { + "AAA_REMOTE_IP_ENABLED": "false", + "AAA_SERVER_CONF": "", + "ACTIVE_MIGRATION": "false", + "ADVERTISE_PIP_BGP": "false", + "ADVERTISE_PIP_ON_BORDER": "true", + "AGENT_INTF": "eth0", + "AI_ML_QOS_POLICY": "AI_Fabric_QOS_400G", + "ALLOW_NXC": "true", + "ALLOW_NXC_PREV": "true", + "ANYCAST_BGW_ADVERTISE_PIP": "false", + "ANYCAST_GW_MAC": "2020.0000.00aa", + "ANYCAST_LB_ID": "", + "ANYCAST_RP_IP_RANGE": "10.254.254.0/24", + "ANYCAST_RP_IP_RANGE_INTERNAL": "", + "AUTO_SYMMETRIC_DEFAULT_VRF": "false", + "AUTO_SYMMETRIC_VRF_LITE": "false", + "AUTO_UNIQUE_VRF_LITE_IP_PREFIX": "false", + "AUTO_UNIQUE_VRF_LITE_IP_PREFIX_PREV": "false", + "AUTO_VRFLITE_IFC_DEFAULT_VRF": "false", + "BANNER": "", + "BFD_AUTH_ENABLE": "false", + "BFD_AUTH_KEY": "", + "BFD_AUTH_KEY_ID": "", + "BFD_ENABLE": "false", + "BFD_ENABLE_PREV": "", + "BFD_IBGP_ENABLE": "false", + "BFD_ISIS_ENABLE": "false", + "BFD_OSPF_ENABLE": "false", + "BFD_PIM_ENABLE": "false", + "BGP_AS": "65001", + "BGP_AS_PREV": "65001", + "BGP_AUTH_ENABLE": "false", + "BGP_AUTH_KEY": "", + "BGP_AUTH_KEY_TYPE": "3", + "BGP_LB_ID": "0", + "BOOTSTRAP_CONF": "", + "BOOTSTRAP_ENABLE": "false", + "BOOTSTRAP_ENABLE_PREV": "false", + "BOOTSTRAP_MULTISUBNET": "#Scope_Start_IP, Scope_End_IP, Scope_Default_Gateway, Scope_Subnet_Prefix", + "BOOTSTRAP_MULTISUBNET_INTERNAL": "", + "BRFIELD_DEBUG_FLAG": "Disable", + "BROWNFIELD_NETWORK_NAME_FORMAT": "Auto_Net_VNI$$VNI$$_VLAN$$VLAN_ID$$", + "BROWNFIELD_SKIP_OVERLAY_NETWORK_ATTACHMENTS": "false", + "CDP_ENABLE": "false", + "COPP_POLICY": "strict", + "DCI_SUBNET_RANGE": "10.33.0.0/16", + "DCI_SUBNET_TARGET_MASK": "30", + "DEAFULT_QUEUING_POLICY_CLOUDSCALE": "queuing_policy_default_8q_cloudscale", + "DEAFULT_QUEUING_POLICY_OTHER": "queuing_policy_default_other", + "DEAFULT_QUEUING_POLICY_R_SERIES": "queuing_policy_default_r_series", + "DEFAULT_VRF_REDIS_BGP_RMAP": "", + "DEPLOYMENT_FREEZE": "false", + "DHCP_ENABLE": "false", + "DHCP_END": "", + "DHCP_END_INTERNAL": "", + "DHCP_IPV6_ENABLE": "", + "DHCP_IPV6_ENABLE_INTERNAL": "", + "DHCP_START": "", + "DHCP_START_INTERNAL": "", + "DNS_SERVER_IP_LIST": "", + "DNS_SERVER_VRF": "", + "DOMAIN_NAME_INTERNAL": "", + "ENABLE_AAA": "false", + "ENABLE_AGENT": "false", + "ENABLE_AI_ML_QOS_POLICY": "false", + "ENABLE_AI_ML_QOS_POLICY_FLAP": "false", + "ENABLE_DEFAULT_QUEUING_POLICY": "false", + "ENABLE_EVPN": "true", + "ENABLE_FABRIC_VPC_DOMAIN_ID": "false", + "ENABLE_FABRIC_VPC_DOMAIN_ID_PREV": "false", + "ENABLE_L3VNI_NO_VLAN": "false", + "ENABLE_MACSEC": "false", + "ENABLE_NETFLOW": "false", + "ENABLE_NETFLOW_PREV": "false", + "ENABLE_NGOAM": "true", + "ENABLE_NXAPI": "true", + "ENABLE_NXAPI_HTTP": "true", + "ENABLE_PBR": "false", + "ENABLE_PVLAN": "false", + "ENABLE_PVLAN_PREV": "false", + "ENABLE_SGT": "false", + "ENABLE_SGT_PREV": "false", + "ENABLE_TENANT_DHCP": "true", + "ENABLE_TRM": "false", + "ENABLE_VPC_PEER_LINK_NATIVE_VLAN": "false", + "ESR_OPTION": "PBR", + "EXTRA_CONF_INTRA_LINKS": "", + "EXTRA_CONF_LEAF": "", + "EXTRA_CONF_SPINE": "", + "EXTRA_CONF_TOR": "", + "EXT_FABRIC_TYPE": "", + "FABRIC_INTERFACE_TYPE": "p2p", + "FABRIC_MTU": "9216", + "FABRIC_MTU_PREV": "9216", + "FABRIC_NAME": "f1", + "FABRIC_TYPE": "Switch_Fabric", + "FABRIC_VPC_DOMAIN_ID": "", + "FABRIC_VPC_DOMAIN_ID_PREV": "", + "FABRIC_VPC_QOS": "false", + "FABRIC_VPC_QOS_POLICY_NAME": "spine_qos_for_fabric_vpc_peering", + "FEATURE_PTP": "false", + "FEATURE_PTP_INTERNAL": "false", + "FF": "Easy_Fabric", + "GRFIELD_DEBUG_FLAG": "Disable", + "HD_TIME": "180", + "HOST_INTF_ADMIN_STATE": "true", + "IBGP_PEER_TEMPLATE": "", + "IBGP_PEER_TEMPLATE_LEAF": "", + "INBAND_DHCP_SERVERS": "", + "INBAND_MGMT": "false", + "INBAND_MGMT_PREV": "false", + "ISIS_AREA_NUM": "0001", + "ISIS_AREA_NUM_PREV": "", + "ISIS_AUTH_ENABLE": "false", + "ISIS_AUTH_KEY": "", + "ISIS_AUTH_KEYCHAIN_KEY_ID": "", + "ISIS_AUTH_KEYCHAIN_NAME": "", + "ISIS_LEVEL": "level-2", + "ISIS_OVERLOAD_ELAPSE_TIME": "", + "ISIS_OVERLOAD_ENABLE": "", + "ISIS_P2P_ENABLE": "", + "L2_HOST_INTF_MTU": "9216", + "L2_HOST_INTF_MTU_PREV": "9216", + "L2_SEGMENT_ID_RANGE": "30000-49000", + "L3VNI_MCAST_GROUP": "", + "L3_PARTITION_ID_RANGE": "50000-59000", + "LINK_STATE_ROUTING": "ospf", + "LINK_STATE_ROUTING_TAG": "UNDERLAY", + "LINK_STATE_ROUTING_TAG_PREV": "UNDERLAY", + "LOOPBACK0_IPV6_RANGE": "", + "LOOPBACK0_IP_RANGE": "10.2.0.0/22", + "LOOPBACK1_IPV6_RANGE": "", + "LOOPBACK1_IP_RANGE": "10.3.0.0/22", + "MACSEC_ALGORITHM": "", + "MACSEC_CIPHER_SUITE": "", + "MACSEC_FALLBACK_ALGORITHM": "", + "MACSEC_FALLBACK_KEY_STRING": "", + "MACSEC_KEY_STRING": "", + "MACSEC_REPORT_TIMER": "", + "MGMT_GW": "", + "MGMT_GW_INTERNAL": "", + "MGMT_PREFIX": "", + "MGMT_PREFIX_INTERNAL": "", + "MGMT_V6PREFIX": "", + "MGMT_V6PREFIX_INTERNAL": "", + "MPLS_HANDOFF": "false", + "MPLS_ISIS_AREA_NUM": "0001", + "MPLS_ISIS_AREA_NUM_PREV": "", + "MPLS_LB_ID": "", + "MPLS_LOOPBACK_IP_RANGE": "", + "MSO_CONNECTIVITY_DEPLOYED": "", + "MSO_CONTROLER_ID": "", + "MSO_SITE_GROUP_NAME": "", + "MSO_SITE_ID": "", + "MST_INSTANCE_RANGE": "", + "MULTICAST_GROUP_SUBNET": "239.1.1.0/25", + "NETFLOW_EXPORTER_LIST": "", + "NETFLOW_MONITOR_LIST": "", + "NETFLOW_RECORD_LIST": "", + "NETWORK_VLAN_RANGE": "2300-2999", + "NTP_SERVER_IP_LIST": "", + "NTP_SERVER_VRF": "", + "NVE_LB_ID": "1", + "NXAPI_HTTPS_PORT": "443", + "NXAPI_HTTP_PORT": "80", + "NXC_DEST_VRF": "", + "NXC_PROXY_PORT": "8080", + "NXC_PROXY_SERVER": "", + "NXC_SRC_INTF": "", + "OBJECT_TRACKING_NUMBER_RANGE": "100-299", + "OSPF_AREA_ID": "0.0.0.0", + "OSPF_AUTH_ENABLE": "false", + "OSPF_AUTH_KEY": "", + "OSPF_AUTH_KEY_ID": "", + "OVERLAY_MODE": "cli", + "OVERLAY_MODE_PREV": "cli", + "OVERWRITE_GLOBAL_NXC": "false", + "PER_VRF_LOOPBACK_AUTO_PROVISION": "false", + "PER_VRF_LOOPBACK_AUTO_PROVISION_PREV": "false", + "PER_VRF_LOOPBACK_AUTO_PROVISION_V6": "false", + "PER_VRF_LOOPBACK_AUTO_PROVISION_V6_PREV": "false", + "PER_VRF_LOOPBACK_IP_RANGE": "", + "PER_VRF_LOOPBACK_IP_RANGE_V6": "", + "PHANTOM_RP_LB_ID1": "", + "PHANTOM_RP_LB_ID2": "", + "PHANTOM_RP_LB_ID3": "", + "PHANTOM_RP_LB_ID4": "", + "PIM_HELLO_AUTH_ENABLE": "false", + "PIM_HELLO_AUTH_KEY": "", + "PM_ENABLE": "false", + "PM_ENABLE_PREV": "false", + "POWER_REDUNDANCY_MODE": "ps-redundant", + "PREMSO_PARENT_FABRIC": "", + "PTP_DOMAIN_ID": "", + "PTP_LB_ID": "", + "REPLICATION_MODE": "Multicast", + "ROUTER_ID_RANGE": "", + "ROUTE_MAP_SEQUENCE_NUMBER_RANGE": "1-65534", + "RP_COUNT": "2", + "RP_LB_ID": "254", + "RP_MODE": "asm", + "RR_COUNT": "2", + "SEED_SWITCH_CORE_INTERFACES": "", + "SERVICE_NETWORK_VLAN_RANGE": "3000-3199", + "SGT_ID_RANGE": "", + "SGT_NAME_PREFIX": "", + "SGT_PREPROVISION": "", + "SITE_ID": "65001", + "SLA_ID_RANGE": "10000-19999", + "SNMP_SERVER_HOST_TRAP": "true", + "SPINE_COUNT": "0", + "SPINE_SWITCH_CORE_INTERFACES": "", + "SSPINE_ADD_DEL_DEBUG_FLAG": "Disable", + "SSPINE_COUNT": "0", + "STATIC_UNDERLAY_IP_ALLOC": "false", + "STP_BRIDGE_PRIORITY": "", + "STP_ROOT_OPTION": "unmanaged", + "STP_VLAN_RANGE": "", + "STRICT_CC_MODE": "false", + "SUBINTERFACE_RANGE": "2-511", + "SUBNET_RANGE": "10.4.0.0/16", + "SUBNET_TARGET_MASK": "30", + "SYSLOG_SERVER_IP_LIST": "", + "SYSLOG_SERVER_VRF": "", + "SYSLOG_SEV": "", + "TCAM_ALLOCATION": "true", + "TOPDOWN_CONFIG_RM_TRACKING": "notstarted", + "UNDERLAY_IS_V6": "false", + "UNNUM_BOOTSTRAP_LB_ID": "", + "UNNUM_DHCP_END": "", + "UNNUM_DHCP_END_INTERNAL": "", + "UNNUM_DHCP_START": "", + "UNNUM_DHCP_START_INTERNAL": "", + "UPGRADE_FROM_VERSION": "", + "USE_LINK_LOCAL": "true", + "V6_SUBNET_RANGE": "", + "V6_SUBNET_TARGET_MASK": "126", + "VPC_AUTO_RECOVERY_TIME": "360", + "VPC_DELAY_RESTORE": "150", + "VPC_DELAY_RESTORE_TIME": "60", + "VPC_DOMAIN_ID_RANGE": "1-1000", + "VPC_ENABLE_IPv6_ND_SYNC": "true", + "VPC_PEER_KEEP_ALIVE_OPTION": "management", + "VPC_PEER_LINK_PO": "500", + "VPC_PEER_LINK_VLAN": "3600", + "VRF_LITE_AUTOCONFIG": "Manual", + "VRF_VLAN_RANGE": "2000-2299", + "abstract_anycast_rp": "anycast_rp", + "abstract_bgp": "base_bgp", + "abstract_bgp_neighbor": "evpn_bgp_rr_neighbor", + "abstract_bgp_rr": "evpn_bgp_rr", + "abstract_dhcp": "base_dhcp", + "abstract_extra_config_bootstrap": "extra_config_bootstrap_11_1", + "abstract_extra_config_leaf": "extra_config_leaf", + "abstract_extra_config_spine": "extra_config_spine", + "abstract_extra_config_tor": "extra_config_tor", + "abstract_feature_leaf": "base_feature_leaf_upg", + "abstract_feature_spine": "base_feature_spine_upg", + "abstract_isis": "base_isis_level2", + "abstract_isis_interface": "isis_interface", + "abstract_loopback_interface": "int_fabric_loopback_11_1", + "abstract_multicast": "base_multicast_11_1", + "abstract_ospf": "base_ospf", + "abstract_ospf_interface": "ospf_interface_11_1", + "abstract_pim_interface": "pim_interface", + "abstract_route_map": "route_map", + "abstract_routed_host": "int_routed_host", + "abstract_trunk_host": "int_trunk_host", + "abstract_vlan_interface": "int_fabric_vlan_11_1", + "abstract_vpc_domain": "base_vpc_domain_11_1", + "default_network": "Default_Network_Universal", + "default_pvlan_sec_network": "", + "default_vrf": "Default_VRF_Universal", + "enableRealTimeBackup": "", + "enableScheduledBackup": "", + "network_extension_template": "Default_Network_Extension_Universal", + "scheduledTime": "", + "temp_anycast_gateway": "anycast_gateway", + "temp_vpc_domain_mgmt": "vpc_domain_mgmt", + "temp_vpc_peer_link": "int_vpc_peer_link_po", + "vrf_extension_template": "Default_VRF_Extension_Universal" + }, + "operStatus": "HEALTHY", + "provisionMode": "DCNMTopDown", + "replicationMode": "Multicast", + "siteId": "65001", + "templateName": "Easy_Fabric", + "vrfExtensionTemplate": "Default_VRF_Extension_Universal", + "vrfTemplate": "Default_VRF_Universal" + } + ], + "MESSAGE": "OK", + "METHOD": "GET", + "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/rest/control/fabrics", + "RETURN_CODE": 200 + }, + "test_fabric_delete_00051a": { + "DATA": [ + { + "asn": "65001", + "createdOn": 1711411093680, + "deviceType": "n9k", + "fabricId": "FABRIC-2", + "fabricName": "f1", + "fabricTechnology": "VXLANFabric", + "fabricTechnologyFriendly": "VXLAN EVPN", + "fabricType": "Switch_Fabric", + "fabricTypeFriendly": "Switch Fabric", + "id": 2, + "modifiedOn": 1711411096857, + "networkExtensionTemplate": "Default_Network_Extension_Universal", + "networkTemplate": "Default_Network_Universal", + "nvPairs": { + "AAA_REMOTE_IP_ENABLED": "false", + "AAA_SERVER_CONF": "", + "ACTIVE_MIGRATION": "false", + "ADVERTISE_PIP_BGP": "false", + "ADVERTISE_PIP_ON_BORDER": "true", + "AGENT_INTF": "eth0", + "AI_ML_QOS_POLICY": "AI_Fabric_QOS_400G", + "ALLOW_NXC": "true", + "ALLOW_NXC_PREV": "true", + "ANYCAST_BGW_ADVERTISE_PIP": "false", + "ANYCAST_GW_MAC": "2020.0000.00aa", + "ANYCAST_LB_ID": "", + "ANYCAST_RP_IP_RANGE": "10.254.254.0/24", + "ANYCAST_RP_IP_RANGE_INTERNAL": "", + "AUTO_SYMMETRIC_DEFAULT_VRF": "false", + "AUTO_SYMMETRIC_VRF_LITE": "false", + "AUTO_UNIQUE_VRF_LITE_IP_PREFIX": "false", + "AUTO_UNIQUE_VRF_LITE_IP_PREFIX_PREV": "false", + "AUTO_VRFLITE_IFC_DEFAULT_VRF": "false", + "BANNER": "", + "BFD_AUTH_ENABLE": "false", + "BFD_AUTH_KEY": "", + "BFD_AUTH_KEY_ID": "", + "BFD_ENABLE": "false", + "BFD_ENABLE_PREV": "", + "BFD_IBGP_ENABLE": "false", + "BFD_ISIS_ENABLE": "false", + "BFD_OSPF_ENABLE": "false", + "BFD_PIM_ENABLE": "false", + "BGP_AS": "65001", + "BGP_AS_PREV": "65001", + "BGP_AUTH_ENABLE": "false", + "BGP_AUTH_KEY": "", + "BGP_AUTH_KEY_TYPE": "3", + "BGP_LB_ID": "0", + "BOOTSTRAP_CONF": "", + "BOOTSTRAP_ENABLE": "false", + "BOOTSTRAP_ENABLE_PREV": "false", + "BOOTSTRAP_MULTISUBNET": "#Scope_Start_IP, Scope_End_IP, Scope_Default_Gateway, Scope_Subnet_Prefix", + "BOOTSTRAP_MULTISUBNET_INTERNAL": "", + "BRFIELD_DEBUG_FLAG": "Disable", + "BROWNFIELD_NETWORK_NAME_FORMAT": "Auto_Net_VNI$$VNI$$_VLAN$$VLAN_ID$$", + "BROWNFIELD_SKIP_OVERLAY_NETWORK_ATTACHMENTS": "false", + "CDP_ENABLE": "false", + "COPP_POLICY": "strict", + "DCI_SUBNET_RANGE": "10.33.0.0/16", + "DCI_SUBNET_TARGET_MASK": "30", + "DEAFULT_QUEUING_POLICY_CLOUDSCALE": "queuing_policy_default_8q_cloudscale", + "DEAFULT_QUEUING_POLICY_OTHER": "queuing_policy_default_other", + "DEAFULT_QUEUING_POLICY_R_SERIES": "queuing_policy_default_r_series", + "DEFAULT_VRF_REDIS_BGP_RMAP": "", + "DEPLOYMENT_FREEZE": "false", + "DHCP_ENABLE": "false", + "DHCP_END": "", + "DHCP_END_INTERNAL": "", + "DHCP_IPV6_ENABLE": "", + "DHCP_IPV6_ENABLE_INTERNAL": "", + "DHCP_START": "", + "DHCP_START_INTERNAL": "", + "DNS_SERVER_IP_LIST": "", + "DNS_SERVER_VRF": "", + "DOMAIN_NAME_INTERNAL": "", + "ENABLE_AAA": "false", + "ENABLE_AGENT": "false", + "ENABLE_AI_ML_QOS_POLICY": "false", + "ENABLE_AI_ML_QOS_POLICY_FLAP": "false", + "ENABLE_DEFAULT_QUEUING_POLICY": "false", + "ENABLE_EVPN": "true", + "ENABLE_FABRIC_VPC_DOMAIN_ID": "false", + "ENABLE_FABRIC_VPC_DOMAIN_ID_PREV": "false", + "ENABLE_L3VNI_NO_VLAN": "false", + "ENABLE_MACSEC": "false", + "ENABLE_NETFLOW": "false", + "ENABLE_NETFLOW_PREV": "false", + "ENABLE_NGOAM": "true", + "ENABLE_NXAPI": "true", + "ENABLE_NXAPI_HTTP": "true", + "ENABLE_PBR": "false", + "ENABLE_PVLAN": "false", + "ENABLE_PVLAN_PREV": "false", + "ENABLE_SGT": "false", + "ENABLE_SGT_PREV": "false", + "ENABLE_TENANT_DHCP": "true", + "ENABLE_TRM": "false", + "ENABLE_VPC_PEER_LINK_NATIVE_VLAN": "false", + "ESR_OPTION": "PBR", + "EXTRA_CONF_INTRA_LINKS": "", + "EXTRA_CONF_LEAF": "", + "EXTRA_CONF_SPINE": "", + "EXTRA_CONF_TOR": "", + "EXT_FABRIC_TYPE": "", + "FABRIC_INTERFACE_TYPE": "p2p", + "FABRIC_MTU": "9216", + "FABRIC_MTU_PREV": "9216", + "FABRIC_NAME": "f1", + "FABRIC_TYPE": "Switch_Fabric", + "FABRIC_VPC_DOMAIN_ID": "", + "FABRIC_VPC_DOMAIN_ID_PREV": "", + "FABRIC_VPC_QOS": "false", + "FABRIC_VPC_QOS_POLICY_NAME": "spine_qos_for_fabric_vpc_peering", + "FEATURE_PTP": "false", + "FEATURE_PTP_INTERNAL": "false", + "FF": "Easy_Fabric", + "GRFIELD_DEBUG_FLAG": "Disable", + "HD_TIME": "180", + "HOST_INTF_ADMIN_STATE": "true", + "IBGP_PEER_TEMPLATE": "", + "IBGP_PEER_TEMPLATE_LEAF": "", + "INBAND_DHCP_SERVERS": "", + "INBAND_MGMT": "false", + "INBAND_MGMT_PREV": "false", + "ISIS_AREA_NUM": "0001", + "ISIS_AREA_NUM_PREV": "", + "ISIS_AUTH_ENABLE": "false", + "ISIS_AUTH_KEY": "", + "ISIS_AUTH_KEYCHAIN_KEY_ID": "", + "ISIS_AUTH_KEYCHAIN_NAME": "", + "ISIS_LEVEL": "level-2", + "ISIS_OVERLOAD_ELAPSE_TIME": "", + "ISIS_OVERLOAD_ENABLE": "", + "ISIS_P2P_ENABLE": "", + "L2_HOST_INTF_MTU": "9216", + "L2_HOST_INTF_MTU_PREV": "9216", + "L2_SEGMENT_ID_RANGE": "30000-49000", + "L3VNI_MCAST_GROUP": "", + "L3_PARTITION_ID_RANGE": "50000-59000", + "LINK_STATE_ROUTING": "ospf", + "LINK_STATE_ROUTING_TAG": "UNDERLAY", + "LINK_STATE_ROUTING_TAG_PREV": "UNDERLAY", + "LOOPBACK0_IPV6_RANGE": "", + "LOOPBACK0_IP_RANGE": "10.2.0.0/22", + "LOOPBACK1_IPV6_RANGE": "", + "LOOPBACK1_IP_RANGE": "10.3.0.0/22", + "MACSEC_ALGORITHM": "", + "MACSEC_CIPHER_SUITE": "", + "MACSEC_FALLBACK_ALGORITHM": "", + "MACSEC_FALLBACK_KEY_STRING": "", + "MACSEC_KEY_STRING": "", + "MACSEC_REPORT_TIMER": "", + "MGMT_GW": "", + "MGMT_GW_INTERNAL": "", + "MGMT_PREFIX": "", + "MGMT_PREFIX_INTERNAL": "", + "MGMT_V6PREFIX": "", + "MGMT_V6PREFIX_INTERNAL": "", + "MPLS_HANDOFF": "false", + "MPLS_ISIS_AREA_NUM": "0001", + "MPLS_ISIS_AREA_NUM_PREV": "", + "MPLS_LB_ID": "", + "MPLS_LOOPBACK_IP_RANGE": "", + "MSO_CONNECTIVITY_DEPLOYED": "", + "MSO_CONTROLER_ID": "", + "MSO_SITE_GROUP_NAME": "", + "MSO_SITE_ID": "", + "MST_INSTANCE_RANGE": "", + "MULTICAST_GROUP_SUBNET": "239.1.1.0/25", + "NETFLOW_EXPORTER_LIST": "", + "NETFLOW_MONITOR_LIST": "", + "NETFLOW_RECORD_LIST": "", + "NETWORK_VLAN_RANGE": "2300-2999", + "NTP_SERVER_IP_LIST": "", + "NTP_SERVER_VRF": "", + "NVE_LB_ID": "1", + "NXAPI_HTTPS_PORT": "443", + "NXAPI_HTTP_PORT": "80", + "NXC_DEST_VRF": "", + "NXC_PROXY_PORT": "8080", + "NXC_PROXY_SERVER": "", + "NXC_SRC_INTF": "", + "OBJECT_TRACKING_NUMBER_RANGE": "100-299", + "OSPF_AREA_ID": "0.0.0.0", + "OSPF_AUTH_ENABLE": "false", + "OSPF_AUTH_KEY": "", + "OSPF_AUTH_KEY_ID": "", + "OVERLAY_MODE": "cli", + "OVERLAY_MODE_PREV": "cli", + "OVERWRITE_GLOBAL_NXC": "false", + "PER_VRF_LOOPBACK_AUTO_PROVISION": "false", + "PER_VRF_LOOPBACK_AUTO_PROVISION_PREV": "false", + "PER_VRF_LOOPBACK_AUTO_PROVISION_V6": "false", + "PER_VRF_LOOPBACK_AUTO_PROVISION_V6_PREV": "false", + "PER_VRF_LOOPBACK_IP_RANGE": "", + "PER_VRF_LOOPBACK_IP_RANGE_V6": "", + "PHANTOM_RP_LB_ID1": "", + "PHANTOM_RP_LB_ID2": "", + "PHANTOM_RP_LB_ID3": "", + "PHANTOM_RP_LB_ID4": "", + "PIM_HELLO_AUTH_ENABLE": "false", + "PIM_HELLO_AUTH_KEY": "", + "PM_ENABLE": "false", + "PM_ENABLE_PREV": "false", + "POWER_REDUNDANCY_MODE": "ps-redundant", + "PREMSO_PARENT_FABRIC": "", + "PTP_DOMAIN_ID": "", + "PTP_LB_ID": "", + "REPLICATION_MODE": "Multicast", + "ROUTER_ID_RANGE": "", + "ROUTE_MAP_SEQUENCE_NUMBER_RANGE": "1-65534", + "RP_COUNT": "2", + "RP_LB_ID": "254", + "RP_MODE": "asm", + "RR_COUNT": "2", + "SEED_SWITCH_CORE_INTERFACES": "", + "SERVICE_NETWORK_VLAN_RANGE": "3000-3199", + "SGT_ID_RANGE": "", + "SGT_NAME_PREFIX": "", + "SGT_PREPROVISION": "", + "SITE_ID": "65001", + "SLA_ID_RANGE": "10000-19999", + "SNMP_SERVER_HOST_TRAP": "true", + "SPINE_COUNT": "0", + "SPINE_SWITCH_CORE_INTERFACES": "", + "SSPINE_ADD_DEL_DEBUG_FLAG": "Disable", + "SSPINE_COUNT": "0", + "STATIC_UNDERLAY_IP_ALLOC": "false", + "STP_BRIDGE_PRIORITY": "", + "STP_ROOT_OPTION": "unmanaged", + "STP_VLAN_RANGE": "", + "STRICT_CC_MODE": "false", + "SUBINTERFACE_RANGE": "2-511", + "SUBNET_RANGE": "10.4.0.0/16", + "SUBNET_TARGET_MASK": "30", + "SYSLOG_SERVER_IP_LIST": "", + "SYSLOG_SERVER_VRF": "", + "SYSLOG_SEV": "", + "TCAM_ALLOCATION": "true", + "TOPDOWN_CONFIG_RM_TRACKING": "notstarted", + "UNDERLAY_IS_V6": "false", + "UNNUM_BOOTSTRAP_LB_ID": "", + "UNNUM_DHCP_END": "", + "UNNUM_DHCP_END_INTERNAL": "", + "UNNUM_DHCP_START": "", + "UNNUM_DHCP_START_INTERNAL": "", + "UPGRADE_FROM_VERSION": "", + "USE_LINK_LOCAL": "true", + "V6_SUBNET_RANGE": "", + "V6_SUBNET_TARGET_MASK": "126", + "VPC_AUTO_RECOVERY_TIME": "360", + "VPC_DELAY_RESTORE": "150", + "VPC_DELAY_RESTORE_TIME": "60", + "VPC_DOMAIN_ID_RANGE": "1-1000", + "VPC_ENABLE_IPv6_ND_SYNC": "true", + "VPC_PEER_KEEP_ALIVE_OPTION": "management", + "VPC_PEER_LINK_PO": "500", + "VPC_PEER_LINK_VLAN": "3600", + "VRF_LITE_AUTOCONFIG": "Manual", + "VRF_VLAN_RANGE": "2000-2299", + "abstract_anycast_rp": "anycast_rp", + "abstract_bgp": "base_bgp", + "abstract_bgp_neighbor": "evpn_bgp_rr_neighbor", + "abstract_bgp_rr": "evpn_bgp_rr", + "abstract_dhcp": "base_dhcp", + "abstract_extra_config_bootstrap": "extra_config_bootstrap_11_1", + "abstract_extra_config_leaf": "extra_config_leaf", + "abstract_extra_config_spine": "extra_config_spine", + "abstract_extra_config_tor": "extra_config_tor", + "abstract_feature_leaf": "base_feature_leaf_upg", + "abstract_feature_spine": "base_feature_spine_upg", + "abstract_isis": "base_isis_level2", + "abstract_isis_interface": "isis_interface", + "abstract_loopback_interface": "int_fabric_loopback_11_1", + "abstract_multicast": "base_multicast_11_1", + "abstract_ospf": "base_ospf", + "abstract_ospf_interface": "ospf_interface_11_1", + "abstract_pim_interface": "pim_interface", + "abstract_route_map": "route_map", + "abstract_routed_host": "int_routed_host", + "abstract_trunk_host": "int_trunk_host", + "abstract_vlan_interface": "int_fabric_vlan_11_1", + "abstract_vpc_domain": "base_vpc_domain_11_1", + "default_network": "Default_Network_Universal", + "default_pvlan_sec_network": "", + "default_vrf": "Default_VRF_Universal", + "enableRealTimeBackup": "", + "enableScheduledBackup": "", + "network_extension_template": "Default_Network_Extension_Universal", + "scheduledTime": "", + "temp_anycast_gateway": "anycast_gateway", + "temp_vpc_domain_mgmt": "vpc_domain_mgmt", + "temp_vpc_peer_link": "int_vpc_peer_link_po", + "vrf_extension_template": "Default_VRF_Extension_Universal" + }, + "operStatus": "HEALTHY", + "provisionMode": "DCNMTopDown", + "replicationMode": "Multicast", + "siteId": "65001", + "templateName": "Easy_Fabric", + "vrfExtensionTemplate": "Default_VRF_Extension_Universal", + "vrfTemplate": "Default_VRF_Universal" + } + ], + "MESSAGE": "OK", + "METHOD": "GET", + "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/rest/control/fabrics", + "RETURN_CODE": 200 + }, + "test_fabric_details_by_name_v2_00200a": { + "TEST_NOTES": [ + "Verify property return values.", + "DATA contains one fabric dict.", + "RETURN_CODE == 200." + ], + "DATA": [ + { + "asn": "65001", + "createdOn": 1711411093680, + "deviceType": "n9k", + "fabricId": "FABRIC-2", + "fabricName": "f1", + "fabricTechnology": "VXLANFabric", + "fabricTechnologyFriendly": "VXLAN EVPN", + "fabricType": "Switch_Fabric", + "fabricTypeFriendly": "Switch Fabric", + "id": 2, + "modifiedOn": 1711411096857, + "networkExtensionTemplate": "Default_Network_Extension_Universal", + "networkTemplate": "Default_Network_Universal", + "nvPairs": { + "AAA_REMOTE_IP_ENABLED": "false", + "AAA_SERVER_CONF": "", + "ACTIVE_MIGRATION": "false", + "ADVERTISE_PIP_BGP": "false", + "ADVERTISE_PIP_ON_BORDER": "true", + "AGENT_INTF": "eth0", + "AI_ML_QOS_POLICY": "AI_Fabric_QOS_400G", + "ALLOW_NXC": "true", + "ALLOW_NXC_PREV": "true", + "ANYCAST_BGW_ADVERTISE_PIP": "false", + "ANYCAST_GW_MAC": "2020.0000.00aa", + "ANYCAST_LB_ID": "", + "ANYCAST_RP_IP_RANGE": "10.254.254.0/24", + "ANYCAST_RP_IP_RANGE_INTERNAL": "", + "AUTO_SYMMETRIC_DEFAULT_VRF": "false", + "AUTO_SYMMETRIC_VRF_LITE": "false", + "AUTO_UNIQUE_VRF_LITE_IP_PREFIX": "false", + "AUTO_UNIQUE_VRF_LITE_IP_PREFIX_PREV": "false", + "AUTO_VRFLITE_IFC_DEFAULT_VRF": "false", + "BANNER": "", + "BFD_AUTH_ENABLE": "false", + "BFD_AUTH_KEY": "", + "BFD_AUTH_KEY_ID": "", + "BFD_ENABLE": "false", + "BFD_ENABLE_PREV": "", + "BFD_IBGP_ENABLE": "false", + "BFD_ISIS_ENABLE": "false", + "BFD_OSPF_ENABLE": "false", + "BFD_PIM_ENABLE": "false", + "BGP_AS": "65001", + "BGP_AS_PREV": "65001", + "BGP_AUTH_ENABLE": "false", + "BGP_AUTH_KEY": "", + "BGP_AUTH_KEY_TYPE": "3", + "BGP_LB_ID": "0", + "BOOTSTRAP_CONF": "", + "BOOTSTRAP_ENABLE": "false", + "BOOTSTRAP_ENABLE_PREV": "false", + "BOOTSTRAP_MULTISUBNET": "#Scope_Start_IP, Scope_End_IP, Scope_Default_Gateway, Scope_Subnet_Prefix", + "BOOTSTRAP_MULTISUBNET_INTERNAL": "", + "BRFIELD_DEBUG_FLAG": "Disable", + "BROWNFIELD_NETWORK_NAME_FORMAT": "Auto_Net_VNI$$VNI$$_VLAN$$VLAN_ID$$", + "BROWNFIELD_SKIP_OVERLAY_NETWORK_ATTACHMENTS": "false", + "CDP_ENABLE": "false", + "COPP_POLICY": "strict", + "DCI_SUBNET_RANGE": "10.33.0.0/16", + "DCI_SUBNET_TARGET_MASK": "30", + "DEAFULT_QUEUING_POLICY_CLOUDSCALE": "queuing_policy_default_8q_cloudscale", + "DEAFULT_QUEUING_POLICY_OTHER": "queuing_policy_default_other", + "DEAFULT_QUEUING_POLICY_R_SERIES": "queuing_policy_default_r_series", + "DEFAULT_VRF_REDIS_BGP_RMAP": "", + "DEPLOYMENT_FREEZE": "false", + "DHCP_ENABLE": "false", + "DHCP_END": "", + "DHCP_END_INTERNAL": "", + "DHCP_IPV6_ENABLE": "", + "DHCP_IPV6_ENABLE_INTERNAL": "", + "DHCP_START": "", + "DHCP_START_INTERNAL": "", + "DNS_SERVER_IP_LIST": "", + "DNS_SERVER_VRF": "", + "DOMAIN_NAME_INTERNAL": "", + "ENABLE_AAA": "false", + "ENABLE_AGENT": "false", + "ENABLE_AI_ML_QOS_POLICY": "false", + "ENABLE_AI_ML_QOS_POLICY_FLAP": "false", + "ENABLE_DEFAULT_QUEUING_POLICY": "false", + "ENABLE_EVPN": "true", + "ENABLE_FABRIC_VPC_DOMAIN_ID": "false", + "ENABLE_FABRIC_VPC_DOMAIN_ID_PREV": "false", + "ENABLE_L3VNI_NO_VLAN": "false", + "ENABLE_MACSEC": "false", + "ENABLE_NETFLOW": "false", + "ENABLE_NETFLOW_PREV": "false", + "ENABLE_NGOAM": "true", + "ENABLE_NXAPI": "true", + "ENABLE_NXAPI_HTTP": "true", + "ENABLE_PBR": "false", + "ENABLE_PVLAN": "false", + "ENABLE_PVLAN_PREV": "false", + "ENABLE_SGT": "false", + "ENABLE_SGT_PREV": "false", + "ENABLE_TENANT_DHCP": "true", + "ENABLE_TRM": "false", + "ENABLE_VPC_PEER_LINK_NATIVE_VLAN": "false", + "ESR_OPTION": "PBR", + "EXTRA_CONF_INTRA_LINKS": "", + "EXTRA_CONF_LEAF": "", + "EXTRA_CONF_SPINE": "", + "EXTRA_CONF_TOR": "", + "EXT_FABRIC_TYPE": "", + "FABRIC_INTERFACE_TYPE": "p2p", + "FABRIC_MTU": "9216", + "FABRIC_MTU_PREV": "9216", + "FABRIC_NAME": "f1", + "FABRIC_TYPE": "Switch_Fabric", + "FABRIC_VPC_DOMAIN_ID": "", + "FABRIC_VPC_DOMAIN_ID_PREV": "", + "FABRIC_VPC_QOS": "false", + "FABRIC_VPC_QOS_POLICY_NAME": "spine_qos_for_fabric_vpc_peering", + "FEATURE_PTP": "false", + "FEATURE_PTP_INTERNAL": "false", + "FF": "Easy_Fabric", + "GRFIELD_DEBUG_FLAG": "Disable", + "HD_TIME": "180", + "HOST_INTF_ADMIN_STATE": "true", + "IBGP_PEER_TEMPLATE": "", + "IBGP_PEER_TEMPLATE_LEAF": "", + "INBAND_DHCP_SERVERS": "", + "INBAND_MGMT": "false", + "INBAND_MGMT_PREV": "false", + "ISIS_AREA_NUM": "0001", + "ISIS_AREA_NUM_PREV": "", + "ISIS_AUTH_ENABLE": "false", + "ISIS_AUTH_KEY": "", + "ISIS_AUTH_KEYCHAIN_KEY_ID": "", + "ISIS_AUTH_KEYCHAIN_NAME": "", + "ISIS_LEVEL": "level-2", + "ISIS_OVERLOAD_ELAPSE_TIME": "", + "ISIS_OVERLOAD_ENABLE": "", + "ISIS_P2P_ENABLE": "", + "L2_HOST_INTF_MTU": "9216", + "L2_HOST_INTF_MTU_PREV": "9216", + "L2_SEGMENT_ID_RANGE": "30000-49000", + "L3VNI_MCAST_GROUP": "", + "L3_PARTITION_ID_RANGE": "50000-59000", + "LINK_STATE_ROUTING": "ospf", + "LINK_STATE_ROUTING_TAG": "UNDERLAY", + "LINK_STATE_ROUTING_TAG_PREV": "UNDERLAY", + "LOOPBACK0_IPV6_RANGE": "", + "LOOPBACK0_IP_RANGE": "10.2.0.0/22", + "LOOPBACK1_IPV6_RANGE": "", + "LOOPBACK1_IP_RANGE": "10.3.0.0/22", + "MACSEC_ALGORITHM": "", + "MACSEC_CIPHER_SUITE": "", + "MACSEC_FALLBACK_ALGORITHM": "", + "MACSEC_FALLBACK_KEY_STRING": "", + "MACSEC_KEY_STRING": "", + "MACSEC_REPORT_TIMER": "", + "MGMT_GW": "", + "MGMT_GW_INTERNAL": "", + "MGMT_PREFIX": "", + "MGMT_PREFIX_INTERNAL": "", + "MGMT_V6PREFIX": "", + "MGMT_V6PREFIX_INTERNAL": "", + "MPLS_HANDOFF": "false", + "MPLS_ISIS_AREA_NUM": "0001", + "MPLS_ISIS_AREA_NUM_PREV": "", + "MPLS_LB_ID": "", + "MPLS_LOOPBACK_IP_RANGE": "", + "MSO_CONNECTIVITY_DEPLOYED": "", + "MSO_CONTROLER_ID": "", + "MSO_SITE_GROUP_NAME": "", + "MSO_SITE_ID": "", + "MST_INSTANCE_RANGE": "", + "MULTICAST_GROUP_SUBNET": "239.1.1.0/25", + "NETFLOW_EXPORTER_LIST": "", + "NETFLOW_MONITOR_LIST": "", + "NETFLOW_RECORD_LIST": "", + "NETWORK_VLAN_RANGE": "2300-2999", + "NTP_SERVER_IP_LIST": "", + "NTP_SERVER_VRF": "", + "NVE_LB_ID": "1", + "NXAPI_HTTPS_PORT": "443", + "NXAPI_HTTP_PORT": "80", + "NXC_DEST_VRF": "", + "NXC_PROXY_PORT": "8080", + "NXC_PROXY_SERVER": "", + "NXC_SRC_INTF": "", + "OBJECT_TRACKING_NUMBER_RANGE": "100-299", + "OSPF_AREA_ID": "0.0.0.0", + "OSPF_AUTH_ENABLE": "false", + "OSPF_AUTH_KEY": "", + "OSPF_AUTH_KEY_ID": "", + "OVERLAY_MODE": "cli", + "OVERLAY_MODE_PREV": "cli", + "OVERWRITE_GLOBAL_NXC": "false", + "PER_VRF_LOOPBACK_AUTO_PROVISION": "false", + "PER_VRF_LOOPBACK_AUTO_PROVISION_PREV": "false", + "PER_VRF_LOOPBACK_AUTO_PROVISION_V6": "false", + "PER_VRF_LOOPBACK_AUTO_PROVISION_V6_PREV": "false", + "PER_VRF_LOOPBACK_IP_RANGE": "", + "PER_VRF_LOOPBACK_IP_RANGE_V6": "", + "PHANTOM_RP_LB_ID1": "", + "PHANTOM_RP_LB_ID2": "", + "PHANTOM_RP_LB_ID3": "", + "PHANTOM_RP_LB_ID4": "", + "PIM_HELLO_AUTH_ENABLE": "false", + "PIM_HELLO_AUTH_KEY": "", + "PM_ENABLE": "false", + "PM_ENABLE_PREV": "false", + "POWER_REDUNDANCY_MODE": "ps-redundant", + "PREMSO_PARENT_FABRIC": "", + "PTP_DOMAIN_ID": "", + "PTP_LB_ID": "", + "REPLICATION_MODE": "Multicast", + "ROUTER_ID_RANGE": "", + "ROUTE_MAP_SEQUENCE_NUMBER_RANGE": "1-65534", + "RP_COUNT": "2", + "RP_LB_ID": "254", + "RP_MODE": "asm", + "RR_COUNT": "2", + "SEED_SWITCH_CORE_INTERFACES": "", + "SERVICE_NETWORK_VLAN_RANGE": "3000-3199", + "SGT_ID_RANGE": "", + "SGT_NAME_PREFIX": "", + "SGT_PREPROVISION": "", + "SITE_ID": "65001", + "SLA_ID_RANGE": "10000-19999", + "SNMP_SERVER_HOST_TRAP": "true", + "SPINE_COUNT": "0", + "SPINE_SWITCH_CORE_INTERFACES": "", + "SSPINE_ADD_DEL_DEBUG_FLAG": "Disable", + "SSPINE_COUNT": "0", + "STATIC_UNDERLAY_IP_ALLOC": "false", + "STP_BRIDGE_PRIORITY": "", + "STP_ROOT_OPTION": "unmanaged", + "STP_VLAN_RANGE": "", + "STRICT_CC_MODE": "false", + "SUBINTERFACE_RANGE": "2-511", + "SUBNET_RANGE": "10.4.0.0/16", + "SUBNET_TARGET_MASK": "30", + "SYSLOG_SERVER_IP_LIST": "", + "SYSLOG_SERVER_VRF": "", + "SYSLOG_SEV": "", + "TCAM_ALLOCATION": "true", + "TOPDOWN_CONFIG_RM_TRACKING": "notstarted", + "UNDERLAY_IS_V6": "false", + "UNNUM_BOOTSTRAP_LB_ID": "", + "UNNUM_DHCP_END": "", + "UNNUM_DHCP_END_INTERNAL": "", + "UNNUM_DHCP_START": "", + "UNNUM_DHCP_START_INTERNAL": "", + "UPGRADE_FROM_VERSION": "", + "USE_LINK_LOCAL": "true", + "V6_SUBNET_RANGE": "", + "V6_SUBNET_TARGET_MASK": "126", + "VPC_AUTO_RECOVERY_TIME": "360", + "VPC_DELAY_RESTORE": "150", + "VPC_DELAY_RESTORE_TIME": "60", + "VPC_DOMAIN_ID_RANGE": "1-1000", + "VPC_ENABLE_IPv6_ND_SYNC": "true", + "VPC_PEER_KEEP_ALIVE_OPTION": "management", + "VPC_PEER_LINK_PO": "500", + "VPC_PEER_LINK_VLAN": "3600", + "VRF_LITE_AUTOCONFIG": "Manual", + "VRF_VLAN_RANGE": "2000-2299", + "abstract_anycast_rp": "anycast_rp", + "abstract_bgp": "base_bgp", + "abstract_bgp_neighbor": "evpn_bgp_rr_neighbor", + "abstract_bgp_rr": "evpn_bgp_rr", + "abstract_dhcp": "base_dhcp", + "abstract_extra_config_bootstrap": "extra_config_bootstrap_11_1", + "abstract_extra_config_leaf": "extra_config_leaf", + "abstract_extra_config_spine": "extra_config_spine", + "abstract_extra_config_tor": "extra_config_tor", + "abstract_feature_leaf": "base_feature_leaf_upg", + "abstract_feature_spine": "base_feature_spine_upg", + "abstract_isis": "base_isis_level2", + "abstract_isis_interface": "isis_interface", + "abstract_loopback_interface": "int_fabric_loopback_11_1", + "abstract_multicast": "base_multicast_11_1", + "abstract_ospf": "base_ospf", + "abstract_ospf_interface": "ospf_interface_11_1", + "abstract_pim_interface": "pim_interface", + "abstract_route_map": "route_map", + "abstract_routed_host": "int_routed_host", + "abstract_trunk_host": "int_trunk_host", + "abstract_vlan_interface": "int_fabric_vlan_11_1", + "abstract_vpc_domain": "base_vpc_domain_11_1", + "default_network": "Default_Network_Universal", + "default_pvlan_sec_network": "", + "default_vrf": "Default_VRF_Universal", + "enableRealTimeBackup": "", + "enableScheduledBackup": "", + "network_extension_template": "Default_Network_Extension_Universal", + "scheduledTime": "", + "temp_anycast_gateway": "anycast_gateway", + "temp_vpc_domain_mgmt": "vpc_domain_mgmt", + "temp_vpc_peer_link": "int_vpc_peer_link_po", + "vrf_extension_template": "Default_VRF_Extension_Universal" + }, + "operStatus": "HEALTHY", + "provisionMode": "DCNMTopDown", + "replicationMode": "Multicast", + "siteId": "65001", + "templateName": "Easy_Fabric", + "vrfExtensionTemplate": "Default_VRF_Extension_Universal", + "vrfTemplate": "Default_VRF_Universal" + } + ], + "MESSAGE": "OK", + "METHOD": "GET", + "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/rest/control/fabrics", + "RETURN_CODE": 200 + }, + "test_fabric_details_by_name_v2_00300a": { + "TEST_NOTES": [ + "Verify properties missing in the controller response return None.", + "DATA contains one fabric dict.", + "DATA[0].nvPairs.FABRIC_NAME == f1", + "DATA[0].nvPairs contains no other items.", + "RETURN_CODE == 200." + ], + "DATA": [ + { + "nvPairs": { + "FABRIC_NAME": "f1" + } + } + ], + "MESSAGE": "OK", + "METHOD": "GET", + "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/rest/control/fabrics", + "RETURN_CODE": 200 + }, + "test_fabric_details_by_name_v2_00500a": { + "TEST_NOTES": [ + "RETURN_CODE == 200.", + "MESSAGE == OK." + ], + "DATA": [ + { + "nvPairs": { + "FABRIC_NAME": "f1" + } + } + ], + "MESSAGE": "OK", + "METHOD": "GET", + "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/rest/control/fabrics", + "RETURN_CODE": 200 + }, + "test_fabric_details_by_name_v2_00510a": { + "TEST_NOTES": [ + "RETURN_CODE == 200.", + "MESSAGE == OK." + ], + "DATA": [ + { + "nvPairs": { + "FABRIC_NAME": "WRONG_FABRIC" + } + } + ], + "MESSAGE": "OK", + "METHOD": "GET", + "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/rest/control/fabrics", + "RETURN_CODE": 200 + }, + "test_fabric_details_by_name_v2_00600a": { + "TEST_NOTES": [ + "RETURN_CODE == 200.", + "MESSAGE == OK." + ], + "DATA": [ + { + "nvPairs": { + "FABRIC_NAME": "SOME_FABRIC" + } + } + ], + "MESSAGE": "OK", + "METHOD": "GET", + "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/rest/control/fabrics", + "RETURN_CODE": 200 + }, + "test_fabric_details_by_name_v2_00610a": { + "TEST_NOTES": [ + "FABRIC_NAME matches filter.", + "RETURN_CODE == 200.", + "MESSAGE == OK." + ], + "DATA": [ + { + "nvPairs": { + "BGP_AS": "65001", + "FABRIC_NAME": "MATCHING_FABRIC", + "ENABLE_NETFLOW": "false" + } + } + ], + "MESSAGE": "OK", + "METHOD": "GET", + "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/rest/control/fabrics", + "RETURN_CODE": 200 + }, + "test_fabric_details_by_name_v2_00700a": { + "TEST_NOTES": [ + "RETURN_CODE == 200.", + "MESSAGE == OK." + ], + "DATA": [ + { + "nvPairs": { + "FABRIC_NAME": "f1" + } + } + ], + "MESSAGE": "OK", + "METHOD": "GET", + "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/rest/control/fabrics", + "RETURN_CODE": 200 + }, + "test_fabric_details_by_name_v2_00710a": { + "TEST_NOTES": [ + "RETURN_CODE == 200.", + "MESSAGE == OK." + ], + "DATA": [ + { + "nvPairs": { + "FABRIC_NAME": "WRONG_FABRIC" + } + } + ], + "MESSAGE": "OK", + "METHOD": "GET", + "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/rest/control/fabrics", + "RETURN_CODE": 200 + }, + "test_fabric_update_bulk_00030a": { + "DATA": [], + "MESSAGE": "OK", + "METHOD": "GET", + "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/rest/control/fabrics", + "RETURN_CODE": 200 + }, + "test_fabric_update_bulk_00031a": { + "DATA": [ + { + "asn": "65001", + "createdOn": 1711411093680, + "deviceType": "n9k", + "fabricId": "FABRIC-2", + "fabricName": "f1", + "fabricTechnology": "VXLANFabric", + "fabricTechnologyFriendly": "VXLAN EVPN", + "fabricType": "Switch_Fabric", + "fabricTypeFriendly": "Switch Fabric", + "id": 2, + "modifiedOn": 1711411096857, + "networkExtensionTemplate": "Default_Network_Extension_Universal", + "networkTemplate": "Default_Network_Universal", + "nvPairs": { + "AAA_REMOTE_IP_ENABLED": "false", + "AAA_SERVER_CONF": "", + "ACTIVE_MIGRATION": "false", + "ADVERTISE_PIP_BGP": "false", + "ADVERTISE_PIP_ON_BORDER": "true", + "AGENT_INTF": "eth0", + "AI_ML_QOS_POLICY": "AI_Fabric_QOS_400G", + "ALLOW_NXC": "true", + "ALLOW_NXC_PREV": "true", + "ANYCAST_BGW_ADVERTISE_PIP": "false", + "ANYCAST_GW_MAC": "2020.0000.00aa", + "ANYCAST_LB_ID": "", + "ANYCAST_RP_IP_RANGE": "10.254.254.0/24", + "ANYCAST_RP_IP_RANGE_INTERNAL": "", + "AUTO_SYMMETRIC_DEFAULT_VRF": "false", + "AUTO_SYMMETRIC_VRF_LITE": "false", + "AUTO_UNIQUE_VRF_LITE_IP_PREFIX": "false", + "AUTO_UNIQUE_VRF_LITE_IP_PREFIX_PREV": "false", + "AUTO_VRFLITE_IFC_DEFAULT_VRF": "false", + "BANNER": "", + "BFD_AUTH_ENABLE": "false", + "BFD_AUTH_KEY": "", + "BFD_AUTH_KEY_ID": "", + "BFD_ENABLE": "false", + "BFD_ENABLE_PREV": "", + "BFD_IBGP_ENABLE": "false", + "BFD_ISIS_ENABLE": "false", + "BFD_OSPF_ENABLE": "false", + "BFD_PIM_ENABLE": "false", + "BGP_AS": "65001", + "BGP_AS_PREV": "65001", + "BGP_AUTH_ENABLE": "false", + "BGP_AUTH_KEY": "", + "BGP_AUTH_KEY_TYPE": "3", + "BGP_LB_ID": "0", + "BOOTSTRAP_CONF": "", + "BOOTSTRAP_ENABLE": "false", + "BOOTSTRAP_ENABLE_PREV": "false", + "BOOTSTRAP_MULTISUBNET": "#Scope_Start_IP, Scope_End_IP, Scope_Default_Gateway, Scope_Subnet_Prefix", + "BOOTSTRAP_MULTISUBNET_INTERNAL": "", + "BRFIELD_DEBUG_FLAG": "Disable", + "BROWNFIELD_NETWORK_NAME_FORMAT": "Auto_Net_VNI$$VNI$$_VLAN$$VLAN_ID$$", + "BROWNFIELD_SKIP_OVERLAY_NETWORK_ATTACHMENTS": "false", + "CDP_ENABLE": "false", + "COPP_POLICY": "strict", + "DCI_SUBNET_RANGE": "10.33.0.0/16", + "DCI_SUBNET_TARGET_MASK": "30", + "DEAFULT_QUEUING_POLICY_CLOUDSCALE": "queuing_policy_default_8q_cloudscale", + "DEAFULT_QUEUING_POLICY_OTHER": "queuing_policy_default_other", + "DEAFULT_QUEUING_POLICY_R_SERIES": "queuing_policy_default_r_series", + "DEFAULT_VRF_REDIS_BGP_RMAP": "", + "DEPLOYMENT_FREEZE": "false", + "DHCP_ENABLE": "false", + "DHCP_END": "", + "DHCP_END_INTERNAL": "", + "DHCP_IPV6_ENABLE": "", + "DHCP_IPV6_ENABLE_INTERNAL": "", + "DHCP_START": "", + "DHCP_START_INTERNAL": "", + "DNS_SERVER_IP_LIST": "", + "DNS_SERVER_VRF": "", + "DOMAIN_NAME_INTERNAL": "", + "ENABLE_AAA": "false", + "ENABLE_AGENT": "false", + "ENABLE_AI_ML_QOS_POLICY": "false", + "ENABLE_AI_ML_QOS_POLICY_FLAP": "false", + "ENABLE_DEFAULT_QUEUING_POLICY": "false", + "ENABLE_EVPN": "true", + "ENABLE_FABRIC_VPC_DOMAIN_ID": "false", + "ENABLE_FABRIC_VPC_DOMAIN_ID_PREV": "false", + "ENABLE_L3VNI_NO_VLAN": "false", + "ENABLE_MACSEC": "false", + "ENABLE_NETFLOW": "false", + "ENABLE_NETFLOW_PREV": "false", + "ENABLE_NGOAM": "true", + "ENABLE_NXAPI": "true", + "ENABLE_NXAPI_HTTP": "true", + "ENABLE_PBR": "false", + "ENABLE_PVLAN": "false", + "ENABLE_PVLAN_PREV": "false", + "ENABLE_SGT": "false", + "ENABLE_SGT_PREV": "false", + "ENABLE_TENANT_DHCP": "true", + "ENABLE_TRM": "false", + "ENABLE_VPC_PEER_LINK_NATIVE_VLAN": "false", + "ESR_OPTION": "PBR", + "EXTRA_CONF_INTRA_LINKS": "", + "EXTRA_CONF_LEAF": "", + "EXTRA_CONF_SPINE": "", + "EXTRA_CONF_TOR": "", + "EXT_FABRIC_TYPE": "", + "FABRIC_INTERFACE_TYPE": "p2p", + "FABRIC_MTU": "9216", + "FABRIC_MTU_PREV": "9216", + "FABRIC_NAME": "f1", + "FABRIC_TYPE": "Switch_Fabric", + "FABRIC_VPC_DOMAIN_ID": "", + "FABRIC_VPC_DOMAIN_ID_PREV": "", + "FABRIC_VPC_QOS": "false", + "FABRIC_VPC_QOS_POLICY_NAME": "spine_qos_for_fabric_vpc_peering", + "FEATURE_PTP": "false", + "FEATURE_PTP_INTERNAL": "false", + "FF": "Easy_Fabric", + "GRFIELD_DEBUG_FLAG": "Disable", + "HD_TIME": "180", + "HOST_INTF_ADMIN_STATE": "true", + "IBGP_PEER_TEMPLATE": "", + "IBGP_PEER_TEMPLATE_LEAF": "", + "INBAND_DHCP_SERVERS": "", + "INBAND_MGMT": "false", + "INBAND_MGMT_PREV": "false", + "ISIS_AREA_NUM": "0001", + "ISIS_AREA_NUM_PREV": "", + "ISIS_AUTH_ENABLE": "false", + "ISIS_AUTH_KEY": "", + "ISIS_AUTH_KEYCHAIN_KEY_ID": "", + "ISIS_AUTH_KEYCHAIN_NAME": "", + "ISIS_LEVEL": "level-2", + "ISIS_OVERLOAD_ELAPSE_TIME": "", + "ISIS_OVERLOAD_ENABLE": "", + "ISIS_P2P_ENABLE": "", + "L2_HOST_INTF_MTU": "9216", + "L2_HOST_INTF_MTU_PREV": "9216", + "L2_SEGMENT_ID_RANGE": "30000-49000", + "L3VNI_MCAST_GROUP": "", + "L3_PARTITION_ID_RANGE": "50000-59000", + "LINK_STATE_ROUTING": "ospf", + "LINK_STATE_ROUTING_TAG": "UNDERLAY", + "LINK_STATE_ROUTING_TAG_PREV": "UNDERLAY", + "LOOPBACK0_IPV6_RANGE": "", + "LOOPBACK0_IP_RANGE": "10.2.0.0/22", + "LOOPBACK1_IPV6_RANGE": "", + "LOOPBACK1_IP_RANGE": "10.3.0.0/22", + "MACSEC_ALGORITHM": "", + "MACSEC_CIPHER_SUITE": "", + "MACSEC_FALLBACK_ALGORITHM": "", + "MACSEC_FALLBACK_KEY_STRING": "", + "MACSEC_KEY_STRING": "", + "MACSEC_REPORT_TIMER": "", + "MGMT_GW": "", + "MGMT_GW_INTERNAL": "", + "MGMT_PREFIX": "", + "MGMT_PREFIX_INTERNAL": "", + "MGMT_V6PREFIX": "", + "MGMT_V6PREFIX_INTERNAL": "", + "MPLS_HANDOFF": "false", + "MPLS_ISIS_AREA_NUM": "0001", + "MPLS_ISIS_AREA_NUM_PREV": "", + "MPLS_LB_ID": "", + "MPLS_LOOPBACK_IP_RANGE": "", + "MSO_CONNECTIVITY_DEPLOYED": "", + "MSO_CONTROLER_ID": "", + "MSO_SITE_GROUP_NAME": "", + "MSO_SITE_ID": "", + "MST_INSTANCE_RANGE": "", + "MULTICAST_GROUP_SUBNET": "239.1.1.0/25", + "NETFLOW_EXPORTER_LIST": "", + "NETFLOW_MONITOR_LIST": "", + "NETFLOW_RECORD_LIST": "", + "NETWORK_VLAN_RANGE": "2300-2999", + "NTP_SERVER_IP_LIST": "", + "NTP_SERVER_VRF": "", + "NVE_LB_ID": "1", + "NXAPI_HTTPS_PORT": "443", + "NXAPI_HTTP_PORT": "80", + "NXC_DEST_VRF": "", + "NXC_PROXY_PORT": "8080", + "NXC_PROXY_SERVER": "", + "NXC_SRC_INTF": "", + "OBJECT_TRACKING_NUMBER_RANGE": "100-299", + "OSPF_AREA_ID": "0.0.0.0", + "OSPF_AUTH_ENABLE": "false", + "OSPF_AUTH_KEY": "", + "OSPF_AUTH_KEY_ID": "", + "OVERLAY_MODE": "cli", + "OVERLAY_MODE_PREV": "cli", + "OVERWRITE_GLOBAL_NXC": "false", + "PER_VRF_LOOPBACK_AUTO_PROVISION": "false", + "PER_VRF_LOOPBACK_AUTO_PROVISION_PREV": "false", + "PER_VRF_LOOPBACK_AUTO_PROVISION_V6": "false", + "PER_VRF_LOOPBACK_AUTO_PROVISION_V6_PREV": "false", + "PER_VRF_LOOPBACK_IP_RANGE": "", + "PER_VRF_LOOPBACK_IP_RANGE_V6": "", + "PHANTOM_RP_LB_ID1": "", + "PHANTOM_RP_LB_ID2": "", + "PHANTOM_RP_LB_ID3": "", + "PHANTOM_RP_LB_ID4": "", + "PIM_HELLO_AUTH_ENABLE": "false", + "PIM_HELLO_AUTH_KEY": "", + "PM_ENABLE": "false", + "PM_ENABLE_PREV": "false", + "POWER_REDUNDANCY_MODE": "ps-redundant", + "PREMSO_PARENT_FABRIC": "", + "PTP_DOMAIN_ID": "", + "PTP_LB_ID": "", + "REPLICATION_MODE": "Multicast", + "ROUTER_ID_RANGE": "", + "ROUTE_MAP_SEQUENCE_NUMBER_RANGE": "1-65534", + "RP_COUNT": "2", + "RP_LB_ID": "254", + "RP_MODE": "asm", + "RR_COUNT": "2", + "SEED_SWITCH_CORE_INTERFACES": "", + "SERVICE_NETWORK_VLAN_RANGE": "3000-3199", + "SGT_ID_RANGE": "", + "SGT_NAME_PREFIX": "", + "SGT_PREPROVISION": "", + "SITE_ID": "65001", + "SLA_ID_RANGE": "10000-19999", + "SNMP_SERVER_HOST_TRAP": "true", + "SPINE_COUNT": "0", + "SPINE_SWITCH_CORE_INTERFACES": "", + "SSPINE_ADD_DEL_DEBUG_FLAG": "Disable", + "SSPINE_COUNT": "0", + "STATIC_UNDERLAY_IP_ALLOC": "false", + "STP_BRIDGE_PRIORITY": "", + "STP_ROOT_OPTION": "unmanaged", + "STP_VLAN_RANGE": "", + "STRICT_CC_MODE": "false", + "SUBINTERFACE_RANGE": "2-511", + "SUBNET_RANGE": "10.4.0.0/16", + "SUBNET_TARGET_MASK": "30", + "SYSLOG_SERVER_IP_LIST": "", + "SYSLOG_SERVER_VRF": "", + "SYSLOG_SEV": "", + "TCAM_ALLOCATION": "true", + "TOPDOWN_CONFIG_RM_TRACKING": "notstarted", + "UNDERLAY_IS_V6": "false", + "UNNUM_BOOTSTRAP_LB_ID": "", + "UNNUM_DHCP_END": "", + "UNNUM_DHCP_END_INTERNAL": "", + "UNNUM_DHCP_START": "", + "UNNUM_DHCP_START_INTERNAL": "", + "UPGRADE_FROM_VERSION": "", + "USE_LINK_LOCAL": "true", + "V6_SUBNET_RANGE": "", + "V6_SUBNET_TARGET_MASK": "126", + "VPC_AUTO_RECOVERY_TIME": "360", + "VPC_DELAY_RESTORE": "150", + "VPC_DELAY_RESTORE_TIME": "60", + "VPC_DOMAIN_ID_RANGE": "1-1000", + "VPC_ENABLE_IPv6_ND_SYNC": "true", + "VPC_PEER_KEEP_ALIVE_OPTION": "management", + "VPC_PEER_LINK_PO": "500", + "VPC_PEER_LINK_VLAN": "3600", + "VRF_LITE_AUTOCONFIG": "Manual", + "VRF_VLAN_RANGE": "2000-2299", + "abstract_anycast_rp": "anycast_rp", + "abstract_bgp": "base_bgp", + "abstract_bgp_neighbor": "evpn_bgp_rr_neighbor", + "abstract_bgp_rr": "evpn_bgp_rr", + "abstract_dhcp": "base_dhcp", + "abstract_extra_config_bootstrap": "extra_config_bootstrap_11_1", + "abstract_extra_config_leaf": "extra_config_leaf", + "abstract_extra_config_spine": "extra_config_spine", + "abstract_extra_config_tor": "extra_config_tor", + "abstract_feature_leaf": "base_feature_leaf_upg", + "abstract_feature_spine": "base_feature_spine_upg", + "abstract_isis": "base_isis_level2", + "abstract_isis_interface": "isis_interface", + "abstract_loopback_interface": "int_fabric_loopback_11_1", + "abstract_multicast": "base_multicast_11_1", + "abstract_ospf": "base_ospf", + "abstract_ospf_interface": "ospf_interface_11_1", + "abstract_pim_interface": "pim_interface", + "abstract_route_map": "route_map", + "abstract_routed_host": "int_routed_host", + "abstract_trunk_host": "int_trunk_host", + "abstract_vlan_interface": "int_fabric_vlan_11_1", + "abstract_vpc_domain": "base_vpc_domain_11_1", + "default_network": "Default_Network_Universal", + "default_pvlan_sec_network": "", + "default_vrf": "Default_VRF_Universal", + "enableRealTimeBackup": "", + "enableScheduledBackup": "", + "network_extension_template": "Default_Network_Extension_Universal", + "scheduledTime": "", + "temp_anycast_gateway": "anycast_gateway", + "temp_vpc_domain_mgmt": "vpc_domain_mgmt", + "temp_vpc_peer_link": "int_vpc_peer_link_po", + "vrf_extension_template": "Default_VRF_Extension_Universal" + }, + "operStatus": "HEALTHY", + "provisionMode": "DCNMTopDown", + "replicationMode": "Multicast", + "siteId": "65001", + "templateName": "Easy_Fabric", + "vrfExtensionTemplate": "Default_VRF_Extension_Universal", + "vrfTemplate": "Default_VRF_Universal" + } + ], + "MESSAGE": "OK", + "METHOD": "GET", + "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/rest/control/fabrics", + "RETURN_CODE": 200 + }, + "test_fabric_update_bulk_00032a": { + "DATA": [ + { + "asn": "65001", + "createdOn": 1711411093680, + "deviceType": "n9k", + "fabricId": "FABRIC-2", + "fabricName": "f1", + "fabricTechnology": "VXLANFabric", + "fabricTechnologyFriendly": "VXLAN EVPN", + "fabricType": "Switch_Fabric", + "fabricTypeFriendly": "Switch Fabric", + "id": 2, + "modifiedOn": 1711411096857, + "networkExtensionTemplate": "Default_Network_Extension_Universal", + "networkTemplate": "Default_Network_Universal", + "nvPairs": { + "AAA_REMOTE_IP_ENABLED": "false", + "AAA_SERVER_CONF": "", + "ACTIVE_MIGRATION": "false", + "ADVERTISE_PIP_BGP": "false", + "ADVERTISE_PIP_ON_BORDER": "true", + "AGENT_INTF": "eth0", + "AI_ML_QOS_POLICY": "AI_Fabric_QOS_400G", + "ALLOW_NXC": "true", + "ALLOW_NXC_PREV": "true", + "ANYCAST_BGW_ADVERTISE_PIP": "false", + "ANYCAST_GW_MAC": "2020.0000.00aa", + "ANYCAST_LB_ID": "", + "ANYCAST_RP_IP_RANGE": "10.254.254.0/24", + "ANYCAST_RP_IP_RANGE_INTERNAL": "", + "AUTO_SYMMETRIC_DEFAULT_VRF": "false", + "AUTO_SYMMETRIC_VRF_LITE": "false", + "AUTO_UNIQUE_VRF_LITE_IP_PREFIX": "false", + "AUTO_UNIQUE_VRF_LITE_IP_PREFIX_PREV": "false", + "AUTO_VRFLITE_IFC_DEFAULT_VRF": "false", + "BANNER": "", + "BFD_AUTH_ENABLE": "false", + "BFD_AUTH_KEY": "", + "BFD_AUTH_KEY_ID": "", + "BFD_ENABLE": "false", + "BFD_ENABLE_PREV": "", + "BFD_IBGP_ENABLE": "false", + "BFD_ISIS_ENABLE": "false", + "BFD_OSPF_ENABLE": "false", + "BFD_PIM_ENABLE": "false", + "BGP_AS": "65001", + "BGP_AS_PREV": "65001", + "BGP_AUTH_ENABLE": "false", + "BGP_AUTH_KEY": "", + "BGP_AUTH_KEY_TYPE": "3", + "BGP_LB_ID": "0", + "BOOTSTRAP_CONF": "", + "BOOTSTRAP_ENABLE": "false", + "BOOTSTRAP_ENABLE_PREV": "false", + "BOOTSTRAP_MULTISUBNET": "#Scope_Start_IP, Scope_End_IP, Scope_Default_Gateway, Scope_Subnet_Prefix", + "BOOTSTRAP_MULTISUBNET_INTERNAL": "", + "BRFIELD_DEBUG_FLAG": "Disable", + "BROWNFIELD_NETWORK_NAME_FORMAT": "Auto_Net_VNI$$VNI$$_VLAN$$VLAN_ID$$", + "BROWNFIELD_SKIP_OVERLAY_NETWORK_ATTACHMENTS": "false", + "CDP_ENABLE": "false", + "COPP_POLICY": "strict", + "DCI_SUBNET_RANGE": "10.33.0.0/16", + "DCI_SUBNET_TARGET_MASK": "30", + "DEAFULT_QUEUING_POLICY_CLOUDSCALE": "queuing_policy_default_8q_cloudscale", + "DEAFULT_QUEUING_POLICY_OTHER": "queuing_policy_default_other", + "DEAFULT_QUEUING_POLICY_R_SERIES": "queuing_policy_default_r_series", + "DEFAULT_VRF_REDIS_BGP_RMAP": "", + "DEPLOYMENT_FREEZE": "false", + "DHCP_ENABLE": "false", + "DHCP_END": "", + "DHCP_END_INTERNAL": "", + "DHCP_IPV6_ENABLE": "", + "DHCP_IPV6_ENABLE_INTERNAL": "", + "DHCP_START": "", + "DHCP_START_INTERNAL": "", + "DNS_SERVER_IP_LIST": "", + "DNS_SERVER_VRF": "", + "DOMAIN_NAME_INTERNAL": "", + "ENABLE_AAA": "false", + "ENABLE_AGENT": "false", + "ENABLE_AI_ML_QOS_POLICY": "false", + "ENABLE_AI_ML_QOS_POLICY_FLAP": "false", + "ENABLE_DEFAULT_QUEUING_POLICY": "false", + "ENABLE_EVPN": "true", + "ENABLE_FABRIC_VPC_DOMAIN_ID": "false", + "ENABLE_FABRIC_VPC_DOMAIN_ID_PREV": "false", + "ENABLE_L3VNI_NO_VLAN": "false", + "ENABLE_MACSEC": "false", + "ENABLE_NETFLOW": "false", + "ENABLE_NETFLOW_PREV": "false", + "ENABLE_NGOAM": "true", + "ENABLE_NXAPI": "true", + "ENABLE_NXAPI_HTTP": "true", + "ENABLE_PBR": "false", + "ENABLE_PVLAN": "false", + "ENABLE_PVLAN_PREV": "false", + "ENABLE_SGT": "false", + "ENABLE_SGT_PREV": "false", + "ENABLE_TENANT_DHCP": "true", + "ENABLE_TRM": "false", + "ENABLE_VPC_PEER_LINK_NATIVE_VLAN": "false", + "ESR_OPTION": "PBR", + "EXTRA_CONF_INTRA_LINKS": "", + "EXTRA_CONF_LEAF": "", + "EXTRA_CONF_SPINE": "", + "EXTRA_CONF_TOR": "", + "EXT_FABRIC_TYPE": "", + "FABRIC_INTERFACE_TYPE": "p2p", + "FABRIC_MTU": "9216", + "FABRIC_MTU_PREV": "9216", + "FABRIC_NAME": "f1", + "FABRIC_TYPE": "Switch_Fabric", + "FABRIC_VPC_DOMAIN_ID": "", + "FABRIC_VPC_DOMAIN_ID_PREV": "", + "FABRIC_VPC_QOS": "false", + "FABRIC_VPC_QOS_POLICY_NAME": "spine_qos_for_fabric_vpc_peering", + "FEATURE_PTP": "false", + "FEATURE_PTP_INTERNAL": "false", + "FF": "Easy_Fabric", + "GRFIELD_DEBUG_FLAG": "Disable", + "HD_TIME": "180", + "HOST_INTF_ADMIN_STATE": "true", + "IBGP_PEER_TEMPLATE": "", + "IBGP_PEER_TEMPLATE_LEAF": "", + "INBAND_DHCP_SERVERS": "", + "INBAND_MGMT": "false", + "INBAND_MGMT_PREV": "false", + "ISIS_AREA_NUM": "0001", + "ISIS_AREA_NUM_PREV": "", + "ISIS_AUTH_ENABLE": "false", + "ISIS_AUTH_KEY": "", + "ISIS_AUTH_KEYCHAIN_KEY_ID": "", + "ISIS_AUTH_KEYCHAIN_NAME": "", + "ISIS_LEVEL": "level-2", + "ISIS_OVERLOAD_ELAPSE_TIME": "", + "ISIS_OVERLOAD_ENABLE": "", + "ISIS_P2P_ENABLE": "", + "L2_HOST_INTF_MTU": "9216", + "L2_HOST_INTF_MTU_PREV": "9216", + "L2_SEGMENT_ID_RANGE": "30000-49000", + "L3VNI_MCAST_GROUP": "", + "L3_PARTITION_ID_RANGE": "50000-59000", + "LINK_STATE_ROUTING": "ospf", + "LINK_STATE_ROUTING_TAG": "UNDERLAY", + "LINK_STATE_ROUTING_TAG_PREV": "UNDERLAY", + "LOOPBACK0_IPV6_RANGE": "", + "LOOPBACK0_IP_RANGE": "10.2.0.0/22", + "LOOPBACK1_IPV6_RANGE": "", + "LOOPBACK1_IP_RANGE": "10.3.0.0/22", + "MACSEC_ALGORITHM": "", + "MACSEC_CIPHER_SUITE": "", + "MACSEC_FALLBACK_ALGORITHM": "", + "MACSEC_FALLBACK_KEY_STRING": "", + "MACSEC_KEY_STRING": "", + "MACSEC_REPORT_TIMER": "", + "MGMT_GW": "", + "MGMT_GW_INTERNAL": "", + "MGMT_PREFIX": "", + "MGMT_PREFIX_INTERNAL": "", + "MGMT_V6PREFIX": "", + "MGMT_V6PREFIX_INTERNAL": "", + "MPLS_HANDOFF": "false", + "MPLS_ISIS_AREA_NUM": "0001", + "MPLS_ISIS_AREA_NUM_PREV": "", + "MPLS_LB_ID": "", + "MPLS_LOOPBACK_IP_RANGE": "", + "MSO_CONNECTIVITY_DEPLOYED": "", + "MSO_CONTROLER_ID": "", + "MSO_SITE_GROUP_NAME": "", + "MSO_SITE_ID": "", + "MST_INSTANCE_RANGE": "", + "MULTICAST_GROUP_SUBNET": "239.1.1.0/25", + "NETFLOW_EXPORTER_LIST": "", + "NETFLOW_MONITOR_LIST": "", + "NETFLOW_RECORD_LIST": "", + "NETWORK_VLAN_RANGE": "2300-2999", + "NTP_SERVER_IP_LIST": "", + "NTP_SERVER_VRF": "", + "NVE_LB_ID": "1", + "NXAPI_HTTPS_PORT": "443", + "NXAPI_HTTP_PORT": "80", + "NXC_DEST_VRF": "", + "NXC_PROXY_PORT": "8080", + "NXC_PROXY_SERVER": "", + "NXC_SRC_INTF": "", + "OBJECT_TRACKING_NUMBER_RANGE": "100-299", + "OSPF_AREA_ID": "0.0.0.0", + "OSPF_AUTH_ENABLE": "false", + "OSPF_AUTH_KEY": "", + "OSPF_AUTH_KEY_ID": "", + "OVERLAY_MODE": "cli", + "OVERLAY_MODE_PREV": "cli", + "OVERWRITE_GLOBAL_NXC": "false", + "PER_VRF_LOOPBACK_AUTO_PROVISION": "false", + "PER_VRF_LOOPBACK_AUTO_PROVISION_PREV": "false", + "PER_VRF_LOOPBACK_AUTO_PROVISION_V6": "false", + "PER_VRF_LOOPBACK_AUTO_PROVISION_V6_PREV": "false", + "PER_VRF_LOOPBACK_IP_RANGE": "", + "PER_VRF_LOOPBACK_IP_RANGE_V6": "", + "PHANTOM_RP_LB_ID1": "", + "PHANTOM_RP_LB_ID2": "", + "PHANTOM_RP_LB_ID3": "", + "PHANTOM_RP_LB_ID4": "", + "PIM_HELLO_AUTH_ENABLE": "false", + "PIM_HELLO_AUTH_KEY": "", + "PM_ENABLE": "false", + "PM_ENABLE_PREV": "false", + "POWER_REDUNDANCY_MODE": "ps-redundant", + "PREMSO_PARENT_FABRIC": "", + "PTP_DOMAIN_ID": "", + "PTP_LB_ID": "", + "REPLICATION_MODE": "Multicast", + "ROUTER_ID_RANGE": "", + "ROUTE_MAP_SEQUENCE_NUMBER_RANGE": "1-65534", + "RP_COUNT": "2", + "RP_LB_ID": "254", + "RP_MODE": "asm", + "RR_COUNT": "2", + "SEED_SWITCH_CORE_INTERFACES": "", + "SERVICE_NETWORK_VLAN_RANGE": "3000-3199", + "SGT_ID_RANGE": "", + "SGT_NAME_PREFIX": "", + "SGT_PREPROVISION": "", + "SITE_ID": "65001", + "SLA_ID_RANGE": "10000-19999", + "SNMP_SERVER_HOST_TRAP": "true", + "SPINE_COUNT": "0", + "SPINE_SWITCH_CORE_INTERFACES": "", + "SSPINE_ADD_DEL_DEBUG_FLAG": "Disable", + "SSPINE_COUNT": "0", + "STATIC_UNDERLAY_IP_ALLOC": "false", + "STP_BRIDGE_PRIORITY": "", + "STP_ROOT_OPTION": "unmanaged", + "STP_VLAN_RANGE": "", + "STRICT_CC_MODE": "false", + "SUBINTERFACE_RANGE": "2-511", + "SUBNET_RANGE": "10.4.0.0/16", + "SUBNET_TARGET_MASK": "30", + "SYSLOG_SERVER_IP_LIST": "", + "SYSLOG_SERVER_VRF": "", + "SYSLOG_SEV": "", + "TCAM_ALLOCATION": "true", + "TOPDOWN_CONFIG_RM_TRACKING": "notstarted", + "UNDERLAY_IS_V6": "false", + "UNNUM_BOOTSTRAP_LB_ID": "", + "UNNUM_DHCP_END": "", + "UNNUM_DHCP_END_INTERNAL": "", + "UNNUM_DHCP_START": "", + "UNNUM_DHCP_START_INTERNAL": "", + "UPGRADE_FROM_VERSION": "", + "USE_LINK_LOCAL": "true", + "V6_SUBNET_RANGE": "", + "V6_SUBNET_TARGET_MASK": "126", + "VPC_AUTO_RECOVERY_TIME": "360", + "VPC_DELAY_RESTORE": "150", + "VPC_DELAY_RESTORE_TIME": "60", + "VPC_DOMAIN_ID_RANGE": "1-1000", + "VPC_ENABLE_IPv6_ND_SYNC": "true", + "VPC_PEER_KEEP_ALIVE_OPTION": "management", + "VPC_PEER_LINK_PO": "500", + "VPC_PEER_LINK_VLAN": "3600", + "VRF_LITE_AUTOCONFIG": "Manual", + "VRF_VLAN_RANGE": "2000-2299", + "abstract_anycast_rp": "anycast_rp", + "abstract_bgp": "base_bgp", + "abstract_bgp_neighbor": "evpn_bgp_rr_neighbor", + "abstract_bgp_rr": "evpn_bgp_rr", + "abstract_dhcp": "base_dhcp", + "abstract_extra_config_bootstrap": "extra_config_bootstrap_11_1", + "abstract_extra_config_leaf": "extra_config_leaf", + "abstract_extra_config_spine": "extra_config_spine", + "abstract_extra_config_tor": "extra_config_tor", + "abstract_feature_leaf": "base_feature_leaf_upg", + "abstract_feature_spine": "base_feature_spine_upg", + "abstract_isis": "base_isis_level2", + "abstract_isis_interface": "isis_interface", + "abstract_loopback_interface": "int_fabric_loopback_11_1", + "abstract_multicast": "base_multicast_11_1", + "abstract_ospf": "base_ospf", + "abstract_ospf_interface": "ospf_interface_11_1", + "abstract_pim_interface": "pim_interface", + "abstract_route_map": "route_map", + "abstract_routed_host": "int_routed_host", + "abstract_trunk_host": "int_trunk_host", + "abstract_vlan_interface": "int_fabric_vlan_11_1", + "abstract_vpc_domain": "base_vpc_domain_11_1", + "default_network": "Default_Network_Universal", + "default_pvlan_sec_network": "", + "default_vrf": "Default_VRF_Universal", + "enableRealTimeBackup": "", + "enableScheduledBackup": "", + "network_extension_template": "Default_Network_Extension_Universal", + "scheduledTime": "", + "temp_anycast_gateway": "anycast_gateway", + "temp_vpc_domain_mgmt": "vpc_domain_mgmt", + "temp_vpc_peer_link": "int_vpc_peer_link_po", + "vrf_extension_template": "Default_VRF_Extension_Universal" + }, + "operStatus": "HEALTHY", + "provisionMode": "DCNMTopDown", + "replicationMode": "Multicast", + "siteId": "65001", + "templateName": "Easy_Fabric", + "vrfExtensionTemplate": "Default_VRF_Extension_Universal", + "vrfTemplate": "Default_VRF_Universal" + } + ], + "MESSAGE": "OK", + "METHOD": "GET", + "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/rest/control/fabrics", + "RETURN_CODE": 200 + }, + "test_fabric_update_bulk_00033a": { + "DATA": [ + { + "asn": "65001", + "createdOn": 1711411093680, + "deviceType": "n9k", + "fabricId": "FABRIC-2", + "fabricName": "f1", + "fabricTechnology": "VXLANFabric", + "fabricTechnologyFriendly": "VXLAN EVPN", + "fabricType": "Switch_Fabric", + "fabricTypeFriendly": "Switch Fabric", + "id": 2, + "modifiedOn": 1711411096857, + "networkExtensionTemplate": "Default_Network_Extension_Universal", + "networkTemplate": "Default_Network_Universal", + "nvPairs": { + "AAA_REMOTE_IP_ENABLED": "false", + "AAA_SERVER_CONF": "", + "ACTIVE_MIGRATION": "false", + "ADVERTISE_PIP_BGP": "false", + "ADVERTISE_PIP_ON_BORDER": "true", + "AGENT_INTF": "eth0", + "AI_ML_QOS_POLICY": "AI_Fabric_QOS_400G", + "ALLOW_NXC": "true", + "ALLOW_NXC_PREV": "true", + "ANYCAST_BGW_ADVERTISE_PIP": "false", + "ANYCAST_GW_MAC": "2020.0000.00aa", + "ANYCAST_LB_ID": "", + "ANYCAST_RP_IP_RANGE": "10.254.254.0/24", + "ANYCAST_RP_IP_RANGE_INTERNAL": "", + "AUTO_SYMMETRIC_DEFAULT_VRF": "false", + "AUTO_SYMMETRIC_VRF_LITE": "false", + "AUTO_UNIQUE_VRF_LITE_IP_PREFIX": "false", + "AUTO_UNIQUE_VRF_LITE_IP_PREFIX_PREV": "false", + "AUTO_VRFLITE_IFC_DEFAULT_VRF": "false", + "BANNER": "", + "BFD_AUTH_ENABLE": "false", + "BFD_AUTH_KEY": "", + "BFD_AUTH_KEY_ID": "", + "BFD_ENABLE": "false", + "BFD_ENABLE_PREV": "", + "BFD_IBGP_ENABLE": "false", + "BFD_ISIS_ENABLE": "false", + "BFD_OSPF_ENABLE": "false", + "BFD_PIM_ENABLE": "false", + "BGP_AS": "65001", + "BGP_AS_PREV": "65001", + "BGP_AUTH_ENABLE": "false", + "BGP_AUTH_KEY": "", + "BGP_AUTH_KEY_TYPE": "3", + "BGP_LB_ID": "0", + "BOOTSTRAP_CONF": "", + "BOOTSTRAP_ENABLE": "false", + "BOOTSTRAP_ENABLE_PREV": "false", + "BOOTSTRAP_MULTISUBNET": "#Scope_Start_IP, Scope_End_IP, Scope_Default_Gateway, Scope_Subnet_Prefix", + "BOOTSTRAP_MULTISUBNET_INTERNAL": "", + "BRFIELD_DEBUG_FLAG": "Disable", + "BROWNFIELD_NETWORK_NAME_FORMAT": "Auto_Net_VNI$$VNI$$_VLAN$$VLAN_ID$$", + "BROWNFIELD_SKIP_OVERLAY_NETWORK_ATTACHMENTS": "false", + "CDP_ENABLE": "false", + "COPP_POLICY": "strict", + "DCI_SUBNET_RANGE": "10.33.0.0/16", + "DCI_SUBNET_TARGET_MASK": "30", + "DEAFULT_QUEUING_POLICY_CLOUDSCALE": "queuing_policy_default_8q_cloudscale", + "DEAFULT_QUEUING_POLICY_OTHER": "queuing_policy_default_other", + "DEAFULT_QUEUING_POLICY_R_SERIES": "queuing_policy_default_r_series", + "DEFAULT_VRF_REDIS_BGP_RMAP": "", + "DEPLOYMENT_FREEZE": "false", + "DHCP_ENABLE": "false", + "DHCP_END": "", + "DHCP_END_INTERNAL": "", + "DHCP_IPV6_ENABLE": "", + "DHCP_IPV6_ENABLE_INTERNAL": "", + "DHCP_START": "", + "DHCP_START_INTERNAL": "", + "DNS_SERVER_IP_LIST": "", + "DNS_SERVER_VRF": "", + "DOMAIN_NAME_INTERNAL": "", + "ENABLE_AAA": "false", + "ENABLE_AGENT": "false", + "ENABLE_AI_ML_QOS_POLICY": "false", + "ENABLE_AI_ML_QOS_POLICY_FLAP": "false", + "ENABLE_DEFAULT_QUEUING_POLICY": "false", + "ENABLE_EVPN": "true", + "ENABLE_FABRIC_VPC_DOMAIN_ID": "false", + "ENABLE_FABRIC_VPC_DOMAIN_ID_PREV": "false", + "ENABLE_L3VNI_NO_VLAN": "false", + "ENABLE_MACSEC": "false", + "ENABLE_NETFLOW": "false", + "ENABLE_NETFLOW_PREV": "false", + "ENABLE_NGOAM": "true", + "ENABLE_NXAPI": "true", + "ENABLE_NXAPI_HTTP": "true", + "ENABLE_PBR": "false", + "ENABLE_PVLAN": "false", + "ENABLE_PVLAN_PREV": "false", + "ENABLE_SGT": "false", + "ENABLE_SGT_PREV": "false", + "ENABLE_TENANT_DHCP": "true", + "ENABLE_TRM": "false", + "ENABLE_VPC_PEER_LINK_NATIVE_VLAN": "false", + "ESR_OPTION": "PBR", + "EXTRA_CONF_INTRA_LINKS": "", + "EXTRA_CONF_LEAF": "", + "EXTRA_CONF_SPINE": "", + "EXTRA_CONF_TOR": "", + "EXT_FABRIC_TYPE": "", + "FABRIC_INTERFACE_TYPE": "p2p", + "FABRIC_MTU": "9216", + "FABRIC_MTU_PREV": "9216", + "FABRIC_NAME": "f1", + "FABRIC_TYPE": "Switch_Fabric", + "FABRIC_VPC_DOMAIN_ID": "", + "FABRIC_VPC_DOMAIN_ID_PREV": "", + "FABRIC_VPC_QOS": "false", + "FABRIC_VPC_QOS_POLICY_NAME": "spine_qos_for_fabric_vpc_peering", + "FEATURE_PTP": "false", + "FEATURE_PTP_INTERNAL": "false", + "FF": "Easy_Fabric", + "GRFIELD_DEBUG_FLAG": "Disable", + "HD_TIME": "180", + "HOST_INTF_ADMIN_STATE": "true", + "IBGP_PEER_TEMPLATE": "", + "IBGP_PEER_TEMPLATE_LEAF": "", + "INBAND_DHCP_SERVERS": "", + "INBAND_MGMT": "false", + "INBAND_MGMT_PREV": "false", + "ISIS_AREA_NUM": "0001", + "ISIS_AREA_NUM_PREV": "", + "ISIS_AUTH_ENABLE": "false", + "ISIS_AUTH_KEY": "", + "ISIS_AUTH_KEYCHAIN_KEY_ID": "", + "ISIS_AUTH_KEYCHAIN_NAME": "", + "ISIS_LEVEL": "level-2", + "ISIS_OVERLOAD_ELAPSE_TIME": "", + "ISIS_OVERLOAD_ENABLE": "", + "ISIS_P2P_ENABLE": "", + "L2_HOST_INTF_MTU": "9216", + "L2_HOST_INTF_MTU_PREV": "9216", + "L2_SEGMENT_ID_RANGE": "30000-49000", + "L3VNI_MCAST_GROUP": "", + "L3_PARTITION_ID_RANGE": "50000-59000", + "LINK_STATE_ROUTING": "ospf", + "LINK_STATE_ROUTING_TAG": "UNDERLAY", + "LINK_STATE_ROUTING_TAG_PREV": "UNDERLAY", + "LOOPBACK0_IPV6_RANGE": "", + "LOOPBACK0_IP_RANGE": "10.2.0.0/22", + "LOOPBACK1_IPV6_RANGE": "", + "LOOPBACK1_IP_RANGE": "10.3.0.0/22", + "MACSEC_ALGORITHM": "", + "MACSEC_CIPHER_SUITE": "", + "MACSEC_FALLBACK_ALGORITHM": "", + "MACSEC_FALLBACK_KEY_STRING": "", + "MACSEC_KEY_STRING": "", + "MACSEC_REPORT_TIMER": "", + "MGMT_GW": "", + "MGMT_GW_INTERNAL": "", + "MGMT_PREFIX": "", + "MGMT_PREFIX_INTERNAL": "", + "MGMT_V6PREFIX": "", + "MGMT_V6PREFIX_INTERNAL": "", + "MPLS_HANDOFF": "false", + "MPLS_ISIS_AREA_NUM": "0001", + "MPLS_ISIS_AREA_NUM_PREV": "", + "MPLS_LB_ID": "", + "MPLS_LOOPBACK_IP_RANGE": "", + "MSO_CONNECTIVITY_DEPLOYED": "", + "MSO_CONTROLER_ID": "", + "MSO_SITE_GROUP_NAME": "", + "MSO_SITE_ID": "", + "MST_INSTANCE_RANGE": "", + "MULTICAST_GROUP_SUBNET": "239.1.1.0/25", + "NETFLOW_EXPORTER_LIST": "", + "NETFLOW_MONITOR_LIST": "", + "NETFLOW_RECORD_LIST": "", + "NETWORK_VLAN_RANGE": "2300-2999", + "NTP_SERVER_IP_LIST": "", + "NTP_SERVER_VRF": "", + "NVE_LB_ID": "1", + "NXAPI_HTTPS_PORT": "443", + "NXAPI_HTTP_PORT": "80", + "NXC_DEST_VRF": "", + "NXC_PROXY_PORT": "8080", + "NXC_PROXY_SERVER": "", + "NXC_SRC_INTF": "", + "OBJECT_TRACKING_NUMBER_RANGE": "100-299", + "OSPF_AREA_ID": "0.0.0.0", + "OSPF_AUTH_ENABLE": "false", + "OSPF_AUTH_KEY": "", + "OSPF_AUTH_KEY_ID": "", + "OVERLAY_MODE": "cli", + "OVERLAY_MODE_PREV": "cli", + "OVERWRITE_GLOBAL_NXC": "false", + "PER_VRF_LOOPBACK_AUTO_PROVISION": "false", + "PER_VRF_LOOPBACK_AUTO_PROVISION_PREV": "false", + "PER_VRF_LOOPBACK_AUTO_PROVISION_V6": "false", + "PER_VRF_LOOPBACK_AUTO_PROVISION_V6_PREV": "false", + "PER_VRF_LOOPBACK_IP_RANGE": "", + "PER_VRF_LOOPBACK_IP_RANGE_V6": "", + "PHANTOM_RP_LB_ID1": "", + "PHANTOM_RP_LB_ID2": "", + "PHANTOM_RP_LB_ID3": "", + "PHANTOM_RP_LB_ID4": "", + "PIM_HELLO_AUTH_ENABLE": "false", + "PIM_HELLO_AUTH_KEY": "", + "PM_ENABLE": "false", + "PM_ENABLE_PREV": "false", + "POWER_REDUNDANCY_MODE": "ps-redundant", + "PREMSO_PARENT_FABRIC": "", + "PTP_DOMAIN_ID": "", + "PTP_LB_ID": "", + "REPLICATION_MODE": "Multicast", + "ROUTER_ID_RANGE": "", + "ROUTE_MAP_SEQUENCE_NUMBER_RANGE": "1-65534", + "RP_COUNT": "2", + "RP_LB_ID": "254", + "RP_MODE": "asm", + "RR_COUNT": "2", + "SEED_SWITCH_CORE_INTERFACES": "", + "SERVICE_NETWORK_VLAN_RANGE": "3000-3199", + "SGT_ID_RANGE": "", + "SGT_NAME_PREFIX": "", + "SGT_PREPROVISION": "", + "SITE_ID": "65001", + "SLA_ID_RANGE": "10000-19999", + "SNMP_SERVER_HOST_TRAP": "true", + "SPINE_COUNT": "0", + "SPINE_SWITCH_CORE_INTERFACES": "", + "SSPINE_ADD_DEL_DEBUG_FLAG": "Disable", + "SSPINE_COUNT": "0", + "STATIC_UNDERLAY_IP_ALLOC": "false", + "STP_BRIDGE_PRIORITY": "", + "STP_ROOT_OPTION": "unmanaged", + "STP_VLAN_RANGE": "", + "STRICT_CC_MODE": "false", + "SUBINTERFACE_RANGE": "2-511", + "SUBNET_RANGE": "10.4.0.0/16", + "SUBNET_TARGET_MASK": "30", + "SYSLOG_SERVER_IP_LIST": "", + "SYSLOG_SERVER_VRF": "", + "SYSLOG_SEV": "", + "TCAM_ALLOCATION": "true", + "TOPDOWN_CONFIG_RM_TRACKING": "notstarted", + "UNDERLAY_IS_V6": "false", + "UNNUM_BOOTSTRAP_LB_ID": "", + "UNNUM_DHCP_END": "", + "UNNUM_DHCP_END_INTERNAL": "", + "UNNUM_DHCP_START": "", + "UNNUM_DHCP_START_INTERNAL": "", + "UPGRADE_FROM_VERSION": "", + "USE_LINK_LOCAL": "true", + "V6_SUBNET_RANGE": "", + "V6_SUBNET_TARGET_MASK": "126", + "VPC_AUTO_RECOVERY_TIME": "360", + "VPC_DELAY_RESTORE": "150", + "VPC_DELAY_RESTORE_TIME": "60", + "VPC_DOMAIN_ID_RANGE": "1-1000", + "VPC_ENABLE_IPv6_ND_SYNC": "true", + "VPC_PEER_KEEP_ALIVE_OPTION": "management", + "VPC_PEER_LINK_PO": "500", + "VPC_PEER_LINK_VLAN": "3600", + "VRF_LITE_AUTOCONFIG": "Manual", + "VRF_VLAN_RANGE": "2000-2299", + "abstract_anycast_rp": "anycast_rp", + "abstract_bgp": "base_bgp", + "abstract_bgp_neighbor": "evpn_bgp_rr_neighbor", + "abstract_bgp_rr": "evpn_bgp_rr", + "abstract_dhcp": "base_dhcp", + "abstract_extra_config_bootstrap": "extra_config_bootstrap_11_1", + "abstract_extra_config_leaf": "extra_config_leaf", + "abstract_extra_config_spine": "extra_config_spine", + "abstract_extra_config_tor": "extra_config_tor", + "abstract_feature_leaf": "base_feature_leaf_upg", + "abstract_feature_spine": "base_feature_spine_upg", + "abstract_isis": "base_isis_level2", + "abstract_isis_interface": "isis_interface", + "abstract_loopback_interface": "int_fabric_loopback_11_1", + "abstract_multicast": "base_multicast_11_1", + "abstract_ospf": "base_ospf", + "abstract_ospf_interface": "ospf_interface_11_1", + "abstract_pim_interface": "pim_interface", + "abstract_route_map": "route_map", + "abstract_routed_host": "int_routed_host", + "abstract_trunk_host": "int_trunk_host", + "abstract_vlan_interface": "int_fabric_vlan_11_1", + "abstract_vpc_domain": "base_vpc_domain_11_1", + "default_network": "Default_Network_Universal", + "default_pvlan_sec_network": "", + "default_vrf": "Default_VRF_Universal", + "enableRealTimeBackup": "", + "enableScheduledBackup": "", + "network_extension_template": "Default_Network_Extension_Universal", + "scheduledTime": "", + "temp_anycast_gateway": "anycast_gateway", + "temp_vpc_domain_mgmt": "vpc_domain_mgmt", + "temp_vpc_peer_link": "int_vpc_peer_link_po", + "vrf_extension_template": "Default_VRF_Extension_Universal" + }, + "operStatus": "HEALTHY", + "provisionMode": "DCNMTopDown", + "replicationMode": "Multicast", + "siteId": "65001", + "templateName": "Easy_Fabric", + "vrfExtensionTemplate": "Default_VRF_Extension_Universal", + "vrfTemplate": "Default_VRF_Universal" + } + ], + "MESSAGE": "OK", + "METHOD": "GET", + "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/rest/control/fabrics", + "RETURN_CODE": 200 + }, + "test_fabric_update_bulk_00034a": { + "DATA": [ + { + "asn": "65001", + "createdOn": 1711411093680, + "deviceType": "n9k", + "fabricId": "FABRIC-2", + "fabricName": "f1", + "fabricTechnology": "VXLANFabric", + "fabricTechnologyFriendly": "VXLAN EVPN", + "fabricType": "Switch_Fabric", + "fabricTypeFriendly": "Switch Fabric", + "id": 2, + "modifiedOn": 1711411096857, + "networkExtensionTemplate": "Default_Network_Extension_Universal", + "networkTemplate": "Default_Network_Universal", + "nvPairs": { + "AAA_REMOTE_IP_ENABLED": "false", + "AAA_SERVER_CONF": "", + "ACTIVE_MIGRATION": "false", + "ADVERTISE_PIP_BGP": "false", + "ADVERTISE_PIP_ON_BORDER": "true", + "AGENT_INTF": "eth0", + "AI_ML_QOS_POLICY": "AI_Fabric_QOS_400G", + "ALLOW_NXC": "true", + "ALLOW_NXC_PREV": "true", + "ANYCAST_BGW_ADVERTISE_PIP": "false", + "ANYCAST_GW_MAC": "2020.0000.00aa", + "ANYCAST_LB_ID": "", + "ANYCAST_RP_IP_RANGE": "10.254.254.0/24", + "ANYCAST_RP_IP_RANGE_INTERNAL": "", + "AUTO_SYMMETRIC_DEFAULT_VRF": "false", + "AUTO_SYMMETRIC_VRF_LITE": "false", + "AUTO_UNIQUE_VRF_LITE_IP_PREFIX": "false", + "AUTO_UNIQUE_VRF_LITE_IP_PREFIX_PREV": "false", + "AUTO_VRFLITE_IFC_DEFAULT_VRF": "false", + "BANNER": "", + "BFD_AUTH_ENABLE": "false", + "BFD_AUTH_KEY": "", + "BFD_AUTH_KEY_ID": "", + "BFD_ENABLE": "false", + "BFD_ENABLE_PREV": "", + "BFD_IBGP_ENABLE": "false", + "BFD_ISIS_ENABLE": "false", + "BFD_OSPF_ENABLE": "false", + "BFD_PIM_ENABLE": "false", + "BGP_AS": "65001", + "BGP_AS_PREV": "65001", + "BGP_AUTH_ENABLE": "false", + "BGP_AUTH_KEY": "", + "BGP_AUTH_KEY_TYPE": "3", + "BGP_LB_ID": "0", + "BOOTSTRAP_CONF": "", + "BOOTSTRAP_ENABLE": "false", + "BOOTSTRAP_ENABLE_PREV": "false", + "BOOTSTRAP_MULTISUBNET": "#Scope_Start_IP, Scope_End_IP, Scope_Default_Gateway, Scope_Subnet_Prefix", + "BOOTSTRAP_MULTISUBNET_INTERNAL": "", + "BRFIELD_DEBUG_FLAG": "Disable", + "BROWNFIELD_NETWORK_NAME_FORMAT": "Auto_Net_VNI$$VNI$$_VLAN$$VLAN_ID$$", + "BROWNFIELD_SKIP_OVERLAY_NETWORK_ATTACHMENTS": "false", + "CDP_ENABLE": "false", + "COPP_POLICY": "strict", + "DCI_SUBNET_RANGE": "10.33.0.0/16", + "DCI_SUBNET_TARGET_MASK": "30", + "DEAFULT_QUEUING_POLICY_CLOUDSCALE": "queuing_policy_default_8q_cloudscale", + "DEAFULT_QUEUING_POLICY_OTHER": "queuing_policy_default_other", + "DEAFULT_QUEUING_POLICY_R_SERIES": "queuing_policy_default_r_series", + "DEFAULT_VRF_REDIS_BGP_RMAP": "", + "DEPLOYMENT_FREEZE": "false", + "DHCP_ENABLE": "false", + "DHCP_END": "", + "DHCP_END_INTERNAL": "", + "DHCP_IPV6_ENABLE": "", + "DHCP_IPV6_ENABLE_INTERNAL": "", + "DHCP_START": "", + "DHCP_START_INTERNAL": "", + "DNS_SERVER_IP_LIST": "", + "DNS_SERVER_VRF": "", + "DOMAIN_NAME_INTERNAL": "", + "ENABLE_AAA": "false", + "ENABLE_AGENT": "false", + "ENABLE_AI_ML_QOS_POLICY": "false", + "ENABLE_AI_ML_QOS_POLICY_FLAP": "false", + "ENABLE_DEFAULT_QUEUING_POLICY": "false", + "ENABLE_EVPN": "true", + "ENABLE_FABRIC_VPC_DOMAIN_ID": "false", + "ENABLE_FABRIC_VPC_DOMAIN_ID_PREV": "false", + "ENABLE_L3VNI_NO_VLAN": "false", + "ENABLE_MACSEC": "false", + "ENABLE_NETFLOW": "false", + "ENABLE_NETFLOW_PREV": "false", + "ENABLE_NGOAM": "true", + "ENABLE_NXAPI": "true", + "ENABLE_NXAPI_HTTP": "true", + "ENABLE_PBR": "false", + "ENABLE_PVLAN": "false", + "ENABLE_PVLAN_PREV": "false", + "ENABLE_SGT": "false", + "ENABLE_SGT_PREV": "false", + "ENABLE_TENANT_DHCP": "true", + "ENABLE_TRM": "false", + "ENABLE_VPC_PEER_LINK_NATIVE_VLAN": "false", + "ESR_OPTION": "PBR", + "EXTRA_CONF_INTRA_LINKS": "", + "EXTRA_CONF_LEAF": "", + "EXTRA_CONF_SPINE": "", + "EXTRA_CONF_TOR": "", + "EXT_FABRIC_TYPE": "", + "FABRIC_INTERFACE_TYPE": "p2p", + "FABRIC_MTU": "9216", + "FABRIC_MTU_PREV": "9216", + "FABRIC_NAME": "f1", + "FABRIC_TYPE": "Switch_Fabric", + "FABRIC_VPC_DOMAIN_ID": "", + "FABRIC_VPC_DOMAIN_ID_PREV": "", + "FABRIC_VPC_QOS": "false", + "FABRIC_VPC_QOS_POLICY_NAME": "spine_qos_for_fabric_vpc_peering", + "FEATURE_PTP": "false", + "FEATURE_PTP_INTERNAL": "false", + "FF": "Easy_Fabric", + "GRFIELD_DEBUG_FLAG": "Disable", + "HD_TIME": "180", + "HOST_INTF_ADMIN_STATE": "true", + "IBGP_PEER_TEMPLATE": "", + "IBGP_PEER_TEMPLATE_LEAF": "", + "INBAND_DHCP_SERVERS": "", + "INBAND_MGMT": "false", + "INBAND_MGMT_PREV": "false", + "ISIS_AREA_NUM": "0001", + "ISIS_AREA_NUM_PREV": "", + "ISIS_AUTH_ENABLE": "false", + "ISIS_AUTH_KEY": "", + "ISIS_AUTH_KEYCHAIN_KEY_ID": "", + "ISIS_AUTH_KEYCHAIN_NAME": "", + "ISIS_LEVEL": "level-2", + "ISIS_OVERLOAD_ELAPSE_TIME": "", + "ISIS_OVERLOAD_ENABLE": "", + "ISIS_P2P_ENABLE": "", + "L2_HOST_INTF_MTU": "9216", + "L2_HOST_INTF_MTU_PREV": "9216", + "L2_SEGMENT_ID_RANGE": "30000-49000", + "L3VNI_MCAST_GROUP": "", + "L3_PARTITION_ID_RANGE": "50000-59000", + "LINK_STATE_ROUTING": "ospf", + "LINK_STATE_ROUTING_TAG": "UNDERLAY", + "LINK_STATE_ROUTING_TAG_PREV": "UNDERLAY", + "LOOPBACK0_IPV6_RANGE": "", + "LOOPBACK0_IP_RANGE": "10.2.0.0/22", + "LOOPBACK1_IPV6_RANGE": "", + "LOOPBACK1_IP_RANGE": "10.3.0.0/22", + "MACSEC_ALGORITHM": "", + "MACSEC_CIPHER_SUITE": "", + "MACSEC_FALLBACK_ALGORITHM": "", + "MACSEC_FALLBACK_KEY_STRING": "", + "MACSEC_KEY_STRING": "", + "MACSEC_REPORT_TIMER": "", + "MGMT_GW": "", + "MGMT_GW_INTERNAL": "", + "MGMT_PREFIX": "", + "MGMT_PREFIX_INTERNAL": "", + "MGMT_V6PREFIX": "", + "MGMT_V6PREFIX_INTERNAL": "", + "MPLS_HANDOFF": "false", + "MPLS_ISIS_AREA_NUM": "0001", + "MPLS_ISIS_AREA_NUM_PREV": "", + "MPLS_LB_ID": "", + "MPLS_LOOPBACK_IP_RANGE": "", + "MSO_CONNECTIVITY_DEPLOYED": "", + "MSO_CONTROLER_ID": "", + "MSO_SITE_GROUP_NAME": "", + "MSO_SITE_ID": "", + "MST_INSTANCE_RANGE": "", + "MULTICAST_GROUP_SUBNET": "239.1.1.0/25", + "NETFLOW_EXPORTER_LIST": "", + "NETFLOW_MONITOR_LIST": "", + "NETFLOW_RECORD_LIST": "", + "NETWORK_VLAN_RANGE": "2300-2999", + "NTP_SERVER_IP_LIST": "", + "NTP_SERVER_VRF": "", + "NVE_LB_ID": "1", + "NXAPI_HTTPS_PORT": "443", + "NXAPI_HTTP_PORT": "80", + "NXC_DEST_VRF": "", + "NXC_PROXY_PORT": "8080", + "NXC_PROXY_SERVER": "", + "NXC_SRC_INTF": "", + "OBJECT_TRACKING_NUMBER_RANGE": "100-299", + "OSPF_AREA_ID": "0.0.0.0", + "OSPF_AUTH_ENABLE": "false", + "OSPF_AUTH_KEY": "", + "OSPF_AUTH_KEY_ID": "", + "OVERLAY_MODE": "cli", + "OVERLAY_MODE_PREV": "cli", + "OVERWRITE_GLOBAL_NXC": "false", + "PER_VRF_LOOPBACK_AUTO_PROVISION": "false", + "PER_VRF_LOOPBACK_AUTO_PROVISION_PREV": "false", + "PER_VRF_LOOPBACK_AUTO_PROVISION_V6": "false", + "PER_VRF_LOOPBACK_AUTO_PROVISION_V6_PREV": "false", + "PER_VRF_LOOPBACK_IP_RANGE": "", + "PER_VRF_LOOPBACK_IP_RANGE_V6": "", + "PHANTOM_RP_LB_ID1": "", + "PHANTOM_RP_LB_ID2": "", + "PHANTOM_RP_LB_ID3": "", + "PHANTOM_RP_LB_ID4": "", + "PIM_HELLO_AUTH_ENABLE": "false", + "PIM_HELLO_AUTH_KEY": "", + "PM_ENABLE": "false", + "PM_ENABLE_PREV": "false", + "POWER_REDUNDANCY_MODE": "ps-redundant", + "PREMSO_PARENT_FABRIC": "", + "PTP_DOMAIN_ID": "", + "PTP_LB_ID": "", + "REPLICATION_MODE": "Multicast", + "ROUTER_ID_RANGE": "", + "ROUTE_MAP_SEQUENCE_NUMBER_RANGE": "1-65534", + "RP_COUNT": "2", + "RP_LB_ID": "254", + "RP_MODE": "asm", + "RR_COUNT": "2", + "SEED_SWITCH_CORE_INTERFACES": "", + "SERVICE_NETWORK_VLAN_RANGE": "3000-3199", + "SGT_ID_RANGE": "", + "SGT_NAME_PREFIX": "", + "SGT_PREPROVISION": "", + "SITE_ID": "65001", + "SLA_ID_RANGE": "10000-19999", + "SNMP_SERVER_HOST_TRAP": "true", + "SPINE_COUNT": "0", + "SPINE_SWITCH_CORE_INTERFACES": "", + "SSPINE_ADD_DEL_DEBUG_FLAG": "Disable", + "SSPINE_COUNT": "0", + "STATIC_UNDERLAY_IP_ALLOC": "false", + "STP_BRIDGE_PRIORITY": "", + "STP_ROOT_OPTION": "unmanaged", + "STP_VLAN_RANGE": "", + "STRICT_CC_MODE": "false", + "SUBINTERFACE_RANGE": "2-511", + "SUBNET_RANGE": "10.4.0.0/16", + "SUBNET_TARGET_MASK": "30", + "SYSLOG_SERVER_IP_LIST": "", + "SYSLOG_SERVER_VRF": "", + "SYSLOG_SEV": "", + "TCAM_ALLOCATION": "true", + "TOPDOWN_CONFIG_RM_TRACKING": "notstarted", + "UNDERLAY_IS_V6": "false", + "UNNUM_BOOTSTRAP_LB_ID": "", + "UNNUM_DHCP_END": "", + "UNNUM_DHCP_END_INTERNAL": "", + "UNNUM_DHCP_START": "", + "UNNUM_DHCP_START_INTERNAL": "", + "UPGRADE_FROM_VERSION": "", + "USE_LINK_LOCAL": "true", + "V6_SUBNET_RANGE": "", + "V6_SUBNET_TARGET_MASK": "126", + "VPC_AUTO_RECOVERY_TIME": "360", + "VPC_DELAY_RESTORE": "150", + "VPC_DELAY_RESTORE_TIME": "60", + "VPC_DOMAIN_ID_RANGE": "1-1000", + "VPC_ENABLE_IPv6_ND_SYNC": "true", + "VPC_PEER_KEEP_ALIVE_OPTION": "management", + "VPC_PEER_LINK_PO": "500", + "VPC_PEER_LINK_VLAN": "3600", + "VRF_LITE_AUTOCONFIG": "Manual", + "VRF_VLAN_RANGE": "2000-2299", + "abstract_anycast_rp": "anycast_rp", + "abstract_bgp": "base_bgp", + "abstract_bgp_neighbor": "evpn_bgp_rr_neighbor", + "abstract_bgp_rr": "evpn_bgp_rr", + "abstract_dhcp": "base_dhcp", + "abstract_extra_config_bootstrap": "extra_config_bootstrap_11_1", + "abstract_extra_config_leaf": "extra_config_leaf", + "abstract_extra_config_spine": "extra_config_spine", + "abstract_extra_config_tor": "extra_config_tor", + "abstract_feature_leaf": "base_feature_leaf_upg", + "abstract_feature_spine": "base_feature_spine_upg", + "abstract_isis": "base_isis_level2", + "abstract_isis_interface": "isis_interface", + "abstract_loopback_interface": "int_fabric_loopback_11_1", + "abstract_multicast": "base_multicast_11_1", + "abstract_ospf": "base_ospf", + "abstract_ospf_interface": "ospf_interface_11_1", + "abstract_pim_interface": "pim_interface", + "abstract_route_map": "route_map", + "abstract_routed_host": "int_routed_host", + "abstract_trunk_host": "int_trunk_host", + "abstract_vlan_interface": "int_fabric_vlan_11_1", + "abstract_vpc_domain": "base_vpc_domain_11_1", + "default_network": "Default_Network_Universal", + "default_pvlan_sec_network": "", + "default_vrf": "Default_VRF_Universal", + "enableRealTimeBackup": "", + "enableScheduledBackup": "", + "network_extension_template": "Default_Network_Extension_Universal", + "scheduledTime": "", + "temp_anycast_gateway": "anycast_gateway", + "temp_vpc_domain_mgmt": "vpc_domain_mgmt", + "temp_vpc_peer_link": "int_vpc_peer_link_po", + "vrf_extension_template": "Default_VRF_Extension_Universal" + }, + "operStatus": "HEALTHY", + "provisionMode": "DCNMTopDown", + "replicationMode": "Multicast", + "siteId": "65001", + "templateName": "Easy_Fabric", + "vrfExtensionTemplate": "Default_VRF_Extension_Universal", + "vrfTemplate": "Default_VRF_Universal" + } + ], + "MESSAGE": "OK", + "METHOD": "GET", + "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/rest/control/fabrics", + "RETURN_CODE": 200 + }, + "test_fabric_update_bulk_00035a": { + "DATA": [ + { + "asn": "65001", + "createdOn": 1711411093680, + "deviceType": "n9k", + "fabricId": "FABRIC-2", + "fabricName": "f1", + "fabricTechnology": "VXLANFabric", + "fabricTechnologyFriendly": "VXLAN EVPN", + "fabricType": "Switch_Fabric", + "fabricTypeFriendly": "Switch Fabric", + "id": 2, + "modifiedOn": 1711411096857, + "networkExtensionTemplate": "Default_Network_Extension_Universal", + "networkTemplate": "Default_Network_Universal", + "nvPairs": { + "AAA_REMOTE_IP_ENABLED": "false", + "AAA_SERVER_CONF": "", + "ACTIVE_MIGRATION": "false", + "ADVERTISE_PIP_BGP": "false", + "ADVERTISE_PIP_ON_BORDER": "true", + "AGENT_INTF": "eth0", + "AI_ML_QOS_POLICY": "AI_Fabric_QOS_400G", + "ALLOW_NXC": "true", + "ALLOW_NXC_PREV": "true", + "ANYCAST_BGW_ADVERTISE_PIP": "false", + "ANYCAST_GW_MAC": "2020.0000.00aa", + "ANYCAST_LB_ID": "", + "ANYCAST_RP_IP_RANGE": "10.254.254.0/24", + "ANYCAST_RP_IP_RANGE_INTERNAL": "", + "AUTO_SYMMETRIC_DEFAULT_VRF": "false", + "AUTO_SYMMETRIC_VRF_LITE": "false", + "AUTO_UNIQUE_VRF_LITE_IP_PREFIX": "false", + "AUTO_UNIQUE_VRF_LITE_IP_PREFIX_PREV": "false", + "AUTO_VRFLITE_IFC_DEFAULT_VRF": "false", + "BANNER": "", + "BFD_AUTH_ENABLE": "false", + "BFD_AUTH_KEY": "", + "BFD_AUTH_KEY_ID": "", + "BFD_ENABLE": "false", + "BFD_ENABLE_PREV": "", + "BFD_IBGP_ENABLE": "false", + "BFD_ISIS_ENABLE": "false", + "BFD_OSPF_ENABLE": "false", + "BFD_PIM_ENABLE": "false", + "BGP_AS": "65001", + "BGP_AS_PREV": "65001", + "BGP_AUTH_ENABLE": "false", + "BGP_AUTH_KEY": "", + "BGP_AUTH_KEY_TYPE": "3", + "BGP_LB_ID": "0", + "BOOTSTRAP_CONF": "", + "BOOTSTRAP_ENABLE": "false", + "BOOTSTRAP_ENABLE_PREV": "false", + "BOOTSTRAP_MULTISUBNET": "#Scope_Start_IP, Scope_End_IP, Scope_Default_Gateway, Scope_Subnet_Prefix", + "BOOTSTRAP_MULTISUBNET_INTERNAL": "", + "BRFIELD_DEBUG_FLAG": "Disable", + "BROWNFIELD_NETWORK_NAME_FORMAT": "Auto_Net_VNI$$VNI$$_VLAN$$VLAN_ID$$", + "BROWNFIELD_SKIP_OVERLAY_NETWORK_ATTACHMENTS": "false", + "CDP_ENABLE": "false", + "COPP_POLICY": "strict", + "DCI_SUBNET_RANGE": "10.33.0.0/16", + "DCI_SUBNET_TARGET_MASK": "30", + "DEAFULT_QUEUING_POLICY_CLOUDSCALE": "queuing_policy_default_8q_cloudscale", + "DEAFULT_QUEUING_POLICY_OTHER": "queuing_policy_default_other", + "DEAFULT_QUEUING_POLICY_R_SERIES": "queuing_policy_default_r_series", + "DEFAULT_VRF_REDIS_BGP_RMAP": "", + "DEPLOYMENT_FREEZE": "false", + "DHCP_ENABLE": "false", + "DHCP_END": "", + "DHCP_END_INTERNAL": "", + "DHCP_IPV6_ENABLE": "", + "DHCP_IPV6_ENABLE_INTERNAL": "", + "DHCP_START": "", + "DHCP_START_INTERNAL": "", + "DNS_SERVER_IP_LIST": "", + "DNS_SERVER_VRF": "", + "DOMAIN_NAME_INTERNAL": "", + "ENABLE_AAA": "false", + "ENABLE_AGENT": "false", + "ENABLE_AI_ML_QOS_POLICY": "false", + "ENABLE_AI_ML_QOS_POLICY_FLAP": "false", + "ENABLE_DEFAULT_QUEUING_POLICY": "false", + "ENABLE_EVPN": "true", + "ENABLE_FABRIC_VPC_DOMAIN_ID": "false", + "ENABLE_FABRIC_VPC_DOMAIN_ID_PREV": "false", + "ENABLE_L3VNI_NO_VLAN": "false", + "ENABLE_MACSEC": "false", + "ENABLE_NETFLOW": "false", + "ENABLE_NETFLOW_PREV": "false", + "ENABLE_NGOAM": "true", + "ENABLE_NXAPI": "true", + "ENABLE_NXAPI_HTTP": "true", + "ENABLE_PBR": "false", + "ENABLE_PVLAN": "false", + "ENABLE_PVLAN_PREV": "false", + "ENABLE_SGT": "false", + "ENABLE_SGT_PREV": "false", + "ENABLE_TENANT_DHCP": "true", + "ENABLE_TRM": "false", + "ENABLE_VPC_PEER_LINK_NATIVE_VLAN": "false", + "ESR_OPTION": "PBR", + "EXTRA_CONF_INTRA_LINKS": "", + "EXTRA_CONF_LEAF": "", + "EXTRA_CONF_SPINE": "", + "EXTRA_CONF_TOR": "", + "EXT_FABRIC_TYPE": "", + "FABRIC_INTERFACE_TYPE": "p2p", + "FABRIC_MTU": "9216", + "FABRIC_MTU_PREV": "9216", + "FABRIC_NAME": "f1", + "FABRIC_TYPE": "Switch_Fabric", + "FABRIC_VPC_DOMAIN_ID": "", + "FABRIC_VPC_DOMAIN_ID_PREV": "", + "FABRIC_VPC_QOS": "false", + "FABRIC_VPC_QOS_POLICY_NAME": "spine_qos_for_fabric_vpc_peering", + "FEATURE_PTP": "false", + "FEATURE_PTP_INTERNAL": "false", + "FF": "Easy_Fabric", + "GRFIELD_DEBUG_FLAG": "Disable", + "HD_TIME": "180", + "HOST_INTF_ADMIN_STATE": "true", + "IBGP_PEER_TEMPLATE": "", + "IBGP_PEER_TEMPLATE_LEAF": "", + "INBAND_DHCP_SERVERS": "", + "INBAND_MGMT": "false", + "INBAND_MGMT_PREV": "false", + "ISIS_AREA_NUM": "0001", + "ISIS_AREA_NUM_PREV": "", + "ISIS_AUTH_ENABLE": "false", + "ISIS_AUTH_KEY": "", + "ISIS_AUTH_KEYCHAIN_KEY_ID": "", + "ISIS_AUTH_KEYCHAIN_NAME": "", + "ISIS_LEVEL": "level-2", + "ISIS_OVERLOAD_ELAPSE_TIME": "", + "ISIS_OVERLOAD_ENABLE": "", + "ISIS_P2P_ENABLE": "", + "L2_HOST_INTF_MTU": "9216", + "L2_HOST_INTF_MTU_PREV": "9216", + "L2_SEGMENT_ID_RANGE": "30000-49000", + "L3VNI_MCAST_GROUP": "", + "L3_PARTITION_ID_RANGE": "50000-59000", + "LINK_STATE_ROUTING": "ospf", + "LINK_STATE_ROUTING_TAG": "UNDERLAY", + "LINK_STATE_ROUTING_TAG_PREV": "UNDERLAY", + "LOOPBACK0_IPV6_RANGE": "", + "LOOPBACK0_IP_RANGE": "10.2.0.0/22", + "LOOPBACK1_IPV6_RANGE": "", + "LOOPBACK1_IP_RANGE": "10.3.0.0/22", + "MACSEC_ALGORITHM": "", + "MACSEC_CIPHER_SUITE": "", + "MACSEC_FALLBACK_ALGORITHM": "", + "MACSEC_FALLBACK_KEY_STRING": "", + "MACSEC_KEY_STRING": "", + "MACSEC_REPORT_TIMER": "", + "MGMT_GW": "", + "MGMT_GW_INTERNAL": "", + "MGMT_PREFIX": "", + "MGMT_PREFIX_INTERNAL": "", + "MGMT_V6PREFIX": "", + "MGMT_V6PREFIX_INTERNAL": "", + "MPLS_HANDOFF": "false", + "MPLS_ISIS_AREA_NUM": "0001", + "MPLS_ISIS_AREA_NUM_PREV": "", + "MPLS_LB_ID": "", + "MPLS_LOOPBACK_IP_RANGE": "", + "MSO_CONNECTIVITY_DEPLOYED": "", + "MSO_CONTROLER_ID": "", + "MSO_SITE_GROUP_NAME": "", + "MSO_SITE_ID": "", + "MST_INSTANCE_RANGE": "", + "MULTICAST_GROUP_SUBNET": "239.1.1.0/25", + "NETFLOW_EXPORTER_LIST": "", + "NETFLOW_MONITOR_LIST": "", + "NETFLOW_RECORD_LIST": "", + "NETWORK_VLAN_RANGE": "2300-2999", + "NTP_SERVER_IP_LIST": "", + "NTP_SERVER_VRF": "", + "NVE_LB_ID": "1", + "NXAPI_HTTPS_PORT": "443", + "NXAPI_HTTP_PORT": "80", + "NXC_DEST_VRF": "", + "NXC_PROXY_PORT": "8080", + "NXC_PROXY_SERVER": "", + "NXC_SRC_INTF": "", + "OBJECT_TRACKING_NUMBER_RANGE": "100-299", + "OSPF_AREA_ID": "0.0.0.0", + "OSPF_AUTH_ENABLE": "false", + "OSPF_AUTH_KEY": "", + "OSPF_AUTH_KEY_ID": "", + "OVERLAY_MODE": "cli", + "OVERLAY_MODE_PREV": "cli", + "OVERWRITE_GLOBAL_NXC": "false", + "PER_VRF_LOOPBACK_AUTO_PROVISION": "false", + "PER_VRF_LOOPBACK_AUTO_PROVISION_PREV": "false", + "PER_VRF_LOOPBACK_AUTO_PROVISION_V6": "false", + "PER_VRF_LOOPBACK_AUTO_PROVISION_V6_PREV": "false", + "PER_VRF_LOOPBACK_IP_RANGE": "", + "PER_VRF_LOOPBACK_IP_RANGE_V6": "", + "PHANTOM_RP_LB_ID1": "", + "PHANTOM_RP_LB_ID2": "", + "PHANTOM_RP_LB_ID3": "", + "PHANTOM_RP_LB_ID4": "", + "PIM_HELLO_AUTH_ENABLE": "false", + "PIM_HELLO_AUTH_KEY": "", + "PM_ENABLE": "false", + "PM_ENABLE_PREV": "false", + "POWER_REDUNDANCY_MODE": "ps-redundant", + "PREMSO_PARENT_FABRIC": "", + "PTP_DOMAIN_ID": "", + "PTP_LB_ID": "", + "REPLICATION_MODE": "Multicast", + "ROUTER_ID_RANGE": "", + "ROUTE_MAP_SEQUENCE_NUMBER_RANGE": "1-65534", + "RP_COUNT": "2", + "RP_LB_ID": "254", + "RP_MODE": "asm", + "RR_COUNT": "2", + "SEED_SWITCH_CORE_INTERFACES": "", + "SERVICE_NETWORK_VLAN_RANGE": "3000-3199", + "SGT_ID_RANGE": "", + "SGT_NAME_PREFIX": "", + "SGT_PREPROVISION": "", + "SITE_ID": "65001", + "SLA_ID_RANGE": "10000-19999", + "SNMP_SERVER_HOST_TRAP": "true", + "SPINE_COUNT": "0", + "SPINE_SWITCH_CORE_INTERFACES": "", + "SSPINE_ADD_DEL_DEBUG_FLAG": "Disable", + "SSPINE_COUNT": "0", + "STATIC_UNDERLAY_IP_ALLOC": "false", + "STP_BRIDGE_PRIORITY": "", + "STP_ROOT_OPTION": "unmanaged", + "STP_VLAN_RANGE": "", + "STRICT_CC_MODE": "false", + "SUBINTERFACE_RANGE": "2-511", + "SUBNET_RANGE": "10.4.0.0/16", + "SUBNET_TARGET_MASK": "30", + "SYSLOG_SERVER_IP_LIST": "", + "SYSLOG_SERVER_VRF": "", + "SYSLOG_SEV": "", + "TCAM_ALLOCATION": "true", + "TOPDOWN_CONFIG_RM_TRACKING": "notstarted", + "UNDERLAY_IS_V6": "false", + "UNNUM_BOOTSTRAP_LB_ID": "", + "UNNUM_DHCP_END": "", + "UNNUM_DHCP_END_INTERNAL": "", + "UNNUM_DHCP_START": "", + "UNNUM_DHCP_START_INTERNAL": "", + "UPGRADE_FROM_VERSION": "", + "USE_LINK_LOCAL": "true", + "V6_SUBNET_RANGE": "", + "V6_SUBNET_TARGET_MASK": "126", + "VPC_AUTO_RECOVERY_TIME": "360", + "VPC_DELAY_RESTORE": "150", + "VPC_DELAY_RESTORE_TIME": "60", + "VPC_DOMAIN_ID_RANGE": "1-1000", + "VPC_ENABLE_IPv6_ND_SYNC": "true", + "VPC_PEER_KEEP_ALIVE_OPTION": "management", + "VPC_PEER_LINK_PO": "500", + "VPC_PEER_LINK_VLAN": "3600", + "VRF_LITE_AUTOCONFIG": "Manual", + "VRF_VLAN_RANGE": "2000-2299", + "abstract_anycast_rp": "anycast_rp", + "abstract_bgp": "base_bgp", + "abstract_bgp_neighbor": "evpn_bgp_rr_neighbor", + "abstract_bgp_rr": "evpn_bgp_rr", + "abstract_dhcp": "base_dhcp", + "abstract_extra_config_bootstrap": "extra_config_bootstrap_11_1", + "abstract_extra_config_leaf": "extra_config_leaf", + "abstract_extra_config_spine": "extra_config_spine", + "abstract_extra_config_tor": "extra_config_tor", + "abstract_feature_leaf": "base_feature_leaf_upg", + "abstract_feature_spine": "base_feature_spine_upg", + "abstract_isis": "base_isis_level2", + "abstract_isis_interface": "isis_interface", + "abstract_loopback_interface": "int_fabric_loopback_11_1", + "abstract_multicast": "base_multicast_11_1", + "abstract_ospf": "base_ospf", + "abstract_ospf_interface": "ospf_interface_11_1", + "abstract_pim_interface": "pim_interface", + "abstract_route_map": "route_map", + "abstract_routed_host": "int_routed_host", + "abstract_trunk_host": "int_trunk_host", + "abstract_vlan_interface": "int_fabric_vlan_11_1", + "abstract_vpc_domain": "base_vpc_domain_11_1", + "default_network": "Default_Network_Universal", + "default_pvlan_sec_network": "", + "default_vrf": "Default_VRF_Universal", + "enableRealTimeBackup": "", + "enableScheduledBackup": "", + "network_extension_template": "Default_Network_Extension_Universal", + "scheduledTime": "", + "temp_anycast_gateway": "anycast_gateway", + "temp_vpc_domain_mgmt": "vpc_domain_mgmt", + "temp_vpc_peer_link": "int_vpc_peer_link_po", + "vrf_extension_template": "Default_VRF_Extension_Universal" + }, + "operStatus": "HEALTHY", + "provisionMode": "DCNMTopDown", + "replicationMode": "Multicast", + "siteId": "65001", + "templateName": "Easy_Fabric", + "vrfExtensionTemplate": "Default_VRF_Extension_Universal", + "vrfTemplate": "Default_VRF_Universal" + } + ], + "MESSAGE": "OK", + "METHOD": "GET", + "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/rest/control/fabrics", + "RETURN_CODE": 200 + }, + "test_fabric_update_bulk_00036a": { + "DATA": [ + { + "asn": "65001", + "createdOn": 1711411093680, + "deviceType": "n9k", + "fabricId": "FABRIC-2", + "fabricName": "f1", + "fabricTechnology": "VXLANFabric", + "fabricTechnologyFriendly": "VXLAN EVPN", + "fabricType": "Switch_Fabric", + "fabricTypeFriendly": "Switch Fabric", + "id": 2, + "modifiedOn": 1711411096857, + "networkExtensionTemplate": "Default_Network_Extension_Universal", + "networkTemplate": "Default_Network_Universal", + "nvPairs": { + "AAA_REMOTE_IP_ENABLED": "false", + "AAA_SERVER_CONF": "", + "ACTIVE_MIGRATION": "false", + "ADVERTISE_PIP_BGP": "false", + "ADVERTISE_PIP_ON_BORDER": "true", + "AGENT_INTF": "eth0", + "AI_ML_QOS_POLICY": "AI_Fabric_QOS_400G", + "ALLOW_NXC": "true", + "ALLOW_NXC_PREV": "true", + "ANYCAST_BGW_ADVERTISE_PIP": "false", + "ANYCAST_GW_MAC": "2020.0000.00aa", + "ANYCAST_LB_ID": "", + "ANYCAST_RP_IP_RANGE": "10.254.254.0/24", + "ANYCAST_RP_IP_RANGE_INTERNAL": "", + "AUTO_SYMMETRIC_DEFAULT_VRF": "false", + "AUTO_SYMMETRIC_VRF_LITE": "false", + "AUTO_UNIQUE_VRF_LITE_IP_PREFIX": "false", + "AUTO_UNIQUE_VRF_LITE_IP_PREFIX_PREV": "false", + "AUTO_VRFLITE_IFC_DEFAULT_VRF": "false", + "BANNER": "", + "BFD_AUTH_ENABLE": "false", + "BFD_AUTH_KEY": "", + "BFD_AUTH_KEY_ID": "", + "BFD_ENABLE": "false", + "BFD_ENABLE_PREV": "", + "BFD_IBGP_ENABLE": "false", + "BFD_ISIS_ENABLE": "false", + "BFD_OSPF_ENABLE": "false", + "BFD_PIM_ENABLE": "false", + "BGP_AS": "65001", + "BGP_AS_PREV": "65001", + "BGP_AUTH_ENABLE": "false", + "BGP_AUTH_KEY": "", + "BGP_AUTH_KEY_TYPE": "3", + "BGP_LB_ID": "0", + "BOOTSTRAP_CONF": "", + "BOOTSTRAP_ENABLE": "false", + "BOOTSTRAP_ENABLE_PREV": "false", + "BOOTSTRAP_MULTISUBNET": "#Scope_Start_IP, Scope_End_IP, Scope_Default_Gateway, Scope_Subnet_Prefix", + "BOOTSTRAP_MULTISUBNET_INTERNAL": "", + "BRFIELD_DEBUG_FLAG": "Disable", + "BROWNFIELD_NETWORK_NAME_FORMAT": "Auto_Net_VNI$$VNI$$_VLAN$$VLAN_ID$$", + "BROWNFIELD_SKIP_OVERLAY_NETWORK_ATTACHMENTS": "false", + "CDP_ENABLE": "false", + "COPP_POLICY": "strict", + "DCI_SUBNET_RANGE": "10.33.0.0/16", + "DCI_SUBNET_TARGET_MASK": "30", + "DEAFULT_QUEUING_POLICY_CLOUDSCALE": "queuing_policy_default_8q_cloudscale", + "DEAFULT_QUEUING_POLICY_OTHER": "queuing_policy_default_other", + "DEAFULT_QUEUING_POLICY_R_SERIES": "queuing_policy_default_r_series", + "DEFAULT_VRF_REDIS_BGP_RMAP": "", + "DEPLOYMENT_FREEZE": "false", + "DHCP_ENABLE": "false", + "DHCP_END": "", + "DHCP_END_INTERNAL": "", + "DHCP_IPV6_ENABLE": "", + "DHCP_IPV6_ENABLE_INTERNAL": "", + "DHCP_START": "", + "DHCP_START_INTERNAL": "", + "DNS_SERVER_IP_LIST": "", + "DNS_SERVER_VRF": "", + "DOMAIN_NAME_INTERNAL": "", + "ENABLE_AAA": "false", + "ENABLE_AGENT": "false", + "ENABLE_AI_ML_QOS_POLICY": "false", + "ENABLE_AI_ML_QOS_POLICY_FLAP": "false", + "ENABLE_DEFAULT_QUEUING_POLICY": "false", + "ENABLE_EVPN": "true", + "ENABLE_FABRIC_VPC_DOMAIN_ID": "false", + "ENABLE_FABRIC_VPC_DOMAIN_ID_PREV": "false", + "ENABLE_L3VNI_NO_VLAN": "false", + "ENABLE_MACSEC": "false", + "ENABLE_NETFLOW": "false", + "ENABLE_NETFLOW_PREV": "false", + "ENABLE_NGOAM": "true", + "ENABLE_NXAPI": "true", + "ENABLE_NXAPI_HTTP": "true", + "ENABLE_PBR": "false", + "ENABLE_PVLAN": "false", + "ENABLE_PVLAN_PREV": "false", + "ENABLE_SGT": "false", + "ENABLE_SGT_PREV": "false", + "ENABLE_TENANT_DHCP": "true", + "ENABLE_TRM": "false", + "ENABLE_VPC_PEER_LINK_NATIVE_VLAN": "false", + "ESR_OPTION": "PBR", + "EXTRA_CONF_INTRA_LINKS": "", + "EXTRA_CONF_LEAF": "", + "EXTRA_CONF_SPINE": "", + "EXTRA_CONF_TOR": "", + "EXT_FABRIC_TYPE": "", + "FABRIC_INTERFACE_TYPE": "p2p", + "FABRIC_MTU": "9216", + "FABRIC_MTU_PREV": "9216", + "FABRIC_NAME": "f1", + "FABRIC_TYPE": "Switch_Fabric", + "FABRIC_VPC_DOMAIN_ID": "", + "FABRIC_VPC_DOMAIN_ID_PREV": "", + "FABRIC_VPC_QOS": "false", + "FABRIC_VPC_QOS_POLICY_NAME": "spine_qos_for_fabric_vpc_peering", + "FEATURE_PTP": "false", + "FEATURE_PTP_INTERNAL": "false", + "FF": "Easy_Fabric", + "GRFIELD_DEBUG_FLAG": "Disable", + "HD_TIME": "180", + "HOST_INTF_ADMIN_STATE": "true", + "IBGP_PEER_TEMPLATE": "", + "IBGP_PEER_TEMPLATE_LEAF": "", + "INBAND_DHCP_SERVERS": "", + "INBAND_MGMT": "false", + "INBAND_MGMT_PREV": "false", + "ISIS_AREA_NUM": "0001", + "ISIS_AREA_NUM_PREV": "", + "ISIS_AUTH_ENABLE": "false", + "ISIS_AUTH_KEY": "", + "ISIS_AUTH_KEYCHAIN_KEY_ID": "", + "ISIS_AUTH_KEYCHAIN_NAME": "", + "ISIS_LEVEL": "level-2", + "ISIS_OVERLOAD_ELAPSE_TIME": "", + "ISIS_OVERLOAD_ENABLE": "", + "ISIS_P2P_ENABLE": "", + "L2_HOST_INTF_MTU": "9216", + "L2_HOST_INTF_MTU_PREV": "9216", + "L2_SEGMENT_ID_RANGE": "30000-49000", + "L3VNI_MCAST_GROUP": "", + "L3_PARTITION_ID_RANGE": "50000-59000", + "LINK_STATE_ROUTING": "ospf", + "LINK_STATE_ROUTING_TAG": "UNDERLAY", + "LINK_STATE_ROUTING_TAG_PREV": "UNDERLAY", + "LOOPBACK0_IPV6_RANGE": "", + "LOOPBACK0_IP_RANGE": "10.2.0.0/22", + "LOOPBACK1_IPV6_RANGE": "", + "LOOPBACK1_IP_RANGE": "10.3.0.0/22", + "MACSEC_ALGORITHM": "", + "MACSEC_CIPHER_SUITE": "", + "MACSEC_FALLBACK_ALGORITHM": "", + "MACSEC_FALLBACK_KEY_STRING": "", + "MACSEC_KEY_STRING": "", + "MACSEC_REPORT_TIMER": "", + "MGMT_GW": "", + "MGMT_GW_INTERNAL": "", + "MGMT_PREFIX": "", + "MGMT_PREFIX_INTERNAL": "", + "MGMT_V6PREFIX": "", + "MGMT_V6PREFIX_INTERNAL": "", + "MPLS_HANDOFF": "false", + "MPLS_ISIS_AREA_NUM": "0001", + "MPLS_ISIS_AREA_NUM_PREV": "", + "MPLS_LB_ID": "", + "MPLS_LOOPBACK_IP_RANGE": "", + "MSO_CONNECTIVITY_DEPLOYED": "", + "MSO_CONTROLER_ID": "", + "MSO_SITE_GROUP_NAME": "", + "MSO_SITE_ID": "", + "MST_INSTANCE_RANGE": "", + "MULTICAST_GROUP_SUBNET": "239.1.1.0/25", + "NETFLOW_EXPORTER_LIST": "", + "NETFLOW_MONITOR_LIST": "", + "NETFLOW_RECORD_LIST": "", + "NETWORK_VLAN_RANGE": "2300-2999", + "NTP_SERVER_IP_LIST": "", + "NTP_SERVER_VRF": "", + "NVE_LB_ID": "1", + "NXAPI_HTTPS_PORT": "443", + "NXAPI_HTTP_PORT": "80", + "NXC_DEST_VRF": "", + "NXC_PROXY_PORT": "8080", + "NXC_PROXY_SERVER": "", + "NXC_SRC_INTF": "", + "OBJECT_TRACKING_NUMBER_RANGE": "100-299", + "OSPF_AREA_ID": "0.0.0.0", + "OSPF_AUTH_ENABLE": "false", + "OSPF_AUTH_KEY": "", + "OSPF_AUTH_KEY_ID": "", + "OVERLAY_MODE": "cli", + "OVERLAY_MODE_PREV": "cli", + "OVERWRITE_GLOBAL_NXC": "false", + "PER_VRF_LOOPBACK_AUTO_PROVISION": "false", + "PER_VRF_LOOPBACK_AUTO_PROVISION_PREV": "false", + "PER_VRF_LOOPBACK_AUTO_PROVISION_V6": "false", + "PER_VRF_LOOPBACK_AUTO_PROVISION_V6_PREV": "false", + "PER_VRF_LOOPBACK_IP_RANGE": "", + "PER_VRF_LOOPBACK_IP_RANGE_V6": "", + "PHANTOM_RP_LB_ID1": "", + "PHANTOM_RP_LB_ID2": "", + "PHANTOM_RP_LB_ID3": "", + "PHANTOM_RP_LB_ID4": "", + "PIM_HELLO_AUTH_ENABLE": "false", + "PIM_HELLO_AUTH_KEY": "", + "PM_ENABLE": "false", + "PM_ENABLE_PREV": "false", + "POWER_REDUNDANCY_MODE": "ps-redundant", + "PREMSO_PARENT_FABRIC": "", + "PTP_DOMAIN_ID": "", + "PTP_LB_ID": "", + "REPLICATION_MODE": "Multicast", + "ROUTER_ID_RANGE": "", + "ROUTE_MAP_SEQUENCE_NUMBER_RANGE": "1-65534", + "RP_COUNT": "2", + "RP_LB_ID": "254", + "RP_MODE": "asm", + "RR_COUNT": "2", + "SEED_SWITCH_CORE_INTERFACES": "", + "SERVICE_NETWORK_VLAN_RANGE": "3000-3199", + "SGT_ID_RANGE": "", + "SGT_NAME_PREFIX": "", + "SGT_PREPROVISION": "", + "SITE_ID": "65001", + "SLA_ID_RANGE": "10000-19999", + "SNMP_SERVER_HOST_TRAP": "true", + "SPINE_COUNT": "0", + "SPINE_SWITCH_CORE_INTERFACES": "", + "SSPINE_ADD_DEL_DEBUG_FLAG": "Disable", + "SSPINE_COUNT": "0", + "STATIC_UNDERLAY_IP_ALLOC": "false", + "STP_BRIDGE_PRIORITY": "", + "STP_ROOT_OPTION": "unmanaged", + "STP_VLAN_RANGE": "", + "STRICT_CC_MODE": "false", + "SUBINTERFACE_RANGE": "2-511", + "SUBNET_RANGE": "10.4.0.0/16", + "SUBNET_TARGET_MASK": "30", + "SYSLOG_SERVER_IP_LIST": "", + "SYSLOG_SERVER_VRF": "", + "SYSLOG_SEV": "", + "TCAM_ALLOCATION": "true", + "TOPDOWN_CONFIG_RM_TRACKING": "notstarted", + "UNDERLAY_IS_V6": "false", + "UNNUM_BOOTSTRAP_LB_ID": "", + "UNNUM_DHCP_END": "", + "UNNUM_DHCP_END_INTERNAL": "", + "UNNUM_DHCP_START": "", + "UNNUM_DHCP_START_INTERNAL": "", + "UPGRADE_FROM_VERSION": "", + "USE_LINK_LOCAL": "true", + "V6_SUBNET_RANGE": "", + "V6_SUBNET_TARGET_MASK": "126", + "VPC_AUTO_RECOVERY_TIME": "360", + "VPC_DELAY_RESTORE": "150", + "VPC_DELAY_RESTORE_TIME": "60", + "VPC_DOMAIN_ID_RANGE": "1-1000", + "VPC_ENABLE_IPv6_ND_SYNC": "true", + "VPC_PEER_KEEP_ALIVE_OPTION": "management", + "VPC_PEER_LINK_PO": "500", + "VPC_PEER_LINK_VLAN": "3600", + "VRF_LITE_AUTOCONFIG": "Manual", + "VRF_VLAN_RANGE": "2000-2299", + "abstract_anycast_rp": "anycast_rp", + "abstract_bgp": "base_bgp", + "abstract_bgp_neighbor": "evpn_bgp_rr_neighbor", + "abstract_bgp_rr": "evpn_bgp_rr", + "abstract_dhcp": "base_dhcp", + "abstract_extra_config_bootstrap": "extra_config_bootstrap_11_1", + "abstract_extra_config_leaf": "extra_config_leaf", + "abstract_extra_config_spine": "extra_config_spine", + "abstract_extra_config_tor": "extra_config_tor", + "abstract_feature_leaf": "base_feature_leaf_upg", + "abstract_feature_spine": "base_feature_spine_upg", + "abstract_isis": "base_isis_level2", + "abstract_isis_interface": "isis_interface", + "abstract_loopback_interface": "int_fabric_loopback_11_1", + "abstract_multicast": "base_multicast_11_1", + "abstract_ospf": "base_ospf", + "abstract_ospf_interface": "ospf_interface_11_1", + "abstract_pim_interface": "pim_interface", + "abstract_route_map": "route_map", + "abstract_routed_host": "int_routed_host", + "abstract_trunk_host": "int_trunk_host", + "abstract_vlan_interface": "int_fabric_vlan_11_1", + "abstract_vpc_domain": "base_vpc_domain_11_1", + "default_network": "Default_Network_Universal", + "default_pvlan_sec_network": "", + "default_vrf": "Default_VRF_Universal", + "enableRealTimeBackup": "", + "enableScheduledBackup": "", + "network_extension_template": "Default_Network_Extension_Universal", + "scheduledTime": "", + "temp_anycast_gateway": "anycast_gateway", + "temp_vpc_domain_mgmt": "vpc_domain_mgmt", + "temp_vpc_peer_link": "int_vpc_peer_link_po", + "vrf_extension_template": "Default_VRF_Extension_Universal" + }, + "operStatus": "HEALTHY", + "provisionMode": "DCNMTopDown", + "replicationMode": "Multicast", + "siteId": "65001", + "templateName": "Easy_Fabric", + "vrfExtensionTemplate": "Default_VRF_Extension_Universal", + "vrfTemplate": "Default_VRF_Universal" + } + ], + "MESSAGE": "OK", + "METHOD": "GET", + "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/rest/control/fabrics", + "RETURN_CODE": 200 + }, + "test_fabric_update_bulk_00040a": { + "DATA": [ + { + "asn": "65001", + "createdOn": 1711411093680, + "deviceType": "n9k", + "fabricId": "FABRIC-2", + "fabricName": "f1", + "fabricTechnology": "VXLANFabric", + "fabricTechnologyFriendly": "VXLAN EVPN", + "fabricType": "Switch_Fabric", + "fabricTypeFriendly": "Switch Fabric", + "id": 2, + "modifiedOn": 1711411096857, + "networkExtensionTemplate": "Default_Network_Extension_Universal", + "networkTemplate": "Default_Network_Universal", + "nvPairs": { + "AAA_REMOTE_IP_ENABLED": "false", + "AAA_SERVER_CONF": "", + "ACTIVE_MIGRATION": "false", + "ADVERTISE_PIP_BGP": "false", + "ADVERTISE_PIP_ON_BORDER": "true", + "AGENT_INTF": "eth0", + "AI_ML_QOS_POLICY": "AI_Fabric_QOS_400G", + "ALLOW_NXC": "true", + "ALLOW_NXC_PREV": "true", + "ANYCAST_BGW_ADVERTISE_PIP": "false", + "ANYCAST_GW_MAC": "2020.0000.00aa", + "ANYCAST_LB_ID": "", + "ANYCAST_RP_IP_RANGE": "10.254.254.0/24", + "ANYCAST_RP_IP_RANGE_INTERNAL": "", + "AUTO_SYMMETRIC_DEFAULT_VRF": "false", + "AUTO_SYMMETRIC_VRF_LITE": "false", + "AUTO_UNIQUE_VRF_LITE_IP_PREFIX": "false", + "AUTO_UNIQUE_VRF_LITE_IP_PREFIX_PREV": "false", + "AUTO_VRFLITE_IFC_DEFAULT_VRF": "false", + "BANNER": "", + "BFD_AUTH_ENABLE": "false", + "BFD_AUTH_KEY": "", + "BFD_AUTH_KEY_ID": "", + "BFD_ENABLE": "false", + "BFD_ENABLE_PREV": "", + "BFD_IBGP_ENABLE": "false", + "BFD_ISIS_ENABLE": "false", + "BFD_OSPF_ENABLE": "false", + "BFD_PIM_ENABLE": "false", + "BGP_AS": "65002", + "BGP_AS_PREV": "65002", + "BGP_AUTH_ENABLE": "false", + "BGP_AUTH_KEY": "", + "BGP_AUTH_KEY_TYPE": "3", + "BGP_LB_ID": "0", + "BOOTSTRAP_CONF": "", + "BOOTSTRAP_ENABLE": "false", + "BOOTSTRAP_ENABLE_PREV": "false", + "BOOTSTRAP_MULTISUBNET": "#Scope_Start_IP, Scope_End_IP, Scope_Default_Gateway, Scope_Subnet_Prefix", + "BOOTSTRAP_MULTISUBNET_INTERNAL": "", + "BRFIELD_DEBUG_FLAG": "Disable", + "BROWNFIELD_NETWORK_NAME_FORMAT": "Auto_Net_VNI$$VNI$$_VLAN$$VLAN_ID$$", + "BROWNFIELD_SKIP_OVERLAY_NETWORK_ATTACHMENTS": "false", + "CDP_ENABLE": "false", + "COPP_POLICY": "strict", + "DCI_SUBNET_RANGE": "10.33.0.0/16", + "DCI_SUBNET_TARGET_MASK": "30", + "DEAFULT_QUEUING_POLICY_CLOUDSCALE": "queuing_policy_default_8q_cloudscale", + "DEAFULT_QUEUING_POLICY_OTHER": "queuing_policy_default_other", + "DEAFULT_QUEUING_POLICY_R_SERIES": "queuing_policy_default_r_series", + "DEFAULT_VRF_REDIS_BGP_RMAP": "", + "DEPLOYMENT_FREEZE": "false", + "DHCP_ENABLE": "false", + "DHCP_END": "", + "DHCP_END_INTERNAL": "", + "DHCP_IPV6_ENABLE": "", + "DHCP_IPV6_ENABLE_INTERNAL": "", + "DHCP_START": "", + "DHCP_START_INTERNAL": "", + "DNS_SERVER_IP_LIST": "", + "DNS_SERVER_VRF": "", + "DOMAIN_NAME_INTERNAL": "", + "ENABLE_AAA": "false", + "ENABLE_AGENT": "false", + "ENABLE_AI_ML_QOS_POLICY": "false", + "ENABLE_AI_ML_QOS_POLICY_FLAP": "false", + "ENABLE_DEFAULT_QUEUING_POLICY": "false", + "ENABLE_EVPN": "true", + "ENABLE_FABRIC_VPC_DOMAIN_ID": "false", + "ENABLE_FABRIC_VPC_DOMAIN_ID_PREV": "false", + "ENABLE_L3VNI_NO_VLAN": "false", + "ENABLE_MACSEC": "false", + "ENABLE_NETFLOW": "false", + "ENABLE_NETFLOW_PREV": "false", + "ENABLE_NGOAM": "true", + "ENABLE_NXAPI": "true", + "ENABLE_NXAPI_HTTP": "true", + "ENABLE_PBR": "false", + "ENABLE_PVLAN": "false", + "ENABLE_PVLAN_PREV": "false", + "ENABLE_SGT": "false", + "ENABLE_SGT_PREV": "false", + "ENABLE_TENANT_DHCP": "true", + "ENABLE_TRM": "false", + "ENABLE_VPC_PEER_LINK_NATIVE_VLAN": "false", + "ESR_OPTION": "PBR", + "EXTRA_CONF_INTRA_LINKS": "", + "EXTRA_CONF_LEAF": "", + "EXTRA_CONF_SPINE": "", + "EXTRA_CONF_TOR": "", + "EXT_FABRIC_TYPE": "", + "FABRIC_INTERFACE_TYPE": "p2p", + "FABRIC_MTU": "9216", + "FABRIC_MTU_PREV": "9216", + "FABRIC_NAME": "f1", + "FABRIC_TYPE": "Switch_Fabric", + "FABRIC_VPC_DOMAIN_ID": "", + "FABRIC_VPC_DOMAIN_ID_PREV": "", + "FABRIC_VPC_QOS": "false", + "FABRIC_VPC_QOS_POLICY_NAME": "spine_qos_for_fabric_vpc_peering", + "FEATURE_PTP": "false", + "FEATURE_PTP_INTERNAL": "false", + "FF": "Easy_Fabric", + "GRFIELD_DEBUG_FLAG": "Disable", + "HD_TIME": "180", + "HOST_INTF_ADMIN_STATE": "true", + "IBGP_PEER_TEMPLATE": "", + "IBGP_PEER_TEMPLATE_LEAF": "", + "INBAND_DHCP_SERVERS": "", + "INBAND_MGMT": "false", + "INBAND_MGMT_PREV": "false", + "ISIS_AREA_NUM": "0001", + "ISIS_AREA_NUM_PREV": "", + "ISIS_AUTH_ENABLE": "false", + "ISIS_AUTH_KEY": "", + "ISIS_AUTH_KEYCHAIN_KEY_ID": "", + "ISIS_AUTH_KEYCHAIN_NAME": "", + "ISIS_LEVEL": "level-2", + "ISIS_OVERLOAD_ELAPSE_TIME": "", + "ISIS_OVERLOAD_ENABLE": "", + "ISIS_P2P_ENABLE": "", + "L2_HOST_INTF_MTU": "9216", + "L2_HOST_INTF_MTU_PREV": "9216", + "L2_SEGMENT_ID_RANGE": "30000-49000", + "L3VNI_MCAST_GROUP": "", + "L3_PARTITION_ID_RANGE": "50000-59000", + "LINK_STATE_ROUTING": "ospf", + "LINK_STATE_ROUTING_TAG": "UNDERLAY", + "LINK_STATE_ROUTING_TAG_PREV": "UNDERLAY", + "LOOPBACK0_IPV6_RANGE": "", + "LOOPBACK0_IP_RANGE": "10.2.0.0/22", + "LOOPBACK1_IPV6_RANGE": "", + "LOOPBACK1_IP_RANGE": "10.3.0.0/22", + "MACSEC_ALGORITHM": "", + "MACSEC_CIPHER_SUITE": "", + "MACSEC_FALLBACK_ALGORITHM": "", + "MACSEC_FALLBACK_KEY_STRING": "", + "MACSEC_KEY_STRING": "", + "MACSEC_REPORT_TIMER": "", + "MGMT_GW": "", + "MGMT_GW_INTERNAL": "", + "MGMT_PREFIX": "", + "MGMT_PREFIX_INTERNAL": "", + "MGMT_V6PREFIX": "", + "MGMT_V6PREFIX_INTERNAL": "", + "MPLS_HANDOFF": "false", + "MPLS_ISIS_AREA_NUM": "0001", + "MPLS_ISIS_AREA_NUM_PREV": "", + "MPLS_LB_ID": "", + "MPLS_LOOPBACK_IP_RANGE": "", + "MSO_CONNECTIVITY_DEPLOYED": "", + "MSO_CONTROLER_ID": "", + "MSO_SITE_GROUP_NAME": "", + "MSO_SITE_ID": "", + "MST_INSTANCE_RANGE": "", + "MULTICAST_GROUP_SUBNET": "239.1.1.0/25", + "NETFLOW_EXPORTER_LIST": "", + "NETFLOW_MONITOR_LIST": "", + "NETFLOW_RECORD_LIST": "", + "NETWORK_VLAN_RANGE": "2300-2999", + "NTP_SERVER_IP_LIST": "", + "NTP_SERVER_VRF": "", + "NVE_LB_ID": "1", + "NXAPI_HTTPS_PORT": "443", + "NXAPI_HTTP_PORT": "80", + "NXC_DEST_VRF": "", + "NXC_PROXY_PORT": "8080", + "NXC_PROXY_SERVER": "", + "NXC_SRC_INTF": "", + "OBJECT_TRACKING_NUMBER_RANGE": "100-299", + "OSPF_AREA_ID": "0.0.0.0", + "OSPF_AUTH_ENABLE": "false", + "OSPF_AUTH_KEY": "", + "OSPF_AUTH_KEY_ID": "", + "OVERLAY_MODE": "cli", + "OVERLAY_MODE_PREV": "cli", + "OVERWRITE_GLOBAL_NXC": "false", + "PER_VRF_LOOPBACK_AUTO_PROVISION": "false", + "PER_VRF_LOOPBACK_AUTO_PROVISION_PREV": "false", + "PER_VRF_LOOPBACK_AUTO_PROVISION_V6": "false", + "PER_VRF_LOOPBACK_AUTO_PROVISION_V6_PREV": "false", + "PER_VRF_LOOPBACK_IP_RANGE": "", + "PER_VRF_LOOPBACK_IP_RANGE_V6": "", + "PHANTOM_RP_LB_ID1": "", + "PHANTOM_RP_LB_ID2": "", + "PHANTOM_RP_LB_ID3": "", + "PHANTOM_RP_LB_ID4": "", + "PIM_HELLO_AUTH_ENABLE": "false", + "PIM_HELLO_AUTH_KEY": "", + "PM_ENABLE": "false", + "PM_ENABLE_PREV": "false", + "POWER_REDUNDANCY_MODE": "ps-redundant", + "PREMSO_PARENT_FABRIC": "", + "PTP_DOMAIN_ID": "", + "PTP_LB_ID": "", + "REPLICATION_MODE": "Multicast", + "ROUTER_ID_RANGE": "", + "ROUTE_MAP_SEQUENCE_NUMBER_RANGE": "1-65534", + "RP_COUNT": "2", + "RP_LB_ID": "254", + "RP_MODE": "asm", + "RR_COUNT": "2", + "SEED_SWITCH_CORE_INTERFACES": "", + "SERVICE_NETWORK_VLAN_RANGE": "3000-3199", + "SGT_ID_RANGE": "", + "SGT_NAME_PREFIX": "", + "SGT_PREPROVISION": "", + "SITE_ID": "65001", + "SLA_ID_RANGE": "10000-19999", + "SNMP_SERVER_HOST_TRAP": "true", + "SPINE_COUNT": "0", + "SPINE_SWITCH_CORE_INTERFACES": "", + "SSPINE_ADD_DEL_DEBUG_FLAG": "Disable", + "SSPINE_COUNT": "0", + "STATIC_UNDERLAY_IP_ALLOC": "false", + "STP_BRIDGE_PRIORITY": "", + "STP_ROOT_OPTION": "unmanaged", + "STP_VLAN_RANGE": "", + "STRICT_CC_MODE": "false", + "SUBINTERFACE_RANGE": "2-511", + "SUBNET_RANGE": "10.4.0.0/16", + "SUBNET_TARGET_MASK": "30", + "SYSLOG_SERVER_IP_LIST": "", + "SYSLOG_SERVER_VRF": "", + "SYSLOG_SEV": "", + "TCAM_ALLOCATION": "true", + "TOPDOWN_CONFIG_RM_TRACKING": "notstarted", + "UNDERLAY_IS_V6": "false", + "UNNUM_BOOTSTRAP_LB_ID": "", + "UNNUM_DHCP_END": "", + "UNNUM_DHCP_END_INTERNAL": "", + "UNNUM_DHCP_START": "", + "UNNUM_DHCP_START_INTERNAL": "", + "UPGRADE_FROM_VERSION": "", + "USE_LINK_LOCAL": "true", + "V6_SUBNET_RANGE": "", + "V6_SUBNET_TARGET_MASK": "126", + "VPC_AUTO_RECOVERY_TIME": "360", + "VPC_DELAY_RESTORE": "150", + "VPC_DELAY_RESTORE_TIME": "60", + "VPC_DOMAIN_ID_RANGE": "1-1000", + "VPC_ENABLE_IPv6_ND_SYNC": "true", + "VPC_PEER_KEEP_ALIVE_OPTION": "management", + "VPC_PEER_LINK_PO": "500", + "VPC_PEER_LINK_VLAN": "3600", + "VRF_LITE_AUTOCONFIG": "Manual", + "VRF_VLAN_RANGE": "2000-2299", + "abstract_anycast_rp": "anycast_rp", + "abstract_bgp": "base_bgp", + "abstract_bgp_neighbor": "evpn_bgp_rr_neighbor", + "abstract_bgp_rr": "evpn_bgp_rr", + "abstract_dhcp": "base_dhcp", + "abstract_extra_config_bootstrap": "extra_config_bootstrap_11_1", + "abstract_extra_config_leaf": "extra_config_leaf", + "abstract_extra_config_spine": "extra_config_spine", + "abstract_extra_config_tor": "extra_config_tor", + "abstract_feature_leaf": "base_feature_leaf_upg", + "abstract_feature_spine": "base_feature_spine_upg", + "abstract_isis": "base_isis_level2", + "abstract_isis_interface": "isis_interface", + "abstract_loopback_interface": "int_fabric_loopback_11_1", + "abstract_multicast": "base_multicast_11_1", + "abstract_ospf": "base_ospf", + "abstract_ospf_interface": "ospf_interface_11_1", + "abstract_pim_interface": "pim_interface", + "abstract_route_map": "route_map", + "abstract_routed_host": "int_routed_host", + "abstract_trunk_host": "int_trunk_host", + "abstract_vlan_interface": "int_fabric_vlan_11_1", + "abstract_vpc_domain": "base_vpc_domain_11_1", + "default_network": "Default_Network_Universal", + "default_pvlan_sec_network": "", + "default_vrf": "Default_VRF_Universal", + "enableRealTimeBackup": "", + "enableScheduledBackup": "", + "network_extension_template": "Default_Network_Extension_Universal", + "scheduledTime": "", + "temp_anycast_gateway": "anycast_gateway", + "temp_vpc_domain_mgmt": "vpc_domain_mgmt", + "temp_vpc_peer_link": "int_vpc_peer_link_po", + "vrf_extension_template": "Default_VRF_Extension_Universal" + }, + "operStatus": "HEALTHY", + "provisionMode": "DCNMTopDown", + "replicationMode": "Multicast", + "siteId": "65001", + "templateName": "Easy_Fabric", + "vrfExtensionTemplate": "Default_VRF_Extension_Universal", + "vrfTemplate": "Default_VRF_Universal" + } + ], + "MESSAGE": "OK", + "METHOD": "GET", + "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/rest/control/fabrics", + "RETURN_CODE": 200 + }, + "test_fabric_update_bulk_00130a": { + "TEST_NOTES": [ + "RETURN_CODE 200", + "MESSAGE OK" + ], + "MESSAGE": "OK", + "METHOD": "GET", + "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/rest/control/fabrics", + "RETURN_CODE": 200 + }, + "test_fabric_update_bulk_00140a": { + "TEST_NOTES": [ + "RETURN_CODE 200", + "MESSAGE OK" ], "MESSAGE": "OK", "METHOD": "GET", diff --git a/tests/unit/modules/dcnm/dcnm_fabric/fixtures/responses_FabricDetailsByNvPair.json b/tests/unit/modules/dcnm/dcnm_fabric/fixtures/responses_FabricDetailsByNvPair.json deleted file mode 100644 index 18a7c57ba..000000000 --- a/tests/unit/modules/dcnm/dcnm_fabric/fixtures/responses_FabricDetailsByNvPair.json +++ /dev/null @@ -1,376 +0,0 @@ -{ - "test_notes": [ - "Mocked responses for FabricDetailsByNvPair() class" - ], - "test_fabric_details_by_nv_pair_00030a": { - "TEST_NOTES": [ - "DATA is an empty list", - "RETURN_CODE is 200" - ], - "DATA": [], - "MESSAGE": "OK", - "METHOD": "GET", - "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/rest/control/fabrics", - "RETURN_CODE": 200 - }, - "test_fabric_details_by_nv_pair_00031a": { - "TEST_NOTES": [ - "DATA key is missing", - "Negative test case", - "RETURN_CODE is 200" - ], - "MESSAGE": "OK", - "METHOD": "GET", - "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/rest/control/fabrics", - "RETURN_CODE": 200 - }, - "test_fabric_details_by_nv_pair_00032a": { - "TEST_NOTES": [ - "DATA contains one fabric dict", - "RETURN_CODE is 200" - ], - "DATA": [ - { - "asn": "65001", - "createdOn": 1711411093680, - "deviceType": "n9k", - "fabricId": "FABRIC-2", - "fabricName": "f1", - "fabricTechnology": "VXLANFabric", - "fabricTechnologyFriendly": "VXLAN EVPN", - "fabricType": "Switch_Fabric", - "fabricTypeFriendly": "Switch Fabric", - "id": 2, - "modifiedOn": 1711411096857, - "networkExtensionTemplate": "Default_Network_Extension_Universal", - "networkTemplate": "Default_Network_Universal", - "nvPairs": { - "AAA_REMOTE_IP_ENABLED": "false", - "AAA_SERVER_CONF": "", - "ACTIVE_MIGRATION": "false", - "ADVERTISE_PIP_BGP": "false", - "ADVERTISE_PIP_ON_BORDER": "true", - "AGENT_INTF": "eth0", - "AI_ML_QOS_POLICY": "AI_Fabric_QOS_400G", - "ALLOW_NXC": "true", - "ALLOW_NXC_PREV": "true", - "ANYCAST_BGW_ADVERTISE_PIP": "false", - "ANYCAST_GW_MAC": "2020.0000.00aa", - "ANYCAST_LB_ID": "", - "ANYCAST_RP_IP_RANGE": "10.254.254.0/24", - "ANYCAST_RP_IP_RANGE_INTERNAL": "", - "AUTO_SYMMETRIC_DEFAULT_VRF": "false", - "AUTO_SYMMETRIC_VRF_LITE": "false", - "AUTO_UNIQUE_VRF_LITE_IP_PREFIX": "false", - "AUTO_UNIQUE_VRF_LITE_IP_PREFIX_PREV": "false", - "AUTO_VRFLITE_IFC_DEFAULT_VRF": "false", - "BANNER": "", - "BFD_AUTH_ENABLE": "false", - "BFD_AUTH_KEY": "", - "BFD_AUTH_KEY_ID": "", - "BFD_ENABLE": "false", - "BFD_ENABLE_PREV": "", - "BFD_IBGP_ENABLE": "false", - "BFD_ISIS_ENABLE": "false", - "BFD_OSPF_ENABLE": "false", - "BFD_PIM_ENABLE": "false", - "BGP_AS": "65001", - "BGP_AS_PREV": "65001", - "BGP_AUTH_ENABLE": "false", - "BGP_AUTH_KEY": "", - "BGP_AUTH_KEY_TYPE": "3", - "BGP_LB_ID": "0", - "BOOTSTRAP_CONF": "", - "BOOTSTRAP_ENABLE": "false", - "BOOTSTRAP_ENABLE_PREV": "false", - "BOOTSTRAP_MULTISUBNET": "#Scope_Start_IP, Scope_End_IP, Scope_Default_Gateway, Scope_Subnet_Prefix", - "BOOTSTRAP_MULTISUBNET_INTERNAL": "", - "BRFIELD_DEBUG_FLAG": "Disable", - "BROWNFIELD_NETWORK_NAME_FORMAT": "Auto_Net_VNI$$VNI$$_VLAN$$VLAN_ID$$", - "BROWNFIELD_SKIP_OVERLAY_NETWORK_ATTACHMENTS": "false", - "CDP_ENABLE": "false", - "COPP_POLICY": "strict", - "DCI_SUBNET_RANGE": "10.33.0.0/16", - "DCI_SUBNET_TARGET_MASK": "30", - "DEAFULT_QUEUING_POLICY_CLOUDSCALE": "queuing_policy_default_8q_cloudscale", - "DEAFULT_QUEUING_POLICY_OTHER": "queuing_policy_default_other", - "DEAFULT_QUEUING_POLICY_R_SERIES": "queuing_policy_default_r_series", - "DEFAULT_VRF_REDIS_BGP_RMAP": "", - "DEPLOYMENT_FREEZE": "false", - "DHCP_ENABLE": "false", - "DHCP_END": "", - "DHCP_END_INTERNAL": "", - "DHCP_IPV6_ENABLE": "", - "DHCP_IPV6_ENABLE_INTERNAL": "", - "DHCP_START": "", - "DHCP_START_INTERNAL": "", - "DNS_SERVER_IP_LIST": "", - "DNS_SERVER_VRF": "", - "DOMAIN_NAME_INTERNAL": "", - "ENABLE_AAA": "false", - "ENABLE_AGENT": "false", - "ENABLE_AI_ML_QOS_POLICY": "false", - "ENABLE_AI_ML_QOS_POLICY_FLAP": "false", - "ENABLE_DEFAULT_QUEUING_POLICY": "false", - "ENABLE_EVPN": "true", - "ENABLE_FABRIC_VPC_DOMAIN_ID": "false", - "ENABLE_FABRIC_VPC_DOMAIN_ID_PREV": "false", - "ENABLE_L3VNI_NO_VLAN": "false", - "ENABLE_MACSEC": "false", - "ENABLE_NETFLOW": "false", - "ENABLE_NETFLOW_PREV": "false", - "ENABLE_NGOAM": "true", - "ENABLE_NXAPI": "true", - "ENABLE_NXAPI_HTTP": "true", - "ENABLE_PBR": "false", - "ENABLE_PVLAN": "false", - "ENABLE_PVLAN_PREV": "false", - "ENABLE_SGT": "false", - "ENABLE_SGT_PREV": "false", - "ENABLE_TENANT_DHCP": "true", - "ENABLE_TRM": "false", - "ENABLE_VPC_PEER_LINK_NATIVE_VLAN": "false", - "ESR_OPTION": "PBR", - "EXTRA_CONF_INTRA_LINKS": "", - "EXTRA_CONF_LEAF": "", - "EXTRA_CONF_SPINE": "", - "EXTRA_CONF_TOR": "", - "EXT_FABRIC_TYPE": "", - "FABRIC_INTERFACE_TYPE": "p2p", - "FABRIC_MTU": "9216", - "FABRIC_MTU_PREV": "9216", - "FABRIC_NAME": "f1", - "FABRIC_TYPE": "Switch_Fabric", - "FABRIC_VPC_DOMAIN_ID": "", - "FABRIC_VPC_DOMAIN_ID_PREV": "", - "FABRIC_VPC_QOS": "false", - "FABRIC_VPC_QOS_POLICY_NAME": "spine_qos_for_fabric_vpc_peering", - "FEATURE_PTP": "false", - "FEATURE_PTP_INTERNAL": "false", - "FF": "Easy_Fabric", - "GRFIELD_DEBUG_FLAG": "Disable", - "HD_TIME": "180", - "HOST_INTF_ADMIN_STATE": "true", - "IBGP_PEER_TEMPLATE": "", - "IBGP_PEER_TEMPLATE_LEAF": "", - "INBAND_DHCP_SERVERS": "", - "INBAND_MGMT": "false", - "INBAND_MGMT_PREV": "false", - "ISIS_AREA_NUM": "0001", - "ISIS_AREA_NUM_PREV": "", - "ISIS_AUTH_ENABLE": "false", - "ISIS_AUTH_KEY": "", - "ISIS_AUTH_KEYCHAIN_KEY_ID": "", - "ISIS_AUTH_KEYCHAIN_NAME": "", - "ISIS_LEVEL": "level-2", - "ISIS_OVERLOAD_ELAPSE_TIME": "", - "ISIS_OVERLOAD_ENABLE": "", - "ISIS_P2P_ENABLE": "", - "L2_HOST_INTF_MTU": "9216", - "L2_HOST_INTF_MTU_PREV": "9216", - "L2_SEGMENT_ID_RANGE": "30000-49000", - "L3VNI_MCAST_GROUP": "", - "L3_PARTITION_ID_RANGE": "50000-59000", - "LINK_STATE_ROUTING": "ospf", - "LINK_STATE_ROUTING_TAG": "UNDERLAY", - "LINK_STATE_ROUTING_TAG_PREV": "UNDERLAY", - "LOOPBACK0_IPV6_RANGE": "", - "LOOPBACK0_IP_RANGE": "10.2.0.0/22", - "LOOPBACK1_IPV6_RANGE": "", - "LOOPBACK1_IP_RANGE": "10.3.0.0/22", - "MACSEC_ALGORITHM": "", - "MACSEC_CIPHER_SUITE": "", - "MACSEC_FALLBACK_ALGORITHM": "", - "MACSEC_FALLBACK_KEY_STRING": "", - "MACSEC_KEY_STRING": "", - "MACSEC_REPORT_TIMER": "", - "MGMT_GW": "", - "MGMT_GW_INTERNAL": "", - "MGMT_PREFIX": "", - "MGMT_PREFIX_INTERNAL": "", - "MGMT_V6PREFIX": "", - "MGMT_V6PREFIX_INTERNAL": "", - "MPLS_HANDOFF": "false", - "MPLS_ISIS_AREA_NUM": "0001", - "MPLS_ISIS_AREA_NUM_PREV": "", - "MPLS_LB_ID": "", - "MPLS_LOOPBACK_IP_RANGE": "", - "MSO_CONNECTIVITY_DEPLOYED": "", - "MSO_CONTROLER_ID": "", - "MSO_SITE_GROUP_NAME": "", - "MSO_SITE_ID": "", - "MST_INSTANCE_RANGE": "", - "MULTICAST_GROUP_SUBNET": "239.1.1.0/25", - "NETFLOW_EXPORTER_LIST": "", - "NETFLOW_MONITOR_LIST": "", - "NETFLOW_RECORD_LIST": "", - "NETWORK_VLAN_RANGE": "2300-2999", - "NTP_SERVER_IP_LIST": "", - "NTP_SERVER_VRF": "", - "NVE_LB_ID": "1", - "NXAPI_HTTPS_PORT": "443", - "NXAPI_HTTP_PORT": "80", - "NXC_DEST_VRF": "", - "NXC_PROXY_PORT": "8080", - "NXC_PROXY_SERVER": "", - "NXC_SRC_INTF": "", - "OBJECT_TRACKING_NUMBER_RANGE": "100-299", - "OSPF_AREA_ID": "0.0.0.0", - "OSPF_AUTH_ENABLE": "false", - "OSPF_AUTH_KEY": "", - "OSPF_AUTH_KEY_ID": "", - "OVERLAY_MODE": "cli", - "OVERLAY_MODE_PREV": "cli", - "OVERWRITE_GLOBAL_NXC": "false", - "PER_VRF_LOOPBACK_AUTO_PROVISION": "false", - "PER_VRF_LOOPBACK_AUTO_PROVISION_PREV": "false", - "PER_VRF_LOOPBACK_AUTO_PROVISION_V6": "false", - "PER_VRF_LOOPBACK_AUTO_PROVISION_V6_PREV": "false", - "PER_VRF_LOOPBACK_IP_RANGE": "", - "PER_VRF_LOOPBACK_IP_RANGE_V6": "", - "PHANTOM_RP_LB_ID1": "", - "PHANTOM_RP_LB_ID2": "", - "PHANTOM_RP_LB_ID3": "", - "PHANTOM_RP_LB_ID4": "", - "PIM_HELLO_AUTH_ENABLE": "false", - "PIM_HELLO_AUTH_KEY": "", - "PM_ENABLE": "false", - "PM_ENABLE_PREV": "false", - "POWER_REDUNDANCY_MODE": "ps-redundant", - "PREMSO_PARENT_FABRIC": "", - "PTP_DOMAIN_ID": "", - "PTP_LB_ID": "", - "REPLICATION_MODE": "Multicast", - "ROUTER_ID_RANGE": "", - "ROUTE_MAP_SEQUENCE_NUMBER_RANGE": "1-65534", - "RP_COUNT": "2", - "RP_LB_ID": "254", - "RP_MODE": "asm", - "RR_COUNT": "2", - "SEED_SWITCH_CORE_INTERFACES": "", - "SERVICE_NETWORK_VLAN_RANGE": "3000-3199", - "SGT_ID_RANGE": "", - "SGT_NAME_PREFIX": "", - "SGT_PREPROVISION": "", - "SITE_ID": "65001", - "SLA_ID_RANGE": "10000-19999", - "SNMP_SERVER_HOST_TRAP": "true", - "SPINE_COUNT": "0", - "SPINE_SWITCH_CORE_INTERFACES": "", - "SSPINE_ADD_DEL_DEBUG_FLAG": "Disable", - "SSPINE_COUNT": "0", - "STATIC_UNDERLAY_IP_ALLOC": "false", - "STP_BRIDGE_PRIORITY": "", - "STP_ROOT_OPTION": "unmanaged", - "STP_VLAN_RANGE": "", - "STRICT_CC_MODE": "false", - "SUBINTERFACE_RANGE": "2-511", - "SUBNET_RANGE": "10.4.0.0/16", - "SUBNET_TARGET_MASK": "30", - "SYSLOG_SERVER_IP_LIST": "", - "SYSLOG_SERVER_VRF": "", - "SYSLOG_SEV": "", - "TCAM_ALLOCATION": "true", - "TOPDOWN_CONFIG_RM_TRACKING": "notstarted", - "UNDERLAY_IS_V6": "false", - "UNNUM_BOOTSTRAP_LB_ID": "", - "UNNUM_DHCP_END": "", - "UNNUM_DHCP_END_INTERNAL": "", - "UNNUM_DHCP_START": "", - "UNNUM_DHCP_START_INTERNAL": "", - "UPGRADE_FROM_VERSION": "", - "USE_LINK_LOCAL": "true", - "V6_SUBNET_RANGE": "", - "V6_SUBNET_TARGET_MASK": "126", - "VPC_AUTO_RECOVERY_TIME": "360", - "VPC_DELAY_RESTORE": "150", - "VPC_DELAY_RESTORE_TIME": "60", - "VPC_DOMAIN_ID_RANGE": "1-1000", - "VPC_ENABLE_IPv6_ND_SYNC": "true", - "VPC_PEER_KEEP_ALIVE_OPTION": "management", - "VPC_PEER_LINK_PO": "500", - "VPC_PEER_LINK_VLAN": "3600", - "VRF_LITE_AUTOCONFIG": "Manual", - "VRF_VLAN_RANGE": "2000-2299", - "abstract_anycast_rp": "anycast_rp", - "abstract_bgp": "base_bgp", - "abstract_bgp_neighbor": "evpn_bgp_rr_neighbor", - "abstract_bgp_rr": "evpn_bgp_rr", - "abstract_dhcp": "base_dhcp", - "abstract_extra_config_bootstrap": "extra_config_bootstrap_11_1", - "abstract_extra_config_leaf": "extra_config_leaf", - "abstract_extra_config_spine": "extra_config_spine", - "abstract_extra_config_tor": "extra_config_tor", - "abstract_feature_leaf": "base_feature_leaf_upg", - "abstract_feature_spine": "base_feature_spine_upg", - "abstract_isis": "base_isis_level2", - "abstract_isis_interface": "isis_interface", - "abstract_loopback_interface": "int_fabric_loopback_11_1", - "abstract_multicast": "base_multicast_11_1", - "abstract_ospf": "base_ospf", - "abstract_ospf_interface": "ospf_interface_11_1", - "abstract_pim_interface": "pim_interface", - "abstract_route_map": "route_map", - "abstract_routed_host": "int_routed_host", - "abstract_trunk_host": "int_trunk_host", - "abstract_vlan_interface": "int_fabric_vlan_11_1", - "abstract_vpc_domain": "base_vpc_domain_11_1", - "default_network": "Default_Network_Universal", - "default_pvlan_sec_network": "", - "default_vrf": "Default_VRF_Universal", - "enableRealTimeBackup": "", - "enableScheduledBackup": "", - "network_extension_template": "Default_Network_Extension_Universal", - "scheduledTime": "", - "temp_anycast_gateway": "anycast_gateway", - "temp_vpc_domain_mgmt": "vpc_domain_mgmt", - "temp_vpc_peer_link": "int_vpc_peer_link_po", - "vrf_extension_template": "Default_VRF_Extension_Universal" - }, - "operStatus": "HEALTHY", - "provisionMode": "DCNMTopDown", - "replicationMode": "Multicast", - "siteId": "65001", - "templateName": "Easy_Fabric", - "vrfExtensionTemplate": "Default_VRF_Extension_Universal", - "vrfTemplate": "Default_VRF_Universal" - } - ], - "MESSAGE": "OK", - "METHOD": "GET", - "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/rest/control/fabrics", - "RETURN_CODE": 200 - }, - "test_fabric_details_by_nv_pair_00040a": { - "TEST_NOTES": [ - "DATA contains two (partial) fabric dict", - "One fabric dict matches the search criteria of filter_key REPLICATION_MODE and filter_value Ingress", - "RETURN_CODE is 200" - ], - "DATA": [ - { - "fabricName": "MC-Fabric", - "fabricType": "Switch_Fabric", - "nvPairs": { - "BGP_AS": "65001", - "FABRIC_NAME": "MC-Fabric", - "REPLICATION_MODE": "Multicast" - } - }, - { - "asn": "65002", - "fabricName": "IR-Fabric", - "fabricType": "Switch_Fabric", - "nvPairs": { - "BGP_AS": "65002", - "FABRIC_NAME": "IR-Fabric", - "REPLICATION_MODE": "Ingress" - } - } - ], - "MESSAGE": "OK", - "METHOD": "GET", - "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/rest/control/fabrics", - "RETURN_CODE": 200 - } -} diff --git a/tests/unit/modules/dcnm/dcnm_fabric/fixtures/responses_FabricConfigDeploy.json b/tests/unit/modules/dcnm/dcnm_fabric/fixtures/responses_ep_fabric_config_deploy.json similarity index 100% rename from tests/unit/modules/dcnm/dcnm_fabric/fixtures/responses_FabricConfigDeploy.json rename to tests/unit/modules/dcnm/dcnm_fabric/fixtures/responses_ep_fabric_config_deploy.json diff --git a/tests/unit/modules/dcnm/dcnm_fabric/fixtures/responses_FabricConfigSave.json b/tests/unit/modules/dcnm/dcnm_fabric/fixtures/responses_ep_fabric_config_save.json similarity index 100% rename from tests/unit/modules/dcnm/dcnm_fabric/fixtures/responses_FabricConfigSave.json rename to tests/unit/modules/dcnm/dcnm_fabric/fixtures/responses_ep_fabric_config_save.json diff --git a/tests/unit/modules/dcnm/dcnm_fabric/test_fabric_common.py b/tests/unit/modules/dcnm/dcnm_fabric/test_fabric_common.py index 214e608d0..0b7062c2a 100644 --- a/tests/unit/modules/dcnm/dcnm_fabric/test_fabric_common.py +++ b/tests/unit/modules/dcnm/dcnm_fabric/test_fabric_common.py @@ -60,46 +60,12 @@ def test_fabric_common_00010(fabric_common) -> None: with does_not_raise(): instance = fabric_common assert instance.class_name == "FabricCommon" - assert instance.state == "merged" - assert instance.check_mode is False - assert instance._properties["fabric_details"] is None - assert instance._properties["fabric_summary"] is None - assert instance._properties["fabric_type"] == "VXLAN_EVPN" - assert instance._properties["rest_send"] is None - assert instance._properties["results"] is None - - -def test_fabric_common_00011() -> None: - """ - Classes and Methods - - FabricCommon - - __init__() - - Summary - - Verify ``ValueError`` is raised when check_mode is missing from params - """ - params_test = copy.deepcopy(params) - params_test.pop("check_mode", None) - match = r"FabricCommon\.__init__\(\): check_mode is required" - with pytest.raises(ValueError, match=match): - FabricCommon(params_test) - - -def test_fabric_common_00012() -> None: - """ - Classes and Methods - - FabricCommon - - __init__() - - Summary - - Verify ``ValueError`` is raised when state is missing from params - """ - params_test = copy.deepcopy(params) - params_test.pop("state", None) - match = r"FabricCommon\.__init__\(\): state is required" - with pytest.raises(ValueError, match=match): - FabricCommon(params_test) + assert instance._fabric_details is None + assert instance._fabric_summary is None + assert instance._fabric_type == "VXLAN_EVPN" + assert instance._rest_send is None + assert instance._results is None def test_fabric_common_00020(fabric_common) -> None: @@ -267,6 +233,7 @@ def test_fabric_common_00100(fabric_common) -> None: match += r"Playbook configuration for fabrics must be a dict\.\s+" match += r"Got type str, value NOT_A_DICT\." with pytest.raises(ValueError, match=match): + instance.action = "fabric_create" instance._verify_payload(payload) @@ -298,6 +265,7 @@ def test_fabric_common_00110(fabric_common, mandatory_key) -> None: with does_not_raise(): instance = fabric_common + instance.action = "fabric_create" match = r"FabricCommon\._verify_payload:\s+" match += r"Playbook configuration for fabric .* is missing mandatory\s+" @@ -314,16 +282,17 @@ def test_fabric_common_00111(fabric_common) -> None: - _verify_payload() Summary - - Verify FabricCommon()_verify_payload() returns if state != "merged". + - Verify FabricCommon()_verify_payload() returns if action + is not one of "fabric_create" or "fabric_replace". NOTES: - - Since state == "query", FabricCommon()._verify_payload() does not + - Since action == "foo", FabricCommon()._verify_payload() does not reach its validation checks and so does not raise ``ValueError`` when its input parameter is the wrong type (str vs dict). """ with does_not_raise(): instance = fabric_common - instance.state = "query" + instance.action = "foo" instance._verify_payload("NOT_A_DICT") @@ -377,6 +346,7 @@ def test_fabric_common_00112(fabric_common, fabric_name, expected) -> None: with does_not_raise(): instance = fabric_common + instance.action = "fabric_create" instance.results = Results() with expected: instance._verify_payload(payload) @@ -418,6 +388,7 @@ def test_fabric_common_00113(fabric_common, fabric_type, expected) -> None: with does_not_raise(): instance = fabric_common + instance.action = "fabric_create" instance.results = Results() with expected: instance._verify_payload(payload) diff --git a/tests/unit/modules/dcnm/dcnm_fabric/test_fabric_config_deploy.py b/tests/unit/modules/dcnm/dcnm_fabric/test_fabric_config_deploy.py index 8cf26e2d7..69b7e0026 100644 --- a/tests/unit/modules/dcnm/dcnm_fabric/test_fabric_config_deploy.py +++ b/tests/unit/modules/dcnm/dcnm_fabric/test_fabric_config_deploy.py @@ -32,22 +32,20 @@ import inspect import pytest -from ansible_collections.cisco.dcnm.plugins.module_utils.common.api.v1.lan_fabric.rest.control.fabrics.fabrics import \ - EpFabricConfigDeploy -from ansible_collections.cisco.dcnm.plugins.module_utils.common.conversion import \ - ConversionUtils -from ansible_collections.cisco.dcnm.plugins.module_utils.common.rest_send import \ +from ansible_collections.cisco.dcnm.plugins.module_utils.common.response_handler import \ + ResponseHandler +from ansible_collections.cisco.dcnm.plugins.module_utils.common.rest_send_v2 import \ RestSend from ansible_collections.cisco.dcnm.plugins.module_utils.common.results import \ Results -from ansible_collections.cisco.dcnm.plugins.module_utils.fabric.config_deploy import \ - FabricConfigDeploy +from ansible_collections.cisco.dcnm.plugins.module_utils.common.sender_file import \ + Sender from ansible_collections.cisco.dcnm.tests.unit.module_utils.common.common_utils import \ ResponseGenerator from ansible_collections.cisco.dcnm.tests.unit.modules.dcnm.dcnm_fabric.utils import ( MockAnsibleModule, does_not_raise, fabric_config_deploy_fixture, - fabric_details_by_name_fixture, fabric_summary_fixture, params, - responses_fabric_config_deploy, responses_fabric_details_by_name, + fabric_details_by_name_v2_fixture, fabric_summary_fixture, params, + responses_ep_fabric_config_deploy, responses_fabric_details_by_name_v2, responses_fabric_summary) @@ -65,50 +63,12 @@ def test_fabric_config_deploy_00010(fabric_config_deploy) -> None: instance = fabric_config_deploy assert instance.class_name == "FabricConfigDeploy" assert instance.action == "config_deploy" - assert instance.check_mode is False assert instance.config_deploy_result == {} assert instance.fabric_name is None - assert instance.path is None assert instance.rest_send is None assert instance.results is None - assert instance.verb is None - assert instance.state == "merged" - assert isinstance(instance.conversion, ConversionUtils) - assert isinstance(instance.ep_config_deploy, EpFabricConfigDeploy) - - -def test_fabric_config_deploy_00011() -> None: - """ - Classes and Methods - - FabricConfigDeploy - - __init__() - - Summary - - Verify FabricConfigDeploy().__init__() raises ``ValueError`` - when check_mode is not set. - """ - params = {"state": "merged"} - match = r"FabricConfigDeploy\.__init__\(\):\s+" - match += r"params is missing mandatory check_mode parameter\." - with pytest.raises(ValueError, match=match): - instance = FabricConfigDeploy(params) # pylint: disable=unused-variable - - -def test_fabric_config_deploy_00012() -> None: - """ - Classes and Methods - - FabricConfigDeploy - - __init__() - - Summary - - Verify FabricConfigDeploy().__init__() raises ``ValueError`` - when state is not set. - """ - params = {"check_mode": False} - match = r"FabricConfigDeploy\.__init__\(\):\s+" - match += r"params is missing mandatory state parameter\." - with pytest.raises(ValueError, match=match): - instance = FabricConfigDeploy(params) # pylint: disable=unused-variable + assert instance.conversion.class_name == "ConversionUtils" + assert instance.ep_config_deploy.class_name == "EpFabricConfigDeploy" MATCH_00020a = r"ConversionUtils\.validate_fabric_name: " @@ -182,7 +142,7 @@ def test_fabric_config_deploy_00020( @pytest.mark.parametrize( "value, does_raise, expected", [ - (RestSend(MockAnsibleModule()), False, does_not_raise()), + (RestSend(params), False, does_not_raise()), (Results(), True, pytest.raises(TypeError, match=MATCH_00030)), (None, True, pytest.raises(TypeError, match=MATCH_00030)), ("foo", True, pytest.raises(TypeError, match=MATCH_00030)), @@ -256,7 +216,7 @@ def test_fabric_config_deploy_00040( def test_fabric_config_deploy_00120( - fabric_config_deploy, fabric_details_by_name, fabric_summary + fabric_config_deploy, fabric_details_by_name_v2, fabric_summary ) -> None: """ Classes and Methods @@ -272,22 +232,24 @@ def test_fabric_config_deploy_00120( - ValueError is raised because payload is not set before calling commit() """ - match = r"FabricConfigDeploy\.commit: " - match += r"FabricConfigDeploy\.payload must be set " - match += r"before calling commit\." with does_not_raise(): instance = fabric_config_deploy - instance.fabric_details = fabric_details_by_name + instance.fabric_details = fabric_details_by_name_v2 instance.fabric_summary = fabric_summary - instance.rest_send = RestSend(MockAnsibleModule()) + instance.rest_send = RestSend(params) instance.results = Results() + + match = r"FabricConfigDeploy\.commit: " + match += r"FabricConfigDeploy\.payload must be set " + match += r"before calling commit\." + with pytest.raises(ValueError, match=match): instance.commit() def test_fabric_config_deploy_00130( - fabric_config_deploy, fabric_details_by_name, fabric_summary + fabric_config_deploy, fabric_details_by_name_v2, fabric_summary ) -> None: """ Classes and Methods @@ -303,22 +265,23 @@ def test_fabric_config_deploy_00130( - ValueError is raised because rest_send is not set before calling commit() """ - match = r"FabricConfigDeploy\.commit: " - match += r"FabricConfigDeploy\.rest_send must be set " - match += r"before calling commit\." - with does_not_raise(): instance = fabric_config_deploy - instance.fabric_details = fabric_details_by_name + instance.fabric_details = fabric_details_by_name_v2 instance.payload = {"FABRIC_NAME": "MyFabric"} instance.fabric_summary = fabric_summary instance.results = Results() + + match = r"FabricConfigDeploy\.commit: " + match += r"FabricConfigDeploy\.rest_send must be set " + match += r"before calling commit\." + with pytest.raises(ValueError, match=match): instance.commit() def test_fabric_config_deploy_00140( - fabric_config_deploy, fabric_details_by_name, fabric_summary + fabric_config_deploy, fabric_details_by_name_v2, fabric_summary ) -> None: """ Classes and Methods @@ -334,16 +297,17 @@ def test_fabric_config_deploy_00140( - ValueError is raised because results is not set before calling commit() """ - match = r"FabricConfigDeploy\.commit: " - match += r"FabricConfigDeploy\.results must be set " - match += r"before calling commit\." - with does_not_raise(): instance = fabric_config_deploy - instance.fabric_details = fabric_details_by_name + instance.fabric_details = fabric_details_by_name_v2 instance.payload = {"FABRIC_NAME": "MyFabric"} instance.fabric_summary = fabric_summary - instance.rest_send = RestSend(MockAnsibleModule()) + instance.rest_send = RestSend(params) + + match = r"FabricConfigDeploy\.commit: " + match += r"FabricConfigDeploy\.results must be set " + match += r"before calling commit\." + with pytest.raises(ValueError, match=match): instance.commit() @@ -363,22 +327,24 @@ def test_fabric_config_deploy_00150(fabric_config_deploy, fabric_summary) -> Non - ValueError is raised because results is not set before calling commit() """ - match = r"FabricConfigDeploy\.commit: " - match += r"FabricConfigDeploy\.fabric_details must be set " - match += r"before calling commit\." with does_not_raise(): instance = fabric_config_deploy instance.payload = {"FABRIC_NAME": "MyFabric"} instance.fabric_summary = fabric_summary - instance.rest_send = RestSend(MockAnsibleModule()) + instance.rest_send = RestSend(params) instance.results = Results() + + match = r"FabricConfigDeploy\.commit: " + match += r"FabricConfigDeploy\.fabric_details must be set " + match += r"before calling commit\." + with pytest.raises(ValueError, match=match): instance.commit() def test_fabric_config_deploy_00160( - fabric_config_deploy, fabric_details_by_name + fabric_config_deploy, fabric_details_by_name_v2 ) -> None: """ Classes and Methods @@ -394,29 +360,32 @@ def test_fabric_config_deploy_00160( - ValueError is raised because fabric_summary is not set before calling commit() """ + with does_not_raise(): + instance = fabric_config_deploy + instance.fabric_details = fabric_details_by_name_v2 + instance.payload = {"FABRIC_NAME": "MyFabric"} + instance.rest_send = RestSend(params) + match = r"FabricConfigDeploy\.commit: " match += r"FabricConfigDeploy\.fabric_summary must be set " match += r"before calling commit\." - with does_not_raise(): - instance = fabric_config_deploy - instance.fabric_details = fabric_details_by_name - instance.payload = {"FABRIC_NAME": "MyFabric"} - instance.rest_send = RestSend(MockAnsibleModule()) with pytest.raises(ValueError, match=match): instance.commit() def test_fabric_config_deploy_00200( - monkeypatch, fabric_config_deploy, fabric_details_by_name, fabric_summary + monkeypatch, fabric_config_deploy, fabric_details_by_name_v2, fabric_summary ) -> None: """ - Classes and Methods + ### Classes and Methods + - FabricConfigDeploy() - __init__() - commit() - Summary + ### Summary + - Verify that FabricConfigDeploy().commit() re-raises ``ValueError`` when EpFabricConfigDeploy() raises ``ValueError``. @@ -437,22 +406,21 @@ def path(self): - Raise ``ValueError``. """ msg = "mocked EpFabricConfigDeploy().path getter exception" + print(f"ZZZ msg {msg}") raise ValueError(msg) - PATCH_DCNM_SEND = "ansible_collections.cisco.dcnm.plugins." - PATCH_DCNM_SEND += "module_utils.common.rest_send.dcnm_send" - def responses(): yield responses_fabric_summary(key) - yield responses_fabric_details_by_name(key) + yield responses_fabric_details_by_name_v2(key) - gen = ResponseGenerator(responses()) + gen_responses = ResponseGenerator(responses()) - def mock_dcnm_send(*args, **kwargs): - item = gen.next - return item - - monkeypatch.setattr(PATCH_DCNM_SEND, mock_dcnm_send) + sender = Sender() + sender.ansible_module = MockAnsibleModule() + sender.gen = gen_responses + rest_send = RestSend(params) + rest_send.response_handler = ResponseHandler() + rest_send.sender = sender payload = { "FABRIC_NAME": "f1", @@ -464,12 +432,12 @@ def mock_dcnm_send(*args, **kwargs): with does_not_raise(): instance = fabric_config_deploy monkeypatch.setattr(instance, "ep_config_deploy", MockEpFabricConfigDeploy()) - instance.fabric_details = fabric_details_by_name - instance.fabric_details.rest_send = RestSend(MockAnsibleModule()) + instance.fabric_details = fabric_details_by_name_v2 + instance.fabric_details.rest_send = rest_send instance.payload = payload instance.fabric_summary = fabric_summary - instance.fabric_summary.rest_send = RestSend(MockAnsibleModule()) - instance.rest_send = RestSend(MockAnsibleModule()) + instance.fabric_summary.rest_send = rest_send + instance.rest_send = rest_send instance.results = Results() match = r"mocked EpFabricConfigDeploy\(\)\.path getter exception" @@ -478,7 +446,7 @@ def mock_dcnm_send(*args, **kwargs): def test_fabric_config_deploy_00210( - monkeypatch, fabric_config_deploy, fabric_details_by_name, fabric_summary + fabric_config_deploy, fabric_details_by_name_v2, fabric_summary ) -> None: """ Classes and Methods @@ -537,19 +505,19 @@ def test_fabric_config_deploy_00210( method_name = inspect.stack()[0][3] key = f"{method_name}a" - PATCH_DCNM_SEND = "ansible_collections.cisco.dcnm.plugins." - PATCH_DCNM_SEND += "module_utils.common.rest_send.dcnm_send" - def responses(): yield responses_fabric_summary(key) - yield responses_fabric_details_by_name(key) - yield responses_fabric_config_deploy(key) + yield responses_fabric_details_by_name_v2(key) + yield responses_ep_fabric_config_deploy(key) - gen = ResponseGenerator(responses()) + gen_responses = ResponseGenerator(responses()) - def mock_dcnm_send(*args, **kwargs): - item = gen.next - return item + sender = Sender() + sender.ansible_module = MockAnsibleModule() + sender.gen = gen_responses + rest_send = RestSend(params) + rest_send.response_handler = ResponseHandler() + rest_send.sender = sender payload = { "FABRIC_NAME": "f1", @@ -560,17 +528,13 @@ def mock_dcnm_send(*args, **kwargs): with does_not_raise(): instance = fabric_config_deploy - instance.rest_send = RestSend(MockAnsibleModule()) - instance.fabric_details = fabric_details_by_name + instance.rest_send = rest_send + instance.fabric_details = fabric_details_by_name_v2 instance.fabric_details.rest_send = instance.rest_send instance.payload = payload instance.fabric_summary = fabric_summary instance.fabric_summary.rest_send = instance.rest_send instance.results = Results() - - monkeypatch.setattr(PATCH_DCNM_SEND, mock_dcnm_send) - - with does_not_raise(): instance.commit() assert isinstance(instance.results.diff, list) @@ -598,7 +562,7 @@ def mock_dcnm_send(*args, **kwargs): def test_fabric_config_deploy_00220( - monkeypatch, fabric_config_deploy, fabric_details_by_name, fabric_summary + fabric_config_deploy, fabric_details_by_name_v2, fabric_summary ) -> None: """ Classes and Methods @@ -647,19 +611,21 @@ def test_fabric_config_deploy_00220( method_name = inspect.stack()[0][3] key = f"{method_name}a" - PATCH_DCNM_SEND = "ansible_collections.cisco.dcnm.plugins." - PATCH_DCNM_SEND += "module_utils.common.rest_send.dcnm_send" - def responses(): yield responses_fabric_summary(key) - yield responses_fabric_details_by_name(key) - yield responses_fabric_config_deploy(key) + yield responses_fabric_details_by_name_v2(key) + yield responses_ep_fabric_config_deploy(key) - gen = ResponseGenerator(responses()) + gen_responses = ResponseGenerator(responses()) - def mock_dcnm_send(*args, **kwargs): - item = gen.next - return item + sender = Sender() + sender.ansible_module = MockAnsibleModule() + sender.gen = gen_responses + rest_send = RestSend(params) + rest_send.unit_test = True + rest_send.timeout = 1 + rest_send.response_handler = ResponseHandler() + rest_send.sender = sender payload = { "FABRIC_NAME": "f1", @@ -670,19 +636,13 @@ def mock_dcnm_send(*args, **kwargs): with does_not_raise(): instance = fabric_config_deploy - instance.rest_send = RestSend(MockAnsibleModule()) - instance.rest_send.unit_test = True - instance.rest_send.timeout = 1 - instance.fabric_details = fabric_details_by_name - instance.fabric_details.rest_send = instance.rest_send + instance.rest_send = rest_send + instance.fabric_details = fabric_details_by_name_v2 + instance.fabric_details.rest_send = rest_send instance.payload = payload instance.fabric_summary = fabric_summary - instance.fabric_summary.rest_send = instance.rest_send + instance.fabric_summary.rest_send = rest_send instance.results = Results() - - monkeypatch.setattr(PATCH_DCNM_SEND, mock_dcnm_send) - - with does_not_raise(): instance.commit() assert isinstance(instance.results.diff, list) diff --git a/tests/unit/modules/dcnm/dcnm_fabric/test_fabric_config_save.py b/tests/unit/modules/dcnm/dcnm_fabric/test_fabric_config_save.py index 6638169f6..21ee30a04 100644 --- a/tests/unit/modules/dcnm/dcnm_fabric/test_fabric_config_save.py +++ b/tests/unit/modules/dcnm/dcnm_fabric/test_fabric_config_save.py @@ -32,26 +32,25 @@ import inspect import pytest -from ansible_collections.cisco.dcnm.plugins.module_utils.common.api.v1.lan_fabric.rest.control.fabrics.fabrics import \ - EpFabricConfigSave -from ansible_collections.cisco.dcnm.plugins.module_utils.common.conversion import \ - ConversionUtils -from ansible_collections.cisco.dcnm.plugins.module_utils.common.rest_send import \ +from ansible_collections.cisco.dcnm.plugins.module_utils.common.response_handler import \ + ResponseHandler +from ansible_collections.cisco.dcnm.plugins.module_utils.common.rest_send_v2 import \ RestSend from ansible_collections.cisco.dcnm.plugins.module_utils.common.results import \ Results -from ansible_collections.cisco.dcnm.plugins.module_utils.fabric.config_save import \ - FabricConfigSave +from ansible_collections.cisco.dcnm.plugins.module_utils.common.sender_file import \ + Sender from ansible_collections.cisco.dcnm.tests.unit.module_utils.common.common_utils import \ ResponseGenerator from ansible_collections.cisco.dcnm.tests.unit.modules.dcnm.dcnm_fabric.utils import ( MockAnsibleModule, does_not_raise, fabric_config_save_fixture, params, - responses_fabric_config_save) + responses_ep_fabric_config_save) def test_fabric_config_save_00010(fabric_config_save) -> None: """ - Classes and Methods + ### Classes and Methods + - FabricConfigSave - __init__() @@ -63,51 +62,13 @@ def test_fabric_config_save_00010(fabric_config_save) -> None: instance = fabric_config_save assert instance.class_name == "FabricConfigSave" assert instance.action == "config_save" - assert instance.check_mode is False assert instance.config_save_result == {} assert instance.fabric_name is None - assert instance.path is None assert instance.payload is None assert instance.rest_send is None assert instance.results is None - assert instance.verb is None - assert instance.state == "merged" - assert isinstance(instance.conversion, ConversionUtils) - assert isinstance(instance.ep_config_save, EpFabricConfigSave) - - -def test_fabric_config_save_00011() -> None: - """ - Classes and Methods - - FabricConfigSave - - __init__() - - Summary - - Verify FabricConfigSave().__init__() raises ``ValueError`` - when check_mode is not set. - """ - params = {"state": "merged"} - match = r"FabricConfigSave\.__init__\(\):\s+" - match += r"params is missing mandatory check_mode parameter\." - with pytest.raises(ValueError, match=match): - instance = FabricConfigSave(params) # pylint: disable=unused-variable - - -def test_fabric_config_save_00012() -> None: - """ - Classes and Methods - - FabricConfigSave - - __init__() - - Summary - - Verify FabricConfigSave().__init__() raises ``ValueError`` - when state is not set. - """ - params = {"check_mode": False} - match = r"FabricConfigSave\.__init__\(\):\s+" - match += r"params is missing mandatory state parameter\." - with pytest.raises(ValueError, match=match): - instance = FabricConfigSave(params) # pylint: disable=unused-variable + assert instance.conversion.class_name == "ConversionUtils" + assert instance.ep_config_save.class_name == "EpFabricConfigSave" MATCH_00020a = r"ConversionUtils\.validate_fabric_name: " @@ -181,7 +142,7 @@ def test_fabric_config_save_00020( @pytest.mark.parametrize( "value, does_raise, expected", [ - (RestSend(MockAnsibleModule()), False, does_not_raise()), + (RestSend(params), False, does_not_raise()), (Results(), True, pytest.raises(TypeError, match=MATCH_00030)), (None, True, pytest.raises(TypeError, match=MATCH_00030)), ("foo", True, pytest.raises(TypeError, match=MATCH_00030)), @@ -269,14 +230,28 @@ def test_fabric_config_save_00050(fabric_config_save) -> None: - ValueError is raised because payload is not set before calling commit() """ - match = r"FabricConfigSave\.commit: " - match += r"FabricConfigSave\.payload must be set " - match += r"before calling commit\." + + def responses(): + yield None + + gen_responses = ResponseGenerator(responses()) + + sender = Sender() + sender.ansible_module = MockAnsibleModule() + sender.gen = gen_responses + rest_send = RestSend(params) + rest_send.response_handler = ResponseHandler() + rest_send.sender = sender with does_not_raise(): instance = fabric_config_save - instance.rest_send = RestSend(MockAnsibleModule()) + instance.rest_send = rest_send instance.results = Results() + + match = r"FabricConfigSave\.commit: " + match += r"FabricConfigSave\.payload must be set " + match += r"before calling commit\." + with pytest.raises(ValueError, match=match): instance.commit() @@ -296,14 +271,15 @@ def test_fabric_config_save_00060(fabric_config_save) -> None: - ValueError is raised because rest_send is not set before calling commit() """ - match = r"FabricConfigSave\.commit: " - match += r"FabricConfigSave\.rest_send must be set " - match += r"before calling commit\." - with does_not_raise(): instance = fabric_config_save instance.payload = {"FABRIC_NAME": "MyFabric"} instance.results = Results() + + match = r"FabricConfigSave\.commit: " + match += r"FabricConfigSave\.rest_send must be set " + match += r"before calling commit\." + with pytest.raises(ValueError, match=match): instance.commit() @@ -323,14 +299,15 @@ def test_fabric_config_save_00070(fabric_config_save) -> None: - ValueError is raised because results is not set before calling commit() """ + with does_not_raise(): + instance = fabric_config_save + instance.payload = {"FABRIC_NAME": "MyFabric"} + instance.rest_send = RestSend(params) + match = r"FabricConfigSave\.commit: " match += r"FabricConfigSave\.results must be set " match += r"before calling commit\." - with does_not_raise(): - instance = fabric_config_save - instance.payload = {"FABRIC_NAME": "MyFabric"} - instance.rest_send = RestSend(MockAnsibleModule()) with pytest.raises(ValueError, match=match): instance.commit() @@ -374,7 +351,7 @@ def path(self): instance = fabric_config_save monkeypatch.setattr(instance, "ep_config_save", MockEpFabricConfigSave()) instance.payload = payload - instance.rest_send = RestSend(MockAnsibleModule()) + instance.rest_send = RestSend(params) instance.results = Results() match = r"mocked EpFabricConfigSave\(\)\.path getter exception" @@ -382,9 +359,10 @@ def path(self): instance.commit() -def test_fabric_config_save_00090(monkeypatch, fabric_config_save) -> None: +def test_fabric_config_save_00090(fabric_config_save) -> None: """ - Classes and Methods + ### Classes and Methods + - FabricConfigSave - __init__() - fabric_names setter @@ -392,16 +370,18 @@ def test_fabric_config_save_00090(monkeypatch, fabric_config_save) -> None: - results setter - commit() - Summary + ### Summary - Verify commit() "happy path" behavior. - Setup + ### Setup + - All properties are properly set prior to calling commit() - RestSend() is patched with response containing: - RETURN_CODE == 200 - MESSAGE == "OK" - Code Flow + ### Code Flow + - FabricConfigSave() is instantiated - FabricConfigSave() properties are set - FabricConfigSave.fabric_name is set "f1" @@ -427,17 +407,17 @@ def test_fabric_config_save_00090(monkeypatch, fabric_config_save) -> None: method_name = inspect.stack()[0][3] key = f"{method_name}a" - PATCH_DCNM_SEND = "ansible_collections.cisco.dcnm.plugins." - PATCH_DCNM_SEND += "module_utils.common.rest_send.dcnm_send" - def responses(): - yield responses_fabric_config_save(key) + yield responses_ep_fabric_config_save(key) - gen = ResponseGenerator(responses()) + gen_responses = ResponseGenerator(responses()) - def mock_dcnm_send(*args, **kwargs): - item = gen.next - return item + sender = Sender() + sender.ansible_module = MockAnsibleModule() + sender.gen = gen_responses + rest_send = RestSend(params) + rest_send.response_handler = ResponseHandler() + rest_send.sender = sender payload = { "FABRIC_NAME": "f1", @@ -449,11 +429,9 @@ def mock_dcnm_send(*args, **kwargs): with does_not_raise(): instance = fabric_config_save instance.payload = payload - instance.rest_send = RestSend(MockAnsibleModule()) + instance.rest_send = rest_send instance.results = Results() - monkeypatch.setattr(PATCH_DCNM_SEND, mock_dcnm_send) - with does_not_raise(): instance.commit() @@ -481,7 +459,7 @@ def mock_dcnm_send(*args, **kwargs): assert False not in instance.results.changed -def test_fabric_config_save_00100(monkeypatch, fabric_config_save) -> None: +def test_fabric_config_save_00100(fabric_config_save) -> None: """ Classes and Methods - FabricConfigSave @@ -529,17 +507,19 @@ def test_fabric_config_save_00100(monkeypatch, fabric_config_save) -> None: method_name = inspect.stack()[0][3] key = f"{method_name}a" - PATCH_DCNM_SEND = "ansible_collections.cisco.dcnm.plugins." - PATCH_DCNM_SEND += "module_utils.common.rest_send.dcnm_send" - def responses(): - yield responses_fabric_config_save(key) + yield responses_ep_fabric_config_save(key) - gen = ResponseGenerator(responses()) + gen_responses = ResponseGenerator(responses()) - def mock_dcnm_send(*args, **kwargs): - item = gen.next - return item + sender = Sender() + sender.ansible_module = MockAnsibleModule() + sender.gen = gen_responses + rest_send = RestSend(params) + rest_send.unit_test = True + rest_send.timeout = 1 + rest_send.response_handler = ResponseHandler() + rest_send.sender = sender payload = { "FABRIC_NAME": "f1", @@ -551,13 +531,9 @@ def mock_dcnm_send(*args, **kwargs): with does_not_raise(): instance = fabric_config_save instance.payload = payload - instance.rest_send = RestSend(MockAnsibleModule()) - instance.rest_send.unit_test = True - instance.rest_send.timeout = 1 + instance.rest_send = rest_send instance.results = Results() - monkeypatch.setattr(PATCH_DCNM_SEND, mock_dcnm_send) - with does_not_raise(): instance.commit() diff --git a/tests/unit/modules/dcnm/dcnm_fabric/test_fabric_create.py b/tests/unit/modules/dcnm/dcnm_fabric/test_fabric_create.py index 668379ecf..37a2b15fa 100644 --- a/tests/unit/modules/dcnm/dcnm_fabric/test_fabric_create.py +++ b/tests/unit/modules/dcnm/dcnm_fabric/test_fabric_create.py @@ -32,44 +32,49 @@ import inspect import pytest -from ansible_collections.cisco.dcnm.plugins.module_utils.common.rest_send import \ +from ansible_collections.cisco.dcnm.plugins.module_utils.common.response_handler import \ + ResponseHandler +from ansible_collections.cisco.dcnm.plugins.module_utils.common.rest_send_v2 import \ RestSend from ansible_collections.cisco.dcnm.plugins.module_utils.common.results import \ Results -from ansible_collections.cisco.dcnm.plugins.module_utils.fabric.fabric_details import \ +from ansible_collections.cisco.dcnm.plugins.module_utils.common.sender_file import \ + Sender +from ansible_collections.cisco.dcnm.plugins.module_utils.fabric.fabric_details_v2 import \ FabricDetailsByName from ansible_collections.cisco.dcnm.tests.unit.module_utils.common.common_utils import \ ResponseGenerator from ansible_collections.cisco.dcnm.tests.unit.modules.dcnm.dcnm_fabric.utils import ( MockAnsibleModule, does_not_raise, fabric_create_fixture, params, payloads_fabric_create, responses_fabric_create, - responses_fabric_details_by_name, rest_send_response_current) + responses_fabric_details_by_name_v2, rest_send_response_current) -def test_fabric_create_00010(fabric_create) -> None: +def test_fabric_create_00000(fabric_create) -> None: """ - Classes and Methods + ### Classes and Methods + - FabricCommon - __init__() - FabricCreate - __init__() - Test + ### Test - Class attributes are initialized to expected values - Exception is not raised """ with does_not_raise(): instance = fabric_create - instance.fabric_details = FabricDetailsByName(params) + instance.fabric_details = FabricDetailsByName() assert instance.class_name == "FabricCreate" - assert instance.action == "create" - assert instance.state == "merged" - assert isinstance(instance.fabric_details, FabricDetailsByName) + assert instance.action == "fabric_create" + assert instance.fabric_details.class_name == "FabricDetailsByName" def test_fabric_create_00020(fabric_create) -> None: """ - Classes and Methods + ### Classes and Methods + - FabricCommon - __init__() - payload setter @@ -77,10 +82,10 @@ def test_fabric_create_00020(fabric_create) -> None: - __init__() - payload setter - Summary + ### Summary - Verify that payload is set to expected value when a valid payload is passed to FabricCreate().payload - - Verify that an Exception is not raised + - Exception is not raised. """ method_name = inspect.stack()[0][3] key = f"{method_name}a" @@ -94,7 +99,8 @@ def test_fabric_create_00020(fabric_create) -> None: def test_fabric_create_00021(fabric_create) -> None: """ - Classes and Methods + ### Classes and Methods + - FabricCommon - __init__() - payloads setter @@ -102,7 +108,7 @@ def test_fabric_create_00021(fabric_create) -> None: - __init__() - payloads setter - Summary + ### Summary - Verify ``ValueError`` is raised because payloads is not a ``dict`` - Verify ``instance.payload`` is not modified, hence it retains its initial value of None @@ -120,20 +126,22 @@ def test_fabric_create_00021(fabric_create) -> None: def test_fabric_create_00022(fabric_create) -> None: """ - Classes and Methods + ### Classes and Methods + - FabricCommon - __init__() - FabricCreate - __init__() - payload setter - Summary - Verify that ``ValueError`` is raised because payload is empty + ### Summary + Verify that ``ValueError`` is raised because payload is empty. """ with does_not_raise(): instance = fabric_create instance.results = Results() - instance.rest_send = RestSend(MockAnsibleModule()) + instance.rest_send = RestSend(params) + match = r"FabricCreate\.payload: payload is empty." with pytest.raises(ValueError, match=match): instance.payload = {} @@ -146,14 +154,15 @@ def test_fabric_create_00022(fabric_create) -> None: ) def test_fabric_create_00023(fabric_create, mandatory_parameter) -> None: """ - Classes and Methods + ### Classes and Methods + - FabricCommon - __init__() - FabricCreate - __init__() - payload setter - Summary + ### Summary - Verify that ``ValueError`` is raised because payload is missing mandatory parameters. @@ -167,7 +176,7 @@ def test_fabric_create_00023(fabric_create, mandatory_parameter) -> None: with does_not_raise(): instance = fabric_create instance.results = Results() - instance.rest_send = RestSend(MockAnsibleModule()) + instance.rest_send = RestSend(params) match = r"FabricCreate\._verify_payload: " match += r"Playbook configuration for fabric .* is missing mandatory\s+" @@ -179,7 +188,8 @@ def test_fabric_create_00023(fabric_create, mandatory_parameter) -> None: def test_fabric_create_00024(fabric_create) -> None: """ - Classes and Methods + ### Classes and Methods + - FabricCommon - __init__() - payloads setter @@ -187,19 +197,20 @@ def test_fabric_create_00024(fabric_create) -> None: - __init__() - commit() - Summary + ### Summary - Verify ``ValueError`` is raised because payload is not set prior to calling commit - Verify instance.payloads is not modified, hence it retains its initial value of None """ - match = r"FabricCreate\.commit: " - match += r"payload must be set prior to calling commit\." - with does_not_raise(): instance = fabric_create instance.results = Results() - instance.rest_send = RestSend(MockAnsibleModule()) + instance.rest_send = RestSend(params) + + match = r"FabricCreate\.commit: " + match += r"payload must be set prior to calling commit\." + with pytest.raises(ValueError, match=match): instance.commit() assert instance.payload is None @@ -226,57 +237,58 @@ def test_fabric_create_00025(fabric_create) -> None: with does_not_raise(): instance = fabric_create - - instance.fabric_details = FabricDetailsByName(params) - instance.fabric_details.rest_send = RestSend(MockAnsibleModule()) + instance.fabric_details = FabricDetailsByName() + instance.fabric_details.rest_send = RestSend(params) instance.fabric_details.rest_send.unit_test = True - - instance.rest_send = RestSend(MockAnsibleModule()) + instance.rest_send = RestSend(params) instance.rest_send.unit_test = True - instance.results = Results() match = r"FabricCreate\._verify_payload:\s+" match += r"Playbook configuration for fabric f1 contains an invalid\s+" match += r"FABRIC_TYPE \(INVALID_FABRIC_TYPE\)\." + with pytest.raises(ValueError, match=match): instance.payload = payloads_fabric_create(key) def test_fabric_create_00026(fabric_create) -> None: """ - Classes and Methods + ### Classes and Methods + - FabricCommon - __init__() - payloads setter - FabricCreateBulk - __init__() - Summary - - Verify behavior when rest_send is not set prior to calling commit + ### Summary + Verify behavior when ``rest_send`` is not set prior to calling commit. - Test - - ``ValueError`` is raised because rest_send is not set prior - to calling commit + ### Test + - ``ValueError`` is raised because ``rest_send`` is not set prior + to calling commit. """ method_name = inspect.stack()[0][3] key = f"{method_name}a" - match = r"FabricCreate\.commit: " - match += r"rest_send must be set prior to calling commit\." - with does_not_raise(): instance = fabric_create - instance.fabric_details = FabricDetailsByName(params) + instance.fabric_details = FabricDetailsByName() instance.results = Results() instance.payload = payloads_fabric_create(key) + + match = r"FabricCreate\.commit: " + match += r"rest_send must be set prior to calling commit\." + with pytest.raises(ValueError, match=match): instance.commit() -def test_fabric_create_00030(monkeypatch, fabric_create) -> None: +def test_fabric_create_00030(fabric_create) -> None: """ - Classes and Methods + ### Classes and Methods + - FabricCommon() - __init__() - payloads setter @@ -290,11 +302,12 @@ def test_fabric_create_00030(monkeypatch, fabric_create) -> None: - __init__() - commit() - Summary + ### Summary - Verify behavior when user attempts to create a fabric and no fabrics exist on the controller and the RestSend() RETURN_CODE is 200. - Code Flow + ### Code Flow + - FabricCreate.payloads is set to contain one payload for a fabric (f1) that does not exist on the controller. - FabricCreate.commit() calls FabricCreate()._build_payloads_to_commit() @@ -310,34 +323,29 @@ def test_fabric_create_00030(monkeypatch, fabric_create) -> None: method_name = inspect.stack()[0][3] key = f"{method_name}a" - PATCH_DCNM_SEND = "ansible_collections.cisco.dcnm.plugins." - PATCH_DCNM_SEND += "module_utils.common.rest_send.dcnm_send" - def responses(): - yield responses_fabric_details_by_name(key) + yield responses_fabric_details_by_name_v2(key) yield responses_fabric_create(key) - gen = ResponseGenerator(responses()) + gen_responses = ResponseGenerator(responses()) - def mock_dcnm_send(*args, **kwargs): - item = gen.next - return item + sender = Sender() + sender.ansible_module = MockAnsibleModule() + sender.gen = gen_responses + rest_send = RestSend(params) + rest_send.unit_test = True + rest_send.timeout = 1 + rest_send.response_handler = ResponseHandler() + rest_send.sender = sender with does_not_raise(): instance = fabric_create - instance.fabric_details = FabricDetailsByName(params) - instance.fabric_details.rest_send = RestSend(MockAnsibleModule()) - instance.fabric_details.rest_send.unit_test = True - - instance.rest_send = RestSend(MockAnsibleModule()) - instance.rest_send.unit_test = True - + instance.fabric_details = FabricDetailsByName() + instance.fabric_details.rest_send = rest_send + instance.fabric_details.results = Results() + instance.rest_send = rest_send instance.results = Results() instance.payload = payloads_fabric_create(key) - instance.fabric_details.rest_send.unit_test = True - - monkeypatch.setattr(PATCH_DCNM_SEND, mock_dcnm_send) - with does_not_raise(): instance.commit() assert isinstance(instance.results.diff, list) @@ -353,7 +361,7 @@ def mock_dcnm_send(*args, **kwargs): assert instance.results.diff[0].get("BGP_AS", None) == 65001 assert instance.results.diff[0].get("FABRIC_NAME", None) == "f1" - assert instance.results.metadata[0].get("action", None) == "create" + assert instance.results.metadata[0].get("action", None) == "fabric_create" assert instance.results.metadata[0].get("check_mode", None) is False assert instance.results.metadata[0].get("sequence_number", None) == 1 assert instance.results.metadata[0].get("state", None) == "merged" @@ -378,7 +386,7 @@ def mock_dcnm_send(*args, **kwargs): assert False not in instance.results.changed -def test_fabric_create_00031(monkeypatch, fabric_create) -> None: +def test_fabric_create_00031(fabric_create) -> None: """ Classes and Methods - FabricCommon() @@ -418,33 +426,28 @@ def test_fabric_create_00031(monkeypatch, fabric_create) -> None: method_name = inspect.stack()[0][3] key = f"{method_name}a" - PATCH_DCNM_SEND = "ansible_collections.cisco.dcnm.plugins." - PATCH_DCNM_SEND += "module_utils.common.rest_send.dcnm_send" - def responses(): - yield responses_fabric_details_by_name(key) + yield responses_fabric_details_by_name_v2(key) - gen = ResponseGenerator(responses()) + gen_responses = ResponseGenerator(responses()) - def mock_dcnm_send(*args, **kwargs): - item = gen.next - return item + sender = Sender() + sender.ansible_module = MockAnsibleModule() + sender.gen = gen_responses + rest_send = RestSend(params) + rest_send.unit_test = True + rest_send.timeout = 1 + rest_send.response_handler = ResponseHandler() + rest_send.sender = sender with does_not_raise(): instance = fabric_create - instance.fabric_details = FabricDetailsByName(params) - instance.fabric_details.rest_send = RestSend(MockAnsibleModule()) - instance.fabric_details.rest_send.unit_test = True - - instance.rest_send = RestSend(MockAnsibleModule()) - instance.rest_send.unit_test = True - + instance.fabric_details = FabricDetailsByName() + instance.fabric_details.rest_send = rest_send + instance.fabric_details.results = Results() + instance.rest_send = rest_send instance.results = Results() instance.payload = payloads_fabric_create(key) - instance.fabric_details.rest_send.unit_test = True - - monkeypatch.setattr(PATCH_DCNM_SEND, mock_dcnm_send) - with does_not_raise(): instance.commit() assert instance._payloads_to_commit == [] @@ -495,34 +498,29 @@ def test_fabric_create_00032(monkeypatch, fabric_create) -> None: method_name = inspect.stack()[0][3] key = f"{method_name}a" - PATCH_DCNM_SEND = "ansible_collections.cisco.dcnm.plugins." - PATCH_DCNM_SEND += "module_utils.common.rest_send.dcnm_send" - def responses(): - yield responses_fabric_details_by_name(key) + yield responses_fabric_details_by_name_v2(key) yield responses_fabric_create(key) - gen = ResponseGenerator(responses()) + gen_responses = ResponseGenerator(responses()) - def mock_dcnm_send(*args, **kwargs): - item = gen.next - return item + sender = Sender() + sender.ansible_module = MockAnsibleModule() + sender.gen = gen_responses + rest_send = RestSend(params) + rest_send.unit_test = True + rest_send.timeout = 1 + rest_send.response_handler = ResponseHandler() + rest_send.sender = sender with does_not_raise(): instance = fabric_create - - instance.fabric_details = FabricDetailsByName(params) - instance.fabric_details.rest_send = RestSend(MockAnsibleModule()) - instance.fabric_details.rest_send.unit_test = True - - instance.rest_send = RestSend(MockAnsibleModule()) - instance.rest_send.unit_test = True - + instance.fabric_details = FabricDetailsByName() + instance.fabric_details.results = Results() + instance.fabric_details.rest_send = rest_send + instance.rest_send = rest_send instance.results = Results() instance.payload = payloads_fabric_create(key) - - monkeypatch.setattr(PATCH_DCNM_SEND, mock_dcnm_send) - with does_not_raise(): instance.commit() assert isinstance(instance.results.diff, list) @@ -536,7 +534,7 @@ def mock_dcnm_send(*args, **kwargs): assert instance.results.diff[0].get("sequence_number", None) == 1 - assert instance.results.metadata[0].get("action", None) == "create" + assert instance.results.metadata[0].get("action", None) == "fabric_create" assert instance.results.metadata[0].get("check_mode", None) is False assert instance.results.metadata[0].get("sequence_number", None) == 1 assert instance.results.metadata[0].get("state", None) == "merged" @@ -559,7 +557,8 @@ def mock_dcnm_send(*args, **kwargs): def test_fabric_create_00033(monkeypatch, fabric_create) -> None: """ - Classes and Methods + ### Classes and Methods + - FabricCommon() - __init__() - payloads setter @@ -573,15 +572,15 @@ def test_fabric_create_00033(monkeypatch, fabric_create) -> None: - __init__() - commit() - Summary + ### Summary - Verify ``ValueError`` is raised when user attempts to create a fabric but the payload contains ``ANYCAST_GW_MAC`` with a malformed mac address. - Setup + ### Setup - FabricDetails().refresh() is set to indicate that no fabrics exist on the controller - Code Flow + ### Code Flow - FabricCreate.payloads is set to contain one payload for a fabric (f1) that does not exist on the controller. ``ANYCAST_GW_MAC`` in this payload has a malformed mac address. @@ -598,33 +597,29 @@ def test_fabric_create_00033(monkeypatch, fabric_create) -> None: method_name = inspect.stack()[0][3] key = f"{method_name}a" - PATCH_DCNM_SEND = "ansible_collections.cisco.dcnm.plugins." - PATCH_DCNM_SEND += "module_utils.common.rest_send.dcnm_send" - def responses(): - yield responses_fabric_details_by_name(key) + yield responses_fabric_details_by_name_v2(key) - gen = ResponseGenerator(responses()) + gen_responses = ResponseGenerator(responses()) - def mock_dcnm_send(*args, **kwargs): - item = gen.next - return item + sender = Sender() + sender.ansible_module = MockAnsibleModule() + sender.gen = gen_responses + rest_send = RestSend(params) + rest_send.unit_test = True + rest_send.timeout = 1 + rest_send.response_handler = ResponseHandler() + rest_send.sender = sender with does_not_raise(): instance = fabric_create - - instance.fabric_details = FabricDetailsByName(params) - instance.fabric_details.rest_send = RestSend(MockAnsibleModule()) - instance.fabric_details.rest_send.unit_test = True - - instance.rest_send = RestSend(MockAnsibleModule()) - instance.rest_send.unit_test = True - + instance.fabric_details = FabricDetailsByName() + instance.fabric_details.results = Results() + instance.fabric_details.rest_send = rest_send + instance.rest_send = rest_send instance.results = Results() instance.payload = payloads_fabric_create(key) - monkeypatch.setattr(PATCH_DCNM_SEND, mock_dcnm_send) - match = r"FabricCreate\._fixup_anycast_gw_mac: " match += "Error translating ANYCAST_GW_MAC for fabric f1, " match += "ANYCAST_GW_MAC: 00:12:34:56:78:9, " diff --git a/tests/unit/modules/dcnm/dcnm_fabric/test_fabric_create_bulk.py b/tests/unit/modules/dcnm/dcnm_fabric/test_fabric_create_bulk.py index b088542e5..77d9e869b 100644 --- a/tests/unit/modules/dcnm/dcnm_fabric/test_fabric_create_bulk.py +++ b/tests/unit/modules/dcnm/dcnm_fabric/test_fabric_create_bulk.py @@ -32,53 +32,58 @@ import inspect import pytest -from ansible_collections.cisco.dcnm.plugins.module_utils.common.rest_send import \ +from ansible_collections.cisco.dcnm.plugins.module_utils.common.response_handler import \ + ResponseHandler +from ansible_collections.cisco.dcnm.plugins.module_utils.common.rest_send_v2 import \ RestSend from ansible_collections.cisco.dcnm.plugins.module_utils.common.results import \ Results -from ansible_collections.cisco.dcnm.plugins.module_utils.fabric.fabric_details import \ +from ansible_collections.cisco.dcnm.plugins.module_utils.common.sender_file import \ + Sender +from ansible_collections.cisco.dcnm.plugins.module_utils.fabric.fabric_details_v2 import \ FabricDetailsByName from ansible_collections.cisco.dcnm.tests.unit.module_utils.common.common_utils import \ ResponseGenerator from ansible_collections.cisco.dcnm.tests.unit.modules.dcnm.dcnm_fabric.utils import ( MockAnsibleModule, does_not_raise, fabric_create_bulk_fixture, params, payloads_fabric_create_bulk, responses_fabric_create_bulk, - responses_fabric_details_by_name, rest_send_response_current) + responses_fabric_details_by_name_v2, rest_send_response_current) -def test_fabric_create_bulk_00010(fabric_create_bulk) -> None: +def test_fabric_create_bulk_00000(fabric_create_bulk) -> None: """ - Classes and Methods + ### Classes and Methods + - FabricCommon - __init__() - FabricCreateBulk - __init__() - Test + ### Test - Class attributes are initialized to expected values - Exception is not raised """ with does_not_raise(): instance = fabric_create_bulk - instance.fabric_details = FabricDetailsByName(params) + instance.fabric_details = FabricDetailsByName() assert instance.class_name == "FabricCreateBulk" - assert instance.action == "create" - assert instance.state == "merged" - assert isinstance(instance.fabric_details, FabricDetailsByName) + assert instance.action == "fabric_create" + assert instance.fabric_details.class_name == "FabricDetailsByName" def test_fabric_create_bulk_00020(fabric_create_bulk) -> None: """ - Classes and Methods + ### Classes and Methods + - FabricCommon - __init__() - payloads setter - FabricCreateBulk - __init__() - Test + ### Test - payloads is set to expected value - - Exception is not raised + - Exception is not raised. """ method_name = inspect.stack()[0][3] key = f"{method_name}a" @@ -92,7 +97,8 @@ def test_fabric_create_bulk_00020(fabric_create_bulk) -> None: def test_fabric_create_bulk_00021(fabric_create_bulk) -> None: """ - Classes and Methods + ### Classes and Methods + - FabricCommon - __init__() - payloads setter @@ -100,17 +106,18 @@ def test_fabric_create_bulk_00021(fabric_create_bulk) -> None: - __init__() - payloads setter - Summary + ### Summary - Verify ``ValueError`` is raised because payloads is not a list - Verify ``instance.payloads`` is not modified, hence it retains its initial value of None """ - match = r"FabricCreateBulk\.payloads: " - match += r"payloads must be a list of dict\." - with does_not_raise(): instance = fabric_create_bulk instance.results = Results() + + match = r"FabricCreateBulk\.payloads: " + match += r"payloads must be a list of dict\." + with pytest.raises(ValueError, match=match): instance.payloads = "NOT_A_LIST" assert instance.payloads is None @@ -118,26 +125,28 @@ def test_fabric_create_bulk_00021(fabric_create_bulk) -> None: def test_fabric_create_bulk_00022(fabric_create_bulk) -> None: """ - Classes and Methods + ### Classes and Methods + - FabricCommon - __init__() - payloads setter - FabricCreateBulk - __init__() - Summary + ### Summary - Verify ``ValueError`` is raised because payloads is a list with a non-dict element - Verify instance.payloads is not modified, hence it retains its initial value of None """ + with does_not_raise(): + instance = fabric_create_bulk + instance.results = Results() + match = r"FabricCreateBulk._verify_payload: " match += r"Playbook configuration for fabrics must be a dict\.\s+" match += r"Got type int, value 1\." - with does_not_raise(): - instance = fabric_create_bulk - instance.results = Results() with pytest.raises(ValueError, match=match): instance.payloads = [1, 2, 3] assert instance.payloads is None @@ -145,7 +154,8 @@ def test_fabric_create_bulk_00022(fabric_create_bulk) -> None: def test_fabric_create_bulk_00023(fabric_create_bulk) -> None: """ - Classes and Methods + ### Classes and Methods + - FabricCommon - __init__() - payloads setter @@ -153,23 +163,24 @@ def test_fabric_create_bulk_00023(fabric_create_bulk) -> None: - __init__() - commit() - Summary - - Verify behavior when payloads is not set prior to calling commit + ### Summary + Verify behavior when payloads is not set prior to calling commit. - Test + ### Test - ``ValueError`` is raised because payloads is not set prior to calling commit - instance.payloads is not modified, hence it retains its initial value of None """ - match = r"FabricCreateBulk\.commit: " - match += r"payloads must be set prior to calling commit\." - with does_not_raise(): instance = fabric_create_bulk instance.results = Results() - instance.rest_send = RestSend(MockAnsibleModule()) + instance.rest_send = RestSend(params) instance.rest_send.unit_test = True + + match = r"FabricCreateBulk\.commit: " + match += r"payloads must be set prior to calling commit\." + with pytest.raises(ValueError, match=match): instance.commit() assert instance.payloads is None @@ -177,27 +188,30 @@ def test_fabric_create_bulk_00023(fabric_create_bulk) -> None: def test_fabric_create_bulk_00024(fabric_create_bulk) -> None: """ - Classes and Methods + ### Classes and Methods + - FabricCommon - __init__() - payloads setter - FabricCreateBulk - __init__() - Summary - Verify behavior when payloads is set to an empty list + ### Summary + Verify behavior when ``payloads`` is set to an empty list. + + ### Setup + + - ``payloads`` is set to an empty list. - Setup - - FabricCreatebulk().payloads is set to an empty list + ### Test - Test - Exception is not raised - - payloads is set to an empty list + - ``payloads`` is set to an empty list. - NOTES: - - element_spec is configured such that AnsibleModule will raise an - exception when config is not a list of dict. Hence, we do not test - instance.commit() here since it would never be reached. + ### NOTES + - element_spec is configured such that an exception is raised when + config is not a list of dict. Hence, we do not test ``commit`` + here since it would never be reached. """ with does_not_raise(): instance = fabric_create_bulk @@ -208,23 +222,24 @@ def test_fabric_create_bulk_00024(fabric_create_bulk) -> None: def test_fabric_create_bulk_00025(fabric_create_bulk) -> None: """ - Classes and Methods + ### Classes and Methods + - FabricCreateCommon - __init__() - payloads setter - FabricCreateBulk - __init__() - Summary - - Verify behavior when payloads contains a dict with an unexpected - value for the FABRIC_TYPE key. + ### Summary + Verify behavior when payloads contains a dict with an unexpected + value for the FABRIC_TYPE key. - Setup + ### Setup - FabricCreatebulk().payloads is set to contain a dict with FABRIC_TYPE - set to "INVALID_FABRIC_TYPE" + set to "INVALID_FABRIC_TYPE". - Test - - ``ValueError`` is raised because the value of FABRIC_TYPE is invalid + ### Test + - ``ValueError`` is raised because the value of FABRIC_TYPE is invalid. """ method_name = inspect.stack()[0][3] key = f"{method_name}a" @@ -232,57 +247,59 @@ def test_fabric_create_bulk_00025(fabric_create_bulk) -> None: with does_not_raise(): instance = fabric_create_bulk - instance.fabric_details = FabricDetailsByName(params) - instance.fabric_details.rest_send = RestSend(MockAnsibleModule()) + instance.fabric_details = FabricDetailsByName() + instance.fabric_details.rest_send = RestSend(params) instance.fabric_details.rest_send.unit_test = True - - instance.rest_send = RestSend(MockAnsibleModule()) + instance.rest_send = RestSend(params) instance.rest_send.unit_test = True - instance.results = Results() match = r"FabricCreateBulk\._verify_payload:\s+" match += r"Playbook configuration for fabric f1 contains an invalid\s+" match += r"FABRIC_TYPE \(INVALID_FABRIC_TYPE\)\.\s+" match += r"Valid values for FABRIC_TYPE:" + with pytest.raises(ValueError, match=match): instance.payloads = payloads_fabric_create_bulk(key) def test_fabric_create_bulk_00026(fabric_create_bulk) -> None: """ - Classes and Methods + ### Classes and Methods + - FabricCommon - __init__() - payloads setter - FabricCreateBulk - __init__() - Summary - - Verify behavior when rest_send is not set prior to calling commit + ### Summary + Verify behavior when ``rest_send`` is not set prior to calling ``commit``. - Test + ### Test - ``ValueError`` is raised because rest_send is not set prior to calling commit """ method_name = inspect.stack()[0][3] key = f"{method_name}a" - match = r"FabricCreateBulk\.commit: " - match += r"rest_send must be set prior to calling commit\." - with does_not_raise(): instance = fabric_create_bulk - instance.fabric_details = FabricDetailsByName(params) + instance.fabric_details = FabricDetailsByName() instance.results = Results() instance.payloads = payloads_fabric_create_bulk(key) + + match = r"FabricCreateBulk\.commit: " + match += r"rest_send must be set prior to calling commit\." + with pytest.raises(ValueError, match=match): instance.commit() -def test_fabric_create_bulk_00030(monkeypatch, fabric_create_bulk) -> None: +def test_fabric_create_bulk_00030(fabric_create_bulk) -> None: """ - Classes and Methods + ### Classes and Methods + - FabricCommon() - __init__() - payloads setter @@ -296,11 +313,12 @@ def test_fabric_create_bulk_00030(monkeypatch, fabric_create_bulk) -> None: - __init__() - commit() - Summary - - Verify behavior when user attempts to create a fabric and no fabrics - exist on the controller and the RestSend() RETURN_CODE is 200. + ### Summary + Verify behavior when user attempts to create a fabric and no fabrics + exist on the controller and the RestSend() RETURN_CODE is 200. + + ### Code Flow - Code Flow - FabricCreateBulk.payloads is set to contain one payload for a fabric (f1) that does not exist on the controller. - FabricCreateBulk.commit() calls FabricCreate()._build_payloads_to_commit() @@ -316,34 +334,29 @@ def test_fabric_create_bulk_00030(monkeypatch, fabric_create_bulk) -> None: method_name = inspect.stack()[0][3] key = f"{method_name}a" - PATCH_DCNM_SEND = "ansible_collections.cisco.dcnm.plugins." - PATCH_DCNM_SEND += "module_utils.common.rest_send.dcnm_send" - def responses(): - yield responses_fabric_details_by_name(key) + yield responses_fabric_details_by_name_v2(key) yield responses_fabric_create_bulk(key) - gen = ResponseGenerator(responses()) + gen_responses = ResponseGenerator(responses()) - def mock_dcnm_send(*args, **kwargs): - item = gen.next - return item + sender = Sender() + sender.ansible_module = MockAnsibleModule() + sender.gen = gen_responses + rest_send = RestSend(params) + rest_send.unit_test = True + rest_send.timeout = 1 + rest_send.response_handler = ResponseHandler() + rest_send.sender = sender with does_not_raise(): instance = fabric_create_bulk - instance.fabric_details = FabricDetailsByName(params) - instance.fabric_details.rest_send = RestSend(MockAnsibleModule()) - instance.fabric_details.rest_send.unit_test = True - - instance.rest_send = RestSend(MockAnsibleModule()) - instance.rest_send.unit_test = True - + instance.fabric_details = FabricDetailsByName() + instance.fabric_details.results = Results() + instance.fabric_details.rest_send = rest_send + instance.rest_send = rest_send instance.results = Results() instance.payloads = payloads_fabric_create_bulk(key) - instance.fabric_details.rest_send.unit_test = True - - monkeypatch.setattr(PATCH_DCNM_SEND, mock_dcnm_send) - with does_not_raise(): instance.commit() assert isinstance(instance.results.diff, list) @@ -359,7 +372,7 @@ def mock_dcnm_send(*args, **kwargs): assert instance.results.diff[0].get("BGP_AS", None) == 65001 assert instance.results.diff[0].get("FABRIC_NAME", None) == "f1" - assert instance.results.metadata[0].get("action", None) == "create" + assert instance.results.metadata[0].get("action", None) == "fabric_create" assert instance.results.metadata[0].get("check_mode", None) is False assert instance.results.metadata[0].get("sequence_number", None) == 1 assert instance.results.metadata[0].get("state", None) == "merged" @@ -383,9 +396,10 @@ def mock_dcnm_send(*args, **kwargs): assert False not in instance.results.changed -def test_fabric_create_bulk_00031(monkeypatch, fabric_create_bulk) -> None: +def test_fabric_create_bulk_00031(fabric_create_bulk) -> None: """ - Classes and Methods + ### Classes and Methods + - FabricCommon() - __init__() - FabricDetails() @@ -401,15 +415,15 @@ def test_fabric_create_bulk_00031(monkeypatch, fabric_create_bulk) -> None: - __init__() - commit() - Summary - - Verify behavior when FabricCreateBulk() is used to create a fabric and - the fabric exists on the controller. + ### Summary + Verify behavior when FabricCreateBulk() is used to create a fabric and + the fabric exists on the controller. - Setup + ### Setup - FabricDetails().refresh() is set to indicate that fabric f1 exists on the controller - Code Flow + ### Code Flow - FabricCreateBulk.payloads is set to contain one payload for a fabric (f1) that already exists on the controller. - FabricCreateBulk.commit() calls FabricCreate()._build_payloads_to_commit() @@ -423,33 +437,29 @@ def test_fabric_create_bulk_00031(monkeypatch, fabric_create_bulk) -> None: method_name = inspect.stack()[0][3] key = f"{method_name}a" - PATCH_DCNM_SEND = "ansible_collections.cisco.dcnm.plugins." - PATCH_DCNM_SEND += "module_utils.common.rest_send.dcnm_send" - def responses(): - yield responses_fabric_details_by_name(key) + yield responses_fabric_details_by_name_v2(key) - gen = ResponseGenerator(responses()) + gen_responses = ResponseGenerator(responses()) - def mock_dcnm_send(*args, **kwargs): - item = gen.next - return item + sender = Sender() + sender.ansible_module = MockAnsibleModule() + sender.gen = gen_responses + rest_send = RestSend(params) + rest_send.unit_test = True + rest_send.timeout = 1 + rest_send.response_handler = ResponseHandler() + rest_send.sender = sender with does_not_raise(): instance = fabric_create_bulk - instance.fabric_details = FabricDetailsByName(params) - instance.fabric_details.rest_send = RestSend(MockAnsibleModule()) - instance.fabric_details.rest_send.unit_test = True - - instance.rest_send = RestSend(MockAnsibleModule()) - instance.rest_send.unit_test = True - + instance.fabric_details = FabricDetailsByName() + instance.fabric_details.results = Results() + instance.fabric_details.rest_send = rest_send + instance.rest_send = rest_send instance.results = Results() instance.payloads = payloads_fabric_create_bulk(key) instance.fabric_details.rest_send.unit_test = True - - monkeypatch.setattr(PATCH_DCNM_SEND, mock_dcnm_send) - with does_not_raise(): instance.commit() assert instance._payloads_to_commit == [] @@ -459,9 +469,10 @@ def mock_dcnm_send(*args, **kwargs): assert instance.results.result == [] -def test_fabric_create_bulk_00032(monkeypatch, fabric_create_bulk) -> None: +def test_fabric_create_bulk_00032(fabric_create_bulk) -> None: """ - Classes and Methods + ### Classes and Methods + - FabricCommon() - __init__() - payloads setter @@ -475,15 +486,15 @@ def test_fabric_create_bulk_00032(monkeypatch, fabric_create_bulk) -> None: - __init__() - commit() - Summary - - Verify behavior when user attempts to create a fabric but the - controller RETURN_CODE is 500. + ### Summary + Verify behavior when user attempts to create a fabric but the + controller RETURN_CODE is 500. - Setup + ### Setup - FabricDetails().refresh() is set to indicate that no fabrics exist on the controller - Code Flow + ### Code Flow - FabricCreateBulk.payloads is set to contain one payload for a fabric (f1) that does not exist on the controller. - FabricCreateBulk.commit() calls FabricCreate()._build_payloads_to_commit() @@ -500,34 +511,29 @@ def test_fabric_create_bulk_00032(monkeypatch, fabric_create_bulk) -> None: method_name = inspect.stack()[0][3] key = f"{method_name}a" - PATCH_DCNM_SEND = "ansible_collections.cisco.dcnm.plugins." - PATCH_DCNM_SEND += "module_utils.common.rest_send.dcnm_send" - def responses(): - yield responses_fabric_details_by_name(key) + yield responses_fabric_details_by_name_v2(key) yield responses_fabric_create_bulk(key) - gen = ResponseGenerator(responses()) + gen_responses = ResponseGenerator(responses()) - def mock_dcnm_send(*args, **kwargs): - item = gen.next - return item + sender = Sender() + sender.ansible_module = MockAnsibleModule() + sender.gen = gen_responses + rest_send = RestSend(params) + rest_send.unit_test = True + rest_send.timeout = 1 + rest_send.response_handler = ResponseHandler() + rest_send.sender = sender with does_not_raise(): instance = fabric_create_bulk - - instance.fabric_details = FabricDetailsByName(params) - instance.fabric_details.rest_send = RestSend(MockAnsibleModule()) - instance.fabric_details.rest_send.unit_test = True - - instance.rest_send = RestSend(MockAnsibleModule()) - instance.rest_send.unit_test = True - + instance.fabric_details = FabricDetailsByName() + instance.fabric_details.results = Results() + instance.fabric_details.rest_send = rest_send + instance.rest_send = rest_send instance.results = Results() instance.payloads = payloads_fabric_create_bulk(key) - - monkeypatch.setattr(PATCH_DCNM_SEND, mock_dcnm_send) - with does_not_raise(): instance.commit() assert isinstance(instance.results.diff, list) @@ -541,7 +547,7 @@ def mock_dcnm_send(*args, **kwargs): assert instance.results.diff[0].get("sequence_number", None) == 1 - assert instance.results.metadata[0].get("action", None) == "create" + assert instance.results.metadata[0].get("action", None) == "fabric_create" assert instance.results.metadata[0].get("check_mode", None) is False assert instance.results.metadata[0].get("sequence_number", None) == 1 assert instance.results.metadata[0].get("state", None) == "merged" @@ -564,7 +570,8 @@ def mock_dcnm_send(*args, **kwargs): def test_fabric_create_bulk_00033(monkeypatch, fabric_create_bulk) -> None: """ - Classes and Methods + ### Classes and Methods + - FabricCommon() - __init__() - payloads setter @@ -578,15 +585,16 @@ def test_fabric_create_bulk_00033(monkeypatch, fabric_create_bulk) -> None: - __init__() - commit() - Summary - - Verify ``ValueError`` is raised when user attempts to create a fabric - but the payload contains ``ANYCAST_GW_MAC`` with a malformed mac address. + ### Summary + Verify ``ValueError`` is raised when user attempts to create a fabric + but the payload contains ``ANYCAST_GW_MAC`` with a malformed mac address. + + ### Setup - Setup - FabricDetails().refresh() is set to indicate that no fabrics exist on the controller - Code Flow + ### Code Flow - FabricCreateBulk.payloads is set to contain one payload for a fabric (f1) that does not exist on the controller. ``ANYCAST_GW_MAC`` in this payload has a malformed mac address. @@ -605,33 +613,30 @@ def test_fabric_create_bulk_00033(monkeypatch, fabric_create_bulk) -> None: method_name = inspect.stack()[0][3] key = f"{method_name}a" - PATCH_DCNM_SEND = "ansible_collections.cisco.dcnm.plugins." - PATCH_DCNM_SEND += "module_utils.common.rest_send.dcnm_send" - def responses(): - yield responses_fabric_details_by_name(key) + yield responses_fabric_details_by_name_v2(key) - gen = ResponseGenerator(responses()) + gen_responses = ResponseGenerator(responses()) - def mock_dcnm_send(*args, **kwargs): - item = gen.next - return item + sender = Sender() + sender.ansible_module = MockAnsibleModule() + sender.gen = gen_responses + rest_send = RestSend(params) + rest_send.unit_test = True + rest_send.timeout = 1 + rest_send.response_handler = ResponseHandler() + rest_send.sender = sender with does_not_raise(): instance = fabric_create_bulk - instance.fabric_details = FabricDetailsByName(params) - instance.fabric_details.rest_send = RestSend(MockAnsibleModule()) - instance.fabric_details.rest_send.unit_test = True - - instance.rest_send = RestSend(MockAnsibleModule()) - instance.rest_send.unit_test = True - + instance.fabric_details = FabricDetailsByName() + instance.fabric_details.results = Results() + instance.fabric_details.rest_send = rest_send + instance.rest_send = rest_send instance.results = Results() instance.payloads = payloads_fabric_create_bulk(key) - monkeypatch.setattr(PATCH_DCNM_SEND, mock_dcnm_send) - match = r"FabricCreateBulk\._fixup_anycast_gw_mac: " match += "Error translating ANYCAST_GW_MAC for fabric f1, " match += "ANYCAST_GW_MAC: 00:12:34:56:78:9, " diff --git a/tests/unit/modules/dcnm/dcnm_fabric/test_fabric_create_common.py b/tests/unit/modules/dcnm/dcnm_fabric/test_fabric_create_common.py index e4a3a3d5b..312820719 100644 --- a/tests/unit/modules/dcnm/dcnm_fabric/test_fabric_create_common.py +++ b/tests/unit/modules/dcnm/dcnm_fabric/test_fabric_create_common.py @@ -55,14 +55,12 @@ def test_fabric_create_common_00010(fabric_create_common) -> None: """ with does_not_raise(): instance = fabric_create_common - instance._build_properties() - assert isinstance(instance.ep_fabric_create, EpFabricCreate) + assert instance.ep_fabric_create.class_name == "EpFabricCreate" + assert instance.fabric_types.class_name == "FabricTypes" assert instance.class_name == "FabricCreateCommon" - assert instance.action == "create" - assert instance.check_mode is False + assert instance.action == "fabric_create" assert instance.path is None assert instance.verb is None - assert instance.state == "merged" assert instance._payloads_to_commit == [] @@ -85,7 +83,6 @@ def test_fabric_create_common_00030(fabric_create_common) -> None: with does_not_raise(): instance = fabric_create_common - instance._build_properties() match = r"FabricCreateCommon\.fabric_type: FABRIC_TYPE must be one of\s+.*" match += "Got INVALID_FABRIC_TYPE" @@ -138,7 +135,6 @@ def fabric_name(self, value): instance = fabric_create_common monkeypatch.setattr(instance, "ep_fabric_create", MockEpFabricCreate()) instance.ep_fabric_create = MockEpFabricCreate() - instance._build_properties() match = r"MockEpFabricCreate\.fabric_name: mocked exception\." with pytest.raises(ValueError, match=match): @@ -190,7 +186,6 @@ def template_name(self, value): instance = fabric_create_common monkeypatch.setattr(instance, "ep_fabric_create", MockEpFabricCreate()) instance.ep_fabric_create = MockEpFabricCreate() - instance._build_properties() match = r"MockEpFabricCreate\.template_name: mocked exception\." with pytest.raises(ValueError, match=match): @@ -240,7 +235,6 @@ def template_name(self): with does_not_raise(): instance = fabric_create_common monkeypatch.setattr(instance, "fabric_types", MockFabricTypes()) - instance._build_properties() match = r"MockEpFabricCreate\.template_name: mocked exception\." with pytest.raises(ValueError, match=match): @@ -282,7 +276,6 @@ def mock_set_fabric_create_endpoint( monkeypatch.setattr( instance, "_set_fabric_create_endpoint", mock_set_fabric_create_endpoint ) - instance._build_properties() instance._payloads_to_commit = [payload] match = r"mock_set_fabric_endpoint\(\): mocked exception\." diff --git a/tests/unit/modules/dcnm/dcnm_fabric/test_fabric_delete.py b/tests/unit/modules/dcnm/dcnm_fabric/test_fabric_delete.py index f5123b2c6..3330928cb 100644 --- a/tests/unit/modules/dcnm/dcnm_fabric/test_fabric_delete.py +++ b/tests/unit/modules/dcnm/dcnm_fabric/test_fabric_delete.py @@ -32,90 +32,98 @@ import inspect import pytest -from ansible_collections.cisco.dcnm.plugins.module_utils.common.api.v1.lan_fabric.rest.control.fabrics.fabrics import \ - EpFabricDelete -from ansible_collections.cisco.dcnm.plugins.module_utils.common.rest_send import \ +from ansible_collections.cisco.dcnm.plugins.module_utils.common.response_handler import \ + ResponseHandler +from ansible_collections.cisco.dcnm.plugins.module_utils.common.rest_send_v2 import \ RestSend from ansible_collections.cisco.dcnm.plugins.module_utils.common.results import \ Results -from ansible_collections.cisco.dcnm.plugins.module_utils.fabric.fabric_details import \ +from ansible_collections.cisco.dcnm.plugins.module_utils.common.sender_file import \ + Sender +from ansible_collections.cisco.dcnm.plugins.module_utils.fabric.fabric_details_v2 import \ FabricDetailsByName from ansible_collections.cisco.dcnm.plugins.module_utils.fabric.fabric_summary import \ FabricSummary from ansible_collections.cisco.dcnm.tests.unit.module_utils.common.common_utils import \ ResponseGenerator from ansible_collections.cisco.dcnm.tests.unit.modules.dcnm.dcnm_fabric.utils import ( - MockAnsibleModule, does_not_raise, fabric_delete_fixture, params, - responses_fabric_delete, responses_fabric_details_by_name, + MockAnsibleModule, does_not_raise, fabric_delete_fixture, + responses_fabric_delete, responses_fabric_details_by_name_v2, responses_fabric_summary, rest_send_response_current) +PARAMS = {"state": "deleted", "check_mode": False} -def test_fabric_delete_00010(fabric_delete) -> None: + +def test_fabric_delete_00000(fabric_delete) -> None: """ - Classes and Methods + ### Classes and Methods + - FabricCommon - __init__() - FabricDelete - __init__() - - _build_properties() - Test + ### Test + - Class attributes are initialized to expected values - Exception is not raised """ with does_not_raise(): instance = fabric_delete - instance.fabric_details = FabricDetailsByName(params) - assert instance.action == "delete" + instance.fabric_details = FabricDetailsByName() + assert instance.action == "fabric_delete" assert instance._cannot_delete_fabric_reason is None assert instance.class_name == "FabricDelete" assert instance.fabric_names is None assert instance._fabrics_to_delete == [] assert instance.path is None - assert instance.state == "deleted" assert instance.verb is None - assert isinstance(instance.ep_fabric_delete, EpFabricDelete) - assert isinstance(instance.fabric_details, FabricDetailsByName) + assert instance.ep_fabric_delete.class_name == "EpFabricDelete" + assert instance.fabric_details.class_name == "FabricDetailsByName" def test_fabric_delete_00020(fabric_delete) -> None: """ - Classes and Methods - - FabricCommon - - __init__() - - FabricDelete - - __init__() - - _set_fabric_delete_endpoint() + ### Classes and Methods + + - FabricCommon + - __init__() + - FabricDelete + - __init__() + - _set_fabric_delete_endpoint() - Summary - - Verify that endpoint values are set correctly when a valid - fabric_name is passed to _set_fabric_delete_endpoint() - - Verify that an Exception is not raised + ### Summary + + - Verify that endpoint values are set correctly when ``fabric_names`` + contains a valid fabric name. + - Verify that an Exception is not raised """ with does_not_raise(): instance = fabric_delete - instance.results = Results() + instance.rest_send = RestSend(PARAMS) instance._set_fabric_delete_endpoint("MyFabric") path = "/appcenter/cisco/ndfc/api/v1/lan-fabric/rest/control/fabrics" path += "/MyFabric" - assert instance.path == path - assert instance.verb == "DELETE" + assert instance.rest_send.path == path + assert instance.rest_send.verb == "DELETE" def test_fabric_delete_00021(fabric_delete) -> None: """ - Classes and Methods - - FabricCommon - - __init__() - - payloads setter - - FabricDelete - - __init__() - - _set_fabric_delete_endpoint() + ### Classes and Methods + + - FabricCommon + - __init__() + - payloads setter + - FabricDelete + - __init__() + - _set_fabric_delete_endpoint() - Summary - - Verify ``TypeError`` is raised because call to - _set_fabric_delete_endpoint() is missing argument - ``fabric_name``. + ### Summary + + - Verify ``TypeError`` is raised because call to + _set_fabric_delete_endpoint() is missing argument + ``fabric_name``. """ with does_not_raise(): instance = fabric_delete @@ -130,19 +138,21 @@ def test_fabric_delete_00021(fabric_delete) -> None: @pytest.mark.parametrize("fabric_name", [None, 123, 123.45, [], {}]) def test_fabric_delete_00022(fabric_delete, fabric_name) -> None: """ - Classes and Methods - - FabricCommon - - __init__() - - payloads setter - - FabricDelete - - __init__() - - _set_fabric_delete_endpoint() - - ApiEndpoints.fabric_delete + ### Classes and Methods + + - FabricCommon + - __init__() + - payloads setter + - FabricDelete + - __init__() + - _set_fabric_delete_endpoint() + - EpFabricDelete() + + ### Summary - Summary - - Verify ApiEndpoints re-raises ``TypeError`` raised by - ConversionUtils() because ``fabric_name`` argument passed - to _set_fabric_delete_endpoint() is not a string. + - Verify EpFabricDelete() re-raises ``TypeError`` raised by + ConversionUtils() because ``fabric_name`` argument passed + to _set_fabric_delete_endpoint() is not a string. """ match = r"ConversionUtils\.validate_fabric_name: " match += "Invalid fabric name. Expected string. Got" @@ -156,19 +166,21 @@ def test_fabric_delete_00022(fabric_delete, fabric_name) -> None: @pytest.mark.parametrize("fabric_name", ["", "1abc", "a,bcd", "abc d", "ab!d"]) def test_fabric_delete_00023(fabric_delete, fabric_name) -> None: """ - Classes and Methods - - FabricCommon - - __init__() - - payloads setter - - FabricDelete - - __init__() - - _set_fabric_delete_endpoint() - - ApiEndpoints.fabric_delete + ### Classes and Methods + + - FabricCommon + - __init__() + - payloads setter + - FabricDelete + - __init__() + - _set_fabric_delete_endpoint() + - EpFabricDelete() + + ### Summary - Summary - - Verify ApiEndpoints() re-raises ``ValueError`` raised by - ConversionUtils() because ``fabric_name`` argument passed - to _set_fabric_delete_endpoint() is an invalid string. + - Verify EpFabricDelete() re-raises ``ValueError`` raised by + ConversionUtils() because ``fabric_name`` argument passed + to _set_fabric_delete_endpoint() is an invalid string. """ match = r"ConversionUtils\.validate_fabric_name: " match += rf"Invalid fabric name: {fabric_name}\. " @@ -184,23 +196,26 @@ def test_fabric_delete_00023(fabric_delete, fabric_name) -> None: def test_fabric_delete_00030(fabric_delete) -> None: """ - Classes and Methods - - FabricCommon - - __init__() - - FabricDelete - - __init__() - - commit() - - _validate_commit_parameters() + ### Classes and Methods + + - FabricCommon + - __init__() + - FabricDelete + - __init__() + - commit() + - _validate_commit_parameters() - Summary - - Verify that ``ValueError`` is raised because fabric_names is not set - prior to calling commit() + ### Summary + + - Verify that ``ValueError`` is raised because fabric_names is not set + prior to calling commit() """ with does_not_raise(): instance = fabric_delete + instance.fabric_details = FabricDetailsByName() + instance.rest_send = RestSend(PARAMS) instance.results = Results() - instance.rest_send = RestSend(MockAnsibleModule()) match = r"FabricDelete\._validate_commit_parameters: " match += "fabric_names must be set prior to calling commit." @@ -210,21 +225,23 @@ def test_fabric_delete_00030(fabric_delete) -> None: def test_fabric_delete_00031(fabric_delete) -> None: """ - Classes and Methods - - FabricCommon - - __init__() - - FabricDelete - - __init__() - - commit() - - _validate_commit_parameters() + ### Classes and Methods - Summary - - Verify that ``ValueError`` is raised because rest_send is not set - prior to calling commit() + - FabricCommon + - __init__() + - FabricDelete + - __init__() + - commit() + - _validate_commit_parameters() + ### Summary + + - Verify that ``ValueError`` is raised because rest_send is not set + prior to calling commit() """ with does_not_raise(): instance = fabric_delete + instance.fabric_details = FabricDetailsByName() instance.results = Results() instance.fabric_names = ["MyFabric"] @@ -236,22 +253,25 @@ def test_fabric_delete_00031(fabric_delete) -> None: def test_fabric_delete_00032(fabric_delete) -> None: """ - Classes and Methods - - FabricCommon - - __init__() - - FabricDelete - - __init__() - - commit() - - _validate_commit_parameters() + ### Classes and Methods + + - FabricCommon + - __init__() + - FabricDelete + - __init__() + - commit() + - _validate_commit_parameters() + + ### Summary - Summary - - Verify that ``ValueError`` is raised because results is not set - prior to calling commit() + - Verify that ``ValueError`` is raised because results is not set + prior to calling commit() """ with does_not_raise(): instance = fabric_delete - instance.rest_send = RestSend(MockAnsibleModule()) + instance.fabric_details = FabricDetailsByName() + instance.rest_send = RestSend(PARAMS) instance.fabric_names = ["MyFabric"] match = r"FabricDelete\._validate_commit_parameters: " @@ -260,9 +280,10 @@ def test_fabric_delete_00032(fabric_delete) -> None: instance.commit() -def test_fabric_delete_00040(monkeypatch, fabric_delete) -> None: +def test_fabric_delete_00040(fabric_delete) -> None: """ - Classes and Methods + ### Classes and Methods + - FabricCommon() - __init__() - payloads setter @@ -276,12 +297,14 @@ def test_fabric_delete_00040(monkeypatch, fabric_delete) -> None: - __init__() - commit() - Summary + ### Summary + - Verify successful fabric delete code path. - The user attempts to delete a fabric and the fabric exists on the controller, and the fabric is empty. - Code Flow + ### Code Flow + - FabricDelete.commit() calls FabricDelete()._validate_commit_parameters() which succeeds since all required parameters are set. - FabricDelete.commit() calls FabricDelete()._get_fabrics_to_delete() @@ -308,39 +331,32 @@ def test_fabric_delete_00040(monkeypatch, fabric_delete) -> None: method_name = inspect.stack()[0][3] key = f"{method_name}a" - PATCH_DCNM_SEND = "ansible_collections.cisco.dcnm.plugins." - PATCH_DCNM_SEND += "module_utils.common.rest_send.dcnm_send" - def responses(): - yield responses_fabric_details_by_name(key) + yield responses_fabric_details_by_name_v2(key) yield responses_fabric_summary(key) yield responses_fabric_delete(key) - gen = ResponseGenerator(responses()) + gen_responses = ResponseGenerator(responses()) - def mock_dcnm_send(*args, **kwargs): - item = gen.next - return item + sender = Sender() + sender.ansible_module = MockAnsibleModule() + sender.gen = gen_responses + rest_send = RestSend({"state": "deleted", "check_mode": False}) + rest_send.unit_test = True + rest_send.timeout = 1 + rest_send.response_handler = ResponseHandler() + rest_send.sender = sender with does_not_raise(): instance = fabric_delete instance.fabric_names = ["f1"] - - instance.fabric_details = FabricDetailsByName(params) - instance.fabric_details.rest_send = RestSend(MockAnsibleModule()) - instance.fabric_details.rest_send.unit_test = True - - instance.fabric_summary = FabricSummary(params) - instance.fabric_summary.rest_send = RestSend(MockAnsibleModule()) - instance.fabric_summary.rest_send.unit_test = True - - instance.rest_send = RestSend(MockAnsibleModule()) - instance.rest_send.unit_test = True - + instance.fabric_details = FabricDetailsByName() + instance.fabric_details.rest_send = rest_send + instance.fabric_details.results = Results() + instance.fabric_summary = FabricSummary() + instance.fabric_summary.rest_send = rest_send + instance.rest_send = rest_send instance.results = Results() - - monkeypatch.setattr(PATCH_DCNM_SEND, mock_dcnm_send) - with does_not_raise(): instance.commit() assert isinstance(instance.results.diff, list) @@ -355,7 +371,7 @@ def mock_dcnm_send(*args, **kwargs): assert instance.results.diff[0].get("sequence_number", None) == 1 assert instance.results.diff[0].get("FABRIC_NAME", None) == "f1" - assert instance.results.metadata[0].get("action", None) == "delete" + assert instance.results.metadata[0].get("action", None) == "fabric_delete" assert instance.results.metadata[0].get("check_mode", None) is False assert instance.results.metadata[0].get("sequence_number", None) == 1 assert instance.results.metadata[0].get("state", None) == "deleted" @@ -377,48 +393,51 @@ def mock_dcnm_send(*args, **kwargs): def test_fabric_delete_00042(monkeypatch, fabric_delete) -> None: """ - Classes and Methods - - FabricCommon() - - __init__() - - payloads setter - - FabricDetails() - - __init__() - - refresh_super() - - FabricDetailsByName() - - __init__() - - refresh() - - FabricDelete - - __init__() - - commit() - - Summary - - Verify FabricDelete().commit() re-raises ``ValueError`` when - ``EpFabricDelete()._send_requests() re-raises ``ValueError`` when - ``EpFabricDelete()._send_request() re-raises ``ValueError`` when - ``FabricDelete()._set_fabric_delete_endpoint()`` raises ``ValueError``. - - The user attempts to delete a fabric and the fabric exists on the - controller, and the fabric is empty, but _set_fabric_delete_endpoint() - re-raises ``ValueError``. - - Code Flow - - FabricDelete.commit() calls FabricDelete()._validate_commit_parameters() - which succeeds since all required parameters are set. - - FabricDelete.commit() calls FabricDelete()._get_fabrics_to_delete() - - FabricDelete()._get_fabrics_to_delete() calls - FabricDetails().refresh() which returns a dict with keys - DATA == [{f1 fabric data dict}], RETURN_CODE == 200 - - FabricDelete()._get_fabrics_to_delete() calls - FabricDelete()._verify_fabric_can_be_deleted() which returns - successfully (does not raise ``ValueError``) - - FabricDelete()._get_fabrics_to_delete() sets - FabricDelete()._fabrics_to_delete to a list containing fabric f1. - - FabricDelete().commit() calls FabricDelete()._send_requests() - - FabricDelete._send_requests() sets RestSend() parameters - - FabricDelete._send_requests() calls FabricDelete._send_request() for - each fabric in the FabricDelete()._fabrics_to_delete list. - - FabricDelete._send_request() calls FabricDelete._set_fabric_delete_endpoint() - which calls EpFabricDelete().fabric_name setter, which is mocked to raise - ``ValueError``. + ### Classes and Methods + + - FabricCommon() + - __init__() + - payloads setter + - FabricDetails() + - __init__() + - refresh_super() + - FabricDetailsByName() + - __init__() + - refresh() + - FabricDelete + - __init__() + - commit() + + ### Summary + + - Verify FabricDelete().commit() re-raises ``ValueError`` when + ``EpFabricDelete()._send_requests() re-raises ``ValueError`` when + ``EpFabricDelete()._send_request() re-raises ``ValueError`` when + ``FabricDelete()._set_fabric_delete_endpoint()`` raises ``ValueError``. + - The user attempts to delete a fabric and the fabric exists on the + controller, and the fabric is empty, but _set_fabric_delete_endpoint() + re-raises ``ValueError``. + + ### Code Flow + + - FabricDelete.commit() calls FabricDelete()._validate_commit_parameters() + which succeeds since all required parameters are set. + - FabricDelete.commit() calls FabricDelete()._get_fabrics_to_delete() + - FabricDelete()._get_fabrics_to_delete() calls + FabricDetails().refresh() which returns a dict with keys + DATA == [{f1 fabric data dict}], RETURN_CODE == 200 + - FabricDelete()._get_fabrics_to_delete() calls + FabricDelete()._verify_fabric_can_be_deleted() which returns + successfully (does not raise ``ValueError``) + - FabricDelete()._get_fabrics_to_delete() sets + FabricDelete()._fabrics_to_delete to a list containing fabric f1. + - FabricDelete().commit() calls FabricDelete()._send_requests() + - FabricDelete._send_requests() sets RestSend() parameters + - FabricDelete._send_requests() calls FabricDelete._send_request() for + each fabric in the FabricDelete()._fabrics_to_delete list. + - FabricDelete._send_request() calls FabricDelete._set_fabric_delete_endpoint() + which calls EpFabricDelete().fabric_name setter, which is mocked to raise + ``ValueError``. """ method_name = inspect.stack()[0][3] key = f"{method_name}a" @@ -442,37 +461,31 @@ def fabric_name(self, value): msg = "mocked MockEpFabricDelete().fabric_name setter exception." raise ValueError(msg) - PATCH_DCNM_SEND = "ansible_collections.cisco.dcnm.plugins." - PATCH_DCNM_SEND += "module_utils.common.rest_send.dcnm_send" - def responses(): - yield responses_fabric_details_by_name(key) + yield responses_fabric_details_by_name_v2(key) yield responses_fabric_summary(key) - gen = ResponseGenerator(responses()) + gen_responses = ResponseGenerator(responses()) - def mock_dcnm_send(*args, **kwargs): - item = gen.next - return item - - monkeypatch.setattr(PATCH_DCNM_SEND, mock_dcnm_send) + sender = Sender() + sender.ansible_module = MockAnsibleModule() + sender.gen = gen_responses + rest_send = RestSend({"state": "deleted", "check_mode": False}) + rest_send.unit_test = True + rest_send.timeout = 1 + rest_send.response_handler = ResponseHandler() + rest_send.sender = sender with does_not_raise(): instance = fabric_delete monkeypatch.setattr(instance, "ep_fabric_delete", MockEpFabricDelete()) instance.fabric_names = ["f1"] - - instance.fabric_details = FabricDetailsByName(params) - instance.fabric_details.rest_send = RestSend(MockAnsibleModule()) - instance.fabric_details.rest_send.unit_test = True - - instance.fabric_summary = FabricSummary(params) - instance.fabric_summary.rest_send = RestSend(MockAnsibleModule()) - instance.fabric_summary.rest_send.unit_test = True - - instance.rest_send = RestSend(MockAnsibleModule()) - instance.rest_send.unit_test = True - + instance.fabric_details = FabricDetailsByName() + instance.fabric_details.rest_send = rest_send + instance.fabric_details.results = Results() + instance.fabric_summary = FabricSummary() + instance.fabric_summary.rest_send = rest_send + instance.rest_send = rest_send instance.results = Results() match = r"mocked MockEpFabricDelete\(\)\.fabric_name setter exception\." @@ -491,12 +504,12 @@ def mock_dcnm_send(*args, **kwargs): assert instance.results.diff[0].get("sequence_number", None) == 1 assert instance.results.diff[1].get("sequence_number", None) == 2 - assert instance.results.metadata[0].get("action", None) == "delete" + assert instance.results.metadata[0].get("action", None) == "fabric_delete" assert instance.results.metadata[0].get("check_mode", None) is False assert instance.results.metadata[0].get("sequence_number", None) == 1 assert instance.results.metadata[0].get("state", None) == "deleted" - assert instance.results.metadata[1].get("action", None) == "delete" + assert instance.results.metadata[1].get("action", None) == "fabric_delete" assert instance.results.metadata[1].get("check_mode", None) is False assert instance.results.metadata[1].get("sequence_number", None) == 2 assert instance.results.metadata[1].get("state", None) == "deleted" @@ -508,80 +521,75 @@ def mock_dcnm_send(*args, **kwargs): assert instance.results.result[1].get("sequence_number", None) == 2 assert True in instance.results.failed - assert False not in instance.results.failed assert False in instance.results.changed assert True not in instance.results.changed -def test_fabric_delete_00043(monkeypatch, fabric_delete) -> None: +def test_fabric_delete_00043(fabric_delete) -> None: """ - Classes and Methods - - FabricCommon() - - __init__() - - payloads setter - - FabricDetails() - - __init__() - - refresh_super() - - FabricDetailsByName() - - __init__() - - refresh() - - FabricDelete - - __init__() - - commit() - - Summary - - Verify successful fabric delete code path (fabric does not exist). - - The user attempts to delete a fabric and the fabric does not exist - on the controller. - - Code Flow - - FabricDelete.commit() calls FabricDelete()._validate_commit_parameters() - which succeeds since all required parameters are set. - - FabricDelete.commit() calls FabricDelete()._get_fabrics_to_delete() - - FabricDelete()._get_fabrics_to_delete() calls - FabricDetails().refresh() which returns a dict with keys - DATA == [], RETURN_CODE == 200 - - FabricDelete()._get_fabrics_to_delete() sets - FabricDelete()._fabrics_to_delete to an empty list. - - FabricDelete().commit() sets results and calls register_result(). - - FabricDelete().register_result() registers the results of the fabric - delete operation. + ### Classes and Methods + + - FabricCommon() + - __init__() + - payloads setter + - FabricDetails() + - __init__() + - refresh_super() + - FabricDetailsByName() + - __init__() + - refresh() + - FabricDelete + - __init__() + - commit() + + ### Summary + + - Verify successful fabric delete code path (fabric does not exist). + - The user attempts to delete a fabric and the fabric does not exist + on the controller. + + ### Code Flow + + - FabricDelete.commit() calls FabricDelete()._validate_commit_parameters() + which succeeds since all required parameters are set. + - FabricDelete.commit() calls FabricDelete()._get_fabrics_to_delete() + - FabricDelete()._get_fabrics_to_delete() calls + FabricDetails().refresh() which returns a dict with keys + DATA == [], RETURN_CODE == 200 + - FabricDelete()._get_fabrics_to_delete() sets + FabricDelete()._fabrics_to_delete to an empty list. + - FabricDelete().commit() sets results and calls register_result(). + - FabricDelete().register_result() registers the results of the fabric + delete operation. """ method_name = inspect.stack()[0][3] key = f"{method_name}a" - PATCH_DCNM_SEND = "ansible_collections.cisco.dcnm.plugins." - PATCH_DCNM_SEND += "module_utils.common.rest_send.dcnm_send" - def responses(): - yield responses_fabric_details_by_name(key) + yield responses_fabric_details_by_name_v2(key) yield responses_fabric_summary(key) - gen = ResponseGenerator(responses()) + gen_responses = ResponseGenerator(responses()) - def mock_dcnm_send(*args, **kwargs): - item = gen.next - return item + sender = Sender() + sender.ansible_module = MockAnsibleModule() + sender.gen = gen_responses + rest_send = RestSend({"state": "deleted", "check_mode": False}) + rest_send.unit_test = True + rest_send.timeout = 1 + rest_send.response_handler = ResponseHandler() + rest_send.sender = sender with does_not_raise(): instance = fabric_delete instance.fabric_names = ["f1"] - - instance.fabric_details = FabricDetailsByName(params) - instance.fabric_details.rest_send = RestSend(MockAnsibleModule()) - instance.fabric_details.rest_send.unit_test = True - - instance.fabric_summary = FabricSummary(params) - instance.fabric_summary.rest_send = RestSend(MockAnsibleModule()) - instance.fabric_summary.rest_send.unit_test = True - - instance.rest_send = RestSend(MockAnsibleModule()) - instance.rest_send.unit_test = True - + instance.fabric_details = FabricDetailsByName() + instance.fabric_details.rest_send = rest_send + instance.fabric_details.results = Results() + instance.fabric_summary = FabricSummary() + instance.fabric_summary.rest_send = rest_send + instance.rest_send = rest_send instance.results = Results() - - monkeypatch.setattr(PATCH_DCNM_SEND, mock_dcnm_send) - with does_not_raise(): instance.commit() assert isinstance(instance.results.diff, list) @@ -596,7 +604,7 @@ def mock_dcnm_send(*args, **kwargs): assert instance.results.diff[0].get("sequence_number", None) == 1 assert instance.results.diff[0].get("fabric_name", None) is None - assert instance.results.metadata[0].get("action", None) == "delete" + assert instance.results.metadata[0].get("action", None) == "fabric_delete" assert instance.results.metadata[0].get("check_mode", None) is False assert instance.results.metadata[0].get("sequence_number", None) == 1 assert instance.results.metadata[0].get("state", None) == "deleted" @@ -613,89 +621,85 @@ def mock_dcnm_send(*args, **kwargs): assert True not in instance.results.changed -def test_fabric_delete_00044(monkeypatch, fabric_delete) -> None: +def test_fabric_delete_00044(fabric_delete) -> None: """ - Classes and Methods - - FabricCommon() - - __init__() - - payloads setter - - FabricDetails() - - __init__() - - refresh_super() - - FabricDetailsByName() - - __init__() - - refresh() - - FabricDelete - - __init__() - - commit() - - Summary - - Verify unsuccessful fabric delete code path. - - The user attempts to delete a fabric and the fabric exists on the - controller, and the fabric is empty, but the controller - RETURN_CODE is not 200. - - Code Flow - - FabricDelete.commit() calls FabricDelete()._validate_commit_parameters() - which succeeds since all required parameters are set. - - FabricDelete.commit() calls FabricDelete()._get_fabrics_to_delete() - - FabricDelete()._get_fabrics_to_delete() calls - FabricDetails().refresh() which returns a dict with keys - DATA == [{f1 fabric data dict}], RETURN_CODE == 200 - - FabricDelete()._get_fabrics_to_delete() calls - FabricDelete()._verify_fabric_can_be_deleted() which returns - successfully (does not raise ``ValueError``) - - FabricDelete()._get_fabrics_to_delete() sets - FabricDelete()._fabrics_to_delete to a list containing fabric f1. - - FabricDelete().commit() calls FabricDelete()._send_requests() - - FabricDelete._send_requests() sets RestSend() parameters - - FabricDelete._send_requests() calls FabricDelete._send_request() for - each fabric in the FabricDelete()._fabrics_to_delete list. - - FabricDelete._send_request() calls FabricDelete._set_fabric_delete_endpoint() - which returns the request endpoint information (path, verb) for fabric f1. - - FabricDelete._send_request() sets RestSend().path and RestSend().verb and - calls RestSend().commit(), which sends the request. - - The response includes a RETURN_CODE != 200 - - FabricDelete()._send_request() calls FabricDelete().register_result() - - FabricDelete().register_result() sets the results for the fabric - delete operation. + ### Classes and Methods + + - FabricCommon() + - __init__() + - payloads setter + - FabricDetails() + - __init__() + - refresh_super() + - FabricDetailsByName() + - __init__() + - refresh() + - FabricDelete + - __init__() + - commit() + + ### Summary + + - Verify unsuccessful fabric delete code path. RETURN_CODE == 500. + - The user attempts to delete a fabric and the fabric exists on the + controller, and the fabric is empty, but the controller + RETURN_CODE is not 200. + + ### Code Flow + + - FabricDelete.commit() calls FabricDelete()._validate_commit_parameters() + which succeeds since all required parameters are set. + - FabricDelete.commit() calls FabricDelete()._get_fabrics_to_delete() + - FabricDelete()._get_fabrics_to_delete() calls + FabricDetails().refresh() which returns a dict with keys + DATA == [{f1 fabric data dict}], RETURN_CODE == 200 + - FabricDelete()._get_fabrics_to_delete() calls + FabricDelete()._verify_fabric_can_be_deleted() which returns + successfully (does not raise ``ValueError``) + - FabricDelete()._get_fabrics_to_delete() sets + FabricDelete()._fabrics_to_delete to a list containing fabric f1. + - FabricDelete().commit() calls FabricDelete()._send_requests() + - FabricDelete._send_requests() sets RestSend() parameters + - FabricDelete._send_requests() calls FabricDelete._send_request() for + each fabric in the FabricDelete()._fabrics_to_delete list. + - FabricDelete._send_request() calls FabricDelete._set_fabric_delete_endpoint() + which returns the request endpoint information (path, verb) for fabric f1. + - FabricDelete._send_request() sets RestSend().path and RestSend().verb and + calls RestSend().commit(), which sends the request. + - The response includes a RETURN_CODE != 200 + - FabricDelete()._send_request() calls FabricDelete().register_result() + - FabricDelete().register_result() sets the results for the fabric + delete operation. """ method_name = inspect.stack()[0][3] key = f"{method_name}a" - PATCH_DCNM_SEND = "ansible_collections.cisco.dcnm.plugins." - PATCH_DCNM_SEND += "module_utils.common.rest_send.dcnm_send" - def responses(): - yield responses_fabric_details_by_name(key) + yield responses_fabric_details_by_name_v2(key) yield responses_fabric_summary(key) yield responses_fabric_delete(key) - gen = ResponseGenerator(responses()) + gen_responses = ResponseGenerator(responses()) - def mock_dcnm_send(*args, **kwargs): - item = gen.next - return item + sender = Sender() + sender.ansible_module = MockAnsibleModule() + sender.gen = gen_responses + rest_send = RestSend({"state": "deleted", "check_mode": False}) + rest_send.unit_test = True + rest_send.timeout = 1 + rest_send.response_handler = ResponseHandler() + rest_send.sender = sender with does_not_raise(): instance = fabric_delete instance.fabric_names = ["f1"] - - instance.fabric_details = FabricDetailsByName(params) - instance.fabric_details.rest_send = RestSend(MockAnsibleModule()) - instance.fabric_details.rest_send.unit_test = True - - instance.fabric_summary = FabricSummary(params) - instance.fabric_summary.rest_send = RestSend(MockAnsibleModule()) - instance.fabric_summary.rest_send.unit_test = True - - instance.rest_send = RestSend(MockAnsibleModule()) - instance.rest_send.unit_test = True - + instance.fabric_details = FabricDetailsByName() + instance.fabric_details.rest_send = rest_send + instance.fabric_details.results = Results() + instance.fabric_summary = FabricSummary() + instance.fabric_summary.rest_send = rest_send + instance.rest_send = rest_send instance.results = Results() - - monkeypatch.setattr(PATCH_DCNM_SEND, mock_dcnm_send) - with does_not_raise(): instance.commit() assert isinstance(instance.results.diff, list) @@ -709,7 +713,7 @@ def mock_dcnm_send(*args, **kwargs): assert instance.results.diff[0].get("sequence_number", None) == 1 - assert instance.results.metadata[0].get("action", None) == "delete" + assert instance.results.metadata[0].get("action", None) == "fabric_delete" assert instance.results.metadata[0].get("check_mode", None) is False assert instance.results.metadata[0].get("sequence_number", None) == 1 assert instance.results.metadata[0].get("state", None) == "deleted" @@ -731,82 +735,79 @@ def mock_dcnm_send(*args, **kwargs): def test_fabric_delete_00050(monkeypatch, fabric_delete) -> None: """ - Classes and Methods - - FabricCommon() - - __init__() - - payloads setter - - FabricDetails() - - __init__() - - refresh_super() - - FabricDetailsByName() - - __init__() - - refresh() - - FabricDelete - - __init__() - - commit() - - _verify_fabric_can_be_deleted() - - Summary - - Verify unsuccessful fabric delete code path. - - FabricDelete()._verify_fabric_can_be_deleted() raises ``ValueError`` - because fabric is not empty. - - The user attempts to delete a fabric and the fabric exists on the - controller, and the fabric is not empty. - - Code Flow - - FabricDelete.commit() calls FabricDelete()._validate_commit_parameters() - which succeeds since all required parameters are set. - - FabricDelete.commit() calls FabricDelete()._get_fabrics_to_delete() - - FabricDelete()._get_fabrics_to_delete() calls - FabricDetails().refresh() which returns a dict with keys - DATA == [{f1 fabric data dict}], RETURN_CODE == 200 - - FabricDelete()._get_fabrics_to_delete() calls - FabricDelete()._verify_fabric_can_be_deleted() raises ``ValueError`` - since the fabric is not empty. - - FabricDelete()._get_fabrics_to_delete() re-raises ``ValueError`` - - FabricDelete().commit() catches the ``ValueError``, sets - the (failed) results for the fabric delete operation, and calls - self.register_result(None) - - FabricDelete().register_result() sets the final result for the - fabric delete operation and returns. - - FabricDelete().commit() re-raises the ``ValueError`` which is caught - by the main Task(), in real life, but caught by the test here. + ### Classes and Methods + + - FabricCommon() + - __init__() + - payloads setter + - FabricDetails() + - __init__() + - refresh_super() + - FabricDetailsByName() + - __init__() + - refresh() + - FabricDelete + - __init__() + - commit() + - _verify_fabric_can_be_deleted() + + ### Summary + + - Verify unsuccessful fabric delete code path. + - FabricDelete()._verify_fabric_can_be_deleted() raises ``ValueError`` + because fabric is not empty. + - The user attempts to delete a fabric and the fabric exists on the + controller, and the fabric is not empty. + + ### Code Flow + + - FabricDelete.commit() calls FabricDelete()._validate_commit_parameters() + which succeeds since all required parameters are set. + - FabricDelete.commit() calls FabricDelete()._get_fabrics_to_delete() + - FabricDelete()._get_fabrics_to_delete() calls + FabricDetails().refresh() which returns a dict with keys + DATA == [{f1 fabric data dict}], RETURN_CODE == 200 + - FabricDelete()._get_fabrics_to_delete() calls + FabricDelete()._verify_fabric_can_be_deleted() raises ``ValueError`` + since the fabric is not empty. + - FabricDelete()._get_fabrics_to_delete() re-raises ``ValueError`` + - FabricDelete().commit() catches the ``ValueError``, sets + the (failed) results for the fabric delete operation, and calls + self.register_result(None) + - FabricDelete().register_result() sets the final result for the + fabric delete operation and returns. + - FabricDelete().commit() re-raises the ``ValueError`` which is caught + by the main Task(), in real life, but caught by the test here. """ method_name = inspect.stack()[0][3] key = f"{method_name}a" - PATCH_DCNM_SEND = "ansible_collections.cisco.dcnm.plugins." - PATCH_DCNM_SEND += "module_utils.common.rest_send.dcnm_send" - def responses(): - yield responses_fabric_details_by_name(key) + yield responses_fabric_details_by_name_v2(key) yield responses_fabric_summary(key) - gen = ResponseGenerator(responses()) + gen_responses = ResponseGenerator(responses()) - def mock_dcnm_send(*args, **kwargs): - item = gen.next - return item + sender = Sender() + sender.ansible_module = MockAnsibleModule() + sender.gen = gen_responses + rest_send = RestSend({"state": "deleted", "check_mode": False}) + rest_send.unit_test = True + rest_send.timeout = 1 + rest_send.response_handler = ResponseHandler() + rest_send.sender = sender with does_not_raise(): instance = fabric_delete instance.fabric_names = ["f1"] - - instance.fabric_details = FabricDetailsByName(params) - instance.fabric_details.rest_send = RestSend(MockAnsibleModule()) - instance.fabric_details.rest_send.unit_test = True - - instance.fabric_summary = FabricSummary(params) - instance.fabric_summary.rest_send = RestSend(MockAnsibleModule()) - instance.fabric_summary.rest_send.unit_test = True - - instance.rest_send = RestSend(MockAnsibleModule()) - instance.rest_send.unit_test = True - + instance.fabric_details = FabricDetailsByName() + instance.fabric_details.rest_send = rest_send + instance.fabric_details.results = Results() + instance.fabric_summary = FabricSummary() + instance.fabric_summary.rest_send = rest_send + instance.rest_send = rest_send instance.results = Results() - monkeypatch.setattr(PATCH_DCNM_SEND, mock_dcnm_send) - match = r"FabricDelete\._verify_fabric_can_be_deleted: " match += "Fabric f1 cannot be deleted since it is not empty. " match += "Remove all devices from the fabric and try again." @@ -833,7 +834,7 @@ def mock_dcnm_send(*args, **kwargs): assert instance.results.result[0].get("changed", None) is False assert instance.results.result[0].get("success", None) is False - assert instance.results.metadata[0].get("action", None) == "delete" + assert instance.results.metadata[0].get("action", None) == "fabric_delete" assert instance.results.metadata[0].get("check_mode", None) is False assert instance.results.metadata[0].get("sequence_number", None) == 1 assert instance.results.metadata[0].get("state", None) == "deleted" @@ -843,83 +844,80 @@ def mock_dcnm_send(*args, **kwargs): def test_fabric_delete_00051(monkeypatch, fabric_delete) -> None: """ - Classes and Methods - - FabricCommon() - - __init__() - - payloads setter - - FabricDetails() - - __init__() - - refresh_super() - - FabricDetailsByName() - - __init__() - - refresh() - - FabricDelete - - __init__() - - commit() - - _verify_fabric_can_be_deleted() - - Summary - - Verify unsuccessful fabric delete code path. - - FabricDelete()._verify_fabric_delete() re-raises ``ValueError`` - because FabricSummary().refresh() raises ``ControllerResponseError``. - - Code Flow - - FabricDelete.commit() calls FabricDelete()._validate_commit_parameters() - which succeeds since all required parameters are set. - - FabricDelete.commit() calls FabricDelete()._get_fabrics_to_delete() - - FabricDelete()._get_fabrics_to_delete() calls - FabricDetails().refresh() which returns a dict with keys - DATA == [{f1 fabric data dict}], RETURN_CODE == 200 - - FabricDelete()._get_fabrics_to_delete() calls - FabricDelete()._verify_fabric_can_be_deleted() which calls - FabricSummary().refresh() which raises ``ControllerResponseError`` - due to a 404 RETURN_CODE. - - FabricDelete()._verify_fabric_can_be_deleted() re-raises the - ``ControllerResponseError`` and a ``ValueError``. - - FabricDelete()._get_fabrics_to_delete() re-raises the ``ValueError``. - - FabricDelete().commit() catches the ``ValueError``, sets - the (failed) results for the fabric delete operation, and calls - self.register_result(None) - - FabricDelete().register_result() sets the final result for the - fabric delete operation and returns. - - FabricDelete().commit() re-raises the ``ValueError`` which is caught - by the main Task() in real life, but caught by the test here. + ### Classes and Methods + + - FabricCommon() + - __init__() + - payloads setter + - FabricDetails() + - __init__() + - refresh_super() + - FabricDetailsByName() + - __init__() + - refresh() + - FabricDelete + - __init__() + - commit() + - _verify_fabric_can_be_deleted() + + ### Summary + + - Verify unsuccessful fabric delete code path. + - FabricDelete()._verify_fabric_delete() re-raises ``ValueError`` + because FabricSummary().refresh() raises ``ControllerResponseError``. + + ### Code Flow + + - FabricDelete.commit() calls FabricDelete()._validate_commit_parameters() + which succeeds since all required parameters are set. + - FabricDelete.commit() calls FabricDelete()._get_fabrics_to_delete() + - FabricDelete()._get_fabrics_to_delete() calls + FabricDetails().refresh() which returns a dict with keys + DATA == [{f1 fabric data dict}], RETURN_CODE == 200 + - FabricDelete()._get_fabrics_to_delete() calls + FabricDelete()._verify_fabric_can_be_deleted() which calls + FabricSummary().refresh() which raises ``ControllerResponseError`` + due to a 404 RETURN_CODE. + - FabricDelete()._verify_fabric_can_be_deleted() re-raises the + ``ControllerResponseError`` and a ``ValueError``. + - FabricDelete()._get_fabrics_to_delete() re-raises the ``ValueError``. + - FabricDelete().commit() catches the ``ValueError``, sets + the (failed) results for the fabric delete operation, and calls + self.register_result(None) + - FabricDelete().register_result() sets the final result for the + fabric delete operation and returns. + - FabricDelete().commit() re-raises the ``ValueError`` which is caught + by the main Task() in real life, but caught by the test here. """ method_name = inspect.stack()[0][3] key = f"{method_name}a" - PATCH_DCNM_SEND = "ansible_collections.cisco.dcnm.plugins." - PATCH_DCNM_SEND += "module_utils.common.rest_send.dcnm_send" - def responses(): - yield responses_fabric_details_by_name(key) + yield responses_fabric_details_by_name_v2(key) yield responses_fabric_summary(key) - gen = ResponseGenerator(responses()) + gen_responses = ResponseGenerator(responses()) - def mock_dcnm_send(*args, **kwargs): - item = gen.next - return item + sender = Sender() + sender.ansible_module = MockAnsibleModule() + sender.gen = gen_responses + rest_send = RestSend({"state": "deleted", "check_mode": False}) + rest_send.unit_test = True + rest_send.timeout = 1 + rest_send.response_handler = ResponseHandler() + rest_send.sender = sender with does_not_raise(): instance = fabric_delete instance.fabric_names = ["f1"] - - instance.fabric_details = FabricDetailsByName(params) - instance.fabric_details.rest_send = RestSend(MockAnsibleModule()) - instance.fabric_details.rest_send.unit_test = True - - instance.fabric_summary = FabricSummary(params) - instance.fabric_summary.rest_send = RestSend(MockAnsibleModule()) - instance.fabric_summary.rest_send.unit_test = True - - instance.rest_send = RestSend(MockAnsibleModule()) - instance.rest_send.unit_test = True - + instance.fabric_details = FabricDetailsByName() + instance.fabric_details.rest_send = rest_send + instance.fabric_details.results = Results() + instance.fabric_summary = FabricSummary() + instance.fabric_summary.rest_send = rest_send + instance.rest_send = rest_send instance.results = Results() - monkeypatch.setattr(PATCH_DCNM_SEND, mock_dcnm_send) - match = r"FabricSummary\._verify_controller_response:\s+" match += r"Failed to retrieve fabric_summary for fabric_name f1.\s+" match += r"RETURN_CODE: 404.\s+" @@ -947,7 +945,7 @@ def mock_dcnm_send(*args, **kwargs): assert instance.results.result[0].get("changed", None) is False assert instance.results.result[0].get("success", None) is False - assert instance.results.metadata[0].get("action", None) == "delete" + assert instance.results.metadata[0].get("action", None) == "fabric_delete" assert instance.results.metadata[0].get("check_mode", None) is False assert instance.results.metadata[0].get("sequence_number", None) == 1 assert instance.results.metadata[0].get("state", None) == "deleted" @@ -957,17 +955,19 @@ def mock_dcnm_send(*args, **kwargs): def test_fabric_delete_00060(fabric_delete) -> None: """ - Classes and Methods - - FabricCommon - - __init__() - - FabricDelete - - __init__() - - commit() - - _validate_commit_parameters() + ### Classes and Methods + + - FabricCommon + - __init__() + - FabricDelete + - __init__() + - commit() + - _validate_commit_parameters() - Summary - - Verify that ``ValueError`` is raised because fabric_names - is not a list. + ### Summary + + - Verify that ``ValueError`` is raised because fabric_names + is not a list. """ with does_not_raise(): instance = fabric_delete @@ -980,21 +980,22 @@ def test_fabric_delete_00060(fabric_delete) -> None: def test_fabric_delete_00061(fabric_delete) -> None: """ - Classes and Methods - - FabricCommon - - __init__() - - FabricDelete - - __init__() - - commit() - - _validate_commit_parameters() + ### Classes and Methods + + - FabricCommon + - __init__() + - FabricDelete + - __init__() + - commit() + - _validate_commit_parameters() + + ### Summary - Summary - - Verify that ``ValueError`` is raised because fabric_names is an - empty list. + - Verify that ``ValueError`` is raised because fabric_names is an + empty list. """ with does_not_raise(): instance = fabric_delete - # instance.rest_send = RestSend(MockAnsibleModule()) match = r"FabricDelete\.fabric_names: " match += r"fabric_names must be a list of at least one string. got \[\]\." @@ -1004,17 +1005,19 @@ def test_fabric_delete_00061(fabric_delete) -> None: def test_fabric_delete_00062(fabric_delete) -> None: """ - Classes and Methods - - FabricCommon - - __init__() - - FabricDelete - - __init__() - - commit() - - _validate_commit_parameters() + ### Classes and Methods + + - FabricCommon + - __init__() + - FabricDelete + - __init__() + - commit() + - _validate_commit_parameters() + + ### Summary - Summary - - Verify that ``ValueError`` is raised because fabric_names is a - list containing non-string elements. + - Verify that ``ValueError`` is raised because fabric_names is a + list containing non-string elements. """ with does_not_raise(): instance = fabric_delete diff --git a/tests/unit/modules/dcnm/dcnm_fabric/test_fabric_details.py b/tests/unit/modules/dcnm/dcnm_fabric/test_fabric_details.py deleted file mode 100644 index 86d46e847..000000000 --- a/tests/unit/modules/dcnm/dcnm_fabric/test_fabric_details.py +++ /dev/null @@ -1,348 +0,0 @@ -# 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 inspect - -import pytest -from ansible_collections.cisco.dcnm.plugins.module_utils.common.api.v1.lan_fabric.rest.control.fabrics.fabrics import \ - EpFabrics -from ansible_collections.cisco.dcnm.plugins.module_utils.common.conversion import \ - ConversionUtils -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.tests.unit.module_utils.common.common_utils import \ - ResponseGenerator -from ansible_collections.cisco.dcnm.tests.unit.modules.dcnm.dcnm_fabric.utils import ( - MockAnsibleModule, does_not_raise, fabric_details_fixture, - responses_fabric_details) - - -def test_fabric_details_00010(fabric_details) -> None: - """ - Classes and Methods - - FabricCommon - - __init__() - - FabricDetails - - __init__() - - Test - - Class attributes are initialized to expected values - - Exception is not raised - """ - with does_not_raise(): - instance = fabric_details - assert instance.class_name == "FabricDetails" - assert instance.data == {} - assert isinstance(instance.ep_fabrics, EpFabrics) - assert isinstance(instance.results, Results) - assert isinstance(instance.conversion, ConversionUtils) - - -def test_fabric_details_00030(monkeypatch, fabric_details) -> None: - """ - Classes and Methods - - FabricCommon() - - __init__() - - FabricDetails() - - __init__() - - refresh_super() - - Summary - - Verify refresh_super() behavior when: - - RETURN_CODE is 200. - - DATA is an empty list, indicating no fabrics - exist on the controller. - - Code Flow - Setup - - FabricDetails() is instantiated - - FabricDetails().RestSend() is instantiated - - FabricDetails().Results() is instantiated - - FabricDetails().refresh_super() is called - - responses_FabricDetails contains a dict with: - - RETURN_CODE == 200 - - DATA == [] - - Code Flow - Test - - FabricDetails().refresh_super() is called - - Expected Result - - Exception is not raised - - Results() are updated - """ - method_name = inspect.stack()[0][3] - key = f"{method_name}a" - - PATCH_DCNM_SEND = "ansible_collections.cisco.dcnm.plugins." - PATCH_DCNM_SEND += "module_utils.common.rest_send.dcnm_send" - - def responses(): - yield responses_fabric_details(key) - - gen = ResponseGenerator(responses()) - - def mock_dcnm_send(*args, **kwargs): - item = gen.next - return item - - with does_not_raise(): - instance = fabric_details - instance.rest_send = RestSend(MockAnsibleModule()) - instance.rest_send.unit_test = True - instance.results = Results() - - monkeypatch.setattr(PATCH_DCNM_SEND, mock_dcnm_send) - - with does_not_raise(): - instance.refresh_super() - - assert isinstance(instance.results.diff, list) - assert isinstance(instance.results.result, list) - assert isinstance(instance.results.response, list) - - assert len(instance.results.diff) == 0 - assert len(instance.results.result) == 1 - assert len(instance.results.response) == 1 - - assert instance.results.response[0].get("RETURN_CODE", None) == 200 - assert instance.results.result[0].get("found", None) is True - assert instance.results.result[0].get("success", None) is True - - 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_fabric_details_00031(monkeypatch, fabric_details) -> None: - """ - Classes and Methods - - FabricCommon() - - __init__() - - FabricDetails() - - __init__() - - refresh_super() - - Summary - - Verify refresh_super() behavior when: - - RETURN_CODE is 200. - - DATA is missing (negative test) - - Code Flow - Setup - - FabricDetails() is instantiated - - FabricDetails().RestSend() is instantiated - - FabricDetails().Results() is instantiated - - FabricDetails().refresh_super() is called - - responses_FabricDetails contains a dict with: - - RETURN_CODE == 200 - - DATA is missing - Code Flow - Test - - FabricDetails().refresh_super() is called - - Expected Result - - Exception is not raised - - Results() are updated - """ - method_name = inspect.stack()[0][3] - key = f"{method_name}a" - - PATCH_DCNM_SEND = "ansible_collections.cisco.dcnm.plugins." - PATCH_DCNM_SEND += "module_utils.common.rest_send.dcnm_send" - - def responses(): - yield responses_fabric_details(key) - - gen = ResponseGenerator(responses()) - - def mock_dcnm_send(*args, **kwargs): - item = gen.next - return item - - with does_not_raise(): - instance = fabric_details - instance.rest_send = RestSend(MockAnsibleModule()) - instance.rest_send.unit_test = True - instance.results = Results() - - monkeypatch.setattr(PATCH_DCNM_SEND, mock_dcnm_send) - - with does_not_raise(): - instance.refresh_super() - - assert isinstance(instance.results.diff, list) - assert isinstance(instance.results.result, list) - assert isinstance(instance.results.response, list) - - assert len(instance.results.diff) == 0 - assert len(instance.results.result) == 1 - assert len(instance.results.response) == 1 - - assert instance.results.response[0].get("RETURN_CODE", None) == 200 - assert instance.results.result[0].get("found", None) is True - assert instance.results.result[0].get("success", None) is True - - 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_fabric_details_00032(monkeypatch, fabric_details) -> None: - """ - Classes and Methods - - FabricCommon() - - __init__() - - FabricDetails() - - __init__() - - refresh_super() - - Summary - - Verify refresh_super() behavior when: - - RETURN_CODE is 200. - - Controller response contains one fabric (f1). - - Code Flow - Setup - - FabricDetails() is instantiated - - FabricDetails().RestSend() is instantiated - - FabricDetails().Results() is instantiated - - FabricDetails().refresh_super() is called - - responses_FabricDetails contains a dict with: - - RETURN_CODE == 200 - - DATA == [] - - Code Flow - Test - - FabricDetails().refresh_super() is called - - Expected Result - - Exception is not raised - - instance.all_data returns expected fabric data - - Results() are updated - """ - method_name = inspect.stack()[0][3] - key = f"{method_name}a" - - PATCH_DCNM_SEND = "ansible_collections.cisco.dcnm.plugins." - PATCH_DCNM_SEND += "module_utils.common.rest_send.dcnm_send" - - def responses(): - yield responses_fabric_details(key) - - gen = ResponseGenerator(responses()) - - def mock_dcnm_send(*args, **kwargs): - item = gen.next - return item - - with does_not_raise(): - instance = fabric_details - instance.rest_send = RestSend(MockAnsibleModule()) - instance.rest_send.unit_test = True - instance.results = Results() - - monkeypatch.setattr(PATCH_DCNM_SEND, mock_dcnm_send) - - with does_not_raise(): - instance.refresh_super() - - assert isinstance(instance.results.diff, list) - assert isinstance(instance.results.result, list) - assert isinstance(instance.results.response, list) - - assert len(instance.results.diff) == 0 - assert len(instance.results.result) == 1 - assert len(instance.results.response) == 1 - - assert instance.results.response[0].get("RETURN_CODE", None) == 200 - assert instance.results.result[0].get("found", None) is True - assert instance.results.result[0].get("success", None) is True - - 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 - - assert instance.all_data.get("f1", {}).get("asn", None) == "65001" - assert instance.all_data.get("f1", {}).get("nvPairs", {}).get("FABRIC_NAME") == "f1" - - -def test_fabric_details_00040(fabric_details) -> None: - """ - Classes and Methods - - FabricCommon() - - __init__() - - FabricDetails() - - __init__() - - _get() - - Summary - - Verify FabricDetails()._get() returns None since it's implemented - only in subclasses - """ - with does_not_raise(): - instance = fabric_details - assert instance._get("foo") is None - - -def test_fabric_details_00050(fabric_details) -> None: - """ - Classes and Methods - - FabricCommon() - - __init__() - - FabricDetails() - - __init__() - - _get_nv_pair() - - Summary - - Verify FabricDetails()._get_nv_pair() returns None since it's implemented - only in subclasses - """ - with does_not_raise(): - instance = fabric_details - assert instance._get_nv_pair("foo") is None - - -def test_fabric_details_00060(fabric_details) -> None: - """ - Classes and Methods - - FabricCommon() - - __init__() - - FabricDetails() - - __init__() - - all_data() - - Summary - - Verify FabricDetails().all_data() returns FabricDetails().data - """ - with does_not_raise(): - instance = fabric_details - instance.data = {"foo": "bar"} - assert instance.all_data == {"foo": "bar"} diff --git a/tests/unit/modules/dcnm/dcnm_fabric/test_fabric_details_by_name.py b/tests/unit/modules/dcnm/dcnm_fabric/test_fabric_details_by_name.py deleted file mode 100644 index 93f642f21..000000000 --- a/tests/unit/modules/dcnm/dcnm_fabric/test_fabric_details_by_name.py +++ /dev/null @@ -1,875 +0,0 @@ -# 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 inspect - -import pytest -from ansible_collections.cisco.dcnm.plugins.module_utils.common.api.v1.lan_fabric.rest.control.fabrics.fabrics import \ - EpFabrics -from ansible_collections.cisco.dcnm.plugins.module_utils.common.conversion import \ - ConversionUtils -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.tests.unit.module_utils.common.common_utils import \ - ResponseGenerator -from ansible_collections.cisco.dcnm.tests.unit.modules.dcnm.dcnm_fabric.utils import ( - MockAnsibleModule, does_not_raise, fabric_details_by_name_fixture, - responses_fabric_details_by_name) - - -def test_fabric_details_by_name_00010(fabric_details_by_name) -> None: - """ - Classes and Methods - - FabricCommon - - __init__() - - FabricDetails - - __init__() - - FabricDetailsByName - - __init__() - - Test - - Class attributes are initialized to expected values - - Exception is not raised - """ - with does_not_raise(): - instance = fabric_details_by_name - assert instance.class_name == "FabricDetailsByName" - assert instance.data == {} - assert instance.data_subclass == {} - assert instance._properties["filter"] is None - assert isinstance(instance.ep_fabrics, EpFabrics) - assert isinstance(instance.results, Results) - assert isinstance(instance.conversion, ConversionUtils) - - -def test_fabric_details_by_name_00030(monkeypatch, fabric_details_by_name) -> None: - """ - Classes and Methods - - FabricCommon() - - __init__() - - FabricDetails() - - __init__() - - refresh_super() - - FabricDetailsByName - - __init__() - - refresh() - - Summary - - Verify FabricDetailsByName.refresh() behavior when: - - RETURN_CODE is 200. - - DATA is an empty list, indicating no fabrics - exist on the controller. - - Code Flow - Setup - - FabricDetailsByName() is instantiated - - FabricDetails().RestSend() is instantiated - - FabricDetails().Results() is instantiated - - FabricDetailsByName().refresh() is called - - FabricDetailsByName().refresh() calls FabricDetails().refresh_super() - - FabricDetails().refresh_super() calls RestSend() and updates Results() - - FabricDetailsByName().refresh() updates FabricDetailsByName().data_subclass - with a copy of FabricDetails().data - - responses_FabricDetailsByName contains a dict with: - - RETURN_CODE == 200 - - DATA == [] - - Code Flow - Test - - FabricDetailsByName().refresh() is called - - Expected Result - - Exception is not raised - - Results() are updated - - FabricDetailsByName().data_subclass is updated with FabricDetails().data - """ - method_name = inspect.stack()[0][3] - key = f"{method_name}a" - - PATCH_DCNM_SEND = "ansible_collections.cisco.dcnm.plugins." - PATCH_DCNM_SEND += "module_utils.common.rest_send.dcnm_send" - - def responses(): - yield responses_fabric_details_by_name(key) - - gen = ResponseGenerator(responses()) - - def mock_dcnm_send(*args, **kwargs): - item = gen.next - return item - - with does_not_raise(): - instance = fabric_details_by_name - instance.rest_send = RestSend(MockAnsibleModule()) - instance.rest_send.unit_test = True - instance.results = Results() - - monkeypatch.setattr(PATCH_DCNM_SEND, mock_dcnm_send) - - with does_not_raise(): - instance.refresh() - - assert instance.data_subclass == instance.data - - assert isinstance(instance.results.diff, list) - assert isinstance(instance.results.result, list) - assert isinstance(instance.results.response, list) - - assert len(instance.results.diff) == 0 - assert len(instance.results.result) == 1 - assert len(instance.results.response) == 1 - - assert instance.results.response[0].get("RETURN_CODE", None) == 200 - assert instance.results.result[0].get("found", None) is True - assert instance.results.result[0].get("success", None) is True - - 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_fabric_details_by_name_00031(monkeypatch, fabric_details_by_name) -> None: - """ - Classes and Methods - - FabricCommon() - - __init__() - - FabricDetails() - - __init__() - - refresh_super() - - FabricDetailsByName - - __init__() - - refresh() - - Summary - - Verify FabricDetailsByName.refresh() behavior when: - - RETURN_CODE is 200. - - DATA is missing (negative test) - - Code Flow - Setup - - FabricDetailsByName() is instantiated - - FabricDetails().RestSend() is instantiated - - FabricDetails().Results() is instantiated - - FabricDetailsByName().refresh() is called - - FabricDetailsByName().refresh() calls FabricDetails().refresh_super() - - FabricDetails().refresh_super() calls RestSend() and updates Results() - - FabricDetailsByName().refresh() updates FabricDetailsByName().data_subclass - with a copy of FabricDetails().data - - responses_FabricDetailsByName contains a dict with: - - RETURN_CODE == 200 - - DATA is missing - - Code Flow - Test - - FabricDetailsByName().refresh() is called - - Expected Result - - Exception is not raised - - Results() are updated - - FabricDetailsByName().data_subclass is updated with FabricDetails().data - """ - method_name = inspect.stack()[0][3] - key = f"{method_name}a" - - PATCH_DCNM_SEND = "ansible_collections.cisco.dcnm.plugins." - PATCH_DCNM_SEND += "module_utils.common.rest_send.dcnm_send" - - def responses(): - yield responses_fabric_details_by_name(key) - - gen = ResponseGenerator(responses()) - - def mock_dcnm_send(*args, **kwargs): - item = gen.next - return item - - with does_not_raise(): - instance = fabric_details_by_name - instance.rest_send = RestSend(MockAnsibleModule()) - instance.rest_send.unit_test = True - instance.results = Results() - - monkeypatch.setattr(PATCH_DCNM_SEND, mock_dcnm_send) - - with does_not_raise(): - instance.refresh() - - assert instance.data_subclass == instance.data - - assert isinstance(instance.results.diff, list) - assert isinstance(instance.results.result, list) - assert isinstance(instance.results.response, list) - - assert len(instance.results.diff) == 0 - assert len(instance.results.result) == 1 - assert len(instance.results.response) == 1 - - assert instance.results.response[0].get("RETURN_CODE", None) == 200 - assert instance.results.result[0].get("found", None) is True - assert instance.results.result[0].get("success", None) is True - - 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_fabric_details_by_name_00032(monkeypatch, fabric_details_by_name) -> None: - """ - Classes and Methods - - FabricCommon() - - __init__() - - FabricDetails() - - __init__() - - refresh_super() - - FabricDetailsByName - - __init__() - - refresh() - - Summary - - Verify refresh() behavior when: - - RETURN_CODE is 200. - - Controller response contains one fabric (f1). - - Code Flow - Setup - - FabricDetailsByName() is instantiated - - FabricDetails().RestSend() is instantiated - - FabricDetails().Results() is instantiated - - FabricDetailsByName().refresh() is called - - FabricDetailsByName().refresh() calls FabricDetails().refresh_super() - - FabricDetails().refresh_super() calls RestSend() and updates Results() - - FabricDetailsByName().refresh() updates FabricDetailsByName().data_subclass - with a copy of FabricDetails().data - - responses_FabricDetailsByName contains a dict with: - - RETURN_CODE == 200 - - DATA == [] - - Code Flow - Test - - FabricDetailsByName().refresh() is called - - Expected Result - - Exception is not raised - - Results() are updated - - FabricDetailsByName().data_subclass is updated with FabricDetails().data - """ - method_name = inspect.stack()[0][3] - key = f"{method_name}a" - - PATCH_DCNM_SEND = "ansible_collections.cisco.dcnm.plugins." - PATCH_DCNM_SEND += "module_utils.common.rest_send.dcnm_send" - - def responses(): - yield responses_fabric_details_by_name(key) - - gen = ResponseGenerator(responses()) - - def mock_dcnm_send(*args, **kwargs): - item = gen.next - return item - - with does_not_raise(): - instance = fabric_details_by_name - instance.rest_send = RestSend(MockAnsibleModule()) - instance.rest_send.unit_test = True - instance.results = Results() - - monkeypatch.setattr(PATCH_DCNM_SEND, mock_dcnm_send) - - with does_not_raise(): - instance.refresh() - - assert instance.data_subclass == instance.data - - assert isinstance(instance.results.diff, list) - assert isinstance(instance.results.result, list) - assert isinstance(instance.results.response, list) - - assert len(instance.results.diff) == 0 - assert len(instance.results.result) == 1 - assert len(instance.results.response) == 1 - - assert instance.results.response[0].get("RETURN_CODE", None) == 200 - assert instance.results.result[0].get("found", None) is True - assert instance.results.result[0].get("success", None) is True - - 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 - - assert instance.all_data.get("f1", {}).get("asn", None) == "65001" - assert instance.all_data.get("f1", {}).get("nvPairs", {}).get("FABRIC_NAME") == "f1" - - -def test_fabric_details_by_name_00040(fabric_details_by_name) -> None: - """ - Classes and Methods - - FabricCommon() - - __init__() - - FabricDetails() - - __init__() - - FabricDetailsByName - - __init__() - - _get() - - Summary - - Verify FabricDetails()._get() raises ``ValueError`` when ``filter`` - is not set. - """ - with does_not_raise(): - instance = fabric_details_by_name - - match = r"FabricDetailsByName\._get: " - match += r"set instance\.filter to a fabric name before accessing property" - with pytest.raises(ValueError, match=match): - instance._get("foo") - - -def test_fabric_details_by_name_00041(fabric_details_by_name) -> None: - """ - Classes and Methods - - FabricCommon() - - __init__() - - FabricDetails() - - __init__() - - FabricDetailsByName - - __init__() - - _get() - - Summary - - Verify FabricDetails()._get() raises ``ValueError`` when ``filter`` - does not exist on the controller. - """ - with does_not_raise(): - instance = fabric_details_by_name - instance.data_subclass = {"YourFabric": "bar"} - instance.filter = "MyFabric" - - match = r"FabricDetailsByName\._get: " - match += r"fabric_name MyFabric does not exist on the controller." - with pytest.raises(ValueError, match=match): - instance._get("BGP_AS") - - -def test_fabric_details_by_name_00042(fabric_details_by_name) -> None: - """ - Classes and Methods - - FabricCommon() - - __init__() - - FabricDetails() - - __init__() - - FabricDetailsByName - - __init__() - - _get() - - Summary - - Verify FabricDetails()._get() raises ``ValueError`` when the fabric - specified by ``filter`` exists on the controller, but does not contain - the requested property. - """ - with does_not_raise(): - instance = fabric_details_by_name - instance.data_subclass = {"MyFabric": {"bar": "baz"}} - instance.filter = "MyFabric" - - match = r"FabricDetailsByName\._get: " - match += r"MyFabric unknown property name: foo." - with pytest.raises(ValueError, match=match): - instance._get("foo") - - -def test_fabric_details_by_name_00043(fabric_details_by_name) -> None: - """ - Classes and Methods - - FabricCommon() - - __init__() - - FabricDetails() - - __init__() - - FabricDetailsByName - - __init__() - - _get() - - Summary - - Verify FabricDetails()._get() retrieves the requested property when - the fabric specified by ``filter`` exists on the controller, and it - contains the requested property. - """ - with does_not_raise(): - instance = fabric_details_by_name - instance.data_subclass = {"MyFabric": {"bar": "baz"}} - instance.filter = "MyFabric" - value = instance._get("bar") - assert value == "baz" - - -def test_fabric_details_by_name_00050(fabric_details_by_name) -> None: - """ - Classes and Methods - - FabricCommon() - - __init__() - - FabricDetails() - - __init__() - - FabricDetailsByName - - __init__() - - _get_nv_pair() - - Summary - - Verify FabricDetails()._get_nv_pair() raises ``ValueError`` when ``filter`` - is not set. - """ - with does_not_raise(): - instance = fabric_details_by_name - - match = r"FabricDetailsByName\._get_nv_pair: " - match += r"set instance\.filter to a fabric name before accessing property" - with pytest.raises(ValueError, match=match): - instance._get_nv_pair("foo") - - -def test_fabric_details_by_name_00051(fabric_details_by_name) -> None: - """ - Classes and Methods - - FabricCommon() - - __init__() - - FabricDetails() - - __init__() - - FabricDetailsByName - - __init__() - - _get_nv_pair() - - Summary - - Verify FabricDetails()._get_nv_pair() raises ``ValueError`` when ``filter`` - does not exist on the controller. - """ - with does_not_raise(): - instance = fabric_details_by_name - instance.data_subclass = {"YourFabric": "bar"} - instance.filter = "MyFabric" - - match = r"FabricDetailsByName\._get_nv_pair: " - match += r"fabric_name MyFabric does not exist on the controller." - with pytest.raises(ValueError, match=match): - instance._get_nv_pair("BGP_AS") - - -def test_fabric_details_by_name_00052(fabric_details_by_name) -> None: - """ - Classes and Methods - - FabricCommon() - - __init__() - - FabricDetails() - - __init__() - - FabricDetailsByName - - __init__() - - _get_nv_pair() - - Summary - - Verify FabricDetails()._get_nv_pair() raises ``ValueError`` when the fabric - specified by ``filter`` exists on the controller, but does not contain - the requested property. - """ - with does_not_raise(): - instance = fabric_details_by_name - instance.data_subclass = {"MyFabric": {"nvPairs": {"BGP_AS": "65001"}}} - instance.filter = "MyFabric" - - match = r"FabricDetailsByName\._get_nv_pair: " - match += r"fabric_name MyFabric unknown property name: FOO_NV_PAIR." - with pytest.raises(ValueError, match=match): - instance._get_nv_pair("FOO_NV_PAIR") - - -def test_fabric_details_by_name_00053(fabric_details_by_name) -> None: - """ - Classes and Methods - - FabricCommon() - - __init__() - - FabricDetails() - - __init__() - - FabricDetailsByName - - __init__() - - _get_nv_pair() - - Summary - - Verify FabricDetails()._get_nv_pair() retrieves the requested property when - the fabric specified by ``filter`` exists on the controller, and it - contains the requested property. - """ - with does_not_raise(): - instance = fabric_details_by_name - instance.data_subclass = {"MyFabric": {"nvPairs": {"BGP_AS": "65001"}}} - instance.filter = "MyFabric" - value = instance._get_nv_pair("BGP_AS") - assert value == "65001" - - -def test_fabric_details_by_name_00060(fabric_details_by_name) -> None: - """ - Classes and Methods - - FabricCommon() - - __init__() - - FabricDetails() - - __init__() - - FabricDetailsByName - - __init__() - - _filtered_data getter - - Summary - - Verify FabricDetailsByName().filtered_data raises ``ValueError`` - if FabricDetailsByName().filter is not set. - """ - with does_not_raise(): - instance = fabric_details_by_name - match = r"FabricDetailsByName\.filtered_data: " - match += r"FabricDetailsByName\.filter must be set before calling " - match += r"FabricDetailsByName\.filtered_data" - with pytest.raises(ValueError, match=match): - instance.filtered_data # pylint: disable=pointless-statement - - -def test_fabric_details_by_name_00061(fabric_details_by_name) -> None: - """ - Classes and Methods - - FabricCommon() - - __init__() - - FabricDetails() - - __init__() - - FabricDetailsByName - - __init__() - - _filtered_data getter - - Summary - - Verify FabricDetailsByName().filtered_data returns the expected - data when FabricDetailsByName().filter is set. - - Verify ``ValueError`` is not raised. - """ - with does_not_raise(): - instance = fabric_details_by_name - instance.data_subclass = {"MyFabric": {"nvPairs": {"BGP_AS": "65001"}}} - instance.filter = "MyFabric" - value = instance.filtered_data - assert value == {"nvPairs": {"BGP_AS": "65001"}} - - -def test_fabric_details_by_name_00070(fabric_details_by_name) -> None: - """ - Classes and Methods - - FabricCommon() - - __init__() - - FabricDetails() - - __init__() - - FabricDetailsByName - - __init__() - - asn getter - - Summary - - Verify FabricDetailsByName().asn returns None - if encountering an error retrieving the asn property - - Verify exception is not raised. - """ - with does_not_raise(): - instance = fabric_details_by_name - instance.filter = "MyFabric" - instance.data_subclass = {"MyFabric": {"nvPairs": {"BGP_AS": "65001"}}} - value = instance.asn - assert value is None - - -def test_fabric_details_by_name_00071(fabric_details_by_name) -> None: - """ - Classes and Methods - - FabricCommon() - - __init__() - - FabricDetails() - - __init__() - - FabricDetailsByName - - __init__() - - asn getter - - Summary - - Verify FabricDetailsByName().asn returns the expected - data when FabricDetailsByName().filter is set. - - Verify ``ValueError`` is not raised. - """ - with does_not_raise(): - instance = fabric_details_by_name - instance.data_subclass = {"MyFabric": {"asn": "65001"}} - instance.filter = "MyFabric" - value = instance.asn - assert value == "65001" - - -def test_fabric_details_by_name_00080(fabric_details_by_name) -> None: - """ - Classes and Methods - - FabricCommon() - - __init__() - - FabricDetails() - - __init__() - - FabricDetailsByName - - __init__() - - enable_pbr getter - - Summary - - Verify FabricDetailsByName().enable_pbr returns None - if encountering an error retrieving the nvPairs.ENABLE_PBR - property. - - Verify exception is not raised. - """ - with does_not_raise(): - instance = fabric_details_by_name - instance.filter = "MyFabric" - instance.data_subclass = {"MyFabric": {"nvPairs": {"BGP_AS": "65001"}}} - value = instance.enable_pbr - assert value is None - - -def test_fabric_details_by_name_00081(fabric_details_by_name) -> None: - """ - Classes and Methods - - FabricCommon() - - __init__() - - FabricDetails() - - __init__() - - FabricDetailsByName - - __init__() - - enable_pbr getter - - Summary - - Verify FabricDetailsByName().enable_pbr returns the expected - data when FabricDetailsByName().filter is set. - - Verify ``ValueError`` is not raised. - """ - with does_not_raise(): - instance = fabric_details_by_name - instance.data_subclass = {"MyFabric": {"nvPairs": {"ENABLE_PBR": "true"}}} - instance.filter = "MyFabric" - value = instance.enable_pbr - assert value is True - - -def test_fabric_details_by_name_00090(fabric_details_by_name) -> None: - """ - Classes and Methods - - FabricCommon() - - __init__() - - FabricDetails() - - __init__() - - FabricDetailsByName - - __init__() - - fabric_id getter - - Summary - - Verify FabricDetailsByName().fabric_id returns None - if encountering an error retrieving the fabric_id - property. - - Verify exception is not raised. - """ - with does_not_raise(): - instance = fabric_details_by_name - instance.filter = "MyFabric" - instance.data_subclass = {"MyFabric": {"nvPairs": {"BGP_AS": "65001"}}} - value = instance.fabric_id - assert value is None - - -def test_fabric_details_by_name_00091(fabric_details_by_name) -> None: - """ - Classes and Methods - - FabricCommon() - - __init__() - - FabricDetails() - - __init__() - - FabricDetailsByName - - __init__() - - fabric_id getter - - Summary - - Verify FabricDetailsByName().fabric_id returns the expected - data when FabricDetailsByName().filter is set. - - Verify ``ValueError`` is not raised. - """ - with does_not_raise(): - instance = fabric_details_by_name - instance.data_subclass = {"MyFabric": {"fabricId": "FABRIC-2"}} - instance.filter = "MyFabric" - value = instance.fabric_id - assert value == "FABRIC-2" - - -def test_fabric_details_by_name_00100(fabric_details_by_name) -> None: - """ - Classes and Methods - - FabricCommon() - - __init__() - - FabricDetails() - - __init__() - - FabricDetailsByName - - __init__() - - template_name getter - - Summary - - Verify FabricDetailsByName().template_name returns None - if encountering an error retrieving the templateName - property. - - Verify exception is not raised. - """ - with does_not_raise(): - instance = fabric_details_by_name - instance.filter = "MyFabric" - instance.data_subclass = {"MyFabric": {"nvPairs": {"BGP_AS": "65001"}}} - value = instance.template_name - assert value is None - - -def test_fabric_details_by_name_00101(fabric_details_by_name) -> None: - """ - Classes and Methods - - FabricCommon() - - __init__() - - FabricDetails() - - __init__() - - FabricDetailsByName - - __init__() - - template_name getter - - Summary - - Verify FabricDetailsByName().replication_mode returns the expected - data when FabricDetailsByName().filter is set. - - Verify ``ValueError`` is not raised. - """ - with does_not_raise(): - instance = fabric_details_by_name - instance.data_subclass = {"MyFabric": {"templateName": "Easy_Fabric"}} - instance.filter = "MyFabric" - value = instance.template_name - assert value == "Easy_Fabric" - - -def test_fabric_details_by_name_00300(fabric_details_by_name) -> None: - """ - Classes and Methods - - FabricCommon() - - __init__() - - FabricDetails() - - __init__() - - FabricDetailsByName - - __init__() - - fabric_type getter - - Summary - - Verify FabricDetailsByName().fabric_type returns None - if encountering an error retrieving the nvPairs.FABRIC_TYPE - property. - - Verify exception is not raised. - """ - with does_not_raise(): - instance = fabric_details_by_name - instance.filter = "MyFabric" - instance.data_subclass = {"MyFabric": {"nvPairs": {"BGP_AS": "65001"}}} - value = instance.fabric_type - assert value is None - - -def test_fabric_details_by_name_00301(fabric_details_by_name) -> None: - """ - Classes and Methods - - FabricCommon() - - __init__() - - FabricDetails() - - __init__() - - FabricDetailsByName - - __init__() - - fabric_type getter - - Summary - - Verify FabricDetailsByName().fabric_type returns the expected - data when FabricDetailsByName().filter is set. - - Verify ``ValueError`` is not raised. - """ - with does_not_raise(): - instance = fabric_details_by_name - instance.data_subclass = { - "MyFabric": {"nvPairs": {"FABRIC_TYPE": "Switch_Fabric"}} - } - instance.filter = "MyFabric" - value = instance.fabric_type - assert value == "Switch_Fabric" - - -def test_fabric_details_by_name_00310(fabric_details_by_name) -> None: - """ - Classes and Methods - - FabricCommon() - - __init__() - - FabricDetails() - - __init__() - - FabricDetailsByName - - __init__() - - replication_mode getter - - Summary - - Verify FabricDetailsByName().replication_mode returns None - if encountering an error retrieving the nvPairs.REPLICATION_MODE - property. - - Verify exception is not raised. - """ - with does_not_raise(): - instance = fabric_details_by_name - instance.filter = "MyFabric" - instance.data_subclass = {"MyFabric": {"nvPairs": {"BGP_AS": "65001"}}} - value = instance.replication_mode - assert value is None - - -def test_fabric_details_by_name_00311(fabric_details_by_name) -> None: - """ - Classes and Methods - - FabricCommon() - - __init__() - - FabricDetails() - - __init__() - - FabricDetailsByName - - __init__() - - replication_mode getter - - Summary - - Verify FabricDetailsByName().replication_mode returns the expected - data when FabricDetailsByName().filter is set. - - Verify ``ValueError`` is not raised. - """ - with does_not_raise(): - instance = fabric_details_by_name - instance.data_subclass = { - "MyFabric": {"nvPairs": {"REPLICATION_MODE": "Ingress"}} - } - instance.filter = "MyFabric" - value = instance.replication_mode - assert value == "Ingress" diff --git a/tests/unit/modules/dcnm/dcnm_fabric/test_fabric_details_by_name_v2.py b/tests/unit/modules/dcnm/dcnm_fabric/test_fabric_details_by_name_v2.py index 70d8b03bf..2a6e7378a 100644 --- a/tests/unit/modules/dcnm/dcnm_fabric/test_fabric_details_by_name_v2.py +++ b/tests/unit/modules/dcnm/dcnm_fabric/test_fabric_details_by_name_v2.py @@ -41,8 +41,6 @@ Results from ansible_collections.cisco.dcnm.plugins.module_utils.common.sender_file import \ Sender -from ansible_collections.cisco.dcnm.plugins.module_utils.fabric.fabric_details_v2 import \ - FabricDetailsByName from ansible_collections.cisco.dcnm.tests.unit.module_utils.common.common_utils import \ ResponseGenerator from ansible_collections.cisco.dcnm.tests.unit.modules.dcnm.dcnm_fabric.utils import ( @@ -52,39 +50,34 @@ PARAMS = {"state": "query", "check_mode": False} -def test_fabric_details_by_name_v2_00000(monkeypatch) -> None: +def test_fabric_details_by_name_v2_00000(fabric_details_by_name_v2) -> None: """ ### Classes and Methods - FabricDetailsByName() - __init__() ### Summary - - Verify that __init__ raises ``ValueError`` if ``super().__init__`` - raises ``ValueError`` + - Verify instance attributes are set correctly. ### Setup - Code - None ### Setup - Data - - params is modified to remove ``check_mode``. + - None ### Trigger - FabricDetailsByName() is instantiated. ### Expected Result - - FabricDetailsByName().__init__() raises ``ValueError`` because - FabricDetails().__init__() raises ``ValueError`` because params - is missing mandatory key ``check_mode``. - - Error message matches expectation. + - Instance attribute values are as expected. + - No expections are raised. """ - match = r"FabricDetailsByName\.__init__:\s+" - match += r"Failed in super\(\)\.__init__\(\)\.\s+" - match += r"Error detail: FabricDetailsByName\.__init__:\s+" - match += r"check_mode is missing from params\. params:.*" - params = copy.copy(PARAMS) - params.pop("check_mode", None) - with pytest.raises(ValueError, match=match): - FabricDetailsByName(params) # pytest: disable=pointless-statement + with does_not_raise(): + instance = fabric_details_by_name_v2 + assert instance.rest_send is None + assert instance.results is None + assert instance.filter is None + assert instance.data_subclass == {} def test_fabric_details_by_name_v2_00200(fabric_details_by_name_v2) -> None: diff --git a/tests/unit/modules/dcnm/dcnm_fabric/test_fabric_details_by_nv_pair.py b/tests/unit/modules/dcnm/dcnm_fabric/test_fabric_details_by_nv_pair.py deleted file mode 100644 index cfc474f36..000000000 --- a/tests/unit/modules/dcnm/dcnm_fabric/test_fabric_details_by_nv_pair.py +++ /dev/null @@ -1,515 +0,0 @@ -# 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 inspect - -import pytest -from ansible_collections.cisco.dcnm.plugins.module_utils.common.api.v1.lan_fabric.rest.control.fabrics.fabrics import \ - EpFabrics -from ansible_collections.cisco.dcnm.plugins.module_utils.common.conversion import \ - ConversionUtils -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.tests.unit.module_utils.common.common_utils import \ - ResponseGenerator -from ansible_collections.cisco.dcnm.tests.unit.modules.dcnm.dcnm_fabric.utils import ( - MockAnsibleModule, does_not_raise, fabric_details_by_nv_pair_fixture, - responses_fabric_details_by_nv_pair) - - -def test_fabric_details_by_nv_pair_00010(fabric_details_by_nv_pair) -> None: - """ - Classes and Methods - - FabricCommon - - __init__() - - FabricDetails - - __init__() - - FabricDetailsByNvPair - - __init__() - - Test - - Class attributes are initialized to expected values - - Exception is not raised - """ - with does_not_raise(): - instance = fabric_details_by_nv_pair - assert instance.class_name == "FabricDetailsByNvPair" - assert instance.data == {} - assert instance.data_subclass == {} - assert instance._properties["filter_key"] is None - assert instance._properties["filter_value"] is None - assert isinstance(instance.ep_fabrics, EpFabrics) - assert isinstance(instance.results, Results) - assert isinstance(instance.conversion, ConversionUtils) - - -def test_fabric_details_by_nv_pair_00030( - monkeypatch, fabric_details_by_nv_pair -) -> None: - """ - Classes and Methods - - FabricCommon() - - __init__() - - FabricDetails() - - __init__() - - refresh_super() - - FabricDetailsByNvPair - - __init__() - - refresh() - - Summary - - Verify FabricDetailsByNvPair.refresh() behavior when: - - RETURN_CODE is 200. - - DATA is an empty list, indicating no fabrics - exist on the controller. - - Code Flow - Setup - - FabricDetailsByNvPair() is instantiated - - FabricDetails().RestSend() is instantiated - - FabricDetails().Results() is instantiated - - FabricDetailsByNvPair().refresh() is called - - FabricDetailsByNvPair().refresh() calls FabricDetails().refresh_super() - - FabricDetails().refresh_super() calls RestSend() and updates Results() - - FabricDetailsByNvPair().refresh() updates FabricDetailsByNvPair().data_subclass - with a copy of FabricDetails().data - - responses_FabricDetailsByNvPair contains a dict with: - - RETURN_CODE == 200 - - DATA == [] - - Code Flow - Test - - FabricDetailsByNvPair().filter_key is set - - FabricDetailsByNvPair().filter_value is set - - FabricDetailsByNvPair().refresh() is called - - Expected Result - - Exception is not raised - - Results() are updated - - FabricDetailsByNvPair().data_subclass is updated with FabricDetails().data - """ - method_name = inspect.stack()[0][3] - key = f"{method_name}a" - - PATCH_DCNM_SEND = "ansible_collections.cisco.dcnm.plugins." - PATCH_DCNM_SEND += "module_utils.common.rest_send.dcnm_send" - - def responses(): - yield responses_fabric_details_by_nv_pair(key) - - gen = ResponseGenerator(responses()) - - def mock_dcnm_send(*args, **kwargs): - item = gen.next - return item - - with does_not_raise(): - instance = fabric_details_by_nv_pair - instance.rest_send = RestSend(MockAnsibleModule()) - instance.rest_send.unit_test = True - instance.results = Results() - - monkeypatch.setattr(PATCH_DCNM_SEND, mock_dcnm_send) - - with does_not_raise(): - instance.filter_key = "FABRIC_NAME" - instance.filter_value = "f1" - instance.refresh() - - assert instance.data_subclass == instance.data - - assert isinstance(instance.results.diff, list) - assert isinstance(instance.results.result, list) - assert isinstance(instance.results.response, list) - - assert len(instance.results.diff) == 0 - assert len(instance.results.result) == 1 - assert len(instance.results.response) == 1 - - assert instance.results.response[0].get("RETURN_CODE", None) == 200 - assert instance.results.result[0].get("found", None) is True - assert instance.results.result[0].get("success", None) is True - - 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_fabric_details_by_nv_pair_00031( - monkeypatch, fabric_details_by_nv_pair -) -> None: - """ - Classes and Methods - - FabricCommon() - - __init__() - - FabricDetails() - - __init__() - - refresh_super() - - FabricDetailsByNvPair - - __init__() - - refresh() - - Summary - - Verify FabricDetailsByNvPair.refresh() behavior when: - - RETURN_CODE is 200. - - DATA is missing (negative test) - - Code Flow - Setup - - FabricDetailsByNvPair() is instantiated - - FabricDetails().RestSend() is instantiated - - FabricDetails().Results() is instantiated - - FabricDetailsByNvPair().refresh() is called - - FabricDetailsByNvPair().refresh() calls FabricDetails().refresh_super() - - FabricDetails().refresh_super() calls RestSend() and updates Results() - - FabricDetailsByNvPair().refresh() updates FabricDetailsByNvPair().data_subclass - with a copy of FabricDetails().data - - responses_FabricDetailsByNvPair contains a dict with: - - RETURN_CODE == 200 - - DATA is missing - - Code Flow - Test - - FabricDetailsByNvPair().filter_key is set - - FabricDetailsByNvPair().filter_value is set - - FabricDetailsByNvPair().refresh() is called - - Expected Result - - Exception is not raised - - Results() are updated - - FabricDetailsByNvPair().data_subclass is updated with FabricDetails().data - """ - method_name = inspect.stack()[0][3] - key = f"{method_name}a" - - PATCH_DCNM_SEND = "ansible_collections.cisco.dcnm.plugins." - PATCH_DCNM_SEND += "module_utils.common.rest_send.dcnm_send" - - def responses(): - yield responses_fabric_details_by_nv_pair(key) - - gen = ResponseGenerator(responses()) - - def mock_dcnm_send(*args, **kwargs): - item = gen.next - return item - - with does_not_raise(): - instance = fabric_details_by_nv_pair - instance.rest_send = RestSend(MockAnsibleModule()) - instance.rest_send.unit_test = True - instance.results = Results() - - monkeypatch.setattr(PATCH_DCNM_SEND, mock_dcnm_send) - - with does_not_raise(): - instance.filter_key = "FABRIC_NAME" - instance.filter_value = "f1" - instance.refresh() - - assert instance.data_subclass == instance.data - - assert isinstance(instance.results.diff, list) - assert isinstance(instance.results.result, list) - assert isinstance(instance.results.response, list) - - assert len(instance.results.diff) == 0 - assert len(instance.results.result) == 1 - assert len(instance.results.response) == 1 - - assert instance.results.response[0].get("RETURN_CODE", None) == 200 - assert instance.results.result[0].get("found", None) is True - assert instance.results.result[0].get("success", None) is True - - 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_fabric_details_by_nv_pair_00032( - monkeypatch, fabric_details_by_nv_pair -) -> None: - """ - Classes and Methods - - FabricCommon() - - __init__() - - FabricDetails() - - __init__() - - refresh_super() - - FabricDetailsByNvPair - - __init__() - - refresh() - - Summary - - Verify refresh() behavior when: - - RETURN_CODE is 200. - - Controller response contains one fabric (f1). - - Code Flow - Setup - - FabricDetailsByNvPair() is instantiated - - FabricDetails().RestSend() is instantiated - - FabricDetails().Results() is instantiated - - FabricDetailsByNvPair().refresh() is called - - FabricDetailsByNvPair().refresh() calls FabricDetails().refresh_super() - - FabricDetails().refresh_super() calls RestSend() and updates Results() - - FabricDetailsByNvPair().refresh() updates FabricDetailsByNvPair().data_subclass - with a copy of FabricDetails().data - - responses_FabricDetailsByNvPair contains a dict with: - - RETURN_CODE == 200 - - DATA == [] - - Code Flow - Test - - FabricDetailsByNvPair().filter_key is set - - FabricDetailsByNvPair().filter_value is set - - FabricDetailsByNvPair().refresh() is called - - Expected Result - - Exception is not raised - - Results() are updated - - FabricDetailsByNvPair().data_subclass is updated with FabricDetails().data - """ - method_name = inspect.stack()[0][3] - key = f"{method_name}a" - - PATCH_DCNM_SEND = "ansible_collections.cisco.dcnm.plugins." - PATCH_DCNM_SEND += "module_utils.common.rest_send.dcnm_send" - - def responses(): - yield responses_fabric_details_by_nv_pair(key) - - gen = ResponseGenerator(responses()) - - def mock_dcnm_send(*args, **kwargs): - item = gen.next - return item - - with does_not_raise(): - instance = fabric_details_by_nv_pair - instance.rest_send = RestSend(MockAnsibleModule()) - instance.rest_send.unit_test = True - instance.results = Results() - - monkeypatch.setattr(PATCH_DCNM_SEND, mock_dcnm_send) - - with does_not_raise(): - instance.filter_key = "FABRIC_NAME" - instance.filter_value = "f1" - instance.refresh() - - assert instance.data_subclass == instance.data - - assert isinstance(instance.results.diff, list) - assert isinstance(instance.results.result, list) - assert isinstance(instance.results.response, list) - - assert len(instance.results.diff) == 0 - assert len(instance.results.result) == 1 - assert len(instance.results.response) == 1 - - assert instance.results.response[0].get("RETURN_CODE", None) == 200 - assert instance.results.result[0].get("found", None) is True - assert instance.results.result[0].get("success", None) is True - - 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 - - assert instance.all_data.get("f1", {}).get("asn", None) == "65001" - assert instance.all_data.get("f1", {}).get("nvPairs", {}).get("FABRIC_NAME") == "f1" - - -def test_fabric_details_by_nv_pair_00033(fabric_details_by_nv_pair) -> None: - """ - Classes and Methods - - FabricCommon() - - __init__() - - FabricDetails() - - __init__() - - FabricDetailsByNvPair - - __init__() - - refresh() - - Summary - - Verify FabricDetails().refresh() raises ``ValueError`` when - ``filter_key`` is not set. - """ - with does_not_raise(): - instance = fabric_details_by_nv_pair - instance.filter_value = "f1" - - match = r"FabricDetailsByNvPair\.refresh: " - match += r"set FabricDetailsByNvPair\.filter_key to a nvPair key " - match += r"before calling FabricDetailsByNvPair\.refresh\(\)\." - with pytest.raises(ValueError, match=match): - instance.refresh() - - -def test_fabric_details_by_nv_pair_00034(fabric_details_by_nv_pair) -> None: - """ - Classes and Methods - - FabricCommon() - - __init__() - - FabricDetails() - - __init__() - - FabricDetailsByNvPair - - __init__() - - refresh() - - Summary - - Verify FabricDetails().refresh() raises ``ValueError`` when - ``filter_value`` is not set. - """ - with does_not_raise(): - instance = fabric_details_by_nv_pair - instance.filter_key = "BGP_AS" - - match = r"FabricDetailsByNvPair\.refresh: " - match += r"set FabricDetailsByNvPair\.filter_value to a nvPair value " - match += r"before calling FabricDetailsByNvPair\.refresh\(\)\." - with pytest.raises(ValueError, match=match): - instance.refresh() - - -def test_fabric_details_by_nv_pair_00040( - monkeypatch, fabric_details_by_nv_pair -) -> None: - """ - Classes and Methods - - FabricCommon() - - __init__() - - FabricDetails() - - __init__() - - refresh_super() - - FabricDetailsByNvPair - - __init__() - - refresh() - - filtered_data - - Summary - - Verify FabricDetailsByNvPair.filtered_data returns only fabrics which - match the filter_key and filter_value. - - Code Flow - Setup - - FabricDetailsByNvPair() is instantiated - - FabricDetails().RestSend() is instantiated - - FabricDetails().Results() is instantiated - - FabricDetailsByNvPair().filter_key is set - - FabricDetailsByNvPair().filter_value is set - - FabricDetailsByNvPair().refresh() is called - - FabricDetailsByNvPair().refresh() calls FabricDetails().refresh_super() - - FabricDetails().refresh_super() calls RestSend() and updates Results() - - FabricDetails().refresh_super() updates FabricDetails().data - - FabricDetailsByNvPair().refresh() updates FabricDetailsByNvPair().data_subclass - with fabrics from FabricDetails().data that match the filter_key and filter_value - - responses_FabricDetailsByNvPair contains a dict with: - - RETURN_CODE == 200 - - DATA == [] - - Code Flow - Test - - FabricDetailsByNvPair().filter_key is set - - FabricDetailsByNvPair().filter_value is set - - FabricDetailsByNvPair().refresh() is called - - Expected Result - - Exception is not raised - - Results() are updated - - FabricDetailsByNvPair().data_subclass is updated with matching - fabrics from FabricDetails().data - """ - method_name = inspect.stack()[0][3] - key = f"{method_name}a" - - PATCH_DCNM_SEND = "ansible_collections.cisco.dcnm.plugins." - PATCH_DCNM_SEND += "module_utils.common.rest_send.dcnm_send" - - def responses(): - yield responses_fabric_details_by_nv_pair(key) - - gen = ResponseGenerator(responses()) - - def mock_dcnm_send(*args, **kwargs): - item = gen.next - return item - - with does_not_raise(): - instance = fabric_details_by_nv_pair - instance.rest_send = RestSend(MockAnsibleModule()) - instance.rest_send.unit_test = True - instance.results = Results() - - monkeypatch.setattr(PATCH_DCNM_SEND, mock_dcnm_send) - - with does_not_raise(): - instance.filter_key = "REPLICATION_MODE" - instance.filter_value = "Ingress" - instance.refresh() - - # Both fabrics are in instance.data - assert "IR-Fabric" in instance.data - assert "MC-Fabric" in instance.data - - # instance.data_subclass only contains the fabric that matches the filter - assert instance.data_subclass != instance.data - assert len(instance.data_subclass) == 1 - assert "IR-Fabric" in instance.data_subclass - - # instance.filtered_data property returns contents of - # instance.data_subclass so will contain only the fabric - # that matches the filter - assert "IR-Fabric" in instance.filtered_data - assert "MC-Fabric" not in instance.filtered_data - - assert isinstance(instance.results.diff, list) - assert isinstance(instance.results.result, list) - assert isinstance(instance.results.response, list) - - assert len(instance.results.diff) == 0 - assert len(instance.results.result) == 1 - assert len(instance.results.response) == 1 - - assert instance.results.response[0].get("RETURN_CODE", None) == 200 - assert instance.results.result[0].get("found", None) is True - assert instance.results.result[0].get("success", None) is True - - 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 - - assert ( - instance.all_data.get("MC-Fabric", {}).get("nvPairs", {}).get("BGP_AS") - == "65001" - ) - assert ( - instance.all_data.get("IR-Fabric", {}).get("nvPairs", {}).get("BGP_AS") - == "65002" - ) diff --git a/tests/unit/modules/dcnm/dcnm_fabric/test_fabric_details_by_nv_pair_v2.py b/tests/unit/modules/dcnm/dcnm_fabric/test_fabric_details_by_nv_pair_v2.py index 8d3e97700..fb92b8d36 100644 --- a/tests/unit/modules/dcnm/dcnm_fabric/test_fabric_details_by_nv_pair_v2.py +++ b/tests/unit/modules/dcnm/dcnm_fabric/test_fabric_details_by_nv_pair_v2.py @@ -41,8 +41,6 @@ Results from ansible_collections.cisco.dcnm.plugins.module_utils.common.sender_file import \ Sender -from ansible_collections.cisco.dcnm.plugins.module_utils.fabric.fabric_details_v2 import \ - FabricDetailsByNvPair from ansible_collections.cisco.dcnm.tests.unit.module_utils.common.common_utils import \ ResponseGenerator from ansible_collections.cisco.dcnm.tests.unit.modules.dcnm.dcnm_fabric.utils import ( @@ -52,39 +50,35 @@ PARAMS = {"state": "query", "check_mode": False} -def test_fabric_details_by_nv_pair_v2_00000(monkeypatch) -> None: +def test_fabric_details_by_nv_pair_v2_00000(fabric_details_by_nv_pair_v2) -> None: """ ### Classes and Methods - FabricDetailsByNvPair() - __init__() ### Summary - - Verify that __init__ raises ``ValueError`` if ``super().__init__`` - raises ``ValueError`` + - Verify instance attributes are set correctly. ### Setup - Code - None ### Setup - Data - - params is modified to remove ``check_mode``. + - None ### Trigger - FabricDetailsByNvPair() is instantiated. ### Expected Result - - FabricDetailsByNvPair().__init__() raises ``ValueError`` because - FabricDetails().__init__() raises ``ValueError`` because params - is missing mandatory key ``check_mode``. - - Error message matches expectation. + - Instance attribute values are as expected. + - No expections are raised. """ - match = r"FabricDetailsByNvPair\.__init__:\s+" - match += r"Failed in super\(\)\.__init__\(\)\.\s+" - match += r"Error detail: FabricDetailsByNvPair\.__init__:\s+" - match += r"check_mode is missing from params\. params:.*" - params = copy.copy(PARAMS) - params.pop("check_mode", None) - with pytest.raises(ValueError, match=match): - FabricDetailsByNvPair(params) # pytest: disable=pointless-statement + with does_not_raise(): + instance = fabric_details_by_nv_pair_v2 + assert instance.rest_send is None + assert instance.results is None + assert instance.filter_key is None + assert instance.filter_value is None + assert instance.filtered_data == {} def test_fabric_details_by_nv_pair_v2_00200(fabric_details_by_nv_pair_v2) -> None: diff --git a/tests/unit/modules/dcnm/dcnm_fabric/test_fabric_details_v2.py b/tests/unit/modules/dcnm/dcnm_fabric/test_fabric_details_v2.py index 9cff01137..d08dfb34e 100644 --- a/tests/unit/modules/dcnm/dcnm_fabric/test_fabric_details_v2.py +++ b/tests/unit/modules/dcnm/dcnm_fabric/test_fabric_details_v2.py @@ -44,8 +44,6 @@ Results from ansible_collections.cisco.dcnm.plugins.module_utils.common.sender_file import \ Sender -from ansible_collections.cisco.dcnm.plugins.module_utils.fabric.fabric_details_v2 import \ - FabricDetails from ansible_collections.cisco.dcnm.tests.unit.module_utils.common.common_utils import \ ResponseGenerator from ansible_collections.cisco.dcnm.tests.unit.modules.dcnm.dcnm_fabric.utils import ( @@ -70,38 +68,6 @@ def test_fabric_details_v2_00000(fabric_details_v2) -> None: assert isinstance(instance.conversion, ConversionUtils) -def test_fabric_details_v2_00010() -> None: - """ - ### Classes and Methods - - FabricDetails - - __init__() - - ### Test - - ``ValueError`` is raised when ``params`` is missing key ``check_mode``. - """ - match = r"FabricDetails\.__init__:\s+" - match += r"check_mode is missing from params\. params:.*\." - with pytest.raises(ValueError, match=match): - instance = FabricDetails({"state": "merged"}) # pylint: disable=unused-variable - - -def test_fabric_details_v2_00020() -> None: - """ - ### Classes and Methods - - FabricDetails - - __init__() - - ### Test - - ``ValueError`` is raised when ``params`` is missing key ``state``. - """ - match = r"FabricDetails\.__init__:\s+" - match += r"state is missing from params\. params:.*\." - with pytest.raises(ValueError, match=match): - instance = FabricDetails( # pylint: disable=unused-variable - {"check_mode": False} - ) - - def test_fabric_details_v2_00100(fabric_details_v2) -> None: """ ### Classes and Methods @@ -535,12 +501,22 @@ def test_fabric_details_v2_00170(fabric_details_v2, monkeypatch) -> None: """ class MockEpFabrics: + """ + Mock EpFabrics class + """ + @property def verb(self): + """ + Mock verb property to raise TypeError + """ raise TypeError("MockEpFabrics.bad_verb") @property def path(self): + """ + Mock path property + """ return "/path" with does_not_raise(): diff --git a/tests/unit/modules/dcnm/dcnm_fabric/test_fabric_query.py b/tests/unit/modules/dcnm/dcnm_fabric/test_fabric_query.py index 80f10ea8b..733d32d91 100644 --- a/tests/unit/modules/dcnm/dcnm_fabric/test_fabric_query.py +++ b/tests/unit/modules/dcnm/dcnm_fabric/test_fabric_query.py @@ -32,52 +32,65 @@ import inspect import pytest -from ansible_collections.cisco.dcnm.plugins.module_utils.common.rest_send import \ +from ansible_collections.cisco.dcnm.plugins.module_utils.common.response_handler import \ + ResponseHandler +from ansible_collections.cisco.dcnm.plugins.module_utils.common.rest_send_v2 import \ RestSend from ansible_collections.cisco.dcnm.plugins.module_utils.common.results import \ Results -from ansible_collections.cisco.dcnm.plugins.module_utils.fabric.fabric_details import \ +from ansible_collections.cisco.dcnm.plugins.module_utils.common.sender_file import \ + Sender +from ansible_collections.cisco.dcnm.plugins.module_utils.fabric.fabric_details_v2 import \ FabricDetailsByName from ansible_collections.cisco.dcnm.tests.unit.module_utils.common.common_utils import \ ResponseGenerator from ansible_collections.cisco.dcnm.tests.unit.modules.dcnm.dcnm_fabric.utils import ( - MockAnsibleModule, does_not_raise, fabric_query_fixture, params, + MockAnsibleModule, does_not_raise, fabric_query_fixture, responses_fabric_query) +PARAMS = {"state": "query", "check_mode": False} -def test_fabric_query_00010(fabric_query) -> None: + +def test_fabric_query_00000(fabric_query) -> None: """ - Classes and Methods + ### Classes and Methods + - FabricCommon - __init__() - FabricQuery - __init__() - Test + ### Test + - Class attributes are initialized to expected values - Exception is not raised """ with does_not_raise(): instance = fabric_query - instance.fabric_details = FabricDetailsByName(params) assert instance.class_name == "FabricQuery" - assert instance.action == "query" - assert instance.state == "query" - assert isinstance(instance.fabric_details, FabricDetailsByName) + assert instance.action == "fabric_query" + assert instance.fabric_details is None + assert instance.fabric_names is None + assert instance._fabrics_to_query == [] def test_fabric_query_00020(fabric_query) -> None: """ - Classes and Methods + ### Classes and Methods + - FabricCommon - __init__() - FabricQuery - __init__() - fabric_names setter - Test - - fabric_names is set to expected value - - Exception is not raised + ### Summary + Verify behavior when ``fabric_names`` is set to a list of strings. + + ### Test + + - ``fabric_names`` is set to expected value. + - Exception is not raised. """ fabric_names = ["FOO", "BAR"] with does_not_raise(): @@ -88,129 +101,237 @@ def test_fabric_query_00020(fabric_query) -> None: def test_fabric_query_00021(fabric_query) -> None: """ - Classes and Methods + ### Classes and Methods + - FabricCommon - __init__() - FabricQuery - __init__() - fabric_names setter - Test - - ValueError is raised because fabric_names is not a list - - instance.fabric_names is not modified, hence it retains its initial value of None - """ - match = "FabricQuery.fabric_names: " - match += "fabric_names must be a list." + ### Summary + Verify behavior when ``fabric_names`` is set to a non-list. + ### Test + - ``ValueError`` is raised because ``fabric_names`` is not a list. + - ``fabric_names`` is not modified, hence it retains its initial value + of None. + """ with does_not_raise(): instance = fabric_query + + match = r"FabricQuery\.fabric_names: " + match += r"fabric_names must be a list\." + with pytest.raises(ValueError, match=match): instance.fabric_names = "NOT_A_LIST" + assert instance.fabric_names is None def test_fabric_query_00022(fabric_query) -> None: """ - Classes and Methods + ### Classes and Methods + - FabricCommon - __init__() - FabricQuery - __init__() - fabric_names setter - Test - - ValueError is raised because fabric_names is a list with a non-string element - - instance.fabric_names is not modified, hence it retains its initial value of None - """ - match = "FabricQuery.fabric_names: " - match += "fabric_names must be a list of strings." + ### Summary + Verify behavior when ``fabric_names`` is set to a list with a non-string + element. + ### Test + + - ``ValueError`` is raised because fabric_names is a list with a + non-string element. + - ``fabric_names`` is not modified, hence it retains its initial value + of None. + """ with does_not_raise(): instance = fabric_query + + match = r"FabricQuery.fabric_names: " + match += r"fabric_names must be a list of strings." + with pytest.raises(ValueError, match=match): instance.fabric_names = [1, 2, 3] + assert instance.fabric_names is None def test_fabric_query_00023(fabric_query) -> None: """ - Classes and Methods + ### Classes and Methods + + - FabricQuery + - fabric_names setter + + ### Summary + Verify behavior when ``fabric_names`` is set to an empty list. + + ### Setup + + - FabricQuery().fabric_names is set to an empty list + + ### Test + - ``ValueError`` is raised from ``fabric_names`` setter. + """ + match = r"FabricQuery\.fabric_names: fabric_names must be a list of " + match += r"at least one string\." + + with pytest.raises(ValueError, match=match): + instance = fabric_query + instance.fabric_names = [] + + +def test_fabric_query_00024(fabric_query) -> None: + """ + ### Classes and Methods + - FabricCommon - __init__() - FabricQuery - __init__() - - fabric_names setter + - commit() + - _validate_commit_parameters() + + ### Summary + Verify behavior when ``fabric_details`` is not set before calling commit. + + ### Test + ``ValueError`` is raised because fabric_details is not set before + calling commit. + """ + with does_not_raise(): + instance = fabric_query + instance.fabric_names = ["f1"] + instance.rest_send = RestSend(PARAMS) + instance.results = Results() + + match = r"FabricQuery._validate_commit_parameters:\s+" + match += r"fabric_details must be set before calling commit\." + + with pytest.raises(ValueError, match=match): + instance.commit() - Summary - Verify behavior when fabric_names is not set prior to calling commit + assert instance.fabric_names == ["f1"] - Test - - ValueError is raised because fabric_names is not set prior to calling commit - - instance.fabric_names is not modified, hence it retains its initial value of None + +def test_fabric_query_00025(fabric_query) -> None: """ - match = r"FabricQuery\.commit: " - match += r"fabric_names must be set prior to calling commit\." + ### Classes and Methods + - FabricCommon + - __init__() + - FabricQuery + - __init__() + - commit() + - _validate_commit_parameters() + + ### Summary + Verify behavior when ``fabric_names`` is not set before calling commit. + + ### Test + + - ``ValueError`` is raised because fabric_names is not set before + calling commit. + - ``fabric_names`` is not modified, hence it retains its initial value + of None. + """ with does_not_raise(): instance = fabric_query + instance.fabric_details = FabricDetailsByName() + instance.rest_send = RestSend(PARAMS) instance.results = Results() + + match = r"FabricQuery\._validate_commit_parameters:\s+" + match += r"fabric_names must be set before calling commit\." + with pytest.raises(ValueError, match=match): instance.commit() + assert instance.fabric_names is None -def test_fabric_query_00024(fabric_query) -> None: +def test_fabric_query_00026(fabric_query) -> None: """ - Classes and Methods + ### Classes and Methods + + - FabricCommon + - __init__() - FabricQuery - - fabric_names setter + - __init__() + - commit() + - _validate_commit_parameters() - Summary - Verify behavior when fabric_names is set to an empty list + ### Summary + Verify behavior when ``rest_send`` is not set before calling commit. - Setup - - FabricQuery().fabric_names is set to an empty list + ### Test - Test - - ValueError is raised from fabric_names setter + - ``ValueError`` is raised because ``rest_send`` is not set before + calling commit. + - ``rest_send`` is not modified, hence it retains its initial value + of None. """ - match = r"FabricQuery\.fabric_names: fabric_names must be a list of " - match += r"at least one string\." - with pytest.raises(ValueError, match=match): + with does_not_raise(): instance = fabric_query - instance.fabric_names = [] + instance.fabric_details = FabricDetailsByName() + instance.fabric_names = ["f1"] + instance.results = Results() + match = r"FabricQuery\._validate_commit_parameters:\s+" + match += r"rest_send must be set before calling commit\." -def test_fabric_query_00025(fabric_query) -> None: + with pytest.raises(ValueError, match=match): + instance.commit() + + assert instance.rest_send is None + + +def test_fabric_query_00027(fabric_query) -> None: """ - Classes and Methods + ### Classes and Methods + - FabricCommon - __init__() - FabricQuery - __init__() - - fabric_details setter + - commit() + - _validate_commit_parameters() - Summary - Verify behavior when fabric_details is not set prior to calling commit + ### Summary + Verify behavior when ``results`` is not set before calling commit. - Test - - ValueError is raised because fabric_details is not set prior to calling commit - """ - match = r"FabricQuery\.commit: " - match += r"fabric_details must be set prior to calling commit\." + ### Test + - ``ValueError`` is raised because ``results`` is not set before + calling commit. + - ``Results()`` is instantiated in ``_validate_commit_parameters`` + in order to register a failed result. + """ with does_not_raise(): instance = fabric_query + instance.fabric_details = FabricDetailsByName() instance.fabric_names = ["f1"] - instance.results = Results() + instance.rest_send = RestSend(PARAMS) + + match = r"FabricQuery\._validate_commit_parameters:\s+" + match += r"results must be set before calling commit\." + with pytest.raises(ValueError, match=match): instance.commit() - assert instance.fabric_names == ["f1"] + assert instance.results.class_name == "Results" -def test_fabric_query_00030(monkeypatch, fabric_query) -> None: + +def test_fabric_query_00030(fabric_query) -> None: """ - Classes and Methods + ### Classes and Methods + - FabricCommon() - __init__() - FabricDetails() @@ -227,11 +348,12 @@ def test_fabric_query_00030(monkeypatch, fabric_query) -> None: - __init__() - commit() - Summary + ### Summary Verify behavior when user queries a fabric and no fabrics exist on the controller and the RestSend() RETURN_CODE is 200. - Code Flow + ### Code Flow + - main.Query() is instantiated and instantiates FabricQuery() - FabricQuery() instantiates FabricDetailsByName() - FabricQuery.fabric_names is set to contain one fabric_name (f1) @@ -254,32 +376,28 @@ def test_fabric_query_00030(monkeypatch, fabric_query) -> None: method_name = inspect.stack()[0][3] key = f"{method_name}a" - PATCH_DCNM_SEND = "ansible_collections.cisco.dcnm.plugins." - PATCH_DCNM_SEND += "module_utils.common.rest_send.dcnm_send" - def responses(): yield responses_fabric_query(key) - gen = ResponseGenerator(responses()) + gen_responses = ResponseGenerator(responses()) - def mock_dcnm_send(*args, **kwargs): - item = gen.next - return item + sender = Sender() + sender.ansible_module = MockAnsibleModule() + sender.gen = gen_responses + rest_send = RestSend(PARAMS) + rest_send.unit_test = True + rest_send.timeout = 1 + rest_send.response_handler = ResponseHandler() + rest_send.sender = sender with does_not_raise(): instance = fabric_query - - instance.fabric_details = FabricDetailsByName(params) - instance.fabric_details.rest_send = RestSend(MockAnsibleModule()) - instance.fabric_details.rest_send.unit_test = True - + instance.fabric_details = FabricDetailsByName() + instance.fabric_details.rest_send = rest_send + instance.fabric_details.results = Results() instance.fabric_names = ["f1"] + instance.rest_send = rest_send instance.results = Results() - - monkeypatch.setattr(PATCH_DCNM_SEND, mock_dcnm_send) - instance.fabric_details.results = Results() - - with does_not_raise(): instance.commit() assert isinstance(instance.results.diff, list) @@ -301,9 +419,10 @@ def mock_dcnm_send(*args, **kwargs): assert True not in instance.results.changed -def test_fabric_query_00031(monkeypatch, fabric_query) -> None: +def test_fabric_query_00031(fabric_query) -> None: """ - Classes and Methods + ### Classes and Methods + - FabricCommon() - __init__() - FabricDetails() @@ -320,12 +439,13 @@ def test_fabric_query_00031(monkeypatch, fabric_query) -> None: - __init__() - commit() - Summary + ### Summary Verify behavior when user queries a fabric that does not exist on the controller. One fabric (f2) exists on the controller, and the RestSend() RETURN_CODE is 200. - Code Flow + ### Code Flow + - main.Query() is instantiated and instantiates FabricQuery() - FabricQuery() instantiates FabricDetailsByName() - FabricQuery.fabric_names is set to contain one fabric_name (f1) @@ -347,7 +467,8 @@ def test_fabric_query_00031(monkeypatch, fabric_query) -> None: - Results().register_task_result() adds sequence_number (with value 1) to each of the results dicts - Test + ### Test + - FabricQuery.commit() calls instance.fabric_details() which sets instance.fabric_details.all_data to a list of dict containing all fabrics on the controller. @@ -364,31 +485,28 @@ def test_fabric_query_00031(monkeypatch, fabric_query) -> None: method_name = inspect.stack()[0][3] key = f"{method_name}a" - PATCH_DCNM_SEND = "ansible_collections.cisco.dcnm.plugins." - PATCH_DCNM_SEND += "module_utils.common.rest_send.dcnm_send" - def responses(): yield responses_fabric_query(key) - gen = ResponseGenerator(responses()) + gen_responses = ResponseGenerator(responses()) - def mock_dcnm_send(*args, **kwargs): - item = gen.next - return item + sender = Sender() + sender.ansible_module = MockAnsibleModule() + sender.gen = gen_responses + rest_send = RestSend(PARAMS) + rest_send.unit_test = True + rest_send.timeout = 1 + rest_send.response_handler = ResponseHandler() + rest_send.sender = sender with does_not_raise(): instance = fabric_query - - instance.fabric_details = FabricDetailsByName(params) - instance.fabric_details.rest_send = RestSend(MockAnsibleModule()) - instance.fabric_details.rest_send.unit_test = True - - instance.results = Results() + instance.fabric_details = FabricDetailsByName() + instance.fabric_details.rest_send = rest_send + instance.fabric_details.results = Results() instance.fabric_names = ["f1"] - - monkeypatch.setattr(PATCH_DCNM_SEND, mock_dcnm_send) - - with does_not_raise(): + instance.rest_send = rest_send + instance.results = Results() instance.commit() assert isinstance(instance.results.diff, list) @@ -410,8 +528,10 @@ def mock_dcnm_send(*args, **kwargs): assert True not in instance.results.changed -def test_fabric_query_00032(monkeypatch, fabric_query) -> None: +def test_fabric_query_00032(fabric_query) -> None: """ + ### Classes and Methods + - FabricCommon() - __init__() - FabricDetails() @@ -428,12 +548,13 @@ def test_fabric_query_00032(monkeypatch, fabric_query) -> None: - __init__() - commit() - Summary + ### Summary Verify behavior when user queries a fabric that does not exist on the controller. One fabric (f2) exists on the controller, but the RestSend() RETURN_CODE is 500. - Code Flow + ### Code Flow + - main.Query() is instantiated and instantiates FabricQuery() - FabricQuery() instantiates FabricDetailsByName() - FabricQuery.fabric_names is set to contain one fabric_name (f1) @@ -455,43 +576,39 @@ def test_fabric_query_00032(monkeypatch, fabric_query) -> None: - Results().register_task_result() adds sequence_number (with value 1) to each of the results dicts - Setup - - RestSend().commit() is mocked to return a dict with key RETURN_CODE == 500 + ### Setup + + - RestSend().commit() response is mocked to return a dict with key + RETURN_CODE == 500 - RestSend().timeout is set to 1 - - RestSend().send_interval is set to 1 - RestSend().unit_test is set to True """ method_name = inspect.stack()[0][3] key = f"{method_name}a" - PATCH_DCNM_SEND = "ansible_collections.cisco.dcnm.plugins." - PATCH_DCNM_SEND += "module_utils.common.rest_send.dcnm_send" - def responses(): yield responses_fabric_query(key) - gen = ResponseGenerator(responses()) + gen_responses = ResponseGenerator(responses()) - def mock_dcnm_send(*args, **kwargs): - item = gen.next - return item + sender = Sender() + sender.ansible_module = MockAnsibleModule() + sender.gen = gen_responses + rest_send = RestSend(PARAMS) + rest_send.unit_test = True + rest_send.timeout = 1 + rest_send.response_handler = ResponseHandler() + rest_send.sender = sender with does_not_raise(): instance = fabric_query - - instance.fabric_details = FabricDetailsByName(params) - instance.fabric_details.rest_send = RestSend(MockAnsibleModule()) - instance.fabric_details.rest_send.unit_test = True - instance.fabric_details.rest_send.timeout = 1 - instance.fabric_details.rest_send.send_interval = 1 - - instance.results = Results() + instance.fabric_details = FabricDetailsByName() + instance.fabric_details.rest_send = rest_send + instance.fabric_details.results = Results() instance.fabric_names = ["f1"] - - monkeypatch.setattr(PATCH_DCNM_SEND, mock_dcnm_send) - instance.fabric_details.results = Results() - with does_not_raise(): + instance.rest_send = rest_send + instance.results = Results() instance.commit() assert isinstance(instance.results.diff, list) @@ -513,9 +630,10 @@ def mock_dcnm_send(*args, **kwargs): assert True not in instance.results.changed -def test_fabric_query_00033(monkeypatch, fabric_query) -> None: +def test_fabric_query_00033(fabric_query) -> None: """ - Classes and Methods + ### Classes and Methods + - FabricCommon() - __init__() - FabricDetails() @@ -532,12 +650,13 @@ def test_fabric_query_00033(monkeypatch, fabric_query) -> None: - __init__() - commit() - Summary + ### Summary Verify behavior when user queries a fabric that exists on the controller. One fabric (f1) exists on the controller, and the RestSend() RETURN_CODE is 200. - Code Flow + ### Code Flow + - main.Query() is instantiated and instantiates FabricQuery() - FabricQuery() instantiates FabricDetailsByName() - FabricQuery.fabric_names is set to contain one fabric_name (f1) @@ -562,33 +681,28 @@ def test_fabric_query_00033(monkeypatch, fabric_query) -> None: method_name = inspect.stack()[0][3] key = f"{method_name}a" - PATCH_DCNM_SEND = "ansible_collections.cisco.dcnm.plugins." - PATCH_DCNM_SEND += "module_utils.common.rest_send.dcnm_send" - def responses(): yield responses_fabric_query(key) - gen = ResponseGenerator(responses()) + gen_responses = ResponseGenerator(responses()) - def mock_dcnm_send(*args, **kwargs): - item = gen.next - return item + sender = Sender() + sender.ansible_module = MockAnsibleModule() + sender.gen = gen_responses + rest_send = RestSend(PARAMS) + rest_send.unit_test = True + rest_send.timeout = 1 + rest_send.response_handler = ResponseHandler() + rest_send.sender = sender with does_not_raise(): instance = fabric_query - - instance.fabric_details = FabricDetailsByName(params) - instance.fabric_details.rest_send = RestSend(MockAnsibleModule()) - instance.fabric_details.rest_send.unit_test = True - instance.fabric_details.rest_send.timeout = 1 - instance.fabric_details.rest_send.send_interval = 1 - - instance.results = Results() + instance.fabric_details = FabricDetailsByName() + instance.fabric_details.rest_send = rest_send + instance.fabric_details.results = Results() instance.fabric_names = ["f1"] - - monkeypatch.setattr(PATCH_DCNM_SEND, mock_dcnm_send) - - with does_not_raise(): + instance.rest_send = rest_send + instance.results = Results() instance.commit() assert isinstance(instance.results.diff, list) diff --git a/tests/unit/modules/dcnm/dcnm_fabric/test_fabric_replaced_bulk.py b/tests/unit/modules/dcnm/dcnm_fabric/test_fabric_replaced_bulk.py index 58de6533d..482963584 100644 --- a/tests/unit/modules/dcnm/dcnm_fabric/test_fabric_replaced_bulk.py +++ b/tests/unit/modules/dcnm/dcnm_fabric/test_fabric_replaced_bulk.py @@ -32,210 +32,176 @@ import inspect import pytest -from ansible_collections.cisco.dcnm.plugins.module_utils.common.api.v1.lan_fabric.rest.control.fabrics.fabrics import \ - EpFabricUpdate -from ansible_collections.cisco.dcnm.plugins.module_utils.common.rest_send import \ +from ansible_collections.cisco.dcnm.plugins.module_utils.common.response_handler import \ + ResponseHandler +from ansible_collections.cisco.dcnm.plugins.module_utils.common.rest_send_v2 import \ RestSend from ansible_collections.cisco.dcnm.plugins.module_utils.common.results import \ Results -from ansible_collections.cisco.dcnm.plugins.module_utils.fabric.fabric_details import \ +from ansible_collections.cisco.dcnm.plugins.module_utils.common.sender_file import \ + Sender +from ansible_collections.cisco.dcnm.plugins.module_utils.fabric.fabric_details_v2 import \ FabricDetailsByName from ansible_collections.cisco.dcnm.plugins.module_utils.fabric.fabric_summary import \ FabricSummary -from ansible_collections.cisco.dcnm.plugins.module_utils.fabric.fabric_types import \ - FabricTypes -from ansible_collections.cisco.dcnm.plugins.module_utils.fabric.param_info import \ - ParamInfo -from ansible_collections.cisco.dcnm.plugins.module_utils.fabric.ruleset import \ - RuleSet -from ansible_collections.cisco.dcnm.plugins.module_utils.fabric.template_get import \ - TemplateGet -from ansible_collections.cisco.dcnm.plugins.module_utils.fabric.verify_playbook_params import \ - VerifyPlaybookParams from ansible_collections.cisco.dcnm.tests.unit.module_utils.common.common_utils import \ ResponseGenerator from ansible_collections.cisco.dcnm.tests.unit.modules.dcnm.dcnm_fabric.utils import ( - MockAnsibleModule, does_not_raise, fabric_replaced_bulk_fixture, params, + MockAnsibleModule, does_not_raise, fabric_replaced_bulk_fixture, payloads_fabric_replaced_bulk, responses_config_deploy, - responses_config_save, responses_fabric_details_by_name, - responses_fabric_replaced_bulk, responses_fabric_summary) + responses_config_save, responses_fabric_replaced_bulk, + responses_fabric_summary) +PARAMS = {"state": "replaced", "check_mode": False} -def test_fabric_replaced_bulk_00010(fabric_replaced_bulk) -> None: + +def test_fabric_replaced_bulk_00000(fabric_replaced_bulk) -> None: """ - Classes and Methods + ### Classes and Methods + - FabricCommon - __init__() - Test - - Class attributes are initialized to expected values - - Exception is not raised + ### Test + + - Class attributes are initialized to expected values. + - Exception is not raised. """ with does_not_raise(): instance = fabric_replaced_bulk - instance.fabric_details = FabricDetailsByName(params) - instance.fabric_summary = FabricSummary(params) - instance.rest_send = RestSend(MockAnsibleModule()) - instance.results = Results() assert instance.class_name == "FabricReplacedBulk" - assert instance.action == "replace" + assert instance.action == "fabric_replace" + assert instance.ep_fabric_update.class_name == "EpFabricUpdate" + assert instance.fabric_details is None + assert instance.fabric_summary is None + assert instance.param_info.class_name == "ParamInfo" + assert instance.rest_send is None + assert instance.results is None + assert instance.ruleset.class_name == "RuleSet" + assert instance.template_get.class_name == "TemplateGet" + assert instance.verify_playbook_params.class_name == "VerifyPlaybookParams" assert instance.path is None assert instance.verb is None - assert instance.state == "replaced" - assert isinstance(instance.ep_fabric_update, EpFabricUpdate) - assert isinstance(instance.fabric_details, FabricDetailsByName) - assert isinstance(instance.fabric_summary, FabricSummary) - assert isinstance(instance.fabric_types, FabricTypes) - assert isinstance(instance.param_info, ParamInfo) - assert isinstance(instance.rest_send, RestSend) - assert isinstance(instance.results, Results) - assert isinstance(instance.ruleset, RuleSet) - assert isinstance(instance.template_get, TemplateGet) - assert isinstance(instance.verify_playbook_params, VerifyPlaybookParams) def test_fabric_replaced_bulk_00020(fabric_replaced_bulk) -> None: """ - Classes and Methods + ### Classes and Methods + - FabricCommon - __init__() - payloads setter - FabricReplacedBulk - __init__() - Summary - A valid payloads list is presented to the payloads setter + ### Summary + A valid payloads list is presented to the ``payloads`` setter. - Test - - payloads is set to expected value - - ``ValueError`` is not raised + ### Test + + - ``payloads`` is set to expected value. + - Exception is not raised. """ method_name = inspect.stack()[0][3] key = f"{method_name}a" with does_not_raise(): instance = fabric_replaced_bulk - instance.fabric_details = FabricDetailsByName(params) - instance.results = Results() instance.payloads = payloads_fabric_replaced_bulk(key) assert instance.payloads == payloads_fabric_replaced_bulk(key) def test_fabric_replaced_bulk_00021(fabric_replaced_bulk) -> None: """ - Classes and Methods + ### Classes and Methods + - FabricCommon - __init__() - payloads setter - FabricReplacedBulk - __init__() - Summary + ### Summary ``payloads`` setter is presented with input that is not a list. - Test - - ``ValueError`` is raised because payloads is not a list - - instance.payloads retains its initial value of None + ### Test + + - ``ValueError`` is raised because ``payloads`` is not a list. + - ``payloads`` retains its initial value of None. """ + with does_not_raise(): + instance = fabric_replaced_bulk + match = r"FabricReplacedBulk\.payloads: " match += r"payloads must be a list of dict\." - with does_not_raise(): - instance = fabric_replaced_bulk - instance.fabric_details = FabricDetailsByName(params) - instance.results = Results() with pytest.raises(ValueError, match=match): instance.payloads = "NOT_A_LIST" + assert instance.payloads is None def test_fabric_replaced_bulk_00022(fabric_replaced_bulk) -> None: """ - Classes and Methods + ### Classes and Methods + - FabricCommon - __init__() - payloads setter - FabricReplacedBulk - __init__() - Summary + ### Summary ``payloads`` setter is presented with a list that contains a non-dict element. - Test - - ``ValueError`` is raised because payloads is a list with non-dict elements - - instance.payloads retains its initial value of None + ### Test + + - ``ValueError`` is raised because payloads is a list with non-dict + elements. + - ``payloads`` retains its initial value of None. """ + with does_not_raise(): + instance = fabric_replaced_bulk + match = r"FabricReplacedBulk._verify_payload:\s+" match += r"Playbook configuration for fabrics must be a dict\.\s+" match += r"Got type int, value 1\." - with does_not_raise(): - instance = fabric_replaced_bulk - instance.fabric_details = FabricDetailsByName(params) - instance.results = Results() with pytest.raises(ValueError, match=match): instance.payloads = [1, 2, 3] + assert instance.payloads is None def test_fabric_replaced_bulk_00023(fabric_replaced_bulk) -> None: """ - Classes and Methods - - FabricCommon - - __init__() - - payloads setter - - FabricReplacedBulk - - __init__() - - Summary - payloads is not set prior to calling commit - - Test - - ``ValueError`` is raised because payloads is not set - prior to calling commit - - instance.payloads retains its initial value of None - """ - match = r"FabricReplacedBulk\.commit: " - match += r"payloads must be set prior to calling commit\." + ### Classes and Methods - with does_not_raise(): - instance = fabric_replaced_bulk - instance.fabric_details = FabricDetailsByName(params) - instance.fabric_summary = FabricSummary(params) - instance.results = Results() - with pytest.raises(ValueError, match=match): - instance.commit() - assert instance.payloads is None - - -def test_fabric_replaced_bulk_00024(fabric_replaced_bulk) -> None: - """ - Classes and Methods - FabricCommon - __init__() - payloads setter - FabricReplacedBulk - __init__() - Summary - payloads is set to an empty list + ### Summary + Verify behavior when ``payloads`` is set to an empty list. Setup - - FabricReplacedBulk().payloads is set to an empty list + - ``payloads`` is set to an empty list. + + ### Test + - ``ValueError`` is not raised. + - ``payloads`` is set to an empty list. - Test - - ``ValueError`` is not raised - - payloads is set to an empty list + ### NOTES - NOTES: - element_spec in dcnm_fabric.py.main() is configured such that AnsibleModule will raise an exception when config is not a list - of dict. Hence, we do not test instance.commit() here since it - would never be reached. + of dict. Hence, we do not test commit() here since it would + never be reached. """ with does_not_raise(): instance = fabric_replaced_bulk - instance.results = Results() instance.payloads = [] assert instance.payloads == [] @@ -244,28 +210,28 @@ def test_fabric_replaced_bulk_00024(fabric_replaced_bulk) -> None: "mandatory_parameter", ["BGP_AS", "FABRIC_NAME", "FABRIC_TYPE"], ) -def test_fabric_replaced_bulk_00025(fabric_replaced_bulk, mandatory_parameter) -> None: +def test_fabric_replaced_bulk_00024(fabric_replaced_bulk, mandatory_parameter) -> None: """ - Classes and Methods + ### Classes and Methods + - FabricReplacedCommon - __init__() - payloads setter - FabricReplacedBulk - __init__() - Summary + ### Summary + - Verify FabricReplacedCommon().payloads setter re-raises ``ValueError`` raised by FabricCommon()._verify_payload() when payloads is missing mandatory keys. - Verify instance.payloads retains its initial value of None. - """ method_name = inspect.stack()[0][3] key = f"{method_name}a" with does_not_raise(): instance = fabric_replaced_bulk - instance.results = Results() payloads = payloads_fabric_replaced_bulk(key) payloads[0].pop(mandatory_parameter, None) @@ -280,15 +246,50 @@ def test_fabric_replaced_bulk_00025(fabric_replaced_bulk, mandatory_parameter) - assert instance.payloads is None +def test_fabric_replaced_bulk_00025(fabric_replaced_bulk) -> None: + """ + ### Classes and Methods + + - FabricCommon + - __init__() + - commit() + - FabricReplacedBulk + - __init__() + + ### Summary + ``payloads`` is not set prior to calling commit. + + ### Test + + - ``ValueError`` is raised because payloads is not set + prior to calling commit + - instance.payloads retains its initial value of None + """ + with does_not_raise(): + instance = fabric_replaced_bulk + instance.fabric_details = FabricDetailsByName() + instance.fabric_summary = FabricSummary() + instance.results = Results() + + match = r"FabricReplacedBulk\.commit: " + match += r"payloads must be set prior to calling commit\." + + with pytest.raises(ValueError, match=match): + instance.commit() + assert instance.payloads is None + + def test_fabric_replaced_bulk_00030(fabric_replaced_bulk) -> None: """ - Classes and Methods + ### Classes and Methods + - FabricReplacedCommon - __init__() - payloads setter - _translate_payload_for_comparison() - Summary + ### Summary + - Verify FabricReplacedCommon()._translate_payload_for_comparison() translates correctly-spelled payload keys to the incorrectly-spelled keys expected by the controller. @@ -300,7 +301,6 @@ def test_fabric_replaced_bulk_00030(fabric_replaced_bulk) -> None: to "false" - Verify VPC_DELAY_RESTORE_TIME is translated from 300 to "300" - """ method_name = inspect.stack()[0][3] key = f"{method_name}a" @@ -350,7 +350,8 @@ def test_fabric_replaced_bulk_00031( fabric_replaced_bulk, mac_in, mac_out, raises, expected ) -> None: """ - Classes and Methods + ### Classes and Methods + - FabricCommon() - __init__() - translate_anycast_gw_mac() @@ -358,7 +359,8 @@ def test_fabric_replaced_bulk_00031( - __init__() - _translate_payload_for_comparison() - Summary + ### Summary + - Verify FabricReplacedCommon()._translate_payload_for_comparison() re-raises ``ValueError`` if ANYCAST_GW_MAC cannot be translated. - Verify the error message when ``ValueError`` is raised. @@ -405,14 +407,15 @@ def test_fabric_replaced_bulk_00040( fabric_replaced_bulk, parameter, playbook, controller, default, expected ) -> None: """ - Classes and Methods + ### Classes and Methods + - FabricReplacedCommon - __init__() - update_replaced_payload() - Summary - - Verify FabricReplacedCommon().update_replaced_payload() returns - expected values for all possible input combinations. + ### Summary + Verify ``update_replaced_payload`` returns expected values for all possible + input combinations. """ with does_not_raise(): instance = fabric_replaced_bulk @@ -445,16 +448,18 @@ def test_fabric_replaced_bulk_00050( fabric_replaced_bulk, user_value, controller_value, default_value, expected ) -> None: """ - Classes and Methods + ### Classes and Methods + - FabricReplacedCommon - __init__() - _verify_value_types_for_comparison() - Summary - - Verify FabricReplacedCommon()._verify_value_types_for_comparison() - does not raise ``ValueError`` when input types are consistent. - - Verify FabricReplacedCommon()._verify_value_types_for_comparison() - raises ``ValueError`` when input types are inconsistent. + ### Summary + + - Verify ``_verify_value_types_for_comparison`` does not raise + ``ValueError`` when input types are consistent. + - Verify ``_verify_value_types_for_comparison`` raises + ``ValueError`` when input types are inconsistent. - Verify the error message when ``ValueError`` is raised. """ fabric = "MyFabric" @@ -469,102 +474,120 @@ def test_fabric_replaced_bulk_00050( def test_fabric_replaced_bulk_00200(fabric_replaced_bulk) -> None: """ - Classes and Methods + ### Classes and Methods + - FabricReplacedBulk - commit() - Summary - - Verify `ValueError`` is raised when FabricReplacedBulk().fabric_details - is not set before calling FabricReplacedBulk().commit(). + ### Summary + Verify `ValueError`` is raised when ``fabric_details`` is not set before + calling ``commit``. """ - match = r"FabricReplacedBulk\.commit:\s+" - match += r"fabric_details must be set prior to calling commit\." - with pytest.raises(ValueError, match=match): + with does_not_raise(): instance = fabric_replaced_bulk - instance.fabric_summary = FabricSummary(params) + instance.fabric_summary = FabricSummary() instance.payloads = [] - instance.rest_send = RestSend(MockAnsibleModule()) + instance.rest_send = RestSend(PARAMS) instance.results = Results() + + match = r"FabricReplacedBulk\.commit:\s+" + match += r"fabric_details must be set prior to calling commit\." + + with pytest.raises(ValueError, match=match): instance.commit() def test_fabric_replaced_bulk_00210(fabric_replaced_bulk) -> None: """ - Classes and Methods + ### Classes and Methods + - FabricReplacedBulk - commit() - Summary - - Verify `ValueError`` is raised when FabricReplacedBulk().fabric_summary - is not set before calling FabricReplacedBulk().commit(). + ### Summary + Verify `ValueError`` is raised when ``fabric_summary`` is not set before + calling ``commit``. """ - match = r"FabricReplacedBulk\.commit:\s+" - match += r"fabric_summary must be set prior to calling commit\." - with pytest.raises(ValueError, match=match): + with does_not_raise(): instance = fabric_replaced_bulk - instance.fabric_details = FabricDetailsByName(params) + instance.fabric_details = FabricDetailsByName() instance.payloads = [] - instance.rest_send = RestSend(MockAnsibleModule()) + instance.rest_send = RestSend(PARAMS) instance.results = Results() + + match = r"FabricReplacedBulk\.commit:\s+" + match += r"fabric_summary must be set prior to calling commit\." + + with pytest.raises(ValueError, match=match): instance.commit() def test_fabric_replaced_bulk_00220(fabric_replaced_bulk) -> None: """ - Classes and Methods + ### Classes and Methods + - FabricReplacedBulk - commit() - Summary - - Verify `ValueError`` is raised when FabricReplacedBulk().payloads - is not set before calling FabricReplacedBulk().commit(). + ### Summary + Verify `ValueError`` is raised when ``payloads`` is not set before + calling ``commit``. """ + with does_not_raise(): + instance = fabric_replaced_bulk + instance.fabric_details = FabricDetailsByName() + instance.fabric_summary = FabricSummary() + instance.rest_send = RestSend(PARAMS) + instance.results = Results() + match = r"FabricReplacedBulk\.commit:\s+" match += r"payloads must be set prior to calling commit\." + with pytest.raises(ValueError, match=match): - instance = fabric_replaced_bulk - instance.fabric_details = FabricDetailsByName(params) - instance.fabric_summary = FabricSummary(params) - instance.rest_send = RestSend(MockAnsibleModule()) - instance.results = Results() instance.commit() def test_fabric_replaced_bulk_00230(fabric_replaced_bulk) -> None: """ - Classes and Methods + ### Classes and Methods + - FabricReplacedBulk - commit() - Summary - - Verify `ValueError`` is raised when FabricReplacedBulk().rest_send - is not set before calling FabricReplacedBulk().commit(). + ### Summary + Verify `ValueError`` is raised when ``rest_send`` is not set before + calling ``commit``. """ - match = r"FabricReplacedBulk\.commit:\s+" - match += r"rest_send must be set prior to calling commit\." - with pytest.raises(ValueError, match=match): + with does_not_raise(): instance = fabric_replaced_bulk - instance.fabric_details = FabricDetailsByName(params) - instance.fabric_summary = FabricSummary(params) + instance.fabric_details = FabricDetailsByName() + instance.fabric_summary = FabricSummary() instance.payloads = [] instance.results = Results() + + match = r"FabricReplacedBulk\.commit:\s+" + match += r"rest_send must be set prior to calling commit\." + + with pytest.raises(ValueError, match=match): instance.commit() -def test_fabric_replaced_bulk_00240(monkeypatch, fabric_replaced_bulk) -> None: +def test_fabric_replaced_bulk_00240(fabric_replaced_bulk) -> None: """ - Classes and Methods + ### Classes and Methods + - FabricReplacedCommon - __init__() - FabricReplacedBulk - __init__() - commit() - Summary + ### Summary + - Verify FabricReplacedBulk().Results() properties are set by FabricReplacedBulk().commit(). - Verify FabricReplacedBulk().rest_send.state is set to "replaced" - - Verify FabricReplacedBulk().results.action is set to "replace" + - Verify FabricReplacedBulk().results.action is set to "fabric_replace" - Verify FabricReplacedBulk().results.state is set to "replaced" - Verify FabricReplacedBulk().template_get.rest_send is set to FabricReplacedBulk().rest_send @@ -580,36 +603,34 @@ def test_fabric_replaced_bulk_00240(monkeypatch, fabric_replaced_bulk) -> None: method_name = inspect.stack()[0][3] key = f"{method_name}a" - PATCH_DCNM_SEND = "ansible_collections.cisco.dcnm.plugins." - PATCH_DCNM_SEND += "module_utils.common.rest_send.dcnm_send" - def responses(): yield responses_fabric_replaced_bulk(key) - gen = ResponseGenerator(responses()) + gen_responses = ResponseGenerator(responses()) - def mock_dcnm_send(*args, **kwargs): - item = gen.next - return item + sender = Sender() + sender.ansible_module = MockAnsibleModule() + sender.gen = gen_responses + rest_send = RestSend(PARAMS) + rest_send.unit_test = True + rest_send.timeout = 1 + rest_send.response_handler = ResponseHandler() + rest_send.sender = sender with does_not_raise(): instance = fabric_replaced_bulk - instance.rest_send = RestSend(MockAnsibleModule()) - instance.rest_send.unit_test = True - instance.rest_send.timeout = 1 - instance.fabric_details = FabricDetailsByName(params) - instance.fabric_details.rest_send = instance.rest_send - instance.fabric_summary = FabricSummary(params) - instance.fabric_summary.rest_send = instance.rest_send + instance.rest_send = rest_send + instance.fabric_details = FabricDetailsByName() + instance.fabric_details.rest_send = rest_send + instance.fabric_details.results = Results() + instance.fabric_summary = FabricSummary() + instance.fabric_summary.rest_send = rest_send instance.payloads = [] instance.results = Results() - - monkeypatch.setattr(PATCH_DCNM_SEND, mock_dcnm_send) - - with does_not_raise(): instance.commit() + assert instance.rest_send.state == "replaced" - assert instance.results.action == "replace" + assert instance.results.action == "fabric_replace" assert instance.results.state == "replaced" assert instance.template_get.rest_send == instance.rest_send assert instance._payloads_to_commit == [] @@ -622,7 +643,7 @@ def mock_dcnm_send(*args, **kwargs): assert instance.results.response[0].get("sequence_number") == 1 assert instance.results.result[0].get("sequence_number") == 1 - assert instance.results.metadata[0].get("action") == "replace" + assert instance.results.metadata[0].get("action") == "fabric_replace" assert instance.results.metadata[0].get("check_mode") is False assert instance.results.metadata[0].get("state") == "replaced" diff --git a/tests/unit/modules/dcnm/dcnm_fabric/test_fabric_summary.py b/tests/unit/modules/dcnm/dcnm_fabric/test_fabric_summary.py index ead8b2649..69c1d5080 100644 --- a/tests/unit/modules/dcnm/dcnm_fabric/test_fabric_summary.py +++ b/tests/unit/modules/dcnm/dcnm_fabric/test_fabric_summary.py @@ -32,16 +32,10 @@ import inspect import pytest -from ansible_collections.cisco.dcnm.plugins.module_utils.common.api.v1.lan_fabric.rest.control.switches.switches import \ - EpFabricSummary -from ansible_collections.cisco.dcnm.plugins.module_utils.common.conversion import \ - ConversionUtils from ansible_collections.cisco.dcnm.plugins.module_utils.common.exceptions import \ ControllerResponseError 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.tests.unit.module_utils.common.common_utils import \ ResponseGenerator from ansible_collections.cisco.dcnm.tests.unit.modules.dcnm.dcnm_fabric.utils import ( @@ -66,14 +60,14 @@ def test_fabric_summary_00010(fabric_summary) -> None: assert instance.class_name == "FabricSummary" assert instance.data is None assert instance.refreshed is False - assert isinstance(instance.ep_fabric_summary, EpFabricSummary) - assert isinstance(instance.results, Results) - assert isinstance(instance.conversion, ConversionUtils) - assert instance._properties["border_gateway_count"] == 0 - assert instance._properties["device_count"] == 0 - assert instance._properties["fabric_name"] is None - assert instance._properties["leaf_count"] == 0 - assert instance._properties["spine_count"] == 0 + assert instance.ep_fabric_summary.class_name == "EpFabricSummary" + assert instance.results.class_name == "Results" + assert instance.conversion.class_name == "ConversionUtils" + assert instance._border_gateway_count == 0 + assert instance._device_count == 0 + assert instance._fabric_name is None + assert instance._leaf_count == 0 + assert instance._spine_count == 0 def test_fabric_summary_00030(fabric_summary) -> None: diff --git a/tests/unit/modules/dcnm/dcnm_fabric/test_fabric_update_bulk.py b/tests/unit/modules/dcnm/dcnm_fabric/test_fabric_update_bulk.py index e24bf777e..98778e73d 100644 --- a/tests/unit/modules/dcnm/dcnm_fabric/test_fabric_update_bulk.py +++ b/tests/unit/modules/dcnm/dcnm_fabric/test_fabric_update_bulk.py @@ -32,64 +32,76 @@ import inspect import pytest -from ansible_collections.cisco.dcnm.plugins.module_utils.common.rest_send import \ +from ansible_collections.cisco.dcnm.plugins.module_utils.common.response_handler import \ + ResponseHandler +from ansible_collections.cisco.dcnm.plugins.module_utils.common.rest_send_v2 import \ RestSend from ansible_collections.cisco.dcnm.plugins.module_utils.common.results import \ Results -from ansible_collections.cisco.dcnm.plugins.module_utils.fabric.fabric_details import \ +from ansible_collections.cisco.dcnm.plugins.module_utils.common.sender_file import \ + Sender +from ansible_collections.cisco.dcnm.plugins.module_utils.fabric.fabric_details_v2 import \ FabricDetailsByName from ansible_collections.cisco.dcnm.plugins.module_utils.fabric.fabric_summary import \ FabricSummary from ansible_collections.cisco.dcnm.tests.unit.module_utils.common.common_utils import \ ResponseGenerator from ansible_collections.cisco.dcnm.tests.unit.modules.dcnm.dcnm_fabric.utils import ( - MockAnsibleModule, does_not_raise, fabric_update_bulk_fixture, params, + MockAnsibleModule, does_not_raise, fabric_update_bulk_fixture, payloads_fabric_update_bulk, responses_config_deploy, - responses_config_save, responses_fabric_details_by_name, + responses_config_save, responses_fabric_details_by_name_v2, responses_fabric_summary, responses_fabric_update_bulk) +PARAMS = {"state": "merged", "check_mode": False} -def test_fabric_update_bulk_00010(fabric_update_bulk) -> None: + +def test_fabric_update_bulk_00000(fabric_update_bulk) -> None: """ - Classes and Methods + ### Classes and Methods + - FabricCommon - __init__() - Test + ### Test + - Class attributes are initialized to expected values - Exception is not raised """ with does_not_raise(): instance = fabric_update_bulk - instance.fabric_details = FabricDetailsByName(params) + instance.fabric_details = FabricDetailsByName() assert instance.class_name == "FabricUpdateBulk" - assert instance.action == "update" - assert instance.state == "merged" - assert isinstance(instance.fabric_details, FabricDetailsByName) + assert instance.action == "fabric_update" + assert instance.ep_fabric_update.class_name == "EpFabricUpdate" + assert instance.fabric_details.class_name == "FabricDetailsByName" + assert instance.fabric_types.class_name == "FabricTypes" def test_fabric_update_bulk_00020(fabric_update_bulk) -> None: """ - Classes and Methods + ### Classes and Methods + - FabricCommon - __init__() - payloads setter - FabricUpdateBulk - __init__() - Summary - A valid payloads list is presented to the payloads setter + ### Summary + + A valid payloads list is presented to the payloads setter. - Test - - payloads is set to expected value - - ``ValueError`` is not raised + ### Test + + - ``payloads`` is set to expected value. + - ``ValueError`` is not raised. """ method_name = inspect.stack()[0][3] key = f"{method_name}a" with does_not_raise(): instance = fabric_update_bulk - instance.fabric_details = FabricDetailsByName(params) + instance.fabric_details = FabricDetailsByName() instance.results = Results() instance.payloads = payloads_fabric_update_bulk(key) assert instance.payloads == payloads_fabric_update_bulk(key) @@ -97,26 +109,28 @@ def test_fabric_update_bulk_00020(fabric_update_bulk) -> None: def test_fabric_update_bulk_00021(fabric_update_bulk) -> None: """ - Classes and Methods + ### Classes and Methods + - FabricCommon - __init__() - payloads setter - FabricUpdateBulk - __init__() - Summary + ### Summary ``payloads`` setter is presented with input that is not a list. - Test - - ``ValueError`` is raised because payloads is not a list - - instance.payloads retains its initial value of None + ### Test + + - ``ValueError`` is raised because payloads is not a list, + - ``payloads`` retains its initial value of None, """ match = r"FabricUpdateBulk\.payloads: " match += r"payloads must be a list of dict\." with does_not_raise(): instance = fabric_update_bulk - instance.fabric_details = FabricDetailsByName(params) + instance.fabric_details = FabricDetailsByName() instance.results = Results() with pytest.raises(ValueError, match=match): instance.payloads = "NOT_A_LIST" @@ -125,21 +139,23 @@ def test_fabric_update_bulk_00021(fabric_update_bulk) -> None: def test_fabric_update_bulk_00022(fabric_update_bulk) -> None: """ - Classes and Methods + ### Classes and Methods + - FabricCommon - __init__() - payloads setter - FabricUpdateBulk - __init__() - Summary + ### Summary ``payloads`` setter is presented with a list that contains a non-dict element. - Test + ### Test + - ``ValueError`` is raised because payloads is a list with non-dict elements - - instance.payloads retains its initial value of None + - ``payloads`` retains its initial value of None. """ match = r"FabricUpdateBulk._verify_payload:\s+" match += r"Playbook configuration for fabrics must be a dict\.\s+" @@ -147,7 +163,7 @@ def test_fabric_update_bulk_00022(fabric_update_bulk) -> None: with does_not_raise(): instance = fabric_update_bulk - instance.fabric_details = FabricDetailsByName(params) + instance.fabric_details = FabricDetailsByName() instance.results = Results() with pytest.raises(ValueError, match=match): instance.payloads = [1, 2, 3] @@ -156,28 +172,30 @@ def test_fabric_update_bulk_00022(fabric_update_bulk) -> None: def test_fabric_update_bulk_00023(fabric_update_bulk) -> None: """ - Classes and Methods + ### Classes and Methods + - FabricCommon - __init__() - payloads setter - FabricUpdateBulk - __init__() - Summary - payloads is not set prior to calling commit + ### Summary + Verify behavior when ``payloads`` is not set prior to calling commit. + + ### Test - Test - ``ValueError`` is raised because payloads is not set prior to calling commit - - instance.payloads retains its initial value of None + - ``payloads`` retains its initial value of None. """ match = r"FabricUpdateBulk\.commit: " match += r"payloads must be set prior to calling commit\." with does_not_raise(): instance = fabric_update_bulk - instance.fabric_details = FabricDetailsByName(params) - instance.fabric_summary = FabricSummary(params) + instance.fabric_details = FabricDetailsByName() + instance.fabric_summary = FabricSummary() instance.results = Results() with pytest.raises(ValueError, match=match): instance.commit() @@ -186,24 +204,28 @@ def test_fabric_update_bulk_00023(fabric_update_bulk) -> None: def test_fabric_update_bulk_00024(fabric_update_bulk) -> None: """ - Classes and Methods + ### Classes and Methods + - FabricCommon - __init__() - payloads setter - FabricUpdateBulk - __init__() - Summary - payloads is set to an empty list + ### Summary + Verify behavior when ``payloads`` is set to an empty list. + + ### Setup - Setup - - FabricUpdatebulk().payloads is set to an empty list + - ``payloads`` is set to an empty list. + + ### Test - Test - ``ValueError`` is not raised - - payloads is set to an empty list + - ``payloads`` is set to an empty list. + + ### NOTES - NOTES: - element_spec in dcnm_fabric.py.main() is configured such that AnsibleModule will raise an exception when config is not a list of dict. Hence, we do not test instance.commit() here since it @@ -222,18 +244,20 @@ def test_fabric_update_bulk_00024(fabric_update_bulk) -> None: ) def test_fabric_update_bulk_00025(fabric_update_bulk, mandatory_parameter) -> None: """ - Classes and Methods + ### Classes and Methods + - FabricUpdateCommon - __init__() - payloads setter - FabricUpdateBulk - __init__() - Summary - - Verify FabricUpdateCommon().payloads setter re-raises ``ValueError`` - raised by FabricCommon()._verify_payload() when payloads is missing - mandatory keys. - - Verify instance.payloads retains its initial value of None. + ### Summary + + - Verify ``payloads`` setter re-raises ``ValueError`` + raised by FabricCommon()._verify_payload() when ``payloads`` is + missing mandatory keys. + - Verify ``payloads`` retains its initial value of None. """ method_name = inspect.stack()[0][3] @@ -241,7 +265,7 @@ def test_fabric_update_bulk_00025(fabric_update_bulk, mandatory_parameter) -> No with does_not_raise(): instance = fabric_update_bulk - instance.fabric_details = FabricDetailsByName(params) + instance.fabric_details = FabricDetailsByName() instance.results = Results() payloads = payloads_fabric_update_bulk(key) @@ -255,9 +279,10 @@ def test_fabric_update_bulk_00025(fabric_update_bulk, mandatory_parameter) -> No assert instance.payloads is None -def test_fabric_update_bulk_00030(monkeypatch, fabric_update_bulk) -> None: +def test_fabric_update_bulk_00030(fabric_update_bulk) -> None: """ - Classes and Methods + ### Classes and Methods + - FabricCommon() - __init__() - payloads setter @@ -271,11 +296,12 @@ def test_fabric_update_bulk_00030(monkeypatch, fabric_update_bulk) -> None: - __init__() - commit() - Summary + ### Summary Verify behavior when user attempts to update a fabric and no fabrics exist on the controller. - Code Flow + ### Code Flow + - FabricUpdateBulk.payloads is set to contain one payload for a fabric (f1) that does not exist on the controller. - FabricUpdateBulk.commit() calls @@ -296,36 +322,35 @@ def test_fabric_update_bulk_00030(monkeypatch, fabric_update_bulk) -> None: method_name = inspect.stack()[0][3] key = f"{method_name}a" - PATCH_DCNM_SEND = "ansible_collections.cisco.dcnm.plugins." - PATCH_DCNM_SEND += "module_utils.common.rest_send.dcnm_send" - def responses(): - yield responses_fabric_details_by_name(key) + yield responses_fabric_details_by_name_v2(key) yield responses_fabric_update_bulk(key) - gen = ResponseGenerator(responses()) + gen_responses = ResponseGenerator(responses()) - def mock_dcnm_send(*args, **kwargs): - item = gen.next - return item + sender = Sender() + sender.ansible_module = MockAnsibleModule() + sender.gen = gen_responses + rest_send = RestSend(PARAMS) + rest_send.unit_test = True + rest_send.timeout = 1 + rest_send.response_handler = ResponseHandler() + rest_send.sender = sender with does_not_raise(): instance = fabric_update_bulk - instance.fabric_details = FabricDetailsByName(params) - instance.fabric_details.rest_send = RestSend(MockAnsibleModule()) - instance.fabric_details.rest_send.unit_test = True + instance.fabric_details = FabricDetailsByName() + instance.fabric_details.rest_send = rest_send + instance.fabric_details.results = Results() - instance.fabric_summary = FabricSummary(params) - instance.fabric_summary.rest_send = RestSend(MockAnsibleModule()) - instance.fabric_summary.rest_send.unit_test = True + instance.fabric_summary = FabricSummary() + instance.fabric_summary.rest_send = rest_send + instance.fabric_summary.results = Results() - instance.rest_send = RestSend(MockAnsibleModule()) + instance.rest_send = rest_send instance.results = Results() instance.payloads = payloads_fabric_update_bulk(key) - - monkeypatch.setattr(PATCH_DCNM_SEND, mock_dcnm_send) - with does_not_raise(): instance.commit() assert isinstance(instance.results.diff, list) @@ -339,7 +364,7 @@ def mock_dcnm_send(*args, **kwargs): assert instance.results.diff[0].get("sequence_number", None) == 1 - assert instance.results.metadata[0].get("action", None) == "update" + assert instance.results.metadata[0].get("action", None) == "fabric_update" assert instance.results.metadata[0].get("check_mode", None) is False assert instance.results.metadata[0].get("sequence_number", None) == 1 assert instance.results.metadata[0].get("state", None) == "merged" @@ -359,9 +384,10 @@ def mock_dcnm_send(*args, **kwargs): assert True not in instance.results.changed -def test_fabric_update_bulk_00031(monkeypatch, fabric_update_bulk) -> None: +def test_fabric_update_bulk_00031(fabric_update_bulk) -> None: """ - Classes and Methods + ### Classes and Methods + - FabricCommon() - __init__() - FabricDetails() @@ -378,7 +404,8 @@ def test_fabric_update_bulk_00031(monkeypatch, fabric_update_bulk) -> None: - __init__() - commit() - Summary + ### Summary + - Verify behavior when user requests to update a fabric and the fabric exists on the controller and the payload contains values that would result in changes to the fabric. @@ -389,10 +416,12 @@ def test_fabric_update_bulk_00031(monkeypatch, fabric_update_bulk) -> None: and ``int`` values. - The fabric is empty, so is updated, but not deployed/saved. - See Also + ### See Also + - test_fabric_update_bulk_00035 for case where fabric is not empty. - Code Flow + ### Code Flow + - FabricUpdateBulk.payloads is set to contain one payload for a fabric (f1) that exists on the controller. - The payload keys contain values that would result in changes to @@ -437,38 +466,37 @@ def test_fabric_update_bulk_00031(monkeypatch, fabric_update_bulk) -> None: method_name = inspect.stack()[0][3] key = f"{method_name}a" - PATCH_DCNM_SEND = "ansible_collections.cisco.dcnm.plugins." - PATCH_DCNM_SEND += "module_utils.common.rest_send.dcnm_send" - def responses(): - yield responses_fabric_details_by_name(key) + yield responses_fabric_details_by_name_v2(key) yield responses_fabric_update_bulk(key) yield responses_config_save(key) yield responses_fabric_summary(key) - gen = ResponseGenerator(responses()) + gen_responses = ResponseGenerator(responses()) - def mock_dcnm_send(*args, **kwargs): - item = gen.next - return item + sender = Sender() + sender.ansible_module = MockAnsibleModule() + sender.gen = gen_responses + rest_send = RestSend(PARAMS) + rest_send.unit_test = True + rest_send.timeout = 1 + rest_send.response_handler = ResponseHandler() + rest_send.sender = sender with does_not_raise(): instance = fabric_update_bulk - instance.fabric_details = FabricDetailsByName(params) - instance.fabric_details.rest_send = RestSend(MockAnsibleModule()) - instance.fabric_details.rest_send.unit_test = True + instance.fabric_details = FabricDetailsByName() + instance.fabric_details.rest_send = rest_send + instance.fabric_details.results = Results() - instance.fabric_summary = FabricSummary(params) - instance.fabric_summary.rest_send = RestSend(MockAnsibleModule()) - instance.fabric_summary.rest_send.unit_test = True + instance.fabric_summary = FabricSummary() + instance.fabric_summary.rest_send = rest_send + instance.fabric_summary.results = Results() - instance.rest_send = RestSend(MockAnsibleModule()) + instance.rest_send = rest_send instance.results = Results() instance.payloads = payloads_fabric_update_bulk(key) - - monkeypatch.setattr(PATCH_DCNM_SEND, mock_dcnm_send) - with does_not_raise(): instance.commit() assert isinstance(instance.results.diff, list) @@ -491,7 +519,7 @@ def mock_dcnm_send(*args, **kwargs): assert instance.results.diff[2].get("sequence_number", None) == 3 - assert instance.results.metadata[0].get("action", None) == "update" + assert instance.results.metadata[0].get("action", None) == "fabric_update" assert instance.results.metadata[0].get("check_mode", None) is False assert instance.results.metadata[0].get("sequence_number", None) == 1 assert instance.results.metadata[0].get("state", None) == "merged" @@ -540,9 +568,10 @@ def mock_dcnm_send(*args, **kwargs): assert False not in instance.results.changed -def test_fabric_update_bulk_00032(monkeypatch, fabric_update_bulk) -> None: +def test_fabric_update_bulk_00032(fabric_update_bulk) -> None: """ - Classes and Methods + ### Classes and Methods + - FabricCommon() - __init__() - FabricDetails() @@ -559,11 +588,13 @@ def test_fabric_update_bulk_00032(monkeypatch, fabric_update_bulk) -> None: - __init__() - commit() - Summary + ### Summary + - Verify behavior when user attempts to update a fabric and the fabric exists on the controller but the RestSend() RETURN_CODE is 500. - Code Flow + ### Code Flow + - FabricUpdateBulk.payloads is set to contain one payload for a fabric (f1) that exists on the controller. This payload contains an invalid parameter (``BOO``) that will cause the update to fail. @@ -591,39 +622,36 @@ def test_fabric_update_bulk_00032(monkeypatch, fabric_update_bulk) -> None: method_name = inspect.stack()[0][3] key = f"{method_name}a" - PATCH_DCNM_SEND = "ansible_collections.cisco.dcnm.plugins." - PATCH_DCNM_SEND += "module_utils.common.rest_send.dcnm_send" - def responses(): - yield responses_fabric_details_by_name(key) + yield responses_fabric_details_by_name_v2(key) yield responses_fabric_summary(key) yield responses_fabric_update_bulk(key) - gen = ResponseGenerator(responses()) + gen_responses = ResponseGenerator(responses()) - def mock_dcnm_send(*args, **kwargs): - item = gen.next - return item + sender = Sender() + sender.ansible_module = MockAnsibleModule() + sender.gen = gen_responses + rest_send = RestSend(PARAMS) + rest_send.unit_test = True + rest_send.timeout = 1 + rest_send.response_handler = ResponseHandler() + rest_send.sender = sender with does_not_raise(): instance = fabric_update_bulk - instance.fabric_details = FabricDetailsByName(params) - instance.fabric_details.rest_send = RestSend(MockAnsibleModule()) - instance.fabric_details.rest_send.unit_test = True - - instance.fabric_summary = FabricSummary(params) - instance.fabric_summary.rest_send = RestSend(MockAnsibleModule()) - instance.fabric_summary.rest_send.unit_test = True + instance.fabric_details = FabricDetailsByName() + instance.fabric_details.rest_send = rest_send + instance.fabric_details.results = Results() - instance.rest_send = RestSend(MockAnsibleModule()) + instance.fabric_summary = FabricSummary() + instance.fabric_summary.rest_send = rest_send + instance.fabric_summary.results = Results() + instance.rest_send = rest_send instance.results = Results() instance.payloads = payloads_fabric_update_bulk(key) - instance.fabric_details.rest_send.unit_test = True - instance.rest_send.unit_test = True - - monkeypatch.setattr(PATCH_DCNM_SEND, mock_dcnm_send) match = r"FabricUpdateBulk\._fabric_needs_update_for_merged_state:\s+" match += r"Invalid key:.*found in payload for fabric.*" @@ -642,7 +670,7 @@ def mock_dcnm_send(*args, **kwargs): assert instance.results.diff[0].get("sequence_number", None) == 1 - assert instance.results.metadata[0].get("action", None) == "update" + assert instance.results.metadata[0].get("action", None) == "fabric_update" assert instance.results.metadata[0].get("check_mode", None) is False assert instance.results.metadata[0].get("sequence_number", None) == 1 assert instance.results.metadata[0].get("state", None) == "merged" @@ -656,9 +684,10 @@ def mock_dcnm_send(*args, **kwargs): assert True not in instance.results.changed -def test_fabric_update_bulk_00033(monkeypatch, fabric_update_bulk) -> None: +def test_fabric_update_bulk_00033(fabric_update_bulk) -> None: """ - Classes and Methods + ### Classes and Methods + - FabricCommon() - __init__() - translate_anycast_gw_mac() @@ -680,19 +709,22 @@ def test_fabric_update_bulk_00033(monkeypatch, fabric_update_bulk) -> None: - __init__() - commit() - Summary + ### Summary + - Verify behavior when user attempts to update a fabric when the payload includes ``ANYCAST_GW_MAC``, formatted to be incompatible with the controller's expectations, and not able to be fixed by FabricUpdateCommon()._fixup_payloads_to_commit(). - Setup + ### Setup + - FabricUpdateBulk().payloads is set to contain one payload for a fabric (f1) that exists on the controller, and the payload includes ``ANYCAST_GW_MAC`` formatted to be incompatible with the controller's expectations. - Code Flow + ### Code Flow + - FabricUpdateBulk.payloads is set to contain one payload for a fabric (f1) that exists on the controller. - FabricUpdateBulk.commit() calls @@ -713,33 +745,35 @@ def test_fabric_update_bulk_00033(monkeypatch, fabric_update_bulk) -> None: PATCH_DCNM_SEND += "module_utils.common.rest_send.dcnm_send" def responses(): - yield responses_fabric_details_by_name(key) + yield responses_fabric_details_by_name_v2(key) yield responses_fabric_summary(key) - gen = ResponseGenerator(responses()) + gen_responses = ResponseGenerator(responses()) - def mock_dcnm_send(*args, **kwargs): - item = gen.next - return item + sender = Sender() + sender.ansible_module = MockAnsibleModule() + sender.gen = gen_responses + rest_send = RestSend(PARAMS) + rest_send.unit_test = True + rest_send.timeout = 1 + rest_send.response_handler = ResponseHandler() + rest_send.sender = sender with does_not_raise(): instance = fabric_update_bulk - instance.fabric_details = FabricDetailsByName(params) - instance.fabric_details.rest_send = RestSend(MockAnsibleModule()) - instance.fabric_details.rest_send.unit_test = True - - instance.fabric_summary = FabricSummary(params) - instance.fabric_summary.rest_send = RestSend(MockAnsibleModule()) - instance.fabric_summary.rest_send.unit_test = True + instance.fabric_details = FabricDetailsByName() + instance.fabric_details.rest_send = rest_send + instance.fabric_details.results = Results() - instance.rest_send = RestSend(MockAnsibleModule()) + instance.fabric_summary = FabricSummary() + instance.fabric_summary.rest_send = rest_send + instance.fabric_summary.results = Results() + instance.rest_send = rest_send instance.results = Results() instance.payloads = payloads_fabric_update_bulk(key) - instance.fabric_details.rest_send.unit_test = True - monkeypatch.setattr(PATCH_DCNM_SEND, mock_dcnm_send) match = r"FabricUpdateBulk\.translate_anycast_gw_mac: " match += r"Error translating ANYCAST_GW_MAC" with pytest.raises(ValueError, match=match): @@ -756,19 +790,19 @@ def mock_dcnm_send(*args, **kwargs): assert instance.results.diff[0].get("sequence_number", None) == 1 - assert instance.results.metadata[0].get("action", None) == "update" + assert instance.results.metadata[0].get("action", None) == "fabric_update" assert instance.results.metadata[0].get("check_mode", None) is False assert instance.results.metadata[0].get("sequence_number", None) == 1 assert instance.results.metadata[0].get("state", None) == "merged" assert True in instance.results.failed - assert False not in instance.results.failed assert False in instance.results.changed -def test_fabric_update_bulk_00034(monkeypatch, fabric_update_bulk) -> None: +def test_fabric_update_bulk_00034(fabric_update_bulk) -> None: """ - Classes and Methods + ### Classes and Methods + - FabricCommon() - __init__() - FabricDetails() @@ -785,13 +819,15 @@ def test_fabric_update_bulk_00034(monkeypatch, fabric_update_bulk) -> None: - __init__() - commit() - Summary + ### Summary + - Idempotence test. - Verify behavior when user requests to update a fabric and the fabric exists on the controller but the payload does not contain any values that would result in changes to the fabric. - Code Flow + ### Code Flow + - FabricUpdateBulk.payloads is set to contain one payload for a fabric (f1) that exists on the controller. - The payload key/values do not contain any values that would result @@ -822,37 +858,36 @@ def test_fabric_update_bulk_00034(monkeypatch, fabric_update_bulk) -> None: method_name = inspect.stack()[0][3] key = f"{method_name}a" - PATCH_DCNM_SEND = "ansible_collections.cisco.dcnm.plugins." - PATCH_DCNM_SEND += "module_utils.common.rest_send.dcnm_send" - def responses(): - yield responses_fabric_details_by_name(key) + yield responses_fabric_details_by_name_v2(key) yield responses_fabric_summary(key) yield responses_fabric_update_bulk(key) - gen = ResponseGenerator(responses()) + gen_responses = ResponseGenerator(responses()) - def mock_dcnm_send(*args, **kwargs): - item = gen.next - return item + sender = Sender() + sender.ansible_module = MockAnsibleModule() + sender.gen = gen_responses + rest_send = RestSend(PARAMS) + rest_send.unit_test = True + rest_send.timeout = 1 + rest_send.response_handler = ResponseHandler() + rest_send.sender = sender with does_not_raise(): instance = fabric_update_bulk - instance.fabric_details = FabricDetailsByName(params) - instance.fabric_details.rest_send = RestSend(MockAnsibleModule()) - instance.fabric_details.rest_send.unit_test = True + instance.fabric_details = FabricDetailsByName() + instance.fabric_details.rest_send = rest_send + instance.fabric_details.results = Results() - instance.fabric_summary = FabricSummary(params) - instance.fabric_summary.rest_send = RestSend(MockAnsibleModule()) - instance.fabric_summary.rest_send.unit_test = True + instance.fabric_summary = FabricSummary() + instance.fabric_summary.rest_send = rest_send + instance.fabric_summary.results = Results() - instance.rest_send = RestSend(MockAnsibleModule()) + instance.rest_send = rest_send instance.results = Results() instance.payloads = payloads_fabric_update_bulk(key) - - monkeypatch.setattr(PATCH_DCNM_SEND, mock_dcnm_send) - with does_not_raise(): instance.commit() assert isinstance(instance.results.diff, list) @@ -866,7 +901,7 @@ def mock_dcnm_send(*args, **kwargs): assert instance.results.diff[0].get("sequence_number", None) == 1 - assert instance.results.metadata[0].get("action", None) == "update" + assert instance.results.metadata[0].get("action", None) == "fabric_update" assert instance.results.metadata[0].get("check_mode", None) is False assert instance.results.metadata[0].get("sequence_number", None) == 1 assert instance.results.metadata[0].get("state", None) == "merged" @@ -888,9 +923,10 @@ def mock_dcnm_send(*args, **kwargs): assert True not in instance.results.changed -def test_fabric_update_bulk_00035(monkeypatch, fabric_update_bulk) -> None: +def test_fabric_update_bulk_00035(fabric_update_bulk) -> None: """ - Classes and Methods + ### Classes and Methods + - FabricCommon() - __init__() - FabricDetails() @@ -907,7 +943,8 @@ def test_fabric_update_bulk_00035(monkeypatch, fabric_update_bulk) -> None: - __init__() - commit() - Summary + ### Summary + - Verify behavior when user requests to update a fabric and the fabric exists on the controller and the payload contains values that would result in changes to the fabric. @@ -918,10 +955,12 @@ def test_fabric_update_bulk_00035(monkeypatch, fabric_update_bulk) -> None: and ``int`` values. - The fabric is not empty, so is also deployed/saved. - See Also + ### See Also + - test_fabric_update_bulk_00031 for case where fabric is empty. - Code Flow + ### Code Flow + - FabricUpdateBulk.payloads is set to contain one payload for a fabric (f1) that exists on the controller. - The payload keys contain values that would result in changes to @@ -974,40 +1013,39 @@ def test_fabric_update_bulk_00035(monkeypatch, fabric_update_bulk) -> None: method_name = inspect.stack()[0][3] key = f"{method_name}a" - PATCH_DCNM_SEND = "ansible_collections.cisco.dcnm.plugins." - PATCH_DCNM_SEND += "module_utils.common.rest_send.dcnm_send" - def responses(): - yield responses_fabric_details_by_name(key) + yield responses_fabric_details_by_name_v2(key) yield responses_fabric_update_bulk(key) yield responses_config_save(key) yield responses_fabric_summary(key) - yield responses_fabric_details_by_name(key) + yield responses_fabric_details_by_name_v2(key) yield responses_config_deploy(key) - gen = ResponseGenerator(responses()) + gen_responses = ResponseGenerator(responses()) - def mock_dcnm_send(*args, **kwargs): - item = gen.next - return item + sender = Sender() + sender.ansible_module = MockAnsibleModule() + sender.gen = gen_responses + rest_send = RestSend(PARAMS) + rest_send.unit_test = True + rest_send.timeout = 1 + rest_send.response_handler = ResponseHandler() + rest_send.sender = sender with does_not_raise(): instance = fabric_update_bulk - instance.fabric_details = FabricDetailsByName(params) - instance.fabric_details.rest_send = RestSend(MockAnsibleModule()) - instance.fabric_details.rest_send.unit_test = True + instance.fabric_details = FabricDetailsByName() + instance.fabric_details.rest_send = rest_send + instance.fabric_details.results = Results() - instance.fabric_summary = FabricSummary(params) - instance.fabric_summary.rest_send = RestSend(MockAnsibleModule()) - instance.fabric_summary.rest_send.unit_test = True + instance.fabric_summary = FabricSummary() + instance.fabric_summary.rest_send = rest_send + instance.fabric_summary.results = Results() - instance.rest_send = RestSend(MockAnsibleModule()) + instance.rest_send = rest_send instance.results = Results() instance.payloads = payloads_fabric_update_bulk(key) - - monkeypatch.setattr(PATCH_DCNM_SEND, mock_dcnm_send) - with does_not_raise(): instance.commit() assert isinstance(instance.results.diff, list) @@ -1031,7 +1069,7 @@ def mock_dcnm_send(*args, **kwargs): assert instance.results.diff[2].get("config_deploy", None) == "OK" assert instance.results.diff[2].get("FABRIC_NAME", None) == "f1" - assert instance.results.metadata[0].get("action", None) == "update" + assert instance.results.metadata[0].get("action", None) == "fabric_update" assert instance.results.metadata[1].get("action", None) == "config_save" assert instance.results.metadata[2].get("action", None) == "config_deploy" @@ -1099,9 +1137,10 @@ def mock_dcnm_send(*args, **kwargs): assert False not in instance.results.changed -def test_fabric_update_bulk_00036(monkeypatch, fabric_update_bulk) -> None: +def test_fabric_update_bulk_00036(fabric_update_bulk) -> None: """ - Classes and Methods + ### Classes and Methods + - FabricCommon() - __init__() - FabricDetails() @@ -1118,11 +1157,13 @@ def test_fabric_update_bulk_00036(monkeypatch, fabric_update_bulk) -> None: - __init__() - commit() - Summary + ### Summary + - Verify behavior when user attempts to update a fabric and the payload contains an invalid parameter. - Code Flow + ### Code Flow + - FabricUpdateBulk.payloads is set to contain one payload for a fabric (f1) that exists on the controller. This payload contains an invalid parameter (BOO). @@ -1150,39 +1191,36 @@ def test_fabric_update_bulk_00036(monkeypatch, fabric_update_bulk) -> None: method_name = inspect.stack()[0][3] key = f"{method_name}a" - PATCH_DCNM_SEND = "ansible_collections.cisco.dcnm.plugins." - PATCH_DCNM_SEND += "module_utils.common.rest_send.dcnm_send" - def responses(): - yield responses_fabric_details_by_name(key) + yield responses_fabric_details_by_name_v2(key) yield responses_fabric_summary(key) yield responses_fabric_update_bulk(key) - gen = ResponseGenerator(responses()) + gen_responses = ResponseGenerator(responses()) - def mock_dcnm_send(*args, **kwargs): - item = gen.next - return item + sender = Sender() + sender.ansible_module = MockAnsibleModule() + sender.gen = gen_responses + rest_send = RestSend(PARAMS) + rest_send.unit_test = True + rest_send.timeout = 1 + rest_send.response_handler = ResponseHandler() + rest_send.sender = sender with does_not_raise(): instance = fabric_update_bulk - instance.fabric_details = FabricDetailsByName(params) - instance.fabric_details.rest_send = RestSend(MockAnsibleModule()) - instance.fabric_details.rest_send.unit_test = True - - instance.fabric_summary = FabricSummary(params) - instance.fabric_summary.rest_send = RestSend(MockAnsibleModule()) - instance.fabric_summary.rest_send.unit_test = True + instance.fabric_details = FabricDetailsByName() + instance.fabric_details.rest_send = rest_send + instance.fabric_details.results = Results() - instance.rest_send = RestSend(MockAnsibleModule()) + instance.fabric_summary = FabricSummary() + instance.fabric_summary.rest_send = rest_send + instance.fabric_summary.results = Results() + instance.rest_send = rest_send instance.results = Results() instance.payloads = payloads_fabric_update_bulk(key) - instance.fabric_details.rest_send.unit_test = True - instance.rest_send.unit_test = True - - monkeypatch.setattr(PATCH_DCNM_SEND, mock_dcnm_send) match = r"FabricUpdateBulk\._fabric_needs_update_for_merged_state:\s+" match += r"Invalid key:.*found in payload for fabric.*" @@ -1201,7 +1239,7 @@ def mock_dcnm_send(*args, **kwargs): assert instance.results.diff[0].get("sequence_number", None) == 1 - assert instance.results.metadata[0].get("action", None) == "update" + assert instance.results.metadata[0].get("action", None) == "fabric_update" assert instance.results.metadata[0].get("check_mode", None) is False assert instance.results.metadata[0].get("sequence_number", None) == 1 assert instance.results.metadata[0].get("state", None) == "merged" @@ -1215,9 +1253,10 @@ def mock_dcnm_send(*args, **kwargs): assert True not in instance.results.changed -def test_fabric_update_bulk_00040(monkeypatch, fabric_update_bulk) -> None: +def test_fabric_update_bulk_00040(fabric_update_bulk) -> None: """ - Classes and Methods + ### Classes and Methods + - FabricCommon() - __init__() - FabricDetails() @@ -1230,7 +1269,8 @@ def test_fabric_update_bulk_00040(monkeypatch, fabric_update_bulk) -> None: - __init__() - commit() - Summary + ### Summary + - Verify behavior when user requests to update a fabric and the fabric exists on the controller and the payload contains values that would result in changes to the fabric. @@ -1242,10 +1282,12 @@ def test_fabric_update_bulk_00040(monkeypatch, fabric_update_bulk) -> None: - The fabric is saved, but FabricSummary().refresh() raises a ``ControllerResponseError`` so the fabric is not deployed. - See Also + ### See Also + - test_fabric_update_bulk_00035 for case where fabric is deployed. - Code Flow + ### Code Flow + - FabricUpdateBulk.payloads is set to contain one payload for a fabric (f1) that exists on the controller. - The payload keys contain values that would result in changes to @@ -1297,40 +1339,37 @@ def test_fabric_update_bulk_00040(monkeypatch, fabric_update_bulk) -> None: method_name = inspect.stack()[0][3] key = f"{method_name}a" - PATCH_DCNM_SEND = "ansible_collections.cisco.dcnm.plugins." - PATCH_DCNM_SEND += "module_utils.common.rest_send.dcnm_send" - def responses(): - yield responses_fabric_details_by_name(key) + yield responses_fabric_details_by_name_v2(key) yield responses_fabric_update_bulk(key) yield responses_config_save(key) yield responses_fabric_summary(key) - gen = ResponseGenerator(responses()) + gen_responses = ResponseGenerator(responses()) - def mock_dcnm_send(*args, **kwargs): - item = gen.next - return item + sender = Sender() + sender.ansible_module = MockAnsibleModule() + sender.gen = gen_responses + rest_send = RestSend(PARAMS) + rest_send.unit_test = True + rest_send.timeout = 1 + rest_send.response_handler = ResponseHandler() + rest_send.sender = sender with does_not_raise(): instance = fabric_update_bulk - instance.fabric_details = FabricDetailsByName(params) - instance.fabric_details.rest_send = RestSend(MockAnsibleModule()) - instance.fabric_details.rest_send.unit_test = True - instance.fabric_details.rest_send.timeout = 1 + instance.fabric_details = FabricDetailsByName() + instance.fabric_details.rest_send = rest_send + instance.fabric_details.results = Results() - instance.fabric_summary = FabricSummary(params) - instance.fabric_summary.rest_send = RestSend(MockAnsibleModule()) - instance.fabric_summary.rest_send.unit_test = True - instance.fabric_summary.rest_send.timeout = 1 + instance.fabric_summary = FabricSummary() + instance.fabric_summary.rest_send = rest_send + instance.fabric_summary.results = Results() - instance.rest_send = RestSend(MockAnsibleModule()) + instance.rest_send = rest_send instance.results = Results() instance.payloads = payloads_fabric_update_bulk(key) - - monkeypatch.setattr(PATCH_DCNM_SEND, mock_dcnm_send) - with does_not_raise(): instance.commit() assert isinstance(instance.results.diff, list) @@ -1351,7 +1390,7 @@ def mock_dcnm_send(*args, **kwargs): assert instance.results.diff[1].get("config_save", None) == "OK" assert instance.results.diff[1].get("FABRIC_NAME", None) == "f1" - assert instance.results.metadata[0].get("action", None) == "update" + assert instance.results.metadata[0].get("action", None) == "fabric_update" assert instance.results.metadata[1].get("action", None) == "config_save" assert instance.results.metadata[2].get("action", None) == "config_deploy" @@ -1415,28 +1454,31 @@ def mock_dcnm_send(*args, **kwargs): def test_fabric_update_bulk_00050(fabric_update_bulk) -> None: """ - Classes and Methods + ### Classes and Methods + - FabricCommon() - __init__() - FabricUpdateBulk() - __init__() - commit() - Summary + ### Summary + - Verify commit() raises ``ValueError`` if ``fabric_details`` is not set. - Setup + ### Setup + - Set everything that FabricUpdateBulk() expects to be set, prior to calling commit(), EXCEPT fabric_details. """ with does_not_raise(): instance = fabric_update_bulk - instance.fabric_summary = FabricSummary(params) - instance.fabric_summary.rest_send = RestSend(MockAnsibleModule()) + instance.fabric_summary = FabricSummary() + instance.fabric_summary.rest_send = RestSend(PARAMS) instance.fabric_summary.rest_send.unit_test = True - instance.rest_send = RestSend(MockAnsibleModule()) + instance.rest_send = RestSend(PARAMS) instance.results = Results() instance.payloads = [ { @@ -1455,28 +1497,31 @@ def test_fabric_update_bulk_00050(fabric_update_bulk) -> None: def test_fabric_update_bulk_00060(fabric_update_bulk) -> None: """ - Classes and Methods + ### Classes and Methods + - FabricCommon() - __init__() - FabricUpdateBulk() - __init__() - commit() - Summary + ### Summary + - Verify commit() raises ``ValueError`` if ``fabric_summary`` is not set. - Setup + ### Setup + - Set everything that FabricUpdateBulk() expects to be set, prior to calling commit(), EXCEPT fabric_summary. """ with does_not_raise(): instance = fabric_update_bulk - instance.fabric_details = FabricDetailsByName(params) - instance.fabric_details.rest_send = RestSend(MockAnsibleModule()) + instance.fabric_details = FabricDetailsByName() + instance.fabric_details.rest_send = RestSend(PARAMS) instance.fabric_details.rest_send.unit_test = True - instance.rest_send = RestSend(MockAnsibleModule()) + instance.rest_send = RestSend(PARAMS) instance.results = Results() instance.payloads = [ { @@ -1495,29 +1540,32 @@ def test_fabric_update_bulk_00060(fabric_update_bulk) -> None: def test_fabric_update_bulk_00070(fabric_update_bulk) -> None: """ - Classes and Methods + ### Classes and Methods + - FabricCommon() - __init__() - FabricUpdateBulk() - __init__() - commit() - Summary + ### Summary + - Verify commit() raises ``ValueError`` if ``rest_send`` is not set. - Setup + ### Setup + - Set everything that FabricUpdateBulk() expects to be set, prior to calling commit(), EXCEPT rest_send. """ with does_not_raise(): instance = fabric_update_bulk - instance.fabric_details = FabricDetailsByName(params) - instance.fabric_details.rest_send = RestSend(MockAnsibleModule()) + instance.fabric_details = FabricDetailsByName() + instance.fabric_details.rest_send = RestSend(PARAMS) instance.fabric_details.rest_send.unit_test = True - instance.fabric_summary = FabricSummary(params) - instance.fabric_summary.rest_send = RestSend(MockAnsibleModule()) + instance.fabric_summary = FabricSummary() + instance.fabric_summary.rest_send = RestSend(PARAMS) instance.fabric_summary.rest_send.unit_test = True instance.results = Results() @@ -1553,7 +1601,8 @@ def test_fabric_update_bulk_00100( value, expected_return_value, fabric_update_bulk ) -> None: """ - Classes and Methods + ### Classes and Methods + - FabricCommon() - __init__() - FabricUpdateCommon() @@ -1562,11 +1611,13 @@ def test_fabric_update_bulk_00100( - FabricUpdateBulk() - __init__() - Summary + ### Summary + - Verify _prepare_parameter_value_for_comparison() returns appropriate values. - NOTES: + ### NOTES + - Python truncates trailing zeros in float values. This presents a problem if users are expecting a float to be returned as a string with the trailing zeros intact. For example with BGP_AS ASDot notation, as shown @@ -1585,7 +1636,8 @@ def test_fabric_update_bulk_00100( def test_fabric_update_bulk_00110(monkeypatch, fabric_update_bulk) -> None: """ - Classes and Methods + ### Classes and Methods + - FabricCommon() - __init__() - _fixup_payloads_to_commit() @@ -1594,12 +1646,14 @@ def test_fabric_update_bulk_00110(monkeypatch, fabric_update_bulk) -> None: - _send_payloads() - Summary + ### Summary + - Verify FabricUpdateCommon()._send_payloads() catches and re-raises ``ValueError`` raised by FabricCommon()._fixup_payloads_to_commit() - Setup + ### Setup + - Mock FabricCommon()._fixup_payloads_to_commit() method to raise ``ValueError``. - Monkeypatch FabricCommon()._fixup_payloads_to_commit() @@ -1621,7 +1675,7 @@ def mock_fixup_payloads_to_commit() -> None: with does_not_raise(): instance = fabric_update_bulk - instance.rest_send = RestSend(MockAnsibleModule()) + instance.rest_send = RestSend(PARAMS) instance._payloads_to_commit = [ { "BGP_AS": "65001", @@ -1643,7 +1697,8 @@ def mock_fixup_payloads_to_commit() -> None: def test_fabric_update_bulk_00120(monkeypatch, fabric_update_bulk) -> None: """ - Classes and Methods + ### Classes and Methods + - FabricCommon() - __init__() - _fixup_payloads_to_commit() @@ -1652,12 +1707,14 @@ def test_fabric_update_bulk_00120(monkeypatch, fabric_update_bulk) -> None: - _send_payloads() - Summary + ### Summary + - Verify FabricUpdateCommon()._send_payloads() catches and re-raises ``ValueError`` raised by FabricCommon()._send_payload() - Setup + ### Setup + - Mock FabricCommon()._send_payload() method to raise ``ValueError``. - Monkeypatch FabricCommon()._send_payload() to the mocked method. @@ -1676,7 +1733,7 @@ def mock_send_payload(payload) -> None: with does_not_raise(): instance = fabric_update_bulk - instance.rest_send = RestSend(MockAnsibleModule()) + instance.rest_send = RestSend(PARAMS) instance._payloads_to_commit = [ { "BGP_AS": "65001", @@ -1695,7 +1752,8 @@ def mock_send_payload(payload) -> None: def test_fabric_update_bulk_00130(monkeypatch, fabric_update_bulk) -> None: """ - Classes and Methods + ### Classes and Methods + - FabricCommon() - __init__() - _fixup_payloads_to_commit() @@ -1704,12 +1762,14 @@ def test_fabric_update_bulk_00130(monkeypatch, fabric_update_bulk) -> None: - _send_payloads() - Summary + ### Summary + - Verify FabricUpdateCommon()._send_payloads() catches and re-raises ``ValueError`` raised by FabricCommon()._config_save() - Setup + ### Setup + - Mock FabricCommon()._config_save() method to raise ``ValueError``. - Monkeypatch FabricCommon()._config_save() to the mocked method. @@ -1726,30 +1786,33 @@ def mock_config_save(payload) -> None: fabric_name = payload.get("FABRIC_NAME", "unknown") raise ValueError(f"raised FabricCommon._config_save {fabric_name} exception.") - PATCH_DCNM_SEND = "ansible_collections.cisco.dcnm.plugins." - PATCH_DCNM_SEND += "module_utils.common.rest_send.dcnm_send" - def responses(): - yield responses_fabric_details_by_name(key) + yield responses_fabric_details_by_name_v2(key) yield responses_fabric_summary(key) - gen = ResponseGenerator(responses()) + gen_responses = ResponseGenerator(responses()) - def mock_dcnm_send(*args, **kwargs): - item = gen.next - return item + sender = Sender() + sender.ansible_module = MockAnsibleModule() + sender.gen = gen_responses + rest_send = RestSend(PARAMS) + rest_send.unit_test = True + rest_send.timeout = 1 + rest_send.response_handler = ResponseHandler() + rest_send.sender = sender with does_not_raise(): instance = fabric_update_bulk - instance.fabric_details = FabricDetailsByName(params) - instance.fabric_details.rest_send = RestSend(MockAnsibleModule()) - instance.fabric_details.rest_send.unit_test = True - instance.fabric_summary = FabricSummary(params) - instance.fabric_summary.rest_send = RestSend(MockAnsibleModule()) - instance.fabric_summary.rest_send.unit_test = True + instance.fabric_details = FabricDetailsByName() + instance.fabric_details.rest_send = rest_send + instance.fabric_details.results = Results() + + instance.fabric_summary = FabricSummary() + instance.fabric_summary.rest_send = rest_send + instance.fabric_summary.results = Results() - instance.rest_send = RestSend(MockAnsibleModule()) + instance.rest_send = rest_send instance.results = Results() instance._payloads_to_commit = [ { @@ -1760,7 +1823,6 @@ def mock_dcnm_send(*args, **kwargs): } ] - monkeypatch.setattr(PATCH_DCNM_SEND, mock_dcnm_send) monkeypatch.setattr(instance, "_config_save", mock_config_save) match = r"raised FabricCommon\._config_save f1 exception\." @@ -1770,7 +1832,8 @@ def mock_dcnm_send(*args, **kwargs): def test_fabric_update_bulk_00140(monkeypatch, fabric_update_bulk) -> None: """ - Classes and Methods + ### Classes and Methods + - FabricCommon() - __init__() - _fixup_payloads_to_commit() @@ -1779,12 +1842,14 @@ def test_fabric_update_bulk_00140(monkeypatch, fabric_update_bulk) -> None: - _send_payloads() - Summary + ### Summary + - Verify FabricUpdateCommon()._send_payloads() catches and re-raises ``ValueError`` raised by FabricCommon()._config_deploy() - Setup + ### Setup + - Mock FabricCommon()._config_deploy() method to raise ``ValueError``. - Monkeypatch FabricCommon()._config_deploy() to the mocked method. @@ -1802,30 +1867,33 @@ def mock_config_deploy(payload) -> None: msg = f"raised FabricCommon._config_deploy {fabric_name} exception." raise ValueError(msg) - PATCH_DCNM_SEND = "ansible_collections.cisco.dcnm.plugins." - PATCH_DCNM_SEND += "module_utils.common.rest_send.dcnm_send" - def responses(): - yield responses_fabric_details_by_name(key) + yield responses_fabric_details_by_name_v2(key) yield responses_fabric_summary(key) - gen = ResponseGenerator(responses()) + gen_responses = ResponseGenerator(responses()) - def mock_dcnm_send(*args, **kwargs): - item = gen.next - return item + sender = Sender() + sender.ansible_module = MockAnsibleModule() + sender.gen = gen_responses + rest_send = RestSend(PARAMS) + rest_send.unit_test = True + rest_send.timeout = 1 + rest_send.response_handler = ResponseHandler() + rest_send.sender = sender with does_not_raise(): instance = fabric_update_bulk - instance.fabric_details = FabricDetailsByName(params) - instance.fabric_details.rest_send = RestSend(MockAnsibleModule()) - instance.fabric_details.rest_send.unit_test = True - instance.fabric_summary = FabricSummary(params) - instance.fabric_summary.rest_send = RestSend(MockAnsibleModule()) - instance.fabric_summary.rest_send.unit_test = True + instance.fabric_details = FabricDetailsByName() + instance.fabric_details.rest_send = rest_send + instance.fabric_details.results = Results() - instance.rest_send = RestSend(MockAnsibleModule()) + instance.fabric_summary = FabricSummary() + instance.fabric_summary.rest_send = rest_send + instance.fabric_summary.results = Results() + + instance.rest_send = rest_send instance.results = Results() instance._payloads_to_commit = [ { @@ -1836,7 +1904,6 @@ def mock_dcnm_send(*args, **kwargs): } ] - monkeypatch.setattr(PATCH_DCNM_SEND, mock_dcnm_send) monkeypatch.setattr(instance, "_config_deploy", mock_config_deploy) match = r"raised FabricCommon\._config_deploy f1 exception\." @@ -1846,7 +1913,8 @@ def mock_dcnm_send(*args, **kwargs): def test_fabric_update_bulk_00150(monkeypatch, fabric_update_bulk) -> None: """ - Classes and Methods + ### Classes and Methods + - EpFabricUpdate().fabric_name setter - FabricCommon() - __init__() @@ -1855,12 +1923,14 @@ def test_fabric_update_bulk_00150(monkeypatch, fabric_update_bulk) -> None: - _send_payload() - Summary + ### Summary + - Verify FabricUpdateCommon()._send_payload() catches and re-raises ``ValueError`` raised by EpFabricUpdate().fabric_name setter. - Setup + ### Setup + - Mock EpFabricUpdate().fabric_name property to raise ``ValueError``. - Monkeypatch EpFabricUpdate().fabric_name to the mocked method. - Populate FabricUpdateCommon._payloads_to_commit with a payload diff --git a/tests/unit/modules/dcnm/dcnm_fabric/test_template_get.py b/tests/unit/modules/dcnm/dcnm_fabric/test_template_get.py index 83b907066..8efa11e95 100644 --- a/tests/unit/modules/dcnm/dcnm_fabric/test_template_get.py +++ b/tests/unit/modules/dcnm/dcnm_fabric/test_template_get.py @@ -32,8 +32,6 @@ import inspect import pytest -from ansible_collections.cisco.dcnm.plugins.module_utils.common.api.v1.configtemplate.rest.config.templates.templates import \ - EpTemplate from ansible_collections.cisco.dcnm.plugins.module_utils.common.exceptions import \ ControllerResponseError from ansible_collections.cisco.dcnm.plugins.module_utils.common.rest_send import \ @@ -47,7 +45,7 @@ template_get_fixture) -def test_template_get_00010(template_get) -> None: +def test_template_get_00000(template_get) -> None: """ Classes and Methods - TemplateGet @@ -60,15 +58,15 @@ def test_template_get_00010(template_get) -> None: with does_not_raise(): instance = template_get assert instance.class_name == "TemplateGet" - assert isinstance(instance.ep_template, EpTemplate) + assert instance.ep_template.class_name == "EpTemplate" assert instance.response == [] assert instance.response_current == {} assert instance.result == [] assert instance.result_current == {} - assert instance._properties["template_name"] is None - assert instance._properties["template"] is None - assert instance._properties["rest_send"] is None - assert instance._properties["results"] is None + assert instance.template_name is None + assert instance.template is None + assert instance.rest_send is None + assert instance.results is None MATCH_00020 = r"TemplateGet\.rest_send: " diff --git a/tests/unit/modules/dcnm/dcnm_fabric/test_template_get_all.py b/tests/unit/modules/dcnm/dcnm_fabric/test_template_get_all.py index aa3cf96f2..e97c99558 100644 --- a/tests/unit/modules/dcnm/dcnm_fabric/test_template_get_all.py +++ b/tests/unit/modules/dcnm/dcnm_fabric/test_template_get_all.py @@ -32,8 +32,6 @@ import inspect import pytest -from ansible_collections.cisco.dcnm.plugins.module_utils.common.api.v1.configtemplate.rest.config.templates.templates import \ - EpTemplates from ansible_collections.cisco.dcnm.plugins.module_utils.common.exceptions import \ ControllerResponseError from ansible_collections.cisco.dcnm.plugins.module_utils.common.rest_send import \ @@ -47,7 +45,7 @@ template_get_all_fixture) -def test_template_get_all_00010(template_get_all) -> None: +def test_template_get_all_00000(template_get_all) -> None: """ Classes and Methods - TemplateGetAll @@ -60,14 +58,14 @@ def test_template_get_all_00010(template_get_all) -> None: with does_not_raise(): instance = template_get_all assert instance.class_name == "TemplateGetAll" - assert isinstance(instance.ep_templates, EpTemplates) + assert instance.ep_templates.class_name == "EpTemplates" assert instance.response == [] assert instance.response_current == {} assert instance.result == [] assert instance.result_current == {} - assert instance._properties["templates"] is None - assert instance._properties["rest_send"] is None - assert instance._properties["results"] is None + assert instance._templates is None + assert instance._rest_send is None + assert instance._results is None MATCH_00020 = r"TemplateGetAll\.rest_send: " diff --git a/tests/unit/modules/dcnm/dcnm_fabric/utils.py b/tests/unit/modules/dcnm/dcnm_fabric/utils.py index 20a52ea6a..a73c41618 100644 --- a/tests/unit/modules/dcnm/dcnm_fabric/utils.py +++ b/tests/unit/modules/dcnm/dcnm_fabric/utils.py @@ -18,7 +18,6 @@ from contextlib import contextmanager -from typing import Any, Dict import pytest from ansible_collections.ansible.netcommon.tests.unit.modules.utils import \ @@ -35,8 +34,6 @@ FabricCreate, FabricCreateBulk, FabricCreateCommon) from ansible_collections.cisco.dcnm.plugins.module_utils.fabric.delete import \ FabricDelete -from ansible_collections.cisco.dcnm.plugins.module_utils.fabric.fabric_details import ( - FabricDetails, FabricDetailsByName, FabricDetailsByNvPair) from ansible_collections.cisco.dcnm.plugins.module_utils.fabric.fabric_details_v2 import \ FabricDetails as FabricDetailsV2 from ansible_collections.cisco.dcnm.plugins.module_utils.fabric.fabric_details_v2 import \ @@ -109,7 +106,7 @@ def fail_json(msg, **kwargs) -> AnsibleFailJson: """ raise AnsibleFailJson(msg, kwargs) - def public_method_for_pylint(self) -> Any: + def public_method_for_pylint(self): """ Add one public method to appease pylint """ @@ -122,10 +119,9 @@ def public_method_for_pylint(self) -> Any: @pytest.fixture(name="fabric_common") def fabric_common_fixture(): """ - return instance of FabricCommon() + Return FabricCommon() instance. """ - instance = MockAnsibleModule() - return FabricCommon(instance.params) + return FabricCommon() @pytest.fixture(name="fabric_config_deploy") @@ -133,8 +129,7 @@ def fabric_config_deploy_fixture(): """ return instance of FabricConfigDeploy() """ - instance = MockAnsibleModule() - return FabricConfigDeploy(instance.params) + return FabricConfigDeploy() @pytest.fixture(name="fabric_config_save") @@ -142,143 +137,93 @@ def fabric_config_save_fixture(): """ return instance of FabricConfigSave() """ - instance = MockAnsibleModule() - return FabricConfigSave(instance.params) + return FabricConfigSave() @pytest.fixture(name="fabric_create") def fabric_create_fixture(): """ - mock FabricCreate + Return FabricCreate() instance. """ - instance = MockAnsibleModule() - instance.state = "merged" - return FabricCreate(instance.params) + return FabricCreate() @pytest.fixture(name="fabric_create_bulk") def fabric_create_bulk_fixture(): """ - mock FabricCreateBulk + Return FabricCreateBulk() instance. """ - instance = MockAnsibleModule() - instance.state = "merged" - return FabricCreateBulk(instance.params) + return FabricCreateBulk() @pytest.fixture(name="fabric_create_common") def fabric_create_common_fixture(): """ - mock FabricCreateCommon + Return FabricCreateCommon() instance. """ - instance = MockAnsibleModule() - instance.state = "merged" - return FabricCreateCommon(instance.params) + return FabricCreateCommon() @pytest.fixture(name="fabric_delete") def fabric_delete_fixture(): """ - mock FabricDelete + Return FabricDelete() instance. """ - instance = MockAnsibleModule() - instance.state = "deleted" - return FabricDelete(instance.params) - - -@pytest.fixture(name="fabric_details") -def fabric_details_fixture(): - """ - mock FabricDetails - """ - instance = MockAnsibleModule() - instance.state = "merged" - return FabricDetails(instance.params) + return FabricDelete() @pytest.fixture(name="fabric_details_v2") def fabric_details_v2_fixture(): """ - mock FabricDetails() v2 + Return FabricDetails() v2 instance """ - return FabricDetailsV2(params) - - -@pytest.fixture(name="fabric_details_by_name") -def fabric_details_by_name_fixture(): - """ - mock FabricDetailsByName - """ - instance = MockAnsibleModule() - instance.state = "merged" - return FabricDetailsByName(instance.params) + return FabricDetailsV2() @pytest.fixture(name="fabric_details_by_name_v2") def fabric_details_by_name_v2_fixture(): """ - mock FabricDetailsByName version 2 + Return FabricDetailsByName version 2 instance """ - instance = MockAnsibleModule() - instance.state = "query" - instance.check_mode = False - return FabricDetailsByNameV2(instance.params) - - -@pytest.fixture(name="fabric_details_by_nv_pair") -def fabric_details_by_nv_pair_fixture(): - """ - mock FabricDetailsByNvPair - """ - instance = MockAnsibleModule() - instance.state = "merged" - return FabricDetailsByNvPair(instance.params) + return FabricDetailsByNameV2() @pytest.fixture(name="fabric_details_by_nv_pair_v2") def fabric_details_by_nv_pair_v2_fixture(): """ - mock FabricDetailsByNvPair version 2 + Return FabricDetailsByNvPair version 2 instance """ - instance = MockAnsibleModule() - instance.state = "merged" - return FabricDetailsByNvPairV2(instance.params) + return FabricDetailsByNvPairV2() @pytest.fixture(name="fabric_query") def fabric_query_fixture(): """ - mock FabricQuery + Return FabricQuery() instance. """ - instance = MockAnsibleModule() - instance.state = "query" - return FabricQuery(instance.params) + return FabricQuery() @pytest.fixture(name="fabric_replaced_bulk") def fabric_replaced_bulk_fixture(): """ - mock FabricReplacedBulk + Return FabricReplacedBulk() instance. """ - instance = MockAnsibleModule() - instance.state = "replaced" - return FabricReplacedBulk(instance.params) + return FabricReplacedBulk() @pytest.fixture(name="fabric_summary") def fabric_summary_fixture(): """ - mock FabricSummary + Return FabricSummary() instance. """ - instance = MockAnsibleModule() - instance.state = "merged" - return FabricSummary(instance.params) + return FabricSummary() @pytest.fixture(name="fabric_types") def fabric_types_fixture(): """ - mock FabricTypes + Return FabricTypes() instance. """ return FabricTypes() @@ -286,17 +231,15 @@ def fabric_types_fixture(): @pytest.fixture(name="fabric_update_bulk") def fabric_update_bulk_fixture(): """ - mock FabricUpdateBulk + Return FabricUpdateBulk() instance. """ - instance = MockAnsibleModule() - instance.state = "merged" - return FabricUpdateBulk(instance.params) + return FabricUpdateBulk() @pytest.fixture(name="response_handler") def response_handler_fixture(): """ - mock ResponseHandler() + Return ResponseHandler() instance. """ return ResponseHandler() @@ -304,7 +247,7 @@ def response_handler_fixture(): @pytest.fixture(name="template_get") def template_get_fixture(): """ - mock TemplateGet + Return TemplateGet() instance. """ return TemplateGet() @@ -312,7 +255,7 @@ def template_get_fixture(): @pytest.fixture(name="template_get_all") def template_get_all_fixture(): """ - mock TemplateGetAll + Return TemplateGetAll() instance. """ return TemplateGetAll() @@ -325,7 +268,7 @@ def does_not_raise(): yield -def nv_pairs_verify_playbook_params(key: str) -> Dict[str, str]: +def nv_pairs_verify_playbook_params(key: str) -> dict[str, str]: """ Return fabric nvPairs for VerifyPlaybookParams """ @@ -335,7 +278,7 @@ def nv_pairs_verify_playbook_params(key: str) -> Dict[str, str]: return data -def payloads_fabric_common(key: str) -> Dict[str, str]: +def payloads_fabric_common(key: str) -> dict[str, str]: """ Return payloads for FabricCommon """ @@ -345,7 +288,7 @@ def payloads_fabric_common(key: str) -> Dict[str, str]: return data -def payloads_fabric_create(key: str) -> Dict[str, str]: +def payloads_fabric_create(key: str) -> dict[str, str]: """ Return payloads for FabricCreate """ @@ -355,7 +298,7 @@ def payloads_fabric_create(key: str) -> Dict[str, str]: return data -def payloads_fabric_create_bulk(key: str) -> Dict[str, str]: +def payloads_fabric_create_bulk(key: str) -> dict[str, str]: """ Return payloads for FabricCreateBulk """ @@ -365,7 +308,7 @@ def payloads_fabric_create_bulk(key: str) -> Dict[str, str]: return data -def payloads_fabric_create_common(key: str) -> Dict[str, str]: +def payloads_fabric_create_common(key: str) -> dict[str, str]: """ Return payloads for FabricCreateCommon """ @@ -375,7 +318,7 @@ def payloads_fabric_create_common(key: str) -> Dict[str, str]: return data -def payloads_fabric_replaced_bulk(key: str) -> Dict[str, str]: +def payloads_fabric_replaced_bulk(key: str) -> dict[str, str]: """ Return payloads for FabricReplacedBulk """ @@ -385,7 +328,7 @@ def payloads_fabric_replaced_bulk(key: str) -> Dict[str, str]: return data -def payloads_fabric_update_bulk(key: str) -> Dict[str, str]: +def payloads_fabric_update_bulk(key: str) -> dict[str, str]: """ Return payloads for FabricUpdateBulk """ @@ -395,7 +338,7 @@ def payloads_fabric_update_bulk(key: str) -> Dict[str, str]: return data -def payloads_verify_playbook_params(key: str) -> Dict[str, str]: +def payloads_verify_playbook_params(key: str) -> dict[str, str]: """ Return payloads for VerifyPlaybookParams """ @@ -405,7 +348,7 @@ def payloads_verify_playbook_params(key: str) -> Dict[str, str]: return data -def responses_config_deploy(key: str) -> Dict[str, str]: +def responses_config_deploy(key: str) -> dict[str, str]: """ Return responses for config_deploy requests """ @@ -415,7 +358,7 @@ def responses_config_deploy(key: str) -> Dict[str, str]: return data -def responses_config_save(key: str) -> Dict[str, str]: +def responses_config_save(key: str) -> dict[str, str]: """ Return responses for config_save requests """ @@ -425,27 +368,27 @@ def responses_config_save(key: str) -> Dict[str, str]: return data -def responses_fabric_config_deploy(key: str) -> Dict[str, str]: +def responses_ep_fabric_config_deploy(key: str) -> dict[str, str]: """ - Return responses for FabricConfigDeploy() class + Return responses for EpFabricConfigDeploy() endpoint """ - data_file = "responses_FabricConfigDeploy" + data_file = "responses_ep_fabric_config_deploy" data = load_fixture(data_file).get(key) print(f"{data_file}: {key} : {data}") return data -def responses_fabric_config_save(key: str) -> Dict[str, str]: +def responses_ep_fabric_config_save(key: str) -> dict[str, str]: """ - Return responses for FabricConfigSave() class + Return responses for EpFabricConfigSave() endpoint. """ - data_file = "responses_FabricConfigSave" + data_file = "responses_ep_fabric_config_save" data = load_fixture(data_file).get(key) print(f"{data_file}: {key} : {data}") return data -def responses_fabric_common(key: str) -> Dict[str, str]: +def responses_fabric_common(key: str) -> dict[str, str]: """ Return responses for FabricCommon """ @@ -455,7 +398,7 @@ def responses_fabric_common(key: str) -> Dict[str, str]: return data -def responses_fabric_create(key: str) -> Dict[str, str]: +def responses_fabric_create(key: str) -> dict[str, str]: """ Return responses for FabricCreate """ @@ -465,7 +408,7 @@ def responses_fabric_create(key: str) -> Dict[str, str]: return data -def responses_fabric_create_bulk(key: str) -> Dict[str, str]: +def responses_fabric_create_bulk(key: str) -> dict[str, str]: """ Return responses for FabricCreateBulk """ @@ -475,7 +418,7 @@ def responses_fabric_create_bulk(key: str) -> Dict[str, str]: return data -def responses_fabric_delete(key: str) -> Dict[str, str]: +def responses_fabric_delete(key: str) -> dict[str, str]: """ Return responses for FabricDelete """ @@ -485,17 +428,7 @@ def responses_fabric_delete(key: str) -> Dict[str, str]: return data -def responses_fabric_details(key: str) -> Dict[str, str]: - """ - Return responses for FabricDetails - """ - data_file = "responses_FabricDetails" - data = load_fixture(data_file).get(key) - print(f"{data_file}: {key} : {data}") - return data - - -def responses_fabric_details_v2(key: str) -> Dict[str, str]: +def responses_fabric_details_v2(key: str) -> dict[str, str]: """ Return responses for FabricDetails version 2 """ @@ -505,17 +438,7 @@ def responses_fabric_details_v2(key: str) -> Dict[str, str]: return data -def responses_fabric_details_by_name(key: str) -> Dict[str, str]: - """ - Return responses for FabricDetailsByName - """ - data_file = "responses_FabricDetailsByName" - data = load_fixture(data_file).get(key) - print(f"{data_file}: {key} : {data}") - return data - - -def responses_fabric_details_by_name_v2(key: str) -> Dict[str, str]: +def responses_fabric_details_by_name_v2(key: str) -> dict[str, str]: """ Return responses for FabricDetailsByName version 2 """ @@ -525,17 +448,7 @@ def responses_fabric_details_by_name_v2(key: str) -> Dict[str, str]: return data -def responses_fabric_details_by_nv_pair(key: str) -> Dict[str, str]: - """ - Return responses for FabricDetailsByNvPair - """ - data_file = "responses_FabricDetailsByNvPair" - data = load_fixture(data_file).get(key) - print(f"{data_file}: {key} : {data}") - return data - - -def responses_fabric_details_by_nv_pair_v2(key: str) -> Dict[str, str]: +def responses_fabric_details_by_nv_pair_v2(key: str) -> dict[str, str]: """ Return responses for FabricDetailsByNvPair version 2 """ @@ -545,7 +458,7 @@ def responses_fabric_details_by_nv_pair_v2(key: str) -> Dict[str, str]: return data -def responses_fabric_query(key: str) -> Dict[str, str]: +def responses_fabric_query(key: str) -> dict[str, str]: """ Return responses for FabricQuery """ @@ -555,7 +468,7 @@ def responses_fabric_query(key: str) -> Dict[str, str]: return data -def responses_fabric_replaced_bulk(key: str) -> Dict[str, str]: +def responses_fabric_replaced_bulk(key: str) -> dict[str, str]: """ Return responses for FabricReplacedBulk """ @@ -565,7 +478,7 @@ def responses_fabric_replaced_bulk(key: str) -> Dict[str, str]: return data -def responses_fabric_summary(key: str) -> Dict[str, str]: +def responses_fabric_summary(key: str) -> dict[str, str]: """ Return responses for FabricSummary """ @@ -575,7 +488,7 @@ def responses_fabric_summary(key: str) -> Dict[str, str]: return data -def responses_fabric_update_bulk(key: str) -> Dict[str, str]: +def responses_fabric_update_bulk(key: str) -> dict[str, str]: """ Return responses for FabricUpdateBulk """ @@ -585,7 +498,7 @@ def responses_fabric_update_bulk(key: str) -> Dict[str, str]: return data -def responses_response_handler(key: str) -> Dict[str, str]: +def responses_response_handler(key: str) -> dict[str, str]: """ Return responses for ResponseHandler """ @@ -595,7 +508,7 @@ def responses_response_handler(key: str) -> Dict[str, str]: return data -def responses_template_get(key: str) -> Dict[str, str]: +def responses_template_get(key: str) -> dict[str, str]: """ Return responses for TemplateGet """ @@ -605,7 +518,7 @@ def responses_template_get(key: str) -> Dict[str, str]: return data -def responses_template_get_all(key: str) -> Dict[str, str]: +def responses_template_get_all(key: str) -> dict[str, str]: """ Return responses for TemplateGetAll """ @@ -615,7 +528,7 @@ def responses_template_get_all(key: str) -> Dict[str, str]: return data -def results_fabric_details(key: str) -> Dict[str, str]: +def results_fabric_details(key: str) -> dict[str, str]: """ Return results for FabricDetails """ @@ -625,7 +538,7 @@ def results_fabric_details(key: str) -> Dict[str, str]: return data -def results_fabric_common(key: str) -> Dict[str, str]: +def results_fabric_common(key: str) -> dict[str, str]: """ Return results for FabricCommon """ @@ -635,7 +548,7 @@ def results_fabric_common(key: str) -> Dict[str, str]: return data -def results_fabric_create_bulk(key: str) -> Dict[str, str]: +def results_fabric_create_bulk(key: str) -> dict[str, str]: """ Return results for FabricCreateBulk """ @@ -645,7 +558,7 @@ def results_fabric_create_bulk(key: str) -> Dict[str, str]: return data -def results_fabric_delete(key: str) -> Dict[str, str]: +def results_fabric_delete(key: str) -> dict[str, str]: """ Return results for FabricDelete """ @@ -655,7 +568,7 @@ def results_fabric_delete(key: str) -> Dict[str, str]: return data -def rest_send_response_current(key: str) -> Dict[str, str]: +def rest_send_response_current(key: str) -> dict[str, str]: """ Mocked return values for RestSend().response_current property """ @@ -665,7 +578,7 @@ def rest_send_response_current(key: str) -> Dict[str, str]: return data -def rest_send_result_current(key: str) -> Dict[str, str]: +def rest_send_result_current(key: str) -> dict[str, str]: """ Mocked return values for RestSend().result_current property """ @@ -675,7 +588,7 @@ def rest_send_result_current(key: str) -> Dict[str, str]: return data -def templates_param_info(key: str) -> Dict[str, str]: +def templates_param_info(key: str) -> dict[str, str]: """ Return fabric templates for ParamInfo """ @@ -684,7 +597,7 @@ def templates_param_info(key: str) -> Dict[str, str]: return data -def templates_ruleset(key: str) -> Dict[str, str]: +def templates_ruleset(key: str) -> dict[str, str]: """ Return fabric templates for RuleSet """ @@ -693,7 +606,7 @@ def templates_ruleset(key: str) -> Dict[str, str]: return data -def templates_verify_playbook_params(key: str) -> Dict[str, str]: +def templates_verify_playbook_params(key: str) -> dict[str, str]: """ Return fabric templates for VerifyPlaybookParams """ 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 index 5be2e4ba6..b0f335727 100644 --- 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 @@ -223,7 +223,6 @@ def payloads(): instance = image_policy_create instance.results = Results() instance.rest_send = rest_send - instance.params = params instance.payload = gen_payloads.next instance.commit() assert instance._payloads_to_commit == [] diff --git a/tests/unit/modules/dcnm/dcnm_image_policy/utils.py b/tests/unit/modules/dcnm/dcnm_image_policy/utils.py index 03743d685..0ec8b0da0 100644 --- a/tests/unit/modules/dcnm/dcnm_image_policy/utils.py +++ b/tests/unit/modules/dcnm/dcnm_image_policy/utils.py @@ -18,7 +18,6 @@ from contextlib import contextmanager -from typing import Any, Dict import pytest from ansible_collections.ansible.netcommon.tests.unit.modules.utils import \ @@ -102,7 +101,7 @@ def next(self): """ return next(self.gen) - def public_method_for_pylint(self) -> Any: + def public_method_for_pylint(self): """ Add one public method to appease pylint """ @@ -150,7 +149,7 @@ def fail_json(msg, **kwargs) -> AnsibleFailJson: """ raise AnsibleFailJson(msg, kwargs) - def public_method_for_pylint(self) -> Any: + def public_method_for_pylint(self): """ Add one public method to appease pylint """ @@ -265,7 +264,7 @@ def does_not_raise(): yield -def data_payload(key: str) -> Dict[str, str]: +def data_payload(key: str) -> dict[str, str]: """ Return data for unit tests of the Payload() class """ @@ -275,7 +274,7 @@ def data_payload(key: str) -> Dict[str, str]: return data -def configs_config2payload(key: str) -> Dict[str, str]: +def configs_config2payload(key: str) -> dict[str, str]: """ Return configs for Config2Payload """ @@ -285,7 +284,7 @@ def configs_config2payload(key: str) -> Dict[str, str]: return data -def configs_payload2config(key: str) -> Dict[str, str]: +def configs_payload2config(key: str) -> dict[str, str]: """ Return configs for Payload2Config """ @@ -295,7 +294,7 @@ def configs_payload2config(key: str) -> Dict[str, str]: return data -def payloads_config2payload(key: str) -> Dict[str, str]: +def payloads_config2payload(key: str) -> dict[str, str]: """ Return payloads for Config2Payload """ @@ -305,7 +304,7 @@ def payloads_config2payload(key: str) -> Dict[str, str]: return data -def payloads_payload2config(key: str) -> Dict[str, str]: +def payloads_payload2config(key: str) -> dict[str, str]: """ Return payloads for Payload2Config """ @@ -315,7 +314,7 @@ def payloads_payload2config(key: str) -> Dict[str, str]: return data -def payloads_image_policy_create(key: str) -> Dict[str, str]: +def payloads_image_policy_create(key: str) -> dict[str, str]: """ Return payloads for ImagePolicyCreate """ @@ -325,7 +324,7 @@ def payloads_image_policy_create(key: str) -> Dict[str, str]: return data -def payloads_image_policy_create_bulk(key: str) -> Dict[str, str]: +def payloads_image_policy_create_bulk(key: str) -> dict[str, str]: """ Return payloads for ImagePolicyCreateBulk """ @@ -335,7 +334,7 @@ def payloads_image_policy_create_bulk(key: str) -> Dict[str, str]: return data -def payloads_image_policy_replace_bulk(key: str) -> Dict[str, str]: +def payloads_image_policy_replace_bulk(key: str) -> dict[str, str]: """ Return payloads for ImagePolicyReplaceBulk """ @@ -345,7 +344,7 @@ def payloads_image_policy_replace_bulk(key: str) -> Dict[str, str]: return data -def payloads_image_policy_update(key: str) -> Dict[str, str]: +def payloads_image_policy_update(key: str) -> dict[str, str]: """ Return payloads for ImagePolicyUpdate """ @@ -355,7 +354,7 @@ def payloads_image_policy_update(key: str) -> Dict[str, str]: return data -def payloads_image_policy_update_bulk(key: str) -> Dict[str, str]: +def payloads_image_policy_update_bulk(key: str) -> dict[str, str]: """ Return payloads for ImagePolicyUpdateBulk """ @@ -365,7 +364,7 @@ def payloads_image_policy_update_bulk(key: str) -> Dict[str, str]: return data -def responses_ep_policies(key: str) -> Dict[str, str]: +def responses_ep_policies(key: str) -> dict[str, str]: """ Return responses for EpPolicies() endpoint """ @@ -375,7 +374,7 @@ def responses_ep_policies(key: str) -> Dict[str, str]: return data -def responses_ep_policy_create(key: str) -> Dict[str, str]: +def responses_ep_policy_create(key: str) -> dict[str, str]: """ Return responses for EpPolicyCreate() endpoint """ @@ -385,7 +384,7 @@ def responses_ep_policy_create(key: str) -> Dict[str, str]: return data -def responses_ep_policy_delete(key: str) -> Dict[str, str]: +def responses_ep_policy_delete(key: str) -> dict[str, str]: """ Return responses for EpPolicyDelete() endpoint """ @@ -395,7 +394,7 @@ def responses_ep_policy_delete(key: str) -> Dict[str, str]: return data -def responses_ep_policy_edit(key: str) -> Dict[str, str]: +def responses_ep_policy_edit(key: str) -> dict[str, str]: """ Return responses for EpPolicyEdit() endpoint """ @@ -405,7 +404,7 @@ def responses_ep_policy_edit(key: str) -> Dict[str, str]: return data -def responses_image_policy_create(key: str) -> Dict[str, str]: +def responses_image_policy_create(key: str) -> dict[str, str]: """ Return responses for ImagePolicyCreate """ @@ -415,7 +414,7 @@ def responses_image_policy_create(key: str) -> Dict[str, str]: return data -def responses_image_policy_create_bulk(key: str) -> Dict[str, str]: +def responses_image_policy_create_bulk(key: str) -> dict[str, str]: """ Return responses for ImagePolicyCreateBulk """ @@ -425,7 +424,7 @@ def responses_image_policy_create_bulk(key: str) -> Dict[str, str]: return data -def responses_image_policy_delete(key: str) -> Dict[str, str]: +def responses_image_policy_delete(key: str) -> dict[str, str]: """ Return responses for ImagePolicyDelete """ @@ -435,7 +434,7 @@ def responses_image_policy_delete(key: str) -> Dict[str, str]: return data -def responses_image_policy_replace_bulk(key: str) -> Dict[str, str]: +def responses_image_policy_replace_bulk(key: str) -> dict[str, str]: """ Return responses for ImagePolicyReplaceBulk """ @@ -445,7 +444,7 @@ def responses_image_policy_replace_bulk(key: str) -> Dict[str, str]: return data -def responses_image_policy_update(key: str) -> Dict[str, str]: +def responses_image_policy_update(key: str) -> dict[str, str]: """ Return responses for ImagePolicyUpdate """ @@ -455,7 +454,7 @@ def responses_image_policy_update(key: str) -> Dict[str, str]: return data -def responses_image_policy_update_bulk(key: str) -> Dict[str, str]: +def responses_image_policy_update_bulk(key: str) -> dict[str, str]: """ Return responses for ImagePolicyUpdateBulk """ @@ -465,7 +464,7 @@ def responses_image_policy_update_bulk(key: str) -> Dict[str, str]: return data -def results_image_policy_create_bulk(key: str) -> Dict[str, str]: +def results_image_policy_create_bulk(key: str) -> dict[str, str]: """ Return results for ImagePolicyCreateBulk """ @@ -475,7 +474,7 @@ def results_image_policy_create_bulk(key: str) -> Dict[str, str]: return data -def results_image_policy_delete(key: str) -> Dict[str, str]: +def results_image_policy_delete(key: str) -> dict[str, str]: """ Return results for ImagePolicyDelete """ @@ -485,7 +484,7 @@ def results_image_policy_delete(key: str) -> Dict[str, str]: return data -def results_image_policy_replace_bulk(key: str) -> Dict[str, str]: +def results_image_policy_replace_bulk(key: str) -> dict[str, str]: """ Return results for ImagePolicyReplaceBulk """ @@ -495,7 +494,7 @@ def results_image_policy_replace_bulk(key: str) -> Dict[str, str]: return data -def results_image_policy_task_result(key: str) -> Dict[str, str]: +def results_image_policy_task_result(key: str) -> dict[str, str]: """ Return results for ImagePolicyTaskResult """ @@ -505,7 +504,7 @@ def results_image_policy_task_result(key: str) -> Dict[str, str]: return data -def results_image_policy_update(key: str) -> Dict[str, str]: +def results_image_policy_update(key: str) -> dict[str, str]: """ Return results for ImagePolicyUpdate """ @@ -515,7 +514,7 @@ def results_image_policy_update(key: str) -> Dict[str, str]: return data -def results_image_policy_update_bulk(key: str) -> Dict[str, str]: +def results_image_policy_update_bulk(key: str) -> dict[str, str]: """ Return results for ImagePolicyUpdateBulk """ @@ -525,7 +524,7 @@ def results_image_policy_update_bulk(key: str) -> Dict[str, str]: return data -def image_policies_all_policies(key: str) -> Dict[str, str]: +def image_policies_all_policies(key: str) -> dict[str, str]: """ Return mocked return values for ImagePolicies().all_policies property """ @@ -535,7 +534,7 @@ def image_policies_all_policies(key: str) -> Dict[str, str]: return data -def rest_send_response_current(key: str) -> Dict[str, str]: +def rest_send_response_current(key: str) -> dict[str, str]: """ Mocked return values for RestSend().response_current property """ @@ -545,7 +544,7 @@ def rest_send_response_current(key: str) -> Dict[str, str]: return data -def rest_send_result_current(key: str) -> Dict[str, str]: +def rest_send_result_current(key: str) -> dict[str, str]: """ Mocked return values for RestSend().result_current property """ diff --git a/tests/unit/modules/dcnm/dcnm_image_upgrade/fixtures/devices_image_upgrade.json b/tests/unit/modules/dcnm/dcnm_image_upgrade/fixtures/devices_image_upgrade.json new file mode 100644 index 000000000..52a28950d --- /dev/null +++ b/tests/unit/modules/dcnm/dcnm_image_upgrade/fixtures/devices_image_upgrade.json @@ -0,0 +1,338 @@ +{ + "test_image_upgrade_01010a": [ + { + "TEST_NOTES": [ + "upgrade.nxos invalid value FOO" + ], + "policy": "KR5M", + "stage": true, + "upgrade": {"nxos": "FOO", "epld": true}, + "options": { + "nxos": {"mode": "disruptive", "bios_force": true}, + "package": {"install": false, "uninstall": false}, + "reboot": {"config_reload": false, "write_erase": false} + }, + "validate": true, + "ip_address": "172.22.150.102", + "policy_changed": false + } + ], + "test_image_upgrade_01020a": [ + { + "TEST_NOTES": [ + "Non-default values for several options" + ], + "policy": "KR5M", + "reboot": false, + "stage": true, + "upgrade": {"nxos": false, "epld": true}, + "options": { + "nxos": {"mode": "disruptive", "bios_force": true}, + "package": {"install": true, "uninstall": false}, + "epld": {"module": 1, "golden": true}, + "reboot": {"config_reload": true, "write_erase": false} + }, + "validate": true, + "ip_address": "172.22.150.102", + "policy_changed": false + } + ], + "test_image_upgrade_01030a": [ + { + "TEST_NOTES": [ + "Default values explicitely set for several options" + ], + "policy": "NR3F", + "reboot": false, + "stage": true, + "upgrade": {"nxos": true, "epld": true}, + "options": { + "nxos": {"mode": "disruptive", "bios_force": false}, + "package": {"install": true, "uninstall": false}, + "epld": {"module": "ALL", "golden": false}, + "reboot": {"config_reload": false, "write_erase": false} + }, + "validate": true, + "ip_address": "172.22.150.102", + "policy_changed": true + } + ], + "test_image_upgrade_01040a": [ + { + "TEST_NOTES": [ + "nxos.mode is invalid" + ], + "policy": "NR3F", + "reboot": false, + "stage": true, + "upgrade": {"nxos": true, "epld": true}, + "options": { + "nxos": {"mode": "FOO", "bios_force": false}, + "package": {"install": false, "uninstall": false}, + "epld": {"module": "ALL", "golden": false}, + "reboot": {"config_reload": false, "write_erase": false} + }, + "validate": true, + "ip_address": "172.22.150.102", + "policy_changed": true + } + ], + "test_image_upgrade_01050a": [ + { + "TEST_NOTES": [ + "nxos.mode == non_disruptive" + ], + "policy": "NR3F", + "reboot": false, + "stage": true, + "upgrade": {"nxos": true, "epld": true}, + "options": { + "nxos": {"mode": "non_disruptive", "bios_force": false}, + "package": {"install": false, "uninstall": false}, + "epld": {"module": "ALL", "golden": false}, + "reboot": {"config_reload": false, "write_erase": false} + }, + "validate": true, + "ip_address": "172.22.150.102", + "policy_changed": true + } + ], + "test_image_upgrade_01060a": [ + { + "TEST_NOTES": [ + "nxos.mode == force_non_disruptive" + ], + "policy": "NR3F", + "reboot": false, + "stage": true, + "upgrade": {"nxos": true, "epld": true}, + "options": { + "nxos": {"mode": "force_non_disruptive", "bios_force": false}, + "package": {"install": false, "uninstall": false}, + "epld": {"module": "ALL", "golden": false}, + "reboot": {"config_reload": false, "write_erase": false} + }, + "validate": true, + "ip_address": "172.22.150.102", + "policy_changed": true + } + ], + "test_image_upgrade_01070a": [ + { + "TEST_NOTES": [ + "options.nxos.bios_force is invalid (FOO)" + ], + "policy": "NR3F", + "reboot": false, + "stage": true, + "upgrade": {"nxos": true, "epld": true}, + "options": { + "nxos": {"mode": "disruptive", "bios_force": "FOO"}, + "package": {"install": false, "uninstall": false}, + "epld": {"module": "ALL", "golden": false}, + "reboot": {"config_reload": false, "write_erase": false} + }, + "validate": true, + "ip_address": "172.22.150.102", + "policy_changed": true + } + ], + "test_image_upgrade_01080a": [ + { + "TEST_NOTES": [ + "options.epld.golden is true and upgrade.nxos is true" + ], + "policy": "NR3F", + "reboot": false, + "stage": true, + "upgrade": {"nxos": true, "epld": true}, + "options": { + "nxos": {"mode": "disruptive", "bios_force": false}, + "package": {"install": false, "uninstall": false}, + "epld": {"module": "ALL", "golden": true}, + "reboot": {"config_reload": false, "write_erase": false} + }, + "validate": true, + "ip_address": "172.22.150.102", + "policy_changed": true + } + ], + "test_image_upgrade_01090a": [ + { + "TEST_NOTES": [ + "options.epld.module is invalid" + ], + "policy": "NR3F", + "reboot": false, + "stage": true, + "upgrade": {"nxos": true, "epld": true}, + "options": { + "nxos": {"mode": "disruptive", "bios_force": false}, + "package": {"install": false, "uninstall": false}, + "epld": {"module": "FOO", "golden": false}, + "reboot": {"config_reload": false, "write_erase": false} + }, + "validate": true, + "ip_address": "172.22.150.102", + "policy_changed": true + } + ], + "test_image_upgrade_01100a": [ + { + "TEST_NOTES": [ + "options.epld.golden is not a boolean" + ], + "policy": "NR3F", + "reboot": false, + "stage": true, + "upgrade": {"nxos": true, "epld": true}, + "options": { + "nxos": {"mode": "disruptive", "bios_force": false}, + "package": {"install": false, "uninstall": false}, + "epld": {"module": "ALL", "golden": "FOO"}, + "reboot": {"config_reload": false, "write_erase": false} + }, + "validate": true, + "ip_address": "172.22.150.102", + "policy_changed": true + } + ], + "test_image_upgrade_01110a": [ + { + "TEST_NOTES": [ + "reboot is invalid" + ], + "policy": "NR3F", + "reboot": "FOO", + "stage": true, + "upgrade": {"nxos": true, "epld": true}, + "options": { + "nxos": {"mode": "disruptive", "bios_force": false}, + "package": {"install": false, "uninstall": false}, + "epld": {"module": "ALL", "golden": false}, + "reboot": {"config_reload": false, "write_erase": false} + }, + "validate": true, + "ip_address": "172.22.150.102", + "policy_changed": true + } + ], + "test_image_upgrade_01120a": [ + { + "TEST_NOTES": [ + "options.reboot.config_reload is invalid" + ], + "policy": "NR3F", + "reboot": true, + "stage": true, + "upgrade": {"nxos": true, "epld": true}, + "options": { + "nxos": {"mode": "disruptive", "bios_force": false}, + "package": {"install": false, "uninstall": false}, + "epld": {"module": "ALL", "golden": false}, + "reboot": {"config_reload": "FOO", "write_erase": false} + }, + "validate": true, + "ip_address": "172.22.150.102", + "policy_changed": true + } + ], + "test_image_upgrade_01130a": [ + { + "TEST_NOTES": [ + "options.reboot.write_erase is invalid" + ], + "policy": "NR3F", + "reboot": true, + "stage": true, + "upgrade": {"nxos": true, "epld": true}, + "options": { + "nxos": {"mode": "disruptive", "bios_force": false}, + "package": {"install": false, "uninstall": false}, + "epld": {"module": "ALL", "golden": false}, + "reboot": {"config_reload": false, "write_erase": "FOO"} + }, + "validate": true, + "ip_address": "172.22.150.102", + "policy_changed": true + } + ], + "test_image_upgrade_01140a": [ + { + "TEST_NOTES": [ + "options.package.uninstall is invalid" + ], + "policy": "NR3F", + "reboot": true, + "stage": true, + "upgrade": {"nxos": true, "epld": true}, + "options": { + "nxos": {"mode": "disruptive", "bios_force": false}, + "package": {"install": false, "uninstall": "FOO"}, + "epld": {"module": "ALL", "golden": false}, + "reboot": {"config_reload": false, "write_erase": false} + }, + "validate": true, + "ip_address": "172.22.150.102", + "policy_changed": true + } + ], + "test_image_upgrade_01150a": [ + { + "TEST_NOTES": [ + "options.package.install is invalid" + ], + "policy": "NR3F", + "reboot": true, + "stage": true, + "upgrade": {"nxos": true, "epld": true}, + "options": { + "nxos": {"mode": "disruptive", "bios_force": false}, + "package": {"install": "FOO", "uninstall": false}, + "epld": {"module": "ALL", "golden": false}, + "reboot": {"config_reload": false, "write_erase": false} + }, + "validate": true, + "ip_address": "172.22.150.102", + "policy_changed": true + } + ], + "test_image_upgrade_01160a": [ + { + "TEST_NOTES": [ + "upgrade.epld is invalid" + ], + "policy": "NR3F", + "stage": true, + "upgrade": {"nxos": true, "epld": "FOO"}, + "options": { + "package": { + "uninstall": false + } + }, + "validate": true, + "ip_address": "172.22.150.102", + "policy_changed": true + } + ], + "test_image_upgrade_02000a": [ + { + "TEST_NOTES": [ + "Valid devices" + ], + "policy": "NR3F", + "reboot": false, + "stage": true, + "upgrade": {"nxos": true, "epld": true}, + "options": { + "nxos": {"mode": "disruptive", "bios_force": false}, + "package": {"install": false, "uninstall": false}, + "epld": {"module": "ALL", "golden": false}, + "reboot": {"config_reload": false, "write_erase": false} + }, + "validate": true, + "ip_address": "172.22.150.102", + "policy_changed": true + } + ] +} \ No newline at end of file diff --git a/tests/unit/modules/dcnm/dcnm_image_upgrade/fixtures/image_upgrade_responses_ImageStage.json b/tests/unit/modules/dcnm/dcnm_image_upgrade/fixtures/image_upgrade_responses_ImageStage.json deleted file mode 100644 index 9ec20cb7b..000000000 --- a/tests/unit/modules/dcnm/dcnm_image_upgrade/fixtures/image_upgrade_responses_ImageStage.json +++ /dev/null @@ -1,88 +0,0 @@ -{ - "test_image_upgrade_stage_00070a": { - "TEST_NOTES": [ - "Needed only for the 200 return code" - ], - "RETURN_CODE": 200, - "METHOD": "GET", - "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/stagingmanagement/stage-info", - "MESSAGE": "OK", - "DATA": { - "status": "SUCCESS", - "lastOperDataObject": [ - ], - "message": "" - } - }, - "test_image_upgrade_stage_00071a": { - "TEST_NOTES": [ - "RETURN_CODE == 200", - "MESSAGE == OK" - ], - "RETURN_CODE": 200, - "METHOD": "GET", - "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/stagingmanagement/stage-info", - "MESSAGE": "OK", - "DATA": { - "status": "SUCCESS", - "lastOperDataObject": [ - ], - "message": "" - } - }, - "test_image_upgrade_stage_00072a": { - "TEST_NOTES": [ - "RETURN_CODE == 200", - "MESSAGE == OK" - ], - "RETURN_CODE": 200, - "METHOD": "GET", - "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/stagingmanagement/stage-info", - "MESSAGE": "OK", - "DATA": { - "status": "SUCCESS", - "lastOperDataObject": [ - ], - "message": "" - } - }, - "test_image_upgrade_stage_00074a": { - "TEST_NOTES": [ - "RETURN_CODE == 500", - "MESSAGE == NOK" - ], - "RETURN_CODE": 500, - "METHOD": "GET", - "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/stagingmanagement/stage-info", - "MESSAGE": "NOK", - "DATA": { - "status": "FAILED", - "lastOperDataObject": [ - ], - "message": "" - } - }, - "test_image_upgrade_stage_00075a": { - "TEST_NOTES": [ - "RETURN_CODE == 200", - "MESSAGE == OK" - ], - "RETURN_CODE": 200, - "METHOD": "GET", - "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/stagingmanagement/stage-info", - "MESSAGE": "OK", - "DATA": { - "status": "SUCCESS", - "lastOperDataObject": [ - { - "deviceName": "leaf1", - "imageStaged": "Success", - "imageStagedPercent": 100, - "ipAddress": "172.22.150.102", - "serialNumber": "FDO21120U5D" - } - ], - "message": "" - } - } -} \ No newline at end of file diff --git a/tests/unit/modules/dcnm/dcnm_image_upgrade/fixtures/image_upgrade_responses_ImageUpgrade.json b/tests/unit/modules/dcnm/dcnm_image_upgrade/fixtures/image_upgrade_responses_ImageUpgrade.json deleted file mode 100644 index 021f7959e..000000000 --- a/tests/unit/modules/dcnm/dcnm_image_upgrade/fixtures/image_upgrade_responses_ImageUpgrade.json +++ /dev/null @@ -1,137 +0,0 @@ -{ - "test_image_upgrade_upgrade_00019a": { - "DATA": 121, - "MESSAGE": "OK", - "METHOD": "POST", - "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/imageupgrade/upgrade-image", - "RETURN_CODE": 200 - }, - "test_image_upgrade_upgrade_00020a": { - "DATA": 123, - "MESSAGE": "OK", - "METHOD": "POST", - "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/imageupgrade/upgrade-image", - "RETURN_CODE": 200 - }, - "test_image_upgrade_upgrade_00022a": { - "DATA": 123, - "MESSAGE": "OK", - "METHOD": "POST", - "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/imageupgrade/upgrade-image", - "RETURN_CODE": 200 - }, - "test_image_upgrade_upgrade_00023a": { - "DATA": 123, - "MESSAGE": "OK", - "METHOD": "POST", - "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/imageupgrade/upgrade-image", - "RETURN_CODE": 200 - }, - "test_image_upgrade_upgrade_00024a": { - "DATA": 123, - "MESSAGE": "OK", - "METHOD": "POST", - "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/imageupgrade/upgrade-image", - "RETURN_CODE": 200 - }, - "test_image_upgrade_upgrade_00025a": { - "DATA": 123, - "MESSAGE": "OK", - "METHOD": "POST", - "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/imageupgrade/upgrade-image", - "RETURN_CODE": 200 - }, - "test_image_upgrade_upgrade_00026a": { - "DATA": 123, - "MESSAGE": "OK", - "METHOD": "POST", - "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/imageupgrade/upgrade-image", - "RETURN_CODE": 200 - }, - "test_image_upgrade_upgrade_00027a": { - "DATA": 123, - "MESSAGE": "OK", - "METHOD": "POST", - "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/imageupgrade/upgrade-image", - "RETURN_CODE": 200 - }, - "test_image_upgrade_upgrade_00028a": { - "DATA": 123, - "MESSAGE": "OK", - "METHOD": "POST", - "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/imageupgrade/upgrade-image", - "RETURN_CODE": 200 - }, - "test_image_upgrade_upgrade_00029a": { - "DATA": 123, - "MESSAGE": "OK", - "METHOD": "POST", - "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/imageupgrade/upgrade-image", - "RETURN_CODE": 200 - }, - "test_image_upgrade_upgrade_00030a": { - "DATA": 123, - "MESSAGE": "OK", - "METHOD": "POST", - "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/imageupgrade/upgrade-image", - "RETURN_CODE": 200 - }, - "test_image_upgrade_upgrade_00031a": { - "DATA": 123, - "MESSAGE": "OK", - "METHOD": "POST", - "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/imageupgrade/upgrade-image", - "RETURN_CODE": 200 - }, - "test_image_upgrade_upgrade_00032a": { - "DATA": 123, - "MESSAGE": "Internal Server Error", - "METHOD": "POST", - "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/imageupgrade/upgrade-image", - "RETURN_CODE": 500 - }, - "test_image_upgrade_upgrade_00033a": { - "DATA": 123, - "MESSAGE": "OK", - "METHOD": "POST", - "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/imageupgrade/upgrade-image", - "RETURN_CODE": 200 - }, - "test_image_upgrade_upgrade_00045a": { - "DATA": 121, - "MESSAGE": "OK", - "METHOD": "POST", - "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/imageupgrade/upgrade-image", - "RETURN_CODE": 200 - }, - "test_image_upgrade_upgrade_00046a": { - "DATA": 121, - "MESSAGE": "OK", - "METHOD": "POST", - "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/imageupgrade/upgrade-image", - "RETURN_CODE": 200 - }, - "test_image_upgrade_upgrade_00047a": { - "DATA": 121, - "MESSAGE": "OK", - "METHOD": "POST", - "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/imageupgrade/upgrade-image", - "RETURN_CODE": 200 - }, - "test_image_upgrade_upgrade_0000XX": { - "DATA": { - "error": "Selected upgrade option is 'isGolden'. It does not allow when upgrade options selected more than one option. " - }, - "MESSAGE": "Internal Server Error", - "METHOD": "POST", - "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/imageupgrade/upgrade-image", - "RETURN_CODE": 500, - "TEST_NOTES": [ - "TODO: Add this test", - "Returned under the following conditions", - "upgrade.epld == True", - "upgrade.nxos == True", - "options.epld.golden == True" - ] - } -} \ No newline at end of file diff --git a/tests/unit/modules/dcnm/dcnm_image_upgrade/fixtures/image_upgrade_payloads_ImageUpgrade.json b/tests/unit/modules/dcnm/dcnm_image_upgrade/fixtures/payloads_ep_image_upgrade.json similarity index 62% rename from tests/unit/modules/dcnm/dcnm_image_upgrade/fixtures/image_upgrade_payloads_ImageUpgrade.json rename to tests/unit/modules/dcnm/dcnm_image_upgrade/fixtures/payloads_ep_image_upgrade.json index ffbc00588..35df757cd 100644 --- a/tests/unit/modules/dcnm/dcnm_image_upgrade/fixtures/image_upgrade_payloads_ImageUpgrade.json +++ b/tests/unit/modules/dcnm/dcnm_image_upgrade/fixtures/payloads_ep_image_upgrade.json @@ -1,5 +1,5 @@ { - "test_image_upgrade_upgrade_00019a": { + "test_image_upgrade_01020a": { "devices": [ { "policyName": "KR5M", @@ -28,7 +28,7 @@ "writeErase": false } }, - "test_image_upgrade_upgrade_00020a": { + "test_image_upgrade_01030a": { "devices": [ { "policyName": "NR3F", @@ -56,34 +56,5 @@ "configReload": false, "writeErase": false } - }, - "test_image_upgrade_upgrade_00021a": { - "devices": [ - { - "policyName": "NR3F", - "serialNumber": "FDO21120U5D" - } - ], - "epldOptions": { - "golden": false, - "moduleNumber": "ALL" - }, - "epldUpgrade": true, - "issuUpgrade": true, - "issuUpgradeOptions1": { - "disruptive": true, - "forceNonDisruptive": false, - "nonDisruptive": false - }, - "issuUpgradeOptions2": { - "biosForce": false - }, - "pacakgeInstall": false, - "pacakgeUnInstall": false, - "reboot": false, - "rebootOptions": { - "configReload": false, - "writeErase": false - } } } \ No newline at end of file diff --git a/tests/unit/modules/dcnm/dcnm_image_upgrade/fixtures/responses_ep_image_stage.json b/tests/unit/modules/dcnm/dcnm_image_upgrade/fixtures/responses_ep_image_stage.json new file mode 100644 index 000000000..eccf12336 --- /dev/null +++ b/tests/unit/modules/dcnm/dcnm_image_upgrade/fixtures/responses_ep_image_stage.json @@ -0,0 +1,83 @@ +{ + "test_image_stage_00900a": { + "TEST_NOTES": [ + "Needed only for 200 RETURN_CODE", + "RETURN_CODE == 200", + "MESSAGE == OK" + ], + "DATA": [ + { + "key": "FDO21120U5D", + "value": "No files to stage" + } + ], + "MESSAGE": "OK", + "METHOD": "POST", + "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/stagingmanagement/stage-image", + "RETURN_CODE": 200 + }, + "test_image_stage_00910a": { + "TEST_NOTES": [ + "RETURN_CODE == 200", + "MESSAGE == OK" + ], + "DATA": [ + { + "key": "FDO21120U5D", + "value": "No files to stage" + } + ], + "MESSAGE": "OK", + "METHOD": "POST", + "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/stagingmanagement/stage-image", + "RETURN_CODE": 200 + }, + "test_image_stage_00910b": { + "TEST_NOTES": [ + "RETURN_CODE == 200", + "MESSAGE == OK" + ], + "DATA": [ + { + "key": "FDO21120U5D", + "value": "No files to stage" + } + ], + "MESSAGE": "OK", + "METHOD": "POST", + "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/stagingmanagement/stage-image", + "RETURN_CODE": 200 + }, + "test_image_stage_00930a": { + "TEST_NOTES": [ + "RETURN_CODE == 500", + "MESSAGE == NOK" + ], + "RETURN_CODE": 500, + "METHOD": "POST", + "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/stagingmanagement/stage-image", + "MESSAGE": "NOK", + "DATA": { + "status": "FAILED", + "lastOperDataObject": [ + ], + "message": "" + } + }, + "test_image_stage_00940a": { + "TEST_NOTES": [ + "RETURN_CODE == 200", + "MESSAGE == OK" + ], + "DATA": [ + { + "key": "FDO21120U5D", + "value": "File successfully staged." + } + ], + "MESSAGE": "OK", + "METHOD": "POST", + "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/stagingmanagement/stage-image", + "RETURN_CODE": 200 + } +} \ No newline at end of file diff --git a/tests/unit/modules/dcnm/dcnm_image_upgrade/fixtures/responses_ep_image_upgrade.json b/tests/unit/modules/dcnm/dcnm_image_upgrade/fixtures/responses_ep_image_upgrade.json new file mode 100644 index 000000000..d778cd7c8 --- /dev/null +++ b/tests/unit/modules/dcnm/dcnm_image_upgrade/fixtures/responses_ep_image_upgrade.json @@ -0,0 +1,60 @@ +{ + "test_image_upgrade_01010a": { + "DATA": 121, + "MESSAGE": "OK", + "METHOD": "POST", + "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/imageupgrade/upgrade-image", + "RETURN_CODE": 200 + }, + "test_image_upgrade_01020a": { + "DATA": 121, + "MESSAGE": "OK", + "METHOD": "POST", + "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/imageupgrade/upgrade-image", + "RETURN_CODE": 200 + }, + "test_image_upgrade_01030a": { + "DATA": 123, + "MESSAGE": "OK", + "METHOD": "POST", + "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/imageupgrade/upgrade-image", + "RETURN_CODE": 200 + }, + "test_image_upgrade_01050a": { + "DATA": 123, + "MESSAGE": "OK", + "METHOD": "POST", + "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/imageupgrade/upgrade-image", + "RETURN_CODE": 200 + }, + "test_image_upgrade_01060a": { + "DATA": 123, + "MESSAGE": "OK", + "METHOD": "POST", + "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/imageupgrade/upgrade-image", + "RETURN_CODE": 200 + }, + "test_image_upgrade_02000a": { + "DATA": 123, + "MESSAGE": "Internal Server Error", + "METHOD": "POST", + "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/imageupgrade/upgrade-image", + "RETURN_CODE": 500 + }, + "test_image_upgrade_0000XX": { + "DATA": { + "error": "Selected upgrade option is 'isGolden'. It does not allow when upgrade options selected more than one option. " + }, + "MESSAGE": "Internal Server Error", + "METHOD": "POST", + "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/imageupgrade/upgrade-image", + "RETURN_CODE": 500, + "TEST_NOTES": [ + "TODO: Add this test", + "Returned under the following conditions", + "upgrade.epld == True", + "upgrade.nxos == True", + "options.epld.golden == True" + ] + } +} \ No newline at end of file diff --git a/tests/unit/modules/dcnm/dcnm_image_upgrade/fixtures/image_upgrade_responses_ImageValidate.json b/tests/unit/modules/dcnm/dcnm_image_upgrade/fixtures/responses_ep_image_validate.json similarity index 56% rename from tests/unit/modules/dcnm/dcnm_image_upgrade/fixtures/image_upgrade_responses_ImageValidate.json rename to tests/unit/modules/dcnm/dcnm_image_upgrade/fixtures/responses_ep_image_validate.json index 6c311b86d..034551f57 100644 --- a/tests/unit/modules/dcnm/dcnm_image_upgrade/fixtures/image_upgrade_responses_ImageValidate.json +++ b/tests/unit/modules/dcnm/dcnm_image_upgrade/fixtures/responses_ep_image_validate.json @@ -1,25 +1,10 @@ { - "test_image_upgrade_validate_00020a": { + "test_image_validate_00930a": { "TEST_NOTES": [ - "Needed only for the 200 return code" - ], - "RETURN_CODE": 200, - "METHOD": "POST", - "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/stagingmanagement/validate-image", - "MESSAGE": "OK", - "DATA": { - "status": "SUCCESS", - "lastOperDataObject": [ - ], - "message": "" - } - }, - "test_image_upgrade_validate_00023a": { - "TEST_NOTES": [ - "RETURN_CODE == 501", + "RETURN_CODE == 500", "MESSAGE == INTERNAL SERVER ERROR" ], - "RETURN_CODE": 501, + "RETURN_CODE": 500, "METHOD": "POST", "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/stagingmanagement/validate-image", "MESSAGE": "INTERNAL SERVER ERROR", @@ -30,7 +15,7 @@ "message": "" } }, - "test_image_upgrade_validate_00024a": { + "test_image_validate_00940a": { "TEST_NOTES": [], "RETURN_CODE": 200, "METHOD": "POST", diff --git a/tests/unit/modules/dcnm/dcnm_image_upgrade/fixtures/image_upgrade_responses_ImageInstallOptions.json b/tests/unit/modules/dcnm/dcnm_image_upgrade/fixtures/responses_ep_install_options.json similarity index 93% rename from tests/unit/modules/dcnm/dcnm_image_upgrade/fixtures/image_upgrade_responses_ImageInstallOptions.json rename to tests/unit/modules/dcnm/dcnm_image_upgrade/fixtures/responses_ep_install_options.json index f915b8a99..7cb78db19 100644 --- a/tests/unit/modules/dcnm/dcnm_image_upgrade/fixtures/image_upgrade_responses_ImageInstallOptions.json +++ b/tests/unit/modules/dcnm/dcnm_image_upgrade/fixtures/responses_ep_install_options.json @@ -1,5 +1,5 @@ { - "test_image_upgrade_install_options_00005a": { + "test_image_install_options_00110a": { "TEST_NOTES": [ "All attributes in compatibilityStatusList are tested", "epldModules is tested", @@ -32,7 +32,7 @@ "errMessage": "" } }, - "test_image_upgrade_install_options_00006a": { + "test_image_install_options_00120a": { "TEST_NOTES": [ "RETURN_CODE 500 is tested" ], @@ -44,7 +44,7 @@ "error": "null" } }, - "test_image_upgrade_install_options_00007a": { + "test_image_install_options_00130a": { "TEST_NOTES": [ "POST REQUEST contents: issu == true, epld == false, packageInstall false", "Device has no policy attached", @@ -81,7 +81,7 @@ "errMessage": "" } }, - "test_image_upgrade_install_options_00008a": { + "test_image_install_options_00140a": { "TEST_NOTES": [ "POST REQUEST contents: issu == true, epld == true, packageInstall false", "Device has no policy attached", @@ -146,7 +146,7 @@ "errMessage": "" } }, - "test_image_upgrade_install_options_00009a": { + "test_image_install_options_00150a": { "TEST_NOTES": [ "POST REQUEST contents: issu == false, epld == true, packageInstall false", "Device has no policy attached", @@ -195,7 +195,7 @@ "errMessage": "" } }, - "test_image_upgrade_install_options_00010a": { + "test_image_install_options_00160a": { "TEST_NOTES": [ "POST REQUEST contents: issu == true, epld == true, packageInstall true", "RETURN_CODE is 500 due to packageInstall is true, but policy contains no packages", @@ -210,7 +210,7 @@ "error": "Selected policy KR5M does not have package to continue." } }, - "test_image_upgrade_upgrade_00019a": { + "test_image_upgrade_01010a": { "DATA": { "compatibilityStatusList": [ { @@ -265,7 +265,62 @@ "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/imageupgrade/install-options", "RETURN_CODE": 200 }, - "test_image_upgrade_upgrade_00020a": { + "test_image_upgrade_01020a": { + "DATA": { + "compatibilityStatusList": [ + { + "compDisp": "REMOVED", + "deviceName": "leaf1", + "installOption": "disruptive", + "ipAddress": "172.22.150.102", + "osType": "64bit", + "platform": "N9K/N3K", + "policyName": "KR5M", + "preIssuLink": "Not Applicable", + "repStatus": "skipped", + "status": "Success", + "timestamp": "NA", + "version": "10.2.5", + "versionCheck": "REMOVED" + } + ], + "epldModules": { + "bException": false, + "exceptionReason": null, + "moduleList": [ + { + "deviceName": "leaf1", + "ipAddress": "172.22.150.102", + "modelName": "N9K-C93180YC-EX", + "module": 1, + "moduleType": "IO FPGA", + "name": null, + "newVersion": "0x15", + "oldVersion": "0x15", + "policyName": "KR5M" + }, + { + "deviceName": "leaf1", + "ipAddress": "172.22.150.102", + "modelName": "N9K-C93180YC-EX", + "module": 1, + "moduleType": "MI FPGA", + "name": null, + "newVersion": "0x04", + "oldVersion": "0x4", + "policyName": "KR5M" + } + ] + }, + "errMessage": "", + "installPacakges": null + }, + "MESSAGE": "OK", + "METHOD": "POST", + "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/imageupgrade/install-options", + "RETURN_CODE": 200 + }, + "test_image_upgrade_01030a": { "DATA": { "compatibilityStatusList": [ { @@ -320,7 +375,7 @@ "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/imageupgrade/install-options", "RETURN_CODE": 200 }, - "test_image_upgrade_upgrade_00021a": { + "test_image_upgrade_01040a": { "DATA": { "compatibilityStatusList": [ { @@ -375,7 +430,7 @@ "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/imageupgrade/install-options", "RETURN_CODE": 200 }, - "test_image_upgrade_upgrade_00022a": { + "test_image_upgrade_01050a": { "DATA": { "compatibilityStatusList": [ { @@ -430,7 +485,7 @@ "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/imageupgrade/install-options", "RETURN_CODE": 200 }, - "test_image_upgrade_upgrade_00023a": { + "test_image_upgrade_01060a": { "DATA": { "compatibilityStatusList": [ { @@ -485,7 +540,7 @@ "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/imageupgrade/install-options", "RETURN_CODE": 200 }, - "test_image_upgrade_upgrade_00024a": { + "test_image_upgrade_01070a": { "DATA": { "compatibilityStatusList": [ { @@ -540,7 +595,7 @@ "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/imageupgrade/install-options", "RETURN_CODE": 200 }, - "test_image_upgrade_upgrade_00025a": { + "test_image_upgrade_01080a": { "DATA": { "compatibilityStatusList": [ { @@ -595,7 +650,7 @@ "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/imageupgrade/install-options", "RETURN_CODE": 200 }, - "test_image_upgrade_upgrade_00026a": { + "test_image_upgrade_01090a": { "DATA": { "compatibilityStatusList": [ { @@ -650,7 +705,7 @@ "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/imageupgrade/install-options", "RETURN_CODE": 200 }, - "test_image_upgrade_upgrade_00027a": { + "test_image_upgrade_01100a": { "DATA": { "compatibilityStatusList": [ { @@ -705,7 +760,7 @@ "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/imageupgrade/install-options", "RETURN_CODE": 200 }, - "test_image_upgrade_upgrade_00028a": { + "test_image_upgrade_01110a": { "DATA": { "compatibilityStatusList": [ { @@ -760,7 +815,7 @@ "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/imageupgrade/install-options", "RETURN_CODE": 200 }, - "test_image_upgrade_upgrade_00029a": { + "test_image_upgrade_01120a": { "DATA": { "compatibilityStatusList": [ { @@ -815,7 +870,7 @@ "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/imageupgrade/install-options", "RETURN_CODE": 200 }, - "test_image_upgrade_upgrade_00030a": { + "test_image_upgrade_01130a": { "DATA": { "compatibilityStatusList": [ { @@ -870,7 +925,7 @@ "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/imageupgrade/install-options", "RETURN_CODE": 200 }, - "test_image_upgrade_upgrade_00031a": { + "test_image_upgrade_01140a": { "DATA": { "compatibilityStatusList": [ { @@ -925,7 +980,7 @@ "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/imageupgrade/install-options", "RETURN_CODE": 200 }, - "test_image_upgrade_upgrade_00032a": { + "test_image_upgrade_01160a": { "DATA": { "compatibilityStatusList": [ { @@ -980,7 +1035,7 @@ "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/imageupgrade/install-options", "RETURN_CODE": 200 }, - "test_image_upgrade_upgrade_00033a": { + "test_image_upgrade_02000a": { "DATA": { "compatibilityStatusList": [ { diff --git a/tests/unit/modules/dcnm/dcnm_image_upgrade/fixtures/image_upgrade_responses_SwitchIssuDetails.json b/tests/unit/modules/dcnm/dcnm_image_upgrade/fixtures/responses_ep_issu.json similarity index 81% rename from tests/unit/modules/dcnm/dcnm_image_upgrade/fixtures/image_upgrade_responses_SwitchIssuDetails.json rename to tests/unit/modules/dcnm/dcnm_image_upgrade/fixtures/responses_ep_issu.json index aa106070a..42c2b9d81 100644 --- a/tests/unit/modules/dcnm/dcnm_image_upgrade/fixtures/image_upgrade_responses_SwitchIssuDetails.json +++ b/tests/unit/modules/dcnm/dcnm_image_upgrade/fixtures/responses_ep_issu.json @@ -1,5 +1,5 @@ { - "test_image_upgrade_switch_issu_details_by_device_name_00020a": { + "test_switch_issu_details_by_device_name_00100a": { "TEST_NOTES": [ "RETURN_CODE 200", "DATA.deviceName is required by the test" @@ -18,7 +18,7 @@ "message": "" } }, - "test_image_upgrade_switch_issu_details_by_device_name_00021a": { + "test_switch_issu_details_by_device_name_00110a": { "TEST_NOTES": [ "RETURN_CODE 200", "Two switches present", @@ -79,7 +79,7 @@ "message": "" } }, - "test_image_upgrade_switch_issu_details_by_device_name_00022a": { + "test_switch_issu_details_by_device_name_00120a": { "TEST_NOTES": [ "RETURN_CODE 200", "DATA.deviceName is required by the test" @@ -98,7 +98,7 @@ "message": "" } }, - "test_image_upgrade_switch_issu_details_by_device_name_00023a": { + "test_switch_issu_details_by_device_name_00130a": { "TEST_NOTES": [ "RETURN_CODE 404", "MESSAGE != OK", @@ -115,7 +115,7 @@ "path": "/bad/path" } }, - "test_image_upgrade_switch_issu_details_by_device_name_00024a": { + "test_switch_issu_details_by_device_name_00140a": { "TEST_NOTES": [ "RETURN_CODE 200", "DATA is empty" @@ -126,7 +126,7 @@ "MESSAGE": "OK", "DATA": {} }, - "test_image_upgrade_switch_issu_details_by_device_name_00025a": { + "test_switch_issu_details_by_device_name_00150a": { "TEST_NOTES": [ "RETURN_CODE 200", "DATA.lastOperDataObject is empty" @@ -141,7 +141,7 @@ "message": "" } }, - "test_image_upgrade_switch_issu_details_by_device_name_00040a": { + "test_switch_issu_details_by_device_name_00200a": { "TEST_NOTES": [ "RETURN_CODE 200", "DATA.deviceName != FOO is required by the test" @@ -160,7 +160,7 @@ "message": "" } }, - "test_image_upgrade_switch_issu_details_by_device_name_00041a": { + "test_switch_issu_details_by_device_name_00210a": { "TEST_NOTES": [ "RETURN_CODE 200", "DATA.deviceName == leaf1 is required by the test" @@ -179,7 +179,7 @@ "message": "" } }, - "test_image_upgrade_switch_issu_details_by_ip_address_00020a": { + "test_switch_issu_details_by_ip_address_00100a": { "TEST_NOTES": [ "RETURN_CODE 200", "DATA.lastOperDataObject.ipAddress is required by the test" @@ -198,7 +198,7 @@ "message": "" } }, - "test_image_upgrade_switch_issu_details_by_ip_address_00021a": { + "test_switch_issu_details_by_ip_address_00110a": { "TEST_NOTES": [ "RETURN_CODE 200", "Two switches present", @@ -260,7 +260,7 @@ "message": "" } }, - "test_image_upgrade_switch_issu_details_by_ip_address_00022a": { + "test_switch_issu_details_by_ip_address_00120a": { "TEST_NOTES": [ "RETURN_CODE 200", "DATA.ipAddress is required by the test" @@ -279,7 +279,7 @@ "message": "" } }, - "test_image_upgrade_switch_issu_details_by_ip_address_00023a": { + "test_switch_issu_details_by_ip_address_00130a": { "TEST_NOTES": [ "RETURN_CODE 404", "MESSAGE != OK", @@ -296,7 +296,7 @@ "path": "/bad/path" } }, - "test_image_upgrade_switch_issu_details_by_ip_address_00024a": { + "test_switch_issu_details_by_ip_address_00140a": { "TEST_NOTES": [ "RETURN_CODE 200", "DATA is empty" @@ -307,7 +307,7 @@ "MESSAGE": "OK", "DATA": {} }, - "test_image_upgrade_switch_issu_details_by_ip_address_00025a": { + "test_switch_issu_details_by_ip_address_00150a": { "TEST_NOTES": [ "RETURN_CODE 200", "DATA.lastOperDataObject is empty" @@ -322,7 +322,7 @@ "message": "" } }, - "test_image_upgrade_switch_issu_details_by_ip_address_00040a": { + "test_switch_issu_details_by_ip_address_00200a": { "TEST_NOTES": [ "RETURN_CODE 200", "DATA.ipAddress != 1.1.1.1 is required by the test" @@ -341,7 +341,7 @@ "message": "" } }, - "test_image_upgrade_switch_issu_details_by_ip_address_00041a": { + "test_switch_issu_details_by_ip_address_00210a": { "TEST_NOTES": [ "RETURN_CODE 200", "DATA.ipAddress == 172.22.150.102 is required by the test" @@ -360,10 +360,10 @@ "message": "" } }, - "test_image_upgrade_switch_issu_details_by_serial_number_00020a": { + "test_switch_issu_details_by_serial_number_00100a": { "TEST_NOTES": [ "RETURN_CODE 200", - "DATA.lastOperDataObject.serialNumber is required by the test" + "DATA.lastOperDataObject[0].serialNumber == FDO21120U5D" ], "RETURN_CODE": 200, "METHOD": "GET", @@ -379,7 +379,7 @@ "message": "" } }, - "test_image_upgrade_switch_issu_details_by_serial_number_00021a": { + "test_switch_issu_details_by_serial_number_00110a": { "TEST_NOTES": [ "RETURN_CODE 200", "Two switches present", @@ -440,7 +440,7 @@ "message": "" } }, - "test_image_upgrade_switch_issu_details_by_serial_number_00022a": { + "test_switch_issu_details_by_serial_number_00120a": { "TEST_NOTES": [ "RETURN_CODE 200", "DATA.serialNumber is required by the test" @@ -459,7 +459,7 @@ "message": "" } }, - "test_image_upgrade_switch_issu_details_by_serial_number_00023a": { + "test_switch_issu_details_by_serial_number_00130a": { "TEST_NOTES": [ "RETURN_CODE 404", "MESSAGE != OK", @@ -476,7 +476,7 @@ "path": "/bad/path" } }, - "test_image_upgrade_switch_issu_details_by_serial_number_00024a": { + "test_switch_issu_details_by_serial_number_00140a": { "TEST_NOTES": [ "RETURN_CODE 200", "DATA is empty" @@ -487,7 +487,7 @@ "MESSAGE": "OK", "DATA": {} }, - "test_image_upgrade_switch_issu_details_by_serial_number_00025a": { + "test_switch_issu_details_by_serial_number_00150a": { "TEST_NOTES": [ "RETURN_CODE 200", "DATA.lastOperDataObject is empty" @@ -502,7 +502,7 @@ "message": "" } }, - "test_image_upgrade_switch_issu_details_by_serial_number_00040a": { + "test_switch_issu_details_by_serial_number_00200a": { "TEST_NOTES": [ "RETURN_CODE 200", "DATA.serialNumber != FOO00000BAR is required by the test" @@ -521,7 +521,7 @@ "message": "" } }, - "test_image_upgrade_switch_issu_details_by_serial_number_00041a": { + "test_switch_issu_details_by_serial_number_00210a": { "TEST_NOTES": [ "RETURN_CODE 200", "DATA.serialNumber == FDO21120U5D is required by the test" @@ -540,7 +540,7 @@ "message": "" } }, - "test_image_upgrade_stage_00004a": { + "test_image_stage_00200a": { "TEST_NOTES": [ "FDO2112189M: imageStaged == none", "FDO211218AX: imageStaged == none", @@ -579,11 +579,11 @@ "message": "" } }, - "test_image_upgrade_stage_00005a": { + "test_image_stage_00300a": { "TEST_NOTES": [ "FDO21120U5D: imageStaged == Success", "FDO2112189M: imageStaged == Failed", - "FDO2112189M: requires deviceName, ipAddress, used in the fail_json message" + "FDO2112189M: requires deviceName, ipAddress, used in the ControllerResponseError message" ], "RETURN_CODE": 200, "METHOD": "GET", @@ -606,14 +606,14 @@ "message": "" } }, - "test_image_upgrade_stage_00020a": { + "test_image_stage_00400a": { "TEST_NOTES": [ "RETURN_CODE == 200", "DATA.lastOperDataObject.serialNumber is present and is this specific value", - "DATA.lastOperDataObject.imageStaged == Success", - "DATA.lastOperDataObject.imageStagedPercent is present", - "DATA.lastOperDataObject.ipAddress is present", - "DATA.lastOperDataObject.deviceName is present", + "DATA.lastOperDataObject[*].imageStaged == Success", + "DATA.lastOperDataObject[*].imageStagedPercent == 100", + "DATA.lastOperDataObject[*].ipAddress is present", + "DATA.lastOperDataObject[*].deviceName is present", "Entries for both serial numbers FDO21120U5D FDO2112189M are present" ], "RETURN_CODE": 200, @@ -641,15 +641,18 @@ "message": "" } }, - "test_image_upgrade_stage_00021a": { + "test_image_stage_00410a": { "TEST_NOTES": [ "RETURN_CODE == 200", "Entries for both serial numbers FDO21120U5D FDO2112189M are present", "FDO21120U5D imageStaged == Success", "FDO2112189M imageStage == Failed", - "DATA.lastOperDataObject.imageStagedPercent is present", - "DATA.lastOperDataObject.ipAddress is present", - "DATA.lastOperDataObject.deviceName is present" + "DATA.lastOperDataObject[0].imageStaged == Success", + "DATA.lastOperDataObject[1].imageStaged == Failed", + "DATA.lastOperDataObject[0].imageStagedPercent == 100", + "DATA.lastOperDataObject[1].imageStagedPercent == 90", + "DATA.lastOperDataObject[*].ipAddress is present", + "DATA.lastOperDataObject[*].deviceName is present" ], "RETURN_CODE": 200, "METHOD": "GET", @@ -659,11 +662,11 @@ "status": "SUCCESS", "lastOperDataObject": [ { + "deviceName": "leaf1", "serialNumber": "FDO21120U5D", "imageStaged": "Success", "imageStagedPercent": 100, - "ipAddress": "172.22.150.102", - "deviceName": "leaf1" + "ipAddress": "172.22.150.102" }, { "deviceName": "cvd-2313-leaf", @@ -676,7 +679,7 @@ "message": "" } }, - "test_image_upgrade_stage_00022a": { + "test_image_stage_00420a": { "TEST_NOTES": [ "RETURN_CODE == 200", "Entries for both serial numbers FDO21120U5D FDO2112189M are present", @@ -694,11 +697,11 @@ "status": "SUCCESS", "lastOperDataObject": [ { + "deviceName": "leaf1", "serialNumber": "FDO21120U5D", "imageStaged": "Success", "imageStagedPercent": 100, - "ipAddress": "172.22.150.102", - "deviceName": "leaf1" + "ipAddress": "172.22.150.102" }, { "deviceName": "cvd-2313-leaf", @@ -711,7 +714,7 @@ "message": "" } }, - "test_image_upgrade_stage_00030a": { + "test_image_stage_00500a": { "TEST_NOTES": [ "FDO21120U5D upgrade, validated, imageStaged == Success", "FDO2112189M upgrade, validated, imageStaged == Success" @@ -739,7 +742,7 @@ "message": "" } }, - "test_image_upgrade_stage_00031a": { + "test_image_stage_00510a": { "TEST_NOTES": [ "FDO21120U5D upgrade, validated, imageStaged == Success", "FDO2112189M upgrade, validated == Success", @@ -768,7 +771,7 @@ "message": "" } }, - "test_image_upgrade_stage_00070a": { + "test_image_stage_00900a": { "RETURN_CODE": 200, "METHOD": "GET", "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/packagemgnt/issu", @@ -858,7 +861,12 @@ "message": "" } }, - "test_image_upgrade_stage_00071a": { + "test_image_stage_00910a": { + "TEST_NOTES": [ + "RETURN_CODE == 200", + "DATA.lastOperDataObject.imageStaged == Success", + "DATA.lastOperDataObject.serialNumber is present" + ], "RETURN_CODE": 200, "METHOD": "GET", "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/packagemgnt/issu", @@ -868,87 +876,13 @@ "lastOperDataObject": [ { "serialNumber": "FDO21120U5D", - "deviceName": "leaf1", - "version": "10.2(5)", - "policy": "KR5M", - "status": "In-Sync", - "reason": "Upgrade", - "imageStaged": "Success", - "validated": "Success", - "upgrade": "Success", - "upgGroups": "null", - "mode": "Normal", - "systemMode": "Normal", - "vpcRole": "null", - "vpcPeer": "null", - "role": "leaf", - "lastUpgAction": "2023-Oct-19 02:20", - "model": "N9K-C93180YC-EX", - "fabric": "easy", - "ipAddress": "172.22.150.102", - "issuAllowed": "", - "statusPercent": 100, - "imageStagedPercent": 100, - "validatedPercent": 100, - "upgradePercent": 100, - "modelType": 0, - "vdcId": 0, - "ethswitchid": 145740, - "platform": "N9K", - "vpc_role": "null", - "ip_address": "172.22.150.102", - "peer": "null", - "vdc_id": -1, - "sys_name": "leaf1", - "id": 1, - "group": "easy", - "fcoEEnabled": "False", - "mds": "False" - }, - { - "serialNumber": "FDO2112189M", - "deviceName": "cvd-2313-leaf", - "version": "10.2(5)", - "policy": "KR5M", - "status": "In-Sync", - "reason": "Upgrade", - "imageStaged": "Failed", - "validated": "Success", - "upgrade": "Success", - "upgGroups": "null", - "mode": "Normal", - "systemMode": "Normal", - "vpcRole": "null", - "vpcPeer": "null", - "role": "leaf", - "lastUpgAction": "2023-Oct-06 03:43", - "model": "N9K-C93180YC-EX", - "fabric": "hard", - "ipAddress": "172.22.150.108", - "issuAllowed": "", - "statusPercent": 100, - "imageStagedPercent": 100, - "validatedPercent": 100, - "upgradePercent": 100, - "modelType": 0, - "vdcId": 0, - "ethswitchid": 39890, - "platform": "N9K", - "vpc_role": "null", - "ip_address": "172.22.150.108", - "peer": "null", - "vdc_id": -1, - "sys_name": "cvd-2313-leaf", - "id": 2, - "group": "hard", - "fcoEEnabled": "False", - "mds": "False" + "imageStaged": "Success" } ], "message": "" } }, - "test_image_upgrade_stage_00072a": { + "test_image_stage_00910b": { "TEST_NOTES": [ "RETURN_CODE == 200", "DATA.lastOperDataObject.imageStaged == Success", @@ -969,7 +903,7 @@ "message": "" } }, - "test_image_upgrade_stage_00073a": { + "test_image_stage_00930a": { "TEST_NOTES": [ "RETURN_CODE == 200", "Using only for RETURN_CODE == 200" @@ -989,10 +923,16 @@ "message": "" } }, - "test_image_upgrade_stage_00074a": { + "test_image_stage_00940a": { "TEST_NOTES": [ "RETURN_CODE == 200", - "Using only for RETURN_CODE == 200" + "MESSAGE == OK", + "DATA.lastOperDataObject.deviceName == leaf1", + "DATA.lastOperDataObject.imageStaged == null", + "DATA.lastOperDataObject.imageStagedPercent == 0", + "DATA.lastOperDataObject.ipAddress == 172.22.150.102", + "DATA.lastOperDataObject.policy == KR5M", + "DATA.lastOperDataObject.serialNumber == FDO21120U5D" ], "RETURN_CODE": 200, "METHOD": "GET", @@ -1002,20 +942,28 @@ "status": "SUCCESS", "lastOperDataObject": [ { + "deviceName": "leaf1", + "imageStaged": "", + "imageStagedPercent": 0, + "ipAddress": "172.22.150.102", + "policy": "KR5M", "serialNumber": "FDO21120U5D", - "imageStaged": "Success" + "validated": "", + "validatedPercent": 0, + "upgrade": "", + "upgradePercent": 0 } ], "message": "" } }, - "test_image_upgrade_stage_00075a": { + "test_image_stage_00940b": { "TEST_NOTES": [ "RETURN_CODE == 200", "MESSAGE == OK", "DATA.lastOperDataObject.deviceName == leaf1", "DATA.lastOperDataObject.imageStaged == Success", - "DATA.lastOperDataObject.imageStagedPercent == 100", + "DATA.lastOperDataObject.imageStagedPercent == 110", "DATA.lastOperDataObject.ipAddress == 172.22.150.102", "DATA.lastOperDataObject.policy == KR5M", "DATA.lastOperDataObject.serialNumber == FDO21120U5D" @@ -1033,13 +981,17 @@ "imageStagedPercent": 100, "ipAddress": "172.22.150.102", "policy": "KR5M", - "serialNumber": "FDO21120U5D" + "serialNumber": "FDO21120U5D", + "validated": "", + "validatedPercent": 100, + "upgrade": "", + "upgradePercent": 100 } ], "message": "" } }, - "test_image_upgrade_validate_00003a": { + "test_image_validate_00200a": { "TEST_NOTES": [ "FDO2112189M validated: none", "FDO211218AX validated: none", @@ -1056,207 +1008,33 @@ "lastOperDataObject": [ { "serialNumber": "FDO2112189M", - "deviceName": "cvd-2313-leaf", - "version": "10.2(5)", - "policy": "KR5M", - "status": "In-Sync", - "reason": "Upgrade", - "imageStaged": "Success", - "validated": "none", - "upgrade": "Success", - "upgGroups": "null", - "mode": "Normal", - "systemMode": "Normal", - "vpcRole": "null", - "vpcPeer": "null", - "role": "leaf", - "lastUpgAction": "2023-Oct-06 03:43", - "model": "N9K-C93180YC-EX", - "fabric": "hard", - "ipAddress": "172.22.150.108", - "issuAllowed": "", - "statusPercent": 100, - "imageStagedPercent": 100, - "validatedPercent": 100, - "upgradePercent": 100, - "modelType": 0, - "vdcId": 0, - "ethswitchid": 39890, - "platform": "N9K", - "vpc_role": "null", - "ip_address": "172.22.150.108", - "peer": "null", - "vdc_id": -1, - "sys_name": "cvd-2313-leaf", - "id": 2, - "group": "hard", - "fcoEEnabled": "False", - "mds": "False" + "validated": "none" }, { "serialNumber": "FDO211218AX", - "deviceName": "cvd-2312-leaf", - "version": "10.2(5)", - "policy": "KR5M", - "status": "In-Sync", - "reason": "Upgrade", - "imageStaged": "Success", - "validated": "none", - "upgrade": "Success", - "upgGroups": "null", - "mode": "Normal", - "systemMode": "Normal", - "vpcRole": "null", - "vpcPeer": "null", - "role": "leaf", - "lastUpgAction": "2023-Oct-06 03:43", - "model": "N9K-C93180YC-EX", - "fabric": "hard", - "ipAddress": "172.22.150.107", - "issuAllowed": "", - "statusPercent": 100, - "imageStagedPercent": 100, - "validatedPercent": 100, - "upgradePercent": 100, - "modelType": 0, - "vdcId": 0, - "ethswitchid": 39740, - "platform": "N9K", - "vpc_role": "null", - "ip_address": "172.22.150.107", - "peer": "null", - "vdc_id": -1, - "sys_name": "cvd-2312-leaf", - "id": 3, - "group": "hard", - "fcoEEnabled": "False", - "mds": "False" + "validated": "none" }, { "serialNumber": "FDO211218B5", - "deviceName": "cvd-2314-leaf", - "version": "10.2(5)", - "policy": "KR5M", - "status": "In-Sync", - "reason": "Upgrade", - "imageStaged": "Success", - "validated": "none", - "upgrade": "Success", - "upgGroups": "null", - "mode": "Normal", - "systemMode": "Normal", - "vpcRole": "null", - "vpcPeer": "null", - "role": "leaf", - "lastUpgAction": "2023-Oct-06 03:43", - "model": "N9K-C93180YC-EX", - "fabric": "hard", - "ipAddress": "172.22.150.109", - "issuAllowed": "", - "statusPercent": 100, - "imageStagedPercent": 100, - "validatedPercent": 100, - "upgradePercent": 100, - "modelType": 0, - "vdcId": 0, - "ethswitchid": 39840, - "platform": "N9K", - "vpc_role": "null", - "ip_address": "172.22.150.109", - "peer": "null", - "vdc_id": -1, - "sys_name": "cvd-2314-leaf", - "id": 4, - "group": "hard", - "fcoEEnabled": "False", - "mds": "False" + "validated": "none" }, { "serialNumber": "FDO211218FV", - "deviceName": "cvd-1314-leaf", - "version": "10.2(5)", - "policy": "KR5M", - "status": "In-Sync", - "reason": "Upgrade", - "imageStaged": "Success", - "validated": "Success", - "upgrade": "Success", - "upgGroups": "null", - "mode": "Normal", - "systemMode": "Normal", - "vpcRole": "null", - "vpcPeer": "null", - "role": "leaf", - "lastUpgAction": "2023-Oct-19 02:40", - "model": "N9K-C93180YC-EX", - "fabric": "easy", - "ipAddress": "172.22.150.105", - "issuAllowed": "", - "statusPercent": 100, - "imageStagedPercent": 100, - "validatedPercent": 100, - "upgradePercent": 100, - "modelType": 0, - "vdcId": 0, - "ethswitchid": 153350, - "platform": "N9K", - "vpc_role": "null", - "ip_address": "172.22.150.105", - "peer": "null", - "vdc_id": -1, - "sys_name": "cvd-1314-leaf", - "id": 5, - "group": "easy", - "fcoEEnabled": "False", - "mds": "False" + "validated": "Success" }, { "serialNumber": "FDO211218GC", - "deviceName": "cvd-1312-leaf", - "version": "10.2(5)", - "policy": "KR5M", - "status": "In-Sync", - "reason": "Upgrade", - "imageStaged": "Success", - "validated": "Success", - "upgrade": "Success", - "upgGroups": "null", - "mode": "Normal", - "systemMode": "Normal", - "vpcRole": "null", - "vpcPeer": "null", - "role": "leaf", - "lastUpgAction": "2023-Oct-23 01:43", - "model": "N9K-C93180YC-EX", - "fabric": "easy", - "ipAddress": "172.22.150.103", - "issuAllowed": "", - "statusPercent": 100, - "imageStagedPercent": 100, - "validatedPercent": 100, - "upgradePercent": 100, - "modelType": 0, - "vdcId": 0, - "ethswitchid": 150610, - "platform": "N9K", - "vpc_role": "null", - "ip_address": "172.22.150.103", - "peer": "null", - "vdc_id": -1, - "sys_name": "cvd-1312-leaf", - "id": 6, - "group": "easy", - "fcoEEnabled": "False", - "mds": "False" + "validated": "Success" } ], "message": "" } }, - "test_image_upgrade_validate_00004a": { + "test_image_validate_00300a": { "TEST_NOTES": [ "FDO21120U5D validated: Success", - "FDO2112189M validated: Failed" + "FDO2112189M validated: Failed", + "FDO2112189M: requires deviceName, ipAddress, used in the ControllerResponseError message" ], "RETURN_CODE": 200, "METHOD": "GET", @@ -1267,90 +1045,27 @@ "lastOperDataObject": [ { "serialNumber": "FDO21120U5D", - "deviceName": "leaf1", - "version": "10.2(5)", - "policy": "KR5M", - "status": "In-Sync", - "reason": "Upgrade", - "imageStaged": "Success", - "validated": "Success", - "upgrade": "Success", - "upgGroups": "null", - "mode": "Normal", - "systemMode": "Normal", - "vpcRole": "null", - "vpcPeer": "null", - "role": "leaf", - "lastUpgAction": "2023-Oct-19 02:20", - "model": "N9K-C93180YC-EX", - "fabric": "easy", - "ipAddress": "172.22.150.102", - "issuAllowed": "", - "statusPercent": 100, - "imageStagedPercent": 100, - "validatedPercent": 100, - "upgradePercent": 100, - "modelType": 0, - "vdcId": 0, - "ethswitchid": 145740, - "platform": "N9K", - "vpc_role": "null", - "ip_address": "172.22.150.102", - "peer": "null", - "vdc_id": -1, - "sys_name": "leaf1", - "id": 1, - "group": "easy", - "fcoEEnabled": "False", - "mds": "False" + "validated": "Success" }, { "serialNumber": "FDO2112189M", - "deviceName": "cvd-2313-leaf", - "version": "10.2(5)", - "policy": "KR5M", - "status": "In-Sync", - "reason": "Upgrade", - "imageStaged": "Success", "validated": "Failed", - "upgrade": "Success", - "upgGroups": "null", - "mode": "Normal", - "systemMode": "Normal", - "vpcRole": "null", - "vpcPeer": "null", - "role": "leaf", - "lastUpgAction": "2023-Oct-06 03:43", - "model": "N9K-C93180YC-EX", - "fabric": "hard", - "ipAddress": "172.22.150.108", - "issuAllowed": "", - "statusPercent": 100, - "imageStagedPercent": 100, - "validatedPercent": 50, - "upgradePercent": 100, - "modelType": 0, - "vdcId": 0, - "ethswitchid": 39890, - "platform": "N9K", - "vpc_role": "null", - "ip_address": "172.22.150.108", - "peer": "null", - "vdc_id": -1, - "sys_name": "cvd-2313-leaf", - "id": 2, - "group": "hard", - "fcoEEnabled": "False", - "mds": "False" + "deviceName": "cvd-2313-leaf", + "ipAddress": "172.22.150.108" } ], "message": "" } }, - "test_image_upgrade_validate_00005a": { + "test_image_validate_00400a": { "TEST_NOTES": [ - "FDO21120U5D validated: Success", - "FDO2112189M validated: Success" + "RETURN_CODE == 200", + "DATA.lastOperDataObject[*].serialNumber is present and is this specific value", + "DATA.lastOperDataObject[*].validated == Success", + "DATA.lastOperDataObject[*].validatedPercent == 100", + "DATA.lastOperDataObject[*].ipAddress is present", + "DATA.lastOperDataObject[*].deviceName is present", + "Entries for both serial numbers FDO21120U5D FDO2112189M are present" ], "RETURN_CODE": 200, "METHOD": "GET", @@ -1360,91 +1075,32 @@ "status": "SUCCESS", "lastOperDataObject": [ { - "serialNumber": "FDO21120U5D", "deviceName": "leaf1", - "version": "10.2(5)", - "policy": "KR5M", - "status": "In-Sync", - "reason": "Upgrade", - "imageStaged": "Success", + "serialNumber": "FDO21120U5D", "validated": "Success", - "upgrade": "Success", - "upgGroups": "null", - "mode": "Normal", - "systemMode": "Normal", - "vpcRole": "null", - "vpcPeer": "null", - "role": "leaf", - "lastUpgAction": "2023-Oct-19 02:20", - "model": "N9K-C93180YC-EX", - "fabric": "easy", - "ipAddress": "172.22.150.102", - "issuAllowed": "", - "statusPercent": 100, - "imageStagedPercent": 100, "validatedPercent": 100, - "upgradePercent": 100, - "modelType": 0, - "vdcId": 0, - "ethswitchid": 145740, - "platform": "N9K", - "vpc_role": "null", - "ip_address": "172.22.150.102", - "peer": "null", - "vdc_id": -1, - "sys_name": "leaf1", - "id": 1, - "group": "easy", - "fcoEEnabled": "False", - "mds": "False" + "ipAddress": "172.22.150.102" }, { - "serialNumber": "FDO2112189M", "deviceName": "cvd-2313-leaf", - "version": "10.2(5)", - "policy": "KR5M", - "status": "In-Sync", - "reason": "Upgrade", - "imageStaged": "Success", + "serialNumber": "FDO2112189M", "validated": "Success", - "upgrade": "Success", - "upgGroups": "null", - "mode": "Normal", - "systemMode": "Normal", - "vpcRole": "null", - "vpcPeer": "null", - "role": "leaf", - "lastUpgAction": "2023-Oct-06 03:43", - "model": "N9K-C93180YC-EX", - "fabric": "hard", - "ipAddress": "172.22.150.108", - "issuAllowed": "", - "statusPercent": 100, - "imageStagedPercent": 100, "validatedPercent": 100, - "upgradePercent": 100, - "modelType": 0, - "vdcId": 0, - "ethswitchid": 39890, - "platform": "N9K", - "vpc_role": "null", - "ip_address": "172.22.150.108", - "peer": "null", - "vdc_id": -1, - "sys_name": "cvd-2313-leaf", - "id": 2, - "group": "hard", - "fcoEEnabled": "False", - "mds": "False" + "ipAddress": "172.22.150.108" } ], "message": "" } }, - "test_image_upgrade_validate_00006a": { + "test_image_validate_00410a": { "TEST_NOTES": [ - "FDO21120U5D validated: Success", - "FDO2112189M validated: Failed" + "RETURN_CODE == 200", + "Entries for both serial numbers FDO21120U5D FDO2112189M are present", + "FDO21120U5D validated == Success", + "FDO2112189M validated == Failed", + "DATA.lastOperDataObject.validatedPercent is present", + "DATA.lastOperDataObject.ipAddress is present", + "DATA.lastOperDataObject.deviceName is present" ], "RETURN_CODE": 200, "METHOD": "GET", @@ -1454,187 +1110,64 @@ "status": "SUCCESS", "lastOperDataObject": [ { - "serialNumber": "FDO21120U5D", "deviceName": "leaf1", - "version": "10.2(5)", - "policy": "KR5M", - "status": "In-Sync", - "reason": "Upgrade", - "imageStaged": "Success", + "serialNumber": "FDO21120U5D", "validated": "Success", - "upgrade": "Success", - "upgGroups": "null", - "mode": "Normal", - "systemMode": "Normal", - "vpcRole": "null", - "vpcPeer": "null", - "role": "leaf", - "lastUpgAction": "2023-Oct-19 02:20", - "model": "N9K-C93180YC-EX", - "fabric": "easy", - "ipAddress": "172.22.150.102", - "issuAllowed": "", - "statusPercent": 100, - "imageStagedPercent": 100, "validatedPercent": 100, - "upgradePercent": 100, - "modelType": 0, - "vdcId": 0, - "ethswitchid": 145740, - "platform": "N9K", - "vpc_role": "null", - "ip_address": "172.22.150.102", - "peer": "null", - "vdc_id": -1, - "sys_name": "leaf1", - "id": 1, - "group": "easy", - "fcoEEnabled": "False", - "mds": "False" + "ipAddress": "172.22.150.102" }, { - "serialNumber": "FDO2112189M", "deviceName": "cvd-2313-leaf", - "version": "10.2(5)", - "policy": "KR5M", - "status": "In-Sync", - "reason": "Upgrade", - "imageStaged": "Success", + "serialNumber": "FDO2112189M", "validated": "Failed", - "upgrade": "Success", - "upgGroups": "null", - "mode": "Normal", - "systemMode": "Normal", - "vpcRole": "null", - "vpcPeer": "null", - "role": "leaf", - "lastUpgAction": "2023-Oct-06 03:43", - "model": "N9K-C93180YC-EX", - "fabric": "hard", - "ipAddress": "172.22.150.108", - "issuAllowed": "", - "statusPercent": 100, - "imageStagedPercent": 90, - "validatedPercent": 100, - "upgradePercent": 100, - "modelType": 0, - "vdcId": 0, - "ethswitchid": 39890, - "platform": "N9K", - "vpc_role": "null", - "ip_address": "172.22.150.108", - "peer": "null", - "vdc_id": -1, - "sys_name": "cvd-2313-leaf", - "id": 2, - "group": "hard", - "fcoEEnabled": "False", - "mds": "False" + "validatedPercent": 90, + "ipAddress": "172.22.150.108" } ], "message": "" } }, - "test_image_upgrade_validate_00007a": { + "test_image_validate_00420a": { "TEST_NOTES": [ + "RETURN_CODE == 200", + "Entries for both serial numbers FDO21120U5D FDO2112189M are present", "FDO21120U5D validated: Success", - "FDO21120U5D imageStagedPercent: 100", "FDO2112189M validated: In-Progress", - "FDO2112189M imageStagedPercent: 50" + "FDO2112189M imageStage == In-Progress", + "FDO21120U5D validatedPercent: 100", + "FDO2112189M validatedPercent: 50", + "DATA.lastOperDataObject[*].ipAddress is present", + "DATA.lastOperDataObject[*].deviceName is present" ], "RETURN_CODE": 200, "METHOD": "GET", "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/packagemgnt/issu", "MESSAGE": "OK", "DATA": { - "status": "SUCCESS", - "lastOperDataObject": [ - { - "serialNumber": "FDO21120U5D", - "deviceName": "leaf1", - "version": "10.2(5)", - "policy": "KR5M", - "status": "In-Sync", - "reason": "Upgrade", - "imageStaged": "Success", - "validated": "Success", - "upgrade": "Success", - "upgGroups": "null", - "mode": "Normal", - "systemMode": "Normal", - "vpcRole": "null", - "vpcPeer": "null", - "role": "leaf", - "lastUpgAction": "2023-Oct-19 02:20", - "model": "N9K-C93180YC-EX", - "fabric": "easy", - "ipAddress": "172.22.150.102", - "issuAllowed": "", - "statusPercent": 100, - "imageStagedPercent": 100, - "validatedPercent": 100, - "upgradePercent": 100, - "modelType": 0, - "vdcId": 0, - "ethswitchid": 145740, - "platform": "N9K", - "vpc_role": "null", - "ip_address": "172.22.150.102", - "peer": "null", - "vdc_id": -1, - "sys_name": "leaf1", - "id": 1, - "group": "easy", - "fcoEEnabled": "False", - "mds": "False" - }, - { - "serialNumber": "FDO2112189M", - "deviceName": "cvd-2313-leaf", - "version": "10.2(5)", - "policy": "KR5M", - "status": "In-Sync", - "reason": "Upgrade", - "imageStaged": "Success", - "validated": "In-Progress", - "upgrade": "Success", - "upgGroups": "null", - "mode": "Normal", - "systemMode": "Normal", - "vpcRole": "null", - "vpcPeer": "null", - "role": "leaf", - "lastUpgAction": "2023-Oct-06 03:43", - "model": "N9K-C93180YC-EX", - "fabric": "hard", - "ipAddress": "172.22.150.108", - "issuAllowed": "", - "statusPercent": 100, - "imageStagedPercent": 50, + "status": "SUCCESS", + "lastOperDataObject": [ + { + "deviceName": "leaf1", + "serialNumber": "FDO21120U5D", + "validated": "Success", "validatedPercent": 100, - "upgradePercent": 100, - "modelType": 0, - "vdcId": 0, - "ethswitchid": 39890, - "platform": "N9K", - "vpc_role": "null", - "ip_address": "172.22.150.108", - "peer": "null", - "vdc_id": -1, - "sys_name": "cvd-2313-leaf", - "id": 2, - "group": "hard", - "fcoEEnabled": "False", - "mds": "False" + "ipAddress": "172.22.150.102" + }, + { + "deviceName": "cvd-2313-leaf", + "serialNumber": "FDO2112189M", + "validated": "In-Progress", + "validatedPercent": 50, + "ipAddress": "172.22.150.108" } ], "message": "" } }, - "test_image_upgrade_validate_00008a": { + "test_image_validate_00500a": { "TEST_NOTES": [ - "FDO21120U5D imageStaged, upgrade, validated: Success", - "FDO2112189M imageStaged, upgrade, validated: Success" + "FDO21120U5D upgrade, validated, imageStaged == Success", + "FDO2112189M upgrade, validated, imageStaged == Success" ], "RETURN_CODE": 200, "METHOD": "GET", @@ -1645,91 +1178,25 @@ "lastOperDataObject": [ { "serialNumber": "FDO21120U5D", - "deviceName": "leaf1", - "version": "10.2(5)", - "policy": "KR5M", - "status": "In-Sync", - "reason": "Upgrade", "imageStaged": "Success", - "validated": "Success", "upgrade": "Success", - "upgGroups": "null", - "mode": "Normal", - "systemMode": "Normal", - "vpcRole": "null", - "vpcPeer": "null", - "role": "leaf", - "lastUpgAction": "2023-Oct-19 02:20", - "model": "N9K-C93180YC-EX", - "fabric": "easy", - "ipAddress": "172.22.150.102", - "issuAllowed": "", - "statusPercent": 100, - "imageStagedPercent": 100, - "validatedPercent": 100, - "upgradePercent": 100, - "modelType": 0, - "vdcId": 0, - "ethswitchid": 145740, - "platform": "N9K", - "vpc_role": "null", - "ip_address": "172.22.150.102", - "peer": "null", - "vdc_id": -1, - "sys_name": "leaf1", - "id": 1, - "group": "easy", - "fcoEEnabled": "False", - "mds": "False" + "validated": "Success" }, { "serialNumber": "FDO2112189M", - "deviceName": "cvd-2313-leaf", - "version": "10.2(5)", - "policy": "KR5M", - "status": "In-Sync", - "reason": "Upgrade", "imageStaged": "Success", - "validated": "Success", "upgrade": "Success", - "upgGroups": "null", - "mode": "Normal", - "systemMode": "Normal", - "vpcRole": "null", - "vpcPeer": "null", - "role": "leaf", - "lastUpgAction": "2023-Oct-06 03:43", - "model": "N9K-C93180YC-EX", - "fabric": "hard", - "ipAddress": "172.22.150.108", - "issuAllowed": "", - "statusPercent": 100, - "imageStagedPercent": 100, - "validatedPercent": 100, - "upgradePercent": 100, - "modelType": 0, - "vdcId": 0, - "ethswitchid": 39890, - "platform": "N9K", - "vpc_role": "null", - "ip_address": "172.22.150.108", - "peer": "null", - "vdc_id": -1, - "sys_name": "cvd-2313-leaf", - "id": 2, - "group": "hard", - "fcoEEnabled": "False", - "mds": "False" + "validated": "Success" } ], "message": "" } }, - "test_image_upgrade_validate_00009a": { + "test_image_validate_00510a": { "TEST_NOTES": [ - "FDO21120U5D imageStaged, upgrade, validated: Success", - "FDO2112189M imageStaged, upgrade: Success", - "FDO2112189M validated: In-Progress" + "FDO21120U5D upgrade, validated, imageStaged == Success", + "FDO2112189M upgrade, imageStaged == Success", + "FDO2112189M validated == In-Progress" ], "RETURN_CODE": 200, "METHOD": "GET", @@ -1740,87 +1207,25 @@ "lastOperDataObject": [ { "serialNumber": "FDO21120U5D", - "deviceName": "leaf1", - "version": "10.2(5)", - "policy": "KR5M", - "status": "In-Sync", - "reason": "Upgrade", "imageStaged": "Success", - "validated": "Success", "upgrade": "Success", - "upgGroups": "null", - "mode": "Normal", - "systemMode": "Normal", - "vpcRole": "null", - "vpcPeer": "null", - "role": "leaf", - "lastUpgAction": "2023-Oct-19 02:20", - "model": "N9K-C93180YC-EX", - "fabric": "easy", - "ipAddress": "172.22.150.102", - "issuAllowed": "", - "statusPercent": 100, - "imageStagedPercent": 100, - "validatedPercent": 100, - "upgradePercent": 100, - "modelType": 0, - "vdcId": 0, - "ethswitchid": 145740, - "platform": "N9K", - "vpc_role": "null", - "ip_address": "172.22.150.102", - "peer": "null", - "vdc_id": -1, - "sys_name": "leaf1", - "id": 1, - "group": "easy", - "fcoEEnabled": "False", - "mds": "False" + "validated": "Success" }, { "serialNumber": "FDO2112189M", - "deviceName": "cvd-2313-leaf", - "version": "10.2(5)", - "policy": "KR5M", - "status": "In-Sync", - "reason": "Upgrade", "imageStaged": "Success", - "validated": "In-Progress", "upgrade": "Success", - "upgGroups": "null", - "mode": "Normal", - "systemMode": "Normal", - "vpcRole": "null", - "vpcPeer": "null", - "role": "leaf", - "lastUpgAction": "2023-Oct-06 03:43", - "model": "N9K-C93180YC-EX", - "fabric": "hard", - "ipAddress": "172.22.150.108", - "issuAllowed": "", - "statusPercent": 100, - "imageStagedPercent": 50, - "validatedPercent": 100, - "upgradePercent": 100, - "modelType": 0, - "vdcId": 0, - "ethswitchid": 39890, - "platform": "N9K", - "vpc_role": "null", - "ip_address": "172.22.150.108", - "peer": "null", - "vdc_id": -1, - "sys_name": "cvd-2313-leaf", - "id": 2, - "group": "hard", - "fcoEEnabled": "False", - "mds": "False" + "validated": "In-Progress" } ], "message": "" } }, - "test_image_upgrade_validate_00020a": { + "test_image_validate_00900a": { + "TEST_NOTES": [ + "FDO21120U5D upgrade, validated, imageStaged == Success", + "FDO2112189M upgrade, validated. imageStaged == Success" + ], "RETURN_CODE": 200, "METHOD": "GET", "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/packagemgnt/issu", @@ -1830,87 +1235,51 @@ "lastOperDataObject": [ { "serialNumber": "FDO21120U5D", - "deviceName": "leaf1", - "version": "10.2(5)", - "policy": "KR5M", - "status": "In-Sync", - "reason": "Upgrade", "imageStaged": "Success", - "validated": "Success", "upgrade": "Success", - "upgGroups": "null", - "mode": "Normal", - "systemMode": "Normal", - "vpcRole": "null", - "vpcPeer": "null", - "role": "leaf", - "lastUpgAction": "2023-Oct-19 02:20", - "model": "N9K-C93180YC-EX", - "fabric": "easy", - "ipAddress": "172.22.150.102", - "issuAllowed": "", - "statusPercent": 100, - "imageStagedPercent": 100, - "validatedPercent": 100, - "upgradePercent": 100, - "modelType": 0, - "vdcId": 0, - "ethswitchid": 145740, - "platform": "N9K", - "vpc_role": "null", - "ip_address": "172.22.150.102", - "peer": "null", - "vdc_id": -1, - "sys_name": "leaf1", - "id": 1, - "group": "easy", - "fcoEEnabled": "False", - "mds": "False" + "validated": "Success" }, { "serialNumber": "FDO2112189M", - "deviceName": "cvd-2313-leaf", - "version": "10.2(5)", - "policy": "KR5M", - "status": "In-Sync", - "reason": "Upgrade", - "imageStaged": "Failed", - "validated": "Success", + "imageStaged": "Success", "upgrade": "Success", - "upgGroups": "null", - "mode": "Normal", - "systemMode": "Normal", - "vpcRole": "null", - "vpcPeer": "null", - "role": "leaf", - "lastUpgAction": "2023-Oct-06 03:43", - "model": "N9K-C93180YC-EX", - "fabric": "hard", - "ipAddress": "172.22.150.108", - "issuAllowed": "", - "statusPercent": 100, - "imageStagedPercent": 100, - "validatedPercent": 100, - "upgradePercent": 100, - "modelType": 0, - "vdcId": 0, - "ethswitchid": 39890, - "platform": "N9K", - "vpc_role": "null", - "ip_address": "172.22.150.108", - "peer": "null", - "vdc_id": -1, - "sys_name": "cvd-2313-leaf", - "id": 2, - "group": "hard", - "fcoEEnabled": "False", - "mds": "False" + "validated": "Success" + } + ], + "message": "" + } + }, + "test_image_validate_00930a": { + "TEST_NOTES": [ + "RETURN_CODE == 200", + "Using only for RETURN_CODE == 200" + ], + "RETURN_CODE": 200, + "METHOD": "GET", + "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/packagemgnt/issu", + "MESSAGE": "OK", + "DATA": { + "status": "SUCCESS", + "lastOperDataObject": [ + { + "serialNumber": "FDO21120U5D", + "validated": "Success" } ], "message": "" } }, - "test_image_upgrade_validate_00023a": { + "test_image_validate_00940a": { + "TEST_NOTES": [ + "RETURN_CODE == 200", + "MESSAGE == OK", + "DATA.lastOperDataObject.deviceName == leaf1", + "DATA.lastOperDataObject.validated == null", + "DATA.lastOperDataObject.validatedPercent == 0", + "DATA.lastOperDataObject.ipAddress == 172.22.150.102", + "DATA.lastOperDataObject.policy == KR5M", + "DATA.lastOperDataObject.serialNumber == FDO21120U5D" + ], "RETURN_CODE": 200, "METHOD": "GET", "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/packagemgnt/issu", @@ -1919,88 +1288,32 @@ "status": "SUCCESS", "lastOperDataObject": [ { - "serialNumber": "FDO21120U5D", "deviceName": "leaf1", - "version": "10.2(5)", - "policy": "KR5M", - "status": "In-Sync", - "reason": "Upgrade", - "imageStaged": "Success", - "validated": "Success", - "upgrade": "Success", - "upgGroups": "null", - "mode": "Normal", - "systemMode": "Normal", - "vpcRole": "null", - "vpcPeer": "null", - "role": "leaf", - "lastUpgAction": "2023-Oct-19 02:20", - "model": "N9K-C93180YC-EX", - "fabric": "easy", - "ipAddress": "172.22.150.102", - "issuAllowed": "", - "statusPercent": 100, + "imageStaged": "", "imageStagedPercent": 100, - "validatedPercent": 100, - "upgradePercent": 100, - "modelType": 0, - "vdcId": 0, - "ethswitchid": 145740, - "platform": "N9K", - "vpc_role": "null", - "ip_address": "172.22.150.102", - "peer": "null", - "vdc_id": -1, - "sys_name": "leaf1", - "id": 1, - "group": "easy", - "fcoEEnabled": "False", - "mds": "False" - }, - { - "serialNumber": "FDO2112189M", - "deviceName": "cvd-2313-leaf", - "version": "10.2(5)", + "ipAddress": "172.22.150.102", "policy": "KR5M", - "status": "In-Sync", - "reason": "Upgrade", - "imageStaged": "Failed", - "validated": "Success", - "upgrade": "Success", - "upgGroups": "null", - "mode": "Normal", - "systemMode": "Normal", - "vpcRole": "null", - "vpcPeer": "null", - "role": "leaf", - "lastUpgAction": "2023-Oct-06 03:43", - "model": "N9K-C93180YC-EX", - "fabric": "hard", - "ipAddress": "172.22.150.108", - "issuAllowed": "", - "statusPercent": 100, - "imageStagedPercent": 100, - "validatedPercent": 100, - "upgradePercent": 100, - "modelType": 0, - "vdcId": 0, - "ethswitchid": 39890, - "platform": "N9K", - "vpc_role": "null", - "ip_address": "172.22.150.108", - "peer": "null", - "vdc_id": -1, - "sys_name": "cvd-2313-leaf", - "id": 2, - "group": "hard", - "fcoEEnabled": "False", - "mds": "False" + "serialNumber": "FDO21120U5D", + "validated": "", + "validatedPercent": 0, + "upgrade": "", + "upgradePercent": 0 } ], "message": "" } }, - "test_image_upgrade_validate_00024a": { + "test_image_validate_00940b": { + "TEST_NOTES": [ + "RETURN_CODE == 200", + "MESSAGE == OK", + "DATA.lastOperDataObject.deviceName == leaf1", + "DATA.lastOperDataObject.validated == Success", + "DATA.lastOperDataObject.validatedPercent == 100", + "DATA.lastOperDataObject.ipAddress == 172.22.150.102", + "DATA.lastOperDataObject.policy == KR5M", + "DATA.lastOperDataObject.serialNumber == FDO21120U5D" + ], "RETURN_CODE": 200, "METHOD": "GET", "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/packagemgnt/issu", @@ -2009,43 +1322,16 @@ "status": "SUCCESS", "lastOperDataObject": [ { - "serialNumber": "FDO21120U5D", "deviceName": "leaf1", - "version": "10.2(5)", - "policy": "KR5M", - "status": "In-Sync", - "reason": "Upgrade", "imageStaged": "Success", - "validated": "Success", - "upgrade": "Success", - "upgGroups": "null", - "mode": "Normal", - "systemMode": "Normal", - "vpcRole": "null", - "vpcPeer": "null", - "role": "leaf", - "lastUpgAction": "2023-Oct-19 02:20", - "model": "N9K-C93180YC-EX", - "fabric": "easy", - "ipAddress": "172.22.150.102", - "issuAllowed": "", - "statusPercent": 100, "imageStagedPercent": 100, + "ipAddress": "172.22.150.102", + "policy": "KR5M", + "serialNumber": "FDO21120U5D", + "validated": "Success", "validatedPercent": 100, - "upgradePercent": 0, - "modelType": 0, - "vdcId": 0, - "ethswitchid": 145740, - "platform": "N9K", - "vpc_role": "null", - "ip_address": "172.22.150.102", - "peer": "null", - "vdc_id": -1, - "sys_name": "leaf1", - "id": 1, - "group": "easy", - "fcoEEnabled": "False", - "mds": "False" + "upgrade": "", + "upgradePercent": 0 } ], "message": "" @@ -2771,7 +2057,7 @@ "message": "" } }, - "test_image_upgrade_upgrade_00004a": { + "test_image_upgrade_00100a": { "TEST_NOTES": [ "FDO21120U5D imageStaged == Success", "FDO2112189M imageStage == Success" @@ -2865,101 +2151,112 @@ "message": "" } }, - "test_image_upgrade_upgrade_00005a": { - "TEST_NOTES": [ - "FDO21120U5D imageStaged == Success", - "FDO2112189M imageStage == Failed" - ], - "RETURN_CODE": 200, - "METHOD": "GET", - "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/packagemgnt/issu", - "MESSAGE": "OK", + "test_image_upgrade_01010a": { "DATA": { - "status": "SUCCESS", "lastOperDataObject": [ { - "serialNumber": "FDO21120U5D", "deviceName": "leaf1", - "version": "10.2(5)", - "policy": "KR5M", - "status": "In-Sync", - "reason": "Upgrade", "imageStaged": "Success", - "validated": "Success", - "upgrade": "Success", - "upgGroups": "null", - "mode": "Normal", - "systemMode": "Normal", - "vpcRole": "null", - "vpcPeer": "null", - "role": "leaf", - "lastUpgAction": "2023-Oct-19 02:20", - "model": "N9K-C93180YC-EX", - "fabric": "easy", + "imageStagedPercent": 100, "ipAddress": "172.22.150.102", - "issuAllowed": "", + "ip_address": "172.22.150.102", + "policy": "KR5M", + "serialNumber": "FDO21120U5D", "statusPercent": 100, - "imageStagedPercent": 100, - "validatedPercent": 100, + "sys_name": "leaf1", + "upgrade": "Success", "upgradePercent": 100, - "modelType": 0, - "vdcId": 0, - "ethswitchid": 145740, - "platform": "N9K", - "vpc_role": "null", + "validated": "Success", + "validatedPercent": 100 + } + ], + "message": "", + "status": "SUCCESS" + }, + "MESSAGE": "OK", + "METHOD": "GET", + "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/packagemgnt/issu", + "RETURN_CODE": 200 + }, + "test_image_upgrade_01020a": { + "DATA": { + "lastOperDataObject": [ + { + "deviceName": "leaf1", + "imageStaged": "Success", + "imageStagedPercent": 100, + "ipAddress": "172.22.150.102", "ip_address": "172.22.150.102", - "peer": "null", - "vdc_id": -1, + "policy": "KR5M", + "serialNumber": "FDO21120U5D", + "statusPercent": 100, "sys_name": "leaf1", - "id": 1, - "group": "easy", - "fcoEEnabled": "False", - "mds": "False" - }, + "upgrade": "Success", + "upgradePercent": 100, + "validated": "Success", + "validatedPercent": 100 + } + ], + "message": "", + "status": "SUCCESS" + }, + "MESSAGE": "OK", + "METHOD": "GET", + "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/packagemgnt/issu", + "RETURN_CODE": 200 + }, + "test_image_upgrade_01030a": { + "DATA": { + "lastOperDataObject": [ { - "serialNumber": "FDO2112189M", - "deviceName": "cvd-2313-leaf", - "version": "10.2(5)", - "policy": "KR5M", - "status": "In-Sync", - "reason": "Upgrade", + "deviceName": "leaf1", + "ethswitchid": 165300, + "fabric": "f8", + "fcoEEnabled": false, + "group": "f8", + "id": 1, "imageStaged": "Success", - "validated": "Success", - "upgrade": "Failed", - "upgGroups": "null", + "imageStagedPercent": 100, + "ipAddress": "172.22.150.102", + "ip_address": "172.22.150.102", + "issuAllowed": "", + "lastUpgAction": "2023-Nov-08 02:11", + "mds": false, "mode": "Normal", - "systemMode": "Normal", - "vpcRole": "null", - "vpcPeer": "null", - "role": "leaf", - "lastUpgAction": "2023-Oct-06 03:43", "model": "N9K-C93180YC-EX", - "fabric": "hard", - "ipAddress": "172.22.150.108", - "issuAllowed": "", - "statusPercent": 100, - "imageStagedPercent": 100, - "validatedPercent": 100, + "modelType": 0, + "peer": null, + "platform": "N9K", + "policy": "NR3F", + "reason": "Validate", + "role": "leaf", + "serialNumber": "FDO21120U5D", + "status": "Success", + "statusPercent": 100, + "sys_name": "leaf1", + "systemMode": "Normal", + "upgGroups": "None", + "upgrade": "Success", "upgradePercent": 100, - "modelType": 0, + "validated": "Success", + "validatedPercent": 100, "vdcId": 0, - "ethswitchid": 39890, - "platform": "N9K", - "vpc_role": "null", - "ip_address": "172.22.150.108", - "peer": "null", "vdc_id": -1, - "sys_name": "cvd-2313-leaf", - "id": 2, - "group": "hard", - "fcoEEnabled": "False", - "mds": "False" + "version": "10.2(5)", + "vpcPeer": null, + "vpcRole": null, + "vpc_role": null } ], - "message": "" - } + "message": "", + "status": "SUCCESS" + }, + "MESSAGE": "OK", + "METHOD": "GET", + "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/packagemgnt/issu", + "RETURN_CODE": 200 }, - "test_image_upgrade_upgrade_00019a": { + "test_image_upgrade_01040a": { "DATA": { "lastOperDataObject": [ { @@ -2974,24 +2271,24 @@ "ipAddress": "172.22.150.102", "ip_address": "172.22.150.102", "issuAllowed": "", - "lastUpgAction": "2023-Nov-07 23:47", + "lastUpgAction": "2023-Nov-08 02:11", "mds": false, "mode": "Normal", "model": "N9K-C93180YC-EX", "modelType": 0, "peer": null, "platform": "N9K", - "policy": "KR5M", - "reason": "Upgrade", + "policy": "NR3F", + "reason": "Validate", "role": "leaf", "serialNumber": "FDO21120U5D", - "status": "In-Sync", + "status": "Out-Of-Sync", "statusPercent": 100, "sys_name": "leaf1", "systemMode": "Normal", "upgGroups": "None", - "upgrade": "Success", - "upgradePercent": 100, + "upgrade": "None", + "upgradePercent": 0, "validated": "Success", "validatedPercent": 100, "vdcId": 0, @@ -3010,7 +2307,7 @@ "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/packagemgnt/issu", "RETURN_CODE": 200 }, - "test_image_upgrade_upgrade_00020a": { + "test_image_upgrade_01050a": { "DATA": { "lastOperDataObject": [ { @@ -3033,15 +2330,15 @@ "peer": null, "platform": "N9K", "policy": "NR3F", - "reason": "Validate", + "reason": "Upgrade", "role": "leaf", "serialNumber": "FDO21120U5D", - "status": "Out-Of-Sync", + "status": "Success", "statusPercent": 100, "sys_name": "leaf1", "systemMode": "Normal", "upgGroups": "None", - "upgrade": "None", + "upgrade": "", "upgradePercent": 0, "validated": "Success", "validatedPercent": 100, @@ -3061,7 +2358,7 @@ "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/packagemgnt/issu", "RETURN_CODE": 200 }, - "test_image_upgrade_upgrade_00021a": { + "test_image_upgrade_01050b": { "DATA": { "lastOperDataObject": [ { @@ -3084,16 +2381,16 @@ "peer": null, "platform": "N9K", "policy": "NR3F", - "reason": "Validate", + "reason": "Upgrade", "role": "leaf", "serialNumber": "FDO21120U5D", - "status": "Out-Of-Sync", + "status": "Success", "statusPercent": 100, "sys_name": "leaf1", "systemMode": "Normal", "upgGroups": "None", - "upgrade": "None", - "upgradePercent": 0, + "upgrade": "Success", + "upgradePercent": 100, "validated": "Success", "validatedPercent": 100, "vdcId": 0, @@ -3112,7 +2409,7 @@ "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/packagemgnt/issu", "RETURN_CODE": 200 }, - "test_image_upgrade_upgrade_00022a": { + "test_image_upgrade_01060a": { "DATA": { "lastOperDataObject": [ { @@ -3135,15 +2432,15 @@ "peer": null, "platform": "N9K", "policy": "NR3F", - "reason": "Validate", + "reason": "Upgrade", "role": "leaf", "serialNumber": "FDO21120U5D", - "status": "Out-Of-Sync", + "status": "Success", "statusPercent": 100, "sys_name": "leaf1", "systemMode": "Normal", "upgGroups": "None", - "upgrade": "None", + "upgrade": "", "upgradePercent": 0, "validated": "Success", "validatedPercent": 100, @@ -3163,7 +2460,7 @@ "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/packagemgnt/issu", "RETURN_CODE": 200 }, - "test_image_upgrade_upgrade_00023a": { + "test_image_upgrade_01060b": { "DATA": { "lastOperDataObject": [ { @@ -3186,16 +2483,16 @@ "peer": null, "platform": "N9K", "policy": "NR3F", - "reason": "Validate", + "reason": "Upgrade", "role": "leaf", "serialNumber": "FDO21120U5D", - "status": "Out-Of-Sync", + "status": "Success", "statusPercent": 100, "sys_name": "leaf1", "systemMode": "Normal", "upgGroups": "None", - "upgrade": "None", - "upgradePercent": 0, + "upgrade": "Success", + "upgradePercent": 100, "validated": "Success", "validatedPercent": 100, "vdcId": 0, @@ -3214,7 +2511,7 @@ "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/packagemgnt/issu", "RETURN_CODE": 200 }, - "test_image_upgrade_upgrade_00024a": { + "test_image_upgrade_01070a": { "DATA": { "lastOperDataObject": [ { @@ -3265,7 +2562,7 @@ "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/packagemgnt/issu", "RETURN_CODE": 200 }, - "test_image_upgrade_upgrade_00025a": { + "test_image_upgrade_01080a": { "DATA": { "lastOperDataObject": [ { @@ -3316,7 +2613,7 @@ "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/packagemgnt/issu", "RETURN_CODE": 200 }, - "test_image_upgrade_upgrade_00026a": { + "test_image_upgrade_01090a": { "DATA": { "lastOperDataObject": [ { @@ -3367,7 +2664,7 @@ "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/packagemgnt/issu", "RETURN_CODE": 200 }, - "test_image_upgrade_upgrade_00027a": { + "test_image_upgrade_01100a": { "DATA": { "lastOperDataObject": [ { @@ -3418,7 +2715,7 @@ "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/packagemgnt/issu", "RETURN_CODE": 200 }, - "test_image_upgrade_upgrade_00028a": { + "test_image_upgrade_01110a": { "DATA": { "lastOperDataObject": [ { @@ -3469,7 +2766,7 @@ "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/packagemgnt/issu", "RETURN_CODE": 200 }, - "test_image_upgrade_upgrade_00029a": { + "test_image_upgrade_01120a": { "DATA": { "lastOperDataObject": [ { @@ -3520,7 +2817,7 @@ "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/packagemgnt/issu", "RETURN_CODE": 200 }, - "test_image_upgrade_upgrade_00030a": { + "test_image_upgrade_01130a": { "DATA": { "lastOperDataObject": [ { @@ -3571,7 +2868,7 @@ "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/packagemgnt/issu", "RETURN_CODE": 200 }, - "test_image_upgrade_upgrade_00031a": { + "test_image_upgrade_01140a": { "DATA": { "lastOperDataObject": [ { @@ -3622,7 +2919,7 @@ "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/packagemgnt/issu", "RETURN_CODE": 200 }, - "test_image_upgrade_upgrade_00032a": { + "test_image_upgrade_01150a": { "DATA": { "lastOperDataObject": [ { @@ -3673,7 +2970,7 @@ "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/packagemgnt/issu", "RETURN_CODE": 200 }, - "test_image_upgrade_upgrade_00033a": { + "test_image_upgrade_01160a": { "DATA": { "lastOperDataObject": [ { @@ -3724,7 +3021,7 @@ "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/packagemgnt/issu", "RETURN_CODE": 200 }, - "test_image_upgrade_upgrade_00045a": { + "test_image_upgrade_02000a": { "DATA": { "lastOperDataObject": [ { @@ -3739,148 +3036,423 @@ "ipAddress": "172.22.150.102", "ip_address": "172.22.150.102", "issuAllowed": "", - "lastUpgAction": "2023-Nov-07 23:47", + "lastUpgAction": "2023-Nov-08 02:11", "mds": false, "mode": "Normal", "model": "N9K-C93180YC-EX", "modelType": 0, "peer": null, "platform": "N9K", - "policy": "KR5M", - "reason": "Upgrade", + "policy": "NR3F", + "reason": "Validate", "role": "leaf", "serialNumber": "FDO21120U5D", - "status": "In-Sync", + "status": "Out-Of-Sync", "statusPercent": 100, "sys_name": "leaf1", "systemMode": "Normal", "upgGroups": "None", + "upgrade": "None", + "upgradePercent": 0, + "validated": "Success", + "validatedPercent": 100, + "vdcId": 0, + "vdc_id": -1, + "version": "10.2(5)", + "vpcPeer": null, + "vpcRole": null, + "vpc_role": null + } + ], + "message": "", + "status": "SUCCESS" + }, + "MESSAGE": "OK", + "METHOD": "GET", + "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/packagemgnt/issu", + "RETURN_CODE": 200 + }, + "test_image_upgrade_04000a": { + "TEST_NOTES": [ + "172.22.150.102 validated, upgrade, imageStaged: Success", + "172.22.150.108 validated, upgrade, imageStaged: Success" + ], + "RETURN_CODE": 200, + "METHOD": "GET", + "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/packagemgnt/issu", + "MESSAGE": "OK", + "DATA": { + "status": "SUCCESS", + "lastOperDataObject": [ + { + "serialNumber": "FDO21120U5D", + "deviceName": "leaf1", + "version": "10.2(5)", + "policy": "KR5M", + "status": "In-Sync", + "reason": "Upgrade", + "imageStaged": "Success", + "validated": "Success", + "upgrade": "Success", + "upgGroups": "null", + "mode": "Normal", + "systemMode": "Normal", + "vpcRole": "null", + "vpcPeer": "null", + "role": "leaf", + "lastUpgAction": "2023-Oct-19 02:20", + "model": "N9K-C93180YC-EX", + "fabric": "easy", + "ipAddress": "172.22.150.102", + "issuAllowed": "", + "statusPercent": 100, + "imageStagedPercent": 100, + "validatedPercent": 100, + "upgradePercent": 100, + "modelType": 0, + "vdcId": 0, + "ethswitchid": 145740, + "platform": "N9K", + "vpc_role": "null", + "ip_address": "172.22.150.102", + "peer": "null", + "vdc_id": -1, + "sys_name": "leaf1", + "id": 1, + "group": "easy", + "fcoEEnabled": "False", + "mds": "False" + }, + { + "serialNumber": "FDO2112189M", + "deviceName": "cvd-2313-leaf", + "version": "10.2(5)", + "policy": "KR5M", + "status": "In-Sync", + "reason": "Upgrade", + "imageStaged": "Success", + "validated": "Success", + "upgrade": "Success", + "upgGroups": "null", + "mode": "Normal", + "systemMode": "Normal", + "vpcRole": "null", + "vpcPeer": "null", + "role": "leaf", + "lastUpgAction": "2023-Oct-06 03:43", + "model": "N9K-C93180YC-EX", + "fabric": "hard", + "ipAddress": "172.22.150.108", + "issuAllowed": "", + "statusPercent": 100, + "imageStagedPercent": 100, + "validatedPercent": 100, + "upgradePercent": 100, + "modelType": 0, + "vdcId": 0, + "ethswitchid": 39890, + "platform": "N9K", + "vpc_role": "null", + "ip_address": "172.22.150.108", + "peer": "null", + "vdc_id": -1, + "sys_name": "cvd-2313-leaf", + "id": 2, + "group": "hard", + "fcoEEnabled": "False", + "mds": "False" + } + ], + "message": "" + } + }, + "test_image_upgrade_04100a": { + "TEST_NOTES": [ + "172.22.150.102 validated: Success, upgrade: Success, imageStaged: In-Progress", + "172.22.150.108 validated: Success, upgrade: Success, imageStaged: In-Progress" + ], + "RETURN_CODE": 200, + "METHOD": "GET", + "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/packagemgnt/issu", + "MESSAGE": "OK", + "DATA": { + "status": "SUCCESS", + "lastOperDataObject": [ + { + "serialNumber": "FDO21120U5D", + "deviceName": "leaf1", + "version": "10.2(5)", + "policy": "KR5M", + "status": "In-Sync", + "reason": "Upgrade", + "imageStaged": "In-Progress", + "validated": "Success", "upgrade": "Success", + "upgGroups": "null", + "mode": "Normal", + "systemMode": "Normal", + "vpcRole": "null", + "vpcPeer": "null", + "role": "leaf", + "lastUpgAction": "2023-Oct-19 02:20", + "model": "N9K-C93180YC-EX", + "fabric": "easy", + "ipAddress": "172.22.150.102", + "issuAllowed": "", + "statusPercent": 100, + "imageStagedPercent": 90, + "validatedPercent": 100, "upgradePercent": 100, + "modelType": 0, + "vdcId": 0, + "ethswitchid": 145740, + "platform": "N9K", + "vpc_role": "null", + "ip_address": "172.22.150.102", + "peer": "null", + "vdc_id": -1, + "sys_name": "leaf1", + "id": 1, + "group": "easy", + "fcoEEnabled": "False", + "mds": "False" + }, + { + "serialNumber": "FDO2112189M", + "deviceName": "cvd-2313-leaf", + "version": "10.2(5)", + "policy": "KR5M", + "status": "In-Sync", + "reason": "Upgrade", + "imageStaged": "In-Progress", "validated": "Success", + "upgrade": "Success", + "upgGroups": "null", + "mode": "Normal", + "systemMode": "Normal", + "vpcRole": "null", + "vpcPeer": "null", + "role": "leaf", + "lastUpgAction": "2023-Oct-06 03:43", + "model": "N9K-C93180YC-EX", + "fabric": "hard", + "ipAddress": "172.22.150.108", + "issuAllowed": "", + "statusPercent": 100, + "imageStagedPercent": 50, "validatedPercent": 100, + "upgradePercent": 100, + "modelType": 0, "vdcId": 0, + "ethswitchid": 39890, + "platform": "N9K", + "vpc_role": "null", + "ip_address": "172.22.150.108", + "peer": "null", "vdc_id": -1, - "version": "10.2(5)", - "vpcPeer": null, - "vpcRole": null, - "vpc_role": null + "sys_name": "cvd-2313-leaf", + "id": 2, + "group": "hard", + "fcoEEnabled": "False", + "mds": "False" } ], - "message": "", - "status": "SUCCESS" - }, - "MESSAGE": "OK", + "message": "" + } + }, + "test_image_upgrade_04100b": { + "TEST_NOTES": [ + "172.22.150.102 validated: Success, upgrade: Success, imageStaged: Success", + "172.22.150.108 validated: Success, upgrade: Success, imageStaged: In-Progress" + ], + "RETURN_CODE": 200, "METHOD": "GET", "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/packagemgnt/issu", - "RETURN_CODE": 200 - }, - "test_image_upgrade_upgrade_00046a": { + "MESSAGE": "OK", "DATA": { + "status": "SUCCESS", "lastOperDataObject": [ { + "serialNumber": "FDO21120U5D", "deviceName": "leaf1", - "ethswitchid": 165300, - "fabric": "f8", - "fcoEEnabled": false, - "group": "f8", - "id": 1, + "version": "10.2(5)", + "policy": "KR5M", + "status": "In-Sync", + "reason": "Upgrade", "imageStaged": "Success", - "imageStagedPercent": 100, - "ipAddress": "172.22.150.102", - "ip_address": "172.22.150.102", - "issuAllowed": "", - "lastUpgAction": "2023-Nov-07 23:47", - "mds": false, + "validated": "Success", + "upgrade": "Success", + "upgGroups": "null", "mode": "Normal", + "systemMode": "Normal", + "vpcRole": "null", + "vpcPeer": "null", + "role": "leaf", + "lastUpgAction": "2023-Oct-19 02:20", "model": "N9K-C93180YC-EX", + "fabric": "easy", + "ipAddress": "172.22.150.102", + "issuAllowed": "", + "statusPercent": 100, + "imageStagedPercent": 100, + "validatedPercent": 100, + "upgradePercent": 100, "modelType": 0, - "peer": null, + "vdcId": 0, + "ethswitchid": 145740, "platform": "N9K", + "vpc_role": "null", + "ip_address": "172.22.150.102", + "peer": "null", + "vdc_id": -1, + "sys_name": "leaf1", + "id": 1, + "group": "easy", + "fcoEEnabled": "False", + "mds": "False" + }, + { + "serialNumber": "FDO2112189M", + "deviceName": "cvd-2313-leaf", + "version": "10.2(5)", "policy": "KR5M", + "status": "In-Sync", "reason": "Upgrade", + "imageStaged": "In-Progress", + "validated": "Success", + "upgrade": "Success", + "upgGroups": "null", + "mode": "Normal", + "systemMode": "Normal", + "vpcRole": "null", + "vpcPeer": "null", "role": "leaf", - "serialNumber": "FDO21120U5D", - "status": "In-Sync", + "lastUpgAction": "2023-Oct-06 03:43", + "model": "N9K-C93180YC-EX", + "fabric": "hard", + "ipAddress": "172.22.150.108", + "issuAllowed": "", "statusPercent": 100, - "sys_name": "leaf1", - "systemMode": "Normal", - "upgGroups": "None", - "upgrade": "Success", - "upgradePercent": 100, - "validated": "Success", + "imageStagedPercent": 90, "validatedPercent": 100, + "upgradePercent": 100, + "modelType": 0, "vdcId": 0, + "ethswitchid": 39890, + "platform": "N9K", + "vpc_role": "null", + "ip_address": "172.22.150.108", + "peer": "null", "vdc_id": -1, - "version": "10.2(5)", - "vpcPeer": null, - "vpcRole": null, - "vpc_role": null + "sys_name": "cvd-2313-leaf", + "id": 2, + "group": "hard", + "fcoEEnabled": "False", + "mds": "False" } ], - "message": "", - "status": "SUCCESS" - }, - "MESSAGE": "OK", + "message": "" + } + }, + "test_image_upgrade_04100c": { + "TEST_NOTES": [ + "172.22.150.102 validated: Success, upgrade: Success, imageStaged: Success", + "172.22.150.108 validated: Success, upgrade: Success, imageStaged: Success" + ], + "RETURN_CODE": 200, "METHOD": "GET", "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/packagemgnt/issu", - "RETURN_CODE": 200 - }, - "test_image_upgrade_upgrade_00047a": { + "MESSAGE": "OK", "DATA": { + "status": "SUCCESS", "lastOperDataObject": [ { + "serialNumber": "FDO21120U5D", "deviceName": "leaf1", - "ethswitchid": 165300, - "fabric": "f8", - "fcoEEnabled": false, - "group": "f8", - "id": 1, + "version": "10.2(5)", + "policy": "KR5M", + "status": "In-Sync", + "reason": "Upgrade", "imageStaged": "Success", - "imageStagedPercent": 100, - "ipAddress": "172.22.150.102", - "ip_address": "172.22.150.102", - "issuAllowed": "", - "lastUpgAction": "2023-Nov-07 23:47", - "mds": false, + "validated": "Success", + "upgrade": "Success", + "upgGroups": "null", "mode": "Normal", + "systemMode": "Normal", + "vpcRole": "null", + "vpcPeer": "null", + "role": "leaf", + "lastUpgAction": "2023-Oct-19 02:20", "model": "N9K-C93180YC-EX", + "fabric": "easy", + "ipAddress": "172.22.150.102", + "issuAllowed": "", + "statusPercent": 100, + "imageStagedPercent": 100, + "validatedPercent": 100, + "upgradePercent": 100, "modelType": 0, - "peer": null, + "vdcId": 0, + "ethswitchid": 145740, "platform": "N9K", + "vpc_role": "null", + "ip_address": "172.22.150.102", + "peer": "null", + "vdc_id": -1, + "sys_name": "leaf1", + "id": 1, + "group": "easy", + "fcoEEnabled": "False", + "mds": "False" + }, + { + "serialNumber": "FDO2112189M", + "deviceName": "cvd-2313-leaf", + "version": "10.2(5)", "policy": "KR5M", + "status": "In-Sync", "reason": "Upgrade", + "imageStaged": "Success", + "validated": "Success", + "upgrade": "Success", + "upgGroups": "null", + "mode": "Normal", + "systemMode": "Normal", + "vpcRole": "null", + "vpcPeer": "null", "role": "leaf", - "serialNumber": "FDO21120U5D", - "status": "In-Sync", + "lastUpgAction": "2023-Oct-06 03:43", + "model": "N9K-C93180YC-EX", + "fabric": "hard", + "ipAddress": "172.22.150.108", + "issuAllowed": "", "statusPercent": 100, - "sys_name": "leaf1", - "systemMode": "Normal", - "upgGroups": "None", - "upgrade": "Success", - "upgradePercent": 100, - "validated": "Success", + "imageStagedPercent": 100, "validatedPercent": 100, + "upgradePercent": 100, + "modelType": 0, "vdcId": 0, + "ethswitchid": 39890, + "platform": "N9K", + "vpc_role": "null", + "ip_address": "172.22.150.108", + "peer": "null", "vdc_id": -1, - "version": "10.2(5)", - "vpcPeer": null, - "vpcRole": null, - "vpc_role": null + "sys_name": "cvd-2313-leaf", + "id": 2, + "group": "hard", + "fcoEEnabled": "False", + "mds": "False" } ], - "message": "", - "status": "SUCCESS" - }, - "MESSAGE": "OK", - "METHOD": "GET", - "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/packagemgnt/issu", - "RETURN_CODE": 200 + "message": "" + } }, - "test_image_upgrade_upgrade_00200a": { + "test_image_upgrade_04110a": { "TEST_NOTES": [ - "172.22.150.102 validated, upgrade, imageStaged: Success", - "172.22.150.108 validated, upgrade, imageStaged: Success" + "172.22.150.102 upgrade: Success", + "172.22.150.108 upgrade: In-Progress", + "172.22.150.108 upgradePercent: 50" ], "RETURN_CODE": 200, "METHOD": "GET", @@ -3937,7 +3509,7 @@ "reason": "Upgrade", "imageStaged": "Success", "validated": "Success", - "upgrade": "Success", + "upgrade": "In-Progress", "upgGroups": "null", "mode": "Normal", "systemMode": "Normal", @@ -3952,7 +3524,7 @@ "statusPercent": 100, "imageStagedPercent": 100, "validatedPercent": 100, - "upgradePercent": 100, + "upgradePercent": 50, "modelType": 0, "vdcId": 0, "ethswitchid": 39890, @@ -3971,10 +3543,11 @@ "message": "" } }, - "test_image_upgrade_upgrade_00205a": { + "test_image_upgrade_04120a": { "TEST_NOTES": [ - "172.22.150.102 validated, upgrade, imageStaged: Success", - "172.22.150.108 validated, upgrade, imageStaged: Success" + "172.22.150.102 upgrade: Success", + "172.22.150.108 upgrade: Failed", + "172.22.150.108 upgradePercent: 50" ], "RETURN_CODE": 200, "METHOD": "GET", @@ -4031,7 +3604,7 @@ "reason": "Upgrade", "imageStaged": "Success", "validated": "Success", - "upgrade": "Success", + "upgrade": "Failed", "upgGroups": "null", "mode": "Normal", "systemMode": "Normal", @@ -4046,7 +3619,7 @@ "statusPercent": 100, "imageStagedPercent": 100, "validatedPercent": 100, - "upgradePercent": 100, + "upgradePercent": 50, "modelType": 0, "vdcId": 0, "ethswitchid": 39890, @@ -4065,7 +3638,7 @@ "message": "" } }, - "test_image_upgrade_upgrade_00210a": { + "test_image_upgrade_04130a": { "TEST_NOTES": [ "172.22.150.102 upgrade: Success", "172.22.150.108 upgrade: In-Progress", @@ -4160,11 +3733,10 @@ "message": "" } }, - "test_image_upgrade_upgrade_00220a": { + "test_image_upgrade_04140a": { "TEST_NOTES": [ "172.22.150.102 upgrade: Success", - "172.22.150.108 upgrade: Failed", - "172.22.150.108 upgradePercent: 50" + "172.22.150.108 upgrade: Success" ], "RETURN_CODE": 200, "METHOD": "GET", @@ -4182,7 +3754,7 @@ "reason": "Upgrade", "imageStaged": "Success", "validated": "Success", - "upgrade": "Success", + "upgrade": "In-Progress", "upgGroups": "null", "mode": "Normal", "systemMode": "Normal", @@ -4195,9 +3767,9 @@ "ipAddress": "172.22.150.102", "issuAllowed": "", "statusPercent": 100, - "imageStagedPercent": 100, + "imageStagedPercent": 50, "validatedPercent": 100, - "upgradePercent": 100, + "upgradePercent": 30, "modelType": 0, "vdcId": 0, "ethswitchid": 145740, @@ -4221,7 +3793,7 @@ "reason": "Upgrade", "imageStaged": "Success", "validated": "Success", - "upgrade": "Failed", + "upgrade": "In-Progress", "upgGroups": "null", "mode": "Normal", "systemMode": "Normal", @@ -4235,8 +3807,8 @@ "issuAllowed": "", "statusPercent": 100, "imageStagedPercent": 100, - "validatedPercent": 100, - "upgradePercent": 50, + "validatedPercent": 10, + "upgradePercent": 100, "modelType": 0, "vdcId": 0, "ethswitchid": 39890, @@ -4255,11 +3827,10 @@ "message": "" } }, - "test_image_upgrade_upgrade_00230a": { + "test_image_upgrade_04140b": { "TEST_NOTES": [ "172.22.150.102 upgrade: Success", - "172.22.150.108 upgrade: In-Progress", - "172.22.150.108 upgradePercent: 50" + "172.22.150.108 upgrade: Success" ], "RETURN_CODE": 200, "METHOD": "GET", @@ -4331,7 +3902,7 @@ "statusPercent": 100, "imageStagedPercent": 100, "validatedPercent": 100, - "upgradePercent": 50, + "upgradePercent": 80, "modelType": 0, "vdcId": 0, "ethswitchid": 39890, @@ -4350,7 +3921,7 @@ "message": "" } }, - "test_image_upgrade_upgrade_00240a": { + "test_image_upgrade_04140c": { "TEST_NOTES": [ "172.22.150.102 upgrade: Success", "172.22.150.108 upgrade: Success" @@ -4444,7 +4015,7 @@ "message": "" } }, - "test_image_upgrade_upgrade_task_00020a": { + "test_image_upgrade_task_00020a": { "TEST_NOTES": [ "RETURN_CODE 200", "Two switches present", diff --git a/tests/unit/modules/dcnm/dcnm_image_upgrade/fixtures/image_upgrade_responses_ControllerVersion.json b/tests/unit/modules/dcnm/dcnm_image_upgrade/fixtures/responses_ep_version.json similarity index 54% rename from tests/unit/modules/dcnm/dcnm_image_upgrade/fixtures/image_upgrade_responses_ControllerVersion.json rename to tests/unit/modules/dcnm/dcnm_image_upgrade/fixtures/responses_ep_version.json index 3bb2d335e..04059591c 100644 --- a/tests/unit/modules/dcnm/dcnm_image_upgrade/fixtures/image_upgrade_responses_ControllerVersion.json +++ b/tests/unit/modules/dcnm/dcnm_image_upgrade/fixtures/responses_ep_version.json @@ -1,5 +1,5 @@ { - "test_image_upgrade_stage_00003a": { + "test_image_stage_00100a": { "RETURN_CODE": 200, "METHOD": "GET", "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/fm/about/version", @@ -15,7 +15,7 @@ "is_upgrade_inprogress": "false" } }, - "test_image_upgrade_stage_00003b": { + "test_image_stage_00100b": { "RETURN_CODE": 200, "METHOD": "GET", "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/fm/about/version", @@ -31,7 +31,7 @@ "is_upgrade_inprogress": "false" } }, - "test_image_upgrade_stage_00070a": { + "test_image_stage_00900a": { "TEST_NOTES": [ "Needed only for the 200 return code" ], @@ -50,7 +50,55 @@ "is_upgrade_inprogress": "false" } }, - "test_image_upgrade_stage_00071a": { + "test_image_stage_00910a": { + "RETURN_CODE": 200, + "METHOD": "GET", + "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/fm/about/version", + "MESSAGE": "OK", + "DATA": { + "version": "12.1.2e", + "mode": "LAN", + "isMediaController": "false", + "dev": "false", + "isHaEnabled": "false", + "install": "EASYFABRIC", + "uuid": "", + "is_upgrade_inprogress": "false" + } + }, + "test_image_stage_00910b": { + "RETURN_CODE": 200, + "METHOD": "GET", + "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/fm/about/version", + "MESSAGE": "OK", + "DATA": { + "version": "12.1.3b", + "mode": "LAN", + "isMediaController": "false", + "dev": "false", + "isHaEnabled": "false", + "install": "EASYFABRIC", + "uuid": "", + "is_upgrade_inprogress": "false" + } + }, + "test_image_stage_00930a": { + "RETURN_CODE": 200, + "METHOD": "GET", + "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/fm/about/version", + "MESSAGE": "OK", + "DATA": { + "version": "12.1.3b", + "mode": "LAN", + "isMediaController": "false", + "dev": "false", + "isHaEnabled": "false", + "install": "EASYFABRIC", + "uuid": "", + "is_upgrade_inprogress": "false" + } + }, + "test_image_stage_00940a": { "RETURN_CODE": 200, "METHOD": "GET", "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/fm/about/version", diff --git a/tests/unit/modules/dcnm/dcnm_image_upgrade/test_image_install_options.py b/tests/unit/modules/dcnm/dcnm_image_upgrade/test_image_install_options.py new file mode 100644 index 000000000..bf868e5ea --- /dev/null +++ b/tests/unit/modules/dcnm/dcnm_image_upgrade/test_image_install_options.py @@ -0,0 +1,802 @@ +# 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 + +__metaclass__ = type + +__copyright__ = "Copyright (c) 2024 Cisco and/or its affiliates." +__author__ = "Allen Robel" + +import inspect + +import pytest +from ansible_collections.ansible.netcommon.tests.unit.modules.utils import \ + AnsibleFailJson +from ansible_collections.cisco.dcnm.plugins.module_utils.common.exceptions import \ + ControllerResponseError +from ansible_collections.cisco.dcnm.plugins.module_utils.common.response_handler import \ + ResponseHandler +from ansible_collections.cisco.dcnm.plugins.module_utils.common.rest_send_v2 import \ + RestSend +from ansible_collections.cisco.dcnm.plugins.module_utils.common.results import \ + Results +from ansible_collections.cisco.dcnm.plugins.module_utils.common.sender_file import \ + Sender +from ansible_collections.cisco.dcnm.tests.unit.module_utils.common.common_utils import \ + ResponseGenerator + +from .utils import (MockAnsibleModule, does_not_raise, + image_install_options_fixture, params, + responses_ep_install_options) + +PATCH_MODULE_UTILS = "ansible_collections.cisco.dcnm.plugins.module_utils." +PATCH_IMAGE_UPGRADE = PATCH_MODULE_UTILS + "image_upgrade." +DCNM_SEND_INSTALL_OPTIONS = PATCH_IMAGE_UPGRADE + "install_options.dcnm_send" + + +def test_image_install_options_00000(image_install_options) -> None: + """ + ### Classes and Methods + + - ``ImageInstallOptions`` + - ``__init__`` + + Test + - Exceptions are not raised. + - Class attributes are initialized to expected values + """ + with does_not_raise(): + instance = image_install_options + + assert instance.class_name == "ImageInstallOptions" + assert instance.conversion.class_name == "ConversionUtils" + assert instance.ep_install_options.class_name == "EpInstallOptions" + path = "/appcenter/cisco/ndfc/api/v1/imagemanagement" + path += "/rest/imageupgrade/install-options" + assert instance.ep_install_options.path == path + assert instance.ep_install_options.verb == "POST" + assert instance.compatibility_status == {} + assert instance.payload == {} + + +def test_image_install_options_00010(image_install_options) -> None: + """ + ### Classes and Methods + + - ``ImageInstallOptions`` + - ``_init_properties`` + + ### Test + + - Class properties are initialized to expected values. + """ + with does_not_raise(): + instance = image_install_options + + assert instance.epld is False + assert instance.issu is True + assert instance.package_install is False + assert instance.policy_name is None + assert instance.response_data is None + assert instance.rest_send is None + assert instance.results is None + assert instance.serial_number is None + + +def test_image_install_options_00100(image_install_options) -> None: + """ + ### Classes and Methods + + - ``ImageInstallOptions`` + - ``refresh`` + + ### Test + - ``ValueError`` is raised because ``serial_number`` is not set before + ``refresh`` is called. + - Error message matches expectation. + """ + + def responses(): + # ImageStage()._populate_controller_version + yield None + + gen_responses = ResponseGenerator(responses()) + + sender = Sender() + sender.ansible_module = MockAnsibleModule() + sender.gen = gen_responses + rest_send = RestSend(params) + rest_send.response_handler = ResponseHandler() + rest_send.sender = sender + + with does_not_raise(): + instance = image_install_options + instance.results = Results() + instance.rest_send = rest_send + instance.policy_name = "FOO" + + match = r"ImageInstallOptions\._validate_refresh_parameters:\s+" + match += r"serial_number must be set before calling refresh\(\)\." + + with pytest.raises(ValueError, match=match): + image_install_options.refresh() + + +def test_image_install_options_00110(image_install_options) -> None: + """ + ### Classes and Methods + + - ``ImageInstallOptions`` + - ``refresh`` + + ### Test + + - Request is successful. + - No exceptions are raised. + - Properties are updated with expected values. + """ + method_name = inspect.stack()[0][3] + key = f"{method_name}a" + + def responses(): + yield responses_ep_install_options(key) + + gen_responses = ResponseGenerator(responses()) + + sender = Sender() + sender.ansible_module = MockAnsibleModule() + sender.gen = gen_responses + rest_send = RestSend(params) + rest_send.unit_test = True + rest_send.response_handler = ResponseHandler() + rest_send.sender = sender + + with does_not_raise(): + instance = image_install_options + instance.results = Results() + instance.rest_send = rest_send + instance.policy_name = "KRM5" + instance.serial_number = "BAR" + instance.refresh() + + assert isinstance(instance.results.response, list) + assert isinstance(instance.results.response_current, dict) + assert instance.rest_send.result_current.get("success") is True + + assert instance.device_name == "cvd-1314-leaf" + assert instance.err_message is None + assert instance.epld_modules is None + assert instance.install_option == "disruptive" + assert instance.install_packages is None + assert instance.ip_address == "172.22.150.105" + assert instance.os_type == "64bit" + assert instance.platform == "N9K/N3K" + assert instance.pre_issu_link == "Not Applicable" + assert isinstance(instance.raw_data, dict) + assert isinstance(instance.raw_response, dict) + assert "compatibilityStatusList" in instance.raw_data + assert instance.rep_status == "skipped" + assert instance.serial_number == "BAR" + assert instance.status == "Success" + assert instance.timestamp == "NA" + assert instance.version == "10.2.5" + comp_disp = "show install all impact nxos bootflash:nxos64-cs.10.2.5.M.bin" + assert instance.comp_disp == comp_disp + + +def test_image_install_options_00120(image_install_options) -> None: + """ + ### Classes and Methods + + - ``ImageInstallOptions`` + - ``refresh`` + + ### Test + + - ``ControllerResponseError`` is raised because response RETURN_CODE != 200. + """ + method_name = inspect.stack()[0][3] + key = f"{method_name}a" + + def responses(): + yield responses_ep_install_options(key) + + gen_responses = ResponseGenerator(responses()) + + sender = Sender() + sender.ansible_module = MockAnsibleModule() + sender.gen = gen_responses + rest_send = RestSend(params) + rest_send.unit_test = True + rest_send.timeout = 1 + rest_send.response_handler = ResponseHandler() + rest_send.sender = sender + + with does_not_raise(): + instance = image_install_options + instance.results = Results() + instance.rest_send = rest_send + instance.policy_name = "KRM5" + instance.serial_number = "BAR" + + match = r"ImageInstallOptions\.refresh:\s+" + match += r"Bad result when retrieving install-options from\s+" + match += r"the controller\. Controller response:.*" + + with pytest.raises(ControllerResponseError, match=match): + instance.refresh() + + +def test_image_install_options_00130(image_install_options) -> None: + """ + ### Classes and Methods + + - ``ImageInstallOptions`` + - ``refresh`` + + ### Setup + + - Device has no policy attached. + - POST REQUEST + - epld is False. + - issu is True. + - package_install is False. + + ### Test + - Request is successful. + - No exceptions are raised. + - Response contains expected values. + """ + method_name = inspect.stack()[0][3] + key = f"{method_name}a" + + def responses(): + yield responses_ep_install_options(key) + + gen_responses = ResponseGenerator(responses()) + + sender = Sender() + sender.ansible_module = MockAnsibleModule() + sender.gen = gen_responses + rest_send = RestSend(params) + rest_send.unit_test = True + rest_send.response_handler = ResponseHandler() + rest_send.sender = sender + + with does_not_raise(): + instance = image_install_options + instance.results = Results() + instance.rest_send = rest_send + + instance.epld = False + instance.issu = True + instance.package_install = False + + instance.policy_name = "KRM5" + instance.serial_number = "FDO21120U5D" + instance.refresh() + + assert isinstance(instance.rest_send.response_current, dict) + assert isinstance(instance.rest_send.response, list) + assert isinstance(instance.rest_send.result_current, dict) + assert isinstance(instance.rest_send.result, list) + assert instance.rest_send.result_current.get("success") is True + + assert instance.device_name == "leaf1" + assert instance.err_message is None + assert instance.epld_modules is None + assert instance.install_option == "NA" + assert instance.install_packages is None + assert instance.ip_address == "172.22.150.102" + assert instance.os_type == "64bit" + assert instance.platform == "N9K/N3K" + assert instance.pre_issu_link == "Not Applicable" + assert isinstance(instance.raw_data, dict) + assert isinstance(instance.raw_response, dict) + assert "compatibilityStatusList" in image_install_options.raw_data + assert instance.rep_status == "skipped" + assert instance.serial_number == "FDO21120U5D" + assert instance.status == "Skipped" + assert instance.timestamp == "NA" + assert instance.version == "10.2.5" + assert instance.version_check == "Compatibility status skipped." + assert instance.comp_disp == "Compatibility status skipped." + + +def test_image_install_options_00140(image_install_options) -> None: + """ + ### Classes and Methods + + - ``ImageInstallOptions`` + - ``refresh`` + + ### Setup + + - Device has no policy attached. + - POST REQUEST + - epld is True. + - issu is True. + - package_install is False. + + ### Test + - Request is successful. + - No exceptions are raised. + - Response contains expected values. + """ + method_name = inspect.stack()[0][3] + key = f"{method_name}a" + + def responses(): + yield responses_ep_install_options(key) + + gen_responses = ResponseGenerator(responses()) + + sender = Sender() + sender.ansible_module = MockAnsibleModule() + sender.gen = gen_responses + rest_send = RestSend(params) + rest_send.unit_test = True + rest_send.response_handler = ResponseHandler() + rest_send.sender = sender + + with does_not_raise(): + instance = image_install_options + instance.results = Results() + instance.rest_send = rest_send + instance.rest_send.timeout = 1 + + instance.epld = True + instance.issu = True + instance.package_install = False + + instance.policy_name = "KRM5" + instance.serial_number = "FDO21120U5D" + instance.refresh() + + assert isinstance(instance.rest_send.response_current, dict) + assert isinstance(instance.rest_send.response, list) + assert isinstance(instance.rest_send.result_current, dict) + assert isinstance(instance.rest_send.result, list) + assert instance.rest_send.result_current.get("success") is True + + assert instance.device_name == "leaf1" + assert instance.err_message is None + assert isinstance(instance.epld_modules, dict) + assert len(instance.epld_modules.get("moduleList")) == 2 + assert instance.install_option == "NA" + assert instance.install_packages is None + assert instance.ip_address == "172.22.150.102" + assert instance.os_type == "64bit" + assert instance.platform == "N9K/N3K" + assert instance.pre_issu_link == "Not Applicable" + assert isinstance(instance.raw_data, dict) + assert isinstance(instance.raw_response, dict) + assert "compatibilityStatusList" in instance.raw_data + assert instance.rep_status == "skipped" + assert instance.serial_number == "FDO21120U5D" + assert instance.status == "Skipped" + assert instance.timestamp == "NA" + assert instance.version == "10.2.5" + assert instance.version_check == "Compatibility status skipped." + assert instance.comp_disp == "Compatibility status skipped." + + +def test_image_install_options_00150(image_install_options) -> None: + """ + ### Classes and Methods + - ``ImageInstallOptions`` + - ``refresh`` + + ### Setup + + - Device has no policy attached. + - POST REQUEST + - epld is True. + - issu is False. + - package_install is False. + + ### Test + + - Request is successful. + - No exceptions are raised. + - Response contains expected values. + """ + method_name = inspect.stack()[0][3] + key = f"{method_name}a" + + def responses(): + yield responses_ep_install_options(key) + + gen_responses = ResponseGenerator(responses()) + + sender = Sender() + sender.ansible_module = MockAnsibleModule() + sender.gen = gen_responses + rest_send = RestSend(params) + rest_send.unit_test = True + rest_send.response_handler = ResponseHandler() + rest_send.sender = sender + + with does_not_raise(): + instance = image_install_options + instance.results = Results() + instance.rest_send = rest_send + instance.rest_send.timeout = 1 + + instance.epld = True + instance.issu = False + instance.package_install = False + + instance.policy_name = "KRM5" + instance.serial_number = "FDO21120U5D" + instance.refresh() + + assert isinstance(instance.rest_send.response_current, dict) + assert isinstance(instance.rest_send.response, list) + assert isinstance(instance.rest_send.result_current, dict) + assert isinstance(instance.rest_send.result, list) + assert instance.rest_send.result_current.get("success") is True + + assert instance.device_name is None + assert instance.err_message is None + assert isinstance(instance.epld_modules, dict) + assert len(instance.epld_modules.get("moduleList")) == 2 + assert instance.install_option is None + assert instance.install_packages is None + assert instance.ip_address is None + assert instance.os_type is None + assert instance.platform is None + assert instance.pre_issu_link is None + assert isinstance(instance.raw_data, dict) + assert isinstance(instance.raw_response, dict) + assert "compatibilityStatusList" in instance.raw_data + assert instance.rep_status is None + assert instance.serial_number == "FDO21120U5D" + assert instance.status is None + assert instance.timestamp is None + assert instance.version is None + assert instance.version_check is None + assert instance.comp_disp is None + + +def test_image_install_options_00160(monkeypatch, image_install_options) -> None: + """ + ### Classes and Methods + + - ``ImageInstallOptions`` + - ``refresh`` + + ### Setup + + - Device has no policy attached. + - POST REQUEST + - issu is False. + - epld is True. + - package_install is True. + - Causes expected error. + + ### Test + + - 500 response from endpoint because + - KR5M policy has no packages defined and, + - package_install set to True. + - Response contains expected values. + """ + method_name = inspect.stack()[0][3] + key = f"{method_name}a" + + def responses(): + yield responses_ep_install_options(key) + + gen_responses = ResponseGenerator(responses()) + + sender = Sender() + sender.ansible_module = MockAnsibleModule() + sender.gen = gen_responses + rest_send = RestSend(params) + rest_send.unit_test = True + rest_send.response_handler = ResponseHandler() + rest_send.sender = sender + + with does_not_raise(): + instance = image_install_options + instance.results = Results() + instance.rest_send = rest_send + instance.rest_send.timeout = 1 + + instance.epld = True + instance.issu = True + instance.package_install = True + + instance.policy_name = "KRM5" + instance.serial_number = "FDO21120U5D" + + match = r"ImageInstallOptions\.refresh:\s+" + match += r"Bad result when retrieving install-options from the\s+" + match += r"controller\.\s+" + match += r"Controller response:.*\.\s+" + match += r"Possible cause:\s+" + match += r"Image policy KRM5 does not have a package defined,\s+" + match += r"and package_install is set to True in the playbook for\s+" + match += r"device FDO21120U5D\." + + with pytest.raises(ControllerResponseError, match=match): + instance.refresh() + + +def test_image_install_options_00170(image_install_options) -> None: + """ + ### Classes and Methods + - ``ImageInstallOptions`` + - ``refresh`` + + ### Setup + + - POST REQUEST + - epld is False + - issu is False + - package_install is False + + ### Test + + - ``ImageInstallOptions`` returns a mocked response when all of + issu, epld, and package_install are False. + - Mocked response contains expected values. + + ### NOTES + ``ResponseGenerator()`` is set to return None since ``ImageInstallOptions`` + never sends a request to the controller in this case. + """ + + def responses(): + yield None + + gen_responses = ResponseGenerator(responses()) + + sender = Sender() + sender.ansible_module = MockAnsibleModule() + sender.gen = gen_responses + rest_send = RestSend(params) + rest_send.unit_test = True + rest_send.response_handler = ResponseHandler() + rest_send.sender = sender + + with does_not_raise(): + instance = image_install_options + instance.results = Results() + instance.rest_send = rest_send + + instance.epld = False + instance.issu = False + instance.package_install = False + + instance.policy_name = "KRM5" + instance.serial_number = "FDO21120U5D" + instance.refresh() + + # response_data + # { + # 'compatibilityStatusList': [], + # 'epldModules': {}, + # 'installPacakges': None, + # 'errMessage': '' + # } + assert isinstance(instance.response_data, dict) + assert instance.response_data.get("compatibilityStatusList") == [] + assert instance.response_data.get("epldModules") == {} + # yes, installPackages is intentionally misspelled below since + # this is what the controller returns in a real response + assert instance.response_data.get("installPacakges") is None + assert instance.response_data.get("errMessage") == "" + + +def test_image_install_options_00180(image_install_options) -> None: + """ + ### Classes and Methods + - ``ImageInstallOptions`` + - ``refresh`` + + ### Test + + - ``refresh()`` raises ValueError because ``policy_name`` is not set. + - Error message matches expectation. + """ + + def responses(): + yield None + + gen_responses = ResponseGenerator(responses()) + + sender = Sender() + sender.ansible_module = MockAnsibleModule() + sender.gen = gen_responses + rest_send = RestSend(params) + rest_send.unit_test = True + rest_send.response_handler = ResponseHandler() + rest_send.sender = sender + + with does_not_raise(): + instance = image_install_options + instance.results = Results() + instance.rest_send = rest_send + instance.serial_number = "FOO" + + match = r"ImageInstallOptions\._validate_refresh_parameters:\s+" + match += r"policy_name must be set before calling refresh\(\)\." + + with pytest.raises(ValueError, match=match): + instance.refresh() + + +def test_image_install_options_00200(image_install_options) -> None: + """ + ### Classes and Methods + - ``ImageInstallOptions`` + - ``build_payload`` + + ### Setup + + - Defaults are not specified by the user. + + ### Test + + - Default values for issu, epld, and package_install are applied. + """ + with does_not_raise(): + instance = image_install_options + instance.policy_name = "KRM5" + instance.serial_number = "BAR" + instance._build_payload() # pylint: disable=protected-access + + assert instance.payload.get("epld") is False + assert instance.payload.get("issu") is True + assert instance.payload.get("packageInstall") is False + assert instance.payload.get("devices")[0].get("policyName") == "KRM5" + assert instance.payload.get("devices")[0].get("serialNumber") == "BAR" + + +def test_image_install_options_00210(image_install_options) -> None: + """ + ### Classes and Methods + - ``ImageInstallOptions`` + - ``build_payload`` + + ### Setup + + - Values are specified by the user. + + ### Test + + - Payload contains user-specified values if the user sets them. + - Defaults for issu, epld, and package_install are overridden by + user values. + """ + with does_not_raise(): + instance = image_install_options + instance.epld = True + instance.issu = False + instance.package_install = True + instance.policy_name = "KRM5" + instance.serial_number = "BAR" + + instance._build_payload() # pylint: disable=protected-access + + assert instance.payload.get("epld") is True + assert instance.payload.get("issu") is False + assert instance.payload.get("packageInstall") is True + assert instance.payload.get("devices")[0].get("policyName") == "KRM5" + assert instance.payload.get("devices")[0].get("serialNumber") == "BAR" + + +def test_image_install_options_00300(image_install_options) -> None: + """ + ### Classes and Methods + - ``ImageInstallOptions`` + - ``issu.setter`` + + ### Test + + - ``TypeError`` is raised because issu is not a boolean. + """ + match = r"ImageInstallOptions\.issu:\s+" + match += r"issu must be a boolean value\." + + with does_not_raise(): + instance = image_install_options + with pytest.raises(TypeError, match=match): + instance.issu = "FOO" + + +def test_image_install_options_00400(image_install_options) -> None: + """ + ### Classes and Methods + + - ``ImageInstallOptions`` + - ``epld.setter`` + + ### Test + + - ``TypeError`` is raised because epld is not a boolean. + """ + match = r"ImageInstallOptions\.epld:\s+" + match += r"epld must be a boolean value\." + + with does_not_raise(): + instance = image_install_options + with pytest.raises(TypeError, match=match): + instance.epld = "FOO" + + +def test_image_install_options_00500(image_install_options) -> None: + """ + ### Classes and Methods + + - ``ImageInstallOptions`` + - ``package_install.setter`` + + ### Test + + - ``TypeError`` is raised because package_install is not a boolean. + """ + match = r"ImageInstallOptions\.package_install:\s+" + match += r"package_install must be a boolean value\." + + with does_not_raise(): + instance = image_install_options + with pytest.raises(TypeError, match=match): + instance.package_install = "FOO" + + +MATCH_00600 = r"ImageInstallOptions\.policy_name: " +MATCH_00600 += r"instance\.policy_name must be a string. Got" + + +@pytest.mark.parametrize( + "value, expected, raise_flag", + [ + ("NR3F", does_not_raise(), False), + (1, pytest.raises(TypeError, match=MATCH_00600), True), + (False, pytest.raises(TypeError, match=MATCH_00600), True), + ({"foo": "bar"}, pytest.raises(TypeError, match=MATCH_00600), True), + ([1, 2], pytest.raises(TypeError, match=MATCH_00600), True), + ], +) +def test_image_install_options_00600( + image_install_options, value, expected, raise_flag +) -> None: + """ + ### Classes and Methods + + - ``ImageInstallOptions`` + - ``policy_name.setter`` + + ### Test + + - ``TypeError`` is raised when ``property_name`` is not a string. + - ``TypeError`` is not raised when ``property_name`` is a string. + """ + with does_not_raise(): + instance = image_install_options + with expected: + instance.policy_name = value + if raise_flag is False: + assert instance.policy_name == value diff --git a/tests/unit/modules/dcnm/dcnm_image_upgrade/test_image_stage.py b/tests/unit/modules/dcnm/dcnm_image_upgrade/test_image_stage.py new file mode 100644 index 000000000..27ea29e69 --- /dev/null +++ b/tests/unit/modules/dcnm/dcnm_image_upgrade/test_image_stage.py @@ -0,0 +1,1065 @@ +# 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 +# pylint: disable=protected-access +""" +ImageStage - unit tests +""" + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +__copyright__ = "Copyright (c) 2024 Cisco and/or its affiliates." +__author__ = "Allen Robel" + + +import inspect + +import pytest +from ansible_collections.cisco.dcnm.plugins.module_utils.common.exceptions import \ + ControllerResponseError +from ansible_collections.cisco.dcnm.plugins.module_utils.common.response_handler import \ + ResponseHandler +from ansible_collections.cisco.dcnm.plugins.module_utils.common.rest_send_v2 import \ + RestSend +from ansible_collections.cisco.dcnm.plugins.module_utils.common.results import \ + Results +from ansible_collections.cisco.dcnm.plugins.module_utils.common.sender_file import \ + Sender +from ansible_collections.cisco.dcnm.tests.unit.module_utils.common.common_utils import \ + ResponseGenerator + +from .utils import (MockAnsibleModule, does_not_raise, image_stage_fixture, + params, responses_ep_image_stage, responses_ep_issu, + responses_ep_version) + + +def test_image_stage_00000(image_stage) -> None: + """ + ### Classes and Methods + - ``ImageStage`` + - ``__init__`` + + ### Test + - Class attributes are initialized to expected values. + """ + with does_not_raise(): + instance = image_stage + assert instance.class_name == "ImageStage" + assert instance.action == "image_stage" + assert instance.controller_version is None + assert instance.diff == {} + assert instance.payload is None + assert instance.saved_response_current == {} + assert instance.saved_result_current == {} + assert isinstance(instance.serial_numbers_done, set) + assert isinstance(instance.serial_numbers_todo, set) + + assert instance.controller_version_instance.class_name == "ControllerVersion" + assert instance.ep_image_stage.class_name == "EpImageStage" + assert instance.issu_detail.class_name == "SwitchIssuDetailsBySerialNumber" + assert instance.wait_for_controller_done.class_name == "WaitForControllerDone" + + module_path = "/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/" + module_path += "stagingmanagement/stage-image" + assert instance.ep_image_stage.path == module_path + assert instance.ep_image_stage.verb == "POST" + + # properties + assert instance.check_interval == 10 + assert instance.check_timeout == 1800 + assert instance.rest_send is None + assert instance.results is None + assert instance.serial_numbers is None + + +@pytest.mark.parametrize( + "key, expected", + [ + ("test_image_stage_00100a", "12.1.2e"), + ("test_image_stage_00100b", "12.1.3b"), + ], +) +def test_image_stage_00100(image_stage, key, expected) -> None: + """ + ### Classes and Methods + - ``ImageStage`` + - ``_populate_controller_version`` + + ### Summary + Verify that ``_populate_controller_version`` sets the controller version + correctly based on the response from the controller. + + ### Test + - test_image_stage_00100a -> instance.controller_version == "12.1.2e" + - test_image_stage_00100b -> instance.controller_version == "12.1.3b" + + ### Description + ``_populate_controller_version`` retrieves the controller version from + the controller. This is used in commit() to populate the payload + with either a misspelled "sereialNum" key/value (12.1.2e) or a + correctly-spelled "serialNumbers" key/value (12.1.3b). + """ + + def responses(): + # ImageStage()._populate_controller_version + yield responses_ep_version(key) + + gen_responses = ResponseGenerator(responses()) + + sender = Sender() + sender.ansible_module = MockAnsibleModule() + sender.gen = gen_responses + rest_send = RestSend(params) + rest_send.response_handler = ResponseHandler() + rest_send.sender = sender + + with does_not_raise(): + instance = image_stage + instance.results = Results() + instance.rest_send = rest_send + instance.controller_version_instance.rest_send = rest_send + instance._populate_controller_version() + assert instance.controller_version == expected + + +def test_image_stage_00200(image_stage) -> None: + """ + ### Classes and Methods + - ``ImageStage`` + - ``prune_serial_numbers`` + + ### Summary + Verify that ``prune_serial_numbers`` prunes serial numbers that have already + been staged. + + ### Setup + - ``responses_ep_issu()`` returns 200 response indicating that + ``imageStaged`` is "none" for three serial numbers and "Success" + for two serial numbers in the serial_numbers list. + + ### Test + - ``serial_numbers`` contains only serial numbers + for which imageStaged == "none" (FDO2112189M, FDO211218AX, FDO211218B5) + - ``serial_numbers`` does not contain serial numbers + for which imageStaged == "Success" (FDO211218FV, FDO211218GC) + + ### Description + ``prune_serial_numbers()`` removes serial numbers from the list for which + imageStaged == "Success" + """ + method_name = inspect.stack()[0][3] + key = f"{method_name}a" + + def responses(): + # ImageStage().prune_serial_numbers + yield responses_ep_issu(key) + + gen_responses = ResponseGenerator(responses()) + + sender = Sender() + sender.ansible_module = MockAnsibleModule() + sender.gen = gen_responses + rest_send = RestSend(params) + rest_send.response_handler = ResponseHandler() + rest_send.sender = sender + + with does_not_raise(): + instance = image_stage + instance.results = Results() + instance.rest_send = rest_send + instance.issu_detail.rest_send = rest_send + instance.issu_detail.results = Results() + instance.serial_numbers = [ + "FDO2112189M", + "FDO211218AX", + "FDO211218B5", + "FDO211218FV", + "FDO211218GC", + ] + instance.prune_serial_numbers() + assert isinstance(instance.serial_numbers, list) + assert len(instance.serial_numbers) == 3 + assert "FDO2112189M" in instance.serial_numbers + assert "FDO211218AX" in instance.serial_numbers + assert "FDO211218B5" in instance.serial_numbers + assert "FDO211218FV" not in instance.serial_numbers + assert "FDO211218GC" not in instance.serial_numbers + + +def test_image_stage_00300(image_stage) -> None: + """ + ### Classes and Methods + - ``ImageStage`` + - ``validate_serial_numbers`` + + ### Summary + Verify that ``validate_serial_numbers`` raises ``ControllerResponseError`` + appropriately. + + ### Setup + - ``responses_ep_issu()`` returns 200 response indicating that + ``imageStaged`` is "Success" for one serial number and "Failed" + for the other serial number in the serial_numbers list. + + ### Test + - ``ControllerResponseError`` is not called when imageStaged == "Success" + - ``ControllerResponseError`` is called when imageStaged == "Failed" + + ### Description + ``validate_serial_numbers`` checks the imageStaged status for each serial + number and raises ``ControllerResponseError`` if imageStaged == "Failed" + for any serial number. + """ + method_name = inspect.stack()[0][3] + key = f"{method_name}a" + + def responses(): + # ImageStage().validate_serial_numbers + yield responses_ep_issu(key) + + gen_responses = ResponseGenerator(responses()) + + sender = Sender() + sender.ansible_module = MockAnsibleModule() + sender.gen = gen_responses + rest_send = RestSend(params) + rest_send.unit_test = True + rest_send.response_handler = ResponseHandler() + rest_send.sender = sender + + with does_not_raise(): + instance = image_stage + instance.results = Results() + instance.rest_send = rest_send + instance.issu_detail.rest_send = rest_send + instance.issu_detail.results = Results() + instance.serial_numbers = ["FDO21120U5D", "FDO2112189M"] + + match = "Image staging is failing for the following switch: " + match += "cvd-2313-leaf, 172.22.150.108, FDO2112189M. " + match += "Check the switch connectivity to the controller " + match += "and try again." + + with pytest.raises(ControllerResponseError, match=match): + instance.validate_serial_numbers() + + +def test_image_stage_00400(image_stage) -> None: + """ + ### Classes and Methods + - ``ImageStage`` + - ``_wait_for_image_stage_to_complete`` + + ### Summary + Verify proper behavior of _wait_for_image_stage_to_complete when + ``imageStaged`` is "Success" for all serial numbers. + + ### Setup + - ``responses_ep_issu()`` returns 200 response indicating that + ``imageStaged`` is "Success" for all serial numbers in the + serial_numbers list. + + ### Test + - "imageStaged" == "Success" for all serial numbers so + ``ControllerResponseError`` is not raised. + - ``serial_numbers_done`` is a set(). + - ``serial_numbers_done`` has length 2. + - ``serial_numbers_done`` == ``serial_numbers``. + + ### Description + ``_wait_for_image_stage_to_complete`` looks at the "imageStaged" status for + each serial number and waits for it to be "Success" or "Failed". + In the case where all serial numbers are "Success", the module returns. + In the case where any serial number is "Failed", the module raises + ``ControllerResponseError``. + """ + method_name = inspect.stack()[0][3] + key = f"{method_name}a" + + def responses(): + # ImageStage()._wait_for_image_stage_to_complete + yield responses_ep_issu(key) + + gen_responses = ResponseGenerator(responses()) + + sender = Sender() + sender.ansible_module = MockAnsibleModule() + sender.gen = gen_responses + rest_send = RestSend(params) + rest_send.unit_test = True + rest_send.response_handler = ResponseHandler() + rest_send.sender = sender + + with does_not_raise(): + instance = image_stage + instance.results = Results() + instance.rest_send = rest_send + instance.issu_detail.rest_send = rest_send + instance.issu_detail.results = Results() + instance.serial_numbers = ["FDO21120U5D", "FDO2112189M"] + instance._wait_for_image_stage_to_complete() + assert isinstance(instance.serial_numbers_done, set) + assert len(instance.serial_numbers_done) == 2 + assert "FDO21120U5D" in instance.serial_numbers_done + assert "FDO2112189M" in instance.serial_numbers_done + + +def test_image_stage_00410(image_stage) -> None: + """ + ### Classes and Methods + - ``ImageStage`` + - ``_wait_for_image_stage_to_complete`` + + ### Summary + Verify proper behavior of ``_wait_for_image_stage_to_complete`` when + imageStaged is "Failed" for one serial number and imageStaged + is "Success" for one serial number. + + ### Setup + - ``responses_ep_issu()`` returns 200 response indicating that + ``imageStaged`` is "Success" for one of the serial numbers in the + serial_numbers list and "Failed" for the other. + + ### Test + - ``serial_numbers_done`` is a set(). + - ``serial_numbers_done`` has length 1. + - ``serial_numbers_done`` contains FDO21120U5D. + because imageStaged is "Success". + - ``ValueError`` is raised on serial number FDO2112189M + because imageStaged is "Failed". + - Error message matches expectation. + + ### Description + ``_wait_for_image_stage_to_complete`` looks at the imageStaged status + for each serial number and waits for it to be "Success" or "Failed". + In the case where all serial numbers are "Success", the module returns. + In the case where any serial number is "Failed", the module raises + ``ValueError``. + """ + method_name = inspect.stack()[0][3] + key = f"{method_name}a" + + def responses(): + # ImageStage()._wait_for_image_stage_to_complete + yield responses_ep_issu(key) + + gen_responses = ResponseGenerator(responses()) + + sender = Sender() + sender.ansible_module = MockAnsibleModule() + sender.gen = gen_responses + rest_send = RestSend(params) + rest_send.unit_test = True + rest_send.response_handler = ResponseHandler() + rest_send.sender = sender + + with does_not_raise(): + instance = image_stage + instance.results = Results() + instance.rest_send = rest_send + instance.issu_detail.rest_send = rest_send + instance.issu_detail.results = Results() + instance.serial_numbers = ["FDO21120U5D", "FDO2112189M"] + + match = "Seconds remaining 1790: stage image failed for " + match += "cvd-2313-leaf, FDO2112189M, 172.22.150.108. image " + match += "staged percent: 90" + + with pytest.raises(ValueError, match=match): + instance._wait_for_image_stage_to_complete() + + assert isinstance(instance.serial_numbers_done, set) + assert len(instance.serial_numbers_done) == 1 + assert "FDO21120U5D" in instance.serial_numbers_done + assert "FDO2112189M" not in instance.serial_numbers_done + + +def test_image_stage_00420(image_stage) -> None: + """ + ### Classes and Methods + - ``ImageStage`` + - ``_wait_for_image_stage_to_complete`` + + ### Summary + Verify proper behavior of ``_wait_for_image_stage_to_complete`` when + timeout is reached for one serial number (i.e. ``imageStaged`` is + "In-Progress") and ``imageStaged`` is "Success" for one serial number. + + ### Setup + - ``responses_ep_issu()`` returns 200 response indicating that + ``imageStaged`` is "Success" for one of the serial numbers in the + serial_numbers list and "In-Progress" for the other. + + ### Test + - ``serial_numbers_done`` is a set(). + - ``serial_numbers_done`` has length 1. + - ``serial_numbers_done`` contains FDO21120U5D. + because imageStaged == "Success". + - ``serial_numbers_done`` does not contain FDO2112189M. + - ``ValueError`` is raised due to timeout because FDO2112189M + ``imageStaged`` == "In-Progress". + - Error message matches expectation. + + ### Description + See test_image_stage_410 for functional details. + """ + method_name = inspect.stack()[0][3] + key = f"{method_name}a" + + def responses(): + # ImageStage()._wait_for_image_stage_to_complete + yield responses_ep_issu(key) + + gen_responses = ResponseGenerator(responses()) + + sender = Sender() + sender.ansible_module = MockAnsibleModule() + sender.gen = gen_responses + rest_send = RestSend(params) + rest_send.unit_test = True + rest_send.send_interval = 1 + rest_send.timeout = 1 + rest_send.response_handler = ResponseHandler() + rest_send.sender = sender + + with does_not_raise(): + instance = image_stage + instance.results = Results() + instance.rest_send = rest_send + instance.check_timeout = 1 + instance.check_interval = 1 + instance.issu_detail.rest_send = rest_send + instance.issu_detail.results = Results() + instance.serial_numbers = ["FDO21120U5D", "FDO2112189M"] + + match = "ImageStage._wait_for_image_stage_to_complete: " + match += "Timed out waiting for image stage to complete. " + match += "serial_numbers_done: FDO21120U5D, " + match += "serial_numbers_todo: FDO21120U5D,FDO2112189M" + + with pytest.raises(ValueError, match=match): + instance._wait_for_image_stage_to_complete() + assert isinstance(instance.serial_numbers_done, set) + assert len(instance.serial_numbers_done) == 1 + assert "FDO21120U5D" in instance.serial_numbers_todo + assert "FDO2112189M" in instance.serial_numbers_todo + assert "FDO21120U5D" in instance.serial_numbers_done + assert "FDO2112189M" not in instance.serial_numbers_done + + +def test_image_stage_00500(image_stage) -> None: + """ + ### Classes and Methods + - ``ImageStage`` + - ``wait_for_controller`` + + ### Summary + Verify proper behavior of ``wait_for_controller`` when no actions + are pending. + + ### Setup + - ``responses_ep_issu()`` returns 200 response indicating that no + actions are "In-Progress". + + ### Test + - ``wait_for_controller_done.done`` is a set(). + - ``serial_numbers_done`` has length 2. + - ``serial_numbers_done`` contains all serial numbers in + ``serial_numbers``. + - Exception is not raised. + + ### Description + ``wait_for_controller`` waits until staging, validation, and upgrade + actions are complete for all serial numbers. It calls + ``SwitchIssuDetailsBySerialNumber.actions_in_progress()`` and expects + this to return False. ``actions_in_progress()`` returns True until none + of the following keys has a value of "In-Progress": + - ``imageStaged`` + - ``upgrade`` + - ``validated`` + """ + method_name = inspect.stack()[0][3] + key = f"{method_name}a" + + def responses(): + # ImageStage().wait_for_controller + yield responses_ep_issu(key) + + gen_responses = ResponseGenerator(responses()) + + sender = Sender() + sender.ansible_module = MockAnsibleModule() + sender.gen = gen_responses + rest_send = RestSend(params) + rest_send.unit_test = True + rest_send.send_interval = 1 + rest_send.timeout = 1 + rest_send.response_handler = ResponseHandler() + rest_send.sender = sender + + with does_not_raise(): + instance = image_stage + instance.results = Results() + instance.rest_send = rest_send + instance.check_timeout = 1 + instance.check_interval = 1 + instance.issu_detail.rest_send = rest_send + instance.issu_detail.results = Results() + instance.serial_numbers = ["FDO21120U5D", "FDO2112189M"] + instance.wait_for_controller() + + assert isinstance(instance.wait_for_controller_done.done, set) + assert len(instance.wait_for_controller_done.done) == 2 + assert "FDO21120U5D" in instance.wait_for_controller_done.todo + assert "FDO2112189M" in instance.wait_for_controller_done.todo + assert "FDO21120U5D" in instance.wait_for_controller_done.done + assert "FDO2112189M" in instance.wait_for_controller_done.done + + +def test_image_stage_00510(image_stage) -> None: + """ + ### Classes and Methods + - ``ImageStage`` + - ``wait_for_controller`` + + ### Summary + Verify proper behavior of ``wait_for_controller`` when there is a timeout + waiting for actions on the controller to complete. + + ### Setup + - ``responses_ep_issu()`` returns 200 response indicating that + ``imageStaged`` is "In-Progress" for one of the serial numbers in the + serial_numbers list. + + ### Test + - `serial_numbers_done` is a set() + - serial_numbers_done has length 1 + - ``serial_numbers_done`` contains FDO21120U5D + because imageStaged == "Success" + - ``serial_numbers_done`` does not contain FDO2112189M + - ``ValueError`` is raised due to timeout because FDO2112189M + imageStaged == "In-Progress" + + ### Description + See test_image_stage_00500 for functional details. + """ + method_name = inspect.stack()[0][3] + key = f"{method_name}a" + + def responses(): + # ImageStage().wait_for_controller + yield responses_ep_issu(key) + + gen_responses = ResponseGenerator(responses()) + + sender = Sender() + sender.ansible_module = MockAnsibleModule() + sender.gen = gen_responses + rest_send = RestSend(params) + rest_send.unit_test = True + rest_send.send_interval = 1 + rest_send.timeout = 1 + rest_send.response_handler = ResponseHandler() + rest_send.sender = sender + + with does_not_raise(): + instance = image_stage + instance.results = Results() + instance.rest_send = rest_send + instance.check_timeout = 1 + instance.check_interval = 1 + instance.issu_detail.rest_send = rest_send + instance.issu_detail.results = Results() + instance.serial_numbers = ["FDO21120U5D", "FDO2112189M"] + + match = r"ImageStage\.wait_for_controller:\s+" + match += r"Error while waiting for controller actions to complete\.\s+" + match += r"Error detail: WaitForControllerDone\.commit:\s+" + match += r"Timed out after 1 seconds waiting for controller actions to\s+" + match += r"complete on items: \['FDO21120U5D', 'FDO2112189M'\]\.\s+" + match += r"The following items did complete: FDO21120U5D\." + + with pytest.raises(ValueError, match=match): + instance.wait_for_controller() + assert isinstance(instance.wait_for_controller_done.done, set) + assert len(instance.wait_for_controller_done.done) == 1 + assert "FDO21120U5D" in instance.wait_for_controller_done.todo + assert "FDO2112189M" in instance.wait_for_controller_done.todo + assert "FDO21120U5D" in instance.wait_for_controller_done.done + assert "FDO2112189M" not in instance.wait_for_controller_done.done + + +MATCH_00600 = r"ImageStage\.check_interval:\s+" +MATCH_00600 += r"must be a positive integer or zero\." + + +@pytest.mark.parametrize( + "arg, value, context", + [ + (True, None, pytest.raises(TypeError, match=MATCH_00600)), + (-1, None, pytest.raises(ValueError, match=MATCH_00600)), + (10, 10, does_not_raise()), + (0, 0, does_not_raise()), + ("a", None, pytest.raises(TypeError, match=MATCH_00600)), + ], +) +def test_image_stage_00600(image_stage, arg, value, context) -> None: + """ + ### Classes and Methods + - ``ImageStage`` + - ``check_interval`` + + ### Summary + Verify that ``check_interval`` argument validation works as expected. + + ### Test + - Verify input arguments to ``check_interval`` property + + ### Description + ``check_interval`` expects a positive integer value, or zero. + """ + with does_not_raise(): + instance = image_stage + with context: + instance.check_interval = arg + if value is not None: + assert instance.check_interval == value + + +MATCH_00700 = r"ImageStage\.check_timeout:\s+" +MATCH_00700 += r"must be a positive integer or zero\." + + +@pytest.mark.parametrize( + "arg, value, context", + [ + (True, None, pytest.raises(TypeError, match=MATCH_00700)), + (-1, None, pytest.raises(ValueError, match=MATCH_00700)), + (10, 10, does_not_raise()), + (0, 0, does_not_raise()), + ("a", None, pytest.raises(TypeError, match=MATCH_00700)), + ], +) +def test_image_stage_00700(image_stage, arg, value, context) -> None: + """ + ### Classes and Methods + - ``ImageStage`` + - ``check_timeout`` + + ### Summary + Verify that ``check_timeout`` argument validation works as expected. + + ### Test + - Verify input arguments to ``check_timeout`` property + + ### Description + ``check_timeout`` expects a positive integer value, or zero. + """ + with does_not_raise(): + instance = image_stage + with context: + instance.check_timeout = arg + if value is not None: + assert instance.check_timeout == value + + +MATCH_00800 = r"ImageStage\.serial_numbers:\s+" +MATCH_00800 += r"must be a python list of switch serial numbers\." + + +@pytest.mark.parametrize( + "arg, value, context", + [ + ("foo", None, pytest.raises(TypeError, match=MATCH_00800)), + (10, None, pytest.raises(TypeError, match=MATCH_00800)), + (["DD001115F", 10], None, pytest.raises(TypeError, match=MATCH_00800)), + (["DD001115F"], ["DD001115F"], does_not_raise()), + ], +) +def test_image_stage_00800(image_stage, arg, value, context) -> None: + """ + ### Classes and Methods + - ``ImageStage`` + - ``serial_numbers`` + + ### Summary + Verify that ``serial_numbers`` argument validation works as expected. + + ### Test + - ``TypeError`` is raised if the input is not a list. + - ``TypeError`` is raised if the input is not a list of strings. + + ### Description + serial_numbers expects a list of serial numbers. + """ + with does_not_raise(): + instance = image_stage + with context: + instance.serial_numbers = arg + if value is not None: + assert instance.serial_numbers == value + + +MATCH_00900 = r"ImageStage\.validate_commit_parameters:\s+" +MATCH_00900 += r"serial_numbers must be set before calling commit\(\)\." + + +@pytest.mark.parametrize( + "serial_numbers_is_set, expected", + [ + (True, does_not_raise()), + (False, pytest.raises(ValueError, match=MATCH_00900)), + ], +) +def test_image_stage_00900(image_stage, serial_numbers_is_set, expected) -> None: + """ + ### Classes and Methods + - ``ImageStage`` + ` ``commit`` + + ### Summary + Verify that ``commit`` raises ``ValueError`` appropriately based on value of + ``serial_numbers``. + + ### Setup + - responses_ep_issu() returns 200 responses. + - responses_ep_version() returns a 200 response. + - responses_ep_image_stage() returns a 200 response. + + ### Test + - ``ValueError`` is raised when serial_numbers is not set. + - ``ValueError`` is not raised when serial_numbers is set. + """ + method_name = inspect.stack()[0][3] + key = f"{method_name}a" + + def responses(): + # ImageStage().prune_serial_numbers + yield responses_ep_issu(key) + # ImageStage().validate_serial_numbers + yield responses_ep_issu(key) + # ImageStage()._populate_controller_version + yield responses_ep_version(key) + # RestSend.commit_normal_mode + yield responses_ep_image_stage(key) + + gen_responses = ResponseGenerator(responses()) + + sender = Sender() + sender.ansible_module = MockAnsibleModule() + sender.gen = gen_responses + rest_send = RestSend(params) + rest_send.unit_test = True + rest_send.send_interval = 1 + rest_send.timeout = 1 + rest_send.response_handler = ResponseHandler() + rest_send.sender = sender + + with does_not_raise(): + instance = image_stage + instance.results = Results() + instance.rest_send = rest_send + instance.check_timeout = 1 + instance.check_interval = 1 + instance.issu_detail.rest_send = rest_send + instance.issu_detail.results = Results() + + if serial_numbers_is_set: + instance.serial_numbers = ["FDO21120U5D"] + with expected: + instance.commit() + + +@pytest.mark.parametrize( + "key, controller_version, expected_serial_number_key", + [ + ("test_image_stage_00910a", "12.1.2e", "sereialNum"), + ("test_image_stage_00910b", "12.1.3b", "serialNumbers"), + ], +) +def test_image_stage_00910( + image_stage, key, controller_version, expected_serial_number_key +) -> None: + """ + ### Classes and Methods + - ``ImageStage`` + ` ``commit`` + + ### Summary + Verify that the serial number key name in the payload is set correctly + based on the controller version. + + ### Setup + - ``responses_ep_issu()`` returns 200 responses. + - ``responses_ep_version()`` returns a 200 response. + - ``responses_ep_image_stage()`` returns a 200 response. + - ``serial_numbers`` is set to ["FDO21120U5D"] + + ### Test + - controller_version 12.1.2e -> key name "sereialNum" (yes, misspelled) + - controller_version 12.1.3b -> key name "serialNumbers + + ### Description + ``commit()`` will set the payload key name for the serial number + based on ``controller_version``. + """ + + def responses(): + # ImageStage().prune_serial_numbers + yield responses_ep_issu(key) + # ImageStage().validate_serial_numbers + yield responses_ep_issu(key) + # ImageStage()._populate_controller_version + yield responses_ep_version(key) + # RestSend.commit_normal_mode + yield responses_ep_image_stage(key) + + gen_responses = ResponseGenerator(responses()) + + sender = Sender() + sender.ansible_module = MockAnsibleModule() + sender.gen = gen_responses + rest_send = RestSend(params) + rest_send.unit_test = True + rest_send.send_interval = 1 + rest_send.timeout = 1 + rest_send.response_handler = ResponseHandler() + rest_send.sender = sender + + with does_not_raise(): + instance = image_stage + instance.results = Results() + instance.rest_send = rest_send + instance.check_timeout = 1 + instance.check_interval = 1 + instance.issu_detail.rest_send = rest_send + instance.issu_detail.results = Results() + instance.serial_numbers = ["FDO21120U5D"] + instance.commit() + + assert expected_serial_number_key in instance.payload.keys() + + +def test_image_stage_00920(image_stage) -> None: + """ + ### Classes and Methods + - ``ImageStage`` + ` ``commit`` + + ### Summary + Verify that commit() sets result, response, and response_data + appropriately when serial_numbers is empty. + + ### Setup + - ``serial_numbers`` is set to [] (empty list) + + ### Test + - commit() sets the following to expected values: + - self.result, self.result_current + - self.response, self.response_current + - self.response_data + + ### Description + When len(serial_numbers) == 0, commit() will set result and + response properties, and return without doing anything else. + """ + method_name = inspect.stack()[0][3] + key = f"{method_name}a" + + def responses(): + yield None + + gen_responses = ResponseGenerator(responses()) + + sender = Sender() + sender.ansible_module = MockAnsibleModule() + sender.gen = gen_responses + rest_send = RestSend(params) + rest_send.unit_test = True + rest_send.send_interval = 1 + rest_send.timeout = 1 + rest_send.response_handler = ResponseHandler() + rest_send.sender = sender + + with does_not_raise(): + instance = image_stage + instance.results = Results() + instance.rest_send = rest_send + instance.check_timeout = 1 + instance.check_interval = 1 + instance.issu_detail.rest_send = rest_send + instance.issu_detail.results = Results() + instance.serial_numbers = [] + instance.commit() + + response_msg = "No images to stage." + assert instance.results.result == [ + {"success": True, "changed": False, "sequence_number": 1} + ] + assert instance.results.result_current == { + "success": True, + "changed": False, + "sequence_number": 1, + } + assert instance.results.response_current == { + "DATA": [{"key": "ALL", "value": response_msg}], + "sequence_number": 1, + } + assert instance.results.response == [instance.results.response_current] + assert instance.results.response_data == [{"response": "No images to stage."}] + + +def test_image_stage_00930(image_stage) -> None: + """ + ### Classes and Methods + - ``ImageStage`` + ` ``commit`` + + ### Summary + Verify that ``ControllerResponseError`` is raised on 500 response from + the controller. + + ### Setup + - ``responses_ep_issu()`` returns 200 responses. + - ``responses_ep_version()`` returns a 200 response. + - ``responses_ep_image_stage()`` returns a 500 response. + + ### Test + - commit() raises ``ControllerResponseError`` + + ### Description + commit() raises ``ControllerResponseError`` on non-success response + from the controller. + """ + method_name = inspect.stack()[0][3] + key = f"{method_name}a" + + def responses(): + # ImageStage().prune_serial_numbers + yield responses_ep_issu(key) + # ImageStage().validate_serial_numbers + yield responses_ep_issu(key) + # ImageStage()._populate_controller_version + yield responses_ep_version(key) + # RestSend.commit_normal_mode + yield responses_ep_image_stage(key) + + gen_responses = ResponseGenerator(responses()) + + sender = Sender() + sender.ansible_module = MockAnsibleModule() + sender.gen = gen_responses + rest_send = RestSend(params) + rest_send.unit_test = True + rest_send.send_interval = 1 + rest_send.timeout = 1 + rest_send.response_handler = ResponseHandler() + rest_send.sender = sender + + with does_not_raise(): + instance = image_stage + instance.results = Results() + instance.rest_send = rest_send + instance.check_timeout = 1 + instance.check_interval = 1 + instance.issu_detail.rest_send = rest_send + instance.issu_detail.results = Results() + instance.serial_numbers = ["FDO21120U5D"] + + match = r"ImageStage\.commit:\s+" + match += r"failed\. Controller response:.*" + with pytest.raises(ControllerResponseError, match=match): + instance.commit() + assert instance.results.result == [ + {"success": False, "changed": False, "sequence_number": 1} + ] + assert instance.results.response_current["RETURN_CODE"] == 500 + + +def test_image_stage_00940(image_stage) -> None: + """ + ### Classes and Methods + - ``ImageStage`` + ` ``commit`` + + ### Summary + Verify that commit() sets self.diff to expected values on 200 response + from the controller for an image stage request. + + ### Setup + - ``responses_ep_issu()`` returns 200 responses. + - ``responses_ep_version()`` returns a 200 response with controller + version 12.1.3b. + - ``responses_ep_image_stage()`` returns a 200 response. + + ### Test + - commit() sets self.diff to the expected values + """ + method_name = inspect.stack()[0][3] + key_a = f"{method_name}a" + key_b = f"{method_name}b" + + def responses(): + # ImageStage().prune_serial_numbers() + yield responses_ep_issu(key_a) + # ImageStage().validate_serial_numbers() + yield responses_ep_issu(key_a) + # ImageStage().wait_for_controller() + yield responses_ep_issu(key_a) + # ImageStage()._populate_controller_version + yield responses_ep_version(key_a) + # ImageStage().commit() -> ImageStage().rest_send.commit() + yield responses_ep_image_stage(key_a) + # ImageStage()._wait_for_image_stage_to_complete() + yield responses_ep_issu(key_b) + + gen_responses = ResponseGenerator(responses()) + + sender = Sender() + sender.ansible_module = MockAnsibleModule() + sender.gen = gen_responses + rest_send = RestSend(params) + rest_send.unit_test = True + rest_send.send_interval = 1 + rest_send.timeout = 1 + rest_send.response_handler = ResponseHandler() + rest_send.sender = sender + + with does_not_raise(): + instance = image_stage + instance.results = Results() + instance.rest_send = rest_send + instance.check_timeout = 1 + instance.check_interval = 1 + instance.issu_detail.rest_send = rest_send + instance.issu_detail.results = Results() + instance.serial_numbers = ["FDO21120U5D"] + instance.commit() + + assert instance.results.result_current == { + "success": True, + "changed": True, + "sequence_number": 1, + } + assert instance.results.diff[0]["172.22.150.102"]["policy_name"] == "KR5M" + assert instance.results.diff[0]["172.22.150.102"]["ip_address"] == "172.22.150.102" + assert instance.results.diff[0]["172.22.150.102"]["serial_number"] == "FDO21120U5D" diff --git a/tests/unit/modules/dcnm/dcnm_image_upgrade/test_image_upgrade.py b/tests/unit/modules/dcnm/dcnm_image_upgrade/test_image_upgrade.py new file mode 100644 index 000000000..134042fbf --- /dev/null +++ b/tests/unit/modules/dcnm/dcnm_image_upgrade/test_image_upgrade.py @@ -0,0 +1,2437 @@ +# 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 +# Some tests require calling protected methods +# pylint: disable=protected-access + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +__copyright__ = "Copyright (c) 2024 Cisco and/or its affiliates." +__author__ = "Allen Robel" + +import inspect + +import pytest +from ansible_collections.cisco.dcnm.plugins.module_utils.common.exceptions import \ + ControllerResponseError +from ansible_collections.cisco.dcnm.plugins.module_utils.common.response_handler import \ + ResponseHandler +from ansible_collections.cisco.dcnm.plugins.module_utils.common.rest_send_v2 import \ + RestSend +from ansible_collections.cisco.dcnm.plugins.module_utils.common.results import \ + Results +from ansible_collections.cisco.dcnm.plugins.module_utils.common.sender_file import \ + Sender +from ansible_collections.cisco.dcnm.tests.unit.module_utils.common.common_utils import \ + ResponseGenerator + +from .utils import (MockAnsibleModule, devices_image_upgrade, does_not_raise, + image_upgrade_fixture, issu_details_by_ip_address_fixture, + params, payloads_ep_image_upgrade, + responses_ep_image_upgrade, responses_ep_install_options, + responses_ep_issu) + + +def test_image_upgrade_00000(image_upgrade) -> None: + """ + ### Classes and Methods + - ``ImageUpgrade`` + - ``__init__`` + + ### Test + + - Class attributes are initialized to expected values. + """ + with does_not_raise(): + instance = image_upgrade + + assert instance.class_name == "ImageUpgrade" + assert instance.action == "image_upgrade" + assert instance.diff == {} + assert instance.payload is None + assert instance.saved_response_current == {} + assert instance.saved_result_current == {} + assert isinstance(instance.ipv4_done, set) + assert isinstance(instance.ipv4_todo, set) + + assert instance.conversion.class_name == "ConversionUtils" + assert instance.ep_upgrade_image.class_name == "EpUpgradeImage" + assert instance.issu_detail.class_name == "SwitchIssuDetailsByIpAddress" + assert instance.wait_for_controller_done.class_name == "WaitForControllerDone" + + endpoint_path = "/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/" + endpoint_path += "imageupgrade/upgrade-image" + assert instance.ep_upgrade_image.path == endpoint_path + assert instance.ep_upgrade_image.verb == "POST" + + # properties + assert instance.check_interval == 10 + assert instance.check_timeout == 1800 + assert instance.non_disruptive is False + assert instance.rest_send is None + assert instance.results is None + + +def test_image_upgrade_00010(image_upgrade) -> None: + """ + ### Classes and Methods + - ``ImageUpgrade`` + - ``_init_properties`` + + ### Test + + - Class properties are initialized to expected values. + """ + instance = image_upgrade + instance._init_properties() + assert instance.bios_force is False + assert instance.check_interval == 10 + assert instance.check_timeout == 1800 + assert instance.config_reload is False + assert instance.devices is None + assert instance.disruptive is True + assert instance.epld_golden is False + assert instance.epld_module == "ALL" + assert instance.epld_upgrade is False + assert instance.force_non_disruptive is False + assert instance.non_disruptive is False + assert instance.force_non_disruptive is False + assert instance.package_install is False + assert instance.package_uninstall is False + assert instance.reboot is False + assert instance.write_erase is False + assert instance.valid_nxos_mode == { + "disruptive", + "non_disruptive", + "force_non_disruptive", + } + + +def test_image_upgrade_00100(image_upgrade) -> None: + """ + ### Classes and Methods + - ``ImageUpgrade`` + - ``validate_devices`` + + ### Test + + - ``ip_addresses`` contains the ip addresses of the devices for which + validation succeeds. + + ### Description + + ImageUpgrade.validate_devices updates the set ImageUpgrade.ip_addresses + with the ip addresses of the devices for which validation succeeds. + Currently, validation succeeds for all devices. This function may be + updated in the future to handle various failure scenarios. + + ### Expected result + + 1. ``ip_addresses`` will contain {"172.22.150.102", "172.22.150.108"} + """ + method_name = inspect.stack()[0][3] + key = f"{method_name}a" + + def responses(): + yield responses_ep_issu(key) + + gen_responses = ResponseGenerator(responses()) + + sender = Sender() + sender.ansible_module = MockAnsibleModule() + sender.gen = gen_responses + rest_send = RestSend(params) + rest_send.response_handler = ResponseHandler() + rest_send.sender = sender + + devices = [{"ip_address": "172.22.150.102"}, {"ip_address": "172.22.150.108"}] + + with does_not_raise(): + instance = image_upgrade + instance.results = Results() + instance.rest_send = rest_send + instance.issu_detail.rest_send = rest_send + instance.issu_detail.results = Results() + instance.devices = devices + instance._validate_devices() # pylint: disable=protected-access + + assert isinstance(instance.ip_addresses, set) + assert len(instance.ip_addresses) == 2 + assert "172.22.150.102" in instance.ip_addresses + assert "172.22.150.108" in instance.ip_addresses + + +def test_image_upgrade_01000(image_upgrade) -> None: + """ + ### Classes and Methods + - ``ImageUpgrade`` + - ``commit`` + + ### Test + + - ``ValueError`` is called because ``devices`` is None. + """ + + def responses(): + yield None + + gen_responses = ResponseGenerator(responses()) + + sender = Sender() + sender.ansible_module = MockAnsibleModule() + sender.gen = gen_responses + rest_send = RestSend(params) + rest_send.response_handler = ResponseHandler() + rest_send.sender = sender + + with does_not_raise(): + instance = image_upgrade + instance.results = Results() + instance.rest_send = rest_send + instance.issu_detail.rest_send = rest_send + instance.issu_detail.results = Results() + + match = r"ImageUpgrade\._validate_devices:\s+" + match += r"call instance.devices before calling commit\." + + with pytest.raises(ValueError, match=match): + instance.unit_test = True + instance.commit() + + +def test_image_upgrade_01010(image_upgrade) -> None: + """ + ### Classes and Methods + - ``ImageUpgrade`` + - ``commit`` + + ### Test + + - ``upgrade.nxos`` set to invalid value + + ### Setup + + - ``devices`` is set to a list of one dict for a device to be upgraded. + - responses_ep_issu.json indicates that the image has already + been staged, validated, and the device has already been upgraded + to the desired version. + - responses_ep_install_options.json indicates that the image EPLD + does not need upgrade. + + ### Expected result + + 1. ``commit`` calls ``_build_payload`` which raises ``ValueError`` + """ + method_name = inspect.stack()[0][3] + key = f"{method_name}a" + + def devices(): + yield devices_image_upgrade(key) + + gen_devices = ResponseGenerator(devices()) + + def responses(): + # ImageUpgrade.validate_commit_parameters. + yield responses_ep_issu(key) + # ImageUpgrade.wait_for_controller + yield responses_ep_issu(key) + # ImageUpgrade._build_payload + # -> ImageInstallOptions.refresh + yield responses_ep_install_options(key) + + gen_responses = ResponseGenerator(responses()) + + sender = Sender() + sender.ansible_module = MockAnsibleModule() + sender.gen = gen_responses + rest_send = RestSend(params) + rest_send.unit_test = True + rest_send.response_handler = ResponseHandler() + rest_send.sender = sender + + with does_not_raise(): + instance = image_upgrade + instance.results = Results() + instance.rest_send = rest_send + instance.issu_detail.rest_send = rest_send + instance.issu_detail.results = Results() + + # Set upgrade.nxos to invalid value "FOO" + instance.devices = gen_devices.next + + match = r"ImageUpgrade\._build_payload_issu_upgrade: upgrade.nxos must be a\s+" + match += r"boolean\. Got FOO\." + with pytest.raises(TypeError, match=match): + instance.commit() + + +def test_image_upgrade_01020(image_upgrade) -> None: + """ + ### Classes and Methods + - ``ImageUpgrade`` + - ``_build_payload`` + - ``commit`` + + ### Test + + - non-default values are set for several options. + - ``policy_changed`` is set to False. + - Verify that payload is built correctly. + + ### Setup + + - ``devices`` is set to a list of one dict for a device to be upgraded. + - responses_ep_issu.json indicates that the image has already + been staged, validated, and the device has already been upgraded + to the desired version. + - responses_ep_install_options.json indicates that the image EPLD + does not need upgrade. + - responses_ep_image_upgrade.json returns a successful response. + + + ### Expected result + + 1. instance.payload (built by instance._build_payload and based on + instance.devices) will equal a payload previously obtained by running + ansible-playbook against the controller for this scenario, which + verifies that the non-default values are included in the payload. + """ + method_name = inspect.stack()[0][3] + key = f"{method_name}a" + + def devices(): + yield devices_image_upgrade(key) + + gen_devices = ResponseGenerator(devices()) + + def responses(): + # ImageUpgrade.validate_commit_parameters + yield responses_ep_issu(key) + # ImageUpgrade.wait_for_controller + yield responses_ep_issu(key) + # ImageUpgrade._build_payload + # -> ImageInstallOptions.refresh + yield responses_ep_install_options(key) + # ImageUpgrade.commit + yield responses_ep_image_upgrade(key) + # ImageUpgrade._wait_for_image_upgrade_to_complete + yield responses_ep_issu(key) + + gen_responses = ResponseGenerator(responses()) + + sender = Sender() + sender.ansible_module = MockAnsibleModule() + sender.gen = gen_responses + rest_send = RestSend(params) + rest_send.unit_test = True + rest_send.response_handler = ResponseHandler() + rest_send.sender = sender + + with does_not_raise(): + instance = image_upgrade + instance.results = Results() + instance.rest_send = rest_send + instance.issu_detail.rest_send = rest_send + instance.issu_detail.results = Results() + + # non-default values are set for several options + instance.devices = gen_devices.next + + with does_not_raise(): + instance.commit() + assert instance.payload == payloads_ep_image_upgrade(key) + + +def test_image_upgrade_01030(image_upgrade) -> None: + """ + ### Classes and Methods + - ``ImageUpgrade`` + - ``_build_payload`` + - ``commit`` + + ### Test + + - User explicitly sets default values for several options. + - ``policy_changed`` is set to True. + + ### Setup + + - ``devices`` is set to a list of one dict for a device to be upgraded. + - responses_ep_issu.json indicates that the image has already + been staged, validated, and the device has already been upgraded to + the desired version. + - responses_ep_install_options.json indicates that the image EPLD + does not need upgrade. + - responses_ep_image_upgrade.json returns a successful response. + + ### Expected result + + - instance.payload will equal a payload previously obtained by + running ansible-playbook against the controller for this scenario. + """ + method_name = inspect.stack()[0][3] + key = f"{method_name}a" + + def devices(): + yield devices_image_upgrade(key) + + gen_devices = ResponseGenerator(devices()) + + def responses(): + # ImageUpgrade.validate_commit_parameters + yield responses_ep_issu(key) + # ImageUpgrade.wait_for_controller + yield responses_ep_issu(key) + # ImageUpgrade._build_payload + # -> ImageInstallOptions.refresh + yield responses_ep_install_options(key) + # ImageUpgrade.commit + yield responses_ep_image_upgrade(key) + # ImageUpgrade._wait_for_image_upgrade_to_complete + yield responses_ep_issu(key) + + gen_responses = ResponseGenerator(responses()) + + sender = Sender() + sender.ansible_module = MockAnsibleModule() + sender.gen = gen_responses + rest_send = RestSend(params) + rest_send.unit_test = True + rest_send.response_handler = ResponseHandler() + rest_send.sender = sender + + with does_not_raise(): + instance = image_upgrade + instance.results = Results() + instance.rest_send = rest_send + instance.issu_detail.rest_send = rest_send + instance.issu_detail.results = Results() + + # Default values explicitely set for several options + instance.devices = gen_devices.next + + with does_not_raise(): + instance.commit() + assert instance.payload == payloads_ep_image_upgrade(key) + + +def test_image_upgrade_01040(image_upgrade) -> None: + """ + ### Classes and Methods + - ``ImageUpgrade`` + - ``_build_payload`` + - ``commit`` + + ### Test + + - Invalid value for ``nxos.mode`` + + ### Setup + + - ``devices`` is set to a list of one dict for a device to be upgraded. + - ``devices`` is set to contain an invalid ``nxos.mode`` value. + - responses_ep_issu.json indicates that the device has not yet been + upgraded to the desired version + - responses_ep_install_options.json indicates that EPLD upgrade is + not needed. + + ### Expected result + + - ``commit`` calls ``_build_payload``, which raises ``ValueError`` + """ + method_name = inspect.stack()[0][3] + key = f"{method_name}a" + + def devices(): + yield devices_image_upgrade(key) + + gen_devices = ResponseGenerator(devices()) + + def responses(): + # ImageUpgrade.validate_commit_parameters + yield responses_ep_issu(key) + # ImageUpgrade.wait_for_controller + yield responses_ep_issu(key) + # ImageUpgrade._build_payload + # -> ImageInstallOptions.refresh + yield responses_ep_install_options(key) + + gen_responses = ResponseGenerator(responses()) + + sender = Sender() + sender.ansible_module = MockAnsibleModule() + sender.gen = gen_responses + rest_send = RestSend(params) + rest_send.unit_test = True + rest_send.response_handler = ResponseHandler() + rest_send.sender = sender + + with does_not_raise(): + instance = image_upgrade + instance.results = Results() + instance.rest_send = rest_send + instance.issu_detail.rest_send = rest_send + instance.issu_detail.results = Results() + + # nxos.mode is invalid + instance.devices = gen_devices.next + + match = r"ImageUpgrade\._build_payload_issu_options_1:\s+" + match += r"options.nxos.mode must be one of\s+" + match += r"\['disruptive', 'force_non_disruptive', 'non_disruptive'\].\s+" + match += r"Got FOO\." + + with pytest.raises(ValueError, match=match): + instance.commit() + + +def test_image_upgrade_01050(image_upgrade) -> None: + """ + ### Classes and Methods + - ``ImageUpgrade`` + - ``_build_payload`` + - ``commit`` + + ### Test + + - Force code coverage of ``nxos.mode`` == "non_disruptive" path. + + ### Setup + + - ``devices`` is set to a list of one dict for a device to be upgraded. + - ``devices`` is set to contain ``nxos.mode`` == "non_disruptive", + forcing the code to take ``nxos_mode`` == "non_disruptive" path. + - responses_ep_issu.json (key_a) indicates that the device has not yet + been upgraded to the desired version + - responses_ep_install_options.json indicates that EPLD upgrade is + not needed. + - responses_ep_issu.json (key_b) indicates that the device upgrade has + completed. + + ### Expected result + + 1. self.payload["issuUpgradeOptions1"]["disruptive"] is False + 2. self.payload["issuUpgradeOptions1"]["forceNonDisruptive"] is False + 3. self.payload["issuUpgradeOptions1"]["nonDisruptive"] is True + """ + method_name = inspect.stack()[0][3] + key_a = f"{method_name}a" + key_b = f"{method_name}b" + + def devices(): + yield devices_image_upgrade(key_a) + + gen_devices = ResponseGenerator(devices()) + + def responses(): + # ImageUpgrade.validate_commit_parameters + yield responses_ep_issu(key_a) + # ImageUpgrade.wait_for_controller + yield responses_ep_issu(key_a) + # ImageUpgrade._build_payload + # -> ImageInstallOptions.refresh + yield responses_ep_install_options(key_a) + # ImageUpgrade.commit + yield responses_ep_image_upgrade(key_a) + # ImageUpgrade._wait_for_image_upgrade_to_complete + yield responses_ep_issu(key_b) + + gen_responses = ResponseGenerator(responses()) + + sender = Sender() + sender.ansible_module = MockAnsibleModule() + sender.gen = gen_responses + rest_send = RestSend(params) + rest_send.unit_test = True + rest_send.response_handler = ResponseHandler() + rest_send.sender = sender + + with does_not_raise(): + instance = image_upgrade + instance.results = Results() + instance.rest_send = rest_send + instance.issu_detail.rest_send = rest_send + instance.issu_detail.results = Results() + + # nxos.mode == non_disruptive + instance.devices = gen_devices.next + + with does_not_raise(): + instance.commit() + + assert instance.payload["issuUpgradeOptions1"]["disruptive"] is False + assert instance.payload["issuUpgradeOptions1"]["forceNonDisruptive"] is False + assert instance.payload["issuUpgradeOptions1"]["nonDisruptive"] is True + + +def test_image_upgrade_01060(image_upgrade) -> None: + """ + ### Classes and Methods + - ``ImageUpgrade`` + - ``_build_payload`` + - ``commit`` + + ### Test + + - Force code coverage of ``nxos.mode`` == "force_non_disruptive" path. + + ### Setup + + - ``devices`` is set to a list of one dict for a device to be upgraded. + - ``devices`` is set to contain ``nxos.mode`` == "force_non_disruptive", + forcing the code to take ``nxos_mode`` == "force_non_disruptive" path + - responses_ep_issu.json (key_a) indicates that the device has not yet + been upgraded to the desired version + - responses_ep_install_options.json indicates that EPLD upgrade is + not needed. + - responses_ep_issu.json (key_b) indicates that the device upgrade has + completed. + + ### Expected result + + 1. self.payload["issuUpgradeOptions1"]["disruptive"] is False + 2. self.payload["issuUpgradeOptions1"]["forceNonDisruptive"] is True + 3. self.payload["issuUpgradeOptions1"]["nonDisruptive"] is False + """ + method_name = inspect.stack()[0][3] + key_a = f"{method_name}a" + key_b = f"{method_name}b" + + def devices(): + yield devices_image_upgrade(key_a) + + gen_devices = ResponseGenerator(devices()) + + def responses(): + # ImageUpgrade.validate_commit_parameters + yield responses_ep_issu(key_a) + # ImageUpgrade.wait_for_controller + yield responses_ep_issu(key_a) + # ImageUpgrade._build_payload + # -> ImageInstallOptions.refresh + yield responses_ep_install_options(key_a) + # ImageUpgrade.commit + yield responses_ep_image_upgrade(key_a) + # ImageUpgrade._wait_for_image_upgrade_to_complete + yield responses_ep_issu(key_b) + + gen_responses = ResponseGenerator(responses()) + + sender = Sender() + sender.ansible_module = MockAnsibleModule() + sender.gen = gen_responses + rest_send = RestSend(params) + rest_send.unit_test = True + rest_send.response_handler = ResponseHandler() + rest_send.sender = sender + + with does_not_raise(): + instance = image_upgrade + instance.results = Results() + instance.rest_send = rest_send + instance.issu_detail.rest_send = rest_send + instance.issu_detail.results = Results() + + # nxos.mode == force_non_disruptive + instance.devices = gen_devices.next + + with does_not_raise(): + instance.commit() + + assert instance.payload["issuUpgradeOptions1"]["disruptive"] is False + assert instance.payload["issuUpgradeOptions1"]["forceNonDisruptive"] is True + assert instance.payload["issuUpgradeOptions1"]["nonDisruptive"] is False + + +def test_image_upgrade_01070(image_upgrade) -> None: + """ + ### Classes and Methods + - ``ImageUpgrade`` + - ``_build_payload`` + + ### Test + + - Invalid value for ``options.nxos.bios_force`` + + ### Setup + + - ``devices`` is set to a list of one dict for a device to be upgraded. + - ``devices`` is set to contain a non-boolean value for + ``options.nxos.bios_force``. + - responses_ep_issu.json indicates that the device has not yet been + upgraded to the desired version. + - responses_ep_install_options.json indicates that EPLD upgrade is + not needed. + + ### Expected result + + 1. ``_build_payload_issu_options_2`` raises ``TypeError`` + """ + method_name = inspect.stack()[0][3] + key = f"{method_name}a" + + def devices(): + yield devices_image_upgrade(key) + + gen_devices = ResponseGenerator(devices()) + + def responses(): + # ImageUpgrade.validate_commit_parameters + yield responses_ep_issu(key) + # ImageUpgrade.wait_for_controller + yield responses_ep_issu(key) + # ImageUpgrade._build_payload + # -> ImageInstallOptions.refresh + yield responses_ep_install_options(key) + + gen_responses = ResponseGenerator(responses()) + + sender = Sender() + sender.ansible_module = MockAnsibleModule() + sender.gen = gen_responses + rest_send = RestSend(params) + rest_send.unit_test = True + rest_send.response_handler = ResponseHandler() + rest_send.sender = sender + + with does_not_raise(): + instance = image_upgrade + instance.results = Results() + instance.rest_send = rest_send + instance.issu_detail.rest_send = rest_send + instance.issu_detail.results = Results() + + # options.nxos.bios_force is invalid (FOO) + instance.devices = gen_devices.next + + match = r"ImageUpgrade\._build_payload_issu_options_2:\s+" + match += r"options\.nxos\.bios_force must be a boolean\.\s+" + match += r"Got FOO\." + with pytest.raises(TypeError, match=match): + instance.commit() + + +def test_image_upgrade_01080(image_upgrade) -> None: + """ + ### Classes and Methods + - ``ImageUpgrade`` + - ``_build_payload`` + - ``commit`` + + ### Test + - Incompatible values for ``options.epld.golden`` and ``upgrade.nxos``. + + ### Setup + + - ``devices`` is set to a list of one dict for a device to be upgraded. + - ``devices`` is set to contain ``epld.golden`` == True and + ``upgrade.nxos`` == True. + - responses_ep_issu.json indicates that the device has not yet been + upgraded to the desired version. + - responses_ep_install_options.json indicates that EPLD upgrade is + not needed. + + ### Expected result + + 1. ``commit`` calls ``_build_payload`` which raises ``ValueError``. + """ + method_name = inspect.stack()[0][3] + key = f"{method_name}a" + + def devices(): + yield devices_image_upgrade(key) + + gen_devices = ResponseGenerator(devices()) + + def responses(): + # ImageUpgrade.validate_commit_parameters + yield responses_ep_issu(key) + # ImageUpgrade.wait_for_controller + yield responses_ep_issu(key) + # ImageUpgrade._build_payload + # -> ImageInstallOptions.refresh + yield responses_ep_install_options(key) + + gen_responses = ResponseGenerator(responses()) + + sender = Sender() + sender.ansible_module = MockAnsibleModule() + sender.gen = gen_responses + rest_send = RestSend(params) + rest_send.unit_test = True + rest_send.response_handler = ResponseHandler() + rest_send.sender = sender + + with does_not_raise(): + instance = image_upgrade + instance.results = Results() + instance.rest_send = rest_send + instance.issu_detail.rest_send = rest_send + instance.issu_detail.results = Results() + + # options.epld.golden is True and upgrade.nxos is True + instance.devices = gen_devices.next + + match = r"ImageUpgrade\._build_payload_epld:\s+" + match += r"Invalid configuration for 172\.22\.150\.102\.\s+" + match += r"If options\.epld.golden is True\s+" + match += r"all other upgrade options, e\.g\. upgrade\.nxos,\s+" + match += r"must be False\." + with pytest.raises(ValueError, match=match): + instance.commit() + + +def test_image_upgrade_01090(image_upgrade) -> None: + """ + ### Classes and Methods + - ``ImageUpgrade`` + - ``_build_payload`` + - ``commit`` + + ### Test + + - Invalid value for ``epld.module`` + + ### Setup + + - ``devices`` is set to a list of one dict for a device to be upgraded. + - ``devices`` is set to contain invalid ``epld.module``. + - responses_ep_issu.json indicates that the device has not yet been + upgraded to the desired version. + - responses_ep_install_options.json indicates that EPLD upgrade is + not needed. + + ### Expected result + + 1. ``commit`` calls ``_build_payload`` which raises ``ValueError`` + """ + method_name = inspect.stack()[0][3] + key = f"{method_name}a" + + def devices(): + yield devices_image_upgrade(key) + + gen_devices = ResponseGenerator(devices()) + + def responses(): + # ImageUpgrade.validate_commit_parameters + yield responses_ep_issu(key) + # ImageUpgrade.wait_for_controller + yield responses_ep_issu(key) + # ImageUpgrade._build_payload + # -> ImageInstallOptions.refresh + yield responses_ep_install_options(key) + + gen_responses = ResponseGenerator(responses()) + + sender = Sender() + sender.ansible_module = MockAnsibleModule() + sender.gen = gen_responses + rest_send = RestSend(params) + rest_send.unit_test = True + rest_send.response_handler = ResponseHandler() + rest_send.sender = sender + + with does_not_raise(): + instance = image_upgrade + instance.results = Results() + instance.rest_send = rest_send + instance.issu_detail.rest_send = rest_send + instance.issu_detail.results = Results() + + # options.epld.module is invalid + instance.devices = gen_devices.next + + match = r"ImageUpgrade\._build_payload_epld:\s+" + match += r"options\.epld\.module must either be 'ALL'\s+" + match += r"or an integer\. Got FOO\." + with pytest.raises(ValueError, match=match): + instance.commit() + + +def test_image_upgrade_01100(image_upgrade) -> None: + """ + ### Classes and Methods + - ``ImageUpgrade`` + - ``_build_payload`` + - ``commit`` + + ### Test + - Invalid value for ``epld.golden`` + + ### Setup + - ``devices`` is set to a list of one dict for a device to be upgraded. + - instance.devices is set to contain invalid ``epld.golden`` + - responses_ep_issu.json indicates that the device has not yet been + upgraded to the desired version. + - responses_ep_install_options.json indicates that EPLD upgrade is + not needed. + + ### Expected result + + 1. ``commit`` calls ``_build_payload`` which raises ``TypeError`` + """ + method_name = inspect.stack()[0][3] + key = f"{method_name}a" + + def devices(): + yield devices_image_upgrade(key) + + gen_devices = ResponseGenerator(devices()) + + def responses(): + # ImageUpgrade.validate_commit_parameters + yield responses_ep_issu(key) + # ImageUpgrade.wait_for_controller + yield responses_ep_issu(key) + # ImageUpgrade._build_payload + # -> ImageInstallOptions.refresh + yield responses_ep_install_options(key) + + gen_responses = ResponseGenerator(responses()) + + sender = Sender() + sender.ansible_module = MockAnsibleModule() + sender.gen = gen_responses + rest_send = RestSend(params) + rest_send.unit_test = True + rest_send.response_handler = ResponseHandler() + rest_send.sender = sender + + with does_not_raise(): + instance = image_upgrade + instance.results = Results() + instance.rest_send = rest_send + instance.issu_detail.rest_send = rest_send + instance.issu_detail.results = Results() + + # options.epld.golden is not a boolean + instance.devices = gen_devices.next + + match = r"ImageUpgrade\._build_payload_epld:\s+" + match += r"options\.epld\.golden must be a boolean\.\s+" + match += r"Got FOO\." + with pytest.raises(TypeError, match=match): + instance.commit() + + +def test_image_upgrade_01110(image_upgrade) -> None: + """ + ### Classes and Methods + - ``ImageUpgrade`` + - ``_build_payload`` + - ``commit`` + + ### Test + - Invalid value for ``reboot`` + + ### Setup + + - ``devices`` is set to a list of one dict for a device to be upgraded. + - ``devices`` is set to contain invalid value for ``reboot``. + - responses_ep_issu.json indicates that the device has not yet been + upgraded to the desired version. + - responses_ep_install_options.json indicates that EPLD upgrade is + not needed. + + ## Expected result + + 1. ``commit`` calls ``_build_payload`` which raises ``TypeError``. + """ + method_name = inspect.stack()[0][3] + key = f"{method_name}a" + + def devices(): + yield devices_image_upgrade(key) + + gen_devices = ResponseGenerator(devices()) + + def responses(): + # ImageUpgrade.validate_commit_parameters + yield responses_ep_issu(key) + # ImageUpgrade.wait_for_controller + yield responses_ep_issu(key) + # ImageUpgrade._build_payload + # -> ImageInstallOptions.refresh + yield responses_ep_install_options(key) + + gen_responses = ResponseGenerator(responses()) + + sender = Sender() + sender.ansible_module = MockAnsibleModule() + sender.gen = gen_responses + rest_send = RestSend(params) + rest_send.unit_test = True + rest_send.response_handler = ResponseHandler() + rest_send.sender = sender + + with does_not_raise(): + instance = image_upgrade + instance.results = Results() + instance.rest_send = rest_send + instance.issu_detail.rest_send = rest_send + instance.issu_detail.results = Results() + + # reboot is invalid + instance.devices = gen_devices.next + + match = r"ImageUpgrade\._build_payload_reboot:\s+" + match += r"reboot must be a boolean\. Got FOO\." + with pytest.raises(TypeError, match=match): + instance.commit() + + +def test_image_upgrade_01120(image_upgrade) -> None: + """ + ### Classes and Methods + - ``ImageUpgrade`` + - ``_build_payload`` + - ``commit`` + + ### Test + + - Invalid value for ``options.reboot.config_reload``. + + ### Setup + + - ``devices`` is set to a list of one dict for a device to be upgraded. + - ``devices`` is set to contain invalid value for + ``options.reboot.config_reload``. + - responses_ep_issu.json indicates that the device has not yet been + upgraded to the desired version. + - responses_ep_install_options.json indicates that EPLD upgrade is + not needed. + + ### Expected result + + 1. ``commit`` calls ``_build_payload`` which raises ``TypeError``. + """ + method_name = inspect.stack()[0][3] + key = f"{method_name}a" + + def devices(): + yield devices_image_upgrade(key) + + gen_devices = ResponseGenerator(devices()) + + def responses(): + # ImageUpgrade.validate_commit_parameters + yield responses_ep_issu(key) + # ImageUpgrade.wait_for_controller + yield responses_ep_issu(key) + # ImageUpgrade._build_payload + # -> ImageInstallOptions.refresh + yield responses_ep_install_options(key) + + gen_responses = ResponseGenerator(responses()) + + sender = Sender() + sender.ansible_module = MockAnsibleModule() + sender.gen = gen_responses + rest_send = RestSend(params) + rest_send.unit_test = True + rest_send.response_handler = ResponseHandler() + rest_send.sender = sender + + with does_not_raise(): + instance = image_upgrade + instance.results = Results() + instance.rest_send = rest_send + instance.issu_detail.rest_send = rest_send + instance.issu_detail.results = Results() + + # options.reboot.config_reload is invalid + instance.devices = gen_devices.next + + match = "ImageUpgrade._build_payload_reboot_options: " + match += r"options.reboot.config_reload must be a boolean. Got FOO\." + with pytest.raises(TypeError, match=match): + instance.commit() + + +def test_image_upgrade_01130(image_upgrade) -> None: + """ + ### Classes and Methods + + - ``ImageUpgrade`` + - ``_build_payload`` + - ``commit`` + + ### Test + + - Invalid value for options.reboot.write_erase + + ### Setup + + - ``devices`` is set to a list of one dict for a device to be upgraded. + - ``devices`` is set to contain invalid value for + ``options.reboot.write_erase``. + - responses_ep_issu.json indicates that the device has not yet been + upgraded to the desired version. + - responses_ep_install_options.json indicates that EPLD upgrade is + not needed. + + ### Expected result + + 1. ``commit`` calls ``_build_payload`` which raises ``TypeError`` + """ + method_name = inspect.stack()[0][3] + key = f"{method_name}a" + + def devices(): + yield devices_image_upgrade(key) + + gen_devices = ResponseGenerator(devices()) + + def responses(): + # ImageUpgrade.validate_commit_parameters + yield responses_ep_issu(key) + # ImageUpgrade.wait_for_controller + yield responses_ep_issu(key) + # ImageUpgrade._build_payload + # -> ImageInstallOptions.refresh + yield responses_ep_install_options(key) + + gen_responses = ResponseGenerator(responses()) + + sender = Sender() + sender.ansible_module = MockAnsibleModule() + sender.gen = gen_responses + rest_send = RestSend(params) + rest_send.unit_test = True + rest_send.response_handler = ResponseHandler() + rest_send.sender = sender + + with does_not_raise(): + instance = image_upgrade + instance.results = Results() + instance.rest_send = rest_send + instance.issu_detail.rest_send = rest_send + instance.issu_detail.results = Results() + + # options.reboot.write_erase is invalid + instance.devices = gen_devices.next + + match = "ImageUpgrade._build_payload_reboot_options: " + match += r"options.reboot.write_erase must be a boolean. Got FOO\." + with pytest.raises(TypeError, match=match): + instance.commit() + + +def test_image_upgrade_01140(image_upgrade) -> None: + """ + ### Classes and Methods + + - ``ImageUpgrade`` + - ``_build_payload`` + - ``commit`` + + ### Test + + Invalid value for ``options.package.uninstall``. + + ### Setup + + - ``devices`` is set to a list of one dict for a device to be upgraded. + - ``devices`` is set to contain invalid value for + ``options.package.uninstall`` + - responses_ep_issu.json indicates that the device has not yet been + upgraded to the desired version. + - responses_ep_install_options.json indicates that EPLD upgrade is + not needed. + + ### Expected result + + 1. ``commit`` calls ``_build_payload`` which raises ``TypeError`` + + ### NOTES + + 1. The corresponding test for options.package.install is missing. + It's not needed since ``ImageInstallOptions`` will raise exceptions + on invalid values before ``ImageUpgrade`` has a chance to verify + the value. + """ + method_name = inspect.stack()[0][3] + key = f"{method_name}a" + + def devices(): + yield devices_image_upgrade(key) + + gen_devices = ResponseGenerator(devices()) + + def responses(): + # ImageUpgrade.validate_commit_parameters + yield responses_ep_issu(key) + # ImageUpgrade.wait_for_controller + yield responses_ep_issu(key) + # ImageUpgrade._build_payload + # -> ImageInstallOptions.refresh + yield responses_ep_install_options(key) + + gen_responses = ResponseGenerator(responses()) + + sender = Sender() + sender.ansible_module = MockAnsibleModule() + sender.gen = gen_responses + rest_send = RestSend(params) + rest_send.unit_test = True + rest_send.response_handler = ResponseHandler() + rest_send.sender = sender + + with does_not_raise(): + instance = image_upgrade + instance.results = Results() + instance.rest_send = rest_send + instance.issu_detail.rest_send = rest_send + instance.issu_detail.results = Results() + + # options.package.uninstall is invalid + instance.devices = gen_devices.next + + match = "ImageUpgrade._build_payload_package: " + match += r"options.package.uninstall must be a boolean. Got FOO\." + with pytest.raises(TypeError, match=match): + instance.commit() + + +def test_image_upgrade_01150(image_upgrade) -> None: + """ + ### Classes and Methods + + - ``ImageUpgrade`` + - ``_build_payload`` + - ``commit`` + + ### Test + + Invalid value for ``options.package.install``. + + ### Setup + + - ``devices`` is set to a list of one dict for a device to be upgraded. + - ``devices`` is set to contain invalid value for + ``options.package.install`` + - responses_ep_issu.json indicates that the device has not yet been + upgraded to the desired version. + - responses_ep_install_options.json indicates that EPLD upgrade is + not needed. + + ### Expected result + + 1. ``commit`` calls ``_build_payload`` which calls + ``ImageInstallOptions.package_install`` which raises + ``TypeError``. + + ### NOTES + 1. This test differs from the previous test since ``ImageInstallOptions`` + catches the error sooner. + """ + method_name = inspect.stack()[0][3] + key = f"{method_name}a" + + def devices(): + yield devices_image_upgrade(key) + + gen_devices = ResponseGenerator(devices()) + + def responses(): + # ImageUpgrade.validate_commit_parameters + yield responses_ep_issu(key) + # ImageUpgrade.wait_for_controller + yield responses_ep_issu(key) + + gen_responses = ResponseGenerator(responses()) + + sender = Sender() + sender.ansible_module = MockAnsibleModule() + sender.gen = gen_responses + rest_send = RestSend(params) + rest_send.unit_test = True + rest_send.response_handler = ResponseHandler() + rest_send.sender = sender + + with does_not_raise(): + instance = image_upgrade + instance.results = Results() + instance.rest_send = rest_send + instance.issu_detail.rest_send = rest_send + instance.issu_detail.results = Results() + + # options.package.install is invalid + instance.devices = gen_devices.next + + match = r"ImageInstallOptions\.package_install:\s+" + match += r"package_install must be a boolean value\.\s+" + match += r"Got FOO\." + with pytest.raises(TypeError, match=match): + instance.commit() + + +def test_image_upgrade_01160(image_upgrade) -> None: + """ + ### Classes and Methods + + - ``ImageUpgrade`` + - ``_build_payload`` + - ``commit`` + + ### Test + + - Invalid value for upgrade.epld + + ### Setup + + - ``devices`` is set to a list of one dict for a device to be upgraded. + - ``devices`` is set to contain invalid value for ``upgrade.epld``. + - responses_ep_issu.json indicates that the device has not yet been + upgraded to the desired version. + - responses_ep_install_options.json indicates that EPLD upgrade is + not needed. + + ### Expected result + + 1. ``commit`` calls ``_build_payload`` which raises ``TypeError``. + """ + method_name = inspect.stack()[0][3] + key = f"{method_name}a" + + def devices(): + yield devices_image_upgrade(key) + + gen_devices = ResponseGenerator(devices()) + + def responses(): + # ImageUpgrade.validate_commit_parameters + yield responses_ep_issu(key) + # ImageUpgrade.wait_for_controller + yield responses_ep_issu(key) + # ImageUpgrade._build_payload + # -> ImageInstallOptions.refresh + yield responses_ep_install_options(key) + + gen_responses = ResponseGenerator(responses()) + + sender = Sender() + sender.ansible_module = MockAnsibleModule() + sender.gen = gen_responses + rest_send = RestSend(params) + rest_send.unit_test = True + rest_send.response_handler = ResponseHandler() + rest_send.sender = sender + + with does_not_raise(): + instance = image_upgrade + instance.results = Results() + instance.rest_send = rest_send + instance.issu_detail.rest_send = rest_send + instance.issu_detail.results = Results() + + # upgrade.epld is invalid + instance.devices = gen_devices.next + + match = "ImageInstallOptions.epld: " + match += r"epld must be a boolean value. Got FOO\." + with pytest.raises(TypeError, match=match): + instance.commit() + + +def test_image_upgrade_02000(image_upgrade) -> None: + """ + ### Classes and Methods + + - ``ImageUpgrade`` + - ``commit`` + + #### Test + + - Bad result code in image upgrade response + + ### Setup + + - ``devices`` is set to a list of one dict for a device to be upgraded. + - responses_ep_issu.json indicates that the device has not yet been + upgraded to the desired version. + - responses_ep_install_options.json indicates that EPLD upgrade is + not needed. + - responses_ep_image_upgrade.json returns RESULT_CODE 500 with + MESSAGE "Internal Server Error". + + ### Expected result + + 1. ``commit`` raises ``ControllerResponseError`` because + ``rest_send.result_current`` does not equal "success". + """ + method_name = inspect.stack()[0][3] + key = f"{method_name}a" + + def devices(): + yield devices_image_upgrade(key) + + gen_devices = ResponseGenerator(devices()) + + def responses(): + # ImageUpgrade.validate_commit_parameters + yield responses_ep_issu(key) + # ImageUpgrade.wait_for_controller + yield responses_ep_issu(key) + # ImageUpgrade._build_payload + # -> ImageInstallOptions.refresh + yield responses_ep_install_options(key) + # ImageUpgrade.commit + yield responses_ep_image_upgrade(key) + + gen_responses = ResponseGenerator(responses()) + + sender = Sender() + sender.ansible_module = MockAnsibleModule() + sender.gen = gen_responses + rest_send = RestSend(params) + rest_send.timeout = 1 + rest_send.unit_test = True + rest_send.response_handler = ResponseHandler() + rest_send.sender = sender + + with does_not_raise(): + instance = image_upgrade + instance.results = Results() + instance.rest_send = rest_send + instance.issu_detail.rest_send = rest_send + instance.issu_detail.results = Results() + + # Valid devices + instance.devices = gen_devices.next + + match = "ImageUpgrade.commit: failed: " + match += r"\{'success': False, 'changed': False\}. " + match += r"Controller response: \{'DATA': 123, " + match += "'MESSAGE': 'Internal Server Error', 'METHOD': 'POST', " + match += "'REQUEST_PATH': " + match += "'https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/" + match += "imagemanagement/rest/imageupgrade/upgrade-image', " + match += r"'RETURN_CODE': 500\}" + with pytest.raises(ControllerResponseError, match=match): + instance.commit() + + +# test getter properties + + +# test setter properties + + +MATCH_03000 = r"ImageUpgrade\.bios_force:\s+" +MATCH_03000 += r"instance.bios_force must be a boolean\." + + +@pytest.mark.parametrize( + "value, expected, raise_flag", + [ + (True, does_not_raise(), False), + (False, does_not_raise(), False), + ("FOO", pytest.raises(TypeError, match=MATCH_03000), True), + ], +) +def test_image_upgrade_03000(image_upgrade, value, expected, raise_flag) -> None: + """ + ### Classes and Methods + + - ``ImageUpgrade`` + - ``bios_force`` + + ### Test + + - ``bios_force`` does not raise ``TypeError`` if passed a boolean. + - ``bios_force`` raises ``TypeError`` if passed a non-boolean. + - The default value is set if ``TypeError`` is raised. + """ + with does_not_raise(): + instance = image_upgrade + + with expected: + instance.bios_force = value + if raise_flag is False: + assert instance.bios_force == value + else: + assert instance.bios_force is False + + +MATCH_03010 = r"ImageUpgrade\.check_interval: instance\.check_interval " +MATCH_03010 += r"must be an integer\." + + +@pytest.mark.parametrize( + "value, expected, raise_flag", + [ + (1, does_not_raise(), False), + (False, pytest.raises(TypeError, match=MATCH_03010), True), + ("FOO", pytest.raises(TypeError, match=MATCH_03010), True), + ], +) +def test_image_upgrade_03010(image_upgrade, value, expected, raise_flag) -> None: + """ + ### Classes and Methods + + - ``ImageUpgrade`` + - ``check_interval`` + + ### Test + + - ``check_interval`` does not raise ``TypeError`` if the value is an + integer + - ``check_interval`` raises ``TypeError`` if the value is not an + integer + - The default value is set if ``TypeError`` is raised. + """ + with does_not_raise(): + instance = image_upgrade + with expected: + instance.check_interval = value + if raise_flag is False: + assert instance.check_interval == value + else: + assert instance.check_interval == 10 + + +MATCH_03020 = r"ImageUpgrade\.check_timeout: instance\.check_timeout " +MATCH_03020 += r"must be an integer\." + + +@pytest.mark.parametrize( + "value, expected, raise_flag", + [ + (1, does_not_raise(), False), + (False, pytest.raises(TypeError, match=MATCH_03020), True), + ("FOO", pytest.raises(TypeError, match=MATCH_03020), True), + ], +) +def test_image_upgrade_03020(image_upgrade, value, expected, raise_flag) -> None: + """ + ### Classes and Methods + + - ``ImageUpgrade`` + - ``check_timeout`` + + ### Test + + - ``check_timeout`` does not raise ``TypeError`` if passed an integer. + - ``check_timeout`` raises ``TypeError`` if passed a non-integer. + - The default value is set if ``TypeError`` is raised. + """ + with does_not_raise(): + instance = image_upgrade + with expected: + instance.check_timeout = value + if raise_flag is False: + assert instance.check_timeout == value + else: + assert instance.check_timeout == 1800 + + +MATCH_03030 = r"ImageUpgrade\.config_reload: " +MATCH_03030 += r"instance\.config_reload must be a boolean\." + + +@pytest.mark.parametrize( + "value, expected, raise_flag", + [ + (True, does_not_raise(), False), + (False, does_not_raise(), False), + ("FOO", pytest.raises(TypeError, match=MATCH_03030), True), + ], +) +def test_image_upgrade_03030(image_upgrade, value, expected, raise_flag) -> None: + """ + ### Classes and Methods + + - ``ImageUpgrade`` + - ``config_reload`` + + ### Test + + - ``config_reload`` does not raise ``TypeError`` if passed a boolean. + - ``config_reload`` raises ``TypeError`` if passed a non-boolean. + - The default value is set if ``TypeError`` is raised. + """ + with does_not_raise(): + instance = image_upgrade + + with expected: + instance.config_reload = value + if raise_flag is False: + assert instance.config_reload == value + else: + assert instance.config_reload is False + + +MATCH_03040_COMMON = r"ImageUpgrade.devices:\s+" +MATCH_03040_COMMON += r"instance\.devices must be a python list of dict" + +MATCH_03040_FAIL_1 = rf"{MATCH_03040_COMMON}. Got not a list\." +MATCH_03040_FAIL_2 = rf"{MATCH_03040_COMMON}. Got \['not a dict'\]\." + +MATCH_03040_FAIL_3 = rf"{MATCH_03040_COMMON}, where each dict contains\s+" +MATCH_03040_FAIL_3 += r"the following keys: ip_address\.\s+" +MATCH_03040_FAIL_3 += r"Got \[\{'bad_key_ip_address': '192.168.1.1'\}\]." + +DATA_03040_PASS = [{"ip_address": "192.168.1.1"}] +DATA_03040_FAIL_1 = "not a list" +DATA_03040_FAIL_2 = ["not a dict"] +DATA_03040_FAIL_3 = [{"bad_key_ip_address": "192.168.1.1"}] + + +@pytest.mark.parametrize( + "value, expected", + [ + (DATA_03040_PASS, does_not_raise()), + (DATA_03040_FAIL_1, pytest.raises(TypeError, match=MATCH_03040_FAIL_1)), + (DATA_03040_FAIL_2, pytest.raises(TypeError, match=MATCH_03040_FAIL_2)), + (DATA_03040_FAIL_3, pytest.raises(ValueError, match=MATCH_03040_FAIL_3)), + ], +) +def test_image_upgrade_03040(image_upgrade, value, expected) -> None: + """ + ### Classes and Methods + + - ``ImageUpgrade`` + - ``devices`` + + ### Test + + - ``devices`` does not raise Exception if passed a valid list + of dict. + - ``devices`` raises ``TypeError`` if passed a non-list or a list of + non-dicts. + - ``devices`` raises ``ValueError`` if passed a list of dict where + dict is missing mandatory key "ip_address". + """ + instance = image_upgrade + + with expected: + instance.devices = value + + +MATCH_03050 = r"ImageUpgrade\.disruptive:\s+" +MATCH_03050 += r"instance\.disruptive must be a boolean\." + + +@pytest.mark.parametrize( + "value, expected, raise_flag", + [ + (True, does_not_raise(), False), + (False, does_not_raise(), False), + ("FOO", pytest.raises(TypeError, match=MATCH_03050), True), + ], +) +def test_image_upgrade_03050(image_upgrade, value, expected, raise_flag) -> None: + """ + ### Classes and Methods + + - ``ImageUpgrade`` + - ``disruptive`` + + ### Test + + - ``disruptive`` does not raise ``TypeError`` if passed a boolean. + - ``disruptive`` raises ``TypeError`` if passed a non-boolean. + - The default value is set if ``TypeError`` is raised. + """ + instance = image_upgrade + + with expected: + instance.disruptive = value + if raise_flag is False: + assert instance.disruptive == value + else: + assert instance.disruptive is True + + +MATCH_03060 = "ImageUpgrade.epld_golden: " +MATCH_03060 += "instance.epld_golden must be a boolean." + + +@pytest.mark.parametrize( + "value, expected, raise_flag", + [ + (True, does_not_raise(), False), + (False, does_not_raise(), False), + ("FOO", pytest.raises(TypeError, match=MATCH_03060), True), + ], +) +def test_image_upgrade_03060(image_upgrade, value, expected, raise_flag) -> None: + """ + ### Classes and Methods + + - ``ImageUpgrade`` + - ``epld_golden`` + + ### Test + + - ``epld_golden`` does not raise ``TypeError`` if passed a boolean. + - ``epld_golden`` raises ``TypeError`` if passed a non-boolean. + - The default value is set if ``TypeError`` is raised. + """ + instance = image_upgrade + + with expected: + instance.epld_golden = value + if raise_flag is False: + assert instance.epld_golden == value + else: + assert instance.epld_golden is False + + +MATCH_03070 = "ImageUpgrade.epld_upgrade: " +MATCH_03070 += "instance.epld_upgrade must be a boolean." + + +@pytest.mark.parametrize( + "value, expected, raise_flag", + [ + (True, does_not_raise(), False), + (False, does_not_raise(), False), + ("FOO", pytest.raises(TypeError, match=MATCH_03070), True), + ], +) +def test_image_upgrade_03070(image_upgrade, value, expected, raise_flag) -> None: + """ + ### Classes and Methods + + - ``ImageUpgrade`` + - ``epld_upgrade`` + + ### Test + + - ``epld_upgrade`` does not raise ``TypeError`` if passed a boolean. + - ``epld_upgrade`` raises ``TypeError`` if passed a non-boolean. + - The default value is set if ``TypeError`` is raised. + """ + instance = image_upgrade + + with expected: + instance.epld_upgrade = value + if raise_flag is False: + assert instance.epld_upgrade == value + else: + assert instance.epld_upgrade is False + + +MATCH_03080 = "ImageUpgrade.epld_module: " +MATCH_03080 += "instance.epld_module must be an integer or 'ALL'" + + +@pytest.mark.parametrize( + "value, expected, raise_flag", + [ + ("ALL", does_not_raise(), False), + (1, does_not_raise(), False), + (27, does_not_raise(), False), + ("27", does_not_raise(), False), + ("FOO", pytest.raises(TypeError, match=MATCH_03080), True), + ], +) +def test_image_upgrade_03080(image_upgrade, value, expected, raise_flag) -> None: + """ + ### Classes and Methods + + - ``ImageUpgrade`` + - ``epld_module`` + + ### Test + + - ``epld_module`` does not raise ``TypeError`` if passed a valid value. + - ``epld_module`` raises ``TypeError`` if passed an invalid value. + - ``epld_module`` converts valid string values to integer. + - The default value ("ALL") is set if ``TypeError`` is raised. + """ + with does_not_raise(): + instance = image_upgrade + with expected: + instance.epld_module = value + if raise_flag is False: + if value == "ALL": + assert instance.epld_module == value + else: + assert instance.epld_module == int(value) + else: + assert instance.epld_module == "ALL" + + +MATCH_00140 = r"ImageUpgrade\.force_non_disruptive: " +MATCH_00140 += r"instance\.force_non_disruptive must be a boolean\." + + +@pytest.mark.parametrize( + "value, expected, raise_flag", + [ + (True, does_not_raise(), False), + (False, does_not_raise(), False), + ("FOO", pytest.raises(TypeError, match=MATCH_00140), True), + ], +) +def test_image_upgrade_03090(image_upgrade, value, expected, raise_flag) -> None: + """ + ### Classes and Methods + + - ``ImageUpgrade`` + - ``force_non_disruptive`` + + ### Test + + - ``force_non_disruptive`` does not raise ``TypeError`` if passed + a boolean. + - ``force_non_disruptive`` raises ``TypeError`` if passed a + non-boolean. + - The default value is set if ``TypeError`` is raised. + """ + instance = image_upgrade + + with expected: + instance.force_non_disruptive = value + if raise_flag is False: + assert instance.force_non_disruptive == value + else: + assert instance.force_non_disruptive is False + + +MATCH_03100 = r"ImageUpgrade\.non_disruptive:\s+" +MATCH_03100 += r"instance\.non_disruptive must be a boolean\." + + +@pytest.mark.parametrize( + "value, expected, raise_flag", + [ + (True, does_not_raise(), False), + (False, does_not_raise(), False), + ("FOO", pytest.raises(TypeError, match=MATCH_03100), True), + ], +) +def test_image_upgrade_03100(image_upgrade, value, expected, raise_flag) -> None: + """ + ### Classes and Methods + + - ``ImageUpgrade`` + - ``non_disruptive`` + + ### Test + + - ``non_disruptive`` does not raise ``TypeError`` if passed a boolean. + - ``non_disruptive`` raises ``TypeError`` if passed a non-boolean. + - The default value is set if ``TypeError`` is raised. + """ + with does_not_raise(): + instance = image_upgrade + with expected: + instance.non_disruptive = value + if raise_flag is False: + assert instance.non_disruptive == value + else: + assert instance.non_disruptive is False + + +MATCH_03110 = r"ImageUpgrade\.package_install:\s+" +MATCH_03110 += r"instance\.package_install must be a boolean\." + + +@pytest.mark.parametrize( + "value, expected, raise_flag", + [ + (True, does_not_raise(), False), + (False, does_not_raise(), False), + ("FOO", pytest.raises(TypeError, match=MATCH_03110), True), + ], +) +def test_image_upgrade_03110(image_upgrade, value, expected, raise_flag) -> None: + """ + ### Classes and Methods + + - ``ImageUpgrade`` + - ``package_install`` + + ### Test + + - ``package_install`` does not raise ``TypeError`` if passed a boolean. + - ``package_install`` raises ``TypeError`` if passed a non-boolean. + - The default value is set if ``TypeError`` is raised. + """ + with does_not_raise(): + instance = image_upgrade + with expected: + instance.package_install = value + if raise_flag is False: + assert instance.package_install == value + else: + assert instance.package_install is False + + +MATCH_03120 = r"ImageUpgrade\.package_uninstall:\s+" +MATCH_03120 += r"instance.package_uninstall must be a boolean\." + + +@pytest.mark.parametrize( + "value, expected, raise_flag", + [ + (True, does_not_raise(), False), + (False, does_not_raise(), False), + ("FOO", pytest.raises(TypeError, match=MATCH_03120), True), + ], +) +def test_image_upgrade_03120(image_upgrade, value, expected, raise_flag) -> None: + """ + ### Classes and Methods + + - ``ImageUpgrade`` + - ``package_uninstall`` + + ### Test + + - ``package_uninstall`` does not raise ``TypeError`` if passed a boolean. + - ``package_uninstall`` raises ``TypeError`` if passed a non-boolean. + - The default value is set if ``TypeError`` is raised. + """ + with does_not_raise(): + instance = image_upgrade + with expected: + instance.package_uninstall = value + if raise_flag is False: + assert instance.package_uninstall == value + else: + assert instance.package_uninstall is False + + +MATCH_03130 = r"ImageUpgrade\.reboot:\s+" +MATCH_03130 += r"instance\.reboot must be a boolean\." + + +@pytest.mark.parametrize( + "value, expected, raise_flag", + [ + (True, does_not_raise(), False), + (False, does_not_raise(), False), + ("FOO", pytest.raises(TypeError, match=MATCH_03130), True), + ], +) +def test_image_upgrade_03130(image_upgrade, value, expected, raise_flag) -> None: + """ + ### Classes and Methods + + - ``ImageUpgrade`` + - ``reboot`` + + ### Test + + - ``reboot`` does not raise ``TypeError`` if passed a boolean. + - ``reboot`` raises ``TypeError`` if passed a non-boolean. + - The default value is set if ``TypeError`` is raised. + """ + with does_not_raise(): + instance = image_upgrade + with expected: + instance.reboot = value + if raise_flag is False: + assert instance.reboot == value + else: + assert instance.reboot is False + + +MATCH_03140 = "ImageUpgrade.write_erase: " +MATCH_03140 += "instance.write_erase must be a boolean." + + +@pytest.mark.parametrize( + "value, expected, raise_flag", + [ + (True, does_not_raise(), False), + (False, does_not_raise(), False), + ("FOO", pytest.raises(TypeError, match=MATCH_03140), True), + ], +) +def test_image_upgrade_03140(image_upgrade, value, expected, raise_flag) -> None: + """ + ### Classes and Methods + + - ``ImageUpgrade`` + - ``write_erase`` + + ### Test + + - ``write_erase`` does not raise ``TypeError`` if passed a boolean. + - ``write_erase`` raises ``TypeError`` if passed a non-boolean. + - The default value is set if ``TypeError`` is raised. + """ + with does_not_raise(): + instance = image_upgrade + with expected: + instance.write_erase = value + if raise_flag is False: + assert instance.write_erase == value + else: + assert instance.write_erase is False + + +def test_image_upgrade_04000(image_upgrade) -> None: + """ + ### Classes and Methods + + - ``ImageUpgrade`` + - ``wait_for_controller`` + + ### Test + + - Two switches are added to ``wait_for_controller_done.done``. + + ### Setup + + - responses_ep_issu_detail.json indicates that both switches are + upgraded to the desired version. + + ### Description + ``wait_for_controller_done`` waits until staging, validation, + and upgrade actions are complete for all ip addresses. It accesses + ``SwitchIssuDetailsByIpAddress.actions_in_progress`` and expects + this to return False. ``actions_in_progress`` returns True until none of + the following keys has a value of "In-Progress": + + ```json + ["imageStaged", "upgrade", "validated"] + ``` + + ### Expected result + + 1. ``instance.wait_for_controller_done.done`` is length 2. + 2. ``instance.wait_for_controller_done.done`` contains all ip + addresses in ``ip_addresses``. + 3. Exceptions are not raised. + """ + method_name = inspect.stack()[0][3] + key = f"{method_name}a" + + def responses(): + # ImageUpgrade.wait_for_controller. + yield responses_ep_issu(key) + + gen_responses = ResponseGenerator(responses()) + + sender = Sender() + sender.ansible_module = MockAnsibleModule() + sender.gen = gen_responses + rest_send = RestSend(params) + rest_send.timeout = 1 + rest_send.unit_test = True + rest_send.response_handler = ResponseHandler() + rest_send.sender = sender + + with does_not_raise(): + instance = image_upgrade + instance.results = Results() + instance.rest_send = rest_send + instance.issu_detail.rest_send = rest_send + instance.issu_detail.results = Results() + instance.ip_addresses = [ + "172.22.150.102", + "172.22.150.108", + ] + instance.wait_for_controller() + assert len(instance.wait_for_controller_done.done) == 2 + assert "172.22.150.102" in instance.wait_for_controller_done.done + assert "172.22.150.108" in instance.wait_for_controller_done.done + + +def test_image_upgrade_04100(image_upgrade) -> None: + """ + ### Classes and Methods + + - ``ImageUpgrade`` + - ``wait_for_controller`` + + ### Test + + - Two switches are added to ``wait_for_controller_done.done``. + + ### Setup + + - responses_ep_issu_detail.json (all keys) indicate that "validated" + is "Success" and "upgrade" is "Success" for all switches. + - responses_ep_issu_detail.json (key_a) indicates that "imageStaged" + is "In-Progress" for all switches + - responses_ep_issu_detail.json (key_a) indicates that "imageStaged" + is "Success" for one switch and "In-Progress" for one switch. + - responses_ep_issu_detail.json (key_c) indicates that "imageStaged" + is "Success" for all switches. + + ### Description + See test_image_upgrade_04000 for functional details. + + This test ensures that the following continue statement in + ``WaitForControllerDone().commit()`` is hit. + + ```python + for item in self.todo: + if item in self.done: + continue + ``` + + ### Expected result + + 1. ``instance.wait_for_controller_done.done`` is length 2. + 2. ``instance.wait_for_controller_done.done`` contains all ip + addresses in ``ip_addresses``. + 3. Exceptions are not raised. + """ + method_name = inspect.stack()[0][3] + key_a = f"{method_name}a" + key_b = f"{method_name}b" + key_c = f"{method_name}c" + + def responses(): + # ImageUpgrade.wait_for_controller. + yield responses_ep_issu(key_a) + yield responses_ep_issu(key_b) + yield responses_ep_issu(key_c) + + gen_responses = ResponseGenerator(responses()) + + sender = Sender() + sender.ansible_module = MockAnsibleModule() + sender.gen = gen_responses + rest_send = RestSend(params) + # rest_send.timeout = 1 + rest_send.unit_test = True + rest_send.response_handler = ResponseHandler() + rest_send.sender = sender + + with does_not_raise(): + instance = image_upgrade + instance.results = Results() + instance.rest_send = rest_send + instance.issu_detail.rest_send = rest_send + instance.issu_detail.results = Results() + instance.ip_addresses = [ + "172.22.150.102", + "172.22.150.108", + ] + instance.wait_for_controller() + assert len(instance.wait_for_controller_done.done) == 2 + assert "172.22.150.102" in instance.wait_for_controller_done.done + assert "172.22.150.108" in instance.wait_for_controller_done.done + + +def test_image_upgrade_04110(image_upgrade) -> None: + """ + ### Classes and Methods + + - ``ImageUpgrade`` + - ``wait_for_controller`` + + ### Test + - one switch is added to ipv4_done + - ValueError is raised due to timeout + + ### Description + See test_image_upgrade_04000 for functional details. + + ### Expected result + + 1. ``wait_for_controller_done.done`` is length 1. + 2. ``wait_for_controller_done.done`` contains 172.22.150.102 + 3. ``wait_for_controller_done.done`` does not contain 172.22.150.108 + 4. ``ValueError`` is raised due to timeout. + 5. ``ValueError`` error message matches expectation. + """ + method_name = inspect.stack()[0][3] + key = f"{method_name}a" + + def responses(): + # ImageUpgrade.wait_for_controller. + yield responses_ep_issu(key) + + gen_responses = ResponseGenerator(responses()) + + sender = Sender() + sender.ansible_module = MockAnsibleModule() + sender.gen = gen_responses + rest_send = RestSend(params) + rest_send.timeout = 1 + rest_send.unit_test = True + rest_send.response_handler = ResponseHandler() + rest_send.sender = sender + + with does_not_raise(): + instance = image_upgrade + instance.results = Results() + instance.rest_send = rest_send + instance.issu_detail.rest_send = rest_send + instance.issu_detail.results = Results() + instance.ip_addresses = [ + "172.22.150.102", + "172.22.150.108", + ] + + match = r"ImageUpgrade\.wait_for_controller:\s+" + match += r"Error WaitForControllerDone\.commit:\s+" + match += r"Timed out after 1 seconds waiting for controller actions\s+" + match += r"to complete on items:\s+" + match += r"\['172.22.150.102', '172.22.150.108'\]\.\s+" + match += r"The following items did complete: 172\.22\.150\.102\.\." + + with pytest.raises(ValueError, match=match): + instance.wait_for_controller() + + assert isinstance(instance.ipv4_done, set) + assert len(instance.wait_for_controller_done.done) == 1 + assert "172.22.150.102" in instance.wait_for_controller_done.done + assert "172.22.150.108" not in instance.wait_for_controller_done.done + + +def test_image_upgrade_04120(image_upgrade) -> None: + """ + ### Classes and Methods + + - ``ImageUpgrade`` + - ``_wait_for_image_upgrade_to_complete`` + + ### Test + + - One ip address is added to ``ipv4_done`` due to + ``issu_detail.upgrade`` == "Success". + - ``ValueError`` is raised due one ip address with + ``issu_detail.upgrade`` == "Failed". + + ### Description + + - ``_wait_for_image_upgrade_to_complete`` looks at the upgrade status for + each ip address and waits for it to be "Success" or "Failed". + - If all ip addresses are "Success", the module returns. + - If any ip address is "Failed", the module raises ``ValueError``. + + ### Expected result + + - ``ipv4_done`` is a set(). + - ``ipv4_done`` has length 1. + - ``ipv4_done`` contains 172.22.150.102, upgrade is "Success". + - ``ValueError`` is raised because ip address 172.22.150.108, + upgrade status is "Failed". + """ + method_name = inspect.stack()[0][3] + key = f"{method_name}a" + + def responses(): + # ImageUpgrade._wait_for_image_upgrade_to_complete. + yield responses_ep_issu(key) + + gen_responses = ResponseGenerator(responses()) + + sender = Sender() + sender.ansible_module = MockAnsibleModule() + sender.gen = gen_responses + rest_send = RestSend(params) + rest_send.timeout = 1 + rest_send.unit_test = True + rest_send.response_handler = ResponseHandler() + rest_send.sender = sender + + with does_not_raise(): + instance = image_upgrade + instance.results = Results() + instance.rest_send = rest_send + instance.issu_detail.rest_send = rest_send + instance.issu_detail.results = Results() + instance.ip_addresses = [ + "172.22.150.102", + "172.22.150.108", + ] + + match = r"ImageUpgrade\._wait_for_image_upgrade_to_complete:\s+" + match += r"Seconds remaining 1790:\s+" + match += r"upgrade image Failed for cvd-2313-leaf, FDO2112189M,\s+" + match += r"172\.22\.150\.108, upgrade_percent 50\.\s+" + match += r"Check the controller to determine the cause\.\s+" + match += r"Operations > Image Management > Devices > View Details\." + + with pytest.raises(ValueError, match=match): + instance._wait_for_image_upgrade_to_complete() + + assert isinstance(instance.ipv4_done, set) + assert len(instance.ipv4_done) == 1 + assert "172.22.150.102" in instance.ipv4_done + assert "172.22.150.108" not in instance.ipv4_done + + +def test_image_upgrade_04130(image_upgrade) -> None: + """ + ### Classes and Methods + + - ``ImageUpgrade`` + - ``_wait_for_image_upgrade_to_complete`` + + ### Test + + - One ip address is added to ``ipv4_done`` because + issu_detail.upgrade == "Success". + - ``ValueError`` is raised due to timeout since one + ip address returns ``issu_detail.upgrade`` == "In-Progress". + + ### Description + _wait_for_image_upgrade_to_complete looks at the upgrade status for each + ip address and waits for it to be "Success" or "Failed". + In the case where all ip addresses are "Success", the module returns. + In the case where any ip address is "Failed", the module calls fail_json. + In the case where any ip address is "In-Progress", the module waits until + timeout is exceeded. + + ### Expected result + + - instance.ipv4_done is a set(). + - instance.ipv4_done has length 1. + - instance.ipv4_done contains 172.22.150.102, upgrade is "Success". + - ''ValueError'' is raised due to timeout exceeded. + """ + method_name = inspect.stack()[0][3] + key = f"{method_name}a" + + def responses(): + # ImageUpgrade._wait_for_image_upgrade_to_complete. + yield responses_ep_issu(key) + # SwitchIssuDetailsByIpAddress.refresh_super + yield responses_ep_issu(key) + + gen_responses = ResponseGenerator(responses()) + + sender = Sender() + sender.ansible_module = MockAnsibleModule() + sender.gen = gen_responses + rest_send = RestSend(params) + rest_send.unit_test = True + rest_send.response_handler = ResponseHandler() + rest_send.sender = sender + + with does_not_raise(): + instance = image_upgrade + instance.check_timeout = 1 + instance.results = Results() + instance.rest_send = rest_send + instance.issu_detail.rest_send = rest_send + instance.issu_detail.results = Results() + instance.ip_addresses = [ + "172.22.150.102", + "172.22.150.108", + ] + + match = r"ImageUpgrade\._wait_for_image_upgrade_to_complete:\s+" + match += r"The following device\(s\) did not complete upgrade:\s+" + match += r"\['172\.22\.150\.108'\].\s+" + match += r"Check the controller to determine the cause\.\s+" + match += r"Operations > Image Management > Devices > View Details\.\s+" + match += r"And/or check the device\(s\)\s+" + match += r"\(e\.g\. show install all status\)\." + + with pytest.raises(ValueError, match=match): + instance._wait_for_image_upgrade_to_complete() + + assert isinstance(instance.ipv4_done, set) + assert len(instance.ipv4_done) == 1 + assert "172.22.150.102" in instance.ipv4_done + assert "172.22.150.108" not in instance.ipv4_done + + +def test_image_upgrade_04140(image_upgrade) -> None: + """ + ### Classes and Methods + + - ``ImageUpgrade`` + - ``_wait_for_image_upgrade_to_complete`` + + ### Test + For code coverage purposes, ensure that, when two ip addresses are + processed, `_wait_for_image_upgrade_to_complete` continue statement + is reached. Specifically: + + ```python + for ipv4 in self.ip_addresses: + if ipv4 in self.ipv4_done: + continue + ``` + + ### Setup + + - responses_ep_issu_detail.json (all keys) indicate that "imageStaged", + "validated" are "Success" for all switches. + - responses_ep_issu_detail.json (key_a) indicates that "upgrade" + is "In-Progress" for all switches + - responses_ep_issu_detail.json (key_a) indicates that "upgrade" + is "Success" for one switch and "In-Progress" for one switch. + - responses_ep_issu_detail.json (key_c) indicates that "upgrade" + is "Success" for all switches. + + Description + _wait_for_image_upgrade_to_complete looks at the upgrade status for each + ip address and waits for it to be "Success" or "Failed". + In the case where all ip addresses are "Success", the module returns. + In the case where any ip address is "In-Progress", the module waits until + timeout is exceeded. For this test, we incrementally change the status + of the ip addresses from "In-Progress" to "Success", until all ip addresses + are "Success". This ensures that the conti``nue statement in the for loop + is reached. + + Expectations: + - instance.ipv4_done will have length 2 + - instance.ipv4_done contains 172.22.150.102 and 172.22.150.108 + - Exceptions are not raised. + """ + method_name = inspect.stack()[0][3] + key_a = f"{method_name}a" + key_b = f"{method_name}b" + key_c = f"{method_name}c" + + def responses(): + # ImageUpgrade._wait_for_image_upgrade_to_complete. + yield responses_ep_issu(key_a) + # ImageUpgrade._wait_for_image_upgrade_to_complete. + yield responses_ep_issu(key_b) + # ImageUpgrade._wait_for_image_upgrade_to_complete. + yield responses_ep_issu(key_c) + + gen_responses = ResponseGenerator(responses()) + + sender = Sender() + sender.ansible_module = MockAnsibleModule() + sender.gen = gen_responses + rest_send = RestSend(params) + rest_send.unit_test = True + rest_send.response_handler = ResponseHandler() + rest_send.sender = sender + + with does_not_raise(): + instance = image_upgrade + instance.results = Results() + instance.rest_send = rest_send + instance.issu_detail.rest_send = rest_send + instance.issu_detail.results = Results() + instance.ip_addresses = [ + "172.22.150.102", + "172.22.150.108", + ] + instance._wait_for_image_upgrade_to_complete() + + assert isinstance(instance.ipv4_done, set) + assert len(instance.ipv4_done) == 2 + assert "172.22.150.102" in instance.ipv4_done + assert "172.22.150.108" in instance.ipv4_done diff --git a/tests/unit/modules/dcnm/dcnm_image_upgrade/test_image_upgrade_api_endpoints.py b/tests/unit/modules/dcnm/dcnm_image_upgrade/test_image_upgrade_api_endpoints.py deleted file mode 100644 index 099ba8e53..000000000 --- a/tests/unit/modules/dcnm/dcnm_image_upgrade/test_image_upgrade_api_endpoints.py +++ /dev/null @@ -1,232 +0,0 @@ -# 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_upgrade.api_endpoints import \ - ApiEndpoints - - -def test_image_upgrade_api_00001() -> None: - """ - Endpoints.__init__ - """ - endpoints = ApiEndpoints() - assert endpoints.endpoint_api_v1 == "/appcenter/cisco/ndfc/api/v1" - assert endpoints.endpoint_feature_manager == "/appcenter/cisco/ndfc/api/v1/fm" - assert ( - endpoints.endpoint_image_management - == "/appcenter/cisco/ndfc/api/v1/imagemanagement" - ) - assert ( - endpoints.endpoint_image_upgrade - == "/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/imageupgrade" - ) - assert endpoints.endpoint_lan_fabric == "/appcenter/cisco/ndfc/api/v1/lan-fabric" - assert ( - endpoints.endpoint_package_mgnt - == "/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/packagemgnt" - ) - assert ( - endpoints.endpoint_policy_mgnt - == "/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/policymgnt" - ) - assert ( - endpoints.endpoint_staging_management - == "/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/stagingmanagement" - ) - - -def test_image_upgrade_api_00002() -> None: - """ - Endpoints.bootflash_info - """ - endpoints = ApiEndpoints() - assert endpoints.bootflash_info.get("verb") == "GET" - assert ( - endpoints.bootflash_info.get("path") - == "/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/imagemgnt/bootFlash/bootflash-info" - ) - - -def test_image_upgrade_api_00003() -> None: - """ - Endpoints.install_options - """ - endpoints = ApiEndpoints() - assert endpoints.install_options.get("verb") == "POST" - assert ( - endpoints.install_options.get("path") - == "/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/imageupgrade/install-options" - ) - - -def test_image_upgrade_api_00004() -> None: - """ - Endpoints.image_stage - """ - endpoints = ApiEndpoints() - assert endpoints.image_stage.get("verb") == "POST" - assert ( - endpoints.image_stage.get("path") - == "/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/stagingmanagement/stage-image" - ) - - -def test_image_upgrade_api_00005() -> None: - """ - Endpoints.image_upgrade - """ - endpoints = ApiEndpoints() - assert endpoints.image_upgrade.get("verb") == "POST" - assert ( - endpoints.image_upgrade.get("path") - == "/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/imageupgrade/upgrade-image" - ) - - -def test_image_upgrade_api_00006() -> None: - """ - Endpoints.image_validate - """ - endpoints = ApiEndpoints() - assert endpoints.image_validate.get("verb") == "POST" - assert ( - endpoints.image_validate.get("path") - == "/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/stagingmanagement/validate-image" - ) - - -def test_image_upgrade_api_00007() -> None: - """ - Endpoints.issu_info - """ - endpoints = ApiEndpoints() - assert endpoints.issu_info.get("verb") == "GET" - assert ( - endpoints.issu_info.get("path") - == "/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/packagemgnt/issu" - ) - - -def test_image_upgrade_api_00008() -> None: - """ - Endpoints.controller_version - """ - endpoints = ApiEndpoints() - assert endpoints.controller_version.get("verb") == "GET" - assert ( - endpoints.controller_version.get("path") - == "/appcenter/cisco/ndfc/api/v1/fm/about/version" - ) - - -def test_image_upgrade_api_00009() -> 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_upgrade_api_00010() -> 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_upgrade_api_00011() -> 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_upgrade_api_00012() -> 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_upgrade_api_00013() -> 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_upgrade_api_00014() -> 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 - - -def test_image_upgrade_api_00015() -> None: - """ - Endpoints.stage_info - """ - endpoints = ApiEndpoints() - assert endpoints.stage_info.get("verb") == "GET" - assert ( - endpoints.stage_info.get("path") - == "/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/stagingmanagement/stage-info" - ) - - -def test_image_upgrade_api_00016() -> None: - """ - Endpoints.switches_info - """ - endpoints = ApiEndpoints() - assert endpoints.switches_info.get("verb") == "GET" - assert ( - endpoints.switches_info.get("path") - == "/appcenter/cisco/ndfc/api/v1/lan-fabric/rest/inventory/allswitches" - ) diff --git a/tests/unit/modules/dcnm/dcnm_image_upgrade/test_image_upgrade_image_install_options.py b/tests/unit/modules/dcnm/dcnm_image_upgrade/test_image_upgrade_image_install_options.py deleted file mode 100644 index d2fa7afc3..000000000 --- a/tests/unit/modules/dcnm/dcnm_image_upgrade/test_image_upgrade_image_install_options.py +++ /dev/null @@ -1,615 +0,0 @@ -# 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 - -__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.image_upgrade.api_endpoints import \ - ApiEndpoints - -from .utils import (MockAnsibleModule, does_not_raise, - image_install_options_fixture, - responses_image_install_options) - -PATCH_MODULE_UTILS = "ansible_collections.cisco.dcnm.plugins.module_utils." -PATCH_IMAGE_UPGRADE = PATCH_MODULE_UTILS + "image_upgrade." -DCNM_SEND_INSTALL_OPTIONS = PATCH_IMAGE_UPGRADE + "install_options.dcnm_send" - - -def test_image_upgrade_install_options_00001(image_install_options) -> None: - """ - Function - - __init__ - - Test - - fail_json is not called - - Class attributes are initialized to expected values - """ - with does_not_raise(): - instance = image_install_options - assert instance.ansible_module == MockAnsibleModule - assert instance.class_name == "ImageInstallOptions" - assert isinstance(instance.endpoints, ApiEndpoints) - path = "/appcenter/cisco/ndfc/api/v1/imagemanagement" - path += "/rest/imageupgrade/install-options" - assert instance.path == path - assert instance.verb == "POST" - assert instance.compatibility_status == {} - - -def test_image_upgrade_install_options_00002(image_install_options) -> None: - """ - Function - - _init_properties - - Test - - Class properties are initialized to expected values - """ - with does_not_raise(): - instance = image_install_options - assert isinstance(instance.properties, dict) - assert instance.properties.get("epld") is False - assert instance.properties.get("epld_modules") is None - assert instance.properties.get("issu") is True - assert instance.properties.get("package_install") is False - assert instance.properties.get("policy_name") is None - assert instance.properties.get("response") == [] - assert instance.properties.get("response_current") == {} - assert instance.properties.get("response_data") is None - assert instance.properties.get("result") == [] - assert instance.properties.get("result_current") == {} - assert instance.properties.get("serial_number") is None - assert instance.properties.get("timeout") == 300 - assert instance.properties.get("unit_test") is False - - -def test_image_upgrade_install_options_00004(image_install_options) -> None: - """ - Function - - refresh - - Test - - fail_json is called because serial_number is not set when refresh is called - - fail_json error message is matched - """ - match = "ImageInstallOptions._validate_refresh_parameters: " - match += "instance.serial_number must be set before " - match += r"calling refresh\(\)" - - instance = image_install_options - instance.policy_name = "FOO" - with pytest.raises(AnsibleFailJson, match=match): - image_install_options.refresh() - - -def test_image_upgrade_install_options_00005( - monkeypatch, image_install_options -) -> None: - """ - Function - - refresh - - Test - - 200 response from endpoint - - Properties are updated with expected values - - endpoint: install-options - """ - - def mock_dcnm_send_install_options(*args, **kwargs) -> Dict[str, Any]: - key = "test_image_upgrade_install_options_00005a" - return responses_image_install_options(key) - - monkeypatch.setattr(DCNM_SEND_INSTALL_OPTIONS, mock_dcnm_send_install_options) - - instance = image_install_options - instance.unit_test = True - instance.policy_name = "KRM5" - instance.serial_number = "BAR" - instance.refresh() - assert isinstance(instance.response, list) - assert isinstance(instance.response_current, dict) - assert instance.device_name == "cvd-1314-leaf" - assert instance.err_message is None - assert instance.epld_modules is None - assert instance.install_option == "disruptive" - assert instance.install_packages is None - assert instance.ip_address == "172.22.150.105" - assert instance.os_type == "64bit" - assert instance.platform == "N9K/N3K" - assert instance.pre_issu_link == "Not Applicable" - assert isinstance(instance.raw_data, dict) - assert isinstance(instance.raw_response, dict) - assert "compatibilityStatusList" in instance.raw_data - assert instance.rep_status == "skipped" - assert instance.serial_number == "BAR" - assert instance.status == "Success" - assert instance.timestamp == "NA" - assert instance.version == "10.2.5" - comp_disp = "show install all impact nxos bootflash:nxos64-cs.10.2.5.M.bin" - assert instance.comp_disp == comp_disp - assert instance.result_current.get("success") is True - - -def test_image_upgrade_install_options_00006( - monkeypatch, image_install_options -) -> None: - """ - Function - - refresh - - Test - - fail_json is called because RETURN_CODE != 200 in the response - """ - - def mock_dcnm_send_install_options(*args, **kwargs) -> Dict[str, Any]: - key = "test_image_upgrade_install_options_00006a" - return responses_image_install_options(key) - - monkeypatch.setattr(DCNM_SEND_INSTALL_OPTIONS, mock_dcnm_send_install_options) - - match = "ImageInstallOptions.refresh: " - match += "Bad result when retrieving install-options from " - match += "the controller. Controller response:" - - instance = image_install_options - instance.unit_test = True - instance.policy_name = "KRM5" - instance.serial_number = "BAR" - with pytest.raises(AnsibleFailJson, match=rf"{match}"): - instance.refresh() - - -def test_image_upgrade_install_options_00007( - monkeypatch, image_install_options -) -> None: - """ - Function - - refresh - - Setup - - Device has no policy attached - - POST REQUEST - - issu is True - - epld is False - - package_install is False - - Test - - 200 response from endpoint - - Response contains expected values - - Endpoint - - install-options - """ - - def mock_dcnm_send_install_options(*args, **kwargs) -> Dict[str, Any]: - key = "test_image_upgrade_install_options_00007a" - return responses_image_install_options(key) - - monkeypatch.setattr(DCNM_SEND_INSTALL_OPTIONS, mock_dcnm_send_install_options) - - instance = image_install_options - instance.policy_name = "KRM5" - instance.serial_number = "FDO21120U5D" - instance.unit_test = True - instance.refresh() - assert isinstance(instance.response_current, dict) - assert isinstance(instance.response, list) - assert isinstance(instance.result_current, dict) - assert isinstance(instance.result, list) - assert instance.device_name == "leaf1" - assert instance.err_message is None - assert instance.epld_modules is None - assert instance.install_option == "NA" - assert instance.install_packages is None - assert instance.ip_address == "172.22.150.102" - assert instance.os_type == "64bit" - assert instance.platform == "N9K/N3K" - assert instance.pre_issu_link == "Not Applicable" - assert isinstance(instance.raw_data, dict) - assert isinstance(instance.raw_response, dict) - assert "compatibilityStatusList" in image_install_options.raw_data - assert instance.rep_status == "skipped" - assert instance.serial_number == "FDO21120U5D" - assert instance.status == "Skipped" - assert instance.timestamp == "NA" - assert instance.version == "10.2.5" - assert instance.version_check == "Compatibility status skipped." - assert instance.comp_disp == "Compatibility status skipped." - assert instance.result_current.get("success") is True - - -def test_image_upgrade_install_options_00008( - monkeypatch, image_install_options -) -> None: - """ - Function - - refresh - - Setup - - Device has no policy attached - - POST REQUEST - - issu is True - - epld is True - - package_install is False - - Test - - 200 response from endpoint - - Response contains expected values - - Endpoint - - install-options - """ - - def mock_dcnm_send_install_options(*args, **kwargs) -> Dict[str, Any]: - key = "test_image_upgrade_install_options_00008a" - return responses_image_install_options(key) - - monkeypatch.setattr(DCNM_SEND_INSTALL_OPTIONS, mock_dcnm_send_install_options) - - instance = image_install_options - instance.policy_name = "KRM5" - instance.serial_number = "FDO21120U5D" - instance.epld = True - instance.issu = True - instance.package_install = False - instance.unit_test = True - instance.refresh() - assert isinstance(instance.response_current, dict) - assert isinstance(instance.response, list) - assert isinstance(instance.result_current, dict) - assert isinstance(instance.result, list) - assert instance.device_name == "leaf1" - assert instance.err_message is None - assert isinstance(instance.epld_modules, dict) - assert len(instance.epld_modules.get("moduleList")) == 2 - assert instance.install_option == "NA" - assert instance.install_packages is None - assert instance.ip_address == "172.22.150.102" - assert instance.os_type == "64bit" - assert instance.platform == "N9K/N3K" - assert instance.pre_issu_link == "Not Applicable" - assert isinstance(instance.raw_data, dict) - assert isinstance(instance.raw_response, dict) - assert "compatibilityStatusList" in instance.raw_data - assert instance.rep_status == "skipped" - assert instance.serial_number == "FDO21120U5D" - assert instance.status == "Skipped" - assert instance.timestamp == "NA" - assert instance.version == "10.2.5" - assert instance.version_check == "Compatibility status skipped." - assert instance.comp_disp == "Compatibility status skipped." - assert instance.result_current.get("success") is True - - -def test_image_upgrade_install_options_00009( - monkeypatch, image_install_options -) -> None: - """ - Function - - refresh - - Setup - - Device has no policy attached - - POST REQUEST - - issu is False - - epld is True - - package_install is False - - Test - - 200 response from endpoint - - Response contains expected values - - Endpoint - - install-options - """ - - def mock_dcnm_send_install_options(*args, **kwargs) -> Dict[str, Any]: - key = "test_image_upgrade_install_options_00009a" - return responses_image_install_options(key) - - monkeypatch.setattr(DCNM_SEND_INSTALL_OPTIONS, mock_dcnm_send_install_options) - - instance = image_install_options - instance.policy_name = "KRM5" - instance.serial_number = "FDO21120U5D" - instance.epld = True - instance.issu = False - instance.package_install = False - instance.unit_test = True - instance.refresh() - assert isinstance(instance.response_current, dict) - assert isinstance(instance.response, list) - assert isinstance(instance.result_current, dict) - assert isinstance(instance.result, list) - assert instance.device_name is None - assert instance.err_message is None - assert isinstance(instance.epld_modules, dict) - assert len(instance.epld_modules.get("moduleList")) == 2 - assert instance.install_option is None - assert instance.install_packages is None - assert instance.ip_address is None - assert instance.os_type is None - assert instance.platform is None - assert instance.pre_issu_link is None - assert isinstance(instance.raw_data, dict) - assert isinstance(instance.raw_response, dict) - assert "compatibilityStatusList" in instance.raw_data - assert instance.rep_status is None - assert instance.serial_number == "FDO21120U5D" - assert instance.status is None - assert instance.timestamp is None - assert instance.version is None - assert instance.version_check is None - assert instance.comp_disp is None - assert instance.result_current.get("success") is True - - -def test_image_upgrade_install_options_00010( - monkeypatch, image_install_options -) -> None: - """ - Function - - refresh - - Setup - - Device has no policy attached - - POST REQUEST - - issu is False - - epld is True - - package_install is True (causes expected error) - - Test - - 500 response from endpoint due to - - KR5M policy has no packages defined and - - package_install set to True - - Response contains expected values - - Endpoint - - install-options - """ - - def mock_dcnm_send_install_options(*args, **kwargs) -> Dict[str, Any]: - key = "test_image_upgrade_install_options_00010a" - return responses_image_install_options(key) - - monkeypatch.setattr(DCNM_SEND_INSTALL_OPTIONS, mock_dcnm_send_install_options) - - match = "Selected policy KR5M does not have package to continue." - - instance = image_install_options - instance.policy_name = "KRM5" - instance.serial_number = "FDO21120U5D" - instance.epld = True - instance.issu = True - instance.package_install = True - instance.unit_test = True - with pytest.raises(AnsibleFailJson, match=match): - instance.refresh() - - -def test_image_upgrade_install_options_00011(image_install_options) -> None: - """ - Function - - refresh - - Setup - - POST REQUEST - - issu is False - - epld is False - - package_install is False - - Test - - ImageInstallOptions returns a mocked response when all of - issu, epld, and package_install are False - - Mocked response contains expected values - - Endpoint - - install-options - - Description: - monkeypatch is not needed here since the class never sends a request - to the controller in this case. - """ - with does_not_raise(): - instance = image_install_options - instance.policy_name = "KRM5" - instance.serial_number = "FDO21120U5D" - instance.epld = False - instance.issu = False - instance.package_install = False - instance.unit_test = True - instance.refresh() - - assert isinstance(instance.response_data, dict) - assert instance.response_data.get("compatibilityStatusList") == [] - assert instance.response_data.get("epldModules") is None - # yes, installPackages is intentionally misspelled below since - # this is what the controller returns in a real response - assert instance.response_data.get("installPacakges") is None - assert instance.response_data.get("errMessage") == "" - - -def test_image_upgrade_install_options_00020(image_install_options) -> None: - """ - Function - - build_payload - - Setup - - Defaults are not specified by the user - - Test - - Default values for issu, epld, and package_install are applied - """ - instance = image_install_options - instance.policy_name = "KRM5" - instance.serial_number = "BAR" - instance.unit_test = True - instance._build_payload() # pylint: disable=protected-access - assert instance.payload.get("devices")[0].get("policyName") == "KRM5" - assert instance.payload.get("devices")[0].get("serialNumber") == "BAR" - assert instance.payload.get("issu") is True - assert instance.payload.get("epld") is False - assert instance.payload.get("packageInstall") is False - - -def test_image_upgrade_install_options_00021(image_install_options) -> None: - """ - Function - - build_payload - - Setup - - Values are specified by the user - - Test - - Payload contains user-specified values if the user sets them - - Defaults for issu, epld, and package_install are overridden by user values. - """ - instance = image_install_options - instance.policy_name = "KRM5" - instance.serial_number = "BAR" - instance.issu = False - instance.epld = True - instance.package_install = True - instance.unit_test = True - instance._build_payload() # pylint: disable=protected-access - assert instance.payload.get("devices")[0].get("policyName") == "KRM5" - assert instance.payload.get("devices")[0].get("serialNumber") == "BAR" - assert instance.payload.get("issu") is False - assert instance.payload.get("epld") is True - assert instance.payload.get("packageInstall") is True - - -def test_image_upgrade_install_options_00022(image_install_options) -> None: - """ - Function - - issu setter - - Test - - fail_json is called if issu is not a boolean. - """ - match = "ImageInstallOptions.issu: issu must be a " - match += "boolean value" - - instance = image_install_options - instance.unit_test = True - with pytest.raises(AnsibleFailJson, match=match): - instance.issu = "FOO" - - -def test_image_upgrade_install_options_00023(image_install_options) -> None: - """ - Function - - epld setter - - Test - - fail_json is called if epld is not a boolean. - """ - match = "ImageInstallOptions.epld: epld must be a " - match += "boolean value" - - instance = image_install_options - instance.unit_test = True - with pytest.raises(AnsibleFailJson, match=match): - instance.epld = "FOO" - - -def test_image_upgrade_install_options_00024(image_install_options) -> None: - """ - Function - - package_install setter - - Test - - fail_json is called if package_install is not a boolean. - """ - match = "ImageInstallOptions.package_install: " - match += "package_install must be a boolean value" - - instance = image_install_options - instance.unit_test = True - with pytest.raises(AnsibleFailJson, match=match): - instance.package_install = "FOO" - - -def test_image_upgrade_install_options_00070(image_install_options) -> None: - """ - Function - - refresh - - policy_name - - Summary - - refresh() calls fail_json if serial_number if policy_name is not set. - - Test - - fail_json is called because policy_name is not set when refresh is called - - fail_json error message is matched - """ - instance = image_install_options - instance.serial_number = "FOO" - match = "ImageInstallOptions._validate_refresh_parameters: " - match += "instance.policy_name must be set before " - match += r"calling refresh\(\)" - with pytest.raises(AnsibleFailJson, match=match): - instance.refresh() - - -MATCH_00080 = r"ImageInstallOptions\.policy_name: " -MATCH_00080 += r"instance\.policy_name must be a string. Got" - - -@pytest.mark.parametrize( - "value, expected, raise_flag", - [ - ("NR3F", does_not_raise(), False), - (1, pytest.raises(AnsibleFailJson, match=MATCH_00080), True), - (False, pytest.raises(AnsibleFailJson, match=MATCH_00080), True), - ({"foo": "bar"}, pytest.raises(AnsibleFailJson, match=MATCH_00080), True), - ([1, 2], pytest.raises(AnsibleFailJson, match=MATCH_00080), True), - ], -) -def test_image_upgrade_install_options_00080( - image_install_options, value, expected, raise_flag -) -> None: - """ - Function - - ImageInstallOptions.policy_name - - Summary - Verify proper behavior of policy_name property - - Test - - fail_json is called when property_name is not a string - - fail_json is not called when property_name is a string - """ - with does_not_raise(): - instance = image_install_options - with expected: - instance.policy_name = value - if raise_flag is False: - assert instance.policy_name == value diff --git a/tests/unit/modules/dcnm/dcnm_image_upgrade/test_image_upgrade_image_policies.py b/tests/unit/modules/dcnm/dcnm_image_upgrade/test_image_upgrade_image_policies.py deleted file mode 100644 index 037b0dba0..000000000 --- a/tests/unit/modules/dcnm/dcnm_image_upgrade/test_image_upgrade_image_policies.py +++ /dev/null @@ -1,519 +0,0 @@ -# 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 - -__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.image_upgrade.api_endpoints import \ - ApiEndpoints - -from .utils import (MockAnsibleModule, does_not_raise, image_policies_fixture, - responses_image_policies) - -PATCH_MODULE_UTILS = "ansible_collections.cisco.dcnm.plugins.module_utils." -PATCH_IMAGE_UPGRADE = PATCH_MODULE_UTILS + "image_upgrade." -DCNM_SEND_IMAGE_POLICIES = PATCH_IMAGE_UPGRADE + "image_policies.dcnm_send" - - -def test_image_upgrade_image_policies_00001(image_policies) -> None: - """ - Function - - ImagePolicies.__init__ - - Test - - Class attributes are initialized to expected values - """ - with does_not_raise(): - instance = image_policies - assert instance.ansible_module == MockAnsibleModule - assert instance.class_name == "ImagePolicies" - assert isinstance(instance.endpoints, ApiEndpoints) - - -def test_image_upgrade_image_policies_00002(image_policies) -> None: - """ - Function - - ImagePolicies._init_properties - - Test - - Class properties are initialized to expected values - """ - with does_not_raise(): - instance = image_policies - assert isinstance(image_policies.properties, dict) - assert instance.properties.get("policy_name") is None - assert instance.properties.get("response_data") == {} - assert instance.properties.get("response") is None - assert instance.properties.get("result") is None - - -def test_image_upgrade_image_policies_00010(monkeypatch, image_policies) -> None: - """ - Function - - ImagePolicies.refresh - - ImagePolicies.policy_name - - - Summary - Verify that refresh returns image policy info and that the filtered - properties associated with policy_name are the expected values. - - Test - - properties for policy_name are set to reflect the response from - the controller - - 200 RETURN_CODE - - fail_json is not called - - Endpoint - - /appcenter/cisco/ndfc/api/v1/imagemanagement/rest/policymgnt/policies - """ - key = "test_image_upgrade_image_policies_00010a" - - def mock_dcnm_send_image_policies(*args) -> Dict[str, Any]: - return responses_image_policies(key) - - monkeypatch.setattr(DCNM_SEND_IMAGE_POLICIES, mock_dcnm_send_image_policies) - - instance = image_policies - with does_not_raise(): - instance.refresh() - instance.policy_name = "KR5M" - assert isinstance(instance.response, dict) - assert instance.agnostic is False - assert instance.description == "10.2.(5) with EPLD" - assert instance.epld_image_name == "n9000-epld.10.2.5.M.img" - assert instance.image_name == "nxos64-cs.10.2.5.M.bin" - assert instance.nxos_version == "10.2.5_nxos64-cs_64bit" - assert instance.package_name is None - assert instance.platform == "N9K/N3K" - assert instance.platform_policies is None - assert instance.policy_name == "KR5M" - assert instance.policy_type == "PLATFORM" - assert instance.ref_count == 10 - assert instance.rpm_images is None - - -def test_image_upgrade_image_policies_00020(monkeypatch, image_policies) -> None: - """ - Function - - ImagePolicies.refresh - - ImagePolicies.result - - Test - - Imagepolicies.result contains expected key/values on 200 response from endpoint. - - Endpoint - - /appcenter/cisco/ndfc/api/v1/imagemanagement/rest/policymgnt/policies - """ - key = "test_image_upgrade_image_policies_00020a" - - def mock_dcnm_send_image_policies(*args) -> Dict[str, Any]: - print(f"mock_dcnm_send_image_policies: {responses_image_policies(key)}") - return responses_image_policies(key) - - monkeypatch.setattr(DCNM_SEND_IMAGE_POLICIES, mock_dcnm_send_image_policies) - - instance = image_policies - with does_not_raise(): - instance.refresh() - assert isinstance(instance.result, dict) - assert instance.result.get("found") is True - assert instance.result.get("success") is True - - -def test_image_upgrade_image_policies_00021(monkeypatch, image_policies) -> None: - """ - Function - - ImagePolicies.refresh - - Summary - Verify that fail_json is called when the response from the controller - contains a 404 RETURN_CODE. - - Test - - fail_json is called on 404 RETURN_CODE in response. - - Endpoint - - /bad/path - """ - key = "test_image_upgrade_image_policies_00021a" - - def mock_dcnm_send_image_policies(*args) -> Dict[str, Any]: - print(f"mock_dcnm_send_image_policies: {responses_image_policies(key)}") - return responses_image_policies(key) - - monkeypatch.setattr(DCNM_SEND_IMAGE_POLICIES, mock_dcnm_send_image_policies) - - match = "ImagePolicies.refresh: Bad response when retrieving " - match += "image policy information from the controller." - - instance = image_policies - with pytest.raises(AnsibleFailJson, match=match): - instance.refresh() - - -def test_image_upgrade_image_policies_00022(monkeypatch, image_policies) -> None: - """ - Function - - ImagePolicies.refresh - - Summary - Verify that fail_json is called when the response from the controller - contains an empty DATA key. - - Test - - fail_json is called on 200 RETURN_CODE with empty DATA key. - - Endpoint - - /appcenter/cisco/ndfc/api/v1/imagemanagement/rest/policymgnt/policies - """ - key = "test_image_upgrade_image_policies_00022a" - - def mock_dcnm_send_image_policies(*args) -> Dict[str, Any]: - print(f"mock_dcnm_send_image_policies: {responses_image_policies(key)}") - return responses_image_policies(key) - - monkeypatch.setattr(DCNM_SEND_IMAGE_POLICIES, mock_dcnm_send_image_policies) - - match = "ImagePolicies.refresh: Bad response when retrieving " - match += "image policy information from the controller." - - instance = image_policies - with pytest.raises(AnsibleFailJson, match=match): - instance.refresh() - - -def test_image_upgrade_image_policies_00023(monkeypatch, image_policies) -> None: - """ - Function - - ImagePolicies.refresh - - Summary - Verify that fail_json is not called when a 200 response from the controller - contains DATA.lastOperDataObject with length == 0. - - Test - - do not fail_json when DATA.lastOperDataObject length == 0 - - 200 response - - Endpoint - - /appcenter/cisco/ndfc/api/v1/imagemanagement/rest/policymgnt/policies - - Discussion - dcnm_image_policy classes ImagePolicyCreate and ImagePolicyCreateBulk - both call ImagePolicies.refresh() when checking if the image policies - they are creating already exist on the controller. Hence, we cannot - fail_json when the length of DATA.lastOperDataObject is zero. - """ - key = "test_image_upgrade_image_policies_00023a" - - def mock_dcnm_send_image_policies(*args) -> Dict[str, Any]: - print(f"mock_dcnm_send_image_policies: {responses_image_policies(key)}") - return responses_image_policies(key) - - monkeypatch.setattr(DCNM_SEND_IMAGE_POLICIES, mock_dcnm_send_image_policies) - - instance = image_policies - with does_not_raise(): - instance.refresh() - - -def test_image_upgrade_image_policies_00024(monkeypatch, image_policies) -> None: - """ - Function - - ImagePolicies.refresh - - ImagePolicies.policy_name - - Summary - Verify when policy_name is set to a policy that does not exist on the - controller, instance.policy returns None. - - Setup - - instance.policy_name is set to a policy that does not exist on the controller. - - Test - - instance.policy returns None - - Endpoint - - /appcenter/cisco/ndfc/api/v1/imagemanagement/rest/policymgnt/policies - """ - key = "test_image_upgrade_image_policies_00024a" - - def mock_dcnm_send_image_policies(*args) -> Dict[str, Any]: - print(f"mock_dcnm_send_image_policies: {responses_image_policies(key)}") - return responses_image_policies(key) - - monkeypatch.setattr(DCNM_SEND_IMAGE_POLICIES, mock_dcnm_send_image_policies) - - with does_not_raise(): - instance = image_policies - instance.refresh() - image_policies.policy_name = "FOO" - - assert image_policies.policy is None - - -def test_image_upgrade_image_policies_00025(monkeypatch, image_policies) -> None: - """ - Function - - ImagePolicies.refresh - - Summary - Verify that fail_json is called when the response from the controller - is missing the policyName key. - - Test - - fail_json is called on response with missing policyName key. - - Endpoint - - /appcenter/cisco/ndfc/api/v1/imagemanagement/rest/policymgnt/policies - - NOTES - - This is to cover a check in ImagePolicies.refresh() - - This scenario should happen only with a bug, or API change, on the controller. - """ - key = "test_image_upgrade_image_policies_00025a" - - def mock_dcnm_send_image_policies(*args) -> Dict[str, Any]: - print(f"mock_dcnm_send_image_policies: {responses_image_policies(key)}") - return responses_image_policies(key) - - monkeypatch.setattr(DCNM_SEND_IMAGE_POLICIES, mock_dcnm_send_image_policies) - - match = "ImagePolicies.refresh: " - match += "Cannot parse policy information from the controller." - - instance = image_policies - with pytest.raises(AnsibleFailJson, match=match): - instance.refresh() - - -def test_image_upgrade_image_policies_00026(monkeypatch, image_policies) -> None: - """ - Function - - ImagePolicies.refresh - - ImageUpgradeCommon._handle_response - - Summary - Verify that fail_json is called when ImageUpgradeCommon._handle_response() - returns a non-successful result. - - Test - - fail_json is called when result["success"] is False. - - """ - key = "test_image_upgrade_image_policies_00026a" - - def mock_dcnm_send_image_policies(*args) -> Dict[str, Any]: - print(f"mock_dcnm_send_image_policies: {responses_image_policies(key)}") - return responses_image_policies(key) - - monkeypatch.setattr(DCNM_SEND_IMAGE_POLICIES, mock_dcnm_send_image_policies) - - match = "ImagePolicies.refresh: Bad result when retrieving image policy " - match += r"information from the controller\." - - instance = image_policies - with pytest.raises(AnsibleFailJson, match=match): - instance.refresh() - - -def test_image_upgrade_image_policies_00040(image_policies) -> None: - """ - Function - - ImagePolicies._get - - Summary - Verify that fail_json is called when _get() is called prior to setting policy_name. - - Test - - fail_json is called when _get() is called prior to setting policy_name. - - Appropriate error message is provided. - """ - match = "ImagePolicies._get: instance.policy_name must be " - match += "set before accessing property imageName." - - instance = image_policies - with pytest.raises(AnsibleFailJson, match=match): - instance._get("imageName") # pylint: disable=protected-access - - -def test_image_upgrade_image_policies_00041(monkeypatch, image_policies) -> None: - """ - Function - - ImagePolicies._get - - Summary - Verify that fail_json is called when ImagePolicies._get is called - with an argument that does not match an item in the response data - for the policy_name returned by the controller. - - Setup - - instance.commit() is called and retrieves a response from the - controller containing informationi for policy KR5M. - - policy_name is set to KR5M. - - Test - - fail_json is called when _get() is called with a bad parameter FOO - - An appropriate error message is provided. - """ - key = "test_image_upgrade_image_policies_00041a" - - def mock_dcnm_send_image_policies(*args) -> Dict[str, Any]: - print(f"mock_dcnm_send_image_policies: {responses_image_policies(key)}") - return responses_image_policies(key) - - monkeypatch.setattr(DCNM_SEND_IMAGE_POLICIES, mock_dcnm_send_image_policies) - - match = r"ImagePolicies\._get: KR5M does not have a key named FOO\." - - with does_not_raise(): - instance = image_policies - instance.refresh() - instance.policy_name = "KR5M" - - with pytest.raises(AnsibleFailJson, match=match): - instance._get("FOO") # pylint: disable=protected-access - - -def test_image_upgrade_image_policies_00042(monkeypatch, image_policies) -> None: - """ - Function - - ImagePolicies._get - - Summary - Verify that the correct image policy information is returned when - ImagePolicies._get is called with the "policy" arguement. - - Setup - - instance.commit() is called and retrieves a response from the - controller containing informationi for policy KR5M. - - policy_name is set to KR5M. - - _get("policy") is called. - - Test - - fail_json is not called - - The expected policy information is returned. - """ - key = "test_image_upgrade_image_policies_00042a" - - def mock_dcnm_send_image_policies(*args) -> Dict[str, Any]: - print(f"mock_dcnm_send_image_policies: {responses_image_policies(key)}") - return responses_image_policies(key) - - monkeypatch.setattr(DCNM_SEND_IMAGE_POLICIES, mock_dcnm_send_image_policies) - - with does_not_raise(): - instance = image_policies - instance.refresh() - instance.policy_name = "KR5M" - value = instance._get("policy") # pylint: disable=protected-access - assert value["agnostic"] == "false" - assert value["epldImgName"] == "n9000-epld.10.2.5.M.img" - assert value["imageName"] == "nxos64-cs.10.2.5.M.bin" - assert value["nxosVersion"] == "10.2.5_nxos64-cs_64bit" - assert value["packageName"] == "" - assert value["platform"] == "N9K/N3K" - assert value["platformPolicies"] == "" - assert value["policyDescr"] == "10.2.(5) with EPLD" - assert value["policyName"] == "KR5M" - assert value["policyType"] == "PLATFORM" - assert value["ref_count"] == 10 - assert value["rpmimages"] == "" - - -def test_image_upgrade_image_policies_00050(image_policies) -> None: - """ - Function - - ImagePolicies.all_policies - - Summary - Verify that all_policies returns an empty dict when no policies exist - on the controller. - - Test - - fail_json is not called. - - all_policies returns an empty dict. - """ - with does_not_raise(): - instance = image_policies - value = instance.all_policies - assert value == {} - - -def test_image_upgrade_image_policies_00051(monkeypatch, image_policies) -> None: - """ - Function - - ImagePolicies.all_policies - - Summary - Verify that, when policies exist on the controller, all_policies returns a dict - containing these policies. - - Test - - fail_json is not called. - - all_policies returns a dict containing the controller's policies. - """ - key = "test_image_upgrade_image_policies_00051a" - - def mock_dcnm_send_image_policies(*args) -> Dict[str, Any]: - print(f"mock_dcnm_send_image_policies: {responses_image_policies(key)}") - return responses_image_policies(key) - - monkeypatch.setattr(DCNM_SEND_IMAGE_POLICIES, mock_dcnm_send_image_policies) - - instance = image_policies - with does_not_raise(): - instance.refresh() - value = instance.all_policies - assert value["KR5M"]["agnostic"] == "false" - assert value["KR5M"]["epldImgName"] == "n9000-epld.10.2.5.M.img" - assert value["KR5M"]["imageName"] == "nxos64-cs.10.2.5.M.bin" - assert value["KR5M"]["nxosVersion"] == "10.2.5_nxos64-cs_64bit" - assert value["KR5M"]["packageName"] == "" - assert value["KR5M"]["platform"] == "N9K/N3K" - assert value["KR5M"]["platformPolicies"] == "" - assert value["KR5M"]["policyDescr"] == "10.2.(5) with EPLD" - assert value["KR5M"]["policyName"] == "KR5M" - assert value["KR5M"]["policyType"] == "PLATFORM" - assert value["KR5M"]["ref_count"] == 10 - assert value["KR5M"]["rpmimages"] == "" - assert value["OR1F"]["agnostic"] == "false" - assert value["OR1F"]["epldImgName"] == "n9000-epld.10.4.1.F.img" - assert value["OR1F"]["imageName"] == "nxos64-cs.10.4.1.F.bin" - assert value["OR1F"]["nxosVersion"] == "10.4.1_nxos64-cs_64bit" - assert value["OR1F"]["packageName"] == "" - assert value["OR1F"]["platform"] == "N9K/N3K" - assert value["OR1F"]["platformPolicies"] == "" - assert value["OR1F"]["policyDescr"] == "OR1F EPLD" - assert value["OR1F"]["policyName"] == "OR1F" - assert value["OR1F"]["policyType"] == "PLATFORM" - assert value["OR1F"]["ref_count"] == 0 - assert value["OR1F"]["rpmimages"] == "" diff --git a/tests/unit/modules/dcnm/dcnm_image_upgrade/test_image_upgrade_image_policy_action.py b/tests/unit/modules/dcnm/dcnm_image_upgrade/test_image_upgrade_image_policy_action.py deleted file mode 100644 index 2e1b94de6..000000000 --- a/tests/unit/modules/dcnm/dcnm_image_upgrade/test_image_upgrade_image_policy_action.py +++ /dev/null @@ -1,871 +0,0 @@ -# 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 - -__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.image_upgrade.api_endpoints import \ - ApiEndpoints -from ansible_collections.cisco.dcnm.plugins.module_utils.image_upgrade.image_policy_action import \ - ImagePolicyAction -from ansible_collections.cisco.dcnm.plugins.module_utils.image_upgrade.switch_issu_details import \ - SwitchIssuDetailsBySerialNumber - -from .fixture import load_fixture -from .utils import (does_not_raise, image_policies_fixture, - image_policy_action_fixture, - issu_details_by_serial_number_fixture, - responses_image_policies, responses_image_policy_action, - responses_switch_details, responses_switch_issu_details) - -PATCH_MODULE_UTILS = "ansible_collections.cisco.dcnm.plugins.module_utils." -PATCH_IMAGE_UPGRADE = PATCH_MODULE_UTILS + "image_upgrade." - -DCNM_SEND_IMAGE_POLICIES = PATCH_IMAGE_UPGRADE + "image_policies.dcnm_send" -DCNM_SEND_IMAGE_UPGRADE_COMMON = PATCH_IMAGE_UPGRADE + "image_upgrade_common.dcnm_send" -DCNM_SEND_SWITCH_DETAILS = PATCH_IMAGE_UPGRADE + "switch_details.RestSend.commit" -DCNM_SEND_SWITCH_ISSU_DETAILS = PATCH_IMAGE_UPGRADE + "switch_issu_details.dcnm_send" - - -def test_image_upgrade_image_policy_action_00001(image_policy_action) -> None: - """ - Function - - ImagePolicyAction.__init__ - - Test - - Class attributes initialized to expected values - - fail_json is not called - """ - with does_not_raise(): - instance = image_policy_action - assert instance.class_name == "ImagePolicyAction" - assert isinstance(instance.endpoints, ApiEndpoints) - assert isinstance(instance, ImagePolicyAction) - assert isinstance(instance.switch_issu_details, SwitchIssuDetailsBySerialNumber) - assert instance.path is None - assert instance.payloads == [] - assert instance.valid_actions == {"attach", "detach", "query"} - assert instance.verb is None - - -def test_image_upgrade_image_policy_action_00002(image_policy_action) -> None: - """ - Function - - ImagePolicyAction._init_properties - - Test - - Class properties are initialized to expected values - """ - instance = image_policy_action - assert isinstance(instance.properties, dict) - assert instance.properties.get("action") is None - assert instance.properties.get("response") == [] - assert instance.properties.get("response_current") == {} - assert instance.properties.get("result") == [] - assert instance.properties.get("result_current") == {} - assert instance.properties.get("policy_name") is None - assert instance.properties.get("query_result") is None - assert instance.properties.get("serial_numbers") is None - - -def test_image_upgrade_image_policy_action_00003( - monkeypatch, image_policy_action, issu_details_by_serial_number -) -> None: - """ - Function - - ImagePolicyAction.build_payload - - Test - - fail_json is not called - - image_policy_action.payloads is a list - - image_policy_action.payloads has length 5 - - Description - build_payload builds the payload to send in the POST request - to attach policies to devices - """ - - def mock_dcnm_send_switch_issu_details(*args) -> Dict[str, Any]: - key = "test_image_upgrade_image_policy_action_00003a" - return responses_switch_issu_details(key) - - monkeypatch.setattr( - DCNM_SEND_SWITCH_ISSU_DETAILS, mock_dcnm_send_switch_issu_details - ) - - instance = image_policy_action - instance.switch_issu_details = issu_details_by_serial_number - instance.policy_name = "KR5M" - instance.serial_numbers = [ - "FDO2112189M", - "FDO211218AX", - "FDO211218B5", - "FDO211218FV", - "FDO211218GC", - ] - with does_not_raise(): - instance.build_payload() - assert isinstance(instance.payloads, list) - assert len(instance.payloads) == 5 - - -def test_image_upgrade_image_policy_action_00004( - monkeypatch, image_policy_action, issu_details_by_serial_number -) -> None: - """ - Function - - ImagePolicyAction.build_payload - - Test - - fail_json is called since deviceName is null in the issu_details_by_serial_number response - - The error message is matched - - Description - build_payload builds the payload to send in the POST request - to attach policies to devices. If any key in the payload has a value - of None, the function calls fail_json. - """ - - def mock_dcnm_send_switch_issu_details(*args) -> Dict[str, Any]: - key = "test_image_upgrade_image_policy_action_00004a" - return responses_switch_issu_details(key) - - monkeypatch.setattr( - DCNM_SEND_SWITCH_ISSU_DETAILS, mock_dcnm_send_switch_issu_details - ) - - instance = image_policy_action - instance.switch_issu_details = issu_details_by_serial_number - instance.policy_name = "KR5M" - instance.serial_numbers = [ - "FDO2112189M", - ] - match = "Unable to determine hostName for switch " - match += "172.22.150.108, FDO2112189M, None. " - match += "Please verify that the switch is managed by " - match += "the controller." - with pytest.raises(AnsibleFailJson, match=match): - instance.build_payload() - - -def test_image_upgrade_image_policy_action_00010( - image_policy_action, issu_details_by_serial_number -) -> None: - """ - Function - - ImagePolicyAction.validate_request - - Test - - fail_json is called because image_policy_action.action is None - - The error message is matched - - Description - validate_request performs a number of validations prior to calling commit. - If any of these validations fail, the function calls fail_json with a - validation-specific error message. - """ - instance = image_policy_action - instance.switch_issu_details = issu_details_by_serial_number - instance.policy_name = "KR5M" - instance.serial_numbers = [ - "FDO2112189M", - ] - match = "ImagePolicyAction.validate_request: " - match += "instance.action must be set before calling commit()" - with pytest.raises(AnsibleFailJson, match=match): - instance.validate_request() - - -MATCH_00011 = "ImagePolicyAction.validate_request: " -MATCH_00011 += "instance.policy_name must be set before calling commit()" - - -@pytest.mark.parametrize( - "action,expected", - [ - ("attach", pytest.raises(AnsibleFailJson, match=MATCH_00011)), - ("detach", pytest.raises(AnsibleFailJson, match=MATCH_00011)), - ("query", pytest.raises(AnsibleFailJson, match=MATCH_00011)), - ], -) -def test_image_upgrade_image_policy_action_00011( - action, expected, image_policy_action, issu_details_by_serial_number -) -> None: - """ - Function - - ImagePolicyAction.validate_request - - Test - - fail_json is called because image_policy_action.policy_name is None - - The error message is matched - - Description - validate_request performs a number of validations prior to calling commit. - If any of these validations fail, the function calls fail_json with a - validation-specific error message. - """ - instance = image_policy_action - instance.switch_issu_details = issu_details_by_serial_number - instance.action = action - instance.serial_numbers = [ - "FDO2112189M", - ] - - with expected: - instance.validate_request() - - -MATCH_00012 = "ImagePolicyAction.validate_request: " -MATCH_00012 += "instance.serial_numbers must be set before calling commit()" - - -@pytest.mark.parametrize( - "action,expected", - [ - ("attach", pytest.raises(AnsibleFailJson, match=MATCH_00012)), - ("detach", pytest.raises(AnsibleFailJson, match=MATCH_00012)), - ("query", does_not_raise()), - ], -) -def test_image_upgrade_image_policy_action_00012( - action, expected, image_policy_action, issu_details_by_serial_number -) -> None: - """ - Function - - ImagePolicyAction.validate_request - - Test - - fail_json is called for action == attach because - image_policy_action.serial_numbers is None - - fail_json is called for action == detach because - image_policy_action.serial_numbers is None - - fail_json is NOT called for action == query because - validate_request is exited early for action == "query" - - The error message, if any, is matched - - Description - validate_request performs a number of validations prior to calling commit, - If any of these validations fail, the function calls fail_json with a - validation-specific error message. - """ - instance = image_policy_action - instance.switch_issu_details = issu_details_by_serial_number - instance.action = action - instance.policy_name = "KR5M" - - with expected: - instance.validate_request() - - -def test_image_upgrade_image_policy_action_00013( - monkeypatch, image_policy_action, issu_details_by_serial_number, image_policies -) -> None: - """ - Function - - ImagePolicyAction.validate_request - - Test - - fail_json is called because policy KR5M supports playform N9K/N3K - and the response from ImagePolicies contains platform - TEST_UNKNOWN_PLATFORM - - The error message is matched - - Description - validate_request performs a number of validations prior to calling commit. - If any of these validations fail, the function calls fail_json with a - validation-specific error message. - """ - key = "test_image_upgrade_image_policy_action_00013a" - - def mock_dcnm_send_switch_issu_details(*args) -> Dict[str, Any]: - return responses_switch_issu_details(key) - - def mock_dcnm_send_image_policies(*args) -> Dict[str, Any]: - return responses_image_policies(key) - - monkeypatch.setattr( - DCNM_SEND_SWITCH_ISSU_DETAILS, mock_dcnm_send_switch_issu_details - ) - monkeypatch.setattr(DCNM_SEND_IMAGE_POLICIES, mock_dcnm_send_image_policies) - - instance = image_policy_action - instance.switch_issu_details = issu_details_by_serial_number - instance.image_policies = image_policies - instance.action = "attach" - instance.policy_name = "KR5M" - instance.serial_numbers = ["FDO2112189M"] - - match = "ImagePolicyAction.validate_request: " - match += "policy KR5M does not support platform TEST_UNKNOWN_PLATFORM. " - match += r"KR5M supports the following platform\(s\): N9K/N3K" - - with pytest.raises(AnsibleFailJson, match=match): - instance.validate_request() - - -def test_image_upgrade_image_policy_action_00014( - monkeypatch, image_policy_action, issu_details_by_serial_number, image_policies -) -> None: - """ - Function - - ImagePolicyAction.validate_request - - Summary - fail_json is called because policy KR5M does not exist on the controller - - Test - - fail_json is called because ImagePolicies returns no policies - - The error message is matched - - Description - validate_request performs a number of validations prior to calling commit. - If any of these validations fail, the function calls fail_json with a - validation-specific error message. - """ - key = "test_image_upgrade_image_policy_action_00014a" - - def mock_dcnm_send_switch_issu_details(*args) -> Dict[str, Any]: - return responses_switch_issu_details(key) - - def mock_dcnm_send_image_policies(*args) -> Dict[str, Any]: - return responses_image_policies(key) - - monkeypatch.setattr( - DCNM_SEND_SWITCH_ISSU_DETAILS, mock_dcnm_send_switch_issu_details - ) - monkeypatch.setattr(DCNM_SEND_IMAGE_POLICIES, mock_dcnm_send_image_policies) - - instance = image_policy_action - instance.switch_issu_details = issu_details_by_serial_number - instance.image_policies = image_policies - instance.action = "attach" - instance.policy_name = "KR5M" - instance.serial_numbers = ["FDO2112189M"] - - match = r"ImagePolicyAction.validate_request: " - match += r"policy KR5M does not exist on the controller\." - - with pytest.raises(AnsibleFailJson, match=match): - instance.validate_request() - - -def test_image_upgrade_image_policy_action_00020( - monkeypatch, image_policy_action -) -> None: - """ - Function - - ImagePolicyAction.commit - - Test - - fail_json is called because action is unknown - - The error message is matched - - Description - commit calls validate_request() and then calls one of the following - functions based on the value of action: - action == "attach" : _attach_policy - action == "detach" : _detach_policy - action == "query" : _query_policy - - If action is not one of [attach, detach, query], commit() calls fail_json. - - This test mocks valid_actions to include "FOO" so that action.setter - will accept it (effectively bypassing the check in the setter). - It also mocks validate_request() to remove it from consideration. - - Since action == "FOO" is not covered in commit()'s if clauses, - the else clause is taken and fail_json is called. - """ - - def mock_validate_request(*args) -> None: - pass - - instance = image_policy_action - monkeypatch.setattr(instance, "validate_request", mock_validate_request) - monkeypatch.setattr(instance, "valid_actions", {"attach", "detach", "query", "FOO"}) - - instance.policy_name = "KR5M" - instance.serial_numbers = ["FDO2112189M"] - instance.action = "FOO" - - match = "ImagePolicyAction.commit: Unknown action FOO." - - with pytest.raises(AnsibleFailJson, match=match): - instance.commit() - - -def test_image_upgrade_image_policy_action_00030( - monkeypatch, image_policy_action -) -> None: - """ - Function - - ImagePolicyAction.commit - - ImagePolicyAction._detach_policy - - Summary - Verify that commit behaves as expected when action is "detach" - and ImagePolicyAction receives a success (200) response - from the controller. - - Test - - ImagePolicyAction._detach_policy is called - - commit is successful given a 200 response from the controller in - ImagePolicyAction._detach_policy - - ImagePolicyAction.response contains RESULT_CODE 200 - - Description - commit calls validate_request() and then calls one of the following - functions based on the value of action: - action == "attach" : _attach_policy - action == "detach" : _detach_policy - action == "query" : _query_policy - """ - key = "test_image_upgrade_image_policy_action_00030a" - - def mock_dcnm_send_image_policies(*args) -> Dict[str, Any]: - return responses_image_policies(key) - - def mock_dcnm_send_switch_issu_details(*args) -> Dict[str, Any]: - return responses_switch_issu_details(key) - - def mock_dcnm_send_image_upgrade_common(*args) -> Dict[str, Any]: - return responses_image_policy_action(key) - - monkeypatch.setattr(DCNM_SEND_IMAGE_POLICIES, mock_dcnm_send_image_policies) - monkeypatch.setattr( - DCNM_SEND_SWITCH_ISSU_DETAILS, mock_dcnm_send_switch_issu_details - ) - - instance = image_policy_action - monkeypatch.setattr(instance, "dcnm_send", mock_dcnm_send_image_upgrade_common) - instance.policy_name = "KR5M" - instance.serial_numbers = ["FDO2112189M"] - instance.action = "detach" - - instance.commit() - assert isinstance(instance.response_current, dict) - assert instance.response_current.get("RETURN_CODE") == 200 - assert instance.response_current.get("METHOD") == "DELETE" - assert instance.response_current.get("MESSAGE") == "OK" - assert ( - instance.response_current.get("DATA") - == "Successfully detach the policy from device." - ) - assert instance.result_current.get("success") is True - assert instance.result_current.get("changed") is True - - -def test_image_upgrade_image_policy_action_00031( - monkeypatch, image_policy_action -) -> None: - """ - Function - - ImagePolicyAction.commit - - ImagePolicyAction._detach_policy - - Summary - Verify that commit behaves as expected when action is "detach" - and ImagePolicyAction receives a failure (500) response - from the controller. - - Test - - ImagePolicyAction._detach_policy is called - - commit is unsuccessful given a 500 response from the controller in - ImagePolicyAction._detach_policy - - fail_json is called and the error message is matched - """ - key = "test_image_upgrade_image_policy_action_00031a" - - def mock_dcnm_send_image_policies(*args) -> Dict[str, Any]: - return responses_image_policies(key) - - def mock_dcnm_send_switch_issu_details(*args) -> Dict[str, Any]: - return responses_switch_issu_details(key) - - def mock_dcnm_send_image_upgrade_common(*args) -> Dict[str, Any]: - return responses_image_policy_action(key) - - monkeypatch.setattr(DCNM_SEND_IMAGE_POLICIES, mock_dcnm_send_image_policies) - monkeypatch.setattr( - DCNM_SEND_SWITCH_ISSU_DETAILS, mock_dcnm_send_switch_issu_details - ) - - with does_not_raise(): - instance = image_policy_action - instance.policy_name = "KR5M" - instance.serial_numbers = ["FDO2112189M"] - instance.action = "detach" - instance.unit_test = True - monkeypatch.setattr(instance, "dcnm_send", mock_dcnm_send_image_upgrade_common) - - match = r"ImagePolicyAction\._detach_policy_normal_mode: " - match += r"Bad result when detaching policy KR5M " - match += r"from the following device\(s\):" - - with pytest.raises(AnsibleFailJson, match=match): - instance.commit() - - -def test_image_upgrade_image_policy_action_00040( - monkeypatch, image_policy_action -) -> None: - """ - Function - - ImagePolicyAction.commit - - ImagePolicyAction._attach_policy - - Summary - Verify that commit behaves as expected when action is "attach" - and ImagePolicyAction receives a success (200) response - from the controller. - - Test - - ImagePolicyAction._attach_policy is called - - commit is successful given a 200 response from the controller in - ImagePolicyAction._attach_policy - - ImagePolicyAction.response contains RESULT_CODE 200 - - Description - commit calls validate_request() and then calls one of the following - functions based on the value of action: - action == "attach" : _attach_policy - action == "detach" : _detach_policy - action == "query" : _query_policy - """ - key = "test_image_upgrade_image_policy_action_00040a" - - def mock_dcnm_send_image_policies(*args) -> Dict[str, Any]: - return responses_image_policies(key) - - def mock_dcnm_send_switch_issu_details(*args) -> Dict[str, Any]: - return responses_switch_issu_details(key) - - def mock_dcnm_send_image_upgrade_common(*args, **kwargs) -> Dict[str, Any]: - return responses_image_policy_action(key) - - monkeypatch.setattr(DCNM_SEND_IMAGE_POLICIES, mock_dcnm_send_image_policies) - monkeypatch.setattr( - DCNM_SEND_SWITCH_ISSU_DETAILS, mock_dcnm_send_switch_issu_details - ) - - instance = image_policy_action - monkeypatch.setattr(instance, "dcnm_send", mock_dcnm_send_image_upgrade_common) - instance.policy_name = "KR5M" - instance.serial_numbers = ["FDO2112189M"] - instance.action = "attach" - - instance.commit() - assert isinstance(instance.response_current, dict) - assert instance.response_current.get("RETURN_CODE") == 200 - assert instance.response_current.get("METHOD") == "POST" - assert instance.response_current.get("MESSAGE") == "OK" - assert instance.response_current.get("DATA") == "[cvd-1313-leaf:Success]" - assert instance.result_current.get("success") is True - assert instance.result_current.get("changed") is True - - -def test_image_upgrade_image_policy_action_00041( - monkeypatch, image_policy_action -) -> None: - """ - Function - - ImagePolicyAction.commit - - ImagePolicyAction._attach_policy - - Summary - Verify that commit behaves as expected when action is "attach" - and ImagePolicyAction receives a failure (500) response - from the controller. - - Test - - ImagePolicyAction._attach_policy is called - - commit is unsuccessful given a 500 response from the controller in - ImagePolicyAction._attach_policy - - ImagePolicyAction.response contains RESULT_CODE 500 - """ - key = "test_image_upgrade_image_policy_action_00041a" - - def mock_dcnm_send_image_policies(*args) -> Dict[str, Any]: - return responses_image_policies(key) - - def mock_dcnm_send_switch_issu_details(*args) -> Dict[str, Any]: - return responses_switch_issu_details(key) - - def mock_dcnm_send_image_upgrade_common(*args, **kwargs) -> Dict[str, Any]: - return responses_image_policy_action(key) - - monkeypatch.setattr(DCNM_SEND_IMAGE_POLICIES, mock_dcnm_send_image_policies) - monkeypatch.setattr( - DCNM_SEND_SWITCH_ISSU_DETAILS, mock_dcnm_send_switch_issu_details - ) - with does_not_raise(): - instance = image_policy_action - monkeypatch.setattr(instance, "dcnm_send", mock_dcnm_send_image_upgrade_common) - instance.policy_name = "KR5M" - instance.serial_numbers = ["FDO2112189M"] - instance.action = "attach" - instance.unit_test = True - - match = r"ImagePolicyAction\._attach_policy_normal_mode: " - match += r"Bad result when attaching policy KR5M to switch\. Payload:" - with pytest.raises(AnsibleFailJson, match=match): - instance.commit() - - -def test_image_upgrade_image_policy_action_00050( - monkeypatch, image_policy_action -) -> None: - """ - Function - - ImagePolicyAction.commit - - ImagePolicyAction._query_policy - - Summary - Verify that commit behaves as expected when action is "query" - and ImagePolicyAction receives a success (200) response - from the controller. - - Test - - ImagePolicyAction._query_policy is called - - commit is successful given a 200 response from the controller in - ImagePolicyAction._query_policy - - ImagePolicyAction.response contains RESULT_CODE 200 - - Description - commit calls validate_request() and then calls one of the following - functions based on the value of action: - action == "attach" : _attach_policy - action == "detach" : _detach_policy - action == "query" : _query_policy - """ - key = "test_image_upgrade_image_policy_action_00050a" - - def mock_dcnm_send_image_policies(*args) -> Dict[str, Any]: - return responses_image_policies(key) - - def mock_dcnm_send_switch_issu_details(*args) -> Dict[str, Any]: - return responses_switch_issu_details(key) - - def mock_dcnm_send_image_upgrade_common(*args) -> Dict[str, Any]: - return responses_image_policy_action(key) - - monkeypatch.setattr(DCNM_SEND_IMAGE_POLICIES, mock_dcnm_send_image_policies) - monkeypatch.setattr( - DCNM_SEND_SWITCH_ISSU_DETAILS, mock_dcnm_send_switch_issu_details - ) - - instance = image_policy_action - monkeypatch.setattr(instance, "dcnm_send", mock_dcnm_send_image_upgrade_common) - instance.policy_name = "KR5M" - instance.serial_numbers = ["FDO2112189M"] - instance.action = "query" - - instance.commit() - assert isinstance(instance.response_current, dict) - assert instance.response_current.get("RETURN_CODE") == 200 - assert instance.response_current.get("METHOD") == "GET" - assert instance.response_current.get("MESSAGE") == "OK" - assert instance.result_current.get("success") is True - assert instance.result_current.get("found") is True - - -def test_image_upgrade_image_policy_action_00051( - monkeypatch, image_policy_action -) -> None: - """ - Function - - ImagePolicyAction.commit - - ImagePolicyAction._query_policy - - Summary - Verify that commit behaves as expected when action is "query" - and ImagePolicyAction receives a failure (500) response - from the controller. - - Test - - fail_json is called and the error message is matched - """ - key = "test_image_upgrade_image_policy_action_00051a" - - def mock_dcnm_send_image_policies(*args) -> Dict[str, Any]: - return responses_image_policies(key) - - def mock_dcnm_send_switch_issu_details(*args) -> Dict[str, Any]: - return responses_switch_issu_details(key) - - def mock_dcnm_send_image_upgrade_common(*args) -> Dict[str, Any]: - return responses_image_policy_action(key) - - monkeypatch.setattr(DCNM_SEND_IMAGE_POLICIES, mock_dcnm_send_image_policies) - monkeypatch.setattr( - DCNM_SEND_SWITCH_ISSU_DETAILS, mock_dcnm_send_switch_issu_details - ) - - instance = image_policy_action - monkeypatch.setattr(instance, "dcnm_send", mock_dcnm_send_image_upgrade_common) - instance.policy_name = "KR5M" - instance.serial_numbers = ["FDO2112189M"] - instance.action = "query" - instance.unit_test = True - - match = r"ImagePolicyAction\._query_policy: " - match += r"Bad result when querying image policy KR5M\." - with pytest.raises(AnsibleFailJson, match=match): - instance.commit() - - -MATCH_00060 = "ImagePolicyAction.action: instance.action must be " -MATCH_00060 += "one of attach,detach,query. Got FOO." - - -@pytest.mark.parametrize( - "value, expected, raise_flag", - [ - ("attach", does_not_raise(), False), - ("detach", does_not_raise(), False), - ("query", does_not_raise(), False), - ("FOO", pytest.raises(AnsibleFailJson, match=MATCH_00060), True), - ], -) -def test_image_upgrade_image_policy_action_00060( - image_policy_action, value, expected, raise_flag -) -> None: - """ - Function - - ImagePolicyAction.action setter - - Test - - Expected values are set - - fail_json is called when value is not a valid action - - fail_json error message is matched - """ - with does_not_raise(): - instance = image_policy_action - with expected: - instance.action = value - if not raise_flag: - assert instance.action == value - - -MATCH_00061 = "ImagePolicyAction.serial_numbers: instance.serial_numbers " -MATCH_00061 += "must be a python list of switch serial numbers. Got FOO." - - -@pytest.mark.parametrize( - "value, expected, raise_flag", - [ - (["FDO2112189M", "FDO21120U5D"], does_not_raise(), False), - ("FOO", pytest.raises(AnsibleFailJson, match=MATCH_00061), True), - ], -) -def test_image_upgrade_image_policy_action_00061( - image_policy_action, value, expected, raise_flag -) -> None: - """ - Function - - ImagePolicyAction.serial_numbers setter - - Test - - fail_json is not called with value is a list - - fail_json is called when value is not a list - - fail_json error message is matched - """ - with does_not_raise(): - instance = image_policy_action - with expected: - instance.serial_numbers = value - if not raise_flag: - assert instance.serial_numbers == value - - -def test_image_upgrade_image_policy_action_00062(image_policy_action) -> None: - """ - Function - - ImagePolicyAction.serial_numbers setter - - Test - - fail_json is called when value is an empty list - - fail_json error message is matched - """ - with does_not_raise(): - instance = image_policy_action - match = r"ImagePolicyAction\.serial_numbers: instance.serial_numbers " - match += r"must contain at least one switch serial number\." - with pytest.raises(AnsibleFailJson, match=match): - instance.serial_numbers = [] - - -MATCH_00070 = r"ImagePolicyAction\.query_result: instance.query_result must be a dict\." - - -@pytest.mark.parametrize( - "value, expected, raise_flag", - [ - ({"found": "true", "success": "true"}, does_not_raise(), False), - ("FOO", does_not_raise(), False), - ], -) -def test_image_upgrade_image_policy_action_00070( - image_policy_action, value, expected, raise_flag -) -> None: - """ - Function - - ImagePolicyAction.query_result setter - - Summary - Verify correct behavior of ImagePolicyAction.query_result - - Test - - fail_json is never called - """ - with does_not_raise(): - instance = image_policy_action - with expected: - instance.query_result = value - if not raise_flag: - assert instance.query_result == value - - -def test_image_upgrade_image_policy_action_00080(image_policy_action) -> None: - """ - Function - - ImagePolicyAction.diff_null getter - - Summary - Verify ImagePolicyAction.diff_null returns the expected value - """ - with does_not_raise(): - instance = image_policy_action - diff_null = instance.diff_null - assert diff_null["action"] is None - assert diff_null["ip_address"] is None - assert diff_null["logical_name"] is None - assert diff_null["policy"] is None - assert diff_null["serial_number"] is None diff --git a/tests/unit/modules/dcnm/dcnm_image_upgrade/test_image_upgrade_image_stage.py b/tests/unit/modules/dcnm/dcnm_image_upgrade/test_image_upgrade_image_stage.py deleted file mode 100644 index 5c9172fc2..000000000 --- a/tests/unit/modules/dcnm/dcnm_image_upgrade/test_image_upgrade_image_stage.py +++ /dev/null @@ -1,825 +0,0 @@ -# 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 -""" -ImageStage - unit tests -""" - -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 -from unittest.mock import MagicMock - -import pytest -from ansible_collections.ansible.netcommon.tests.unit.modules.utils import \ - AnsibleFailJson -from ansible_collections.cisco.dcnm.plugins.module_utils.image_upgrade.api_endpoints import \ - ApiEndpoints -from ansible_collections.cisco.dcnm.plugins.module_utils.image_upgrade.switch_issu_details import \ - SwitchIssuDetailsBySerialNumber - -from .utils import (MockAnsibleModule, does_not_raise, image_stage_fixture, - issu_details_by_serial_number_fixture, - responses_controller_version, responses_image_stage, - responses_switch_issu_details) - -PATCH_MODULE_UTILS = "ansible_collections.cisco.dcnm.plugins.module_utils." -PATCH_IMAGE_UPGRADE = PATCH_MODULE_UTILS + "image_upgrade." -PATCH_COMMON = PATCH_MODULE_UTILS + "common." -PATCH_IMAGE_STAGE_REST_SEND_COMMIT = PATCH_IMAGE_UPGRADE + "image_stage.RestSend.commit" -PATCH_IMAGE_STAGE_REST_SEND_RESULT_CURRENT = ( - PATCH_IMAGE_UPGRADE + "image_stage.RestSend.result_current" -) -PATCH_IMAGE_STAGE_POPULATE_CONTROLLER_VERSION = ( - "ansible_collections.cisco.dcnm.plugins.modules.dcnm_image_upgrade." - "ImageStage._populate_controller_version" -) - -DCNM_SEND_CONTROLLER_VERSION = PATCH_COMMON + "controller_version.dcnm_send" -DCNM_SEND_ISSU_DETAILS = PATCH_IMAGE_UPGRADE + "switch_issu_details.dcnm_send" - - -def test_image_upgrade_stage_00001(image_stage) -> None: - """ - Function - - __init__ - - Test - - Class attributes are initialized to expected values - """ - instance = image_stage - assert instance.ansible_module == MockAnsibleModule - assert instance.class_name == "ImageStage" - assert isinstance(instance.properties, dict) - assert isinstance(instance.serial_numbers_done, set) - assert instance.controller_version is None - assert instance.payload is None - assert isinstance(instance.issu_detail, SwitchIssuDetailsBySerialNumber) - assert isinstance(instance.endpoints, ApiEndpoints) - - module_path = "/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/" - module_path += "stagingmanagement/stage-image" - assert instance.path == module_path - assert instance.verb == "POST" - - -def test_image_upgrade_stage_00002(image_stage) -> None: - """ - Function - - _init_properties - - Test - - Class properties are initialized to expected values - """ - instance = image_stage - assert isinstance(instance.properties, dict) - assert instance.properties.get("response_data") == [] - assert instance.properties.get("response") == [] - assert instance.properties.get("result") == [] - assert instance.properties.get("serial_numbers") is None - assert instance.properties.get("check_interval") == 10 - assert instance.properties.get("check_timeout") == 1800 - - -@pytest.mark.parametrize( - "key, expected", - [ - ("test_image_upgrade_stage_00003a", "12.1.2e"), - ("test_image_upgrade_stage_00003b", "12.1.3b"), - ], -) -def test_image_upgrade_stage_00003(monkeypatch, image_stage, key, expected) -> None: - """ - Function - - _populate_controller_version - - Test - - test_image_upgrade_stage_00003a -> instance.controller_version == "12.1.2e" - - test_image_upgrade_stage_00003b -> instance.controller_version == "12.1.3b" - - Description - _populate_controller_version retrieves the controller version from - the controller. This is used in commit() to populate the payload - with either a misspelled "sereialNum" key/value (12.1.2e) or a - correctly-spelled "serialNumbers" key/value (12.1.3b). - """ - - def mock_dcnm_send_controller_version(*args) -> Dict[str, Any]: - return responses_controller_version(key) - - monkeypatch.setattr(DCNM_SEND_CONTROLLER_VERSION, mock_dcnm_send_controller_version) - - instance = image_stage - instance._populate_controller_version() # pylint: disable=protected-access - assert instance.controller_version == expected - - -def test_image_upgrade_stage_00004( - monkeypatch, image_stage, issu_details_by_serial_number -) -> None: - """ - Function - - prune_serial_numbers - - Summary - Verify that prune_serial_numbers prunes serial numbers that have already - been staged. - - Test - - module.serial_numbers contains only serial numbers - for which imageStaged == "none" (FDO2112189M, FDO211218AX, FDO211218B5) - - module.serial_numbers does not contain serial numbers - for which imageStaged == "Success" (FDO211218FV, FDO211218GC) - - Description - prune_serial_numbers removes serial numbers from the list for which - imageStaged == "Success" - """ - key = "test_image_upgrade_stage_00004a" - - def mock_dcnm_send_switch_issu_details(*args) -> Dict[str, Any]: - return responses_switch_issu_details(key) - - monkeypatch.setattr(DCNM_SEND_ISSU_DETAILS, mock_dcnm_send_switch_issu_details) - - instance = image_stage - instance.issu_detail = issu_details_by_serial_number - instance.serial_numbers = [ - "FDO2112189M", - "FDO211218AX", - "FDO211218B5", - "FDO211218FV", - "FDO211218GC", - ] - instance.prune_serial_numbers() - assert isinstance(instance.serial_numbers, list) - assert len(instance.serial_numbers) == 3 - assert "FDO2112189M" in instance.serial_numbers - assert "FDO211218AX" in instance.serial_numbers - assert "FDO211218B5" in instance.serial_numbers - assert "FDO211218FV" not in instance.serial_numbers - assert "FDO211218GC" not in instance.serial_numbers - - -def test_image_upgrade_stage_00005( - monkeypatch, image_stage, issu_details_by_serial_number -) -> None: - """ - Function - - validate_serial_numbers - - Summary - Verify that validate_serial_numbers raises fail_json appropriately. - - Test - - fail_json is not called when imageStaged == "Success" - - fail_json is called when imageStaged == "Failed" - - Description - validate_serial_numbers checks the imageStaged status for each serial - number and raises fail_json if imageStaged == "Failed" for any serial - number. - """ - key = "test_image_upgrade_stage_00005a" - - def mock_dcnm_send_switch_issu_details(*args) -> Dict[str, Any]: - return responses_switch_issu_details(key) - - monkeypatch.setattr(DCNM_SEND_ISSU_DETAILS, mock_dcnm_send_switch_issu_details) - - match = "Image staging is failing for the following switch: " - match += "cvd-2313-leaf, 172.22.150.108, FDO2112189M. " - match += "Check the switch connectivity to the controller " - match += "and try again." - - instance = image_stage - instance.issu_detail = issu_details_by_serial_number - instance.serial_numbers = ["FDO21120U5D", "FDO2112189M"] - with pytest.raises(AnsibleFailJson, match=match): - instance.validate_serial_numbers() - - -def test_image_upgrade_stage_00020( - monkeypatch, image_stage, issu_details_by_serial_number -) -> None: - """ - Function - - _wait_for_image_stage_to_complete - - Summary - Verify proper behavior of _wait_for_image_stage_to_complete when - imageStaged is "Success" for all serial numbers. - - Test - - imageStaged == "Success" for all serial numbers so - fail_json is not called - - instance.serial_numbers_done is a set() - - instance.serial_numbers_done has length 2 - - instance.serial_numbers_done == module.serial_numbers - - Description - _wait_for_image_stage_to_complete looks at the imageStaged status for each - serial number and waits for it to be "Success" or "Failed". - In the case where all serial numbers are "Success", the module returns. - In the case where any serial number is "Failed", the module calls fail_json. - """ - key = "test_image_upgrade_stage_00020a" - - def mock_dcnm_send_switch_issu_details(*args) -> Dict[str, Any]: - return responses_switch_issu_details(key) - - monkeypatch.setattr(DCNM_SEND_ISSU_DETAILS, mock_dcnm_send_switch_issu_details) - - with does_not_raise(): - instance = image_stage - instance.unit_test = True - instance.issu_detail = issu_details_by_serial_number - instance.serial_numbers = [ - "FDO21120U5D", - "FDO2112189M", - ] - instance._wait_for_image_stage_to_complete() # pylint: disable=protected-access - assert isinstance(instance.serial_numbers_done, set) - assert len(instance.serial_numbers_done) == 2 - assert "FDO21120U5D" in instance.serial_numbers_done - assert "FDO2112189M" in instance.serial_numbers_done - - -def test_image_upgrade_stage_00021( - monkeypatch, image_stage, issu_details_by_serial_number -) -> None: - """ - Function - - _wait_for_image_stage_to_complete - - Summary - Verify proper behavior of _wait_for_image_stage_to_complete when - imageStaged is "Failed" for one serial number and imageStaged - is "Success" for one serial number. - - Test - - module.serial_numbers_done is a set() - - module.serial_numbers_done has length 1 - - module.serial_numbers_done contains FDO21120U5D - because imageStaged is "Success" - - fail_json is called on serial number FDO2112189M - because imageStaged is "Failed" - - error message matches expected - - Description - _wait_for_image_stage_to_complete looks at the imageStaged status for each - serial number and waits for it to be "Success" or "Failed". - In the case where all serial numbers are "Success", the module returns. - In the case where any serial number is "Failed", the module calls fail_json. - """ - key = "test_image_upgrade_stage_00021a" - - def mock_dcnm_send_switch_issu_details(*args) -> Dict[str, Any]: - return responses_switch_issu_details(key) - - monkeypatch.setattr(DCNM_SEND_ISSU_DETAILS, mock_dcnm_send_switch_issu_details) - - with does_not_raise(): - instance = image_stage - instance.unit_test = True - instance.issu_detail = issu_details_by_serial_number - instance.serial_numbers = [ - "FDO21120U5D", - "FDO2112189M", - ] - - match = "Seconds remaining 1790: stage image failed for " - match += "cvd-2313-leaf, FDO2112189M, 172.22.150.108. image " - match += "staged percent: 90" - with pytest.raises(AnsibleFailJson, match=match): - instance._wait_for_image_stage_to_complete() # pylint: disable=protected-access - assert isinstance(instance.serial_numbers_done, set) - assert len(instance.serial_numbers_done) == 1 - assert "FDO21120U5D" in instance.serial_numbers_done - assert "FDO2112189M" not in instance.serial_numbers_done - - -def test_image_upgrade_stage_00022( - monkeypatch, image_stage, issu_details_by_serial_number -) -> None: - """ - Function - - _wait_for_image_stage_to_complete - - Summary - Verify proper behavior of _wait_for_image_stage_to_complete when - timeout is reached for one serial number (i.e. imageStaged is - "In-Progress") and imageStaged is "Success" for one serial number. - - Test - - module.serial_numbers_done is a set() - - module.serial_numbers_done has length 1 - - module.serial_numbers_done contains FDO21120U5D - because imageStaged == "Success" - - module.serial_numbers_done does not contain FDO2112189M - - fail_json is called due to timeout because FDO2112189M - imageStaged == "In-Progress" - - error message matches expected - - Description - See test_wait_for_image_stage_to_complete for functional details. - """ - key = "test_image_upgrade_stage_00022a" - - def mock_dcnm_send_switch_issu_details(*args) -> Dict[str, Any]: - return responses_switch_issu_details(key) - - monkeypatch.setattr(DCNM_SEND_ISSU_DETAILS, mock_dcnm_send_switch_issu_details) - - with does_not_raise(): - instance = image_stage - instance.unit_test = True - instance.issu_detail = issu_details_by_serial_number - instance.serial_numbers = [ - "FDO21120U5D", - "FDO2112189M", - ] - - match = "ImageStage._wait_for_image_stage_to_complete: " - match += "Timed out waiting for image stage to complete. " - match += "serial_numbers_done: FDO21120U5D, " - match += "serial_numbers_todo: FDO21120U5D,FDO2112189M" - - with pytest.raises(AnsibleFailJson, match=match): - instance._wait_for_image_stage_to_complete() # pylint: disable=protected-access - assert isinstance(instance.serial_numbers_done, set) - assert len(instance.serial_numbers_done) == 1 - assert "FDO21120U5D" in instance.serial_numbers_done - assert "FDO2112189M" not in instance.serial_numbers_done - - -def test_image_upgrade_stage_00030( - monkeypatch, image_stage, issu_details_by_serial_number -) -> None: - """ - Function - - _wait_for_current_actions_to_complete - - Summary - Verify proper behavior of _wait_for_current_actions_to_complete when - there are no actions pending. - - Test - - instance.serial_numbers_done is a set() - - instance.serial_numbers_done has length 2 - - instance.serial_numbers_done contains all serial numbers - in instance.serial_numbers - - fail_json is not called - - Description - _wait_for_current_actions_to_complete waits until staging, validation, - and upgrade actions are complete for all serial numbers. It calls - SwitchIssuDetailsBySerialNumber.actions_in_progress() and expects - this to return False. actions_in_progress() returns True until none of - the following keys has a value of "In-Progress": - - imageStaged - - upgrade - - validated - """ - key = "test_image_upgrade_stage_00030a" - - def mock_dcnm_send_switch_issu_details(*args) -> Dict[str, Any]: - return responses_switch_issu_details(key) - - monkeypatch.setattr(DCNM_SEND_ISSU_DETAILS, mock_dcnm_send_switch_issu_details) - - with does_not_raise(): - instance = image_stage - instance.unit_test = True - instance.issu_detail = issu_details_by_serial_number - instance.serial_numbers = [ - "FDO21120U5D", - "FDO2112189M", - ] - instance._wait_for_current_actions_to_complete() # pylint: disable=protected-access - assert isinstance(instance.serial_numbers_done, set) - assert len(instance.serial_numbers_done) == 2 - assert "FDO21120U5D" in instance.serial_numbers_done - assert "FDO2112189M" in instance.serial_numbers_done - - -def test_image_upgrade_stage_00031( - monkeypatch, image_stage, issu_details_by_serial_number -) -> None: - """ - Function - - _wait_for_current_actions_to_complete - - Summary - Verify proper behavior of _wait_for_current_actions_to_complete when - there is a timeout waiting for one serial number to complete staging. - - Test - - module.serial_numbers_done is a set() - - module.serial_numbers_done has length 1 - - module.serial_numbers_done contains FDO21120U5D - because imageStaged == "Success" - - module.serial_numbers_done does not contain FDO2112189M - - fail_json is called due to timeout because FDO2112189M - imageStaged == "In-Progress" - - Description - See test_image_upgrade_stage_00030 for functional details. - """ - key = "test_image_upgrade_stage_00031a" - - def mock_dcnm_send_switch_issu_details(*args) -> Dict[str, Any]: - return responses_switch_issu_details(key) - - monkeypatch.setattr(DCNM_SEND_ISSU_DETAILS, mock_dcnm_send_switch_issu_details) - - match = "ImageStage._wait_for_current_actions_to_complete: " - match += "Timed out waiting for actions to complete. " - match += "serial_numbers_done: FDO21120U5D, " - match += "serial_numbers_todo: FDO21120U5D,FDO2112189M" - - with does_not_raise(): - instance = image_stage - instance.unit_test = True - instance.issu_detail = issu_details_by_serial_number - instance.serial_numbers = [ - "FDO21120U5D", - "FDO2112189M", - ] - - with pytest.raises(AnsibleFailJson, match=match): - instance._wait_for_current_actions_to_complete() # pylint: disable=protected-access - assert isinstance(instance.serial_numbers_done, set) - assert len(instance.serial_numbers_done) == 1 - assert "FDO21120U5D" in instance.serial_numbers_done - assert "FDO2112189M" not in instance.serial_numbers_done - - -MATCH_00040 = "ImageStage.check_interval: must be a positive integer or zero." - - -@pytest.mark.parametrize( - "arg, value, context", - [ - (True, None, pytest.raises(AnsibleFailJson, match=MATCH_00040)), - (-1, None, pytest.raises(AnsibleFailJson, match=MATCH_00040)), - (10, 10, does_not_raise()), - ("a", None, pytest.raises(AnsibleFailJson, match=MATCH_00040)), - ], -) -def test_image_upgrade_stage_00040(image_stage, arg, value, context) -> None: - """ - Function - - check_interval - - Summary - Verify that check_interval argument validation works as expected. - - Test - - Verify input arguments to check_interval property - - Description - check_interval expects a positive integer value, or zero. - """ - with does_not_raise(): - instance = image_stage - with context: - instance.check_interval = arg - if value is not None: - assert instance.check_interval == value - - -MATCH_00050 = "ImageStage.check_timeout: must be a positive integer or zero." - - -@pytest.mark.parametrize( - "arg, value, context", - [ - (True, None, pytest.raises(AnsibleFailJson, match=MATCH_00050)), - (-1, None, pytest.raises(AnsibleFailJson, match=MATCH_00050)), - (10, 10, does_not_raise()), - ("a", None, pytest.raises(AnsibleFailJson, match=MATCH_00050)), - ], -) -def test_image_upgrade_stage_00050(image_stage, arg, value, context) -> None: - """ - Function - - check_interval - - Summary - Verify that check_timeout argument validation works as expected. - - Test - - Verify input arguments to check_timeout property - - Description - check_timeout expects a positive integer value, or zero. - """ - with does_not_raise(): - instance = image_stage - with context: - instance.check_timeout = arg - if value is not None: - assert instance.check_timeout == value - - -MATCH_00060 = ( - "ImageStage.serial_numbers: must be a python list of switch serial numbers." -) - - -@pytest.mark.parametrize( - "arg, value, context", - [ - ("foo", None, pytest.raises(AnsibleFailJson, match=MATCH_00060)), - (10, None, pytest.raises(AnsibleFailJson, match=MATCH_00060)), - (["DD001115F"], ["DD001115F"], does_not_raise()), - ], -) -def test_image_upgrade_stage_00060(image_stage, arg, value, context) -> None: - """ - Function - - serial_numbers - - Summary - Verify that serial_numbers argument validation works as expected. - - Test - - Verify inputs to serial_numbers property - - Verify that fail_json is called if the input is not a list - - Description - serial_numbers expects a list of serial numbers. - """ - with does_not_raise(): - instance = image_stage - with context: - instance.serial_numbers = arg - if value is not None: - assert instance.serial_numbers == value - - -MATCH_00070 = "ImageStage.commit_normal_mode: call instance.serial_numbers " -MATCH_00070 += "before calling commit." - - -@pytest.mark.parametrize( - "serial_numbers_is_set, expected", - [ - (True, does_not_raise()), - (False, pytest.raises(AnsibleFailJson, match=MATCH_00070)), - ], -) -def test_image_upgrade_stage_00070( - monkeypatch, image_stage, serial_numbers_is_set, expected -) -> None: - """ - Function - commit - - Summary - Verify that commit raises fail_json appropriately based on value of - instance.serial_numbers. - - Test - - fail_json is called when serial_numbers is None - - fail_json is not called when serial_numbers is set - """ - key = "test_image_upgrade_stage_00070a" - - def mock_dcnm_send_controller_version(*args, **kwargs) -> Dict[str, Any]: - return responses_controller_version(key) - - def mock_rest_send_image_stage(*args, **kwargs) -> Dict[str, Any]: - return responses_image_stage(key) - - def mock_dcnm_send_switch_issu_details(*args, **kwargs) -> Dict[str, Any]: - return responses_switch_issu_details(key) - - monkeypatch.setattr(DCNM_SEND_CONTROLLER_VERSION, mock_dcnm_send_controller_version) - monkeypatch.setattr(DCNM_SEND_ISSU_DETAILS, mock_dcnm_send_switch_issu_details) - monkeypatch.setattr(PATCH_IMAGE_STAGE_REST_SEND_COMMIT, mock_rest_send_image_stage) - monkeypatch.setattr(PATCH_IMAGE_STAGE_REST_SEND_RESULT_CURRENT, {"success": True}) - - instance = image_stage - if serial_numbers_is_set: - instance.serial_numbers = ["FDO21120U5D"] - with expected: - instance.commit() - - -@pytest.mark.parametrize( - "controller_version, expected_serial_number_key", - [ - ("12.1.2e", "sereialNum"), - ("12.1.3b", "serialNumbers"), - ], -) -def test_image_upgrade_stage_00072( - monkeypatch, image_stage, controller_version, expected_serial_number_key -) -> None: - """ - Function - - commit - - Summary - Verify that the serial number key name in the payload is set correctly - based on the controller version. - - Test - - controller_version 12.1.2e -> key name "sereialNum" (yes, misspelled) - - controller_version 12.1.3b -> key name "serialNumbers - - Description - commit() will set the payload key name for the serial number - based on the controller version, per Expected Results below - """ - key = "test_image_upgrade_stage_00072a" - - def mock_controller_version(*args) -> None: - instance.controller_version = controller_version - - controller_version_patch = "ansible_collections.cisco.dcnm.plugins." - controller_version_patch += "modules.dcnm_image_upgrade." - controller_version_patch += "ImageStage._populate_controller_version" - monkeypatch.setattr(controller_version_patch, mock_controller_version) - - def mock_rest_send_image_stage(*args, **kwargs) -> Dict[str, Any]: - return responses_image_stage(key) - - def mock_dcnm_send_switch_issu_details(*args) -> Dict[str, Any]: - return responses_switch_issu_details(key) - - monkeypatch.setattr(DCNM_SEND_ISSU_DETAILS, mock_dcnm_send_switch_issu_details) - monkeypatch.setattr(PATCH_IMAGE_STAGE_REST_SEND_COMMIT, mock_rest_send_image_stage) - monkeypatch.setattr(PATCH_IMAGE_STAGE_REST_SEND_RESULT_CURRENT, {"success": True}) - - instance = image_stage - instance.serial_numbers = ["FDO21120U5D"] - instance.commit() - assert expected_serial_number_key in instance.payload.keys() - - -def test_image_upgrade_stage_00073(monkeypatch, image_stage) -> None: - """ - Function - - commit - - Summary - Verify that commit() sets result, response, and response_data - appropriately when serial_numbers is empty. - - Setup - - SwitchIssuDetailsBySerialNumber is mocked to return a successful response - - self.serial_numbers is set to [] (empty list) - - Test - - commit() sets the following to expected values: - - self.result, self.result_current - - self.response, self.response_current - - self.response_data - - Description - When len(serial_numbers) == 0, commit() will set result and - response properties, and return without doing anything else. - """ - monkeypatch.setattr(PATCH_IMAGE_STAGE_REST_SEND_RESULT_CURRENT, {"success": True}) - - response_msg = "No files to stage." - with does_not_raise(): - instance = image_stage - instance.serial_numbers = [] - instance.commit() - assert instance.result == [{"success": True, "changed": False}] - assert instance.result_current == {"success": True, "changed": False} - assert instance.response_current == { - "DATA": [{"key": "ALL", "value": response_msg}] - } - assert instance.response == [instance.response_current] - assert instance.response_data == [instance.response_current.get("DATA")] - - -def test_image_upgrade_stage_00074(monkeypatch, image_stage) -> None: - """ - Function - - commit - - Summary - Verify that commit() calls fail_json() on 500 response from the controller. - - Setup - - IssuDetailsBySerialNumber is mocked to return a successful response - - ImageStage is mocked to return a non-successful (500) response - - Test - - commit() will call fail_json() - - Description - commit() will call fail_json() on non-success response from the controller. - """ - key = "test_image_upgrade_stage_00074a" - - def mock_controller_version(*args) -> None: - instance.controller_version = "12.1.3b" - - def mock_dcnm_send_switch_issu_details(*args) -> Dict[str, Any]: - return responses_switch_issu_details(key) - - def mock_rest_send_image_stage(*args, **kwargs) -> Dict[str, Any]: - return responses_image_stage(key) - - monkeypatch.setattr( - PATCH_IMAGE_STAGE_POPULATE_CONTROLLER_VERSION, mock_controller_version - ) - monkeypatch.setattr(DCNM_SEND_ISSU_DETAILS, mock_dcnm_send_switch_issu_details) - monkeypatch.setattr(PATCH_IMAGE_STAGE_REST_SEND_COMMIT, mock_rest_send_image_stage) - monkeypatch.setattr(PATCH_IMAGE_STAGE_REST_SEND_RESULT_CURRENT, {"success": False}) - - instance = image_stage - instance.serial_numbers = ["FDO21120U5D"] - match = "ImageStage.commit_normal_mode: failed" - with pytest.raises(AnsibleFailJson, match=match): - instance.commit() - - -def test_image_upgrade_stage_00075(monkeypatch, image_stage) -> None: - """ - Function - - commit - - Summary - Verify that commit() sets self.diff to expected values on 200 response - from the controller. - - Setup - - IssuDetailsBySerialNumber is mocked to return a successful response - - ImageStage._populate_controller_version is mocked to 12.1.3b - - ImageStage.rest_send.commit is mocked to return a successful response - - ImageStage.rest_send.current_result is mocked to return a successful result - - ImageStage.validate_serial_numbers is tracked with MagicMock to ensure - it's called once - - Test - - commit() sets self.diff to the expected values - """ - key = "test_image_upgrade_stage_00075a" - - def mock_controller_version(*args) -> None: - instance.controller_version = "12.1.3b" - - def mock_dcnm_send_switch_issu_details(*args) -> Dict[str, Any]: - return responses_switch_issu_details(key) - - def mock_rest_send_image_stage(*args, **kwargs) -> Dict[str, Any]: - return responses_image_stage(key) - - def mock_wait_for_image_stage_to_complete(*args) -> None: - instance.serial_numbers_done = {"FDO21120U5D"} - - monkeypatch.setattr( - PATCH_IMAGE_STAGE_POPULATE_CONTROLLER_VERSION, mock_controller_version - ) - monkeypatch.setattr(DCNM_SEND_ISSU_DETAILS, mock_dcnm_send_switch_issu_details) - monkeypatch.setattr(PATCH_IMAGE_STAGE_REST_SEND_COMMIT, mock_rest_send_image_stage) - monkeypatch.setattr( - PATCH_IMAGE_STAGE_REST_SEND_RESULT_CURRENT, {"success": True, "changed": True} - ) - validate_serial_numbers = MagicMock(name="validate_serial_numbers") - - instance = image_stage - instance.serial_numbers = ["FDO21120U5D"] - monkeypatch.setattr( - instance, - "_wait_for_image_stage_to_complete", - mock_wait_for_image_stage_to_complete, - ) - monkeypatch.setattr(instance, "validate_serial_numbers", validate_serial_numbers) - instance.commit() - assert validate_serial_numbers.assert_called_once - assert instance.serial_numbers_done == {"FDO21120U5D"} - assert instance.result_current == {"success": True, "changed": True} - assert instance.diff[0]["policy"] == "KR5M" - assert instance.diff[0]["ip_address"] == "172.22.150.102" - assert instance.diff[0]["serial_number"] == "FDO21120U5D" diff --git a/tests/unit/modules/dcnm/dcnm_image_upgrade/test_image_upgrade_image_upgrade.py b/tests/unit/modules/dcnm/dcnm_image_upgrade/test_image_upgrade_image_upgrade.py deleted file mode 100644 index 4b1db4f42..000000000 --- a/tests/unit/modules/dcnm/dcnm_image_upgrade/test_image_upgrade_image_upgrade.py +++ /dev/null @@ -1,2599 +0,0 @@ -# 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 -# Some tests require calling protected methods -# pylint: disable=protected-access - -from __future__ import absolute_import, division, print_function - -__metaclass__ = type - -__copyright__ = "Copyright (c) 2024 Cisco and/or its affiliates." -__author__ = "Allen Robel" - -import logging -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_upgrade.image_upgrade import \ - ImageUpgrade - -from .utils import (does_not_raise, image_upgrade_fixture, - issu_details_by_ip_address_fixture, payloads_image_upgrade, - responses_image_install_options, responses_image_upgrade, - responses_switch_issu_details) - -PATCH_MODULE_UTILS = "ansible_collections.cisco.dcnm.plugins.module_utils." -PATCH_IMAGE_UPGRADE = PATCH_MODULE_UTILS + "image_upgrade." - -PATCH_IMAGE_UPGRADE_REST_SEND_COMMIT = ( - PATCH_IMAGE_UPGRADE + "image_upgrade.RestSend.commit" -) -PATCH_IMAGE_UPGRADE_REST_SEND_RESPONSE_CURRENT = ( - PATCH_IMAGE_UPGRADE + "image_upgrade.RestSend.response_current" -) -PATCH_IMAGE_UPGRADE_REST_SEND_RESULT_CURRENT = ( - PATCH_IMAGE_UPGRADE + "image_upgrade.RestSend.result_current" -) - -REST_SEND_IMAGE_UPGRADE = PATCH_IMAGE_UPGRADE + "image_upgrade.RestSend" -DCNM_SEND_IMAGE_UPGRADE_COMMON = PATCH_IMAGE_UPGRADE + "image_upgrade_common.dcnm_send" -DCNM_SEND_INSTALL_OPTIONS = PATCH_IMAGE_UPGRADE + "install_options.dcnm_send" -DCNM_SEND_ISSU_DETAILS = PATCH_IMAGE_UPGRADE + "switch_issu_details.dcnm_send" - - -def test_image_upgrade_upgrade_00001(image_upgrade) -> None: - """ - Function - - ImageUpgrade.__init__ - - Test - - Class attributes are initialized to expected values - """ - instance = image_upgrade - assert isinstance(instance, ImageUpgrade) - assert isinstance(instance.ipv4_done, set) - assert isinstance(instance.ipv4_todo, set) - assert isinstance(instance.payload, dict) - assert instance.class_name == "ImageUpgrade" - assert ( - instance.path - == "/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/imageupgrade/upgrade-image" - ) - assert instance.verb == "POST" - - -def test_image_upgrade_upgrade_00003(image_upgrade) -> None: - """ - Function - - ImageUpgrade._init_properties - - Test - - Class properties are initialized to expected values - """ - instance = image_upgrade - instance._init_properties() - assert isinstance(instance.properties, dict) - assert instance.properties.get("bios_force") is False - assert instance.properties.get("check_interval") == 10 - assert instance.properties.get("check_timeout") == 1800 - assert instance.properties.get("config_reload") is False - assert instance.properties.get("devices") is None - assert instance.properties.get("disruptive") is True - assert instance.properties.get("epld_golden") is False - assert instance.properties.get("epld_module") == "ALL" - assert instance.properties.get("epld_upgrade") is False - assert instance.properties.get("force_non_disruptive") is False - assert instance.properties.get("response_data") == [] - assert instance.properties.get("response") == [] - assert instance.properties.get("result") == [] - assert instance.properties.get("non_disruptive") is False - assert instance.properties.get("force_non_disruptive") is False - assert instance.properties.get("package_install") is False - assert instance.properties.get("package_uninstall") is False - assert instance.properties.get("reboot") is False - assert instance.properties.get("write_erase") is False - assert instance.valid_nxos_mode == { - "disruptive", - "non_disruptive", - "force_non_disruptive", - } - - -def test_image_upgrade_upgrade_00004(monkeypatch, image_upgrade) -> None: - """ - Function - - ImageUpgrade.validate_devices - - Test - - ip_addresses contains the ip addresses of the devices for which - validation succeeds - - Description - ImageUpgrade.validate_devices updates the set ImageUpgrade.ip_addresses - with the ip addresses of the devices for which validation succeeds. - Currently, validation succeeds for all devices. This function may be - updated in the future to handle various failure scenarios. - - Expected results: - - 1. instance.ip_addresses will contain {"172.22.150.102", "172.22.150.108"} - """ - devices = [{"ip_address": "172.22.150.102"}, {"ip_address": "172.22.150.108"}] - - instance = image_upgrade - instance.devices = devices - - def mock_dcnm_send_issu_details(*args, **kwargs) -> Dict[str, Any]: - key = "test_image_upgrade_upgrade_00004a" - return responses_switch_issu_details(key) - - monkeypatch.setattr(DCNM_SEND_ISSU_DETAILS, mock_dcnm_send_issu_details) - - instance._validate_devices() # pylint: disable=protected-access - assert isinstance(instance.ip_addresses, set) - assert len(instance.ip_addresses) == 2 - assert "172.22.150.102" in instance.ip_addresses - assert "172.22.150.108" in instance.ip_addresses - - -def test_image_upgrade_upgrade_00005(image_upgrade) -> None: - """ - Function - - ImageUpgrade.commit - - Test - - fail_json is called because devices is None - """ - instance = image_upgrade - - match = ( - "ImageUpgrade._validate_devices: call instance.devices before calling commit." - ) - with pytest.raises(AnsibleFailJson, match=match): - instance.unit_test = True - instance.commit() - - -def test_image_upgrade_upgrade_00018(monkeypatch, image_upgrade) -> None: - """ - Function - - ImageUpgrade.commit - - Test - - upgrade.nxos set to invalid value - - Setup - - ImageUpgrade.devices is set to a list of one dict for a device - to be upgraded. - - The methods called by commit are mocked to simulate that the - the image has already been staged and validated and the device - has already been upgraded to the desired version. - - Methods called by commit that wait for current actions, and - image upgrade, to complete are mocked to do nothing. - - Expected results: - - 1. commit will call _build_payload which will call fail_json - """ - instance = image_upgrade - - key = "test_image_upgrade_upgrade_00019a" - - def mock_dcnm_send_install_options(*args, **kwargs) -> Dict[str, Any]: - return responses_image_install_options(key) - - def mock_dcnm_send_issu_details(*args, **kwargs) -> Dict[str, Any]: - return responses_switch_issu_details(key) - - def mock_wait_for_current_actions_to_complete(*args, **kwargs): - pass - - monkeypatch.setattr(DCNM_SEND_INSTALL_OPTIONS, mock_dcnm_send_install_options) - monkeypatch.setattr(DCNM_SEND_ISSU_DETAILS, mock_dcnm_send_issu_details) - monkeypatch.setattr( - instance, - "_wait_for_current_actions_to_complete", - mock_wait_for_current_actions_to_complete, - ) - - instance.devices = [ - { - "policy": "KR5M", - "stage": True, - "upgrade": {"nxos": "FOO", "epld": True}, - "options": { - "nxos": {"mode": "disruptive", "bios_force": True}, - "package": {"install": False, "uninstall": False}, - "reboot": {"config_reload": False, "write_erase": False}, - }, - "validate": True, - "ip_address": "172.22.150.102", - "policy_changed": False, - } - ] - match = r"ImageUpgrade._build_payload_issu_upgrade: upgrade.nxos must be a boolean. Got FOO\." - with pytest.raises(AnsibleFailJson, match=match): - instance.unit_test = True - instance.commit() - - -def test_image_upgrade_upgrade_00019(monkeypatch, image_upgrade) -> None: - """ - Function - - ImageUpgrade._build_payload - - Test - - non-default values are set for several options - - policy_changed is set to False - - Verify that payload is built correctly - - - Setup - - ImageUpgrade.devices is set to a list of one dict for a device - to be upgraded. - - commit -> _build_payload -> issu_details is mocked to simulate - that the image has already been staged and validated and the - device has already been upgraded to the desired version. - - commit -> _build_payload -> install_options is mocked to simulate - that the EPLD image does not need upgrade. - - The following methods, called by commit() are mocked to do nothing: - - _wait_for_current_actions_to_complete - - _wait_for_image_upgrade_to_complete - - RestSend is mocked to return a successful response - - - Expected results: - - 1. instance.payload (built by instance._build_payload and based on - instance.devices) will equal a payload previously obtained by running - ansible-playbook against the controller for this scenario, which verifies - that the non-default values are included in the payload. - """ - instance = image_upgrade - - key = "test_image_upgrade_upgrade_00019a" - - def mock_dcnm_send_install_options(*args, **kwargs) -> Dict[str, Any]: - return responses_image_install_options(key) - - def mock_dcnm_send_issu_details(*args, **kwargs) -> Dict[str, Any]: - return responses_switch_issu_details(key) - - def mock_wait_for_current_actions_to_complete(*args, **kwargs): - pass - - def mock_wait_for_image_upgrade_to_complete(*args, **kwargs): - pass - - def mock_rest_send_image_upgrade(*args, **kwargs) -> Dict[str, Any]: - return responses_image_upgrade(key) - - monkeypatch.setattr( - PATCH_IMAGE_UPGRADE_REST_SEND_COMMIT, mock_rest_send_image_upgrade - ) - monkeypatch.setattr(PATCH_IMAGE_UPGRADE_REST_SEND_RESULT_CURRENT, {"success": True}) - - monkeypatch.setattr(DCNM_SEND_INSTALL_OPTIONS, mock_dcnm_send_install_options) - monkeypatch.setattr(DCNM_SEND_ISSU_DETAILS, mock_dcnm_send_issu_details) - - monkeypatch.setattr( - instance, - "_wait_for_current_actions_to_complete", - mock_wait_for_current_actions_to_complete, - ) - monkeypatch.setattr( - instance, - "_wait_for_image_upgrade_to_complete", - mock_wait_for_image_upgrade_to_complete, - ) - - instance.devices = [ - { - "policy": "KR5M", - "reboot": False, - "stage": True, - "upgrade": {"nxos": False, "epld": True}, - "options": { - "nxos": {"mode": "disruptive", "bios_force": True}, - "package": {"install": True, "uninstall": False}, - "epld": {"module": 1, "golden": True}, - "reboot": {"config_reload": True, "write_erase": False}, - }, - "validate": True, - "ip_address": "172.22.150.102", - "policy_changed": False, - } - ] - - instance.unit_test = True - instance.commit() - - assert instance.payload == payloads_image_upgrade(key) - - -def test_image_upgrade_upgrade_00020(monkeypatch, image_upgrade) -> None: - """ - Function - - ImageUpgrade.commit - - Test - - User explicitely sets default values for several options - - policy_changed is set to True - - Setup: - - ImageUpgrade.devices is set to a list of one dict for a device - to be upgraded - - commit -> _build_payload -> issu_details is mocked to simulate - that the image has already been staged and validated and the - device has already been upgraded to the desired version. - - commit -> _build_payload -> install_options is mocked to simulate - that the image EPLD does not need upgrade. - - The following methods, called by commit() are mocked to do nothing: - - _wait_for_current_actions_to_complete - - _wait_for_image_upgrade_to_complete - - RestSend is mocked to return a successful response - - - Expected results: - - 1. instance.payload will equal a payload previously obtained by - running ansible-playbook against the controller for this scenario - """ - instance = image_upgrade - - key = "test_image_upgrade_upgrade_00020a" - - def mock_dcnm_send_install_options(*args, **kwargs) -> Dict[str, Any]: - return responses_image_install_options(key) - - def mock_dcnm_send_issu_details(*args, **kwargs) -> Dict[str, Any]: - return responses_switch_issu_details(key) - - def mock_wait_for_current_actions_to_complete(*args, **kwargs): - pass - - def mock_wait_for_image_upgrade_to_complete(*args, **kwargs): - pass - - def mock_rest_send_image_upgrade(*args, **kwargs) -> Dict[str, Any]: - return responses_image_upgrade(key) - - monkeypatch.setattr( - PATCH_IMAGE_UPGRADE_REST_SEND_COMMIT, mock_rest_send_image_upgrade - ) - monkeypatch.setattr(PATCH_IMAGE_UPGRADE_REST_SEND_RESULT_CURRENT, {"success": True}) - - monkeypatch.setattr(DCNM_SEND_INSTALL_OPTIONS, mock_dcnm_send_install_options) - monkeypatch.setattr(DCNM_SEND_ISSU_DETAILS, mock_dcnm_send_issu_details) - - monkeypatch.setattr( - instance, - "_wait_for_current_actions_to_complete", - mock_wait_for_current_actions_to_complete, - ) - monkeypatch.setattr( - instance, - "_wait_for_image_upgrade_to_complete", - mock_wait_for_image_upgrade_to_complete, - ) - - instance.devices = [ - { - "policy": "NR3F", - "reboot": False, - "stage": True, - "upgrade": {"nxos": True, "epld": True}, - "options": { - "nxos": {"mode": "disruptive", "bios_force": False}, - "package": {"install": True, "uninstall": False}, - "epld": {"module": "ALL", "golden": False}, - "reboot": {"config_reload": False, "write_erase": False}, - }, - "validate": True, - "ip_address": "172.22.150.102", - "policy_changed": True, - } - ] - - instance.unit_test = True - instance.commit() - assert instance.payload == payloads_image_upgrade(key) - - -def test_image_upgrade_upgrade_00021(monkeypatch, image_upgrade) -> None: - """ - Function - - ImageUpgrade.commit - - Test - - Invalid value for nxos.mode - - Setup: - - ImageUpgrade.devices is set to a list of one dict for a device - to be upgraded - - The methods called by commit are mocked to simulate that the - device has not yet been upgraded to the desired version - - Method called by commit, _wait_for_current_actions_to_complete - is mocked to do nothing - - instance.devices is set to contain an invalid nxos.mode value - - Expected results: - - 1. commit calls _build_payload, which calls fail_json - """ - instance = image_upgrade - - key = "test_image_upgrade_upgrade_00021a" - - def mock_dcnm_send_install_options(*args, **kwargs) -> Dict[str, Any]: - return responses_image_install_options(key) - - def mock_dcnm_send_issu_details(*args, **kwargs) -> Dict[str, Any]: - return responses_switch_issu_details(key) - - def mock_wait_for_current_actions_to_complete(*args, **kwargs): - pass - - monkeypatch.setattr(DCNM_SEND_INSTALL_OPTIONS, mock_dcnm_send_install_options) - monkeypatch.setattr(DCNM_SEND_ISSU_DETAILS, mock_dcnm_send_issu_details) - monkeypatch.setattr( - instance, - "_wait_for_current_actions_to_complete", - mock_wait_for_current_actions_to_complete, - ) - - instance.devices = [ - { - "policy": "NR3F", - "reboot": False, - "stage": True, - "upgrade": {"nxos": True, "epld": True}, - "options": { - "nxos": {"mode": "FOO", "bios_force": False}, - "package": {"install": False, "uninstall": False}, - "epld": {"module": "ALL", "golden": False}, - "reboot": {"config_reload": False, "write_erase": False}, - }, - "validate": True, - "ip_address": "172.22.150.102", - "policy_changed": True, - } - ] - - match = "ImageUpgrade._build_payload_issu_options_1: " - match += "options.nxos.mode must be one of " - match += r"\['disruptive', 'force_non_disruptive', 'non_disruptive'\]. " - match += "Got FOO." - instance.unit_test = True - with pytest.raises(AnsibleFailJson, match=match): - instance.commit() - - -def test_image_upgrade_upgrade_00022(monkeypatch, image_upgrade) -> None: - """ - Function - - ImageUpgrade.commit - - Test - - Force code coverage of nxos.mode == "non_disruptive" path - - Setup: - - ImageUpgrade.devices is set to a list of one dict for a device - to be upgraded - - The methods called by commit are mocked to simulate that the - device has not yet been upgraded to the desired version - - Methods called by commit that wait for current actions, and - image upgrade, to complete are mocked to do nothing - - instance.devices is set to contain nxos.mode non_disruptive - forcing the code to take nxos_mode == "non_disruptive" path - - Expected results: - - 1. self.payload["issuUpgradeOptions1"]["disruptive"] is False - 2. self.payload["issuUpgradeOptions1"]["forceNonDisruptive"] is False - 3. self.payload["issuUpgradeOptions1"]["nonDisruptive"] is True - """ - instance = image_upgrade - - key = "test_image_upgrade_upgrade_00022a" - - def mock_dcnm_send_install_options(*args, **kwargs) -> Dict[str, Any]: - return responses_image_install_options(key) - - def mock_dcnm_send_issu_details(*args, **kwargs) -> Dict[str, Any]: - return responses_switch_issu_details(key) - - def mock_wait_for_current_actions_to_complete(*args, **kwargs): - pass - - def mock_wait_for_image_upgrade_to_complete(*args, **kwargs): - pass - - def mock_rest_send_image_upgrade(*args, **kwargs) -> Dict[str, Any]: - return responses_image_upgrade(key) - - monkeypatch.setattr( - PATCH_IMAGE_UPGRADE_REST_SEND_COMMIT, mock_rest_send_image_upgrade - ) - monkeypatch.setattr(PATCH_IMAGE_UPGRADE_REST_SEND_RESULT_CURRENT, {"success": True}) - - monkeypatch.setattr(DCNM_SEND_INSTALL_OPTIONS, mock_dcnm_send_install_options) - monkeypatch.setattr(DCNM_SEND_ISSU_DETAILS, mock_dcnm_send_issu_details) - monkeypatch.setattr( - instance, - "_wait_for_current_actions_to_complete", - mock_wait_for_current_actions_to_complete, - ) - monkeypatch.setattr( - instance, - "_wait_for_image_upgrade_to_complete", - mock_wait_for_image_upgrade_to_complete, - ) - - instance.devices = [ - { - "policy": "NR3F", - "reboot": False, - "stage": True, - "upgrade": {"nxos": True, "epld": True}, - "options": { - "nxos": {"mode": "non_disruptive", "bios_force": False}, - "package": {"install": False, "uninstall": False}, - "epld": {"module": "ALL", "golden": False}, - "reboot": {"config_reload": False, "write_erase": False}, - }, - "validate": True, - "ip_address": "172.22.150.102", - "policy_changed": True, - } - ] - - instance.unit_test = True - instance.commit() - assert instance.payload["issuUpgradeOptions1"]["disruptive"] is False - assert instance.payload["issuUpgradeOptions1"]["forceNonDisruptive"] is False - assert instance.payload["issuUpgradeOptions1"]["nonDisruptive"] is True - - -def test_image_upgrade_upgrade_00023(monkeypatch, image_upgrade) -> None: - """ - Function - - ImageUpgrade.commit - - Test - - Force code coverage of nxos.mode == "force_non_disruptive" path - - Setup: - - ImageUpgrade.devices is set to a list of one dict for a device - to be upgraded - - The methods called by commit are mocked to simulate that the - device has not yet been upgraded to the desired version - - Methods called by commit that wait for current actions, and - image upgrade, to complete are mocked to do nothing - - instance.devices is set to contain nxos.mode force_non_disruptive - forcing the code to take nxos_mode == "force_non_disruptive" path - - Expected results: - - 1. self.payload["issuUpgradeOptions1"]["disruptive"] is False - 2. self.payload["issuUpgradeOptions1"]["forceNonDisruptive"] is True - 3. self.payload["issuUpgradeOptions1"]["nonDisruptive"] is False - """ - instance = image_upgrade - - key = "test_image_upgrade_upgrade_00023a" - - def mock_dcnm_send_install_options(*args, **kwargs) -> Dict[str, Any]: - return responses_image_install_options(key) - - def mock_dcnm_send_issu_details(*args, **kwargs) -> Dict[str, Any]: - return responses_switch_issu_details(key) - - def mock_wait_for_current_actions_to_complete(*args, **kwargs): - pass - - def mock_wait_for_image_upgrade_to_complete(*args, **kwargs): - pass - - def mock_rest_send_image_upgrade(*args, **kwargs) -> Dict[str, Any]: - return responses_image_upgrade(key) - - monkeypatch.setattr( - PATCH_IMAGE_UPGRADE_REST_SEND_COMMIT, mock_rest_send_image_upgrade - ) - monkeypatch.setattr(PATCH_IMAGE_UPGRADE_REST_SEND_RESULT_CURRENT, {"success": True}) - - monkeypatch.setattr(DCNM_SEND_INSTALL_OPTIONS, mock_dcnm_send_install_options) - monkeypatch.setattr(DCNM_SEND_ISSU_DETAILS, mock_dcnm_send_issu_details) - monkeypatch.setattr( - instance, - "_wait_for_current_actions_to_complete", - mock_wait_for_current_actions_to_complete, - ) - monkeypatch.setattr( - instance, - "_wait_for_image_upgrade_to_complete", - mock_wait_for_image_upgrade_to_complete, - ) - - instance.devices = [ - { - "policy": "NR3F", - "reboot": False, - "stage": True, - "upgrade": {"nxos": True, "epld": True}, - "options": { - "nxos": {"mode": "force_non_disruptive", "bios_force": False}, - "package": {"install": False, "uninstall": False}, - "epld": {"module": "ALL", "golden": False}, - "reboot": {"config_reload": False, "write_erase": False}, - }, - "validate": True, - "ip_address": "172.22.150.102", - "policy_changed": True, - } - ] - - instance.unit_test = True - instance.commit() - assert instance.payload["issuUpgradeOptions1"]["disruptive"] is False - assert instance.payload["issuUpgradeOptions1"]["forceNonDisruptive"] is True - assert instance.payload["issuUpgradeOptions1"]["nonDisruptive"] is False - - -def test_image_upgrade_upgrade_00024(monkeypatch, image_upgrade) -> None: - """ - Function - - ImageUpgrade.commit - - Test - - Invalid value for options.nxos.bios_force - - Setup: - - ImageUpgrade.devices is set to a list of one dict for a device - to be upgraded - - The methods called by commit are mocked to simulate that the - device has not yet been upgraded to the desired version - - Methods called by commit that wait for current actions, and - image upgrade, to complete are mocked to do nothing - - instance.devices is set to contain invalid value for - options.nxos.bios_force - - Expected results: - - 1. commit calls _build_payload which calls fail_json - """ - instance = image_upgrade - - key = "test_image_upgrade_upgrade_00024a" - - def mock_dcnm_send_install_options(*args, **kwargs) -> Dict[str, Any]: - return responses_image_install_options(key) - - def mock_dcnm_send_issu_details(*args, **kwargs) -> Dict[str, Any]: - return responses_switch_issu_details(key) - - def mock_wait_for_current_actions_to_complete(*args, **kwargs): - pass - - monkeypatch.setattr(DCNM_SEND_INSTALL_OPTIONS, mock_dcnm_send_install_options) - monkeypatch.setattr(DCNM_SEND_ISSU_DETAILS, mock_dcnm_send_issu_details) - monkeypatch.setattr( - instance, - "_wait_for_current_actions_to_complete", - mock_wait_for_current_actions_to_complete, - ) - - instance.devices = [ - { - "policy": "NR3F", - "reboot": False, - "stage": True, - "upgrade": {"nxos": True, "epld": True}, - "options": { - "nxos": {"mode": "disruptive", "bios_force": "FOO"}, - "package": {"install": False, "uninstall": False}, - "epld": {"module": "ALL", "golden": False}, - "reboot": {"config_reload": False, "write_erase": False}, - }, - "validate": True, - "ip_address": "172.22.150.102", - "policy_changed": True, - } - ] - - match = "ImageUpgrade._build_payload_issu_options_2: " - match += r"options.nxos.bios_force must be a boolean. Got FOO\." - with pytest.raises(AnsibleFailJson, match=match): - instance.unit_test = True - instance.commit() - - -def test_image_upgrade_upgrade_00025(monkeypatch, image_upgrade) -> None: - """ - Function - - ImageUpgrade.commit - - Test - - Incompatible values for options.epld.golden and upgrade.nxos - - Setup: - - ImageUpgrade.devices is set to a list of one dict for a device - to be upgraded - - The methods called by commit are mocked to simulate that the - device has not yet been upgraded to the desired version - - Methods called by commit that wait for current actions, and - image upgrade, to complete are mocked to do nothing - - instance.devices is set to contain epld golden True and - upgrade.nxos True. - - Expected results: - - 1. commit calls _build_payload which calls fail_json - """ - instance = image_upgrade - - key = "test_image_upgrade_upgrade_00025a" - - def mock_dcnm_send_install_options(*args, **kwargs) -> Dict[str, Any]: - return responses_image_install_options(key) - - def mock_dcnm_send_issu_details(*args, **kwargs) -> Dict[str, Any]: - return responses_switch_issu_details(key) - - def mock_wait_for_current_actions_to_complete(*args, **kwargs): - pass - - monkeypatch.setattr(DCNM_SEND_INSTALL_OPTIONS, mock_dcnm_send_install_options) - monkeypatch.setattr(DCNM_SEND_ISSU_DETAILS, mock_dcnm_send_issu_details) - monkeypatch.setattr( - instance, - "_wait_for_current_actions_to_complete", - mock_wait_for_current_actions_to_complete, - ) - - instance.devices = [ - { - "policy": "NR3F", - "reboot": False, - "stage": True, - "upgrade": {"nxos": True, "epld": True}, - "options": { - "nxos": {"mode": "disruptive", "bios_force": False}, - "package": {"install": False, "uninstall": False}, - "epld": {"module": "ALL", "golden": True}, - "reboot": {"config_reload": False, "write_erase": False}, - }, - "validate": True, - "ip_address": "172.22.150.102", - "policy_changed": True, - } - ] - - match = "ImageUpgrade._build_payload_epld: Invalid configuration for " - match += "172.22.150.102. If options.epld.golden is True " - match += "all other upgrade options, e.g. upgrade.nxos, " - match += "must be False." - with pytest.raises(AnsibleFailJson, match=match): - instance.unit_test = True - instance.commit() - - -def test_image_upgrade_upgrade_00026(monkeypatch, image_upgrade) -> None: - """ - Function - - ImageUpgrade.commit - - Test - - Invalid value for epld.module - - Setup: - - ImageUpgrade.devices is set to a list of one dict for a device - to be upgraded - - The methods called by commit are mocked to simulate that the - device has not yet been upgraded to the desired version - - Methods called by commit that wait for current actions, and - image upgrade, to complete are mocked to do nothing - - instance.devices is set to contain invalid epld.module - - Expected results: - - 1. commit calls _build_payload which calls fail_json - """ - instance = image_upgrade - - key = "test_image_upgrade_upgrade_00026a" - - def mock_dcnm_send_install_options(*args, **kwargs) -> Dict[str, Any]: - return responses_image_install_options(key) - - def mock_dcnm_send_issu_details(*args, **kwargs) -> Dict[str, Any]: - return responses_switch_issu_details(key) - - def mock_wait_for_current_actions_to_complete(*args, **kwargs): - pass - - monkeypatch.setattr(DCNM_SEND_INSTALL_OPTIONS, mock_dcnm_send_install_options) - monkeypatch.setattr(DCNM_SEND_ISSU_DETAILS, mock_dcnm_send_issu_details) - monkeypatch.setattr( - instance, - "_wait_for_current_actions_to_complete", - mock_wait_for_current_actions_to_complete, - ) - - instance.devices = [ - { - "policy": "NR3F", - "reboot": False, - "stage": True, - "upgrade": {"nxos": True, "epld": True}, - "options": { - "nxos": {"mode": "disruptive", "bios_force": False}, - "package": {"install": False, "uninstall": False}, - "epld": {"module": "FOO", "golden": False}, - "reboot": {"config_reload": False, "write_erase": False}, - }, - "validate": True, - "ip_address": "172.22.150.102", - "policy_changed": True, - } - ] - - match = "ImageUpgrade._build_payload_epld: " - match += "options.epld.module must either be 'ALL' " - match += r"or an integer. Got FOO\." - with pytest.raises(AnsibleFailJson, match=match): - instance.unit_test = True - instance.commit() - - -def test_image_upgrade_upgrade_00027(monkeypatch, image_upgrade) -> None: - """ - Function - - ImageUpgrade.commit - - Test - - Invalid value for epld.golden - - Setup: - - ImageUpgrade.devices is set to a list of one dict for a device - to be upgraded - - The methods called by commit are mocked to simulate that the - device has not yet been upgraded to the desired version - - Methods called by commit that wait for current actions, and - image upgrade, to complete are mocked to do nothing - - instance.devices is set to contain invalid epld.golden - - Expected results: - - 1. commit calls _build_payload which calls fail_json - """ - instance = image_upgrade - - key = "test_image_upgrade_upgrade_00027a" - - def mock_dcnm_send_install_options(*args, **kwargs) -> Dict[str, Any]: - return responses_image_install_options(key) - - def mock_dcnm_send_issu_details(*args, **kwargs) -> Dict[str, Any]: - return responses_switch_issu_details(key) - - def mock_wait_for_current_actions_to_complete(*args, **kwargs): - pass - - monkeypatch.setattr(DCNM_SEND_INSTALL_OPTIONS, mock_dcnm_send_install_options) - monkeypatch.setattr(DCNM_SEND_ISSU_DETAILS, mock_dcnm_send_issu_details) - monkeypatch.setattr( - instance, - "_wait_for_current_actions_to_complete", - mock_wait_for_current_actions_to_complete, - ) - - instance.devices = [ - { - "policy": "NR3F", - "reboot": False, - "stage": True, - "upgrade": {"nxos": True, "epld": True}, - "options": { - "nxos": {"mode": "disruptive", "bios_force": False}, - "package": {"install": False, "uninstall": False}, - "epld": {"module": "ALL", "golden": "FOO"}, - "reboot": {"config_reload": False, "write_erase": False}, - }, - "validate": True, - "ip_address": "172.22.150.102", - "policy_changed": True, - } - ] - - match = "ImageUpgrade._build_payload_epld: " - match += r"options.epld.golden must be a boolean. Got FOO\." - with pytest.raises(AnsibleFailJson, match=match): - instance.unit_test = True - instance.commit() - - -def test_image_upgrade_upgrade_00028(monkeypatch, image_upgrade) -> None: - """ - Function - - ImageUpgrade.commit - - Test - - Invalid value for reboot - - Setup: - - ImageUpgrade.devices is set to a list of one dict for a device - to be upgraded - - The methods called by commit are mocked to simulate that the - device has not yet been upgraded to the desired version - - Methods called by commit that wait for current actions, and - image upgrade, to complete are mocked to do nothing - - instance.devices is set to contain invalid value for reboot - - Expected results: - - 1. commit calls _build_payload which calls fail_json - """ - instance = image_upgrade - - key = "test_image_upgrade_upgrade_00028a" - - def mock_dcnm_send_install_options(*args, **kwargs) -> Dict[str, Any]: - return responses_image_install_options(key) - - def mock_dcnm_send_issu_details(*args, **kwargs) -> Dict[str, Any]: - return responses_switch_issu_details(key) - - def mock_wait_for_current_actions_to_complete(*args, **kwargs): - pass - - monkeypatch.setattr(DCNM_SEND_INSTALL_OPTIONS, mock_dcnm_send_install_options) - monkeypatch.setattr(DCNM_SEND_ISSU_DETAILS, mock_dcnm_send_issu_details) - monkeypatch.setattr( - instance, - "_wait_for_current_actions_to_complete", - mock_wait_for_current_actions_to_complete, - ) - - instance.devices = [ - { - "policy": "NR3F", - "reboot": "FOO", - "stage": True, - "upgrade": {"nxos": True, "epld": True}, - "options": { - "nxos": {"mode": "disruptive", "bios_force": False}, - "package": {"install": False, "uninstall": False}, - "epld": {"module": "ALL", "golden": False}, - "reboot": {"config_reload": False, "write_erase": False}, - }, - "validate": True, - "ip_address": "172.22.150.102", - "policy_changed": True, - } - ] - - match = "ImageUpgrade._build_payload_reboot: " - match += r"reboot must be a boolean. Got FOO\." - with pytest.raises(AnsibleFailJson, match=match): - instance.unit_test = True - instance.commit() - - -def test_image_upgrade_upgrade_00029(monkeypatch, image_upgrade) -> None: - """ - Function - - ImageUpgrade.commit - - Test - - Invalid value for options.reboot.config_reload - - Setup: - - ImageUpgrade.devices is set to a list of one dict for a device - to be upgraded - - The methods called by commit are mocked to simulate that the - device has not yet been upgraded to the desired version - - Methods called by commit that wait for current actions, and - image upgrade, to complete are mocked to do nothing - - instance.devices is set to contain invalid value for - options.reboot.config_reload - - Expected results: - - 1. commit calls _build_payload which calls fail_json - """ - instance = image_upgrade - - key = "test_image_upgrade_upgrade_00029a" - - def mock_dcnm_send_install_options(*args, **kwargs) -> Dict[str, Any]: - return responses_image_install_options(key) - - def mock_dcnm_send_issu_details(*args, **kwargs) -> Dict[str, Any]: - return responses_switch_issu_details(key) - - def mock_wait_for_current_actions_to_complete(*args, **kwargs): - pass - - monkeypatch.setattr(DCNM_SEND_INSTALL_OPTIONS, mock_dcnm_send_install_options) - monkeypatch.setattr(DCNM_SEND_ISSU_DETAILS, mock_dcnm_send_issu_details) - monkeypatch.setattr( - instance, - "_wait_for_current_actions_to_complete", - mock_wait_for_current_actions_to_complete, - ) - - instance.devices = [ - { - "policy": "NR3F", - "reboot": True, - "stage": True, - "upgrade": {"nxos": True, "epld": True}, - "options": { - "nxos": {"mode": "disruptive", "bios_force": False}, - "package": {"install": False, "uninstall": False}, - "epld": {"module": "ALL", "golden": False}, - "reboot": {"config_reload": "FOO", "write_erase": False}, - }, - "validate": True, - "ip_address": "172.22.150.102", - "policy_changed": True, - } - ] - - match = "ImageUpgrade._build_payload_reboot_options: " - match += r"options.reboot.config_reload must be a boolean. Got FOO\." - with pytest.raises(AnsibleFailJson, match=match): - instance.unit_test = True - instance.commit() - - -def test_image_upgrade_upgrade_00030(monkeypatch, image_upgrade) -> None: - """ - Function - - ImageUpgrade.commit - - Test - - Invalid value for options.reboot.write_erase - - Setup: - - ImageUpgrade.devices is set to a list of one dict for a device - to be upgraded - - The methods called by commit are mocked to simulate that the - device has not yet been upgraded to the desired version - - Methods called by commit that wait for current actions, and - image upgrade, to complete are mocked to do nothing - - instance.devices is set to contain invalid value for - options.reboot.write_erase - - Expected results: - - 1. commit calls _build_payload which calls fail_json - """ - instance = image_upgrade - - key = "test_image_upgrade_upgrade_00030a" - - def mock_dcnm_send_install_options(*args, **kwargs) -> Dict[str, Any]: - return responses_image_install_options(key) - - def mock_dcnm_send_issu_details(*args, **kwargs) -> Dict[str, Any]: - return responses_switch_issu_details(key) - - def mock_wait_for_current_actions_to_complete(*args, **kwargs): - pass - - monkeypatch.setattr(DCNM_SEND_INSTALL_OPTIONS, mock_dcnm_send_install_options) - monkeypatch.setattr(DCNM_SEND_ISSU_DETAILS, mock_dcnm_send_issu_details) - monkeypatch.setattr( - instance, - "_wait_for_current_actions_to_complete", - mock_wait_for_current_actions_to_complete, - ) - - instance.devices = [ - { - "policy": "NR3F", - "reboot": True, - "stage": True, - "upgrade": {"nxos": True, "epld": True}, - "options": { - "nxos": {"mode": "disruptive", "bios_force": False}, - "package": {"install": False, "uninstall": False}, - "epld": {"module": "ALL", "golden": False}, - "reboot": {"config_reload": False, "write_erase": "FOO"}, - }, - "validate": True, - "ip_address": "172.22.150.102", - "policy_changed": True, - } - ] - - match = "ImageUpgrade._build_payload_reboot_options: " - match += r"options.reboot.write_erase must be a boolean. Got FOO\." - with pytest.raises(AnsibleFailJson, match=match): - instance.unit_test = True - instance.commit() - - -def test_image_upgrade_upgrade_00031(monkeypatch, image_upgrade) -> None: - """ - Function - - ImageUpgrade.commit - - Test - - Invalid value for options.package.uninstall - - Setup: - - ImageUpgrade.devices is set to a list of one dict for a device - to be upgraded - - The methods called by commit are mocked to simulate that the - device has not yet been upgraded to the desired version - - Methods called by commit that wait for current actions, and - image upgrade, to complete are mocked to do nothing - - instance.devices is set to contain invalid value for - options.package.uninstall - - Expected results: - - 1. commit calls _build_payload which calls fail_json - - NOTES: - 1. The corresponding test for options.package.install is missing. - It's not needed since ImageInstallOptions will call fail_json - on invalid values before ImageUpgrade has a chance to verify - the value. - """ - instance = image_upgrade - - key = "test_image_upgrade_upgrade_00031a" - - def mock_dcnm_send_install_options(*args, **kwargs) -> Dict[str, Any]: - return responses_image_install_options(key) - - def mock_dcnm_send_issu_details(*args, **kwargs) -> Dict[str, Any]: - return responses_switch_issu_details(key) - - def mock_wait_for_current_actions_to_complete(*args, **kwargs): - pass - - monkeypatch.setattr(DCNM_SEND_INSTALL_OPTIONS, mock_dcnm_send_install_options) - monkeypatch.setattr(DCNM_SEND_ISSU_DETAILS, mock_dcnm_send_issu_details) - monkeypatch.setattr( - instance, - "_wait_for_current_actions_to_complete", - mock_wait_for_current_actions_to_complete, - ) - - instance.devices = [ - { - "policy": "NR3F", - "reboot": True, - "stage": True, - "upgrade": {"nxos": True, "epld": True}, - "options": { - "nxos": {"mode": "disruptive", "bios_force": False}, - "package": {"install": False, "uninstall": "FOO"}, - "epld": {"module": "ALL", "golden": False}, - "reboot": {"config_reload": False, "write_erase": False}, - }, - "validate": True, - "ip_address": "172.22.150.102", - "policy_changed": True, - } - ] - - match = "ImageUpgrade._build_payload_package: " - match += r"options.package.uninstall must be a boolean. Got FOO\." - with pytest.raises(AnsibleFailJson, match=match): - instance.unit_test = True - instance.commit() - - -def test_image_upgrade_upgrade_00032(monkeypatch, image_upgrade) -> None: - """ - Function - - ImageUpgrade.commit - - Test - - Bad result code in image upgrade response - - Setup: - - ImageUpgrade.devices is set to a list of one dict for a device - to be upgraded - - The methods called by commit are mocked to simulate that the - device has not yet been upgraded to the desired version - - Methods called by commit that wait for current actions, and - image upgrade, to complete are mocked to do nothing - - ImageUpgrade response (mock_dcnm_send_image_upgrade_commit) is set - to return RESULT_CODE 500 with MESSAGE "Internal Server Error" - - Expected results: - - 1. commit calls fail_json because self.result will not equal "success" - - """ - instance = image_upgrade - - key = "test_image_upgrade_upgrade_00032a" - - def mock_dcnm_send_install_options(*args, **kwargs) -> Dict[str, Any]: - return responses_image_install_options(key) - - def mock_dcnm_send_issu_details(*args, **kwargs) -> Dict[str, Any]: - return responses_switch_issu_details(key) - - def mock_wait_for_current_actions_to_complete(*args, **kwargs): - pass - - def mock_rest_send_image_upgrade(*args, **kwargs) -> Dict[str, Any]: - return responses_image_upgrade(key) - - monkeypatch.setattr( - PATCH_IMAGE_UPGRADE_REST_SEND_COMMIT, mock_rest_send_image_upgrade - ) - monkeypatch.setattr( - PATCH_IMAGE_UPGRADE_REST_SEND_RESPONSE_CURRENT, responses_image_upgrade(key) - ) - monkeypatch.setattr( - PATCH_IMAGE_UPGRADE_REST_SEND_RESULT_CURRENT, - {"success": False, "changed": False}, - ) - - monkeypatch.setattr(DCNM_SEND_INSTALL_OPTIONS, mock_dcnm_send_install_options) - monkeypatch.setattr(DCNM_SEND_ISSU_DETAILS, mock_dcnm_send_issu_details) - monkeypatch.setattr( - instance, - "_wait_for_current_actions_to_complete", - mock_wait_for_current_actions_to_complete, - ) - - instance.devices = [ - { - "policy": "NR3F", - "reboot": False, - "stage": True, - "upgrade": {"nxos": True, "epld": True}, - "options": { - "nxos": {"mode": "disruptive", "bios_force": False}, - "package": {"install": False, "uninstall": False}, - "epld": {"module": "ALL", "golden": False}, - "reboot": {"config_reload": False, "write_erase": False}, - }, - "validate": True, - "ip_address": "172.22.150.102", - "policy_changed": True, - } - ] - - match = "ImageUpgrade.commit_normal_mode: failed: " - match += r"\{'success': False, 'changed': False\}. " - match += r"Controller response: \{'DATA': 123, " - match += "'MESSAGE': 'Internal Server Error', 'METHOD': 'POST', " - match += "'REQUEST_PATH': " - match += "'https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/" - match += "imagemanagement/rest/imageupgrade/upgrade-image', " - match += r"'RETURN_CODE': 500\}" - with pytest.raises(AnsibleFailJson, match=match): - instance.commit() - - -def test_image_upgrade_upgrade_00033(monkeypatch, image_upgrade) -> None: - """ - Function - - ImageUpgrade.commit - - Test - - Invalid value for upgrade.epld - - Setup: - - ImageUpgrade.devices is set to a list of one dict for a device - to be upgraded - - The methods called by commit are mocked to simulate that the - device has not yet been upgraded to the desired version - - Methods called by commit that wait for current actions, and - image upgrade, to complete are mocked to do nothing - - instance.devices is set to contain invalid value for - upgrade.epld - - Expected results: - - 1. commit calls _build_payload which calls fail_json - """ - instance = image_upgrade - - key = "test_image_upgrade_upgrade_00033a" - - def mock_dcnm_send_issu_details(*args, **kwargs) -> Dict[str, Any]: - return responses_switch_issu_details(key) - - def mock_wait_for_current_actions_to_complete(*args, **kwargs): - pass - - monkeypatch.setattr(DCNM_SEND_ISSU_DETAILS, mock_dcnm_send_issu_details) - monkeypatch.setattr( - instance, - "_wait_for_current_actions_to_complete", - mock_wait_for_current_actions_to_complete, - ) - - instance.devices = [ - { - "policy": "NR3F", - "stage": True, - "upgrade": {"nxos": True, "epld": "FOO"}, - "options": { - "package": { - "uninstall": False, - } - }, - "validate": True, - "ip_address": "172.22.150.102", - "policy_changed": True, - } - ] - - match = "ImageInstallOptions.epld: " - match += r"epld must be a boolean value. Got FOO\." - with pytest.raises(AnsibleFailJson, match=match): - instance.unit_test = True - instance.commit() - - -# test getter properties -# check_interval (see test_image_upgrade_upgrade_00070) -# check_timeout (see test_image_upgrade_upgrade_00075) - - -def test_image_upgrade_upgrade_00045(monkeypatch, image_upgrade) -> None: - """ - Function - - ImageUpgrade.commit - - ImageUpgradeCommon.response_data getter - - Setup: - - ImageUpgrade.devices is set to a list of one dict for a device - to be upgraded. - - The methods called by commit are mocked to simulate that the - the image has already been staged and validated and the device - has already been upgraded to the desired version. - - Methods called by commit that wait for current actions, and - image upgrade, to complete are mocked to do nothing. - - - Expected results: - - 1. instance.response_data == 121 - """ - with does_not_raise(): - instance = image_upgrade - - key = "test_image_upgrade_upgrade_00045a" - - def mock_dcnm_send_install_options(*args, **kwargs) -> Dict[str, Any]: - return {} - - def mock_dcnm_send_issu_details(*args, **kwargs) -> Dict[str, Any]: - return responses_switch_issu_details(key) - - def mock_wait_for_current_actions_to_complete(*args, **kwargs): - pass - - def mock_wait_for_image_upgrade_to_complete(*args, **kwargs): - pass - - def mock_rest_send_image_upgrade(*args, **kwargs) -> Dict[str, Any]: - return responses_image_upgrade(key) - - monkeypatch.setattr( - PATCH_IMAGE_UPGRADE_REST_SEND_COMMIT, mock_rest_send_image_upgrade - ) - monkeypatch.setattr( - PATCH_IMAGE_UPGRADE_REST_SEND_RESPONSE_CURRENT, responses_image_upgrade(key) - ) - monkeypatch.setattr( - PATCH_IMAGE_UPGRADE_REST_SEND_RESULT_CURRENT, {"success": True, "changed": True} - ) - - monkeypatch.setattr(DCNM_SEND_INSTALL_OPTIONS, mock_dcnm_send_install_options) - monkeypatch.setattr(DCNM_SEND_ISSU_DETAILS, mock_dcnm_send_issu_details) - monkeypatch.setattr( - instance, - "_wait_for_current_actions_to_complete", - mock_wait_for_current_actions_to_complete, - ) - monkeypatch.setattr( - instance, - "_wait_for_image_upgrade_to_complete", - mock_wait_for_image_upgrade_to_complete, - ) - - instance.devices = [ - { - "policy": "NR3F", - "reboot": False, - "stage": True, - "upgrade": {"nxos": True, "epld": True}, - "options": { - "nxos": {"mode": "disruptive", "bios_force": False}, - "package": {"install": False, "uninstall": False}, - "epld": {"module": "ALL", "golden": False}, - "reboot": {"config_reload": False, "write_erase": False}, - }, - "validate": True, - "ip_address": "172.22.150.102", - "policy_changed": True, - } - ] - with does_not_raise(): - instance.commit() - assert instance.response_data == [121] - - -def test_image_upgrade_upgrade_00046(monkeypatch, image_upgrade) -> None: - """ - Function - - ImageUpgradeCommon.result - - ImageUpgrade.commit - - Setup: - - ImageUpgrade.devices is set to a list of one dict for a device - to be upgraded. - - The methods called by commit are mocked to simulate that the - the image has already been staged and validated and the device - has already been upgraded to the desired version. - - Methods called by commit that wait for current actions, and - image upgrade, to complete are mocked to do nothing. - - - Expected results: - - 1. instance.result is a list: [{'success': True, 'changed': True}] - """ - with does_not_raise(): - instance = image_upgrade - - key = "test_image_upgrade_upgrade_00046a" - - def mock_dcnm_send_install_options(*args, **kwargs) -> Dict[str, Any]: - return {} - - def mock_dcnm_send_issu_details(*args, **kwargs) -> Dict[str, Any]: - return responses_switch_issu_details(key) - - def mock_wait_for_current_actions_to_complete(*args, **kwargs): - pass - - def mock_wait_for_image_upgrade_to_complete(*args, **kwargs): - pass - - def mock_rest_send_image_upgrade(*args, **kwargs) -> Dict[str, Any]: - return responses_image_upgrade(key) - - monkeypatch.setattr( - PATCH_IMAGE_UPGRADE_REST_SEND_COMMIT, mock_rest_send_image_upgrade - ) - monkeypatch.setattr( - PATCH_IMAGE_UPGRADE_REST_SEND_RESULT_CURRENT, {"success": True, "changed": True} - ) - - monkeypatch.setattr(DCNM_SEND_INSTALL_OPTIONS, mock_dcnm_send_install_options) - monkeypatch.setattr(DCNM_SEND_ISSU_DETAILS, mock_dcnm_send_issu_details) - monkeypatch.setattr( - instance, - "_wait_for_current_actions_to_complete", - mock_wait_for_current_actions_to_complete, - ) - monkeypatch.setattr( - instance, - "_wait_for_image_upgrade_to_complete", - mock_wait_for_image_upgrade_to_complete, - ) - - instance.devices = [ - { - "policy": "KR5M", - "reboot": False, - "stage": True, - "upgrade": {"nxos": False, "epld": True}, - "options": { - "nxos": {"mode": "disruptive", "bios_force": True}, - "package": {"install": False, "uninstall": False}, - "epld": {"module": "ALL", "golden": False}, - "reboot": {"config_reload": False, "write_erase": False}, - }, - "validate": True, - "ip_address": "172.22.150.102", - "policy_changed": False, - } - ] - - with does_not_raise(): - instance.unit_test = True - instance.commit() - assert instance.result == [{"success": True, "changed": True}] - - -def test_image_upgrade_upgrade_00047(monkeypatch, image_upgrade) -> None: - """ - Function - - ImageUpgradeCommon.response - - ImageUpgrade.commit - - Setup: - - ImageUpgrade.devices is set to a list of one dict for a device - to be upgraded. - - The methods called by commit are mocked to simulate that the - the image has already been staged and validated and the device - has already been upgraded to the desired version. - - Methods called by commit that wait for current actions, and - image upgrade, to complete are mocked to do nothing. - - - Expected results: - - 1. instance.response is a list - """ - with does_not_raise(): - instance = image_upgrade - - key = "test_image_upgrade_upgrade_00047a" - - def mock_dcnm_send_install_options(*args, **kwargs) -> Dict[str, Any]: - return {} - - def mock_dcnm_send_issu_details(*args, **kwargs) -> Dict[str, Any]: - return responses_switch_issu_details(key) - - def mock_wait_for_current_actions_to_complete(*args, **kwargs): - pass - - def mock_wait_for_image_upgrade_to_complete(*args, **kwargs): - pass - - def mock_rest_send_image_upgrade(*args, **kwargs) -> Dict[str, Any]: - return responses_image_upgrade(key) - - monkeypatch.setattr( - PATCH_IMAGE_UPGRADE_REST_SEND_COMMIT, mock_rest_send_image_upgrade - ) - monkeypatch.setattr( - PATCH_IMAGE_UPGRADE_REST_SEND_RESPONSE_CURRENT, responses_image_upgrade(key) - ) - monkeypatch.setattr( - PATCH_IMAGE_UPGRADE_REST_SEND_RESULT_CURRENT, {"success": True, "changed": True} - ) - - monkeypatch.setattr(DCNM_SEND_INSTALL_OPTIONS, mock_dcnm_send_install_options) - monkeypatch.setattr(DCNM_SEND_ISSU_DETAILS, mock_dcnm_send_issu_details) - monkeypatch.setattr( - instance, - "_wait_for_current_actions_to_complete", - mock_wait_for_current_actions_to_complete, - ) - monkeypatch.setattr( - instance, - "_wait_for_image_upgrade_to_complete", - mock_wait_for_image_upgrade_to_complete, - ) - - instance.devices = [ - { - "policy": "KR5M", - "reboot": False, - "stage": True, - "upgrade": {"nxos": False, "epld": True}, - "options": { - "nxos": {"mode": "disruptive", "bios_force": True}, - "package": {"install": False, "uninstall": False}, - "epld": {"module": "ALL", "golden": False}, - "reboot": {"config_reload": False, "write_erase": False}, - }, - "validate": True, - "ip_address": "172.22.150.102", - "policy_changed": False, - } - ] - - with does_not_raise(): - instance.commit() - assert isinstance(instance.response, list) - assert instance.response[0]["DATA"] == 121 - - -# test setter properties - -MATCH_00060 = "ImageUpgrade.bios_force: instance.bios_force must be a boolean." - - -@pytest.mark.parametrize( - "value, expected, raise_flag", - [ - (True, does_not_raise(), False), - (False, does_not_raise(), False), - ("FOO", pytest.raises(AnsibleFailJson, match=MATCH_00060), True), - ], -) -def test_image_upgrade_upgrade_00060( - image_upgrade, value, expected, raise_flag -) -> None: - """ - Function - - ImageUpgrade.bios_force - - Verify that bios_force does not call fail_json if passed a boolean. - Verify that bios_force does call fail_json if passed a non-boolean. - Verify that the default value is set if fail_json is called. - """ - with does_not_raise(): - instance = image_upgrade - - with expected: - instance.bios_force = value - if raise_flag is False: - assert instance.bios_force == value - else: - assert instance.bios_force is False - - -MATCH_00070 = r"ImageUpgrade\.check_interval: instance\.check_interval " -MATCH_00070 += r"must be an integer\." - - -@pytest.mark.parametrize( - "value, expected, raise_flag", - [ - (1, does_not_raise(), False), - (False, pytest.raises(AnsibleFailJson, match=MATCH_00070), True), - ("FOO", pytest.raises(AnsibleFailJson, match=MATCH_00070), True), - ], -) -def test_image_upgrade_upgrade_00070( - image_upgrade, value, expected, raise_flag -) -> None: - """ - Function - - ImageUpgrade.check_interval - - Summary - Verify that check_interval does not call fail_json if the value is an integer - and does call fail_json if the value is not an integer. Verify that the - default value is set if fail_json is called. - """ - with does_not_raise(): - instance = image_upgrade - with expected: - instance.check_interval = value - if raise_flag is False: - assert instance.check_interval == value - else: - assert instance.check_interval == 10 - - -MATCH_00075 = r"ImageUpgrade\.check_timeout: instance\.check_timeout " -MATCH_00075 += r"must be an integer\." - - -@pytest.mark.parametrize( - "value, expected, raise_flag", - [ - (1, does_not_raise(), False), - (False, pytest.raises(AnsibleFailJson, match=MATCH_00075), True), - ("FOO", pytest.raises(AnsibleFailJson, match=MATCH_00075), True), - ], -) -def test_image_upgrade_upgrade_00075( - image_upgrade, value, expected, raise_flag -) -> None: - """ - Function - - ImageUpgrade.check_timeout - - Summary - Verify that check_timeout does not call fail_json if the value is an integer - and does call fail_json if the value is not an integer. Verify that the - default value is set if fail_json is called. - """ - with does_not_raise(): - instance = image_upgrade - with expected: - instance.check_timeout = value - if raise_flag is False: - assert instance.check_timeout == value - else: - assert instance.check_timeout == 1800 - - -MATCH_00080 = r"ImageUpgrade\.config_reload: " -MATCH_00080 += r"instance\.config_reload must be a boolean\." - - -@pytest.mark.parametrize( - "value, expected, raise_flag", - [ - (True, does_not_raise(), False), - (False, does_not_raise(), False), - ("FOO", pytest.raises(AnsibleFailJson, match=MATCH_00080), True), - ], -) -def test_image_upgrade_upgrade_00080( - image_upgrade, value, expected, raise_flag -) -> None: - """ - Function - - ImageUpgrade.config_reload - - Summary - Verify that config_reload does not call fail_json if passed a boolean. - Verify that config_reload does call fail_json if passed a non-boolean. - Verify that the default value is set if fail_json is called. - """ - with does_not_raise(): - instance = image_upgrade - - with expected: - instance.config_reload = value - if raise_flag is False: - assert instance.config_reload == value - else: - assert instance.config_reload is False - - -MATCH_00090_COMMON = "ImageUpgrade.devices: " -MATCH_00090_COMMON += "instance.devices must be a python list of dict" - -MATCH_00090_FAIL_1 = f"{MATCH_00090_COMMON}. Got not a list." -MATCH_00090_FAIL_2 = rf"{MATCH_00090_COMMON}. Got \['not a dict'\]." - -MATCH_00090_FAIL_3 = f"{MATCH_00090_COMMON}, where each dict contains " -MATCH_00090_FAIL_3 += "the following keys: ip_address. " -MATCH_00090_FAIL_3 += r"Got \[\{'bad_key_ip_address': '192.168.1.1'\}\]." - -DATA_00090_PASS = [{"ip_address": "192.168.1.1"}] -DATA_00090_FAIL_1 = "not a list" -DATA_00090_FAIL_2 = ["not a dict"] -DATA_00090_FAIL_3 = [{"bad_key_ip_address": "192.168.1.1"}] - - -@pytest.mark.parametrize( - "value, expected", - [ - (DATA_00090_PASS, does_not_raise()), - (DATA_00090_FAIL_1, pytest.raises(AnsibleFailJson, match=MATCH_00090_FAIL_1)), - (DATA_00090_FAIL_2, pytest.raises(AnsibleFailJson, match=MATCH_00090_FAIL_2)), - (DATA_00090_FAIL_3, pytest.raises(AnsibleFailJson, match=MATCH_00090_FAIL_3)), - ], -) -def test_image_upgrade_upgrade_00090(image_upgrade, value, expected) -> None: - """ - Function - - ImageUpgrade.devices - - Summary - Verify that devices does not call fail_json if passed a list of dicts - and does call fail_json if passed a non-list or a list of non-dicts. - """ - instance = image_upgrade - - with expected: - instance.devices = value - - -MATCH_00100 = "ImageUpgrade.disruptive: " -MATCH_00100 += "instance.disruptive must be a boolean." - - -@pytest.mark.parametrize( - "value, expected, raise_flag", - [ - (True, does_not_raise(), False), - (False, does_not_raise(), False), - ("FOO", pytest.raises(AnsibleFailJson, match=MATCH_00100), True), - ], -) -def test_image_upgrade_upgrade_00100( - image_upgrade, value, expected, raise_flag -) -> None: - """ - Function - - ImageUpgrade.disruptive - - Summary - Verify that disruptive does not call fail_json if passed a boolean. - Verify that disruptive does call fail_json if passed a non-boolean. - Verify that the default value is set if fail_json is called. - """ - instance = image_upgrade - - with expected: - instance.disruptive = value - if raise_flag is False: - assert instance.disruptive == value - else: - assert instance.disruptive is True - - -MATCH_00110 = "ImageUpgrade.epld_golden: " -MATCH_00110 += "instance.epld_golden must be a boolean." - - -@pytest.mark.parametrize( - "value, expected, raise_flag", - [ - (True, does_not_raise(), False), - (False, does_not_raise(), False), - ("FOO", pytest.raises(AnsibleFailJson, match=MATCH_00110), True), - ], -) -def test_image_upgrade_upgrade_00110( - image_upgrade, value, expected, raise_flag -) -> None: - """ - Function - - ImageUpgrade.epld_golden - - Summary - Verify that epld_golden does not call fail_json if passed a boolean. - Verify that epld_golden does call fail_json if passed a non-boolean. - Verify that the default value is set if fail_json is called. - """ - instance = image_upgrade - - with expected: - instance.epld_golden = value - if raise_flag is False: - assert instance.epld_golden == value - else: - assert instance.epld_golden is False - - -MATCH_00120 = "ImageUpgrade.epld_upgrade: " -MATCH_00120 += "instance.epld_upgrade must be a boolean." - - -@pytest.mark.parametrize( - "value, expected, raise_flag", - [ - (True, does_not_raise(), False), - (False, does_not_raise(), False), - ("FOO", pytest.raises(AnsibleFailJson, match=MATCH_00120), True), - ], -) -def test_image_upgrade_upgrade_00120( - image_upgrade, value, expected, raise_flag -) -> None: - """ - Function - - ImageUpgrade.epld_upgrade - - Summary - Verify that epld_upgrade does not call fail_json if passed a boolean. - Verify that epld_upgrade does call fail_json if passed a non-boolean. - Verify that the default value is set if fail_json is called. - """ - instance = image_upgrade - - with expected: - instance.epld_upgrade = value - if raise_flag is False: - assert instance.epld_upgrade == value - else: - assert instance.epld_upgrade is False - - -MATCH_00130 = "ImageUpgrade.epld_module: " -MATCH_00130 += "instance.epld_module must be an integer or 'ALL'" - - -@pytest.mark.parametrize( - "value, expected, raise_flag", - [ - ("ALL", does_not_raise(), False), - (1, does_not_raise(), False), - (27, does_not_raise(), False), - ("27", does_not_raise(), False), - ("FOO", pytest.raises(AnsibleFailJson, match=MATCH_00130), True), - ], -) -def test_image_upgrade_upgrade_00130( - image_upgrade, value, expected, raise_flag -) -> None: - """ - Function - - ImageUpgrade.epld_module - - Summary - Verify that epld_module does not call fail_json if passed a valid value. - Verify that epld_module does call fail_json if passed an invalid value. - Verify that the default value is set if fail_json is called. - Verify that valid string values are converted to int() - """ - with does_not_raise(): - instance = image_upgrade - with expected: - instance.epld_module = value - if raise_flag is False: - if value == "ALL": - assert instance.epld_module == value - else: - assert instance.epld_module == int(value) - else: - assert instance.epld_module == "ALL" - - -MATCH_00140 = r"ImageUpgrade\.force_non_disruptive: " -MATCH_00140 += r"instance\.force_non_disruptive must be a boolean\." - - -@pytest.mark.parametrize( - "value, expected, raise_flag", - [ - (True, does_not_raise(), False), - (False, does_not_raise(), False), - ("FOO", pytest.raises(AnsibleFailJson, match=MATCH_00140), True), - ], -) -def test_image_upgrade_upgrade_00140( - image_upgrade, value, expected, raise_flag -) -> None: - """ - Function - - ImageUpgrade.force_non_disruptive - - Summary - Verify that force_non_disruptive does not call fail_json if passed a boolean. - Verify that force_non_disruptive does call fail_json if passed a non-boolean. - Verify that the default value is set if fail_json is called. - """ - instance = image_upgrade - - with expected: - instance.force_non_disruptive = value - if raise_flag is False: - assert instance.force_non_disruptive == value - else: - assert instance.force_non_disruptive is False - - -MATCH_00150 = r"ImageUpgrade\.non_disruptive: " -MATCH_00150 += r"instance\.non_disruptive must be a boolean\." - - -@pytest.mark.parametrize( - "value, expected, raise_flag", - [ - (True, does_not_raise(), False), - (False, does_not_raise(), False), - ("FOO", pytest.raises(AnsibleFailJson, match=MATCH_00150), True), - ], -) -def test_image_upgrade_upgrade_00150( - image_upgrade, value, expected, raise_flag -) -> None: - """ - Function - - ImageUpgrade.non_disruptive - - Summary - Verify that non_disruptive does not call fail_json if passed a boolean. - Verify that non_disruptive does call fail_json if passed a non-boolean. - Verify that the default value is set if fail_json is called. - """ - with does_not_raise(): - instance = image_upgrade - with expected: - instance.non_disruptive = value - if raise_flag is False: - assert instance.non_disruptive == value - else: - assert instance.non_disruptive is False - - -MATCH_00160 = r"ImageUpgrade\.package_install: " -MATCH_00160 += r"instance\.package_install must be a boolean\." - - -@pytest.mark.parametrize( - "value, expected, raise_flag", - [ - (True, does_not_raise(), False), - (False, does_not_raise(), False), - ("FOO", pytest.raises(AnsibleFailJson, match=MATCH_00160), True), - ], -) -def test_image_upgrade_upgrade_00160( - image_upgrade, value, expected, raise_flag -) -> None: - """ - Function - - ImageUpgrade.package_install - - Summary - Verify that package_install does not call fail_json if passed a boolean. - Verify that package_install does call fail_json if passed a non-boolean. - Verify that the default value is set if fail_json is called. - """ - with does_not_raise(): - instance = image_upgrade - with expected: - instance.package_install = value - if raise_flag is False: - assert instance.package_install == value - else: - assert instance.package_install is False - - -MATCH_00170 = "ImageUpgrade.package_uninstall: " -MATCH_00170 += "instance.package_uninstall must be a boolean." - - -@pytest.mark.parametrize( - "value, expected, raise_flag", - [ - (True, does_not_raise(), False), - (False, does_not_raise(), False), - ("FOO", pytest.raises(AnsibleFailJson, match=MATCH_00170), True), - ], -) -def test_image_upgrade_upgrade_00170( - image_upgrade, value, expected, raise_flag -) -> None: - """ - Function - - ImageUpgrade.package_uninstall - - Summary - Verify that package_uninstall does not call fail_json if passed a boolean. - Verify that package_uninstall does call fail_json if passed a non-boolean. - Verify that the default value is set if fail_json is called. - """ - with does_not_raise(): - instance = image_upgrade - with expected: - instance.package_uninstall = value - if raise_flag is False: - assert instance.package_uninstall == value - else: - assert instance.package_uninstall is False - - -MATCH_00180 = r"ImageUpgrade\.reboot: " -MATCH_00180 += r"instance\.reboot must be a boolean\." - - -@pytest.mark.parametrize( - "value, expected, raise_flag", - [ - (True, does_not_raise(), False), - (False, does_not_raise(), False), - ("FOO", pytest.raises(AnsibleFailJson, match=MATCH_00180), True), - ], -) -def test_image_upgrade_upgrade_00180( - image_upgrade, value, expected, raise_flag -) -> None: - """ - Function - - ImageUpgrade.reboot - - Summary - Verify that reboot does not call fail_json if passed a boolean. - Verify that reboot does call fail_json if passed a non-boolean. - Verify that the default value is set if fail_json is called. - """ - with does_not_raise(): - instance = image_upgrade - with expected: - instance.reboot = value - if raise_flag is False: - assert instance.reboot == value - else: - assert instance.reboot is False - - -MATCH_00190 = "ImageUpgrade.write_erase: " -MATCH_00190 += "instance.write_erase must be a boolean." - - -@pytest.mark.parametrize( - "value, expected, raise_flag", - [ - (True, does_not_raise(), False), - (False, does_not_raise(), False), - ("FOO", pytest.raises(AnsibleFailJson, match=MATCH_00190), True), - ], -) -def test_image_upgrade_upgrade_00190( - image_upgrade, value, expected, raise_flag -) -> None: - """ - Function - - ImageUpgrade.write_erase - - Summary - Verify that write_erase does not call fail_json if passed a boolean. - Verify that write_erase does call fail_json if passed a non-boolean. - Verify that the default value is set if fail_json is called. - """ - with does_not_raise(): - instance = image_upgrade - with expected: - instance.write_erase = value - if raise_flag is False: - assert instance.write_erase == value - else: - assert instance.write_erase is False - - -def test_image_upgrade_upgrade_00200( - monkeypatch, image_upgrade, issu_details_by_ip_address -) -> None: - """ - Function - - ImageUpgrade._wait_for_current_actions_to_complete - - Test - - Two switches are added to ipv4_done - - Description - _wait_for_current_actions_to_complete waits until staging, validation, - and upgrade actions are complete for all ip addresses. It calls - SwitchIssuDetailsByIpAddress.actions_in_progress() and expects - this to return False. actions_in_progress() returns True until none of - the following keys has a value of "In-Progress": - - ["imageStaged", "upgrade", "validated"] - - Expectations: - 1. instance.ipv4_done is a set() - 2. instance.ipv4_done is length 2 - 3. instance.ipv4_done contains all ip addresses in - instance.ip_addresses - 4. fail_json is not called - """ - - def mock_dcnm_send_issu_details(*args, **kwargs) -> Dict[str, Any]: - key = "test_image_upgrade_upgrade_00200a" - return responses_switch_issu_details(key) - - monkeypatch.setattr(DCNM_SEND_ISSU_DETAILS, mock_dcnm_send_issu_details) - - with does_not_raise(): - instance = image_upgrade - instance.unit_test = True - instance.issu_detail = issu_details_by_ip_address - instance.ip_addresses = [ - "172.22.150.102", - "172.22.150.108", - ] - instance.check_interval = 0 - instance._wait_for_current_actions_to_complete() - assert isinstance(instance.ipv4_done, set) - assert len(instance.ipv4_done) == 2 - assert "172.22.150.102" in instance.ipv4_done - assert "172.22.150.108" in instance.ipv4_done - - -def test_image_upgrade_upgrade_00205( - monkeypatch, image_upgrade, issu_details_by_ip_address -) -> None: - """ - Function - - ImageUpgrade._wait_for_current_actions_to_complete - - Summary - - Verify that ipv4_done contains two ip addresses since - issu_detail is mocked to indicate that no actions are in - progress for either ip address. - - Verify in post analysis that the continue statement is - hit in the for loop that iterates over ip addresses since - one of the ip addresses is manually added to ipv4_done. - - Setup - - Manually add one ip address to ipv4_done - - Set instance.unit_test to True so that instance.ipv4_done is not - initialized to an empty set in _wait_for_current_actions_to_complete - - Description - _wait_for_current_actions_to_complete waits until staging, validation, - and upgrade actions are complete for all ip addresses. It calls - SwitchIssuDetailsByIpAddress.actions_in_progress() and expects - this to return False. actions_in_progress() returns True until none of - the following keys has a value of "In-Progress": - - ["imageStaged", "upgrade", "validated"] - - Expectations: - 1. instance.ipv4_done is a set() - 2. instance.ipv4_done is length 2 - 3. instance.ipv4_done contains all ip addresses in - instance.ip_addresses - 4. fail_json is not called - 5. (Post analysis) converage tool indicates tha the continue - statement is hit. - """ - - def mock_dcnm_send_issu_details(*args, **kwargs) -> Dict[str, Any]: - key = "test_image_upgrade_upgrade_00205a" - return responses_switch_issu_details(key) - - monkeypatch.setattr(DCNM_SEND_ISSU_DETAILS, mock_dcnm_send_issu_details) - - with does_not_raise(): - instance = image_upgrade - instance.unit_test = True - instance.issu_detail = issu_details_by_ip_address - instance.ip_addresses = [ - "172.22.150.102", - "172.22.150.108", - ] - instance.check_interval = 0 - instance.ipv4_done.add("172.22.150.102") - instance._wait_for_current_actions_to_complete() - assert isinstance(instance.ipv4_done, set) - assert len(instance.ipv4_done) == 2 - assert "172.22.150.102" in instance.ipv4_done - assert "172.22.150.108" in instance.ipv4_done - - -def test_image_upgrade_upgrade_00210( - monkeypatch, image_upgrade, issu_details_by_ip_address -) -> None: - """ - Function - - ImageUpgrade._wait_for_current_actions_to_complete - - Test - - one switch is added to ipv4_done - - fail_json is called due to timeout - - See test_image_upgrade_upgrade_00080 for functional details. - - Expectations: - - instance.ipv4_done is a set() - - instance.ipv4_done is length 1 - - instance.ipv4_done contains 172.22.150.102 - - instance.ipv4_done does not contain 172.22.150.108 - - fail_json is called due to timeout - - fail_json error message is matched - """ - - def mock_dcnm_send_issu_details(*args, **kwargs) -> Dict[str, Any]: - key = "test_image_upgrade_upgrade_00210a" - return responses_switch_issu_details(key) - - monkeypatch.setattr(DCNM_SEND_ISSU_DETAILS, mock_dcnm_send_issu_details) - - with does_not_raise(): - instance = image_upgrade - instance.unit_test = True - instance.issu_detail = issu_details_by_ip_address - instance.ip_addresses = [ - "172.22.150.102", - "172.22.150.108", - ] - instance.check_interval = 1 - instance.check_timeout = 1 - - match = "ImageUpgrade._wait_for_current_actions_to_complete: " - match += "Timed out waiting for actions to complete. " - match += r"ipv4_done: 172\.22\.150\.102, " - match += r"ipv4_todo: 172\.22\.150\.102,172\.22\.150\.108\. " - match += r"check the device\(s\) to determine the cause " - match += r"\(e\.g\. show install all status\)\." - with pytest.raises(AnsibleFailJson, match=match): - instance._wait_for_current_actions_to_complete() - assert isinstance(instance.ipv4_done, set) - assert len(instance.ipv4_done) == 1 - assert "172.22.150.102" in instance.ipv4_done - assert "172.22.150.108" not in instance.ipv4_done - - -def test_image_upgrade_upgrade_00220( - monkeypatch, image_upgrade, issu_details_by_ip_address -) -> None: - """ - Function - - ImageUpgrade._wait_for_image_upgrade_to_complete - - Test - - One ip address is added to ipv4_done due to issu_detail.upgrade == "Success" - - fail_json is called due one ip address with issu_detail.upgrade == "Failed" - - Description - _wait_for_image_upgrade_to_complete looks at the upgrade status for each - ip address and waits for it to be "Success" or "Failed". - In the case where all ip addresses are "Success", the module returns. - In the case where any ip address is "Failed", the module calls fail_json. - - Expectations: - - instance.ipv4_done is a set() - - instance.ipv4_done has length 1 - - instance.ipv4_done contains 172.22.150.102, upgrade is "Success" - - Call fail_json on ip address 172.22.150.108, upgrade is "Failed" - """ - - def mock_dcnm_send_issu_details(*args, **kwargs) -> Dict[str, Any]: - key = "test_image_upgrade_upgrade_00220a" - return responses_switch_issu_details(key) - - monkeypatch.setattr(DCNM_SEND_ISSU_DETAILS, mock_dcnm_send_issu_details) - - with does_not_raise(): - instance = image_upgrade - instance.unit_test = True - instance.issu_detail = issu_details_by_ip_address - instance.ip_addresses = [ - "172.22.150.102", - "172.22.150.108", - ] - instance.check_interval = 0 - match = "ImageUpgrade._wait_for_image_upgrade_to_complete: " - match += "Seconds remaining 1800: " - match += "upgrade image Failed for cvd-2313-leaf, FDO2112189M, " - match += r"172\.22\.150\.108, upgrade_percent 50\. " - match += "Check the controller to determine the cause. " - match += "Operations > Image Management > Devices > View Details." - with pytest.raises(AnsibleFailJson, match=match): - instance._wait_for_image_upgrade_to_complete() - assert isinstance(instance.ipv4_done, set) - assert len(instance.ipv4_done) == 1 - assert "172.22.150.102" in instance.ipv4_done - assert "172.22.150.108" not in instance.ipv4_done - - -def test_image_upgrade_upgrade_00230( - monkeypatch, image_upgrade, issu_details_by_ip_address -) -> None: - """ - Function - - ImageUpgrade._wait_for_image_upgrade_to_complete - - Test - - One ip address is added to ipv4_done as - issu_detail.upgrade == "Success" - - fail_json is called due to timeout since one - ip address has value issu_detail.upgrade == "In-Progress" - - Description - _wait_for_image_upgrade_to_complete looks at the upgrade status for each - ip address and waits for it to be "Success" or "Failed". - In the case where all ip addresses are "Success", the module returns. - In the case where any ip address is "Failed", the module calls fail_json. - In the case where any ip address is "In-Progress", the module waits until - timeout is exceeded - - Expectations: - - instance.ipv4_done is a set() - - instance.ipv4_done has length 1 - - instance.ipv4_done contains 172.22.150.102, upgrade is "Success" - - fail_json is called due to timeout exceeded - """ - - def mock_dcnm_send_issu_details(*args, **kwargs) -> Dict[str, Any]: - key = "test_image_upgrade_upgrade_00230a" - return responses_switch_issu_details(key) - - monkeypatch.setattr(DCNM_SEND_ISSU_DETAILS, mock_dcnm_send_issu_details) - - with does_not_raise(): - instance = image_upgrade - instance.unit_test = True - instance.issu_detail = issu_details_by_ip_address - instance.ip_addresses = [ - "172.22.150.102", - "172.22.150.108", - ] - instance.check_interval = 1 - instance.check_timeout = 1 - - match = "ImageUpgrade._wait_for_image_upgrade_to_complete: " - match += r"The following device\(s\) did not complete upgrade: " - match += r"\['172\.22\.150\.108'\]. " - match += "Check the controller to determine the cause. " - match += "Operations > Image Management > Devices > View Details. " - match += r"And/or check the device\(s\) " - match += r"\(e\.g\. show install all status\)\." - with pytest.raises(AnsibleFailJson, match=match): - instance._wait_for_image_upgrade_to_complete() - assert isinstance(instance.ipv4_done, set) - assert len(instance.ipv4_done) == 1 - assert "172.22.150.102" in instance.ipv4_done - assert "172.22.150.108" not in instance.ipv4_done - - -def test_image_upgrade_upgrade_00240( - monkeypatch, image_upgrade, issu_details_by_ip_address -) -> None: - """ - Function - - ImageUpgrade._wait_for_image_upgrade_to_complete - - Summary - Verify that, when two ip addresses are checked, the method's - continue statement is reached. This is verified in post analysis - using the coverage report. - - Setup - - SwitchIssuDetails is mocked to indicate that both ip address - upgrade status == Success - - instance.ipv4_done is set manually to contain one of the ip addresses - - Set instance.unit_test to True so that instance.ipv4_done is not - initialized to an empty set in _wait_for_image_upgrade_to_complete - - Description - _wait_for_image_upgrade_to_complete looks at the upgrade status for each - ip address and waits for it to be "Success" or "Failed". - In the case where all ip addresses are "Success", the module returns. - Since instance.ipv4_done is manually populated with one of the ip addresses, - and instance.unit_test is set to True, the method's continue statement is - reached. This is verified in post analysis using the coverage report. - - Expectations: - - instance.ipv4_done will have length 2 - - instance.ipv4_done contains 172.22.150.102 and 172.22.150.108 - - fail_json is not called - """ - - def mock_dcnm_send_issu_details(*args, **kwargs) -> Dict[str, Any]: - key = "test_image_upgrade_upgrade_00240a" - return responses_switch_issu_details(key) - - monkeypatch.setattr(DCNM_SEND_ISSU_DETAILS, mock_dcnm_send_issu_details) - - with does_not_raise(): - instance = image_upgrade - instance.unit_test = True - instance.issu_detail = issu_details_by_ip_address - instance.ip_addresses = [ - "172.22.150.102", - "172.22.150.108", - ] - instance.check_interval = 1 - instance.check_timeout = 1 - instance.ipv4_done.add("172.22.150.102") - instance._wait_for_image_upgrade_to_complete() - assert isinstance(instance.ipv4_done, set) - assert len(instance.ipv4_done) == 2 - assert "172.22.150.102" in instance.ipv4_done - assert "172.22.150.108" in instance.ipv4_done - - -def test_image_upgrade_upgrade_00250(image_upgrade) -> None: - """ - Function - - ImageUpgrade._build_payload_issu_upgrade - - Summary - Verify that fail_json is called when device.upgrade.nxos is not a boolean - - Setup - - device.upgrade.nxos is set to "FOO" - - device is passed to _build_payload_issu_upgrade - """ - match = r"ImageUpgrade\._build_payload_issu_upgrade: upgrade\.nxos must " - match += r"be a boolean\. Got FOO\." - - device = {"upgrade": {"nxos": "FOO"}} - - with does_not_raise(): - instance = image_upgrade - with pytest.raises(AnsibleFailJson, match=match): - instance._build_payload_issu_upgrade(device) - - -def test_image_upgrade_upgrade_00260(image_upgrade) -> None: - """ - Function - - ImageUpgrade._build_payload_issu_options_1 - - Summary - Verify that fail_json is called when device.options.nxos.mode is - set to an invalid value. - - Setup - - device.options.nxos.mode is set to invalid value "FOO" - - device is passed to _build_payload_issu_options_1 - """ - match = r"ImageUpgrade\._build_payload_issu_options_1: " - match += r"options\.nxos\.mode must be one of.*Got FOO\." - - device = {"options": {"nxos": {"mode": "FOO"}}} - - with does_not_raise(): - instance = image_upgrade - with pytest.raises(AnsibleFailJson, match=match): - instance._build_payload_issu_options_1(device) - - -def test_image_upgrade_upgrade_00270(image_upgrade) -> None: - """ - Function - - ImageUpgrade._build_payload_epld - - Summary - Verify that fail_json is called when device.upgrade.epld is not a boolean - - Setup - - device.upgrade.epld is set to "FOO" - - device is passed to _build_payload_epld - """ - match = r"ImageUpgrade\._build_payload_epld: upgrade.epld must be a " - match += r"boolean\. Got FOO\." - - device = {"upgrade": {"epld": "FOO"}} - - with does_not_raise(): - instance = image_upgrade - with pytest.raises(AnsibleFailJson, match=match): - instance._build_payload_epld(device) - - -def test_image_upgrade_upgrade_00280(image_upgrade) -> None: - """ - Function - - ImageUpgrade._build_payload_package - - Summary - Verify that fail_json is called when device.options.package.install - is not a boolean - - Setup - - device.options.package.install is set to "FOO" - - device is passed to _build_payload_package - """ - match = r"ImageUpgrade\._build_payload_package: options.package.install " - match += r"must be a boolean\. Got FOO\." - - device = {"options": {"package": {"install": "FOO"}}} - - with does_not_raise(): - instance = image_upgrade - with pytest.raises(AnsibleFailJson, match=match): - instance._build_payload_package(device) - - -def test_image_upgrade_upgrade_00281(image_upgrade) -> None: - """ - Function - - ImageUpgrade._build_payload_package - - Summary - Verify that fail_json is called when device.options.package.uninstall - is not a boolean - - Setup - - device.options.package.install is set to a boolean - - device.options.package.uninstall is set to "FOO" - - device is passed to _build_payload_package - """ - match = r"ImageUpgrade\._build_payload_package: options.package.uninstall " - match += r"must be a boolean\. Got FOO\." - - device = {"options": {"package": {"install": True, "uninstall": "FOO"}}} - - with does_not_raise(): - instance = image_upgrade - with pytest.raises(AnsibleFailJson, match=match): - instance._build_payload_package(device) diff --git a/tests/unit/modules/dcnm/dcnm_image_upgrade/test_image_upgrade_image_upgrade_common.py b/tests/unit/modules/dcnm/dcnm_image_upgrade/test_image_upgrade_image_upgrade_common.py deleted file mode 100644 index d6c9c0b19..000000000 --- a/tests/unit/modules/dcnm/dcnm_image_upgrade/test_image_upgrade_image_upgrade_common.py +++ /dev/null @@ -1,831 +0,0 @@ -# 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 - -""" -ImageUpgradeCommon - unit tests -""" - -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 Dict - -import pytest -from ansible_collections.ansible.netcommon.tests.unit.modules.utils import \ - AnsibleFailJson -from ansible_collections.cisco.dcnm.plugins.module_utils.common.log import Log - -from .utils import (does_not_raise, image_upgrade_common_fixture, - responses_image_upgrade_common) - - -def test_image_upgrade_image_upgrade_common_00001(image_upgrade_common) -> None: - """ - Function - - ImageUpgradeCommon.__init__ - - Summary - Verify that instance.params accepts well-formed input and that the - params getter returns the expected value. - - Test - - fail_json is not called - - image_upgrade_common.params is set to the expected value - - All other instance properties are initialized to expected values - """ - test_params = {"config": {"switches": [{"ip_address": "172.22.150.105"}]}} - - with does_not_raise(): - instance = image_upgrade_common - assert instance.params == test_params - assert instance.changed is False - assert instance.response == [] - assert instance.response_current == {} - assert instance.response_data == [] - assert instance.result == [] - assert instance.result_current == {} - assert instance.send_interval == 5 - assert instance.timeout == 300 - assert instance.unit_test is False - - -@pytest.mark.parametrize( - "key, expected", - [ - ( - "test_image_upgrade_image_upgrade_common_00020a", - {"success": True, "changed": True}, - ), - ( - "test_image_upgrade_image_upgrade_common_00020b", - {"success": False, "changed": False}, - ), - ( - "test_image_upgrade_image_upgrade_common_00020c", - {"success": False, "changed": False}, - ), - ], -) -def test_image_upgrade_image_upgrade_common_00020( - image_upgrade_common, key, expected -) -> None: - """ - Function - - ImageUpgradeCommon._handle_response - - Test - - json_fail is not called - - success and changed are returned as expected for DELETE requests - - Description - _handle_reponse() calls either _handle_get_reponse if verb is "GET" or - _handle_post_put_delete_response if verb is "DELETE", "POST", or "PUT" - """ - instance = image_upgrade_common - - data = responses_image_upgrade_common(key) - with does_not_raise(): - result = instance._handle_response( # pylint: disable=protected-access - data.get("response"), data.get("verb") - ) - assert result.get("success") == expected.get("success") - assert result.get("changed") == expected.get("changed") - - -@pytest.mark.parametrize( - "key, expected", - [ - ( - "test_image_upgrade_image_upgrade_common_00030a", - {"success": True, "changed": True}, - ), - ( - "test_image_upgrade_image_upgrade_common_00030b", - {"success": False, "changed": False}, - ), - ( - "test_image_upgrade_image_upgrade_common_00030c", - {"success": False, "changed": False}, - ), - ], -) -def test_image_upgrade_image_upgrade_common_00030( - image_upgrade_common, key, expected -) -> None: - """ - Function - - ImageUpgradeCommon._handle_response - - Test - - json_fail is not called - - success and changed are returned as expected for POST requests - - Description - _handle_reponse() calls either _handle_get_reponse if verb is "GET" or - _handle_post_put_delete_response if verb is "DELETE", "POST", or "PUT" - """ - instance = image_upgrade_common - - data = responses_image_upgrade_common(key) - with does_not_raise(): - result = instance._handle_response( # pylint: disable=protected-access - data.get("response"), data.get("verb") - ) - assert result.get("success") == expected.get("success") - assert result.get("changed") == expected.get("changed") - - -@pytest.mark.parametrize( - "key, expected", - [ - ( - "test_image_upgrade_image_upgrade_common_00040a", - {"success": True, "changed": True}, - ), - ( - "test_image_upgrade_image_upgrade_common_00040b", - {"success": False, "changed": False}, - ), - ( - "test_image_upgrade_image_upgrade_common_00040c", - {"success": False, "changed": False}, - ), - ], -) -def test_image_upgrade_image_upgrade_common_00040( - image_upgrade_common, key, expected -) -> None: - """ - Function - - ImageUpgradeCommon._handle_response - - Test - - json_fail is not called - - success and changed are returned as expected for PUT requests - - Description - _handle_reponse() calls either _handle_get_reponse if verb is "GET" or - _handle_post_put_delete_response if verb is "DELETE", "POST", or "PUT" - """ - instance = image_upgrade_common - - data = responses_image_upgrade_common(key) - with does_not_raise(): - result = instance._handle_response( # pylint: disable=protected-access - data.get("response"), data.get("verb") - ) - assert result.get("success") == expected.get("success") - assert result.get("changed") == expected.get("changed") - - -@pytest.mark.parametrize( - "key, expected", - [ - ( - "test_image_upgrade_image_upgrade_common_00050a", - {"success": True, "found": True}, - ), - ( - "test_image_upgrade_image_upgrade_common_00050b", - {"success": False, "found": False}, - ), - ( - "test_image_upgrade_image_upgrade_common_00050c", - {"success": True, "found": False}, - ), - ( - "test_image_upgrade_image_upgrade_common_00050d", - {"success": False, "found": False}, - ), - ], -) -def test_image_upgrade_image_upgrade_common_00050( - image_upgrade_common, key, expected -) -> None: - """ - Function - - ImageUpgradeCommon._handle_response - - Test - - _handle_reponse returns expected values for GET requests - """ - instance = image_upgrade_common - - data = responses_image_upgrade_common(key) - result = instance._handle_response( # pylint: disable=protected-access - data.get("response"), data.get("verb") - ) - assert result.get("success") == expected.get("success") - assert result.get("changed") == expected.get("changed") - - -def test_image_upgrade_image_upgrade_common_00060(image_upgrade_common) -> None: - """ - Function - - ImageUpgradeCommon._handle_response - - Test - - fail_json is called because an unknown request verb is provided - """ - instance = image_upgrade_common - - data = responses_image_upgrade_common( - "test_image_upgrade_image_upgrade_common_00060a" - ) - with pytest.raises(AnsibleFailJson, match=r"Unknown request verb \(FOO\)"): - instance._handle_response( # pylint: disable=protected-access - data.get("response"), data.get("verb") - ) # pylint: disable=protected-access - - -@pytest.mark.parametrize( - "key, expected", - [ - ( - "test_image_upgrade_image_upgrade_common_00070a", - {"success": True, "found": True}, - ), - ( - "test_image_upgrade_image_upgrade_common_00070b", - {"success": False, "found": False}, - ), - ( - "test_image_upgrade_image_upgrade_common_00070c", - {"success": True, "found": False}, - ), - ( - "test_image_upgrade_image_upgrade_common_00070d", - {"success": False, "found": False}, - ), - ], -) -def test_image_upgrade_image_upgrade_common_00070( - image_upgrade_common, key, expected -) -> None: - """ - Function - - ImageUpgradeCommon._handle_get_response - - Test - - fail_json is not called - - _handle_get_reponse() returns expected values for GET requests - """ - instance = image_upgrade_common - - data = responses_image_upgrade_common(key) - with does_not_raise(): - result = instance._handle_get_response( # pylint: disable=protected-access - data.get("response") - ) # pylint: disable=protected-access - - assert result.get("success") == expected.get("success") - assert result.get("changed") == expected.get("changed") - - -@pytest.mark.parametrize( - "key, expected", - [ - ( - "test_image_upgrade_image_upgrade_common_00080a", - {"success": True, "changed": True}, - ), - ( - "test_image_upgrade_image_upgrade_common_00080b", - {"success": False, "changed": False}, - ), - ( - "test_image_upgrade_image_upgrade_common_00080c", - {"success": False, "changed": False}, - ), - ], -) -def test_image_upgrade_image_upgrade_common_00080( - image_upgrade_common, key, expected -) -> None: - """ - Function - - ImageUpgradeCommon._handle_post_put_delete_response - - Summary - Verify that expected values are returned for POST requests. - - Test - - fail_json is not called - - return expected values for POST requests, when: - - 00080a. MESSAGE == "OK" - - 00080b. MESSAGE != "OK" - - 00080c. MESSAGE field is missing - """ - instance = image_upgrade_common - - data = responses_image_upgrade_common(key) - with does_not_raise(): - result = instance._handle_post_put_delete_response( # pylint: disable=protected-access - data.get("response") - ) - assert result.get("success") == expected.get("success") - assert result.get("changed") == expected.get("changed") - - -@pytest.mark.parametrize( - "key, expected", - [ - ("True", True), - ("true", True), - ("TRUE", True), - ("True", True), - ("False", False), - ("false", False), - ("FALSE", False), - ("False", False), - ("foo", "foo"), - (0, 0), - (1, 1), - (None, None), - (None, None), - ({"foo": 10}, {"foo": 10}), - ([1, 2, "3"], [1, 2, "3"]), - ], -) -def test_image_upgrade_image_upgrade_common_00090( - image_upgrade_common, key, expected -) -> None: - """ - Function - - ImageUpgradeCommon.make_boolean - - Test - - expected values are returned for all cases - """ - instance = image_upgrade_common - assert instance.make_boolean(key) == expected - - -@pytest.mark.parametrize( - "key, expected", - [ - ("", None), - ("none", None), - ("None", None), - ("NONE", None), - ("null", None), - ("Null", None), - ("NULL", None), - ("None", None), - ("foo", "foo"), - (0, 0), - (1, 1), - (True, True), - (False, False), - ({"foo": 10}, {"foo": 10}), - ([1, 2, "3"], [1, 2, "3"]), - ], -) -def test_image_upgrade_image_upgrade_common_00100( - image_upgrade_common, key, expected -) -> None: - """ - Function - - ImageUpgradeCommon.make_none - - Test - - expected values are returned for all cases - """ - instance = image_upgrade_common - assert instance.make_none(key) == expected - - -def test_image_upgrade_image_upgrade_common_00110(image_upgrade_common) -> None: - """ - Function - - ImageUpgradeCommon.log.log_msg - - Test - - log.debug returns None when the base logger is disabled - - Base logger is disabled if Log.config is None (which is the default) - """ - instance = image_upgrade_common - - message = "This is a message" - assert instance.log.debug(message) is None - assert instance.log.info(message) is None - - -def test_image_upgrade_image_upgrade_common_00120( - monkeypatch, image_upgrade_common -) -> None: - """ - Function - - ImageUpgradeCommon.dcnm_send_with_retry - - Summary - Verify that result and response are set to the expected values when - payload is None and the response is successful. - - """ - - def mock_dcnm_send(*args, **kwargs): - return {"MESSAGE": "OK", "RETURN_CODE": 200} - - instance = image_upgrade_common - instance.timeout = 1 - monkeypatch.setattr(instance, "dcnm_send", mock_dcnm_send) - - instance.dcnm_send_with_retry("PUT", "https://foo.bar.com/endpoint", None) - assert instance.response_current == {"MESSAGE": "OK", "RETURN_CODE": 200} - assert instance.result == [{"changed": True, "success": True}] - - -def test_image_upgrade_image_upgrade_common_00121( - monkeypatch, image_upgrade_common -) -> None: - """ - Function - - ImageUpgradeCommon.dcnm_send_with_retry - - Summary - Verify that result and response are set to the expected values when - payload is set and the response is successful. - - """ - - def mock_dcnm_send(*args, **kwargs): - return {"MESSAGE": "OK", "RETURN_CODE": 200} - - with does_not_raise(): - instance = image_upgrade_common - monkeypatch.setattr(instance, "dcnm_send", mock_dcnm_send) - instance.dcnm_send_with_retry( - "PUT", "https://foo.bar.com/endpoint", {"foo": "bar"} - ) - assert instance.response_current == {"MESSAGE": "OK", "RETURN_CODE": 200} - assert instance.result == [{"changed": True, "success": True}] - - -MATCH_00130 = "ImageUpgradeCommon.changed: changed must be a bool." - - -@pytest.mark.parametrize( - "value, expected, raise_flag", - [ - (True, does_not_raise(), False), - (False, does_not_raise(), False), - ("FOO", pytest.raises(AnsibleFailJson, match=MATCH_00130), True), - ], -) -def test_image_upgrade_upgrade_00130( - image_upgrade_common, value, expected, raise_flag -) -> None: - """ - Function - - ImageUpgradeCommon.changed - - Verify that changed does not call fail_json if passed a boolean. - Verify that changed does call fail_json if passed a non-boolean. - Verify that the default value is set if fail_json is called. - """ - with does_not_raise(): - instance = image_upgrade_common - - with expected: - instance.changed = value - if raise_flag is False: - assert instance.changed == value - else: - assert instance.changed is False - - -MATCH_00140 = "ImageUpgradeCommon.diff: diff must be a dict." - - -@pytest.mark.parametrize( - "value, expected, raise_flag", - [ - ({}, does_not_raise(), False), - ({"foo": "bar"}, does_not_raise(), False), - ("FOO", pytest.raises(AnsibleFailJson, match=MATCH_00140), True), - (True, pytest.raises(AnsibleFailJson, match=MATCH_00140), True), - (1, pytest.raises(AnsibleFailJson, match=MATCH_00140), True), - ], -) -def test_image_upgrade_upgrade_00140( - image_upgrade_common, value, expected, raise_flag -) -> None: - """ - Function - - ImageUpgradeCommon.diff - - Verify that diff does not call fail_json if passed a dict. - Verify that diff does call fail_json if passed a non-dict. - Verify that diff returns list(value) when its getter is called. - Verify that the default value ([]) is set if fail_json is called. - """ - with does_not_raise(): - instance = image_upgrade_common - - with expected: - instance.diff = value - if raise_flag is False: - assert instance.diff == [value] - else: - assert instance.diff == [] - - -MATCH_00150 = "ImageUpgradeCommon.failed: failed must be a bool." - - -@pytest.mark.parametrize( - "value, expected, raise_flag", - [ - (True, does_not_raise(), False), - (False, does_not_raise(), False), - ("FOO", pytest.raises(AnsibleFailJson, match=MATCH_00150), True), - ], -) -def test_image_upgrade_upgrade_00150( - image_upgrade_common, value, expected, raise_flag -) -> None: - """ - Function - - ImageUpgradeCommon.failed - - Verify that failed does not call fail_json if passed a boolean. - Verify that failed does call fail_json if passed a non-boolean. - Verify that the default value is set if fail_json is called. - """ - with does_not_raise(): - instance = image_upgrade_common - - with expected: - instance.failed = value - if raise_flag is False: - assert instance.failed == value - else: - assert instance.failed is False - - -MATCH_00160 = "ImageUpgradeCommon.response_current: response_current must be a dict." - - -@pytest.mark.parametrize( - "value, expected, raise_flag", - [ - ({}, does_not_raise(), False), - ({"foo": "bar"}, does_not_raise(), False), - ("FOO", pytest.raises(AnsibleFailJson, match=MATCH_00160), True), - (True, pytest.raises(AnsibleFailJson, match=MATCH_00160), True), - (1, pytest.raises(AnsibleFailJson, match=MATCH_00160), True), - ], -) -def test_image_upgrade_upgrade_00160( - image_upgrade_common, value, expected, raise_flag -) -> None: - """ - Function - - ImageUpgradeCommon.response_current - - Verify that response_current does not call fail_json if passed a dict. - Verify that response_current does call fail_json if passed a non-dict. - Verify that response_current returns value when its getter is called. - Verify that the default value ({}) is set if fail_json is called. - """ - with does_not_raise(): - instance = image_upgrade_common - - with expected: - instance.response_current = value - if raise_flag is False: - assert instance.response_current == value - else: - assert instance.response_current == {} - - -MATCH_00170 = "ImageUpgradeCommon.response: response must be a dict." - - -@pytest.mark.parametrize( - "value, expected, raise_flag", - [ - ({}, does_not_raise(), False), - ({"foo": "bar"}, does_not_raise(), False), - ("FOO", pytest.raises(AnsibleFailJson, match=MATCH_00170), True), - (True, pytest.raises(AnsibleFailJson, match=MATCH_00170), True), - (1, pytest.raises(AnsibleFailJson, match=MATCH_00170), True), - ], -) -def test_image_upgrade_upgrade_00170( - image_upgrade_common, value, expected, raise_flag -) -> None: - """ - Function - - ImageUpgradeCommon.response - - Verify that response does not call fail_json if passed a dict. - Verify that response does call fail_json if passed a non-dict. - Verify that response returns list(value) when its getter is called. - Verify that the default value ([]) is set if fail_json is called. - """ - with does_not_raise(): - instance = image_upgrade_common - - with expected: - instance.response = value - if raise_flag is False: - assert instance.response == [value] - else: - assert instance.response == [] - - -MATCH_00180 = "ImageUpgradeCommon.result: result must be a dict." - - -@pytest.mark.parametrize( - "value, expected, raise_flag", - [ - ({}, does_not_raise(), False), - ({"foo": "bar"}, does_not_raise(), False), - ("FOO", pytest.raises(AnsibleFailJson, match=MATCH_00180), True), - (True, pytest.raises(AnsibleFailJson, match=MATCH_00180), True), - (1, pytest.raises(AnsibleFailJson, match=MATCH_00180), True), - ], -) -def test_image_upgrade_upgrade_00180( - image_upgrade_common, value, expected, raise_flag -) -> None: - """ - Function - - ImageUpgradeCommon.result - - Verify that result does not call fail_json if passed a dict. - Verify that result does call fail_json if passed a non-dict. - Verify that result returns list(value) when its getter is called. - Verify that the default value ([]) is set if fail_json is called. - """ - with does_not_raise(): - instance = image_upgrade_common - - with expected: - instance.result = value - if raise_flag is False: - assert instance.result == [value] - else: - assert instance.result == [] - - -MATCH_00190 = "ImageUpgradeCommon.result_current: result_current must be a dict." - - -@pytest.mark.parametrize( - "value, expected, raise_flag", - [ - ({}, does_not_raise(), False), - ({"foo": "bar"}, does_not_raise(), False), - ("FOO", pytest.raises(AnsibleFailJson, match=MATCH_00190), True), - (True, pytest.raises(AnsibleFailJson, match=MATCH_00190), True), - (1, pytest.raises(AnsibleFailJson, match=MATCH_00190), True), - ], -) -def test_image_upgrade_upgrade_00190( - image_upgrade_common, value, expected, raise_flag -) -> None: - """ - Function - - ImageUpgradeCommon.result_current - - Verify that result_current does not call fail_json if passed a dict. - Verify that result_current does call fail_json if passed a non-dict. - Verify that result_current returns value when its getter is called. - Verify that the default value ({}) is set if fail_json is called. - """ - with does_not_raise(): - instance = image_upgrade_common - - with expected: - instance.result_current = value - if raise_flag is False: - assert instance.result_current == value - else: - assert instance.result_current == {} - - -MATCH_00200 = r"ImageUpgradeCommon\.send_interval: send_interval " -MATCH_00200 += r"must be an integer\." - - -@pytest.mark.parametrize( - "value, expected, raise_flag", - [ - (1, does_not_raise(), False), - (False, pytest.raises(AnsibleFailJson, match=MATCH_00200), True), - ("FOO", pytest.raises(AnsibleFailJson, match=MATCH_00200), True), - ], -) -def test_image_upgrade_upgrade_00200( - image_upgrade_common, value, expected, raise_flag -) -> None: - """ - Function - - ImageUpgrade.send_interval - - Summary - Verify that send_interval does not call fail_json if the value is an integer - and does call fail_json if the value is not an integer. Verify that the - default value is set if fail_json is called. - """ - with does_not_raise(): - instance = image_upgrade_common - with expected: - instance.send_interval = value - if raise_flag is False: - assert instance.send_interval == value - else: - assert instance.send_interval == 5 - - -MATCH_00210 = r"ImageUpgradeCommon\.timeout: timeout " -MATCH_00210 += r"must be an integer\." - - -@pytest.mark.parametrize( - "value, expected, raise_flag", - [ - (1, does_not_raise(), False), - (False, pytest.raises(AnsibleFailJson, match=MATCH_00210), True), - ("FOO", pytest.raises(AnsibleFailJson, match=MATCH_00210), True), - ], -) -def test_image_upgrade_upgrade_00210( - image_upgrade_common, value, expected, raise_flag -) -> None: - """ - Function - - ImageUpgrade.timeout - - Summary - Verify that timeout does not call fail_json if the value is an integer - and does call fail_json if the value is not an integer. Verify that the - default value is set if fail_json is called. - """ - with does_not_raise(): - instance = image_upgrade_common - with expected: - instance.timeout = value - if raise_flag is False: - assert instance.timeout == value - else: - assert instance.timeout == 300 - - -MATCH_00220 = "ImageUpgradeCommon.unit_test: unit_test must be a bool." - - -@pytest.mark.parametrize( - "value, expected, raise_flag", - [ - (True, does_not_raise(), False), - (False, does_not_raise(), False), - ("FOO", pytest.raises(AnsibleFailJson, match=MATCH_00220), True), - ], -) -def test_image_upgrade_upgrade_00220( - image_upgrade_common, value, expected, raise_flag -) -> None: - """ - Function - - ImageUpgradeCommon.unit_test - - Verify that unit_test does not call fail_json if passed a boolean. - Verify that unit_test does call fail_json if passed a non-boolean. - Verify that the default value is set if fail_json is called. - """ - with does_not_raise(): - instance = image_upgrade_common - - with expected: - instance.unit_test = value - if raise_flag is False: - assert instance.unit_test == value - else: - assert instance.unit_test is False diff --git a/tests/unit/modules/dcnm/dcnm_image_upgrade/test_image_upgrade_image_upgrade_task.py b/tests/unit/modules/dcnm/dcnm_image_upgrade/test_image_upgrade_image_upgrade_task.py deleted file mode 100644 index c58ddea42..000000000 --- a/tests/unit/modules/dcnm/dcnm_image_upgrade/test_image_upgrade_image_upgrade_task.py +++ /dev/null @@ -1,823 +0,0 @@ -# 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 -# Some tests require calling protected methods -# pylint: disable=protected-access - -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.image_upgrade.image_policies import \ - ImagePolicies -from ansible_collections.cisco.dcnm.plugins.module_utils.image_upgrade.switch_details import \ - SwitchDetails -from ansible_collections.cisco.dcnm.plugins.modules.dcnm_image_upgrade import \ - ImageUpgradeTask - -from .utils import (MockAnsibleModule, does_not_raise, image_upgrade_fixture, - image_upgrade_task_fixture, - issu_details_by_ip_address_fixture, load_playbook_config, - payloads_image_upgrade, responses_image_install_options, - responses_image_upgrade, responses_switch_issu_details) - -PATCH_MODULE_UTILS = "ansible_collections.cisco.dcnm.plugins.module_utils." -PATCH_IMAGE_UPGRADE = PATCH_MODULE_UTILS + "image_upgrade." - -DCNM_SEND_IMAGE_UPGRADE = PATCH_IMAGE_UPGRADE + "image_upgrade.dcnm_send" -DCNM_SEND_INSTALL_OPTIONS = PATCH_IMAGE_UPGRADE + "install_options.dcnm_send" -DCNM_SEND_ISSU_DETAILS = PATCH_IMAGE_UPGRADE + "switch_issu_details.dcnm_send" - - -@pytest.fixture(name="image_upgrade_task_bare") -def image_upgrade_task_bare_fixture(): - """ - This fixture differs from image_upgrade_task_fixture - in that it does not use a patched MockAnsibleModule. - This is because we need to modify MockAnsibleModule for - some of the test cases below. - """ - return ImageUpgradeTask - - -# def test_image_upgrade_upgrade_task_00001(image_upgrade_task_bare) -> None: -# """ -# Function -# - __init__ - -# Test -# - Class attributes are initialized to expected values -# """ -# instance = image_upgrade_task_bare(MockAnsibleModule) -# assert isinstance(instance, ImageUpgradeTask) -# assert instance.class_name == "ImageUpgradeTask" -# assert instance.have is None -# assert instance.idempotent_want is None -# assert instance.switch_configs == [] -# assert instance.path is None -# assert instance.verb is None -# assert instance.config == {"switches": [{"ip_address": "172.22.150.105"}]} -# assert instance.check_mode is False -# assert instance.validated == {} -# assert instance.want == [] -# assert instance.need == [] -# assert instance.task_result.module_result == { -# "changed": False, -# "diff": { -# "attach_policy": [], -# "detach_policy": [], -# "issu_status": [], -# "stage": [], -# "upgrade": [], -# "validate": [], -# }, -# "response": { -# "attach_policy": [], -# "detach_policy": [], -# "issu_status": [], -# "stage": [], -# "upgrade": [], -# "validate": [], -# }, -# } -# assert isinstance(instance.switch_details, SwitchDetails) -# assert isinstance(instance.image_policies, ImagePolicies) - - -def test_image_upgrade_upgrade_task_00001(image_upgrade_task_bare) -> None: - """ - Function - - __init__ - - Test - - Class attributes are initialized to expected values - """ - instance = image_upgrade_task_bare(MockAnsibleModule) - assert isinstance(instance, ImageUpgradeTask) - assert instance.class_name == "ImageUpgradeTask" - assert instance.have is None - assert instance.idempotent_want is None - assert instance.switch_configs == [] - assert instance.path is None - assert instance.verb is None - assert instance.config == {"switches": [{"ip_address": "172.22.150.105"}]} - assert instance.check_mode is False - assert instance.validated == {} - assert instance.want == [] - assert instance.need == [] - assert instance.task_result.module_result == { - "changed": False, - "diff": [], - "response": [], - } - assert isinstance(instance.switch_details, SwitchDetails) - assert isinstance(instance.image_policies, ImagePolicies) - - -def test_image_upgrade_upgrade_task_00002(image_upgrade_task_bare) -> None: - """ - Function - - __init__ - - Test - - fail_json is called because config is not a dict - """ - match = "ImageUpgradeTask.__init__: expected dict type " - match += "for self.config. got str" - - mock_ansible_module = MockAnsibleModule() - mock_ansible_module.params = {"config": "foo"} - with pytest.raises(AnsibleFailJson, match=match): - instance = image_upgrade_task_bare(mock_ansible_module) - assert isinstance(instance, ImageUpgradeTask) - - -def test_image_upgrade_upgrade_task_00020(monkeypatch, image_upgrade_task) -> None: - """ - Function - - get_have - - Test - - SwitchIssuDetailsByIpAddress attributes are set to expected values - """ - key = "test_image_upgrade_upgrade_task_00020a" - - def mock_dcnm_send_issu_details(*args, **kwargs) -> Dict[str, Any]: - return responses_switch_issu_details(key) - - monkeypatch.setattr(DCNM_SEND_ISSU_DETAILS, mock_dcnm_send_issu_details) - - instance = image_upgrade_task - instance.get_have() - instance.have.filter = "1.1.1.1" - assert instance.have.device_name == "leaf1" - instance.have.filter = "2.2.2.2" - assert instance.have.device_name == "cvd-2313-leaf" - assert instance.have.serial_number == "FDO2112189M" - assert instance.have.fabric == "hard" - - -def test_image_upgrade_upgrade_task_00030(monkeypatch, image_upgrade_task_bare) -> None: - """ - Function - - get_want - - _merge_global_and_switch_configs - - _validate_switch_configs - - Test - - global_config options are all set to default values - (see ImageUpgrade._init_defaults) - - switch_1 does not override any global_config options - so all values will be default - - switch_2 overrides all global_config options - so all values will be non-default - """ - key = "test_image_upgrade_upgrade_task_00030a" - - mock_ansible_module = MockAnsibleModule() - mock_ansible_module.params = load_playbook_config(key) - instance = image_upgrade_task_bare(mock_ansible_module) - instance.get_want() - switch_1 = instance.want[0] - switch_2 = instance.want[1] - assert switch_1.get("ip_address") == "1.1.1.1" - assert switch_1.get("options").get("epld").get("golden") is False - assert switch_1.get("options").get("epld").get("module") == "ALL" - assert switch_1.get("options").get("nxos").get("bios_force") is False - assert switch_1.get("options").get("nxos").get("mode") == "disruptive" - assert switch_1.get("options").get("package").get("install") is False - assert switch_1.get("options").get("package").get("uninstall") is False - assert switch_1.get("options").get("reboot").get("config_reload") is False - assert switch_1.get("options").get("reboot").get("write_erase") is False - assert switch_1.get("policy") == "NR3F" - assert switch_1.get("reboot") is False - assert switch_1.get("stage") is True - assert switch_1.get("upgrade").get("epld") is False - assert switch_1.get("upgrade").get("nxos") is True - assert switch_1.get("validate") is True - - assert switch_2.get("ip_address") == "2.2.2.2" - assert switch_2.get("options").get("epld").get("golden") is True - assert switch_2.get("options").get("epld").get("module") == "1" - assert switch_2.get("options").get("nxos").get("bios_force") is True - assert switch_2.get("options").get("nxos").get("mode") == "non_disruptive" - assert switch_2.get("options").get("package").get("install") is True - assert switch_2.get("options").get("package").get("uninstall") is True - assert switch_2.get("options").get("reboot").get("config_reload") is True - assert switch_2.get("options").get("reboot").get("write_erase") is True - assert switch_2.get("policy") == "NR3F" - assert switch_2.get("reboot") is True - assert switch_2.get("stage") is False - assert switch_2.get("upgrade").get("epld") is True - assert switch_2.get("upgrade").get("nxos") is False - assert switch_2.get("validate") is False - - -def test_image_upgrade_upgrade_task_00031(monkeypatch, image_upgrade_task_bare) -> None: - """ - Function - - get_want - - _merge_global_and_switch_configs - - _validate_switch_configs - - Test - - global_config options are all set to default values - - switch_1 overrides global_config.options.nxos.bios_force - with a default value (False) - - switch_1 overrides global_config.options.nxos.mode - with a non-default value (non_disruptive) - - switch_1 overrides global_config.options.reboot.write_erase - with default value (False) - - switch_1 overrides global_config.reboot with - a default value (False) - - switch_1 overrides global_config.stage with a - non-default value (False) - - switch_1 overrides global_config.validate with a - non-default value (False) - - switch_2 overrides global_config.upgrade.epld - with a non-default value (True) - - All other values for switch_1 and switch_2 are default - """ - key = "test_image_upgrade_upgrade_task_00031a" - - mock_ansible_module = MockAnsibleModule() - mock_ansible_module.params = load_playbook_config(key) - instance = image_upgrade_task_bare(mock_ansible_module) - - instance.get_want() - - switch_1 = instance.want[0] - switch_2 = instance.want[1] - assert switch_1.get("ip_address") == "1.1.1.1" - assert switch_1.get("options").get("epld").get("golden") is False - assert switch_1.get("options").get("epld").get("module") == "ALL" - assert switch_1.get("options").get("nxos").get("bios_force") is False - assert switch_1.get("options").get("nxos").get("mode") == "non_disruptive" - assert switch_1.get("options").get("package").get("install") is False - assert switch_1.get("options").get("package").get("uninstall") is False - assert switch_1.get("options").get("reboot").get("config_reload") is False - assert switch_1.get("options").get("reboot").get("write_erase") is False - assert switch_1.get("policy") == "NR3F" - assert switch_1.get("reboot") is False - assert switch_1.get("stage") is False - assert switch_1.get("upgrade").get("epld") is False - assert switch_1.get("upgrade").get("nxos") is True - assert switch_1.get("validate") is False - - assert switch_2.get("ip_address") == "2.2.2.2" - assert switch_2.get("options").get("epld").get("golden") is False - assert switch_2.get("options").get("epld").get("module") == "ALL" - assert switch_2.get("options").get("nxos").get("bios_force") is False - assert switch_2.get("options").get("nxos").get("mode") == "disruptive" - assert switch_2.get("options").get("package").get("install") is False - assert switch_2.get("options").get("package").get("uninstall") is False - assert switch_2.get("options").get("reboot").get("config_reload") is False - assert switch_2.get("options").get("reboot").get("write_erase") is False - assert switch_2.get("policy") == "NR3F" - assert switch_2.get("reboot") is False - assert switch_2.get("stage") is True - assert switch_2.get("upgrade").get("epld") is True - assert switch_2.get("upgrade").get("nxos") is True - assert switch_2.get("validate") is True - - -def test_image_upgrade_upgrade_task_00040(image_upgrade_task_bare) -> None: - """ - Function - - get_want - - _merge_global_and_switch_configs - - _merge_defaults_to_switch_configs - - Setup - - playbook is initialized with one switch config containing mandatory - keys only, such that all optional (default) keys are populated by - _merge_defaults_to_switch_configs - - Test - - instance.switch_configs contains expected default values - """ - key = "test_image_upgrade_upgrade_task_00040a" - - mock_ansible_module = MockAnsibleModule() - mock_ansible_module.params = load_playbook_config(key) - instance = image_upgrade_task_bare(mock_ansible_module) - - instance.get_want() - - assert instance.switch_configs[0]["reboot"] is False - assert instance.switch_configs[0]["stage"] is True - assert instance.switch_configs[0]["validate"] is True - assert instance.switch_configs[0]["upgrade"]["nxos"] is True - assert instance.switch_configs[0]["upgrade"]["epld"] is False - assert instance.switch_configs[0]["options"]["nxos"]["mode"] == "disruptive" - assert instance.switch_configs[0]["options"]["nxos"]["bios_force"] is False - assert instance.switch_configs[0]["options"]["epld"]["module"] == "ALL" - assert instance.switch_configs[0]["options"]["epld"]["golden"] is False - assert instance.switch_configs[0]["options"]["reboot"]["config_reload"] is False - assert instance.switch_configs[0]["options"]["reboot"]["write_erase"] is False - assert instance.switch_configs[0]["options"]["package"]["install"] is False - assert instance.switch_configs[0]["options"]["package"]["uninstall"] is False - - -def test_image_upgrade_upgrade_task_00041(image_upgrade_task_bare) -> None: - """ - Function - - get_want - - _merge_global_and_switch_configs - - _merge_defaults_to_switch_configs - - Setup - - instance.switch_configs list is initialized with one switch - config containing all mandatory keys, and one optional/default - key (upgrade.nxos) which is set to a non-default value. - - Test - - instance.switch_configs contains expected default values - - instance.switch_configs contains expected non-default values - - """ - key = "test_image_upgrade_upgrade_task_00041a" - - mock_ansible_module = MockAnsibleModule() - mock_ansible_module.params = load_playbook_config(key) - instance = image_upgrade_task_bare(mock_ansible_module) - - instance.get_want() - - assert instance.switch_configs[0]["reboot"] is False - assert instance.switch_configs[0]["stage"] is True - assert instance.switch_configs[0]["validate"] is True - assert instance.switch_configs[0]["upgrade"]["nxos"] is False - assert instance.switch_configs[0]["upgrade"]["epld"] is False - assert instance.switch_configs[0]["options"]["nxos"]["mode"] == "disruptive" - assert instance.switch_configs[0]["options"]["nxos"]["bios_force"] is False - assert instance.switch_configs[0]["options"]["epld"]["module"] == "ALL" - assert instance.switch_configs[0]["options"]["epld"]["golden"] is False - assert instance.switch_configs[0]["options"]["reboot"]["config_reload"] is False - assert instance.switch_configs[0]["options"]["reboot"]["write_erase"] is False - assert instance.switch_configs[0]["options"]["package"]["install"] is False - assert instance.switch_configs[0]["options"]["package"]["uninstall"] is False - - -def test_image_upgrade_upgrade_task_00042(image_upgrade_task_bare) -> None: - """ - Function - - get_want - - _merge_global_and_switch_configs - - _merge_defaults_to_switch_configs - - Setup - - instance.switch_configs list is initialized with one switch - config containing all mandatory keys, and one optional/default - key (upgrade.epld) which is set to a non-default value. - - Test - - instance.switch_configs contains expected default values - - instance.switch_configs contains expected non-default values - - """ - key = "test_image_upgrade_upgrade_task_00042a" - - mock_ansible_module = MockAnsibleModule() - mock_ansible_module.params = load_playbook_config(key) - instance = image_upgrade_task_bare(mock_ansible_module) - - instance.get_want() - - assert instance.switch_configs[0]["reboot"] is False - assert instance.switch_configs[0]["stage"] is True - assert instance.switch_configs[0]["validate"] is True - assert instance.switch_configs[0]["upgrade"]["nxos"] is True - assert instance.switch_configs[0]["upgrade"]["epld"] is True - assert instance.switch_configs[0]["options"]["nxos"]["mode"] == "disruptive" - assert instance.switch_configs[0]["options"]["nxos"]["bios_force"] is False - assert instance.switch_configs[0]["options"]["epld"]["module"] == "ALL" - assert instance.switch_configs[0]["options"]["epld"]["golden"] is False - assert instance.switch_configs[0]["options"]["reboot"]["config_reload"] is False - assert instance.switch_configs[0]["options"]["reboot"]["write_erase"] is False - assert instance.switch_configs[0]["options"]["package"]["install"] is False - assert instance.switch_configs[0]["options"]["package"]["uninstall"] is False - - -def test_image_upgrade_upgrade_task_00043(image_upgrade_task_bare) -> None: - """ - Function - - get_want - - _merge_global_and_switch_configs - - _merge_defaults_to_switch_configs - - Setup - - instance.switch_configs list is initialized with one switch - config containing all mandatory keys, and one optional/default - key (options) which is expected to contain sub-options, but which - is empty. - - Test - - instance.switch_configs contains expected default values - - instance.switch_configs contains expected non-default values - - Description - When options is empty, the default values for all sub-options are added - """ - key = "test_image_upgrade_upgrade_task_00043a" - - mock_ansible_module = MockAnsibleModule() - mock_ansible_module.params = load_playbook_config(key) - instance = image_upgrade_task_bare(mock_ansible_module) - - instance.get_want() - - assert instance.switch_configs[0]["reboot"] is False - assert instance.switch_configs[0]["stage"] is True - assert instance.switch_configs[0]["validate"] is True - assert instance.switch_configs[0]["upgrade"]["nxos"] is True - assert instance.switch_configs[0]["upgrade"]["epld"] is False - assert instance.switch_configs[0]["options"]["nxos"]["mode"] == "disruptive" - assert instance.switch_configs[0]["options"]["nxos"]["bios_force"] is False - assert instance.switch_configs[0]["options"]["epld"]["module"] == "ALL" - assert instance.switch_configs[0]["options"]["epld"]["golden"] is False - assert instance.switch_configs[0]["options"]["reboot"]["config_reload"] is False - assert instance.switch_configs[0]["options"]["reboot"]["write_erase"] is False - assert instance.switch_configs[0]["options"]["package"]["install"] is False - assert instance.switch_configs[0]["options"]["package"]["uninstall"] is False - - -def test_image_upgrade_upgrade_task_00044(image_upgrade_task_bare) -> None: - """ - Function - - get_want - - _merge_global_and_switch_configs - - _merge_defaults_to_switch_configs - - Setup - - instance.switch_configs list is initialized with one switch - config containing all mandatory keys, and one optional/default - key (options.nxos.mode) which is set to a non-default value. - - Test - - Default value for options.nxos.bios_force is added - - instance.switch_configs contains expected default values - - instance.switch_configs contains expected non-default values - - Description - When options.nxos.mode is the only key present in options.nxos, - options.nxos.bios_force sub-option should be added with default value. - """ - key = "test_image_upgrade_upgrade_task_00044a" - - mock_ansible_module = MockAnsibleModule() - mock_ansible_module.params = load_playbook_config(key) - instance = image_upgrade_task_bare(mock_ansible_module) - - instance.get_want() - - assert instance.switch_configs[0]["reboot"] is False - assert instance.switch_configs[0]["stage"] is True - assert instance.switch_configs[0]["validate"] is True - assert instance.switch_configs[0]["upgrade"]["nxos"] is True - assert instance.switch_configs[0]["upgrade"]["epld"] is False - assert instance.switch_configs[0]["options"]["nxos"]["mode"] == "non_disruptive" - assert instance.switch_configs[0]["options"]["nxos"]["bios_force"] is False - assert instance.switch_configs[0]["options"]["epld"]["module"] == "ALL" - assert instance.switch_configs[0]["options"]["epld"]["golden"] is False - assert instance.switch_configs[0]["options"]["reboot"]["config_reload"] is False - assert instance.switch_configs[0]["options"]["reboot"]["write_erase"] is False - assert instance.switch_configs[0]["options"]["package"]["install"] is False - assert instance.switch_configs[0]["options"]["package"]["uninstall"] is False - - -def test_image_upgrade_upgrade_task_00045(image_upgrade_task_bare) -> None: - """ - Function - - get_want - - _merge_global_and_switch_configs - - _merge_defaults_to_switch_configs - - Setup - - instance.switch_configs list is initialized with one switch - config containing all mandatory keys, and one optional/default - key (options.nxos.bios_force) which is set to a non-default value. - - Test - - Default value for options.nxos.mode is added - - instance.switch_configs contains expected default values - - instance.switch_configs contains expected non-default values - - Description - When options.nxos.bios_force is the only key present in options.nxos, - options.nxos.mode sub-option should be added with default value. - """ - key = "test_image_upgrade_upgrade_task_00045a" - - mock_ansible_module = MockAnsibleModule() - mock_ansible_module.params = load_playbook_config(key) - instance = image_upgrade_task_bare(mock_ansible_module) - - instance.get_want() - - assert instance.switch_configs[0]["reboot"] is False - assert instance.switch_configs[0]["stage"] is True - assert instance.switch_configs[0]["validate"] is True - assert instance.switch_configs[0]["upgrade"]["nxos"] is True - assert instance.switch_configs[0]["upgrade"]["epld"] is False - assert instance.switch_configs[0]["options"]["nxos"]["mode"] == "disruptive" - assert instance.switch_configs[0]["options"]["nxos"]["bios_force"] is True - assert instance.switch_configs[0]["options"]["epld"]["module"] == "ALL" - assert instance.switch_configs[0]["options"]["epld"]["golden"] is False - assert instance.switch_configs[0]["options"]["reboot"]["config_reload"] is False - assert instance.switch_configs[0]["options"]["reboot"]["write_erase"] is False - assert instance.switch_configs[0]["options"]["package"]["install"] is False - assert instance.switch_configs[0]["options"]["package"]["uninstall"] is False - - -def test_image_upgrade_upgrade_task_00046(image_upgrade_task_bare) -> None: - """ - Function - - get_want - - _merge_global_and_switch_configs - - _merge_defaults_to_switch_configs - - Setup - - instance.switch_configs list is initialized with one switch - config containing all mandatory keys, and one optional/default - key (options.epld.module) which is set to a non-default value. - - Test - - Default value for options.epld.golden is added - - instance.switch_configs contains expected default values - - instance.switch_configs contains expected non-default values - - Description - When options.epld.module is the only key present in options.epld, - options.epld.golden sub-option should be added with default value. - """ - key = "test_image_upgrade_upgrade_task_00046a" - - mock_ansible_module = MockAnsibleModule() - mock_ansible_module.params = load_playbook_config(key) - instance = image_upgrade_task_bare(mock_ansible_module) - - instance.get_want() - - assert instance.switch_configs[0]["reboot"] is False - assert instance.switch_configs[0]["stage"] is True - assert instance.switch_configs[0]["validate"] is True - assert instance.switch_configs[0]["upgrade"]["nxos"] is True - assert instance.switch_configs[0]["upgrade"]["epld"] is False - assert instance.switch_configs[0]["options"]["nxos"]["mode"] == "disruptive" - assert instance.switch_configs[0]["options"]["nxos"]["bios_force"] is False - assert instance.switch_configs[0]["options"]["epld"]["module"] == "27" - assert instance.switch_configs[0]["options"]["epld"]["golden"] is False - assert instance.switch_configs[0]["options"]["reboot"]["config_reload"] is False - assert instance.switch_configs[0]["options"]["reboot"]["write_erase"] is False - assert instance.switch_configs[0]["options"]["package"]["install"] is False - assert instance.switch_configs[0]["options"]["package"]["uninstall"] is False - - -def test_image_upgrade_upgrade_task_00047(image_upgrade_task_bare) -> None: - """ - Function - - get_want - - _merge_global_and_switch_configs - - _merge_defaults_to_switch_configs - - Setup - - instance.switch_configs list is initialized with one switch - config containing all mandatory keys, and one optional/default - key (options.epld.golden) which is set to a non-default value. - - Test - - Default value for options.epld.module is added - - instance.switch_configs contains expected default values - - instance.switch_configs contains expected non-default values - - Description - When options.epld.golden is the only key present in options.epld, - options.epld.module sub-option should be added with default value. - """ - key = "test_image_upgrade_upgrade_task_00047a" - - mock_ansible_module = MockAnsibleModule() - mock_ansible_module.params = load_playbook_config(key) - instance = image_upgrade_task_bare(mock_ansible_module) - - instance.get_want() - - assert instance.switch_configs[0]["reboot"] is False - assert instance.switch_configs[0]["stage"] is True - assert instance.switch_configs[0]["validate"] is True - assert instance.switch_configs[0]["upgrade"]["nxos"] is True - assert instance.switch_configs[0]["upgrade"]["epld"] is False - assert instance.switch_configs[0]["options"]["nxos"]["mode"] == "disruptive" - assert instance.switch_configs[0]["options"]["nxos"]["bios_force"] is False - assert instance.switch_configs[0]["options"]["epld"]["module"] == "ALL" - assert instance.switch_configs[0]["options"]["epld"]["golden"] is True - assert instance.switch_configs[0]["options"]["reboot"]["config_reload"] is False - assert instance.switch_configs[0]["options"]["reboot"]["write_erase"] is False - assert instance.switch_configs[0]["options"]["package"]["install"] is False - assert instance.switch_configs[0]["options"]["package"]["uninstall"] is False - - -def test_image_upgrade_upgrade_task_00048(image_upgrade_task_bare) -> None: - """ - Function - - get_want - - _merge_global_and_switch_configs - - _merge_defaults_to_switch_configs - - Setup - - instance.switch_configs list is initialized with one switch - config containing all mandatory keys, and one optional/default - key (options.reboot.config_reload) which is set to a non-default - value. - - Test - - Default value for options.reboot.write_erase is added - - instance.switch_configs contains expected default values - - instance.switch_configs contains expected non-default values - - Description - When options.reboot.config_reload is the only key present in options.reboot, - options.reboot.write_erase sub-option should be added with default value. - """ - key = "test_image_upgrade_upgrade_task_00048a" - - mock_ansible_module = MockAnsibleModule() - mock_ansible_module.params = load_playbook_config(key) - instance = image_upgrade_task_bare(mock_ansible_module) - - instance.get_want() - - assert instance.switch_configs[0]["reboot"] is False - assert instance.switch_configs[0]["stage"] is True - assert instance.switch_configs[0]["validate"] is True - assert instance.switch_configs[0]["upgrade"]["nxos"] is True - assert instance.switch_configs[0]["upgrade"]["epld"] is False - assert instance.switch_configs[0]["options"]["nxos"]["mode"] == "disruptive" - assert instance.switch_configs[0]["options"]["nxos"]["bios_force"] is False - assert instance.switch_configs[0]["options"]["epld"]["module"] == "ALL" - assert instance.switch_configs[0]["options"]["epld"]["golden"] is False - assert instance.switch_configs[0]["options"]["reboot"]["config_reload"] is True - assert instance.switch_configs[0]["options"]["reboot"]["write_erase"] is False - assert instance.switch_configs[0]["options"]["package"]["install"] is False - assert instance.switch_configs[0]["options"]["package"]["uninstall"] is False - - -def test_image_upgrade_upgrade_task_00049(image_upgrade_task_bare) -> None: - """ - Function - - get_want - - _merge_global_and_switch_configs - - _merge_defaults_to_switch_configs - - Setup - - instance.switch_configs list is initialized with one switch - config containing all mandatory keys, and one optional/default - key (options.reboot.write_erase) which is set to a non-default - value. - - Test - - Default value for options.reboot.config_reload is added - - instance.switch_configs contains expected default values - - instance.switch_configs contains expected non-default values - - Description - When options.reboot.write_erase is the only key present in options.reboot, - options.reboot.config_reload sub-option should be added with default value. - """ - key = "test_image_upgrade_upgrade_task_00049a" - - mock_ansible_module = MockAnsibleModule() - mock_ansible_module.params = load_playbook_config(key) - instance = image_upgrade_task_bare(mock_ansible_module) - - instance.get_want() - - assert instance.switch_configs[0]["reboot"] is False - assert instance.switch_configs[0]["stage"] is True - assert instance.switch_configs[0]["validate"] is True - assert instance.switch_configs[0]["upgrade"]["nxos"] is True - assert instance.switch_configs[0]["upgrade"]["epld"] is False - assert instance.switch_configs[0]["options"]["nxos"]["mode"] == "disruptive" - assert instance.switch_configs[0]["options"]["nxos"]["bios_force"] is False - assert instance.switch_configs[0]["options"]["epld"]["module"] == "ALL" - assert instance.switch_configs[0]["options"]["epld"]["golden"] is False - assert instance.switch_configs[0]["options"]["reboot"]["config_reload"] is False - assert instance.switch_configs[0]["options"]["reboot"]["write_erase"] is True - assert instance.switch_configs[0]["options"]["package"]["install"] is False - assert instance.switch_configs[0]["options"]["package"]["uninstall"] is False - - -def test_image_upgrade_upgrade_task_00050(image_upgrade_task_bare) -> None: - """ - Function - - get_want - - _merge_global_and_switch_configs - - _merge_defaults_to_switch_configs - - Setup - - instance.switch_configs list is initialized with one switch - config containing all mandatory keys, and one optional/default - key (options.package.install) which is set to a non-default - value. - - Test - - Default value for options.package.uninstall is added - - instance.switch_configs contains expected default values - - instance.switch_configs contains expected non-default values - - Description - When options.package.install is the only key present in options.package, - options.package.uninstall sub-option should be added with default value. - """ - key = "test_image_upgrade_upgrade_task_00050a" - - mock_ansible_module = MockAnsibleModule() - mock_ansible_module.params = load_playbook_config(key) - instance = image_upgrade_task_bare(mock_ansible_module) - - instance.get_want() - - assert instance.switch_configs[0]["reboot"] is False - assert instance.switch_configs[0]["stage"] is True - assert instance.switch_configs[0]["validate"] is True - assert instance.switch_configs[0]["upgrade"]["nxos"] is True - assert instance.switch_configs[0]["upgrade"]["epld"] is False - assert instance.switch_configs[0]["options"]["nxos"]["mode"] == "disruptive" - assert instance.switch_configs[0]["options"]["nxos"]["bios_force"] is False - assert instance.switch_configs[0]["options"]["epld"]["module"] == "ALL" - assert instance.switch_configs[0]["options"]["epld"]["golden"] is False - assert instance.switch_configs[0]["options"]["reboot"]["config_reload"] is False - assert instance.switch_configs[0]["options"]["reboot"]["write_erase"] is False - assert instance.switch_configs[0]["options"]["package"]["install"] is True - assert instance.switch_configs[0]["options"]["package"]["uninstall"] is False - - -def test_image_upgrade_upgrade_task_00051(image_upgrade_task_bare) -> None: - """ - Function - - get_want - - _merge_global_and_switch_configs - - _merge_defaults_to_switch_configs - - Setup - - instance.switch_configs list is initialized with one switch - config containing all mandatory keys, and one optional/default - key (options.package.uninstall) which is set to a non-default - value. - - Test - - Default value for options.package.install is added - - instance.switch_configs contains expected default values - - instance.switch_configs contains expected non-default values - - Description - When options.package.uninstall is the only key present in options.package, - options.package.install sub-option should be added with default value. - """ - key = "test_image_upgrade_upgrade_task_00051a" - - mock_ansible_module = MockAnsibleModule() - mock_ansible_module.params = load_playbook_config(key) - instance = image_upgrade_task_bare(mock_ansible_module) - - instance.get_want() - - assert instance.switch_configs[0]["reboot"] is False - assert instance.switch_configs[0]["stage"] is True - assert instance.switch_configs[0]["validate"] is True - assert instance.switch_configs[0]["upgrade"]["nxos"] is True - assert instance.switch_configs[0]["upgrade"]["epld"] is False - assert instance.switch_configs[0]["options"]["nxos"]["mode"] == "disruptive" - assert instance.switch_configs[0]["options"]["nxos"]["bios_force"] is False - assert instance.switch_configs[0]["options"]["epld"]["module"] == "ALL" - assert instance.switch_configs[0]["options"]["epld"]["golden"] is False - assert instance.switch_configs[0]["options"]["reboot"]["config_reload"] is False - assert instance.switch_configs[0]["options"]["reboot"]["write_erase"] is False - assert instance.switch_configs[0]["options"]["package"]["install"] is False - assert instance.switch_configs[0]["options"]["package"]["uninstall"] is True diff --git a/tests/unit/modules/dcnm/dcnm_image_upgrade/test_image_upgrade_image_upgrade_task_result.py b/tests/unit/modules/dcnm/dcnm_image_upgrade/test_image_upgrade_image_upgrade_task_result.py deleted file mode 100644 index 95da97055..000000000 --- a/tests/unit/modules/dcnm/dcnm_image_upgrade/test_image_upgrade_image_upgrade_task_result.py +++ /dev/null @@ -1,373 +0,0 @@ -# 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 -# Some tests require calling protected methods -# pylint: disable=protected-access - -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.modules.dcnm_image_upgrade import \ - ImageUpgradeTaskResult - -from .utils import does_not_raise, image_upgrade_task_result_fixture - - -def test_image_upgrade_upgrade_task_result_00010(image_upgrade_task_result) -> None: - """ - Function - - ImageUpgradeTaskResult.__init__ - - ImageUpgradeTaskResult._build_properties - - Test - - Class attributes and properties are initialized to expected values - """ - instance = image_upgrade_task_result - assert isinstance(instance, ImageUpgradeTaskResult) - assert instance.class_name == "ImageUpgradeTaskResult" - assert isinstance(instance.diff_properties, dict) - assert instance.diff_attach_policy == [] - assert instance.diff_detach_policy == [] - assert instance.diff_issu_status == [] - assert instance.diff_stage == [] - assert instance.diff_upgrade == [] - assert instance.diff_validate == [] - assert isinstance(instance.response_properties, dict) - assert instance.response_attach_policy == [] - assert instance.response_detach_policy == [] - assert instance.response_issu_status == [] - assert instance.response_stage == [] - assert instance.response_upgrade == [] - assert instance.response_validate == [] - assert isinstance(instance.properties, dict) - - -@pytest.mark.parametrize( - "state, return_value", - [ - ("no_change", False), - ("attach_policy", True), - ("detach_policy", True), - ("issu_status", False), - ("stage", True), - ("upgrade", True), - ("validate", True), - ], -) -def test_image_upgrade_upgrade_task_result_00020( - image_upgrade_task_result, state, return_value -) -> None: - """ - Function - - ImageUpgradeTaskResult.__init__ - - ImageUpgradeTaskResult.did_anything_change - - Summary - Verify that did_anything_change: - - returns False when no changes have been made - - returns True when changes have been made to attach_policy, - detach_policy, stage, upgrade, or validate - - returns False when changes have been made to issu_status - """ - diff = {"diff": "diff"} - with does_not_raise(): - instance = image_upgrade_task_result - if state == "attach_policy": - instance.diff_attach_policy = diff - elif state == "detach_policy": - instance.diff_detach_policy = diff - elif state == "issu_status": - instance.diff_issu_status = diff - elif state == "stage": - instance.diff_stage = diff - elif state == "upgrade": - instance.diff_upgrade = diff - elif state == "validate": - instance.diff_validate = diff - elif state == "no_change": - pass - assert instance.did_anything_change() == return_value - - -MATCH_00030 = r"ImageUpgradeTaskResult\._verify_is_dict: value must be a dict\." - - -@pytest.mark.parametrize( - "value, expected", - [ - ({}, does_not_raise()), - ("not a dict", pytest.raises(AnsibleFailJson, match=MATCH_00030)), - ], -) -def test_image_upgrade_upgrade_task_result_00030( - image_upgrade_task_result, value, expected -) -> None: - """ - Function - - ImageUpgradeTaskResult._verify_is_dict - - Summary - Verify that _verify_is_dict does not call fail_json when value is a dict - and does call fail_json value is not a dict. - """ - with does_not_raise(): - instance = image_upgrade_task_result - with expected: - instance._verify_is_dict(value) - - -def test_image_upgrade_upgrade_task_result_00040(image_upgrade_task_result) -> None: - """ - Function - - ImageUpgradeTaskResult.failed_result - - Summary - Verify that failed_result returns a dict with expected values - """ - test_diff_keys = [ - "diff_attach_policy", - "diff_detach_policy", - "diff_issu_status", - "diff_stage", - "diff_upgrade", - "diff_validate", - ] - test_response_keys = [ - "response_attach_policy", - "response_detach_policy", - "response_issu_status", - "response_stage", - "response_upgrade", - "response_validate", - ] - with does_not_raise(): - instance = image_upgrade_task_result - result = instance.failed_result - assert isinstance(result, dict) - assert result["changed"] is False - assert result["failed"] is True - for key in test_diff_keys: - assert result["diff"][key] == [] - for key in test_response_keys: - assert result["response"][key] == [] - - -def test_image_upgrade_upgrade_task_result_00050(image_upgrade_task_result) -> None: - """ - Function - - ImageUpgradeTaskResult.module_result - - Summary - Verify that module_result returns a dict with expected values when - no changes have been made. - """ - test_keys = [ - "attach_policy", - "detach_policy", - "issu_status", - "stage", - "upgrade", - "validate", - ] - with does_not_raise(): - instance = image_upgrade_task_result - result = instance.module_result - assert isinstance(result, dict) - assert result["changed"] is False - assert result["diff"] == [] - assert result["response"] == [] - - -# REMOVING DUE TO CHANGES IN RESULT STRUCTURE -# @pytest.mark.parametrize( -# "state, changed", -# [ -# ("attach_policy", True), -# ("detach_policy", True), -# ("issu_status", False), -# ("stage", True), -# ("upgrade", True), -# ("validate", True), -# ], -# ) -# def test_image_upgrade_upgrade_task_result_00051( -# image_upgrade_task_result, state, changed -# ) -> None: -# """ -# Function -# - ImageUpgradeTaskResult.module_result -# - ImageUpgradeTaskResult.did_anything_change -# - ImageUpgradeTaskResult.diff_* -# - ImageUpgradeTaskResult.response_* - -# Summary -# Verify that module_result returns a dict with expected values when -# changes have been made to each of the supported states. - -# Test -# - For non-query-state properties, "changed" should be True -# - The diff should be a list containing the dict passed to -# the state's diff property (e.g. diff_stage, diff_issu_status, etc) -# - The response should be a list containing the dict passed to -# the state's response property (e.g. response_stage, response_issu_status, etc) -# - All other diffs should be empty lists -# - All other responses should be empty lists -# """ -# test_key = state -# test_keys = [ -# "attach_policy", -# "detach_policy", -# "issu_status", -# "stage", -# "upgrade", -# "validate", -# ] -# diff = {"diff": "diff"} -# response = {"response": "response"} - -# with does_not_raise(): -# instance = image_upgrade_task_result -# if state == "attach_policy": -# instance.diff_attach_policy = diff -# instance.response_attach_policy = response -# elif state == "detach_policy": -# instance.diff_detach_policy = diff -# instance.response_detach_policy = response -# elif state == "issu_status": -# instance.diff_issu_status = diff -# instance.response_issu_status = response -# elif state == "stage": -# instance.diff_stage = diff -# instance.response_stage = response -# elif state == "upgrade": -# instance.diff_upgrade = diff -# instance.response_upgrade = response -# elif state == "validate": -# instance.diff_validate = diff -# instance.response_validate = response -# result = instance.module_result -# assert isinstance(result, dict) -# assert result["changed"] == changed -# for key in test_keys: -# if key == test_key: -# assert result["diff"][key] == [diff] -# assert result["response"][key] == [response] -# else: -# assert result["diff"][key] == [] -# assert result["response"][key] == [] - - -@pytest.mark.parametrize( - "state", - [ - ("attach_policy"), - ("detach_policy"), - ("issu_status"), - ("stage"), - ("upgrade"), - ("validate"), - ], -) -def test_image_upgrade_upgrade_task_result_00060( - image_upgrade_task_result, state -) -> None: - """ - Function - - ImageUpgradeTaskResult.module_result - - ImageUpgradeTaskResult.did_anything_change - - ImageUpgradeTaskResult.diff_* - - Summary - Verify that fail_json is called by instance.diff_* when the diff - is not a dict. - """ - diff = "not a dict" - match = r"ImageUpgradeTaskResult\._verify_is_dict: value must be a dict\." - with does_not_raise(): - instance = image_upgrade_task_result - with pytest.raises(AnsibleFailJson, match=match): - if state == "attach_policy": - instance.diff_attach_policy = diff - elif state == "detach_policy": - instance.diff_detach_policy = diff - elif state == "issu_status": - instance.diff_issu_status = diff - elif state == "stage": - instance.diff_stage = diff - elif state == "upgrade": - instance.diff_upgrade = diff - elif state == "validate": - instance.diff_validate = diff - else: - pass - - -@pytest.mark.parametrize( - "state", - [ - ("attach_policy"), - ("detach_policy"), - ("issu_status"), - ("stage"), - ("upgrade"), - ("validate"), - ], -) -def test_image_upgrade_upgrade_task_result_00070( - image_upgrade_task_result, state -) -> None: - """ - Function - - ImageUpgradeTaskResult.module_result - - ImageUpgradeTaskResult.did_anything_change - - ImageUpgradeTaskResult.response_* - - Summary - Verify that fail_json is called by instance.response_* when the response - is not a dict. - """ - response = "not a dict" - match = r"ImageUpgradeTaskResult\._verify_is_dict: value must be a dict\." - with does_not_raise(): - instance = image_upgrade_task_result - with pytest.raises(AnsibleFailJson, match=match): - if state == "attach_policy": - instance.response_attach_policy = response - elif state == "detach_policy": - instance.response_detach_policy = response - elif state == "issu_status": - instance.response_issu_status = response - elif state == "stage": - instance.response_stage = response - elif state == "upgrade": - instance.response_upgrade = response - elif state == "validate": - instance.response_validate = response - else: - pass diff --git a/tests/unit/modules/dcnm/dcnm_image_upgrade/test_image_upgrade_image_validate.py b/tests/unit/modules/dcnm/dcnm_image_upgrade/test_image_upgrade_image_validate.py deleted file mode 100644 index 9f6a9ee80..000000000 --- a/tests/unit/modules/dcnm/dcnm_image_upgrade/test_image_upgrade_image_validate.py +++ /dev/null @@ -1,660 +0,0 @@ -# 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 -# Some tests require calling protected methods -# pylint: disable=protected-access - -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.image_upgrade.api_endpoints import \ - ApiEndpoints -from ansible_collections.cisco.dcnm.plugins.module_utils.image_upgrade.switch_issu_details import \ - SwitchIssuDetailsBySerialNumber - -from .utils import (does_not_raise, image_validate_fixture, - issu_details_by_serial_number_fixture, - responses_image_validate, responses_switch_issu_details) - -PATCH_MODULE_UTILS = "ansible_collections.cisco.dcnm.plugins.module_utils." -PATCH_IMAGE_UPGRADE = PATCH_MODULE_UTILS + "image_upgrade." -DCNM_SEND_IMAGE_VALIDATE = PATCH_IMAGE_UPGRADE + "image_validate.dcnm_send" -DCNM_SEND_ISSU_DETAILS = PATCH_IMAGE_UPGRADE + "switch_issu_details.dcnm_send" -PATCH_IMAGE_VALIDATE_REST_SEND_COMMIT = ( - PATCH_IMAGE_UPGRADE + "image_validate.RestSend.commit" -) -PATCH_IMAGE_VALIDATE_REST_SEND_RESULT_CURRENT = ( - PATCH_IMAGE_UPGRADE + "image_validate.RestSend.result_current" -) - - -def test_image_upgrade_validate_00001(image_validate) -> None: - """ - Function - - __init__ - - Test - - Class attributes are initialized to expected values - """ - instance = image_validate - assert instance.class_name == "ImageValidate" - assert isinstance(instance.endpoints, ApiEndpoints) - assert isinstance(instance.issu_detail, SwitchIssuDetailsBySerialNumber) - assert isinstance(instance.serial_numbers_done, set) - assert ( - instance.path - == "/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/stagingmanagement/validate-image" - ) - assert instance.verb == "POST" - - -def test_image_upgrade_validate_00002(image_validate) -> None: - """ - Function - - _init_properties - - Test - - Class properties are initialized to expected values - """ - instance = image_validate - assert isinstance(instance.properties, dict) - assert instance.properties.get("check_interval") == 10 - assert instance.properties.get("check_timeout") == 1800 - assert instance.properties.get("response_data") == [] - assert instance.properties.get("response") == [] - assert instance.properties.get("result") == [] - assert instance.properties.get("non_disruptive") is False - assert instance.properties.get("serial_numbers") == [] - - -def test_image_upgrade_validate_00003( - monkeypatch, image_validate, issu_details_by_serial_number -) -> None: - """ - Function - - prune_serial_numbers - - Test - - instance.serial_numbers contains only serial numbers for which - "validated" == "none" - - serial_numbers does not contain serial numbers for which - "validated" == "Success" - - Description - prune_serial_numbers removes serial numbers from the list for which - "validated" == "Success" (TODO: AND policy == ) - - Expected results: - 1. instance.serial_numbers == ["FDO2112189M", "FDO211218AX", "FDO211218B5"] - 2. instance.serial_numbers != ["FDO211218FV", "FDO211218GC"] - """ - instance = image_validate - - def mock_dcnm_send_issu_details(*args) -> Dict[str, Any]: - key = "test_image_upgrade_validate_00003a" - return responses_switch_issu_details(key) - - monkeypatch.setattr(DCNM_SEND_ISSU_DETAILS, mock_dcnm_send_issu_details) - - instance.issu_detail = issu_details_by_serial_number - instance.serial_numbers = [ - "FDO2112189M", - "FDO211218AX", - "FDO211218B5", - "FDO211218FV", - "FDO211218GC", - ] - instance.prune_serial_numbers() - assert isinstance(instance.serial_numbers, list) - assert len(instance.serial_numbers) == 3 - assert "FDO2112189M" in instance.serial_numbers - assert "FDO211218AX" in instance.serial_numbers - assert "FDO211218B5" in instance.serial_numbers - assert "FDO211218FV" not in instance.serial_numbers - assert "FDO211218GC" not in instance.serial_numbers - - -def test_image_upgrade_validate_00004( - monkeypatch, image_validate, issu_details_by_serial_number -) -> None: - """ - Function - - validate_serial_numbers - - Test - - fail_json is called when imageStaged == "Failed". - - fail_json error message is matched - - Expectations: - - FDO21120U5D should pass since validated == "Success" - FDO2112189M should fail since validated == "Failed" - """ - instance = image_validate - - def mock_dcnm_send_issu_details(*args) -> Dict[str, Any]: - key = "test_image_upgrade_validate_00004a" - return responses_switch_issu_details(key) - - monkeypatch.setattr(DCNM_SEND_ISSU_DETAILS, mock_dcnm_send_issu_details) - - instance.issu_detail = issu_details_by_serial_number - instance.serial_numbers = ["FDO21120U5D", "FDO2112189M"] - - match = "ImageValidate.validate_serial_numbers: " - match += "image validation is failing for the following switch: " - match += "cvd-2313-leaf, 172.22.150.108, FDO2112189M. If this " - match += "persists, check the switch connectivity to the " - match += "controller and try again." - - with pytest.raises(AnsibleFailJson, match=match): - instance.validate_serial_numbers() - - -def test_image_upgrade_validate_00005( - monkeypatch, image_validate, issu_details_by_serial_number -) -> None: - """ - Function - - _wait_for_image_validate_to_complete - - Test - - serial_numbers_done is a set() - - serial_numbers_done has length 2 - - serial_numbers_done contains all serial numbers in instance.serial_numbers - - fail_json is not called - - Description - _wait_for_image_validate_to_complete looks at the "validated" status for each - serial number and waits for it to be "Success" or "Failed". - In the case where all serial numbers are "Success", the module returns. - In the case where any serial number is "Failed", the module calls fail_json. - """ - - def mock_dcnm_send_issu_details(*args) -> Dict[str, Any]: - key = "test_image_upgrade_validate_00005a" - return responses_switch_issu_details(key) - - monkeypatch.setattr(DCNM_SEND_ISSU_DETAILS, mock_dcnm_send_issu_details) - - with does_not_raise(): - instance = image_validate - instance.unit_test = True - instance.issu_detail = issu_details_by_serial_number - instance.serial_numbers = [ - "FDO21120U5D", - "FDO2112189M", - ] - instance._wait_for_image_validate_to_complete() - assert isinstance(instance.serial_numbers_done, set) - assert len(instance.serial_numbers_done) == 2 - assert "FDO21120U5D" in instance.serial_numbers_done - assert "FDO2112189M" in instance.serial_numbers_done - - -def test_image_upgrade_validate_00006( - monkeypatch, image_validate, issu_details_by_serial_number -) -> None: - """ - Function - - _wait_for_image_validate_to_complete - - Test - - serial_numbers_done is a set() - - serial_numbers_done has length 1 - - serial_numbers_done contains FDO21120U5D since "validated" == "Success" - - serial_numbers_done does not contain FDO2112189M since "validated" == "Failed" - - fail_json is called - - fail_json error message is matched - - Description - _wait_for_image_validate_to_complete looks at the "validated" status for each - serial number and waits for it to be "Success" or "Failed". - In the case where all serial numbers are "Success", the module returns. - In the case where any serial number is "Failed", the module calls fail_json. - """ - - def mock_dcnm_send_issu_details(*args) -> Dict[str, Any]: - key = "test_image_upgrade_validate_00006a" - return responses_switch_issu_details(key) - - monkeypatch.setattr(DCNM_SEND_ISSU_DETAILS, mock_dcnm_send_issu_details) - - with does_not_raise(): - instance = image_validate - instance.unit_test = True - instance.issu_detail = issu_details_by_serial_number - instance.serial_numbers = [ - "FDO21120U5D", - "FDO2112189M", - ] - - match = "Seconds remaining 1790: validate image Failed for " - match += "cvd-2313-leaf, 172.22.150.108, FDO2112189M, " - match += "image validated percent: 100. Check the switch e.g. " - match += "show install log detail, show incompatibility-all nxos " - match += ". Or check Operations > Image Management > " - match += "Devices > View Details > Validate on the controller " - match += "GUI for more details." - - with pytest.raises(AnsibleFailJson, match=match): - instance._wait_for_image_validate_to_complete() # pylint: disable=protected-access - assert isinstance(instance.serial_numbers_done, set) - assert len(instance.serial_numbers_done) == 1 - assert "FDO21120U5D" in instance.serial_numbers_done - assert "FDO2112189M" not in instance.serial_numbers_done - - -def test_image_upgrade_validate_00007( - monkeypatch, image_validate, issu_details_by_serial_number -) -> None: - """ - Function - - _wait_for_image_validate_to_complete - - Test - - serial_numbers_done is a set() - - serial_numbers_done has length 1 - - serial_numbers_done contains FDO21120U5D since "validated" == "Success" - - serial_numbers_done does not contain FDO2112189M since "validated" == "In-Progress" - - fail_json is called due to timeout - - fail_json error message is matched - - Description - See test_wait_for_image_stage_to_complete for functional details. - """ - - def mock_dcnm_send_issu_details(*args) -> Dict[str, Any]: - key = "test_image_upgrade_validate_00007a" - return responses_switch_issu_details(key) - - monkeypatch.setattr(DCNM_SEND_ISSU_DETAILS, mock_dcnm_send_issu_details) - - with does_not_raise(): - instance = image_validate - instance.unit_test = True - instance.issu_detail = issu_details_by_serial_number - instance.serial_numbers = [ - "FDO21120U5D", - "FDO2112189M", - ] - instance.check_interval = 1 - instance.check_timeout = 1 - - match = "ImageValidate._wait_for_image_validate_to_complete: " - match += "Timed out waiting for image validation to complete. " - match += "serial_numbers_done: FDO21120U5D, " - match += "serial_numbers_todo: FDO21120U5D,FDO2112189M" - - with pytest.raises(AnsibleFailJson, match=match): - instance._wait_for_image_validate_to_complete() # pylint: disable=protected-access - assert isinstance(instance.serial_numbers_done, set) - assert len(instance.serial_numbers_done) == 1 - assert "FDO21120U5D" in instance.serial_numbers_done - assert "FDO2112189M" not in instance.serial_numbers_done - - -def test_image_upgrade_validate_00008( - monkeypatch, image_validate, issu_details_by_serial_number -) -> None: - """ - Function - - _wait_for_current_actions_to_complete - - Test - - serial_numbers_done is a set() - - serial_numbers_done has length 2 - - serial_numbers_done contains all serial numbers in - serial_numbers - - fail_json is not called - - Description - _wait_for_current_actions_to_complete waits until staging, validation, - and upgrade actions are complete for all serial numbers. It calls - SwitchIssuDetailsBySerialNumber.actions_in_progress() and expects - this to return False. actions_in_progress() returns True until none of - the following keys has a value of "In-Progress": - - ["imageStaged", "upgrade", "validated"] - """ - - def mock_dcnm_send_issu_details(*args) -> Dict[str, Any]: - key = "test_image_upgrade_validate_00008a" - return responses_switch_issu_details(key) - - monkeypatch.setattr(DCNM_SEND_ISSU_DETAILS, mock_dcnm_send_issu_details) - - with does_not_raise(): - instance = image_validate - instance.unit_test = True - instance.issu_detail = issu_details_by_serial_number - instance.serial_numbers = [ - "FDO21120U5D", - "FDO2112189M", - ] - instance._wait_for_current_actions_to_complete() # pylint: disable=protected-access - assert isinstance(instance.serial_numbers_done, set) - assert len(instance.serial_numbers_done) == 2 - assert "FDO21120U5D" in instance.serial_numbers_done - assert "FDO2112189M" in instance.serial_numbers_done - - -def test_image_upgrade_validate_00009( - monkeypatch, image_validate, issu_details_by_serial_number -) -> None: - """ - Function - - _wait_for_current_actions_to_complete - - Test - - serial_numbers_done is a set() - - serial_numbers_done has length 1 - - serial_numbers_done contains FDO21120U5D since "validated" == "Success" - - serial_numbers_done does not contain FDO2112189M since "validated" == "In-Progress" - - fail_json is called due to timeout - - fail_json error message is matched - - Description - See test_wait_for_current_actions_to_complete for functional details. - """ - - def mock_dcnm_send_issu_details(*args) -> Dict[str, Any]: - key = "test_image_upgrade_validate_00009a" - return responses_switch_issu_details(key) - - monkeypatch.setattr(DCNM_SEND_ISSU_DETAILS, mock_dcnm_send_issu_details) - - with does_not_raise(): - instance = image_validate - instance.unit_test = True - instance.issu_detail = issu_details_by_serial_number - instance.serial_numbers = [ - "FDO21120U5D", - "FDO2112189M", - ] - instance.check_interval = 1 - instance.check_timeout = 1 - - match = "ImageValidate._wait_for_current_actions_to_complete: " - match += "Timed out waiting for actions to complete. " - match += "serial_numbers_done: FDO21120U5D, " - match += "serial_numbers_todo: FDO21120U5D,FDO2112189M" - - with pytest.raises(AnsibleFailJson, match=match): - instance._wait_for_current_actions_to_complete() # pylint: disable=protected-access - assert isinstance(instance.serial_numbers_done, set) - assert len(instance.serial_numbers_done) == 1 - assert "FDO21120U5D" in instance.serial_numbers_done - assert "FDO2112189M" not in instance.serial_numbers_done - - -def test_image_upgrade_validate_00022(image_validate) -> None: - """ - Function - - commit - - Summary - Verify that instance.commit() returns without calling dcnm_send when - instance.serial_numbers is an empty list. - - Test - - instance.response is set to {} because dcnm_send was not called - - instance.result is set to {} because dcnm_send was not called - - Description - If instance.serial_numbers is an empty list, instance.commit() returns - without calling dcnm_send. - """ - with does_not_raise(): - instance = image_validate - instance.serial_numbers = [] - instance.commit() - assert instance.response == [{"response": "No serial numbers to validate."}] - assert instance.result == [{"success": True}] - - -def test_image_upgrade_validate_00023(monkeypatch, image_validate) -> None: - """ - Function - - commit - - Summary - Verify that instance.commit() calls fail_json on failure response from - the controller (501). - - Test - - fail_json is called on 501 response from controller - """ - key = "test_image_upgrade_validate_00023a" - - # Needed only for the 501 return code - def mock_rest_send_image_validate(*args, **kwargs) -> Dict[str, Any]: - return responses_image_validate(key) - - def mock_dcnm_send_issu_details(*args, **kwargs) -> Dict[str, Any]: - return responses_switch_issu_details(key) - - monkeypatch.setattr( - PATCH_IMAGE_VALIDATE_REST_SEND_COMMIT, mock_rest_send_image_validate - ) - monkeypatch.setattr( - PATCH_IMAGE_VALIDATE_REST_SEND_RESULT_CURRENT, - {"success": False, "changed": False}, - ) - monkeypatch.setattr(DCNM_SEND_ISSU_DETAILS, mock_dcnm_send_issu_details) - - with does_not_raise(): - instance = image_validate - instance.serial_numbers = ["FDO21120U5D"] - MATCH = "ImageValidate.commit_normal_mode: failed: " - with pytest.raises(AnsibleFailJson, match=MATCH): - instance.commit() - - -def test_image_upgrade_validate_00024(monkeypatch, image_validate) -> None: - """ - Function - - commit - - Summary - Verify that instance.commit() sets instance.diff appropriately on - a successful response from the controller. - - Test - - instance.diff is set to the expected value - - fail_json is not called - """ - key = "test_image_upgrade_validate_00024a" - - def mock_rest_send_image_validate(*args, **kwargs) -> Dict[str, Any]: - return responses_image_validate(key) - - def mock_dcnm_send_issu_details(*args, **kwargs) -> Dict[str, Any]: - return responses_switch_issu_details(key) - - def mock_wait_for_image_validate_to_complete(*args) -> None: - instance.serial_numbers_done = {"FDO21120U5D"} - - monkeypatch.setattr( - PATCH_IMAGE_VALIDATE_REST_SEND_COMMIT, mock_rest_send_image_validate - ) - monkeypatch.setattr( - PATCH_IMAGE_VALIDATE_REST_SEND_RESULT_CURRENT, - {"success": True, "changed": True}, - ) - monkeypatch.setattr(DCNM_SEND_ISSU_DETAILS, mock_dcnm_send_issu_details) - - with does_not_raise(): - instance = image_validate - instance.unit_test = True - instance.serial_numbers = ["FDO21120U5D"] - monkeypatch.setattr( - instance, - "_wait_for_image_validate_to_complete", - mock_wait_for_image_validate_to_complete, - ) - instance.commit() - - assert instance.diff[0]["action"] == "validate" - assert instance.diff[0]["policy"] == "KR5M" - assert instance.diff[0]["ip_address"] == "172.22.150.102" - assert instance.diff[0]["serial_number"] == "FDO21120U5D" - - -MATCH_00030 = "ImageValidate.serial_numbers: " -MATCH_00030 += "instance.serial_numbers must be a python list " -MATCH_00030 += "of switch serial numbers." - - -@pytest.mark.parametrize( - "value, expected", - [ - ([], does_not_raise()), - (True, pytest.raises(AnsibleFailJson, match=MATCH_00030)), - (False, pytest.raises(AnsibleFailJson, match=MATCH_00030)), - (None, pytest.raises(AnsibleFailJson, match=MATCH_00030)), - ("FOO", pytest.raises(AnsibleFailJson, match=MATCH_00030)), - (10, pytest.raises(AnsibleFailJson, match=MATCH_00030)), - ({1, 2}, pytest.raises(AnsibleFailJson, match=MATCH_00030)), - ({"a": 1, "b": 2}, pytest.raises(AnsibleFailJson, match=MATCH_00030)), - ], -) -def test_image_upgrade_validate_00030(image_validate, value, expected) -> None: - """ - Function - - serial_numbers.setter - - Test - - fail_json when serial_numbers is not a list - """ - with does_not_raise(): - instance = image_validate - assert instance.class_name == "ImageValidate" - - with expected: - instance.serial_numbers = value - - -MATCH_00040 = "ImageValidate.non_disruptive: " -MATCH_00040 += "instance.non_disruptive must be a boolean." - - -@pytest.mark.parametrize( - "value, expected", - [ - (True, does_not_raise()), - (False, does_not_raise()), - (None, pytest.raises(AnsibleFailJson, match=MATCH_00040)), - ("FOO", pytest.raises(AnsibleFailJson, match=MATCH_00040)), - (10, pytest.raises(AnsibleFailJson, match=MATCH_00040)), - ([1, 2], pytest.raises(AnsibleFailJson, match=MATCH_00040)), - ({1, 2}, pytest.raises(AnsibleFailJson, match=MATCH_00040)), - ({"a": 1, "b": 2}, pytest.raises(AnsibleFailJson, match=MATCH_00040)), - ], -) -def test_image_upgrade_validate_00040(image_validate, value, expected) -> None: - """ - Function - - non_disruptive.setter - - Test - - fail_json when non_disruptive is not a boolean - """ - with does_not_raise(): - instance = image_validate - assert instance.class_name == "ImageValidate" - - with expected: - instance.non_disruptive = value - - -MATCH_00050 = "ImageValidate.check_interval: " -MATCH_00050 += "must be a positive integer or zero." - - -@pytest.mark.parametrize( - "value, expected", - [ - (10, does_not_raise()), - (-10, pytest.raises(AnsibleFailJson, match=MATCH_00050)), - ("FOO", pytest.raises(AnsibleFailJson, match=MATCH_00050)), - (False, pytest.raises(AnsibleFailJson, match=MATCH_00050)), - (None, pytest.raises(AnsibleFailJson, match=MATCH_00050)), - ([1, 2], pytest.raises(AnsibleFailJson, match=MATCH_00050)), - ({1, 2}, pytest.raises(AnsibleFailJson, match=MATCH_00050)), - ({"a": 1, "b": 2}, pytest.raises(AnsibleFailJson, match=MATCH_00050)), - ], -) -def test_image_upgrade_validate_00050(image_validate, value, expected) -> None: - """ - Function - - check_interval.setter - - Test - - fail_json when check_interval is not an integer - """ - with does_not_raise(): - instance = image_validate - assert instance.class_name == "ImageValidate" - - with expected: - instance.check_interval = value - - -MATCH_00060 = "ImageValidate.check_timeout: " -MATCH_00060 += "must be a positive integer or zero." - - -@pytest.mark.parametrize( - "value, expected", - [ - (10, does_not_raise()), - (-10, pytest.raises(AnsibleFailJson, match=MATCH_00060)), - ("FOO", pytest.raises(AnsibleFailJson, match=MATCH_00060)), - (False, pytest.raises(AnsibleFailJson, match=MATCH_00060)), - (None, pytest.raises(AnsibleFailJson, match=MATCH_00060)), - ([1, 2], pytest.raises(AnsibleFailJson, match=MATCH_00060)), - ({1, 2}, pytest.raises(AnsibleFailJson, match=MATCH_00060)), - ({"a": 1, "b": 2}, pytest.raises(AnsibleFailJson, match=MATCH_00060)), - ], -) -def test_image_upgrade_validate_00060(image_validate, value, expected) -> None: - """ - Function - - check_timeout.setter - - Test - - fail_json when check_timeout is not an integer - """ - with does_not_raise(): - instance = image_validate - assert instance.class_name == "ImageValidate" - - with expected: - instance.check_timeout = value diff --git a/tests/unit/modules/dcnm/dcnm_image_upgrade/test_image_upgrade_switch_details.py b/tests/unit/modules/dcnm/dcnm_image_upgrade/test_image_upgrade_switch_details.py deleted file mode 100644 index 8953ac221..000000000 --- a/tests/unit/modules/dcnm/dcnm_image_upgrade/test_image_upgrade_switch_details.py +++ /dev/null @@ -1,481 +0,0 @@ -# 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 -# Some tests require calling protected methods -# pylint: disable=protected-access - -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.image_upgrade.switch_details import \ - SwitchDetails - -from .utils import (does_not_raise, responses_switch_details, - switch_details_fixture) - -PATCH_MODULE_UTILS = "ansible_collections.cisco.dcnm.plugins.module_utils." -PATCH_IMAGE_UPGRADE = PATCH_MODULE_UTILS + "image_upgrade." -PATCH_SWITCH_DETAILS = PATCH_IMAGE_UPGRADE + "switch_details." -PATCH_SWITCH_DETAILS_REST_SEND_RESPONSE_CURRENT = ( - PATCH_SWITCH_DETAILS + "RestSend.response_current" -) -PATCH_SWITCH_DETAILS_REST_SEND_RESULT_CURRENT = ( - PATCH_SWITCH_DETAILS + "RestSend.result_current" -) -REST_SEND_SWITCH_DETAILS = PATCH_IMAGE_UPGRADE + "switch_details.RestSend.commit" - - -def test_image_upgrade_switch_details_00001(switch_details) -> None: - """ - Function - - __init__ - - Summary - Verify that the class attributes are initialized to expected values. - - Test - - Class attributes are initialized to expected values - - fail_json is not called - """ - with does_not_raise(): - instance = switch_details - assert isinstance(instance, SwitchDetails) - assert instance.class_name == "SwitchDetails" - assert instance.verb == "GET" - assert ( - instance.path - == "/appcenter/cisco/ndfc/api/v1/lan-fabric/rest/inventory/allswitches" - ) - - -def test_image_upgrade_switch_details_00002(switch_details) -> None: - """ - Function - - _init_properties - - Summary - Verify that the class properties are initialized to expected values. - - Test - - Class properties are initialized to expected values - - fail_json is not called - """ - with does_not_raise(): - instance = switch_details - assert isinstance(instance.properties, dict) - assert instance.properties.get("ip_address") is None - assert instance.properties.get("info") == {} - assert instance.properties.get("response_data") == [] - assert instance.properties.get("response") == [] - assert instance.properties.get("response_current") == {} - assert instance.properties.get("result") == [] - assert instance.properties.get("result_current") == {} - - -def test_image_upgrade_switch_details_00020(monkeypatch, switch_details) -> None: - """ - Function - - refresh - - Test (X == SwitchDetails) - - X.response_data, X.response, X.result are lists - - X.response_current, X.result_current are dictionaries - - X.response_current, X.result_current are set to the mocked RestSend values - """ - key = "test_image_upgrade_switch_details_00020a" - - def mock_rest_send_switch_details(*args, **kwargs) -> Dict[str, Any]: - return responses_switch_details(key) - - monkeypatch.setattr(REST_SEND_SWITCH_DETAILS, mock_rest_send_switch_details) - monkeypatch.setattr( - PATCH_SWITCH_DETAILS_REST_SEND_RESPONSE_CURRENT, mock_rest_send_switch_details() - ) - monkeypatch.setattr( - PATCH_SWITCH_DETAILS_REST_SEND_RESULT_CURRENT, {"success": True, "found": True} - ) - with does_not_raise(): - instance = switch_details - instance.refresh() - assert isinstance(instance.response_data, list) - assert isinstance(instance.result, list) - assert isinstance(instance.response, list) - assert isinstance(instance.response_current, dict) - assert isinstance(instance.result_current, dict) - assert instance.result_current == {"success": True, "found": True} - assert instance.response_current == responses_switch_details(key) - - -def test_image_upgrade_switch_details_00021(monkeypatch, switch_details) -> None: - """ - Function - - SwitchDetails.refresh - - SwitchDetails.ip_address.setter - - SwitchDetails.fabric_name - - SwitchDetails.hostname - - SwitchDetails.info - - SwitchDetails.logical_name - - SwitchDetails.model - - SwitchDetails.platform - - SwitchDetails.role - - SwitchDetails.serial_number - - Summary - Verify that, after refresh() is called, and the ip_address setter - property is set, the getter properties return values specific to the - ip_address that was set. - - Test - - response_data is a dictionary - - ip_address is set - - getter properties will return values specific to ip_address - - fail_json is not called - """ - - def mock_rest_send_switch_details(*args, **kwargs) -> Dict[str, Any]: - key = "test_image_upgrade_switch_details_00021a" - return responses_switch_details(key) - - monkeypatch.setattr(REST_SEND_SWITCH_DETAILS, mock_rest_send_switch_details) - monkeypatch.setattr( - PATCH_SWITCH_DETAILS_REST_SEND_RESPONSE_CURRENT, mock_rest_send_switch_details() - ) - monkeypatch.setattr( - PATCH_SWITCH_DETAILS_REST_SEND_RESULT_CURRENT, {"success": True, "found": True} - ) - with does_not_raise(): - instance = switch_details - instance.refresh() - assert isinstance(instance.response_data, list) - - with does_not_raise(): - instance.ip_address = "172.22.150.110" - assert instance.hostname == "cvd-1111-bgw" - - with does_not_raise(): - instance.ip_address = "172.22.150.111" - # We use the above IP address to test the remaining properties - assert instance.fabric_name == "easy" - assert instance.hostname == "cvd-1112-bgw" - assert instance.logical_name == "cvd-1112-bgw" - assert instance.model == "N9K-C9504" - # This is derived from "model" and is not in the controller response - assert instance.platform == "N9K" - assert instance.role == "border gateway" - assert instance.serial_number == "FOX2109PGD1" - assert "172.22.150.110" in instance.info.keys() - assert instance.info["172.22.150.110"]["hostName"] == "cvd-1111-bgw" - - -MATCH_00022 = "Unable to retrieve switch information from the controller." - - -@pytest.mark.parametrize( - "key,expected", - [ - ("test_image_upgrade_switch_details_00022a", does_not_raise()), - ( - "test_image_upgrade_switch_details_00022b", - pytest.raises(AnsibleFailJson, match=MATCH_00022), - ), - ( - "test_image_upgrade_switch_details_00022c", - pytest.raises(AnsibleFailJson, match=MATCH_00022), - ), - ], -) -def test_image_upgrade_switch_details_00022( - monkeypatch, switch_details, key, expected -) -> None: - """ - Function - - SwitchDetails.refresh - - RestSend._handle_response - - Summary - Verify that RestSend._handle_response() returns an appropriate result - when SwitchDetails.refresh() is called. - - Test - - test_image_upgrade_switch_details_00022a - - 200 RETURN_CODE, MESSAGE == "OK" - - result == {'found': True, 'success': True} - - test_image_upgrade_switch_details_00022b - - 404 RETURN_CODE, MESSAGE == "Not Found" - - result == {'found': False, 'success': True} - - test_image_upgrade_switch_details_00022c - - 500 RETURN_CODE, MESSAGE ~= "Internal Server Error" - - result == {'found': False, 'success': False} - """ - instance = switch_details - - def mock_rest_send_switch_details(*args, **kwargs) -> Dict[str, Any]: - return responses_switch_details(key) - - monkeypatch.setattr(REST_SEND_SWITCH_DETAILS, mock_rest_send_switch_details) - monkeypatch.setattr( - PATCH_SWITCH_DETAILS_REST_SEND_RESPONSE_CURRENT, mock_rest_send_switch_details() - ) - - with expected: - instance.refresh() - - -@pytest.mark.parametrize( - "item, expected", - [ - ("fabricName", "easy"), - ("hostName", "cvd-1111-bgw"), - ("licenseViolation", False), - ("location", None), - ("logicalName", "cvd-1111-bgw"), - ("managable", True), - ("model", "N9K-C9504"), - ("present", True), - ("serialNumber", "FOX2109PGCT"), - ("switchRole", "border gateway"), - ], -) -def test_image_upgrade_switch_details_00023( - monkeypatch, switch_details, item, expected -) -> None: - """ - Function - - SwitchDetails.refresh - - SwitchDetails.ip_address - - SwitchDetails._get - - Summary - Verify that SwitchDetails._get returns expected property values. - - Test - - _get returns property values consistent with the controller response. - - Description - - SwitchDetails._get is called by all getter properties. - - It raises AnsibleFailJson if the user has not set ip_address or if - the ip_address is unknown, or if an unknown property name is queried. - - It returns the value of the requested property if the user has set - ip_address and the property name is known. - - Property values are passed to make_boolean() and make_none(), which either: - - converts value to a boolean - - converts value to NoneType - - returns value unchanged - """ - instance = switch_details - - def mock_rest_send_switch_details(*args, **kwargs) -> Dict[str, Any]: - key = "test_image_upgrade_switch_details_00023a" - return responses_switch_details(key) - - monkeypatch.setattr(REST_SEND_SWITCH_DETAILS, mock_rest_send_switch_details) - monkeypatch.setattr( - PATCH_SWITCH_DETAILS_REST_SEND_RESPONSE_CURRENT, mock_rest_send_switch_details() - ) - - with does_not_raise(): - instance.refresh() - instance.ip_address = "172.22.150.110" - assert instance._get(item) == expected - - -def test_image_upgrade_switch_details_00024(monkeypatch, switch_details) -> None: - """ - Function - - SwitchDetails.refresh - - SwitchDetails.ip_address - - SwitchDetails._get - - Summary - Verify that fail_json is called when SwitchDetails.ip_address does not exist - on the controller and a property associated with ip_address is queried. - - Test - - _get calls fail_json when SwitchDetails.ip_address is unknown - - Description - SwitchDetails._get is called by all getter properties. - It raises AnsibleFailJson if the user has not set ip_address or if - the ip_address is unknown, or if an unknown property name is queried. - It returns the value of the requested property if the user has set a known - ip_address. - """ - - def mock_rest_send_switch_details(*args, **kwargs) -> Dict[str, Any]: - key = "test_image_upgrade_switch_details_00024a" - return responses_switch_details(key) - - monkeypatch.setattr(REST_SEND_SWITCH_DETAILS, mock_rest_send_switch_details) - monkeypatch.setattr( - PATCH_SWITCH_DETAILS_REST_SEND_RESPONSE_CURRENT, mock_rest_send_switch_details() - ) - - match = "SwitchDetails._get: 1.1.1.1 does not exist " - match += "on the controller." - - with does_not_raise(): - instance = switch_details - instance.refresh() - instance.ip_address = "1.1.1.1" - with pytest.raises(AnsibleFailJson, match=match): - instance._get("hostName") - - -def test_image_upgrade_switch_details_00025(monkeypatch, switch_details) -> None: - """ - Function - - SwitchDetails.refresh - - SwitchDetails.ip_address - - SwitchDetails._get - - Summary - Verify that fail_json is called when an unknown property name is queried. - - Test - - _get calls fail_json when an unknown property name is queried - - Description - SwitchDetails._get is called by all getter properties. - It raises AnsibleFailJson if the user has not set ip_address or if - the ip_address is unknown, or if an unknown property name is queried. - """ - - def mock_rest_send_switch_details(*args, **kwargs) -> Dict[str, Any]: - key = "test_image_upgrade_switch_details_00025a" - return responses_switch_details(key) - - monkeypatch.setattr(REST_SEND_SWITCH_DETAILS, mock_rest_send_switch_details) - monkeypatch.setattr( - PATCH_SWITCH_DETAILS_REST_SEND_RESPONSE_CURRENT, mock_rest_send_switch_details() - ) - - match = "SwitchDetails._get: 172.22.150.110 does not have a key named FOO." - - with does_not_raise(): - instance = switch_details - instance.refresh() - instance.ip_address = "172.22.150.110" - with pytest.raises(AnsibleFailJson, match=match): - instance._get("FOO") - - -def test_image_upgrade_switch_details_00026(switch_details) -> None: - """ - Function - - SwitchDetails.fabric_name - - SwitchDetails._get - - Summary - Verify that SwitchDetails.fabric_name calls SwitchDetails._get() - which then calls fail_json when ip_address has not been set. - - Test - - _get calls fail_json when ip_address is None - - Description - SwitchDetails._get is called by all getter properties. - It raises AnsibleFailJson if the user has not set ip_address or if - the ip_address is unknown, or if an unknown property name is queried. - """ - match = r"SwitchDetails\._get: " - match += r"set instance\.ip_address before accessing property fabricName\." - - with does_not_raise(): - instance = switch_details - with pytest.raises(AnsibleFailJson, match=match): - instance.fabric_name - - -def test_image_upgrade_switch_details_00030(monkeypatch, switch_details) -> None: - """ - Function - - SwitchDetails.platform - - Summary - Verify that, SwitchDetails.platform returns None if SwitchDetails.model is None. - - Test - - platform returns None - - fail_json is not called - """ - - def mock_rest_send_switch_details(*args, **kwargs) -> Dict[str, Any]: - key = "test_image_upgrade_switch_details_00030a" - return responses_switch_details(key) - - monkeypatch.setattr(REST_SEND_SWITCH_DETAILS, mock_rest_send_switch_details) - monkeypatch.setattr( - PATCH_SWITCH_DETAILS_REST_SEND_RESPONSE_CURRENT, mock_rest_send_switch_details() - ) - monkeypatch.setattr( - PATCH_SWITCH_DETAILS_REST_SEND_RESULT_CURRENT, {"success": True, "found": True} - ) - with does_not_raise(): - instance = switch_details - instance.refresh() - - with does_not_raise(): - instance.ip_address = "172.22.150.111" - platform = instance.platform - assert platform is None - - -# setters - - -@pytest.mark.parametrize( - "ip_address_is_set, expected", - [ - (True, "1.2.3.4"), - (False, None), - ], -) -def test_image_upgrade_switch_details_00060( - switch_details, ip_address_is_set, expected -) -> None: - """ - Function - - ip_address.setter - - Summary - Verify proper behavior of ip_address setter - - Test - - return IP address, if set - - return None, if not set - """ - with does_not_raise(): - instance = switch_details - if ip_address_is_set: - instance.ip_address = "1.2.3.4" - assert instance.ip_address == expected diff --git a/tests/unit/modules/dcnm/dcnm_image_upgrade/test_image_upgrade_switch_issu_details_by_device_name.py b/tests/unit/modules/dcnm/dcnm_image_upgrade/test_image_upgrade_switch_issu_details_by_device_name.py deleted file mode 100644 index f77cce289..000000000 --- a/tests/unit/modules/dcnm/dcnm_image_upgrade/test_image_upgrade_switch_issu_details_by_device_name.py +++ /dev/null @@ -1,402 +0,0 @@ -# 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 - -__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 .utils import (does_not_raise, issu_details_by_device_name_fixture, - responses_switch_issu_details) - -PATCH_MODULE_UTILS = "ansible_collections.cisco.dcnm.plugins.module_utils." -PATCH_IMAGE_UPGRADE = PATCH_MODULE_UTILS + "image_upgrade." -DCNM_SEND_ISSU_DETAILS = PATCH_IMAGE_UPGRADE + "switch_issu_details.dcnm_send" - - -def test_image_upgrade_switch_issu_details_by_device_name_00001( - issu_details_by_device_name, -) -> None: - """ - Function - - SwitchIssuDetailsByDeviceName.__init__ - - Test - - fail_json is not called - - instance.properties is a dict - """ - with does_not_raise(): - instance = issu_details_by_device_name - assert isinstance(instance.properties, dict) - - -def test_image_upgrade_switch_issu_details_by_device_name_00002( - issu_details_by_device_name, -) -> None: - """ - Function - - SwitchIssuDetailsByDeviceName._init_properties - - Test - - Class properties initialized to expected values - - instance.properties is a dict - - instance.action_keys is a set - - action_keys contains expected values - """ - instance = issu_details_by_device_name - action_keys = {"imageStaged", "upgrade", "validated"} - - assert isinstance(instance.properties, dict) - assert isinstance(instance.properties.get("action_keys"), set) - assert instance.properties.get("action_keys") == action_keys - assert instance.properties.get("response_data") == [] - assert instance.properties.get("response") == [] - assert instance.properties.get("response_current") == {} - assert instance.properties.get("result") == [] - assert instance.properties.get("result_current") == {} - assert instance.properties.get("device_name") is None - - -def test_image_upgrade_switch_issu_details_by_device_name_00020( - monkeypatch, issu_details_by_device_name -) -> None: - """ - Function - - SwitchIssuDetailsByDeviceName.refresh - - Test - - instance.response is a dict - - instance.response_data is a list - """ - - key = "test_image_upgrade_switch_issu_details_by_device_name_00020a" - - def mock_dcnm_send_issu_details(*args, **kwargs) -> Dict[str, Any]: - print(f"mock_dcnm_send_issu_details: {responses_switch_issu_details(key)}") - return responses_switch_issu_details(key) - - monkeypatch.setattr(DCNM_SEND_ISSU_DETAILS, mock_dcnm_send_issu_details) - instance = issu_details_by_device_name - instance.refresh() - assert isinstance(instance.response_current, dict) - assert isinstance(instance.response_data, list) - - -def test_image_upgrade_switch_issu_details_by_device_name_00021( - monkeypatch, issu_details_by_device_name -) -> None: - """ - Function - - SwitchIssuDetailsByDeviceName.refresh - - Test - - Properties are set based on device_name - - Expected property values are returned - """ - instance = issu_details_by_device_name - - key = "test_image_upgrade_switch_issu_details_by_device_name_00021a" - - def mock_dcnm_send_issu_details(*args, **kwargs) -> Dict[str, Any]: - print(f"mock_dcnm_send_issu_details: {responses_switch_issu_details(key)}") - return responses_switch_issu_details(key) - - monkeypatch.setattr(DCNM_SEND_ISSU_DETAILS, mock_dcnm_send_issu_details) - - instance.refresh() - instance.filter = "leaf1" - assert instance.device_name == "leaf1" - assert instance.serial_number == "FDO21120U5D" - # change device_name to a different switch, expect different information - instance.filter = "cvd-2313-leaf" - assert instance.device_name == "cvd-2313-leaf" - assert instance.serial_number == "FDO2112189M" - # verify remaining properties using current device_name - assert instance.eth_switch_id == 39890 - assert instance.fabric == "hard" - assert instance.fcoe_enabled is False - assert instance.group == "hard" - # NOTE: For "id" see switch_id below - assert instance.image_staged == "Success" - assert instance.image_staged_percent == 100 - assert instance.ip_address == "172.22.150.108" - assert instance.issu_allowed is None - assert instance.last_upg_action == "2023-Oct-06 03:43" - assert instance.mds is False - assert instance.mode == "Normal" - assert instance.model == "N9K-C93180YC-EX" - assert instance.model_type == 0 - assert instance.peer is None - assert instance.platform == "N9K" - assert instance.policy == "KR5M" - assert instance.reason == "Upgrade" - assert instance.role == "leaf" - assert instance.status == "In-Sync" - assert instance.status_percent == 100 - # NOTE: switch_id appears in the response data as "id" - # NOTE: "id" is a python reserved keyword, so we changed the property name - assert instance.switch_id == 2 - assert instance.sys_name == "cvd-2313-leaf" - assert instance.system_mode == "Normal" - assert instance.upg_groups is None - assert instance.upgrade == "Success" - assert instance.upgrade_percent == 100 - assert instance.validated == "Success" - assert instance.validated_percent == 100 - assert instance.version == "10.2(5)" - # NOTE: Two vdc_id values exist in the response data for each switch. - # NOTE: Namely, "vdcId" and "vdc_id" - # NOTE: Properties are provided for both, as follows. - # NOTE: vdc_id == vdcId - # NOTE: vdc_id2 == vdc_id - assert instance.vdc_id == 0 - assert instance.vdc_id2 == -1 - assert instance.vpc_peer is None - # NOTE: Two vpc role keys exist in the response data for each switch. - # NOTE: Namely, "vpcRole" and "vpc_role" - # NOTE: Properties are provided for both, as follows. - # NOTE: vpc_role == vpcRole - # NOTE: vpc_role2 == vpc_role - # NOTE: Values are synthesized in the response for this test - assert instance.vpc_role == "FOO" - assert instance.vpc_role2 == "BAR" - assert isinstance(instance.filtered_data, dict) - assert instance.filtered_data.get("deviceName") == "cvd-2313-leaf" - - -def test_image_upgrade_switch_issu_details_by_device_name_00022( - monkeypatch, issu_details_by_device_name -) -> None: - """ - Function - - SwitchIssuDetailsByDeviceName.refresh - - Test - - instance.result is a dict - - instance.result contains expected key/values for 200 RESULT_CODE - """ - instance = issu_details_by_device_name - - key = "test_image_upgrade_switch_issu_details_by_device_name_00022a" - - def mock_dcnm_send_issu_details(*args, **kwargs) -> Dict[str, Any]: - print(f"mock_dcnm_send_issu_details: {responses_switch_issu_details(key)}") - return responses_switch_issu_details(key) - - monkeypatch.setattr(DCNM_SEND_ISSU_DETAILS, mock_dcnm_send_issu_details) - - instance.refresh() - assert isinstance(instance.result, list) - assert isinstance(instance.result_current, dict) - assert instance.result_current.get("found") is True - assert instance.result_current.get("success") is True - - -def test_image_upgrade_switch_issu_details_by_device_name_00023( - monkeypatch, issu_details_by_device_name -) -> None: - """ - Function - - SwitchIssuDetailsByDeviceName.refresh - - Test - - refresh calls handle_response, which calls json_fail on 404 response - - Error message matches expectation - """ - instance = issu_details_by_device_name - - key = "test_image_upgrade_switch_issu_details_by_device_name_00023a" - - def mock_dcnm_send_issu_details(*args, **kwargs) -> Dict[str, Any]: - return responses_switch_issu_details(key) - - monkeypatch.setattr(DCNM_SEND_ISSU_DETAILS, mock_dcnm_send_issu_details) - - match = "Bad result when retriving switch information from the controller" - with pytest.raises(AnsibleFailJson, match=match): - instance.refresh() - - -def test_image_upgrade_switch_issu_details_by_device_name_00024( - monkeypatch, issu_details_by_device_name -) -> None: - """ - Function - - SwitchIssuDetailsByDeviceName.refresh - - Test - - fail_json is called on 200 response with empty DATA key - - Error message matches expectation - """ - instance = issu_details_by_device_name - - key = "test_image_upgrade_switch_issu_details_by_device_name_00024a" - - def mock_dcnm_send_issu_details(*args, **kwargs) -> Dict[str, Any]: - return responses_switch_issu_details(key) - - monkeypatch.setattr(DCNM_SEND_ISSU_DETAILS, mock_dcnm_send_issu_details) - - match = "SwitchIssuDetailsByDeviceName.refresh_super: " - match += "The controller has no switch ISSU information." - with pytest.raises(AnsibleFailJson, match=match): - instance.refresh() - - -def test_image_upgrade_switch_issu_details_by_device_name_00025( - monkeypatch, issu_details_by_device_name -) -> None: - """ - Function - - SwitchIssuDetailsByDeviceName.refresh - - Test - - fail_json is called on 200 response with DATA.lastOperDataObject length 0 - - Error message matches expectation - """ - instance = issu_details_by_device_name - - key = "test_image_upgrade_switch_issu_details_by_device_name_00025a" - - def mock_dcnm_send_issu_details(*args, **kwargs) -> Dict[str, Any]: - print(f"mock_dcnm_send_issu_details: {responses_switch_issu_details(key)}") - return responses_switch_issu_details(key) - - monkeypatch.setattr(DCNM_SEND_ISSU_DETAILS, mock_dcnm_send_issu_details) - - match = "SwitchIssuDetailsByDeviceName.refresh_super: " - match += "The controller has no switch ISSU information." - with pytest.raises(AnsibleFailJson, match=match): - instance.refresh() - - -def test_image_upgrade_switch_issu_details_by_device_name_00040( - monkeypatch, issu_details_by_device_name -) -> None: - """ - Function - - SwitchIssuDetailsByDeviceName._get - - Summary - Verify that _get() calls fail_json because filter is set to an - unknown device_name - - Test - - fail_json is called because filter is set to an unknown device_name - - Error message matches expectation - - Description - SwitchIssuDetailsByDeviceName._get is called by all getter properties. - It raises AnsibleFailJson if the user has not set filter or if - filter is unknown, or if an unknown property name is queried. - It returns the value of the requested property if the user has filter - to a device_name that exists on the controller. - - Expected result - 1. fail_json is called with appropriate error message since filter - is set to an unknown device_name. - """ - instance = issu_details_by_device_name - - def mock_dcnm_send_issu_details(*args, **kwargs) -> Dict[str, Any]: - key = "test_image_upgrade_switch_issu_details_by_device_name_00040a" - return responses_switch_issu_details(key) - - monkeypatch.setattr(DCNM_SEND_ISSU_DETAILS, mock_dcnm_send_issu_details) - - instance.refresh() - instance.filter = "FOO" - match = "SwitchIssuDetailsByDeviceName._get: FOO does not exist " - match += "on the controller." - with pytest.raises(AnsibleFailJson, match=match): - instance._get("serialNumber") # pylint: disable=protected-access - - -def test_image_upgrade_switch_issu_details_by_device_name_00041( - monkeypatch, issu_details_by_device_name -) -> None: - """ - Function - - _get - - Summary - Verify that _get() calls fail_json because an unknown property is queried - - Test - - fail_json is called on access of unknown property name - - Error message matches expectation - - Description - SwitchIssuDetailsByDeviceName._get is called by all getter properties. - It raises AnsibleFailJson if the user has not set filter or if - filter is unknown, or if an unknown property name is queried. - It returns the value of the requested property if the user has filter - to a device_name that exists on the controller. - """ - instance = issu_details_by_device_name - - def mock_dcnm_send_issu_details(*args, **kwargs) -> Dict[str, Any]: - key = "test_image_upgrade_switch_issu_details_by_device_name_00041a" - return responses_switch_issu_details(key) - - monkeypatch.setattr(DCNM_SEND_ISSU_DETAILS, mock_dcnm_send_issu_details) - - instance.refresh() - instance.filter = "leaf1" - match = "SwitchIssuDetailsByDeviceName._get: leaf1 unknown " - match += "property name: FOO" - with pytest.raises(AnsibleFailJson, match=match): - instance._get("FOO") # pylint: disable=protected-access - - -def test_image_upgrade_switch_issu_details_by_device_name_00042( - issu_details_by_device_name, -) -> None: - """ - Function - - _get - - Test - - fail_json is called because instance.filter is not set - - Error message matches expectation - - Description - SwitchIssuDetailsByDeviceName._get is called by all getter properties. - It raises AnsibleFailJson if the user has not set filter or if - filter is unknown, or if an unknown property name is queried. - It returns the value of the requested property if the user has filter - to a device_name that exists on the controller. - """ - with does_not_raise(): - instance = issu_details_by_device_name - match = r"SwitchIssuDetailsByDeviceName\._get: " - match += r"set instance\.filter to a switch deviceName " - match += r"before accessing property role\." - with pytest.raises(AnsibleFailJson, match=match): - instance.role diff --git a/tests/unit/modules/dcnm/dcnm_image_upgrade/test_image_upgrade_switch_issu_details_by_ip_address.py b/tests/unit/modules/dcnm/dcnm_image_upgrade/test_image_upgrade_switch_issu_details_by_ip_address.py deleted file mode 100644 index a870ad180..000000000 --- a/tests/unit/modules/dcnm/dcnm_image_upgrade/test_image_upgrade_switch_issu_details_by_ip_address.py +++ /dev/null @@ -1,416 +0,0 @@ -# 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 - -__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 .utils import (does_not_raise, issu_details_by_ip_address_fixture, - responses_switch_issu_details) - -PATCH_MODULE_UTILS = "ansible_collections.cisco.dcnm.plugins.module_utils." -PATCH_IMAGE_UPGRADE = PATCH_MODULE_UTILS + "image_upgrade." -DCNM_SEND_ISSU_DETAILS = PATCH_IMAGE_UPGRADE + "switch_issu_details.dcnm_send" - - -def test_image_upgrade_switch_issu_details_by_ip_address_00001( - issu_details_by_ip_address, -) -> None: - """ - Function - - SwitchIssuDetailsByIpAddress.__init__ - - Test - - fail_json is not called - - instance.properties is a dict - """ - with does_not_raise(): - instance = issu_details_by_ip_address - assert isinstance(instance.properties, dict) - - -def test_image_upgrade_switch_issu_details_by_ip_address_00002( - issu_details_by_ip_address, -) -> None: - """ - Function - - SwitchIssuDetailsByIpAddress._init_properties - - Test - - Class properties initialized to expected values - - instance.properties is a dict - - instance.action_keys is a set - - action_keys contains expected values - """ - instance = issu_details_by_ip_address - - action_keys = {"imageStaged", "upgrade", "validated"} - - instance._init_properties() # pylint: disable=protected-access - assert isinstance(instance.properties, dict) - assert isinstance(instance.properties.get("action_keys"), set) - assert instance.properties.get("action_keys") == action_keys - assert instance.properties.get("response") == [] - assert instance.properties.get("response_current") == {} - assert instance.properties.get("response_data") == [] - assert instance.properties.get("result") == [] - assert instance.properties.get("result_current") == {} - assert instance.properties.get("ip_address") is None - - -def test_image_upgrade_switch_issu_details_by_ip_address_00020( - monkeypatch, issu_details_by_ip_address -) -> None: - """ - Function - - SwitchIssuDetailsByIpAddress.refresh - - Test - - instance.response is a list - - instance.response_current is a dict - - instance.result is a list - - instance.result_current is a dict - - instance.response_data is a list - """ - instance = issu_details_by_ip_address - - key = "test_image_upgrade_switch_issu_details_by_ip_address_00020a" - - def mock_dcnm_send_issu_details(*args, **kwargs) -> Dict[str, Any]: - print(f"mock_dcnm_send_issu_details: {responses_switch_issu_details(key)}") - return responses_switch_issu_details(key) - - monkeypatch.setattr(DCNM_SEND_ISSU_DETAILS, mock_dcnm_send_issu_details) - - instance.refresh() - assert isinstance(instance.response, list) - assert isinstance(instance.response_current, dict) - assert isinstance(instance.result, list) - assert isinstance(instance.result_current, dict) - assert isinstance(instance.response_data, list) - - -def test_image_upgrade_switch_issu_details_by_ip_address_00021( - monkeypatch, issu_details_by_ip_address -) -> None: - """ - Function - - SwitchIssuDetailsByIpAddress.refresh - - Test - - Properties are set based on device_name - - Expected property values are returned - """ - instance = issu_details_by_ip_address - - key = "test_image_upgrade_switch_issu_details_by_ip_address_00021a" - - def mock_dcnm_send_issu_details(*args, **kwargs) -> Dict[str, Any]: - print(f"mock_dcnm_send_issu_details: {responses_switch_issu_details(key)}") - return responses_switch_issu_details(key) - - monkeypatch.setattr(DCNM_SEND_ISSU_DETAILS, mock_dcnm_send_issu_details) - - instance.refresh() - instance.filter = "172.22.150.102" - assert instance.device_name == "leaf1" - assert instance.serial_number == "FDO21120U5D" - # change ip_address to a different switch, expect different information - instance.filter = "172.22.150.108" - assert instance.device_name == "cvd-2313-leaf" - assert instance.serial_number == "FDO2112189M" - # verify remaining properties using current ip_address - assert instance.eth_switch_id == 39890 - assert instance.fabric == "hard" - assert instance.fcoe_enabled is False - assert instance.group == "hard" - # NOTE: For "id" see switch_id below - assert instance.image_staged == "Success" - assert instance.image_staged_percent == 100 - assert instance.ip_address == "172.22.150.108" - assert instance.issu_allowed is None - assert instance.last_upg_action == "2023-Oct-06 03:43" - assert instance.mds is False - assert instance.mode == "Normal" - assert instance.model == "N9K-C93180YC-EX" - assert instance.model_type == 0 - assert instance.peer is None - assert instance.platform == "N9K" - assert instance.policy == "KR5M" - assert instance.reason == "Upgrade" - assert instance.role == "leaf" - assert instance.status == "In-Sync" - assert instance.status_percent == 100 - # NOTE: switch_id appears in the response data as "id" - # NOTE: "id" is a python reserved keyword, so we changed the property name - assert instance.switch_id == 2 - assert instance.sys_name == "cvd-2313-leaf" - assert instance.system_mode == "Normal" - assert instance.upg_groups is None - assert instance.upgrade == "Success" - assert instance.upgrade_percent == 100 - assert instance.validated == "Success" - assert instance.validated_percent == 100 - assert instance.version == "10.2(5)" - # NOTE: Two vdc_id values exist in the response data for each switch. - # NOTE: Namely, "vdcId" and "vdc_id" - # NOTE: Properties are provided for both, as follows. - # NOTE: vdc_id == vdcId - # NOTE: vdc_id2 == vdc_id - assert instance.vdc_id == 0 - assert instance.vdc_id2 == -1 - assert instance.vpc_peer is None - # NOTE: Two vpc role keys exist in the response data for each switch. - # NOTE: Namely, "vpcRole" and "vpc_role" - # NOTE: Properties are provided for both, as follows. - # NOTE: vpc_role == vpcRole - # NOTE: vpc_role2 == vpc_role - # NOTE: Values are synthesized in the response for this test - assert instance.vpc_role == "FOO" - assert instance.vpc_role2 == "BAR" - assert isinstance(instance.filtered_data, dict) - assert instance.filtered_data.get("deviceName") == "cvd-2313-leaf" - - -def test_image_upgrade_switch_issu_details_by_ip_address_00022( - monkeypatch, issu_details_by_ip_address -) -> None: - """ - Function - - SwitchIssuDetailsByIpAddress.refresh - - Test - - instance.result is a dict - - instance.result contains expected key/values for 200 RESULT_CODE - """ - instance = issu_details_by_ip_address - - key = "test_image_upgrade_switch_issu_details_by_ip_address_00022a" - - def mock_dcnm_send_issu_details(*args, **kwargs) -> Dict[str, Any]: - print(f"mock_dcnm_send_issu_details: {responses_switch_issu_details(key)}") - return responses_switch_issu_details(key) - - monkeypatch.setattr(DCNM_SEND_ISSU_DETAILS, mock_dcnm_send_issu_details) - - instance.refresh() - assert isinstance(instance.result, list) - assert isinstance(instance.result_current, dict) - assert instance.result_current.get("found") is True - assert instance.result_current.get("success") is True - - -def test_image_upgrade_switch_issu_details_by_ip_address_00023( - monkeypatch, issu_details_by_ip_address -) -> None: - """ - Function - - SwitchIssuDetailsByIpAddress.refresh - - Test - - refresh calls handle_response, which calls json_fail on 404 response - - Error message matches expectation - """ - instance = issu_details_by_ip_address - - key = "test_image_upgrade_switch_issu_details_by_ip_address_00023a" - - def mock_dcnm_send_issu_details(*args, **kwargs) -> Dict[str, Any]: - print(f"mock_dcnm_send_issu_details: {responses_switch_issu_details(key)}") - return responses_switch_issu_details(key) - - monkeypatch.setattr(DCNM_SEND_ISSU_DETAILS, mock_dcnm_send_issu_details) - - match = "Bad result when retriving switch information from the controller" - with pytest.raises(AnsibleFailJson, match=match): - instance.refresh() - - -def test_image_upgrade_switch_issu_details_by_ip_address_00024( - monkeypatch, issu_details_by_ip_address -) -> None: - """ - Function - - SwitchIssuDetailsByIpAddress.refresh - - Test - - fail_json is called on 200 response with empty DATA key - - Error message matches expectation - """ - instance = issu_details_by_ip_address - - key = "test_image_upgrade_switch_issu_details_by_ip_address_00024a" - - def mock_dcnm_send_issu_details(*args, **kwargs) -> Dict[str, Any]: - return responses_switch_issu_details(key) - - monkeypatch.setattr(DCNM_SEND_ISSU_DETAILS, mock_dcnm_send_issu_details) - - match = "SwitchIssuDetailsByIpAddress.refresh_super: " - match += "The controller has no switch ISSU information." - with pytest.raises(AnsibleFailJson, match=match): - instance.refresh() - - -def test_image_upgrade_switch_issu_details_by_ip_address_00025( - monkeypatch, issu_details_by_ip_address -) -> None: - """ - Function - - SwitchIssuDetailsByIpAddress.refresh - - Test - - fail_json is called on 200 response with DATA.lastOperDataObject length 0 - - Error message matches expectation - """ - instance = issu_details_by_ip_address - - key = "test_image_upgrade_switch_issu_details_by_ip_address_00025a" - - def mock_dcnm_send_issu_details(*args, **kwargs) -> Dict[str, Any]: - print(f"mock_dcnm_send_issu_details: {responses_switch_issu_details(key)}") - return responses_switch_issu_details(key) - - monkeypatch.setattr(DCNM_SEND_ISSU_DETAILS, mock_dcnm_send_issu_details) - - match = "SwitchIssuDetailsByIpAddress.refresh_super: " - match += "The controller has no switch ISSU information." - with pytest.raises(AnsibleFailJson, match=match): - instance.refresh() - - -def test_image_upgrade_switch_issu_details_by_ip_address_00040( - monkeypatch, issu_details_by_ip_address -) -> None: - """ - Function - - SwitchIssuDetailsByIpAddress._get - - Summary - Verify that _get() calls fail_json because filter is set to an - unknown ip_address - - Test - - fail_json is called because filter is set to an unknown ip_address - - Error message matches expectation - - Description - SwitchIssuDetailsByIpAddress._get is called by all getter properties. - It raises AnsibleFailJson if the user has not set filter or if - the filter is unknown, or if an unknown property name is queried. - It returns the value of the requested property if the user has filter - to an ip_address that exists on the controller. - - Expected result: - 1. fail_json is called with appropriate error message since filter - is set to an unknown ip_address. - """ - instance = issu_details_by_ip_address - - def mock_dcnm_send_issu_details(*args, **kwargs) -> Dict[str, Any]: - key = "test_image_upgrade_switch_issu_details_by_ip_address_00040a" - return responses_switch_issu_details(key) - - monkeypatch.setattr(DCNM_SEND_ISSU_DETAILS, mock_dcnm_send_issu_details) - - instance.refresh() - instance.filter = "1.1.1.1" - match = "SwitchIssuDetailsByIpAddress._get: 1.1.1.1 does not exist " - match += "on the controller." - with pytest.raises(AnsibleFailJson, match=match): - instance._get("serialNumber") # pylint: disable=protected-access - - -def test_image_upgrade_switch_issu_details_by_ip_address_00041( - monkeypatch, issu_details_by_ip_address -) -> None: - """ - Function - SwitchIssuDetailsByIpAddress._get - - Summary - Verify that _get() calls fail_json because an unknown property is queried - - Test - - fail_json is called on access of unknown property name - - Error message matches expectation - - Description - SwitchIssuDetailsByIpAddress._get is called by all getter properties. - It raises AnsibleFailJson if the user has not set filter or if - the filter is unknown, or if an unknown property name is queried. - It returns the value of the requested property if the user has filter - to an ip_address that exists on the controller. - - Expected results - 1. fail_json is called with appropriate error message since an unknown - property is queried. - """ - instance = issu_details_by_ip_address - - def mock_dcnm_send_issu_details(*args, **kwargs) -> Dict[str, Any]: - key = "test_image_upgrade_switch_issu_details_by_ip_address_00041a" - return responses_switch_issu_details(key) - - monkeypatch.setattr(DCNM_SEND_ISSU_DETAILS, mock_dcnm_send_issu_details) - - instance.refresh() - instance.filter = "172.22.150.102" - match = "SwitchIssuDetailsByIpAddress._get: 172.22.150.102 unknown " - match += "property name: FOO" - with pytest.raises(AnsibleFailJson, match=match): - instance._get("FOO") # pylint: disable=protected-access - - -def test_image_upgrade_switch_issu_details_by_ip_address_00042( - issu_details_by_ip_address, -) -> None: - """ - Function - - SwitchIssuDetailsByIpAddress._get - - Test - - _get() calls fail_json because instance.filter is not set - - Error message matches expectation - - Description - SwitchIssuDetailsByIpAddress._get is called by all getter properties. - It raises AnsibleFailJson if the user has not set filter or if - filter is unknown, or if an unknown property name is queried. - It returns the value of the requested property if the user has filter - to an ip_address that exists on the controller. - """ - with does_not_raise(): - instance = issu_details_by_ip_address - match = r"SwitchIssuDetailsByIpAddress\._get: " - match += r"set instance\.filter to a switch ipAddress " - match += r"before accessing property role\." - with pytest.raises(AnsibleFailJson, match=match): - instance.role diff --git a/tests/unit/modules/dcnm/dcnm_image_upgrade/test_image_upgrade_switch_issu_details_by_serial_number.py b/tests/unit/modules/dcnm/dcnm_image_upgrade/test_image_upgrade_switch_issu_details_by_serial_number.py deleted file mode 100644 index f0ac2dd95..000000000 --- a/tests/unit/modules/dcnm/dcnm_image_upgrade/test_image_upgrade_switch_issu_details_by_serial_number.py +++ /dev/null @@ -1,413 +0,0 @@ -# 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 - -__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 .utils import (does_not_raise, issu_details_by_serial_number_fixture, - responses_switch_issu_details) - -PATCH_MODULE_UTILS = "ansible_collections.cisco.dcnm.plugins.module_utils." -PATCH_IMAGE_UPGRADE = PATCH_MODULE_UTILS + "image_upgrade." -DCNM_SEND_ISSU_DETAILS = PATCH_IMAGE_UPGRADE + "switch_issu_details.dcnm_send" - - -def test_image_upgrade_switch_issu_details_by_serial_number_00001( - issu_details_by_serial_number, -) -> None: - """ - Function - - SwitchIssuDetailsBySerialNumber.__init__ - - Test - - fail_json is not called - - instance.properties is a dict - """ - with does_not_raise(): - instance = issu_details_by_serial_number - assert isinstance(instance.properties, dict) - - -def test_image_upgrade_switch_issu_details_by_serial_number_00002( - issu_details_by_serial_number, -) -> None: - """ - Function - - SwitchIssuDetailsBySerialNumber._init_properties - - Test - - Class properties initialized to expected values - - instance.properties is a dict - - instance.action_keys is a set - - action_keys contains expected values - """ - instance = issu_details_by_serial_number - action_keys = {"imageStaged", "upgrade", "validated"} - - instance._init_properties() # pylint: disable=protected-access - assert isinstance(instance.properties, dict) - assert isinstance(instance.properties.get("action_keys"), set) - assert instance.properties.get("action_keys") == action_keys - assert instance.properties.get("response_data") == [] - assert instance.properties.get("response") == [] - assert instance.properties.get("response_current") == {} - assert instance.properties.get("result") == [] - assert instance.properties.get("result_current") == {} - assert instance.properties.get("serial_number") is None - - -def test_image_upgrade_switch_issu_details_by_serial_number_00020( - monkeypatch, issu_details_by_serial_number -) -> None: - """ - Function - - SwitchIssuDetailsBySerialNumber.refresh - - Test - - instance.response is a list - - instance.response_current is a dict - - instance.result is a list - - instance.result_current is a dict - - instance.response_data is a list - """ - instance = issu_details_by_serial_number - - key = "test_image_upgrade_switch_issu_details_by_serial_number_00020a" - - def mock_dcnm_send_issu_details(*args, **kwargs) -> Dict[str, Any]: - print(f"mock_dcnm_send_issu_details: {responses_switch_issu_details(key)}") - return responses_switch_issu_details(key) - - monkeypatch.setattr(DCNM_SEND_ISSU_DETAILS, mock_dcnm_send_issu_details) - - instance.refresh() - assert isinstance(instance.response, list) - assert isinstance(instance.response_current, dict) - assert isinstance(instance.result, list) - assert isinstance(instance.result_current, dict) - assert isinstance(instance.response_data, list) - - -def test_image_upgrade_switch_issu_details_by_serial_number_00021( - monkeypatch, issu_details_by_serial_number -) -> None: - """ - Function - - SwitchIssuDetailsBySerialNumber.refresh - - Test - - Properties are set based on device_name - - Expected property values are returned - """ - instance = issu_details_by_serial_number - - key = "test_image_upgrade_switch_issu_details_by_serial_number_00021a" - - def mock_dcnm_send_issu_details(*args, **kwargs) -> Dict[str, Any]: - return responses_switch_issu_details(key) - - monkeypatch.setattr(DCNM_SEND_ISSU_DETAILS, mock_dcnm_send_issu_details) - - instance.refresh() - instance.filter = "FDO21120U5D" - assert instance.device_name == "leaf1" - assert instance.serial_number == "FDO21120U5D" - # change serial_number to a different switch, expect different information - instance.filter = "FDO2112189M" - assert instance.device_name == "cvd-2313-leaf" - assert instance.serial_number == "FDO2112189M" - # verify remaining properties using current serial_number - assert instance.eth_switch_id == 39890 - assert instance.fabric == "hard" - assert instance.fcoe_enabled is False - assert instance.group == "hard" - # NOTE: For "id" see switch_id below - assert instance.image_staged == "Success" - assert instance.image_staged_percent == 100 - assert instance.ip_address == "172.22.150.108" - assert instance.issu_allowed is None - assert instance.last_upg_action == "2023-Oct-06 03:43" - assert instance.mds is False - assert instance.mode == "Normal" - assert instance.model == "N9K-C93180YC-EX" - assert instance.model_type == 0 - assert instance.peer is None - assert instance.platform == "N9K" - assert instance.policy == "KR5M" - assert instance.reason == "Upgrade" - assert instance.role == "leaf" - assert instance.status == "In-Sync" - assert instance.status_percent == 100 - # NOTE: switch_id appears in the response data as "id" - # NOTE: "id" is a python reserved keyword, so we changed the property name - assert instance.switch_id == 2 - assert instance.sys_name == "cvd-2313-leaf" - assert instance.system_mode == "Normal" - assert instance.upg_groups is None - assert instance.upgrade == "Success" - assert instance.upgrade_percent == 100 - assert instance.validated == "Success" - assert instance.validated_percent == 100 - assert instance.version == "10.2(5)" - # NOTE: Two vdc_id values exist in the response data for each switch. - # NOTE: Namely, "vdcId" and "vdc_id" - # NOTE: Properties are provided for both, as follows. - # NOTE: vdc_id == vdcId - # NOTE: vdc_id2 == vdc_id - assert instance.vdc_id == 0 - assert instance.vdc_id2 == -1 - assert instance.vpc_peer is None - # NOTE: Two vpc role keys exist in the response data for each switch. - # NOTE: Namely, "vpcRole" and "vpc_role" - # NOTE: Properties are provided for both, as follows. - # NOTE: vpc_role == vpcRole - # NOTE: vpc_role2 == vpc_role - # NOTE: Values are synthesized in the response for this test - assert instance.vpc_role == "FOO" - assert instance.vpc_role2 == "BAR" - assert isinstance(instance.filtered_data, dict) - assert instance.filtered_data.get("deviceName") == "cvd-2313-leaf" - - -def test_image_upgrade_switch_issu_details_by_serial_number_00022( - monkeypatch, issu_details_by_serial_number -) -> None: - """ - Function - - SwitchIssuDetailsBySerialNumber.refresh - - Test - - instance.result_current is a dict - - instance.result_current contains expected key/values for 200 RESULT_CODE - """ - instance = issu_details_by_serial_number - - key = "test_image_upgrade_switch_issu_details_by_serial_number_00022a" - - def mock_dcnm_send_issu_details(*args, **kwargs) -> Dict[str, Any]: - return responses_switch_issu_details(key) - - monkeypatch.setattr(DCNM_SEND_ISSU_DETAILS, mock_dcnm_send_issu_details) - - instance.refresh() - assert isinstance(instance.result_current, dict) - assert instance.result_current.get("found") is True - assert instance.result_current.get("success") is True - - -def test_image_upgrade_switch_issu_details_by_serial_number_00023( - monkeypatch, issu_details_by_serial_number -) -> None: - """ - Function - - SwitchIssuDetailsBySerialNumber.refresh - - Test - - refresh calls handle_response, which calls json_fail on 404 response - - Error message matches expectation - """ - instance = issu_details_by_serial_number - - key = "test_image_upgrade_switch_issu_details_by_serial_number_00023a" - - def mock_dcnm_send_issu_details(*args, **kwargs) -> Dict[str, Any]: - return responses_switch_issu_details(key) - - monkeypatch.setattr(DCNM_SEND_ISSU_DETAILS, mock_dcnm_send_issu_details) - - match = "Bad result when retriving switch information from the controller" - with pytest.raises(AnsibleFailJson, match=match): - instance.refresh() - - -def test_image_upgrade_switch_issu_details_by_serial_number_00024( - monkeypatch, issu_details_by_serial_number -) -> None: - """ - Function - - SwitchIssuDetailsBySerialNumber.refresh - - Test - - fail_json is called on 200 response with empty DATA key - - Error message matches expectation - """ - instance = issu_details_by_serial_number - - key = "test_image_upgrade_switch_issu_details_by_serial_number_00024a" - - def mock_dcnm_send_issu_details(*args, **kwargs) -> Dict[str, Any]: - return responses_switch_issu_details(key) - - monkeypatch.setattr(DCNM_SEND_ISSU_DETAILS, mock_dcnm_send_issu_details) - - match = "SwitchIssuDetailsBySerialNumber.refresh_super: " - match += "The controller has no switch ISSU information." - with pytest.raises(AnsibleFailJson, match=match): - instance.refresh() - - -def test_image_upgrade_switch_issu_details_by_serial_number_00025( - monkeypatch, issu_details_by_serial_number -) -> None: - """ - Function - - SwitchIssuDetailsBySerialNumber.refresh - - Test - - fail_json is called on 200 response with DATA.lastOperDataObject length 0 - - Error message matches expectation - """ - instance = issu_details_by_serial_number - - key = "test_image_upgrade_switch_issu_details_by_serial_number_00025a" - - def mock_dcnm_send_issu_details(*args, **kwargs) -> Dict[str, Any]: - return responses_switch_issu_details(key) - - monkeypatch.setattr(DCNM_SEND_ISSU_DETAILS, mock_dcnm_send_issu_details) - - match = "SwitchIssuDetailsBySerialNumber.refresh_super: " - match += "The controller has no switch ISSU information." - with pytest.raises(AnsibleFailJson, match=match): - instance.refresh() - - -def test_image_upgrade_switch_issu_details_by_serial_number_00040( - monkeypatch, issu_details_by_serial_number -) -> None: - """ - Function - - SwitchIssuDetailsBySerialNumber._get - - Summary - Verify that _get() calls fail_json because filter is set to an - unknown serial_number - - Test - - fail_json is called because filter is set to an unknown serial_number - - Error message matches expectation - - Description - SwitchIssuDetailsBySerialNumber._get is called by all getter properties. - It raises AnsibleFailJson if the user has not set filter or if - filter is unknown, or if an unknown property name is queried. - It returns the value of the requested property if the user has filter - to a serial_number that exists on the controller. - - Expected result: - 1. fail_json is called with appropriate error message since filter - is set to an unknown serial_number. - """ - instance = issu_details_by_serial_number - - def mock_dcnm_send_issu_details(*args, **kwargs) -> Dict[str, Any]: - key = "test_image_upgrade_switch_issu_details_by_serial_number_00040a" - return responses_switch_issu_details(key) - - monkeypatch.setattr(DCNM_SEND_ISSU_DETAILS, mock_dcnm_send_issu_details) - - match = "SwitchIssuDetailsBySerialNumber._get: FOO00000BAR does not exist " - match += "on the controller." - - instance.refresh() - instance.filter = "FOO00000BAR" - with pytest.raises(AnsibleFailJson, match=match): - instance._get("serialNumber") # pylint: disable=protected-access - - -def test_image_upgrade_switch_issu_details_by_serial_number_00041( - monkeypatch, issu_details_by_serial_number -) -> None: - """ - Function - SwitchIssuDetailsBySerialNumber._get - - Summary - Verify that _get() calls fail_json because an unknown property is queried - - Test - - fail_json is called on access of unknown property name - - Error message matches expectation - - Description - SwitchIssuDetailsBySerialNumber._get is called by all getter properties. - It raises AnsibleFailJson if the user has not set filter or if - filter is unknown, or if an unknown property name is queried. - It returns the value of the requested property if the user has filter - to a serial_number that exists on the controller. - - Expected results - 1. fail_json is called with appropriate error message since an unknown - property is queried. - """ - instance = issu_details_by_serial_number - - def mock_dcnm_send_issu_details(*args, **kwargs) -> Dict[str, Any]: - key = "test_image_upgrade_switch_issu_details_by_serial_number_00041a" - return responses_switch_issu_details(key) - - monkeypatch.setattr(DCNM_SEND_ISSU_DETAILS, mock_dcnm_send_issu_details) - - match = "SwitchIssuDetailsBySerialNumber._get: FDO21120U5D unknown " - match += "property name: FOO" - - instance.refresh() - instance.filter = "FDO21120U5D" - with pytest.raises(AnsibleFailJson, match=match): - instance._get("FOO") # pylint: disable=protected-access - - -def test_image_upgrade_switch_issu_details_by_serial_number_00042( - issu_details_by_serial_number, -) -> None: - """ - Function - - SwitchIssuDetailsBySerialNumber._get - - Test - - _get() calls fail_json because instance.filter is not set - - Error message matches expectation - - Description - SwitchIssuDetailsBySerialNumber._get is called by all getter properties. - It raises AnsibleFailJson if the user has not set filter or if - filter is unknown, or if an unknown property name is queried. - It returns the value of the requested property if the user has filter - to a serial_number that exists on the controller. - """ - with does_not_raise(): - instance = issu_details_by_serial_number - match = r"SwitchIssuDetailsBySerialNumber\._get: " - match += r"set instance\.filter to a switch serialNumber " - match += r"before accessing property role\." - with pytest.raises(AnsibleFailJson, match=match): - instance.role diff --git a/tests/unit/modules/dcnm/dcnm_image_upgrade/test_image_validate.py b/tests/unit/modules/dcnm/dcnm_image_upgrade/test_image_validate.py new file mode 100644 index 000000000..972393346 --- /dev/null +++ b/tests/unit/modules/dcnm/dcnm_image_upgrade/test_image_validate.py @@ -0,0 +1,986 @@ +# 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 +# Some tests require calling protected methods +# pylint: disable=protected-access + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +__copyright__ = "Copyright (c) 2024 Cisco and/or its affiliates." +__author__ = "Allen Robel" + +import inspect + +import pytest +from ansible_collections.cisco.dcnm.plugins.module_utils.common.exceptions import \ + ControllerResponseError +from ansible_collections.cisco.dcnm.plugins.module_utils.common.response_handler import \ + ResponseHandler +from ansible_collections.cisco.dcnm.plugins.module_utils.common.rest_send_v2 import \ + RestSend +from ansible_collections.cisco.dcnm.plugins.module_utils.common.results import \ + Results +from ansible_collections.cisco.dcnm.plugins.module_utils.common.sender_file import \ + Sender +from ansible_collections.cisco.dcnm.tests.unit.module_utils.common.common_utils import \ + ResponseGenerator + +from .utils import (MockAnsibleModule, does_not_raise, image_validate_fixture, + params, responses_ep_image_validate, responses_ep_issu) + + +def test_image_validate_00000(image_validate) -> None: + """ + ### Classes and Methods + - ``ImageValidate`` + - ``__init__`` + + ### Test + - Class attributes are initialized to expected values. + """ + with does_not_raise(): + instance = image_validate + + assert instance.class_name == "ImageValidate" + assert instance.action == "image_validate" + assert instance.diff == {} + assert instance.payload is None + assert instance.saved_response_current == {} + assert instance.saved_result_current == {} + assert isinstance(instance.serial_numbers_done, set) + assert isinstance(instance.serial_numbers_todo, set) + + assert instance.conversion.class_name == "ConversionUtils" + assert instance.ep_image_validate.class_name == "EpImageValidate" + assert instance.issu_detail.class_name == "SwitchIssuDetailsBySerialNumber" + assert instance.wait_for_controller_done.class_name == "WaitForControllerDone" + + endpoint_path = "/appcenter/cisco/ndfc/api/v1/imagemanagement/rest/" + endpoint_path += "stagingmanagement/validate-image" + assert instance.ep_image_validate.path == endpoint_path + assert instance.ep_image_validate.verb == "POST" + + # properties + assert instance.check_interval == 10 + assert instance.check_timeout == 1800 + assert instance.non_disruptive is False + assert instance.rest_send is None + assert instance.results is None + assert instance.serial_numbers is None + + +# def test_image_validate_00010(image_validate) -> None: +# """ +# Function +# - _init_properties + +# Test +# - Class properties are initialized to expected values +# """ +# with does_not_raise(): +# instance = image_validate +# assert instance.check_interval == 10 +# assert instance.check_timeout == 1800 +# assert instance.non_disruptive is False +# assert instance.rest_send is None +# assert instance.results is None +# assert instance.serial_numbers is None + + +def test_image_validate_00200(image_validate) -> None: + """ + ### Classes and Methods + - ``ImageValidate`` + - ``prune_serial_numbers`` + + ### Summary + Verify that ``prune_serial_numbers`` prunes serial numbers that have already + been validated. + + ### Setup + - ``responses_ep_issu()`` returns 200 response indicating that + ``imageStaged`` is "none" for three serial numbers and "Success" + for two serial numbers in the serial_numbers list. + + ### Test + - ``serial_numbers`` contains only serial numbers for which + "validated" == "none" + - ``serial_numbers`` does not contain serial numbers for which + "validated" == "Success" + + ### Description + prune_serial_numbers removes serial numbers from the list for which + "validated" == "Success" + + ### Expected results + 1. instance.serial_numbers == ["FDO2112189M", "FDO211218AX", "FDO211218B5"] + 2. instance.serial_numbers != ["FDO211218FV", "FDO211218GC"] + """ + method_name = inspect.stack()[0][3] + key = f"{method_name}a" + + def responses(): + yield responses_ep_issu(key) + + gen_responses = ResponseGenerator(responses()) + + sender = Sender() + sender.ansible_module = MockAnsibleModule() + sender.gen = gen_responses + rest_send = RestSend(params) + rest_send.response_handler = ResponseHandler() + rest_send.sender = sender + + with does_not_raise(): + instance = image_validate + instance.results = Results() + instance.rest_send = rest_send + instance.issu_detail.rest_send = rest_send + instance.issu_detail.results = Results() + instance.serial_numbers = [ + "FDO2112189M", + "FDO211218AX", + "FDO211218B5", + "FDO211218FV", + "FDO211218GC", + ] + instance.prune_serial_numbers() + assert isinstance(instance.serial_numbers, list) + assert len(instance.serial_numbers) == 3 + assert "FDO2112189M" in instance.serial_numbers + assert "FDO211218AX" in instance.serial_numbers + assert "FDO211218B5" in instance.serial_numbers + assert "FDO211218FV" not in instance.serial_numbers + assert "FDO211218GC" not in instance.serial_numbers + + +def test_image_validate_00300(image_validate) -> None: + """ + ### Classes and Methods + - ``ImageValidate`` + - ``validate_serial_numbers`` + + ### Summary + Verify that ``validate_serial_numbers`` raises ``ControllerResponseError`` + appropriately. + + ### Setup + - ``responses_ep_issu()`` returns 200 response indicating that + ``validated`` is "Success" for one serial number and "Failed" + for the other serial number in the serial_numbers list. + + ### Test + - ``ControllerResponseError`` is not called when ``validated`` == "Success" + - ``ControllerResponseError`` is called when ``validated`` == "Failed" + + ### Description + ``validate_serial_numbers`` checks the ``validated`` status for each serial + number and raises ``ControllerResponseError`` if ``validated`` == "Failed" + for any serial number. + + ### Expectations + + FDO21120U5D should pass since ``validated`` == "Success" + FDO2112189M should fail since ``validated`` == "Failed" + """ + method_name = inspect.stack()[0][3] + key = f"{method_name}a" + + def responses(): + yield responses_ep_issu(key) + + gen_responses = ResponseGenerator(responses()) + + sender = Sender() + sender.ansible_module = MockAnsibleModule() + sender.gen = gen_responses + rest_send = RestSend(params) + rest_send.unit_test = True + rest_send.response_handler = ResponseHandler() + rest_send.sender = sender + + with does_not_raise(): + instance = image_validate + instance.results = Results() + instance.rest_send = rest_send + instance.issu_detail.rest_send = rest_send + instance.issu_detail.results = Results() + instance.serial_numbers = ["FDO21120U5D", "FDO2112189M"] + + match = "ImageValidate.validate_serial_numbers: " + match += "image validation is failing for the following switch: " + match += "cvd-2313-leaf, 172.22.150.108, FDO2112189M. If this " + match += "persists, check the switch connectivity to the " + match += "controller and try again." + + with pytest.raises(ControllerResponseError, match=match): + instance.validate_serial_numbers() + + +def test_image_validate_00400(image_validate) -> None: + """ + ### Classes and Methods + - ``ImageValidate`` + - ``_wait_for_image_validate_to_complete`` + + ### Summary + Verify proper behavior of ``_wait_for_image_validate_to_complete`` when + ``validated`` is "Success" for all serial numbers. + + ### Setup + - ``responses_ep_issu()`` returns 200 response indicating that + ``validated`` is "Success" for all serial numbers in the + serial_numbers list. + + ### Test + - "validated" == "Success" for all serial numbers so + ``ControllerResponseError`` is not raised. + - ``serial_numbers_done`` is a set(). + - ``serial_numbers_done`` has length 2. + - ``serial_numbers_done`` == ``serial_numbers``. + + Description + ``_wait_for_image_validate_to_complete`` looks at the "validated" + status for each serial number and waits for it to be "Success" or "Failed". + In the case where all serial numbers are "Success", the module returns. + In the case where any serial number is "Failed", the module calls fail_json. + """ + method_name = inspect.stack()[0][3] + key = f"{method_name}a" + + def responses(): + yield responses_ep_issu(key) + + gen_responses = ResponseGenerator(responses()) + + sender = Sender() + sender.ansible_module = MockAnsibleModule() + sender.gen = gen_responses + rest_send = RestSend(params) + rest_send.unit_test = True + rest_send.response_handler = ResponseHandler() + rest_send.sender = sender + + with does_not_raise(): + instance = image_validate + instance.results = Results() + instance.rest_send = rest_send + instance.issu_detail.rest_send = rest_send + instance.issu_detail.results = Results() + instance.serial_numbers = ["FDO21120U5D", "FDO2112189M"] + instance._wait_for_image_validate_to_complete() + + assert isinstance(instance.serial_numbers_done, set) + assert len(instance.serial_numbers_done) == 2 + assert "FDO21120U5D" in instance.serial_numbers_done + assert "FDO2112189M" in instance.serial_numbers_done + + +def test_image_validate_00410(image_validate) -> None: + """ + ### Classes and Methods + - ``ImageValidate`` + - ``_wait_for_image_validate_to_complete`` + + ### Summary + Verify proper behavior of ``_wait_for_image_validate_to_complete`` when + ''validated'' is "Failed" for one serial number and ``validated`` + is "Success" for one serial number. + + ### Test + - ``serial_numbers_done`` is a set() + - ``serial_numbers_done`` has length 1 + - ``serial_numbers_done`` contains FDO21120U5D since + "validated" == "Success" + - ``serial_numbers_done`` does not contain FDO2112189M since + "validated" == "Failed" + - ``ValueError`` is raised on serial number FDO2112189M + because "validated" is "Failed". + - Error message matches expectation. + + ### Description + ``_wait_for_image_validate_to_complete`` looks at the "validated" status + for each serial number and waits for it to be "Success" or "Failed". + In the case where all serial numbers are "Success", the module returns. + In the case where any serial number is "Failed", the module raises + ``ValueError``. + """ + + method_name = inspect.stack()[0][3] + key = f"{method_name}a" + + def responses(): + yield responses_ep_issu(key) + + gen_responses = ResponseGenerator(responses()) + + sender = Sender() + sender.ansible_module = MockAnsibleModule() + sender.gen = gen_responses + rest_send = RestSend(params) + rest_send.unit_test = True + rest_send.response_handler = ResponseHandler() + rest_send.sender = sender + + with does_not_raise(): + instance = image_validate + instance.results = Results() + instance.rest_send = rest_send + instance.issu_detail.rest_send = rest_send + instance.issu_detail.results = Results() + instance.serial_numbers = ["FDO21120U5D", "FDO2112189M"] + + match = "Seconds remaining 1790: validate image Failed for " + match += "cvd-2313-leaf, 172.22.150.108, FDO2112189M, " + match += "image validated percent: 90. Check the switch e.g. " + match += "show install log detail, show incompatibility-all nxos " + match += ". Or check Operations > Image Management > " + match += "Devices > View Details > Validate on the controller " + match += "GUI for more details." + + with pytest.raises(ValueError, match=match): + instance._wait_for_image_validate_to_complete() + assert isinstance(instance.serial_numbers_done, set) + assert len(instance.serial_numbers_done) == 1 + assert "FDO21120U5D" in instance.serial_numbers_done + assert "FDO2112189M" not in instance.serial_numbers_done + + +def test_image_validate_00420(image_validate) -> None: + """ + ### Classes and Methods + - ``ImageValidate`` + - ``_wait_for_image_validate_to_complete`` + + ### Summary + Verify proper behavior of ``_wait_for_image_validate_to_complete`` when + timeout is reached for one serial number (i.e. ``validated`` is + "In-Progress") and ``validated`` is "Success" for one serial number. + + ### Setup + - ``responses_ep_issu()`` returns 200 response indicating that + ``validated`` is "Success" for one of the serial numbers in the + ``serial_numbers`` list and "In-Progress" for the other. + + ### Test + - ``serial_numbers_done`` is a set() + - ``serial_numbers_done`` has length 1 + - ``serial_numbers_done`` contains FDO21120U5D since + "validated" == "Success" + - ``serial_numbers_done`` does not contain FDO2112189M since + "validated" == "In-Progress" + - ``ValueError`` is raised due to timeout because FDO2112189M + ``validated`` == "In-Progress". + - Error message matches expectation. + + ### Description + See test_image_validate_00410 for functional details. + """ + method_name = inspect.stack()[0][3] + key = f"{method_name}a" + + def responses(): + yield responses_ep_issu(key) + + gen_responses = ResponseGenerator(responses()) + + sender = Sender() + sender.ansible_module = MockAnsibleModule() + sender.gen = gen_responses + rest_send = RestSend(params) + rest_send.unit_test = True + rest_send.send_interval = 1 + rest_send.timeout = 1 + rest_send.response_handler = ResponseHandler() + rest_send.sender = sender + + with does_not_raise(): + instance = image_validate + instance.results = Results() + instance.rest_send = rest_send + instance.check_timeout = 1 + instance.check_interval = 1 + instance.issu_detail.rest_send = rest_send + instance.issu_detail.results = Results() + instance.serial_numbers = ["FDO21120U5D", "FDO2112189M"] + + match = "ImageValidate._wait_for_image_validate_to_complete: " + match += "Timed out waiting for image validation to complete. " + match += "serial_numbers_done: FDO21120U5D, " + match += "serial_numbers_todo: FDO21120U5D,FDO2112189M" + + with pytest.raises(ValueError, match=match): + instance._wait_for_image_validate_to_complete() + assert isinstance(instance.serial_numbers_done, set) + assert len(instance.serial_numbers_done) == 1 + assert "FDO21120U5D" in instance.serial_numbers_done + assert "FDO2112189M" not in instance.serial_numbers_done + + +def test_image_validate_00500(image_validate) -> None: + """ + ### Classes and Methods + - ``ImageValidate`` + - ``wait_for_controller`` + + ### Summary + Verify proper behavior of ``wait_for_controller`` when no actions + are pending. + + ### Setup + - ``responses_ep_issu()`` returns 200 response indicating that no + actions are "In-Progress". + + ### Test + - ``wait_for_controller_done.done`` is a set(). + - ``serial_numbers_done`` has length 2. + - ``serial_numbers_done`` contains all serial numbers in + ``serial_numbers``. + - Exception is not raised. + + ### Description + ``wait_for_controller`` waits until staging, validation, and upgrade + actions are complete for all serial numbers. It calls + ``SwitchIssuDetailsBySerialNumber.actions_in_progress()`` and expects + this to return False. ``actions_in_progress()`` returns True until none + of the following keys has a value of "In-Progress": + - ``imageStaged`` + - ``upgrade`` + - ``validated`` + """ + method_name = inspect.stack()[0][3] + key = f"{method_name}a" + + def responses(): + yield responses_ep_issu(key) + + gen_responses = ResponseGenerator(responses()) + + sender = Sender() + sender.ansible_module = MockAnsibleModule() + sender.gen = gen_responses + rest_send = RestSend(params) + rest_send.unit_test = True + rest_send.send_interval = 1 + rest_send.timeout = 1 + rest_send.response_handler = ResponseHandler() + rest_send.sender = sender + + with does_not_raise(): + instance = image_validate + instance.results = Results() + instance.rest_send = rest_send + instance.check_timeout = 1 + instance.check_interval = 1 + instance.issu_detail.rest_send = rest_send + instance.issu_detail.results = Results() + instance.serial_numbers = ["FDO21120U5D", "FDO2112189M"] + instance.wait_for_controller() + + assert isinstance(instance.wait_for_controller_done.done, set) + assert len(instance.wait_for_controller_done.done) == 2 + assert "FDO21120U5D" in instance.wait_for_controller_done.todo + assert "FDO2112189M" in instance.wait_for_controller_done.todo + assert "FDO21120U5D" in instance.wait_for_controller_done.done + assert "FDO2112189M" in instance.wait_for_controller_done.done + + +def test_image_validate_00510(image_validate) -> None: + """ + ### Classes and Methods + - ``ImageValidate`` + - ``wait_for_controller`` + + ### Summary + Verify proper behavior of ``wait_for_controller`` when there is a timeout + waiting for actions on the controller to complete. + + ### Setup + - ``responses_ep_issu()`` returns 200 response indicating that + ``imageStaged`` is "In-Progress" for one of the serial numbers in the + ``serial_numbers`` list. + + ### Test + - `serial_numbers_done` is a set() + - ``serial_numbers_done`` has length 1 + - ``serial_numbers_done`` contains FDO21120U5D + because ``validated`` == "Success" + - ``serial_numbers_done`` does not contain FDO2112189M + - ``ValueError`` is raised due to timeout because FDO2112189M + ``validated`` == "In-Progress" + + ### Description + See test_image_validate_00500 for functional details. + """ + method_name = inspect.stack()[0][3] + key = f"{method_name}a" + + def responses(): + yield responses_ep_issu(key) + + gen_responses = ResponseGenerator(responses()) + + sender = Sender() + sender.ansible_module = MockAnsibleModule() + sender.gen = gen_responses + rest_send = RestSend(params) + rest_send.unit_test = True + rest_send.send_interval = 1 + rest_send.timeout = 1 + rest_send.response_handler = ResponseHandler() + rest_send.sender = sender + + with does_not_raise(): + instance = image_validate + instance.results = Results() + instance.rest_send = rest_send + instance.check_timeout = 1 + instance.check_interval = 1 + instance.issu_detail.rest_send = rest_send + instance.issu_detail.results = Results() + instance.serial_numbers = ["FDO21120U5D", "FDO2112189M"] + + match = r"ImageValidate\.wait_for_controller:\s+" + match += r"Error WaitForControllerDone\.commit:\s+" + match += r"Timed out after 1 seconds waiting for controller actions to\s+" + match += r"complete on items: \['FDO21120U5D', 'FDO2112189M'\]\.\s+" + match += r"The following items did complete: FDO21120U5D\." + + with pytest.raises(ValueError, match=match): + instance.wait_for_controller() + assert isinstance(instance.wait_for_controller_done.done, set) + assert len(instance.wait_for_controller_done.done) == 1 + assert "FDO21120U5D" in instance.wait_for_controller_done.todo + assert "FDO2112189M" in instance.wait_for_controller_done.todo + assert "FDO21120U5D" in instance.wait_for_controller_done.done + assert "FDO2112189M" not in instance.wait_for_controller_done.done + + +MATCH_00600 = r"ImageValidate\.check_interval:\s+" +MATCH_00600 += r"must be a positive integer or zero\." + + +@pytest.mark.parametrize( + "arg, value, context", + [ + (True, None, pytest.raises(TypeError, match=MATCH_00600)), + (-1, None, pytest.raises(ValueError, match=MATCH_00600)), + (10, 10, does_not_raise()), + (0, 0, does_not_raise()), + ("a", None, pytest.raises(TypeError, match=MATCH_00600)), + ], +) +def test_image_validate_00600(image_validate, arg, value, context) -> None: + """ + ### Classes and Methods + - ``ImageValidate`` + - ``check_interval`` + + ### Summary + Verify that ``check_interval`` argument validation works as expected. + + ### Test + - Verify input arguments to ``check_interval`` property + + ### Description + ``check_interval`` expects a positive integer value, or zero. + """ + with does_not_raise(): + instance = image_validate + with context: + instance.check_interval = arg + if value is not None: + assert instance.check_interval == value + + +MATCH_00700 = r"ImageValidate\.check_timeout:\s+" +MATCH_00700 += r"must be a positive integer or zero\." + + +@pytest.mark.parametrize( + "arg, value, context", + [ + (True, None, pytest.raises(TypeError, match=MATCH_00700)), + (-1, None, pytest.raises(ValueError, match=MATCH_00700)), + (10, 10, does_not_raise()), + (0, 0, does_not_raise()), + ("a", None, pytest.raises(TypeError, match=MATCH_00700)), + ], +) +def test_image_validate_00700(image_validate, arg, value, context) -> None: + """ + ### Classes and Methods + - ``ImageValidate`` + - ``check_timeout`` + + ### Summary + Verify that ``check_timeout`` argument validation works as expected. + + ### Test + - Verify input arguments to ``check_timeout`` property + + ### Description + ``check_timeout`` expects a positive integer value, or zero. + """ + with does_not_raise(): + instance = image_validate + with context: + instance.check_timeout = arg + if value is not None: + assert instance.check_timeout == value + + +MATCH_00800 = r"ImageValidate\.serial_numbers:\s+" +MATCH_00800 += r"must be a python list of switch serial numbers\." + + +@pytest.mark.parametrize( + "arg, value, context", + [ + ("foo", None, pytest.raises(TypeError, match=MATCH_00800)), + (10, None, pytest.raises(TypeError, match=MATCH_00800)), + (["DD001115F", 10], None, pytest.raises(TypeError, match=MATCH_00800)), + (["DD001115F"], ["DD001115F"], does_not_raise()), + ], +) +def test_image_validate_00800(image_validate, arg, value, context) -> None: + """ + ### Classes and Methods + - ``ImageValidate`` + - ``serial_numbers`` + + ### Summary + Verify that ``serial_numbers`` argument validation works as expected. + + ### Test + - ``TypeError`` is raised if the input is not a list. + - ``TypeError`` is raised if the input is not a list of strings. + + ### Description + serial_numbers expects a list of serial numbers. + """ + with does_not_raise(): + instance = image_validate + with context: + instance.serial_numbers = arg + if value is not None: + assert instance.serial_numbers == value + + +MATCH_00900 = r"ImageValidate\.validate_commit_parameters:\s+" +MATCH_00900 += r"serial_numbers must be set before calling commit\(\)\." + + +@pytest.mark.parametrize( + "serial_numbers_is_set, expected", + [ + (True, does_not_raise()), + (False, pytest.raises(ValueError, match=MATCH_00900)), + ], +) +def test_image_validate_00900(image_validate, serial_numbers_is_set, expected) -> None: + """ + ### Classes and Methods + - ``ImageValidate`` + - ``commit`` + + ### Summary + Verify that ``commit`` raises ``ValueError`` appropriately based on value of + ``serial_numbers``. + + ### Setup + - responses_ep_issu() returns 200 responses. + - responses_ep_version() returns a 200 response. + - responses_ep_image_validate() returns a 200 response. + + ### Test + - ``ValueError`` is raised when serial_numbers is not set. + - ``ValueError`` is not raised when serial_numbers is set. + """ + method_name = inspect.stack()[0][3] + key = f"{method_name}a" + + def responses(): + yield responses_ep_issu(key) + yield responses_ep_issu(key) + yield responses_ep_issu(key) + + gen_responses = ResponseGenerator(responses()) + + sender = Sender() + sender.ansible_module = MockAnsibleModule() + sender.gen = gen_responses + rest_send = RestSend(params) + rest_send.unit_test = True + rest_send.send_interval = 1 + rest_send.timeout = 1 + rest_send.response_handler = ResponseHandler() + rest_send.sender = sender + + with does_not_raise(): + instance = image_validate + instance.results = Results() + instance.rest_send = rest_send + instance.check_timeout = 1 + instance.check_interval = 1 + instance.issu_detail.rest_send = rest_send + instance.issu_detail.results = Results() + + if serial_numbers_is_set: + instance.serial_numbers = ["FDO21120U5D"] + with expected: + instance.commit() + + +def test_image_validate_00920(image_validate) -> None: + """ + ### Classes and Methods + - ``ImageValidate`` + ` ``commit`` + + ### Summary + Verify that commit() sets result, response, and response_data + appropriately when serial_numbers is empty. + + ### Setup + - ``serial_numbers`` is set to [] (empty list) + + ### Test + - commit() sets the following to expected values: + - self.result, self.result_current + - self.response, self.response_current + - self.response_data + + ### Description + When len(serial_numbers) == 0, commit() will set result and + response properties, and return without doing anything else. + """ + + def responses(): + yield None + + gen_responses = ResponseGenerator(responses()) + + sender = Sender() + sender.ansible_module = MockAnsibleModule() + sender.gen = gen_responses + rest_send = RestSend(params) + rest_send.unit_test = True + rest_send.send_interval = 1 + rest_send.timeout = 1 + rest_send.response_handler = ResponseHandler() + rest_send.sender = sender + + with does_not_raise(): + instance = image_validate + instance.results = Results() + instance.rest_send = rest_send + instance.check_timeout = 1 + instance.check_interval = 1 + instance.issu_detail.rest_send = rest_send + instance.issu_detail.results = Results() + instance.serial_numbers = [] + instance.commit() + + response_msg = "No images to validate." + assert instance.results.result == [ + {"success": True, "changed": False, "sequence_number": 1} + ] + assert instance.results.result_current == { + "success": True, + "changed": False, + "sequence_number": 1, + } + assert instance.results.response_current == { + "response": response_msg, + "sequence_number": 1, + } + assert instance.results.response == [instance.results.response_current] + assert instance.results.response_data == [{"response": response_msg}] + + +def test_image_validate_00930(image_validate) -> None: + """ + ### Classes and Methods + - ``ImageValidate`` + ` ``commit`` + + ### Summary + Verify that ``ControllerResponseError`` is raised on 500 response from + the controller. + + ### Setup + - ``responses_ep_issu()`` returns 200 responses. + - ``responses_ep_version()`` returns a 200 response. + - ``responses_ep_image_stage()`` returns a 500 response. + + ### Test + - commit() raises ``ControllerResponseError`` + + ### Description + commit() raises ``ControllerResponseError`` on non-success response + from the controller. + """ + method_name = inspect.stack()[0][3] + key = f"{method_name}a" + + def responses(): + # ImageValidate().prune_serial_numbers + yield responses_ep_issu(key) + # ImageValidate().validate_serial_numbers + yield responses_ep_issu(key) + # RestSend.commit_normal_mode + yield responses_ep_image_validate(key) + + gen_responses = ResponseGenerator(responses()) + + sender = Sender() + sender.ansible_module = MockAnsibleModule() + sender.gen = gen_responses + rest_send = RestSend(params) + rest_send.unit_test = True + rest_send.send_interval = 1 + rest_send.timeout = 1 + rest_send.response_handler = ResponseHandler() + rest_send.sender = sender + + with does_not_raise(): + instance = image_validate + instance.results = Results() + instance.rest_send = rest_send + instance.check_timeout = 1 + instance.check_interval = 1 + instance.issu_detail.rest_send = rest_send + instance.issu_detail.results = Results() + instance.serial_numbers = ["FDO21120U5D"] + + match = r"ImageValidate\.commit:\s+" + match += r"failed\. Controller response:.*" + with pytest.raises(ControllerResponseError, match=match): + instance.commit() + assert instance.results.result == [ + {"success": False, "changed": False, "sequence_number": 1} + ] + assert instance.results.response_current["RETURN_CODE"] == 500 + + +def test_image_validate_00940(image_validate) -> None: + """ + ### Classes and Methods + - ``ImageValidate`` + ` ``commit`` + + ### Summary + Verify that commit() sets self.diff to expected values on 200 response + from the controller for an image stage request. + + ### Setup + - ``responses_ep_issu()`` returns 200 responses. + - ``responses_ep_version()`` returns a 200 response with controller + version 12.1.3b. + - ``responses_ep_image_stage()`` returns a 200 response. + + ### Test + - commit() sets self.diff to the expected values + """ + method_name = inspect.stack()[0][3] + key_a = f"{method_name}a" + key_b = f"{method_name}b" + + def responses(): + # ImageValidate.prune_serial_numbers() + yield responses_ep_issu(key_a) + # ImageValidate.validate_serial_numbers() + yield responses_ep_issu(key_a) + # ImageValidate().wait_for_controller() + yield responses_ep_issu(key_a) + # ImageStage().commit() -> ImageStage().rest_send.commit() + yield responses_ep_image_validate(key_a) + # ImageValidate._wait_for_image_validate_to_complete() + yield responses_ep_issu(key_b) + + gen_responses = ResponseGenerator(responses()) + + sender = Sender() + sender.ansible_module = MockAnsibleModule() + sender.gen = gen_responses + rest_send = RestSend(params) + rest_send.unit_test = True + rest_send.send_interval = 1 + rest_send.timeout = 1 + rest_send.response_handler = ResponseHandler() + rest_send.sender = sender + + with does_not_raise(): + instance = image_validate + instance.results = Results() + instance.rest_send = rest_send + instance.check_timeout = 1 + instance.check_interval = 1 + instance.issu_detail.rest_send = rest_send + instance.issu_detail.results = Results() + instance.serial_numbers = ["FDO21120U5D"] + instance.commit() + + assert instance.results.result_current == { + "success": True, + "changed": True, + "sequence_number": 1, + } + assert instance.results.diff[0]["172.22.150.102"]["policy_name"] == "KR5M" + assert instance.results.diff[0]["172.22.150.102"]["ip_address"] == "172.22.150.102" + assert instance.results.diff[0]["172.22.150.102"]["serial_number"] == "FDO21120U5D" + + +MATCH_01000 = "ImageValidate.non_disruptive: " +MATCH_01000 += "instance.non_disruptive must be a boolean." + + +@pytest.mark.parametrize( + "value, expected", + [ + (True, does_not_raise()), + (False, does_not_raise()), + (None, pytest.raises(TypeError, match=MATCH_01000)), + ("FOO", pytest.raises(TypeError, match=MATCH_01000)), + (10, pytest.raises(TypeError, match=MATCH_01000)), + ([1, 2], pytest.raises(TypeError, match=MATCH_01000)), + ({1, 2}, pytest.raises(TypeError, match=MATCH_01000)), + ({"a": 1, "b": 2}, pytest.raises(TypeError, match=MATCH_01000)), + ], +) +def test_image_validate_01000(image_validate, value, expected) -> None: + """ + ### Classes and Methods + - ``ImageValidate`` + - ``non_disruptive.setter`` + + ### Test + - ``TypeError`` is raised if ``non_disruptive`` is not a boolean. + + """ + with does_not_raise(): + instance = image_validate + assert instance.class_name == "ImageValidate" + + with expected: + instance.non_disruptive = value diff --git a/tests/unit/modules/dcnm/dcnm_image_upgrade/test_switch_issu_details_by_device_name.py b/tests/unit/modules/dcnm/dcnm_image_upgrade/test_switch_issu_details_by_device_name.py new file mode 100644 index 000000000..b3e4900b2 --- /dev/null +++ b/tests/unit/modules/dcnm/dcnm_image_upgrade/test_switch_issu_details_by_device_name.py @@ -0,0 +1,490 @@ +# 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 +# pylint: disable=protected-access + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +__copyright__ = "Copyright (c) 2024 Cisco and/or its affiliates." +__author__ = "Allen Robel" + +import inspect + +import pytest +from ansible_collections.cisco.dcnm.plugins.module_utils.common.response_handler import \ + ResponseHandler +from ansible_collections.cisco.dcnm.plugins.module_utils.common.rest_send_v2 import \ + RestSend +from ansible_collections.cisco.dcnm.plugins.module_utils.common.results import \ + Results +from ansible_collections.cisco.dcnm.plugins.module_utils.common.sender_file import \ + Sender +from ansible_collections.cisco.dcnm.tests.unit.module_utils.common.common_utils import \ + ResponseGenerator + +from .utils import (MockAnsibleModule, does_not_raise, + issu_details_by_device_name_fixture, params, + responses_ep_issu) + + +def test_switch_issu_details_by_device_name_00000( + issu_details_by_device_name, +) -> None: + """ + ### Classes and Methods + - ``SwitchIssuDetailsByDeviceName`` + - ``__init__`` + + ### Summary + Verify class initialization. + + ### Test + - Class properties initialized to expected values. + - ``action_keys`` is a set. + - ``action_keys`` contains expected values. + - Exception is not raised. + """ + with does_not_raise(): + instance = issu_details_by_device_name + + action_keys = {"imageStaged", "upgrade", "validated"} + + assert isinstance(instance._action_keys, set) + assert instance._action_keys == action_keys + assert instance.data == {} + assert instance.rest_send is None + assert instance.results is None + + assert instance.ep_issu.class_name == "EpIssu" + assert instance.conversion.class_name == "ConversionUtils" + + +def test_switch_issu_details_by_device_name_00100(issu_details_by_device_name) -> None: + """ + ### Classes and Methods + - ``SwitchIssuDetailsBySerialNumber`` + - ``refresh`` + + ### Test + - instance.results.response is a list + - instance.results.response_current is a dict + - instance.results.result is a list + - instance.results.result_current is a dict + - instance.results.response_data is a list + """ + method_name = inspect.stack()[0][3] + key = f"{method_name}a" + + def responses(): + yield responses_ep_issu(key) + + gen_responses = ResponseGenerator(responses()) + + sender = Sender() + sender.ansible_module = MockAnsibleModule() + sender.gen = gen_responses + rest_send = RestSend(params) + rest_send.unit_test = True + rest_send.response_handler = ResponseHandler() + rest_send.sender = sender + + with does_not_raise(): + instance = issu_details_by_device_name + instance.results = Results() + instance.rest_send = rest_send + instance.refresh() + + assert isinstance(instance.results.response, list) + assert isinstance(instance.results.response_current, dict) + assert isinstance(instance.results.result, list) + assert isinstance(instance.results.result_current, dict) + assert isinstance(instance.results.response_data, list) + + +def test_switch_issu_details_by_device_name_00110(issu_details_by_device_name) -> None: + """ + ### Classes and Methods + - ``SwitchIssuDetailsByDeviceName`` + - ``refresh`` + + ### Test + - Properties are set based on device_name + - Expected property values are returned + """ + method_name = inspect.stack()[0][3] + key = f"{method_name}a" + + def responses(): + yield responses_ep_issu(key) + + gen_responses = ResponseGenerator(responses()) + + sender = Sender() + sender.ansible_module = MockAnsibleModule() + sender.gen = gen_responses + rest_send = RestSend(params) + rest_send.unit_test = True + rest_send.response_handler = ResponseHandler() + rest_send.sender = sender + + with does_not_raise(): + instance = issu_details_by_device_name + instance.results = Results() + instance.rest_send = rest_send + instance.refresh() + instance.filter = "leaf1" + + assert instance.device_name == "leaf1" + assert instance.serial_number == "FDO21120U5D" + # change device_name to a different switch, expect different information + instance.filter = "cvd-2313-leaf" + assert instance.device_name == "cvd-2313-leaf" + assert instance.serial_number == "FDO2112189M" + # verify remaining properties using current device_name + assert instance.eth_switch_id == 39890 + assert instance.fabric == "hard" + assert instance.fcoe_enabled is False + assert instance.group == "hard" + # NOTE: For "id" see switch_id below + assert instance.image_staged == "Success" + assert instance.image_staged_percent == 100 + assert instance.ip_address == "172.22.150.108" + assert instance.issu_allowed is None + assert instance.last_upg_action == "2023-Oct-06 03:43" + assert instance.mds is False + assert instance.mode == "Normal" + assert instance.model == "N9K-C93180YC-EX" + assert instance.model_type == 0 + assert instance.peer is None + assert instance.platform == "N9K" + assert instance.policy == "KR5M" + assert instance.reason == "Upgrade" + assert instance.role == "leaf" + assert instance.status == "In-Sync" + assert instance.status_percent == 100 + # NOTE: switch_id appears in the response data as "id" + # NOTE: "id" is a python reserved keyword, so we changed the property name + assert instance.switch_id == 2 + assert instance.sys_name == "cvd-2313-leaf" + assert instance.system_mode == "Normal" + assert instance.upg_groups is None + assert instance.upgrade == "Success" + assert instance.upgrade_percent == 100 + assert instance.validated == "Success" + assert instance.validated_percent == 100 + assert instance.version == "10.2(5)" + # NOTE: Two vdc_id values exist in the response data for each switch. + # NOTE: Namely, "vdcId" and "vdc_id" + # NOTE: Properties are provided for both, as follows. + # NOTE: vdc_id == vdcId + # NOTE: vdc_id2 == vdc_id + assert instance.vdc_id == 0 + assert instance.vdc_id2 == -1 + assert instance.vpc_peer is None + # NOTE: Two vpc role keys exist in the response data for each switch. + # NOTE: Namely, "vpcRole" and "vpc_role" + # NOTE: Properties are provided for both, as follows. + # NOTE: vpc_role == vpcRole + # NOTE: vpc_role2 == vpc_role + # NOTE: Values are synthesized in the response for this test + assert instance.vpc_role == "FOO" + assert instance.vpc_role2 == "BAR" + assert isinstance(instance.filtered_data, dict) + assert instance.filtered_data.get("deviceName") == "cvd-2313-leaf" + + +def test_switch_issu_details_by_device_name_00120(issu_details_by_device_name) -> None: + """ + ### Classes and Methods + - ``SwitchIssuDetailsByDeviceName`` + - ``refresh`` + + ### Test + - ``results.result_current`` is a dict. + - ``results.result_current`` contains expected key/values + for 200 RESULT_CODE. + """ + method_name = inspect.stack()[0][3] + key = f"{method_name}a" + + def responses(): + yield responses_ep_issu(key) + + gen_responses = ResponseGenerator(responses()) + + sender = Sender() + sender.ansible_module = MockAnsibleModule() + sender.gen = gen_responses + rest_send = RestSend(params) + rest_send.unit_test = True + rest_send.response_handler = ResponseHandler() + rest_send.sender = sender + + with does_not_raise(): + instance = issu_details_by_device_name + instance.results = Results() + instance.rest_send = rest_send + instance.refresh() + assert isinstance(instance.results.result_current, dict) + assert instance.results.result_current.get("found") is True + assert instance.results.result_current.get("success") is True + + +def test_switch_issu_details_by_device_name_00130(issu_details_by_device_name) -> None: + """ + ### Classes and Methods + - ``SwitchIssuDetailsByDeviceName`` + - ``refresh`` + + ### Summary + Verify behavior when controller response is 404. + + ### Test + - ``ValueError`` is raised. + - Error message matches expectation. + """ + method_name = inspect.stack()[0][3] + key = f"{method_name}a" + + def responses(): + yield responses_ep_issu(key) + + gen_responses = ResponseGenerator(responses()) + + sender = Sender() + sender.ansible_module = MockAnsibleModule() + sender.gen = gen_responses + rest_send = RestSend(params) + rest_send.unit_test = True + rest_send.response_handler = ResponseHandler() + rest_send.sender = sender + + with does_not_raise(): + instance = issu_details_by_device_name + instance.results = Results() + instance.rest_send = rest_send + + match = r"SwitchIssuDetailsByDeviceName\.refresh_super:\s+" + match += r"Bad result when retriving switch ISSU details from the\s+" + match += r"controller\." + with pytest.raises(ValueError, match=match): + instance.refresh() + + +def test_switch_issu_details_by_device_name_00140(issu_details_by_device_name) -> None: + """ + ### Classes and Methods + - ``SwitchIssuDetailsByDeviceName`` + - ``refresh`` + + ### Test + - ``ValueError`` is raised on 200 response with empty DATA key. + - Error message matches expectation. + """ + method_name = inspect.stack()[0][3] + key = f"{method_name}a" + + def responses(): + yield responses_ep_issu(key) + + gen_responses = ResponseGenerator(responses()) + + sender = Sender() + sender.ansible_module = MockAnsibleModule() + sender.gen = gen_responses + rest_send = RestSend(params) + rest_send.unit_test = True + rest_send.response_handler = ResponseHandler() + rest_send.sender = sender + + with does_not_raise(): + instance = issu_details_by_device_name + instance.results = Results() + instance.rest_send = rest_send + + match = r"SwitchIssuDetailsByDeviceName\.refresh_super:\s+" + match += r"The controller has no switch ISSU information\." + with pytest.raises(ValueError, match=match): + instance.refresh() + + +def test_switch_issu_details_by_device_name_00150(issu_details_by_device_name) -> None: + """ + ### Classes and Methods + - ``SwitchIssuDetailsByDeviceName`` + - ``refresh`` + + ### Test + - ``ValueError`` is raised on 200 response with + DATA.lastOperDataObject length 0. + - Error message matches expectation. + """ + method_name = inspect.stack()[0][3] + key = f"{method_name}a" + + def responses(): + yield responses_ep_issu(key) + + gen_responses = ResponseGenerator(responses()) + + sender = Sender() + sender.ansible_module = MockAnsibleModule() + sender.gen = gen_responses + rest_send = RestSend(params) + rest_send.unit_test = True + rest_send.response_handler = ResponseHandler() + rest_send.sender = sender + + with does_not_raise(): + instance = issu_details_by_device_name + instance.results = Results() + instance.rest_send = rest_send + + match = r"SwitchIssuDetailsByDeviceName\.refresh_super:\s+" + match += r"The controller has no switch ISSU information\." + with pytest.raises(ValueError, match=match): + instance.refresh() + + +def test_switch_issu_details_by_device_name_00200(issu_details_by_device_name) -> None: + """ + ### Classes and Methods + - ``SwitchIssuDetailsByDeviceName`` + - ``_get`` + + ### Summary + Verify that _get() raises ``ValueError`` because filter is set to an + unknown device_name + + ### Test + - ``ValueError`` is raised because filter is set to an unknown + device_name. + - Error message matches expectation. + + ### Description + ``SwitchIssuDetailsByDeviceName._get`` is called by all getter + properties. It raises ``ValueError`` in the following cases: + + - If the user has not set filter. + - If filter is unknown. + - If an unknown property name is queried. + + It returns the value of the requested property if ``filter`` is set + to a serial_number that exists on the controller. + """ + method_name = inspect.stack()[0][3] + key = f"{method_name}a" + + def responses(): + yield responses_ep_issu(key) + + gen_responses = ResponseGenerator(responses()) + + sender = Sender() + sender.ansible_module = MockAnsibleModule() + sender.gen = gen_responses + rest_send = RestSend(params) + rest_send.unit_test = True + rest_send.response_handler = ResponseHandler() + rest_send.sender = sender + + with does_not_raise(): + instance = issu_details_by_device_name + instance.results = Results() + instance.rest_send = rest_send + instance.refresh() + instance.filter = "FOO" + + match = r"SwitchIssuDetailsByDeviceName\._get:\s+" + match += r"FOO does not exist on the controller\." + with pytest.raises(ValueError, match=match): + instance._get("serialNumber") + + +def test_switch_issu_details_by_device_name_00210( + issu_details_by_device_name +) -> None: + """ + ### Classes and Methods + - ``SwitchIssuDetailsByDeviceName`` + - ``_get`` + + ### Summary + Verify that ``_get()`` raises ``ValueError`` because an unknown property + is queried. + + ### Test + - ``ValueError`` is raised on access of unknown property name. + - Error message matches expectation. + + ### Description + See test_switch_issu_details_by_device_name_00200. + """ + method_name = inspect.stack()[0][3] + key = f"{method_name}a" + + def responses(): + yield responses_ep_issu(key) + + gen_responses = ResponseGenerator(responses()) + + sender = Sender() + sender.ansible_module = MockAnsibleModule() + sender.gen = gen_responses + rest_send = RestSend(params) + rest_send.unit_test = True + rest_send.response_handler = ResponseHandler() + rest_send.sender = sender + + with does_not_raise(): + instance = issu_details_by_device_name + instance.results = Results() + instance.rest_send = rest_send + instance.refresh() + instance.filter = "leaf1" + + match = r"SwitchIssuDetailsByDeviceName\._get:\s+" + match += r"leaf1 unknown property name: FOO\." + + with pytest.raises(ValueError, match=match): + instance._get("FOO") + + +def test_switch_issu_details_by_device_name_00220( + issu_details_by_device_name, +) -> None: + """ + ### Classes and Methods + - ``SwitchIssuDetailsByDeviceName`` + - ``_get`` + + ### Test + - ``_get()`` raises ``ValueError`` because ``filter`` is not set. + - Error message matches expectation. + """ + with does_not_raise(): + instance = issu_details_by_device_name + match = r"SwitchIssuDetailsByDeviceName\._get: " + match += r"set instance\.filter to a switch deviceName " + match += r"before accessing property role\." + with pytest.raises(ValueError, match=match): + instance.role # pylint: disable=pointless-statement diff --git a/tests/unit/modules/dcnm/dcnm_image_upgrade/test_switch_issu_details_by_ip_address.py b/tests/unit/modules/dcnm/dcnm_image_upgrade/test_switch_issu_details_by_ip_address.py new file mode 100644 index 000000000..7b0875ebf --- /dev/null +++ b/tests/unit/modules/dcnm/dcnm_image_upgrade/test_switch_issu_details_by_ip_address.py @@ -0,0 +1,490 @@ +# 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 +# pylint: disable=protected-access + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +__copyright__ = "Copyright (c) 2024 Cisco and/or its affiliates." +__author__ = "Allen Robel" + +import inspect + +import pytest +from ansible_collections.cisco.dcnm.plugins.module_utils.common.response_handler import \ + ResponseHandler +from ansible_collections.cisco.dcnm.plugins.module_utils.common.rest_send_v2 import \ + RestSend +from ansible_collections.cisco.dcnm.plugins.module_utils.common.results import \ + Results +from ansible_collections.cisco.dcnm.plugins.module_utils.common.sender_file import \ + Sender +from ansible_collections.cisco.dcnm.tests.unit.module_utils.common.common_utils import \ + ResponseGenerator + +from .utils import (MockAnsibleModule, does_not_raise, + issu_details_by_ip_address_fixture, params, + responses_ep_issu) + + +def test_switch_issu_details_by_ip_address_00000( + issu_details_by_ip_address, +) -> None: + """ + ### Classes and Methods + - ``SwitchIssuDetailsByIpAddress`` + - ``__init__`` + + ### Summary + Verify class initialization. + + ### Test + - Class properties initialized to expected values. + - ``action_keys`` is a set. + - ``action_keys`` contains expected values. + - Exception is not raised. + """ + with does_not_raise(): + instance = issu_details_by_ip_address + + action_keys = {"imageStaged", "upgrade", "validated"} + + assert isinstance(instance._action_keys, set) + assert instance._action_keys == action_keys + assert instance.data == {} + assert instance.rest_send is None + assert instance.results is None + + assert instance.ep_issu.class_name == "EpIssu" + assert instance.conversion.class_name == "ConversionUtils" + + +def test_switch_issu_details_by_ip_address_00100(issu_details_by_ip_address) -> None: + """ + ### Classes and Methods + - ``SwitchIssuDetailsBySerialNumber`` + - ``refresh`` + + ### Test + - instance.results.response is a list + - instance.results.response_current is a dict + - instance.results.result is a list + - instance.results.result_current is a dict + - instance.results.response_data is a list + """ + method_name = inspect.stack()[0][3] + key = f"{method_name}a" + + def responses(): + yield responses_ep_issu(key) + + gen_responses = ResponseGenerator(responses()) + + sender = Sender() + sender.ansible_module = MockAnsibleModule() + sender.gen = gen_responses + rest_send = RestSend(params) + rest_send.unit_test = True + rest_send.response_handler = ResponseHandler() + rest_send.sender = sender + + with does_not_raise(): + instance = issu_details_by_ip_address + instance.results = Results() + instance.rest_send = rest_send + instance.refresh() + + assert isinstance(instance.results.response, list) + assert isinstance(instance.results.response_current, dict) + assert isinstance(instance.results.result, list) + assert isinstance(instance.results.result_current, dict) + assert isinstance(instance.results.response_data, list) + + +def test_switch_issu_details_by_ip_address_00110(issu_details_by_ip_address) -> None: + """ + ### Classes and Methods + - ``SwitchIssuDetailsByIpAddress`` + - ``refresh`` + + ### Test + - Properties are set based on device_name + - Expected property values are returned + """ + method_name = inspect.stack()[0][3] + key = f"{method_name}a" + + def responses(): + yield responses_ep_issu(key) + + gen_responses = ResponseGenerator(responses()) + + sender = Sender() + sender.ansible_module = MockAnsibleModule() + sender.gen = gen_responses + rest_send = RestSend(params) + rest_send.unit_test = True + rest_send.response_handler = ResponseHandler() + rest_send.sender = sender + + with does_not_raise(): + instance = issu_details_by_ip_address + instance.results = Results() + instance.rest_send = rest_send + instance.refresh() + instance.filter = "172.22.150.102" + + assert instance.device_name == "leaf1" + assert instance.serial_number == "FDO21120U5D" + # change ip_address to a different switch, expect different information + instance.filter = "172.22.150.108" + assert instance.device_name == "cvd-2313-leaf" + assert instance.serial_number == "FDO2112189M" + # verify remaining properties using current ip_address + assert instance.eth_switch_id == 39890 + assert instance.fabric == "hard" + assert instance.fcoe_enabled is False + assert instance.group == "hard" + # NOTE: For "id" see switch_id below + assert instance.image_staged == "Success" + assert instance.image_staged_percent == 100 + assert instance.ip_address == "172.22.150.108" + assert instance.issu_allowed is None + assert instance.last_upg_action == "2023-Oct-06 03:43" + assert instance.mds is False + assert instance.mode == "Normal" + assert instance.model == "N9K-C93180YC-EX" + assert instance.model_type == 0 + assert instance.peer is None + assert instance.platform == "N9K" + assert instance.policy == "KR5M" + assert instance.reason == "Upgrade" + assert instance.role == "leaf" + assert instance.status == "In-Sync" + assert instance.status_percent == 100 + # NOTE: switch_id appears in the response data as "id" + # NOTE: "id" is a python reserved keyword, so we changed the property name + assert instance.switch_id == 2 + assert instance.sys_name == "cvd-2313-leaf" + assert instance.system_mode == "Normal" + assert instance.upg_groups is None + assert instance.upgrade == "Success" + assert instance.upgrade_percent == 100 + assert instance.validated == "Success" + assert instance.validated_percent == 100 + assert instance.version == "10.2(5)" + # NOTE: Two vdc_id values exist in the response data for each switch. + # NOTE: Namely, "vdcId" and "vdc_id" + # NOTE: Properties are provided for both, as follows. + # NOTE: vdc_id == vdcId + # NOTE: vdc_id2 == vdc_id + assert instance.vdc_id == 0 + assert instance.vdc_id2 == -1 + assert instance.vpc_peer is None + # NOTE: Two vpc role keys exist in the response data for each switch. + # NOTE: Namely, "vpcRole" and "vpc_role" + # NOTE: Properties are provided for both, as follows. + # NOTE: vpc_role == vpcRole + # NOTE: vpc_role2 == vpc_role + # NOTE: Values are synthesized in the response for this test + assert instance.vpc_role == "FOO" + assert instance.vpc_role2 == "BAR" + assert isinstance(instance.filtered_data, dict) + assert instance.filtered_data.get("deviceName") == "cvd-2313-leaf" + + +def test_switch_issu_details_by_ip_address_00120(issu_details_by_ip_address) -> None: + """ + ### Classes and Methods + - ``SwitchIssuDetailsByIpAddress`` + - ``refresh`` + + ### Test + - ``results.result_current`` is a dict. + - ``results.result_current`` contains expected key/values + for 200 RESULT_CODE. + """ + method_name = inspect.stack()[0][3] + key = f"{method_name}a" + + def responses(): + yield responses_ep_issu(key) + + gen_responses = ResponseGenerator(responses()) + + sender = Sender() + sender.ansible_module = MockAnsibleModule() + sender.gen = gen_responses + rest_send = RestSend(params) + rest_send.unit_test = True + rest_send.response_handler = ResponseHandler() + rest_send.sender = sender + + with does_not_raise(): + instance = issu_details_by_ip_address + instance.results = Results() + instance.rest_send = rest_send + instance.refresh() + assert isinstance(instance.results.result_current, dict) + assert instance.results.result_current.get("found") is True + assert instance.results.result_current.get("success") is True + + +def test_switch_issu_details_by_ip_address_00130(issu_details_by_ip_address) -> None: + """ + ### Classes and Methods + - ``SwitchIssuDetailsByIpAddress`` + - ``refresh`` + + ### Summary + Verify behavior when controller response is 404. + + ### Test + - ``ValueError`` is raised. + - Error message matches expectation. + """ + method_name = inspect.stack()[0][3] + key = f"{method_name}a" + + def responses(): + yield responses_ep_issu(key) + + gen_responses = ResponseGenerator(responses()) + + sender = Sender() + sender.ansible_module = MockAnsibleModule() + sender.gen = gen_responses + rest_send = RestSend(params) + rest_send.unit_test = True + rest_send.response_handler = ResponseHandler() + rest_send.sender = sender + + with does_not_raise(): + instance = issu_details_by_ip_address + instance.results = Results() + instance.rest_send = rest_send + + match = r"SwitchIssuDetailsByIpAddress\.refresh_super:\s+" + match += r"Bad result when retriving switch ISSU details from the\s+" + match += r"controller\." + with pytest.raises(ValueError, match=match): + instance.refresh() + + +def test_switch_issu_details_by_ip_address_00140(issu_details_by_ip_address) -> None: + """ + ### Classes and Methods + - ``SwitchIssuDetailsByIpAddress`` + - ``refresh`` + + ### Test + - ``ValueError`` is raised on 200 response with empty DATA key. + - Error message matches expectation. + """ + method_name = inspect.stack()[0][3] + key = f"{method_name}a" + + def responses(): + yield responses_ep_issu(key) + + gen_responses = ResponseGenerator(responses()) + + sender = Sender() + sender.ansible_module = MockAnsibleModule() + sender.gen = gen_responses + rest_send = RestSend(params) + rest_send.unit_test = True + rest_send.response_handler = ResponseHandler() + rest_send.sender = sender + + with does_not_raise(): + instance = issu_details_by_ip_address + instance.results = Results() + instance.rest_send = rest_send + + match = r"SwitchIssuDetailsByIpAddress\.refresh_super:\s+" + match += r"The controller has no switch ISSU information\." + with pytest.raises(ValueError, match=match): + instance.refresh() + + +def test_switch_issu_details_by_ip_address_00150(issu_details_by_ip_address) -> None: + """ + ### Classes and Methods + - ``SwitchIssuDetailsByIpAddress`` + - ``refresh`` + + ### Test + - ``ValueError`` is raised on 200 response with + DATA.lastOperDataObject length 0. + - Error message matches expectation. + """ + method_name = inspect.stack()[0][3] + key = f"{method_name}a" + + def responses(): + yield responses_ep_issu(key) + + gen_responses = ResponseGenerator(responses()) + + sender = Sender() + sender.ansible_module = MockAnsibleModule() + sender.gen = gen_responses + rest_send = RestSend(params) + rest_send.unit_test = True + rest_send.response_handler = ResponseHandler() + rest_send.sender = sender + + with does_not_raise(): + instance = issu_details_by_ip_address + instance.results = Results() + instance.rest_send = rest_send + + match = r"SwitchIssuDetailsByIpAddress\.refresh_super:\s+" + match += r"The controller has no switch ISSU information\." + with pytest.raises(ValueError, match=match): + instance.refresh() + + +def test_switch_issu_details_by_ip_address_00200(issu_details_by_ip_address) -> None: + """ + ### Classes and Methods + - ``SwitchIssuDetailsByIpAddress`` + - ``_get`` + + ### Summary + Verify that _get() raises ``ValueError`` because filter is set to an + unknown ip_address + + ### Test + - ``ValueError`` is raised because filter is set to an unknown + ip_address. + - Error message matches expectation. + + ### Description + ``SwitchIssuDetailsByIpAddress._get`` is called by all getter + properties. It raises ``ValueError`` in the following cases: + + - If the user has not set filter. + - If filter is unknown. + - If an unknown property name is queried. + + It returns the value of the requested property if ``filter`` is set + to a serial_number that exists on the controller. + """ + method_name = inspect.stack()[0][3] + key = f"{method_name}a" + + def responses(): + yield responses_ep_issu(key) + + gen_responses = ResponseGenerator(responses()) + + sender = Sender() + sender.ansible_module = MockAnsibleModule() + sender.gen = gen_responses + rest_send = RestSend(params) + rest_send.unit_test = True + rest_send.response_handler = ResponseHandler() + rest_send.sender = sender + + with does_not_raise(): + instance = issu_details_by_ip_address + instance.results = Results() + instance.rest_send = rest_send + instance.refresh() + instance.filter = "1.1.1.1" + + match = r"SwitchIssuDetailsByIpAddress\._get:\s+" + match += r"1\.1\.1\.1 does not exist on the controller\." + with pytest.raises(ValueError, match=match): + instance._get("serialNumber") + + +def test_switch_issu_details_by_ip_address_00210( + issu_details_by_ip_address +) -> None: + """ + ### Classes and Methods + - ``SwitchIssuDetailsByIpAddress`` + - ``_get`` + + ### Summary + Verify that ``_get()`` raises ``ValueError`` because an unknown property + is queried. + + ### Test + - ``ValueError`` is raised on access of unknown property name. + - Error message matches expectation. + + ### Description + See test_switch_issu_details_by_ip_address_00200. + """ + method_name = inspect.stack()[0][3] + key = f"{method_name}a" + + def responses(): + yield responses_ep_issu(key) + + gen_responses = ResponseGenerator(responses()) + + sender = Sender() + sender.ansible_module = MockAnsibleModule() + sender.gen = gen_responses + rest_send = RestSend(params) + rest_send.unit_test = True + rest_send.response_handler = ResponseHandler() + rest_send.sender = sender + + with does_not_raise(): + instance = issu_details_by_ip_address + instance.results = Results() + instance.rest_send = rest_send + instance.refresh() + instance.filter = "172.22.150.102" + + match = r"SwitchIssuDetailsByIpAddress\._get:\s+" + match += r"172\.22\.150\.102 unknown property name: FOO\." + + with pytest.raises(ValueError, match=match): + instance._get("FOO") + + +def test_switch_issu_details_by_ip_address_00220( + issu_details_by_ip_address, +) -> None: + """ + ### Classes and Methods + - ``SwitchIssuDetailsByIpAddress`` + - ``_get`` + + ### Test + - ``_get()`` raises ``ValueError`` because ``filter`` is not set. + - Error message matches expectation. + """ + with does_not_raise(): + instance = issu_details_by_ip_address + match = r"SwitchIssuDetailsByIpAddress\._get: " + match += r"set instance\.filter to a switch ipAddress " + match += r"before accessing property role\." + with pytest.raises(ValueError, match=match): + instance.role # pylint: disable=pointless-statement diff --git a/tests/unit/modules/dcnm/dcnm_image_upgrade/test_switch_issu_details_by_serial_number.py b/tests/unit/modules/dcnm/dcnm_image_upgrade/test_switch_issu_details_by_serial_number.py new file mode 100644 index 000000000..f4fcab1dd --- /dev/null +++ b/tests/unit/modules/dcnm/dcnm_image_upgrade/test_switch_issu_details_by_serial_number.py @@ -0,0 +1,505 @@ +# 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 +# pylint: disable=protected-access + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +__copyright__ = "Copyright (c) 2024 Cisco and/or its affiliates." +__author__ = "Allen Robel" + +import inspect + +import pytest +from ansible_collections.cisco.dcnm.plugins.module_utils.common.response_handler import \ + ResponseHandler +from ansible_collections.cisco.dcnm.plugins.module_utils.common.rest_send_v2 import \ + RestSend +from ansible_collections.cisco.dcnm.plugins.module_utils.common.results import \ + Results +from ansible_collections.cisco.dcnm.plugins.module_utils.common.sender_file import \ + Sender +from ansible_collections.cisco.dcnm.tests.unit.module_utils.common.common_utils import \ + ResponseGenerator + +from .utils import (MockAnsibleModule, does_not_raise, + issu_details_by_serial_number_fixture, params, + responses_ep_issu) + + +def test_switch_issu_details_by_serial_number_00000( + issu_details_by_serial_number, +) -> None: + """ + ### Classes and Methods + - ``SwitchIssuDetailsBySerialNumber`` + - ``__init__`` + + ### Summary + Verify class initialization. + + ### Test + - Class properties initialized to expected values. + - ``action_keys`` is a set. + - ``action_keys`` contains expected values. + - Exception is not raised. + """ + with does_not_raise(): + instance = issu_details_by_serial_number + + action_keys = {"imageStaged", "upgrade", "validated"} + + assert isinstance(instance._action_keys, set) + assert instance._action_keys == action_keys + assert instance.data == {} + assert instance.rest_send is None + assert instance.results is None + + assert instance.ep_issu.class_name == "EpIssu" + assert instance.conversion.class_name == "ConversionUtils" + + +def test_switch_issu_details_by_serial_number_00100( + issu_details_by_serial_number, +) -> None: + """ + ### Classes and Methods + - ``SwitchIssuDetailsBySerialNumber`` + - ``refresh`` + + ### Test + - instance.results.response is a list + - instance.results.response_current is a dict + - instance.results.result is a list + - instance.results.result_current is a dict + - instance.results.response_data is a list + """ + method_name = inspect.stack()[0][3] + key = f"{method_name}a" + + def responses(): + yield responses_ep_issu(key) + + gen_responses = ResponseGenerator(responses()) + + sender = Sender() + sender.ansible_module = MockAnsibleModule() + sender.gen = gen_responses + rest_send = RestSend(params) + rest_send.unit_test = True + rest_send.response_handler = ResponseHandler() + rest_send.sender = sender + + with does_not_raise(): + instance = issu_details_by_serial_number + instance.results = Results() + instance.rest_send = rest_send + instance.refresh() + + assert isinstance(instance.results.response, list) + assert isinstance(instance.results.response_current, dict) + assert isinstance(instance.results.result, list) + assert isinstance(instance.results.result_current, dict) + assert isinstance(instance.results.response_data, list) + + +def test_switch_issu_details_by_serial_number_00110( + issu_details_by_serial_number, +) -> None: + """ + ### Classes and Methods + - ``SwitchIssuDetailsBySerialNumber`` + - ``refresh`` + + ### Test + - Properties are set based on ``filter`` value. + - Expected property values are returned. + """ + method_name = inspect.stack()[0][3] + key = f"{method_name}a" + + def responses(): + yield responses_ep_issu(key) + + gen_responses = ResponseGenerator(responses()) + + sender = Sender() + sender.ansible_module = MockAnsibleModule() + sender.gen = gen_responses + rest_send = RestSend(params) + rest_send.unit_test = True + rest_send.response_handler = ResponseHandler() + rest_send.sender = sender + + with does_not_raise(): + instance = issu_details_by_serial_number + instance.results = Results() + instance.rest_send = rest_send + instance.refresh() + instance.filter = "FDO21120U5D" + + assert instance.device_name == "leaf1" + assert instance.serial_number == "FDO21120U5D" + # change serial_number to a different switch, expect different information + instance.filter = "FDO2112189M" + assert instance.device_name == "cvd-2313-leaf" + assert instance.serial_number == "FDO2112189M" + # verify remaining properties using current serial_number + assert instance.eth_switch_id == 39890 + assert instance.fabric == "hard" + assert instance.fcoe_enabled is False + assert instance.group == "hard" + # NOTE: For "id" see switch_id below + assert instance.image_staged == "Success" + assert instance.image_staged_percent == 100 + assert instance.ip_address == "172.22.150.108" + assert instance.issu_allowed is None + assert instance.last_upg_action == "2023-Oct-06 03:43" + assert instance.mds is False + assert instance.mode == "Normal" + assert instance.model == "N9K-C93180YC-EX" + assert instance.model_type == 0 + assert instance.peer is None + assert instance.platform == "N9K" + assert instance.policy == "KR5M" + assert instance.reason == "Upgrade" + assert instance.role == "leaf" + assert instance.status == "In-Sync" + assert instance.status_percent == 100 + # NOTE: switch_id appears in the response data as "id" + # NOTE: "id" is a python reserved keyword, so we changed the property name + assert instance.switch_id == 2 + assert instance.sys_name == "cvd-2313-leaf" + assert instance.system_mode == "Normal" + assert instance.upg_groups is None + assert instance.upgrade == "Success" + assert instance.upgrade_percent == 100 + assert instance.validated == "Success" + assert instance.validated_percent == 100 + assert instance.version == "10.2(5)" + # NOTE: Two vdc_id values exist in the response data for each switch. + # NOTE: Namely, "vdcId" and "vdc_id" + # NOTE: Properties are provided for both, as follows. + # NOTE: vdc_id == vdcId + # NOTE: vdc_id2 == vdc_id + assert instance.vdc_id == 0 + assert instance.vdc_id2 == -1 + assert instance.vpc_peer is None + # NOTE: Two vpc role keys exist in the response data for each switch. + # NOTE: Namely, "vpcRole" and "vpc_role" + # NOTE: Properties are provided for both, as follows. + # NOTE: vpc_role == vpcRole + # NOTE: vpc_role2 == vpc_role + # NOTE: Values are synthesized in the response for this test + assert instance.vpc_role == "FOO" + assert instance.vpc_role2 == "BAR" + assert isinstance(instance.filtered_data, dict) + assert instance.filtered_data.get("deviceName") == "cvd-2313-leaf" + + +def test_switch_issu_details_by_serial_number_00120( + issu_details_by_serial_number, +) -> None: + """ + ### Classes and Methods + - ``SwitchIssuDetailsBySerialNumber`` + - ``refresh`` + + ### Test + - ``results.result_current`` is a dict. + - ``results.result_current`` contains expected key/values + for 200 RESULT_CODE. + """ + method_name = inspect.stack()[0][3] + key = f"{method_name}a" + + def responses(): + yield responses_ep_issu(key) + + gen_responses = ResponseGenerator(responses()) + + sender = Sender() + sender.ansible_module = MockAnsibleModule() + sender.gen = gen_responses + rest_send = RestSend(params) + rest_send.unit_test = True + rest_send.response_handler = ResponseHandler() + rest_send.sender = sender + + with does_not_raise(): + instance = issu_details_by_serial_number + instance.results = Results() + instance.rest_send = rest_send + instance.refresh() + assert isinstance(instance.results.result_current, dict) + assert instance.results.result_current.get("found") is True + assert instance.results.result_current.get("success") is True + + +def test_switch_issu_details_by_serial_number_00130( + issu_details_by_serial_number, +) -> None: + """ + ### Classes and Methods + - ``SwitchIssuDetailsBySerialNumber`` + - ``refresh`` + + ### Summary + Verify behavior when controller response is 404. + + ### Test + - ``ValueError`` is raised. + - Error message matches expectation. + """ + method_name = inspect.stack()[0][3] + key = f"{method_name}a" + + def responses(): + yield responses_ep_issu(key) + + gen_responses = ResponseGenerator(responses()) + + sender = Sender() + sender.ansible_module = MockAnsibleModule() + sender.gen = gen_responses + rest_send = RestSend(params) + rest_send.unit_test = True + rest_send.response_handler = ResponseHandler() + rest_send.sender = sender + + with does_not_raise(): + instance = issu_details_by_serial_number + instance.results = Results() + instance.rest_send = rest_send + + match = r"SwitchIssuDetailsBySerialNumber\.refresh_super:\s+" + match += r"Bad result when retriving switch ISSU details from the\s+" + match += r"controller\." + with pytest.raises(ValueError, match=match): + instance.refresh() + + +def test_switch_issu_details_by_serial_number_00140( + issu_details_by_serial_number, +) -> None: + """ + ### Classes and Methods + - ``SwitchIssuDetailsBySerialNumber`` + - ``refresh`` + + ### Test + - ``ValueError`` is raised on 200 response with empty DATA key. + - Error message matches expectation. + """ + method_name = inspect.stack()[0][3] + key = f"{method_name}a" + + def responses(): + yield responses_ep_issu(key) + + gen_responses = ResponseGenerator(responses()) + + sender = Sender() + sender.ansible_module = MockAnsibleModule() + sender.gen = gen_responses + rest_send = RestSend(params) + rest_send.unit_test = True + rest_send.response_handler = ResponseHandler() + rest_send.sender = sender + + with does_not_raise(): + instance = issu_details_by_serial_number + instance.results = Results() + instance.rest_send = rest_send + + match = r"SwitchIssuDetailsBySerialNumber\.refresh_super:\s+" + match += r"The controller has no switch ISSU information\." + with pytest.raises(ValueError, match=match): + instance.refresh() + + +def test_switch_issu_details_by_serial_number_00150( + issu_details_by_serial_number, +) -> None: + """ + ### Classes and Methods + - ``SwitchIssuDetailsBySerialNumber`` + - ``refresh`` + + ### Test + - ``ValueError`` is raised on 200 response with + DATA.lastOperDataObject length 0. + - Error message matches expectation. + """ + method_name = inspect.stack()[0][3] + key = f"{method_name}a" + + def responses(): + yield responses_ep_issu(key) + + gen_responses = ResponseGenerator(responses()) + + sender = Sender() + sender.ansible_module = MockAnsibleModule() + sender.gen = gen_responses + rest_send = RestSend(params) + rest_send.unit_test = True + rest_send.response_handler = ResponseHandler() + rest_send.sender = sender + + with does_not_raise(): + instance = issu_details_by_serial_number + instance.results = Results() + instance.rest_send = rest_send + + match = r"SwitchIssuDetailsBySerialNumber\.refresh_super:\s+" + match += r"The controller has no switch ISSU information\." + with pytest.raises(ValueError, match=match): + instance.refresh() + + +def test_switch_issu_details_by_serial_number_00200( + issu_details_by_serial_number, +) -> None: + """ + ### Classes and Methods + - ``SwitchIssuDetailsBySerialNumber`` + - ``_get`` + + ### Summary + Verify that _get() raises ``ValueError`` because filter is set to an + unknown serial_number + + ### Test + - `ValueError`` is raised because filter is set to an unknown + serial_number. + - Error message matches expectation. + + ### Description + ``SwitchIssuDetailsBySerialNumber._get`` is called by all getter + properties. It raises ``ValueError`` in the following cases: + + - If the user has not set filter. + - If filter is unknown. + - If an unknown property name is queried. + + It returns the value of the requested property if ``filter`` is set + to a serial_number that exists on the controller. + """ + method_name = inspect.stack()[0][3] + key = f"{method_name}a" + + def responses(): + yield responses_ep_issu(key) + + gen_responses = ResponseGenerator(responses()) + + sender = Sender() + sender.ansible_module = MockAnsibleModule() + sender.gen = gen_responses + rest_send = RestSend(params) + rest_send.unit_test = True + rest_send.response_handler = ResponseHandler() + rest_send.sender = sender + + with does_not_raise(): + instance = issu_details_by_serial_number + instance.results = Results() + instance.rest_send = rest_send + instance.refresh() + instance.filter = "FOO00000BAR" + + match = r"SwitchIssuDetailsBySerialNumber\._get:\s+" + match += r"FOO00000BAR does not exist on the controller\." + with pytest.raises(ValueError, match=match): + instance._get("serialNumber") + + +def test_switch_issu_details_by_serial_number_00210( + issu_details_by_serial_number, +) -> None: + """ + ### Classes and Methods + - ``SwitchIssuDetailsBySerialNumber`` + - ``_get`` + + + ### Summary + Verify that ``_get()`` raises ``ValueError`` because an unknown property + is queried. + + ### Test + - ``ValueError`` is raised on access of unknown property name. + - Error message matches expectation. + + ### Description + See test_switch_issu_details_by_serial_number_00200. + """ + method_name = inspect.stack()[0][3] + key = f"{method_name}a" + + def responses(): + yield responses_ep_issu(key) + + gen_responses = ResponseGenerator(responses()) + + sender = Sender() + sender.ansible_module = MockAnsibleModule() + sender.gen = gen_responses + rest_send = RestSend(params) + rest_send.unit_test = True + rest_send.response_handler = ResponseHandler() + rest_send.sender = sender + + with does_not_raise(): + instance = issu_details_by_serial_number + instance.results = Results() + instance.rest_send = rest_send + instance.refresh() + instance.filter = "FDO21120U5D" + + match = r"SwitchIssuDetailsBySerialNumber\._get:\s+" + match += r"FDO21120U5D unknown property name: FOO\." + + with pytest.raises(ValueError, match=match): + instance._get("FOO") + + +def test_switch_issu_details_by_serial_number_00220( + issu_details_by_serial_number, +) -> None: + """ + ### Classes and Methods + - ``SwitchIssuDetailsBySerialNumber`` + - ``_get`` + + ### Test + - ``_get()`` raises ``ValueError`` because ``filter`` is not set. + - Error message matches expectation. + """ + with does_not_raise(): + instance = issu_details_by_serial_number + match = r"SwitchIssuDetailsBySerialNumber\._get: " + match += r"set instance\.filter to a switch serialNumber " + match += r"before accessing property role\." + with pytest.raises(ValueError, match=match): + instance.role # pylint: disable=pointless-statement diff --git a/tests/unit/modules/dcnm/dcnm_image_upgrade/utils.py b/tests/unit/modules/dcnm/dcnm_image_upgrade/utils.py index e1536724e..650c49557 100644 --- a/tests/unit/modules/dcnm/dcnm_image_upgrade/utils.py +++ b/tests/unit/modules/dcnm/dcnm_image_upgrade/utils.py @@ -18,39 +18,42 @@ 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.common.params_validate import \ +from ansible_collections.cisco.dcnm.plugins.module_utils.common.params_validate_v2 import \ ParamsValidate -from ansible_collections.cisco.dcnm.plugins.module_utils.image_upgrade.image_policies import \ - ImagePolicies -from ansible_collections.cisco.dcnm.plugins.module_utils.image_upgrade.image_policy_action import \ - ImagePolicyAction +from ansible_collections.cisco.dcnm.plugins.module_utils.common.switch_details import \ + SwitchDetails from ansible_collections.cisco.dcnm.plugins.module_utils.image_upgrade.image_stage import \ ImageStage from ansible_collections.cisco.dcnm.plugins.module_utils.image_upgrade.image_upgrade import \ ImageUpgrade -from ansible_collections.cisco.dcnm.plugins.module_utils.image_upgrade.image_upgrade_common import \ - ImageUpgradeCommon -from ansible_collections.cisco.dcnm.plugins.module_utils.image_upgrade.image_upgrade_task_result import \ - ImageUpgradeTaskResult from ansible_collections.cisco.dcnm.plugins.module_utils.image_upgrade.image_validate import \ ImageValidate from ansible_collections.cisco.dcnm.plugins.module_utils.image_upgrade.install_options import \ ImageInstallOptions -from ansible_collections.cisco.dcnm.plugins.module_utils.image_upgrade.switch_details import \ - SwitchDetails from ansible_collections.cisco.dcnm.plugins.module_utils.image_upgrade.switch_issu_details import ( SwitchIssuDetailsByDeviceName, SwitchIssuDetailsByIpAddress, SwitchIssuDetailsBySerialNumber) -from ansible_collections.cisco.dcnm.plugins.modules.dcnm_image_upgrade import \ - ImageUpgradeTask from ansible_collections.cisco.dcnm.tests.unit.modules.dcnm.dcnm_image_upgrade.fixture import \ load_fixture +params = { + "state": "merged", + "check_mode": False, + "config": [ + { + "name": "NR1F", + "agnostic": False, + "description": "NR1F", + "platform": "N9K", + "type": "PLATFORM", + } + ], +} + class MockAnsibleModule: """ @@ -74,7 +77,7 @@ def fail_json(msg, **kwargs) -> AnsibleFailJson: """ raise AnsibleFailJson(msg, kwargs) - def public_method_for_pylint(self) -> Any: + def public_method_for_pylint(self): """ Add one public method to appease pylint """ @@ -87,113 +90,73 @@ def public_method_for_pylint(self) -> Any: @pytest.fixture(name="image_install_options") def image_install_options_fixture(): """ - mock ImageInstallOptions - """ - return ImageInstallOptions(MockAnsibleModule) - - -@pytest.fixture(name="image_policies") -def image_policies_fixture(): - """ - mock ImagePolicies - """ - return ImagePolicies(MockAnsibleModule) - - -@pytest.fixture(name="image_policy_action") -def image_policy_action_fixture(): - """ - mock ImagePolicyAction + Return ImageInstallOptions instance. """ - return ImagePolicyAction(MockAnsibleModule) + return ImageInstallOptions() @pytest.fixture(name="image_stage") def image_stage_fixture(): """ - mock ImageStage - """ - return ImageStage(MockAnsibleModule) - - -@pytest.fixture(name="image_upgrade_common") -def image_upgrade_common_fixture(): - """ - mock ImageUpgradeCommon + Return ImageStage instance. """ - return ImageUpgradeCommon(MockAnsibleModule) + return ImageStage() @pytest.fixture(name="image_upgrade") def image_upgrade_fixture(): """ - mock ImageUpgrade - """ - return ImageUpgrade(MockAnsibleModule) - - -@pytest.fixture(name="image_upgrade_task") -def image_upgrade_task_fixture(): - """ - mock ImageUpgradeTask - """ - return ImageUpgradeTask(MockAnsibleModule) - - -@pytest.fixture(name="image_upgrade_task_result") -def image_upgrade_task_result_fixture(): - """ - mock ImageUpgradeTaskResult + Return ImageUpgrade instance. """ - return ImageUpgradeTaskResult(MockAnsibleModule) + return ImageUpgrade() @pytest.fixture(name="image_validate") def image_validate_fixture(): """ - mock ImageValidate + Return ImageValidate instance. """ - return ImageValidate(MockAnsibleModule) + return ImageValidate() @pytest.fixture(name="params_validate") def params_validate_fixture(): """ - mock ParamsValidate + Return ParamsValidate instance. """ - return ParamsValidate(MockAnsibleModule) + return ParamsValidate() @pytest.fixture(name="issu_details_by_device_name") -def issu_details_by_device_name_fixture(): +def issu_details_by_device_name_fixture() -> SwitchIssuDetailsByDeviceName: """ - mock SwitchIssuDetailsByDeviceName + Return SwitchIssuDetailsByDeviceName instance. """ - return SwitchIssuDetailsByDeviceName(MockAnsibleModule) + return SwitchIssuDetailsByDeviceName() @pytest.fixture(name="issu_details_by_ip_address") -def issu_details_by_ip_address_fixture(): +def issu_details_by_ip_address_fixture() -> SwitchIssuDetailsByIpAddress: """ - mock SwitchIssuDetailsByIpAddress + Return SwitchIssuDetailsByIpAddress instance. """ - return SwitchIssuDetailsByIpAddress(MockAnsibleModule) + return SwitchIssuDetailsByIpAddress() @pytest.fixture(name="issu_details_by_serial_number") def issu_details_by_serial_number_fixture() -> SwitchIssuDetailsBySerialNumber: """ - mock SwitchIssuDetailsBySerialNumber + Return SwitchIssuDetailsBySerialNumber instance. """ - return SwitchIssuDetailsBySerialNumber(MockAnsibleModule) + return SwitchIssuDetailsBySerialNumber() @pytest.fixture(name="switch_details") def switch_details_fixture(): """ - mock SwitchDetails + Return SwitchDetails instance. """ - return SwitchDetails(MockAnsibleModule) + return SwitchDetails() @contextmanager @@ -204,7 +167,7 @@ def does_not_raise(): yield -def load_playbook_config(key: str) -> Dict[str, str]: +def load_playbook_config(key: str) -> dict[str, str]: """ Return playbook configs for ImageUpgradeTask """ @@ -214,112 +177,113 @@ def load_playbook_config(key: str) -> Dict[str, str]: return playbook_config -def payloads_image_upgrade(key: str) -> Dict[str, str]: +def devices_image_upgrade(key: str) -> dict[str, str]: """ - Return payloads for ImageUpgrade + Return data for the ImageUpgrade().devices property. + Used by test_image_upgrade.py """ - payload_file = "image_upgrade_payloads_ImageUpgrade" - payload = load_fixture(payload_file).get(key) - print(f"payload_data_image_upgrade: {key} : {payload}") - return payload + devices_file = "devices_image_upgrade" + devices = load_fixture(devices_file).get(key) + print(f"devices_image_upgrade: {key} : {devices}") + return devices -def responses_controller_version(key: str) -> Dict[str, str]: +def payloads_ep_image_upgrade(key: str) -> dict[str, str]: """ - Return ControllerVersion controller responses + Return payloads for EpImageUpgrade """ - response_file = "image_upgrade_responses_ControllerVersion" - response = load_fixture(response_file).get(key) - print(f"responses_controller_version: {key} : {response}") - return response + payload_file = "payloads_ep_image_upgrade" + payload = load_fixture(payload_file).get(key) + print(f"payloads_ep_image_upgrade: {key} : {payload}") + return payload -def responses_image_install_options(key: str) -> Dict[str, str]: +def responses_ep_image_stage(key: str) -> dict[str, str]: """ - Return ImageInstallOptions controller responses + Return EpImageStage controller responses """ - response_file = "image_upgrade_responses_ImageInstallOptions" + response_file = "responses_ep_image_stage" response = load_fixture(response_file).get(key) - print(f"{key} : : {response}") + print(f"responses_ep_image_stage: {key} : {response}") return response -def responses_image_policies(key: str) -> Dict[str, str]: +def responses_ep_image_upgrade(key: str) -> dict[str, str]: """ - Return ImagePolicies controller responses + Return EpImageUpgrade responses """ - response_file = "image_upgrade_responses_ImagePolicies" + response_file = "responses_ep_image_upgrade" response = load_fixture(response_file).get(key) - print(f"responses_image_policies: {key} : {response}") + print(f"responses_ep_image_upgrade: {key} : {response}") return response -def responses_image_policy_action(key: str) -> Dict[str, str]: +def responses_ep_image_validate(key: str) -> dict[str, str]: """ - Return ImagePolicyAction controller responses + Return EpImageValidate responses """ - response_file = "image_upgrade_responses_ImagePolicyAction" + response_file = "responses_ep_image_validate" response = load_fixture(response_file).get(key) - print(f"responses_image_policy_action: {key} : {response}") + print(f"responses_ep_image_validate: {key} : {response}") return response -def responses_image_stage(key: str) -> Dict[str, str]: +def responses_ep_issu(key: str) -> dict[str, str]: """ - Return ImageStage controller responses + Return EpIssu responses """ - response_file = "image_upgrade_responses_ImageStage" + response_file = "responses_ep_issu" response = load_fixture(response_file).get(key) - print(f"responses_image_stage: {key} : {response}") + print(f"responses_ep_issu: {key} : {response}") return response -def responses_image_upgrade(key: str) -> Dict[str, str]: +def responses_ep_version(key: str) -> dict[str, str]: """ - Return ImageUpgrade controller responses + Return EpVersion responses """ - response_file = "image_upgrade_responses_ImageUpgrade" + response_file = "responses_ep_version" response = load_fixture(response_file).get(key) - print(f"response_data_image_upgrade: {key} : {response}") + print(f"responses_ep_version: {key} : {response}") return response -def responses_image_upgrade_common(key: str) -> Dict[str, str]: +def responses_ep_install_options(key: str) -> dict[str, str]: """ - Return ImageUpgradeCommon controller responses + Return EpInstallOptions responses """ - response_file = "image_upgrade_responses_ImageUpgradeCommon" + response_file = "responses_ep_install_options" response = load_fixture(response_file).get(key) - verb = response.get("METHOD") - print(f"{key} : {verb} : {response}") - return {"response": response, "verb": verb} + print(f"responses_ep_install_options: {key} : {response}") + return response -def responses_image_validate(key: str) -> Dict[str, str]: +def responses_image_policy_action(key: str) -> dict[str, str]: """ - Return ImageValidate controller responses + Return ImagePolicyAction responses """ - response_file = "image_upgrade_responses_ImageValidate" + response_file = "image_upgrade_responses_ImagePolicyAction" response = load_fixture(response_file).get(key) - print(f"responses_image_validate: {key} : {response}") + print(f"responses_image_policy_action: {key} : {response}") return response -def responses_switch_details(key: str) -> Dict[str, str]: +def responses_image_upgrade_common(key: str) -> dict[str, str]: """ - Return SwitchDetails controller responses + Return ImageUpgradeCommon responses """ - response_file = "image_upgrade_responses_SwitchDetails" + response_file = "image_upgrade_responses_ImageUpgradeCommon" response = load_fixture(response_file).get(key) - print(f"responses_switch_details: {key} : {response}") - return response + verb = response.get("METHOD") + print(f"{key} : {verb} : {response}") + return {"response": response, "verb": verb} -def responses_switch_issu_details(key: str) -> Dict[str, str]: +def responses_switch_details(key: str) -> dict[str, str]: """ - Return SwitchIssuDetails controller responses + Return SwitchDetails responses """ - response_file = "image_upgrade_responses_SwitchIssuDetails" + response_file = "image_upgrade_responses_SwitchDetails" response = load_fixture(response_file).get(key) - print(f"responses_switch_issu_details: {key} : {response}") + print(f"responses_switch_details: {key} : {response}") return response diff --git a/tests/unit/modules/dcnm/dcnm_maintenance_mode/utils.py b/tests/unit/modules/dcnm/dcnm_maintenance_mode/utils.py index 7ce1e6082..93245efd8 100644 --- a/tests/unit/modules/dcnm/dcnm_maintenance_mode/utils.py +++ b/tests/unit/modules/dcnm/dcnm_maintenance_mode/utils.py @@ -104,12 +104,9 @@ def common_fixture(): @pytest.fixture(name="fabric_details_by_name_v2") def fabric_details_by_name_v2_fixture(): """ - mock FabricDetailsByName version 2 + Return FabricDetailsByName version 2 instance """ - instance = MockAnsibleModule() - instance.state = "query" - instance.check_mode = False - return FabricDetailsByNameV2(instance.params) + return FabricDetailsByNameV2() @pytest.fixture(name="response_handler")