From 7bc101f9a8de5dcf5a7e44f1b4742f3c3b5af65e Mon Sep 17 00:00:00 2001 From: mmudigon <62759545+mmudigon@users.noreply.github.com> Date: Tue, 27 Sep 2022 19:01:44 +0530 Subject: [PATCH 1/7] DCNM Links module initial checkin (#158) * DCNM Links module initial checkin * Update after running black and generating documentation * Update after fixing yaml lint issues * updates to unit test code to fix sanity errors * Fixing Lint errors * Fixing more linit errors * Update after addressing review comments --- README.md | 1 + docs/cisco.dcnm.dcnm_links_module.rst | 1284 ++++ plugins/modules/dcnm_links.py | 3326 ++++++++++ .../targets/dcnm_links/defaults/main.yaml | 2 + .../targets/dcnm_links/meta/main.yaml | 1 + .../targets/dcnm_links/tasks/dcnm.yaml | 20 + .../targets/dcnm_links/tasks/main.yaml | 2 + .../dcnm/dcnm_links_inter_ipv6_delete.yaml | 266 + .../dcnm/dcnm_links_inter_ipv6_merge.yaml | 432 ++ .../dcnm/dcnm_links_inter_ipv6_modify.yaml | 311 + .../dcnm/dcnm_links_inter_ipv6_replace.yaml | 361 + .../dcnm/dcnm_links_inter_missing_params.yaml | 314 + .../dcnm_links_inter_numbered_delete.yaml | 364 ++ .../dcnm/dcnm_links_inter_numbered_merge.yaml | 574 ++ .../dcnm_links_inter_numbered_modify.yaml | 479 ++ .../dcnm_links_inter_numbered_replace.yaml | 531 ++ .../tests/dcnm/dcnm_links_inter_query.yaml | 426 ++ .../dcnm_links_inter_template_change.yaml | 198 + .../dcnm/dcnm_links_intra_ipv6_delete.yaml | 182 + .../dcnm/dcnm_links_intra_ipv6_merge.yaml | 284 + .../dcnm/dcnm_links_intra_ipv6_modify.yaml | 249 + .../dcnm/dcnm_links_intra_ipv6_query.yaml | 263 + .../dcnm/dcnm_links_intra_ipv6_replace.yaml | 261 + .../dcnm/dcnm_links_intra_missing_params.yaml | 366 ++ .../dcnm_links_intra_numbered_delete.yaml | 164 + .../dcnm/dcnm_links_intra_numbered_merge.yaml | 301 + .../dcnm_links_intra_numbered_modify.yaml | 190 + .../dcnm/dcnm_links_intra_numbered_query.yaml | 235 + .../dcnm_links_intra_numbered_replace.yaml | 207 + ..._links_intra_numbered_template_change.yaml | 147 + .../dcnm_links_intra_unnumbered_delete.yaml | 167 + .../dcnm_links_intra_unnumbered_merge.yaml | 233 + .../dcnm_links_intra_unnumbered_modify.yaml | 167 + .../dcnm_links_intra_unnumbered_query.yaml | 235 + .../dcnm_links_intra_unnumbered_replace.yaml | 201 + ...inks_intra_unnumbered_template_change.yaml | 144 + .../dcnm/dcnm_links_intra_vpc_delete.yaml | 163 + .../dcnm/dcnm_links_intra_vpc_merge.yaml | 221 + .../dcnm/dcnm_links_intra_vpc_modify.yaml | 173 + .../dcnm/dcnm_links_intra_vpc_query.yaml | 218 + .../dcnm/dcnm_links_intra_vpc_replace.yaml | 195 + .../dcnm_links_intra_xe_delete.yaml | 154 + .../dcnm_links_intra_xe_merge.yaml | 220 + .../dcnm_links_intra_xe_modify.yaml | 175 + .../dcnm_links_intra_xe_query.yaml | 216 + .../dcnm_links_intra_xe_replace.yaml | 197 + .../prepare_dcnm_links/tasks/main.yaml | 81 + tests/sanity/ignore-2.10.txt | 1 + tests/sanity/ignore-2.11.txt | 1 + tests/sanity/ignore-2.12.txt | 1 + tests/sanity/ignore-2.9.txt | 1 + .../dcnm/fixtures/dcnm_links_configs.json | 2203 +++++++ .../dcnm/fixtures/dcnm_links_payloads.json | 1353 ++++ tests/unit/modules/dcnm/test_dcnm_links.py | 5783 +++++++++++++++++ 54 files changed, 24244 insertions(+) create mode 100644 docs/cisco.dcnm.dcnm_links_module.rst create mode 100644 plugins/modules/dcnm_links.py create mode 100644 tests/integration/targets/dcnm_links/defaults/main.yaml create mode 100644 tests/integration/targets/dcnm_links/meta/main.yaml create mode 100644 tests/integration/targets/dcnm_links/tasks/dcnm.yaml create mode 100644 tests/integration/targets/dcnm_links/tasks/main.yaml create mode 100644 tests/integration/targets/dcnm_links/tests/dcnm/dcnm_links_inter_ipv6_delete.yaml create mode 100644 tests/integration/targets/dcnm_links/tests/dcnm/dcnm_links_inter_ipv6_merge.yaml create mode 100644 tests/integration/targets/dcnm_links/tests/dcnm/dcnm_links_inter_ipv6_modify.yaml create mode 100644 tests/integration/targets/dcnm_links/tests/dcnm/dcnm_links_inter_ipv6_replace.yaml create mode 100644 tests/integration/targets/dcnm_links/tests/dcnm/dcnm_links_inter_missing_params.yaml create mode 100644 tests/integration/targets/dcnm_links/tests/dcnm/dcnm_links_inter_numbered_delete.yaml create mode 100644 tests/integration/targets/dcnm_links/tests/dcnm/dcnm_links_inter_numbered_merge.yaml create mode 100644 tests/integration/targets/dcnm_links/tests/dcnm/dcnm_links_inter_numbered_modify.yaml create mode 100644 tests/integration/targets/dcnm_links/tests/dcnm/dcnm_links_inter_numbered_replace.yaml create mode 100644 tests/integration/targets/dcnm_links/tests/dcnm/dcnm_links_inter_query.yaml create mode 100644 tests/integration/targets/dcnm_links/tests/dcnm/dcnm_links_inter_template_change.yaml create mode 100644 tests/integration/targets/dcnm_links/tests/dcnm/dcnm_links_intra_ipv6_delete.yaml create mode 100644 tests/integration/targets/dcnm_links/tests/dcnm/dcnm_links_intra_ipv6_merge.yaml create mode 100644 tests/integration/targets/dcnm_links/tests/dcnm/dcnm_links_intra_ipv6_modify.yaml create mode 100644 tests/integration/targets/dcnm_links/tests/dcnm/dcnm_links_intra_ipv6_query.yaml create mode 100644 tests/integration/targets/dcnm_links/tests/dcnm/dcnm_links_intra_ipv6_replace.yaml create mode 100644 tests/integration/targets/dcnm_links/tests/dcnm/dcnm_links_intra_missing_params.yaml create mode 100644 tests/integration/targets/dcnm_links/tests/dcnm/dcnm_links_intra_numbered_delete.yaml create mode 100644 tests/integration/targets/dcnm_links/tests/dcnm/dcnm_links_intra_numbered_merge.yaml create mode 100644 tests/integration/targets/dcnm_links/tests/dcnm/dcnm_links_intra_numbered_modify.yaml create mode 100644 tests/integration/targets/dcnm_links/tests/dcnm/dcnm_links_intra_numbered_query.yaml create mode 100644 tests/integration/targets/dcnm_links/tests/dcnm/dcnm_links_intra_numbered_replace.yaml create mode 100644 tests/integration/targets/dcnm_links/tests/dcnm/dcnm_links_intra_numbered_template_change.yaml create mode 100644 tests/integration/targets/dcnm_links/tests/dcnm/dcnm_links_intra_unnumbered_delete.yaml create mode 100644 tests/integration/targets/dcnm_links/tests/dcnm/dcnm_links_intra_unnumbered_merge.yaml create mode 100644 tests/integration/targets/dcnm_links/tests/dcnm/dcnm_links_intra_unnumbered_modify.yaml create mode 100644 tests/integration/targets/dcnm_links/tests/dcnm/dcnm_links_intra_unnumbered_query.yaml create mode 100644 tests/integration/targets/dcnm_links/tests/dcnm/dcnm_links_intra_unnumbered_replace.yaml create mode 100644 tests/integration/targets/dcnm_links/tests/dcnm/dcnm_links_intra_unnumbered_template_change.yaml create mode 100644 tests/integration/targets/dcnm_links/tests/dcnm/dcnm_links_intra_vpc_delete.yaml create mode 100644 tests/integration/targets/dcnm_links/tests/dcnm/dcnm_links_intra_vpc_merge.yaml create mode 100644 tests/integration/targets/dcnm_links/tests/dcnm/dcnm_links_intra_vpc_modify.yaml create mode 100644 tests/integration/targets/dcnm_links/tests/dcnm/dcnm_links_intra_vpc_query.yaml create mode 100644 tests/integration/targets/dcnm_links/tests/dcnm/dcnm_links_intra_vpc_replace.yaml create mode 100644 tests/integration/targets/dcnm_links/tests/dcnm/xe-test-cases/dcnm_links_intra_xe_delete.yaml create mode 100644 tests/integration/targets/dcnm_links/tests/dcnm/xe-test-cases/dcnm_links_intra_xe_merge.yaml create mode 100644 tests/integration/targets/dcnm_links/tests/dcnm/xe-test-cases/dcnm_links_intra_xe_modify.yaml create mode 100644 tests/integration/targets/dcnm_links/tests/dcnm/xe-test-cases/dcnm_links_intra_xe_query.yaml create mode 100644 tests/integration/targets/dcnm_links/tests/dcnm/xe-test-cases/dcnm_links_intra_xe_replace.yaml create mode 100644 tests/integration/targets/prepare_dcnm_links/tasks/main.yaml create mode 100644 tests/unit/modules/dcnm/fixtures/dcnm_links_configs.json create mode 100644 tests/unit/modules/dcnm/fixtures/dcnm_links_payloads.json create mode 100644 tests/unit/modules/dcnm/test_dcnm_links.py diff --git a/README.md b/README.md index 63a8874c2..f88bce05d 100644 --- a/README.md +++ b/README.md @@ -34,6 +34,7 @@ Name | Description --- | --- [cisco.dcnm.dcnm_interface](https://github.com/CiscoDevNet/ansible-dcnm/blob/main/docs/cisco.dcnm.dcnm_interface_module.rst)|DCNM Ansible Module for managing interfaces. [cisco.dcnm.dcnm_inventory](https://github.com/CiscoDevNet/ansible-dcnm/blob/main/docs/cisco.dcnm.dcnm_inventory_module.rst)|Add and remove Switches from a DCNM managed VXLAN fabric. +[cisco.dcnm.dcnm_links](https://github.com/CiscoDevNet/ansible-dcnm/blob/main/docs/cisco.dcnm.dcnm_links_module.rst)|DCNM ansible module for managing Links. [cisco.dcnm.dcnm_network](https://github.com/CiscoDevNet/ansible-dcnm/blob/main/docs/cisco.dcnm.dcnm_network_module.rst)|Add and remove Networks from a DCNM managed VXLAN fabric. [cisco.dcnm.dcnm_policy](https://github.com/CiscoDevNet/ansible-dcnm/blob/main/docs/cisco.dcnm.dcnm_policy_module.rst)|DCNM Ansible Module for managing policies. [cisco.dcnm.dcnm_resource_manager](https://github.com/CiscoDevNet/ansible-dcnm/blob/main/docs/cisco.dcnm.dcnm_resource_manager_module.rst)|DCNM ansible module for managing resources. diff --git a/docs/cisco.dcnm.dcnm_links_module.rst b/docs/cisco.dcnm.dcnm_links_module.rst new file mode 100644 index 000000000..634cacb3e --- /dev/null +++ b/docs/cisco.dcnm.dcnm_links_module.rst @@ -0,0 +1,1284 @@ +.. _cisco.dcnm.dcnm_links_module: + + +********************* +cisco.dcnm.dcnm_links +********************* + +**DCNM ansible module for managing Links.** + + +Version added: 2.1.0 + +.. contents:: + :local: + :depth: 1 + + +Synopsis +-------- +- DCNM ansible module for creating, modifying, deleting and querying Links + + + + +Parameters +---------- + +.. raw:: html + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ParameterChoices/DefaultsComments
+
+ config + +
+ list + / elements=dictionary +
+
+ +
A list of dictionaries containing Links information.
+
+
+ dst_device + +
+ string + / required +
+
+ +
IP address or DNS name of the destination switch which is part of the link being configured.
+
+
+ dst_fabric + +
+ string + / required +
+
+ +
Name of the destination fabric. If this is same as 'src_fabric' then the link is considered intra-fabric link. If this parameter is different from 'src_fabric', then the link is considered inter-fabric link.
+
+
+ dst_interface + +
+ string + / required +
+
+ +
Interface on the destination device which is part of the link being configured.
+
+
+ profile + +
+ - +
+
+ +
Additional link related parameters that must be included while creating links.
+
+
+ admin_state + +
+ boolean + / required +
+
+
    Choices: +
  • no
  • +
  • yes
  • +
+
+
Admin state of the link.
+
This parameter is not required if template is 'ext_evpn_multisite_overlay_setup', 'ext_multisite_underlay_setup', and 'ext_fabric_setup'.
+
+
+ auto_deploy + +
+ string + / required +
+
+ +
Flag that controls auto generation of neighbor VRF Lite configuration for managed neighbor devices.
+
This parameter is required only if template is 'ext_fabric_setup'.
+
+
+ bgp_multihop + +
+ integer +
+
+ Default:
5
+
+
eBGP Time-To-Live Value for Remote Peer.
+
This parameter is required only if template is 'ext_evpn_multisite_overlay_setup'.
+
+
+ deploy_dci_tracking + +
+ boolean +
+
+
    Choices: +
  • no ←
  • +
  • yes
  • +
+
+
Flag to enable deploy DCI tracking.
+
This parameter is required only if template is 'ext_multisite_underlay_setup'.
+
This parameter MUST be included only if the fabrics are part of multisite.
+
+
+ dst_asn + +
+ string + / required +
+
+ +
BGP ASN number on the destination fabric.
+
This parameter is required only if template is 'ext_fabric_setup' or 'ext_multisite_underlay_setup'. or "ext_evpn_multisite_overlay_setup"
+
+
+ ebgp_auth_key_type + +
+ integer + / required +
+
+
    Choices: +
  • 3 (3DES)
  • +
  • 7 (Cisco)
  • +
+
+
BGP Key Encryption Type.
+
This parameter is required only if template is 'ext_multisite_underlay_setup' or 'ext_evpn_multisite_overlay_setup'.
+
This parameter is required only if inherit_from_msd is false.
+
+
+ ebgp_password + +
+ string + / required +
+
+ +
Encrypted eBGP Password Hex String.
+
This parameter is required only if template is 'ext_multisite_underlay_setup' or 'ext_evpn_multisite_overlay_setup'.
+
This parameter is required only if inherit_from_msd is false.
+
+
+ ebgp_password_enable + +
+ boolean +
+
+
    Choices: +
  • no
  • +
  • yes ←
  • +
+
+
Flag to enable eBGP password.
+
This parameter is required only if template is 'ext_multisite_underlay_setup' or 'ext_evpn_multisite_overlay_setup'.
+
+
+ enable_macsec + +
+ boolean +
+
+
    Choices: +
  • no ←
  • +
  • yes
  • +
+
+
Enable MACsec on the link.
+
This parameter is applicable only if MACsec feature is enabled on the fabric.
+
This parameter is applicable only if template is 'int_intra_fabric_ipv6_link_local' or 'int_intra_fabric_num_link' or 'int_intra_fabric_unnum_link'.
+
+
+ inherit_from_msd + +
+ boolean +
+
+
    Choices: +
  • no
  • +
  • yes ←
  • +
+
+
Flag indicating whether to inherit BGP password from MSD information.
+
Applicable only when source and destination fabric are in the same MSD fabric.
+
This parameter is required only if template is 'ext_multisite_underlay_setup' or 'ext_evpn_multisite_overlay_setup'
+
+
+ intf_vrf + +
+ string +
+
+ Default:
""
+
+
Name of the non-default VRF for the link.
+
Make sure to configure the VRF before using it here.
+
This parameter is applicable only if template is 'int_intra_vpc_peer_keep_alive_link'.
+
+
+ ipv4_address + +
+ string + / required +
+
+ +
IPV4 address of the source interface without mask.
+
This parameter is required only if template is 'ext_evpn_multisite_overlay_setup'.
+
+
+ ipv4_subnet + +
+ string + / required +
+
+ +
IPV4 address of the source interface with mask.
+
This parameter is required only if template is 'ext_fabric_setup' or 'ext_multisite_underlay_setup'.
+
+
+ max_paths + +
+ integer +
+
+ Default:
1
+
+
Maximum number of iBGP/eBGP paths.
+
This parameter is required only if template is 'ext_multisite_underlay_setup'.
+
+
+ mtu + +
+ integer + / required +
+
+ +
MTU of the link.
+
This parameter is optional if template is 'ios_xe_int_intra_fabric_num_link'. The default value in this case will be 1500.
+
This parameter is not required if template is 'ext_evpn_multisite_overlay_setup'.
+
+
+ neighbor_ip + +
+ string + / required +
+
+ +
IPV4 address of the neighbor switch on the destination fabric.
+
This parameter is required only if template is 'ext_fabric_setup' or 'ext_multisite_underlay_setup' or "ext_evpn_multisite_overlay_setup"
+
+
+ peer1_bfd_echo_disable + +
+ boolean +
+
+
    Choices: +
  • no ←
  • +
  • yes
  • +
+
+
Enable BFD echo on the source interface. Only applicable if BFD is enabled on the fabric.
+
This parameter is applicable only if template is 'int_intra_fabric_num_link'.
+
+
+ peer1_cmds + +
+ list +
+
+ Default:
[]
+
+
Commands to be included in the configuration under the source interface.
+
This parameter is not required if template is 'ext_evpn_multisite_overlay_setup'.
+
+
+ peer1_description + +
+ string +
+
+ Default:
""
+
+
Description of the source interface.
+
This parameter is not required if template is 'ext_evpn_multisite_overlay_setup'.
+
+
+ peer1_ipv4_address + +
+ string + / required +
+
+ +
IPV4 address of the source interface.
+
This parameter is optional if the underlying fabric is ipv6 enabled.
+
This parameter is required only if template is 'int_intra_fabric_num_link' or 'ios_xe_int_intra_fabric_num_link' or 'int_intra_vpc_peer_keep_alive_link'.
+
+
+ peer1_ipv6_address + +
+ string +
+
+ Default:
""
+
+
IPV6 address of the source interface.
+
This parameter is required only if the underlying fabric is ipv6 enabled.
+
This parameter is required only if template is 'int_intra_fabric_num_link' or 'ios_xe_int_intra_fabric_num_link' or 'int_intra_vpc_peer_keep_alive_link'.
+
+
+ peer2_bfd_echo_disable + +
+ boolean +
+
+
    Choices: +
  • no ←
  • +
  • yes
  • +
+
+
Enable BFD echo on the destination interface. Only applicable if BFD is enabled on the fabric.
+
This parameter is applicable only if template is 'int_intra_fabric_num_link'.
+
+
+ peer2_cmds + +
+ list +
+
+ Default:
[]
+
+
Commands to be included in the configuration under the destination interface.
+
This parameter is not required if template is 'ext_evpn_multisite_overlay_setup'.
+
+
+ peer2_description + +
+ string +
+
+ Default:
""
+
+
Description of the destination interface.
+
This parameter is not required if template is 'ext_evpn_multisite_overlay_setup'.
+
+
+ peer2_ipv4_address + +
+ string + / required +
+
+ +
IPV4 address of the destination interface.
+
This parameter is optional if the underlying fabric is ipv6 enabled.
+
This parameter is required only if template is 'int_intra_fabric_num_link' or 'ios_xe_int_intra_fabric_num_link' or 'int_intra_vpc_peer_keep_alive_link'.
+
+
+ peer2_ipv6_address + +
+ string +
+
+ Default:
""
+
+
IPV6 address of the destination interface.
+
This parameter is required only if the underlying fabric is ipv6 enabled.
+
This parameter is required only if template is 'int_intra_fabric_num_link' or 'ios_xe_int_intra_fabric_num_link' or 'int_intra_vpc_peer_keep_alive_link'.
+
+
+ route_tag + +
+ string +
+
+ Default:
""
+
+
Routing tag associated with interface IP.
+
This parameter is required only if template is 'ext_multisite_underlay_setup'
+
+
+ src_asn + +
+ string + / required +
+
+ +
BGP ASN number on the source fabric.
+
This parameter is required only if template is 'ext_fabric_setup' or 'ext_multisite_underlay_setup' or "ext_evpn_multisite_overlay_setup"
+
+
+ trm_enabled + +
+ boolean +
+
+
    Choices: +
  • no ←
  • +
  • yes
  • +
+
+
Flag to enable Tenant Routed Multicast.
+
This parameter is required only if template is 'ext_evpn_multisite_overlay_setup'.
+
+
+ src_device + +
+ list + / required +
+
+ +
IP address or DNS name of the source switch which is part of the link being configured.
+
+
+ src_interface + +
+ string + / required +
+
+ +
Interface on the source device which is part of the link being configured.
+
+
+ template + +
+ string + / required +
+
+
    Choices: +
  • int_intra_fabric_ipv6_link_local(intra-fabric)
  • +
  • int_intra_fabric_num_link (intra-fabric)
  • +
  • int_intra_fabric_unnum_link (intra-fabric)
  • +
  • int_intra_vpc_peer_keep_alive_link (intra-fabric)
  • +
  • int_pre_provision_intra_fabric_link (intra-fabric)
  • +
  • ios_xe_int_intra_fabric_num_link (intra-fabric)
  • +
  • ext_fabric_setup (inter-fabric)
  • +
  • ext_multisite_underlay_setup (inter-fabric)
  • +
  • ext_evpn_multisite_overlay_setup (inter-fabric)
  • +
+
+
Name of the template that is applied on the link being configured.
+
The last 3 template choices are applicable for inter-fabric links and the others are applicable for intra-fabric links.
+
This parameter is required only for 'merged' and 'replaced' states. It is
+
optional for other states.
+
+
+ deploy + +
+ boolean +
+
+
    Choices: +
  • no
  • +
  • yes ←
  • +
+
+
Flag to control deployment of links. If set to 'true' then the links included will be deployed to specified switches. If set to 'false', the links will be created but not deployed.
+
Setting this flag to 'true' will result in all pending configurations on the source and destination devices to be deployed.
+
+
+ src_fabric + +
+ string + / required +
+
+ +
Name of the source fabric for links operations.
+
+
+ state + +
+ string +
+
+
    Choices: +
  • merged ←
  • +
  • replaced
  • +
  • deleted
  • +
  • query
  • +
+
+
The required state of the configuration after module completion.
+
+
+ + + + +Examples +-------- + +.. code-block:: yaml + + # States: + # This module supports the following states: + # + # Merged: + # Links defined in the playbook will be merged into the target fabric. + # + # The links listed in the playbook will be created if not already present on the DCNM + # server. If the link is already present and the configuration information included + # in the playbook is either different or not present in DCNM, then the corresponding + # information is added to the link on DCNM. If a link mentioned in playbook + # is already present on DCNM and there is no difference in configuration, no operation + # will be performed for such link. + # + # Replaced: + # Links defined in the playbook will be replaced in the target fabric. + # + # The state of the links listed in the playbook will serve as source of truth for the + # same links present on the DCNM under the fabric mentioned. Additions and updations + # will be done to bring the DCNM links to the state listed in the playbook. + # Note: Replace will only work on the links mentioned in the playbook. + # + # Deleted: + # Links defined in the playbook will be deleted in the target fabric. + # + # WARNING: Deleting a Link will deploy all pending configurations on the impacted switches + # + # Query: + # Returns the current DCNM state for the links listed in the playbook. Information included + # in the playbook will be used as filters to get the desired output. + # + # CREATE LINKS + # + # NUMBERED FABRIC + # + # INTRA-FABRIC + + - name: Create Links + cisco.dcnm.dcnm_links: + state: merged # choose from [merged, replaced, deleted, query] + src_fabric: "ansible_num_fabric" + config: + - dst_fabric: "ansible_num_fabric" # Destination fabric + src_interface: "Ethernet1/1" # Interface on the Source fabric + dst_interface: "Ethernet1/1" # Interface on the Destination fabric + src_device: 193.168.1.1 # Device on the Source fabric + dst_device: 193.168.1.2 # Device on the Destination fabric + template: int_intra_fabric_num_link # template to be applied, choose from + # [ int_intra_fabric_ipv6_link_local, int_intra_fabric_num_link, + # int_intra_fabric_unnum_link, int_intra_vpc_peer_keep_alive_link, + # int_pre_provision_intra_fabric_link, ios_xe_int_intra_fabric_num_link ] + + profile: + peer1_ipv4_addr: 192.168.1.1 # IP address of the Source interface + peer2_ipv4_addr: 192.168.1.2 # IP address of the Destination interface + admin_state: true # choose from [true, false] + mtu: 9216 # + peer1_description: "Description of source" # optional, default is "" + peer2_description: "Description of dest" # optional, default is "" + peer1_bfd_echo_disable: false # optional, choose from [true, false] + peer2_bfd_echo_disable: false # optional, choose from [true, false] + enable_macsec: false # optional, choose from [true, false] + peer1_cmds: # Freeform config for source device + - no shutdown # optional, default is "" + peer2_cmds: # Freeform config for destination device + - no shutdown # optional, default is "" + + - dst_fabric: "ansible_num_fabric" # Destination fabric + src_interface: "Ethernet1/2" # Interface on the Source fabric + dst_interface: "Ethernet1/2" # Interface on the Destination fabric + src_device: 193.168.1.1 # Device on the Source fabric + dst_device: 193.168.1.2 # Device on the Destination fabric + template: int_pre_provision_intra_fabric_link # template to be applied, choose from + # [ int_intra_fabric_ipv6_link_local, int_intra_fabric_num_link, + # int_intra_fabric_unnum_link, int_intra_vpc_peer_keep_alive_link, + # int_pre_provision_intra_fabric_link, ios_xe_int_intra_fabric_num_link ] + - dst_fabric: "ansible_num_fabric" # Destination fabric + src_interface: "Ethernet1/3" # Interface on the Source fabric + dst_interface: "Ethernet1/3" # Interface on the Destination fabric + src_device: 193.168.1.1 # Device on the Source fabric + dst_device: 193.168.1.2 # Device on the Destination fabric + template: ios_xe_int_intra_fabric_num_link # template to be applied, choose from + # [ int_intra_fabric_ipv6_link_local, int_intra_fabric_num_link, + # int_intra_fabric_unnum_link, int_intra_vpc_peer_keep_alive_link, + # int_pre_provision_intra_fabric_link, ios_xe_int_intra_fabric_num_link ] + + profile: + peer1_ipv4_addr: 192.169.2.1 # IPV4 address of the Source interface + peer2_ipv4_addr: 192.169.2.2 # IPV4 address of the Destination interface + peer1_ipv6_addr: fe80:01::01 # optional, default is "" + peer2_ipv6_addr: fe80:01::02 # optional, default is "" + admin_state: true # choose from [true, false] + mtu: 1500 # optional, default is 1500 + peer1_description: "Description of source" # optional, default is "" + peer2_description: "Description of dest" # optional, default is "" + peer1_bfd_echo_disable: false # optional, choose from [true, false] + peer2_bfd_echo_disable: false # optional, choose from [true, false] + enable_macsec: false # optional, choose from [true, false] + peer1_cmds: # Freeform config for source device + - no shutdown # optional, default is "" + peer2_cmds: # Freeform config for destination device + - no shutdown # optional, default is "" + # + # INTER-FABRIC + + - name: Create Links including optional parameters + cisco.dcnm.dcnm_links: &links_merge_with_opt + state: merged # choose from [merged, replaced, deleted, query] + src_fabric: "{{ ansible_num_fabric }}" + config: + - dst_fabric: "{{ ansible_unnum_fabric }}" # Destination fabric + src_interface: "{{ intf_1_3 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_3 }}" # Interface on the Destination fabric + src_device: "{{ ansible_num_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_unnum_switch1 }}" # Device on the Destination fabric + template: ext_fabric_setup # template to be applied, choose from + # [ ext_fabric_setup, ext_multisite_underlay_setup, + # ext_evpn_multisite_overlay_setup ] + profile: + ipv4_subnet: 193.168.1.1/24 # IP address of interface in src fabric with mask + neighbor_ip: 193.168.1.2 # IP address of the interface in dst fabric + src_asn: 1000 # BGP ASN in source fabric + dst_asn: 1001 # BGP ASN in destination fabric + mtu: 9216 # + auto_deploy: false # optional, default is false + # Flag that controls auto generation of neighbor VRF Lite configuration + peer1_description: "Description of source" # optional, default is "" + peer2_description: "Description of dest" # optional, default is "" + peer1_cmds: # Freeform config for source interface + - no shutdown # optional, default is "" + peer2_cmds: # Freeform config for destination interface + - no shutdown # optional, default is "" + + - dst_fabric: "{{ ansible_unnum_fabric }}" # Destination fabric + src_interface: "{{ intf_1_4 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_4 }}" # Interface on the Destination fabric + src_device: "{{ ansible_num_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_unnum_switch1 }}" # Device on the Destination fabric + template: ext_multisite_underlay_setup # template to be applied, choose from + # [ ext_fabric_setup, ext_multisite_underlay_setup, + # ext_evpn_multisite_overlay_setup ] + profile: + ipv4_subnet: 193.168.2.1/24 # IP address of interface in src fabric with mask + neighbor_ip: 193.168.2.2 # IP address of the interface in dst fabric + src_asn: 1200 # BGP ASN in source fabric + dst_asn: 1201 # BGP ASN in destination fabric + mtu: 9216 # + deploy_dci_tracking: false # optional, default is false + max_paths: 1 # optional, default is 1 + route_tag: 12345 # optional, optional is "" + ebgp_password_enable: true # optional, default is true + ebgp_password: 0102030405 # optional, required only if ebgp_password_enable flag is true, and inherit_from_msd + # is false. + inherit_from_msd: True # optional, required only if ebgp_password_enable flag is true, default is false + ebgp_auth_key_type: 3 # optional, required only if ebpg_password_enable is true, and inherit_from_msd + # is false. Default is 3 + # choose from [3 - 3DES, 7 - Cisco ] + peer1_description: "Description of source" # optional, default is "" + peer2_description: "Description of dest" # optional, default is "" + peer1_cmds: # Freeform config for source interface + - no shutdown # optional, default is "" + peer2_cmds: # Freeform config for destination interface + - no shutdown # optional, default is "" + + - dst_fabric: "{{ ansible_unnum_fabric }}" # Destination fabric + src_interface: "{{ intf_1_5 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_5 }}" # Interface on the Destination fabric + src_device: "{{ ansible_num_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_unnum_switch1 }}" # Device on the Destination fabric + template: ext_evpn_multisite_overlay_setup # template to be applied, choose from + # [ ext_fabric_setup, ext_multisite_underlay_setup, + # ext_evpn_multisite_overlay_setup ] + profile: + ipv4_addr: 193.168.3.1 # IP address of interface in src fabric + neighbor_ip: 193.168.3.2 # IP address of the interface in dst fabric + src_asn: 1300 # BGP ASN in source fabric + dst_asn: 1301 # BGP ASN in destination fabric + trm_enabled: false # optional, default is false + bgp_multihop: 5 # optional, default is 5 + ebgp_password_enable: true # optional, default is true + ebgp_password: 0102030405 # optional, required only if ebgp_password_enable flag is true, and inherit_from_msd + # is false. Default is 3 + inherit_from_msd: false # optional, required only if ebgp_password_enable flag is true, default is false + ebpg_auth_key_type: 3 # optional, required only if ebpg_password_enable is true, and inherit_from_msd + # is false. Default is 3 + # choose from [3 - 3DES, 7 - Cisco ] + + # FABRIC WITH VPC PAIRED SWITCHES + + - name: Create Links + cisco.dcnm.dcnm_links: + state: merged # choose from [merged, replaced, deleted, query] + src_fabric: "ansible_vpc_fabric" + config: + - dst_fabric: "ansible_vpc_fabric" # Destination fabric + src_interface: "Ethernet1/4" # Interface on the Source fabric + dst_interface: "Ethernet1/4" # Interface on the Destination fabric + src_device: "ansible_vpc_switch1" # Device on the Source fabric + dst_device: "ansible_vpc_switch2" # Device on the Destination fabric + template: int_intra_vpc_peer_keep_alive_link # template to be applied, choose from + # [ int_intra_fabric_ipv6_link_local, int_intra_fabric_num_link, + # int_intra_fabric_unnum_link, int_intra_vpc_peer_keep_alive_link, + # int_pre_provision_intra_fabric_link, ios_xe_int_intra_fabric_num_link ] + + profile: + peer1_ipv4_addr: 192.170.1.1 # IPV4 address of the Source interface + peer2_ipv4_addr: 192.170.1.2 # IPV4 address of the Destination interface + peer1_ipv6_addr: fe80:2a::01 # optional, default is "" + peer2_ipv6_addr: fe80:2a::02 # optional, default is "" + admin_state: true # choose from [true, false] + mtu: 9216 # + peer1_description: "Description of source" # optional, default is "" + peer2_description: "Description of dest" # optional, default is "" + enable_macsec: false # optional, choose from [true, false] + peer1_cmds: # Freeform config for source device + - no shutdown # optional, default is "" + peer2_cmds: # Freeform config for destination device + - no shutdown # optional, default is "" + intf_vrf: "test_vrf" # optional, default is "" + + # UNNUMBERED FABRIC + + - name: Create Links + cisco.dcnm.dcnm_links: + state: merged # choose from [merged, replaced, deleted, query] + src_fabric: "ansible_unnum_fabric" + config: + - dst_fabric: "ansible_unnum_fabric" # Destination fabric + src_interface: "Ethernet1/1" # Interface on the Source fabric + dst_interface: "Ethernet1/1" # Interface on the Destination fabric + src_device: "ansible_unnum_switch1" # Device on the Source fabric + dst_device: "ansible_unnum_switch2" # Device on the Destination fabric + template: int_intra_fabric_unnum_link # template to be applied, choose from + # [ int_intra_fabric_ipv6_link_local, int_intra_fabric_num_link, + # int_intra_fabric_unnum_link, int_intra_vpc_peer_keep_alive_link, + # int_pre_provision_intra_fabric_link, ios_xe_int_intra_fabric_num_link ] + + profile: + admin_state: true # choose from [true, false] + mtu: 9216 # + peer1_description: "Description of source" # optional, default is "" + peer2_description: "Description of dest" # optional, default is "" + enable_macsec: false # optional, choose from [true, false] + peer1_cmds: # Freeform config for source device + - no shutdown # optional, default is "" + peer2_cmds: # Freeform config for destination device + - no shutdown # optional, default is "" + + - dst_fabric: "ansible_unnum_fabric" # Destination fabric + src_interface: "Ethernet1/2" # Interface on the Source fabric + dst_interface: "Ethernet1/2" # Interface on the Destination fabric + src_device: "ansible_unnum_switch1" # Device on the Source fabric + dst_device: "ansible_unnum_switch2" # Device on the Destination fabric + template: int_pre_provision_intra_fabric_link # template to be applied, choose from + # [ int_intra_fabric_ipv6_link_local, int_intra_fabric_num_link, + # int_intra_fabric_unnum_link, int_intra_vpc_peer_keep_alive_link, + # int_pre_provision_intra_fabric_link, ios_xe_int_intra_fabric_num_link ] + + # IPV6 UNDERLAY FABRIC + + - name: Create Links + cisco.dcnm.dcnm_links: + state: merged # choose from [merged, replaced, deleted, query] + src_fabric: "ansible_ipv6_fabric" + config: + - dst_fabric: "ansible_ipv6_fabric" # Destination fabric + src_interface: "Ethernet1/1" # Interface on the Source fabric + dst_interface: "Ethernet1/1" # Interface on the Destination fabric + src_device: "ansible_ipv6_switch1" # Device on the Source fabric + dst_device: "ansible_ipv6_switch2" # Device on the Destination fabric + template: int_intra_fabric_ipv6_link_local # template to be applied, choose from + # [ int_intra_fabric_ipv6_link_local, int_intra_fabric_num_link, + # int_intra_fabric_unnum_link, int_intra_vpc_peer_keep_alive_link, + # int_pre_provision_intra_fabric_link, ios_xe_int_intra_fabric_num_link ] + + profile: + peer1_ipv4_addr: 192.169.1.1 # optional, default is "" + peer2_ipv4_addr: 192.169.1.2 # optional, default is "" + peer1_ipv6_addr: fe80:0201::01 # IP address of the Source interface + peer2_ipv6_addr: fe80:0201::02 # IP address of the Source interface + admin_state: true # choose from [true, false] + mtu: 9216 # + peer1_description: "Description of source" # optional, default is "" + peer2_description: "Description of dest" # optional, default is "" + peer1_bfd_echo_disable: false # optional, choose from [true, false] + peer2_bfd_echo_disable: false # optional, choose from [true, false] + enable_macsec: false # optional, choose from [true, false] + peer1_cmds: # Freeform config for source device + - no shutdown # optional, default is "" + peer2_cmds: # Freeform config for destination device + - no shutdown # optional, default is "" + + - dst_fabric: "ansible_ipv6_fabric" # Destination fabric + src_interface: "Ethernet1/2" # Interface on the Source fabric + dst_interface: "Ethernet1/2" # Interface on the Destination fabric + src_device: "ansible_ipv6_switch1" # Device on the Source fabric + dst_device: "ansible_ipv6_switch2" # Device on the Destination fabric + template: int_pre_provision_intra_fabric_link # template to be applied, choose from + # [ int_intra_fabric_ipv6_link_local, int_intra_fabric_num_link, + # int_intra_fabric_unnum_link, int_intra_vpc_peer_keep_alive_link, + # int_pre_provision_intra_fabric_link, ios_xe_int_intra_fabric_num_link ] + - dst_fabric: "ansible_ipv6_fabric" # Destination fabric + src_interface: "Ethernet1/3" # Interface on the Source fabric + dst_interface: "Ethernet1/3" # Interface on the Destination fabric + src_device: "ansible_ipv6_switch1" # Device on the Source fabric + dst_device: "ansible_ipv6_switch2" # Device on the Destination fabric + template: int_intra_fabric_num_link # template to be applied, choose from + # [ int_intra_fabric_ipv6_link_local, int_intra_fabric_num_link, + # int_intra_fabric_unnum_link, int_intra_vpc_peer_keep_alive_link, + # int_pre_provision_intra_fabric_link, ios_xe_int_intra_fabric_num_link ] + + profile: + peer1_ipv4_addr: 192.169.2.1 # IPV4 address of the Source interface + peer2_ipv4_addr: 192.169.2.2 # IPV4 address of the Destination interface + peer1_ipv6_addr: fe80:0202::01 # IP address of the Source interface + peer2_ipv6_addr: fe80:0202::02 # IP address of the Source interface + admin_state: true # choose from [true, false] + mtu: 1500 # optional, default is 1500 + peer1_description: "Description of source" # optional, default is "" + peer2_description: "Description of dest" # optional, default is "" + peer1_bfd_echo_disable: false # optional, choose from [true, false] + peer2_bfd_echo_disable: false # optional, choose from [true, false] + enable_macsec: false # optional, choose from [true, false] + peer1_cmds: # Freeform config for source device + - no shutdown # optional, default is "" + peer2_cmds: # Freeform config for destination device + - no shutdown # optional, default is "" + # DELETE LINKS + + - name: Delete Links + cisco.dcnm.dcnm_links: + state: deleted # choose from [merged, replaced, deleted, query] + src_fabric: "ansible_num_fabric" + config: + - dst_fabric: "ansible_num_fabric" # Destination fabric + src_interface: "Ethernet1/1" # Interface on the Source fabric + dst_interface: "Ethernet1/1" # Interface on the Destination fabric + src_device: 193.168.1.1 # Device on the Source fabric + dst_device: 193.168.1.2 # Device on the Destination fabric + + # QUERY LINKS + + - name: Query Links - with Src Fabric + cisco.dcnm.dcnm_links: + state: query # choose from [merged, replaced, deleted, query] + src_fabric: "ansible_num_fabric" + + - name: Query Links - with Src & Dst Fabric + cisco.dcnm.dcnm_links: + state: query # choose from [merged, replaced, deleted, query] + src_fabric: "ansible_num_fabric" + config: + - dst_fabric: "ansible_num_fabric" # optional, Destination fabric + + - name: Query Links - with Src & Dst Fabric, Src Intf + cisco.dcnm.dcnm_links: + state: query # choose from [merged, replaced, deleted, query] + src_fabric: "ansible_num_fabric" + config: + - dst_fabric: "ansible_num_fabric" # optional, Destination fabric + src_interface: "Ethernet1/1" # optional, Interface on the Source fabric + + - name: Query Links - with Src & Dst Fabric, Src & Dst Intf + cisco.dcnm.dcnm_links: + state: query # choose from [merged, replaced, deleted, query] + src_fabric: "ansible_num_fabric" + config: + - dst_fabric: "ansible_num_fabric" # optional, Destination fabric + src_interface: "Ethernet1/1" # optional, Interface on the Source fabric + dst_interface: "Ethernet1/1" # optional, Interface on the Destination fabric + + - name: Query Links - with Src & Dst Fabric, Src & Dst Intf, Src Device + cisco.dcnm.dcnm_links: + state: query # choose from [merged, replaced, deleted, query] + src_fabric: "ansible_num_fabric" + config: + - dst_fabric: "ansible_num_fabric" # optional, Destination fabric + src_interface: "Ethernet1/1" # optional, Interface on the Source fabric + dst_interface: "Ethernet1/1" # optional, Interface on the Destination fabric + src_device: 193.168.1.1 # optional, Device on the Source fabric + register: result + + - assert: + that: + '(result["response"] | length) >= 1' + + - name: Query Links - with Src & Dst Fabric, Src & Dst Intf, Src & Dst Device + cisco.dcnm.dcnm_links: + state: query # choose from [merged, replaced, deleted, query] + src_fabric: "ansible_num_fabric" + config: + - dst_fabric: "ansible_num_fabric" # optional, Destination fabric + src_interface: "Ethernet1/1" # optional, Interface on the Source fabric + dst_interface: "Ethernet1/1" # optional, Interface on the Destination fabric + src_device: 193.168.1.1 # optional, Device on the Source fabric + dst_device: 193.168.1.2 # optional, Device on the Destination fabric + # + # INTRA-FABRIC + # + - name: Query Links - with Src & Dst Fabric, Src & Dst Intf, Src & Dst Device, Template + cisco.dcnm.dcnm_links: + state: query # choose from [merged, replaced, deleted, query] + src_fabric: "ansible_num_fabric" + config: + - dst_fabric: "ansible_num_fabric" # optional, Destination fabric + src_interface: "Ethernet1/1" # optional, Interface on the Source fabric + dst_interface: "Ethernet1/1" # optional, Interface on the Destination fabric + src_device: 193.168.1.1 # optional, Device on the Source fabric + dst_device: 193.168.1.2 # optional, Device on the Destination fabric + template: int_intra_fabric_num_link # optional, template to be applied, choose from + # [ int_intra_fabric_ipv6_link_local, int_intra_fabric_num_link, + # int_intra_fabric_unnum_link, int_intra_vpc_peer_keep_alive_link, + # int_pre_provision_intra_fabric_link, ios_xe_int_intra_fabric_num_link ] + # + # INTER-FABRIC + # + - name: Query Links - with Src & Dst Fabric, Src & Dst Intf, Src & Dst Device, Template + cisco.dcnm.dcnm_links: + state: query # choose from [merged, replaced, deleted, query] + src_fabric: "{{ ansible_num_fabric }}" + config: + - dst_fabric: "{{ ansible_ipv6_fabric }}" # optional, Destination fabric + src_interface: "{{ intf_1_6 }}" # optional, Interface on the Source fabric + dst_interface: "{{ intf_1_6 }}" # optional, Interface on the Destination fabric + src_device: "{{ ansible_num_switch1 }}" # optional, Device on the Source fabric + dst_device: "{{ ansible_ipv6_switch1 }}" # optional, Device on the Destination fabric + template: ext_fabric_setup # optional, template to be applied, choose from + # [ ext_fabric_setup, ext_multisite_underlay_setup, + # ext_evpn_multisite_overlay_setup ] + + + + +Status +------ + + +Authors +~~~~~~~ + +- Mallik Mudigonda (@mmudigon) diff --git a/plugins/modules/dcnm_links.py b/plugins/modules/dcnm_links.py new file mode 100644 index 000000000..417addcc6 --- /dev/null +++ b/plugins/modules/dcnm_links.py @@ -0,0 +1,3326 @@ +#!/usr/bin/python +# +# Copyright (c) 2022 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__ = "Mallik Mudigonda" + +DOCUMENTATION = """ +--- +module: dcnm_links +short_description: DCNM ansible module for managing Links. +version_added: "2.1.0" +description: + - DCNM ansible module for creating, modifying, deleting and querying Links +author: Mallik Mudigonda (@mmudigon) +options: + src_fabric: + description: + - Name of the source fabric for links operations. + type: str + required: true + state: + description: + - The required state of the configuration after module completion. + type: str + required: false + choices: + - merged + - replaced + - deleted + - query + default: merged + deploy: + description: + - Flag to control deployment of links. If set to 'true' then the links included will be deployed to + specified switches. If set to 'false', the links will be created but not deployed. + - Setting this flag to 'true' will result in all pending configurations on the source and destination + devices to be deployed. + type: bool + required: false + default: true + config: + description: + - A list of dictionaries containing Links information. + type: list + elements: dict + suboptions: + dst_fabric: + description: + - Name of the destination fabric. If this is same as 'src_fabric' then the link is considered + intra-fabric link. If this parameter is different from 'src_fabric', then the link is considered + inter-fabric link. + type: str + required: true + src_device: + description: + - IP address or DNS name of the source switch which is part of the link being configured. + type: list + required: true + dst_device: + description: + - IP address or DNS name of the destination switch which is part of the link being configured. + type: str + required: true + src_interface: + description: + - Interface on the source device which is part of the link being configured. + type: str + required: true + dst_interface: + description: + - Interface on the destination device which is part of the link being configured. + type: str + required: true + template: + description: + - Name of the template that is applied on the link being configured. + - The last 3 template choices are applicable for inter-fabric links and the others + are applicable for intra-fabric links. + - This parameter is required only for 'merged' and 'replaced' states. It is + - optional for other states. + type: str + required: true + choices: + - int_intra_fabric_ipv6_link_local(intra-fabric) + - int_intra_fabric_num_link (intra-fabric) + - int_intra_fabric_unnum_link (intra-fabric) + - int_intra_vpc_peer_keep_alive_link (intra-fabric) + - int_pre_provision_intra_fabric_link (intra-fabric) + - ios_xe_int_intra_fabric_num_link (intra-fabric) + - ext_fabric_setup (inter-fabric) + - ext_multisite_underlay_setup (inter-fabric) + - ext_evpn_multisite_overlay_setup (inter-fabric) + profile: + description: + - Additional link related parameters that must be included while creating links. + suboptions: + peer1_ipv4_address: + description: + - IPV4 address of the source interface. + - This parameter is optional if the underlying fabric is ipv6 enabled. + - This parameter is required only if template is 'int_intra_fabric_num_link' or + 'ios_xe_int_intra_fabric_num_link' or 'int_intra_vpc_peer_keep_alive_link'. + type: str + required: true + peer2_ipv4_address: + description: + - IPV4 address of the destination interface. + - This parameter is optional if the underlying fabric is ipv6 enabled. + - This parameter is required only if template is 'int_intra_fabric_num_link' or + 'ios_xe_int_intra_fabric_num_link' or 'int_intra_vpc_peer_keep_alive_link'. + type: str + required: true + peer1_ipv6_address: + description: + - IPV6 address of the source interface. + - This parameter is required only if the underlying fabric is ipv6 enabled. + - This parameter is required only if template is 'int_intra_fabric_num_link' or + 'ios_xe_int_intra_fabric_num_link' or 'int_intra_vpc_peer_keep_alive_link'. + type: str + required: false + default: "" + peer2_ipv6_address: + description: + - IPV6 address of the destination interface. + - This parameter is required only if the underlying fabric is ipv6 enabled. + - This parameter is required only if template is 'int_intra_fabric_num_link' or + 'ios_xe_int_intra_fabric_num_link' or 'int_intra_vpc_peer_keep_alive_link'. + type: str + required: false + default: "" + ipv4_subnet: + description: + - IPV4 address of the source interface with mask. + - This parameter is required only if template is 'ext_fabric_setup' or 'ext_multisite_underlay_setup'. + type: str + required: true + ipv4_address: + description: + - IPV4 address of the source interface without mask. + - This parameter is required only if template is 'ext_evpn_multisite_overlay_setup'. + type: str + required: true + neighbor_ip: + description: + - IPV4 address of the neighbor switch on the destination fabric. + - This parameter is required only if template is 'ext_fabric_setup' or 'ext_multisite_underlay_setup' + or "ext_evpn_multisite_overlay_setup" + type: str + required: true + src_asn: + description: + - BGP ASN number on the source fabric. + - This parameter is required only if template is 'ext_fabric_setup' or 'ext_multisite_underlay_setup' + or "ext_evpn_multisite_overlay_setup" + type: str + required: true + dst_asn: + description: + - BGP ASN number on the destination fabric. + - This parameter is required only if template is 'ext_fabric_setup' or 'ext_multisite_underlay_setup'. + or "ext_evpn_multisite_overlay_setup" + type: str + required: true + auto_deploy: + description: + - Flag that controls auto generation of neighbor VRF Lite configuration for managed neighbor devices. + - This parameter is required only if template is 'ext_fabric_setup'. + type: str + required: true + max_paths: + description: + - Maximum number of iBGP/eBGP paths. + - This parameter is required only if template is 'ext_multisite_underlay_setup'. + type: int + required: false + default: 1 + ebgp_password_enable: + description: + - Flag to enable eBGP password. + - This parameter is required only if template is 'ext_multisite_underlay_setup' or 'ext_evpn_multisite_overlay_setup'. + type: bool + required: false + default: true + inherit_from_msd: + description: + - Flag indicating whether to inherit BGP password from MSD information. + - Applicable only when source and destination fabric are in the same MSD fabric. + - This parameter is required only if template is 'ext_multisite_underlay_setup' or 'ext_evpn_multisite_overlay_setup' + type: bool + required: false + default: true + ebgp_password: + description: + - Encrypted eBGP Password Hex String. + - This parameter is required only if template is 'ext_multisite_underlay_setup' or 'ext_evpn_multisite_overlay_setup'. + - This parameter is required only if inherit_from_msd is false. + type: str + required: true + ebgp_auth_key_type: + description: + - BGP Key Encryption Type. + - This parameter is required only if template is 'ext_multisite_underlay_setup' or 'ext_evpn_multisite_overlay_setup'. + - This parameter is required only if inherit_from_msd is false. + type: int + required: true + choices: + - 3 (3DES) + - 7 (Cisco) + route_tag: + description: + - Routing tag associated with interface IP. + - This parameter is required only if template is 'ext_multisite_underlay_setup' + type: str + default: '' + deploy_dci_tracking: + description: + - Flag to enable deploy DCI tracking. + - This parameter is required only if template is 'ext_multisite_underlay_setup'. + - This parameter MUST be included only if the fabrics are part of multisite. + type: bool + required: false + default: false + trm_enabled: + description: + - Flag to enable Tenant Routed Multicast. + - This parameter is required only if template is 'ext_evpn_multisite_overlay_setup'. + type: bool + required: false + default: false + bgp_multihop: + description: + - eBGP Time-To-Live Value for Remote Peer. + - This parameter is required only if template is 'ext_evpn_multisite_overlay_setup'. + type: int + required: false + default: 5 + admin_state: + description: + - Admin state of the link. + - This parameter is not required if template is 'ext_evpn_multisite_overlay_setup', 'ext_multisite_underlay_setup', + and 'ext_fabric_setup'. + type: bool + required: true + mtu: + description: + - MTU of the link. + - This parameter is optional if template is 'ios_xe_int_intra_fabric_num_link'. The default value + in this case will be 1500. + - This parameter is not required if template is 'ext_evpn_multisite_overlay_setup'. + type: int + required: true + peer1_description: + description: + - Description of the source interface. + - This parameter is not required if template is 'ext_evpn_multisite_overlay_setup'. + type: str + required: false + default: "" + peer2_description: + description: + - Description of the destination interface. + - This parameter is not required if template is 'ext_evpn_multisite_overlay_setup'. + type: str + required: false + default: "" + peer1_cmds: + description: + - Commands to be included in the configuration under the source interface. + - This parameter is not required if template is 'ext_evpn_multisite_overlay_setup'. + type: list + required: false + default: [] + peer2_cmds: + description: + - Commands to be included in the configuration under the destination interface. + - This parameter is not required if template is 'ext_evpn_multisite_overlay_setup'. + type: list + required: false + default: [] + enable_macsec: + description: + - Enable MACsec on the link. + - This parameter is applicable only if MACsec feature is enabled on the fabric. + - This parameter is applicable only if template is 'int_intra_fabric_ipv6_link_local' or + 'int_intra_fabric_num_link' or 'int_intra_fabric_unnum_link'. + type: bool + required: false + default: false + peer1_bfd_echo_disable: + description: + - Enable BFD echo on the source interface. Only applicable if BFD is enabled on the fabric. + - This parameter is applicable only if template is 'int_intra_fabric_num_link'. + type: bool + required: false + default: false + peer2_bfd_echo_disable: + description: + - Enable BFD echo on the destination interface. Only applicable if BFD is enabled on the fabric. + - This parameter is applicable only if template is 'int_intra_fabric_num_link'. + type: bool + required: false + default: false + intf_vrf: + description: + - Name of the non-default VRF for the link. + - Make sure to configure the VRF before using it here. + - This parameter is applicable only if template is 'int_intra_vpc_peer_keep_alive_link'. + type: str + required: false + default: "" +""" + +EXAMPLES = """ + +# States: +# This module supports the following states: +# +# Merged: +# Links defined in the playbook will be merged into the target fabric. +# +# The links listed in the playbook will be created if not already present on the DCNM +# server. If the link is already present and the configuration information included +# in the playbook is either different or not present in DCNM, then the corresponding +# information is added to the link on DCNM. If a link mentioned in playbook +# is already present on DCNM and there is no difference in configuration, no operation +# will be performed for such link. +# +# Replaced: +# Links defined in the playbook will be replaced in the target fabric. +# +# The state of the links listed in the playbook will serve as source of truth for the +# same links present on the DCNM under the fabric mentioned. Additions and updations +# will be done to bring the DCNM links to the state listed in the playbook. +# Note: Replace will only work on the links mentioned in the playbook. +# +# Deleted: +# Links defined in the playbook will be deleted in the target fabric. +# +# WARNING: Deleting a Link will deploy all pending configurations on the impacted switches +# +# Query: +# Returns the current DCNM state for the links listed in the playbook. Information included +# in the playbook will be used as filters to get the desired output. +# +# CREATE LINKS +# +# NUMBERED FABRIC +# +# INTRA-FABRIC + + - name: Create Links + cisco.dcnm.dcnm_links: + state: merged # choose from [merged, replaced, deleted, query] + src_fabric: "ansible_num_fabric" + config: + - dst_fabric: "ansible_num_fabric" # Destination fabric + src_interface: "Ethernet1/1" # Interface on the Source fabric + dst_interface: "Ethernet1/1" # Interface on the Destination fabric + src_device: 193.168.1.1 # Device on the Source fabric + dst_device: 193.168.1.2 # Device on the Destination fabric + template: int_intra_fabric_num_link # template to be applied, choose from + # [ int_intra_fabric_ipv6_link_local, int_intra_fabric_num_link, + # int_intra_fabric_unnum_link, int_intra_vpc_peer_keep_alive_link, + # int_pre_provision_intra_fabric_link, ios_xe_int_intra_fabric_num_link ] + + profile: + peer1_ipv4_addr: 192.168.1.1 # IP address of the Source interface + peer2_ipv4_addr: 192.168.1.2 # IP address of the Destination interface + admin_state: true # choose from [true, false] + mtu: 9216 # + peer1_description: "Description of source" # optional, default is "" + peer2_description: "Description of dest" # optional, default is "" + peer1_bfd_echo_disable: false # optional, choose from [true, false] + peer2_bfd_echo_disable: false # optional, choose from [true, false] + enable_macsec: false # optional, choose from [true, false] + peer1_cmds: # Freeform config for source device + - no shutdown # optional, default is "" + peer2_cmds: # Freeform config for destination device + - no shutdown # optional, default is "" + + - dst_fabric: "ansible_num_fabric" # Destination fabric + src_interface: "Ethernet1/2" # Interface on the Source fabric + dst_interface: "Ethernet1/2" # Interface on the Destination fabric + src_device: 193.168.1.1 # Device on the Source fabric + dst_device: 193.168.1.2 # Device on the Destination fabric + template: int_pre_provision_intra_fabric_link # template to be applied, choose from + # [ int_intra_fabric_ipv6_link_local, int_intra_fabric_num_link, + # int_intra_fabric_unnum_link, int_intra_vpc_peer_keep_alive_link, + # int_pre_provision_intra_fabric_link, ios_xe_int_intra_fabric_num_link ] + - dst_fabric: "ansible_num_fabric" # Destination fabric + src_interface: "Ethernet1/3" # Interface on the Source fabric + dst_interface: "Ethernet1/3" # Interface on the Destination fabric + src_device: 193.168.1.1 # Device on the Source fabric + dst_device: 193.168.1.2 # Device on the Destination fabric + template: ios_xe_int_intra_fabric_num_link # template to be applied, choose from + # [ int_intra_fabric_ipv6_link_local, int_intra_fabric_num_link, + # int_intra_fabric_unnum_link, int_intra_vpc_peer_keep_alive_link, + # int_pre_provision_intra_fabric_link, ios_xe_int_intra_fabric_num_link ] + + profile: + peer1_ipv4_addr: 192.169.2.1 # IPV4 address of the Source interface + peer2_ipv4_addr: 192.169.2.2 # IPV4 address of the Destination interface + peer1_ipv6_addr: fe80:01::01 # optional, default is "" + peer2_ipv6_addr: fe80:01::02 # optional, default is "" + admin_state: true # choose from [true, false] + mtu: 1500 # optional, default is 1500 + peer1_description: "Description of source" # optional, default is "" + peer2_description: "Description of dest" # optional, default is "" + peer1_bfd_echo_disable: false # optional, choose from [true, false] + peer2_bfd_echo_disable: false # optional, choose from [true, false] + enable_macsec: false # optional, choose from [true, false] + peer1_cmds: # Freeform config for source device + - no shutdown # optional, default is "" + peer2_cmds: # Freeform config for destination device + - no shutdown # optional, default is "" +# +# INTER-FABRIC + + - name: Create Links including optional parameters + cisco.dcnm.dcnm_links: &links_merge_with_opt + state: merged # choose from [merged, replaced, deleted, query] + src_fabric: "{{ ansible_num_fabric }}" + config: + - dst_fabric: "{{ ansible_unnum_fabric }}" # Destination fabric + src_interface: "{{ intf_1_3 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_3 }}" # Interface on the Destination fabric + src_device: "{{ ansible_num_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_unnum_switch1 }}" # Device on the Destination fabric + template: ext_fabric_setup # template to be applied, choose from + # [ ext_fabric_setup, ext_multisite_underlay_setup, + # ext_evpn_multisite_overlay_setup ] + profile: + ipv4_subnet: 193.168.1.1/24 # IP address of interface in src fabric with mask + neighbor_ip: 193.168.1.2 # IP address of the interface in dst fabric + src_asn: 1000 # BGP ASN in source fabric + dst_asn: 1001 # BGP ASN in destination fabric + mtu: 9216 # + auto_deploy: false # optional, default is false + # Flag that controls auto generation of neighbor VRF Lite configuration + peer1_description: "Description of source" # optional, default is "" + peer2_description: "Description of dest" # optional, default is "" + peer1_cmds: # Freeform config for source interface + - no shutdown # optional, default is "" + peer2_cmds: # Freeform config for destination interface + - no shutdown # optional, default is "" + + - dst_fabric: "{{ ansible_unnum_fabric }}" # Destination fabric + src_interface: "{{ intf_1_4 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_4 }}" # Interface on the Destination fabric + src_device: "{{ ansible_num_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_unnum_switch1 }}" # Device on the Destination fabric + template: ext_multisite_underlay_setup # template to be applied, choose from + # [ ext_fabric_setup, ext_multisite_underlay_setup, + # ext_evpn_multisite_overlay_setup ] + profile: + ipv4_subnet: 193.168.2.1/24 # IP address of interface in src fabric with mask + neighbor_ip: 193.168.2.2 # IP address of the interface in dst fabric + src_asn: 1200 # BGP ASN in source fabric + dst_asn: 1201 # BGP ASN in destination fabric + mtu: 9216 # + deploy_dci_tracking: false # optional, default is false + max_paths: 1 # optional, default is 1 + route_tag: 12345 # optional, optional is "" + ebgp_password_enable: true # optional, default is true + ebgp_password: 0102030405 # optional, required only if ebgp_password_enable flag is true, and inherit_from_msd + # is false. + inherit_from_msd: True # optional, required only if ebgp_password_enable flag is true, default is false + ebgp_auth_key_type: 3 # optional, required only if ebpg_password_enable is true, and inherit_from_msd + # is false. Default is 3 + # choose from [3 - 3DES, 7 - Cisco ] + peer1_description: "Description of source" # optional, default is "" + peer2_description: "Description of dest" # optional, default is "" + peer1_cmds: # Freeform config for source interface + - no shutdown # optional, default is "" + peer2_cmds: # Freeform config for destination interface + - no shutdown # optional, default is "" + + - dst_fabric: "{{ ansible_unnum_fabric }}" # Destination fabric + src_interface: "{{ intf_1_5 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_5 }}" # Interface on the Destination fabric + src_device: "{{ ansible_num_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_unnum_switch1 }}" # Device on the Destination fabric + template: ext_evpn_multisite_overlay_setup # template to be applied, choose from + # [ ext_fabric_setup, ext_multisite_underlay_setup, + # ext_evpn_multisite_overlay_setup ] + profile: + ipv4_addr: 193.168.3.1 # IP address of interface in src fabric + neighbor_ip: 193.168.3.2 # IP address of the interface in dst fabric + src_asn: 1300 # BGP ASN in source fabric + dst_asn: 1301 # BGP ASN in destination fabric + trm_enabled: false # optional, default is false + bgp_multihop: 5 # optional, default is 5 + ebgp_password_enable: true # optional, default is true + ebgp_password: 0102030405 # optional, required only if ebgp_password_enable flag is true, and inherit_from_msd + # is false. Default is 3 + inherit_from_msd: false # optional, required only if ebgp_password_enable flag is true, default is false + ebpg_auth_key_type: 3 # optional, required only if ebpg_password_enable is true, and inherit_from_msd + # is false. Default is 3 + # choose from [3 - 3DES, 7 - Cisco ] + +# FABRIC WITH VPC PAIRED SWITCHES + + - name: Create Links + cisco.dcnm.dcnm_links: + state: merged # choose from [merged, replaced, deleted, query] + src_fabric: "ansible_vpc_fabric" + config: + - dst_fabric: "ansible_vpc_fabric" # Destination fabric + src_interface: "Ethernet1/4" # Interface on the Source fabric + dst_interface: "Ethernet1/4" # Interface on the Destination fabric + src_device: "ansible_vpc_switch1" # Device on the Source fabric + dst_device: "ansible_vpc_switch2" # Device on the Destination fabric + template: int_intra_vpc_peer_keep_alive_link # template to be applied, choose from + # [ int_intra_fabric_ipv6_link_local, int_intra_fabric_num_link, + # int_intra_fabric_unnum_link, int_intra_vpc_peer_keep_alive_link, + # int_pre_provision_intra_fabric_link, ios_xe_int_intra_fabric_num_link ] + + profile: + peer1_ipv4_addr: 192.170.1.1 # IPV4 address of the Source interface + peer2_ipv4_addr: 192.170.1.2 # IPV4 address of the Destination interface + peer1_ipv6_addr: fe80:2a::01 # optional, default is "" + peer2_ipv6_addr: fe80:2a::02 # optional, default is "" + admin_state: true # choose from [true, false] + mtu: 9216 # + peer1_description: "Description of source" # optional, default is "" + peer2_description: "Description of dest" # optional, default is "" + enable_macsec: false # optional, choose from [true, false] + peer1_cmds: # Freeform config for source device + - no shutdown # optional, default is "" + peer2_cmds: # Freeform config for destination device + - no shutdown # optional, default is "" + intf_vrf: "test_vrf" # optional, default is "" + +# UNNUMBERED FABRIC + + - name: Create Links + cisco.dcnm.dcnm_links: + state: merged # choose from [merged, replaced, deleted, query] + src_fabric: "ansible_unnum_fabric" + config: + - dst_fabric: "ansible_unnum_fabric" # Destination fabric + src_interface: "Ethernet1/1" # Interface on the Source fabric + dst_interface: "Ethernet1/1" # Interface on the Destination fabric + src_device: "ansible_unnum_switch1" # Device on the Source fabric + dst_device: "ansible_unnum_switch2" # Device on the Destination fabric + template: int_intra_fabric_unnum_link # template to be applied, choose from + # [ int_intra_fabric_ipv6_link_local, int_intra_fabric_num_link, + # int_intra_fabric_unnum_link, int_intra_vpc_peer_keep_alive_link, + # int_pre_provision_intra_fabric_link, ios_xe_int_intra_fabric_num_link ] + + profile: + admin_state: true # choose from [true, false] + mtu: 9216 # + peer1_description: "Description of source" # optional, default is "" + peer2_description: "Description of dest" # optional, default is "" + enable_macsec: false # optional, choose from [true, false] + peer1_cmds: # Freeform config for source device + - no shutdown # optional, default is "" + peer2_cmds: # Freeform config for destination device + - no shutdown # optional, default is "" + + - dst_fabric: "ansible_unnum_fabric" # Destination fabric + src_interface: "Ethernet1/2" # Interface on the Source fabric + dst_interface: "Ethernet1/2" # Interface on the Destination fabric + src_device: "ansible_unnum_switch1" # Device on the Source fabric + dst_device: "ansible_unnum_switch2" # Device on the Destination fabric + template: int_pre_provision_intra_fabric_link # template to be applied, choose from + # [ int_intra_fabric_ipv6_link_local, int_intra_fabric_num_link, + # int_intra_fabric_unnum_link, int_intra_vpc_peer_keep_alive_link, + # int_pre_provision_intra_fabric_link, ios_xe_int_intra_fabric_num_link ] + +# IPV6 UNDERLAY FABRIC + + - name: Create Links + cisco.dcnm.dcnm_links: + state: merged # choose from [merged, replaced, deleted, query] + src_fabric: "ansible_ipv6_fabric" + config: + - dst_fabric: "ansible_ipv6_fabric" # Destination fabric + src_interface: "Ethernet1/1" # Interface on the Source fabric + dst_interface: "Ethernet1/1" # Interface on the Destination fabric + src_device: "ansible_ipv6_switch1" # Device on the Source fabric + dst_device: "ansible_ipv6_switch2" # Device on the Destination fabric + template: int_intra_fabric_ipv6_link_local # template to be applied, choose from + # [ int_intra_fabric_ipv6_link_local, int_intra_fabric_num_link, + # int_intra_fabric_unnum_link, int_intra_vpc_peer_keep_alive_link, + # int_pre_provision_intra_fabric_link, ios_xe_int_intra_fabric_num_link ] + + profile: + peer1_ipv4_addr: 192.169.1.1 # optional, default is "" + peer2_ipv4_addr: 192.169.1.2 # optional, default is "" + peer1_ipv6_addr: fe80:0201::01 # IP address of the Source interface + peer2_ipv6_addr: fe80:0201::02 # IP address of the Source interface + admin_state: true # choose from [true, false] + mtu: 9216 # + peer1_description: "Description of source" # optional, default is "" + peer2_description: "Description of dest" # optional, default is "" + peer1_bfd_echo_disable: false # optional, choose from [true, false] + peer2_bfd_echo_disable: false # optional, choose from [true, false] + enable_macsec: false # optional, choose from [true, false] + peer1_cmds: # Freeform config for source device + - no shutdown # optional, default is "" + peer2_cmds: # Freeform config for destination device + - no shutdown # optional, default is "" + + - dst_fabric: "ansible_ipv6_fabric" # Destination fabric + src_interface: "Ethernet1/2" # Interface on the Source fabric + dst_interface: "Ethernet1/2" # Interface on the Destination fabric + src_device: "ansible_ipv6_switch1" # Device on the Source fabric + dst_device: "ansible_ipv6_switch2" # Device on the Destination fabric + template: int_pre_provision_intra_fabric_link # template to be applied, choose from + # [ int_intra_fabric_ipv6_link_local, int_intra_fabric_num_link, + # int_intra_fabric_unnum_link, int_intra_vpc_peer_keep_alive_link, + # int_pre_provision_intra_fabric_link, ios_xe_int_intra_fabric_num_link ] + - dst_fabric: "ansible_ipv6_fabric" # Destination fabric + src_interface: "Ethernet1/3" # Interface on the Source fabric + dst_interface: "Ethernet1/3" # Interface on the Destination fabric + src_device: "ansible_ipv6_switch1" # Device on the Source fabric + dst_device: "ansible_ipv6_switch2" # Device on the Destination fabric + template: int_intra_fabric_num_link # template to be applied, choose from + # [ int_intra_fabric_ipv6_link_local, int_intra_fabric_num_link, + # int_intra_fabric_unnum_link, int_intra_vpc_peer_keep_alive_link, + # int_pre_provision_intra_fabric_link, ios_xe_int_intra_fabric_num_link ] + + profile: + peer1_ipv4_addr: 192.169.2.1 # IPV4 address of the Source interface + peer2_ipv4_addr: 192.169.2.2 # IPV4 address of the Destination interface + peer1_ipv6_addr: fe80:0202::01 # IP address of the Source interface + peer2_ipv6_addr: fe80:0202::02 # IP address of the Source interface + admin_state: true # choose from [true, false] + mtu: 1500 # optional, default is 1500 + peer1_description: "Description of source" # optional, default is "" + peer2_description: "Description of dest" # optional, default is "" + peer1_bfd_echo_disable: false # optional, choose from [true, false] + peer2_bfd_echo_disable: false # optional, choose from [true, false] + enable_macsec: false # optional, choose from [true, false] + peer1_cmds: # Freeform config for source device + - no shutdown # optional, default is "" + peer2_cmds: # Freeform config for destination device + - no shutdown # optional, default is "" +# DELETE LINKS + + - name: Delete Links + cisco.dcnm.dcnm_links: + state: deleted # choose from [merged, replaced, deleted, query] + src_fabric: "ansible_num_fabric" + config: + - dst_fabric: "ansible_num_fabric" # Destination fabric + src_interface: "Ethernet1/1" # Interface on the Source fabric + dst_interface: "Ethernet1/1" # Interface on the Destination fabric + src_device: 193.168.1.1 # Device on the Source fabric + dst_device: 193.168.1.2 # Device on the Destination fabric + +# QUERY LINKS + + - name: Query Links - with Src Fabric + cisco.dcnm.dcnm_links: + state: query # choose from [merged, replaced, deleted, query] + src_fabric: "ansible_num_fabric" + + - name: Query Links - with Src & Dst Fabric + cisco.dcnm.dcnm_links: + state: query # choose from [merged, replaced, deleted, query] + src_fabric: "ansible_num_fabric" + config: + - dst_fabric: "ansible_num_fabric" # optional, Destination fabric + + - name: Query Links - with Src & Dst Fabric, Src Intf + cisco.dcnm.dcnm_links: + state: query # choose from [merged, replaced, deleted, query] + src_fabric: "ansible_num_fabric" + config: + - dst_fabric: "ansible_num_fabric" # optional, Destination fabric + src_interface: "Ethernet1/1" # optional, Interface on the Source fabric + + - name: Query Links - with Src & Dst Fabric, Src & Dst Intf + cisco.dcnm.dcnm_links: + state: query # choose from [merged, replaced, deleted, query] + src_fabric: "ansible_num_fabric" + config: + - dst_fabric: "ansible_num_fabric" # optional, Destination fabric + src_interface: "Ethernet1/1" # optional, Interface on the Source fabric + dst_interface: "Ethernet1/1" # optional, Interface on the Destination fabric + + - name: Query Links - with Src & Dst Fabric, Src & Dst Intf, Src Device + cisco.dcnm.dcnm_links: + state: query # choose from [merged, replaced, deleted, query] + src_fabric: "ansible_num_fabric" + config: + - dst_fabric: "ansible_num_fabric" # optional, Destination fabric + src_interface: "Ethernet1/1" # optional, Interface on the Source fabric + dst_interface: "Ethernet1/1" # optional, Interface on the Destination fabric + src_device: 193.168.1.1 # optional, Device on the Source fabric + register: result + + - assert: + that: + '(result["response"] | length) >= 1' + + - name: Query Links - with Src & Dst Fabric, Src & Dst Intf, Src & Dst Device + cisco.dcnm.dcnm_links: + state: query # choose from [merged, replaced, deleted, query] + src_fabric: "ansible_num_fabric" + config: + - dst_fabric: "ansible_num_fabric" # optional, Destination fabric + src_interface: "Ethernet1/1" # optional, Interface on the Source fabric + dst_interface: "Ethernet1/1" # optional, Interface on the Destination fabric + src_device: 193.168.1.1 # optional, Device on the Source fabric + dst_device: 193.168.1.2 # optional, Device on the Destination fabric + # + # INTRA-FABRIC + # + - name: Query Links - with Src & Dst Fabric, Src & Dst Intf, Src & Dst Device, Template + cisco.dcnm.dcnm_links: + state: query # choose from [merged, replaced, deleted, query] + src_fabric: "ansible_num_fabric" + config: + - dst_fabric: "ansible_num_fabric" # optional, Destination fabric + src_interface: "Ethernet1/1" # optional, Interface on the Source fabric + dst_interface: "Ethernet1/1" # optional, Interface on the Destination fabric + src_device: 193.168.1.1 # optional, Device on the Source fabric + dst_device: 193.168.1.2 # optional, Device on the Destination fabric + template: int_intra_fabric_num_link # optional, template to be applied, choose from + # [ int_intra_fabric_ipv6_link_local, int_intra_fabric_num_link, + # int_intra_fabric_unnum_link, int_intra_vpc_peer_keep_alive_link, + # int_pre_provision_intra_fabric_link, ios_xe_int_intra_fabric_num_link ] +# +# INTER-FABRIC +# + - name: Query Links - with Src & Dst Fabric, Src & Dst Intf, Src & Dst Device, Template + cisco.dcnm.dcnm_links: + state: query # choose from [merged, replaced, deleted, query] + src_fabric: "{{ ansible_num_fabric }}" + config: + - dst_fabric: "{{ ansible_ipv6_fabric }}" # optional, Destination fabric + src_interface: "{{ intf_1_6 }}" # optional, Interface on the Source fabric + dst_interface: "{{ intf_1_6 }}" # optional, Interface on the Destination fabric + src_device: "{{ ansible_num_switch1 }}" # optional, Device on the Source fabric + dst_device: "{{ ansible_ipv6_switch1 }}" # optional, Device on the Destination fabric + template: ext_fabric_setup # optional, template to be applied, choose from + # [ ext_fabric_setup, ext_multisite_underlay_setup, + # ext_evpn_multisite_overlay_setup ] +""" + +import time +import json +import copy +import ipaddress + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.cisco.dcnm.plugins.module_utils.network.dcnm.dcnm import ( + dcnm_send, + validate_list_of_dicts, + dcnm_version_supported, + get_ip_sn_dict, + get_fabric_inventory_details, + get_fabric_details, + dcnm_get_ip_addr_info, +) + +from datetime import datetime + + +# Resource Class object which includes all the required methods and data to configure and maintain Links +class DcnmLinks: + dcnm_links_paths = { + 11: { + "LINKS_GET_BY_SWITCH_PAIR": "/rest/control/links", + "LINKS_CREATE": "/rest/control/links", + "LINKS_DELETE": "/rest/control/links/", + "LINKS_UPDATE": "/rest/control/links/", + "LINKS_GET_BY_FABRIC": "/rest/control/links/fabrics/{}", + "LINKS_CFG_DEPLOY": "/rest/control/fabrics/{}/config-deploy/", + "CONFIG_PREVIEW": "/rest/control/fabrics/{}/config-preview/", + }, + 12: { + "LINKS_GET_BY_SWITCH_PAIR": "/appcenter/cisco/ndfc/api/v1/lan-fabric/rest/control/links", + "LINKS_CREATE": "/appcenter/cisco/ndfc/api/v1/lan-fabric/rest/control/links", + "LINKS_DELETE": "/appcenter/cisco/ndfc/api/v1/lan-fabric/rest/control/links/", + "LINKS_UPDATE": "/appcenter/cisco/ndfc/api/v1/lan-fabric/rest/control/links/", + "LINKS_GET_BY_FABRIC": "/appcenter/cisco/ndfc/api/v1/lan-fabric/rest/control/links/fabrics/{}", + "LINKS_CFG_DEPLOY": "/appcenter/cisco/ndfc/api/v1/lan-fabric/rest/control/fabrics/{}/config-deploy/", + "CONFIG_PREVIEW": "/appcenter/cisco/ndfc/api/v1/lan-fabric/rest/control/fabrics/{}/config-preview/", + }, + } + + dcnm_links_xlate_template = { + 11: { + "int_intra_fabric_ipv6_link_local": "int_intra_fabric_ipv6_link_local", + "int_intra_fabric_num_link": "int_intra_fabric_num_link_11_1", + "int_intra_fabric_unnum_link": "int_intra_fabric_unnum_link_11_1", + "int_intra_vpc_peer_keep_alive_link": "int_intra_vpc_peer_keep_alive_link_11_1", + "int_pre_provision_intra_fabric_link": "int_pre_provision_intra_fabric_link", + "ios_xe_int_intra_fabric_num_link": "ios_xe_int_intra_fabric_num_link", + "ext_fabric_setup": "ext_fabric_setup_11_1", + "ext_multisite_underlay_setup": "ext_multisite_underlay_setup_11_1", + "ext_evpn_multisite_overlay_setup": "ext_evpn_multisite_overlay_setup", + }, + 12: { + "int_intra_fabric_ipv6_link_local": "int_intra_fabric_ipv6_link_local", + "int_intra_fabric_num_link": "int_intra_fabric_num_link", + "int_intra_fabric_unnum_link": "int_intra_fabric_unnum_link", + "int_intra_vpc_peer_keep_alive_link": "int_intra_vpc_peer_keep_alive_link", + "int_pre_provision_intra_fabric_link": "int_pre_provision_intra_fabric_link", + "ios_xe_int_intra_fabric_num_link": "ios_xe_int_intra_fabric_num_link", + "ext_fabric_setup": "ext_fabric_setup", + "ext_multisite_underlay_setup": "ext_multisite_underlay_setup", + "ext_evpn_multisite_overlay_setup": "ext_evpn_multisite_overlay_setup", + }, + } + + dcnm_links_xlated_template_names = { + 11: [ + "int_intra_fabric_ipv6_link_local", + "int_intra_fabric_num_link_11_1", + "int_intra_fabric_unnum_link_11_1", + "int_intra_vpc_peer_keep_alive_link_11_1", + "int_pre_provision_intra_fabric_link", + "ios_xe_int_intra_fabric_num_link", + "ext_fabric_setup_11_1", + "ext_multisite_underlay_setup_11_1", + "ext_evpn_multisite_overlay_setup", + ], + 12: [ + "int_intra_fabric_ipv6_link_local", + "int_intra_fabric_num_link", + "int_intra_fabric_unnum_link", + "int_intra_vpc_peer_keep_alive_link", + "int_pre_provision_intra_fabric_link", + "ios_xe_int_intra_fabric_num_link", + "ext_fabric_setup", + "ext_multisite_underlay_setup", + "ext_evpn_multisite_overlay_setup", + ], + } + + def __init__(self, module): + self.module = module + self.params = module.params + self.fabric = module.params["src_fabric"] + self.deploy = module.params["deploy"] + self.config = copy.deepcopy(module.params.get("config", [])) + self.links_info = [] + self.want = [] + self.have = [] + self.diff_create = [] + self.diff_modify = [] + self.diff_delete = [] + self.diff_deploy = {} + self.fd = None + self.changed_dict = [ + { + "merged": [], + "deleted": [], + "modified": [], + "query": [], + "deploy": [], + "debugs": [], + } + ] + + self.dcnm_version = dcnm_version_supported(self.module) + self.inventory_data = get_fabric_inventory_details( + self.module, self.fabric + ) + + self.src_fabric_info = get_fabric_details(self.module, self.fabric) + + self.paths = self.dcnm_links_paths[self.dcnm_version] + self.templates = self.dcnm_links_xlate_template[self.dcnm_version] + self.template_choices = self.dcnm_links_xlated_template_names[ + self.dcnm_version + ] + + self.result = dict(changed=False, diff=[], response=[]) + + def log_msg(self, msg): + + if self.fd is None: + self.fd = open("dcnm_links.log", "a+") + if self.fd is not None: + self.fd.write(msg) + self.fd.write("\n") + self.fd.flush() + + def dcnm_links_compare_ip_addresses(self, addr1, addr2): + + """ + Routine to compare the IP address values after converting to IP address objects. + + Parameters: + addrr1 : First IP address value + addrr2 : Second IP address value + + Returns: + True - if both addresses are same + False - otherwise + """ + + rv1 = "" + rv2 = "" + + if "/" in addr1: + rv1 = addr1.split("/") + if "/" in addr2: + rv2 = addr2.split("/") + + if rv1 and not rv2: + return False + if rv2 and not rv1: + return False + if rv1 and rv2: + return ( + ipaddress.ip_address(rv1[0]) == ipaddress.ip_address(rv2[0]) + ) and (rv1[1] == rv2[1]) + else: + return ipaddress.ip_address(addr1) == ipaddress.ip_address(addr2) + + def dcnm_links_validate_and_build_links_info(self, cfg, link_spec): + + """ + Routine to validate the playbook input and fill up default values for objects not included. + In this case we validate the playbook against link_spec which includes required information + This routine updates self.links_info with validated playbook information by defaulting values + not included + + Parameters: + cfg (dict): The config from playbook + link_spec (dict): Links spec + + Returns: + None + """ + + links_info, invalid_params = validate_list_of_dicts(cfg, link_spec) + if invalid_params: + mesg = "Invalid parameters in playbook: {0}".format( + "while processing Link Info related to - [ " + + "src_fabric: " + + self.fabric + + ", " + + "dst_fabric: " + + cfg[0].get("dst_fabric", "NA") + + ", " + + "src_device: " + + cfg[0].get("src_device", "NA") + + ", " + + "dst_device: " + + cfg[0].get("dst_device", "NA") + + ", " + + "src_interface: " + + cfg[0].get("src_interface", "NA") + + ", " + + "dst_interface: " + + cfg[0].get("dst_interface", "NA") + + ", " + + "template: " + + cfg[0].get("template", "NA") + + " ], " + + "\n".join(invalid_params) + ) + self.module.fail_json(msg=mesg) + + if cfg[0].get("profile", "") == "": + self.links_info.extend(links_info) + return + + cfg_profile = [] + cfg_profile.append(cfg[0]["profile"]) + + profile_info, invalid_params = validate_list_of_dicts( + cfg_profile, link_spec["profile"] + ) + if invalid_params: + mesg = "Invalid parameters in playbook: {0}".format( + "while processing Link Info related to - [ " + + "src_fabric: " + + self.fabric + + ", " + + "dst_fabric: " + + cfg[0].get("dst_fabric", "NA") + + ", " + + "src_device: " + + cfg[0].get("src_device", "NA") + + ", " + + "dst_device: " + + cfg[0].get("dst_device", "NA") + + ", " + + "src_interface: " + + cfg[0].get("src_interface", "NA") + + ", " + + "dst_interface: " + + cfg[0].get("dst_interface", "NA") + + ", " + + "template: " + + cfg[0].get("template", "NA") + + " ], " + + "\n".join(invalid_params) + ) + self.module.fail_json(msg=mesg) + + links_info[0]["profile"] = profile_info[0] + self.links_info.extend(links_info) + + def dcnm_links_validate_input(self): + + """ + Routine to validate playbook input based on the state. Since each state has a different + config structure, this routine handles the validation based on the given state + + Parameters: + None + + Returns: + None + """ + + if [] is self.config: + return + + cfg = [] + for item in self.config: + + citem = copy.deepcopy(item) + + cfg.append(citem) + + if self.module.params["state"] == "query": + # config for query state is different. So validate query state differently + self.dcnm_links_validate_query_state_input(cfg) + elif self.module.params["state"] == "deleted": + # config for deleted state is different. So validate deleted state differently + self.dcnm_links_validate_deleted_state_input(cfg) + else: + self.dcnm_links_validate_links_input(cfg) + cfg.remove(citem) + + def dcnm_links_get_intra_fabric_link_spec(self, cfg): + + intra_fabric_choices = self.template_choices[0:6] + + link_spec = dict( + dst_fabric=dict(required=True, type="str"), + src_device=dict(required=True, type="str"), + dst_device=dict(required=True, type="str"), + src_interface=dict(required=True, type="str"), + dst_interface=dict(required=True, type="str"), + template=dict( + required=True, type="str", choices=intra_fabric_choices + ), + profile=dict(), + ) + + if cfg[0].get("template", "") == "": + self.module.fail_json(msg="Required parameter not found: template") + + if ( + cfg[0]["template"] + != self.templates["int_pre_provision_intra_fabric_link"] + ): + if cfg[0].get("profile", None) is None: + self.module.fail_json( + msg="Required information not found: profile" + ) + + if ( + (cfg[0]["template"] == self.templates["int_intra_fabric_num_link"]) + or ( + cfg[0]["template"] + == self.templates["ios_xe_int_intra_fabric_num_link"] + ) + or ( + cfg[0]["template"] + == self.templates["int_intra_vpc_peer_keep_alive_link"] + ) + ): + if ( + self.src_fabric_info["nvPairs"]["UNDERLAY_IS_V6"].lower() + == "false" + ): + link_spec["profile"]["peer1_ipv4_addr"] = dict( + required=True, type="ipv4" + ) + link_spec["profile"]["peer2_ipv4_addr"] = dict( + required=True, type="ipv4" + ) + else: + link_spec["profile"]["peer1_ipv6_addr"] = dict( + required=True, type="ipv6" + ) + link_spec["profile"]["peer2_ipv6_addr"] = dict( + required=True, type="ipv6" + ) + link_spec["profile"]["peer1_ipv4_addr"] = dict( + type="ipv4", default="" + ) + link_spec["profile"]["peer2_ipv4_addr"] = dict( + type="ipv4", default="" + ) + if ( + cfg[0]["template"] + != self.templates["int_pre_provision_intra_fabric_link"] + ): + link_spec["profile"]["admin_state"] = dict( + required=True, type="bool", choices=[True, False] + ) + if ( + cfg[0]["template"] + != self.templates["ios_xe_int_intra_fabric_num_link"] + ): + link_spec["profile"]["mtu"] = dict(required=True, type="int") + else: + link_spec["profile"]["mtu"] = dict(type="int", default=1500) + link_spec["profile"]["peer1_description"] = dict( + type="str", default="" + ) + link_spec["profile"]["peer2_description"] = dict( + type="str", default="" + ) + link_spec["profile"]["peer1_cmds"] = dict(type="list", default=[]) + link_spec["profile"]["peer2_cmds"] = dict(type="list", default=[]) + + if ( + (cfg[0]["template"] == self.templates["int_intra_fabric_num_link"]) + or ( + cfg[0]["template"] + == self.templates["int_intra_fabric_ipv6_link_local"] + ) + or ( + cfg[0]["template"] + == self.templates["int_intra_fabric_unnum_link"] + ) + ): + link_spec["profile"]["enable_macsec"] = dict( + type="bool", default=False + ) + + if ( + cfg[0]["template"] + == self.templates["int_intra_vpc_peer_keep_alive_link"] + ): + link_spec["profile"]["intf_vrf"] = dict(type="str", default="") + + if cfg[0]["template"] == self.templates["int_intra_fabric_num_link"]: + link_spec["profile"]["peer1_bfd_echo_disable"] = dict( + type="bool", default=False + ) + link_spec["profile"]["peer2_bfd_echo_disable"] = dict( + type="bool", default=False + ) + + return link_spec + + def dcnm_links_get_inter_fabric_link_spec(self, cfg): + + inter_fabric_choices = self.template_choices[6:9] + + link_spec = dict( + dst_fabric=dict(required=True, type="str"), + src_device=dict(required=True, type="str"), + dst_device=dict(required=True, type="str"), + src_interface=dict(required=True, type="str"), + dst_interface=dict(required=True, type="str"), + template=dict( + required=True, type="str", choices=inter_fabric_choices + ), + profile=dict(), + ) + + if cfg[0].get("template", "") == "": + self.module.fail_json(msg="Required parameter not found: template") + + if ( + cfg[0].get("template", "") + == self.templates["ext_multisite_underlay_setup"] + ) or ( + cfg[0].get("template", "") == self.templates["ext_fabric_setup"] + ): + link_spec["profile"]["ipv4_subnet"] = dict( + required=True, type="ipv4_subnet" + ) + link_spec["profile"]["mtu"] = dict(type="int", default=9216) + link_spec["profile"]["peer1_description"] = dict( + type="str", default="" + ) + link_spec["profile"]["peer2_description"] = dict( + type="str", default="" + ) + link_spec["profile"]["peer1_cmds"] = dict(type="list", default=[]) + link_spec["profile"]["peer2_cmds"] = dict(type="list", default=[]) + else: + link_spec["profile"]["ipv4_addr"] = dict( + required=True, type="ipv4" + ) + + link_spec["profile"]["neighbor_ip"] = dict(required=True, type="ipv4") + link_spec["profile"]["src_asn"] = dict(required=True, type="int") + link_spec["profile"]["dst_asn"] = dict(required=True, type="int") + + if cfg[0].get("template", "") == self.templates["ext_fabric_setup"]: + link_spec["profile"]["auto_deploy"] = dict( + type="bool", default=False + ) + + if ( + cfg[0].get("template", "") + == self.templates["ext_multisite_underlay_setup"] + ): + link_spec["profile"]["max_paths"] = dict(type="int", default=1) + link_spec["profile"]["route_tag"] = dict(type="str", default="") + link_spec["profile"]["deploy_dci_tracking"] = dict( + type="bool", default=False + ) + + if ( + cfg[0].get("template", "") + == self.templates["ext_evpn_multisite_overlay_setup"] + ): + link_spec["profile"]["trm_enabled"] = dict( + type="bool", default=False + ) + link_spec["profile"]["bgp_multihop"] = dict(type="int", default=5) + + if ( + cfg[0].get("template", "") + == self.templates["ext_multisite_underlay_setup"] + ) or ( + cfg[0].get("template", "") + == self.templates["ext_evpn_multisite_overlay_setup"] + ): + link_spec["profile"]["ebgp_password_enable"] = dict( + type="bool", default=True + ) + + if cfg[0].get("profile", None) is None: + self.module.fail_json( + msg="Required information not found: profile" + ) + + # Other parameters depend on "bgp_password_enable" flag. + if cfg[0]["profile"].get("ebgp_password_enable", True) is True: + link_spec["profile"]["inherit_from_msd"] = dict( + type="bool", default=True + ) + if cfg[0]["profile"].get("inherit_from_msd", True) is False: + link_spec["profile"]["ebgp_password"] = dict( + required=True, type="str" + ) + link_spec["profile"]["ebgp_auth_key_type"] = dict( + type="int", default=3, choices=[3, 7] + ) + + return link_spec + + def dcnm_links_validate_links_input(self, cfg): + + """ + Routine to validate the playbook input. This routine updates self.links_info + with validated playbook information by defaulting values not included + + Parameters: + cfg (dict): The config from playbook + + Returns: + None + """ + + if cfg[0].get("dst_fabric", "") == "": + self.module.fail_json( + msg="Required parameter not found: dst_fabric" + ) + + if self.fabric == cfg[0]["dst_fabric"]: + link_spec = self.dcnm_links_get_intra_fabric_link_spec(cfg) + else: + link_spec = self.dcnm_links_get_inter_fabric_link_spec(cfg) + + self.dcnm_links_validate_and_build_links_info(cfg, link_spec) + + def dcnm_links_validate_deleted_state_input(self, cfg): + + """ + Playbook input will be different for differnt states. This routine validates the query state + input. This routine updates self.links_info with validated playbook information related to query + state. + + Parameters: + cfg (dict): The config from playbook + + Returns: + None + """ + + link_spec = dict( + dst_fabric=dict(required=True, type="str"), + src_device=dict(required=True, type="str"), + dst_device=dict(required=True, type="str"), + src_interface=dict(required=True, type="str"), + dst_interface=dict(required=True, type="str"), + ) + + links_info, invalid_params = validate_list_of_dicts(cfg, link_spec) + if invalid_params: + mesg = "Invalid parameters in playbook: {0}".format(invalid_params) + self.module.fail_json(msg=mesg) + + if links_info: + self.links_info.extend(links_info) + + def dcnm_links_validate_query_state_input(self, cfg): + + """ + Playbook input will be different for differnt states. This routine validates the query state + input. This routine updates self.links_info with validated playbook information related to query + state. + + Parameters: + cfg (dict): The config from playbook + + Returns: + None + """ + + link_spec = dict( + dst_fabric=dict(type="str", default=""), + src_device=dict(type="str", default=""), + dst_device=dict(type="str", default=""), + src_interface=dict(type="str", default=""), + dst_interface=dict(type="str", default=""), + template=dict( + type="str", choices=self.template_choices, default="" + ), + ) + + links_info, invalid_params = validate_list_of_dicts(cfg, link_spec) + if invalid_params: + mesg = "Invalid parameters in playbook: {0}".format(invalid_params) + self.module.fail_json(msg=mesg) + + if links_info: + self.links_info.extend(links_info) + + def dcnm_links_get_links_payload(self, link): + + """ + This routine builds the complete Links payload based on the information in self.want + + Parameters: + link (dict): Link information + + Returns: + link_payload (dict): Link payload information populated with appropriate data from playbook config + """ + + link_payload = { + "sourceFabric": self.fabric, + "destinationFabric": link.get("dst_fabric"), + "sourceInterface": link.get("src_interface"), + "destinationInterface": link.get("dst_interface"), + "sourceDevice": self.ip_sn[link.get("src_device")], + "destinationDevice": self.ip_sn[link.get("dst_device")], + } + + if self.module.params["state"] == "deleted": + return link_payload + + link_payload["sourceSwitchName"] = self.sn_hn.get( + link_payload["sourceDevice"], "Switch1" + ) + link_payload["destinationSwitchName"] = self.sn_hn.get( + link_payload["destinationDevice"], "Switch2" + ) + link_payload["templateName"] = link.get("template") + + # Intra and Inter fabric payloads are different. Build them separately + if link_payload["sourceFabric"] == link_payload["destinationFabric"]: + self.dcnm_links_get_intra_fabric_links_payload(link, link_payload) + else: + self.dcnm_links_get_inter_fabric_links_payload(link, link_payload) + + return link_payload + + def dcnm_links_get_inter_fabric_links_payload(self, link, link_payload): + + """ + This routine builds the inter-fabric Links payload based on the information in the given + link + + Parameters: + link (dict): Link information + link_payload (dict): Link payload to be updated + + Returns: + link_payload (dict): Link payload information populated with appropriate data from playbook config + """ + + link_payload["nvPairs"] = {} + if ( + link["template"] == self.templates["ext_multisite_underlay_setup"] + ) or (link["template"] == self.templates["ext_fabric_setup"]): + ip_prefix = link["profile"]["ipv4_subnet"].split("/") + + link_payload["nvPairs"]["IP_MASK"] = ( + str(ipaddress.ip_address(ip_prefix[0])) + "/" + ip_prefix[1] + ) + link_payload["nvPairs"]["MTU"] = link["profile"]["mtu"] + link_payload["nvPairs"]["PEER1_DESC"] = link["profile"][ + "peer1_description" + ] + link_payload["nvPairs"]["PEER2_DESC"] = link["profile"][ + "peer2_description" + ] + + if link["profile"].get("peer1_cmds") == []: + link_payload["nvPairs"]["PEER1_CONF"] = "" + else: + link_payload["nvPairs"]["PEER1_CONF"] = "\n".join( + link["profile"].get("peer1_cmds") + ) + + if link["profile"].get("peer2_cmds") == []: + link_payload["nvPairs"]["PEER2_CONF"] = "" + else: + link_payload["nvPairs"]["PEER2_CONF"] = "\n".join( + link["profile"].get("peer2_cmds") + ) + else: + link_payload["nvPairs"]["SOURCE_IP"] = str( + ipaddress.ip_address(link["profile"]["ipv4_addr"]) + ) + + link_payload["nvPairs"]["NEIGHBOR_IP"] = str( + ipaddress.ip_address(link["profile"]["neighbor_ip"]) + ) + link_payload["nvPairs"]["asn"] = link["profile"]["src_asn"] + link_payload["nvPairs"]["NEIGHBOR_ASN"] = link["profile"]["dst_asn"] + + if link["template"] == self.templates["ext_fabric_setup"]: + link_payload["nvPairs"]["AUTO_VRF_LITE_FLAG"] = link["profile"][ + "auto_deploy" + ] + link_payload["nvPairs"][ + "VRF_LITE_JYTHON_TEMPLATE" + ] = "Ext_VRF_Lite_Jython" + + if link["template"] == self.templates["ext_multisite_underlay_setup"]: + link_payload["nvPairs"]["MAX_PATHS"] = link["profile"]["max_paths"] + link_payload["nvPairs"]["ROUTING_TAG"] = link["profile"][ + "route_tag" + ] + link_payload["nvPairs"]["DEPLOY_DCI_TRACKING"] = link["profile"][ + "deploy_dci_tracking" + ] + + if ( + link["template"] + == self.templates["ext_evpn_multisite_overlay_setup"] + ): + link_payload["nvPairs"]["TRM_ENABLED"] = link["profile"][ + "trm_enabled" + ] + link_payload["nvPairs"]["BGP_MULTIHOP"] = link["profile"][ + "bgp_multihop" + ] + + if ( + link["template"] == self.templates["ext_multisite_underlay_setup"] + ) or ( + link["template"] + == self.templates["ext_evpn_multisite_overlay_setup"] + ): + link_payload["nvPairs"]["BGP_PASSWORD_ENABLE"] = link["profile"][ + "ebgp_password_enable" + ] + + # Other parameters depend on "bgp_password_enable" flag. + if link["profile"].get("ebgp_password_enable", True) is True: + link_payload["nvPairs"][ + "BGP_PASSWORD_INHERIT_FROM_MSD" + ] = link["profile"]["inherit_from_msd"] + if link["profile"].get("inherit_from_msd", True) is False: + link_payload["nvPairs"]["BGP_PASSWORD"] = link["profile"][ + "ebgp_password" + ] + link_payload["nvPairs"]["BGP_AUTH_KEY_TYPE"] = link[ + "profile" + ]["ebgp_auth_key_type"] + + def dcnm_links_get_intra_fabric_links_payload(self, link, link_payload): + + """ + This routine builds the intra-fabric Links payload based on the information in the given + link + + Parameters: + link (dict): Link information + link_payload (dict): Link payload to be updated + + Returns: + link_payload (dict): Link payload information populated with appropriate data from playbook config + """ + + if ( + link["template"] + != self.templates["int_pre_provision_intra_fabric_link"] + ): + link_payload["nvPairs"] = {} + link_payload["nvPairs"]["ADMIN_STATE"] = link["profile"].get( + "admin_state" + ) + link_payload["nvPairs"]["MTU"] = link["profile"].get("mtu") + link_payload["nvPairs"]["PEER1_DESC"] = link["profile"].get( + "peer1_description" + ) + link_payload["nvPairs"]["PEER2_DESC"] = link["profile"].get( + "peer2_description" + ) + + if link["profile"].get("peer1_cmds") == []: + link_payload["nvPairs"]["PEER1_CONF"] = "" + else: + link_payload["nvPairs"]["PEER1_CONF"] = "\n".join( + link["profile"].get("peer1_cmds") + ) + + if link["profile"].get("peer2_cmds") == []: + link_payload["nvPairs"]["PEER2_CONF"] = "" + else: + link_payload["nvPairs"]["PEER2_CONF"] = "\n".join( + link["profile"].get("peer2_cmds") + ) + + if ( + (link["template"] == self.templates["int_intra_fabric_num_link"]) + or ( + link["template"] + == self.templates["ios_xe_int_intra_fabric_num_link"] + ) + or ( + link["template"] + == self.templates["int_intra_vpc_peer_keep_alive_link"] + ) + ): + if ( + self.src_fabric_info["nvPairs"]["UNDERLAY_IS_V6"].lower() + == "false" + ): + link_payload["nvPairs"]["PEER1_IP"] = str( + ipaddress.ip_address( + link["profile"].get("peer1_ipv4_addr") + ) + ) + link_payload["nvPairs"]["PEER2_IP"] = str( + ipaddress.ip_address( + link["profile"].get("peer2_ipv4_addr") + ) + ) + else: + if ( + link["template"] + != self.templates["ios_xe_int_intra_fabric_num_link"] + ): + link_payload["nvPairs"]["PEER1_V6IP"] = str( + ipaddress.ip_address( + link["profile"].get("peer1_ipv6_addr") + ) + ) + link_payload["nvPairs"]["PEER2_V6IP"] = str( + ipaddress.ip_address( + link["profile"].get("peer2_ipv6_addr") + ) + ) + if link["profile"].get("peer1_ipv4_addr", "") != "": + link_payload["nvPairs"]["PEER1_IP"] = str( + ipaddress.ip_address( + link["profile"].get("peer1_ipv4_addr") + ) + ) + else: + link_payload["nvPairs"]["PEER1_IP"] = "" + + if link["profile"].get("peer2_ipv4_addr", "") != "": + link_payload["nvPairs"]["PEER2_IP"] = str( + ipaddress.ip_address( + link["profile"].get("peer2_ipv4_addr") + ) + ) + else: + link_payload["nvPairs"]["PEER2_IP"] = "" + else: + link_payload["nvPairs"]["PEER1_IP"] = str( + ipaddress.ip_address( + link["profile"].get("peer1_ipv6_addr") + ) + ) + link_payload["nvPairs"]["PEER2_IP"] = str( + ipaddress.ip_address( + link["profile"].get("peer2_ipv6_addr") + ) + ) + + if ( + (link["template"] == self.templates["int_intra_fabric_num_link"]) + or ( + link["template"] + == self.templates["int_intra_fabric_ipv6_link_local"] + ) + or ( + link["template"] + == self.templates["int_intra_fabric_unnum_link"] + ) + ): + link_payload["nvPairs"]["ENABLE_MACSEC"] = link["profile"].get( + "enable_macsec" + ) + + if ( + link["template"] + == self.templates["int_intra_vpc_peer_keep_alive_link"] + ): + link_payload["nvPairs"]["INTF_VRF"] = link["profile"].get( + "intf_vrf" + ) + + if link["template"] == self.templates["int_intra_fabric_num_link"]: + link_payload["nvPairs"]["PEER1_BFD_ECHO_DISABLE"] = link[ + "profile" + ].get("peer1_bfd_echo_disable") + link_payload["nvPairs"]["PEER2_BFD_ECHO_DISABLE"] = link[ + "profile" + ].get("peer2_bfd_echo_disable") + + def dcnm_links_update_inter_fabric_links_information( + self, wlink, hlink, cfg + ): + + if (wlink.get("nvPairs", None) is None) or ( + (hlink.get("nvPairs", None) is None) + ): + return + + if ( + wlink["templateName"] + == self.templates["ext_multisite_underlay_setup"] + ) or (wlink["templateName"] == self.templates["ext_fabric_setup"]): + if cfg["profile"].get("ipv4_subnet", None) is None: + wlink["nvPairs"]["IP_MASK"] = hlink["nvPairs"]["IP_MASK"] + + if cfg["profile"].get("mtu", None) is None: + wlink["nvPairs"]["MTU"] = hlink["nvPairs"]["MTU"] + + if cfg["profile"].get("peer1_decription", None) is None: + wlink["nvPairs"]["PEER1_DESC"] = hlink["nvPairs"]["PEER1_DESC"] + if cfg["profile"].get("peer2_decription", None) is None: + wlink["nvPairs"]["PEER2_DESC"] = hlink["nvPairs"]["PEER2_DESC"] + + # Note down that 'want' is updated with information from 'have'. We will need + # this to properly merge 'want' and 'have' during diff_merge. + if cfg["profile"].get("peer1_cmds", None) is None: + wlink["nvPairs"]["PEER1_CONF"] = hlink["nvPairs"]["PEER1_CONF"] + wlink["peer1_conf_defaulted"] = True + if cfg["profile"].get("peer2_cmds", None) is None: + wlink["nvPairs"]["PEER2_CONF"] = hlink["nvPairs"]["PEER2_CONF"] + wlink["peer2_conf_defaulted"] = True + else: + if cfg["profile"].get("ipv4_addr", None) is None: + wlink["nvPairs"]["SOURCE_IP"] = hlink["nvPairs"]["SOURCE_IP"] + + # This template does not include PEER1_CONF and PEER2_CONF parameters. Mark the following + # so that dcnm_links_merge_want_and_have() can be generic for all cases + wlink["peer1_conf_defaulted"] = True + wlink["peer2_conf_defaulted"] = True + + if cfg["profile"].get("neighbor_ip", None) is None: + wlink["nvPairs"]["NEIGHBOR_IP"] = hlink["nvPairs"]["NEIGHBOR_IP"] + if cfg["profile"].get("src_asn", None) is None: + wlink["nvPairs"]["asn"] = hlink["nvPairs"]["asn"] + if cfg["profile"].get("dst_asn", None) is None: + wlink["nvPairs"]["NEIGHBOR_ASN"] = hlink["nvPairs"]["NEIGHBOR_ASN"] + + if wlink["templateName"] == self.templates["ext_fabric_setup"]: + if cfg["profile"].get("auto_deploy", None) is None: + wlink["nvPairs"]["AUTO_VRF_LITE_FLAG"] = hlink["nvPairs"][ + "AUTO_VRF_LITE_FLAG" + ] + wlink["nvPairs"]["VRF_LITE_JYTHON_TEMPLATE"] = hlink["nvPairs"][ + "VRF_LITE_JYTHON_TEMPLATE" + ] + + if ( + wlink["templateName"] + == self.templates["ext_multisite_underlay_setup"] + ): + if cfg["profile"].get("max_paths", None) is None: + wlink["nvPairs"]["MAX_PATHS"] = hlink["nvPairs"]["MAX_PATHS"] + if cfg["profile"].get("route_tag", None) is None: + wlink["nvPairs"]["ROUTING_TAG"] = hlink["nvPairs"][ + "ROUTING_TAG" + ] + if cfg["profile"].get("deploy_dci_tracking", None) is None: + wlink["nvPairs"]["DEPLOY_DCI_TRACKING"] = hlink["nvPairs"][ + "DEPLOY_DCI_TRACKING" + ] + + if ( + wlink["templateName"] + == self.templates["ext_evpn_multisite_overlay_setup"] + ): + if cfg["profile"].get("trm_enabled", None) is None: + wlink["nvPairs"]["TRM_ENABLED"] = hlink["nvPairs"][ + "TRM_ENABLED" + ] + if cfg["profile"].get("bgp_multihop", None) is None: + wlink["nvPairs"]["BGP_MULTIHOP"] = hlink["nvPairs"][ + "BGP_MULTIHOP" + ] + + if ( + wlink["templateName"] + == self.templates["ext_multisite_underlay_setup"] + ) or ( + wlink["templateName"] + == self.templates["ext_evpn_multisite_overlay_setup"] + ): + if cfg["profile"].get("ebgp_password_enable", None) is None: + wlink["nvPairs"]["BGP_PASSWORD_ENABLE"] = hlink["nvPairs"][ + "BGP_PASSWORD_ENABLE" + ] + + # Other parameters depend on "bgp_password_enable" flag. + if wlink["nvPairs"]["BGP_PASSWORD_ENABLE"] is True: + if cfg["profile"].get("inherit_from_msd", None) is None: + wlink["nvPairs"]["BGP_PASSWORD_INHERIT_FROM_MSD"] = hlink[ + "nvPairs" + ]["BGP_PASSWORD_INHERIT_FROM_MSD"] + if wlink["nvPairs"]["BGP_PASSWORD_INHERIT_FROM_MSD"] is False: + if cfg["profile"].get("ebgp_password", None) is None: + wlink["nvPairs"]["BGP_PASSWORD"] = hlink["nvPairs"][ + "BGP_PASSWORD" + ] + if cfg["profile"].get("ebgp_auth_key_type", None) is None: + wlink["nvPairs"]["BGP_AUTH_KEY_TYPE"] = hlink[ + "nvPairs" + ]["BGP_AUTH_KEY_TYPE"] + + def dcnm_links_update_intra_fabric_links_information( + self, wlink, hlink, cfg + ): + + if (wlink.get("nvPairs", None) is None) or ( + (hlink.get("nvPairs", None) is None) + ): + return + + if cfg["profile"].get("admin_state", None) is None: + wlink["nvPairs"]["ADMIN_STATE"] = hlink["nvPairs"]["ADMIN_STATE"] + if cfg["profile"].get("mtu", None) is None: + wlink["nvPairs"]["MTU"] = hlink["nvPairs"]["MTU"] + if cfg["profile"].get("peer1_description", None) is None: + wlink["nvPairs"]["PEER1_DESC"] = hlink["nvPairs"]["PEER1_DESC"] + if cfg["profile"].get("peer2_description", None) is None: + wlink["nvPairs"]["PEER2_DESC"] = hlink["nvPairs"]["PEER2_DESC"] + + # Note down that 'want' is updated with information from 'have'. We will need + # this to properly merge 'want' and 'have' during diff_merge. + if cfg["profile"].get("peer1_cmds", None) is None: + wlink["nvPairs"]["PEER1_CONF"] = hlink["nvPairs"]["PEER1_CONF"] + wlink["peer1_conf_defaulted"] = True + if cfg["profile"].get("peer2_cmds", None) is None: + wlink["nvPairs"]["PEER2_CONF"] = hlink["nvPairs"]["PEER2_CONF"] + wlink["peer2_conf_defaulted"] = True + + if ( + ( + wlink["templateName"] + == self.templates["int_intra_fabric_num_link"] + ) + or ( + wlink["templateName"] + == self.templates["ios_xe_int_intra_fabric_num_link"] + ) + or ( + wlink["templateName"] + == self.templates["int_intra_vpc_peer_keep_alive_link"] + ) + ): + if ( + self.src_fabric_info["nvPairs"]["UNDERLAY_IS_V6"].lower() + == "false" + ): + if cfg["profile"].get("peer1_ipv4_addr", None) is None: + wlink["nvPairs"]["PEER1_IP"] = hlink["nvPairs"]["PEER1_IP"] + if cfg["profile"].get("peer2_ipv4_addr", None) is None: + wlink["nvPairs"]["PEER2_IP"] = hlink["nvPairs"]["PEER2_IP"] + else: + if ( + wlink["templateName"] + != self.templates["ios_xe_int_intra_fabric_num_link"] + ): + if cfg["profile"].get("peer1_ipv6_addr", None) is None: + wlink["nvPairs"]["PEER1_V6IP"] = hlink["nvPairs"][ + "PEER1_V6IP" + ] + if cfg["profile"].get("peer2_ipv6_addr", None) is None: + wlink["nvPairs"]["PEER2_V6IP"] = hlink["nvPairs"][ + "PEER2_V6IP" + ] + else: + if cfg["profile"].get("peer1_ipv4_addr", None) is None: + wlink["nvPairs"]["PEER1_IP"] = hlink["nvPairs"][ + "PEER1_IP" + ] + if cfg["profile"].get("peer2_ipv4_addr", None) is None: + wlink["nvPairs"]["PEER2_IP"] = hlink["nvPairs"][ + "PEER2_IP" + ] + + if ( + ( + wlink["templateName"] + == self.templates["int_intra_fabric_num_link"] + ) + or ( + wlink["templateName"] + == self.templates["int_intra_fabric_ipv6_link_local"] + ) + or ( + wlink["templateName"] + == self.templates["int_intra_fabric_unnum_link"] + ) + ): + if cfg["profile"].get("enable_macsec", None) is None: + wlink["nvPairs"]["ENABLE_MACSEC"] = hlink["nvPairs"][ + "ENABLE_MACSEC" + ] + + if ( + wlink["templateName"] + == self.templates["int_intra_vpc_peer_keep_alive_link"] + ): + if cfg["profile"].get("intf_vrf", None) is None: + wlink["nvPairs"]["INTF_VRF"] = hlink["nvPairs"]["INTF_VRF"] + + if ( + wlink["templateName"] + == self.templates["int_intra_fabric_num_link"] + ): + if cfg["profile"].get("peer1_bfd_echo_disable", None) is None: + wlink["nvPairs"]["PEER1_BFD_ECHO_DISABLE"] = hlink["nvPairs"][ + "PEER1_BFD_ECHO_DISABLE" + ] + if cfg["profile"].get("peer2_bfd_echo_disable", None) is None: + wlink["nvPairs"]["PEER2_BFD_ECHO_DISABLE"] = hlink["nvPairs"][ + "PEER2_BFD_ECHO_DISABLE" + ] + + def dcnm_links_update_want(self): + + if self.module.params["state"] != "merged": + return + + for want in self.want: + + match_links = [ + have + for have in self.have + if ( + (have["sw1-info"]["fabric-name"] == want["sourceFabric"]) + and ( + have["sw2-info"]["fabric-name"] + == want["destinationFabric"] + ) + and ( + have["sw1-info"]["if-name"] == want["sourceInterface"] + ) + and ( + have["sw2-info"]["if-name"] + == want["destinationInterface"] + ) + and ( + have["sw1-info"]["sw-serial-number"] + == want["sourceDevice"] + ) + and ( + have["sw2-info"]["sw-serial-number"] + == want["destinationDevice"] + ) + and ( + have.get("templateName", "") + == want.get("templateName", "") + ) + ) + ] + + match_cfg = [ + cfg + for cfg in self.config + if ( + (self.fabric == want["sourceFabric"]) + and (cfg["dst_fabric"] == want["destinationFabric"]) + and (cfg["src_interface"] == want["sourceInterface"]) + and (cfg["dst_interface"] == want["destinationInterface"]) + and (self.ip_sn[cfg["src_device"]] == want["sourceDevice"]) + and ( + self.ip_sn[cfg["dst_device"]] + == want["destinationDevice"] + ) + and (cfg["template"] == want["templateName"]) + ) + ] + + if match_cfg == []: + continue + + for mlink in match_links: + if want["sourceFabric"] == want["destinationFabric"]: + self.dcnm_links_update_intra_fabric_links_information( + want, mlink, match_cfg[0] + ) + else: + self.dcnm_links_update_inter_fabric_links_information( + want, mlink, match_cfg[0] + ) + + def dcnm_links_get_want(self): + + """ + This routine updates self.want with the payload information based on the playbook configuration. + + Parameters: + None + + Returns: + None + """ + + if [] is self.config: + return + + if not self.links_info: + return + + for link_elem in self.links_info: + + link_payload = self.dcnm_links_get_links_payload(link_elem) + if link_payload not in self.want: + self.want.append(link_payload) + + def dcnm_links_get_links_info_from_dcnm(self, link): + + """ + Routine to get existing Links information from DCNM which matches the given Link. + + Parameters: + link (dict): Link information + + Returns: + resp["DATA"] (dict): Link informatikon obtained from the DCNM server if it exists + [] otherwise + """ + + # link object is from self.want. These objets would have translated devices to serial numbers already. + path = self.paths[ + "LINKS_GET_BY_SWITCH_PAIR" + ] + "?switch1Sn={0}&switch2Sn={1}".format( + link["sourceDevice"], link["destinationDevice"] + ) + path = path + "&switch1IfName={0}&switch2IfName={1}".format( + link["sourceInterface"], link["destinationInterface"] + ) + + resp = dcnm_send(self.module, "GET", path) + + if ( + resp + and (resp["RETURN_CODE"] == 200) + and (resp["MESSAGE"] == "OK") + and resp["DATA"] + ): + # The response DATA will include all links between src_device and dst_device. We are interested + # only in a LINK that matches the given 'link'. Try to match for the information in the response + + # resp["DATA"] will be a list if there is more than one link. It will be a dict otherwise + + if not isinstance(resp["DATA"], list): + resp["DATA"] = [resp["DATA"]] + + match_link = [ + link_elem + for link_elem in resp["DATA"] + if ( + ( + link_elem["sw1-info"]["fabric-name"] + == link["sourceFabric"] + ) + and ( + link_elem["sw2-info"]["fabric-name"] + == link["destinationFabric"] + ) + ) + ] + + if match_link != []: + return match_link[0] + else: + return [] + else: + return [] + + def dcnm_links_get_have(self): + + """ + Routine to get exisitng links information from DCNM that matches information in self.want. + This routine updates self.have with all the Links that match the given playbook configuration + + Parameters: + None + + Returns: + None + """ + + if self.want == []: + return + + for link in self.want: + have = self.dcnm_links_get_links_info_from_dcnm(link) + if (have != []) and (have not in self.have): + self.have.append(have) + + def dcnm_links_compare_inter_fabric_link_params(self, wlink, hlink): + + """ + Routine to compare two links and update mismatch information. + + Parameters: + wlink (dict): Requested link information + hlink (dict): Existing link information + Returns: + DCNM_LINK_EXIST(str): - if given link is not found + DCNM_LINK_MERGE(str): - if given link exists but there are changes in parameters + mismatch_reasons(list): a list identifying objects that differed if required. [] otherwise + hlink(dict): existing link if required, [] otherwise + """ + + mismatch_reasons = [] + + if hlink["templateName"] != wlink["templateName"]: + # We found a Link that matched all other key values, but the template is different. This means + # the user is trying to change the template of an existing link. So go ahead and merge the same + mismatch_reasons.append( + { + "TEMPLATE_MISMATCH": [ + wlink["templateName"], + hlink["templateName"], + ] + } + ) + return "DCNM_LINK_MERGE", mismatch_reasons, hlink + + if (wlink.get("nvPairs", None) is None) or ( + (hlink.get("nvPairs", None) is None) + ): + return "DCNM_LINK_EXIST", [], [] + + if ( + wlink["templateName"] + == self.templates["ext_multisite_underlay_setup"] + ) or (wlink["templateName"] == self.templates["ext_fabric_setup"]): + if ( + self.dcnm_links_compare_ip_addresses( + wlink["nvPairs"]["IP_MASK"], hlink["nvPairs"]["IP_MASK"] + ) + is False + ): + mismatch_reasons.append( + { + "IP_MASK_MISMATCH": [ + wlink["nvPairs"]["IP_MASK"], + hlink["nvPairs"]["IP_MASK"], + ] + } + ) + if ( + str(wlink["nvPairs"]["MTU"]).lower() + != str(hlink["nvPairs"]["MTU"]).lower() + ): + mismatch_reasons.append( + { + "MTU_MISMATCH": [ + str(wlink["nvPairs"]["MTU"]).lower(), + str(hlink["nvPairs"]["MTU"]).lower(), + ] + } + ) + if ( + wlink["nvPairs"]["PEER1_DESC"] + != hlink["nvPairs"]["PEER1_DESC"] + ): + mismatch_reasons.append( + { + "PEER1_DESC_MISMATCH": [ + wlink["nvPairs"]["PEER1_DESC"], + hlink["nvPairs"]["PEER1_DESC"], + ] + } + ) + if ( + wlink["nvPairs"]["PEER2_DESC"] + != hlink["nvPairs"]["PEER2_DESC"] + ): + mismatch_reasons.append( + { + "PEER2_DESC_MISMATCH": [ + wlink["nvPairs"]["PEER2_DESC"], + hlink["nvPairs"]["PEER2_DESC"], + ] + } + ) + + if ( + wlink["nvPairs"]["PEER1_CONF"] + != hlink["nvPairs"]["PEER1_CONF"] + ): + mismatch_reasons.append( + { + "PEER1_CONF_MISMATCH": [ + wlink["nvPairs"]["PEER1_CONF"], + hlink["nvPairs"]["PEER1_CONF"], + ] + } + ) + if ( + wlink["nvPairs"]["PEER2_CONF"] + != hlink["nvPairs"]["PEER2_CONF"] + ): + mismatch_reasons.append( + { + "PEER2_CONF_MISMATCH": [ + wlink["nvPairs"]["PEER2_CONF"], + hlink["nvPairs"]["PEER2_CONF"], + ] + } + ) + else: + if ( + self.dcnm_links_compare_ip_addresses( + wlink["nvPairs"]["SOURCE_IP"], + hlink["nvPairs"]["SOURCE_IP"], + ) + is False + ): + mismatch_reasons.append( + { + "SOURCE_IP_MISMATCH": [ + wlink["nvPairs"]["SOURCE_IP"], + hlink["nvPairs"]["SOURCE_IP"], + ] + } + ) + + if ( + self.dcnm_links_compare_ip_addresses( + wlink["nvPairs"]["NEIGHBOR_IP"], + hlink["nvPairs"]["NEIGHBOR_IP"], + ) + is False + ): + mismatch_reasons.append( + { + "NEIGHBOR_IP_MISMATCH": [ + wlink["nvPairs"]["NEIGHBOR_IP"], + hlink["nvPairs"]["NEIGHBOR_IP"], + ] + } + ) + if ( + str(wlink["nvPairs"]["asn"]).lower() + != str(hlink["nvPairs"]["asn"]).lower() + ): + mismatch_reasons.append( + { + "ASN_MISMATCH": [ + str(wlink["nvPairs"]["asn"]).lower(), + str(hlink["nvPairs"]["asn"]).lower(), + ] + } + ) + if ( + str(wlink["nvPairs"]["NEIGHBOR_ASN"]).lower() + != str(hlink["nvPairs"]["NEIGHBOR_ASN"]).lower() + ): + mismatch_reasons.append( + { + "NEIGHBOR_ASN_MISMATCH": [ + str(wlink["nvPairs"]["NEIGHBOR_ASN"]).lower(), + str(hlink["nvPairs"]["NEIGHBOR_ASN"]).lower(), + ] + } + ) + + if wlink["templateName"] == self.templates["ext_fabric_setup"]: + + if ( + str(wlink["nvPairs"]["AUTO_VRF_LITE_FLAG"]).lower() + != str(hlink["nvPairs"]["AUTO_VRF_LITE_FLAG"]).lower() + ): + mismatch_reasons.append( + { + "AUTO_VRF_LITE_FLAG_MISMATCH": [ + str( + wlink["nvPairs"]["AUTO_VRF_LITE_FLAG"] + ).lower(), + str( + hlink["nvPairs"]["AUTO_VRF_LITE_FLAG"] + ).lower(), + ] + } + ) + if ( + wlink["nvPairs"]["VRF_LITE_JYTHON_TEMPLATE"] + != hlink["nvPairs"]["VRF_LITE_JYTHON_TEMPLATE"] + ): + mismatch_reasons.append( + { + "VRF_LITE_JYTHON_TEMPLATE_MISMATCH": [ + wlink["nvPairs"]["VRF_LITE_JYTHON_TEMPLATE"], + hlink["nvPairs"]["VRF_LITE_JYTHON_TEMPLATE"], + ] + } + ) + + if ( + wlink["templateName"] + == self.templates["ext_multisite_underlay_setup"] + ): + + if ( + str(wlink["nvPairs"]["MAX_PATHS"]).lower() + != str(hlink["nvPairs"]["MAX_PATHS"]).lower() + ): + mismatch_reasons.append( + { + "MAX_PATHS_MISMATCH": [ + str(wlink["nvPairs"]["MAX_PATHS"]).lower(), + str(hlink["nvPairs"]["MAX_PATHS"]).lower(), + ] + } + ) + if ( + str(wlink["nvPairs"]["ROUTING_TAG"]).lower() + != str(hlink["nvPairs"]["ROUTING_TAG"]).lower() + ): + mismatch_reasons.append( + { + "ROUTING_TAG_MISMATCH": [ + str(wlink["nvPairs"]["ROUTING_TAG"]).lower(), + str(hlink["nvPairs"]["ROUTING_TAG"]).lower(), + ] + } + ) + if ( + str(wlink["nvPairs"]["DEPLOY_DCI_TRACKING"]).lower() + != str(hlink["nvPairs"]["DEPLOY_DCI_TRACKING"]).lower() + ): + mismatch_reasons.append( + { + "DEPLOY_DCI_TRACKING_MISMATCH": [ + str( + wlink["nvPairs"]["DEPLOY_DCI_TRACKING"] + ).lower(), + str( + hlink["nvPairs"]["DEPLOY_DCI_TRACKING"] + ).lower(), + ] + } + ) + + if ( + wlink["templateName"] + == self.templates["ext_evpn_multisite_overlay_setup"] + ): + + if ( + str(wlink["nvPairs"]["TRM_ENABLED"]).lower() + != str(hlink["nvPairs"]["TRM_ENABLED"]).lower() + ): + mismatch_reasons.append( + { + "TRM_ENABLED_MISMATCH": [ + str(wlink["nvPairs"]["TRM_ENABLED"]).lower(), + str(hlink["nvPairs"]["TRM_ENABLED"]).lower(), + ] + } + ) + if ( + str(wlink["nvPairs"]["BGP_MULTIHOP"]).lower() + != str(hlink["nvPairs"]["BGP_MULTIHOP"]).lower() + ): + mismatch_reasons.append( + { + "BGP_MULTIHOP_MISMATCH": [ + str(wlink["nvPairs"]["BGP_MULTIHOP"]).lower(), + str(hlink["nvPairs"]["BGP_MULTIHOP"]).lower(), + ] + } + ) + + if ( + wlink["templateName"] + == self.templates["ext_multisite_underlay_setup"] + ) or ( + wlink["templateName"] + == self.templates["ext_evpn_multisite_overlay_setup"] + ): + + if ( + str(wlink["nvPairs"]["BGP_PASSWORD_ENABLE"]).lower() + != str(hlink["nvPairs"]["BGP_PASSWORD_ENABLE"]).lower() + ): + mismatch_reasons.append( + { + "BGP_PASSWORD_ENABLE_MISMATCH": [ + str( + wlink["nvPairs"]["BGP_PASSWORD_ENABLE"] + ).lower(), + str( + hlink["nvPairs"]["BGP_PASSWORD_ENABLE"] + ).lower(), + ] + } + ) + # Other parameters depend on "bgp_password_enable" flag. + if wlink["nvPairs"]["BGP_PASSWORD_ENABLE"] is True: + + if ( + str( + wlink["nvPairs"]["BGP_PASSWORD_INHERIT_FROM_MSD"] + ).lower() + != str( + hlink["nvPairs"]["BGP_PASSWORD_INHERIT_FROM_MSD"] + ).lower() + ): + mismatch_reasons.append( + { + "BGP_PASSWORD_INHERIT_FROM_MSD_MISMATCH": [ + str( + wlink["nvPairs"][ + "BGP_PASSWORD_INHERIT_FROM_MSD" + ] + ).lower(), + str( + hlink["nvPairs"][ + "BGP_PASSWORD_INHERIT_FROM_MSD" + ] + ).lower(), + ] + } + ) + if wlink["nvPairs"]["BGP_PASSWORD_INHERIT_FROM_MSD"] is False: + + if wlink["nvPairs"]["BGP_PASSWORD"] != hlink[ + "nvPairs" + ].get("BGP_PASSWORD", ""): + mismatch_reasons.append( + { + "BGP_PASSWORD_MISMATCH": [ + wlink["nvPairs"]["BGP_PASSWORD"], + hlink["nvPairs"].get("BGP_PASSWORD", ""), + ] + } + ) + if ( + str(wlink["nvPairs"]["BGP_AUTH_KEY_TYPE"]).lower() + != str( + hlink["nvPairs"].get("BGP_AUTH_KEY_TYPE", "") + ).lower() + ): + mismatch_reasons.append( + { + "BGP_AUTH_KEY_TYPE_MISMATCH": [ + str( + wlink["nvPairs"]["BGP_AUTH_KEY_TYPE"] + ).lower(), + str( + hlink["nvPairs"].get( + "BGP_AUTH_KEY_TYPE", "" + ) + ).lower(), + ] + } + ) + + if mismatch_reasons != []: + return "DCNM_LINK_MERGE", mismatch_reasons, hlink + else: + return "DCNM_LINK_EXIST", [], [] + + def dcnm_links_compare_intra_fabric_link_params(self, wlink, hlink): + + """ + Routine to compare two links and update mismatch information. + + Parameters: + wlink (dict): Requested link information + hlink (dict): Existing link information + Returns: + DCNM_LINK_EXIST(str): - if given link is not found + DCNM_LINK_MERGE(str): - if given link exists but there are changes in parameters + mismatch_reasons(list): a list identifying objects that differed if required. [] otherwise + hlink(dict): existing link if required, [] otherwise + """ + + mismatch_reasons = [] + + if hlink["templateName"] != wlink["templateName"]: + # We found a Link that matched all other key values, but the template is different. This means + # the user is trying to change the template of an existing link. So go ahead and merge the same + mismatch_reasons.append( + { + "TEMPLATE_MISMATCH": [ + wlink["templateName"], + hlink["templateName"], + ] + } + ) + return "DCNM_LINK_MERGE", mismatch_reasons, hlink + + if (wlink.get("nvPairs", None) is None) or ( + (hlink.get("nvPairs", None) is None) + ): + return "DCNM_LINK_EXIST", [], [] + + # Compare common info for all templates first + if ( + str(wlink["nvPairs"]["ADMIN_STATE"]).lower() + != str(hlink["nvPairs"]["ADMIN_STATE"]).lower() + ): + mismatch_reasons.append( + { + "ADMIN_STATE_MISMATCH": [ + str(wlink["nvPairs"]["ADMIN_STATE"]).lower(), + str(hlink["nvPairs"]["ADMIN_STATE"]).lower(), + ] + } + ) + if ( + str(wlink["nvPairs"]["MTU"]).lower() + != str(hlink["nvPairs"]["MTU"]).lower() + ): + mismatch_reasons.append( + { + "MTU_MISMATCH": [ + wlink["nvPairs"]["MTU"], + hlink["nvPairs"]["MTU"], + ] + } + ) + if wlink["nvPairs"]["PEER1_DESC"] != hlink["nvPairs"]["PEER1_DESC"]: + mismatch_reasons.append( + { + "PEER1_DESC_MISMATCH": [ + wlink["nvPairs"]["PEER1_DESC"], + hlink["nvPairs"]["PEER1_DESC"], + ] + } + ) + if wlink["nvPairs"]["PEER2_DESC"] != hlink["nvPairs"]["PEER2_DESC"]: + mismatch_reasons.append( + { + "PEER2_DESC_MISMATCH": [ + wlink["nvPairs"]["PEER2_DESC"], + hlink["nvPairs"]["PEER2_DESC"], + ] + } + ) + if wlink["nvPairs"]["PEER1_CONF"] != hlink["nvPairs"]["PEER1_CONF"]: + mismatch_reasons.append( + { + "PEER1_CONF_MISMATCH": [ + wlink["nvPairs"]["PEER1_CONF"], + hlink["nvPairs"]["PEER1_CONF"], + ] + } + ) + if wlink["nvPairs"]["PEER2_CONF"] != hlink["nvPairs"]["PEER2_CONF"]: + mismatch_reasons.append( + { + "PEER2_CONF_MISMATCH": [ + wlink["nvPairs"]["PEER2_CONF"], + hlink["nvPairs"]["PEER2_CONF"], + ] + } + ) + + if ( + ( + wlink["templateName"] + == self.templates["int_intra_fabric_num_link"] + ) + or ( + wlink["templateName"] + == self.templates["ios_xe_int_intra_fabric_num_link"] + ) + or ( + wlink["templateName"] + == self.templates["int_intra_vpc_peer_keep_alive_link"] + ) + ): + if ( + self.src_fabric_info["nvPairs"]["UNDERLAY_IS_V6"].lower() + == "false" + ): + if ( + self.dcnm_links_compare_ip_addresses( + wlink["nvPairs"]["PEER1_IP"], + hlink["nvPairs"].get("PEER1_IP"), + ) + is False + ): + mismatch_reasons.append( + { + "PEER1_IP_MISMATCH": [ + wlink["nvPairs"]["PEER1_IP"], + hlink["nvPairs"]["PEER1_IP"], + ] + } + ) + if ( + self.dcnm_links_compare_ip_addresses( + wlink["nvPairs"]["PEER2_IP"], + hlink["nvPairs"].get("PEER2_IP"), + ) + is False + ): + mismatch_reasons.append( + { + "PEER2_IP_MISMATCH": [ + wlink["nvPairs"]["PEER2_IP"], + hlink["nvPairs"]["PEER2_IP"], + ] + } + ) + else: + if ( + wlink["templateName"] + != self.templates["ios_xe_int_intra_fabric_num_link"] + ): + if ( + self.dcnm_links_compare_ip_addresses( + wlink["nvPairs"]["PEER1_V6IP"], + hlink["nvPairs"].get("PEER1_V6IP"), + ) + is False + ): + mismatch_reasons.append( + { + "PEER1_IPV6_MISMATCH": [ + wlink["nvPairs"]["PEER1_V6IP"], + hlink["nvPairs"]["PEER1_V6IP"], + ] + } + ) + if ( + self.dcnm_links_compare_ip_addresses( + wlink["nvPairs"]["PEER2_V6IP"], + hlink["nvPairs"].get("PEER2_V6IP"), + ) + is False + ): + mismatch_reasons.append( + { + "PEER2_IPV6_MISMATCH": [ + wlink["nvPairs"]["PEER2_V6IP"], + hlink["nvPairs"]["PEER2_V6IP"], + ] + } + ) + else: + if ( + self.dcnm_links_compare_ip_addresses( + wlink["nvPairs"]["PEER1_IP"], + hlink["nvPairs"].get("PEER1_IP"), + ) + is False + ): + mismatch_reasons.append( + { + "PEER1_IP_MISMATCH": [ + wlink["nvPairs"]["PEER1_IP"], + hlink["nvPairs"]["PEER1_IP"], + ] + } + ) + if ( + self.dcnm_links_compare_ip_addresses( + wlink["nvPairs"]["PEER2_IP"], + hlink["nvPairs"].get("PEER2_IP"), + ) + is False + ): + mismatch_reasons.append( + { + "PEER2_IP_MISMATCH": [ + wlink["nvPairs"]["PEER2_IP"], + hlink["nvPairs"]["PEER2_IP"], + ] + } + ) + + if ( + ( + wlink["templateName"] + == self.templates["int_intra_fabric_num_link"] + ) + or ( + wlink["templateName"] + == self.templates["int_intra_fabric_ipv6_link_local"] + ) + or ( + wlink["templateName"] + == self.templates["int_intra_fabric_unnum_link"] + ) + ): + if ( + str(wlink["nvPairs"]["ENABLE_MACSEC"]).lower() + != str(hlink["nvPairs"].get("ENABLE_MACSEC")).lower() + ): + mismatch_reasons.append( + { + "ENABLE_MACSEC_MISMATCH": [ + str(wlink["nvPairs"]["ENABLE_MACSEC"]).lower(), + str(hlink["nvPairs"]["ENABLE_MACSEC"]).lower(), + ] + } + ) + + if ( + wlink["templateName"] + == self.templates["int_intra_vpc_peer_keep_alive_link"] + ): + + if wlink["nvPairs"]["INTF_VRF"] != hlink["nvPairs"].get( + "INTF_VRF" + ): + mismatch_reasons.append( + { + "INTF_VRF_MISMATCH": [ + wlink["nvPairs"]["INTF_VRF"], + hlink["nvPairs"]["INTF_VRF"], + ] + } + ) + + if ( + wlink["templateName"] + == self.templates["int_intra_fabric_num_link"] + ): + if ( + str(wlink["nvPairs"]["PEER1_BFD_ECHO_DISABLE"]).lower() + != str(hlink["nvPairs"].get("PEER1_BFD_ECHO_DISABLE")).lower() + ): + mismatch_reasons.append( + { + "PEER1_BFD_ECHO_DISABLE_MISMATCH": [ + str( + wlink["nvPairs"]["PEER1_BFD_ECHO_DISABLE"] + ).lower(), + str( + hlink["nvPairs"]["PEER1_BFD_ECHO_DISABLE"] + ).lower(), + ] + } + ) + if ( + str(wlink["nvPairs"]["PEER2_BFD_ECHO_DISABLE"]).lower() + != str(hlink["nvPairs"].get("PEER2_BFD_ECHO_DISABLE")).lower() + ): + mismatch_reasons.append( + { + "PEER2_BFD_ECHO_DISABLE_MISMATCH": [ + str( + wlink["nvPairs"]["PEER2_BFD_ECHO_DISABLE"] + ).lower(), + str( + hlink["nvPairs"]["PEER2_BFD_ECHO_DISABLE"] + ).lower(), + ] + } + ) + + if mismatch_reasons != []: + return "DCNM_LINK_MERGE", mismatch_reasons, hlink + else: + return "DCNM_LINK_EXIST", [], [] + + def dcnm_links_compare_Links(self, want): + + """ + This routine finds a link in self.have that matches the given link information. If the given + link already exist then the link is not added to the links list to be created on + DCNM server in the current run. The given link is added to the list of Links to be + created otherwise + + Parameters: + link : Link to be matched from self.have + + Returns: + DCNM_LINK_CREATE (str): - if a new link is to be created + return value of dcnm_links_compare_intra_fabric_link_params or dcnm_links_compare_inter_fabric_link_params + """ + + match_have = [ + have + for have in self.have + if ( + (have["sw1-info"]["fabric-name"] == want["sourceFabric"]) + and ( + have["sw2-info"]["fabric-name"] + == want["destinationFabric"] + ) + and (have["sw1-info"]["if-name"] == want["sourceInterface"]) + and ( + have["sw2-info"]["if-name"] == want["destinationInterface"] + ) + and ( + have["sw1-info"]["sw-serial-number"] + == want["sourceDevice"] + ) + and ( + have["sw2-info"]["sw-serial-number"] + == want["destinationDevice"] + ) + ) + ] + + for mlink in match_have: + if want["sourceFabric"] == want["destinationFabric"]: + return self.dcnm_links_compare_intra_fabric_link_params( + want, mlink + ) + else: + return self.dcnm_links_compare_inter_fabric_link_params( + want, mlink + ) + + return "DCNM_LINK_CREATE", [], [] + + def dcnm_links_merge_want_and_have(self, want, have): + + if (want.get("nvPairs", None) is None) or ( + (have.get("nvPairs", None) is None) + ): + return + + if want.get("peer1_conf_defaulted", False) is False: + + # In the current run, if want["nvPairs"]["PEER1_CONF"] is not included + # then it would have been updated with values from 'have' in the function + # dcnm_links_update_want(). So no need to do the merge here. + if want["nvPairs"].get("PEER1_CONF", "") == "": + want["nvPairs"]["PEER1_CONF"] = have["nvPairs"]["PEER1_CONF"] + elif have["nvPairs"].get("PEER1_CONF", "") == "": + # Nothing to merge. Leave want as it is + pass + else: + want["nvPairs"]["PEER1_CONF"] = ( + have["nvPairs"]["PEER1_CONF"] + + "\n" + + want["nvPairs"]["PEER1_CONF"] + ) + else: + # Remove the "peer1_conf_defaulted" from want + want.pop("peer1_conf_defaulted") + + if want.get("peer2_conf_defaulted", False) is False: + + # In the current run, if want["nvPairs"]["PEER2_CONF"] is not included + # then it would have been updated with values from 'have' in the function + # dcnm_links_update_want(). So no need to do the merge here. + if want["nvPairs"].get("PEER2_CONF", "") == "": + want["nvPairs"]["PEER2_CONF"] = have["nvPairs"]["PEER2_CONF"] + elif have["nvPairs"].get("PEER2_CONF", "") == "": + # Nothing to merge. Leave want as it is + pass + else: + want["nvPairs"]["PEER2_CONF"] = ( + have["nvPairs"]["PEER2_CONF"] + + "\n" + + want["nvPairs"]["PEER2_CONF"] + ) + else: + # Remove the "peer2_conf_defaulted" from want + want.pop("peer2_conf_defaulted") + + def dcnm_links_update_diff_deploy(self, fabric, device): + + if self.diff_deploy.get(fabric, "") == "": + self.diff_deploy[fabric] = [] + + if device not in self.diff_deploy[fabric]: + self.diff_deploy[fabric].append(device) + + def dcnm_links_get_diff_merge(self): + + """ + Routine to populate a list of payload information in self.diff_create to create/update Links. + + Parameters: + None + + Returns: + None + """ + + if not self.want: + return + + for link in self.want: + + rc, reasons, have = self.dcnm_links_compare_Links(link) + + if rc == "DCNM_LINK_CREATE": + # Link does not exists, create a new one. + if link not in self.diff_create: + self.changed_dict[0]["merged"].append(link) + self.diff_create.append(link) + if rc == "DCNM_LINK_MERGE": + # Link already exists, and needs an update + if link not in self.diff_modify: + # Note down the Link UUID in link. We will need this to update the link + link["link-uuid"] = have["link-uuid"] + self.changed_dict[0]["modified"].append(link) + self.changed_dict[0]["debugs"].append({"REASONS": reasons}) + # Fields like CONF which are a list of commands should be handled differently in this case. + # For existing links, we will have to merge the current list of commands with already existing + # ones in have. For replace, no need to merge them. They must be replaced with what is given. + if self.module.params["state"] == "merged": + # Check if the templates are same. If not dont try to merge want and have, because + # the parameters in want anf have will be different. Since template has changed, go ahead + # and push MODIFY request with the new payload + + if link["templateName"] == have["templateName"]: + self.dcnm_links_merge_want_and_have(link, have) + self.diff_modify.append(link) + + # Check if "deploy" flag is True. If True, deploy the changes. + # NOTE: There is no Link level deploy functionality. Deploy always happens at switch level. + # If "deploy" flag is set to "true", then all pending configurations on the source and + # destination devices will be deployed. + if self.deploy: + self.dcnm_links_update_diff_deploy( + self.fabric, link["sourceDevice"] + ) + self.dcnm_links_update_diff_deploy( + link["destinationFabric"], link["destinationDevice"] + ) + + if self.diff_deploy != {}: + self.changed_dict[0]["deploy"].append( + copy.deepcopy(self.diff_deploy) + ) + + def dcnm_links_get_diff_deleted(self): + + """ + Routine to get a list of payload information that will be used to delete Links. + This routine updates self.diff_delete with payloads that are used to delete Links + from the server. + + Parameters: + None + + Returns: + None + """ + + for link in self.links_info: + + match_links = [ + have + for have in self.have + if ( + (have["sw1-info"]["fabric-name"] == self.fabric) + and (have["sw2-info"]["fabric-name"] == link["dst_fabric"]) + and (have["sw1-info"]["if-name"] == link["src_interface"]) + and (have["sw2-info"]["if-name"] == link["dst_interface"]) + and ( + have["sw1-info"]["sw-serial-number"] + == self.ip_sn[link["src_device"]] + ) + and ( + have["sw2-info"]["sw-serial-number"] + == self.ip_sn[link["dst_device"]] + ) + ) + ] + + for mlink in match_links: + self.diff_delete.append(mlink["link-uuid"]) + self.changed_dict[0]["deleted"].append( + { + "src_fabric": self.fabric, + "dst_fabric": link["dst_fabric"], + "src_interface": link["src_interface"], + "dst_interface": link["dst_interface"], + "src_device": link["src_device"], + "dst_device": link["dst_device"], + "UUID": mlink["link-uuid"], + } + ) + + self.dcnm_links_update_diff_deploy( + self.fabric, self.ip_sn[link["src_device"]] + ) + self.dcnm_links_update_diff_deploy( + link["dst_fabric"], self.ip_sn[link["dst_device"]] + ) + + if self.diff_deploy != {}: + self.changed_dict[0]["deploy"].append( + copy.deepcopy(self.diff_deploy) + ) + + def dcnm_links_get_diff_query(self): + + """ + Routine to get links information based on the playbook configuration. + This routine updates self.result with Links requested for in the playbook if they exist on + the DCNM server. + + Parameters: + None + + Returns: + None + """ + + # 'src_fabric' is always given. Use that to get all links and then filter based on arguments + # included in playbook + + path = self.paths["LINKS_GET_BY_FABRIC"].format(self.fabric) + + resp = dcnm_send(self.module, "GET", path) + + if resp and resp["RETURN_CODE"] == 200 and resp["DATA"]: + if self.links_info == []: + # Get all the links based on the 'path' computed above. + self.result["response"].extend(resp["DATA"]) + + for link in self.links_info: + + match_query = [] + + match_query = [ + rlink + for rlink in resp["DATA"] + if ( + ( + (link["dst_fabric"] == "") + or ( + rlink["sw2-info"]["fabric-name"] + == link["dst_fabric"] + ) + ) + and ( + (link["src_interface"] == "") + or ( + rlink["sw1-info"]["if-name"] + == link["src_interface"] + ) + ) + and ( + (link["dst_interface"] == "") + or ( + rlink["sw2-info"]["if-name"] + == link["dst_interface"] + ) + ) + and ( + (link["src_device"] == "") + or ( + rlink["sw1-info"]["sw-serial-number"] + == self.ip_sn[link["src_device"]] + ) + ) + and ( + (link["dst_device"] == "") + or ( + rlink["sw2-info"]["sw-serial-number"] + == self.ip_sn[link["dst_device"]] + ) + ) + and ( + (link["template"] == "") + or ( + rlink.get("templateName", None) + == link["template"] + ) + ) + ) + ] + + for lelem in match_query: + if lelem not in self.result["response"]: + self.result["response"].append(lelem) + + def dcnm_links_deploy_to_switches(self): + + resp = {} + resp["RETURN_CODE"] = 200 + + for fabric in self.diff_deploy.keys(): + if self.diff_deploy[fabric] != []: + + deploy_path = self.paths["LINKS_CFG_DEPLOY"].format(fabric) + + switches = ",".join(self.diff_deploy[fabric]) + deploy_path = deploy_path + switches + + resp = dcnm_send(self.module, "POST", deploy_path, "") + + if resp and resp["RETURN_CODE"] == 200: + self.result["response"].append(resp) + else: + return resp + return resp + + def dcnm_links_get_switch_sync_status(self): + + retry = False + + for fabric in self.diff_deploy.keys(): + + if self.diff_deploy[fabric] != []: + + path = self.paths["CONFIG_PREVIEW"].format(fabric) + path = ( + path + + ",".join(self.diff_deploy[fabric]) + + "?forceShowRun=false&showBrief=true" + ) + + cp_resp = dcnm_send(self.module, "GET", path, "") + + if cp_resp.get("RETURN_CODE", 0) == 200: + if cp_resp not in self.result["response"]: + self.result["response"].append(cp_resp) + match_data = [ + item + for item in cp_resp.get("DATA", []) + if item["switchId"] in self.diff_deploy[fabric] + ] + else: + cp_resp["CHANGED"] = self.changed_dict[0] + self.module.fail_json(msg=cp_resp) + + retry = False + for item in match_data: + if item["status"].lower() != "in-sync": + retry = True + else: + # remove the sno which is in "in-sync" from snos list of that fabric + self.diff_deploy[fabric].remove(item["switchId"]) + return retry + + def dcnm_links_send_message_to_dcnm(self): + + """ + Routine to push payloads to DCNM server. This routine implements reqquired error checks and retry mechanisms to handle + transient errors. This routine checks self.diff_create, self.diff_delete lists and push appropriate requests to DCNM. + + Parameters: + None + + Returns: + None + """ + + resp = None + create_flag = False + modified_flag = False + delete_flag = False + deploy_flag = False + + for link_uid in self.diff_delete: + + path = self.paths["LINKS_DELETE"] + + del_path = path + link_uid + "?isLogicalLink=false" + + resp = dcnm_send(self.module, "DELETE", del_path) + + if resp != []: + self.result["response"].append(resp) + + if resp and resp.get("RETURN_CODE") != 200: + resp["CHANGED"] = self.changed_dict[0] + self.module.fail_json(msg=resp) + else: + delete_flag = True + + path = self.paths["LINKS_CREATE"] + + for link in self.diff_create: + + json_payload = json.dumps(link) + resp = dcnm_send(self.module, "POST", path, json_payload) + + if resp != []: + self.result["response"].append(resp) + if resp and resp.get("RETURN_CODE") != 200: + resp["CHANGED"] = self.changed_dict[0] + self.module.fail_json(msg=resp) + else: + create_flag = True + + for link in self.diff_modify: + + path = self.paths["LINKS_UPDATE"] + link["link-uuid"] + + json_payload = json.dumps(link) + resp = dcnm_send(self.module, "PUT", path, json_payload) + + if resp != []: + self.result["response"].append(resp) + + if resp and resp.get("RETURN_CODE") != 200: + resp["CHANGED"] = self.changed_dict[0] + self.module.fail_json(msg=resp) + else: + modified_flag = True + + if self.diff_deploy != {}: + + retries = 0 + while retries < 3: + + retry = False + retries += 1 + + resp = self.dcnm_links_deploy_to_switches() + + if resp and (resp["RETURN_CODE"] != 200): + resp["CHANGED"] = self.changed_dict[0] + self.module.fail_json(msg=resp) + else: + deploy_flag = True + + retry = self.dcnm_links_get_switch_sync_status() + + if retry: + time.sleep(1) + else: + break + + self.result["changed"] = ( + create_flag or modified_flag or delete_flag or deploy_flag + ) + + def dcnm_links_update_inventory_data(self): + + """ + Routine to update inventory data for all fabrics included in the playbook. This routine + also updates ip_sn, sn_hn and hn_sn objetcs from the updated inventory data. + + Parameters: + None + + Returns: + None + """ + + processed_fabrics = [] + + if [] is self.config: + return + + # Soure fabric is already processed. Add it to processed list + processed_fabrics.append(self.fabric) + + for cfg in self.config: + + # For every fabric included in the playbook, get the inventory details. This info is required + # to get ip_sn, hn_sn and sn_hn details + if cfg.get("dst_fabric", "") != "": + if cfg["dst_fabric"] not in processed_fabrics: + processed_fabrics.append(cfg["dst_fabric"]) + inv_data = get_fabric_inventory_details( + self.module, cfg["dst_fabric"] + ) + self.inventory_data.update(inv_data) + + # Based on the updated inventory_data, update ip_sn, hn_sn and sn_hn objects + self.ip_sn, self.hn_sn = get_ip_sn_dict(self.inventory_data) + self.sn_hn = dict([(value, key) for key, value in self.hn_sn.items()]) + + def dcnm_links_translate_playbook_info(self, config, ip_sn, hn_sn): + + """ + Routine to translate parameters in playbook if required. + - This routine converts the hostname information included in + playbook to actual addresses. + - translates template names based on version of DCNM + + Parameters: + config - The resource which needs tranlation + ip_sn - IP address to serial number mappings + hn_sn - hostname to serial number mappings + + Returns: + None + """ + + if [] is config: + return + + for cfg in config: + + if cfg.get("src_device", "") != "": + cfg["src_device"] = dcnm_get_ip_addr_info( + self.module, cfg["src_device"], ip_sn, hn_sn + ) + if cfg.get("dst_device", "") != "": + cfg["dst_device"] = dcnm_get_ip_addr_info( + self.module, cfg["dst_device"], ip_sn, hn_sn + ) + + if cfg.get("template", None) is not None: + cfg["template"] = self.templates.get( + cfg["template"], "dcnm_links_invalid_template" + ) + + +def main(): + + """ main entry point for module execution + """ + element_spec = dict( + src_fabric=dict(required=True, type="str"), + config=dict(required=False, type="list", elements="dict", default=[]), + state=dict( + type="str", + default="merged", + choices=["merged", "deleted", "replaced", "query"], + ), + deploy=dict(type="bool", default="true"), + ) + + module = AnsibleModule( + argument_spec=element_spec, supports_check_mode=True + ) + + dcnm_links = DcnmLinks(module) + + state = module.params["state"] + + if [] is dcnm_links.config: + if state == "merged" or state == "replaced": + module.fail_json( + msg="'config' element is mandatory for state '{0}', given = '{1}'".format( + state, dcnm_links.config + ) + ) + + dcnm_links.dcnm_links_update_inventory_data() + + dcnm_links.dcnm_links_translate_playbook_info( + dcnm_links.config, dcnm_links.ip_sn, dcnm_links.hn_sn + ) + + dcnm_links.dcnm_links_validate_input() + + if module.params["state"] != "query": + dcnm_links.dcnm_links_get_want() + dcnm_links.dcnm_links_get_have() + + # self.want would have defaulted all optional objects not included in playbook. But the way + # these objects are handled is different between 'merged' and 'replaced' states. For 'merged' + # state, objects not included in the playbook must be left as they are and for state 'replaced' + # they must be purged or defaulted. + + dcnm_links.dcnm_links_update_want() + + if (module.params["state"] == "merged") or ( + module.params["state"] == "replaced" + ): + dcnm_links.dcnm_links_get_diff_merge() + + if module.params["state"] == "deleted": + dcnm_links.dcnm_links_get_diff_deleted() + + if module.params["state"] == "query": + dcnm_links.dcnm_links_get_diff_query() + + dcnm_links.result["diff"] = dcnm_links.changed_dict + + if dcnm_links.diff_create or dcnm_links.diff_delete: + dcnm_links.result["changed"] = True + + if module.check_mode: + dcnm_links.result["changed"] = False + module.exit_json(**dcnm_links.result) + + dcnm_links.dcnm_links_send_message_to_dcnm() + + module.exit_json(**dcnm_links.result) + + +if __name__ == "__main__": + main() diff --git a/tests/integration/targets/dcnm_links/defaults/main.yaml b/tests/integration/targets/dcnm_links/defaults/main.yaml new file mode 100644 index 000000000..5f709c5aa --- /dev/null +++ b/tests/integration/targets/dcnm_links/defaults/main.yaml @@ -0,0 +1,2 @@ +--- +testcase: "*" diff --git a/tests/integration/targets/dcnm_links/meta/main.yaml b/tests/integration/targets/dcnm_links/meta/main.yaml new file mode 100644 index 000000000..d0f393ae4 --- /dev/null +++ b/tests/integration/targets/dcnm_links/meta/main.yaml @@ -0,0 +1 @@ +dependencies: [prepare_dcnm_links] diff --git a/tests/integration/targets/dcnm_links/tasks/dcnm.yaml b/tests/integration/targets/dcnm_links/tasks/dcnm.yaml new file mode 100644 index 000000000..881b81cb6 --- /dev/null +++ b/tests/integration/targets/dcnm_links/tasks/dcnm.yaml @@ -0,0 +1,20 @@ +--- +- name: collect dcnm test cases + find: + paths: "{{ role_path }}/tests/dcnm" + 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: "{{ test_case_to_run }}" + with_items: "{{ test_items }}" + loop_control: + loop_var: test_case_to_run diff --git a/tests/integration/targets/dcnm_links/tasks/main.yaml b/tests/integration/targets/dcnm_links/tasks/main.yaml new file mode 100644 index 000000000..78c5fb834 --- /dev/null +++ b/tests/integration/targets/dcnm_links/tasks/main.yaml @@ -0,0 +1,2 @@ +--- +- { include: dcnm.yaml, tags: ['dcnm'] } \ No newline at end of file diff --git a/tests/integration/targets/dcnm_links/tests/dcnm/dcnm_links_inter_ipv6_delete.yaml b/tests/integration/targets/dcnm_links/tests/dcnm/dcnm_links_inter_ipv6_delete.yaml new file mode 100644 index 000000000..4bb39fecb --- /dev/null +++ b/tests/integration/targets/dcnm_links/tests/dcnm/dcnm_links_inter_ipv6_delete.yaml @@ -0,0 +1,266 @@ +############################################## +## SETUP ## +############################################## + +- name: Remove local log file + local_action: command rm -f dcnm_links.log + +- block: + +############################################## +## DELETE ## +############################################## + + - name: Initial setup - Delete Links on ipv6 fabric + cisco.dcnm.dcnm_links: &links_delete + state: deleted # choose from [merged, replaced, deleted, query] + src_fabric: "{{ ansible_ipv6_fabric }}" + config: + - dst_fabric: "{{ ansible_unnum_fabric }}" # Destination fabric + src_interface: "{{ intf_1_3 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_3 }}" # Interface on the Destination fabric + src_device: "{{ ansible_ipv6_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_unnum_switch1 }}" # Device on the Destination fabric + + - dst_fabric: "{{ ansible_unnum_fabric }}" # Destination fabric + src_interface: "{{ intf_1_4 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_4 }}" # Interface on the Destination fabric + src_device: "{{ ansible_ipv6_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_unnum_switch1 }}" # Device on the Destination fabric + + - dst_fabric: "{{ ansible_unnum_fabric }}" # Destination fabric + src_interface: "{{ intf_1_5 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_5 }}" # Interface on the Destination fabric + src_device: "{{ ansible_ipv6_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_unnum_switch1 }}" # Device on the Destination fabric + register: result + + - assert: + that: + - 'item["RETURN_CODE"] == 200' + loop: '{{ result.response }}' + +############################################## +## MERGE ## +############################################## + + - name: Create Links including optional parameters + cisco.dcnm.dcnm_links: &links_merge_with_opt1 + state: merged # choose from [merged, replaced, deleted, query] + src_fabric: "{{ ansible_ipv6_fabric }}" + config: + - dst_fabric: "{{ ansible_unnum_fabric }}" # Destination fabric + src_interface: "{{ intf_1_3 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_3 }}" # Interface on the Destination fabric + src_device: "{{ ansible_ipv6_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_unnum_switch1 }}" # Device on the Destination fabric + template: ext_fabric_setup # template to be applied, choose from + # [ ext_fabric_setup, ext_multisite_underlay_setup, + # ext_evpn_multisite_overlay_setup ] + profile: + ipv4_subnet: 193.168.1.1/24 # IP address of interface in src fabric with mask + neighbor_ip: 193.168.1.2 # IP address of the interface in dst fabric + src_asn: "{{ ansible_ipv6_asn }}" # BGP ASN in source fabric + dst_asn: "{{ ansible_unnum_asn }}" # BGP ASN in destination fabric + mtu: 9216 # + auto_deploy: false # optional, default is false + # Flag that controls auto generation of neighbor VRF Lite configuration + peer1_description: "Description of source" # optional, default is "" + peer2_description: "Description of dest" # optional, default is "" + peer1_cmds: # Freeform config for source interface + - no shutdown # optional, default is "" + peer2_cmds: # Freeform config for destination interface + - no shutdown # optional, default is "" + + register: result + + - assert: + that: + - 'result.changed == true' + - '(result["diff"][0]["merged"] | length) == 1' + - '(result["diff"][0]["modified"] | length) == 0' + - '(result["diff"][0]["deleted"] | length) == 0' + - '(result["diff"][0]["query"] | length) == 0' + - '(result["diff"][0]["deploy"][0][ "{{ ansible_unnum_fabric }}" ] | length) == 1' + - '(result["diff"][0]["deploy"][0][ "{{ ansible_ipv6_fabric }}" ] | length) == 1' + + - assert: + that: + - 'item["RETURN_CODE"] == 200' + loop: '{{ result.response }}' + +############################################### +### DELETE ## +############################################### + + - name: Delete Links + cisco.dcnm.dcnm_links: *links_delete + register: result + + - assert: + that: + - 'result.changed == true' + - '(result["diff"][0]["merged"] | length) == 0' + - '(result["diff"][0]["modified"] | length) == 0' + - '(result["diff"][0]["deleted"] | length) == 1' + - '(result["diff"][0]["query"] | length) == 0' + - '(result["diff"][0]["deploy"][0][ "{{ ansible_unnum_fabric }}" ] | length) == 1' + - '(result["diff"][0]["deploy"][0][ "{{ ansible_ipv6_fabric }}" ] | length) == 1' + + - assert: + that: + - 'item["RETURN_CODE"] == 200' + loop: '{{ result.response }}' + +############################################### +### IDEMPOTENCE ## +############################################### + + - name: Delete Links - Idempotence + cisco.dcnm.dcnm_links: *links_delete + register: result + + - assert: + that: + - 'result.changed == false' + - '(result["diff"][0]["merged"] | length) == 0' + - '(result["diff"][0]["modified"] | length) == 0' + - '(result["diff"][0]["deleted"] | length) == 0' + - '(result["diff"][0]["query"] | length) == 0' + - '(result["diff"][0]["deploy"] | length) == 0' + + - assert: + that: + - 'item["RETURN_CODE"] == 200' + loop: '{{ result.response }}' + +############################################## +## MERGE ## +############################################## + + - name: Create Links (multisite) including optional parameters + cisco.dcnm.dcnm_links: &links_merge_with_opt2 + state: merged # choose from [merged, replaced, deleted, query] + src_fabric: "{{ ansible_ipv6_fabric }}" + config: + - dst_fabric: "{{ ansible_unnum_fabric }}" # Destination fabric + src_interface: "{{ intf_1_4 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_4 }}" # Interface on the Destination fabric + src_device: "{{ ansible_ipv6_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_unnum_switch1 }}" # Device on the Destination fabric + template: ext_multisite_underlay_setup # template to be applied, choose from + # [ ext_fabric_setup, ext_multisite_underlay_setup, + # ext_evpn_multisite_overlay_setup ] + profile: + ipv4_subnet: 193.168.2.1/24 # IP address of interface in src fabric with mask + neighbor_ip: 193.168.2.2 # IP address of the interface in dst fabric + src_asn: "{{ ansible_ipv6_asn }}" # BGP ASN in source fabric + dst_asn: "{{ ansible_unnum_asn }}" # BGP ASN in destination fabric + mtu: 9216 # + deploy_dci_tracking: false # optional, default is false + max_paths: 1 # optional, default is 1 + route_tag: 12345 # optional, optional is "" + ebgp_password_enable: true # optional, default is true + ebgp_password: 9BFE3270E19CA112 # optional, required only if ebgp_password_enable flag is true and inherit_from_msd is false + inherit_from_msd: True # optional, required only if ebgp_password_enable flag is true, default is false + ebgp_auth_key_type: 3 # optional, required only if ebpg_password_enable is true and inherit_from_msd + # is false. Default is 3 + # choose from [3 - 3DES, 7 - Cisco ] + peer1_description: "Description of source" # optional, default is "" + peer2_description: "Description of dest" # optional, default is "" + peer1_cmds: # Freeform config for source interface + - no shutdown # optional, default is "" + peer2_cmds: # Freeform config for destination interface + - no shutdown # optional, default is "" + + - dst_fabric: "{{ ansible_unnum_fabric }}" # Destination fabric + src_interface: "{{ intf_1_5 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_5 }}" # Interface on the Destination fabric + src_device: "{{ ansible_ipv6_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_unnum_switch1 }}" # Device on the Destination fabric + template: ext_evpn_multisite_overlay_setup # template to be applied, choose from + # [ ext_fabric_setup, ext_multisite_underlay_setup, + # ext_evpn_multisite_overlay_setup ] + profile: + ipv4_addr: 193.168.3.1 # IP address of interface in src fabric + neighbor_ip: 193.168.3.2 # IP address of the interface in dst fabric + src_asn: "{{ ansible_ipv6_asn }}" # BGP ASN in source fabric + dst_asn: "{{ ansible_unnum_asn }}" # BGP ASN in destination fabric + trm_enabled: false # optional, default is false + bgp_multihop: 5 # optional, default is 5 + ebgp_password_enable: true # optional, default is true + ebgp_password: 9BFE3270E19CA112 # optional, required only if ebgp_password_enable flag is true and inherit_from_msd is false + inherit_from_msd: false # optional, required only if ebgp_password_enable flag is true, default is false + ebpg_auth_key_type: 3 # optional, required only if ebpg_password_enable is true and inherit_from_msd + # is false. Default is 3 + # choose from [3 - 3DES, 7 - Cisco ] + register: result + when: (ansible_ipv6_fabric in ms_fabric_list or ansible_unnum_fabric in ms_fabric_list) + + - assert: + that: + - 'result.changed == true' + - '(result["diff"][0]["merged"] | length) == 2' + - '(result["diff"][0]["modified"] | length) == 0' + - '(result["diff"][0]["deleted"] | length) == 0' + - '(result["diff"][0]["query"] | length) == 0' + - '(result["diff"][0]["deploy"][0][ "{{ ansible_unnum_fabric }}" ] | length) == 1' + - '(result["diff"][0]["deploy"][0][ "{{ ansible_ipv6_fabric }}" ] | length) == 1' + when: (ansible_ipv6_fabric in ms_fabric_list or ansible_unnum_fabric in ms_fabric_list) + + - assert: + that: + - 'item["RETURN_CODE"] == 200' + loop: '{{ result.response }}' + when: (ansible_ipv6_fabric in ms_fabric_list or ansible_unnum_fabric in ms_fabric_list) + +############################################### +### DELETE ## +############################################### + + - name: Delete Links + cisco.dcnm.dcnm_links: *links_delete + register: result + when: (ansible_ipv6_fabric in ms_fabric_list or ansible_unnum_fabric in ms_fabric_list) + + - assert: + that: + - 'result.changed == true' + - '(result["diff"][0]["merged"] | length) == 0' + - '(result["diff"][0]["modified"] | length) == 0' + - '(result["diff"][0]["deleted"] | length) == 2' + - '(result["diff"][0]["query"] | length) == 0' + - '(result["diff"][0]["deploy"][0][ "{{ ansible_unnum_fabric }}" ] | length) == 1' + - '(result["diff"][0]["deploy"][0][ "{{ ansible_ipv6_fabric }}" ] | length) == 1' + when: (ansible_ipv6_fabric in ms_fabric_list or ansible_unnum_fabric in ms_fabric_list) + + - assert: + that: + - 'item["RETURN_CODE"] == 200' + loop: '{{ result.response }}' + when: (ansible_ipv6_fabric in ms_fabric_list or ansible_unnum_fabric in ms_fabric_list) + +############################################### +### IDEMPOTENCE ## +############################################### + + - name: Delete Links - Idempotence + cisco.dcnm.dcnm_links: *links_delete + register: result + when: (ansible_ipv6_fabric in ms_fabric_list or ansible_unnum_fabric in ms_fabric_list) + + - assert: + that: + - 'result.changed == false' + - '(result["diff"][0]["merged"] | length) == 0' + - '(result["diff"][0]["modified"] | length) == 0' + - '(result["diff"][0]["deleted"] | length) == 0' + - '(result["diff"][0]["query"] | length) == 0' + - '(result["diff"][0]["deploy"] | length) == 0' + when: (ansible_ipv6_fabric in ms_fabric_list or ansible_unnum_fabric in ms_fabric_list) + + - assert: + that: + - 'item["RETURN_CODE"] == 200' + loop: '{{ result.response }}' + when: (ansible_ipv6_fabric in ms_fabric_list or ansible_unnum_fabric in ms_fabric_list) diff --git a/tests/integration/targets/dcnm_links/tests/dcnm/dcnm_links_inter_ipv6_merge.yaml b/tests/integration/targets/dcnm_links/tests/dcnm/dcnm_links_inter_ipv6_merge.yaml new file mode 100644 index 000000000..04113094a --- /dev/null +++ b/tests/integration/targets/dcnm_links/tests/dcnm/dcnm_links_inter_ipv6_merge.yaml @@ -0,0 +1,432 @@ +############################################## +## SETUP ## +############################################## + +- name: Remove local log file + local_action: command rm -f dcnm_links.log + +- block: + +############################################## +## DELETE ## +############################################## + + - name: Initial setup - Delete Links on ipv6 fabric + cisco.dcnm.dcnm_links: &links_delete + state: deleted # choose from [merged, replaced, deleted, query] + src_fabric: "{{ ansible_ipv6_fabric }}" + config: + - dst_fabric: "{{ ansible_unnum_fabric }}" # Destination fabric + src_interface: "{{ intf_1_3 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_3 }}" # Interface on the Destination fabric + src_device: "{{ ansible_ipv6_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_unnum_switch1 }}" # Device on the Destination fabric + + - dst_fabric: "{{ ansible_unnum_fabric }}" # Destination fabric + src_interface: "{{ intf_1_4 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_4 }}" # Interface on the Destination fabric + src_device: "{{ ansible_ipv6_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_unnum_switch1 }}" # Device on the Destination fabric + + - dst_fabric: "{{ ansible_unnum_fabric }}" # Destination fabric + src_interface: "{{ intf_1_5 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_5 }}" # Interface on the Destination fabric + src_device: "{{ ansible_ipv6_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_unnum_switch1 }}" # Device on the Destination fabric + + register: result + + - assert: + that: + - 'item["RETURN_CODE"] == 200' + loop: '{{ result.response }}' + +############################################## +## MERGE ## +############################################## + + - name: Create Links without including optional parameters + cisco.dcnm.dcnm_links: &links_merge_no_opt1 + state: merged # choose from [merged, replaced, deleted, query] + src_fabric: "{{ ansible_ipv6_fabric }}" + config: + - dst_fabric: "{{ ansible_unnum_fabric }}" # Destination fabric + src_interface: "{{ intf_1_3 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_3 }}" # Interface on the Destination fabric + src_device: "{{ ansible_ipv6_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_unnum_switch1 }}" # Device on the Destination fabric + template: ext_fabric_setup # template to be applied, choose from + # [ ext_fabric_setup, ext_multisite_underlay_setup, + # ext_evpn_multisite_overlay_setup ] + profile: + ipv4_subnet: 193.168.1.1/24 # IP address of interface in src fabric with mask + neighbor_ip: 193.168.1.2 # IP address of the interface in dst fabric + src_asn: "{{ ansible_ipv6_asn }}" # BGP ASN in source fabric + dst_asn: "{{ ansible_unnum_asn }}" # BGP ASN in destination fabric + + register: result + + - assert: + that: + - 'result.changed == true' + - '(result["diff"][0]["merged"] | length) == 1' + - '(result["diff"][0]["modified"] | length) == 0' + - '(result["diff"][0]["deleted"] | length) == 0' + - '(result["diff"][0]["query"] | length) == 0' + - '(result["diff"][0]["deploy"][0][ "{{ ansible_unnum_fabric }}" ] | length) == 1' + - '(result["diff"][0]["deploy"][0][ "{{ ansible_ipv6_fabric }}" ] | length) == 1' + + - assert: + that: + - 'item["RETURN_CODE"] == 200' + loop: '{{ result.response }}' + +############################################### +### IDEMPOTENCE ## +############################################### + + - name: Create Links - Idempotence + cisco.dcnm.dcnm_links: *links_merge_no_opt1 + register: result + + - assert: + that: + - 'result.changed == true' + - '(result["diff"][0]["merged"] | length) == 0' + - '(result["diff"][0]["modified"] | length) == 0' + - '(result["diff"][0]["deleted"] | length) == 0' + - '(result["diff"][0]["query"] | length) == 0' + - '(result["diff"][0]["deploy"][0][ "{{ ansible_unnum_fabric }}" ] | length) == 1' + - '(result["diff"][0]["deploy"][0][ "{{ ansible_ipv6_fabric }}" ] | length) == 1' + + - assert: + that: + - 'item["RETURN_CODE"] == 200' + loop: '{{ result.response }}' + +############################################### +### DELETE ## +############################################### + + - name: Delete Links + cisco.dcnm.dcnm_links: *links_delete + register: result + + - assert: + that: + - 'result.changed == true' + - '(result["diff"][0]["merged"] | length) == 0' + - '(result["diff"][0]["modified"] | length) == 0' + - '(result["diff"][0]["deleted"] | length) == 1' + - '(result["diff"][0]["query"] | length) == 0' + - '(result["diff"][0]["deploy"][0][ "{{ ansible_unnum_fabric }}" ] | length) == 1' + - '(result["diff"][0]["deploy"][0][ "{{ ansible_ipv6_fabric }}" ] | length) == 1' + + - assert: + that: + - 'item["RETURN_CODE"] == 200' + loop: '{{ result.response }}' + +############################################## +## MERGE ## +############################################## + + - name: Create Links (multisite) without including optional parameters + cisco.dcnm.dcnm_links: &links_merge_no_opt2 + state: merged # choose from [merged, replaced, deleted, query] + src_fabric: "{{ ansible_ipv6_fabric }}" + config: + - dst_fabric: "{{ ansible_unnum_fabric }}" # Destination fabric + src_interface: "{{ intf_1_4 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_4 }}" # Interface on the Destination fabric + src_device: "{{ ansible_ipv6_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_unnum_switch1 }}" # Device on the Destination fabric + template: ext_multisite_underlay_setup # template to be applied, choose from + # [ ext_fabric_setup, ext_multisite_underlay_setup, + # ext_evpn_multisite_overlay_setup ] + profile: + ipv4_subnet: 193.168.2.1/24 # IP address of interface in src fabric with mask + neighbor_ip: 193.168.2.2 # IP address of the interface in dst fabric + src_asn: "{{ ansible_ipv6_asn }}" # BGP ASN in source fabric + dst_asn: "{{ ansible_unnum_asn }}" # BGP ASN in destination fabric + + - dst_fabric: "{{ ansible_unnum_fabric }}" # Destination fabric + src_interface: "{{ intf_1_5 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_5 }}" # Interface on the Destination fabric + src_device: "{{ ansible_ipv6_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_unnum_switch1 }}" # Device on the Destination fabric + template: ext_evpn_multisite_overlay_setup # template to be applied, choose from + # [ ext_fabric_setup, ext_multisite_underlay_setup, + # ext_evpn_multisite_overlay_setup ] + profile: + ipv4_addr: 193.168.3.1 # IP address of interface in src fabric + neighbor_ip: 193.168.3.2 # IP address of the interface in dst fabric + src_asn: "{{ ansible_ipv6_asn }}" # BGP ASN in source fabric + dst_asn: "{{ ansible_unnum_asn }}" # BGP ASN in destination fabric + + register: result + when: (ansible_ipv6_fabric in ms_fabric_list or ansible_unnum_fabric in ms_fabric_list) + + - assert: + that: + - 'result.changed == true' + - '(result["diff"][0]["merged"] | length) == 2' + - '(result["diff"][0]["modified"] | length) == 0' + - '(result["diff"][0]["deleted"] | length) == 0' + - '(result["diff"][0]["query"] | length) == 0' + - '(result["diff"][0]["deploy"][0][ "{{ ansible_unnum_fabric }}" ] | length) == 1' + - '(result["diff"][0]["deploy"][0][ "{{ ansible_ipv6_fabric }}" ] | length) == 1' + when: (ansible_ipv6_fabric in ms_fabric_list or ansible_unnum_fabric in ms_fabric_list) + + - assert: + that: + - 'item["RETURN_CODE"] == 200' + loop: '{{ result.response }}' + when: (ansible_ipv6_fabric in ms_fabric_list or ansible_unnum_fabric in ms_fabric_list) + +############################################### +### IDEMPOTENCE ## +############################################### + + - name: Create Links - Idempotence + cisco.dcnm.dcnm_links: *links_merge_no_opt2 + register: result + when: (ansible_ipv6_fabric in ms_fabric_list or ansible_unnum_fabric in ms_fabric_list) + + - assert: + that: + - 'result.changed == true' + - '(result["diff"][0]["merged"] | length) == 0' + - '(result["diff"][0]["modified"] | length) == 0' + - '(result["diff"][0]["deleted"] | length) == 0' + - '(result["diff"][0]["query"] | length) == 0' + - '(result["diff"][0]["deploy"][0][ "{{ ansible_unnum_fabric }}" ] | length) == 1' + - '(result["diff"][0]["deploy"][0][ "{{ ansible_ipv6_fabric }}" ] | length) == 1' + when: (ansible_ipv6_fabric in ms_fabric_list or ansible_unnum_fabric in ms_fabric_list) + + - assert: + that: + - 'item["RETURN_CODE"] == 200' + loop: '{{ result.response }}' + when: (ansible_ipv6_fabric in ms_fabric_list or ansible_unnum_fabric in ms_fabric_list) + +############################################### +### DELETE ## +############################################### + + - name: Delete Links + cisco.dcnm.dcnm_links: *links_delete + register: result + when: (ansible_ipv6_fabric in ms_fabric_list or ansible_unnum_fabric in ms_fabric_list) + + - assert: + that: + - 'result.changed == true' + - '(result["diff"][0]["merged"] | length) == 0' + - '(result["diff"][0]["modified"] | length) == 0' + - '(result["diff"][0]["deleted"] | length) == 2' + - '(result["diff"][0]["query"] | length) == 0' + - '(result["diff"][0]["deploy"][0][ "{{ ansible_unnum_fabric }}" ] | length) == 1' + - '(result["diff"][0]["deploy"][0][ "{{ ansible_ipv6_fabric }}" ] | length) == 1' + when: (ansible_ipv6_fabric in ms_fabric_list or ansible_unnum_fabric in ms_fabric_list) + + - assert: + that: + - 'item["RETURN_CODE"] == 200' + loop: '{{ result.response }}' + when: (ansible_ipv6_fabric in ms_fabric_list or ansible_unnum_fabric in ms_fabric_list) + +############################################## +## MERGE ## +############################################## + + - name: Create Links including optional parameters + cisco.dcnm.dcnm_links: &links_merge_with_opt1 + state: merged # choose from [merged, replaced, deleted, query] + src_fabric: "{{ ansible_ipv6_fabric }}" + config: + - dst_fabric: "{{ ansible_unnum_fabric }}" # Destination fabric + src_interface: "{{ intf_1_3 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_3 }}" # Interface on the Destination fabric + src_device: "{{ ansible_ipv6_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_unnum_switch1 }}" # Device on the Destination fabric + template: ext_fabric_setup # template to be applied, choose from + # [ ext_fabric_setup, ext_multisite_underlay_setup, + # ext_evpn_multisite_overlay_setup ] + profile: + ipv4_subnet: 193.168.1.1/24 # IP address of interface in src fabric with mask + neighbor_ip: 193.168.1.2 # IP address of the interface in dst fabric + src_asn: "{{ ansible_ipv6_asn }}" # BGP ASN in source fabric + dst_asn: "{{ ansible_unnum_asn }}" # BGP ASN in destination fabric + mtu: 9216 # + auto_deploy: false # optional, default is false + # Flag that controls auto generation of neighbor VRF Lite configuration + peer1_description: "Description of source" # optional, default is "" + peer2_description: "Description of dest" # optional, default is "" + peer1_cmds: # Freeform config for source interface + - no shutdown # optional, default is "" + peer2_cmds: # Freeform config for destination interface + - no shutdown # optional, default is "" + register: result + + - assert: + that: + - 'result.changed == true' + - '(result["diff"][0]["merged"] | length) == 1' + - '(result["diff"][0]["modified"] | length) == 0' + - '(result["diff"][0]["deleted"] | length) == 0' + - '(result["diff"][0]["query"] | length) == 0' + - '(result["diff"][0]["deploy"][0][ "{{ ansible_unnum_fabric }}" ] | length) == 1' + - '(result["diff"][0]["deploy"][0][ "{{ ansible_ipv6_fabric }}" ] | length) == 1' + + - assert: + that: + - 'item["RETURN_CODE"] == 200' + loop: '{{ result.response }}' + +############################################### +### IDEMPOTENCE ## +############################################### + + - name: Create Links - Idempotence + cisco.dcnm.dcnm_links: *links_merge_with_opt1 + register: result + + - assert: + that: + - 'result.changed == true' + - '(result["diff"][0]["merged"] | length) == 0' + - '(result["diff"][0]["modified"] | length) == 0' + - '(result["diff"][0]["deleted"] | length) == 0' + - '(result["diff"][0]["query"] | length) == 0' + - '(result["diff"][0]["deploy"][0][ "{{ ansible_unnum_fabric }}" ] | length) == 1' + - '(result["diff"][0]["deploy"][0][ "{{ ansible_ipv6_fabric }}" ] | length) == 1' + + - assert: + that: + - 'item["RETURN_CODE"] == 200' + loop: '{{ result.response }}' + + +############################################## +## MERGE ## +############################################## + + - name: Create Links (multisite) including optional parameters + cisco.dcnm.dcnm_links: &links_merge_with_opt2 + state: merged # choose from [merged, replaced, deleted, query] + src_fabric: "{{ ansible_ipv6_fabric }}" + config: + - dst_fabric: "{{ ansible_unnum_fabric }}" # Destination fabric + src_interface: "{{ intf_1_4 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_4 }}" # Interface on the Destination fabric + src_device: "{{ ansible_ipv6_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_unnum_switch1 }}" # Device on the Destination fabric + template: ext_multisite_underlay_setup # template to be applied, choose from + # [ ext_fabric_setup, ext_multisite_underlay_setup, + # ext_evpn_multisite_overlay_setup ] + profile: + ipv4_subnet: 193.168.2.1/24 # IP address of interface in src fabric with mask + neighbor_ip: 193.168.2.2 # IP address of the interface in dst fabric + src_asn: "{{ ansible_ipv6_asn }}" # BGP ASN in source fabric + dst_asn: "{{ ansible_unnum_asn }}" # BGP ASN in destination fabric + mtu: 9216 # + deploy_dci_tracking: false # optional, default is false + max_paths: 1 # optional, default is 1 + route_tag: 12345 # optional, optional is "" + ebgp_password_enable: true # optional, default is true + ebgp_password: 9BFE3270E19CA112 # optional, required only if ebgp_password_enable flag is true and inherit_from_msd is false + inherit_from_msd: True # optional, required only if ebgp_password_enable flag is true, default is false + ebgp_auth_key_type: 3 # optional, required only if ebpg_password_enable is true and inherit_from_msd + # is false. Default is 3 + # choose from [3 - 3DES, 7 - Cisco ] + peer1_description: "Description of source" # optional, default is "" + peer2_description: "Description of dest" # optional, default is "" + peer1_cmds: # Freeform config for source interface + - no shutdown # optional, default is "" + peer2_cmds: # Freeform config for destination interface + - no shutdown # optional, default is "" + + - dst_fabric: "{{ ansible_unnum_fabric }}" # Destination fabric + src_interface: "{{ intf_1_5 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_5 }}" # Interface on the Destination fabric + src_device: "{{ ansible_ipv6_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_unnum_switch1 }}" # Device on the Destination fabric + template: ext_evpn_multisite_overlay_setup # template to be applied, choose from + # [ ext_fabric_setup, ext_multisite_underlay_setup, + # ext_evpn_multisite_overlay_setup ] + profile: + ipv4_addr: 193.168.3.1 # IP address of interface in src fabric + neighbor_ip: 193.168.3.2 # IP address of the interface in dst fabric + src_asn: "{{ ansible_ipv6_asn }}" # BGP ASN in source fabric + dst_asn: "{{ ansible_unnum_asn }}" # BGP ASN in destination fabric + trm_enabled: false # optional, default is false + bgp_multihop: 5 # optional, default is 5 + ebgp_password_enable: true # optional, default is true + ebgp_password: 9BFE3270E19CA112 # optional, required only if ebgp_password_enable flag is true and inherit_from_msd is false + inherit_from_msd: false # optional, required only if ebgp_password_enable flag is true, default is false + ebgp_auth_key_type: 3 # optional, required only if ebpg_password_enable is true and inherit_from_msd + # is false. Default is 3 + # choose from [3 - 3DES, 7 - Cisco ] + register: result + when: (ansible_ipv6_fabric in ms_fabric_list or ansible_unnum_fabric in ms_fabric_list) + + - assert: + that: + - 'result.changed == true' + - '(result["diff"][0]["merged"] | length) == 2' + - '(result["diff"][0]["modified"] | length) == 0' + - '(result["diff"][0]["deleted"] | length) == 0' + - '(result["diff"][0]["query"] | length) == 0' + - '(result["diff"][0]["deploy"][0][ "{{ ansible_unnum_fabric }}" ] | length) == 1' + - '(result["diff"][0]["deploy"][0][ "{{ ansible_ipv6_fabric }}" ] | length) == 1' + when: (ansible_ipv6_fabric in ms_fabric_list or ansible_unnum_fabric in ms_fabric_list) + + - assert: + that: + - 'item["RETURN_CODE"] == 200' + loop: '{{ result.response }}' + when: (ansible_ipv6_fabric in ms_fabric_list or ansible_unnum_fabric in ms_fabric_list) + +############################################### +### IDEMPOTENCE ## +############################################### + + - name: Create Links - Idempotence + cisco.dcnm.dcnm_links: *links_merge_with_opt2 + register: result + when: (ansible_ipv6_fabric in ms_fabric_list or ansible_unnum_fabric in ms_fabric_list) + + - assert: + that: + - 'result.changed == true' + - '(result["diff"][0]["merged"] | length) == 0' + - '(result["diff"][0]["modified"] | length) == 0' + - '(result["diff"][0]["deleted"] | length) == 0' + - '(result["diff"][0]["query"] | length) == 0' + - '(result["diff"][0]["deploy"][0][ "{{ ansible_unnum_fabric }}" ] | length) == 1' + - '(result["diff"][0]["deploy"][0][ "{{ ansible_ipv6_fabric }}" ] | length) == 1' + when: (ansible_ipv6_fabric in ms_fabric_list or ansible_unnum_fabric in ms_fabric_list) + + - assert: + that: + - 'item["RETURN_CODE"] == 200' + loop: '{{ result.response }}' + when: (ansible_ipv6_fabric in ms_fabric_list or ansible_unnum_fabric in ms_fabric_list) + +############################################## +## CLEANUP ## +############################################## + + always: + + - name: Cleanup - Delete Links + cisco.dcnm.dcnm_links: *links_delete + register: result + when: IT_CONTEXT is not defined + + - assert: + that: + - 'item["RETURN_CODE"] == 200' + loop: '{{ result.response }}' + when: IT_CONTEXT is not defined diff --git a/tests/integration/targets/dcnm_links/tests/dcnm/dcnm_links_inter_ipv6_modify.yaml b/tests/integration/targets/dcnm_links/tests/dcnm/dcnm_links_inter_ipv6_modify.yaml new file mode 100644 index 000000000..44d6b773e --- /dev/null +++ b/tests/integration/targets/dcnm_links/tests/dcnm/dcnm_links_inter_ipv6_modify.yaml @@ -0,0 +1,311 @@ +############################################## +## SETUP ## +############################################## + +- name: Remove local log file + local_action: command rm -f dcnm_links.log + +- block: + +############################################## +## DELETE ## +############################################## + + - name: Initial setup - Delete Links on ipv6 fabric + cisco.dcnm.dcnm_links: &links_delete + state: deleted # choose from [merged, replaced, deleted, query] + src_fabric: "{{ ansible_ipv6_fabric }}" + config: + - dst_fabric: "{{ ansible_unnum_fabric }}" # Destination fabric + src_interface: "{{ intf_1_3 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_3 }}" # Interface on the Destination fabric + src_device: "{{ ansible_ipv6_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_unnum_switch1 }}" # Device on the Destination fabric + + - dst_fabric: "{{ ansible_unnum_fabric }}" # Destination fabric + src_interface: "{{ intf_1_4 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_4 }}" # Interface on the Destination fabric + src_device: "{{ ansible_ipv6_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_unnum_switch1 }}" # Device on the Destination fabric + + - dst_fabric: "{{ ansible_unnum_fabric }}" # Destination fabric + src_interface: "{{ intf_1_5 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_5 }}" # Interface on the Destination fabric + src_device: "{{ ansible_ipv6_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_unnum_switch1 }}" # Device on the Destination fabric + register: result + + - assert: + that: + - 'item["RETURN_CODE"] == 200' + loop: '{{ result.response }}' + +############################################## +## MERGE ## +############################################## + + - name: Create Links including optional parameters + cisco.dcnm.dcnm_links: &links_merge_with_opt1 + state: merged # choose from [merged, replaced, deleted, query] + src_fabric: "{{ ansible_ipv6_fabric }}" + config: + - dst_fabric: "{{ ansible_unnum_fabric }}" # Destination fabric + src_interface: "{{ intf_1_3 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_3 }}" # Interface on the Destination fabric + src_device: "{{ ansible_ipv6_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_unnum_switch1 }}" # Device on the Destination fabric + template: ext_fabric_setup # template to be applied, choose from + # [ ext_fabric_setup, ext_multisite_underlay_setup, + # ext_evpn_multisite_overlay_setup ] + profile: + ipv4_subnet: 193.168.1.1/24 # IP address of interface in src fabric with mask + neighbor_ip: 193.168.1.2 # IP address of the interface in dst fabric + src_asn: "{{ ansible_ipv6_asn }}" # BGP ASN in source fabric + dst_asn: "{{ ansible_unnum_asn }}" # BGP ASN in destination fabric + mtu: 9216 # + auto_deploy: false # optional, default is false + # Flag that controls auto generation of neighbor VRF Lite configuration + peer1_description: "Description of source" # optional, default is "" + peer2_description: "Description of dest" # optional, default is "" + peer1_cmds: # Freeform config for source interface + - no shutdown # optional, default is "" + peer2_cmds: # Freeform config for destination interface + - no shutdown # optional, default is "" + register: result + + - assert: + that: + - 'result.changed == true' + - '(result["diff"][0]["merged"] | length) == 1' + - '(result["diff"][0]["modified"] | length) == 0' + - '(result["diff"][0]["deleted"] | length) == 0' + - '(result["diff"][0]["query"] | length) == 0' + - '(result["diff"][0]["deploy"][0][ "{{ ansible_unnum_fabric }}" ] | length) == 1' + - '(result["diff"][0]["deploy"][0][ "{{ ansible_ipv6_fabric }}" ] | length) == 1' + + - assert: + that: + - 'item["RETURN_CODE"] == 200' + loop: '{{ result.response }}' + + +############################################## +## MERGE ## +############################################## + + - name: Create Links (multisite) including optional parameters + cisco.dcnm.dcnm_links: &links_merge_with_opt2 + state: merged # choose from [merged, replaced, deleted, query] + src_fabric: "{{ ansible_ipv6_fabric }}" + config: + - dst_fabric: "{{ ansible_unnum_fabric }}" # Destination fabric + src_interface: "{{ intf_1_4 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_4 }}" # Interface on the Destination fabric + src_device: "{{ ansible_ipv6_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_unnum_switch1 }}" # Device on the Destination fabric + template: ext_multisite_underlay_setup # template to be applied, choose from + # [ ext_fabric_setup, ext_multisite_underlay_setup, + # ext_evpn_multisite_overlay_setup ] + profile: + ipv4_subnet: 193.168.2.1/24 # IP address of interface in src fabric with mask + neighbor_ip: 193.168.2.2 # IP address of the interface in dst fabric + src_asn: "{{ ansible_ipv6_asn }}" # BGP ASN in source fabric + dst_asn: "{{ ansible_unnum_asn }}" # BGP ASN in destination fabric + mtu: 9216 # + deploy_dci_tracking: false # optional, default is false + max_paths: 1 # optional, default is 1 + route_tag: 12345 # optional, optional is "" + ebgp_password_enable: true # optional, default is true + ebgp_password: 9BFE3270E19CA112 # optional, required only if ebgp_password_enable flag is true and inherit_from_msd is false + inherit_from_msd: True # optional, required only if ebgp_password_enable flag is true, default is false + ebgp_auth_key_type: 3 # optional, required only if ebpg_password_enable is true and inherit_from_msd + # is false. Default is 3 + # choose from [3 - 3DES, 7 - Cisco ] + peer1_description: "Description of source" # optional, default is "" + peer2_description: "Description of dest" # optional, default is "" + peer1_cmds: # Freeform config for source interface + - no shutdown # optional, default is "" + peer2_cmds: # Freeform config for destination interface + - no shutdown # optional, default is "" + + - dst_fabric: "{{ ansible_unnum_fabric }}" # Destination fabric + src_interface: "{{ intf_1_5 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_5 }}" # Interface on the Destination fabric + src_device: "{{ ansible_ipv6_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_unnum_switch1 }}" # Device on the Destination fabric + template: ext_evpn_multisite_overlay_setup # template to be applied, choose from + # [ ext_fabric_setup, ext_multisite_underlay_setup, + # ext_evpn_multisite_overlay_setup ] + profile: + ipv4_addr: 193.168.3.1 # IP address of interface in src fabric + neighbor_ip: 193.168.3.2 # IP address of the interface in dst fabric + src_asn: "{{ ansible_ipv6_asn }}" # BGP ASN in source fabric + dst_asn: "{{ ansible_unnum_asn }}" # BGP ASN in destination fabric + trm_enabled: false # optional, default is false + bgp_multihop: 5 # optional, default is 5 + ebgp_password_enable: true # optional, default is true + ebgp_password: 9BFE3270E19CA112 # optional, required only if ebgp_password_enable flag is true and inherit_from_msd is false + inherit_from_msd: false # optional, required only if ebgp_password_enable flag is true, default is false + ebgp_auth_key_type: 3 # optional, required only if ebpg_password_enable is true and inherit_from_msd + # is false. Default is 3 + # choose from [3 - 3DES, 7 - Cisco ] + register: result + when: (ansible_ipv6_fabric in ms_fabric_list or ansible_unnum_fabric in ms_fabric_list) + + - assert: + that: + - 'result.changed == true' + - '(result["diff"][0]["merged"] | length) == 2' + - '(result["diff"][0]["modified"] | length) == 0' + - '(result["diff"][0]["deleted"] | length) == 0' + - '(result["diff"][0]["query"] | length) == 0' + - '(result["diff"][0]["deploy"][0][ "{{ ansible_unnum_fabric }}" ] | length) == 1' + - '(result["diff"][0]["deploy"][0][ "{{ ansible_ipv6_fabric }}" ] | length) == 1' + when: (ansible_ipv6_fabric in ms_fabric_list or ansible_unnum_fabric in ms_fabric_list) + + - assert: + that: + - 'item["RETURN_CODE"] == 200' + loop: '{{ result.response }}' + when: (ansible_ipv6_fabric in ms_fabric_list or ansible_unnum_fabric in ms_fabric_list) + +############################################## +## MODIFY ## +############################################## + + - name: Merge modified info into existing links + cisco.dcnm.dcnm_links: &links_modify1 + state: merged # choose from [merged, replaced, deleted, query] + src_fabric: "{{ ansible_ipv6_fabric }}" + config: + - dst_fabric: "{{ ansible_unnum_fabric }}" # Destination fabric + src_interface: "{{ intf_1_3 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_3 }}" # Interface on the Destination fabric + src_device: "{{ ansible_ipv6_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_unnum_switch1 }}" # Device on the Destination fabric + template: ext_fabric_setup # template to be applied, choose from + # [ ext_fabric_setup, ext_multisite_underlay_setup, + # ext_evpn_multisite_overlay_setup ] + profile: + ipv4_subnet: 193.168.10.1/24 # IP address of interface in src fabric with mask + neighbor_ip: 193.168.10.2 # IP address of the interface in dst fabric + src_asn: "{{ ansible_ipv6_asn }}" # BGP ASN in source fabric + dst_asn: "{{ ansible_unnum_asn }}" # BGP ASN in destination fabric + mtu: 1216 # + auto_deploy: true # optional, default is false + # Flag that controls auto generation of neighbor VRF Lite configuration + peer1_description: "Description of source - MOD" # optional, default is "" + peer2_description: "Description of dest - MOD" # optional, default is "" + peer1_cmds: # Freeform config for source interface + - cdp enable # optional, default is "" + peer2_cmds: # Freeform config for destination interface + - cdp enable # optional, default is "" + register: result + + - assert: + that: + - 'result.changed == true' + - '(result["diff"][0]["merged"] | length) == 0' + - '(result["diff"][0]["modified"] | length) == 1' + - '(result["diff"][0]["deleted"] | length) == 0' + - '(result["diff"][0]["query"] | length) == 0' + - '(result["diff"][0]["deploy"][0][ "{{ ansible_unnum_fabric }}" ] | length) == 1' + - '(result["diff"][0]["deploy"][0][ "{{ ansible_ipv6_fabric }}" ] | length) == 1' + + - assert: + that: + - 'item["RETURN_CODE"] == 200' + loop: '{{ result.response }}' + +############################################## +## MERGE ## +############################################## + + - name: Merge (multisite) modified info into existing links + cisco.dcnm.dcnm_links: &links_modify2 + state: merged # choose from [merged, replaced, deleted, query] + src_fabric: "{{ ansible_ipv6_fabric }}" + config: + - dst_fabric: "{{ ansible_unnum_fabric }}" # Destination fabric + src_interface: "{{ intf_1_4 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_4 }}" # Interface on the Destination fabric + src_device: "{{ ansible_ipv6_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_unnum_switch1 }}" # Device on the Destination fabric + template: ext_multisite_underlay_setup # template to be applied, choose from + # [ ext_fabric_setup, ext_multisite_underlay_setup, + # ext_evpn_multisite_overlay_setup ] + profile: + ipv4_subnet: 193.168.20.1/24 # IP address of interface in src fabric with mask + neighbor_ip: 193.168.20.2 # IP address of the interface in dst fabric + src_asn: "{{ ansible_ipv6_asn }}" # BGP ASN in source fabric + dst_asn: "{{ ansible_unnum_asn }}" # BGP ASN in destination fabric + mtu: 2216 # + deploy_dci_tracking: true # optional, default is false + max_paths: 2 # optional, default is 1 + route_tag: 11111 # optional, optional is "" + ebgp_password_enable: false # optional, default is true + ebgp_password: 28E71E338DA17111 # optional, required only if ebgp_password_enable flag is true and inherit_from_msd is false + inherit_from_msd: False # optional, required only if ebgp_password_enable flag is true, default is false + ebgp_auth_key_type: 7 # optional, required only if ebpg_password_enable is true and inherit_from_msd + # is false. Default is 3 + # choose from [3 - 3DES, 7 - Cisco ] + peer1_description: "Description of source - MOD" # optional, default is "" + peer2_description: "Description of dest - MOD" # optional, default is "" + + - dst_fabric: "{{ ansible_unnum_fabric }}" # Destination fabric + src_interface: "{{ intf_1_5 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_5 }}" # Interface on the Destination fabric + src_device: "{{ ansible_ipv6_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_unnum_switch1 }}" # Device on the Destination fabric + template: ext_evpn_multisite_overlay_setup # template to be applied, choose from + # [ ext_fabric_setup, ext_multisite_underlay_setup, + # ext_evpn_multisite_overlay_setup ] + profile: + ipv4_addr: 193.168.30.1 # IP address of interface in src fabric + neighbor_ip: 193.168.30.2 # IP address of the interface in dst fabric + src_asn: "{{ ansible_ipv6_asn }}" # BGP ASN in source fabric + dst_asn: "{{ ansible_unnum_asn }}" # BGP ASN in destination fabric + trm_enabled: true # optional, default is false + bgp_multihop: 3 # optional, default is 5 + ebgp_password_enable: false # optional, default is true + ebgp_password: 8F8F790E1CB7AF60 # optional, required only if ebgp_password_enable flag is true and inherit_from_msd is false + inherit_from_msd: true # optional, required only if ebgp_password_enable flag is true, default is false + ebgp_auth_key_type: 7 # optional, required only if ebpg_password_enable is true and inherit_from_msd + # is false. Default is 3 + # choose from [3 - 3DES, 7 - Cisco ] + register: result + when: (ansible_ipv6_fabric in ms_fabric_list or ansible_unnum_fabric in ms_fabric_list) + + - assert: + that: + - 'result.changed == true' + - '(result["diff"][0]["merged"] | length) == 0' + - '(result["diff"][0]["modified"] | length) == 2' + - '(result["diff"][0]["deleted"] | length) == 0' + - '(result["diff"][0]["query"] | length) == 0' + - '(result["diff"][0]["deploy"][0][ "{{ ansible_unnum_fabric }}" ] | length) == 1' + - '(result["diff"][0]["deploy"][0][ "{{ ansible_ipv6_fabric }}" ] | length) == 1' + when: (ansible_ipv6_fabric in ms_fabric_list or ansible_unnum_fabric in ms_fabric_list) + + - assert: + that: + - 'item["RETURN_CODE"] == 200' + loop: '{{ result.response }}' + when: (ansible_ipv6_fabric in ms_fabric_list or ansible_unnum_fabric in ms_fabric_list) + +############################################## +## CLEANUP ## +############################################## + + always: + + - name: Cleanup - Delete Links + cisco.dcnm.dcnm_links: *links_delete + register: result + when: IT_CONTEXT is not defined + + - assert: + that: + - 'item["RETURN_CODE"] == 200' + loop: '{{ result.response }}' + when: IT_CONTEXT is not defined diff --git a/tests/integration/targets/dcnm_links/tests/dcnm/dcnm_links_inter_ipv6_replace.yaml b/tests/integration/targets/dcnm_links/tests/dcnm/dcnm_links_inter_ipv6_replace.yaml new file mode 100644 index 000000000..e891d4bf4 --- /dev/null +++ b/tests/integration/targets/dcnm_links/tests/dcnm/dcnm_links_inter_ipv6_replace.yaml @@ -0,0 +1,361 @@ +############################################## +## SETUP ## +############################################## + +- name: Remove local log file + local_action: command rm -f dcnm_links.log + +- block: + +############################################## +## DELETE ## +############################################## + + - name: Initial setup - Delete Links on ipv6 fabric + cisco.dcnm.dcnm_links: &links_delete + state: deleted # choose from [merged, replaced, deleted, query] + src_fabric: "{{ ansible_ipv6_fabric }}" + config: + - dst_fabric: "{{ ansible_unnum_fabric }}" # Destination fabric + src_interface: "{{ intf_1_3 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_3 }}" # Interface on the Destination fabric + src_device: "{{ ansible_ipv6_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_unnum_switch1 }}" # Device on the Destination fabric + + - dst_fabric: "{{ ansible_unnum_fabric }}" # Destination fabric + src_interface: "{{ intf_1_4 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_4 }}" # Interface on the Destination fabric + src_device: "{{ ansible_ipv6_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_unnum_switch1 }}" # Device on the Destination fabric + + - dst_fabric: "{{ ansible_unnum_fabric }}" # Destination fabric + src_interface: "{{ intf_1_5 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_5 }}" # Interface on the Destination fabric + src_device: "{{ ansible_ipv6_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_unnum_switch1 }}" # Device on the Destination fabric + register: result + + - assert: + that: + - 'item["RETURN_CODE"] == 200' + loop: '{{ result.response }}' + +############################################## +## MERGE ## +############################################## + + - name: Create Links including optional parameters + cisco.dcnm.dcnm_links: &links_merge_with_opt1 + state: merged # choose from [merged, replaced, deleted, query] + src_fabric: "{{ ansible_ipv6_fabric }}" + config: + - dst_fabric: "{{ ansible_unnum_fabric }}" # Destination fabric + src_interface: "{{ intf_1_3 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_3 }}" # Interface on the Destination fabric + src_device: "{{ ansible_ipv6_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_unnum_switch1 }}" # Device on the Destination fabric + template: ext_fabric_setup # template to be applied, choose from + # [ ext_fabric_setup, ext_multisite_underlay_setup, + # ext_evpn_multisite_overlay_setup ] + profile: + ipv4_subnet: 193.168.1.1/24 # IP address of interface in src fabric with mask + neighbor_ip: 193.168.1.2 # IP address of the interface in dst fabric + src_asn: "{{ ansible_ipv6_asn }}" # BGP ASN in source fabric + dst_asn: "{{ ansible_unnum_asn }}" # BGP ASN in destination fabric + mtu: 9216 # + auto_deploy: false # optional, default is false + # Flag that controls auto generation of neighbor VRF Lite configuration + peer1_description: "Description of source" # optional, default is "" + peer2_description: "Description of dest" # optional, default is "" + peer1_cmds: # Freeform config for source interface + - no shutdown # optional, default is "" + peer2_cmds: # Freeform config for destination interface + - no shutdown # optional, default is "" + register: result + + - assert: + that: + - 'result.changed == true' + - '(result["diff"][0]["merged"] | length) == 1' + - '(result["diff"][0]["modified"] | length) == 0' + - '(result["diff"][0]["deleted"] | length) == 0' + - '(result["diff"][0]["query"] | length) == 0' + - '(result["diff"][0]["deploy"][0][ "{{ ansible_unnum_fabric }}" ] | length) == 1' + - '(result["diff"][0]["deploy"][0][ "{{ ansible_ipv6_fabric }}" ] | length) == 1' + + - assert: + that: + - 'item["RETURN_CODE"] == 200' + loop: '{{ result.response }}' + + +############################################## +## MERGE ## +############################################## + + - name: Create Links (multisite) including optional parameters + cisco.dcnm.dcnm_links: &links_merge_with_opt2 + state: merged # choose from [merged, replaced, deleted, query] + src_fabric: "{{ ansible_ipv6_fabric }}" + config: + - dst_fabric: "{{ ansible_unnum_fabric }}" # Destination fabric + src_interface: "{{ intf_1_4 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_4 }}" # Interface on the Destination fabric + src_device: "{{ ansible_ipv6_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_unnum_switch1 }}" # Device on the Destination fabric + template: ext_multisite_underlay_setup # template to be applied, choose from + # [ ext_fabric_setup, ext_multisite_underlay_setup, + # ext_evpn_multisite_overlay_setup ] + profile: + ipv4_subnet: 193.168.2.1/24 # IP address of interface in src fabric with mask + neighbor_ip: 193.168.2.2 # IP address of the interface in dst fabric + src_asn: "{{ ansible_ipv6_asn }}" # BGP ASN in source fabric + dst_asn: "{{ ansible_unnum_asn }}" # BGP ASN in destination fabric + mtu: 9216 # + deploy_dci_tracking: false # optional, default is false + max_paths: 1 # optional, default is 1 + route_tag: 12345 # optional, optional is "" + ebgp_password_enable: true # optional, default is true + ebgp_password: 9BFE3270E19CA112 # optional, required only if ebgp_password_enable flag is true and inherit_from_msd is false + inherit_from_msd: True # optional, required only if ebgp_password_enable flag is true, default is false + ebgp_auth_key_type: 3 # optional, required only if ebpg_password_enable is true and inherit_from_msd + # is false. Default is 3 + # choose from [3 - 3DES, 7 - Cisco ] + peer1_description: "Description of source" # optional, default is "" + peer2_description: "Description of dest" # optional, default is "" + peer1_cmds: # Freeform config for source interface + - no shutdown # optional, default is "" + peer2_cmds: # Freeform config for destination interface + - no shutdown # optional, default is "" + + - dst_fabric: "{{ ansible_unnum_fabric }}" # Destination fabric + src_interface: "{{ intf_1_5 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_5 }}" # Interface on the Destination fabric + src_device: "{{ ansible_ipv6_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_unnum_switch1 }}" # Device on the Destination fabric + template: ext_evpn_multisite_overlay_setup # template to be applied, choose from + # [ ext_fabric_setup, ext_multisite_underlay_setup, + # ext_evpn_multisite_overlay_setup ] + profile: + ipv4_addr: 193.168.3.1 # IP address of interface in src fabric + neighbor_ip: 193.168.3.2 # IP address of the interface in dst fabric + src_asn: "{{ ansible_ipv6_asn }}" # BGP ASN in source fabric + dst_asn: "{{ ansible_unnum_asn }}" # BGP ASN in destination fabric + trm_enabled: false # optional, default is false + bgp_multihop: 5 # optional, default is 5 + ebgp_password_enable: true # optional, default is true + ebgp_password: 9BFE3270E19CA112 # optional, required only if ebgp_password_enable flag is true and inherit_from_msd is false + inherit_from_msd: false # optional, required only if ebgp_password_enable flag is true, default is false + ebgp_auth_key_type: 3 # optional, required only if ebpg_password_enable is true and inherit_from_msd + # is false. Default is 3 + # choose from [3 - 3DES, 7 - Cisco ] + register: result + when: (ansible_ipv6_fabric in ms_fabric_list or ansible_unnum_fabric in ms_fabric_list) + + - assert: + that: + - 'result.changed == true' + - '(result["diff"][0]["merged"] | length) == 2' + - '(result["diff"][0]["modified"] | length) == 0' + - '(result["diff"][0]["deleted"] | length) == 0' + - '(result["diff"][0]["query"] | length) == 0' + - '(result["diff"][0]["deploy"][0][ "{{ ansible_unnum_fabric }}" ] | length) == 1' + - '(result["diff"][0]["deploy"][0][ "{{ ansible_ipv6_fabric }}" ] | length) == 1' + when: (ansible_ipv6_fabric in ms_fabric_list or ansible_unnum_fabric in ms_fabric_list) + + - assert: + that: + - 'item["RETURN_CODE"] == 200' + loop: '{{ result.response }}' + when: (ansible_ipv6_fabric in ms_fabric_list or ansible_unnum_fabric in ms_fabric_list) + +############################################## +## REPLACE ## +############################################## + + - name: Replace existing links + cisco.dcnm.dcnm_links: &links_replace1 + state: replaced # choose from [merged, replaced, deleted, query] + src_fabric: "{{ ansible_ipv6_fabric }}" + config: + - dst_fabric: "{{ ansible_unnum_fabric }}" # Destination fabric + src_interface: "{{ intf_1_3 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_3 }}" # Interface on the Destination fabric + src_device: "{{ ansible_ipv6_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_unnum_switch1 }}" # Device on the Destination fabric + template: ext_fabric_setup # template to be applied, choose from + # [ ext_fabric_setup, ext_multisite_underlay_setup, + # ext_evpn_multisite_overlay_setup ] + profile: + ipv4_subnet: 193.168.10.1/24 # IP address of interface in src fabric with mask + neighbor_ip: 193.168.10.2 # IP address of the interface in dst fabric + src_asn: "{{ ansible_ipv6_asn }}" # BGP ASN in source fabric + dst_asn: "{{ ansible_unnum_asn }}" # BGP ASN in destination fabric + mtu: 1216 # + auto_deploy: true # optional, default is false + # Flag that controls auto generation of neighbor VRF Lite configuration + peer1_description: "Description of source - REP" # optional, default is "" + peer2_description: "Description of dest - REP" # optional, default is "" + peer1_cmds: # Freeform config for source interface + - cdp enable # optional, default is "" + peer2_cmds: # Freeform config for destination interface + - cdp enable # optional, default is "" + + register: result + + - assert: + that: + - 'result.changed == true' + - '(result["diff"][0]["merged"] | length) == 0' + - '(result["diff"][0]["modified"] | length) == 1' + - '(result["diff"][0]["deleted"] | length) == 0' + - '(result["diff"][0]["query"] | length) == 0' + - '(result["diff"][0]["deploy"][0][ "{{ ansible_ipv6_fabric }}" ] | length) == 1' + - '(result["diff"][0]["deploy"][0][ "{{ ansible_unnum_fabric }}" ] | length) == 1' + + - assert: + that: + - 'item["RETURN_CODE"] == 200' + loop: '{{ result.response }}' + +############################################## +## IDEMPOTENCE ## +############################################## + + - name: Replace Links - Idempotence + cisco.dcnm.dcnm_links: *links_replace1 + register: result + + - assert: + that: + - 'result.changed == true' + - '(result["diff"][0]["merged"] | length) == 0' + - '(result["diff"][0]["modified"] | length) == 0' + - '(result["diff"][0]["deleted"] | length) == 0' + - '(result["diff"][0]["query"] | length) == 0' + - '(result["diff"][0]["deploy"][0][ "{{ ansible_ipv6_fabric }}" ] | length) == 1' + - '(result["diff"][0]["deploy"][0][ "{{ ansible_unnum_fabric }}" ] | length) == 1' + + - assert: + that: + - 'item["RETURN_CODE"] == 200' + loop: '{{ result.response }}' + +############################################## +## REPLACE ## +############################################## + + - name: Replace (multisite) existing links + cisco.dcnm.dcnm_links: &links_replace2 + state: replaced # choose from [merged, replaced, deleted, query] + src_fabric: "{{ ansible_ipv6_fabric }}" + config: + - dst_fabric: "{{ ansible_unnum_fabric }}" # Destination fabric + src_interface: "{{ intf_1_4 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_4 }}" # Interface on the Destination fabric + src_device: "{{ ansible_ipv6_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_unnum_switch1 }}" # Device on the Destination fabric + template: ext_multisite_underlay_setup # template to be applied, choose from + # [ ext_fabric_setup, ext_multisite_underlay_setup, + # ext_evpn_multisite_overlay_setup ] + profile: + ipv4_subnet: 193.168.20.1/24 # IP address of interface in src fabric with mask + neighbor_ip: 193.168.20.2 # IP address of the interface in dst fabric + src_asn: "{{ ansible_ipv6_asn }}" # BGP ASN in source fabric + dst_asn: "{{ ansible_unnum_asn }}" # BGP ASN in destination fabric + mtu: 2216 # + deploy_dci_tracking: true # optional, default is false + max_paths: 2 # optional, default is 1 + route_tag: 11111 # optional, optional is "" + ebgp_password_enable: false # optional, default is true + ebgp_password: 28E71E338DA17111 # optional, required only if ebgp_password_enable flag is true and inherit_from_msd is false + inherit_from_msd: False # optional, required only if ebgp_password_enable flag is true, default is false + ebgp_auth_key_type: 7 # optional, required only if ebpg_password_enable is true and inherit_from_msd + # is false. Default is 3 + # choose from [3 - 3DES, 7 - Cisco ] + peer1_description: "Description of source - REP" # optional, default is "" + peer2_description: "Description of dest - REP" # optional, default is "" + + - dst_fabric: "{{ ansible_unnum_fabric }}" # Destination fabric + src_interface: "{{ intf_1_5 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_5 }}" # Interface on the Destination fabric + src_device: "{{ ansible_ipv6_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_unnum_switch1 }}" # Device on the Destination fabric + template: ext_evpn_multisite_overlay_setup # template to be applied, choose from + # [ ext_fabric_setup, ext_multisite_underlay_setup, + # ext_evpn_multisite_overlay_setup ] + profile: + ipv4_addr: 193.168.30.1 # IP address of interface in src fabric + neighbor_ip: 193.168.30.2 # IP address of the interface in dst fabric + src_asn: "{{ ansible_ipv6_asn }}" # BGP ASN in source fabric + dst_asn: "{{ ansible_unnum_asn }}" # BGP ASN in destination fabric + trm_enabled: true # optional, default is false + bgp_multihop: 3 # optional, default is 5 + ebgp_password_enable: false # optional, default is true + ebgp_password: 8F8F790E1CB7AF60 # optional, required only if ebgp_password_enable flag is true and inherit_from_msd is false + inherit_from_msd: true # optional, required only if ebgp_password_enable flag is true, default is false + ebgp_auth_key_type: 7 # optional, required only if ebpg_password_enable is true and inherit_from_msd + # is false. Default is 3 + # choose from [3 - 3DES, 7 - Cisco ] + register: result + when: (ansible_ipv6_fabric in ms_fabric_list or ansible_unnum_fabric in ms_fabric_list) + + - assert: + that: + - 'result.changed == true' + - '(result["diff"][0]["merged"] | length) == 0' + - '(result["diff"][0]["modified"] | length) == 2' + - '(result["diff"][0]["deleted"] | length) == 0' + - '(result["diff"][0]["query"] | length) == 0' + - '(result["diff"][0]["deploy"][0][ "{{ ansible_ipv6_fabric }}" ] | length) == 1' + - '(result["diff"][0]["deploy"][0][ "{{ ansible_unnum_fabric }}" ] | length) == 1' + when: (ansible_ipv6_fabric in ms_fabric_list or ansible_unnum_fabric in ms_fabric_list) + + - assert: + that: + - 'item["RETURN_CODE"] == 200' + loop: '{{ result.response }}' + when: (ansible_ipv6_fabric in ms_fabric_list or ansible_unnum_fabric in ms_fabric_list) + +############################################## +## IDEMPOTENCE ## +############################################## + + - name: Replace (multisite) Links - Idempotence + cisco.dcnm.dcnm_links: *links_replace2 + register: result + when: (ansible_ipv6_fabric in ms_fabric_list or ansible_unnum_fabric in ms_fabric_list) + + - assert: + that: + - 'result.changed == true' + - '(result["diff"][0]["merged"] | length) == 0' + - '(result["diff"][0]["modified"] | length) == 0' + - '(result["diff"][0]["deleted"] | length) == 0' + - '(result["diff"][0]["query"] | length) == 0' + - '(result["diff"][0]["deploy"][0][ "{{ ansible_ipv6_fabric }}" ] | length) == 1' + - '(result["diff"][0]["deploy"][0][ "{{ ansible_unnum_fabric }}" ] | length) == 1' + when: (ansible_ipv6_fabric in ms_fabric_list or ansible_unnum_fabric in ms_fabric_list) + + - assert: + that: + - 'item["RETURN_CODE"] == 200' + loop: '{{ result.response }}' + when: (ansible_ipv6_fabric in ms_fabric_list or ansible_unnum_fabric in ms_fabric_list) + +############################################## +## CLEANUP ## +############################################## + + always: + + - name: Cleanup - Delete Links + cisco.dcnm.dcnm_links: *links_delete + register: result + when: IT_CONTEXT is not defined + + - assert: + that: + - 'item["RETURN_CODE"] == 200' + loop: '{{ result.response }}' + when: IT_CONTEXT is not defined diff --git a/tests/integration/targets/dcnm_links/tests/dcnm/dcnm_links_inter_missing_params.yaml b/tests/integration/targets/dcnm_links/tests/dcnm/dcnm_links_inter_missing_params.yaml new file mode 100644 index 000000000..bec59f95e --- /dev/null +++ b/tests/integration/targets/dcnm_links/tests/dcnm/dcnm_links_inter_missing_params.yaml @@ -0,0 +1,314 @@ +############################################## +## SETUP ## +############################################## + +- name: Remove local log file + local_action: command rm -f dcnm_links.log + +- block: + +############################################## +## MERGE ## +############################################## + + - name: Create Links with invalid template name + cisco.dcnm.dcnm_links: + src_fabric: "{{ ansible_num_fabric }}" + state: merged # choose from [merged, replaced, deleted, query] + config: + - dst_fabric: "{{ ansible_num_fabric }}" # Destination fabric + src_interface: "{{ intf_1_1 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_1 }}" # Interface on the Destination fabric + src_device: "{{ ansible_num_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_num_switch2 }}" # Device on the Destination fabric + template: dcnm_links_invalid_template # template to be applied, choose from + # [ ext_fabric_setup, ext_multisite_underlay_setup, + # ext_evpn_multisite_overlay_setup ] + + profile: + ipv4_subnet: 193.168.1.1/24 # IP address of interface in src fabric with mask + neighbor_ip: 193.168.1.2 # IP address of the interface in dst fabric + src_asn: "{{ ansible_num_asn }}" # BGP ASN in source fabric + dst_asn: "{{ ansible_unnum_asn }}" # BGP ASN in destination fabric + ignore_errors: yes + register: result + + - name: Create Links without source fabric + cisco.dcnm.dcnm_links: + state: merged # choose from [merged, replaced, deleted, query] + config: + - dst_fabric: "{{ ansible_unnum_fabric }}" # Destination fabric + src_interface: "{{ intf_1_3 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_3 }}" # Interface on the Destination fabric + src_device: "{{ ansible_num_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_unnum_switch1 }}" # Device on the Destination fabric + template: ext_fabric_setup # template to be applied, choose from + # [ ext_fabric_setup, ext_multisite_underlay_setup, + # ext_evpn_multisite_overlay_setup ] + + profile: + ipv4_subnet: 193.168.1.1/24 # IP address of interface in src fabric with mask + neighbor_ip: 193.168.1.2 # IP address of the interface in dst fabric + src_asn: "{{ ansible_num_asn }}" # BGP ASN in source fabric + dst_asn: "{{ ansible_unnum_asn }}" # BGP ASN in destination fabric + ignore_errors: yes + register: result + + - name: Create Links without destination fabric + cisco.dcnm.dcnm_links: + state: merged # choose from [merged, replaced, deleted, query] + src_fabric: "{{ ansible_num_fabric }}" + config: + - src_interface: "{{ intf_1_3 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_3 }}" # Interface on the Destination fabric + src_device: "{{ ansible_num_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_unnum_switch1 }}" # Device on the Destination fabric + template: ext_fabric_setup # template to be applied, choose from + # [ ext_fabric_setup, ext_multisite_underlay_setup, + # ext_evpn_multisite_overlay_setup ] + + profile: + ipv4_subnet: 193.168.1.1/24 # IP address of interface in src fabric with mask + neighbor_ip: 193.168.1.2 # IP address of the interface in dst fabric + src_asn: "{{ ansible_num_asn }}" # BGP ASN in source fabric + dst_asn: "{{ ansible_unnum_asn }}" # BGP ASN in destination fabric + ignore_errors: yes + register: result + + - name: Create Links without source interface + cisco.dcnm.dcnm_links: + state: merged # choose from [merged, replaced, deleted, query] + src_fabric: "{{ ansible_num_fabric }}" + config: + - dst_fabric: "{{ ansible_unnum_fabric }}" # Destination fabric + dst_interface: "{{ intf_1_3 }}" # Interface on the Destination fabric + src_device: "{{ ansible_num_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_unnum_switch1 }}" # Device on the Destination fabric + template: ext_fabric_setup # template to be applied, choose from + # [ ext_fabric_setup, ext_multisite_underlay_setup, + # ext_evpn_multisite_overlay_setup ] + + profile: + ipv4_subnet: 193.168.1.1/24 # IP address of interface in src fabric with mask + neighbor_ip: 193.168.1.2 # IP address of the interface in dst fabric + src_asn: "{{ ansible_num_asn }}" # BGP ASN in source fabric + dst_asn: "{{ ansible_unnum_asn }}" # BGP ASN in destination fabric + ignore_errors: yes + register: result + + - name: Create Links without destination interface + cisco.dcnm.dcnm_links: + state: merged # choose from [merged, replaced, deleted, query] + src_fabric: "{{ ansible_num_fabric }}" + config: + - dst_fabric: "{{ ansible_unnum_fabric }}" # Destination fabric + src_interface: "{{ intf_1_3 }}" # Interface on the Source fabric + src_device: "{{ ansible_num_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_unnum_switch1 }}" # Device on the Destination fabric + template: ext_fabric_setup # template to be applied, choose from + # [ ext_fabric_setup, ext_multisite_underlay_setup, + # ext_evpn_multisite_overlay_setup ] + + profile: + ipv4_subnet: 193.168.1.1/24 # IP address of interface in src fabric with mask + neighbor_ip: 193.168.1.2 # IP address of the interface in dst fabric + src_asn: "{{ ansible_num_asn }}" # BGP ASN in source fabric + dst_asn: "{{ ansible_unnum_asn }}" # BGP ASN in destination fabric + ignore_errors: yes + register: result + + - name: Create Links without source device + cisco.dcnm.dcnm_links: + state: merged # choose from [merged, replaced, deleted, query] + src_fabric: "{{ ansible_num_fabric }}" + config: + - dst_fabric: "{{ ansible_unnum_fabric }}" # Destination fabric + src_interface: "{{ intf_1_3 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_3 }}" # Interface on the Destination fabric + dst_device: "{{ ansible_unnum_switch1 }}" # Device on the Destination fabric + template: ext_fabric_setup # template to be applied, choose from + # [ ext_fabric_setup, ext_multisite_underlay_setup, + # ext_evpn_multisite_overlay_setup ] + + profile: + ipv4_subnet: 193.168.1.1/24 # IP address of interface in src fabric with mask + neighbor_ip: 193.168.1.2 # IP address of the interface in dst fabric + src_asn: "{{ ansible_num_asn }}" # BGP ASN in source fabric + dst_asn: "{{ ansible_unnum_asn }}" # BGP ASN in destination fabric + ignore_errors: yes + register: result + + - name: Create Links without destination device + cisco.dcnm.dcnm_links: + state: merged # choose from [merged, replaced, deleted, query] + src_fabric: "{{ ansible_num_fabric }}" + config: + - dst_fabric: "{{ ansible_unnum_fabric }}" # Destination fabric + src_interface: "{{ intf_1_3 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_3 }}" # Interface on the Destination fabric + src_device: "{{ ansible_num_switch1 }}" # Device on the Source fabric + template: ext_fabric_setup # template to be applied, choose from + # [ ext_fabric_setup, ext_multisite_underlay_setup, + # ext_evpn_multisite_overlay_setup ] + + profile: + ipv4_subnet: 193.168.1.1/24 # IP address of interface in src fabric with mask + neighbor_ip: 193.168.1.2 # IP address of the interface in dst fabric + src_asn: "{{ ansible_num_asn }}" # BGP ASN in source fabric + dst_asn: "{{ ansible_unnum_asn }}" # BGP ASN in destination fabric + ignore_errors: yes + register: result + + - name: Create Links without template + cisco.dcnm.dcnm_links: + state: merged # choose from [merged, replaced, deleted, query] + src_fabric: "{{ ansible_num_fabric }}" + config: + - dst_fabric: "{{ ansible_unnum_fabric }}" # Destination fabric + src_interface: "{{ intf_1_3 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_3 }}" # Interface on the Destination fabric + src_device: "{{ ansible_num_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_unnum_switch1 }}" # Device on the Destination fabric + profile: + ipv4_subnet: 193.168.1.1/24 # IP address of interface in src fabric with mask + neighbor_ip: 193.168.1.2 # IP address of the interface in dst fabric + src_asn: "{{ ansible_num_asn }}" # BGP ASN in source fabric + dst_asn: "{{ ansible_unnum_asn }}" # BGP ASN in destination fabric + ignore_errors: yes + register: result + + - name: Create Links without ipv4_subnet + cisco.dcnm.dcnm_links: + state: merged # choose from [merged, replaced, deleted, query] + src_fabric: "{{ ansible_ipv6_fabric }}" + config: + - dst_fabric: "{{ ansible_unnum_fabric }}" # Destination fabric + src_interface: "{{ intf_1_3 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_3 }}" # Interface on the Destination fabric + src_device: "{{ ansible_ipv6_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_unnum_switch1 }}" # Device on the Destination fabric + template: ext_fabric_setup # template to be applied, choose from + # [ ext_fabric_setup, ext_multisite_underlay_setup, + # ext_evpn_multisite_overlay_setup ] + + profile: + neighbor_ip: 193.168.1.2 # IP address of the interface in dst fabric + src_asn: "{{ ansible_ipv6_asn }}" # BGP ASN in source fabric + dst_asn: "{{ ansible_unnum_asn }}" # BGP ASN in destination fabric + ignore_errors: yes + register: result + + - name: Create Links without ipv4_addr + cisco.dcnm.dcnm_links: + state: merged # choose from [merged, replaced, deleted, query] + src_fabric: "{{ ansible_num_fabric }}" + config: + - dst_fabric: "{{ ansible_unnum_fabric }}" # Destination fabric + src_interface: "{{ intf_1_3 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_3 }}" # Interface on the Destination fabric + src_device: "{{ ansible_num_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_unnum_switch1 }}" # Device on the Destination fabric + template: ext_evpn_multisite_overlay_setup # template to be applied, choose from + # [ ext_fabric_setup, ext_multisite_underlay_setup, + # ext_evpn_multisite_overlay_setup ] + + profile: + neighbor_ip: 193.168.3.2 # IP address of the interface in dst fabric + src_asn: "{{ ansible_num_asn }}" # BGP ASN in source fabric + dst_asn: "{{ ansible_unnum_asn }}" # BGP ASN in destination fabric + trm_enabled: false # optional, default is false + ignore_errors: yes + register: result + + - name: Create Links without neighbor_ip + cisco.dcnm.dcnm_links: + state: merged # choose from [merged, replaced, deleted, query] + src_fabric: "{{ ansible_num_fabric }}" + config: + - dst_fabric: "{{ ansible_unnum_fabric }}" # Destination fabric + src_interface: "{{ intf_1_3 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_3 }}" # Interface on the Destination fabric + src_device: "{{ ansible_num_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_unnum_switch1 }}" # Device on the Destination fabric + template: ext_multisite_underlay_setup # template to be applied, choose from + # [ ext_fabric_setup, ext_multisite_underlay_setup, + # ext_evpn_multisite_overlay_setup ] + + profile: + ipv4_subnet: 193.168.1.1/24 # IP address of interface in src fabric with mask + src_asn: "{{ ansible_num_asn }}" # BGP ASN in source fabric + dst_asn: "{{ ansible_unnum_asn }}" # BGP ASN in destination fabric + ignore_errors: yes + register: result + + - name: Create Links without src_asn + cisco.dcnm.dcnm_links: + state: merged # choose from [merged, replaced, deleted, query] + src_fabric: "{{ ansible_num_fabric }}" + config: + - dst_fabric: "{{ ansible_unnum_fabric }}" # Destination fabric + src_interface: "{{ intf_1_3 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_3 }}" # Interface on the Destination fabric + src_device: "{{ ansible_num_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_unnum_switch1 }}" # Device on the Destination fabric + template: ext_fabric_setup # template to be applied, choose from + # [ ext_fabric_setup, ext_multisite_underlay_setup, + # ext_evpn_multisite_overlay_setup ] + + profile: + ipv4_subnet: 193.168.1.1/24 # IP address of interface in src fabric with mask + neighbor_ip: 193.168.1.2 # IP address of the interface in dst fabric + dst_asn: "{{ ansible_unnum_asn }}" # BGP ASN in destination fabric + ignore_errors: yes + register: result + + - name: Create Links without dst_asn + cisco.dcnm.dcnm_links: + state: merged # choose from [merged, replaced, deleted, query] + src_fabric: "{{ ansible_num_fabric }}" + config: + - dst_fabric: "{{ ansible_unnum_fabric }}" # Destination fabric + src_interface: "{{ intf_1_3 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_3 }}" # Interface on the Destination fabric + src_device: "{{ ansible_num_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_unnum_switch1 }}" # Device on the Destination fabric + template: ext_evpn_multisite_overlay_setup # template to be applied, choose from + # [ ext_fabric_setup, ext_multisite_underlay_setup, + # ext_evpn_multisite_overlay_setup ] + + profile: + ipv4_addr: 193.168.1.1 # IP address of interface in src fabric with mask + neighbor_ip: 193.168.1.2 # IP address of the interface in dst fabric + src_asn: "{{ ansible_num_asn }}" # BGP ASN in source fabric + ignore_errors: yes + register: result + + - name: Create Links without profile + cisco.dcnm.dcnm_links: + state: merged # choose from [merged, replaced, deleted, query] + src_fabric: "{{ ansible_num_fabric }}" + config: + - dst_fabric: "{{ ansible_unnum_fabric }}" # Destination fabric + template: ext_multisite_underlay_setup # template to be applied, choose from + # [ ext_fabric_setup, ext_multisite_underlay_setup, + # ext_evpn_multisite_overlay_setup ] + ignore_errors: yes + register: result + + - name: Create Links without multiple profile parameters + cisco.dcnm.dcnm_links: + state: merged # choose from [merged, replaced, deleted, query] + src_fabric: "{{ ansible_num_fabric }}" + config: + - dst_fabric: "{{ ansible_unnum_fabric }}" # Destination fabric + src_interface: "{{ intf_1_1 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_1 }}" # Interface on the Destination fabric + src_device: "{{ ansible_num_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_unnum_switch1 }}" # Device on the Destination fabric + template: ext_multisite_underlay_setup # template to be applied, choose from + # [ ext_fabric_setup, ext_multisite_underlay_setup, + # ext_evpn_multisite_overlay_setup ] + profile: + dummy: 1 + ignore_errors: yes + register: result diff --git a/tests/integration/targets/dcnm_links/tests/dcnm/dcnm_links_inter_numbered_delete.yaml b/tests/integration/targets/dcnm_links/tests/dcnm/dcnm_links_inter_numbered_delete.yaml new file mode 100644 index 000000000..62732ad37 --- /dev/null +++ b/tests/integration/targets/dcnm_links/tests/dcnm/dcnm_links_inter_numbered_delete.yaml @@ -0,0 +1,364 @@ +############################################## +## SETUP ## +############################################## + +- name: Remove local log file + local_action: command rm -f dcnm_links.log + +- block: + +############################################## +## DELETE ## +############################################## + + - name: Initial setup - Delete Links on numbered fabric + cisco.dcnm.dcnm_links: &links_delete + state: deleted # choose from [merged, replaced, deleted, query] + src_fabric: "{{ ansible_num_fabric }}" + config: + - dst_fabric: "{{ ansible_unnum_fabric }}" # Destination fabric + src_interface: "{{ intf_1_3 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_3 }}" # Interface on the Destination fabric + src_device: "{{ ansible_num_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_unnum_switch1 }}" # Device on the Destination fabric + + - dst_fabric: "{{ ansible_unnum_fabric }}" # Destination fabric + src_interface: "{{ intf_1_4 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_4 }}" # Interface on the Destination fabric + src_device: "{{ ansible_num_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_unnum_switch1 }}" # Device on the Destination fabric + + - dst_fabric: "{{ ansible_unnum_fabric }}" # Destination fabric + src_interface: "{{ intf_1_5 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_5 }}" # Interface on the Destination fabric + src_device: "{{ ansible_num_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_unnum_switch1 }}" # Device on the Destination fabric + + - dst_fabric: "{{ ansible_ipv6_fabric }}" # Destination fabric + src_interface: "{{ intf_1_6 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_6 }}" # Interface on the Destination fabric + src_device: "{{ ansible_num_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_ipv6_switch1 }}" # Device on the Destination fabric + + - dst_fabric: "{{ ansible_ipv6_fabric }}" # Destination fabric + src_interface: "{{ intf_1_7 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_7 }}" # Interface on the Destination fabric + src_device: "{{ ansible_num_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_ipv6_switch1 }}" # Device on the Destination fabric + + - dst_fabric: "{{ ansible_ipv6_fabric }}" # Destination fabric + src_interface: "{{ intf_1_8 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_8 }}" # Interface on the Destination fabric + src_device: "{{ ansible_num_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_ipv6_switch1 }}" # Device on the Destination fabric + + register: result + + - assert: + that: + - 'item["RETURN_CODE"] == 200' + loop: '{{ result.response }}' + +############################################## +## MERGE ## +############################################## + + - name: Create Links including optional parameters + cisco.dcnm.dcnm_links: &links_merge_with_opt1 + state: merged # choose from [merged, replaced, deleted, query] + src_fabric: "{{ ansible_num_fabric }}" + config: + - dst_fabric: "{{ ansible_unnum_fabric }}" # Destination fabric + src_interface: "{{ intf_1_3 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_3 }}" # Interface on the Destination fabric + src_device: "{{ ansible_num_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_unnum_switch1 }}" # Device on the Destination fabric + template: ext_fabric_setup # template to be applied, choose from + # [ ext_fabric_setup, ext_multisite_underlay_setup, + # ext_evpn_multisite_overlay_setup ] + profile: + ipv4_subnet: 193.168.1.1/24 # IP address of interface in src fabric with mask + neighbor_ip: 193.168.1.2 # IP address of the interface in dst fabric + src_asn: "{{ ansible_num_asn }}" # BGP ASN in source fabric + dst_asn: "{{ ansible_unnum_asn }}" # BGP ASN in destination fabric + mtu: 9216 # + auto_deploy: false # optional, default is false + # Flag that controls auto generation of neighbor VRF Lite configuration + peer1_description: "Description of source" # optional, default is "" + peer2_description: "Description of dest" # optional, default is "" + peer1_cmds: # Freeform config for source interface + - no shutdown # optional, default is "" + peer2_cmds: # Freeform config for destination interface + - no shutdown # optional, default is "" + + - dst_fabric: "{{ ansible_ipv6_fabric }}" # Destination fabric + src_interface: "{{ intf_1_6 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_6 }}" # Interface on the Destination fabric + src_device: "{{ ansible_num_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_ipv6_switch1 }}" # Device on the Destination fabric + template: ext_fabric_setup # template to be applied, choose from + # [ ext_fabric_setup, ext_multisite_underlay_setup, + # ext_evpn_multisite_overlay_setup ] + profile: + ipv4_subnet: 193.168.4.1/24 # IP address of interface in src fabric with mask + neighbor_ip: 193.168.4.2 # IP address of the interface in dst fabric + src_asn: "{{ ansible_num_asn }}" # BGP ASN in source fabric + dst_asn: "{{ ansible_ipv6_asn }}" # BGP ASN in destination fabric + mtu: 9216 # + auto_deploy: false # optional, default is false + # Flag that controls auto generation of neighbor VRF Lite configuration + peer1_description: "Description of source" # optional, default is "" + peer2_description: "Description of dest" # optional, default is "" + peer1_cmds: # Freeform config for source interface + - no shutdown # optional, default is "" + peer2_cmds: # Freeform config for destination interface + - no shutdown # optional, default is "" + + register: result + + - assert: + that: + - 'result.changed == true' + - '(result["diff"][0]["merged"] | length) == 2' + - '(result["diff"][0]["modified"] | length) == 0' + - '(result["diff"][0]["deleted"] | length) == 0' + - '(result["diff"][0]["query"] | length) == 0' + - '(result["diff"][0]["deploy"][0][ "{{ ansible_num_fabric }}" ] | length) == 1' + - '(result["diff"][0]["deploy"][0][ "{{ ansible_unnum_fabric }}" ] | length) == 1' + - '(result["diff"][0]["deploy"][0][ "{{ ansible_ipv6_fabric }}" ] | length) == 1' + + - assert: + that: + - 'item["RETURN_CODE"] == 200' + loop: '{{ result.response }}' + +############################################### +### DELETE ## +############################################### + + - name: Delete Links + cisco.dcnm.dcnm_links: *links_delete + register: result + + - assert: + that: + - 'result.changed == true' + - '(result["diff"][0]["merged"] | length) == 0' + - '(result["diff"][0]["modified"] | length) == 0' + - '(result["diff"][0]["deleted"] | length) == 2' + - '(result["diff"][0]["query"] | length) == 0' + - '(result["diff"][0]["deploy"][0][ "{{ ansible_num_fabric }}" ] | length) == 1' + - '(result["diff"][0]["deploy"][0][ "{{ ansible_unnum_fabric }}" ] | length) == 1' + - '(result["diff"][0]["deploy"][0][ "{{ ansible_ipv6_fabric }}" ] | length) == 1' + + - assert: + that: + - 'item["RETURN_CODE"] == 200' + loop: '{{ result.response }}' + +############################################### +### IDEMPOTENCE ## +############################################### + + - name: Delete Links - Idempotence + cisco.dcnm.dcnm_links: *links_delete + register: result + + - assert: + that: + - 'result.changed == false' + - '(result["diff"][0]["merged"] | length) == 0' + - '(result["diff"][0]["modified"] | length) == 0' + - '(result["diff"][0]["deleted"] | length) == 0' + - '(result["diff"][0]["query"] | length) == 0' + - '(result["diff"][0]["deploy"] | length) == 0' + + - assert: + that: + - 'item["RETURN_CODE"] == 200' + loop: '{{ result.response }}' + +############################################## +## MERGE ## +############################################## + + - name: Create Links (multisite) including optional parameters + cisco.dcnm.dcnm_links: &links_merge_with_opt2 + state: merged # choose from [merged, replaced, deleted, query] + src_fabric: "{{ ansible_num_fabric }}" + config: + - dst_fabric: "{{ ansible_unnum_fabric }}" # Destination fabric + src_interface: "{{ intf_1_4 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_4 }}" # Interface on the Destination fabric + src_device: "{{ ansible_num_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_unnum_switch1 }}" # Device on the Destination fabric + template: ext_multisite_underlay_setup # template to be applied, choose from + # [ ext_fabric_setup, ext_multisite_underlay_setup, + # ext_evpn_multisite_overlay_setup ] + profile: + ipv4_subnet: 193.168.2.1/24 # IP address of interface in src fabric with mask + neighbor_ip: 193.168.2.2 # IP address of the interface in dst fabric + src_asn: "{{ ansible_num_asn }}" # BGP ASN in source fabric + dst_asn: "{{ ansible_unnum_asn }}" # BGP ASN in destination fabric + mtu: 9216 # + deploy_dci_tracking: false # optional, default is false + max_paths: 1 # optional, default is 1 + route_tag: 12345 # optional, optional is "" + ebgp_password_enable: true # optional, default is true + ebgp_password: 9BFE3270E19CA112 # optional, required only if ebgp_password_enable flag is true and inherit_from_msd is false + inherit_from_msd: True # optional, required only if ebgp_password_enable flag is true, default is false + ebgp_auth_key_type: 3 # optional, required only if ebpg_password_enable is true and inherit_from_msd + # is false. Default is 3 + # choose from [3 - 3DES, 7 - Cisco ] + peer1_description: "Description of source" # optional, default is "" + peer2_description: "Description of dest" # optional, default is "" + peer1_cmds: # Freeform config for source interface + - no shutdown # optional, default is "" + peer2_cmds: # Freeform config for destination interface + - no shutdown # optional, default is "" + + - dst_fabric: "{{ ansible_unnum_fabric }}" # Destination fabric + src_interface: "{{ intf_1_5 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_5 }}" # Interface on the Destination fabric + src_device: "{{ ansible_num_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_unnum_switch1 }}" # Device on the Destination fabric + template: ext_evpn_multisite_overlay_setup # template to be applied, choose from + # [ ext_fabric_setup, ext_multisite_underlay_setup, + # ext_evpn_multisite_overlay_setup ] + profile: + ipv4_addr: 193.168.3.1 # IP address of interface in src fabric + neighbor_ip: 193.168.3.2 # IP address of the interface in dst fabric + src_asn: "{{ ansible_num_asn }}" # BGP ASN in source fabric + dst_asn: "{{ ansible_unnum_asn }}" # BGP ASN in destination fabric + trm_enabled: false # optional, default is false + bgp_multihop: 5 # optional, default is 5 + ebgp_password_enable: true # optional, default is true + ebgp_password: 9BFE3270E19CA112 # optional, required only if ebgp_password_enable flag is true and inherit_from_msd is false + inherit_from_msd: false # optional, required only if ebgp_password_enable flag is true, default is false + ebgp_auth_key_type: 3 # optional, required only if ebpg_password_enable is true and inherit_from_msd + # is false. Default is 3 + # choose from [3 - 3DES, 7 - Cisco ] + + - dst_fabric: "{{ ansible_ipv6_fabric }}" # Destination fabric + src_interface: "{{ intf_1_7 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_7 }}" # Interface on the Destination fabric + src_device: "{{ ansible_num_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_ipv6_switch1 }}" # Device on the Destination fabric + template: ext_multisite_underlay_setup # template to be applied, choose from + # [ ext_fabric_setup, ext_multisite_underlay_setup, + # ext_evpn_multisite_overlay_setup ] + profile: + ipv4_subnet: 193.168.5.1/24 # IP address of interface in src fabric with mask + neighbor_ip: 193.168.5.2 # IP address of the interface in dst fabric + src_asn: "{{ ansible_num_asn }}" # BGP ASN in source fabric + dst_asn: "{{ ansible_ipv6_asn }}" # BGP ASN in destination fabric + mtu: 9216 # + deploy_dci_tracking: false # optional, default is false + max_paths: 1 # optional, default is 1 + route_tag: 12345 # optional, optional is "" + ebpg_password_enable: True # optional, default is true + ebgp_password: 9BFE3270E19CA112 # optional, required only if ebgp_password_enable flag is true and inherit_from_msd is false + inherit_from_msd: True # optional, required only if ebgp_password_enable flag is true, default is false + ebgp_auth_key_type: 3 # optional, required only if ebpg_password_enable is true and inherit_from_msd + # is false. Default is 3 + # choose from [3 - 3DES, 7 - Cisco ] + peer1_description: "Description of source" # optional, default is "" + peer2_description: "Description of dest" # optional, default is "" + peer1_cmds: # Freeform config for source interface + - no shutdown # optional, default is "" + peer2_cmds: # Freeform config for destination interface + - no shutdown # optional, default is "" + + - dst_fabric: "{{ ansible_ipv6_fabric }}" # Destination fabric + src_interface: "{{ intf_1_8 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_8 }}" # Interface on the Destination fabric + src_device: "{{ ansible_num_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_ipv6_switch1 }}" # Device on the Destination fabric + template: ext_evpn_multisite_overlay_setup # template to be applied, choose from + # [ ext_fabric_setup, ext_multisite_underlay_setup, + # ext_evpn_multisite_overlay_setup ] + profile: + ipv4_addr: 193.168.6.1 # IP address of interface in src fabric + neighbor_ip: 193.168.6.2 # IP address of the interface in dst fabric + src_asn: "{{ ansible_num_asn }}" # BGP ASN in source fabric + dst_asn: "{{ ansible_ipv6_asn }}" # BGP ASN in destination fabric + trm_enabled: false # optional, default is false + bgp_multihop: 5 # optional, default is 5 + ebgp_password_enable: True # optional, default is true + ebgp_password: 9BFE3270E19CA112 # optional, required only if ebgp_password_enable flag is true and inherit_from_msd is false + inherit_from_msd: false # optional, required only if ebgp_password_enable flag is true, default is false + ebgp_auth_key_type: 3 # optional, required only if ebpg_password_enable is true and inherit_from_msd + # is false. Default is 3 + # choose from [3 - 3DES, 7 - Cisco ] + register: result + when: (ansible_num_fabric in ms_fabric_list or ansible_ipv6_fabric in ms_fabric_list or ansible_unnum_fabric in ms_fabric_list) + + - assert: + that: + - 'result.changed == true' + - '(result["diff"][0]["merged"] | length) == 4' + - '(result["diff"][0]["modified"] | length) == 0' + - '(result["diff"][0]["deleted"] | length) == 0' + - '(result["diff"][0]["query"] | length) == 0' + - '(result["diff"][0]["deploy"][0][ "{{ ansible_num_fabric }}" ] | length) == 1' + - '(result["diff"][0]["deploy"][0][ "{{ ansible_unnum_fabric }}" ] | length) == 1' + - '(result["diff"][0]["deploy"][0][ "{{ ansible_ipv6_fabric }}" ] | length) == 1' + when: (ansible_num_fabric in ms_fabric_list or ansible_ipv6_fabric in ms_fabric_list or ansible_unnum_fabric in ms_fabric_list) + + - assert: + that: + - 'item["RETURN_CODE"] == 200' + loop: '{{ result.response }}' + when: (ansible_num_fabric in ms_fabric_list or ansible_ipv6_fabric in ms_fabric_list or ansible_unnum_fabric in ms_fabric_list) + +############################################### +### DELETE ## +############################################### + + - name: Delete Links + cisco.dcnm.dcnm_links: *links_delete + register: result + when: (ansible_num_fabric in ms_fabric_list or ansible_ipv6_fabric in ms_fabric_list or ansible_unnum_fabric in ms_fabric_list) + + - assert: + that: + - 'result.changed == true' + - '(result["diff"][0]["merged"] | length) == 0' + - '(result["diff"][0]["modified"] | length) == 0' + - '(result["diff"][0]["deleted"] | length) == 4' + - '(result["diff"][0]["query"] | length) == 0' + - '(result["diff"][0]["deploy"][0][ "{{ ansible_num_fabric }}" ] | length) == 1' + - '(result["diff"][0]["deploy"][0][ "{{ ansible_unnum_fabric }}" ] | length) == 1' + - '(result["diff"][0]["deploy"][0][ "{{ ansible_ipv6_fabric }}" ] | length) == 1' + when: (ansible_num_fabric in ms_fabric_list or ansible_ipv6_fabric in ms_fabric_list or ansible_unnum_fabric in ms_fabric_list) + + - assert: + that: + - 'item["RETURN_CODE"] == 200' + loop: '{{ result.response }}' + when: (ansible_num_fabric in ms_fabric_list or ansible_ipv6_fabric in ms_fabric_list or ansible_unnum_fabric in ms_fabric_list) + +############################################### +### IDEMPOTENCE ## +############################################### + + - name: Delete Links - Idempotence + cisco.dcnm.dcnm_links: *links_delete + register: result + when: (ansible_num_fabric in ms_fabric_list or ansible_ipv6_fabric in ms_fabric_list or ansible_unnum_fabric in ms_fabric_list) + + - assert: + that: + - 'result.changed == false' + - '(result["diff"][0]["merged"] | length) == 0' + - '(result["diff"][0]["modified"] | length) == 0' + - '(result["diff"][0]["deleted"] | length) == 0' + - '(result["diff"][0]["query"] | length) == 0' + - '(result["diff"][0]["deploy"] | length) == 0' + when: (ansible_num_fabric in ms_fabric_list or ansible_ipv6_fabric in ms_fabric_list or ansible_unnum_fabric in ms_fabric_list) + + - assert: + that: + - 'item["RETURN_CODE"] == 200' + loop: '{{ result.response }}' + when: (ansible_num_fabric in ms_fabric_list or ansible_ipv6_fabric in ms_fabric_list or ansible_unnum_fabric in ms_fabric_list) diff --git a/tests/integration/targets/dcnm_links/tests/dcnm/dcnm_links_inter_numbered_merge.yaml b/tests/integration/targets/dcnm_links/tests/dcnm/dcnm_links_inter_numbered_merge.yaml new file mode 100644 index 000000000..3010d1ad3 --- /dev/null +++ b/tests/integration/targets/dcnm_links/tests/dcnm/dcnm_links_inter_numbered_merge.yaml @@ -0,0 +1,574 @@ +############################################## +## SETUP ## +############################################## + +- name: Remove local log file + local_action: command rm -f dcnm_links.log + +- block: + +############################################## +## DELETE ## +############################################## + + - name: Initial setup - Delete Links on numbered fabric + cisco.dcnm.dcnm_links: &links_delete + state: deleted # choose from [merged, replaced, deleted, query] + src_fabric: "{{ ansible_num_fabric }}" + config: + - dst_fabric: "{{ ansible_unnum_fabric }}" # Destination fabric + src_interface: "{{ intf_1_3 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_3 }}" # Interface on the Destination fabric + src_device: "{{ ansible_num_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_unnum_switch1 }}" # Device on the Destination fabric + + - dst_fabric: "{{ ansible_unnum_fabric }}" # Destination fabric + src_interface: "{{ intf_1_4 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_4 }}" # Interface on the Destination fabric + src_device: "{{ ansible_num_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_unnum_switch1 }}" # Device on the Destination fabric + + - dst_fabric: "{{ ansible_unnum_fabric }}" # Destination fabric + src_interface: "{{ intf_1_5 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_5 }}" # Interface on the Destination fabric + src_device: "{{ ansible_num_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_unnum_switch1 }}" # Device on the Destination fabric + + - dst_fabric: "{{ ansible_ipv6_fabric }}" # Destination fabric + src_interface: "{{ intf_1_6 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_6 }}" # Interface on the Destination fabric + src_device: "{{ ansible_num_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_ipv6_switch1 }}" # Device on the Destination fabric + + - dst_fabric: "{{ ansible_ipv6_fabric }}" # Destination fabric + src_interface: "{{ intf_1_7 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_7 }}" # Interface on the Destination fabric + src_device: "{{ ansible_num_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_ipv6_switch1 }}" # Device on the Destination fabric + + - dst_fabric: "{{ ansible_ipv6_fabric }}" # Destination fabric + src_interface: "{{ intf_1_8 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_8 }}" # Interface on the Destination fabric + src_device: "{{ ansible_num_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_ipv6_switch1 }}" # Device on the Destination fabric + + register: result + + - assert: + that: + - 'item["RETURN_CODE"] == 200' + loop: '{{ result.response }}' + +############################################## +## MERGE ## +############################################## + + - name: Create Links without including optional parameters + cisco.dcnm.dcnm_links: &links_merge_no_opt1 + state: merged # choose from [merged, replaced, deleted, query] + src_fabric: "{{ ansible_num_fabric }}" + config: + - dst_fabric: "{{ ansible_unnum_fabric }}" # Destination fabric + src_interface: "{{ intf_1_3 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_3 }}" # Interface on the Destination fabric + src_device: "{{ ansible_num_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_unnum_switch1 }}" # Device on the Destination fabric + template: ext_fabric_setup # template to be applied, choose from + # [ ext_fabric_setup, ext_multisite_underlay_setup, + # ext_evpn_multisite_overlay_setup ] + profile: + ipv4_subnet: 193.168.1.1/24 # IP address of interface in src fabric with mask + neighbor_ip: 193.168.1.2 # IP address of the interface in dst fabric + src_asn: "{{ ansible_num_asn }}" # BGP ASN in source fabric + dst_asn: "{{ ansible_unnum_asn }}" # BGP ASN in destination fabric + + - dst_fabric: "{{ ansible_ipv6_fabric }}" # Destination fabric + src_interface: "{{ intf_1_6 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_6 }}" # Interface on the Destination fabric + src_device: "{{ ansible_num_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_ipv6_switch1 }}" # Device on the Destination fabric + template: ext_fabric_setup # template to be applied, choose from + # [ ext_fabric_setup, ext_multisite_underlay_setup, + # ext_evpn_multisite_overlay_setup ] + profile: + ipv4_subnet: 193.168.4.1/24 # IP address of interface in src fabric with mask + neighbor_ip: 193.168.4.2 # IP address of the interface in dst fabric + src_asn: "{{ ansible_num_asn }}" # BGP ASN in source fabric + dst_asn: "{{ ansible_ipv6_asn }}" # BGP ASN in destination fabric + register: result + + - assert: + that: + - 'result.changed == true' + - '(result["diff"][0]["merged"] | length) == 2' + - '(result["diff"][0]["modified"] | length) == 0' + - '(result["diff"][0]["deleted"] | length) == 0' + - '(result["diff"][0]["query"] | length) == 0' + - '(result["diff"][0]["deploy"][0][ "{{ ansible_num_fabric }}" ] | length) == 1' + - '(result["diff"][0]["deploy"][0][ "{{ ansible_unnum_fabric }}" ] | length) == 1' + - '(result["diff"][0]["deploy"][0][ "{{ ansible_ipv6_fabric }}" ] | length) == 1' + + - assert: + that: + - 'item["RETURN_CODE"] == 200' + loop: '{{ result.response }}' + +############################################### +### IDEMPOTENCE ## +############################################### + + - name: Create Links - Idempotence + cisco.dcnm.dcnm_links: *links_merge_no_opt1 + register: result + + - assert: + that: + - 'result.changed == true' + - '(result["diff"][0]["merged"] | length) == 0' + - '(result["diff"][0]["modified"] | length) == 0' + - '(result["diff"][0]["deleted"] | length) == 0' + - '(result["diff"][0]["query"] | length) == 0' + - '(result["diff"][0]["deploy"][0][ "{{ ansible_num_fabric }}" ] | length) == 1' + - '(result["diff"][0]["deploy"][0][ "{{ ansible_unnum_fabric }}" ] | length) == 1' + - '(result["diff"][0]["deploy"][0][ "{{ ansible_ipv6_fabric }}" ] | length) == 1' + + - assert: + that: + - 'item["RETURN_CODE"] == 200' + loop: '{{ result.response }}' + +############################################### +### DELETE ## +############################################### + + - name: Delete Links + cisco.dcnm.dcnm_links: *links_delete + register: result + + - assert: + that: + - 'result.changed == true' + - '(result["diff"][0]["merged"] | length) == 0' + - '(result["diff"][0]["modified"] | length) == 0' + - '(result["diff"][0]["deleted"] | length) == 2' + - '(result["diff"][0]["query"] | length) == 0' + - '(result["diff"][0]["deploy"][0][ "{{ ansible_num_fabric }}" ] | length) == 1' + - '(result["diff"][0]["deploy"][0][ "{{ ansible_unnum_fabric }}" ] | length) == 1' + - '(result["diff"][0]["deploy"][0][ "{{ ansible_ipv6_fabric }}" ] | length) == 1' + + - assert: + that: + - 'item["RETURN_CODE"] == 200' + loop: '{{ result.response }}' + +############################################## +## MERGE ## +############################################## + + - name: Create Links (multisite) without including optional parameters + cisco.dcnm.dcnm_links: &links_merge_no_opt2 + state: merged # choose from [merged, replaced, deleted, query] + src_fabric: "{{ ansible_num_fabric }}" + config: + - dst_fabric: "{{ ansible_unnum_fabric }}" # Destination fabric + src_interface: "{{ intf_1_4 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_4 }}" # Interface on the Destination fabric + src_device: "{{ ansible_num_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_unnum_switch1 }}" # Device on the Destination fabric + template: ext_multisite_underlay_setup # template to be applied, choose from + # [ ext_fabric_setup, ext_multisite_underlay_setup, + # ext_evpn_multisite_overlay_setup ] + profile: + ipv4_subnet: 193.168.2.1/24 # IP address of interface in src fabric with mask + neighbor_ip: 193.168.2.2 # IP address of the interface in dst fabric + src_asn: "{{ ansible_num_asn }}" # BGP ASN in source fabric + dst_asn: "{{ ansible_unnum_asn }}" # BGP ASN in destination fabric + + - dst_fabric: "{{ ansible_unnum_fabric }}" # Destination fabric + src_interface: "{{ intf_1_5 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_5 }}" # Interface on the Destination fabric + src_device: "{{ ansible_num_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_unnum_switch1 }}" # Device on the Destination fabric + template: ext_evpn_multisite_overlay_setup # template to be applied, choose from + # [ ext_fabric_setup, ext_multisite_underlay_setup, + # ext_evpn_multisite_overlay_setup ] + profile: + ipv4_addr: 193.168.3.1 # IP address of interface in src fabric + neighbor_ip: 193.168.3.2 # IP address of the interface in dst fabric + src_asn: "{{ ansible_num_asn }}" # BGP ASN in source fabric + dst_asn: "{{ ansible_unnum_asn }}" # BGP ASN in destination fabric + + - dst_fabric: "{{ ansible_ipv6_fabric }}" # Destination fabric + src_interface: "{{ intf_1_7 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_7 }}" # Interface on the Destination fabric + src_device: "{{ ansible_num_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_ipv6_switch1 }}" # Device on the Destination fabric + template: ext_multisite_underlay_setup # template to be applied, choose from + # [ ext_fabric_setup, ext_multisite_underlay_setup, + # ext_evpn_multisite_overlay_setup ] + profile: + ipv4_subnet: 193.168.5.1/24 # IP address of interface in src fabric with mask + neighbor_ip: 193.168.5.2 # IP address of the interface in dst fabric + src_asn: "{{ ansible_num_asn }}" # BGP ASN in source fabric + dst_asn: "{{ ansible_ipv6_asn }}" # BGP ASN in destination fabric + + - dst_fabric: "{{ ansible_ipv6_fabric }}" # Destination fabric + src_interface: "{{ intf_1_8 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_8 }}" # Interface on the Destination fabric + src_device: "{{ ansible_num_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_ipv6_switch1 }}" # Device on the Destination fabric + template: ext_evpn_multisite_overlay_setup # template to be applied, choose from + # [ ext_fabric_setup, ext_multisite_underlay_setup, + # ext_evpn_multisite_overlay_setup ] + profile: + ipv4_addr: 193.168.6.1 # IP address of interface in src fabric + neighbor_ip: 193.168.6.2 # IP address of the interface in dst fabric + src_asn: "{{ ansible_num_asn }}" # BGP ASN in source fabric + dst_asn: "{{ ansible_ipv6_asn }}" # BGP ASN in destination fabric + register: result + when: (ansible_num_fabric in ms_fabric_list or ansible_ipv6_fabric in ms_fabric_list or ansible_unnum_fabric in ms_fabric_list) + + - assert: + that: + - 'result.changed == true' + - '(result["diff"][0]["merged"] | length) == 4' + - '(result["diff"][0]["modified"] | length) == 0' + - '(result["diff"][0]["deleted"] | length) == 0' + - '(result["diff"][0]["query"] | length) == 0' + - '(result["diff"][0]["deploy"][0][ "{{ ansible_num_fabric }}" ] | length) == 1' + - '(result["diff"][0]["deploy"][0][ "{{ ansible_unnum_fabric }}" ] | length) == 1' + - '(result["diff"][0]["deploy"][0][ "{{ ansible_ipv6_fabric }}" ] | length) == 1' + when: (ansible_num_fabric in ms_fabric_list or ansible_ipv6_fabric in ms_fabric_list or ansible_unnum_fabric in ms_fabric_list) + + - assert: + that: + - 'item["RETURN_CODE"] == 200' + loop: '{{ result.response }}' + when: (ansible_num_fabric in ms_fabric_list or ansible_ipv6_fabric in ms_fabric_list or ansible_unnum_fabric in ms_fabric_list) + +############################################### +### IDEMPOTENCE ## +############################################### + + - name: Create Links - Idempotence + cisco.dcnm.dcnm_links: *links_merge_no_opt2 + register: result + when: (ansible_num_fabric in ms_fabric_list or ansible_ipv6_fabric in ms_fabric_list or ansible_unnum_fabric in ms_fabric_list) + + - assert: + that: + - 'result.changed == true' + - '(result["diff"][0]["merged"] | length) == 0' + - '(result["diff"][0]["modified"] | length) == 0' + - '(result["diff"][0]["deleted"] | length) == 0' + - '(result["diff"][0]["query"] | length) == 0' + - '(result["diff"][0]["deploy"][0][ "{{ ansible_num_fabric }}" ] | length) == 1' + - '(result["diff"][0]["deploy"][0][ "{{ ansible_unnum_fabric }}" ] | length) == 1' + - '(result["diff"][0]["deploy"][0][ "{{ ansible_ipv6_fabric }}" ] | length) == 1' + when: (ansible_num_fabric in ms_fabric_list or ansible_ipv6_fabric in ms_fabric_list or ansible_unnum_fabric in ms_fabric_list) + + - assert: + that: + - 'item["RETURN_CODE"] == 200' + loop: '{{ result.response }}' + when: (ansible_num_fabric in ms_fabric_list or ansible_ipv6_fabric in ms_fabric_list or ansible_unnum_fabric in ms_fabric_list) + +############################################### +### DELETE ## +############################################### + + - name: Delete Links + cisco.dcnm.dcnm_links: *links_delete + register: result + when: (ansible_num_fabric in ms_fabric_list or ansible_ipv6_fabric in ms_fabric_list or ansible_unnum_fabric in ms_fabric_list) + + - assert: + that: + - 'result.changed == true' + - '(result["diff"][0]["merged"] | length) == 0' + - '(result["diff"][0]["modified"] | length) == 0' + - '(result["diff"][0]["deleted"] | length) == 4' + - '(result["diff"][0]["query"] | length) == 0' + - '(result["diff"][0]["deploy"][0][ "{{ ansible_num_fabric }}" ] | length) == 1' + - '(result["diff"][0]["deploy"][0][ "{{ ansible_unnum_fabric }}" ] | length) == 1' + - '(result["diff"][0]["deploy"][0][ "{{ ansible_ipv6_fabric }}" ] | length) == 1' + when: (ansible_num_fabric in ms_fabric_list or ansible_ipv6_fabric in ms_fabric_list or ansible_unnum_fabric in ms_fabric_list) + + - assert: + that: + - 'item["RETURN_CODE"] == 200' + loop: '{{ result.response }}' + when: (ansible_num_fabric in ms_fabric_list or ansible_ipv6_fabric in ms_fabric_list or ansible_unnum_fabric in ms_fabric_list) + +############################################## +## MERGE ## +############################################## + + - name: Create Links including optional parameters + cisco.dcnm.dcnm_links: &links_merge_with_opt3 + state: merged # choose from [merged, replaced, deleted, query] + src_fabric: "{{ ansible_num_fabric }}" + config: + - dst_fabric: "{{ ansible_unnum_fabric }}" # Destination fabric + src_interface: "{{ intf_1_3 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_3 }}" # Interface on the Destination fabric + src_device: "{{ ansible_num_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_unnum_switch1 }}" # Device on the Destination fabric + template: ext_fabric_setup # template to be applied, choose from + # [ ext_fabric_setup, ext_multisite_underlay_setup, + # ext_evpn_multisite_overlay_setup ] + profile: + ipv4_subnet: 193.168.1.1/24 # IP address of interface in src fabric with mask + neighbor_ip: 193.168.1.2 # IP address of the interface in dst fabric + src_asn: "{{ ansible_num_asn }}" # BGP ASN in source fabric + dst_asn: "{{ ansible_unnum_asn }}" # BGP ASN in destination fabric + mtu: 9216 # + auto_deploy: false # optional, default is false + # Flag that controls auto generation of neighbor VRF Lite configuration + peer1_description: "Description of source" # optional, default is "" + peer2_description: "Description of dest" # optional, default is "" + peer1_cmds: # Freeform config for source interface + - no shutdown # optional, default is "" + peer2_cmds: # Freeform config for destination interface + - no shutdown # optional, default is "" + + - dst_fabric: "{{ ansible_ipv6_fabric }}" # Destination fabric + src_interface: "{{ intf_1_6 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_6 }}" # Interface on the Destination fabric + src_device: "{{ ansible_num_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_ipv6_switch1 }}" # Device on the Destination fabric + template: ext_fabric_setup # template to be applied, choose from + # [ ext_fabric_setup, ext_multisite_underlay_setup, + # ext_evpn_multisite_overlay_setup ] + profile: + ipv4_subnet: 193.168.4.1/24 # IP address of interface in src fabric with mask + neighbor_ip: 193.168.4.2 # IP address of the interface in dst fabric + src_asn: "{{ ansible_num_asn }}" # BGP ASN in source fabric + dst_asn: "{{ ansible_ipv6_asn }}" # BGP ASN in destination fabric + mtu: 9216 # + auto_deploy: false # optional, default is false + # Flag that controls auto generation of neighbor VRF Lite configuration + peer1_description: "Description of source" # optional, default is "" + peer2_description: "Description of dest" # optional, default is "" + peer1_cmds: # Freeform config for source interface + - no shutdown # optional, default is "" + peer2_cmds: # Freeform config for destination interface + - no shutdown # optional, default is "" + register: result + + - assert: + that: + - 'result.changed == true' + - '(result["diff"][0]["merged"] | length) == 2' + - '(result["diff"][0]["modified"] | length) == 0' + - '(result["diff"][0]["deleted"] | length) == 0' + - '(result["diff"][0]["query"] | length) == 0' + - '(result["diff"][0]["deploy"][0][ "{{ ansible_num_fabric }}" ] | length) == 1' + - '(result["diff"][0]["deploy"][0][ "{{ ansible_unnum_fabric }}" ] | length) == 1' + - '(result["diff"][0]["deploy"][0][ "{{ ansible_ipv6_fabric }}" ] | length) == 1' + + - assert: + that: + - 'item["RETURN_CODE"] == 200' + loop: '{{ result.response }}' + +############################################### +### IDEMPOTENCE ## +############################################### + + - name: Create Links - Idempotence + cisco.dcnm.dcnm_links: *links_merge_with_opt3 + register: result + + - assert: + that: + - 'result.changed == true' + - '(result["diff"][0]["merged"] | length) == 0' + - '(result["diff"][0]["modified"] | length) == 0' + - '(result["diff"][0]["deleted"] | length) == 0' + - '(result["diff"][0]["query"] | length) == 0' + - '(result["diff"][0]["deploy"][0][ "{{ ansible_num_fabric }}" ] | length) == 1' + - '(result["diff"][0]["deploy"][0][ "{{ ansible_unnum_fabric }}" ] | length) == 1' + - '(result["diff"][0]["deploy"][0][ "{{ ansible_ipv6_fabric }}" ] | length) == 1' + + - assert: + that: + - 'item["RETURN_CODE"] == 200' + loop: '{{ result.response }}' + +############################################## +## MERGE ## +############################################## + + - name: Create Links (multisite) including optional parameters + cisco.dcnm.dcnm_links: &links_merge_with_opt4 + state: merged # choose from [merged, replaced, deleted, query] + src_fabric: "{{ ansible_num_fabric }}" + config: + - dst_fabric: "{{ ansible_unnum_fabric }}" # Destination fabric + src_interface: "{{ intf_1_4 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_4 }}" # Interface on the Destination fabric + src_device: "{{ ansible_num_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_unnum_switch1 }}" # Device on the Destination fabric + template: ext_multisite_underlay_setup # template to be applied, choose from + # [ ext_fabric_setup, ext_multisite_underlay_setup, + # ext_evpn_multisite_overlay_setup ] + profile: + ipv4_subnet: 193.168.2.1/24 # IP address of interface in src fabric with mask + neighbor_ip: 193.168.2.2 # IP address of the interface in dst fabric + src_asn: "{{ ansible_num_asn }}" # BGP ASN in source fabric + dst_asn: "{{ ansible_unnum_asn }}" # BGP ASN in destination fabric + mtu: 9216 # + deploy_dci_tracking: false # optional, default is false + max_paths: 1 # optional, default is 1 + route_tag: 12345 # optional, optional is "" + ebgp_password_enable: true # optional, default is true + ebgp_password: 9BFE3270E19CA112 # optional, required only if ebgp_password_enable flag is true and inherit_from_msd is false + inherit_from_msd: True # optional, required only if ebgp_password_enable flag is true, default is false + ebgp_auth_key_type: 3 # optional, required only if ebpg_password_enable is true and inherit_from_msd + # is false. Default is 3 + # choose from [3 - 3DES, 7 - Cisco ] + peer1_description: "Description of source" # optional, default is "" + peer2_description: "Description of dest" # optional, default is "" + peer1_cmds: # Freeform config for source interface + - no shutdown # optional, default is "" + peer2_cmds: # Freeform config for destination interface + - no shutdown # optional, default is "" + + - dst_fabric: "{{ ansible_unnum_fabric }}" # Destination fabric + src_interface: "{{ intf_1_5 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_5 }}" # Interface on the Destination fabric + src_device: "{{ ansible_num_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_unnum_switch1 }}" # Device on the Destination fabric + template: ext_evpn_multisite_overlay_setup # template to be applied, choose from + # [ ext_fabric_setup, ext_multisite_underlay_setup, + # ext_evpn_multisite_overlay_setup ] + profile: + ipv4_addr: 193.168.3.1 # IP address of interface in src fabric + neighbor_ip: 193.168.3.2 # IP address of the interface in dst fabric + src_asn: "{{ ansible_num_asn }}" # BGP ASN in source fabric + dst_asn: "{{ ansible_unnum_asn }}" # BGP ASN in destination fabric + trm_enabled: false # optional, default is false + bgp_multihop: 5 # optional, default is 5 + ebgp_password_enable: true # optional, default is true + ebgp_password: 9BFE3270E19CA112 # optional, required only if ebgp_password_enable flag is true and inherit_from_msd is false + inherit_from_msd: false # optional, required only if ebgp_password_enable flag is true, default is false + ebgp_auth_key_type: 3 # optional, required only if ebpg_password_enable is true and inherit_from_msd + # is false. Default is 3 + # choose from [3 - 3DES, 7 - Cisco ] + + - dst_fabric: "{{ ansible_ipv6_fabric }}" # Destination fabric + src_interface: "{{ intf_1_7 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_7 }}" # Interface on the Destination fabric + src_device: "{{ ansible_num_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_ipv6_switch1 }}" # Device on the Destination fabric + template: ext_multisite_underlay_setup # template to be applied, choose from + # [ ext_fabric_setup, ext_multisite_underlay_setup, + # ext_evpn_multisite_overlay_setup ] + profile: + ipv4_subnet: 193.168.5.1/24 # IP address of interface in src fabric with mask + neighbor_ip: 193.168.5.2 # IP address of the interface in dst fabric + src_asn: "{{ ansible_num_asn }}" # BGP ASN in source fabric + dst_asn: "{{ ansible_ipv6_asn }}" # BGP ASN in destination fabric + mtu: 9216 # + deploy_dci_tracking: false # optional, default is false + max_paths: 1 # optional, default is 1 + route_tag: 12345 # optional, optional is "" + ebpg_password_enable: True # optional, default is true + ebgp_password: 9BFE3270E19CA112 # optional, required only if ebgp_password_enable flag is true and inherit_from_msd is false + inherit_from_msd: True # optional, required only if ebgp_password_enable flag is true, default is false + ebgp_auth_key_type: 3 # optional, required only if ebpg_password_enable is true and inherit_from_msd + # is false. Default is 3 + # choose from [3 - 3DES, 7 - Cisco ] + peer1_description: "Description of source" # optional, default is "" + peer2_description: "Description of dest" # optional, default is "" + peer1_cmds: # Freeform config for source interface + - no shutdown # optional, default is "" + peer2_cmds: # Freeform config for destination interface + - no shutdown # optional, default is "" + + - dst_fabric: "{{ ansible_ipv6_fabric }}" # Destination fabric + src_interface: "{{ intf_1_8 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_8 }}" # Interface on the Destination fabric + src_device: "{{ ansible_num_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_ipv6_switch1 }}" # Device on the Destination fabric + template: ext_evpn_multisite_overlay_setup # template to be applied, choose from + # [ ext_fabric_setup, ext_multisite_underlay_setup, + # ext_evpn_multisite_overlay_setup ] + profile: + ipv4_addr: 193.168.6.1 # IP address of interface in src fabric + neighbor_ip: 193.168.6.2 # IP address of the interface in dst fabric + src_asn: "{{ ansible_num_asn }}" # BGP ASN in source fabric + dst_asn: "{{ ansible_ipv6_asn }}" # BGP ASN in destination fabric + trm_enabled: false # optional, default is false + bgp_multihop: 5 # optional, default is 5 + ebgp_password_enable: True # optional, default is true + ebgp_password: 9BFE3270E19CA112 # optional, required only if ebgp_password_enable flag is true and inherit_from_msd is false + inherit_from_msd: false # optional, required only if ebgp_password_enable flag is true, default is false + ebgp_auth_key_type: 3 # optional, required only if ebpg_password_enable is true and inherit_from_msd + # is false. Default is 3 + # choose from [3 - 3DES, 7 - Cisco ] + register: result + when: (ansible_num_fabric in ms_fabric_list or ansible_ipv6_fabric in ms_fabric_list or ansible_unnum_fabric in ms_fabric_list) + + - assert: + that: + - 'result.changed == true' + - '(result["diff"][0]["merged"] | length) == 4' + - '(result["diff"][0]["modified"] | length) == 0' + - '(result["diff"][0]["deleted"] | length) == 0' + - '(result["diff"][0]["query"] | length) == 0' + - '(result["diff"][0]["deploy"][0][ "{{ ansible_num_fabric }}" ] | length) == 1' + - '(result["diff"][0]["deploy"][0][ "{{ ansible_unnum_fabric }}" ] | length) == 1' + - '(result["diff"][0]["deploy"][0][ "{{ ansible_ipv6_fabric }}" ] | length) == 1' + when: (ansible_num_fabric in ms_fabric_list or ansible_ipv6_fabric in ms_fabric_list or ansible_unnum_fabric in ms_fabric_list) + + - assert: + that: + - 'item["RETURN_CODE"] == 200' + loop: '{{ result.response }}' + when: (ansible_num_fabric in ms_fabric_list or ansible_ipv6_fabric in ms_fabric_list or ansible_unnum_fabric in ms_fabric_list) + +############################################### +### IDEMPOTENCE ## +############################################### + + - name: Create Links - Idempotence + cisco.dcnm.dcnm_links: *links_merge_with_opt4 + register: result + when: (ansible_num_fabric in ms_fabric_list or ansible_ipv6_fabric in ms_fabric_list or ansible_unnum_fabric in ms_fabric_list) + + - assert: + that: + - 'result.changed == true' + - '(result["diff"][0]["merged"] | length) == 0' + - '(result["diff"][0]["modified"] | length) == 0' + - '(result["diff"][0]["deleted"] | length) == 0' + - '(result["diff"][0]["query"] | length) == 0' + - '(result["diff"][0]["deploy"][0][ "{{ ansible_num_fabric }}" ] | length) == 1' + - '(result["diff"][0]["deploy"][0][ "{{ ansible_unnum_fabric }}" ] | length) == 1' + - '(result["diff"][0]["deploy"][0][ "{{ ansible_ipv6_fabric }}" ] | length) == 1' + when: (ansible_num_fabric in ms_fabric_list or ansible_ipv6_fabric in ms_fabric_list or ansible_unnum_fabric in ms_fabric_list) + + - assert: + that: + - 'item["RETURN_CODE"] == 200' + loop: '{{ result.response }}' + when: (ansible_num_fabric in ms_fabric_list or ansible_ipv6_fabric in ms_fabric_list or ansible_unnum_fabric in ms_fabric_list) + +############################################## +## CLEANUP ## +############################################## + + always: + + - name: Cleanup - Delete Links + cisco.dcnm.dcnm_links: *links_delete + register: result + when: IT_CONTEXT is not defined + + - assert: + that: + - 'item["RETURN_CODE"] == 200' + loop: '{{ result.response }}' + when: IT_CONTEXT is not defined diff --git a/tests/integration/targets/dcnm_links/tests/dcnm/dcnm_links_inter_numbered_modify.yaml b/tests/integration/targets/dcnm_links/tests/dcnm/dcnm_links_inter_numbered_modify.yaml new file mode 100644 index 000000000..7f825e129 --- /dev/null +++ b/tests/integration/targets/dcnm_links/tests/dcnm/dcnm_links_inter_numbered_modify.yaml @@ -0,0 +1,479 @@ +############################################## +## SETUP ## +############################################## + +- name: Remove local log file + local_action: command rm -f dcnm_links.log + +- block: + +############################################## +## DELETE ## +############################################## + + - name: Initial setup - Delete Links on numbered fabric + cisco.dcnm.dcnm_links: &links_delete + state: deleted # choose from [merged, replaced, deleted, query] + src_fabric: "{{ ansible_num_fabric }}" + config: + - dst_fabric: "{{ ansible_unnum_fabric }}" # Destination fabric + src_interface: "{{ intf_1_3 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_3 }}" # Interface on the Destination fabric + src_device: "{{ ansible_num_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_unnum_switch1 }}" # Device on the Destination fabric + + - dst_fabric: "{{ ansible_unnum_fabric }}" # Destination fabric + src_interface: "{{ intf_1_4 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_4 }}" # Interface on the Destination fabric + src_device: "{{ ansible_num_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_unnum_switch1 }}" # Device on the Destination fabric + + - dst_fabric: "{{ ansible_unnum_fabric }}" # Destination fabric + src_interface: "{{ intf_1_5 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_5 }}" # Interface on the Destination fabric + src_device: "{{ ansible_num_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_unnum_switch1 }}" # Device on the Destination fabric + + - dst_fabric: "{{ ansible_ipv6_fabric }}" # Destination fabric + src_interface: "{{ intf_1_6 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_6 }}" # Interface on the Destination fabric + src_device: "{{ ansible_num_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_ipv6_switch1 }}" # Device on the Destination fabric + + - dst_fabric: "{{ ansible_ipv6_fabric }}" # Destination fabric + src_interface: "{{ intf_1_7 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_7 }}" # Interface on the Destination fabric + src_device: "{{ ansible_num_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_ipv6_switch1 }}" # Device on the Destination fabric + + - dst_fabric: "{{ ansible_ipv6_fabric }}" # Destination fabric + src_interface: "{{ intf_1_8 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_8 }}" # Interface on the Destination fabric + src_device: "{{ ansible_num_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_ipv6_switch1 }}" # Device on the Destination fabric + + register: result + + - assert: + that: + - 'item["RETURN_CODE"] == 200' + loop: '{{ result.response }}' + +############################################## +## MERGE ## +############################################## + + - name: Create Links including optional parameters + cisco.dcnm.dcnm_links: &links_merge_with_opt1 + state: merged # choose from [merged, replaced, deleted, query] + src_fabric: "{{ ansible_num_fabric }}" + config: + - dst_fabric: "{{ ansible_unnum_fabric }}" # Destination fabric + src_interface: "{{ intf_1_3 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_3 }}" # Interface on the Destination fabric + src_device: "{{ ansible_num_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_unnum_switch1 }}" # Device on the Destination fabric + template: ext_fabric_setup # template to be applied, choose from + # [ ext_fabric_setup, ext_multisite_underlay_setup, + # ext_evpn_multisite_overlay_setup ] + profile: + ipv4_subnet: 193.168.1.1/24 # IP address of interface in src fabric with mask + neighbor_ip: 193.168.1.2 # IP address of the interface in dst fabric + src_asn: "{{ ansible_num_asn }}" # BGP ASN in source fabric + dst_asn: "{{ ansible_unnum_asn }}" # BGP ASN in destination fabric + mtu: 9216 # + auto_deploy: false # optional, default is false + # Flag that controls auto generation of neighbor VRF Lite configuration + peer1_description: "Description of source" # optional, default is "" + peer2_description: "Description of dest" # optional, default is "" + peer1_cmds: # Freeform config for source interface + - no shutdown # optional, default is "" + peer2_cmds: # Freeform config for destination interface + - no shutdown # optional, default is "" + + - dst_fabric: "{{ ansible_ipv6_fabric }}" # Destination fabric + src_interface: "{{ intf_1_6 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_6 }}" # Interface on the Destination fabric + src_device: "{{ ansible_num_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_ipv6_switch1 }}" # Device on the Destination fabric + template: ext_fabric_setup # template to be applied, choose from + # [ ext_fabric_setup, ext_multisite_underlay_setup, + # ext_evpn_multisite_overlay_setup ] + profile: + ipv4_subnet: 193.168.4.1/24 # IP address of interface in src fabric with mask + neighbor_ip: 193.168.4.2 # IP address of the interface in dst fabric + src_asn: "{{ ansible_num_asn }}" # BGP ASN in source fabric + dst_asn: "{{ ansible_ipv6_asn }}" # BGP ASN in destination fabric + mtu: 9216 # + auto_deploy: false # optional, default is false + # Flag that controls auto generation of neighbor VRF Lite configuration + peer1_description: "Description of source" # optional, default is "" + peer2_description: "Description of dest" # optional, default is "" + peer1_cmds: # Freeform config for source interface + - no shutdown # optional, default is "" + peer2_cmds: # Freeform config for destination interface + - no shutdown # optional, default is "" + register: result + + - assert: + that: + - 'result.changed == true' + - '(result["diff"][0]["merged"] | length) == 2' + - '(result["diff"][0]["modified"] | length) == 0' + - '(result["diff"][0]["deleted"] | length) == 0' + - '(result["diff"][0]["query"] | length) == 0' + - '(result["diff"][0]["deploy"][0][ "{{ ansible_num_fabric }}" ] | length) == 1' + - '(result["diff"][0]["deploy"][0][ "{{ ansible_unnum_fabric }}" ] | length) == 1' + - '(result["diff"][0]["deploy"][0][ "{{ ansible_ipv6_fabric }}" ] | length) == 1' + + - assert: + that: + - 'item["RETURN_CODE"] == 200' + loop: '{{ result.response }}' + +############################################## +## MERGE ## +############################################## + + - name: Create Links (multisite) including optional parameters + cisco.dcnm.dcnm_links: &links_merge_with_opt2 + state: merged # choose from [merged, replaced, deleted, query] + src_fabric: "{{ ansible_num_fabric }}" + config: + - dst_fabric: "{{ ansible_unnum_fabric }}" # Destination fabric + src_interface: "{{ intf_1_4 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_4 }}" # Interface on the Destination fabric + src_device: "{{ ansible_num_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_unnum_switch1 }}" # Device on the Destination fabric + template: ext_multisite_underlay_setup # template to be applied, choose from + # [ ext_fabric_setup, ext_multisite_underlay_setup, + # ext_evpn_multisite_overlay_setup ] + profile: + ipv4_subnet: 193.168.2.1/24 # IP address of interface in src fabric with mask + neighbor_ip: 193.168.2.2 # IP address of the interface in dst fabric + src_asn: "{{ ansible_num_asn }}" # BGP ASN in source fabric + dst_asn: "{{ ansible_unnum_asn }}" # BGP ASN in destination fabric + mtu: 9216 # + deploy_dci_tracking: false # optional, default is false + max_paths: 1 # optional, default is 1 + route_tag: 12345 # optional, optional is "" + ebgp_password_enable: true # optional, default is true + ebgp_password: 9BFE3270E19CA112 # optional, required only if ebgp_password_enable flag is true and inherit_from_msd is false + inherit_from_msd: True # optional, required only if ebgp_password_enable flag is true, default is false + ebgp_auth_key_type: 3 # optional, required only if ebpg_password_enable is true and inherit_from_msd + # is false. Default is 3 + # choose from [3 - 3DES, 7 - Cisco ] + peer1_description: "Description of source" # optional, default is "" + peer2_description: "Description of dest" # optional, default is "" + peer1_cmds: # Freeform config for source interface + - no shutdown # optional, default is "" + peer2_cmds: # Freeform config for destination interface + - no shutdown # optional, default is "" + + - dst_fabric: "{{ ansible_unnum_fabric }}" # Destination fabric + src_interface: "{{ intf_1_5 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_5 }}" # Interface on the Destination fabric + src_device: "{{ ansible_num_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_unnum_switch1 }}" # Device on the Destination fabric + template: ext_evpn_multisite_overlay_setup # template to be applied, choose from + # [ ext_fabric_setup, ext_multisite_underlay_setup, + # ext_evpn_multisite_overlay_setup ] + profile: + ipv4_addr: 193.168.3.1 # IP address of interface in src fabric + neighbor_ip: 193.168.3.2 # IP address of the interface in dst fabric + src_asn: "{{ ansible_num_asn }}" # BGP ASN in source fabric + dst_asn: "{{ ansible_unnum_asn }}" # BGP ASN in destination fabric + trm_enabled: false # optional, default is false + bgp_multihop: 5 # optional, default is 5 + ebgp_password_enable: true # optional, default is true + ebgp_password: 9BFE3270E19CA112 # optional, required only if ebgp_password_enable flag is true and inherit_from_msd is false + inherit_from_msd: false # optional, required only if ebgp_password_enable flag is true, default is false + ebgp_auth_key_type: 3 # optional, required only if ebpg_password_enable is true and inherit_from_msd + # is false. Default is 3 + # choose from [3 - 3DES, 7 - Cisco ] + + - dst_fabric: "{{ ansible_ipv6_fabric }}" # Destination fabric + src_interface: "{{ intf_1_7 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_7 }}" # Interface on the Destination fabric + src_device: "{{ ansible_num_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_ipv6_switch1 }}" # Device on the Destination fabric + template: ext_multisite_underlay_setup # template to be applied, choose from + # [ ext_fabric_setup, ext_multisite_underlay_setup, + # ext_evpn_multisite_overlay_setup ] + profile: + ipv4_subnet: 193.168.5.1/24 # IP address of interface in src fabric with mask + neighbor_ip: 193.168.5.2 # IP address of the interface in dst fabric + src_asn: "{{ ansible_num_asn }}" # BGP ASN in source fabric + dst_asn: "{{ ansible_ipv6_asn }}" # BGP ASN in destination fabric + mtu: 9216 # + deploy_dci_tracking: false # optional, default is false + max_paths: 1 # optional, default is 1 + route_tag: 12345 # optional, optional is "" + ebpg_password_enable: True # optional, default is true + ebgp_password: 9BFE3270E19CA112 # optional, required only if ebgp_password_enable flag is true and inherit_from_msd is false + inherit_from_msd: True # optional, required only if ebgp_password_enable flag is true, default is false + ebgp_auth_key_type: 3 # optional, required only if ebpg_password_enable is true and inherit_from_msd + # is false. Default is 3 + # choose from [3 - 3DES, 7 - Cisco ] + peer1_description: "Description of source" # optional, default is "" + peer2_description: "Description of dest" # optional, default is "" + peer1_cmds: # Freeform config for source interface + - no shutdown # optional, default is "" + peer2_cmds: # Freeform config for destination interface + - no shutdown # optional, default is "" + + - dst_fabric: "{{ ansible_ipv6_fabric }}" # Destination fabric + src_interface: "{{ intf_1_8 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_8 }}" # Interface on the Destination fabric + src_device: "{{ ansible_num_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_ipv6_switch1 }}" # Device on the Destination fabric + template: ext_evpn_multisite_overlay_setup # template to be applied, choose from + # [ ext_fabric_setup, ext_multisite_underlay_setup, + # ext_evpn_multisite_overlay_setup ] + profile: + ipv4_addr: 193.168.6.1 # IP address of interface in src fabric + neighbor_ip: 193.168.6.2 # IP address of the interface in dst fabric + src_asn: "{{ ansible_num_asn }}" # BGP ASN in source fabric + dst_asn: "{{ ansible_ipv6_asn }}" # BGP ASN in destination fabric + trm_enabled: false # optional, default is false + bgp_multihop: 5 # optional, default is 5 + ebgp_password_enable: True # optional, default is true + ebgp_password: 9BFE3270E19CA112 # optional, required only if ebgp_password_enable flag is true and inherit_from_msd is false + inherit_from_msd: false # optional, required only if ebgp_password_enable flag is true, default is false + ebgp_auth_key_type: 3 # optional, required only if ebpg_password_enable is true and inherit_from_msd + # is false. Default is 3 + # choose from [3 - 3DES, 7 - Cisco ] + register: result + when: (ansible_num_fabric in ms_fabric_list or ansible_ipv6_fabric in ms_fabric_list or ansible_unnum_fabric in ms_fabric_list) + + - assert: + that: + - 'result.changed == true' + - '(result["diff"][0]["merged"] | length) == 4' + - '(result["diff"][0]["modified"] | length) == 0' + - '(result["diff"][0]["deleted"] | length) == 0' + - '(result["diff"][0]["query"] | length) == 0' + - '(result["diff"][0]["deploy"][0][ "{{ ansible_num_fabric }}" ] | length) == 1' + - '(result["diff"][0]["deploy"][0][ "{{ ansible_unnum_fabric }}" ] | length) == 1' + - '(result["diff"][0]["deploy"][0][ "{{ ansible_ipv6_fabric }}" ] | length) == 1' + when: (ansible_num_fabric in ms_fabric_list or ansible_ipv6_fabric in ms_fabric_list or ansible_unnum_fabric in ms_fabric_list) + + - assert: + that: + - 'item["RETURN_CODE"] == 200' + loop: '{{ result.response }}' + when: (ansible_num_fabric in ms_fabric_list or ansible_ipv6_fabric in ms_fabric_list or ansible_unnum_fabric in ms_fabric_list) + +############################################## +## MODIFY ## +############################################## + + - name: Merge modified info into existing links + cisco.dcnm.dcnm_links: &links_modify1 + state: merged # choose from [merged, replaced, deleted, query] + src_fabric: "{{ ansible_num_fabric }}" + config: + - dst_fabric: "{{ ansible_unnum_fabric }}" # Destination fabric + src_interface: "{{ intf_1_3 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_3 }}" # Interface on the Destination fabric + src_device: "{{ ansible_num_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_unnum_switch1 }}" # Device on the Destination fabric + template: ext_fabric_setup # template to be applied, choose from + # [ ext_fabric_setup, ext_multisite_underlay_setup, + # ext_evpn_multisite_overlay_setup ] + profile: + ipv4_subnet: 193.168.10.1/24 # IP address of interface in src fabric with mask + neighbor_ip: 193.168.10.2 # IP address of the interface in dst fabric + src_asn: "{{ ansible_num_asn }}" # BGP ASN in source fabric + dst_asn: "{{ ansible_unnum_asn }}" # BGP ASN in destination fabric + mtu: 1216 # + auto_deploy: true # optional, default is false + # Flag that controls auto generation of neighbor VRF Lite configuration + peer1_description: "Description of source - MOD" # optional, default is "" + peer2_description: "Description of dest - MOD" # optional, default is "" + peer1_cmds: # Freeform config for source interface + - cdp enable # optional, default is "" + peer2_cmds: # Freeform config for destination interface + - cdp enable # optional, default is "" + + - dst_fabric: "{{ ansible_ipv6_fabric }}" # Destination fabric + src_interface: "{{ intf_1_6 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_6 }}" # Interface on the Destination fabric + src_device: "{{ ansible_num_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_ipv6_switch1 }}" # Device on the Destination fabric + template: ext_fabric_setup # template to be applied, choose from + # [ ext_fabric_setup, ext_multisite_underlay_setup, + # ext_evpn_multisite_overlay_setup ] + profile: + ipv4_subnet: 193.168.40.1/24 # IP address of interface in src fabric with mask + neighbor_ip: 193.168.40.2 # IP address of the interface in dst fabric + src_asn: "{{ ansible_num_asn }}" # BGP ASN in source fabric + dst_asn: "{{ ansible_ipv6_asn }}" # BGP ASN in destination fabric + mtu: 4216 # + auto_deploy: true # optional, default is false + # Flag that controls auto generation of neighbor VRF Lite configuration + peer1_description: "Description of source" # optional, default is "" + peer2_description: "Description of dest" # optional, default is "" + peer1_cmds: # Freeform config for source interface + - no shutdown # optional, default is "" + peer2_cmds: # Freeform config for destination interface + - no shutdown # optional, default is "" + register: result + + - assert: + that: + - 'result.changed == true' + - '(result["diff"][0]["merged"] | length) == 0' + - '(result["diff"][0]["modified"] | length) == 2' + - '(result["diff"][0]["deleted"] | length) == 0' + - '(result["diff"][0]["query"] | length) == 0' + - '(result["diff"][0]["deploy"][0][ "{{ ansible_num_fabric }}" ] | length) == 1' + - '(result["diff"][0]["deploy"][0][ "{{ ansible_unnum_fabric }}" ] | length) == 1' + - '(result["diff"][0]["deploy"][0][ "{{ ansible_ipv6_fabric }}" ] | length) == 1' + + - assert: + that: + - 'item["RETURN_CODE"] == 200' + loop: '{{ result.response }}' + +############################################## +## MODIFY ## +############################################## + + - name: Merge (multisite) modified info into existing links + cisco.dcnm.dcnm_links: &links_modify2 + state: merged # choose from [merged, replaced, deleted, query] + src_fabric: "{{ ansible_num_fabric }}" + config: + - dst_fabric: "{{ ansible_unnum_fabric }}" # Destination fabric + src_interface: "{{ intf_1_4 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_4 }}" # Interface on the Destination fabric + src_device: "{{ ansible_num_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_unnum_switch1 }}" # Device on the Destination fabric + template: ext_multisite_underlay_setup # template to be applied, choose from + # [ ext_fabric_setup, ext_multisite_underlay_setup, + # ext_evpn_multisite_overlay_setup ] + profile: + ipv4_subnet: 193.168.20.1/24 # IP address of interface in src fabric with mask + neighbor_ip: 193.168.20.2 # IP address of the interface in dst fabric + src_asn: "{{ ansible_num_asn }}" # BGP ASN in source fabric + dst_asn: "{{ ansible_unnum_asn }}" # BGP ASN in destination fabric + mtu: 2216 # + deploy_dci_tracking: true # optional, default is false + max_paths: 2 # optional, default is 1 + route_tag: 11111 # optional, optional is "" + ebgp_password_enable: false # optional, default is true + ebgp_password: 28E71E338DA17111 # optional, required only if ebgp_password_enable flag is true and inherit_from_msd is false + inherit_from_msd: False # optional, required only if ebgp_password_enable flag is true, default is false + ebgp_auth_key_type: 7 # optional, required only if ebpg_password_enable is true and inherit_from_msd + # is false. Default is 3 + # choose from [3 - 3DES, 7 - Cisco ] + peer1_description: "Description of source - MOD" # optional, default is "" + peer2_description: "Description of dest - MOD" # optional, default is "" + + - dst_fabric: "{{ ansible_unnum_fabric }}" # Destination fabric + src_interface: "{{ intf_1_5 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_5 }}" # Interface on the Destination fabric + src_device: "{{ ansible_num_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_unnum_switch1 }}" # Device on the Destination fabric + template: ext_evpn_multisite_overlay_setup # template to be applied, choose from + # [ ext_fabric_setup, ext_multisite_underlay_setup, + # ext_evpn_multisite_overlay_setup ] + profile: + ipv4_addr: 193.168.30.1 # IP address of interface in src fabric + neighbor_ip: 193.168.30.2 # IP address of the interface in dst fabric + src_asn: "{{ ansible_num_asn }}" # BGP ASN in source fabric + dst_asn: "{{ ansible_unnum_asn }}" # BGP ASN in destination fabric + trm_enabled: true # optional, default is false + bgp_multihop: 3 # optional, default is 5 + ebgp_password_enable: false # optional, default is true + ebgp_password: 8F8F790E1CB7AF60 # optional, required only if ebgp_password_enable flag is true + inherit_from_msd: true # optional, required only if ebgp_password_enable flag is true, default is false + ebgp_auth_key_type: 7 # optional, required only if ebpg_password_enable is true and inherit_from_msd + # is false. Default is 3 + # choose from [3 - 3DES, 7 - Cisco ] + + - dst_fabric: "{{ ansible_ipv6_fabric }}" # Destination fabric + src_interface: "{{ intf_1_7 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_7 }}" # Interface on the Destination fabric + src_device: "{{ ansible_num_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_ipv6_switch1 }}" # Device on the Destination fabric + template: ext_multisite_underlay_setup # template to be applied, choose from + # [ ext_fabric_setup, ext_multisite_underlay_setup, + # ext_evpn_multisite_overlay_setup ] + profile: + ipv4_subnet: 193.168.50.1/24 # IP address of interface in src fabric with mask + neighbor_ip: 193.168.50.2 # IP address of the interface in dst fabric + src_asn: "{{ ansible_num_asn }}" # BGP ASN in source fabric + dst_asn: "{{ ansible_ipv6_asn }}" # BGP ASN in destination fabric + mtu: 5216 # + deploy_dci_tracking: false # optional, default is false + max_paths: 1 # optional, default is 1 + route_tag: 12345 # optional, optional is "" + ebpg_password_enable: True # optional, default is true + ebgp_password: 9BFE3270E19CA112 # optional, required only if ebgp_password_enable flag is true and inherit_from_msd is false + inherit_from_msd: false # optional, required only if ebgp_password_enable flag is true, default is false + ebgp_auth_key_type: 7 # optional, required only if ebpg_password_enable is true and inherit_from_msd + # is false. Default is 3 + # choose from [3 - 3DES, 7 - Cisco ] + peer1_description: "Description of source" # optional, default is "" + peer2_description: "Description of dest" # optional, default is "" + + - dst_fabric: "{{ ansible_ipv6_fabric }}" # Destination fabric + src_interface: "{{ intf_1_8 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_8 }}" # Interface on the Destination fabric + src_device: "{{ ansible_num_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_ipv6_switch1 }}" # Device on the Destination fabric + template: ext_evpn_multisite_overlay_setup # template to be applied, choose from + # [ ext_fabric_setup, ext_multisite_underlay_setup, + # ext_evpn_multisite_overlay_setup ] + profile: + ipv4_addr: 193.168.60.1 # IP address of interface in src fabric + neighbor_ip: 193.168.60.2 # IP address of the interface in dst fabric + src_asn: "{{ ansible_num_asn }}" # BGP ASN in source fabric + dst_asn: "{{ ansible_ipv6_asn }}" # BGP ASN in destination fabric + trm_enabled: true # optional, default is false + bgp_multihop: 6 # optional, default is 5 + ebgp_password_enable: True # optional, default is true + ebgp_password: 9BFE3270E19CA112 # optional, required only if ebgp_password_enable flag is true and inherit_from_msd is false + inherit_from_msd: false # optional, required only if ebgp_password_enable flag is true, default is false + ebgp_auth_key_type: 7 # optional, required only if ebpg_password_enable is true and inherit_from_msd + # is false. Default is 3 + # choose from [3 - 3DES, 7 - Cisco ] + register: result + when: (ansible_num_fabric in ms_fabric_list or ansible_ipv6_fabric in ms_fabric_list or ansible_unnum_fabric in ms_fabric_list) + + - assert: + that: + - 'result.changed == true' + - '(result["diff"][0]["merged"] | length) == 0' + - '(result["diff"][0]["modified"] | length) == 4' + - '(result["diff"][0]["deleted"] | length) == 0' + - '(result["diff"][0]["query"] | length) == 0' + - '(result["diff"][0]["deploy"][0][ "{{ ansible_num_fabric }}" ] | length) == 1' + - '(result["diff"][0]["deploy"][0][ "{{ ansible_unnum_fabric }}" ] | length) == 1' + - '(result["diff"][0]["deploy"][0][ "{{ ansible_ipv6_fabric }}" ] | length) == 1' + when: (ansible_num_fabric in ms_fabric_list or ansible_ipv6_fabric in ms_fabric_list or ansible_unnum_fabric in ms_fabric_list) + + - assert: + that: + - 'item["RETURN_CODE"] == 200' + loop: '{{ result.response }}' + when: (ansible_num_fabric in ms_fabric_list or ansible_ipv6_fabric in ms_fabric_list or ansible_unnum_fabric in ms_fabric_list) + +############################################## +## CLEANUP ## +############################################## + + always: + + - name: Cleanup - Delete Links + cisco.dcnm.dcnm_links: *links_delete + register: result + when: IT_CONTEXT is not defined + + - assert: + that: + - 'item["RETURN_CODE"] == 200' + loop: '{{ result.response }}' + when: IT_CONTEXT is not defined diff --git a/tests/integration/targets/dcnm_links/tests/dcnm/dcnm_links_inter_numbered_replace.yaml b/tests/integration/targets/dcnm_links/tests/dcnm/dcnm_links_inter_numbered_replace.yaml new file mode 100644 index 000000000..ece601070 --- /dev/null +++ b/tests/integration/targets/dcnm_links/tests/dcnm/dcnm_links_inter_numbered_replace.yaml @@ -0,0 +1,531 @@ +############################################## +## SETUP ## +############################################## + +- name: Remove local log file + local_action: command rm -f dcnm_links.log + +- block: + +############################################## +## DELETE ## +############################################## + + - name: Initial setup - Delete Links on numbered fabric + cisco.dcnm.dcnm_links: &links_delete + state: deleted # choose from [merged, replaced, deleted, query] + src_fabric: "{{ ansible_num_fabric }}" + config: + - dst_fabric: "{{ ansible_unnum_fabric }}" # Destination fabric + src_interface: "{{ intf_1_3 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_3 }}" # Interface on the Destination fabric + src_device: "{{ ansible_num_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_unnum_switch1 }}" # Device on the Destination fabric + + - dst_fabric: "{{ ansible_unnum_fabric }}" # Destination fabric + src_interface: "{{ intf_1_4 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_4 }}" # Interface on the Destination fabric + src_device: "{{ ansible_num_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_unnum_switch1 }}" # Device on the Destination fabric + + - dst_fabric: "{{ ansible_unnum_fabric }}" # Destination fabric + src_interface: "{{ intf_1_5 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_5 }}" # Interface on the Destination fabric + src_device: "{{ ansible_num_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_unnum_switch1 }}" # Device on the Destination fabric + + - dst_fabric: "{{ ansible_ipv6_fabric }}" # Destination fabric + src_interface: "{{ intf_1_6 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_6 }}" # Interface on the Destination fabric + src_device: "{{ ansible_num_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_ipv6_switch1 }}" # Device on the Destination fabric + + - dst_fabric: "{{ ansible_ipv6_fabric }}" # Destination fabric + src_interface: "{{ intf_1_7 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_7 }}" # Interface on the Destination fabric + src_device: "{{ ansible_num_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_ipv6_switch1 }}" # Device on the Destination fabric + + - dst_fabric: "{{ ansible_ipv6_fabric }}" # Destination fabric + src_interface: "{{ intf_1_8 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_8 }}" # Interface on the Destination fabric + src_device: "{{ ansible_num_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_ipv6_switch1 }}" # Device on the Destination fabric + + register: result + + - assert: + that: + - 'item["RETURN_CODE"] == 200' + loop: '{{ result.response }}' + +############################################## +## MERGE ## +############################################## + + - name: Create Links including optional parameters + cisco.dcnm.dcnm_links: &links_merge_with_opt1 + state: merged # choose from [merged, replaced, deleted, query] + src_fabric: "{{ ansible_num_fabric }}" + config: + - dst_fabric: "{{ ansible_unnum_fabric }}" # Destination fabric + src_interface: "{{ intf_1_3 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_3 }}" # Interface on the Destination fabric + src_device: "{{ ansible_num_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_unnum_switch1 }}" # Device on the Destination fabric + template: ext_fabric_setup # template to be applied, choose from + # [ ext_fabric_setup, ext_multisite_underlay_setup, + # ext_evpn_multisite_overlay_setup ] + profile: + ipv4_subnet: 193.168.1.1/24 # IP address of interface in src fabric with mask + neighbor_ip: 193.168.1.2 # IP address of the interface in dst fabric + src_asn: "{{ ansible_num_asn }}" # BGP ASN in source fabric + dst_asn: "{{ ansible_unnum_asn }}" # BGP ASN in destination fabric + mtu: 9216 # + auto_deploy: false # optional, default is false + # Flag that controls auto generation of neighbor VRF Lite configuration + peer1_description: "Description of source" # optional, default is "" + peer2_description: "Description of dest" # optional, default is "" + peer1_cmds: # Freeform config for source interface + - no shutdown # optional, default is "" + peer2_cmds: # Freeform config for destination interface + - no shutdown # optional, default is "" + + - dst_fabric: "{{ ansible_ipv6_fabric }}" # Destination fabric + src_interface: "{{ intf_1_6 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_6 }}" # Interface on the Destination fabric + src_device: "{{ ansible_num_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_ipv6_switch1 }}" # Device on the Destination fabric + template: ext_fabric_setup # template to be applied, choose from + # [ ext_fabric_setup, ext_multisite_underlay_setup, + # ext_evpn_multisite_overlay_setup ] + profile: + ipv4_subnet: 193.168.4.1/24 # IP address of interface in src fabric with mask + neighbor_ip: 193.168.4.2 # IP address of the interface in dst fabric + src_asn: "{{ ansible_num_asn }}" # BGP ASN in source fabric + dst_asn: "{{ ansible_ipv6_asn }}" # BGP ASN in destination fabric + mtu: 9216 # + auto_deploy: false # optional, default is false + # Flag that controls auto generation of neighbor VRF Lite configuration + peer1_description: "Description of source" # optional, default is "" + peer2_description: "Description of dest" # optional, default is "" + peer1_cmds: # Freeform config for source interface + - no shutdown # optional, default is "" + peer2_cmds: # Freeform config for destination interface + - no shutdown # optional, default is "" + register: result + + - assert: + that: + - 'result.changed == true' + - '(result["diff"][0]["merged"] | length) == 2' + - '(result["diff"][0]["modified"] | length) == 0' + - '(result["diff"][0]["deleted"] | length) == 0' + - '(result["diff"][0]["query"] | length) == 0' + - '(result["diff"][0]["deploy"][0][ "{{ ansible_num_fabric }}" ] | length) == 1' + - '(result["diff"][0]["deploy"][0][ "{{ ansible_unnum_fabric }}" ] | length) == 1' + - '(result["diff"][0]["deploy"][0][ "{{ ansible_ipv6_fabric }}" ] | length) == 1' + + - assert: + that: + - 'item["RETURN_CODE"] == 200' + loop: '{{ result.response }}' + +############################################## +## MERGE ## +############################################## + + - name: Create Links (multisite) including optional parameters + cisco.dcnm.dcnm_links: &links_merge_with_opt2 + state: merged # choose from [merged, replaced, deleted, query] + src_fabric: "{{ ansible_num_fabric }}" + config: + - dst_fabric: "{{ ansible_unnum_fabric }}" # Destination fabric + src_interface: "{{ intf_1_4 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_4 }}" # Interface on the Destination fabric + src_device: "{{ ansible_num_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_unnum_switch1 }}" # Device on the Destination fabric + template: ext_multisite_underlay_setup # template to be applied, choose from + # [ ext_fabric_setup, ext_multisite_underlay_setup, + # ext_evpn_multisite_overlay_setup ] + profile: + ipv4_subnet: 193.168.2.1/24 # IP address of interface in src fabric with mask + neighbor_ip: 193.168.2.2 # IP address of the interface in dst fabric + src_asn: "{{ ansible_num_asn }}" # BGP ASN in source fabric + dst_asn: "{{ ansible_unnum_asn }}" # BGP ASN in destination fabric + mtu: 9216 # + deploy_dci_tracking: false # optional, default is false + max_paths: 1 # optional, default is 1 + route_tag: 12345 # optional, optional is "" + ebgp_password_enable: true # optional, default is true + ebgp_password: 9BFE3270E19CA112 # optional, required only if ebgp_password_enable flag is true and inherit_from_msd is false + inherit_from_msd: True # optional, required only if ebgp_password_enable flag is true, default is false + ebgp_auth_key_type: 3 # optional, required only if ebpg_password_enable is true and inherit_from_msd + # is false. Default is 3 + # choose from [3 - 3DES, 7 - Cisco ] + peer1_description: "Description of source" # optional, default is "" + peer2_description: "Description of dest" # optional, default is "" + peer1_cmds: # Freeform config for source interface + - no shutdown # optional, default is "" + peer2_cmds: # Freeform config for destination interface + - no shutdown # optional, default is "" + + - dst_fabric: "{{ ansible_unnum_fabric }}" # Destination fabric + src_interface: "{{ intf_1_5 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_5 }}" # Interface on the Destination fabric + src_device: "{{ ansible_num_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_unnum_switch1 }}" # Device on the Destination fabric + template: ext_evpn_multisite_overlay_setup # template to be applied, choose from + # [ ext_fabric_setup, ext_multisite_underlay_setup, + # ext_evpn_multisite_overlay_setup ] + profile: + ipv4_addr: 193.168.3.1 # IP address of interface in src fabric + neighbor_ip: 193.168.3.2 # IP address of the interface in dst fabric + src_asn: "{{ ansible_num_asn }}" # BGP ASN in source fabric + dst_asn: "{{ ansible_unnum_asn }}" # BGP ASN in destination fabric + trm_enabled: false # optional, default is false + bgp_multihop: 5 # optional, default is 5 + ebgp_password_enable: true # optional, default is true + ebgp_password: 9BFE3270E19CA112 # optional, required only if ebgp_password_enable flag is true and inherit_from_msd is false + inherit_from_msd: false # optional, required only if ebgp_password_enable flag is true, default is false + ebgp_auth_key_type: 3 # optional, required only if ebpg_password_enable is true and inherit_from_msd + # is false. Default is 3 + # choose from [3 - 3DES, 7 - Cisco ] + + - dst_fabric: "{{ ansible_ipv6_fabric }}" # Destination fabric + src_interface: "{{ intf_1_7 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_7 }}" # Interface on the Destination fabric + src_device: "{{ ansible_num_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_ipv6_switch1 }}" # Device on the Destination fabric + template: ext_multisite_underlay_setup # template to be applied, choose from + # [ ext_fabric_setup, ext_multisite_underlay_setup, + # ext_evpn_multisite_overlay_setup ] + profile: + ipv4_subnet: 193.168.5.1/24 # IP address of interface in src fabric with mask + neighbor_ip: 193.168.5.2 # IP address of the interface in dst fabric + src_asn: "{{ ansible_num_asn }}" # BGP ASN in source fabric + dst_asn: "{{ ansible_ipv6_asn }}" # BGP ASN in destination fabric + mtu: 9216 # + deploy_dci_tracking: false # optional, default is false + max_paths: 1 # optional, default is 1 + route_tag: 12345 # optional, optional is "" + ebpg_password_enable: True # optional, default is true + ebgp_password: 9BFE3270E19CA112 # optional, required only if ebgp_password_enable flag is true and inherit_from_msd is false + inherit_from_msd: True # optional, required only if ebgp_password_enable flag is true, default is false + ebgp_auth_key_type: 3 # optional, required only if ebpg_password_enable is true and inherit_from_msd + # is false. Default is 3 + # choose from [3 - 3DES, 7 - Cisco ] + peer1_description: "Description of source" # optional, default is "" + peer2_description: "Description of dest" # optional, default is "" + peer1_cmds: # Freeform config for source interface + - no shutdown # optional, default is "" + peer2_cmds: # Freeform config for destination interface + - no shutdown # optional, default is "" + + - dst_fabric: "{{ ansible_ipv6_fabric }}" # Destination fabric + src_interface: "{{ intf_1_8 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_8 }}" # Interface on the Destination fabric + src_device: "{{ ansible_num_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_ipv6_switch1 }}" # Device on the Destination fabric + template: ext_evpn_multisite_overlay_setup # template to be applied, choose from + # [ ext_fabric_setup, ext_multisite_underlay_setup, + # ext_evpn_multisite_overlay_setup ] + profile: + ipv4_addr: 193.168.6.1 # IP address of interface in src fabric + neighbor_ip: 193.168.6.2 # IP address of the interface in dst fabric + src_asn: "{{ ansible_num_asn }}" # BGP ASN in source fabric + dst_asn: "{{ ansible_ipv6_asn }}" # BGP ASN in destination fabric + trm_enabled: false # optional, default is false + bgp_multihop: 5 # optional, default is 5 + ebgp_password_enable: True # optional, default is true + ebgp_password: 9BFE3270E19CA112 # optional, required only if ebgp_password_enable flag is true and inherit_from_msd is false + inherit_from_msd: false # optional, required only if ebgp_password_enable flag is true, default is false + ebgp_auth_key_type: 3 # optional, required only if ebpg_password_enable is true and inherit_from_msd + # is false. Default is 3 + # choose from [3 - 3DES, 7 - Cisco ] + register: result + when: (ansible_num_fabric in ms_fabric_list or ansible_ipv6_fabric in ms_fabric_list or ansible_unnum_fabric in ms_fabric_list) + + - assert: + that: + - 'result.changed == true' + - '(result["diff"][0]["merged"] | length) == 4' + - '(result["diff"][0]["modified"] | length) == 0' + - '(result["diff"][0]["deleted"] | length) == 0' + - '(result["diff"][0]["query"] | length) == 0' + - '(result["diff"][0]["deploy"][0][ "{{ ansible_num_fabric }}" ] | length) == 1' + - '(result["diff"][0]["deploy"][0][ "{{ ansible_unnum_fabric }}" ] | length) == 1' + - '(result["diff"][0]["deploy"][0][ "{{ ansible_ipv6_fabric }}" ] | length) == 1' + when: (ansible_num_fabric in ms_fabric_list or ansible_ipv6_fabric in ms_fabric_list or ansible_unnum_fabric in ms_fabric_list) + + - assert: + that: + - 'item["RETURN_CODE"] == 200' + loop: '{{ result.response }}' + when: (ansible_num_fabric in ms_fabric_list or ansible_ipv6_fabric in ms_fabric_list or ansible_unnum_fabric in ms_fabric_list) + +############################################## +## REPLACE ## +############################################## + + - name: Replace existing links + cisco.dcnm.dcnm_links: &links_replace1 + state: replaced # choose from [merged, replaced, deleted, query] + src_fabric: "{{ ansible_num_fabric }}" + config: + - dst_fabric: "{{ ansible_unnum_fabric }}" # Destination fabric + src_interface: "{{ intf_1_3 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_3 }}" # Interface on the Destination fabric + src_device: "{{ ansible_num_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_unnum_switch1 }}" # Device on the Destination fabric + template: ext_fabric_setup # template to be applied, choose from + # [ ext_fabric_setup, ext_multisite_underlay_setup, + # ext_evpn_multisite_overlay_setup ] + profile: + ipv4_subnet: 193.168.10.1/24 # IP address of interface in src fabric with mask + neighbor_ip: 193.168.10.2 # IP address of the interface in dst fabric + src_asn: "{{ ansible_num_asn }}" # BGP ASN in source fabric + dst_asn: "{{ ansible_unnum_asn }}" # BGP ASN in destination fabric + mtu: 1216 # + auto_deploy: true # optional, default is false + # Flag that controls auto generation of neighbor VRF Lite configuration + peer1_description: "Description of source - REP" # optional, default is "" + peer2_description: "Description of dest - REP" # optional, default is "" + peer1_cmds: # Freeform config for source interface + - cdp enable # optional, default is "" + peer2_cmds: # Freeform config for destination interface + - cdp enable # optional, default is "" + + - dst_fabric: "{{ ansible_ipv6_fabric }}" # Destination fabric + src_interface: "{{ intf_1_6 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_6 }}" # Interface on the Destination fabric + src_device: "{{ ansible_num_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_ipv6_switch1 }}" # Device on the Destination fabric + template: ext_fabric_setup # template to be applied, choose from + # [ ext_fabric_setup, ext_multisite_underlay_setup, + # ext_evpn_multisite_overlay_setup ] + profile: + ipv4_subnet: 193.168.40.1/24 # IP address of interface in src fabric with mask + neighbor_ip: 193.168.40.2 # IP address of the interface in dst fabric + src_asn: "{{ ansible_num_asn }}" # BGP ASN in source fabric + dst_asn: "{{ ansible_ipv6_asn }}" # BGP ASN in destination fabric + mtu: 4216 # + auto_deploy: true # optional, default is false + # Flag that controls auto generation of neighbor VRF Lite configuration + peer1_description: "Description of source - REP" # optional, default is "" + peer2_description: "Description of dest - REP" # optional, default is "" + peer1_cmds: # Freeform config for source interface + - no shutdown # optional, default is "" + peer2_cmds: # Freeform config for destination interface + - no shutdown # optional, default is "" + register: result + + - assert: + that: + - 'result.changed == true' + - '(result["diff"][0]["merged"] | length) == 0' + - '(result["diff"][0]["modified"] | length) == 2' + - '(result["diff"][0]["deleted"] | length) == 0' + - '(result["diff"][0]["query"] | length) == 0' + - '(result["diff"][0]["deploy"][0][ "{{ ansible_num_fabric }}" ] | length) == 1' + - '(result["diff"][0]["deploy"][0][ "{{ ansible_unnum_fabric }}" ] | length) == 1' + - '(result["diff"][0]["deploy"][0][ "{{ ansible_ipv6_fabric }}" ] | length) == 1' + + - assert: + that: + - 'item["RETURN_CODE"] == 200' + loop: '{{ result.response }}' + +############################################## +## IDEMPOTENCE ## +############################################## + + - name: Replace Links - Idempotence + cisco.dcnm.dcnm_links: *links_replace1 + register: result + + - assert: + that: + - 'result.changed == true' + - '(result["diff"][0]["merged"] | length) == 0' + - '(result["diff"][0]["modified"] | length) == 0' + - '(result["diff"][0]["deleted"] | length) == 0' + - '(result["diff"][0]["query"] | length) == 0' + - '(result["diff"][0]["deploy"][0][ "{{ ansible_num_fabric }}" ] | length) == 1' + - '(result["diff"][0]["deploy"][0][ "{{ ansible_unnum_fabric }}" ] | length) == 1' + - '(result["diff"][0]["deploy"][0][ "{{ ansible_ipv6_fabric }}" ] | length) == 1' + + - assert: + that: + - 'item["RETURN_CODE"] == 200' + loop: '{{ result.response }}' + + +############################################## +## REPLACE ## +############################################## + + - name: Replace (multisite) existing links + cisco.dcnm.dcnm_links: &links_replace2 + state: replaced # choose from [merged, replaced, deleted, query] + src_fabric: "{{ ansible_num_fabric }}" + config: + - dst_fabric: "{{ ansible_unnum_fabric }}" # Destination fabric + src_interface: "{{ intf_1_4 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_4 }}" # Interface on the Destination fabric + src_device: "{{ ansible_num_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_unnum_switch1 }}" # Device on the Destination fabric + template: ext_multisite_underlay_setup # template to be applied, choose from + # [ ext_fabric_setup, ext_multisite_underlay_setup, + # ext_evpn_multisite_overlay_setup ] + profile: + ipv4_subnet: 193.168.20.1/24 # IP address of interface in src fabric with mask + neighbor_ip: 193.168.20.2 # IP address of the interface in dst fabric + src_asn: "{{ ansible_num_asn }}" # BGP ASN in source fabric + dst_asn: "{{ ansible_unnum_asn }}" # BGP ASN in destination fabric + mtu: 2216 # + deploy_dci_tracking: true # optional, default is false + max_paths: 2 # optional, default is 1 + route_tag: 11111 # optional, optional is "" + ebgp_password_enable: false # optional, default is true + ebgp_password: 28E71E338DA17111 # optional, required only if ebgp_password_enable flag is true and inherit_from_msd is false + inherit_from_msd: False # optional, required only if ebgp_password_enable flag is true, default is false + ebgp_auth_key_type: 7 # optional, required only if ebpg_password_enable is true and inherit_from_msd + # is false. Default is 3 + # choose from [3 - 3DES, 7 - Cisco ] + peer1_description: "Description of source - REP" # optional, default is "" + peer2_description: "Description of dest - REP" # optional, default is "" + + - dst_fabric: "{{ ansible_unnum_fabric }}" # Destination fabric + src_interface: "{{ intf_1_5 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_5 }}" # Interface on the Destination fabric + src_device: "{{ ansible_num_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_unnum_switch1 }}" # Device on the Destination fabric + template: ext_evpn_multisite_overlay_setup # template to be applied, choose from + # [ ext_fabric_setup, ext_multisite_underlay_setup, + # ext_evpn_multisite_overlay_setup ] + profile: + ipv4_addr: 193.168.30.1 # IP address of interface in src fabric + neighbor_ip: 193.168.30.2 # IP address of the interface in dst fabric + src_asn: "{{ ansible_num_asn }}" # BGP ASN in source fabric + dst_asn: "{{ ansible_unnum_asn }}" # BGP ASN in destination fabric + trm_enabled: true # optional, default is false + bgp_multihop: 3 # optional, default is 5 + ebgp_password_enable: false # optional, default is true + ebgp_password: 8F8F790E1CB7AF60 # optional, required only if ebgp_password_enable flag is true and inherit_from_msd is false + inherit_from_msd: true # optional, required only if ebgp_password_enable flag is true, default is false + ebgp_auth_key_type: 7 # optional, required only if ebpg_password_enable is true and inherit_from_msd + # is false. Default is 3 + # choose from [3 - 3DES, 7 - Cisco ] + + - dst_fabric: "{{ ansible_ipv6_fabric }}" # Destination fabric + src_interface: "{{ intf_1_7 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_7 }}" # Interface on the Destination fabric + src_device: "{{ ansible_num_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_ipv6_switch1 }}" # Device on the Destination fabric + template: ext_multisite_underlay_setup # template to be applied, choose from + # [ ext_fabric_setup, ext_multisite_underlay_setup, + # ext_evpn_multisite_overlay_setup ] + profile: + ipv4_subnet: 193.168.50.1/24 # IP address of interface in src fabric with mask + neighbor_ip: 193.168.50.2 # IP address of the interface in dst fabric + src_asn: "{{ ansible_num_asn }}" # BGP ASN in source fabric + dst_asn: "{{ ansible_ipv6_asn }}" # BGP ASN in destination fabric + mtu: 5216 # + deploy_dci_tracking: false # optional, default is false + max_paths: 1 # optional, default is 1 + route_tag: 12345 # optional, optional is "" + ebpg_password_enable: True # optional, default is true + ebgp_password: 34DEA21BE32A757D # optional, required only if ebgp_password_enable flag is true and inherit_from_msd is false + inherit_from_msd: false # optional, required only if ebgp_password_enable flag is true, default is false + ebgp_auth_key_type: 7 # optional, required only if ebpg_password_enable is true and inherit_from_msd + # is false. Default is 3 + # choose from [3 - 3DES, 7 - Cisco ] + peer1_description: "Description of source" # optional, default is "" + peer2_description: "Description of dest" # optional, default is "" + + - dst_fabric: "{{ ansible_ipv6_fabric }}" # Destination fabric + src_interface: "{{ intf_1_8 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_8 }}" # Interface on the Destination fabric + src_device: "{{ ansible_num_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_ipv6_switch1 }}" # Device on the Destination fabric + template: ext_evpn_multisite_overlay_setup # template to be applied, choose from + # [ ext_fabric_setup, ext_multisite_underlay_setup, + # ext_evpn_multisite_overlay_setup ] + profile: + ipv4_addr: 193.168.60.1 # IP address of interface in src fabric + neighbor_ip: 193.168.60.2 # IP address of the interface in dst fabric + src_asn: "{{ ansible_num_asn }}" # BGP ASN in source fabric + dst_asn: "{{ ansible_ipv6_asn }}" # BGP ASN in destination fabric + trm_enabled: true # optional, default is false + bgp_multihop: 6 # optional, default is 5 + ebgp_password_enable: True # optional, default is true + ebgp_password: 0DAEAF7E1F5F0CC6 # optional, required only if ebgp_password_enable flag is true and inherit_from_msd is false + inherit_from_msd: false # optional, required only if ebgp_password_enable flag is true, default is false + ebgp_auth_key_type: 7 # optional, required only if ebpg_password_enable is true and inherit_from_msd + # is false. Default is 3 + # choose from [3 - 3DES, 7 - Cisco ] + register: result + when: (ansible_num_fabric in ms_fabric_list or ansible_ipv6_fabric in ms_fabric_list or ansible_unnum_fabric in ms_fabric_list) + + - assert: + that: + - 'result.changed == true' + - '(result["diff"][0]["merged"] | length) == 0' + - '(result["diff"][0]["modified"] | length) == 4' + - '(result["diff"][0]["deleted"] | length) == 0' + - '(result["diff"][0]["query"] | length) == 0' + - '(result["diff"][0]["deploy"][0][ "{{ ansible_num_fabric }}" ] | length) == 1' + - '(result["diff"][0]["deploy"][0][ "{{ ansible_unnum_fabric }}" ] | length) == 1' + - '(result["diff"][0]["deploy"][0][ "{{ ansible_ipv6_fabric }}" ] | length) == 1' + when: (ansible_num_fabric in ms_fabric_list or ansible_ipv6_fabric in ms_fabric_list or ansible_unnum_fabric in ms_fabric_list) + + - assert: + that: + - 'item["RETURN_CODE"] == 200' + loop: '{{ result.response }}' + when: (ansible_num_fabric in ms_fabric_list or ansible_ipv6_fabric in ms_fabric_list or ansible_unnum_fabric in ms_fabric_list) + +############################################## +## IDEMPOTENCE ## +############################################## + + - name: Replace Links - Idempotence + cisco.dcnm.dcnm_links: *links_replace2 + register: result + when: (ansible_num_fabric in ms_fabric_list or ansible_ipv6_fabric in ms_fabric_list or ansible_unnum_fabric in ms_fabric_list) + + - assert: + that: + - 'result.changed == true' + - '(result["diff"][0]["merged"] | length) == 0' + - '(result["diff"][0]["modified"] | length) == 0' + - '(result["diff"][0]["deleted"] | length) == 0' + - '(result["diff"][0]["query"] | length) == 0' + - '(result["diff"][0]["deploy"][0][ "{{ ansible_num_fabric }}" ] | length) == 1' + - '(result["diff"][0]["deploy"][0][ "{{ ansible_unnum_fabric }}" ] | length) == 1' + - '(result["diff"][0]["deploy"][0][ "{{ ansible_ipv6_fabric }}" ] | length) == 1' + when: (ansible_num_fabric in ms_fabric_list or ansible_ipv6_fabric in ms_fabric_list or ansible_unnum_fabric in ms_fabric_list) + + - assert: + that: + - 'item["RETURN_CODE"] == 200' + loop: '{{ result.response }}' + when: (ansible_num_fabric in ms_fabric_list or ansible_ipv6_fabric in ms_fabric_list or ansible_unnum_fabric in ms_fabric_list) + +############################################## +## CLEANUP ## +############################################## + + always: + + - name: Cleanup - Delete Links + cisco.dcnm.dcnm_links: *links_delete + register: result + when: IT_CONTEXT is not defined + + - assert: + that: + - 'item["RETURN_CODE"] == 200' + loop: '{{ result.response }}' + when: IT_CONTEXT is not defined diff --git a/tests/integration/targets/dcnm_links/tests/dcnm/dcnm_links_inter_query.yaml b/tests/integration/targets/dcnm_links/tests/dcnm/dcnm_links_inter_query.yaml new file mode 100644 index 000000000..f996a7398 --- /dev/null +++ b/tests/integration/targets/dcnm_links/tests/dcnm/dcnm_links_inter_query.yaml @@ -0,0 +1,426 @@ +############################################## +## SETUP ## +############################################## + +- name: Remove local log file + local_action: command rm -f dcnm_links.log + +- block: + +############################################## +## DELETE ## +############################################## + + - name: Initial setup - Delete Links on numbered fabric + cisco.dcnm.dcnm_links: &links_delete + state: deleted # choose from [merged, replaced, deleted, query] + src_fabric: "{{ ansible_num_fabric }}" + config: + - dst_fabric: "{{ ansible_unnum_fabric }}" # Destination fabric + src_interface: "{{ intf_1_3 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_3 }}" # Interface on the Destination fabric + src_device: "{{ ansible_num_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_unnum_switch1 }}" # Device on the Destination fabric + + - dst_fabric: "{{ ansible_unnum_fabric }}" # Destination fabric + src_interface: "{{ intf_1_4 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_4 }}" # Interface on the Destination fabric + src_device: "{{ ansible_num_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_unnum_switch1 }}" # Device on the Destination fabric + + - dst_fabric: "{{ ansible_unnum_fabric }}" # Destination fabric + src_interface: "{{ intf_1_5 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_5 }}" # Interface on the Destination fabric + src_device: "{{ ansible_num_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_unnum_switch1 }}" # Device on the Destination fabric + + - dst_fabric: "{{ ansible_ipv6_fabric }}" # Destination fabric + src_interface: "{{ intf_1_6 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_6 }}" # Interface on the Destination fabric + src_device: "{{ ansible_num_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_ipv6_switch1 }}" # Device on the Destination fabric + + - dst_fabric: "{{ ansible_ipv6_fabric }}" # Destination fabric + src_interface: "{{ intf_1_7 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_7 }}" # Interface on the Destination fabric + src_device: "{{ ansible_num_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_ipv6_switch1 }}" # Device on the Destination fabric + + - dst_fabric: "{{ ansible_ipv6_fabric }}" # Destination fabric + src_interface: "{{ intf_1_8 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_8 }}" # Interface on the Destination fabric + src_device: "{{ ansible_num_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_ipv6_switch1 }}" # Device on the Destination fabric + + register: result + + - assert: + that: + - 'item["RETURN_CODE"] == 200' + loop: '{{ result.response }}' + +############################################## +## MERGE ## +############################################## + + - name: Create Links without including optional parameters + cisco.dcnm.dcnm_links: &links_merge_no_opt + state: merged # choose from [merged, replaced, deleted, query] + src_fabric: "{{ ansible_num_fabric }}" + deploy: false + config: + - dst_fabric: "{{ ansible_unnum_fabric }}" # Destination fabric + src_interface: "{{ intf_1_3 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_3 }}" # Interface on the Destination fabric + src_device: "{{ ansible_num_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_unnum_switch1 }}" # Device on the Destination fabric + template: ext_fabric_setup # template to be applied, choose from + # [ ext_fabric_setup, ext_multisite_underlay_setup, + # ext_evpn_multisite_overlay_setup ] + profile: + ipv4_subnet: 193.168.1.1/24 # IP address of interface in src fabric with mask + neighbor_ip: 193.168.1.2 # IP address of the interface in dst fabric + src_asn: "{{ ansible_num_asn }}" # BGP ASN in source fabric + dst_asn: "{{ ansible_unnum_asn }}" # BGP ASN in destination fabric + mtu: 9216 # + + - dst_fabric: "{{ ansible_unnum_fabric }}" # Destination fabric + src_interface: "{{ intf_1_4 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_4 }}" # Interface on the Destination fabric + src_device: "{{ ansible_num_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_unnum_switch1 }}" # Device on the Destination fabric + template: ext_multisite_underlay_setup # template to be applied, choose from + # [ ext_fabric_setup, ext_multisite_underlay_setup, + # ext_evpn_multisite_overlay_setup ] + profile: + ipv4_subnet: 193.168.2.1/24 # IP address of interface in src fabric with mask + neighbor_ip: 193.168.2.2 # IP address of the interface in dst fabric + src_asn: "{{ ansible_num_asn }}" # BGP ASN in source fabric + dst_asn: "{{ ansible_unnum_asn }}" # BGP ASN in destination fabric + mtu: 9216 # + + - dst_fabric: "{{ ansible_unnum_fabric }}" # Destination fabric + src_interface: "{{ intf_1_5 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_5 }}" # Interface on the Destination fabric + src_device: "{{ ansible_num_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_unnum_switch1 }}" # Device on the Destination fabric + template: ext_evpn_multisite_overlay_setup # template to be applied, choose from + # [ ext_fabric_setup, ext_multisite_underlay_setup, + # ext_evpn_multisite_overlay_setup ] + profile: + ipv4_addr: 193.168.3.1 # IP address of interface in src fabric + neighbor_ip: 193.168.3.2 # IP address of the interface in dst fabric + src_asn: "{{ ansible_num_asn }}" # BGP ASN in source fabric + dst_asn: "{{ ansible_unnum_asn }}" # BGP ASN in destination fabric + trm_enabled: false # optional, default is false + + - dst_fabric: "{{ ansible_ipv6_fabric }}" # Destination fabric + src_interface: "{{ intf_1_6 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_6 }}" # Interface on the Destination fabric + src_device: "{{ ansible_num_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_ipv6_switch1 }}" # Device on the Destination fabric + template: ext_fabric_setup # template to be applied, choose from + # [ ext_fabric_setup, ext_multisite_underlay_setup, + # ext_evpn_multisite_overlay_setup ] + profile: + ipv4_subnet: 193.168.4.1/24 # IP address of interface in src fabric with mask + neighbor_ip: 193.168.4.2 # IP address of the interface in dst fabric + src_asn: "{{ ansible_num_asn }}" # BGP ASN in source fabric + dst_asn: "{{ ansible_ipv6_asn }}" # BGP ASN in destination fabric + mtu: 9216 # + + - dst_fabric: "{{ ansible_ipv6_fabric }}" # Destination fabric + src_interface: "{{ intf_1_7 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_7 }}" # Interface on the Destination fabric + src_device: "{{ ansible_num_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_ipv6_switch1 }}" # Device on the Destination fabric + template: ext_multisite_underlay_setup # template to be applied, choose from + # [ ext_fabric_setup, ext_multisite_underlay_setup, + # ext_evpn_multisite_overlay_setup ] + profile: + ipv4_subnet: 193.168.5.1/24 # IP address of interface in src fabric with mask + neighbor_ip: 193.168.5.2 # IP address of the interface in dst fabric + src_asn: "{{ ansible_num_asn }}" # BGP ASN in source fabric + dst_asn: "{{ ansible_ipv6_asn }}" # BGP ASN in destination fabric + mtu: 9216 # + + - dst_fabric: "{{ ansible_ipv6_fabric }}" # Destination fabric + src_interface: "{{ intf_1_8 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_8 }}" # Interface on the Destination fabric + src_device: "{{ ansible_num_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_ipv6_switch1 }}" # Device on the Destination fabric + template: ext_evpn_multisite_overlay_setup # template to be applied, choose from + # [ ext_fabric_setup, ext_multisite_underlay_setup, + # ext_evpn_multisite_overlay_setup ] + profile: + ipv4_addr: 193.168.6.1 # IP address of interface in src fabric + neighbor_ip: 193.168.6.2 # IP address of the interface in dst fabric + src_asn: "{{ ansible_num_asn }}" # BGP ASN in source fabric + dst_asn: "{{ ansible_ipv6_asn }}" # BGP ASN in destination fabric + trm_enabled: false # optional, default is false + + register: result + + - assert: + that: + - 'result.changed == true' + - '(result["diff"][0]["merged"] | length) == 6' + - '(result["diff"][0]["modified"] | length) == 0' + - '(result["diff"][0]["deleted"] | length) == 0' + - '(result["diff"][0]["query"] | length) == 0' + - '(result["diff"][0]["deploy"] | length) == 0' + + - assert: + that: + - 'item["RETURN_CODE"] == 200' + loop: '{{ result.response }}' + + +############################################### +### QUERY ## +############################################### + + - name: Query Links - with Src Fabric + cisco.dcnm.dcnm_links: + state: query # choose from [merged, replaced, deleted, query] + src_fabric: "{{ ansible_num_fabric }}" + register: result + + - assert: + that: + '(result["response"] | length) >= 6' + + - name: Query Links - with Src & Dst Fabric + cisco.dcnm.dcnm_links: + state: query # choose from [merged, replaced, deleted, query] + src_fabric: "{{ ansible_num_fabric }}" + config: + - dst_fabric: "{{ ansible_unnum_fabric }}" # optional, Destination fabric + register: result + + - assert: + that: + '(result["response"] | length) >= 3' + + - name: Query Links - with Src & Dst Fabric + cisco.dcnm.dcnm_links: + state: query # choose from [merged, replaced, deleted, query] + src_fabric: "{{ ansible_num_fabric }}" + config: + - dst_fabric: "{{ ansible_ipv6_fabric }}" # optional, Destination fabric + register: result + + - assert: + that: + '(result["response"] | length) >= 3' + + - name: Query Links - with Src & Dst Fabric, Src Intf + cisco.dcnm.dcnm_links: + state: query # choose from [merged, replaced, deleted, query] + src_fabric: "{{ ansible_num_fabric }}" + config: + - dst_fabric: "{{ ansible_unnum_fabric }}" # optional, Destination fabric + src_interface: "{{ intf_1_3 }}" # optional, Interface on the Source fabric + register: result + + - assert: + that: + '(result["response"] | length) >= 1' + + - name: Query Links - with Src & Dst Fabric, Src Intf + cisco.dcnm.dcnm_links: + state: query # choose from [merged, replaced, deleted, query] + src_fabric: "{{ ansible_num_fabric }}" + config: + - dst_fabric: "{{ ansible_ipv6_fabric }}" # optional, Destination fabric + src_interface: "{{ intf_1_6 }}" # optional, Interface on the Source fabric + register: result + + - assert: + that: + '(result["response"] | length) >= 1' + + - name: Query Links - with Src & Dst Fabric, Src & Dst Intf + cisco.dcnm.dcnm_links: + state: query # choose from [merged, replaced, deleted, query] + src_fabric: "{{ ansible_num_fabric }}" + config: + - dst_fabric: "{{ ansible_unnum_fabric }}" # optional, Destination fabric + src_interface: "{{ intf_1_3 }}" # optional, Interface on the Source fabric + dst_interface: "{{ intf_1_3 }}" # optional, Interface on the Destination fabric + register: result + + - assert: + that: + '(result["response"] | length) >= 1' + + - name: Query Links - with Src & Dst Fabric, Src & Dst Intf + cisco.dcnm.dcnm_links: + state: query # choose from [merged, replaced, deleted, query] + src_fabric: "{{ ansible_num_fabric }}" + config: + - dst_fabric: "{{ ansible_ipv6_fabric }}" # optional, Destination fabric + src_interface: "{{ intf_1_6 }}" # optional, Interface on the Source fabric + dst_interface: "{{ intf_1_6 }}" # optional, Interface on the Destination fabric + register: result + + - assert: + that: + '(result["response"] | length) >= 1' + + - name: Query Links - with Src & Dst Fabric, Src & Dst Intf, Src Device + cisco.dcnm.dcnm_links: + state: query # choose from [merged, replaced, deleted, query] + src_fabric: "{{ ansible_num_fabric }}" + config: + - dst_fabric: "{{ ansible_unnum_fabric }}" # optional, Destination fabric + src_interface: "{{ intf_1_3 }}" # optional, Interface on the Source fabric + dst_interface: "{{ intf_1_3 }}" # optional, Interface on the Destination fabric + src_device: "{{ ansible_num_switch1 }}" # optional, Device on the Source fabric + register: result + + - assert: + that: + '(result["response"] | length) >= 1' + + - name: Query Links - with Src & Dst Fabric, Src & Dst Intf, Src Device + cisco.dcnm.dcnm_links: + state: query # choose from [merged, replaced, deleted, query] + src_fabric: "{{ ansible_num_fabric }}" + config: + - dst_fabric: "{{ ansible_ipv6_fabric }}" # optional, Destination fabric + src_interface: "{{ intf_1_6 }}" # optional, Interface on the Source fabric + dst_interface: "{{ intf_1_6 }}" # optional, Interface on the Destination fabric + src_device: "{{ ansible_num_switch1 }}" # optional, Device on the Source fabric + register: result + + - assert: + that: + '(result["response"] | length) >= 1' + + - name: Query Links - with Src & Dst Fabric, Src & Dst Intf, Src & Dst Device + cisco.dcnm.dcnm_links: + state: query # choose from [merged, replaced, deleted, query] + src_fabric: "{{ ansible_num_fabric }}" + config: + - dst_fabric: "{{ ansible_unnum_fabric }}" # optional, Destination fabric + src_interface: "{{ intf_1_3 }}" # optional, Interface on the Source fabric + dst_interface: "{{ intf_1_3 }}" # optional, Interface on the Destination fabric + src_device: "{{ ansible_num_switch1 }}" # optional, Device on the Source fabric + dst_device: "{{ ansible_unnum_switch1 }}" # optional, Device on the Destination fabric + register: result + + - assert: + that: + '(result["response"] | length) >= 1' + + - name: Query Links - with Src & Dst Fabric, Src & Dst Intf, Src & Dst Device + cisco.dcnm.dcnm_links: + state: query # choose from [merged, replaced, deleted, query] + src_fabric: "{{ ansible_num_fabric }}" + config: + - dst_fabric: "{{ ansible_ipv6_fabric }}" # optional, Destination fabric + src_interface: "{{ intf_1_6 }}" # optional, Interface on the Source fabric + dst_interface: "{{ intf_1_6 }}" # optional, Interface on the Destination fabric + src_device: "{{ ansible_num_switch1 }}" # optional, Device on the Source fabric + dst_device: "{{ ansible_ipv6_switch1 }}" # optional, Device on the Destination fabric + register: result + + - assert: + that: + '(result["response"] | length) >= 1' + + - name: Query Links - with Src & Dst Fabric, Src & Dst Intf, Src & Dst Device, Template + cisco.dcnm.dcnm_links: + state: query # choose from [merged, replaced, deleted, query] + src_fabric: "{{ ansible_num_fabric }}" + config: + - dst_fabric: "{{ ansible_unnum_fabric }}" # optional, Destination fabric + src_interface: "{{ intf_1_3 }}" # optional, Interface on the Source fabric + dst_interface: "{{ intf_1_3 }}" # optional, Interface on the Destination fabric + src_device: "{{ ansible_num_switch1 }}" # optional, Device on the Source fabric + dst_device: "{{ ansible_unnum_switch1 }}" # optional, Device on the Destination fabric + template: ext_fabric_setup # optional, template to be applied, choose from + # [ ext_fabric_setup, ext_multisite_underlay_setup, + # ext_evpn_multisite_overlay_setup ] + + + - dst_fabric: "{{ ansible_unnum_fabric }}" # optional, Destination fabric + src_interface: "{{ intf_1_4 }}" # optional, Interface on the Source fabric + dst_interface: "{{ intf_1_4 }}" # optional, Interface on the Destination fabric + src_device: "{{ ansible_num_switch1 }}" # optional, Device on the Source fabric + dst_device: "{{ ansible_unnum_switch1 }}" # optional, Device on the Destination fabric + template: ext_multisite_underlay_setup # optional, template to be applied, choose from + # [ ext_fabric_setup, ext_multisite_underlay_setup, + # ext_evpn_multisite_overlay_setup ] + + - dst_fabric: "{{ ansible_unnum_fabric }}" # optional, Destination fabric + src_interface: "{{ intf_1_5 }}" # optional, Interface on the Source fabric + dst_interface: "{{ intf_1_5 }}" # optional, Interface on the Destination fabric + src_device: "{{ ansible_num_switch1 }}" # optional, Device on the Source fabric + dst_device: "{{ ansible_unnum_switch1 }}" # optional, Device on the Destination fabric + template: ext_evpn_multisite_overlay_setup # optional, template to be applied, choose from + # [ ext_fabric_setup, ext_multisite_underlay_setup, + # ext_evpn_multisite_overlay_setup ] + + register: result + + - assert: + that: + '(result["response"] | length) >= 3' + + - name: Query Links - with Src & Dst Fabric, Src & Dst Intf, Src & Dst Device, Template + cisco.dcnm.dcnm_links: + state: query # choose from [merged, replaced, deleted, query] + src_fabric: "{{ ansible_num_fabric }}" + config: + - dst_fabric: "{{ ansible_ipv6_fabric }}" # optional, Destination fabric + src_interface: "{{ intf_1_6 }}" # optional, Interface on the Source fabric + dst_interface: "{{ intf_1_6 }}" # optional, Interface on the Destination fabric + src_device: "{{ ansible_num_switch1 }}" # optional, Device on the Source fabric + dst_device: "{{ ansible_ipv6_switch1 }}" # optional, Device on the Destination fabric + template: ext_fabric_setup # optional, template to be applied, choose from + # [ ext_fabric_setup, ext_multisite_underlay_setup, + # ext_evpn_multisite_overlay_setup ] + + + - dst_fabric: "{{ ansible_ipv6_fabric }}" # optional, Destination fabric + src_interface: "{{ intf_1_7 }}" # optional, Interface on the Source fabric + dst_interface: "{{ intf_1_7 }}" # optional, Interface on the Destination fabric + src_device: "{{ ansible_num_switch1 }}" # optional, Device on the Source fabric + dst_device: "{{ ansible_ipv6_switch1 }}" # optional, Device on the Destination fabric + template: ext_multisite_underlay_setup # optional, template to be applied, choose from + # [ ext_fabric_setup, ext_multisite_underlay_setup, + # ext_evpn_multisite_overlay_setup ] + + - dst_fabric: "{{ ansible_ipv6_fabric }}" # optional, Destination fabric + src_interface: "{{ intf_1_8 }}" # optional, Interface on the Source fabric + dst_interface: "{{ intf_1_8 }}" # optional, Interface on the Destination fabric + src_device: "{{ ansible_num_switch1 }}" # optional, Device on the Source fabric + dst_device: "{{ ansible_ipv6_switch1 }}" # optional, Device on the Destination fabric + template: ext_evpn_multisite_overlay_setup # optional, template to be applied, choose from + # [ ext_fabric_setup, ext_multisite_underlay_setup, + # ext_evpn_multisite_overlay_setup ] + + register: result + + - assert: + that: + '(result["response"] | length) >= 3' + +############################################## +## CLEANUP ## +############################################## + + always: + + - name: Cleanup - Delete Links + cisco.dcnm.dcnm_links: *links_delete + register: result + when: IT_CONTEXT is not defined + + - assert: + that: + - 'item["RETURN_CODE"] == 200' + loop: '{{ result.response }}' + when: IT_CONTEXT is not defined diff --git a/tests/integration/targets/dcnm_links/tests/dcnm/dcnm_links_inter_template_change.yaml b/tests/integration/targets/dcnm_links/tests/dcnm/dcnm_links_inter_template_change.yaml new file mode 100644 index 000000000..d81b5a04d --- /dev/null +++ b/tests/integration/targets/dcnm_links/tests/dcnm/dcnm_links_inter_template_change.yaml @@ -0,0 +1,198 @@ +############################################## +## SETUP ## +############################################## + +- name: Remove local log file + local_action: command rm -f dcnm_links.log + +- block: + +############################################## +## DELETE ## +############################################## + + - name: Initial setup - Delete Links on numbered fabric + cisco.dcnm.dcnm_links: &links_delete + state: deleted # choose from [merged, replaced, deleted, query] + src_fabric: "{{ ansible_num_fabric }}" + config: + - dst_fabric: "{{ ansible_unnum_fabric }}" # Destination fabric + src_interface: "{{ intf_1_3 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_3 }}" # Interface on the Destination fabric + src_device: "{{ ansible_num_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_unnum_switch1 }}" # Device on the Destination fabric + + - dst_fabric: "{{ ansible_unnum_fabric }}" # Destination fabric + src_interface: "{{ intf_1_4 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_4 }}" # Interface on the Destination fabric + src_device: "{{ ansible_num_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_unnum_switch1 }}" # Device on the Destination fabric + + - dst_fabric: "{{ ansible_unnum_fabric }}" # Destination fabric + src_interface: "{{ intf_1_5 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_5 }}" # Interface on the Destination fabric + src_device: "{{ ansible_num_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_unnum_switch1 }}" # Device on the Destination fabric + + - dst_fabric: "{{ ansible_ipv6_fabric }}" # Destination fabric + src_interface: "{{ intf_1_6 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_6 }}" # Interface on the Destination fabric + src_device: "{{ ansible_num_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_ipv6_switch1 }}" # Device on the Destination fabric + + - dst_fabric: "{{ ansible_ipv6_fabric }}" # Destination fabric + src_interface: "{{ intf_1_7 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_7 }}" # Interface on the Destination fabric + src_device: "{{ ansible_num_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_ipv6_switch1 }}" # Device on the Destination fabric + + - dst_fabric: "{{ ansible_ipv6_fabric }}" # Destination fabric + src_interface: "{{ intf_1_8 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_8 }}" # Interface on the Destination fabric + src_device: "{{ ansible_num_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_ipv6_switch1 }}" # Device on the Destination fabric + + register: result + + - assert: + that: + - 'item["RETURN_CODE"] == 200' + loop: '{{ result.response }}' + +############################################## +## MERGE ## +############################################## + + - name: Create Links with ext_fabric_setup template + cisco.dcnm.dcnm_links: + state: merged # choose from [merged, replaced, deleted, query] + src_fabric: "{{ ansible_num_fabric }}" + config: + - dst_fabric: "{{ ansible_unnum_fabric }}" # Destination fabric + src_interface: "{{ intf_1_3 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_3 }}" # Interface on the Destination fabric + src_device: "{{ ansible_num_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_unnum_switch1 }}" # Device on the Destination fabric + template: ext_fabric_setup # template to be applied, choose from + # [ ext_fabric_setup, ext_multisite_underlay_setup, + # ext_evpn_multisite_overlay_setup ] + profile: + ipv4_subnet: 193.168.1.1/24 # IP address of interface in src fabric with mask + neighbor_ip: 193.168.1.2 # IP address of the interface in dst fabric + src_asn: "{{ ansible_num_asn }}" # BGP ASN in source fabric + dst_asn: "{{ ansible_unnum_asn }}" # BGP ASN in destination fabric + mtu: 9216 # + + register: result + + - assert: + that: + - 'result.changed == true' + - '(result["diff"][0]["merged"] | length) == 1' + - '(result["diff"][0]["modified"] | length) == 0' + - '(result["diff"][0]["deleted"] | length) == 0' + - '(result["diff"][0]["query"] | length) == 0' + - '(result["diff"][0]["deploy"][0][ "{{ ansible_num_fabric }}" ] | length) == 1' + - '(result["diff"][0]["deploy"][0][ "{{ ansible_unnum_fabric }}" ] | length) == 1' + + - assert: + that: + - 'item["RETURN_CODE"] == 200' + loop: '{{ result.response }}' + + - name: Modify Links changing template to ext_multisite_underlay_setup + cisco.dcnm.dcnm_links: + state: merged # choose from [merged, replaced, deleted, query] + src_fabric: "{{ ansible_num_fabric }}" + config: + - dst_fabric: "{{ ansible_unnum_fabric }}" # Destination fabric + src_interface: "{{ intf_1_3 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_3 }}" # Interface on the Destination fabric + src_device: "{{ ansible_num_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_unnum_switch1 }}" # Device on the Destination fabric + template: ext_multisite_underlay_setup # template to be applied, choose from + # [ ext_fabric_setup, ext_multisite_underlay_setup, + # ext_evpn_multisite_overlay_setup ] + profile: + ipv4_subnet: 193.168.2.1/24 # IP address of interface in src fabric with mask + neighbor_ip: 193.168.2.2 # IP address of the interface in dst fabric + src_asn: "{{ ansible_num_asn }}" # BGP ASN in source fabric + dst_asn: "{{ ansible_unnum_asn }}" # BGP ASN in destination fabric + mtu: 2216 # + + register: result + when: (ansible_num_fabric in ms_fabric_list or ansible_unnum_fabric in ms_fabric_list) + + - assert: + that: + - 'result.changed == true' + - '(result["diff"][0]["merged"] | length) == 0' + - '(result["diff"][0]["modified"] | length) == 1' + - '(result["diff"][0]["deleted"] | length) == 0' + - '(result["diff"][0]["query"] | length) == 0' + - '(result["diff"][0]["deploy"][0][ "{{ ansible_num_fabric }}" ] | length) == 1' + - '(result["diff"][0]["deploy"][0][ "{{ ansible_unnum_fabric }}" ] | length) == 1' + when: (ansible_num_fabric in ms_fabric_list or ansible_unnum_fabric in ms_fabric_list) + + - assert: + that: + - 'item["RETURN_CODE"] == 200' + loop: '{{ result.response }}' + when: (ansible_num_fabric in ms_fabric_list or ansible_unnum_fabric in ms_fabric_list) + + - name: Modify Links changing template to ext_evpn_multisite_overlay_setup + cisco.dcnm.dcnm_links: + state: merged # choose from [merged, replaced, deleted, query] + src_fabric: "{{ ansible_num_fabric }}" + config: + - dst_fabric: "{{ ansible_unnum_fabric }}" # Destination fabric + src_interface: "{{ intf_1_3 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_3 }}" # Interface on the Destination fabric + src_device: "{{ ansible_num_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_unnum_switch1 }}" # Device on the Destination fabric + template: ext_evpn_multisite_overlay_setup # template to be applied, choose from + # [ ext_fabric_setup, ext_multisite_underlay_setup, + # ext_evpn_multisite_overlay_setup ] + profile: + ipv4_addr: 193.168.3.1 # IP address of interface in src fabric + neighbor_ip: 193.168.3.2 # IP address of the interface in dst fabric + src_asn: "{{ ansible_num_asn }}" # BGP ASN in source fabric + dst_asn: "{{ ansible_unnum_asn }}" # BGP ASN in destination fabric + trm_enabled: false # optional, default is false + + register: result + when: (ansible_num_fabric in ms_fabric_list or ansible_unnum_fabric in ms_fabric_list) + + - assert: + that: + - 'result.changed == true' + - '(result["diff"][0]["merged"] | length) == 0' + - '(result["diff"][0]["modified"] | length) == 1' + - '(result["diff"][0]["deleted"] | length) == 0' + - '(result["diff"][0]["query"] | length) == 0' + - '(result["diff"][0]["deploy"][0][ "{{ ansible_num_fabric }}" ] | length) == 1' + - '(result["diff"][0]["deploy"][0][ "{{ ansible_unnum_fabric }}" ] | length) == 1' + when: (ansible_num_fabric in ms_fabric_list or ansible_unnum_fabric in ms_fabric_list) + + - assert: + that: + - 'item["RETURN_CODE"] == 200' + loop: '{{ result.response }}' + when: (ansible_num_fabric in ms_fabric_list or ansible_unnum_fabric in ms_fabric_list) + +############################################## +## CLEANUP ## +############################################## + + always: + + - name: Cleanup - Delete Links + cisco.dcnm.dcnm_links: *links_delete + register: result + when: IT_CONTEXT is not defined + + - assert: + that: + - 'item["RETURN_CODE"] == 200' + loop: '{{ result.response }}' + when: IT_CONTEXT is not defined diff --git a/tests/integration/targets/dcnm_links/tests/dcnm/dcnm_links_intra_ipv6_delete.yaml b/tests/integration/targets/dcnm_links/tests/dcnm/dcnm_links_intra_ipv6_delete.yaml new file mode 100644 index 000000000..e7f0f157a --- /dev/null +++ b/tests/integration/targets/dcnm_links/tests/dcnm/dcnm_links_intra_ipv6_delete.yaml @@ -0,0 +1,182 @@ +############################################## +## SETUP ## +############################################## + +- name: Remove local log file + local_action: command rm -f dcnm_links.log + +- block: + +############################################## +## DELETE ## +############################################## + + - name: Initial setup - Delete Links + cisco.dcnm.dcnm_links: &links_delete + state: deleted # choose from [merged, replaced, deleted, query] + src_fabric: "{{ ansible_ipv6_fabric }}" + config: + - dst_fabric: "{{ ansible_ipv6_fabric }}" # Destination fabric + src_interface: "{{ intf_1_1 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_1 }}" # Interface on the Destination fabric + src_device: "{{ ansible_ipv6_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_ipv6_switch2 }}" # Device on the Destination fabric + + - dst_fabric: "{{ ansible_ipv6_fabric }}" # Destination fabric + src_interface: "{{ intf_1_2 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_2 }}" # Interface on the Destination fabric + src_device: "{{ ansible_ipv6_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_ipv6_switch2 }}" # Device on the Destination fabric + + - dst_fabric: "{{ ansible_ipv6_fabric }}" # Destination fabric + src_interface: "{{ intf_1_3 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_3 }}" # Interface on the Destination fabric + src_device: "{{ ansible_ipv6_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_ipv6_switch2 }}" # Device on the Destination fabric + + - dst_fabric: "{{ ansible_ipv6_fabric }}" # Destination fabric + src_interface: "{{ intf_1_4 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_4 }}" # Interface on the Destination fabric + src_device: "{{ ansible_ipv6_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_ipv6_switch2 }}" # Device on the Destination fabric + + - dst_fabric: "{{ ansible_ipv6_fabric }}" # Destination fabric + src_interface: "{{ intf_1_5 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_5 }}" # Interface on the Destination fabric + src_device: "{{ ansible_ipv6_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_ipv6_switch2 }}" # Device on the Destination fabric + + register: result + + - assert: + that: + - 'item["RETURN_CODE"] == 200' + loop: '{{ result.response }}' + +############################################## +## MERGE ## +############################################## + + - name: Create Links without including optional parameters + cisco.dcnm.dcnm_links: &links_merge_no_opt + state: merged # choose from [merged, replaced, deleted, query] + src_fabric: "{{ ansible_ipv6_fabric }}" + config: + - dst_fabric: "{{ ansible_ipv6_fabric }}" # Destination fabric + src_interface: "{{ intf_1_1 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_1 }}" # Interface on the Destination fabric + src_device: "{{ ansible_ipv6_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_ipv6_switch2 }}" # Device on the Destination fabric + template: int_intra_fabric_ipv6_link_local # template to be applied, choose from + # [ int_intra_fabric_ipv6_link_local, int_intra_fabric_num_link, + # int_intra_fabric_unnum_link, int_intra_vpc_peer_keep_alive_link, + # int_pre_provision_intra_fabric_link, ios_xe_int_intra_fabric_num_link ] + + profile: + peer1_ipv6_addr: 2080:0201::01 # IP address of the Source interface + peer2_ipv6_addr: 2080:0201::02 # IP address of the Source interface + admin_state: true # choose from [true, false] + mtu: 9216 # + + - dst_fabric: "{{ ansible_ipv6_fabric }}" # Destination fabric + src_interface: "{{ intf_1_2 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_2 }}" # Interface on the Destination fabric + src_device: "{{ ansible_ipv6_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_ipv6_switch2 }}" # Device on the Destination fabric + template: int_pre_provision_intra_fabric_link # template to be applied, choose from + # [ int_intra_fabric_ipv6_link_local, int_intra_fabric_num_link, + # int_intra_fabric_unnum_link, int_intra_vpc_peer_keep_alive_link, + # int_pre_provision_intra_fabric_link, ios_xe_int_intra_fabric_num_link ] + + - dst_fabric: "{{ ansible_ipv6_fabric }}" # Destination fabric + src_interface: "{{ intf_1_3 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_3 }}" # Interface on the Destination fabric + src_device: "{{ ansible_ipv6_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_ipv6_switch2 }}" # Device on the Destination fabric + template: int_intra_fabric_num_link # template to be applied, choose from + # [ int_intra_fabric_ipv6_link_local, int_intra_fabric_num_link, + # int_intra_fabric_unnum_link, int_intra_vpc_peer_keep_alive_link, + # int_pre_provision_intra_fabric_link, ios_xe_int_intra_fabric_num_link ] + + profile: + peer1_ipv6_addr: 2080:0202::01 # IP address of the Source interface + peer2_ipv6_addr: 2080:0202::02 # IP address of the Source interface + admin_state: true # choose from [true, false] + mtu: 9216 # + + register: result + + - assert: + that: + - 'result.changed == true' + - '(result["diff"][0]["merged"] | length) == 3' + - '(result["diff"][0]["modified"] | length) == 0' + - '(result["diff"][0]["deleted"] | length) == 0' + - '(result["diff"][0]["query"] | length) == 0' + - '(result["diff"][0]["deploy"][0][ "{{ ansible_ipv6_fabric }}" ] | length) == 2' + + - assert: + that: + - 'item["RETURN_CODE"] == 200' + loop: '{{ result.response }}' + + +############################################### +### DELETE ## +############################################### + + - name: Delete Links + cisco.dcnm.dcnm_links: *links_delete + register: result + + - assert: + that: + - 'result.changed == true' + - '(result["diff"][0]["merged"] | length) == 0' + - '(result["diff"][0]["modified"] | length) == 0' + - '(result["diff"][0]["deleted"] | length) == 3' + - '(result["diff"][0]["query"] | length) == 0' + - '(result["diff"][0]["deploy"][0][ "{{ ansible_ipv6_fabric }}" ] | length) == 2' + + - assert: + that: + - 'item["RETURN_CODE"] == 200' + loop: '{{ result.response }}' + + +############################################## +## IDEMPOTENCE ## +############################################## + + - name: Delete Links - Idempotence + cisco.dcnm.dcnm_links: *links_delete + register: result + + - assert: + that: + - 'result.changed == false' + - '(result["diff"][0]["merged"] | length) == 0' + - '(result["diff"][0]["modified"] | length) == 0' + - '(result["diff"][0]["deleted"] | length) == 0' + - '(result["diff"][0]["query"] | length) == 0' + - '(result["diff"][0]["deploy"] | length) == 0' + + - assert: + that: + - 'item["RETURN_CODE"] == 200' + loop: '{{ result.response }}' + +############################################## +## CLEANUP ## +############################################## + + always: + + - name: Delete Links + cisco.dcnm.dcnm_links: *links_delete + register: result + + - assert: + that: + - 'item["RETURN_CODE"] == 200' + loop: '{{ result.response }}' diff --git a/tests/integration/targets/dcnm_links/tests/dcnm/dcnm_links_intra_ipv6_merge.yaml b/tests/integration/targets/dcnm_links/tests/dcnm/dcnm_links_intra_ipv6_merge.yaml new file mode 100644 index 000000000..732a3e886 --- /dev/null +++ b/tests/integration/targets/dcnm_links/tests/dcnm/dcnm_links_intra_ipv6_merge.yaml @@ -0,0 +1,284 @@ +############################################## +## SETUP ## +############################################## + +- name: Remove local log file + local_action: command rm -f dcnm_links.log + +- block: + +############################################## +## DELETE ## +############################################## + + - name: Initial setup - Delete Links + cisco.dcnm.dcnm_links: &links_delete + state: deleted # choose from [merged, replaced, deleted, query] + src_fabric: "{{ ansible_ipv6_fabric }}" + config: + - dst_fabric: "{{ ansible_ipv6_fabric }}" # Destination fabric + src_interface: "{{ intf_1_1 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_1 }}" # Interface on the Destination fabric + src_device: "{{ ansible_ipv6_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_ipv6_switch2 }}" # Device on the Destination fabric + + - dst_fabric: "{{ ansible_ipv6_fabric }}" # Destination fabric + src_interface: "{{ intf_1_2 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_2 }}" # Interface on the Destination fabric + src_device: "{{ ansible_ipv6_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_ipv6_switch2 }}" # Device on the Destination fabric + + - dst_fabric: "{{ ansible_ipv6_fabric }}" # Destination fabric + src_interface: "{{ intf_1_3 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_3 }}" # Interface on the Destination fabric + src_device: "{{ ansible_ipv6_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_ipv6_switch2 }}" # Device on the Destination fabric + + - dst_fabric: "{{ ansible_ipv6_fabric }}" # Destination fabric + src_interface: "{{ intf_1_4 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_4 }}" # Interface on the Destination fabric + src_device: "{{ ansible_ipv6_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_ipv6_switch2 }}" # Device on the Destination fabric + + - dst_fabric: "{{ ansible_ipv6_fabric }}" # Destination fabric + src_interface: "{{ intf_1_5 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_5 }}" # Interface on the Destination fabric + src_device: "{{ ansible_ipv6_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_ipv6_switch2 }}" # Device on the Destination fabric + + register: result + + - assert: + that: + - 'item["RETURN_CODE"] == 200' + loop: '{{ result.response }}' + +############################################## +## MERGE ## +############################################## + + - name: Create Links without including optional parameters + cisco.dcnm.dcnm_links: &links_merge_no_opt + state: merged # choose from [merged, replaced, deleted, query] + src_fabric: "{{ ansible_ipv6_fabric }}" + config: + - dst_fabric: "{{ ansible_ipv6_fabric }}" # Destination fabric + src_interface: "{{ intf_1_1 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_1 }}" # Interface on the Destination fabric + src_device: "{{ ansible_ipv6_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_ipv6_switch2 }}" # Device on the Destination fabric + template: int_intra_fabric_ipv6_link_local # template to be applied, choose from + # [ int_intra_fabric_ipv6_link_local, int_intra_fabric_num_link, + # int_intra_fabric_unnum_link, int_intra_vpc_peer_keep_alive_link, + # int_pre_provision_intra_fabric_link, ios_xe_int_intra_fabric_num_link ] + + profile: + peer1_ipv6_addr: 2080:0201::01 # IP address of the Source interface + peer2_ipv6_addr: 2080:0201::02 # IP address of the Source interface + admin_state: true # choose from [true, false] + mtu: 9216 # + + - dst_fabric: "{{ ansible_ipv6_fabric }}" # Destination fabric + src_interface: "{{ intf_1_2 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_2 }}" # Interface on the Destination fabric + src_device: "{{ ansible_ipv6_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_ipv6_switch2 }}" # Device on the Destination fabric + template: int_pre_provision_intra_fabric_link # template to be applied, choose from + # [ int_intra_fabric_ipv6_link_local, int_intra_fabric_num_link, + # int_intra_fabric_unnum_link, int_intra_vpc_peer_keep_alive_link, + # int_pre_provision_intra_fabric_link, ios_xe_int_intra_fabric_num_link ] + + - dst_fabric: "{{ ansible_ipv6_fabric }}" # Destination fabric + src_interface: "{{ intf_1_3 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_3 }}" # Interface on the Destination fabric + src_device: "{{ ansible_ipv6_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_ipv6_switch2 }}" # Device on the Destination fabric + template: int_intra_fabric_num_link # template to be applied, choose from + # [ int_intra_fabric_ipv6_link_local, int_intra_fabric_num_link, + # int_intra_fabric_unnum_link, int_intra_vpc_peer_keep_alive_link, + # int_pre_provision_intra_fabric_link, ios_xe_int_intra_fabric_num_link ] + + profile: + peer1_ipv6_addr: 2080:0202::01 # IP address of the Source interface + peer2_ipv6_addr: 2080:0202::02 # IP address of the Source interface + admin_state: true # choose from [true, false] + mtu: 9216 # + + register: result + + - assert: + that: + - 'result.changed == true' + - '(result["diff"][0]["merged"] | length) == 3' + - '(result["diff"][0]["modified"] | length) == 0' + - '(result["diff"][0]["deleted"] | length) == 0' + - '(result["diff"][0]["query"] | length) == 0' + - '(result["diff"][0]["deploy"][0][ "{{ ansible_ipv6_fabric }}" ] | length) == 2' + + - assert: + that: + - 'item["RETURN_CODE"] == 200' + loop: '{{ result.response }}' + + +############################################## +## IDEMPOTENCE ## +############################################## + + - name: Create Links - Idempotence + cisco.dcnm.dcnm_links: *links_merge_no_opt + register: result + + - assert: + that: + - 'result.changed == true' + - '(result["diff"][0]["merged"] | length) == 0' + - '(result["diff"][0]["modified"] | length) == 0' + - '(result["diff"][0]["deleted"] | length) == 0' + - '(result["diff"][0]["query"] | length) == 0' + - '(result["diff"][0]["deploy"][0][ "{{ ansible_ipv6_fabric }}" ] | length) == 2' + + - assert: + that: + - 'item["RETURN_CODE"] == 200' + loop: '{{ result.response }}' + +############################################## +## DELETE ## +############################################## + + - name: Delete Links + cisco.dcnm.dcnm_links: *links_delete + register: result + + - assert: + that: + - 'item["RETURN_CODE"] == 200' + loop: '{{ result.response }}' + +############################################## +## MERGE ## +############################################## + + - name: Create Links including all optional parameters + cisco.dcnm.dcnm_links: &links_merge_with_opt + state: merged # choose from [merged, replaced, deleted, query] + src_fabric: "{{ ansible_ipv6_fabric }}" + config: + - dst_fabric: "{{ ansible_ipv6_fabric }}" # Destination fabric + src_interface: "{{ intf_1_1 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_1 }}" # Interface on the Destination fabric + src_device: "{{ ansible_ipv6_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_ipv6_switch2 }}" # Device on the Destination fabric + template: int_intra_fabric_ipv6_link_local # template to be applied, choose from + # [ int_intra_fabric_ipv6_link_local, int_intra_fabric_num_link, + # int_intra_fabric_unnum_link, int_intra_vpc_peer_keep_alive_link, + # int_pre_provision_intra_fabric_link, ios_xe_int_intra_fabric_num_link ] + + profile: + peer1_ipv4_addr: 192.169.1.1 # optional, default is "" + peer2_ipv4_addr: 192.169.1.2 # optional, default is "" + peer1_ipv6_addr: 2080:0201::01 # IP address of the Source interface + peer2_ipv6_addr: 2080:0201::02 # IP address of the Source interface + admin_state: true # choose from [true, false] + mtu: 9216 # + peer1_description: "Description of source" # optional, default is "" + peer2_description: "Description of dest" # optional, default is "" + peer1_bfd_echo_disable: false # optional, choose from [true, false] + peer2_bfd_echo_disable: false # optional, choose from [true, false] + enable_macsec: false # optional, choose from [true, false] + peer1_cmds: # Freeform config for source device + - no shutdown # optional, default is "" + peer2_cmds: # Freeform config for destination device + - no shutdown # optional, default is "" + + - dst_fabric: "{{ ansible_ipv6_fabric }}" # Destination fabric + src_interface: "{{ intf_1_2 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_2 }}" # Interface on the Destination fabric + src_device: "{{ ansible_ipv6_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_ipv6_switch2 }}" # Device on the Destination fabric + template: int_pre_provision_intra_fabric_link # template to be applied, choose from + # [ int_intra_fabric_ipv6_link_local, int_intra_fabric_num_link, + # int_intra_fabric_unnum_link, int_intra_vpc_peer_keep_alive_link, + # int_pre_provision_intra_fabric_link, ios_xe_int_intra_fabric_num_link ] + - dst_fabric: "{{ ansible_ipv6_fabric }}" # Destination fabric + src_interface: "{{ intf_1_3 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_3 }}" # Interface on the Destination fabric + src_device: "{{ ansible_ipv6_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_ipv6_switch2 }}" # Device on the Destination fabric + template: int_intra_fabric_num_link # template to be applied, choose from + # [ int_intra_fabric_ipv6_link_local, int_intra_fabric_num_link, + # int_intra_fabric_unnum_link, int_intra_vpc_peer_keep_alive_link, + # int_pre_provision_intra_fabric_link, ios_xe_int_intra_fabric_num_link ] + + profile: + peer1_ipv4_addr: 192.169.2.1 # IPV4 address of the Source interface + peer2_ipv4_addr: 192.169.2.2 # IPV4 address of the Destination interface + peer1_ipv6_addr: 2080:0202::01 # IP address of the Source interface + peer2_ipv6_addr: 2080:0202::02 # IP address of the Source interface + admin_state: true # choose from [true, false] + mtu: 1500 # optional, default is 1500 + peer1_description: "Description of source" # optional, default is "" + peer2_description: "Description of dest" # optional, default is "" + peer1_bfd_echo_disable: false # optional, choose from [true, false] + peer2_bfd_echo_disable: false # optional, choose from [true, false] + enable_macsec: false # optional, choose from [true, false] + peer1_cmds: # Freeform config for source device + - no shutdown # optional, default is "" + peer2_cmds: # Freeform config for destination device + - no shutdown # optional, default is "" + + register: result + + - assert: + that: + - 'result.changed == true' + - '(result["diff"][0]["merged"] | length) == 3' + - '(result["diff"][0]["modified"] | length) == 0' + - '(result["diff"][0]["deleted"] | length) == 0' + - '(result["diff"][0]["query"] | length) == 0' + - '(result["diff"][0]["deploy"][0][ "{{ ansible_ipv6_fabric }}" ] | length) == 2' + + - assert: + that: + - 'item["RETURN_CODE"] == 200' + loop: '{{ result.response }}' + +############################################## +## IDEMPOTENCE ## +############################################## + + - name: Create Links - Idempotence + cisco.dcnm.dcnm_links: *links_merge_with_opt + register: result + + - assert: + that: + - 'result.changed == true' + - '(result["diff"][0]["merged"] | length) == 0' + - '(result["diff"][0]["modified"] | length) == 0' + - '(result["diff"][0]["deleted"] | length) == 0' + - '(result["diff"][0]["query"] | length) == 0' + - '(result["diff"][0]["deploy"][0][ "{{ ansible_ipv6_fabric }}" ] | length) == 2' + + - assert: + that: + - 'item["RETURN_CODE"] == 200' + loop: '{{ result.response }}' + +############################################### +### CLEANUP ## +############################################### + + always: + + - name: Cleanup - Delete Links + cisco.dcnm.dcnm_links: *links_delete + register: result + when: IT_CONTEXT is not defined + + - assert: + that: + - 'item["RETURN_CODE"] == 200' + loop: '{{ result.response }}' + when: IT_CONTEXT is not defined diff --git a/tests/integration/targets/dcnm_links/tests/dcnm/dcnm_links_intra_ipv6_modify.yaml b/tests/integration/targets/dcnm_links/tests/dcnm/dcnm_links_intra_ipv6_modify.yaml new file mode 100644 index 000000000..65021c1fb --- /dev/null +++ b/tests/integration/targets/dcnm_links/tests/dcnm/dcnm_links_intra_ipv6_modify.yaml @@ -0,0 +1,249 @@ +############################################## +## SETUP ## +############################################## + +- name: Remove local log file + local_action: command rm -f dcnm_links.log + +- block: + +############################################## +## DELETE ## +############################################## + + - name: Initial setup - Delete Links + cisco.dcnm.dcnm_links: &links_delete + state: deleted # choose from [merged, replaced, deleted, query] + src_fabric: "{{ ansible_ipv6_fabric }}" + config: + - dst_fabric: "{{ ansible_ipv6_fabric }}" # Destination fabric + src_interface: "{{ intf_1_1 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_1 }}" # Interface on the Destination fabric + src_device: "{{ ansible_ipv6_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_ipv6_switch2 }}" # Device on the Destination fabric + + - dst_fabric: "{{ ansible_ipv6_fabric }}" # Destination fabric + src_interface: "{{ intf_1_2 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_2 }}" # Interface on the Destination fabric + src_device: "{{ ansible_ipv6_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_ipv6_switch2 }}" # Device on the Destination fabric + # [ int_intra_fabric_ipv6_link_local, int_intra_fabric_num_link, + # int_intra_fabric_unnum_link, int_intra_vpc_peer_keep_alive_link, + # int_pre_provision_intra_fabric_link, ios_xe_int_intra_fabric_num_link ] + - dst_fabric: "{{ ansible_ipv6_fabric }}" # Destination fabric + src_interface: "{{ intf_1_3 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_3 }}" # Interface on the Destination fabric + src_device: "{{ ansible_ipv6_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_ipv6_switch2 }}" # Device on the Destination fabric + + - dst_fabric: "{{ ansible_ipv6_fabric }}" # Destination fabric + src_interface: "{{ intf_1_4 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_4 }}" # Interface on the Destination fabric + src_device: "{{ ansible_ipv6_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_ipv6_switch2 }}" # Device on the Destination fabric + + - dst_fabric: "{{ ansible_ipv6_fabric }}" # Destination fabric + src_interface: "{{ intf_1_5 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_5 }}" # Interface on the Destination fabric + src_device: "{{ ansible_ipv6_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_ipv6_switch2 }}" # Device on the Destination fabric + + register: result + + - assert: + that: + - 'item["RETURN_CODE"] == 200' + loop: '{{ result.response }}' + +############################################## +## MERGE ## +############################################## + + - name: Create Links including all optional parameters + cisco.dcnm.dcnm_links: + state: merged # choose from [merged, replaced, deleted, query] + src_fabric: "{{ ansible_ipv6_fabric }}" + config: + - dst_fabric: "{{ ansible_ipv6_fabric }}" # Destination fabric + src_interface: "{{ intf_1_1 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_1 }}" # Interface on the Destination fabric + src_device: "{{ ansible_ipv6_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_ipv6_switch2 }}" # Device on the Destination fabric + template: int_intra_fabric_ipv6_link_local # template to be applied, choose from + # [ int_intra_fabric_ipv6_link_local, int_intra_fabric_num_link, + # int_intra_fabric_unnum_link, int_intra_vpc_peer_keep_alive_link, + # int_pre_provision_intra_fabric_link, ios_xe_int_intra_fabric_num_link ] + + profile: + peer1_ipv4_addr: 192.171.1.1 # optional, default is "" + peer2_ipv4_addr: 192.171.1.2 # optional, default is "" + peer1_ipv6_addr: 2080:0301::01 # IP address of the Source interface + peer2_ipv6_addr: 2080:0301::02 # IP address of the Source interface + admin_state: true # choose from [true, false] + mtu: 9216 # + peer1_description: "Description of source" # optional, default is "" + peer2_description: "Description of dest" # optional, default is "" + peer1_bfd_echo_disable: false # optional, choose from [true, false] + peer2_bfd_echo_disable: false # optional, choose from [true, false] + enable_macsec: false # optional, choose from [true, false] + peer1_cmds: # Freeform config for source device + - no shutdown # optional, default is "" + peer2_cmds: # Freeform config for destination device + - no shutdown # optional, default is "" + + - dst_fabric: "{{ ansible_ipv6_fabric }}" # Destination fabric + src_interface: "{{ intf_1_2 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_2 }}" # Interface on the Destination fabric + src_device: "{{ ansible_ipv6_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_ipv6_switch2 }}" # Device on the Destination fabric + template: int_pre_provision_intra_fabric_link # template to be applied, choose from + # [ int_intra_fabric_ipv6_link_local, int_intra_fabric_num_link, + # int_intra_fabric_unnum_link, int_intra_vpc_peer_keep_alive_link, + # int_pre_provision_intra_fabric_link, ios_xe_int_intra_fabric_num_link ] + - dst_fabric: "{{ ansible_ipv6_fabric }}" # Destination fabric + src_interface: "{{ intf_1_3 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_3 }}" # Interface on the Destination fabric + src_device: "{{ ansible_ipv6_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_ipv6_switch2 }}" # Device on the Destination fabric + template: int_intra_fabric_num_link # template to be applied, choose from + # [ int_intra_fabric_ipv6_link_local, int_intra_fabric_num_link, + # int_intra_fabric_unnum_link, int_intra_vpc_peer_keep_alive_link, + # int_pre_provision_intra_fabric_link, ios_xe_int_intra_fabric_num_link ] + + profile: + peer1_ipv4_addr: 192.172.2.1 # optional, default is "" + peer2_ipv4_addr: 192.172.2.2 # optional, default is "" + peer1_ipv6_addr: 2080:0302::01 # IP address of the Source interface + peer2_ipv6_addr: 2080:0302::02 # IP address of the Source interface + admin_state: true # choose from [true, false] + mtu: 1500 # optional, default is 1500 + peer1_description: "Description of source" # optional, default is "" + peer2_description: "Description of dest" # optional, default is "" + peer1_bfd_echo_disable: false # optional, choose from [true, false] + peer2_bfd_echo_disable: false # optional, choose from [true, false] + enable_macsec: false # optional, choose from [true, false] + peer1_cmds: # Freeform config for source device + - no shutdown # optional, default is "" + peer2_cmds: # Freeform config for destination device + - no shutdown # optional, default is "" + + register: result + + - assert: + that: + - 'result.changed == true' + - 'result.changed == true' + - '(result["diff"][0]["merged"] | length) == 3' + - '(result["diff"][0]["modified"] | length) == 0' + - '(result["diff"][0]["deleted"] | length) == 0' + - '(result["diff"][0]["query"] | length) == 0' + - '(result["diff"][0]["deploy"][0][ "{{ ansible_ipv6_fabric }}" ] | length) == 2' + + - assert: + that: + - 'item["RETURN_CODE"] == 200' + loop: '{{ result.response }}' + + +############################################## +## MODIFY ## +############################################## + + - name: Merge modified info into existing links + cisco.dcnm.dcnm_links: + state: merged # choose from [merged, replaced, deleted, query] + src_fabric: "{{ ansible_ipv6_fabric }}" + config: + - dst_fabric: "{{ ansible_ipv6_fabric }}" # Destination fabric + src_interface: "{{ intf_1_1 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_1 }}" # Interface on the Destination fabric + src_device: "{{ ansible_ipv6_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_ipv6_switch2 }}" # Device on the Destination fabric + template: int_intra_fabric_ipv6_link_local # template to be applied, choose from + # [ int_intra_fabric_ipv6_link_local, int_intra_fabric_num_link, + # int_intra_fabric_unnum_link, int_intra_vpc_peer_keep_alive_link, + # int_pre_provision_intra_fabric_link, ios_xe_int_intra_fabric_num_link ] + + profile: + peer1_ipv4_addr: 193.171.1.1 # optional, default is "" + peer2_ipv4_addr: 193.171.1.2 # optional, default is "" + peer1_ipv6_addr: 2089:0301::01 # IP address of the Source interface + peer2_ipv6_addr: 2089:0301::02 # IP address of the Source interface + admin_state: false # choose from [true, false] + mtu: 1500 # + peer1_description: "Description of source - MOD" # optional, default is "" + peer2_description: "Description of dest - MOD" # optional, default is "" + peer1_bfd_echo_disable: true # optional, choose from [true, false] + peer2_bfd_echo_disable: true # optional, choose from [true, false] + enable_macsec: false # optional, choose from [true, false] + peer1_cmds: # Freeform config for source device + - cdp enable # optional, default is "" + peer2_cmds: # Freeform config for destination device + - cdp enable # optional, default is "" + + - dst_fabric: "{{ ansible_ipv6_fabric }}" # Destination fabric + src_interface: "{{ intf_1_2 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_2 }}" # Interface on the Destination fabric + src_device: "{{ ansible_ipv6_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_ipv6_switch2 }}" # Device on the Destination fabric + template: int_pre_provision_intra_fabric_link # template to be applied, choose from + # [ int_intra_fabric_ipv6_link_local, int_intra_fabric_num_link, + # int_intra_fabric_unnum_link, int_intra_vpc_peer_keep_alive_link, + # int_pre_provision_intra_fabric_link, ios_xe_int_intra_fabric_num_link ] + - dst_fabric: "{{ ansible_ipv6_fabric }}" # Destination fabric + src_interface: "{{ intf_1_3 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_3 }}" # Interface on the Destination fabric + src_device: "{{ ansible_ipv6_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_ipv6_switch2 }}" # Device on the Destination fabric + template: int_intra_fabric_num_link # template to be applied, choose from + # [ int_intra_fabric_ipv6_link_local, int_intra_fabric_num_link, + # int_intra_fabric_unnum_link, int_intra_vpc_peer_keep_alive_link, + # int_pre_provision_intra_fabric_link, ios_xe_int_intra_fabric_num_link ] + + profile: + peer1_ipv4_addr: 193.172.2.1 # optional, default is "" + peer2_ipv4_addr: 193.172.2.2 # optional, default is "" + peer1_ipv6_addr: 2089:0302::01 # IP address of the Source interface + peer2_ipv6_addr: 2089:0302::02 # IP address of the Source interface + admin_state: false # choose from [true, false] + mtu: 1900 # optional, default is 1500 + peer1_description: "Description of source - MOD" # optional, default is "" + peer2_description: "Description of dest - MOD" # optional, default is "" + peer1_bfd_echo_disable: true # optional, choose from [true, false] + peer2_bfd_echo_disable: true # optional, choose from [true, false] + enable_macsec: false # optional, choose from [true, false] + peer1_cmds: # Freeform config for source device + - cdp enable # optional, default is "" + peer2_cmds: # Freeform config for destination device + - cdp enable # optional, default is "" + + register: result + + - assert: + that: + - 'result.changed == true' + - '(result["diff"][0]["merged"] | length) == 0' + - '(result["diff"][0]["modified"] | length) == 2' + - '(result["diff"][0]["deleted"] | length) == 0' + - '(result["diff"][0]["query"] | length) == 0' + - '(result["diff"][0]["deploy"][0][ "{{ ansible_ipv6_fabric }}" ] | length) == 2' + + - assert: + that: + - 'item["RETURN_CODE"] == 200' + loop: '{{ result.response }}' + +############################################## +## CLEANUP ## +############################################## + + - name: Cleanup - Delete Links + cisco.dcnm.dcnm_links: *links_delete + register: result + when: IT_CONTEXT is not defined + + - assert: + that: + - 'item["RETURN_CODE"] == 200' + loop: '{{ result.response }}' + when: IT_CONTEXT is not defined diff --git a/tests/integration/targets/dcnm_links/tests/dcnm/dcnm_links_intra_ipv6_query.yaml b/tests/integration/targets/dcnm_links/tests/dcnm/dcnm_links_intra_ipv6_query.yaml new file mode 100644 index 000000000..27d62e3a2 --- /dev/null +++ b/tests/integration/targets/dcnm_links/tests/dcnm/dcnm_links_intra_ipv6_query.yaml @@ -0,0 +1,263 @@ +############################################## +## SETUP ## +############################################## + +- name: Remove local log file + local_action: command rm -f dcnm_links.log + +- block: + +############################################## +## DELETE ## +############################################## + + - name: Initial setup - Delete Links + cisco.dcnm.dcnm_links: &links_delete + state: deleted # choose from [merged, replaced, deleted, query] + src_fabric: "{{ ansible_ipv6_fabric }}" + config: + - dst_fabric: "{{ ansible_ipv6_fabric }}" # Destination fabric + src_interface: "{{ intf_1_1 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_1 }}" # Interface on the Destination fabric + src_device: "{{ ansible_ipv6_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_ipv6_switch2 }}" # Device on the Destination fabric + + - dst_fabric: "{{ ansible_ipv6_fabric }}" # Destination fabric + src_interface: "{{ intf_1_2 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_2 }}" # Interface on the Destination fabric + src_device: "{{ ansible_ipv6_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_ipv6_switch2 }}" # Device on the Destination fabric + + - dst_fabric: "{{ ansible_ipv6_fabric }}" # Destination fabric + src_interface: "{{ intf_1_3 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_3 }}" # Interface on the Destination fabric + src_device: "{{ ansible_ipv6_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_ipv6_switch2 }}" # Device on the Destination fabric + + - dst_fabric: "{{ ansible_ipv6_fabric }}" # Destination fabric + src_interface: "{{ intf_1_4 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_4 }}" # Interface on the Destination fabric + src_device: "{{ ansible_ipv6_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_ipv6_switch2 }}" # Device on the Destination fabric + + - dst_fabric: "{{ ansible_ipv6_fabric }}" # Destination fabric + src_interface: "{{ intf_1_5 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_5 }}" # Interface on the Destination fabric + src_device: "{{ ansible_ipv6_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_ipv6_switch2 }}" # Device on the Destination fabric + + register: result + + - assert: + that: + - 'item["RETURN_CODE"] == 200' + loop: '{{ result.response }}' + +############################################### +### MERGE ## +############################################### + + - name: Create Links without including optional parameters + cisco.dcnm.dcnm_links: &links_merge_no_opt + state: merged # choose from [merged, replaced, deleted, query] + src_fabric: "{{ ansible_ipv6_fabric }}" + config: + - dst_fabric: "{{ ansible_ipv6_fabric }}" # Destination fabric + src_interface: "{{ intf_1_1 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_1 }}" # Interface on the Destination fabric + src_device: "{{ ansible_ipv6_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_ipv6_switch2 }}" # Device on the Destination fabric + template: int_intra_fabric_ipv6_link_local # template to be applied, choose from + # [ int_intra_fabric_ipv6_link_local, int_intra_fabric_num_link, + # int_intra_fabric_unnum_link, int_intra_vpc_peer_keep_alive_link, + # int_pre_provision_intra_fabric_link, ios_xe_int_intra_fabric_num_link ] + + profile: + peer1_ipv6_addr: 2080:0201::01 # IP address of the Source interface + peer2_ipv6_addr: 2080:0201::02 # IP address of the Source interface + admin_state: true # choose from [true, false] + mtu: 9216 # + + - dst_fabric: "{{ ansible_ipv6_fabric }}" # Destination fabric + src_interface: "{{ intf_1_2 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_2 }}" # Interface on the Destination fabric + src_device: "{{ ansible_ipv6_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_ipv6_switch2 }}" # Device on the Destination fabric + template: int_pre_provision_intra_fabric_link # template to be applied, choose from + # [ int_intra_fabric_ipv6_link_local, int_intra_fabric_num_link, + # int_intra_fabric_unnum_link, int_intra_vpc_peer_keep_alive_link, + # int_pre_provision_intra_fabric_link, ios_xe_int_intra_fabric_num_link ] + + - dst_fabric: "{{ ansible_ipv6_fabric }}" # Destination fabric + src_interface: "{{ intf_1_3 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_3 }}" # Interface on the Destination fabric + src_device: "{{ ansible_ipv6_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_ipv6_switch2 }}" # Device on the Destination fabric + template: int_intra_fabric_num_link # template to be applied, choose from + # [ int_intra_fabric_ipv6_link_local, int_intra_fabric_num_link, + # int_intra_fabric_unnum_link, int_intra_vpc_peer_keep_alive_link, + # int_pre_provision_intra_fabric_link, ios_xe_int_intra_fabric_num_link ] + + profile: + peer1_ipv6_addr: 2080:0202::01 # IP address of the Source interface + peer2_ipv6_addr: 2080:0202::02 # IP address of the Source interface + admin_state: true # choose from [true, false] + mtu: 9216 # + + register: result + + - assert: + that: + - 'result.changed == true' + - '(result["diff"][0]["merged"] | length) == 3' + - '(result["diff"][0]["modified"] | length) == 0' + - '(result["diff"][0]["deleted"] | length) == 0' + - '(result["diff"][0]["query"] | length) == 0' + - '(result["diff"][0]["deploy"][0][ "{{ ansible_ipv6_fabric }}" ] | length) == 2' + + - assert: + that: + - 'item["RETURN_CODE"] == 200' + loop: '{{ result.response }}' + +############################################### +### QUERY ## +############################################### + + - name: Query Links - with Src Fabric + cisco.dcnm.dcnm_links: + state: query # choose from [merged, replaced, deleted, query] + src_fabric: "{{ ansible_ipv6_fabric }}" + register: result + + - assert: + that: + '(result["response"] | length) >= 3' + + - name: Query Links - with Src & Dst Fabric + cisco.dcnm.dcnm_links: + state: query # choose from [merged, replaced, deleted, query] + src_fabric: "{{ ansible_ipv6_fabric }}" + config: + - dst_fabric: "{{ ansible_ipv6_fabric }}" # optional, Destination fabric + register: result + + - assert: + that: + '(result["response"] | length) >= 3' + + - name: Query Links - with Src & Dst Fabric, Src Intf + cisco.dcnm.dcnm_links: + state: query # choose from [merged, replaced, deleted, query] + src_fabric: "{{ ansible_ipv6_fabric }}" + config: + - dst_fabric: "{{ ansible_ipv6_fabric }}" # optional, Destination fabric + src_interface: "{{ intf_1_1 }}" # optional, Interface on the Source fabric + register: result + + - assert: + that: + '(result["response"] | length) >= 1' + + - name: Query Links - with Src & Dst Fabric, Src & Dst Intf + cisco.dcnm.dcnm_links: + state: query # choose from [merged, replaced, deleted, query] + src_fabric: "{{ ansible_ipv6_fabric }}" + config: + - dst_fabric: "{{ ansible_ipv6_fabric }}" # optional, Destination fabric + src_interface: "{{ intf_1_1 }}" # optional, Interface on the Source fabric + dst_interface: "{{ intf_1_1 }}" # optional, Interface on the Destination fabric + register: result + + - assert: + that: + '(result["response"] | length) >= 1' + + - name: Query Links - with Src & Dst Fabric, Src & Dst Intf, Src Device + cisco.dcnm.dcnm_links: + state: query # choose from [merged, replaced, deleted, query] + src_fabric: "{{ ansible_ipv6_fabric }}" + config: + - dst_fabric: "{{ ansible_ipv6_fabric }}" # optional, Destination fabric + src_interface: "{{ intf_1_1 }}" # optional, Interface on the Source fabric + dst_interface: "{{ intf_1_1 }}" # optional, Interface on the Destination fabric + src_device: "{{ ansible_ipv6_switch1 }}" # optional, Device on the Source fabric + register: result + + - assert: + that: + '(result["response"] | length) >= 1' + + - name: Query Links - with Src & Dst Fabric, Src & Dst Intf, Src & Dst Device + cisco.dcnm.dcnm_links: + state: query # choose from [merged, replaced, deleted, query] + src_fabric: "{{ ansible_ipv6_fabric }}" + config: + - dst_fabric: "{{ ansible_ipv6_fabric }}" # optional, Destination fabric + src_interface: "{{ intf_1_1 }}" # optional, Interface on the Source fabric + dst_interface: "{{ intf_1_1 }}" # optional, Interface on the Destination fabric + src_device: "{{ ansible_ipv6_switch1 }}" # optional, Device on the Source fabric + dst_device: "{{ ansible_ipv6_switch2 }}" # optional, Device on the Destination fabric + register: result + + - assert: + that: + '(result["response"] | length) >= 1' + + - name: Query Links - with Src & Dst Fabric, Src & Dst Intf, Src & Dst Device, Template + cisco.dcnm.dcnm_links: + state: query # choose from [merged, replaced, deleted, query] + src_fabric: "{{ ansible_ipv6_fabric }}" + config: + + - dst_fabric: "{{ ansible_ipv6_fabric }}" # optional, Destination fabric + src_interface: "{{ intf_1_1 }}" # optional, Interface on the Source fabric + dst_interface: "{{ intf_1_1 }}" # optional, Interface on the Destination fabric + src_device: "{{ ansible_ipv6_switch1 }}" # optional, Device on the Source fabric + dst_device: "{{ ansible_ipv6_switch2 }}" # optional, Device on the Destination fabric + template: int_intra_fabric_ipv6_link_local # optional, template to be applied, choose from + # [ int_intra_fabric_ipv6_link_local, int_intra_fabric_num_link, + # int_intra_fabric_unnum_link, int_intra_vpc_peer_keep_alive_link, + # int_pre_provision_intra_fabric_link, ios_xe_int_intra_fabric_num_link ] + + - dst_fabric: "{{ ansible_ipv6_fabric }}" # optional, Destination fabric + src_interface: "{{ intf_1_2 }}" # optional, Interface on the Source fabric + dst_interface: "{{ intf_1_2 }}" # optional, Interface on the Destination fabric + src_device: "{{ ansible_ipv6_switch1 }}" # optional, Device on the Source fabric + dst_device: "{{ ansible_ipv6_switch2 }}" # optional, Device on the Destination fabric + template: int_pre_provision_intra_fabric_link # optional, template to be applied, choose from + # [ int_intra_fabric_ipv6_link_local, int_intra_fabric_num_link, + # int_intra_fabric_unnum_link, int_intra_vpc_peer_keep_alive_link, + # int_pre_provision_intra_fabric_link, ios_xe_int_intra_fabric_num_link ] + + - dst_fabric: "{{ ansible_ipv6_fabric }}" # optional, Destination fabric + src_interface: "{{ intf_1_3 }}" # optional, Interface on the Source fabric + dst_interface: "{{ intf_1_3 }}" # optional, Interface on the Destination fabric + src_device: "{{ ansible_ipv6_switch1 }}" # optional, Device on the Source fabric + dst_device: "{{ ansible_ipv6_switch2 }}" # optional, Device on the Destination fabric + template: int_intra_fabric_num_link # optional, template to be applied, choose from + # [ int_intra_fabric_ipv6_link_local, int_intra_fabric_num_link, + # int_intra_fabric_unnum_link, int_intra_vpc_peer_keep_alive_link, + # int_pre_provision_intra_fabric_link, ios_xe_int_intra_fabric_num_link ] + register: result + + - assert: + that: + '(result["response"] | length) >= 3' + +############################################### +### CLEANUP ## +############################################### + + always: + + - name: Cleanup - Delete Links + cisco.dcnm.dcnm_links: *links_delete + register: result + when: IT_CONTEXT is not defined + + - assert: + that: + - 'item["RETURN_CODE"] == 200' + loop: '{{ result.response }}' + when: IT_CONTEXT is not defined diff --git a/tests/integration/targets/dcnm_links/tests/dcnm/dcnm_links_intra_ipv6_replace.yaml b/tests/integration/targets/dcnm_links/tests/dcnm/dcnm_links_intra_ipv6_replace.yaml new file mode 100644 index 000000000..a3d7039a5 --- /dev/null +++ b/tests/integration/targets/dcnm_links/tests/dcnm/dcnm_links_intra_ipv6_replace.yaml @@ -0,0 +1,261 @@ +############################################## +## SETUP ## +############################################## + +- name: Remove local log file + local_action: command rm -f dcnm_links.log + +- block: + +############################################## +## DELETE ## +############################################## + + - name: Initial setup - Delete Links + cisco.dcnm.dcnm_links: &links_delete + state: deleted # choose from [merged, replaced, deleted, query] + src_fabric: "{{ ansible_ipv6_fabric }}" + config: + - dst_fabric: "{{ ansible_ipv6_fabric }}" # Destination fabric + src_interface: "{{ intf_1_1 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_1 }}" # Interface on the Destination fabric + src_device: "{{ ansible_ipv6_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_ipv6_switch2 }}" # Device on the Destination fabric + + - dst_fabric: "{{ ansible_ipv6_fabric }}" # Destination fabric + src_interface: "{{ intf_1_2 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_2 }}" # Interface on the Destination fabric + src_device: "{{ ansible_ipv6_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_ipv6_switch2 }}" # Device on the Destination fabric + + - dst_fabric: "{{ ansible_ipv6_fabric }}" # Destination fabric + src_interface: "{{ intf_1_3 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_3 }}" # Interface on the Destination fabric + src_device: "{{ ansible_ipv6_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_ipv6_switch2 }}" # Device on the Destination fabric + + - dst_fabric: "{{ ansible_ipv6_fabric }}" # Destination fabric + src_interface: "{{ intf_1_4 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_4 }}" # Interface on the Destination fabric + src_device: "{{ ansible_ipv6_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_ipv6_switch2 }}" # Device on the Destination fabric + + - dst_fabric: "{{ ansible_ipv6_fabric }}" # Destination fabric + src_interface: "{{ intf_1_5 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_5 }}" # Interface on the Destination fabric + src_device: "{{ ansible_ipv6_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_ipv6_switch2 }}" # Device on the Destination fabric + + register: result + + - assert: + that: + - 'item["RETURN_CODE"] == 200' + loop: '{{ result.response }}' + +############################################## +## MERGE ## +############################################## + + - name: Create Links including all optional parameters + cisco.dcnm.dcnm_links: &links_merge_with_opt + state: merged # choose from [merged, replaced, deleted, query] + src_fabric: "{{ ansible_ipv6_fabric }}" + config: + - dst_fabric: "{{ ansible_ipv6_fabric }}" # Destination fabric + src_interface: "{{ intf_1_1 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_1 }}" # Interface on the Destination fabric + src_device: "{{ ansible_ipv6_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_ipv6_switch2 }}" # Device on the Destination fabric + template: int_intra_fabric_ipv6_link_local # template to be applied, choose from + # [ int_intra_fabric_ipv6_link_local, int_intra_fabric_num_link, + # int_intra_fabric_unnum_link, int_intra_vpc_peer_keep_alive_link, + # int_pre_provision_intra_fabric_link, ios_xe_int_intra_fabric_num_link ] + + profile: + peer1_ipv4_addr: 192.169.1.1 # optional, default is "" + peer2_ipv4_addr: 192.169.1.2 # optional, default is "" + peer1_ipv6_addr: 2080:0201::01 # IP address of the Source interface + peer2_ipv6_addr: 2080:0201::02 # IP address of the Source interface + admin_state: true # choose from [true, false] + mtu: 9216 # + peer1_description: "Description of source" # optional, default is "" + peer2_description: "Description of dest" # optional, default is "" + peer1_bfd_echo_disable: false # optional, choose from [true, false] + peer2_bfd_echo_disable: false # optional, choose from [true, false] + enable_macsec: false # optional, choose from [true, false] + peer1_cmds: # Freeform config for source device + - no shutdown # optional, default is "" + peer2_cmds: # Freeform config for destination device + - no shutdown # optional, default is "" + + - dst_fabric: "{{ ansible_ipv6_fabric }}" # Destination fabric + src_interface: "{{ intf_1_2 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_2 }}" # Interface on the Destination fabric + src_device: "{{ ansible_ipv6_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_ipv6_switch2 }}" # Device on the Destination fabric + template: int_pre_provision_intra_fabric_link # template to be applied, choose from + # [ int_intra_fabric_ipv6_link_local, int_intra_fabric_num_link, + # int_intra_fabric_unnum_link, int_intra_vpc_peer_keep_alive_link, + # int_pre_provision_intra_fabric_link, ios_xe_int_intra_fabric_num_link ] + - dst_fabric: "{{ ansible_ipv6_fabric }}" # Destination fabric + src_interface: "{{ intf_1_3 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_3 }}" # Interface on the Destination fabric + src_device: "{{ ansible_ipv6_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_ipv6_switch2 }}" # Device on the Destination fabric + template: int_intra_fabric_num_link # template to be applied, choose from + # [ int_intra_fabric_ipv6_link_local, int_intra_fabric_num_link, + # int_intra_fabric_unnum_link, int_intra_vpc_peer_keep_alive_link, + # int_pre_provision_intra_fabric_link, ios_xe_int_intra_fabric_num_link ] + + profile: + peer1_ipv4_addr: 192.169.2.1 # IPV4 address of the Source interface + peer2_ipv4_addr: 192.169.2.2 # IPV4 address of the Destination interface + peer1_ipv6_addr: 2080:0202::01 # IP address of the Source interface + peer2_ipv6_addr: 2080:0202::02 # IP address of the Source interface + admin_state: true # choose from [true, false] + mtu: 1500 # optional, default is 1500 + peer1_description: "Description of source" # optional, default is "" + peer2_description: "Description of dest" # optional, default is "" + peer1_bfd_echo_disable: false # optional, choose from [true, false] + peer2_bfd_echo_disable: false # optional, choose from [true, false] + enable_macsec: false # optional, choose from [true, false] + peer1_cmds: # Freeform config for source device + - no shutdown # optional, default is "" + peer2_cmds: # Freeform config for destination device + - no shutdown # optional, default is "" + + register: result + + - assert: + that: + - 'result.changed == true' + - '(result["diff"][0]["merged"] | length) == 3' + - '(result["diff"][0]["modified"] | length) == 0' + - '(result["diff"][0]["deleted"] | length) == 0' + - '(result["diff"][0]["query"] | length) == 0' + - '(result["diff"][0]["deploy"][0][ "{{ ansible_ipv6_fabric }}" ] | length) == 2' + + - assert: + that: + - 'item["RETURN_CODE"] == 200' + loop: '{{ result.response }}' + +############################################## +## REPLACE ## +############################################## + + - name: Replace Links + cisco.dcnm.dcnm_links: &links_replace + state: replaced # choose from [merged, replaced, deleted, query] + src_fabric: "{{ ansible_ipv6_fabric }}" + config: + - dst_fabric: "{{ ansible_ipv6_fabric }}" # Destination fabric + src_interface: "{{ intf_1_1 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_1 }}" # Interface on the Destination fabric + src_device: "{{ ansible_ipv6_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_ipv6_switch2 }}" # Device on the Destination fabric + template: int_intra_fabric_ipv6_link_local # template to be applied, choose from + # [ int_intra_fabric_ipv6_link_local, int_intra_fabric_num_link, + # int_intra_fabric_unnum_link, int_intra_vpc_peer_keep_alive_link, + # int_pre_provision_intra_fabric_link, ios_xe_int_intra_fabric_num_link ] + + profile: + peer1_ipv4_addr: 193.169.1.1 # optional, default is "" + peer2_ipv4_addr: 193.169.1.2 # optional, default is "" + peer1_ipv6_addr: 2089:0201::01 # IP address of the Source interface + peer2_ipv6_addr: 2089:0201::02 # IP address of the Source interface + admin_state: true # choose from [true, false] + mtu: 3216 # + enable_macsec: false # optional, choose from [true, false] + peer1_cmds: # Freeform config for source device + - cdp enable # optional, default is "" + peer2_cmds: # Freeform config for destination device + - cdp enable # optional, default is "" + + - dst_fabric: "{{ ansible_ipv6_fabric }}" # Destination fabric + src_interface: "{{ intf_1_2 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_2 }}" # Interface on the Destination fabric + src_device: "{{ ansible_ipv6_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_ipv6_switch2 }}" # Device on the Destination fabric + template: int_pre_provision_intra_fabric_link # template to be applied, choose from + # [ int_intra_fabric_ipv6_link_local, int_intra_fabric_num_link, + # int_intra_fabric_unnum_link, int_intra_vpc_peer_keep_alive_link, + # int_pre_provision_intra_fabric_link, ios_xe_int_intra_fabric_num_link ] + - dst_fabric: "{{ ansible_ipv6_fabric }}" # Destination fabric + src_interface: "{{ intf_1_3 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_3 }}" # Interface on the Destination fabric + src_device: "{{ ansible_ipv6_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_ipv6_switch2 }}" # Device on the Destination fabric + template: int_intra_fabric_num_link # template to be applied, choose from + # [ int_intra_fabric_ipv6_link_local, int_intra_fabric_num_link, + # int_intra_fabric_unnum_link, int_intra_vpc_peer_keep_alive_link, + # int_pre_provision_intra_fabric_link, ios_xe_int_intra_fabric_num_link ] + + profile: + peer1_ipv4_addr: 193.169.2.1 # optional, default is "" + peer2_ipv4_addr: 193.169.2.2 # optional, default is "" + peer1_ipv6_addr: 2089:0202::01 # IP address of the Source interface + peer2_ipv6_addr: 2089:0202::02 # IP address of the Source interface + admin_state: true # choose from [true, false] + mtu: 2000 # + peer1_description: "Description of source" # optional, default is "" + peer2_description: "Description of dest" # optional, default is "" + peer1_bfd_echo_disable: false # optional, choose from [true, false] + peer2_bfd_echo_disable: false # optional, choose from [true, false] + enable_macsec: false # optional, choose from [true, false] + + register: result + + - assert: + that: + - 'result.changed == true' + - '(result["diff"][0]["merged"] | length) == 0' + - '(result["diff"][0]["modified"] | length) == 2' + - '(result["diff"][0]["deleted"] | length) == 0' + - '(result["diff"][0]["query"] | length) == 0' + - '(result["diff"][0]["deploy"][0][ "{{ ansible_ipv6_fabric }}" ] | length) == 2' + + - assert: + that: + - 'item["RETURN_CODE"] == 200' + loop: '{{ result.response }}' + +############################################## +## IDEMPOTENCE ## +############################################## + + - name: Repalce Links - Idempotence + cisco.dcnm.dcnm_links: *links_replace + register: result + + - assert: + that: + - 'result.changed == true' + - '(result["diff"][0]["merged"] | length) == 0' + - '(result["diff"][0]["modified"] | length) == 0' + - '(result["diff"][0]["deleted"] | length) == 0' + - '(result["diff"][0]["query"] | length) == 0' + - '(result["diff"][0]["deploy"][0][ "{{ ansible_ipv6_fabric }}" ] | length) == 2' + + - assert: + that: + - 'item["RETURN_CODE"] == 200' + loop: '{{ result.response }}' + +############################################### +### CLEANUP ## +############################################### + + always: + + - name: Cleanup - Delete Links + cisco.dcnm.dcnm_links: *links_delete + register: result + when: IT_CONTEXT is not defined + + - assert: + that: + - 'item["RETURN_CODE"] == 200' + loop: '{{ result.response }}' + when: IT_CONTEXT is not defined diff --git a/tests/integration/targets/dcnm_links/tests/dcnm/dcnm_links_intra_missing_params.yaml b/tests/integration/targets/dcnm_links/tests/dcnm/dcnm_links_intra_missing_params.yaml new file mode 100644 index 000000000..18ab96f9a --- /dev/null +++ b/tests/integration/targets/dcnm_links/tests/dcnm/dcnm_links_intra_missing_params.yaml @@ -0,0 +1,366 @@ +############################################## +## SETUP ## +############################################## + +- name: Remove local log file + local_action: command rm -f dcnm_links.log + +- block: + +############################################## +## MERGE ## +############################################## + + - name: Create Links with invalid template name + cisco.dcnm.dcnm_links: + src_fabric: "{{ ansible_num_fabric }}" + state: merged # choose from [merged, replaced, deleted, query] + config: + - dst_fabric: "{{ ansible_num_fabric }}" # Destination fabric + src_interface: "{{ intf_1_1 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_1 }}" # Interface on the Destination fabric + src_device: "{{ ansible_num_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_num_switch2 }}" # Device on the Destination fabric + template: int_intra_fabric_invalid_temp # template to be applied, choose from + # [ int_intra_fabric_ipv6_link_local, int_intra_fabric_num_link, + # int_intra_fabric_unnum_link, int_intra_vpc_peer_keep_alive_link, + # int_pre_provision_intra_fabric_link, ios_xe_int_intra_fabric_num_link ] + + profile: + peer1_ipv4_addr: 192.168.1.1 # IP address of the Source interface + peer2_ipv4_addr: 192.168.1.2 # IP address of the Destination interface + admin_state: true # optional, choose from [true, false], default is true + mtu: 9216 # + ignore_errors: yes + register: result + + - name: Create Links without source fabric + cisco.dcnm.dcnm_links: + state: merged # choose from [merged, replaced, deleted, query] + config: + - dst_fabric: "{{ ansible_num_fabric }}" # Destination fabric + src_interface: "{{ intf_1_1 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_1 }}" # Interface on the Destination fabric + src_device: "{{ ansible_num_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_num_switch2 }}" # Device on the Destination fabric + template: int_intra_fabric_num_link # template to be applied, choose from + # [ int_intra_fabric_ipv6_link_local, int_intra_fabric_num_link, + # int_intra_fabric_unnum_link, int_intra_vpc_peer_keep_alive_link, + # int_pre_provision_intra_fabric_link, ios_xe_int_intra_fabric_num_link ] + + profile: + peer1_ipv4_addr: 192.168.1.1 # IP address of the Source interface + peer2_ipv4_addr: 192.168.1.2 # IP address of the Destination interface + admin_state: true # optional, choose from [true, false], default is true + mtu: 9216 # + ignore_errors: yes + register: result + + - name: Create Links without destination fabric + cisco.dcnm.dcnm_links: + state: merged # choose from [merged, replaced, deleted, query] + src_fabric: "{{ ansible_num_fabric }}" + config: + - src_interface: "{{ intf_1_1 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_1 }}" # Interface on the Destination fabric + src_device: "{{ ansible_num_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_num_switch2 }}" # Device on the Destination fabric + template: int_intra_fabric_num_link # template to be applied, choose from + # [ int_intra_fabric_ipv6_link_local, int_intra_fabric_num_link, + # int_intra_fabric_unnum_link, int_intra_vpc_peer_keep_alive_link, + # int_pre_provision_intra_fabric_link, ios_xe_int_intra_fabric_num_link ] + + profile: + peer1_ipv4_addr: 192.168.1.1 # IP address of the Source interface + peer2_ipv4_addr: 192.168.1.2 # IP address of the Destination interface + admin_state: true # optional, choose from [true, false], default is true + mtu: 9216 # + ignore_errors: yes + register: result + + - name: Create Links without source interface + cisco.dcnm.dcnm_links: + state: merged # choose from [merged, replaced, deleted, query] + src_fabric: "{{ ansible_num_fabric }}" + config: + - dst_fabric: "{{ ansible_num_fabric }}" # Destination fabric + dst_interface: "{{ intf_1_1 }}" # Interface on the Destination fabric + src_device: "{{ ansible_num_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_num_switch2 }}" # Device on the Destination fabric + template: int_intra_fabric_num_link # template to be applied, choose from + # [ int_intra_fabric_ipv6_link_local, int_intra_fabric_num_link, + # int_intra_fabric_unnum_link, int_intra_vpc_peer_keep_alive_link, + # int_pre_provision_intra_fabric_link, ios_xe_int_intra_fabric_num_link ] + + profile: + peer1_ipv4_addr: 192.168.1.1 # IP address of the Source interface + peer2_ipv4_addr: 192.168.1.2 # IP address of the Destination interface + admin_state: true # optional, choose from [true, false], default is true + mtu: 9216 # + ignore_errors: yes + register: result + + - name: Create Links without destination interface + cisco.dcnm.dcnm_links: + state: merged # choose from [merged, replaced, deleted, query] + src_fabric: "{{ ansible_num_fabric }}" + config: + - dst_fabric: "{{ ansible_num_fabric }}" # Destination fabric + src_interface: "{{ intf_1_1 }}" # Interface on the Source fabric + src_device: "{{ ansible_num_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_num_switch2 }}" # Device on the Destination fabric + template: int_pre_provision_intra_fabric_link # template to be applied, choose from + # [ int_intra_fabric_ipv6_link_local, int_intra_fabric_num_link, + # int_intra_fabric_unnum_link, int_intra_vpc_peer_keep_alive_link, + # int_pre_provision_intra_fabric_link, ios_xe_int_intra_fabric_num_link ] + ignore_errors: yes + register: result + + - name: Create Links without source device + cisco.dcnm.dcnm_links: + state: merged # choose from [merged, replaced, deleted, query] + src_fabric: "{{ ansible_num_fabric }}" + config: + - dst_fabric: "{{ ansible_num_fabric }}" # Destination fabric + src_interface: "{{ intf_1_1 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_1 }}" # Interface on the Destination fabric + dst_device: "{{ ansible_num_switch2 }}" # Device on the Destination fabric + template: ios_xe_int_intra_fabric_num_link # template to be applied, choose from + # [ int_intra_fabric_ipv6_link_local, int_intra_fabric_num_link, + # int_intra_fabric_unnum_link, int_intra_vpc_peer_keep_alive_link, + # int_pre_provision_intra_fabric_link, ios_xe_int_intra_fabric_num_link ] + + profile: + peer1_ipv4_addr: 192.168.1.1 # IPV4 address of the Source interface + peer2_ipv4_addr: 192.168.1.2 # IPV4 address of the Destination interface + peer1_ipv6_addr: 2080:21::01 # optional, default is "" + peer2_ipv6_addr: 2080:21::02 # optional, default is "" + admin_state: true # optional, choose from [true, false], default is true + mtu: 1500 # optional, default is 1500 + ignore_errors: yes + register: result + + - name: Create Links without destination device + cisco.dcnm.dcnm_links: + state: merged # choose from [merged, replaced, deleted, query] + src_fabric: "{{ ansible_num_fabric }}" + config: + - dst_fabric: "{{ ansible_num_fabric }}" # Destination fabric + src_interface: "{{ intf_1_1 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_1 }}" # Interface on the Destination fabric + src_device: "{{ ansible_num_switch1 }}" # Device on the Source fabric + template: int_intra_vpc_peer_keep_alive_link # template to be applied, choose from + # [ int_intra_fabric_ipv6_link_local, int_intra_fabric_num_link, + # int_intra_fabric_unnum_link, int_intra_vpc_peer_keep_alive_link, + # int_pre_provision_intra_fabric_link, ios_xe_int_intra_fabric_num_link ] + + profile: + peer1_ipv4_addr: 192.168.1.1 # IPV4 address of the Source interface + peer2_ipv4_addr: 192.168.1.2 # IPV4 address of the Destination interface + peer1_ipv6_addr: 2080:11::01 # optional, default is "" + peer2_ipv6_addr: 2080:11::02 # optional, default is "" + admin_state: true # optional, choose from [true, false], default is true + mtu: 9216 # + ignore_errors: yes + register: result + + - name: Create Links without template + cisco.dcnm.dcnm_links: + state: merged # choose from [merged, replaced, deleted, query] + src_fabric: "{{ ansible_num_fabric }}" + config: + - dst_fabric: "{{ ansible_num_fabric }}" # Destination fabric + src_interface: "{{ intf_1_1 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_1 }}" # Interface on the Destination fabric + src_device: "{{ ansible_num_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_num_switch2 }}" # Device on the Destination fabric + profile: + peer1_ipv4_addr: 192.168.1.1 # IPV4 address of the Source interface + peer2_ipv4_addr: 192.168.1.2 # IPV4 address of the Destination interface + peer1_ipv6_addr: 2080:11::01 # optional, default is "" + peer2_ipv6_addr: 2080:11::02 # optional, default is "" + admin_state: true # optional, choose from [true, false], default is true + mtu: 9216 # + ignore_errors: yes + register: result + + - name: Create Links without peer1 IPV6 + cisco.dcnm.dcnm_links: + state: merged # choose from [merged, replaced, deleted, query] + src_fabric: "{{ ansible_ipv6_fabric }}" + config: + - dst_fabric: "{{ ansible_ipv6_fabric }}" # Destination fabric + src_interface: "{{ intf_1_1 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_1 }}" # Interface on the Destination fabric + src_device: "{{ ansible_ipv6_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_ipv6_switch2 }}" # Device on the Destination fabric + template: int_intra_vpc_peer_keep_alive_link # template to be applied, choose from + # [ int_intra_fabric_ipv6_link_local, int_intra_fabric_num_link, + # int_intra_fabric_unnum_link, int_intra_vpc_peer_keep_alive_link, + # int_pre_provision_intra_fabric_link, ios_xe_int_intra_fabric_num_link ] + + profile: + peer1_ipv4_addr: 192.168.1.1 # IPV4 address of the Source interface + peer2_ipv4_addr: 192.168.1.2 # IPV4 address of the Destination interface + peer2_ipv6_addr: 2080:11::02 # optional, default is "" + admin_state: true # optional, choose from [true, false], default is true + mtu: 9216 + ignore_errors: yes + register: result + + - name: Create Links without peer2 IPV6 + cisco.dcnm.dcnm_links: + state: merged # choose from [merged, replaced, deleted, query] + src_fabric: "{{ ansible_ipv6_fabric }}" + config: + - dst_fabric: "{{ ansible_ipv6_fabric }}" # Destination fabric + src_interface: "{{ intf_1_1 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_1 }}" # Interface on the Destination fabric + src_device: "{{ ansible_ipv6_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_ipv6_switch2 }}" # Device on the Destination fabric + template: int_intra_vpc_peer_keep_alive_link # template to be applied, choose from + # [ int_intra_fabric_ipv6_link_local, int_intra_fabric_num_link, + # int_intra_fabric_unnum_link, int_intra_vpc_peer_keep_alive_link, + # int_pre_provision_intra_fabric_link, ios_xe_int_intra_fabric_num_link ] + + profile: + peer1_ipv4_addr: 192.168.1.1 # IPV4 address of the Source interface + peer2_ipv4_addr: 192.168.1.2 # IPV4 address of the Destination interface + peer1_ipv6_addr: 2080:11::01 # optional, default is "" + admin_state: true # optional, choose from [true, false], default is true + mtu: 9216 + ignore_errors: yes + register: result + + - name: Create Links without MTU + cisco.dcnm.dcnm_links: + state: merged # choose from [merged, replaced, deleted, query] + src_fabric: "{{ ansible_num_fabric }}" + config: + - dst_fabric: "{{ ansible_num_fabric }}" # Destination fabric + src_interface: "{{ intf_1_1 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_1 }}" # Interface on the Destination fabric + src_device: "{{ ansible_num_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_num_switch2 }}" # Device on the Destination fabric + template: int_intra_vpc_peer_keep_alive_link # template to be applied, choose from + # [ int_intra_fabric_ipv6_link_local, int_intra_fabric_num_link, + # int_intra_fabric_unnum_link, int_intra_vpc_peer_keep_alive_link, + # int_pre_provision_intra_fabric_link, ios_xe_int_intra_fabric_num_link ] + + profile: + peer1_ipv4_addr: 192.168.1.1 # IPV4 address of the Source interface + peer2_ipv4_addr: 192.168.1.2 # IPV4 address of the Destination interface + peer1_ipv6_addr: 2080:11::01 # optional, default is "" + peer2_ipv6_addr: 2080:11::02 # optional, default is "" + admin_state: true # optional, choose from [true, false], default is true + ignore_errors: yes + register: result + + - name: Create Links without peer1 IPV4 + cisco.dcnm.dcnm_links: + state: merged # choose from [merged, replaced, deleted, query] + src_fabric: "{{ ansible_num_fabric }}" + config: + - dst_fabric: "{{ ansible_num_fabric }}" # Destination fabric + src_interface: "{{ intf_1_1 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_1 }}" # Interface on the Destination fabric + src_device: "{{ ansible_num_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_num_switch2 }}" # Device on the Destination fabric + template: int_intra_vpc_peer_keep_alive_link # template to be applied, choose from + # [ int_intra_fabric_ipv6_link_local, int_intra_fabric_num_link, + # int_intra_fabric_unnum_link, int_intra_vpc_peer_keep_alive_link, + # int_pre_provision_intra_fabric_link, ios_xe_int_intra_fabric_num_link ] + + profile: + peer2_ipv4_addr: 192.168.1.2 # IPV4 address of the Destination interface + peer1_ipv6_addr: 2080:11::01 # optional, default is "" + peer2_ipv6_addr: 2080:11::02 # optional, default is "" + admin_state: true # optional, choose from [true, false], default is true + mtu: 9216 # + ignore_errors: yes + register: result + + - name: Create Links without peer2 IPV4 + cisco.dcnm.dcnm_links: + state: merged # choose from [merged, replaced, deleted, query] + src_fabric: "{{ ansible_num_fabric }}" + config: + - dst_fabric: "{{ ansible_num_fabric }}" # Destination fabric + src_interface: "{{ intf_1_1 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_1 }}" # Interface on the Destination fabric + src_device: "{{ ansible_num_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_num_switch2 }}" # Device on the Destination fabric + template: int_intra_vpc_peer_keep_alive_link # template to be applied, choose from + # [ int_intra_fabric_ipv6_link_local, int_intra_fabric_num_link, + # int_intra_fabric_unnum_link, int_intra_vpc_peer_keep_alive_link, + # int_pre_provision_intra_fabric_link, ios_xe_int_intra_fabric_num_link ] + + profile: + peer1_ipv4_addr: 192.168.1.1 # IPV4 address of the Destination interface + peer1_ipv6_addr: 2080:11::01 # optional, default is "" + peer2_ipv6_addr: 2080:11::02 # optional, default is "" + admin_state: true # optional, choose from [true, false], default is true + mtu: 9216 # + ignore_errors: yes + register: result + + - name: Create Links without Admin State + cisco.dcnm.dcnm_links: + state: merged # choose from [merged, replaced, deleted, query] + src_fabric: "{{ ansible_num_fabric }}" + config: + - dst_fabric: "{{ ansible_num_fabric }}" # Destination fabric + src_interface: "{{ intf_1_1 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_1 }}" # Interface on the Destination fabric + src_device: "{{ ansible_num_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_num_switch2 }}" # Device on the Destination fabric + template: int_intra_vpc_peer_keep_alive_link # template to be applied, choose from + # [ int_intra_fabric_ipv6_link_local, int_intra_fabric_num_link, + # int_intra_fabric_unnum_link, int_intra_vpc_peer_keep_alive_link, + # int_pre_provision_intra_fabric_link, ios_xe_int_intra_fabric_num_link ] + + profile: + peer1_ipv4_addr: 192.168.1.1 # IPV4 address of the Destination interface + peer2_ipv4_addr: 192.168.1.2 # IPV4 address of the Destination interface + peer1_ipv6_addr: 2080:11::01 # optional, default is "" + peer2_ipv6_addr: 2080:11::02 # optional, default is "" + mtu: 9216 # + ignore_errors: yes + register: result + + - name: Create Links without profile + cisco.dcnm.dcnm_links: + state: merged # choose from [merged, replaced, deleted, query] + src_fabric: "{{ ansible_num_fabric }}" + config: + - dst_fabric: "{{ ansible_num_fabric }}" # Destination fabric + src_interface: "{{ intf_1_1 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_1 }}" # Interface on the Destination fabric + src_device: "{{ ansible_num_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_num_switch2 }}" # Device on the Destination fabric + template: int_intra_vpc_peer_keep_alive_link # template to be applied, choose from + # [ int_intra_fabric_ipv6_link_local, int_intra_fabric_num_link, + # int_intra_fabric_unnum_link, int_intra_vpc_peer_keep_alive_link, + # int_pre_provision_intra_fabric_link, ios_xe_int_intra_fabric_num_link ] + ignore_errors: yes + register: result + + - name: Create Links with profile parameters + cisco.dcnm.dcnm_links: + src_fabric: "{{ ansible_num_fabric }}" + state: merged # choose from [merged, replaced, deleted, query] + config: + - dst_fabric: "{{ ansible_num_fabric }}" # Destination fabric + src_interface: "{{ intf_1_1 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_1 }}" # Interface on the Destination fabric + src_device: "{{ ansible_num_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_num_switch2 }}" # Device on the Destination fabric + template: int_intra_vpc_peer_keep_alive_link # template to be applied, choose from + # [ int_intra_fabric_ipv6_link_local, int_intra_fabric_num_link, + # int_intra_fabric_unnum_link, int_intra_vpc_peer_keep_alive_link, + # int_pre_provision_intra_fabric_link, ios_xe_int_intra_fabric_num_link ] + + profile: + dummy: 11 + ignore_errors: yes + register: result diff --git a/tests/integration/targets/dcnm_links/tests/dcnm/dcnm_links_intra_numbered_delete.yaml b/tests/integration/targets/dcnm_links/tests/dcnm/dcnm_links_intra_numbered_delete.yaml new file mode 100644 index 000000000..2319a3655 --- /dev/null +++ b/tests/integration/targets/dcnm_links/tests/dcnm/dcnm_links_intra_numbered_delete.yaml @@ -0,0 +1,164 @@ +############################################## +## SETUP ## +############################################## + +- name: Remove local log file + local_action: command rm -f dcnm_links.log + +- block: + +############################################## +## DELETE ## +############################################## + + - name: Initial setup - Delete Links + cisco.dcnm.dcnm_links: &links_delete + state: deleted # choose from [merged, replaced, deleted, query] + src_fabric: "{{ ansible_num_fabric }}" + config: + - dst_fabric: "{{ ansible_num_fabric }}" # Destination fabric + src_interface: "{{ intf_1_1 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_1 }}" # Interface on the Destination fabric + src_device: "{{ ansible_num_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_num_switch2 }}" # Device on the Destination fabric + + - dst_fabric: "{{ ansible_num_fabric }}" # Destination fabric + src_interface: "{{ intf_1_2 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_2 }}" # Interface on the Destination fabric + src_device: "{{ ansible_num_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_num_switch2 }}" # Device on the Destination fabric + + - dst_fabric: "{{ ansible_num_fabric }}" # Destination fabric + src_interface: "{{ intf_1_3 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_3 }}" # Interface on the Destination fabric + src_device: "{{ ansible_num_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_num_switch2 }}" # Device on the Destination fabric + + - dst_fabric: "{{ ansible_num_fabric }}" # Destination fabric + src_interface: "{{ intf_1_4 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_4 }}" # Interface on the Destination fabric + src_device: "{{ ansible_num_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_num_switch2 }}" # Device on the Destination fabric + + - dst_fabric: "{{ ansible_num_fabric }}" # Destination fabric + src_interface: "{{ intf_1_5 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_5 }}" # Interface on the Destination fabric + src_device: "{{ ansible_num_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_num_switch2 }}" # Device on the Destination fabric + + register: result + + - assert: + that: + - 'item["RETURN_CODE"] == 200' + loop: '{{ result.response }}' + +############################################## +## MERGE ## +############################################## + + - name: Create Links without including optional parameters + cisco.dcnm.dcnm_links: &links_merge_no_opt + state: merged # choose from [merged, replaced, deleted, query] + src_fabric: "{{ ansible_num_fabric }}" + config: + - dst_fabric: "{{ ansible_num_fabric }}" # Destination fabric + src_interface: "{{ intf_1_1 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_1 }}" # Interface on the Destination fabric + src_device: "{{ ansible_num_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_num_switch2 }}" # Device on the Destination fabric + template: int_intra_fabric_num_link # template to be applied, choose from + # [ int_intra_fabric_ipv6_link_local, int_intra_fabric_num_link, + # int_intra_fabric_unnum_link, int_intra_vpc_peer_keep_alive_link, + # int_pre_provision_intra_fabric_link, ios_xe_int_intra_fabric_num_link ] + + profile: + peer1_ipv4_addr: 192.168.1.1 # IP address of the Source interface + peer2_ipv4_addr: 192.168.1.2 # IP address of the Destination interface + admin_state: true # choose from [true, false] + mtu: 9216 # + + - dst_fabric: "{{ ansible_num_fabric }}" # Destination fabric + src_interface: "{{ intf_1_2 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_2 }}" # Interface on the Destination fabric + src_device: "{{ ansible_num_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_num_switch2 }}" # Device on the Destination fabric + template: int_pre_provision_intra_fabric_link # template to be applied, choose from + # [ int_intra_fabric_ipv6_link_local, int_intra_fabric_num_link, + # int_intra_fabric_unnum_link, int_intra_vpc_peer_keep_alive_link, + # int_pre_provision_intra_fabric_link, ios_xe_int_intra_fabric_num_link ] + register: result + + - assert: + that: + - 'result.changed == true' + - '(result["diff"][0]["merged"] | length) == 2' + - '(result["diff"][0]["modified"] | length) == 0' + - '(result["diff"][0]["deleted"] | length) == 0' + - '(result["diff"][0]["query"] | length) == 0' + - '(result["diff"][0]["deploy"][0][ "{{ ansible_num_fabric }}" ] | length) == 2' + + - assert: + that: + - 'item["RETURN_CODE"] == 200' + loop: '{{ result.response }}' + + +############################################### +### DELETE ## +############################################### + + - name: Delete Links + cisco.dcnm.dcnm_links: *links_delete + register: result + + - assert: + that: + - 'result.changed == true' + - '(result["diff"][0]["merged"] | length) == 0' + - '(result["diff"][0]["modified"] | length) == 0' + - '(result["diff"][0]["deleted"] | length) == 2' + - '(result["diff"][0]["query"] | length) == 0' + - '(result["diff"][0]["deploy"][0][ "{{ ansible_num_fabric }}" ] | length) == 2' + + - assert: + that: + - 'item["RETURN_CODE"] == 200' + loop: '{{ result.response }}' + +############################################## +## IDEMPOTENCE ## +############################################## + + - name: Delete Links - Idempotence + cisco.dcnm.dcnm_links: *links_delete + register: result + + - assert: + that: + - 'result.changed == false' + - '(result["diff"][0]["merged"] | length) == 0' + - '(result["diff"][0]["modified"] | length) == 0' + - '(result["diff"][0]["deleted"] | length) == 0' + - '(result["diff"][0]["query"] | length) == 0' + - '(result["diff"][0]["deploy"] | length) == 0' + + - assert: + that: + - 'item["RETURN_CODE"] == 200' + loop: '{{ result.response }}' + +############################################## +## CLEANUP ## +############################################## + + always: + + - name: Delete Links + cisco.dcnm.dcnm_links: *links_delete + register: result + + - assert: + that: + - 'item["RETURN_CODE"] == 200' + loop: '{{ result.response }}' diff --git a/tests/integration/targets/dcnm_links/tests/dcnm/dcnm_links_intra_numbered_merge.yaml b/tests/integration/targets/dcnm_links/tests/dcnm/dcnm_links_intra_numbered_merge.yaml new file mode 100644 index 000000000..0651bbcf6 --- /dev/null +++ b/tests/integration/targets/dcnm_links/tests/dcnm/dcnm_links_intra_numbered_merge.yaml @@ -0,0 +1,301 @@ +############################################## +## SETUP ## +############################################## + +- name: Remove local log file + local_action: command rm -f dcnm_links.log + +- block: + +############################################## +## DELETE ## +############################################## + + - name: Initial setup - Delete Links + cisco.dcnm.dcnm_links: &links_delete + state: deleted # choose from [merged, replaced, deleted, query] + src_fabric: "{{ ansible_num_fabric }}" + config: + - dst_fabric: "{{ ansible_num_fabric }}" # Destination fabric + src_interface: "{{ intf_1_1 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_1 }}" # Interface on the Destination fabric + src_device: "{{ ansible_num_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_num_switch2 }}" # Device on the Destination fabric + + - dst_fabric: "{{ ansible_num_fabric }}" # Destination fabric + src_interface: "{{ intf_1_2 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_2 }}" # Interface on the Destination fabric + src_device: "{{ ansible_num_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_num_switch2 }}" # Device on the Destination fabric + + - dst_fabric: "{{ ansible_num_fabric }}" # Destination fabric + src_interface: "{{ intf_1_3 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_3 }}" # Interface on the Destination fabric + src_device: "{{ ansible_num_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_num_switch2 }}" # Device on the Destination fabric + + - dst_fabric: "{{ ansible_num_fabric }}" # Destination fabric + src_interface: "{{ intf_1_4 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_4 }}" # Interface on the Destination fabric + src_device: "{{ ansible_num_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_num_switch2 }}" # Device on the Destination fabric + + - dst_fabric: "{{ ansible_num_fabric }}" # Destination fabric + src_interface: "{{ intf_1_5 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_5 }}" # Interface on the Destination fabric + src_device: "{{ ansible_num_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_num_switch2 }}" # Device on the Destination fabric + + register: result + + - assert: + that: + - 'item["RETURN_CODE"] == 200' + loop: '{{ result.response }}' + +############################################## +## MERGE ## +############################################## + + - name: Create Links without including optional parameters + cisco.dcnm.dcnm_links: &links_merge_no_opt + state: merged # choose from [merged, replaced, deleted, query] + src_fabric: "{{ ansible_num_fabric }}" + config: + - dst_fabric: "{{ ansible_num_fabric }}" # Destination fabric + src_interface: "{{ intf_1_1 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_1 }}" # Interface on the Destination fabric + src_device: "{{ ansible_num_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_num_switch2 }}" # Device on the Destination fabric + template: int_intra_fabric_num_link # template to be applied, choose from + # [ int_intra_fabric_ipv6_link_local, int_intra_fabric_num_link, + # int_intra_fabric_unnum_link, int_intra_vpc_peer_keep_alive_link, + # int_pre_provision_intra_fabric_link, ios_xe_int_intra_fabric_num_link ] + + profile: + peer1_ipv4_addr: 192.168.1.1 # IP address of the Source interface + peer2_ipv4_addr: 192.168.1.2 # IP address of the Destination interface + admin_state: true # choose from [true, false] + mtu: 9216 # + + - dst_fabric: "{{ ansible_num_fabric }}" # Destination fabric + src_interface: "{{ intf_1_2 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_2 }}" # Interface on the Destination fabric + src_device: "{{ ansible_num_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_num_switch2 }}" # Device on the Destination fabric + template: int_pre_provision_intra_fabric_link # template to be applied, choose from + # [ int_intra_fabric_ipv6_link_local, int_intra_fabric_num_link, + # int_intra_fabric_unnum_link, int_intra_vpc_peer_keep_alive_link, + # int_pre_provision_intra_fabric_link, ios_xe_int_intra_fabric_num_link ] + register: result + + - assert: + that: + - 'result.changed == true' + - '(result["diff"][0]["merged"] | length) == 2' + - '(result["diff"][0]["modified"] | length) == 0' + - '(result["diff"][0]["deleted"] | length) == 0' + - '(result["diff"][0]["query"] | length) == 0' + - '(result["diff"][0]["deploy"][0][ "{{ ansible_num_fabric }}" ] | length) == 2' + + - assert: + that: + - 'item["RETURN_CODE"] == 200' + loop: '{{ result.response }}' + + +############################################## +## IDEMPOTENCE ## +############################################## + + - name: Create Links - Idempotence + cisco.dcnm.dcnm_links: *links_merge_no_opt + register: result + + - assert: + that: + - 'result.changed == true' + - '(result["diff"][0]["merged"] | length) == 0' + - '(result["diff"][0]["modified"] | length) == 0' + - '(result["diff"][0]["deleted"] | length) == 0' + - '(result["diff"][0]["query"] | length) == 0' + - '(result["diff"][0]["deploy"][0][ "{{ ansible_num_fabric }}" ] | length) == 2' + + - assert: + that: + - 'item["RETURN_CODE"] == 200' + loop: '{{ result.response }}' + +############################################## +## DELETE ## +############################################## + + - name: Delete Links + cisco.dcnm.dcnm_links: *links_delete + register: result + + - assert: + that: + - 'item["RETURN_CODE"] == 200' + loop: '{{ result.response }}' + +############################################## +## MERGE ## +############################################## + + - name: Create Links including all optional parameters + cisco.dcnm.dcnm_links: &links_merge_with_opt + state: merged # choose from [merged, replaced, deleted, query] + src_fabric: "{{ ansible_num_fabric }}" + config: + - dst_fabric: "{{ ansible_num_fabric }}" # Destination fabric + src_interface: "{{ intf_1_1 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_1 }}" # Interface on the Destination fabric + src_device: "{{ ansible_num_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_num_switch2 }}" # Device on the Destination fabric + template: int_intra_fabric_num_link # template to be applied, choose from + # [ int_intra_fabric_ipv6_link_local, int_intra_fabric_num_link, + # int_intra_fabric_unnum_link, int_intra_vpc_peer_keep_alive_link, + # int_pre_provision_intra_fabric_link, ios_xe_int_intra_fabric_num_link ] + + profile: + peer1_ipv4_addr: 192.168.1.1 # IP address of the Source interface + peer2_ipv4_addr: 192.168.1.2 # IP address of the Destination interface + admin_state: true # choose from [true, false] + mtu: 9216 # + peer1_description: "Description of source" # optional, default is "" + peer2_description: "Description of dest" # optional, default is "" + peer1_bfd_echo_disable: false # optional, choose from [true, false] + peer2_bfd_echo_disable: false # optional, choose from [true, false] + enable_macsec: false # optional, choose from [true, false] + peer1_cmds: # Freeform config for source device + - no shutdown # optional, default is "" + peer2_cmds: # Freeform config for destination device + - no shutdown # optional, default is "" + + - dst_fabric: "{{ ansible_num_fabric }}" # Destination fabric + src_interface: "{{ intf_1_2 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_2 }}" # Interface on the Destination fabric + src_device: "{{ ansible_num_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_num_switch2 }}" # Device on the Destination fabric + template: int_pre_provision_intra_fabric_link # template to be applied, choose from + # [ int_intra_fabric_ipv6_link_local, int_intra_fabric_num_link, + # int_intra_fabric_unnum_link, int_intra_vpc_peer_keep_alive_link, + # int_pre_provision_intra_fabric_link, ios_xe_int_intra_fabric_num_link ] + register: result + + - assert: + that: + - 'result.changed == true' + - '(result["diff"][0]["merged"] | length) == 2' + - '(result["diff"][0]["modified"] | length) == 0' + - '(result["diff"][0]["deleted"] | length) == 0' + - '(result["diff"][0]["query"] | length) == 0' + - '(result["diff"][0]["deploy"][0][ "{{ ansible_num_fabric }}" ] | length) == 2' + + - assert: + that: + - 'item["RETURN_CODE"] == 200' + loop: '{{ result.response }}' + +############################################## +## IDEMPOTENCE ## +############################################## + + - name: Create Links - Idempotence + cisco.dcnm.dcnm_links: *links_merge_with_opt + register: result + + - assert: + that: + - 'result.changed == true' + - '(result["diff"][0]["merged"] | length) == 0' + - '(result["diff"][0]["modified"] | length) == 0' + - '(result["diff"][0]["deleted"] | length) == 0' + - '(result["diff"][0]["query"] | length) == 0' + - '(result["diff"][0]["deploy"][0][ "{{ ansible_num_fabric }}" ] | length) == 2' + + - assert: + that: + - 'item["RETURN_CODE"] == 200' + loop: '{{ result.response }}' + +############################################## +## DELETE ## +############################################## + + - name: Delete Links + cisco.dcnm.dcnm_links: *links_delete + register: result + + - assert: + that: + - 'item["RETURN_CODE"] == 200' + loop: '{{ result.response }}' + +############################################## +## MERGE ## +############################################## + + - name: Create Links - with deploy being false + cisco.dcnm.dcnm_links: + state: merged # choose from [merged, replaced, deleted, query] + deploy: false + src_fabric: "{{ ansible_num_fabric }}" + config: + - dst_fabric: "{{ ansible_num_fabric }}" # Destination fabric + src_interface: "{{ intf_1_1 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_1 }}" # Interface on the Destination fabric + src_device: "{{ ansible_num_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_num_switch2 }}" # Device on the Destination fabric + template: int_intra_fabric_num_link # template to be applied, choose from + # [ int_intra_fabric_ipv6_link_local, int_intra_fabric_num_link, + # int_intra_fabric_unnum_link, int_intra_vpc_peer_keep_alive_link, + # int_pre_provision_intra_fabric_link, ios_xe_int_intra_fabric_num_link ] + + profile: + peer1_ipv4_addr: 192.168.1.1 # IP address of the Source interface + peer2_ipv4_addr: 192.168.1.2 # IP address of the Destination interface + admin_state: true # choose from [true, false] + mtu: 9216 # + peer1_description: "Description of source" # optional, default is "" + peer2_description: "Description of dest" # optional, default is "" + peer1_bfd_echo_disable: false # optional, choose from [true, false] + peer2_bfd_echo_disable: false # optional, choose from [true, false] + enable_macsec: false # optional, choose from [true, false] + peer1_cmds: # Freeform config for source device + - no shutdown # optional, default is "" + peer2_cmds: # Freeform config for destination device + - no shutdown # optional, default is "" + register: result + + - assert: + that: + - 'result.changed == true' + - '(result["diff"][0]["merged"] | length) == 1' + - '(result["diff"][0]["modified"] | length) == 0' + - '(result["diff"][0]["deleted"] | length) == 0' + - '(result["diff"][0]["query"] | length) == 0' + - '(result["diff"][0]["deploy"] | length) == 0' + + - assert: + that: + - 'item["RETURN_CODE"] == 200' + loop: '{{ result.response }}' + +############################################### +### CLEANUP ## +############################################### + + always: + + - name: Cleanup - Delete Links + cisco.dcnm.dcnm_links: *links_delete + register: result + when: IT_CONTEXT is not defined + + - assert: + that: + - 'item["RETURN_CODE"] == 200' + loop: '{{ result.response }}' + when: IT_CONTEXT is not defined diff --git a/tests/integration/targets/dcnm_links/tests/dcnm/dcnm_links_intra_numbered_modify.yaml b/tests/integration/targets/dcnm_links/tests/dcnm/dcnm_links_intra_numbered_modify.yaml new file mode 100644 index 000000000..c775d08a2 --- /dev/null +++ b/tests/integration/targets/dcnm_links/tests/dcnm/dcnm_links_intra_numbered_modify.yaml @@ -0,0 +1,190 @@ +############################################## +## SETUP ## +############################################## + +- name: Remove local log file + local_action: command rm -f dcnm_links.log + +- block: + +############################################## +## DELETE ## +############################################## + + - name: Initial setup - Delete Links + cisco.dcnm.dcnm_links: &links_delete + state: deleted # choose from [merged, replaced, deleted, query] + src_fabric: "{{ ansible_num_fabric }}" + config: + - dst_fabric: "{{ ansible_num_fabric }}" # Destination fabric + src_interface: "{{ intf_1_1 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_1 }}" # Interface on the Destination fabric + src_device: "{{ ansible_num_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_num_switch2 }}" # Device on the Destination fabric + + - dst_fabric: "{{ ansible_num_fabric }}" # Destination fabric + src_interface: "{{ intf_1_2 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_2 }}" # Interface on the Destination fabric + src_device: "{{ ansible_num_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_num_switch2 }}" # Device on the Destination fabric + # [ int_intra_fabric_ipv6_link_local, int_intra_fabric_num_link, + # int_intra_fabric_unnum_link, int_intra_vpc_peer_keep_alive_link, + # int_pre_provision_intra_fabric_link, ios_xe_int_intra_fabric_num_link ] + - dst_fabric: "{{ ansible_num_fabric }}" # Destination fabric + src_interface: "{{ intf_1_3 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_3 }}" # Interface on the Destination fabric + src_device: "{{ ansible_num_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_num_switch2 }}" # Device on the Destination fabric + + - dst_fabric: "{{ ansible_num_fabric }}" # Destination fabric + src_interface: "{{ intf_1_4 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_4 }}" # Interface on the Destination fabric + src_device: "{{ ansible_num_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_num_switch2 }}" # Device on the Destination fabric + + - dst_fabric: "{{ ansible_num_fabric }}" # Destination fabric + src_interface: "{{ intf_1_5 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_5 }}" # Interface on the Destination fabric + src_device: "{{ ansible_num_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_num_switch2 }}" # Device on the Destination fabric + + register: result + + - assert: + that: + - 'item["RETURN_CODE"] == 200' + loop: '{{ result.response }}' + +############################################## +## MERGE ## +############################################## + + - name: Create Links including all optional parameters + cisco.dcnm.dcnm_links: + state: merged # choose from [merged, replaced, deleted, query] + src_fabric: "{{ ansible_num_fabric }}" + config: + - dst_fabric: "{{ ansible_num_fabric }}" # Destination fabric + src_interface: "{{ intf_1_1 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_1 }}" # Interface on the Destination fabric + src_device: "{{ ansible_num_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_num_switch2 }}" # Device on the Destination fabric + template: int_intra_fabric_num_link # template to be applied, choose from + # [ int_intra_fabric_ipv6_link_local, int_intra_fabric_num_link, + # int_intra_fabric_unnum_link, int_intra_vpc_peer_keep_alive_link, + # int_pre_provision_intra_fabric_link, ios_xe_int_intra_fabric_num_link ] + + profile: + peer1_ipv4_addr: 192.168.1.1 # IP address of the Source interface + peer2_ipv4_addr: 192.168.1.2 # IP address of the Destination interface + admin_state: true # choose from [true, false] + mtu: 9216 # + peer1_description: "Description of source - 1" # optional, default is "" + peer2_description: "Description of dest - 1" # optional, default is "" + peer1_bfd_echo_disable: false # optional, choose from [true, false] + peer2_bfd_echo_disable: false # optional, choose from [true, false] + enable_macsec: false # optional, choose from [true, false] + peer1_cmds: # Freeform config for source device + - no shutdown # optional, default is "" + peer2_cmds: # Freeform config for destination device + - no shutdown # optional, default is "" + + - dst_fabric: "{{ ansible_num_fabric }}" # Destination fabric + src_interface: "{{ intf_1_2 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_2 }}" # Interface on the Destination fabric + src_device: "{{ ansible_num_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_num_switch2 }}" # Device on the Destination fabric + template: int_pre_provision_intra_fabric_link # template to be applied, choose from + # [ int_intra_fabric_ipv6_link_local, int_intra_fabric_num_link, + # int_intra_fabric_unnum_link, int_intra_vpc_peer_keep_alive_link, + # int_pre_provision_intra_fabric_link, ios_xe_int_intra_fabric_num_link ] + register: result + + - assert: + that: + - 'result.changed == true' + - '(result["diff"][0]["merged"] | length) == 2' + - '(result["diff"][0]["modified"] | length) == 0' + - '(result["diff"][0]["deleted"] | length) == 0' + - '(result["diff"][0]["query"] | length) == 0' + - '(result["diff"][0]["deploy"][0][ "{{ ansible_num_fabric }}" ] | length) == 2' + + - assert: + that: + - 'item["RETURN_CODE"] == 200' + loop: '{{ result.response }}' + + +############################################## +## MODIFY ## +############################################## + + - name: Merge modified info into existing links + cisco.dcnm.dcnm_links: + state: merged # choose from [merged, replaced, deleted, query] + src_fabric: "{{ ansible_num_fabric }}" + config: + - dst_fabric: "{{ ansible_num_fabric }}" # Destination fabric + src_interface: "{{ intf_1_1 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_1 }}" # Interface on the Destination fabric + src_device: "{{ ansible_num_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_num_switch2 }}" # Device on the Destination fabric + template: int_intra_fabric_num_link # template to be applied, choose from + # [ int_intra_fabric_ipv6_link_local, int_intra_fabric_num_link, + # int_intra_fabric_unnum_link, int_intra_vpc_peer_keep_alive_link, + # int_pre_provision_intra_fabric_link, ios_xe_int_intra_fabric_num_link ] + + profile: + peer1_ipv4_addr: 193.168.1.1 # IP address of the Source interface + peer2_ipv4_addr: 193.168.1.2 # IP address of the Destination interface + admin_state: false # choose from [true, false] + mtu: 1000 # + peer1_description: "MOD - Description of source - 1" # optional, default is "" + peer2_description: "MOD - Description of dest - 1" # optional, default is "" + peer1_bfd_echo_disable: true # optional, choose from [true, false] + peer2_bfd_echo_disable: true # optional, choose from [true, false] + enable_macsec: false # optional, choose from [true, false] + peer1_cmds: # Freeform config for source device + - cdp enable # optional, default is "" + peer2_cmds: # Freeform config for destination device + - cdp enable # optional, default is "" + + - dst_fabric: "{{ ansible_num_fabric }}" # Destination fabric + src_interface: "{{ intf_1_2 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_2 }}" # Interface on the Destination fabric + src_device: "{{ ansible_num_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_num_switch2 }}" # Device on the Destination fabric + template: int_pre_provision_intra_fabric_link # template to be applied, choose from + # [ int_intra_fabric_ipv6_link_local, int_intra_fabric_num_link, + # int_intra_fabric_unnum_link, int_intra_vpc_peer_keep_alive_link, + # int_pre_provision_intra_fabric_link, ios_xe_int_intra_fabric_num_link ] + register: result + + - assert: + that: + - 'result.changed == true' + - '(result["diff"][0]["merged"] | length) == 0' + - '(result["diff"][0]["modified"] | length) == 1' + - '(result["diff"][0]["deleted"] | length) == 0' + - '(result["diff"][0]["query"] | length) == 0' + - '(result["diff"][0]["deploy"][0][ "{{ ansible_num_fabric }}" ] | length) == 2' + + - assert: + that: + - 'item["RETURN_CODE"] == 200' + loop: '{{ result.response }}' + +############################################## +## CLEANUP ## +############################################## + + - name: Cleanup - Delete Links + cisco.dcnm.dcnm_links: *links_delete + register: result + when: IT_CONTEXT is not defined + + - assert: + that: + - 'item["RETURN_CODE"] == 200' + loop: '{{ result.response }}' + when: IT_CONTEXT is not defined diff --git a/tests/integration/targets/dcnm_links/tests/dcnm/dcnm_links_intra_numbered_query.yaml b/tests/integration/targets/dcnm_links/tests/dcnm/dcnm_links_intra_numbered_query.yaml new file mode 100644 index 000000000..3093562ac --- /dev/null +++ b/tests/integration/targets/dcnm_links/tests/dcnm/dcnm_links_intra_numbered_query.yaml @@ -0,0 +1,235 @@ +############################################## +## SETUP ## +############################################## + +- name: Remove local log file + local_action: command rm -f dcnm_links.log + +- block: + +############################################## +## DELETE ## +############################################## + + - name: Initial setup - Delete Links + cisco.dcnm.dcnm_links: &links_delete + state: deleted # choose from [merged, replaced, deleted, query] + src_fabric: "{{ ansible_num_fabric }}" + config: + - dst_fabric: "{{ ansible_num_fabric }}" # Destination fabric + src_interface: "{{ intf_1_1 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_1 }}" # Interface on the Destination fabric + src_device: "{{ ansible_num_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_num_switch2 }}" # Device on the Destination fabric + + - dst_fabric: "{{ ansible_num_fabric }}" # Destination fabric + src_interface: "{{ intf_1_2 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_2 }}" # Interface on the Destination fabric + src_device: "{{ ansible_num_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_num_switch2 }}" # Device on the Destination fabric + + - dst_fabric: "{{ ansible_num_fabric }}" # Destination fabric + src_interface: "{{ intf_1_3 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_3 }}" # Interface on the Destination fabric + src_device: "{{ ansible_num_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_num_switch2 }}" # Device on the Destination fabric + + - dst_fabric: "{{ ansible_num_fabric }}" # Destination fabric + src_interface: "{{ intf_1_4 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_4 }}" # Interface on the Destination fabric + src_device: "{{ ansible_num_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_num_switch2 }}" # Device on the Destination fabric + + - dst_fabric: "{{ ansible_num_fabric }}" # Destination fabric + src_interface: "{{ intf_1_5 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_5 }}" # Interface on the Destination fabric + src_device: "{{ ansible_num_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_num_switch2 }}" # Device on the Destination fabric + + register: result + + - assert: + that: + - 'item["RETURN_CODE"] == 200' + loop: '{{ result.response }}' + +############################################### +### MERGE ## +############################################### + + - name: Create Links without including optional parameters + cisco.dcnm.dcnm_links: + state: merged # choose from [merged, replaced, deleted, query] + src_fabric: "{{ ansible_num_fabric }}" + config: + - dst_fabric: "{{ ansible_num_fabric }}" # Destination fabric + src_interface: "{{ intf_1_1 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_1 }}" # Interface on the Destination fabric + src_device: "{{ ansible_num_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_num_switch2 }}" # Device on the Destination fabric + template: int_intra_fabric_num_link # template to be applied, choose from + # [ int_intra_fabric_ipv6_link_local, int_intra_fabric_num_link, + # int_intra_fabric_unnum_link, int_intra_vpc_peer_keep_alive_link, + # int_pre_provision_intra_fabric_link, ios_xe_int_intra_fabric_num_link ] + + profile: + peer1_ipv4_addr: 192.168.1.1 # IP address of the Source interface + peer2_ipv4_addr: 192.168.1.2 # IP address of the Destination interface + admin_state: true # choose from [true, false] + mtu: 9216 # + + - dst_fabric: "{{ ansible_num_fabric }}" # Destination fabric + src_interface: "{{ intf_1_2 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_2 }}" # Interface on the Destination fabric + src_device: "{{ ansible_num_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_num_switch2 }}" # Device on the Destination fabric + template: int_pre_provision_intra_fabric_link # template to be applied, choose from + # [ int_intra_fabric_ipv6_link_local, int_intra_fabric_num_link, + # int_intra_fabric_unnum_link, int_intra_vpc_peer_keep_alive_link, + # int_pre_provision_intra_fabric_link, ios_xe_int_intra_fabric_num_link ] + register: result + + - assert: + that: + - 'result.changed == true' + - '(result["diff"][0]["merged"] | length) == 2' + - '(result["diff"][0]["modified"] | length) == 0' + - '(result["diff"][0]["deleted"] | length) == 0' + - '(result["diff"][0]["query"] | length) == 0' + - '(result["diff"][0]["deploy"][0][ "{{ ansible_num_fabric }}" ] | length) == 2' + + - assert: + that: + - 'item["RETURN_CODE"] == 200' + loop: '{{ result.response }}' + +############################################### +### QUERY ## +############################################### + + - name: Query Links - with Src Fabric + cisco.dcnm.dcnm_links: + state: query # choose from [merged, replaced, deleted, query] + src_fabric: "{{ ansible_num_fabric }}" + register: result + + - assert: + that: + '(result["response"] | length) >= 2' + + - name: Query Links - with Src & Dst Fabric + cisco.dcnm.dcnm_links: + state: query # choose from [merged, replaced, deleted, query] + src_fabric: "{{ ansible_num_fabric }}" + config: + - dst_fabric: "{{ ansible_num_fabric }}" # optional, Destination fabric + register: result + + - assert: + that: + '(result["response"] | length) >= 2' + + - name: Query Links - with Src & Dst Fabric, Src Intf + cisco.dcnm.dcnm_links: + state: query # choose from [merged, replaced, deleted, query] + src_fabric: "{{ ansible_num_fabric }}" + config: + - dst_fabric: "{{ ansible_num_fabric }}" # optional, Destination fabric + src_interface: "{{ intf_1_1 }}" # optional, Interface on the Source fabric + register: result + + - assert: + that: + '(result["response"] | length) >= 1' + + - name: Query Links - with Src & Dst Fabric, Src & Dst Intf + cisco.dcnm.dcnm_links: + state: query # choose from [merged, replaced, deleted, query] + src_fabric: "{{ ansible_num_fabric }}" + config: + - dst_fabric: "{{ ansible_num_fabric }}" # optional, Destination fabric + src_interface: "{{ intf_1_1 }}" # optional, Interface on the Source fabric + dst_interface: "{{ intf_1_1 }}" # optional, Interface on the Destination fabric + register: result + + - assert: + that: + '(result["response"] | length) >= 1' + + - name: Query Links - with Src & Dst Fabric, Src & Dst Intf, Src Device + cisco.dcnm.dcnm_links: + state: query # choose from [merged, replaced, deleted, query] + src_fabric: "{{ ansible_num_fabric }}" + config: + - dst_fabric: "{{ ansible_num_fabric }}" # optional, Destination fabric + src_interface: "{{ intf_1_1 }}" # optional, Interface on the Source fabric + dst_interface: "{{ intf_1_1 }}" # optional, Interface on the Destination fabric + src_device: "{{ ansible_num_switch1 }}" # optional, Device on the Source fabric + register: result + + - assert: + that: + '(result["response"] | length) >= 1' + + - name: Query Links - with Src & Dst Fabric, Src & Dst Intf, Src & Dst Device + cisco.dcnm.dcnm_links: + state: query # choose from [merged, replaced, deleted, query] + src_fabric: "{{ ansible_num_fabric }}" + config: + - dst_fabric: "{{ ansible_num_fabric }}" # optional, Destination fabric + src_interface: "{{ intf_1_1 }}" # optional, Interface on the Source fabric + dst_interface: "{{ intf_1_1 }}" # optional, Interface on the Destination fabric + src_device: "{{ ansible_num_switch1 }}" # optional, Device on the Source fabric + dst_device: "{{ ansible_num_switch2 }}" # optional, Device on the Destination fabric + register: result + + - assert: + that: + '(result["response"] | length) >= 1' + + - name: Query Links - with Src & Dst Fabric, Src & Dst Intf, Src & Dst Device, Template + cisco.dcnm.dcnm_links: + state: query # choose from [merged, replaced, deleted, query] + src_fabric: "{{ ansible_num_fabric }}" + config: + - dst_fabric: "{{ ansible_num_fabric }}" # optional, Destination fabric + src_interface: "{{ intf_1_1 }}" # optional, Interface on the Source fabric + dst_interface: "{{ intf_1_1 }}" # optional, Interface on the Destination fabric + src_device: "{{ ansible_num_switch1 }}" # optional, Device on the Source fabric + dst_device: "{{ ansible_num_switch2 }}" # optional, Device on the Destination fabric + template: int_intra_fabric_num_link # optional, template to be applied, choose from + # [ int_intra_fabric_ipv6_link_local, int_intra_fabric_num_link, + # int_intra_fabric_unnum_link, int_intra_vpc_peer_keep_alive_link, + # int_pre_provision_intra_fabric_link, ios_xe_int_intra_fabric_num_link ] + + - dst_fabric: "{{ ansible_num_fabric }}" # optional, Destination fabric + src_interface: "{{ intf_1_2 }}" # optional, Interface on the Source fabric + dst_interface: "{{ intf_1_2 }}" # optional, Interface on the Destination fabric + src_device: "{{ ansible_num_switch1 }}" # optional, Device on the Source fabric + dst_device: "{{ ansible_num_switch2 }}" # optional, Device on the Destination fabric + template: int_pre_provision_intra_fabric_link # optional, template to be applied, choose from + # [ int_intra_fabric_ipv6_link_local, int_intra_fabric_num_link, + # int_intra_fabric_unnum_link, int_intra_vpc_peer_keep_alive_link, + # int_pre_provision_intra_fabric_link, ios_xe_int_intra_fabric_num_link ] + register: result + + - assert: + that: + '(result["response"] | length) >= 2' + +############################################### +### CLEANUP ## +############################################### + + always: + + - name: Cleanup - Delete Links + cisco.dcnm.dcnm_links: *links_delete + register: result + when: IT_CONTEXT is not defined + + - assert: + that: + - 'item["RETURN_CODE"] == 200' + loop: '{{ result.response }}' + when: IT_CONTEXT is not defined diff --git a/tests/integration/targets/dcnm_links/tests/dcnm/dcnm_links_intra_numbered_replace.yaml b/tests/integration/targets/dcnm_links/tests/dcnm/dcnm_links_intra_numbered_replace.yaml new file mode 100644 index 000000000..6f64fcc14 --- /dev/null +++ b/tests/integration/targets/dcnm_links/tests/dcnm/dcnm_links_intra_numbered_replace.yaml @@ -0,0 +1,207 @@ +############################################## +## SETUP ## +############################################## + +- name: Remove local log file + local_action: command rm -f dcnm_links.log + +- block: + +############################################## +## DELETE ## +############################################## + + - name: Initial setup - Delete Links + cisco.dcnm.dcnm_links: &links_delete + state: deleted # choose from [merged, replaced, deleted, query] + src_fabric: "{{ ansible_num_fabric }}" + config: + - dst_fabric: "{{ ansible_num_fabric }}" # Destination fabric + src_interface: "{{ intf_1_1 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_1 }}" # Interface on the Destination fabric + src_device: "{{ ansible_num_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_num_switch2 }}" # Device on the Destination fabric + + - dst_fabric: "{{ ansible_num_fabric }}" # Destination fabric + src_interface: "{{ intf_1_2 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_2 }}" # Interface on the Destination fabric + src_device: "{{ ansible_num_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_num_switch2 }}" # Device on the Destination fabric + + - dst_fabric: "{{ ansible_num_fabric }}" # Destination fabric + src_interface: "{{ intf_1_3 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_3 }}" # Interface on the Destination fabric + src_device: "{{ ansible_num_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_num_switch2 }}" # Device on the Destination fabric + + - dst_fabric: "{{ ansible_num_fabric }}" # Destination fabric + src_interface: "{{ intf_1_4 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_4 }}" # Interface on the Destination fabric + src_device: "{{ ansible_num_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_num_switch2 }}" # Device on the Destination fabric + + - dst_fabric: "{{ ansible_num_fabric }}" # Destination fabric + src_interface: "{{ intf_1_5 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_5 }}" # Interface on the Destination fabric + src_device: "{{ ansible_num_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_num_switch2 }}" # Device on the Destination fabric + + register: result + + - assert: + that: + - 'item["RETURN_CODE"] == 200' + loop: '{{ result.response }}' + +############################################## +## MERGE ## +############################################## + + - name: Create Links + cisco.dcnm.dcnm_links: + state: merged # choose from [merged, replaced, deleted, query] + src_fabric: "{{ ansible_num_fabric }}" + config: + - dst_fabric: "{{ ansible_num_fabric }}" # Destination fabric + src_interface: "{{ intf_1_1 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_1 }}" # Interface on the Destination fabric + src_device: "{{ ansible_num_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_num_switch2 }}" # Device on the Destination fabric + template: int_intra_fabric_num_link # template to be applied, choose from + # [ int_intra_fabric_ipv6_link_local, int_intra_fabric_num_link, + # int_intra_fabric_unnum_link, int_intra_vpc_peer_keep_alive_link, + # int_pre_provision_intra_fabric_link, ios_xe_int_intra_fabric_num_link ] + + profile: + peer1_ipv4_addr: 192.168.1.1 # IP address of the Source interface + peer2_ipv4_addr: 192.168.1.2 # IP address of the Destination interface + admin_state: true # choose from [true, false] + mtu: 9216 # + peer1_description: "Description of source" # optional, default is "" + peer2_description: "Description of dest" # optional, default is "" + peer1_bfd_echo_disable: false # optional, choose from [true, false] + peer2_bfd_echo_disable: false # optional, choose from [true, false] + enable_macsec: false # optional, choose from [true, false] + peer1_cmds: # Freeform config for source device + - no shutdown # optional, default is "" + peer2_cmds: # Freeform config for destination device + - no shutdown # optional, default is "" + + - dst_fabric: "{{ ansible_num_fabric }}" # Destination fabric + src_interface: "{{ intf_1_2 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_2 }}" # Interface on the Destination fabric + src_device: "{{ ansible_num_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_num_switch2 }}" # Device on the Destination fabric + template: int_pre_provision_intra_fabric_link # template to be applied, choose from + # [ int_intra_fabric_ipv6_link_local, int_intra_fabric_num_link, + # int_intra_fabric_unnum_link, int_intra_vpc_peer_keep_alive_link, + # int_pre_provision_intra_fabric_link, ios_xe_int_intra_fabric_num_link ] + register: result + + - assert: + that: + - 'result.changed == true' + - '(result["diff"][0]["merged"] | length) == 2' + - '(result["diff"][0]["modified"] | length) == 0' + - '(result["diff"][0]["deleted"] | length) == 0' + - '(result["diff"][0]["query"] | length) == 0' + - '(result["diff"][0]["deploy"][0][ "{{ ansible_num_fabric }}" ] | length) == 2' + + - assert: + that: + - 'item["RETURN_CODE"] == 200' + loop: '{{ result.response }}' + +############################################## +## REPLACE ## +############################################## + + - name: Replace Links + cisco.dcnm.dcnm_links: &links_replace + state: replaced # choose from [merged, replaced, deleted, query] + src_fabric: "{{ ansible_num_fabric }}" + config: + - dst_fabric: "{{ ansible_num_fabric }}" # Destination fabric + src_interface: "{{ intf_1_1 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_1 }}" # Interface on the Destination fabric + src_device: "{{ ansible_num_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_num_switch2 }}" # Device on the Destination fabric + template: int_intra_fabric_num_link # template to be applied, choose from + # [ int_intra_fabric_ipv6_link_local, int_intra_fabric_num_link, + # int_intra_fabric_unnum_link, int_intra_vpc_peer_keep_alive_link, + # int_pre_provision_intra_fabric_link, ios_xe_int_intra_fabric_num_link ] + + profile: + peer1_ipv4_addr: 192.169.1.1 # IP address of the Source interface + peer2_ipv4_addr: 192.169.1.2 # IP address of the Destination interface + admin_state: false # choose from [true, false] + mtu: 1000 # + peer1_description: "" # optional, default is "" + peer2_description: "" # optional, default is "" + peer1_bfd_echo_disable: true # optional, choose from [true, false] + peer2_bfd_echo_disable: true # optional, choose from [true, false] + enable_macsec: false # optional, choose from [true, false] + + - dst_fabric: "{{ ansible_num_fabric }}" # Destination fabric + src_interface: "{{ intf_1_2 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_2 }}" # Interface on the Destination fabric + src_device: "{{ ansible_num_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_num_switch2 }}" # Device on the Destination fabric + template: int_pre_provision_intra_fabric_link # template to be applied, choose from + # [ int_intra_fabric_ipv6_link_local, int_intra_fabric_num_link, + # int_intra_fabric_unnum_link, int_intra_vpc_peer_keep_alive_link, + # int_pre_provision_intra_fabric_link, ios_xe_int_intra_fabric_num_link ] + register: result + + - assert: + that: + - 'result.changed == true' + - '(result["diff"][0]["merged"] | length) == 0' + - '(result["diff"][0]["modified"] | length) == 1' + - '(result["diff"][0]["deleted"] | length) == 0' + - '(result["diff"][0]["query"] | length) == 0' + - '(result["diff"][0]["deploy"][0][ "{{ ansible_num_fabric }}" ] | length) == 2' + + - assert: + that: + - 'item["RETURN_CODE"] == 200' + loop: '{{ result.response }}' + +############################################## +## IDEMPOTENCE ## +############################################## + + - name: Repalce Links - Idempotence + cisco.dcnm.dcnm_links: *links_replace + register: result + + - assert: + that: + - 'result.changed == true' + - '(result["diff"][0]["merged"] | length) == 0' + - '(result["diff"][0]["modified"] | length) == 0' + - '(result["diff"][0]["deleted"] | length) == 0' + - '(result["diff"][0]["query"] | length) == 0' + - '(result["diff"][0]["deploy"][0][ "{{ ansible_num_fabric }}" ] | length) == 2' + + - assert: + that: + - 'item["RETURN_CODE"] == 200' + loop: '{{ result.response }}' + +############################################### +### CLEANUP ## +############################################### + + always: + + - name: Cleanup - Delete Links + cisco.dcnm.dcnm_links: *links_delete + register: result + when: IT_CONTEXT is not defined + + - assert: + that: + - 'item["RETURN_CODE"] == 200' + loop: '{{ result.response }}' + when: IT_CONTEXT is not defined diff --git a/tests/integration/targets/dcnm_links/tests/dcnm/dcnm_links_intra_numbered_template_change.yaml b/tests/integration/targets/dcnm_links/tests/dcnm/dcnm_links_intra_numbered_template_change.yaml new file mode 100644 index 000000000..3bfe6aa2d --- /dev/null +++ b/tests/integration/targets/dcnm_links/tests/dcnm/dcnm_links_intra_numbered_template_change.yaml @@ -0,0 +1,147 @@ +############################################## +## SETUP ## +############################################## + +- name: Remove local log file + local_action: command rm -f dcnm_links.log + +- block: + +############################################## +## DELETE ## +############################################## + + - name: Initial setup - Delete Links + cisco.dcnm.dcnm_links: &links_delete + state: deleted # choose from [merged, replaced, deleted, query] + src_fabric: "{{ ansible_num_fabric }}" + config: + - dst_fabric: "{{ ansible_num_fabric }}" # Destination fabric + src_interface: "{{ intf_1_1 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_1 }}" # Interface on the Destination fabric + src_device: "{{ ansible_num_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_num_switch2 }}" # Device on the Destination fabric + + - dst_fabric: "{{ ansible_num_fabric }}" # Destination fabric + src_interface: "{{ intf_1_2 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_2 }}" # Interface on the Destination fabric + src_device: "{{ ansible_num_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_num_switch2 }}" # Device on the Destination fabric + + - dst_fabric: "{{ ansible_num_fabric }}" # Destination fabric + src_interface: "{{ intf_1_3 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_3 }}" # Interface on the Destination fabric + src_device: "{{ ansible_num_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_num_switch2 }}" # Device on the Destination fabric + + - dst_fabric: "{{ ansible_num_fabric }}" # Destination fabric + src_interface: "{{ intf_1_4 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_4 }}" # Interface on the Destination fabric + src_device: "{{ ansible_num_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_num_switch2 }}" # Device on the Destination fabric + + - dst_fabric: "{{ ansible_num_fabric }}" # Destination fabric + src_interface: "{{ intf_1_5 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_5 }}" # Interface on the Destination fabric + src_device: "{{ ansible_num_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_num_switch2 }}" # Device on the Destination fabric + + register: result + + - assert: + that: + - 'item["RETURN_CODE"] == 200' + loop: '{{ result.response }}' + +############################################## +## MERGE ## +############################################## + + - name: Create Links without including optional parameters + cisco.dcnm.dcnm_links: &links_merge_no_opt + state: merged # choose from [merged, replaced, deleted, query] + src_fabric: "{{ ansible_num_fabric }}" + config: + - dst_fabric: "{{ ansible_num_fabric }}" # Destination fabric + src_interface: "{{ intf_1_1 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_1 }}" # Interface on the Destination fabric + src_device: "{{ ansible_num_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_num_switch2 }}" # Device on the Destination fabric + template: int_intra_fabric_num_link # template to be applied, choose from + # [ int_intra_fabric_ipv6_link_local, int_intra_fabric_num_link, + # int_intra_fabric_unnum_link, int_intra_vpc_peer_keep_alive_link, + # int_pre_provision_intra_fabric_link, ios_xe_int_intra_fabric_num_link ] + + profile: + peer1_ipv4_addr: 192.168.1.1 # IP address of the Source interface + peer2_ipv4_addr: 192.168.1.2 # IP address of the Destination interface + admin_state: true # choose from [true, false] + mtu: 9216 # + + register: result + + - assert: + that: + - 'result.changed == true' + - '(result["diff"][0]["merged"] | length) == 1' + - '(result["diff"][0]["modified"] | length) == 0' + - '(result["diff"][0]["deleted"] | length) == 0' + - '(result["diff"][0]["query"] | length) == 0' + - '(result["diff"][0]["deploy"][0][ "{{ ansible_num_fabric }}" ] | length) == 2' + + - assert: + that: + - 'item["RETURN_CODE"] == 200' + loop: '{{ result.response }}' + +############################################## +## MODIFY ## +############################################## + + - name: Modify the template name + cisco.dcnm.dcnm_links: + state: merged # choose from [merged, replaced, deleted, query] + src_fabric: "{{ ansible_num_fabric }}" + config: + - dst_fabric: "{{ ansible_num_fabric }}" # Destination fabric + src_interface: "{{ intf_1_1 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_1 }}" # Interface on the Destination fabric + src_device: "{{ ansible_num_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_num_switch2 }}" # Device on the Destination fabric + template: int_pre_provision_intra_fabric_link # template to be applied, choose from + # [ int_intra_fabric_ipv6_link_local, int_intra_fabric_num_link, + # int_intra_fabric_unnum_link, int_intra_vpc_peer_keep_alive_link, + # int_pre_provision_intra_fabric_link, ios_xe_int_intra_fabric_num_link ] + register: result + + - assert: + that: + - 'result.changed == true' + - '(result["diff"][0]["merged"] | length) == 0' + - '(result["diff"][0]["modified"] | length) == 1' + - '(result["diff"][0]["deleted"] | length) == 0' + - '(result["diff"][0]["query"] | length) == 0' + - '(result["diff"][0]["deploy"][0][ "{{ ansible_num_fabric }}" ] | length) == 2' + + - assert: + that: + - 'item["RETURN_CODE"] == 200' + loop: '{{ result.response }}' + + +############################################### +### CLEANUP ## +############################################### + + always: + + - name: Cleanup - Delete Links + cisco.dcnm.dcnm_links: *links_delete + register: result + when: IT_CONTEXT is not defined + + - assert: + that: + - 'item["RETURN_CODE"] == 200' + loop: '{{ result.response }}' + when: IT_CONTEXT is not defined diff --git a/tests/integration/targets/dcnm_links/tests/dcnm/dcnm_links_intra_unnumbered_delete.yaml b/tests/integration/targets/dcnm_links/tests/dcnm/dcnm_links_intra_unnumbered_delete.yaml new file mode 100644 index 000000000..8a3406673 --- /dev/null +++ b/tests/integration/targets/dcnm_links/tests/dcnm/dcnm_links_intra_unnumbered_delete.yaml @@ -0,0 +1,167 @@ +############################################## +## SETUP ## +############################################## + +- name: Remove local log file + local_action: command rm -f dcnm_links.log + +- block: + +############################################## +## DELETE ## +############################################## + + - name: Initial setup - Delete Links + cisco.dcnm.dcnm_links: &links_delete + state: deleted # choose from [merged, replaced, deleted, query] + src_fabric: "{{ ansible_unnum_fabric }}" + config: + - dst_fabric: "{{ ansible_unnum_fabric }}" # Destination fabric + src_interface: "{{ intf_1_1 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_1 }}" # Interface on the Destination fabric + src_device: "{{ ansible_unnum_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_unnum_switch2 }}" # Device on the Destination fabric + + - dst_fabric: "{{ ansible_unnum_fabric }}" # Destination fabric + src_interface: "{{ intf_1_2 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_2 }}" # Interface on the Destination fabric + src_device: "{{ ansible_unnum_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_unnum_switch2 }}" # Device on the Destination fabric + + - dst_fabric: "{{ ansible_unnum_fabric }}" # Destination fabric + src_interface: "{{ intf_1_3 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_3 }}" # Interface on the Destination fabric + src_device: "{{ ansible_unnum_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_unnum_switch2 }}" # Device on the Destination fabric + + - dst_fabric: "{{ ansible_unnum_fabric }}" # Destination fabric + src_interface: "{{ intf_1_4 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_4 }}" # Interface on the Destination fabric + src_device: "{{ ansible_unnum_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_unnum_switch2 }}" # Device on the Destination fabric + + - dst_fabric: "{{ ansible_unnum_fabric }}" # Destination fabric + src_interface: "{{ intf_1_5 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_5 }}" # Interface on the Destination fabric + src_device: "{{ ansible_unnum_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_unnum_switch2 }}" # Device on the Destination fabric + + register: result + + - assert: + that: + - 'item["RETURN_CODE"] == 200' + loop: '{{ result.response }}' + +############################################## +## MERGE ## +############################################## + + - name: Create Links without including optional parameters + cisco.dcnm.dcnm_links: &links_merge_no_opt + state: merged # choose from [merged, replaced, deleted, query] + src_fabric: "{{ ansible_unnum_fabric }}" + config: + - dst_fabric: "{{ ansible_unnum_fabric }}" # Destination fabric + src_interface: "{{ intf_1_1 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_1 }}" # Interface on the Destination fabric + src_device: "{{ ansible_unnum_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_unnum_switch2 }}" # Device on the Destination fabric + template: int_intra_fabric_unnum_link # template to be applied, choose from + # [ int_intra_fabric_ipv6_link_local, int_intra_fabric_num_link, + # int_intra_fabric_unnum_link, int_intra_vpc_peer_keep_alive_link, + # int_pre_provision_intra_fabric_link, ios_xe_int_intra_fabric_num_link ] + + profile: + peer1_ipv4_addr: 192.173.1.1 # IP address of the Source interface + peer2_ipv4_addr: 192.173.1.2 # IP address of the Destination interface + admin_state: true # choose from [true, false] + mtu: 9216 # + + - dst_fabric: "{{ ansible_unnum_fabric }}" # Destination fabric + src_interface: "{{ intf_1_2 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_2 }}" # Interface on the Destination fabric + src_device: "{{ ansible_unnum_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_unnum_switch2 }}" # Device on the Destination fabric + template: int_pre_provision_intra_fabric_link # template to be applied, choose from + # [ int_intra_fabric_ipv6_link_local, int_intra_fabric_num_link, + # int_intra_fabric_unnum_link, int_intra_vpc_peer_keep_alive_link, + # int_pre_provision_intra_fabric_link, ios_xe_int_intra_fabric_num_link ] + + register: result + + - assert: + that: + - 'result.changed == true' + - '(result["diff"][0]["merged"] | length) == 2' + - '(result["diff"][0]["modified"] | length) == 0' + - '(result["diff"][0]["deleted"] | length) == 0' + - '(result["diff"][0]["query"] | length) == 0' + - '(result["diff"][0]["deploy"][0][ "{{ ansible_unnum_fabric }}" ] | length) == 2' + + - assert: + that: + - 'item["RETURN_CODE"] == 200' + loop: '{{ result.response }}' + + +############################################### +### DELETE ## +############################################### + + - name: Delete Links + cisco.dcnm.dcnm_links: *links_delete + register: result + + - assert: + that: + - 'result.changed == true' + - '(result["diff"][0]["merged"] | length) == 0' + - '(result["diff"][0]["modified"] | length) == 0' + - '(result["diff"][0]["deleted"] | length) == 2' + - '(result["diff"][0]["query"] | length) == 0' + - '(result["diff"][0]["deploy"][0][ "{{ ansible_unnum_fabric }}" ] | length) == 2' + + - assert: + that: + - 'item["RETURN_CODE"] == 200' + loop: '{{ result.response }}' + + +############################################## +## IDEMPOTENCE ## +############################################## + + - name: Delete Links - Idempotence + cisco.dcnm.dcnm_links: *links_delete + register: result + + - assert: + that: + - 'result.changed == false' + - '(result["diff"][0]["merged"] | length) == 0' + - '(result["diff"][0]["modified"] | length) == 0' + - '(result["diff"][0]["deleted"] | length) == 0' + - '(result["diff"][0]["query"] | length) == 0' + - '(result["diff"][0]["deploy"] | length) == 0' + + - assert: + that: + - 'item["RETURN_CODE"] == 200' + loop: '{{ result.response }}' + + +############################################### +### CLEANUP ## +############################################### + + always: + + - name: Delete Links + cisco.dcnm.dcnm_links: *links_delete + register: result + + - assert: + that: + - 'item["RETURN_CODE"] == 200' + loop: '{{ result.response }}' diff --git a/tests/integration/targets/dcnm_links/tests/dcnm/dcnm_links_intra_unnumbered_merge.yaml b/tests/integration/targets/dcnm_links/tests/dcnm/dcnm_links_intra_unnumbered_merge.yaml new file mode 100644 index 000000000..01be13939 --- /dev/null +++ b/tests/integration/targets/dcnm_links/tests/dcnm/dcnm_links_intra_unnumbered_merge.yaml @@ -0,0 +1,233 @@ +############################################## +## SETUP ## +############################################## + +- name: Remove local log file + local_action: command rm -f dcnm_links.log + +- block: + +############################################## +## DELETE ## +############################################## + + - name: Initial setup - Delete Links + cisco.dcnm.dcnm_links: &links_delete + state: deleted # choose from [merged, replaced, deleted, query] + src_fabric: "{{ ansible_unnum_fabric }}" + config: + - dst_fabric: "{{ ansible_unnum_fabric }}" # Destination fabric + src_interface: "{{ intf_1_1 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_1 }}" # Interface on the Destination fabric + src_device: "{{ ansible_unnum_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_unnum_switch2 }}" # Device on the Destination fabric + + - dst_fabric: "{{ ansible_unnum_fabric }}" # Destination fabric + src_interface: "{{ intf_1_2 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_2 }}" # Interface on the Destination fabric + src_device: "{{ ansible_unnum_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_unnum_switch2 }}" # Device on the Destination fabric + + - dst_fabric: "{{ ansible_unnum_fabric }}" # Destination fabric + src_interface: "{{ intf_1_3 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_3 }}" # Interface on the Destination fabric + src_device: "{{ ansible_unnum_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_unnum_switch2 }}" # Device on the Destination fabric + + - dst_fabric: "{{ ansible_unnum_fabric }}" # Destination fabric + src_interface: "{{ intf_1_4 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_4 }}" # Interface on the Destination fabric + src_device: "{{ ansible_unnum_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_unnum_switch2 }}" # Device on the Destination fabric + + - dst_fabric: "{{ ansible_unnum_fabric }}" # Destination fabric + src_interface: "{{ intf_1_5 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_5 }}" # Interface on the Destination fabric + src_device: "{{ ansible_unnum_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_unnum_switch2 }}" # Device on the Destination fabric + + register: result + + - assert: + that: + - 'item["RETURN_CODE"] == 200' + loop: '{{ result.response }}' + +############################################## +## MERGE ## +############################################## + + - name: Create Links without including optional parameters + cisco.dcnm.dcnm_links: &links_merge_no_opt + state: merged # choose from [merged, replaced, deleted, query] + src_fabric: "{{ ansible_unnum_fabric }}" + config: + - dst_fabric: "{{ ansible_unnum_fabric }}" # Destination fabric + src_interface: "{{ intf_1_1 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_1 }}" # Interface on the Destination fabric + src_device: "{{ ansible_unnum_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_unnum_switch2 }}" # Device on the Destination fabric + template: int_intra_fabric_unnum_link # template to be applied, choose from + # [ int_intra_fabric_ipv6_link_local, int_intra_fabric_num_link, + # int_intra_fabric_unnum_link, int_intra_vpc_peer_keep_alive_link, + # int_pre_provision_intra_fabric_link, ios_xe_int_intra_fabric_num_link ] + + profile: + admin_state: true # choose from [true, false] + mtu: 9216 # + + - dst_fabric: "{{ ansible_unnum_fabric }}" # Destination fabric + src_interface: "{{ intf_1_2 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_2 }}" # Interface on the Destination fabric + src_device: "{{ ansible_unnum_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_unnum_switch2 }}" # Device on the Destination fabric + template: int_pre_provision_intra_fabric_link # template to be applied, choose from + # [ int_intra_fabric_ipv6_link_local, int_intra_fabric_num_link, + # int_intra_fabric_unnum_link, int_intra_vpc_peer_keep_alive_link, + # int_pre_provision_intra_fabric_link, ios_xe_int_intra_fabric_num_link ] + + register: result + + - assert: + that: + - 'result.changed == true' + - '(result["diff"][0]["merged"] | length) == 2' + - '(result["diff"][0]["modified"] | length) == 0' + - '(result["diff"][0]["deleted"] | length) == 0' + - '(result["diff"][0]["query"] | length) == 0' + - '(result["diff"][0]["deploy"][0][ "{{ ansible_unnum_fabric }}" ] | length) == 2' + + - assert: + that: + - 'item["RETURN_CODE"] == 200' + loop: '{{ result.response }}' + + +############################################## +## IDEMPOTENCE ## +############################################## + + - name: Create Links - Idempotence + cisco.dcnm.dcnm_links: *links_merge_no_opt + register: result + + - assert: + that: + - 'result.changed == true' + - '(result["diff"][0]["merged"] | length) == 0' + - '(result["diff"][0]["modified"] | length) == 0' + - '(result["diff"][0]["deleted"] | length) == 0' + - '(result["diff"][0]["query"] | length) == 0' + - '(result["diff"][0]["deploy"][0][ "{{ ansible_unnum_fabric }}" ] | length) == 2' + + - assert: + that: + - 'item["RETURN_CODE"] == 200' + loop: '{{ result.response }}' + +############################################## +## DELETE ## +############################################## + + - name: Delete Links + cisco.dcnm.dcnm_links: *links_delete + register: result + + - assert: + that: + - 'item["RETURN_CODE"] == 200' + loop: '{{ result.response }}' + +############################################## +## MERGE ## +############################################## + + - name: Create Links including all optional parameters + cisco.dcnm.dcnm_links: &links_merge_with_opt + state: merged # choose from [merged, replaced, deleted, query] + src_fabric: "{{ ansible_unnum_fabric }}" + config: + - dst_fabric: "{{ ansible_unnum_fabric }}" # Destination fabric + src_interface: "{{ intf_1_1 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_1 }}" # Interface on the Destination fabric + src_device: "{{ ansible_unnum_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_unnum_switch2 }}" # Device on the Destination fabric + template: int_intra_fabric_unnum_link # template to be applied, choose from + # [ int_intra_fabric_ipv6_link_local, int_intra_fabric_num_link, + # int_intra_fabric_unnum_link, int_intra_vpc_peer_keep_alive_link, + # int_pre_provision_intra_fabric_link, ios_xe_int_intra_fabric_num_link ] + + profile: + admin_state: true # choose from [true, false] + mtu: 9216 # + peer1_description: "Description of source" # optional, default is "" + peer2_description: "Description of dest" # optional, default is "" + enable_macsec: false # optional, choose from [true, false] + peer1_cmds: # Freeform config for source device + - no shutdown # optional, default is "" + peer2_cmds: # Freeform config for destination device + - no shutdown # optional, default is "" + + - dst_fabric: "{{ ansible_unnum_fabric }}" # Destination fabric + src_interface: "{{ intf_1_2 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_2 }}" # Interface on the Destination fabric + src_device: "{{ ansible_unnum_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_unnum_switch2 }}" # Device on the Destination fabric + template: int_pre_provision_intra_fabric_link # template to be applied, choose from + # [ int_intra_fabric_ipv6_link_local, int_intra_fabric_num_link, + # int_intra_fabric_unnum_link, int_intra_vpc_peer_keep_alive_link, + # int_pre_provision_intra_fabric_link, ios_xe_int_intra_fabric_num_link ] + register: result + + - assert: + that: + - 'result.changed == true' + - '(result["diff"][0]["merged"] | length) == 2' + - '(result["diff"][0]["modified"] | length) == 0' + - '(result["diff"][0]["deleted"] | length) == 0' + - '(result["diff"][0]["query"] | length) == 0' + - '(result["diff"][0]["deploy"][0][ "{{ ansible_unnum_fabric }}" ] | length) == 2' + + - assert: + that: + - 'item["RETURN_CODE"] == 200' + loop: '{{ result.response }}' + +############################################## +## IDEMPOTENCE ## +############################################## + + - name: Create Links - Idempotence + cisco.dcnm.dcnm_links: *links_merge_with_opt + register: result + + - assert: + that: + - 'result.changed == true' + - '(result["diff"][0]["merged"] | length) == 0' + - '(result["diff"][0]["modified"] | length) == 0' + - '(result["diff"][0]["deleted"] | length) == 0' + - '(result["diff"][0]["query"] | length) == 0' + - '(result["diff"][0]["deploy"][0][ "{{ ansible_unnum_fabric }}" ] | length) == 2' + + - assert: + that: + - 'item["RETURN_CODE"] == 200' + loop: '{{ result.response }}' + +############################################### +### CLEANUP ## +############################################### + + always: + + - name: Cleanup - Delete Links + cisco.dcnm.dcnm_links: *links_delete + register: result + when: IT_CONTEXT is not defined + + - assert: + that: + - 'item["RETURN_CODE"] == 200' + loop: '{{ result.response }}' + when: IT_CONTEXT is not defined diff --git a/tests/integration/targets/dcnm_links/tests/dcnm/dcnm_links_intra_unnumbered_modify.yaml b/tests/integration/targets/dcnm_links/tests/dcnm/dcnm_links_intra_unnumbered_modify.yaml new file mode 100644 index 000000000..26e59fc67 --- /dev/null +++ b/tests/integration/targets/dcnm_links/tests/dcnm/dcnm_links_intra_unnumbered_modify.yaml @@ -0,0 +1,167 @@ +############################################## +## SETUP ## +############################################## + +- name: Remove local log file + local_action: command rm -f dcnm_links.log + +- block: + +############################################## +## DELETE ## +############################################## + + - name: Initial setup - Delete Links + cisco.dcnm.dcnm_links: &links_delete + state: deleted # choose from [merged, replaced, deleted, query] + src_fabric: "{{ ansible_unnum_fabric }}" + config: + - dst_fabric: "{{ ansible_unnum_fabric }}" # Destination fabric + src_interface: "{{ intf_1_1 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_1 }}" # Interface on the Destination fabric + src_device: "{{ ansible_unnum_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_unnum_switch2 }}" # Device on the Destination fabric + + - dst_fabric: "{{ ansible_unnum_fabric }}" # Destination fabric + src_interface: "{{ intf_1_2 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_2 }}" # Interface on the Destination fabric + src_device: "{{ ansible_unnum_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_unnum_switch2 }}" # Device on the Destination fabric + + - dst_fabric: "{{ ansible_unnum_fabric }}" # Destination fabric + src_interface: "{{ intf_1_3 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_3 }}" # Interface on the Destination fabric + src_device: "{{ ansible_unnum_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_unnum_switch2 }}" # Device on the Destination fabric + + - dst_fabric: "{{ ansible_unnum_fabric }}" # Destination fabric + src_interface: "{{ intf_1_4 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_4 }}" # Interface on the Destination fabric + src_device: "{{ ansible_unnum_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_unnum_switch2 }}" # Device on the Destination fabric + + - dst_fabric: "{{ ansible_unnum_fabric }}" # Destination fabric + src_interface: "{{ intf_1_5 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_5 }}" # Interface on the Destination fabric + src_device: "{{ ansible_unnum_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_unnum_switch2 }}" # Device on the Destination fabric + + register: result + + - assert: + that: + - 'item["RETURN_CODE"] == 200' + loop: '{{ result.response }}' + +############################################## +## MERGE ## +############################################## + + - name: Create Links without including optional parameters + cisco.dcnm.dcnm_links: &links_merge_no_opt + state: merged # choose from [merged, replaced, deleted, query] + src_fabric: "{{ ansible_unnum_fabric }}" + config: + - dst_fabric: "{{ ansible_unnum_fabric }}" # Destination fabric + src_interface: "{{ intf_1_1 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_1 }}" # Interface on the Destination fabric + src_device: "{{ ansible_unnum_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_unnum_switch2 }}" # Device on the Destination fabric + template: int_intra_fabric_unnum_link # template to be applied, choose from + # [ int_intra_fabric_ipv6_link_local, int_intra_fabric_num_link, + # int_intra_fabric_unnum_link, int_intra_vpc_peer_keep_alive_link, + # int_pre_provision_intra_fabric_link, ios_xe_int_intra_fabric_num_link ] + + profile: + admin_state: true # choose from [true, false] + mtu: 9216 # + + - dst_fabric: "{{ ansible_unnum_fabric }}" # Destination fabric + src_interface: "{{ intf_1_2 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_2 }}" # Interface on the Destination fabric + src_device: "{{ ansible_unnum_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_unnum_switch2 }}" # Device on the Destination fabric + template: int_pre_provision_intra_fabric_link # template to be applied, choose from + # [ int_intra_fabric_ipv6_link_local, int_intra_fabric_num_link, + # int_intra_fabric_unnum_link, int_intra_vpc_peer_keep_alive_link, + # int_pre_provision_intra_fabric_link, ios_xe_int_intra_fabric_num_link ] + + register: result + + - assert: + that: + - 'result.changed == true' + - '(result["diff"][0]["merged"] | length) == 2' + - '(result["diff"][0]["modified"] | length) == 0' + - '(result["diff"][0]["deleted"] | length) == 0' + - '(result["diff"][0]["query"] | length) == 0' + - '(result["diff"][0]["deploy"][0][ "{{ ansible_unnum_fabric }}" ] | length) == 2' + + - assert: + that: + - 'item["RETURN_CODE"] == 200' + loop: '{{ result.response }}' + + +############################################## +## MODIFY ## +############################################## + + - name: Merge modified info into existing links + cisco.dcnm.dcnm_links: + state: merged # choose from [merged, replaced, deleted, query] + src_fabric: "{{ ansible_unnum_fabric }}" + config: + - dst_fabric: "{{ ansible_unnum_fabric }}" # Destination fabric + src_interface: "{{ intf_1_1 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_1 }}" # Interface on the Destination fabric + src_device: "{{ ansible_unnum_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_unnum_switch2 }}" # Device on the Destination fabric + template: int_intra_fabric_unnum_link # template to be applied, choose from + # [ int_intra_fabric_ipv6_link_local, int_intra_fabric_num_link, + # int_intra_fabric_unnum_link, int_intra_vpc_peer_keep_alive_link, + # int_pre_provision_intra_fabric_link, ios_xe_int_intra_fabric_num_link ] + + profile: + admin_state: false # choose from [true, false] + mtu: 1800 # + + - dst_fabric: "{{ ansible_unnum_fabric }}" # Destination fabric + src_interface: "{{ intf_1_2 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_2 }}" # Interface on the Destination fabric + src_device: "{{ ansible_unnum_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_unnum_switch2 }}" # Device on the Destination fabric + template: int_pre_provision_intra_fabric_link # template to be applied, choose from + # [ int_intra_fabric_ipv6_link_local, int_intra_fabric_num_link, + # int_intra_fabric_unnum_link, int_intra_vpc_peer_keep_alive_link, + # int_pre_provision_intra_fabric_link, ios_xe_int_intra_fabric_num_link ] + register: result + + - assert: + that: + - 'result.changed == true' + - '(result["diff"][0]["merged"] | length) == 0' + - '(result["diff"][0]["modified"] | length) == 1' + - '(result["diff"][0]["deleted"] | length) == 0' + - '(result["diff"][0]["query"] | length) == 0' + - '(result["diff"][0]["deploy"][0][ "{{ ansible_unnum_fabric }}" ] | length) == 2' + + - assert: + that: + - 'item["RETURN_CODE"] == 200' + loop: '{{ result.response }}' + +############################################## +## CLEANUP ## +############################################## + + - name: Cleanup - Delete Links + cisco.dcnm.dcnm_links: *links_delete + register: result + when: IT_CONTEXT is not defined + + - assert: + that: + - 'item["RETURN_CODE"] == 200' + loop: '{{ result.response }}' + when: IT_CONTEXT is not defined diff --git a/tests/integration/targets/dcnm_links/tests/dcnm/dcnm_links_intra_unnumbered_query.yaml b/tests/integration/targets/dcnm_links/tests/dcnm/dcnm_links_intra_unnumbered_query.yaml new file mode 100644 index 000000000..92a2349b7 --- /dev/null +++ b/tests/integration/targets/dcnm_links/tests/dcnm/dcnm_links_intra_unnumbered_query.yaml @@ -0,0 +1,235 @@ +############################################## +## SETUP ## +############################################## + +- name: Remove local log file + local_action: command rm -f dcnm_links.log + +- block: + +############################################## +## DELETE ## +############################################## + + - name: Initial setup - Delete Links + cisco.dcnm.dcnm_links: &links_delete + state: deleted # choose from [merged, replaced, deleted, query] + src_fabric: "{{ ansible_unnum_fabric }}" + config: + - dst_fabric: "{{ ansible_unnum_fabric }}" # Destination fabric + src_interface: "{{ intf_1_1 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_1 }}" # Interface on the Destination fabric + src_device: "{{ ansible_unnum_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_unnum_switch2 }}" # Device on the Destination fabric + + - dst_fabric: "{{ ansible_unnum_fabric }}" # Destination fabric + src_interface: "{{ intf_1_2 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_2 }}" # Interface on the Destination fabric + src_device: "{{ ansible_unnum_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_unnum_switch2 }}" # Device on the Destination fabric + + - dst_fabric: "{{ ansible_unnum_fabric }}" # Destination fabric + src_interface: "{{ intf_1_3 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_3 }}" # Interface on the Destination fabric + src_device: "{{ ansible_unnum_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_unnum_switch2 }}" # Device on the Destination fabric + + - dst_fabric: "{{ ansible_unnum_fabric }}" # Destination fabric + src_interface: "{{ intf_1_4 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_4 }}" # Interface on the Destination fabric + src_device: "{{ ansible_unnum_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_unnum_switch2 }}" # Device on the Destination fabric + + - dst_fabric: "{{ ansible_unnum_fabric }}" # Destination fabric + src_interface: "{{ intf_1_5 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_5 }}" # Interface on the Destination fabric + src_device: "{{ ansible_unnum_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_unnum_switch2 }}" # Device on the Destination fabric + register: result + + - assert: + that: + - 'item["RETURN_CODE"] == 200' + loop: '{{ result.response }}' + +############################################### +### MERGE ## +############################################### + + - name: Create Links without including optional parameters + cisco.dcnm.dcnm_links: &links_merge_no_opt + state: merged # choose from [merged, replaced, deleted, query] + src_fabric: "{{ ansible_unnum_fabric }}" + config: + - dst_fabric: "{{ ansible_unnum_fabric }}" # Destination fabric + src_interface: "{{ intf_1_1 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_1 }}" # Interface on the Destination fabric + src_device: "{{ ansible_unnum_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_unnum_switch2 }}" # Device on the Destination fabric + template: int_intra_fabric_unnum_link # template to be applied, choose from + # [ int_intra_fabric_ipv6_link_local, int_intra_fabric_num_link, + # int_intra_fabric_unnum_link, int_intra_vpc_peer_keep_alive_link, + # int_pre_provision_intra_fabric_link, ios_xe_int_intra_fabric_num_link ] + + profile: + admin_state: true # choose from [true, false] + mtu: 9216 # + + - dst_fabric: "{{ ansible_unnum_fabric }}" # Destination fabric + src_interface: "{{ intf_1_2 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_2 }}" # Interface on the Destination fabric + src_device: "{{ ansible_unnum_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_unnum_switch2 }}" # Device on the Destination fabric + template: int_pre_provision_intra_fabric_link # template to be applied, choose from + # [ int_intra_fabric_ipv6_link_local, int_intra_fabric_num_link, + # int_intra_fabric_unnum_link, int_intra_vpc_peer_keep_alive_link, + # int_pre_provision_intra_fabric_link, ios_xe_int_intra_fabric_num_link ] + + register: result + + - assert: + that: + - 'result.changed == true' + - '(result["diff"][0]["merged"] | length) == 2' + - '(result["diff"][0]["modified"] | length) == 0' + - '(result["diff"][0]["deleted"] | length) == 0' + - '(result["diff"][0]["query"] | length) == 0' + - '(result["diff"][0]["deploy"][0][ "{{ ansible_unnum_fabric }}" ] | length) == 2' + + - assert: + that: + - 'item["RETURN_CODE"] == 200' + loop: '{{ result.response }}' + +############################################### +### QUERY ## +############################################### + + - name: Query Links - with Src Fabric + cisco.dcnm.dcnm_links: + state: query # choose from [merged, replaced, deleted, query] + src_fabric: "{{ ansible_unnum_fabric }}" + register: result + + - assert: + that: + '(result["response"] | length) >= 2' + + - name: Query Links - with Src & Dst Fabric + cisco.dcnm.dcnm_links: + state: query # choose from [merged, replaced, deleted, query] + src_fabric: "{{ ansible_unnum_fabric }}" + config: + - dst_fabric: "{{ ansible_unnum_fabric }}" # optional, Destination fabric + register: result + + - assert: + that: + '(result["response"] | length) >= 2' + + - name: Query Links - with Src & Dst Fabric, Src Intf + cisco.dcnm.dcnm_links: + state: query # choose from [merged, replaced, deleted, query] + src_fabric: "{{ ansible_unnum_fabric }}" + config: + - dst_fabric: "{{ ansible_unnum_fabric }}" # optional, Destination fabric + src_interface: "{{ intf_1_1 }}" # optional, Interface on the Source fabric + register: result + + - assert: + that: + '(result["response"] | length) >= 1' + + - name: Query Links - with Src & Dst Fabric, Src & Dst Intf + cisco.dcnm.dcnm_links: + state: query # choose from [merged, replaced, deleted, query] + src_fabric: "{{ ansible_unnum_fabric }}" + config: + - dst_fabric: "{{ ansible_unnum_fabric }}" # optional, Destination fabric + src_interface: "{{ intf_1_1 }}" # optional, Interface on the Source fabric + dst_interface: "{{ intf_1_1 }}" # optional, Interface on the Destination fabric + register: result + + - assert: + that: + '(result["response"] | length) >= 1' + + - name: Query Links - with Src & Dst Fabric, Src & Dst Intf, Src Device + cisco.dcnm.dcnm_links: + state: query # choose from [merged, replaced, deleted, query] + src_fabric: "{{ ansible_unnum_fabric }}" + config: + - dst_fabric: "{{ ansible_unnum_fabric }}" # optional, Destination fabric + src_interface: "{{ intf_1_1 }}" # optional, Interface on the Source fabric + dst_interface: "{{ intf_1_1 }}" # optional, Interface on the Destination fabric + src_device: "{{ ansible_unnum_switch1 }}" # optional, Device on the Source fabric + register: result + + - assert: + that: + '(result["response"] | length) >= 1' + + - name: Query Links - with Src & Dst Fabric, Src & Dst Intf, Src & Dst Device + cisco.dcnm.dcnm_links: + state: query # choose from [merged, replaced, deleted, query] + src_fabric: "{{ ansible_unnum_fabric }}" + config: + - dst_fabric: "{{ ansible_unnum_fabric }}" # optional, Destination fabric + src_interface: "{{ intf_1_1 }}" # optional, Interface on the Source fabric + dst_interface: "{{ intf_1_1 }}" # optional, Interface on the Destination fabric + src_device: "{{ ansible_unnum_switch1 }}" # optional, Device on the Source fabric + dst_device: "{{ ansible_unnum_switch2 }}" # optional, Device on the Destination fabric + register: result + + - assert: + that: + '(result["response"] | length) >= 1' + + - name: Query Links - with Src & Dst Fabric, Src & Dst Intf, Src & Dst Device, Template + cisco.dcnm.dcnm_links: + state: query # choose from [merged, replaced, deleted, query] + src_fabric: "{{ ansible_unnum_fabric }}" + config: + - dst_fabric: "{{ ansible_unnum_fabric }}" # optional, Destination fabric + src_interface: "{{ intf_1_1 }}" # optional, Interface on the Source fabric + dst_interface: "{{ intf_1_1 }}" # optional, Interface on the Destination fabric + src_device: "{{ ansible_unnum_switch1 }}" # optional, Device on the Source fabric + dst_device: "{{ ansible_unnum_switch2 }}" # optional, Device on the Destination fabric + template: int_intra_fabric_unnum_link # optional, template to be applied, choose from + # [ int_intra_fabric_ipv6_link_local, int_intra_fabric_num_link, + # int_intra_fabric_unnum_link, int_intra_vpc_peer_keep_alive_link, + # int_pre_provision_intra_fabric_link, ios_xe_int_intra_fabric_num_link ] + + - dst_fabric: "{{ ansible_unnum_fabric }}" # optional, Destination fabric + src_interface: "{{ intf_1_2 }}" # optional, Interface on the Source fabric + dst_interface: "{{ intf_1_2 }}" # optional, Interface on the Destination fabric + src_device: "{{ ansible_unnum_switch1 }}" # optional, Device on the Source fabric + dst_device: "{{ ansible_unnum_switch2 }}" # optional, Device on the Destination fabric + template: int_pre_provision_intra_fabric_link # optional, template to be applied, choose from + # [ int_intra_fabric_ipv6_link_local, int_intra_fabric_num_link, + # int_intra_fabric_unnum_link, int_intra_vpc_peer_keep_alive_link, + # int_pre_provision_intra_fabric_link, ios_xe_int_intra_fabric_num_link ] + register: result + + - assert: + that: + '(result["response"] | length) >= 2' + + register: result + +############################################### +### CLEANUP ## +############################################### + + always: + + - name: Cleanup - Delete Links + cisco.dcnm.dcnm_links: *links_delete + register: result + when: IT_CONTEXT is not defined + + - assert: + that: + - 'item["RETURN_CODE"] == 200' + loop: '{{ result.response }}' + when: IT_CONTEXT is not defined diff --git a/tests/integration/targets/dcnm_links/tests/dcnm/dcnm_links_intra_unnumbered_replace.yaml b/tests/integration/targets/dcnm_links/tests/dcnm/dcnm_links_intra_unnumbered_replace.yaml new file mode 100644 index 000000000..7123b3f83 --- /dev/null +++ b/tests/integration/targets/dcnm_links/tests/dcnm/dcnm_links_intra_unnumbered_replace.yaml @@ -0,0 +1,201 @@ +############################################## +## SETUP ## +############################################## + +- name: Remove local log file + local_action: command rm -f dcnm_links.log + +- block: + +############################################## +## DELETE ## +############################################## + + - name: Initial setup - Delete Links + cisco.dcnm.dcnm_links: &links_delete + state: deleted # choose from [merged, replaced, deleted, query] + src_fabric: "{{ ansible_unnum_fabric }}" + config: + - dst_fabric: "{{ ansible_unnum_fabric }}" # Destination fabric + src_interface: "{{ intf_1_1 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_1 }}" # Interface on the Destination fabric + src_device: "{{ ansible_unnum_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_unnum_switch2 }}" # Device on the Destination fabric + + - dst_fabric: "{{ ansible_unnum_fabric }}" # Destination fabric + src_interface: "{{ intf_1_2 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_2 }}" # Interface on the Destination fabric + src_device: "{{ ansible_unnum_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_unnum_switch2 }}" # Device on the Destination fabric + + - dst_fabric: "{{ ansible_unnum_fabric }}" # Destination fabric + src_interface: "{{ intf_1_3 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_3 }}" # Interface on the Destination fabric + src_device: "{{ ansible_unnum_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_unnum_switch2 }}" # Device on the Destination fabric + + - dst_fabric: "{{ ansible_unnum_fabric }}" # Destination fabric + src_interface: "{{ intf_1_4 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_4 }}" # Interface on the Destination fabric + src_device: "{{ ansible_unnum_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_unnum_switch2 }}" # Device on the Destination fabric + + - dst_fabric: "{{ ansible_unnum_fabric }}" # Destination fabric + src_interface: "{{ intf_1_5 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_5 }}" # Interface on the Destination fabric + src_device: "{{ ansible_unnum_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_unnum_switch2 }}" # Device on the Destination fabric + + register: result + + - assert: + that: + - 'item["RETURN_CODE"] == 200' + loop: '{{ result.response }}' + +############################################## +## MERGE ## +############################################## + + - name: Create Links including all optional parameters + cisco.dcnm.dcnm_links: &links_merge_with_opt + state: merged # choose from [merged, replaced, deleted, query] + src_fabric: "{{ ansible_unnum_fabric }}" + config: + - dst_fabric: "{{ ansible_unnum_fabric }}" # Destination fabric + src_interface: "{{ intf_1_1 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_1 }}" # Interface on the Destination fabric + src_device: "{{ ansible_unnum_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_unnum_switch2 }}" # Device on the Destination fabric + template: int_intra_fabric_unnum_link # template to be applied, choose from + # [ int_intra_fabric_ipv6_link_local, int_intra_fabric_num_link, + # int_intra_fabric_unnum_link, int_intra_vpc_peer_keep_alive_link, + # int_pre_provision_intra_fabric_link, ios_xe_int_intra_fabric_num_link ] + + profile: + admin_state: true # choose from [true, false] + mtu: 9216 # + peer1_description: "Description of source" # optional, default is "" + peer2_description: "Description of dest" # optional, default is "" + enable_macsec: false # optional, choose from [true, false] + peer1_cmds: # Freeform config for source device + - no shutdown # optional, default is "" + peer2_cmds: # Freeform config for destination device + - no shutdown # optional, default is "" + + - dst_fabric: "{{ ansible_unnum_fabric }}" # Destination fabric + src_interface: "{{ intf_1_2 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_2 }}" # Interface on the Destination fabric + src_device: "{{ ansible_unnum_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_unnum_switch2 }}" # Device on the Destination fabric + template: int_pre_provision_intra_fabric_link # template to be applied, choose from + # [ int_intra_fabric_ipv6_link_local, int_intra_fabric_num_link, + # int_intra_fabric_unnum_link, int_intra_vpc_peer_keep_alive_link, + # int_pre_provision_intra_fabric_link, ios_xe_int_intra_fabric_num_link ] + register: result + + - assert: + that: + - 'result.changed == true' + - '(result["diff"][0]["merged"] | length) == 2' + - '(result["diff"][0]["modified"] | length) == 0' + - '(result["diff"][0]["deleted"] | length) == 0' + - '(result["diff"][0]["query"] | length) == 0' + - '(result["diff"][0]["deploy"][0][ "{{ ansible_unnum_fabric }}" ] | length) == 2' + + - assert: + that: + - 'item["RETURN_CODE"] == 200' + loop: '{{ result.response }}' + +############################################## +## REPLACE ## +############################################## + + - name: Replace Links + cisco.dcnm.dcnm_links: &links_replace + state: replaced # choose from [merged, replaced, deleted, query] + src_fabric: "{{ ansible_unnum_fabric }}" + config: + - dst_fabric: "{{ ansible_unnum_fabric }}" # Destination fabric + src_interface: "{{ intf_1_1 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_1 }}" # Interface on the Destination fabric + src_device: "{{ ansible_unnum_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_unnum_switch2 }}" # Device on the Destination fabric + template: int_intra_fabric_unnum_link # template to be applied, choose from + # [ int_intra_fabric_ipv6_link_local, int_intra_fabric_num_link, + # int_intra_fabric_unnum_link, int_intra_vpc_peer_keep_alive_link, + # int_pre_provision_intra_fabric_link, ios_xe_int_intra_fabric_num_link ] + + profile: + admin_state: false # choose from [true, false] + mtu: 1216 # + peer1_description: "Description of source - MOD" # optional, default is "" + peer2_description: "Description of dest - MOD" # optional, default is "" + enable_macsec: false # optional, choose from [true, false] + peer1_cmds: # Freeform config for source device + - cdp enable # optional, default is "" + + - dst_fabric: "{{ ansible_unnum_fabric }}" # Destination fabric + src_interface: "{{ intf_1_2 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_2 }}" # Interface on the Destination fabric + src_device: "{{ ansible_unnum_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_unnum_switch2 }}" # Device on the Destination fabric + template: int_pre_provision_intra_fabric_link # template to be applied, choose from + # [ int_intra_fabric_ipv6_link_local, int_intra_fabric_num_link, + # int_intra_fabric_unnum_link, int_intra_vpc_peer_keep_alive_link, + # int_pre_provision_intra_fabric_link, ios_xe_int_intra_fabric_num_link ] + register: result + + - assert: + that: + - 'result.changed == true' + - '(result["diff"][0]["merged"] | length) == 0' + - '(result["diff"][0]["modified"] | length) == 1' + - '(result["diff"][0]["deleted"] | length) == 0' + - '(result["diff"][0]["query"] | length) == 0' + - '(result["diff"][0]["deploy"][0][ "{{ ansible_unnum_fabric }}" ] | length) == 2' + + - assert: + that: + - 'item["RETURN_CODE"] == 200' + loop: '{{ result.response }}' + +############################################## +## IDEMPOTENCE ## +############################################## + + - name: Repalce Links - Idempotence + cisco.dcnm.dcnm_links: *links_replace + register: result + + - assert: + that: + - 'result.changed == true' + - '(result["diff"][0]["merged"] | length) == 0' + - '(result["diff"][0]["modified"] | length) == 0' + - '(result["diff"][0]["deleted"] | length) == 0' + - '(result["diff"][0]["query"] | length) == 0' + - '(result["diff"][0]["deploy"][0][ "{{ ansible_unnum_fabric }}" ] | length) == 2' + + - assert: + that: + - 'item["RETURN_CODE"] == 200' + loop: '{{ result.response }}' + +############################################### +### CLEANUP ## +############################################### + + always: + + - name: Cleanup - Delete Links + cisco.dcnm.dcnm_links: *links_delete + register: result + when: IT_CONTEXT is not defined + + - assert: + that: + - 'item["RETURN_CODE"] == 200' + loop: '{{ result.response }}' + when: IT_CONTEXT is not defined diff --git a/tests/integration/targets/dcnm_links/tests/dcnm/dcnm_links_intra_unnumbered_template_change.yaml b/tests/integration/targets/dcnm_links/tests/dcnm/dcnm_links_intra_unnumbered_template_change.yaml new file mode 100644 index 000000000..7e12bc093 --- /dev/null +++ b/tests/integration/targets/dcnm_links/tests/dcnm/dcnm_links_intra_unnumbered_template_change.yaml @@ -0,0 +1,144 @@ +############################################## +## SETUP ## +############################################## + +- name: Remove local log file + local_action: command rm -f dcnm_links.log + +- block: + +############################################## +## DELETE ## +############################################## + + - name: Initial setup - Delete Links + cisco.dcnm.dcnm_links: &links_delete + state: deleted # choose from [merged, replaced, deleted, query] + src_fabric: "{{ ansible_unnum_fabric }}" + config: + - dst_fabric: "{{ ansible_unnum_fabric }}" # Destination fabric + src_interface: "{{ intf_1_1 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_1 }}" # Interface on the Destination fabric + src_device: "{{ ansible_unnum_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_unnum_switch2 }}" # Device on the Destination fabric + + - dst_fabric: "{{ ansible_unnum_fabric }}" # Destination fabric + src_interface: "{{ intf_1_2 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_2 }}" # Interface on the Destination fabric + src_device: "{{ ansible_unnum_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_unnum_switch2 }}" # Device on the Destination fabric + + - dst_fabric: "{{ ansible_unnum_fabric }}" # Destination fabric + src_interface: "{{ intf_1_3 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_3 }}" # Interface on the Destination fabric + src_device: "{{ ansible_unnum_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_unnum_switch2 }}" # Device on the Destination fabric + + - dst_fabric: "{{ ansible_unnum_fabric }}" # Destination fabric + src_interface: "{{ intf_1_4 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_4 }}" # Interface on the Destination fabric + src_device: "{{ ansible_unnum_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_unnum_switch2 }}" # Device on the Destination fabric + + - dst_fabric: "{{ ansible_unnum_fabric }}" # Destination fabric + src_interface: "{{ intf_1_5 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_5 }}" # Interface on the Destination fabric + src_device: "{{ ansible_unnum_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_unnum_switch2 }}" # Device on the Destination fabric + + register: result + + - assert: + that: + - 'item["RETURN_CODE"] == 200' + loop: '{{ result.response }}' + +############################################## +## MERGE ## +############################################## + + - name: Create Links without including optional parameters + cisco.dcnm.dcnm_links: &links_merge_no_opt + state: merged # choose from [merged, replaced, deleted, query] + src_fabric: "{{ ansible_unnum_fabric }}" + config: + - dst_fabric: "{{ ansible_unnum_fabric }}" # Destination fabric + src_interface: "{{ intf_1_1 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_1 }}" # Interface on the Destination fabric + src_device: "{{ ansible_unnum_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_unnum_switch2 }}" # Device on the Destination fabric + template: int_intra_fabric_unnum_link # template to be applied, choose from + # [ int_intra_fabric_ipv6_link_local, int_intra_fabric_num_link, + # int_intra_fabric_unnum_link, int_intra_vpc_peer_keep_alive_link, + # int_pre_provision_intra_fabric_link, ios_xe_int_intra_fabric_num_link ] + + profile: + admin_state: true # choose from [true, false] + mtu: 9216 # + + register: result + + - assert: + that: + - 'result.changed == true' + - '(result["diff"][0]["merged"] | length) == 1' + - '(result["diff"][0]["modified"] | length) == 0' + - '(result["diff"][0]["deleted"] | length) == 0' + - '(result["diff"][0]["query"] | length) == 0' + - '(result["diff"][0]["deploy"][0][ "{{ ansible_unnum_fabric }}" ] | length) == 2' + + - assert: + that: + - 'item["RETURN_CODE"] == 200' + loop: '{{ result.response }}' + +############################################## +## MODIFY ## +############################################## + + - name: Modify template name + cisco.dcnm.dcnm_links: + state: merged # choose from [merged, replaced, deleted, query] + src_fabric: "{{ ansible_unnum_fabric }}" + config: + - dst_fabric: "{{ ansible_unnum_fabric }}" # Destination fabric + src_interface: "{{ intf_1_1 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_1 }}" # Interface on the Destination fabric + src_device: "{{ ansible_unnum_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_unnum_switch2 }}" # Device on the Destination fabric + template: int_pre_provision_intra_fabric_link # template to be applied, choose from + # [ int_intra_fabric_ipv6_link_local, int_intra_fabric_num_link, + # int_intra_fabric_unnum_link, int_intra_vpc_peer_keep_alive_link, + # int_pre_provision_intra_fabric_link, ios_xe_int_intra_fabric_num_link ] + register: result + + - assert: + that: + - 'result.changed == true' + - '(result["diff"][0]["merged"] | length) == 0' + - '(result["diff"][0]["modified"] | length) == 1' + - '(result["diff"][0]["deleted"] | length) == 0' + - '(result["diff"][0]["query"] | length) == 0' + - '(result["diff"][0]["deploy"][0][ "{{ ansible_unnum_fabric }}" ] | length) == 2' + + - assert: + that: + - 'item["RETURN_CODE"] == 200' + loop: '{{ result.response }}' + +############################################### +### CLEANUP ## +############################################### + + always: + + - name: Cleanup - Delete Links + cisco.dcnm.dcnm_links: *links_delete + register: result + when: IT_CONTEXT is not defined + + - assert: + that: + - 'item["RETURN_CODE"] == 200' + loop: '{{ result.response }}' + when: IT_CONTEXT is not defined diff --git a/tests/integration/targets/dcnm_links/tests/dcnm/dcnm_links_intra_vpc_delete.yaml b/tests/integration/targets/dcnm_links/tests/dcnm/dcnm_links_intra_vpc_delete.yaml new file mode 100644 index 000000000..bbad7fb4c --- /dev/null +++ b/tests/integration/targets/dcnm_links/tests/dcnm/dcnm_links_intra_vpc_delete.yaml @@ -0,0 +1,163 @@ +############################################## +## SETUP ## +############################################## + +- name: Remove local log file + local_action: command rm -f dcnm_links.log + +- block: + +############################################## +## DELETE ## +############################################## + + - name: Initial setup - Delete Links + cisco.dcnm.dcnm_links: + state: deleted # choose from [merged, replaced, deleted, query] + src_fabric: "{{ ansible_vpc_fabric }}" + config: + - dst_fabric: "{{ ansible_vpc_fabric }}" # Destination fabric + src_interface: "{{ intf_1_1 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_1 }}" # Interface on the Destination fabric + src_device: "{{ ansible_vpc_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_vpc_switch2 }}" # Device on the Destination fabric + + - dst_fabric: "{{ ansible_vpc_fabric }}" # Destination fabric + src_interface: "{{ intf_1_2 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_2 }}" # Interface on the Destination fabric + src_device: "{{ ansible_vpc_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_vpc_switch2 }}" # Device on the Destination fabric + + - dst_fabric: "{{ ansible_vpc_fabric }}" # Destination fabric + src_interface: "{{ intf_1_3 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_3 }}" # Interface on the Destination fabric + src_device: "{{ ansible_vpc_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_vpc_switch2 }}" # Device on the Destination fabric + + - dst_fabric: "{{ ansible_vpc_fabric }}" # Destination fabric + src_interface: "{{ intf_1_4 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_4 }}" # Interface on the Destination fabric + src_device: "{{ ansible_vpc_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_vpc_switch2 }}" # Device on the Destination fabric + + - dst_fabric: "{{ ansible_vpc_fabric }}" # Destination fabric + src_interface: "{{ intf_1_5 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_5 }}" # Interface on the Destination fabric + src_device: "{{ ansible_vpc_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_vpc_switch2 }}" # Device on the Destination fabric + + register: result + + - assert: + that: + - 'item["RETURN_CODE"] == 200' + loop: '{{ result.response }}' + +############################################## +## MERGE ## +############################################## + + - name: Create Links without including optional parameters + cisco.dcnm.dcnm_links: &links_merge_no_opt + state: merged # choose from [merged, replaced, deleted, query] + src_fabric: "{{ ansible_vpc_fabric }}" + config: + - dst_fabric: "{{ ansible_vpc_fabric }}" # Destination fabric + src_interface: "{{ intf_1_4 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_4 }}" # Interface on the Destination fabric + src_device: "{{ ansible_vpc_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_vpc_switch2 }}" # Device on the Destination fabric + template: int_intra_vpc_peer_keep_alive_link # template to be applied, choose from + # [ int_intra_fabric_ipv6_link_local, int_intra_fabric_num_link, + # int_intra_fabric_unnum_link, int_intra_vpc_peer_keep_alive_link, + # int_pre_provision_intra_fabric_link, ios_xe_int_intra_fabric_num_link ] + + profile: + peer1_ipv4_addr: 192.168.1.1 # IPV4 address of the Source interface + peer2_ipv4_addr: 192.168.1.2 # IPV4 address of the Destination interface + admin_state: true # choose from [true, false] + mtu: 9216 # + + register: result + + - assert: + that: + - 'result.changed == true' + - '(result["diff"][0]["merged"] | length) == 1' + - '(result["diff"][0]["modified"] | length) == 0' + - '(result["diff"][0]["deleted"] | length) == 0' + - '(result["diff"][0]["query"] | length) == 0' + - '(result["diff"][0]["deploy"][0][ "{{ ansible_vpc_fabric }}" ] | length) == 2' + + - assert: + that: + - 'item["RETURN_CODE"] == 200' + loop: '{{ result.response }}' + + +############################################## +## DELETE ## +############################################## + + - name: Delete Links + cisco.dcnm.dcnm_links: &links_delete + state: deleted # choose from [merged, replaced, deleted, query] + src_fabric: "{{ ansible_vpc_fabric }}" + config: + - dst_fabric: "{{ ansible_vpc_fabric }}" # Destination fabric + src_interface: "{{ intf_1_4 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_4 }}" # Interface on the Destination fabric + src_device: "{{ ansible_vpc_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_vpc_switch2 }}" # Device on the Destination fabric + register: result + + - assert: + that: + - 'result.changed == true' + - '(result["diff"][0]["merged"] | length) == 0' + - '(result["diff"][0]["modified"] | length) == 0' + - '(result["diff"][0]["deleted"] | length) == 1' + - '(result["diff"][0]["query"] | length) == 0' + - '(result["diff"][0]["deploy"][0][ "{{ ansible_vpc_fabric }}" ] | length) == 2' + + - assert: + that: + - 'item["RETURN_CODE"] == 200' + loop: '{{ result.response }}' + +############################################## +## IDEMPOTENCE ## +############################################## + + - name: Delete Links - Idempotence + cisco.dcnm.dcnm_links: *links_delete + register: result + + - assert: + that: + - 'result.changed == false' + - '(result["diff"][0]["merged"] | length) == 0' + - '(result["diff"][0]["modified"] | length) == 0' + - '(result["diff"][0]["deleted"] | length) == 0' + - '(result["diff"][0]["query"] | length) == 0' + - '(result["diff"][0]["deploy"] | length) == 0' + + - assert: + that: + - 'item["RETURN_CODE"] == 200' + loop: '{{ result.response }}' + +############################################## +## CLEANUP ## +############################################## + + always: + + - name: Delete Links - Idempotence + cisco.dcnm.dcnm_links: *links_delete + register: result + + - assert: + that: + - 'item["RETURN_CODE"] == 200' + loop: '{{ result.response }}' diff --git a/tests/integration/targets/dcnm_links/tests/dcnm/dcnm_links_intra_vpc_merge.yaml b/tests/integration/targets/dcnm_links/tests/dcnm/dcnm_links_intra_vpc_merge.yaml new file mode 100644 index 000000000..58609ace0 --- /dev/null +++ b/tests/integration/targets/dcnm_links/tests/dcnm/dcnm_links_intra_vpc_merge.yaml @@ -0,0 +1,221 @@ +############################################## +## SETUP ## +############################################## + +- name: Remove local log file + local_action: command rm -f dcnm_links.log + +- block: + +############################################## +## DELETE ## +############################################## + + - name: Initial setup - Delete Links + cisco.dcnm.dcnm_links: &links_delete + state: deleted # choose from [merged, replaced, deleted, query] + src_fabric: "{{ ansible_vpc_fabric }}" + config: + - dst_fabric: "{{ ansible_vpc_fabric }}" # Destination fabric + src_interface: "{{ intf_1_1 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_1 }}" # Interface on the Destination fabric + src_device: "{{ ansible_vpc_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_vpc_switch2 }}" # Device on the Destination fabric + + - dst_fabric: "{{ ansible_vpc_fabric }}" # Destination fabric + src_interface: "{{ intf_1_2 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_2 }}" # Interface on the Destination fabric + src_device: "{{ ansible_vpc_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_vpc_switch2 }}" # Device on the Destination fabric + + - dst_fabric: "{{ ansible_vpc_fabric }}" # Destination fabric + src_interface: "{{ intf_1_3 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_3 }}" # Interface on the Destination fabric + src_device: "{{ ansible_vpc_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_vpc_switch2 }}" # Device on the Destination fabric + + - dst_fabric: "{{ ansible_vpc_fabric }}" # Destination fabric + src_interface: "{{ intf_1_4 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_4 }}" # Interface on the Destination fabric + src_device: "{{ ansible_vpc_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_vpc_switch2 }}" # Device on the Destination fabric + + - dst_fabric: "{{ ansible_vpc_fabric }}" # Destination fabric + src_interface: "{{ intf_1_5 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_5 }}" # Interface on the Destination fabric + src_device: "{{ ansible_vpc_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_vpc_switch2 }}" # Device on the Destination fabric + + register: result + + - assert: + that: + - 'item["RETURN_CODE"] == 200' + loop: '{{ result.response }}' + +############################################## +## MERGE ## +############################################## + + - name: Create Links without including optional parameters + cisco.dcnm.dcnm_links: &links_merge_no_opt + state: merged # choose from [merged, replaced, deleted, query] + src_fabric: "{{ ansible_vpc_fabric }}" + config: + - dst_fabric: "{{ ansible_vpc_fabric }}" # Destination fabric + src_interface: "{{ intf_1_4 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_4 }}" # Interface on the Destination fabric + src_device: "{{ ansible_vpc_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_vpc_switch2 }}" # Device on the Destination fabric + template: int_intra_vpc_peer_keep_alive_link # template to be applied, choose from + # [ int_intra_fabric_ipv6_link_local, int_intra_fabric_num_link, + # int_intra_fabric_unnum_link, int_intra_vpc_peer_keep_alive_link, + # int_pre_provision_intra_fabric_link, ios_xe_int_intra_fabric_num_link ] + + profile: + peer1_ipv4_addr: 192.168.1.1 # IPV4 address of the Source interface + peer2_ipv4_addr: 192.168.1.2 # IPV4 address of the Destination interface + admin_state: true # choose from [true, false] + mtu: 9216 # + + register: result + + - assert: + that: + - 'result.changed == true' + - '(result["diff"][0]["merged"] | length) == 1' + - '(result["diff"][0]["modified"] | length) == 0' + - '(result["diff"][0]["deleted"] | length) == 0' + - '(result["diff"][0]["query"] | length) == 0' + - '(result["diff"][0]["deploy"][0][ "{{ ansible_vpc_fabric }}" ] | length) == 2' + + - assert: + that: + - 'item["RETURN_CODE"] == 200' + loop: '{{ result.response }}' + + +############################################## +## IDEMPOTENCE ## +############################################## + + - name: Create Links - Idempotence + cisco.dcnm.dcnm_links: *links_merge_no_opt + register: result + + - assert: + that: + - 'result.changed == true' + - '(result["diff"][0]["merged"] | length) == 0' + - '(result["diff"][0]["modified"] | length) == 0' + - '(result["diff"][0]["deleted"] | length) == 0' + - '(result["diff"][0]["query"] | length) == 0' + - '(result["diff"][0]["deploy"][0][ "{{ ansible_vpc_fabric }}" ] | length) == 2' + + - assert: + that: + - 'item["RETURN_CODE"] == 200' + loop: '{{ result.response }}' + +############################################## +## DELETE ## +############################################## + + - name: Delete Links + cisco.dcnm.dcnm_links: *links_delete + register: result + + - assert: + that: + - 'item["RETURN_CODE"] == 200' + loop: '{{ result.response }}' + +############################################## +## MERGE ## +############################################## + + - name: Create Links including all optional parameters + cisco.dcnm.dcnm_links: &links_merge_with_opt + state: merged # choose from [merged, replaced, deleted, query] + src_fabric: "{{ ansible_vpc_fabric }}" + config: + - dst_fabric: "{{ ansible_vpc_fabric }}" # Destination fabric + src_interface: "{{ intf_1_4 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_4 }}" # Interface on the Destination fabric + src_device: "{{ ansible_vpc_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_vpc_switch2 }}" # Device on the Destination fabric + template: int_intra_vpc_peer_keep_alive_link # template to be applied, choose from + # [ int_intra_fabric_ipv6_link_local, int_intra_fabric_num_link, + # int_intra_fabric_unnum_link, int_intra_vpc_peer_keep_alive_link, + # int_pre_provision_intra_fabric_link, ios_xe_int_intra_fabric_num_link ] + + profile: + peer1_ipv4_addr: 192.170.1.1 # IPV4 address of the Source interface + peer2_ipv4_addr: 192.170.1.2 # IPV4 address of the Destination interface + peer1_ipv6_addr: 2080:2a::01 # optional, default is "" + peer2_ipv6_addr: 2080:2a::02 # optional, default is "" + admin_state: true # choose from [true, false] + mtu: 9216 # + peer1_description: "Description of source" # optional, default is "" + peer2_description: "Description of dest" # optional, default is "" + enable_macsec: false # optional, choose from [true, false] + peer1_cmds: # Freeform config for source device + - no shutdown # optional, default is "" + peer2_cmds: # Freeform config for destination device + - no shutdown # optional, default is "" + intf_vrf: "test_vrf" # optional, default is "" + + register: result + + - assert: + that: + - 'result.changed == true' + - '(result["diff"][0]["merged"] | length) == 1' + - '(result["diff"][0]["modified"] | length) == 0' + - '(result["diff"][0]["deleted"] | length) == 0' + - '(result["diff"][0]["query"] | length) == 0' + - '(result["diff"][0]["deploy"][0][ "{{ ansible_vpc_fabric }}" ] | length) == 2' + + - assert: + that: + - 'item["RETURN_CODE"] == 200' + loop: '{{ result.response }}' + +############################################## +## IDEMPOTENCE ## +############################################## + + - name: Create Links - Idempotence + cisco.dcnm.dcnm_links: *links_merge_with_opt + register: result + + - assert: + that: + - 'result.changed == true' + - '(result["diff"][0]["merged"] | length) == 0' + - '(result["diff"][0]["modified"] | length) == 0' + - '(result["diff"][0]["deleted"] | length) == 0' + - '(result["diff"][0]["query"] | length) == 0' + - '(result["diff"][0]["deploy"][0][ "{{ ansible_vpc_fabric }}" ] | length) == 2' + + - assert: + that: + - 'item["RETURN_CODE"] == 200' + loop: '{{ result.response }}' + +############################################### +### CLEANUP ## +############################################### + + always: + + - name: Cleanup - Delete Links + cisco.dcnm.dcnm_links: *links_delete + register: result + when: IT_CONTEXT is not defined + + - assert: + that: + - 'item["RETURN_CODE"] == 200' + loop: '{{ result.response }}' + when: IT_CONTEXT is not defined diff --git a/tests/integration/targets/dcnm_links/tests/dcnm/dcnm_links_intra_vpc_modify.yaml b/tests/integration/targets/dcnm_links/tests/dcnm/dcnm_links_intra_vpc_modify.yaml new file mode 100644 index 000000000..81f625c60 --- /dev/null +++ b/tests/integration/targets/dcnm_links/tests/dcnm/dcnm_links_intra_vpc_modify.yaml @@ -0,0 +1,173 @@ +############################################## +## SETUP ## +############################################## + +- name: Remove local log file + local_action: command rm -f dcnm_links.log + +- block: + +############################################## +## DELETE ## +############################################## + + - name: Initial setup - Delete Links + cisco.dcnm.dcnm_links: &links_delete + state: deleted # choose from [merged, replaced, deleted, query] + src_fabric: "{{ ansible_vpc_fabric }}" + config: + - dst_fabric: "{{ ansible_vpc_fabric }}" # Destination fabric + src_interface: "{{ intf_1_1 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_1 }}" # Interface on the Destination fabric + src_device: "{{ ansible_vpc_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_vpc_switch2 }}" # Device on the Destination fabric + + - dst_fabric: "{{ ansible_vpc_fabric }}" # Destination fabric + src_interface: "{{ intf_1_2 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_2 }}" # Interface on the Destination fabric + src_device: "{{ ansible_vpc_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_vpc_switch2 }}" # Device on the Destination fabric + + - dst_fabric: "{{ ansible_vpc_fabric }}" # Destination fabric + src_interface: "{{ intf_1_3 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_3 }}" # Interface on the Destination fabric + src_device: "{{ ansible_vpc_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_vpc_switch2 }}" # Device on the Destination fabric + + - dst_fabric: "{{ ansible_vpc_fabric }}" # Destination fabric + src_interface: "{{ intf_1_4 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_4 }}" # Interface on the Destination fabric + src_device: "{{ ansible_vpc_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_vpc_switch2 }}" # Device on the Destination fabric + + - dst_fabric: "{{ ansible_vpc_fabric }}" # Destination fabric + src_interface: "{{ intf_1_5 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_5 }}" # Interface on the Destination fabric + src_device: "{{ ansible_vpc_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_vpc_switch2 }}" # Device on the Destination fabric + + register: result + + - assert: + that: + - 'item["RETURN_CODE"] == 200' + loop: '{{ result.response }}' + +############################################## +## MERGE ## +############################################## + + - name: Create Links including all optional parameters + cisco.dcnm.dcnm_links: + state: merged # choose from [merged, replaced, deleted, query] + src_fabric: "{{ ansible_vpc_fabric }}" + config: + - dst_fabric: "{{ ansible_vpc_fabric }}" # Destination fabric + src_interface: "{{ intf_1_4 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_4 }}" # Interface on the Destination fabric + src_device: "{{ ansible_vpc_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_vpc_switch2 }}" # Device on the Destination fabric + template: int_intra_vpc_peer_keep_alive_link # template to be applied, choose from + # [ int_intra_fabric_ipv6_link_local, int_intra_fabric_num_link, + # int_intra_fabric_unnum_link, int_intra_vpc_peer_keep_alive_link, + # int_pre_provision_intra_fabric_link, ios_xe_int_intra_fabric_num_link ] + + profile: + peer1_ipv4_addr: 192.170.1.1 # IPV4 address of the Source interface + peer2_ipv4_addr: 192.170.1.2 # IPV4 address of the Destination interface + peer1_ipv6_addr: 2080:2a::01 # optional, default is "" + peer2_ipv6_addr: 2080:2a::02 # optional, default is "" + admin_state: true # choose from [true, false] + mtu: 9216 # + peer1_description: "Description of source" # optional, default is "" + peer2_description: "Description of dest" # optional, default is "" + enable_macsec: false # optional, choose from [true, false] + peer1_cmds: # Freeform config for source device + - no shutdown # optional, default is "" + peer2_cmds: # Freeform config for destination device + - no shutdown # optional, default is "" + intf_vrf: "test_vrf" # optional, default is "" + + register: result + + - assert: + that: + - 'result.changed == true' + - '(result["diff"][0]["merged"] | length) == 1' + - '(result["diff"][0]["modified"] | length) == 0' + - '(result["diff"][0]["deleted"] | length) == 0' + - '(result["diff"][0]["query"] | length) == 0' + - '(result["diff"][0]["deploy"][0][ "{{ ansible_vpc_fabric }}" ] | length) == 2' + + - assert: + that: + - 'item["RETURN_CODE"] == 200' + loop: '{{ result.response }}' + +############################################## +## MODIFY ## +############################################## + + - name: Merge modified info into existing links + cisco.dcnm.dcnm_links: &links_merge_with_opt + state: merged # choose from [merged, replaced, deleted, query] + src_fabric: "{{ ansible_vpc_fabric }}" + config: + - dst_fabric: "{{ ansible_vpc_fabric }}" # Destination fabric + src_interface: "{{ intf_1_4 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_4 }}" # Interface on the Destination fabric + src_device: "{{ ansible_vpc_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_vpc_switch2 }}" # Device on the Destination fabric + template: int_intra_vpc_peer_keep_alive_link # template to be applied, choose from + # [ int_intra_fabric_ipv6_link_local, int_intra_fabric_num_link, + # int_intra_fabric_unnum_link, int_intra_vpc_peer_keep_alive_link, + # int_pre_provision_intra_fabric_link, ios_xe_int_intra_fabric_num_link ] + + profile: + peer1_ipv4_addr: 193.170.1.1 # IPV4 address of the Source interface + peer2_ipv4_addr: 193.170.1.2 # IPV4 address of the Destination interface + peer1_ipv6_addr: 2080:29::01 # optional, default is "" + peer2_ipv6_addr: 2080:29::02 # optional, default is "" + admin_state: true # choose from [true, false] + mtu: 9216 # + peer1_description: "Description of source - MOD" # optional, default is "" + peer2_description: "Description of dest - MOD" # optional, default is "" + enable_macsec: false # optional, choose from [true, false] + peer1_cmds: # Freeform config for source device + - cdp enable # optional, default is "" + peer2_cmds: # Freeform config for destination device + - cdp enable # optional, default is "" + intf_vrf: "test2_vrf" # optional, default is "" + + register: result + + - assert: + that: + - 'result.changed == true' + - '(result["diff"][0]["merged"] | length) == 0' + - '(result["diff"][0]["modified"] | length) == 1' + - '(result["diff"][0]["deleted"] | length) == 0' + - '(result["diff"][0]["query"] | length) == 0' + - '(result["diff"][0]["deploy"][0][ "{{ ansible_vpc_fabric }}" ] | length) == 2' + + - assert: + that: + - 'item["RETURN_CODE"] == 200' + loop: '{{ result.response }}' + +############################################### +### CLEANUP ## +############################################### + + always: + + - name: Cleanup - Delete Links + cisco.dcnm.dcnm_links: *links_delete + register: result + when: IT_CONTEXT is not defined + + - assert: + that: + - 'item["RETURN_CODE"] == 200' + loop: '{{ result.response }}' + when: IT_CONTEXT is not defined diff --git a/tests/integration/targets/dcnm_links/tests/dcnm/dcnm_links_intra_vpc_query.yaml b/tests/integration/targets/dcnm_links/tests/dcnm/dcnm_links_intra_vpc_query.yaml new file mode 100644 index 000000000..d4b60942a --- /dev/null +++ b/tests/integration/targets/dcnm_links/tests/dcnm/dcnm_links_intra_vpc_query.yaml @@ -0,0 +1,218 @@ +############################################## +## SETUP ## +############################################## + +- name: Remove local log file + local_action: command rm -f dcnm_links.log + +- block: + +############################################## +## DELETE ## +############################################## + + - name: Initial setup - Delete Links + cisco.dcnm.dcnm_links: &links_delete + state: deleted # choose from [merged, replaced, deleted, query] + src_fabric: "{{ ansible_vpc_fabric }}" + config: + - dst_fabric: "{{ ansible_vpc_fabric }}" # Destination fabric + src_interface: "{{ intf_1_1 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_1 }}" # Interface on the Destination fabric + src_device: "{{ ansible_vpc_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_vpc_switch2 }}" # Device on the Destination fabric + + - dst_fabric: "{{ ansible_vpc_fabric }}" # Destination fabric + src_interface: "{{ intf_1_2 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_2 }}" # Interface on the Destination fabric + src_device: "{{ ansible_vpc_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_vpc_switch2 }}" # Device on the Destination fabric + + - dst_fabric: "{{ ansible_vpc_fabric }}" # Destination fabric + src_interface: "{{ intf_1_3 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_3 }}" # Interface on the Destination fabric + src_device: "{{ ansible_vpc_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_vpc_switch2 }}" # Device on the Destination fabric + + - dst_fabric: "{{ ansible_vpc_fabric }}" # Destination fabric + src_interface: "{{ intf_1_4 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_4 }}" # Interface on the Destination fabric + src_device: "{{ ansible_vpc_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_vpc_switch2 }}" # Device on the Destination fabric + + - dst_fabric: "{{ ansible_vpc_fabric }}" # Destination fabric + src_interface: "{{ intf_1_5 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_5 }}" # Interface on the Destination fabric + src_device: "{{ ansible_vpc_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_vpc_switch2 }}" # Device on the Destination fabric + + register: result + + - assert: + that: + - 'item["RETURN_CODE"] == 200' + loop: '{{ result.response }}' + +############################################### +### MERGE ## +############################################### + + - name: Create Links without including optional parameters + cisco.dcnm.dcnm_links: &links_merge_no_opt + state: merged # choose from [merged, replaced, deleted, query] + src_fabric: "{{ ansible_vpc_fabric }}" + config: + - dst_fabric: "{{ ansible_vpc_fabric }}" # Destination fabric + src_interface: "{{ intf_1_4 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_4 }}" # Interface on the Destination fabric + src_device: "{{ ansible_vpc_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_vpc_switch2 }}" # Device on the Destination fabric + template: int_intra_vpc_peer_keep_alive_link # template to be applied, choose from + # [ int_intra_fabric_ipv6_link_local, int_intra_fabric_num_link, + # int_intra_fabric_unnum_link, int_intra_vpc_peer_keep_alive_link, + # int_pre_provision_intra_fabric_link, ios_xe_int_intra_fabric_num_link ] + + profile: + peer1_ipv4_addr: 192.168.1.1 # IPV4 address of the Source interface + peer2_ipv4_addr: 192.168.1.2 # IPV4 address of the Destination interface + admin_state: true # choose from [true, false] + mtu: 9216 # + + register: result + + - assert: + that: + - 'result.changed == true' + - '(result["diff"][0]["merged"] | length) == 1' + - '(result["diff"][0]["modified"] | length) == 0' + - '(result["diff"][0]["deleted"] | length) == 0' + - '(result["diff"][0]["query"] | length) == 0' + - '(result["diff"][0]["deploy"][0][ "{{ ansible_vpc_fabric }}" ] | length) == 2' + + - assert: + that: + - 'item["RETURN_CODE"] == 200' + loop: '{{ result.response }}' + + +############################################### +### QUERY ## +############################################### + + - name: Query Links - with Src Fabric + cisco.dcnm.dcnm_links: + state: query # choose from [merged, replaced, deleted, query] + src_fabric: "{{ ansible_vpc_fabric }}" + register: result + + - assert: + that: + '(result["response"] | length) >= 1' + + - name: Query Links - with Src & Dst Fabric + cisco.dcnm.dcnm_links: + state: query # choose from [merged, replaced, deleted, query] + src_fabric: "{{ ansible_vpc_fabric }}" + config: + - dst_fabric: "{{ ansible_vpc_fabric }}" # optional, Destination fabric + register: result + + - assert: + that: + '(result["response"] | length) >= 1' + + - name: Query Links - with Src & Dst Fabric, Src Intf + cisco.dcnm.dcnm_links: + state: query # choose from [merged, replaced, deleted, query] + src_fabric: "{{ ansible_vpc_fabric }}" + config: + - dst_fabric: "{{ ansible_vpc_fabric }}" # optional, Destination fabric + src_interface: "{{ intf_1_4 }}" # optional, Interface on the Source fabric + register: result + + - assert: + that: + '(result["response"] | length) >= 1' + + - name: Query Links - with Src & Dst Fabric, Src & Dst Intf + cisco.dcnm.dcnm_links: + state: query # choose from [merged, replaced, deleted, query] + src_fabric: "{{ ansible_vpc_fabric }}" + config: + - dst_fabric: "{{ ansible_vpc_fabric }}" # optional, Destination fabric + src_interface: "{{ intf_1_4 }}" # optional, Interface on the Source fabric + dst_interface: "{{ intf_1_4 }}" # optional, Interface on the Destination fabric + register: result + + - assert: + that: + '(result["response"] | length) >= 1' + + - name: Query Links - with Src & Dst Fabric, Src & Dst Intf, Src Device + cisco.dcnm.dcnm_links: + state: query # choose from [merged, replaced, deleted, query] + src_fabric: "{{ ansible_vpc_fabric }}" + config: + - dst_fabric: "{{ ansible_vpc_fabric }}" # optional, Destination fabric + src_interface: "{{ intf_1_4 }}" # optional, Interface on the Source fabric + dst_interface: "{{ intf_1_4 }}" # optional, Interface on the Destination fabric + src_device: "{{ ansible_vpc_switch1 }}" # optional, Device on the Source fabric + register: result + + - assert: + that: + '(result["response"] | length) >= 1' + + - name: Query Links - with Src & Dst Fabric, Src & Dst Intf, Src & Dst Device + cisco.dcnm.dcnm_links: + state: query # choose from [merged, replaced, deleted, query] + src_fabric: "{{ ansible_vpc_fabric }}" + config: + - dst_fabric: "{{ ansible_vpc_fabric }}" # optional, Destination fabric + src_interface: "{{ intf_1_4 }}" # optional, Interface on the Source fabric + dst_interface: "{{ intf_1_4 }}" # optional, Interface on the Destination fabric + src_device: "{{ ansible_vpc_switch1 }}" # optional, Device on the Source fabric + dst_device: "{{ ansible_vpc_switch2 }}" # optional, Device on the Destination fabric + register: result + + - assert: + that: + '(result["response"] | length) >= 1' + + - name: Query Links - with Src & Dst Fabric, Src & Dst Intf, Src & Dst Device, Template + cisco.dcnm.dcnm_links: + state: query # choose from [merged, replaced, deleted, query] + src_fabric: "{{ ansible_vpc_fabric }}" + config: + - dst_fabric: "{{ ansible_vpc_fabric }}" # optional, Destination fabric + src_interface: "{{ intf_1_4 }}" # optional, Interface on the Source fabric + dst_interface: "{{ intf_1_4 }}" # optional, Interface on the Destination fabric + src_device: "{{ ansible_vpc_switch1 }}" # optional, Device on the Source fabric + dst_device: "{{ ansible_vpc_switch2 }}" # optional, Device on the Destination fabric + template: int_intra_vpc_peer_keep_alive_link # optional, template to be applied, choose from + # [ int_intra_fabric_ipv6_link_local, int_intra_fabric_num_link, + # int_intra_fabric_unnum_link, int_intra_vpc_peer_keep_alive_link, + # int_pre_provision_intra_fabric_link, ios_xe_int_intra_fabric_num_link ] + + register: result + + - assert: + that: + '(result["response"] | length) >= 1' + +############################################### +### CLEANUP ## +############################################### + + always: + + - name: Cleanup - Delete Links + cisco.dcnm.dcnm_links: *links_delete + register: result + when: IT_CONTEXT is not defined + + - assert: + that: + - 'item["RETURN_CODE"] == 200' + loop: '{{ result.response }}' + when: IT_CONTEXT is not defined diff --git a/tests/integration/targets/dcnm_links/tests/dcnm/dcnm_links_intra_vpc_replace.yaml b/tests/integration/targets/dcnm_links/tests/dcnm/dcnm_links_intra_vpc_replace.yaml new file mode 100644 index 000000000..bcef998d7 --- /dev/null +++ b/tests/integration/targets/dcnm_links/tests/dcnm/dcnm_links_intra_vpc_replace.yaml @@ -0,0 +1,195 @@ +############################################## +## SETUP ## +############################################## + +- name: Remove local log file + local_action: command rm -f dcnm_links.log + +- block: + +############################################## +## DELETE ## +############################################## + + - name: Initial setup - Delete Links + cisco.dcnm.dcnm_links: &links_delete + state: deleted # choose from [merged, replaced, deleted, query] + src_fabric: "{{ ansible_vpc_fabric }}" + config: + - dst_fabric: "{{ ansible_vpc_fabric }}" # Destination fabric + src_interface: "{{ intf_1_1 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_1 }}" # Interface on the Destination fabric + src_device: "{{ ansible_vpc_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_vpc_switch2 }}" # Device on the Destination fabric + + - dst_fabric: "{{ ansible_vpc_fabric }}" # Destination fabric + src_interface: "{{ intf_1_2 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_2 }}" # Interface on the Destination fabric + src_device: "{{ ansible_vpc_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_vpc_switch2 }}" # Device on the Destination fabric + + - dst_fabric: "{{ ansible_vpc_fabric }}" # Destination fabric + src_interface: "{{ intf_1_3 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_3 }}" # Interface on the Destination fabric + src_device: "{{ ansible_vpc_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_vpc_switch2 }}" # Device on the Destination fabric + + - dst_fabric: "{{ ansible_vpc_fabric }}" # Destination fabric + src_interface: "{{ intf_1_4 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_4 }}" # Interface on the Destination fabric + src_device: "{{ ansible_vpc_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_vpc_switch2 }}" # Device on the Destination fabric + + - dst_fabric: "{{ ansible_vpc_fabric }}" # Destination fabric + src_interface: "{{ intf_1_5 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_5 }}" # Interface on the Destination fabric + src_device: "{{ ansible_vpc_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_vpc_switch2 }}" # Device on the Destination fabric + + register: result + + - assert: + that: + - 'item["RETURN_CODE"] == 200' + loop: '{{ result.response }}' + +############################################## +## MERGE ## +############################################## + + - name: Create Links including all optional parameters + cisco.dcnm.dcnm_links: + state: merged # choose from [merged, replaced, deleted, query] + src_fabric: "{{ ansible_vpc_fabric }}" + config: + - dst_fabric: "{{ ansible_vpc_fabric }}" # Destination fabric + src_interface: "{{ intf_1_4 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_4 }}" # Interface on the Destination fabric + src_device: "{{ ansible_vpc_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_vpc_switch2 }}" # Device on the Destination fabric + template: int_intra_vpc_peer_keep_alive_link # template to be applied, choose from + # [ int_intra_fabric_ipv6_link_local, int_intra_fabric_num_link, + # int_intra_fabric_unnum_link, int_intra_vpc_peer_keep_alive_link, + # int_pre_provision_intra_fabric_link, ios_xe_int_intra_fabric_num_link ] + + profile: + peer1_ipv4_addr: 192.170.1.1 # IPV4 address of the Source interface + peer2_ipv4_addr: 192.170.1.2 # IPV4 address of the Destination interface + peer1_ipv6_addr: 2080:2a::01 # optional, default is "" + peer2_ipv6_addr: 2080:2a::02 # optional, default is "" + admin_state: true # choose from [true, false] + mtu: 9216 # + peer1_description: "Description of source" # optional, default is "" + peer2_description: "Description of dest" # optional, default is "" + enable_macsec: false # optional, choose from [true, false] + peer1_cmds: # Freeform config for source device + - no shutdown # optional, default is "" + peer2_cmds: # Freeform config for destination device + - no shutdown # optional, default is "" + intf_vrf: "test_vrf" # optional, default is "" + + register: result + + - assert: + that: + - 'result.changed == true' + - '(result["diff"][0]["merged"] | length) == 1' + - '(result["diff"][0]["modified"] | length) == 0' + - '(result["diff"][0]["deleted"] | length) == 0' + - '(result["diff"][0]["query"] | length) == 0' + - '(result["diff"][0]["deploy"][0][ "{{ ansible_vpc_fabric }}" ] | length) == 2' + + - assert: + that: + - 'item["RETURN_CODE"] == 200' + loop: '{{ result.response }}' + +############################################## +## MODIFY ## +############################################## + + - name: Replace Link + cisco.dcnm.dcnm_links: &links_replace + state: replaced # choose from [merged, replaced, deleted, query] + src_fabric: "{{ ansible_vpc_fabric }}" + config: + - dst_fabric: "{{ ansible_vpc_fabric }}" # Destination fabric + src_interface: "{{ intf_1_4 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_4 }}" # Interface on the Destination fabric + src_device: "{{ ansible_vpc_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_vpc_switch2 }}" # Device on the Destination fabric + template: int_intra_vpc_peer_keep_alive_link # template to be applied, choose from + # [ int_intra_fabric_ipv6_link_local, int_intra_fabric_num_link, + # int_intra_fabric_unnum_link, int_intra_vpc_peer_keep_alive_link, + # int_pre_provision_intra_fabric_link, ios_xe_int_intra_fabric_num_link ] + + profile: + peer1_ipv4_addr: 193.170.1.1 # IPV4 address of the Source interface + peer2_ipv4_addr: 193.170.1.2 # IPV4 address of the Destination interface + peer1_ipv6_addr: 2080:29::01 # optional, default is "" + peer2_ipv6_addr: 2080:29::02 # optional, default is "" + admin_state: true # choose from [true, false] + mtu: 9216 # + peer1_description: "Description of source - REP" # optional, default is "" + peer2_description: "Description of dest - REP" # optional, default is "" + enable_macsec: false # optional, choose from [true, false] + peer1_cmds: # Freeform config for source device + - cdp enable # optional, default is "" + peer2_cmds: # Freeform config for destination device + - cdp enable # optional, default is "" + intf_vrf: "test_vrf" # optional, default is "" + + register: result + + - assert: + that: + - 'result.changed == true' + - '(result["diff"][0]["merged"] | length) == 0' + - '(result["diff"][0]["modified"] | length) == 1' + - '(result["diff"][0]["deleted"] | length) == 0' + - '(result["diff"][0]["query"] | length) == 0' + - '(result["diff"][0]["deploy"][0][ "{{ ansible_vpc_fabric }}" ] | length) == 2' + + - assert: + that: + - 'item["RETURN_CODE"] == 200' + loop: '{{ result.response }}' + +############################################## +## IDEMPOTENCE ## +############################################## + + - name: Repalce Links - Idempotence + cisco.dcnm.dcnm_links: *links_replace + register: result + + - assert: + that: + - 'result.changed == true' + - '(result["diff"][0]["merged"] | length) == 0' + - '(result["diff"][0]["modified"] | length) == 0' + - '(result["diff"][0]["deleted"] | length) == 0' + - '(result["diff"][0]["query"] | length) == 0' + - '(result["diff"][0]["deploy"][0][ "{{ ansible_vpc_fabric }}" ] | length) == 2' + + - assert: + that: + - 'item["RETURN_CODE"] == 200' + loop: '{{ result.response }}' + +############################################### +### CLEANUP ## +############################################### + + always: + + - name: Cleanup - Delete Links + cisco.dcnm.dcnm_links: *links_delete + register: result + when: IT_CONTEXT is not defined + + - assert: + that: + - 'item["RETURN_CODE"] == 200' + loop: '{{ result.response }}' + when: IT_CONTEXT is not defined diff --git a/tests/integration/targets/dcnm_links/tests/dcnm/xe-test-cases/dcnm_links_intra_xe_delete.yaml b/tests/integration/targets/dcnm_links/tests/dcnm/xe-test-cases/dcnm_links_intra_xe_delete.yaml new file mode 100644 index 000000000..a260c202c --- /dev/null +++ b/tests/integration/targets/dcnm_links/tests/dcnm/xe-test-cases/dcnm_links_intra_xe_delete.yaml @@ -0,0 +1,154 @@ +############################################## +## SETUP ## +############################################## + +- name: Remove local log file + local_action: command rm -f dcnm_links.log + +- block: + +############################################## +## DELETE ## +############################################## + + - name: Initial setup - Delete Links + cisco.dcnm.dcnm_links: &links_delete + state: deleted # choose from [merged, replaced, deleted, query] + src_fabric: "{{ ansible_xe_fabric }}" + config: + - dst_fabric: "{{ ansible_xe_fabric }}" # Destination fabric + src_interface: "{{ intf_1_1 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_1 }}" # Interface on the Destination fabric + src_device: "{{ ansible_xe_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_xe_switch2 }}" # Device on the Destination fabric + + - dst_fabric: "{{ ansible_xe_fabric }}" # Destination fabric + src_interface: "{{ intf_1_2 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_2 }}" # Interface on the Destination fabric + src_device: "{{ ansible_xe_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_xe_switch2 }}" # Device on the Destination fabric + + - dst_fabric: "{{ ansible_xe_fabric }}" # Destination fabric + src_interface: "{{ intf_1_3 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_3 }}" # Interface on the Destination fabric + src_device: "{{ ansible_xe_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_xe_switch2 }}" # Device on the Destination fabric + + - dst_fabric: "{{ ansible_xe_fabric }}" # Destination fabric + src_interface: "{{ intf_1_4 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_4 }}" # Interface on the Destination fabric + src_device: "{{ ansible_xe_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_xe_switch2 }}" # Device on the Destination fabric + + - dst_fabric: "{{ ansible_xe_fabric }}" # Destination fabric + src_interface: "{{ intf_1_5 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_5 }}" # Interface on the Destination fabric + src_device: "{{ ansible_xe_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_xe_switch2 }}" # Device on the Destination fabric + + register: result + + - assert: + that: + - 'item["RETURN_CODE"] == 200' + loop: '{{ result.response }}' + +############################################## +## MERGE ## +############################################## + + - name: Create Links without including optional parameters + cisco.dcnm.dcnm_links: &links_merge_no_opt + state: merged # choose from [merged, replaced, deleted, query] + src_fabric: "{{ ansible_xe_fabric }}" + config: + - dst_fabric: "{{ ansible_xe_fabric }}" # Destination fabric + src_interface: "{{ intf_1_3 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_3 }}" # Interface on the Destination fabric + src_device: "{{ ansible_xe_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_xe_switch2 }}" # Device on the Destination fabric + template: ios_xe_int_intra_fabric_num_link # template to be applied, choose from + # [ int_intra_fabric_ipv6_link_local, int_intra_fabric_num_link, + # int_intra_fabric_unnum_link, int_intra_vpc_peer_keep_alive_link, + # int_pre_provision_intra_fabric_link, ios_xe_int_intra_fabric_num_link ] + + profile: + peer1_ipv4_addr: 192.168.2.1 # IPV4 address of the Source interface + peer2_ipv4_addr: 192.168.2.2 # IPV4 address of the Destination interface + admin_state: true # choose from [true, false] + + register: result + + - assert: + that: + - 'result.changed == true' + - '(result["diff"][0]["merged"] | length) == 1' + - '(result["diff"][0]["modified"] | length) == 0' + - '(result["diff"][0]["deleted"] | length) == 0' + - '(result["diff"][0]["query"] | length) == 0' + - '(result["diff"][0]["deploy"][0][ "{{ ansible_xe_fabric }}" ] | length) == 1' + + - assert: + that: + - 'item["RETURN_CODE"] == 200' + loop: '{{ result.response }}' + + +############################################### +### DELETE ## +############################################### + + - name: Delete Links + cisco.dcnm.dcnm_links: *links_delete + register: result + + - assert: + that: + - 'result.changed == true' + - '(result["diff"][0]["merged"] | length) == 0' + - '(result["diff"][0]["modified"] | length) == 0' + - '(result["diff"][0]["deleted"] | length) == 1' + - '(result["diff"][0]["query"] | length) == 0' + - '(result["diff"][0]["deploy"][0][ "{{ ansible_xe_fabric }}" ] | length) == 1' + + - assert: + that: + - 'item["RETURN_CODE"] == 200' + loop: '{{ result.response }}' + +############################################## +## IDEMPOTENCE ## +############################################## + + - name: Delete Links - Idempotence + cisco.dcnm.dcnm_links: *links_delete + register: result + + - assert: + that: + - 'result.changed == false' + - '(result["diff"][0]["merged"] | length) == 0' + - '(result["diff"][0]["modified"] | length) == 0' + - '(result["diff"][0]["deleted"] | length) == 0' + - '(result["diff"][0]["query"] | length) == 0' + - '(result["diff"][0]["deploy"] | length) == 0' + + - assert: + that: + - 'item["RETURN_CODE"] == 200' + loop: '{{ result.response }}' + +############################################## +## CLEANUP ## +############################################## + + always: + + - name: Delete Links + cisco.dcnm.dcnm_links: *links_delete + register: result + + - assert: + that: + - 'item["RETURN_CODE"] == 200' + loop: '{{ result.response }}' diff --git a/tests/integration/targets/dcnm_links/tests/dcnm/xe-test-cases/dcnm_links_intra_xe_merge.yaml b/tests/integration/targets/dcnm_links/tests/dcnm/xe-test-cases/dcnm_links_intra_xe_merge.yaml new file mode 100644 index 000000000..31fa06bc7 --- /dev/null +++ b/tests/integration/targets/dcnm_links/tests/dcnm/xe-test-cases/dcnm_links_intra_xe_merge.yaml @@ -0,0 +1,220 @@ +############################################## +## SETUP ## +############################################## + +- name: Remove local log file + local_action: command rm -f dcnm_links.log + +- block: + +############################################## +## DELETE ## +############################################## + + - name: Initial setup - Delete Links + cisco.dcnm.dcnm_links: &links_delete + state: deleted # choose from [merged, replaced, deleted, query] + src_fabric: "{{ ansible_num_fabric }}" + config: + - dst_fabric: "{{ ansible_num_fabric }}" # Destination fabric + src_interface: "{{ intf_1_1 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_1 }}" # Interface on the Destination fabric + src_device: "{{ ansible_num_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_num_switch2 }}" # Device on the Destination fabric + + - dst_fabric: "{{ ansible_num_fabric }}" # Destination fabric + src_interface: "{{ intf_1_2 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_2 }}" # Interface on the Destination fabric + src_device: "{{ ansible_num_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_num_switch2 }}" # Device on the Destination fabric + + - dst_fabric: "{{ ansible_num_fabric }}" # Destination fabric + src_interface: "{{ intf_1_3 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_3 }}" # Interface on the Destination fabric + src_device: "{{ ansible_num_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_num_switch2 }}" # Device on the Destination fabric + + - dst_fabric: "{{ ansible_num_fabric }}" # Destination fabric + src_interface: "{{ intf_1_4 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_4 }}" # Interface on the Destination fabric + src_device: "{{ ansible_num_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_num_switch2 }}" # Device on the Destination fabric + + - dst_fabric: "{{ ansible_num_fabric }}" # Destination fabric + src_interface: "{{ intf_1_5 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_5 }}" # Interface on the Destination fabric + src_device: "{{ ansible_num_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_num_switch2 }}" # Device on the Destination fabric + + register: result + + - assert: + that: + - 'item["RETURN_CODE"] == 200' + loop: '{{ result.response }}' + +############################################## +## MERGE ## +############################################## + + - name: Create Links without including optional parameters + cisco.dcnm.dcnm_links: &links_merge_no_opt + state: merged # choose from [merged, replaced, deleted, query] + src_fabric: "{{ ansible_num_fabric }}" + config: + - dst_fabric: "{{ ansible_num_fabric }}" # Destination fabric + src_interface: "{{ intf_1_3 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_3 }}" # Interface on the Destination fabric + src_device: "{{ ansible_num_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_num_switch2 }}" # Device on the Destination fabric + template: ios_xe_int_intra_fabric_num_link # template to be applied, choose from + # [ int_intra_fabric_ipv6_link_local, int_intra_fabric_num_link, + # int_intra_fabric_unnum_link, int_intra_vpc_peer_keep_alive_link, + # int_pre_provision_intra_fabric_link, ios_xe_int_intra_fabric_num_link ] + profile: + peer1_ipv4_addr: 192.168.2.1 # IPV4 address of the Source interface + peer2_ipv4_addr: 192.168.2.2 # IPV4 address of the Destination interface + admin_state: true # choose from [true, false] + + register: result + + - assert: + that: + - 'result.changed == true' + - '(result["diff"][0]["merged"] | length) == 1' + - '(result["diff"][0]["modified"] | length) == 0' + - '(result["diff"][0]["deleted"] | length) == 0' + - '(result["diff"][0]["query"] | length) == 0' + - '(result["diff"][0]["deploy"][0][ "{{ ansible_xe_fabric }}" ] | length) == 2' + + - assert: + that: + - 'item["RETURN_CODE"] == 200' + loop: '{{ result.response }}' + + +############################################## +## IDEMPOTENCE ## +############################################## + + - name: Create Links - Idempotence + cisco.dcnm.dcnm_links: *links_merge_no_opt + register: result + + - assert: + that: + - 'result.changed == true' + - '(result["diff"][0]["merged"] | length) == 0' + - '(result["diff"][0]["modified"] | length) == 0' + - '(result["diff"][0]["deleted"] | length) == 0' + - '(result["diff"][0]["query"] | length) == 0' + - '(result["diff"][0]["deploy"][0][ "{{ ansible_xe_fabric }}" ] | length) == 2' + + - assert: + that: + - 'item["RETURN_CODE"] == 200' + loop: '{{ result.response }}' + +############################################## +## DELETE ## +############################################## + + - name: Delete Links + cisco.dcnm.dcnm_links: *links_delete + register: result + + - assert: + that: + - 'item["RETURN_CODE"] == 200' + loop: '{{ result.response }}' + +############################################## +## MERGE ## +############################################## + + - name: Create Links including all optional parameters + cisco.dcnm.dcnm_links: &links_merge_with_opt + state: merged # choose from [merged, replaced, deleted, query] + src_fabric: "{{ ansible_num_fabric }}" + config: + - dst_fabric: "{{ ansible_num_fabric }}" # Destination fabric + src_interface: "{{ intf_1_3 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_3 }}" # Interface on the Destination fabric + src_device: "{{ ansible_num_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_num_switch2 }}" # Device on the Destination fabric + template: ios_xe_int_intra_fabric_num_link # template to be applied, choose from + # [ int_intra_fabric_ipv6_link_local, int_intra_fabric_num_link, + # int_intra_fabric_unnum_link, int_intra_vpc_peer_keep_alive_link, + # int_pre_provision_intra_fabric_link, ios_xe_int_intra_fabric_num_link ] + + profile: + peer1_ipv4_addr: 192.169.2.1 # IPV4 address of the Source interface + peer2_ipv4_addr: 192.169.2.2 # IPV4 address of the Destination interface + peer1_ipv6_addr: 2080:01::01 # optional, default is "" + peer2_ipv6_addr: 2080:01::02 # optional, default is "" + admin_state: true # choose from [true, false] + mtu: 1500 # optional, default is 1500 + peer1_description: "Description of source" # optional, default is "" + peer2_description: "Description of dest" # optional, default is "" + peer1_bfd_echo_disable: false # optional, choose from [true, false] + peer2_bfd_echo_disable: false # optional, choose from [true, false] + enable_macsec: false # optional, choose from [true, false] + peer1_cmds: # Freeform config for source device + - no shutdown # optional, default is "" + peer2_cmds: # Freeform config for destination device + - no shutdown # optional, default is "" + + register: result + + - assert: + that: + - 'result.changed == true' + - '(result["diff"][0]["merged"] | length) == 1' + - '(result["diff"][0]["modified"] | length) == 0' + - '(result["diff"][0]["deleted"] | length) == 0' + - '(result["diff"][0]["query"] | length) == 0' + - '(result["diff"][0]["deploy"][0][ "{{ ansible_xe_fabric }}" ] | length) == 2' + + - assert: + that: + - 'item["RETURN_CODE"] == 200' + loop: '{{ result.response }}' + +############################################## +## IDEMPOTENCE ## +############################################## + + - name: Create Links - Idempotence + cisco.dcnm.dcnm_links: *links_merge_with_opt + register: result + + - assert: + that: + - 'result.changed == true' + - '(result["diff"][0]["merged"] | length) == 0' + - '(result["diff"][0]["modified"] | length) == 0' + - '(result["diff"][0]["deleted"] | length) == 0' + - '(result["diff"][0]["query"] | length) == 0' + - '(result["diff"][0]["deploy"][0][ "{{ ansible_xe_fabric }}" ] | length) == 2' + + - assert: + that: + - 'item["RETURN_CODE"] == 200' + loop: '{{ result.response }}' + +############################################### +### CLEANUP ## +############################################### + + always: + + - name: Cleanup - Delete Links + cisco.dcnm.dcnm_links: *links_delete + register: result + when: IT_CONTEXT is not defined + + - assert: + that: + - 'item["RETURN_CODE"] == 200' + loop: '{{ result.response }}' + when: IT_CONTEXT is not defined diff --git a/tests/integration/targets/dcnm_links/tests/dcnm/xe-test-cases/dcnm_links_intra_xe_modify.yaml b/tests/integration/targets/dcnm_links/tests/dcnm/xe-test-cases/dcnm_links_intra_xe_modify.yaml new file mode 100644 index 000000000..4279aa390 --- /dev/null +++ b/tests/integration/targets/dcnm_links/tests/dcnm/xe-test-cases/dcnm_links_intra_xe_modify.yaml @@ -0,0 +1,175 @@ +############################################## +## SETUP ## +############################################## + +- name: Remove local log file + local_action: command rm -f dcnm_links.log + +- block: + +############################################## +## DELETE ## +############################################## + + - name: Initial setup - Delete Links + cisco.dcnm.dcnm_links: &links_delete + state: deleted # choose from [merged, replaced, deleted, query] + src_fabric: "{{ ansible_xe_fabric }}" + config: + - dst_fabric: "{{ ansible_xe_fabric }}" # Destination fabric + src_interface: "{{ intf_1_1 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_1 }}" # Interface on the Destination fabric + src_device: "{{ ansible_xe_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_xe_switch2 }}" # Device on the Destination fabric + + - dst_fabric: "{{ ansible_xe_fabric }}" # Destination fabric + src_interface: "{{ intf_1_2 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_2 }}" # Interface on the Destination fabric + src_device: "{{ ansible_xe_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_xe_switch2 }}" # Device on the Destination fabric + # [ int_intra_fabric_ipv6_link_local, int_intra_fabric_num_link, + # int_intra_fabric_unnum_link, int_intra_vpc_peer_keep_alive_link, + # int_pre_provision_intra_fabric_link, ios_xe_int_intra_fabric_num_link ] + - dst_fabric: "{{ ansible_xe_fabric }}" # Destination fabric + src_interface: "{{ intf_1_3 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_3 }}" # Interface on the Destination fabric + src_device: "{{ ansible_xe_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_xe_switch2 }}" # Device on the Destination fabric + + - dst_fabric: "{{ ansible_xe_fabric }}" # Destination fabric + src_interface: "{{ intf_1_4 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_4 }}" # Interface on the Destination fabric + src_device: "{{ ansible_xe_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_xe_switch2 }}" # Device on the Destination fabric + + - dst_fabric: "{{ ansible_xe_fabric }}" # Destination fabric + src_interface: "{{ intf_1_5 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_5 }}" # Interface on the Destination fabric + src_device: "{{ ansible_xe_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_xe_switch2 }}" # Device on the Destination fabric + + register: result + + - assert: + that: + - 'item["RETURN_CODE"] == 200' + loop: '{{ result.response }}' + +############################################## +## MERGE ## +############################################## + + - name: Create Links including all optional parameters + cisco.dcnm.dcnm_links: + state: merged # choose from [merged, replaced, deleted, query] + src_fabric: "{{ ansible_xe_fabric }}" + config: + - dst_fabric: "{{ ansible_xe_fabric }}" # Destination fabric + src_interface: "{{ intf_1_3 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_3 }}" # Interface on the Destination fabric + src_device: "{{ ansible_xe_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_xe_switch2 }}" # Device on the Destination fabric + template: ios_xe_int_intra_fabric_num_link # template to be applied, choose from + # [ int_intra_fabric_ipv6_link_local, int_intra_fabric_num_link, + # int_intra_fabric_unnum_link, int_intra_vpc_peer_keep_alive_link, + # int_pre_provision_intra_fabric_link, ios_xe_int_intra_fabric_num_link ] + + profile: + peer1_ipv4_addr: 192.169.1.1 # IPV4 address of the Source interface + peer2_ipv4_addr: 192.169.1.2 # IPV4 address of the Destination interface + peer1_ipv6_addr: 2080:29::01 # optional, default is "" + peer2_ipv6_addr: 2080:29::02 # optional, default is "" + admin_state: true # choose from [true, false] + mtu: 1500 # optional, default is 1500 + peer1_description: "Description of source - 3" # optional, default is "" + peer2_description: "Description of dest - 3" # optional, default is "" + peer1_bfd_echo_disable: false # optional, choose from [true, false] + peer2_bfd_echo_disable: false # optional, choose from [true, false] + enable_macsec: false # optional, choose from [true, false] + peer1_cmds: # Freeform config for source device + - no shutdown # optional, default is "" + peer2_cmds: # Freeform config for destination device + - no shutdown # optional, default is "" + register: result + + - assert: + that: + - 'result.changed == true' + - '(result["diff"][0]["merged"] | length) == 1' + - '(result["diff"][0]["modified"] | length) == 0' + - '(result["diff"][0]["deleted"] | length) == 0' + - '(result["diff"][0]["query"] | length) == 0' + - '(result["diff"][0]["deploy"][0][ "{{ ansible_xe_fabric }}" ] | length) == 2' + + - assert: + that: + - 'item["RETURN_CODE"] == 200' + loop: '{{ result.response }}' + + +############################################## +## MODIFY ## +############################################## + + - name: Merge modified info into existing links + cisco.dcnm.dcnm_links: + state: merged # choose from [merged, replaced, deleted, query] + src_fabric: "{{ ansible_xe_fabric }}" + config: + - dst_fabric: "{{ ansible_xe_fabric }}" # Destination fabric + src_interface: "{{ intf_1_3 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_3 }}" # Interface on the Destination fabric + src_device: "{{ ansible_xe_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_xe_switch2 }}" # Device on the Destination fabric + template: ios_xe_int_intra_fabric_num_link # template to be applied, choose from + # [ int_intra_fabric_ipv6_link_local, int_intra_fabric_num_link, + # int_intra_fabric_unnum_link, int_intra_vpc_peer_keep_alive_link, + # int_pre_provision_intra_fabric_link, ios_xe_int_intra_fabric_num_link ] + + profile: + peer1_ipv4_addr: 193.169.1.1 # IPV4 address of the Source interface + peer2_ipv4_addr: 193.169.1.2 # IPV4 address of the Destination interface + peer1_ipv6_addr: 2089:29::01 # optional, default is "" + peer2_ipv6_addr: 2089:29::02 # optional, default is "" + admin_state: false # choose from [true, false] + mtu: 2000 # optional, default is 1500 + peer1_description: "MOD - Description of source - 3" # optional, default is "" + peer2_description: "MOD - Description of dest - 3" # optional, default is "" + peer1_bfd_echo_disable: true # optional, choose from [true, false] + peer2_bfd_echo_disable: true # optional, choose from [true, false] + enable_macsec: false # optional, choose from [true, false] + peer1_cmds: # Freeform config for source device + - cdp enable # optional, default is "" + peer2_cmds: # Freeform config for destination device + - cdp enable # optional, default is "" + + register: result + + - assert: + that: + - 'result.changed == true' + - '(result["diff"][0]["merged"] | length) == 0' + - '(result["diff"][0]["modified"] | length) == 1' + - '(result["diff"][0]["deleted"] | length) == 0' + - '(result["diff"][0]["query"] | length) == 0' + - '(result["diff"][0]["deploy"][0][ "{{ ansible_xe_fabric }}" ] | length) == 2' + + - assert: + that: + - 'item["RETURN_CODE"] == 200' + loop: '{{ result.response }}' + +############################################## +## CLEANUP ## +############################################## + + - name: Cleanup - Delete Links + cisco.dcnm.dcnm_links: *links_delete + register: result + when: IT_CONTEXT is not defined + + - assert: + that: + - 'item["RETURN_CODE"] == 200' + loop: '{{ result.response }}' + when: IT_CONTEXT is not defined diff --git a/tests/integration/targets/dcnm_links/tests/dcnm/xe-test-cases/dcnm_links_intra_xe_query.yaml b/tests/integration/targets/dcnm_links/tests/dcnm/xe-test-cases/dcnm_links_intra_xe_query.yaml new file mode 100644 index 000000000..3cae3430f --- /dev/null +++ b/tests/integration/targets/dcnm_links/tests/dcnm/xe-test-cases/dcnm_links_intra_xe_query.yaml @@ -0,0 +1,216 @@ +############################################## +## SETUP ## +############################################## + +- name: Remove local log file + local_action: command rm -f dcnm_links.log + +- block: + +############################################## +## DELETE ## +############################################## + + - name: Initial setup - Delete Links + cisco.dcnm.dcnm_links: &links_delete + state: deleted # choose from [merged, replaced, deleted, query] + src_fabric: "{{ ansible_xe_fabric }}" + config: + - dst_fabric: "{{ ansible_xe_fabric }}" # Destination fabric + src_interface: "{{ intf_1_1 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_1 }}" # Interface on the Destination fabric + src_device: "{{ ansible_xe_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_xe_switch2 }}" # Device on the Destination fabric + + - dst_fabric: "{{ ansible_xe_fabric }}" # Destination fabric + src_interface: "{{ intf_1_2 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_2 }}" # Interface on the Destination fabric + src_device: "{{ ansible_xe_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_xe_switch2 }}" # Device on the Destination fabric + + - dst_fabric: "{{ ansible_xe_fabric }}" # Destination fabric + src_interface: "{{ intf_1_3 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_3 }}" # Interface on the Destination fabric + src_device: "{{ ansible_xe_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_xe_switch2 }}" # Device on the Destination fabric + + - dst_fabric: "{{ ansible_xe_fabric }}" # Destination fabric + src_interface: "{{ intf_1_4 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_4 }}" # Interface on the Destination fabric + src_device: "{{ ansible_xe_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_xe_switch2 }}" # Device on the Destination fabric + + - dst_fabric: "{{ ansible_xe_fabric }}" # Destination fabric + src_interface: "{{ intf_1_5 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_5 }}" # Interface on the Destination fabric + src_device: "{{ ansible_xe_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_xe_switch2 }}" # Device on the Destination fabric + + register: result + + - assert: + that: + - 'item["RETURN_CODE"] == 200' + loop: '{{ result.response }}' + +############################################### +### MERGE ## +############################################### + + - name: Create Links without including optional parameters + cisco.dcnm.dcnm_links: + state: merged # choose from [merged, replaced, deleted, query] + src_fabric: "{{ ansible_xe_fabric }}" + config: + - dst_fabric: "{{ ansible_xe_fabric }}" # Destination fabric + src_interface: "{{ intf_1_3 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_3 }}" # Interface on the Destination fabric + src_device: "{{ ansible_xe_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_xe_switch2 }}" # Device on the Destination fabric + template: ios_xe_int_intra_fabric_num_link # template to be applied, choose from + # [ int_intra_fabric_ipv6_link_local, int_intra_fabric_num_link, + # int_intra_fabric_unnum_link, int_intra_vpc_peer_keep_alive_link, + # int_pre_provision_intra_fabric_link, ios_xe_int_intra_fabric_num_link ] + + profile: + peer1_ipv4_addr: 192.168.2.1 # IPV4 address of the Source interface + peer2_ipv4_addr: 192.168.2.2 # IPV4 address of the Destination interface + admin_state: true # choose from [true, false] + + register: result + + - assert: + that: + - 'result.changed == true' + - '(result["diff"][0]["merged"] | length) == 1' + - '(result["diff"][0]["modified"] | length) == 0' + - '(result["diff"][0]["deleted"] | length) == 0' + - '(result["diff"][0]["query"] | length) == 0' + - '(result["diff"][0]["deploy"][0][ "{{ ansible_xe_fabric }}" ] | length) == 2' + + - assert: + that: + - 'item["RETURN_CODE"] == 200' + loop: '{{ result.response }}' + +############################################### +### QUERY ## +############################################### + + - name: Query Links - with Src Fabric + cisco.dcnm.dcnm_links: + state: query # choose from [merged, replaced, deleted, query] + src_fabric: "{{ ansible_xe_fabric }}" + register: result + + - assert: + that: + '(result["response"] | length) >= 1' + + - name: Query Links - with Src & Dst Fabric + cisco.dcnm.dcnm_links: + state: query # choose from [merged, replaced, deleted, query] + src_fabric: "{{ ansible_xe_fabric }}" + config: + - dst_fabric: "{{ ansible_xe_fabric }}" # optional, Destination fabric + register: result + + - assert: + that: + '(result["response"] | length) >= 1' + + - name: Query Links - with Src & Dst Fabric, Src Intf + cisco.dcnm.dcnm_links: + state: query # choose from [merged, replaced, deleted, query] + src_fabric: "{{ ansible_xe_fabric }}" + config: + - dst_fabric: "{{ ansible_xe_fabric }}" # optional, Destination fabric + src_interface: "{{ intf_1_1 }}" # optional, Interface on the Source fabric + register: result + + - assert: + that: + '(result["response"] | length) >= 1' + + - name: Query Links - with Src & Dst Fabric, Src & Dst Intf + cisco.dcnm.dcnm_links: + state: query # choose from [merged, replaced, deleted, query] + src_fabric: "{{ ansible_xe_fabric }}" + config: + - dst_fabric: "{{ ansible_xe_fabric }}" # optional, Destination fabric + src_interface: "{{ intf_1_1 }}" # optional, Interface on the Source fabric + dst_interface: "{{ intf_1_1 }}" # optional, Interface on the Destination fabric + register: result + + - assert: + that: + '(result["response"] | length) >= 1' + + - name: Query Links - with Src & Dst Fabric, Src & Dst Intf, Src Device + cisco.dcnm.dcnm_links: + state: query # choose from [merged, replaced, deleted, query] + src_fabric: "{{ ansible_xe_fabric }}" + config: + - dst_fabric: "{{ ansible_xe_fabric }}" # optional, Destination fabric + src_interface: "{{ intf_1_1 }}" # optional, Interface on the Source fabric + dst_interface: "{{ intf_1_1 }}" # optional, Interface on the Destination fabric + src_device: "{{ ansible_xe_switch1 }}" # optional, Device on the Source fabric + register: result + + - assert: + that: + '(result["response"] | length) >= 1' + + - name: Query Links - with Src & Dst Fabric, Src & Dst Intf, Src & Dst Device + cisco.dcnm.dcnm_links: + state: query # choose from [merged, replaced, deleted, query] + src_fabric: "{{ ansible_xe_fabric }}" + config: + - dst_fabric: "{{ ansible_xe_fabric }}" # optional, Destination fabric + src_interface: "{{ intf_1_1 }}" # optional, Interface on the Source fabric + dst_interface: "{{ intf_1_1 }}" # optional, Interface on the Destination fabric + src_device: "{{ ansible_xe_switch1 }}" # optional, Device on the Source fabric + dst_device: "{{ ansible_xe_switch2 }}" # optional, Device on the Destination fabric + register: result + + - assert: + that: + '(result["response"] | length) >= 1' + + - name: Query Links - with Src & Dst Fabric, Src & Dst Intf, Src & Dst Device, Template + cisco.dcnm.dcnm_links: + state: query # choose from [merged, replaced, deleted, query] + src_fabric: "{{ ansible_xe_fabric }}" + config: + - dst_fabric: "{{ ansible_xe_fabric }}" # optional, Destination fabric + src_interface: "{{ intf_1_3 }}" # optional, Interface on the Source fabric + dst_interface: "{{ intf_1_3 }}" # optional, Interface on the Destination fabric + src_device: "{{ ansible_xe_switch1 }}" # optional, Device on the Source fabric + dst_device: "{{ ansible_xe_switch2 }}" # optional, Device on the Destination fabric + template: ios_xe_int_intra_fabric_num_link # optional, template to be applied, choose from + # [ int_intra_fabric_ipv6_link_local, int_intra_fabric_num_link, + # int_intra_fabric_unnum_link, int_intra_vpc_peer_keep_alive_link, + # int_pre_provision_intra_fabric_link, ios_xe_int_intra_fabric_num_link ] + + register: result + + - assert: + that: + '(result["response"] | length) >= 1' + +############################################### +### CLEANUP ## +############################################### + + always: + + - name: Cleanup - Delete Links + cisco.dcnm.dcnm_links: *links_delete + register: result + when: IT_CONTEXT is not defined + + - assert: + that: + - 'item["RETURN_CODE"] == 200' + loop: '{{ result.response }}' + when: IT_CONTEXT is not defined diff --git a/tests/integration/targets/dcnm_links/tests/dcnm/xe-test-cases/dcnm_links_intra_xe_replace.yaml b/tests/integration/targets/dcnm_links/tests/dcnm/xe-test-cases/dcnm_links_intra_xe_replace.yaml new file mode 100644 index 000000000..61dfb6fa6 --- /dev/null +++ b/tests/integration/targets/dcnm_links/tests/dcnm/xe-test-cases/dcnm_links_intra_xe_replace.yaml @@ -0,0 +1,197 @@ +############################################## +## SETUP ## +############################################## + +- name: Remove local log file + local_action: command rm -f dcnm_links.log + +- block: + +############################################## +## DELETE ## +############################################## + + - name: Initial setup - Delete Links + cisco.dcnm.dcnm_links: &links_delete + state: deleted # choose from [merged, replaced, deleted, query] + src_fabric: "{{ ansible_xe_fabric }}" + config: + - dst_fabric: "{{ ansible_xe_fabric }}" # Destination fabric + src_interface: "{{ intf_1_1 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_1 }}" # Interface on the Destination fabric + src_device: "{{ ansible_xe_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_xe_switch2 }}" # Device on the Destination fabric + + - dst_fabric: "{{ ansible_xe_fabric }}" # Destination fabric + src_interface: "{{ intf_1_2 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_2 }}" # Interface on the Destination fabric + src_device: "{{ ansible_xe_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_xe_switch2 }}" # Device on the Destination fabric + + - dst_fabric: "{{ ansible_xe_fabric }}" # Destination fabric + src_interface: "{{ intf_1_3 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_3 }}" # Interface on the Destination fabric + src_device: "{{ ansible_xe_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_xe_switch2 }}" # Device on the Destination fabric + + - dst_fabric: "{{ ansible_xe_fabric }}" # Destination fabric + src_interface: "{{ intf_1_4 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_4 }}" # Interface on the Destination fabric + src_device: "{{ ansible_xe_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_xe_switch2 }}" # Device on the Destination fabric + + - dst_fabric: "{{ ansible_xe_fabric }}" # Destination fabric + src_interface: "{{ intf_1_5 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_5 }}" # Interface on the Destination fabric + src_device: "{{ ansible_xe_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_xe_switch2 }}" # Device on the Destination fabric + + register: result + + - assert: + that: + - 'item["RETURN_CODE"] == 200' + loop: '{{ result.response }}' + +############################################## +## MERGE ## +############################################## + + - name: Create Links + cisco.dcnm.dcnm_links: + state: merged # choose from [merged, replaced, deleted, query] + src_fabric: "{{ ansible_xe_fabric }}" + config: + - dst_fabric: "{{ ansible_xe_fabric }}" # Destination fabric + src_interface: "{{ intf_1_3 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_3 }}" # Interface on the Destination fabric + src_device: "{{ ansible_xe_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_xe_switch2 }}" # Device on the Destination fabric + template: ios_xe_int_intra_fabric_num_link # template to be applied, choose from + # [ int_intra_fabric_ipv6_link_local, int_intra_fabric_num_link, + # int_intra_fabric_unnum_link, int_intra_vpc_peer_keep_alive_link, + # int_pre_provision_intra_fabric_link, ios_xe_int_intra_fabric_num_link ] + + profile: + peer1_ipv4_addr: 192.169.2.1 # IPV4 address of the Source interface + peer2_ipv4_addr: 192.169.2.2 # IPV4 address of the Destination interface + peer1_ipv6_addr: 2080:01::01 # optional, default is "" + peer2_ipv6_addr: 2080:01::02 # optional, default is "" + admin_state: true # choose from [true, false] + mtu: 1500 # optional, default is 1500 + peer1_description: "Description of source" # optional, default is "" + peer2_description: "Description of dest" # optional, default is "" + peer1_bfd_echo_disable: false # optional, choose from [true, false] + peer2_bfd_echo_disable: false # optional, choose from [true, false] + enable_macsec: false # optional, choose from [true, false] + peer1_cmds: # Freeform config for source device + - no shutdown # optional, default is "" + peer2_cmds: # Freeform config for destination device + - no shutdown # optional, default is "" + + register: result + + - assert: + that: + - 'result.changed == true' + - '(result["diff"][0]["merged"] | length) == 1' + - '(result["diff"][0]["modified"] | length) == 0' + - '(result["diff"][0]["deleted"] | length) == 0' + - '(result["diff"][0]["query"] | length) == 0' + - '(result["diff"][0]["deploy"][0][ "{{ ansible_xe_fabric }}" ] | length) == 2' + + - assert: + that: + - 'item["RETURN_CODE"] == 200' + loop: '{{ result.response }}' + +############################################## +## REPLACE ## +############################################## + + - name: Replace Links + cisco.dcnm.dcnm_links: &links_replace + state: replaced # choose from [merged, replaced, deleted, query] + src_fabric: "{{ ansible_xe_fabric }}" + config: + - dst_fabric: "{{ ansible_xe_fabric }}" # Destination fabric + src_interface: "{{ intf_1_3 }}" # Interface on the Source fabric + dst_interface: "{{ intf_1_3 }}" # Interface on the Destination fabric + src_device: "{{ ansible_xe_switch1 }}" # Device on the Source fabric + dst_device: "{{ ansible_xe_switch2 }}" # Device on the Destination fabric + template: ios_xe_int_intra_fabric_num_link # template to be applied, choose from + # [ int_intra_fabric_ipv6_link_local, int_intra_fabric_num_link, + # int_intra_fabric_unnum_link, int_intra_vpc_peer_keep_alive_link, + # int_pre_provision_intra_fabric_link, ios_xe_int_intra_fabric_num_link ] + + profile: + peer1_ipv4_addr: 192.170.2.1 # IPV4 address of the Source interface + peer2_ipv4_addr: 192.170.2.2 # IPV4 address of the Destination interface + peer1_ipv6_addr: 2080:91::01 # optional, default is "" + peer2_ipv6_addr: 2080:91::02 # optional, default is "" + admin_state: false # choose from [true, false] + mtu: 2000 # optional, default is 1500 + peer1_description: "New Description - Rep" # optional, default is "" + peer2_description: "New Description - Rep" # optional, default is "" + peer1_bfd_echo_disable: false # optional, choose from [true, false] + peer2_bfd_echo_disable: false # optional, choose from [true, false] + enable_macsec: false # optional, choose from [true, false] + peer1_cmds: # Freeform config for source device + - cdp enable # optional, default is "" + peer2_cmds: # Freeform config for destination device + - cdp enable # optional, default is "" + + register: result + + - assert: + that: + - 'result.changed == true' + - '(result["diff"][0]["merged"] | length) == 0' + - '(result["diff"][0]["modified"] | length) == 1' + - '(result["diff"][0]["deleted"] | length) == 0' + - '(result["diff"][0]["query"] | length) == 0' + - '(result["diff"][0]["deploy"][0][ "{{ ansible_xe_fabric }}" ] | length) == 2' + + - assert: + that: + - 'item["RETURN_CODE"] == 200' + loop: '{{ result.response }}' + +############################################## +## IDEMPOTENCE ## +############################################## + + - name: Repalce Links - Idempotence + cisco.dcnm.dcnm_links: *links_replace + register: result + + - assert: + that: + - 'result.changed == true' + - '(result["diff"][0]["merged"] | length) == 0' + - '(result["diff"][0]["modified"] | length) == 0' + - '(result["diff"][0]["deleted"] | length) == 0' + - '(result["diff"][0]["query"] | length) == 0' + - '(result["diff"][0]["deploy"][0][ "{{ ansible_xe_fabric }}" ] | length) == 2' + + - assert: + that: + - 'item["RETURN_CODE"] == 200' + loop: '{{ result.response }}' + +############################################### +### CLEANUP ## +############################################### + + always: + + - name: Cleanup - Delete Links + cisco.dcnm.dcnm_links: *links_delete + register: result + when: IT_CONTEXT is not defined + + - assert: + that: + - 'item["RETURN_CODE"] == 200' + loop: '{{ result.response }}' + when: IT_CONTEXT is not defined diff --git a/tests/integration/targets/prepare_dcnm_links/tasks/main.yaml b/tests/integration/targets/prepare_dcnm_links/tasks/main.yaml new file mode 100644 index 000000000..dd144ca9c --- /dev/null +++ b/tests/integration/targets/prepare_dcnm_links/tasks/main.yaml @@ -0,0 +1,81 @@ +############################################## +## SETUP ## +############################################## + +- name: Determine version of DCNM or NDFC + cisco.dcnm.dcnm_rest: + method: GET + path: /appcenter/cisco/ndfc/api/about/version + register: result + ignore_errors: yes + +- set_fact: + controller_version: "{{ result.response['DATA']['version'][0:2] | int }}" + when: ( result.response['DATA']['version'] is search("\d\d.\d+") ) + ignore_errors: yes + +- name: Determine version of DCNM or NDFC + cisco.dcnm.dcnm_rest: + method: GET + path: /fm/fmrest/about/version + register: result + ignore_errors: yes + +- set_fact: + controller_version: "{{ result.response['DATA']['version'][0:2] | int }}" + when: ( result.response['DATA']['version'] is search("\d\d.\d+") ) + ignore_errors: yes + +- name: Determine version of DCNM or NDFC + cisco.dcnm.dcnm_rest: + method: GET + path: /fm/fmrest/about/version + register: result + ignore_errors: yes + +- set_fact: + controller_version: '11' + when: ( result.response['DATA']['version'] == 'DEVEL' ) + ignore_errors: yes + +- name: Initialise ms_fabric_list + set_fact: + ms_fabric_list: [] + +- name: Get the Fabric associations + cisco.dcnm.dcnm_rest: + method: GET + path: /rest/control/fabrics/msd/fabric-associations + when: controller_version == '11' + register: result + ignore_errors: yes + +- name: Setting fact + set_fact: + ms_fabric_list: "{{ (ms_fabric_list | default([])) + [item['fabricName']] }}" + when: (controller_version == '11' and result.response["RETURN_CODE"] == 200) and (item['fabricParent'] != 'None') + loop: '{{ result.response["DATA"] }}' + ignore_errors: yes + +- name: Show the multisite fabric list information + debug: + var: ms_fabric_list + +- name: Get the Fabric associations + cisco.dcnm.dcnm_rest: + method: GET + path: /appcenter/cisco/ndfc/api/v1/lan-fabric/rest/control/fabrics/msd/fabric-associations + when: controller_version == '12' + register: result + ignore_errors: yes + +- name: Setting fact + set_fact: + ms_fabric_list: "{{ (ms_fabric_list | default([])) + [item['fabricName']] }}" + when: (controller_version == '12' and result.response["RETURN_CODE"] == 200) and (item['fabricParent'] != 'None') + loop: '{{ result.response["DATA"] }}' + ignore_errors: yes + +- name: Show the multisite fabric list information + debug: + var: ms_fabric_list diff --git a/tests/sanity/ignore-2.10.txt b/tests/sanity/ignore-2.10.txt index 0963270f6..139297635 100644 --- a/tests/sanity/ignore-2.10.txt +++ b/tests/sanity/ignore-2.10.txt @@ -9,5 +9,6 @@ plugins/modules/dcnm_template.py validate-modules:missing-gplv3-license # GPLv3 plugins/modules/dcnm_service_route_peering.py validate-modules:missing-gplv3-license # GPLv3 license header not found in the first 20 lines of the module plugins/modules/dcnm_service_policy.py validate-modules:missing-gplv3-license # GPLv3 license header not found in the first 20 lines of the module plugins/modules/dcnm_resource_manager.py validate-modules:missing-gplv3-license # GPLv3 license header not found in the first 20 lines of the module +plugins/modules/dcnm_links.py validate-modules:missing-gplv3-license # GPLv3 license header not found in the first 20 lines of the module plugins/modules/dcnm_rest.py import-2.6!skip plugins/modules/dcnm_rest.py import-2.7!skip diff --git a/tests/sanity/ignore-2.11.txt b/tests/sanity/ignore-2.11.txt index 0513b0c04..2361dc569 100644 --- a/tests/sanity/ignore-2.11.txt +++ b/tests/sanity/ignore-2.11.txt @@ -9,6 +9,7 @@ plugins/modules/dcnm_template.py validate-modules:missing-gplv3-license # GPLv3 plugins/modules/dcnm_service_route_peering.py validate-modules:missing-gplv3-license # GPLv3 license header not found in the first 20 lines of the module plugins/modules/dcnm_service_policy.py validate-modules:missing-gplv3-license # GPLv3 license header not found in the first 20 lines of the module plugins/modules/dcnm_resource_manager.py validate-modules:missing-gplv3-license # GPLv3 license header not found in the first 20 lines of the module +plugins/modules/dcnm_links.py validate-modules:missing-gplv3-license # GPLv3 license header not found in the first 20 lines of the module plugins/modules/dcnm_rest.py import-2.6!skip plugins/modules/dcnm_rest.py import-2.7!skip plugins/httpapi/dcnm.py import-2.7!skip diff --git a/tests/sanity/ignore-2.12.txt b/tests/sanity/ignore-2.12.txt index 2a623b353..9c0b33aeb 100644 --- a/tests/sanity/ignore-2.12.txt +++ b/tests/sanity/ignore-2.12.txt @@ -9,6 +9,7 @@ plugins/modules/dcnm_template.py validate-modules:missing-gplv3-license # GPLv3 plugins/modules/dcnm_service_route_peering.py validate-modules:missing-gplv3-license # GPLv3 license header not found in the first 20 lines of the module plugins/modules/dcnm_service_policy.py validate-modules:missing-gplv3-license # GPLv3 license header not found in the first 20 lines of the module plugins/modules/dcnm_resource_manager.py validate-modules:missing-gplv3-license # GPLv3 license header not found in the first 20 lines of the module +plugins/modules/dcnm_links.py validate-modules:missing-gplv3-license # GPLv3 license header not found in the first 20 lines of the module plugins/modules/dcnm_rest.py import-2.6!skip plugins/modules/dcnm_rest.py import-2.7!skip plugins/httpapi/dcnm.py import-3.8!skip diff --git a/tests/sanity/ignore-2.9.txt b/tests/sanity/ignore-2.9.txt index 0963270f6..139297635 100644 --- a/tests/sanity/ignore-2.9.txt +++ b/tests/sanity/ignore-2.9.txt @@ -9,5 +9,6 @@ plugins/modules/dcnm_template.py validate-modules:missing-gplv3-license # GPLv3 plugins/modules/dcnm_service_route_peering.py validate-modules:missing-gplv3-license # GPLv3 license header not found in the first 20 lines of the module plugins/modules/dcnm_service_policy.py validate-modules:missing-gplv3-license # GPLv3 license header not found in the first 20 lines of the module plugins/modules/dcnm_resource_manager.py validate-modules:missing-gplv3-license # GPLv3 license header not found in the first 20 lines of the module +plugins/modules/dcnm_links.py validate-modules:missing-gplv3-license # GPLv3 license header not found in the first 20 lines of the module plugins/modules/dcnm_rest.py import-2.6!skip plugins/modules/dcnm_rest.py import-2.7!skip diff --git a/tests/unit/modules/dcnm/fixtures/dcnm_links_configs.json b/tests/unit/modules/dcnm/fixtures/dcnm_links_configs.json new file mode 100644 index 000000000..e2e4d3143 --- /dev/null +++ b/tests/unit/modules/dcnm/fixtures/dcnm_links_configs.json @@ -0,0 +1,2203 @@ +{ + "intra_delete_num_config": [ + { + "dst_device": "192.168.123.161", + "dst_fabric": "mmudigon-numbered", + "dst_interface": "Ethernet1/1", + "src_device": "192.168.123.160", + "src_interface": "Ethernet1/1" + }, + { + "dst_device": "192.168.123.161", + "dst_fabric": "mmudigon-numbered", + "dst_interface": "Ethernet1/2", + "src_device": "192.168.123.160", + "src_interface": "Ethernet1/2" + }, + { + "dst_device": "192.168.123.161", + "dst_fabric": "mmudigon-numbered", + "dst_interface": "Ethernet1/3", + "src_device": "192.168.123.160", + "src_interface": "Ethernet1/3" + }, + { + "dst_device": "192.168.123.161", + "dst_fabric": "mmudigon-numbered", + "dst_interface": "Ethernet1/4", + "src_device": "192.168.123.160", + "src_interface": "Ethernet1/4" + }, + { + "dst_device": "192.168.123.161", + "dst_fabric": "mmudigon-numbered", + "dst_interface": "Ethernet1/5", + "src_device": "192.168.123.160", + "src_interface": "Ethernet1/5" + } + ], + + "intra_delete_ipv6_config": [ + { + "dst_device": "192.168.123.157", + "dst_fabric": "mmudigon-ipv6-underlay", + "dst_interface": "Ethernet1/1", + "src_device": "192.168.123.156", + "src_interface": "Ethernet1/1" + }, + { + "dst_device": "192.168.123.157", + "dst_fabric": "mmudigon-ipv6-underlay", + "dst_interface": "Ethernet1/2", + "src_device": "192.168.123.156", + "src_interface": "Ethernet1/2" + }, + { + "dst_device": "192.168.123.157", + "dst_fabric": "mmudigon-ipv6-underlay", + "dst_interface": "Ethernet1/3", + "src_device": "192.168.123.156", + "src_interface": "Ethernet1/3" + }, + { + "dst_device": "192.168.123.157", + "dst_fabric": "mmudigon-ipv6-underlay", + "dst_interface": "Ethernet1/4", + "src_device": "192.168.123.156", + "src_interface": "Ethernet1/4" + }, + { + "dst_device": "192.168.123.157", + "dst_fabric": "mmudigon-ipv6-underlay", + "dst_interface": "Ethernet1/5", + "src_device": "192.168.123.156", + "src_interface": "Ethernet1/5" + } + ], + + "intra_delete_unnum_config": [ + { + "dst_device": "192.168.123.171", + "dst_fabric": "mmudigon-unnumbered", + "dst_interface": "Ethernet1/1", + "src_device": "192.168.123.170", + "src_interface": "Ethernet1/1" + }, + { + "dst_device": "192.168.123.171", + "dst_fabric": "mmudigon-unnumbered", + "dst_interface": "Ethernet1/2", + "src_device": "192.168.123.170", + "src_interface": "Ethernet1/2" + }, + { + "dst_device": "192.168.123.171", + "dst_fabric": "mmudigon-unnumbered", + "dst_interface": "Ethernet1/3", + "src_device": "192.168.123.170", + "src_interface": "Ethernet1/3" + }, + { + "dst_device": "192.168.123.171", + "dst_fabric": "mmudigon-unnumbered", + "dst_interface": "Ethernet1/4", + "src_device": "192.168.123.170", + "src_interface": "Ethernet1/4" + }, + { + "dst_device": "192.168.123.171", + "dst_fabric": "mmudigon-unnumbered", + "dst_interface": "Ethernet1/5", + "src_device": "192.168.123.170", + "src_interface": "Ethernet1/5" + } + ], + + "intra_delete_vpc_config": [ + { + "dst_device": "192.168.123.151", + "dst_fabric": "mmudigon", + "dst_interface": "Ethernet1/1", + "src_device": "192.168.123.150", + "src_interface": "Ethernet1/1" + }, + { + "dst_device": "192.168.123.151", + "dst_fabric": "mmudigon", + "dst_interface": "Ethernet1/2", + "src_device": "192.168.123.150", + "src_interface": "Ethernet1/2" + }, + { + "dst_device": "192.168.123.151", + "dst_fabric": "mmudigon", + "dst_interface": "Ethernet1/3", + "src_device": "192.168.123.150", + "src_interface": "Ethernet1/3" + }, + { + "dst_device": "192.168.123.151", + "dst_fabric": "mmudigon", + "dst_interface": "Ethernet1/4", + "src_device": "192.168.123.150", + "src_interface": "Ethernet1/4" + }, + { + "dst_device": "192.168.123.151", + "dst_fabric": "mmudigon", + "dst_interface": "Ethernet1/5", + "src_device": "192.168.123.150", + "src_interface": "Ethernet1/5" + } + ], + + "inter_delete_num_config": [ + { + "dst_device": "10.64.78.231", + "dst_fabric": "test_net", + "dst_interface": "Ethernet1/3", + "src_device": "10.64.78.227", + "src_interface": "Ethernet1/3" + }, + { + "dst_device": "10.64.78.231", + "dst_fabric": "test_net", + "dst_interface": "Ethernet1/4", + "src_device": "10.64.78.227", + "src_interface": "Ethernet1/4" + }, + { + "dst_device": "10.64.78.231", + "dst_fabric": "test_net", + "dst_interface": "Ethernet1/5", + "src_device": "10.64.78.227", + "src_interface": "Ethernet1/5" + }, + { + "dst_device": "10.64.78.225", + "dst_fabric": "test_net1", + "dst_interface": "Ethernet1/6", + "src_device": "10.64.78.227", + "src_interface": "Ethernet1/6" + }, + { + "dst_device": "10.64.78.225", + "dst_fabric": "test_net1", + "dst_interface": "Ethernet1/7", + "src_device": "10.64.78.227", + "src_interface": "Ethernet1/7" + }, + { + "dst_device": "10.64.78.225", + "dst_fabric": "test_net1", + "dst_interface": "Ethernet1/8", + "src_device": "10.64.78.227", + "src_interface": "Ethernet1/8" + } + ], + + "intra_merge_num_no_opts_config": [ + { + "dst_device": "192.168.123.161", + "dst_fabric": "mmudigon-numbered", + "dst_interface": "Ethernet1/1", + "profile": { + "admin_state": true, + "mtu": 9216, + "peer1_ipv4_addr": "192.168.1.1", + "peer2_ipv4_addr": "192.168.1.2" + }, + "src_device": "192.168.123.160", + "src_interface": "Ethernet1/1", + "template": "int_intra_fabric_num_link" + }, + { + "dst_device": "192.168.123.161", + "dst_fabric": "mmudigon-numbered", + "dst_interface": "Ethernet1/2", + "src_device": "192.168.123.160", + "src_interface": "Ethernet1/2", + "template": "int_pre_provision_intra_fabric_link" + }, + { + "dst_device": "192.168.123.161", + "dst_fabric": "mmudigon-numbered", + "dst_interface": "Ethernet1/3", + "profile": { + "admin_state": true, + "peer1_ipv4_addr": "192.168.2.1", + "peer2_ipv4_addr": "192.168.2.2" + }, + "src_device": "192.168.123.160", + "src_interface": "Ethernet1/3", + "template": "ios_xe_int_intra_fabric_num_link" + } + ], + + "intra_merge_unnum_no_opts_config": [ + { + "dst_device": "192.168.123.171", + "dst_fabric": "mmudigon-unnumbered", + "dst_interface": "Ethernet1/1", + "profile": { + "admin_state": true, + "mtu": 9216 + }, + "src_device": "192.168.123.170", + "src_interface": "Ethernet1/1", + "template": "int_intra_fabric_unnum_link" + }, + { + "dst_device": "192.168.123.171", + "dst_fabric": "mmudigon-unnumbered", + "dst_interface": "Ethernet1/2", + "src_device": "192.168.123.170", + "src_interface": "Ethernet1/2", + "template": "int_pre_provision_intra_fabric_link" + } + ], + + "intra_merge_ipv6_no_opts_config": [ + { + "dst_device": "192.168.123.157", + "dst_fabric": "mmudigon-ipv6-underlay", + "dst_interface": "Ethernet1/1", + "profile": { + "admin_state": true, + "mtu": 9216, + "peer1_ipv6_addr": "fe80:0201::01", + "peer2_ipv6_addr": "fe80:0201::02" + }, + "src_device": "192.168.123.156", + "src_interface": "Ethernet1/1", + "template": "int_intra_fabric_ipv6_link_local" + }, + { + "dst_device": "192.168.123.157", + "dst_fabric": "mmudigon-ipv6-underlay", + "dst_interface": "Ethernet1/2", + "src_device": "192.168.123.156", + "src_interface": "Ethernet1/2", + "template": "int_pre_provision_intra_fabric_link" + }, + { + "dst_device": "192.168.123.157", + "dst_fabric": "mmudigon-ipv6-underlay", + "dst_interface": "Ethernet1/3", + "profile": { + "admin_state": true, + "mtu": 9216, + "peer1_ipv6_addr": "fe80:0202::01", + "peer2_ipv6_addr": "fe80:0202::02" + }, + "src_device": "192.168.123.156", + "src_interface": "Ethernet1/3", + "template": "int_intra_fabric_num_link" + } + ], + + "intra_merge_vpc_no_opts_config": [ + { + "dst_device": "192.168.123.151", + "dst_fabric": "mmudigon", + "dst_interface": "Ethernet1/4", + "profile": { + "admin_state": true, + "mtu": 9216, + "peer1_ipv4_addr": "192.168.1.1", + "peer2_ipv4_addr": "192.168.1.2" + }, + "src_device": "192.168.123.150", + "src_interface": "Ethernet1/4", + "template": "int_intra_vpc_peer_keep_alive_link" + } + ], + + "inter_merge_num_no_opts_config": [ + { + "dst_device": "10.64.78.231", + "dst_fabric": "test_net", + "dst_interface": "Ethernet1/3", + "profile": { + "dst_asn": 1001, + "ipv4_subnet": "193.168.1.1/24", + "neighbor_ip": "193.168.1.2", + "src_asn": 1000 + }, + "src_device": "10.64.78.227", + "src_interface": "Ethernet1/3", + "template": "ext_fabric_setup" + }, + { + "dst_device": "10.64.78.231", + "dst_fabric": "test_net", + "dst_interface": "Ethernet1/4", + "profile": { + "dst_asn": 1201, + "ipv4_subnet": "193.168.2.1/24", + "neighbor_ip": "193.168.2.2", + "src_asn": 1200 + }, + "src_device": "10.64.78.227", + "src_interface": "Ethernet1/4", + "template": "ext_multisite_underlay_setup" + }, + { + "dst_device": "10.64.78.231", + "dst_fabric": "test_net", + "dst_interface": "Ethernet1/5", + "profile": { + "dst_asn": 1301, + "ipv4_addr": "193.168.3.1", + "neighbor_ip": "193.168.3.2", + "src_asn": 1300 + }, + "src_device": "10.64.78.227", + "src_interface": "Ethernet1/5", + "template": "ext_evpn_multisite_overlay_setup" + }, + { + "dst_device": "10.64.78.225", + "dst_fabric": "test_net1", + "dst_interface": "Ethernet1/6", + "profile": { + "dst_asn": 1401, + "ipv4_subnet": "193.168.4.1/24", + "neighbor_ip": "193.168.4.2", + "src_asn": 1400 + }, + "src_device": "10.64.78.227", + "src_interface": "Ethernet1/6", + "template": "ext_fabric_setup" + }, + { + "dst_device": "10.64.78.225", + "dst_fabric": "test_net1", + "dst_interface": "Ethernet1/7", + "profile": { + "dst_asn": 1501, + "ipv4_subnet": "193.168.5.1/24", + "neighbor_ip": "193.168.5.2", + "src_asn": 1500 + }, + "src_device": "10.64.78.227", + "src_interface": "Ethernet1/7", + "template": "ext_multisite_underlay_setup" + }, + { + "dst_device": "10.64.78.225", + "dst_fabric": "test_net1", + "dst_interface": "Ethernet1/8", + "profile": { + "dst_asn": 1601, + "ipv4_addr": "193.168.6.1", + "neighbor_ip": "193.168.6.2", + "src_asn": 1600 + }, + "src_device": "10.64.78.227", + "src_interface": "Ethernet1/8", + "template": "ext_evpn_multisite_overlay_setup" + } + ], + + "intra_merge_num_config": [ + { + "dst_device": "192.168.123.161", + "dst_fabric": "mmudigon-numbered", + "dst_interface": "Ethernet1/1", + "profile": { + "admin_state": true, + "enable_macsec": false, + "mtu": 9216, + "peer1_bfd_echo_disable": false, + "peer1_cmds": [ + "no shutdown" + ], + "peer1_description": "Description of source", + "peer1_ipv4_addr": "192.168.1.1", + "peer2_bfd_echo_disable": false, + "peer2_cmds": [ + "no shutdown" + ], + "peer2_description": "Description of dest", + "peer2_ipv4_addr": "192.168.1.2" + }, + "src_device": "192.168.123.160", + "src_interface": "Ethernet1/1", + "template": "int_intra_fabric_num_link" + }, + { + "dst_device": "192.168.123.161", + "dst_fabric": "mmudigon-numbered", + "dst_interface": "Ethernet1/2", + "src_device": "192.168.123.160", + "src_interface": "Ethernet1/2", + "template": "int_pre_provision_intra_fabric_link" + }, + { + "dst_device": "192.168.123.161", + "dst_fabric": "mmudigon-numbered", + "dst_interface": "Ethernet1/3", + "profile": { + "admin_state": true, + "enable_macsec": false, + "mtu": 1500, + "peer1_bfd_echo_disable": false, + "peer1_cmds": [ + "no shutdown" + ], + "peer1_description": "Description of source", + "peer1_ipv4_addr": "192.168.2.1", + "peer1_ipv6_addr": "fe80:01::01", + "peer2_bfd_echo_disable": false, + "peer2_cmds": [ + "no shutdown" + ], + "peer2_description": "Description of dest", + "peer2_ipv4_addr": "192.168.2.2", + "peer2_ipv6_addr": "fe80:01::02" + }, + "src_device": "192.168.123.160", + "src_interface": "Ethernet1/3", + "template": "ios_xe_int_intra_fabric_num_link" + } + ], + + "intra_merge_unnum_config": [ + { + "dst_device": "192.168.123.171", + "dst_fabric": "mmudigon-unnumbered", + "dst_interface": "Ethernet1/1", + "profile": { + "admin_state": true, + "enable_macsec": false, + "mtu": 9216, + "peer1_cmds": [ + "no shutdown" + ], + "peer1_description": "Description of source", + "peer2_cmds": [ + "no shutdown" + ], + "peer2_description": "Description of dest" + }, + "src_device": "192.168.123.170", + "src_interface": "Ethernet1/1", + "template": "int_intra_fabric_unnum_link" + }, + { + "dst_device": "192.168.123.171", + "dst_fabric": "mmudigon-unnumbered", + "dst_interface": "Ethernet1/2", + "src_device": "192.168.123.170", + "src_interface": "Ethernet1/2", + "template": "int_pre_provision_intra_fabric_link" + } + ], + + "intra_merge_ipv6_config": [ + { + "dst_device": "192.168.123.157", + "dst_fabric": "mmudigon-ipv6-underlay", + "dst_interface": "Ethernet1/1", + "profile": { + "admin_state": true, + "enable_macsec": false, + "mtu": 9216, + "peer1_bfd_echo_disable": false, + "peer1_cmds": [ + "no shutdown" + ], + "peer1_description": "Description of source", + "peer1_ipv4_addr": "192.169.1.1", + "peer1_ipv6_addr": "fe80:0201::01", + "peer2_bfd_echo_disable": false, + "peer2_cmds": [ + "no shutdown" + ], + "peer2_description": "Description of dest", + "peer2_ipv4_addr": "192.169.1.2", + "peer2_ipv6_addr": "fe80:0201::02" + }, + "src_device": "192.168.123.156", + "src_interface": "Ethernet1/1", + "template": "int_intra_fabric_ipv6_link_local" + }, + { + "dst_device": "192.168.123.157", + "dst_fabric": "mmudigon-ipv6-underlay", + "dst_interface": "Ethernet1/2", + "src_device": "192.168.123.156", + "src_interface": "Ethernet1/2", + "template": "int_pre_provision_intra_fabric_link" + }, + { + "dst_device": "192.168.123.157", + "dst_fabric": "mmudigon-ipv6-underlay", + "dst_interface": "Ethernet1/3", + "profile": { + "admin_state": true, + "enable_macsec": false, + "mtu": 1500, + "peer1_bfd_echo_disable": false, + "peer1_cmds": [ + "no shutdown" + ], + "peer1_description": "Description of source", + "peer1_ipv4_addr": "192.169.2.1", + "peer1_ipv6_addr": "fe80:0202::01", + "peer2_bfd_echo_disable": false, + "peer2_cmds": [ + "no shutdown" + ], + "peer2_description": "Description of dest", + "peer2_ipv4_addr": "192.169.2.2", + "peer2_ipv6_addr": "fe80:0202::02" + }, + "src_device": "192.168.123.156", + "src_interface": "Ethernet1/3", + "template": "int_intra_fabric_num_link" + } + ], + + "intra_merge_vpc_config": [ + { + "dst_device": "192.168.123.151", + "dst_fabric": "mmudigon", + "dst_interface": "Ethernet1/4", + "profile": { + "admin_state": true, + "enable_macsec": false, + "intf_vrf": "test_vrf", + "mtu": 9216, + "peer1_cmds": [ + "no shutdown" + ], + "peer1_description": "Description of source", + "peer1_ipv4_addr": "192.170.1.1", + "peer1_ipv6_addr": "fe80:2a::01", + "peer2_cmds": [ + "no shutdown" + ], + "peer2_description": "Description of dest", + "peer2_ipv4_addr": "192.170.1.2", + "peer2_ipv6_addr": "fe80:2a::02" + }, + "src_device": "192.168.123.150", + "src_interface": "Ethernet1/4", + "template": "int_intra_vpc_peer_keep_alive_link" + } + ], + + "inter_merge_num_config": [ + { + "dst_device": "10.64.78.231", + "dst_fabric": "test_net", + "dst_interface": "Ethernet1/3", + "profile": { + "auto_deploy": false, + "dst_asn": 1001, + "ipv4_subnet": "193.168.1.1/24", + "mtu": 9216, + "neighbor_ip": "193.168.1.2", + "peer1_cmds": [ + "no shutdown" + ], + "peer1_description": "Description of source", + "peer2_cmds": [ + "no shutdown" + ], + "peer2_description": "Description of dest", + "src_asn": 1000 + }, + "src_device": "10.64.78.227", + "src_interface": "Ethernet1/3", + "template": "ext_fabric_setup" + }, + { + "dst_device": "10.64.78.231", + "dst_fabric": "test_net", + "dst_interface": "Ethernet1/4", + "profile": { + "deploy_dci_tracking": false, + "dst_asn": 1201, + "ebgp_auth_key_type": 3, + "ebgp_password": 17314053, + "ebgp_password_enable": true, + "inherit_from_msd": true, + "ipv4_subnet": "193.168.2.1/24", + "max_paths": 1, + "mtu": 9216, + "neighbor_ip": "193.168.2.2", + "peer1_cmds": [ + "no shutdown" + ], + "peer1_description": "Description of source", + "peer2_cmds": [ + "no shutdown" + ], + "peer2_description": "Description of dest", + "route_tag": 12345, + "src_asn": 1200 + }, + "src_device": "10.64.78.227", + "src_interface": "Ethernet1/4", + "template": "ext_multisite_underlay_setup" + }, + { + "dst_device": "10.64.78.231", + "dst_fabric": "test_net", + "dst_interface": "Ethernet1/5", + "profile": { + "bgp_multihop": 5, + "dst_asn": 1301, + "ebgp_password": 17314053, + "ebgp_password_enable": true, + "ebpg_auth_key_type": 3, + "inherit_from_msd": false, + "ipv4_addr": "193.168.3.1", + "neighbor_ip": "193.168.3.2", + "src_asn": 1300, + "trm_enabled": false + }, + "src_device": "10.64.78.227", + "src_interface": "Ethernet1/5", + "template": "ext_evpn_multisite_overlay_setup" + }, + { + "dst_device": "10.64.78.225", + "dst_fabric": "test_net1", + "dst_interface": "Ethernet1/6", + "profile": { + "auto_deploy": false, + "dst_asn": 1401, + "ipv4_subnet": "193.168.4.1/24", + "mtu": 9216, + "neighbor_ip": "193.168.4.2", + "peer1_cmds": [ + "no shutdown" + ], + "peer1_description": "Description of source", + "peer2_cmds": [ + "no shutdown" + ], + "peer2_description": "Description of dest", + "src_asn": 1400 + }, + "src_device": "10.64.78.227", + "src_interface": "Ethernet1/6", + "template": "ext_fabric_setup" + }, + { + "dst_device": "10.64.78.225", + "dst_fabric": "test_net1", + "dst_interface": "Ethernet1/7", + "profile": { + "deploy_dci_tracking": false, + "dst_asn": 1501, + "ebgp_auth_key_type": 3, + "ebgp_password": 17314053, + "ebpg_password_enable": true, + "inherit_from_msd": true, + "ipv4_subnet": "193.168.5.1/24", + "max_paths": 1, + "mtu": 9216, + "neighbor_ip": "193.168.5.2", + "peer1_cmds": [ + "no shutdown" + ], + "peer1_description": "Description of source", + "peer2_cmds": [ + "no shutdown" + ], + "peer2_description": "Description of dest", + "route_tag": 12345, + "src_asn": 1500 + }, + "src_device": "10.64.78.227", + "src_interface": "Ethernet1/7", + "template": "ext_multisite_underlay_setup" + }, + { + "dst_device": "10.64.78.225", + "dst_fabric": "test_net1", + "dst_interface": "Ethernet1/8", + "profile": { + "bgp_multihop": 5, + "dst_asn": 1601, + "ebgp_password": 17314053, + "ebgp_password_enable": true, + "ebpg_auth_key_type": 3, + "inherit_from_msd": false, + "ipv4_addr": "193.168.6.1", + "neighbor_ip": "193.168.6.2", + "src_asn": 1600, + "trm_enabled": false + }, + "src_device": "10.64.78.227", + "src_interface": "Ethernet1/8", + "template": "ext_evpn_multisite_overlay_setup" + } + ], + + "intra_modify_num_config": [ + { + "dst_device": "192.168.123.161", + "dst_fabric": "mmudigon-numbered", + "dst_interface": "Ethernet1/1", + "profile": { + "admin_state": false, + "enable_macsec": false, + "mtu": 1000, + "peer1_bfd_echo_disable": true, + "peer1_cmds": [ + "show version" + ], + "peer1_description": "MOD - Description of source - 1", + "peer1_ipv4_addr": "193.168.1.1", + "peer2_bfd_echo_disable": true, + "peer2_cmds": [ + "show version" + ], + "peer2_description": "MOD - Description of dest - 1", + "peer2_ipv4_addr": "193.168.1.2" + }, + "src_device": "192.168.123.160", + "src_interface": "Ethernet1/1", + "template": "int_intra_fabric_num_link" + }, + { + "dst_device": "192.168.123.161", + "dst_fabric": "mmudigon-numbered", + "dst_interface": "Ethernet1/2", + "src_device": "192.168.123.160", + "src_interface": "Ethernet1/2", + "template": "int_pre_provision_intra_fabric_link" + }, + { + "dst_device": "192.168.123.161", + "dst_fabric": "mmudigon-numbered", + "dst_interface": "Ethernet1/3", + "profile": { + "admin_state": false, + "enable_macsec": false, + "mtu": 2000, + "peer1_bfd_echo_disable": true, + "peer1_cmds": [ + "show version" + ], + "peer1_description": "MOD - Description of source - 3", + "peer1_ipv4_addr": "193.169.1.1", + "peer1_ipv6_addr": "fe89:29::01", + "peer2_bfd_echo_disable": true, + "peer2_cmds": [ + "show version" + ], + "peer2_description": "MOD - Description of dest - 3", + "peer2_ipv4_addr": "193.169.1.2", + "peer2_ipv6_addr": "fe89:29::02" + }, + "src_device": "192.168.123.160", + "src_interface": "Ethernet1/3", + "template": "ios_xe_int_intra_fabric_num_link" + } + ], + + "intra_modify_unnum_config": [ + { + "dst_device": "192.168.123.171", + "dst_fabric": "mmudigon-unnumbered", + "dst_interface": "Ethernet1/1", + "profile": { + "admin_state": false, + "mtu": 1800 + }, + "src_device": "192.168.123.170", + "src_interface": "Ethernet1/1", + "template": "int_intra_fabric_unnum_link" + }, + { + "dst_device": "192.168.123.171", + "dst_fabric": "mmudigon-unnumbered", + "dst_interface": "Ethernet1/2", + "src_device": "192.168.123.170", + "src_interface": "Ethernet1/2", + "template": "int_pre_provision_intra_fabric_link" + } + ], + + "intra_modify_ipv6_config": [ + { + "dst_device": "192.168.123.157", + "dst_fabric": "mmudigon-ipv6-underlay", + "dst_interface": "Ethernet1/1", + "profile": { + "admin_state": false, + "enable_macsec": false, + "mtu": 1500, + "peer1_bfd_echo_disable": true, + "peer1_cmds": [ + "show version" + ], + "peer1_description": "Description of source - MOD", + "peer1_ipv4_addr": "193.171.1.1", + "peer1_ipv6_addr": "fe89:0301::01", + "peer2_bfd_echo_disable": true, + "peer2_cmds": [ + "show version" + ], + "peer2_description": "Description of dest - MOD", + "peer2_ipv4_addr": "193.171.1.2", + "peer2_ipv6_addr": "fe89:0301::02" + }, + "src_device": "192.168.123.156", + "src_interface": "Ethernet1/1", + "template": "int_intra_fabric_ipv6_link_local" + }, + { + "dst_device": "192.168.123.157", + "dst_fabric": "mmudigon-ipv6-underlay", + "dst_interface": "Ethernet1/2", + "src_device": "192.168.123.156", + "src_interface": "Ethernet1/2", + "template": "int_pre_provision_intra_fabric_link" + }, + { + "dst_device": "192.168.123.157", + "dst_fabric": "mmudigon-ipv6-underlay", + "dst_interface": "Ethernet1/3", + "profile": { + "admin_state": false, + "enable_macsec": false, + "mtu": 1900, + "peer1_bfd_echo_disable": true, + "peer1_cmds": [ + "show version" + ], + "peer1_description": "Description of source - MOD", + "peer1_ipv4_addr": "193.172.2.1", + "peer1_ipv6_addr": "fe89:0302::01", + "peer2_bfd_echo_disable": true, + "peer2_cmds": [ + "show version" + ], + "peer2_description": "Description of dest - MOD", + "peer2_ipv4_addr": "193.172.2.2", + "peer2_ipv6_addr": "fe89:0302::02" + }, + "src_device": "192.168.123.156", + "src_interface": "Ethernet1/3", + "template": "int_intra_fabric_num_link" + } + ], + + "intra_modify_vpc_config": [ + { + "dst_device": "192.168.123.151", + "dst_fabric": "mmudigon", + "dst_interface": "Ethernet1/4", + "profile": { + "admin_state": true, + "enable_macsec": false, + "intf_vrf": "test2_vrf", + "mtu": 9216, + "peer1_cmds": [ + "show version" + ], + "peer1_description": "Description of source - MOD", + "peer1_ipv4_addr": "193.170.1.1", + "peer1_ipv6_addr": "fe80:29::01", + "peer2_cmds": [ + "show version" + ], + "peer2_description": "Description of dest - MOD", + "peer2_ipv4_addr": "193.170.1.2", + "peer2_ipv6_addr": "fe80:29::02" + }, + "src_device": "192.168.123.150", + "src_interface": "Ethernet1/4", + "template": "int_intra_vpc_peer_keep_alive_link" + } + ], + + "inter_modify_num_config": [ + { + "dst_device": "10.64.78.231", + "dst_fabric": "test_net", + "dst_interface": "Ethernet1/3", + "profile": { + "auto_deploy": true, + "dst_asn": 1001, + "ipv4_subnet": "193.168.10.1/24", + "mtu": 1216, + "neighbor_ip": "193.168.10.2", + "peer1_cmds": [ + "show version" + ], + "peer1_description": "Description of source - MOD", + "peer2_cmds": [ + "show version" + ], + "peer2_description": "Description of dest - MOD", + "src_asn": 1000 + }, + "src_device": "10.64.78.227", + "src_interface": "Ethernet1/3", + "template": "ext_fabric_setup" + }, + { + "dst_device": "10.64.78.231", + "dst_fabric": "test_net", + "dst_interface": "Ethernet1/4", + "profile": { + "deploy_dci_tracking": true, + "dst_asn": 1201, + "ebgp_auth_key_type": 7, + "ebgp_password": 17043525, + "ebgp_password_enable": false, + "inherit_from_msd": false, + "ipv4_subnet": "193.168.20.1/24", + "max_paths": 2, + "mtu": 2216, + "neighbor_ip": "193.168.20.2", + "peer1_description": "Description of source - MOD", + "peer2_description": "Description of dest - MOD", + "route_tag": 11111, + "src_asn": 1200 + }, + "src_device": "10.64.78.227", + "src_interface": "Ethernet1/4", + "template": "ext_multisite_underlay_setup" + }, + { + "dst_device": "10.64.78.231", + "dst_fabric": "test_net", + "dst_interface": "Ethernet1/5", + "profile": { + "bgp_multihop": 3, + "dst_asn": 1301, + "ebgp_password": 51130565, + "ebgp_password_enable": false, + "ebpg_auth_key_type": 7, + "inherit_from_msd": true, + "ipv4_addr": "193.168.30.1", + "neighbor_ip": "193.168.30.2", + "src_asn": 1300, + "trm_enabled": true + }, + "src_device": "10.64.78.227", + "src_interface": "Ethernet1/5", + "template": "ext_evpn_multisite_overlay_setup" + }, + { + "dst_device": "10.64.78.225", + "dst_fabric": "test_net1", + "dst_interface": "Ethernet1/6", + "profile": { + "auto_deploy": true, + "dst_asn": 1401, + "ipv4_subnet": "193.168.40.1/24", + "mtu": 4216, + "neighbor_ip": "193.168.40.2", + "peer1_cmds": [ + "no shutdown" + ], + "peer1_description": "Description of source", + "peer2_cmds": [ + "no shutdown" + ], + "peer2_description": "Description of dest", + "src_asn": 1400 + }, + "src_device": "10.64.78.227", + "src_interface": "Ethernet1/6", + "template": "ext_fabric_setup" + }, + { + "dst_device": "10.64.78.225", + "dst_fabric": "test_net1", + "dst_interface": "Ethernet1/7", + "profile": { + "deploy_dci_tracking": false, + "dst_asn": 1501, + "ebgp_auth_key_type": 7, + "ebgp_password": 17314053, + "ebpg_password_enable": true, + "inherit_from_msd": false, + "ipv4_subnet": "193.168.50.1/24", + "max_paths": 1, + "mtu": 5216, + "neighbor_ip": "193.168.50.2", + "peer1_description": "Description of source", + "peer2_description": "Description of dest", + "route_tag": 12345, + "src_asn": 1500 + }, + "src_device": "10.64.78.227", + "src_interface": "Ethernet1/7", + "template": "ext_multisite_underlay_setup" + }, + { + "dst_device": "10.64.78.225", + "dst_fabric": "test_net1", + "dst_interface": "Ethernet1/8", + "profile": { + "bgp_multihop": 6, + "dst_asn": 1601, + "ebgp_password": 17314053, + "ebgp_password_enable": true, + "ebpg_auth_key_type": 7, + "inherit_from_msd": false, + "ipv4_addr": "193.168.60.1", + "neighbor_ip": "193.168.60.2", + "src_asn": 1600, + "trm_enabled": true + }, + "src_device": "10.64.78.227", + "src_interface": "Ethernet1/8", + "template": "ext_evpn_multisite_overlay_setup" + } + ], + + "intra_replace_num_config": [ + { + "dst_device": "192.168.123.161", + "dst_fabric": "mmudigon-numbered", + "dst_interface": "Ethernet1/1", + "profile": { + "admin_state": false, + "enable_macsec": false, + "mtu": 1000, + "peer1_bfd_echo_disable": true, + "peer1_description": "", + "peer1_ipv4_addr": "192.169.1.1", + "peer2_bfd_echo_disable": true, + "peer2_description": "", + "peer2_ipv4_addr": "192.169.1.2" + }, + "src_device": "192.168.123.160", + "src_interface": "Ethernet1/1", + "template": "int_intra_fabric_num_link" + }, + { + "dst_device": "192.168.123.161", + "dst_fabric": "mmudigon-numbered", + "dst_interface": "Ethernet1/2", + "src_device": "192.168.123.160", + "src_interface": "Ethernet1/2", + "template": "int_pre_provision_intra_fabric_link" + }, + { + "dst_device": "192.168.123.161", + "dst_fabric": "mmudigon-numbered", + "dst_interface": "Ethernet1/3", + "profile": { + "admin_state": false, + "enable_macsec": false, + "mtu": 2000, + "peer1_bfd_echo_disable": false, + "peer1_cmds": [ + "show version" + ], + "peer1_description": "New Description - Rep", + "peer1_ipv4_addr": "192.170.2.1", + "peer1_ipv6_addr": "fe80:91::01", + "peer2_bfd_echo_disable": false, + "peer2_cmds": [ + "show version" + ], + "peer2_description": "New Description - Rep", + "peer2_ipv4_addr": "192.170.2.2", + "peer2_ipv6_addr": "fe80:91::02" + }, + "src_device": "192.168.123.160", + "src_interface": "Ethernet1/3", + "template": "ios_xe_int_intra_fabric_num_link" + } + ], + + "intra_replace_unnum_config": [ + { + "dst_device": "192.168.123.171", + "dst_fabric": "mmudigon-unnumbered", + "dst_interface": "Ethernet1/1", + "profile": { + "admin_state": false, + "enable_macsec": false, + "mtu": 1216, + "peer1_cmds": [ + "show version" + ], + "peer1_description": "Description of source - MOD", + "peer2_description": "Description of dest - MOD" + }, + "src_device": "192.168.123.170", + "src_interface": "Ethernet1/1", + "template": "int_intra_fabric_unnum_link" + }, + { + "dst_device": "192.168.123.171", + "dst_fabric": "mmudigon-unnumbered", + "dst_interface": "Ethernet1/2", + "src_device": "192.168.123.170", + "src_interface": "Ethernet1/2", + "template": "int_pre_provision_intra_fabric_link" + } + ], + + "intra_replace_ipv6_config": [ + { + "dst_device": "192.168.123.157", + "dst_fabric": "mmudigon-ipv6-underlay", + "dst_interface": "Ethernet1/1", + "profile": { + "admin_state": true, + "enable_macsec": false, + "mtu": 3216, + "peer1_cmds": [ + "show version" + ], + "peer1_ipv4_addr": "193.169.1.1", + "peer1_ipv6_addr": "fe89:0201::01", + "peer2_cmds": [ + "show version" + ], + "peer2_ipv4_addr": "193.169.1.2", + "peer2_ipv6_addr": "fe89:0201::02" + }, + "src_device": "192.168.123.156", + "src_interface": "Ethernet1/1", + "template": "int_intra_fabric_ipv6_link_local" + }, + { + "dst_device": "192.168.123.157", + "dst_fabric": "mmudigon-ipv6-underlay", + "dst_interface": "Ethernet1/2", + "src_device": "192.168.123.156", + "src_interface": "Ethernet1/2", + "template": "int_pre_provision_intra_fabric_link" + }, + { + "dst_device": "192.168.123.157", + "dst_fabric": "mmudigon-ipv6-underlay", + "dst_interface": "Ethernet1/3", + "profile": { + "admin_state": true, + "enable_macsec": false, + "mtu": 2000, + "peer1_bfd_echo_disable": false, + "peer1_description": "Description of source", + "peer1_ipv4_addr": "193.169.2.1", + "peer1_ipv6_addr": "fe89:0202::01", + "peer2_bfd_echo_disable": false, + "peer2_description": "Description of dest", + "peer2_ipv4_addr": "193.169.2.2", + "peer2_ipv6_addr": "fe89:0202::02" + }, + "src_device": "192.168.123.156", + "src_interface": "Ethernet1/3", + "template": "int_intra_fabric_num_link" + } + ], + + "intra_replace_vpc_config": [ + { + "dst_device": "192.168.123.151", + "dst_fabric": "mmudigon", + "dst_interface": "Ethernet1/4", + "profile": { + "admin_state": true, + "enable_macsec": false, + "intf_vrf": "test_vrf", + "mtu": 9216, + "peer1_cmds": [ + "show version" + ], + "peer1_description": "Description of source - REP", + "peer1_ipv4_addr": "193.170.1.1", + "peer1_ipv6_addr": "fe80:29::01", + "peer2_cmds": [ + "show version" + ], + "peer2_description": "Description of dest - REP", + "peer2_ipv4_addr": "193.170.1.2", + "peer2_ipv6_addr": "fe80:29::02" + }, + "src_device": "192.168.123.150", + "src_interface": "Ethernet1/4", + "template": "int_intra_vpc_peer_keep_alive_link" + } + ], + + "inter_replace_num_config": [ + { + "dst_device": "10.64.78.231", + "dst_fabric": "test_net", + "dst_interface": "Ethernet1/3", + "profile": { + "auto_deploy": true, + "dst_asn": 1001, + "ipv4_subnet": "193.168.10.1/24", + "mtu": 1216, + "neighbor_ip": "193.168.10.2", + "peer1_cmds": [ + "show version" + ], + "peer1_description": "Description of source - REP", + "peer2_cmds": [ + "show version" + ], + "peer2_description": "Description of dest - REP", + "src_asn": 1000 + }, + "src_device": "10.64.78.227", + "src_interface": "Ethernet1/3", + "template": "ext_fabric_setup" + }, + { + "dst_device": "10.64.78.231", + "dst_fabric": "test_net", + "dst_interface": "Ethernet1/4", + "profile": { + "deploy_dci_tracking": true, + "dst_asn": 1201, + "ebgp_auth_key_type": 7, + "ebgp_password": 17043525, + "ebgp_password_enable": false, + "inherit_from_msd": false, + "ipv4_subnet": "193.168.20.1/24", + "max_paths": 2, + "mtu": 2216, + "neighbor_ip": "193.168.20.2", + "peer1_description": "Description of source - REP", + "peer2_description": "Description of dest - REP", + "route_tag": 11111, + "src_asn": 1200 + }, + "src_device": "10.64.78.227", + "src_interface": "Ethernet1/4", + "template": "ext_multisite_underlay_setup" + }, + { + "dst_device": "10.64.78.231", + "dst_fabric": "test_net", + "dst_interface": "Ethernet1/5", + "profile": { + "bgp_multihop": 3, + "dst_asn": 1301, + "ebgp_password": 51130565, + "ebgp_password_enable": false, + "ebpg_auth_key_type": 7, + "inherit_from_msd": true, + "ipv4_addr": "193.168.30.1", + "neighbor_ip": "193.168.30.2", + "src_asn": 1300, + "trm_enabled": true + }, + "src_device": "10.64.78.227", + "src_interface": "Ethernet1/5", + "template": "ext_evpn_multisite_overlay_setup" + }, + { + "dst_device": "10.64.78.225", + "dst_fabric": "test_net1", + "dst_interface": "Ethernet1/6", + "profile": { + "auto_deploy": true, + "dst_asn": 1401, + "ipv4_subnet": "193.168.40.1/24", + "mtu": 4216, + "neighbor_ip": "193.168.40.2", + "peer1_cmds": [ + "no shutdown" + ], + "peer1_description": "Description of source - REP", + "peer2_cmds": [ + "no shutdown" + ], + "peer2_description": "Description of dest - REP", + "src_asn": 1400 + }, + "src_device": "10.64.78.227", + "src_interface": "Ethernet1/6", + "template": "ext_fabric_setup" + }, + { + "dst_device": "10.64.78.225", + "dst_fabric": "test_net1", + "dst_interface": "Ethernet1/7", + "profile": { + "deploy_dci_tracking": false, + "dst_asn": 1501, + "ebgp_auth_key_type": 7, + "ebgp_password": 17043717, + "ebpg_password_enable": true, + "inherit_from_msd": false, + "ipv4_subnet": "193.168.50.1/24", + "max_paths": 1, + "mtu": 5216, + "neighbor_ip": "193.168.50.2", + "peer1_description": "Description of source", + "peer2_description": "Description of dest", + "route_tag": 12345, + "src_asn": 1500 + }, + "src_device": "10.64.78.227", + "src_interface": "Ethernet1/7", + "template": "ext_multisite_underlay_setup" + }, + { + "dst_device": "10.64.78.225", + "dst_fabric": "test_net1", + "dst_interface": "Ethernet1/8", + "profile": { + "bgp_multihop": 6, + "dst_asn": 1601, + "ebgp_password": 17309829, + "ebgp_password_enable": true, + "ebpg_auth_key_type": 7, + "inherit_from_msd": false, + "ipv4_addr": "193.168.60.1", + "neighbor_ip": "193.168.60.2", + "src_asn": 1600, + "trm_enabled": true + }, + "src_device": "10.64.78.227", + "src_interface": "Ethernet1/8", + "template": "ext_evpn_multisite_overlay_setup" + } + ], + + "intra_modify_num_template_config": [ + { + "dst_device": "192.168.123.161", + "dst_fabric": "mmudigon-numbered", + "dst_interface": "Ethernet1/1", + "profile": { + "admin_state": true, + "peer1_ipv4_addr": "198.168.2.1", + "peer2_ipv4_addr": "198.168.2.2" + }, + "src_device": "192.168.123.160", + "src_interface": "Ethernet1/1", + "template": "ios_xe_int_intra_fabric_num_link" + } + ], + + "intra_modify_unnum_template_config": [ + { + "dst_device": "192.168.123.171", + "dst_fabric": "mmudigon-unnumbered", + "dst_interface": "Ethernet1/1", + "src_device": "192.168.123.170", + "src_interface": "Ethernet1/1", + "template": "int_pre_provision_intra_fabric_link" + } + ], + + "inter_modify_num_template_config": [ + { + "dst_device": "10.64.78.231", + "dst_fabric": "test_net", + "dst_interface": "Ethernet1/3", + "profile": { + "dst_asn": 1001, + "ipv4_addr": "193.168.3.1", + "neighbor_ip": "193.168.3.2", + "src_asn": 1000, + "trm_enabled": false + }, + "src_device": "10.64.78.227", + "src_interface": "Ethernet1/3", + "template": "ext_evpn_multisite_overlay_setup" + } + ], + + "intra_query_num_dest_fabric_config": [ + { + "dst_fabric": "mmudigon-numbered" + } + ], + + "intra_query_num_src_dev_config": [ + { + "src_device": "192.168.123.160", + "dst_fabric": "mmudigon-numbered" + } + ], + + "intra_query_num_dst_dev_config": [ + { + "dst_fabric": "mmudigon-numbered", + "src_device": "192.168.123.160", + "dst_device": "192.168.123.161" + } + ], + + "intra_query_num_src_intf_config": [ + { + "dst_fabric": "mmudigon-numbered", + "src_device": "192.168.123.160", + "dst_device": "192.168.123.161", + "src_interface": "Ethernet1/1" + } + ], + + "intra_query_num_dst_intf_config": [ + { + "dst_device": "192.168.123.161", + "dst_fabric": "mmudigon-numbered", + "src_interface": "Ethernet1/1", + "dst_interface": "Ethernet1/1", + "src_device": "192.168.123.160" + } + ], + + "intra_query_num_config": [ + { + "dst_device": "192.168.123.161", + "dst_fabric": "mmudigon-numbered", + "dst_interface": "Ethernet1/1", + "src_device": "192.168.123.160", + "src_interface": "Ethernet1/1", + "template": "int_intra_fabric_num_link" + }, + { + "dst_device": "192.168.123.161", + "dst_fabric": "mmudigon-numbered", + "dst_interface": "Ethernet1/2", + "src_device": "192.168.123.160", + "src_interface": "Ethernet1/2", + "template": "int_pre_provision_intra_fabric_link" + }, + { + "dst_device": "192.168.123.161", + "dst_fabric": "mmudigon-numbered", + "dst_interface": "Ethernet1/3", + "src_device": "192.168.123.160", + "src_interface": "Ethernet1/3", + "template": "ios_xe_int_intra_fabric_num_link" + } + ], + + "intra_query_vpc_dest_fabric_config": [ + { + "dst_fabric": "mmudigon" + } + ], + + "intra_query_vpc_src_dev_config": [ + { + "dst_fabric": "mmudigon", + "src_device": "192.168.123.150" + } + ], + + "intra_query_vpc_dst_dev_config": [ + { + "dst_device": "192.168.123.151", + "dst_fabric": "mmudigon", + "src_device": "192.168.123.150" + } + ], + + "intra_query_vpc_src_intf_config": [ + { + "dst_device": "192.168.123.151", + "dst_fabric": "mmudigon", + "src_device": "192.168.123.150", + "src_interface": "Ethernet1/4" + } + ], + + "intra_query_vpc_dst_intf_config": [ + { + "dst_device": "192.168.123.151", + "dst_fabric": "mmudigon", + "dst_interface": "Ethernet1/4", + "src_device": "192.168.123.150", + "src_interface": "Ethernet1/4" + } + ], + + "intra_query_vpc_config": [ + { + "dst_device": "192.168.123.151", + "dst_fabric": "mmudigon", + "dst_interface": "Ethernet1/4", + "src_device": "192.168.123.150", + "src_interface": "Ethernet1/4", + "template": "int_intra_vpc_peer_keep_alive_link" + } + ], + + "intra_query_unnum_dest_fabric_config": [ + { + "dst_fabric": "mmudigon-unnumbered" + } + ], + + "intra_query_unnum_src_dev_config": [ + { + "dst_fabric": "mmudigon-unnumbered", + "src_device": "192.168.123.170" + } + ], + + "intra_query_unnum_dst_dev_config": [ + { + "dst_device": "192.168.123.171", + "dst_fabric": "mmudigon-unnumbered", + "src_device": "192.168.123.170" + } + ], + + "intra_query_unnum_src_intf_config": [ + { + "dst_device": "192.168.123.171", + "dst_fabric": "mmudigon-unnumbered", + "src_device": "192.168.123.170", + "src_interface": "Ethernet1/1" + } + ], + + "intra_query_unnum_dst_intf_config": [ + { + "dst_device": "192.168.123.171", + "dst_fabric": "mmudigon-unnumbered", + "dst_interface": "Ethernet1/1", + "src_device": "192.168.123.170", + "src_interface": "Ethernet1/1" + } + ], + + "intra_query_unnum_config": [ + { + "dst_device": "192.168.123.171", + "dst_fabric": "mmudigon-unnumbered", + "dst_interface": "Ethernet1/1", + "src_device": "192.168.123.170", + "src_interface": "Ethernet1/1", + "template": "int_intra_fabric_unnum_link" + }, + { + "dst_device": "192.168.123.171", + "dst_fabric": "mmudigon-unnumbered", + "dst_interface": "Ethernet1/2", + "src_device": "192.168.123.170", + "src_interface": "Ethernet1/2", + "template": "int_pre_provision_intra_fabric_link" + } + ], + + "intra_query_unnum_not_exist": [ + { + "dst_device": "192.168.123.171", + "dst_fabric": "mmudigon-unnumbered", + "dst_interface": "Ethernet1/12", + "src_device": "192.168.123.170", + "src_interface": "Ethernet11/1", + "template": "int_pre_provision_intra_fabric_link" + }], + + "intra_query_ipv6_dest_fabric_config": [ + { + "dst_fabric": "mmudigon-ipv6-underlay" + } + ], + + "intra_query_ipv6_src_dev_config": [ + { + "dst_fabric": "mmudigon-ipv6-underlay", + "src_device": "192.168.123.156" + } + ], + + "intra_query_ipv6_dst_dev_config": [ + { + "dst_device": "192.168.123.157", + "dst_fabric": "mmudigon-ipv6-underlay", + "src_device": "192.168.123.156" + } + ], + + "intra_query_ipv6_src_intf_config": [ + { + "dst_device": "192.168.123.157", + "dst_fabric": "mmudigon-ipv6-underlay", + "src_device": "192.168.123.156", + "src_interface": "Ethernet1/1" + } + ], + + "intra_query_ipv6_dst_intf_config": [ + { + "dst_device": "192.168.123.157", + "dst_fabric": "mmudigon-ipv6-underlay", + "dst_interface": "Ethernet1/1", + "src_device": "192.168.123.156", + "src_interface": "Ethernet1/1" + } + ], + + "intra_query_ipv6_config": [ + { + "dst_device": "192.168.123.157", + "dst_fabric": "mmudigon-ipv6-underlay", + "dst_interface": "Ethernet1/1", + "src_device": "192.168.123.156", + "src_interface": "Ethernet1/1", + "template": "int_intra_fabric_ipv6_link_local" + }, + { + "dst_device": "192.168.123.157", + "dst_fabric": "mmudigon-ipv6-underlay", + "dst_interface": "Ethernet1/2", + "src_device": "192.168.123.156", + "src_interface": "Ethernet1/2", + "template": "int_pre_provision_intra_fabric_link" + }, + { + "dst_device": "192.168.123.157", + "dst_fabric": "mmudigon-ipv6-underlay", + "dst_interface": "Ethernet1/3", + "src_device": "192.168.123.156", + "src_interface": "Ethernet1/3", + "template": "int_intra_fabric_num_link" + } + ], + + "intra_query_ipv6_not_exist": [ + { + "dst_device": "192.168.123.171", + "dst_fabric": "mmudigon-unnumbered", + "dst_interface": "Ethernet1/12", + "src_device": "192.168.123.170", + "src_interface": "Ethernet11/1", + "template": "int_pre_provision_intra_fabric_link" + }], + + "inter_query_num_dest_fabric_config": [ + { + "dst_fabric": "test_net" + } + ], + + "inter_query_num_src_intf_config": [ + { + "dst_fabric": "test_net", + "src_interface": "Ethernet1/3" + } + ], + + "inter_query_num_dst_intf_config": [ + { + "dst_fabric": "test_net", + "dst_interface": "Ethernet1/3", + "src_interface": "Ethernet1/3" + } + ], + + "inter_query_num_src_dev_config": [ + { + "dst_fabric": "test_net", + "dst_interface": "Ethernet1/3", + "src_device": "10.64.78.227", + "src_interface": "Ethernet1/3" + } + ], + + "inter_query_num_dst_dev_config": [ + { + "dst_device": "10.64.78.231", + "dst_fabric": "test_net", + "dst_interface": "Ethernet1/3", + "src_device": "10.64.78.227", + "src_interface": "Ethernet1/3" + } + ], + + "inter_query_num_config": [ + { + "dst_device": "10.64.78.231", + "dst_fabric": "test_net", + "dst_interface": "Ethernet1/3", + "src_device": "10.64.78.227", + "src_interface": "Ethernet1/3", + "template": "ext_fabric_setup" + }, + { + "dst_device": "10.64.78.231", + "dst_fabric": "test_net", + "dst_interface": "Ethernet1/4", + "src_device": "10.64.78.227", + "src_interface": "Ethernet1/4", + "template": "ext_multisite_underlay_setup" + }, + { + "dst_device": "10.64.78.231", + "dst_fabric": "test_net", + "dst_interface": "Ethernet1/5", + "src_device": "10.64.78.227", + "src_interface": "Ethernet1/5", + "template": "ext_evpn_multisite_overlay_setup" + } + ], + + "inter_query_num_not_exist": [ + { + "dst_device": "10.64.78.231", + "dst_fabric": "test_net", + "dst_interface": "Ethernet12/3", + "src_device": "10.64.78.227", + "src_interface": "Ethernet11/3", + "template": "ext_fabric_setup" + } + ], + + "intra_invalid_template_config": [ + { + "dst_device": "192.168.123.161", + "dst_fabric": "mmudigon-unnumbered", + "dst_interface": "Ethernet1/1", + "profile": { + "admin_state": true, + "mtu": 9216, + "peer1_ipv4_addr": "192.168.1.1", + "peer2_ipv4_addr": "192.168.1.2" + }, + "src_device": "192.168.123.160", + "src_interface": "Ethernet1/1", + "template": "int_intra_fabric_invalid_temp" + } + ], + + "intra_missing_src_fabric_config": [ + { + "dst_device": "192.168.123.161", + "dst_fabric": "mmudigon-numbered", + "dst_interface": "Ethernet1/1", + "profile": { + "admin_state": true, + "mtu": 9216, + "peer1_ipv4_addr": "192.168.1.1", + "peer2_ipv4_addr": "192.168.1.2" + }, + "src_device": "192.168.123.160", + "src_interface": "Ethernet1/1", + "template": "int_intra_fabric_num_link" + } + ], + + "intra_missing_dst_fabric_config": [ + { + "dst_device": "192.168.123.161", + "dst_interface": "Ethernet1/1", + "profile": { + "admin_state": true, + "mtu": 9216, + "peer1_ipv4_addr": "192.168.1.1", + "peer2_ipv4_addr": "192.168.1.2" + }, + "src_device": "192.168.123.160", + "src_interface": "Ethernet1/1", + "template": "int_intra_fabric_num_link" + } + ], + + "intra_missing_src_intf_config": [ + { + "dst_device": "192.168.123.161", + "dst_fabric": "mmudigon-numbered", + "dst_interface": "Ethernet1/1", + "profile": { + "admin_state": true, + "mtu": 9216, + "peer1_ipv4_addr": "192.168.1.1", + "peer2_ipv4_addr": "192.168.1.2" + }, + "src_device": "192.168.123.160", + "template": "int_intra_fabric_num_link" + } + ], + + "intra_missing_dst_intf_config": [ + { + "dst_device": "192.168.123.161", + "dst_fabric": "mmudigon-numbered", + "src_device": "192.168.123.160", + "src_interface": "Ethernet1/1", + "template": "int_pre_provision_intra_fabric_link" + } + ], + + "intra_missing_src_device_config": [ + { + "dst_device": "192.168.123.161", + "dst_fabric": "mmudigon-numbered", + "dst_interface": "Ethernet1/1", + "profile": { + "admin_state": true, + "mtu": 1500, + "peer1_ipv4_addr": "192.168.1.1", + "peer1_ipv6_addr": "fe80:21::01", + "peer2_ipv4_addr": "192.168.1.2", + "peer2_ipv6_addr": "fe80:21::02" + }, + "src_interface": "Ethernet1/1", + "template": "ios_xe_int_intra_fabric_num_link" + } + ], + + "intra_missing_dst_device_config": [ + { + "dst_fabric": "mmudigon-numbered", + "dst_interface": "Ethernet1/1", + "profile": { + "admin_state": true, + "mtu": 9216, + "peer1_ipv4_addr": "192.168.1.1", + "peer1_ipv6_addr": "fe80:11::01", + "peer2_ipv4_addr": "192.168.1.2", + "peer2_ipv6_addr": "fe80:11::02" + }, + "src_device": "192.168.123.160", + "src_interface": "Ethernet1/1", + "template": "int_intra_vpc_peer_keep_alive_link" + } + ], + + "intra_missing_template_config": [ + { + "dst_device": "192.168.123.161", + "dst_fabric": "mmudigon-numbered", + "dst_interface": "Ethernet1/1", + "profile": { + "admin_state": true, + "mtu": 9216, + "peer1_ipv4_addr": "192.168.1.1", + "peer1_ipv6_addr": "fe80:11::01", + "peer2_ipv4_addr": "192.168.1.2", + "peer2_ipv6_addr": "fe80:11::02" + }, + "src_device": "192.168.123.160", + "src_interface": "Ethernet1/1" + } + ], + + "intra_missing_peer1_ipv6_config": [ + { + "dst_device": "192.168.123.157", + "dst_fabric": "mmudigon-ipv6-underlay", + "dst_interface": "Ethernet1/1", + "profile": { + "admin_state": true, + "mtu": 9216, + "peer1_ipv4_addr": "192.168.1.1", + "peer2_ipv4_addr": "192.168.1.2", + "peer2_ipv6_addr": "fe80:11::02" + }, + "src_device": "192.168.123.156", + "src_interface": "Ethernet1/1", + "template": "int_intra_vpc_peer_keep_alive_link" + } + ], + + "intra_missing_peer2_ipv6_config": [ + { + "dst_device": "192.168.123.157", + "dst_fabric": "mmudigon-ipv6-underlay", + "dst_interface": "Ethernet1/1", + "profile": { + "admin_state": true, + "mtu": 9216, + "peer1_ipv4_addr": "192.168.1.1", + "peer1_ipv6_addr": "fe80:11::01", + "peer2_ipv4_addr": "192.168.1.2" + }, + "src_device": "192.168.123.156", + "src_interface": "Ethernet1/1", + "template": "int_intra_vpc_peer_keep_alive_link" + } + ], + + "intra_missing_mtu_config": [ + { + "dst_device": "192.168.123.161", + "dst_fabric": "mmudigon-numbered", + "dst_interface": "Ethernet1/1", + "profile": { + "admin_state": true, + "peer1_ipv4_addr": "192.168.1.1", + "peer1_ipv6_addr": "fe80:11::01", + "peer2_ipv4_addr": "192.168.1.2", + "peer2_ipv6_addr": "fe80:11::02" + }, + "src_device": "192.168.123.160", + "src_interface": "Ethernet1/1", + "template": "int_intra_vpc_peer_keep_alive_link" + } + ], + + "intra_missing_peer1_ipv4_config": [ + { + "dst_device": "192.168.123.161", + "dst_fabric": "mmudigon-numbered", + "dst_interface": "Ethernet1/1", + "profile": { + "admin_state": true, + "mtu": 9216, + "peer1_ipv6_addr": "fe80:11::01", + "peer2_ipv4_addr": "192.168.1.2", + "peer2_ipv6_addr": "fe80:11::02" + }, + "src_device": "192.168.123.160", + "src_interface": "Ethernet1/1", + "template": "int_intra_vpc_peer_keep_alive_link" + } + ], + + "intra_missing_peer2_ipv4_config": [ + { + "dst_device": "192.168.123.161", + "dst_fabric": "mmudigon-numbered", + "dst_interface": "Ethernet1/1", + "profile": { + "admin_state": true, + "mtu": 9216, + "peer1_ipv4_addr": "192.168.1.1", + "peer1_ipv6_addr": "fe80:11::01", + "peer2_ipv6_addr": "fe80:11::02" + }, + "src_device": "192.168.123.160", + "src_interface": "Ethernet1/1", + "template": "int_intra_vpc_peer_keep_alive_link" + } + ], + + "intra_missing_admin_state_config": [ + { + "dst_device": "192.168.123.161", + "dst_fabric": "mmudigon-numbered", + "dst_interface": "Ethernet1/1", + "profile": { + "mtu": 9216, + "peer1_ipv4_addr": "192.168.1.1", + "peer1_ipv6_addr": "fe80:11::01", + "peer2_ipv4_addr": "192.168.1.2", + "peer2_ipv6_addr": "fe80:11::02" + }, + "src_device": "192.168.123.160", + "src_interface": "Ethernet1/1", + "template": "int_intra_vpc_peer_keep_alive_link" + } + ], + + "inter_invalid_template_config": [ + { + "dst_device": "192.168.123.161", + "dst_fabric": "mmudigon-unnumbered", + "dst_interface": "Ethernet1/1", + "profile": { + "admin_state": true, + "mtu": 9216 + }, + "src_device": "192.168.123.160", + "src_interface": "Ethernet1/1", + "template": "int_inter_fabric_invalid_temp" + } + ], + + "inter_missing_src_fabric_config": [ + { + "dst_device": "192.168.123.161", + "dst_fabric": "mmudigon-unnumbered", + "dst_interface": "Ethernet1/1", + "profile": { + "dst_asn": 1001, + "ipv4_subnet": "193.168.1.1/24", + "neighbor_ip": "193.168.1.2", + "src_asn": 1000 + }, + "src_device": "192.168.123.160", + "src_interface": "Ethernet1/1", + "template": "ext_fabric_setup" + } + ], + + "inter_missing_dst_fabric_config": [ + { + "dst_device": "192.168.123.161", + "dst_interface": "Ethernet1/1", + "profile": { + "dst_asn": 1001, + "ipv4_subnet": "193.168.1.1/24", + "neighbor_ip": "193.168.1.2", + "src_asn": 1000 + }, + "src_device": "192.168.123.160", + "src_interface": "Ethernet1/1", + "template": "ext_fabric_setup" + } + ], + + "inter_missing_src_intf_config": [ + { + "dst_device": "192.168.123.161", + "dst_fabric": "mmudigon-unnumbered", + "dst_interface": "Ethernet1/1", + "profile": { + "dst_asn": 1001, + "ipv4_subnet": "193.168.1.1/24", + "neighbor_ip": "193.168.1.2", + "src_asn": 1000 + }, + "src_device": "192.168.123.160", + "template": "ext_fabric_setup" + } + ], + + "inter_missing_dst_intf_config": [ + { + "dst_device": "192.168.123.161", + "dst_fabric": "mmudigon-unnumbered", + "src_device": "192.168.123.160", + "src_interface": "Ethernet1/1", + "profile": { + "dst_asn": 1001, + "ipv4_subnet": "193.168.1.1/24", + "neighbor_ip": "193.168.1.2", + "src_asn": 1000 + }, + "template": "ext_fabric_setup" + } + ], + + "inter_missing_src_device_config": [ + { + "dst_device": "192.168.123.161", + "dst_fabric": "mmudigon-unnumbered", + "dst_interface": "Ethernet1/1", + "profile": { + "dst_asn": 1001, + "ipv4_subnet": "193.168.1.1/24", + "neighbor_ip": "193.168.1.2", + "src_asn": 1000 + }, + "src_interface": "Ethernet1/1", + "template": "ext_fabric_setup" + } + ], + + "inter_missing_dst_device_config": [ + { + "dst_fabric": "mmudigon-unnumbered", + "dst_interface": "Ethernet1/1", + "profile": { + "dst_asn": 1001, + "ipv4_subnet": "193.168.1.1/24", + "neighbor_ip": "193.168.1.2", + "src_asn": 1000 + }, + "src_device": "192.168.123.160", + "src_interface": "Ethernet1/1", + "template": "ext_fabric_setup" + } + ], + + "inter_missing_template_config": [ + { + "dst_device": "192.168.123.161", + "dst_fabric": "mmudigon-unnumbered", + "dst_interface": "Ethernet1/1", + "profile": { + "dst_asn": 1001, + "ipv4_subnet": "193.168.1.1/24", + "neighbor_ip": "193.168.1.2", + "src_asn": 1000 + }, + "src_device": "192.168.123.160", + "src_interface": "Ethernet1/1" + } + ], + + "inter_missing_ipv4_subnet_config": [ + { + "dst_device": "192.168.123.157", + "dst_fabric": "mmudigon-ipv6-underlay", + "dst_interface": "Ethernet1/1", + "profile": { + "dst_asn": 1001, + "neighbor_ip": "193.168.1.2", + "src_asn": 1000 + }, + "src_device": "192.168.123.156", + "src_interface": "Ethernet1/1", + "template": "ext_fabric_setup" + } + ], + + "inter_missing_neighbor_ip_config": [ + { + "dst_device": "192.168.123.157", + "dst_fabric": "mmudigon-ipv6-underlay", + "dst_interface": "Ethernet1/1", + "profile": { + "dst_asn": 1001, + "ipv4_subnet": "193.168.1.1/24", + "src_asn": 1000 + }, + "src_device": "192.168.123.156", + "src_interface": "Ethernet1/1", + "template": "ext_fabric_setup" + } + ], + + "inter_missing_src_asn_config": [ + { + "dst_device": "192.168.123.161", + "dst_fabric": "mmudigon-unnumbered", + "dst_interface": "Ethernet1/1", + "profile": { + "dst_asn": 1001, + "ipv4_subnet": "193.168.1.1/24", + "neighbor_ip": "193.168.1.2" + }, + "src_device": "192.168.123.160", + "src_interface": "Ethernet1/1", + "template": "ext_fabric_setup" + } + ], + + "inter_missing_dst_asn_config": [ + { + "dst_device": "192.168.123.161", + "dst_fabric": "mmudigon-unnumbered", + "dst_interface": "Ethernet1/1", + "profile": { + "ipv4_subnet": "193.168.1.1/24", + "neighbor_ip": "193.168.1.2", + "src_asn": 1000 + }, + "src_device": "192.168.123.160", + "src_interface": "Ethernet1/1", + "template": "ext_fabric_setup" + } + ], + + "inter_missing_ipv4_addr_config": [ + { + "dst_device": "192.168.123.161", + "dst_fabric": "mmudigon-unnumbered", + "dst_interface": "Ethernet1/1", + "profile": { + "dst_asn": 1301, + "neighbor_ip": "193.168.3.2", + "src_asn": 1300 + }, + "src_device": "192.168.123.160", + "src_interface": "Ethernet1/1", + "template": "ext_evpn_multisite_overlay_setup" + } + ] +} diff --git a/tests/unit/modules/dcnm/fixtures/dcnm_links_payloads.json b/tests/unit/modules/dcnm/fixtures/dcnm_links_payloads.json new file mode 100644 index 000000000..9c1c343c4 --- /dev/null +++ b/tests/unit/modules/dcnm/fixtures/dcnm_links_payloads.json @@ -0,0 +1,1353 @@ +{ + "mock_num_fab_data": { + "nvPairs": { + "UNDERLAY_IS_V6": "False" + } + }, + + "mock_unnum_fab_data": { + "nvPairs": { + "UNDERLAY_IS_V6": "False" + } + }, + + "mock_ipv6_fab_data": { + "nvPairs": { + "UNDERLAY_IS_V6": "True" + } + }, + + "mock_fab_inv_data": { + "192.168.123.150": { + "isVpcConfigured": "True", + "vpcDomain": 1 + }, + "192.168.123.151": { + "isVpcConfigured": "True", + "vpcDomain": 1 + }, + "192.168.123.156": { + "isVpcConfigured": "True", + "vpcDomain": 1 + }, + "192.168.123.157": { + "isVpcConfigured": "True", + "vpcDomain": 1 + }, + "192.168.123.160": { + "isVpcConfigured": "True", + "vpcDomain": 1 + }, + "192.168.123.161": { + "isVpcConfigured": "True", + "vpcDomain": 1 + }, + "192.168.123.170": { + "isVpcConfigured": "True", + "vpcDomain": 1 + }, + "192.168.123.171": { + "isVpcConfigured": "True", + "vpcDomain": 1 + }, + "10.64.78.225": { + "isVpcConfigured": "True", + "vpcDomain": 1 + }, + "10.64.78.226": { + "isVpcConfigured": "True", + "vpcDomain": 1 + }, + "10.64.78.227": { + "isVpcConfigured": "True", + "vpcDomain": 1 + }, + "10.64.78.228": { + "isVpcConfigured": "True", + "vpcDomain": 1 + }, + "10.64.78.230": { + "isVpcConfigured": "True", + "vpcDomain": 1 + }, + "10.64.78.231": { + "isVpcConfigured": "True", + "vpcDomain": 1 + } + }, + + "mock_ip_sn" : { + "192.168.123.150": "9M99N34RDED", + "192.168.123.151": "9NXHSNTEO6C", + "192.168.123.156": "9BH0813WFWT", + "192.168.123.157": "9ITWBH9OIAH", + "192.168.123.160": "9IF87L089SZ", + "192.168.123.161": "9FX7O3TU2QM", + "192.168.123.170": "9EFX823RUL3", + "192.168.123.171": "9AF3VNZYAKS", + "10.64.78.225": "98YWRN9WCSC", + "10.64.78.226": "94UTIRVSX58", + "10.64.78.227": "953E68OKK1L", + "10.64.78.228": "9WCPR0JUV6M", + "10.64.78.230": "9XLP8I4TPPM", + "10.64.78.231": "9E15XSEM5MS" + }, + + "mock_hn_sn" : { + "n9kv_100": "9M99N34RDED", + "n9kv-200": "9NXHSNTEO6C", + "n9kv-ipv6-1": "9BH0813WFWT", + "n9kv-ipv6-2": "9ITWBH9OIAH", + "n9kv-num-1": "9IF87L089SZ", + "n9kv-num-2": "9FX7O3TU2QM", + "n9kv-unnum-1": "9EFX823RUL3", + "n9kv-unnum-2": "9AF3VNZYAKS", + "n9kv-test1": "10.64.78.225", + "n9kv-test2": "10.64.78.226", + "n9kv-227": "10.64.78.227", + "n9kv-228": "10.64.78.228", + "test11": "10.64.78.230", + "test22": "10.64.78.231" + }, + + "mock_sn_hn" : { + "9M99N34RDED": "n9kv-100", + "9NXHSNTEO6C": "n9kv-200", + "9BH0813WFWT": "n9kv-ipv6-1", + "9ITWBH9OIAH": "n9kv-ipv6-2", + "9IF87L089SZ": "n9kv-num-1", + "9FX7O3TU2QM": "n9kv-num-2", + "9EFX823RUL3": "n9kv-unnum-1", + "9AF3VNZYAKS": "n9kv-unnum-2", + "98YWRN9WCSC": "n9kv-test1", + "94UTIRVSX58": "n9kv-test2", + "953E68OKK1L": "n9kv-227", + "9WCPR0JUV6M": "n9kv-228", + "9XLP8I4TPPM": "test11", + "9E15XSEM5MS": "test22" + }, + + "deploy_resp": { + "RETURN_CODE": 200, + "METHOD": "POST", + "MESSAGE": "OK", + "DATA": { + "status": "Config deployment has been triggered" + } + }, + + "config_preview_resp": { + "RETURN_CODE": 200, + "METHOD": "GET", + "MESSAGE": "OK", + "DATA": [ + { + "switchId": "9M99N34RDED", + "status": "In-Sync" + }, + { + "switchId": "9NXHSNTEO6C", + "status": "In-Sync" + }, + { + "switchId": "9BH0813WFWT", + "status": "In-Sync" + }, + { + "switchId": "9ITWBH9OIAH", + "status": "In-Sync" + }, + { + "switchId": "9IF87L089SZ", + "status": "In-Sync" + }, + { + "switchId": "9FX7O3TU2QM", + "status": "In-Sync" + }, + { + "switchId": "9EFX823RUL3", + "status": "In-Sync" + }, + { + "switchId": "9AF3VNZYAKS", + "status": "In-Sync" + }, + { + "switchId": "98YWRN9WCSC", + "status": "In-Sync" + }, + { + "switchId": "94UTIRVSX58", + "status": "In-Sync" + }, + { + "switchId": "953E68OKK1L", + "status": "In-Sync" + }, + { + "switchId": "9WCPR0JUV6M", + "status": "In-Sync" + }, + { + "switchId": "9XLP8I4TPPM", + "status": "In-Sync" + }, + { + "switchId": "9E15XSEM5MS", + "status": "In-Sync" + } + ] + }, + + "intra_have_link1_num_fabric_response": + { + "RETURN_CODE": 200, + "METHOD": "GET", + "REQUEST_PATH": "https://10.195.225.193:443/appcenter/cisco/ndfc/api/v1/lan-fabric/rest/control/links?switch1Sn=9IF87L089SZ&switch2Sn=9FX7O3TU2QM&switch1IfName=Ethernet1/1&switch2IfName=Ethernet1/1", + "MESSAGE": "OK", + "DATA": { + "link-uuid": "LINK-UUID-1446600", + "sw2-info": { + "fabric-name": "mmudigon-numbered", + "if-name": "Ethernet1/1", + "sw-serial-number": "9FX7O3TU2QM" + }, + "link-dbid": 1856090, + "nvPairs": { + "PEER2_BFD_ECHO_DISABLE": "false", + "PEER2_DESC": "Description of dest", + "ENABLE_MACSEC": "false", + "PEER2_INTF": "Ethernet1/1", + "PEER1_INTF": "Ethernet1/1", + "FABRIC2": "mmudigon-numbered", + "PRIORITY": "500", + "LINK_UUID": "LINK-UUID-1446600", + "FABRIC1": "mmudigon-numbered", + "PEER1_DESC": "Description of source", + "PEER2_SN": "9FX7O3TU2QM", + "PEER1_SN": "9IF87L089SZ", + "MTU": "9216", + "PEER2_IP": "192.168.1.2", + "PEER2_CONF": "no shutdown", + "PEER1_IP": "192.168.1.1", + "PEER1_BFD_ECHO_DISABLE": "false", + "ADMIN_STATE": "true", + "PEER1_CONF": "no shutdown", + "HOSTNAME1": "Switch1", + "HOSTNAME2": "Switch2" + }, + "templateName": "int_intra_fabric_num_link", + "sw1-info": { + "fabric-name": "mmudigon-numbered", + "if-name": "Ethernet1/1", + "sw-serial-number": "9IF87L089SZ" + }, + "fabricName": "mmudigon-numbered" + } + }, + + "intra_have_link2_num_fabric_response": + { + "RETURN_CODE": 200, + "METHOD": "GET", + "REQUEST_PATH": "https://10.195.225.193:443/appcenter/cisco/ndfc/api/v1/lan-fabric/rest/control/links?switch1Sn=9IF87L089SZ&switch2Sn=9FX7O3TU2QM&switch1IfName=Ethernet1/2&switch2IfName=Ethernet1/2", + "MESSAGE": "OK", + "DATA": { + "link-uuid": "LINK-UUID-1419150", + "sw2-info": { + "fabric-name": "mmudigon-numbered", + "if-name": "Ethernet1/2", + "sw-serial-number": "9FX7O3TU2QM" + }, + "link-dbid": 1856250, + "nvPairs": { + "LINK_UUID": "LINK-UUID-1419150" + }, + "templateName": "int_pre_provision_intra_fabric_link", + "sw1-info": { + "fabric-name": "mmudigon-numbered", + "if-name": "Ethernet1/2", + "sw-serial-number": "9IF87L089SZ" + }, + "fabricName": "mmudigon-numbered" + } + }, + + "intra_have_link3_num_fabric_response": + { + "RETURN_CODE": 200, + "METHOD": "GET", + "REQUEST_PATH": "https://10.195.225.193:443/appcenter/cisco/ndfc/api/v1/lan-fabric/rest/control/links?switch1Sn=9IF87L089SZ&switch2Sn=9FX7O3TU2QM&switch1IfName=Ethernet1/3&switch2IfName=Ethernet1/3", + "MESSAGE": "OK", + "DATA": { + "link-uuid": "LINK-UUID-1419160", + "sw2-info": { + "fabric-name": "mmudigon-numbered", + "if-name": "Ethernet1/3", + "sw-serial-number": "9FX7O3TU2QM" + }, + "nvPairs": { + "PEER2_DESC": "Description of dest", + "PEER2_INTF": "Ethernet1/3", + "PEER1_INTF": "Ethernet1/3", + "FABRIC2": "mmudigon-numbered", + "LINK_UUID": "LINK-UUID-1419160", + "FABRIC1": "mmudigon-numbered", + "PEER1_DESC": "Description of source", + "PEER2_SN": "9FX7O3TU2QM", + "MTU": "1500", + "PEER2_IP": "192.168.2.2", + "PEER2_CONF": "no shutdown", + "PEER1_IP": "192.168.2.1", + "ADMIN_STATE": "true", + "PEER1_CONF": "no shutdown", + "HOSTNAME1": "Switch1", + "HOSTNAME2": "Switch2" + }, + "templateName": "ios_xe_int_intra_fabric_num_link", + "sw1-info": { + "fabric-name": "mmudigon-numbered", + "if-name": "Ethernet1/3", + "sw-serial-number": "9IF87L089SZ" + }, + "fabricName": "mmudigon-numbered" + } + }, + + "intra_have_link1_unnum_fabric_response": + { + "RETURN_CODE": 200, + "METHOD": "GET", + "REQUEST_PATH": "https://10.195.225.193:443/appcenter/cisco/ndfc/api/v1/lan-fabric/rest/control/links?switch1Sn=9EFX823RUL3&switch2Sn=9AF3VNZYAKS&switch1IfName=Ethernet1/1&switch2IfName=Ethernet1/1", + "MESSAGE": "OK", + "DATA": { + "link-uuid": "LINK-UUID-1435300", + "sw2-info": { + "fabric-name": "mmudigon-unnumbered", + "if-name": "Ethernet1/1", + "sw-serial-number": "9AF3VNZYAKS" + }, + "nvPairs": { + "PEER2_DESC": "Description of dest", + "ENABLE_MACSEC": "false", + "PEER2_INTF": "Ethernet1/1", + "PEER1_INTF": "Ethernet1/1", + "FABRIC2": "mmudigon-unnumbered", + "LINK_UUID": "LINK-UUID-1435300", + "FABRIC1": "mmudigon-unnumbered", + "PEER1_DESC": "Description of source", + "PEER2_SN": "9AF3VNZYAKS", + "PEER1_SN": "9EFX823RUL3", + "MTU": "9216", + "PEER2_CONF": "no shutdown", + "ADMIN_STATE": "true", + "PEER1_CONF": "no shutdown", + "POLICY_DESC": "", + "HOSTNAME1": "Switch1", + "HOSTNAME2": "Switch2" + }, + "templateName": "int_intra_fabric_unnum_link", + "sw1-info": { + "fabric-name": "mmudigon-unnumbered", + "if-name": "Ethernet1/1", + "sw-serial-number": "9EFX823RUL3" + }, + "fabricName": "mmudigon-unnumbered" + } + }, + + "intra_have_link2_unnum_fabric_response": + { + "RETURN_CODE": 200, + "METHOD": "GET", + "REQUEST_PATH": "https://10.195.225.193:443/appcenter/cisco/ndfc/api/v1/lan-fabric/rest/control/links?switch1Sn=9EFX823RUL3&switch2Sn=9AF3VNZYAKS&switch1IfName=Ethernet1/2&switch2IfName=Ethernet1/2", + "MESSAGE": "OK", + "DATA": { + "link-uuid": "LINK-UUID-1446600", + "sw2-info": { + "fabric-name": "mmudigon-unnumbered", + "if-name": "Ethernet1/2", + "sw-serial-number": "9AF3VNZYAKS" + }, + "nvPairs": { + }, + "templateName": "int_pre_provision_intra_fabric_link", + "sw1-info": { + "fabric-name": "mmudigon-unnumbered", + "if-name": "Ethernet1/2", + "sw-serial-number": "9EFX823RUL3" + }, + "fabricName": "mmudigon-unnumbered" + } + }, + + "intra_have_link1_ipv6_fabric_response": + { + "RETURN_CODE": 200, + "METHOD": "GET", + "REQUEST_PATH": "https://10.195.225.193:443/appcenter/cisco/ndfc/api/v1/lan-fabric/rest/control/links?switch1Sn=9EFX823RUL3&switch2Sn=9AF3VNZYAKS&switch1IfName=Ethernet1/2&switch2IfName=Ethernet1/2", + "MESSAGE": "OK", + "DATA": { + "link-uuid": "LINK-UUID-1419050", + "sw2-info": { + "fabric-name": "mmudigon-ipv6-underlay", + "if-name": "Ethernet1/1", + "sw-serial-number": "9ITWBH9OIAH" + }, + "nvPairs": { + "PEER2_DESC": "Description of dest", + "ENABLE_MACSEC": "false", + "PEER2_INTF": "Ethernet1/1", + "PEER1_INTF": "Ethernet1/1", + "FABRIC2": "mmudigon-ipv6-underlay", + "LINK_UUID": "LINK-UUID-1419050", + "FABRIC1": "mmudigon-ipv6-underlay", + "PEER1_DESC": "Description of source", + "PEER2_SN": "9ITWBH9OIAH", + "PEER1_SN": "9BH0813WFWT", + "MTU": "9216", + "PEER2_CONF": "no shutdown", + "ADMIN_STATE": "true", + "PEER1_CONF": "no shutdown", + "POLICY_DESC": "", + "HOSTNAME1": "Switch1", + "HOSTNAME2": "Switch2" + }, + "templateName": "int_intra_fabric_ipv6_link_local", + "sw1-info": { + "fabric-name": "mmudigon-ipv6-underlay", + "if-name": "Ethernet1/1", + "sw-serial-number": "9BH0813WFWT" + }, + "fabricName": "mmudigon-ipv6-underlay" + } + }, + + "intra_have_link2_ipv6_fabric_response": + { + "RETURN_CODE": 200, + "METHOD": "GET", + "REQUEST_PATH": "https://10.195.225.193:443/appcenter/cisco/ndfc/api/v1/lan-fabric/rest/control/links?switch1Sn=9EFX823RUL3&switch2Sn=9AF3VNZYAKS&switch1IfName=Ethernet1/2&switch2IfName=Ethernet1/2", + "MESSAGE": "OK", + "DATA": { + "link-uuid": "LINK-UUID-1419110", + "sw2-info": { + "fabric-name": "mmudigon-ipv6-underlay", + "if-name": "Ethernet1/2", + "sw-serial-number": "9ITWBH9OIAH" + }, + "link-dbid": 1859550, + "nvPairs": { + }, + "templateName": "int_pre_provision_intra_fabric_link", + "sw1-info": { + "fabric-name": "mmudigon-ipv6-underlay", + "if-name": "Ethernet1/2", + "sw-serial-number": "9BH0813WFWT" + }, + "fabricName": "mmudigon-ipv6-underlay" + } + }, + + "intra_have_link3_ipv6_fabric_response": + { + "RETURN_CODE": 200, + "METHOD": "GET", + "REQUEST_PATH": "https://10.195.225.193:443/appcenter/cisco/ndfc/api/v1/lan-fabric/rest/control/links?switch1Sn=9EFX823RUL3&switch2Sn=9AF3VNZYAKS&switch1IfName=Ethernet1/2&switch2IfName=Ethernet1/2", + "MESSAGE": "OK", + "DATA": { + "link-uuid": "LINK-UUID-1419180", + "sw2-info": { + "fabric-name": "mmudigon-ipv6-underlay", + "if-name": "Ethernet1/3", + "sw-serial-number": "9ITWBH9OIAH" + }, + "nvPairs": { + "PEER2_BFD_ECHO_DISABLE": "false", + "PEER1_INTF": "Ethernet1/3", + "FABRIC2": "mmudigon-ipv6-underlay", + "FABRIC1": "mmudigon-ipv6-underlay", + "PEER1_DESC": "Description of source", + "PEER1_SN": "9BH0813WFWT", + "MTU": "1500", + "PEER1_IP": "", + "PEER1_CONF": "no shutdown", + "PEER2_V6IP": "fe80:0202::02", + "PEER2_DESC": "Description of dest", + "PEER1_V6IP": "fe80:0202::01", + "ENABLE_MACSEC": "false", + "PEER2_INTF": "Ethernet1/3", + "LINK_UUID": "LINK-UUID-1419180", + "PEER2_SN": "9ITWBH9OIAH", + "PEER2_IP": "", + "PEER2_CONF": "no shutdown", + "PEER1_BFD_ECHO_DISABLE": "false", + "ADMIN_STATE": "true", + "HOSTNAME1": "Switch1", + "HOSTNAME2": "Switch2" + }, + "templateName": "int_intra_fabric_num_link", + "sw1-info": { + "fabric-name": "mmudigon-ipv6-underlay", + "if-name": "Ethernet1/3", + "sw-serial-number": "9BH0813WFWT" + }, + "fabricName": "mmudigon-ipv6-underlay" + } + }, + + "intra_have_link1_vpc_num_fabric_response": + { + "RETURN_CODE": 200, + "METHOD": "GET", + "REQUEST_PATH": "https://10.195.225.193:443/appcenter/cisco/ndfc/api/v1/lan-fabric/rest/control/links?switch1Sn=9M99N34RDED&switch2Sn=9NXHSNTEO6C&switch1IfName=Ethernet1/4&switch2IfName=Ethernet1/4", + "MESSAGE": "OK", + "DATA": { + "link-uuid": "LINK-UUID-1419030", + "sw2-info": { + "fabric-name": "mmudigon", + "if-name": "Ethernet1/4", + "sw-serial-number": "9NXHSNTEO6C" + }, + "link-dbid": 1862750, + "nvPairs": { + "PEER2_DESC": "Description of dest", + "PEER2_INTF": "Ethernet1/4", + "PEER1_INTF": "Ethernet1/4", + "FABRIC2": "mmudigon", + "LINK_UUID": "LINK-UUID-1419030", + "FABRIC1": "mmudigon", + "PEER1_DESC": "Description of source", + "PEER2_SN": "9NXHSNTEO6C", + "PEER1_SN": "9M99N34RDED", + "MTU": "9216", + "PEER2_IP": "192.170.1.2", + "PEER2_CONF": "no shutdown", + "PEER1_IP": "192.170.1.1", + "ADMIN_STATE": "true", + "PEER1_CONF": "no shutdown", + "INTF_VRF": "test_vrf", + "HOSTNAME1": "Switch1", + "HOSTNAME2": "Switch2" + }, + "templateName": "int_intra_vpc_peer_keep_alive_link", + "sw1-info": { + "fabric-name": "mmudigon", + "if-name": "Ethernet1/4", + "sw-serial-number": "9M99N34RDED" + }, + "fabricName": "mmudigon" + } + }, + + "inter_have_link1_num_fabric_response": + { + "RETURN_CODE": 200, + "METHOD": "GET", + "REQUEST_PATH": "https://10.64.78.151:443/rest/control/links?switch1Sn=953E68OKK1L&switch2Sn=9E15XSEM5MS&switch1IfName=Ethernet1/3&switch2IfName=Ethernet1/3", + "MESSAGE": "OK", + "DATA": { + "link-uuid": "LINK-UUID-1446600", + "templateName": "ext_fabric_setup", + "sw2-info": { + "if-name": "Ethernet1/3", + "sw-serial-number": "9E15XSEM5MS", + "fabric-name": "test_net" + }, + "sw1-info": { + "fabric-name": "mmudigon-numbered", + "if-name": "Ethernet1/3", + "sw-serial-number": "953E68OKK1L" + }, + "nvPairs": { + "SOURCE_IF_NAME": "Ethernet1/3", + "PEER2_DESC": "Description of dest", + "SOURCE_FABRIC_NAME": "mmudigon-numbered", + "SOURCE_SWITCH_NAME": "n9kv-test3", + "NEIGHBOR_IP": "193.168.1.2", + "VRF_LITE_JYTHON_TEMPLATE": "Ext_VRF_Lite_Jython", + "PRIORITY": "500", + "AUTO_VRF_LITE_FLAG": "false", + "LINK_UUID": "LINK-UUID-1424200", + "DEST_FABRIC_NAME": "test_net", + "PEER1_DESC": "Description of source", + "IP_MASK": "193.168.1.1/24", + "DEST_SWITCH_NAME": "test22", + "MTU": "9216", + "NEIGHBOR_ASN": "1001", + "PEER2_CONF": "no shutdown", + "DEST_IF_NAME": "Ethernet1/3", + "DEST_SERIAL_NUMBER": "9E15XSEM5MS", + "SOURCE_SERIAL_NUMBER": "953E68OKK1L", + "PEER1_CONF": "no shutdown", + "asn": "1000" + } + } + }, + + "inter_have_link2_num_fabric_response": + { + "RETURN_CODE": 200, + "METHOD": "GET", + "REQUEST_PATH": "https://10.64.78.151:443/rest/control/links?switch1Sn=953E68OKK1L&switch2Sn=9E15XSEM5MS&switch1IfName=Ethernet1/4&switch2IfName=Ethernet1/4", + "MESSAGE": "OK", + "DATA": { + "link-uuid": "LINK-UUID-1446600", + "templateName": "ext_multisite_underlay_setup", + "sw2-info": { + "if-name": "Ethernet1/4", + "sw-serial-number": "9E15XSEM5MS", + "fabric-name": "test_net", + "sw-sys-name": "test22" + }, + "link-dbid": 1467700, + "sw1-info": { + "fabric-name": "mmudigon-numbered", + "if-name": "Ethernet1/4", + "sw-serial-number": "953E68OKK1L", + "sw-sys-name": "n9kv-test3" + }, + "nvPairs": { + "SOURCE_IF_NAME": "Ethernet1/4", + "SOURCE_FABRIC_NAME": "mmudigon-numbered", + "NEIGHBOR_IP": "193.168.2.2", + "PRIORITY": "500", + "DEST_FABRIC_NAME": "test_net", + "PEER1_DESC": "Description of source", + "IP_MASK": "193.168.2.1/24", + "MTU": "9216", + "NEIGHBOR_ASN": "1201", + "DEST_SERIAL_NUMBER": "9E15XSEM5MS", + "BGP_PASSWORD_ENABLE": "true", + "PEER1_CONF": "no shutdown", + "MAX_PATHS": "1", + "PEER2_DESC": "Description of dest", + "SOURCE_SWITCH_NAME": "n9kv-test3", + "LINK_UUID": "LINK-UUID-1424330", + "DEPLOY_DCI_TRACKING": "false", + "DEST_SWITCH_NAME": "test22", + "BGP_PASSWORD_INHERIT_FROM_MSD": "true", + "PEER2_CONF": "no shutdown", + "DEST_IF_NAME": "Ethernet1/4", + "SOURCE_SERIAL_NUMBER": "953E68OKK1L", + "asn": "1200", + "ROUTING_TAG": "12345" + } + } + }, + + "inter_have_link3_num_fabric_response": + { + "RETURN_CODE": 200, + "METHOD": "GET", + "REQUEST_PATH": "https://10.64.78.151:443/rest/control/links?switch1Sn=953E68OKK1L&switch2Sn=9E15XSEM5MS&switch1IfName=Ethernet1/5&switch2IfName=Ethernet1/5", + "MESSAGE": "OK", + "DATA": { + "link-uuid": "LINK-UUID-1446600", + "templateName": "ext_evpn_multisite_overlay_setup", + "sw2-info": { + "if-name": "Ethernet1/5", + "sw-serial-number": "9E15XSEM5MS", + "fabric-name": "test_net", + "sw-sys-name": "test22" + }, + "sw1-info": { + "fabric-name": "mmudigon-numbered", + "if-name": "Ethernet1/5", + "sw-serial-number": "953E68OKK1L", + "sw-sys-name": "n9kv-test3" + }, + "nvPairs": { + "TRM_ENABLED": "false", + "SOURCE_IF_NAME": "Ethernet1/5", + "BGP_AUTH_KEY_TYPE": "3", + "SOURCE_FABRIC_NAME": "mmudigon-numbered", + "SOURCE_SWITCH_NAME": "n9kv-test3", + "NEIGHBOR_IP": "193.168.3.2", + "LINK_UUID": "LINK-UUID-1424480", + "DEST_FABRIC_NAME": "test_net", + "BGP_PASSWORD": "17314053", + "DEST_SWITCH_NAME": "test22", + "NEIGHBOR_ASN": "1301", + "BGP_PASSWORD_INHERIT_FROM_MSD": "false", + "DEST_IF_NAME": "Ethernet1/5", + "DEST_SERIAL_NUMBER": "9E15XSEM5MS", + "BGP_MULTIHOP": "5", + "SOURCE_IP": "193.168.3.1", + "BGP_PASSWORD_ENABLE": "true", + "SOURCE_SERIAL_NUMBER": "953E68OKK1L", + "asn": "1300" + } + } + }, + + "inter_have_link4_num_fabric_response": + { + "RETURN_CODE": 200, + "METHOD": "GET", + "REQUEST_PATH": "https://10.64.78.151:443/rest/control/links?switch1Sn=953E68OKK1L&switch2Sn=98YWRN9WCSC&switch1IfName=Ethernet1/6&switch2IfName=Ethernet1/6", + "MESSAGE": "OK", + "DATA": { + "link-uuid": "LINK-UUID-1446600", + "templateName": "ext_fabric_setup", + "sw2-info": { + "if-name": "Ethernet1/6", + "sw-serial-number": "98YWRN9WCSC", + "fabric-name": "test_net1", + "sw-sys-name": "n9kv-test1" + }, + "sw1-info": { + "fabric-name": "mmudigon-numbered", + "if-name": "Ethernet1/6", + "sw-serial-number": "953E68OKK1L", + "sw-sys-name": "n9kv-test3" + }, + "nvPairs": { + "SOURCE_IF_NAME": "Ethernet1/6", + "PEER2_DESC": "Description of dest", + "SOURCE_FABRIC_NAME": "mmudigon-numbered", + "SOURCE_SWITCH_NAME": "n9kv-test3", + "NEIGHBOR_IP": "193.168.4.2", + "VRF_LITE_JYTHON_TEMPLATE": "Ext_VRF_Lite_Jython", + "AUTO_VRF_LITE_FLAG": "false", + "LINK_UUID": "LINK-UUID-1424530", + "DEST_FABRIC_NAME": "test_net1", + "PEER1_DESC": "Description of source", + "IP_MASK": "193.168.4.1/24", + "DEST_SWITCH_NAME": "n9kv-test1", + "MTU": "9216", + "NEIGHBOR_ASN": "1401", + "PEER2_CONF": "no shutdown", + "DEST_IF_NAME": "Ethernet1/6", + "DEST_SERIAL_NUMBER": "98YWRN9WCSC", + "SOURCE_SERIAL_NUMBER": "953E68OKK1L", + "PEER1_CONF": "no shutdown", + "asn": "1400" + } + } + }, + + "inter_have_link5_num_fabric_response": + { + "RETURN_CODE": 200, + "METHOD": "GET", + "REQUEST_PATH": "https://10.64.78.151:443/rest/control/links?switch1Sn=953E68OKK1L&switch2Sn=98YWRN9WCSC&switch1IfName=Ethernet1/7&switch2IfName=Ethernet1/7", + "MESSAGE": "OK", + "DATA": { + "link-uuid": "LINK-UUID-1446600", + "templateName": "ext_multisite_underlay_setup", + "sw2-info": { + "if-name": "Ethernet1/7", + "sw-serial-number": "98YWRN9WCSC", + "fabric-name": "test_net1", + "sw-sys-name": "n9kv-test1" + }, + "sw1-info": { + "fabric-name": "mmudigon-numbered", + "if-name": "Ethernet1/7", + "sw-serial-number": "953E68OKK1L", + "sw-sys-name": "n9kv-test3" + }, + "nvPairs": { + "SOURCE_IF_NAME": "Ethernet1/7", + "SOURCE_FABRIC_NAME": "mmudigon-numbered", + "NEIGHBOR_IP": "193.168.5.2", + "PRIORITY": "500", + "DEST_FABRIC_NAME": "test_net1", + "PEER1_DESC": "Description of source", + "IP_MASK": "193.168.5.1/24", + "MTU": "9216", + "NEIGHBOR_ASN": "1501", + "DEST_SERIAL_NUMBER": "98YWRN9WCSC", + "BGP_PASSWORD_ENABLE": "true", + "PEER1_CONF": "no shutdown", + "MAX_PATHS": "1", + "PEER2_DESC": "Description of dest", + "SOURCE_SWITCH_NAME": "n9kv-test3", + "LINK_UUID": "LINK-UUID-1424640", + "DEPLOY_DCI_TRACKING": "false", + "POLICY_ID": "POLICY-1468070", + "DEST_SWITCH_NAME": "n9kv-test1", + "BGP_PASSWORD_INHERIT_FROM_MSD": "true", + "PEER2_CONF": "no shutdown", + "DEST_IF_NAME": "Ethernet1/7", + "SOURCE_SERIAL_NUMBER": "953E68OKK1L", + "asn": "1500", + "ROUTING_TAG": "12345" + } + } + }, + + "inter_have_link6_num_fabric_response": + { + "RETURN_CODE": 200, + "METHOD": "GET", + "REQUEST_PATH": "https://10.64.78.151:443/rest/control/links?switch1Sn=953E68OKK1L&switch2Sn=98YWRN9WCSC&switch1IfName=Ethernet1/8&switch2IfName=Ethernet1/8", + "MESSAGE": "OK", + "DATA": { + "link-uuid": "LINK-UUID-1446600", + "templateName": "ext_evpn_multisite_overlay_setup", + "sw2-info": { + "if-name": "Ethernet1/8", + "sw-serial-number": "98YWRN9WCSC", + "fabric-name": "test_net1", + "sw-sys-name": "n9kv-test1" + }, + "sw1-info": { + "fabric-name": "mmudigon-numbered", + "if-name": "Ethernet1/8", + "sw-serial-number": "953E68OKK1L", + "sw-sys-name": "n9kv-test3" + }, + "nvPairs": { + "TRM_ENABLED": "false", + "SOURCE_IF_NAME": "Ethernet1/8", + "BGP_AUTH_KEY_TYPE": "3", + "SOURCE_FABRIC_NAME": "mmudigon-numbered", + "SOURCE_SWITCH_NAME": "n9kv-test3", + "NEIGHBOR_IP": "193.168.6.2", + "LINK_UUID": "LINK-UUID-1424790", + "DEST_FABRIC_NAME": "test_net1", + "BGP_PASSWORD": "17314053", + "DEST_SWITCH_NAME": "n9kv-test1", + "NEIGHBOR_ASN": "1601", + "BGP_PASSWORD_INHERIT_FROM_MSD": "false", + "DEST_IF_NAME": "Ethernet1/8", + "DEST_SERIAL_NUMBER": "98YWRN9WCSC", + "BGP_MULTIHOP": "5", + "SOURCE_IP": "193.168.6.1", + "BGP_PASSWORD_ENABLE": "true", + "SOURCE_SERIAL_NUMBER": "953E68OKK1L", + "asn": "1600" + } + } + }, + + "merge_links_fabric_response": + { + "DATA": { + "linkUUID": "LINK-UUID-1446600", + "status": "Success" + }, + "MESSAGE": "OK", + "METHOD": "POST", + "REQUEST_PATH": "https://10.195.225.193:443/appcenter/cisco/ndfc/api/v1/lan-fabric/rest/control/links", + "RETURN_CODE": 200 + }, + + "modify_replace_links_fabric_response": + { + "DATA": { + "linkUUID": "LINK-UUID-1446600", + "status": "Success" + }, + "MESSAGE": "OK", + "METHOD": "POST", + "REQUEST_PATH": "https://10.195.225.193:443/appcenter/cisco/ndfc/api/v1/lan-fabric/rest/control/links", + "RETURN_CODE": 200 + }, + + "delete_links_fabric_response": + { + "DATA": { + "linkUUID": "LINK-UUID-1446600", + "status": "Success" + }, + "MESSAGE": "OK", + "METHOD": "DELETE", + "REQUEST_PATH": "https://10.195.225.193:443/appcenter/cisco/ndfc/api/v1/lan-fabric/rest/control/links/LINK-UUID-1446600?isLogicalLink=false", + "RETURN_CODE": 200 + }, + + "intra_query_links_num_fabric_response": + { + "MESSAGE": "OK", + "METHOD": "GET", + "REQUEST_PATH": "https://10.195.225.193:443/appcenter/cisco/ndfc/api/v1/lan-fabric/rest/control/links/LINK-UUID-1446600?isLogicalLink=false", + "RETURN_CODE": 200, + "DATA": [ + { + "fabricName": "mmudigon-numbered", + "link-uuid": "LINK-UUID-1446600", + "nvPairs": { + }, + "policyId": "POLICY-1855130", + "sw1-info": { + "fabric-name": "mmudigon-numbered", + "if-name": "Ethernet1/1", + "sw-serial-number": "9IF87L089SZ" + }, + "sw2-info": { + "fabric-name": "mmudigon-numbered", + "if-name": "Ethernet1/1", + "sw-serial-number": "9FX7O3TU2QM" + }, + "templateName": "int_intra_fabric_num_link" + }, + { + "fabricName": "mmudigon-numbered", + "link-uuid": "LINK-UUID-1419150", + "nvPairs": { + }, + "policyId": "POLICY-1855290", + "sw1-info": { + "fabric-name": "mmudigon-numbered", + "if-name": "Ethernet1/2", + "sw-serial-number": "9IF87L089SZ" + }, + "sw2-info": { + "fabric-name": "mmudigon-numbered", + "if-name": "Ethernet1/2", + "sw-serial-number": "9FX7O3TU2QM" + }, + "templateName": "int_pre_provision_intra_fabric_link" + }, + { + "fabricName": "mmudigon-numbered", + "link-uuid": "LINK-UUID-1419160", + "nvPairs": { + }, + "policyId": "POLICY-1855310", + "sw1-info": { + "fabric-name": "mmudigon-numbered", + "if-name": "Ethernet1/3", + "sw-serial-number": "9IF87L089SZ" + }, + "sw2-info": { + "fabric-name": "mmudigon-numbered", + "if-name": "Ethernet1/3", + "sw-serial-number": "9FX7O3TU2QM" + }, + "templateName": "ios_xe_int_intra_fabric_num_link" + } + ] + }, + + "intra_query_links_unnum_fabric_response": + { + "MESSAGE": "OK", + "METHOD": "GET", + "REQUEST_PATH": "https://10.195.225.193:443/appcenter/cisco/ndfc/api/v1/lan-fabric/rest/control/links/LINK-UUID-1446600?isLogicalLink=false", + "RETURN_CODE": 200, + "DATA": [ + { + "fabricName": "mmudigon-unnumbered", + "nvPairs": { + "ADMIN_STATE": "true", + "ENABLE_MACSEC": "false", + "FABRIC1": "mmudigon-unnumbered", + "FABRIC2": "mmudigon-unnumbered", + "HOSTNAME1": "Switch1", + "HOSTNAME2": "Switch2", + "MTU": "9216", + "PEER1_CONF": "", + "PEER1_DESC": "", + "PEER1_INTF": "Ethernet1/1", + "PEER1_SN": "9EFX823RUL3", + "PEER2_CONF": "", + "PEER2_DESC": "", + "PEER2_INTF": "Ethernet1/1", + "PEER2_SN": "9AF3VNZYAKS" + }, + "sw1-info": { + "fabric-name": "mmudigon-unnumbered", + "if-name": "Ethernet1/1", + "sw-serial-number": "9EFX823RUL3" + }, + "sw2-info": { + "fabric-name": "mmudigon-unnumbered", + "if-name": "Ethernet1/1", + "sw-serial-number": "9AF3VNZYAKS" + }, + "templateName": "int_intra_fabric_unnum_link" + }, + { + "fabricName": "mmudigon-unnumbered", + "nvPairs": { + }, + "policyId": "POLICY-1859300", + "sw1-info": { + "fabric-name": "mmudigon-unnumbered", + "if-name": "Ethernet1/2", + "sw-serial-number": "9EFX823RUL3" + }, + "sw2-info": { + "fabric-name": "mmudigon-unnumbered", + "if-name": "Ethernet1/2", + "sw-serial-number": "9AF3VNZYAKS" + }, + "templateName": "int_pre_provision_intra_fabric_link" + } + ] + + }, + + "intra_query_links_ipv6_fabric_response": + { + "MESSAGE": "OK", + "METHOD": "GET", + "REQUEST_PATH": "https://10.195.225.193:443/appcenter/cisco/ndfc/api/v1/lan-fabric/rest/control/links/LINK-UUID-1446600?isLogicalLink=false", + "RETURN_CODE": 200, + "DATA": [ + { + "fabricName": "mmudigon-ipv6-underlay", + "link-uuid": "LINK-UUID-1419050", + "nvPairs": { + "ADMIN_STATE": "true", + "ENABLE_MACSEC": "false", + "FABRIC1": "mmudigon-ipv6-underlay", + "FABRIC2": "mmudigon-ipv6-underlay", + "HOSTNAME1": "Switch1", + "HOSTNAME2": "Switch2", + "LINK_UUID": "LINK-UUID-1419050", + "MTU": "9216", + "PEER1_CONF": "", + "PEER1_DESC": "", + "PEER1_INTF": "Ethernet1/1", + "PEER1_SN": "9BH0813WFWT", + "PEER2_CONF": "", + "PEER2_DESC": "", + "PEER2_INTF": "Ethernet1/1", + "PEER2_SN": "9ITWBH9OIAH" + }, + "sw1-info": { + "fabric-name": "mmudigon-ipv6-underlay", + "if-name": "Ethernet1/1", + "if-op-reason": "Link not connected", + "sw-serial-number": "9BH0813WFWT" + }, + "sw2-info": { + "fabric-name": "mmudigon-ipv6-underlay", + "if-name": "Ethernet1/1", + "sw-serial-number": "9ITWBH9OIAH" + }, + "templateName": "int_intra_fabric_ipv6_link_local" + }, + { + "fabricName": "mmudigon-ipv6-underlay", + "link-uuid": "LINK-UUID-1419110", + "nvPairs": { + }, + "sw1-info": { + "fabric-name": "mmudigon-ipv6-underlay", + "if-name": "Ethernet1/2", + "sw-serial-number": "9BH0813WFWT" + }, + "sw2-info": { + "fabric-name": "mmudigon-ipv6-underlay", + "if-name": "Ethernet1/2", + "sw-serial-number": "9ITWBH9OIAH" + }, + "templateName": "int_pre_provision_intra_fabric_link" + }, + { + "fabricName": "mmudigon-ipv6-underlay", + "link-uuid": "LINK-UUID-1419180", + "nvPairs": { + "ADMIN_STATE": "true", + "ENABLE_MACSEC": "false", + "FABRIC1": "mmudigon-ipv6-underlay", + "FABRIC2": "mmudigon-ipv6-underlay", + "HOSTNAME1": "Switch1", + "HOSTNAME2": "Switch2", + "LINK_UUID": "LINK-UUID-1419180", + "MTU": "9216", + "PEER1_BFD_ECHO_DISABLE": "false", + "PEER1_CONF": "", + "PEER1_DESC": "", + "PEER1_INTF": "Ethernet1/3", + "PEER1_IP": "", + "PEER1_SN": "9BH0813WFWT", + "PEER1_V6IP": "fe80:0202::01", + "PEER2_BFD_ECHO_DISABLE": "false", + "PEER2_CONF": "", + "PEER2_DESC": "", + "PEER2_INTF": "Ethernet1/3", + "PEER2_IP": "", + "PEER2_SN": "9ITWBH9OIAH", + "PEER2_V6IP": "fe80:0202::02" + }, + "sw1-info": { + "fabric-name": "mmudigon-ipv6-underlay", + "if-name": "Ethernet1/3", + "sw-serial-number": "9BH0813WFWT" + }, + "sw2-info": { + "fabric-name": "mmudigon-ipv6-underlay", + "if-name": "Ethernet1/3", + "sw-serial-number": "9ITWBH9OIAH" + }, + "templateName": "int_intra_fabric_num_link" + }] + }, + + "intra_query_links_vpc_response": + { + "MESSAGE": "OK", + "METHOD": "GET", + "REQUEST_PATH": "https://10.195.225.193:443/appcenter/cisco/ndfc/api/v1/lan-fabric/rest/control/links/LINK-UUID-1446600?isLogicalLink=false", + "RETURN_CODE": 200, + "DATA": [ + { + "fabricName": "mmudigon", + "nvPairs": { + "ADMIN_STATE": "true", + "FABRIC1": "mmudigon", + "FABRIC2": "mmudigon", + "HOSTNAME1": "Switch1", + "HOSTNAME2": "Switch2", + "INTF_VRF": "", + "LINK_UUID": "LINK-UUID-1419030", + "MTU": "9216", + "PEER1_CONF": "", + "PEER1_DESC": "", + "PEER1_INTF": "Ethernet1/4", + "PEER1_IP": "192.168.1.1", + "PEER1_SN": "9M99N34RDED", + "PEER2_CONF": "", + "PEER2_DESC": "", + "PEER2_INTF": "Ethernet1/4", + "PEER2_IP": "192.168.1.2", + "PEER2_SN": "9NXHSNTEO6C" + }, + "sw1-info": { + "fabric-name": "mmudigon", + "if-name": "Ethernet1/4", + "sw-serial-number": "9M99N34RDED" + }, + "sw2-info": { + "fabric-name": "mmudigon", + "if-name": "Ethernet1/4", + "sw-serial-number": "9NXHSNTEO6C" + }, + "templateName": "int_intra_vpc_peer_keep_alive_link" + }] + }, + + "inter_query_links_num_fabric_response": + { + "MESSAGE": "OK", + "METHOD": "GET", + "REQUEST_PATH": "https://10.195.225.193:443/appcenter/cisco/ndfc/api/v1/lan-fabric/rest/control/links/LINK-UUID-1446600?isLogicalLink=false", + "RETURN_CODE": 200, + "DATA": [ + { + "link-uuid": "LINK-UUID-1424790", + "nvPairs": { + "BGP_MULTIHOP": "5", + "BGP_PASSWORD_ENABLE": "true", + "BGP_PASSWORD_INHERIT_FROM_MSD": "true", + "DEST_FABRIC_NAME": "test_net1", + "DEST_IF_NAME": "Ethernet1/8", + "DEST_SERIAL_NUMBER": "98YWRN9WCSC", + "DEST_SWITCH_NAME": "n9kv-test1", + "LINK_UUID": "LINK-UUID-1424790", + "NEIGHBOR_ASN": "1601", + "NEIGHBOR_IP": "193.168.6.2", + "SOURCE_FABRIC_NAME": "mmudigon-numbered", + "SOURCE_IF_NAME": "Ethernet1/8", + "SOURCE_IP": "193.168.6.1", + "SOURCE_SERIAL_NUMBER": "953E68OKK1L", + "SOURCE_SWITCH_NAME": "n9kv-test3", + "TRM_ENABLED": "false", + "asn": "1600" + }, + "sw1-info": { + "fabric-name": "mmudigon-numbered", + "if-name": "Ethernet1/8", + "sw-serial-number": "953E68OKK1L" + }, + "sw2-info": { + "fabric-name": "test_net1", + "if-name": "Ethernet1/8", + "sw-serial-number": "98YWRN9WCSC" + }, + "templateName": "ext_evpn_multisite_overlay_setup" + }, + { + "link-uuid": "LINK-UUID-1424640", + "nvPairs": { + "BGP_PASSWORD_ENABLE": "true", + "BGP_PASSWORD_INHERIT_FROM_MSD": "true", + "DEPLOY_DCI_TRACKING": "false", + "DEST_FABRIC_NAME": "test_net1", + "DEST_IF_NAME": "Ethernet1/7", + "DEST_SERIAL_NUMBER": "98YWRN9WCSC", + "DEST_SWITCH_NAME": "n9kv-test1", + "IP_MASK": "193.168.5.1/24", + "IS_METASWITCH": "false", + "LINK_UUID": "LINK-UUID-1424640", + "MAX_PATHS": "1", + "MTU": "9216", + "NEIGHBOR_ASN": "1501", + "NEIGHBOR_IP": "193.168.5.2", + "PEER1_CONF": "", + "PEER1_DESC": "", + "PEER2_CONF": "", + "PEER2_DESC": "", + "ROUTING_TAG": "", + "SOURCE_FABRIC_NAME": "mmudigon-numbered", + "SOURCE_IF_NAME": "Ethernet1/7", + "SOURCE_SERIAL_NUMBER": "953E68OKK1L", + "SOURCE_SWITCH_NAME": "n9kv-test3", + "asn": "1500" + }, + "sw1-info": { + "fabric-name": "mmudigon-numbered", + "if-name": "Ethernet1/7", + "sw-serial-number": "953E68OKK1L" + }, + "sw2-info": { + "fabric-name": "test_net1", + "if-name": "Ethernet1/7", + "sw-serial-number": "98YWRN9WCSC" + }, + "templateName": "ext_multisite_underlay_setup" + }, + { + "link-uuid": "LINK-UUID-1424530", + "nvPairs": { + "AUTO_VRF_LITE_FLAG": "false", + "DEST_FABRIC_NAME": "test_net1", + "DEST_IF_NAME": "Ethernet1/6", + "DEST_SERIAL_NUMBER": "98YWRN9WCSC", + "DEST_SWITCH_NAME": "n9kv-test1", + "IP_MASK": "193.168.4.1/24", + "LINK_UUID": "LINK-UUID-1424530", + "MTU": "9216", + "NEIGHBOR_ASN": "1401", + "NEIGHBOR_IP": "193.168.4.2", + "PEER1_CONF": "", + "PEER1_DESC": "", + "PEER2_CONF": "", + "PEER2_DESC": "", + "SOURCE_FABRIC_NAME": "mmudigon-numbered", + "SOURCE_IF_NAME": "Ethernet1/6", + "SOURCE_SERIAL_NUMBER": "953E68OKK1L", + "SOURCE_SWITCH_NAME": "n9kv-test3", + "VRF_LITE_JYTHON_TEMPLATE": "Ext_VRF_Lite_Jython", + "asn": "1400" + }, + "sw1-info": { + "fabric-name": "mmudigon-numbered", + "if-name": "Ethernet1/6", + "sw-serial-number": "953E68OKK1L" + }, + "sw2-info": { + "fabric-name": "test_net1", + "if-name": "Ethernet1/6", + "sw-serial-number": "98YWRN9WCSC" + }, + "templateName": "ext_fabric_setup" + }, + { + "link-uuid": "LINK-UUID-1424480", + "nvPairs": { + "BGP_MULTIHOP": "5", + "BGP_PASSWORD_ENABLE": "true", + "BGP_PASSWORD_INHERIT_FROM_MSD": "true", + "DEST_FABRIC_NAME": "test_net", + "DEST_IF_NAME": "Ethernet1/5", + "DEST_SERIAL_NUMBER": "9E15XSEM5MS", + "DEST_SWITCH_NAME": "test22", + "LINK_UUID": "LINK-UUID-1424480", + "NEIGHBOR_ASN": "1301", + "NEIGHBOR_IP": "193.168.3.2", + "SOURCE_FABRIC_NAME": "mmudigon-numbered", + "SOURCE_IF_NAME": "Ethernet1/5", + "SOURCE_IP": "193.168.3.1", + "SOURCE_SERIAL_NUMBER": "953E68OKK1L", + "SOURCE_SWITCH_NAME": "n9kv-test3", + "TRM_ENABLED": "false", + "asn": "1300" + }, + "sw1-info": { + "fabric-name": "mmudigon-numbered", + "if-name": "Ethernet1/5", + "sw-serial-number": "953E68OKK1L" + }, + "sw2-info": { + "fabric-name": "test_net", + "if-name": "Ethernet1/5", + "sw-serial-number": "9E15XSEM5MS" + }, + "templateName": "ext_evpn_multisite_overlay_setup" + }, + { + "link-uuid": "LINK-UUID-1424330", + "nvPairs": { + "BGP_PASSWORD_ENABLE": "true", + "BGP_PASSWORD_INHERIT_FROM_MSD": "true", + "DEPLOY_DCI_TRACKING": "false", + "DEST_FABRIC_NAME": "test_net", + "DEST_IF_NAME": "Ethernet1/4", + "DEST_SERIAL_NUMBER": "9E15XSEM5MS", + "DEST_SWITCH_NAME": "test22", + "IP_MASK": "193.168.2.1/24", + "LINK_UUID": "LINK-UUID-1424330", + "MAX_PATHS": "1", + "MTU": "9216", + "NEIGHBOR_ASN": "1201", + "NEIGHBOR_IP": "193.168.2.2", + "PEER1_CONF": "", + "PEER1_DESC": "", + "PEER2_CONF": "", + "PEER2_DESC": "", + "ROUTING_TAG": "", + "SOURCE_FABRIC_NAME": "mmudigon-numbered", + "SOURCE_IF_NAME": "Ethernet1/4", + "SOURCE_SERIAL_NUMBER": "953E68OKK1L", + "SOURCE_SWITCH_NAME": "n9kv-test3", + "asn": "1200" + }, + "sw1-info": { + "fabric-name": "mmudigon-numbered", + "if-name": "Ethernet1/4", + "sw-serial-number": "953E68OKK1L" + }, + "sw2-info": { + "fabric-name": "test_net", + "if-name": "Ethernet1/4", + "sw-serial-number": "9E15XSEM5MS" + }, + "templateName": "ext_multisite_underlay_setup" + }, + { + "link-uuid": "LINK-UUID-1424200", + "nvPairs": { + "AUTO_VRF_LITE_FLAG": "false", + "DEST_FABRIC_NAME": "test_net", + "DEST_IF_NAME": "Ethernet1/3", + "DEST_SERIAL_NUMBER": "9E15XSEM5MS", + "DEST_SWITCH_NAME": "test22", + "IP_MASK": "193.168.1.1/24", + "LINK_UUID": "LINK-UUID-1424200", + "MTU": "9216", + "NEIGHBOR_ASN": "1001", + "NEIGHBOR_IP": "193.168.1.2", + "PEER1_CONF": "", + "PEER1_DESC": "", + "PEER2_CONF": "", + "PEER2_DESC": "", + "SOURCE_FABRIC_NAME": "mmudigon-numbered", + "SOURCE_IF_NAME": "Ethernet1/3", + "SOURCE_SERIAL_NUMBER": "953E68OKK1L", + "SOURCE_SWITCH_NAME": "n9kv-test3", + "VRF_LITE_JYTHON_TEMPLATE": "Ext_VRF_Lite_Jython", + "asn": "1000" + }, + "sw1-info": { + "fabric-name": "mmudigon-numbered", + "if-name": "Ethernet1/3", + "sw-serial-number": "953E68OKK1L" + }, + "sw2-info": { + "fabric-name": "test_net", + "if-name": "Ethernet1/3", + "sw-serial-number": "9E15XSEM5MS" + }, + "templateName": "ext_fabric_setup" + }] + } +} diff --git a/tests/unit/modules/dcnm/test_dcnm_links.py b/tests/unit/modules/dcnm/test_dcnm_links.py new file mode 100644 index 000000000..1d3cd2bf5 --- /dev/null +++ b/tests/unit/modules/dcnm/test_dcnm_links.py @@ -0,0 +1,5783 @@ +# Copyright (c) 2020-2022 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. + +# Make coding more python3-ish +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +from unittest.mock import patch + +from ansible_collections.cisco.dcnm.plugins.modules import dcnm_links +from .dcnm_module import TestDcnmModule, set_module_args, loadPlaybookData + +import json +import copy + + +class TestDcnmLinksModule(TestDcnmModule): + + module = dcnm_links + fd = None + + def init_data(self): + self.fd = None + + def log_msg(self, msg): + if self.fd is None: + self.fd = open("links-ut.log", "a+") + self.fd.write(msg) + + def setUp(self): + + super(TestDcnmLinksModule, self).setUp() + + self.mock_dcnm_ip_sn = patch( + "ansible_collections.cisco.dcnm.plugins.modules.dcnm_links.get_ip_sn_dict" + ) + self.run_dcnm_ip_sn = self.mock_dcnm_ip_sn.start() + + self.mock_dcnm_fabric_details = patch( + "ansible_collections.cisco.dcnm.plugins.modules.dcnm_links.get_fabric_inventory_details" + ) + self.run_dcnm_fabric_details = self.mock_dcnm_fabric_details.start() + + self.mock_dcnm_fabric_info = patch( + "ansible_collections.cisco.dcnm.plugins.modules.dcnm_links.get_fabric_details" + ) + self.run_dcnm_fabric_info = self.mock_dcnm_fabric_info.start() + + self.mock_dcnm_send = patch( + "ansible_collections.cisco.dcnm.plugins.modules.dcnm_links.dcnm_send" + ) + self.run_dcnm_send = self.mock_dcnm_send.start() + + self.mock_dcnm_version_supported = patch( + "ansible_collections.cisco.dcnm.plugins.modules.dcnm_links.dcnm_version_supported" + ) + self.run_dcnm_version_supported = ( + self.mock_dcnm_version_supported.start() + ) + + def tearDown(self): + + super(TestDcnmLinksModule, self).tearDown() + self.mock_dcnm_send.stop() + self.mock_dcnm_version_supported.stop() + self.mock_dcnm_fabric_details.stop() + self.mock_dcnm_fabric_info.stop() + self.mock_dcnm_ip_sn.stop() + + # -------------------------- FIXTURES -------------------------- + + def load_links_fixtures(self): + + if "test_dcnm_intra_links_numbered_" in self._testMethodName: + self.run_dcnm_fabric_info.side_effect = [self.mock_num_fab_info] + + if "test_dcnm_intra_links_unnumbered_" in self._testMethodName: + self.run_dcnm_fabric_info.side_effect = [self.mock_unnum_fab_info] + + if "test_dcnm_intra_links_ipv6_" in self._testMethodName: + self.run_dcnm_fabric_info.side_effect = [self.mock_ipv6_fab_info] + + if "test_dcnm_intra_links_invalid_" in self._testMethodName: + self.run_dcnm_fabric_info.side_effect = [self.mock_num_fab_info] + + if "test_dcnm_intra_links_vpc_" in self._testMethodName: + self.run_dcnm_fabric_info.side_effect = [self.mock_num_fab_info] + + if "test_dcnm_intra_links_missing_" in self._testMethodName: + self.run_dcnm_fabric_info.side_effect = [self.mock_num_fab_info] + + if ( + "test_dcnm_intra_links_missing_peer1_ipv6" in self._testMethodName + ) or ( + ( + "test_dcnm_intra_links_missing_peer2_ipv6" + in self._testMethodName + ) + ): + self.run_dcnm_fabric_info.side_effect = [self.mock_ipv6_fab_info] + + # -------------------------- INTRA-FABRIC-UNNUMBERED -------------------------- + + if ( + "test_dcnm_intra_links_unnumbered_merged_new_no_opts" + == self._testMethodName + ): + + merge_links_resp = self.payloads_data.get( + "merge_links_fabric_response" + ) + deploy_resp = self.payloads_data.get("deploy_resp") + config_preview_resp = self.payloads_data.get("config_preview_resp") + + self.run_dcnm_send.side_effect = [ + [], + [], + merge_links_resp, + merge_links_resp, + deploy_resp, + config_preview_resp, + ] + + if ( + "test_dcnm_intra_links_unnumbered_merged_new" + == self._testMethodName + ): + + merge_links_resp = self.payloads_data.get( + "merge_links_fabric_response" + ) + deploy_resp = self.payloads_data.get("deploy_resp") + config_preview_resp = self.payloads_data.get("config_preview_resp") + + self.run_dcnm_send.side_effect = [ + [], + [], + merge_links_resp, + merge_links_resp, + deploy_resp, + config_preview_resp, + ] + + if ( + "test_dcnm_intra_links_unnumbered_merged_new_no_deploy" + == self._testMethodName + ): + + merge_links_resp = self.payloads_data.get( + "merge_links_fabric_response" + ) + + self.run_dcnm_send.side_effect = [ + [], + [], + merge_links_resp, + merge_links_resp, + ] + + if ( + "test_dcnm_intra_links_unnumbered_merged_existing" + == self._testMethodName + ): + + have_links_resp1 = self.payloads_data.get( + "intra_have_link1_unnum_fabric_response" + ) + have_links_resp2 = self.payloads_data.get( + "intra_have_link2_unnum_fabric_response" + ) + merge_links_resp = self.payloads_data.get( + "merge_links_fabric_response" + ) + deploy_resp = self.payloads_data.get("deploy_resp") + config_preview_resp = self.payloads_data.get("config_preview_resp") + + self.run_dcnm_send.side_effect = [ + have_links_resp1, + have_links_resp2, + deploy_resp, + config_preview_resp, + ] + + if ( + "test_dcnm_intra_links_unnumbered_merged_new_no_state" + == self._testMethodName + ): + + merge_links_resp = self.payloads_data.get( + "merge_links_fabric_response" + ) + deploy_resp = self.payloads_data.get("deploy_resp") + config_preview_resp = self.payloads_data.get("config_preview_resp") + + self.run_dcnm_send.side_effect = [ + [], + [], + merge_links_resp, + merge_links_resp, + deploy_resp, + config_preview_resp, + ] + + if ( + "test_dcnm_intra_links_merged_new_check_mode" + == self._testMethodName + ): + pass + + if ( + "test_dcnm_intra_links_unnumbered_merged_new_existing_and_non_existing" + == self._testMethodName + ): + + have_links_resp1 = self.payloads_data.get( + "intra_have_link1_unnum_fabric_response" + ) + have_links_resp2 = [] + merge_links_resp = self.payloads_data.get( + "merge_links_fabric_response" + ) + deploy_resp = self.payloads_data.get("deploy_resp") + config_preview_resp = self.payloads_data.get("config_preview_resp") + + self.run_dcnm_send.side_effect = [ + have_links_resp1, + have_links_resp2, + merge_links_resp, + deploy_resp, + config_preview_resp, + ] + + if ( + "test_dcnm_intra_links_unnumbered_modify_existing" + == self._testMethodName + ): + + have_links_resp1 = self.payloads_data.get( + "intra_have_link1_unnum_fabric_response" + ) + have_links_resp2 = self.payloads_data.get( + "intra_have_link2_unnum_fabric_response" + ) + merge_links_resp = self.payloads_data.get( + "merge_links_fabric_response" + ) + deploy_resp = self.payloads_data.get("deploy_resp") + config_preview_resp = self.payloads_data.get("config_preview_resp") + + self.run_dcnm_send.side_effect = [ + have_links_resp1, + have_links_resp2, + merge_links_resp, + deploy_resp, + config_preview_resp, + ] + + if ( + "test_dcnm_intra_links_unnumbered_replace_existing" + == self._testMethodName + ): + + have_links_resp1 = self.payloads_data.get( + "intra_have_link1_unnum_fabric_response" + ) + have_links_resp2 = self.payloads_data.get( + "intra_have_link2_unnum_fabric_response" + ) + merge_links_resp = self.payloads_data.get( + "merge_links_fabric_response" + ) + deploy_resp = self.payloads_data.get("deploy_resp") + config_preview_resp = self.payloads_data.get("config_preview_resp") + + self.run_dcnm_send.side_effect = [ + have_links_resp1, + have_links_resp2, + merge_links_resp, + deploy_resp, + config_preview_resp, + ] + + if ( + "test_dcnm_intra_links_unnumbered_delete_existing" + == self._testMethodName + ): + + have_links_resp1 = self.payloads_data.get( + "intra_have_link1_unnum_fabric_response" + ) + have_links_resp2 = self.payloads_data.get( + "intra_have_link2_unnum_fabric_response" + ) + delete_links_resp = self.payloads_data.get( + "delete_links_fabric_response" + ) + deploy_resp = self.payloads_data.get("deploy_resp") + config_preview_resp = self.payloads_data.get("config_preview_resp") + + self.run_dcnm_send.side_effect = [ + have_links_resp1, + have_links_resp2, + [], + [], + [], + delete_links_resp, + delete_links_resp, + deploy_resp, + config_preview_resp, + ] + + if ( + "test_dcnm_intra_links_unnumbered_delete_existing_and_non_existing" + == self._testMethodName + ): + + have_links_resp1 = self.payloads_data.get( + "intra_have_link1_unnum_fabric_response" + ) + delete_links_resp = self.payloads_data.get( + "delete_links_fabric_response" + ) + deploy_resp = self.payloads_data.get("deploy_resp") + config_preview_resp = self.payloads_data.get("config_preview_resp") + + self.run_dcnm_send.side_effect = [ + have_links_resp1, + [], + [], + [], + [], + delete_links_resp, + deploy_resp, + config_preview_resp, + ] + + if ( + "test_dcnm_intra_links_unnumbered_delete_non_existing" + == self._testMethodName + ): + + self.run_dcnm_send.side_effect = [[], [], [], [], []] + + if ( + "test_dcnm_intra_links_unnumbered_template_change" + == self._testMethodName + ): + + have_links_resp1 = self.payloads_data.get( + "intra_have_link1_unnum_fabric_response" + ) + merge_links_resp = self.payloads_data.get( + "merge_links_fabric_response" + ) + deploy_resp = self.payloads_data.get("deploy_resp") + config_preview_resp = self.payloads_data.get("config_preview_resp") + + self.run_dcnm_send.side_effect = [ + have_links_resp1, + merge_links_resp, + deploy_resp, + config_preview_resp, + ] + + if "test_dcnm_intra_links_unnumbered_query" in self._testMethodName: + + query_links_resp = self.payloads_data.get( + "intra_query_links_unnum_fabric_response" + ) + self.run_dcnm_send.side_effect = [query_links_resp] + + # -------------------------- INTRA-FABRIC-IPV6 ---------------------------------- + + if ( + "test_dcnm_intra_links_ipv6_merged_new_no_opts" + == self._testMethodName + ): + + merge_links_resp = self.payloads_data.get( + "merge_links_fabric_response" + ) + deploy_resp = self.payloads_data.get("deploy_resp") + config_preview_resp = self.payloads_data.get("config_preview_resp") + + self.run_dcnm_send.side_effect = [ + [], + [], + [], + merge_links_resp, + merge_links_resp, + merge_links_resp, + deploy_resp, + config_preview_resp, + ] + + if "test_dcnm_intra_links_ipv6_merged_new" == self._testMethodName: + + merge_links_resp = self.payloads_data.get( + "merge_links_fabric_response" + ) + deploy_resp = self.payloads_data.get("deploy_resp") + config_preview_resp = self.payloads_data.get("config_preview_resp") + + self.run_dcnm_send.side_effect = [ + [], + [], + [], + merge_links_resp, + merge_links_resp, + merge_links_resp, + deploy_resp, + config_preview_resp, + ] + + if ( + "test_dcnm_intra_links_ipv6_merged_existing" + == self._testMethodName + ): + + have_links_resp1 = self.payloads_data.get( + "intra_have_link1_ipv6_fabric_response" + ) + have_links_resp2 = self.payloads_data.get( + "intra_have_link2_ipv6_fabric_response" + ) + have_links_resp3 = self.payloads_data.get( + "intra_have_link3_ipv6_fabric_response" + ) + merge_links_resp = self.payloads_data.get( + "merge_links_fabric_response" + ) + deploy_resp = self.payloads_data.get("deploy_resp") + config_preview_resp = self.payloads_data.get("config_preview_resp") + + self.run_dcnm_send.side_effect = [ + have_links_resp1, + have_links_resp2, + have_links_resp3, + deploy_resp, + config_preview_resp, + ] + + if ( + "test_dcnm_intra_links_ipv6_merged_new_no_state" + == self._testMethodName + ): + + merge_links_resp = self.payloads_data.get( + "merge_links_fabric_response" + ) + deploy_resp = self.payloads_data.get("deploy_resp") + config_preview_resp = self.payloads_data.get("config_preview_resp") + + self.run_dcnm_send.side_effect = [ + [], + [], + [], + merge_links_resp, + merge_links_resp, + merge_links_resp, + deploy_resp, + config_preview_resp, + ] + + if ( + "test_dcnm_intra_links_ipv6_merged_new_existing_and_non_existing" + == self._testMethodName + ): + + have_links_resp1 = self.payloads_data.get( + "intra_have_link1_ipv6_fabric_response" + ) + have_links_resp2 = [] + have_links_resp3 = self.payloads_data.get( + "intra_have_link3_ipv6_fabric_response" + ) + + merge_links_resp = self.payloads_data.get( + "merge_links_fabric_response" + ) + deploy_resp = self.payloads_data.get("deploy_resp") + config_preview_resp = self.payloads_data.get("config_preview_resp") + + self.run_dcnm_send.side_effect = [ + have_links_resp1, + have_links_resp2, + have_links_resp3, + merge_links_resp, + deploy_resp, + config_preview_resp, + ] + + if ( + "test_dcnm_intra_links_ipv6_modify_existing" + == self._testMethodName + ): + + have_links_resp1 = self.payloads_data.get( + "intra_have_link1_ipv6_fabric_response" + ) + have_links_resp2 = self.payloads_data.get( + "intra_have_link2_ipv6_fabric_response" + ) + have_links_resp3 = self.payloads_data.get( + "intra_have_link3_ipv6_fabric_response" + ) + merge_links_resp = self.payloads_data.get( + "merge_links_fabric_response" + ) + deploy_resp = self.payloads_data.get("deploy_resp") + config_preview_resp = self.payloads_data.get("config_preview_resp") + + self.run_dcnm_send.side_effect = [ + have_links_resp1, + have_links_resp2, + have_links_resp3, + merge_links_resp, + merge_links_resp, + deploy_resp, + config_preview_resp, + ] + + if ( + "test_dcnm_intra_links_ipv6_replace_existing" + == self._testMethodName + ): + + have_links_resp1 = self.payloads_data.get( + "intra_have_link1_ipv6_fabric_response" + ) + have_links_resp2 = self.payloads_data.get( + "intra_have_link2_ipv6_fabric_response" + ) + have_links_resp3 = self.payloads_data.get( + "intra_have_link3_ipv6_fabric_response" + ) + merge_links_resp = self.payloads_data.get( + "merge_links_fabric_response" + ) + deploy_resp = self.payloads_data.get("deploy_resp") + config_preview_resp = self.payloads_data.get("config_preview_resp") + + self.run_dcnm_send.side_effect = [ + have_links_resp1, + have_links_resp2, + have_links_resp3, + merge_links_resp, + merge_links_resp, + deploy_resp, + config_preview_resp, + ] + + if ( + "test_dcnm_intra_links_ipv6_delete_existing" + == self._testMethodName + ): + + have_links_resp1 = self.payloads_data.get( + "intra_have_link1_ipv6_fabric_response" + ) + have_links_resp2 = self.payloads_data.get( + "intra_have_link2_ipv6_fabric_response" + ) + have_links_resp3 = self.payloads_data.get( + "intra_have_link3_ipv6_fabric_response" + ) + delete_links_resp = self.payloads_data.get( + "delete_links_fabric_response" + ) + deploy_resp = self.payloads_data.get("deploy_resp") + config_preview_resp = self.payloads_data.get("config_preview_resp") + + self.run_dcnm_send.side_effect = [ + have_links_resp1, + have_links_resp2, + have_links_resp3, + [], + [], + delete_links_resp, + delete_links_resp, + delete_links_resp, + deploy_resp, + config_preview_resp, + ] + + if ( + "test_dcnm_intra_links_ipv6_delete_existing_and_non_existing" + == self._testMethodName + ): + + have_links_resp1 = self.payloads_data.get( + "intra_have_link1_ipv6_fabric_response" + ) + have_links_resp2 = self.payloads_data.get( + "intra_have_link2_ipv6_fabric_response" + ) + have_links_resp3 = [] + delete_links_resp = self.payloads_data.get( + "delete_links_fabric_response" + ) + deploy_resp = self.payloads_data.get("deploy_resp") + config_preview_resp = self.payloads_data.get("config_preview_resp") + + self.run_dcnm_send.side_effect = [ + have_links_resp1, + have_links_resp2, + have_links_resp3, + [], + [], + delete_links_resp, + delete_links_resp, + deploy_resp, + config_preview_resp, + ] + + if ( + "test_dcnm_intra_links_ipv6_delete_non_existing" + == self._testMethodName + ): + + self.run_dcnm_send.side_effect = [[], [], [], [], []] + + if "test_dcnm_intra_links_ipv6_query" in self._testMethodName: + + query_links_resp = self.payloads_data.get( + "intra_query_links_ipv6_fabric_response" + ) + self.run_dcnm_send.side_effect = [query_links_resp] + + # -------------------------- INTRA-FABRIC-NUMBERED -------------------------- + + if ( + "test_dcnm_intra_links_numbered_merged_new_no_opts" + == self._testMethodName + ): + + merge_links_resp = self.payloads_data.get( + "merge_links_fabric_response" + ) + deploy_resp = self.payloads_data.get("deploy_resp") + config_preview_resp = self.payloads_data.get("config_preview_resp") + + self.run_dcnm_send.side_effect = [ + [], + [], + [], + merge_links_resp, + merge_links_resp, + merge_links_resp, + deploy_resp, + config_preview_resp, + ] + + if "test_dcnm_intra_links_numbered_merged_new" == self._testMethodName: + + merge_links_resp = self.payloads_data.get( + "merge_links_fabric_response" + ) + deploy_resp = self.payloads_data.get("deploy_resp") + config_preview_resp = self.payloads_data.get("config_preview_resp") + + self.run_dcnm_send.side_effect = [ + [], + [], + [], + merge_links_resp, + merge_links_resp, + merge_links_resp, + deploy_resp, + config_preview_resp, + ] + + if ( + "test_dcnm_intra_links_numbered_merged_existing" + == self._testMethodName + ): + + have_links_resp1 = self.payloads_data.get( + "intra_have_link1_num_fabric_response" + ) + have_links_resp2 = self.payloads_data.get( + "intra_have_link2_num_fabric_response" + ) + have_links_resp3 = self.payloads_data.get( + "intra_have_link3_num_fabric_response" + ) + merge_links_resp = self.payloads_data.get( + "merge_links_fabric_response" + ) + deploy_resp = self.payloads_data.get("deploy_resp") + config_preview_resp = self.payloads_data.get("config_preview_resp") + + self.run_dcnm_send.side_effect = [ + have_links_resp1, + have_links_resp2, + have_links_resp3, + deploy_resp, + config_preview_resp, + ] + + if ( + "test_dcnm_intra_links_numbered_merged_new_no_state" + == self._testMethodName + ): + + merge_links_resp = self.payloads_data.get( + "merge_links_fabric_response" + ) + deploy_resp = self.payloads_data.get("deploy_resp") + config_preview_resp = self.payloads_data.get("config_preview_resp") + + self.run_dcnm_send.side_effect = [ + [], + [], + [], + merge_links_resp, + merge_links_resp, + merge_links_resp, + deploy_resp, + config_preview_resp, + ] + + if ( + "test_dcnm_intra_links_numbered_merged_new_existing_and_non_existing" + == self._testMethodName + ): + + have_links_resp1 = self.payloads_data.get( + "intra_have_link1_num_fabric_response" + ) + have_links_resp2 = [] + have_links_resp3 = self.payloads_data.get( + "intra_have_link3_num_fabric_response" + ) + merge_links_resp = self.payloads_data.get( + "merge_links_fabric_response" + ) + deploy_resp = self.payloads_data.get("deploy_resp") + config_preview_resp = self.payloads_data.get("config_preview_resp") + + self.run_dcnm_send.side_effect = [ + have_links_resp1, + have_links_resp2, + have_links_resp3, + merge_links_resp, + deploy_resp, + config_preview_resp, + ] + + if ( + "test_dcnm_intra_links_numbered_modify_existing" + == self._testMethodName + ): + + have_links_resp1 = self.payloads_data.get( + "intra_have_link1_num_fabric_response" + ) + have_links_resp2 = self.payloads_data.get( + "intra_have_link2_num_fabric_response" + ) + have_links_resp3 = self.payloads_data.get( + "intra_have_link3_num_fabric_response" + ) + merge_links_resp = self.payloads_data.get( + "merge_links_fabric_response" + ) + deploy_resp = self.payloads_data.get("deploy_resp") + config_preview_resp = self.payloads_data.get("config_preview_resp") + + self.run_dcnm_send.side_effect = [ + have_links_resp1, + have_links_resp2, + have_links_resp3, + merge_links_resp, + merge_links_resp, + deploy_resp, + config_preview_resp, + ] + + if ( + "test_dcnm_intra_links_numbered_replace_existing" + == self._testMethodName + ): + + have_links_resp1 = self.payloads_data.get( + "intra_have_link1_num_fabric_response" + ) + have_links_resp2 = self.payloads_data.get( + "intra_have_link2_num_fabric_response" + ) + have_links_resp3 = self.payloads_data.get( + "intra_have_link3_num_fabric_response" + ) + merge_links_resp = self.payloads_data.get( + "merge_links_fabric_response" + ) + deploy_resp = self.payloads_data.get("deploy_resp") + config_preview_resp = self.payloads_data.get("config_preview_resp") + + self.run_dcnm_send.side_effect = [ + have_links_resp1, + have_links_resp2, + have_links_resp3, + merge_links_resp, + merge_links_resp, + deploy_resp, + config_preview_resp, + ] + + if ( + "test_dcnm_intra_links_numbered_delete_existing" + == self._testMethodName + ): + + have_links_resp1 = self.payloads_data.get( + "intra_have_link1_num_fabric_response" + ) + have_links_resp2 = self.payloads_data.get( + "intra_have_link2_num_fabric_response" + ) + have_links_resp3 = self.payloads_data.get( + "intra_have_link3_num_fabric_response" + ) + delete_links_resp = self.payloads_data.get( + "delete_links_fabric_response" + ) + deploy_resp = self.payloads_data.get("deploy_resp") + config_preview_resp = self.payloads_data.get("config_preview_resp") + + self.run_dcnm_send.side_effect = [ + have_links_resp1, + have_links_resp2, + have_links_resp3, + [], + [], + delete_links_resp, + delete_links_resp, + delete_links_resp, + deploy_resp, + config_preview_resp, + ] + + if ( + "test_dcnm_intra_links_numbered_delete_existing_and_non_existing" + == self._testMethodName + ): + + have_links_resp1 = self.payloads_data.get( + "intra_have_link1_num_fabric_response" + ) + have_links_resp2 = self.payloads_data.get( + "intra_have_link2_num_fabric_response" + ) + have_links_resp3 = [] + delete_links_resp = self.payloads_data.get( + "delete_links_fabric_response" + ) + deploy_resp = self.payloads_data.get("deploy_resp") + config_preview_resp = self.payloads_data.get("config_preview_resp") + + self.run_dcnm_send.side_effect = [ + have_links_resp1, + have_links_resp2, + have_links_resp3, + [], + [], + delete_links_resp, + delete_links_resp, + deploy_resp, + config_preview_resp, + ] + + if ( + "test_dcnm_intra_links_numbered_delete_non_existing" + == self._testMethodName + ): + + self.run_dcnm_send.side_effect = [[], [], [], [], []] + + if ( + "test_dcnm_intra_links_numbered_template_change" + == self._testMethodName + ): + + have_links_resp1 = self.payloads_data.get( + "intra_have_link1_num_fabric_response" + ) + merge_links_resp = self.payloads_data.get( + "merge_links_fabric_response" + ) + deploy_resp = self.payloads_data.get("deploy_resp") + config_preview_resp = self.payloads_data.get("config_preview_resp") + + self.run_dcnm_send.side_effect = [ + have_links_resp1, + merge_links_resp, + deploy_resp, + config_preview_resp, + ] + + if "test_dcnm_intra_links_numbered_query" in self._testMethodName: + + query_links_resp = self.payloads_data.get( + "intra_query_links_num_fabric_response" + ) + self.run_dcnm_send.side_effect = [query_links_resp] + + # ------------------------------ INTRA-FABRIC-VPC --------------------------- + + if ( + "test_dcnm_intra_links_vpc_merged_new_no_opts" + == self._testMethodName + ): + + merge_links_resp = self.payloads_data.get( + "merge_links_fabric_response" + ) + deploy_resp = self.payloads_data.get("deploy_resp") + config_preview_resp = self.payloads_data.get("config_preview_resp") + + self.run_dcnm_send.side_effect = [ + [], + merge_links_resp, + deploy_resp, + config_preview_resp, + ] + + if "test_dcnm_intra_links_vpc_merged_new" == self._testMethodName: + + merge_links_resp = self.payloads_data.get( + "merge_links_fabric_response" + ) + deploy_resp = self.payloads_data.get("deploy_resp") + config_preview_resp = self.payloads_data.get("config_preview_resp") + + self.run_dcnm_send.side_effect = [ + [], + merge_links_resp, + deploy_resp, + config_preview_resp, + ] + + if "test_dcnm_intra_links_vpc_merged_existing" == self._testMethodName: + + have_links_resp1 = self.payloads_data.get( + "intra_have_link1_vpc_num_fabric_response" + ) + merge_links_resp = self.payloads_data.get( + "merge_links_fabric_response" + ) + deploy_resp = self.payloads_data.get("deploy_resp") + config_preview_resp = self.payloads_data.get("config_preview_resp") + + self.run_dcnm_send.side_effect = [ + have_links_resp1, + deploy_resp, + config_preview_resp, + ] + + if ( + "test_dcnm_intra_links_vpc_merged_new_no_state" + == self._testMethodName + ): + + merge_links_resp = self.payloads_data.get( + "merge_links_fabric_response" + ) + deploy_resp = self.payloads_data.get("deploy_resp") + config_preview_resp = self.payloads_data.get("config_preview_resp") + + self.run_dcnm_send.side_effect = [ + [], + merge_links_resp, + deploy_resp, + config_preview_resp, + ] + + if ( + "test_dcnm_intra_links_vpc_merged_new_existing_and_non_existing" + == self._testMethodName + ): + + have_links_resp1 = [] + + merge_links_resp = self.payloads_data.get( + "merge_links_fabric_response" + ) + deploy_resp = self.payloads_data.get("deploy_resp") + config_preview_resp = self.payloads_data.get("config_preview_resp") + + self.run_dcnm_send.side_effect = [ + have_links_resp1, + merge_links_resp, + deploy_resp, + config_preview_resp, + ] + + if "test_dcnm_intra_links_vpc_modify_existing" == self._testMethodName: + + have_links_resp1 = self.payloads_data.get( + "intra_have_link1_vpc_num_fabric_response" + ) + merge_links_resp = self.payloads_data.get( + "merge_links_fabric_response" + ) + deploy_resp = self.payloads_data.get("deploy_resp") + config_preview_resp = self.payloads_data.get("config_preview_resp") + + self.run_dcnm_send.side_effect = [ + have_links_resp1, + merge_links_resp, + deploy_resp, + config_preview_resp, + ] + + if ( + "test_dcnm_intra_links_vpc_replace_existing" + == self._testMethodName + ): + + have_links_resp1 = self.payloads_data.get( + "intra_have_link1_vpc_num_fabric_response" + ) + merge_links_resp = self.payloads_data.get( + "merge_links_fabric_response" + ) + deploy_resp = self.payloads_data.get("deploy_resp") + config_preview_resp = self.payloads_data.get("config_preview_resp") + + self.run_dcnm_send.side_effect = [ + have_links_resp1, + merge_links_resp, + deploy_resp, + config_preview_resp, + ] + + if "test_dcnm_intra_links_vpc_delete_existing" == self._testMethodName: + + have_links_resp1 = self.payloads_data.get( + "intra_have_link1_vpc_num_fabric_response" + ) + delete_links_resp = self.payloads_data.get( + "delete_links_fabric_response" + ) + deploy_resp = self.payloads_data.get("deploy_resp") + config_preview_resp = self.payloads_data.get("config_preview_resp") + + self.run_dcnm_send.side_effect = [ + have_links_resp1, + [], + [], + [], + [], + delete_links_resp, + deploy_resp, + config_preview_resp, + ] + + if ( + "test_dcnm_intra_links_vpc_delete_existing_and_non_existing" + == self._testMethodName + ): + + have_links_resp1 = self.payloads_data.get( + "intra_have_link1_vpc_num_fabric_response" + ) + delete_links_resp = self.payloads_data.get( + "delete_links_fabric_response" + ) + deploy_resp = self.payloads_data.get("deploy_resp") + config_preview_resp = self.payloads_data.get("config_preview_resp") + + self.run_dcnm_send.side_effect = [ + have_links_resp1, + [], + [], + [], + [], + delete_links_resp, + deploy_resp, + config_preview_resp, + ] + + if ( + "test_dcnm_intra_links_vpc_delete_non_existing" + == self._testMethodName + ): + + self.run_dcnm_send.side_effect = [[], [], [], [], []] + + if "test_dcnm_intra_links_vpc_query" in self._testMethodName: + + query_links_resp = self.payloads_data.get( + "intra_query_links_vpc_response" + ) + self.run_dcnm_send.side_effect = [query_links_resp] + + # -------------------------- INTER-FABRIC-NUMBERED -------------------------- + + if ( + "test_dcnm_inter_links_numbered_merged_new_no_opts" + == self._testMethodName + ): + + merge_links_resp = self.payloads_data.get( + "merge_links_fabric_response" + ) + deploy_resp = self.payloads_data.get("deploy_resp") + config_preview_resp = self.payloads_data.get("config_preview_resp") + + self.run_dcnm_send.side_effect = [ + [], + [], + [], + [], + [], + [], + merge_links_resp, + merge_links_resp, + merge_links_resp, + merge_links_resp, + merge_links_resp, + merge_links_resp, + deploy_resp, + deploy_resp, + deploy_resp, + config_preview_resp, + config_preview_resp, + config_preview_resp, + ] + + if "test_dcnm_inter_links_numbered_merged_new" == self._testMethodName: + + merge_links_resp = self.payloads_data.get( + "merge_links_fabric_response" + ) + deploy_resp = self.payloads_data.get("deploy_resp") + config_preview_resp = self.payloads_data.get("config_preview_resp") + + self.run_dcnm_send.side_effect = [ + [], + [], + [], + [], + [], + [], + merge_links_resp, + merge_links_resp, + merge_links_resp, + merge_links_resp, + merge_links_resp, + merge_links_resp, + deploy_resp, + deploy_resp, + deploy_resp, + config_preview_resp, + config_preview_resp, + config_preview_resp, + ] + + if ( + "test_dcnm_inter_links_numbered_merged_existing" + == self._testMethodName + ): + + have_links_resp1 = self.payloads_data.get( + "inter_have_link1_num_fabric_response" + ) + have_links_resp2 = self.payloads_data.get( + "inter_have_link2_num_fabric_response" + ) + have_links_resp3 = self.payloads_data.get( + "inter_have_link3_num_fabric_response" + ) + have_links_resp4 = self.payloads_data.get( + "inter_have_link4_num_fabric_response" + ) + have_links_resp5 = self.payloads_data.get( + "inter_have_link5_num_fabric_response" + ) + have_links_resp6 = self.payloads_data.get( + "inter_have_link6_num_fabric_response" + ) + merge_links_resp = self.payloads_data.get( + "merge_links_fabric_response" + ) + deploy_resp = self.payloads_data.get("deploy_resp") + config_preview_resp = self.payloads_data.get("config_preview_resp") + + self.run_dcnm_send.side_effect = [ + have_links_resp1, + have_links_resp2, + have_links_resp3, + have_links_resp4, + have_links_resp5, + have_links_resp6, + deploy_resp, + deploy_resp, + deploy_resp, + config_preview_resp, + config_preview_resp, + config_preview_resp, + ] + + if ( + "test_dcnm_inter_links_numbered_merged_new_no_state" + == self._testMethodName + ): + + merge_links_resp = self.payloads_data.get( + "merge_links_fabric_response" + ) + deploy_resp = self.payloads_data.get("deploy_resp") + config_preview_resp = self.payloads_data.get("config_preview_resp") + + self.run_dcnm_send.side_effect = [ + [], + [], + [], + [], + [], + [], + merge_links_resp, + merge_links_resp, + merge_links_resp, + merge_links_resp, + merge_links_resp, + merge_links_resp, + deploy_resp, + deploy_resp, + deploy_resp, + config_preview_resp, + config_preview_resp, + config_preview_resp, + ] + + if ( + "test_dcnm_inter_links_numbered_merged_new_existing_and_non_existing" + == self._testMethodName + ): + + have_links_resp1 = self.payloads_data.get( + "inter_have_link1_num_fabric_response" + ) + have_links_resp2 = [] + have_links_resp3 = self.payloads_data.get( + "inter_have_link3_num_fabric_response" + ) + have_links_resp4 = [] + have_links_resp5 = self.payloads_data.get( + "inter_have_link5_num_fabric_response" + ) + have_links_resp6 = [] + + merge_links_resp = self.payloads_data.get( + "merge_links_fabric_response" + ) + deploy_resp = self.payloads_data.get("deploy_resp") + config_preview_resp = self.payloads_data.get("config_preview_resp") + + self.run_dcnm_send.side_effect = [ + have_links_resp1, + have_links_resp2, + have_links_resp3, + have_links_resp4, + have_links_resp5, + have_links_resp6, + merge_links_resp, + merge_links_resp, + merge_links_resp, + deploy_resp, + deploy_resp, + deploy_resp, + config_preview_resp, + config_preview_resp, + config_preview_resp, + ] + + if ( + "test_dcnm_inter_links_numbered_modify_existing" + == self._testMethodName + ): + + have_links_resp1 = self.payloads_data.get( + "inter_have_link1_num_fabric_response" + ) + have_links_resp2 = self.payloads_data.get( + "inter_have_link2_num_fabric_response" + ) + have_links_resp3 = self.payloads_data.get( + "inter_have_link3_num_fabric_response" + ) + have_links_resp4 = self.payloads_data.get( + "inter_have_link4_num_fabric_response" + ) + have_links_resp5 = self.payloads_data.get( + "inter_have_link5_num_fabric_response" + ) + have_links_resp6 = self.payloads_data.get( + "inter_have_link6_num_fabric_response" + ) + merge_links_resp = self.payloads_data.get( + "merge_links_fabric_response" + ) + deploy_resp = self.payloads_data.get("deploy_resp") + config_preview_resp = self.payloads_data.get("config_preview_resp") + + self.run_dcnm_send.side_effect = [ + have_links_resp1, + have_links_resp2, + have_links_resp3, + have_links_resp4, + have_links_resp5, + have_links_resp6, + merge_links_resp, + merge_links_resp, + merge_links_resp, + merge_links_resp, + merge_links_resp, + merge_links_resp, + deploy_resp, + deploy_resp, + deploy_resp, + config_preview_resp, + config_preview_resp, + config_preview_resp, + ] + + if ( + "test_dcnm_inter_links_numbered_replace_existing" + == self._testMethodName + ): + + have_links_resp1 = self.payloads_data.get( + "inter_have_link1_num_fabric_response" + ) + have_links_resp2 = self.payloads_data.get( + "inter_have_link2_num_fabric_response" + ) + have_links_resp3 = self.payloads_data.get( + "inter_have_link3_num_fabric_response" + ) + have_links_resp4 = self.payloads_data.get( + "inter_have_link4_num_fabric_response" + ) + have_links_resp5 = self.payloads_data.get( + "inter_have_link5_num_fabric_response" + ) + have_links_resp6 = self.payloads_data.get( + "inter_have_link6_num_fabric_response" + ) + merge_links_resp = self.payloads_data.get( + "merge_links_fabric_response" + ) + deploy_resp = self.payloads_data.get("deploy_resp") + config_preview_resp = self.payloads_data.get("config_preview_resp") + + self.run_dcnm_send.side_effect = [ + have_links_resp1, + have_links_resp2, + have_links_resp3, + have_links_resp4, + have_links_resp5, + have_links_resp6, + merge_links_resp, + merge_links_resp, + merge_links_resp, + merge_links_resp, + merge_links_resp, + merge_links_resp, + deploy_resp, + deploy_resp, + deploy_resp, + config_preview_resp, + config_preview_resp, + config_preview_resp, + ] + + if ( + "test_dcnm_inter_links_numbered_delete_existing" + == self._testMethodName + ): + + have_links_resp1 = self.payloads_data.get( + "inter_have_link1_num_fabric_response" + ) + have_links_resp2 = self.payloads_data.get( + "inter_have_link2_num_fabric_response" + ) + have_links_resp3 = self.payloads_data.get( + "inter_have_link3_num_fabric_response" + ) + have_links_resp4 = self.payloads_data.get( + "inter_have_link4_num_fabric_response" + ) + have_links_resp5 = self.payloads_data.get( + "inter_have_link5_num_fabric_response" + ) + have_links_resp6 = self.payloads_data.get( + "inter_have_link6_num_fabric_response" + ) + delete_links_resp = self.payloads_data.get( + "delete_links_fabric_response" + ) + deploy_resp = self.payloads_data.get("deploy_resp") + config_preview_resp = self.payloads_data.get("config_preview_resp") + + self.run_dcnm_send.side_effect = [ + have_links_resp1, + have_links_resp2, + have_links_resp3, + have_links_resp4, + have_links_resp5, + have_links_resp6, + delete_links_resp, + delete_links_resp, + delete_links_resp, + delete_links_resp, + delete_links_resp, + delete_links_resp, + deploy_resp, + deploy_resp, + deploy_resp, + config_preview_resp, + config_preview_resp, + config_preview_resp, + ] + + if ( + "test_dcnm_inter_links_numbered_delete_existing_and_non_existing" + == self._testMethodName + ): + + have_links_resp1 = self.payloads_data.get( + "inter_have_link1_num_fabric_response" + ) + have_links_resp2 = self.payloads_data.get( + "inter_have_link2_num_fabric_response" + ) + have_links_resp3 = [] + have_links_resp4 = self.payloads_data.get( + "inter_have_link4_num_fabric_response" + ) + have_links_resp5 = self.payloads_data.get( + "inter_have_link5_num_fabric_response" + ) + have_links_resp6 = [] + delete_links_resp = self.payloads_data.get( + "delete_links_fabric_response" + ) + deploy_resp = self.payloads_data.get("deploy_resp") + config_preview_resp = self.payloads_data.get("config_preview_resp") + + self.run_dcnm_send.side_effect = [ + have_links_resp1, + have_links_resp2, + have_links_resp3, + have_links_resp4, + have_links_resp5, + have_links_resp6, + delete_links_resp, + delete_links_resp, + delete_links_resp, + delete_links_resp, + deploy_resp, + deploy_resp, + deploy_resp, + config_preview_resp, + config_preview_resp, + config_preview_resp, + ] + + if ( + "test_dcnm_inter_links_numbered_delete_non_existing" + == self._testMethodName + ): + + self.run_dcnm_send.side_effect = [[], [], [], [], [], []] + + if ( + "test_dcnm_inter_links_numbered_template_change" + == self._testMethodName + ): + + have_links_resp1 = self.payloads_data.get( + "inter_have_link1_num_fabric_response" + ) + merge_links_resp = self.payloads_data.get( + "merge_links_fabric_response" + ) + deploy_resp = self.payloads_data.get("deploy_resp") + config_preview_resp = self.payloads_data.get("config_preview_resp") + + self.run_dcnm_send.side_effect = [ + have_links_resp1, + merge_links_resp, + deploy_resp, + deploy_resp, + config_preview_resp, + config_preview_resp, + ] + + if "test_dcnm_inter_links_numbered_query" in self._testMethodName: + + query_links_resp = self.payloads_data.get( + "inter_query_links_num_fabric_response" + ) + self.run_dcnm_send.side_effect = [query_links_resp] + + def load_fixtures(self, response=None, device=""): + + self.run_dcnm_version_supported.side_effect = [12] + self.run_dcnm_fabric_details.side_effect = [ + self.mock_fab_inv, + self.mock_fab_inv, + self.mock_fab_inv, + ] + self.run_dcnm_ip_sn.side_effect = [[self.mock_ip_sn, self.mock_hn_sn]] + # Load Links related side-effects + self.load_links_fixtures() + + # -------------------------- FIXTURES END -------------------------- + # -------------------------- TEST-CASES ---------------------------- + # -------------------------- INTRA-FABRIC-NUMBERED ------------------------------ + + def test_dcnm_intra_links_numbered_merged_new_no_opts(self): + + # load the json from playbooks + self.config_data = loadPlaybookData("dcnm_links_configs") + self.payloads_data = loadPlaybookData("dcnm_links_payloads") + + # load required config data + self.playbook_config = self.config_data.get( + "intra_merge_num_no_opts_config" + ) + self.mock_ip_sn = self.payloads_data.get("mock_ip_sn") + self.mock_hn_sn = self.payloads_data.get("mock_hn_sn") + self.mock_fab_inv = self.payloads_data.get("mock_fab_inv_data") + self.mock_num_fab_info = self.payloads_data.get("mock_num_fab_data") + + set_module_args( + dict( + state="merged", + src_fabric="mmudigon-numbered", + config=self.playbook_config, + ) + ) + result = self.execute_module(changed=True, failed=False) + + self.assertEqual(len(result["diff"][0]["merged"]), 3) + self.assertEqual(len(result["diff"][0]["modified"]), 0) + self.assertEqual(len(result["diff"][0]["deleted"]), 0) + self.assertEqual(len(result["diff"][0]["query"]), 0) + self.assertEqual( + len(result["diff"][0]["deploy"][0]["mmudigon-numbered"]), 2 + ) + + # Validate create responses + for resp in result["response"]: + self.assertEqual(resp["RETURN_CODE"], 200) + + def test_dcnm_intra_links_numbered_merged_new(self): + + # load the json from playbooks + self.config_data = loadPlaybookData("dcnm_links_configs") + self.payloads_data = loadPlaybookData("dcnm_links_payloads") + + # load required config data + self.playbook_config = self.config_data.get("intra_merge_num_config") + self.mock_ip_sn = self.payloads_data.get("mock_ip_sn") + self.mock_hn_sn = self.payloads_data.get("mock_hn_sn") + self.mock_fab_inv = self.payloads_data.get("mock_fab_inv_data") + self.mock_num_fab_info = self.payloads_data.get("mock_num_fab_data") + + set_module_args( + dict( + state="merged", + src_fabric="mmudigon-numbered", + config=self.playbook_config, + ) + ) + result = self.execute_module(changed=True, failed=False) + + self.assertEqual(len(result["diff"][0]["merged"]), 3) + self.assertEqual(len(result["diff"][0]["modified"]), 0) + self.assertEqual(len(result["diff"][0]["deleted"]), 0) + self.assertEqual(len(result["diff"][0]["query"]), 0) + self.assertEqual( + len(result["diff"][0]["deploy"][0]["mmudigon-numbered"]), 2 + ) + + # Validate create responses + for resp in result["response"]: + self.assertEqual(resp["RETURN_CODE"], 200) + + def test_dcnm_intra_links_numbered_merged_existing(self): + + # load the json from playbooks + self.config_data = loadPlaybookData("dcnm_links_configs") + self.payloads_data = loadPlaybookData("dcnm_links_payloads") + + # load required config data + self.playbook_config = self.config_data.get("intra_merge_num_config") + self.mock_ip_sn = self.payloads_data.get("mock_ip_sn") + self.mock_hn_sn = self.payloads_data.get("mock_hn_sn") + self.mock_fab_inv = self.payloads_data.get("mock_fab_inv_data") + self.mock_num_fab_info = self.payloads_data.get("mock_num_fab_data") + + set_module_args( + dict( + state="merged", + src_fabric="mmudigon-numbered", + config=self.playbook_config, + ) + ) + result = self.execute_module(changed=True, failed=False) + + self.assertEqual(len(result["diff"][0]["merged"]), 0) + self.assertEqual(len(result["diff"][0]["modified"]), 0) + self.assertEqual(len(result["diff"][0]["deleted"]), 0) + self.assertEqual(len(result["diff"][0]["query"]), 0) + self.assertEqual( + len(result["diff"][0]["deploy"][0]["mmudigon-numbered"]), 2 + ) + + # Validate create responses + for resp in result["response"]: + self.assertEqual(resp["RETURN_CODE"], 200) + + def test_dcnm_intra_links_numbered_merged_new_no_state(self): + + # load the json from playbooks + self.config_data = loadPlaybookData("dcnm_links_configs") + self.payloads_data = loadPlaybookData("dcnm_links_payloads") + + # load required config data + self.playbook_config = self.config_data.get("intra_merge_num_config") + self.mock_ip_sn = self.payloads_data.get("mock_ip_sn") + self.mock_hn_sn = self.payloads_data.get("mock_hn_sn") + self.mock_fab_inv = self.payloads_data.get("mock_fab_inv_data") + self.mock_num_fab_info = self.payloads_data.get("mock_num_fab_data") + + set_module_args( + dict( + state="merged", + src_fabric="mmudigon-numbered", + config=self.playbook_config, + ) + ) + result = self.execute_module(changed=True, failed=False) + + self.assertEqual(len(result["diff"][0]["merged"]), 3) + self.assertEqual(len(result["diff"][0]["modified"]), 0) + self.assertEqual(len(result["diff"][0]["deleted"]), 0) + self.assertEqual(len(result["diff"][0]["query"]), 0) + self.assertEqual( + len(result["diff"][0]["deploy"][0]["mmudigon-numbered"]), 2 + ) + + # Validate create responses + for resp in result["response"]: + self.assertEqual(resp["RETURN_CODE"], 200) + + def test_dcnm_intra_links_numbered_merged_new_check_mode(self): + + # load the json from playbooks + self.config_data = loadPlaybookData("dcnm_links_configs") + self.payloads_data = loadPlaybookData("dcnm_links_payloads") + + # load required config data + self.playbook_config = self.config_data.get("intra_merge_num_config") + self.mock_ip_sn = self.payloads_data.get("mock_ip_sn") + self.mock_hn_sn = self.payloads_data.get("mock_hn_sn") + self.mock_fab_inv = self.payloads_data.get("mock_fab_inv_data") + self.mock_num_fab_info = self.payloads_data.get("mock_num_fab_data") + + set_module_args( + dict( + _ansible_check_mode=True, + state="merged", + src_fabric="mmudigon-numbered", + config=self.playbook_config, + ) + ) + result = self.execute_module(changed=False, failed=False) + + self.assertEqual(len(result["diff"][0]["merged"]), 3) + self.assertEqual(len(result["diff"][0]["modified"]), 0) + self.assertEqual(len(result["diff"][0]["deleted"]), 0) + self.assertEqual(len(result["diff"][0]["query"]), 0) + self.assertEqual( + len(result["diff"][0]["deploy"][0]["mmudigon-numbered"]), 2 + ) + + # Validate create responses + for resp in result["response"]: + self.assertEqual(resp["RETURN_CODE"], 200) + + def test_dcnm_intra_links_numbered_merged_new_existing_and_non_existing( + self + ): + + # load the json from playbooks + self.config_data = loadPlaybookData("dcnm_links_configs") + self.payloads_data = loadPlaybookData("dcnm_links_payloads") + + # load required config data + self.playbook_config = self.config_data.get("intra_merge_num_config") + self.mock_ip_sn = self.payloads_data.get("mock_ip_sn") + self.mock_hn_sn = self.payloads_data.get("mock_hn_sn") + self.mock_fab_inv = self.payloads_data.get("mock_fab_inv_data") + self.mock_num_fab_info = self.payloads_data.get("mock_num_fab_data") + + set_module_args( + dict( + state="merged", + src_fabric="mmudigon-numbered", + config=self.playbook_config, + ) + ) + + result = self.execute_module(changed=True, failed=False) + + self.assertEqual(len(result["diff"][0]["merged"]), 1) + self.assertEqual(len(result["diff"][0]["modified"]), 0) + self.assertEqual(len(result["diff"][0]["deleted"]), 0) + self.assertEqual(len(result["diff"][0]["query"]), 0) + self.assertEqual( + len(result["diff"][0]["deploy"][0]["mmudigon-numbered"]), 2 + ) + + # Validate create responses + for resp in result["response"]: + self.assertEqual(resp["RETURN_CODE"], 200) + + def test_dcnm_intra_links_numbered_modify_existing(self): + + # load the json from playbooks + self.config_data = loadPlaybookData("dcnm_links_configs") + self.payloads_data = loadPlaybookData("dcnm_links_payloads") + + # load required config data + self.playbook_config = self.config_data.get("intra_modify_num_config") + self.mock_ip_sn = self.payloads_data.get("mock_ip_sn") + self.mock_hn_sn = self.payloads_data.get("mock_hn_sn") + self.mock_fab_inv = self.payloads_data.get("mock_fab_inv_data") + self.mock_num_fab_info = self.payloads_data.get("mock_num_fab_data") + + set_module_args( + dict( + state="merged", + src_fabric="mmudigon-numbered", + config=self.playbook_config, + ) + ) + + result = self.execute_module(changed=True, failed=False) + + self.assertEqual(len(result["diff"][0]["merged"]), 0) + self.assertEqual(len(result["diff"][0]["modified"]), 2) + self.assertEqual(len(result["diff"][0]["deleted"]), 0) + self.assertEqual(len(result["diff"][0]["query"]), 0) + self.assertEqual( + len(result["diff"][0]["deploy"][0]["mmudigon-numbered"]), 2 + ) + + # Validate create responses + for resp in result["response"]: + self.assertEqual(resp["RETURN_CODE"], 200) + + def test_dcnm_intra_links_numbered_replace_existing(self): + + # load the json from playbooks + self.config_data = loadPlaybookData("dcnm_links_configs") + self.payloads_data = loadPlaybookData("dcnm_links_payloads") + + # load required config data + self.playbook_config = self.config_data.get("intra_replace_num_config") + self.mock_ip_sn = self.payloads_data.get("mock_ip_sn") + self.mock_hn_sn = self.payloads_data.get("mock_hn_sn") + self.mock_fab_inv = self.payloads_data.get("mock_fab_inv_data") + self.mock_num_fab_info = self.payloads_data.get("mock_num_fab_data") + + set_module_args( + dict( + state="replaced", + src_fabric="mmudigon-numbered", + config=self.playbook_config, + ) + ) + + result = self.execute_module(changed=True, failed=False) + + self.assertEqual(len(result["diff"][0]["merged"]), 0) + self.assertEqual(len(result["diff"][0]["modified"]), 2) + self.assertEqual(len(result["diff"][0]["deleted"]), 0) + self.assertEqual(len(result["diff"][0]["query"]), 0) + self.assertEqual( + len(result["diff"][0]["deploy"][0]["mmudigon-numbered"]), 2 + ) + + # Validate create responses + for resp in result["response"]: + self.assertEqual(resp["RETURN_CODE"], 200) + + def test_dcnm_intra_links_numbered_delete_existing(self): + + # load the json from playbooks + self.config_data = loadPlaybookData("dcnm_links_configs") + self.payloads_data = loadPlaybookData("dcnm_links_payloads") + + # load required config data + self.playbook_config = self.config_data.get("intra_delete_num_config") + self.mock_ip_sn = self.payloads_data.get("mock_ip_sn") + self.mock_hn_sn = self.payloads_data.get("mock_hn_sn") + self.mock_fab_inv = self.payloads_data.get("mock_fab_inv_data") + self.mock_num_fab_info = self.payloads_data.get("mock_num_fab_data") + + set_module_args( + dict( + state="deleted", + src_fabric="mmudigon-numbered", + config=self.playbook_config, + ) + ) + + result = self.execute_module(changed=True, failed=False) + + self.assertEqual(len(result["diff"][0]["merged"]), 0) + self.assertEqual(len(result["diff"][0]["modified"]), 0) + self.assertEqual(len(result["diff"][0]["deleted"]), 3) + self.assertEqual(len(result["diff"][0]["query"]), 0) + self.assertEqual( + len(result["diff"][0]["deploy"][0]["mmudigon-numbered"]), 2 + ) + + # Validate delete responses + for resp in result["response"]: + self.assertEqual(resp["RETURN_CODE"], 200) + + def test_dcnm_intra_links_numbered_delete_existing_and_non_existing(self): + + # load the json from playbooks + self.config_data = loadPlaybookData("dcnm_links_configs") + self.payloads_data = loadPlaybookData("dcnm_links_payloads") + + # load required config data + self.playbook_config = self.config_data.get("intra_delete_num_config") + self.mock_ip_sn = self.payloads_data.get("mock_ip_sn") + self.mock_hn_sn = self.payloads_data.get("mock_hn_sn") + self.mock_fab_inv = self.payloads_data.get("mock_fab_inv_data") + self.mock_num_fab_info = self.payloads_data.get("mock_num_fab_data") + + set_module_args( + dict( + state="deleted", + src_fabric="mmudigon-numbered", + config=self.playbook_config, + ) + ) + + result = self.execute_module(changed=True, failed=False) + + self.assertEqual(len(result["diff"][0]["merged"]), 0) + self.assertEqual(len(result["diff"][0]["modified"]), 0) + self.assertEqual(len(result["diff"][0]["deleted"]), 2) + self.assertEqual(len(result["diff"][0]["query"]), 0) + self.assertEqual( + len(result["diff"][0]["deploy"][0]["mmudigon-numbered"]), 2 + ) + + # Validate delete responses + for resp in result["response"]: + self.assertEqual(resp["RETURN_CODE"], 200) + + def test_dcnm_intra_links_numbered_delete_non_existing(self): + + # load the json from playbooks + self.config_data = loadPlaybookData("dcnm_links_configs") + self.payloads_data = loadPlaybookData("dcnm_links_payloads") + + # load required config data + self.playbook_config = self.config_data.get("intra_delete_num_config") + self.mock_ip_sn = self.payloads_data.get("mock_ip_sn") + self.mock_hn_sn = self.payloads_data.get("mock_hn_sn") + self.mock_fab_inv = self.payloads_data.get("mock_fab_inv_data") + self.mock_num_fab_info = self.payloads_data.get("mock_num_fab_data") + + set_module_args( + dict( + state="deleted", + src_fabric="mmudigon-numbered", + config=self.playbook_config, + ) + ) + + result = self.execute_module(changed=False, failed=False) + + self.assertEqual(len(result["diff"][0]["merged"]), 0) + self.assertEqual(len(result["diff"][0]["modified"]), 0) + self.assertEqual(len(result["diff"][0]["deleted"]), 0) + self.assertEqual(len(result["diff"][0]["query"]), 0) + self.assertEqual(len(result["diff"][0]["deploy"]), 0) + + # Validate delete responses + for resp in result["response"]: + self.assertEqual(resp["RETURN_CODE"], 200) + + def test_dcnm_intra_links_numbered_template_change(self): + + # load the json from playbooks + self.config_data = loadPlaybookData("dcnm_links_configs") + self.payloads_data = loadPlaybookData("dcnm_links_payloads") + + # load required config data + self.playbook_config = self.config_data.get( + "intra_modify_num_template_config" + ) + self.mock_ip_sn = self.payloads_data.get("mock_ip_sn") + self.mock_hn_sn = self.payloads_data.get("mock_hn_sn") + self.mock_fab_inv = self.payloads_data.get("mock_fab_inv_data") + self.mock_num_fab_info = self.payloads_data.get("mock_num_fab_data") + + set_module_args( + dict( + state="merged", + src_fabric="mmudigon-numbered", + config=self.playbook_config, + ) + ) + + result = self.execute_module(changed=True, failed=False) + + self.assertEqual(len(result["diff"][0]["merged"]), 0) + self.assertEqual(len(result["diff"][0]["modified"]), 1) + self.assertEqual(len(result["diff"][0]["deleted"]), 0) + self.assertEqual(len(result["diff"][0]["query"]), 0) + self.assertEqual( + len(result["diff"][0]["deploy"][0]["mmudigon-numbered"]), 2 + ) + + # Validate delete responses + for resp in result["response"]: + self.assertEqual(resp["RETURN_CODE"], 200) + + def test_dcnm_intra_links_numbered_query_no_config(self): + + # load the json from playbooks + self.config_data = loadPlaybookData("dcnm_links_configs") + self.payloads_data = loadPlaybookData("dcnm_links_payloads") + + # load required config data + self.playbook_config = [] + self.mock_ip_sn = self.payloads_data.get("mock_ip_sn") + self.mock_hn_sn = self.payloads_data.get("mock_hn_sn") + self.mock_fab_inv = self.payloads_data.get("mock_fab_inv_data") + self.mock_num_fab_info = self.payloads_data.get("mock_num_fab_data") + + set_module_args( + dict( + state="query", + src_fabric="mmudigon-numbered", + config=self.playbook_config, + ) + ) + + result = self.execute_module(changed=False, failed=False) + + self.assertEqual(len(result["diff"][0]["merged"]), 0) + self.assertEqual(len(result["diff"][0]["modified"]), 0) + self.assertEqual(len(result["diff"][0]["deleted"]), 0) + self.assertEqual((len(result["response"]) == 3), True) + self.assertEqual(len(result["diff"][0]["deploy"]), 0) + + def test_dcnm_intra_links_numbered_query_with_dst_fabric(self): + + # load the json from playbooks + self.config_data = loadPlaybookData("dcnm_links_configs") + self.payloads_data = loadPlaybookData("dcnm_links_payloads") + + # load required config data + self.playbook_config = self.config_data.get( + "intra_query_num_dest_fabric_config" + ) + self.mock_ip_sn = self.payloads_data.get("mock_ip_sn") + self.mock_hn_sn = self.payloads_data.get("mock_hn_sn") + self.mock_fab_inv = self.payloads_data.get("mock_fab_inv_data") + self.mock_num_fab_info = self.payloads_data.get("mock_num_fab_data") + + set_module_args( + dict( + state="query", + src_fabric="mmudigon-numbered", + config=self.playbook_config, + ) + ) + + result = self.execute_module(changed=False, failed=False) + + self.assertEqual(len(result["diff"][0]["merged"]), 0) + self.assertEqual(len(result["diff"][0]["modified"]), 0) + self.assertEqual(len(result["diff"][0]["deleted"]), 0) + self.assertEqual((len(result["response"]) == 3), True) + self.assertEqual(len(result["diff"][0]["deploy"]), 0) + + def test_dcnm_intra_links_numbered_query_with_src_device(self): + + # load the json from playbooks + self.config_data = loadPlaybookData("dcnm_links_configs") + self.payloads_data = loadPlaybookData("dcnm_links_payloads") + + # load required config data + self.playbook_config = self.config_data.get( + "intra_query_num_src_dev_config" + ) + self.mock_ip_sn = self.payloads_data.get("mock_ip_sn") + self.mock_hn_sn = self.payloads_data.get("mock_hn_sn") + self.mock_fab_inv = self.payloads_data.get("mock_fab_inv_data") + self.mock_num_fab_info = self.payloads_data.get("mock_num_fab_data") + + set_module_args( + dict( + state="query", + src_fabric="mmudigon-numbered", + config=self.playbook_config, + ) + ) + + result = self.execute_module(changed=False, failed=False) + + self.assertEqual(len(result["diff"][0]["merged"]), 0) + self.assertEqual(len(result["diff"][0]["modified"]), 0) + self.assertEqual(len(result["diff"][0]["deleted"]), 0) + self.assertEqual((len(result["response"]) == 3), True) + self.assertEqual(len(result["diff"][0]["deploy"]), 0) + + def test_dcnm_intra_links_numbered_query_with_dst_device(self): + + # load the json from playbooks + self.config_data = loadPlaybookData("dcnm_links_configs") + self.payloads_data = loadPlaybookData("dcnm_links_payloads") + + # load required config data + self.playbook_config = self.config_data.get( + "intra_query_num_dst_dev_config" + ) + self.mock_ip_sn = self.payloads_data.get("mock_ip_sn") + self.mock_hn_sn = self.payloads_data.get("mock_hn_sn") + self.mock_fab_inv = self.payloads_data.get("mock_fab_inv_data") + self.mock_num_fab_info = self.payloads_data.get("mock_num_fab_data") + + set_module_args( + dict( + state="query", + src_fabric="mmudigon-numbered", + config=self.playbook_config, + ) + ) + + result = self.execute_module(changed=False, failed=False) + + self.assertEqual(len(result["diff"][0]["merged"]), 0) + self.assertEqual(len(result["diff"][0]["modified"]), 0) + self.assertEqual(len(result["diff"][0]["deleted"]), 0) + self.assertEqual((len(result["response"]) == 3), True) + self.assertEqual(len(result["diff"][0]["deploy"]), 0) + + def test_dcnm_intra_links_numbered_query_with_src_interface(self): + + # load the json from playbooks + self.config_data = loadPlaybookData("dcnm_links_configs") + self.payloads_data = loadPlaybookData("dcnm_links_payloads") + + # load required config data + self.playbook_config = self.config_data.get( + "intra_query_num_src_intf_config" + ) + self.mock_ip_sn = self.payloads_data.get("mock_ip_sn") + self.mock_hn_sn = self.payloads_data.get("mock_hn_sn") + self.mock_fab_inv = self.payloads_data.get("mock_fab_inv_data") + self.mock_num_fab_info = self.payloads_data.get("mock_num_fab_data") + + set_module_args( + dict( + state="query", + src_fabric="mmudigon-numbered", + config=self.playbook_config, + ) + ) + + result = self.execute_module(changed=False, failed=False) + + self.assertEqual(len(result["diff"][0]["merged"]), 0) + self.assertEqual(len(result["diff"][0]["modified"]), 0) + self.assertEqual(len(result["diff"][0]["deleted"]), 0) + self.assertEqual((len(result["response"]) == 1), True) + self.assertEqual(len(result["diff"][0]["deploy"]), 0) + + def test_dcnm_intra_links_numbered_query_with_dst_interface(self): + + # load the json from playbooks + self.config_data = loadPlaybookData("dcnm_links_configs") + self.payloads_data = loadPlaybookData("dcnm_links_payloads") + + # load required config data + self.playbook_config = self.config_data.get( + "intra_query_num_dst_intf_config" + ) + self.mock_ip_sn = self.payloads_data.get("mock_ip_sn") + self.mock_hn_sn = self.payloads_data.get("mock_hn_sn") + self.mock_fab_inv = self.payloads_data.get("mock_fab_inv_data") + self.mock_num_fab_info = self.payloads_data.get("mock_num_fab_data") + + set_module_args( + dict( + state="query", + src_fabric="mmudigon-numbered", + config=self.playbook_config, + ) + ) + + result = self.execute_module(changed=False, failed=False) + + self.assertEqual(len(result["diff"][0]["merged"]), 0) + self.assertEqual(len(result["diff"][0]["modified"]), 0) + self.assertEqual(len(result["diff"][0]["deleted"]), 0) + self.assertEqual((len(result["response"]) == 1), True) + self.assertEqual(len(result["diff"][0]["deploy"]), 0) + + def test_dcnm_intra_links_numbered_query(self): + + # load the json from playbooks + self.config_data = loadPlaybookData("dcnm_links_configs") + self.payloads_data = loadPlaybookData("dcnm_links_payloads") + + # load required config data + self.playbook_config = self.config_data.get("intra_query_num_config") + self.mock_ip_sn = self.payloads_data.get("mock_ip_sn") + self.mock_hn_sn = self.payloads_data.get("mock_hn_sn") + self.mock_fab_inv = self.payloads_data.get("mock_fab_inv_data") + self.mock_num_fab_info = self.payloads_data.get("mock_num_fab_data") + + set_module_args( + dict( + state="query", + src_fabric="mmudigon-numbered", + config=self.playbook_config, + ) + ) + + result = self.execute_module(changed=False, failed=False) + + self.assertEqual(len(result["diff"][0]["merged"]), 0) + self.assertEqual(len(result["diff"][0]["modified"]), 0) + self.assertEqual(len(result["diff"][0]["deleted"]), 0) + self.assertEqual((len(result["response"]) == 3), True) + self.assertEqual(len(result["diff"][0]["deploy"]), 0) + + # ------------------------- INTRA-FABRIC-UNNUMBERED ----------------------------- + + def test_dcnm_intra_links_unnumbered_merged_new_no_opts(self): + + # load the json from playbooks + self.config_data = loadPlaybookData("dcnm_links_configs") + self.payloads_data = loadPlaybookData("dcnm_links_payloads") + + # load required config data + self.playbook_config = self.config_data.get( + "intra_merge_unnum_no_opts_config" + ) + self.mock_ip_sn = self.payloads_data.get("mock_ip_sn") + self.mock_hn_sn = self.payloads_data.get("mock_hn_sn") + self.mock_fab_inv = self.payloads_data.get("mock_fab_inv_data") + self.mock_unnum_fab_info = self.payloads_data.get( + "mock_umnum_fab_data" + ) + + set_module_args( + dict( + state="merged", + src_fabric="mmudigon-unnumbered", + config=self.playbook_config, + ) + ) + result = self.execute_module(changed=True, failed=False) + + self.assertEqual(len(result["diff"][0]["merged"]), 2) + self.assertEqual(len(result["diff"][0]["modified"]), 0) + self.assertEqual(len(result["diff"][0]["deleted"]), 0) + self.assertEqual(len(result["diff"][0]["query"]), 0) + self.assertEqual( + len(result["diff"][0]["deploy"][0]["mmudigon-unnumbered"]), 2 + ) + + # Validate create responses + for resp in result["response"]: + self.assertEqual(resp["RETURN_CODE"], 200) + + def test_dcnm_intra_links_unnumbered_merged_new(self): + + # load the json from playbooks + self.config_data = loadPlaybookData("dcnm_links_configs") + self.payloads_data = loadPlaybookData("dcnm_links_payloads") + + # load required config data + self.playbook_config = self.config_data.get("intra_merge_unnum_config") + self.mock_ip_sn = self.payloads_data.get("mock_ip_sn") + self.mock_hn_sn = self.payloads_data.get("mock_hn_sn") + self.mock_fab_inv = self.payloads_data.get("mock_fab_inv_data") + self.mock_unnum_fab_info = self.payloads_data.get( + "mock_unnum_fab_data" + ) + + set_module_args( + dict( + state="merged", + src_fabric="mmudigon-unnumbered", + config=self.playbook_config, + ) + ) + result = self.execute_module(changed=True, failed=False) + + self.assertEqual(len(result["diff"][0]["merged"]), 2) + self.assertEqual(len(result["diff"][0]["modified"]), 0) + self.assertEqual(len(result["diff"][0]["deleted"]), 0) + self.assertEqual(len(result["diff"][0]["query"]), 0) + self.assertEqual( + len(result["diff"][0]["deploy"][0]["mmudigon-unnumbered"]), 2 + ) + + # Validate create responses + for resp in result["response"]: + self.assertEqual(resp["RETURN_CODE"], 200) + + def test_dcnm_intra_links_unnumbered_merged_new_no_deploy(self): + + # load the json from playbooks + self.config_data = loadPlaybookData("dcnm_links_configs") + self.payloads_data = loadPlaybookData("dcnm_links_payloads") + + # load required config data + self.playbook_config = self.config_data.get("intra_merge_unnum_config") + self.mock_ip_sn = self.payloads_data.get("mock_ip_sn") + self.mock_hn_sn = self.payloads_data.get("mock_hn_sn") + self.mock_fab_inv = self.payloads_data.get("mock_fab_inv_data") + self.mock_unnum_fab_info = self.payloads_data.get( + "mock_unnum_fab_data" + ) + + set_module_args( + dict( + state="merged", + src_fabric="mmudigon-unnumbered", + config=self.playbook_config, + deploy=False, + ) + ) + result = self.execute_module(changed=True, failed=False) + + self.assertEqual(len(result["diff"][0]["merged"]), 2) + self.assertEqual(len(result["diff"][0]["modified"]), 0) + self.assertEqual(len(result["diff"][0]["deleted"]), 0) + self.assertEqual(len(result["diff"][0]["query"]), 0) + self.assertEqual(len(result["diff"][0]["deploy"]), 0) + + # Validate create responses + for resp in result["response"]: + self.assertEqual(resp["RETURN_CODE"], 200) + + def test_dcnm_intra_links_unnumbered_merged_existing(self): + + # load the json from playbooks + self.config_data = loadPlaybookData("dcnm_links_configs") + self.payloads_data = loadPlaybookData("dcnm_links_payloads") + + # load required config data + self.playbook_config = self.config_data.get("intra_merge_unnum_config") + self.mock_ip_sn = self.payloads_data.get("mock_ip_sn") + self.mock_hn_sn = self.payloads_data.get("mock_hn_sn") + self.mock_fab_inv = self.payloads_data.get("mock_fab_inv_data") + self.mock_unnum_fab_info = self.payloads_data.get( + "mock_unnum_fab_data" + ) + + set_module_args( + dict( + state="merged", + src_fabric="mmudigon-unnumbered", + config=self.playbook_config, + ) + ) + result = self.execute_module(changed=True, failed=False) + + self.assertEqual(len(result["diff"][0]["merged"]), 0) + self.assertEqual(len(result["diff"][0]["modified"]), 0) + self.assertEqual(len(result["diff"][0]["deleted"]), 0) + self.assertEqual(len(result["diff"][0]["query"]), 0) + self.assertEqual( + len(result["diff"][0]["deploy"][0]["mmudigon-unnumbered"]), 2 + ) + + # Validate create responses + for resp in result["response"]: + self.assertEqual(resp["RETURN_CODE"], 200) + + def test_dcnm_intra_links_unnumbered_merged_new_no_state(self): + + # load the json from playbooks + self.config_data = loadPlaybookData("dcnm_links_configs") + self.payloads_data = loadPlaybookData("dcnm_links_payloads") + + # load required config data + self.playbook_config = self.config_data.get("intra_merge_unnum_config") + self.mock_ip_sn = self.payloads_data.get("mock_ip_sn") + self.mock_hn_sn = self.payloads_data.get("mock_hn_sn") + self.mock_fab_inv = self.payloads_data.get("mock_fab_inv_data") + self.mock_unnum_fab_info = self.payloads_data.get( + "mock_unnum_fab_data" + ) + + set_module_args( + dict( + state="merged", + src_fabric="mmudigon-unnumbered", + config=self.playbook_config, + ) + ) + result = self.execute_module(changed=True, failed=False) + + self.assertEqual(len(result["diff"][0]["merged"]), 2) + self.assertEqual(len(result["diff"][0]["modified"]), 0) + self.assertEqual(len(result["diff"][0]["deleted"]), 0) + self.assertEqual(len(result["diff"][0]["query"]), 0) + self.assertEqual( + len(result["diff"][0]["deploy"][0]["mmudigon-unnumbered"]), 2 + ) + + # Validate create responses + for resp in result["response"]: + self.assertEqual(resp["RETURN_CODE"], 200) + + def test_dcnm_intra_links_unnumbered_merged_new_check_mode(self): + + # load the json from playbooks + self.config_data = loadPlaybookData("dcnm_links_configs") + self.payloads_data = loadPlaybookData("dcnm_links_payloads") + + # load required config data + self.playbook_config = self.config_data.get("intra_merge_unnum_config") + self.mock_ip_sn = self.payloads_data.get("mock_ip_sn") + self.mock_hn_sn = self.payloads_data.get("mock_hn_sn") + self.mock_fab_inv = self.payloads_data.get("mock_fab_inv_data") + self.mock_unnum_fab_info = self.payloads_data.get( + "mock_unnum_fab_data" + ) + + set_module_args( + dict( + _ansible_check_mode=True, + state="merged", + src_fabric="mmudigon-unnumbered", + config=self.playbook_config, + ) + ) + result = self.execute_module(changed=False, failed=False) + + self.assertEqual(len(result["diff"][0]["merged"]), 2) + self.assertEqual(len(result["diff"][0]["modified"]), 0) + self.assertEqual(len(result["diff"][0]["deleted"]), 0) + self.assertEqual(len(result["diff"][0]["query"]), 0) + self.assertEqual( + len(result["diff"][0]["deploy"][0]["mmudigon-unnumbered"]), 2 + ) + + # Validate create responses + for resp in result["response"]: + self.assertEqual(resp["RETURN_CODE"], 200) + + def test_dcnm_intra_links_unnumbered_merged_new_existing_and_non_existing( + self + ): + + # load the json from playbooks + self.config_data = loadPlaybookData("dcnm_links_configs") + self.payloads_data = loadPlaybookData("dcnm_links_payloads") + + # load required config data + self.playbook_config = self.config_data.get("intra_merge_unnum_config") + self.mock_ip_sn = self.payloads_data.get("mock_ip_sn") + self.mock_hn_sn = self.payloads_data.get("mock_hn_sn") + self.mock_fab_inv = self.payloads_data.get("mock_fab_inv_data") + self.mock_unnum_fab_info = self.payloads_data.get( + "mock_unnum_fab_data" + ) + + set_module_args( + dict( + state="merged", + src_fabric="mmudigon-unnumbered", + config=self.playbook_config, + ) + ) + + result = self.execute_module(changed=True, failed=False) + + self.assertEqual(len(result["diff"][0]["merged"]), 1) + self.assertEqual(len(result["diff"][0]["modified"]), 0) + self.assertEqual(len(result["diff"][0]["deleted"]), 0) + self.assertEqual(len(result["diff"][0]["query"]), 0) + self.assertEqual( + len(result["diff"][0]["deploy"][0]["mmudigon-unnumbered"]), 2 + ) + + # Validate create responses + for resp in result["response"]: + self.assertEqual(resp["RETURN_CODE"], 200) + + def test_dcnm_intra_links_unnumbered_modify_existing(self): + + # load the json from playbooks + self.config_data = loadPlaybookData("dcnm_links_configs") + self.payloads_data = loadPlaybookData("dcnm_links_payloads") + + # load required config data + self.playbook_config = self.config_data.get( + "intra_modify_unnum_config" + ) + self.mock_ip_sn = self.payloads_data.get("mock_ip_sn") + self.mock_hn_sn = self.payloads_data.get("mock_hn_sn") + self.mock_fab_inv = self.payloads_data.get("mock_fab_inv_data") + self.mock_unnum_fab_info = self.payloads_data.get( + "mock_unnum_fab_data" + ) + + set_module_args( + dict( + state="merged", + src_fabric="mmudigon-unnumbered", + config=self.playbook_config, + ) + ) + + result = self.execute_module(changed=True, failed=False) + + self.assertEqual(len(result["diff"][0]["merged"]), 0) + self.assertEqual(len(result["diff"][0]["modified"]), 1) + self.assertEqual(len(result["diff"][0]["deleted"]), 0) + self.assertEqual(len(result["diff"][0]["query"]), 0) + self.assertEqual( + len(result["diff"][0]["deploy"][0]["mmudigon-unnumbered"]), 2 + ) + + # Validate create responses + for resp in result["response"]: + self.assertEqual(resp["RETURN_CODE"], 200) + + def test_dcnm_intra_links_unnumbered_replace_existing(self): + + # load the json from playbooks + self.config_data = loadPlaybookData("dcnm_links_configs") + self.payloads_data = loadPlaybookData("dcnm_links_payloads") + + # load required config data + self.playbook_config = self.config_data.get( + "intra_replace_unnum_config" + ) + self.mock_ip_sn = self.payloads_data.get("mock_ip_sn") + self.mock_hn_sn = self.payloads_data.get("mock_hn_sn") + self.mock_fab_inv = self.payloads_data.get("mock_fab_inv_data") + self.mock_unnum_fab_info = self.payloads_data.get( + "mock_unnum_fab_data" + ) + + set_module_args( + dict( + state="replaced", + src_fabric="mmudigon-unnumbered", + config=self.playbook_config, + ) + ) + + result = self.execute_module(changed=True, failed=False) + + self.assertEqual(len(result["diff"][0]["merged"]), 0) + self.assertEqual(len(result["diff"][0]["modified"]), 1) + self.assertEqual(len(result["diff"][0]["deleted"]), 0) + self.assertEqual(len(result["diff"][0]["query"]), 0) + self.assertEqual( + len(result["diff"][0]["deploy"][0]["mmudigon-unnumbered"]), 2 + ) + + # Validate create responses + for resp in result["response"]: + self.assertEqual(resp["RETURN_CODE"], 200) + + def test_dcnm_intra_links_unnumbered_delete_existing(self): + + # load the json from playbooks + self.config_data = loadPlaybookData("dcnm_links_configs") + self.payloads_data = loadPlaybookData("dcnm_links_payloads") + + # load required config data + self.playbook_config = self.config_data.get( + "intra_delete_unnum_config" + ) + self.mock_ip_sn = self.payloads_data.get("mock_ip_sn") + self.mock_hn_sn = self.payloads_data.get("mock_hn_sn") + self.mock_fab_inv = self.payloads_data.get("mock_fab_inv_data") + self.mock_unnum_fab_info = self.payloads_data.get( + "mock_unnum_fab_data" + ) + + set_module_args( + dict( + state="deleted", + src_fabric="mmudigon-unnumbered", + config=self.playbook_config, + ) + ) + + result = self.execute_module(changed=True, failed=False) + + self.assertEqual(len(result["diff"][0]["merged"]), 0) + self.assertEqual(len(result["diff"][0]["modified"]), 0) + self.assertEqual(len(result["diff"][0]["deleted"]), 2) + self.assertEqual(len(result["diff"][0]["query"]), 0) + self.assertEqual( + len(result["diff"][0]["deploy"][0]["mmudigon-unnumbered"]), 2 + ) + + # Validate delete responses + for resp in result["response"]: + self.assertEqual(resp["RETURN_CODE"], 200) + + def test_dcnm_intra_links_unnumbered_delete_existing_and_non_existing( + self + ): + + # load the json from playbooks + self.config_data = loadPlaybookData("dcnm_links_configs") + self.payloads_data = loadPlaybookData("dcnm_links_payloads") + + # load required config data + self.playbook_config = self.config_data.get( + "intra_delete_unnum_config" + ) + self.mock_ip_sn = self.payloads_data.get("mock_ip_sn") + self.mock_hn_sn = self.payloads_data.get("mock_hn_sn") + self.mock_fab_inv = self.payloads_data.get("mock_fab_inv_data") + self.mock_unnum_fab_info = self.payloads_data.get( + "mock_unnum_fab_data" + ) + + set_module_args( + dict( + state="deleted", + src_fabric="mmudigon-unnumbered", + config=self.playbook_config, + ) + ) + + result = self.execute_module(changed=True, failed=False) + + self.assertEqual(len(result["diff"][0]["merged"]), 0) + self.assertEqual(len(result["diff"][0]["modified"]), 0) + self.assertEqual(len(result["diff"][0]["deleted"]), 1) + self.assertEqual(len(result["diff"][0]["query"]), 0) + self.assertEqual( + len(result["diff"][0]["deploy"][0]["mmudigon-unnumbered"]), 2 + ) + + # Validate delete responses + for resp in result["response"]: + self.assertEqual(resp["RETURN_CODE"], 200) + + def test_dcnm_intra_links_unnumbered_delete_non_existing(self): + + # load the json from playbooks + self.config_data = loadPlaybookData("dcnm_links_configs") + self.payloads_data = loadPlaybookData("dcnm_links_payloads") + + # load required config data + self.playbook_config = self.config_data.get( + "intra_delete_unnum_config" + ) + self.mock_ip_sn = self.payloads_data.get("mock_ip_sn") + self.mock_hn_sn = self.payloads_data.get("mock_hn_sn") + self.mock_fab_inv = self.payloads_data.get("mock_fab_inv_data") + self.mock_unnum_fab_info = self.payloads_data.get( + "mock_unnum_fab_data" + ) + + set_module_args( + dict( + state="deleted", + src_fabric="mmudigon-unnumbered", + config=self.playbook_config, + ) + ) + + result = self.execute_module(changed=False, failed=False) + + self.assertEqual(len(result["diff"][0]["merged"]), 0) + self.assertEqual(len(result["diff"][0]["modified"]), 0) + self.assertEqual(len(result["diff"][0]["deleted"]), 0) + self.assertEqual(len(result["diff"][0]["query"]), 0) + self.assertEqual(len(result["diff"][0]["deploy"]), 0) + + # Validate delete responses + for resp in result["response"]: + self.assertEqual(resp["RETURN_CODE"], 200) + + def test_dcnm_intra_links_unnumbered_template_change(self): + + # load the json from playbooks + self.config_data = loadPlaybookData("dcnm_links_configs") + self.payloads_data = loadPlaybookData("dcnm_links_payloads") + + # load required config data + self.playbook_config = self.config_data.get( + "intra_modify_unnum_template_config" + ) + self.mock_ip_sn = self.payloads_data.get("mock_ip_sn") + self.mock_hn_sn = self.payloads_data.get("mock_hn_sn") + self.mock_fab_inv = self.payloads_data.get("mock_fab_inv_data") + self.mock_unnum_fab_info = self.payloads_data.get( + "mock_unnum_fab_data" + ) + + set_module_args( + dict( + state="merged", + src_fabric="mmudigon-unnumbered", + config=self.playbook_config, + ) + ) + + result = self.execute_module(changed=True, failed=False) + + self.assertEqual(len(result["diff"][0]["merged"]), 0) + self.assertEqual(len(result["diff"][0]["modified"]), 1) + self.assertEqual(len(result["diff"][0]["deleted"]), 0) + self.assertEqual(len(result["diff"][0]["query"]), 0) + self.assertEqual( + len(result["diff"][0]["deploy"][0]["mmudigon-unnumbered"]), 2 + ) + + # Validate delete responses + for resp in result["response"]: + self.assertEqual(resp["RETURN_CODE"], 200) + + def test_dcnm_intra_links_unnumbered_query_not_exist(self): + + # load the json from playbooks + self.config_data = loadPlaybookData("dcnm_links_configs") + self.payloads_data = loadPlaybookData("dcnm_links_payloads") + + # load required config data + self.playbook_config = self.config_data.get( + "intra_query_unnum_not_exist" + ) + self.mock_ip_sn = self.payloads_data.get("mock_ip_sn") + self.mock_hn_sn = self.payloads_data.get("mock_hn_sn") + self.mock_fab_inv = self.payloads_data.get("mock_fab_inv_data") + self.mock_unnum_fab_info = self.payloads_data.get( + "mock_unnum_fab_data" + ) + + set_module_args( + dict( + state="query", + src_fabric="mmudigon-unnumbered", + config=self.playbook_config, + ) + ) + + result = self.execute_module(changed=False, failed=False) + + self.assertEqual(len(result["diff"][0]["merged"]), 0) + self.assertEqual(len(result["diff"][0]["modified"]), 0) + self.assertEqual(len(result["diff"][0]["deleted"]), 0) + self.assertEqual((len(result["response"]) == 0), True) + self.assertEqual(len(result["diff"][0]["deploy"]), 0) + + def test_dcnm_intra_links_unnumbered_query_no_config(self): + + # load the json from playbooks + self.config_data = loadPlaybookData("dcnm_links_configs") + self.payloads_data = loadPlaybookData("dcnm_links_payloads") + + # load required config data + self.playbook_config = [] + self.mock_ip_sn = self.payloads_data.get("mock_ip_sn") + self.mock_hn_sn = self.payloads_data.get("mock_hn_sn") + self.mock_fab_inv = self.payloads_data.get("mock_fab_inv_data") + self.mock_unnum_fab_info = self.payloads_data.get( + "mock_unnum_fab_data" + ) + + set_module_args( + dict( + state="query", + src_fabric="mmudigon-unnumbered", + config=self.playbook_config, + ) + ) + + result = self.execute_module(changed=False, failed=False) + + self.assertEqual(len(result["diff"][0]["merged"]), 0) + self.assertEqual(len(result["diff"][0]["modified"]), 0) + self.assertEqual(len(result["diff"][0]["deleted"]), 0) + self.assertEqual((len(result["response"]) == 2), True) + self.assertEqual(len(result["diff"][0]["deploy"]), 0) + + def test_dcnm_intra_links_unnumbered_query_with_dst_fabric(self): + + # load the json from playbooks + self.config_data = loadPlaybookData("dcnm_links_configs") + self.payloads_data = loadPlaybookData("dcnm_links_payloads") + + # load required config data + self.playbook_config = self.config_data.get( + "intra_query_unnum_dest_fabric_config" + ) + self.mock_ip_sn = self.payloads_data.get("mock_ip_sn") + self.mock_hn_sn = self.payloads_data.get("mock_hn_sn") + self.mock_fab_inv = self.payloads_data.get("mock_fab_inv_data") + self.mock_unnum_fab_info = self.payloads_data.get( + "mock_unnum_fab_data" + ) + + set_module_args( + dict( + state="query", + src_fabric="mmudigon-unnumbered", + config=self.playbook_config, + ) + ) + + result = self.execute_module(changed=False, failed=False) + + self.assertEqual(len(result["diff"][0]["merged"]), 0) + self.assertEqual(len(result["diff"][0]["modified"]), 0) + self.assertEqual(len(result["diff"][0]["deleted"]), 0) + self.assertEqual((len(result["response"]) == 2), True) + self.assertEqual(len(result["diff"][0]["deploy"]), 0) + + def test_dcnm_intra_links_unnumbered_query_with_src_device(self): + + # load the json from playbooks + self.config_data = loadPlaybookData("dcnm_links_configs") + self.payloads_data = loadPlaybookData("dcnm_links_payloads") + + # load required config data + self.playbook_config = self.config_data.get( + "intra_query_unnum_src_dev_config" + ) + self.mock_ip_sn = self.payloads_data.get("mock_ip_sn") + self.mock_hn_sn = self.payloads_data.get("mock_hn_sn") + self.mock_fab_inv = self.payloads_data.get("mock_fab_inv_data") + self.mock_unnum_fab_info = self.payloads_data.get( + "mock_unnum_fab_data" + ) + + set_module_args( + dict( + state="query", + src_fabric="mmudigon-unnumbered", + config=self.playbook_config, + ) + ) + + result = self.execute_module(changed=False, failed=False) + + self.assertEqual(len(result["diff"][0]["merged"]), 0) + self.assertEqual(len(result["diff"][0]["modified"]), 0) + self.assertEqual(len(result["diff"][0]["deleted"]), 0) + self.assertEqual((len(result["response"]) == 2), True) + self.assertEqual(len(result["diff"][0]["deploy"]), 0) + + def test_dcnm_intra_links_unnumbered_query_with_dst_device(self): + + # load the json from playbooks + self.config_data = loadPlaybookData("dcnm_links_configs") + self.payloads_data = loadPlaybookData("dcnm_links_payloads") + + # load required config data + self.playbook_config = self.config_data.get( + "intra_query_unnum_dst_dev_config" + ) + self.mock_ip_sn = self.payloads_data.get("mock_ip_sn") + self.mock_hn_sn = self.payloads_data.get("mock_hn_sn") + self.mock_fab_inv = self.payloads_data.get("mock_fab_inv_data") + self.mock_unnum_fab_info = self.payloads_data.get( + "mock_unnum_fab_data" + ) + + set_module_args( + dict( + state="query", + src_fabric="mmudigon-unnumbered", + config=self.playbook_config, + ) + ) + + result = self.execute_module(changed=False, failed=False) + + self.assertEqual(len(result["diff"][0]["merged"]), 0) + self.assertEqual(len(result["diff"][0]["modified"]), 0) + self.assertEqual(len(result["diff"][0]["deleted"]), 0) + self.assertEqual((len(result["response"]) == 2), True) + self.assertEqual(len(result["diff"][0]["deploy"]), 0) + + def test_dcnm_intra_links_unnumbered_query_with_src_interface(self): + + # load the json from playbooks + self.config_data = loadPlaybookData("dcnm_links_configs") + self.payloads_data = loadPlaybookData("dcnm_links_payloads") + + # load required config data + self.playbook_config = self.config_data.get( + "intra_query_unnum_src_intf_config" + ) + self.mock_ip_sn = self.payloads_data.get("mock_ip_sn") + self.mock_hn_sn = self.payloads_data.get("mock_hn_sn") + self.mock_fab_inv = self.payloads_data.get("mock_fab_inv_data") + self.mock_unnum_fab_info = self.payloads_data.get( + "mock_unnum_fab_data" + ) + + set_module_args( + dict( + state="query", + src_fabric="mmudigon-unnumbered", + config=self.playbook_config, + ) + ) + + result = self.execute_module(changed=False, failed=False) + + self.assertEqual(len(result["diff"][0]["merged"]), 0) + self.assertEqual(len(result["diff"][0]["modified"]), 0) + self.assertEqual(len(result["diff"][0]["deleted"]), 0) + self.assertEqual((len(result["response"]) == 1), True) + self.assertEqual(len(result["diff"][0]["deploy"]), 0) + + def test_dcnm_intra_links_unnumbered_query_with_dst_interface(self): + + # load the json from playbooks + self.config_data = loadPlaybookData("dcnm_links_configs") + self.payloads_data = loadPlaybookData("dcnm_links_payloads") + + # load required config data + self.playbook_config = self.config_data.get( + "intra_query_unnum_dst_intf_config" + ) + self.mock_ip_sn = self.payloads_data.get("mock_ip_sn") + self.mock_hn_sn = self.payloads_data.get("mock_hn_sn") + self.mock_fab_inv = self.payloads_data.get("mock_fab_inv_data") + self.mock_unnum_fab_info = self.payloads_data.get( + "mock_unnum_fab_data" + ) + + set_module_args( + dict( + state="query", + src_fabric="mmudigon-unnumbered", + config=self.playbook_config, + ) + ) + + result = self.execute_module(changed=False, failed=False) + + self.assertEqual(len(result["diff"][0]["merged"]), 0) + self.assertEqual(len(result["diff"][0]["modified"]), 0) + self.assertEqual(len(result["diff"][0]["deleted"]), 0) + self.assertEqual((len(result["response"]) == 1), True) + self.assertEqual(len(result["diff"][0]["deploy"]), 0) + + def test_dcnm_intra_links_unnumbered_query(self): + + # load the json from playbooks + self.config_data = loadPlaybookData("dcnm_links_configs") + self.payloads_data = loadPlaybookData("dcnm_links_payloads") + + # load required config data + self.playbook_config = self.config_data.get("intra_query_unnum_config") + self.mock_ip_sn = self.payloads_data.get("mock_ip_sn") + self.mock_hn_sn = self.payloads_data.get("mock_hn_sn") + self.mock_fab_inv = self.payloads_data.get("mock_fab_inv_data") + self.mock_unnum_fab_info = self.payloads_data.get( + "mock_unnum_fab_data" + ) + + set_module_args( + dict( + state="query", + src_fabric="mmudigon-unnumbered", + config=self.playbook_config, + ) + ) + + result = self.execute_module(changed=False, failed=False) + + self.assertEqual(len(result["diff"][0]["merged"]), 0) + self.assertEqual(len(result["diff"][0]["modified"]), 0) + self.assertEqual(len(result["diff"][0]["deleted"]), 0) + self.assertEqual((len(result["response"]) == 2), True) + self.assertEqual(len(result["diff"][0]["deploy"]), 0) + + # ------------------------- INTRA-FABRIC-IPV6 ----------------------------------- + + def test_dcnm_intra_links_ipv6_merged_new_no_opts(self): + + # load the json from playbooks + self.config_data = loadPlaybookData("dcnm_links_configs") + self.payloads_data = loadPlaybookData("dcnm_links_payloads") + + # load required config data + self.playbook_config = self.config_data.get( + "intra_merge_ipv6_no_opts_config" + ) + self.mock_ip_sn = self.payloads_data.get("mock_ip_sn") + self.mock_hn_sn = self.payloads_data.get("mock_hn_sn") + self.mock_fab_inv = self.payloads_data.get("mock_fab_inv_data") + self.mock_ipv6_fab_info = self.payloads_data.get("mock_ipv6_fab_data") + + set_module_args( + dict( + state="merged", + src_fabric="mmudigon-ipv6-underlay", + config=self.playbook_config, + ) + ) + result = self.execute_module(changed=True, failed=False) + + self.assertEqual(len(result["diff"][0]["merged"]), 3) + self.assertEqual(len(result["diff"][0]["modified"]), 0) + self.assertEqual(len(result["diff"][0]["deleted"]), 0) + self.assertEqual(len(result["diff"][0]["query"]), 0) + self.assertEqual( + len(result["diff"][0]["deploy"][0]["mmudigon-ipv6-underlay"]), 2 + ) + + # Validate create responses + for resp in result["response"]: + self.assertEqual(resp["RETURN_CODE"], 200) + + def test_dcnm_intra_links_ipv6_merged_new(self): + + # load the json from playbooks + self.config_data = loadPlaybookData("dcnm_links_configs") + self.payloads_data = loadPlaybookData("dcnm_links_payloads") + + # load required config data + self.playbook_config = self.config_data.get("intra_merge_ipv6_config") + self.mock_ip_sn = self.payloads_data.get("mock_ip_sn") + self.mock_hn_sn = self.payloads_data.get("mock_hn_sn") + self.mock_fab_inv = self.payloads_data.get("mock_fab_inv_data") + self.mock_ipv6_fab_info = self.payloads_data.get("mock_ipv6_fab_data") + + set_module_args( + dict( + state="merged", + src_fabric="mmudigon-ipv6-underlay", + config=self.playbook_config, + ) + ) + result = self.execute_module(changed=True, failed=False) + + self.assertEqual(len(result["diff"][0]["merged"]), 3) + self.assertEqual(len(result["diff"][0]["modified"]), 0) + self.assertEqual(len(result["diff"][0]["deleted"]), 0) + self.assertEqual(len(result["diff"][0]["query"]), 0) + self.assertEqual( + len(result["diff"][0]["deploy"][0]["mmudigon-ipv6-underlay"]), 2 + ) + + # Validate create responses + for resp in result["response"]: + self.assertEqual(resp["RETURN_CODE"], 200) + + def test_dcnm_intra_links_ipv6_merged_existing(self): + + # load the json from playbooks + self.config_data = loadPlaybookData("dcnm_links_configs") + self.payloads_data = loadPlaybookData("dcnm_links_payloads") + + # load required config data + self.playbook_config = self.config_data.get("intra_merge_ipv6_config") + self.mock_ip_sn = self.payloads_data.get("mock_ip_sn") + self.mock_hn_sn = self.payloads_data.get("mock_hn_sn") + self.mock_fab_inv = self.payloads_data.get("mock_fab_inv_data") + self.mock_ipv6_fab_info = self.payloads_data.get("mock_ipv6_fab_data") + + set_module_args( + dict( + state="merged", + src_fabric="mmudigon-ipv6-underlay", + config=self.playbook_config, + ) + ) + result = self.execute_module(changed=True, failed=False) + + self.assertEqual(len(result["diff"][0]["merged"]), 0) + self.assertEqual(len(result["diff"][0]["modified"]), 0) + self.assertEqual(len(result["diff"][0]["deleted"]), 0) + self.assertEqual(len(result["diff"][0]["query"]), 0) + self.assertEqual( + len(result["diff"][0]["deploy"][0]["mmudigon-ipv6-underlay"]), 2 + ) + + # Validate create responses + for resp in result["response"]: + self.assertEqual(resp["RETURN_CODE"], 200) + + def test_dcnm_intra_links_ipv6_merged_new_no_state(self): + + # load the json from playbooks + self.config_data = loadPlaybookData("dcnm_links_configs") + self.payloads_data = loadPlaybookData("dcnm_links_payloads") + + # load required config data + self.playbook_config = self.config_data.get("intra_merge_ipv6_config") + self.mock_ip_sn = self.payloads_data.get("mock_ip_sn") + self.mock_hn_sn = self.payloads_data.get("mock_hn_sn") + self.mock_fab_inv = self.payloads_data.get("mock_fab_inv_data") + self.mock_ipv6_fab_info = self.payloads_data.get("mock_ipv6_fab_data") + + set_module_args( + dict( + state="merged", + src_fabric="mmudigon-ipv6-underlay", + config=self.playbook_config, + ) + ) + result = self.execute_module(changed=True, failed=False) + + self.assertEqual(len(result["diff"][0]["merged"]), 3) + self.assertEqual(len(result["diff"][0]["modified"]), 0) + self.assertEqual(len(result["diff"][0]["deleted"]), 0) + self.assertEqual(len(result["diff"][0]["query"]), 0) + self.assertEqual( + len(result["diff"][0]["deploy"][0]["mmudigon-ipv6-underlay"]), 2 + ) + + # Validate create responses + for resp in result["response"]: + self.assertEqual(resp["RETURN_CODE"], 200) + + def test_dcnm_intra_links_ipv6_merged_new_check_mode(self): + + # load the json from playbooks + self.config_data = loadPlaybookData("dcnm_links_configs") + self.payloads_data = loadPlaybookData("dcnm_links_payloads") + + # load required config data + self.playbook_config = self.config_data.get("intra_merge_ipv6_config") + self.mock_ip_sn = self.payloads_data.get("mock_ip_sn") + self.mock_hn_sn = self.payloads_data.get("mock_hn_sn") + self.mock_fab_inv = self.payloads_data.get("mock_fab_inv_data") + self.mock_ipv6_fab_info = self.payloads_data.get("mock_ipv6_fab_data") + + set_module_args( + dict( + _ansible_check_mode=True, + state="merged", + src_fabric="mmudigon-ipv6-underlay", + config=self.playbook_config, + ) + ) + result = self.execute_module(changed=False, failed=False) + + self.assertEqual(len(result["diff"][0]["merged"]), 3) + self.assertEqual(len(result["diff"][0]["modified"]), 0) + self.assertEqual(len(result["diff"][0]["deleted"]), 0) + self.assertEqual(len(result["diff"][0]["query"]), 0) + self.assertEqual( + len(result["diff"][0]["deploy"][0]["mmudigon-ipv6-underlay"]), 2 + ) + + # Validate create responses + for resp in result["response"]: + self.assertEqual(resp["RETURN_CODE"], 200) + + def test_dcnm_intra_links_ipv6_merged_new_existing_and_non_existing(self): + + # load the json from playbooks + self.config_data = loadPlaybookData("dcnm_links_configs") + self.payloads_data = loadPlaybookData("dcnm_links_payloads") + + # load required config data + self.playbook_config = self.config_data.get("intra_merge_ipv6_config") + self.mock_ip_sn = self.payloads_data.get("mock_ip_sn") + self.mock_hn_sn = self.payloads_data.get("mock_hn_sn") + self.mock_fab_inv = self.payloads_data.get("mock_fab_inv_data") + self.mock_ipv6_fab_info = self.payloads_data.get("mock_ipv6_fab_data") + + set_module_args( + dict( + state="merged", + src_fabric="mmudigon-ipv6-underlay", + config=self.playbook_config, + ) + ) + + result = self.execute_module(changed=True, failed=False) + + self.assertEqual(len(result["diff"][0]["merged"]), 1) + self.assertEqual(len(result["diff"][0]["modified"]), 0) + self.assertEqual(len(result["diff"][0]["deleted"]), 0) + self.assertEqual(len(result["diff"][0]["query"]), 0) + self.assertEqual( + len(result["diff"][0]["deploy"][0]["mmudigon-ipv6-underlay"]), 2 + ) + + # Validate create responses + for resp in result["response"]: + self.assertEqual(resp["RETURN_CODE"], 200) + + def test_dcnm_intra_links_ipv6_modify_existing(self): + + # load the json from playbooks + self.config_data = loadPlaybookData("dcnm_links_configs") + self.payloads_data = loadPlaybookData("dcnm_links_payloads") + + # load required config data + self.playbook_config = self.config_data.get("intra_modify_ipv6_config") + self.mock_ip_sn = self.payloads_data.get("mock_ip_sn") + self.mock_hn_sn = self.payloads_data.get("mock_hn_sn") + self.mock_fab_inv = self.payloads_data.get("mock_fab_inv_data") + self.mock_ipv6_fab_info = self.payloads_data.get("mock_ipv6_fab_data") + + set_module_args( + dict( + state="merged", + src_fabric="mmudigon-ipv6-underlay", + config=self.playbook_config, + ) + ) + + result = self.execute_module(changed=True, failed=False) + + self.assertEqual(len(result["diff"][0]["merged"]), 0) + self.assertEqual(len(result["diff"][0]["modified"]), 2) + self.assertEqual(len(result["diff"][0]["deleted"]), 0) + self.assertEqual(len(result["diff"][0]["query"]), 0) + self.assertEqual( + len(result["diff"][0]["deploy"][0]["mmudigon-ipv6-underlay"]), 2 + ) + + # Validate create responses + for resp in result["response"]: + self.assertEqual(resp["RETURN_CODE"], 200) + + def test_dcnm_intra_links_ipv6_replace_existing(self): + + # load the json from playbooks + self.config_data = loadPlaybookData("dcnm_links_configs") + self.payloads_data = loadPlaybookData("dcnm_links_payloads") + + # load required config data + self.playbook_config = self.config_data.get( + "intra_replace_ipv6_config" + ) + self.mock_ip_sn = self.payloads_data.get("mock_ip_sn") + self.mock_hn_sn = self.payloads_data.get("mock_hn_sn") + self.mock_fab_inv = self.payloads_data.get("mock_fab_inv_data") + self.mock_ipv6_fab_info = self.payloads_data.get("mock_ipv6_fab_data") + + set_module_args( + dict( + state="replaced", + src_fabric="mmudigon-ipv6-underlay", + config=self.playbook_config, + ) + ) + + result = self.execute_module(changed=True, failed=False) + + self.assertEqual(len(result["diff"][0]["merged"]), 0) + self.assertEqual(len(result["diff"][0]["modified"]), 2) + self.assertEqual(len(result["diff"][0]["deleted"]), 0) + self.assertEqual(len(result["diff"][0]["query"]), 0) + self.assertEqual( + len(result["diff"][0]["deploy"][0]["mmudigon-ipv6-underlay"]), 2 + ) + + # Validate create responses + for resp in result["response"]: + self.assertEqual(resp["RETURN_CODE"], 200) + + def test_dcnm_intra_links_ipv6_delete_existing(self): + + # load the json from playbooks + self.config_data = loadPlaybookData("dcnm_links_configs") + self.payloads_data = loadPlaybookData("dcnm_links_payloads") + + # load required config data + self.playbook_config = self.config_data.get("intra_delete_ipv6_config") + self.mock_ip_sn = self.payloads_data.get("mock_ip_sn") + self.mock_hn_sn = self.payloads_data.get("mock_hn_sn") + self.mock_fab_inv = self.payloads_data.get("mock_fab_inv_data") + self.mock_ipv6_fab_info = self.payloads_data.get("mock_ipv6_fab_data") + + set_module_args( + dict( + state="deleted", + src_fabric="mmudigon-ipv6-underlay", + config=self.playbook_config, + ) + ) + + result = self.execute_module(changed=True, failed=False) + + self.assertEqual(len(result["diff"][0]["merged"]), 0) + self.assertEqual(len(result["diff"][0]["modified"]), 0) + self.assertEqual(len(result["diff"][0]["deleted"]), 3) + self.assertEqual(len(result["diff"][0]["query"]), 0) + self.assertEqual( + len(result["diff"][0]["deploy"][0]["mmudigon-ipv6-underlay"]), 2 + ) + + # Validate delete responses + for resp in result["response"]: + self.assertEqual(resp["RETURN_CODE"], 200) + + def test_dcnm_intra_links_ipv6_delete_existing_and_non_existing(self): + + # load the json from playbooks + self.config_data = loadPlaybookData("dcnm_links_configs") + self.payloads_data = loadPlaybookData("dcnm_links_payloads") + + # load required config data + self.playbook_config = self.config_data.get("intra_delete_ipv6_config") + self.mock_ip_sn = self.payloads_data.get("mock_ip_sn") + self.mock_hn_sn = self.payloads_data.get("mock_hn_sn") + self.mock_fab_inv = self.payloads_data.get("mock_fab_inv_data") + self.mock_ipv6_fab_info = self.payloads_data.get("mock_ipv6_fab_data") + + set_module_args( + dict( + state="deleted", + src_fabric="mmudigon-ipv6-underlay", + config=self.playbook_config, + ) + ) + + result = self.execute_module(changed=True, failed=False) + + self.assertEqual(len(result["diff"][0]["merged"]), 0) + self.assertEqual(len(result["diff"][0]["modified"]), 0) + self.assertEqual(len(result["diff"][0]["deleted"]), 2) + self.assertEqual(len(result["diff"][0]["query"]), 0) + self.assertEqual( + len(result["diff"][0]["deploy"][0]["mmudigon-ipv6-underlay"]), 2 + ) + + # Validate delete responses + for resp in result["response"]: + self.assertEqual(resp["RETURN_CODE"], 200) + + def test_dcnm_intra_links_ipv6_delete_non_existing(self): + + # load the json from playbooks + self.config_data = loadPlaybookData("dcnm_links_configs") + self.payloads_data = loadPlaybookData("dcnm_links_payloads") + + # load required config data + self.playbook_config = self.config_data.get("intra_delete_ipv6_config") + self.mock_ip_sn = self.payloads_data.get("mock_ip_sn") + self.mock_hn_sn = self.payloads_data.get("mock_hn_sn") + self.mock_fab_inv = self.payloads_data.get("mock_fab_inv_data") + self.mock_ipv6_fab_info = self.payloads_data.get("mock_ipv6_fab_data") + + set_module_args( + dict( + state="deleted", + src_fabric="mmudigon-ipv6-underlay", + config=self.playbook_config, + ) + ) + + result = self.execute_module(changed=False, failed=False) + + self.assertEqual(len(result["diff"][0]["merged"]), 0) + self.assertEqual(len(result["diff"][0]["modified"]), 0) + self.assertEqual(len(result["diff"][0]["deleted"]), 0) + self.assertEqual(len(result["diff"][0]["query"]), 0) + self.assertEqual(len(result["diff"][0]["deploy"]), 0) + + # Validate delete responses + for resp in result["response"]: + self.assertEqual(resp["RETURN_CODE"], 200) + + def test_dcnm_intra_links_ipv6_query_no_config(self): + + # load the json from playbooks + self.config_data = loadPlaybookData("dcnm_links_configs") + self.payloads_data = loadPlaybookData("dcnm_links_payloads") + + # load required config data + self.playbook_config = [] + self.mock_ip_sn = self.payloads_data.get("mock_ip_sn") + self.mock_hn_sn = self.payloads_data.get("mock_hn_sn") + self.mock_fab_inv = self.payloads_data.get("mock_fab_inv_data") + self.mock_ipv6_fab_info = self.payloads_data.get("mock_ipv6_fab_data") + + set_module_args( + dict( + state="query", + src_fabric="mmudigon-ipv6-underlay", + config=self.playbook_config, + ) + ) + + result = self.execute_module(changed=False, failed=False) + + self.assertEqual(len(result["diff"][0]["merged"]), 0) + self.assertEqual(len(result["diff"][0]["modified"]), 0) + self.assertEqual(len(result["diff"][0]["deleted"]), 0) + self.assertEqual((len(result["response"]) == 3), True) + self.assertEqual(len(result["diff"][0]["deploy"]), 0) + + def test_dcnm_intra_links_ipv6_query_with_dst_fabric(self): + + # load the json from playbooks + self.config_data = loadPlaybookData("dcnm_links_configs") + self.payloads_data = loadPlaybookData("dcnm_links_payloads") + + # load required config data + self.playbook_config = self.config_data.get( + "intra_query_ipv6_dest_fabric_config" + ) + self.mock_ip_sn = self.payloads_data.get("mock_ip_sn") + self.mock_hn_sn = self.payloads_data.get("mock_hn_sn") + self.mock_fab_inv = self.payloads_data.get("mock_fab_inv_data") + self.mock_ipv6_fab_info = self.payloads_data.get("mock_ipv6_fab_data") + + set_module_args( + dict( + state="query", + src_fabric="mmudigon-ipv6-underlay", + config=self.playbook_config, + ) + ) + + result = self.execute_module(changed=False, failed=False) + + self.assertEqual(len(result["diff"][0]["merged"]), 0) + self.assertEqual(len(result["diff"][0]["modified"]), 0) + self.assertEqual(len(result["diff"][0]["deleted"]), 0) + self.assertEqual((len(result["response"]) == 3), True) + self.assertEqual(len(result["diff"][0]["deploy"]), 0) + + def test_dcnm_intra_links_ipv6_query_with_src_device(self): + + # load the json from playbooks + self.config_data = loadPlaybookData("dcnm_links_configs") + self.payloads_data = loadPlaybookData("dcnm_links_payloads") + + # load required config data + self.playbook_config = self.config_data.get( + "intra_query_ipv6_src_dev_config" + ) + self.mock_ip_sn = self.payloads_data.get("mock_ip_sn") + self.mock_hn_sn = self.payloads_data.get("mock_hn_sn") + self.mock_fab_inv = self.payloads_data.get("mock_fab_inv_data") + self.mock_ipv6_fab_info = self.payloads_data.get("mock_ipv6_fab_data") + + set_module_args( + dict( + state="query", + src_fabric="mmudigon-ipv6-underlay", + config=self.playbook_config, + ) + ) + + result = self.execute_module(changed=False, failed=False) + + self.assertEqual(len(result["diff"][0]["merged"]), 0) + self.assertEqual(len(result["diff"][0]["modified"]), 0) + self.assertEqual(len(result["diff"][0]["deleted"]), 0) + self.assertEqual((len(result["response"]) == 3), True) + self.assertEqual(len(result["diff"][0]["deploy"]), 0) + + def test_dcnm_intra_links_ipv6_query_with_dst_device(self): + + # load the json from playbooks + self.config_data = loadPlaybookData("dcnm_links_configs") + self.payloads_data = loadPlaybookData("dcnm_links_payloads") + + # load required config data + self.playbook_config = self.config_data.get( + "intra_query_ipv6_dst_dev_config" + ) + self.mock_ip_sn = self.payloads_data.get("mock_ip_sn") + self.mock_hn_sn = self.payloads_data.get("mock_hn_sn") + self.mock_fab_inv = self.payloads_data.get("mock_fab_inv_data") + self.mock_ipv6_fab_info = self.payloads_data.get("mock_ipv6_fab_data") + + set_module_args( + dict( + state="query", + src_fabric="mmudigon-ipv6-underlay", + config=self.playbook_config, + ) + ) + + result = self.execute_module(changed=False, failed=False) + + self.assertEqual(len(result["diff"][0]["merged"]), 0) + self.assertEqual(len(result["diff"][0]["modified"]), 0) + self.assertEqual(len(result["diff"][0]["deleted"]), 0) + self.assertEqual((len(result["response"]) == 3), True) + self.assertEqual(len(result["diff"][0]["deploy"]), 0) + + def test_dcnm_intra_links_ipv6_query_with_src_interface(self): + + # load the json from playbooks + self.config_data = loadPlaybookData("dcnm_links_configs") + self.payloads_data = loadPlaybookData("dcnm_links_payloads") + + # load required config data + self.playbook_config = self.config_data.get( + "intra_query_ipv6_src_intf_config" + ) + self.mock_ip_sn = self.payloads_data.get("mock_ip_sn") + self.mock_hn_sn = self.payloads_data.get("mock_hn_sn") + self.mock_fab_inv = self.payloads_data.get("mock_fab_inv_data") + self.mock_ipv6_fab_info = self.payloads_data.get("mock_ipv6_fab_data") + + set_module_args( + dict( + state="query", + src_fabric="mmudigon-ipv6-underlay", + config=self.playbook_config, + ) + ) + + result = self.execute_module(changed=False, failed=False) + + self.assertEqual(len(result["diff"][0]["merged"]), 0) + self.assertEqual(len(result["diff"][0]["modified"]), 0) + self.assertEqual(len(result["diff"][0]["deleted"]), 0) + self.assertEqual((len(result["response"]) == 1), True) + self.assertEqual(len(result["diff"][0]["deploy"]), 0) + + def test_dcnm_intra_links_ipv6_query_with_dst_interface(self): + + # load the json from playbooks + self.config_data = loadPlaybookData("dcnm_links_configs") + self.payloads_data = loadPlaybookData("dcnm_links_payloads") + + # load required config data + self.playbook_config = self.config_data.get( + "intra_query_ipv6_dst_intf_config" + ) + self.mock_ip_sn = self.payloads_data.get("mock_ip_sn") + self.mock_hn_sn = self.payloads_data.get("mock_hn_sn") + self.mock_fab_inv = self.payloads_data.get("mock_fab_inv_data") + self.mock_ipv6_fab_info = self.payloads_data.get("mock_ipv6_fab_data") + + set_module_args( + dict( + state="query", + src_fabric="mmudigon-ipv6-underlay", + config=self.playbook_config, + ) + ) + + result = self.execute_module(changed=False, failed=False) + + self.assertEqual(len(result["diff"][0]["merged"]), 0) + self.assertEqual(len(result["diff"][0]["modified"]), 0) + self.assertEqual(len(result["diff"][0]["deleted"]), 0) + self.assertEqual((len(result["response"]) == 1), True) + self.assertEqual(len(result["diff"][0]["deploy"]), 0) + + def test_dcnm_intra_links_ipv6_query(self): + + # load the json from playbooks + self.config_data = loadPlaybookData("dcnm_links_configs") + self.payloads_data = loadPlaybookData("dcnm_links_payloads") + + # load required config data + self.playbook_config = self.config_data.get("intra_query_ipv6_config") + self.mock_ip_sn = self.payloads_data.get("mock_ip_sn") + self.mock_hn_sn = self.payloads_data.get("mock_hn_sn") + self.mock_fab_inv = self.payloads_data.get("mock_fab_inv_data") + self.mock_ipv6_fab_info = self.payloads_data.get("mock_ipv6_fab_data") + + set_module_args( + dict( + state="query", + src_fabric="mmudigon-ipv6-underlay", + config=self.playbook_config, + ) + ) + + result = self.execute_module(changed=False, failed=False) + + self.assertEqual(len(result["diff"][0]["merged"]), 0) + self.assertEqual(len(result["diff"][0]["modified"]), 0) + self.assertEqual(len(result["diff"][0]["deleted"]), 0) + self.assertEqual((len(result["response"]) == 3), True) + self.assertEqual(len(result["diff"][0]["deploy"]), 0) + + def test_dcnm_intra_links_ipv6_query_not_exist(self): + + # load the json from playbooks + self.config_data = loadPlaybookData("dcnm_links_configs") + self.payloads_data = loadPlaybookData("dcnm_links_payloads") + + # load required config data + self.playbook_config = self.config_data.get( + "intra_query_ipv6_not_exist" + ) + self.mock_ip_sn = self.payloads_data.get("mock_ip_sn") + self.mock_hn_sn = self.payloads_data.get("mock_hn_sn") + self.mock_fab_inv = self.payloads_data.get("mock_fab_inv_data") + self.mock_ipv6_fab_info = self.payloads_data.get("mock_ipv6_fab_data") + + set_module_args( + dict( + state="query", + src_fabric="mmudigon-unnumbered", + config=self.playbook_config, + ) + ) + + result = self.execute_module(changed=False, failed=False) + + self.assertEqual(len(result["diff"][0]["merged"]), 0) + self.assertEqual(len(result["diff"][0]["modified"]), 0) + self.assertEqual(len(result["diff"][0]["deleted"]), 0) + self.assertEqual((len(result["response"]) == 0), True) + self.assertEqual(len(result["diff"][0]["deploy"]), 0) + + # -------------------------- INTRA-FABRIC-VPC --------------------------------- + + def test_dcnm_intra_links_vpc_merged_new_no_opts(self): + + # load the json from playbooks + self.config_data = loadPlaybookData("dcnm_links_configs") + self.payloads_data = loadPlaybookData("dcnm_links_payloads") + + # load required config data + self.playbook_config = self.config_data.get( + "intra_merge_vpc_no_opts_config" + ) + self.mock_ip_sn = self.payloads_data.get("mock_ip_sn") + self.mock_hn_sn = self.payloads_data.get("mock_hn_sn") + self.mock_fab_inv = self.payloads_data.get("mock_fab_inv_data") + self.mock_num_fab_info = self.payloads_data.get("mock_num_fab_data") + + set_module_args( + dict( + state="merged", + src_fabric="mmudigon", + config=self.playbook_config, + ) + ) + result = self.execute_module(changed=True, failed=False) + + self.assertEqual(len(result["diff"][0]["merged"]), 1) + self.assertEqual(len(result["diff"][0]["modified"]), 0) + self.assertEqual(len(result["diff"][0]["deleted"]), 0) + self.assertEqual(len(result["diff"][0]["query"]), 0) + self.assertEqual(len(result["diff"][0]["deploy"][0]["mmudigon"]), 2) + + # Validate create responses + for resp in result["response"]: + self.assertEqual(resp["RETURN_CODE"], 200) + + def test_dcnm_intra_links_vpc_merged_new(self): + + # load the json from playbooks + self.config_data = loadPlaybookData("dcnm_links_configs") + self.payloads_data = loadPlaybookData("dcnm_links_payloads") + + # load required config data + self.playbook_config = self.config_data.get("intra_merge_vpc_config") + self.mock_ip_sn = self.payloads_data.get("mock_ip_sn") + self.mock_hn_sn = self.payloads_data.get("mock_hn_sn") + self.mock_fab_inv = self.payloads_data.get("mock_fab_inv_data") + self.mock_num_fab_info = self.payloads_data.get("mock_num_fab_data") + + set_module_args( + dict( + state="merged", + src_fabric="mmudigon", + config=self.playbook_config, + ) + ) + result = self.execute_module(changed=True, failed=False) + + self.assertEqual(len(result["diff"][0]["merged"]), 1) + self.assertEqual(len(result["diff"][0]["modified"]), 0) + self.assertEqual(len(result["diff"][0]["deleted"]), 0) + self.assertEqual(len(result["diff"][0]["query"]), 0) + self.assertEqual(len(result["diff"][0]["deploy"][0]["mmudigon"]), 2) + + # Validate create responses + for resp in result["response"]: + self.assertEqual(resp["RETURN_CODE"], 200) + + def test_dcnm_intra_links_vpc_merged_existing(self): + + # load the json from playbooks + self.config_data = loadPlaybookData("dcnm_links_configs") + self.payloads_data = loadPlaybookData("dcnm_links_payloads") + + # load required config data + self.playbook_config = self.config_data.get("intra_merge_vpc_config") + self.mock_ip_sn = self.payloads_data.get("mock_ip_sn") + self.mock_hn_sn = self.payloads_data.get("mock_hn_sn") + self.mock_fab_inv = self.payloads_data.get("mock_fab_inv_data") + self.mock_num_fab_info = self.payloads_data.get("mock_num_fab_data") + + set_module_args( + dict( + state="merged", + src_fabric="mmudigon", + config=self.playbook_config, + ) + ) + result = self.execute_module(changed=True, failed=False) + + self.assertEqual(len(result["diff"][0]["merged"]), 0) + self.assertEqual(len(result["diff"][0]["modified"]), 0) + self.assertEqual(len(result["diff"][0]["deleted"]), 0) + self.assertEqual(len(result["diff"][0]["query"]), 0) + self.assertEqual(len(result["diff"][0]["deploy"][0]["mmudigon"]), 2) + + # Validate create responses + for resp in result["response"]: + self.assertEqual(resp["RETURN_CODE"], 200) + + def test_dcnm_intra_links_vpc_merged_new_no_state(self): + + # load the json from playbooks + self.config_data = loadPlaybookData("dcnm_links_configs") + self.payloads_data = loadPlaybookData("dcnm_links_payloads") + + # load required config data + self.playbook_config = self.config_data.get("intra_merge_vpc_config") + self.mock_ip_sn = self.payloads_data.get("mock_ip_sn") + self.mock_hn_sn = self.payloads_data.get("mock_hn_sn") + self.mock_fab_inv = self.payloads_data.get("mock_fab_inv_data") + self.mock_num_fab_info = self.payloads_data.get("mock_num_fab_data") + + set_module_args( + dict( + state="merged", + src_fabric="mmudigon", + config=self.playbook_config, + ) + ) + result = self.execute_module(changed=True, failed=False) + + self.assertEqual(len(result["diff"][0]["merged"]), 1) + self.assertEqual(len(result["diff"][0]["modified"]), 0) + self.assertEqual(len(result["diff"][0]["deleted"]), 0) + self.assertEqual(len(result["diff"][0]["query"]), 0) + self.assertEqual(len(result["diff"][0]["deploy"][0]["mmudigon"]), 2) + + # Validate create responses + for resp in result["response"]: + self.assertEqual(resp["RETURN_CODE"], 200) + + def test_dcnm_intra_links_vpc_merged_new_check_mode(self): + + # load the json from playbooks + self.config_data = loadPlaybookData("dcnm_links_configs") + self.payloads_data = loadPlaybookData("dcnm_links_payloads") + + # load required config data + self.playbook_config = self.config_data.get("intra_merge_vpc_config") + self.mock_ip_sn = self.payloads_data.get("mock_ip_sn") + self.mock_hn_sn = self.payloads_data.get("mock_hn_sn") + self.mock_fab_inv = self.payloads_data.get("mock_fab_inv_data") + self.mock_num_fab_info = self.payloads_data.get("mock_num_fab_data") + + set_module_args( + dict( + _ansible_check_mode=True, + state="merged", + src_fabric="mmudigon", + config=self.playbook_config, + ) + ) + result = self.execute_module(changed=False, failed=False) + + self.assertEqual(len(result["diff"][0]["merged"]), 1) + self.assertEqual(len(result["diff"][0]["modified"]), 0) + self.assertEqual(len(result["diff"][0]["deleted"]), 0) + self.assertEqual(len(result["diff"][0]["query"]), 0) + self.assertEqual(len(result["diff"][0]["deploy"][0]["mmudigon"]), 2) + + # Validate create responses + for resp in result["response"]: + self.assertEqual(resp["RETURN_CODE"], 200) + + def test_dcnm_intra_links_vpc_merged_new_existing_and_non_existing(self): + + # load the json from playbooks + self.config_data = loadPlaybookData("dcnm_links_configs") + self.payloads_data = loadPlaybookData("dcnm_links_payloads") + + # load required config data + self.playbook_config = self.config_data.get("intra_merge_vpc_config") + self.mock_ip_sn = self.payloads_data.get("mock_ip_sn") + self.mock_hn_sn = self.payloads_data.get("mock_hn_sn") + self.mock_fab_inv = self.payloads_data.get("mock_fab_inv_data") + self.mock_num_fab_info = self.payloads_data.get("mock_num_fab_data") + + set_module_args( + dict( + state="merged", + src_fabric="mmudigon", + config=self.playbook_config, + ) + ) + + result = self.execute_module(changed=True, failed=False) + + self.assertEqual(len(result["diff"][0]["merged"]), 1) + self.assertEqual(len(result["diff"][0]["modified"]), 0) + self.assertEqual(len(result["diff"][0]["deleted"]), 0) + self.assertEqual(len(result["diff"][0]["query"]), 0) + self.assertEqual(len(result["diff"][0]["deploy"][0]["mmudigon"]), 2) + + # Validate create responses + for resp in result["response"]: + self.assertEqual(resp["RETURN_CODE"], 200) + + def test_dcnm_intra_links_vpc_modify_existing(self): + + # load the json from playbooks + self.config_data = loadPlaybookData("dcnm_links_configs") + self.payloads_data = loadPlaybookData("dcnm_links_payloads") + + # load required config data + self.playbook_config = self.config_data.get("intra_modify_vpc_config") + self.mock_ip_sn = self.payloads_data.get("mock_ip_sn") + self.mock_hn_sn = self.payloads_data.get("mock_hn_sn") + self.mock_fab_inv = self.payloads_data.get("mock_fab_inv_data") + self.mock_num_fab_info = self.payloads_data.get("mock_num_fab_data") + + set_module_args( + dict( + state="merged", + src_fabric="mmudigon", + config=self.playbook_config, + ) + ) + + result = self.execute_module(changed=True, failed=False) + + self.assertEqual(len(result["diff"][0]["merged"]), 0) + self.assertEqual(len(result["diff"][0]["modified"]), 1) + self.assertEqual(len(result["diff"][0]["deleted"]), 0) + self.assertEqual(len(result["diff"][0]["query"]), 0) + self.assertEqual(len(result["diff"][0]["deploy"][0]["mmudigon"]), 2) + + # Validate create responses + for resp in result["response"]: + self.assertEqual(resp["RETURN_CODE"], 200) + + def test_dcnm_intra_links_vpc_replace_existing(self): + + # load the json from playbooks + self.config_data = loadPlaybookData("dcnm_links_configs") + self.payloads_data = loadPlaybookData("dcnm_links_payloads") + + # load required config data + self.playbook_config = self.config_data.get("intra_replace_vpc_config") + self.mock_ip_sn = self.payloads_data.get("mock_ip_sn") + self.mock_hn_sn = self.payloads_data.get("mock_hn_sn") + self.mock_fab_inv = self.payloads_data.get("mock_fab_inv_data") + self.mock_num_fab_info = self.payloads_data.get("mock_num_fab_data") + + set_module_args( + dict( + state="replaced", + src_fabric="mmudigon", + config=self.playbook_config, + ) + ) + + result = self.execute_module(changed=True, failed=False) + + self.assertEqual(len(result["diff"][0]["merged"]), 0) + self.assertEqual(len(result["diff"][0]["modified"]), 1) + self.assertEqual(len(result["diff"][0]["deleted"]), 0) + self.assertEqual(len(result["diff"][0]["query"]), 0) + self.assertEqual(len(result["diff"][0]["deploy"][0]["mmudigon"]), 2) + + # Validate create responses + for resp in result["response"]: + self.assertEqual(resp["RETURN_CODE"], 200) + + def test_dcnm_intra_links_vpc_delete_existing(self): + + # load the json from playbooks + self.config_data = loadPlaybookData("dcnm_links_configs") + self.payloads_data = loadPlaybookData("dcnm_links_payloads") + + # load required config data + self.playbook_config = self.config_data.get("intra_delete_vpc_config") + self.mock_ip_sn = self.payloads_data.get("mock_ip_sn") + self.mock_hn_sn = self.payloads_data.get("mock_hn_sn") + self.mock_fab_inv = self.payloads_data.get("mock_fab_inv_data") + self.mock_num_fab_info = self.payloads_data.get("mock_num_fab_data") + + set_module_args( + dict( + state="deleted", + src_fabric="mmudigon", + config=self.playbook_config, + ) + ) + + result = self.execute_module(changed=True, failed=False) + + self.assertEqual(len(result["diff"][0]["merged"]), 0) + self.assertEqual(len(result["diff"][0]["modified"]), 0) + self.assertEqual(len(result["diff"][0]["deleted"]), 1) + self.assertEqual(len(result["diff"][0]["query"]), 0) + self.assertEqual(len(result["diff"][0]["deploy"][0]["mmudigon"]), 2) + + # Validate delete responses + for resp in result["response"]: + self.assertEqual(resp["RETURN_CODE"], 200) + + def test_dcnm_intra_links_vpc_delete_existing_and_non_existing(self): + + # load the json from playbooks + self.config_data = loadPlaybookData("dcnm_links_configs") + self.payloads_data = loadPlaybookData("dcnm_links_payloads") + + # load required config data + self.playbook_config = self.config_data.get("intra_delete_vpc_config") + self.mock_ip_sn = self.payloads_data.get("mock_ip_sn") + self.mock_hn_sn = self.payloads_data.get("mock_hn_sn") + self.mock_fab_inv = self.payloads_data.get("mock_fab_inv_data") + self.mock_num_fab_info = self.payloads_data.get("mock_num_fab_data") + + set_module_args( + dict( + state="deleted", + src_fabric="mmudigon", + config=self.playbook_config, + ) + ) + + result = self.execute_module(changed=True, failed=False) + + self.assertEqual(len(result["diff"][0]["merged"]), 0) + self.assertEqual(len(result["diff"][0]["modified"]), 0) + self.assertEqual(len(result["diff"][0]["deleted"]), 1) + self.assertEqual(len(result["diff"][0]["query"]), 0) + self.assertEqual(len(result["diff"][0]["deploy"][0]["mmudigon"]), 2) + + # Validate delete responses + for resp in result["response"]: + self.assertEqual(resp["RETURN_CODE"], 200) + + def test_dcnm_intra_links_vpc_delete_non_existing(self): + + # load the json from playbooks + self.config_data = loadPlaybookData("dcnm_links_configs") + self.payloads_data = loadPlaybookData("dcnm_links_payloads") + + # load required config data + self.playbook_config = self.config_data.get("intra_delete_vpc_config") + self.mock_ip_sn = self.payloads_data.get("mock_ip_sn") + self.mock_hn_sn = self.payloads_data.get("mock_hn_sn") + self.mock_fab_inv = self.payloads_data.get("mock_fab_inv_data") + self.mock_num_fab_info = self.payloads_data.get("mock_num_fab_data") + + set_module_args( + dict( + state="deleted", + src_fabric="mmudigon", + config=self.playbook_config, + ) + ) + + result = self.execute_module(changed=False, failed=False) + + self.assertEqual(len(result["diff"][0]["merged"]), 0) + self.assertEqual(len(result["diff"][0]["modified"]), 0) + self.assertEqual(len(result["diff"][0]["deleted"]), 0) + self.assertEqual(len(result["diff"][0]["query"]), 0) + self.assertEqual(len(result["diff"][0]["deploy"]), 0) + + # Validate delete responses + for resp in result["response"]: + self.assertEqual(resp["RETURN_CODE"], 200) + + def test_dcnm_intra_links_vpc_query_no_config(self): + + # load the json from playbooks + self.config_data = loadPlaybookData("dcnm_links_configs") + self.payloads_data = loadPlaybookData("dcnm_links_payloads") + + # load required config data + self.playbook_config = [] + self.mock_ip_sn = self.payloads_data.get("mock_ip_sn") + self.mock_hn_sn = self.payloads_data.get("mock_hn_sn") + self.mock_fab_inv = self.payloads_data.get("mock_fab_inv_data") + self.mock_num_fab_info = self.payloads_data.get("mock_num_fab_data") + + set_module_args( + dict( + state="query", + src_fabric="mmudigon", + config=self.playbook_config, + ) + ) + + result = self.execute_module(changed=False, failed=False) + + self.assertEqual(len(result["diff"][0]["merged"]), 0) + self.assertEqual(len(result["diff"][0]["modified"]), 0) + self.assertEqual(len(result["diff"][0]["deleted"]), 0) + self.assertEqual((len(result["response"]) == 1), True) + self.assertEqual(len(result["diff"][0]["deploy"]), 0) + + def test_dcnm_intra_links_vpc_query_with_dst_fabric(self): + + # load the json from playbooks + self.config_data = loadPlaybookData("dcnm_links_configs") + self.payloads_data = loadPlaybookData("dcnm_links_payloads") + + # load required config data + self.playbook_config = self.config_data.get( + "intra_query_vpc_dest_fabric_config" + ) + self.mock_ip_sn = self.payloads_data.get("mock_ip_sn") + self.mock_hn_sn = self.payloads_data.get("mock_hn_sn") + self.mock_fab_inv = self.payloads_data.get("mock_fab_inv_data") + self.mock_num_fab_info = self.payloads_data.get("mock_num_fab_data") + + set_module_args( + dict( + state="query", + src_fabric="mmudigon", + config=self.playbook_config, + ) + ) + + result = self.execute_module(changed=False, failed=False) + + self.assertEqual(len(result["diff"][0]["merged"]), 0) + self.assertEqual(len(result["diff"][0]["modified"]), 0) + self.assertEqual(len(result["diff"][0]["deleted"]), 0) + self.assertEqual((len(result["response"]) == 1), True) + self.assertEqual(len(result["diff"][0]["deploy"]), 0) + + def test_dcnm_intra_links_vpc_query_with_src_device(self): + + # load the json from playbooks + self.config_data = loadPlaybookData("dcnm_links_configs") + self.payloads_data = loadPlaybookData("dcnm_links_payloads") + + # load required config data + self.playbook_config = self.config_data.get( + "intra_query_vpc_src_dev_config" + ) + self.mock_ip_sn = self.payloads_data.get("mock_ip_sn") + self.mock_hn_sn = self.payloads_data.get("mock_hn_sn") + self.mock_fab_inv = self.payloads_data.get("mock_fab_inv_data") + self.mock_num_fab_info = self.payloads_data.get("mock_num_fab_data") + + set_module_args( + dict( + state="query", + src_fabric="mmudigon", + config=self.playbook_config, + ) + ) + + result = self.execute_module(changed=False, failed=False) + + self.assertEqual(len(result["diff"][0]["merged"]), 0) + self.assertEqual(len(result["diff"][0]["modified"]), 0) + self.assertEqual(len(result["diff"][0]["deleted"]), 0) + self.assertEqual((len(result["response"]) == 1), True) + self.assertEqual(len(result["diff"][0]["deploy"]), 0) + + def test_dcnm_intra_links_vpc_query_with_dst_device(self): + + # load the json from playbooks + self.config_data = loadPlaybookData("dcnm_links_configs") + self.payloads_data = loadPlaybookData("dcnm_links_payloads") + + # load required config data + self.playbook_config = self.config_data.get( + "intra_query_vpc_dst_dev_config" + ) + self.mock_ip_sn = self.payloads_data.get("mock_ip_sn") + self.mock_hn_sn = self.payloads_data.get("mock_hn_sn") + self.mock_fab_inv = self.payloads_data.get("mock_fab_inv_data") + self.mock_num_fab_info = self.payloads_data.get("mock_num_fab_data") + + set_module_args( + dict( + state="query", + src_fabric="mmudigon", + config=self.playbook_config, + ) + ) + + result = self.execute_module(changed=False, failed=False) + + self.assertEqual(len(result["diff"][0]["merged"]), 0) + self.assertEqual(len(result["diff"][0]["modified"]), 0) + self.assertEqual(len(result["diff"][0]["deleted"]), 0) + self.assertEqual((len(result["response"]) == 1), True) + self.assertEqual(len(result["diff"][0]["deploy"]), 0) + + def test_dcnm_intra_links_vpc_query_with_src_interface(self): + + # load the json from playbooks + self.config_data = loadPlaybookData("dcnm_links_configs") + self.payloads_data = loadPlaybookData("dcnm_links_payloads") + + # load required config data + self.playbook_config = self.config_data.get( + "intra_query_vpc_src_intf_config" + ) + self.mock_ip_sn = self.payloads_data.get("mock_ip_sn") + self.mock_hn_sn = self.payloads_data.get("mock_hn_sn") + self.mock_fab_inv = self.payloads_data.get("mock_fab_inv_data") + self.mock_num_fab_info = self.payloads_data.get("mock_num_fab_data") + + set_module_args( + dict( + state="query", + src_fabric="mmudigon", + config=self.playbook_config, + ) + ) + + result = self.execute_module(changed=False, failed=False) + + self.assertEqual(len(result["diff"][0]["merged"]), 0) + self.assertEqual(len(result["diff"][0]["modified"]), 0) + self.assertEqual(len(result["diff"][0]["deleted"]), 0) + self.assertEqual((len(result["response"]) == 1), True) + self.assertEqual(len(result["diff"][0]["deploy"]), 0) + + def test_dcnm_intra_links_vpc_query_with_dst_interface(self): + + # load the json from playbooks + self.config_data = loadPlaybookData("dcnm_links_configs") + self.payloads_data = loadPlaybookData("dcnm_links_payloads") + + # load required config data + self.playbook_config = self.config_data.get( + "intra_query_vpc_dst_intf_config" + ) + self.mock_ip_sn = self.payloads_data.get("mock_ip_sn") + self.mock_hn_sn = self.payloads_data.get("mock_hn_sn") + self.mock_fab_inv = self.payloads_data.get("mock_fab_inv_data") + self.mock_num_fab_info = self.payloads_data.get("mock_num_fab_data") + + set_module_args( + dict( + state="query", + src_fabric="mmudigon", + config=self.playbook_config, + ) + ) + + result = self.execute_module(changed=False, failed=False) + + self.assertEqual(len(result["diff"][0]["merged"]), 0) + self.assertEqual(len(result["diff"][0]["modified"]), 0) + self.assertEqual(len(result["diff"][0]["deleted"]), 0) + self.assertEqual((len(result["response"]) == 1), True) + self.assertEqual(len(result["diff"][0]["deploy"]), 0) + + def test_dcnm_intra_links_vpc_query(self): + + # load the json from playbooks + self.config_data = loadPlaybookData("dcnm_links_configs") + self.payloads_data = loadPlaybookData("dcnm_links_payloads") + + # load required config data + self.playbook_config = self.config_data.get("intra_query_vpc_config") + self.mock_ip_sn = self.payloads_data.get("mock_ip_sn") + self.mock_hn_sn = self.payloads_data.get("mock_hn_sn") + self.mock_fab_inv = self.payloads_data.get("mock_fab_inv_data") + self.mock_num_fab_info = self.payloads_data.get("mock_num_fab_data") + + set_module_args( + dict( + state="query", + src_fabric="mmudigon", + config=self.playbook_config, + ) + ) + + result = self.execute_module(changed=False, failed=False) + + self.assertEqual(len(result["diff"][0]["merged"]), 0) + self.assertEqual(len(result["diff"][0]["modified"]), 0) + self.assertEqual(len(result["diff"][0]["deleted"]), 0) + self.assertEqual((len(result["response"]) == 1), True) + self.assertEqual(len(result["diff"][0]["deploy"]), 0) + + # -------------------------- INTER-FABRIC NUMBERED ------------------------------ + + def test_dcnm_inter_links_numbered_merged_new_no_opts(self): + + # load the json from playbooks + self.config_data = loadPlaybookData("dcnm_links_configs") + self.payloads_data = loadPlaybookData("dcnm_links_payloads") + + # load required config data + self.playbook_config = self.config_data.get( + "inter_merge_num_no_opts_config" + ) + self.mock_ip_sn = self.payloads_data.get("mock_ip_sn") + self.mock_hn_sn = self.payloads_data.get("mock_hn_sn") + self.mock_fab_inv = self.payloads_data.get("mock_fab_inv_data") + self.mock_num_fab_info = self.payloads_data.get("mock_num_fab_data") + self.mock_unnum_fab_info = self.payloads_data.get( + "mock_unnum_fab_data" + ) + self.mock_ipv6_fab_info = self.payloads_data.get("mock_ipv6_fab_data") + + set_module_args( + dict( + state="merged", + src_fabric="mmudigon-numbered", + config=self.playbook_config, + ) + ) + result = self.execute_module(changed=True, failed=False) + + self.assertEqual(len(result["diff"][0]["merged"]), 6) + self.assertEqual(len(result["diff"][0]["modified"]), 0) + self.assertEqual(len(result["diff"][0]["deleted"]), 0) + self.assertEqual(len(result["diff"][0]["query"]), 0) + self.assertEqual( + len(result["diff"][0]["deploy"][0]["mmudigon-numbered"]), 1 + ) + self.assertEqual(len(result["diff"][0]["deploy"][0]["test_net"]), 1) + self.assertEqual(len(result["diff"][0]["deploy"][0]["test_net1"]), 1) + + # Validate create responses + for resp in result["response"]: + self.assertEqual(resp["RETURN_CODE"], 200) + + def test_dcnm_inter_links_numbered_merged_new(self): + + # load the json from playbooks + self.config_data = loadPlaybookData("dcnm_links_configs") + self.payloads_data = loadPlaybookData("dcnm_links_payloads") + + # load required config data + self.playbook_config = self.config_data.get("inter_merge_num_config") + self.mock_ip_sn = self.payloads_data.get("mock_ip_sn") + self.mock_hn_sn = self.payloads_data.get("mock_hn_sn") + self.mock_fab_inv = self.payloads_data.get("mock_fab_inv_data") + self.mock_num_fab_info = self.payloads_data.get("mock_num_fab_data") + self.mock_unnum_fab_info = self.payloads_data.get( + "mock_unnum_fab_data" + ) + self.mock_ipv6_fab_info = self.payloads_data.get("mock_ipv6_fab_data") + + set_module_args( + dict( + state="merged", + src_fabric="mmudigon-numbered", + config=self.playbook_config, + ) + ) + result = self.execute_module(changed=True, failed=False) + + self.assertEqual(len(result["diff"][0]["merged"]), 6) + self.assertEqual(len(result["diff"][0]["modified"]), 0) + self.assertEqual(len(result["diff"][0]["deleted"]), 0) + self.assertEqual(len(result["diff"][0]["query"]), 0) + self.assertEqual( + len(result["diff"][0]["deploy"][0]["mmudigon-numbered"]), 1 + ) + self.assertEqual(len(result["diff"][0]["deploy"][0]["test_net"]), 1) + self.assertEqual(len(result["diff"][0]["deploy"][0]["test_net1"]), 1) + + # Validate create responses + for resp in result["response"]: + self.assertEqual(resp["RETURN_CODE"], 200) + + def test_dcnm_inter_links_numbered_merged_existing(self): + + # load the json from playbooks + self.config_data = loadPlaybookData("dcnm_links_configs") + self.payloads_data = loadPlaybookData("dcnm_links_payloads") + + # load required config data + self.playbook_config = self.config_data.get("inter_merge_num_config") + self.mock_ip_sn = self.payloads_data.get("mock_ip_sn") + self.mock_hn_sn = self.payloads_data.get("mock_hn_sn") + self.mock_fab_inv = self.payloads_data.get("mock_fab_inv_data") + self.mock_num_fab_info = self.payloads_data.get("mock_num_fab_data") + self.mock_unnum_fab_info = self.payloads_data.get( + "mock_unnum_fab_data" + ) + self.mock_ipv6_fab_info = self.payloads_data.get("mock_ipv6_fab_data") + + set_module_args( + dict( + state="merged", + src_fabric="mmudigon-numbered", + config=self.playbook_config, + ) + ) + result = self.execute_module(changed=True, failed=False) + + self.assertEqual(len(result["diff"][0]["merged"]), 0) + self.assertEqual(len(result["diff"][0]["modified"]), 0) + self.assertEqual(len(result["diff"][0]["deleted"]), 0) + self.assertEqual(len(result["diff"][0]["query"]), 0) + self.assertEqual( + len(result["diff"][0]["deploy"][0]["mmudigon-numbered"]), 1 + ) + self.assertEqual(len(result["diff"][0]["deploy"][0]["test_net"]), 1) + self.assertEqual(len(result["diff"][0]["deploy"][0]["test_net1"]), 1) + + # Validate create responses + for resp in result["response"]: + self.assertEqual(resp["RETURN_CODE"], 200) + + def test_dcnm_inter_links_numbered_merged_new_no_state(self): + + # load the json from playbooks + self.config_data = loadPlaybookData("dcnm_links_configs") + self.payloads_data = loadPlaybookData("dcnm_links_payloads") + + # load required config data + self.playbook_config = self.config_data.get("inter_merge_num_config") + self.mock_ip_sn = self.payloads_data.get("mock_ip_sn") + self.mock_hn_sn = self.payloads_data.get("mock_hn_sn") + self.mock_fab_inv = self.payloads_data.get("mock_fab_inv_data") + self.mock_num_fab_info = self.payloads_data.get("mock_num_fab_data") + self.mock_unnum_fab_info = self.payloads_data.get( + "mock_unnum_fab_data" + ) + self.mock_ipv6_fab_info = self.payloads_data.get("mock_ipv6_fab_data") + + set_module_args( + dict( + state="merged", + src_fabric="mmudigon-numbered", + config=self.playbook_config, + ) + ) + result = self.execute_module(changed=True, failed=False) + + self.assertEqual(len(result["diff"][0]["merged"]), 6) + self.assertEqual(len(result["diff"][0]["modified"]), 0) + self.assertEqual(len(result["diff"][0]["deleted"]), 0) + self.assertEqual(len(result["diff"][0]["query"]), 0) + self.assertEqual( + len(result["diff"][0]["deploy"][0]["mmudigon-numbered"]), 1 + ) + self.assertEqual(len(result["diff"][0]["deploy"][0]["test_net"]), 1) + self.assertEqual(len(result["diff"][0]["deploy"][0]["test_net1"]), 1) + + # Validate create responses + for resp in result["response"]: + self.assertEqual(resp["RETURN_CODE"], 200) + + def test_dcnm_inter_links_numbered_merged_new_check_mode(self): + + # load the json from playbooks + self.config_data = loadPlaybookData("dcnm_links_configs") + self.payloads_data = loadPlaybookData("dcnm_links_payloads") + + # load required config data + self.playbook_config = self.config_data.get("inter_merge_num_config") + self.mock_ip_sn = self.payloads_data.get("mock_ip_sn") + self.mock_hn_sn = self.payloads_data.get("mock_hn_sn") + self.mock_fab_inv = self.payloads_data.get("mock_fab_inv_data") + self.mock_num_fab_info = self.payloads_data.get("mock_num_fab_data") + self.mock_unnum_fab_info = self.payloads_data.get( + "mock_unnum_fab_data" + ) + self.mock_ipv6_fab_info = self.payloads_data.get("mock_ipv6_fab_data") + + set_module_args( + dict( + _ansible_check_mode=True, + state="merged", + src_fabric="mmudigon-numbered", + config=self.playbook_config, + ) + ) + result = self.execute_module(changed=False, failed=False) + + self.assertEqual(len(result["diff"][0]["merged"]), 6) + self.assertEqual(len(result["diff"][0]["modified"]), 0) + self.assertEqual(len(result["diff"][0]["deleted"]), 0) + self.assertEqual(len(result["diff"][0]["query"]), 0) + self.assertEqual( + len(result["diff"][0]["deploy"][0]["mmudigon-numbered"]), 1 + ) + self.assertEqual(len(result["diff"][0]["deploy"][0]["test_net"]), 1) + self.assertEqual(len(result["diff"][0]["deploy"][0]["test_net1"]), 1) + + # Validate create responses + for resp in result["response"]: + self.assertEqual(resp["RETURN_CODE"], 200) + + def test_dcnm_inter_links_numbered_merged_new_existing_and_non_existing( + self + ): + + # load the json from playbooks + self.config_data = loadPlaybookData("dcnm_links_configs") + self.payloads_data = loadPlaybookData("dcnm_links_payloads") + + # load required config data + self.playbook_config = self.config_data.get("inter_merge_num_config") + self.mock_ip_sn = self.payloads_data.get("mock_ip_sn") + self.mock_hn_sn = self.payloads_data.get("mock_hn_sn") + self.mock_fab_inv = self.payloads_data.get("mock_fab_inv_data") + self.mock_num_fab_info = self.payloads_data.get("mock_num_fab_data") + self.mock_unnum_fab_info = self.payloads_data.get( + "mock_unnum_fab_data" + ) + self.mock_ipv6_fab_info = self.payloads_data.get("mock_ipv6_fab_data") + + set_module_args( + dict( + state="merged", + src_fabric="mmudigon-numbered", + config=self.playbook_config, + ) + ) + + result = self.execute_module(changed=True, failed=False) + + self.assertEqual(len(result["diff"][0]["merged"]), 3) + self.assertEqual(len(result["diff"][0]["modified"]), 0) + self.assertEqual(len(result["diff"][0]["deleted"]), 0) + self.assertEqual(len(result["diff"][0]["query"]), 0) + self.assertEqual( + len(result["diff"][0]["deploy"][0]["mmudigon-numbered"]), 1 + ) + self.assertEqual(len(result["diff"][0]["deploy"][0]["test_net"]), 1) + self.assertEqual(len(result["diff"][0]["deploy"][0]["test_net1"]), 1) + + # Validate create responses + for resp in result["response"]: + self.assertEqual(resp["RETURN_CODE"], 200) + + def test_dcnm_inter_links_numbered_modify_existing(self): + + # load the json from playbooks + self.config_data = loadPlaybookData("dcnm_links_configs") + self.payloads_data = loadPlaybookData("dcnm_links_payloads") + + # load required config data + self.playbook_config = self.config_data.get("inter_modify_num_config") + self.mock_ip_sn = self.payloads_data.get("mock_ip_sn") + self.mock_hn_sn = self.payloads_data.get("mock_hn_sn") + self.mock_fab_inv = self.payloads_data.get("mock_fab_inv_data") + self.mock_num_fab_info = self.payloads_data.get("mock_num_fab_data") + self.mock_unnum_fab_info = self.payloads_data.get( + "mock_unnum_fab_data" + ) + self.mock_ipv6_fab_info = self.payloads_data.get("mock_ipv6_fab_data") + + set_module_args( + dict( + state="merged", + src_fabric="mmudigon-numbered", + config=self.playbook_config, + ) + ) + + result = self.execute_module(changed=True, failed=False) + + self.assertEqual(len(result["diff"][0]["merged"]), 0) + self.assertEqual(len(result["diff"][0]["modified"]), 6) + self.assertEqual(len(result["diff"][0]["deleted"]), 0) + self.assertEqual(len(result["diff"][0]["query"]), 0) + self.assertEqual( + len(result["diff"][0]["deploy"][0]["mmudigon-numbered"]), 1 + ) + self.assertEqual(len(result["diff"][0]["deploy"][0]["test_net"]), 1) + self.assertEqual(len(result["diff"][0]["deploy"][0]["test_net1"]), 1) + + # Validate create responses + for resp in result["response"]: + self.assertEqual(resp["RETURN_CODE"], 200) + + def test_dcnm_inter_links_numbered_replace_existing(self): + + # load the json from playbooks + self.config_data = loadPlaybookData("dcnm_links_configs") + self.payloads_data = loadPlaybookData("dcnm_links_payloads") + + # load required config data + self.playbook_config = self.config_data.get("inter_replace_num_config") + self.mock_ip_sn = self.payloads_data.get("mock_ip_sn") + self.mock_hn_sn = self.payloads_data.get("mock_hn_sn") + self.mock_fab_inv = self.payloads_data.get("mock_fab_inv_data") + self.mock_num_fab_info = self.payloads_data.get("mock_num_fab_data") + self.mock_unnum_fab_info = self.payloads_data.get( + "mock_unnum_fab_data" + ) + self.mock_ipv6_fab_info = self.payloads_data.get("mock_ipv6_fab_data") + + set_module_args( + dict( + state="replaced", + src_fabric="mmudigon-numbered", + config=self.playbook_config, + ) + ) + + result = self.execute_module(changed=True, failed=False) + + self.assertEqual(len(result["diff"][0]["merged"]), 0) + self.assertEqual(len(result["diff"][0]["modified"]), 6) + self.assertEqual(len(result["diff"][0]["deleted"]), 0) + self.assertEqual(len(result["diff"][0]["query"]), 0) + self.assertEqual( + len(result["diff"][0]["deploy"][0]["mmudigon-numbered"]), 1 + ) + self.assertEqual(len(result["diff"][0]["deploy"][0]["test_net"]), 1) + self.assertEqual(len(result["diff"][0]["deploy"][0]["test_net1"]), 1) + + # Validate create responses + for resp in result["response"]: + self.assertEqual(resp["RETURN_CODE"], 200) + + def test_dcnm_inter_links_numbered_delete_existing(self): + + # load the json from playbooks + self.config_data = loadPlaybookData("dcnm_links_configs") + self.payloads_data = loadPlaybookData("dcnm_links_payloads") + + # load required config data + self.playbook_config = self.config_data.get("inter_delete_num_config") + self.mock_ip_sn = self.payloads_data.get("mock_ip_sn") + self.mock_hn_sn = self.payloads_data.get("mock_hn_sn") + self.mock_fab_inv = self.payloads_data.get("mock_fab_inv_data") + self.mock_num_fab_info = self.payloads_data.get("mock_num_fab_data") + self.mock_unnum_fab_info = self.payloads_data.get( + "mock_unnum_fab_data" + ) + self.mock_ipv6_fab_info = self.payloads_data.get("mock_ipv6_fab_data") + + set_module_args( + dict( + state="deleted", + src_fabric="mmudigon-numbered", + config=self.playbook_config, + ) + ) + + result = self.execute_module(changed=True, failed=False) + + self.assertEqual(len(result["diff"][0]["merged"]), 0) + self.assertEqual(len(result["diff"][0]["modified"]), 0) + self.assertEqual(len(result["diff"][0]["deleted"]), 6) + self.assertEqual(len(result["diff"][0]["query"]), 0) + self.assertEqual( + len(result["diff"][0]["deploy"][0]["mmudigon-numbered"]), 1 + ) + self.assertEqual(len(result["diff"][0]["deploy"][0]["test_net"]), 1) + self.assertEqual(len(result["diff"][0]["deploy"][0]["test_net1"]), 1) + + # Validate delete responses + for resp in result["response"]: + self.assertEqual(resp["RETURN_CODE"], 200) + + def test_dcnm_inter_links_numbered_delete_existing_and_non_existing(self): + + # load the json from playbooks + self.config_data = loadPlaybookData("dcnm_links_configs") + self.payloads_data = loadPlaybookData("dcnm_links_payloads") + + # load required config data + self.playbook_config = self.config_data.get("inter_delete_num_config") + self.mock_ip_sn = self.payloads_data.get("mock_ip_sn") + self.mock_hn_sn = self.payloads_data.get("mock_hn_sn") + self.mock_fab_inv = self.payloads_data.get("mock_fab_inv_data") + self.mock_num_fab_info = self.payloads_data.get("mock_num_fab_data") + self.mock_unnum_fab_info = self.payloads_data.get( + "mock_unnum_fab_data" + ) + self.mock_ipv6_fab_info = self.payloads_data.get("mock_ipv6_fab_data") + + set_module_args( + dict( + state="deleted", + src_fabric="mmudigon-numbered", + config=self.playbook_config, + ) + ) + + result = self.execute_module(changed=True, failed=False) + + self.assertEqual(len(result["diff"][0]["merged"]), 0) + self.assertEqual(len(result["diff"][0]["modified"]), 0) + self.assertEqual(len(result["diff"][0]["deleted"]), 4) + self.assertEqual(len(result["diff"][0]["query"]), 0) + self.assertEqual( + len(result["diff"][0]["deploy"][0]["mmudigon-numbered"]), 1 + ) + self.assertEqual(len(result["diff"][0]["deploy"][0]["test_net"]), 1) + self.assertEqual(len(result["diff"][0]["deploy"][0]["test_net1"]), 1) + + # Validate delete responses + for resp in result["response"]: + self.assertEqual(resp["RETURN_CODE"], 200) + + def test_dcnm_inter_links_numbered_delete_non_existing(self): + + # load the json from playbooks + self.config_data = loadPlaybookData("dcnm_links_configs") + self.payloads_data = loadPlaybookData("dcnm_links_payloads") + + # load required config data + self.playbook_config = self.config_data.get("inter_delete_num_config") + self.mock_ip_sn = self.payloads_data.get("mock_ip_sn") + self.mock_hn_sn = self.payloads_data.get("mock_hn_sn") + self.mock_fab_inv = self.payloads_data.get("mock_fab_inv_data") + self.mock_num_fab_info = self.payloads_data.get("mock_num_fab_data") + self.mock_unnum_fab_info = self.payloads_data.get( + "mock_unnum_fab_data" + ) + self.mock_ipv6_fab_info = self.payloads_data.get("mock_ipv6_fab_data") + + set_module_args( + dict( + state="deleted", + src_fabric="mmudigon-numbered", + config=self.playbook_config, + ) + ) + + result = self.execute_module(changed=False, failed=False) + + self.assertEqual(len(result["diff"][0]["merged"]), 0) + self.assertEqual(len(result["diff"][0]["modified"]), 0) + self.assertEqual(len(result["diff"][0]["deleted"]), 0) + self.assertEqual(len(result["diff"][0]["query"]), 0) + self.assertEqual(len(result["diff"][0]["deploy"]), 0) + + # Validate delete responses + for resp in result["response"]: + self.assertEqual(resp["RETURN_CODE"], 200) + + def test_dcnm_inter_links_numbered_template_change(self): + + # load the json from playbooks + self.config_data = loadPlaybookData("dcnm_links_configs") + self.payloads_data = loadPlaybookData("dcnm_links_payloads") + + # load required config data + self.playbook_config = self.config_data.get( + "inter_modify_num_template_config" + ) + self.mock_ip_sn = self.payloads_data.get("mock_ip_sn") + self.mock_hn_sn = self.payloads_data.get("mock_hn_sn") + self.mock_fab_inv = self.payloads_data.get("mock_fab_inv_data") + self.mock_num_fab_info = self.payloads_data.get("mock_num_fab_data") + self.mock_unnum_fab_info = self.payloads_data.get( + "mock_unnum_fab_data" + ) + self.mock_ipv6_fab_info = self.payloads_data.get("mock_ipv6_fab_data") + + set_module_args( + dict( + state="merged", + src_fabric="mmudigon-numbered", + config=self.playbook_config, + ) + ) + + result = self.execute_module(changed=True, failed=False) + + self.assertEqual(len(result["diff"][0]["merged"]), 0) + self.assertEqual(len(result["diff"][0]["modified"]), 1) + self.assertEqual(len(result["diff"][0]["deleted"]), 0) + self.assertEqual(len(result["diff"][0]["query"]), 0) + self.assertEqual( + len(result["diff"][0]["deploy"][0]["mmudigon-numbered"]), 1 + ) + self.assertEqual(len(result["diff"][0]["deploy"][0]["test_net"]), 1) + + # Validate delete responses + for resp in result["response"]: + self.assertEqual(resp["RETURN_CODE"], 200) + + def test_dcnm_inter_links_numbered_query_no_config(self): + + # load the json from playbooks + self.config_data = loadPlaybookData("dcnm_links_configs") + self.payloads_data = loadPlaybookData("dcnm_links_payloads") + + # load required config data + self.playbook_config = [] + self.mock_ip_sn = self.payloads_data.get("mock_ip_sn") + self.mock_hn_sn = self.payloads_data.get("mock_hn_sn") + self.mock_fab_inv = self.payloads_data.get("mock_fab_inv_data") + self.mock_num_fab_info = self.payloads_data.get("mock_num_fab_data") + self.mock_unnum_fab_info = self.payloads_data.get( + "mock_unnum_fab_data" + ) + self.mock_ipv6_fab_info = self.payloads_data.get("mock_ipv6_fab_data") + + set_module_args( + dict( + state="query", + src_fabric="mmudigon-numbered", + config=self.playbook_config, + ) + ) + + result = self.execute_module(changed=False, failed=False) + + self.assertEqual(len(result["diff"][0]["merged"]), 0) + self.assertEqual(len(result["diff"][0]["modified"]), 0) + self.assertEqual(len(result["diff"][0]["deleted"]), 0) + self.assertEqual((len(result["response"]) == 6), True) + self.assertEqual(len(result["diff"][0]["deploy"]), 0) + + def test_dcnm_inter_links_numbered_query_with_dst_fabric(self): + + # load the json from playbooks + self.config_data = loadPlaybookData("dcnm_links_configs") + self.payloads_data = loadPlaybookData("dcnm_links_payloads") + + # load required config data + self.playbook_config = self.config_data.get( + "inter_query_num_dest_fabric_config" + ) + self.mock_ip_sn = self.payloads_data.get("mock_ip_sn") + self.mock_hn_sn = self.payloads_data.get("mock_hn_sn") + self.mock_fab_inv = self.payloads_data.get("mock_fab_inv_data") + self.mock_num_fab_info = self.payloads_data.get("mock_num_fab_data") + self.mock_unnum_fab_info = self.payloads_data.get( + "mock_unnum_fab_data" + ) + self.mock_ipv6_fab_info = self.payloads_data.get("mock_ipv6_fab_data") + + set_module_args( + dict( + state="query", + src_fabric="mmudigon-numbered", + config=self.playbook_config, + ) + ) + + result = self.execute_module(changed=False, failed=False) + + self.assertEqual(len(result["diff"][0]["merged"]), 0) + self.assertEqual(len(result["diff"][0]["modified"]), 0) + self.assertEqual(len(result["diff"][0]["deleted"]), 0) + self.assertEqual((len(result["response"]) == 3), True) + self.assertEqual(len(result["diff"][0]["deploy"]), 0) + + def test_dcnm_inter_links_numbered_query_with_src_device(self): + + # load the json from playbooks + self.config_data = loadPlaybookData("dcnm_links_configs") + self.payloads_data = loadPlaybookData("dcnm_links_payloads") + + # load required config data + self.playbook_config = self.config_data.get( + "inter_query_num_src_dev_config" + ) + self.mock_ip_sn = self.payloads_data.get("mock_ip_sn") + self.mock_hn_sn = self.payloads_data.get("mock_hn_sn") + self.mock_fab_inv = self.payloads_data.get("mock_fab_inv_data") + self.mock_num_fab_info = self.payloads_data.get("mock_num_fab_data") + self.mock_unnum_fab_info = self.payloads_data.get( + "mock_unnum_fab_data" + ) + self.mock_ipv6_fab_info = self.payloads_data.get("mock_ipv6_fab_data") + + set_module_args( + dict( + state="query", + src_fabric="mmudigon-numbered", + config=self.playbook_config, + ) + ) + + result = self.execute_module(changed=False, failed=False) + + self.assertEqual(len(result["diff"][0]["merged"]), 0) + self.assertEqual(len(result["diff"][0]["modified"]), 0) + self.assertEqual(len(result["diff"][0]["deleted"]), 0) + self.assertEqual((len(result["response"]) == 1), True) + self.assertEqual(len(result["diff"][0]["deploy"]), 0) + + def test_dcnm_inter_links_numbered_query_with_dst_device(self): + + # load the json from playbooks + self.config_data = loadPlaybookData("dcnm_links_configs") + self.payloads_data = loadPlaybookData("dcnm_links_payloads") + + # load required config data + self.playbook_config = self.config_data.get( + "inter_query_num_dst_dev_config" + ) + self.mock_ip_sn = self.payloads_data.get("mock_ip_sn") + self.mock_hn_sn = self.payloads_data.get("mock_hn_sn") + self.mock_fab_inv = self.payloads_data.get("mock_fab_inv_data") + self.mock_num_fab_info = self.payloads_data.get("mock_num_fab_data") + self.mock_unnum_fab_info = self.payloads_data.get( + "mock_unnum_fab_data" + ) + self.mock_ipv6_fab_info = self.payloads_data.get("mock_ipv6_fab_data") + + set_module_args( + dict( + state="query", + src_fabric="mmudigon-numbered", + config=self.playbook_config, + ) + ) + + result = self.execute_module(changed=False, failed=False) + + self.assertEqual(len(result["diff"][0]["merged"]), 0) + self.assertEqual(len(result["diff"][0]["modified"]), 0) + self.assertEqual(len(result["diff"][0]["deleted"]), 0) + self.assertEqual((len(result["response"]) == 1), True) + self.assertEqual(len(result["diff"][0]["deploy"]), 0) + + def test_dcnm_inter_links_numbered_query_with_src_interface(self): + + # load the json from playbooks + self.config_data = loadPlaybookData("dcnm_links_configs") + self.payloads_data = loadPlaybookData("dcnm_links_payloads") + + # load required config data + self.playbook_config = self.config_data.get( + "inter_query_num_src_intf_config" + ) + self.mock_ip_sn = self.payloads_data.get("mock_ip_sn") + self.mock_hn_sn = self.payloads_data.get("mock_hn_sn") + self.mock_fab_inv = self.payloads_data.get("mock_fab_inv_data") + self.mock_num_fab_info = self.payloads_data.get("mock_num_fab_data") + self.mock_unnum_fab_info = self.payloads_data.get( + "mock_unnum_fab_data" + ) + self.mock_ipv6_fab_info = self.payloads_data.get("mock_ipv6_fab_data") + + set_module_args( + dict( + state="query", + src_fabric="mmudigon-numbered", + config=self.playbook_config, + ) + ) + + result = self.execute_module(changed=False, failed=False) + + self.assertEqual(len(result["diff"][0]["merged"]), 0) + self.assertEqual(len(result["diff"][0]["modified"]), 0) + self.assertEqual(len(result["diff"][0]["deleted"]), 0) + self.assertEqual((len(result["response"]) == 1), True) + self.assertEqual(len(result["diff"][0]["deploy"]), 0) + + def test_dcnm_inter_links_numbered_query_with_dst_interface(self): + + # load the json from playbooks + self.config_data = loadPlaybookData("dcnm_links_configs") + self.payloads_data = loadPlaybookData("dcnm_links_payloads") + + # load required config data + self.playbook_config = self.config_data.get( + "inter_query_num_dst_intf_config" + ) + self.mock_ip_sn = self.payloads_data.get("mock_ip_sn") + self.mock_hn_sn = self.payloads_data.get("mock_hn_sn") + self.mock_fab_inv = self.payloads_data.get("mock_fab_inv_data") + self.mock_num_fab_info = self.payloads_data.get("mock_num_fab_data") + self.mock_unnum_fab_info = self.payloads_data.get( + "mock_unnum_fab_data" + ) + self.mock_ipv6_fab_info = self.payloads_data.get("mock_ipv6_fab_data") + + set_module_args( + dict( + state="query", + src_fabric="mmudigon-numbered", + config=self.playbook_config, + ) + ) + + result = self.execute_module(changed=False, failed=False) + + self.assertEqual(len(result["diff"][0]["merged"]), 0) + self.assertEqual(len(result["diff"][0]["modified"]), 0) + self.assertEqual(len(result["diff"][0]["deleted"]), 0) + self.assertEqual((len(result["response"]) == 1), True) + self.assertEqual(len(result["diff"][0]["deploy"]), 0) + + def test_dcnm_inter_links_numbered_query(self): + + # load the json from playbooks + self.config_data = loadPlaybookData("dcnm_links_configs") + self.payloads_data = loadPlaybookData("dcnm_links_payloads") + + # load required config data + self.playbook_config = self.config_data.get("inter_query_num_config") + self.mock_ip_sn = self.payloads_data.get("mock_ip_sn") + self.mock_hn_sn = self.payloads_data.get("mock_hn_sn") + self.mock_fab_inv = self.payloads_data.get("mock_fab_inv_data") + self.mock_num_fab_info = self.payloads_data.get("mock_num_fab_data") + self.mock_unnum_fab_info = self.payloads_data.get( + "mock_unnum_fab_data" + ) + self.mock_ipv6_fab_info = self.payloads_data.get("mock_ipv6_fab_data") + + set_module_args( + dict( + state="query", + src_fabric="mmudigon-numbered", + config=self.playbook_config, + ) + ) + + result = self.execute_module(changed=False, failed=False) + + self.assertEqual(len(result["diff"][0]["merged"]), 0) + self.assertEqual(len(result["diff"][0]["modified"]), 0) + self.assertEqual(len(result["diff"][0]["deleted"]), 0) + self.assertEqual((len(result["response"]) == 3), True) + self.assertEqual(len(result["diff"][0]["deploy"]), 0) + + def test_dcnm_inter_links_numbered_query_not_exist(self): + + # load the json from playbooks + self.config_data = loadPlaybookData("dcnm_links_configs") + self.payloads_data = loadPlaybookData("dcnm_links_payloads") + + # load required config data + self.playbook_config = self.config_data.get( + "inter_query_num_not_exist" + ) + self.mock_ip_sn = self.payloads_data.get("mock_ip_sn") + self.mock_hn_sn = self.payloads_data.get("mock_hn_sn") + self.mock_fab_inv = self.payloads_data.get("mock_fab_inv_data") + self.mock_num_fab_info = self.payloads_data.get("mock_num_fab_data") + self.mock_unnum_fab_info = self.payloads_data.get( + "mock_unnum_fab_data" + ) + self.mock_ipv6_fab_info = self.payloads_data.get("mock_ipv6_fab_data") + + set_module_args( + dict( + state="query", + src_fabric="mmudigon-numbered", + config=self.playbook_config, + ) + ) + + result = self.execute_module(changed=False, failed=False) + + self.assertEqual(len(result["diff"][0]["merged"]), 0) + self.assertEqual(len(result["diff"][0]["modified"]), 0) + self.assertEqual(len(result["diff"][0]["deleted"]), 0) + self.assertEqual((len(result["response"]) == 0), True) + self.assertEqual(len(result["diff"][0]["deploy"]), 0) + + # ---------------------- INTRA-FABRIC INVALID PARAMS ---------------------------- + + def test_dcnm_intra_links_invalid_template(self): + + # load the json from playbooks + self.config_data = loadPlaybookData("dcnm_links_configs") + self.payloads_data = loadPlaybookData("dcnm_links_payloads") + + # load required config data + self.playbook_config = self.config_data.get( + "intra_invalid_template_config" + ) + self.mock_ip_sn = self.payloads_data.get("mock_ip_sn") + self.mock_hn_sn = self.payloads_data.get("mock_hn_sn") + self.mock_fab_inv = self.payloads_data.get("mock_fab_inv_data") + self.mock_num_fab_info = self.payloads_data.get("mock_num_fab_data") + + set_module_args( + dict( + state="query", + src_fabric="mmudigon-unnumbered", + config=self.playbook_config, + ) + ) + + result = None + + try: + result = self.execute_module(changed=False, failed=False) + except Exception as e: + self.assertEqual(result, None) + self.assertEqual(("Invalid choice provided" in str(e)), True) + + def test_dcnm_intra_links_missing_src_fabric(self): + + # load the json from playbooks + self.config_data = loadPlaybookData("dcnm_links_configs") + self.payloads_data = loadPlaybookData("dcnm_links_payloads") + + # load required config data + self.playbook_config = self.config_data.get( + "intra_missing_src_fabric_config" + ) + self.mock_ip_sn = self.payloads_data.get("mock_ip_sn") + self.mock_hn_sn = self.payloads_data.get("mock_hn_sn") + self.mock_fab_inv = self.payloads_data.get("mock_fab_inv_data") + self.mock_num_fab_info = self.payloads_data.get("mock_num_fab_data") + + set_module_args(dict(state="merged", config=self.playbook_config)) + + result = None + + try: + result = self.execute_module(changed=False, failed=False) + except Exception as e: + self.assertEqual(result, None) + self.assertEqual( + ("missing required arguments: src_fabric" in str(e)), True + ) + + def test_dcnm_intra_links_missing_dst_fabric(self): + + # load the json from playbooks + self.config_data = loadPlaybookData("dcnm_links_configs") + self.payloads_data = loadPlaybookData("dcnm_links_payloads") + + # load required config data + self.playbook_config = self.config_data.get( + "intra_missing_dst_fabric_config" + ) + self.mock_ip_sn = self.payloads_data.get("mock_ip_sn") + self.mock_hn_sn = self.payloads_data.get("mock_hn_sn") + self.mock_fab_inv = self.payloads_data.get("mock_fab_inv_data") + self.mock_num_fab_info = self.payloads_data.get("mock_num_fab_data") + + set_module_args( + dict( + state="merged", + src_fabric="mmudigon-numbered", + config=self.playbook_config, + ) + ) + + result = None + + try: + result = self.execute_module(changed=False, failed=False) + except Exception as e: + self.assertEqual(result, None) + self.assertEqual( + ("Required parameter not found: dst_fabric" in str(e)), True + ) + + def test_dcnm_intra_links_missing_src_intf(self): + + # load the json from playbooks + self.config_data = loadPlaybookData("dcnm_links_configs") + self.payloads_data = loadPlaybookData("dcnm_links_payloads") + + # load required config data + self.playbook_config = self.config_data.get( + "intra_missing_src_intf_config" + ) + self.mock_ip_sn = self.payloads_data.get("mock_ip_sn") + self.mock_hn_sn = self.payloads_data.get("mock_hn_sn") + self.mock_fab_inv = self.payloads_data.get("mock_fab_inv_data") + self.mock_num_fab_info = self.payloads_data.get("mock_num_fab_data") + + set_module_args( + dict( + state="merged", + src_fabric="mmudigon-numbered", + config=self.playbook_config, + ) + ) + + result = None + + try: + result = self.execute_module(changed=False, failed=False) + except Exception as e: + self.assertEqual(result, None) + self.assertEqual( + ("src_interface : Required parameter not found" in str(e)), + True, + ) + + def test_dcnm_intra_links_missing_dst_intf(self): + + # load the json from playbooks + self.config_data = loadPlaybookData("dcnm_links_configs") + self.payloads_data = loadPlaybookData("dcnm_links_payloads") + + # load required config data + self.playbook_config = self.config_data.get( + "intra_missing_dst_intf_config" + ) + self.mock_ip_sn = self.payloads_data.get("mock_ip_sn") + self.mock_hn_sn = self.payloads_data.get("mock_hn_sn") + self.mock_fab_inv = self.payloads_data.get("mock_fab_inv_data") + self.mock_num_fab_info = self.payloads_data.get("mock_num_fab_data") + + set_module_args( + dict( + state="merged", + src_fabric="mmudigon-numbered", + config=self.playbook_config, + ) + ) + + result = None + + try: + result = self.execute_module(changed=False, failed=False) + except Exception as e: + self.assertEqual(result, None) + self.assertEqual( + ("dst_interface : Required parameter not found" in str(e)), + True, + ) + + def test_dcnm_intra_links_missing_src_device(self): + + # load the json from playbooks + self.config_data = loadPlaybookData("dcnm_links_configs") + self.payloads_data = loadPlaybookData("dcnm_links_payloads") + + # load required config data + self.playbook_config = self.config_data.get( + "intra_missing_src_device_config" + ) + self.mock_ip_sn = self.payloads_data.get("mock_ip_sn") + self.mock_hn_sn = self.payloads_data.get("mock_hn_sn") + self.mock_fab_inv = self.payloads_data.get("mock_fab_inv_data") + self.mock_num_fab_info = self.payloads_data.get("mock_num_fab_data") + + set_module_args( + dict( + state="merged", + src_fabric="mmudigon-numbered", + config=self.playbook_config, + ) + ) + + result = None + + try: + result = self.execute_module(changed=False, failed=False) + except Exception as e: + self.assertEqual(result, None) + self.assertEqual( + ("src_device : Required parameter not found" in str(e)), True + ) + + def test_dcnm_intra_links_missing_dst_device(self): + + # load the json from playbooks + self.config_data = loadPlaybookData("dcnm_links_configs") + self.payloads_data = loadPlaybookData("dcnm_links_payloads") + + # load required config data + self.playbook_config = self.config_data.get( + "intra_missing_dst_device_config" + ) + self.mock_ip_sn = self.payloads_data.get("mock_ip_sn") + self.mock_hn_sn = self.payloads_data.get("mock_hn_sn") + self.mock_fab_inv = self.payloads_data.get("mock_fab_inv_data") + self.mock_num_fab_info = self.payloads_data.get("mock_num_fab_data") + + set_module_args( + dict( + state="merged", + src_fabric="mmudigon-numbered", + config=self.playbook_config, + ) + ) + + result = None + + try: + result = self.execute_module(changed=False, failed=False) + except Exception as e: + self.assertEqual(result, None) + self.assertEqual( + ("dst_device : Required parameter not found" in str(e)), True + ) + + def test_dcnm_intra_links_missing_template(self): + + # load the json from playbooks + self.config_data = loadPlaybookData("dcnm_links_configs") + self.payloads_data = loadPlaybookData("dcnm_links_payloads") + + # load required config data + self.playbook_config = self.config_data.get( + "intra_missing_template_config" + ) + self.mock_ip_sn = self.payloads_data.get("mock_ip_sn") + self.mock_hn_sn = self.payloads_data.get("mock_hn_sn") + self.mock_fab_inv = self.payloads_data.get("mock_fab_inv_data") + self.mock_num_fab_info = self.payloads_data.get("mock_num_fab_data") + + set_module_args( + dict( + state="merged", + src_fabric="mmudigon-numbered", + config=self.playbook_config, + ) + ) + + result = None + + try: + result = self.execute_module(changed=False, failed=False) + except Exception as e: + self.assertEqual(result, None) + self.assertEqual( + ("Required parameter not found: template" in str(e)), True + ) + + def test_dcnm_intra_links_missing_peer1_ipv6(self): + + # load the json from playbooks + self.config_data = loadPlaybookData("dcnm_links_configs") + self.payloads_data = loadPlaybookData("dcnm_links_payloads") + + # load required config data + self.playbook_config = self.config_data.get( + "intra_missing_peer1_ipv6_config" + ) + self.mock_ip_sn = self.payloads_data.get("mock_ip_sn") + self.mock_hn_sn = self.payloads_data.get("mock_hn_sn") + self.mock_fab_inv = self.payloads_data.get("mock_fab_inv_data") + self.mock_num_fab_info = self.payloads_data.get("mock_num_fab_data") + self.mock_ipv6_fab_info = self.payloads_data.get("mock_ipv6_fab_data") + + set_module_args( + dict( + state="merged", + src_fabric="mmudigon-ipv6-underlay", + config=self.playbook_config, + ) + ) + + result = None + + try: + result = self.execute_module(changed=False, failed=False) + except Exception as e: + self.assertEqual(result, None) + self.assertEqual( + ("peer1_ipv6_addr : Required parameter not found" in str(e)), + True, + ) + + def test_dcnm_intra_links_missing_peer2_ipv6(self): + + # load the json from playbooks + self.config_data = loadPlaybookData("dcnm_links_configs") + self.payloads_data = loadPlaybookData("dcnm_links_payloads") + + # load required config data + self.playbook_config = self.config_data.get( + "intra_missing_peer2_ipv6_config" + ) + self.mock_ip_sn = self.payloads_data.get("mock_ip_sn") + self.mock_hn_sn = self.payloads_data.get("mock_hn_sn") + self.mock_fab_inv = self.payloads_data.get("mock_fab_inv_data") + self.mock_num_fab_info = self.payloads_data.get("mock_num_fab_data") + self.mock_ipv6_fab_info = self.payloads_data.get("mock_ipv6_fab_data") + + set_module_args( + dict( + state="merged", + src_fabric="mmudigon-ipv6-underlay", + config=self.playbook_config, + ) + ) + + result = None + + try: + result = self.execute_module(changed=False, failed=False) + except Exception as e: + self.assertEqual(result, None) + self.assertEqual( + ("peer2_ipv6_addr : Required parameter not found" in str(e)), + True, + ) + + def test_dcnm_intra_links_missing_peer1_ipv4(self): + + # load the json from playbooks + self.config_data = loadPlaybookData("dcnm_links_configs") + self.payloads_data = loadPlaybookData("dcnm_links_payloads") + + # load required config data + self.playbook_config = self.config_data.get( + "intra_missing_peer1_ipv4_config" + ) + self.mock_ip_sn = self.payloads_data.get("mock_ip_sn") + self.mock_hn_sn = self.payloads_data.get("mock_hn_sn") + self.mock_fab_inv = self.payloads_data.get("mock_fab_inv_data") + self.mock_num_fab_info = self.payloads_data.get("mock_num_fab_data") + + set_module_args( + dict( + state="merged", + src_fabric="mmudigon-numbered", + config=self.playbook_config, + ) + ) + + result = None + + try: + result = self.execute_module(changed=False, failed=False) + except Exception as e: + self.assertEqual(result, None) + self.assertEqual( + ("peer1_ipv4_addr : Required parameter not found" in str(e)), + True, + ) + + def test_dcnm_intra_links_missing_peer2_ipv4(self): + + # load the json from playbooks + self.config_data = loadPlaybookData("dcnm_links_configs") + self.payloads_data = loadPlaybookData("dcnm_links_payloads") + + # load required config data + self.playbook_config = self.config_data.get( + "intra_missing_peer2_ipv4_config" + ) + self.mock_ip_sn = self.payloads_data.get("mock_ip_sn") + self.mock_hn_sn = self.payloads_data.get("mock_hn_sn") + self.mock_fab_inv = self.payloads_data.get("mock_fab_inv_data") + self.mock_num_fab_info = self.payloads_data.get("mock_num_fab_data") + + set_module_args( + dict( + state="merged", + src_fabric="mmudigon-numbered", + config=self.playbook_config, + ) + ) + + result = None + + try: + result = self.execute_module(changed=False, failed=False) + except Exception as e: + self.assertEqual(result, None) + self.assertEqual( + ("peer2_ipv4_addr : Required parameter not found" in str(e)), + True, + ) + + def test_dcnm_intra_links_missing_mtu(self): + + # load the json from playbooks + self.config_data = loadPlaybookData("dcnm_links_configs") + self.payloads_data = loadPlaybookData("dcnm_links_payloads") + + # load required config data + self.playbook_config = self.config_data.get("intra_missing_mtu_config") + self.mock_ip_sn = self.payloads_data.get("mock_ip_sn") + self.mock_hn_sn = self.payloads_data.get("mock_hn_sn") + self.mock_fab_inv = self.payloads_data.get("mock_fab_inv_data") + self.mock_num_fab_info = self.payloads_data.get("mock_num_fab_data") + + set_module_args( + dict( + state="merged", + src_fabric="mmudigon-numbered", + config=self.playbook_config, + ) + ) + + result = None + + try: + result = self.execute_module(changed=False, failed=False) + except Exception as e: + self.assertEqual(result, None) + self.assertEqual( + ("mtu : Required parameter not found" in str(e)), True + ) + + def test_dcnm_intra_links_missing_admin_state(self): + + # load the json from playbooks + self.config_data = loadPlaybookData("dcnm_links_configs") + self.payloads_data = loadPlaybookData("dcnm_links_payloads") + + # load required config data + self.playbook_config = self.config_data.get( + "intra_missing_admin_state_config" + ) + self.mock_ip_sn = self.payloads_data.get("mock_ip_sn") + self.mock_hn_sn = self.payloads_data.get("mock_hn_sn") + self.mock_fab_inv = self.payloads_data.get("mock_fab_inv_data") + self.mock_num_fab_info = self.payloads_data.get("mock_num_fab_data") + + set_module_args( + dict( + state="merged", + src_fabric="mmudigon-numbered", + config=self.playbook_config, + ) + ) + + result = None + + try: + result = self.execute_module(changed=False, failed=False) + except Exception as e: + self.assertEqual(result, None) + self.assertEqual( + ("admin_state : Required parameter not found" in str(e)), True + ) + + # ---------------------- INTER-FABRIC INVALID PARAMS ---------------------------- + + def test_dcnm_inter_links_invalid_template(self): + + # load the json from playbooks + self.config_data = loadPlaybookData("dcnm_links_configs") + self.payloads_data = loadPlaybookData("dcnm_links_payloads") + + # load required config data + self.playbook_config = self.config_data.get( + "inter_invalid_template_config" + ) + self.mock_ip_sn = self.payloads_data.get("mock_ip_sn") + self.mock_hn_sn = self.payloads_data.get("mock_hn_sn") + self.mock_fab_inv = self.payloads_data.get("mock_fab_inv_data") + self.mock_num_fab_info = self.payloads_data.get("mock_num_fab_data") + + set_module_args( + dict( + state="query", + src_fabric="mmudigon-numbered", + config=self.playbook_config, + ) + ) + + result = None + + try: + result = self.execute_module(changed=False, failed=False) + except Exception as e: + self.assertEqual(result, None) + self.assertEqual(("Invalid choice provided" in str(e)), True) + + def test_dcnm_inter_links_missing_src_fabric(self): + + # load the json from playbooks + self.config_data = loadPlaybookData("dcnm_links_configs") + self.payloads_data = loadPlaybookData("dcnm_links_payloads") + + # load required config data + self.playbook_config = self.config_data.get( + "inter_missing_src_fabric_config" + ) + self.mock_ip_sn = self.payloads_data.get("mock_ip_sn") + self.mock_hn_sn = self.payloads_data.get("mock_hn_sn") + self.mock_fab_inv = self.payloads_data.get("mock_fab_inv_data") + self.mock_num_fab_info = self.payloads_data.get("mock_num_fab_data") + + set_module_args(dict(state="merged", config=self.playbook_config)) + + result = None + + try: + result = self.execute_module(changed=False, failed=False) + except Exception as e: + self.assertEqual(result, None) + self.assertEqual( + ("missing required arguments: src_fabric" in str(e)), True + ) + + def test_dcnm_inter_links_missing_dst_fabric(self): + + # load the json from playbooks + self.config_data = loadPlaybookData("dcnm_links_configs") + self.payloads_data = loadPlaybookData("dcnm_links_payloads") + + # load required config data + self.playbook_config = self.config_data.get( + "inter_missing_dst_fabric_config" + ) + self.mock_ip_sn = self.payloads_data.get("mock_ip_sn") + self.mock_hn_sn = self.payloads_data.get("mock_hn_sn") + self.mock_fab_inv = self.payloads_data.get("mock_fab_inv_data") + self.mock_num_fab_info = self.payloads_data.get("mock_num_fab_data") + + set_module_args( + dict( + state="merged", + src_fabric="mmudigon-numbered", + config=self.playbook_config, + ) + ) + + result = None + + try: + result = self.execute_module(changed=False, failed=False) + except Exception as e: + self.assertEqual(result, None) + self.assertEqual( + ("Required parameter not found: dst_fabric" in str(e)), True + ) + + def test_dcnm_inter_links_missing_src_intf(self): + + # load the json from playbooks + self.config_data = loadPlaybookData("dcnm_links_configs") + self.payloads_data = loadPlaybookData("dcnm_links_payloads") + + # load required config data + self.playbook_config = self.config_data.get( + "inter_missing_src_intf_config" + ) + self.mock_ip_sn = self.payloads_data.get("mock_ip_sn") + self.mock_hn_sn = self.payloads_data.get("mock_hn_sn") + self.mock_fab_inv = self.payloads_data.get("mock_fab_inv_data") + self.mock_num_fab_info = self.payloads_data.get("mock_num_fab_data") + + set_module_args( + dict( + state="merged", + src_fabric="mmudigon-numbered", + config=self.playbook_config, + ) + ) + + result = None + + try: + result = self.execute_module(changed=False, failed=False) + except Exception as e: + self.assertEqual(result, None) + self.assertEqual( + ("src_interface : Required parameter not found" in str(e)), + True, + ) + + def test_dcnm_inter_links_missing_dst_intf(self): + + # load the json from playbooks + self.config_data = loadPlaybookData("dcnm_links_configs") + self.payloads_data = loadPlaybookData("dcnm_links_payloads") + + # load required config data + self.playbook_config = self.config_data.get( + "inter_missing_dst_intf_config" + ) + self.mock_ip_sn = self.payloads_data.get("mock_ip_sn") + self.mock_hn_sn = self.payloads_data.get("mock_hn_sn") + self.mock_fab_inv = self.payloads_data.get("mock_fab_inv_data") + self.mock_num_fab_info = self.payloads_data.get("mock_num_fab_data") + + set_module_args( + dict( + state="merged", + src_fabric="mmudigon-numbered", + config=self.playbook_config, + ) + ) + + result = None + + try: + result = self.execute_module(changed=False, failed=False) + except Exception as e: + self.assertEqual(result, None) + self.assertEqual( + ("dst_interface : Required parameter not found" in str(e)), + True, + ) + + def test_dcnm_inter_links_missing_src_device(self): + + # load the json from playbooks + self.config_data = loadPlaybookData("dcnm_links_configs") + self.payloads_data = loadPlaybookData("dcnm_links_payloads") + + # load required config data + self.playbook_config = self.config_data.get( + "inter_missing_src_device_config" + ) + self.mock_ip_sn = self.payloads_data.get("mock_ip_sn") + self.mock_hn_sn = self.payloads_data.get("mock_hn_sn") + self.mock_fab_inv = self.payloads_data.get("mock_fab_inv_data") + self.mock_num_fab_info = self.payloads_data.get("mock_num_fab_data") + + set_module_args( + dict( + state="merged", + src_fabric="mmudigon-numbered", + config=self.playbook_config, + ) + ) + + result = None + + try: + result = self.execute_module(changed=False, failed=False) + except Exception as e: + self.assertEqual(result, None) + self.assertEqual( + ("src_device : Required parameter not found" in str(e)), True + ) + + def test_dcnm_inter_links_missing_dst_device(self): + + # load the json from playbooks + self.config_data = loadPlaybookData("dcnm_links_configs") + self.payloads_data = loadPlaybookData("dcnm_links_payloads") + + # load required config data + self.playbook_config = self.config_data.get( + "inter_missing_dst_device_config" + ) + self.mock_ip_sn = self.payloads_data.get("mock_ip_sn") + self.mock_hn_sn = self.payloads_data.get("mock_hn_sn") + self.mock_fab_inv = self.payloads_data.get("mock_fab_inv_data") + self.mock_num_fab_info = self.payloads_data.get("mock_num_fab_data") + + set_module_args( + dict( + state="merged", + src_fabric="mmudigon-numbered", + config=self.playbook_config, + ) + ) + + result = None + + try: + result = self.execute_module(changed=False, failed=False) + except Exception as e: + self.assertEqual(result, None) + self.assertEqual( + ("dst_device : Required parameter not found" in str(e)), True + ) + + def test_dcnm_inter_links_missing_template(self): + + # load the json from playbooks + self.config_data = loadPlaybookData("dcnm_links_configs") + self.payloads_data = loadPlaybookData("dcnm_links_payloads") + + # load required config data + self.playbook_config = self.config_data.get( + "inter_missing_template_config" + ) + self.mock_ip_sn = self.payloads_data.get("mock_ip_sn") + self.mock_hn_sn = self.payloads_data.get("mock_hn_sn") + self.mock_fab_inv = self.payloads_data.get("mock_fab_inv_data") + self.mock_num_fab_info = self.payloads_data.get("mock_num_fab_data") + + set_module_args( + dict( + state="merged", + src_fabric="mmudigon-numbered", + config=self.playbook_config, + ) + ) + + result = None + + try: + result = self.execute_module(changed=False, failed=False) + except Exception as e: + self.assertEqual(result, None) + self.assertEqual( + ("Required parameter not found: template" in str(e)), True + ) + + def test_dcnm_inter_links_missing_ipv4_subnet(self): + + # load the json from playbooks + self.config_data = loadPlaybookData("dcnm_links_configs") + self.payloads_data = loadPlaybookData("dcnm_links_payloads") + + # load required config data + self.playbook_config = self.config_data.get( + "inter_missing_ipv4_subnet_config" + ) + self.mock_ip_sn = self.payloads_data.get("mock_ip_sn") + self.mock_hn_sn = self.payloads_data.get("mock_hn_sn") + self.mock_fab_inv = self.payloads_data.get("mock_fab_inv_data") + self.mock_num_fab_info = self.payloads_data.get("mock_num_fab_data") + self.mock_ipv6_fab_info = self.payloads_data.get("mock_ipv6_fab_data") + + set_module_args( + dict( + state="merged", + src_fabric="mmudigon-numbered", + config=self.playbook_config, + ) + ) + + result = None + + try: + result = self.execute_module(changed=False, failed=False) + except Exception as e: + self.assertEqual(result, None) + self.assertEqual( + ("ipv4_subnet : Required parameter not found" in str(e)), True + ) + + def test_dcnm_inter_links_missing_neighbor_ip(self): + + # load the json from playbooks + self.config_data = loadPlaybookData("dcnm_links_configs") + self.payloads_data = loadPlaybookData("dcnm_links_payloads") + + # load required config data + self.playbook_config = self.config_data.get( + "inter_missing_neighbor_ip_config" + ) + self.mock_ip_sn = self.payloads_data.get("mock_ip_sn") + self.mock_hn_sn = self.payloads_data.get("mock_hn_sn") + self.mock_fab_inv = self.payloads_data.get("mock_fab_inv_data") + self.mock_num_fab_info = self.payloads_data.get("mock_num_fab_data") + self.mock_ipv6_fab_info = self.payloads_data.get("mock_ipv6_fab_data") + + set_module_args( + dict( + state="merged", + src_fabric="mmudigon-numbered", + config=self.playbook_config, + ) + ) + + result = None + + try: + result = self.execute_module(changed=False, failed=False) + except Exception as e: + self.assertEqual(result, None) + self.assertEqual( + ("neighbor_ip : Required parameter not found" in str(e)), True + ) + + def test_dcnm_inter_links_missing_src_asn(self): + + # load the json from playbooks + self.config_data = loadPlaybookData("dcnm_links_configs") + self.payloads_data = loadPlaybookData("dcnm_links_payloads") + + # load required config data + self.playbook_config = self.config_data.get( + "inter_missing_src_asn_config" + ) + self.mock_ip_sn = self.payloads_data.get("mock_ip_sn") + self.mock_hn_sn = self.payloads_data.get("mock_hn_sn") + self.mock_fab_inv = self.payloads_data.get("mock_fab_inv_data") + self.mock_num_fab_info = self.payloads_data.get("mock_num_fab_data") + + set_module_args( + dict( + state="merged", + src_fabric="mmudigon-numbered", + config=self.playbook_config, + ) + ) + + result = None + + try: + result = self.execute_module(changed=False, failed=False) + except Exception as e: + self.assertEqual(result, None) + self.assertEqual( + ("src_asn : Required parameter not found" in str(e)), True + ) + + def test_dcnm_inter_links_missing_dst_asn(self): + + # load the json from playbooks + self.config_data = loadPlaybookData("dcnm_links_configs") + self.payloads_data = loadPlaybookData("dcnm_links_payloads") + + # load required config data + self.playbook_config = self.config_data.get( + "inter_missing_dst_asn_config" + ) + self.mock_ip_sn = self.payloads_data.get("mock_ip_sn") + self.mock_hn_sn = self.payloads_data.get("mock_hn_sn") + self.mock_fab_inv = self.payloads_data.get("mock_fab_inv_data") + self.mock_num_fab_info = self.payloads_data.get("mock_num_fab_data") + + set_module_args( + dict( + state="merged", + src_fabric="mmudigon-numbered", + config=self.playbook_config, + ) + ) + + result = None + + try: + result = self.execute_module(changed=False, failed=False) + except Exception as e: + self.assertEqual(result, None) + self.assertEqual( + ("dst_asn : Required parameter not found" in str(e)), True + ) + + def test_dcnm_inter_links_missing_ipv4_addr(self): + + # load the json from playbooks + self.config_data = loadPlaybookData("dcnm_links_configs") + self.payloads_data = loadPlaybookData("dcnm_links_payloads") + + # load required config data + self.playbook_config = self.config_data.get( + "inter_missing_ipv4_addr_config" + ) + self.mock_ip_sn = self.payloads_data.get("mock_ip_sn") + self.mock_hn_sn = self.payloads_data.get("mock_hn_sn") + self.mock_fab_inv = self.payloads_data.get("mock_fab_inv_data") + self.mock_num_fab_info = self.payloads_data.get("mock_num_fab_data") + + set_module_args( + dict( + state="merged", + src_fabric="mmudigon-numbered", + config=self.playbook_config, + ) + ) + + result = None + + try: + result = self.execute_module(changed=False, failed=False) + except Exception as e: + self.assertEqual(result, None) + self.assertEqual( + ("ipv4_addr : Required parameter not found" in str(e)), True + ) From 6ef9cb871bce181b091357ddd21a436612fe39d4 Mon Sep 17 00:00:00 2001 From: praveenramoorthy <62758226+praveenramoorthy@users.noreply.github.com> Date: Tue, 27 Sep 2022 19:02:08 +0530 Subject: [PATCH 2/7] MFD Fabric rollback fix (#167) --- plugins/modules/dcnm_network.py | 11 ++++++++++- plugins/modules/dcnm_vrf.py | 8 +++++++- .../tests/dcnm/{ => self-contained-tests}/scale.yaml | 0 .../tests/dcnm/{ => self-contained-tests}/scale.yaml | 0 4 files changed, 17 insertions(+), 2 deletions(-) rename tests/integration/targets/dcnm_network/tests/dcnm/{ => self-contained-tests}/scale.yaml (100%) rename tests/integration/targets/dcnm_vrf/tests/dcnm/{ => self-contained-tests}/scale.yaml (100%) diff --git a/plugins/modules/dcnm_network.py b/plugins/modules/dcnm_network.py index 05ef1fa29..2d5c2edc1 100644 --- a/plugins/modules/dcnm_network.py +++ b/plugins/modules/dcnm_network.py @@ -2227,6 +2227,9 @@ def validate_input(self): net["attach"], att_spec ) net["attach"] = valid_att + for attach in net["attach"]: + if attach.get("ports"): + attach["ports"] = [port.capitalize() for port in attach["ports"]] invalid_params.extend(invalid_att) if state != "deleted": @@ -2316,7 +2319,7 @@ def handle_response(self, resp, op): return False, False # Responses to all other operations POST and PUT are handled here. - if res.get("MESSAGE") != "OK": + if res.get("MESSAGE") != "OK" or res["RETURN_CODE"] != 200: fail = True changed = False return fail, changed @@ -2336,6 +2339,12 @@ def handle_response(self, resp, op): def failure(self, resp): + # Donot Rollback for Multi-site fabrics + if self.is_ms_fabric: + self.failed_to_rollback = True + self.module.fail_json(msg=resp) + return + # Implementing a per task rollback logic here so that we rollback DCNM to the have state # whenever there is a failure in any of the APIs. # The idea would be to run overridden state with want=have and have=dcnm_state diff --git a/plugins/modules/dcnm_vrf.py b/plugins/modules/dcnm_vrf.py index 0b2699b1c..1af9c7d6b 100644 --- a/plugins/modules/dcnm_vrf.py +++ b/plugins/modules/dcnm_vrf.py @@ -2037,7 +2037,7 @@ def handle_response(self, res, op): return False, False # Responses to all other operations POST and PUT are handled here. - if res.get("MESSAGE") != "OK": + if res.get("MESSAGE") != "OK" or res["RETURN_CODE"] != 200: fail = True changed = False return fail, changed @@ -2054,6 +2054,12 @@ def handle_response(self, res, op): def failure(self, resp): + # Donot Rollback for Multi-site fabrics + if self.fabric_type == "MFD": + self.failed_to_rollback = True + self.module.fail_json(msg=resp) + return + # Implementing a per task rollback logic here so that we rollback DCNM to the have state # whenever there is a failure in any of the APIs. # The idea would be to run overridden state with want=have and have=dcnm_state diff --git a/tests/integration/targets/dcnm_network/tests/dcnm/scale.yaml b/tests/integration/targets/dcnm_network/tests/dcnm/self-contained-tests/scale.yaml similarity index 100% rename from tests/integration/targets/dcnm_network/tests/dcnm/scale.yaml rename to tests/integration/targets/dcnm_network/tests/dcnm/self-contained-tests/scale.yaml diff --git a/tests/integration/targets/dcnm_vrf/tests/dcnm/scale.yaml b/tests/integration/targets/dcnm_vrf/tests/dcnm/self-contained-tests/scale.yaml similarity index 100% rename from tests/integration/targets/dcnm_vrf/tests/dcnm/scale.yaml rename to tests/integration/targets/dcnm_vrf/tests/dcnm/self-contained-tests/scale.yaml From 204daa54c397fd61a72a03e532a2d9860220aa04 Mon Sep 17 00:00:00 2001 From: praveenramoorthy <62758226+praveenramoorthy@users.noreply.github.com> Date: Fri, 7 Oct 2022 18:39:51 +0530 Subject: [PATCH 3/7] Fix for network module traceback (#170) --- plugins/modules/dcnm_network.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/plugins/modules/dcnm_network.py b/plugins/modules/dcnm_network.py index 2d5c2edc1..76dec9b65 100644 --- a/plugins/modules/dcnm_network.py +++ b/plugins/modules/dcnm_network.py @@ -2415,9 +2415,9 @@ def dcnm_update_network_information(self, want, have, cfg): if cfg.get("is_l2only", None) is None: json_to_dict_want["isLayer2Only"] = json_to_dict_have["isLayer2Only"] - if json_to_dict_want["isLayer2Only"].lower() == "true": + if str(json_to_dict_want["isLayer2Only"]).lower() == "true": json_to_dict_want["isLayer2Only"] = True - elif json_to_dict_want["isLayer2Only"].lower() == "false": + elif str(json_to_dict_want["isLayer2Only"]).lower() == "false": json_to_dict_want["isLayer2Only"] = False if cfg.get("vlan_name", None) is None: @@ -2433,9 +2433,9 @@ def dcnm_update_network_information(self, want, have, cfg): if cfg.get("arp_suppress", None) is None: json_to_dict_want["suppressArp"] = json_to_dict_have["suppressArp"] - if json_to_dict_want["suppressArp"].lower() == "true": + if str(json_to_dict_want["suppressArp"]).lower() == "true": json_to_dict_want["suppressArp"] = True - elif json_to_dict_want["suppressArp"].lower() == "false": + elif str(json_to_dict_want["suppressArp"]).lower() == "false": json_to_dict_want["suppressArp"] = False if cfg.get("dhcp_srvr1_ip", None) is None: From 783673240776af19cd5a0a005d24272a8dd21745 Mon Sep 17 00:00:00 2001 From: praveenramoorthy <62758226+praveenramoorthy@users.noreply.github.com> Date: Fri, 14 Oct 2022 14:39:49 +0530 Subject: [PATCH 4/7] Update CHANGELOG.md --- CHANGELOG.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d34ffd146..661059006 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,18 @@ This project adheres to [Semantic Versioning](http://semver.org/). ## Unreleased +## [2.2.0] - 2022-10-14 + +### Added + +* The following new modules are included in this release + * `dcnm_links` - Module for managing dcnm links + +### Fixed + +* https://github.com/CiscoDevNet/ansible-dcnm/issues/155 +* https://github.com/CiscoDevNet/ansible-dcnm/issues/169 + ## [2.1.1] - 2022-08-18 ### Fixed @@ -178,6 +190,7 @@ The Ansible Cisco Data Center Network Manager (DCNM) collection includes modules * cisco.dcnm.dcnm_network - Add and remove Networks from a DCNM managed VXLAN fabric. * cisco.dcnm.dcnm_interface - DCNM Ansible Module for managing interfaces. +[2.2.0]: https://github.com/CiscoDevNet/ansible-dcnm/compare/2.1.1...2.2.0 [2.1.1]: https://github.com/CiscoDevNet/ansible-dcnm/compare/2.1.0...2.1.1 [2.1.0]: https://github.com/CiscoDevNet/ansible-dcnm/compare/2.0.1...2.1.0 [2.0.1]: https://github.com/CiscoDevNet/ansible-dcnm/compare/2.0.0...2.0.1 From 5e546af38808f4897788af97793af68030e667cd Mon Sep 17 00:00:00 2001 From: praveenramoorthy <62758226+praveenramoorthy@users.noreply.github.com> Date: Fri, 14 Oct 2022 14:43:24 +0530 Subject: [PATCH 5/7] Update galaxy.yml --- galaxy.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/galaxy.yml b/galaxy.yml index f7cbae7b9..ab1908dcc 100644 --- a/galaxy.yml +++ b/galaxy.yml @@ -1,7 +1,7 @@ --- namespace: cisco name: dcnm -version: 2.1.1 +version: 2.2.0 readme: README.md authors: - Shrishail Kariyappanavar From 31fa01c6a11f38ccb3dc93d7b7acdb0f2a1575f2 Mon Sep 17 00:00:00 2001 From: praveenramoorthy <62758226+praveenramoorthy@users.noreply.github.com> Date: Fri, 14 Oct 2022 14:47:56 +0530 Subject: [PATCH 6/7] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f88bce05d..bf831a932 100644 --- a/README.md +++ b/README.md @@ -61,7 +61,7 @@ You can also include it in a `requirements.yml` file and install it with `ansibl --- collections: - name: cisco.dcnm - version: 2.1.1 + version: 2.2.0 ``` ## Using this collection From 0c08fe2a0054908bfcbda65671ebc9332e199047 Mon Sep 17 00:00:00 2001 From: praveenramoorthy <62758226+praveenramoorthy@users.noreply.github.com> Date: Fri, 14 Oct 2022 15:03:06 +0530 Subject: [PATCH 7/7] Update CHANGELOG.md --- CHANGELOG.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 661059006..61f7680ec 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,8 +2,6 @@ All notable changes to this project will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org/). -## Unreleased - ## [2.2.0] - 2022-10-14 ### Added