From b0760bd06e330e08f2ba2c67ec5011eca2256bf7 Mon Sep 17 00:00:00 2001 From: prabahal Date: Thu, 6 Mar 2025 18:45:40 +0530 Subject: [PATCH 01/13] Initial commit for child_fabric_module --- .../rest/control/fabrics/msd/msd.py | 134 ++++ .../module_utils/msd/Fabric_associations.py | 88 +++ plugins/module_utils/msd/add_child_fab.py | 133 ++++ plugins/module_utils/msd/delete_child_fab.py | 135 ++++ plugins/module_utils/msd/query_child_fab.py | 186 +++++ plugins/modules/dcnm_child_fabric.py | 668 ++++++++++++++++++ 6 files changed, 1344 insertions(+) create mode 100644 plugins/module_utils/common/api/v1/lan_fabric/rest/control/fabrics/msd/msd.py create mode 100644 plugins/module_utils/msd/Fabric_associations.py create mode 100644 plugins/module_utils/msd/add_child_fab.py create mode 100644 plugins/module_utils/msd/delete_child_fab.py create mode 100644 plugins/module_utils/msd/query_child_fab.py create mode 100644 plugins/modules/dcnm_child_fabric.py diff --git a/plugins/module_utils/common/api/v1/lan_fabric/rest/control/fabrics/msd/msd.py b/plugins/module_utils/common/api/v1/lan_fabric/rest/control/fabrics/msd/msd.py new file mode 100644 index 000000000..9b71ced54 --- /dev/null +++ b/plugins/module_utils/common/api/v1/lan_fabric/rest/control/fabrics/msd/msd.py @@ -0,0 +1,134 @@ +# 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. +# pylint: disable=line-too-long +from __future__ import absolute_import, division, print_function + +__metaclass__ = type +__author__ = "prabahal" + +import logging + +from ..fabrics import Fabrics + + +class Msd(Fabrics): + """ + ## api.v1.lan-fabric.rest.control.fabrics.Msd() + + ### Description + Common methods and properties for Msd() subclasses. + + ### Path + - ``/api/v1/lan-fabric/rest/control/fabrics/msd`` + """ + + def __init__(self): + super().__init__() + self.class_name = self.__class__.__name__ + self.log = logging.getLogger(f"dcnm.{self.class_name}") + self.msd = f"{self.fabrics}/msd" + msg = f"ENTERED api.v1.lan_fabric.rest.control.fabrics.{self.class_name}" + self.log.debug(msg) + + +class EpFabricAssociations(Msd): + """ + ## api.v1.lan-fabric.rest.control.fabrics.msd.EpFabricAssociations() + + ### Description + Common methods and properties for EpFabricAssociations() subclasses. + + ### Path + - ``/api/v1/lan-fabric/rest/control/fabrics/msd/fabric-associations`` + """ + + def __init__(self): + super().__init__() + self.class_name = self.__class__.__name__ + self.log = logging.getLogger(f"dcnm.{self.class_name}") + msg = f"ENTERED api.v1.lan_fabric.rest.control.fabrics.msd.{self.class_name}" + self.log.debug(msg) + + def _build_properties(self): + super()._build_properties() + self.properties["verb"] = "GET" + + @property + def path(self): + """ + Return endpoint path. + """ + return f"{self.msd}/fabric-associations" + + +class EpChildFabricAdd(Msd): + """ + ## api.v1.lan-fabric.rest.control.fabrics.msd.EpChildFabricAdd() + + ### Description + Common methods and properties for EpChildFabricAdd() subclasses. + + ### Path + - ``/api/v1/lan-fabric/rest/control/fabrics/msdAdd`` + """ + + def __init__(self): + super().__init__() + self.class_name = self.__class__.__name__ + self.log = logging.getLogger(f"dcnm.{self.class_name}") + self.msdAdd = f"{self.msd}Add" + msg = f"ENTERED api.v1.lan_fabric.rest.control.fabrics.msd.{self.class_name}" + self.log.debug(msg) + + def _build_properties(self): + super()._build_properties() + self.properties["verb"] = "POST" + + @property + def path(self): + """ + Return endpoint path. + """ + return f"{self.msd}Add" + + +class EpChildFabricExit(Msd): + """ + ## api.v1.lan-fabric.rest.control.fabrics.msd.EpChildFabricExit() + + ### Description + Common methods and properties for EpChildFabricExit() subclasses. + + ### Path + - ``/api/v1/lan-fabric/rest/control/fabrics/msdExit`` + """ + + def __init__(self): + super().__init__() + self.class_name = self.__class__.__name__ + self.log = logging.getLogger(f"dcnm.{self.class_name}") + self.msdExit = f"{self.msd}Exit" + msg = f"ENTERED api.v1.lan_fabric.rest.control.fabrics.msd.{self.class_name}" + self.log.debug(msg) + + def _build_properties(self): + super()._build_properties() + self.properties["verb"] = "POST" + + @property + def path(self): + """ + Return endpoint path. + """ + return f"{self.msd}Exit" diff --git a/plugins/module_utils/msd/Fabric_associations.py b/plugins/module_utils/msd/Fabric_associations.py new file mode 100644 index 000000000..8dc815ca2 --- /dev/null +++ b/plugins/module_utils/msd/Fabric_associations.py @@ -0,0 +1,88 @@ +# +# 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, division, print_function + +__metaclass__ = type +__author__ = "Prabahal" + +import copy +import inspect +import json +import logging + +from ..common.api.v1.lan_fabric.rest.control.fabrics.msd.msd import \ + EpFabricAssociations + + +# Import Results() only for the case where the user has not set Results() +# prior to calling commit(). In this case, we instantiate Results() +# in _validate_commit_parameters() so that we can register the failure +# in commit(). +from ..common.results import Results + + +class FabricAssociations(): + + def __init__(self): + self.class_name = self.__class__.__name__ + self.log = logging.getLogger(f"dcnm.{self.class_name}") + self.data = None + self.results = Results() + self.refreshed = False + self.fabric_association_data = [] + + @property + def all_data(self) -> dict: + """ + - Return raw fabric association data from the controller. + - Raise ``ValueError`` if ``refresh()`` has not been called. + """ + method_name = inspect.stack()[0][3] + try: + self.verify_refresh_has_been_called(method_name) + except ValueError as error: + raise ValueError(error) from error + return self.fabric_association_data + + def verify_refresh_has_been_called(self, attempted_method_name): + """ + - raise ``ValueError`` if ``refresh()`` has not been called. + """ + if self.refreshed is True: + return + msg = f"{self.class_name}.refresh() must be called before accessing " + msg += f"{self.class_name}.{attempted_method_name}." + raise ValueError(msg) + + def refresh(self): + + self.ep_fabrics_associations = EpFabricAssociations() + self.rest_send.path = self.ep_fabrics_associations.path + self.rest_send.verb = self.ep_fabrics_associations.verb + save_check_mode = self.rest_send.check_mode + self.rest_send.check_mode = False + self.rest_send.commit() + self.rest_send.check_mode = save_check_mode + self.fabric_association_data = copy.deepcopy(self.rest_send.response_current.get("DATA", {})) + + msg = f"self.data: {json.dumps(self.data, indent=4, sort_keys=True)}" + self.log.debug(msg) + self.refreshed = True + self.results.response_current = self.rest_send.response_current + self.results.response = self.rest_send.response_current + self.results.result_current = self.rest_send.result_current + self.results.result = self.rest_send.result_current + self.results.register_task_result() diff --git a/plugins/module_utils/msd/add_child_fab.py b/plugins/module_utils/msd/add_child_fab.py new file mode 100644 index 000000000..32d8092f3 --- /dev/null +++ b/plugins/module_utils/msd/add_child_fab.py @@ -0,0 +1,133 @@ +# +# 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, division, print_function + +__metaclass__ = type +__author__ = "Prabahal" + +import copy +import inspect +import json +import logging + +from ..common.api.v1.lan_fabric.rest.control.fabrics.msd.msd import \ + EpChildFabricAdd + +# Import Results() only for the case where the user has not set Results() +# prior to calling commit(). In this case, we instantiate Results() +# in _validate_commit_parameters() so that we can register the failure +# in commit(). +from ..common.results import Results + + +class childFabricAdd(): + """ + methods and properties for adding Child fabric into MSD: + + """ + + def __init__(self): + self.class_name = self.__class__.__name__ + self.action = "child_fabric_add" + self.log = logging.getLogger(f"dcnm.{self.class_name}") + self.ep_fabric_add = EpChildFabricAdd() + msg = "ENTERED childFabricAdd()" + self.log.debug(msg) + + def commit(self, payload): + """ + ### Summary + - Add child fabrics to Mentioned Parent fabric. + + ### Raises + - ``ValueError`` if: + - ``_validate_commit_parameters`` raises ``ValueError``. + + """ + try: + self._validate_commit_parameters() + except ValueError as error: + # pylint: disable=no-member + self.results.action = self.action + self.results.changed = False + self.results.failed = True + if self.rest_send is not None: + self.results.check_mode = self.rest_send.check_mode + self.results.state = self.rest_send.state + else: + self.results.check_mode = False + self.results.state = "Added" + self.results.register_task_result() + raise ValueError(error) from error + # pylint: enable=no-member + + for payload_item in payload: + try: + self.rest_send.path = self.ep_fabric_add.path + self.rest_send.verb = self.ep_fabric_add.verb + self.rest_send.payload = payload_item + self.rest_send.save_settings() + self.rest_send.check_mode = False + self.rest_send.timeout = 1 + self.rest_send.commit() + self.rest_send.restore_settings() + except (TypeError, ValueError) as error: + raise ValueError(error) from error + + if self.rest_send.result_current["success"] is False: + self.results.diff_current = {} + else: + self.results.diff_current = copy.deepcopy(payload_item) + self.results.action = self.action + self.results.state = self.rest_send.state + self.results.check_mode = self.rest_send.check_mode + self.results.response_current = copy.deepcopy( + self.rest_send.response_current + ) + self.results.result_current = copy.deepcopy(self.rest_send.result_current) + self.results.register_task_result() + + msg = f"self.results.diff: {json.dumps(self.results.diff, indent=4, sort_keys=True)}" + self.log.debug(msg) + + def _validate_commit_parameters(self): + """ + - validate the parameters for commit + - raise ``ValueError`` if ``fabric_names`` is not set + """ + method_name = inspect.stack()[0][3] # pylint: disable=unused-variable + + if self.fabric_names is None: + msg = f"{self.class_name}.{method_name}: " + msg += "fabric_names must be set prior to calling commit." + raise ValueError(msg) + + # pylint: disable=no-member + if self.rest_send is None: + msg = f"{self.class_name}.{method_name}: " + msg += "rest_send must be set prior to calling commit." + raise ValueError(msg) + + # pylint: disable=access-member-before-definition + # pylint: disable=attribute-defined-outside-init + if self.results is None: + # Instantiate Results() only to register the failure + self.results = Results() + msg = f"{self.class_name}.{method_name}: " + msg += "results must be set prior to calling commit." + raise ValueError(msg) + # pylint: enable=access-member-before-definition + # pylint: enable=attribute-defined-outside-init diff --git a/plugins/module_utils/msd/delete_child_fab.py b/plugins/module_utils/msd/delete_child_fab.py new file mode 100644 index 000000000..a2d5ea2b0 --- /dev/null +++ b/plugins/module_utils/msd/delete_child_fab.py @@ -0,0 +1,135 @@ +# Copyright (c) 2024 Cisco and/or its affiliates. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +from __future__ import absolute_import, division, print_function + +__metaclass__ = type +__author__ = "Prabahal" + +import copy +import json +import inspect +import logging + +from ..common.api.v1.lan_fabric.rest.control.fabrics.msd.msd import \ + EpChildFabricExit + + +# Import Results() only for the case where the user has not set Results() +# prior to calling commit(). In this case, we instantiate Results() +# in _validate_commit_parameters() so that we can register the failure +# in commit(). +from ..common.results import Results + + +class childFabricDelete(): + """ + Delete child fabrics from Parent fabrics + """ + + def __init__(self): + self.class_name = self.__class__.__name__ + self.action = "child_fabric_delete" + + self.log = logging.getLogger(f"dcnm.{self.class_name}") + + self._fabrics_to_delete = [] + self.ep_child_fabric_delete = EpChildFabricExit() + self._fabric_names = None + + self._cannot_delete_fabric_reason = None + + msg = "ENTERED childFabricDelete()" + self.log.debug(msg) + + def commit(self, payload): + """ + ### Summary + - Delete fabrics in Mentioned Parent fabric. + + ### Raises + - ``ValueError`` if: + - ``_validate_commit_parameters`` raises ``ValueError``. + + """ + + try: + self._validate_commit_parameters() + except ValueError as error: + # pylint: disable=no-member + self.results.action = self.action + self.results.changed = False + self.results.failed = True + if self.rest_send is not None: + self.results.check_mode = self.rest_send.check_mode + self.results.state = self.rest_send.state + else: + self.results.check_mode = False + self.results.state = "Deleted" + self.results.register_task_result() + raise ValueError(error) from error + # pylint: enable=no-member + + for payload_item in payload: + try: + self.rest_send.path = self.ep_child_fabric_delete.path + self.rest_send.verb = self.ep_child_fabric_delete.verb + self.rest_send.payload = payload_item + self.rest_send.save_settings() + self.rest_send.check_mode = False + self.rest_send.timeout = 1 + self.rest_send.commit() + self.rest_send.restore_settings() + except (TypeError, ValueError) as error: + raise ValueError(error) from error + + if self.rest_send.result_current["success"] is False: + self.results.diff_current = {} + else: + self.results.diff_current = copy.deepcopy(payload_item) + self.results.action = self.action + self.results.state = self.rest_send.state + self.results.check_mode = self.rest_send.check_mode + self.results.response_current = copy.deepcopy( + self.rest_send.response_current + ) + self.results.result_current = copy.deepcopy(self.rest_send.result_current) + self.results.register_task_result() + + msg = f"self.results.diff: {json.dumps(self.results.diff, indent=4, sort_keys=True)}" + self.log.debug(msg) + + def _validate_commit_parameters(self): + """ + - validate the parameters for commit + - raise ``ValueError`` if ``fabric_names`` is not set + """ + method_name = inspect.stack()[0][3] # pylint: disable=unused-variable + + if self.fabric_names is None: + msg = f"{self.class_name}.{method_name}: " + msg += "fabric_names must be set prior to calling commit." + raise ValueError(msg) + + # pylint: disable=no-member + if self.rest_send is None: + msg = f"{self.class_name}.{method_name}: " + msg += "rest_send must be set prior to calling commit." + raise ValueError(msg) + + if self.results is None: + # Instantiate Results() only to register the failure + self.results = Results() + msg = f"{self.class_name}.{method_name}: " + msg += "results must be set prior to calling commit." + raise ValueError(msg) diff --git a/plugins/module_utils/msd/query_child_fab.py b/plugins/module_utils/msd/query_child_fab.py new file mode 100644 index 000000000..a97d32cd6 --- /dev/null +++ b/plugins/module_utils/msd/query_child_fab.py @@ -0,0 +1,186 @@ +# Copyright (c) 2024 Cisco and/or its affiliates. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +from __future__ import absolute_import, division, print_function + +__metaclass__ = type +__author__ = "Prabahal" +import copy +import inspect +import logging + +from ..common.results import Results +from ..msd.Fabric_associations import FabricAssociations + + +class childFabricQuery(): + """ + ### Summary + Query child fabrics. + + ### Raises + - ``ValueError`` if: + - ``fabric_names`` is not set. + - ``rest_send`` is not set. + - ``results`` is not set. + """ + + def __init__(self): + self.class_name = self.__class__.__name__ + self.action = "child_fabric_query" + + self.log = logging.getLogger(f"dcnm.{self.class_name}") + + self._fabric_names = None + self._fabric_associations = [] + self.rest_send = None + msg = "ENTERED ChildFabricQuery()" + self.log.debug(msg) + + @property + def fabric_names(self): + """ + ### Summary + - setter: return the fabric names + - getter: set the fabric_names + + ### Raises + - ``ValueError`` if: + - ``value`` is not a list. + - ``value`` is an empty list. + - ``value`` is not a list of strings. + + """ + return self._fabric_names + + @fabric_names.setter + def fabric_names(self, value): + method_name = inspect.stack()[0][3] + if not isinstance(value, list): + msg = f"{self.class_name}.{method_name}: " + msg += "fabric_names must be a list. " + msg += f"got {type(value).__name__} for " + msg += f"value {value}" + raise ValueError(msg) + if len(value) == 0: + msg = f"{self.class_name}.{method_name}: " + msg += "fabric_names must be a list of at least one string. " + msg += f"got {value}." + raise ValueError(msg) + for item in value: + if not isinstance(item, str): + msg = f"{self.class_name}.{method_name}: " + msg += "fabric_names must be a list of strings. " + msg += f"got {type(item).__name__} for " + msg += f"value {item}" + raise ValueError(msg) + self._fabric_names = value + + def _validate_commit_parameters(self): + """ + ### Summary + - validate the parameters for commit. + + ### Raises + - ``ValueError`` if: + - ``fabric_names`` is not set. + - ``rest_send`` is not set. + - ``results`` is not set. + """ + method_name = inspect.stack()[0][3] # pylint: disable=unused-variable + + if self.fabric_names is None: + msg = f"{self.class_name}.{method_name}: " + msg += "fabric_names must be set before calling commit." + raise ValueError(msg) + + # pylint: disable=no-member + if self.rest_send is None: + msg = f"{self.class_name}.{method_name}: " + msg += "rest_send must be set before calling commit." + raise ValueError(msg) + + # pylint: disable=access-member-before-definition + if self.results is None: + # Instantiate Results() to register the failure + self.results = Results() + msg = f"{self.class_name}.{method_name}: " + msg += "results must be set before calling commit." + raise ValueError(msg) + + def commit(self): + """ + ### Summary + - query each of the fabrics in ``fabric_names``. + + ### Raises + - ``ValueError`` if: + - ``_validate_commit_parameters`` raises ``ValueError``. + + """ + try: + self._validate_commit_parameters() + except ValueError as error: + # pylint: disable=no-member + self.results.action = self.action + self.results.changed = False + self.results.failed = True + if self.rest_send is not None: + self.results.check_mode = self.rest_send.check_mode + self.results.state = self.rest_send.state + else: + self.results.check_mode = False + self.results.state = "query" + self.results.register_task_result() + raise ValueError(error) from error + # pylint: enable=no-member + try: + self.fab_association = FabricAssociations() + self.fab_association.rest_send = self.rest_send + self.fab_association.refresh() + + except (TypeError, ValueError) as error: + raise ValueError(error) from error + + self.data = {} + if self.fab_association.fabric_association_data is None: + # The DATA key should always be present. We should never hit this. + return + for item in self.fab_association.fabric_association_data: + fabric_name = item.get("fabricName", None) + if fabric_name is None: + continue + self.data[fabric_name] = item + + add_to_diff = {} + for fabric_name in self.fabric_names: + for item in self.data: + if self.data[item]['fabricParent'] == fabric_name: + add_to_diff[self.data[item]['fabricName']] = self.data[item] + + msg = f"filtered data : {add_to_diff}" + self.log.debug(msg) + # pylint: disable=no-member + self.results.action = self.action + self.results.check_mode = self.rest_send.check_mode + self.results.state = self.rest_send.state + # pylint: disable=no-member + self.results.diff_current = add_to_diff + self.results.result_current = {"success": True, "found": True} + self.results.response_current = copy.deepcopy( + self.rest_send.response_current + ) + self.results.result_current = copy.deepcopy( + self.results.result_current + ) + self.results.register_task_result() diff --git a/plugins/modules/dcnm_child_fabric.py b/plugins/modules/dcnm_child_fabric.py new file mode 100644 index 000000000..30168ae6d --- /dev/null +++ b/plugins/modules/dcnm_child_fabric.py @@ -0,0 +1,668 @@ +#!/usr/bin/python +# +# 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, division, print_function + +__metaclass__ = type +__author__ = "Prabahal" + +DOCUMENTATION = """ +--- +module: dcnm_child_fabric +short_description: Manage addition and deletion of NDFC fabrics to MSD. +version_added: "3.5.0" +author: Prabahal (@prabahal) +description: +- Create, Delete, Query NDFC child fabrics. +options: + state: + choices: + - deleted + - merged + - query + default: merged + description: + - The state of the feature or object after module completion + type: str + config: + description: + - A list of fabric configuration dictionaries + type: list + elements: dict + suboptions: + DEPLOY: + default: False + description: + - Save and deploy the fabric configuration. + required: false + type: bool + FABRIC_NAME: + description: + - The name of the MSD fabric. + required: true + type: str + CHILD_FABRIC_NAME: + description: + - The child fabric of MSD fabric. + required: true + type: str +""" + +EXAMPLES = """ + +- name: Create fabrics + cisco.dcnm.dcnm_child_fabric: + state: merged + config: + - FABRIC_NAME: MSD_Parent1 + CHILD_FABRIC_NAME: child1 + - FABRIC_NAME: MSD_Parent2 + CHILD_FABRIC_NAME: child2 + - FABRIC_NAME: MSD_Parent2 + CHILD_FABRIC_NAME: child3 + register: result +- debug: + var: result + +# Query the child fabrics of a MSD Fabric. + +- name: Query the child fabrics of MSD fabrics. + cisco.dcnm.dcnm_child_fabric: + state: query + config: + - FABRIC_NAME: MSD_Fabric1 + - FABRIC_NAME: MSD_Fabric2 + - FABRIC_NAME: MSD_Fabric3 + register: result +- debug: + var: result + +# Delete the fabrics. + +- name: Delete the fabrics. + cisco.dcnm.dcnm_fabric: + state: deleted + config: + - FABRIC_NAME: MSD_Parent1 + CHILD_FABRIC_NAME: child1 + - FABRIC_NAME: MSD_Parent2 + CHILD_FABRIC_NAME: child2 + register: result +- debug: + var: result + +""" +# pylint: disable=wrong-import-position +import copy +import inspect +import logging + +from ansible.module_utils.basic import AnsibleModule + +from ..module_utils.common.controller_version import ControllerVersion +from ..module_utils.common.exceptions import ControllerResponseError +from ..module_utils.common.log_v2 import Log +from ..module_utils.common.properties import Properties +from ..module_utils.common.response_handler import ResponseHandler +from ..module_utils.common.rest_send_v2 import RestSend +from ..module_utils.common.results import Results +from ..module_utils.common.sender_dcnm import Sender +from ..module_utils.common.conversion import ConversionUtils +from ..module_utils.msd.query_child_fab import childFabricQuery +from ..module_utils.msd.delete_child_fab import childFabricDelete +from ..module_utils.msd.add_child_fab import childFabricAdd +from ..module_utils.msd.Fabric_associations import FabricAssociations +from ..module_utils.fabric.verify_playbook_params import VerifyPlaybookParams + + +@Properties.add_rest_send +class childCommon(): + """ + Common methods, properties, and resources for all states. + """ + + def __init__(self, params): + self.class_name = self.__class__.__name__ + self.log = logging.getLogger(f"dcnm.{self.class_name}") + method_name = inspect.stack()[0][3] # pylint: disable=unused-variable + + self.controller_version = ControllerVersion() + self.features = {} + self._implemented_states = set() + + self.params = params + + self.populate_check_mode() + self.populate_state() + self.populate_config() + + self.results = Results() + self.results.state = self.state + self.results.check_mode = self.check_mode + self._verify_playbook_params = VerifyPlaybookParams() + self.conversion = ConversionUtils() + self.payloads = [] + self.want = [] + + msg = "ENTERED Common(): " + msg += f"{method_name}: state: {self.state}, " + msg += f"check_mode: {self.check_mode}" + self.log.debug(msg) + + def verify_parent_fab_exists_in_controller(self): + method_name = inspect.stack()[0][3] + for item in self.payloads: + for fabric in self.data: + if fabric == item["destFabric"]: + break + else: + msg = f"{self.class_name}: {method_name}: " + msg += f"Playbook configuration for FABRIC_NAME {item["destFabric"]} " + msg += "is not found in Controller. Please create and try again" + raise ValueError(msg) + + def verify_parent_fab_is_MSD(self): + method_name = inspect.stack()[0][3] + for item in self.payloads: + for fabric in self.data: + if fabric == item["destFabric"]: + if (self.data[fabric]['fabricType'] != "MSD"): + msg = f"{self.class_name}: {method_name}: " + msg += f"Playbook configuration for FABRIC_NAME {item["destFabric"]} " + msg += "is not of type MSD" + raise ValueError(msg) + + def verify_child_fab_exists_in_controller(self): + method_name = inspect.stack()[0][3] + for item in self.payloads: + for fabric in self.data: + if fabric == item["sourceFabric"]: + break + else: + msg = f"{self.class_name}: {method_name}: " + msg += f"Playbook configuration for CHILD_FABRIC_NAME {item["sourceFabric"]} " + msg += "is not found in Controller. Please create and try again" + raise ValueError(msg) + + def verify_child_fabric_is_member_of_another_fabric(self): + method_name = inspect.stack()[0][3] + for item in self.payloads: + for fabric in self.data: + if fabric == item["sourceFabric"]: + if (self.data[fabric]['fabricParent'] != item["destFabric"]) \ + and (self.data[fabric]['fabricParent'] != "None"): + msg = f"Invalid Operation: Child fabric {item["sourceFabric"]} " + msg += f"is member of another Fabric {self.data[fabric]['fabricParent']}." + self.log.debug(msg) + raise ValueError(msg) + + def verify_child_fabric_is_already_member(self) -> bool: + method_name = inspect.stack()[0][3] + for item in self.payloads: + for fabric in self.data: + if fabric == item["sourceFabric"]: + if (self.data[fabric]['fabricParent'] == item["destFabric"]): + return True + return False + + def get_want(self): + method_name = inspect.stack()[0][3] + for config in self.config: + msg = f"payload: {config}" + self.log.debug(msg) + + if not isinstance(config, dict): + msg = f"{self.class_name}.{method_name}: " + msg += "Playbook configuration for fabrics must be a dict. " + msg += f"Got type {type(config).__name__}, " + msg += f"value {config}." + raise ValueError(msg) + try: + parent_fabric = config.get("FABRIC_NAME", None) + child_fabric = config.get("CHILD_FABRIC_NAME", None) + try: + self.conversion.validate_fabric_name(parent_fabric) + self.conversion.validate_fabric_name(child_fabric) + except (TypeError, ValueError) as error: + msg = f"{self.class_name}: " + msg += "Playbook configuration for FABRIC_NAME or CHILD_FABRIC_NAME " + msg += "contains an invalid FABRIC_NAME. " + # error below already contains a period "." at the end + msg += f"Error detail: {error} " + msg += f"Bad configuration: {config}." + raise ValueError(msg) from error + except ValueError as error: + raise ValueError(f"{error}") from error + config_payload = {'destFabric': parent_fabric, 'sourceFabric': child_fabric} + self.payloads.append(copy.deepcopy(config_payload)) + + def populate_check_mode(self): + """ + ### Summary + Populate ``check_mode`` with the playbook check_mode. + + ### Raises + - ValueError if check_mode is not provided. + """ + method_name = inspect.stack()[0][3] + self.check_mode = self.params.get("check_mode", None) + if self.check_mode is None: + msg = f"{self.class_name}.{method_name}: " + msg += "check_mode is required." + raise ValueError(msg) + + def populate_config(self): + """ + ### Summary + Populate ``config`` with the playbook config. + + ### Raises + - ValueError if: + - ``state`` is "merged" or "deleted" and ``config`` is None. + - ``config`` is not a list. + """ + method_name = inspect.stack()[0][3] + states_requiring_config = {"merged", "deleted"} + self.config = self.params.get("config", None) + if self.state in states_requiring_config: + if self.config is None: + msg = f"{self.class_name}.{method_name}: " + msg += "params is missing config parameter." + raise ValueError(msg) + if not isinstance(self.config, list): + msg = f"{self.class_name}.{method_name}: " + msg += "expected list type for self.config. " + msg += f"got {type(self.config).__name__}" + raise ValueError(msg) + + def populate_state(self): + """ + ### Summary + Populate ``state`` with the playbook state. + + ### Raises + - ValueError if: + - ``state`` is not provided. + - ``state`` is not a valid state. + """ + method_name = inspect.stack()[0][3] + + valid_states = ["deleted", "merged", "query"] + + self.state = self.params.get("state", None) + if self.state is None: + msg = f"{self.class_name}.{method_name}: " + msg += "params is missing state parameter." + raise ValueError(msg) + if self.state not in valid_states: + msg = f"{self.class_name}.{method_name}: " + msg += f"Invalid state: {self.state}. " + msg += f"Expected one of: {','.join(valid_states)}." + raise ValueError(msg) + + def get_want_query(self) -> None: + """ + ### Summary + - Validate the playbook configs. + - Update self.want with the playbook configs. + + ### Raises + - ``ValueError`` if the playbook configs are invalid. + """ + merged_configs = [] + for config in self.config: + merged_configs.append(copy.deepcopy(config)) + + self.want = [] + for config in merged_configs: + self.want.append(copy.deepcopy(config)) + +# Keeping this function to check lower NDFC version support. yet to get data + def get_controller_version(self): + """ + ### Summary + Initialize and refresh self.controller_version. + + ### Raises + + - ``ValueError`` if the controller returns an error when attempting + to retrieve the controller version. + """ + method_name = inspect.stack()[0][3] + try: + self.controller_version.rest_send = self.rest_send + self.controller_version.refresh() + except (ControllerResponseError, ValueError) as error: + msg = f"{self.class_name}.{method_name}: " + msg += "Controller returned error when attempting to retrieve " + msg += "controller version. " + msg += f"Error detail: {error}" + raise ValueError(msg) from error + + +class Deleted(childCommon): + """ + ### Summary + Handle deleted state + """ + + def __init__(self, params): + self.class_name = self.__class__.__name__ + super().__init__(params) + + self.action = "child_fabric_delete" + self._implemented_states.add("deleted") + + self.log = logging.getLogger(f"dcnm.{self.class_name}") + msg = "ENTERED child fabric Deleted(): " + msg += f"state: {self.results.state}, " + msg += f"check_mode: {self.results.check_mode}" + self.log.debug(msg) + self.data = {} + + def commit(self) -> None: + """ + ### Summary + delete the fabrics in ``self.want`` that exist on the controller. + + ### Raises + + - ``ValueError`` if the controller returns an error when attempting to + delete the fabrics. + """ + method_name = inspect.stack()[0][3] + + msg = f"ENTERED: {self.class_name}.{method_name}" + self.log.debug(msg) + + self.get_want() + + self.fab_association = FabricAssociations() + self.fab_association.rest_send = self.rest_send + self.fab_association.refresh() + msg = f"Fab association data{self.fab_association.fabric_association_data}" + self.log.debug(msg) + for item in self.fab_association.fabric_association_data: + fabric_name = item.get("fabricName", None) + if fabric_name is None: + continue + self.data[fabric_name] = item + + self.verify_parent_fab_exists_in_controller() + self.verify_parent_fab_is_MSD() + if not self.verify_child_fabric_is_already_member(): + self.results.result_current = {"success": True, "changed": False} + msg = "Child fabric is not a member of Parent fabric." + self.results.response_current = {"RETURN_CODE": 200, "MESSAGE": msg} + self.results.register_task_result() + return + self.delete = childFabricDelete() + self.delete.rest_send = self.rest_send + self.delete.results = self.results + + fabric_names_to_delete = [] + for want in self.payloads: + fabric_names_to_delete.append(want["sourceFabric"]) + fabric_names_to_delete.append(want["destFabric"]) + try: + self.delete.fabric_names = fabric_names_to_delete + except ValueError as error: + raise ValueError(f"{error}") from error + + try: + self.delete.commit(self.payloads) + except ValueError as error: + raise ValueError(f"{error}") from error + + +class Merged(childCommon): + """ + ### Summary + Handle merged state for adding the child Fabrics. + + ### Raises + + - ``ValueError`` if: + - The controller features required for the fabric type are not + running on the controller. + - The playbook parameters are invalid. + - The controller returns an error when attempting to retrieve + the fabric details. + - The controller returns an error when attempting to create + the fabric. + - The controller returns an error when attempting to update + the fabric. + """ + + def __init__(self, params): + self.class_name = self.__class__.__name__ + super().__init__(params) + + self.action = "child_fabric_add" + self._implemented_states.add("merged") + + self.log = logging.getLogger(f"dcnm.{self.class_name}") + self.add = childFabricAdd() + msg = "ENTERED child fabric merged(): " + msg += f"state: {self.results.state}, " + msg += f"check_mode: {self.results.check_mode}" + self.log.debug(msg) + self.data = {} + + def commit(self) -> None: + """ + ### Summary + Add the fabrics in ``self.payloads`` that exist on the controller. + + ### Raises + + - ``ValueError`` if the controller returns an error when attempting to + add the fabrics. + """ + method_name = inspect.stack()[0][3] + + msg = f"ENTERED: {self.class_name}.{method_name}" + self.log.debug(msg) + + self.get_want() + + self.add.results = self.results + self.fab_association = FabricAssociations() + self.fab_association.rest_send = self.rest_send + self.fab_association.refresh() + msg = f"Fab association data{self.fab_association.fabric_association_data}" + self.log.debug(msg) + for item in self.fab_association.fabric_association_data: + fabric_name = item.get("fabricName", None) + if fabric_name is None: + continue + self.data[fabric_name] = item + + self.verify_parent_fab_exists_in_controller() + self.verify_parent_fab_is_MSD() + self.verify_child_fab_exists_in_controller() + self.verify_child_fabric_is_member_of_another_fabric() + + if self.verify_child_fabric_is_already_member(): + self.results.result_current = {"success": True, "changed": False} + msg = "Child fabric is already member of Parent fabric." + self.results.response_current = {"RETURN_CODE": 200, "MESSAGE": msg} + self.results.register_task_result() + return + + self.add.rest_send = self.rest_send + fabric_names_to_add = [] + for want in self.payloads: + fabric_names_to_add.append(want["sourceFabric"]) + try: + self.add.fabric_names = fabric_names_to_add + except ValueError as error: + raise ValueError(f"{error}") from error + + try: + self.add.commit(self.payloads) + except ValueError as error: + raise ValueError(f"{error}") from error + + +class Query(childCommon): + """ + ### Summary + Handle query state. + + ### Raises + + - ``ValueError`` if: + - The playbook parameters are invalid. + - The controller returns an error when attempting to retrieve + the fabric details. + """ + + def __init__(self, params): + self.class_name = self.__class__.__name__ + super().__init__(params) + + self.action = "child_fabric_query" + self._implemented_states.add("query") + self.log = logging.getLogger(f"dcnm.{self.class_name}") + + msg = "ENTERED Query(): " + msg += f"state: {self.state}, " + msg += f"check_mode: {self.check_mode}" + self.log.debug(msg) + + def verify_payload(self): + if self.config is None: + msg = f"{self.class_name}: " + msg += "Playbook configuration for FABRIC_NAME is missing" + raise ValueError(msg) + for config in self.config: + try: + fabric_name = config.get("FABRIC_NAME", None) + try: + self.conversion.validate_fabric_name(fabric_name) + except (TypeError, ValueError) as error: + msg = f"{self.class_name}: " + msg += "Playbook configuration for FABRIC_NAME is missing or " + msg += "contains an invalid FABRIC_NAME. " + # error below already contains a period "." at the end + msg += f"Error detail: {error} " + msg += f"Bad configuration: {config}." + raise ValueError(msg) from error + except ValueError as error: + raise ValueError(f"{error}") from error + + def commit(self) -> None: + """ + ### Summary + query the fabrics in ``self.want`` that exist on the controller. + + ### Raises + + - ``ValueError`` if: + - Any fabric names are invalid. + - The controller returns an error when attempting to + query the fabrics. + """ + self.verify_payload() + self.get_want_query() + fabric_query = childFabricQuery() + fabric_query.rest_send = self.rest_send + fabric_query.results = self.results + + fabric_names_to_query = [] + for want in self.want: + fabric_names_to_query.append(want["FABRIC_NAME"]) + try: + fabric_query.fabric_names = copy.copy(fabric_names_to_query) + except ValueError as error: + raise ValueError(f"{error}") from error + + try: + fabric_query.commit() + except ValueError as error: + raise ValueError(f"{error}") from error + + +def main(): + """ + ### Summary + main entry point for module execution. + + - In the event that ``ValueError`` is raised, ``AnsibleModule.fail_json`` + is called with the error message. + - Else, ``AnsibleModule.exit_json`` is called with the final result. + + ### Raises + - ``ValueError`` if: + - The playbook parameters are invalid. + - The controller returns an error when attempting to + delete, add, query child fabrics. + """ + + argument_spec = {} + argument_spec["config"] = {"required": False, "type": "list", "elements": "dict"} + argument_spec["state"] = { + "default": "merged", + "choices": ["deleted", "merged", "query"], + } + + ansible_module = AnsibleModule( + argument_spec=argument_spec, supports_check_mode=True + ) + params = copy.deepcopy(ansible_module.params) + params["check_mode"] = ansible_module.check_mode + + # Logging setup + try: + log = Log() + log.commit() + except ValueError as error: + ansible_module.fail_json(str(error)) + + sender = Sender() + sender.ansible_module = ansible_module + rest_send = RestSend(params) + rest_send.response_handler = ResponseHandler() + rest_send.sender = sender + try: + task = None + if params["state"] == "merged": + task = Merged(params) + elif params["state"] == "deleted": + task = Deleted(params) + elif params["state"] == "query": + task = Query(params) + + if task is None: + ansible_module.fail_json(f"Invalid state: {params['state']}") + task.rest_send = rest_send + task.commit() + except ValueError as error: + ansible_module.fail_json(f"{error}", **task.results.failed_result) + + task.results.build_final_result() + + # 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 + msg = "Module failed." + ansible_module.fail_json(msg, **task.results.final_result) + ansible_module.exit_json(**task.results.final_result) + + +if __name__ == "__main__": + main() From c624adece51ed325796c8f65ccc85c0cd62e9583 Mon Sep 17 00:00:00 2001 From: prabahal Date: Thu, 6 Mar 2025 20:18:06 +0530 Subject: [PATCH 02/13] CI flow issue fix --- plugins/modules/dcnm_child_fabric.py | 25 +++++++++++-------------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/plugins/modules/dcnm_child_fabric.py b/plugins/modules/dcnm_child_fabric.py index 30168ae6d..7b5d2f03d 100644 --- a/plugins/modules/dcnm_child_fabric.py +++ b/plugins/modules/dcnm_child_fabric.py @@ -42,12 +42,6 @@ type: list elements: dict suboptions: - DEPLOY: - default: False - description: - - Save and deploy the fabric configuration. - required: false - type: bool FABRIC_NAME: description: - The name of the MSD fabric. @@ -62,7 +56,7 @@ EXAMPLES = """ -- name: Create fabrics +- name: add child fabrics to MSD cisco.dcnm.dcnm_child_fabric: state: merged config: @@ -168,8 +162,9 @@ def verify_parent_fab_exists_in_controller(self): if fabric == item["destFabric"]: break else: + invalid_fab = item["destFabric"] msg = f"{self.class_name}: {method_name}: " - msg += f"Playbook configuration for FABRIC_NAME {item["destFabric"]} " + msg += f"Playbook configuration for FABRIC_NAME {invalid_fab} " msg += "is not found in Controller. Please create and try again" raise ValueError(msg) @@ -179,8 +174,9 @@ def verify_parent_fab_is_MSD(self): for fabric in self.data: if fabric == item["destFabric"]: if (self.data[fabric]['fabricType'] != "MSD"): + invalid_fab = item["destFabric"] msg = f"{self.class_name}: {method_name}: " - msg += f"Playbook configuration for FABRIC_NAME {item["destFabric"]} " + msg += f"Playbook configuration for FABRIC_NAME {invalid_fab} " msg += "is not of type MSD" raise ValueError(msg) @@ -191,25 +187,26 @@ def verify_child_fab_exists_in_controller(self): if fabric == item["sourceFabric"]: break else: + invalid_fab = item["sourceFabric"] msg = f"{self.class_name}: {method_name}: " - msg += f"Playbook configuration for CHILD_FABRIC_NAME {item["sourceFabric"]} " + msg += f"Playbook configuration for CHILD_FABRIC_NAME {invalid_fab} " msg += "is not found in Controller. Please create and try again" raise ValueError(msg) def verify_child_fabric_is_member_of_another_fabric(self): - method_name = inspect.stack()[0][3] for item in self.payloads: for fabric in self.data: if fabric == item["sourceFabric"]: if (self.data[fabric]['fabricParent'] != item["destFabric"]) \ and (self.data[fabric]['fabricParent'] != "None"): - msg = f"Invalid Operation: Child fabric {item["sourceFabric"]} " - msg += f"is member of another Fabric {self.data[fabric]['fabricParent']}." + inv_child_fab = item["sourceFabric"] + another_fab = self.data[fabric]['fabricParent'] + msg = f"Invalid Operation: Child fabric {inv_child_fab} " + msg += f"is member of another Fabric {another_fab}." self.log.debug(msg) raise ValueError(msg) def verify_child_fabric_is_already_member(self) -> bool: - method_name = inspect.stack()[0][3] for item in self.payloads: for fabric in self.data: if fabric == item["sourceFabric"]: From 08be81ca38332b9b460b9092ff7393022cecaa8a Mon Sep 17 00:00:00 2001 From: prabahal Date: Thu, 6 Mar 2025 20:24:38 +0530 Subject: [PATCH 03/13] ignore dcnm_child_fabric.py --- tests/sanity/ignore-2.15.txt | 1 + tests/sanity/ignore-2.16.txt | 1 + 2 files changed, 2 insertions(+) diff --git a/tests/sanity/ignore-2.15.txt b/tests/sanity/ignore-2.15.txt index aaa380c51..bbb5c0f23 100644 --- a/tests/sanity/ignore-2.15.txt +++ b/tests/sanity/ignore-2.15.txt @@ -24,3 +24,4 @@ plugins/httpapi/dcnm.py import-3.10!skip plugins/module_utils/common/sender_requests.py import-3.9 # TODO remove this if/when requests is added to the standard library plugins/module_utils/common/sender_requests.py import-3.10 # TODO remove this if/when requests is added to the standard library plugins/module_utils/common/sender_requests.py import-3.11 # TODO remove this if/when requests is added to the standard library +plugins/modules/dcnm_child_fabric.py validate-modules:missing-gplv3-license # GPLv3 license header not found in the first 20 lines of the module diff --git a/tests/sanity/ignore-2.16.txt b/tests/sanity/ignore-2.16.txt index 33ff9b333..fac628f9b 100644 --- a/tests/sanity/ignore-2.16.txt +++ b/tests/sanity/ignore-2.16.txt @@ -21,3 +21,4 @@ plugins/modules/dcnm_bootflash.py validate-modules:missing-gplv3-license # GPLv3 plugins/modules/dcnm_log.py validate-modules:missing-gplv3-license # GPLv3 license header not found in the first 20 lines of the module plugins/module_utils/common/sender_requests.py import-3.10 # TODO remove this if/when requests is added to the standard library plugins/module_utils/common/sender_requests.py import-3.11 # TODO remove this if/when requests is added to the standard library +plugins/modules/dcnm_child_fabric.py alidate-modules:missing-gplv3-license # GPLv3 license header not found in the first 20 lines of the module From 0c15f9a00f485ad57c3f86ef7241adab3613fe1d Mon Sep 17 00:00:00 2001 From: prabahal Date: Thu, 6 Mar 2025 20:29:09 +0530 Subject: [PATCH 04/13] ignore 2.16 --- tests/sanity/ignore-2.16.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/sanity/ignore-2.16.txt b/tests/sanity/ignore-2.16.txt index fac628f9b..2b496e690 100644 --- a/tests/sanity/ignore-2.16.txt +++ b/tests/sanity/ignore-2.16.txt @@ -21,4 +21,4 @@ plugins/modules/dcnm_bootflash.py validate-modules:missing-gplv3-license # GPLv3 plugins/modules/dcnm_log.py validate-modules:missing-gplv3-license # GPLv3 license header not found in the first 20 lines of the module plugins/module_utils/common/sender_requests.py import-3.10 # TODO remove this if/when requests is added to the standard library plugins/module_utils/common/sender_requests.py import-3.11 # TODO remove this if/when requests is added to the standard library -plugins/modules/dcnm_child_fabric.py alidate-modules:missing-gplv3-license # GPLv3 license header not found in the first 20 lines of the module +plugins/modules/dcnm_child_fabric.py validate-modules:missing-gplv3-license # GPLv3 license header not found in the first 20 lines of the module From 4d4feccdacae6f824c6226c818a63fd0c68e81dc Mon Sep 17 00:00:00 2001 From: prabahal Date: Thu, 20 Mar 2025 18:52:24 +0530 Subject: [PATCH 05/13] Integration Tests - child Fabric modules UT issues fix --- plugins/module_utils/msd/add_child_fab.py | 54 +- plugins/module_utils/msd/delete_child_fab.py | 53 +- plugins/modules/dcnm_child_fabric.py | 101 +- .../dcnm_child_fabric/defaults/main.yaml | 2 + .../targets/dcnm_child_fabric/meta/main.yaml | 1 + .../targets/dcnm_child_fabric/tasks/dcnm.yaml | 20 + .../targets/dcnm_child_fabric/tasks/main.yaml | 2 + .../tests/dcnm_child_fabric.yaml | 1453 +++++++++++++++++ 8 files changed, 1579 insertions(+), 107 deletions(-) create mode 100644 tests/integration/targets/dcnm_child_fabric/defaults/main.yaml create mode 100644 tests/integration/targets/dcnm_child_fabric/meta/main.yaml create mode 100644 tests/integration/targets/dcnm_child_fabric/tasks/dcnm.yaml create mode 100644 tests/integration/targets/dcnm_child_fabric/tasks/main.yaml create mode 100644 tests/integration/targets/dcnm_child_fabric/tests/dcnm_child_fabric.yaml diff --git a/plugins/module_utils/msd/add_child_fab.py b/plugins/module_utils/msd/add_child_fab.py index 32d8092f3..261f99c34 100644 --- a/plugins/module_utils/msd/add_child_fab.py +++ b/plugins/module_utils/msd/add_child_fab.py @@ -32,7 +32,6 @@ # in commit(). from ..common.results import Results - class childFabricAdd(): """ methods and properties for adding Child fabric into MSD: @@ -74,34 +73,31 @@ def commit(self, payload): raise ValueError(error) from error # pylint: enable=no-member - for payload_item in payload: - try: - self.rest_send.path = self.ep_fabric_add.path - self.rest_send.verb = self.ep_fabric_add.verb - self.rest_send.payload = payload_item - self.rest_send.save_settings() - self.rest_send.check_mode = False - self.rest_send.timeout = 1 - self.rest_send.commit() - self.rest_send.restore_settings() - except (TypeError, ValueError) as error: - raise ValueError(error) from error - - if self.rest_send.result_current["success"] is False: - self.results.diff_current = {} - else: - self.results.diff_current = copy.deepcopy(payload_item) - self.results.action = self.action - self.results.state = self.rest_send.state - self.results.check_mode = self.rest_send.check_mode - self.results.response_current = copy.deepcopy( - self.rest_send.response_current - ) - self.results.result_current = copy.deepcopy(self.rest_send.result_current) - self.results.register_task_result() - - msg = f"self.results.diff: {json.dumps(self.results.diff, indent=4, sort_keys=True)}" - self.log.debug(msg) + try: + self.rest_send.path = self.ep_fabric_add.path + self.rest_send.verb = self.ep_fabric_add.verb + self.rest_send.payload = payload + self.rest_send.save_settings() + self.rest_send.check_mode = False + self.rest_send.timeout = 1 + self.rest_send.commit() + self.rest_send.restore_settings() + except (TypeError, ValueError) as error: + raise ValueError(error) from error + if self.rest_send.result_current["success"] is False: + self.results.diff_current = {} + else: + self.results.diff_current = copy.deepcopy(payload) + self.results.action = self.action + self.results.state = self.rest_send.state + self.results.check_mode = self.rest_send.check_mode + self.results.response_current = copy.deepcopy( + self.rest_send.response_current + ) + self.results.result_current = copy.deepcopy(self.rest_send.result_current) + self.results.register_task_result() + msg = f"self.results.diff: {json.dumps(self.results.diff, indent=4, sort_keys=True)}" + self.log.debug(msg) def _validate_commit_parameters(self): """ diff --git a/plugins/module_utils/msd/delete_child_fab.py b/plugins/module_utils/msd/delete_child_fab.py index a2d5ea2b0..4ce0232ce 100644 --- a/plugins/module_utils/msd/delete_child_fab.py +++ b/plugins/module_utils/msd/delete_child_fab.py @@ -80,34 +80,31 @@ def commit(self, payload): raise ValueError(error) from error # pylint: enable=no-member - for payload_item in payload: - try: - self.rest_send.path = self.ep_child_fabric_delete.path - self.rest_send.verb = self.ep_child_fabric_delete.verb - self.rest_send.payload = payload_item - self.rest_send.save_settings() - self.rest_send.check_mode = False - self.rest_send.timeout = 1 - self.rest_send.commit() - self.rest_send.restore_settings() - except (TypeError, ValueError) as error: - raise ValueError(error) from error - - if self.rest_send.result_current["success"] is False: - self.results.diff_current = {} - else: - self.results.diff_current = copy.deepcopy(payload_item) - self.results.action = self.action - self.results.state = self.rest_send.state - self.results.check_mode = self.rest_send.check_mode - self.results.response_current = copy.deepcopy( - self.rest_send.response_current - ) - self.results.result_current = copy.deepcopy(self.rest_send.result_current) - self.results.register_task_result() - - msg = f"self.results.diff: {json.dumps(self.results.diff, indent=4, sort_keys=True)}" - self.log.debug(msg) + try: + self.rest_send.path = self.ep_child_fabric_delete.path + self.rest_send.verb = self.ep_child_fabric_delete.verb + self.rest_send.payload = payload + self.rest_send.save_settings() + self.rest_send.check_mode = False + self.rest_send.timeout = 1 + self.rest_send.commit() + self.rest_send.restore_settings() + except (TypeError, ValueError) as error: + raise ValueError(error) from error + if self.rest_send.result_current["success"] is False: + self.results.diff_current = {} + else: + self.results.diff_current = copy.deepcopy(payload) + self.results.action = self.action + self.results.state = self.rest_send.state + self.results.check_mode = self.rest_send.check_mode + self.results.response_current = copy.deepcopy( + self.rest_send.response_current + ) + self.results.result_current = copy.deepcopy(self.rest_send.result_current) + self.results.register_task_result() + msg = f"self.results.diff: {json.dumps(self.results.diff, indent=4, sort_keys=True)}" + self.log.debug(msg) def _validate_commit_parameters(self): """ diff --git a/plugins/modules/dcnm_child_fabric.py b/plugins/modules/dcnm_child_fabric.py index 7b5d2f03d..b80dc0115 100644 --- a/plugins/modules/dcnm_child_fabric.py +++ b/plugins/modules/dcnm_child_fabric.py @@ -206,12 +206,11 @@ def verify_child_fabric_is_member_of_another_fabric(self): self.log.debug(msg) raise ValueError(msg) - def verify_child_fabric_is_already_member(self) -> bool: - for item in self.payloads: - for fabric in self.data: - if fabric == item["sourceFabric"]: - if (self.data[fabric]['fabricParent'] == item["destFabric"]): - return True + def verify_child_fabric_is_already_member(self, item) -> bool: + for fabric in self.data: + if fabric == item["sourceFabric"]: + if (self.data[fabric]['fabricParent'] == item["destFabric"]): + return True return False def get_want(self): @@ -399,29 +398,31 @@ def commit(self) -> None: self.verify_parent_fab_exists_in_controller() self.verify_parent_fab_is_MSD() - if not self.verify_child_fabric_is_already_member(): - self.results.result_current = {"success": True, "changed": False} - msg = "Child fabric is not a member of Parent fabric." - self.results.response_current = {"RETURN_CODE": 200, "MESSAGE": msg} - self.results.register_task_result() - return - self.delete = childFabricDelete() - self.delete.rest_send = self.rest_send - self.delete.results = self.results - - fabric_names_to_delete = [] - for want in self.payloads: - fabric_names_to_delete.append(want["sourceFabric"]) - fabric_names_to_delete.append(want["destFabric"]) - try: - self.delete.fabric_names = fabric_names_to_delete - except ValueError as error: - raise ValueError(f"{error}") from error - - try: - self.delete.commit(self.payloads) - except ValueError as error: - raise ValueError(f"{error}") from error + for item in self.payloads: + if not self.verify_child_fabric_is_already_member(item): + self.results.action = self.action + self.results.result_current = {"success": True, "changed": False} + msg = "Child fabric is not a member of Parent fabric." + self.results.response_current = {"RETURN_CODE": 200, "MESSAGE": msg} + self.results.register_task_result() + else: + self.delete = childFabricDelete() + self.delete.rest_send = self.rest_send + self.delete.results = self.results + + fabric_names_to_delete = [] + for want in self.payloads: + fabric_names_to_delete.append(want["sourceFabric"]) + fabric_names_to_delete.append(want["destFabric"]) + try: + self.delete.fabric_names = fabric_names_to_delete + except ValueError as error: + raise ValueError(f"{error}") from error + + try: + self.delete.commit(item) + except ValueError as error: + raise ValueError(f"{error}") from error class Merged(childCommon): @@ -491,27 +492,27 @@ def commit(self) -> None: self.verify_parent_fab_is_MSD() self.verify_child_fab_exists_in_controller() self.verify_child_fabric_is_member_of_another_fabric() - - if self.verify_child_fabric_is_already_member(): - self.results.result_current = {"success": True, "changed": False} - msg = "Child fabric is already member of Parent fabric." - self.results.response_current = {"RETURN_CODE": 200, "MESSAGE": msg} - self.results.register_task_result() - return - - self.add.rest_send = self.rest_send - fabric_names_to_add = [] - for want in self.payloads: - fabric_names_to_add.append(want["sourceFabric"]) - try: - self.add.fabric_names = fabric_names_to_add - except ValueError as error: - raise ValueError(f"{error}") from error - - try: - self.add.commit(self.payloads) - except ValueError as error: - raise ValueError(f"{error}") from error + for item in self.payloads: + if self.verify_child_fabric_is_already_member(item): + self.results.action = self.action + self.results.result_current = {"success": True, "changed": False} + msg = "Child fabric is already member of Parent fabric." + self.results.response_current = {"RETURN_CODE": 200, "MESSAGE": msg} + self.results.register_task_result() + else: + self.add.rest_send = self.rest_send + fabric_names_to_add = [] + for want in self.payloads: + fabric_names_to_add.append(want["sourceFabric"]) + try: + self.add.fabric_names = fabric_names_to_add + except ValueError as error: + raise ValueError(f"{error}") from error + + try: + self.add.commit(item) + except ValueError as error: + raise ValueError(f"{error}") from error class Query(childCommon): diff --git a/tests/integration/targets/dcnm_child_fabric/defaults/main.yaml b/tests/integration/targets/dcnm_child_fabric/defaults/main.yaml new file mode 100644 index 000000000..5f709c5aa --- /dev/null +++ b/tests/integration/targets/dcnm_child_fabric/defaults/main.yaml @@ -0,0 +1,2 @@ +--- +testcase: "*" diff --git a/tests/integration/targets/dcnm_child_fabric/meta/main.yaml b/tests/integration/targets/dcnm_child_fabric/meta/main.yaml new file mode 100644 index 000000000..32cf5dda7 --- /dev/null +++ b/tests/integration/targets/dcnm_child_fabric/meta/main.yaml @@ -0,0 +1 @@ +dependencies: [] diff --git a/tests/integration/targets/dcnm_child_fabric/tasks/dcnm.yaml b/tests/integration/targets/dcnm_child_fabric/tasks/dcnm.yaml new file mode 100644 index 000000000..e419fc865 --- /dev/null +++ b/tests/integration/targets/dcnm_child_fabric/tasks/dcnm.yaml @@ -0,0 +1,20 @@ +--- +- name: collect dcnm test cases + find: + paths: "{{ role_path }}/tests" + patterns: "{{ testcase }}.yaml" + connection: local + register: dcnm_cases + +- set_fact: + test_cases: + files: "{{ dcnm_cases.files }}" + +- name: set test_items + set_fact: test_items="{{ test_cases.files | map(attribute='path') | list }}" + +- name: run test cases (connection=httpapi) + include_tasks: "{{ test_case_to_run }}" + with_items: "{{ test_items }}" + loop_control: + loop_var: test_case_to_run diff --git a/tests/integration/targets/dcnm_child_fabric/tasks/main.yaml b/tests/integration/targets/dcnm_child_fabric/tasks/main.yaml new file mode 100644 index 000000000..fbcfa5803 --- /dev/null +++ b/tests/integration/targets/dcnm_child_fabric/tasks/main.yaml @@ -0,0 +1,2 @@ +--- +- { include_tasks: dcnm.yaml, tags: ['dcnm'] } diff --git a/tests/integration/targets/dcnm_child_fabric/tests/dcnm_child_fabric.yaml b/tests/integration/targets/dcnm_child_fabric/tests/dcnm_child_fabric.yaml new file mode 100644 index 000000000..9ac356bf1 --- /dev/null +++ b/tests/integration/targets/dcnm_child_fabric/tests/dcnm_child_fabric.yaml @@ -0,0 +1,1453 @@ +################################################################################ +# RUNTIME +################################################################################ +# Recent run times (MM:SS.ms): +# 00:12.62 +################################################################################ +# DESCRIPTION - CHILD FABRIC TEST +# +# Test child Fabric configurations and verify results. +# - config-save and config-deploy not tested here. +################################################################################ +# STEPS +################################################################################ +# SETUP +################################################################################ +# 1. The following fabrics must be empty on the controller +# See vars: section in cisco/dcnm/playbooks/dcnm_tests.yaml +# - fabric_name_1 +# - fabric_type_1 # VXLAN_EVPN +# - fabric_name_2 +# - fabric_type_2 # VXLAN_EVPN_MSD +# - fabric_name_3 +# - fabric_type_3 # LAN_CLASSIC +# 2. Delete fabrics under test, if they exist +# - fabric_name_1 +# - fabric_name_2 +# - fabric_name_3 +################################################################################ +# TEST +################################################################################ +# 3. Create fabrics and verify result +# - msd_fabric_name_1 +# - child_fabric_name_11 +# - child_fabric_name_12 +# - child_fabric_name_13 +# - msd_fabric_name_2 +# - child_fabric_name_21 +# - child_fabric_name_22 +# - child_fabric_name_23 +# 4. Merge additional configs into fabric_1 and fabric_2 and verify result +# 5. Merge additional config into fabric_3 and verify result +################################################################################ +# CLEANUP +################################################################################ +# 6. Delete fabrics under test +# - msd_fabric_name_1 +# - child_fabric_name_11 +# - child_fabric_name_12 +# - child_fabric_name_13 +# - msd_fabric_name_2 +# - child_fabric_name_21 +# - child_fabric_name_22 +# - child_fabric_name_23 +################################################################################ +# REQUIREMENTS +################################################################################ +# Inventory: +# ./playbooks/roles/dcnm_fabric/dcnm_hosts.yaml +# Playbook: +# ./playbooks/roles/dcnm_fabric/dcnm_tests.yaml +# Roles: +# ./tests/integration/targets/dcnm_fabric/tests/*.yaml +# +# Example vars: +# +# vars: +# testcase: dcnm_child_fabric_merged +# msd_fabric_name_1: MSD_1 +# MSD_fabric_type: VXLAN_EVPN_MSD +# child_fabric_name_11: child_11 +# child_fabric_type: VXLAN_EVPN +# child_fabric_name_12: child_12 +# child_fabric_type: VXLAN_EVPN +# child_fabric_name_13: child_13 +# child_fabric_type_2: ISN +# msd_fabric_name_2: MSD_2 +# child_fabric_name_21: child_21 +# child_fabric_type: VXLAN_EVPN +# child_fabric_name_22: child_22 +# child_fabric_type: VXLAN_EVPN +# child_fabric_name_23: child_23 +# child_fabric_type_2: ISN +################################################################################ +# MERGED - SETUP - Delete fabrics from Controller +################################################################################ +- name: MERGED - SETUP - Delete fabrics + cisco.dcnm.dcnm_fabric: + state: deleted + config: + - FABRIC_NAME: "{{ msd_fabric_name_1 }}" + - FABRIC_NAME: "{{ child_fabric_name_11 }}" + - FABRIC_NAME: "{{ child_fabric_name_12 }}" + - FABRIC_NAME: "{{ child_fabric_name_13 }}" + - FABRIC_NAME: "{{ msd_fabric_name_2 }}" + - FABRIC_NAME: "{{ child_fabric_name_21 }}" + - FABRIC_NAME: "{{ child_fabric_name_22 }}" + - FABRIC_NAME: "{{ child_fabric_name_23 }}" + register: result + ignore_errors: true +- debug: + var: result +################################################################################ +# MERGED - TEST - Create MSD fabric and child Fabrics +################################################################################ +# Expected result +## changed: [10.78.210.227] => { +# "changed": true, +# "diff": [ +# { +# "FABRIC_NAME": "msd_3", +# "sequence_number": 1 +# }, +# { +# "BGP_AS": 72, +# "FABRIC_NAME": "child_11", +# "sequence_number": 1 +# }, +# { +# "BGP_AS": 144, +# "FABRIC_NAME": "child_12", +# "sequence_number": 2 +# }, + +# ], +# "invocation": { +# "module_args": { +# "config": [ +# { +# "FABRIC_NAME": "msd_3", +# "FABRIC_TYPE": "VXLAN_EVPN_MSD" +# }, +# { +# "BGP_AS": 72, +# "FABRIC_NAME": "child_11", +# "FABRIC_TYPE": "VXLAN_EVPN" +# }, +# { +# "BGP_AS": 144, +# "FABRIC_NAME": "child_12", +# "FABRIC_TYPE": "VXLAN_EVPN" +# } +# "skip_validation": false, +# "state": "merged" +# } +# }, +# "metadata": [ +# { +# "action": "fabric_create", +# "check_mode": false, +# "sequence_number": 1, +# "state": "merged" +# }, +# { +# "action": "fabric_create", +# "check_mode": false, +# "sequence_number": 2, +# "state": "merged" +# }, +# { +# "action": "fabric_create", +# "check_mode": false, +# "sequence_number": 3, +# "state": "merged" +# } +# ], +# "response": [ +# { +# "DATA": { +# "deviceType": "n9k", +# "fabricId": "FABRIC-25", +# "fabricName": "msd_3", +# "fabricTechnology": "VXLANFabric", +# "fabricTechnologyFriendly": "VXLAN EVPN", +# "fabricType": "MFD", +# "fabricTypeFriendly": "VXLAN EVPN Multi-Site", +# "id": 25, +# "networkExtensionTemplate": "Default_Network_Extension_Universal", +# "networkTemplate": "Default_Network_Universal", +# "nvPairs": { +# ... +# }, +# "provisionMode": "DCNMTopDown", +# "replicationMode": "IngressReplication", +# "templateFabricType": "", +# "templateName": "MSD_Fabric", +# "vrfExtensionTemplate": "Default_VRF_Extension_Universal", +# "vrfTemplate": "Default_VRF_Universal" +# }, +# "MESSAGE": "OK", +# "METHOD": "POST", +# "REQUEST_PATH": "https://10.78.210.227:443/appcenter/cisco/ndfc/api/v1/lan-fabric/rest/control/fabrics/msd_3/MSD_Fabric", +# "RETURN_CODE": 200, +# "sequence_number": 1 +# }, +# { +# "DATA": { +# "asn": "72", +# "deviceType": "n9k", +# "fabricId": "FABRIC-25", +# "fabricName": "child_11", +# "fabricTechnology": "VXLANFabric", +# "fabricTechnologyFriendly": "VXLAN EVPN", +# "fabricType": "Switch_Fabric", +# "fabricTypeFriendly": "Switch Fabric", +# "id": 25, +# "networkExtensionTemplate": "Default_Network_Extension_Universal", +# "networkTemplate": "Default_Network_Universal", +# "nvPairs": { +# ... +# }, +# "provisionMode": "DCNMTopDown", +# "replicationMode": "Multicast", +# "siteId": "", +# "templateFabricType": "", +# "templateName": "Easy_Fabric", +# "vrfExtensionTemplate": "Default_VRF_Extension_Universal", +# "vrfTemplate": "Default_VRF_Universal" +# }, +# "MESSAGE": "OK", +# "METHOD": "POST", +# "REQUEST_PATH": "https://10.78.210.227:443/appcenter/cisco/ndfc/api/v1/lan-fabric/rest/control/fabrics/child_11/Easy_Fabric", +# "RETURN_CODE": 200, +# "sequence_number": 1 +# }, +# { +# "DATA": { +# "asn": "144", +# "deviceType": "n9k", +# "fabricId": "FABRIC-26", +# "fabricName": "child_12", +# "fabricTechnology": "VXLANFabric", +# "fabricTechnologyFriendly": "VXLAN EVPN", +# "fabricType": "Switch_Fabric", +# "fabricTypeFriendly": "Switch Fabric", +# "id": 26, +# "networkExtensionTemplate": "Default_Network_Extension_Universal", +# "networkTemplate": "Default_Network_Universal", +# "nvPairs": { +# ... +# }, +# "provisionMode": "DCNMTopDown", +# "replicationMode": "Multicast", +# "siteId": "", +# "templateFabricType": "", +# "templateName": "Easy_Fabric", +# "vrfExtensionTemplate": "Default_VRF_Extension_Universal", +# "vrfTemplate": "Default_VRF_Universal" +# }, +# "MESSAGE": "OK", +# "METHOD": "POST", +# "REQUEST_PATH": "https://10.78.210.227:443/appcenter/cisco/ndfc/api/v1/lan-fabric/rest/control/fabrics/child_12/Easy_Fabric", +# "RETURN_CODE": 200, +# "sequence_number": 2 +# } +# ], +# "result": [ +# { +# "changed": true, +# "sequence_number": 1, +# "success": true +# }, +# { +# "changed": true, +# "sequence_number": 2, +# "success": true +# }, +# { +# "changed": true, +# "sequence_number": 3, +# "success": true +# } +# ] +#} +################################################################################# +- name: MERGED - TEST - Create MSD fabric and child Fabrics + cisco.dcnm.dcnm_fabric: + state: merged + config: + - FABRIC_NAME: "{{ msd_fabric_name_1 }}" + FABRIC_TYPE: "{{ MSD_fabric_type }}" + DEPLOY: true + - FABRIC_NAME: "{{ child_fabric_name_11 }}" + FABRIC_TYPE: "{{ child_fabric_type }}" + BGP_AS: 72 + DEPLOY: true + - FABRIC_NAME: "{{ child_fabric_name_12 }}" + FABRIC_TYPE: "{{ child_fabric_type }}" + BGP_AS: 144 + DEPLOY: true + - FABRIC_NAME: "{{ child_fabric_name_13 }}" + FABRIC_TYPE: "{{ child_fabric_type_2 }}" + BGP_AS: 216 + DEPLOY: true + - FABRIC_NAME: "{{ msd_fabric_name_2 }}" + FABRIC_TYPE: "{{ MSD_fabric_type }}" + DEPLOY: true + - FABRIC_NAME: "{{ child_fabric_name_21 }}" + FABRIC_TYPE: "{{ child_fabric_type }}" + BGP_AS: 361 + DEPLOY: true + - FABRIC_NAME: "{{ child_fabric_name_22 }}" + FABRIC_TYPE: "{{ child_fabric_type }}" + BGP_AS: 362 + DEPLOY: true + - FABRIC_NAME: "{{ child_fabric_name_23 }}" + FABRIC_TYPE: "{{ child_fabric_type_2 }}" + BGP_AS: 363 + DEPLOY: true + register: result +- debug: + var: result +- assert: + that: + - result.changed == true + - result.failed == false + - (result.diff | length) == 8 + - result.diff[0].FABRIC_NAME == msd_fabric_name_1 + - result.diff[0].sequence_number == 1 + - result.diff[1].FABRIC_NAME == child_fabric_name_11 + - result.diff[1].sequence_number == 2 + - result.diff[1].BGP_AS == 72 + - result.diff[2].FABRIC_NAME == child_fabric_name_12 + - result.diff[2].sequence_number == 3 + - result.diff[2].BGP_AS == 144 + - result.diff[3].FABRIC_NAME == child_fabric_name_13 + - result.diff[3].sequence_number == 4 + - result.diff[3].BGP_AS == 216 + - result.diff[4].FABRIC_NAME == msd_fabric_name_2 + - result.diff[4].sequence_number == 5 + - result.diff[5].FABRIC_NAME == child_fabric_name_21 + - result.diff[5].sequence_number == 6 + - result.diff[5].BGP_AS == 361 + - result.diff[6].FABRIC_NAME == child_fabric_name_22 + - result.diff[6].sequence_number == 7 + - result.diff[6].BGP_AS == 362 + - result.diff[7].FABRIC_NAME == child_fabric_name_23 + - result.diff[7].sequence_number == 8 + - result.diff[7].BGP_AS == 363 + - result.metadata[0].action == "fabric_create" + - result.metadata[0].check_mode == False + - result.metadata[0].sequence_number == 1 + - result.metadata[0].state == "merged" + - result.metadata[1].action == "fabric_create" + - result.metadata[1].check_mode == False + - result.metadata[1].sequence_number == 2 + - result.metadata[1].state == "merged" + - result.metadata[2].action == "fabric_create" + - result.metadata[2].check_mode == False + - result.metadata[2].sequence_number == 3 + - result.metadata[2].state == "merged" + - result.metadata[3].action == "fabric_create" + - result.metadata[3].check_mode == False + - result.metadata[3].sequence_number == 4 + - result.metadata[3].state == "merged" + - result.metadata[4].action == "fabric_create" + - result.metadata[4].check_mode == False + - result.metadata[4].sequence_number == 5 + - result.metadata[4].state == "merged" + - result.metadata[5].action == "fabric_create" + - result.metadata[5].check_mode == False + - result.metadata[5].sequence_number == 6 + - result.metadata[5].state == "merged" + - result.metadata[6].action == "fabric_create" + - result.metadata[6].check_mode == False + - result.metadata[6].sequence_number == 7 + - result.metadata[6].state == "merged" + - result.metadata[7].action == "fabric_create" + - result.metadata[7].check_mode == False + - result.metadata[7].sequence_number == 8 + - result.metadata[7].state == "merged" + - (result.response | length) == 8 + - result.response[0].sequence_number == 1 + - result.response[0].MESSAGE == "OK" + - result.response[0].METHOD == "POST" + - result.response[0].RETURN_CODE == 200 + - result.response[1].sequence_number == 2 + - result.response[1].MESSAGE == "OK" + - result.response[1].METHOD == "POST" + - result.response[1].RETURN_CODE == 200 + - result.response[2].sequence_number == 3 + - result.response[2].MESSAGE == "OK" + - result.response[2].METHOD == "POST" + - result.response[2].RETURN_CODE == 200 + - result.response[3].sequence_number == 4 + - result.response[3].MESSAGE == "OK" + - result.response[3].METHOD == "POST" + - result.response[3].RETURN_CODE == 200 + - result.response[4].sequence_number == 5 + - result.response[4].MESSAGE == "OK" + - result.response[4].METHOD == "POST" + - result.response[4].RETURN_CODE == 200 + - result.response[5].sequence_number == 6 + - result.response[5].MESSAGE == "OK" + - result.response[5].METHOD == "POST" + - result.response[5].RETURN_CODE == 200 + - result.response[6].sequence_number == 7 + - result.response[6].MESSAGE == "OK" + - result.response[6].METHOD == "POST" + - result.response[6].RETURN_CODE == 200 + - result.response[7].sequence_number == 8 + - result.response[7].MESSAGE == "OK" + - result.response[7].METHOD == "POST" + - result.response[7].RETURN_CODE == 200 +################################################################################ +# MERGED - TEST - Merge child Fabrics into MSD Fabric +################################################################################ +# Expected result +#changed: [10.78.210.227] => { +# "changed": true, +# "diff": [ +# { +# "destFabric": "msd_2", +# "sequence_number": 1, +# "sourceFabric": "child2" +# }, +# { +# "destFabric": "msd_2", +# "sequence_number": 2, +# "sourceFabric": "child1" +# } +# ], +# "invocation": { +# "module_args": { +# "config": [ +# { +# "CHILD_FABRIC_NAME": "child2", +# "FABRIC_NAME": "msd_2" +# }, +# { +# "CHILD_FABRIC_NAME": "child1", +# "FABRIC_NAME": "msd_2" +# } +# ], +# "state": "deleted" +# } +# }, +# "metadata": [ +# { +# "action": "child_fabric_delete", +# "check_mode": false, +# "sequence_number": 1, +# "state": "deleted" +# }, +# { +# "action": "child_fabric_delete", +# "check_mode": false, +# "sequence_number": 2, +# "state": "deleted" +# } +# ], +# "response": [ +# { +# "DATA": {}, +# "MESSAGE": "OK", +# "METHOD": "POST", +# "REQUEST_PATH": "https://10.78.210.227:443/appcenter/cisco/ndfc/api/v1/lan-fabric/rest/control/fabrics/msdExit", +# "RETURN_CODE": 200, +# "sequence_number": 1 +# }, +# { +# "DATA": {}, +# "MESSAGE": "OK", +# "METHOD": "POST", +# "REQUEST_PATH": "https://10.78.210.227:443/appcenter/cisco/ndfc/api/v1/lan-fabric/rest/control/fabrics/msdExit", +# "RETURN_CODE": 200, +# "sequence_number": 2 +# } +# ], +# "result": [ +# { +# "changed": true, +# "sequence_number": 1, +# "success": true +# }, +# { +# "changed": true, +# "sequence_number": 2, +# "success": true +# } +# ] +#} + +#changed: [10.78.210.227] => { +# "changed": true, +# "diff": [ +# { +# "destFabric": "msd_2", +# "sequence_number": 1, +# "sourceFabric": "child2" +# }, +# { +# "destFabric": "msd_2", +# "sequence_number": 2, +# "sourceFabric": "child1" +# } +# ], +# "invocation": { +# "module_args": { +# "config": [ +# { +# "CHILD_FABRIC_NAME": "child2", +# "FABRIC_NAME": "msd_2" +# }, +# { +# "CHILD_FABRIC_NAME": "child1", +# "FABRIC_NAME": "msd_2" +# } +# ], +# "state": "merged" +# } +# }, +# "metadata": [ +# { +# "action": "child_fabric_add", +# "check_mode": false, +# "sequence_number": 1, +# "state": "merged" +# }, +# { +# "action": "child_fabric_add", +# "check_mode": false, +# "sequence_number": 2, +# "state": "merged" +# } +# ], +# "response": [ +# { +# "DATA": {}, +# "MESSAGE": "OK", +# "METHOD": "POST", +# "REQUEST_PATH": "https://10.78.210.227:443/appcenter/cisco/ndfc/api/v1/lan-fabric/rest/control/fabrics/msdAdd", +# "RETURN_CODE": 200, +# "sequence_number": 1 +# }, +# { +# "DATA": {}, +# "MESSAGE": "OK", +# "METHOD": "POST", +# "REQUEST_PATH": "https://10.78.210.227:443/appcenter/cisco/ndfc/api/v1/lan-fabric/rest/control/fabrics/msdAdd", +# "RETURN_CODE": 200, +# "sequence_number": 2 +# } +# ], +# "result": [ +# { +# "changed": true, +# "sequence_number": 1, +# "success": true +# }, +# { +# "changed": true, +# "sequence_number": 2, +# "success": true +# } +# ] +#} + +################################################################################ +- name: MERGED - TEST - Merge child fabrics into MSD Fabric + cisco.dcnm.dcnm_child_fabric: + state: merged + config: + - FABRIC_NAME: "{{ msd_fabric_name_1 }}" + CHILD_FABRIC_NAME: "{{ child_fabric_name_11 }}" + - FABRIC_NAME: "{{ msd_fabric_name_1 }}" + CHILD_FABRIC_NAME: "{{ child_fabric_name_12 }}" + - FABRIC_NAME: "{{ msd_fabric_name_2 }}" + CHILD_FABRIC_NAME: "{{ child_fabric_name_21 }}" + - FABRIC_NAME: "{{ msd_fabric_name_2 }}" + CHILD_FABRIC_NAME: "{{ child_fabric_name_22 }}" + register: result +- debug: + var: result +- assert: + that: + - result.changed == true + - result.failed == false + - (result.diff | length) == 4 + - result.diff[0].destFabric == msd_fabric_name_1 + - result.diff[0].sourceFabric == child_fabric_name_11 + - result.diff[0].sequence_number == 1 + - result.diff[1].destFabric == msd_fabric_name_1 + - result.diff[1].sourceFabric == child_fabric_name_12 + - result.diff[1].sequence_number == 2 + - result.diff[2].destFabric == msd_fabric_name_2 + - result.diff[2].sourceFabric == child_fabric_name_21 + - result.diff[2].sequence_number == 3 + - result.diff[3].destFabric == msd_fabric_name_2 + - result.diff[3].sourceFabric == child_fabric_name_22 + - result.diff[3].sequence_number == 4 + - (result.metadata | length) == 4 + - result.metadata[0].action == "child_fabric_add" + - result.metadata[0].check_mode == false + - result.metadata[0].sequence_number == 1 + - result.metadata[0].state == "merged" + - result.metadata[1].action == "child_fabric_add" + - result.metadata[1].check_mode == false + - result.metadata[1].sequence_number == 2 + - result.metadata[1].state == "merged" + - result.metadata[2].action == "child_fabric_add" + - result.metadata[2].check_mode == false + - result.metadata[2].sequence_number == 3 + - result.metadata[2].state == "merged" + - result.metadata[3].action == "child_fabric_add" + - result.metadata[3].check_mode == false + - result.metadata[3].sequence_number == 4 + - result.metadata[3].state == "merged" + - (result.response | length) == 4 + - result.response[0].sequence_number == 1 + - result.response[0].MESSAGE == "OK" + - result.response[0].METHOD == "POST" + - result.response[0].RETURN_CODE == 200 + - result.response[1].sequence_number == 2 + - result.response[1].MESSAGE == "OK" + - result.response[1].METHOD == "POST" + - result.response[1].RETURN_CODE == 200 + - result.response[2].sequence_number == 3 + - result.response[2].MESSAGE == "OK" + - result.response[2].METHOD == "POST" + - result.response[2].RETURN_CODE == 200 + - result.response[3].sequence_number == 4 + - result.response[3].MESSAGE == "OK" + - result.response[3].METHOD == "POST" + - result.response[3].RETURN_CODE == 200 + - (result.result | length) == 4 + - result.result[0].changed == true + - result.result[0].success == true + - result.result[0].sequence_number == 1 + - result.result[1].changed == true + - result.result[1].success == true + - result.result[1].sequence_number == 2 + - result.result[2].changed == true + - result.result[2].success == true + - result.result[2].sequence_number == 3 + - result.result[3].changed == true + - result.result[3].success == true + - result.result[3].sequence_number == 4 + +######################################################################################### +# - name: MERGED - TEST - Merge additional fabric child_fabric_name_13 into MSD Fabric +######################################################################################### +#Expected result: +#ok: [10.78.210.227] => { +# "result": { +# "changed": true, +# "diff": [ +# { +# "destFabric": "MSD_5", +# "sequence_number": 1, +# "sourceFabric": "child_13" +# } +# ], +# "failed": false, +# "metadata": [ +# { +# "action": "child_fabric_add", +# "check_mode": false, +# "sequence_number": 1, +# "state": "merged" +# } +# ], +# "response": [ +# { +# "DATA": {}, +# "MESSAGE": "OK", +# "METHOD": "POST", +# "REQUEST_PATH": "https://10.78.210.227:443/appcenter/cisco/ndfc/api/v1/lan-fabric/rest/control/fabrics/msdAdd", +# "RETURN_CODE": 200, +# "sequence_number": 1 +# } +# ], +# "result": [ +# { +# "changed": true, +# "sequence_number": 1, +# "success": true +# } +# ] +# } +#} +####################################################################################### +- name: MERGED - TEST - Merge additional fabric child_fabric_name_13 into MSD Fabric + cisco.dcnm.dcnm_child_fabric: &merge_child_fabric + state: merged + config: + - FABRIC_NAME: "{{ msd_fabric_name_1 }}" + CHILD_FABRIC_NAME: "{{ child_fabric_name_13 }}" + - FABRIC_NAME: "{{ msd_fabric_name_2 }}" + CHILD_FABRIC_NAME: "{{ child_fabric_name_23 }}" + register: result +- debug: + var: result +- assert: + that: + - result.changed == true + - result.failed == false + - (result.diff | length) == 2 + - result.diff[0].destFabric == msd_fabric_name_1 + - result.diff[0].sourceFabric == child_fabric_name_13 + - result.diff[0].sequence_number == 1 + - result.diff[1].destFabric == msd_fabric_name_2 + - result.diff[1].sourceFabric == child_fabric_name_23 + - result.diff[1].sequence_number == 2 + - (result.metadata | length) == 2 + - result.metadata[0].action == "child_fabric_add" + - result.metadata[0].check_mode == false + - result.metadata[0].sequence_number == 1 + - result.metadata[0].state == "merged" + - result.metadata[1].action == "child_fabric_add" + - result.metadata[1].check_mode == false + - result.metadata[1].sequence_number == 2 + - result.metadata[1].state == "merged" + - (result.response | length) == 2 + - result.response[0].sequence_number == 1 + - result.response[0].MESSAGE == "OK" + - result.response[0].METHOD == "POST" + - result.response[0].RETURN_CODE == 200 + - result.response[1].sequence_number == 2 + - result.response[1].MESSAGE == "OK" + - result.response[1].METHOD == "POST" + - result.response[1].RETURN_CODE == 200 +################################################################################################### +#-name QUERY - TEST - Query child fabrics of MSD Fabric +################################################################################################### +- name: QUERY - TEST - Query child fabrics of MSD Fabric + cisco.dcnm.dcnm_child_fabric: + state: query + config: + - FABRIC_NAME: "{{ msd_fabric_name_1 }}" + - FABRIC_NAME: "{{ msd_fabric_name_2 }}" + register: result +- debug: + var: result +- assert: + that: + - result.changed == false + - result.failed == false + - (result.diff | length) == 1 + - child_fabric_name_11 in result.diff[0] + - child_fabric_name_12 in result.diff[0] + - child_fabric_name_13 in result.diff[0] + - child_fabric_name_21 in result.diff[0] + - child_fabric_name_22 in result.diff[0] + - child_fabric_name_23 in result.diff[0] + - result.diff[0].sequence_number == 1 + - (result.metadata | length) == 1 + - result.metadata[0].action == "child_fabric_query" + - result.metadata[0].check_mode == false + - result.metadata[0].sequence_number == 1 + - result.metadata[0].state == "query" + - (result.response | length) == 1 + - result.response[0].MESSAGE == "OK" + - result.response[0].METHOD == "GET" + - result.response[0].RETURN_CODE == 200 + - result.response[0].sequence_number == 1 + - result.result[0].found == true + - result.result[0].sequence_number == 1 + - result.result[0].success == true +#################################################################################################### +# MERGED - TEST - Merge additional 3rd child fabric into MSD Fabric - idempotence +##################################################################################################### +## Expected result +#ok: [10.78.210.227] => { +# "changed": false, +# "diff": [ +# { +# "sequence_number": 1 +# }, +# { +# "sequence_number": 2 +# } +# ], +# "invocation": { +# "module_args": { +# "config": [ +# { +# "CHILD_FABRIC_NAME": "child2", +# "FABRIC_NAME": "msd_2" +# }, +# { +# "CHILD_FABRIC_NAME": "child1", +# "FABRIC_NAME": "msd_2" +# } +# ], +# "state": "merged" +# } +# }, +# "metadata": [ +# { +# "action": "child_fabric_add", +# "check_mode": false, +# "sequence_number": 1, +# "state": "merged" +# }, +# { +# "action": "child_fabric_add", +# "check_mode": false, +# "sequence_number": 2, +# "state": "merged" +# } +# ], +# "response": [ +# { +# "MESSAGE": "Child fabric is already member of Parent fabric.", +# "RETURN_CODE": 200, +# "sequence_number": 1 +# }, +# { +# "MESSAGE": "Child fabric is already member of Parent fabric.", +# "RETURN_CODE": 200, +# "sequence_number": 2 +# } +# ], +# "result": [ +# { +# "changed": false, +# "sequence_number": 1, +# "success": true +# }, +# { +# "changed": false, +# "sequence_number": 2, +# "success": true +# } +# ] +#} +################################################################################# +- name: MERGED - TEST - Merge additional config into MSD fabric - idempotence + cisco.dcnm.dcnm_child_fabric: *merge_child_fabric + register: result +- debug: + var: result +- assert: + that: + - result.changed == false + - result.failed == false + - (result.diff | length) == 2 + - result.diff[0].sequence_number == 1 + - result.diff[1].sequence_number == 2 + - (result.metadata | length) == 2 + - result.metadata[0].action == "child_fabric_add" + - result.metadata[0].check_mode == False + - result.metadata[0].sequence_number == 1 + - result.metadata[0].state == "merged" + - result.metadata[1].action == "child_fabric_add" + - result.metadata[1].check_mode == False + - result.metadata[1].sequence_number == 2 + - result.metadata[1].state == "merged" + - (result.response | length) == 2 + - result.response[0].sequence_number == 1 + - result.response[0].MESSAGE == "Child fabric is already member of Parent fabric." + - result.response[0].RETURN_CODE == 200 + - result.response[1].sequence_number == 2 + - result.response[1].MESSAGE == "Child fabric is already member of Parent fabric." + - result.response[1].RETURN_CODE == 200 + - (result.result | length) == 2 + - result.result[0].changed == false + - result.result[0].success == true + - result.result[0].sequence_number == 1 + - result.result[1].changed == false + - result.result[1].success == true + - result.result[1].sequence_number == 2 +################################################################################ +# MERGED - TEST - Delete child Fabrics from MSD Fabric +################################################################################ +# Expected result +#changed: [10.78.210.227] => { +# "changed": true, +# "diff": [ +# { +# "destFabric": "msd_2", +# "sequence_number": 1, +# "sourceFabric": "child2" +# }, +# { +# "destFabric": "msd_2", +# "sequence_number": 2, +# "sourceFabric": "child1" +# } +# ], +# "invocation": { +# "module_args": { +# "config": [ +# { +# "CHILD_FABRIC_NAME": "child2", +# "FABRIC_NAME": "msd_2" +# }, +# { +# "CHILD_FABRIC_NAME": "child1", +# "FABRIC_NAME": "msd_2" +# } +# ], +# "state": "deleted" +# } +# }, +# "metadata": [ +# { +# "action": "child_fabric_delete", +# "check_mode": false, +# "sequence_number": 1, +# "state": "deleted" +# }, +# { +# "action": "child_fabric_delete", +# "check_mode": false, +# "sequence_number": 2, +# "state": "deleted" +# } +# ], +# "response": [ +# { +# "DATA": {}, +# "MESSAGE": "OK", +# "METHOD": "POST", +# "REQUEST_PATH": "https://10.78.210.227:443/appcenter/cisco/ndfc/api/v1/lan-fabric/rest/control/fabrics/msdExit", +# "RETURN_CODE": 200, +# "sequence_number": 1 +# }, +# { +# "DATA": {}, +# "MESSAGE": "OK", +# "METHOD": "POST", +# "REQUEST_PATH": "https://10.78.210.227:443/appcenter/cisco/ndfc/api/v1/lan-fabric/rest/control/fabrics/msdExit", +# "RETURN_CODE": 200, +# "sequence_number": 2 +# } +# ], +# "result": [ +# { +# "changed": true, +# "sequence_number": 1, +# "success": true +# }, +# { +# "changed": true, +# "sequence_number": 2, +# "success": true +# } +# ] +#} +################################################################################ +- name: DELETED - SETUP - Delete child fabrics from MSD fabric + cisco.dcnm.dcnm_child_fabric: &delete_child_fabric + state: deleted + config: + - FABRIC_NAME: "{{ msd_fabric_name_1 }}" + CHILD_FABRIC_NAME: "{{ child_fabric_name_11 }}" + - FABRIC_NAME: "{{ msd_fabric_name_1 }}" + CHILD_FABRIC_NAME: "{{ child_fabric_name_12 }}" + - FABRIC_NAME: "{{ msd_fabric_name_1 }}" + CHILD_FABRIC_NAME: "{{ child_fabric_name_13 }}" + - FABRIC_NAME: "{{ msd_fabric_name_2 }}" + CHILD_FABRIC_NAME: "{{ child_fabric_name_21 }}" + - FABRIC_NAME: "{{ msd_fabric_name_2 }}" + CHILD_FABRIC_NAME: "{{ child_fabric_name_22 }}" + - FABRIC_NAME: "{{ msd_fabric_name_2 }}" + CHILD_FABRIC_NAME: "{{ child_fabric_name_23 }}" + register: result +- debug: + var: result +- assert: + that: + - result.changed == true + - result.failed == false + - (result.diff | length) == 6 + - result.diff[0].destFabric == msd_fabric_name_1 + - result.diff[0].sourceFabric == child_fabric_name_11 + - result.diff[0].sequence_number == 1 + - result.diff[1].destFabric == msd_fabric_name_1 + - result.diff[1].sourceFabric == child_fabric_name_12 + - result.diff[1].sequence_number == 2 + - result.diff[2].destFabric == msd_fabric_name_1 + - result.diff[2].sourceFabric == child_fabric_name_13 + - result.diff[2].sequence_number == 3 + - result.diff[3].destFabric == msd_fabric_name_2 + - result.diff[3].sourceFabric == child_fabric_name_21 + - result.diff[3].sequence_number == 4 + - result.diff[4].destFabric == msd_fabric_name_2 + - result.diff[4].sourceFabric == child_fabric_name_22 + - result.diff[4].sequence_number == 5 + - result.diff[5].destFabric == msd_fabric_name_2 + - result.diff[5].sourceFabric == child_fabric_name_23 + - result.diff[5].sequence_number == 6 + - (result.metadata | length) == 6 + - result.metadata[0].action == "child_fabric_delete" + - result.metadata[0].check_mode == false + - result.metadata[0].sequence_number == 1 + - result.metadata[0].state == "deleted" + - result.metadata[1].action == "child_fabric_delete" + - result.metadata[1].check_mode == false + - result.metadata[1].sequence_number == 2 + - result.metadata[1].state == "deleted" + - result.metadata[2].action == "child_fabric_delete" + - result.metadata[2].check_mode == false + - result.metadata[2].sequence_number == 3 + - result.metadata[2].state == "deleted" + - result.metadata[3].action == "child_fabric_delete" + - result.metadata[3].check_mode == false + - result.metadata[3].sequence_number == 4 + - result.metadata[3].state == "deleted" + - result.metadata[4].action == "child_fabric_delete" + - result.metadata[4].check_mode == false + - result.metadata[4].sequence_number == 5 + - result.metadata[4].state == "deleted" + - result.metadata[5].action == "child_fabric_delete" + - result.metadata[5].check_mode == false + - result.metadata[5].sequence_number == 6 + - result.metadata[5].state == "deleted" + - (result.response | length) == 6 + - result.response[0].sequence_number == 1 + - result.response[0].MESSAGE == "OK" + - result.response[0].METHOD == "POST" + - result.response[0].RETURN_CODE == 200 + - result.response[1].sequence_number == 2 + - result.response[1].MESSAGE == "OK" + - result.response[1].METHOD == "POST" + - result.response[1].RETURN_CODE == 200 + - result.response[2].sequence_number == 3 + - result.response[2].MESSAGE == "OK" + - result.response[2].METHOD == "POST" + - result.response[2].RETURN_CODE == 200 + - result.response[3].sequence_number == 4 + - result.response[3].MESSAGE == "OK" + - result.response[3].METHOD == "POST" + - result.response[3].RETURN_CODE == 200 + - result.response[4].sequence_number == 5 + - result.response[4].MESSAGE == "OK" + - result.response[4].METHOD == "POST" + - result.response[4].RETURN_CODE == 200 + - result.response[5].sequence_number == 6 + - result.response[5].MESSAGE == "OK" + - result.response[5].METHOD == "POST" + - result.response[5].RETURN_CODE == 200 + - (result.result | length) == 6 + - result.result[0].changed == true + - result.result[0].success == true + - result.result[0].sequence_number == 1 + - result.result[1].changed == true + - result.result[1].success == true + - result.result[1].sequence_number == 2 + - result.result[2].changed == true + - result.result[2].success == true + - result.result[2].sequence_number == 3 + - result.result[3].changed == true + - result.result[3].success == true + - result.result[3].sequence_number == 4 + - result.result[4].changed == true + - result.result[4].success == true + - result.result[4].sequence_number == 5 + - result.result[5].changed == true + - result.result[5].success == true + - result.result[5].sequence_number == 6 +################################################################################# +#- name: DELETED - TEST - Delete child Fabrics From MSD fabric - idempotence +################################################################################# +#expected result: +#ok: [10.78.210.227] => { +# "result": { +# "changed": false, +# "diff": [ +# { +# "sequence_number": 1 +# }, +# { +# "sequence_number": 2 +# }, +# { +# "sequence_number": 3 +# } +# ], +# "failed": false, +# "metadata": [ +# { +# "action": "child_fabric_delete", +# "check_mode": false, +# "sequence_number": 1, +# "state": "deleted" +# }, +# { +# "action": "child_fabric_delete", +# "check_mode": false, +# "sequence_number": 2, +# "state": "deleted" +# }, +# { +# "action": "child_fabric_delete", +# "check_mode": false, +# "sequence_number": 3, +# "state": "deleted" +# } +# ], +# "response": [ +# { +# "MESSAGE": "Child fabric is not a member of Parent fabric.", +# "RETURN_CODE": 200, +# "sequence_number": 1 +# }, +# { +# "MESSAGE": "Child fabric is not a member of Parent fabric.", +# "RETURN_CODE": 200, +# "sequence_number": 2 +# }, +# { +# "MESSAGE": "Child fabric is not a member of Parent fabric.", +# "RETURN_CODE": 200, +# "sequence_number": 3 +# } +# ], +# "result": [ +# { +# "changed": false, +# "sequence_number": 1, +# "success": true +# }, +# { +# "changed": false, +# "sequence_number": 2, +# "success": true +# }, +# { +# "changed": false, +# "sequence_number": 3, +# "success": true +# } +# ] +# } +#} +############################################################################################### +- name: MERGED - TEST - Delete child Fabrics into MSD fabric - idempotence + cisco.dcnm.dcnm_child_fabric: *delete_child_fabric + register: result +- debug: + var: result +- assert: + that: + - result.changed == false + - result.failed == false + - (result.diff | length) == 6 + - result.diff[0].sequence_number == 1 + - result.diff[1].sequence_number == 2 + - result.diff[2].sequence_number == 3 + - result.diff[3].sequence_number == 4 + - result.diff[4].sequence_number == 5 + - result.diff[5].sequence_number == 6 + - (result.metadata | length) == 6 + - result.metadata[0].action == "child_fabric_delete" + - result.metadata[0].check_mode == False + - result.metadata[0].sequence_number == 1 + - result.metadata[0].state == "deleted" + - result.metadata[1].action == "child_fabric_delete" + - result.metadata[1].check_mode == False + - result.metadata[1].sequence_number == 2 + - result.metadata[1].state == "deleted" + - result.metadata[2].action == "child_fabric_delete" + - result.metadata[2].check_mode == False + - result.metadata[2].sequence_number == 3 + - result.metadata[2].state == "deleted" + - result.metadata[3].action == "child_fabric_delete" + - result.metadata[3].check_mode == False + - result.metadata[3].sequence_number == 4 + - result.metadata[3].state == "deleted" + - result.metadata[4].action == "child_fabric_delete" + - result.metadata[4].check_mode == False + - result.metadata[4].sequence_number == 5 + - result.metadata[4].state == "deleted" + - result.metadata[5].action == "child_fabric_delete" + - result.metadata[5].check_mode == False + - result.metadata[5].sequence_number == 6 + - result.metadata[5].state == "deleted" + - (result.response | length) == 6 + - result.response[0].sequence_number == 1 + - result.response[0].MESSAGE == "Child fabric is not a member of Parent fabric." + - result.response[0].RETURN_CODE == 200 + - result.response[1].sequence_number == 2 + - result.response[1].MESSAGE == "Child fabric is not a member of Parent fabric." + - result.response[1].RETURN_CODE == 200 + - result.response[2].sequence_number == 3 + - result.response[2].MESSAGE == "Child fabric is not a member of Parent fabric." + - result.response[2].RETURN_CODE == 200 + - result.response[3].sequence_number == 4 + - result.response[3].MESSAGE == "Child fabric is not a member of Parent fabric." + - result.response[3].RETURN_CODE == 200 + - result.response[4].sequence_number == 5 + - result.response[4].MESSAGE == "Child fabric is not a member of Parent fabric." + - result.response[4].RETURN_CODE == 200 + - result.response[5].sequence_number == 6 + - result.response[5].MESSAGE == "Child fabric is not a member of Parent fabric." + - result.response[5].RETURN_CODE == 200 + - (result.result | length) == 6 + - result.result[0].changed == false + - result.result[0].success == true + - result.result[0].sequence_number == 1 + - result.result[1].changed == false + - result.result[1].success == true + - result.result[1].sequence_number == 2 + - result.result[2].changed == false + - result.result[2].success == true + - result.result[2].sequence_number == 3 + - result.result[3].changed == false + - result.result[3].success == true + - result.result[3].sequence_number == 4 + - result.result[4].changed == false + - result.result[4].success == true + - result.result[4].sequence_number == 5 + - result.result[5].changed == false + - result.result[5].success == true + - result.result[5].sequence_number == 6 +################################################################################ +# MERGED - CLEANUP - Delete the fabrics +################################################################################ +# Expected result +#ok: [10.78.210.227] => { +# "result": { +# "changed": true, +# "diff": [ +# { +# "FABRIC_NAME": "MSD_5", +# "sequence_number": 1 +# }, +# { +# "FABRIC_NAME": "child_11", +# "sequence_number": 2 +# }, +# { +# "FABRIC_NAME": "child_12", +# "sequence_number": 3 +# }, +# { +# "FABRIC_NAME": "child_13", +# "sequence_number": 4 +# } +# ], +# "failed": false, +# "metadata": [ +# { +# "action": "fabric_delete", +# "check_mode": false, +# "sequence_number": 1, +# "state": "deleted" +# }, +# { +# "action": "fabric_delete", +# "check_mode": false, +# "sequence_number": 2, +# "state": "deleted" +# }, +# { +# "action": "fabric_delete", +# "check_mode": false, +# "sequence_number": 3, +# "state": "deleted" +# }, +# { +# "action": "fabric_delete", +# "check_mode": false, +# "sequence_number": 4, +# "state": "deleted" +# } +# ], +# "response": [ +# { +# "DATA": "Invalid JSON response: Fabric 'MSD_5' is deleted successfully!", +# "MESSAGE": "OK", +# "METHOD": "DELETE", +# "REQUEST_PATH": "https://10.78.210.227:443/appcenter/cisco/ndfc/api/v1/lan-fabric/rest/control/fabrics/MSD_5", +# "RETURN_CODE": 200, +# "sequence_number": 1 +# }, +# { +# "DATA": "Invalid JSON response: Fabric 'child_11' is deleted successfully!", +# "MESSAGE": "OK", +# "METHOD": "DELETE", +# "REQUEST_PATH": "https://10.78.210.227:443/appcenter/cisco/ndfc/api/v1/lan-fabric/rest/control/fabrics/child_11", +# "RETURN_CODE": 200, +# "sequence_number": 2 +# }, +# { +# "DATA": "Invalid JSON response: Fabric 'child_12' is deleted successfully!", +# "MESSAGE": "OK", +# "METHOD": "DELETE", +# "REQUEST_PATH": "https://10.78.210.227:443/appcenter/cisco/ndfc/api/v1/lan-fabric/rest/control/fabrics/child_12", +# "RETURN_CODE": 200, +# "sequence_number": 3 +# }, +# { +# "DATA": "Invalid JSON response: Fabric 'child_13' is deleted successfully!", +# "MESSAGE": "OK", +# "METHOD": "DELETE", +# "REQUEST_PATH": "https://10.78.210.227:443/appcenter/cisco/ndfc/api/v1/lan-fabric/rest/control/fabrics/child_13", +# "RETURN_CODE": 200, +# "sequence_number": 4 +# } +# ], +# "result": [ +# { +# "changed": true, +# "sequence_number": 1, +# "success": true +# }, +# { +# "changed": true, +# "sequence_number": 2, +# "success": true +# }, +# { +# "changed": true, +# "sequence_number": 3, +# "success": true +# }, +# { +# "changed": true, +# "sequence_number": 4, +# "success": true +# } +# ] +# } +#} +################################################################################ +- name: MERGED - CLEANUP - Delete the fabrics + cisco.dcnm.dcnm_fabric: + state: deleted + config: + - FABRIC_NAME: "{{ msd_fabric_name_1 }}" + - FABRIC_NAME: "{{ child_fabric_name_11 }}" + - FABRIC_NAME: "{{ child_fabric_name_12 }}" + - FABRIC_NAME: "{{ child_fabric_name_13 }}" + - FABRIC_NAME: "{{ msd_fabric_name_2 }}" + - FABRIC_NAME: "{{ child_fabric_name_21 }}" + - FABRIC_NAME: "{{ child_fabric_name_22 }}" + - FABRIC_NAME: "{{ child_fabric_name_23 }}" + register: result +- debug: + var: result +- assert: + that: + - result.changed == true + - result.failed == false + - (result.diff | length) == 8 + - result.diff[0].FABRIC_NAME == msd_fabric_name_1 + - result.diff[0].sequence_number == 1 + - result.diff[1].FABRIC_NAME == child_fabric_name_11 + - result.diff[1].sequence_number == 2 + - result.diff[2].FABRIC_NAME == child_fabric_name_12 + - result.diff[2].sequence_number == 3 + - result.diff[3].FABRIC_NAME == child_fabric_name_13 + - result.diff[3].sequence_number == 4 + - result.diff[4].FABRIC_NAME == msd_fabric_name_2 + - result.diff[4].sequence_number == 5 + - result.diff[5].FABRIC_NAME == child_fabric_name_21 + - result.diff[5].sequence_number == 6 + - result.diff[6].FABRIC_NAME == child_fabric_name_22 + - result.diff[6].sequence_number == 7 + - result.diff[7].FABRIC_NAME == child_fabric_name_23 + - result.diff[7].sequence_number == 8 + - (result.metadata | length) == 8 + - result.metadata[0].action == "fabric_delete" + - result.metadata[0].check_mode == False + - result.metadata[0].sequence_number == 1 + - result.metadata[0].state == "deleted" + - result.metadata[1].action == "fabric_delete" + - result.metadata[1].check_mode == False + - result.metadata[1].sequence_number == 2 + - result.metadata[1].state == "deleted" + - result.metadata[2].action == "fabric_delete" + - result.metadata[2].check_mode == False + - result.metadata[2].sequence_number == 3 + - result.metadata[2].state == "deleted" + - result.metadata[3].action == "fabric_delete" + - result.metadata[3].check_mode == False + - result.metadata[3].sequence_number == 4 + - result.metadata[3].state == "deleted" + - result.metadata[4].action == "fabric_delete" + - result.metadata[4].check_mode == False + - result.metadata[4].sequence_number == 5 + - result.metadata[4].state == "deleted" + - result.metadata[5].action == "fabric_delete" + - result.metadata[5].check_mode == False + - result.metadata[5].sequence_number == 6 + - result.metadata[5].state == "deleted" + - result.metadata[6].action == "fabric_delete" + - result.metadata[6].check_mode == False + - result.metadata[6].sequence_number == 7 + - result.metadata[6].state == "deleted" + - result.metadata[7].action == "fabric_delete" + - result.metadata[7].check_mode == False + - result.metadata[7].sequence_number == 8 + - result.metadata[7].state == "deleted" + - (result.response | length) == 8 + - result.response[0].DATA is match '.*deleted successfully.*' + - result.response[0].MESSAGE == "OK" + - result.response[0].METHOD == "DELETE" + - result.response[0].RETURN_CODE == 200 + - result.response[0].sequence_number == 1 + - result.response[1].DATA is match '.*deleted successfully.*' + - result.response[1].MESSAGE == "OK" + - result.response[1].METHOD == "DELETE" + - result.response[1].RETURN_CODE == 200 + - result.response[1].sequence_number == 2 + - result.response[2].DATA is match '.*deleted successfully.*' + - result.response[2].MESSAGE == "OK" + - result.response[2].METHOD == "DELETE" + - result.response[2].RETURN_CODE == 200 + - result.response[2].sequence_number == 3 + - result.response[3].DATA is match '.*deleted successfully.*' + - result.response[3].MESSAGE == "OK" + - result.response[3].METHOD == "DELETE" + - result.response[3].RETURN_CODE == 200 + - result.response[3].sequence_number == 4 + - result.response[4].DATA is match '.*deleted successfully.*' + - result.response[4].MESSAGE == "OK" + - result.response[4].METHOD == "DELETE" + - result.response[4].RETURN_CODE == 200 + - result.response[4].sequence_number == 5 + - result.response[5].DATA is match '.*deleted successfully.*' + - result.response[5].MESSAGE == "OK" + - result.response[5].METHOD == "DELETE" + - result.response[5].RETURN_CODE == 200 + - result.response[5].sequence_number == 6 + - result.response[6].DATA is match '.*deleted successfully.*' + - result.response[6].MESSAGE == "OK" + - result.response[6].METHOD == "DELETE" + - result.response[6].RETURN_CODE == 200 + - result.response[6].sequence_number == 7 + - result.response[7].DATA is match '.*deleted successfully.*' + - result.response[7].MESSAGE == "OK" + - result.response[7].METHOD == "DELETE" + - result.response[7].RETURN_CODE == 200 + - result.response[7].sequence_number == 8 + - (result.result | length) == 8 + - result.result[0].changed == true + - result.result[0].success == true + - result.result[0].sequence_number == 1 + - result.result[1].changed == true + - result.result[1].success == true + - result.result[1].sequence_number == 2 + - result.result[2].changed == true + - result.result[2].success == true + - result.result[2].sequence_number == 3 + - result.result[3].changed == true + - result.result[3].success == true + - result.result[3].sequence_number == 4 + - result.result[4].changed == true + - result.result[4].success == true + - result.result[4].sequence_number == 5 + - result.result[5].changed == true + - result.result[5].success == true + - result.result[5].sequence_number == 6 + - result.result[6].changed == true + - result.result[6].success == true + - result.result[6].sequence_number == 7 + - result.result[7].changed == true + - result.result[7].success == true + - result.result[7].sequence_number == 8 From 3a461a4aa78721b16f9fa908c02ab3bbe3a9a31e Mon Sep 17 00:00:00 2001 From: prabahal Date: Thu, 20 Mar 2025 18:58:36 +0530 Subject: [PATCH 06/13] Fix code sanity issues --- plugins/module_utils/msd/add_child_fab.py | 1 + plugins/modules/dcnm_child_fabric.py | 6 +++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/plugins/module_utils/msd/add_child_fab.py b/plugins/module_utils/msd/add_child_fab.py index 261f99c34..55e99ef61 100644 --- a/plugins/module_utils/msd/add_child_fab.py +++ b/plugins/module_utils/msd/add_child_fab.py @@ -32,6 +32,7 @@ # in commit(). from ..common.results import Results + class childFabricAdd(): """ methods and properties for adding Child fabric into MSD: diff --git a/plugins/modules/dcnm_child_fabric.py b/plugins/modules/dcnm_child_fabric.py index b80dc0115..b8b00c98e 100644 --- a/plugins/modules/dcnm_child_fabric.py +++ b/plugins/modules/dcnm_child_fabric.py @@ -409,7 +409,7 @@ def commit(self) -> None: self.delete = childFabricDelete() self.delete.rest_send = self.rest_send self.delete.results = self.results - + fabric_names_to_delete = [] for want in self.payloads: fabric_names_to_delete.append(want["sourceFabric"]) @@ -418,7 +418,7 @@ def commit(self) -> None: self.delete.fabric_names = fabric_names_to_delete except ValueError as error: raise ValueError(f"{error}") from error - + try: self.delete.commit(item) except ValueError as error: @@ -508,7 +508,7 @@ def commit(self) -> None: self.add.fabric_names = fabric_names_to_add except ValueError as error: raise ValueError(f"{error}") from error - + try: self.add.commit(item) except ValueError as error: From 699db0c3731938a900c17b3f6e3006db1345d16f Mon Sep 17 00:00:00 2001 From: prabahal Date: Thu, 20 Mar 2025 19:14:24 +0530 Subject: [PATCH 07/13] Modified the comments in IT description --- .../tests/dcnm_child_fabric.yaml | 32 +++++++++++-------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/tests/integration/targets/dcnm_child_fabric/tests/dcnm_child_fabric.yaml b/tests/integration/targets/dcnm_child_fabric/tests/dcnm_child_fabric.yaml index 9ac356bf1..6615f570d 100644 --- a/tests/integration/targets/dcnm_child_fabric/tests/dcnm_child_fabric.yaml +++ b/tests/integration/targets/dcnm_child_fabric/tests/dcnm_child_fabric.yaml @@ -15,20 +15,19 @@ ################################################################################ # 1. The following fabrics must be empty on the controller # See vars: section in cisco/dcnm/playbooks/dcnm_tests.yaml -# - fabric_name_1 -# - fabric_type_1 # VXLAN_EVPN -# - fabric_name_2 -# - fabric_type_2 # VXLAN_EVPN_MSD -# - fabric_name_3 -# - fabric_type_3 # LAN_CLASSIC -# 2. Delete fabrics under test, if they exist -# - fabric_name_1 -# - fabric_name_2 -# - fabric_name_3 +# Delete fabrics under test, if they exist +# - msd_fabric_name_1 +# - child_fabric_name_11 +# - child_fabric_name_12 +# - child_fabric_name_13 +# - msd_fabric_name_2 +# - child_fabric_name_21 +# - child_fabric_name_22 +# - child_fabric_name_23 ################################################################################ # TEST ################################################################################ -# 3. Create fabrics and verify result +# 2. Create fabrics and verify result # - msd_fabric_name_1 # - child_fabric_name_11 # - child_fabric_name_12 @@ -37,12 +36,17 @@ # - child_fabric_name_21 # - child_fabric_name_22 # - child_fabric_name_23 -# 4. Merge additional configs into fabric_1 and fabric_2 and verify result -# 5. Merge additional config into fabric_3 and verify result +# 3. Merge 2 child Fabrics into each MSD Fabrics and verify. +# 4. Add one more child fabric of type "ISN" to MSD Fabric and verify. +# 5. Idempotence : Add one more child fabric of type "ISN" to MSD Fabric and verify. +# 6. Query the MSD Fabrics to get the child Fabrics. +# 7. Delete the child Fabrics from MSD fabrics and verify. +# 8. Idempotence : Delete the child Fabrics from MSD fabrics and verify. + ################################################################################ # CLEANUP ################################################################################ -# 6. Delete fabrics under test +# 9. Delete fabrics under test # - msd_fabric_name_1 # - child_fabric_name_11 # - child_fabric_name_12 From e2e800601c8fdcc6db3decb0ec5795bcdb62238c Mon Sep 17 00:00:00 2001 From: prabahal Date: Tue, 25 Mar 2025 14:45:40 +0530 Subject: [PATCH 08/13] Addressing IT issues --- plugins/module_utils/msd/query_child_fab.py | 5 ++++- plugins/modules/dcnm_child_fabric.py | 18 +++++++++--------- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/plugins/module_utils/msd/query_child_fab.py b/plugins/module_utils/msd/query_child_fab.py index a97d32cd6..f6ffacda6 100644 --- a/plugins/module_utils/msd/query_child_fab.py +++ b/plugins/module_utils/msd/query_child_fab.py @@ -176,7 +176,10 @@ def commit(self): self.results.state = self.rest_send.state # pylint: disable=no-member self.results.diff_current = add_to_diff - self.results.result_current = {"success": True, "found": True} + if not add_to_diff: + self.results.result_current = {"success": True, "found": False} + else: + self.results.result_current = {"success": True, "found": True} self.results.response_current = copy.deepcopy( self.rest_send.response_current ) diff --git a/plugins/modules/dcnm_child_fabric.py b/plugins/modules/dcnm_child_fabric.py index b8b00c98e..3a1311e8c 100644 --- a/plugins/modules/dcnm_child_fabric.py +++ b/plugins/modules/dcnm_child_fabric.py @@ -155,7 +155,7 @@ def __init__(self, params): msg += f"check_mode: {self.check_mode}" self.log.debug(msg) - def verify_parent_fab_exists_in_controller(self): + def verify_msd_fab_exists_in_controller(self): method_name = inspect.stack()[0][3] for item in self.payloads: for fabric in self.data: @@ -168,7 +168,7 @@ def verify_parent_fab_exists_in_controller(self): msg += "is not found in Controller. Please create and try again" raise ValueError(msg) - def verify_parent_fab_is_MSD(self): + def verify_msd_fab_type(self): method_name = inspect.stack()[0][3] for item in self.payloads: for fabric in self.data: @@ -226,10 +226,10 @@ def get_want(self): msg += f"value {config}." raise ValueError(msg) try: - parent_fabric = config.get("FABRIC_NAME", None) + msd_fabric = config.get("FABRIC_NAME", None) child_fabric = config.get("CHILD_FABRIC_NAME", None) try: - self.conversion.validate_fabric_name(parent_fabric) + self.conversion.validate_fabric_name(msd_fabric) self.conversion.validate_fabric_name(child_fabric) except (TypeError, ValueError) as error: msg = f"{self.class_name}: " @@ -241,7 +241,7 @@ def get_want(self): raise ValueError(msg) from error except ValueError as error: raise ValueError(f"{error}") from error - config_payload = {'destFabric': parent_fabric, 'sourceFabric': child_fabric} + config_payload = {'destFabric': msd_fabric, 'sourceFabric': child_fabric} self.payloads.append(copy.deepcopy(config_payload)) def populate_check_mode(self): @@ -396,8 +396,8 @@ def commit(self) -> None: continue self.data[fabric_name] = item - self.verify_parent_fab_exists_in_controller() - self.verify_parent_fab_is_MSD() + self.verify_msd_fab_exists_in_controller() + self.verify_msd_fab_type() for item in self.payloads: if not self.verify_child_fabric_is_already_member(item): self.results.action = self.action @@ -488,8 +488,8 @@ def commit(self) -> None: continue self.data[fabric_name] = item - self.verify_parent_fab_exists_in_controller() - self.verify_parent_fab_is_MSD() + self.verify_msd_fab_exists_in_controller() + self.verify_msd_fab_type() self.verify_child_fab_exists_in_controller() self.verify_child_fabric_is_member_of_another_fabric() for item in self.payloads: From ae4983162eff08959c4622e7757ac2e82dc22cd3 Mon Sep 17 00:00:00 2001 From: prabahal Date: Tue, 1 Apr 2025 23:26:15 +0530 Subject: [PATCH 09/13] UT issues and IT cases addition --- plugins/module_utils/fabric/common.py | 26 +- plugins/module_utils/msd/add_child_fab.py | 15 +- plugins/module_utils/msd/delete_child_fab.py | 15 +- plugins/modules/dcnm_child_fabric.py | 13 +- .../tests/dcnm_child_fabric.yaml | 326 +++++++++++++++++- 5 files changed, 366 insertions(+), 29 deletions(-) diff --git a/plugins/module_utils/fabric/common.py b/plugins/module_utils/fabric/common.py index 76f3eef8e..d9f5c3084 100644 --- a/plugins/module_utils/fabric/common.py +++ b/plugins/module_utils/fabric/common.py @@ -133,17 +133,25 @@ def _config_save(self, payload): """ method_name = inspect.stack()[0][3] - fabric_name = payload.get("FABRIC_NAME", None) + if self.action == "child_fabric_add" or self.action == "child_fabric_delete": + fabric_name = payload.get("destFabric", None) + payload.update({'FABRIC_NAME': fabric_name}) + else: + fabric_name = payload.get("FABRIC_NAME", None) + + msg = f"{method_name}: action{self.action} payloads {payload} fab {fabric_name}" + self.log.debug(msg) if fabric_name is None: msg = f"{self.class_name}.{method_name}: " msg += "payload is missing mandatory parameter: FABRIC_NAME." raise ValueError(msg) - if self.send_payload_result[fabric_name] is False: - # Skip config-save if send_payload failed - # Set config_save_result to False so that config_deploy is skipped - self.config_save_result[fabric_name] = False - return + if not (self.action == "child_fabric_add" or self.action == "child_fabric_delete"): + if self.send_payload_result[fabric_name] is False: + # Skip config-save if send_payload failed + # Set config_save_result to False so that config_deploy is skipped + self.config_save_result[fabric_name] = False + return self.config_save.payload = payload # pylint: disable=no-member @@ -164,7 +172,11 @@ def _config_deploy(self, payload): - Raise ``ValueError`` if the payload is missing the FABRIC_NAME key. """ method_name = inspect.stack()[0][3] - fabric_name = payload.get("FABRIC_NAME") + if self.action == "child_fabric_add": + fabric_name = payload.get("destFabric", None) + payload.update({'FABRIC_NAME': fabric_name}) + else: + fabric_name = payload.get("FABRIC_NAME", None) if fabric_name is None: msg = f"{self.class_name}.{method_name}: " msg += "payload is missing mandatory parameter: FABRIC_NAME." diff --git a/plugins/module_utils/msd/add_child_fab.py b/plugins/module_utils/msd/add_child_fab.py index 55e99ef61..6517fcd73 100644 --- a/plugins/module_utils/msd/add_child_fab.py +++ b/plugins/module_utils/msd/add_child_fab.py @@ -31,21 +31,24 @@ # in _validate_commit_parameters() so that we can register the failure # in commit(). from ..common.results import Results +from ...module_utils.fabric.common import FabricCommon -class childFabricAdd(): +class childFabricAdd(FabricCommon): """ methods and properties for adding Child fabric into MSD: """ def __init__(self): + super().__init__() self.class_name = self.__class__.__name__ self.action = "child_fabric_add" self.log = logging.getLogger(f"dcnm.{self.class_name}") self.ep_fabric_add = EpChildFabricAdd() msg = "ENTERED childFabricAdd()" self.log.debug(msg) + self.deploy = False def commit(self, payload): """ @@ -57,6 +60,9 @@ def commit(self, payload): - ``_validate_commit_parameters`` raises ``ValueError``. """ + if 'DEPLOY' in payload: + self.deploy = payload.pop('DEPLOY') + try: self._validate_commit_parameters() except ValueError as error: @@ -100,6 +106,13 @@ def commit(self, payload): msg = f"self.results.diff: {json.dumps(self.results.diff, indent=4, sort_keys=True)}" self.log.debug(msg) + if True in self.results.failed: + return + + if self.deploy is True: + payload.update({'DEPLOY': True}) + self._config_save(payload) + def _validate_commit_parameters(self): """ - validate the parameters for commit diff --git a/plugins/module_utils/msd/delete_child_fab.py b/plugins/module_utils/msd/delete_child_fab.py index 4ce0232ce..69cb0465a 100644 --- a/plugins/module_utils/msd/delete_child_fab.py +++ b/plugins/module_utils/msd/delete_child_fab.py @@ -30,14 +30,16 @@ # in _validate_commit_parameters() so that we can register the failure # in commit(). from ..common.results import Results +from ...module_utils.fabric.common import FabricCommon -class childFabricDelete(): +class childFabricDelete(FabricCommon): """ Delete child fabrics from Parent fabrics """ def __init__(self): + super().__init__() self.class_name = self.__class__.__name__ self.action = "child_fabric_delete" @@ -51,6 +53,7 @@ def __init__(self): msg = "ENTERED childFabricDelete()" self.log.debug(msg) + self.deploy = False def commit(self, payload): """ @@ -62,7 +65,8 @@ def commit(self, payload): - ``_validate_commit_parameters`` raises ``ValueError``. """ - + if 'DEPLOY' in payload: + self.deploy = payload.pop('DEPLOY') try: self._validate_commit_parameters() except ValueError as error: @@ -106,6 +110,13 @@ def commit(self, payload): msg = f"self.results.diff: {json.dumps(self.results.diff, indent=4, sort_keys=True)}" self.log.debug(msg) + if True in self.results.failed: + return + + if self.deploy is True: + payload.update({'DEPLOY': True}) + self._config_save(payload) + def _validate_commit_parameters(self): """ - validate the parameters for commit diff --git a/plugins/modules/dcnm_child_fabric.py b/plugins/modules/dcnm_child_fabric.py index 3a1311e8c..afcde41ab 100644 --- a/plugins/modules/dcnm_child_fabric.py +++ b/plugins/modules/dcnm_child_fabric.py @@ -42,6 +42,12 @@ type: list elements: dict suboptions: + DEPLOY: + default: False + description: + - Save the member fabric configuration. + required: false + type: bool FABRIC_NAME: description: - The name of the MSD fabric. @@ -228,6 +234,7 @@ def get_want(self): try: msd_fabric = config.get("FABRIC_NAME", None) child_fabric = config.get("CHILD_FABRIC_NAME", None) + deploy = config.get("DEPLOY", None) try: self.conversion.validate_fabric_name(msd_fabric) self.conversion.validate_fabric_name(child_fabric) @@ -241,7 +248,7 @@ def get_want(self): raise ValueError(msg) from error except ValueError as error: raise ValueError(f"{error}") from error - config_payload = {'destFabric': msd_fabric, 'sourceFabric': child_fabric} + config_payload = {'destFabric': msd_fabric, 'sourceFabric': child_fabric, 'DEPLOY': deploy} self.payloads.append(copy.deepcopy(config_payload)) def populate_check_mode(self): @@ -402,7 +409,7 @@ def commit(self) -> None: if not self.verify_child_fabric_is_already_member(item): self.results.action = self.action self.results.result_current = {"success": True, "changed": False} - msg = "Child fabric is not a member of Parent fabric." + msg = "Given child fabric is already not a member of MSD fabric" self.results.response_current = {"RETURN_CODE": 200, "MESSAGE": msg} self.results.register_task_result() else: @@ -496,7 +503,7 @@ def commit(self) -> None: if self.verify_child_fabric_is_already_member(item): self.results.action = self.action self.results.result_current = {"success": True, "changed": False} - msg = "Child fabric is already member of Parent fabric." + msg = "Child fabric is already member of MSD fabric." self.results.response_current = {"RETURN_CODE": 200, "MESSAGE": msg} self.results.register_task_result() else: diff --git a/tests/integration/targets/dcnm_child_fabric/tests/dcnm_child_fabric.yaml b/tests/integration/targets/dcnm_child_fabric/tests/dcnm_child_fabric.yaml index 6615f570d..43059a0ea 100644 --- a/tests/integration/targets/dcnm_child_fabric/tests/dcnm_child_fabric.yaml +++ b/tests/integration/targets/dcnm_child_fabric/tests/dcnm_child_fabric.yaml @@ -2,7 +2,7 @@ # RUNTIME ################################################################################ # Recent run times (MM:SS.ms): -# 00:12.62 +# 02:17.62 ################################################################################ # DESCRIPTION - CHILD FABRIC TEST # @@ -42,11 +42,12 @@ # 6. Query the MSD Fabrics to get the child Fabrics. # 7. Delete the child Fabrics from MSD fabrics and verify. # 8. Idempotence : Delete the child Fabrics from MSD fabrics and verify. - +# 9. Add Child fabrics into MSD with Config-save +#10. Delete child fabrics from MSD with config-save ################################################################################ # CLEANUP ################################################################################ -# 9. Delete fabrics under test +# 11. Delete fabrics under test # - msd_fabric_name_1 # - child_fabric_name_11 # - child_fabric_name_12 @@ -805,12 +806,12 @@ # ], # "response": [ # { -# "MESSAGE": "Child fabric is already member of Parent fabric.", +# "MESSAGE": "Child fabric is already member of MSD fabric.", # "RETURN_CODE": 200, # "sequence_number": 1 # }, # { -# "MESSAGE": "Child fabric is already member of Parent fabric.", +# "MESSAGE": "Child fabric is already member of MSD fabric.", # "RETURN_CODE": 200, # "sequence_number": 2 # } @@ -852,10 +853,10 @@ - result.metadata[1].state == "merged" - (result.response | length) == 2 - result.response[0].sequence_number == 1 - - result.response[0].MESSAGE == "Child fabric is already member of Parent fabric." + - result.response[0].MESSAGE == "Child fabric is already member of MSD fabric." - result.response[0].RETURN_CODE == 200 - result.response[1].sequence_number == 2 - - result.response[1].MESSAGE == "Child fabric is already member of Parent fabric." + - result.response[1].MESSAGE == "Child fabric is already member of MSD fabric." - result.response[1].RETURN_CODE == 200 - (result.result | length) == 2 - result.result[0].changed == false @@ -1095,17 +1096,17 @@ # ], # "response": [ # { -# "MESSAGE": "Child fabric is not a member of Parent fabric.", +# "MESSAGE": "Given child fabric is already not a member of MSD fabric", # "RETURN_CODE": 200, # "sequence_number": 1 # }, # { -# "MESSAGE": "Child fabric is not a member of Parent fabric.", +# "MESSAGE": "Given child fabric is already not a member of MSD fabric", # "RETURN_CODE": 200, # "sequence_number": 2 # }, # { -# "MESSAGE": "Child fabric is not a member of Parent fabric.", +# "MESSAGE": "Given child fabric is already not a member of MSD fabric", # "RETURN_CODE": 200, # "sequence_number": 3 # } @@ -1173,22 +1174,22 @@ - result.metadata[5].state == "deleted" - (result.response | length) == 6 - result.response[0].sequence_number == 1 - - result.response[0].MESSAGE == "Child fabric is not a member of Parent fabric." + - result.response[0].MESSAGE == "Given child fabric is already not a member of MSD fabric" - result.response[0].RETURN_CODE == 200 - result.response[1].sequence_number == 2 - - result.response[1].MESSAGE == "Child fabric is not a member of Parent fabric." + - result.response[1].MESSAGE == "Given child fabric is already not a member of MSD fabric" - result.response[1].RETURN_CODE == 200 - result.response[2].sequence_number == 3 - - result.response[2].MESSAGE == "Child fabric is not a member of Parent fabric." + - result.response[2].MESSAGE == "Given child fabric is already not a member of MSD fabric" - result.response[2].RETURN_CODE == 200 - result.response[3].sequence_number == 4 - - result.response[3].MESSAGE == "Child fabric is not a member of Parent fabric." + - result.response[3].MESSAGE == "Given child fabric is already not a member of MSD fabric" - result.response[3].RETURN_CODE == 200 - result.response[4].sequence_number == 5 - - result.response[4].MESSAGE == "Child fabric is not a member of Parent fabric." + - result.response[4].MESSAGE == "Given child fabric is already not a member of MSD fabric" - result.response[4].RETURN_CODE == 200 - result.response[5].sequence_number == 6 - - result.response[5].MESSAGE == "Child fabric is not a member of Parent fabric." + - result.response[5].MESSAGE == "Given child fabric is already not a member of MSD fabric" - result.response[5].RETURN_CODE == 200 - (result.result | length) == 6 - result.result[0].changed == false @@ -1209,6 +1210,299 @@ - result.result[5].changed == false - result.result[5].success == true - result.result[5].sequence_number == 6 +############################################################################################### +#- name: MERGED - TEST - ADD child Fabrics into MSD fabric with Deploy +################################################################################################ +- name: Merged - TEST - Merge child fabrics into MSD Fabric with Deploy flag - To save the config + cisco.dcnm.dcnm_child_fabric: + state: merged + config: + - FABRIC_NAME: "{{ msd_fabric_name_1 }}" + CHILD_FABRIC_NAME: "{{ child_fabric_name_11 }}" + DEPLOY: true + - FABRIC_NAME: "{{ msd_fabric_name_1 }}" + CHILD_FABRIC_NAME: "{{ child_fabric_name_12 }}" + DEPLOY: true + - FABRIC_NAME: "{{ msd_fabric_name_2 }}" + CHILD_FABRIC_NAME: "{{ child_fabric_name_21 }}" + DEPLOY: true + - FABRIC_NAME: "{{ msd_fabric_name_2 }}" + CHILD_FABRIC_NAME: "{{ child_fabric_name_22 }}" + DEPLOY: true + register: result +- debug: + var: result +- assert: + that: + - result.changed == true + - result.failed == false + - (result.diff | length) == 8 + - result.diff[0].destFabric == msd_fabric_name_1 + - result.diff[0].sourceFabric == child_fabric_name_11 + - result.diff[0].sequence_number == 1 + - result.diff[1].FABRIC_NAME == msd_fabric_name_1 + - result.diff[1].config_save == "OK" + - result.diff[1].sequence_number == 2 + - result.diff[2].destFabric == msd_fabric_name_1 + - result.diff[2].sourceFabric == child_fabric_name_12 + - result.diff[2].sequence_number == 3 + - result.diff[3].FABRIC_NAME == msd_fabric_name_1 + - result.diff[3].config_save == "OK" + - result.diff[3].sequence_number == 4 + - result.diff[4].destFabric == msd_fabric_name_2 + - result.diff[4].sourceFabric == child_fabric_name_21 + - result.diff[4].sequence_number == 5 + - result.diff[5].FABRIC_NAME == msd_fabric_name_2 + - result.diff[5].config_save == "OK" + - result.diff[5].sequence_number == 6 + - result.diff[6].destFabric == msd_fabric_name_2 + - result.diff[6].sourceFabric == child_fabric_name_22 + - result.diff[6].sequence_number == 7 + - result.diff[7].FABRIC_NAME == msd_fabric_name_2 + - result.diff[7].config_save == "OK" + - result.diff[7].sequence_number == 8 + - (result.metadata | length) == 8 + - result.metadata[0].action == "child_fabric_add" + - result.metadata[0].check_mode == false + - result.metadata[0].sequence_number == 1 + - result.metadata[0].state == "merged" + - result.metadata[1].action == "config_save" + - result.metadata[1].check_mode == false + - result.metadata[1].sequence_number == 2 + - result.metadata[1].state == "merged" + - result.metadata[2].action == "child_fabric_add" + - result.metadata[2].check_mode == false + - result.metadata[2].sequence_number == 3 + - result.metadata[2].state == "merged" + - result.metadata[3].action == "config_save" + - result.metadata[3].check_mode == false + - result.metadata[3].sequence_number == 4 + - result.metadata[3].state == "merged" + - result.metadata[4].action == "child_fabric_add" + - result.metadata[4].check_mode == false + - result.metadata[4].sequence_number == 5 + - result.metadata[4].state == "merged" + - result.metadata[5].action == "config_save" + - result.metadata[5].check_mode == false + - result.metadata[5].sequence_number == 6 + - result.metadata[5].state == "merged" + - result.metadata[6].action == "child_fabric_add" + - result.metadata[6].check_mode == false + - result.metadata[6].sequence_number == 7 + - result.metadata[6].state == "merged" + - result.metadata[7].action == "config_save" + - result.metadata[7].check_mode == false + - result.metadata[7].sequence_number == 8 + - result.metadata[7].state == "merged" + - (result.response | length) == 8 + - result.response[0].sequence_number == 1 + - result.response[0].MESSAGE == "OK" + - result.response[0].METHOD == "POST" + - result.response[0].RETURN_CODE == 200 + - result.response[1].sequence_number == 2 + - result.response[1].MESSAGE == "OK" + - result.response[1].METHOD == "POST" + - result.response[1].RETURN_CODE == 200 + - result.response[1].DATA.status == "Config save is completed" + - result.response[2].sequence_number == 3 + - result.response[2].MESSAGE == "OK" + - result.response[2].METHOD == "POST" + - result.response[2].RETURN_CODE == 200 + - result.response[3].sequence_number == 4 + - result.response[3].MESSAGE == "OK" + - result.response[3].METHOD == "POST" + - result.response[3].RETURN_CODE == 200 + - result.response[3].DATA.status == "Config save is completed" + - result.response[4].sequence_number == 5 + - result.response[4].MESSAGE == "OK" + - result.response[4].METHOD == "POST" + - result.response[4].RETURN_CODE == 200 + - result.response[5].sequence_number == 6 + - result.response[5].MESSAGE == "OK" + - result.response[5].METHOD == "POST" + - result.response[5].RETURN_CODE == 200 + - result.response[5].DATA.status == "Config save is completed" + - result.response[6].sequence_number == 7 + - result.response[6].MESSAGE == "OK" + - result.response[6].METHOD == "POST" + - result.response[6].RETURN_CODE == 200 + - result.response[7].sequence_number == 8 + - result.response[7].MESSAGE == "OK" + - result.response[7].METHOD == "POST" + - result.response[7].RETURN_CODE == 200 + - result.response[7].DATA.status == "Config save is completed" + - (result.result | length) == 8 + - result.result[0].changed == true + - result.result[0].success == true + - result.result[0].sequence_number == 1 + - result.result[1].changed == true + - result.result[1].success == true + - result.result[1].sequence_number == 2 + - result.result[2].changed == true + - result.result[2].success == true + - result.result[2].sequence_number == 3 + - result.result[3].changed == true + - result.result[3].success == true + - result.result[3].sequence_number == 4 + - result.result[4].changed == true + - result.result[4].success == true + - result.result[4].sequence_number == 5 + - result.result[5].changed == true + - result.result[5].success == true + - result.result[5].sequence_number == 6 + - result.result[6].changed == true + - result.result[6].success == true + - result.result[6].sequence_number == 7 + - result.result[7].changed == true + - result.result[7].success == true + - result.result[7].sequence_number == 8 + +############################################################################################### +#- name: DELETED - TEST - Delete child Fabrics from MSD fabric with Deploy +################################################################################################ +- name: Deleted - TEST - Delete child fabrics from MSD Fabric with Deploy flag - To save the config + cisco.dcnm.dcnm_child_fabric: + state: deleted + config: + - FABRIC_NAME: "{{ msd_fabric_name_1 }}" + CHILD_FABRIC_NAME: "{{ child_fabric_name_11 }}" + DEPLOY: true + - FABRIC_NAME: "{{ msd_fabric_name_1 }}" + CHILD_FABRIC_NAME: "{{ child_fabric_name_12 }}" + DEPLOY: true + - FABRIC_NAME: "{{ msd_fabric_name_2 }}" + CHILD_FABRIC_NAME: "{{ child_fabric_name_21 }}" + DEPLOY: true + - FABRIC_NAME: "{{ msd_fabric_name_2 }}" + CHILD_FABRIC_NAME: "{{ child_fabric_name_22 }}" + DEPLOY: true + register: result +- debug: + var: result +- assert: + that: + - result.changed == true + - result.failed == false + - (result.diff | length) == 8 + - result.diff[0].destFabric == msd_fabric_name_1 + - result.diff[0].sourceFabric == child_fabric_name_11 + - result.diff[0].sequence_number == 1 + - result.diff[1].FABRIC_NAME == msd_fabric_name_1 + - result.diff[1].config_save == "OK" + - result.diff[1].sequence_number == 2 + - result.diff[2].destFabric == msd_fabric_name_1 + - result.diff[2].sourceFabric == child_fabric_name_12 + - result.diff[2].sequence_number == 3 + - result.diff[3].FABRIC_NAME == msd_fabric_name_1 + - result.diff[3].config_save == "OK" + - result.diff[3].sequence_number == 4 + - result.diff[4].destFabric == msd_fabric_name_2 + - result.diff[4].sourceFabric == child_fabric_name_21 + - result.diff[4].sequence_number == 5 + - result.diff[5].FABRIC_NAME == msd_fabric_name_2 + - result.diff[5].config_save == "OK" + - result.diff[5].sequence_number == 6 + - result.diff[6].destFabric == msd_fabric_name_2 + - result.diff[6].sourceFabric == child_fabric_name_22 + - result.diff[6].sequence_number == 7 + - result.diff[7].FABRIC_NAME == msd_fabric_name_2 + - result.diff[7].config_save == "OK" + - result.diff[7].sequence_number == 8 + - (result.metadata | length) == 8 + - result.metadata[0].action == "child_fabric_delete" + - result.metadata[0].check_mode == false + - result.metadata[0].sequence_number == 1 + - result.metadata[0].state == "deleted" + - result.metadata[1].action == "config_save" + - result.metadata[1].check_mode == false + - result.metadata[1].sequence_number == 2 + - result.metadata[1].state == "deleted" + - result.metadata[2].action == "child_fabric_delete" + - result.metadata[2].check_mode == false + - result.metadata[2].sequence_number == 3 + - result.metadata[2].state == "deleted" + - result.metadata[3].action == "config_save" + - result.metadata[3].check_mode == false + - result.metadata[3].sequence_number == 4 + - result.metadata[3].state == "deleted" + - result.metadata[4].action == "child_fabric_delete" + - result.metadata[4].check_mode == false + - result.metadata[4].sequence_number == 5 + - result.metadata[4].state == "deleted" + - result.metadata[5].action == "config_save" + - result.metadata[5].check_mode == false + - result.metadata[5].sequence_number == 6 + - result.metadata[5].state == "deleted" + - result.metadata[6].action == "child_fabric_delete" + - result.metadata[6].check_mode == false + - result.metadata[6].sequence_number == 7 + - result.metadata[6].state == "deleted" + - result.metadata[7].action == "config_save" + - result.metadata[7].check_mode == false + - result.metadata[7].sequence_number == 8 + - result.metadata[7].state == "deleted" + - (result.response | length) == 8 + - result.response[0].sequence_number == 1 + - result.response[0].MESSAGE == "OK" + - result.response[0].METHOD == "POST" + - result.response[0].RETURN_CODE == 200 + - result.response[1].sequence_number == 2 + - result.response[1].MESSAGE == "OK" + - result.response[1].METHOD == "POST" + - result.response[1].RETURN_CODE == 200 + - result.response[1].DATA.status == "Config save is completed" + - result.response[2].sequence_number == 3 + - result.response[2].MESSAGE == "OK" + - result.response[2].METHOD == "POST" + - result.response[2].RETURN_CODE == 200 + - result.response[3].sequence_number == 4 + - result.response[3].MESSAGE == "OK" + - result.response[3].METHOD == "POST" + - result.response[3].RETURN_CODE == 200 + - result.response[3].DATA.status == "Config save is completed" + - result.response[4].sequence_number == 5 + - result.response[4].MESSAGE == "OK" + - result.response[4].METHOD == "POST" + - result.response[4].RETURN_CODE == 200 + - result.response[5].sequence_number == 6 + - result.response[5].MESSAGE == "OK" + - result.response[5].METHOD == "POST" + - result.response[5].RETURN_CODE == 200 + - result.response[5].DATA.status == "Config save is completed" + - result.response[6].sequence_number == 7 + - result.response[6].MESSAGE == "OK" + - result.response[6].METHOD == "POST" + - result.response[6].RETURN_CODE == 200 + - result.response[7].sequence_number == 8 + - result.response[7].MESSAGE == "OK" + - result.response[7].METHOD == "POST" + - result.response[7].RETURN_CODE == 200 + - result.response[7].DATA.status == "Config save is completed" + - (result.result | length) == 8 + - result.result[0].changed == true + - result.result[0].success == true + - result.result[0].sequence_number == 1 + - result.result[1].changed == true + - result.result[1].success == true + - result.result[1].sequence_number == 2 + - result.result[2].changed == true + - result.result[2].success == true + - result.result[2].sequence_number == 3 + - result.result[3].changed == true + - result.result[3].success == true + - result.result[3].sequence_number == 4 + - result.result[4].changed == true + - result.result[4].success == true + - result.result[4].sequence_number == 5 + - result.result[5].changed == true + - result.result[5].success == true + - result.result[5].sequence_number == 6 + - result.result[6].changed == true + - result.result[6].success == true + - result.result[6].sequence_number == 7 + - result.result[7].changed == true + - result.result[7].success == true + - result.result[7].sequence_number == 8 ################################################################################ # MERGED - CLEANUP - Delete the fabrics ################################################################################ From 322b0af255a8cae9d576adabd391cf7abde56376 Mon Sep 17 00:00:00 2001 From: prabahal Date: Sat, 5 Apr 2025 16:18:30 +0530 Subject: [PATCH 10/13] Addressing review comments --- plugins/module_utils/msd/query_child_fab.py | 2 +- ..._child_fabric.py => dcnm_fabric_member.py} | 107 +++++++++--------- .../defaults/main.yaml | 0 .../meta/main.yaml | 0 .../tasks/dcnm.yaml | 0 .../tasks/main.yaml | 0 .../tests/dcnm_fabric_member.yaml} | 18 +-- tests/sanity/ignore-2.15.txt | 2 +- tests/sanity/ignore-2.16.txt | 2 +- 9 files changed, 67 insertions(+), 64 deletions(-) rename plugins/modules/{dcnm_child_fabric.py => dcnm_fabric_member.py} (90%) rename tests/integration/targets/{dcnm_child_fabric => dcnm_fabric_member}/defaults/main.yaml (100%) rename tests/integration/targets/{dcnm_child_fabric => dcnm_fabric_member}/meta/main.yaml (100%) rename tests/integration/targets/{dcnm_child_fabric => dcnm_fabric_member}/tasks/dcnm.yaml (100%) rename tests/integration/targets/{dcnm_child_fabric => dcnm_fabric_member}/tasks/main.yaml (100%) rename tests/integration/targets/{dcnm_child_fabric/tests/dcnm_child_fabric.yaml => dcnm_fabric_member/tests/dcnm_fabric_member.yaml} (99%) diff --git a/plugins/module_utils/msd/query_child_fab.py b/plugins/module_utils/msd/query_child_fab.py index f6ffacda6..fbe2107b3 100644 --- a/plugins/module_utils/msd/query_child_fab.py +++ b/plugins/module_utils/msd/query_child_fab.py @@ -20,7 +20,7 @@ import logging from ..common.results import Results -from ..msd.Fabric_associations import FabricAssociations +from ..msd.fabric_associations import FabricAssociations class childFabricQuery(): diff --git a/plugins/modules/dcnm_child_fabric.py b/plugins/modules/dcnm_fabric_member.py similarity index 90% rename from plugins/modules/dcnm_child_fabric.py rename to plugins/modules/dcnm_fabric_member.py index afcde41ab..012df7094 100644 --- a/plugins/modules/dcnm_child_fabric.py +++ b/plugins/modules/dcnm_fabric_member.py @@ -20,7 +20,7 @@ DOCUMENTATION = """ --- -module: dcnm_child_fabric +module: dcnm_fabric_member short_description: Manage addition and deletion of NDFC fabrics to MSD. version_added: "3.5.0" author: Prabahal (@prabahal) @@ -63,7 +63,7 @@ EXAMPLES = """ - name: add child fabrics to MSD - cisco.dcnm.dcnm_child_fabric: + cisco.dcnm.dcnm_fabric_member: state: merged config: - FABRIC_NAME: MSD_Parent1 @@ -79,7 +79,7 @@ # Query the child fabrics of a MSD Fabric. - name: Query the child fabrics of MSD fabrics. - cisco.dcnm.dcnm_child_fabric: + cisco.dcnm.dcnm_fabric_member: state: query config: - FABRIC_NAME: MSD_Fabric1 @@ -123,9 +123,9 @@ from ..module_utils.msd.query_child_fab import childFabricQuery from ..module_utils.msd.delete_child_fab import childFabricDelete from ..module_utils.msd.add_child_fab import childFabricAdd -from ..module_utils.msd.Fabric_associations import FabricAssociations +from ..module_utils.msd.fabric_associations import FabricAssociations from ..module_utils.fabric.verify_playbook_params import VerifyPlaybookParams - +from ..module_utils.network.dcnm.dcnm import validate_list_of_dicts @Properties.add_rest_send class childCommon(): @@ -151,7 +151,6 @@ def __init__(self, params): self.results = Results() self.results.state = self.state self.results.check_mode = self.check_mode - self._verify_playbook_params = VerifyPlaybookParams() self.conversion = ConversionUtils() self.payloads = [] self.want = [] @@ -218,36 +217,54 @@ def verify_child_fabric_is_already_member(self, item) -> bool: if (self.data[fabric]['fabricParent'] == item["destFabric"]): return True return False - - def get_want(self): - method_name = inspect.stack()[0][3] + + def validate_input(self): + if self.state != "query": + fab_member_spec = dict( + FABRIC_NAME=dict(required=True, type="str"), + CHILD_FABRIC_NAME=dict(required=True, type="str"), + DEPLOY=dict(type="bool", default=False), + ) + else: + fab_member_spec = dict( + FABRIC_NAME=dict(type="str"), + ) + fab_mem_info, invalid_params = validate_list_of_dicts(self.config, fab_member_spec, None) + if invalid_params: + mesg = "Invalid parameters in playbook: {0}".format( + "while processing config " + + "\n".join(invalid_params) + ) + raise ValueError(mesg) for config in self.config: - msg = f"payload: {config}" - self.log.debug(msg) - if not isinstance(config, dict): msg = f"{self.class_name}.{method_name}: " - msg += "Playbook configuration for fabrics must be a dict. " + msg += "Playbook configuration for fabric_member must be a dict. " msg += f"Got type {type(config).__name__}, " msg += f"value {config}." raise ValueError(msg) + msd_fabric = config.get("FABRIC_NAME", None) + child_fabric = config.get("CHILD_FABRIC_NAME", None) try: - msd_fabric = config.get("FABRIC_NAME", None) - child_fabric = config.get("CHILD_FABRIC_NAME", None) - deploy = config.get("DEPLOY", None) - try: - self.conversion.validate_fabric_name(msd_fabric) - self.conversion.validate_fabric_name(child_fabric) - except (TypeError, ValueError) as error: - msg = f"{self.class_name}: " - msg += "Playbook configuration for FABRIC_NAME or CHILD_FABRIC_NAME " - msg += "contains an invalid FABRIC_NAME. " - # error below already contains a period "." at the end - msg += f"Error detail: {error} " - msg += f"Bad configuration: {config}." - raise ValueError(msg) from error - except ValueError as error: - raise ValueError(f"{error}") from error + self.conversion.validate_fabric_name(msd_fabric) + self.conversion.validate_fabric_name(child_fabric) + except (TypeError, ValueError) as error: + msg = f"{self.class_name}: " + msg += "Playbook configuration for FABRIC_NAME or CHILD_FABRIC_NAME " + msg += "contains an invalid FABRIC_NAME. " + # error below already contains a period "." at the end + msg += f"Error detail: {error} " + msg += f"Bad configuration: {config}." + raise ValueError(msg) from error + + def get_want(self): + method_name = inspect.stack()[0][3] + for config in self.config: + msg = f"{method_name} payload: {config}" + self.log.debug(msg) + msd_fabric = config.get("FABRIC_NAME", None) + child_fabric = config.get("CHILD_FABRIC_NAME", None) + deploy = config.get("DEPLOY", None) config_payload = {'destFabric': msd_fabric, 'sourceFabric': child_fabric, 'DEPLOY': deploy} self.payloads.append(copy.deepcopy(config_payload)) @@ -315,24 +332,7 @@ def populate_state(self): msg += f"Expected one of: {','.join(valid_states)}." raise ValueError(msg) - def get_want_query(self) -> None: - """ - ### Summary - - Validate the playbook configs. - - Update self.want with the playbook configs. - - ### Raises - - ``ValueError`` if the playbook configs are invalid. - """ - merged_configs = [] - for config in self.config: - merged_configs.append(copy.deepcopy(config)) - - self.want = [] - for config in merged_configs: - self.want.append(copy.deepcopy(config)) - -# Keeping this function to check lower NDFC version support. yet to get data +# Keeping this function to check lower NDFC version support. yet to get data from Mike def get_controller_version(self): """ ### Summary @@ -389,7 +389,8 @@ def commit(self) -> None: msg = f"ENTERED: {self.class_name}.{method_name}" self.log.debug(msg) - + + self.validate_input() self.get_want() self.fab_association = FabricAssociations() @@ -480,7 +481,9 @@ def commit(self) -> None: msg = f"ENTERED: {self.class_name}.{method_name}" self.log.debug(msg) - + self.get_controller_version() + ### Version validation needs to be added + self.validate_input() self.get_want() self.add.results = self.results @@ -582,14 +585,14 @@ def commit(self) -> None: query the fabrics. """ self.verify_payload() - self.get_want_query() + self.get_want() fabric_query = childFabricQuery() fabric_query.rest_send = self.rest_send fabric_query.results = self.results fabric_names_to_query = [] - for want in self.want: - fabric_names_to_query.append(want["FABRIC_NAME"]) + for item in self.payloads: + fabric_names_to_query.append(item["destFabric"]) try: fabric_query.fabric_names = copy.copy(fabric_names_to_query) except ValueError as error: diff --git a/tests/integration/targets/dcnm_child_fabric/defaults/main.yaml b/tests/integration/targets/dcnm_fabric_member/defaults/main.yaml similarity index 100% rename from tests/integration/targets/dcnm_child_fabric/defaults/main.yaml rename to tests/integration/targets/dcnm_fabric_member/defaults/main.yaml diff --git a/tests/integration/targets/dcnm_child_fabric/meta/main.yaml b/tests/integration/targets/dcnm_fabric_member/meta/main.yaml similarity index 100% rename from tests/integration/targets/dcnm_child_fabric/meta/main.yaml rename to tests/integration/targets/dcnm_fabric_member/meta/main.yaml diff --git a/tests/integration/targets/dcnm_child_fabric/tasks/dcnm.yaml b/tests/integration/targets/dcnm_fabric_member/tasks/dcnm.yaml similarity index 100% rename from tests/integration/targets/dcnm_child_fabric/tasks/dcnm.yaml rename to tests/integration/targets/dcnm_fabric_member/tasks/dcnm.yaml diff --git a/tests/integration/targets/dcnm_child_fabric/tasks/main.yaml b/tests/integration/targets/dcnm_fabric_member/tasks/main.yaml similarity index 100% rename from tests/integration/targets/dcnm_child_fabric/tasks/main.yaml rename to tests/integration/targets/dcnm_fabric_member/tasks/main.yaml diff --git a/tests/integration/targets/dcnm_child_fabric/tests/dcnm_child_fabric.yaml b/tests/integration/targets/dcnm_fabric_member/tests/dcnm_fabric_member.yaml similarity index 99% rename from tests/integration/targets/dcnm_child_fabric/tests/dcnm_child_fabric.yaml rename to tests/integration/targets/dcnm_fabric_member/tests/dcnm_fabric_member.yaml index 43059a0ea..47c46f000 100644 --- a/tests/integration/targets/dcnm_child_fabric/tests/dcnm_child_fabric.yaml +++ b/tests/integration/targets/dcnm_fabric_member/tests/dcnm_fabric_member.yaml @@ -69,7 +69,7 @@ # Example vars: # # vars: -# testcase: dcnm_child_fabric_merged +# testcase: dcnm_fabric_member_merged # msd_fabric_name_1: MSD_1 # MSD_fabric_type: VXLAN_EVPN_MSD # child_fabric_name_11: child_11 @@ -562,7 +562,7 @@ ################################################################################ - name: MERGED - TEST - Merge child fabrics into MSD Fabric - cisco.dcnm.dcnm_child_fabric: + cisco.dcnm.dcnm_fabric_member: state: merged config: - FABRIC_NAME: "{{ msd_fabric_name_1 }}" @@ -685,7 +685,7 @@ #} ####################################################################################### - name: MERGED - TEST - Merge additional fabric child_fabric_name_13 into MSD Fabric - cisco.dcnm.dcnm_child_fabric: &merge_child_fabric + cisco.dcnm.dcnm_fabric_member: &merge_child_fabric state: merged config: - FABRIC_NAME: "{{ msd_fabric_name_1 }}" @@ -728,7 +728,7 @@ #-name QUERY - TEST - Query child fabrics of MSD Fabric ################################################################################################### - name: QUERY - TEST - Query child fabrics of MSD Fabric - cisco.dcnm.dcnm_child_fabric: + cisco.dcnm.dcnm_fabric_member: state: query config: - FABRIC_NAME: "{{ msd_fabric_name_1 }}" @@ -831,7 +831,7 @@ #} ################################################################################# - name: MERGED - TEST - Merge additional config into MSD fabric - idempotence - cisco.dcnm.dcnm_child_fabric: *merge_child_fabric + cisco.dcnm.dcnm_fabric_member: *merge_child_fabric register: result - debug: var: result @@ -945,7 +945,7 @@ #} ################################################################################ - name: DELETED - SETUP - Delete child fabrics from MSD fabric - cisco.dcnm.dcnm_child_fabric: &delete_child_fabric + cisco.dcnm.dcnm_fabric_member: &delete_child_fabric state: deleted config: - FABRIC_NAME: "{{ msd_fabric_name_1 }}" @@ -1132,7 +1132,7 @@ #} ############################################################################################### - name: MERGED - TEST - Delete child Fabrics into MSD fabric - idempotence - cisco.dcnm.dcnm_child_fabric: *delete_child_fabric + cisco.dcnm.dcnm_fabric_member: *delete_child_fabric register: result - debug: var: result @@ -1214,7 +1214,7 @@ #- name: MERGED - TEST - ADD child Fabrics into MSD fabric with Deploy ################################################################################################ - name: Merged - TEST - Merge child fabrics into MSD Fabric with Deploy flag - To save the config - cisco.dcnm.dcnm_child_fabric: + cisco.dcnm.dcnm_fabric_member: state: merged config: - FABRIC_NAME: "{{ msd_fabric_name_1 }}" @@ -1361,7 +1361,7 @@ #- name: DELETED - TEST - Delete child Fabrics from MSD fabric with Deploy ################################################################################################ - name: Deleted - TEST - Delete child fabrics from MSD Fabric with Deploy flag - To save the config - cisco.dcnm.dcnm_child_fabric: + cisco.dcnm.dcnm_fabric_member: state: deleted config: - FABRIC_NAME: "{{ msd_fabric_name_1 }}" diff --git a/tests/sanity/ignore-2.15.txt b/tests/sanity/ignore-2.15.txt index bbb5c0f23..46d6067d0 100644 --- a/tests/sanity/ignore-2.15.txt +++ b/tests/sanity/ignore-2.15.txt @@ -24,4 +24,4 @@ plugins/httpapi/dcnm.py import-3.10!skip plugins/module_utils/common/sender_requests.py import-3.9 # TODO remove this if/when requests is added to the standard library plugins/module_utils/common/sender_requests.py import-3.10 # TODO remove this if/when requests is added to the standard library plugins/module_utils/common/sender_requests.py import-3.11 # TODO remove this if/when requests is added to the standard library -plugins/modules/dcnm_child_fabric.py validate-modules:missing-gplv3-license # GPLv3 license header not found in the first 20 lines of the module +plugins/modules/dcnm_fabric_member.py validate-modules:missing-gplv3-license # GPLv3 license header not found in the first 20 lines of the module diff --git a/tests/sanity/ignore-2.16.txt b/tests/sanity/ignore-2.16.txt index 2b496e690..fc7d17b53 100644 --- a/tests/sanity/ignore-2.16.txt +++ b/tests/sanity/ignore-2.16.txt @@ -21,4 +21,4 @@ plugins/modules/dcnm_bootflash.py validate-modules:missing-gplv3-license # GPLv3 plugins/modules/dcnm_log.py validate-modules:missing-gplv3-license # GPLv3 license header not found in the first 20 lines of the module plugins/module_utils/common/sender_requests.py import-3.10 # TODO remove this if/when requests is added to the standard library plugins/module_utils/common/sender_requests.py import-3.11 # TODO remove this if/when requests is added to the standard library -plugins/modules/dcnm_child_fabric.py validate-modules:missing-gplv3-license # GPLv3 license header not found in the first 20 lines of the module +plugins/modules/dcnm_fabric_member.py validate-modules:missing-gplv3-license # GPLv3 license header not found in the first 20 lines of the module From 9ebae03af5d45e2647c59897481aa182c6e93ba2 Mon Sep 17 00:00:00 2001 From: prabahal Date: Sat, 5 Apr 2025 17:12:30 +0530 Subject: [PATCH 11/13] Sanity errors --- plugins/modules/dcnm_fabric_member.py | 31 +++++++++++---------------- tests/sanity/ignore-2.15.txt | 1 + tests/sanity/ignore-2.16.txt | 1 + 3 files changed, 14 insertions(+), 19 deletions(-) diff --git a/plugins/modules/dcnm_fabric_member.py b/plugins/modules/dcnm_fabric_member.py index 012df7094..e0e37fed2 100644 --- a/plugins/modules/dcnm_fabric_member.py +++ b/plugins/modules/dcnm_fabric_member.py @@ -124,9 +124,9 @@ from ..module_utils.msd.delete_child_fab import childFabricDelete from ..module_utils.msd.add_child_fab import childFabricAdd from ..module_utils.msd.fabric_associations import FabricAssociations -from ..module_utils.fabric.verify_playbook_params import VerifyPlaybookParams from ..module_utils.network.dcnm.dcnm import validate_list_of_dicts + @Properties.add_rest_send class childCommon(): """ @@ -217,25 +217,19 @@ def verify_child_fabric_is_already_member(self, item) -> bool: if (self.data[fabric]['fabricParent'] == item["destFabric"]): return True return False - + def validate_input(self): - if self.state != "query": - fab_member_spec = dict( - FABRIC_NAME=dict(required=True, type="str"), - CHILD_FABRIC_NAME=dict(required=True, type="str"), - DEPLOY=dict(type="bool", default=False), - ) - else: - fab_member_spec = dict( - FABRIC_NAME=dict(type="str"), - ) + method_name = inspect.stack()[0][3] + fab_member_spec = dict( + FABRIC_NAME=dict(required=True, type="str"), + CHILD_FABRIC_NAME=dict(required=True, type="str"), + DEPLOY=dict(type="bool", default=False), + ) fab_mem_info, invalid_params = validate_list_of_dicts(self.config, fab_member_spec, None) if invalid_params: - mesg = "Invalid parameters in playbook: {0}".format( - "while processing config " - + "\n".join(invalid_params) - ) - raise ValueError(mesg) + msg = "Invalid parameters in playbook: {invalid_params}" + msg += "while processing config \n" + raise ValueError(msg) for config in self.config: if not isinstance(config, dict): msg = f"{self.class_name}.{method_name}: " @@ -389,7 +383,6 @@ def commit(self) -> None: msg = f"ENTERED: {self.class_name}.{method_name}" self.log.debug(msg) - self.validate_input() self.get_want() @@ -482,7 +475,7 @@ def commit(self) -> None: msg = f"ENTERED: {self.class_name}.{method_name}" self.log.debug(msg) self.get_controller_version() - ### Version validation needs to be added + # Version validation needs to be added self.validate_input() self.get_want() diff --git a/tests/sanity/ignore-2.15.txt b/tests/sanity/ignore-2.15.txt index 46d6067d0..a0b8d9437 100644 --- a/tests/sanity/ignore-2.15.txt +++ b/tests/sanity/ignore-2.15.txt @@ -25,3 +25,4 @@ plugins/module_utils/common/sender_requests.py import-3.9 # TODO remove this if/ plugins/module_utils/common/sender_requests.py import-3.10 # TODO remove this if/when requests is added to the standard library plugins/module_utils/common/sender_requests.py import-3.11 # TODO remove this if/when requests is added to the standard library plugins/modules/dcnm_fabric_member.py validate-modules:missing-gplv3-license # GPLv3 license header not found in the first 20 lines of the module +plugins/action/tests/unit/ndfc_pc_members_validate.py action-plugin-docs # action plugin has no matching module to provide documentation diff --git a/tests/sanity/ignore-2.16.txt b/tests/sanity/ignore-2.16.txt index fc7d17b53..9e87ab049 100644 --- a/tests/sanity/ignore-2.16.txt +++ b/tests/sanity/ignore-2.16.txt @@ -22,3 +22,4 @@ plugins/modules/dcnm_log.py validate-modules:missing-gplv3-license # GPLv3 licen plugins/module_utils/common/sender_requests.py import-3.10 # TODO remove this if/when requests is added to the standard library plugins/module_utils/common/sender_requests.py import-3.11 # TODO remove this if/when requests is added to the standard library plugins/modules/dcnm_fabric_member.py validate-modules:missing-gplv3-license # GPLv3 license header not found in the first 20 lines of the module +plugins/action/tests/unit/ndfc_pc_members_validate.py action-plugin-docs # action plugin has no matching module to provide documentation From 342da054412eac9b90d1345d3cca1647f43680ec Mon Sep 17 00:00:00 2001 From: Prabaharan Lakshmanan Date: Sat, 5 Apr 2025 17:13:49 +0530 Subject: [PATCH 12/13] Rename Fabric_associations.py to fabric_associations.py --- .../msd/{Fabric_associations.py => fabric_associations.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename plugins/module_utils/msd/{Fabric_associations.py => fabric_associations.py} (100%) diff --git a/plugins/module_utils/msd/Fabric_associations.py b/plugins/module_utils/msd/fabric_associations.py similarity index 100% rename from plugins/module_utils/msd/Fabric_associations.py rename to plugins/module_utils/msd/fabric_associations.py From 4f9a1a6e23c7a25f5e0517148780abee25c3211a Mon Sep 17 00:00:00 2001 From: prabahal Date: Mon, 14 Apr 2025 23:51:22 +0530 Subject: [PATCH 13/13] UT/IT issue fix and UT addition --- plugins/module_utils/msd/add_child_fab.py | 58 +- plugins/module_utils/msd/delete_child_fab.py | 52 +- .../module_utils/msd/fabric_associations.py | 9 + plugins/module_utils/msd/query_child_fab.py | 16 +- plugins/modules/dcnm_fabric_member.py | 77 +- .../tests/dcnm_fabric_member.yaml | 247 +- .../dcnm/fixtures/test_fabric_member.json | 323 +++ .../modules/dcnm/test_dcnm_fabric_member.py | 2271 +++++++++++++++++ 8 files changed, 2993 insertions(+), 60 deletions(-) create mode 100644 tests/unit/modules/dcnm/fixtures/test_fabric_member.json create mode 100644 tests/unit/modules/dcnm/test_dcnm_fabric_member.py diff --git a/plugins/module_utils/msd/add_child_fab.py b/plugins/module_utils/msd/add_child_fab.py index 6517fcd73..ab4a4b25e 100644 --- a/plugins/module_utils/msd/add_child_fab.py +++ b/plugins/module_utils/msd/add_child_fab.py @@ -16,7 +16,7 @@ from __future__ import absolute_import, division, print_function __metaclass__ = type -__author__ = "Prabahal" +__author__ = "prabahal" import copy import inspect @@ -25,11 +25,6 @@ from ..common.api.v1.lan_fabric.rest.control.fabrics.msd.msd import \ EpChildFabricAdd - -# Import Results() only for the case where the user has not set Results() -# prior to calling commit(). In this case, we instantiate Results() -# in _validate_commit_parameters() so that we can register the failure -# in commit(). from ..common.results import Results from ...module_utils.fabric.common import FabricCommon @@ -48,12 +43,52 @@ def __init__(self): self.ep_fabric_add = EpChildFabricAdd() msg = "ENTERED childFabricAdd()" self.log.debug(msg) + self._fabric_names = None self.deploy = False + @property + def fabric_names(self): + """ + ### Summary + - setter: return the fabric names + - getter: set the fabric_names + + ### Raises + - ``ValueError`` if: + - ``value`` is not a list. + - ``value`` is an empty list. + - ``value`` is not a list of strings. + + """ + return self._fabric_names + + @fabric_names.setter + def fabric_names(self, value): + method_name = inspect.stack()[0][3] + if not isinstance(value, list): + msg = f"{self.class_name}.{method_name}: " + msg += "fabric_names must be a list. " + msg += f"got {type(value).__name__} for " + msg += f"value {value}" + raise ValueError(msg) + if len(value) == 0: + msg = f"{self.class_name}.{method_name}: " + msg += "fabric_names must be a list of at least one string. " + msg += f"got {value}." + raise ValueError(msg) + for item in value: + if not isinstance(item, str): + msg = f"{self.class_name}.{method_name}: " + msg += "fabric_names must be a list of strings. " + msg += f"got {type(item).__name__} for " + msg += f"value {item}" + raise ValueError(msg) + self._fabric_names = value + def commit(self, payload): """ ### Summary - - Add child fabrics to Mentioned Parent fabric. + - Add child fabrics to Mentioned MSD fabric. ### Raises - ``ValueError`` if: @@ -66,7 +101,6 @@ def commit(self, payload): try: self._validate_commit_parameters() except ValueError as error: - # pylint: disable=no-member self.results.action = self.action self.results.changed = False self.results.failed = True @@ -78,7 +112,6 @@ def commit(self, payload): self.results.state = "Added" self.results.register_task_result() raise ValueError(error) from error - # pylint: enable=no-member try: self.rest_send.path = self.ep_fabric_add.path @@ -118,26 +151,21 @@ def _validate_commit_parameters(self): - validate the parameters for commit - raise ``ValueError`` if ``fabric_names`` is not set """ - method_name = inspect.stack()[0][3] # pylint: disable=unused-variable + method_name = inspect.stack()[0][3] if self.fabric_names is None: msg = f"{self.class_name}.{method_name}: " msg += "fabric_names must be set prior to calling commit." raise ValueError(msg) - # pylint: disable=no-member if self.rest_send is None: msg = f"{self.class_name}.{method_name}: " msg += "rest_send must be set prior to calling commit." raise ValueError(msg) - # pylint: disable=access-member-before-definition - # pylint: disable=attribute-defined-outside-init if self.results is None: # Instantiate Results() only to register the failure self.results = Results() msg = f"{self.class_name}.{method_name}: " msg += "results must be set prior to calling commit." raise ValueError(msg) - # pylint: enable=access-member-before-definition - # pylint: enable=attribute-defined-outside-init diff --git a/plugins/module_utils/msd/delete_child_fab.py b/plugins/module_utils/msd/delete_child_fab.py index 69cb0465a..2fdeedd53 100644 --- a/plugins/module_utils/msd/delete_child_fab.py +++ b/plugins/module_utils/msd/delete_child_fab.py @@ -1,4 +1,4 @@ -# Copyright (c) 2024 Cisco and/or its affiliates. +# 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. @@ -35,7 +35,7 @@ class childFabricDelete(FabricCommon): """ - Delete child fabrics from Parent fabrics + Delete child fabrics from MSD fabrics """ def __init__(self): @@ -49,16 +49,53 @@ def __init__(self): self.ep_child_fabric_delete = EpChildFabricExit() self._fabric_names = None - self._cannot_delete_fabric_reason = None - msg = "ENTERED childFabricDelete()" self.log.debug(msg) self.deploy = False + @property + def fabric_names(self): + """ + ### Summary + - setter: return the fabric names + - getter: set the fabric_names + + ### Raises + - ``ValueError`` if: + - ``value`` is not a list. + - ``value`` is an empty list. + - ``value`` is not a list of strings. + + """ + return self._fabric_names + + @fabric_names.setter + def fabric_names(self, value): + method_name = inspect.stack()[0][3] + if not isinstance(value, list): + msg = f"{self.class_name}.{method_name}: " + msg += "fabric_names must be a list. " + msg += f"got {type(value).__name__} for " + msg += f"value {value}" + raise ValueError(msg) + if len(value) == 0: + msg = f"{self.class_name}.{method_name}: " + msg += "fabric_names must be a list of at least one string. " + msg += f"got {value}." + raise ValueError(msg) + for item in value: + if not isinstance(item, str): + msg = f"{self.class_name}.{method_name}: " + msg += "fabric_names must be a list of strings. " + msg += f"got {type(item).__name__} for " + msg += f"value {item}" + raise ValueError(msg) + self._fabric_names = value + def commit(self, payload): """ ### Summary - - Delete fabrics in Mentioned Parent fabric. + - Remove fabrics in Mentioned MSD fabric. ### Raises - ``ValueError`` if: @@ -82,14 +119,12 @@ def commit(self, payload): self.results.state = "Deleted" self.results.register_task_result() raise ValueError(error) from error - # pylint: enable=no-member try: self.rest_send.path = self.ep_child_fabric_delete.path self.rest_send.verb = self.ep_child_fabric_delete.verb self.rest_send.payload = payload self.rest_send.save_settings() - self.rest_send.check_mode = False self.rest_send.timeout = 1 self.rest_send.commit() self.rest_send.restore_settings() @@ -122,14 +157,13 @@ def _validate_commit_parameters(self): - validate the parameters for commit - raise ``ValueError`` if ``fabric_names`` is not set """ - method_name = inspect.stack()[0][3] # pylint: disable=unused-variable + method_name = inspect.stack()[0][3] if self.fabric_names is None: msg = f"{self.class_name}.{method_name}: " msg += "fabric_names must be set prior to calling commit." raise ValueError(msg) - # pylint: disable=no-member if self.rest_send is None: msg = f"{self.class_name}.{method_name}: " msg += "rest_send must be set prior to calling commit." diff --git a/plugins/module_utils/msd/fabric_associations.py b/plugins/module_utils/msd/fabric_associations.py index 8dc815ca2..dc40fc323 100644 --- a/plugins/module_utils/msd/fabric_associations.py +++ b/plugins/module_utils/msd/fabric_associations.py @@ -69,6 +69,7 @@ def verify_refresh_has_been_called(self, attempted_method_name): def refresh(self): + method_name = inspect.stack()[0][3] self.ep_fabrics_associations = EpFabricAssociations() self.rest_send.path = self.ep_fabrics_associations.path self.rest_send.verb = self.ep_fabrics_associations.verb @@ -81,8 +82,16 @@ def refresh(self): msg = f"self.data: {json.dumps(self.data, indent=4, sort_keys=True)}" self.log.debug(msg) self.refreshed = True + self.results.response_current = self.rest_send.response_current self.results.response = self.rest_send.response_current self.results.result_current = self.rest_send.result_current self.results.result = self.rest_send.result_current + self.results.register_task_result() + + if self.results.result_current.get("success", None) is False: + msg = f"{self.class_name}: {method_name}: " + msg += "Fabric Association response from NDFC controller returns failure. " + msg += "Cannot proceed further." + raise ValueError(msg) diff --git a/plugins/module_utils/msd/query_child_fab.py b/plugins/module_utils/msd/query_child_fab.py index fbe2107b3..26baa2fbe 100644 --- a/plugins/module_utils/msd/query_child_fab.py +++ b/plugins/module_utils/msd/query_child_fab.py @@ -18,7 +18,6 @@ import copy import inspect import logging - from ..common.results import Results from ..msd.fabric_associations import FabricAssociations @@ -38,9 +37,7 @@ class childFabricQuery(): def __init__(self): self.class_name = self.__class__.__name__ self.action = "child_fabric_query" - self.log = logging.getLogger(f"dcnm.{self.class_name}") - self._fabric_names = None self._fabric_associations = [] self.rest_send = None @@ -97,20 +94,18 @@ def _validate_commit_parameters(self): - ``rest_send`` is not set. - ``results`` is not set. """ - method_name = inspect.stack()[0][3] # pylint: disable=unused-variable + method_name = inspect.stack()[0][3] if self.fabric_names is None: msg = f"{self.class_name}.{method_name}: " msg += "fabric_names must be set before calling commit." raise ValueError(msg) - # pylint: disable=no-member if self.rest_send is None: msg = f"{self.class_name}.{method_name}: " msg += "rest_send must be set before calling commit." raise ValueError(msg) - # pylint: disable=access-member-before-definition if self.results is None: # Instantiate Results() to register the failure self.results = Results() @@ -131,7 +126,6 @@ def commit(self): try: self._validate_commit_parameters() except ValueError as error: - # pylint: disable=no-member self.results.action = self.action self.results.changed = False self.results.failed = True @@ -143,7 +137,6 @@ def commit(self): self.results.state = "query" self.results.register_task_result() raise ValueError(error) from error - # pylint: enable=no-member try: self.fab_association = FabricAssociations() self.fab_association.rest_send = self.rest_send @@ -154,7 +147,6 @@ def commit(self): self.data = {} if self.fab_association.fabric_association_data is None: - # The DATA key should always be present. We should never hit this. return for item in self.fab_association.fabric_association_data: fabric_name = item.get("fabricName", None) @@ -170,16 +162,18 @@ def commit(self): msg = f"filtered data : {add_to_diff}" self.log.debug(msg) - # pylint: disable=no-member self.results.action = self.action self.results.check_mode = self.rest_send.check_mode self.results.state = self.rest_send.state - # pylint: disable=no-member self.results.diff_current = add_to_diff + if not add_to_diff: self.results.result_current = {"success": True, "found": False} + if self.rest_send.response_current.get("RETURN_CODE") != 200: + self.results.result_current = {"success": False, "found": False} else: self.results.result_current = {"success": True, "found": True} + self.results.response_current = copy.deepcopy( self.rest_send.response_current ) diff --git a/plugins/modules/dcnm_fabric_member.py b/plugins/modules/dcnm_fabric_member.py index e0e37fed2..87e65e019 100644 --- a/plugins/modules/dcnm_fabric_member.py +++ b/plugins/modules/dcnm_fabric_member.py @@ -220,14 +220,19 @@ def verify_child_fabric_is_already_member(self, item) -> bool: def validate_input(self): method_name = inspect.stack()[0][3] + if self.config is None: + msg = f"{self.class_name}.{method_name}: " + msg += "params is missing config parameter." + raise ValueError(msg) fab_member_spec = dict( FABRIC_NAME=dict(required=True, type="str"), CHILD_FABRIC_NAME=dict(required=True, type="str"), DEPLOY=dict(type="bool", default=False), ) + fab_mem_info, invalid_params = validate_list_of_dicts(self.config, fab_member_spec, None) if invalid_params: - msg = "Invalid parameters in playbook: {invalid_params}" + msg = f"Invalid parameters in playbook: {invalid_params} " msg += "while processing config \n" raise ValueError(msg) for config in self.config: @@ -246,7 +251,6 @@ def validate_input(self): msg = f"{self.class_name}: " msg += "Playbook configuration for FABRIC_NAME or CHILD_FABRIC_NAME " msg += "contains an invalid FABRIC_NAME. " - # error below already contains a period "." at the end msg += f"Error detail: {error} " msg += f"Bad configuration: {config}." raise ValueError(msg) from error @@ -369,6 +373,13 @@ def __init__(self, params): self.log.debug(msg) self.data = {} + def refresh_fab_association_data(self): + for item in self.fab_association.fabric_association_data: + fabric_name = item.get("fabricName", None) + if fabric_name is None: + continue + self.data[fabric_name] = item + def commit(self) -> None: """ ### Summary @@ -386,16 +397,16 @@ def commit(self) -> None: self.validate_input() self.get_want() - self.fab_association = FabricAssociations() - self.fab_association.rest_send = self.rest_send - self.fab_association.refresh() + try: + self.fab_association = FabricAssociations() + self.fab_association.rest_send = self.rest_send + self.fab_association.refresh() + except (TypeError, ValueError) as error: + raise ValueError(error) from error + msg = f"Fab association data{self.fab_association.fabric_association_data}" self.log.debug(msg) - for item in self.fab_association.fabric_association_data: - fabric_name = item.get("fabricName", None) - if fabric_name is None: - continue - self.data[fabric_name] = item + self.refresh_fab_association_data() self.verify_msd_fab_exists_in_controller() self.verify_msd_fab_type() @@ -405,15 +416,19 @@ def commit(self) -> None: self.results.result_current = {"success": True, "changed": False} msg = "Given child fabric is already not a member of MSD fabric" self.results.response_current = {"RETURN_CODE": 200, "MESSAGE": msg} + self.results.diff_current = {} + self.results.state = self.state + self.results.changed = False + self.results.failed = False self.results.register_task_result() else: self.delete = childFabricDelete() self.delete.rest_send = self.rest_send + self.delete.rest_send.check_mode = self.check_mode self.delete.results = self.results fabric_names_to_delete = [] for want in self.payloads: - fabric_names_to_delete.append(want["sourceFabric"]) fabric_names_to_delete.append(want["destFabric"]) try: self.delete.fabric_names = fabric_names_to_delete @@ -424,6 +439,9 @@ def commit(self) -> None: self.delete.commit(item) except ValueError as error: raise ValueError(f"{error}") from error + self.fab_association.refreshed = False + self.fab_association.refresh() + self.refresh_fab_association_data() class Merged(childCommon): @@ -460,6 +478,13 @@ def __init__(self, params): self.log.debug(msg) self.data = {} + def refresh_fab_association_data(self): + for item in self.fab_association.fabric_association_data: + fabric_name = item.get("fabricName", None) + if fabric_name is None: + continue + self.data[fabric_name] = item + def commit(self) -> None: """ ### Summary @@ -480,16 +505,16 @@ def commit(self) -> None: self.get_want() self.add.results = self.results - self.fab_association = FabricAssociations() - self.fab_association.rest_send = self.rest_send - self.fab_association.refresh() + try: + self.fab_association = FabricAssociations() + self.fab_association.rest_send = self.rest_send + self.fab_association.refresh() + except (TypeError, ValueError) as error: + raise ValueError(error) from error + msg = f"Fab association data{self.fab_association.fabric_association_data}" self.log.debug(msg) - for item in self.fab_association.fabric_association_data: - fabric_name = item.get("fabricName", None) - if fabric_name is None: - continue - self.data[fabric_name] = item + self.refresh_fab_association_data() self.verify_msd_fab_exists_in_controller() self.verify_msd_fab_type() @@ -501,9 +526,14 @@ def commit(self) -> None: self.results.result_current = {"success": True, "changed": False} msg = "Child fabric is already member of MSD fabric." self.results.response_current = {"RETURN_CODE": 200, "MESSAGE": msg} + self.results.diff_current = {} + self.results.state = self.state + self.results.changed = False + self.results.failed = False self.results.register_task_result() else: self.add.rest_send = self.rest_send + self.add.rest_send.check_mode = self.check_mode fabric_names_to_add = [] for want in self.payloads: fabric_names_to_add.append(want["sourceFabric"]) @@ -517,6 +547,10 @@ def commit(self) -> None: except ValueError as error: raise ValueError(f"{error}") from error + self.fab_association.refreshed = False + self.fab_association.refresh() + self.refresh_fab_association_data() + class Query(childCommon): """ @@ -528,7 +562,7 @@ class Query(childCommon): - ``ValueError`` if: - The playbook parameters are invalid. - The controller returns an error when attempting to retrieve - the fabric details. + the fabric Association details. """ def __init__(self, params): @@ -558,7 +592,6 @@ def verify_payload(self): msg = f"{self.class_name}: " msg += "Playbook configuration for FABRIC_NAME is missing or " msg += "contains an invalid FABRIC_NAME. " - # error below already contains a period "." at the end msg += f"Error detail: {error} " msg += f"Bad configuration: {config}." raise ValueError(msg) from error @@ -581,6 +614,7 @@ def commit(self) -> None: self.get_want() fabric_query = childFabricQuery() fabric_query.rest_send = self.rest_send + fabric_query.rest_send.check_mode = self.check_mode fabric_query.results = self.results fabric_names_to_query = [] @@ -626,7 +660,6 @@ def main(): params = copy.deepcopy(ansible_module.params) params["check_mode"] = ansible_module.check_mode - # Logging setup try: log = Log() log.commit() diff --git a/tests/integration/targets/dcnm_fabric_member/tests/dcnm_fabric_member.yaml b/tests/integration/targets/dcnm_fabric_member/tests/dcnm_fabric_member.yaml index 47c46f000..14f0ae133 100644 --- a/tests/integration/targets/dcnm_fabric_member/tests/dcnm_fabric_member.yaml +++ b/tests/integration/targets/dcnm_fabric_member/tests/dcnm_fabric_member.yaml @@ -2,7 +2,7 @@ # RUNTIME ################################################################################ # Recent run times (MM:SS.ms): -# 02:17.62 +# 02:57.62 ################################################################################ # DESCRIPTION - CHILD FABRIC TEST # @@ -44,6 +44,8 @@ # 8. Idempotence : Delete the child Fabrics from MSD fabrics and verify. # 9. Add Child fabrics into MSD with Config-save #10. Delete child fabrics from MSD with config-save +#11. Add same child fabric into MSD Fabric twice - 2nd config is idempotent +#12. Delete same child fabric from MSD Fabric twice - 2nd config is idempotent ################################################################################ # CLEANUP ################################################################################ @@ -1503,8 +1505,247 @@ - result.result[7].changed == true - result.result[7].success == true - result.result[7].sequence_number == 8 -################################################################################ -# MERGED - CLEANUP - Delete the fabrics + +########################################################################################### +# MERGED - Add same child fabric again in the same playbook. 2nd config should be idempotent +############################################################################################ +#changed: [10.78.210.227] => { +# "changed": true, +# "diff": [ +# { +# "destFabric": "msd_2", +# "sequence_number": 1, +# "sourceFabric": "child2" +# }, +# { +# "destFabric": "msd_2", +# "sequence_number": 2, +# "sourceFabric": "child2" +# } +# ], +# "invocation": { +# "module_args": { +# "config": [ +# { +# "CHILD_FABRIC_NAME": "child2", +# "FABRIC_NAME": "msd_2" +# }, +# { +# "CHILD_FABRIC_NAME": "child2", +# "FABRIC_NAME": "msd_2" +# } +# ], +# "state": "merged" +# } +# }, +# "metadata": [ +# { +# "action": "child_fabric_add", +# "check_mode": false, +# "sequence_number": 1, +# "state": "merged" +# }, +# { +# "action": "child_fabric_add", +# "check_mode": false, +# "sequence_number": 2, +# "state": "merged" +# } +# ], +# "response": [ +# { +# "DATA": {}, +# "MESSAGE": "OK", +# "METHOD": "POST", +# "REQUEST_PATH": "https://10.78.210.227:443/appcenter/cisco/ndfc/api/v1/lan-fabric/rest/control/fabrics/msdAdd", +# "RETURN_CODE": 200, +# "sequence_number": 1 +# }, +# { +# "MESSAGE": "Child fabric is already member of MSD fabric.", +# "RETURN_CODE": 200, +# "sequence_number": 2 +# } +# ], +# "result": [ +# { +# "changed": true, +# "sequence_number": 1, +# "success": true +# }, +# { +# "changed": false, +# "sequence_number": 2, +# "success": true +# } +# ] +#} +########################################################################################### +- name: Merged - TEST - Merge same child fabrics into MSD Fabric twice - 2nd config is idempotent + cisco.dcnm.dcnm_fabric_member: + state: merged + config: + - FABRIC_NAME: "{{ msd_fabric_name_1 }}" + CHILD_FABRIC_NAME: "{{ child_fabric_name_11 }}" + - FABRIC_NAME: "{{ msd_fabric_name_1 }}" + CHILD_FABRIC_NAME: "{{ child_fabric_name_11 }}" + register: result +- debug: + var: result +- assert: + that: + - result.changed == true + - result.failed == false + - (result.diff | length) == 2 + - result.diff[0].sequence_number == 1 + - result.diff[0].destFabric == "{{ msd_fabric_name_1 }}" + - result.diff[0].sourceFabric == "{{ child_fabric_name_11 }}" + - result.diff[1].sequence_number == 2 + - (result.metadata | length) == 2 + - result.metadata[0].action == "child_fabric_add" + - result.metadata[0].check_mode == False + - result.metadata[0].sequence_number == 1 + - result.metadata[0].state == "merged" + - result.metadata[1].action == "child_fabric_add" + - result.metadata[1].check_mode == False + - result.metadata[1].sequence_number == 2 + - result.metadata[1].state == "merged" + - (result.response | length) == 2 + - result.response[0].sequence_number == 1 + - result.response[0].MESSAGE == "OK" + - result.response[0].METHOD == "POST" + - result.response[0].RETURN_CODE == 200 + - result.response[1].sequence_number == 2 + - result.response[1].MESSAGE == "Child fabric is already member of MSD fabric." + - result.response[1].RETURN_CODE == 200 + - (result.result | length) == 2 + - result.result[0].changed == true + - result.result[0].success == true + - result.result[0].sequence_number == 1 + - result.result[1].changed == false + - result.result[1].success == true + - result.result[1].sequence_number == 2 +########################################################################################### +# DELETED - Add same child fabric again in the same playbook. 2nd config should be idempotent +############################################################################################ +#changed: [10.78.210.227] => { +# "changed": true, +# "diff": [ +# { +# "destFabric": "msd_2", +# "sequence_number": 1, +# "sourceFabric": "child2" +# }, +# { +# "destFabric": "msd_2", +# "sequence_number": 2, +# "sourceFabric": "child2" +# } +# ], +# "invocation": { +# "module_args": { +# "config": [ +# { +# "CHILD_FABRIC_NAME": "child2", +# "FABRIC_NAME": "msd_2" +# }, +# { +# "CHILD_FABRIC_NAME": "child2", +# "FABRIC_NAME": "msd_2" +# } +# ], +# "state": "deleted" +# } +# }, +# "metadata": [ +# { +# "action": "child_fabric_delete", +# "check_mode": false, +# "sequence_number": 1, +# "state": "deleted" +# }, +# { +# "action": "child_fabric_delete", +# "check_mode": false, +# "sequence_number": 2, +# "state": "deleted" +# } +# ], +# "response": [ +# { +# "DATA": {}, +# "MESSAGE": "OK", +# "METHOD": "POST", +# "REQUEST_PATH": "https://10.78.210.227:443/appcenter/cisco/ndfc/api/v1/lan-fabric/rest/control/fabrics/msdExit", +# "RETURN_CODE": 200, +# "sequence_number": 1 +# }, +# { +# "MESSAGE": "Given child fabric is already not a member of MSD fabric", +# "RETURN_CODE": 200, +# "sequence_number": 2 +# } +# ], +# "result": [ +# { +# "changed": true, +# "sequence_number": 1, +# "success": true +# }, +# { +# "changed": false, +# "sequence_number": 2, +# "success": true +# } +# ] +#} +# +- name: Deleted - TEST - Delete same child fabric from MSD Fabric twice - 2nd config is idempotent + cisco.dcnm.dcnm_fabric_member: + state: deleted + config: + - FABRIC_NAME: "{{ msd_fabric_name_1 }}" + CHILD_FABRIC_NAME: "{{ child_fabric_name_11 }}" + - FABRIC_NAME: "{{ msd_fabric_name_1 }}" + CHILD_FABRIC_NAME: "{{ child_fabric_name_11 }}" + register: result +- debug: + var: result +- assert: + that: + - result.changed == true + - result.failed == false + - (result.diff | length) == 2 + - result.diff[0].sequence_number == 1 + - result.diff[0].destFabric == "{{ msd_fabric_name_1 }}" + - result.diff[0].sourceFabric == "{{ child_fabric_name_11 }}" + - result.diff[1].sequence_number == 2 + - (result.metadata | length) == 2 + - result.metadata[0].action == "child_fabric_delete" + - result.metadata[0].check_mode == False + - result.metadata[0].sequence_number == 1 + - result.metadata[0].state == "deleted" + - result.metadata[1].action == "child_fabric_delete" + - result.metadata[1].check_mode == False + - result.metadata[1].sequence_number == 2 + - result.metadata[1].state == "deleted" + - (result.response | length) == 2 + - result.response[0].sequence_number == 1 + - result.response[0].MESSAGE == "OK" + - result.response[0].METHOD == "POST" + - result.response[0].RETURN_CODE == 200 + - result.response[1].sequence_number == 2 + - result.response[1].MESSAGE == "Given child fabric is already not a member of MSD fabric" + - result.response[1].RETURN_CODE == 200 + - (result.result | length) == 2 + - result.result[0].changed == true + - result.result[0].success == true + - result.result[0].sequence_number == 1 + - result.result[1].changed == false + - result.result[1].success == true + - result.result[1].sequence_number == 2 +############################################################################### +# CLEANUP - Delete the fabrics ################################################################################ # Expected result #ok: [10.78.210.227] => { diff --git a/tests/unit/modules/dcnm/fixtures/test_fabric_member.json b/tests/unit/modules/dcnm/fixtures/test_fabric_member.json new file mode 100644 index 000000000..7f93ee511 --- /dev/null +++ b/tests/unit/modules/dcnm/fixtures/test_fabric_member.json @@ -0,0 +1,323 @@ +{ + "get_controller_version": + { + "RETURN_CODE": 200, + "METHOD": "GET", + "REQUEST_PATH": "https://10.78.210.227:443/appcenter/cisco/ndfc/api/v1/fm/about/version", + "MESSAGE": "OK", + "DATA": {"version": "12.2.2.241", "mode": "LAN", "isMediaController": "False", "dev": "False", "isHaEnabled": "False", "install": "EASYFABRIC", "uuid": "", "is_upgrade_inprogress": "False"} + }, + "Fabric_association_get_data": [ + { + "fabricId": 2, + "fabricName": "test_fab", + "fabricParent": "None", + "fabricState": "standalone", + "fabricTechnology": "VXLANFabric", + "fabricType": "Switch_Fabric" + }, + { + "fabricId": 6, + "fabricName": "nac-site1", + "fabricParent": "None", + "fabricState": "standalone", + "fabricTechnology": "VXLANFabric", + "fabricType": "Switch_Fabric" + }, + { + "fabricId": 7, + "fabricName": "child13", + "fabricParent": "MSD_Fabric1", + "fabricState": "member", + "fabricTechnology": "VXLANFabric", + "fabricType": "Switch_Fabric" + }, + { + "fabricId": 8, + "fabricName": "child12", + "fabricParent": "MSD_Fabric1", + "fabricState": "member", + "fabricTechnology": "VXLANFabric", + "fabricType": "Switch_Fabric" + }, + { + "fabricId": 9, + "fabricName": "MSD_Fabric1", + "fabricParent": "None", + "fabricState": "msd", + "fabricTechnology": "VXLANFabric", + "fabricType": "MSD" + }, + { + "fabricId": 10, + "fabricName": "child11", + "fabricParent": "MSD_Fabric1", + "fabricState": "member", + "fabricTechnology": "External", + "fabricType": "External" + }, + { + "fabricId": 13, + "fabricName": "IT_Prabahal", + "fabricParent": "None", + "fabricState": "standalone", + "fabricTechnology": "VXLANFabric", + "fabricType": "Switch_Fabric" + }, + { + "fabricId": 16, + "fabricName": "MSD_Fabric2", + "fabricParent": "None", + "fabricState": "msd", + "fabricTechnology": "VXLANFabric", + "fabricType": "MSD" + }, + { + "fabricId": 19, + "fabricName": "child21", + "fabricParent": "MSD_Fabric2", + "fabricState": "member", + "fabricTechnology": "VXLANFabric", + "fabricType": "Switch_Fabric" + }, + { + "fabricId": 20, + "fabricName": "msd_3", + "fabricParent": "None", + "fabricState": "msd", + "fabricTechnology": "VXLANFabric", + "fabricType": "MSD" + }, + { + "fabricId": 21, + "fabricName": "child3", + "fabricParent": "None", + "fabricState": "standalone", + "fabricTechnology": "VXLANFabric", + "fabricType": "Switch_Fabric" + }, + { + "fabricId": 22, + "fabricName": "dup1", + "fabricParent": "None", + "fabricState": "standalone", + "fabricTechnology": "VXLANFabric", + "fabricType": "Switch_Fabric" + }, + { + "fabricId": 23, + "fabricName": "dup2", + "fabricParent": "None", + "fabricState": "standalone", + "fabricTechnology": "VXLANFabric", + "fabricType": "Switch_Fabric" + }, + { + "fabricId": 24, + "fabricName": "child22", + "fabricParent": "MSD_Fabric2", + "fabricState": "member", + "fabricTechnology": "VXLANFabric", + "fabricType": "Switch_Fabric" + } + ], + "Fabric_association_get_response": + { + "RETURN_CODE": 200, + "METHOD": "GET", + "REQUEST_PATH": "https://10.78.210.227:443/appcenter/cisco/ndfc/api/v1/lan-fabric/rest/control/fabrics/msd/fabric-associations", + "MESSAGE": "OK", + "DATA": [{"fabricId": 2, "fabricName": "test_fab", "fabricType": "Switch_Fabric", "fabricState": "standalone", "fabricParent": "None", "fabricTechnology": "VXLANFabric"}] + }, + "Fabric_association_get_response_00019": + { + "RETURN_CODE": 200, + "METHOD": "GET", + "REQUEST_PATH": "https://10.78.210.227:443/appcenter/cisco/ndfc/api/v1/lan-fabric/rest/control/fabrics/msd/fabric-associations", + "MESSAGE": "OK", + "DATA": [{ + "fabricId": 16, + "fabricName": "f1", + "fabricParent": "None", + "fabricState": "msd", + "fabricTechnology": "VXLANFabric", + "fabricType": "MSD" + }] + }, + "Fabric_association_get_response_exists": + { + "RETURN_CODE": 200, + "METHOD": "GET", + "REQUEST_PATH": "https://10.78.210.227:443/appcenter/cisco/ndfc/api/v1/lan-fabric/rest/control/fabrics/msd/fabric-associations", + "MESSAGE": "OK", + "DATA": [{ + "fabricId": 16, + "fabricName": "f1", + "fabricParent": "None", + "fabricState": "msd", + "fabricTechnology": "VXLANFabric", + "fabricType": "MSD" + }, + { + "fabricId": 19, + "fabricName": "child", + "fabricParent": "f1", + "fabricState": "member", + "fabricTechnology": "VXLANFabric", + "fabricType": "Switch_Fabric" + }] + }, + "Fabric_association_get_valid_data": + { + "RETURN_CODE": 200, + "METHOD": "GET", + "REQUEST_PATH": "https://10.78.210.227:443/appcenter/cisco/ndfc/api/v1/lan-fabric/rest/control/fabrics/msd/fabric-associations", + "DATA": [ + { + "fabricId": 19, + "fabricName": "f1", + "fabricParent": "None", + "fabricState": "member", + "fabricTechnology": "VXLANFabric", + "fabricType": "Switch_Fabric" + }], + "MESSAGE": "OK" + }, + "Fabric_association_get_data_empty": + { + "RETURN_CODE": 200, + "METHOD": "GET", + "REQUEST_PATH": "https://10.78.210.227:443/appcenter/cisco/ndfc/api/v1/lan-fabric/rest/control/fabrics/msd/fabric-associations", + "MESSAGE": "OK", + "DATA": [] + }, + "childFabric_delete_response": + { + "changed": true, + "diff": [ + { + "destFabric": "msd", + "sequence_number": 1, + "sourceFabric": "child" + } + ], + "invocation": { + "module_args": { + "config": [ + { + "CHILD_FABRIC_NAME": "child", + "FABRIC_NAME": "msd" + } + ], + "state": "deleted" + } + }, + "metadata": [ + { + "action": "child_fabric_delete", + "check_mode": false, + "sequence_number": 1, + "state": "deleted" + } + ], + "response": [ + { + "DATA": {}, + "MESSAGE": "OK", + "METHOD": "POST", + "REQUEST_PATH": "https://10.78.210.227:443/appcenter/cisco/ndfc/api/v1/lan-fabric/rest/control/fabrics/msdExit", + "RETURN_CODE": 200, + "sequence_number": 1 + } + ], + "result": [ + { + "changed": true, + "sequence_number": 1, + "success": true + } + ] + }, + "childFabric_delete_response_2": + { + "DATA": {}, + "MESSAGE": "OK", + "METHOD": "POST", + "REQUEST_PATH": "https://10.78.210.227:443/appcenter/cisco/ndfc/api/v1/lan-fabric/rest/control/fabrics/msdExit", + "RETURN_CODE": 200, + "sequence_number": 1 + }, + "childFabric_add_response": + { + "DATA": {}, + "MESSAGE": "OK", + "METHOD": "POST", + "REQUEST_PATH": "https://10.78.210.227:443/appcenter/cisco/ndfc/api/v1/lan-fabric/rest/control/fabrics/msdAdd", + "RETURN_CODE": 200, + "sequence_number": 1 + }, + "childFabric_nok_get_response": + { + "DATA": [], + "MESSAGE": "NOK", + "METHOD": "GET", + "REQUEST_PATH": "https://10.78.210.227:443/appcenter/cisco/ndfc/api/v1/lan-fabric/rest/control/fabrics/msd/fabric-associations", + "RETURN_CODE": 500 + }, + "childFabric_delete_response_3": + { + "DATA": {}, + "MESSAGE": "NOK", + "METHOD": "POST", + "REQUEST_PATH": "https://10.78.210.227:443/appcenter/cisco/ndfc/api/v1/lan-fabric/rest/control/fabrics/msdExit", + "RETURN_CODE": 500, + "sequence_number": 1 + }, + "childFabric_add_response_3": + { + "DATA": {}, + "MESSAGE": "NOK", + "METHOD": "POST", + "REQUEST_PATH": "https://10.78.210.227:443/appcenter/cisco/ndfc/api/v1/lan-fabric/rest/control/fabrics/msdAdd", + "RETURN_CODE": 500, + "sequence_number": 1 + }, + "Fabric_association_get_failure_response": + { + "RETURN_CODE": 404, + "METHOD": "GET", + "REQUEST_PATH": "https://10.78.210.227:443/appcenter/cisco/ndfc/api/v1/lan-fabric/rest/control/fabrics/msd/fabric-associations", + "MESSAGE": "NOK" + }, + "Fabric_association_get_response_00018": + { + "RETURN_CODE": 200, + "METHOD": "GET", + "REQUEST_PATH": "https://10.78.210.227:443/appcenter/cisco/ndfc/api/v1/lan-fabric/rest/control/fabrics/msd/fabric-associations", + "MESSAGE": "OK", + "DATA": [{ + "fabricId": 16, + "fabricName": "f2", + "fabricParent": "None", + "fabricState": "msd", + "fabricTechnology": "VXLANFabric", + "fabricType": "MSD" + }, + { + "fabricId": 16, + "fabricName": "f1", + "fabricParent": "None", + "fabricState": "msd", + "fabricTechnology": "VXLANFabric", + "fabricType": "MSD" + }, + { + "fabricId": 19, + "fabricName": "child2", + "fabricParent": "f2", + "fabricState": "member", + "fabricTechnology": "VXLANFabric", + "fabricType": "Switch_Fabric" + }] + } +} diff --git a/tests/unit/modules/dcnm/test_dcnm_fabric_member.py b/tests/unit/modules/dcnm/test_dcnm_fabric_member.py new file mode 100644 index 000000000..0595d2c18 --- /dev/null +++ b/tests/unit/modules/dcnm/test_dcnm_fabric_member.py @@ -0,0 +1,2271 @@ +# Copyright (c) 2025 Cisco and/or its affiliates. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# See the following regarding *_fixture imports +# https://pylint.pycqa.org/en/latest/user_guide/messages/warning/redefined-outer-name.html +# Due to the above, we also need to disable unused-import +# Also, fixtures need to use *args to match the signature of the function they are mocking +# pylint: disable=unused-import +# pylint: disable=redefined-outer-name +# pylint: disable=protected-access +# pylint: disable=unused-argument +# pylint: disable=invalid-name + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +__copyright__ = "Copyright (c) 2025 Cisco and/or its affiliates." +__author__ = "Prabahal" + +import inspect +import pytest +import os +import json +import sys + +from ansible_collections.cisco.dcnm.plugins.module_utils.common.response_handler import \ + ResponseHandler +from ansible_collections.cisco.dcnm.plugins.module_utils.common.rest_send_v2 import \ + RestSend +from ansible_collections.cisco.dcnm.plugins.module_utils.common.results import \ + Results +from ansible_collections.cisco.dcnm.plugins.module_utils.common.sender_file import \ + Sender +from ansible_collections.cisco.dcnm.tests.unit.module_utils.common.common_utils import \ + ResponseGenerator +from ansible_collections.cisco.dcnm.tests.unit.modules.dcnm.dcnm_fabric.utils import ( + MockAnsibleModule, does_not_raise) + +from ansible_collections.cisco.dcnm.tests.unit.modules.dcnm.dcnm_fabric.fixture import \ + load_fixture + +from ansible_collections.cisco.dcnm.plugins.module_utils.msd.add_child_fab import childFabricAdd +from ansible_collections.cisco.dcnm.plugins.module_utils.msd.delete_child_fab import childFabricDelete + +from ansible_collections.cisco.dcnm.plugins.module_utils.msd.query_child_fab import childFabricQuery +from ansible_collections.cisco.dcnm.plugins.modules.dcnm_fabric_member import Query +from ansible_collections.cisco.dcnm.plugins.modules.dcnm_fabric_member import Deleted +from ansible_collections.cisco.dcnm.plugins.modules.dcnm_fabric_member import Merged + + +params = {'state': 'query', "check_mode": False} + + +# Fixtures path +fixture_path = os.path.join(os.path.dirname(__file__), "fixtures") + + +@pytest.fixture(name="child_fabric_query") +def fabric_member_query_fixture(): + """ + Return childFabricQuery() instance. + """ + return childFabricQuery() + + +@pytest.fixture(name="child_fabric_delete") +def fabric_member_delete_fixture(): + """ + Return childFabricDelete() instance. + """ + return childFabricDelete() + + +@pytest.fixture(name="child_fabric_add") +def fabric_member_add_fixture(): + """ + Return childFabricAdd() instance. + """ + return childFabricAdd() + + +def load_fixture(filename): + """ + load test inputs from json files + """ + path = os.path.join(fixture_path, f"{filename}.json") + + try: + with open(path, encoding="utf-8") as file_handle: + data = file_handle.read() + except IOError as exception: + msg = f"Exception opening test input file {filename}.json : " + msg += f"Exception detail: {exception}" + print(msg) + sys.exit(1) + + try: + fixture = json.loads(data) + except json.JSONDecodeError as exception: + msg = "Exception reading JSON contents in " + msg += f"test input file {filename}.json : " + msg += f"Exception detail: {exception}" + print(msg) + sys.exit(1) + + return fixture + + +@pytest.fixture +def mock_params(): + return { + 'check_mode': True, + 'config': [{'FABRIC_NAME': 'fabric1', 'CHILD_FABRIC_NAME': 'child1'}], + 'state': 'merged' + } + + +def fabric_member_data(key: str) -> dict[str, str]: + """ + Return data from test_child_fabric.json for unit tests. + """ + data_file = "test_fabric_member" + data = load_fixture(data_file).get(key) + print(f"fabric member mock data : {key} : {data}") + return data + + +def test_fab_mem_query_init_00001(): + query_instance = Query(params) + assert query_instance.class_name == 'Query' + assert query_instance.action == 'child_fabric_query' + assert 'query' in query_instance._implemented_states + + +def test_fab_mem_query_verify_payload_none_config_00002(): + query_instance = Query(params) + with pytest.raises(ValueError) as excinfo: + query_instance.verify_payload() + assert "Playbook configuration for FABRIC_NAME is missing" in str(excinfo.value) + + +@pytest.mark.parametrize( + "invalid_fabric_name", + ["@123", "", "$abd#", "-!`", "%^&", None], +) +def test_fab_mem_query_verify_payload_invalid_fabric_name_00003(invalid_fabric_name): + t_params = {'config': [{'FABRIC_NAME': {invalid_fabric_name}}]} + t_params.update(params) + query_instance = Query(t_params) + with pytest.raises(ValueError) as excinfo: + query_instance.verify_payload() + assert "contains an invalid FABRIC_NAME" in str(excinfo.value) + + +def test_fab_mem_query_verify_payload_invalid_config_00004(): + t_params = {'config': None} + t_params.update(params) + query_instance = Query(t_params) + with pytest.raises(ValueError) as excinfo: + query_instance.verify_payload() + assert "Playbook configuration for FABRIC_NAME is missing" in str(excinfo.value) + + +def test_fab_mem_query_verify_payload_invalid_config_00005(): + t_params = {'config': [{'FABRIC_NAME': ''}]} + t_params.update(params) + query_instance = Query(t_params) + with pytest.raises(ValueError) as excinfo: + query_instance.verify_payload() + assert "Playbook configuration for FABRIC_NAME is missing" in str(excinfo.value) + + +def test_fab_mem_query_commit_verify_payload_failure_00006(): + t_params = { + 'config': None, # This will cause verify_payload to raise an error + } + t_params.update(params) + query_instance = Query(t_params) + query_instance.rest_send = RestSend(t_params) + with pytest.raises(ValueError) as excinfo: + query_instance.commit() + assert "Playbook configuration for FABRIC_NAME is missing" in str(excinfo.value) + + +def test_fab_mem_query_00007(child_fabric_query) -> None: + """ + ### Classes and Methods + - childFabricQuery + - __init__() + + ### Test + + - Class attributes are initialized to expected values + - Exception is not raised + """ + with does_not_raise(): + instance = child_fabric_query + assert instance.class_name == "childFabricQuery" + assert instance.action == "child_fabric_query" + assert instance.fabric_names is None + + +def test_fab_mem_query_00008(child_fabric_query) -> None: + """ + ### Classes and Methods + + - childFabricQuery + - __init__() + - fabric_names setter + + ### Summary + Verify behavior when ``fabric_names`` is set to a list of strings. + + ### Test + + - ``fabric_names`` is set to expected value. + - Exception is not raised. + """ + fabric_names = ["FOO", "BAR"] + with does_not_raise(): + instance = child_fabric_query + instance.fabric_names = fabric_names + assert instance.fabric_names == fabric_names + + +def test_fab_mem_query_00009(child_fabric_query) -> None: + """ + ### Classes and Methods + + - childFabricQuery + - __init__() + - fabric_names setter + + ### Summary + Verify behavior when ``fabric_names`` is set to a non-list. + + ### Test + - ``ValueError`` is raised because ``fabric_names`` is not a list. + - ``fabric_names`` is not modified, hence it retains its initial value + of None. + """ + with does_not_raise(): + instance = child_fabric_query + + match = r"FabricQuery\.fabric_names: " + match += r"fabric_names must be a list\." + + with pytest.raises(ValueError, match=match): + instance.fabric_names = "NOT_A_LIST" + + assert instance.fabric_names is None + + +def test_fab_mem_query_00010(child_fabric_query) -> None: + """ + ### Classes and Methods + + - childFabricQuery + - __init__() + - fabric_names setter + + ### Summary + Verify behavior when ``fabric_names`` is set to a list with a non-string + element. + + ### Test + + - ``ValueError`` is raised because fabric_names is a list with a + non-string element. + - ``fabric_names`` is not modified, hence it retains its initial value + of None. + """ + with does_not_raise(): + instance = child_fabric_query + + match = r"FabricQuery.fabric_names: " + match += r"fabric_names must be a list of strings." + + with pytest.raises(ValueError, match=match): + instance.fabric_names = [1, 2, 3] + + assert instance.fabric_names is None + + +def test_fab_mem_query_00011(child_fabric_query) -> None: + """ + ### Classes and Methods + + - childFabricQuery + - fabric_names setter + + ### Summary + Verify behavior when ``fabric_names`` is set to an empty list. + + ### Setup + + - childFabricQuery().fabric_names is set to an empty list + + ### Test + - ``ValueError`` is raised from ``fabric_names`` setter. + """ + match = r"FabricQuery\.fabric_names: fabric_names must be a list of " + match += r"at least one string\." + + with pytest.raises(ValueError, match=match): + instance = child_fabric_query + instance.fabric_names = [] + + +def test_fab_mem_query_00012(child_fabric_query) -> None: + """ + ### Classes and Methods + + - childFabricQuery + - __init__() + - commit() + - _validate_commit_parameters() + + ### Summary + Verify behavior when ``fabric_names`` is not set before calling commit. + + ### Test + + - ``ValueError`` is raised because fabric_names is not set before + calling commit. + - ``fabric_names`` is not modified, hence it retains its initial value + of None. + """ + with does_not_raise(): + instance = child_fabric_query + instance.rest_send = RestSend(params) + instance.results = Results() + + match = r"FabricQuery\._validate_commit_parameters:\s+" + match += r"fabric_names must be set before calling commit\." + + with pytest.raises(ValueError, match=match): + instance.commit() + + assert instance.fabric_names is None + + +def test_fab_mem_query_00013(child_fabric_query) -> None: + """ + ### Classes and Methods + + - childFabricQuery + - __init__() + - commit() + - _validate_commit_parameters() + + ### Summary + Verify behavior when ``rest_send`` is not set before calling commit. + + ### Test + + - ``ValueError`` is raised because ``rest_send`` is not set before + calling commit. + - ``rest_send`` is not modified, hence it retains its initial value + of None. + """ + with does_not_raise(): + instance = child_fabric_query + instance.fabric_names = ["f1"] + instance.results = Results() + + match = r"FabricQuery\._validate_commit_parameters:\s+" + match += r"rest_send must be set before calling commit\." + + with pytest.raises(ValueError, match=match): + instance.commit() + + assert instance.rest_send is None + + +def test_fab_mem_query_00014(child_fabric_query) -> None: + """ + ### Classes and Methods + + - childFabricQuery + - __init__() + - commit() + - _validate_commit_parameters() + + ### Summary + Verify behavior when ``results`` is not set before calling commit. + + ### Test + + - ``ValueError`` is raised because ``results`` is not set before + calling commit. + - ``Results()`` is instantiated in ``_validate_commit_parameters`` + in order to register a failed result. + """ + with does_not_raise(): + instance = child_fabric_query + instance.fabric_names = ["f1"] + instance.rest_send = RestSend(params) + instance.results = None + + match = r"FabricQuery\._validate_commit_parameters:\s+" + match += r"results must be set before calling commit\." + + with pytest.raises(ValueError, match=match): + instance.commit() + + assert instance.results.class_name == "Results" + + +def test_fab_mem_query_00015(child_fabric_query) -> None: + """ + ### Classes and Methods + + - childFabricQuery + - __init__() + - fabric_names setter + - commit() + - Query() + - __init__() + - commit() + + ### Summary + Verify behavior when user queries child fabric of a MSD fabric and no fabrics exist + on the controller and the RestSend() RETURN_CODE is 200 and data is empty. + + ### Code Flow + + - main.Query() is instantiated and instantiates childFabricQuery() + - FabricQuery.fabric_names is set to contain one fabric_name (f1) + that does not exist on the controller. + - FabricAssociations.refresh() calls RestSend().commit() which sets + RestSend().response_current to a dict with keys DATA == [], + RETURN_CODE == 200, MESSAGE="OK" + - Hence, FabricAssociation().data is set to an empty dict: {} + - Results().register_task_result() adds sequence_number (with value 1) to + each of the results dicts + - since instance.fab_associations is empty, none of the + following are set: + - instance.results.diff_current + - instance.results.response_current + - instance.results.result_current + - instance.results.changed set() contains False + - instance.results.failed set() contains False + - commit() returns without doing anything else + - Exception is not raised + """ + method_name = inspect.stack()[0][3] + key = "Fabric_association_get_data_empty" + + def responses(): + yield fabric_member_data(key) + + gen_responses = ResponseGenerator(responses()) + + sender = Sender() + sender.ansible_module = MockAnsibleModule() + sender.gen = gen_responses + rest_send = RestSend(params) + rest_send.unit_test = True + rest_send.timeout = 1 + rest_send.response_handler = ResponseHandler() + rest_send.sender = sender + + with does_not_raise(): + instance = child_fabric_query + instance.fabric_names = ["f1"] + instance.rest_send = rest_send + instance.results = Results() + instance.commit() + + assert isinstance(instance.results.diff, list) + assert isinstance(instance.results.result, list) + assert isinstance(instance.results.response, list) + + assert len(instance.results.diff) == 1 + assert len(instance.results.result) == 1 + assert len(instance.results.response) == 1 + + assert instance.results.diff[0].get("sequence_number", None) == 1 + assert len(instance.results.diff[0].get("child", {})) == 0 + assert instance.results.response[0].get("RETURN_CODE", None) == 200 + assert instance.results.result[0].get("found", None) is False + assert instance.results.result[0].get("success", None) is True + + assert False in instance.results.failed + assert True not in instance.results.failed + assert False in instance.results.changed + assert True not in instance.results.changed + + +def test_fab_mem_query_00016(child_fabric_query) -> None: + """ + ### Classes and Methods + - childFabricQuery + - __init__() + - fabric_names setter + - commit() + - Query() + - __init__() + - commit() + + ### Summary + Verify behavior when user queries child fabric of a MSD, child fabrics are not existing + on the controller. Some other fabric exists on the controller, + and the RestSend() RETURN_CODE is 200. + + ### Code Flow + + - main.Query() is instantiated and instantiates childFabricQuery() + - childFabricQuery.fabric_names is set to contain one fabric_name (f1) + that does not exist on the controller. + - FabricAssociations.refresh() calls RestSend().commit() which sets + RestSend().response_current to a dict with keys DATA == [{other fabric}], + RETURN_CODE == 200, MESSAGE="OK" + - Hence, FabricAssociation().data is set to an empty dict: {} + - Results().register_task_result() adds sequence_number (with value 1) to + each of the results dicts + + ### Test + + - since instance.fab_associations is not empty, none of the + following are set: + - instance.results.diff_current + - instance.results.response_current + - instance.results.result_current + - instance.results.changed set() contains False + - instance.results.failed set() contains False + - commit() returns without doing anything else + - Exception is not raised + """ + method_name = inspect.stack()[0][3] + key = "Fabric_association_get_response" + + def responses(): + yield fabric_member_data(key) + + gen_responses = ResponseGenerator(responses()) + + sender = Sender() + sender.ansible_module = MockAnsibleModule() + sender.gen = gen_responses + rest_send = RestSend(params) + rest_send.unit_test = True + rest_send.timeout = 1 + rest_send.response_handler = ResponseHandler() + rest_send.sender = sender + + with does_not_raise(): + instance = child_fabric_query + instance.fabric_names = ["f1"] + instance.rest_send = rest_send + instance.results = Results() + instance.commit() + + assert isinstance(instance.results.diff, list) + assert isinstance(instance.results.result, list) + assert isinstance(instance.results.response, list) + + assert len(instance.results.diff) == 1 + assert len(instance.results.result) == 1 + assert len(instance.results.response) == 1 + + assert instance.results.diff[0].get("sequence_number", None) == 1 + assert len(instance.results.diff[0].get("child", {})) == 0 + assert instance.results.response[0].get("RETURN_CODE", None) == 200 + assert instance.results.result[0].get("found", None) is False + assert instance.results.result[0].get("success", None) is True + + assert False in instance.results.failed + assert True not in instance.results.failed + assert False in instance.results.changed + assert True not in instance.results.changed + + +def test_fab_mem_query_00017(child_fabric_query) -> None: + """ + ## Need to Re-visit this Case whether error "RETURN_CODE" is 500 or valueerror is ok + ### Classes and Methods + + - childFabricQuery + - __init__() + - fabric_names setter + - commit() + - Query() + - __init__() + - commit() + + ### Summary + Verify behavior when user queries child fabric that does not exist + on the controller. One fabric (f2) exists on the controller, + but the RestSend() RETURN_CODE is 500 for fabric associations. + + ### Setup + + - RestSend().commit() response is mocked to return a dict with key + RETURN_CODE == 500 + - RestSend().timeout is set to 1 + - RestSend().unit_test is set to True + + """ + method_name = inspect.stack()[0][3] + key = "childFabric_nok_get_response" + + def responses(): + yield fabric_member_data(key) + + gen_responses = ResponseGenerator(responses()) + + sender = Sender() + sender.ansible_module = MockAnsibleModule() + sender.gen = gen_responses + rest_send = RestSend(params) + rest_send.unit_test = True + rest_send.timeout = 1 + rest_send.response_handler = ResponseHandler() + rest_send.sender = sender + + with does_not_raise(): + instance = child_fabric_query + instance.fabric_names = ["f1"] + instance.rest_send = rest_send + instance.results = Results() + match = "Fabric Association response from NDFC controller returns failure" + with pytest.raises(ValueError, match=match): + instance.commit() + + +def test_fab_mem_query_00018(child_fabric_query) -> None: + """ + ### Classes and Methods + + - childFabricQuery + - __init__() + - fabric_names setter + - commit() + - Query() + - __init__() + - commit() + + ### Summary + Verify behavior when user queries a fabric that exists + on the controller. One fabric (f1) exists on the controller, + and the RestSend() RETURN_CODE is 200. + + """ + method_name = inspect.stack()[0][3] + key = "Fabric_association_get_response_exists" + + def responses(): + yield fabric_member_data(key) + + gen_responses = ResponseGenerator(responses()) + + sender = Sender() + sender.ansible_module = MockAnsibleModule() + sender.gen = gen_responses + rest_send = RestSend(params) + rest_send.unit_test = True + rest_send.timeout = 1 + rest_send.response_handler = ResponseHandler() + rest_send.sender = sender + + with does_not_raise(): + instance = child_fabric_query + instance.fabric_names = ["f1"] + instance.rest_send = rest_send + instance.results = Results() + instance.commit() + + assert isinstance(instance.results.diff, list) + assert isinstance(instance.results.result, list) + assert isinstance(instance.results.response, list) + + assert len(instance.results.diff) == 1 + assert len(instance.results.result) == 1 + assert len(instance.results.response) == 1 + + assert instance.results.diff[0].get("sequence_number", None) == 1 + assert instance.results.diff[0].get("child", {}).get("fabricName", None) == "child" + assert instance.results.diff[0].get("child", {}).get("fabricParent", None) == "f1" + assert instance.results.diff[0].get("child", {}).get("fabricState", None) == "member" + assert instance.results.response[0].get("RETURN_CODE", None) == 200 + + assert instance.results.result[0].get("found", None) is True + assert instance.results.result[0].get("success", None) is True + + assert False in instance.results.failed + assert True not in instance.results.failed + assert False in instance.results.changed + assert True not in instance.results.changed + + +def test_fab_mem_query_00019(child_fabric_query) -> None: + """ + ### Classes and Methods + - childFabricQuery + - __init__() + - fabric_names setter + - commit() + - Query() + - __init__() + - commit() + + ### Summary + Verify behavior when user queries child fabric of a MSD, child fabrics are not existing + on the controller. MSD exists on the controller + and the RestSend() RETURN_CODE is 200. + + ### Test + + - since instance.fab_associations is not empty, none of the + following are set: + - instance.results.diff_current + - instance.results.response_current + - instance.results.result_current + - instance.results.changed set() contains False + - instance.results.failed set() contains False + - commit() returns without doing anything else + - Exception is not raised + """ + method_name = inspect.stack()[0][3] + key = "Fabric_association_get_response_00019" + + def responses(): + yield fabric_member_data(key) + + gen_responses = ResponseGenerator(responses()) + + sender = Sender() + sender.ansible_module = MockAnsibleModule() + sender.gen = gen_responses + rest_send = RestSend(params) + rest_send.unit_test = True + rest_send.timeout = 1 + rest_send.response_handler = ResponseHandler() + rest_send.sender = sender + + with does_not_raise(): + instance = child_fabric_query + instance.fabric_names = ["f1"] + instance.rest_send = rest_send + instance.results = Results() + instance.commit() + + assert isinstance(instance.results.diff, list) + assert isinstance(instance.results.result, list) + assert isinstance(instance.results.response, list) + + assert len(instance.results.diff) == 1 + assert len(instance.results.result) == 1 + assert len(instance.results.response) == 1 + + assert instance.results.diff[0].get("sequence_number", None) == 1 + assert len(instance.results.diff[0].get("child", {})) == 0 + assert instance.results.response[0].get("RETURN_CODE", None) == 200 + assert instance.results.result[0].get("found", None) is False + assert instance.results.result[0].get("success", None) is True + + assert False in instance.results.failed + assert True not in instance.results.failed + assert False in instance.results.changed + assert True not in instance.results.changed + + +del_params = {'state': 'deleted', 'check_mode': False} + + +def test_fab_mem_delete_init_00020(): + t_params = {'config': [{'FABRIC_NAME': 'valid_fabric'}, {'CHILD_FABRIC_NAME': 'child_fabric'}]} + t_params.update(del_params) + deleted_instance = Deleted(t_params) + assert deleted_instance.class_name == 'Deleted' + assert deleted_instance.action == 'child_fabric_delete' + assert 'deleted' in deleted_instance._implemented_states + + +@pytest.mark.parametrize( + "invalid_fabric_name", + ["@123", "", "$abd#", "-!`", "%^&", None], +) +def test_fab_mem_delete_invalid_msd_fabric_name_00021(invalid_fabric_name): + t_params = {'config': [{'FABRIC_NAME': {invalid_fabric_name}, 'CHILD_FABRIC_NAME': "abc"}]} + t_params.update(params) + delete_instance = Deleted(t_params) + with pytest.raises(ValueError) as excinfo: + delete_instance.commit() + assert "contains an invalid FABRIC_NAME" in str(excinfo.value) + + +@pytest.mark.parametrize( + "invalid_fabric_name", + ["@123", "", "$abd#", "-!`", "%^&", None], +) +def test_fab_mem_delete_invalid_child_fabric_name_00022(invalid_fabric_name): + t_params = {'config': [{'FABRIC_NAME': "abc", 'CHILD_FABRIC_NAME': {invalid_fabric_name}}]} + t_params.update(params) + delete_instance = Deleted(t_params) + with pytest.raises(ValueError) as excinfo: + delete_instance.commit() + assert "Playbook configuration for FABRIC_NAME or CHILD_FABRIC_NAME contains an invalid FABRIC_NAME" in str(excinfo.value) + + +def test_fab_mem_delete_invalid_config_00023(): + t_params = {'config': None} + t_params.update(params) + delete_instance = Deleted(t_params) + with pytest.raises(ValueError) as excinfo: + delete_instance.commit() + assert "params is missing config parameter" in str(excinfo.value) + + +def test_fab_mem_delete_invalid_config_00024(): + t_params = {'config': [{'FABRIC_NAME': ''}]} + t_params.update(params) + delete_instance = Deleted(t_params) + with pytest.raises(ValueError) as excinfo: + delete_instance.commit() + assert "CHILD_FABRIC_NAME : Required parameter not found" in str(excinfo.value) + + +def test_fab_mem_delete_invalid_config_00025(): + t_params = {'config': [{'FABRIC_NAME': 123, 'CHILD_FABRIC_NAME': True}]} + t_params.update(params) + delete_instance = Deleted(t_params) + with pytest.raises(ValueError) as excinfo: + delete_instance.commit() + assert "Playbook configuration for FABRIC_NAME or CHILD_FABRIC_NAME contains an invalid FABRIC_NAME" in str(excinfo.value) + + +def test_fab_mem_delete_00026(child_fabric_delete) -> None: + """ + ### Classes and Methods + - childFabricDelete + - __init__() + + ### Test + + - Class attributes are initialized to expected values + - Exception is not raised + """ + with does_not_raise(): + instance = child_fabric_delete + assert instance.class_name == "childFabricDelete" + assert instance.action == "child_fabric_delete" + assert instance.fabric_names is None + assert instance.path is None + assert instance.verb is None + assert instance.ep_child_fabric_delete.class_name == "EpChildFabricExit" + + +def test_fab_mem_delete_00027(child_fabric_delete) -> None: + """ + ### Classes and Methods + + - childFabricDelete + - __init__() + - fabric_names setter + + ### Summary + Verify behavior when ``fabric_names`` is set to a list of strings. + + ### Test + + - ``fabric_names`` is set to expected value. + - Exception is not raised. + """ + fabric_names = ["FOO", "BAR"] + with does_not_raise(): + instance = child_fabric_delete + instance.fabric_names = fabric_names + assert instance.fabric_names == fabric_names + + +def test_fab_mem_delete_00028(child_fabric_delete) -> None: + """ + ### Classes and Methods + + - childFabricDelete + - __init__() + - fabric_names setter + + ### Summary + Verify behavior when ``fabric_names`` is set to a non-list. + + ### Test + - ``ValueError`` is raised because ``fabric_names`` is not a list. + - ``fabric_names`` is not modified, hence it retains its initial value + of None. + """ + with does_not_raise(): + instance = child_fabric_delete + + match = r"childFabricDelete\.fabric_names: " + match += r"fabric_names must be a list\." + + with pytest.raises(ValueError, match=match): + instance.fabric_names = "NOT_A_LIST" + + assert instance.fabric_names is None + + +def test_fab_mem_delete_00029(child_fabric_delete) -> None: + """ + ### Classes and Methods + + - childFabricDelete + - __init__() + - fabric_names setter + + ### Summary + Verify behavior when ``fabric_names`` is set to a list with a non-string + element. + + ### Test + + - ``ValueError`` is raised because fabric_names is a list with a + non-string element. + - ``fabric_names`` is not modified, hence it retains its initial value + of None. + """ + with does_not_raise(): + instance = child_fabric_delete + + match = r"childFabricDelete.fabric_names: " + match += r"fabric_names must be a list of strings." + + with pytest.raises(ValueError, match=match): + instance.fabric_names = [1, 2, 3] + + assert instance.fabric_names is None + + +def test_fab_mem_delete_00030(child_fabric_delete) -> None: + """ + ### Classes and Methods + + - childFabricDelete + - fabric_names setter + + ### Summary + Verify behavior when ``fabric_names`` is set to an empty list. + + ### Setup + + - childFabricQuery().fabric_names is set to an empty list + + ### Test + - ``ValueError`` is raised from ``fabric_names`` setter. + """ + match = r"childFabricDelete.fabric_names: fabric_names must be a list of " + match += r"at least one string." + + with pytest.raises(ValueError, match=match): + instance = child_fabric_delete + instance.fabric_names = [] + + +def test_fab_mem_delete_00031(child_fabric_delete) -> None: + """ + ### Classes and Methods + + - childFabricDelete + - __init__() + - commit() + - _validate_commit_parameters() + + ### Summary + Verify behavior when ``fabric_names`` is not set before calling commit. + + ### Test + + - ``ValueError`` is raised because fabric_names is not set before + calling commit. + - ``fabric_names`` is not modified, hence it retains its initial value + of None. + """ + payload = {'destFabric': 'MSD1', 'sourceFabric': 'child'} + with does_not_raise(): + instance = child_fabric_delete + instance.rest_send = RestSend(params) + instance.results = Results() + + match = r"childFabricDelete._validate_commit_parameters: " + match += r"fabric_names must be set prior to calling commit." + + with pytest.raises(ValueError, match=match): + instance.commit(payload) + + assert instance.fabric_names is None + + +def test_fab_mem_delete_00032(child_fabric_delete) -> None: + """ + ### Classes and Methods + + - childFabricDelete + - __init__() + - commit() + - _validate_commit_parameters() + + ### Summary + Verify behavior when ``rest_send`` is not set before calling commit. + + ### Test + + - ``ValueError`` is raised because ``rest_send`` is not set before + calling commit. + - ``rest_send`` is not modified, hence it retains its initial value + of None. + """ + payload = {'destFabric': 'MSD1', 'sourceFabric': 'child'} + with does_not_raise(): + instance = child_fabric_delete + instance.fabric_names = ["f1"] + instance.results = Results() + + match = r"childFabricDelete._validate_commit_parameters: " + match += r"rest_send must be set prior to calling commit." + + with pytest.raises(ValueError, match=match): + instance.commit(payload) + + assert instance.rest_send is None + + +def test_fab_mem_delete_00033(child_fabric_delete) -> None: + """ + ### Classes and Methods + + - childFabricDelete + - __init__() + - commit() + - _validate_commit_parameters() + + ### Summary + Verify behavior when ``results`` is not set before calling commit. + + ### Test + + - ``ValueError`` is raised because ``results`` is not set before + calling commit. + - ``Results()`` is instantiated in ``_validate_commit_parameters`` + in order to register a failed result. + """ + payload = {'destFabric': 'MSD1', 'sourceFabric': 'child'} + with does_not_raise(): + instance = child_fabric_delete + instance.fabric_names = ["f1"] + instance.rest_send = RestSend(params) + + match = r"childFabricDelete\._validate_commit_parameters:\s+" + match += r"results must be set prior to calling commit\." + + with pytest.raises(ValueError, match=match): + instance.commit(payload) + + +def test_fab_mem_delete_00034(child_fabric_delete) -> None: + """ + ### Classes and Methods + + - childFabricCommon + - __init__() + - childFabricDelete + - __init__() + + ### Summary + + - Verify that an Exception is not raised + """ + with does_not_raise(): + instance = child_fabric_delete + instance.rest_send = RestSend(params) + instance.rest_send.path = instance.ep_child_fabric_delete.path + instance.rest_send.verb = instance.ep_child_fabric_delete.verb + path = "/appcenter/cisco/ndfc/api/v1/lan-fabric/rest/control/fabrics/msdExit" + assert instance.rest_send.path == path + assert instance.rest_send.verb == "POST" + + +@pytest.mark.parametrize("fabric_name", [123, 123.45, [], {}]) +def test_fab_mem_delete_00035(fabric_name) -> None: + t_params = {'config': [{'FABRIC_NAME': fabric_name, 'CHILD_FABRIC_NAME': 'child'}]} + t_params.update(params) + delete_instance = Deleted(t_params) + match = r"ConversionUtils\.validate_fabric_name: " + match += "Invalid fabric name. Expected string. Got" + with pytest.raises(ValueError, match=match): + delete_instance.commit() + + +@pytest.mark.parametrize("fabric_name", [123, 123.45, [], {}]) +def test_fab_mem_delete_00036(fabric_name) -> None: + t_params = {'config': [{'FABRIC_NAME': 'MSD', 'CHILD_FABRIC_NAME': fabric_name}]} + t_params.update(params) + delete_instance = Deleted(t_params) + match = r"ConversionUtils\.validate_fabric_name: " + match += "Invalid fabric name. Expected string. Got" + with pytest.raises(ValueError, match=match): + delete_instance.commit() + + +def test_fab_mem_delete_00037() -> None: + """ + ### Classes and Methods + + - childFabricCommon() + - __init__() + - payloads setter + - childFabricDelete + - __init__() + - commit() + + ### Summary + + - The user attempts to delete a child fabric from the MSD fabric that does not exists on the + controller. raises ValueError + + """ + key = "Fabric_association_get_response_exists" + t_params = {'config': [{'FABRIC_NAME': 'f2', 'CHILD_FABRIC_NAME': 'child'}]} + t_params.update(del_params) + + def responses(): + yield fabric_member_data(key) + + gen_responses = ResponseGenerator(responses()) + + sender = Sender() + sender.ansible_module = MockAnsibleModule() + sender.gen = gen_responses + rest_send = RestSend(t_params) + rest_send.unit_test = True + rest_send.timeout = 1 + rest_send.response_handler = ResponseHandler() + rest_send.sender = sender + + with does_not_raise(): + instance = Deleted(t_params) + instance.fabric_names = ["f2"] + instance.rest_send = rest_send + instance.results = Results() + match = "is not found in Controller. Please create and try again" + with pytest.raises(ValueError, match=match): + instance.commit() + + +def test_fab_mem_delete_00038() -> None: + """ + ### Classes and Methods + + - childFabricCommon() + - __init__() + - payloads setter + - childFabricDelete + - __init__() + - commit() + + ### Summary + + - The user attempts to delete a child fabric from the MSD fabric that does not exists on the + controller. raises ValueError + + """ + method_name = inspect.stack()[0][3] + key = "Fabric_association_get_valid_data" + t_params = {'config': [{'FABRIC_NAME': 'f1', 'CHILD_FABRIC_NAME': 'child'}]} + t_params.update(del_params) + + def responses(): + yield fabric_member_data(key) + + gen_responses = ResponseGenerator(responses()) + + sender = Sender() + sender.ansible_module = MockAnsibleModule() + sender.gen = gen_responses + rest_send = RestSend(t_params) + rest_send.unit_test = True + rest_send.timeout = 1 + rest_send.response_handler = ResponseHandler() + rest_send.sender = sender + + with does_not_raise(): + instance = Deleted(t_params) + instance.fabric_names = ["f1"] + instance.rest_send = rest_send + instance.results = Results() + match = "is not of type MSD" + with pytest.raises(ValueError, match=match): + instance.commit() + + +def test_fab_mem_delete_00039() -> None: + """ + ### Classes and Methods + + - childFabricCommon() + - __init__() + - payloads setter + - childFabricDelete + - __init__() + - commit() + + ### Summary + + - Verify successful fabric delete code path. + - The user attempts to delete a child fabric that is not a child anymore + """ + method_name = inspect.stack()[0][3] + key = "Fabric_association_get_response_exists" + t_params = {'config': [{'FABRIC_NAME': 'f1', 'CHILD_FABRIC_NAME': 'child2'}]} + t_params.update(del_params) + + def responses(): + yield fabric_member_data(key) + + gen_responses = ResponseGenerator(responses()) + + sender = Sender() + sender.ansible_module = MockAnsibleModule() + sender.gen = gen_responses + rest_send = RestSend(t_params) + rest_send.unit_test = True + rest_send.timeout = 1 + rest_send.response_handler = ResponseHandler() + rest_send.sender = sender + + with does_not_raise(): + instance = Deleted(t_params) + instance.fabric_names = ["f1"] + instance.rest_send = rest_send + instance.results = Results() + instance.commit() + + assert isinstance(instance.results.diff, list) + assert isinstance(instance.results.result, list) + assert isinstance(instance.results.response, list) + + assert len(instance.results.diff) == 1 + assert len(instance.results.metadata) == 1 + assert len(instance.results.response) == 1 + assert len(instance.results.result) == 1 + + assert instance.results.diff[0].get("sequence_number", None) == 1 + assert instance.results.metadata[0].get("action", None) == "child_fabric_delete" + assert instance.results.metadata[0].get("check_mode", None) is False + assert instance.results.metadata[0].get("sequence_number", None) == 1 + assert instance.results.metadata[0].get("state", None) == "deleted" + assert instance.results.response[0].get("RETURN_CODE", None) == 200 + assert instance.results.response[0].get("MESSAGE", None) == "Given child fabric is already not a member of MSD fabric" + assert instance.results.result[0].get("changed", None) is False + assert instance.results.result[0].get("success", None) is True + + assert False in instance.results.failed + assert True not in instance.results.failed + assert False in instance.results.changed + assert True not in instance.results.changed + + +def test_fab_mem_delete_00040(child_fabric_delete) -> None: + """ + ### Classes and Methods + + - childFabricCommon() + - __init__() + - payloads setter + - childFabricDelete + - __init__() + - commit() + + ### Summary + + - Verify successful child fabric delete code path. + - The user attempts to delete a child fabric from the MSD fabric. + """ + method_name = inspect.stack()[0][3] + key = "childFabric_delete_response_2" + + def responses(): + yield fabric_member_data(key) + + gen_responses = ResponseGenerator(responses()) + + sender = Sender() + sender.ansible_module = MockAnsibleModule() + sender.gen = gen_responses + rest_send = RestSend({"state": "deleted", "check_mode": False}) + rest_send.unit_test = True + rest_send.timeout = 1 + rest_send.response_handler = ResponseHandler() + rest_send.sender = sender + payload = {'destFabric': 'f1', 'sourceFabric': 'child'} + with does_not_raise(): + instance = child_fabric_delete + instance.fabric_names = ["f1"] + instance.rest_send = rest_send + instance.results = Results() + instance.commit(payload) + + assert isinstance(instance.results.diff, list) + assert isinstance(instance.results.result, list) + assert isinstance(instance.results.response, list) + + assert len(instance.results.diff) == 1 + assert len(instance.results.metadata) == 1 + assert len(instance.results.response) == 1 + assert len(instance.results.result) == 1 + + assert instance.results.diff[0].get("sequence_number", None) == 1 + assert instance.results.diff[0].get("destFabric", None) == "f1" + assert instance.results.diff[0].get("sourceFabric", None) == "child" + + assert instance.results.metadata[0].get("action", None) == "child_fabric_delete" + assert instance.results.metadata[0].get("check_mode", None) is False + assert instance.results.metadata[0].get("sequence_number", None) == 1 + assert instance.results.metadata[0].get("state", None) == "deleted" + + assert instance.results.response[0].get("RETURN_CODE", None) == 200 + assert instance.results.response[0].get("MESSAGE", None) == "OK" + + assert instance.results.result[0].get("changed", None) is True + assert instance.results.result[0].get("success", None) is True + + assert False in instance.results.failed + assert True not in instance.results.failed + assert True in instance.results.changed + assert False not in instance.results.changed + + +def test_fab_mem_delete_00041(child_fabric_delete) -> None: + """ + ### Classes and Methods + + - childFabricCommon() + - __init__() + - payloads setter + - childFabricDelete + - __init__() + - commit() + + ### Summary + + - Verify unsuccessful scenario - Failure response from NDFC during child fabric delete is displayed properly. + - The user attempts to delete a child fabric from the MSD fabric. + """ + + key = "childFabric_delete_response_3" + + def responses(): + yield fabric_member_data(key) + + gen_responses = ResponseGenerator(responses()) + + sender = Sender() + sender.ansible_module = MockAnsibleModule() + sender.gen = gen_responses + rest_send = RestSend({"state": "deleted", "check_mode": False}) + rest_send.unit_test = True + rest_send.timeout = 1 + rest_send.response_handler = ResponseHandler() + rest_send.sender = sender + payload = {'destFabric': 'f1', 'sourceFabric': 'child'} + with does_not_raise(): + instance = child_fabric_delete + instance.fabric_names = ["f1"] + instance.rest_send = rest_send + instance.results = Results() + instance.commit(payload) + + assert isinstance(instance.results.diff, list) + assert isinstance(instance.results.result, list) + assert isinstance(instance.results.response, list) + + assert len(instance.results.diff) == 1 + assert len(instance.results.metadata) == 1 + assert len(instance.results.response) == 1 + assert len(instance.results.result) == 1 + + assert instance.results.diff[0].get("sequence_number", None) == 1 + assert instance.results.diff[0].get("destFabric", None) is None + assert instance.results.diff[0].get("sourceFabric", None) is None + + assert instance.results.metadata[0].get("action", None) == "child_fabric_delete" + assert instance.results.metadata[0].get("check_mode", None) is False + assert instance.results.metadata[0].get("sequence_number", None) == 1 + assert instance.results.metadata[0].get("state", None) == "deleted" + + assert instance.results.response[0].get("RETURN_CODE", None) == 500 + assert instance.results.response[0].get("MESSAGE", None) == "NOK" + + assert instance.results.result[0].get("changed", None) is False + assert instance.results.result[0].get("success", None) is False + + assert True in instance.results.failed + assert False not in instance.results.failed + assert False in instance.results.changed + assert True not in instance.results.changed + + +def test_fab_mem_delete_00042() -> None: + """ + ### Classes and Methods + + - childFabricCommon() + - __init__() + - payloads setter + - childFabricDelete + - __init__() + - commit() + + ### Summary + + - Verify unsuccessful fabric-associations response and reaction of child fabric delete class + - The user attempts to delete a child fabric + """ + key = "Fabric_association_get_failure_response" + t_params = {'config': [{'FABRIC_NAME': 'f1', 'CHILD_FABRIC_NAME': 'child2'}]} + t_params.update(del_params) + + def responses(): + yield fabric_member_data(key) + + gen_responses = ResponseGenerator(responses()) + + sender = Sender() + sender.ansible_module = MockAnsibleModule() + sender.gen = gen_responses + rest_send = RestSend(t_params) + rest_send.unit_test = True + rest_send.timeout = 1 + rest_send.response_handler = ResponseHandler() + rest_send.sender = sender + + with does_not_raise(): + instance = Deleted(t_params) + instance.fabric_names = ["f1"] + instance.rest_send = rest_send + instance.results = Results() + + match = "Fabric Association response from NDFC controller returns failure" + with pytest.raises(ValueError, match=match): + instance.commit() + + +merged_params = {'state': 'merged', 'check_mode': False} + + +def test_fab_mem_merged_init_00043(): + t_params = {'config': [{'FABRIC_NAME': 'valid_fabric'}, {'CHILD_FABRIC_NAME': 'child_fabric'}]} + t_params.update(merged_params) + merged_instance = Merged(t_params) + assert merged_instance.class_name == 'Merged' + assert merged_instance.action == 'child_fabric_add' + assert 'merged' in merged_instance._implemented_states + + +@pytest.mark.parametrize( + "invalid_fabric_name", + ["@123", "", "$abd#", "-!`", "%^&", None], +) +def test_fab_mem_merged_invalid_msd_fabric_name_00044(invalid_fabric_name): + t_params = {'config': [{'FABRIC_NAME': {invalid_fabric_name}, 'CHILD_FABRIC_NAME': "abc"}]} + t_params.update(params) + key = "childFabric_add_response" + + def responses(): + yield fabric_member_data(key) + + gen_responses = ResponseGenerator(responses()) + sender = Sender() + sender.ansible_module = MockAnsibleModule() + rest_send = RestSend(t_params) + rest_send.response_handler = ResponseHandler() + rest_send.sender = sender + sender.gen = gen_responses + merged_instance = Merged(t_params) + merged_instance.rest_send = rest_send + with pytest.raises(ValueError) as excinfo: + merged_instance.commit() + assert "contains an invalid FABRIC_NAME" in str(excinfo.value) + + +@pytest.mark.parametrize( + "invalid_fabric_name", + ["@123", "", "$abd#", "-!`", "%^&", None], +) +def test_fab_mem_merged_invalid_child_fabric_name_00045(invalid_fabric_name): + t_params = {'config': [{'FABRIC_NAME': "abc", 'CHILD_FABRIC_NAME': {invalid_fabric_name}}]} + t_params.update(params) + key = "childFabric_add_response" + + def responses(): + yield fabric_member_data(key) + + gen_responses = ResponseGenerator(responses()) + sender = Sender() + sender.ansible_module = MockAnsibleModule() + rest_send = RestSend(t_params) + rest_send.response_handler = ResponseHandler() + rest_send.sender = sender + sender.gen = gen_responses + merged_instance = Merged(t_params) + merged_instance.rest_send = rest_send + with pytest.raises(ValueError) as excinfo: + merged_instance.commit() + assert "Playbook configuration for FABRIC_NAME or CHILD_FABRIC_NAME contains an invalid FABRIC_NAME" in str(excinfo.value) + + +def test_fab_mem_merged_invalid_config_00046(): + t_params = {'config': None} + t_params.update(params) + key = "childFabric_add_response" + + def responses(): + yield fabric_member_data(key) + + gen_responses = ResponseGenerator(responses()) + sender = Sender() + sender.ansible_module = MockAnsibleModule() + rest_send = RestSend(t_params) + rest_send.response_handler = ResponseHandler() + rest_send.sender = sender + sender.gen = gen_responses + merged_instance = Merged(t_params) + merged_instance.rest_send = rest_send + with pytest.raises(ValueError) as excinfo: + merged_instance.commit() + assert "params is missing config parameter" in str(excinfo.value) + + +def test_fab_mem_merged_invalid_config_00047(): + t_params = {'config': [{'FABRIC_NAME': ''}]} + t_params.update(params) + key = "childFabric_add_response" + + def responses(): + yield fabric_member_data(key) + + gen_responses = ResponseGenerator(responses()) + sender = Sender() + sender.ansible_module = MockAnsibleModule() + rest_send = RestSend(t_params) + rest_send.response_handler = ResponseHandler() + rest_send.sender = sender + sender.gen = gen_responses + merged_instance = Merged(t_params) + merged_instance.rest_send = rest_send + with pytest.raises(ValueError) as excinfo: + merged_instance.commit() + assert "CHILD_FABRIC_NAME : Required parameter not found" in str(excinfo.value) + + +def test_fab_mem_merged_invalid_config_00048(): + t_params = {'config': [{'FABRIC_NAME': 123, 'CHILD_FABRIC_NAME': True}]} + t_params.update(params) + key = "childFabric_add_response" + + def responses(): + yield fabric_member_data(key) + + gen_responses = ResponseGenerator(responses()) + sender = Sender() + sender.ansible_module = MockAnsibleModule() + rest_send = RestSend(t_params) + rest_send.response_handler = ResponseHandler() + rest_send.sender = sender + sender.gen = gen_responses + merged_instance = Merged(t_params) + merged_instance.rest_send = rest_send + with pytest.raises(ValueError) as excinfo: + merged_instance.commit() + assert "Playbook configuration for FABRIC_NAME or CHILD_FABRIC_NAME contains an invalid FABRIC_NAME" in str(excinfo.value) + + +def test_fab_mem_merged_00049(child_fabric_add) -> None: + """ + ### Classes and Methods + - childFabricAdd + - __init__() + + ### Test + + - Class attributes are initialized to expected values + - Exception is not raised + """ + with does_not_raise(): + instance = child_fabric_add + assert instance.class_name == "childFabricAdd" + assert instance.action == "child_fabric_add" + assert instance.fabric_names is None + assert instance.path is None + assert instance.verb is None + assert instance.ep_fabric_add.class_name == "EpChildFabricAdd" + + +def test_fab_mem_merged_00050(child_fabric_add) -> None: + """ + ### Classes and Methods + + - childFabricAdd + - __init__() + - fabric_names setter + + ### Summary + Verify behavior when ``fabric_names`` is set to a list of strings. + + ### Test + + - ``fabric_names`` is set to expected value. + - Exception is not raised. + """ + fabric_names = ["FOO", "BAR"] + with does_not_raise(): + instance = child_fabric_add + instance.fabric_names = fabric_names + assert instance.fabric_names == fabric_names + + +def test_fab_mem_merged_00051(child_fabric_add) -> None: + """ + ### Classes and Methods + + - childFabricAdd + - __init__() + - fabric_names setter + + ### Summary + Verify behavior when ``fabric_names`` is set to a non-list. + + ### Test + - ``ValueError`` is raised because ``fabric_names`` is not a list. + - ``fabric_names`` is not modified, hence it retains its initial value + of None. + """ + with does_not_raise(): + instance = child_fabric_add + + match = r"childFabricAdd\.fabric_names: " + match += r"fabric_names must be a list\." + + with pytest.raises(ValueError, match=match): + instance.fabric_names = "NOT_A_LIST" + + assert instance.fabric_names is None + + +def test_fab_mem_merged_00052(child_fabric_add) -> None: + """ + ### Classes and Methods + + - childFabricAdd + - __init__() + - fabric_names setter + + ### Summary + Verify behavior when ``fabric_names`` is set to a list with a non-string + element. + + ### Test + + - ``ValueError`` is raised because fabric_names is a list with a + non-string element. + - ``fabric_names`` is not modified, hence it retains its initial value + of None. + """ + with does_not_raise(): + instance = child_fabric_add + + match = r"childFabricAdd.fabric_names: " + match += r"fabric_names must be a list of strings." + + with pytest.raises(ValueError, match=match): + instance.fabric_names = [1, 2, 3] + + assert instance.fabric_names is None + + +def test_fab_mem_merged_00053(child_fabric_add) -> None: + """ + ### Classes and Methods + + - childFabricAdd + - fabric_names setter + + ### Summary + Verify behavior when ``fabric_names`` is set to an empty list. + + ### Setup + + - childFabricAdd().fabric_names is set to an empty list + + ### Test + - ``ValueError`` is raised from ``fabric_names`` setter. + """ + match = r"childFabricAdd.fabric_names: fabric_names must be a list of " + match += r"at least one string." + + with pytest.raises(ValueError, match=match): + instance = child_fabric_add + instance.fabric_names = [] + + +def test_fab_mem_merged_00054(child_fabric_add) -> None: + """ + ### Classes and Methods + + - childFabricAdd + - __init__() + - commit() + - _validate_commit_parameters() + + ### Summary + Verify behavior when ``fabric_names`` is not set before calling commit. + + ### Test + + - ``ValueError`` is raised because fabric_names is not set before + calling commit. + - ``fabric_names`` is not modified, hence it retains its initial value + of None. + """ + payload = {'destFabric': 'MSD1', 'sourceFabric': 'child'} + with does_not_raise(): + instance = child_fabric_add + instance.rest_send = RestSend(params) + instance.results = Results() + + match = r"childFabricAdd._validate_commit_parameters: " + match += r"fabric_names must be set prior to calling commit." + + with pytest.raises(ValueError, match=match): + instance.commit(payload) + + assert instance.fabric_names is None + + +def test_fab_mem_merged_00055(child_fabric_add) -> None: + """ + ### Classes and Methods + + - childFabricAdd + - __init__() + - commit() + - _validate_commit_parameters() + + ### Summary + Verify behavior when ``rest_send`` is not set before calling commit. + + ### Test + + - ``ValueError`` is raised because ``rest_send`` is not set before + calling commit. + - ``rest_send`` is not modified, hence it retains its initial value + of None. + """ + payload = {'destFabric': 'MSD1', 'sourceFabric': 'child'} + with does_not_raise(): + instance = child_fabric_add + instance.fabric_names = ["f1"] + instance.results = Results() + + match = r"childFabricAdd._validate_commit_parameters: " + match += r"rest_send must be set prior to calling commit." + + with pytest.raises(ValueError, match=match): + instance.commit(payload) + + assert instance.rest_send is None + + +def test_fab_mem_merged_00056(child_fabric_add) -> None: + """ + ### Classes and Methods + + - childFabricAdd + - __init__() + - commit() + - _validate_commit_parameters() + + ### Summary + Verify behavior when ``results`` is not set before calling commit. + + ### Test + + - ``ValueError`` is raised because ``results`` is not set before + calling commit. + - ``Results()`` is instantiated in ``_validate_commit_parameters`` + in order to register a failed result. + """ + payload = {'destFabric': 'MSD1', 'sourceFabric': 'child'} + with does_not_raise(): + instance = child_fabric_add + instance.fabric_names = ["f1"] + instance.rest_send = RestSend(params) + + match = r"childFabricAdd\._validate_commit_parameters:\s+" + match += r"results must be set prior to calling commit\." + + with pytest.raises(ValueError, match=match): + instance.commit(payload) + + +def test_fab_mem_merged_00057(child_fabric_add) -> None: + """ + ### Classes and Methods + + - childFabricCommon + - __init__() + - childFabricAdd + - __init__() + + ### Summary + + - Verify that endpoint values are set correctly when ``fabric_names`` + contains a valid fabric name. + - Verify that an Exception is not raised + """ + with does_not_raise(): + instance = child_fabric_add + instance.rest_send = RestSend(params) + instance.rest_send.path = instance.ep_fabric_add.path + instance.rest_send.verb = instance.ep_fabric_add.verb + path = "/appcenter/cisco/ndfc/api/v1/lan-fabric/rest/control/fabrics/msdAdd" + assert instance.rest_send.path == path + assert instance.rest_send.verb == "POST" + + +@pytest.mark.parametrize("fabric_name", [123, 123.45, [], {}]) +def test_fab_mem_merged_00058(fabric_name) -> None: + t_params = {'config': [{'FABRIC_NAME': fabric_name, 'CHILD_FABRIC_NAME': 'child'}]} + t_params.update(params) + key = "childFabric_add_response" + + def responses(): + yield fabric_member_data(key) + + gen_responses = ResponseGenerator(responses()) + sender = Sender() + sender.ansible_module = MockAnsibleModule() + rest_send = RestSend(t_params) + rest_send.response_handler = ResponseHandler() + rest_send.sender = sender + sender.gen = gen_responses + merged_instance = Merged(t_params) + merged_instance.rest_send = rest_send + match = r"ConversionUtils\.validate_fabric_name: " + match += "Invalid fabric name. Expected string. Got" + with pytest.raises(ValueError, match=match): + merged_instance.commit() + + +@pytest.mark.parametrize("fabric_name", [123, 123.45, [], {}]) +def test_fab_mem_merged_00059(fabric_name) -> None: + t_params = {'config': [{'FABRIC_NAME': 'MSD', 'CHILD_FABRIC_NAME': fabric_name}]} + t_params.update(params) + key = "childFabric_add_response" + + def responses(): + yield fabric_member_data(key) + + gen_responses = ResponseGenerator(responses()) + sender = Sender() + sender.ansible_module = MockAnsibleModule() + rest_send = RestSend(t_params) + rest_send.response_handler = ResponseHandler() + rest_send.sender = sender + sender.gen = gen_responses + merged_instance = Merged(t_params) + merged_instance.rest_send = rest_send + match = r"ConversionUtils\.validate_fabric_name: " + match += "Invalid fabric name. Expected string. Got" + with pytest.raises(ValueError, match=match): + merged_instance.commit() + + +def test_fab_mem_merged_00060() -> None: + """ + ### Classes and Methods + + - childFabricCommon() + - __init__() + - payloads setter + - childFabricAdd + - __init__() + - commit() + + ### Summary + + - The user attempts to add a child fabric into the MSD fabric that does not exists on the + controller. raises ValueError + + """ + method_name = inspect.stack()[0][3] + key1 = "get_controller_version" + key2 = "Fabric_association_get_response_exists" + + t_params = {'config': [{'FABRIC_NAME': 'f2', 'CHILD_FABRIC_NAME': 'child'}]} + t_params.update(merged_params) + + def responses(): + yield fabric_member_data(key1) + yield fabric_member_data(key2) + + gen_responses = ResponseGenerator(responses()) + + sender = Sender() + sender.ansible_module = MockAnsibleModule() + sender.gen = gen_responses + rest_send = RestSend(t_params) + rest_send.unit_test = True + rest_send.timeout = 1 + rest_send.response_handler = ResponseHandler() + rest_send.sender = sender + + with does_not_raise(): + instance = Merged(t_params) + instance.fabric_names = ["f2"] + instance.rest_send = rest_send + instance.results = Results() + match = "is not found in Controller. Please create and try again" + with pytest.raises(ValueError, match=match): + instance.commit() + + +def test_fab_mem_merged_00061() -> None: + """ + ### Classes and Methods + + - childFabricCommon() + - __init__() + - payloads setter + - childFabricAdd + - __init__() + - commit() + + ### Summary + + - The user attempts to add a child fabric into the MSD fabric that is not a MSD fabric. + - raises ValueError + + """ + method_name = inspect.stack()[0][3] + key1 = "get_controller_version" + key2 = "Fabric_association_get_valid_data" + t_params = {'config': [{'FABRIC_NAME': 'f1', 'CHILD_FABRIC_NAME': 'child'}]} + t_params.update(merged_params) + + def responses(): + yield fabric_member_data(key1) + yield fabric_member_data(key2) + + gen_responses = ResponseGenerator(responses()) + + sender = Sender() + sender.ansible_module = MockAnsibleModule() + sender.gen = gen_responses + rest_send = RestSend(t_params) + rest_send.unit_test = True + rest_send.timeout = 1 + rest_send.response_handler = ResponseHandler() + rest_send.sender = sender + + with does_not_raise(): + instance = Merged(t_params) + instance.fabric_names = ["f1"] + instance.rest_send = rest_send + instance.results = Results() + match = "is not of type MSD" + with pytest.raises(ValueError, match=match): + instance.commit() + + +def test_fab_mem_merged_00062() -> None: + """ + ### Classes and Methods + + - childFabricCommon() + - __init__() + - payloads setter + - childFabricAdd + - __init__() + - commit() + + ### Summary + + - Verify unsuccessful child fabric add code path. + - The user attempts to add a child fabric that is not exists in the controller + """ + method_name = inspect.stack()[0][3] + key1 = "get_controller_version" + key2 = "Fabric_association_get_response_exists" + + t_params = {'config': [{'FABRIC_NAME': 'f1', 'CHILD_FABRIC_NAME': 'child2'}]} + t_params.update(del_params) + + def responses(): + yield fabric_member_data(key1) + yield fabric_member_data(key2) + + gen_responses = ResponseGenerator(responses()) + + sender = Sender() + sender.ansible_module = MockAnsibleModule() + sender.gen = gen_responses + rest_send = RestSend(t_params) + rest_send.unit_test = True + rest_send.timeout = 1 + rest_send.response_handler = ResponseHandler() + rest_send.sender = sender + + with does_not_raise(): + instance = Merged(t_params) + instance.fabric_names = ["f1"] + instance.rest_send = rest_send + instance.results = Results() + + match = r"verify_child_fab_exists_in_controller: Playbook configuration for CHILD_FABRIC_NAME (\S+) " + match += r"is not found in Controller. Please create and try again" + with pytest.raises(ValueError, match=match): + instance.commit() + + +def test_fab_mem_merged_00063(child_fabric_add) -> None: + """ + ### Classes and Methods + + - childFabricCommon() + - __init__() + - payloads setter + - childFabricAdd + - __init__() + - commit() + + ### Summary + + - Verify successful child fabric add code path. + - The user attempts to add a child fabric into the MSD fabric. + """ + + key1 = "get_controller_version" + key2 = "childFabric_add_response" + + def responses(): + yield fabric_member_data(key1) + yield fabric_member_data(key2) + + gen_responses = ResponseGenerator(responses()) + + sender = Sender() + sender.ansible_module = MockAnsibleModule() + sender.gen = gen_responses + rest_send = RestSend({"state": "merged", "check_mode": False}) + rest_send.unit_test = True + rest_send.timeout = 1 + rest_send.response_handler = ResponseHandler() + rest_send.sender = sender + payload = {'destFabric': 'f1', 'sourceFabric': 'child'} + with does_not_raise(): + instance = child_fabric_add + instance.fabric_names = ["f1"] + instance.rest_send = rest_send + instance.results = Results() + instance.commit(payload) + + assert isinstance(instance.results.diff, list) + assert isinstance(instance.results.result, list) + assert isinstance(instance.results.response, list) + + assert len(instance.results.diff) == 1 + assert len(instance.results.metadata) == 1 + assert len(instance.results.response) == 1 + assert len(instance.results.result) == 1 + + assert instance.results.diff[0].get("sequence_number", None) == 1 + assert instance.results.diff[0].get("destFabric", None) == "f1" + assert instance.results.diff[0].get("sourceFabric", None) == "child" + + assert instance.results.metadata[0].get("action", None) == "child_fabric_add" + assert instance.results.metadata[0].get("check_mode", None) is False + assert instance.results.metadata[0].get("sequence_number", None) == 1 + assert instance.results.metadata[0].get("state", None) == "merged" + + assert instance.results.response[0].get("RETURN_CODE", None) == 200 + assert instance.results.response[0].get("MESSAGE", None) == "OK" + + assert instance.results.result[0].get("changed", None) is True + assert instance.results.result[0].get("success", None) is True + + assert False in instance.results.failed + assert True not in instance.results.failed + assert True in instance.results.changed + assert False not in instance.results.changed + + +def test_fab_mem_merged_00064(child_fabric_add) -> None: + """ + ### Classes and Methods + + - childFabricCommon() + - __init__() + - payloads setter + - childFabricAdd + - __init__() + - commit() + + ### Summary + + - Verify unsuccessful scenario - Failure response from NDFC during child fabric Add is displayed properly. + - The user attempts to Add a child fabric from the MSD fabric. + """ + key = "childFabric_add_response_3" + + def responses(): + yield fabric_member_data(key) + + gen_responses = ResponseGenerator(responses()) + + sender = Sender() + sender.ansible_module = MockAnsibleModule() + sender.gen = gen_responses + rest_send = RestSend({"state": "merged", "check_mode": False}) + rest_send.unit_test = True + rest_send.timeout = 1 + rest_send.response_handler = ResponseHandler() + rest_send.sender = sender + payload = {'destFabric': 'f1', 'sourceFabric': 'child'} + with does_not_raise(): + instance = child_fabric_add + instance.fabric_names = ["f1"] + instance.rest_send = rest_send + instance.results = Results() + instance.commit(payload) + + assert isinstance(instance.results.diff, list) + assert isinstance(instance.results.result, list) + assert isinstance(instance.results.response, list) + + assert len(instance.results.diff) == 1 + assert len(instance.results.metadata) == 1 + assert len(instance.results.response) == 1 + assert len(instance.results.result) == 1 + + assert instance.results.diff[0].get("sequence_number", None) == 1 + assert instance.results.diff[0].get("destFabric", None) is None + assert instance.results.diff[0].get("sourceFabric", None) is None + + assert instance.results.metadata[0].get("action", None) == "child_fabric_add" + assert instance.results.metadata[0].get("check_mode", None) is False + assert instance.results.metadata[0].get("sequence_number", None) == 1 + assert instance.results.metadata[0].get("state", None) == "merged" + + assert instance.results.response[0].get("RETURN_CODE", None) == 500 + assert instance.results.response[0].get("MESSAGE", None) == "NOK" + + assert instance.results.result[0].get("changed", None) is False + assert instance.results.result[0].get("success", None) is False + + assert True in instance.results.failed + assert False not in instance.results.failed + assert False in instance.results.changed + assert True not in instance.results.changed + + +def test_fab_mem_merged_00065() -> None: + """ + ### Classes and Methods + + - childFabricCommon() + - __init__() + - payloads setter + - childFabricAdd + - __init__() + - commit() + + ### Summary + + - Verify unsuccessful fabric-associations response and reaction of child add class + - The user attempts to add a child fabric + """ + + key1 = "get_controller_version" + key2 = "Fabric_association_get_failure_response" + t_params = {'config': [{'FABRIC_NAME': 'f1', 'CHILD_FABRIC_NAME': 'child2'}]} + t_params.update(merged_params) + + def responses(): + yield fabric_member_data(key1) + yield fabric_member_data(key2) + + gen_responses = ResponseGenerator(responses()) + + sender = Sender() + sender.ansible_module = MockAnsibleModule() + sender.gen = gen_responses + rest_send = RestSend(t_params) + rest_send.unit_test = True + rest_send.timeout = 1 + rest_send.response_handler = ResponseHandler() + rest_send.sender = sender + + with does_not_raise(): + instance = Merged(t_params) + instance.fabric_names = ["f1"] + instance.rest_send = rest_send + instance.results = Results() + + match = "Fabric Association response from NDFC controller returns failure" + with pytest.raises(ValueError, match=match): + instance.commit() + + +def test_fab_mem_merged_00066() -> None: + """ + ### Classes and Methods + + - childFabricCommon() + - __init__() + - payloads setter + - childFabricAdd + - __init__() + - commit() + + ### Summary + + - Verify unsuccessful child fabric add code path. + - The user attempts to add a child fabric to a MSD. but child fabric alread part of another MSD + """ + method_name = inspect.stack()[0][3] + key1 = "get_controller_version" + key2 = "Fabric_association_get_response_00018" + + t_params = {'config': [{'FABRIC_NAME': 'f1', 'CHILD_FABRIC_NAME': 'child2'}]} + t_params.update(del_params) + + def responses(): + yield fabric_member_data(key1) + yield fabric_member_data(key2) + + gen_responses = ResponseGenerator(responses()) + + sender = Sender() + sender.ansible_module = MockAnsibleModule() + sender.gen = gen_responses + rest_send = RestSend(t_params) + rest_send.unit_test = True + rest_send.timeout = 1 + rest_send.response_handler = ResponseHandler() + rest_send.sender = sender + + with does_not_raise(): + instance = Merged(t_params) + instance.fabric_names = ["f1"] + instance.rest_send = rest_send + instance.results = Results() + + match = r"Child fabric (\S+) is member of another Fabric" + with pytest.raises(ValueError, match=match): + instance.commit()