From 1e7219bdc8e56c963c64d4b357b4b6412bf953ef Mon Sep 17 00:00:00 2001 From: Allen Robel Date: Mon, 24 Nov 2025 09:56:50 -1000 Subject: [PATCH 01/24] Address FabricSummary (v2) tech debt 1. Unit tests for FabricSummary (v2) - This class did not have unit tests, added unit tests and fixtures - Updated dcnm_fabric/utils.py with FabricSummary (v2) support 2. FabricSummary (v2) - Added type hints throughout - Updated docstrings to use Markdown throughout - Made several public instances private (e.g. self.conversion -> self._conversion) - Updated rest_send and results properties with enhanced versions we use in other classes --- .../module_utils/fabric/fabric_summary_v2.py | 328 +++++-- .../fixtures/responses_FabricSummary_V2.json | 389 +++++++++ .../dcnm_fabric/test_fabric_summary_v2.py | 811 ++++++++++++++++++ tests/unit/modules/dcnm/dcnm_fabric/utils.py | 79 +- 4 files changed, 1477 insertions(+), 130 deletions(-) create mode 100644 tests/unit/modules/dcnm/dcnm_fabric/fixtures/responses_FabricSummary_V2.json create mode 100644 tests/unit/modules/dcnm/dcnm_fabric/test_fabric_summary_v2.py diff --git a/plugins/module_utils/fabric/fabric_summary_v2.py b/plugins/module_utils/fabric/fabric_summary_v2.py index 0ed719000..a58a229cf 100644 --- a/plugins/module_utils/fabric/fabric_summary_v2.py +++ b/plugins/module_utils/fabric/fabric_summary_v2.py @@ -25,6 +25,7 @@ import inspect import json import logging +from typing import Any, Literal from ..common.api.v1.lan_fabric.rest.control.switches.switches import EpFabricSummary from ..common.conversion import ConversionUtils @@ -35,7 +36,9 @@ class FabricSummary: """ - Populate ``dict`` ``self.data`` with fabric summary information. + # Summary + + Populate dict `self.data` with fabric summary information. Convenience properties are provided to access the data, including: @@ -45,8 +48,9 @@ class FabricSummary: - @border_gateway_count - @in_sync_count - @out_of_sync_count + - @fabric_is_empty - self.data will contain the following structure. + After a successful call to `refresh()`, `self.data` will contain the following structure. ```python { @@ -73,7 +77,8 @@ class FabricSummary: } } ``` - Usage: + + ## Usage ```python # params is typically obtained from ansible_module.params @@ -96,26 +101,27 @@ class FabricSummary: instance.refresh() fabric_summary = instance.data device_count = instance.device_count + fabric_is_empty = instance.fabric_is_empty ``` etc... """ def __init__(self): - self.class_name = self.__class__.__name__ + self.class_name: str = self.__class__.__name__ - self.log = logging.getLogger(f"dcnm.{self.class_name}") + self.log: logging.Logger = logging.getLogger(f"dcnm.{self.class_name}") - self.data: dict[str, dict] = {"switchSWVersions": {}, "switchHealth": {}, "switchHWVersions": {}, "switchConfig": {}, "switchRoles": {}} - self.ep_fabric_summary = EpFabricSummary() - self.conversion = ConversionUtils() + self.data: dict[str, dict[str, Any]] = {} + self._conversion: ConversionUtils = ConversionUtils() + self._ep_fabric_summary: EpFabricSummary = EpFabricSummary() + self._results: Results = Results() + self._rest_send: RestSend = RestSend(params={}) # set to True in refresh() after a successful request to the controller # Used by getter properties to ensure refresh() has been called prior # to returning data. - self.refreshed = False + self.refreshed: bool = False - self._rest_send: RestSend = RestSend({}) - self._results: Results = Results() self._border_gateway_count: int = 0 self._device_count: int = 0 self._fabric_name: str = "" @@ -127,20 +133,25 @@ def __init__(self): def _update_device_counts(self): """ - - From the controller response, update class properties - pertaining to device counts. - - By the time refresh() calls this method, self.data - has been verified, so no need to verify it here. + # Summary + + From the controller response, update class properties pertaining to device counts. + + ## Raises + + ### ValueError + + - `self.data` is empty. """ - method_name = inspect.stack()[0][3] + method_name: str = inspect.stack()[0][3] msg = f"{self.class_name}.{method_name}: " msg = f"self.data: {json.dumps(self.data, indent=4, sort_keys=True)}" self.log.debug(msg) - if self.data is None: + if not self.data: msg = f"{self.class_name}.{method_name}: " - msg += "self.data is None. Unable to update device counts." + msg += "self.data is empty. Unable to update device counts." raise ValueError(msg) self._border_gateway_count = self.data.get("switchRoles", {}).get("border gateway", 0) @@ -148,32 +159,45 @@ def _update_device_counts(self): self._spine_count = self.data.get("switchRoles", {}).get("spine", 0) self._device_count = self.leaf_count + self.spine_count + self.border_gateway_count - def _set_fabric_summary_endpoint(self): + def _set_fabric_summary_endpoint(self) -> None: """ - - Set the fabric_summary endpoint. - - Raise ``ValueError`` if unable to retrieve the endpoint. + # Summary + + Set the fabric_summary endpoint. + + ## Raises + + ### ValueError + + - Unable to retrieve the endpoint. """ 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 + self._ep_fabric_summary.fabric_name = self.fabric_name + self.rest_send.path = self._ep_fabric_summary.path + self.rest_send.verb = self._ep_fabric_summary.verb except ValueError as error: msg = "Error retrieving fabric_summary endpoint. " msg += f"Detail: {error}" self.log.debug(msg) raise ValueError(msg) from error - def _verify_controller_response(self): + def _verify_controller_response(self) -> None: """ - - Raise ``ControllerResponseError`` if RETURN_CODE != 200. - - Raise ``ControllerResponseError`` if DATA is missing or empty. + # Summary + + Verify the controller response after a refresh(). + + ## Raises + + ### ControllerResponseError + + - RETURN_CODE != 200. + - DATA is missing or empty. """ - method_name = inspect.stack()[0][3] + method_name: str = inspect.stack()[0][3] - # pylint: disable=no-member - controller_return_code = self.rest_send.response_current.get("RETURN_CODE", None) - controller_message = self.rest_send.response_current.get("MESSAGE", None) + controller_return_code = self._rest_send.response_current.get("RETURN_CODE", None) + controller_message = self._rest_send.response_current.get("MESSAGE", None) if controller_return_code != 200: msg = f"{self.class_name}.{method_name}: " msg += "Failed to retrieve fabric_summary for fabric_name " @@ -185,35 +209,40 @@ def _verify_controller_response(self): # DATA is set to an empty dict in refresh() if the controller response # does not contain a DATA key. - if self.data is None: - msg = f"{self.class_name}.{method_name}: " - msg += "Controller responded with missing DATA." - raise ControllerResponseError(msg) - if len(self.data) == 0: + if not self.data: msg = f"{self.class_name}.{method_name}: " msg += "Controller responded with missing or empty DATA." raise ControllerResponseError(msg) - def refresh(self): + def refresh(self) -> None: """ - - Refresh fabric summary info from the controller and - populate ``self.data`` with the result. - - ``self.data`` is a ``dict`` of fabric summary info for one fabric. - - raise ``ValueError`` if ``fabric_name`` is not set. - - raise ``ValueError`` if unable to retrieve fabric_summary endpoint. - - raise ``ValueError`` if ``_update_device_counts()`` fails. - - raise ``ControllerResponseError`` if the controller - ``RETURN_CODE`` != 200 + # Summary + + Refresh fabric summary info from the controller and populate `self.data` with the result. + + `self.data` is a dict of fabric summary info for one fabric. + + # Raises + + ## `ValueError` if + + - `fabric_name` is not set. + - `rest_send` is not properly configured (rest_send.params is empty). + - Unable to retrieve fabric_summary endpoint. + - `_update_device_counts()` fails. + + ## `ControllerResponseError` if + + - The controller `RETURN_CODE` != 200 """ - method_name = inspect.stack()[0][3] - if self.fabric_name is None: + method_name: str = inspect.stack()[0][3] + if self.fabric_name == "": msg = f"{self.class_name}.{method_name}: " msg += f"Set {self.class_name}.fabric_name prior to calling " msg += f"{self.class_name}.refresh()." raise ValueError(msg) - # pylint: disable=no-member - if self.rest_send is None: + if self._rest_send.params == {}: msg = f"{self.class_name}.{method_name}: " msg += f"Set {self.class_name}.rest_send prior to calling " msg += f"{self.class_name}.refresh()." @@ -229,22 +258,20 @@ def refresh(self): # 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. - save_check_mode = self.rest_send.check_mode - self.rest_send.check_mode = False - self.rest_send.commit() - self.rest_send.check_mode = save_check_mode - self.data = copy.deepcopy(self.rest_send.response_current.get("DATA", {})) - + save_check_mode = self._rest_send.check_mode + self._rest_send.check_mode = False + self._rest_send.commit() + self._rest_send.check_mode = save_check_mode + self.data = copy.deepcopy(self._rest_send.response_current.get("DATA", {})) msg = f"self.data: {json.dumps(self.data, indent=4, sort_keys=True)}" self.log.debug(msg) - 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 + self.results.response_current = self._rest_send.response_current + # self.results.add_response(self._rest_send.response_current) + self.results.result_current = self._rest_send.result_current + # self.results.add_result(self._rest_send.result_current) self.results.register_task_result() - # pylint: enable=no-member try: self._verify_controller_response() except ControllerResponseError as error: @@ -255,9 +282,17 @@ def refresh(self): self.refreshed = True self._update_device_counts() - def verify_refresh_has_been_called(self, attempted_method_name): + def verify_refresh_has_been_called(self, attempted_method_name: str) -> None: """ - - raise ``ValueError`` if ``refresh()`` has not been called. + # Summary + + Verify that `refresh()` has been called prior to accessing properties that depend on `refresh()`. + + # Raises + + ## ValueError + + - `refresh()` has not been called. """ if self.refreshed is True: return @@ -266,25 +301,45 @@ def verify_refresh_has_been_called(self, attempted_method_name): raise ValueError(msg) @property - def all_data(self) -> dict: + def all_data(self) -> dict [str, dict[str, Any]]: """ - - Return raw fabric summary data from the controller. - - Raise ``ValueError`` if ``refresh()`` has not been called. + # Summary + + Return raw fabric summary data from the controller. + + ## Raises + + ### ValueError + + - `refresh()` has not been called. """ - method_name = inspect.stack()[0][3] + method_name: str = inspect.stack()[0][3] try: self.verify_refresh_has_been_called(method_name) except ValueError as error: raise ValueError(error) from error + if self.data == {}: + msg = f"{self.class_name}.{method_name}: " + msg += "self.data is empty. Unable to return fabric summary data. " + msg += f"Ensure {self.class_name}.refresh() has been called successfully." + self.log.error(msg) + raise ValueError(msg) return self.data @property def border_gateway_count(self) -> int: """ - - Return the number of border gateway devices in fabric fabric_name. - - Raise ``ValueError`` if ``refresh()`` has not been called. + # Summary + + Return the number of border gateway devices in fabric fabric_name. + + ## Raises + + ### ValueError + + - `refresh()` has not been called. """ - method_name = inspect.stack()[0][3] + method_name: str = inspect.stack()[0][3] try: self.verify_refresh_has_been_called(method_name) except ValueError as error: @@ -294,10 +349,17 @@ def border_gateway_count(self) -> int: @property def device_count(self) -> int: """ - - Return the total number of devices in fabric fabric_name. - - Raise ``ValueError`` if ``refresh()`` has not been called. + # Summary + + Return the total number of devices in fabric fabric_name. + + ## Raises + + ### ValueError + + - `refresh()` has not been called. """ - method_name = inspect.stack()[0][3] + method_name: str = inspect.stack()[0][3] try: self.verify_refresh_has_been_called(method_name) except ValueError as error: @@ -307,10 +369,17 @@ def device_count(self) -> int: @property def fabric_is_empty(self) -> bool: """ - - Return True if the fabric is empty. - - Raise ``ValueError`` if ``refresh()`` has not been called. + # Summary + + Return True if the fabric is empty. + + ## Raises + + ### ValueError + + - `refresh()` has not been called. """ - method_name = inspect.stack()[0][3] + method_name: str = inspect.stack()[0][3] try: self.verify_refresh_has_been_called(method_name) except ValueError as error: @@ -322,29 +391,42 @@ def fabric_is_empty(self) -> bool: @property def fabric_name(self) -> str: """ + # Summary + - getter: Return the fabric_name to query. - setter: Set the fabric_name to query. - - setter: Raise ``ValueError`` if fabric_name is not a string. - - setter: Raise ``ValueError`` if fabric_name is invalid (i.e. - the controller would return an error due to invalid characters). + + ## Raises + + ### ValueError + + - setter: fabric_name is not a string. + - setter: fabric_name is invalid (i.e. the controller would return an error due to invalid characters). """ return self._fabric_name @fabric_name.setter def fabric_name(self, value: str): try: - self.conversion.validate_fabric_name(value) - except ValueError as error: + self._conversion.validate_fabric_name(value) + except (TypeError, ValueError) as error: raise ValueError(error) from error self._fabric_name = value @property def leaf_count(self) -> int: """ - - Return the number of leaf devices in fabric fabric_name. - - Raise ``ValueError`` if ``refresh()`` has not been called. + # Summary + + Return the number of leaf devices in fabric fabric_name. + + ## Raises + + ### ValueError + + - `refresh()` has not been called. """ - method_name = inspect.stack()[0][3] + method_name: str = inspect.stack()[0][3] try: self.verify_refresh_has_been_called(method_name) except ValueError as error: @@ -354,37 +436,99 @@ def leaf_count(self) -> int: @property def rest_send(self) -> RestSend: """ + # Summary + An instance of the RestSend class. + + ## Raises + + - setter: `TypeError` if the value is not an instance of RestSend. + - setter: `ValueError` if RestSend.params is not set. + + ## getter + + Return an instance of the RestSend class. + + ## setter + + Set an instance of the RestSend class. """ + method_name: str = inspect.stack()[0][3] + if not self._rest_send.params: + msg = f"{self.class_name}.{method_name}: " + msg += "RestSend.params must be set before accessing." + raise ValueError(msg) return self._rest_send @rest_send.setter def rest_send(self, value: RestSend) -> None: - if not value.params: - method_name = inspect.stack()[0][3] - msg = f"{self.class_name}.{method_name}: " - msg += "rest_send must have params set." - raise ValueError(msg) + method_name: str = inspect.stack()[0][3] + _class_have: str = "" + _class_need: Literal["RestSend"] = "RestSend" + msg = f"{self.class_name}.{method_name}: " + msg += f"value must be an instance of {_class_need}. " + msg += f"Got value {value} of type {type(value).__name__}." + try: + _class_have = value.class_name + except AttributeError as error: + msg += f" Error detail: {error}." + raise TypeError(msg) from error + if _class_have != _class_need: + raise TypeError(msg) self._rest_send = value @property def results(self) -> Results: """ + # Summary + An instance of the Results class. + + ## Raises + + - setter: `TypeError` if the value is not an instance of Results. + + ## getter + + Return an instance of the Results class. + + ## setter + + Set an instance of the Results class. """ return self._results @results.setter def results(self, value: Results) -> None: + method_name: str = inspect.stack()[0][3] + _class_have: str = "" + _class_need: Literal["Results"] = "Results" + msg = f"{self.class_name}.{method_name}: " + msg += f"value must be an instance of {_class_need}. " + msg += f"Got value {value} of type {type(value).__name__}." + try: + _class_have = value.class_name + except AttributeError as error: + msg += f" Error detail: {error}." + raise TypeError(msg) from error + if _class_have != _class_need: + raise TypeError(msg) self._results = value @property def spine_count(self) -> int: """ - - Return the number of spine devices in fabric fabric_name. - - Raise ``ValueError`` if ``refresh()`` has not been called. + # Summary + + Return the number of spine devices in fabric fabric_name. + + ## Raises + + ### ValueError + + - `refresh()` has not been called. """ - method_name = inspect.stack()[0][3] + method_name: str = inspect.stack()[0][3] try: self.verify_refresh_has_been_called(method_name) except ValueError as error: diff --git a/tests/unit/modules/dcnm/dcnm_fabric/fixtures/responses_FabricSummary_V2.json b/tests/unit/modules/dcnm/dcnm_fabric/fixtures/responses_FabricSummary_V2.json new file mode 100644 index 000000000..c88826705 --- /dev/null +++ b/tests/unit/modules/dcnm/dcnm_fabric/fixtures/responses_FabricSummary_V2.json @@ -0,0 +1,389 @@ +{ + "test_notes": [ + "Mocked responses for FabricSummary (v2) class.", + "Includes responses for config_deploy, fabric_delete, fabric_summary_v2, and fabric_update_bulk tests.", + "As of 2025-11-24, only the responses for fabric_summary_v2 tests are actively used.", + "Other responses are retained for potential future use when config_deploy, fabric_delete, and fabric_update_bulk are updated to use FabricSummary (v2)." + ], + "test_fabric_config_deploy_00200a": { + "DATA": { + "switchSWVersions": { + "10.2(5)": 2, + "10.3(1)": 2 + }, + "switchHealth": { + "Healthy": 1, + "Minor": 3 + }, + "switchHWVersions": { + "N9K-C93180YC-EX": 2, + "N9K-C9336C-FX2": 1, + "N9K-C9504": 1 + }, + "switchConfig": { + "NA": 4 + }, + "switchRoles": { + "leaf": 2, + "spine": 1, + "border gateway": 1 + } + }, + "MESSAGE": "OK", + "METHOD": "GET", + "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/lan-fabric/rest/control/switches/MyFabric/overview", + "RETURN_CODE": 200 + }, + "test_fabric_config_deploy_00210a": { + "DATA": { + "switchSWVersions": { + "10.2(5)": 2, + "10.3(1)": 2 + }, + "switchHealth": { + "Healthy": 1, + "Minor": 3 + }, + "switchHWVersions": { + "N9K-C93180YC-EX": 2, + "N9K-C9336C-FX2": 1, + "N9K-C9504": 1 + }, + "switchConfig": { + "NA": 4 + }, + "switchRoles": { + "leaf": 2, + "spine": 1, + "border gateway": 1 + } + }, + "MESSAGE": "OK", + "METHOD": "GET", + "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/lan-fabric/rest/control/switches/MyFabric/overview", + "RETURN_CODE": 200 + }, + "test_fabric_config_deploy_00220a": { + "DATA": { + "switchSWVersions": { + "10.2(5)": 2, + "10.3(1)": 2 + }, + "switchHealth": { + "Healthy": 1, + "Minor": 3 + }, + "switchHWVersions": { + "N9K-C93180YC-EX": 2, + "N9K-C9336C-FX2": 1, + "N9K-C9504": 1 + }, + "switchConfig": { + "NA": 4 + }, + "switchRoles": { + "leaf": 2, + "spine": 1, + "border gateway": 1 + } + }, + "MESSAGE": "OK", + "METHOD": "GET", + "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/lan-fabric/rest/control/switches/MyFabric/overview", + "RETURN_CODE": 200 + }, + "test_fabric_delete_00040a": { + "DATA": { + "switchConfig": {}, + "switchHWVersions": {}, + "switchHealth": {}, + "switchRoles": {}, + "switchSWVersions": {} + }, + "MESSAGE": "OK", + "METHOD": "GET", + "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/lan-fabric/rest/control/switches/f1/overview", + "RETURN_CODE": 200 + }, + "test_fabric_delete_00042a": { + "DATA": { + "switchConfig": {}, + "switchHWVersions": {}, + "switchHealth": {}, + "switchRoles": {}, + "switchSWVersions": {} + }, + "MESSAGE": "OK", + "METHOD": "GET", + "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/lan-fabric/rest/control/switches/f1/overview", + "RETURN_CODE": 200 + }, + "test_fabric_delete_00043a": { + "DATA": { + "switchConfig": {}, + "switchHWVersions": {}, + "switchHealth": {}, + "switchRoles": {}, + "switchSWVersions": {} + }, + "MESSAGE": "OK", + "METHOD": "GET", + "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/lan-fabric/rest/control/switches/f1/overview", + "RETURN_CODE": 200 + }, + "test_fabric_delete_00044a": { + "DATA": { + "switchConfig": {}, + "switchHWVersions": {}, + "switchHealth": {}, + "switchRoles": {}, + "switchSWVersions": {} + }, + "MESSAGE": "OK", + "METHOD": "GET", + "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/lan-fabric/rest/control/switches/MyFabric/overview", + "RETURN_CODE": 200 + }, + "test_fabric_delete_00050a": { + "DATA": { + "switchConfig": {}, + "switchHWVersions": {}, + "switchHealth": {}, + "switchRoles": { + "leaf": 4 + }, + "switchSWVersions": {} + }, + "MESSAGE": "OK", + "METHOD": "GET", + "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/lan-fabric/rest/control/switches/f1/overview", + "RETURN_CODE": 200 + }, + "test_fabric_delete_00051a": { + "DATA": { + "timestamp": 1713467047741, + "status": 404, + "error": "Not Found", + "path": "/rest/control/switches/MyFabric/overview" + }, + "MESSAGE": "Not Found", + "METHOD": "GET", + "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/lan-fabric/rest/control/switches/f1/overview", + "RETURN_CODE": 404 + }, + "test_fabric_summary_v2_00033a": { + "DATA": { + "switchSWVersions": { + "10.2(5)": 2, + "10.3(1)": 2 + }, + "switchHealth": { + "Healthy": 1, + "Minor": 3 + }, + "switchHWVersions": { + "N9K-C93180YC-EX": 2, + "N9K-C9336C-FX2": 1, + "N9K-C9504": 1 + }, + "switchConfig": { + "NA": 4 + }, + "switchRoles": { + "leaf": 2, + "spine": 1, + "border gateway": 1 + } + }, + "MESSAGE": "OK", + "METHOD": "GET", + "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/lan-fabric/rest/control/switches/MyFabric/overview", + "RETURN_CODE": 200 + }, + "test_fabric_summary_v2_00034a": { + "DATA": { + "switchConfig": {}, + "switchHWVersions": {}, + "switchHealth": {}, + "switchRoles": {}, + "switchSWVersions": {} + }, + "MESSAGE": "OK", + "METHOD": "GET", + "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/lan-fabric/rest/control/switches/MyFabric/overview", + "RETURN_CODE": 200 + }, + "test_fabric_summary_v2_00035a": { + "DATA": { + "timestamp": 1713467047741, + "status": 404, + "error": "Not Found", + "path": "/rest/control/switches/MyFabric/overview" + }, + "MESSAGE": "Not Found", + "METHOD": "GET", + "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/lan-fabric/rest/control/switches/MyFabric/overview", + "RETURN_CODE": 404 + }, + "test_fabric_summary_v2_00036a": { + "TEST_NOTES": [ + "DATA is missing.", + "This should never happen, but we need to handle it gracefully." + ], + "MESSAGE": "OK", + "METHOD": "GET", + "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/lan-fabric/rest/control/switches/MyFabric/overview", + "RETURN_CODE": 200 + }, + "test_fabric_update_bulk_00031a": { + "DATA": { + "switchConfig": {}, + "switchHWVersions": {}, + "switchHealth": {}, + "switchRoles": {}, + "switchSWVersions": {} + }, + "MESSAGE": "OK", + "METHOD": "GET", + "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/lan-fabric/rest/control/switches/f1/overview", + "RETURN_CODE": 200 + }, + "test_fabric_update_bulk_00032a": { + "DATA": { + "switchConfig": {}, + "switchHWVersions": {}, + "switchHealth": {}, + "switchRoles": {}, + "switchSWVersions": {} + }, + "MESSAGE": "OK", + "METHOD": "GET", + "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/lan-fabric/rest/control/switches/f1/overview", + "RETURN_CODE": 200 + }, + "test_fabric_update_bulk_00033a": { + "DATA": { + "switchConfig": {}, + "switchHWVersions": {}, + "switchHealth": {}, + "switchRoles": {}, + "switchSWVersions": {} + }, + "MESSAGE": "OK", + "METHOD": "GET", + "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/lan-fabric/rest/control/switches/f1/overview", + "RETURN_CODE": 200 + }, + "test_fabric_update_bulk_00034a": { + "DATA": { + "switchConfig": {}, + "switchHWVersions": {}, + "switchHealth": {}, + "switchRoles": {}, + "switchSWVersions": {} + }, + "MESSAGE": "OK", + "METHOD": "GET", + "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/lan-fabric/rest/control/switches/f1/overview", + "RETURN_CODE": 200 + }, + "test_fabric_update_bulk_00035a": { + "DATA": { + "switchSWVersions": { + "10.2(5)": 2, + "10.3(1)": 2 + }, + "switchHealth": { + "Healthy": 1, + "Minor": 3 + }, + "switchHWVersions": { + "N9K-C93180YC-EX": 2, + "N9K-C9336C-FX2": 1, + "N9K-C9504": 1 + }, + "switchConfig": { + "NA": 4 + }, + "switchRoles": { + "leaf": 2, + "spine": 1, + "border gateway": 1 + } + }, + "MESSAGE": "OK", + "METHOD": "GET", + "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/lan-fabric/rest/control/switches/f1/overview", + "RETURN_CODE": 200 + }, + "test_fabric_update_bulk_00040a": { + "DATA": { + "timestamp": 1713467047741, + "status": 404, + "error": "Not Found", + "path": "/rest/control/switches/MyFabric/overview" + }, + "MESSAGE": "Not Found", + "METHOD": "GET", + "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/lan-fabric/rest/control/switches/f1/overview", + "RETURN_CODE": 404 + }, + "test_fabric_update_bulk_00130a": { + "DATA": { + "switchSWVersions": { + "10.2(5)": 2, + "10.3(1)": 2 + }, + "switchHealth": { + "Healthy": 1, + "Minor": 3 + }, + "switchHWVersions": { + "N9K-C93180YC-EX": 2, + "N9K-C9336C-FX2": 1, + "N9K-C9504": 1 + }, + "switchConfig": { + "NA": 4 + }, + "switchRoles": { + "leaf": 2, + "spine": 1, + "border gateway": 1 + } + }, + "MESSAGE": "OK", + "METHOD": "GET", + "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/lan-fabric/rest/control/switches/f1/overview", + "RETURN_CODE": 200 + }, + "test_fabric_update_bulk_00140a": { + "DATA": { + "switchSWVersions": { + "10.2(5)": 2, + "10.3(1)": 2 + }, + "switchHealth": { + "Healthy": 1, + "Minor": 3 + }, + "switchHWVersions": { + "N9K-C93180YC-EX": 2, + "N9K-C9336C-FX2": 1, + "N9K-C9504": 1 + }, + "switchConfig": { + "NA": 4 + }, + "switchRoles": { + "leaf": 2, + "spine": 1, + "border gateway": 1 + } + }, + "MESSAGE": "OK", + "METHOD": "GET", + "REQUEST_PATH": "https://172.22.150.244:443/appcenter/cisco/ndfc/api/v1/lan-fabric/rest/control/switches/f1/overview", + "RETURN_CODE": 200 + } +} diff --git a/tests/unit/modules/dcnm/dcnm_fabric/test_fabric_summary_v2.py b/tests/unit/modules/dcnm/dcnm_fabric/test_fabric_summary_v2.py new file mode 100644 index 000000000..5d480d4d3 --- /dev/null +++ b/tests/unit/modules/dcnm/dcnm_fabric/test_fabric_summary_v2.py @@ -0,0 +1,811 @@ +# Copyright (c) 2025 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 +""" +Unit tests for FabricSummary (v2) class +""" +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +__copyright__ = "Copyright (c) 2025 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.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 ( + does_not_raise, + fabric_summary_v2_fixture, + responses_fabric_summary_v2, +) + + +def test_fabric_summary_v2_00010(fabric_summary_v2) -> None: + """ + Classes and Methods + - FabricCommon + - __init__() + - FabricSummary + - __init__() + + Test + - Class attributes are initialized to expected values + - Exception is not raised + """ + with does_not_raise(): + instance = fabric_summary_v2 + assert instance.class_name == "FabricSummary" + assert instance.data == {} + assert instance.refreshed is False + assert instance._conversion.class_name == "ConversionUtils" + assert instance._ep_fabric_summary.class_name == "EpFabricSummary" + assert instance._results.class_name == "Results" + assert instance._border_gateway_count == 0 + assert instance._device_count == 0 + assert instance._fabric_name == "" + assert instance._leaf_count == 0 + assert instance._spine_count == 0 + + +def test_fabric_summary_v2_00030(fabric_summary_v2) -> None: + """ + Classes and Methods + - FabricCommon() + - __init__() + - FabricSummary() + - __init__() + - refresh() + + Summary + - Verify FabricSummary().refresh() raises ``ValueError`` + when ``FabricSummary().fabric_name`` is not set. + + Code Flow - Setup + - FabricSummary() is instantiated + - FabricSummary().rest_send is set + + Code Flow - Test + - FabricSummary().refresh() is called without having + first set FabricSummary().fabric_name + + Expected Result + - ``ValueError`` is raised + - Exception message matches expected + """ + + def responses(): + yield {"key": "value"} + + with does_not_raise(): + sender = Sender() + sender.gen = ResponseGenerator(responses()) + instance = fabric_summary_v2 + instance.rest_send = RestSend(params={"check_mode": False, "state": "query"}) + instance.rest_send.sender = sender + + match = r"FabricSummary\.refresh: " + match += r"Set FabricSummary\.fabric_name prior to calling " + match += r"FabricSummary\.refresh\(\)\." + with pytest.raises(ValueError, match=match): + instance.refresh() + + +def test_fabric_summary_v2_00031(fabric_summary_v2) -> None: + """ + Classes and Methods + - FabricCommon() + - __init__() + - FabricSummary() + - __init__() + - refresh() + + Summary + - Verify FabricSummary().refresh() raises ``ValueError`` + when ``FabricSummary().rest_send`` is not set. + + Code Flow - Setup + - FabricSummary() is instantiated + - FabricSummary().fabric_name is set + + Code Flow - Test + - FabricSummary().refresh() is called without having + first set FabricSummary().rest_send + + Expected Result + - ``ValueError`` is raised + - Exception message matches expected + """ + with does_not_raise(): + instance = fabric_summary_v2 + instance.fabric_name = "MyFabric" + + match = r"FabricSummary\.refresh: " + match += r"Set FabricSummary\.rest_send prior to calling " + match += r"FabricSummary\.refresh\(\)\." + with pytest.raises(ValueError, match=match): + instance.refresh() + + +def test_fabric_summary_v2_00032(monkeypatch, fabric_summary_v2) -> None: + """ + Classes and Methods + - FabricCommon() + - __init__() + - FabricSummary() + - __init__() + - refresh() + - _set_fabric_summary_endpoint() + + Summary + - Verify that FabricSummary()._set_fabric_summary_endpoint() + re-raises ``ValueError`` when EpFabricSummary() raises + ``ValueError``. + """ + + class MockEpFabricSummary: # pylint: disable=too-few-public-methods + """ + Mock the EpFabricSummary.fabric_name getter property to raise ``ValueError``. + """ + + def validate_fabric_name(self, value="MyFabric"): + """ + Mocked method required for test, but not relevant to test result. + """ + + @property + def fabric_name(self): + """ + - Mocked fabric_name property getter + """ + + @fabric_name.setter + def fabric_name(self, value): + """ + - Mocked fabric_name property setter + """ + msg = "mocked MockEpFabricSummary().fabric_name setter exception." + raise ValueError(msg) + + def responses(): + yield {"key": "value"} + + with does_not_raise(): + instance = fabric_summary_v2 + monkeypatch.setattr(instance, "_ep_fabric_summary", MockEpFabricSummary()) + instance.fabric_name = "MyFabric" + sender = Sender() + sender.gen = ResponseGenerator(responses()) + instance.rest_send = RestSend(params={"check_mode": False, "state": "query"}) + instance.rest_send.sender = sender + + match = r"Error retrieving fabric_summary endpoint\. Detail: mocked MockEpFabricSummary\(\)\.fabric_name setter exception\." + + with pytest.raises(ValueError, match=match): + instance.refresh() + + +def test_fabric_summary_v2_00033(fabric_summary_v2) -> None: + """ + Classes and Methods + - FabricCommon() + - __init__() + - FabricSummary() + - __init__() + - refresh() + - _update_device_counts() + + Summary + - Verify refresh() success case with populated fabric: + - RETURN_CODE is 200. + - Controller response contains one fabric (f1). + - Fabric contains 2x leaf, 1x spine, 1x border_gateway. + + Code Flow - Setup + - FabricSummary() is instantiated + - FabricSummary().RestSend() is instantiated + - FabricSummary().Results() is instantiated + - FabricSummary().refresh() is called + - responses_FabricSummary contains a dict with: + - RETURN_CODE == 200 + - DATA == [] + + Code Flow - Test + - FabricSummary().refresh() is called + + Expected Result + - Exception is not raised + - instance.data returns expected fabric data + - Results() are updated + - FabricSummary().refreshed is True + - FabricSummary()._properties are updated + """ + method_name = inspect.stack()[0][3] + key = f"{method_name}a" + + def responses(): + yield responses_fabric_summary_v2(key) + + sender = Sender() + sender.gen = ResponseGenerator(responses()) + rest_send = RestSend(params={"check_mode": False, "state": "query"}) + rest_send.unit_test = True + rest_send.timeout = 1 + rest_send.response_handler = ResponseHandler() + rest_send.sender = sender + with does_not_raise(): + instance = fabric_summary_v2 + instance.rest_send = rest_send + instance.fabric_name = "MyFabric" + + with does_not_raise(): + instance.refresh() + + assert isinstance(instance.results.diff, list) + assert isinstance(instance.results.result, list) + assert isinstance(instance.results.response, list) + + assert len(instance.results.diff) == 1 + assert len(instance.results.result) == 1 + assert len(instance.results.response) == 1 + + assert instance.results.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.data.get("switchRoles", {}).get("leaf", None) == 2 + assert instance.data.get("switchRoles", {}).get("spine", None) == 1 + assert instance.data.get("switchRoles", {}).get("border gateway", None) == 1 + assert instance.border_gateway_count == 1 + assert instance.device_count == 4 + assert instance.leaf_count == 2 + assert instance.spine_count == 1 + assert instance.fabric_is_empty is False + + +def test_fabric_summary_v2_00034(fabric_summary_v2) -> None: + """ + Classes and Methods + - FabricCommon() + - __init__() + - FabricSummary() + - __init__() + - refresh() + - _update_device_counts() + + Summary + - Verify refresh() success case with empty fabric: + - RETURN_CODE is 200. + - Controller response contains one fabric (f1). + - Fabric does not contain any switches. + + Code Flow - Setup + - FabricSummary() is instantiated + - FabricSummary().RestSend() is instantiated + - FabricSummary().Results() is instantiated + - FabricSummary().refresh() is called + - responses_FabricSummary contains a dict with: + - RETURN_CODE == 200 + - DATA == [] + + Code Flow - Test + - FabricSummary().refresh() is called + + Expected Result + - Exception is not raised + - instance.all_data returns expected fabric data + - Results() are updated + - FabricSummary().refreshed is True + - FabricSummary()._properties are updated + """ + method_name = inspect.stack()[0][3] + key = f"{method_name}a" + + def responses(): + yield responses_fabric_summary_v2(key) + + sender = Sender() + sender.gen = ResponseGenerator(responses()) + rest_send = RestSend(params={"check_mode": False, "state": "query"}) + rest_send.unit_test = True + rest_send.timeout = 1 + rest_send.response_handler = ResponseHandler() + rest_send.sender = sender + + with does_not_raise(): + instance = fabric_summary_v2 + instance.rest_send = rest_send + instance.fabric_name = "MyFabric" + + with does_not_raise(): + instance.refresh() + + assert instance.refreshed is True + + assert isinstance(instance.results.diff, list) + assert isinstance(instance.results.result, list) + assert isinstance(instance.results.response, list) + + assert len(instance.results.diff) == 1 + assert len(instance.results.result) == 1 + assert len(instance.results.response) == 1 + + assert instance.results.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("switchRoles", {}).get("leaf", None) is None + assert instance.all_data.get("switchRoles", {}).get("spine", None) is None + assert instance.all_data.get("switchRoles", {}).get("border gateway", None) is None + assert instance.border_gateway_count == 0 + assert instance.device_count == 0 + assert instance.leaf_count == 0 + assert instance.spine_count == 0 + assert instance.fabric_is_empty is True + + +def test_fabric_summary_v2_00035(fabric_summary_v2) -> None: + """ + Classes and Methods + - FabricCommon() + - __init__() + - FabricSummary() + - __init__() + - refresh() + - _update_device_counts() + + Summary + - Verify refresh() failure case: + - RETURN_CODE is 404. + - Controller response when fabric does not exist + + Code Flow - Setup + - FabricSummary() is instantiated. + - FabricSummary().RestSend() is instantiated. + - FabricSummary().fabric_name is set to a fabric that does not exist. + - FabricSummary().refresh() is called. + - responses_FabricSummary contains a dict with: + - RETURN_CODE == 404 + - DATA == { + "timestamp": 1713467047741, + "status": 404, + "error": "Not Found", + "path": "/rest/control/switches/MyFabric/overview" + } + + Code Flow - Test + - FabricSummary().refresh() is called + + Expected Result + - ``ControllerResponseException`` is raised + - instance.data contains error data + - Results() are updated + - FabricSummary().refreshed is False + - FabricSummary()._properties remain at their default values + """ + method_name = inspect.stack()[0][3] + key = f"{method_name}a" + + def responses(): + yield responses_fabric_summary_v2(key) + + sender = Sender() + sender.gen = ResponseGenerator(responses()) + rest_send = RestSend(params={"check_mode": False, "state": "query"}) + rest_send.unit_test = True + rest_send.timeout = 1 + rest_send.response_handler = ResponseHandler() + rest_send.sender = sender + + with does_not_raise(): + instance = fabric_summary_v2 + instance.rest_send = rest_send + instance.rest_send.unit_test = True + instance.rest_send.timeout = 1 + instance.fabric_name = "MyFabric" + + match = r"FabricSummary\._verify_controller_response:\s+" + match += r"Failed to retrieve fabric_summary for fabric_name MyFabric\.\s+" + match += r"RETURN_CODE: 404\.\s+" + match += r"MESSAGE: Not Found\." + with pytest.raises(ControllerResponseError, match=match): + instance.refresh() + + assert instance.refreshed is False + + assert isinstance(instance.results.diff, list) + assert isinstance(instance.results.result, list) + assert isinstance(instance.results.response, list) + + assert len(instance.results.diff) == 1 + assert len(instance.results.result) == 1 + assert len(instance.results.response) == 1 + + assert instance.results.response[0].get("RETURN_CODE", None) == 404 + assert instance.results.result[0].get("found", None) is False + 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.data.get("switchRoles", {}).get("leaf", None) is None + assert instance.data.get("switchRoles", {}).get("spine", None) is None + assert instance.data.get("switchRoles", {}).get("border gateway", None) is None + + +def test_fabric_summary_v2_00036(fabric_summary_v2) -> None: + """ + Classes and Methods + - FabricCommon() + - __init__() + - FabricSummary() + - __init__() + - refresh() + - _update_device_counts() + + Summary + - Verify refresh() failure case: + - RETURN_CODE is 200. + - DATA field is missing in the response. + - This shouldn't happen, but we need to handle it. + + Code Flow - Setup + - FabricSummary() is instantiated. + - FabricSummary().RestSend() is instantiated. + - FabricSummary().fabric_name is set to a fabric that does not exist. + - FabricSummary().refresh() is called. + - responses_FabricSummary contains a dict with: + - RETURN_CODE == 200 + - DATA missing + + Code Flow - Test + - FabricSummary().refresh() is called + + Expected Result + - ``ValueError`` is raised in FabricSummary()._verify_controller_response() + - instance.data is empty + - Results() are updated + - FabricSummary().refreshed is False + - FabricSummary()._properties remain at their default values + """ + method_name = inspect.stack()[0][3] + key = f"{method_name}a" + + def responses(): + yield responses_fabric_summary_v2(key) + + sender = Sender() + sender.gen = ResponseGenerator(responses()) + rest_send = RestSend(params={"check_mode": False, "state": "query"}) + rest_send.unit_test = True + rest_send.timeout = 1 + rest_send.response_handler = ResponseHandler() + rest_send.sender = sender + + with does_not_raise(): + instance = fabric_summary_v2 + instance.rest_send = rest_send + instance.rest_send.unit_test = True + instance.rest_send.timeout = 1 + instance.fabric_name = "MyFabric" + + match = r"FabricSummary.\_verify_controller_response:\s+" + match += r"Controller responded with missing or empty DATA\." + with pytest.raises(ControllerResponseError, match=match): + instance.refresh() + + assert instance.refreshed is False + + assert 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) == 1 + 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_summary_v2_00040(fabric_summary_v2) -> None: + """ + Classes and Methods + - FabricCommon() + - __init__() + - FabricSummary() + - __init__() + - all_data getter + + Summary + - Verify FabricSummary().all_data raises ``ValueError`` + when ``FabricSummary().refresh()`` has not been called. + + Code Flow - Setup + - FabricSummary() is instantiated + + Code Flow - Test + - FabricSummary().all_data is accessed without having + first called FabricSummary().refresh() + + Expected Result + - ``ValueError`` is raised + - Exception message matches expected + """ + with does_not_raise(): + instance = fabric_summary_v2 + + match = r"FabricSummary\.refresh\(\) must be called before " + match += r"accessing FabricSummary\.all_data\." + with pytest.raises(ValueError, match=match): + instance.all_data # pylint: disable=pointless-statement + + +def test_fabric_summary_v2_00050(fabric_summary_v2) -> None: + """ + Classes and Methods + - FabricCommon() + - __init__() + - FabricSummary() + - __init__() + - border_gateway_count getter + + Summary + - Verify FabricSummary().border_gateway_count raises ``ValueError`` + when ``FabricSummary().refresh()`` has not been called. + + Code Flow - Setup + - FabricSummary() is instantiated + + Code Flow - Test + - FabricSummary().border_gateway_count is accessed without having + first called FabricSummary().refresh() + + Expected Result + - ``ValueError`` is raised + - Exception message matches expected + """ + with does_not_raise(): + instance = fabric_summary_v2 + + match = r"FabricSummary\.refresh\(\) must be called before " + match += r"accessing FabricSummary\.border_gateway_count\." + with pytest.raises(ValueError, match=match): + instance.border_gateway_count # pylint: disable=pointless-statement + + +def test_fabric_summary_v2_00060(fabric_summary_v2) -> None: + """ + Classes and Methods + - FabricCommon() + - __init__() + - FabricSummary() + - __init__() + - device_count getter + + Summary + - Verify FabricSummary().device_count raises ``ValueError`` + when ``FabricSummary().refresh()`` has not been called. + + Code Flow - Setup + - FabricSummary() is instantiated + + Code Flow - Test + - FabricSummary().device_count is accessed without having + first called FabricSummary().refresh() + + Expected Result + - ``ValueError`` is raised + - Exception message matches expected + """ + with does_not_raise(): + instance = fabric_summary_v2 + + match = r"FabricSummary\.refresh\(\) must be called before " + match += r"accessing FabricSummary\.device_count\." + with pytest.raises(ValueError, match=match): + instance.device_count # pylint: disable=pointless-statement + + +def test_fabric_summary_v2_00070(fabric_summary_v2) -> None: + """ + Classes and Methods + - FabricCommon() + - __init__() + - FabricSummary() + - __init__() + - fabric_is_empty getter + + Summary + - Verify FabricSummary().fabric_is_empty raises ``ValueError`` + when ``FabricSummary().refresh()`` has not been called. + + Code Flow - Setup + - FabricSummary() is instantiated + + Code Flow - Test + - FabricSummary().fabric_is_empty is accessed without having + first called FabricSummary().refresh() + + Expected Result + - ``ValueError`` is raised + - Exception message matches expected + """ + with does_not_raise(): + instance = fabric_summary_v2 + + match = r"FabricSummary\.refresh\(\) must be called before " + match += r"accessing FabricSummary\.fabric_is_empty\." + with pytest.raises(ValueError, match=match): + instance.fabric_is_empty # pylint: disable=pointless-statement + + +MATCH_00080a = r"ConversionUtils\.validate_fabric_name: " +MATCH_00080a += r"Invalid fabric name\. " +MATCH_00080a += r"Expected string\. Got.*\." + +MATCH_00080b = r"ConversionUtils\.validate_fabric_name: " +MATCH_00080b += r"Invalid fabric name:.*\. " +MATCH_00080b += "Fabric name must start with a letter A-Z or a-z and " +MATCH_00080b += r"contain only the characters in: \[A-Z,a-z,0-9,-,_\]\." + + +@pytest.mark.parametrize( + "fabric_name, expected, does_raise", + [ + ("MyFabric", does_not_raise(), False), + ("My_Fabric", does_not_raise(), False), + ("My-Fabric", does_not_raise(), False), + ("M", does_not_raise(), False), + (1, pytest.raises(ValueError, match=MATCH_00080a), True), + ({}, pytest.raises(ValueError, match=MATCH_00080a), True), + ([1, 2, 3], pytest.raises(ValueError, match=MATCH_00080a), True), + ("1", pytest.raises(ValueError, match=MATCH_00080b), True), + ("-MyFabric", pytest.raises(ValueError, match=MATCH_00080b), True), + ("_MyFabric", pytest.raises(ValueError, match=MATCH_00080b), True), + ("1MyFabric", pytest.raises(ValueError, match=MATCH_00080b), True), + ("My Fabric", pytest.raises(ValueError, match=MATCH_00080b), True), + ("My*Fabric", pytest.raises(ValueError, match=MATCH_00080b), True), + ], +) +def test_fabric_summary_v2_00080(fabric_summary_v2, fabric_name, expected, does_raise) -> None: + """ + Classes and Methods + - FabricCommon() + - __init__() + - FabricSummary() + - __init__() + - fabric_name setter/getter + + Summary + - Verify FabricSummary().fabric_name re-raises ``ValueError`` + when fabric_name is invalid. + + Code Flow - Setup + - FabricSummary() is instantiated + + Code Flow - Test + - FabricSummary().fabric_name is set to a value that would + cause the controller to return an error. + + Expected Result + - ``ValueError`` is raised + - Exception message matches expected + """ + with does_not_raise(): + instance = fabric_summary_v2 + with expected: + instance.fabric_name = fabric_name + if does_raise is False: + assert instance.fabric_name == fabric_name + + +def test_fabric_summary_v2_00090(fabric_summary_v2) -> None: + """ + Classes and Methods + - FabricCommon() + - __init__() + - FabricSummary() + - __init__() + - leaf_count getter + + Summary + - Verify FabricSummary().leaf_count raises ``ValueError`` + when ``FabricSummary().refresh()`` has not been called. + + Code Flow - Setup + - FabricSummary() is instantiated + + Code Flow - Test + - FabricSummary().leaf_count is accessed without having + first called FabricSummary().refresh() + + Expected Result + - ``ValueError`` is raised + - Exception message matches expected + """ + with does_not_raise(): + instance = fabric_summary_v2 + + match = r"FabricSummary\.refresh\(\) must be called before " + match += r"accessing FabricSummary\.leaf_count\." + with pytest.raises(ValueError, match=match): + instance.leaf_count # pylint: disable=pointless-statement + + +def test_fabric_summary_v2_00100(fabric_summary_v2) -> None: + """ + Classes and Methods + - FabricCommon() + - __init__() + - FabricSummary() + - __init__() + - spine_count getter + + Summary + - Verify FabricSummary().spine_count raises ``ValueError`` + when ``FabricSummary().refresh()`` has not been called. + + Code Flow - Setup + - FabricSummary() is instantiated + + Code Flow - Test + - FabricSummary().spine_count is accessed without having + first called FabricSummary().refresh() + + Expected Result + - ``ValueError`` is raised + - Exception message matches expected + """ + with does_not_raise(): + instance = fabric_summary_v2 + + match = r"FabricSummary\.refresh\(\) must be called before " + match += r"accessing FabricSummary\.spine_count\." + with pytest.raises(ValueError, match=match): + instance.spine_count # pylint: disable=pointless-statement diff --git a/tests/unit/modules/dcnm/dcnm_fabric/utils.py b/tests/unit/modules/dcnm/dcnm_fabric/utils.py index a73c41618..e439b5871 100644 --- a/tests/unit/modules/dcnm/dcnm_fabric/utils.py +++ b/tests/unit/modules/dcnm/dcnm_fabric/utils.py @@ -11,51 +11,36 @@ # 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. - +""" +Utilities for dcnm_fabric module unit tests +""" from __future__ import absolute_import, division, print_function -__metaclass__ = type +__metaclass__ = type # pylint: disable=invalid-name from contextlib import contextmanager import pytest -from ansible_collections.ansible.netcommon.tests.unit.modules.utils import \ - AnsibleFailJson -from ansible_collections.cisco.dcnm.plugins.module_utils.common.response_handler import \ - ResponseHandler -from ansible_collections.cisco.dcnm.plugins.module_utils.fabric.common import \ - FabricCommon -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 \ - FabricConfigSave -from ansible_collections.cisco.dcnm.plugins.module_utils.fabric.create import ( - 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_v2 import \ - FabricDetails as FabricDetailsV2 -from ansible_collections.cisco.dcnm.plugins.module_utils.fabric.fabric_details_v2 import \ - FabricDetailsByName as FabricDetailsByNameV2 -from ansible_collections.cisco.dcnm.plugins.module_utils.fabric.fabric_details_v2 import \ - FabricDetailsByNvPair as FabricDetailsByNvPairV2 -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.query import \ - FabricQuery -from ansible_collections.cisco.dcnm.plugins.module_utils.fabric.replaced import \ - FabricReplacedBulk -from ansible_collections.cisco.dcnm.plugins.module_utils.fabric.template_get import \ - TemplateGet -from ansible_collections.cisco.dcnm.plugins.module_utils.fabric.template_get_all import \ - TemplateGetAll -from ansible_collections.cisco.dcnm.plugins.module_utils.fabric.update import \ - FabricUpdateBulk -from ansible_collections.cisco.dcnm.tests.unit.modules.dcnm.dcnm_fabric.fixture import \ - load_fixture +from ansible_collections.ansible.netcommon.tests.unit.modules.utils import AnsibleFailJson +from ansible_collections.cisco.dcnm.plugins.module_utils.common.response_handler import ResponseHandler +from ansible_collections.cisco.dcnm.plugins.module_utils.fabric.common import FabricCommon +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 FabricConfigSave +from ansible_collections.cisco.dcnm.plugins.module_utils.fabric.create import 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_v2 import FabricDetails as FabricDetailsV2 +from ansible_collections.cisco.dcnm.plugins.module_utils.fabric.fabric_details_v2 import FabricDetailsByName as FabricDetailsByNameV2 +from ansible_collections.cisco.dcnm.plugins.module_utils.fabric.fabric_details_v2 import FabricDetailsByNvPair as FabricDetailsByNvPairV2 +from ansible_collections.cisco.dcnm.plugins.module_utils.fabric.fabric_summary import FabricSummary +from ansible_collections.cisco.dcnm.plugins.module_utils.fabric.fabric_summary_v2 import FabricSummary as FabricSummaryV2 +from ansible_collections.cisco.dcnm.plugins.module_utils.fabric.fabric_types import FabricTypes +from ansible_collections.cisco.dcnm.plugins.module_utils.fabric.query import FabricQuery +from ansible_collections.cisco.dcnm.plugins.module_utils.fabric.replaced import FabricReplacedBulk +from ansible_collections.cisco.dcnm.plugins.module_utils.fabric.template_get import TemplateGet +from ansible_collections.cisco.dcnm.plugins.module_utils.fabric.template_get_all import TemplateGetAll +from ansible_collections.cisco.dcnm.plugins.module_utils.fabric.update import FabricUpdateBulk +from ansible_collections.cisco.dcnm.tests.unit.modules.dcnm.dcnm_fabric.fixture import load_fixture params = { "state": "merged", @@ -220,6 +205,14 @@ def fabric_summary_fixture(): return FabricSummary() +@pytest.fixture(name="fabric_summary_v2") +def fabric_summary_v2_fixture(): + """ + Return FabricSummary() instance. + """ + return FabricSummaryV2() + + @pytest.fixture(name="fabric_types") def fabric_types_fixture(): """ @@ -488,6 +481,16 @@ def responses_fabric_summary(key: str) -> dict[str, str]: return data +def responses_fabric_summary_v2(key: str) -> dict[str, str]: + """ + Return responses for FabricSummary (v2) + """ + data_file = "responses_FabricSummary_V2" + data = load_fixture(data_file).get(key) + print(f"{data_file}: {key} : {data}") + return data + + def responses_fabric_update_bulk(key: str) -> dict[str, str]: """ Return responses for FabricUpdateBulk From 2f9344c72a3824e36cb7fe09d7033bb0be72f018 Mon Sep 17 00:00:00 2001 From: Allen Robel Date: Mon, 24 Nov 2025 10:12:16 -1000 Subject: [PATCH 02/24] Appease linters No functional changes in this commit --- plugins/module_utils/fabric/fabric_summary_v2.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/plugins/module_utils/fabric/fabric_summary_v2.py b/plugins/module_utils/fabric/fabric_summary_v2.py index a58a229cf..d41913fda 100644 --- a/plugins/module_utils/fabric/fabric_summary_v2.py +++ b/plugins/module_utils/fabric/fabric_summary_v2.py @@ -77,7 +77,7 @@ class FabricSummary: } } ``` - + ## Usage ```python @@ -136,7 +136,7 @@ def _update_device_counts(self): # Summary From the controller response, update class properties pertaining to device counts. - + ## Raises ### ValueError @@ -301,7 +301,7 @@ def verify_refresh_has_been_called(self, attempted_method_name: str) -> None: raise ValueError(msg) @property - def all_data(self) -> dict [str, dict[str, Any]]: + def all_data(self) -> dict[str, dict[str, Any]]: """ # Summary @@ -395,7 +395,7 @@ def fabric_name(self) -> str: - getter: Return the fabric_name to query. - setter: Set the fabric_name to query. - + ## Raises ### ValueError @@ -417,7 +417,7 @@ def fabric_name(self, value: str): def leaf_count(self) -> int: """ # Summary - + Return the number of leaf devices in fabric fabric_name. ## Raises From c1c3df5b649cc3bf6bf7970b10107d07f3d0b2b4 Mon Sep 17 00:00:00 2001 From: Allen Robel Date: Mon, 24 Nov 2025 10:26:59 -1000 Subject: [PATCH 03/24] Use private attributes instead of property getters when calculating device count. Use private attributes instead of property getters when calculating device count. The current code calls property getters (self.leaf_count, self.spine_count, self.border_gateway_count) which invoke verify_refresh_has_been_called(), adding unnecessary overhead. Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- plugins/module_utils/fabric/fabric_summary_v2.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/module_utils/fabric/fabric_summary_v2.py b/plugins/module_utils/fabric/fabric_summary_v2.py index d41913fda..e185b0df6 100644 --- a/plugins/module_utils/fabric/fabric_summary_v2.py +++ b/plugins/module_utils/fabric/fabric_summary_v2.py @@ -157,7 +157,7 @@ def _update_device_counts(self): self._border_gateway_count = self.data.get("switchRoles", {}).get("border gateway", 0) self._leaf_count = self.data.get("switchRoles", {}).get("leaf", 0) self._spine_count = self.data.get("switchRoles", {}).get("spine", 0) - self._device_count = self.leaf_count + self.spine_count + self.border_gateway_count + self._device_count = self._leaf_count + self._spine_count + self._border_gateway_count def _set_fabric_summary_endpoint(self) -> None: """ From ebfa9f07d8042a1577e6d277417d2bbdd1831882 Mon Sep 17 00:00:00 2001 From: Allen Robel Date: Mon, 24 Nov 2025 10:32:52 -1000 Subject: [PATCH 04/24] FabricSummary (v2): Explain why lines are commented No functional changes in this commit. Addressing a Copilot comment regarding commented lines in fabric_summary_v2.py --- plugins/module_utils/fabric/fabric_summary_v2.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/plugins/module_utils/fabric/fabric_summary_v2.py b/plugins/module_utils/fabric/fabric_summary_v2.py index d41913fda..0b6fdcea0 100644 --- a/plugins/module_utils/fabric/fabric_summary_v2.py +++ b/plugins/module_utils/fabric/fabric_summary_v2.py @@ -267,8 +267,10 @@ def refresh(self) -> None: self.log.debug(msg) self.results.response_current = self._rest_send.response_current - # self.results.add_response(self._rest_send.response_current) self.results.result_current = self._rest_send.result_current + # TODO: We are leaving these commented out for now. We need to look into how + # these are used downstream (e.g. in fabric_delete, fabric_update, etc.) + # self.results.add_response(self._rest_send.response_current) # self.results.add_result(self._rest_send.result_current) self.results.register_task_result() From c05dcf585e33431e61653159ae4217f25c2543f2 Mon Sep 17 00:00:00 2001 From: Allen Robel Date: Mon, 24 Nov 2025 10:37:27 -1000 Subject: [PATCH 05/24] Remove redundant empty data check in all_data property. Address below Copilot review comment. [nitpick] Remove redundant empty data check in all_data property. After a successful call to refresh(), self.data cannot be empty ({}) because _verify_controller_response() at line 212 would have raised a ControllerResponseError. The verify_refresh_has_been_called() call at line 318 already ensures refresh() completed successfully. This additional check is unnecessary and inconsistent with other properties like border_gateway_count, device_count, etc. Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- plugins/module_utils/fabric/fabric_summary_v2.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/plugins/module_utils/fabric/fabric_summary_v2.py b/plugins/module_utils/fabric/fabric_summary_v2.py index 78656e186..a4822c549 100644 --- a/plugins/module_utils/fabric/fabric_summary_v2.py +++ b/plugins/module_utils/fabric/fabric_summary_v2.py @@ -320,12 +320,6 @@ def all_data(self) -> dict[str, dict[str, Any]]: self.verify_refresh_has_been_called(method_name) except ValueError as error: raise ValueError(error) from error - if self.data == {}: - msg = f"{self.class_name}.{method_name}: " - msg += "self.data is empty. Unable to return fabric summary data. " - msg += f"Ensure {self.class_name}.refresh() has been called successfully." - self.log.error(msg) - raise ValueError(msg) return self.data @property From 898638a18bd18ead64ef698e841a8e7998ee09ae Mon Sep 17 00:00:00 2001 From: Allen Robel Date: Mon, 24 Nov 2025 10:39:35 -1000 Subject: [PATCH 06/24] Remove rest_send getter validation Addressing the below Copilot review comment. [nitpick] The rest_send property getter validation prevents accessing the property before calling the setter, which is overly restrictive. Internal methods like _set_fabric_summary_endpoint() (line 176) use self.rest_send.path, which would fail this check. While refresh() validates params at line 245 before calling _set_fabric_summary_endpoint(), this getter-level validation adds unnecessary coupling and prevents legitimate use cases like inspecting the rest_send object. Consider removing lines 457-460 from the getter, as the setter and refresh() method already provide adequate validation. Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- plugins/module_utils/fabric/fabric_summary_v2.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/plugins/module_utils/fabric/fabric_summary_v2.py b/plugins/module_utils/fabric/fabric_summary_v2.py index a4822c549..ea3e4f6a5 100644 --- a/plugins/module_utils/fabric/fabric_summary_v2.py +++ b/plugins/module_utils/fabric/fabric_summary_v2.py @@ -450,10 +450,6 @@ def rest_send(self) -> RestSend: Set an instance of the RestSend class. """ method_name: str = inspect.stack()[0][3] - if not self._rest_send.params: - msg = f"{self.class_name}.{method_name}: " - msg += "RestSend.params must be set before accessing." - raise ValueError(msg) return self._rest_send @rest_send.setter From f9cce22eb00dfe922cdb6d894a551ee177b21625 Mon Sep 17 00:00:00 2001 From: Allen Robel Date: Wed, 26 Nov 2025 07:30:15 -1000 Subject: [PATCH 07/24] dcnm_fabric: black 160 character line length There are no functional changes in this PR. 1. Lint with black after change of max line length to 160. --- plugins/module_utils/fabric/config_save.py | 3 +- plugins/module_utils/fabric/config_save_v2.py | 3 +- plugins/module_utils/fabric/template_get.py | 3 +- .../module_utils/fabric/template_get_all.py | 3 +- plugins/module_utils/fabric_group/create.py | 2 +- .../fabric_group/fabric_group_details.py | 4 +-- .../fabric_group/fabric_groups.py | 2 +- plugins/module_utils/fabric_group/query.py | 2 +- .../dcnm_fabric/test_fabric_config_save.py | 35 ++++++++---------- .../test_fabric_details_by_name_v2.py | 21 +++++------ .../test_fabric_details_by_nv_pair_v2.py | 36 +++++++------------ .../dcnm_fabric/test_fabric_details_v2.py | 33 ++++++----------- .../dcnm/dcnm_fabric/test_fabric_types.py | 11 ++---- .../dcnm/dcnm_fabric/test_param_info.py | 6 ++-- .../modules/dcnm/dcnm_fabric/test_ruleset.py | 15 ++++---- .../dcnm/dcnm_fabric/test_template_get.py | 19 +++++----- .../dcnm/dcnm_fabric/test_template_get_all.py | 24 ++++++------- .../test_verify_playbook_params.py | 35 +++++++----------- 18 files changed, 97 insertions(+), 160 deletions(-) diff --git a/plugins/module_utils/fabric/config_save.py b/plugins/module_utils/fabric/config_save.py index 7de137c3d..33d94312e 100644 --- a/plugins/module_utils/fabric/config_save.py +++ b/plugins/module_utils/fabric/config_save.py @@ -21,8 +21,7 @@ import inspect import logging -from ..common.api.v1.lan_fabric.rest.control.fabrics.fabrics import \ - EpFabricConfigSave +from ..common.api.v1.lan_fabric.rest.control.fabrics.fabrics import EpFabricConfigSave from ..common.conversion import ConversionUtils from ..common.properties import Properties diff --git a/plugins/module_utils/fabric/config_save_v2.py b/plugins/module_utils/fabric/config_save_v2.py index 1d39e8e95..c7faa50b5 100644 --- a/plugins/module_utils/fabric/config_save_v2.py +++ b/plugins/module_utils/fabric/config_save_v2.py @@ -23,8 +23,7 @@ import inspect import logging -from ..common.api.v1.lan_fabric.rest.control.fabrics.fabrics import \ - EpFabricConfigSave +from ..common.api.v1.lan_fabric.rest.control.fabrics.fabrics import EpFabricConfigSave from ..common.conversion import ConversionUtils from ..common.rest_send_v2 import RestSend from ..common.results_v2 import Results diff --git a/plugins/module_utils/fabric/template_get.py b/plugins/module_utils/fabric/template_get.py index fa3afb52d..67b03eaee 100644 --- a/plugins/module_utils/fabric/template_get.py +++ b/plugins/module_utils/fabric/template_get.py @@ -22,8 +22,7 @@ import inspect import logging -from ..common.api.v1.configtemplate.rest.config.templates.templates import \ - EpTemplate +from ..common.api.v1.configtemplate.rest.config.templates.templates import EpTemplate from ..common.exceptions import ControllerResponseError from ..common.properties import Properties diff --git a/plugins/module_utils/fabric/template_get_all.py b/plugins/module_utils/fabric/template_get_all.py index dc742f7c6..4d0392763 100644 --- a/plugins/module_utils/fabric/template_get_all.py +++ b/plugins/module_utils/fabric/template_get_all.py @@ -22,8 +22,7 @@ import inspect import logging -from ..common.api.v1.configtemplate.rest.config.templates.templates import \ - EpTemplates +from ..common.api.v1.configtemplate.rest.config.templates.templates import EpTemplates from ..common.exceptions import ControllerResponseError from ..common.properties import Properties diff --git a/plugins/module_utils/fabric_group/create.py b/plugins/module_utils/fabric_group/create.py index 5705d7a91..ed465f674 100644 --- a/plugins/module_utils/fabric_group/create.py +++ b/plugins/module_utils/fabric_group/create.py @@ -44,7 +44,7 @@ class FabricGroupCreate(FabricGroupCommon): ```python from ansible_collections.cisco.dcnm.plugins.module_utils.fabric_group.create import FabricGroupCreate - from ansible_collections.cisco.dcnm.plugins.module_utils.common.results import Results + from ansible_collections.cisco.dcnm.plugins.module_utils.common.results_v2 import Results payloads = [ { "FABRIC_NAME": "fabric1", "BGP_AS": 65000 }, { "FABRIC_NAME": "fabric2", "BGP_AS": 65001 } diff --git a/plugins/module_utils/fabric_group/fabric_group_details.py b/plugins/module_utils/fabric_group/fabric_group_details.py index 337fdb3f1..33b155088 100644 --- a/plugins/module_utils/fabric_group/fabric_group_details.py +++ b/plugins/module_utils/fabric_group/fabric_group_details.py @@ -53,7 +53,7 @@ class FabricGroupDetails: ```python 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.results_v2 import Results from ansible_collections.cisco.dcnm.plugins.module_utils.common.sender_dcnm import Sender params = {"check_mode": False, "state": "merged"} @@ -83,7 +83,7 @@ class FabricGroupDetails: ```python 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.results_v2 import Results from ansible_collections.cisco.dcnm.plugins.module_utils.common.sender_dcnm import Sender params = {"check_mode": False, "state": "merged"} diff --git a/plugins/module_utils/fabric_group/fabric_groups.py b/plugins/module_utils/fabric_group/fabric_groups.py index dee267acc..71d15ee16 100644 --- a/plugins/module_utils/fabric_group/fabric_groups.py +++ b/plugins/module_utils/fabric_group/fabric_groups.py @@ -498,7 +498,7 @@ class FabricGroups: ```python 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.results_v2 import Results from ansible_collections.cisco.dcnm.plugins.module_utils.common.sender_dcnm import Sender params = {"check_mode": False, "state": "merged"} diff --git a/plugins/module_utils/fabric_group/query.py b/plugins/module_utils/fabric_group/query.py index 2e3067c96..2f563088f 100644 --- a/plugins/module_utils/fabric_group/query.py +++ b/plugins/module_utils/fabric_group/query.py @@ -47,7 +47,7 @@ class FabricGroupQuery: ```python from ansible_collections.cisco.dcnm.plugins.module_utils.fabric_group.query import FabricGroupQuery - from ansible_collections.cisco.dcnm.plugins.module_utils.common.results import Results + from ansible_collections.cisco.dcnm.plugins.module_utils.common.results_v2 import Results from ansible_collections.cisco.dcnm.plugins.module_utils.common.rest_send_v2 import RestSend params = {"state": "query", "check_mode": False} 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 21ee30a04..a8888aac0 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,19 +32,18 @@ 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 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 ansible_collections.cisco.dcnm.tests.unit.modules.dcnm.dcnm_fabric.utils import ( - MockAnsibleModule, does_not_raise, fabric_config_save_fixture, params, - responses_ep_fabric_config_save) + MockAnsibleModule, + does_not_raise, + fabric_config_save_fixture, + params, + responses_ep_fabric_config_save, +) def test_fabric_config_save_00010(fabric_config_save) -> None: @@ -99,9 +98,7 @@ def test_fabric_config_save_00010(fabric_config_save) -> None: ("My*Fabric", pytest.raises(ValueError, match=MATCH_00020b), True), ], ) -def test_fabric_config_save_00020( - fabric_config_save, fabric_name, expected, does_raise -) -> None: +def test_fabric_config_save_00020(fabric_config_save, fabric_name, expected, does_raise) -> None: """ Classes and Methods - FabricConfigSave @@ -151,9 +148,7 @@ def test_fabric_config_save_00020( ({10}, True, pytest.raises(TypeError, match=MATCH_00030)), ], ) -def test_fabric_config_save_00030( - fabric_config_save, value, does_raise, expected -) -> None: +def test_fabric_config_save_00030(fabric_config_save, value, does_raise, expected) -> None: """ Classes and Methods - FabricConfigSave @@ -191,9 +186,7 @@ def test_fabric_config_save_00030( ({10}, True, pytest.raises(TypeError, match=MATCH_00040)), ], ) -def test_fabric_config_save_00040( - fabric_config_save, value, does_raise, expected -) -> None: +def test_fabric_config_save_00040(fabric_config_save, value, does_raise, expected) -> None: """ Classes and Methods - FabricConfigSave 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 976a2313f..e3d105418 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 @@ -33,19 +33,16 @@ 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 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 ansible_collections.cisco.dcnm.tests.unit.modules.dcnm.dcnm_fabric.utils import ( - does_not_raise, fabric_details_by_name_v2_fixture, - responses_fabric_details_by_name_v2) + does_not_raise, + fabric_details_by_name_v2_fixture, + responses_fabric_details_by_name_v2, +) PARAMS = {"state": "query", "check_mode": False} 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 fb92b8d36..a2658b305 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 @@ -33,19 +33,16 @@ 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 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 ansible_collections.cisco.dcnm.tests.unit.modules.dcnm.dcnm_fabric.utils import ( - does_not_raise, fabric_details_by_nv_pair_v2_fixture, - responses_fabric_details_by_nv_pair_v2) + does_not_raise, + fabric_details_by_nv_pair_v2_fixture, + responses_fabric_details_by_nv_pair_v2, +) PARAMS = {"state": "query", "check_mode": False} @@ -155,18 +152,9 @@ def responses(): assert False in instance.results.changed assert True not in instance.results.changed - assert ( - instance.filtered_data.get("f1", {}).get("nvPairs", {}).get("FEATURE_PTP", None) - == "false" - ) - assert ( - instance.filtered_data.get("f2", {}).get("nvPairs", {}).get("FEATURE_PTP", None) - == "false" - ) - assert ( - instance.filtered_data.get("f3", {}).get("nvPairs", {}).get("FEATURE_PTP", None) - is None - ) + assert instance.filtered_data.get("f1", {}).get("nvPairs", {}).get("FEATURE_PTP", None) == "false" + assert instance.filtered_data.get("f2", {}).get("nvPairs", {}).get("FEATURE_PTP", None) == "false" + assert instance.filtered_data.get("f3", {}).get("nvPairs", {}).get("FEATURE_PTP", None) is None def test_fabric_details_by_nv_pair_v2_00210(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 d08dfb34e..5017ba37e 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 @@ -32,22 +32,14 @@ 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.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 ansible_collections.cisco.dcnm.tests.unit.modules.dcnm.dcnm_fabric.utils import ( - does_not_raise, fabric_details_v2_fixture, responses_fabric_details_v2) +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.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 ansible_collections.cisco.dcnm.tests.unit.modules.dcnm.dcnm_fabric.utils import does_not_raise, fabric_details_v2_fixture, responses_fabric_details_v2 def test_fabric_details_v2_00000(fabric_details_v2) -> None: @@ -342,10 +334,7 @@ def responses(): assert False not in instance.results.failed assert False in instance.results.changed assert True not in instance.results.changed - assert ( - instance.all_data.get("VXLAN_Fabric", {}).get("nvPairs", {}).get("FABRIC_NAME") - == "VXLAN_Fabric" - ) + assert instance.all_data.get("VXLAN_Fabric", {}).get("nvPairs", {}).get("FABRIC_NAME") == "VXLAN_Fabric" def test_fabric_details_v2_00140(fabric_details_v2, monkeypatch) -> None: @@ -594,9 +583,7 @@ def test_fabric_details_v2_00400(fabric_details_v2) -> None: (RestSend({"state": "merged", "check_mode": False}), False, does_not_raise()), ], ) -def test_fabric_details_v2_00500( - fabric_details_v2, param, does_raise, expected -) -> None: +def test_fabric_details_v2_00500(fabric_details_v2, param, does_raise, expected) -> None: """ ### Classes and Methods - FabricDetails() diff --git a/tests/unit/modules/dcnm/dcnm_fabric/test_fabric_types.py b/tests/unit/modules/dcnm/dcnm_fabric/test_fabric_types.py index 8b2f951f6..1a16a1ca7 100644 --- a/tests/unit/modules/dcnm/dcnm_fabric/test_fabric_types.py +++ b/tests/unit/modules/dcnm/dcnm_fabric/test_fabric_types.py @@ -28,8 +28,7 @@ __author__ = "Allen Robel" import pytest -from ansible_collections.cisco.dcnm.tests.unit.modules.dcnm.dcnm_fabric.utils import ( - does_not_raise, fabric_types_fixture) +from ansible_collections.cisco.dcnm.tests.unit.modules.dcnm.dcnm_fabric.utils import does_not_raise, fabric_types_fixture def test_fabric_types_00010(fabric_types) -> None: @@ -78,9 +77,7 @@ def test_fabric_types_00010(fabric_types) -> None: ), ], ) -def test_fabric_types_00020( - fabric_types, fabric_type, template_name, does_raise, expected -) -> None: +def test_fabric_types_00020(fabric_types, fabric_type, template_name, does_raise, expected) -> None: """ Classes and Methods - FabricTypes @@ -147,9 +144,7 @@ def test_fabric_types_00030(fabric_types) -> None: ), ], ) -def test_fabric_types_00040( - fabric_types, fabric_type, parameters, does_raise, expected -) -> None: +def test_fabric_types_00040(fabric_types, fabric_type, parameters, does_raise, expected) -> None: """ Classes and Methods - FabricTypes diff --git a/tests/unit/modules/dcnm/dcnm_fabric/test_param_info.py b/tests/unit/modules/dcnm/dcnm_fabric/test_param_info.py index fd7833a43..b689616f3 100644 --- a/tests/unit/modules/dcnm/dcnm_fabric/test_param_info.py +++ b/tests/unit/modules/dcnm/dcnm_fabric/test_param_info.py @@ -33,10 +33,8 @@ import inspect import pytest -from ansible_collections.cisco.dcnm.plugins.module_utils.fabric.param_info import \ - ParamInfo -from ansible_collections.cisco.dcnm.tests.unit.modules.dcnm.dcnm_fabric.utils import ( - does_not_raise, templates_param_info) +from ansible_collections.cisco.dcnm.plugins.module_utils.fabric.param_info import ParamInfo +from ansible_collections.cisco.dcnm.tests.unit.modules.dcnm.dcnm_fabric.utils import does_not_raise, templates_param_info def test_param_info_00010() -> None: diff --git a/tests/unit/modules/dcnm/dcnm_fabric/test_ruleset.py b/tests/unit/modules/dcnm/dcnm_fabric/test_ruleset.py index adcbec42c..c813cd825 100644 --- a/tests/unit/modules/dcnm/dcnm_fabric/test_ruleset.py +++ b/tests/unit/modules/dcnm/dcnm_fabric/test_ruleset.py @@ -33,12 +33,9 @@ import re import pytest -from ansible_collections.cisco.dcnm.plugins.module_utils.common.conversion import \ - ConversionUtils -from ansible_collections.cisco.dcnm.plugins.module_utils.fabric.ruleset import \ - RuleSet -from ansible_collections.cisco.dcnm.tests.unit.modules.dcnm.dcnm_fabric.utils import ( - does_not_raise, templates_ruleset) +from ansible_collections.cisco.dcnm.plugins.module_utils.common.conversion import ConversionUtils +from ansible_collections.cisco.dcnm.plugins.module_utils.fabric.ruleset import RuleSet +from ansible_collections.cisco.dcnm.tests.unit.modules.dcnm.dcnm_fabric.utils import does_not_raise, templates_ruleset def test_ruleset_00010() -> None: @@ -118,9 +115,9 @@ def test_ruleset_00030() -> None: instance = RuleSet() instance.template = templates_ruleset(key) instance.refresh() - assert instance.ruleset.get("ADVERTISE_PIP_ON_BORDER", {}).get("terms", {}).get( - "na" - ) == [{"operator": "!=", "parameter": "ADVERTISE_PIP_BGP", "value": True}] + assert instance.ruleset.get("ADVERTISE_PIP_ON_BORDER", {}).get("terms", {}).get("na") == [ + {"operator": "!=", "parameter": "ADVERTISE_PIP_BGP", "value": True} + ] @pytest.mark.parametrize("bad_value", [("BAD_STRING"), (None), (10), ([10]), ({10})]) 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 8efa11e95..0b933886d 100644 --- a/tests/unit/modules/dcnm/dcnm_fabric/test_template_get.py +++ b/tests/unit/modules/dcnm/dcnm_fabric/test_template_get.py @@ -32,17 +32,16 @@ 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.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.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 ( - MockAnsibleModule, does_not_raise, responses_template_get, - template_get_fixture) + MockAnsibleModule, + does_not_raise, + responses_template_get, + template_get_fixture, +) def test_template_get_00000(template_get) -> None: 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 e97c99558..bbaa4f224 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,17 +32,16 @@ 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.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.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 ( - MockAnsibleModule, does_not_raise, responses_template_get_all, - template_get_all_fixture) + MockAnsibleModule, + does_not_raise, + responses_template_get_all, + template_get_all_fixture, +) def test_template_get_all_00000(template_get_all) -> None: @@ -304,10 +303,7 @@ def mock_dcnm_send(*args, **kwargs): instance.refresh() assert isinstance(instance.templates, dict) assert instance.templates.get("Easy_Fabric", {}).get("name") == "Easy_Fabric" - assert ( - instance.templates.get("Easy_Fabric_Classic", {}).get("templateType") - == "FABRIC" - ) + assert instance.templates.get("Easy_Fabric_Classic", {}).get("templateType") == "FABRIC" assert len(instance.response) == 1 assert len(instance.result) == 1 assert instance.result_current.get("success", None) is True diff --git a/tests/unit/modules/dcnm/dcnm_fabric/test_verify_playbook_params.py b/tests/unit/modules/dcnm/dcnm_fabric/test_verify_playbook_params.py index be8b853e2..0d8d39b14 100644 --- a/tests/unit/modules/dcnm/dcnm_fabric/test_verify_playbook_params.py +++ b/tests/unit/modules/dcnm/dcnm_fabric/test_verify_playbook_params.py @@ -33,17 +33,16 @@ import inspect import pytest -from ansible_collections.cisco.dcnm.plugins.module_utils.common.conversion import \ - ConversionUtils -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.verify_playbook_params import \ - VerifyPlaybookParams +from ansible_collections.cisco.dcnm.plugins.module_utils.common.conversion import ConversionUtils +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.verify_playbook_params import VerifyPlaybookParams from ansible_collections.cisco.dcnm.tests.unit.modules.dcnm.dcnm_fabric.utils import ( - does_not_raise, nv_pairs_verify_playbook_params, - payloads_verify_playbook_params, templates_verify_playbook_params) + does_not_raise, + nv_pairs_verify_playbook_params, + payloads_verify_playbook_params, + templates_verify_playbook_params, +) def test_verify_playbook_params_00010() -> None: @@ -734,9 +733,7 @@ def mock_controller_param_is_valid(*args): with does_not_raise(): instance = VerifyPlaybookParams() - monkeypatch.setattr( - instance, "controller_param_is_valid", mock_controller_param_is_valid - ) + monkeypatch.setattr(instance, "controller_param_is_valid", mock_controller_param_is_valid) item = {"operator": "==", "parameter": "STP_ROOT_OPTION", "value": "rpvst+"} match = r"controller_param_is_valid: KeyError" with pytest.raises(KeyError, match=match): @@ -762,9 +759,7 @@ def mock_playbook_param_is_valid(*args): instance = VerifyPlaybookParams() instance.config_controller = None - monkeypatch.setattr( - instance, "playbook_param_is_valid", mock_playbook_param_is_valid - ) + monkeypatch.setattr(instance, "playbook_param_is_valid", mock_playbook_param_is_valid) item = {"operator": "==", "parameter": "STP_ROOT_OPTION", "value": "rpvst+"} match = r"playbook_param_is_valid: KeyError" with pytest.raises(KeyError, match=match): @@ -794,9 +789,7 @@ def mock_playbook_param_is_valid(*args): instance.config_controller = None monkeypatch.setattr(instance, "default_param_is_valid", mock_default_param_is_valid) - monkeypatch.setattr( - instance, "playbook_param_is_valid", mock_playbook_param_is_valid - ) + monkeypatch.setattr(instance, "playbook_param_is_valid", mock_playbook_param_is_valid) item = {"operator": "==", "parameter": "STP_ROOT_OPTION", "value": "rpvst+"} match = r"default_param_is_valid: KeyError" @@ -836,9 +829,7 @@ def mock_playbook_param_is_valid(*args): instance.config_controller = None monkeypatch.setattr(instance, "default_param_is_valid", mock_default_param_is_valid) - monkeypatch.setattr( - instance, "playbook_param_is_valid", mock_playbook_param_is_valid - ) + monkeypatch.setattr(instance, "playbook_param_is_valid", mock_playbook_param_is_valid) param_rule = { "terms": { From 86d2fe94c9f8983886fe4e987cbf4bb6109db89d Mon Sep 17 00:00:00 2001 From: Allen Robel Date: Wed, 26 Nov 2025 09:35:09 -1000 Subject: [PATCH 08/24] FabricDeleted: address tech-debt # Summary This commit includes the following changes to FabricDeleted: - Remove the need to pre-instantiate two classes - Use latest versions of all versioned classes - Removes constraint that fabric_names list be populated This commit includes the following changes to FabricDetails (v3) - Add fabric_names property (FabricDelete leverages this) - Add type-hints - Use proper Markdown for docstrings ## Functional changes 1. Set the following classes internally to FabricDeleted - FabricDetailsByName - FabricSummary Previously, the user had to instantiate these classes and pass them to FabricDeleted. 2. Use latest versions of the above classes - fabric_details_v3 - fabric_summary_v2 3. Use latest version of Resuts (results_v2) 4. Use latest version of FabricCommon (common_v2) 5 fabric_names.setter - do not require list to be populated We now handle empty-list later in the code flow. This allows easier handling of result logic. # Non-functional changes 1. Add type-hints everywhere 2. Update docstrings to use Markdown 3. Add local copies of RestSend and Results property getter/setters 4. Add module docstring 5. Add pylint directive to suppress __metaclass__ invalid-name --- plugins/module_utils/fabric/delete.py | 376 +++++++--- .../module_utils/fabric/fabric_details_v3.py | 695 +++++++++++------- .../dcnm/dcnm_fabric/test_fabric_delete.py | 202 ++--- 3 files changed, 737 insertions(+), 536 deletions(-) diff --git a/plugins/module_utils/fabric/delete.py b/plugins/module_utils/fabric/delete.py index 7d6e4102b..55e13634b 100644 --- a/plugins/module_utils/fabric/delete.py +++ b/plugins/module_utils/fabric/delete.py @@ -11,24 +11,32 @@ # 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. +""" +Delete fabrics. A fabric must be empty before it can be deleted. +""" from __future__ import absolute_import, division, print_function -__metaclass__ = type +__metaclass__ = type # pylint: disable=invalid-name __author__ = "Allen Robel" import copy import inspect import logging +from typing import Literal -from ..common.api.v1.lan_fabric.rest.control.fabrics.fabrics import \ - EpFabricDelete +from ..common.api.v1.lan_fabric.rest.control.fabrics.fabrics import EpFabricDelete from ..common.exceptions import ControllerResponseError +from ..common.operation_type import OperationType +from ..common.rest_send_v2 import RestSend + # Import Results() only for the case where the user has not set Results() # prior to calling commit(). In this case, we instantiate Results() # in _validate_commit_parameters() so that we can register the failure # in commit(). -from ..common.results import Results -from .common import FabricCommon +from ..common.results_v2 import Results +from .common_v2 import FabricCommon +from .fabric_details_v3 import FabricDetailsByName +from .fabric_summary_v2 import FabricSummary class FabricDelete(FabricCommon): @@ -39,10 +47,8 @@ class FabricDelete(FabricCommon): Usage: - from ansible_collections.cisco.dcnm.plugins.module_utils.fabric.delete import \ - FabricDelete - from ansible_collections.cisco.dcnm.plugins.module_utils.common.results import \ - Results + from ansible_collections.cisco.dcnm.plugins.module_utils.fabric.delete import FabricDelete + from ansible_collections.cisco.dcnm.plugins.module_utils.common.results_v2 import Results instance = FabricDelete(ansible_module) instance.fabric_names = ["FABRIC_1", "FABRIC_2"] @@ -66,18 +72,22 @@ class FabricDelete(FabricCommon): ansible_module.exit_json(**task.results.final_result) """ - def __init__(self): + def __init__(self) -> None: super().__init__() - self.class_name = self.__class__.__name__ - self.action = "fabric_delete" - - self.log = logging.getLogger(f"dcnm.{self.class_name}") + self.class_name: str = self.__class__.__name__ + self.action: str = "fabric_delete" - self._fabrics_to_delete = [] - self.ep_fabric_delete = EpFabricDelete() - self._fabric_names = None + self.log: logging.Logger = logging.getLogger(f"dcnm.{self.class_name}") + self._fabric_details_by_name: FabricDetailsByName = FabricDetailsByName() + self._fabric_summary: FabricSummary = FabricSummary() + self._results: Results = Results() + self._results.operation_type = OperationType.DELETE + self._rest_send: RestSend = RestSend(params={}) - self._cannot_delete_fabric_reason = None + self._cannot_delete_fabric_reason: str = "" + self._ep_fabric_delete: EpFabricDelete = EpFabricDelete() + self._fabric_names: list[str] = [] + self._fabrics_to_delete: list[str] = [] msg = "ENTERED FabricDelete()" self.log.debug(msg) @@ -89,98 +99,124 @@ def _get_fabrics_to_delete(self) -> None: - Raise ``ValueError`` if any fabric in ``fabric_names`` cannot be deleted. """ - self.fabric_details.refresh() + method_name: str = inspect.stack()[0][3] # pylint: disable=unused-variable + self._fabric_details_by_name.refresh() + + msg = f"{self.class_name}.{method_name}: " + msg += "self._fabric_details_by_name.fabric_names: " + msg += f"{self._fabric_details_by_name.fabric_names}" + self.log.debug(msg) + + msg = f"{self.class_name}.{method_name}: " + msg += f"self.fabric_names: {self.fabric_names}" + self.log.debug(msg) self._fabrics_to_delete = [] for fabric_name in self.fabric_names: - if fabric_name in self.fabric_details.all_data: + msg = f"{self.class_name}.{method_name}: " + msg += f"Checking if fabric {fabric_name} can be deleted." + self.log.debug(msg) + if fabric_name in self._fabric_details_by_name.fabric_names: try: - self._verify_fabric_can_be_deleted(fabric_name) + self._verify_fabric_can_be_deleted(fabric_name=fabric_name) except ValueError as error: raise ValueError(error) from error + msg = f"{self.class_name}.{method_name}: " + msg += f"Fabric {fabric_name} can be deleted. " + msg += "Appending to self._fabric_to_delete." + self.log.debug(msg) self._fabrics_to_delete.append(fabric_name) + msg = f"{self.class_name}.{method_name}: " + msg += f"self._fabrics_to_delete: {self._fabrics_to_delete}" + self.log.debug(msg) - def _verify_fabric_can_be_deleted(self, fabric_name): + def _verify_fabric_can_be_deleted(self, fabric_name: str) -> None: """ - raise ``ValueError`` if the fabric cannot be deleted - return otherwise + # Summary + + Verify that fabric_name can be deleted by checking that it is empty. + + ## Raises + + ### ValueError + + - fabric_name cannot be deleted """ - method_name = inspect.stack()[0][3] # pylint: disable=unused-variable - self.fabric_summary.fabric_name = fabric_name + method_name: str = inspect.stack()[0][3] # pylint: disable=unused-variable + self._fabric_summary.fabric_name = fabric_name try: - self.fabric_summary.refresh() + self._fabric_summary.refresh() except (ControllerResponseError, ValueError) as error: raise ValueError(error) from error - if self.fabric_summary.fabric_is_empty is True: + if self._fabric_summary.fabric_is_empty is True: + msg = f"{self.class_name}.{method_name}: " + msg += f"Fabric {fabric_name} is empty and can be deleted." + self.log.debug(msg) return msg = f"{self.class_name}.{method_name}: " msg += f"Fabric {fabric_name} cannot be deleted since it is not " msg += "empty. Remove all devices from the fabric and try again." + self.log.debug(msg) raise ValueError(msg) - def _validate_commit_parameters(self): - """ - - validate the parameters for commit - - raise ``ValueError`` if ``fabric_names`` is not set + def _validate_commit_parameters(self) -> None: """ - method_name = inspect.stack()[0][3] # pylint: disable=unused-variable + # Summary - 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) + Validate the parameters for commit - 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) + ## Raises - # 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) + ### ValueError - # 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() + - `fabric_names` is not set + """ + method_name: str = inspect.stack()[0][3] # pylint: disable=unused-variable + + if not self._rest_send.params: msg = f"{self.class_name}.{method_name}: " - msg += "results must be set prior to calling commit." + msg += "rest_send 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): + def commit(self) -> None: """ - - delete each of the fabrics in self.fabric_names - - raise ``ValueError`` if any commit parameters are invalid - """ - method_name = inspect.stack()[0][3] # pylint: disable=unused-variable + # Summary + + Delete each of the fabrics in self.fabric_names. + ## Raises + + ### ValueError + + - Any parameters required by commit() are invalid + """ try: self._validate_commit_parameters() except ValueError as error: - self.results.changed = False - self.results.failed = True - self.register_result(None) + self._results.add_changed(False) + self._results.add_failed(True) + self.register_result(fabric_name="") raise ValueError(error) from error - # pylint: disable=no-member - self.results.action = self.action - self.results.check_mode = self.rest_send.check_mode - self.results.state = self.rest_send.state - self.results.diff_current = {} + self._fabric_summary.rest_send = self._rest_send + self._fabric_summary.results = Results() + + self._fabric_details_by_name.rest_send = self._rest_send + self._fabric_details_by_name.results = Results() + + self._results.action = self.action + self._results.check_mode = self._rest_send.check_mode + self._results.state = self._rest_send.state + self._results.diff_current = {} try: self._get_fabrics_to_delete() except ValueError as error: - self.results.changed = False - self.results.failed = True - self.register_result(None) + self._results.add_changed(False) + self._results.add_failed(True) + self.register_result(fabric_name="") raise ValueError(error) from error msg = f"self._fabrics_to_delete: {self._fabrics_to_delete}" @@ -189,18 +225,16 @@ def commit(self): try: self._send_requests() except ValueError as error: - self.results.changed = False - self.results.failed = True - self.register_result(None) + self.register_result(fabric_name="") raise ValueError(error) from error return - self.results.changed = False - self.results.failed = False - self.results.result_current = {"success": True, "changed": False} + self._results.add_changed(False) + self._results.add_failed(False) + self._results.result_current = {"success": True, "changed": False} msg = "No fabrics to delete" - self.results.response_current = {"RETURN_CODE": 200, "MESSAGE": msg} - self.results.register_task_result() + self._results.response_current = {"RETURN_CODE": 200, "MESSAGE": msg} + self._results.register_task_result() def _send_requests(self): """ @@ -217,7 +251,6 @@ def _send_requests(self): timeout to 1 second and restore the original timeout after the requests are sent. """ - # pylint: disable=no-member self.rest_send.save_settings() self.rest_send.timeout = 1 @@ -225,22 +258,36 @@ def _send_requests(self): try: self._send_request(fabric_name) except ValueError as error: - self.results.changed = False - self.results.failed = True - self.register_result(fabric_name) + self.register_result(fabric_name="") raise ValueError(error) from error self.rest_send.restore_settings() - def _set_fabric_delete_endpoint(self, fabric_name): + def _set_fabric_delete_endpoint(self, fabric_name: str) -> None: + """ + # Summary + + Set the fabric delete endpoint parameters in RestSend. + + ## Raises + + ### ValueError + + - If fabric_name is invalid. + - If verb is not a string. + + ### TypeError + + - If verb is not a valid HTTP method. + """ 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 + self._ep_fabric_delete.fabric_name = fabric_name + 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): + + def _send_request(self, fabric_name: str) -> None: """ ### Summary Send a delete request to the controller and register the result. @@ -251,57 +298,64 @@ def _send_request(self, fabric_name): # pylint: disable=no-member try: self._set_fabric_delete_endpoint(fabric_name) - self.rest_send.commit() except (ValueError, TypeError) as error: raise ValueError(error) from error - self.register_result(fabric_name) + try: + self._rest_send.commit() + except (TypeError, ValueError) as error: + raise ValueError(error) from error + self.register_result(fabric_name=fabric_name) - def register_result(self, fabric_name): + def register_result(self, fabric_name: str) -> None: """ - Register the result of the fabric delete request - - If ``fabric_name`` is ``None``, set the result to indicate + - If `fabric_name` is "" (empty string), set the result to indicate no changes occurred and the request was not successful. - - If ``fabric_name`` is not ``None``, set the result to indicate + - If `fabric_name` is not "" (empty string), set the result to indicate the success or failure of the request. """ - # pylint: disable=no-member - self.results.action = self.action - if self.rest_send is not None: - self.results.check_mode = self.rest_send.check_mode - self.results.state = self.rest_send.state + method_name: str = inspect.stack()[0][3] # pylint: disable=unused-variable + self._results.action = self.action + if self._rest_send.params: + 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 or self.rest_send is None: - self.results.diff_current = {} - self.results.response_current = {} - self.results.result_current = {"success": False, "changed": False} - self.results.register_task_result() + self._results.check_mode = False + self._results.state = "unknown" + + if fabric_name == "" or not self._rest_send.params: + self._results.diff_current = {} + self._results.response_current = {} + self._results.result_current = {"success": False, "changed": False} + self._results.register_task_result() return - if self.rest_send.result_current.get("success", None) is True: - self.results.diff_current = {"FABRIC_NAME": fabric_name} + if self._rest_send.result_current.get("success", None) is True: + self._results.diff_current = {"FABRIC_NAME": fabric_name} # need this to match the else clause below since we # pass response_current (altered or not) to the results object - response_current = copy.deepcopy(self.rest_send.response_current) + response_current = copy.deepcopy(self._rest_send.response_current) else: - self.results.diff_current = {} + self._results.diff_current = {} # Improve the controller's error message to include the fabric_name - response_current = copy.deepcopy(self.rest_send.response_current) + response_current = copy.deepcopy(self._rest_send.response_current) if "DATA" in response_current: if "Failed to delete the fabric." in response_current["DATA"]: msg = f"Failed to delete fabric {fabric_name}." response_current["DATA"] = msg - self.results.response_current = response_current - self.results.result_current = self.rest_send.result_current + self._results.response_current = response_current + self._results.result_current = self._rest_send.result_current + + msg = f"{self.class_name}.{method_name}: " + msg += f"self._results.result_current: {self._results.result_current}" + self.log.debug(msg) - self.results.register_task_result() + self._results.register_task_result() @property - def fabric_names(self): + def fabric_names(self) -> list[str]: """ - getter: return list of fabric_names - setter: set list of fabric_names @@ -310,19 +364,14 @@ def fabric_names(self): return self._fabric_names @fabric_names.setter - def fabric_names(self, value): - method_name = inspect.stack()[0][3] + def fabric_names(self, value: list[str]) -> None: + method_name: str = inspect.stack()[0][3] if not isinstance(value, list): msg = f"{self.class_name}.{method_name}: " msg += "fabric_names must be a list. " msg += f"got {type(value).__name__} for " msg += f"value {value}" raise ValueError(msg) - if len(value) == 0: - msg = f"{self.class_name}.{method_name}: " - msg += "fabric_names must be a list of at least one string. " - msg += f"got {value}." - raise ValueError(msg) for item in value: if not isinstance(item, str): msg = f"{self.class_name}.{method_name}: " @@ -331,3 +380,86 @@ def fabric_names(self, value): msg += f"value {item}" raise ValueError(msg) self._fabric_names = value + + @property + def rest_send(self) -> RestSend: + """ + # Summary + + An instance of the RestSend class. + + ## Raises + + - setter: `TypeError` if the value is not an instance of RestSend. + - setter: `ValueError` if RestSend.params is not set. + + ## getter + + Return an instance of the RestSend class. + + ## setter + + Set an instance of the RestSend class. + """ + return self._rest_send + + @rest_send.setter + def rest_send(self, value: RestSend) -> None: + method_name: str = inspect.stack()[0][3] + _class_have: str = "" + _class_need: Literal["RestSend"] = "RestSend" + msg = f"{self.class_name}.{method_name}: " + msg += f"value must be an instance of {_class_need}. " + msg += f"Got value {value} of type {type(value).__name__}." + try: + _class_have = value.class_name + except AttributeError as error: + msg += f" Error detail: {error}." + raise TypeError(msg) from error + if _class_have != _class_need: + raise TypeError(msg) + if not value.params: + msg = f"{self.class_name}.{method_name}: " + msg += "RestSend.params must be set." + raise ValueError(msg) + self._rest_send = value + + @property + def results(self) -> Results: + """ + # Summary + + An instance of the Results class. + + ## Raises + + - setter: `TypeError` if the value is not an instance of Results. + + ## getter + + Return an instance of the Results class. + + ## setter + + Set an instance of the Results class. + """ + return self._results + + @results.setter + def results(self, value: Results) -> None: + method_name: str = inspect.stack()[0][3] + _class_have: str = "" + _class_need: Literal["Results"] = "Results" + msg = f"{self.class_name}.{method_name}: " + msg += f"value must be an instance of {_class_need}. " + msg += f"Got value {value} of type {type(value).__name__}." + try: + _class_have = value.class_name + except AttributeError as error: + msg += f" Error detail: {error}." + raise TypeError(msg) from error + if _class_have != _class_need: + raise TypeError(msg) + self._results = value + self._results.action = self.action + self._results.operation_type = OperationType.DELETE diff --git a/plugins/module_utils/fabric/fabric_details_v3.py b/plugins/module_utils/fabric/fabric_details_v3.py index 9f1c6d827..8d42255a5 100644 --- a/plugins/module_utils/fabric/fabric_details_v3.py +++ b/plugins/module_utils/fabric/fabric_details_v3.py @@ -31,21 +31,23 @@ import copy import inspect import logging +from typing import Any, Literal from ..common.api.v1.lan_fabric.rest.control.fabrics.fabrics import EpFabrics from ..common.conversion import ConversionUtils +from ..common.operation_type import OperationType +from ..common.response_handler import ResponseHandler from ..common.rest_send_v2 import RestSend from ..common.results_v2 import Results class FabricDetails: """ - ### Summary + # Summary + Parent class for *FabricDetails() subclasses. - See subclass docstrings for details. - ### Raises - None + See subclass docstrings for details. """ def __init__(self) -> None: @@ -62,31 +64,36 @@ def __init__(self) -> None: self.ep_fabrics: EpFabrics = EpFabrics() self._refreshed: bool = False - self.rest_send: RestSend = None # type: ignore[assignment] - self.results: Results = None # type: ignore[assignment] + self._rest_send: RestSend = RestSend({}) + self._rest_send.response_handler = ResponseHandler() + self._results: Results = Results() + self._results.action = self.action + self._results.operation_type = OperationType.QUERY def register_result(self) -> None: """ - ### Summary - Update the results object with the current state of the fabric - details and register the result. + # Summary + + Update the results object with the current state of the fabric details and register the result. - ### Raises - - ``ValueError``if: - - ``Results()`` raises ``TypeError`` + ## Raises + + ### ValueError + + - `Results()` raises `TypeError` """ - method_name = inspect.stack()[0][3] + method_name: str = inspect.stack()[0][3] try: - self.results.action = self.action - self.results.response_current = self.rest_send.response_current - self.results.result_current = self.rest_send.result_current - if self.results.response_current.get("RETURN_CODE") == 200: - self.results.add_failed(False) + self._results.action = self.action + self._results.response_current = self._rest_send.response_current + self._results.result_current = self._rest_send.result_current + if self._results.response_current.get("RETURN_CODE") == 200: + self._results.add_failed(False) else: - self.results.add_failed(True) + self._results.add_failed(True) # FabricDetails never changes the controller state - self.results.add_changed(False) - self.results.register_task_result() + self._results.add_changed(False) + self._results.register_task_result() except TypeError as error: msg = f"{self.class_name}.{method_name}: " msg += "Failed to register result. " @@ -95,43 +102,42 @@ def register_result(self) -> None: def validate_refresh_parameters(self) -> None: """ - ### Summary + # Summary + Validate that mandatory parameters are set before calling refresh(). - ### Raises - - ``ValueError``if: - - ``rest_send`` is not set. - - ``results`` is not set. + ## Raises + + ### ValueError + + - `rest_send` is not set. """ - method_name = inspect.stack()[0][3] - if self.rest_send is None: + method_name: str = inspect.stack()[0][3] + if not self._rest_send.params: 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 the fabric details from the controller and - populate self.data with the results. + # Summary + + Refresh the fabric details from the controller and populate self.data with the results. - ### Raises - - ``ValueError`` if: - - ``validate_refresh_parameters()`` raises ``ValueError``. - - ``RestSend`` raises ``TypeError`` or ``ValueError``. - - ``register_result()`` raises ``ValueError``. + ## Raises - ### Notes - - ``self.data`` is a dictionary of fabric details, keyed on - fabric name. + ### ValueError + + - `validate_refresh_parameters()` raises `ValueError`. + - `RestSend`` raises `TypeError` or `ValueError`. + - `register_result()` raises `ValueError`. + + ## Notes + + - `self.data` is a dictionary of fabric details, keyed on fabric name. """ - method_name = inspect.stack()[0][3] # pylint: disable=unused-variable + method_name: str = inspect.stack()[0][3] # pylint: disable=unused-variable try: self.validate_refresh_parameters() @@ -139,8 +145,8 @@ def refresh_super(self) -> None: raise ValueError(error) from error try: - self.rest_send.path = self.ep_fabrics.path - self.rest_send.verb = self.ep_fabrics.verb + 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. @@ -148,25 +154,25 @@ def refresh_super(self) -> None: # 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() + 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 = {} - if self.rest_send is None: + if self._rest_send is None: # We should never hit this. return - if self.rest_send.response_current is None: + if self._rest_send.response_current is None: # We should never hit this. return - if self.rest_send.response_current["DATA"] is None: + if self._rest_send.response_current["DATA"] is None: # The DATA key should always be present. We should never hit this. return - for item in self.rest_send.response_current["DATA"]: + for item in self._rest_send.response_current["DATA"]: fabric_name = item.get("nvPairs", {}).get("FABRIC_NAME", None) if fabric_name is None: return @@ -177,30 +183,34 @@ def refresh_super(self) -> None: except ValueError as error: raise ValueError(error) from error - msg = f"{self.class_name}.{method_name}: calling self.rest_send.commit() DONE" + msg = f"{self.class_name}.{method_name}: calling self._rest_send.commit() DONE" self.log.debug(msg) - def _get(self, item): + def _get(self, item: str) -> Any: """ - ### Summary + # Summary + overridden in subclasses """ - def _get_nv_pair(self, item): + def _get_nv_pair(self, item: str) -> Any: """ - ### Summary + # Summary + overridden in subclasses """ @property def all_data(self) -> dict: """ - ### Summary + # Summary + Return all fabric details from the controller (i.e. self.data) - ``refresh`` must be called before accessing this property. + `refresh` must be called before accessing this property. + + ## Raises - ### Raises None """ return self.data @@ -208,21 +218,21 @@ def all_data(self) -> dict: @property def asn(self) -> str: """ - ### Summary - Return the BGP asn of the fabric specified with filter, if it exists. - Return "" (empty string) otherwise. + # Summary + + - Return the BGP asn of the fabric specified with filter, if it exists. + - Return "" (empty string) otherwise. This is an alias of bgp_as. - ### Raises + ## Raises + None - ### Type - string + ## Returns - ### Returns - - e.g. "65000" - - "" (empty string) if BGP_AS is not set + - e.g. "65000" + - "" (empty string) if BGP_AS is not set """ try: return self._get_nv_pair("BGP_AS") or "" @@ -234,19 +244,21 @@ def asn(self) -> str: @property def bgp_as(self) -> str: """ - ### Summary - Return ``nvPairs.BGP_AS`` of the fabric specified with filter, if it exists. - Return "" (empty string) otherwise + # Summary - ### Raises - None + - Return `nvPairs.BGP_AS` of the fabric specified with filter, if it exists. + - Return "" (empty string) otherwise + + ## Raises + + ### ValueError + + - `_get_nv_pair` raises ValueError - ### Type - string + ## Returns - ### Returns - - e.g. "65000" - - "" (empty string) if BGP_AS is not set + - e.g. "65000" + - "" (empty string) if BGP_AS is not set """ try: return self._get_nv_pair("BGP_AS") or "" @@ -258,16 +270,18 @@ def bgp_as(self) -> str: @property def deployment_freeze(self) -> bool: """ - ### Summary - The nvPairs.DEPLOYMENT_FREEZE of the fabric specified with filter. + # Summary - ### Raises - None + The `nvPairs.DEPLOYMENT_FREEZE` of the fabric specified with filter. + + ## Raises + + ### ValueError + + - `_get_nv_pair` raises ValueError - ### Type - boolean + ## Returns - ### Returns - False (if set to False, or not set) - True """ @@ -281,16 +295,18 @@ def deployment_freeze(self) -> bool: @property def enable_pbr(self) -> bool: """ - ### Summary + # Summary + The PBR enable state of the fabric specified with filter. - ### Raises - None + ## Raises + + ### ValueError - ### Type - boolean + - `_get_nv_pair` raises ValueError + + ## Returns - ### Returns - False (if set to False, or not set) - True """ @@ -304,16 +320,18 @@ def enable_pbr(self) -> bool: @property def fabric_id(self) -> str: """ - ### Summary - The ``fabricId`` value of the fabric specified with filter. + # Summary - ### Raises - None + The `fabricId` value of the fabric specified with filter. + + ## Raises - ### Type - string + ### ValueError + + - `_get` raises ValueError + + ## Returns - ### Returns - e.g. FABRIC-5 - "" if fabricId is not set """ @@ -325,18 +343,39 @@ def fabric_id(self) -> str: return "" @property - def fabric_type(self) -> str: + def fabric_names(self) -> list[str]: """ - ### Summary - The ``nvPairs.FABRIC_TYPE`` value of the fabric specified with filter. + # Summary + + Return a list of all fabric names on the controller. + + `refresh` must be called before accessing this property. + + ## Raises - ### Raises None - ### Type - string + ## Returns + + - e.g. ["Fabric1", "Fabric2", "Fabric3"] + """ + return list(self.data.keys()) + + @property + def fabric_type(self) -> str: + """ + # Summary + + The `nvPairs.FABRIC_TYPE` value of the fabric specified with filter. + + ## Raises + + ### ValueError + + - `_get_nv_pair` raises ValueError + + ## Returns - ### Returns - e.g. Switch_Fabric - "" (empty string) if FABRIC_TYPE is not set """ @@ -350,16 +389,18 @@ def fabric_type(self) -> str: @property def is_read_only(self) -> bool: """ - ### Summary - The ``nvPairs.IS_READ_ONLY`` value of the fabric specified with filter. + # Summary - ### Raises - None + The `nvPairs.IS_READ_ONLY` value of the fabric specified with filter. + + ## Raises + + ### ValueError - ### Type - boolean + - `_get_nv_pair` raises ValueError + + ## Returns - ### Returns - True - False (if set to False, or not set) """ @@ -373,17 +414,18 @@ def is_read_only(self) -> bool: @property def per_vrf_loopback_auto_provision(self) -> bool: """ - ### Summary - The ``nvPairs.PER_VRF_LOOPBACK_AUTO_PROVISION`` value of the fabric - specified with filter. + # Summary - ### Raises - None + The `nvPairs.PER_VRF_LOOPBACK_AUTO_PROVISION` value of the fabric specified with filter. + + ## Raises - ### Type - boolean + ### ValueError + + - `_get_nv_pair` raises ValueError + + ## Returns - ### Returns - True - False (if set to False, or not set) """ @@ -398,17 +440,18 @@ def per_vrf_loopback_auto_provision(self) -> bool: @property def replication_mode(self) -> str: """ - ### Summary - The ``nvPairs.REPLICATION_MODE`` value of the fabric specified - with filter. + # Summary - ### Raises - None + The `nvPairs.REPLICATION_MODE` value of the fabric specified with filter. + + ## Raises + + ### ValueError + + - `_get_nv_pair` raises ValueError - ### Type - string + ## Returns - ### Returns - Ingress - Multicast - "" (empty string) if REPLICATION_MODE is not set @@ -423,24 +466,117 @@ def replication_mode(self) -> str: @property def refreshed(self) -> bool: """ + # Summary + Indicates whether the fabric details have been refreshed. + + ## Raises + + None """ return self._refreshed + @property + def rest_send(self) -> RestSend: + """ + # Summary + + An instance of the RestSend class. + + ## Raises + + ### TypeError + + - setter: The value is not an instance of RestSend. + + ### ValueError + + - setter: RestSend.params is not set. + + ## getter + + Return an instance of the RestSend class. + + ## setter + + Set an instance of the RestSend class. + """ + return self._rest_send + + @rest_send.setter + def rest_send(self, value: RestSend) -> None: + method_name: str = inspect.stack()[0][3] + _class_have: str = "" + _class_need: Literal["RestSend"] = "RestSend" + msg = f"{self.class_name}.{method_name}: " + msg += f"value must be an instance of {_class_need}. " + msg += f"Got value {value} of type {type(value).__name__}." + try: + _class_have = value.class_name + except AttributeError as error: + msg += f" Error detail: {error}." + raise TypeError(msg) from error + if _class_have != _class_need: + raise TypeError(msg) + self._rest_send = value + + @property + def results(self) -> Results: + """ + # Summary + + An instance of the Results class. + + ## Raises + + ### TypeError + + - setter: The value is not an instance of Results. + + ## getter + + Return an instance of the Results class. + + ## setter + + Set an instance of the Results class. + """ + return self._results + + @results.setter + def results(self, value: Results) -> None: + method_name: str = inspect.stack()[0][3] + _class_have: str = "" + _class_need: Literal["Results"] = "Results" + msg = f"{self.class_name}.{method_name}: " + msg += f"value must be an instance of {_class_need}. " + msg += f"Got value {value} of type {type(value).__name__}." + try: + _class_have = value.class_name + except AttributeError as error: + msg += f" Error detail: {error}." + raise TypeError(msg) from error + if _class_have != _class_need: + raise TypeError(msg) + self._results = value + self._results.action = self.action + self._results.operation_type = OperationType.QUERY + @property def template_name(self) -> str: """ - ### Summary - The ``templateName`` value of the fabric specified - with filter. + # Summary - ### Raises - None + The `templateName` value of the fabric specified with filter. + + ## Raises + + ### ValueError + + - `_get` raises `ValueError` - ### Type - string + ## Returns - ### Returns - e.g. Easy_Fabric - Empty string, if templateName is not set """ @@ -454,25 +590,26 @@ def template_name(self) -> str: class FabricDetailsByName(FabricDetails): """ - ### Summary - Retrieve fabric details from the controller and provide - property accessors for the fabric attributes. - - ### Raises - - ``ValueError`` if: - - ``super.__init__()`` raises ``ValueError``. - - ``refresh_super()`` raises ``ValueError``. - - ``refresh()`` raises ``ValueError``. - - ``filter`` is not set before accessing properties. - - ``fabric_name`` does not exist on the controller. - - An attempt is made to access a key that does not exist - for the filtered fabric. + # Summary + + Retrieve fabric details from the controller and provide property accessors for the fabric attributes. + + ## Raises + + ### ValueError + + - `super.__init__()` raises `ValueError`. + - `refresh_super()` raises `ValueError`. + - `refresh()` raises `ValueError`. + - `filter` is not set before accessing properties. + - `fabric_name` does not exist on the controller. + - An attempt is made to access a key that does not exist for the filtered fabric. ### Usage ```python 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.results_v2 import Results from ansible_collections.cisco.dcnm.plugins.module_utils.common.sender_dcnm import Sender params = {"check_mode": False, "state": "merged"} @@ -502,7 +639,7 @@ class FabricDetailsByName(FabricDetails): ```python 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.results_v2 import Results from ansible_collections.cisco.dcnm.plugins.module_utils.common.sender_dcnm import Sender params = {"check_mode": False, "state": "merged"} @@ -520,11 +657,11 @@ class FabricDetailsByName(FabricDetails): all_fabrics = instance.all_data ``` - Where ``all_fabrics`` will be a dictionary of all fabrics on the + Where `all_fabrics` will be a dictionary of all fabrics on the controller, keyed on fabric name. """ - def __init__(self): + def __init__(self) -> None: self.class_name = self.__class__.__name__ super().__init__() @@ -532,16 +669,20 @@ def __init__(self): msg = "ENTERED FabricDetailsByName()" self.log.debug(msg) - self.data_subclass = {} + self.data_subclass: dict[str, Any] = {} self._filter: str = "" - def refresh(self): + def refresh(self) -> None: """ - ### Refresh fabric_name current details from the controller + # Summary + + Refresh fabric_name current details from the controller - ### Raises - - ``ValueError`` if: - - Mandatory properties are not set. + ## Raises + + ### ValueError + + - Mandatory properties are not set. """ try: self.refresh_super() @@ -553,19 +694,26 @@ def refresh(self): self.data_subclass = copy.deepcopy(self.data) self._refreshed = True - def _get(self, item): + def _get(self, item: str) -> Any: """ + # Summary + 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. + ## Raises + + ### ValueError + + - `self.filter` has not been set. + - `self.filter` (fabric_name) does not exist on the controller. + - item is not a valid property name for the fabric. + + ## See also - See also: ``_get_nv_pair()`` + - `_get_nv_pair()` """ - method_name = inspect.stack()[0][3] + method_name: str = inspect.stack()[0][3] msg = f"{self.class_name}.{method_name}: " msg += f"instance.filter {self.filter} " @@ -589,21 +737,25 @@ def _get(self, item): return self.conversion.make_none(self.conversion.make_boolean(self.data_subclass[self.filter].get(item))) - def _get_nv_pair(self, item): + def _get_nv_pair(self, item: str) -> Any: """ - ### Summary + # Summary + Retrieve the value of the nvPair item for fabric_name. - ### Raises - - ``ValueError`` if: - - ``self.filter`` has not been set. - - ``self.filter`` (fabric_name) does not exist on the controller. - - ``item`` is not a valid property name for the fabric. + ## Raises + + ### ValueError + + - `self.filter` has not been set. + - `self.filter` (fabric_name) does not exist on the controller. + - `item` is not a valid property name for the fabric. + + ## See also - ### See also - ``self._get()`` + `self._get()` """ - method_name = inspect.stack()[0][3] + method_name: str = inspect.stack()[0][3] msg = f"{self.class_name}.{method_name}: " msg += f"instance.filter {self.filter} " @@ -632,18 +784,22 @@ def _get_nv_pair(self, item): @property def filtered_data(self) -> dict: """ - ### Summary + # Summary + The DATA portion of the dictionary for the fabric specified with filter. - ### Raises - - ``ValueError`` if: - - ``self.filter`` has not been set. + ## Raises + + ### ValueError + + - `self.filter` has not been set. + + ## Returns - ### Returns - A dictionary of the fabric matching self.filter. - Empty dictionary, if the fabric does not exist on the controller. """ - method_name = inspect.stack()[0][3] + method_name: str = inspect.stack()[0][3] if not self.filter: msg = f"{self.class_name}.{method_name}: " msg += f"{self.class_name}.filter must be set before accessing " @@ -654,14 +810,17 @@ def filtered_data(self) -> dict: @property def filter(self) -> str: """ - ### Summary + # Summary + Set the fabric_name of the fabric to query. - ### Raises + ## Raises + None - ### NOTES - ``filter`` must be set before accessing this class's properties. + ## Notes + + - `filter` must be set before accessing this class's properties. """ return self._filter @@ -669,51 +828,33 @@ def filter(self) -> str: def filter(self, value: str) -> None: self._filter = value - @property - def rest_send(self) -> RestSend: - """ - An instance of the RestSend class. - """ - return self._rest_send - - @rest_send.setter - def rest_send(self, value: RestSend) -> None: - self._rest_send = value - - @property - def results(self) -> Results: - """ - An instance of the Results class. - """ - return self._results - - @results.setter - def results(self, value: Results) -> None: - self._results = value - class FabricDetailsByNvPair(FabricDetails): """ - ### Summary + # Summary + Retrieve fabric details from the controller filtered by nvPair key - and value. Calling ``refresh`` retrieves data for all fabrics. - After having called ``refresh`` data for a fabric accessed by setting - ``filter_key`` and ``filter_value`` which sets the ``filtered_data`` + and value. Calling `refresh` retrieves data for all fabrics. + After having called `refresh` data for a fabric accessed by setting + `filter_key` and `filter_value` which sets the `filtered_data` property to a dictionary containing fabrics on the controller - that match ``filter_key`` and ``filter_value``. + that match `filter_key` and `filter_value`. - ### Raises - - ``ValueError`` if: - - ``super.__init__()`` raises ``ValueError``. - - ``refresh_super()`` raises ``ValueError``. - - ``refresh()`` raises ``ValueError``. - - ``filter_key`` is not set before calling ``refresh()``. - - ``filter_value`` is not set before calling ``refresh()``. + ## Raises + + ## ValueError + + - `super.__init__()` raises `ValueError`. + - `refresh_super()` raises `ValueError`. + - `refresh()` raises `ValueError`. + - `filter_key` is not set before calling `refresh()`. + - `filter_value` is not set before calling `refresh()`. + + ## Usage - ### Usage ```python 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.results_v2 import Results from ansible_collections.cisco.dcnm.plugins.module_utils.common.sender_dcnm import Sender params = {"check_mode": False, "state": "query"} @@ -732,11 +873,11 @@ class FabricDetailsByNvPair(FabricDetails): ``` """ - def __init__(self): - self.class_name = self.__class__.__name__ + def __init__(self) -> None: + self.class_name: str = self.__class__.__name__ super().__init__() - self.log = logging.getLogger(f"dcnm.{self.class_name}") + self.log: logging.Logger = logging.getLogger(f"dcnm.{self.class_name}") msg = "ENTERED FabricDetailsByNvPair() " self.log.debug(msg) @@ -747,15 +888,18 @@ def __init__(self): def refresh(self) -> None: """ - ### Summary + # Summary + Refresh fabric_name current details from the controller. - ### Raises - - ``ValueError`` if: - - ``filter_key`` has not been set. - - ``filter_value`` has not been set. + ## Raises + + ### ValueError + + - `filter_key` has not been set. + - `filter_value` has not been set. """ - method_name = inspect.stack()[0][3] + method_name: str = inspect.stack()[0][3] if not self.filter_key: msg = f"{self.class_name}.{method_name}: " @@ -775,14 +919,14 @@ def refresh(self) -> None: msg += f"Error detail: {error}" raise ValueError(msg) from error - self._refreshed = True + self._refreshed: bool = True if len(self.data) == 0: - self.results.add_diff({}) - self.results.add_response(self.rest_send.response_current) - self.results.add_result(self.rest_send.result_current) - self.results.add_failed(True) - self.results.add_changed(False) + self._results.add_diff({}) + self._results.add_response(self._rest_send.response_current) + self._results.add_result(self._rest_send.result_current) + self._results.add_failed(True) + self._results.add_changed(False) return for item, value in self.data.items(): if value.get("nvPairs", {}).get(self.filter_key) == self.filter_value: @@ -791,32 +935,35 @@ def refresh(self) -> None: @property def filtered_data(self) -> dict: """ - ### Summary - A dictionary of the fabric(s) matching ``filter_key`` and - ``filter_value``. + # Summary + + A dictionary of the fabric(s) matching `filter_key` and `filter_value`. + + ## Raises - ### Raises None - ### Returns - - A ``dict`` of the fabric(s) matching ``filter_key`` and - ``filter_value``. - - An empty ``dict`` if the fabric does not exist on the controller. + ## Returns + + - A `dict` of the fabric(s) matching `filter_key` and `filter_value`. + - An empty `dict` if the fabric does not exist on the controller. """ return self.data_subclass @property def filter_key(self) -> str: """ - ### Summary - The ``nvPairs`` key on which to filter. + # Summary + + The `nvPairs` key on which to filter. + + ## Raises - ### Raises None - ### Notes - ``filter_key``should be an exact match for the key in the ``nvPairs`` - dictionary for the fabric. + ## Notes + + - `filter_key` should be an exact match for the key in the `nvPairs` dictionary for the fabric. """ return self._filter_key @@ -827,40 +974,20 @@ def filter_key(self, value: str) -> None: @property def filter_value(self) -> str: """ - ### Summary - The ``nvPairs`` value on which to filter. + # Summary + + The `nvPairs` value on which to filter. + + ## Raises - ### Raises None - ### Notes - ``filter_value`` should be an exact match for the value in the ``nvPairs`` - dictionary for the fabric. + ## Notes + + - `filter_value` should be an exact match for the value in the `nvPairs` dictionary for the fabric. """ return self._filter_value @filter_value.setter def filter_value(self, value: str) -> None: self._filter_value = value - - @property - def rest_send(self) -> RestSend: - """ - An instance of the RestSend class. - """ - return self._rest_send - - @rest_send.setter - def rest_send(self, value: RestSend) -> None: - self._rest_send = value - - @property - def results(self) -> Results: - """ - An instance of the Results class. - """ - return self._results - - @results.setter - def results(self, value: Results) -> None: - self._results = value 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 3330928cb..7a71422c1 100644 --- a/tests/unit/modules/dcnm/dcnm_fabric/test_fabric_delete.py +++ b/tests/unit/modules/dcnm/dcnm_fabric/test_fabric_delete.py @@ -32,24 +32,22 @@ 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.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.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_v2 import 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_v3 import FabricDetailsByName +from ansible_collections.cisco.dcnm.plugins.module_utils.fabric.fabric_summary_v2 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, - responses_fabric_delete, responses_fabric_details_by_name_v2, - responses_fabric_summary, rest_send_response_current) + MockAnsibleModule, + does_not_raise, + fabric_delete_fixture, + responses_fabric_delete, + responses_fabric_details_by_name_v2, + responses_fabric_summary_v2, + rest_send_response_current, +) PARAMS = {"state": "deleted", "check_mode": False} @@ -70,16 +68,15 @@ def test_fabric_delete_00000(fabric_delete) -> None: """ with does_not_raise(): instance = fabric_delete - instance.fabric_details = FabricDetailsByName() assert instance.action == "fabric_delete" - assert instance._cannot_delete_fabric_reason is None + assert instance._cannot_delete_fabric_reason == "" assert instance.class_name == "FabricDelete" - assert instance.fabric_names is None + assert instance.fabric_names == [] assert instance._fabrics_to_delete == [] - assert instance.path is None - assert instance.verb is None - assert instance.ep_fabric_delete.class_name == "EpFabricDelete" - assert instance.fabric_details.class_name == "FabricDetailsByName" + assert instance.path == "" + assert instance.verb == "" + assert instance._ep_fabric_delete.class_name == "EpFabricDelete" + assert instance._fabric_details_by_name.class_name == "FabricDetailsByName" def test_fabric_delete_00020(fabric_delete) -> None: @@ -178,7 +175,7 @@ def test_fabric_delete_00023(fabric_delete, fabric_name) -> None: ### Summary - - Verify EpFabricDelete() re-raises ``ValueError`` raised by + - Verify EpFabricDelete() re-raises `ValueError` raised by ConversionUtils() because ``fabric_name`` argument passed to _set_fabric_delete_endpoint() is an invalid string. """ @@ -203,25 +200,32 @@ def test_fabric_delete_00030(fabric_delete) -> None: - FabricDelete - __init__() - commit() - - _validate_commit_parameters() ### Summary - - Verify that ``ValueError`` is raised because fabric_names is not set - prior to calling commit() + Verify that an exception is NOT raised if fabric_names is not set + prior to calling commit() """ + + def responses(): + yield {"MESSAGE": "OK", "DATA": [], "RETURN_CODE": 200} + + sender = Sender() + sender.ansible_module = MockAnsibleModule() + sender.gen = ResponseGenerator(responses()) + rest_send = RestSend(PARAMS) + rest_send.sender = sender + rest_send.response_handler = ResponseHandler() with does_not_raise(): instance = fabric_delete - instance.fabric_details = FabricDetailsByName() - instance.rest_send = RestSend(PARAMS) + instance.rest_send = rest_send instance.results = Results() - - match = r"FabricDelete\._validate_commit_parameters: " - match += "fabric_names must be set prior to calling commit." - with pytest.raises(ValueError, match=match): instance.commit() + assert instance.results.result_current.get("changed", None) is False + assert instance.results.result_current.get("success", None) is True + def test_fabric_delete_00031(fabric_delete) -> None: """ @@ -236,7 +240,7 @@ def test_fabric_delete_00031(fabric_delete) -> None: ### Summary - - Verify that ``ValueError`` is raised because rest_send is not set + - Verify that `ValueError` is raised because rest_send is not set prior to calling commit() """ with does_not_raise(): @@ -251,33 +255,8 @@ def test_fabric_delete_00031(fabric_delete) -> None: instance.commit() -def test_fabric_delete_00032(fabric_delete) -> None: - """ - ### Classes and Methods - - - FabricCommon - - __init__() - - FabricDelete - - __init__() - - commit() - - _validate_commit_parameters() - - ### Summary - - - Verify that ``ValueError`` is raised because results is not set - prior to calling commit() - - """ - with does_not_raise(): - instance = fabric_delete - instance.fabric_details = FabricDetailsByName() - instance.rest_send = RestSend(PARAMS) - instance.fabric_names = ["MyFabric"] - - match = r"FabricDelete\._validate_commit_parameters: " - match += "results must be set prior to calling commit." - with pytest.raises(ValueError, match=match): - instance.commit() +# test_fabric_delete_00032 removed since FabricDelete.Results() is not optional +# as it is set in __init__() def test_fabric_delete_00040(fabric_delete) -> None: @@ -313,7 +292,7 @@ def test_fabric_delete_00040(fabric_delete) -> None: 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``) + 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() @@ -333,7 +312,7 @@ def test_fabric_delete_00040(fabric_delete) -> None: def responses(): yield responses_fabric_details_by_name_v2(key) - yield responses_fabric_summary(key) + yield responses_fabric_summary_v2(key) yield responses_fabric_delete(key) gen_responses = ResponseGenerator(responses()) @@ -410,13 +389,13 @@ def test_fabric_delete_00042(monkeypatch, fabric_delete) -> None: ### 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``. + - Verify FabricDelete().commit() re-raises `ValueError` when + `FabricDelete()._send_requests()` re-raises `ValueError` when + `FabricDelete()._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``. + re-raises `ValueError`. ### Code Flow @@ -424,11 +403,11 @@ def test_fabric_delete_00042(monkeypatch, fabric_delete) -> None: 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 + FabricDetailsByName().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``) + 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() @@ -437,14 +416,14 @@ def test_fabric_delete_00042(monkeypatch, fabric_delete) -> None: 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``. + `ValueError`. """ method_name = inspect.stack()[0][3] key = f"{method_name}a" class MockEpFabricDelete: # pylint: disable=too-few-public-methods """ - Mock the EpFabricDelete.path property to raise ``ValueError``. + Mock the EpFabricDelete.path property to raise `ValueError`. """ @property @@ -463,7 +442,7 @@ def fabric_name(self, value): def responses(): yield responses_fabric_details_by_name_v2(key) - yield responses_fabric_summary(key) + yield responses_fabric_summary_v2(key) gen_responses = ResponseGenerator(responses()) @@ -478,13 +457,8 @@ def responses(): with does_not_raise(): instance = fabric_delete - monkeypatch.setattr(instance, "ep_fabric_delete", MockEpFabricDelete()) + monkeypatch.setattr(instance, "_ep_fabric_delete", MockEpFabricDelete()) instance.fabric_names = ["f1"] - 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() @@ -567,7 +541,7 @@ def test_fabric_delete_00043(fabric_delete) -> None: def responses(): yield responses_fabric_details_by_name_v2(key) - yield responses_fabric_summary(key) + yield responses_fabric_summary_v2(key) gen_responses = ResponseGenerator(responses()) @@ -655,7 +629,7 @@ def test_fabric_delete_00044(fabric_delete) -> None: 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``) + 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() @@ -676,7 +650,7 @@ def test_fabric_delete_00044(fabric_delete) -> None: def responses(): yield responses_fabric_details_by_name_v2(key) - yield responses_fabric_summary(key) + yield responses_fabric_summary_v2(key) yield responses_fabric_delete(key) gen_responses = ResponseGenerator(responses()) @@ -728,7 +702,6 @@ def responses(): assert instance.results.result[0].get("success", None) is False 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 @@ -754,7 +727,7 @@ def test_fabric_delete_00050(monkeypatch, fabric_delete) -> None: ### Summary - Verify unsuccessful fabric delete code path. - - FabricDelete()._verify_fabric_can_be_deleted() raises ``ValueError`` + - 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. @@ -768,15 +741,15 @@ def test_fabric_delete_00050(monkeypatch, fabric_delete) -> None: 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`` + 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 + - 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 + - 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] @@ -784,7 +757,7 @@ def test_fabric_delete_00050(monkeypatch, fabric_delete) -> None: def responses(): yield responses_fabric_details_by_name_v2(key) - yield responses_fabric_summary(key) + yield responses_fabric_summary_v2(key) gen_responses = ResponseGenerator(responses()) @@ -800,13 +773,8 @@ def responses(): with does_not_raise(): instance = fabric_delete instance.fabric_names = ["f1"] - 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() + # instance.results = Results() match = r"FabricDelete\._verify_fabric_can_be_deleted: " match += "Fabric f1 cannot be deleted since it is not empty. " @@ -819,7 +787,6 @@ def responses(): assert isinstance(instance.results.response, list) 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 @@ -863,7 +830,7 @@ def test_fabric_delete_00051(monkeypatch, fabric_delete) -> None: ### Summary - Verify unsuccessful fabric delete code path. - - FabricDelete()._verify_fabric_delete() re-raises ``ValueError`` + - FabricDelete()._verify_fabric_delete() re-raises `ValueError` because FabricSummary().refresh() raises ``ControllerResponseError``. ### Code Flow @@ -879,14 +846,14 @@ def test_fabric_delete_00051(monkeypatch, fabric_delete) -> None: 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 + ``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 + - 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] @@ -894,7 +861,7 @@ def test_fabric_delete_00051(monkeypatch, fabric_delete) -> None: def responses(): yield responses_fabric_details_by_name_v2(key) - yield responses_fabric_summary(key) + yield responses_fabric_summary_v2(key) gen_responses = ResponseGenerator(responses()) @@ -966,7 +933,7 @@ def test_fabric_delete_00060(fabric_delete) -> None: ### Summary - - Verify that ``ValueError`` is raised because fabric_names + - Verify that `ValueError` is raised because fabric_names is not a list. """ with does_not_raise(): @@ -978,31 +945,6 @@ def test_fabric_delete_00060(fabric_delete) -> None: instance.fabric_names = "NOT_A_LIST" -def test_fabric_delete_00061(fabric_delete) -> None: - """ - ### Classes and Methods - - - FabricCommon - - __init__() - - FabricDelete - - __init__() - - commit() - - _validate_commit_parameters() - - ### Summary - - - Verify that ``ValueError`` is raised because fabric_names is an - empty list. - """ - with does_not_raise(): - instance = fabric_delete - - match = r"FabricDelete\.fabric_names: " - match += r"fabric_names must be a list of at least one string. got \[\]\." - with pytest.raises(ValueError, match=match): - instance.fabric_names = [] - - def test_fabric_delete_00062(fabric_delete) -> None: """ ### Classes and Methods @@ -1016,7 +958,7 @@ def test_fabric_delete_00062(fabric_delete) -> None: ### Summary - - Verify that ``ValueError`` is raised because fabric_names is a + - Verify that `ValueError` is raised because fabric_names is a list containing non-string elements. """ with does_not_raise(): From 224aeb0defb9d99e7d050166dd9d22d44c451066 Mon Sep 17 00:00:00 2001 From: Allen Robel Date: Wed, 26 Nov 2025 09:45:11 -1000 Subject: [PATCH 09/24] Appease linters Fix below sanity error: ERROR: plugins/module_utils/fabric/delete.py:290:5: E303: too many blank lines (2) --- plugins/module_utils/fabric/delete.py | 1 - 1 file changed, 1 deletion(-) diff --git a/plugins/module_utils/fabric/delete.py b/plugins/module_utils/fabric/delete.py index 55e13634b..31c75aaa3 100644 --- a/plugins/module_utils/fabric/delete.py +++ b/plugins/module_utils/fabric/delete.py @@ -286,7 +286,6 @@ def _set_fabric_delete_endpoint(self, fabric_name: str) -> None: except (ValueError, TypeError) as error: raise ValueError(error) from error - def _send_request(self, fabric_name: str) -> None: """ ### Summary From c19e15ca3b59d011d3ceeeb1a3033b3bea7b32d2 Mon Sep 17 00:00:00 2001 From: Allen Robel Date: Wed, 26 Nov 2025 09:51:24 -1000 Subject: [PATCH 10/24] FabricDeleted: temporarily override path and verb UT asserts are failing since path and verb were redefined from None to str in FabricCommon (common_v2.py). - Override these in FabricDeleted for now. - Add TODO to remove later. --- plugins/module_utils/fabric/delete.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/plugins/module_utils/fabric/delete.py b/plugins/module_utils/fabric/delete.py index 31c75aaa3..f8d1a65e3 100644 --- a/plugins/module_utils/fabric/delete.py +++ b/plugins/module_utils/fabric/delete.py @@ -88,6 +88,9 @@ def __init__(self) -> None: self._ep_fabric_delete: EpFabricDelete = EpFabricDelete() self._fabric_names: list[str] = [] self._fabrics_to_delete: list[str] = [] + # TODO: Overriding path and verb from FabricCommon for now to pass UT during initial commit. Remove when common_v2.py is added. + self.path: str = "" + self.verb: str = "" msg = "ENTERED FabricDelete()" self.log.debug(msg) From 63e654bbbf511068d4e21fc6d44002176eca42f9 Mon Sep 17 00:00:00 2001 From: Allen Robel Date: Wed, 26 Nov 2025 10:28:09 -1000 Subject: [PATCH 11/24] Address Copilot review comments 1. Fix inconsistent use of self._rest_send vs self.rest_send 2. _send_requests() add method signature return type annotation 3. Update remaining docstrings to proper Markdown --- plugins/module_utils/fabric/delete.py | 75 ++++++++++++++++++++------- 1 file changed, 56 insertions(+), 19 deletions(-) diff --git a/plugins/module_utils/fabric/delete.py b/plugins/module_utils/fabric/delete.py index f8d1a65e3..95b4eb07f 100644 --- a/plugins/module_utils/fabric/delete.py +++ b/plugins/module_utils/fabric/delete.py @@ -97,10 +97,19 @@ def __init__(self) -> None: def _get_fabrics_to_delete(self) -> None: """ - - Retrieve fabric info from the controller and set the list of - controller fabrics that are in our fabric_names list. - - Raise ``ValueError`` if any fabric in ``fabric_names`` - cannot be deleted. + # Summary + + Determine which fabrics in self.fabric_names can be deleted. + + Retrieve fabric info from the controller and set the list of + controller fabrics that are in our `fabric_names` list. Verify + that each fabric in the list can be deleted. + + ## Raises + + ### ValueError + + - Any fabric in `fabric_names` cannot be deleted. """ method_name: str = inspect.stack()[0][3] # pylint: disable=unused-variable self._fabric_details_by_name.refresh() @@ -239,23 +248,33 @@ def commit(self) -> None: self._results.response_current = {"RETURN_CODE": 200, "MESSAGE": msg} self._results.register_task_result() - def _send_requests(self): + def _send_requests(self) -> None: """ - - Update RestSend() parameters: - - check_mode : Enables or disables sending the request - - timeout : Reduce to 1 second from default of 300 seconds - - Call _send_request() for each fabric to be deleted. - - Raise ``ValueError`` if any fabric cannot be deleted. + # Summary + + Send delete requests for each fabric in self._fabrics_to_delete. + + - Update RestSend() parameters: + - check_mode : Enables or disables sending the request + - timeout : Reduce to 1 second from default of 300 seconds + - Call _send_request() for each fabric to be deleted. + + ## Raises + + ### ValueError + + - Any fabric cannot be deleted. + + ## Notes - NOTES: - 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 and restore the original timeout after the requests are sent. """ - self.rest_send.save_settings() - self.rest_send.timeout = 1 + self._rest_send.save_settings() + self._rest_send.timeout = 1 for fabric_name in self._fabrics_to_delete: try: @@ -263,7 +282,7 @@ def _send_requests(self): except ValueError as error: self.register_result(fabric_name="") raise ValueError(error) from error - self.rest_send.restore_settings() + self._rest_send.restore_settings() def _set_fabric_delete_endpoint(self, fabric_name: str) -> None: """ @@ -291,13 +310,16 @@ def _set_fabric_delete_endpoint(self, fabric_name: str) -> None: def _send_request(self, fabric_name: str) -> None: """ - ### Summary + # Summary + Send a delete request to the controller and register the result. - ### Raises - - ``ValueError`` if the fabric delete endpoint cannot be set. + ## Raises + + ### ValueError + + - The fabric delete endpoint cannot be set. """ - # pylint: disable=no-member try: self._set_fabric_delete_endpoint(fabric_name) except (ValueError, TypeError) as error: @@ -311,11 +333,17 @@ def _send_request(self, fabric_name: str) -> None: def register_result(self, fabric_name: str) -> None: """ + # Summary + - Register the result of the fabric delete request - If `fabric_name` is "" (empty string), set the result to indicate no changes occurred and the request was not successful. - If `fabric_name` is not "" (empty string), set the result to indicate the success or failure of the request. + + ## Raises + + None """ method_name: str = inspect.stack()[0][3] # pylint: disable=unused-variable self._results.action = self.action @@ -359,9 +387,18 @@ def register_result(self, fabric_name: str) -> None: @property def fabric_names(self) -> list[str]: """ + # Summary + + The list of fabric names to delete. + - getter: return list of fabric_names - setter: set list of fabric_names - - setter: raise ``ValueError`` if ``value`` is not a ``list`` of ``str`` + + ## Raises + + ### ValueError + + - setter: `value` is not a `list` of `str` """ return self._fabric_names From 4597931cfccb2145c0c83973862a8ef55fba328f Mon Sep 17 00:00:00 2001 From: Allen Robel Date: Wed, 26 Nov 2025 10:50:54 -1000 Subject: [PATCH 12/24] Appease linters Fix the below sanity errors: ERROR: plugins/module_utils/fabric/delete.py:107:1: W293: blank line contains whitespace ERROR: plugins/module_utils/fabric/delete.py:343:1: W293: blank line contains whitespace --- plugins/module_utils/fabric/delete.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/module_utils/fabric/delete.py b/plugins/module_utils/fabric/delete.py index 95b4eb07f..b6cf1f2d2 100644 --- a/plugins/module_utils/fabric/delete.py +++ b/plugins/module_utils/fabric/delete.py @@ -104,7 +104,7 @@ def _get_fabrics_to_delete(self) -> None: Retrieve fabric info from the controller and set the list of controller fabrics that are in our `fabric_names` list. Verify that each fabric in the list can be deleted. - + ## Raises ### ValueError @@ -340,7 +340,7 @@ def register_result(self, fabric_name: str) -> None: no changes occurred and the request was not successful. - If `fabric_name` is not "" (empty string), set the result to indicate the success or failure of the request. - + ## Raises None From 401d610ed5137c5d312dae89691522c0f806ff80 Mon Sep 17 00:00:00 2001 From: Allen Robel Date: Wed, 26 Nov 2025 11:46:09 -1000 Subject: [PATCH 13/24] Remove FabricSummary v1 # Summary All dependencies on FabricSummary v1 have been (hopefully) moved to FabricSummary v2. This PR removes FabricSummary v1. ## Changes 1. Remove plugins/module_utils/fabric/fabric_summary.py 2. tests/unit/modules/dcnm/dcnm_fabric/utils.py - Remove references and imports for FabricSummary v1 3. tests/unit/modules/dcnm/dcnm_fabric/test_fabric_summary.py - Remove unit tests for FabricSummary v1 4. tests/unit/modules/dcnm/dcnm_fabric/test_common.py Remove unit tests for FabricCommon v1 since these import FabricSummary v1 --- plugins/module_utils/fabric/fabric_summary.py | 363 -------- .../dcnm/dcnm_fabric/test_fabric_common.py | 433 ---------- .../dcnm/dcnm_fabric/test_fabric_summary.py | 815 ------------------ tests/unit/modules/dcnm/dcnm_fabric/utils.py | 122 ++- 4 files changed, 75 insertions(+), 1658 deletions(-) delete mode 100644 plugins/module_utils/fabric/fabric_summary.py delete mode 100644 tests/unit/modules/dcnm/dcnm_fabric/test_fabric_common.py delete mode 100644 tests/unit/modules/dcnm/dcnm_fabric/test_fabric_summary.py diff --git a/plugins/module_utils/fabric/fabric_summary.py b/plugins/module_utils/fabric/fabric_summary.py deleted file mode 100644 index cb0b25e85..000000000 --- a/plugins/module_utils/fabric/fabric_summary.py +++ /dev/null @@ -1,363 +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 ..common.api.v1.lan_fabric.rest.control.switches.switches import \ - EpFabricSummary -from ..common.conversion import ConversionUtils -from ..common.exceptions import ControllerResponseError -from ..common.results import Results -from .common import FabricCommon - - -class FabricSummary(FabricCommon): - """ - Populate ``dict`` ``self.data`` with fabric summary information. - - Convenience properties are provided to access the data, including: - - - @device_count - - @leaf_count - - @spine_count - - @border_gateway_count - - @in_sync_count - - @out_of_sync_count - - self.data will contain the following structure. - - ```python - { - "switchSWVersions": { - "10.2(5)": 7, - "10.3(1)": 2 - }, - "switchHealth": { - "Healthy": 2, - "Minor": 7 - }, - "switchHWVersions": { - "N9K-C93180YC-EX": 4, - "N9K-C9504": 5 - }, - "switchConfig": { - "Out-of-Sync": 5, - "In-Sync": 4 - }, - "switchRoles": { - "leaf": 4, - "spine": 3, - "border gateway": 2 - } - } - ``` - 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 = FabricSummary() - instance.rest_send = rest_send - instance.fabric_name = "MyFabric" - instance.refresh() - fabric_summary = instance.data - device_count = instance.device_count - ``` - etc... - """ - - def __init__(self): - super().__init__() - self.class_name = self.__class__.__name__ - - self.log = logging.getLogger(f"dcnm.{self.class_name}") - - self.data = None - self.ep_fabric_summary = EpFabricSummary() - self.conversion = ConversionUtils() - - # set to True in refresh() after a successful request to the controller - # Used by getter properties to ensure refresh() has been called prior - # to returning data. - self.refreshed = False - - self.results = Results() - - self._border_gateway_count = 0 - self._device_count = 0 - self._fabric_name = None - self._leaf_count = 0 - self._spine_count = 0 - - msg = "ENTERED FabricSummary()" - self.log.debug(msg) - - def _update_device_counts(self): - """ - - From the controller response, update class properties - pertaining to device counts. - - By the time refresh() calls this method, self.data - has been verified, so no need to verify it here. - """ - method_name = inspect.stack()[0][3] - - msg = f"{self.class_name}.{method_name}: " - msg = f"self.data: {json.dumps(self.data, indent=4, sort_keys=True)}" - self.log.debug(msg) - - self._border_gateway_count = self.data.get("switchRoles", {}).get( - "border gateway", 0 - ) - self._leaf_count = self.data.get("switchRoles", {}).get("leaf", 0) - self._spine_count = self.data.get("switchRoles", {}).get( - "spine", 0 - ) - self._device_count = ( - self.leaf_count + self.spine_count + self.border_gateway_count - ) - - def _set_fabric_summary_endpoint(self): - """ - - Set the fabric_summary endpoint. - - Raise ``ValueError`` if unable to retrieve the endpoint. - """ - 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: - msg = "Error retrieving fabric_summary endpoint. " - msg += f"Detail: {error}" - self.log.debug(msg) - raise ValueError(msg) from error - - def _verify_controller_response(self): - """ - - Raise ``ControllerResponseError`` if RETURN_CODE != 200. - - Raise ``ControllerResponseError`` if DATA is missing or empty. - """ - method_name = inspect.stack()[0][3] - - # pylint: disable=no-member - controller_return_code = self.rest_send.response_current.get( - "RETURN_CODE", None - ) - controller_message = self.rest_send.response_current.get("MESSAGE", None) - if controller_return_code != 200: - msg = f"{self.class_name}.{method_name}: " - msg += "Failed to retrieve fabric_summary for fabric_name " - msg += f"{self.fabric_name}. " - msg += f"RETURN_CODE: {controller_return_code}. " - msg += f"MESSAGE: {controller_message}." - self.log.error(msg) - raise ControllerResponseError(msg) - - # DATA is set to an empty dict in refresh() if the controller response - # does not contain a DATA key. - if len(self.data) == 0: - msg = f"{self.class_name}.{method_name}: " - msg += "Controller responded with missing or empty DATA." - raise ControllerResponseError(msg) - - def refresh(self): - """ - - Refresh fabric summary info from the controller and - populate ``self.data`` with the result. - - ``self.data`` is a ``dict`` of fabric summary info for one fabric. - - raise ``ValueError`` if ``fabric_name`` is not set. - - raise ``ValueError`` if unable to retrieve fabric_summary endpoint. - - raise ``ValueError`` if ``_update_device_counts()`` fails. - - raise ``ControllerResponseError`` if the controller - ``RETURN_CODE`` != 200 - """ - method_name = inspect.stack()[0][3] - if self.fabric_name is None: - msg = f"{self.class_name}.{method_name}: " - msg += f"Set {self.class_name}.fabric_name prior to calling " - 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 " - msg += f"{self.class_name}.refresh()." - raise ValueError(msg) - - try: - self._set_fabric_summary_endpoint() - except ValueError as error: - raise ValueError(error) from error - - # 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. - save_check_mode = self.rest_send.check_mode - self.rest_send.check_mode = False - self.rest_send.commit() - self.rest_send.check_mode = save_check_mode - self.data = copy.deepcopy(self.rest_send.response_current.get("DATA", {})) - - msg = f"self.data: {json.dumps(self.data, indent=4, sort_keys=True)}" - self.log.debug(msg) - - 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 - self.results.register_task_result() - - # pylint: enable=no-member - try: - self._verify_controller_response() - except ControllerResponseError as error: - raise ControllerResponseError(error) from error - - # self.refreshed must be True before calling - # self._update_device_counts() below - self.refreshed = True - self._update_device_counts() - - def verify_refresh_has_been_called(self, attempted_method_name): - """ - - raise ``ValueError`` if ``refresh()`` has not been called. - """ - if self.refreshed is True: - return - msg = f"{self.class_name}.refresh() must be called before accessing " - msg += f"{self.class_name}.{attempted_method_name}." - raise ValueError(msg) - - @property - def all_data(self) -> dict: - """ - - Return raw fabric summary data from the controller. - - Raise ``ValueError`` if ``refresh()`` has not been called. - """ - method_name = inspect.stack()[0][3] - try: - self.verify_refresh_has_been_called(method_name) - except ValueError as error: - raise ValueError(error) from error - return self.data - - @property - def border_gateway_count(self) -> int: - """ - - Return the number of border gateway devices in fabric fabric_name. - - Raise ``ValueError`` if ``refresh()`` has not been called. - """ - method_name = inspect.stack()[0][3] - try: - self.verify_refresh_has_been_called(method_name) - except ValueError as error: - raise ValueError(error) from error - return self._border_gateway_count - - @property - def device_count(self) -> int: - """ - - Return the total number of devices in fabric fabric_name. - - Raise ``ValueError`` if ``refresh()`` has not been called. - """ - method_name = inspect.stack()[0][3] - try: - self.verify_refresh_has_been_called(method_name) - except ValueError as error: - raise ValueError(error) from error - return self._device_count - - @property - def fabric_is_empty(self) -> bool: - """ - - Return True if the fabric is empty. - - Raise ``ValueError`` if ``refresh()`` has not been called. - """ - method_name = inspect.stack()[0][3] - try: - self.verify_refresh_has_been_called(method_name) - except ValueError as error: - raise ValueError(error) from error - if self.device_count == 0: - return True - return False - - @property - def fabric_name(self) -> str: - """ - - getter: Return the fabric_name to query. - - setter: Set the fabric_name to query. - - setter: Raise ``ValueError`` if fabric_name is not a string. - - setter: Raise ``ValueError`` if fabric_name is invalid (i.e. - the controller would return an error due to invalid characters). - """ - return self._fabric_name - - @fabric_name.setter - def fabric_name(self, value: str): - try: - self.conversion.validate_fabric_name(value) - except ValueError as error: - raise ValueError(error) from error - self._fabric_name = value - - @property - def leaf_count(self) -> int: - """ - - Return the number of leaf devices in fabric fabric_name. - - Raise ``ValueError`` if ``refresh()`` has not been called. - """ - method_name = inspect.stack()[0][3] - try: - self.verify_refresh_has_been_called(method_name) - except ValueError as error: - raise ValueError(error) from error - return self._leaf_count - - @property - def spine_count(self) -> int: - """ - - Return the number of spine devices in fabric fabric_name. - - Raise ``ValueError`` if ``refresh()`` has not been called. - """ - method_name = inspect.stack()[0][3] - try: - self.verify_refresh_has_been_called(method_name) - except ValueError as error: - raise ValueError(error) from error - return self._spine_count diff --git a/tests/unit/modules/dcnm/dcnm_fabric/test_fabric_common.py b/tests/unit/modules/dcnm/dcnm_fabric/test_fabric_common.py deleted file mode 100644 index 0b7062c2a..000000000 --- a/tests/unit/modules/dcnm/dcnm_fabric/test_fabric_common.py +++ /dev/null @@ -1,433 +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 copy -import inspect - -import pytest -from ansible_collections.cisco.dcnm.plugins.module_utils.common.results import \ - Results -from ansible_collections.cisco.dcnm.plugins.module_utils.fabric.common import \ - FabricCommon -from ansible_collections.cisco.dcnm.tests.unit.modules.dcnm.dcnm_fabric.utils import ( - does_not_raise, fabric_common_fixture, params, payloads_fabric_common, - responses_fabric_common) - - -def test_fabric_common_00010(fabric_common) -> None: - """ - Classes and Methods - - FabricCommon - - __init__() - - FabricUpdateBulk - - __init__() - - Summary - - Verify the class attributes are initialized to expected values. - - Test - - Class attributes are initialized to expected values - - ``ValueError`` is not called - """ - with does_not_raise(): - instance = fabric_common - assert instance.class_name == "FabricCommon" - - 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: - """ - Classes and Methods - - FabricCommon - - __init__() - - _fixup_payloads_to_commit() - - _fixup_anycast_gw_mac() - - Summary - - Verify ``ValueError`` is raised when ANYCAST_GW_MAC is malformed. - """ - method_name = inspect.stack()[0][3] - key = f"{method_name}a" - with does_not_raise(): - instance = fabric_common - instance.results = Results() - instance._payloads_to_commit = payloads_fabric_common(key) - match = r"FabricCommon\._fixup_anycast_gw_mac: " - match += "Error translating ANYCAST_GW_MAC for fabric f1, " - match += r"ANYCAST_GW_MAC: 00\.54, Error detail: Invalid MAC address" - with pytest.raises(ValueError, match=match): - instance._fixup_payloads_to_commit() - - -MATCH_00021a = r"FabricCommon\._fixup_bgp_as:\s+" -MATCH_00021a += r"Invalid BGP_AS .* for fabric f1,\s+" -MATCH_00021a += r"Error detail: BGP ASN .* failed regex validation\." - -MATCH_00021b = r"FabricCommon\._fixup_bgp_as:\s+" -MATCH_00021b += r"Invalid BGP_AS .* for fabric f1,\s+" -MATCH_00021b += r"Error detail: BGP ASN \(.*\) cannot be type float\(\)\s+" -MATCH_00021b += r"due to loss of trailing zeros\.\s+" -MATCH_00021b += r"Use a string or integer instead\." - - -@pytest.mark.parametrize( - "bgp_as, expected", - [ - ("65001.65535", does_not_raise()), - ("65001.0", does_not_raise()), - ("65001", does_not_raise()), - (65001, does_not_raise()), - (4294967295, does_not_raise()), - (0, pytest.raises(ValueError, match=MATCH_00021a)), - (4294967296, pytest.raises(ValueError, match=MATCH_00021a)), - ("FOOBAR", pytest.raises(ValueError, match=MATCH_00021a)), - ("65001.65536", pytest.raises(ValueError, match=MATCH_00021a)), - ("65001:65000", pytest.raises(ValueError, match=MATCH_00021a)), - (65001.65000, pytest.raises(ValueError, match=MATCH_00021b)), - ], -) -def test_fabric_common_00021(fabric_common, bgp_as, expected) -> None: - """ - Classes and Methods - - FabricCommon - - __init__() - - _fixup_payloads_to_commit() - - _fixup_bgp_as() - - Summary - - Verify ``ValueError`` is raised when BGP_AS fails regex validation. - """ - with does_not_raise(): - instance = fabric_common - instance.results = Results() - instance._payloads_to_commit = [ - {"BGP_AS": bgp_as, "FABRIC_NAME": "f1", "FABRIC_TYPE": "VXLAN_EVPN"} - ] - with expected: - instance._fixup_payloads_to_commit() - - -@pytest.mark.parametrize( - "value, expected_return_value", - [ - ("", None), - ("null", None), - ("Null", None), - ("NULL", None), - ("none", None), - ("None", None), - ("NONE", None), - (None, None), - (10, 10), - ({"foo": "bar"}, {"foo": "bar"}), - (["foo", "bar"], ["foo", "bar"]), - (101.4, 101.4), - ], -) -def test_fabric_common_00070(fabric_common, value, expected_return_value) -> None: - """ - Classes and Methods - - ConversionUtils - - make_none() - - FabricCommon - - __init__() - - Summary - - Verify FabricCommon().conversion.make_none returns expected values. - """ - with does_not_raise(): - instance = fabric_common - return_value = instance.conversion.make_none(value) - assert return_value == expected_return_value - - -@pytest.mark.parametrize( - "value, expected_return_value", - [ - (True, True), - ("true", True), - ("TRUE", True), - ("yes", True), - ("YES", True), - (False, False), - ("false", False), - ("FALSE", False), - ("no", False), - ("NO", False), - (10, 10), - ({"foo": "bar"}, {"foo": "bar"}), - (["foo", "bar"], ["foo", "bar"]), - (101.4, 101.4), - ], -) -def test_fabric_common_00080(fabric_common, value, expected_return_value) -> None: - """ - Classes and Methods - - ConversionUtils - - make_boolean() - - FabricCommon - - __init__() - - conversion.make_boolean() - - Summary - - Verify FabricCommon().conversion.make_boolean returns expected values. - """ - with does_not_raise(): - instance = fabric_common - return_value = instance.conversion.make_boolean(value) - assert return_value == expected_return_value - - -def test_fabric_common_00100(fabric_common) -> None: - """ - Classes and Methods - - FabricCommon - - __init__() - - _verify_payload() - - Summary - - Verify ``ValueError`` is raised when payload is not a `dict``. - """ - method_name = inspect.stack()[0][3] - key = f"{method_name}a" - - payload = payloads_fabric_common(key) - - with does_not_raise(): - instance = fabric_common - - match = r"FabricCommon\._verify_payload:\s+" - 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) - - -@pytest.mark.parametrize( - "mandatory_key", - [ - "BGP_AS", - "FABRIC_NAME", - "FABRIC_TYPE", - ], -) -def test_fabric_common_00110(fabric_common, mandatory_key) -> None: - """ - Classes and Methods - - FabricCommon - - __init__() - - _verify_payload() - - Summary - - Verify ``ValueError`` is raised when payload is missing - mandatory parameters. - """ - method_name = inspect.stack()[0][3] - key = f"{method_name}a" - - payload = payloads_fabric_common(key) - - payload.pop(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+" - match += r"parameter.*\." - with pytest.raises(ValueError, match=match): - instance._verify_payload(payload) - - -def test_fabric_common_00111(fabric_common) -> None: - """ - Classes and Methods - - FabricCommon - - __init__() - - _verify_payload() - - Summary - - Verify FabricCommon()_verify_payload() returns if action - is not one of "fabric_create" or "fabric_replace". - - NOTES: - - 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.action = "foo" - instance._verify_payload("NOT_A_DICT") - - -MATCH_00112a = r"FabricCommon\._verify_payload:\s+" -MATCH_00112a += r"Playbook configuration for fabric .* contains an invalid\s+" -MATCH_00112a += r"FABRIC_NAME\.\s+" -MATCH_00112a += r"Error detail: ConversionUtils\.validate_fabric_name:\s+" -MATCH_00112a += r"Invalid fabric name:\s+.*\.\s+" -MATCH_00112a += r"Fabric name must start with a letter A-Z or a-z and\s+" -MATCH_00112a += r"contain only the characters in: \[A-Z,a-z,0-9,-,_\]\.\s+" -MATCH_00112a += r"Bad configuration:.*" - - -MATCH_00112b = r"FabricCommon\._verify_payload:\s+" -MATCH_00112b += r"Playbook configuration for fabric .* contains an invalid\s+" -MATCH_00112b += r"FABRIC_NAME\.\s+" -MATCH_00112b += r"Error detail: ConversionUtils\.validate_fabric_name:\s+" -MATCH_00112b += r"Invalid fabric name\. Expected string. Got .*\.\s+" -MATCH_00112b += r"Bad configuration:.*" - - -@pytest.mark.parametrize( - "fabric_name, expected", - [ - ("MyFabric", does_not_raise()), - ("My_Fabric", does_not_raise()), - ("My-Fabric-66", does_not_raise()), - (0, pytest.raises(ValueError, match=MATCH_00112b)), - (100.100, pytest.raises(ValueError, match=MATCH_00112b)), - ("10_MyFabric", pytest.raises(ValueError, match=MATCH_00112a)), - ("My:Fabric", pytest.raises(ValueError, match=MATCH_00112a)), - ("My,Fabric", pytest.raises(ValueError, match=MATCH_00112a)), - ("@MyFabric", pytest.raises(ValueError, match=MATCH_00112a)), - ], -) -def test_fabric_common_00112(fabric_common, fabric_name, expected) -> None: - """ - Classes and Methods - - FabricCommon - - __init__() - - _verify_payload() - - Summary - - Verify ``ValueError`` is raised when FABRIC_NAME fails regex validation. - """ - payload = { - "BGP_AS": "65000.100", - "FABRIC_NAME": fabric_name, - "FABRIC_TYPE": "VXLAN_EVPN", - } - - with does_not_raise(): - instance = fabric_common - instance.action = "fabric_create" - instance.results = Results() - with expected: - instance._verify_payload(payload) - - -MATCH_00113a = r"FabricCommon\._verify_payload:\s+" -MATCH_00113a += r"Playbook configuration for fabric .* contains an invalid\s+" -MATCH_00113a += r"FABRIC_TYPE\s+\(.*\)\.\s+" -MATCH_00113a += r"Valid values for FABRIC_TYPE:\s+" -MATCH_00113a += r"\[.*]\.\s+" -MATCH_00113a += r"Bad configuration:\s+" - - -@pytest.mark.parametrize( - "fabric_type, expected", - [ - ("LAN_CLASSIC", does_not_raise()), - ("VXLAN_EVPN", does_not_raise()), - ("VXLAN_EVPN_MSD", does_not_raise()), - (0, pytest.raises(ValueError, match=MATCH_00113a)), - ("FOOBAR", pytest.raises(ValueError, match=MATCH_00113a)), - ], -) -def test_fabric_common_00113(fabric_common, fabric_type, expected) -> None: - """ - Classes and Methods - - FabricCommon - - __init__() - - _verify_payload() - - Summary - - Verify ``ValueError`` is raised when FABRIC_TYPE is invalid. - """ - payload = { - "BGP_AS": "65000.100", - "FABRIC_NAME": "MyFabric", - "FABRIC_TYPE": fabric_type, - } - - with does_not_raise(): - instance = fabric_common - instance.action = "fabric_create" - instance.results = Results() - with expected: - instance._verify_payload(payload) - - -MATCH_00120a = r"FabricCommon\.translate_anycast_gw_mac:\s+" -MATCH_00120a += r"Error translating ANYCAST_GW_MAC: for fabric MyFabric,\s+" -MATCH_00120a += r"ANYCAST_GW_MAC: .*, Error detail: Invalid MAC address:\s+.*" - - -@pytest.mark.parametrize( - "mac_in, mac_out, raises, expected", - [ - ("0001aabbccdd", "0001.aabb.ccdd", False, does_not_raise()), - ("00:01:aa:bb:cc:dd", "0001.aabb.ccdd", False, does_not_raise()), - ("00:---01:***aa:b//b:cc:dd", "0001.aabb.ccdd", False, does_not_raise()), - ("00zz.aabb.ccdd", None, True, pytest.raises(ValueError, match=MATCH_00120a)), - ("0001", None, True, pytest.raises(ValueError, match=MATCH_00120a)), - ], -) -def test_fabric_common_00120(fabric_common, mac_in, mac_out, raises, expected) -> None: - """ - Classes and Methods - - FabricCommon() - - __init__() - - translate_anycast_gw_mac() - - Summary - - Verify FabricCommon().translate_anycast_gw_mac() - raises ``ValueError`` if mac_in cannot be translated into a format - expected by the controller. - - Verify the error message when ``ValueError`` is raised. - - Verify ``ValueError`` is not raised when ANYCAST_GW_MAC can be - translated. - """ - with does_not_raise(): - instance = fabric_common - instance.results = Results() - with expected: - result = instance.translate_anycast_gw_mac("MyFabric", mac_in) - if raises is False: - assert result == mac_out diff --git a/tests/unit/modules/dcnm/dcnm_fabric/test_fabric_summary.py b/tests/unit/modules/dcnm/dcnm_fabric/test_fabric_summary.py deleted file mode 100644 index 69c1d5080..000000000 --- a/tests/unit/modules/dcnm/dcnm_fabric/test_fabric_summary.py +++ /dev/null @@ -1,815 +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.exceptions import \ - ControllerResponseError -from ansible_collections.cisco.dcnm.plugins.module_utils.common.rest_send import \ - RestSend -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_summary_fixture, - responses_fabric_summary) - - -def test_fabric_summary_00010(fabric_summary) -> None: - """ - Classes and Methods - - FabricCommon - - __init__() - - FabricSummary - - __init__() - - Test - - Class attributes are initialized to expected values - - Exception is not raised - """ - with does_not_raise(): - instance = fabric_summary - assert instance.class_name == "FabricSummary" - assert instance.data is None - assert instance.refreshed is False - 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: - """ - Classes and Methods - - FabricCommon() - - __init__() - - FabricSummary() - - __init__() - - refresh() - - Summary - - Verify FabricSummary().refresh() raises ``ValueError`` - when ``FabricSummary().fabric_name`` is not set. - - Code Flow - Setup - - FabricSummary() is instantiated - - FabricSummary().rest_send is set - - Code Flow - Test - - FabricSummary().refresh() is called without having - first set FabricSummary().fabric_name - - Expected Result - - ``ValueError`` is raised - - Exception message matches expected - """ - with does_not_raise(): - instance = fabric_summary - instance.rest_send = RestSend(MockAnsibleModule()) - - match = r"FabricSummary\.refresh: " - match += r"Set FabricSummary\.fabric_name prior to calling " - match += r"FabricSummary\.refresh\(\)\." - with pytest.raises(ValueError, match=match): - instance.refresh() - - -def test_fabric_summary_00031(fabric_summary) -> None: - """ - Classes and Methods - - FabricCommon() - - __init__() - - FabricSummary() - - __init__() - - refresh() - - Summary - - Verify FabricSummary().refresh() raises ``ValueError`` - when ``FabricSummary().rest_send`` is not set. - - Code Flow - Setup - - FabricSummary() is instantiated - - FabricSummary().fabric_name is set - - Code Flow - Test - - FabricSummary().refresh() is called without having - first set FabricSummary().rest_send - - Expected Result - - ``ValueError`` is raised - - Exception message matches expected - """ - with does_not_raise(): - instance = fabric_summary - instance.fabric_name = "MyFabric" - - match = r"FabricSummary\.refresh: " - match += r"Set FabricSummary\.rest_send prior to calling " - match += r"FabricSummary\.refresh\(\)\." - with pytest.raises(ValueError, match=match): - instance.refresh() - - -def test_fabric_summary_00032(monkeypatch, fabric_summary) -> None: - """ - Classes and Methods - - FabricCommon() - - __init__() - - FabricSummary() - - __init__() - - refresh() - - _set_fabric_summary_endpoint() - - Summary - - Verify that FabricSummary()._set_fabric_summary_endpoint() - re-raises ``ValueError`` when EpFabricSummary() raises - ``ValueError``. - """ - - class MockEpFabricSummary: # pylint: disable=too-few-public-methods - """ - Mock the EpFabricSummary.fabric_name getter property to raise ``ValueError``. - """ - - def validate_fabric_name(self, value="MyFabric"): - """ - Mocked method required for test, but not relevant to test result. - """ - - @property - def fabric_name(self): - """ - - Mocked fabric_name property getter - """ - - @fabric_name.setter - def fabric_name(self, value): - """ - - Mocked fabric_name property setter - """ - msg = "mocked MockEpFabricSummary().fabric_name setter exception." - raise ValueError(msg) - - match = r"Error retrieving fabric_summary endpoint\.\s+" - match += r"Detail: mocked MockEpFabricSummary\(\)\.fabric_name\s+" - match += r"setter exception\." - - with does_not_raise(): - instance = fabric_summary - monkeypatch.setattr(instance, "ep_fabric_summary", MockEpFabricSummary()) - instance.fabric_name = "MyFabric" - instance.rest_send = RestSend(MockAnsibleModule()) - with pytest.raises(ValueError, match=match): - instance.refresh() - - -def test_fabric_summary_00033(monkeypatch, fabric_summary) -> None: - """ - Classes and Methods - - FabricCommon() - - __init__() - - FabricSummary() - - __init__() - - refresh() - - _update_device_counts() - - Summary - - Verify refresh() success case with populated fabric: - - RETURN_CODE is 200. - - Controller response contains one fabric (f1). - - Fabric contains 2x leaf, 1x spine, 1x border_gateway. - - Code Flow - Setup - - FabricSummary() is instantiated - - FabricSummary().RestSend() is instantiated - - FabricSummary().Results() is instantiated - - FabricSummary().refresh() is called - - responses_FabricSummary contains a dict with: - - RETURN_CODE == 200 - - DATA == [] - - Code Flow - Test - - FabricSummary().refresh() is called - - Expected Result - - Exception is not raised - - instance.data returns expected fabric data - - Results() are updated - - FabricSummary().refreshed is True - - FabricSummary()._properties 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_summary(key) - - gen = ResponseGenerator(responses()) - - def mock_dcnm_send(*args, **kwargs): - item = gen.next - return item - - with does_not_raise(): - instance = fabric_summary - instance.rest_send = RestSend(MockAnsibleModule()) - instance.rest_send.unit_test = True - instance.rest_send.timeout = 1 - instance.fabric_name = "MyFabric" - - monkeypatch.setattr(PATCH_DCNM_SEND, mock_dcnm_send) - - with does_not_raise(): - instance.refresh() - - assert isinstance(instance.results.diff, list) - assert isinstance(instance.results.result, list) - assert isinstance(instance.results.response, list) - - assert len(instance.results.diff) == 1 - assert len(instance.results.result) == 2 - assert len(instance.results.response) == 2 - - 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.data.get("switchRoles", {}).get("leaf", None) == 2 - assert instance.data.get("switchRoles", {}).get("spine", None) == 1 - assert instance.data.get("switchRoles", {}).get("border gateway", None) == 1 - assert instance.border_gateway_count == 1 - assert instance.device_count == 4 - assert instance.leaf_count == 2 - assert instance.spine_count == 1 - assert instance.fabric_is_empty is False - - -def test_fabric_summary_00034(monkeypatch, fabric_summary) -> None: - """ - Classes and Methods - - FabricCommon() - - __init__() - - FabricSummary() - - __init__() - - refresh() - - _update_device_counts() - - Summary - - Verify refresh() success case with empty fabric: - - RETURN_CODE is 200. - - Controller response contains one fabric (f1). - - Fabric does not contain any switches. - - Code Flow - Setup - - FabricSummary() is instantiated - - FabricSummary().RestSend() is instantiated - - FabricSummary().Results() is instantiated - - FabricSummary().refresh() is called - - responses_FabricSummary contains a dict with: - - RETURN_CODE == 200 - - DATA == [] - - Code Flow - Test - - FabricSummary().refresh() is called - - Expected Result - - Exception is not raised - - instance.all_data returns expected fabric data - - Results() are updated - - FabricSummary().refreshed is True - - FabricSummary()._properties 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_summary(key) - - gen = ResponseGenerator(responses()) - - def mock_dcnm_send(*args, **kwargs): - item = gen.next - return item - - with does_not_raise(): - instance = fabric_summary - instance.rest_send = RestSend(MockAnsibleModule()) - instance.rest_send.unit_test = True - instance.rest_send.timeout = 1 - instance.fabric_name = "MyFabric" - - monkeypatch.setattr(PATCH_DCNM_SEND, mock_dcnm_send) - - with does_not_raise(): - instance.refresh() - - assert instance.refreshed is True - - assert isinstance(instance.results.diff, list) - assert isinstance(instance.results.result, list) - assert isinstance(instance.results.response, list) - - assert len(instance.results.diff) == 1 - assert len(instance.results.result) == 2 - assert len(instance.results.response) == 2 - - 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("switchRoles", {}).get("leaf", None) is None - assert instance.all_data.get("switchRoles", {}).get("spine", None) is None - assert instance.all_data.get("switchRoles", {}).get("border gateway", None) is None - assert instance.border_gateway_count == 0 - assert instance.device_count == 0 - assert instance.leaf_count == 0 - assert instance.spine_count == 0 - assert instance.fabric_is_empty is True - - -def test_fabric_summary_00035(monkeypatch, fabric_summary) -> None: - """ - Classes and Methods - - FabricCommon() - - __init__() - - FabricSummary() - - __init__() - - refresh() - - _update_device_counts() - - Summary - - Verify refresh() failure case: - - RETURN_CODE is 404. - - Controller response when fabric does not exist - - Code Flow - Setup - - FabricSummary() is instantiated. - - FabricSummary().RestSend() is instantiated. - - FabricSummary().fabric_name is set to a fabric that does not exist. - - FabricSummary().refresh() is called. - - responses_FabricSummary contains a dict with: - - RETURN_CODE == 404 - - DATA == { - "timestamp": 1713467047741, - "status": 404, - "error": "Not Found", - "path": "/rest/control/switches/MyFabric/overview" - } - - Code Flow - Test - - FabricSummary().refresh() is called - - Expected Result - - ``ControllerResponseException`` is raised - - instance.data contains error data - - Results() are updated - - FabricSummary().refreshed is False - - FabricSummary()._properties remain at their default values - """ - 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) - - gen = ResponseGenerator(responses()) - - def mock_dcnm_send(*args, **kwargs): - item = gen.next - return item - - with does_not_raise(): - instance = fabric_summary - instance.rest_send = RestSend(MockAnsibleModule()) - instance.rest_send.unit_test = True - instance.rest_send.timeout = 1 - instance.fabric_name = "MyFabric" - - 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 MyFabric\.\s+" - match += r"RETURN_CODE: 404\.\s+" - match += r"MESSAGE: Not Found\." - with pytest.raises(ControllerResponseError, match=match): - instance.refresh() - - assert instance.refreshed is False - - assert isinstance(instance.results.diff, list) - assert isinstance(instance.results.result, list) - assert isinstance(instance.results.response, list) - - assert len(instance.results.diff) == 1 - assert len(instance.results.result) == 2 - assert len(instance.results.response) == 2 - - assert instance.results.response[0].get("RETURN_CODE", None) == 404 - assert instance.results.result[0].get("found", None) is False - 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.data.get("switchRoles", {}).get("leaf", None) is None - assert instance.data.get("switchRoles", {}).get("spine", None) is None - assert instance.data.get("switchRoles", {}).get("border gateway", None) is None - - -def test_fabric_summary_00036(monkeypatch, fabric_summary) -> None: - """ - Classes and Methods - - FabricCommon() - - __init__() - - FabricSummary() - - __init__() - - refresh() - - _update_device_counts() - - Summary - - Verify refresh() failure case: - - RETURN_CODE is 200. - - DATA field is missing in the response. - - This shouldn't happen, but we need to handle it. - - Code Flow - Setup - - FabricSummary() is instantiated. - - FabricSummary().RestSend() is instantiated. - - FabricSummary().fabric_name is set to a fabric that does not exist. - - FabricSummary().refresh() is called. - - responses_FabricSummary contains a dict with: - - RETURN_CODE == 200 - - DATA missing - - Code Flow - Test - - FabricSummary().refresh() is called - - Expected Result - - ``ValueError`` is raised in FabricSummary()._verify_controller_response() - - instance.data is empty - - Results() are updated - - FabricSummary().refreshed is False - - FabricSummary()._properties remain at their default values - """ - 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) - - gen = ResponseGenerator(responses()) - - def mock_dcnm_send(*args, **kwargs): - item = gen.next - return item - - with does_not_raise(): - instance = fabric_summary - instance.rest_send = RestSend(MockAnsibleModule()) - instance.rest_send.unit_test = True - instance.rest_send.timeout = 1 - instance.fabric_name = "MyFabric" - - monkeypatch.setattr(PATCH_DCNM_SEND, mock_dcnm_send) - - match = r"FabricSummary.\_verify_controller_response:\s+" - match += r"Controller responded with missing or empty DATA\." - with pytest.raises(ControllerResponseError, match=match): - instance.refresh() - - assert instance.refreshed is False - - assert 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) == 1 - assert len(instance.results.result) == 2 - assert len(instance.results.response) == 2 - - 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_summary_00040(fabric_summary) -> None: - """ - Classes and Methods - - FabricCommon() - - __init__() - - FabricSummary() - - __init__() - - all_data getter - - Summary - - Verify FabricSummary().all_data raises ``ValueError`` - when ``FabricSummary().refresh()`` has not been called. - - Code Flow - Setup - - FabricSummary() is instantiated - - Code Flow - Test - - FabricSummary().all_data is accessed without having - first called FabricSummary().refresh() - - Expected Result - - ``ValueError`` is raised - - Exception message matches expected - """ - with does_not_raise(): - instance = fabric_summary - - match = r"FabricSummary\.refresh\(\) must be called before " - match += r"accessing FabricSummary\.all_data\." - with pytest.raises(ValueError, match=match): - instance.all_data # pylint: disable=pointless-statement - - -def test_fabric_summary_00050(fabric_summary) -> None: - """ - Classes and Methods - - FabricCommon() - - __init__() - - FabricSummary() - - __init__() - - border_gateway_count getter - - Summary - - Verify FabricSummary().border_gateway_count raises ``ValueError`` - when ``FabricSummary().refresh()`` has not been called. - - Code Flow - Setup - - FabricSummary() is instantiated - - Code Flow - Test - - FabricSummary().border_gateway_count is accessed without having - first called FabricSummary().refresh() - - Expected Result - - ``ValueError`` is raised - - Exception message matches expected - """ - with does_not_raise(): - instance = fabric_summary - - match = r"FabricSummary\.refresh\(\) must be called before " - match += r"accessing FabricSummary\.border_gateway_count\." - with pytest.raises(ValueError, match=match): - instance.border_gateway_count # pylint: disable=pointless-statement - - -def test_fabric_summary_00060(fabric_summary) -> None: - """ - Classes and Methods - - FabricCommon() - - __init__() - - FabricSummary() - - __init__() - - device_count getter - - Summary - - Verify FabricSummary().device_count raises ``ValueError`` - when ``FabricSummary().refresh()`` has not been called. - - Code Flow - Setup - - FabricSummary() is instantiated - - Code Flow - Test - - FabricSummary().device_count is accessed without having - first called FabricSummary().refresh() - - Expected Result - - ``ValueError`` is raised - - Exception message matches expected - """ - with does_not_raise(): - instance = fabric_summary - - match = r"FabricSummary\.refresh\(\) must be called before " - match += r"accessing FabricSummary\.device_count\." - with pytest.raises(ValueError, match=match): - instance.device_count # pylint: disable=pointless-statement - - -def test_fabric_summary_00070(fabric_summary) -> None: - """ - Classes and Methods - - FabricCommon() - - __init__() - - FabricSummary() - - __init__() - - fabric_is_empty getter - - Summary - - Verify FabricSummary().fabric_is_empty raises ``ValueError`` - when ``FabricSummary().refresh()`` has not been called. - - Code Flow - Setup - - FabricSummary() is instantiated - - Code Flow - Test - - FabricSummary().fabric_is_empty is accessed without having - first called FabricSummary().refresh() - - Expected Result - - ``ValueError`` is raised - - Exception message matches expected - """ - with does_not_raise(): - instance = fabric_summary - - match = r"FabricSummary\.refresh\(\) must be called before " - match += r"accessing FabricSummary\.fabric_is_empty\." - with pytest.raises(ValueError, match=match): - instance.fabric_is_empty # pylint: disable=pointless-statement - - -MATCH_00080a = r"ConversionUtils\.validate_fabric_name: " -MATCH_00080a += r"Invalid fabric name\. " -MATCH_00080a += r"Expected string\. Got.*\." - -MATCH_00080b = r"ConversionUtils\.validate_fabric_name: " -MATCH_00080b += r"Invalid fabric name:.*\. " -MATCH_00080b += "Fabric name must start with a letter A-Z or a-z and " -MATCH_00080b += r"contain only the characters in: \[A-Z,a-z,0-9,-,_\]\." - - -@pytest.mark.parametrize( - "fabric_name, expected, does_raise", - [ - ("MyFabric", does_not_raise(), False), - ("My_Fabric", does_not_raise(), False), - ("My-Fabric", does_not_raise(), False), - ("M", does_not_raise(), False), - (1, pytest.raises(TypeError, match=MATCH_00080a), True), - ({}, pytest.raises(TypeError, match=MATCH_00080a), True), - ([1, 2, 3], pytest.raises(TypeError, match=MATCH_00080a), True), - ("1", pytest.raises(ValueError, match=MATCH_00080b), True), - ("-MyFabric", pytest.raises(ValueError, match=MATCH_00080b), True), - ("_MyFabric", pytest.raises(ValueError, match=MATCH_00080b), True), - ("1MyFabric", pytest.raises(ValueError, match=MATCH_00080b), True), - ("My Fabric", pytest.raises(ValueError, match=MATCH_00080b), True), - ("My*Fabric", pytest.raises(ValueError, match=MATCH_00080b), True), - ], -) -def test_fabric_summary_00080( - fabric_summary, fabric_name, expected, does_raise -) -> None: - """ - Classes and Methods - - FabricCommon() - - __init__() - - FabricSummary() - - __init__() - - fabric_name setter/getter - - Summary - - Verify FabricSummary().fabric_name re-raises ``ValueError`` - when fabric_name is invalid. - - Code Flow - Setup - - FabricSummary() is instantiated - - Code Flow - Test - - FabricSummary().fabric_name is set to a value that would - cause the controller to return an error. - - Expected Result - - ``ValueError`` is raised - - Exception message matches expected - """ - with does_not_raise(): - instance = fabric_summary - with expected: - instance.fabric_name = fabric_name - if does_raise is False: - assert instance.fabric_name == fabric_name - - -def test_fabric_summary_00090(fabric_summary) -> None: - """ - Classes and Methods - - FabricCommon() - - __init__() - - FabricSummary() - - __init__() - - leaf_count getter - - Summary - - Verify FabricSummary().leaf_count raises ``ValueError`` - when ``FabricSummary().refresh()`` has not been called. - - Code Flow - Setup - - FabricSummary() is instantiated - - Code Flow - Test - - FabricSummary().leaf_count is accessed without having - first called FabricSummary().refresh() - - Expected Result - - ``ValueError`` is raised - - Exception message matches expected - """ - with does_not_raise(): - instance = fabric_summary - - match = r"FabricSummary\.refresh\(\) must be called before " - match += r"accessing FabricSummary\.leaf_count\." - with pytest.raises(ValueError, match=match): - instance.leaf_count # pylint: disable=pointless-statement - - -def test_fabric_summary_00100(fabric_summary) -> None: - """ - Classes and Methods - - FabricCommon() - - __init__() - - FabricSummary() - - __init__() - - spine_count getter - - Summary - - Verify FabricSummary().spine_count raises ``ValueError`` - when ``FabricSummary().refresh()`` has not been called. - - Code Flow - Setup - - FabricSummary() is instantiated - - Code Flow - Test - - FabricSummary().spine_count is accessed without having - first called FabricSummary().refresh() - - Expected Result - - ``ValueError`` is raised - - Exception message matches expected - """ - with does_not_raise(): - instance = fabric_summary - - match = r"FabricSummary\.refresh\(\) must be called before " - match += r"accessing FabricSummary\.spine_count\." - with pytest.raises(ValueError, match=match): - instance.spine_count # pylint: disable=pointless-statement diff --git a/tests/unit/modules/dcnm/dcnm_fabric/utils.py b/tests/unit/modules/dcnm/dcnm_fabric/utils.py index e439b5871..fa6b265a7 100644 --- a/tests/unit/modules/dcnm/dcnm_fabric/utils.py +++ b/tests/unit/modules/dcnm/dcnm_fabric/utils.py @@ -20,19 +20,23 @@ from contextlib import contextmanager +from typing import Any import pytest from ansible_collections.ansible.netcommon.tests.unit.modules.utils import AnsibleFailJson from ansible_collections.cisco.dcnm.plugins.module_utils.common.response_handler import ResponseHandler from ansible_collections.cisco.dcnm.plugins.module_utils.fabric.common import FabricCommon -from ansible_collections.cisco.dcnm.plugins.module_utils.fabric.config_deploy import FabricConfigDeploy +from ansible_collections.cisco.dcnm.plugins.module_utils.fabric.common_v2 import FabricCommon as FabricCommonV2 +from ansible_collections.cisco.dcnm.plugins.module_utils.fabric.config_deploy_v2 import FabricConfigDeploy from ansible_collections.cisco.dcnm.plugins.module_utils.fabric.config_save import FabricConfigSave from ansible_collections.cisco.dcnm.plugins.module_utils.fabric.create import 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_v2 import FabricDetails as FabricDetailsV2 from ansible_collections.cisco.dcnm.plugins.module_utils.fabric.fabric_details_v2 import FabricDetailsByName as FabricDetailsByNameV2 from ansible_collections.cisco.dcnm.plugins.module_utils.fabric.fabric_details_v2 import FabricDetailsByNvPair as FabricDetailsByNvPairV2 -from ansible_collections.cisco.dcnm.plugins.module_utils.fabric.fabric_summary import FabricSummary +from ansible_collections.cisco.dcnm.plugins.module_utils.fabric.fabric_details_v3 import FabricDetails as FabricDetailsV3 +from ansible_collections.cisco.dcnm.plugins.module_utils.fabric.fabric_details_v3 import FabricDetailsByName as FabricDetailsByNameV3 +from ansible_collections.cisco.dcnm.plugins.module_utils.fabric.fabric_details_v3 import FabricDetailsByNvPair as FabricDetailsByNvPairV3 from ansible_collections.cisco.dcnm.plugins.module_utils.fabric.fabric_summary_v2 import FabricSummary as FabricSummaryV2 from ansible_collections.cisco.dcnm.plugins.module_utils.fabric.fabric_types import FabricTypes from ansible_collections.cisco.dcnm.plugins.module_utils.fabric.query import FabricQuery @@ -54,7 +58,7 @@ class MockAnsibleModule: Mock the AnsibleModule class """ - check_mode = False + check_mode: bool = False params = { "state": "merged", @@ -68,17 +72,17 @@ class MockAnsibleModule: "choices": ["deleted", "overridden", "merged", "query", "replaced"], }, } - supports_check_mode = True + supports_check_mode: bool = True @property - def state(self): + def state(self) -> str: """ return the state """ - return self.params["state"] + return self.params.get("state", "") @state.setter - def state(self, value): + def state(self, value: str): """ set the state """ @@ -102,15 +106,23 @@ def public_method_for_pylint(self): @pytest.fixture(name="fabric_common") -def fabric_common_fixture(): +def fabric_common_fixture() -> FabricCommon: """ Return FabricCommon() instance. """ return FabricCommon() +@pytest.fixture(name="fabric_common_v2") +def fabric_common_v2_fixture() -> FabricCommonV2: + """ + Return FabricCommon() V2 instance. + """ + return FabricCommonV2() + + @pytest.fixture(name="fabric_config_deploy") -def fabric_config_deploy_fixture(): +def fabric_config_deploy_fixture() -> FabricConfigDeploy: """ return instance of FabricConfigDeploy() """ @@ -118,7 +130,7 @@ def fabric_config_deploy_fixture(): @pytest.fixture(name="fabric_config_save") -def fabric_config_save_fixture(): +def fabric_config_save_fixture() -> FabricConfigSave: """ return instance of FabricConfigSave() """ @@ -126,7 +138,7 @@ def fabric_config_save_fixture(): @pytest.fixture(name="fabric_create") -def fabric_create_fixture(): +def fabric_create_fixture() -> FabricCreate: """ Return FabricCreate() instance. """ @@ -134,7 +146,7 @@ def fabric_create_fixture(): @pytest.fixture(name="fabric_create_bulk") -def fabric_create_bulk_fixture(): +def fabric_create_bulk_fixture() -> FabricCreateBulk: """ Return FabricCreateBulk() instance. """ @@ -142,7 +154,7 @@ def fabric_create_bulk_fixture(): @pytest.fixture(name="fabric_create_common") -def fabric_create_common_fixture(): +def fabric_create_common_fixture() -> FabricCreateCommon: """ Return FabricCreateCommon() instance. """ @@ -150,7 +162,7 @@ def fabric_create_common_fixture(): @pytest.fixture(name="fabric_delete") -def fabric_delete_fixture(): +def fabric_delete_fixture() -> FabricDelete: """ Return FabricDelete() instance. """ @@ -158,7 +170,7 @@ def fabric_delete_fixture(): @pytest.fixture(name="fabric_details_v2") -def fabric_details_v2_fixture(): +def fabric_details_v2_fixture() -> FabricDetailsV2: """ Return FabricDetails() v2 instance """ @@ -166,23 +178,47 @@ def fabric_details_v2_fixture(): @pytest.fixture(name="fabric_details_by_name_v2") -def fabric_details_by_name_v2_fixture(): +def fabric_details_by_name_v2_fixture() -> FabricDetailsByNameV2: """ Return FabricDetailsByName version 2 instance """ return FabricDetailsByNameV2() +@pytest.fixture(name="fabric_details_by_name_v3") +def fabric_details_by_name_v3_fixture() -> FabricDetailsByNameV3: + """ + Return FabricDetailsByName version 3 instance + """ + return FabricDetailsByNameV3() + + @pytest.fixture(name="fabric_details_by_nv_pair_v2") -def fabric_details_by_nv_pair_v2_fixture(): +def fabric_details_by_nv_pair_v2_fixture() -> FabricDetailsByNvPairV2: """ Return FabricDetailsByNvPair version 2 instance """ return FabricDetailsByNvPairV2() +@pytest.fixture(name="fabric_details_by_nv_pair_v3") +def fabric_details_by_nv_pair_v3_fixture() -> FabricDetailsByNvPairV3: + """ + Return FabricDetailsByNvPair version 3 instance + """ + return FabricDetailsByNvPairV3() + + +@pytest.fixture(name="fabric_details_v3") +def fabric_details_v3_fixture() -> FabricDetailsV3: + """ + Return FabricDetails() v3 instance + """ + return FabricDetailsV3() + + @pytest.fixture(name="fabric_query") -def fabric_query_fixture(): +def fabric_query_fixture() -> FabricQuery: """ Return FabricQuery() instance. """ @@ -190,31 +226,23 @@ def fabric_query_fixture(): @pytest.fixture(name="fabric_replaced_bulk") -def fabric_replaced_bulk_fixture(): +def fabric_replaced_bulk_fixture() -> FabricReplacedBulk: """ Return FabricReplacedBulk() instance. """ return FabricReplacedBulk() -@pytest.fixture(name="fabric_summary") -def fabric_summary_fixture(): - """ - Return FabricSummary() instance. - """ - return FabricSummary() - - @pytest.fixture(name="fabric_summary_v2") -def fabric_summary_v2_fixture(): +def fabric_summary_v2_fixture() -> FabricSummaryV2: """ - Return FabricSummary() instance. + Return FabricSummaryV2() instance. """ return FabricSummaryV2() @pytest.fixture(name="fabric_types") -def fabric_types_fixture(): +def fabric_types_fixture() -> FabricTypes: """ Return FabricTypes() instance. """ @@ -222,7 +250,7 @@ def fabric_types_fixture(): @pytest.fixture(name="fabric_update_bulk") -def fabric_update_bulk_fixture(): +def fabric_update_bulk_fixture() -> FabricUpdateBulk: """ Return FabricUpdateBulk() instance. """ @@ -230,7 +258,7 @@ def fabric_update_bulk_fixture(): @pytest.fixture(name="response_handler") -def response_handler_fixture(): +def response_handler_fixture() -> ResponseHandler: """ Return ResponseHandler() instance. """ @@ -238,7 +266,7 @@ def response_handler_fixture(): @pytest.fixture(name="template_get") -def template_get_fixture(): +def template_get_fixture() -> TemplateGet: """ Return TemplateGet() instance. """ @@ -246,7 +274,7 @@ def template_get_fixture(): @pytest.fixture(name="template_get_all") -def template_get_all_fixture(): +def template_get_all_fixture() -> TemplateGetAll: """ Return TemplateGetAll() instance. """ @@ -271,7 +299,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, dict[str, Any]]: """ Return payloads for FabricCommon """ @@ -281,6 +309,16 @@ def payloads_fabric_common(key: str) -> dict[str, str]: return data +def payloads_fabric_common_v2(key: str) -> dict[str, dict[str, Any]]: + """ + Return payloads for FabricCommon + """ + data_file = "payloads_FabricCommon_V2" + data = load_fixture(data_file).get(key) + print(f"{data_file}: {key} : {data}") + return data + + def payloads_fabric_create(key: str) -> dict[str, str]: """ Return payloads for FabricCreate @@ -321,7 +359,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) -> list[dict[str, Any]]: """ Return payloads for FabricUpdateBulk """ @@ -471,19 +509,9 @@ def responses_fabric_replaced_bulk(key: str) -> dict[str, str]: return data -def responses_fabric_summary(key: str) -> dict[str, str]: - """ - Return responses for FabricSummary - """ - data_file = "responses_FabricSummary" - data = load_fixture(data_file).get(key) - print(f"{data_file}: {key} : {data}") - return data - - def responses_fabric_summary_v2(key: str) -> dict[str, str]: """ - Return responses for FabricSummary (v2) + Return responses for FabricSummaryV2 """ data_file = "responses_FabricSummary_V2" data = load_fixture(data_file).get(key) From 497e8e5a9afd69f6dd6b8298be7bf1d5055ea071 Mon Sep 17 00:00:00 2001 From: Allen Robel Date: Thu, 27 Nov 2025 10:38:28 -1000 Subject: [PATCH 14/24] Add remaining files MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Sorry, but the dependencies are across all (or most) of these files, so these cannot be commited independently. In the future, I plan to lint first as a separate PR to reduce the “noise” but, alas, I didn’t think to do that this time. --- plugins/module_utils/common/rest_send_v2.py | 2 +- plugins/module_utils/common/results_v2.py | 24 +- plugins/module_utils/fabric/common_v2.py | 139 ++++-- plugins/module_utils/fabric/config_deploy.py | 313 +++++++----- .../module_utils/fabric/config_deploy_v2.py | 270 +++++----- plugins/module_utils/fabric/create.py | 284 ++++++++--- .../module_utils/fabric/fabric_details_v2.py | 9 +- plugins/module_utils/fabric/query.py | 322 +++++++----- plugins/module_utils/fabric/replaced.py | 116 ++--- plugins/module_utils/fabric/update.py | 286 +++++++---- plugins/modules/dcnm_fabric.py | 340 +++++++------ .../fixtures/payloads_FabricCommon_V2.json | 40 ++ .../dcnm/dcnm_fabric/test_fabric_common_v2.py | 432 ++++++++++++++++ .../dcnm_fabric/test_fabric_config_deploy.py | 193 ++------ .../dcnm/dcnm_fabric/test_fabric_create.py | 81 ++- .../dcnm_fabric/test_fabric_create_bulk.py | 63 +-- .../dcnm_fabric/test_fabric_create_common.py | 55 +-- .../dcnm/dcnm_fabric/test_fabric_query.py | 251 ++++------ .../dcnm_fabric/test_fabric_replaced_bulk.py | 274 ++++------- .../dcnm_fabric/test_fabric_update_bulk.py | 462 +++++------------- 20 files changed, 2172 insertions(+), 1784 deletions(-) create mode 100644 tests/unit/modules/dcnm/dcnm_fabric/fixtures/payloads_FabricCommon_V2.json create mode 100644 tests/unit/modules/dcnm/dcnm_fabric/test_fabric_common_v2.py diff --git a/plugins/module_utils/common/rest_send_v2.py b/plugins/module_utils/common/rest_send_v2.py index fee113eb3..01ddc4023 100644 --- a/plugins/module_utils/common/rest_send_v2.py +++ b/plugins/module_utils/common/rest_send_v2.py @@ -28,7 +28,7 @@ from time import sleep # Using only for its failed_result property -from .results import Results +from .results_v2 import Results class RestSend: diff --git a/plugins/module_utils/common/results_v2.py b/plugins/module_utils/common/results_v2.py index 80aa41133..7d580e6a3 100644 --- a/plugins/module_utils/common/results_v2.py +++ b/plugins/module_utils/common/results_v2.py @@ -505,13 +505,13 @@ def register_task_result(self) -> None: self.add_diff(self.diff_current) if self.did_anything_change() is False: - self.changed.add(False) + self.add_changed(False) else: - self.changed.add(True) + self.add_changed(True) if self.result_current.get("success") is True: - self._failed.add(False) + self.add_failed(False) elif self.result_current.get("success") is False: - self._failed.add(True) + self.add_failed(True) else: msg = f"{self.class_name}.{method_name}: " msg += "self.result_current['success'] is not a boolean. " @@ -586,22 +586,6 @@ def build_final_result(self) -> None: self.final_result["result"] = self.result self.final_result["metadata"] = self.metadata - def add_to_failed(self, value: bool) -> None: - """ - # Summary - - Add a boolean value to the failed set. - - ## Raises - - - `ValueError`: if value is not a bool - """ - if not isinstance(value, bool): - msg = f"{self.class_name}.add_to_failed: " - msg += f"instance.add_to_failed must be a bool. Got {value}" - raise ValueError(msg) - self._failed.add(value) - @property def failed_result(self) -> dict: """ diff --git a/plugins/module_utils/fabric/common_v2.py b/plugins/module_utils/fabric/common_v2.py index 83ca09b30..2b2191a6b 100644 --- a/plugins/module_utils/fabric/common_v2.py +++ b/plugins/module_utils/fabric/common_v2.py @@ -22,12 +22,17 @@ import inspect import logging +from typing import Any, Literal from ..common.conversion import ConversionUtils +from ..common.operation_type import OperationType +from ..common.response_handler import ResponseHandler from ..common.rest_send_v2 import RestSend from ..common.results_v2 import Results from .config_deploy_v2 import FabricConfigDeploy from .config_save_v2 import FabricConfigSave +from .fabric_details_v3 import FabricDetailsByName +from .fabric_summary_v2 import FabricSummary from .fabric_types import FabricTypes @@ -45,12 +50,12 @@ def __init__(self): """ def __init__(self): - self.class_name = self.__class__.__name__ - self.action = None + self.class_name: str = self.__class__.__name__ + self.action = "fabric_common_v2" self.log = logging.getLogger(f"dcnm.{self.class_name}") - self.conversion = ConversionUtils() + self._conversion: ConversionUtils = ConversionUtils() self.config_save = FabricConfigSave() self.config_deploy = FabricConfigDeploy() self.fabric_types = FabricTypes() @@ -84,18 +89,19 @@ def __init__(self): # - self._fabric_needs_update_for_replaced_state() self._fabric_update_required = set() - self._payloads_to_commit: list = [] + self._payloads_to_commit: list[dict[str, Any]] = [] # 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 + # must be set first. Set these to "" here and define them later in # the commit() method. - self.path = None - self.verb = None + self.path: str = "" + self.verb: str = "" - self._fabric_details = None - self._fabric_summary = None + self._fabric_details: FabricDetailsByName = FabricDetailsByName() + self._fabric_summary: FabricSummary = FabricSummary() self._fabric_type = "VXLAN_EVPN" self._rest_send: RestSend = RestSend({}) + self._rest_send.response_handler = ResponseHandler() self._results: Results = Results() self._init_key_translations() @@ -126,7 +132,7 @@ def _config_save(self, payload): Raise ``ValueError`` if payload is missing FABRIC_NAME. - Raise ``ValueError`` if the endpoint assignment fails. """ - method_name = inspect.stack()[0][3] + method_name: str = inspect.stack()[0][3] fabric_name = payload.get("FABRIC_NAME", None) if fabric_name is None: @@ -142,13 +148,13 @@ def _config_save(self, payload): self.config_save.payload = payload # pylint: disable=no-member - self.config_save.rest_send = self.rest_send - self.config_save.results = self.results + self.config_save.rest_send = self._rest_send + self.config_save.results = self._results try: self.config_save.commit() except ValueError as error: raise ValueError(error) from error - result = self.rest_send.result_current["success"] + result = self._rest_send.result_current["success"] self.config_save_result[fabric_name] = result def _config_deploy(self, payload): @@ -158,9 +164,9 @@ def _config_deploy(self, payload): - Re-raise ``ValueError`` from FabricConfigDeploy(), if any. - Raise ``ValueError`` if the payload is missing the FABRIC_NAME key. """ - method_name = inspect.stack()[0][3] + method_name: str = inspect.stack()[0][3] fabric_name = payload.get("FABRIC_NAME") - if fabric_name is None: + if not fabric_name: msg = f"{self.class_name}.{method_name}: " msg += "payload is missing mandatory parameter: FABRIC_NAME." raise ValueError(msg) @@ -169,12 +175,9 @@ def _config_deploy(self, payload): return try: - 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 + self.config_deploy.rest_send = self._rest_send + self.config_deploy.results = self._results except TypeError as error: raise ValueError(error) from error try: @@ -213,14 +216,14 @@ def translate_anycast_gw_mac(self, fabric_name, mac_address): - Register the task result - raise ``ValueError`` """ - method_name = inspect.stack()[0][3] + method_name: str = inspect.stack()[0][3] try: - mac_address = self.conversion.translate_mac_address(mac_address) + mac_address = self._conversion.translate_mac_address(mac_address) except ValueError as error: # pylint: disable=no-member - self.results.add_failed(True) - self.results.add_changed(False) - self.results.register_task_result() + self._results.add_failed(True) + self._results.add_changed(False) + self._results.register_task_result() msg = f"{self.class_name}.{method_name}: " msg += "Error translating ANYCAST_GW_MAC: " @@ -249,9 +252,9 @@ def _fixup_payloads_to_commit(self) -> None: self._fixup_bgp_as() except ValueError as error: # pylint: disable=no-member - self.results.add_failed(True) - self.results.add_changed(False) - self.results.register_task_result() + self._results.add_failed(True) + self._results.add_changed(False) + self._results.register_task_result() raise ValueError(error) from error def _fixup_anycast_gw_mac(self) -> None: @@ -260,12 +263,12 @@ def _fixup_anycast_gw_mac(self) -> None: controller expects. - Raise ``ValueError`` if the translation fails. """ - method_name = inspect.stack()[0][3] + method_name: str = inspect.stack()[0][3] for payload in self._payloads_to_commit: if "ANYCAST_GW_MAC" not in payload: continue try: - payload["ANYCAST_GW_MAC"] = self.conversion.translate_mac_address(payload["ANYCAST_GW_MAC"]) + payload["ANYCAST_GW_MAC"] = self._conversion.translate_mac_address(payload["ANYCAST_GW_MAC"]) except ValueError as error: fabric_name = payload.get("FABRIC_NAME", "UNKNOWN") anycast_gw_mac = payload.get("ANYCAST_GW_MAC", "UNKNOWN") @@ -281,27 +284,27 @@ def _fixup_bgp_as(self) -> None: """ Raise ``ValueError`` if BGP_AS is not a valid BGP ASN. """ - method_name = inspect.stack()[0][3] + method_name: str = inspect.stack()[0][3] for payload in self._payloads_to_commit: if "BGP_AS" not in payload: continue bgp_as = payload["BGP_AS"] - if not self.conversion.bgp_as_is_valid(bgp_as): + if not self._conversion.bgp_as_is_valid(bgp_as): fabric_name = payload.get("FABRIC_NAME", "UNKNOWN") msg = f"{self.class_name}.{method_name}: " msg += f"Invalid BGP_AS {bgp_as} " msg += f"for fabric {fabric_name}, " - msg += f"Error detail: {self.conversion.bgp_as_invalid_reason}" + msg += f"Error detail: {self._conversion.bgp_as_invalid_reason}" raise ValueError(msg) - def _verify_payload(self, payload) -> None: + def _verify_payload(self, payload: dict[str, Any]) -> None: """ - Verify that the payload is a dict and contains all mandatory keys - raise ``ValueError`` if the payload is not a dict - raise ``ValueError`` if the payload is missing mandatory keys """ - method_name = inspect.stack()[0][3] - if self.action not in {"fabric_create", "fabric_replace", "fabric_update"}: + method_name: str = inspect.stack()[0][3] + if self.action not in {"fabric_create", "fabric_replace", "fabric_update", "fabric_update_bulk"}: return msg = f"{self.class_name}.{method_name}: " msg += f"payload: {payload}" @@ -337,7 +340,7 @@ def _verify_payload(self, payload) -> None: raise ValueError(msg) try: - self.conversion.validate_fabric_name(fabric_name) + self._conversion.validate_fabric_name(fabric_name) except (TypeError, ValueError) as error: msg = f"{self.class_name}.{method_name}: " msg += f"Playbook configuration for fabric {fabric_name} " @@ -400,7 +403,7 @@ def fabric_type(self): @fabric_type.setter def fabric_type(self, value): - method_name = inspect.stack()[0][3] + method_name: str = inspect.stack()[0][3] if value not in self.fabric_types.valid_fabric_types: msg = f"{self.class_name}.{method_name}: " msg += "FABRIC_TYPE must be one of " @@ -412,26 +415,82 @@ def fabric_type(self, value): @property def rest_send(self) -> RestSend: """ + # Summary + An instance of the RestSend class. + + ## Raises + + - setter: `TypeError` if the value is not an instance of RestSend. + - setter: `ValueError` if RestSend.params is not set. + + ## getter + + Return an instance of the RestSend class. + + ## setter + + Set an instance of the RestSend class. """ return self._rest_send @rest_send.setter def rest_send(self, value: RestSend) -> None: + method_name: str = inspect.stack()[0][3] + _class_have: str = "" + _class_need: Literal["RestSend"] = "RestSend" + msg = f"{self.class_name}.{method_name}: " + msg += f"value must be an instance of {_class_need}. " + msg += f"Got value {value} of type {type(value).__name__}." + try: + _class_have = value.class_name + except AttributeError as error: + msg += f" Error detail: {error}." + raise TypeError(msg) from error + if _class_have != _class_need: + raise TypeError(msg) if not value.params: - method_name = inspect.stack()[0][3] msg = f"{self.class_name}.{method_name}: " - msg += "rest_send must have params set." + msg += "RestSend.params must be set." raise ValueError(msg) self._rest_send = value @property def results(self) -> Results: """ + # Summary + An instance of the Results class. + + ## Raises + + - setter: `TypeError` if the value is not an instance of Results. + + ## getter + + Return an instance of the Results class. + + ## setter + + Set an instance of the Results class. """ return self._results @results.setter def results(self, value: Results) -> None: + method_name: str = inspect.stack()[0][3] + _class_have: str = "" + _class_need: Literal["Results"] = "Results" + msg = f"{self.class_name}.{method_name}: " + msg += f"value must be an instance of {_class_need}. " + msg += f"Got value {value} of type {type(value).__name__}." + try: + _class_have = value.class_name + except AttributeError as error: + msg += f" Error detail: {error}." + raise TypeError(msg) from error + if _class_have != _class_need: + raise TypeError(msg) self._results = value + self._results.action = self.action + self._results.operation_type = OperationType.QUERY diff --git a/plugins/module_utils/fabric/config_deploy.py b/plugins/module_utils/fabric/config_deploy.py index fff7f318f..091c16eb4 100644 --- a/plugins/module_utils/fabric/config_deploy.py +++ b/plugins/module_utils/fabric/config_deploy.py @@ -14,23 +14,24 @@ from __future__ import absolute_import, division, print_function -__metaclass__ = type +__metaclass__ = type # pylint: disable=invalid-name __author__ = "Allen Robel" import copy import inspect import logging +from typing import Any, Literal -from ..common.api.v1.lan_fabric.rest.control.fabrics.fabrics import \ - EpFabricConfigDeploy +from ..common.api.v1.lan_fabric.rest.control.fabrics.fabrics import EpFabricConfigDeploy from ..common.conversion import ConversionUtils -from ..common.results import Results from ..common.exceptions import ControllerResponseError -from ..common.properties import Properties +from ..common.operation_type import OperationType +from ..common.rest_send_v2 import RestSend +from ..common.results_v2 import Results +from .fabric_details_v3 import FabricDetailsByName +from .fabric_summary_v2 import FabricSummary -@Properties.add_rest_send -@Properties.add_results class FabricConfigDeploy: """ # Initiate a fabric config-deploy operation on the controller. @@ -66,26 +67,26 @@ class FabricConfigDeploy: ``` """ - def __init__(self): - self.class_name = self.__class__.__name__ + def __init__(self) -> None: + self.class_name: str = self.__class__.__name__ - self.log = logging.getLogger(f"dcnm.{self.class_name}") + self.log: logging.Logger = logging.getLogger(f"dcnm.{self.class_name}") - self.action = "config_deploy" - self.cannot_deploy_fabric_reason = "" - self.config_deploy_failed = False + self.action: Literal["config_deploy"] = "config_deploy" + self.cannot_deploy_fabric_reason: str = "" + self.config_deploy_failed: bool = False self.config_deploy_result: dict[str, bool] = {} - self.conversion = ConversionUtils() - self.ep_config_deploy = EpFabricConfigDeploy() + self.conversion: ConversionUtils = ConversionUtils() + self.ep_config_deploy: EpFabricConfigDeploy = EpFabricConfigDeploy() + self._fabric_details: FabricDetailsByName = FabricDetailsByName() + self._fabric_summary: FabricSummary = FabricSummary() - 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 + self._fabric_can_be_deployed: bool = False + self._fabric_name: str = "" + self._payload: dict[str, Any] = {} + self._rest_send: RestSend = RestSend({}) + self._results: Results = Results() msg = "ENTERED FabricConfigDeploy():" self.log.debug(msg) @@ -93,13 +94,13 @@ def __init__(self): def _can_fabric_be_deployed(self) -> None: """ ### Summary - - Set self.fabric_can_be_deployed to True if the fabric configuration + - Set self._fabric_can_be_deployed to True if the fabric configuration can be deployed. - - Set self.fabric_can_be_deployed to False otherwise. + - Set self._fabric_can_be_deployed to False otherwise. """ method_name = inspect.stack()[0][3] - self.fabric_can_be_deployed = False + self._fabric_can_be_deployed = False deploy = self.payload.get("DEPLOY", None) if deploy is False or deploy is None: @@ -108,12 +109,12 @@ def _can_fabric_be_deployed(self) -> None: msg += "Skipping config-deploy." self.log.debug(msg) self.cannot_deploy_fabric_reason = msg - self.fabric_can_be_deployed = False + self._fabric_can_be_deployed = False self.config_deploy_failed = False return try: - self.fabric_summary.fabric_name = self.fabric_name + self._fabric_summary.fabric_name = self.fabric_name except ValueError as error: msg = f"{self.class_name}.{method_name}: " msg += f"Fabric {self.fabric_name} is invalid. " @@ -121,67 +122,67 @@ def _can_fabric_be_deployed(self) -> None: msg += f"Error detail: {error}" self.log.debug(msg) self.cannot_deploy_fabric_reason = msg - self.fabric_can_be_deployed = False + self._fabric_can_be_deployed = False self.config_deploy_failed = True return try: - self.fabric_summary.refresh() + self._fabric_summary.refresh() except (ControllerResponseError, ValueError) as error: 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._fabric_can_be_deployed = False self.config_deploy_failed = True return - if self.fabric_summary.fabric_is_empty is True: + if self._fabric_summary.fabric_is_empty is True: msg = f"Fabric {self.fabric_name} is empty. " msg += "Cannot deploy an empty fabric." self.log.debug(msg) self.cannot_deploy_fabric_reason = msg - self.fabric_can_be_deployed = False + self._fabric_can_be_deployed = False self.config_deploy_failed = False return try: - self.fabric_details.results = Results() - self.fabric_details.refresh() + 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._fabric_can_be_deployed = False self.config_deploy_failed = True return - self.fabric_details.filter = self.fabric_name + self._fabric_details.filter = self.fabric_name - if self.fabric_details.deployment_freeze is True: + if self._fabric_details.deployment_freeze is 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 - self.fabric_can_be_deployed = False + self._fabric_can_be_deployed = False self.config_deploy_failed = False return - if self.fabric_details.is_read_only is True: + if self._fabric_details.is_read_only is 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 - self.fabric_can_be_deployed = False + self._fabric_can_be_deployed = False self.config_deploy_failed = False return - self.fabric_can_be_deployed = True + self._fabric_can_be_deployed = True def commit(self): """ @@ -191,45 +192,34 @@ 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: - msg = f"{self.class_name}.{method_name}: " - msg += f"{self.class_name}.fabric_details must be set " - msg += "before calling commit." - raise ValueError(msg) - if self.payload is None: + print("ZZZ: FabricConfigDeploy.commit: ENTERED") + + if not self.payload: msg = f"{self.class_name}.{method_name}: " msg += f"{self.class_name}.payload must be set " msg += "before calling commit." raise ValueError(msg) - if self.fabric_summary is None: - msg = f"{self.class_name}.{method_name}: " - msg += f"{self.class_name}.fabric_summary must be set " - msg += "before calling commit." - raise ValueError(msg) - if self.rest_send is None: + + if not self.rest_send.params: msg = f"{self.class_name}.{method_name}: " msg += f"{self.class_name}.rest_send must be set " msg += "before calling commit." raise ValueError(msg) - if self.results is None: - msg = f"{self.class_name}.{method_name}: " - msg += f"{self.class_name}.results must be set " - msg += "before calling commit." - raise ValueError(msg) + self._fabric_summary.rest_send = self._rest_send + self._fabric_details.rest_send = self._rest_send self._can_fabric_be_deployed() msg = f"{self.class_name}.{method_name}: " msg += f"fabric_name: {self.fabric_name}, " - msg += f"fabric_can_be_deployed: {self.fabric_can_be_deployed}, " + msg += f"fabric_can_be_deployed: {self._fabric_can_be_deployed}, " msg += f"cannot_deploy_fabric_reason: {self.cannot_deploy_fabric_reason}" msg += f"config_deploy_failed: {self.config_deploy_failed}" self.log.debug(msg) - if self.fabric_can_be_deployed is False: + if self._fabric_can_be_deployed is False: self.results.diff_current = {} self.results.action = self.action self.results.check_mode = self.rest_send.check_mode @@ -254,22 +244,22 @@ def commit(self): except ValueError as error: raise ValueError(error) from error - result = self.rest_send.result_current["success"] + 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: - self.results.diff_current = {} + self._results.diff_current = {} else: - self.results.diff_current = { + self._results.diff_current = { "FABRIC_NAME": self.fabric_name, f"{self.action}": "OK", } - self.results.action = self.action - 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() + self._results.action = self.action + 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() @property def fabric_name(self): @@ -286,57 +276,57 @@ def fabric_name(self, value): raise ValueError(error) from error self._fabric_name = value - @property - def fabric_details(self): - """ - - getter: Return an instance of the FabricDetailsByName class. - - setter: Set an instance of the FabricDetailsByName class. - - setter: Raise ``TypeError`` if the value is not an - instance of FabricDetailsByName. - """ - return self._fabric_details - - @fabric_details.setter - def fabric_details(self, value): - method_name = inspect.stack()[0][3] - msg = f"{self.class_name}.{method_name}: " - msg += "fabric_details must be an instance of FabricDetailsByName. " - try: - class_name = value.class_name - except AttributeError as error: - msg += f"Error detail: {error}. " - raise TypeError(msg) from error - if class_name != "FabricDetailsByName": - msg += f"Got {class_name}." - self.log.debug(msg) - raise TypeError(msg) - self._fabric_details = value - - @property - def fabric_summary(self): - """ - - getter: Return an instance of the FabricSummary class. - - setter: Set an instance of the FabricSummary class. - - setter: Raise ``TypeError`` if the value is not an - instance of FabricSummary. - """ - return self._fabric_summary - - @fabric_summary.setter - def fabric_summary(self, value): - method_name = inspect.stack()[0][3] - msg = f"{self.class_name}.{method_name}: " - msg += "fabric_summary must be an instance of FabricSummary. " - try: - class_name = value.class_name - except AttributeError as error: - msg += f"Error detail: {error}. " - raise TypeError(msg) from error - if class_name != "FabricSummary": - msg += f"Got {class_name}." - self.log.debug(msg) - raise TypeError(msg) - self._fabric_summary = value + # @property + # def fabric_details(self): + # """ + # - getter: Return an instance of the FabricDetailsByName class. + # - setter: Set an instance of the FabricDetailsByName class. + # - setter: Raise ``TypeError`` if the value is not an + # instance of FabricDetailsByName. + # """ + # return self._fabric_details + + # @fabric_details.setter + # def fabric_details(self, value): + # method_name = inspect.stack()[0][3] + # msg = f"{self.class_name}.{method_name}: " + # msg += "fabric_details must be an instance of FabricDetailsByName. " + # try: + # class_name = value.class_name + # except AttributeError as error: + # msg += f"Error detail: {error}. " + # raise TypeError(msg) from error + # if class_name != "FabricDetailsByName": + # msg += f"Got {class_name}." + # self.log.debug(msg) + # raise TypeError(msg) + # self._fabric_details = value + + # @property + # def fabric_summary(self): + # """ + # - getter: Return an instance of the FabricSummary class. + # - setter: Set an instance of the FabricSummary class. + # - setter: Raise ``TypeError`` if the value is not an + # instance of FabricSummary. + # """ + # return self._fabric_summary + + # @fabric_summary.setter + # def fabric_summary(self, value): + # method_name = inspect.stack()[0][3] + # msg = f"{self.class_name}.{method_name}: " + # msg += "fabric_summary must be an instance of FabricSummary. " + # try: + # class_name = value.class_name + # except AttributeError as error: + # msg += f"Error detail: {error}. " + # raise TypeError(msg) from error + # if class_name != "FabricSummary": + # msg += f"Got {class_name}." + # self.log.debug(msg) + # raise TypeError(msg) + # self._fabric_summary = value @property def payload(self): @@ -366,3 +356,84 @@ def payload(self, value): except ValueError as error: raise ValueError(error) from error self._payload = value + + @property + def rest_send(self) -> RestSend: + """ + # Summary + + An instance of the RestSend class. + + ## Raises + + - setter: `TypeError` if the value is not an instance of RestSend. + - setter: `ValueError` if RestSend.params is not set. + + ## getter + + Return an instance of the RestSend class. + + ## setter + + Set an instance of the RestSend class. + """ + return self._rest_send + + @rest_send.setter + def rest_send(self, value: RestSend) -> None: + method_name: str = inspect.stack()[0][3] + print("ZZZ: FabricConfigDeploy.rest_send.setter: ENTERED") + _class_have: str = "" + _class_need: Literal["RestSend"] = "RestSend" + msg = f"{self.class_name}.{method_name}: " + msg += f"value must be an instance of {_class_need}. " + msg += f"Got value {value} of type {type(value).__name__}." + try: + _class_have = value.class_name + except AttributeError as error: + msg += f" Error detail: {error}." + raise TypeError(msg) from error + if _class_have != _class_need: + raise TypeError(msg) + self._rest_send = value + print(f"ZZZ: FabricConfigDeploy._rest_send.params: {self._rest_send.params}") + + @property + def results(self) -> Results: + """ + # Summary + + An instance of the Results class. + + ## Raises + + - setter: `TypeError` if the value is not an instance of Results. + + ## getter + + Return an instance of the Results class. + + ## setter + + Set an instance of the Results class. + """ + return self._results + + @results.setter + def results(self, value: Results) -> None: + method_name: str = inspect.stack()[0][3] + _class_have: str = "" + _class_need: Literal["Results"] = "Results" + msg = f"{self.class_name}.{method_name}: " + msg += f"value must be an instance of {_class_need}. " + msg += f"Got value {value} of type {type(value).__name__}." + try: + _class_have = value.class_name + except AttributeError as error: + msg += f" Error detail: {error}." + raise TypeError(msg) from error + if _class_have != _class_need: + raise TypeError(msg) + self._results = value + self._results.action = self.action + self._results.operation_type = OperationType.UPDATE diff --git a/plugins/module_utils/fabric/config_deploy_v2.py b/plugins/module_utils/fabric/config_deploy_v2.py index e96145beb..2b0220e3b 100644 --- a/plugins/module_utils/fabric/config_deploy_v2.py +++ b/plugins/module_utils/fabric/config_deploy_v2.py @@ -22,11 +22,12 @@ import copy import inspect import logging +from typing import Any, Literal -from ..common.api.v1.lan_fabric.rest.control.fabrics.fabrics import \ - EpFabricConfigDeploy +from ..common.api.v1.lan_fabric.rest.control.fabrics.fabrics import EpFabricConfigDeploy from ..common.conversion import ConversionUtils from ..common.exceptions import ControllerResponseError +from ..common.operation_type import OperationType from ..common.rest_send_v2 import RestSend from ..common.results_v2 import Results from .fabric_details_v3 import FabricDetailsByName @@ -58,8 +59,6 @@ class FabricConfigDeploy: config_deploy = FabricConfigDeploy() config_deploy.rest_send = rest_send config_deploy.payload = payload # a valid payload dictionary - config_deploy.fabric_details = FabricDetailsByName() - config_deploy.fabric_summary = FabricSummary(params) config_deploy.results = results try: config_deploy.commit() @@ -68,40 +67,41 @@ class FabricConfigDeploy: ``` """ - def __init__(self): - self.class_name = self.__class__.__name__ + def __init__(self) -> None: + self.class_name: str = self.__class__.__name__ - self.log = logging.getLogger(f"dcnm.{self.class_name}") + self.log: logging.Logger = logging.getLogger(f"dcnm.{self.class_name}") - self.action = "config_deploy" - self.cannot_deploy_fabric_reason = "" - self.config_deploy_failed = False + self.action: str = "config_deploy" + self.cannot_deploy_fabric_reason: str = "" + self.config_deploy_failed: bool = False self.config_deploy_result: dict[str, bool] = {} - self.conversion = ConversionUtils() - self.ep_config_deploy = EpFabricConfigDeploy() + self.conversion: ConversionUtils = ConversionUtils() + self.ep_config_deploy: EpFabricConfigDeploy = EpFabricConfigDeploy() - self.fabric_can_be_deployed = False self._fabric_details: FabricDetailsByName = FabricDetailsByName() - self._fabric_name: str = "" self._fabric_summary: FabricSummary = FabricSummary() - self._payload: dict = {} self._rest_send: RestSend = RestSend({}) self._results: Results = Results() + self._fabric_can_be_deployed: bool = False + self._fabric_name: str = "" + self._payload: dict = {} + msg = "ENTERED FabricConfigDeploy():" self.log.debug(msg) def _can_fabric_be_deployed(self) -> None: """ ### Summary - - Set self.fabric_can_be_deployed to True if the fabric configuration + - Set self._fabric_can_be_deployed to True if the fabric configuration can be deployed. - - Set self.fabric_can_be_deployed to False otherwise. + - Set self._fabric_can_be_deployed to False otherwise. """ - method_name = inspect.stack()[0][3] + method_name: str = inspect.stack()[0][3] - self.fabric_can_be_deployed = False + self._fabric_can_be_deployed = False deploy = self.payload.get("DEPLOY", None) if deploy is False or deploy is None: @@ -110,12 +110,12 @@ def _can_fabric_be_deployed(self) -> None: msg += "Skipping config-deploy." self.log.debug(msg) self.cannot_deploy_fabric_reason = msg - self.fabric_can_be_deployed = False + self._fabric_can_be_deployed = False self.config_deploy_failed = False return try: - self.fabric_summary.fabric_name = self.fabric_name + self._fabric_summary.fabric_name = self.fabric_name except ValueError as error: msg = f"{self.class_name}.{method_name}: " msg += f"Fabric {self.fabric_name} is invalid. " @@ -123,69 +123,69 @@ def _can_fabric_be_deployed(self) -> None: msg += f"Error detail: {error}" self.log.debug(msg) self.cannot_deploy_fabric_reason = msg - self.fabric_can_be_deployed = False + self._fabric_can_be_deployed = False self.config_deploy_failed = True return try: - self.fabric_summary.refresh() + self._fabric_summary.refresh() except (ControllerResponseError, ValueError) as error: 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._fabric_can_be_deployed = False self.config_deploy_failed = True return - if self.fabric_summary.fabric_is_empty is True: + if self._fabric_summary.fabric_is_empty is True: msg = f"Fabric {self.fabric_name} is empty. " msg += "Cannot deploy an empty fabric." self.log.debug(msg) self.cannot_deploy_fabric_reason = msg - self.fabric_can_be_deployed = False + self._fabric_can_be_deployed = False self.config_deploy_failed = False return try: - self.fabric_details.results = Results() - self.fabric_details.refresh() + 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._fabric_can_be_deployed = False self.config_deploy_failed = True return - self.fabric_details.filter = self.fabric_name + self._fabric_details.filter = self.fabric_name - if self.fabric_details.deployment_freeze is True: + if self._fabric_details.deployment_freeze is 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 - self.fabric_can_be_deployed = False + self._fabric_can_be_deployed = False self.config_deploy_failed = False return - if self.fabric_details.is_read_only is True: + if self._fabric_details.is_read_only is 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 - self.fabric_can_be_deployed = False + self._fabric_can_be_deployed = False self.config_deploy_failed = False return - self.fabric_can_be_deployed = True + self._fabric_can_be_deployed = True - def commit(self): + def commit(self) -> None: """ - Initiate a config-deploy operation on the controller. - Raise ``ValueError`` if FabricConfigDeploy().payload is not set. @@ -193,95 +193,86 @@ 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] + method_name: str = inspect.stack()[0][3] - if self.fabric_details is None: - msg = f"{self.class_name}.{method_name}: " - msg += f"{self.class_name}.fabric_details must be set " - msg += "before calling commit." - raise ValueError(msg) - if self.payload is None: + if not self.payload: msg = f"{self.class_name}.{method_name}: " msg += f"{self.class_name}.payload must be set " msg += "before calling commit." raise ValueError(msg) - if self.fabric_summary is None: - msg = f"{self.class_name}.{method_name}: " - msg += f"{self.class_name}.fabric_summary must be set " - msg += "before calling commit." - raise ValueError(msg) - if self.rest_send is None: + + if not self._rest_send.params: msg = f"{self.class_name}.{method_name}: " msg += f"{self.class_name}.rest_send must be set " msg += "before calling commit." raise ValueError(msg) - if self.results is None: - msg = f"{self.class_name}.{method_name}: " - msg += f"{self.class_name}.results must be set " - msg += "before calling commit." - raise ValueError(msg) + + self._fabric_summary.rest_send = self._rest_send + self._fabric_summary.results = Results() + + self._fabric_details.rest_send = self._rest_send + self._fabric_details.results = Results() self._can_fabric_be_deployed() msg = f"{self.class_name}.{method_name}: " msg += f"fabric_name: {self.fabric_name}, " - msg += f"fabric_can_be_deployed: {self.fabric_can_be_deployed}, " + msg += f"fabric_can_be_deployed: {self._fabric_can_be_deployed}, " msg += f"cannot_deploy_fabric_reason: {self.cannot_deploy_fabric_reason}" msg += f"config_deploy_failed: {self.config_deploy_failed}" self.log.debug(msg) - if self.fabric_can_be_deployed is False: - self.results.diff_current = {} - self.results.action = self.action - self.results.check_mode = self.rest_send.check_mode - self.results.state = self.rest_send.state - self.results.response_current = { + if self._fabric_can_be_deployed is False: + self._results.diff_current = {} + self._results.action = self.action + 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, } if self.config_deploy_failed is True: - self.results.result_current = {"changed": False, "success": False} + self._results.result_current = {"changed": False, "success": False} else: - self.results.result_current = {"changed": True, "success": True} - self.results.register_task_result() + self._results.result_current = {"changed": True, "success": True} + self._results.register_task_result() return try: self.ep_config_deploy.fabric_name = self.fabric_name - 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() + 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 - result = self.rest_send.result_current["success"] + 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: - self.results.diff_current = {} + self._results.diff_current = {} else: - self.results.diff_current = { + self._results.diff_current = { "FABRIC_NAME": self.fabric_name, f"{self.action}": "OK", } - self.results.action = self.action - 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() + self._results.action = self.action + 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() @property - def fabric_name(self): + def fabric_name(self) -> str: """ The name of the fabric to config-save. """ return self._fabric_name @fabric_name.setter - def fabric_name(self, value): + def fabric_name(self, value: str) -> None: try: self.conversion.validate_fabric_name(value) except (TypeError, ValueError) as error: @@ -289,59 +280,7 @@ def fabric_name(self, value): self._fabric_name = value @property - def fabric_details(self): - """ - - getter: Return an instance of the FabricDetailsByName class. - - setter: Set an instance of the FabricDetailsByName class. - - setter: Raise ``TypeError`` if the value is not an - instance of FabricDetailsByName. - """ - return self._fabric_details - - @fabric_details.setter - def fabric_details(self, value): - method_name = inspect.stack()[0][3] - msg = f"{self.class_name}.{method_name}: " - msg += "fabric_details must be an instance of FabricDetailsByName. " - try: - class_name = value.class_name - except AttributeError as error: - msg += f"Error detail: {error}. " - raise TypeError(msg) from error - if class_name != "FabricDetailsByName": - msg += f"Got {class_name}." - self.log.debug(msg) - raise TypeError(msg) - self._fabric_details = value - - @property - def fabric_summary(self): - """ - - getter: Return an instance of the FabricSummary class. - - setter: Set an instance of the FabricSummary class. - - setter: Raise ``TypeError`` if the value is not an - instance of FabricSummary. - """ - return self._fabric_summary - - @fabric_summary.setter - def fabric_summary(self, value): - method_name = inspect.stack()[0][3] - msg = f"{self.class_name}.{method_name}: " - msg += "fabric_summary must be an instance of FabricSummary. " - try: - class_name = value.class_name - except AttributeError as error: - msg += f"Error detail: {error}. " - raise TypeError(msg) from error - if class_name != "FabricSummary": - msg += f"Got {class_name}." - self.log.debug(msg) - raise TypeError(msg) - self._fabric_summary = value - - @property - def payload(self): + def payload(self) -> dict[str, Any]: """ - The fabric payload used to create/merge/replace the fabric. - Raise ``ValueError`` if the value is not a dictionary. @@ -350,8 +289,8 @@ def payload(self): return self._payload @payload.setter - def payload(self, value): - method_name = inspect.stack()[0][3] + def payload(self, value: dict[str, Any]) -> None: + method_name: str = inspect.stack()[0][3] if not isinstance(value, dict): msg = f"{self.class_name}.{method_name} must be a dictionary. " @@ -372,26 +311,83 @@ def payload(self, value): @property def rest_send(self) -> RestSend: """ + # Summary + An instance of the RestSend class. + + ## Raises + + - setter: `TypeError` if the value is not an instance of RestSend. + - setter: `ValueError` if RestSend.params is not set. + + ## getter + + Return an instance of the RestSend class. + + ## setter + + Set an instance of the RestSend class. """ return self._rest_send @rest_send.setter def rest_send(self, value: RestSend) -> None: + method_name: str = inspect.stack()[0][3] + _class_have: str = "" + _class_need: Literal["RestSend"] = "RestSend" + msg = f"{self.class_name}.{method_name}: " + msg += f"value must be an instance of {_class_need}. " + msg += f"Got value {value} of type {type(value).__name__}." + try: + _class_have = value.class_name + except AttributeError as error: + msg += f" Error detail: {error}." + raise TypeError(msg) from error + if _class_have != _class_need: + raise TypeError(msg) if not value.params: - method_name = inspect.stack()[0][3] msg = f"{self.class_name}.{method_name}: " - msg += "rest_send must have params set." + msg += "RestSend.params must be set before assigning " + msg += "to FabricConfigDeploy.rest_send." raise ValueError(msg) self._rest_send = value @property def results(self) -> Results: """ + # Summary + An instance of the Results class. + + ## Raises + + - setter: `TypeError` if the value is not an instance of Results. + + ## getter + + Return an instance of the Results class. + + ## setter + + Set an instance of the Results class. """ return self._results @results.setter def results(self, value: Results) -> None: + method_name: str = inspect.stack()[0][3] + _class_have: str = "" + _class_need: Literal["Results"] = "Results" + msg = f"{self.class_name}.{method_name}: " + msg += f"value must be an instance of {_class_need}. " + msg += f"Got value {value} of type {type(value).__name__}." + try: + _class_have = value.class_name + except AttributeError as error: + msg += f" Error detail: {error}." + raise TypeError(msg) from error + if _class_have != _class_need: + raise TypeError(msg) self._results = value + self._results.action = self.action + self._results.operation_type = OperationType.UPDATE diff --git a/plugins/module_utils/fabric/create.py b/plugins/module_utils/fabric/create.py index c6a540dd9..83d78cc26 100644 --- a/plugins/module_utils/fabric/create.py +++ b/plugins/module_utils/fabric/create.py @@ -1,5 +1,5 @@ # -# Copyright (c) 2024 Cisco and/or its affiliates. +# Copyright (c) 2024-2025 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. @@ -12,20 +12,30 @@ # 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. +""" +Common methods and properties for: + +- FabricCreate +- FabricCreateBulk +""" from __future__ import absolute_import, division, print_function -__metaclass__ = type +__metaclass__ = type # pylint: disable=invalid-name __author__ = "Allen Robel" import copy import inspect import json import logging - -from ..common.api.v1.lan_fabric.rest.control.fabrics.fabrics import \ - EpFabricCreate -from .common import FabricCommon +from typing import Any, Literal + +from ..common.api.v1.lan_fabric.rest.control.fabrics.fabrics import EpFabricCreate +from ..common.operation_type import OperationType +from ..common.rest_send_v2 import RestSend +from ..common.results_v2 import Results +from .common_v2 import FabricCommon +from .fabric_details_v3 import FabricDetailsByName from .fabric_types import FabricTypes @@ -36,30 +46,38 @@ class FabricCreateCommon(FabricCommon): - FabricCreateBulk """ - def __init__(self): + def __init__(self) -> None: super().__init__() - self.class_name = self.__class__.__name__ - self.action = "fabric_create" + self.class_name: str = self.__class__.__name__ + self.action: str = "fabric_create" - self.log = logging.getLogger(f"dcnm.{self.class_name}") + self.log: logging.Logger = logging.getLogger(f"dcnm.{self.class_name}") - self.ep_fabric_create = EpFabricCreate() - self.fabric_types = FabricTypes() + self._ep_fabric_create: EpFabricCreate = EpFabricCreate() + self._fabric_details_by_name: FabricDetailsByName = FabricDetailsByName() + self._rest_send: RestSend = RestSend({}) + self._results: Results = Results() + self._results.operation_type = OperationType.CREATE + self._results.action = self.action + + self.fabric_types: FabricTypes = FabricTypes() # path and verb cannot be defined here because # EpFabricCreate().fabric_name must be set first. # Set these to None here and define them later in # _set_fabric_create_endpoint(). - self.path: str = None - self.verb: str = None + self.path: str = "" + self.verb: str = "" - self._payloads_to_commit: list = [] + self._payloads_to_commit: list[dict[str, Any]] = [] msg = "ENTERED FabricCreateCommon()" self.log.debug(msg) def _build_payloads_to_commit(self) -> None: """ + # Summary + Build a list of payloads to commit. Skip any payloads that already exist on the controller. @@ -68,49 +86,62 @@ def _build_payloads_to_commit(self) -> None: Populates self._payloads_to_commit with a list of payloads to commit. + + ## Raises + + None """ - self.fabric_details.refresh() + self._fabric_details_by_name.rest_send = self._rest_send + self._fabric_details_by_name.results = Results() + self._fabric_details_by_name.refresh() - self._payloads_to_commit: list = [] + self._payloads_to_commit = [] for payload in self.payloads: - if payload.get("FABRIC_NAME", None) in self.fabric_details.all_data: + if payload.get("FABRIC_NAME", None) in self._fabric_details_by_name.all_data: continue self._payloads_to_commit.append(copy.deepcopy(payload)) - def _set_fabric_create_endpoint(self, payload): + def _set_fabric_create_endpoint(self, payload: dict[str, Any]) -> None: """ - - Set the endpoint for the fabric create API call. - - raise ``ValueError`` if FABRIC_TYPE in the payload is invalid - - raise ``ValueError`` if the fabric_type to template_name mapping fails - - raise ``ValueError`` if the fabric_create endpoint assignment fails + # Summary + + Set the endpoint for the fabric create API call. + + ## Raises + + ### ValueError + + - FABRIC_TYPE in the payload is invalid + - fabric_type to template_name mapping fails + - _ep_fabric_create endpoint assignment fails """ - method_name = inspect.stack()[0][3] # pylint: disable=unused-variable + method_name: str = inspect.stack()[0][3] # pylint: disable=unused-variable try: - self.ep_fabric_create.fabric_name = payload.get("FABRIC_NAME") + self._ep_fabric_create.fabric_name = payload.get("FABRIC_NAME") except ValueError as error: raise ValueError(error) from error try: - self.fabric_type = copy.copy(payload.get("FABRIC_TYPE")) + _fabric_type = copy.copy(payload.get("FABRIC_TYPE")) except ValueError as error: raise ValueError(error) from error try: - self.fabric_types.fabric_type = self.fabric_type - template_name = self.fabric_types.template_name + self.fabric_types.fabric_type = _fabric_type + _template_name = self.fabric_types.template_name except ValueError as error: raise ValueError(error) from error try: - self.ep_fabric_create.template_name = template_name + self._ep_fabric_create.template_name = _template_name except ValueError as error: raise ValueError(error) from error payload.pop("FABRIC_TYPE", None) - self.path = self.ep_fabric_create.path - self.verb = self.ep_fabric_create.verb + self.path = self._ep_fabric_create.path + self.verb = self._ep_fabric_create.verb - def _add_ext_fabric_type_to_payload(self, payload: dict) -> dict: + def _add_ext_fabric_type_to_payload(self, payload: dict[str, Any]) -> dict[str, Any]: """ # Summary @@ -123,7 +154,7 @@ def _add_ext_fabric_type_to_payload(self, payload: dict) -> dict: None """ - method_name = inspect.stack()[0][3] + method_name: str = inspect.stack()[0][3] fabric_type = payload.get("FABRIC_TYPE") if fabric_type not in self.fabric_types.external_fabric_types: @@ -142,16 +173,24 @@ def _add_ext_fabric_type_to_payload(self, payload: dict) -> dict: self.log.debug(msg) return payload - def _send_payloads(self): + def _send_payloads(self) -> None: """ + # Summary + - If ``check_mode`` is ``False``, send the payloads to the controller. - If ``check_mode`` is ``True``, do not send the payloads to the controller. - In both cases, register results. - - raise ``ValueError`` if the fabric_create endpoint assignment fails - NOTES: + ## Raises + + ### ValueError + + - fabric_create endpoint assignment fails + + ## NOTES + - This overrides the parent class method. """ for payload in self._payloads_to_commit: @@ -171,45 +210,50 @@ def _send_payloads(self): # 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.timeout = 1 - self.rest_send.path = self.path - self.rest_send.verb = self.verb - self.rest_send.payload = payload - self.rest_send.commit() + self._rest_send.path = self.path + self._rest_send.verb = self.verb + self._rest_send.payload = payload + self._rest_send.commit() - if self.rest_send.result_current["success"] is False: - self.results.diff_current = {} + if self._rest_send.result_current["success"] is False: + self._results.diff_current = {} else: - self.results.diff_current = copy.deepcopy(payload) - self.results.action = self.action - self.results.state = self.rest_send.state - self.results.check_mode = self.rest_send.check_mode - self.results.response_current = copy.deepcopy( - self.rest_send.response_current - ) - self.results.result_current = copy.deepcopy(self.rest_send.result_current) - self.results.register_task_result() - - msg = f"self.results.diff: {json.dumps(self.results.diff, indent=4, sort_keys=True)}" + self._results.diff_current = copy.deepcopy(payload) + 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) + self._results.result_current = copy.deepcopy(self._rest_send.result_current) + self._results.register_task_result() + + msg = f"self._results.diff: {json.dumps(self._results.diff, indent=4, sort_keys=True)}" self.log.debug(msg) @property def payloads(self): """ - Payloads must be a ``list`` of ``dict`` of payloads for the - ``fabric_create`` endpoint. + # Summary + + Get or set the fabric create payloads. + + Payloads must be a `list` of `dict` of payloads for the `fabric_create` endpoint. - getter: Return the fabric create payloads - setter: Set the fabric create payloads - - setter: raise ``ValueError`` if ``payloads`` is not a ``list`` of ``dict`` - - setter: raise ``ValueError`` if any payload is missing mandatory keys + + ## Raises + + ### ValueError + + - setter: `payloads` is not a `list` of `dict` + - setter: Any payload is missing mandatory keys """ return self._payloads @payloads.setter - def payloads(self, value): - method_name = inspect.stack()[0][3] + def payloads(self, value: list[dict[str, Any]]) -> None: + method_name: str = inspect.stack()[0][3] msg = f"{self.class_name}.{method_name}: " msg += f"value: {value}" @@ -228,6 +272,84 @@ def payloads(self, value): raise ValueError(error) from error self._payloads = value + @property + def rest_send(self) -> RestSend: + """ + # Summary + + An instance of the RestSend class. + + ## Raises + + - setter: `TypeError` if the value is not an instance of RestSend. + - setter: `ValueError` if RestSend.params is not set. + + ## getter + + Return an instance of the RestSend class. + + ## setter + + Set an instance of the RestSend class. + """ + return self._rest_send + + @rest_send.setter + def rest_send(self, value: RestSend) -> None: + method_name: str = inspect.stack()[0][3] + _class_have: str = "" + _class_need: Literal["RestSend"] = "RestSend" + msg = f"{self.class_name}.{method_name}: " + msg += f"value must be an instance of {_class_need}. " + msg += f"Got value {value} of type {type(value).__name__}." + try: + _class_have = value.class_name + except AttributeError as error: + msg += f" Error detail: {error}." + raise TypeError(msg) from error + if _class_have != _class_need: + raise TypeError(msg) + self._rest_send = value + + @property + def results(self) -> Results: + """ + # Summary + + An instance of the Results class. + + ## Raises + + - setter: `TypeError` if the value is not an instance of Results. + + ## getter + + Return an instance of the Results class. + + ## setter + + Set an instance of the Results class. + """ + return self._results + + @results.setter + def results(self, value: Results) -> None: + method_name: str = inspect.stack()[0][3] + _class_have: str = "" + _class_need: Literal["Results"] = "Results" + msg = f"{self.class_name}.{method_name}: " + msg += f"value must be an instance of {_class_need}. " + msg += f"Got value {value} of type {type(value).__name__}." + try: + _class_have = value.class_name + except AttributeError as error: + msg += f" Error detail: {error}." + raise TypeError(msg) from error + if _class_have != _class_need: + raise TypeError(msg) + self._results = value + self._results.action = self.action + self._results.operation_type = OperationType.CREATE class FabricCreateBulk(FabricCreateCommon): """ @@ -238,7 +360,7 @@ class FabricCreateBulk(FabricCreateCommon): ```python from ansible_collections.cisco.dcnm.plugins.module_utils.fabric.create import \ FabricCreateBulk - from ansible_collections.cisco.dcnm.plugins.module_utils.common.results import \ + from ansible_collections.cisco.dcnm.plugins.module_utils.common.results_v2 import \ Results payloads = [ @@ -270,15 +392,15 @@ class FabricCreateBulk(FabricCreateCommon): ``` """ - def __init__(self): + def __init__(self) -> None: super().__init__() - self.class_name = self.__class__.__name__ + self.class_name: str = self.__class__.__name__ - self.log = logging.getLogger(f"dcnm.{self.class_name}") - self._payloads = None + self.log: logging.Logger = logging.getLogger(f"dcnm.{self.class_name}") + self._payloads: list[dict[str, Any]] = [] self.log.debug("ENTERED FabricCreateBulk()") - def commit(self): + def commit(self) -> None: """ # create fabrics. @@ -287,15 +409,14 @@ def commit(self): - raise ``ValueError`` if payload fixup fails. - raise ``ValueError`` if sending the payloads fails. """ - method_name = inspect.stack()[0][3] + method_name: str = inspect.stack()[0][3] - # pylint: disable=no-member - if self.rest_send is None: + if not self._rest_send.params: msg = f"{self.class_name}.{method_name}: " msg += "rest_send must be set prior to calling commit. " raise ValueError(msg) - if self.payloads is None: + if not self.payloads: msg = f"{self.class_name}.{method_name}: " msg += "payloads must be set prior to calling commit." raise ValueError(msg) @@ -328,15 +449,15 @@ class FabricCreate(FabricCreateCommon): - FabricCreateBulk is used instead. """ - def __init__(self): + def __init__(self) -> None: super().__init__() - self.class_name = self.__class__.__name__ + self.class_name: str = self.__class__.__name__ - self.log = logging.getLogger(f"dcnm.{self.class_name}") - self._payload = None + self.log: logging.Logger = logging.getLogger(f"dcnm.{self.class_name}") + self._payload: dict[str, Any] = {} self.log.debug("ENTERED FabricCreate()") - def commit(self): + def commit(self) -> None: """ - Send the fabric create request to the controller. - raise ``ValueError`` if ``rest_send`` is not set. @@ -351,13 +472,14 @@ def commit(self): to a list and leverage the processing that already exists in FabricCreateCommom() """ - method_name = inspect.stack()[0][3] - if self.rest_send is None: # pylint: disable=no-member + method_name: str = inspect.stack()[0][3] + + if not self._rest_send.params: msg = f"{self.class_name}.{method_name}: " msg += "rest_send must be set prior to calling commit. " raise ValueError(msg) - if self.payload is None: + if not self.payload: msg = f"{self.class_name}.{method_name}: " msg += "payload must be set prior to calling commit. " raise ValueError(msg) @@ -377,15 +499,15 @@ def commit(self): raise ValueError(error) from error @property - def payload(self): + def payload(self) -> dict[str, Any]: """ Return a fabric create payload. """ return self._payload @payload.setter - def payload(self, value): - method_name = inspect.stack()[0][3] + def payload(self, value: dict[str, Any]) -> None: + method_name: str = inspect.stack()[0][3] if not isinstance(value, dict): msg = f"{self.class_name}.{method_name}: " msg += "payload must be a dict. " diff --git a/plugins/module_utils/fabric/fabric_details_v2.py b/plugins/module_utils/fabric/fabric_details_v2.py index 4d0253b9f..85b8a0247 100644 --- a/plugins/module_utils/fabric/fabric_details_v2.py +++ b/plugins/module_utils/fabric/fabric_details_v2.py @@ -25,8 +25,7 @@ import inspect import logging -from ..common.api.v1.lan_fabric.rest.control.fabrics.fabrics import \ - EpFabrics +from ..common.api.v1.lan_fabric.rest.control.fabrics.fabrics import EpFabrics from ..common.conversion import ConversionUtils from ..common.properties import Properties @@ -457,7 +456,7 @@ class FabricDetailsByName(FabricDetails): ```python 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.results_v2 import Results from ansible_collections.cisco.dcnm.plugins.module_utils.common.sender_dcnm import Sender params = {"check_mode": False, "state": "merged"} @@ -487,7 +486,7 @@ class FabricDetailsByName(FabricDetails): ```python 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.results_v2 import Results from ansible_collections.cisco.dcnm.plugins.module_utils.common.sender_dcnm import Sender params = {"check_mode": False, "state": "merged"} @@ -681,7 +680,7 @@ class FabricDetailsByNvPair(FabricDetails): ### Usage ```python 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.results_v2 import Results from ansible_collections.cisco.dcnm.plugins.module_utils.common.sender_dcnm import Sender params = {"check_mode": False, "state": "query"} diff --git a/plugins/module_utils/fabric/query.py b/plugins/module_utils/fabric/query.py index b4343f9de..e68b3088b 100644 --- a/plugins/module_utils/fabric/query.py +++ b/plugins/module_utils/fabric/query.py @@ -11,49 +11,52 @@ # 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 +__metaclass__ = type # pylint: disable=invalid-name __author__ = "Allen Robel" import copy import inspect import logging +from typing import Literal -from ..common.results import Results -from .common import FabricCommon +from ..common.operation_type import OperationType +from ..common.rest_send_v2 import RestSend +from ..common.results_v2 import Results +from .common_v2 import FabricCommon +from .fabric_details_v3 import FabricDetailsByName class FabricQuery(FabricCommon): """ - ### Summary + # Summary + Query fabrics. - ### Raises - - ``ValueError`` if: - - ``fabric_details`` is not set. - - ``fabric_names`` is not set. - - ``rest_send`` is not set. - - ``results`` is not set. + ## Raises + + ### ValueError + + - `fabric_names` is not set. + - `rest_send` is not set. - ### Usage + ## 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 + from ansible_collections.cisco.dcnm.plugins.module_utils.common.operation_type import OperationType + from ansible_collections.cisco.dcnm.plugins.module_utils.common.rest_send_v2 import RestSend + from ansible_collections.cisco.dcnm.plugins.module_utils.common.results_v2 import Results params = {"state": "query", "check_mode": False} rest_send = RestSend(params) results = Results() - - 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. + results.operation_type = OperationType.QUERY instance = FabricQuery() - instance.fabric_details = fabric_details instance.fabric_names = ["FABRIC_1", "FABRIC_2"] instance.results = results instance.commit() @@ -77,38 +80,142 @@ class FabricQuery(FabricCommon): ``` """ - def __init__(self): + def __init__(self) -> None: super().__init__() - self.class_name = self.__class__.__name__ - self.action = "fabric_query" + self.class_name: str = self.__class__.__name__ + self.action: str = "fabric_query" + + self.log: logging.Logger = logging.getLogger(f"dcnm.{self.class_name}") + + self._results: Results = Results() + self._results.operation_type = OperationType.QUERY + self._rest_send: RestSend = RestSend(params={}) - self.log = logging.getLogger(f"dcnm.{self.class_name}") + self._fabric_names: list[str] = [] + self._fabrics_to_query: list[str] = [] - self._fabrics_to_query = [] - self._fabric_names = None + self._fabric_details_by_name: FabricDetailsByName = FabricDetailsByName() + self._fabric_details_by_name.rest_send = self._rest_send + self._fabric_details_by_name.results = self._results msg = "ENTERED FabricQuery()" self.log.debug(msg) + def _validate_commit_parameters(self) -> None: + """ + # Summary + + Validate mandatory parameters for commit are set. + + ## Raises + + ### ValueError + + - `fabric_names` is not set. + - `rest_send` is not set. + """ + method_name: str = inspect.stack()[0][3] + + if not self._fabric_names: + msg = f"{self.class_name}.{method_name}: " + msg += "fabric_names must be set before calling commit." + raise ValueError(msg) + + if not self._rest_send.params: + msg = f"{self.class_name}.{method_name}: " + msg += "rest_send must be set before calling commit." + raise ValueError(msg) + + def commit(self) -> None: + """ + # Summary + + query each of the fabrics in `fabric_names`. + + ## Raises + + ### ValueError + + - `_validate_commit_parameters` raises `ValueError`. + + """ + try: + self._validate_commit_parameters() + except ValueError as error: + self._results.action = self.action + self._results.add_changed(False) + self._results.add_failed(True) + if self._rest_send.params: + 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 + + self._fabric_details_by_name.rest_send = self._rest_send + self._fabric_details_by_name.results = Results() + self._fabric_details_by_name.results.operation_type = OperationType.QUERY + self._fabric_details_by_name.refresh() + + self._results.action = self.action + self._results.check_mode = self._rest_send.check_mode + self._results.state = self._rest_send.state + + msg = f"self._fabric_names: {self._fabric_names}" + self.log.debug(msg) + add_to_diff = {} + for fabric_name in self._fabric_names: + if fabric_name in self._fabric_details_by_name.all_data: + add_to_diff[fabric_name] = copy.deepcopy( + self._fabric_details_by_name.all_data[fabric_name] + ) + + self._results.diff_current = add_to_diff + self._results.response_current = copy.deepcopy( + self._fabric_details_by_name.results.response_current + ) + self._results.result_current = copy.deepcopy( + self._fabric_details_by_name.results.result_current + ) + + if not add_to_diff: + msg = f"No fabric details found for {self._fabric_names}." + self.log.debug(msg) + self._results.result_current["found"] = False + self._results.result_current["success"] = False + else: + msg = f"Found fabric details for {self._fabric_names}." + self.log.debug(msg) + + self._results.register_task_result() + @property - def fabric_names(self): + def fabric_names(self) -> list[str]: """ - ### Summary + # Summary + + The list of fabric names to query. + - 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. + ## Raises + + ### ValueError + + - `value` is not a list. + - `value` is an empty list. + - `value` is not a list of strings. """ return self._fabric_names @fabric_names.setter - def fabric_names(self, value): - method_name = inspect.stack()[0][3] + def fabric_names(self, value: list[str]) -> None: + method_name: str = inspect.stack()[0][3] + if not isinstance(value, list): msg = f"{self.class_name}.{method_name}: " msg += "fabric_names must be a list. " @@ -129,104 +236,81 @@ def fabric_names(self, value): raise ValueError(msg) self._fabric_names = value - def _validate_commit_parameters(self): - """ - ### 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. + @property + def rest_send(self) -> RestSend: """ - method_name = inspect.stack()[0][3] # pylint: disable=unused-variable + # Summary - 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) + An instance of the RestSend class. - if self.fabric_names is None: - msg = f"{self.class_name}.{method_name}: " - msg += "fabric_names must be set before calling commit." - raise ValueError(msg) + ## Raises - # 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) + - setter: `TypeError` if the value is not an instance of RestSend. + - setter: `ValueError` if RestSend.params is not set. - # 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 += "results must be set before calling commit." - raise ValueError(msg) + ## getter - def commit(self): - """ - ### Summary - - query each of the fabrics in ``fabric_names``. + Return an instance of the RestSend class. - ### Raises - - ``ValueError`` if: - - ``_validate_commit_parameters`` raises ``ValueError``. + ## setter + Set an instance of the RestSend class. """ + return self._rest_send + + @rest_send.setter + def rest_send(self, value: RestSend) -> None: + method_name: str = inspect.stack()[0][3] + _class_have: str = "" + _class_need: Literal["RestSend"] = "RestSend" + msg = f"{self.class_name}.{method_name}: " + msg += f"value must be an instance of {_class_need}. " + msg += f"Got value {value} of type {type(value).__name__}." 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 + _class_have = value.class_name + except AttributeError as error: + msg += f" Error detail: {error}." + raise TypeError(msg) from error + if _class_have != _class_need: + raise TypeError(msg) + self._rest_send = value - self.fabric_details.refresh() + @property + def results(self) -> Results: + """ + # Summary - # pylint: disable=no-member - self.results.action = self.action - self.results.check_mode = self.rest_send.check_mode - self.results.state = self.rest_send.state - # pylint: enable=no-member + An instance of the Results class. - msg = f"self.fabric_names: {self.fabric_names}" - self.log.debug(msg) - add_to_diff = {} - for fabric_name in self.fabric_names: - if fabric_name in self.fabric_details.all_data: - add_to_diff[fabric_name] = copy.deepcopy( - self.fabric_details.all_data[fabric_name] - ) + ## Raises - # pylint: disable=no-member - self.results.diff_current = add_to_diff - self.results.response_current = copy.deepcopy( - self.fabric_details.results.response_current - ) - self.results.result_current = copy.deepcopy( - self.fabric_details.results.result_current - ) + - setter: `TypeError` if the value is not an instance of Results. - if not add_to_diff: - msg = f"No fabric details found for {self.fabric_names}." - self.log.debug(msg) - self.results.result_current["found"] = False - self.results.result_current["success"] = False - else: - msg = f"Found fabric details for {self.fabric_names}." - self.log.debug(msg) + ## getter + + Return an instance of the Results class. + + ## setter - self.results.register_task_result() + Set an instance of the Results class. + """ + return self._results + + @results.setter + def results(self, value: Results) -> None: + method_name: str = inspect.stack()[0][3] + _class_have: str = "" + _class_need: Literal["Results"] = "Results" + msg = f"{self.class_name}.{method_name}: " + msg += f"value must be an instance of {_class_need}. " + msg += f"Got value {value} of type {type(value).__name__}." + try: + _class_have = value.class_name + except AttributeError as error: + msg += f" Error detail: {error}." + raise TypeError(msg) from error + if _class_have != _class_need: + raise TypeError(msg) + self._results = value + self._results.action = self.action + self._results.operation_type = OperationType.QUERY diff --git a/plugins/module_utils/fabric/replaced.py b/plugins/module_utils/fabric/replaced.py index 096d47931..81c98373f 100644 --- a/plugins/module_utils/fabric/replaced.py +++ b/plugins/module_utils/fabric/replaced.py @@ -15,18 +15,20 @@ from __future__ import absolute_import, division, print_function -__metaclass__ = type +__metaclass__ = type # pylint: disable=invalid-name __author__ = "Allen Robel" import copy import inspect import json import logging +from typing import Any -from ..common.api.v1.lan_fabric.rest.control.fabrics.fabrics import \ - EpFabricUpdate +from ..common.api.v1.lan_fabric.rest.control.fabrics.fabrics import EpFabricUpdate from ..common.exceptions import ControllerResponseError -from .common import FabricCommon +from .common_v2 import FabricCommon +from .fabric_details_v3 import FabricDetailsByName +from .fabric_summary_v2 import FabricSummary from .fabric_types import FabricTypes from .param_info import ParamInfo from .ruleset import RuleSet @@ -40,19 +42,21 @@ class FabricReplacedCommon(FabricCommon): - FabricReplacedBulk """ - def __init__(self): + def __init__(self) -> None: super().__init__() - self.class_name = self.__class__.__name__ - self.action = "fabric_replace" + self.class_name: str = self.__class__.__name__ + self.action: str = "fabric_replace" - self.log = logging.getLogger(f"dcnm.{self.class_name}") + self.log: logging.Logger = logging.getLogger(f"dcnm.{self.class_name}") - self.ep_fabric_update = EpFabricUpdate() - self.fabric_types = FabricTypes() - self.param_info = ParamInfo() - self.ruleset = RuleSet() - self.template_get = TemplateGet() - self.verify_playbook_params = VerifyPlaybookParams() + self._ep_fabric_update: EpFabricUpdate = EpFabricUpdate() + self._fabric_summary: FabricSummary = FabricSummary() + self._fabric_details: FabricDetailsByName = FabricDetailsByName() + self._fabric_types: FabricTypes = FabricTypes() + self._param_info: ParamInfo = ParamInfo() + self._ruleset: RuleSet = RuleSet() + self._template_get: TemplateGet = TemplateGet() + self._verify_playbook_params: VerifyPlaybookParams = VerifyPlaybookParams() # key: fabric_type, value: dict # Updated in _build_fabric_templates() @@ -263,11 +267,11 @@ def _fabric_needs_update_for_replaced_state(self, payload): # Refresh ParamInfo() with the fabric template try: - self.param_info.template = self.fabric_templates.get(fabric_type) + self._param_info.template = self.fabric_templates.get(fabric_type) except TypeError as error: raise ValueError(error) from error try: - self.param_info.refresh() + self._param_info.refresh() except ValueError as error: raise ValueError(error) from error @@ -287,7 +291,7 @@ def _fabric_needs_update_for_replaced_state(self, payload): self.log.debug(msg) try: - parameter_info = self.param_info.parameter(parameter) + parameter_info = self._param_info.parameter(parameter) except KeyError as error: msg = f"SKIP parameter: {parameter} in fabric {fabric_name}. " msg += "parameter not found in template." @@ -357,13 +361,13 @@ def _build_fabric_templates(self): if fabric_type in self.fabric_templates: continue try: - self.fabric_types.fabric_type = fabric_type + self._fabric_types.fabric_type = fabric_type except ValueError as error: raise ValueError(error) from error - self.template_get.template_name = self.fabric_types.template_name - self.template_get.refresh() - self.fabric_templates[fabric_type] = self.template_get.template + self._template_get.template_name = self._fabric_types.template_name + self._template_get.refresh() + self.fabric_templates[fabric_type] = self._template_get.template def _build_payloads_for_replaced_state(self): """ @@ -382,6 +386,7 @@ def _build_payloads_for_replaced_state(self): configuration. """ self.fabric_details.refresh() + print(f"ZZZ: self.fabric_details.all_data: {self.fabric_details.all_data}") self._payloads_to_commit = [] # Builds self.fabric_templates dictionary, keyed on fabric type. # Value is the fabric template associated with each fabric_type. @@ -421,17 +426,17 @@ def _initial_payload_validation(self, payload) -> None: fabric_type = payload.get("FABRIC_TYPE", None) fabric_name = payload.get("FABRIC_NAME", None) try: - self.verify_playbook_params.config_playbook = payload + self._verify_playbook_params.config_playbook = payload except TypeError as error: raise ValueError(error) from error try: - self.fabric_types.fabric_type = fabric_type + self._fabric_types.fabric_type = fabric_type except ValueError as error: raise ValueError(error) from error try: - self.verify_playbook_params.template = self.fabric_templates[fabric_type] + self._verify_playbook_params.template = self.fabric_templates[fabric_type] except TypeError as error: raise ValueError(error) from error config_controller = self.fabric_details.all_data.get(fabric_name, {}).get( @@ -439,12 +444,12 @@ def _initial_payload_validation(self, payload) -> None: ) try: - self.verify_playbook_params.config_controller = config_controller + self._verify_playbook_params.config_controller = config_controller except TypeError as error: raise ValueError(error) from error try: - self.verify_playbook_params.commit() + self._verify_playbook_params.commit() except ValueError as error: raise ValueError(error) from error @@ -502,24 +507,24 @@ def _set_fabric_update_endpoint(self, payload): - raise ``ValueError`` if the enpoint assignment fails """ try: - self.ep_fabric_update.fabric_name = payload.get("FABRIC_NAME") + self._ep_fabric_update.fabric_name = payload.get("FABRIC_NAME") except ValueError as error: raise ValueError(error) from error self.fabric_type = copy.copy(payload.get("FABRIC_TYPE")) try: - self.fabric_types.fabric_type = self.fabric_type + self._fabric_types.fabric_type = self.fabric_type except ValueError as error: raise ValueError(error) from error try: - self.ep_fabric_update.template_name = self.fabric_types.template_name + self._ep_fabric_update.template_name = self._fabric_types.template_name except ValueError as error: raise ValueError(error) from error payload.pop("FABRIC_TYPE", None) - self.path = self.ep_fabric_update.path - self.verb = self.ep_fabric_update.verb + self.path = self._ep_fabric_update.path + self.verb = self._ep_fabric_update.verb def _send_payload(self, payload): """ @@ -566,13 +571,21 @@ def _send_payload(self, payload): @property def payloads(self): """ - Payloads must be a ``list`` of ``dict`` of payloads for the - ``fabric_update`` endpoint. + # Summary + + Get/Set the payloads for fabric update in replaced state. + + Payloads must be a `list` of `dict` of payloads for the `fabric_update` endpoint. - getter: Return the fabric update payloads - setter: Set the fabric update payloads - - setter: raise ``ValueError`` if ``payloads`` is not a ``list`` of ``dict`` - - setter: raise ``ValueError`` if any payload is missing mandatory keys + + ## Raises + + ### ValueError + + - setter: `payloads` is not a `list` of `dict` + - setter: Any payload is missing mandatory keys """ return self._payloads @@ -585,6 +598,10 @@ def payloads(self, value): msg += f"got {type(value).__name__} for " msg += f"value {value}" raise ValueError(msg) + if len(value) == 0: + msg = f"{self.class_name}.{method_name}: " + msg += "payloads must not be an empty list." + raise ValueError(msg) for item in value: try: self._verify_payload(item) @@ -599,10 +616,8 @@ class FabricReplacedBulk(FabricReplacedCommon): Usage (where params is an AnsibleModule.params dictionary): ```python - from ansible_collections.cisco.dcnm.plugins.module_utils.fabric.update import \ - FabricReplacedBulk - from ansible_collections.cisco.dcnm.plugins.module_utils.common.results import \ - Results + from ansible_collections.cisco.dcnm.plugins.module_utils.fabric.update import FabricReplacedBulk + from ansible_collections.cisco.dcnm.plugins.module_utils.common.results_v2 import Results payloads = [ { "FABRIC_NAME": "fabric1", "FABRIC_TYPE": "VXLAN_EVPN", "BGP_AS": 65000, "DEPLOY": True }, @@ -637,7 +652,7 @@ def __init__(self): self.class_name = self.__class__.__name__ self.log = logging.getLogger(f"dcnm.{self.class_name}") - self._payloads = None + self._payloads: list[dict[str, Any]] = [] self.log.debug("ENTERED FabricReplacedBulk()") @@ -645,31 +660,19 @@ def commit(self): """ - Update fabrics and register results. - Return if there are no fabrics to update for replaced state. - - raise ``ValueError`` if ``fabric_details`` is not set - - raise ``ValueError`` if ``fabric_summary`` is not set - raise ``ValueError`` if ``payloads`` is not set - raise ``ValueError`` if ``rest_send`` is not set - raise ``ValueError`` if ``_build_payloads_for_replaced_state`` fails - raise ``ValueError`` if ``_send_payloads`` fails """ method_name = inspect.stack()[0][3] - 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_summary is None: - msg = f"{self.class_name}.{method_name}: " - msg += "fabric_summary must be set prior to calling commit." - raise ValueError(msg) - if self.payloads is None: + if not self._payloads: msg = f"{self.class_name}.{method_name}: " msg += "payloads must be set prior to calling commit." raise ValueError(msg) - # pylint: disable=no-member - if self.rest_send is None: + if not self._rest_send.params: msg = f"{self.class_name}.{method_name}: " msg += "rest_send must be set prior to calling commit." raise ValueError(msg) @@ -678,7 +681,10 @@ def commit(self): self.results.check_mode = self.rest_send.check_mode self.results.state = self.rest_send.state - self.template_get.rest_send = self.rest_send + self._fabric_details.rest_send = self.rest_send + self._fabric_summary.rest_send = self.rest_send + + self._template_get.rest_send = self.rest_send try: self._build_payloads_for_replaced_state() except ValueError as error: diff --git a/plugins/module_utils/fabric/update.py b/plugins/module_utils/fabric/update.py index e50daf2eb..8317f1c59 100644 --- a/plugins/module_utils/fabric/update.py +++ b/plugins/module_utils/fabric/update.py @@ -12,45 +12,57 @@ # 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. - +""" +Update fabrics in bulk. +""" from __future__ import absolute_import, division, print_function -__metaclass__ = type +__metaclass__ = type # pylint: disable=invalid-name __author__ = "Allen Robel" import copy import inspect import json import logging +from typing import Any, Literal -from ..common.api.v1.lan_fabric.rest.control.fabrics.fabrics import \ - EpFabricUpdate +from ..common.api.v1.lan_fabric.rest.control.fabrics.fabrics import EpFabricUpdate from ..common.exceptions import ControllerResponseError -from .common import FabricCommon +from ..common.operation_type import OperationType +from ..common.rest_send_v2 import RestSend +from ..common.results_v2 import Results +from .common_v2 import FabricCommon +from .fabric_details_v3 import FabricDetailsByName +from .fabric_summary_v2 import FabricSummary from .fabric_types import FabricTypes class FabricUpdateCommon(FabricCommon): """ + # Summary + Common methods and properties for: + - FabricUpdate - FabricUpdateBulk """ - def __init__(self): + def __init__(self) -> None: super().__init__() - self.class_name = self.__class__.__name__ - self.action = "fabric_update" + self.class_name: str = self.__class__.__name__ + self.action: str = "fabric_update" - self.log = logging.getLogger(f"dcnm.{self.class_name}") + self.log: logging.Logger = logging.getLogger(f"dcnm.{self.class_name}") - self.ep_fabric_update = EpFabricUpdate() - self.fabric_types = FabricTypes() + self._ep_fabric_update: EpFabricUpdate = EpFabricUpdate() + self._fabric_details_by_name: FabricDetailsByName = FabricDetailsByName() + self._fabric_summary: FabricSummary = FabricSummary() + self.fabric_types: FabricTypes = FabricTypes() msg = "ENTERED FabricUpdateCommon()" self.log.debug(msg) - def _fabric_needs_update_for_merged_state(self, payload): + def _fabric_needs_update_for_merged_state(self, payload: dict[str, Any]) -> None: """ - Add True to self._fabric_update_required set() if the fabric needs to be updated for merged state. @@ -69,13 +81,16 @@ def _fabric_needs_update_for_merged_state(self, payload): - We've already verified that the fabric exists on the controller in ``_build_payloads_for_merged_state()``. """ - method_name = inspect.stack()[0][3] + method_name: str = inspect.stack()[0][3] - fabric_name = payload.get("FABRIC_NAME", None) + fabric_name: str = payload.get("FABRIC_NAME", "") self._fabric_changes_payload[fabric_name] = {} - nv_pairs = self.fabric_details.all_data[fabric_name].get("nvPairs", {}) + nv_pairs: dict[str, Any] = self._fabric_details_by_name.all_data[fabric_name].get("nvPairs", {}) + key: str = "" + payload_value: Any + payload_key: str for payload_key, payload_value in payload.items(): # Translate payload keys to equivilent keys on the controller # if necessary. This handles cases where the controller key @@ -92,33 +107,28 @@ def _fabric_needs_update_for_merged_state(self, payload): if key == "FABRIC_TYPE": continue - # self._key_translations returns None for any keys that would not - # be found in the controller configuration (e.g. DEPLOY). - # Skip these keys. - if key is None: + if not key: continue # If a key is in the payload that is not in the fabric # 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} - self.results.failed = True - self.results.changed = False - self.results.failed_result["msg"] = ( + self._results.diff_current = {} + self._results.result_current = {"success": False, "changed": False} + self._results.add_failed(True) + self._results.add_changed(False) + self._results.failed_result["msg"] = ( f"Key {key} not found in fabric configuration for " f"fabric {fabric_name}" ) - self.results.register_task_result() + self._results.register_task_result() msg = f"{self.class_name}.{method_name}: " msg += f"Invalid key: {key} found in payload for " 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)}" @@ -174,12 +184,13 @@ def _build_payloads_for_merged_state(self): - self._fabric_needs_update_for_merged_state() may remove payload key/values that would not change the controller configuration. """ - self.fabric_details.refresh() + self._fabric_details_by_name.rest_send = self._rest_send + self._fabric_details_by_name.refresh() self._payloads_to_commit = [] for payload in self.payloads: fabric_name = payload.get("FABRIC_NAME", None) - if fabric_name not in self.fabric_details.all_data: + if fabric_name not in self._fabric_details_by_name.all_data: continue self._fabric_update_required = set() @@ -222,8 +233,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: + if True in self._results.failed: return for payload in self._payloads_to_commit: @@ -233,9 +243,8 @@ def _send_payloads(self): raise ValueError(error) from error # Skip config-deploy if prior actions encountered errors. - if True in self.results.failed: + if True in self._results.failed: return - # pylint: enable=no-member for payload in self._payloads_to_commit: try: @@ -249,7 +258,7 @@ def _set_fabric_update_endpoint(self, payload): - raise ``ValueError`` if the enpoint assignment fails """ try: - self.ep_fabric_update.fabric_name = payload.get("FABRIC_NAME") + self._ep_fabric_update.fabric_name = payload.get("FABRIC_NAME") except ValueError as error: raise ValueError(error) from error @@ -261,20 +270,20 @@ def _set_fabric_update_endpoint(self, payload): raise ValueError(error) from error try: - self.ep_fabric_update.template_name = self.fabric_types.template_name + self._ep_fabric_update.template_name = self.fabric_types.template_name except ValueError as error: raise ValueError(error) from error payload.pop("FABRIC_TYPE", None) - self.path = self.ep_fabric_update.path - self.verb = self.ep_fabric_update.verb + self.path = self._ep_fabric_update.path + self.verb = self._ep_fabric_update.verb def _send_payload(self, payload): """ - Send one fabric update payload - raise ``ValueError`` if the endpoint assignment fails """ - method_name = inspect.stack()[0][3] + method_name: str = inspect.stack()[0][3] try: self._set_fabric_update_endpoint(payload) @@ -289,30 +298,29 @@ 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 - self.rest_send.payload = payload - self.rest_send.commit() - - if self.rest_send.result_current["success"] is False: - self.results.diff_current = {} + self._rest_send.timeout = 1 + self._rest_send.path = self.path + self._rest_send.verb = self.verb + self._rest_send.payload = payload + self._rest_send.commit() + + if self._rest_send.result_current["success"] is False: + self._results.diff_current = {} else: - self.results.diff_current = copy.deepcopy(payload) + self._results.diff_current = copy.deepcopy(payload) self.send_payload_result[payload["FABRIC_NAME"]] = ( - self.rest_send.result_current["success"] + self._rest_send.result_current["success"] ) - self.results.action = self.action - 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() + self._results.action = self.action + 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() @property - def payloads(self): + def payloads(self) -> list[dict[str, Any]]: """ Payloads must be a ``list`` of ``dict`` of payloads for the ``fabric_update`` endpoint. @@ -325,8 +333,8 @@ def payloads(self): return self._payloads @payloads.setter - def payloads(self, value): - method_name = inspect.stack()[0][3] + def payloads(self, value: list[dict[str, Any]]) -> None: + method_name: str = inspect.stack()[0][3] if not isinstance(value, list): msg = f"{self.class_name}.{method_name}: " msg += "payloads must be a list of dict. " @@ -343,13 +351,15 @@ def payloads(self, value): class FabricUpdateBulk(FabricUpdateCommon): """ + # Summary + Update fabrics in bulk. - Usage: - from ansible_collections.cisco.dcnm.plugins.module_utils.fabric.update import \ - FabricUpdateBulk - from ansible_collections.cisco.dcnm.plugins.module_utils.common.results import \ - Results + ##vUsage + + ```python + from ansible_collections.cisco.dcnm.plugins.module_utils.fabric.update import FabricUpdateBulk + from ansible_collections.cisco.dcnm.plugins.module_utils.common.results_v2 import Results payloads = [ { "FABRIC_NAME": "fabric1", "BGP_AS": 65000, "DEPLOY": True }, @@ -376,54 +386,57 @@ class FabricUpdateBulk(FabricUpdateCommon): msg = "Fabric update(s) failed." ansible_module.fail_json(msg, **task.results.final_result) ansible_module.exit_json(**task.results.final_result) + ``` """ def __init__(self): super().__init__() self.class_name = self.__class__.__name__ + self.action: str = "fabric_update_bulk" + + self.log: logging.Logger = logging.getLogger(f"dcnm.{self.class_name}") + + self._rest_send: RestSend = RestSend({}) - self.log = logging.getLogger(f"dcnm.{self.class_name}") - self._payloads = None + self._results: Results = Results() + self._results.action = self.action + self._results.operation_type = OperationType.UPDATE + self._payloads: list[dict] = [] msg = "ENTERED FabricUpdateBulk()" self.log.debug(msg) def commit(self): """ + # Summary + - Update fabrics and register results. - Return if there are no fabrics to update for merged state. - - raise ``ValueError`` if ``fabric_details`` is not set - - raise ``ValueError`` if ``fabric_summary`` is not set - - raise ``ValueError`` if ``payloads`` is not set - - raise ``ValueError`` if ``rest_send`` is not set - - raise ``ValueError`` if ``_build_payloads_for_merged_state`` fails - - raise ``ValueError`` if ``_send_payloads`` fails - """ - method_name = inspect.stack()[0][3] - 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_summary is None: - msg = f"{self.class_name}.{method_name}: " - msg += "fabric_summary must be set prior to calling commit." - raise ValueError(msg) + ## Raises + + ### ValueError + + - `payloads` is not set + - `rest_send` is not set + - `_build_payloads_for_merged_state` fails + - `_send_payloads` fails + """ + method_name: str = inspect.stack()[0][3] - if self.payloads is None: + if not self.payloads: msg = f"{self.class_name}.{method_name}: " msg += "payloads must be set prior to calling commit." raise ValueError(msg) - # pylint: disable=no-member - if self.rest_send is None: + if not self._rest_send.params: 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.rest_send.check_mode - self.results.state = self.rest_send.state + self._results.action = self.action + self._results.check_mode = self._rest_send.check_mode + self._results.state = self._rest_send.state try: self._build_payloads_for_merged_state() @@ -431,24 +444,103 @@ def commit(self): raise ValueError(error) from error if len(self._payloads_to_commit) == 0: - self.results.diff_current = {} - self.results.result_current = {"success": True, "changed": False} + self._results.diff_current = {} + self._results.result_current = {"success": True, "changed": False} msg = "No fabrics to update for merged state." - self.results.response_current = {"RETURN_CODE": 200, "MESSAGE": msg} - self.results.register_task_result() + self._results.response_current = {"RETURN_CODE": 200, "MESSAGE": msg} + self._results.register_task_result() return try: self._send_payloads() except ValueError as error: - self.results.diff_current = {} - self.results.result_current = {"success": False, "changed": False} - return_code = self.rest_send.response_current.get("RETURN_CODE", None) - msg = f"ValueError self.results.response: {self.results.response}" + self._results.diff_current = {} + self._results.result_current = {"success": False, "changed": False} + return_code = self._rest_send.response_current.get("RETURN_CODE", None) + msg = f"ValueError self._results.response: {self._results.response}" self.log.debug(msg) - self.results.response_current = { + self._results.response_current = { "RETURN_CODE": f"{return_code}", "MESSAGE": f"{error}", } - self.results.register_task_result() + self._results.register_task_result() raise ValueError(error) from error + + @property + def rest_send(self) -> RestSend: + """ + # Summary + + An instance of the RestSend class. + + ## Raises + + - setter: `TypeError` if the value is not an instance of RestSend. + - setter: `ValueError` if RestSend.params is not set. + + ## getter + + Return an instance of the RestSend class. + + ## setter + + Set an instance of the RestSend class. + """ + return self._rest_send + + @rest_send.setter + def rest_send(self, value: RestSend) -> None: + method_name: str = inspect.stack()[0][3] + _class_have: str = "" + _class_need: Literal["RestSend"] = "RestSend" + msg = f"{self.class_name}.{method_name}: " + msg += f"value must be an instance of {_class_need}. " + msg += f"Got value {value} of type {type(value).__name__}." + try: + _class_have = value.class_name + except AttributeError as error: + msg += f" Error detail: {error}." + raise TypeError(msg) from error + if _class_have != _class_need: + raise TypeError(msg) + self._rest_send = value + + @property + def results(self) -> Results: + """ + # Summary + + An instance of the Results class. + + ## Raises + + - setter: `TypeError` if the value is not an instance of Results. + + ## getter + + Return an instance of the Results class. + + ## setter + + Set an instance of the Results class. + """ + return self._results + + @results.setter + def results(self, value: Results) -> None: + method_name: str = inspect.stack()[0][3] + _class_have: str = "" + _class_need: Literal["Results"] = "Results" + msg = f"{self.class_name}.{method_name}: " + msg += f"value must be an instance of {_class_need}. " + msg += f"Got value {value} of type {type(value).__name__}." + try: + _class_have = value.class_name + except AttributeError as error: + msg += f" Error detail: {error}." + raise TypeError(msg) from error + if _class_have != _class_need: + raise TypeError(msg) + self._results = value + self._results.action = self.action + self._results.operation_type = OperationType.UPDATE diff --git a/plugins/modules/dcnm_fabric.py b/plugins/modules/dcnm_fabric.py index ac3c2d93c..5c6a0470b 100644 --- a/plugins/modules/dcnm_fabric.py +++ b/plugins/modules/dcnm_fabric.py @@ -13,10 +13,13 @@ # 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=too-many-lines +""" +Manage creation and configuration of NDFC fabrics +""" from __future__ import absolute_import, division, print_function -__metaclass__ = type +__metaclass__ = type # pylint: disable=invalid-name __author__ = "Allen Robel" DOCUMENTATION = """ @@ -3720,23 +3723,23 @@ import inspect import json import logging +from typing import Any, Literal from ansible.module_utils.basic import AnsibleModule -from ..module_utils.common.controller_features import ControllerFeatures -from ..module_utils.common.controller_version import ControllerVersion +from ..module_utils.common.controller_features_v2 import ControllerFeatures +from ..module_utils.common.controller_version_v2 import ControllerVersion from ..module_utils.common.exceptions import ControllerResponseError from ..module_utils.common.log_v2 import Log -from ..module_utils.common.properties import Properties from ..module_utils.common.response_handler import ResponseHandler from ..module_utils.common.rest_send_v2 import RestSend -from ..module_utils.common.results import Results +from ..module_utils.common.results_v2 import Results from ..module_utils.common.sender_dcnm import Sender -from ..module_utils.fabric.common import FabricCommon +from ..module_utils.fabric.common_v2 import FabricCommon from ..module_utils.fabric.create import FabricCreateBulk from ..module_utils.fabric.delete import FabricDelete -from ..module_utils.fabric.fabric_details_v2 import FabricDetailsByName -from ..module_utils.fabric.fabric_summary import FabricSummary +from ..module_utils.fabric.fabric_details_v3 import FabricDetailsByName +from ..module_utils.fabric.fabric_summary_v2 import FabricSummary from ..module_utils.fabric.fabric_types import FabricTypes from ..module_utils.fabric.query import FabricQuery from ..module_utils.fabric.replaced import FabricReplacedBulk @@ -3745,48 +3748,46 @@ from ..module_utils.fabric.verify_playbook_params import VerifyPlaybookParams -def json_pretty(msg): +def json_pretty(msg) -> str: """ Return a pretty-printed JSON string for logging messages """ return json.dumps(msg, indent=4, sort_keys=True) -@Properties.add_rest_send class Common(FabricCommon): """ Common methods, properties, and resources for all states. """ - def __init__(self, params): - self.class_name = self.__class__.__name__ - self.log = logging.getLogger(f"dcnm.{self.class_name}") + def __init__(self, params: dict[str, Any]) -> None: + self.class_name: str = self.__class__.__name__ + self.log: logging.Logger = logging.getLogger(f"dcnm.{self.class_name}") super().__init__() - method_name = inspect.stack()[0][3] # pylint: disable=unused-variable + method_name: str = inspect.stack()[0][3] # pylint: disable=unused-variable - self.controller_features = ControllerFeatures() - self.controller_version = ControllerVersion() - self.features = {} - self._implemented_states = set() + self.controller_features: ControllerFeatures = ControllerFeatures() + self.controller_version: ControllerVersion = ControllerVersion() + self.features: dict = {} + self._implemented_states: set = set() - self.params = params + self.params: dict[str, Any] = params # populated in self.validate_input() - self.payloads = {} + self.payloads: dict[str, Any] = {} 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._results: Results = Results() + self._results.state = self.state + self._results.check_mode = self.check_mode + self._rest_send: RestSend = RestSend({}) + self._verify_playbook_params: VerifyPlaybookParams = VerifyPlaybookParams() + self.have: FabricDetailsByName = FabricDetailsByName() self.query = [] self.validated = [] - self.want = [] + self.want: list[dict[str, Any]] = [] msg = "ENTERED Common(): " msg += f"state: {self.state}, " @@ -3801,12 +3802,16 @@ def populate_check_mode(self): ### 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: + method_name: str = inspect.stack()[0][3] + if self.params.get("check_mode") is None: msg = f"{self.class_name}.{method_name}: " msg += "check_mode is required." raise ValueError(msg) + if not isinstance(self.params["check_mode"], bool): + msg = f"{self.class_name}.{method_name}: " + msg += "check_mode must be a boolean." + raise ValueError(msg) + self.check_mode: bool = self.params["check_mode"] def populate_config(self): """ @@ -3818,19 +3823,20 @@ def populate_config(self): - ``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) + method_name: str = inspect.stack()[0][3] + states_requiring_config = {"deleted", "merged", "replaced"} + self.config: list[dict[str, Any]] = [] if self.state in states_requiring_config: - if self.config is None: + if self.params.get("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): + if not isinstance(self.params["config"], list): msg = f"{self.class_name}.{method_name}: " msg += "expected list type for self.config. " - msg += f"got {type(self.config).__name__}" + msg += f"got {type(self.params['config']).__name__}" raise ValueError(msg) + self.config = self.params["config"] def populate_state(self): """ @@ -3842,7 +3848,7 @@ def populate_state(self): - ``state`` is not provided. - ``state`` is not a valid state. """ - method_name = inspect.stack()[0][3] + method_name: str = inspect.stack()[0][3] valid_states = ["deleted", "merged", "query", "replaced"] @@ -3859,35 +3865,22 @@ def populate_state(self): def get_have(self): """ - ### Summary - Build ``self.have``, which is a dict containing the current controller - fabrics and their details. + # Summary - ### Raises - - ``ValueError`` if the controller returns an error when attempting to - retrieve the fabric details. - - ### have structure + Instantiate and refresh `self.have` to retrieve 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 - ```python - have = { - "fabric_name": "fabric_name", - "fabric_config": { - "fabricName": "fabric_name", - "fabricType": "VXLAN EVPN", - "etc...": "etc..." - } - } - ``` + ### ValueError + + - The controller returns an error when attempting to retrieve the fabric details. """ - method_name = inspect.stack()[0][3] # pylint: disable=unused-variable + method_name: str = inspect.stack()[0][3] # pylint: disable=unused-variable try: self.have = FabricDetailsByName() - self.have.rest_send = self.rest_send + self.have.rest_send = self._rest_send self.have.results = Results() self.have.refresh() except ValueError as error: @@ -3906,8 +3899,8 @@ def get_want(self) -> None: ### Raises - ``ValueError`` if the playbook configs are invalid. """ - method_name = inspect.stack()[0][3] # pylint: disable=unused-variable - merged_configs = [] + method_name: str = inspect.stack()[0][3] # pylint: disable=unused-variable + merged_configs: list[dict[str, Any]] = [] for config in self.config: try: self._verify_payload(config) @@ -3935,9 +3928,9 @@ def get_controller_features(self) -> None: - ``ValueError`` if the controller returns an error when attempting to retrieve the controller features. """ - method_name = inspect.stack()[0][3] + method_name: str = inspect.stack()[0][3] self.features = {} - self.controller_features.rest_send = self.rest_send + self.controller_features.rest_send = self._rest_send try: self.controller_features.refresh() except ControllerResponseError as error: @@ -3962,9 +3955,9 @@ def get_controller_version(self): - ``ValueError`` if the controller returns an error when attempting to retrieve the controller version. """ - method_name = inspect.stack()[0][3] + method_name: str = inspect.stack()[0][3] try: - self.controller_version.rest_send = self.rest_send + self.controller_version.rest_send = self._rest_send self.controller_version.refresh() except (ControllerResponseError, ValueError) as error: msg = f"{self.class_name}.{method_name}: " @@ -3973,6 +3966,86 @@ def get_controller_version(self): msg += f"Error detail: {error}" raise ValueError(msg) from error + @property + def rest_send(self) -> RestSend: + """ + # Summary + + An instance of the RestSend class. + + ## Raises + + - setter: `TypeError` if the value is not an instance of RestSend. + - setter: `ValueError` if RestSend.params is not set. + + ## getter + + Return an instance of the RestSend class. + + ## setter + + Set an instance of the RestSend class. + """ + return self._rest_send + + @rest_send.setter + def rest_send(self, value: RestSend) -> None: + method_name: str = inspect.stack()[0][3] + _class_have: str = "" + _class_need: Literal["RestSend"] = "RestSend" + msg = f"{self.class_name}.{method_name}: " + msg += f"value must be an instance of {_class_need}. " + msg += f"Got value {value} of type {type(value).__name__}." + try: + _class_have = value.class_name + except AttributeError as error: + msg += f" Error detail: {error}." + raise TypeError(msg) from error + if _class_have != _class_need: + raise TypeError(msg) + if not value.params: + msg = f"{self.class_name}.{method_name}: " + msg += "RestSend.params must be set." + raise ValueError(msg) + self._rest_send = value + + @property + def results(self) -> Results: + """ + # Summary + + An instance of the Results class. + + ## Raises + + - setter: `TypeError` if the value is not an instance of Results. + + ## getter + + Return an instance of the Results class. + + ## setter + + Set an instance of the Results class. + """ + return self._results + + @results.setter + def results(self, value: Results) -> None: + method_name: str = inspect.stack()[0][3] + _class_have: str = "" + _class_need: Literal["Results"] = "Results" + msg = f"{self.class_name}.{method_name}: " + msg += f"value must be an instance of {_class_need}. " + msg += f"Got value {value} of type {type(value).__name__}." + try: + _class_have = value.class_name + except AttributeError as error: + msg += f" Error detail: {error}." + raise TypeError(msg) from error + if _class_have != _class_need: + raise TypeError(msg) + self._results = value class Deleted(Common): """ @@ -3991,8 +4064,8 @@ def __init__(self, params): self.log = logging.getLogger(f"dcnm.{self.class_name}") msg = "ENTERED Deleted(): " - msg += f"state: {self.results.state}, " - msg += f"check_mode: {self.results.check_mode}" + msg += f"state: {self._results.state}, " + msg += f"check_mode: {self._results.check_mode}" self.log.debug(msg) def commit(self) -> None: @@ -4006,23 +4079,13 @@ def commit(self) -> None: delete the fabrics. """ self.get_want() - method_name = inspect.stack()[0][3] + method_name: str = inspect.stack()[0][3] msg = f"ENTERED: {self.class_name}.{method_name}" self.log.debug(msg) - self.fabric_details = FabricDetailsByName() - self.fabric_details.rest_send = self.rest_send - self.fabric_details.results = Results() - - self.fabric_summary = FabricSummary() - self.fabric_summary.rest_send = self.rest_send - self.fabric_summary.results = 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 + self.delete.rest_send = self._rest_send + self.delete.results = self._results fabric_names_to_delete = [] for want in self.want: @@ -4060,47 +4123,45 @@ class Merged(Common): the fabric. """ - def __init__(self, params): - self.class_name = self.__class__.__name__ - super().__init__(params) - method_name = inspect.stack()[0][3] # pylint: disable=unused-variable + def __init__(self, params: dict[str, Any]) -> None: + self.class_name: str = self.__class__.__name__ + super().__init__(params=params) + method_name: str = inspect.stack()[0][3] # pylint: disable=unused-variable - self.action = "fabric_create" - self.log = logging.getLogger(f"dcnm.{self.class_name}") + self.action: str = "fabric_create" + self.log: logging.Logger = logging.getLogger(f"dcnm.{self.class_name}") - self.fabric_details = FabricDetailsByName() - self.fabric_summary = FabricSummary() self.fabric_create = FabricCreateBulk() self.fabric_types = FabricTypes() self.fabric_update = FabricUpdateBulk() - self.template = TemplateGet() + self.template_get: TemplateGet = TemplateGet() msg = f"ENTERED Merged.{method_name}: " msg += f"state: {self.state}, " msg += f"check_mode: {self.check_mode}" self.log.debug(msg) - self.need_create = [] - self.need_update = [] + self.need_create: list[dict[str, Any]] = [] + self.need_update: list[dict[str, Any]] = [] self._implemented_states.add("merged") def get_need(self): """ - ### Summary - Build ``self.need`` for merged state. + # Summary - ### 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. + Build `self.need` for merged state. + + ## Raises + + ### ValueError + + - 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] + method_name: str = inspect.stack()[0][3] self.payloads = {} for want in self.want: @@ -4139,15 +4200,15 @@ def get_need(self): raise ValueError(f"{error}") from error try: - template_name = self.fabric_types.template_name + template_name: str = self.fabric_types.template_name except ValueError as error: raise ValueError(f"{error}") from error - self.template.rest_send = self.rest_send - self.template.template_name = template_name + self.template_get.rest_send = self._rest_send + self.template_get.template_name = template_name try: - self.template.refresh() + self.template_get.refresh() except ValueError as error: raise ValueError(f"{error}") from error except ControllerResponseError as error: @@ -4158,7 +4219,7 @@ def get_need(self): raise ValueError(msg) from error try: - self._verify_playbook_params.template = self.template.template + self._verify_playbook_params.template = self.template_get.template except TypeError as error: raise ValueError(f"{error}") from error @@ -4223,18 +4284,11 @@ def commit(self): the fabric. - The controller returns an error when attempting to update """ - method_name = inspect.stack()[0][3] # pylint: disable=unused-variable + method_name: str = inspect.stack()[0][3] # pylint: disable=unused-variable msg = f"{self.class_name}.{method_name}: entered" self.log.debug(msg) self.get_controller_version() - - 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() @@ -4254,7 +4308,7 @@ def send_need_create(self) -> None: - The controller returns an error when attempting to create the fabric. """ - method_name = inspect.stack()[0][3] # pylint: disable=unused-variable + method_name: str = inspect.stack()[0][3] # pylint: disable=unused-variable msg = f"{self.class_name}.{method_name}: entered. " msg += f"self.need_create: {json_pretty(self.need_create)}" self.log.debug(msg) @@ -4266,8 +4320,8 @@ def send_need_create(self) -> None: return self.fabric_create.fabric_details = self.fabric_details - self.fabric_create.rest_send = self.rest_send - self.fabric_create.results = self.results + self.fabric_create.rest_send = self._rest_send + self.fabric_create.results = self._results try: self.fabric_create.payloads = self.need_create @@ -4291,7 +4345,7 @@ def send_need_update(self) -> None: - The controller returns an error when attempting to update the fabric. """ - method_name = inspect.stack()[0][3] # pylint: disable=unused-variable + method_name: str = inspect.stack()[0][3] # pylint: disable=unused-variable msg = f"{self.class_name}.{method_name}: entered. " msg += "self.need_update: " msg += f"{json_pretty(self.need_update)}" @@ -4303,10 +4357,8 @@ def send_need_update(self) -> None: self.log.debug(msg) return - self.fabric_update.fabric_details = self.fabric_details - self.fabric_update.fabric_summary = self.fabric_summary - self.fabric_update.rest_send = self.rest_send - self.fabric_update.results = self.results + self.fabric_update.rest_send = self._rest_send + self.fabric_update.results = self._results try: self.fabric_update.payloads = self.need_update @@ -4359,15 +4411,14 @@ def commit(self) -> None: query the fabrics. """ self.fabric_details = FabricDetailsByName() - self.fabric_details.rest_send = self.rest_send + self.fabric_details.rest_send = self._rest_send self.fabric_details.results = Results() self.get_want() fabric_query = FabricQuery() - fabric_query.fabric_details = self.fabric_details - fabric_query.rest_send = self.rest_send - fabric_query.results = self.results + fabric_query.rest_send = self._rest_send + fabric_query.results = self._results fabric_names_to_query = [] for want in self.want: @@ -4406,7 +4457,7 @@ class Replaced(Common): def __init__(self, params): self.class_name = self.__class__.__name__ super().__init__(params) - method_name = inspect.stack()[0][3] # pylint: disable=unused-variable + method_name: str = inspect.stack()[0][3] # pylint: disable=unused-variable self.action = "fabric_replaced" self.log = logging.getLogger(f"dcnm.{self.class_name}") @@ -4418,7 +4469,7 @@ def __init__(self, params): self.merged = None self.need_create = [] self.need_replaced = [] - self.template = TemplateGet() + self.template_get: TemplateGet = TemplateGet() self._implemented_states.add("replaced") msg = f"ENTERED Replaced.{method_name}: " @@ -4436,7 +4487,7 @@ def get_need(self): - The controller features required for the fabric type are not running on the controller. """ - method_name = inspect.stack()[0][3] + method_name: str = inspect.stack()[0][3] self.payloads = {} for want in self.want: @@ -4480,14 +4531,14 @@ def commit(self): - The controller features required for the fabric type are not running on the controller. """ - method_name = inspect.stack()[0][3] + method_name: str = inspect.stack()[0][3] msg = f"{self.class_name}.{method_name}: entered" self.log.debug(msg) self.get_controller_version() - self.fabric_details.rest_send = self.rest_send - self.fabric_summary.rest_send = self.rest_send + 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() @@ -4511,7 +4562,7 @@ def send_need_replaced(self) -> None: - The controller returns an error when attempting to update the fabric. """ - method_name = inspect.stack()[0][3] # pylint: disable=unused-variable + method_name: str = inspect.stack()[0][3] # pylint: disable=unused-variable msg = f"{self.class_name}.{method_name}: entered. " msg += "self.need_replaced: " msg += f"{json_pretty(self.need_replaced)}" @@ -4519,10 +4570,10 @@ def send_need_replaced(self) -> None: if len(self.need_create) != 0: self.merged = Merged(self.params) - 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 - self.merged.results = self.results + 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 + self.merged.results = self._results self.merged.need_create = self.need_create self.merged.send_need_create() @@ -4534,8 +4585,8 @@ 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 = self.rest_send - self.fabric_replaced.results = self.results + self.fabric_replaced.rest_send = self._rest_send + self.fabric_replaced.results = self._results try: self.fabric_replaced.payloads = self.need_replaced @@ -4610,6 +4661,9 @@ def main(): task.rest_send = rest_send task.commit() except ValueError as error: + if task is None: + msg = f"Invalid state: {params['state']}" + ansible_module.fail_json(msg) ansible_module.fail_json(f"{error}", **task.results.failed_result) task.results.build_final_result() diff --git a/tests/unit/modules/dcnm/dcnm_fabric/fixtures/payloads_FabricCommon_V2.json b/tests/unit/modules/dcnm/dcnm_fabric/fixtures/payloads_FabricCommon_V2.json new file mode 100644 index 000000000..ef8f42577 --- /dev/null +++ b/tests/unit/modules/dcnm/dcnm_fabric/fixtures/payloads_FabricCommon_V2.json @@ -0,0 +1,40 @@ +{ + "TEST_NOTES": [ + "Mocked payloads for FabricCommon unit tests.", + "tests/unit/modules/dcnm/dcnm_fabric/test_fabric_common.py" + ], + "test_fabric_common_v2_00020a": [ + { + "TEST_NOTES": [ + "ANYCAST_GW_MAC is malformed." + ], + "BGP_AS": 65001, + "DEPLOY": true, + "FABRIC_NAME": "f1", + "FABRIC_TYPE": "VXLAN_EVPN", + "ANYCAST_GW_MAC": "00.54" + } + ], + "test_fabric_common_v2_00021a": [ + { + "TEST_NOTES": [ + "BGP_AS is malformed." + ], + "BGP_AS": "65001.65536", + "DEPLOY": true, + "FABRIC_NAME": "f1", + "FABRIC_TYPE": "VXLAN_EVPN" + } + ], + "test_fabric_common_v2_00100a": "NOT_A_DICT", + "test_fabric_common_v2_00110a": { + "TEST_NOTES": [ + "All mandatory keys are present.", + "Mandatory keys are pop()'ed in unit test." + ], + "BGP_AS": 65000, + "DEPLOY": true, + "FABRIC_NAME": "f1", + "FABRIC_TYPE": "VXLAN_EVPN" + } +} diff --git a/tests/unit/modules/dcnm/dcnm_fabric/test_fabric_common_v2.py b/tests/unit/modules/dcnm/dcnm_fabric/test_fabric_common_v2.py new file mode 100644 index 000000000..25bd7a16f --- /dev/null +++ b/tests/unit/modules/dcnm/dcnm_fabric/test_fabric_common_v2.py @@ -0,0 +1,432 @@ +# Copyright (c) 2025 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 +""" +Unit tests for FabricCommon V2 class in module_utils/fabric/common_v2.py +""" +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +__copyright__ = "Copyright (c) 2025 Cisco and/or its affiliates." +__author__ = "Allen Robel" + +import inspect + +import pytest +from ansible_collections.cisco.dcnm.plugins.module_utils.common.results_v2 import Results +from ansible_collections.cisco.dcnm.tests.unit.modules.dcnm.dcnm_fabric.utils import ( + does_not_raise, + fabric_common_v2_fixture, + payloads_fabric_common_v2, + responses_fabric_common, +) + + +def test_fabric_common_v2_00010(fabric_common_v2) -> None: + """ + Classes and Methods + - FabricCommon + - __init__() + - FabricUpdateBulk + - __init__() + + Summary + - Verify the class attributes are initialized to expected values. + + Test + - Class attributes are initialized to expected values + - ``ValueError`` is not called + """ + with does_not_raise(): + instance = fabric_common_v2 + assert instance.class_name == "FabricCommon" + + assert instance._fabric_details.class_name == "FabricDetailsByName" + assert instance._fabric_summary.class_name == "FabricSummary" + assert instance._fabric_type == "VXLAN_EVPN" + assert instance._rest_send.params == {} + assert instance._results.class_name == "Results" + + +def test_fabric_common_v2_00020(fabric_common_v2) -> None: + """ + Classes and Methods + - FabricCommon + - __init__() + - _fixup_payloads_to_commit() + - _fixup_anycast_gw_mac() + + Summary + - Verify ``ValueError`` is raised when ANYCAST_GW_MAC is malformed. + """ + method_name = inspect.stack()[0][3] + key = f"{method_name}a" + with does_not_raise(): + instance = fabric_common_v2 + instance.results = Results() + instance._payloads_to_commit = payloads_fabric_common_v2(key) + match = r"FabricCommon\._fixup_anycast_gw_mac: " + match += "Error translating ANYCAST_GW_MAC for fabric f1, " + match += r"ANYCAST_GW_MAC: 00\.54, Error detail: Invalid MAC address" + with pytest.raises(ValueError, match=match): + instance._fixup_payloads_to_commit() + + +MATCH_00021a = r"FabricCommon\._fixup_bgp_as:\s+" +MATCH_00021a += r"Invalid BGP_AS .* for fabric f1,\s+" +MATCH_00021a += r"Error detail: BGP ASN .* failed regex validation\." + +MATCH_00021b = r"FabricCommon\._fixup_bgp_as:\s+" +MATCH_00021b += r"Invalid BGP_AS .* for fabric f1,\s+" +MATCH_00021b += r"Error detail: BGP ASN \(.*\) cannot be type float\(\)\s+" +MATCH_00021b += r"due to loss of trailing zeros\.\s+" +MATCH_00021b += r"Use a string or integer instead\." + + +@pytest.mark.parametrize( + "bgp_as, expected", + [ + ("65001.65535", does_not_raise()), + ("65001.0", does_not_raise()), + ("65001", does_not_raise()), + (65001, does_not_raise()), + (4294967295, does_not_raise()), + (0, pytest.raises(ValueError, match=MATCH_00021a)), + (4294967296, pytest.raises(ValueError, match=MATCH_00021a)), + ("FOOBAR", pytest.raises(ValueError, match=MATCH_00021a)), + ("65001.65536", pytest.raises(ValueError, match=MATCH_00021a)), + ("65001:65000", pytest.raises(ValueError, match=MATCH_00021a)), + (65001.65000, pytest.raises(ValueError, match=MATCH_00021b)), + ], +) +def test_fabric_common_v2_00021(fabric_common_v2, bgp_as, expected) -> None: + """ + Classes and Methods + - FabricCommon + - __init__() + - _fixup_payloads_to_commit() + - _fixup_bgp_as() + + Summary + - Verify ``ValueError`` is raised when BGP_AS fails regex validation. + """ + with does_not_raise(): + instance = fabric_common_v2 + instance.results = Results() + instance._payloads_to_commit = [{"BGP_AS": bgp_as, "FABRIC_NAME": "f1", "FABRIC_TYPE": "VXLAN_EVPN"}] + with expected: + instance._fixup_payloads_to_commit() + + +@pytest.mark.parametrize( + "value, expected_return_value", + [ + ("", None), + ("null", None), + ("Null", None), + ("NULL", None), + ("none", None), + ("None", None), + ("NONE", None), + (None, None), + (10, 10), + ({"foo": "bar"}, {"foo": "bar"}), + (["foo", "bar"], ["foo", "bar"]), + (101.4, 101.4), + ], +) +def test_fabric_common_v2_00070(fabric_common_v2, value, expected_return_value) -> None: + """ + Classes and Methods + - ConversionUtils + - make_none() + - FabricCommon + - __init__() + + Summary + - Verify FabricCommon().conversion.make_none returns expected values. + """ + with does_not_raise(): + instance = fabric_common_v2 + return_value = instance._conversion.make_none(value) + assert return_value == expected_return_value + + +@pytest.mark.parametrize( + "value, expected_return_value", + [ + (True, True), + ("true", True), + ("TRUE", True), + ("yes", True), + ("YES", True), + (False, False), + ("false", False), + ("FALSE", False), + ("no", False), + ("NO", False), + (10, 10), + ({"foo": "bar"}, {"foo": "bar"}), + (["foo", "bar"], ["foo", "bar"]), + (101.4, 101.4), + ], +) +def test_fabric_common_v2_00080(fabric_common_v2, value, expected_return_value) -> None: + """ + Classes and Methods + - ConversionUtils + - make_boolean() + - FabricCommon + - __init__() + - conversion.make_boolean() + + Summary + - Verify FabricCommon().conversion.make_boolean returns expected values. + """ + with does_not_raise(): + instance = fabric_common_v2 + return_value = instance._conversion.make_boolean(value) + assert return_value == expected_return_value + + +def test_fabric_common_v2_00100(fabric_common_v2) -> None: + """ + Classes and Methods + - FabricCommon + - __init__() + - _verify_payload() + + Summary + - Verify ``ValueError`` is raised when payload is not a `dict``. + """ + method_name = inspect.stack()[0][3] + key = f"{method_name}a" + + payload = payloads_fabric_common_v2(key) + + with does_not_raise(): + instance = fabric_common_v2 + + match = r"FabricCommon\._verify_payload:\s+" + 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) + + +@pytest.mark.parametrize( + "mandatory_key", + [ + "BGP_AS", + "FABRIC_NAME", + "FABRIC_TYPE", + ], +) +def test_fabric_common_v2_00110(fabric_common_v2, mandatory_key) -> None: + """ + Classes and Methods + - FabricCommon + - __init__() + - _verify_payload() + + Summary + - Verify ``ValueError`` is raised when payload is missing + mandatory parameters. + """ + method_name = inspect.stack()[0][3] + key = f"{method_name}a" + + payload = payloads_fabric_common_v2(key) + + payload.pop(mandatory_key, None) + + with does_not_raise(): + instance = fabric_common_v2 + instance.action = "fabric_create" + + match = r"FabricCommon\._verify_payload:\s+" + match += r"Playbook configuration for fabric .* is missing mandatory\s+" + match += r"parameter.*\." + with pytest.raises(ValueError, match=match): + instance._verify_payload(payload) + + +def test_fabric_common_v2_00111(fabric_common_v2) -> None: + """ + Classes and Methods + - FabricCommon + - __init__() + - _verify_payload() + + Summary + - Verify FabricCommon()_verify_payload() returns if action + is not one of "fabric_create" or "fabric_replace". + + NOTES: + - 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_v2 + instance.action = "foo" + instance._verify_payload("NOT_A_DICT") + + +MATCH_00112a = r"FabricCommon\._verify_payload:\s+" +MATCH_00112a += r"Playbook configuration for fabric .* contains an invalid\s+" +MATCH_00112a += r"FABRIC_NAME\.\s+" +MATCH_00112a += r"Error detail: ConversionUtils\.validate_fabric_name:\s+" +MATCH_00112a += r"Invalid fabric name:\s+.*\.\s+" +MATCH_00112a += r"Fabric name must start with a letter A-Z or a-z and\s+" +MATCH_00112a += r"contain only the characters in: \[A-Z,a-z,0-9,-,_\]\.\s+" +MATCH_00112a += r"Bad configuration:.*" + + +MATCH_00112b = r"FabricCommon\._verify_payload:\s+" +MATCH_00112b += r"Playbook configuration for fabric .* contains an invalid\s+" +MATCH_00112b += r"FABRIC_NAME\.\s+" +MATCH_00112b += r"Error detail: ConversionUtils\.validate_fabric_name:\s+" +MATCH_00112b += r"Invalid fabric name\. Expected string. Got .*\.\s+" +MATCH_00112b += r"Bad configuration:.*" + + +@pytest.mark.parametrize( + "fabric_name, expected", + [ + ("MyFabric", does_not_raise()), + ("My_Fabric", does_not_raise()), + ("My-Fabric-66", does_not_raise()), + (0, pytest.raises(ValueError, match=MATCH_00112b)), + (100.100, pytest.raises(ValueError, match=MATCH_00112b)), + ("10_MyFabric", pytest.raises(ValueError, match=MATCH_00112a)), + ("My:Fabric", pytest.raises(ValueError, match=MATCH_00112a)), + ("My,Fabric", pytest.raises(ValueError, match=MATCH_00112a)), + ("@MyFabric", pytest.raises(ValueError, match=MATCH_00112a)), + ], +) +def test_fabric_common_v2_00112(fabric_common_v2, fabric_name, expected) -> None: + """ + Classes and Methods + - FabricCommon + - __init__() + - _verify_payload() + + Summary + - Verify ``ValueError`` is raised when FABRIC_NAME fails regex validation. + """ + payload = { + "BGP_AS": "65000.100", + "FABRIC_NAME": fabric_name, + "FABRIC_TYPE": "VXLAN_EVPN", + } + + with does_not_raise(): + instance = fabric_common_v2 + instance.action = "fabric_create" + instance.results = Results() + with expected: + instance._verify_payload(payload) + + +MATCH_00113a = r"FabricCommon\._verify_payload:\s+" +MATCH_00113a += r"Playbook configuration for fabric .* contains an invalid\s+" +MATCH_00113a += r"FABRIC_TYPE\s+\(.*\)\.\s+" +MATCH_00113a += r"Valid values for FABRIC_TYPE:\s+" +MATCH_00113a += r"\[.*]\.\s+" +MATCH_00113a += r"Bad configuration:\s+" + + +@pytest.mark.parametrize( + "fabric_type, expected", + [ + ("LAN_CLASSIC", does_not_raise()), + ("VXLAN_EVPN", does_not_raise()), + ("VXLAN_EVPN_MSD", does_not_raise()), + (0, pytest.raises(ValueError, match=MATCH_00113a)), + ("FOOBAR", pytest.raises(ValueError, match=MATCH_00113a)), + ], +) +def test_fabric_common_v2_00113(fabric_common_v2, fabric_type, expected) -> None: + """ + Classes and Methods + - FabricCommon + - __init__() + - _verify_payload() + + Summary + - Verify ``ValueError`` is raised when FABRIC_TYPE is invalid. + """ + payload = { + "BGP_AS": "65000.100", + "FABRIC_NAME": "MyFabric", + "FABRIC_TYPE": fabric_type, + } + + with does_not_raise(): + instance = fabric_common_v2 + instance.action = "fabric_create" + instance.results = Results() + with expected: + instance._verify_payload(payload) + + +MATCH_00120a = r"FabricCommon\.translate_anycast_gw_mac:\s+" +MATCH_00120a += r"Error translating ANYCAST_GW_MAC: for fabric MyFabric,\s+" +MATCH_00120a += r"ANYCAST_GW_MAC: .*, Error detail: Invalid MAC address:\s+.*" + + +@pytest.mark.parametrize( + "mac_in, mac_out, raises, expected", + [ + ("0001aabbccdd", "0001.aabb.ccdd", False, does_not_raise()), + ("00:01:aa:bb:cc:dd", "0001.aabb.ccdd", False, does_not_raise()), + ("00:---01:***aa:b//b:cc:dd", "0001.aabb.ccdd", False, does_not_raise()), + ("00zz.aabb.ccdd", None, True, pytest.raises(ValueError, match=MATCH_00120a)), + ("0001", None, True, pytest.raises(ValueError, match=MATCH_00120a)), + ], +) +def test_fabric_common_v2_00120(fabric_common_v2, mac_in, mac_out, raises, expected) -> None: + """ + Classes and Methods + - FabricCommon() + - __init__() + - translate_anycast_gw_mac() + + Summary + - Verify FabricCommon().translate_anycast_gw_mac() + raises ``ValueError`` if mac_in cannot be translated into a format + expected by the controller. + - Verify the error message when ``ValueError`` is raised. + - Verify ``ValueError`` is not raised when ANYCAST_GW_MAC can be + translated. + """ + with does_not_raise(): + instance = fabric_common_v2 + instance.results = Results() + with expected: + result = instance.translate_anycast_gw_mac("MyFabric", mac_in) + if raises is False: + assert result == mac_out 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 69b7e0026..983cb91f9 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 @@ -1,4 +1,4 @@ -# Copyright (c) 2024 Cisco and/or its affiliates. +# Copyright (c) 2024-2025 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. @@ -21,32 +21,33 @@ # pylint: disable=protected-access # pylint: disable=unused-argument # pylint: disable=invalid-name - +""" +Unit tests for FabricConfigDeploy class in module_utils/fabric/config_deploy_v2.py +""" from __future__ import absolute_import, division, print_function __metaclass__ = type -__copyright__ = "Copyright (c) 2024 Cisco and/or its affiliates." +__copyright__ = "Copyright (c) 2024-2025 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 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 ansible_collections.cisco.dcnm.tests.unit.modules.dcnm.dcnm_fabric.utils import ( - MockAnsibleModule, does_not_raise, fabric_config_deploy_fixture, - fabric_details_by_name_v2_fixture, fabric_summary_fixture, params, - responses_ep_fabric_config_deploy, responses_fabric_details_by_name_v2, - responses_fabric_summary) + MockAnsibleModule, + does_not_raise, + fabric_config_deploy_fixture, + params, + responses_ep_fabric_config_deploy, + responses_fabric_details_by_name_v2, + responses_fabric_summary_v2, +) def test_fabric_config_deploy_00010(fabric_config_deploy) -> None: @@ -64,9 +65,10 @@ def test_fabric_config_deploy_00010(fabric_config_deploy) -> None: assert instance.class_name == "FabricConfigDeploy" assert instance.action == "config_deploy" assert instance.config_deploy_result == {} - assert instance.fabric_name is None - assert instance.rest_send is None - assert instance.results is None + assert instance.fabric_name == "" + assert instance.rest_send.class_name == "RestSend" + assert instance.rest_send.params == {} + assert instance.results.class_name == "Results" assert instance.conversion.class_name == "ConversionUtils" assert instance.ep_config_deploy.class_name == "EpFabricConfigDeploy" @@ -99,9 +101,7 @@ def test_fabric_config_deploy_00010(fabric_config_deploy) -> None: ("My*Fabric", pytest.raises(ValueError, match=MATCH_00020b), True), ], ) -def test_fabric_config_deploy_00020( - fabric_config_deploy, fabric_name, expected, does_raise -) -> None: +def test_fabric_config_deploy_00020(fabric_config_deploy, fabric_name, expected, does_raise) -> None: """ Classes and Methods - FabricConfigDeploy @@ -151,9 +151,7 @@ def test_fabric_config_deploy_00020( ({10}, True, pytest.raises(TypeError, match=MATCH_00030)), ], ) -def test_fabric_config_deploy_00030( - fabric_config_deploy, value, does_raise, expected -) -> None: +def test_fabric_config_deploy_00030(fabric_config_deploy, value, does_raise, expected) -> None: """ Classes and Methods - FabricConfigDeploy @@ -191,9 +189,7 @@ def test_fabric_config_deploy_00030( ({10}, True, pytest.raises(TypeError, match=MATCH_00040)), ], ) -def test_fabric_config_deploy_00040( - fabric_config_deploy, value, does_raise, expected -) -> None: +def test_fabric_config_deploy_00040(fabric_config_deploy, value, does_raise, expected) -> None: """ Classes and Methods - FabricConfigDeploy @@ -215,9 +211,7 @@ def test_fabric_config_deploy_00040( assert instance.results == value -def test_fabric_config_deploy_00120( - fabric_config_deploy, fabric_details_by_name_v2, fabric_summary -) -> None: +def test_fabric_config_deploy_00120(fabric_config_deploy) -> None: """ Classes and Methods - FabricConfigDeploy @@ -235,8 +229,6 @@ def test_fabric_config_deploy_00120( with does_not_raise(): instance = fabric_config_deploy - instance.fabric_details = fabric_details_by_name_v2 - instance.fabric_summary = fabric_summary instance.rest_send = RestSend(params) instance.results = Results() @@ -248,9 +240,7 @@ def test_fabric_config_deploy_00120( instance.commit() -def test_fabric_config_deploy_00130( - fabric_config_deploy, fabric_details_by_name_v2, fabric_summary -) -> None: +def test_fabric_config_deploy_00130(fabric_config_deploy) -> None: """ Classes and Methods - FabricConfigDeploy @@ -267,9 +257,9 @@ def test_fabric_config_deploy_00130( """ with does_not_raise(): instance = fabric_config_deploy - instance.fabric_details = fabric_details_by_name_v2 + # instance.fabric_details = fabric_details_by_name_v2 instance.payload = {"FABRIC_NAME": "MyFabric"} - instance.fabric_summary = fabric_summary + # instance.fabric_summary = fabric_summary_v2 instance.results = Results() match = r"FabricConfigDeploy\.commit: " @@ -280,103 +270,12 @@ def test_fabric_config_deploy_00130( instance.commit() -def test_fabric_config_deploy_00140( - fabric_config_deploy, fabric_details_by_name_v2, fabric_summary -) -> None: - """ - Classes and Methods - - FabricConfigDeploy - - __init__() - - results setter - - commit() - - Summary - - Verify behavior when results is not set before calling commit() - - Test - - ValueError is raised because results 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.fabric_summary = fabric_summary - 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() - - -def test_fabric_config_deploy_00150(fabric_config_deploy, fabric_summary) -> None: - """ - Classes and Methods - - FabricConfigDeploy - - __init__() - - fabric_details setter - - commit() - - Summary - - Verify behavior when fabric_details is not set before calling commit() - - Test - - ValueError is raised because results is not set 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(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_v2 -) -> None: - """ - Classes and Methods - - FabricConfigDeploy - - __init__() - - fabric_summary setter - - commit() - - Summary - - Verify behavior when fabric_summary is not set before calling commit() - - Test - - 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 pytest.raises(ValueError, match=match): - instance.commit() +# test_fabric_config_deploy_00140 removed since results is now set in FabricConfigDeploy.__init__() +# test_fabric_config_deploy_00150 removed since fabric_details is now set in FabricConfigDeploy.__init__() +# test_fabric_config_deploy_00160 removed since fabric_summary is now set in FabricConfigDeploy.__init__() -def test_fabric_config_deploy_00200( - monkeypatch, fabric_config_deploy, fabric_details_by_name_v2, fabric_summary -) -> None: +def test_fabric_config_deploy_00200(monkeypatch, fabric_config_deploy) -> None: """ ### Classes and Methods @@ -410,7 +309,7 @@ def path(self): raise ValueError(msg) def responses(): - yield responses_fabric_summary(key) + yield responses_fabric_summary_v2(key) yield responses_fabric_details_by_name_v2(key) gen_responses = ResponseGenerator(responses()) @@ -432,11 +331,7 @@ def responses(): with does_not_raise(): instance = fabric_config_deploy monkeypatch.setattr(instance, "ep_config_deploy", MockEpFabricConfigDeploy()) - 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 = rest_send instance.rest_send = rest_send instance.results = Results() @@ -445,9 +340,7 @@ def responses(): instance.commit() -def test_fabric_config_deploy_00210( - fabric_config_deploy, fabric_details_by_name_v2, fabric_summary -) -> None: +def test_fabric_config_deploy_00210(fabric_config_deploy) -> None: """ Classes and Methods - FabricConfigDeploy @@ -506,7 +399,7 @@ def test_fabric_config_deploy_00210( key = f"{method_name}a" def responses(): - yield responses_fabric_summary(key) + yield responses_fabric_summary_v2(key) yield responses_fabric_details_by_name_v2(key) yield responses_ep_fabric_config_deploy(key) @@ -529,11 +422,7 @@ def responses(): with does_not_raise(): instance = fabric_config_deploy 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() instance.commit() @@ -561,9 +450,7 @@ def responses(): assert False not in instance.results.changed -def test_fabric_config_deploy_00220( - fabric_config_deploy, fabric_details_by_name_v2, fabric_summary -) -> None: +def test_fabric_config_deploy_00220(fabric_config_deploy) -> None: """ Classes and Methods - FabricConfigDeploy @@ -612,7 +499,7 @@ def test_fabric_config_deploy_00220( key = f"{method_name}a" def responses(): - yield responses_fabric_summary(key) + yield responses_fabric_summary_v2(key) yield responses_fabric_details_by_name_v2(key) yield responses_ep_fabric_config_deploy(key) @@ -637,11 +524,7 @@ def responses(): with does_not_raise(): instance = fabric_config_deploy 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 = rest_send instance.results = Results() 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 ee394936f..cfa6c4df4 100644 --- a/tests/unit/modules/dcnm/dcnm_fabric/test_fabric_create.py +++ b/tests/unit/modules/dcnm/dcnm_fabric/test_fabric_create.py @@ -32,22 +32,22 @@ 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.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.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_v2 import 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 ( - MockAnsibleModule, does_not_raise, fabric_create_fixture, params, - payloads_fabric_create, responses_fabric_create, - responses_fabric_details_by_name_v2, rest_send_response_current) + MockAnsibleModule, + does_not_raise, + fabric_create_fixture, + params, + payloads_fabric_create, + responses_fabric_create, + responses_fabric_details_by_name_v2, + rest_send_response_current, +) def test_fabric_create_00000(fabric_create) -> None: @@ -109,9 +109,8 @@ def test_fabric_create_00021(fabric_create) -> None: - payloads setter ### 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 + - Verify `ValueError` is raised because payloads is not a `dict` + - Verify ``instance.payload`` is not modified, hence it retains its initial value of {} """ match = r"FabricCreate\.payload: " match += r"payload must be a dict\." @@ -121,7 +120,7 @@ def test_fabric_create_00021(fabric_create) -> None: instance.results = Results() with pytest.raises(ValueError, match=match): instance.payload = "NOT_A_DICT" - assert instance.payload is None + assert instance.payload == {} def test_fabric_create_00022(fabric_create) -> None: @@ -135,7 +134,7 @@ def test_fabric_create_00022(fabric_create) -> None: - payload setter ### Summary - Verify that ``ValueError`` is raised because payload is empty. + Verify that `ValueError` is raised because payload is empty. """ with does_not_raise(): instance = fabric_create @@ -145,7 +144,8 @@ def test_fabric_create_00022(fabric_create) -> None: match = r"FabricCreate\.payload: payload is empty." with pytest.raises(ValueError, match=match): instance.payload = {} - assert instance.payload is None + + assert instance.payload == {} @pytest.mark.parametrize( @@ -163,8 +163,7 @@ def test_fabric_create_00023(fabric_create, mandatory_parameter) -> None: - payload setter ### Summary - - Verify that ``ValueError`` is raised because payload is missing - mandatory parameters. + - Verify that `ValueError` is raised because payload is missing mandatory parameters. """ method_name = inspect.stack()[0][3] @@ -183,7 +182,7 @@ def test_fabric_create_00023(fabric_create, mandatory_parameter) -> None: match += r"parameter" with pytest.raises(ValueError, match=match): instance.payload = payload - assert instance.payload is None + assert instance.payload == {} def test_fabric_create_00024(fabric_create) -> None: @@ -198,10 +197,8 @@ def test_fabric_create_00024(fabric_create) -> None: - commit() ### 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 + - 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 {} """ with does_not_raise(): instance = fabric_create @@ -213,7 +210,7 @@ def test_fabric_create_00024(fabric_create) -> None: with pytest.raises(ValueError, match=match): instance.commit() - assert instance.payload is None + assert instance.payload == {} def test_fabric_create_00025(fabric_create) -> None: @@ -230,7 +227,7 @@ def test_fabric_create_00025(fabric_create) -> None: an unexpected value. Test - - ``ValueError`` is raised because the value of FABRIC_TYPE is invalid + - `ValueError` is raised because the value of FABRIC_TYPE is invalid """ method_name = inspect.stack()[0][3] key = f"{method_name}a" @@ -266,7 +263,7 @@ def test_fabric_create_00026(fabric_create) -> None: Verify behavior when ``rest_send`` is not set prior to calling commit. ### Test - - ``ValueError`` is raised because ``rest_send`` is not set prior + - `ValueError` is raised because ``rest_send`` is not set prior to calling commit. """ method_name = inspect.stack()[0][3] @@ -368,13 +365,7 @@ def responses(): assert instance.results.response[0].get("RETURN_CODE", None) == 200 print(f"response[0] {instance.results.response[0]}") - assert ( - instance.results.response[0] - .get("DATA", {}) - .get("nvPairs", {}) - .get("BGP_AS", None) - == "65001" - ) + assert instance.results.response[0].get("DATA", {}).get("nvPairs", {}).get("BGP_AS", None) == "65001" assert instance.results.response[0].get("METHOD", None) == "POST" assert instance.results.result[0].get("changed", None) is True @@ -540,10 +531,7 @@ def responses(): assert instance.results.metadata[0].get("state", None) == "merged" assert instance.results.response[0].get("RETURN_CODE", None) == 500 - assert ( - instance.results.response[0].get("DATA", {}) - == "Error in validating provided name value pair: [BGP_AS]" - ) + assert instance.results.response[0].get("DATA", {}) == "Error in validating provided name value pair: [BGP_AS]" assert instance.results.response[0].get("METHOD", None) == "POST" assert instance.results.result[0].get("changed", None) is False @@ -555,7 +543,7 @@ def responses(): assert True not in instance.results.changed -def test_fabric_create_00033(monkeypatch, fabric_create) -> None: +def test_fabric_create_00033(fabric_create) -> None: """ ### Classes and Methods @@ -573,7 +561,7 @@ def test_fabric_create_00033(monkeypatch, fabric_create) -> None: - commit() ### Summary - - Verify ``ValueError`` is raised when user attempts to create a fabric + - Verify `ValueError` is raised when user attempts to create a fabric but the payload contains ``ANYCAST_GW_MAC`` with a malformed mac address. ### Setup @@ -591,7 +579,7 @@ def test_fabric_create_00033(monkeypatch, fabric_create) -> None: to a list containing fabric f1 payload - FabricCreate.commit() calls FabricCommon_fixup_payloads_to_commit() - FabricCommon_fixup_payloads_to_commit() calls - FabricCommon()._fixup_anycast_gw_mac() which raises ``ValueError`` + FabricCommon()._fixup_anycast_gw_mac() which raises `ValueError` because the mac address is malformed. """ method_name = inspect.stack()[0][3] @@ -613,9 +601,6 @@ def responses(): with does_not_raise(): instance = fabric_create - 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) 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 ad6bac666..54ac16819 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,22 +32,22 @@ 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.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.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_v2 import 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 ( - MockAnsibleModule, does_not_raise, fabric_create_bulk_fixture, params, - payloads_fabric_create_bulk, responses_fabric_create_bulk, - responses_fabric_details_by_name_v2, rest_send_response_current) + MockAnsibleModule, + does_not_raise, + fabric_create_bulk_fixture, + params, + payloads_fabric_create_bulk, + responses_fabric_create_bulk, + responses_fabric_details_by_name_v2, + rest_send_response_current, +) def test_fabric_create_bulk_00000(fabric_create_bulk) -> None: @@ -120,7 +120,7 @@ def test_fabric_create_bulk_00021(fabric_create_bulk) -> None: with pytest.raises(ValueError, match=match): instance.payloads = "NOT_A_LIST" - assert instance.payloads is None + assert instance.payloads == [] def test_fabric_create_bulk_00022(fabric_create_bulk) -> None: @@ -149,7 +149,7 @@ def test_fabric_create_bulk_00022(fabric_create_bulk) -> None: with pytest.raises(ValueError, match=match): instance.payloads = [1, 2, 3] - assert instance.payloads is None + assert instance.payloads == [] def test_fabric_create_bulk_00023(fabric_create_bulk) -> None: @@ -172,18 +172,28 @@ def test_fabric_create_bulk_00023(fabric_create_bulk) -> None: - instance.payloads is not modified, hence it retains its initial value of None """ + + def responses(): + yield {"MESSAGE": "OK", "RETURN_CODE": 200, "DATA": {}} + + sender = Sender() + sender.ansible_module = MockAnsibleModule() + sender.gen = ResponseGenerator(responses()) + rest_send = RestSend(params) + rest_send.unit_test = True + rest_send.response_handler = ResponseHandler() + rest_send.sender = sender with does_not_raise(): instance = fabric_create_bulk instance.results = Results() - instance.rest_send = RestSend(params) - instance.rest_send.unit_test = True + instance.rest_send = rest_send 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 + assert instance.payloads == [] def test_fabric_create_bulk_00024(fabric_create_bulk) -> None: @@ -378,13 +388,7 @@ def responses(): assert instance.results.metadata[0].get("state", None) == "merged" assert instance.results.response[0].get("RETURN_CODE", None) == 200 - assert ( - instance.results.response[0] - .get("DATA", {}) - .get("nvPairs", {}) - .get("BGP_AS", None) - == "65001" - ) + assert instance.results.response[0].get("DATA", {}).get("nvPairs", {}).get("BGP_AS", None) == "65001" assert instance.results.response[0].get("METHOD", None) == "POST" assert instance.results.result[0].get("changed", None) is True @@ -553,10 +557,7 @@ def responses(): assert instance.results.metadata[0].get("state", None) == "merged" assert instance.results.response[0].get("RETURN_CODE", None) == 500 - assert ( - instance.results.response[0].get("DATA", {}) - == "Error in validating provided name value pair: [BGP_AS]" - ) + assert instance.results.response[0].get("DATA", {}) == "Error in validating provided name value pair: [BGP_AS]" assert instance.results.response[0].get("METHOD", None) == "POST" assert instance.results.result[0].get("changed", None) is False 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 312820719..090426bca 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 @@ -32,13 +32,14 @@ import inspect import pytest -from ansible_collections.cisco.dcnm.plugins.module_utils.common.api.v1.lan_fabric.rest.control.fabrics.fabrics import \ - EpFabricCreate -from ansible_collections.cisco.dcnm.plugins.module_utils.common.rest_send import \ - RestSend +from ansible_collections.cisco.dcnm.plugins.module_utils.common.api.v1.lan_fabric.rest.control.fabrics.fabrics import EpFabricCreate +from ansible_collections.cisco.dcnm.plugins.module_utils.common.rest_send import RestSend from ansible_collections.cisco.dcnm.tests.unit.modules.dcnm.dcnm_fabric.utils import ( - MockAnsibleModule, does_not_raise, fabric_create_common_fixture, - payloads_fabric_create_common) + MockAnsibleModule, + does_not_raise, + fabric_create_common_fixture, + payloads_fabric_create_common, +) def test_fabric_create_common_00010(fabric_create_common) -> None: @@ -55,12 +56,12 @@ def test_fabric_create_common_00010(fabric_create_common) -> None: """ with does_not_raise(): instance = fabric_create_common - assert instance.ep_fabric_create.class_name == "EpFabricCreate" + assert instance._ep_fabric_create.class_name == "EpFabricCreate" assert instance.fabric_types.class_name == "FabricTypes" assert instance.class_name == "FabricCreateCommon" assert instance.action == "fabric_create" - assert instance.path is None - assert instance.verb is None + assert instance.path == "" + assert instance.verb == "" assert instance._payloads_to_commit == [] @@ -84,8 +85,9 @@ def test_fabric_create_common_00030(fabric_create_common) -> None: with does_not_raise(): instance = fabric_create_common - match = r"FabricCreateCommon\.fabric_type: FABRIC_TYPE must be one of\s+.*" - match += "Got INVALID_FABRIC_TYPE" + match = r"FabricTypes\.fabric_type.setter: " + match += r"Invalid fabric type: INVALID_FABRIC_TYPE\.\s" + match += r"Expected one of: BGP, IPFM, ISN, LAN_CLASSIC, MCFG, VXLAN_EVPN, VXLAN_EVPN_MSD\." with pytest.raises(ValueError, match=match): instance._set_fabric_create_endpoint(payload) @@ -98,12 +100,12 @@ def test_fabric_create_common_00032(monkeypatch, fabric_create_common) -> None: - FabricCreateCommon - __init__() - _set_fabric_create_endpoint - - ep_fabric_create.fabric_name setter + - _ep_fabric_create.fabric_name setter Summary - - ``ValueError`` is raised when ep_fabric_create.fabric_name raises an exception. - - Since ``fabric_name`` and ``template_name`` are already verified in - _set_fabric_create_endpoint, EpFabricCreate().fabric_name setter needs + - `ValueError` is raised when `_ep_fabric_create.fabric_name` raises an exception. + - Since `fabric_name` and `template_name` are already verified in + `_set_fabric_create_endpoint`, `EpFabricCreate().fabric_name` setter needs to be mocked to raise an exception. """ method_name = inspect.stack()[0][3] @@ -133,8 +135,8 @@ def fabric_name(self, value): with does_not_raise(): instance = fabric_create_common - monkeypatch.setattr(instance, "ep_fabric_create", MockEpFabricCreate()) - instance.ep_fabric_create = MockEpFabricCreate() + monkeypatch.setattr(instance, "_ep_fabric_create", MockEpFabricCreate()) + instance._ep_fabric_create = MockEpFabricCreate() match = r"MockEpFabricCreate\.fabric_name: mocked exception\." with pytest.raises(ValueError, match=match): @@ -149,12 +151,12 @@ def test_fabric_create_common_00033(monkeypatch, fabric_create_common) -> None: - FabricCreateCommon - __init__() - _set_fabric_create_endpoint - - ep_fabric_create.template_name setter + - _ep_fabric_create.template_name setter Summary - - ``ValueError`` is raised when ep_fabric_create.template_name raises an exception. - - Since ``fabric_name`` and ``template_name`` are already verified in - _set_fabric_create_endpoint, EpFabricCreate().template_name setter needs + - `ValueError` is raised when `_ep_fabric_create.template_name` raises an exception. + - Since `fabric_name` and `template_name` are already verified in + `_set_fabric_create_endpoint`, `EpFabricCreate().template_name` setter needs to be mocked to raise an exception. """ method_name = inspect.stack()[0][3] @@ -164,8 +166,7 @@ def test_fabric_create_common_00033(monkeypatch, fabric_create_common) -> None: class MockEpFabricCreate: # pylint: disable=too-few-public-methods """ - Mock the EpFabricCreate.template_name setter property - to raise ``ValueError``. + Mock the EpFabricCreate.template_name setter property to raise `ValueError`. """ @property @@ -184,8 +185,8 @@ def template_name(self, value): with does_not_raise(): instance = fabric_create_common - monkeypatch.setattr(instance, "ep_fabric_create", MockEpFabricCreate()) - instance.ep_fabric_create = MockEpFabricCreate() + monkeypatch.setattr(instance, "_ep_fabric_create", MockEpFabricCreate()) + instance._ep_fabric_create = MockEpFabricCreate() match = r"MockEpFabricCreate\.template_name: mocked exception\." with pytest.raises(ValueError, match=match): @@ -273,9 +274,7 @@ def mock_set_fabric_create_endpoint( with does_not_raise(): instance = fabric_create_common instance.rest_send = RestSend(MockAnsibleModule()) - monkeypatch.setattr( - instance, "_set_fabric_create_endpoint", mock_set_fabric_create_endpoint - ) + monkeypatch.setattr(instance, "_set_fabric_create_endpoint", mock_set_fabric_create_endpoint) instance._payloads_to_commit = [payload] match = r"mock_set_fabric_endpoint\(\): mocked exception\." 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 bd4591a38..79d7eb831 100644 --- a/tests/unit/modules/dcnm/dcnm_fabric/test_fabric_query.py +++ b/tests/unit/modules/dcnm/dcnm_fabric/test_fabric_query.py @@ -32,21 +32,18 @@ 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.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.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_v2 import 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 ( - MockAnsibleModule, does_not_raise, fabric_query_fixture, - responses_fabric_query) + MockAnsibleModule, + does_not_raise, + fabric_query_fixture, + responses_fabric_query, +) PARAMS = {"state": "query", "check_mode": False} @@ -69,8 +66,8 @@ def test_fabric_query_00000(fabric_query) -> None: instance = fabric_query assert instance.class_name == "FabricQuery" assert instance.action == "fabric_query" - assert instance.fabric_details is None - assert instance.fabric_names is None + assert instance._fabric_details_by_name.class_name == "FabricDetailsByName" + assert instance.fabric_names == [] assert instance._fabrics_to_query == [] @@ -84,15 +81,16 @@ def test_fabric_query_00020(fabric_query) -> None: - __init__() - fabric_names setter - ### Summary - Verify behavior when ``fabric_names`` is set to a list of strings. + # Summary - ### Test + 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` is set to expected value. + - Exception is not raised. """ - fabric_names = ["FOO", "BAR"] + fabric_names: list[str] = ["FOO", "BAR"] with does_not_raise(): instance = fabric_query instance.fabric_names = fabric_names @@ -101,21 +99,21 @@ def test_fabric_query_00020(fabric_query) -> None: def test_fabric_query_00021(fabric_query) -> None: """ - ### Classes and Methods + # 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 "". + + ## Classes and Methods - FabricCommon - __init__() - FabricQuery - __init__() - fabric_names setter - - ### 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 @@ -126,12 +124,21 @@ def test_fabric_query_00021(fabric_query) -> None: with pytest.raises(ValueError, match=match): instance.fabric_names = "NOT_A_LIST" - assert instance.fabric_names is None + assert instance.fabric_names == [] def test_fabric_query_00022(fabric_query) -> None: """ - ### Classes and Methods + # 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 "". + + ## Classes and Methods - FabricCommon - __init__() @@ -139,16 +146,6 @@ def test_fabric_query_00022(fabric_query) -> None: - __init__() - fabric_names setter - ### 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 @@ -159,25 +156,28 @@ def test_fabric_query_00022(fabric_query) -> None: with pytest.raises(ValueError, match=match): instance.fabric_names = [1, 2, 3] - assert instance.fabric_names is None + assert instance.fabric_names == [] def test_fabric_query_00023(fabric_query) -> None: """ - ### Classes and Methods + # Summary - - FabricQuery - - fabric_names setter + Verify behavior when `fabric_names` is set to an empty list. - ### Summary - Verify behavior when ``fabric_names`` is set to an empty list. + ## Setup - ### Setup + - FabricQuery().fabric_names is set to an empty list - - FabricQuery().fabric_names is set to an empty list + ## Test + + - `ValueError` is raised from `fabric_names` setter. + + ## Classes and Methods + + - FabricQuery + - fabric_names setter - ### 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\." @@ -187,37 +187,7 @@ def test_fabric_query_00023(fabric_query) -> None: instance.fabric_names = [] -def test_fabric_query_00024(fabric_query) -> None: - """ - ### Classes and Methods - - - FabricCommon - - __init__() - - FabricQuery - - __init__() - - 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() - - assert instance.fabric_names == ["f1"] +# test_fabric_query_00024 removed since fabric_details is now set in FabricQuery.__init__() def test_fabric_query_00025(fabric_query) -> None: @@ -232,18 +202,17 @@ def test_fabric_query_00025(fabric_query) -> None: - _validate_commit_parameters() ### Summary - Verify behavior when ``fabric_names`` is not set before calling commit. + Verify behavior when `fabric_names` is not set before calling commit. ### Test - - ``ValueError`` is raised because fabric_names is not set before + - `ValueError` is raised because fabric_names is not set before calling commit. - - ``fabric_names`` is not modified, hence it retains its initial value + - `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() @@ -253,48 +222,21 @@ def test_fabric_query_00025(fabric_query) -> None: with pytest.raises(ValueError, match=match): instance.commit() - assert instance.fabric_names is None + assert instance.fabric_names == [] def test_fabric_query_00026(fabric_query) -> None: """ - ### Classes and Methods + # Summary - - FabricCommon - - __init__() - - FabricQuery - - __init__() - - commit() - - _validate_commit_parameters() + Verify behavior when `rest_send` is not set before calling commit. - ### Summary - Verify behavior when ``rest_send`` is not set before calling commit. + ## Test - ### Test + - `ValueError` is raised because `rest_send.params` is not set before calling commit. + - `rest_send.params` is not modified, hence it retains its initial value of {}. - - ``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. - """ - with does_not_raise(): - instance = fabric_query - 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\." - - 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__() @@ -303,56 +245,31 @@ def test_fabric_query_00027(fabric_query) -> None: - commit() - _validate_commit_parameters() - ### Summary - Verify behavior when ``results`` is not set before 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.rest_send = RestSend(PARAMS) match = r"FabricQuery\._validate_commit_parameters:\s+" - match += r"results must be set before calling commit\." + match += r"rest_send must be set before calling commit\." with pytest.raises(ValueError, match=match): instance.commit() - assert instance.results.class_name == "Results" + assert instance.rest_send.params == {} + + +# test_fabric_query_00027 removed since results is now set in FabricQuery.__init__() and is now optional def test_fabric_query_00030(fabric_query) -> None: """ - ### Classes and Methods + # Summary - - FabricCommon() - - __init__() - - FabricDetails() - - __init__() - - refresh_super() - - FabricDetailsByName() - - __init__() - - refresh() - - FabricQuery - - __init__() - - fabric_names setter - - commit() - - Query() - - __init__() - - commit() - - ### 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() @@ -372,6 +289,24 @@ def test_fabric_query_00030(fabric_query) -> None: - FabricQuery.commit() calls Results().register_task_result() - Results().register_task_result() adds sequence_number (with value 1) to each of the results dicts + + ## Classes and Methods + + - FabricCommon() + - __init__() + - FabricDetails() + - __init__() + - refresh_super() + - FabricDetailsByName() + - __init__() + - refresh() + - FabricQuery + - __init__() + - fabric_names setter + - commit() + - Query() + - __init__() + - commit() """ method_name = inspect.stack()[0][3] key = f"{method_name}a" @@ -392,9 +327,6 @@ def responses(): with does_not_raise(): instance = fabric_query - 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() @@ -715,10 +647,7 @@ def responses(): assert instance.results.diff[0].get("sequence_number", None) == 1 assert instance.results.diff[0].get("f1", {}).get("asn", None) == "65001" - assert ( - instance.results.diff[0].get("f1", {}).get("nvPairs", {}).get("BGP_AS") - == "65001" - ) + assert instance.results.diff[0].get("f1", {}).get("nvPairs", {}).get("BGP_AS") == "65001" assert instance.results.response[0].get("RETURN_CODE", None) == 200 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 482963584..941b0e11e 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,25 +32,15 @@ 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.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.plugins.module_utils.common.rest_send_v2 import RestSend +from ansible_collections.cisco.dcnm.plugins.module_utils.common.results_v2 import Results +from ansible_collections.cisco.dcnm.plugins.module_utils.fabric.fabric_details_v3 import FabricDetailsByName +from ansible_collections.cisco.dcnm.plugins.module_utils.fabric.fabric_summary_v2 import FabricSummary from ansible_collections.cisco.dcnm.tests.unit.modules.dcnm.dcnm_fabric.utils import ( - MockAnsibleModule, does_not_raise, fabric_replaced_bulk_fixture, - payloads_fabric_replaced_bulk, responses_config_deploy, - responses_config_save, responses_fabric_replaced_bulk, - responses_fabric_summary) + does_not_raise, + fabric_replaced_bulk_fixture, + payloads_fabric_replaced_bulk, +) PARAMS = {"state": "replaced", "check_mode": False} @@ -71,17 +61,17 @@ def test_fabric_replaced_bulk_00000(fabric_replaced_bulk) -> None: instance = fabric_replaced_bulk assert instance.class_name == "FabricReplacedBulk" 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._ep_fabric_update.class_name == "EpFabricUpdate" + assert instance.fabric_details.class_name == "FabricDetailsByName" + assert instance.fabric_summary.class_name == "FabricSummary" + assert instance._param_info.class_name == "ParamInfo" + assert instance.rest_send.class_name == "RestSend" + assert instance.results.class_name == "Results" + assert instance._ruleset.class_name == "RuleSet" + assert instance._template_get.class_name == "TemplateGet" + assert instance._verify_playbook_params.class_name == "VerifyPlaybookParams" + assert instance.path == "" + assert instance.verb == "" def test_fabric_replaced_bulk_00020(fabric_replaced_bulk) -> None: @@ -138,7 +128,7 @@ def test_fabric_replaced_bulk_00021(fabric_replaced_bulk) -> None: with pytest.raises(ValueError, match=match): instance.payloads = "NOT_A_LIST" - assert instance.payloads is None + assert instance.payloads == [] def test_fabric_replaced_bulk_00022(fabric_replaced_bulk) -> None: @@ -170,7 +160,7 @@ def test_fabric_replaced_bulk_00022(fabric_replaced_bulk) -> None: with pytest.raises(ValueError, match=match): instance.payloads = [1, 2, 3] - assert instance.payloads is None + assert instance.payloads == [] def test_fabric_replaced_bulk_00023(fabric_replaced_bulk) -> None: @@ -183,24 +173,18 @@ def test_fabric_replaced_bulk_00023(fabric_replaced_bulk) -> None: - FabricReplacedBulk - __init__() - ### Summary - Verify behavior when ``payloads`` is set to an empty list. + # Summary - Setup - - ``payloads`` is set to an empty list. + Verify that ValueError is raised when `payloads` is set to an empty list. - ### Test - - ``ValueError`` is not raised. - - ``payloads`` is set to an empty list. + ## Setup - ### NOTES + - `payloads` is set to an empty list. - - 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 commit() here since it would - never be reached. """ - with does_not_raise(): + match = r"FabricReplacedBulk\.payloads: " + match += r"payloads must not be an empty list\." + with pytest.raises(ValueError, match=match): instance = fabric_replaced_bulk instance.payloads = [] assert instance.payloads == [] @@ -225,10 +209,10 @@ def test_fabric_replaced_bulk_00024(fabric_replaced_bulk, mandatory_parameter) - - 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. + - Verify instance.payloads retains its initial value of []. """ - method_name = inspect.stack()[0][3] - key = f"{method_name}a" + method_name: str = inspect.stack()[0][3] + key: str = f"{method_name}a" with does_not_raise(): instance = fabric_replaced_bulk @@ -243,7 +227,7 @@ def test_fabric_replaced_bulk_00024(fabric_replaced_bulk, mandatory_parameter) - match += r"parameter.*" with pytest.raises(ValueError, match=match): instance.payloads = payloads - assert instance.payloads is None + assert instance.payloads == [] def test_fabric_replaced_bulk_00025(fabric_replaced_bulk) -> None: @@ -276,7 +260,7 @@ def test_fabric_replaced_bulk_00025(fabric_replaced_bulk) -> None: with pytest.raises(ValueError, match=match): instance.commit() - assert instance.payloads is None + assert instance.payloads == [] def test_fabric_replaced_bulk_00030(fabric_replaced_bulk) -> None: @@ -346,9 +330,7 @@ def test_fabric_replaced_bulk_00030(fabric_replaced_bulk) -> None: ("0001", None, True, pytest.raises(ValueError, match=MATCH_00031a)), ], ) -def test_fabric_replaced_bulk_00031( - fabric_replaced_bulk, mac_in, mac_out, raises, expected -) -> None: +def test_fabric_replaced_bulk_00031(fabric_replaced_bulk, mac_in, mac_out, raises, expected) -> None: """ ### Classes and Methods @@ -403,9 +385,7 @@ def test_fabric_replaced_bulk_00031( ("PARAM_15", None, None, "c", {"PARAM_15": "c"}), ], ) -def test_fabric_replaced_bulk_00040( - fabric_replaced_bulk, parameter, playbook, controller, default, expected -) -> None: +def test_fabric_replaced_bulk_00040(fabric_replaced_bulk, parameter, playbook, controller, default, expected) -> None: """ ### Classes and Methods @@ -419,9 +399,7 @@ def test_fabric_replaced_bulk_00040( """ with does_not_raise(): instance = fabric_replaced_bulk - assert expected == instance.update_replaced_payload( - parameter, playbook, controller, default - ) + assert expected == instance.update_replaced_payload(parameter, playbook, controller, default) MATCH_00050 = r"FabricReplacedBulk\._verify_value_types_for_comparison:\s+" @@ -444,9 +422,7 @@ def test_fabric_replaced_bulk_00040( ("a", None, {}, pytest.raises(ValueError, match=MATCH_00050c)), ], ) -def test_fabric_replaced_bulk_00050( - fabric_replaced_bulk, user_value, controller_value, default_value, expected -) -> None: +def test_fabric_replaced_bulk_00050(fabric_replaced_bulk, user_value, controller_value, default_value, expected) -> None: """ ### Classes and Methods @@ -467,59 +443,59 @@ def test_fabric_replaced_bulk_00050( with does_not_raise(): instance = fabric_replaced_bulk with expected: - instance._verify_value_types_for_comparison( - fabric, parameter, user_value, controller_value, default_value - ) + instance._verify_value_types_for_comparison(fabric, parameter, user_value, controller_value, default_value) -def test_fabric_replaced_bulk_00200(fabric_replaced_bulk) -> None: - """ - ### Classes and Methods +# test_fabric_replaced_bulk_00200 removed because fabric_details is now set in FabricReplacedBulk.__init__() +# test_fabric_replaced_bulk_00210 removed because fabric_summary is now set in FabricReplacedBulk.__init__() - - FabricReplacedBulk - - commit() +# def test_fabric_replaced_bulk_00200(fabric_replaced_bulk) -> None: +# """ +# ### Classes and Methods - ### Summary - Verify `ValueError`` is raised when ``fabric_details`` is not set before - calling ``commit``. - """ - with does_not_raise(): - instance = fabric_replaced_bulk - instance.fabric_summary = FabricSummary() - instance.payloads = [] - instance.rest_send = RestSend(PARAMS) - instance.results = Results() +# - FabricReplacedBulk +# - commit() - match = r"FabricReplacedBulk\.commit:\s+" - match += r"fabric_details must be set prior to calling commit\." +# ### Summary +# Verify `ValueError`` is raised when ``fabric_details`` is not set before +# calling `commit`. +# """ +# with does_not_raise(): +# instance = fabric_replaced_bulk +# instance.payloads = [] +# instance.rest_send = RestSend(PARAMS) +# instance.results = Results() - with pytest.raises(ValueError, match=match): - instance.commit() +# 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 - - FabricReplacedBulk - - commit() +# def test_fabric_replaced_bulk_00210(fabric_replaced_bulk) -> None: +# """ +# ### Classes and Methods - ### Summary - Verify `ValueError`` is raised when ``fabric_summary`` is not set before - calling ``commit``. - """ - with does_not_raise(): - instance = fabric_replaced_bulk - instance.fabric_details = FabricDetailsByName() - instance.payloads = [] - instance.rest_send = RestSend(PARAMS) - instance.results = Results() +# - FabricReplacedBulk +# - commit() - match = r"FabricReplacedBulk\.commit:\s+" - match += r"fabric_summary must be set prior to calling commit\." +# ### Summary +# Verify `ValueError`` is raised when ``fabric_summary`` is not set before +# calling `commit`. +# """ +# with does_not_raise(): +# instance = fabric_replaced_bulk +# instance.fabric_details = FabricDetailsByName() +# instance.payloads = [] +# instance.rest_send = RestSend(PARAMS) +# instance.results = Results() - with pytest.raises(ValueError, match=match): - instance.commit() +# 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: @@ -531,7 +507,7 @@ def test_fabric_replaced_bulk_00220(fabric_replaced_bulk) -> None: ### Summary Verify `ValueError`` is raised when ``payloads`` is not set before - calling ``commit``. + calling `commit`. """ with does_not_raise(): instance = fabric_replaced_bulk @@ -555,14 +531,11 @@ def test_fabric_replaced_bulk_00230(fabric_replaced_bulk) -> None: - commit() ### Summary - Verify `ValueError`` is raised when ``rest_send`` is not set before - calling ``commit``. + Verify `ValueError`` is raised when `rest_send` is not set before calling `commit`. """ with does_not_raise(): instance = fabric_replaced_bulk - instance.fabric_details = FabricDetailsByName() - instance.fabric_summary = FabricSummary() - instance.payloads = [] + instance.payloads = [{"FABRIC_NAME": "MyFabric", "FABRIC_TYPE": "VXLAN_EVPN", "BGP_AS": 65000}] instance.results = Results() match = r"FabricReplacedBulk\.commit:\s+" @@ -572,86 +545,5 @@ def test_fabric_replaced_bulk_00230(fabric_replaced_bulk) -> None: instance.commit() -def test_fabric_replaced_bulk_00240(fabric_replaced_bulk) -> None: - """ - ### Classes and Methods - - - FabricReplacedCommon - - __init__() - - FabricReplacedBulk - - __init__() - - commit() - - ### 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 "fabric_replace" - - Verify FabricReplacedBulk().results.state is set to "replaced" - - Verify FabricReplacedBulk().template_get.rest_send is set to - FabricReplacedBulk().rest_send - - Verify FabricReplacedBulk()._build_payloads_for_replaced_state() - does not raise ``ValueError`` when called by commit(). - - Verify FabricReplacedBulk()._payloads_to_commit is set to an empty - because FabricReplacedBulk().payloads is empty. - - Verify FabricReplacedBulk().results.failed contains False - - Verify FabricReplacedBulk().results.failed does not contain True - - Verify FabricReplacedBulk().results.changed contains False - - Verify FabricReplacedBulk().results.changed does not contain True - """ - method_name = inspect.stack()[0][3] - key = f"{method_name}a" - - def responses(): - yield responses_fabric_replaced_bulk(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 = fabric_replaced_bulk - 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() - instance.commit() - - assert instance.rest_send.state == "replaced" - 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 == [] - 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.results.metadata[0].get("sequence_number") == 1 - 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") == "fabric_replace" - assert instance.results.metadata[0].get("check_mode") is False - assert instance.results.metadata[0].get("state") == "replaced" - - assert ( - instance.results.response[0].get("MESSAGE") - == "No fabrics to update for replaced state." - ) - assert instance.results.response[0].get("RETURN_CODE") == 200 - - assert instance.results.result[0].get("changed") is False - assert instance.results.result[0].get("success") is True +# test_fabric_replaced_bulk_00240 is removed because we now check for empty payloads in the setter +# and this is tested in test_fabric_replaced_bulk_00023 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 98778e73d..8219ba9f2 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 @@ -21,7 +21,9 @@ # pylint: disable=protected-access # pylint: disable=unused-argument # pylint: disable=invalid-name - +""" +Unit tests for FabricUpdateBulk class in module_utils/fabric/update.py +""" from __future__ import absolute_import, division, print_function __metaclass__ = type @@ -32,25 +34,22 @@ 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.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.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_v2 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 ansible_collections.cisco.dcnm.tests.unit.modules.dcnm.dcnm_fabric.utils import ( - MockAnsibleModule, does_not_raise, fabric_update_bulk_fixture, - payloads_fabric_update_bulk, responses_config_deploy, - responses_config_save, responses_fabric_details_by_name_v2, - responses_fabric_summary, responses_fabric_update_bulk) + MockAnsibleModule, + does_not_raise, + fabric_update_bulk_fixture, + payloads_fabric_update_bulk, + responses_config_deploy, + responses_config_save, + responses_fabric_details_by_name_v2, + responses_fabric_summary_v2, + responses_fabric_update_bulk, +) PARAMS = {"state": "merged", "check_mode": False} @@ -69,10 +68,9 @@ def test_fabric_update_bulk_00000(fabric_update_bulk) -> None: """ with does_not_raise(): instance = fabric_update_bulk - instance.fabric_details = FabricDetailsByName() assert instance.class_name == "FabricUpdateBulk" - assert instance.action == "fabric_update" - assert instance.ep_fabric_update.class_name == "EpFabricUpdate" + assert instance.action == "fabric_update_bulk" + assert instance._ep_fabric_update.class_name == "EpFabricUpdate" assert instance.fabric_details.class_name == "FabricDetailsByName" assert instance.fabric_types.class_name == "FabricTypes" @@ -93,15 +91,14 @@ def test_fabric_update_bulk_00020(fabric_update_bulk) -> None: ### Test - - ``payloads`` is set to expected value. - - ``ValueError`` is not raised. + - `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() instance.results = Results() instance.payloads = payloads_fabric_update_bulk(key) assert instance.payloads == payloads_fabric_update_bulk(key) @@ -118,23 +115,22 @@ def test_fabric_update_bulk_00021(fabric_update_bulk) -> None: - __init__() ### Summary - ``payloads`` setter is presented with input that is not a list. + `payloads` setter is presented with input that is not a list. ### Test - - ``ValueError`` is raised because payloads is not a list, - - ``payloads`` retains its initial value of None, + - `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() instance.results = Results() with pytest.raises(ValueError, match=match): instance.payloads = "NOT_A_LIST" - assert instance.payloads is None + assert instance.payloads == [] def test_fabric_update_bulk_00022(fabric_update_bulk) -> None: @@ -148,14 +144,14 @@ def test_fabric_update_bulk_00022(fabric_update_bulk) -> None: - __init__() ### Summary - ``payloads`` setter is presented with a list that contains a + `payloads` setter is presented with a list that contains a non-dict element. ### Test - - ``ValueError`` is raised because payloads is a list with + - `ValueError` is raised because payloads is a list with non-dict elements - - ``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+" @@ -163,11 +159,10 @@ def test_fabric_update_bulk_00022(fabric_update_bulk) -> None: with does_not_raise(): instance = fabric_update_bulk - instance.fabric_details = FabricDetailsByName() instance.results = Results() with pytest.raises(ValueError, match=match): instance.payloads = [1, 2, 3] - assert instance.payloads is None + assert instance.payloads == [] def test_fabric_update_bulk_00023(fabric_update_bulk) -> None: @@ -181,25 +176,21 @@ def test_fabric_update_bulk_00023(fabric_update_bulk) -> None: - __init__() ### Summary - Verify behavior when ``payloads`` is not set prior to calling commit. + Verify behavior when `payloads` is not set prior to calling commit. ### Test - - ``ValueError`` is raised because payloads is not set - prior to calling commit - - ``payloads`` retains its initial value of None. + - `ValueError` is raised because payloads is not set prior to calling commit + - `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() - instance.fabric_summary = FabricSummary() - instance.results = Results() with pytest.raises(ValueError, match=match): instance.commit() - assert instance.payloads is None + assert instance.payloads == [] def test_fabric_update_bulk_00024(fabric_update_bulk) -> None: @@ -213,16 +204,16 @@ def test_fabric_update_bulk_00024(fabric_update_bulk) -> None: - __init__() ### Summary - Verify behavior when ``payloads`` is set to an empty list. + Verify behavior when `payloads` is set to an empty list. ### Setup - - ``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. + - `ValueError` is not raised + - `payloads` is set to an empty list. ### NOTES @@ -233,7 +224,6 @@ def test_fabric_update_bulk_00024(fabric_update_bulk) -> None: """ with does_not_raise(): instance = fabric_update_bulk - instance.results = Results() instance.payloads = [] assert instance.payloads == [] @@ -254,18 +244,17 @@ def test_fabric_update_bulk_00025(fabric_update_bulk, mandatory_parameter) -> No ### Summary - - Verify ``payloads`` setter re-raises ``ValueError`` - raised by FabricCommon()._verify_payload() when ``payloads`` is + - 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. + - Verify `payloads` retains its initial value of None. """ - method_name = inspect.stack()[0][3] + method_name: str = inspect.stack()[0][3] key = f"{method_name}a" with does_not_raise(): instance = fabric_update_bulk - instance.fabric_details = FabricDetailsByName() instance.results = Results() payloads = payloads_fabric_update_bulk(key) @@ -276,7 +265,7 @@ def test_fabric_update_bulk_00025(fabric_update_bulk, mandatory_parameter) -> No match += r"parameter.*" with pytest.raises(ValueError, match=match): instance.payloads = payloads - assert instance.payloads is None + assert instance.payloads == [] def test_fabric_update_bulk_00030(fabric_update_bulk) -> None: @@ -340,14 +329,6 @@ def responses(): with does_not_raise(): instance = fabric_update_bulk - 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 = rest_send instance.results = Results() instance.payloads = payloads_fabric_update_bulk(key) @@ -364,16 +345,13 @@ def responses(): assert instance.results.diff[0].get("sequence_number", None) == 1 - assert instance.results.metadata[0].get("action", None) == "fabric_update" + assert instance.results.metadata[0].get("action", None) == "fabric_update_bulk" 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 instance.results.response[0].get("RETURN_CODE", None) == 200 - assert ( - instance.results.response[0].get("MESSAGE", None) - == "No fabrics to update for merged state." - ) + assert instance.results.response[0].get("MESSAGE", None) == "No fabrics to update for merged state." assert instance.results.result[0].get("changed", None) is False assert instance.results.result[0].get("success", None) is True @@ -412,8 +390,7 @@ def test_fabric_update_bulk_00031(fabric_update_bulk) -> None: - The fabric payload includes ANYCAST_GW_MAC, formatted to be incompatible with the controller's requirements, but able to be fixed by FabricUpdateCommon()._fixup_payloads_to_commit(). - - The fabric payload also contains keys that include ``bool` - and ``int`` values. + - The fabric payload also contains keys that include bool and int values. - The fabric is empty, so is updated, but not deployed/saved. ### See Also @@ -424,8 +401,7 @@ def test_fabric_update_bulk_00031(fabric_update_bulk) -> None: - 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 - the fabric. + - The payload keys contain values that would result in changes to the fabric. - FabricUpdateBulk.commit() calls FabricUpdateCommon()._build_payloads_for_merged_state() - FabricUpdateCommon()._build_payloads_for_merged_state() calls @@ -435,7 +411,7 @@ def test_fabric_update_bulk_00031(fabric_update_bulk) -> None: _fabric_update_required to an empty set() and calls FabricUpdateCommon()._fabric_needs_update_for_merged_state() with the payload. - - FabricUpdateCommon()._fabric_needs_update_for_merged_state() updates + - FabricUpdateCommon()._fabric_needs_update_for_merged_state() compares the payload to the fabric details and determines that changes are required. Hence, it adds True to _fabric_update_required. - FabricUpdateCommon()._build_payloads_for_merged_state() finds True in @@ -470,7 +446,7 @@ def responses(): 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_summary_v2(key) gen_responses = ResponseGenerator(responses()) @@ -485,15 +461,6 @@ def responses(): with does_not_raise(): instance = fabric_update_bulk - - 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 = rest_send instance.results = Results() instance.payloads = payloads_fabric_update_bulk(key) @@ -519,7 +486,7 @@ def responses(): assert instance.results.diff[2].get("sequence_number", None) == 3 - assert instance.results.metadata[0].get("action", None) == "fabric_update" + assert instance.results.metadata[0].get("action", None) == "fabric_update_bulk" 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" @@ -545,13 +512,7 @@ def responses(): assert instance.results.response[0].get("METHOD", None) == "PUT" assert instance.results.response[1].get("METHOD", None) == "POST" - assert ( - instance.results.response[0] - .get("DATA", {}) - .get("nvPairs", {}) - .get("BGP_AS", None) - == "65001" - ) + assert instance.results.response[0].get("DATA", {}).get("nvPairs", {}).get("BGP_AS", None) == "65001" msg = "Config save is completed" assert instance.results.response[1].get("DATA", {}).get("status") == msg @@ -617,14 +578,14 @@ def test_fabric_update_bulk_00032(fabric_update_bulk) -> None: - FabricUpdateCommon()._fabric_needs_update_for_merged_state() calls Results().register_task_result() - FabricUpdateCommon()._fabric_needs_update_for_merged_state() raises - ``ValueError`` because the payload contains an invalid key. + `ValueError` because the payload contains an invalid key. """ method_name = inspect.stack()[0][3] key = f"{method_name}a" def responses(): yield responses_fabric_details_by_name_v2(key) - yield responses_fabric_summary(key) + yield responses_fabric_summary_v2(key) yield responses_fabric_update_bulk(key) gen_responses = ResponseGenerator(responses()) @@ -640,15 +601,6 @@ def responses(): with does_not_raise(): instance = fabric_update_bulk - - 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 = rest_send instance.results = Results() instance.payloads = payloads_fabric_update_bulk(key) @@ -670,7 +622,7 @@ def responses(): assert instance.results.diff[0].get("sequence_number", None) == 1 - assert instance.results.metadata[0].get("action", None) == "fabric_update" + assert instance.results.metadata[0].get("action", None) == "fabric_update_bulk" 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" @@ -736,7 +688,7 @@ def test_fabric_update_bulk_00033(fabric_update_bulk) -> None: ``ANYCAST_GW_MAC`` key is present in the payload. - FabricCommon().translate_anycast_gw_mac(): - Updates Results() - - raises ``ValueError`` because the mac address is not convertable. + - raises `ValueError` because the mac address is not convertable. """ method_name = inspect.stack()[0][3] key = f"{method_name}a" @@ -746,7 +698,7 @@ def test_fabric_update_bulk_00033(fabric_update_bulk) -> None: def responses(): yield responses_fabric_details_by_name_v2(key) - yield responses_fabric_summary(key) + yield responses_fabric_summary_v2(key) gen_responses = ResponseGenerator(responses()) @@ -761,15 +713,6 @@ def responses(): with does_not_raise(): instance = fabric_update_bulk - - 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 = rest_send instance.results = Results() instance.payloads = payloads_fabric_update_bulk(key) @@ -790,7 +733,7 @@ def responses(): assert instance.results.diff[0].get("sequence_number", None) == 1 - assert instance.results.metadata[0].get("action", None) == "fabric_update" + assert instance.results.metadata[0].get("action", None) == "fabric_update_bulk" 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" @@ -860,7 +803,7 @@ def test_fabric_update_bulk_00034(fabric_update_bulk) -> None: def responses(): yield responses_fabric_details_by_name_v2(key) - yield responses_fabric_summary(key) + yield responses_fabric_summary_v2(key) yield responses_fabric_update_bulk(key) gen_responses = ResponseGenerator(responses()) @@ -876,15 +819,6 @@ def responses(): with does_not_raise(): instance = fabric_update_bulk - - 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 = rest_send instance.results = Results() instance.payloads = payloads_fabric_update_bulk(key) @@ -901,16 +835,13 @@ def responses(): assert instance.results.diff[0].get("sequence_number", None) == 1 - assert instance.results.metadata[0].get("action", None) == "fabric_update" + assert instance.results.metadata[0].get("action", None) == "fabric_update_bulk" 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 instance.results.response[0].get("RETURN_CODE", None) == 200 - assert ( - instance.results.response[0].get("MESSAGE", None) - == "No fabrics to update for merged state." - ) + assert instance.results.response[0].get("MESSAGE", None) == "No fabrics to update for merged state." assert instance.results.response[0].get("sequence_number", None) == 1 assert instance.results.result[0].get("changed", None) is False @@ -1017,7 +948,7 @@ def responses(): 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_summary_v2(key) yield responses_fabric_details_by_name_v2(key) yield responses_config_deploy(key) @@ -1034,15 +965,6 @@ def responses(): with does_not_raise(): instance = fabric_update_bulk - - 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 = rest_send instance.results = Results() instance.payloads = payloads_fabric_update_bulk(key) @@ -1069,7 +991,7 @@ def responses(): 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) == "fabric_update" + assert instance.results.metadata[0].get("action", None) == "fabric_update_bulk" assert instance.results.metadata[1].get("action", None) == "config_save" assert instance.results.metadata[2].get("action", None) == "config_deploy" @@ -1097,31 +1019,13 @@ def responses(): assert instance.results.response[1].get("RETURN_CODE", None) == 200 assert instance.results.response[2].get("RETURN_CODE", None) == 200 - assert ( - instance.results.response[0] - .get("DATA", {}) - .get("nvPairs", {}) - .get("VPC_DELAY_RESTORE_TIME", None) - == "300" - ) - - assert ( - instance.results.response[0] - .get("DATA", {}) - .get("nvPairs", {}) - .get("ANYCAST_GW_MAC", None) - == "0001.aabb.ccdd" - ) - - assert ( - instance.results.response[1].get("DATA", {}).get("status", None) - == "Config save is completed" - ) - - assert ( - instance.results.response[2].get("DATA", {}).get("status", None) - == "Configuration deployment completed." - ) + assert instance.results.response[0].get("DATA", {}).get("nvPairs", {}).get("VPC_DELAY_RESTORE_TIME", None) == "300" + + assert instance.results.response[0].get("DATA", {}).get("nvPairs", {}).get("ANYCAST_GW_MAC", None) == "0001.aabb.ccdd" + + assert instance.results.response[1].get("DATA", {}).get("status", None) == "Config save is completed" + + assert instance.results.response[2].get("DATA", {}).get("status", None) == "Configuration deployment completed." assert instance.results.result[0].get("changed", None) is True assert instance.results.result[1].get("changed", None) is True @@ -1186,14 +1090,14 @@ def test_fabric_update_bulk_00036(fabric_update_bulk) -> None: - FabricUpdateCommon()._fabric_needs_update_for_merged_state() calls Results().register_task_result() - FabricUpdateCommon()._fabric_needs_update_for_merged_state() raises - ``ValueError`` because the payload contains an invalid key. + `ValueError` because the payload contains an invalid key. """ method_name = inspect.stack()[0][3] key = f"{method_name}a" def responses(): yield responses_fabric_details_by_name_v2(key) - yield responses_fabric_summary(key) + yield responses_fabric_summary_v2(key) yield responses_fabric_update_bulk(key) gen_responses = ResponseGenerator(responses()) @@ -1209,15 +1113,6 @@ def responses(): with does_not_raise(): instance = fabric_update_bulk - - 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 = rest_send instance.results = Results() instance.payloads = payloads_fabric_update_bulk(key) @@ -1239,7 +1134,7 @@ def responses(): assert instance.results.diff[0].get("sequence_number", None) == 1 - assert instance.results.metadata[0].get("action", None) == "fabric_update" + assert instance.results.metadata[0].get("action", None) == "fabric_update_bulk" 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" @@ -1343,7 +1238,7 @@ def responses(): 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_summary_v2(key) gen_responses = ResponseGenerator(responses()) @@ -1358,15 +1253,6 @@ def responses(): with does_not_raise(): instance = fabric_update_bulk - - 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 = rest_send instance.results = Results() instance.payloads = payloads_fabric_update_bulk(key) @@ -1390,7 +1276,7 @@ def responses(): 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) == "fabric_update" + assert instance.results.metadata[0].get("action", None) == "fabric_update_bulk" assert instance.results.metadata[1].get("action", None) == "config_save" assert instance.results.metadata[2].get("action", None) == "config_deploy" @@ -1418,18 +1304,9 @@ def responses(): assert instance.results.response[1].get("RETURN_CODE", None) == 200 assert instance.results.response[2].get("RETURN_CODE", None) == 200 - assert ( - instance.results.response[0] - .get("DATA", {}) - .get("nvPairs", {}) - .get("ANYCAST_GW_MAC", None) - == "0001.aabb.ccdd" - ) + assert instance.results.response[0].get("DATA", {}).get("nvPairs", {}).get("ANYCAST_GW_MAC", None) == "0001.aabb.ccdd" - assert ( - instance.results.response[1].get("DATA", {}).get("status", None) - == "Config save is completed" - ) + assert instance.results.response[1].get("DATA", {}).get("status", None) == "Config save is completed" msg = "FabricConfigDeploy._can_fabric_be_deployed: " msg += "Error during FabricSummary().refresh(). " @@ -1452,90 +1329,8 @@ def responses(): assert False in instance.results.changed -def test_fabric_update_bulk_00050(fabric_update_bulk) -> None: - """ - ### Classes and Methods - - - FabricCommon() - - __init__() - - FabricUpdateBulk() - - __init__() - - commit() - - ### Summary - - - Verify commit() raises ``ValueError`` if ``fabric_details`` is not set. - - ### 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() - instance.fabric_summary.rest_send = RestSend(PARAMS) - instance.fabric_summary.rest_send.unit_test = True - - instance.rest_send = RestSend(PARAMS) - instance.results = Results() - instance.payloads = [ - { - "BGP_AS": "65001", - "DEPLOY": "true", - "FABRIC_NAME": "f1", - "FABRIC_TYPE": "VXLAN_EVPN", - } - ] - - match = r"FabricUpdateBulk\.commit:\s+" - match += r"fabric_details must be set prior to calling commit\." - with pytest.raises(ValueError, match=match): - fabric_update_bulk.commit() - - -def test_fabric_update_bulk_00060(fabric_update_bulk) -> None: - """ - ### Classes and Methods - - - FabricCommon() - - __init__() - - FabricUpdateBulk() - - __init__() - - commit() - - ### Summary - - - Verify commit() raises ``ValueError`` if ``fabric_summary`` is not set. - - ### 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() - instance.fabric_details.rest_send = RestSend(PARAMS) - instance.fabric_details.rest_send.unit_test = True - - instance.rest_send = RestSend(PARAMS) - instance.results = Results() - instance.payloads = [ - { - "BGP_AS": "65001", - "DEPLOY": "true", - "FABRIC_NAME": "f1", - "FABRIC_TYPE": "VXLAN_EVPN", - } - ] - - match = r"FabricUpdateBulk\.commit:\s+" - match += r"fabric_summary must be set prior to calling commit\." - with pytest.raises(ValueError, match=match): - fabric_update_bulk.commit() +# test_fabric_update_bulk_00050 removed because fabric_details is now already set within FabricUpdateBulk() +# test_fabric_update_bulk_00060 removed because fabric_summary is now already set within FabricUpdateBulk() def test_fabric_update_bulk_00070(fabric_update_bulk) -> None: @@ -1550,7 +1345,7 @@ def test_fabric_update_bulk_00070(fabric_update_bulk) -> None: ### Summary - - Verify commit() raises ``ValueError`` if ``rest_send`` is not set. + - Verify commit() raises `ValueError` if ``rest_send`` is not set. ### Setup @@ -1559,15 +1354,6 @@ def test_fabric_update_bulk_00070(fabric_update_bulk) -> None: """ with does_not_raise(): instance = fabric_update_bulk - - instance.fabric_details = FabricDetailsByName() - instance.fabric_details.rest_send = RestSend(PARAMS) - instance.fabric_details.rest_send.unit_test = True - - instance.fabric_summary = FabricSummary() - instance.fabric_summary.rest_send = RestSend(PARAMS) - instance.fabric_summary.rest_send.unit_test = True - instance.results = Results() instance.payloads = [ { @@ -1597,9 +1383,7 @@ def test_fabric_update_bulk_00070(fabric_update_bulk) -> None: (None, None), ], ) -def test_fabric_update_bulk_00100( - value, expected_return_value, fabric_update_bulk -) -> None: +def test_fabric_update_bulk_00100(value, expected_return_value, fabric_update_bulk) -> None: """ ### Classes and Methods @@ -1649,13 +1433,13 @@ def test_fabric_update_bulk_00110(monkeypatch, fabric_update_bulk) -> None: ### Summary - Verify FabricUpdateCommon()._send_payloads() catches and - re-raises ``ValueError`` raised by + re-raises `ValueError` raised by FabricCommon()._fixup_payloads_to_commit() ### Setup - Mock FabricCommon()._fixup_payloads_to_commit() method to - raise ``ValueError``. + raise `ValueError`. - Monkeypatch FabricCommon()._fixup_payloads_to_commit() to the mocked method. - Populate FabricUpdateCommon._payloads_to_commit with a payload @@ -1665,7 +1449,7 @@ def test_fabric_update_bulk_00110(monkeypatch, fabric_update_bulk) -> None: def mock_fixup_payloads_to_commit() -> None: """ Mock the FabricUpdateCommon._fixup_payloads_to_commit() - to raise ``ValueError``. + to raise `ValueError`. """ msg = "raised FabricUpdateCommon._fixup_payloads_to_commit exception." raise ValueError(msg) @@ -1686,9 +1470,7 @@ def mock_fixup_payloads_to_commit() -> None: } ] - monkeypatch.setattr( - instance, "_fixup_payloads_to_commit", mock_fixup_payloads_to_commit - ) + monkeypatch.setattr(instance, "_fixup_payloads_to_commit", mock_fixup_payloads_to_commit) match = r"raised FabricUpdateCommon\._fixup_payloads_to_commit exception\." with pytest.raises(ValueError, match=match): @@ -1710,13 +1492,13 @@ def test_fabric_update_bulk_00120(monkeypatch, fabric_update_bulk) -> None: ### Summary - Verify FabricUpdateCommon()._send_payloads() catches and - re-raises ``ValueError`` raised by + re-raises `ValueError` raised by FabricCommon()._send_payload() ### Setup - Mock FabricCommon()._send_payload() method to - raise ``ValueError``. + raise `ValueError`. - Monkeypatch FabricCommon()._send_payload() to the mocked method. - Populate FabricUpdateCommon._payloads_to_commit with a payload which contains a valid payload. @@ -1724,7 +1506,7 @@ def test_fabric_update_bulk_00120(monkeypatch, fabric_update_bulk) -> None: def mock_send_payload(payload) -> None: """ - Mock the FabricCommon()._send_payload() ``ValueError``. + Mock the FabricCommon()._send_payload() `ValueError`. """ raise ValueError("raised FabricCommon.self_payload exception.") @@ -1765,13 +1547,13 @@ def test_fabric_update_bulk_00130(monkeypatch, fabric_update_bulk) -> None: ### Summary - Verify FabricUpdateCommon()._send_payloads() catches and - re-raises ``ValueError`` raised by + re-raises `ValueError` raised by FabricCommon()._config_save() ### Setup - Mock FabricCommon()._config_save() method to - raise ``ValueError``. + raise `ValueError`. - Monkeypatch FabricCommon()._config_save() to the mocked method. - Populate FabricUpdateCommon._payloads_to_commit with a payload which contains a valid payload. @@ -1781,14 +1563,14 @@ def test_fabric_update_bulk_00130(monkeypatch, fabric_update_bulk) -> None: def mock_config_save(payload) -> None: """ - Mock FabricCommon()._config_save() ``ValueError``. + Mock FabricCommon()._config_save() `ValueError`. """ fabric_name = payload.get("FABRIC_NAME", "unknown") raise ValueError(f"raised FabricCommon._config_save {fabric_name} exception.") def responses(): yield responses_fabric_details_by_name_v2(key) - yield responses_fabric_summary(key) + yield responses_fabric_summary_v2(key) gen_responses = ResponseGenerator(responses()) @@ -1803,15 +1585,6 @@ def responses(): with does_not_raise(): instance = fabric_update_bulk - - 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 = rest_send instance.results = Results() instance._payloads_to_commit = [ @@ -1845,13 +1618,13 @@ def test_fabric_update_bulk_00140(monkeypatch, fabric_update_bulk) -> None: ### Summary - Verify FabricUpdateCommon()._send_payloads() catches and - re-raises ``ValueError`` raised by + re-raises `ValueError` raised by FabricCommon()._config_deploy() ### Setup - Mock FabricCommon()._config_deploy() method to - raise ``ValueError``. + raise `ValueError`. - Monkeypatch FabricCommon()._config_deploy() to the mocked method. - Populate FabricUpdateCommon._payloads_to_commit with a payload which contains a valid payload. @@ -1861,7 +1634,7 @@ def test_fabric_update_bulk_00140(monkeypatch, fabric_update_bulk) -> None: def mock_config_deploy(payload) -> None: """ - Mock FabricCommon()._config_deploy() ``ValueError``. + Mock FabricCommon()._config_deploy() `ValueError`. """ fabric_name = payload.get("FABRIC_NAME", "unknown") msg = f"raised FabricCommon._config_deploy {fabric_name} exception." @@ -1869,7 +1642,7 @@ def mock_config_deploy(payload) -> None: def responses(): yield responses_fabric_details_by_name_v2(key) - yield responses_fabric_summary(key) + yield responses_fabric_summary_v2(key) gen_responses = ResponseGenerator(responses()) @@ -1884,15 +1657,6 @@ def responses(): with does_not_raise(): instance = fabric_update_bulk - - 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 = rest_send instance.results = Results() instance._payloads_to_commit = [ @@ -1913,7 +1677,19 @@ def responses(): def test_fabric_update_bulk_00150(monkeypatch, fabric_update_bulk) -> None: """ - ### Classes and Methods + # Summary + + Verify FabricUpdateCommon()._send_payload() catches and re-raises `ValueError` raised by + EpFabricUpdate().fabric_name setter. + + ## 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 + which contains a valid payload. + + ## Classes and Methods - EpFabricUpdate().fabric_name setter - FabricCommon() @@ -1921,25 +1697,11 @@ def test_fabric_update_bulk_00150(monkeypatch, fabric_update_bulk) -> None: - FabricUpdateCommon() - __init__() - _send_payload() - - - ### Summary - - - Verify FabricUpdateCommon()._send_payload() catches and - re-raises ``ValueError`` raised by - EpFabricUpdate().fabric_name setter. - - ### 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 - which contains a valid payload. """ class MockEpFabricUpdate: # pylint: disable=too-few-public-methods """ - Mock the MockEpFabricUpdate.fabric_name property to raise ``ValueError``. + Mock the MockEpFabricUpdate.fabric_name property to raise `ValueError`. """ @property @@ -1953,14 +1715,12 @@ def fabric_name(self, value): """ Mocked property setter """ - raise ValueError( - "mocked MockEpFabricUpdate().fabric_name setter exception." - ) + raise ValueError("mocked MockEpFabricUpdate().fabric_name setter exception.") with does_not_raise(): instance = fabric_update_bulk - monkeypatch.setattr(instance, "ep_fabric_update", MockEpFabricUpdate()) + monkeypatch.setattr(instance, "_ep_fabric_update", MockEpFabricUpdate()) payload = { "BGP_AS": "65001", From 28a1eb672d2526f302fef466e726fce834ada168 Mon Sep 17 00:00:00 2001 From: Allen Robel Date: Thu, 27 Nov 2025 10:47:57 -1000 Subject: [PATCH 15/24] Remove debug print() statements --- plugins/module_utils/fabric/config_deploy.py | 4 ---- plugins/module_utils/fabric/replaced.py | 1 - 2 files changed, 5 deletions(-) diff --git a/plugins/module_utils/fabric/config_deploy.py b/plugins/module_utils/fabric/config_deploy.py index 091c16eb4..84af92aee 100644 --- a/plugins/module_utils/fabric/config_deploy.py +++ b/plugins/module_utils/fabric/config_deploy.py @@ -194,8 +194,6 @@ def commit(self): """ method_name = inspect.stack()[0][3] - print("ZZZ: FabricConfigDeploy.commit: ENTERED") - if not self.payload: msg = f"{self.class_name}.{method_name}: " msg += f"{self.class_name}.payload must be set " @@ -382,7 +380,6 @@ def rest_send(self) -> RestSend: @rest_send.setter def rest_send(self, value: RestSend) -> None: method_name: str = inspect.stack()[0][3] - print("ZZZ: FabricConfigDeploy.rest_send.setter: ENTERED") _class_have: str = "" _class_need: Literal["RestSend"] = "RestSend" msg = f"{self.class_name}.{method_name}: " @@ -396,7 +393,6 @@ def rest_send(self, value: RestSend) -> None: if _class_have != _class_need: raise TypeError(msg) self._rest_send = value - print(f"ZZZ: FabricConfigDeploy._rest_send.params: {self._rest_send.params}") @property def results(self) -> Results: diff --git a/plugins/module_utils/fabric/replaced.py b/plugins/module_utils/fabric/replaced.py index 81c98373f..e9d7aba08 100644 --- a/plugins/module_utils/fabric/replaced.py +++ b/plugins/module_utils/fabric/replaced.py @@ -386,7 +386,6 @@ def _build_payloads_for_replaced_state(self): configuration. """ self.fabric_details.refresh() - print(f"ZZZ: self.fabric_details.all_data: {self.fabric_details.all_data}") self._payloads_to_commit = [] # Builds self.fabric_templates dictionary, keyed on fabric type. # Value is the fabric template associated with each fabric_type. From 39693f05a5f2554957540637e84e2bd87c69f9fb Mon Sep 17 00:00:00 2001 From: Allen Robel Date: Thu, 27 Nov 2025 10:48:10 -1000 Subject: [PATCH 16/24] Add module docstring --- plugins/module_utils/fabric/query.py | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/module_utils/fabric/query.py b/plugins/module_utils/fabric/query.py index e68b3088b..f913f884b 100644 --- a/plugins/module_utils/fabric/query.py +++ b/plugins/module_utils/fabric/query.py @@ -12,6 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. """ +Query fabrics. """ from __future__ import absolute_import, division, print_function From 59d8417aed829bb7b6d8c9ca6ae0bf40b6bac90c Mon Sep 17 00:00:00 2001 From: Allen Robel Date: Thu, 27 Nov 2025 10:53:02 -1000 Subject: [PATCH 17/24] Appease linters No functional changes in this commit. --- plugins/module_utils/fabric/create.py | 1 + plugins/modules/dcnm_fabric.py | 7 +++---- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/plugins/module_utils/fabric/create.py b/plugins/module_utils/fabric/create.py index 83d78cc26..d5679b51f 100644 --- a/plugins/module_utils/fabric/create.py +++ b/plugins/module_utils/fabric/create.py @@ -351,6 +351,7 @@ def results(self, value: Results) -> None: self._results.action = self.action self._results.operation_type = OperationType.CREATE + class FabricCreateBulk(FabricCreateCommon): """ Create fabrics in bulk. Skip any fabrics that already exist. diff --git a/plugins/modules/dcnm_fabric.py b/plugins/modules/dcnm_fabric.py index 5c6a0470b..b048ea74b 100644 --- a/plugins/modules/dcnm_fabric.py +++ b/plugins/modules/dcnm_fabric.py @@ -3873,7 +3873,7 @@ def get_have(self): ## Raises ### ValueError - + - The controller returns an error when attempting to retrieve the fabric details. """ @@ -4047,6 +4047,7 @@ def results(self, value: Results) -> None: raise TypeError(msg) self._results = value + class Deleted(Common): """ ### Summary @@ -4627,9 +4628,7 @@ def main(): "choices": ["deleted", "merged", "query", "replaced"], } - ansible_module = AnsibleModule( - argument_spec=argument_spec, supports_check_mode=True - ) + ansible_module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True) params = copy.deepcopy(ansible_module.params) params["check_mode"] = ansible_module.check_mode From 276f1cdfe8648b88d74f2554fac1431cffbacfed Mon Sep 17 00:00:00 2001 From: Allen Robel Date: Thu, 27 Nov 2025 11:52:32 -1000 Subject: [PATCH 18/24] Address Copilot review comments 1. plugins/module_utils/config_deploy.py - Remove commented code 2. tests/unit/modules/dcnm/dcnm_fabric/test_fabric_replaced_bulk.py - Remove commented test cases that are no longer needed 3. tests/unit/modules/dcnm/dcnm_fabric/test_fabric_create_common.py - Unused import of 'EpFabricCreate' removed --- plugins/module_utils/fabric/config_deploy.py | 52 ------------------- .../dcnm_fabric/test_fabric_create_common.py | 1 - .../dcnm_fabric/test_fabric_replaced_bulk.py | 48 ----------------- 3 files changed, 101 deletions(-) diff --git a/plugins/module_utils/fabric/config_deploy.py b/plugins/module_utils/fabric/config_deploy.py index 84af92aee..37a4f461a 100644 --- a/plugins/module_utils/fabric/config_deploy.py +++ b/plugins/module_utils/fabric/config_deploy.py @@ -274,58 +274,6 @@ def fabric_name(self, value): raise ValueError(error) from error self._fabric_name = value - # @property - # def fabric_details(self): - # """ - # - getter: Return an instance of the FabricDetailsByName class. - # - setter: Set an instance of the FabricDetailsByName class. - # - setter: Raise ``TypeError`` if the value is not an - # instance of FabricDetailsByName. - # """ - # return self._fabric_details - - # @fabric_details.setter - # def fabric_details(self, value): - # method_name = inspect.stack()[0][3] - # msg = f"{self.class_name}.{method_name}: " - # msg += "fabric_details must be an instance of FabricDetailsByName. " - # try: - # class_name = value.class_name - # except AttributeError as error: - # msg += f"Error detail: {error}. " - # raise TypeError(msg) from error - # if class_name != "FabricDetailsByName": - # msg += f"Got {class_name}." - # self.log.debug(msg) - # raise TypeError(msg) - # self._fabric_details = value - - # @property - # def fabric_summary(self): - # """ - # - getter: Return an instance of the FabricSummary class. - # - setter: Set an instance of the FabricSummary class. - # - setter: Raise ``TypeError`` if the value is not an - # instance of FabricSummary. - # """ - # return self._fabric_summary - - # @fabric_summary.setter - # def fabric_summary(self, value): - # method_name = inspect.stack()[0][3] - # msg = f"{self.class_name}.{method_name}: " - # msg += "fabric_summary must be an instance of FabricSummary. " - # try: - # class_name = value.class_name - # except AttributeError as error: - # msg += f"Error detail: {error}. " - # raise TypeError(msg) from error - # if class_name != "FabricSummary": - # msg += f"Got {class_name}." - # self.log.debug(msg) - # raise TypeError(msg) - # self._fabric_summary = value - @property def payload(self): """ 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 090426bca..8c63dc7ff 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 @@ -32,7 +32,6 @@ import inspect import pytest -from ansible_collections.cisco.dcnm.plugins.module_utils.common.api.v1.lan_fabric.rest.control.fabrics.fabrics import EpFabricCreate from ansible_collections.cisco.dcnm.plugins.module_utils.common.rest_send import RestSend from ansible_collections.cisco.dcnm.tests.unit.modules.dcnm.dcnm_fabric.utils import ( MockAnsibleModule, 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 941b0e11e..afd35cabe 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 @@ -449,54 +449,6 @@ def test_fabric_replaced_bulk_00050(fabric_replaced_bulk, user_value, controller # test_fabric_replaced_bulk_00200 removed because fabric_details is now set in FabricReplacedBulk.__init__() # test_fabric_replaced_bulk_00210 removed because fabric_summary is now set in FabricReplacedBulk.__init__() -# def test_fabric_replaced_bulk_00200(fabric_replaced_bulk) -> None: -# """ -# ### Classes and Methods - -# - FabricReplacedBulk -# - commit() - -# ### Summary -# Verify `ValueError`` is raised when ``fabric_details`` is not set before -# calling `commit`. -# """ -# with does_not_raise(): -# instance = fabric_replaced_bulk -# instance.payloads = [] -# 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 - -# - FabricReplacedBulk -# - commit() - -# ### Summary -# Verify `ValueError`` is raised when ``fabric_summary`` is not set before -# calling `commit`. -# """ -# with does_not_raise(): -# instance = fabric_replaced_bulk -# instance.fabric_details = FabricDetailsByName() -# instance.payloads = [] -# 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: """ From 841213534783b7a1f73281a0ec1b76030d21e0a2 Mon Sep 17 00:00:00 2001 From: Allen Robel Date: Thu, 27 Nov 2025 13:18:01 -1000 Subject: [PATCH 19/24] FabricCreateCommon: RestSend.setter; Add check for value.params Align @rest_send setter with latest enhancements by adding a check to verify value.params is set. --- plugins/module_utils/fabric/create.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/plugins/module_utils/fabric/create.py b/plugins/module_utils/fabric/create.py index d5679b51f..9d30d79c2 100644 --- a/plugins/module_utils/fabric/create.py +++ b/plugins/module_utils/fabric/create.py @@ -309,6 +309,11 @@ def rest_send(self, value: RestSend) -> None: raise TypeError(msg) from error if _class_have != _class_need: raise TypeError(msg) + if not value.params: + msg = f"{self.class_name}.{method_name}: " + msg += "RestSend.params must be set before assigning " + msg += "to FabricConfigDeploy.rest_send." + raise ValueError(msg) self._rest_send = value @property From ce6f532c81e2bf640795824ab06af43bd60df199 Mon Sep 17 00:00:00 2001 From: Allen Robel Date: Thu, 27 Nov 2025 13:21:06 -1000 Subject: [PATCH 20/24] RestSend.setter; Add check for value.params Align @rest_send setter with latest enhancements by adding a check to verify value.params is set. --- plugins/module_utils/fabric/fabric_details_v3.py | 5 +++++ plugins/module_utils/fabric/fabric_summary_v2.py | 5 +++++ plugins/module_utils/fabric/query.py | 5 +++++ plugins/module_utils/fabric/update.py | 5 +++++ 4 files changed, 20 insertions(+) diff --git a/plugins/module_utils/fabric/fabric_details_v3.py b/plugins/module_utils/fabric/fabric_details_v3.py index 8d42255a5..a0167072f 100644 --- a/plugins/module_utils/fabric/fabric_details_v3.py +++ b/plugins/module_utils/fabric/fabric_details_v3.py @@ -518,6 +518,11 @@ def rest_send(self, value: RestSend) -> None: raise TypeError(msg) from error if _class_have != _class_need: raise TypeError(msg) + if not value.params: + msg = f"{self.class_name}.{method_name}: " + msg += "RestSend.params must be set before assigning " + msg += "to FabricConfigDeploy.rest_send." + raise ValueError(msg) self._rest_send = value @property diff --git a/plugins/module_utils/fabric/fabric_summary_v2.py b/plugins/module_utils/fabric/fabric_summary_v2.py index ea3e4f6a5..2df9c9059 100644 --- a/plugins/module_utils/fabric/fabric_summary_v2.py +++ b/plugins/module_utils/fabric/fabric_summary_v2.py @@ -467,6 +467,11 @@ def rest_send(self, value: RestSend) -> None: raise TypeError(msg) from error if _class_have != _class_need: raise TypeError(msg) + if not value.params: + msg = f"{self.class_name}.{method_name}: " + msg += "RestSend.params must be set before assigning " + msg += "to FabricConfigDeploy.rest_send." + raise ValueError(msg) self._rest_send = value @property diff --git a/plugins/module_utils/fabric/query.py b/plugins/module_utils/fabric/query.py index f913f884b..ce00d50e7 100644 --- a/plugins/module_utils/fabric/query.py +++ b/plugins/module_utils/fabric/query.py @@ -274,6 +274,11 @@ def rest_send(self, value: RestSend) -> None: raise TypeError(msg) from error if _class_have != _class_need: raise TypeError(msg) + if not value.params: + msg = f"{self.class_name}.{method_name}: " + msg += "RestSend.params must be set before assigning " + msg += "to FabricConfigDeploy.rest_send." + raise ValueError(msg) self._rest_send = value @property diff --git a/plugins/module_utils/fabric/update.py b/plugins/module_utils/fabric/update.py index 8317f1c59..315cd26d0 100644 --- a/plugins/module_utils/fabric/update.py +++ b/plugins/module_utils/fabric/update.py @@ -503,6 +503,11 @@ def rest_send(self, value: RestSend) -> None: raise TypeError(msg) from error if _class_have != _class_need: raise TypeError(msg) + if not value.params: + msg = f"{self.class_name}.{method_name}: " + msg += "RestSend.params must be set before assigning " + msg += "to FabricConfigDeploy.rest_send." + raise ValueError(msg) self._rest_send = value @property From d703426d09b1e5c6cbdc2abe5d76e35b0419d6d2 Mon Sep 17 00:00:00 2001 From: Allen Robel Date: Thu, 27 Nov 2025 13:34:14 -1000 Subject: [PATCH 21/24] =?UTF-8?q?FabricQuery.=5F=5Finit=5F=5F():=20don?= =?UTF-8?q?=E2=80=99t=20set=20FabricDetailsByName?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Wait until commit() to set: FabricDetailsByName.rest_send FabricDetailsByName.results Since rest_send and results are not fully initialized until commit() is called. --- plugins/module_utils/fabric/query.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/plugins/module_utils/fabric/query.py b/plugins/module_utils/fabric/query.py index ce00d50e7..3a1b70e64 100644 --- a/plugins/module_utils/fabric/query.py +++ b/plugins/module_utils/fabric/query.py @@ -88,6 +88,7 @@ def __init__(self) -> None: self.log: logging.Logger = logging.getLogger(f"dcnm.{self.class_name}") + self._fabric_details_by_name: FabricDetailsByName = FabricDetailsByName() self._results: Results = Results() self._results.operation_type = OperationType.QUERY self._rest_send: RestSend = RestSend(params={}) @@ -95,10 +96,6 @@ def __init__(self) -> None: self._fabric_names: list[str] = [] self._fabrics_to_query: list[str] = [] - self._fabric_details_by_name: FabricDetailsByName = FabricDetailsByName() - self._fabric_details_by_name.rest_send = self._rest_send - self._fabric_details_by_name.results = self._results - msg = "ENTERED FabricQuery()" self.log.debug(msg) From 9175352b5b4dd41e0bac3f48d79fae625e284d41 Mon Sep 17 00:00:00 2001 From: Allen Robel Date: Sun, 30 Nov 2025 11:47:19 -1000 Subject: [PATCH 22/24] Fix for query-state config # Summary Fix issue where query-state configuration is ignored. Not sure why this ever worked? I think we initially wanted an empty query config to return all fabrics. Maybe we still want this; in which case we need to modify query.py accordingly. Anyway, for now, we populate self.config for query-state if params is not None. An alternative fix would be to mandate that params is not empty and add query-state to the list of states that requires a config. Currently, in query.py, a config is required so an empty config will result in an error here. This fix at least ensures that a populated query config is accepted in dcnm_fabric.py and passed downstream to query.py. --- plugins/modules/dcnm_fabric.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/plugins/modules/dcnm_fabric.py b/plugins/modules/dcnm_fabric.py index b048ea74b..901af9d19 100644 --- a/plugins/modules/dcnm_fabric.py +++ b/plugins/modules/dcnm_fabric.py @@ -3772,6 +3772,7 @@ def __init__(self, params: dict[str, Any]) -> None: self._implemented_states: set = set() self.params: dict[str, Any] = params + # populated in self.validate_input() self.payloads: dict[str, Any] = {} @@ -3791,7 +3792,8 @@ def __init__(self, params: dict[str, Any]) -> None: msg = "ENTERED Common(): " msg += f"state: {self.state}, " - msg += f"check_mode: {self.check_mode}" + msg += f"check_mode: {self.check_mode}, " + msg += f"params: {json_pretty(self.params)}" self.log.debug(msg) def populate_check_mode(self): @@ -3837,6 +3839,15 @@ def populate_config(self): msg += f"got {type(self.params['config']).__name__}" raise ValueError(msg) self.config = self.params["config"] + else: + # For "query" state, config is optional + if self.params.get("config") is not None: + if not isinstance(self.params["config"], list): + msg = f"{self.class_name}.{method_name}: " + msg += "expected list type for self.config. " + msg += f"got {type(self.params['config']).__name__}" + raise ValueError(msg) + self.config = self.params["config"] def populate_state(self): """ From d3ea42bcadbb45b0717d14718bba24af96c47581 Mon Sep 17 00:00:00 2001 From: Allen Robel Date: Mon, 1 Dec 2025 06:53:40 -1000 Subject: [PATCH 23/24] Fix typo in docstring (address Copilot comment) No functional changes in this commit. --- plugins/module_utils/fabric/update.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/module_utils/fabric/update.py b/plugins/module_utils/fabric/update.py index 315cd26d0..49df59aa3 100644 --- a/plugins/module_utils/fabric/update.py +++ b/plugins/module_utils/fabric/update.py @@ -355,7 +355,7 @@ class FabricUpdateBulk(FabricUpdateCommon): Update fabrics in bulk. - ##vUsage + ## Usage ```python from ansible_collections.cisco.dcnm.plugins.module_utils.fabric.update import FabricUpdateBulk From b895a009c686863e33dfa2cfa9438df672728dd4 Mon Sep 17 00:00:00 2001 From: Allen Robel Date: Wed, 3 Dec 2025 10:31:12 -1000 Subject: [PATCH 24/24] Address Sanity error (pylint) Address the following error in sanity tests: ERROR: tests/unit/modules/dcnm/dcnm_fabric/utils.py:20:0: misplaced-future: __future__ import is not the first non docstring statement --- tests/unit/modules/dcnm/dcnm_fabric/utils.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/tests/unit/modules/dcnm/dcnm_fabric/utils.py b/tests/unit/modules/dcnm/dcnm_fabric/utils.py index 821ce0c28..430066599 100644 --- a/tests/unit/modules/dcnm/dcnm_fabric/utils.py +++ b/tests/unit/modules/dcnm/dcnm_fabric/utils.py @@ -14,9 +14,6 @@ # 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. -""" -Utilities for dcnm_fabric module unit tests -""" from __future__ import absolute_import, division, print_function __metaclass__ = type # pylint: disable=invalid-name