diff --git a/plugins/module_utils/common/enums.py b/plugins/module_utils/common/enums.py new file mode 100644 index 000000000..9918f3aad --- /dev/null +++ b/plugins/module_utils/common/enums.py @@ -0,0 +1,111 @@ +""" +Common enum definitions for module utilities in module_utils/common +""" +# 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. + +from __future__ import absolute_import, annotations, division, print_function + +__metaclass__ = type # pylint: disable=invalid-name +__author__ = "Allen Robel" + +from enum import Enum + + +class MaintenanceModeSetEnum(str, Enum): + """ + # Summary + + Valid maintenance mode values for switches. + + These are modes that the user can set. + + ## Raises + + None + + ## Values + + - `MAINTENANCE`: Switch is in maintenance mode + - `NORMAL`: Switch is in normal operational mode + + ## See also + + MaintenanceModeGetEnum + """ + + MAINTENANCE = "maintenance" + NORMAL = "normal" + + @classmethod + def values(cls) -> list[str]: + """ + # Summary + + Return a set of valid maintenance mode values. + + ## Raises + + None + + ## Returns + + List of string values for all maintenance modes. + """ + return [mode.value for mode in cls] + + +class MaintenanceModeGetEnum(str, Enum): + """ + # Summary + + Valid maintenance mode values for switches. + + These are modes that the user can retrieve. + + ## Raises + + None + + ## Values + + - `INCONSISTENT`: A synthesized mode indicating that mode != systemMode + - `MAINTENANCE`: Switch is in maintenance mode + - `NORMAL`: Switch is in normal operational mode + + ## See also + + MaintenanceModeGetEnum + """ + + INCONSISTENT = "inconsistent" + MAINTENANCE = "maintenance" + NORMAL = "normal" + + @classmethod + def values(cls) -> list[str]: + """ + # Summary + + Return a set of valid maintenance mode values. + + ## Raises + + None + + ## Returns + + List of string values for all maintenance modes. + """ + return [mode.value for mode in cls] diff --git a/plugins/module_utils/common/maintenance_mode.py b/plugins/module_utils/common/maintenance_mode.py index 00f4dce5f..d0bf0a4c5 100644 --- a/plugins/module_utils/common/maintenance_mode.py +++ b/plugins/module_utils/common/maintenance_mode.py @@ -1,3 +1,7 @@ +""" +Modify the maintenance mode state of switches and optionally deploy the changes +""" + # Copyright (c) 2024 Cisco and/or its affiliates. # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -11,58 +15,55 @@ # 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 +from __future__ import absolute_import, annotations, division, print_function -__metaclass__ = type +__metaclass__ = type # pylint: disable=invalid-name __author__ = "Allen Robel" -# Required for class decorators -# pylint: disable=no-member - import copy import inspect import logging +from typing import Any, Literal -from .api.v1.lan_fabric.rest.control.fabrics.fabrics import ( - EpFabricConfigDeploy, EpMaintenanceModeDeploy, EpMaintenanceModeDisable, - EpMaintenanceModeEnable) +from .api.v1.lan_fabric.rest.control.fabrics.fabrics import EpMaintenanceModeDeploy, EpMaintenanceModeDisable, EpMaintenanceModeEnable from .conversion import ConversionUtils +from .enums import MaintenanceModeSetEnum from .exceptions import ControllerResponseError -from .properties import Properties +from .rest_send_v2 import RestSend +from .results import Results -@Properties.add_rest_send -@Properties.add_results class MaintenanceMode: """ - ### Summary - - Modify the maintenance mode state of switches. - - Optionally deploy the changes. - - ### Raises - - ``ValueError`` in the following methods: - - __init__() if params is missing mandatory parameters - ``check_mode`` or ``state``. - - - ``ValueError`` in the following properties: - - ``config`` if config contains invalid content. - - ``commit`` if config, rest_send, or results are not set. - - ``commit`` if ``EpMaintenanceModeEnable`` or - ``EpMaintenanceModeDisable`` raise ``ValueError``. - - ``commit`` if either ``chance_system_mode()`` or - ``deploy_switches()`` raise ``ControllerResponseError``. - - - ``TypeError`` in the following properties: - - ``rest_send`` if value is not an instance of RestSend. - - ``results`` if value is not an instance of Results. - - ### Details - - Updates MaintenanceMode().results to reflect success/failure of - the operation on the controller. - - For switches that are to be deployed, initiates a per-fabric - bulk switch config-deploy. - - ### Example value for ``config`` in the ``Usage`` section below: + # Summary + + Modify the maintenance mode state of switches and optionally deploy the changes. + + ## Raises + + ### ValueError + + - `__init__()`: params is missing mandatory parameter `state`. + - `config` property setter: config contains invalid content. + - `commit()`: config or rest_send.params are not set. + - `commit()`: `EpMaintenanceModeEnable` or `EpMaintenanceModeDisable` raise `ValueError`. + - `commit()`: either `change_system_mode()` or `deploy_switches()` raise `ControllerResponseError`. + - `rest_send` property setter: rest_send.params is not set. + + ### TypeError + + - `rest_send` property setter: value is not an instance of RestSend. + - `results` property setter: value is not an instance of Results. + + ## Details + + - Updates MaintenanceMode().results to reflect success/failure of + the operation on the controller. + - For switches that are to be deployed, initiates a per-fabric + bulk switch config-deploy. + + ## Example value for `config` in the `Usage` section below + ```json [ { @@ -82,16 +83,17 @@ class MaintenanceMode: ] ``` - ### Usage - - Where ``params`` is ``AnsibleModule.params`` - - Where ``config`` is a list of dicts, each containing the following: - - ``deploy``: ``bool``. If True, the switch maintenance mode - will be deployed. - - ``fabric_name``: ``str``. The name of the switch's hosting fabric. - - ``ip_address``: ``str``. The ip address of the switch. - - ``mode``: ``str``. The intended maintenance mode. Must be one of - "maintenance" or "normal". - - ``serial_number``: ``str``. The serial number of the switch. + ## Usage + + Where `params` is `AnsibleModule.params` and `config` is a list of dicts, + each containing the following: + + - `deploy`: `bool`. If True, the switch maintenance mode will be deployed. + - `fabric_name`: `str`. The name of the switch's hosting fabric. + - `ip_address`: `str`. The ip address of the switch. + - `mode`: `str`. The intended maintenance mode. Must be one of + "maintenance" or "normal". + - `serial_number`: `str`. The serial number of the switch. ```python instance = MaintenanceMode(params) @@ -108,67 +110,71 @@ class MaintenanceMode: ``` """ - def __init__(self, params): - self.class_name = self.__class__.__name__ - method_name = inspect.stack()[0][3] + def __init__(self, params: dict[str, Any]) -> None: + self.class_name: str = self.__class__.__name__ + method_name: str = inspect.stack()[0][3] - self.log = logging.getLogger(f"dcnm.{self.class_name}") + self.log: logging.Logger = logging.getLogger(f"dcnm.{self.class_name}") - self.params = params - self.action = "maintenance_mode" - self.endpoints = [] + self.params: dict[str, Any] = params + self.action: str = "maintenance_mode" + self._endpoints: list[dict[str, str]] = [] - self.check_mode = self.params.get("check_mode", None) - if self.check_mode is None: - msg = f"{self.class_name}.{method_name}: " - msg += "params is missing mandatory parameter: check_mode." - raise ValueError(msg) + self._check_mode: bool = self.params.get("check_mode", False) - self.state = self.params.get("state", None) - if self.state is None: + self.state = self.params.get("state", "") + if not self.state: msg = f"{self.class_name}.{method_name}: " msg += "params is missing mandatory parameter: state." raise ValueError(msg) # Populated in build_deploy_dict() - self.deploy_dict = {} - self.serial_number_to_ip_address = {} + self._deploy_dict: dict[str, Any] = {} + self.serial_number_to_ip_address: dict[str, str] = {} - self.valid_modes = ["maintenance", "normal"] + self._valid_modes: list[str] = MaintenanceModeSetEnum.values() - self.conversion = ConversionUtils() - self.ep_maintenance_mode_deploy = EpMaintenanceModeDeploy() - self.ep_maintenance_mode_disable = EpMaintenanceModeDisable() - self.ep_maintenance_mode_enable = EpMaintenanceModeEnable() - self.ep_fabric_config_deploy = EpFabricConfigDeploy() + self._conversion = ConversionUtils() + self._ep_maintenance_mode_deploy = EpMaintenanceModeDeploy() + self._ep_maintenance_mode_disable = EpMaintenanceModeDisable() + self._ep_maintenance_mode_enable = EpMaintenanceModeEnable() - self._config = None - self._rest_send = None - self._results = None + self._config: list[dict[str, Any]] = [] + self._rest_send: RestSend = RestSend({}) + self._results: Results = Results() msg = "ENTERED MaintenanceMode(): " - msg += f"check_mode: {self.check_mode}, " + msg += f"check_mode: {self._check_mode}, " msg += f"state: {self.state}" self.log.debug(msg) - def verify_config_parameters(self, value) -> None: + def verify_config_parameters(self, value: list[dict[str, Any]]) -> None: """ - ### Summary + # Summary + Verify that required parameters are present in config. - ### Raises - - ``TypeError`` if ``config`` is not a list. - - ``ValueError`` if ``config`` contains invalid content. + ## Raises + + ### TypeError + + - `config` is not a list. + + ### ValueError + + - `config` contains invalid content. - ### NOTES - 1. See the following validation methods for details: - - verify_deploy() - - verify_fabric_name() - - verify_ip_address() - - verify_mode() - - verify_serial_number() + ## Notes + + See the following validation methods for details: + + - verify_deploy() + - verify_fabric_name() + - verify_ip_address() + - verify_mode() + - verify_serial_number() """ - method_name = inspect.stack()[0][3] + method_name: str = inspect.stack()[0][3] if not isinstance(value, list): msg = f"{self.class_name}.{method_name}: " msg += f"{self.class_name}.config must be a list. " @@ -186,18 +192,23 @@ def verify_config_parameters(self, value) -> None: except (TypeError, ValueError) as error: raise ValueError(error) from error - def verify_deploy(self, item) -> None: + def verify_deploy(self, item: dict[str, Any]) -> None: """ - ### Summary - Verify the ``deploy`` parameter. + # Summary + + Verify the `deploy` parameter. + + ## Raises + + ### ValueError + + - `deploy` is not present. + + ### TypeError - ### Raises - - ``ValueError`` if: - - ``deploy`` is not present. - - ``TypeError`` if: - - `deploy`` is not a boolean. + - `deploy` is not a boolean. """ - method_name = inspect.stack()[0][3] + method_name: str = inspect.stack()[0][3] if item.get("deploy", None) is None: msg = f"{self.class_name}.{method_name}: " msg += "config is missing mandatory key: deploy." @@ -209,89 +220,106 @@ def verify_deploy(self, item) -> None: msg += f"value {item.get('deploy', None)}." raise TypeError(msg) - def verify_fabric_name(self, item) -> None: + def verify_fabric_name(self, item: dict[str, Any]) -> None: """ - ### Summary - Validate the ``fabric_name`` parameter. + # Summary - ### Raises - - ``ValueError`` if: - - ``fabric_name`` is not present. - - ``fabric_name`` is not a valid fabric name. + Validate the `fabric_name` parameter. + + ## Raises + + ### ValueError + + - `fabric_name` is not present. + - `fabric_name` is not a valid fabric name. """ - method_name = inspect.stack()[0][3] + method_name: str = inspect.stack()[0][3] if item.get("fabric_name", None) is None: msg = f"{self.class_name}.{method_name}: " msg += "config is missing mandatory key: fabric_name." raise ValueError(msg) try: - self.conversion.validate_fabric_name(item.get("fabric_name", None)) + self._conversion.validate_fabric_name(item.get("fabric_name", None)) except (TypeError, ValueError) as error: raise ValueError(error) from error - def verify_ip_address(self, item) -> None: + def verify_ip_address(self, item: dict[str, Any]) -> None: """ - ### Summary - Validate the ``ip_address`` parameter. + # Summary + + Validate the `ip_address` parameter. - ### Raises - - ``ValueError`` if: - - ``ip_address`` is not present. + ## Raises + + ### ValueError + + - `ip_address` is not present. """ - method_name = inspect.stack()[0][3] + method_name: str = inspect.stack()[0][3] if item.get("ip_address", None) is None: msg = f"{self.class_name}.{method_name}: " msg += "config is missing mandatory key: ip_address." raise ValueError(msg) - def verify_mode(self, item) -> None: + def verify_mode(self, item: dict[str, Any]) -> None: """ - ### Summary - Validate the ``mode`` parameter. + # Summary + + Validate the `mode` parameter. - ### Raises - - ``ValueError`` if: - - ``mode`` is not present. - - ``mode`` is not one of "maintenance" or "normal". + ## Raises + + ### ValueError + + - `mode` is not present. + - `mode` is not one of "maintenance" or "normal". """ - method_name = inspect.stack()[0][3] + method_name: str = inspect.stack()[0][3] if item.get("mode", None) is None: msg = f"{self.class_name}.{method_name}: " msg += "config is missing mandatory key: mode." raise ValueError(msg) - if item.get("mode", None) not in self.valid_modes: + if item.get("mode", None) not in self._valid_modes: msg = f"{self.class_name}.{method_name}: " - msg += f"mode must be one of {' or '.join(self.valid_modes)}. " + msg += f"mode must be one of {' or '.join(list(self._valid_modes))}. " msg += f"Got {item.get('mode', None)}." raise ValueError(msg) - def verify_serial_number(self, item) -> None: + def verify_serial_number(self, item: dict[str, Any]) -> None: """ - ### Summary - Validate the ``serial_number`` parameter. + # Summary + + Validate the `serial_number` parameter. + + ## Raises - ### Raises - - ``ValueError`` if: - - ``serial_number`` is not present. + ### ValueError + + - `serial_number` is not present. """ - method_name = inspect.stack()[0][3] + method_name: str = inspect.stack()[0][3] if item.get("serial_number", None) is None: msg = f"{self.class_name}.{method_name}: " msg += "config is missing mandatory key: serial_number." raise ValueError(msg) - def verify_wait_for_mode_change(self, item) -> None: + def verify_wait_for_mode_change(self, item: dict[str, Any]) -> None: """ - ### Summary - Verify the ``wait_for_mode_change`` parameter. + # Summary + + Verify the `wait_for_mode_change` parameter. + + ## Raises - ### Raises - - ``ValueError`` if: - - ``wait_for_mode_change`` is not present. - - ``TypeError`` if: - - `wait_for_mode_change`` is not a boolean. + ### ValueError + + - `wait_for_mode_change` is not present. + + ### TypeError + + - `wait_for_mode_change` is not a boolean. """ - method_name = inspect.stack()[0][3] + method_name: str = inspect.stack()[0][3] if item.get("wait_for_mode_change", None) is None: msg = f"{self.class_name}.{method_name}: " msg += "config is missing mandatory key: wait_for_mode_change." @@ -305,46 +333,46 @@ def verify_wait_for_mode_change(self, item) -> None: def verify_commit_parameters(self) -> None: """ - ### Summary + # Summary + Verify that required parameters are present before calling commit. - ### Raises - - ``ValueError`` if: - - ``config`` is not set. - - ``rest_send`` is not set. - - ``results`` is not set. + ## Raises + + ### ValueError + + - `config` is not set. + - `rest_send.params` is not set. """ - method_name = inspect.stack()[0][3] - if self.config is None: + method_name: str = inspect.stack()[0][3] + if not self.config: msg = f"{self.class_name}.{method_name}: " msg += f"{self.class_name}.config 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) def commit(self) -> None: """ - ### Summary + # Summary + Initiates the maintenance mode change on the controller. - ### Raises - - ``ValueError`` if - - ``config`` is not set. - - ``rest_send`` is not set. - - ``results`` is not set. - - any exception is raised by: - - ``verify_commit_parameters()`` - - ``change_system_mode()`` - - ``deploy_switches()`` + ## Raises + + ### ValueError + + - `config` is not set. + - `rest_send` is not set. + - `results` is not set. + - any exception is raised by: + - `verify_commit_parameters()` + - `change_system_mode()` + - `deploy_switches()` """ try: self.verify_commit_parameters() @@ -359,20 +387,27 @@ def commit(self) -> None: def change_system_mode(self) -> None: """ - ### Summary + # Summary + Send the maintenance mode change request to the controller. - ### Raises - - ``ControllerResponseError`` if: - - controller response != 200. - - ``ValueError`` if: - - ``fabric_name`` is invalid. - - endpoint cannot be resolved. - - ``Results()`` raises an exception. - - ``TypeError`` if: - - ``serial_number`` is not a string. + ## Raises + + ### ControllerResponseError + + - controller response != 200. + + ### ValueError + + - `fabric_name` is invalid. + - endpoint cannot be resolved. + - `Results()` raises an exception. + + ### TypeError + + - `serial_number` is not a string. """ - method_name = inspect.stack()[0][3] + method_name: str = inspect.stack()[0][3] for item in self.config: # Build endpoint @@ -380,10 +415,10 @@ def change_system_mode(self) -> None: fabric_name = item.get("fabric_name") ip_address = item.get("ip_address") serial_number = item.get("serial_number") - if mode == "normal": - endpoint = self.ep_maintenance_mode_disable + if mode == MaintenanceModeSetEnum.NORMAL.value: + endpoint = self._ep_maintenance_mode_disable else: - endpoint = self.ep_maintenance_mode_enable + endpoint = self._ep_maintenance_mode_enable try: endpoint.fabric_name = fabric_name @@ -415,14 +450,10 @@ def change_system_mode(self) -> None: # register result try: self.results.action = "change_sytem_mode" - self.results.check_mode = self.check_mode + self.results.check_mode = self._check_mode self.results.state = self.state - self.results.response_current = copy.deepcopy( - self.rest_send.response_current - ) - self.results.result_current = copy.deepcopy( - self.rest_send.result_current - ) + self.results.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() except (TypeError, ValueError) as error: raise ValueError(error) from error @@ -438,77 +469,84 @@ def change_system_mode(self) -> None: def build_deploy_dict(self) -> None: """ - ### Summary - - Build the deploy_dict + # Summary + + Build the deploy_dict + + ## Raises - ### Raises None - ### Structure - - key: fabric_name - - value: list of dict - - each dict contains ``serial_number`` and ``wait_for_mode_change keys`` + ## Structure + + - key: fabric_name + - value: list of dict + - each dict contains `serial_number` and `wait_for_mode_change` keys + + ## Example - ### Example ```json { "MyFabric": [ { "serial_number": "CDM4593459", - "wait_for_mode_change": True + "wait_for_mode_change": true }, { "serial_number": "CDM4593460", - "wait_for_mode_change": False + "wait_for_mode_change": false } ], "YourFabric": [ { "serial_number": "DDM0455882", - "wait_for_mode_change": True + "wait_for_mode_change": true }, { "serial_number": "DDM5598759", - "wait_for_mode_change": True + "wait_for_mode_change": true } ] } + ``` """ - self.deploy_dict = {} + self._deploy_dict = {} for item in self.config: fabric_name = item.get("fabric_name") serial_number = item.get("serial_number") deploy = item.get("deploy") wait_for_mode_change = item.get("wait_for_mode_change") - if fabric_name not in self.deploy_dict: - self.deploy_dict[fabric_name] = [] + if fabric_name not in self._deploy_dict: + self._deploy_dict[fabric_name] = [] item_dict = {} if deploy is True: item_dict["serial_number"] = serial_number item_dict["wait_for_mode_change"] = wait_for_mode_change - self.deploy_dict[fabric_name].append(item_dict) + self._deploy_dict[fabric_name].append(item_dict) def build_serial_number_to_ip_address(self) -> None: """ - ### Summary + # Summary + Populate self.serial_number_to_ip_address dict. - ### Raises + ## Raises + None - ### Structure - - key: switch serial_number - - value: associated switch ip_address + ## Structure + + - key: switch serial_number + - value: associated switch ip_address ```json { "CDM4593459": "192.168.1.2" } ``` - ### Raises - None - ### Notes - - ip_address and serial_number are added to the diff in the - ``deploy_switches()`` method. + ## Notes + + ip_address and serial_number are added to the diff in the + `deploy_switches()` method. """ for item in self.config: serial_number = item.get("serial_number") @@ -517,45 +555,54 @@ def build_serial_number_to_ip_address(self) -> None: def build_endpoints(self) -> None: """ - ### Summary - Build ``endpoints`` dict used in ``self.deploy_switches``. + # Summary + + Build `endpoints` dict used in `self.deploy_switches`. + + ## Raises + + ### ValueError - ### Raises - ``ValueError`` if endpoint configuration fails. + - endpoint configuration fails. """ - method_name = inspect.stack()[0][3] + method_name: str = inspect.stack()[0][3] endpoints = [] - for fabric_name, switches in self.deploy_dict.items(): + for fabric_name, switches in self._deploy_dict.items(): for item in switches: endpoint = {} try: - self.ep_maintenance_mode_deploy.fabric_name = fabric_name - self.ep_maintenance_mode_deploy.serial_number = item["serial_number"] - self.ep_maintenance_mode_deploy.wait_for_mode_change = item["wait_for_mode_change"] + self._ep_maintenance_mode_deploy.fabric_name = fabric_name + self._ep_maintenance_mode_deploy.serial_number = item["serial_number"] + self._ep_maintenance_mode_deploy.wait_for_mode_change = item["wait_for_mode_change"] except (KeyError, TypeError, ValueError) as error: msg = f"{self.class_name}.{method_name}: " msg += "Error resolving endpoint: " msg += f"Error details: {error}." raise ValueError(msg) from error - endpoint["path"] = self.ep_maintenance_mode_deploy.path - endpoint["verb"] = self.ep_maintenance_mode_deploy.verb - endpoint["serial_number"] = self.ep_maintenance_mode_deploy.serial_number + endpoint["path"] = self._ep_maintenance_mode_deploy.path + endpoint["verb"] = self._ep_maintenance_mode_deploy.verb + endpoint["serial_number"] = self._ep_maintenance_mode_deploy.serial_number endpoint["fabric_name"] = fabric_name endpoints.append(copy.copy(endpoint)) - self.endpoints = copy.copy(endpoints) + self._endpoints = copy.copy(endpoints) def deploy_switches(self) -> None: """ - ### Summary - Initiate config-deploy for the switches in ``self.deploy_dict``. + # Summary + + Initiate config-deploy for the switches in `self._deploy_dict`. + + ## Raises + + ### ControllerResponseError - ### Raises - - ``ControllerResponseError`` if: - - controller response != 200. - - ``ValueError`` if: - - endpoint cannot be resolved. + - controller response != 200. + + ### ValueError + + - endpoint cannot be resolved. """ - method_name = inspect.stack()[0][3] + method_name: str = inspect.stack()[0][3] self.build_deploy_dict() self.build_serial_number_to_ip_address() try: @@ -566,7 +613,7 @@ def deploy_switches(self) -> None: msg += f"Error detail: {error}" raise ValueError(msg) from error - for endpoint in self.endpoints: + for endpoint in self._endpoints: # Send request self.rest_send.path = endpoint["path"] self.rest_send.verb = endpoint["verb"] @@ -586,11 +633,9 @@ def deploy_switches(self) -> None: self.results.diff_current = diff self.results.action = action - self.results.check_mode = self.check_mode + self.results.check_mode = self._check_mode self.results.state = self.state - self.results.response_current = copy.deepcopy( - self.rest_send.response_current - ) + self.results.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() @@ -604,33 +649,32 @@ def deploy_switches(self) -> None: raise ControllerResponseError(msg) @property - def config(self) -> list: + def config(self) -> list[dict[str, Any]]: """ - ### Summary - The maintenance mode configurations to be sent to the controller. + # Summary + + Get/set the maintenance mode configurations to be sent to the controller. + + ## Raises - ### Raises - - setter: ``ValueError`` if: - - value is not a list. - - value contains invalid content. + ### ValueError - ### getter - Return ``config``. + - setter: value is not a list. + - setter: value contains invalid content. - ### setter - Set ``config``. + ## Value structure - ### Value structure - value is a ``list`` of ``dict``. Each dict must contain the following: - - ``deploy``: ``bool``. If True, the switch maintenance mode - will be deployed. - - ``fabric_name``: ``str``. The name of the switch's hosting fabric. - - ``ip_address``: ``str``. The ip address of the switch. - - ``mode``: ``str``. The intended maintenance mode. Must be one of - "maintenance" or "normal". - - ``serial_number``: ``str``. The serial number of the switch. + value is a `list` of `dict`. Each dict must contain the following: + + - `deploy`: `bool`. If True, the switch maintenance mode will be deployed. + - `fabric_name`: `str`. The name of the switch's hosting fabric. + - `ip_address`: `str`. The ip address of the switch. + - `mode`: `str`. The intended maintenance mode. Must be one of + "maintenance" or "normal". + - `serial_number`: `str`. The serial number of the switch. + + ## Example - ### Example ```json [ { @@ -653,9 +697,91 @@ def config(self) -> list: return self._config @config.setter - def config(self, value): + def config(self, value: list[dict[str, Any]]) -> None: try: self.verify_config_parameters(value) except (TypeError, ValueError) as error: raise ValueError(error) from error self._config = 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 diff --git a/plugins/module_utils/common/maintenance_mode_info.py b/plugins/module_utils/common/maintenance_mode_info.py index ded423897..2bdf83b8e 100644 --- a/plugins/module_utils/common/maintenance_mode_info.py +++ b/plugins/module_utils/common/maintenance_mode_info.py @@ -1,3 +1,6 @@ +""" +Retrieve the maintenance mode state of switches. +""" # Copyright (c) 2024 Cisco and/or its affiliates. # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -13,7 +16,7 @@ # limitations under the License. from __future__ import absolute_import, division, print_function -__metaclass__ = type +__metaclass__ = type # pylint: disable=invalid-name __author__ = "Allen Robel" # Required for class decorators @@ -22,72 +25,85 @@ import copy import inspect import logging +from typing import Any, Literal +# TODO: import fabric_details_v3 when SwitchDetails (v2) has been merged. +from ..fabric.fabric_details_v2 import FabricDetailsByName from .conversion import ConversionUtils +from .enums import MaintenanceModeGetEnum from .exceptions import ControllerResponseError -from .properties import Properties +from .rest_send_v2 import RestSend + +# TODO: import results_v2 when SwitchDetails (v2) has been merged. +from .results import Results + +# TODO: import switch_details_v2 when SwitchDetails (v2) has been merged. from .switch_details import SwitchDetails -from ..fabric.fabric_details_v2 import FabricDetailsByName -@Properties.add_rest_send -@Properties.add_results class MaintenanceModeInfo: """ - ### Summary - - Retrieve the maintenance mode state of switches. - - ### Raises - - ``TypeError`` in the following public properties: - - ``config`` if value is not a list. - - ``rest_send`` if value is not an instance of RestSend. - - ``results`` if value is not an instance of Results. - - - ``ValueError`` in the following public methods: - - ``refresh()`` if: - - ``config`` has not been set. - - ``rest_send`` has not been set. - - ``results`` has not been set. - - ### Details - Updates ``MaintenanceModeInfo().results`` to reflect success/failure of + # Summary + + Retrieve the maintenance mode state of switches. + + ## Raises + + ### TypeError + + - `config` property setter: value is not a list. + - `rest_send` property setter: value is not an instance of RestSend. + - `results` property setter: value is not an instance of Results. + + ### ValueError + + - `refresh()`: `config` has not been set. + - `refresh()`: `rest_send` has not been set. + + ## Details + + Updates `MaintenanceModeInfo().results` to reflect success/failure of the operation on the controller. - Example value for ``config`` in the ``Usage`` section below: + ## Example value for `config` in the `Usage` section below + ```json ["192.168.1.2", "192.168.1.3"] ``` - Example value for ``info`` in the ``Usage`` section below: - ```json - { - "192.169.1.2": { - deployment_disabled: true - fabric_freeze_mode: true, - fabric_name: "MyFabric", - fabric_read_only: true - mode: "maintenance", - role: "spine", - serial_number: "FCI1234567" - }, - "192.169.1.3": { - deployment_disabled: false, - fabric_freeze_mode: false, - fabric_name: "YourFabric", - fabric_read_only: false - mode: "normal", - role: "leaf", - serial_number: "FCH2345678" - } + ## Example value for `info` in the `Usage` section below + + ```json + { + "192.169.1.2": { + "deployment_disabled": true, + "fabric_freeze_mode": true, + "fabric_name": "MyFabric", + "fabric_read_only": true, + "mode": "maintenance", + "role": "spine", + "serial_number": "FCI1234567" + }, + "192.169.1.3": { + "deployment_disabled": false, + "fabric_freeze_mode": false, + "fabric_name": "YourFabric", + "fabric_read_only": false, + "mode": "normal", + "role": "leaf", + "serial_number": "FCH2345678" } - ``` + } + ``` + + ## Usage - ### Usage - - Where: - - ``params`` is ``AnsibleModule.params`` - - ``config`` is per the above example. - - ``sender`` is an instance of a Sender() class. - See ``sender_dcnm.py`` for usage. + Where: + + - `params` is `AnsibleModule.params` + - `config` is per the above example. + - `sender` is an instance of a Sender() class. + See `sender_dcnm.py` for usage. ```python ansible_module = AnsibleModule() @@ -117,98 +133,102 @@ class MaintenanceModeInfo: ``` """ - def __init__(self, params): + def __init__(self, params: dict[str, Any]) -> None: self.class_name = self.__class__.__name__ - self.log = logging.getLogger(f"dcnm.{self.class_name}") - self.action = "maintenance_mode_info" + self.log: logging.Logger = logging.getLogger(f"dcnm.{self.class_name}") + self.action: str = "maintenance_mode_info" - self.params = params - self.conversion = ConversionUtils() - self.fabric_details = FabricDetailsByName() - self.switch_details = SwitchDetails() + self.params: dict[str, Any] = params + self._conversion: ConversionUtils = ConversionUtils() + self._fabric_details: FabricDetailsByName = FabricDetailsByName() + self._switch_details: SwitchDetails = SwitchDetails() - self._config = None - self._filter = None - self._info = None - self._rest_send = None - self._results = None + self._config: list[str] = [] + self._filter: str = "" + self._info: dict[str, Any] = {} + self._rest_send: RestSend = RestSend(params={}) + self._results: Results = Results() + self._valid_modes: list[str] = MaintenanceModeGetEnum.values() msg = "ENTERED MaintenanceModeInfo(): " self.log.debug(msg) def verify_refresh_parameters(self) -> None: """ - ### Summary - Verify that required parameters are present before - calling ``refresh()``. - - ### Raises - - ``ValueError`` if: - - ``config`` is not set. - - ``rest_send`` is not set. - - ``results`` is not set. + # Summary + + Verify that required parameters are present before calling `refresh()`. + + ## Raises + + ### ValueError + + - `config` is not set. + - `rest_send` is not set. + - `results` is not set. """ method_name = inspect.stack()[0][3] - if self.config is None: + if not self.config: msg = f"{self.class_name}.{method_name}: " msg += f"{self.class_name}.config must be set " msg += "before calling refresh." 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 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 " - msg += "before calling refresh." - raise ValueError(msg) def refresh(self): """ - ### Summary - Build ``self.info``, a dict containing the current maintenance mode + # Summary + + Build `self.info`, a dict containing the current maintenance mode status of all switches in self.config. - ### Raises - - ``ValueError`` if: - - ``SwitchDetails()`` raises ``ControllerResponseError`` - - ``SwitchDetails()`` raises ``ValueError`` - - ``FabricDetails()`` raises ``ControllerResponseError`` - - switch with ``ip_address`` does not exist on the controller. + ## Raises + + ### ValueError + + - `SwitchDetails()` raises `ControllerResponseError` + - `SwitchDetails()` raises `ValueError` + - `FabricDetails()` raises `ControllerResponseError` + - switch with `ip_address` does not exist on the controller. + + ## self.info structure - ### self.info structure info is a dict, keyed on switch_ip, where each element is a dict with the following structure: - - ``fabric_name``: The name of the switch's hosting fabric. - - ``freeze_mode``: The current state of the switch's hosting fabric. - If freeze_mode is True, configuration changes cannot be made to the - fabric or the switches within the fabric. - - ``mode``: The current maintenance mode of the switch. - - ``role``: The role of the switch in the hosting fabric. - - ``serial_number``: The serial number of the switch. + + - `fabric_name`: The name of the switch's hosting fabric. + - `freeze_mode`: The current state of the switch's hosting fabric. + If freeze_mode is True, configuration changes cannot be made to the + fabric or the switches within the fabric. + - `mode`: The current maintenance mode of the switch. + - `role`: The role of the switch in the hosting fabric. + - `serial_number`: The serial number of the switch. ```json { "192.169.1.2": { - fabric_deployment_disabled: true - fabric_freeze_mode: true, - fabric_name: "MyFabric", - fabric_read_only: true - mode: "maintenance", - role: "spine", - serial_number: "FCI1234567" + "fabric_deployment_disabled": true, + "fabric_freeze_mode": true, + "fabric_name": "MyFabric", + "fabric_read_only": true, + "mode": "maintenance", + "role": "spine", + "serial_number": "FCI1234567" }, "192.169.1.3": { - fabric_deployment_disabled: false, - fabric_freeze_mode: false, - fabric_name: "YourFabric", - fabric_read_only: false - mode: "normal", - role: "leaf", - serial_number: "FCH2345678" + "fabric_deployment_disabled": false, + "fabric_freeze_mode": false, + "fabric_name": "YourFabric", + "fabric_read_only": false, + "mode": "normal", + "role": "leaf", + "serial_number": "FCH2345678" } } ``` @@ -218,31 +238,31 @@ def refresh(self): self.verify_refresh_parameters() try: - self.switch_details.rest_send = self.rest_send - self.fabric_details.rest_send = self.rest_send + self._switch_details.rest_send = self.rest_send + self._fabric_details.rest_send = self.rest_send - self.switch_details.results = self.results - self.fabric_details.results = self.results + self._switch_details.results = self.results + self._fabric_details.results = self.results except TypeError as error: raise ValueError(error) from error try: - self.switch_details.refresh() + self._switch_details.refresh() except (ControllerResponseError, ValueError) as error: raise ValueError(error) from error try: - self.fabric_details.refresh() + self._fabric_details.refresh() except (ControllerResponseError, ValueError) as error: raise ValueError(error) from error info = {} # Populate info dict for ip_address in self.config: - self.switch_details.filter = ip_address + self._switch_details.filter = ip_address try: - serial_number = self.switch_details.serial_number + serial_number = self._switch_details.serial_number except ValueError as error: raise ValueError(error) from error @@ -254,10 +274,10 @@ def refresh(self): raise ValueError(msg) try: - fabric_name = self.switch_details.fabric_name - freeze_mode = self.switch_details.freeze_mode - mode = self.switch_details.maintenance_mode - role = self.switch_details.switch_role + fabric_name = self._switch_details.fabric_name + freeze_mode = self._switch_details.freeze_mode + mode = self._switch_details.maintenance_mode + role = self._switch_details.switch_role except ValueError as error: msg = f"{self.class_name}.{method_name}: " msg += "Error setting properties for switch with ip_address " @@ -266,11 +286,11 @@ def refresh(self): raise ValueError(msg) from error try: - self.fabric_details.filter = fabric_name + self._fabric_details.filter = fabric_name except ValueError as error: raise ValueError(error) from error - fabric_read_only = self.fabric_details.is_read_only + fabric_read_only = self._fabric_details.is_read_only info[ip_address] = {} info[ip_address].update({"fabric_name": fabric_name}) @@ -301,82 +321,100 @@ def refresh(self): self.info = copy.deepcopy(info) - def _get(self, item): + def _get(self, item: str) -> Any: """ + # Summary + Return the value of the item from the filtered switch. - ### Raises - - ``ValueError`` if: - - ``filter`` is not set. - - ``filter`` is not in the controller response. - ### NOTES - - We do not need to check that ``item`` exists in the filtered - switch dict, since ``refresh()`` has already done so. + ## Raises + + ### ValueError + + - `filter` is not set. + - `filter` is not in the controller response. + + ## Notes + + We do not need to check that `item` exists in the filtered + switch dict, since `refresh()` has already done so. """ method_name = inspect.stack()[0][3] - if self.filter is None: + if not self._filter: msg = f"{self.class_name}.{method_name}: " msg += "set instance.filter before accessing " msg += f"property {item}." raise ValueError(msg) - if self.filter not in self._info: + if self._filter not in self._info: msg = f"{self.class_name}.{method_name}: " - msg += f"Switch with ip_address {self.filter} does not exist on " + msg += f"Switch with ip_address {self._filter} does not exist on " msg += "the controller." raise ValueError(msg) - return self.conversion.make_boolean( - self.conversion.make_none(self._info[self.filter].get(item)) - ) + return self._conversion.make_boolean(self._conversion.make_none(self._info[self._filter].get(item))) @property - def filter(self): + def filter(self) -> str: """ - ### Summary + # Summary + Set the query filter (switch IP address) - ### Raises - None. However, if ``filter`` is not set, or ``filter`` is set to + ## Raises + + None + + However, if `filter` is not set, or `filter` is set to an ip_address for a switch that does not exist on the controller, - ``ValueError`` will be raised when accessing the various getter + `ValueError` will be raised when accessing the various getter properties. - ### Details + ## Details + The filter should be the ip_address of the switch from which to retrieve details. - ``filter`` must be set before accessing this class's properties. + `filter` must be set before accessing this class's properties. """ return self._filter @filter.setter - def filter(self, value): + def filter(self, value: str) -> None: self._filter = value @property - def config(self) -> list: + def config(self) -> list[str]: """ - ### Summary + # Summary + A list of switch ip addresses for which maintenance mode state will be retrieved. - ### Raises - - setter: ``TypeError`` if: - - ``config`` is not a ``list``. - - Elements of ``config`` are not ``str``. + ## Raises + + ### TypeError + + setter: + + - `config` is not a `list`. + - Elements of `config` are not `str`. + + ## getter + + Return `config`. + + ## setter - ### getter - Return ``config``. + Set `config`. - ### setter - Set ``config``. + ## Value structure - ### Value structure - value is a ``list`` of ip addresses + value is a `list` of ip addresses + + ## Example - ### Example ```json ["172.22.150.2", "172.22.150.3"] ``` @@ -384,7 +422,7 @@ def config(self) -> list: return self._config @config.setter - def config(self, value): + def config(self, value: list[str]) -> None: method_name = inspect.stack()[0][3] if not isinstance(value, list): msg = f"{self.class_name}.{method_name}: " @@ -404,144 +442,179 @@ def config(self, value): self._config = value @property - def fabric_deployment_disabled(self): + def fabric_deployment_disabled(self) -> bool: """ - ### Summary - The current ``fabric_deployment_disabled`` state of the + # Summary + + The current `fabric_deployment_disabled` state of the filtered switch's hosting fabric. - ### Raises - - ``ValueError`` if: - - ``filter`` is not set. - - ``filter`` is not in the controller response. - - ``deployment_disabled`` is not in the filtered switch dict. - - ### Valid values - - ``True``: The fabric is in a state where configuration changes - cannot be made. - - ``False``: The fabric is in a state where configuration changes - can be made. + ## Raises + + ### ValueError + + - `filter` is not set. + - `filter` is not in the controller response. + - `deployment_disabled` is not in the filtered switch dict. + + ## Valid values + + - `True`: The fabric is in a state where configuration changes + cannot be made. + - `False`: The fabric is in a state where configuration changes + can be made. """ - return self._get("fabric_deployment_disabled") + value = self._get("fabric_deployment_disabled") + if value is None: + return False + return value @property - def fabric_freeze_mode(self): + def fabric_freeze_mode(self) -> bool: """ - ### Summary + # Summary + The freezeMode state of the fabric in which the filtered switch resides. - ### Raises - - ``ValueError`` if: - - ``filter`` is not set. - - ``filter`` is not in the controller response. - - ``fabric_name`` is not in the filtered switch dict. - - ### Valid values - - ``True``: The fabric is in a state where configuration changes - cannot be made. - - ``False``: The fabric is in a state where configuration changes - can be made. + ## Raises + + ### ValueError + + - `filter` is not set. + - `filter` is not in the controller response. + - `fabric_name` is not in the filtered switch dict. + + ## Valid values + + - `True`: The fabric is in a state where configuration changes + cannot be made. + - `False`: The fabric is in a state where configuration changes + can be made. """ - return self._get("fabric_freeze_mode") + value = self._get("fabric_freeze_mode") + if value is None: + return False + return value @property - def fabric_name(self): + def fabric_name(self) -> str: """ - ### Summary + # Summary + The name of the fabric in which the filtered switch resides. - ### Raises - - ``ValueError`` if: - - ``filter`` is not set. - - ``filter`` is not in the controller response. - - ``fabric_name`` is not in the filtered switch dict. + ## Raises + + ### ValueError + + - `filter` is not set. + - `filter` is not in the controller response. + - `fabric_name` is not in the filtered switch dict. """ - return self._get("fabric_name") + value = self._get("fabric_name") + if value is None: + return "" + return value @property - def fabric_read_only(self): + def fabric_read_only(self) -> bool: """ - ### Summary + # Summary + The read-only state of the fabric in which the filtered switch resides. - ### Raises - - ``ValueError`` if: - - ``filter`` is not set. - - ``filter`` is not in the controller response. - - ``fabric_name`` is not in the filtered switch dict. - - ### Valid values - - ``True``: The fabric is in a state where configuration changes - cannot be made. - - ``False``: The fabric is in a state where configuration changes - can be made. + ## Raises + + ### ValueError + + - `filter` is not set. + - `filter` is not in the controller response. + - `fabric_name` is not in the filtered switch dict. + + ## Valid values + + - `True`: The fabric is in a state where configuration changes + cannot be made. + - `False`: The fabric is in a state where configuration changes + can be made. """ - return self._get("fabric_read_only") + value = self._get("fabric_read_only") + if value is None: + return False + return value @property - def info(self) -> dict: + def info(self) -> dict[str, Any]: """ - ### Summary + # Summary + Return or set the current maintenance mode state of the switches represented by the ip_addresses in self.config. - ### Raises - - ``ValueError`` if: - - ``refresh()`` has not been called before accessing ``info``. + ## Raises + + ### ValueError + + - `refresh()` has not been called before accessing `info`. - ### getter - Return ``info``. + ## getter - ### setter - Set ``info``. + Return `info`. - ### ``info`` structure - ``info`` is a dict, keyed on switch_ip, where each element is a dict + ## setter + + Set `info`. + + ## `info` structure + + `info` is a dict, keyed on switch_ip, where each element is a dict with the following structure: - - ``fabric_deployment_disabled``: The current state of the switch's - hosting fabric. If fabric_deployment_disabled is True, - configuration changes cannot be made to the fabric or the switches - within the fabric. - - ``fabric_name``: The name of the switch's hosting fabric. - - ``fabric_freeze_mode``: The current state of the switch's - hosting fabric. If freeze_mode is True, configuration changes - cannot be made to the fabric or the switches within the fabric. - - ``fabric_read_only``: The current state of the switch's - hosting fabric. If fabric_read_only is True, configuration changes - cannot be made to the fabric or the switches within the fabric. - - ``mode``: The current maintenance mode of the switch. - - ``role``: The role of the switch in the hosting fabric. - - ``serial_number``: The serial number of the switch. - - ### Example info dict + + - `fabric_deployment_disabled`: The current state of the switch's + hosting fabric. If fabric_deployment_disabled is True, + configuration changes cannot be made to the fabric or the switches + within the fabric. + - `fabric_name`: The name of the switch's hosting fabric. + - `fabric_freeze_mode`: The current state of the switch's + hosting fabric. If freeze_mode is True, configuration changes + cannot be made to the fabric or the switches within the fabric. + - `fabric_read_only`: The current state of the switch's + hosting fabric. If fabric_read_only is True, configuration changes + cannot be made to the fabric or the switches within the fabric. + - `mode`: The current maintenance mode of the switch. + - `role`: The role of the switch in the hosting fabric. + - `serial_number`: The serial number of the switch. + + ## Example info dict + ```json { "192.169.1.2": { - fabric_deployment_disabled: true - fabric_freeze_mode: true, - fabric_name: "MyFabric", - fabric_read_only: true - mode: "maintenance", - role: "spine", - serial_number: "FCI1234567" + "fabric_deployment_disabled": true, + "fabric_freeze_mode": true, + "fabric_name": "MyFabric", + "fabric_read_only": true, + "mode": "maintenance", + "role": "spine", + "serial_number": "FCI1234567" }, "192.169.1.3": { - fabric_deployment_disabled: false - fabric_freeze_mode: false, - fabric_name: "YourFabric", - fabric_read_only: false - mode: "normal", - role: "leaf", - serial_number: "FCH2345678" + "fabric_deployment_disabled": false, + "fabric_freeze_mode": false, + "fabric_name": "YourFabric", + "fabric_read_only": false, + "mode": "normal", + "role": "leaf", + "serial_number": "FCH2345678" } } ``` """ method_name = inspect.stack()[0][3] - if self._info is None: + if not self._info: msg = f"{self.class_name}.{method_name}: " msg += f"{self.class_name}.refresh() must be called before " msg += f"accessing {self.class_name}.{method_name}." @@ -549,7 +622,7 @@ def info(self) -> dict: return copy.deepcopy(self._info) @info.setter - def info(self, value: dict): + def info(self, value: dict[str, Any]) -> None: if not isinstance(value, dict): msg = f"{self.class_name}.info.setter: " msg += "value must be a dict. " @@ -560,41 +633,142 @@ def info(self, value: dict): @property def mode(self): """ - ### Summary + # Summary + The current maintenance mode of the filtered switch. - ### Raises - - ``ValueError`` if: - - ``filter`` is not set. - - ``filter`` is not in the controller response. - - ``mode`` is not in the filtered switch dict. + ## Raises + + ### ValueError + + - `filter` is not set. + - `filter` is not in the controller response. + - `mode` is not in the filtered switch dict. + + ## Example values + + - `maintenance` + - `normal` + """ + value = self._get("mode") + if value not in self._valid_modes: + msg = f"{self.class_name}.mode: " + msg += f"Invalid mode value: {value}." + raise ValueError(msg) + return 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._get("mode") + 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 @property def role(self): """ - ### Summary + # Summary + The role of the filtered switch in the hosting fabric. - ### Raises - - ``ValueError`` if: - - ``filter`` is not set. - - ``filter`` is not in the controller response. - - ``role`` is not in the filtered switch dict. + ## Raises + + ### ValueError + + - `filter` is not set. + - `filter` is not in the controller response. + - `role` is not in the filtered switch dict. """ return self._get("role") @property def serial_number(self): """ - ### Summary + # Summary + The serial number of the filtered switch. - ### Raises - - ``ValueError`` if: - - ``filter`` is not set. - - ``filter`` is not in the controller response. - - ``serial_number`` is not in the filtered switch dict. + ## Raises + + ### ValueError + + - `filter` is not set. + - `filter` is not in the controller response. + - `serial_number` is not in the filtered switch dict. """ return self._get("serial_number") diff --git a/plugins/modules/dcnm_maintenance_mode.py b/plugins/modules/dcnm_maintenance_mode.py index f281904e0..4c44deaeb 100644 --- a/plugins/modules/dcnm_maintenance_mode.py +++ b/plugins/modules/dcnm_maintenance_mode.py @@ -1,6 +1,9 @@ #!/usr/bin/python +""" +Ansible module to manage Maintenance Mode Configuration of NX-OS Switches. +""" # -# Copyright (c) 2020-2024 Cisco and/or its affiliates. +# Copyright (c) 2020-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. @@ -13,10 +16,11 @@ # 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 -from __future__ import absolute_import, division, print_function +from __future__ import absolute_import, annotations, division, print_function -__metaclass__ = type +__metaclass__ = type # pylint: disable=invalid-name __author__ = "Allen Robel" DOCUMENTATION = """ @@ -147,15 +151,16 @@ import inspect import json import logging +from typing import Any, Literal from ansible.module_utils.basic import AnsibleModule + from ..module_utils.common.log_v2 import Log from ..module_utils.common.maintenance_mode import MaintenanceMode from ..module_utils.common.maintenance_mode_info import MaintenanceModeInfo from ..module_utils.common.merge_dicts_v2 import MergeDicts from ..module_utils.common.params_merge_defaults_v2 import ParamsMergeDefaults from ..module_utils.common.params_validate_v2 import ParamsValidate -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 @@ -171,9 +176,16 @@ def json_pretty(msg): class ParamsSpec: """ + # Summary + Build parameter specifications for the dcnm_maintenance_mode module. - ### Usage + ## Raises + + None + + ## Usage + ```python from ansible.module_utils.basic import AnsibleModule @@ -203,39 +215,55 @@ class ParamsSpec: ``` """ - 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.log.debug("ENTERED ParamsSpec()") - self._params = None - self._params_spec: dict = {} + self._params: dict[str, Any] = {} + self._params_spec: dict[str, Any] = {} - self.valid_states = ["merged", "query"] + self._valid_states: list[str] = ["merged", "query"] - def commit(self): + def commit(self) -> None: """ + # Summary + Build the parameter specification based on the state ## Raises - - ``ValueError`` if params is not set + + ### ValueError + + - params is not set + - params["state"] is not a valid state (merged or query) """ - if self._params is None: + if not self._params: msg = f"{self.class_name}.commit: " msg += "params must be set before calling commit()." raise ValueError(msg) - if self.params["state"] == "merged": + if self.params.get("state") == "merged": self._build_params_spec_for_merged_state() - if self.params["state"] == "query": + elif self.params.get("state") == "query": self._build_params_spec_for_query_state() + else: + msg = f"{self.class_name}.commit: " + msg += f"Invalid state {self.params.get('state')}." + raise ValueError(msg) def _build_params_spec_for_merged_state(self) -> None: """ + # Summary + Build the parameter specifications for ``merged`` state. + + ## Raises + + None """ - self._params_spec: dict = {} + self._params_spec = {} self._params_spec["ip_address"] = {} self._params_spec["ip_address"]["required"] = True self._params_spec["ip_address"]["type"] = "ipv4" @@ -258,45 +286,58 @@ def _build_params_spec_for_merged_state(self) -> None: def _build_params_spec_for_query_state(self) -> None: """ + # Summary + Build the parameter specifications for ``query`` state. + + ## Raises + + None """ - self._params_spec: dict = {} + self._params_spec = {} self._params_spec["ip_address"] = {} self._params_spec["ip_address"]["required"] = True self._params_spec["ip_address"]["type"] = "ipv4" @property - def params_spec(self) -> dict: + def params_spec(self) -> dict[str, Any]: """ return the parameter specification """ return self._params_spec @property - def params(self) -> dict: + def params(self) -> dict[str, Any]: """ - ### Summary + # Summary + Expects value to be a dictionary containing, at mimimum, the key "state" with value of either "merged" or "query". - ### Raises - - setter: raise ``ValueError`` if value is not a dict - - setter: raise ``ValueError`` if value["state"] is missing - - setter: raise ``ValueError`` if value["state"] is not a valid state + ## Raises - ### Details - - Valid params: {"state": "merged"} or {"state": "query"} - - getter: return the params - - setter: set the params + ### ValueError + + setter: + + - value is not a dict + - value["state"] is missing + - value["state"] is not a valid state + + ## Details + + - Valid params: {"state": "merged"} or {"state": "query"} + - getter: return the params + - setter: set the params """ return self._params @params.setter - def params(self, value: dict) -> None: + def params(self, value: dict[str, Any]) -> None: """ - setter: set the params """ - method_name = inspect.stack()[0][3] + method_name: str = inspect.stack()[0][3] if not isinstance(value, dict): msg = f"{self.class_name}.{method_name}.setter: " msg += "Invalid type. Expected dict but " @@ -309,10 +350,10 @@ def params(self, value: dict) -> None: msg += "params.state is required but missing." raise ValueError(msg) - if value["state"] not in self.valid_states: + if value["state"] not in self._valid_states: msg = f"{self.class_name}.{method_name}.setter: " msg += f"params.state is invalid: {value['state']}. " - msg += f"Expected one of {', '.join(self.valid_states)}." + msg += f"Expected one of {', '.join(self._valid_states)}." raise ValueError(msg) self._params = value @@ -320,91 +361,95 @@ def params(self, value: dict) -> None: class Want: """ - ### Summary + # Summary + Build self.want, a list of validated playbook configurations. - ### Raises - - ``ValueError`` in the following cases: - - ``commit()`` is issued before setting mandatory properties - - When passing invalid values to property setters - - ``TypeError`` in the following cases: - - When passing invalid types to property setters + ## Raises + + ### ValueError + + - `commit()` is issued before setting mandatory properties + - When passing invalid values to property setters + ### TypeError + + - When passing invalid types to property setters + + ## Details - ### Details 1. Merge the playbook global config into each switch config. 2. Validate the merged configs from step 1 against the param spec. 3. Populate self.want with the validated configs. - ### Usage + ## Usage + ```python try: instance = Want() instance.config = playbook_config instance.params = ansible_module.params - instance.params_spec = ParamsSpec() instance.items_key = "switches" - instance.validator = ParamsValidate() instance.commit() want = instance.want except (TypeError, ValueError) as error: handle_error(error) ``` - ### self.want structure + + ## self.want structure ```json [ { "ip_address": "192.168.1.2", "mode": "maintenance", - "deploy": false + "deploy": false, "wait_for_mode_change": false }, { "ip_address": "192.168.1.3", "mode": "normal", - "deploy": true + "deploy": true, "wait_for_mode_change": true } ] ``` """ - 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.log.debug("ENTERED Want()") - self._config = None - self._items_key = None - self._params = None - self._params_spec = None - self._validator = None - self._want = [] + self._config: dict[str, Any] = {} + self._items_key: str = "" + self._params: dict[str, Any] = {} + self._params_spec: ParamsSpec = ParamsSpec() + self._validator: ParamsValidate = ParamsValidate() + self._want: list[dict[str, Any]] = [] - self.merge_dicts = MergeDicts() - self.merged_configs = [] - self.item_configs = [] + self.merge_dicts: MergeDicts = MergeDicts() + self.merged_configs: list[dict[str, Any]] = [] + self.item_configs: list[dict[str, Any]] = [] def generate_params_spec(self) -> None: """ - ### Summary + # Summary + Generate the params_spec used to validate the configs - ### Raises - - ``ValueError`` if self.params is not set - - ``ValueError`` if self.params_spec is not set + ## Raises + + ### ValueError + + - self.params is not set """ # Generate the params_spec used to validate the configs - if self.params is None: + if not self.params: msg = f"{self.class_name}.generate_params_spec(): " msg += "params is not set, and is required." raise ValueError(msg) - if self.params_spec is None: - msg = f"{self.class_name}.generate_params_spec(): " - msg += "params_spec is not set, and is required." - raise ValueError(msg) try: self.params_spec.params = self.params @@ -415,34 +460,39 @@ def generate_params_spec(self) -> None: def validate_configs(self) -> None: """ - ### Summary + # Summary + Validate the merged configs against the param spec and populate self.want with the validated configs. - ### Raises + ## Raises + None - ### Notes - - validator is already verified in commit()s + ## Notes + + validator is already verified in commit() """ - self.validator.params_spec = self.params_spec.params_spec + self._validator.params_spec = self._params_spec.params_spec for config in self.merged_configs: - self.validator.parameters = config - self.validator.commit() - self.want.append(copy.deepcopy(config)) + self._validator.parameters = config + self._validator.commit() + self._want.append(copy.deepcopy(config)) def build_merged_configs(self) -> None: """ - ### Summary + # Summary + If a parameter is missing from the config, and the parameter has a default value, merge the default value for the parameter into the config. - ### Raises + ## Raises + None """ self.merged_configs = [] - merge_defaults = ParamsMergeDefaults() + merge_defaults: ParamsMergeDefaults = ParamsMergeDefaults() merge_defaults.params_spec = self.params_spec.params_spec for config in self.item_configs: merge_defaults.parameters = config @@ -455,33 +505,35 @@ def build_merged_configs(self) -> None: def commit(self) -> None: """ - ### Summary + # Summary + Build self.want, a list of validated playbook configurations. - ### Raises - - ``ValueError`` if: - - self.config is not set - - self.item_key is not set - - self.params is not set - - self.params_spec is not set - - self.validator is not set - - self.params_spec raises ``ValueError`` - - _merge_global_and_switch_configs() raises ``ValueError`` - - merge_dicts() raises `TypeError``` or ``ValueError`` - - playbook is missing list of items - - ### Details + ## Raises + + ### ValueError + + - self.config is not set + - self.item_key is not set + - self.params is not set + - self.params_spec raises `ValueError` + - _merge_global_and_switch_configs() raises `ValueError` + - merge_dicts() raises `ValueError` + - playbook is missing list of items + + ### TypeError + + - merge_dicts() raises `TypeError` + + ## Details + See class docstring. - ### self.want structure + ## self.want structure + See class docstring. """ - method_name = inspect.stack()[0][3] - - if self.validator is None: - msg = f"{self.class_name}.{method_name}: " - msg += f"self.validator must be set before calling {method_name}." - raise ValueError(msg) + method_name: str = inspect.stack()[0][3] try: self.generate_params_spec() @@ -511,47 +563,55 @@ def commit(self) -> None: def _merge_global_and_item_configs(self) -> None: """ - ### Summary + # Summary + Builds self.item_configs from self.config Merge the global playbook config with each item config and populate a list of merged configs (``self.item_configs``). - ### Raises - - ``ValueError`` if self.config is not set - - ``ValueError`` if self.items_key is not set - - ``ValueError`` if playbook is missing list of items - - ``ValueError`` if merge_dicts raises ``TypeError`` or ``ValueError`` - - ### Merge rules - - item_config takes precedence over global_config. - - If item_config is missing a parameter, use parameter - from global_config. - - If item_config has a parameter, use it. + ## Raises + + ### ValueError + + - self.config is not set + - self._items_key is not set + - playbook is missing list of items + - merge_dicts raises `ValueError` + + ### TypeError + + - merge_dicts raises `TypeError` + + ## Merge rules + + - item_config takes precedence over global_config. + - If item_config is missing a parameter, use parameter + from global_config. + - If item_config has a parameter, use it. """ - method_name = inspect.stack()[0][3] + method_name: str = inspect.stack()[0][3] - if self.config is None: + if not self.config: msg = f"{self.class_name}.{method_name}: " msg += "config is not set, and is required." raise ValueError(msg) - if self.items_key is None: + if not self._items_key: msg = f"{self.class_name}.{method_name}: " msg += "items_key is not set, and is required." raise ValueError(msg) - if not self.config.get(self.items_key): + if not self.config.get(self._items_key): msg = f"{self.class_name}.{method_name}: " - msg += f"playbook is missing list of {self.items_key}." + msg += f"playbook is missing list of {self._items_key}." raise ValueError(msg) self.item_configs = [] - merged_configs = [] - for item in self.config[self.items_key]: + merged_configs: list[dict[str, Any]] = [] + for item in self.config[self._items_key]: # we need to rebuild global_config in this loop # because merge_dicts modifies it in place global_config = copy.deepcopy(self.config) - global_config.pop(self.items_key, None) - + global_config.pop(self._items_key, None) msg = f"{self.class_name}.{method_name}: " msg += "global_config: " msg += f"{json.dumps(global_config, indent=4, sort_keys=True)}" @@ -582,22 +642,32 @@ def _merge_global_and_item_configs(self) -> None: self.item_configs = copy.copy(merged_configs) @property - def config(self): + def config(self) -> dict[str, Any]: """ - ### Summary + # Summary + The playbook configuration to be processed. - ``config`` is processed by ``_merge_global_and_switch_configs()`` - to build ``switch_configs``. + `config` is processed by `_merge_global_and_switch_configs()` + to build `switch_configs`. + + ## Raises + + ### TypeError + + setter: + + - value is not a dict + + ## Details - - getter: return config - - setter: set config - - setter: raise ``ValueError`` if value is not a dict + - getter: return config + - setter: set config """ return self._config @config.setter - def config(self, value) -> None: + def config(self, value: dict[str, Any]) -> None: if not isinstance(value, dict): msg = f"{self.class_name}.config.setter: " msg += "expected dict but got " @@ -608,12 +678,23 @@ def config(self, value) -> None: @property def items_key(self) -> str: """ + # Summary + Expects value to be the key for the list of items in the playbook config. - - getter: return the items_key - - setter: set the items_key - - setter: raise ``ValueError`` if value is not a string + ## Raises + + ### TypeError + + setter: + + - value is not a string + + ## Details + + - getter: return the items_key + - setter: set the items_key """ return self._items_key @@ -630,34 +711,44 @@ def items_key(self, value: str) -> None: self._items_key = value @property - def want(self) -> list: + def want(self) -> list[dict[str, Any]]: """ - ### Summary + # Summary + Return the want list. See class docstring for structure details. + + ## Raises + + None """ return self._want @property - def params(self) -> dict: + def params(self) -> dict[str, Any]: """ - ### Summary - The return value of ``AnsibleModule.params`` property - (or equivalent dict). This is passed to ``params_spec`` + # Summary + + The return value of `AnsibleModule.params` property + (or equivalent dict). This is passed to `params_spec` and used in playbook config validation. - ### Raises - - setter: raise ``ValueError`` if value is not a ``dict``. + ## Raises + + ### TypeError + + setter: + + - value is not a `dict` - ### getter - Return params + ## Details - ### setter - Set params + - getter: Return params + - setter: Set params """ return self._params @params.setter - def params(self, value: dict) -> None: + def params(self, value: dict[str, Any]) -> None: """ - setter: set the params """ @@ -669,30 +760,33 @@ def params(self, value: dict) -> None: self._params = value @property - def params_spec(self): + def params_spec(self) -> ParamsSpec: """ - ### Summary + # Summary + The parameter specification used to validate the playbook config. - Expects value to be an instance of ``ParamsSpec()``. + Expects value to be an instance of `ParamsSpec()`. - ``params_spec`` is passed to ``validator`` to validate the - playbook config. + `params_spec` is passed to `validator` to validate the playbook config. + + `params_spec` is optional. If not set, a default instance of `ParamsSpec()` is used. - ### Raises - - setter: raise ``TypeError`` if value is not an instance - of ParamsSpec() + ## Raises + + ### TypeError - ### getter - Return params_spec + - setter: value is not an instance of ParamsSpec() - ### setter - Set params_spec + ## Details + + - getter: Return params_spec + - setter: Set params_spec """ return self._params_spec @params_spec.setter - def params_spec(self, value) -> None: - method_name = inspect.stack()[0][3] + def params_spec(self, value: ParamsSpec) -> None: + method_name: str = inspect.stack()[0][3] _class_have = None _class_need = "ParamsSpec" msg = f"{self.class_name}.{method_name}: " @@ -709,26 +803,31 @@ def params_spec(self, value) -> None: self._params_spec = value @property - def validator(self): + def validator(self) -> ParamsValidate: """ - ### Summary - ``validator`` is used to validate the playbook config. - Expects value to be an instance of ``ParamsValidate()``. + # Summary + + `validator` is used to validate the playbook config. + Expects value to be an instance of `ParamsValidate()`. + + `validator` is optional. If not set, a default instance of `ParamsValidate()` is used. + + ## Raises - ### Raises - - setter: ``TypeError`` if value is not an instance of ``ParamsValidate()`` + ### TypeError - ### getter - Return validator + - setter: value is not an instance of `ParamsValidate()` - ### setter - Set validator + ## Details + + - getter: Return validator + - setter: Set validator """ return self._validator @validator.setter - def validator(self, value) -> None: - method_name = inspect.stack()[0][3] + def validator(self, value: ParamsValidate) -> None: + method_name: str = inspect.stack()[0][3] _class_have = None _class_need = "ParamsValidate" msg = f"{self.class_name}.{method_name}: " @@ -745,56 +844,68 @@ def validator(self, value) -> None: self._validator = value -@Properties.add_rest_send class Common: """ Common methods, properties, and resources for all states. """ - def __init__(self, params): + def __init__(self, params: dict[str, Any]): """ - ### Raises - - ``ValueError`` if: - - ``params`` does not contain ``check_mode`` - - ``params`` does not contain ``state`` - - ``params`` does not contain ``config`` - - ``TypeError`` if: - - ``config`` is not a dict + # Summary + + Initialize Common instance + + ## Raises + + ### ValueError + + - `params` does not contain `check_mode` + - `params` does not contain `config` + - `params` does not contain `state` + + ### TypeError + + - `config` is not a dict """ - self.class_name = self.__class__.__name__ - method_name = inspect.stack()[0][3] + self.class_name: str = self.__class__.__name__ + method_name: str = inspect.stack()[0][3] + + self.params: dict[str, Any] = params - self.params = params - self.log = logging.getLogger(f"dcnm.{self.class_name}") + self.log: logging.Logger = logging.getLogger(f"dcnm.{self.class_name}") - self.check_mode = self.params.get("check_mode", None) - if self.check_mode is None: + self._check_mode: bool | None = self.params.get("check_mode", None) + if self._check_mode is None: msg = f"{self.class_name}.{method_name}: " msg += "check_mode is required." raise ValueError(msg) - self.state = self.params.get("state", None) - if self.state is None: + self.state: str = self.params.get("state", "") + if not self.state: msg = f"{self.class_name}.{method_name}: " msg += "state is required." raise ValueError(msg) - self.config = self.params.get("config", None) - if self.config is None: + self.config: dict[str, Any] = self.params.get("config", {}) + if not self.config and self.state != "query": msg = f"{self.class_name}.{method_name}: " msg += "config is required." raise ValueError(msg) + if not isinstance(self.config, dict): msg = f"{self.class_name}.{method_name}: " msg += "Expected dict type for self.config. " msg += f"Got {type(self.config).__name__}" raise TypeError(msg) - self._rest_send = None + self._rest_send: RestSend = RestSend({}) + + self.results: Results = Results() + self.results.state = self.state + self.results.check_mode = self._check_mode - self.results = Results() self.results.state = self.state - self.results.check_mode = self.check_mode + self.results.check_mode = self._check_mode self.have = {} # populated in self.validate_input() @@ -804,16 +915,24 @@ def __init__(self, params): msg = f"ENTERED Common().{method_name}: " msg += f"state: {self.state}, " - msg += f"check_mode: {self.check_mode}" + msg += f"check_mode: {self._check_mode}" self.log.debug(msg) def get_want(self) -> None: """ - ### Summary + # Summary + Build self.want, a list of validated playbook configurations. - ### Raises - - ``ValueError`` if Want() instance raises ``ValueError`` + ## Raises + + ### ValueError + + - Want() instance raises `ValueError` + + ### TypeError + + - Want() instance raises `TypeError` """ try: instance = Want() @@ -827,22 +946,77 @@ def get_want(self) -> None: except (TypeError, ValueError) as error: raise ValueError(error) from error + @property + def rest_send(self) -> RestSend: + """ + # Summary + + Get/set 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. + """ + 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 + class Merged(Common): """ + # Summary + Handle merged state - ### Raises - - ``ValueError`` if Common().__init__() raises ``ValueError`` + ## Raises + + ### ValueError + + - Common().__init__() raises ``ValueError`` + + ### TypeError + + - Common().__init__() raises `TypeError` """ - def __init__(self, params): + def __init__(self, params: dict[str, Any]) -> None: """ - ### Raises - - ``ValueError`` if Common().__init__() raises ``ValueError`` + # Summary + + Initialize Merged instance + + ## Raises + + ### ValueError + + - Common().__init__() raises `ValueError` + + ### TypeError + + - Common().__init__() raises `TypeError` """ - self.class_name = self.__class__.__name__ - method_name = inspect.stack()[0][3] + self.class_name: str = self.__class__.__name__ + method_name: str = inspect.stack()[0][3] try: super().__init__(params) except (TypeError, ValueError) as error: @@ -851,75 +1025,84 @@ def __init__(self, params): msg += f"Error detail: {error}" raise ValueError(msg) from error - self.log = logging.getLogger(f"dcnm.{self.class_name}") + self.log: logging.Logger = logging.getLogger(f"dcnm.{self.class_name}") - self.maintenance_mode = MaintenanceMode(params) + self.maintenance_mode: MaintenanceMode = MaintenanceMode(params) msg = f"ENTERED Merged.{method_name}: " msg += f"state: {self.state}, " - msg += f"check_mode: {self.check_mode}" + msg += f"check_mode: {self._check_mode}" self.log.debug(msg) - self.need = [] + self.have: dict[str, Any] = {} + self.need: list[dict[str, Any]] = [] - def get_have(self): + def get_have(self) -> None: """ - ### Summary + # Summary + Build self.have, a dict containing the current mode of all switches. - ### Raises - - ``ValueError`` if self.ansible_module is not set - - ``ValueError`` if MaintenanceModeInfo() raises ``ValueError`` + ## Raises + + ### ValueError + + - self.ansible_module is not set + - MaintenanceModeInfo() raises `ValueError` + + ### TypeError + + - MaintenanceModeInfo() raises `TypeError` + + ## self.have structure - ### self.have structure Have is a dict, keyed on switch_ip, where each element is a dict with the following structure: - - ``fabric_name``: The name of the switch's hosting fabric. - - ``fabric_freeze_mode``: The current ``freezeMode`` state of the switch's - hosting fabric. If ``freeze_mode`` is True, configuration changes cannot - be made to the fabric or the switches within the fabric. - - ``fabric_read_only``: The current ``IS_READ_ONLY`` state of the switch's - hosting fabric. If ``fabric_read_only`` is True, configuration changes cannot - be made to the fabric or the switches within the fabric. - - ``mode``: The current maintenance mode of the switch. - Possible values include: , ``inconsistent``, ``maintenance``, - ``migration``, ``normal``. - - ``role``: The role of the switch in the hosting fabric, e.g. - ``spine``, ``leaf``, ``border_gateway``, etc. - - ``serial_number``: The serial number of the switch. + + - `fabric_name`: The name of the switch's hosting fabric. + - `fabric_freeze_mode`: The current `freezeMode` state of the switch's + hosting fabric. If `freeze_mode` is True, configuration changes cannot + be made to the fabric or the switches within the fabric. + - `fabric_read_only`: The current `IS_READ_ONLY` state of the switch's + hosting fabric. If `fabric_read_only` is True, configuration changes cannot + be made to the fabric or the switches within the fabric. + - `mode`: The current maintenance mode of the switch. + Possible values include: , `inconsistent`, `maintenance`, + `migration`, `normal`. + - `role`: The role of the switch in the hosting fabric, e.g. + `spine`, `leaf`, `border_gateway`, etc. + - `serial_number`: The serial number of the switch. ```json { "192.169.1.2": { - fabric_deployment_disabled: true - fabric_freeze_mode: true, - fabric_name: "MyFabric", - fabric_read_only: true - mode: "maintenance", - role: "spine", - serial_number: "FCI1234567" + "fabric_deployment_disabled": true, + "fabric_freeze_mode": true, + "fabric_name": "MyFabric", + "fabric_read_only": true, + "mode": "maintenance", + "role": "spine", + "serial_number": "FCI1234567" }, "192.169.1.3": { - fabric_deployment_disabled: false - fabric_freeze_mode: false, - fabric_name: "YourFabric", - fabric_read_only: false - mode: "normal", - role: "leaf", - serial_number: "FCH2345678" + "fabric_deployment_disabled": false, + "fabric_freeze_mode": false, + "fabric_name": "YourFabric", + "fabric_read_only": false, + "mode": "normal", + "role": "leaf", + "serial_number": "FCH2345678" } } ``` """ - method_name = inspect.stack()[0][3] # pylint: disable=unused-variable + method_name: str = inspect.stack()[0][3] # pylint: disable=unused-variable try: instance = MaintenanceModeInfo(self.params) instance.rest_send = self.rest_send instance.results = self.results - instance.config = [ - item["ip_address"] for item in self.config.get("switches", {}) - ] + instance.config = [item["ip_address"] for item in self.config.get("switches", {})] instance.refresh() except (TypeError, ValueError) as error: msg = f"{self.class_name}.{method_name}: " @@ -930,16 +1113,21 @@ def get_have(self): def fabric_deployment_disabled(self) -> None: """ - ### Summary + # Summary + Handle the following cases: - - switch migration mode is ``migration`` - - fabric is in read-only mode (IS_READ_ONLY is True) - - fabric is in freeze mode (Deployment Disable) - ### Raises - - ``ValueError`` if any of the above cases are true + - switch migration mode is `migration` + - fabric is in read-only mode (IS_READ_ONLY is True) + - fabric is in freeze mode (Deployment Disable) + + ## Raises + + ### ValueError + + - any of the above cases are true """ - method_name = inspect.stack()[0][3] + method_name: str = inspect.stack()[0][3] for ip_address, value in self.have.items(): fabric_name = value.get("fabric_name") mode = value.get("mode") @@ -999,15 +1187,20 @@ def fabric_deployment_disabled(self) -> None: msg += additional_info raise ValueError(msg) - def get_need(self): + def get_need(self) -> None: """ - ### Summary + # Summary + Build self.need for merged state. - ### Raises - - ``ValueError`` if the switch is not found on the controller. + ## Raises + + ### ValueError + + - the switch is not found on the controller + + ## self.need structure - ### self.need structure ```json [ { @@ -1015,7 +1208,7 @@ def get_need(self): "fabric_name": "MyFabric", "ip_address": "172.22.150.2", "mode": "maintenance", - "serial_number": "FCI1234567" + "serial_number": "FCI1234567", "wait_for_mode_change": true }, { @@ -1023,12 +1216,13 @@ def get_need(self): "fabric_name": "YourFabric", "ip_address": "172.22.150.3", "mode": "normal", - "serial_number": "HMD2345678" + "serial_number": "HMD2345678", "wait_for_mode_change": true } ] + ``` """ - method_name = inspect.stack()[0][3] + method_name: str = inspect.stack()[0][3] self.need = [] for want in self.want: ip_address = want.get("ip_address", None) @@ -1049,23 +1243,26 @@ def get_need(self): need.update({"wait_for_mode_change": want.get("wait_for_mode_change")}) self.need.append(copy.copy(need)) - def commit(self): + def commit(self) -> None: """ - ### Summary + # Summary + Commit the merged state request - ### Raises - - ``ValueError`` if: - - ``rest_send`` is not set. - - ``get_want()`` raises ``ValueError`` - - ``get_have()`` raises ``ValueError`` - - ``send_need()`` raises ``ValueError`` + ## Raises + + ### ValueError + + - `rest_send` is not set + - `get_want()` raises `ValueError` + - `get_have()` raises `ValueError` + - `send_need()` raises `ValueError` """ - 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) - 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 before calling commit." raise ValueError(msg) @@ -1100,15 +1297,21 @@ def commit(self): def send_need(self) -> None: """ - ### Summary + # Summary + Build and send the payload to modify maintenance mode. - ### Raises - - ``ValueError`` if MaintenanceMode() raises either - ``TypeError`` or ``ValueError`` + ## Raises + + ### ValueError + + - MaintenanceMode() raises `ValueError` + + ### TypeError + - MaintenanceMode() raises `TypeError` """ - method_name = inspect.stack()[0][3] # pylint: disable=unused-variable + method_name: str = inspect.stack()[0][3] # pylint: disable=unused-variable if len(self.need) == 0: msg = f"{self.class_name}.{method_name}: " @@ -1127,21 +1330,41 @@ def send_need(self) -> None: class Query(Common): """ + # Summary + Handle query state - ### Raises - - ``ValueError`` if Common().__init__() raises ``ValueError`` - - ``ValueError`` if get_want() raises ``ValueError`` - - ``ValueError`` if get_have() raises ``ValueError`` + ## Raises + + ### ValueError + + - Common().__init__() raises `ValueError` + - get_want() raises `ValueError` + - get_have() raises `ValueError` + + ### TypeError + + - Common().__init__() raises `TypeError` """ - def __init__(self, params): + def __init__(self, params: dict[str, Any]) -> None: """ - ### Raises - - ``ValueError`` if Common().__init__() raises ``ValueError`` + # Summary + + Initialize Query instance + + ## Raises + + ### ValueError + + - Common().__init__() raises `ValueError` + + ### TypeError + + - Common().__init__() raises `TypeError` """ - self.class_name = self.__class__.__name__ - method_name = inspect.stack()[0][3] + self.class_name: str = self.__class__.__name__ + method_name: str = inspect.stack()[0][3] try: super().__init__(params) except (TypeError, ValueError) as error: @@ -1150,71 +1373,79 @@ def __init__(self, params): msg += f"Error detail: {error}" raise ValueError(msg) from error - self.log = logging.getLogger(f"dcnm.{self.class_name}") + self.log: logging.Logger = logging.getLogger(f"dcnm.{self.class_name}") - self.maintenance_mode_info = MaintenanceModeInfo(self.params) + self.maintenance_mode_info: MaintenanceModeInfo = MaintenanceModeInfo(self.params) msg = "ENTERED Query(): " msg += f"state: {self.state}, " - msg += f"check_mode: {self.check_mode}" + msg += f"check_mode: {self._check_mode}" self.log.debug(msg) - def get_have(self): + def get_have(self) -> None: """ - ### Summary + # Summary + Build self.have, a dict containing the current mode of all switches. - ### Raises - - ``ValueError`` if MaintenanceModeInfo() raises ``ValueError`` + ## Raises + + ### ValueError + + - MaintenanceModeInfo() raises `ValueError` + + ### TypeError + + - MaintenanceModeInfo() raises `TypeError` + + ## self.have structure - ### self.have structure Have is a dict, keyed on switch_ip, where each element is a dict with the following structure: - - ``fabric_name``: The name of the switch's hosting fabric. - - ``fabric_freeze_mode``: The current ``freezeMode`` state of the switch's - hosting fabric. If ``freeze_mode`` is True, configuration changes cannot - be made to the fabric or the switches within the fabric. - - ``fabric_read_only``: The current ``IS_READ_ONLY`` state of the switch's - hosting fabric. If ``fabric_read_only`` is True, configuration changes cannot - be made to the fabric or the switches within the fabric. - - ``mode``: The current maintenance mode of the switch. - Possible values include: , ``inconsistent``, ``maintenance``, - ``migration``, ``normal``. - - ``role``: The role of the switch in the hosting fabric, e.g. - ``spine``, ``leaf``, ``border_gateway``, etc. - - ``serial_number``: The serial number of the switch. + + - `fabric_name`: The name of the switch's hosting fabric. + - `fabric_freeze_mode`: The current `freezeMode` state of the switch's + hosting fabric. If `freeze_mode` is True, configuration changes cannot + be made to the fabric or the switches within the fabric. + - `fabric_read_only`: The current `IS_READ_ONLY` state of the switch's + hosting fabric. If `fabric_read_only` is True, configuration changes cannot + be made to the fabric or the switches within the fabric. + - `mode`: The current maintenance mode of the switch. + Possible values include: , `inconsistent`, `maintenance`, + `migration`, `normal`. + - `role`: The role of the switch in the hosting fabric, e.g. + `spine`, `leaf`, `border_gateway`, etc. + - `serial_number`: The serial number of the switch. ```json { "192.169.1.2": { - fabric_deployment_disabled: true - fabric_freeze_mode: true, - fabric_name: "MyFabric", - fabric_read_only: true - mode: "maintenance", - role: "spine", - serial_number: "FCI1234567" + "fabric_deployment_disabled": true, + "fabric_freeze_mode": true, + "fabric_name": "MyFabric", + "fabric_read_only": true, + "mode": "maintenance", + "role": "spine", + "serial_number": "FCI1234567" }, "192.169.1.3": { - fabric_deployment_disabled: false - fabric_freeze_mode: false, - fabric_name: "YourFabric", - fabric_read_only: false - mode: "normal", - role: "leaf", - serial_number: "FCH2345678" + "fabric_deployment_disabled": false, + "fabric_freeze_mode": false, + "fabric_name": "YourFabric", + "fabric_read_only": false, + "mode": "normal", + "role": "leaf", + "serial_number": "FCH2345678" } } ``` """ - method_name = inspect.stack()[0][3] # pylint: disable=unused-variable + method_name: str = inspect.stack()[0][3] # pylint: disable=unused-variable try: self.maintenance_mode_info.rest_send = self.rest_send self.maintenance_mode_info.results = self.results - self.maintenance_mode_info.config = [ - item["ip_address"] for item in self.config.get("switches", {}) - ] + self.maintenance_mode_info.config = [item["ip_address"] for item in self.config.get("switches", {})] self.maintenance_mode_info.refresh() except (TypeError, ValueError) as error: msg = f"{self.class_name}.{method_name}: " @@ -1225,21 +1456,24 @@ def get_have(self): def commit(self) -> None: """ - ### Summary + # Summary + Query the switches in self.want that exist on the controller and update ``self.results`` with the query results. - ### Raises - - ``ValueError`` if: - - ``rest_send`` is not set. - - ``get_want()`` raises ``ValueError`` - - ``get_have()`` raises ``ValueError`` + ## Raises + + ### ValueError + + - `rest_send` is not set + - `get_want()` raises `ValueError` + - `get_have()` raises `ValueError` """ - 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) - 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 before calling commit." raise ValueError(msg) @@ -1280,7 +1514,7 @@ def commit(self) -> None: def main(): """main entry point for module execution""" - argument_spec = {} + argument_spec: dict[str, Any] = {} argument_spec["config"] = { "required": True, "type": "dict", @@ -1292,10 +1526,8 @@ def main(): "type": "str", } - ansible_module = AnsibleModule( - argument_spec=argument_spec, supports_check_mode=True - ) - params = copy.deepcopy(ansible_module.params) + ansible_module: AnsibleModule = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True) + params: dict[str, Any] = copy.deepcopy(ansible_module.params) params["check_mode"] = ansible_module.check_mode # Logging setup @@ -1305,9 +1537,9 @@ def main(): except ValueError as error: ansible_module.fail_json(str(error)) - sender = Sender() + sender: Sender = Sender() sender.ansible_module = ansible_module - rest_send = RestSend(params) + rest_send: RestSend = RestSend(params) rest_send.response_handler = ResponseHandler() rest_send.sender = sender @@ -1333,15 +1565,22 @@ def main(): msg = f"Unknown state {params['state']}" ansible_module.fail_json(msg) - task.results.build_final_result() + if params["state"] == "merged": + task.results.build_final_result() + final_result = task.results.final_result + failed = task.results.failed + else: + task.results.build_final_result() + final_result = task.results.final_result + failed = task.results.failed # Results().failed is a property that returns a set() # of boolean values. pylint doesn't seem to understand this so we've # disabled the unsupported-membership-test warning. - if True in task.results.failed: # pylint: disable=unsupported-membership-test + if True in failed: # pylint: disable=unsupported-membership-test msg = "Module failed." - ansible_module.fail_json(msg, **task.results.final_result) - ansible_module.exit_json(**task.results.final_result) + ansible_module.fail_json(msg, **final_result) + ansible_module.exit_json(**final_result) if __name__ == "__main__": diff --git a/tests/unit/module_utils/common/common_utils.py b/tests/unit/module_utils/common/common_utils.py index 80964b263..62c918f29 100644 --- a/tests/unit/module_utils/common/common_utils.py +++ b/tests/unit/module_utils/common/common_utils.py @@ -1,4 +1,7 @@ -# Copyright (c) 2024 Cisco and/or its affiliates. +""" +Utility functions and fixtures for unit tests in tests/unit/module_utils/common +""" +# 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. @@ -14,7 +17,7 @@ from __future__ import absolute_import, division, print_function -__metaclass__ = type +__metaclass__ = type # pylint: disable=invalid-name from contextlib import contextmanager diff --git a/tests/unit/module_utils/common/test_maintenance_mode.py b/tests/unit/module_utils/common/test_maintenance_mode.py index c18cd0793..24a89ce04 100644 --- a/tests/unit/module_utils/common/test_maintenance_mode.py +++ b/tests/unit/module_utils/common/test_maintenance_mode.py @@ -1,3 +1,6 @@ +""" +Unit tests for MaintenanceMode class in module_utils/common/maintenance_mode.py +""" # Copyright (c) 2024 Cisco and/or its affiliates. # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -21,6 +24,7 @@ # pylint: disable=protected-access # pylint: disable=unused-argument # pylint: disable=invalid-name +# pylint: disable=too-many-lines from __future__ import absolute_import, division, print_function @@ -34,24 +38,24 @@ import pytest from ansible_collections.cisco.dcnm.plugins.module_utils.common.api.v1.lan_fabric.rest.control.fabrics.fabrics import ( - EpMaintenanceModeDisable, EpMaintenanceModeEnable) -from ansible_collections.cisco.dcnm.plugins.module_utils.common.conversion import \ - ConversionUtils -from ansible_collections.cisco.dcnm.plugins.module_utils.common.exceptions import \ - ControllerResponseError -from ansible_collections.cisco.dcnm.plugins.module_utils.common.maintenance_mode import \ - MaintenanceMode -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 + EpMaintenanceModeDisable, + EpMaintenanceModeEnable, +) +from ansible_collections.cisco.dcnm.plugins.module_utils.common.conversion import ConversionUtils +from ansible_collections.cisco.dcnm.plugins.module_utils.common.exceptions import ControllerResponseError +from ansible_collections.cisco.dcnm.plugins.module_utils.common.maintenance_mode import MaintenanceMode +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, does_not_raise, maintenance_mode_fixture, params, - responses_deploy_maintenance_mode, responses_maintenance_mode) + ResponseGenerator, + does_not_raise, + maintenance_mode_fixture, + params, + responses_deploy_maintenance_mode, + responses_maintenance_mode, +) FABRIC_NAME = "VXLAN_Fabric" CONFIG = [ @@ -68,61 +72,74 @@ def test_maintenance_mode_00000(maintenance_mode) -> None: """ - Classes and Methods - - MaintenanceMode - - __init__() + # Summary + + Verify MaintenanceMode class attributes are initialized to expected values. + + ## Classes and Methods + + - MaintenanceMode.__init__() + + ## Test - Test - Class attributes are initialized to expected values - Exception is not raised """ with does_not_raise(): instance = maintenance_mode - assert instance._config is None - assert instance._rest_send is None - assert instance._results is None + assert instance._config == [] + assert instance._endpoints == [] + assert instance._rest_send.class_name == "RestSend" + assert instance._rest_send.params == {} + assert instance._results.class_name == "Results" assert instance.action == "maintenance_mode" - assert instance.check_mode is False + assert instance._check_mode is False assert instance.class_name == "MaintenanceMode" - assert instance.config is None - assert instance.deploy_dict == {} - assert instance.rest_send is None - assert instance.results is None + assert instance._deploy_dict == {} assert instance.serial_number_to_ip_address == {} assert instance.state == "merged" - assert instance.valid_modes == ["maintenance", "normal"] + assert instance._valid_modes == ["maintenance", "normal"] - assert isinstance(instance.conversion, ConversionUtils) - assert isinstance(instance.ep_maintenance_mode_disable, EpMaintenanceModeDisable) - assert isinstance(instance.ep_maintenance_mode_enable, EpMaintenanceModeEnable) + assert isinstance(instance._conversion, ConversionUtils) + assert isinstance(instance._ep_maintenance_mode_disable, EpMaintenanceModeDisable) + assert isinstance(instance._ep_maintenance_mode_enable, EpMaintenanceModeEnable) def test_maintenance_mode_00010() -> None: """ - Classes and Methods - - MaintenanceMode - - __init__() + # Summary + + Verify check_mode is set to False when params is missing check_mode key. - Test - - ``ValueError`` is raised when params is missing check_mode key. + ## Classes and Methods + + - MaintenanceMode.__init__() + + ## Test + + - check_mode is set to False when params is missing check_mode key """ params = {"state": "merged"} - match = r"MaintenanceMode\.__init__:\s+" - match += r"params is missing mandatory parameter: check_mode\." - with pytest.raises(ValueError, match=match): - instance = MaintenanceMode(params) # pylint: disable=unused-variable + with does_not_raise(): + instance = MaintenanceMode(params) + assert instance._check_mode is False def test_maintenance_mode_00020() -> None: """ - Classes and Methods - - MaintenanceMode - - __init__() + # Summary + + Verify `ValueError` is raised when params is missing state key. + + ## Classes and Methods - Test - - ``ValueError`` is raised when params is missing state key. + - MaintenanceMode.__init__() + + ## Test + + - `ValueError` is raised when params is missing state key """ params = {"check_mode": False} match = r"MaintenanceMode\.__init__:\s+" @@ -133,31 +150,33 @@ def test_maintenance_mode_00020() -> None: def test_maintenance_mode_00030(maintenance_mode) -> None: """ - Classes and Methods - - MaintenanceMode() - - __init__() - - verify_commit_parameters() - - commit() - - Summary - - Verify MaintenanceMode().commit() raises ``ValueError`` when - ``config`` is not set. - - Code Flow - Setup - - MaintenanceMode() is instantiated - - Other required attributes are set - - Code Flow - Test - - ``MaintenanceMode().commit()`` is called without having first set - ``MaintenanceMode().config`` - - Expected Result - - ``ValueError`` is raised - - Exception message matches expected + # Summary + + Verify MaintenanceMode().commit() raises `ValueError` when `config` is not set. + + ## Classes and Methods + + - MaintenanceMode.__init__() + - MaintenanceMode.verify_commit_parameters() + - MaintenanceMode.commit() + + ## Code Flow - Setup + + - MaintenanceMode() is instantiated + - Other required attributes are set + + ## Code Flow - Test + + - MaintenanceMode().commit() is called without having first set MaintenanceMode().config + + ## Expected Result + + - `ValueError` is raised + - Exception message matches expected """ with does_not_raise(): instance = maintenance_mode - instance.rest_send = RestSend({}) + instance.rest_send = RestSend(params=params) instance.results = Results() match = r"MaintenanceMode\.verify_commit_parameters: " @@ -169,71 +188,37 @@ def test_maintenance_mode_00030(maintenance_mode) -> None: def test_maintenance_mode_00040(maintenance_mode) -> None: """ - Classes and Methods - - MaintenanceMode() - - __init__() - - verify_commit_parameters() - - commit() - - Summary - - Verify MaintenanceMode().commit() raises ``ValueError`` - when ``rest_send`` is not set. - - Code Flow - Setup - - MaintenanceMode() is instantiated - - Other required attributes are set - - Code Flow - Test - - MaintenanceMode().commit() is called without having - first set MaintenanceMode().rest_send - - Expected Result - - ``ValueError`` is raised - - Exception message matches expected - """ - with does_not_raise(): - instance = maintenance_mode - instance.results = Results() - instance.config = CONFIG + # Summary - match = r"MaintenanceMode\.verify_commit_parameters: " - match += r"MaintenanceMode\.rest_send must be set before calling\s+" - match += r"commit\." - with pytest.raises(ValueError, match=match): - instance.commit() + Verify MaintenanceMode().commit() raises `ValueError` when `rest_send` is not set. + ## Classes and Methods -def test_maintenance_mode_00050(maintenance_mode) -> None: - """ - Classes and Methods - - MaintenanceMode() - - __init__() - - verify_commit_parameters() - - commit() - - Summary - - Verify MaintenanceMode().commit() raises ``ValueError`` - when ``MaintenanceMode().results`` is not set. - - Code Flow - Setup - - MaintenanceMode() is instantiated - - Other required attributes are set - - Code Flow - Test - - MaintenanceMode().commit() is called without having - first set MaintenanceMode().results - - Expected Result - - ``ValueError`` is raised - - Exception message matches expected + - MaintenanceMode.__init__() + - MaintenanceMode.verify_commit_parameters() + - MaintenanceMode.commit() + + ## Code Flow - Setup + + - MaintenanceMode() is instantiated + - Other required attributes are set + + ## Code Flow - Test + + - MaintenanceMode().commit() is called without having first set MaintenanceMode().rest_send + + ## Expected Result + + - `ValueError` is raised + - Exception message matches expected """ with does_not_raise(): instance = maintenance_mode - instance.rest_send = RestSend({}) + instance.results = Results() instance.config = CONFIG match = r"MaintenanceMode\.verify_commit_parameters: " - match += r"MaintenanceMode\.results must be set before calling\s+" + match += r"MaintenanceMode\.rest_send must be set before calling\s+" match += r"commit\." with pytest.raises(ValueError, match=match): instance.commit() @@ -247,34 +232,39 @@ def test_maintenance_mode_00050(maintenance_mode) -> None: (ValueError, ValueError, "Bad value"), ], ) -def test_maintenance_mode_00200( - monkeypatch, maintenance_mode, mock_exception, expected_exception, mock_message -) -> None: +def test_maintenance_mode_00200(monkeypatch, maintenance_mode, mock_exception, expected_exception, mock_message) -> None: """ - Classes and Methods - - MaintenanceMode() - - __init__() - - commit() - - Summary - - Verify MaintenanceMode().commit() raises ``ValueError`` when - ``MaintenanceMode().change_system_mode`` raises any of: - - ``ControllerResponseError`` - - ``TypeError`` - - ``ValueError`` - - - Code Flow - Setup - - MaintenanceMode() is instantiated - - Required attributes are set - - change_system_mode() is mocked to raise each of the above exceptions - - Code Flow - Test - - MaintenanceMode().commit() is called for each exception - - Expected Result - - ``ValueError`` is raised - - Exception message matches expected + # Summary + + Verify MaintenanceMode().commit() raises `ValueError` when change_system_mode raises exceptions. + + ## Classes and Methods + + - MaintenanceMode.__init__() + - MaintenanceMode.commit() + + ## Test + + Verify MaintenanceMode().commit() raises `ValueError` when MaintenanceMode().change_system_mode raises any of: + + - `ControllerResponseError` + - `TypeError` + - `ValueError` + + ## Code Flow - Setup + + - MaintenanceMode() is instantiated + - Required attributes are set + - change_system_mode() is mocked to raise each of the above exceptions + + ## Code Flow - Test + + - MaintenanceMode().commit() is called for each exception + + ## Expected Result + + - `ValueError` is raised + - Exception message matches expected """ def mock_change_system_mode(*args, **kwargs): @@ -283,7 +273,7 @@ def mock_change_system_mode(*args, **kwargs): with does_not_raise(): instance = maintenance_mode instance.config = CONFIG - instance.rest_send = RestSend({}) + instance.rest_send = RestSend(params=params) instance.results = Results() monkeypatch.setattr(instance, "change_system_mode", mock_change_system_mode) @@ -298,34 +288,39 @@ def mock_change_system_mode(*args, **kwargs): (ValueError, ValueError, "Bad value"), ], ) -def test_maintenance_mode_00210( - monkeypatch, maintenance_mode, mock_exception, expected_exception, mock_message -) -> None: +def test_maintenance_mode_00210(monkeypatch, maintenance_mode, mock_exception, expected_exception, mock_message) -> None: """ - Classes and Methods - - MaintenanceMode() - - __init__() - - commit() - - Summary - - Verify MaintenanceMode().commit() raises ``ValueError`` when - ``MaintenanceMode().deploy_switches`` raises any of: - - ``ControllerResponseError`` - - ``ValueError`` - - - Code Flow - Setup - - MaintenanceMode() is instantiated - - Required attributes are set - - change_system_mode() is mocked to do nothing - - deploy_switches() is mocked to raise each of the above exceptions - - Code Flow - Test - - MaintenanceMode().commit() is called for each exception - - Expected Result - - ``ValueError`` is raised - - Exception message matches expected + # Summary + + Verify MaintenanceMode().commit() raises `ValueError` when deploy_switches raises exceptions. + + ## Classes and Methods + + - MaintenanceMode.__init__() + - MaintenanceMode.commit() + + ## Test + + Verify MaintenanceMode().commit() raises `ValueError` when MaintenanceMode().deploy_switches raises any of: + + - `ControllerResponseError` + - `ValueError` + + ## Code Flow - Setup + + - MaintenanceMode() is instantiated + - Required attributes are set + - change_system_mode() is mocked to do nothing + - deploy_switches() is mocked to raise each of the above exceptions + + ## Code Flow - Test + + - MaintenanceMode().commit() is called for each exception + + ## Expected Result + + - `ValueError` is raised + - Exception message matches expected """ def mock_change_system_mode(*args, **kwargs): @@ -337,7 +332,7 @@ def mock_deploy_switches(*args, **kwargs): with does_not_raise(): instance = maintenance_mode instance.config = CONFIG - instance.rest_send = RestSend({}) + instance.rest_send = RestSend(params=params) instance.results = Results() monkeypatch.setattr(instance, "change_system_mode", mock_change_system_mode) @@ -357,34 +352,41 @@ def mock_deploy_switches(*args, **kwargs): ) def test_maintenance_mode_00220(maintenance_mode, mode, deploy) -> None: """ - Classes and Methods - - MaintenanceMode() - - __init__() - - commit() - - change_system_mode() - - deploy_switches() - - Summary - - Verify commit() success case: - - RETURN_CODE is 200. - - Controller response contains expected structure and values. - - Code Flow - Setup - - MaintenanceMode() is instantiated - - Sender() is mocked to return expected responses - - Required attributes are set - - MaintenanceMode().commit() is called - - responses_MaintenanceMode contains a dict with: - - RETURN_CODE == 200 - - DATA == {"status": "Success"} - - Code Flow - Test - - MaintenanceMode().commit() is called - - Expected Result - - Exception is not raised - - instance.response_data returns expected data - - MaintenanceMode()._properties are updated + # Summary + + Verify commit() success case with RETURN_CODE 200. + + ## Classes and Methods + + - MaintenanceMode.__init__() + - MaintenanceMode.commit() + - MaintenanceMode.change_system_mode() + - MaintenanceMode.deploy_switches() + + ## Test + + - RETURN_CODE is 200 + - Controller response contains expected structure and values + + ## Code Flow - Setup + + - MaintenanceMode() is instantiated + - Sender() is mocked to return expected responses + - Required attributes are set + - MaintenanceMode().commit() is called + - responses_MaintenanceMode contains a dict with: + - RETURN_CODE == 200 + - DATA == {"status": "Success"} + + ## Code Flow - Test + + - MaintenanceMode().commit() is called + + ## Expected Result + + - Exception is not raised + - instance.response_data returns expected data + - MaintenanceMode()._properties are updated """ method_name = inspect.stack()[0][3] key = f"{method_name}a" @@ -438,10 +440,7 @@ def responses(): assert instance.results.diff[1].get("deploy_maintenance_mode", None) is True assert instance.results.diff[1].get("sequence_number", None) == 2 - assert ( - instance.results.metadata[1].get("action", None) - == "deploy_maintenance_mode" - ) + assert instance.results.metadata[1].get("action", None) == "deploy_maintenance_mode" assert instance.results.metadata[1].get("sequence_number", None) == 2 assert instance.results.metadata[1].get("state", None) == "merged" @@ -464,38 +463,44 @@ def responses(): ) def test_maintenance_mode_00230(maintenance_mode, mode) -> None: """ - Classes and Methods - - MaintenanceMode() - - __init__() - - commit() - - change_system_mode() - - deploy_switches() - - Summary - - Verify commit() unsuccessful case: - - RETURN_CODE == 500. - - commit raises ``ValueError`` when change_system_mode() raises - ``ControllerResponseError``. - - Controller response contains expected structure and values. - - Code Flow - Setup - - MaintenanceMode() is instantiated - - Sender() is mocked to return expected responses - - Required attributes are set - - MaintenanceMode().commit() is called - - responses_MaintenanceMode contains a dict with: - - RETURN_CODE == 500 - - DATA == {"status": "Failure"} - - Code Flow - Test - - ``MaintenanceMode().commit()`` is called - - ``change_system_mode()`` raises ``ControllerResponseError`` - - ``commit()`` raises ``ValueError`` - - Expected Result - - ``commit()`` raises ``ValueError`` - - instance.response_data returns expected data - - MaintenanceMode()._properties are updated + # Summary + + Verify commit() unsuccessful case with RETURN_CODE 500. + + ## Classes and Methods + + - MaintenanceMode.__init__() + - MaintenanceMode.commit() + - MaintenanceMode.change_system_mode() + - MaintenanceMode.deploy_switches() + + ## Test + + - RETURN_CODE == 500 + - commit raises `ValueError` when change_system_mode() raises `ControllerResponseError` + - Controller response contains expected structure and values + + ## Code Flow - Setup + + - MaintenanceMode() is instantiated + - Sender() is mocked to return expected responses + - Required attributes are set + - MaintenanceMode().commit() is called + - responses_MaintenanceMode contains a dict with: + - RETURN_CODE == 500 + - DATA == {"status": "Failure"} + + ## Code Flow - Test + + - MaintenanceMode().commit() is called + - change_system_mode() raises `ControllerResponseError` + - commit() raises `ValueError` + + ## Expected Result + + - commit() raises `ValueError` + - instance.response_data returns expected data + - MaintenanceMode()._properties are updated """ method_name = inspect.stack()[0][3] key = f"{method_name}a" @@ -550,30 +555,35 @@ def responses(): def test_maintenance_mode_00300(maintenance_mode) -> None: """ - Classes and Methods - - MaintenanceMode() - - __init__() - - verify_config_parameters() - - config.setter - - Summary - - Verify MaintenanceMode().verify_config_parameters() raises - - ``TypeError`` if: - - value is not a list - - Verify MaintenanceMode().config.setter re-raises: - - ``TypeError`` as ``ValueError`` - - Code Flow - Setup - - MaintenanceMode() is instantiated - - config is set to a non-list value - - Code Flow - Test - - MaintenanceMode().config.setter is accessed with non-list - - Expected Result - - verify_config_parameters() raises ``TypeError``. - - config.setter re-raises as ``ValueError``. - - Exception message matches expected. + # Summary + + Verify MaintenanceMode().verify_config_parameters() raises `TypeError` if value is not a list. + + ## Classes and Methods + + - MaintenanceMode.__init__() + - MaintenanceMode.verify_config_parameters() + - MaintenanceMode.config.setter + + ## Test + + - Verify MaintenanceMode().verify_config_parameters() raises `TypeError` if value is not a list + - Verify MaintenanceMode().config.setter re-raises `TypeError` as `ValueError` + + ## Code Flow - Setup + + - MaintenanceMode() is instantiated + - config is set to a non-list value + + ## Code Flow - Test + + - MaintenanceMode().config.setter is accessed with non-list + + ## Expected Result + + - verify_config_parameters() raises `TypeError` + - config.setter re-raises as `ValueError` + - Exception message matches expected """ with does_not_raise(): instance = maintenance_mode @@ -597,33 +607,39 @@ def test_maintenance_mode_00300(maintenance_mode) -> None: ) def test_maintenance_mode_00310(maintenance_mode, remove_param) -> None: """ - Classes and Methods - - MaintenanceMode() - - __init__() - - verify_config_parameters() - - config.setter - - Summary - - Verify MaintenanceMode().verify_config_parameters() raises - - ``ValueError`` if: - - deploy is missing from config - - fabric_name is missing from config - - ip_address is missing from config - - mode is missing from config - - serial_number is missing from config - - wait_for_mode_change is missing from config - - - Code Flow - Setup - - MaintenanceMode() is instantiated - - Code Flow - Test - - MaintenanceMode().config is set to a dict with all of the above - keys present, except that each key, in turn, is removed. - - Expected Result - - ``ValueError`` is raised - - Exception message matches expected + # Summary + + Verify MaintenanceMode().verify_config_parameters() raises `ValueError` for missing config keys. + + ## Classes and Methods + + - MaintenanceMode.__init__() + - MaintenanceMode.verify_config_parameters() + - MaintenanceMode.config.setter + + ## Test + + Verify MaintenanceMode().verify_config_parameters() raises `ValueError` if: + + - deploy is missing from config + - fabric_name is missing from config + - ip_address is missing from config + - mode is missing from config + - serial_number is missing from config + - wait_for_mode_change is missing from config + + ## Code Flow - Setup + + - MaintenanceMode() is instantiated + + ## Code Flow - Test + + - MaintenanceMode().config is set to a dict with all of the above keys present, except that each key, in turn, is removed + + ## Expected Result + + - `ValueError` is raised + - Exception message matches expected """ with does_not_raise(): @@ -650,29 +666,34 @@ def test_maintenance_mode_00310(maintenance_mode, remove_param) -> None: ) def test_maintenance_mode_00400(maintenance_mode, param, raises) -> None: """ - Classes and Methods - - MaintenanceMode() - - __init__() - - verify_config_parameters() - - config.setter - - Summary - - Verify MaintenanceMode().verify_config_parameters() re-raises - - ``ValueError`` if: - - ``deploy`` raises ``TypeError`` - - Code Flow - Setup - - MaintenanceMode() is instantiated - - Code Flow - Test - - MaintenanceMode().config is set to a dict. - - The dict is updated with deploy set to valid and invalid - values of ``deploy`` - - Expected Result - - ``ValueError`` is raised when deploy is not a boolean - - Exception message matches expected - - Exception is not raised when deploy is a boolean + # Summary + + Verify MaintenanceMode().verify_config_parameters() validates deploy parameter type. + + ## Classes and Methods + + - MaintenanceMode.__init__() + - MaintenanceMode.verify_config_parameters() + - MaintenanceMode.config.setter + + ## Test + + - Verify MaintenanceMode().verify_config_parameters() re-raises `ValueError` if `deploy` raises `TypeError` + + ## Code Flow - Setup + + - MaintenanceMode() is instantiated + + ## Code Flow - Test + + - MaintenanceMode().config is set to a dict + - The dict is updated with deploy set to valid and invalid values of `deploy` + + ## Expected Result + + - `ValueError` is raised when deploy is not a boolean + - Exception message matches expected + - Exception is not raised when deploy is a boolean """ with does_not_raise(): @@ -703,30 +724,34 @@ def test_maintenance_mode_00400(maintenance_mode, param, raises) -> None: ) def test_maintenance_mode_00500(maintenance_mode, param, raises) -> None: """ - Classes and Methods - - MaintenanceMode() - - __init__() - - verify_config_parameters() - - config.setter - - Summary - - Verify MaintenanceMode().verify_config_parameters() re-raises - - ``ValueError`` if: - - ``fabric_name`` raises ``ValueError`` due to being an - invalid value. - - Code Flow - Setup - - MaintenanceMode() is instantiated - - Code Flow - Test - - MaintenanceMode().config is set to a dict. - - The dict is updated with fabric_name set to valid and invalid - values of ``fabric_name`` - - Expected Result - - ``ValueError`` is raised when fabric_name is not a valid value - - Exception message matches expected - - Exception is not raised when fabric_name is a valid value + # Summary + + Verify MaintenanceMode().verify_config_parameters() validates fabric_name parameter. + + ## Classes and Methods + + - MaintenanceMode.__init__() + - MaintenanceMode.verify_config_parameters() + - MaintenanceMode.config.setter + + ## Test + + - Verify MaintenanceMode().verify_config_parameters() re-raises `ValueError` if `fabric_name` raises `ValueError` due to being an invalid value + + ## Code Flow - Setup + + - MaintenanceMode() is instantiated + + ## Code Flow - Test + + - MaintenanceMode().config is set to a dict + - The dict is updated with fabric_name set to valid and invalid values of `fabric_name` + + ## Expected Result + + - `ValueError` is raised when fabric_name is not a valid value + - Exception message matches expected + - Exception is not raised when fabric_name is a valid value """ with does_not_raise(): @@ -758,30 +783,34 @@ def test_maintenance_mode_00500(maintenance_mode, param, raises) -> None: ) def test_maintenance_mode_00600(maintenance_mode, param, raises) -> None: """ - Classes and Methods - - MaintenanceMode() - - __init__() - - verify_config_parameters() - - config.setter - - Summary - - Verify MaintenanceMode().verify_config_parameters() re-raises - - ``ValueError`` if: - - ``mode`` raises ``ValueError`` due to being an - invalid value. - - Code Flow - Setup - - MaintenanceMode() is instantiated - - Code Flow - Test - - MaintenanceMode().config is set to a dict. - - The dict is updated with mode set to valid and invalid - values of ``mode`` - - Expected Result - - ``ValueError`` is raised when mode is not a valid value - - Exception message matches expected - - Exception is not raised when mode is a valid value + # Summary + + Verify MaintenanceMode().verify_config_parameters() validates mode parameter. + + ## Classes and Methods + + - MaintenanceMode.__init__() + - MaintenanceMode.verify_config_parameters() + - MaintenanceMode.config.setter + + ## Test + + - Verify MaintenanceMode().verify_config_parameters() re-raises `ValueError` if `mode` raises `ValueError` due to being an invalid value + + ## Code Flow - Setup + + - MaintenanceMode() is instantiated + + ## Code Flow - Test + + - MaintenanceMode().config is set to a dict + - The dict is updated with mode set to valid and invalid values of `mode` + + ## Expected Result + + - `ValueError` is raised when mode is not a valid value + - Exception message matches expected + - Exception is not raised when mode is a valid value """ with does_not_raise(): @@ -812,29 +841,34 @@ def test_maintenance_mode_00600(maintenance_mode, param, raises) -> None: ) def test_maintenance_mode_00700(maintenance_mode, param, raises) -> None: """ - Classes and Methods - - MaintenanceMode() - - __init__() - - verify_config_parameters() - - config.setter - - Summary - - Verify MaintenanceMode().verify_config_parameters() re-raises - - ``ValueError`` if: - - ``wait_for_mode_change`` raises ``TypeError`` - - Code Flow - Setup - - MaintenanceMode() is instantiated - - Code Flow - Test - - MaintenanceMode().config is set to a dict. - - The dict is updated with wait_for_mode_change set to valid and invalid - values of ``wait_for_mode_change`` - - Expected Result - - ``ValueError`` is raised when wait_for_mode_change is not a boolean - - Exception message matches expected - - Exception is not raised when wait_for_mode_change is a boolean + # Summary + + Verify MaintenanceMode().verify_config_parameters() validates wait_for_mode_change parameter type. + + ## Classes and Methods + + - MaintenanceMode.__init__() + - MaintenanceMode.verify_config_parameters() + - MaintenanceMode.config.setter + + ## Test + + - Verify MaintenanceMode().verify_config_parameters() re-raises `ValueError` if `wait_for_mode_change` raises `TypeError` + + ## Code Flow - Setup + + - MaintenanceMode() is instantiated + + ## Code Flow - Test + + - MaintenanceMode().config is set to a dict + - The dict is updated with wait_for_mode_change set to valid and invalid values of `wait_for_mode_change` + + ## Expected Result + + - `ValueError` is raised when wait_for_mode_change is not a boolean + - Exception message matches expected + - Exception is not raised when wait_for_mode_change is a boolean """ with does_not_raise(): @@ -856,10 +890,10 @@ def test_maintenance_mode_00700(maintenance_mode, param, raises) -> None: @pytest.mark.parametrize( "endpoint_instance, mock_exception, expected_exception, mock_message", [ - ("ep_maintenance_mode_disable", TypeError, ValueError, "Bad type"), - ("ep_maintenance_mode_disable", ValueError, ValueError, "Bad value"), - ("ep_maintenance_mode_enable", TypeError, ValueError, "Bad type"), - ("ep_maintenance_mode_enable", ValueError, ValueError, "Bad value"), + ("_ep_maintenance_mode_disable", TypeError, ValueError, "Bad type"), + ("_ep_maintenance_mode_disable", ValueError, ValueError, "Bad value"), + ("_ep_maintenance_mode_enable", TypeError, ValueError, "Bad type"), + ("_ep_maintenance_mode_enable", ValueError, ValueError, "Bad value"), ], ) def test_maintenance_mode_00800( @@ -871,32 +905,37 @@ def test_maintenance_mode_00800( mock_message, ) -> None: """ - Classes and Methods - - MaintenanceMode() - - __init__() - - commit() - - Summary - - Verify MaintenanceMode().change_system_mode() raises ``ValueError`` - when ``EpMaintenanceModeEnable`` or ``EpMaintenanceModeDisable`` raise - any of: - - ``TypeError`` - - ``ValueError`` - - Code Flow - Setup - - MaintenanceMode() is instantiated - - Required attributes are set - - EpMaintenanceModeEnable() is mocked to raise each - of the above exceptions - - EpMaintenanceModeDisable() is mocked to raise each - of the above exceptions - - Code Flow - Test - - MaintenanceMode().commit() is called for each exception - - Expected Result - - ``ValueError`` is raised. - - Exception message matches expected. + # Summary + + Verify MaintenanceMode().change_system_mode() raises `ValueError` when endpoint classes raise exceptions. + + ## Classes and Methods + + - MaintenanceMode.__init__() + - MaintenanceMode.commit() + + ## Test + + Verify MaintenanceMode().change_system_mode() raises `ValueError` when `EpMaintenanceModeEnable` or `EpMaintenanceModeDisable` raise any of: + + - `TypeError` + - `ValueError` + + ## Code Flow - Setup + + - MaintenanceMode() is instantiated + - Required attributes are set + - EpMaintenanceModeEnable() is mocked to raise each of the above exceptions + - EpMaintenanceModeDisable() is mocked to raise each of the above exceptions + + ## Code Flow - Test + + - MaintenanceMode().commit() is called for each exception + + ## Expected Result + + - `ValueError` is raised + - Exception message matches expected """ class MockEndpoint: @@ -933,10 +972,10 @@ def serial_number(self, value): with does_not_raise(): instance = maintenance_mode config = copy.deepcopy(CONFIG[0]) - if endpoint_instance == "ep_maintenance_mode_disable": + if endpoint_instance == "_ep_maintenance_mode_disable": config["mode"] = "normal" instance.config = [config] - instance.rest_send = RestSend({}) + instance.rest_send = RestSend(params=params) instance.results = Results() monkeypatch.setattr(instance, endpoint_instance, MockEndpoint()) @@ -947,8 +986,8 @@ def serial_number(self, value): @pytest.mark.parametrize( "endpoint_instance, mock_exception, expected_exception, mock_message", [ - ("ep_maintenance_mode_deploy", TypeError, ValueError, "Bad type"), - ("ep_maintenance_mode_deploy", ValueError, ValueError, "Bad value"), + ("_ep_maintenance_mode_deploy", TypeError, ValueError, "Bad type"), + ("_ep_maintenance_mode_deploy", ValueError, ValueError, "Bad value"), ], ) def test_maintenance_mode_00900( @@ -960,29 +999,36 @@ def test_maintenance_mode_00900( mock_message, ) -> None: """ - Classes and Methods - - MaintenanceMode() - - __init__() - - commit() + # Summary + + Verify MaintenanceMode().deploy_switches() raises `ValueError` when EpMaintenanceModeDeploy raises exceptions. - Summary - - Verify MaintenanceMode().deploy_switches() raises ``ValueError`` - when ``EpMaintenanceModeDeploy`` raises any of: - - ``TypeError`` - - ``ValueError`` + ## Classes and Methods + - MaintenanceMode.__init__() + - MaintenanceMode.commit() - Code Flow - Setup - - MaintenanceMode() is instantiated - - Required attributes are set - - EpMaintenanceModeDeploy() is mocked to raise each of the above exceptions + ## Test - Code Flow - Test - - MaintenanceMode().commit() is called for each exception + Verify MaintenanceMode().deploy_switches() raises `ValueError` when `EpMaintenanceModeDeploy` raises any of: - Expected Result - - ``TypeError`` and ``ValueError`` are raised. - - Exception message matches expected. + - `TypeError` + - `ValueError` + + ## Code Flow - Setup + + - MaintenanceMode() is instantiated + - Required attributes are set + - EpMaintenanceModeDeploy() is mocked to raise each of the above exceptions + + ## Code Flow - Test + + - MaintenanceMode().commit() is called for each exception + + ## Expected Result + + - `TypeError` and `ValueError` are raised + - Exception message matches expected """ class MockEndpoint: @@ -1063,35 +1109,38 @@ def responses(): (ValueError, ValueError, r"Converted ValueError to ValueError"), ], ) -def test_maintenance_mode_01000( - maintenance_mode, mock_exception, expected_exception, mock_message -) -> None: +def test_maintenance_mode_01000(maintenance_mode, mock_exception, expected_exception, mock_message) -> None: """ - Classes and Methods - - MaintenanceMode() - - __init__() - - change_system_mode() + # Summary + + Verify MaintenanceMode().change_system_mode() raises `ValueError` when Results() raises exceptions. + + ## Classes and Methods + + - MaintenanceMode.__init__() + - MaintenanceMode.change_system_mode() + + ## Test + + Verify MaintenanceMode().change_system_mode() raises `ValueError` when MaintenanceMode().results() raises any of: + - `TypeError` + - `ValueError` - Summary - - Verify MaintenanceMode().change_system_mode() raises ``ValueError`` - when ``MaintenanceMode().results()`` raises any of: - - ``TypeError`` - - ``ValueError`` + ## Code Flow - Setup + - MaintenanceMode() is instantiated + - Required attributes are set + - Results().response_current.setter is mocked to raise each of the above exceptions - Code Flow - Setup - - MaintenanceMode() is instantiated - - Required attributes are set - - Results().response_current.setter is mocked to raise each of the above - exceptions + ## Code Flow - Test - Code Flow - Test - - MaintenanceMode().commit() is called for each exception + - MaintenanceMode().commit() is called for each exception - Expected Result - - ``ValueError`` is raised - - Exception message matches expected + ## Expected Result + + - `ValueError` is raised + - Exception message matches expected """ class MockResults: @@ -1139,29 +1188,35 @@ def responses(): def test_maintenance_mode_01100(monkeypatch, maintenance_mode) -> None: """ - Classes and Methods - - MaintenanceMode() - - __init__() - - commit() - - Summary - - Verify MaintenanceMode().commit() raises ``ValueError`` when - ``MaintenanceMode().deploy_switches()`` raises - ``ControllerResponseError`` when the RETURN_CODE in the - response is not 200. - - Code Flow - Setup - - MaintenanceMode() is instantiated - - Required attributes are set - - Code Flow - Test - - MaintenanceMode().commit() is called with simulated responses: - - 200 response for ``change_system_mode()`` - - 500 response ``deploy_switches()`` - - Expected Result - - ``ValueError``is raised. - - Exception message matches expected. + # Summary + + Verify MaintenanceMode().commit() raises `ValueError` when deploy_switches() gets non-200 response. + + ## Classes and Methods + + - MaintenanceMode.__init__() + - MaintenanceMode.commit() + + ## Test + + - Verify MaintenanceMode().commit() raises `ValueError` when MaintenanceMode().deploy_switches() raises `ControllerResponseError` + when the RETURN_CODE in the response is not 200 + + ## Code Flow - Setup + + - MaintenanceMode() is instantiated + - Required attributes are set + + ## Code Flow - Test + + - MaintenanceMode().commit() is called with simulated responses: + - 200 response for `change_system_mode()` + - 500 response `deploy_switches()` + + ## Expected Result + + - `ValueError` is raised + - Exception message matches expected """ def responses(): diff --git a/tests/unit/module_utils/common/test_maintenance_mode_info.py b/tests/unit/module_utils/common/test_maintenance_mode_info.py index 6d20cc9f3..cbe7ae76a 100644 --- a/tests/unit/module_utils/common/test_maintenance_mode_info.py +++ b/tests/unit/module_utils/common/test_maintenance_mode_info.py @@ -1,4 +1,7 @@ -# Copyright (c) 2024 Cisco and/or its affiliates. +""" +Unit tests for MaintenanceModeInfo class in tests/unit/module_utils/common/maintenance_mode_info.py +""" +# 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,38 +24,34 @@ # pylint: disable=protected-access # pylint: disable=unused-argument # pylint: disable=invalid-name +# pylint: disable=too-many-lines 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.conversion import \ - ConversionUtils -from ansible_collections.cisco.dcnm.plugins.module_utils.common.exceptions import \ - ControllerResponseError -from ansible_collections.cisco.dcnm.plugins.module_utils.common.maintenance_mode_info import \ - MaintenanceModeInfo -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.mocks.mock_fabric_details_by_name import \ - MockFabricDetailsByName -from ansible_collections.cisco.dcnm.tests.unit.mocks.mock_switch_details import \ - MockSwitchDetails +from ansible_collections.cisco.dcnm.plugins.module_utils.common.conversion import ConversionUtils +from ansible_collections.cisco.dcnm.plugins.module_utils.common.exceptions import ControllerResponseError +from ansible_collections.cisco.dcnm.plugins.module_utils.common.maintenance_mode_info import MaintenanceModeInfo +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.mocks.mock_fabric_details_by_name import MockFabricDetailsByName +from ansible_collections.cisco.dcnm.tests.unit.mocks.mock_switch_details import MockSwitchDetails from ansible_collections.cisco.dcnm.tests.unit.module_utils.common.common_utils import ( - ResponseGenerator, does_not_raise, maintenance_mode_info_fixture, - responses_fabric_details_by_name, responses_switch_details) + ResponseGenerator, + does_not_raise, + maintenance_mode_info_fixture, + responses_fabric_details_by_name, + responses_switch_details, +) FABRIC_NAME = "VXLAN_Fabric" CONFIG = ["192.168.1.2"] @@ -61,71 +60,89 @@ def test_maintenance_mode_info_00000(maintenance_mode_info) -> None: """ - ### Classes and Methods - - ``MaintenanceModeInfo()`` - - ``__init__()`` + # Summary + + Verify the `__init__()` method. + + ## Classes and Methods + + - MaintenanceModeInfo.__init__() + + ## Test + + - Class attributes are initialized to expected values. + - Exception is not raised. + + ## Setup - Data + + - None - ### Summary - - Verify the __init__() method. + ## Setup - Code - ### Setup - Data - - None + - None - ### Setup - Code - - None + ## Trigger - ### Trigger - - ``MaintenanceModeInfo`` is instantiated. + - `MaintenanceModeInfo` is instantiated. - ### Expected Result - - Class attributes are initialized to expected values. - - Exception is not raised. + ## Expected Result + + - Class attributes are initialized to expected values. + - Exception is not raised. """ with does_not_raise(): instance = maintenance_mode_info - assert instance._config is None - assert instance._info is None - assert instance._rest_send is None - assert instance._results is None - + assert instance._config == [] + assert instance._info == {} + assert instance._rest_send.class_name == "RestSend" + assert instance._results.class_name == "Results" + assert instance._valid_modes == ["inconsistent", "maintenance", "normal"] assert instance.action == "maintenance_mode_info" assert instance.class_name == "MaintenanceModeInfo" - assert instance.config is None - assert instance.rest_send is None - assert instance.results is None - assert isinstance(instance.conversion, ConversionUtils) + assert isinstance(instance._conversion, ConversionUtils) def test_maintenance_mode_info_00100(maintenance_mode_info) -> None: """ - ### Classes and Methods - - ``MaintenanceModeInfo()`` - - ``verify_refresh_parameters()`` - - ``refresh()`` + # Summary + + Verify `MaintenanceModeInfo().refresh()` raises `ValueError` when + `config` is not set. + + ## Classes and Methods + + - MaintenanceModeInfo.verify_refresh_parameters() + - MaintenanceModeInfo.refresh() + + ## Test + + - `ValueError` is raised. + - Exception message matches expectations. + + ## Setup - Data - ### Summary - - Verify MaintenanceModeInfo().refresh() raises ``ValueError`` when - ``config`` is not set. + - None - ### Setup - Data - - None + ## Setup - Code - ### Setup - Code - - ``MaintenanceModeInfo()`` is instantiated. - - Other required attributes are set. + - `MaintenanceModeInfo()` is instantiated. + - Other required attributes are set. - ### Trigger - - ``refresh()`` is called without having first set ``config``. + ## Trigger + + - `refresh()` is called without having first set `config`. + + ## Expected Result + + - `ValueError` is raised. + - Exception message matches expectations. - ### Expected Result - - ``ValueError`` is raised. - - Exception message matches expectations. """ with does_not_raise(): instance = maintenance_mode_info - instance.rest_send = RestSend({}) + instance.rest_send = RestSend(params=PARAMS) instance.results = Results() match = r"MaintenanceModeInfo\.verify_refresh_parameters: " @@ -137,72 +154,46 @@ def test_maintenance_mode_info_00100(maintenance_mode_info) -> None: def test_maintenance_mode_info_00110(maintenance_mode_info) -> None: """ - ### Classes and Methods - - ``MaintenanceModeInfo()`` - - ``verify_refresh_parameters()`` - - ``refresh()`` + # Summary - ### Summary - - Verify ``refresh()`` raises ``ValueError`` when ``rest_send`` - is not set. + Verify `refresh()` raises `ValueError` when `rest_send` is not set. - ### Setup - Data - - None + ## Classes and Methods - ### Setup - Code - - ``MaintenanceModeInfo()`` is instantiated. - - Other required attributes are set. + - MaintenanceModeInfo.verify_refresh_parameters() + - MaintenanceModeInfo.refresh() - ### Trigger - - ``refresh()`` is called without having first set ``rest_send``. + ## Test - ### Expected Result - - ``ValueError`` is raised. - - Exception message matches expectations. - """ - with does_not_raise(): - instance = maintenance_mode_info - instance.results = Results() - instance.config = CONFIG + - `ValueError` is raised. + - Exception message matches expectations. - match = r"MaintenanceModeInfo\.verify_refresh_parameters: " - match += r"MaintenanceModeInfo\.rest_send must be set before calling\s+" - match += r"refresh\." - with pytest.raises(ValueError, match=match): - instance.refresh() + ## Setup - Data + - None -def test_maintenance_mode_info_00120(maintenance_mode_info) -> None: - """ - ### Classes and Methods - - ``MaintenanceModeInfo()`` - - ``verify_refresh_parameters()`` - - ``refresh()`` + ## Setup - Code - ### Summary - - Verify ``refresh()`` raises ``ValueError`` when ``results`` is not set. + - `MaintenanceModeInfo()` is instantiated. + - Other required attributes are set. - ### Setup - Data - - None + ## Trigger - ### Setup - Code - - ``MaintenanceModeInfo()`` is instantiated. - - Other required attributes are set. + - `refresh()` is called without having first set `rest_send`. - ### Trigger - - ``refresh()`` is called without having first set ``results``. + ## Expected Result + + - `ValueError` is raised. + - Exception message matches expectations. - ### Expected Result - - ``ValueError`` is raised. - - Exception message matches expectations. """ with does_not_raise(): instance = maintenance_mode_info - instance.rest_send = RestSend({}) + instance.results = Results() instance.config = CONFIG match = r"MaintenanceModeInfo\.verify_refresh_parameters: " - match += r"MaintenanceModeInfo\.results must be set before calling\s+" + match += r"MaintenanceModeInfo\.rest_send must be set before calling\s+" match += r"refresh\." with pytest.raises(ValueError, match=match): instance.refresh() @@ -264,32 +255,41 @@ def test_maintenance_mode_info_00200( mock_message, ) -> None: """ - ### Classes and Methods - - ``MaintenanceModeInfo()`` - - ``refresh()`` - - ### Summary - - Verify ``refresh()`` raises ``ValueError`` when: - - ``fabric_details`` properties ``rest_send`` and ``results`` - raise ``TypeError``. - - ``switch_details`` properties ``rest_send`` and ``results`` - raise ``TypeError``. - - ### Setup - Data - - None - - ### Setup - Code - - ``MaintenanceModeInfo()`` is instantiated - - Required attributes are set - - ``FabricDetails()`` is mocked to conditionally raise ``TypeError``. - - ``SwitchDetails()`` is mocked to conditionally raise ``TypeError``. - - ### Trigger - - ``refresh()`` is called. - - ### Expected Result - - ``ValueError`` is raised. - - Exception message matches expectations. + # Summary + + Verify `refresh()` raises `ValueError` when: + - `fabric_details` properties `rest_send` and `results` raise `TypeError`. + - `switch_details` properties `rest_send` and `results` raise `TypeError`. + + ## Classes and Methods + + - MaintenanceModeInfo.refresh() + + ## Test + + - `ValueError` is raised. + - Exception message matches expectations. + + ## Setup - Data + + - None + + ## Setup - Code + + - `MaintenanceModeInfo()` is instantiated + - Required attributes are set + - `FabricDetails()` is mocked to conditionally raise `TypeError`. + - `SwitchDetails()` is mocked to conditionally raise `TypeError`. + + ## Trigger + + - `refresh()` is called. + + ## Expected Result + + - `ValueError` is raised. + - Exception message matches expectations. + """ method_name = inspect.stack()[0][3] key = f"{method_name}a" @@ -318,8 +318,8 @@ def responses(): mock_switch_details.mock_message = mock_message mock_switch_details.mock_property = mock_property - monkeypatch.setattr(instance, "fabric_details", mock_fabric_details) - monkeypatch.setattr(instance, "switch_details", mock_switch_details) + monkeypatch.setattr(instance, "_fabric_details", mock_fabric_details) + monkeypatch.setattr(instance, "_switch_details", mock_switch_details) with does_not_raise(): instance.config = CONFIG @@ -351,31 +351,42 @@ def test_maintenance_mode_info_00210( mock_message, ) -> None: """ - ### Classes and Methods - - MaintenanceModeInfo() - - refresh() - - ### Summary - - Verify ``refresh()`` raises ``ValueError`` when - ``switch_details.serial_number`` raises ``ValueError``. - - ### Setup - Data - - ``responses_SwitchDetails.json``: - - RETURN_CODE: 200 - - MESSAGE: OK - - ### Setup - Code - - ``MaintenanceModeInfo()`` is instantiated - - Required attributes are set - - ``SwitchDetails()`` is mocked to conditionally raise - ``ValueError`` in the ``serial_number.getter`` property. - - ### Trigger - - ``refresh()`` is called. - - ### Expected Result - - ``ValueError`` is raised. - - Exception message matches expectations. + # Summary + + Verify `refresh()` raises `ValueError` when + `switch_details.serial_number` raises `ValueError`. + + ## Classes and Methods + + - MaintenanceModeInfo.refresh() + + ## Test + + - `ValueError` is raised. + - Exception message matches expectations. + + ## Setup - Data + + - `responses_SwitchDetails.json`: + - RETURN_CODE: 200 + - MESSAGE: OK + + ## Setup - Code + + - `MaintenanceModeInfo()` is instantiated + - Required attributes are set + - `SwitchDetails()` is mocked to conditionally raise + `ValueError` in the `serial_number.getter` property. + + ## Trigger + + - `refresh()` is called. + + ## Expected Result + + - `ValueError` is raised. + - Exception message matches expectations. + """ method_name = inspect.stack()[0][3] key = f"{method_name}a" @@ -398,7 +409,7 @@ def responses(): mock_switch_details.mock_message = mock_message mock_switch_details.mock_property = mock_property - monkeypatch.setattr(instance, "switch_details", mock_switch_details) + monkeypatch.setattr(instance, "_switch_details", mock_switch_details) with does_not_raise(): instance.config = CONFIG @@ -411,43 +422,54 @@ def responses(): def test_maintenance_mode_info_00300() -> None: """ - ### Classes and Methods - - MaintenanceModeInfo() - - __init__() - - refresh() + # Summary - ### Summary - Verify ``refresh()`` raises ``ValueError`` when - ``switch_details._get()`` raises ``ValueError``. + Verify `refresh()` raises `ValueError` when + `switch_details._get()` raises `ValueError`. This happens when the switch is not found in the response from the controller. - ### Setup - Data - - ``ipAddress`` is set to something other than 192.168.1.2 - - ``responses_SwitchDetails.json``: - - "DATA[0].fabricName: VXLAN_Fabric", - - "DATA[0].freezeMode: null", - - "DATA[0].ipAddress: 192.168.1.1", - - "DATA[0].mode: Normal", - - "DATA[0].serialNumber: FDO211218FV", - - "DATA[0].switchRole: leaf", - - "DATA[0].systemMode: Normal" - - RETURN_CODE: 200 - - MESSAGE: OK - - ``responses_FabricDetailsByName.json``: - - RETURN_CODE: 200 - - MESSAGE: OK - - ### Setup - Code - - ``MaintenanceModeInfo()`` is instantiated - - Required attributes are set - - ### Trigger - - ``refresh()`` is called. - - ### Expected Result - - ``ValueError`` is raised. - - Exception message matches expectations. + ## Classes and Methods + + - MaintenanceModeInfo.__init__() + - MaintenanceModeInfo.refresh() + + ## Test + + - `ValueError` is raised. + - Exception message matches expectations. + + ## Setup - Data + + - `ipAddress` is set to something other than 192.168.1.2 + - `responses_SwitchDetails.json`: + - "DATA[0].fabricName: VXLAN_Fabric", + - "DATA[0].freezeMode: null", + - "DATA[0].ipAddress: 192.168.1.1", + - "DATA[0].mode: Normal", + - "DATA[0].serialNumber: FDO211218FV", + - "DATA[0].switchRole: leaf", + - "DATA[0].systemMode: Normal" + - RETURN_CODE: 200 + - MESSAGE: OK + - `responses_FabricDetailsByName.json`: + - RETURN_CODE: 200 + - MESSAGE: OK + + ## Setup - Code + + - `MaintenanceModeInfo()` is instantiated + - Required attributes are set + + ## Trigger + + - `refresh()` is called. + + ## Expected Result + + - `ValueError` is raised. + - Exception message matches expectations. + """ method_name = inspect.stack()[0][3] key = f"{method_name}a" @@ -477,45 +499,56 @@ def responses(): def test_maintenance_mode_info_00310() -> None: """ - ### Classes and Methods - - MaintenanceModeInfo() - - __init__() - - refresh() + # Summary - ### Summary - Verify ``refresh()`` raises ``ValueError`` when - ``switch_details.serial_number`` is ``None``. + Verify `refresh()` raises `ValueError` when + `switch_details.serial_number` is `None`. This happens when the switch exists on the controller but its serial_number is null. This is a negative test case since we expect the serial_number to be set. - ### Setup - Data - - ``ipAddress`` is set to something other than 192.168.1.2 - - ``responses_SwitchDetails.json``: - - "DATA[0].fabricName: VXLAN_Fabric", - - "DATA[0].freezeMode: null", - - "DATA[0].ipAddress: 192.168.1.2", - - "DATA[0].mode: Normal", - - "DATA[0].serialNumber: null", - - "DATA[0].switchRole: leaf", - - "DATA[0].systemMode: Normal" - - RETURN_CODE: 200 - - MESSAGE: OK - - ``responses_FabricDetailsByName.json``: - - RETURN_CODE: 200 - - MESSAGE: OK - - ### Setup - Code - - ``MaintenanceModeInfo()`` is instantiated - - Required attributes are set - - ### Trigger - - ``refresh()`` is called. - - ### Expected Result - - ``ValueError`` is raised. - - Exception message matches expectations. + ## Classes and Methods + + - MaintenanceModeInfo.__init__() + - MaintenanceModeInfo.refresh() + + ## Test + + - `ValueError` is raised. + - Exception message matches expectations. + + ## Setup - Data + + - `ipAddress` is set to something other than 192.168.1.2 + - `responses_SwitchDetails.json`: + - "DATA[0].fabricName: VXLAN_Fabric", + - "DATA[0].freezeMode: null", + - "DATA[0].ipAddress: 192.168.1.2", + - "DATA[0].mode: Normal", + - "DATA[0].serialNumber: null", + - "DATA[0].switchRole: leaf", + - "DATA[0].systemMode: Normal" + - RETURN_CODE: 200 + - MESSAGE: OK + - `responses_FabricDetailsByName.json`: + - RETURN_CODE: 200 + - MESSAGE: OK + + ## Setup - Code + + - `MaintenanceModeInfo()` is instantiated + - Required attributes are set + + ## Trigger + + - `refresh()` is called. + + ## Expected Result + + - `ValueError` is raised. + - Exception message matches expectations. + """ method_name = inspect.stack()[0][3] key = f"{method_name}a" @@ -565,38 +598,49 @@ def test_maintenance_mode_info_00400( mock_message, ) -> None: """ - ### Classes and Methods - - MaintenanceModeInfo() - - refresh() - - ### Summary - - Verify ``refresh()`` raises ``ValueError`` when - ``fabric_details.filter`` raises ``ValueError``. - - ### Setup - Data - - ``responses_SwitchDetails.json``: - - "DATA[0].fabricName: VXLAN_Fabric", - - "DATA[0].freezeMode: null", - - "DATA[0].ipAddress: 192.168.1.2", - - "DATA[0].mode: Normal", - - "DATA[0].serialNumber: FDO211218FV", - - "DATA[0].switchRole: leaf", - - "DATA[0].systemMode: Normal" - - RETURN_CODE: 200 - - MESSAGE: OK - - ### Code Flow - Setup - - ``MaintenanceModeInfo()`` is instantiated. - - Required attributes are set. - - ``FabricDetailsByName().filter`` is mocked to conditionally raise - ``ValueError``. - - ### Trigger - - ``refresh()`` is called. - - ### Expected Result - - ``ValueError`` is raised. - - Exception message matches expectations. + # Summary + + Verify `refresh()` raises `ValueError` when + `fabric_details.filter` raises `ValueError`. + + ## Classes and Methods + + - MaintenanceModeInfo.refresh() + + ## Test + + - `ValueError` is raised. + - Exception message matches expectations. + + ## Setup - Data + + - `responses_SwitchDetails.json`: + - "DATA[0].fabricName: VXLAN_Fabric", + - "DATA[0].freezeMode: null", + - "DATA[0].ipAddress: 192.168.1.2", + - "DATA[0].mode: Normal", + - "DATA[0].serialNumber: FDO211218FV", + - "DATA[0].switchRole: leaf", + - "DATA[0].systemMode: Normal" + - RETURN_CODE: 200 + - MESSAGE: OK + + ## Setup - Code + + - `MaintenanceModeInfo()` is instantiated. + - Required attributes are set. + - `FabricDetailsByName().filter` is mocked to conditionally raise + `ValueError`. + + ## Trigger + + - `refresh()` is called. + + ## Expected Result + + - `ValueError` is raised. + - Exception message matches expectations. + """ method_name = inspect.stack()[0][3] key = f"{method_name}a" @@ -619,7 +663,7 @@ def responses(): mock_fabric_details.mock_message = mock_message mock_fabric_details.mock_property = mock_property - monkeypatch.setattr(instance, "fabric_details", mock_fabric_details) + monkeypatch.setattr(instance, "_fabric_details", mock_fabric_details) with does_not_raise(): instance.config = CONFIG @@ -632,39 +676,49 @@ def responses(): def test_maintenance_mode_info_00500() -> None: """ - ### Classes and Methods - - MaintenanceModeInfo() - - refresh() - - ### Summary - - Verify when ``freezeMode`` == null in the response, - ``freezeMode`` is set to False. - - ### Setup - Data - - ``responses_SwitchDetails.json``: - - "DATA[0].fabricName: VXLAN_Fabric", - - "DATA[0].freezeMode: null", - - "DATA[0].ipAddress: 192.168.1.2", - - "DATA[0].mode: Normal", - - "DATA[0].serialNumber: FDO211218FV", - - "DATA[0].switchRole: leaf", - - "DATA[0].systemMode: Normal" - - RETURN_CODE: 200 - - MESSAGE: OK - - ``responses_FabricDetailsByName.json``: - - RETURN_CODE: 200 - - MESSAGE: OK - - ### Setup - Code - - ``MaintenanceModeInfo()`` is instantiated - - Required attributes are set - - ### Trigger - - ``refresh()`` is called. - - ### Expected Result - - Exception is not raised. - - ``MaintenanceModeInfo().results`` contains expected data. + # Summary + + Verify when `freezeMode` == null in the response, + `freezeMode` is set to False. + + ## Classes and Methods + + - MaintenanceModeInfo.refresh() + + ## Test + + - Exception is not raised. + - `MaintenanceModeInfo().results` contains expected data. + + ## Setup - Data + + - `responses_SwitchDetails.json`: + - "DATA[0].fabricName: VXLAN_Fabric", + - "DATA[0].freezeMode: null", + - "DATA[0].ipAddress: 192.168.1.2", + - "DATA[0].mode: Normal", + - "DATA[0].serialNumber: FDO211218FV", + - "DATA[0].switchRole: leaf", + - "DATA[0].systemMode: Normal" + - RETURN_CODE: 200 + - MESSAGE: OK + - `responses_FabricDetailsByName.json`: + - RETURN_CODE: 200 + - MESSAGE: OK + + ## Setup - Code + + - `MaintenanceModeInfo()` is instantiated + - Required attributes are set + + ## Trigger + + - `refresh()` is called. + + ## Expected Result + + - Exception is not raised. + - `MaintenanceModeInfo().results` contains expected data. """ method_name = inspect.stack()[0][3] @@ -697,40 +751,50 @@ def responses(): def test_maintenance_mode_info_00510() -> None: """ - ### Classes and Methods - - MaintenanceModeInfo() - - __init__() - - refresh() - - ### Summary - - Verify happy path with: - - switch_details: freezeMode is True - - ### Setup - Data - - ``responses_SwitchDetails.json``: - - "DATA[0].fabricName: VXLAN_Fabric", - - "DATA[0].freezeMode: true", - - "DATA[0].ipAddress: 192.168.1.2", - - "DATA[0].mode: Normal", - - "DATA[0].serialNumber: FDO211218FV", - - "DATA[0].switchRole: leaf", - - "DATA[0].systemMode: Normal" - - RETURN_CODE: 200 - - MESSAGE: OK - - ``responses_FabricDetailsByName.json``: - - RETURN_CODE: 200 - - MESSAGE: OK - - ### Setup - Code - - ``MaintenanceModeInfo()`` is instantiated - - Required attributes are set - - ### Trigger - - ``refresh()`` is called. - - ### Expected Result - - Exception is not raised. - - ``MaintenanceModeInfo().results`` contains expected data. + # Summary + + Verify happy path with: + - switch_details: freezeMode is True + + ## Classes and Methods + + - MaintenanceModeInfo.__init__() + - MaintenanceModeInfo.refresh() + + ## Test + + - Exception is not raised. + - `MaintenanceModeInfo().results` contains expected data. + + ## Setup - Data + + - `responses_SwitchDetails.json`: + - "DATA[0].fabricName: VXLAN_Fabric", + - "DATA[0].freezeMode: true", + - "DATA[0].ipAddress: 192.168.1.2", + - "DATA[0].mode: Normal", + - "DATA[0].serialNumber: FDO211218FV", + - "DATA[0].switchRole: leaf", + - "DATA[0].systemMode: Normal" + - RETURN_CODE: 200 + - MESSAGE: OK + - `responses_FabricDetailsByName.json`: + - RETURN_CODE: 200 + - MESSAGE: OK + + ## Setup - Code + + - `MaintenanceModeInfo()` is instantiated + - Required attributes are set + + ## Trigger + + - `refresh()` is called. + + ## Expected Result + + - Exception is not raised. + - `MaintenanceModeInfo().results` contains expected data. """ method_name = inspect.stack()[0][3] @@ -763,41 +827,52 @@ def responses(): def test_maintenance_mode_info_00520() -> None: """ - ### Classes and Methods - - MaintenanceModeInfo() - - __init__() - - refresh() - - ### Summary - - Verify: - - ``mode`` == "inconsistent" when ``mode`` != ``systemMode``. - - ### Setup - Data - - ``responses_SwitchDetails.json``: - - DATA[0].fabricName: VXLAN_Fabric - - DATA[0].freezeMode: true - - DATA[0].ipAddress: 192.168.1.2 - - DATA[0].mode: Normal - - DATA[0].serialNumber: FDO211218FV - - DATA[0].switchRole: leaf - - DATA[0].systemMode: Maintenance - - RETURN_CODE: 200 - - MESSAGE: OK - - ``responses_FabricDetailsByName.json``: - - RETURN_CODE: 200 - - MESSAGE: OK - - ### Setup - Code - - ``MaintenanceModeInfo()`` is instantiated - - Required attributes are set - - ### Trigger - - ``refresh()`` is called. - - ### Expected Result - - Conditions in Summary are confirmed. - - Exception is not raised. - - ``MaintenanceModeInfo().results`` contains expected data. + # Summary + + Verify: + - `mode` == "inconsistent" when `mode` != `systemMode`. + + ## Classes and Methods + + - MaintenanceModeInfo.__init__() + - MaintenanceModeInfo.refresh() + + ## Test + + - Conditions in Summary are confirmed. + - Exception is not raised. + - `MaintenanceModeInfo().results` contains expected data. + + ## Setup - Data + + - `responses_SwitchDetails.json`: + - DATA[0].fabricName: VXLAN_Fabric + - DATA[0].freezeMode: true + - DATA[0].ipAddress: 192.168.1.2 + - DATA[0].mode: Normal + - DATA[0].serialNumber: FDO211218FV + - DATA[0].switchRole: leaf + - DATA[0].systemMode: Maintenance + - RETURN_CODE: 200 + - MESSAGE: OK + - `responses_FabricDetailsByName.json`: + - RETURN_CODE: 200 + - MESSAGE: OK + + ## Setup - Code + + - `MaintenanceModeInfo()` is instantiated + - Required attributes are set + + ## Trigger + + - `refresh()` is called. + + ## Expected Result + + - Conditions in Summary are confirmed. + - Exception is not raised. + - `MaintenanceModeInfo().results` contains expected data. """ method_name = inspect.stack()[0][3] @@ -821,6 +896,8 @@ def responses(): instance.refresh() instance.filter = CONFIG[0] assert instance.mode == "inconsistent" + assert instance.results.response is not None + assert instance.results.result is not None assert instance.results.response[0]["DATA"][0]["mode"] == "Normal" assert instance.results.response[0]["DATA"][0]["systemMode"] == "Maintenance" assert instance.results.result[0]["success"] is True @@ -831,45 +908,55 @@ def responses(): def test_maintenance_mode_info_00600() -> None: """ - ### Classes and Methods - - MaintenanceModeInfo() - - refresh() - - FabricDetailsByName() - - refresh() - - ### Summary - - Verify: - - ``fabric_read_only`` is set to True when ``IS_READ_ONLY`` - is true in the controller response (FabricDetailsByName). - - ### Setup - Data - - ``responses_SwitchDetails.json``: - - DATA[0].fabricName: LAN_Classic - - DATA[0].freezeMode: null - - DATA[0].ipAddress: 192.168.1.2 - - DATA[0].mode: Normal - - DATA[0].serialNumber: FDO211218FV - - DATA[0].switchRole: leaf - - DATA[0].systemMode: Normal - - RETURN_CODE: 200 - - MESSAGE: OK - - ``responses_FabricDetailsByName.json``: - - DATA[0].nvPairs.FABRIC_NAME: LAN_Classic - - DATA[0].nvPairs.IS_READ_ONLY: true - - RETURN_CODE: 200 - - MESSAGE: OK - - ### Setup - Code - - ``MaintenanceModeInfo()`` is instantiated - - Required attributes are set - - ### Trigger - - ``refresh()`` is called. - - ### Expected Result - - Conditions in Summary are confirmed. - - Exception is not raised. - - ``MaintenanceModeInfo().results`` contains expected data. + # Summary + + Verify: + - `fabric_read_only` is set to True when `IS_READ_ONLY` + is true in the controller response (FabricDetailsByName). + + ## Classes and Methods + + - MaintenanceModeInfo.refresh() + - FabricDetailsByName.refresh() + + ## Test + + - Conditions in Summary are confirmed. + - Exception is not raised. + - `MaintenanceModeInfo().results` contains expected data. + + ## Setup - Data + + - `responses_SwitchDetails.json`: + - DATA[0].fabricName: LAN_Classic + - DATA[0].freezeMode: null + - DATA[0].ipAddress: 192.168.1.2 + - DATA[0].mode: Normal + - DATA[0].serialNumber: FDO211218FV + - DATA[0].switchRole: leaf + - DATA[0].systemMode: Normal + - RETURN_CODE: 200 + - MESSAGE: OK + - `responses_FabricDetailsByName.json`: + - DATA[0].nvPairs.FABRIC_NAME: LAN_Classic + - DATA[0].nvPairs.IS_READ_ONLY: true + - RETURN_CODE: 200 + - MESSAGE: OK + + ## Setup - Code + + - `MaintenanceModeInfo()` is instantiated + - Required attributes are set + + ## Trigger + + - `refresh()` is called. + + ## Expected Result + + - Conditions in Summary are confirmed. + - Exception is not raised. + - `MaintenanceModeInfo().results` contains expected data. """ method_name = inspect.stack()[0][3] @@ -892,6 +979,7 @@ def responses(): instance.results = Results() instance.refresh() instance.filter = CONFIG[0] + assert instance.results.result is not None assert instance.fabric_read_only is True assert instance.results.result[0]["success"] is True assert instance.results.result[1]["success"] is True @@ -901,50 +989,61 @@ def responses(): def test_maintenance_mode_info_00700() -> None: """ - ### Classes and Methods - - MaintenanceModeInfo() - - refresh() - - SwitchDetails() - - refresh() - - FabricDetailsByName() - - refresh() - - ### Summary - - Verify: - - ``role`` is set to "na" when ``switchRole`` is null in the - controller response. - - ### Setup - Data - - ``responses_SwitchDetails.json``: - - DATA[0].fabricName: LAN_Classic - - DATA[0].freezeMode: null - - DATA[0].ipAddress: 192.168.1.2 - - DATA[0].mode: Normal - - DATA[0].serialNumber: FDO211218FV - - DATA[0].switchRole: null - - DATA[0].systemMode: Normal - - RETURN_CODE: 200 - - MESSAGE: OK - - ``responses_FabricDetailsByName.json``: - - RETURN_CODE: 200 - - MESSAGE: OK - - ### Setup - Code - - ``MaintenanceModeInfo()`` is instantiated - - Required attributes are set - - ### Trigger - - ``refresh()`` is called. - - ### Expected Result - - Conditions in Summary are confirmed. - - Exception is not raised. - - ``MaintenanceModeInfo().results`` contains expected data. - - ### NOTES - - ``SwitchDetails().role`` is an alias of ``SwitchDetails().switch_role``. - - ``MaintenanceModeInfo().role`` is set based on the value of - ``SwitchDetails().role``. + # Summary + + Verify: + - `role` is set to "na" when `switchRole` is null in the + controller response. + + ## Classes and Methods + + - MaintenanceModeInfo.refresh() + - SwitchDetails.refresh() + - FabricDetailsByName.refresh() + + ## Test + + - Conditions in Summary are confirmed. + - Exception is not raised. + - `MaintenanceModeInfo().results` contains expected data. + + ## Setup - Data + + - `responses_SwitchDetails.json`: + - DATA[0].fabricName: LAN_Classic + - DATA[0].freezeMode: null + - DATA[0].ipAddress: 192.168.1.2 + - DATA[0].mode: Normal + - DATA[0].serialNumber: FDO211218FV + - DATA[0].switchRole: null + - DATA[0].systemMode: Normal + - RETURN_CODE: 200 + - MESSAGE: OK + - `responses_FabricDetailsByName.json`: + - RETURN_CODE: 200 + - MESSAGE: OK + + ## Setup - Code + + - `MaintenanceModeInfo()` is instantiated + - Required attributes are set + + ## Trigger + + - `refresh()` is called. + + ## Expected Result + + - Conditions in Summary are confirmed. + - Exception is not raised. + - `MaintenanceModeInfo().results` contains expected data. + + ## Notes + + - `SwitchDetails().role` is an alias of `SwitchDetails().switch_role`. + - `MaintenanceModeInfo().role` is set based on the value of + `SwitchDetails().role`. + """ method_name = inspect.stack()[0][3] key = f"{method_name}a" @@ -967,6 +1066,7 @@ def responses(): instance.refresh() instance.filter = CONFIG[0] assert instance.role == "na" + assert instance.results.result is not None assert instance.results.result[0]["success"] is True assert instance.results.result[1]["success"] is True assert instance.results.result[0]["found"] is True @@ -975,30 +1075,38 @@ def responses(): def test_maintenance_mode_info_00800() -> None: """ - ### Classes and Methods - - MaintenanceModeInfo() - - refresh() - - SwitchDetails() - - refresh() - - FabricDetailsByName() - - refresh() - - ### Summary - - Verify: - - _get() raises ``ValueError`` if ``filter`` is not set. - - ### Setup - Data + # Summary + + Verify: + - _get() raises `ValueError` if `filter` is not set. + + ## Classes and Methods + + - MaintenanceModeInfo.refresh() + - SwitchDetails.refresh() + - FabricDetailsByName.refresh() + + ## Test + + - Conditions in Summary are confirmed. + + ## Setup - Data + None - ### Setup - Code - - ``MaintenanceModeInfo()`` is instantiated + ## Setup - Code + + - `MaintenanceModeInfo()` is instantiated + + ## Trigger - ### Trigger - - ``MaintenanceModeInfo().role`` is accessed without setting - ``filter``. + - `MaintenanceModeInfo().role` is accessed without setting + `filter`. + + ## Expected Result + + - Conditions in Summary are confirmed. - ### Expected Result - - Conditions in Summary are confirmed. """ with does_not_raise(): instance = MaintenanceModeInfo(PARAMS) @@ -1012,49 +1120,56 @@ def test_maintenance_mode_info_00800() -> None: def test_maintenance_mode_info_00810() -> None: """ - ### Classes and Methods - - MaintenanceModeInfo() - - refresh() - - SwitchDetails() - - refresh() - - FabricDetailsByName() - - refresh() - - ### Summary - - Verify: - - ``_get()`` raises ``ValueError`` if ``filter`` (switch IP) - is not found in the controller response when the user accesses - a property. - - ### Setup - Data - - ``CONFIG``: ["192.168.1.2"] - - ``responses_SwitchDetails.json``: - - DATA[0].fabricName: LAN_Classic - - DATA[0].freezeMode: null - - DATA[0].ipAddress: 192.168.1.2 - - DATA[0].mode: Normal - - DATA[0].serialNumber: FDO211218FV - - DATA[0].switchRole: null - - DATA[0].systemMode: Normal - - RETURN_CODE: 200 - - MESSAGE: OK - - ``responses_FabricDetailsByName.json``: - - DATA[0].nvPairs.FABRIC_NAME: VXLAN_Fabric - - RETURN_CODE: 200 - - MESSAGE: OK - - ### Setup - Code - - ``MaintenanceModeInfo()`` is instantiated - - Required attributes are set - - ``refresh()`` is called. - - ``filter`` is set to 1.2.3.4 - - - ### Trigger - - ``serial_number`` is accessed - - ### Expected Result - - Conditions in Summary are confirmed. + # Summary + + Verify: + - `_get()` raises `ValueError` if `filter` (switch IP) + is not found in the controller response when the user accesses + a property. + + ## Classes and Methods + + - MaintenanceModeInfo.refresh() + - SwitchDetails.refresh() + - FabricDetailsByName.refresh() + + ## Test + + - Conditions in Summary are confirmed. + + ## Setup - Data + + - `CONFIG`: ["192.168.1.2"] + - `responses_SwitchDetails.json`: + - DATA[0].fabricName: LAN_Classic + - DATA[0].freezeMode: null + - DATA[0].ipAddress: 192.168.1.2 + - DATA[0].mode: Normal + - DATA[0].serialNumber: FDO211218FV + - DATA[0].switchRole: null + - DATA[0].systemMode: Normal + - RETURN_CODE: 200 + - MESSAGE: OK + - `responses_FabricDetailsByName.json`: + - DATA[0].nvPairs.FABRIC_NAME: VXLAN_Fabric + - RETURN_CODE: 200 + - MESSAGE: OK + + ## Setup - Code + + - `MaintenanceModeInfo()` is instantiated + - Required attributes are set + - `refresh()` is called. + - `filter` is set to 1.2.3.4 + + ## Trigger + + - `serial_number` is accessed + + ## Expected Result + + - Conditions in Summary are confirmed. + """ method_name = inspect.stack()[0][3] key = f"{method_name}a" @@ -1084,48 +1199,55 @@ def responses(): def test_maintenance_mode_info_00820() -> None: """ - ### Classes and Methods - - MaintenanceModeInfo() - - refresh() - - SwitchDetails() - - refresh() - - FabricDetailsByName() - - refresh() - - ### Summary - - Verify: - - ``refresh`` re-raises ``ValueError`` raised by - ``SwitchDetails()._get()`` when ``item`` is not found in the - controller response. In this, case ``item`` is ``freezeMode``. - - ### Setup - Data - - ``CONFIG``: ["192.168.1.2"] - - ``responses_SwitchDetails.json`` is missing the key ``freezeMode``. - - ``responses_SwitchDetails.json``: - - DATA[0].fabricName: LAN_Classic - - DATA[0].ipAddress: 192.168.1.2 - - DATA[0].mode: Normal - - DATA[0].serialNumber: FDO211218FV - - DATA[0].switchRole: null - - DATA[0].systemMode: Normal - - RETURN_CODE: 200 - - MESSAGE: OK - - ``responses_FabricDetailsByName.json``: - - DATA[0].nvPairs.FABRIC_NAME: VXLAN_Fabric - - DATA[0].nvPairs.IS_READ_ONLY: false - - RETURN_CODE: 200 - - MESSAGE: OK - - ### Setup - Code - - ``MaintenanceModeInfo()`` is instantiated - - Required attributes are set - - - ### Trigger - - ``refresh()`` is called. - - ### Expected Result - - Conditions in Summary are confirmed. + # Summary + + Verify: + - `refresh` re-raises `ValueError` raised by + `SwitchDetails()._get()` when `item` is not found in the + controller response. In this, case `item` is `freezeMode`. + + ## Classes and Methods + + - MaintenanceModeInfo.refresh() + - SwitchDetails.refresh() + - FabricDetailsByName.refresh() + + ## Test + + - Conditions in Summary are confirmed. + + ## Setup - Data + + - `CONFIG`: ["192.168.1.2"] + - `responses_SwitchDetails.json` is missing the key `freezeMode`. + - `responses_SwitchDetails.json`: + - DATA[0].fabricName: LAN_Classic + - DATA[0].ipAddress: 192.168.1.2 + - DATA[0].mode: Normal + - DATA[0].serialNumber: FDO211218FV + - DATA[0].switchRole: null + - DATA[0].systemMode: Normal + - RETURN_CODE: 200 + - MESSAGE: OK + - `responses_FabricDetailsByName.json`: + - DATA[0].nvPairs.FABRIC_NAME: VXLAN_Fabric + - DATA[0].nvPairs.IS_READ_ONLY: false + - RETURN_CODE: 200 + - MESSAGE: OK + + ## Setup - Code + + - `MaintenanceModeInfo()` is instantiated + - Required attributes are set + + ## Trigger + + - `refresh()` is called. + + ## Expected Result + + - Conditions in Summary are confirmed. + """ method_name = inspect.stack()[0][3] key = f"{method_name}a" @@ -1157,26 +1279,36 @@ def responses(): def test_maintenance_mode_info_00900() -> None: """ - ### Classes and Methods - - ``MaintenanceModeInfo()`` - - ``config.setter`` + # Summary + + Verify: + - `config` raises `TypeError` when set to an invalid type. + + ## Classes and Methods - ### Summary - - Verify: - - ``config`` raises ``TypeError`` when set to an invalid type. + - MaintenanceModeInfo.config setter + + ## Test + + - Conditions in Summary are confirmed. + + ## Setup - Data - ### Setup - Data None - ### Setup - Code - - ``MaintenanceModeInfo()`` is instantiated - - Required attributes are set + ## Setup - Code + + - `MaintenanceModeInfo()` is instantiated + - Required attributes are set - ### Trigger - - ``config`` is set to a value that is not a ``list``. + ## Trigger + + - `config` is set to a value that is not a `list`. + + ## Expected Result + + - Conditions in Summary are confirmed. - ### Expected Result - - Conditions in Summary are confirmed. """ with does_not_raise(): instance = MaintenanceModeInfo(PARAMS) @@ -1185,32 +1317,42 @@ def test_maintenance_mode_info_00900() -> None: match += r"MaintenanceModeInfo\.config must be a list\.\s+" match += r"Got type: str\." with pytest.raises(TypeError, match=match): - instance.config = "NOT_A_LIST" + instance.config = "NOT_A_LIST" # type: ignore[arg-type] def test_maintenance_mode_info_00910() -> None: """ - ### Classes and Methods - - ``MaintenanceModeInfo()`` - - ``config.setter`` + # Summary + + Verify: + - `config` raises `TypeError` when an element in the list is + not a `str`. - ### Summary - - Verify: - - ``config`` raises ``TypeError`` when an element in the list is - not a ``str``. + ## Classes and Methods + + - MaintenanceModeInfo.config setter + + ## Test + + - Conditions in Summary are confirmed. + + ## Setup - Data - ### Setup - Data None - ### Setup - Code - - ``MaintenanceModeInfo()`` is instantiated - - Required attributes are set + ## Setup - Code + + - `MaintenanceModeInfo()` is instantiated + - Required attributes are set + + ## Trigger + + - `config` is set to a value that is not a `list`. + + ## Expected Result - ### Trigger - - ``config`` is set to a value that is not a ``list``. + - Conditions in Summary are confirmed. - ### Expected Result - - Conditions in Summary are confirmed. """ with does_not_raise(): instance = MaintenanceModeInfo(PARAMS) @@ -1221,32 +1363,42 @@ def test_maintenance_mode_info_00910() -> None: match += r"value contains element of type int.\s+" match += r"value:.*\." with pytest.raises(TypeError, match=match): - instance.config = ["192.168.1.1", 10, "192.168.1.2"] + instance.config = ["192.168.1.1", 10, "192.168.1.2"] # type: ignore[list-item] def test_maintenance_mode_info_01000() -> None: """ - ### Classes and Methods - - ``MaintenanceModeInfo()`` - - ``info.getter`` + # Summary - ### Summary - - Verify: - - ``info`` raises ``ValueError`` when accessed before - ``refresh()`` is called. + Verify: + - `info` raises `ValueError` when accessed before + `refresh()` is called. + + ## Classes and Methods + + - MaintenanceModeInfo.info getter + + ## Test + + - Conditions in Summary are confirmed. + + ## Setup - Data - ### Setup - Data None - ### Setup - Code - - ``MaintenanceModeInfo()`` is instantiated - - Required attributes are set + ## Setup - Code + + - `MaintenanceModeInfo()` is instantiated + - Required attributes are set + + ## Trigger + + - `info` is accessed without having first called `refresh()`. - ### Trigger - - ``info`` is accessed without having first called ``refresh()``. + ## Expected Result + + - Conditions in Summary are confirmed. - ### Expected Result - - Conditions in Summary are confirmed. """ with does_not_raise(): instance = MaintenanceModeInfo(PARAMS) @@ -1260,41 +1412,51 @@ def test_maintenance_mode_info_01000() -> None: def test_maintenance_mode_info_01010() -> None: """ - ### Classes and Methods - - ``MaintenanceModeInfo()`` - - ``info.getter`` - - ### Summary - - Verify: - - ``info`` returns expected information in the happy path. - - ### Setup - Data - - ``CONFIG``: ["192.168.1.2"] - - ``responses_SwitchDetails.json``: - - DATA[0].fabricName: VXLAN_Fabric - - DATA[0].freezeMode: null - - DATA[0].ipAddress: 192.168.1.2 - - DATA[0].mode: Normal - - DATA[0].serialNumber: FDO211218FV - - DATA[0].switchRole: leaf - - DATA[0].systemMode: Maintenance - - RETURN_CODE: 200 - - MESSAGE: OK - - ``responses_FabricDetailsByName.json``: - - DATA[0].nvPairs.FABRIC_NAME: VXLAN_Fabric - - DATA[0].nvPairs.IS_READ_ONLY: false - - RETURN_CODE: 200 - - MESSAGE: OK - - ### Setup - Code - - ``MaintenanceModeInfo()`` is instantiated - - Required attributes are set - - ### Trigger - - ``info`` is accessed without having first called ``refresh()``. - - ### Expected Result - - Conditions in Summary are confirmed. + # Summary + + Verify: + - `info` returns expected information in the happy path. + + ## Classes and Methods + + - MaintenanceModeInfo.info getter + + ## Test + + - Conditions in Summary are confirmed. + + ## Setup - Data + + - `CONFIG`: ["192.168.1.2"] + - `responses_SwitchDetails.json`: + - DATA[0].fabricName: VXLAN_Fabric + - DATA[0].freezeMode: null + - DATA[0].ipAddress: 192.168.1.2 + - DATA[0].mode: Normal + - DATA[0].serialNumber: FDO211218FV + - DATA[0].switchRole: leaf + - DATA[0].systemMode: Maintenance + - RETURN_CODE: 200 + - MESSAGE: OK + - `responses_FabricDetailsByName.json`: + - DATA[0].nvPairs.FABRIC_NAME: VXLAN_Fabric + - DATA[0].nvPairs.IS_READ_ONLY: false + - RETURN_CODE: 200 + - MESSAGE: OK + + ## Setup - Code + + - `MaintenanceModeInfo()` is instantiated + - Required attributes are set + + ## Trigger + + - `info` is accessed without having first called `refresh()`. + + ## Expected Result + + - Conditions in Summary are confirmed. + """ method_name = inspect.stack()[0][3] key = f"{method_name}a" @@ -1327,26 +1489,36 @@ def responses(): def test_maintenance_mode_info_01020() -> None: """ - ### Classes and Methods - - ``MaintenanceModeInfo()`` - - ``info.setter`` + # Summary + + Verify: + - `info` raises `TypeError` when set to an invalid type. - ### Summary - - Verify: - - ``info`` raises ``TypeError`` when set to an invalid type. + ## Classes and Methods + + - MaintenanceModeInfo.info setter + + ## Test + + - Conditions in Summary are confirmed. + + ## Setup - Data - ### Setup - Data None - ### Setup - Code - - ``MaintenanceModeInfo()`` is instantiated - - Required attributes are set + ## Setup - Code + + - `MaintenanceModeInfo()` is instantiated + - Required attributes are set + + ## Trigger + + - `info` is set to a value that is not a `dict`. + + ## Expected Result - ### Trigger - - ``info`` is set to a value that is not a ``dict``. + - Conditions in Summary are confirmed. - ### Expected Result - - Conditions in Summary are confirmed. """ with does_not_raise(): instance = MaintenanceModeInfo(PARAMS) @@ -1355,4 +1527,4 @@ def test_maintenance_mode_info_01020() -> None: match += r"value must be a dict\.\s+" match += r"Got value NOT_A_DICT of type str\." with pytest.raises(TypeError, match=match): - instance.info = "NOT_A_DICT" + instance.info = "NOT_A_DICT" # type: ignore[assignment] diff --git a/tests/unit/modules/dcnm/dcnm_maintenance_mode/fixtures/configs_Want.json b/tests/unit/modules/dcnm/dcnm_maintenance_mode/fixtures/configs_Want.json index b4e233696..2b8d7a6a4 100644 --- a/tests/unit/modules/dcnm/dcnm_maintenance_mode/fixtures/configs_Want.json +++ b/tests/unit/modules/dcnm/dcnm_maintenance_mode/fixtures/configs_Want.json @@ -17,19 +17,6 @@ } ] }, - "test_dcnm_maintenance_mode_want_00110a": { - "deploy": true, - "mode": "normal", - "wait_for_mode_change": true, - "switches": [ - { - "ip_address": "192.168.1.2" - }, - { - "ip_address": "192.168.1.3" - } - ] - }, "test_dcnm_maintenance_mode_want_00120a": { "deploy": true, "mode": "normal", @@ -43,19 +30,6 @@ } ] }, - "test_dcnm_maintenance_mode_want_00121a": { - "deploy": true, - "mode": "normal", - "wait_for_mode_change": true, - "switches": [ - { - "ip_address": "192.168.1.2" - }, - { - "ip_address": "192.168.1.3" - } - ] - }, "test_dcnm_maintenance_mode_want_00130a": { "deploy": true, "mode": "normal", diff --git a/tests/unit/modules/dcnm/dcnm_maintenance_mode/test_dcnm_maintenance_mode_common.py b/tests/unit/modules/dcnm/dcnm_maintenance_mode/test_dcnm_maintenance_mode_common.py index 734cc5826..c277c4894 100644 --- a/tests/unit/modules/dcnm/dcnm_maintenance_mode/test_dcnm_maintenance_mode_common.py +++ b/tests/unit/modules/dcnm/dcnm_maintenance_mode/test_dcnm_maintenance_mode_common.py @@ -1,3 +1,7 @@ +""" +Unit tests for dcnm_maintenance_mode Common class +""" + # Copyright (c) 2024 Cisco and/or its affiliates. # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -16,11 +20,11 @@ # 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=unused-import,protected-access from __future__ import absolute_import, division, print_function -__metaclass__ = type +__metaclass__ = type # pylint: disable=invalid-name __copyright__ = "Copyright (c) 2024 Cisco and/or its affiliates." __author__ = "Allen Robel" @@ -29,12 +33,9 @@ import inspect import pytest -from ansible_collections.cisco.dcnm.plugins.modules.dcnm_maintenance_mode import \ - Common -from ansible_collections.cisco.dcnm.tests.unit.module_utils.common.common_utils import \ - ResponseGenerator -from ansible_collections.cisco.dcnm.tests.unit.modules.dcnm.dcnm_maintenance_mode.utils import ( - common_fixture, configs_common, does_not_raise, params) +from ansible_collections.cisco.dcnm.plugins.modules.dcnm_maintenance_mode import Common +from ansible_collections.cisco.dcnm.tests.unit.module_utils.common.common_utils import ResponseGenerator +from ansible_collections.cisco.dcnm.tests.unit.modules.dcnm.dcnm_maintenance_mode.utils import common_fixture, configs_common, does_not_raise, params def test_dcnm_maintenance_mode_common_00000(common) -> None: @@ -54,7 +55,7 @@ def test_dcnm_maintenance_mode_common_00000(common) -> None: instance = common assert instance.class_name == "Common" assert instance.state == "merged" - assert instance.check_mode is False + assert instance._check_mode is False assert instance.have == {} assert instance.payloads == {} assert instance.query == [] @@ -66,30 +67,42 @@ def test_dcnm_maintenance_mode_common_00000(common) -> None: def test_dcnm_maintenance_mode_common_00010() -> None: """ - ### Classes and Methods + # Summary + + Verify check_mode is False if not set in params. + + ## Setup - Code + + - `check_mode` is removed from params to simulate missing `check_mode` key/value. + + ## Expected Results + + - check_mode is False if not set in params. + + ## Classes and Methods + - Common - __init__() - ### Summary - - Verify ``ValueError`` is raised. - - params is missing ``check_mode`` key/value. """ params_test = copy.deepcopy(params) params_test.pop("check_mode", None) - match = r"Common\.__init__: check_mode is required" - with pytest.raises(ValueError, match=match): + with pytest.raises(ValueError, match=r"Common\.__init__: check_mode is required."): Common(params_test) def test_dcnm_maintenance_mode_common_00020() -> None: """ - ### Classes and Methods - - Common - - __init__() + # Summary - ### Summary - Verify ``ValueError`` is raised. - params is missing ``state`` key/value. + + ## Classes and Methods + + - Common + - __init__() + """ params_test = copy.deepcopy(params) params_test.pop("state", None) @@ -100,13 +113,14 @@ def test_dcnm_maintenance_mode_common_00020() -> None: def test_dcnm_maintenance_mode_common_00030() -> None: """ - ### Classes and Methods - - Common - - __init__() + # Summary - ### Summary - Verify ``ValueError`` is raised. - params is missing ``config`` key/value. + + ## Classes and Methods + - Common + - __init__() """ params_test = copy.deepcopy(params) params_test.pop("config", None) @@ -117,13 +131,15 @@ def test_dcnm_maintenance_mode_common_00030() -> None: def test_dcnm_maintenance_mode_common_00040() -> None: """ - ### Classes and Methods - - Common - - __init__() + # Summary - ### Summary - Verify ``TypeError`` is raised. - config is not a dict. + + ## Classes and Methods + + - Common + - __init__() """ params_test = copy.deepcopy(params) params_test.update({"config": 10}) @@ -134,13 +150,14 @@ def test_dcnm_maintenance_mode_common_00040() -> None: def test_dcnm_maintenance_mode_common_00100() -> None: """ - ### Classes and Methods - - Common - - get_want() + # Summary - ### Summary - Verify Common().get_want() builds expected want contents. - All switches inherit top-level config. + + ## Classes and Methods + - Common + - get_want() """ method_name = inspect.stack()[0][3] key = f"{method_name}a" @@ -168,14 +185,16 @@ def configs(): def test_dcnm_maintenance_mode_common_00110() -> None: """ - ### Classes and Methods - - Common - - get_want() + # Summary - ### Summary - Verify Common().get_want() builds expected want contents. - 192.168.1.2 inherits top-level config. - 192.168.1.3 overrides top-level config. + + ## Classes and Methods + + - Common + - get_want() """ method_name = inspect.stack()[0][3] key = f"{method_name}a" @@ -203,15 +222,18 @@ def configs(): def test_dcnm_maintenance_mode_common_00120() -> None: """ - ### Classes and Methods - - Common - - get_want() + # Summary - ### Summary - Verify Common().get_want() builds expected want contents. - top-level config is missing. - 192.168.1.2 uses switch-level config. - 192.168.1.3 uses switch-level config. + + ## Classes and Methods + + - Common + - get_want() + """ method_name = inspect.stack()[0][3] key = f"{method_name}a" @@ -239,11 +261,8 @@ def configs(): def test_dcnm_maintenance_mode_common_00130() -> None: """ - ### Classes and Methods - - Common - - get_want() + # Summary - ### Summary - Verify Common().get_want() builds expected want contents. - 192.168.1.2 missing all optional parameters, so default values are provided. @@ -251,6 +270,12 @@ def test_dcnm_maintenance_mode_common_00130() -> None: - mode default value is "normal". - wait_for_mode_change default value is False. - 192.168.1.3 uses switch-level config. + + ## Classes and Methods + + - Common + - get_want() + """ method_name = inspect.stack()[0][3] key = f"{method_name}a" @@ -278,13 +303,15 @@ def configs(): def test_dcnm_maintenance_mode_common_00140() -> None: """ - ### Classes and Methods - - Common - - get_want() + # Summary - ### Summary - Verify ``ValueError`` is raised. - switch is missing mandatory parameter ip_address + + ## Classes and Methods + + - Common + - get_want() """ method_name = inspect.stack()[0][3] key = f"{method_name}a" @@ -307,13 +334,16 @@ def configs(): def test_dcnm_maintenance_mode_common_00150() -> None: """ - ### Classes and Methods - - Common - - get_want() + # Summary - ### Summary - Verify ``ValueError`` is raised. - 192.168.1.2 contains invalid choice for mode + + ## Classes and Methods + + - Common + - get_want() + """ method_name = inspect.stack()[0][3] key = f"{method_name}a" @@ -337,13 +367,15 @@ def configs(): def test_dcnm_maintenance_mode_common_00160() -> None: """ - ### Classes and Methods - - Common - - get_want() + # Summary - ### Summary - Verify ``ValueError`` is raised. - 192.168.1.2 contains non-boolean value for deploy + + ## Classes and Methods + + - Common + - get_want() """ method_name = inspect.stack()[0][3] key = f"{method_name}a" @@ -367,13 +399,14 @@ def configs(): def test_dcnm_maintenance_mode_common_00170() -> None: """ - ### Classes and Methods - - Common - - get_want() + # Summary - ### Summary - Verify ``ValueError`` is raised. - 192.168.1.2 contains non-boolean value for wait_for_mode_change + + ## Classes and Methods + - Common + - get_want() """ method_name = inspect.stack()[0][3] key = f"{method_name}a" @@ -397,13 +430,15 @@ def configs(): def test_dcnm_maintenance_mode_common_00180() -> None: """ - ### Classes and Methods - - Common - - get_want() + # Summary - ### Summary - Verify ``ValueError`` is raised. - params contains invalid value for ``state`` + + ## Classes and Methods + + - Common + - get_want() """ method_name = inspect.stack()[0][3] key = f"{method_name}a" diff --git a/tests/unit/modules/dcnm/dcnm_maintenance_mode/test_dcnm_maintenance_mode_merged.py b/tests/unit/modules/dcnm/dcnm_maintenance_mode/test_dcnm_maintenance_mode_merged.py index c4b43a40c..bcf4eb788 100644 --- a/tests/unit/modules/dcnm/dcnm_maintenance_mode/test_dcnm_maintenance_mode_merged.py +++ b/tests/unit/modules/dcnm/dcnm_maintenance_mode/test_dcnm_maintenance_mode_merged.py @@ -1,4 +1,8 @@ -# Copyright (c) 2024 Cisco and/or its affiliates. +""" +Unit tests for dcnm_maintenance_mode Merged class. +""" + +# 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,45 +25,48 @@ from __future__ import absolute_import, division, print_function -__metaclass__ = type +__metaclass__ = type # pylint: disable=invalid-name -__copyright__ = "Copyright (c) 2024 Cisco and/or its affiliates." +__copyright__ = "Copyright (c) 2024-2025 Cisco and/or its affiliates." __author__ = "Allen Robel" import copy 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.sender_file import \ - Sender -from ansible_collections.cisco.dcnm.plugins.modules.dcnm_maintenance_mode import \ - Merged -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.sender_file import Sender +from ansible_collections.cisco.dcnm.plugins.modules.dcnm_maintenance_mode import Merged +from ansible_collections.cisco.dcnm.tests.unit.module_utils.common.common_utils import ResponseGenerator from ansible_collections.cisco.dcnm.tests.unit.modules.dcnm.dcnm_maintenance_mode.utils import ( - MockAnsibleModule, configs_merged, does_not_raise, params, - responses_ep_all_switches, responses_ep_fabrics, + MockAnsibleModule, + configs_merged, + does_not_raise, + params, + responses_ep_all_switches, + responses_ep_fabrics, responses_ep_maintenance_mode_deploy, responses_ep_maintenance_mode_disable, - responses_ep_maintenance_mode_enable) + responses_ep_maintenance_mode_enable, +) def test_dcnm_maintenance_mode_merged_00000() -> None: """ - ### Classes and Methods - - Common - - __init__() + # Summary - ### Summary - - Verify the class attributes are initialized to expected values. + Verify the class attributes are initialized to expected values. + + ## Test - ### Test - Class attributes are initialized to expected values. - Exception is not raised. + + ## Classes and Methods + + - Common + - `__init__()` """ with does_not_raise(): instance = Merged(params) @@ -68,7 +75,7 @@ def test_dcnm_maintenance_mode_merged_00000() -> None: assert instance.class_name == "Merged" assert instance.log.name == "dcnm.Merged" - assert instance.check_mode is False + assert instance._check_mode is False assert instance.state == "merged" assert isinstance(instance.config, dict) @@ -83,7 +90,7 @@ def test_dcnm_maintenance_mode_merged_00000() -> None: assert instance.maintenance_mode.class_name == "MaintenanceMode" assert instance.maintenance_mode.state == "merged" - assert instance.maintenance_mode.check_mode is False + assert instance.maintenance_mode._check_mode is False assert instance.results.class_name == "Results" assert instance.results.state == "merged" @@ -92,15 +99,18 @@ def test_dcnm_maintenance_mode_merged_00000() -> None: def test_dcnm_maintenance_mode_merged_00100() -> None: """ - ### Classes and Methods - - Merged() - - commit() + # Summary + + Verify `commit()` happy path. - ### Summary - - Verify ``commit()`` happy path. - - Change switch mode from maintenance to normal. - - No exceptions are raised. - - want contains expected structure and values. + - Change switch mode from maintenance to normal. + - No exceptions are raised. + - want contains expected structure and values. + + ## Classes and Methods + + - Merged() + - `commit()` """ method_name = inspect.stack()[0][3] key = f"{method_name}" @@ -188,15 +198,18 @@ def responses(): def test_dcnm_maintenance_mode_merged_00110() -> None: """ - ### Classes and Methods - - Merged() - - commit() + # Summary + + Verify `commit()` happy path. + + - Change switch mode from normal to maintenance. + - No exceptions are raised. + - want contains expected structure and values. + + ## Classes and Methods - ### Summary - - Verify ``commit()`` happy path. - - Change switch mode from normal to maintenance. - - No exceptions are raised. - - want contains expected structure and values. + - Merged() + - `commit()` """ method_name = inspect.stack()[0][3] key = f"{method_name}" @@ -284,17 +297,20 @@ def responses(): def test_dcnm_maintenance_mode_merged_00115() -> None: """ - ### Classes and Methods + # Summary + + Verify `commit()` happy path. + + - User wants to change switches to maintenance mode, but all + switches are already in maintenance mode. + - `send_need()` returns without sending any requests since + `instance.need` is empty. + - No exceptions are raised. + + ## Classes and Methods + - Merged() - - commit() - - ### Summary - - Verify ``commit()`` happy path. - - User wants to change switches to maintenance mode, but all - switches are already in maintenance mode. - - send_need() returns without sending any requests since - instance.need is empty. - - No exceptions are raised. + - `commit()` """ method_name = inspect.stack()[0][3] key = f"{method_name}" @@ -346,14 +362,16 @@ def responses(): def test_dcnm_maintenance_mode_merged_00120() -> None: """ - ### Classes and Methods - - Merged() - - get_need() - - commit() + # Summary + + Verify `get_have()` raises `ValueError` when `ip_address` + does not exist on the controller. + + ## Classes and Methods - ### Summary - - Verify ``get_have()`` raises ``ValueError`` when ip_address - does not exist on the controller. + - Merged() + - `get_need()` + - `commit()` """ method_name = inspect.stack()[0][3] key = f"{method_name}" @@ -414,14 +432,16 @@ def responses(): def test_dcnm_maintenance_mode_merged_00130() -> None: """ - ### Classes and Methods - - Merged() - - fabric_deployment_disabled() - - commit() + # Summary + + Verify `fabric_deployment_disabled()` raises `ValueError` when + have `ip_address` is in migration mode. + + ## Classes and Methods - ### Summary - - Verify ``fabric_deployment_disabled()`` raises ``ValueError`` when - have ip_address is in migration mode. + - Merged() + - `fabric_deployment_disabled()` + - `commit()` """ method_name = inspect.stack()[0][3] key = f"{method_name}" @@ -495,14 +515,16 @@ def responses(): def test_dcnm_maintenance_mode_merged_00140() -> None: """ - ### Classes and Methods - - Merged() - - fabric_deployment_disabled() - - commit() + # Summary + + Verify `fabric_deployment_disabled()` raises `ValueError` when + the fabric is in read-only mode. - ### Summary - - Verify ``fabric_deployment_disabled()`` raises ``ValueError`` when - the fabric is in read-only mode. + ## Classes and Methods + + - Merged() + - `fabric_deployment_disabled()` + - `commit()` """ method_name = inspect.stack()[0][3] key = f"{method_name}" @@ -568,14 +590,16 @@ def responses(): def test_dcnm_maintenance_mode_merged_00150() -> None: """ - ### Classes and Methods - - Merged() - - fabric_deployment_disabled() - - commit() + # Summary + + Verify `fabric_deployment_disabled()` raises `ValueError` when + fabric freeze-mode is True. - ### Summary - - Verify ``fabric_deployment_disabled()`` raises ``ValueError`` when - fabric freeze-mode is True. + ## Classes and Methods + + - Merged() + - `fabric_deployment_disabled()` + - `commit()` """ method_name = inspect.stack()[0][3] key = f"{method_name}" @@ -606,9 +630,7 @@ def responses(): instance.rest_send = rest_send instance.config = params_test.get("config") match = r"Merged\.fabric_deployment_disabled:\s+" - match += ( - r"The hosting fabric is in 'Deployment Disable' state for the switch with\s+" - ) + match += r"The hosting fabric is in 'Deployment Disable' state for the switch with\s+" match += r"ip_address 192\.168\.1\.2,\s+" match += r"serial_number FD2222222GA\.\s+" match += r"Review the 'Deployment Enable / Deployment Disable' setting on the controller at:\s+" @@ -643,13 +665,15 @@ def responses(): def test_dcnm_maintenance_mode_merged_00200() -> None: """ - ### Classes and Methods - - Merged() - - commit() + # Summary + + Verify `commit()` raises `ValueError` when `rest_send` has not + been set. - ### Summary - - Verify ``commit()`` raises ``ValueError`` when rest_send has not - been set. + ## Classes and Methods + + - Merged() + - `commit()` """ with does_not_raise(): instance = Merged(params) @@ -666,14 +690,16 @@ def test_dcnm_maintenance_mode_merged_00200() -> None: def test_dcnm_maintenance_mode_merged_00300(monkeypatch) -> None: """ - ### Classes and Methods - - Merged() - - get_need() - - commit() + # Summary + + Verify `get_need()` raises `ValueError` when `ip_address` + does not exist in `self.have`. - ### Summary - - Verify ``get_need()`` raises ``ValueError`` when ip_address - does not exist in self.have. + ## Classes and Methods + + - Merged() + - `get_need()` + - `commit()` """ method_name = inspect.stack()[0][3] key = f"{method_name}" @@ -720,14 +746,16 @@ def mock_get_have(): def test_dcnm_maintenance_mode_merged_00400(monkeypatch) -> None: """ - ### Classes and Methods - - Merged() - - get_want() - - commit() + # Summary - ### Summary - - Verify ``commit`` re-raises ``ValueError`` when ``get_want()`` - raises ``ValueError``. + Verify `commit()` re-raises `ValueError` when `get_want()` + raises `ValueError`. + + ## Classes and Methods + + - Merged() + - `get_want()` + - `commit()` """ params_test = copy.deepcopy(params) params_test.update({"config": {}}) @@ -755,13 +783,15 @@ def mock_get_want(): def test_dcnm_maintenance_mode_merged_00500() -> None: """ - ### Classes and Methods - - Merged() - - __init__() + # Summary - ### Summary - - Verify ``__init__`` re-raises ``ValueError`` when ``Common().__init__`` - raises ``ValueError``. + Verify `__init__()` re-raises `ValueError` when `Common().__init__()` + raises `ValueError`. + + ## Classes and Methods + + - Merged() + - `__init__()` """ params_test = copy.deepcopy(params) params_test.update({"config": {}}) @@ -777,14 +807,16 @@ def test_dcnm_maintenance_mode_merged_00500() -> None: def test_dcnm_maintenance_mode_merged_00600(monkeypatch) -> None: """ - ### Classes and Methods - - Merged() - - send_need() - - commit() + # Summary - ### Summary - - Verify ``commit()`` re-raises ``ValueError`` when - send_need() raises ``ValueError``. + Verify `commit()` re-raises `ValueError` when + `send_need()` raises `ValueError`. + + ## Classes and Methods + + - Merged() + - `send_need()` + - `commit()` """ method_name = inspect.stack()[0][3] key = f"{method_name}" @@ -845,14 +877,16 @@ def mock_send_need(): def test_dcnm_maintenance_mode_merged_00700(monkeypatch) -> None: """ - ### Classes and Methods - - Merged() - - send_need() - - commit() + # Summary - ### Summary - - Verify ``send_need()`` re-raises ``ValueError`` when - MaintenanceMode.commit() raises ``ValueError``. + Verify `send_need()` re-raises `ValueError` when + `MaintenanceMode.commit()` raises `ValueError`. + + ## Classes and Methods + + - Merged() + - `send_need()` + - `commit()` """ method_name = inspect.stack()[0][3] key = f"{method_name}" @@ -902,9 +936,7 @@ def commit(self): match += r"Error detail:\s+" match += r"MockMaintenanceModeInfo\.refresh: Mocked ValueError\." with pytest.raises(ValueError, match=match): - monkeypatch.setattr( - instance, "maintenance_mode", MockMaintenanceMode(params_test) - ) + monkeypatch.setattr(instance, "maintenance_mode", MockMaintenanceMode(params_test)) instance.commit() assert len(instance.results.diff) == 2 diff --git a/tests/unit/modules/dcnm/dcnm_maintenance_mode/test_dcnm_maintenance_mode_params_spec.py b/tests/unit/modules/dcnm/dcnm_maintenance_mode/test_dcnm_maintenance_mode_params_spec.py index 9b80c1456..04b94444a 100644 --- a/tests/unit/modules/dcnm/dcnm_maintenance_mode/test_dcnm_maintenance_mode_params_spec.py +++ b/tests/unit/modules/dcnm/dcnm_maintenance_mode/test_dcnm_maintenance_mode_params_spec.py @@ -1,4 +1,8 @@ -# Copyright (c) 2024 Cisco and/or its affiliates. +""" +Unit tests for dcnm_maintenance_mode ParamsSpec class. +""" + +# 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. @@ -24,50 +28,54 @@ from __future__ import absolute_import, division, print_function -__metaclass__ = type +__metaclass__ = type # pylint: disable=invalid-name -__copyright__ = "Copyright (c) 2024 Cisco and/or its affiliates." +__copyright__ = "Copyright (c) 2024-2025 Cisco and/or its affiliates." __author__ = "Allen Robel" import copy import pytest -from ansible_collections.cisco.dcnm.plugins.modules.dcnm_maintenance_mode import \ - ParamsSpec -from ansible_collections.cisco.dcnm.tests.unit.modules.dcnm.dcnm_maintenance_mode.utils import ( - does_not_raise, params) +from ansible_collections.cisco.dcnm.plugins.modules.dcnm_maintenance_mode import ParamsSpec +from ansible_collections.cisco.dcnm.tests.unit.modules.dcnm.dcnm_maintenance_mode.utils import does_not_raise, params def test_dcnm_maintenance_mode_params_spec_00000() -> None: """ - ### Classes and Methods - - ParamsSpec - - __init__() + # Summary + + Verify the class attributes are initialized to expected values. - ### Summary - - Verify the class attributes are initialized to expected values. + ## Test - ### Test - Class attributes are initialized to expected values - - ``ValueError`` is not called + - `ValueError` is not called + + ## Classes and Methods + + - ParamsSpec + - `__init__()` """ with does_not_raise(): instance = ParamsSpec() assert instance.class_name == "ParamsSpec" - assert instance._params is None + assert instance._params == {} assert instance._params_spec == {} - assert instance.valid_states == ["merged", "query"] + assert instance._valid_states == ["merged", "query"] def test_dcnm_maintenance_mode_params_spec_00100() -> None: """ - ### Classes and Methods - - ParamsSpec - - params.setter + # Summary + + Verify `TypeError` is raised. + + - `params` is not a dict. - ### Summary - - Verify ``TypeError`` is raised. - - params is not a dict. + ## Classes and Methods + + - ParamsSpec + - `params.setter` """ params_test = "foo" @@ -77,18 +85,21 @@ def test_dcnm_maintenance_mode_params_spec_00100() -> None: match = r"ParamsSpec\.params.setter:\s+" match += r"Invalid type\. Expected dict but got type str, value foo\." with pytest.raises(TypeError, match=match): - instance.params = params_test + instance.params = params_test # type: ignore def test_dcnm_maintenance_mode_params_spec_00110() -> None: """ - ### Classes and Methods - - ParamsSpec - - params.setter + # Summary + + Verify `ValueError` is raised. - ### Summary - - Verify ``ValueError`` is raised. - - params is missing ``state`` key/value. + - `params` is missing `state` key/value. + + ## Classes and Methods + + - ParamsSpec + - `params.setter` """ params_test = copy.deepcopy(params) params_test.pop("state", None) @@ -104,13 +115,16 @@ def test_dcnm_maintenance_mode_params_spec_00110() -> None: def test_dcnm_maintenance_mode_params_spec_00120() -> None: """ - ### Classes and Methods - - ParamsSpec - - params.setter + # Summary - ### Summary - - Verify ``ValueError`` is raised. - - params ``state`` has invalid value. + Verify `ValueError` is raised. + + - `params` `state` has invalid value. + + ## Classes and Methods + + - ParamsSpec + - `params.setter` """ params_test = copy.deepcopy(params) params_test.update({"state": "foo"}) @@ -126,13 +140,15 @@ def test_dcnm_maintenance_mode_params_spec_00120() -> None: def test_dcnm_maintenance_mode_params_spec_00200() -> None: """ - ### Classes and Methods - - ParamsSpec - - params.setter - - commit() + # Summary - ### Summary - - Verify commit() happy path for merged state. + Verify `commit()` happy path for merged state. + + ## Classes and Methods + + - ParamsSpec + - `params.setter` + - `commit()` """ params_test = copy.deepcopy(params) @@ -162,13 +178,15 @@ def test_dcnm_maintenance_mode_params_spec_00200() -> None: def test_dcnm_maintenance_mode_params_spec_00210() -> None: """ - ### Classes and Methods - - ParamsSpec - - params.setter - - commit() + # Summary - ### Summary - - Verify commit() happy path for query state. + Verify `commit()` happy path for query state. + + ## Classes and Methods + + - ParamsSpec + - `params.setter` + - `commit()` """ params_test = copy.deepcopy(params) params_test.update({"state": "query"}) @@ -186,15 +204,18 @@ def test_dcnm_maintenance_mode_params_spec_00210() -> None: def test_dcnm_maintenance_mode_params_spec_00220() -> None: """ - ### Classes and Methods - - ParamsSpec - - params.setter - - commit() + # Summary - ### Summary - - Verify commit() sad path. - - params is not set before calling commit. - - commit() raises ``ValueError`` when params is not set. + Verify `commit()` sad path. + + - `params` is not set before calling `commit()`. + - `commit()` raises `ValueError` when `params` is not set. + + ## Classes and Methods + + - ParamsSpec + - `params.setter` + - `commit()` """ params_test = copy.deepcopy(params) params_test.update({"state": "query"}) diff --git a/tests/unit/modules/dcnm/dcnm_maintenance_mode/test_dcnm_maintenance_mode_query.py b/tests/unit/modules/dcnm/dcnm_maintenance_mode/test_dcnm_maintenance_mode_query.py index 934912b82..cf28c1012 100644 --- a/tests/unit/modules/dcnm/dcnm_maintenance_mode/test_dcnm_maintenance_mode_query.py +++ b/tests/unit/modules/dcnm/dcnm_maintenance_mode/test_dcnm_maintenance_mode_query.py @@ -1,4 +1,8 @@ -# Copyright (c) 2024 Cisco and/or its affiliates. +""" +Unit tests for dcnm_maintenance_mode Query class. +""" + +# 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,42 +25,45 @@ from __future__ import absolute_import, division, print_function -__metaclass__ = type +__metaclass__ = type # pylint: disable=invalid-name -__copyright__ = "Copyright (c) 2024 Cisco and/or its affiliates." +__copyright__ = "Copyright (c) 2024-2025 Cisco and/or its affiliates." __author__ = "Allen Robel" import copy 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.sender_file import \ - Sender -from ansible_collections.cisco.dcnm.plugins.modules.dcnm_maintenance_mode import \ - Query -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.sender_file import Sender +from ansible_collections.cisco.dcnm.plugins.modules.dcnm_maintenance_mode import Query +from ansible_collections.cisco.dcnm.tests.unit.module_utils.common.common_utils import ResponseGenerator from ansible_collections.cisco.dcnm.tests.unit.modules.dcnm.dcnm_maintenance_mode.utils import ( - MockAnsibleModule, configs_query, does_not_raise, params_query, - responses_ep_all_switches, responses_ep_fabrics) + MockAnsibleModule, + configs_query, + does_not_raise, + params_query, + responses_ep_all_switches, + responses_ep_fabrics, +) def test_dcnm_maintenance_mode_query_00000() -> None: """ - ### Classes and Methods - - Common - - __init__() + # Summary - ### Summary - - Verify the class attributes are initialized to expected values. + Verify the class attributes are initialized to expected values. + + ## Test - ### Test - Class attributes are initialized to expected values. - Exception is not raised. + + ## Classes and Methods + + - Common + - `__init__()` """ with does_not_raise(): instance = Query(params_query) @@ -65,7 +72,7 @@ def test_dcnm_maintenance_mode_query_00000() -> None: assert instance.class_name == "Query" assert instance.log.name == "dcnm.Query" - assert instance.check_mode is False + assert instance._check_mode is False assert instance.state == "query" assert isinstance(instance.config, dict) @@ -83,14 +90,17 @@ def test_dcnm_maintenance_mode_query_00000() -> None: def test_dcnm_maintenance_mode_query_00100() -> None: """ - ### Classes and Methods - - Query() - - commit() + # Summary + + Verify `commit()` happy path. - ### Summary - - Verify ``commit()`` happy path. - - No exceptions are raised. - - want contains expected structure and values. + - No exceptions are raised. + - want contains expected structure and values. + + ## Classes and Methods + + - Query() + - `commit()` """ method_name = inspect.stack()[0][3] key = f"{method_name}" @@ -175,13 +185,15 @@ def responses(): def test_dcnm_maintenance_mode_query_00200() -> None: """ - ### Classes and Methods - - Query() - - commit() + # Summary - ### Summary - - Verify ``commit()`` raises ``ValueError`` when rest_send has not - been set. + Verify `commit()` raises `ValueError` when `rest_send` has not + been set. + + ## Classes and Methods + + - Query() + - `commit()` """ with does_not_raise(): instance = Query(params_query) @@ -198,14 +210,16 @@ def test_dcnm_maintenance_mode_query_00200() -> None: def test_dcnm_maintenance_mode_query_00300(monkeypatch) -> None: """ - ### Classes and Methods - - Query() - - get_need() - - commit() + # Summary - ### Summary - - Verify ``get_need()`` raises ``ValueError`` when ip_address - does not exist in self.have. + Verify `get_need()` raises `ValueError` when `ip_address` + does not exist in `self.have`. + + ## Classes and Methods + + - Query() + - `get_need()` + - `commit()` """ method_name = inspect.stack()[0][3] key = f"{method_name}" @@ -253,14 +267,15 @@ def mock_get_have(): def test_dcnm_maintenance_mode_query_00400(monkeypatch) -> None: """ - ### Classes and Methods - - Merged() - - get_want() - - commit() - - ### Summary - - Verify ``commit`` re-raises ``ValueError`` when ``get_want()`` - raises ``ValueError``. + # Summary + + Verify `commit()` re-raises `ValueError` when `get_want()` raises `ValueError`. + + ## Classes and Methods + + - Query() + - `get_want()` + - `commit()` """ params_test = copy.deepcopy(params_query) params_test.update({"config": {}}) @@ -288,13 +303,15 @@ def mock_get_want(): def test_dcnm_maintenance_mode_query_00500() -> None: """ - ### Classes and Methods - - Query() - - __init__() + # Summary - ### Summary - - Verify ``__init__`` re-raises ``ValueError`` when ``Common().__init__`` - raises ``ValueError``. + Verify `__init__()` re-raises `ValueError` when `Common().__init__()` + raises `ValueError`. + + ## Classes and Methods + + - Query() + - `__init__()` """ params_test = copy.deepcopy(params_query) params_test.update({"config": {}}) @@ -310,13 +327,15 @@ def test_dcnm_maintenance_mode_query_00500() -> None: def test_dcnm_maintenance_mode_query_00600(monkeypatch) -> None: """ - ### Classes and Methods - - Query() - - commit() + # Summary - ### Summary - - Verify ``commit`` re-raises ``ValueError`` when ``get_have()`` - raises ``ValueError``. + Verify `commit()` re-raises `ValueError` when `get_have()` + raises `ValueError`. + + ## Classes and Methods + + - Query() + - `commit()` """ method_name = inspect.stack()[0][3] key = f"{method_name}" @@ -350,6 +369,7 @@ class MockMaintenanceModeInfo: # pylint: disable=too-few-public-methods """ Mocked MaintenanceModeInfo class. """ + def __init__(self, *args): pass @@ -367,7 +387,5 @@ def refresh(self): match += r"Error detail: MockMaintenanceModeInfo\.refresh:\s+" match += r"Mocked ValueError\." with pytest.raises(ValueError, match=match): - monkeypatch.setattr( - instance, "maintenance_mode_info", MockMaintenanceModeInfo() - ) + monkeypatch.setattr(instance, "maintenance_mode_info", MockMaintenanceModeInfo()) instance.commit() diff --git a/tests/unit/modules/dcnm/dcnm_maintenance_mode/test_dcnm_maintenance_mode_want.py b/tests/unit/modules/dcnm/dcnm_maintenance_mode/test_dcnm_maintenance_mode_want.py index 31d79f753..f1300bbc0 100644 --- a/tests/unit/modules/dcnm/dcnm_maintenance_mode/test_dcnm_maintenance_mode_want.py +++ b/tests/unit/modules/dcnm/dcnm_maintenance_mode/test_dcnm_maintenance_mode_want.py @@ -1,4 +1,8 @@ -# Copyright (c) 2024 Cisco and/or its affiliates. +""" +Unit tests for dcnm_maintenance_mode Want class. +""" + +# 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,46 +25,45 @@ from __future__ import absolute_import, division, print_function -__metaclass__ = type +__metaclass__ = type # pylint: disable=invalid-name -__copyright__ = "Copyright (c) 2024 Cisco and/or its affiliates." +__copyright__ = "Copyright (c) 2024-2025 Cisco and/or its affiliates." __author__ = "Allen Robel" import copy import inspect import pytest -from ansible_collections.cisco.dcnm.plugins.modules.dcnm_maintenance_mode import ( - ParamsSpec, Want) -from ansible_collections.cisco.dcnm.tests.unit.module_utils.common.common_utils import \ - ResponseGenerator -from ansible_collections.cisco.dcnm.tests.unit.module_utils.common.test_params_validate_v2 import \ - ParamsValidate -from ansible_collections.cisco.dcnm.tests.unit.modules.dcnm.dcnm_maintenance_mode.utils import ( - configs_want, does_not_raise, params) +from ansible_collections.cisco.dcnm.plugins.modules.dcnm_maintenance_mode import ParamsSpec, Want +from ansible_collections.cisco.dcnm.tests.unit.module_utils.common.common_utils import ResponseGenerator +from ansible_collections.cisco.dcnm.tests.unit.module_utils.common.test_params_validate_v2 import ParamsValidate +from ansible_collections.cisco.dcnm.tests.unit.modules.dcnm.dcnm_maintenance_mode.utils import configs_want, does_not_raise, params def test_dcnm_maintenance_mode_want_00000() -> None: """ - ### Classes and Methods - - Common - - __init__() + # Summary + + Verify the class attributes are initialized to expected values. - ### Summary - - Verify the class attributes are initialized to expected values. + ## Test - ### Test - Class attributes are initialized to expected values - - ``ValueError`` is not called + - `ValueError` is not called + + ## Classes and Methods + + - Common + - `__init__()` """ with does_not_raise(): instance = Want() assert instance.class_name == "Want" - assert instance._config is None - assert instance._items_key is None - assert instance._params is None - assert instance._params_spec is None - assert instance._validator is None + assert instance._config == {} + assert instance._items_key == "" + assert instance._params == {} + assert instance._params_spec.class_name == "ParamsSpec" + assert instance._validator.class_name == "ParamsValidate" assert instance._want == [] assert instance.merged_configs == [] assert instance.item_configs == [] @@ -68,14 +71,17 @@ def test_dcnm_maintenance_mode_want_00000() -> None: def test_dcnm_maintenance_mode_want_00100() -> None: """ - ### Classes and Methods - - Want() - - commit() + # Summary + + Verify `commit()` happy path. + + - No exceptions are raised. + - want contains expected structure and values. - ### Summary - - Verify ``commit()`` happy path. - - No exceptions are raised. - - want contains expected structure and values. + ## Classes and Methods + + - Want() + - `commit()` """ method_name = inspect.stack()[0][3] key = f"{method_name}a" @@ -93,8 +99,6 @@ def configs(): instance.items_key = "switches" instance.config = params_test.get("config") instance.params = params_test - instance.params_spec = ParamsSpec() - instance.validator = ParamsValidate() instance.commit() assert instance.want[0].get("deploy", None) is True assert instance.want[0].get("ip_address", None) == "192.168.1.2" @@ -106,49 +110,19 @@ def configs(): assert instance.want[1].get("wait_for_mode_change", None) is True -def test_dcnm_maintenance_mode_want_00110() -> None: - """ - ### Classes and Methods - - Want() - - commit() - - ### Summary - - Verify ``ValueError`` is raised. - - Want().validator is not set prior to calling commit(). +def test_dcnm_maintenance_mode_want_00120() -> None: """ - method_name = inspect.stack()[0][3] - key = f"{method_name}a" - - def configs(): - yield configs_want(key) + # Summary - gen = ResponseGenerator(configs()) - - params_test = copy.deepcopy(params) - params_test.update({"config": gen.next}) + Verify `Want().commit()` catches and re-raises `ValueError`. - with does_not_raise(): - instance = Want() - instance.items_key = "switches" - instance.config = params_test.get("config") - instance.params = params_test - instance.params_spec = ParamsSpec() - match = r"Want\.commit:\s+" - match += r"self\.validator must be set before calling commit\." - with pytest.raises(ValueError, match=match): - instance.commit() + - `Want().generate_params_spec()` raises `ValueError` because + `params` is not set. + ## Classes and Methods -def test_dcnm_maintenance_mode_want_00120() -> None: - """ - ### Classes and Methods - Want() - - commit() - - ### Summary - - Verify Want().commit() catches and re-raises ``ValueError``. - - Want().generate_params_spec() raises ``ValueError`` because - ``params`` is not set. + - `commit()` """ method_name = inspect.stack()[0][3] key = f"{method_name}a" @@ -176,53 +150,19 @@ def configs(): instance.commit() -def test_dcnm_maintenance_mode_want_00121() -> None: +def test_dcnm_maintenance_mode_want_00130() -> None: """ - ### Classes and Methods - - Want() - - commit() + # Summary - ### Summary - - Verify Want().commit() catches and re-raises ``ValueError``. - - Want().generate_params_spec() raises ``ValueError`` because - ``params_spec`` is not set. - """ - method_name = inspect.stack()[0][3] - key = f"{method_name}a" - - def configs(): - yield configs_want(key) - - gen = ResponseGenerator(configs()) - - params_test = copy.deepcopy(params) - params_test.update({"config": gen.next}) + Verify `Want().commit()` catches and re-raises `ValueError`. - with does_not_raise(): - instance = Want() - instance.items_key = "switches" - instance.config = params_test.get("config") - instance.params = params_test - instance.validator = ParamsValidate() - match = r"Want\.commit:\s+" - match += r"Error generating params_spec\.\s+" - match += r"Error detail:\s+" - match += r"Want\.generate_params_spec\(\):\s+" - match += r"params_spec is not set, and is required\." - with pytest.raises(ValueError, match=match): - instance.commit() + - `Want()._merge_global_and_item_configs()` raises `ValueError` + because `config` is not set. + ## Classes and Methods -def test_dcnm_maintenance_mode_want_00130() -> None: - """ - ### Classes and Methods - Want() - - commit() - - ### Summary - - Verify Want().commit() catches and re-raises ``ValueError``. - - Want()._merge_global_and_item_configs() raises ``ValueError`` - because ``config`` is not set. + - `commit()` """ method_name = inspect.stack()[0][3] key = f"{method_name}a" @@ -252,14 +192,17 @@ def configs(): def test_dcnm_maintenance_mode_want_00131() -> None: """ - ### Classes and Methods - - Want() - - commit() + # Summary + + Verify `Want().commit()` catches and re-raises `ValueError`. - ### Summary - - Verify Want().commit() catches and re-raises ``ValueError``. - - Want()._merge_global_and_item_configs() raises ``ValueError`` - because ``items_key`` is not set. + - `Want()._merge_global_and_item_configs()` raises `ValueError` + because `items_key` is not set. + + ## Classes and Methods + + - Want() + - `commit()` """ method_name = inspect.stack()[0][3] key = f"{method_name}a" @@ -276,8 +219,6 @@ def configs(): instance = Want() instance.config = params_test.get("config") instance.params = params_test - instance.params_spec = ParamsSpec() - instance.validator = ParamsValidate() match = r"Want\.commit:\s+" match += r"Error merging global and item configs\.\s+" match += r"Error detail:\s+" @@ -289,14 +230,17 @@ def configs(): def test_dcnm_maintenance_mode_want_00132() -> None: """ - ### Classes and Methods - - Want() - - commit() + # Summary + + Verify `Want().commit()` catches and re-raises `ValueError`. + + - `Want()._merge_global_and_item_configs()` raises `ValueError` + because `config` is missing the key specified by `items_key`. + + ## Classes and Methods - ### Summary - - Verify Want().commit() catches and re-raises ``ValueError``. - - Want()._merge_global_and_item_configs() raises ``ValueError`` - because ``config`` is missing the key specified by items_key. + - Want() + - `commit()` """ method_name = inspect.stack()[0][3] key = f"{method_name}a" @@ -314,8 +258,6 @@ def configs(): instance.config = params_test.get("config") instance.items_key = "NOT_PRESENT_IN_CONFIG" instance.params = params_test - instance.params_spec = ParamsSpec() - instance.validator = ParamsValidate() match = r"Want\.commit:\s+" match += r"Error merging global and item configs\.\s+" match += r"Error detail:\s+" @@ -327,14 +269,17 @@ def configs(): def test_dcnm_maintenance_mode_want_00133(monkeypatch) -> None: """ - ### Classes and Methods - - Want() - - commit() + # Summary - ### Summary - - Verify Want().commit() catches and re-raises ``ValueError``. - - Want()._merge_global_and_item_configs() raises ``ValueError`` - because MergeDict().commit() raises ``ValueError``. + Verify `Want().commit()` catches and re-raises `ValueError`. + + - `Want()._merge_global_and_item_configs()` raises `ValueError` + because `MergeDict().commit()` raises `ValueError`. + + ## Classes and Methods + + - Want() + - `commit()` """ method_name = inspect.stack()[0][3] key = f"{method_name}a" @@ -355,11 +300,13 @@ class MockMergeDicts: # pylint: disable=too-few-public-methods @staticmethod def commit(): """ - ### Summary - Mock method for MergeDicts().commit(). + # Summary - ### Raises - ValueError: Always + Mock method for `MergeDicts().commit()`. + + ## Raises + + - `ValueError`: Always """ raise ValueError("MergeDicts().commit(). ValueError.") @@ -369,8 +316,6 @@ def commit(): instance.config = params_test.get("config") instance.items_key = "switches" instance.params = params_test - instance.params_spec = ParamsSpec() - instance.validator = ParamsValidate() match = r"Want\.commit: Error merging global and item configs\.\s+" match += r"Error detail:\s+" match += r"Want\._merge_global_and_item_configs:\s+" @@ -382,14 +327,17 @@ def commit(): def test_dcnm_maintenance_mode_want_00140(monkeypatch) -> None: """ - ### Classes and Methods - - Want() - - commit() + # Summary - ### Summary - - Verify Want().commit() catches and re-raises ``ValueError`` - when Want().validate_configs() raises ``ValueError``. - - Want().validate_configs() is mocked to raise ``ValueError``. + Verify `Want().commit()` catches and re-raises `ValueError` + when `Want().validate_configs()` raises `ValueError`. + + - `Want().validate_configs()` is mocked to raise `ValueError`. + + ## Classes and Methods + + - Want() + - `commit()` """ method_name = inspect.stack()[0][3] key = f"{method_name}a" @@ -410,9 +358,7 @@ def mock_def(): monkeypatch.setattr(instance, "validate_configs", mock_def) instance.config = params_test.get("config") instance.params = params_test - instance.params_spec = ParamsSpec() instance.items_key = "switches" - instance.validator = ParamsValidate() match = r"Want\.commit:\s+" match += r"Error validating playbook configs against params spec\.\s+" match += r"Error detail: validate_configs ValueError\." @@ -422,12 +368,14 @@ def mock_def(): def test_dcnm_maintenance_mode_want_00200() -> None: """ - ### Classes and Methods - - Want() - - config.setter + # Summary + + Verify `Want().config` raises `TypeError` when `config` is not a dict. - ### Summary - - Verify Want().config raises ``TypeError`` when config is not a dict. + ## Classes and Methods + + - Want() + - `config.setter` """ with does_not_raise(): instance = Want() @@ -440,13 +388,15 @@ def test_dcnm_maintenance_mode_want_00200() -> None: def test_dcnm_maintenance_mode_want_00300() -> None: """ - ### Classes and Methods - - Want() - - items_key.setter + # Summary + + Verify `Want().items_key` raises `TypeError` when `items_key` is not + a string. - ### Summary - - Verify Want().items_key raises ``TypeError`` when items_key is not - a string. + ## Classes and Methods + + - Want() + - `items_key.setter` """ with does_not_raise(): instance = Want() @@ -454,17 +404,19 @@ def test_dcnm_maintenance_mode_want_00300() -> None: match = r"Want\.items_key\.setter:\s+" match += r"expected string but got set, value {'NOT_A_STRING'}\." with pytest.raises(TypeError, match=match): - instance.items_key = {"NOT_A_STRING"} + instance.items_key = {"NOT_A_STRING"} # type: ignore def test_dcnm_maintenance_mode_want_00400() -> None: """ - ### Classes and Methods - - Want() - - params.setter + # Summary + + Verify `Want().params` happy path. + + ## Classes and Methods - ### Summary - Verify Want().params happy path. + - Want() + - `params.setter` """ with does_not_raise(): instance = Want() @@ -473,12 +425,14 @@ def test_dcnm_maintenance_mode_want_00400() -> None: def test_dcnm_maintenance_mode_want_00410() -> None: """ - ### Classes and Methods - - Want() - - params.setter + # Summary + + Verify `Want().params` raises `TypeError` when `params` is not a dict. + + ## Classes and Methods - ### Summary - - Verify Want().params raises ``TypeError`` when params is not a dict. + - Want() + - `params.setter` """ with does_not_raise(): instance = Want() @@ -486,17 +440,19 @@ def test_dcnm_maintenance_mode_want_00410() -> None: match = r"Want\.params\.setter:\s+" match += r"expected dict but got str, value NOT_A_DICT\." with pytest.raises(TypeError, match=match): - instance.params = "NOT_A_DICT" + instance.params = "NOT_A_DICT" # type: ignore def test_dcnm_maintenance_mode_want_00500() -> None: """ - ### Classes and Methods - - Want() - - params_spec.setter + # Summary + + Verify `Want().params_spec` happy path. + + ## Classes and Methods - ### Summary - Verify Want().params_spec happy path. + - Want() + - `params_spec.setter` """ with does_not_raise(): instance = Want() @@ -505,13 +461,15 @@ def test_dcnm_maintenance_mode_want_00500() -> None: def test_dcnm_maintenance_mode_want_00510() -> None: """ - ### Classes and Methods - - Want() - - params_spec.setter + # Summary + + Verify `Want().params_spec` raises `TypeError` when `params_spec` + is not an instance of `ParamsSpec()`. + + ## Classes and Methods - ### Summary - - Verify Want().params_spec raises ``TypeError`` when params_spec - is not an instance of ParamsSpec(). + - Want() + - `params_spec.setter` """ with does_not_raise(): instance = Want() @@ -521,19 +479,21 @@ def test_dcnm_maintenance_mode_want_00510() -> None: match += r"Got type str, value NOT_AN_INSTANCE_OF_PARAMS_SPEC\.\s+" match += r"Error detail: 'str' object has no attribute 'class_name'\." with pytest.raises(TypeError, match=match): - instance.params_spec = "NOT_AN_INSTANCE_OF_PARAMS_SPEC" + instance.params_spec = "NOT_AN_INSTANCE_OF_PARAMS_SPEC" # type: ignore def test_dcnm_maintenance_mode_want_00520() -> None: """ - ### Classes and Methods - - Want() - - params_spec.setter + # Summary - ### Summary - Verify Want().params_spec raises ``TypeError`` when params_spec - is not an instance of ParamsSpec(), but IS an instance of another + Verify `Want().params_spec` raises `TypeError` when `params_spec` + is not an instance of `ParamsSpec()`, but IS an instance of another class. + + ## Classes and Methods + + - Want() + - `params_spec.setter` """ with does_not_raise(): instance = Want() @@ -542,17 +502,19 @@ def test_dcnm_maintenance_mode_want_00520() -> None: match += r"value must be an instance of ParamsSpec\.\s+" match += r"Got type ParamsValidate, value .* object at 0x.*\." with pytest.raises(TypeError, match=match): - instance.params_spec = ParamsValidate() + instance.params_spec = ParamsValidate() # type: ignore def test_dcnm_maintenance_mode_want_00600() -> None: """ - ### Classes and Methods - - Want() - - validator.setter + # Summary + + Verify `Want().validator` happy path. - ### Summary - Verify Want().validator happy path. + ## Classes and Methods + + - Want() + - `validator.setter` """ with does_not_raise(): instance = Want() @@ -561,13 +523,15 @@ def test_dcnm_maintenance_mode_want_00600() -> None: def test_dcnm_maintenance_mode_want_00610() -> None: """ - ### Classes and Methods - - Want() - - validator.setter + # Summary + + Verify `Want().validator` raises `TypeError` when `validator` + is not an instance of `ParamsValidate()`. - ### Summary - - Verify Want().validator raises ``TypeError`` when validator - is not an instance of ParamsValidate(). + ## Classes and Methods + + - Want() + - `validator.setter` """ with does_not_raise(): instance = Want() @@ -577,19 +541,21 @@ def test_dcnm_maintenance_mode_want_00610() -> None: match += r"Got type str, value NOT_AN_INSTANCE_OF_PARAMS_VALIDATE\.\s+" match += r"Error detail: 'str' object has no attribute 'class_name'\." with pytest.raises(TypeError, match=match): - instance.validator = "NOT_AN_INSTANCE_OF_PARAMS_VALIDATE" + instance.validator = "NOT_AN_INSTANCE_OF_PARAMS_VALIDATE" # type: ignore def test_dcnm_maintenance_mode_want_00620() -> None: """ - ### Classes and Methods - - Want() - - validator.setter + # Summary - ### Summary - Verify Want().validator raises ``TypeError`` when validator - is not an instance of ParamsValidate(), but IS an instance of + Verify `Want().validator` raises `TypeError` when `validator` + is not an instance of `ParamsValidate()`, but IS an instance of another class. + + ## Classes and Methods + + - Want() + - `validator.setter` """ with does_not_raise(): instance = Want() @@ -598,4 +564,4 @@ def test_dcnm_maintenance_mode_want_00620() -> None: match += r"value must be an instance of ParamsValidate\.\s+" match += r"Got type ParamsSpec, value .* object at 0x.*\." with pytest.raises(TypeError, match=match): - instance.validator = ParamsSpec() + instance.validator = ParamsSpec() # type: ignore diff --git a/tests/unit/modules/dcnm/dcnm_maintenance_mode/utils.py b/tests/unit/modules/dcnm/dcnm_maintenance_mode/utils.py index 93245efd8..2fd5a55c1 100644 --- a/tests/unit/modules/dcnm/dcnm_maintenance_mode/utils.py +++ b/tests/unit/modules/dcnm/dcnm_maintenance_mode/utils.py @@ -1,4 +1,7 @@ -# Copyright (c) 2024 Cisco and/or its affiliates. +""" +Utility functions and fixtures for dcnm_maintenance_mode unit tests +""" +# 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. @@ -14,22 +17,17 @@ 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.fabric_details_v2 import \ - FabricDetailsByName as FabricDetailsByNameV2 -from ansible_collections.cisco.dcnm.plugins.modules.dcnm_maintenance_mode import \ - Common -from ansible_collections.cisco.dcnm.tests.unit.modules.dcnm.dcnm_maintenance_mode.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.fabric_details_v2 import FabricDetailsByName as FabricDetailsByNameV2 +from ansible_collections.cisco.dcnm.plugins.modules.dcnm_maintenance_mode import Common +from ansible_collections.cisco.dcnm.tests.unit.modules.dcnm.dcnm_maintenance_mode.fixture import load_fixture params_query = { "state": "query",