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/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 new file mode 100644 index 000000000..ab4a4b25e --- /dev/null +++ b/plugins/module_utils/msd/add_child_fab.py @@ -0,0 +1,171 @@ +# +# 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 +from ..common.results import Results +from ...module_utils.fabric.common import FabricCommon + + +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._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 MSD fabric. + + ### Raises + - ``ValueError`` if: + - ``_validate_commit_parameters`` raises ``ValueError``. + + """ + if 'DEPLOY' in payload: + self.deploy = payload.pop('DEPLOY') + + try: + self._validate_commit_parameters() + except ValueError as error: + 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 + + 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) + + 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 + - raise ``ValueError`` if ``fabric_names`` is not set + """ + 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) + + 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/delete_child_fab.py b/plugins/module_utils/msd/delete_child_fab.py new file mode 100644 index 000000000..2fdeedd53 --- /dev/null +++ b/plugins/module_utils/msd/delete_child_fab.py @@ -0,0 +1,177 @@ +# 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 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 +from ...module_utils.fabric.common import FabricCommon + + +class childFabricDelete(FabricCommon): + """ + Delete child fabrics from MSD fabrics + """ + + def __init__(self): + super().__init__() + 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 + + 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 + - Remove fabrics in Mentioned MSD fabric. + + ### Raises + - ``ValueError`` if: + - ``_validate_commit_parameters`` raises ``ValueError``. + + """ + if 'DEPLOY' in payload: + self.deploy = payload.pop('DEPLOY') + 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 + + 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.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) + + 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 + - raise ``ValueError`` if ``fabric_names`` is not set + """ + 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) + + 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/fabric_associations.py b/plugins/module_utils/msd/fabric_associations.py new file mode 100644 index 000000000..dc40fc323 --- /dev/null +++ b/plugins/module_utils/msd/fabric_associations.py @@ -0,0 +1,97 @@ +# +# 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): + + 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 + 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() + + 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 new file mode 100644 index 000000000..26baa2fbe --- /dev/null +++ b/plugins/module_utils/msd/query_child_fab.py @@ -0,0 +1,183 @@ +# 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] + + 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) + + if self.rest_send is None: + msg = f"{self.class_name}.{method_name}: " + msg += "rest_send must be set before calling commit." + raise ValueError(msg) + + if self.results is None: + # 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: + 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 + 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: + 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) + self.results.action = self.action + self.results.check_mode = self.rest_send.check_mode + self.results.state = self.rest_send.state + self.results.diff_current = 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 + ) + self.results.result_current = copy.deepcopy( + self.results.result_current + ) + self.results.register_task_result() diff --git a/plugins/modules/dcnm_fabric_member.py b/plugins/modules/dcnm_fabric_member.py new file mode 100644 index 000000000..87e65e019 --- /dev/null +++ b/plugins/modules/dcnm_fabric_member.py @@ -0,0 +1,702 @@ +#!/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_fabric_member +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 the member 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: add child fabrics to MSD + cisco.dcnm.dcnm_fabric_member: + 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_fabric_member: + 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.network.dcnm.dcnm import validate_list_of_dicts + + +@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.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_msd_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: + invalid_fab = item["destFabric"] + msg = f"{self.class_name}: {method_name}: " + msg += f"Playbook configuration for FABRIC_NAME {invalid_fab} " + msg += "is not found in Controller. Please create and try again" + raise ValueError(msg) + + def verify_msd_fab_type(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"): + invalid_fab = item["destFabric"] + msg = f"{self.class_name}: {method_name}: " + msg += f"Playbook configuration for FABRIC_NAME {invalid_fab} " + 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: + invalid_fab = item["sourceFabric"] + msg = f"{self.class_name}: {method_name}: " + 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): + 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"): + 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, item) -> bool: + for fabric in self.data: + if fabric == item["sourceFabric"]: + if (self.data[fabric]['fabricParent'] == item["destFabric"]): + return True + return False + + 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 = f"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}: " + 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: + 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. " + 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)) + + 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) + +# Keeping this function to check lower NDFC version support. yet to get data from Mike + 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 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 + 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.validate_input() + self.get_want() + + 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) + self.refresh_fab_association_data() + + 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 + 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["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 + self.fab_association.refreshed = False + self.fab_association.refresh() + self.refresh_fab_association_data() + + +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 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 + 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_controller_version() + # Version validation needs to be added + self.validate_input() + self.get_want() + + self.add.results = self.results + 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) + self.refresh_fab_association_data() + + 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: + 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 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"]) + 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 + + self.fab_association.refreshed = False + self.fab_association.refresh() + self.refresh_fab_association_data() + + +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 Association 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. " + 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() + 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 = [] + 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: + 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 + + 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() diff --git a/tests/integration/targets/dcnm_fabric_member/defaults/main.yaml b/tests/integration/targets/dcnm_fabric_member/defaults/main.yaml new file mode 100644 index 000000000..5f709c5aa --- /dev/null +++ b/tests/integration/targets/dcnm_fabric_member/defaults/main.yaml @@ -0,0 +1,2 @@ +--- +testcase: "*" diff --git a/tests/integration/targets/dcnm_fabric_member/meta/main.yaml b/tests/integration/targets/dcnm_fabric_member/meta/main.yaml new file mode 100644 index 000000000..32cf5dda7 --- /dev/null +++ b/tests/integration/targets/dcnm_fabric_member/meta/main.yaml @@ -0,0 +1 @@ +dependencies: [] diff --git a/tests/integration/targets/dcnm_fabric_member/tasks/dcnm.yaml b/tests/integration/targets/dcnm_fabric_member/tasks/dcnm.yaml new file mode 100644 index 000000000..e419fc865 --- /dev/null +++ b/tests/integration/targets/dcnm_fabric_member/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_fabric_member/tasks/main.yaml b/tests/integration/targets/dcnm_fabric_member/tasks/main.yaml new file mode 100644 index 000000000..fbcfa5803 --- /dev/null +++ b/tests/integration/targets/dcnm_fabric_member/tasks/main.yaml @@ -0,0 +1,2 @@ +--- +- { include_tasks: dcnm.yaml, tags: ['dcnm'] } 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 new file mode 100644 index 000000000..14f0ae133 --- /dev/null +++ b/tests/integration/targets/dcnm_fabric_member/tests/dcnm_fabric_member.yaml @@ -0,0 +1,1992 @@ +################################################################################ +# RUNTIME +################################################################################ +# Recent run times (MM:SS.ms): +# 02:57.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 +# 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 +################################################################################ +# 2. 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 +# 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. +# 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 +################################################################################ +# 11. 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_fabric_member_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_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_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_fabric_member: &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_fabric_member: + 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 MSD fabric.", +# "RETURN_CODE": 200, +# "sequence_number": 1 +# }, +# { +# "MESSAGE": "Child fabric is already member of MSD 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_fabric_member: *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 MSD fabric." + - 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 == 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_fabric_member: &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": "Given child fabric is already not a member of MSD fabric", +# "RETURN_CODE": 200, +# "sequence_number": 1 +# }, +# { +# "MESSAGE": "Given child fabric is already not a member of MSD fabric", +# "RETURN_CODE": 200, +# "sequence_number": 2 +# }, +# { +# "MESSAGE": "Given child fabric is already not a member of MSD 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_fabric_member: *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 == "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 == "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 == "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 == "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 == "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 == "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 + - 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 +############################################################################################### +#- 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_fabric_member: + 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_fabric_member: + 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 - 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] => { +# "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 diff --git a/tests/sanity/ignore-2.15.txt b/tests/sanity/ignore-2.15.txt index 9950a8963..a0b8d9437 100644 --- a/tests/sanity/ignore-2.15.txt +++ b/tests/sanity/ignore-2.15.txt @@ -24,4 +24,5 @@ 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_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 f573fb4d6..9e87ab049 100644 --- a/tests/sanity/ignore-2.16.txt +++ b/tests/sanity/ignore-2.16.txt @@ -21,4 +21,5 @@ 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_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/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()