diff options
Diffstat (limited to 'ansible_collections/cisco/aci')
247 files changed, 29859 insertions, 2292 deletions
diff --git a/ansible_collections/cisco/aci/.DS_Store b/ansible_collections/cisco/aci/.DS_Store Binary files differindex 85ed7edfb..8d4247a73 100644 --- a/ansible_collections/cisco/aci/.DS_Store +++ b/ansible_collections/cisco/aci/.DS_Store diff --git a/ansible_collections/cisco/aci/CHANGELOG.rst b/ansible_collections/cisco/aci/CHANGELOG.rst index 2c0be271f..4f4887bb5 100644 --- a/ansible_collections/cisco/aci/CHANGELOG.rst +++ b/ansible_collections/cisco/aci/CHANGELOG.rst @@ -5,6 +5,86 @@ Cisco ACI Ansible Collection Release Notes .. contents:: Topics +v2.8.0 +====== + +Release Summary +--------------- + +Release v2.8.0 of the ``ansible-aci`` collection on 2023-11-04. +This changelog describes all changes made to the modules and plugins included in this collection since v2.7.0. + + +Minor Changes +------------- + +- Add 8.0 option for dvs_version attribute in aci_vmm_controller +- Add Match Rules for aci_route_control_profile modules +- Add aci_bgp_timers_policy and aci_bgp_best_path_policy modules +- Add aci_fabric_interface_policy_group module +- Add aci_interface_policy_leaf_fc_policy_group and aci_interface_policy_spine_policy_group module +- Add aci_l3out_bgp_protocol_profile module +- Add aci_match_community_factor module. +- Add aci_route_control_context and aci_match_rule modules +- Add aci_route_control_profile module +- Add hmac-sha2-224, hmac-sha2-256, hmac-sha2-384, hmac-sha2-512 authentication types and description to aci_snmp_user module +- Add loopback interface profile as a child class for aci_l3out_logical_node. +- Add missing attributes in aci_interface_policy_leaf_policy_group +- Add missing attributes to aci_l3out_extepg module +- Add missing test cases, fix found issues and add missing attributes for aci_fabric_scheduler, aci_firmware_group, aci_firmware_group_node, aci_firmware_policy, aci_interface_policy_fc, aci_interface_policy_lldp, aci_interface_policy_mcp, aci_interface_policy_ospf, aci_interface_policy_port_channel, aci_maintenance_group, aci_maintenance_group_node, aci_maintenance_policy and aci_tenant_ep_retention_policy modules (#453) +- Add support for checkmode in aci_rest module +- Add support for configuration of fabric node control with aci_fabric_node_control module +- Add support for configuration of fabric pod selectors with aci_fabric_pod_selector module +- Add support for configuration of system banner and alias with aci_system_banner module +- Add support for configuration of system endpoint controls, ip aging, ep loop protection and roque endpoint control with aci_system_endpoint_controls module +- Add support for configuration of system fabric wide settings with aci_fabric_wide_settings module +- Add support for configuration of system global aes passphrase encryption with aci_system_global_aes_passphrase_encryption module +- Add support for global infra dhcp relay policy configuration in aci_dhcp_relay +- Add support for global infra dhcp relay policy configuration in aci_dhcp_relay_provider + +Bugfixes +-------- + +- Fixed issue with default values for ssl, proxy, timeout in aci.py and the display of host in the url when the plugin httpapi is used +- Modified aci_rest and aci_config_snapshot modules to display the correct URL output string (#487) + +v2.7.0 +====== + +Release Summary +--------------- + +Release v2.7.0 of the ``ansible-aci`` collection on 2023-08-04. +This changelog describes all changes made to the modules and plugins included in this collection since v2.6.0. + + +Minor Changes +------------- + +- Add ACI HTTPAPI plugin with multi host support (#114) +- Add OSPF parameters to aci_l3out module and create the associated test case. +- Add aci_access_span_src_group modules for access span source group support +- Add aci_access_span_src_group_src module for access span source support +- Add aci_access_span_src_group_src_path module for access span source path support +- Add aci_epg_subnet module (#424) +- Add aci_fabric_span_dst_group module for fabric span destination group support +- Add aci_fabric_span_src_group module for fabric span source group support +- Add aci_fabric_span_src_group_src module for fabric span source support +- Add aci_fabric_span_src_group_src_node module for fabric span source node support +- Add aci_fabric_span_src_group_src_path module for fabric span source path support +- Add aci_file_remote_path module (#379) +- Add aci_vrf_leak_internal_subnet module (#449) +- Add description parameter for aci_l3out_logical_interface_profile +- Add ip_data_plane_learning attribute to aci_bd_subnet and aci_vrf modules (#413) +- Add local_as_number_config and local_as_number attributes to support bgpLocalAsnP child object in aci_l3out_bgp_peer module (#416) +- Add node_type and remote_leaf_pool_id attributes to aci_fabric_node +- Add source_port, source_port_start, source_port_end, tcp_flags and match_only_fragments attributes to aci_filter_entry module (#426) + +Bugfixes +-------- + +- Change input of prefix_suppression to type string to allow enable, disable and inherit options for aci_interface_policy_ospf + v2.6.0 ====== diff --git a/ansible_collections/cisco/aci/FILES.json b/ansible_collections/cisco/aci/FILES.json index fe6f80a13..5f5760ef8 100644 --- a/ansible_collections/cisco/aci/FILES.json +++ b/ansible_collections/cisco/aci/FILES.json @@ -11,7 +11,7 @@ "name": ".DS_Store", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "8ab10835fc34527778d4129e34dbacbb158d73e27473fca94b386068b6140440", + "chksum_sha256": "be8536e540953dd873ccdfc3591329e1a8093e6c12ca7cbf38dd0bc2563f7a83", "format": 1 }, { @@ -130,7 +130,7 @@ "name": "plugins/doc_fragments/aci.py", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "0f1f277fcc52a2f5d54f6d86c02c7379a4cf0f11440a06b36c3587ff8a7963db", + "chksum_sha256": "631f5585bf747ab5c3fb044c2c877f21b459c06d7abdf3b3364c4be1b5d5e83d", "format": 1 }, { @@ -155,6 +155,20 @@ "format": 1 }, { + "name": "plugins/httpapi", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "plugins/httpapi/aci.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "264746072bfde8e36623c5e266ad846c13d665f35d9180368a258c85c7899741", + "format": 1 + }, + { "name": "plugins/module_utils", "ftype": "dir", "chksum_type": null, @@ -165,7 +179,7 @@ "name": "plugins/module_utils/constants.py", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "eb8f39107b02f9657864e0f175aeb90c3152b81f7509d237e3acae01e460ce48", + "chksum_sha256": "e15db3f08848a793cc4590cc19d160890d363b3f396d3e438b874e00e1ed9112", "format": 1 }, { @@ -179,7 +193,7 @@ "name": "plugins/module_utils/aci.py", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "259513c4d37eaecd9a3fe0f7087dd1b6981d158310ae81cd38fe345d217d311b", + "chksum_sha256": "1abc88c0d889479a346251e722ef7ad4e823d4eab9d326247fcc3af2865bf120", "format": 1 }, { @@ -207,7 +221,14 @@ "name": "plugins/modules/aci_l3out_bgp_peer.py", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "faab5ad0cf9a7238be58ced7d757a32ace5cd303645574783dbf13eb2c97de2f", + "chksum_sha256": "bc162cb93c76cb70063f916bc48f46326b3ee70aa444ef0f8c9a2fcfc8198532", + "format": 1 + }, + { + "name": "plugins/modules/aci_bgp_timers_policy.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "3a449b3f6278ea5215c75674923e2c68e3219824866190a39e6af667e683f3c6", "format": 1 }, { @@ -235,7 +256,7 @@ "name": "plugins/modules/aci_domain_to_encap_pool.py", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "1fb77ae60036c6d3b70da75efc094c0751b8c46b6ab3b40446b2f57297bccda3", + "chksum_sha256": "5467c60221c3a7518f5e4210c54d59dfe29067accc32a7771f4fb67f37756ac0", "format": 1 }, { @@ -270,7 +291,14 @@ "name": "plugins/modules/aci_l3out_logical_interface_profile_ospf_policy.py", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "9bfc58040b98a57e97d1997f62663fd4eb928bedbafc14851a46a30065b4cdc7", + "chksum_sha256": "22e305b0ab4190723cef3630013cacb40e10041143b10314be506b281f1601ee", + "format": 1 + }, + { + "name": "plugins/modules/aci_system_banner.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "794eb271593b0c42162d6f9fa36b9418fb219550822cba248413af8b500fb5bd", "format": 1 }, { @@ -284,7 +312,7 @@ "name": "plugins/modules/aci_domain_to_vlan_pool.py", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "adfa943c844a93c2ecdfacc7691750179ddf9d78da1501856fec35cf74c24fed", + "chksum_sha256": "101d246d0da768de469e06b2f20c172023edcb3a800f994d6dff3ea5edacea43", "format": 1 }, { @@ -298,14 +326,14 @@ "name": "plugins/modules/aci_l3out_logical_interface_profile.py", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "3cdbb75d8876061bc25be514ad013b999258e3cc0be3db70e3876872262f3c72", + "chksum_sha256": "49e176ad8ece44f6c3d1e76e3ca82edfd005de6921d51171290262af06414aed", "format": 1 }, { "name": "plugins/modules/aci_l3out_static_routes.py", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "a17712517b17eae7754b7cac5d34f5e2cdec38b791f4ff605b9315c8e1151317", + "chksum_sha256": "a75ad5dbd6e8eedce392adc2ccce2d2777d1aa60558f6ffc2a713840dde492bc", "format": 1 }, { @@ -316,6 +344,20 @@ "format": 1 }, { + "name": "plugins/modules/aci_match_as_path_regex_term.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "51624d35d5122c4b40eb36253409211c8680a353ae589800e0b8860c8d4058c0", + "format": 1 + }, + { + "name": "plugins/modules/aci_fabric_span_src_group.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "20398858c1d8e9883b641ea0dfa73564745c076a68d373dc9b5859aaff0a6c93", + "format": 1 + }, + { "name": "plugins/modules/aci_cloud_subnet.py", "ftype": "file", "chksum_type": "sha256", @@ -326,7 +368,7 @@ "name": "plugins/modules/aci_aep_to_domain.py", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "ec2f67cebfdbadd923cfd40445e51fc83c6fae49d007dd4d2f7c36ce50e76d1c", + "chksum_sha256": "f1993e5ecd2b23c0a89eae972b51fabe0b3a694db2177bb6daa07e37b76195a0", "format": 1 }, { @@ -354,7 +396,7 @@ "name": "plugins/modules/aci_firmware_group_node.py", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "23da2ac4be9e4abd679ebc779dfff301051c05e1a768721bcdab9e495cd1232e", + "chksum_sha256": "81bdb29637c19e9e6865aeeaf3bbba04d55d32b28bc27f7c1209a7af752cd203", "format": 1 }, { @@ -389,7 +431,7 @@ "name": "plugins/modules/aci_config_snapshot.py", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "f978081320905c5a81461a34988f81da1f36351d99ad5a210131f3bfedb817c4", + "chksum_sha256": "d73660d7443d035f9af9602b69fa9d68bb650f9a746879c79d0f152cd8b49726", "format": 1 }, { @@ -410,7 +452,14 @@ "name": "plugins/modules/aci_interface_policy_lldp.py", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "022d1bb902ce695cec467ccc689fd6aa28eb839ed3791b7a1c6285160c780726", + "chksum_sha256": "5f70f805184081c839bd8ac1f2c03b7eb33613342c7edbeab7353dc2311d835c", + "format": 1 + }, + { + "name": "plugins/modules/aci_system_endpoint_controls.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "938ed82e982e1fac1b4c1aa35d952336cf466ec2b2ee5efcdb1107580698a491", "format": 1 }, { @@ -431,14 +480,14 @@ "name": "plugins/modules/aci_tenant_span_src_group.py", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "086453fe4b224e26006d8efc214f95384f019f33b22c4d69d543dce19fb382b4", + "chksum_sha256": "208cdb40e3cceae7697af03747527892db675a103736c22e566b255640a0af19", "format": 1 }, { "name": "plugins/modules/aci_vmm_controller.py", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "e8a0ab2347bbb1c5027624d1012a80afc8f403c1aa298f69eef6921ec88e72a1", + "chksum_sha256": "bf296cd7457a775c3ac07f5a1c902b2f498a85eb4c50193289dedc42eea12d93", "format": 1 }, { @@ -452,7 +501,7 @@ "name": "plugins/modules/aci_static_binding_to_epg.py", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "b00279e1d059f29bbbdc193757c98d0a94c7de3671a76873dc6b5e743b586439", + "chksum_sha256": "5ccc1b76298ca9c8451e9f4a340e044bc9f3117557182f242f28c0dc1c893f9a", "format": 1 }, { @@ -463,6 +512,20 @@ "format": 1 }, { + "name": "plugins/modules/aci_match_community_factor.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "a803921466585d3717393fd47e9534c96d51abb3e1a7b90abd51c53baee276f5", + "format": 1 + }, + { + "name": "plugins/modules/aci_fabric_interface_policy_group.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "f991680f4f3b96c2f1256317a457adfe5b736fb245e6b312f6faa1ee2de81d1f", + "format": 1 + }, + { "name": "plugins/modules/aci_l3out_interface_secondary_ip.py", "ftype": "file", "chksum_type": "sha256", @@ -494,14 +557,14 @@ "name": "plugins/modules/aci_filter_entry.py", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "a657cf4f15ff00037e071c9f00ac879b701d3a4bf32697984730548527f80c54", + "chksum_sha256": "e1f7211a557bd071203fab94d37eeb8454b9e79b37ac63bdd44584b4381f98d6", "format": 1 }, { "name": "plugins/modules/aci_vrf.py", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "81fc6b1ba647ef5fe66b6e8e56843a169cb22005df4a4e2d0285e5801c97b9ca", + "chksum_sha256": "eedace51d17534ed55654c8a3658f0a0bf7b393b334a668c08fb3dfd7f5c4e24", "format": 1 }, { @@ -522,14 +585,14 @@ "name": "plugins/modules/aci_l3out.py", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "1645e534765fcccc01efe6b01f078a23c28b283714a053cb0385b62112bef0eb", + "chksum_sha256": "7671c4aa2a641a1f9c2a3020d9577b876edcee0533dd44da8b6e14177f48aa0c", "format": 1 }, { "name": "plugins/modules/aci_interface_policy_fc.py", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "b1c4ce46f1d6acd77f9dc55654fab914414dd8897c573d4f1d89360baab8ba45", + "chksum_sha256": "ad1059205a850876d017993164033315d59d2999643906c3939aadcb4db1f72e", "format": 1 }, { @@ -550,7 +613,7 @@ "name": "plugins/modules/aci_tenant_span_src_group_src.py", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "ae02661fe57c7f6dac5c23b901221f9d364aba93cc432ea73333fdf881cada49", + "chksum_sha256": "31be504c952dfad7494a6bb8a62b671ecba4ef69e4a2d2665d7991587cd80c7f", "format": 1 }, { @@ -571,14 +634,28 @@ "name": "plugins/modules/aci_interface_policy_mcp.py", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "61c9fe964d6101de5dc2f14914f810a5ad9cb6ab42220b1d9a70135b5c21a10e", + "chksum_sha256": "9c65d9b64d15f0b104dcb0d87bffb52c90c707e8f3e54a0bd20c4e060c1f7225", + "format": 1 + }, + { + "name": "plugins/modules/aci_fabric_span_src_group_src_node.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "1cda122f6d761e6a8038ab994914079056c1f24b9a3aa9b363f1e8d6901bab7a", "format": 1 }, { "name": "plugins/modules/aci_maintenance_policy.py", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "5d804d0965126662623c013aaf432908ccd69248d6991cdfe09ce6224dbb4eac", + "chksum_sha256": "cc16c435345467c701db77796f79085b83114454d4534588c16551dcd28b6f4a", + "format": 1 + }, + { + "name": "plugins/modules/aci_interface_policy_leaf_fc_policy_group.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "25f72bb8b07c4ce0caf364f5b8413e43bfe6b773c54414fa4de8f64c329dd0d9", "format": 1 }, { @@ -592,7 +669,7 @@ "name": "plugins/modules/aci_access_span_filter_group_entry.py", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "64adb830f89649c066b27d6b7f9970b7780a3c35e371ad0d3237d90a3f1d6437", + "chksum_sha256": "c36ed531ca3bf8e98e6f64e9385efb74421275210c012873b61373e78aa2c7e3", "format": 1 }, { @@ -627,7 +704,35 @@ "name": "plugins/modules/aci_tenant_span_dst_group.py", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "44d2bda6d8a34f593cacc9cc6dd22a4218578d9782e60583564739b17c293890", + "chksum_sha256": "8d9925ec534dcaa27a1d21df95ea096fe3b68a981833d6661ee62190d7155180", + "format": 1 + }, + { + "name": "plugins/modules/aci_match_community_regex_term.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "2fdb633814a5b5295a686f15a131ea4222be7be64d61e515f6baefe545fb5a66", + "format": 1 + }, + { + "name": "plugins/modules/aci_route_control_profile.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "51eccf37889832ae2284b28cea4fa1f7c40c9c293e9ae5c4bb1f125aba4733c5", + "format": 1 + }, + { + "name": "plugins/modules/aci_access_span_src_group_src.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "a43b91a93cb9b087e60688485de6c9679894f0271b5a8b63291e98b9ce67bf03", + "format": 1 + }, + { + "name": "plugins/modules/aci_fabric_span_dst_group.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "3045c8c83a93e4956c434f1891ac3770717c8bb3fe2d8a849eaa44bbd880b03e", "format": 1 }, { @@ -638,6 +743,13 @@ "format": 1 }, { + "name": "plugins/modules/aci_fabric_span_src_group_src_path.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "ebd390dea4cbd43ddec4c08b693248ea1a8da5ffe30df4fdcceedecacd159ab6", + "format": 1 + }, + { "name": "plugins/modules/aci_contract.py", "ftype": "file", "chksum_type": "sha256", @@ -648,7 +760,7 @@ "name": "plugins/modules/aci_fabric_node.py", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "7600747bb6b48c0dfe7e0f45668c8f1eede45f3cad43692462d7ee6f23fbeb7d", + "chksum_sha256": "61ca3965bfbce19ef1a2aaf3ed41d7fecf187f06563acf4db3fb4ae9373ad736", "format": 1 }, { @@ -662,14 +774,14 @@ "name": "plugins/modules/aci_firmware_group.py", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "7f46f31a4f391b9b77e64bc067118a18f042353e3ecc73cccab125ee69bf1632", + "chksum_sha256": "217b392ac902fab9a1dd42d65ab4d710a797dc9f2df555712625b3cf697ab441", "format": 1 }, { "name": "plugins/modules/aci_firmware_policy.py", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "90f4734b7c45e96f951087f886da80fe0fc17c2798a23cb7d97fc5644bf0a779", + "chksum_sha256": "2c54e6442c0762f4821eb87c7037e395a1b6b4abe100ce347ddaaac81afe75a7", "format": 1 }, { @@ -690,7 +802,7 @@ "name": "plugins/modules/aci_interface_policy_ospf.py", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "00e00306fdb73ae59422b684669ec3ab4dab23e85adf93ec809b7182a57d9874", + "chksum_sha256": "cb25e6f65a89c31b22df59cc6c33a318276d336e6e0f535cc525d648a6a9b29d", "format": 1 }, { @@ -704,7 +816,7 @@ "name": "plugins/modules/aci_snmp_user.py", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "b2dd6c13e5b6887f0d939c25f9eeaf39e2a57b6b04e1ecc1d8d78e55933d7238", + "chksum_sha256": "90dd375a7441d6b293b0d889310f157a97d186ca8504cc3245da41a5eef8853a", "format": 1 }, { @@ -722,6 +834,13 @@ "format": 1 }, { + "name": "plugins/modules/aci_epg_subnet.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "f000d54a2ea5d0909f319e43c5fb1ca51029a9a052f8e49122d51de8f2d8535a", + "format": 1 + }, + { "name": "plugins/modules/aci_node_mgmt_epg.py", "ftype": "file", "chksum_type": "sha256", @@ -729,6 +848,13 @@ "format": 1 }, { + "name": "plugins/modules/aci_l3out_bgp_protocol_profile.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "f63d4e7f80ad2dcd1a7ec3aa3a85aa9cea110e622c07874b89cb77ca5e7d35c7", + "format": 1 + }, + { "name": "plugins/modules/aci_cloud_region.py", "ftype": "file", "chksum_type": "sha256", @@ -739,14 +865,14 @@ "name": "plugins/modules/aci_encap_pool_range.py", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "03fa1e9ac1a84ccba3fad0aecb24b4c6caff745eabfc012d22047d18c5c67e5a", + "chksum_sha256": "0faeb5ce1485c0d0ef993c58de5eb8c7056b5c6964ee9765b9e4ee45049e905e", "format": 1 }, { "name": "plugins/modules/aci_access_span_dst_group.py", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "cf6caf90d76ef111f7efff8acb2835163924d4bf3c2f1fd26524ed1091e7cbaa", + "chksum_sha256": "b6e34ce9d14e5a984922ea907de25f480778ad01a0ebab49faea4fb632852bfc", "format": 1 }, { @@ -788,7 +914,7 @@ "name": "plugins/modules/aci_vlan_pool_encap_block.py", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "22938907ba58af0b13ab88938b15e4216e7169e141dfe5ebd2bbdbd876759bbd", + "chksum_sha256": "d5aeb8a60e479ddc534ff5e41b203a857722cce1181a02b097998418c2876c54", "format": 1 }, { @@ -802,21 +928,21 @@ "name": "plugins/modules/aci_interface_config.py", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "10fc10fb95d06c0410e1cc6006d45c4424ca49307e1a96d5181af3ae9d4d3bd5", + "chksum_sha256": "a2245b78051930e1939be20957cef47b177c7a5132fe18971e95db197080fcbf", "format": 1 }, { "name": "plugins/modules/aci_l3out_logical_node_profile.py", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "46d4966e09b7c0d7456e9abf8e4e16fbbb2a30c3642d504ddad388d94cd9ce2a", + "chksum_sha256": "6e2104cbc94406fc36b5ce27f76c924f18c8199db79c4efdfcdc6ffb8ab07f94", "format": 1 }, { "name": "plugins/modules/aci_maintenance_group_node.py", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "5f989517c7324fcefe4fbd425541292ba2c21558f487d3ae5f89b4b428b0890c", + "chksum_sha256": "50b579ec246dd92a298d9438d1a127ec616c08a702cd744f3eebb5af12c5d352", "format": 1 }, { @@ -841,24 +967,31 @@ "format": 1 }, { + "name": "plugins/modules/aci_file_remote_path.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "3090836575849a21c16126d8a7ff50073c287a008b38501e2ed1e69307ac2bce", + "format": 1 + }, + { "name": "plugins/modules/aci_maintenance_group.py", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "05cf96356912bda7c414d1c7421d955933d7301d77156ddc3b1863d08949c1d0", + "chksum_sha256": "94cf71f04d0698158076dc4d258858c01b1eb5c0023975663d46e2e11d0b8be8", "format": 1 }, { "name": "plugins/modules/aci_bulk_static_binding_to_epg.py", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "069df8b23931968bd3706c55fa312cb4bc841e388c691a61e1ac55f4930d9295", + "chksum_sha256": "a456d7160f81a4a5643537b87d6fbe03b8254947c39c3a1fce76413f040f7c74", "format": 1 }, { "name": "plugins/modules/aci_tag.py", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "84b981c1ed860f7bc18bea770dbdff7e611647a2ff6b3e7fa0f5a55c699acc88", + "chksum_sha256": "7cef96d7872d8c72eec261ae88fb365d3357556d78192922f9e31bac6eddc2e8", "format": 1 }, { @@ -883,10 +1016,24 @@ "format": 1 }, { + "name": "plugins/modules/aci_match_route_destination.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "2e07f2b93fbad18f038cb0a3c9b417f02b1b21993885387d696ddeb9f99dba6d", + "format": 1 + }, + { + "name": "plugins/modules/aci_fabric_span_src_group_src.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "3e8f55259a42e3c2b495a85713441f4018e5a3f69210bf9eab5b0cf02601db0b", + "format": 1 + }, + { "name": "plugins/modules/aci_bd_subnet.py", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "1ed7880f36f254cc404e587cad2fb45052d0c2b9a005e8f611066989806f788c", + "chksum_sha256": "58930e496a02c5b5edc9d629dc6947490aac1cd8062cd3a7a0947f0a86802445", "format": 1 }, { @@ -925,6 +1072,13 @@ "format": 1 }, { + "name": "plugins/modules/aci_fabric_wide_settings.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "14ae98b14724769b9dc68d8ba82a05a4822638373908bd35ac22124d4190aa76", + "format": 1 + }, + { "name": "plugins/modules/aci_aaa_user_domain.py", "ftype": "file", "chksum_type": "sha256", @@ -939,10 +1093,17 @@ "format": 1 }, { + "name": "plugins/modules/aci_match_community_term.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "398e03d7c11c76bd711f1d1622f57464ba02d2afc328f157c17b17551327c439", + "format": 1 + }, + { "name": "plugins/modules/aci_access_port_to_interface_policy_leaf_profile.py", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "56584e8a7fdff50fc585672523b62ea14cd4485472fbb41e4efe9a1c9f128fbb", + "chksum_sha256": "bc69261f8fa08ba96021350c055a15a24c9b5f4d1ebdc5cd1811c35667c5005e", "format": 1 }, { @@ -984,7 +1145,7 @@ "name": "plugins/modules/aci_config_rollback.py", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "e4c1fb58858025d229d62aef534d91e0fe9234a8a012eaf50bf0a1dc37c4a5c7", + "chksum_sha256": "56f01c139ab3d314d1d4ad42660b5bd722c6cd944b135ba425f2beebc268e7fc", "format": 1 }, { @@ -998,7 +1159,7 @@ "name": "plugins/modules/aci_tenant_ep_retention_policy.py", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "65b10149680330cce74ab66fdee91c9ad3ab3c8c320739bfffa24d0240ddf788", + "chksum_sha256": "562b639ecebe39eb545fb81fb18e0fecfd4227ab2f261ce061b7c890c1817faa", "format": 1 }, { @@ -1033,7 +1194,14 @@ "name": "plugins/modules/aci_rest.py", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "f262f40e7e121bef0fefef3587e2b49b5239ecae79596a6e3a9a404020af7d9e", + "chksum_sha256": "a3e4869643545856630c76bab8af0c1e8a71ca3b4af7ef29a79b88c1ed2464f2", + "format": 1 + }, + { + "name": "plugins/modules/aci_fabric_pod_selector.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "fead1bdf7c39203eb394f8fd96f7a454ed89de4d40206e3c735d6b5e870ec58d", "format": 1 }, { @@ -1065,6 +1233,13 @@ "format": 1 }, { + "name": "plugins/modules/aci_route_control_context.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "61e92079ef547b98c9044b5ccddb60f710119b4061b758ad961402ac774f27c6", + "format": 1 + }, + { "name": "plugins/modules/aci_l2out_extepg_to_contract.py", "ftype": "file", "chksum_type": "sha256", @@ -1082,7 +1257,7 @@ "name": "plugins/modules/aci_l3out_logical_node.py", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "27975f7192acd2b5ddea8b828822c660114b88a04bd1636b00a7f4fc6f9218df", + "chksum_sha256": "762ecc6681dab6e00794b8567a1cbadd5bd42ecdc6ab7af60f9b151d89e4a908", "format": 1 }, { @@ -1096,7 +1271,7 @@ "name": "plugins/modules/aci_interface_policy_leaf_policy_group.py", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "ba0d4f00c4b60eaed99adde1d14ff1969feaa781dd138578a4645615a2d6a945", + "chksum_sha256": "606a16df31e90d074ff9c9c4e2b1cff46c7a2da68f43cc356a26aed1c4d2ee49", "format": 1 }, { @@ -1117,7 +1292,7 @@ "name": "plugins/modules/aci_fabric_scheduler.py", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "70100797015c2f1fd61ba63645230c2f89fa0da5fddcff0793120f5ae939a6a9", + "chksum_sha256": "4cbd08823742d4c36c1dbe3b65d0b73593591c8e502b7f2d3910140e7f867f33", "format": 1 }, { @@ -1131,7 +1306,14 @@ "name": "plugins/modules/aci_l3out_extepg.py", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "1c3d1068c4df63e04cca69631ad69635047e406b928303e43b0af2eff4d764c3", + "chksum_sha256": "bcfd941ee89b3956e4f76b203ab20bdbe40c4af72e1c363e300447ac4ba81209", + "format": 1 + }, + { + "name": "plugins/modules/aci_bgp_best_path_policy.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "866b0ec86a66ff6e2671e3127a60b11807d3fea75825576a5ebb72ac4cc4fd11", "format": 1 }, { @@ -1142,6 +1324,13 @@ "format": 1 }, { + "name": "plugins/modules/aci_access_span_src_group.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "95956d8a6861e45fd56fc9e15bd78673ca0a4f225b131b5a60898841f4d94c8c", + "format": 1 + }, + { "name": "plugins/modules/aci_cloud_external_epg_selector.py", "ftype": "file", "chksum_type": "sha256", @@ -1173,7 +1362,7 @@ "name": "plugins/modules/aci_dhcp_relay.py", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "7d09a7b86750651e3913f5d027aff2fce42124335054200ae0cf0b6a29185735", + "chksum_sha256": "eb3bd3dc51d09790c499266a64790bfc40b6c0c87a9d4d9849f73aead09f6e96", "format": 1 }, { @@ -1194,14 +1383,14 @@ "name": "plugins/modules/aci_interface_policy_port_channel.py", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "aa7216407b9bf4cd818fa59948a791fb56fdb0c4a13835af96638fdf53df86a2", + "chksum_sha256": "2c45394073761ae31b610adf9da1c0fd34416b0a6ad69e99b474d89781c2f23c", "format": 1 }, { "name": "plugins/modules/aci_access_span_filter_group.py", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "e04d64a39914eea55f3450d48b117692a333f2ad9759dd4fb2b45997d9c339b7", + "chksum_sha256": "91fe6540051b326d667c5b5ec2b67daff4d6ec7c0507c2452cda18d3659b40b5", "format": 1 }, { @@ -1233,6 +1422,20 @@ "format": 1 }, { + "name": "plugins/modules/aci_fabric_node_control.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "c68d16143e301d6d506524a7dda6e9a3ac47796737e155a6b3672bde08efe805", + "format": 1 + }, + { + "name": "plugins/modules/aci_interface_policy_spine_policy_group.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "e2271925e9c7269fe4de4cb390337f8670d53e0f30b6feaa5417e6fa38cbf428", + "format": 1 + }, + { "name": "plugins/modules/aci_l2out_logical_node_profile.py", "ftype": "file", "chksum_type": "sha256", @@ -1254,10 +1457,17 @@ "format": 1 }, { + "name": "plugins/modules/aci_access_span_src_group_src_path.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "4adf5b343bc0b07287358ea924891e4af638ffca6b0403e75a95fb0207f8ac5e", + "format": 1 + }, + { "name": "plugins/modules/aci_dhcp_relay_provider.py", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "e5b4ac7e5f2313664a27eb8dc1cf3109e6f938aa07ccb28e4a120515195f06a7", + "chksum_sha256": "c656312b9b7372480f95e1b2b1ee2d1c06981a91847415b602de675241f38783", "format": 1 }, { @@ -1268,6 +1478,13 @@ "format": 1 }, { + "name": "plugins/modules/aci_vrf_leak_internal_subnet.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "327fce7b845f9d1efe6c498e097c7b17ead6be139e7793a17dfe31f6a5853e97", + "format": 1 + }, + { "name": "plugins/modules/aci_esg_ip_subnet_selector.py", "ftype": "file", "chksum_type": "sha256", @@ -1278,7 +1495,7 @@ "name": "plugins/modules/aci_syslog_source.py", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "44c8e2138741c88892b7064d4e80a7904ad9c115be42f54d494f45992b0f869a", + "chksum_sha256": "33378f1e3da1418b7b7bf2bf3c2b56e22d6017489b9cbb8480e160f7658b1638", "format": 1 }, { @@ -1310,6 +1527,20 @@ "format": 1 }, { + "name": "plugins/modules/aci_system_global_aes_passphrase_encryption.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "bfe575c169928b6d9e68831fe660e3f6f00d7dbb23210103302ebd0e5520f793", + "format": 1 + }, + { + "name": "plugins/modules/aci_match_rule.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "128637fe8018eb9a70c023ddb2e44713b01eabe3a2e80f0f29866f07d79eb7bd", + "format": 1 + }, + { "name": "plugins/modules/aci_l2out_logical_interface_path.py", "ftype": "file", "chksum_type": "sha256", @@ -1625,6 +1856,34 @@ "format": 1 }, { + "name": "tests/integration/targets/aci_maintenance_group", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/aci_maintenance_group/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/aci_maintenance_group/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "b844b65db6cdf1934a6533e89a7591f20bc6dd2304acfe5706fe7dd1e1fb7426", + "format": 1 + }, + { + "name": "tests/integration/targets/aci_maintenance_group/aliases", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "616f27a9a7bb49e561ef3782330e18d1a8cea3840e3166e798796684dd546a69", + "format": 1 + }, + { "name": "tests/integration/targets/aci_tenant_span_src_group_src", "ftype": "dir", "chksum_type": null, @@ -1653,6 +1912,34 @@ "format": 1 }, { + "name": "tests/integration/targets/aci_interface_policy_leaf_fc_policy_group", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/aci_interface_policy_leaf_fc_policy_group/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/aci_interface_policy_leaf_fc_policy_group/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "fe1b3e59b230ed7f25bdf7c813f197dfa66e1ed964995b17dff7768416ddbe45", + "format": 1 + }, + { + "name": "tests/integration/targets/aci_interface_policy_leaf_fc_policy_group/aliases", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "616f27a9a7bb49e561ef3782330e18d1a8cea3840e3166e798796684dd546a69", + "format": 1 + }, + { "name": "tests/integration/targets/aci_firmware_source", "ftype": "dir", "chksum_type": null, @@ -1754,7 +2041,7 @@ "name": "tests/integration/targets/aci_filter_entry/tasks/main.yml", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "ad56f28789bc4b8a20ab2fef64747c40caa7ed00cec076afe826f24c0c0c2a02", + "chksum_sha256": "e61460bafe4da68c7fde6ba4f048add6429fe9019de9ed587a21cf24c5f1272f", "format": 1 }, { @@ -1782,7 +2069,7 @@ "name": "tests/integration/targets/aci_interface_config/tasks/main.yml", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "176eed25698191fc4f4aba70f475de372e7461a9d83917bcf8f9f078f55d5de2", + "chksum_sha256": "949614da80caa4cd9533e7063e8a4d6dfc08e3ae6da70672209b7df2d1b60346", "format": 1 }, { @@ -1810,7 +2097,7 @@ "name": "tests/integration/targets/aci_vlan_pool_encap_block/tasks/main.yml", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "f5286d672241e4ac9e659a27d40e64e11c761053e4cd572e13cefd9e960ca202", + "chksum_sha256": "c27de4117ad90e8466f98e511ff516843648ee8da6fe25645a9f8ae87b25768d", "format": 1 }, { @@ -1996,6 +2283,62 @@ "format": 1 }, { + "name": "tests/integration/targets/aci_interface_policy_mcp", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/aci_interface_policy_mcp/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/aci_interface_policy_mcp/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "deef48fee05f5fb8bc48390e26b4cff4871bcdcfb06d94fba77a01bcdae94bb6", + "format": 1 + }, + { + "name": "tests/integration/targets/aci_interface_policy_mcp/aliases", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "616f27a9a7bb49e561ef3782330e18d1a8cea3840e3166e798796684dd546a69", + "format": 1 + }, + { + "name": "tests/integration/targets/aci_match_rule", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/aci_match_rule/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/aci_match_rule/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "e5084a752a4704a990bb9d8ca629d47d0447209e5cbc8dac92439a9048f96758", + "format": 1 + }, + { + "name": "tests/integration/targets/aci_match_rule/aliases", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "616f27a9a7bb49e561ef3782330e18d1a8cea3840e3166e798796684dd546a69", + "format": 1 + }, + { "name": "tests/integration/targets/aci_ntp_server", "ftype": "dir", "chksum_type": null, @@ -2125,7 +2468,7 @@ "name": "tests/integration/targets/aci_dns_provider/tasks/main.yml", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "86793ae99ef2e4b0251aac7333f09e0a723cc10afebab10ad00cdee58bddae03", + "chksum_sha256": "8deb0048459357964c22ea4ceeddc9581317b58e9bb61d833430069f67d63368", "format": 1 }, { @@ -2136,6 +2479,34 @@ "format": 1 }, { + "name": "tests/integration/targets/aci_fabric_wide_settings", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/aci_fabric_wide_settings/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/aci_fabric_wide_settings/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "148d337c86ab59cfab9434d4d23959de9a7fc4f2cac867d7af9f9e4e52d02e9b", + "format": 1 + }, + { + "name": "tests/integration/targets/aci_fabric_wide_settings/aliases", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "616f27a9a7bb49e561ef3782330e18d1a8cea3840e3166e798796684dd546a69", + "format": 1 + }, + { "name": "tests/integration/targets/aci_esg_contract_master", "ftype": "dir", "chksum_type": null, @@ -2220,6 +2591,34 @@ "format": 1 }, { + "name": "tests/integration/targets/aci_interface_policy_fc", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/aci_interface_policy_fc/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/aci_interface_policy_fc/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "b2d68e0c3e83127354a824682dc77c62a7a8f7aff69dae9f125c6b068265358f", + "format": 1 + }, + { + "name": "tests/integration/targets/aci_interface_policy_fc/aliases", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "616f27a9a7bb49e561ef3782330e18d1a8cea3840e3166e798796684dd546a69", + "format": 1 + }, + { "name": "tests/integration/targets/aci_bd", "ftype": "dir", "chksum_type": null, @@ -2321,7 +2720,7 @@ "name": "tests/integration/targets/aci_config_snapshot/tasks/main.yml", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "94eb9ae9cada7c47f86dfc6a051f34a297d1b9594286b10f34797db1b3302c40", + "chksum_sha256": "0a799646b1dbd9af66d8df8679820cf8605a7cb64de234e8c88d3d4eaf7f0502", "format": 1 }, { @@ -2332,6 +2731,55 @@ "format": 1 }, { + "name": "tests/integration/targets/aci_config_snapshot/pki", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/aci_config_snapshot/pki/admin.crt", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "f97928b2cd1ce43cfe9ae9bbf47a40b858a749c4c3f25a7ec02898611f4fee90", + "format": 1 + }, + { + "name": "tests/integration/targets/aci_config_snapshot/pki/admin.key", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "947c1b5a6c419641d4b2c8492b91f2d71588958666aeceeffb1f878460b1d256", + "format": 1 + }, + { + "name": "tests/integration/targets/aci_access_span_src_group", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/aci_access_span_src_group/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/aci_access_span_src_group/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "bb9a24d78c38d7e0bff38cdcf51520f0deb18fa0679bc58856121caa71564413", + "format": 1 + }, + { + "name": "tests/integration/targets/aci_access_span_src_group/aliases", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "616f27a9a7bb49e561ef3782330e18d1a8cea3840e3166e798796684dd546a69", + "format": 1 + }, + { "name": "tests/integration/targets/aci_interface_policy_leaf_breakout_port_group", "ftype": "dir", "chksum_type": null, @@ -2416,6 +2864,118 @@ "format": 1 }, { + "name": "tests/integration/targets/aci_interface_policy_lldp", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/aci_interface_policy_lldp/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/aci_interface_policy_lldp/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "3c2094fbab84d87ce85c8e69e2b7c5b37f906d95eb36578b4c39ade77eb46e35", + "format": 1 + }, + { + "name": "tests/integration/targets/aci_interface_policy_lldp/aliases", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "616f27a9a7bb49e561ef3782330e18d1a8cea3840e3166e798796684dd546a69", + "format": 1 + }, + { + "name": "tests/integration/targets/aci_access_span_src_group_src_path", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/aci_access_span_src_group_src_path/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/aci_access_span_src_group_src_path/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "a01cd09447143359ef684d14444f762e33d2c56d9afb6cad3263989678be66bd", + "format": 1 + }, + { + "name": "tests/integration/targets/aci_access_span_src_group_src_path/aliases", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "616f27a9a7bb49e561ef3782330e18d1a8cea3840e3166e798796684dd546a69", + "format": 1 + }, + { + "name": "tests/integration/targets/aci_interface_policy_l2", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/aci_interface_policy_l2/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/aci_interface_policy_l2/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "9ca36be150a08cbb8940d974c1a6ee059739fd0728b539ffcee805426964bfe8", + "format": 1 + }, + { + "name": "tests/integration/targets/aci_interface_policy_l2/aliases", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "616f27a9a7bb49e561ef3782330e18d1a8cea3840e3166e798796684dd546a69", + "format": 1 + }, + { + "name": "tests/integration/targets/aci_l3out_route_tag_policy", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/aci_l3out_route_tag_policy/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/aci_l3out_route_tag_policy/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "2c1e76ca9add4ba716db7e406f1d4cfca1487881d5264dbc4a0057c9a3a26502", + "format": 1 + }, + { + "name": "tests/integration/targets/aci_l3out_route_tag_policy/aliases", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "616f27a9a7bb49e561ef3782330e18d1a8cea3840e3166e798796684dd546a69", + "format": 1 + }, + { "name": "tests/integration/targets/aci_esg_ip_subnet_selector", "ftype": "dir", "chksum_type": null, @@ -2444,6 +3004,62 @@ "format": 1 }, { + "name": "tests/integration/targets/aci_fabric_span_src_group_src_node", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/aci_fabric_span_src_group_src_node/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/aci_fabric_span_src_group_src_node/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "8ef543f2ea69f83824459c15f967cee7dc2d8b553a613d42ba58c794cbc71f9c", + "format": 1 + }, + { + "name": "tests/integration/targets/aci_fabric_span_src_group_src_node/aliases", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "616f27a9a7bb49e561ef3782330e18d1a8cea3840e3166e798796684dd546a69", + "format": 1 + }, + { + "name": "tests/integration/targets/aci_fabric_pod_selector", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/aci_fabric_pod_selector/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/aci_fabric_pod_selector/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "142a54f5ea8d61ca6a19bd8caf6a851799de963b6d41d427acb6205bf00fe0ab", + "format": 1 + }, + { + "name": "tests/integration/targets/aci_fabric_pod_selector/aliases", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "616f27a9a7bb49e561ef3782330e18d1a8cea3840e3166e798796684dd546a69", + "format": 1 + }, + { "name": "tests/integration/targets/aci_bd_subnet", "ftype": "dir", "chksum_type": null, @@ -2461,7 +3077,7 @@ "name": "tests/integration/targets/aci_bd_subnet/tasks/main.yml", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "c1c0aa678592c2d11cd38d071fe1fac13af1067a4d27a518bd0d37037c3aa641", + "chksum_sha256": "860384656ad549aa29df90bc8684d04ba250a9bdcbd50625c4db12a8da6c5b7a", "format": 1 }, { @@ -2472,6 +3088,34 @@ "format": 1 }, { + "name": "tests/integration/targets/aci_access_span_src_group_src", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/aci_access_span_src_group_src/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/aci_access_span_src_group_src/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "9dc18d120ffb94f5d068e0d153497a8cf5be32366a8b698b62f0d59bfa40fa6c", + "format": 1 + }, + { + "name": "tests/integration/targets/aci_access_span_src_group_src/aliases", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "616f27a9a7bb49e561ef3782330e18d1a8cea3840e3166e798796684dd546a69", + "format": 1 + }, + { "name": "tests/integration/targets/aci_aaa_ssh_auth", "ftype": "dir", "chksum_type": null, @@ -2514,6 +3158,34 @@ "format": 1 }, { + "name": "tests/integration/targets/aci_system_banner", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/aci_system_banner/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/aci_system_banner/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "6055791dda8f2b3c4fa18573d3b704229cda2a6b817f3b8984fd46475d97652f", + "format": 1 + }, + { + "name": "tests/integration/targets/aci_system_banner/aliases", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "616f27a9a7bb49e561ef3782330e18d1a8cea3840e3166e798796684dd546a69", + "format": 1 + }, + { "name": "tests/integration/targets/aci_interface_blacklist", "ftype": "dir", "chksum_type": null, @@ -2531,7 +3203,7 @@ "name": "tests/integration/targets/aci_interface_blacklist/tasks/main.yml", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "7b4b0c6019e6e3e5d02f3be381902eb6517eecf11da092731413d9ccaa2ef8e0", + "chksum_sha256": "4d3c6bde54b9a5c3b7c01058b41a3cb7e055425062699463733b0d2bbd0efca4", "format": 1 }, { @@ -2587,7 +3259,7 @@ "name": "tests/integration/targets/aci_syslog_remote_dest/tasks/main.yml", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "ebe110b063722cb3cd8262d1ff0af7b5c820a4b5d5f17d602320f40df7a9b6bc", + "chksum_sha256": "8cdde3499fe651dad5bd9afc73a318d9b2ccbe1ba1bed3d7e3e2829268484373", "format": 1 }, { @@ -2738,6 +3410,62 @@ "format": 1 }, { + "name": "tests/integration/targets/aci_l3out", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/aci_l3out/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/aci_l3out/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "c085a58785b7e4539654e0254479dd8585f5ed9d94005772e2cff7b02db13e50", + "format": 1 + }, + { + "name": "tests/integration/targets/aci_l3out/aliases", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "616f27a9a7bb49e561ef3782330e18d1a8cea3840e3166e798796684dd546a69", + "format": 1 + }, + { + "name": "tests/integration/targets/aci_epg_monitoring_policy", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/aci_epg_monitoring_policy/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/aci_epg_monitoring_policy/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "cd7cb391b571c4b8294030393cd288334b14eee8c1f88bf678776f8cc60e7440", + "format": 1 + }, + { + "name": "tests/integration/targets/aci_epg_monitoring_policy/aliases", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "616f27a9a7bb49e561ef3782330e18d1a8cea3840e3166e798796684dd546a69", + "format": 1 + }, + { "name": "tests/integration/targets/aci_interface_policy_leaf_policy_group", "ftype": "dir", "chksum_type": null, @@ -2755,7 +3483,7 @@ "name": "tests/integration/targets/aci_interface_policy_leaf_policy_group/tasks/main.yml", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "04a50ad6df8089fbb2bc7dff3d956735b611b92d9c518c57b489487b07cd8ed3", + "chksum_sha256": "8821f0cf43e9a9d1f5d8965fc4b38f78a75d62d1b599a74a4dff8546e9866919", "format": 1 }, { @@ -2766,6 +3494,34 @@ "format": 1 }, { + "name": "tests/integration/targets/aci_match_route_destination", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/aci_match_route_destination/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/aci_match_route_destination/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "85f354ddd29b16e87aaa87111e966a120a6c6003d9a20426900be897c2920a87", + "format": 1 + }, + { + "name": "tests/integration/targets/aci_match_route_destination/aliases", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "616f27a9a7bb49e561ef3782330e18d1a8cea3840e3166e798796684dd546a69", + "format": 1 + }, + { "name": "tests/integration/targets/aci_lookup_interface_range", "ftype": "dir", "chksum_type": null, @@ -2822,6 +3578,62 @@ "format": 1 }, { + "name": "tests/integration/targets/aci_match_community_factor", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/aci_match_community_factor/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/aci_match_community_factor/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "85b19ffcc7f1722664426544bc85648f722f8cda85876e702b651174a6d6c251", + "format": 1 + }, + { + "name": "tests/integration/targets/aci_match_community_factor/aliases", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "616f27a9a7bb49e561ef3782330e18d1a8cea3840e3166e798796684dd546a69", + "format": 1 + }, + { + "name": "tests/integration/targets/aci_interface_policy_spine_policy_group", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/aci_interface_policy_spine_policy_group/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/aci_interface_policy_spine_policy_group/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "70e00aa9a7355ec778b136a583b71ac3211f5cd0a37e4574b5b9f421f99ab6ea", + "format": 1 + }, + { + "name": "tests/integration/targets/aci_interface_policy_spine_policy_group/aliases", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "616f27a9a7bb49e561ef3782330e18d1a8cea3840e3166e798796684dd546a69", + "format": 1 + }, + { "name": "tests/integration/targets/aci_contract_subject_to_service_graph", "ftype": "dir", "chksum_type": null, @@ -2951,7 +3763,7 @@ "name": "tests/integration/targets/aci_domain_to_vlan_pool/tasks/main.yml", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "bdff5f7ba9d35aaad6093532776d8c6b579446a9af60e64c79a98e4dd2b3dadb", + "chksum_sha256": "7f14fc189439710b1e57a89af9ba53d34850b8b289c59207b9239d73cd0edabd", "format": 1 }, { @@ -3007,7 +3819,7 @@ "name": "tests/integration/targets/aci_dhcp_relay_provider/tasks/main.yml", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "3f1762d885c7a01fdc314b3ec626d0ff62514f45848675237cc6486ef209e977", + "chksum_sha256": "3bcac5486e000d2c50ae9d748e6ac6870227c67671d6d43603f272f198adb317", "format": 1 }, { @@ -3018,6 +3830,34 @@ "format": 1 }, { + "name": "tests/integration/targets/aci_fabric_node_control", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/aci_fabric_node_control/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/aci_fabric_node_control/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "be5b110f64ea6906839446cb0d79b30222fda3a9f4afb9da7cbb10bc75944898", + "format": 1 + }, + { + "name": "tests/integration/targets/aci_fabric_node_control/aliases", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "616f27a9a7bb49e561ef3782330e18d1a8cea3840e3166e798796684dd546a69", + "format": 1 + }, + { "name": "tests/integration/targets/aci_interface_policy_cdp", "ftype": "dir", "chksum_type": null, @@ -3088,10 +3928,31 @@ "format": 1 }, { + "name": "tests/integration/targets/aci_tenant/tasks/httpapi_connection.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "af2e683ad88998f1ff8062ded1c66873ce24f38165afca72d36356e435e295b5", + "format": 1 + }, + { "name": "tests/integration/targets/aci_tenant/tasks/main.yml", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "89a65f3170147e81db5173b982d3085b9de3d640a8a8da05418b4b7f922005d2", + "chksum_sha256": "6a0620f26d77dee85bcf88b1bdc61aaadc57fd04daafaa6bd91cc8a0898ac507", + "format": 1 + }, + { + "name": "tests/integration/targets/aci_tenant/filter_plugins", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/aci_tenant/filter_plugins/generate_ips.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "334529a31b2760cdccb063474365dcb062a0e7c7f5a643f50a313100ada78a78", "format": 1 }, { @@ -3102,6 +3963,90 @@ "format": 1 }, { + "name": "tests/integration/targets/aci_tenant/pki", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/aci_tenant/pki/admin_invalid.key", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "55fb85eae133a7b8649703b1d6e303cdf5b585d381e761f3674d1fcc5beb40cb", + "format": 1 + }, + { + "name": "tests/integration/targets/aci_tenant/pki/rsa_user.key", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "8a74dffaf7d04e74642988302b185f0e9eda2dd35a5b7f32317696b12a9790c1", + "format": 1 + }, + { + "name": "tests/integration/targets/aci_tenant/pki/admin.crt", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "f97928b2cd1ce43cfe9ae9bbf47a40b858a749c4c3f25a7ec02898611f4fee90", + "format": 1 + }, + { + "name": "tests/integration/targets/aci_tenant/pki/openssh_rsa.key", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "81a6ac3a61f2df64339252db67fea1307e891c41232e2da53a1e5219f677c917", + "format": 1 + }, + { + "name": "tests/integration/targets/aci_tenant/pki/admin.key", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "947c1b5a6c419641d4b2c8492b91f2d71588958666aeceeffb1f878460b1d256", + "format": 1 + }, + { + "name": "tests/integration/targets/aci_tenant/pki/rsa_user.crt", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "77629e6b31c8c9fad7052f4696dcead786fecef66e33e81c50ce09fcb39d8b4a", + "format": 1 + }, + { + "name": "tests/integration/targets/aci_tenant/pki/rsa_ansible.key", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "6945fcb844179106a63be7e8ea5b407f31a8a1e95a1eaa0b5e67da1c26802032", + "format": 1 + }, + { + "name": "tests/integration/targets/aci_maintenance_group_node", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/aci_maintenance_group_node/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/aci_maintenance_group_node/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "b342a36b963e0b3001e11800be162c1faf958dc1bdfa552f32b928c516a532ed", + "format": 1 + }, + { + "name": "tests/integration/targets/aci_maintenance_group_node/aliases", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "616f27a9a7bb49e561ef3782330e18d1a8cea3840e3166e798796684dd546a69", + "format": 1 + }, + { "name": "tests/integration/targets/aci_syslog_group", "ftype": "dir", "chksum_type": null, @@ -3130,6 +4075,34 @@ "format": 1 }, { + "name": "tests/integration/targets/aci_route_control_profile", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/aci_route_control_profile/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/aci_route_control_profile/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "f5c0118b53e4cab847d81acd60edfba3e5f58a263aa32f980ccce6c9240a2f09", + "format": 1 + }, + { + "name": "tests/integration/targets/aci_route_control_profile/aliases", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "616f27a9a7bb49e561ef3782330e18d1a8cea3840e3166e798796684dd546a69", + "format": 1 + }, + { "name": "tests/integration/targets/aci_snmp_client", "ftype": "dir", "chksum_type": null, @@ -3203,7 +4176,7 @@ "name": "tests/integration/targets/aci_l3out_logical_interface_profile/tasks/main.yml", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "c9b813f3dac9c4fd53bab159ca484839df353d3b65238d2b11d7777cdc3850f3", + "chksum_sha256": "5a87867498dd6a864410a35eb31e8d8db264675bf15a3935f583efa16f563b56", "format": 1 }, { @@ -3298,6 +4271,62 @@ "format": 1 }, { + "name": "tests/integration/targets/aci_interface_policy_port_channel", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/aci_interface_policy_port_channel/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/aci_interface_policy_port_channel/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "dd6ca5c9bc1c622ad186c3081434eb5d82f61d82e756e9a7731fbe39a85a1c26", + "format": 1 + }, + { + "name": "tests/integration/targets/aci_interface_policy_port_channel/aliases", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "616f27a9a7bb49e561ef3782330e18d1a8cea3840e3166e798796684dd546a69", + "format": 1 + }, + { + "name": "tests/integration/targets/aci_match_community_regex_term", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/aci_match_community_regex_term/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/aci_match_community_regex_term/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "a317a7798696e01a0bbcea910360a73b59f03c9fcf0946a8f0e1566daaa222cb", + "format": 1 + }, + { + "name": "tests/integration/targets/aci_match_community_regex_term/aliases", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "616f27a9a7bb49e561ef3782330e18d1a8cea3840e3166e798796684dd546a69", + "format": 1 + }, + { "name": "tests/integration/targets/aci_snmp_policy", "ftype": "dir", "chksum_type": null, @@ -3501,6 +4530,34 @@ "format": 1 }, { + "name": "tests/integration/targets/aci_l3out_extepg", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/aci_l3out_extepg/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/aci_l3out_extepg/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "e3cc46789ecac2e9636478d437fac86b9006cc9f2ed504961fe2f0e110e1c69d", + "format": 1 + }, + { + "name": "tests/integration/targets/aci_l3out_extepg/aliases", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "616f27a9a7bb49e561ef3782330e18d1a8cea3840e3166e798796684dd546a69", + "format": 1 + }, + { "name": "tests/integration/targets/aci_aaa_custom_privilege", "ftype": "dir", "chksum_type": null, @@ -3529,6 +4586,90 @@ "format": 1 }, { + "name": "tests/integration/targets/aci_fabric_scheduler", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/aci_fabric_scheduler/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/aci_fabric_scheduler/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "704b7c6e84833030f155e856359f86d7166319df9f4595a463c2662ad048218d", + "format": 1 + }, + { + "name": "tests/integration/targets/aci_fabric_scheduler/aliases", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "616f27a9a7bb49e561ef3782330e18d1a8cea3840e3166e798796684dd546a69", + "format": 1 + }, + { + "name": "tests/integration/targets/aci_fabric_span_src_group", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/aci_fabric_span_src_group/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/aci_fabric_span_src_group/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "8b252a0ada511e83d47a0b183a5aa1bb06a30296081f84cb99af94e26077d70e", + "format": 1 + }, + { + "name": "tests/integration/targets/aci_fabric_span_src_group/aliases", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "616f27a9a7bb49e561ef3782330e18d1a8cea3840e3166e798796684dd546a69", + "format": 1 + }, + { + "name": "tests/integration/targets/aci_firmware_group", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/aci_firmware_group/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/aci_firmware_group/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "f2fd0c9c021e33ae4d4fec5ce9ffce4128b16c2d8ca1b67595954a50f59694db", + "format": 1 + }, + { + "name": "tests/integration/targets/aci_firmware_group/aliases", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "616f27a9a7bb49e561ef3782330e18d1a8cea3840e3166e798796684dd546a69", + "format": 1 + }, + { "name": "tests/integration/targets/aci_epg_to_contract", "ftype": "dir", "chksum_type": null, @@ -3585,6 +4726,90 @@ "format": 1 }, { + "name": "tests/integration/targets/aci_bd_to_l3out", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/aci_bd_to_l3out/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/aci_bd_to_l3out/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "ae0c2f1524cebfd9d304242f67006adfcf029fe89ba8376ad1f27e53305d37de", + "format": 1 + }, + { + "name": "tests/integration/targets/aci_bd_to_l3out/aliases", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "616f27a9a7bb49e561ef3782330e18d1a8cea3840e3166e798796684dd546a69", + "format": 1 + }, + { + "name": "tests/integration/targets/aci_fabric_span_dst_group", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/aci_fabric_span_dst_group/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/aci_fabric_span_dst_group/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "89f09f54b34b4070853e81358c3bfc5afcac772fec7c242cb6f4712960118340", + "format": 1 + }, + { + "name": "tests/integration/targets/aci_fabric_span_dst_group/aliases", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "616f27a9a7bb49e561ef3782330e18d1a8cea3840e3166e798796684dd546a69", + "format": 1 + }, + { + "name": "tests/integration/targets/aci_bgp_best_path_policy", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/aci_bgp_best_path_policy/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/aci_bgp_best_path_policy/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "bd01f327bd89e5f470bd0b9fc80e2a0246c42a9fd91de72285f6bca6aee14b85", + "format": 1 + }, + { + "name": "tests/integration/targets/aci_bgp_best_path_policy/aliases", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "616f27a9a7bb49e561ef3782330e18d1a8cea3840e3166e798796684dd546a69", + "format": 1 + }, + { "name": "tests/integration/targets/aci_fabric_node", "ftype": "dir", "chksum_type": null, @@ -3602,7 +4827,7 @@ "name": "tests/integration/targets/aci_fabric_node/tasks/main.yml", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "8415e4f506dc94ddd4659c35437bb044783a9a022df9b6e292d28c6686f14c99", + "chksum_sha256": "11155626d26d8df736b01f6b694e7724cfb3edf8a1690474e77deda7bdbc0c1f", "format": 1 }, { @@ -3630,7 +4855,7 @@ "name": "tests/integration/targets/aci_aep_to_domain/tasks/main.yml", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "86ddd1977329ee6442404a15f6ca0a06f6947faddabbd3b3515ddc8993d1a65a", + "chksum_sha256": "52a218b485aa23a4981d67fbbf2c7e7dd9c344f16e5fc19904f839f1f2d22dc2", "format": 1 }, { @@ -3658,7 +4883,7 @@ "name": "tests/integration/targets/aci_dns_domain/tasks/main.yml", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "8354eb80cbb4ad430e3f129144878cd82ffd3aad0eb8e4d974a78f0a34b1ee45", + "chksum_sha256": "5942d788bbbef1eacb07ff15c38c50de8c3f2f7dc6e80edd5efebcc193c1e1c4", "format": 1 }, { @@ -3686,7 +4911,7 @@ "name": "tests/integration/targets/aci_encap_pool_range/tasks/vsan.yml", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "b465efdc08e1e1a1a14290d23b5577dc5715fde6e1cba402d0312b3ef61f8a17", + "chksum_sha256": "33a77d78e48ca5615f806248beefe920ac99a7c43ed1f19e96c0660381a23b9d", "format": 1 }, { @@ -3700,7 +4925,7 @@ "name": "tests/integration/targets/aci_encap_pool_range/tasks/vxlan.yml", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "9a6dfeec5aa6516e733189aa6d28b0e6ef8876f23c173fb75079a2b7d6466f8c", + "chksum_sha256": "13773b8b2e1ae5d22eb4c2919c3b14bc2192b0876e4bc2dca286b40b8486b88a", "format": 1 }, { @@ -3774,6 +4999,34 @@ "format": 1 }, { + "name": "tests/integration/targets/aci_tenant_ep_retention_policy", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/aci_tenant_ep_retention_policy/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/aci_tenant_ep_retention_policy/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "843240370cdf77e385f381e5046df7655f88a4eb9843237c37b1d2b7339fd25e", + "format": 1 + }, + { + "name": "tests/integration/targets/aci_tenant_ep_retention_policy/aliases", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "616f27a9a7bb49e561ef3782330e18d1a8cea3840e3166e798796684dd546a69", + "format": 1 + }, + { "name": "tests/integration/targets/aci_interface_policy_ospf", "ftype": "dir", "chksum_type": null, @@ -3791,7 +5044,7 @@ "name": "tests/integration/targets/aci_interface_policy_ospf/tasks/main.yml", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "e3707a170b25eb07c59cc65e75290dd434d3dea58f8d8b07034e03f0cd98cb0c", + "chksum_sha256": "9b9ba9779d76f78bb5e11c9486b65c3153fecae854844ff74734ffff8233002b", "format": 1 }, { @@ -3875,7 +5128,7 @@ "name": "tests/integration/targets/aci_dhcp_relay/tasks/main.yml", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "af0ca04282d5a1a8a535c0c05b940c1255814b30e8cbecd9a648462878894aff", + "chksum_sha256": "773df8a3cfbfbaf68a2c5de6619e9bf1ce6026d7f45b2e45c0025c4dd9ad6d1f", "format": 1 }, { @@ -3959,7 +5212,7 @@ "name": "tests/integration/targets/aci_l3out_bgp_peer/tasks/main.yml", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "e49c69d0423405d221357cc36ddc6d0a69b53a3321354a27ea6f879b926526e7", + "chksum_sha256": "b58713b299f63cbc303b82df604713e8c1cf2702ed61b5b178461fad4c36e190", "format": 1 }, { @@ -4054,6 +5307,48 @@ "format": 1 }, { + "name": "tests/integration/targets/aci_file_remote_path", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/aci_file_remote_path/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/aci_file_remote_path/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "be86a485a2200db58a988ec0095acb483fbc9d7710ff026c96d9ef780cb2fc5b", + "format": 1 + }, + { + "name": "tests/integration/targets/aci_file_remote_path/aliases", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "616f27a9a7bb49e561ef3782330e18d1a8cea3840e3166e798796684dd546a69", + "format": 1 + }, + { + "name": "tests/integration/targets/aci_file_remote_path/pki", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/aci_file_remote_path/pki/rsa_ansible.key", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "6945fcb844179106a63be7e8ea5b407f31a8a1e95a1eaa0b5e67da1c26802032", + "format": 1 + }, + { "name": "tests/integration/targets/aci_aaa_user", "ftype": "dir", "chksum_type": null, @@ -4071,7 +5366,7 @@ "name": "tests/integration/targets/aci_aaa_user/tasks/main.yml", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "01f000fc46cb25fe69e31fb742d4f3be7c6e2513399bdb738c1268c44c276c04", + "chksum_sha256": "0a27716e570cda199712f2864aa28f6845cda283992a047be6e435d739c30ce0", "format": 1 }, { @@ -4138,6 +5433,34 @@ "format": 1 }, { + "name": "tests/integration/targets/aci_firmware_policy", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/aci_firmware_policy/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/aci_firmware_policy/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "cffeb0a042ff0bba5cceabcf4ddbf8a233ebba5620ae52ed5ea38bb392532cda", + "format": 1 + }, + { + "name": "tests/integration/targets/aci_firmware_policy/aliases", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "616f27a9a7bb49e561ef3782330e18d1a8cea3840e3166e798796684dd546a69", + "format": 1 + }, + { "name": "tests/integration/targets/aci_cloud_subnet", "ftype": "dir", "chksum_type": null, @@ -4183,7 +5506,7 @@ "name": "tests/integration/targets/aci_vrf/tasks/main.yml", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "ece41bf2c94bc130d531ed7053f7f7f8140ac9a279800ca2b4490265838cbc5b", + "chksum_sha256": "f8e835d03f2b31ef9aab370d7fca0b89b821069d50d7917aac1ab757d19acb09", "format": 1 }, { @@ -4267,7 +5590,7 @@ "name": "tests/integration/targets/aci_tenant_span_dst_group/tasks/main.yml", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "9ba7a3196711cf088e7350ed9f50870bb7000e5a4e0589fd1fe3fed7d41431b3", + "chksum_sha256": "ff4eb055593d4fff6ee7b8a530e750ec83e6bef96f7e30b01debdfc10a45a11b", "format": 1 }, { @@ -4362,6 +5685,62 @@ "format": 1 }, { + "name": "tests/integration/targets/aci_fabric_span_src_group_src_path", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/aci_fabric_span_src_group_src_path/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/aci_fabric_span_src_group_src_path/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "36d0fe1f44db748661297e8588d6311dded74f304e8c43e1575ae67e31309031", + "format": 1 + }, + { + "name": "tests/integration/targets/aci_fabric_span_src_group_src_path/aliases", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "616f27a9a7bb49e561ef3782330e18d1a8cea3840e3166e798796684dd546a69", + "format": 1 + }, + { + "name": "tests/integration/targets/aci_fabric_span_src_group_src", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/aci_fabric_span_src_group_src/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/aci_fabric_span_src_group_src/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "7376aad0adce8582bb048f45bb6999bef5e4c0bd92b879ca9818b3a34af60596", + "format": 1 + }, + { + "name": "tests/integration/targets/aci_fabric_span_src_group_src/aliases", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "616f27a9a7bb49e561ef3782330e18d1a8cea3840e3166e798796684dd546a69", + "format": 1 + }, + { "name": "tests/integration/targets/aci_tenant_span_src_group", "ftype": "dir", "chksum_type": null, @@ -4474,6 +5853,34 @@ "format": 1 }, { + "name": "tests/integration/targets/aci_system_endpoint_controls", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/aci_system_endpoint_controls/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/aci_system_endpoint_controls/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "d38cac5f1adb2065455a24ba14eeb63e9798c2d7beb71ec609b7aa2c24a57ed6", + "format": 1 + }, + { + "name": "tests/integration/targets/aci_system_endpoint_controls/aliases", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "616f27a9a7bb49e561ef3782330e18d1a8cea3840e3166e798796684dd546a69", + "format": 1 + }, + { "name": "tests/integration/targets/aci_l3out_interface_secondary_ip", "ftype": "dir", "chksum_type": null, @@ -4519,7 +5926,7 @@ "name": "tests/integration/targets/aci_static_binding_to_epg/tasks/main.yml", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "c9341e6fe8987fa97b30bdcb8785534aabc7b22be15b591b69431f22e2f90ccb", + "chksum_sha256": "3c12648b939c15bfbd8a35c6483db89011dbccd407961c4d41215ca270a71971", "format": 1 }, { @@ -4558,6 +5965,62 @@ "format": 1 }, { + "name": "tests/integration/targets/aci_firmware_group_node", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/aci_firmware_group_node/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/aci_firmware_group_node/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "c3e4f4978c8f104052bd17ebd014e836998899f4f807661656accefbf40e19fc", + "format": 1 + }, + { + "name": "tests/integration/targets/aci_firmware_group_node/aliases", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "616f27a9a7bb49e561ef3782330e18d1a8cea3840e3166e798796684dd546a69", + "format": 1 + }, + { + "name": "tests/integration/targets/aci_bgp_timers_policy", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/aci_bgp_timers_policy/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/aci_bgp_timers_policy/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "e1e28cfb914f228702667f03f8a265f9b869f13e6766c767a90e8edb0166762b", + "format": 1 + }, + { + "name": "tests/integration/targets/aci_bgp_timers_policy/aliases", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "616f27a9a7bb49e561ef3782330e18d1a8cea3840e3166e798796684dd546a69", + "format": 1 + }, + { "name": "tests/integration/targets/aci_access_span_filter_group", "ftype": "dir", "chksum_type": null, @@ -4614,6 +6077,34 @@ "format": 1 }, { + "name": "tests/integration/targets/aci_system_global_aes_passphrase_encryption", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/aci_system_global_aes_passphrase_encryption/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/aci_system_global_aes_passphrase_encryption/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "ae783a7828434afa0de8e616e3e9ecfe84317763a28203beeba2aff1b63bcd55", + "format": 1 + }, + { + "name": "tests/integration/targets/aci_system_global_aes_passphrase_encryption/aliases", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "616f27a9a7bb49e561ef3782330e18d1a8cea3840e3166e798796684dd546a69", + "format": 1 + }, + { "name": "tests/integration/targets/aci_aaa_user_certificate", "ftype": "dir", "chksum_type": null, @@ -4631,7 +6122,7 @@ "name": "tests/integration/targets/aci_aaa_user_certificate/tasks/main.yml", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "59be2863e2b08a1917d279af9a2108db0601fa0ab671ca855f6c85b02931a9c2", + "chksum_sha256": "de23c78bd0da077a2e5259699d1c861ca6c9ed0b161398c068c74fc95e31d59c", "format": 1 }, { @@ -4726,6 +6217,34 @@ "format": 1 }, { + "name": "tests/integration/targets/aci_tenant_action_rule_profile", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/aci_tenant_action_rule_profile/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/aci_tenant_action_rule_profile/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "8a81f5e051113d70e75f90145d1d81cd6444922e3d38b0131d1e6601f7816dbd", + "format": 1 + }, + { + "name": "tests/integration/targets/aci_tenant_action_rule_profile/aliases", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "616f27a9a7bb49e561ef3782330e18d1a8cea3840e3166e798796684dd546a69", + "format": 1 + }, + { "name": "tests/integration/targets/aci_aaa_user_role", "ftype": "dir", "chksum_type": null, @@ -4995,7 +6514,7 @@ "name": "tests/integration/targets/aci_l3out_logical_node/tasks/main.yml", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "31e8b509fbce92cdc6941ac653b6f1a32e027e49472949679025ddde483fe196", + "chksum_sha256": "4607bdbef2f2b481fe393e457c18ba02c78af0b6d16f1d53c84e69c0057d30d0", "format": 1 }, { @@ -5034,6 +6553,34 @@ "format": 1 }, { + "name": "tests/integration/targets/aci_route_control_context", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/aci_route_control_context/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/aci_route_control_context/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "54fcba37e0a385af9767ffaa2740fd81458fc3cee008e017ec702f8162f18e70", + "format": 1 + }, + { + "name": "tests/integration/targets/aci_route_control_context/aliases", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "616f27a9a7bb49e561ef3782330e18d1a8cea3840e3166e798796684dd546a69", + "format": 1 + }, + { "name": "tests/integration/targets/aci_switch_policy_leaf_profile", "ftype": "dir", "chksum_type": null, @@ -5107,7 +6654,7 @@ "name": "tests/integration/targets/aci_syslog_source/tasks/main.yml", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "863d15e411fb38d1f1383ce79143cd8b4060acbd192dbf8cba504c97904d2f32", + "chksum_sha256": "201852b3cc6e35b2452031b52492754e0824fc6b6beefbf1afa676b7a4c4edb9", "format": 1 }, { @@ -5170,7 +6717,7 @@ "name": "tests/integration/targets/aci_snmp_user/tasks/main.yml", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "1543375087d5cba81c022f7c2173582882111cbfebcc2b672276312e400b5c3e", + "chksum_sha256": "efb1388a0c49eb283d44aff2448b64c978ee381bd5c3d47db73b9b1acedc45fb", "format": 1 }, { @@ -5310,7 +6857,7 @@ "name": "tests/integration/targets/aci_config_rollback/tasks/main.yml", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "95f056dc7344f0b394532ff3fa8a1844a0bbdaa980f4e2baf3061a93bd1d7399", + "chksum_sha256": "6d4937bb22c5f4a4a7fe5771ba24fabaa8f0961137cf6811726fb2e07f02f9f5", "format": 1 }, { @@ -5405,6 +6952,34 @@ "format": 1 }, { + "name": "tests/integration/targets/aci_epg_subnet", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/aci_epg_subnet/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/aci_epg_subnet/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "88b56f6acb0c44928cab5e4b92aaf89a13a592bc7d1e0b88f70515743e29e624", + "format": 1 + }, + { + "name": "tests/integration/targets/aci_epg_subnet/aliases", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "616f27a9a7bb49e561ef3782330e18d1a8cea3840e3166e798796684dd546a69", + "format": 1 + }, + { "name": "tests/integration/targets/aci_interface_policy_spanning_tree", "ftype": "dir", "chksum_type": null, @@ -5433,6 +7008,62 @@ "format": 1 }, { + "name": "tests/integration/targets/aci_fabric_interface_policy_group", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/aci_fabric_interface_policy_group/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/aci_fabric_interface_policy_group/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "827c31fe1653a6a1609f63e81ff2387defe400094845091b89f9b22ae5f536b8", + "format": 1 + }, + { + "name": "tests/integration/targets/aci_fabric_interface_policy_group/aliases", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "616f27a9a7bb49e561ef3782330e18d1a8cea3840e3166e798796684dd546a69", + "format": 1 + }, + { + "name": "tests/integration/targets/aci_match_as_path_regex_term", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/aci_match_as_path_regex_term/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/aci_match_as_path_regex_term/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "c80e4ffc685fcdc906015246d0d97a767f81e3e76531b08571ed9033f0791dbb", + "format": 1 + }, + { + "name": "tests/integration/targets/aci_match_as_path_regex_term/aliases", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "616f27a9a7bb49e561ef3782330e18d1a8cea3840e3166e798796684dd546a69", + "format": 1 + }, + { "name": "tests/integration/targets/aci_bd_dhcp_label", "ftype": "dir", "chksum_type": null, @@ -5489,6 +7120,34 @@ "format": 1 }, { + "name": "tests/integration/targets/aci_l3out_bgp_protocol_profile", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/aci_l3out_bgp_protocol_profile/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/aci_l3out_bgp_protocol_profile/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "4c9776bba48af35f22f3ac068ec7c2fa8ebd63127faababdbfff602bedca5452", + "format": 1 + }, + { + "name": "tests/integration/targets/aci_l3out_bgp_protocol_profile/aliases", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "616f27a9a7bb49e561ef3782330e18d1a8cea3840e3166e798796684dd546a69", + "format": 1 + }, + { "name": "tests/integration/targets/aci_l2out_logical_node_profile", "ftype": "dir", "chksum_type": null, @@ -5643,6 +7302,20 @@ "format": 1 }, { + "name": "tests/integration/targets/aci_rest/tasks/xml_files/fail_parse.json", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "63e65bd0869adcb4b5edb61e9621910488b2c4832a245ac4847329fb3ad0e5f5", + "format": 1 + }, + { + "name": "tests/integration/targets/aci_rest/tasks/xml_files/fail_parse.xml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "7dc96e9f62b6a31956edc1b5a6d2325aa83c6fcd4aeb9f02f4867962f850a0bf", + "format": 1 + }, + { "name": "tests/integration/targets/aci_rest/tasks/xml_files/tn-ans_test_delete.xml", "ftype": "file", "chksum_type": "sha256", @@ -5674,56 +7347,56 @@ "name": "tests/integration/targets/aci_rest/tasks/json_inline.yml", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "a1ff9d6891ccf01fa4dcb08f1de31fe4e484d7d56632dce362989c6b5f731887", + "chksum_sha256": "e927a5abc052195e3e728d73c5c66557b87ae3bbd976a8c8b7f6611b0c6c16d0", "format": 1 }, { "name": "tests/integration/targets/aci_rest/tasks/json_string.yml", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "e290b2979a1fac0d7765d29b517215b5590fd20288b91f7286d1f32aab350e1f", + "chksum_sha256": "f3da19544e0a9300231d8b96368c1eea2f0e818d0279cece4198e3ba5f89535e", "format": 1 }, { "name": "tests/integration/targets/aci_rest/tasks/yaml_inline.yml", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "56f653f63378ed596d11764df28f5703066d41d3b8829bc6efb91807a60f1c30", + "chksum_sha256": "5703d0de444ddbe609527833236735e85bd605f60e50b81ac7f003f53cc37f9c", "format": 1 }, { "name": "tests/integration/targets/aci_rest/tasks/xml_file.yml", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "ccd7491145430e6638632d14372a79a3c0374f89589ec631ee9a376749544ef3", + "chksum_sha256": "940d954f67332ff5ff2a853dc92968a7017fab3d4346b0c1703f38ba43993451", "format": 1 }, { "name": "tests/integration/targets/aci_rest/tasks/xml_string.yml", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "7c1720f00012ec0e72d8bda66f6407bc24b724d04baf77d0404cfe1b89af21ab", + "chksum_sha256": "0f0aa8fde34bbb16b8ddfc80dd8a80f596004a8f5b0242d5e0403ac820e1b3d4", "format": 1 }, { "name": "tests/integration/targets/aci_rest/tasks/yaml_string.yml", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "56f653f63378ed596d11764df28f5703066d41d3b8829bc6efb91807a60f1c30", + "chksum_sha256": "3616adeb0651848fca25ffd6a229d10f1093581059463d24c783aa9775380dd4", "format": 1 }, { "name": "tests/integration/targets/aci_rest/tasks/main.yml", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "3a962776a04142a44ffdfab3e3a7e895c16d4190de2b48e47210964b05bd0139", + "chksum_sha256": "83f24d55d45b7d9dabfb4f332f208eabee34d9dd97e56bcc555ad83bc1f81c5a", "format": 1 }, { "name": "tests/integration/targets/aci_rest/tasks/error_handling.yml", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "68bc253e5400720af3f581361cc45bb0ee0a760412f844a1f891142352f54e52", + "chksum_sha256": "4b5de110ac6eb32bf731bea15cc3c88b19571e0d39a1c9e85c9131c822bfa242", "format": 1 }, { @@ -5755,6 +7428,34 @@ "format": 1 }, { + "name": "tests/integration/targets/aci_match_community_term", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/aci_match_community_term/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/aci_match_community_term/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "d5be84110acb7260a79bb832aa116b5cd301a6455705e9091bc963ee35ba0fe7", + "format": 1 + }, + { + "name": "tests/integration/targets/aci_match_community_term/aliases", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "616f27a9a7bb49e561ef3782330e18d1a8cea3840e3166e798796684dd546a69", + "format": 1 + }, + { "name": "tests/integration/targets/aci_contract_subject_to_filter", "ftype": "dir", "chksum_type": null, @@ -5772,7 +7473,7 @@ "name": "tests/integration/targets/aci_contract_subject_to_filter/tasks/main.yml", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "1b4f768ff3e9d6a2b601bb20f1fc18db42ee337d377d62be490b615180540998", + "chksum_sha256": "32b77df5bb1d7d8a17718eee2daef2bc17af9e26c0cced27a70e5ea23ccf3e10", "format": 1 }, { @@ -5839,6 +7540,34 @@ "format": 1 }, { + "name": "tests/integration/targets/aci_vrf_leak_internal_subnet", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/aci_vrf_leak_internal_subnet/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/aci_vrf_leak_internal_subnet/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "a2109f9cc263e3a385afdc3517ddc75c744865636b0dff13915aeecff9e0adb0", + "format": 1 + }, + { + "name": "tests/integration/targets/aci_vrf_leak_internal_subnet/aliases", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "format": 1 + }, + { "name": "tests/integration/targets/aci_cloud_cidr", "ftype": "dir", "chksum_type": null, @@ -5867,6 +7596,34 @@ "format": 1 }, { + "name": "tests/integration/targets/aci_maintenance_policy", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/aci_maintenance_policy/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/aci_maintenance_policy/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "e19c89dc5c8b6d6d369ffd128946262764e40ba0a31d63cdd9ecb6a9ef2c8534", + "format": 1 + }, + { + "name": "tests/integration/targets/aci_maintenance_policy/aliases", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "616f27a9a7bb49e561ef3782330e18d1a8cea3840e3166e798796684dd546a69", + "format": 1 + }, + { "name": "tests/integration/network-integration.requirements.txt", "ftype": "file", "chksum_type": "sha256", @@ -5884,7 +7641,7 @@ "name": "tests/integration/inventory.networking", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "2f98cc7be6476b7a7ad0e2caea267a1ad0611ddbf68a41631eaf68f0be892594", + "chksum_sha256": "ad3de59540f46afab94288be8016738381fb364db4a1cce0f7b26f9e63ac521a", "format": 1 }, { @@ -5919,7 +7676,7 @@ "name": "meta/runtime.yml", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "17ab3614c00de79cc4ec11a5e518cc3e261b57da667f356f44b02722713b7efd", + "chksum_sha256": "f46a8922200bc41ffe0f26b3be9ef551b64046819d574471631f87913ac8d0b2", "format": 1 }, { @@ -5961,7 +7718,7 @@ "name": "changelogs/config.yaml", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "3e20eced2de0cbf27b3220fcf0e10f59ef770591c430effa860ce0ceba6c0cbf", + "chksum_sha256": "5ba5480a4348c1f003132c6667e2e03bcca83874e8dcda80309ada33332704c1", "format": 1 }, { @@ -5975,14 +7732,14 @@ "name": "changelogs/.plugin-cache.yaml", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "8e2a55e7abe9dd6f1fb50dc47ce19a1a4fd2ad43e130cc154ebaa2b55c471129", + "chksum_sha256": "8760c5fd8ac292f4cb5d1e38be9d92e149e5c38f6575d75e62afaf606fb56d4a", "format": 1 }, { "name": "changelogs/changelog.yaml", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "696a0b9125e79832a7ee911690ba32197e650b9f08303a9f6349ec78a97948f4", + "chksum_sha256": "5b5b4f364b875b398bfc855bf601a4acf3199406aaeba2d3191cf0e18bd670db", "format": 1 }, { @@ -6073,7 +7830,7 @@ "name": "CHANGELOG.rst", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "42d567e55f3e72b7de9ab5bc53d9cbf5006d02c30085bb9c47a89e772c973c53", + "chksum_sha256": "803f408887301e778901174e6ac06a58a26ce84267e24936b3aa58d4441bd333", "format": 1 }, { diff --git a/ansible_collections/cisco/aci/MANIFEST.json b/ansible_collections/cisco/aci/MANIFEST.json index 2e43ac5e6..f326a92fb 100644 --- a/ansible_collections/cisco/aci/MANIFEST.json +++ b/ansible_collections/cisco/aci/MANIFEST.json @@ -2,7 +2,7 @@ "collection_info": { "namespace": "cisco", "name": "aci", - "version": "2.6.0", + "version": "2.8.0", "authors": [ "Dag Wieers (@dagwieers) <dag@wieers.com>", "Swetha Chunduri (@schunduri)", @@ -28,7 +28,9 @@ "description": "Ansible Modules for Cisco ACI", "license": [], "license_file": "LICENSE", - "dependencies": {}, + "dependencies": { + "ansible.netcommon": "*" + }, "repository": "https://github.com/CiscoDevNet/ansible-aci", "documentation": "https://docs.ansible.com/ansible/latest/scenario_guides/guide_aci.html", "homepage": "https://github.com/CiscoDevNet/ansible-aci", @@ -38,7 +40,7 @@ "name": "FILES.json", "ftype": "file", "chksum_type": "sha256", - "chksum_sha256": "7ddfe44411d58631ea3cb85958389cae33da23590ad0bfa512f32b13efefb32a", + "chksum_sha256": "0c3eb373bfc6269787c650369f02ac533918bab3cdc51c3bf65c4e6de2114714", "format": 1 }, "format": 1 diff --git a/ansible_collections/cisco/aci/changelogs/.plugin-cache.yaml b/ansible_collections/cisco/aci/changelogs/.plugin-cache.yaml index e314fd821..18556ea00 100644 --- a/ansible_collections/cisco/aci/changelogs/.plugin-cache.yaml +++ b/ansible_collections/cisco/aci/changelogs/.plugin-cache.yaml @@ -6,7 +6,12 @@ plugins: callback: {} cliconf: {} connection: {} - httpapi: {} + filter: {} + httpapi: + aci: + description: Ansible ACI HTTPAPI Plugin. + name: aci + version_added: null inventory: {} lookup: interface_range: @@ -14,6 +19,21 @@ plugins: name: interface_range version_added: null module: + aci_aaa_custom_privilege: + description: Manage AAA RBAC Custom Privileges (aaa:RbacClassPriv) + name: aci_aaa_custom_privilege + namespace: '' + version_added: null + aci_aaa_domain: + description: Manage AAA domains (aaa:Domain) + name: aci_aaa_domain + namespace: '' + version_added: null + aci_aaa_role: + description: Manage AAA roles (aaa:Role) + name: aci_aaa_role + namespace: '' + version_added: null aci_aaa_ssh_auth: description: Manage AAA SSH auth (aaaSshAuth) objects. name: aci_aaa_ssh_auth @@ -30,12 +50,12 @@ plugins: namespace: '' version_added: null aci_aaa_user_domain: - description: Manage AAA user domain (aaaUserDomain) objects. + description: Manage AAA user domains (aaa:UserDomain) name: aci_aaa_user_domain namespace: '' version_added: null aci_aaa_user_role: - description: Manage AAA user role (aaaUserRole) objects. + description: Manage AAA user roles (aaa:UserRole) name: aci_aaa_user_role namespace: '' version_added: null @@ -51,6 +71,36 @@ plugins: name: aci_access_port_to_interface_policy_leaf_profile namespace: '' version_added: null + aci_access_span_dst_group: + description: Manage Access SPAN destination groups (span:DestGrp) + name: aci_access_span_dst_group + namespace: '' + version_added: null + aci_access_span_filter_group: + description: Manage Access SPAN filter groups (span:FilterGrp) + name: aci_access_span_filter_group + namespace: '' + version_added: null + aci_access_span_filter_group_entry: + description: Manage Access SPAN filter group entries (span:FilterEntry) + name: aci_access_span_filter_group_entry + namespace: '' + version_added: null + aci_access_span_src_group: + description: Manage Access SPAN source groups (span:SrcGrp) + name: aci_access_span_src_group + namespace: '' + version_added: null + aci_access_span_src_group_src: + description: Manage Access SPAN sources (span:Src) + name: aci_access_span_src_group_src + namespace: '' + version_added: null + aci_access_span_src_group_src_path: + description: Manage Access SPAN source paths (span:RsSrcToPathEp) + name: aci_access_span_src_group_src_path + namespace: '' + version_added: null aci_access_sub_port_block_to_access_port: description: Manage sub port blocks of Fabric interface policy leaf profile interface selectors (infra:HPortS, infra:SubPortBlk) @@ -98,6 +148,11 @@ plugins: name: aci_bd_to_l3out namespace: '' version_added: null + aci_bgp_best_path_policy: + description: Manage BGP Best Path policy (bgp:BestPathCtrlPol) + name: aci_bgp_best_path_policy + namespace: '' + version_added: null aci_bgp_rr_asn: description: Manage BGP Route Reflector ASN. name: aci_bgp_rr_asn @@ -108,6 +163,11 @@ plugins: name: aci_bgp_rr_node namespace: '' version_added: null + aci_bgp_timers_policy: + description: Manage BGP timers policy (bgp:CtxPol) + name: aci_bgp_timers_policy + namespace: '' + version_added: null aci_bulk_static_binding_to_epg: description: Bind static paths to EPGs (fv:RsPathAtt) name: aci_bulk_static_binding_to_epg @@ -183,6 +243,11 @@ plugins: name: aci_cloud_zone namespace: '' version_added: null + aci_config_export_policy: + description: Manage Configuration Export Policy (config:ExportP) + name: aci_config_export_policy + namespace: '' + version_added: null aci_config_rollback: description: Provides rollback and rollback preview functionality (config:ImportP) name: aci_config_rollback @@ -279,6 +344,11 @@ plugins: name: aci_epg_monitoring_policy namespace: '' version_added: null + aci_epg_subnet: + description: Manage EPG Subnets (fv:Subnet) + name: aci_epg_subnet + namespace: '' + version_added: null aci_epg_to_contract: description: Bind EPGs to Contracts (fv:RsCons, fv:RsProv) name: aci_epg_to_contract @@ -324,6 +394,11 @@ plugins: name: aci_esg_tag_selector namespace: '' version_added: null + aci_fabric_interface_policy_group: + description: Manage Fabric Interface Policy Groups (fabric:LePortPGrp, fabric:SpPortPGrp) + name: aci_fabric_interface_policy_group + namespace: '' + version_added: null aci_fabric_leaf_profile: description: Manage fabric leaf profiles (fabric:LeafP). name: aci_fabric_leaf_profile @@ -340,11 +415,51 @@ plugins: name: aci_fabric_node namespace: '' version_added: null + aci_fabric_node_control: + description: Manage Fabric Node Controls (fabric:NodeControl) + name: aci_fabric_node_control + namespace: '' + version_added: null + aci_fabric_pod_policy_group: + description: Manage Fabric Pod Policy Groups (fabric:PodPGrp) + name: aci_fabric_pod_policy_group + namespace: '' + version_added: null + aci_fabric_pod_selector: + description: Manage Fabric Pod Selectors (fabric:PodS) + name: aci_fabric_pod_selector + namespace: '' + version_added: null aci_fabric_scheduler: - description: This modules creates ACI schedulers. + description: This module creates ACI schedulers (trig:SchedP) name: aci_fabric_scheduler namespace: '' version_added: null + aci_fabric_span_dst_group: + description: Manage Fabric SPAN destination groups (span:DestGrp) + name: aci_fabric_span_dst_group + namespace: '' + version_added: null + aci_fabric_span_src_group: + description: Manage Fabric SPAN source groups (span:SrcGrp) + name: aci_fabric_span_src_group + namespace: '' + version_added: null + aci_fabric_span_src_group_src: + description: Manage Fabric SPAN sources (span:Src) + name: aci_fabric_span_src_group_src + namespace: '' + version_added: null + aci_fabric_span_src_group_src_node: + description: Manage Fabric SPAN source nodes (span:RsSrcToNode) + name: aci_fabric_span_src_group_src_node + namespace: '' + version_added: null + aci_fabric_span_src_group_src_path: + description: Manage Fabric SPAN source paths (span:RsSrcToPathEp) + name: aci_fabric_span_src_group_src_path + namespace: '' + version_added: null aci_fabric_spine_profile: description: Manage fabric spine profiles (fabric:SpineP). name: aci_fabric_spine_profile @@ -366,6 +481,16 @@ plugins: name: aci_fabric_switch_policy_group namespace: '' version_added: null + aci_fabric_wide_settings: + description: Manage Fabric Wide Settings (infra:SetPol) + name: aci_fabric_wide_settings + namespace: '' + version_added: null + aci_file_remote_path: + description: Manage Import/Export File Remote Paths (file:RemotePath) + name: aci_file_remote_path + namespace: '' + version_added: null aci_filter: description: Manages top level filter objects (vz:Filter) name: aci_filter @@ -377,17 +502,17 @@ plugins: namespace: '' version_added: null aci_firmware_group: - description: This module creates a firmware group + description: Manage firmware groups (firmware:FwGrp) name: aci_firmware_group namespace: '' version_added: null aci_firmware_group_node: - description: This modules adds and remove nodes from the firmware group + description: Manage firmware group nodes (fabric:NodeBlk) name: aci_firmware_group_node namespace: '' version_added: null aci_firmware_policy: - description: This creates a firmware policy + description: Manage firmware policies (firmware:FwP) name: aci_firmware_policy namespace: '' version_added: null @@ -396,11 +521,22 @@ plugins: name: aci_firmware_source namespace: '' version_added: null + aci_igmp_interface_policy: + description: Manage IGMP Interface Policies (igmp:IfPol) + name: aci_igmp_interface_policy + namespace: '' + version_added: null aci_interface_blacklist: description: Enabling or Disabling physical interfaces. name: aci_interface_blacklist namespace: '' version_added: null + aci_interface_config: + description: Manage Interface Configuration of Access (infra:PortConfig) and + Fabric (fabric:PortConfig) Ports is only supported for ACI 5.2(7)+ + name: aci_interface_config + namespace: '' + version_added: null aci_interface_description: description: Setting and removing description on physical interfaces. name: aci_interface_description @@ -426,6 +562,12 @@ plugins: name: aci_interface_policy_leaf_breakout_port_group namespace: '' version_added: null + aci_interface_policy_leaf_fc_policy_group: + description: Manage Fibre Channel (FC) interface policy groups (infra:FcAccBndlGrp, + infra:FcAccPortGrp) + name: aci_interface_policy_leaf_fc_policy_group + namespace: '' + version_added: null aci_interface_policy_leaf_policy_group: description: Manage fabric interface policy leaf policy groups (infra:AccBndlGrp, infra:AccPortGrp) @@ -437,6 +579,11 @@ plugins: name: aci_interface_policy_leaf_profile namespace: '' version_added: null + aci_interface_policy_leaf_profile_fex_policy_group: + description: Manage leaf interface profiles fex policy group (infra:FexBndlGrp) + name: aci_interface_policy_leaf_profile_fex_policy_group + namespace: '' + version_added: null aci_interface_policy_link_level: description: Manage Link Level interface policies (fabric:HIfPol) name: aci_interface_policy_link_level @@ -467,6 +614,16 @@ plugins: name: aci_interface_policy_port_security namespace: '' version_added: null + aci_interface_policy_spanning_tree: + description: Manage spanning tree interface policies (stp:IfPol) + name: aci_interface_policy_spanning_tree + namespace: '' + version_added: null + aci_interface_policy_spine_policy_group: + description: Manage spine access interface policy groups (infra:SpAccPortGrp) + name: aci_interface_policy_spine_policy_group + namespace: '' + version_added: null aci_interface_selector_to_switch_policy_leaf_profile: description: Bind interface selector profiles to switch policy leaf profiles (infra:RsAccPortP) @@ -513,6 +670,11 @@ plugins: name: aci_l3out_bgp_peer namespace: '' version_added: null + aci_l3out_bgp_protocol_profile: + description: Manage BGP Protocol Profile (bgp:ProtP) + name: aci_l3out_bgp_protocol_profile + namespace: '' + version_added: null aci_l3out_extepg: description: Manage External Network Instance Profile (ExtEpg) objects (l3extInstP:instP) name: aci_l3out_extepg @@ -561,7 +723,7 @@ plugins: namespace: '' version_added: null aci_l3out_logical_node_profile: - description: Manage Layer 3 Outside (L3Out) logical node profiles (l3extLNodeP:lnodep) + description: Manage Layer 3 Outside (L3Out) logical node profiles (l3ext:LNodeP) name: aci_l3out_logical_node_profile namespace: '' version_added: null @@ -581,20 +743,50 @@ plugins: namespace: '' version_added: null aci_maintenance_group: - description: This creates an ACI maintenance group + description: This creates an ACI maintenance group (maint:MaintGrp) name: aci_maintenance_group namespace: '' version_added: null aci_maintenance_group_node: - description: Manage maintenance group nodes + description: Manage maintenance group nodes (fabric:NodeBlk) name: aci_maintenance_group_node namespace: '' version_added: null aci_maintenance_policy: - description: Manage firmware maintenance policies + description: Manage firmware maintenance policies (maint:MaintP) name: aci_maintenance_policy namespace: '' version_added: null + aci_match_as_path_regex_term: + description: Manage Match Regular Expression AS-Path Term (rtctrl:MatchAsPathRegexTerm) + name: aci_match_as_path_regex_term + namespace: '' + version_added: null + aci_match_community_factor: + description: Manage Match Community Factor (rtctrl:MatchCommFactor) + name: aci_match_community_factor + namespace: '' + version_added: null + aci_match_community_regex_term: + description: Manage Match Regular Expression Community Term (rtctrl:MatchCommRegexTerm) + name: aci_match_community_regex_term + namespace: '' + version_added: null + aci_match_community_term: + description: Manage Match Community Term (rtctrl:MatchCommTerm) + name: aci_match_community_term + namespace: '' + version_added: null + aci_match_route_destination: + description: Manage Match action rule term based on the Route Destination. (rtctrl:MatchRtDest) + name: aci_match_route_destination + namespace: '' + version_added: null + aci_match_rule: + description: Manage Match Rule (rtcrtl:SubjP) + name: aci_match_rule + namespace: '' + version_added: null aci_node_mgmt_epg: description: In band or Out of band management EPGs name: aci_node_mgmt_epg @@ -615,6 +807,16 @@ plugins: name: aci_rest namespace: '' version_added: null + aci_route_control_context: + description: Manage Route Control Context (rtcrtl:CtxP) + name: aci_route_control_context + namespace: '' + version_added: null + aci_route_control_profile: + description: Manage Route Control Profile (rtcrtl:Profile) + name: aci_route_control_profile + namespace: '' + version_added: null aci_snmp_client: description: Manage SNMP clients (snmp:ClientP). name: aci_snmp_client @@ -688,6 +890,22 @@ plugins: name: aci_system namespace: '' version_added: null + aci_system_banner: + description: Manages Alias and Banners (aaa:PreLoginBanner) + name: aci_system_banner + namespace: '' + version_added: null + aci_system_endpoint_controls: + description: Manage System Endpoint Controls (ep:IpAgingP, ep:ControlP, and + ep:LoopProtectP) + name: aci_system_endpoint_controls + namespace: '' + version_added: null + aci_system_global_aes_passphrase_encryption: + description: Manage Global AES Passphrase Encryption Settings (pki:ExportEncryptionKey) + name: aci_system_global_aes_passphrase_encryption + namespace: '' + version_added: null aci_taboo_contract: description: Manage taboo contracts (vz:BrCP) name: aci_taboo_contract @@ -723,6 +941,11 @@ plugins: name: aci_tenant_span_src_group namespace: '' version_added: null + aci_tenant_span_src_group_src: + description: Manage SPAN source group sources (span:Src) + name: aci_tenant_span_src_group_src + namespace: '' + version_added: null aci_tenant_span_src_group_to_dst_group: description: Bind SPAN source groups to destination groups (span:SpanLbl) name: aci_tenant_span_src_group_to_dst_group @@ -768,6 +991,11 @@ plugins: name: aci_vrf namespace: '' version_added: null + aci_vrf_leak_internal_subnet: + description: Manage VRF leaking of subnets (fv:leakInternalSubnet) + name: aci_vrf_leak_internal_subnet + namespace: '' + version_added: null aci_vzany_to_contract: description: Attach contracts to vzAny (vz:RsAnyToProv, vz:RsAnyToCons, vz:RsAnyToConsIf) name: aci_vzany_to_contract @@ -776,5 +1004,6 @@ plugins: netconf: {} shell: {} strategy: {} + test: {} vars: {} -version: 2.3.0 +version: 2.8.0 diff --git a/ansible_collections/cisco/aci/changelogs/changelog.yaml b/ansible_collections/cisco/aci/changelogs/changelog.yaml index 10e897d03..ae1c6a3f5 100644 --- a/ansible_collections/cisco/aci/changelogs/changelog.yaml +++ b/ansible_collections/cisco/aci/changelogs/changelog.yaml @@ -3,359 +3,459 @@ releases: 1.0.0: changes: bugfixes: - - Fix sanity issues to support 2.10.0 - - Fix some doc issues for a few modules - - Fix some formatting issues (flake8) in unit tests. - - Fixing integration tests and sanity. Tested on ACI 4.2(3l). + - Fix sanity issues to support 2.10.0 + - Fix some doc issues for a few modules + - Fix some formatting issues (flake8) in unit tests. + - Fixing integration tests and sanity. Tested on ACI 4.2(3l). minor_changes: - - Add Fex capability to aci_interface_policy_leaf_profile, aci_access_port_to_interface_policy_leaf_profile - and aci_access_port_block_to_access_port - - Add LICENSE file - - Add aci_epg_to_contract_master module - - Add annotation attribute to aci.py and to doc fragment. - - Add annotation to every payload and add test case for annotation. - - Add changelog - - Add collection prefix to all integration tests - - Add galaxy.yml file for collection listing - - Add github action CI pipeline - - Add module and test file for aci_bd_dhcp_label - - Add modules and test files for aci_cloud_ctx_profile, aci_cloud_cidr, aci_cloud_subnet - and aci_cloud_zone - - Add modules and test files for aci_l2out, aci_l2out_extepg and aci_l3out_extepg_to_contract - - Add names to documentation examples for modules from community.network - - Add preferred group support to aci_vrf - - Add support for Azure on all cloud modules - - Add support for output_path to allow dump of REST API objects - - Add support for owner_key and owner_tag for all modules and add test case - for it. - - Add vpn gateway dedicated module and remove vpn_gateway from cloud_ctx_profile - module - - Fix M() and module to use FQCN - - Initial commit based on the collection migration available at "ansible-collection-migration/cisco.aci" - which contains the ACI module from Ansible Core - - Move aci.py to base of module_utils and fix references - - Move test file to root of tests/unit/module_utils - - Update Ansible version in CI and add 2.10.0 to sanity in CI. - - Update Readme with supported versions - - Update to test files to make the tests work on both 3.2 and 4.2. - release_summary: 'This is the first official release of the ``cisco.aci`` collection + - Add Fex capability to aci_interface_policy_leaf_profile, aci_access_port_to_interface_policy_leaf_profile + and aci_access_port_block_to_access_port + - Add LICENSE file + - Add aci_epg_to_contract_master module + - Add annotation attribute to aci.py and to doc fragment. + - Add annotation to every payload and add test case for annotation. + - Add changelog + - Add collection prefix to all integration tests + - Add galaxy.yml file for collection listing + - Add github action CI pipeline + - Add module and test file for aci_bd_dhcp_label + - Add modules and test files for aci_cloud_ctx_profile, aci_cloud_cidr, aci_cloud_subnet + and aci_cloud_zone + - Add modules and test files for aci_l2out, aci_l2out_extepg and aci_l3out_extepg_to_contract + - Add names to documentation examples for modules from community.network + - Add preferred group support to aci_vrf + - Add support for Azure on all cloud modules + - Add support for output_path to allow dump of REST API objects + - Add support for owner_key and owner_tag for all modules and add test case + for it. + - Add vpn gateway dedicated module and remove vpn_gateway from cloud_ctx_profile + module + - Fix M() and module to use FQCN + - Initial commit based on the collection migration available at "ansible-collection-migration/cisco.aci" + which contains the ACI module from Ansible Core + - Move aci.py to base of module_utils and fix references + - Move test file to root of tests/unit/module_utils + - Update Ansible version in CI and add 2.10.0 to sanity in CI. + - Update Readme with supported versions + - Update to test files to make the tests work on both 3.2 and 4.2. + release_summary: + "This is the first official release of the ``cisco.aci`` collection on 2020-08-18. This changelog describes all changes made to the modules and plugins included in this collection since Ansible 2.9.0. - ' - release_date: '2020-08-18' + " + release_date: "2020-08-18" 1.0.1: changes: bugfixes: - - Fix convertion of json/yaml payload to xml in aci_rest - - Fix dump of config for aci_rest - - Fix issue of "current" in firmware_source module - - Fix sanity issue in aci_rest and bump version to v1.0.1 + - Fix convertion of json/yaml payload to xml in aci_rest + - Fix dump of config for aci_rest + - Fix issue of "current" in firmware_source module + - Fix sanity issue in aci_rest and bump version to v1.0.1 minor_changes: - - Enable/Disable infra vlan in aci_aep and its test module - - Set scope default value in aci_l3out_extsubnet - release_summary: 'Release v1.0.1 of the ``cisco.aci`` collection on 2020-10-13. + - Enable/Disable infra vlan in aci_aep and its test module + - Set scope default value in aci_l3out_extsubnet + release_summary: + "Release v1.0.1 of the ``cisco.aci`` collection on 2020-10-13. This changelog describes all changes made to the modules and plugins included - in this collection since v1.0.0. ' - release_date: '2020-10-13' + in this collection since v1.0.0. " + release_date: "2020-10-13" 1.1.0: changes: bugfixes: - - Existing_config variable is not reset during loop - - Fix galaxy import warnings - - Fix how validity of private key/private key file is checked to support new - types - - Fix incorrect domain types in aci_domain_to_encap_pool module + - Existing_config variable is not reset during loop + - Fix galaxy import warnings + - Fix how validity of private key/private key file is checked to support new + types + - Fix incorrect domain types in aci_domain_to_encap_pool module minor_changes: - - Ability to add monitoring policy to epgs and anps - - Add Ansible Network ENV to fallback - - Add aci_l3out_external_path_to_member.py & aci_l3out_static_routes modules - - Add env_fallback for common connection params - - Add env_fallback for the rest of the argument spec - - Add new Subclass path support - - Add new module and test file for leaf breakout port group - - Added failure message to aci_interface_policy_leaf_policy_group - - Update README.md - - Update inventory - - aci_epg_to_domain addition of promiscuous mode (#79) - - aci_interface_policy_port_security addition of attribute:timeout (#80) - release_summary: 'Release v1.1.0 of the ``cisco.aci`` collection on 2020-10-30. + - Ability to add monitoring policy to epgs and anps + - Add Ansible Network ENV to fallback + - Add aci_l3out_external_path_to_member.py & aci_l3out_static_routes modules + - Add env_fallback for common connection params + - Add env_fallback for the rest of the argument spec + - Add new Subclass path support + - Add new module and test file for leaf breakout port group + - Added failure message to aci_interface_policy_leaf_policy_group + - Update README.md + - Update inventory + - aci_epg_to_domain addition of promiscuous mode (#79) + - aci_interface_policy_port_security addition of attribute:timeout (#80) + release_summary: + "Release v1.1.0 of the ``cisco.aci`` collection on 2020-10-30. This changelog describes all changes made to the modules and plugins included - in this collection since v1.0.1. ' - release_date: '2020-10-30' + in this collection since v1.0.1. " + release_date: "2020-10-30" 1.1.1: changes: bugfixes: - - Fix galaxy import warnings - - Fix sanity issue in aci_epg_to_domain + - Fix galaxy import warnings + - Fix sanity issue in aci_epg_to_domain minor_changes: - - Add test file for aci_domain_to_encap_pool - - aci_epg_to_domain moving child configs & classes to each domain type - release_summary: 'Release v1.1.1 of the ``cisco.aci`` collection on 2020-11-23. + - Add test file for aci_domain_to_encap_pool + - aci_epg_to_domain moving child configs & classes to each domain type + release_summary: + "Release v1.1.1 of the ``cisco.aci`` collection on 2020-11-23. This changelog describes all changes made to the modules and plugins included - in this collection since v1.1.0. ' - release_date: '2020-11-23' + in this collection since v1.1.0. " + release_date: "2020-11-23" 2.0.0: changes: major_changes: - - Change certificate_name to name in aci_aaa_user_certificate module for query - operation + - Change certificate_name to name in aci_aaa_user_certificate module for query + operation minor_changes: - - Add aci_node_mgmt_epg module to manage in band or out of band management EPGs - - Add aci_static_node_mgmt_address module & test file - - Add test file for aci_node_mgmt_epg - release_summary: 'Release v2.0.0 of the ``cisco.aci`` collection on 2020-12-15. + - Add aci_node_mgmt_epg module to manage in band or out of band management EPGs + - Add aci_static_node_mgmt_address module & test file + - Add test file for aci_node_mgmt_epg + release_summary: + "Release v2.0.0 of the ``cisco.aci`` collection on 2020-12-15. This changelog describes all changes made to the modules and plugins included - in this collection since v1.1.1. ' - release_date: '2020-12-15' + in this collection since v1.1.1. " + release_date: "2020-12-15" 2.1.0: changes: bugfixes: - - Fix blacklist bug - - Fix cleanup of MGMT EPGs - - Fix module reference for l3out_node_profile cleanup task - - Fix required variables for absent and present states for l3out_node_profile - - Fix sanity & importer check errors - - Fix test and assertion variables and module references for l3out_node_profile - tests - - pylint fix for .format() + - Fix blacklist bug + - Fix cleanup of MGMT EPGs + - Fix module reference for l3out_node_profile cleanup task + - Fix required variables for absent and present states for l3out_node_profile + - Fix sanity & importer check errors + - Fix test and assertion variables and module references for l3out_node_profile + tests + - pylint fix for .format() minor_changes: - - Add APIC 5.x to inventory for Integration tests - - Add a requirements file - - Add ability to change custom epg name - - Add aci_cloud_ap module and test file - - Add aci_cloud_aws_provider module and its test file (#181) - - Add aci_cloud_bgp_asn module and test file (#180) - - Add aci_cloud_epg_selector module and test file (#182) - - Add aci_fabric_spine_profile, aci_fabric_spine_switch_assoc and aci_fabric_switch_block - modules and integration tests (#187) - - Add aci_info - - Add aci_interface_description module and test file (#167) - - Add aci_l3out_bgp_peer and aci_l3out_interface modules and test files (#177) - - Add aci_snmp_client, aci_snmp_client_group, aci_snmp_community_policy, aci_snmp_policy - and aci_snmp_user modules and test files (#176) - - Add aci_syslog_group module and test file (#170) - - Add aci_syslog_source and aci_syslog_remote_dest modules and test files (#174) - - Add aci_vmm_controller module and test file - - Add aci_vmm_vswitch module and test file (#142) - - Add check for enhanced lag policy - - Add cloud_external_epg and cloud_external_epg_selector modules and test files - (#185) - - Add directory and aliases file for l3out node profile tests - - Add ethertype for IPv6 - - Add ethertype ipv4 - - Add functionality to support cryptography for signing - - Add galaxy-importer check (#115) - - Add ipv6_l3_unknown_multicast parameter support for aci_bd - - Add issue templates - - Add module aci_cloud_epg & test file (#175) - - Add module aci_l3out_logical_node_profile to manage l3out node profiles - - Add module and test for aci_contract_subject_to_service_graph - - Add new module aci_l2out_extepg_to_contract and test file based on aci_l3out_extepg_to_contract - - Add new modules for L2out - aci_l2out_logical_* - - Add primary_encap in module tests - - Add route_profile, route_profile_l3_out to aci_bd - - Add support and tests for custom_qos_policy parameter in aci_epg - - Add support for ANSIBLE_NET_SSH_KEYFILE - - Add support for vmm domain infra port group and tag collection in aci_domain - module (#141) - - Add task to create requirement for enhanced lag policy - - Add test case for custom epg name - - Add test file for aci_bd - - Add tests for ipv6_l3_unknown_multicast parameter support in aci_bd - - Add tests for l3out node profile module - - Add tests to create multiple node profiles and query all node profiles in - an L3out - - Add variable references and fix naming in l3out_node_profile tests - - Add version check for changing custom epg name - - Added Enhanced Lag Policy for VMware VMM Domain Profile in module aci_epg_to_domain - - Change CI to latest version of ansible and python 3.8 - - Change child_configs & child_classes - - Change dscp to target_dscp in aci_l3out_logical_node_profile module to avoid - future var conflicts - - Change naming of lagpolicy - - Change primary_encap --> primaryEncap - - Change test case for enhanced_lag_policy - - Changes made to execute aci_epg_to_domain and aci_cloud_cidr modules, also - generalised the cloud variables - - Check WARNINGs and ERRORs in galaxy-importer check (#118) - - Correcting sanity in aci_static_binding_to_epg.py module - - Fix broken test parameters for aci_l3out_logical_interface_profile - - Fix documentation and add example to query all node profiles for L3out - - Fix feedback - - Fix indentation causing linting error - - Fix lag_plicy tDn - - Fix missed separators '/' in path attribute of ACIModule class - - Fix module reference and remove unused aliases in aci_l3out_logical_node_profile - tests - - Fixed default values in docs and specs - - Fixed the behavior when output is specified in aci_rest. (#169) - - Initial changes to aci_cloud_ctx_profile module to execute only cloud sites - from inventory - - Interface types added for Po's and vPC's using fex-ports and test files - - L3Out Enhancements - - L3Out Interface Profile (#134) - - Made changes in collection version segment - - Made changes in mso.py to generalize construct_url - - Made changes to support aci non cloud host >=3.2 - - Made changes with respect to galaxy importer similar to MSO - - Modified 12 files affected from inventory file changes, by differentiating - tasks into cloud and non-cloud specific hosts - - Move custom_qos_policy to conditional and remove unnecessary custom_qos_policy - from monitoring policy in test - - Move ipv6_l3_unknown_multicast to condition and check version in test - - Remove uneccessary delegate_to variable for l3out_node_profile cleanup task - - Separated assert statements for cloud and non-cloud sites and added additional - condition statement required for execution of version<=4.1 - - Supports primaryEncap value as unknown (#157) - - Update aci_l3out_extepg_to_contract.py - - W291 + boolean correction - - contract_enhancements (#135) - - doc-required-mismatch fix - - interface blacklist test fix - - interface disable/enable fabricRsOosPath - - interface disable/enable fex support - release_summary: 'Release v2.1.0 of the ``ansible-aci`` collection on 2021-10-06. + - Add APIC 5.x to inventory for Integration tests + - Add a requirements file + - Add ability to change custom epg name + - Add aci_cloud_ap module and test file + - Add aci_cloud_aws_provider module and its test file (#181) + - Add aci_cloud_bgp_asn module and test file (#180) + - Add aci_cloud_epg_selector module and test file (#182) + - Add aci_fabric_spine_profile, aci_fabric_spine_switch_assoc and aci_fabric_switch_block + modules and integration tests (#187) + - Add aci_info + - Add aci_interface_description module and test file (#167) + - Add aci_l3out_bgp_peer and aci_l3out_interface modules and test files (#177) + - Add aci_snmp_client, aci_snmp_client_group, aci_snmp_community_policy, aci_snmp_policy + and aci_snmp_user modules and test files (#176) + - Add aci_syslog_group module and test file (#170) + - Add aci_syslog_source and aci_syslog_remote_dest modules and test files (#174) + - Add aci_vmm_controller module and test file + - Add aci_vmm_vswitch module and test file (#142) + - Add check for enhanced lag policy + - Add cloud_external_epg and cloud_external_epg_selector modules and test files + (#185) + - Add directory and aliases file for l3out node profile tests + - Add ethertype for IPv6 + - Add ethertype ipv4 + - Add functionality to support cryptography for signing + - Add galaxy-importer check (#115) + - Add ipv6_l3_unknown_multicast parameter support for aci_bd + - Add issue templates + - Add module aci_cloud_epg & test file (#175) + - Add module aci_l3out_logical_node_profile to manage l3out node profiles + - Add module and test for aci_contract_subject_to_service_graph + - Add new module aci_l2out_extepg_to_contract and test file based on aci_l3out_extepg_to_contract + - Add new modules for L2out - aci_l2out_logical_* + - Add primary_encap in module tests + - Add route_profile, route_profile_l3_out to aci_bd + - Add support and tests for custom_qos_policy parameter in aci_epg + - Add support for ANSIBLE_NET_SSH_KEYFILE + - Add support for vmm domain infra port group and tag collection in aci_domain + module (#141) + - Add task to create requirement for enhanced lag policy + - Add test case for custom epg name + - Add test file for aci_bd + - Add tests for ipv6_l3_unknown_multicast parameter support in aci_bd + - Add tests for l3out node profile module + - Add tests to create multiple node profiles and query all node profiles in + an L3out + - Add variable references and fix naming in l3out_node_profile tests + - Add version check for changing custom epg name + - Added Enhanced Lag Policy for VMware VMM Domain Profile in module aci_epg_to_domain + - Change CI to latest version of ansible and python 3.8 + - Change child_configs & child_classes + - Change dscp to target_dscp in aci_l3out_logical_node_profile module to avoid + future var conflicts + - Change naming of lagpolicy + - Change primary_encap --> primaryEncap + - Change test case for enhanced_lag_policy + - Changes made to execute aci_epg_to_domain and aci_cloud_cidr modules, also + generalised the cloud variables + - Check WARNINGs and ERRORs in galaxy-importer check (#118) + - Correcting sanity in aci_static_binding_to_epg.py module + - Fix broken test parameters for aci_l3out_logical_interface_profile + - Fix documentation and add example to query all node profiles for L3out + - Fix feedback + - Fix indentation causing linting error + - Fix lag_plicy tDn + - Fix missed separators '/' in path attribute of ACIModule class + - Fix module reference and remove unused aliases in aci_l3out_logical_node_profile + tests + - Fixed default values in docs and specs + - Fixed the behavior when output is specified in aci_rest. (#169) + - Initial changes to aci_cloud_ctx_profile module to execute only cloud sites + from inventory + - Interface types added for Po's and vPC's using fex-ports and test files + - L3Out Enhancements + - L3Out Interface Profile (#134) + - Made changes in collection version segment + - Made changes in mso.py to generalize construct_url + - Made changes to support aci non cloud host >=3.2 + - Made changes with respect to galaxy importer similar to MSO + - Modified 12 files affected from inventory file changes, by differentiating + tasks into cloud and non-cloud specific hosts + - Move custom_qos_policy to conditional and remove unnecessary custom_qos_policy + from monitoring policy in test + - Move ipv6_l3_unknown_multicast to condition and check version in test + - Remove uneccessary delegate_to variable for l3out_node_profile cleanup task + - Separated assert statements for cloud and non-cloud sites and added additional + condition statement required for execution of version<=4.1 + - Supports primaryEncap value as unknown (#157) + - Update aci_l3out_extepg_to_contract.py + - W291 + boolean correction + - contract_enhancements (#135) + - doc-required-mismatch fix + - interface blacklist test fix + - interface disable/enable fabricRsOosPath + - interface disable/enable fex support + release_summary: + "Release v2.1.0 of the ``ansible-aci`` collection on 2021-10-06. This changelog describes all changes made to the modules and plugins included - in this collection since v2.0.0. ' - release_date: '2021-10-06' + in this collection since v2.0.0. " + release_date: "2021-10-06" 2.2.0: changes: bugfixes: - - Add pool_allocation_mode to the required parameter list in aci_vlan_pool_encap_block - module - - Fix bfd issues in aci_l3out_static_routes module on pre-4.2 APICs - - Fix output_path to support multiple APIC runs in parallel - - Fix small sanity issue in aci_epg_to_contract - - Remove owner_key, owner_tag and annotation from module that do not support - them - - Removed block_name from the required parameter list in aci_vlan_pool_encap_block - module + - Add pool_allocation_mode to the required parameter list in aci_vlan_pool_encap_block + module + - Fix bfd issues in aci_l3out_static_routes module on pre-4.2 APICs + - Fix output_path to support multiple APIC runs in parallel + - Fix small sanity issue in aci_epg_to_contract + - Remove owner_key, owner_tag and annotation from module that do not support + them + - Removed block_name from the required parameter list in aci_vlan_pool_encap_block + module minor_changes: - - Add access_mode and enable_vm_folder attributes to aci_domain - - Add aci_bgp_rr_asn and aci_bgp_rr_node module and tests - - Add aci_dhcp_relay and aci_dhcp_relay_provider modules and test files (#211) - - Add aci_dns_profile, aci_dns_domain and aci_dns_provider modules and test - files (#221) - - Add aci_epg_to_contract_interface module and test file - - Add aci_esg, aci_esg_contract_master, aci_esg_epg_selector, aci_esg_ip_subnet_selector - and aci_esg_tag_selector modules (#212) - - Add aci_fabric_leaf_profile and aci_fabric_leaf_switch_assoc modules and test - files - - Add aci_fabric_switch_policy_group module and test file - - Add aci_l3out_interface_secondary_ip module and test file - - Add description to aci_fabric_spine_switch_assoc module - - Add destination_epg, source_ip, destination_ip, span_version, flow_id, ttl, - mtu, dscp, and version_enforced attributes to aci_tenant_span_dst_group module - - Add mtu and ipv6_dad attributes to aci_l3out_interface - - Add new aci_vmm_uplink and aci_vmm_uplink_container modules and test files (#189) - - Add new priorities in the aci_epg_to_contract priority module attribute - - Add support for contract_label and subject_label into aci_epg_to_contract - module - - Add support for tagging with new module aci_tag (#210) - - Add useg attribute to aci_epg module - release_summary: 'Release v2.2.0 of the ``ansible-aci`` collection on 2022-03-15. + - Add access_mode and enable_vm_folder attributes to aci_domain + - Add aci_bgp_rr_asn and aci_bgp_rr_node module and tests + - Add aci_dhcp_relay and aci_dhcp_relay_provider modules and test files (#211) + - Add aci_dns_profile, aci_dns_domain and aci_dns_provider modules and test + files (#221) + - Add aci_epg_to_contract_interface module and test file + - Add aci_esg, aci_esg_contract_master, aci_esg_epg_selector, aci_esg_ip_subnet_selector + and aci_esg_tag_selector modules (#212) + - Add aci_fabric_leaf_profile and aci_fabric_leaf_switch_assoc modules and test + files + - Add aci_fabric_switch_policy_group module and test file + - Add aci_l3out_interface_secondary_ip module and test file + - Add description to aci_fabric_spine_switch_assoc module + - Add destination_epg, source_ip, destination_ip, span_version, flow_id, ttl, + mtu, dscp, and version_enforced attributes to aci_tenant_span_dst_group module + - Add mtu and ipv6_dad attributes to aci_l3out_interface + - Add new aci_vmm_uplink and aci_vmm_uplink_container modules and test files (#189) + - Add new priorities in the aci_epg_to_contract priority module attribute + - Add support for contract_label and subject_label into aci_epg_to_contract + module + - Add support for tagging with new module aci_tag (#210) + - Add useg attribute to aci_epg module + release_summary: + "Release v2.2.0 of the ``ansible-aci`` collection on 2022-03-15. This changelog describes all changes made to the modules and plugins included in this collection since v2.1.0. - ' - release_date: '2022-03-15' + " + release_date: "2022-03-15" 2.3.0: changes: bugfixes: - - Fix HTTP status returned by aci_rest (#279) - - Fix aci_aep_to_epg absent issue to only delete the correct binding (#263) - - Fix aci_interface_description query interface filtering (#238) - - Fix aci_interface_selector_to_switch_policy_leaf_profile error when querying - interface_selector without specifying a switch policy leaf profile (#318) - - Fix aci_rest output_path issues when content is not JSON + - Fix HTTP status returned by aci_rest (#279) + - Fix aci_aep_to_epg absent issue to only delete the correct binding (#263) + - Fix aci_interface_description query interface filtering (#238) + - Fix aci_interface_selector_to_switch_policy_leaf_profile error when querying + interface_selector without specifying a switch policy leaf profile (#318) + - Fix aci_rest output_path issues when content is not JSON minor_changes: - - Add aci_bulk_static_binding_to_epg module to bind multiple interfaces to an - EPG in one API call - - Add aci_l3out_logical_interface_profile_ospf_policy module to apply ospfIfP - policy to L3out logical interface profile (#301) - - Add aci_ntp_policy and aci_ntp_server modules (#229) - - Add cisco.aci.interface_range lookup plugin for interface range handling (#302) - - Add new aci_aaa_ssh_auth, aci_aaa_user_domain and aci_aaa_user_role modules - (#223) - - Add support for active/stanby vmm uplinks in aci_epg_to_domain - - Add support for aggregate attribute, scope default and "import-rtctrl" to - scope choices in aci_l3out_extsubnet module (#260) - - Added fex_port_channel and fex_vpc interface types to aci_access_port_to_interface_policy_leaf_profile - (#241) - - Adding missing options to aci_epg_to_domain - release_summary: 'Release v2.3.0 of the ``ansible-aci`` collection on 2022-10-14. + - Add aci_bulk_static_binding_to_epg module to bind multiple interfaces to an + EPG in one API call + - Add aci_l3out_logical_interface_profile_ospf_policy module to apply ospfIfP + policy to L3out logical interface profile (#301) + - Add aci_ntp_policy and aci_ntp_server modules (#229) + - Add cisco.aci.interface_range lookup plugin for interface range handling (#302) + - Add new aci_aaa_ssh_auth, aci_aaa_user_domain and aci_aaa_user_role modules + (#223) + - Add support for active/stanby vmm uplinks in aci_epg_to_domain + - Add support for aggregate attribute, scope default and "import-rtctrl" to + scope choices in aci_l3out_extsubnet module (#260) + - Added fex_port_channel and fex_vpc interface types to aci_access_port_to_interface_policy_leaf_profile + (#241) + - Adding missing options to aci_epg_to_domain + release_summary: + "Release v2.3.0 of the ``ansible-aci`` collection on 2022-10-14. This changelog describes all changes made to the modules and plugins included in this collection since v2.2.0. - ' - release_date: '2022-10-14' + " + release_date: "2022-10-14" 2.4.0: changes: bugfixes: - - Add snapshot job details in result of aci_config_snapshot to support query - of snapshot results (#342) - - Fix aci_encap_pool_range by removing range_name from required parameters (#368) - - Fix query of all blacklisted interfaces using aci_interface_blacklist (#367) + - Add snapshot job details in result of aci_config_snapshot to support query + of snapshot results (#342) + - Fix aci_encap_pool_range by removing range_name from required parameters (#368) + - Fix query of all blacklisted interfaces using aci_interface_blacklist (#367) minor_changes: - - Add Node Profile BGP Peer and Route Control Profile functionalities to aci_l3out_bgp_peer - module (#340) - - Add SVI auto state support (auto_state attribute) to aci_l3out_interface (#348) - - Add aci_aaa_domain, aci_aaa_role and aci_custom_privilege modules (#226) - - Add aci_fabric_pod_policy_group module (#230) - - Add aci_interface_policy_leaf_profile_fex_policy_group module and add FEX - support to aci_access_port_to_interface_policy_leaf_profile (#233) - - Add aci_tenant_span_src_group_src module (#344) - - Add action_groups for module_defaults (#316) - - Add support for filter direction in aci_contract_subject and aci_contract_subject_to_filter - (#306) - - Update modules to assign roles and permissions to a user (#225) - release_summary: 'Release v2.4.0 of the ``ansible-aci`` collection on 2023-02-04. + - Add Node Profile BGP Peer and Route Control Profile functionalities to aci_l3out_bgp_peer + module (#340) + - Add SVI auto state support (auto_state attribute) to aci_l3out_interface (#348) + - Add aci_aaa_domain, aci_aaa_role and aci_custom_privilege modules (#226) + - Add aci_fabric_pod_policy_group module (#230) + - Add aci_interface_policy_leaf_profile_fex_policy_group module and add FEX + support to aci_access_port_to_interface_policy_leaf_profile (#233) + - Add aci_tenant_span_src_group_src module (#344) + - Add action_groups for module_defaults (#316) + - Add support for filter direction in aci_contract_subject and aci_contract_subject_to_filter + (#306) + - Update modules to assign roles and permissions to a user (#225) + release_summary: + "Release v2.4.0 of the ``ansible-aci`` collection on 2023-02-04. This changelog describes all changes made to the modules and plugins included in this collection since v2.3.0. - ' - release_date: '2023-02-04' + " + release_date: "2023-02-04" 2.5.0: changes: bugfixes: - - Fix missing annotation field in aci_ntp_policy and aci_ntp_server (#392) - - Forced unicode encoding for lxml XML fragment validation output to fix issue - with Certificate authentication and aci_rest with XML payload (#341) + - Fix missing annotation field in aci_ntp_policy and aci_ntp_server (#392) + - Forced unicode encoding for lxml XML fragment validation output to fix issue + with Certificate authentication and aci_rest with XML payload (#341) minor_changes: - - Add aci_interface_config module for new interface configuration available - in ACI v5.2(5)+ (#383) - - Add aci_interface_policy_spanning_tree module (#387) - release_summary: 'Release v2.5.0 of the ``ansible-aci`` collection on 2023-03-31. + - Add aci_interface_config module for new interface configuration available + in ACI v5.2(5)+ (#383) + - Add aci_interface_policy_spanning_tree module (#387) + release_summary: + "Release v2.5.0 of the ``ansible-aci`` collection on 2023-03-31. This changelog describes all changes made to the modules and plugins included in this collection since v2.4.0. - ' - release_date: '2023-03-31' + " + release_date: "2023-03-31" 2.6.0: changes: minor_changes: - - Add aci_access_span_dst_group module for fabric access policies span destination - group support (#405) - - Add aci_access_span_filter_group and aci_access_span_filter_group_entry modules - for access span filter group support (#407) - - Add aci_config_export_policy module (#380) - - Add aci_igmp_interface_policy module (#381) - release_summary: 'Release v2.6.0 of the ``ansible-aci`` collection on 2023-04-19. + - Add aci_access_span_dst_group module for fabric access policies span destination + group support (#405) + - Add aci_access_span_filter_group and aci_access_span_filter_group_entry modules + for access span filter group support (#407) + - Add aci_config_export_policy module (#380) + - Add aci_igmp_interface_policy module (#381) + release_summary: + "Release v2.6.0 of the ``ansible-aci`` collection on 2023-04-19. This changelog describes all changes made to the modules and plugins included in this collection since v2.5.0. - ' - release_date: '2023-04-19' + " + release_date: "2023-04-19" + 2.7.0: + changes: + bugfixes: + - Change input of prefix_suppression to type string to allow enable, disable + and inherit options for aci_interface_policy_ospf + minor_changes: + - Add ACI HTTPAPI plugin with multi host support (#114) + - Add OSPF parameters to aci_l3out module and create the associated test case. + - Add aci_access_span_src_group modules for access span source group support + - Add aci_access_span_src_group_src module for access span source support + - Add aci_access_span_src_group_src_path module for access span source path + support + - Add aci_epg_subnet module (#424) + - Add aci_fabric_span_dst_group module for fabric span destination group support + - Add aci_fabric_span_src_group module for fabric span source group support + - Add aci_fabric_span_src_group_src module for fabric span source support + - Add aci_fabric_span_src_group_src_node module for fabric span source node + support + - Add aci_fabric_span_src_group_src_path module for fabric span source path + support + - Add aci_file_remote_path module (#379) + - Add aci_vrf_leak_internal_subnet module (#449) + - Add description parameter for aci_l3out_logical_interface_profile + - Add ip_data_plane_learning attribute to aci_bd_subnet and aci_vrf modules + (#413) + - Add local_as_number_config and local_as_number attributes to support bgpLocalAsnP + child object in aci_l3out_bgp_peer module (#416) + - Add node_type and remote_leaf_pool_id attributes to aci_fabric_node + - Add source_port, source_port_start, source_port_end, tcp_flags and match_only_fragments + attributes to aci_filter_entry module (#426) + release_summary: + "Release v2.7.0 of the ``ansible-aci`` collection on 2023-08-04. + + This changelog describes all changes made to the modules and plugins included + in this collection since v2.6.0. + + " + release_date: "2023-08-04" + 2.8.0: + changes: + bugfixes: + - Fixed issue with default values for ssl, proxy, timeout in aci.py and the + display of host in the url when the plugin httpapi is used + - Modified aci_rest and aci_config_snapshot modules to display the correct + URL output string (#487) + minor_changes: + - Add 8.0 option for dvs_version attribute in aci_vmm_controller + - Add Match Rules for aci_route_control_profile modules + - Add aci_bgp_timers_policy and aci_bgp_best_path_policy modules + - Add aci_fabric_interface_policy_group module + - Add aci_interface_policy_leaf_fc_policy_group and aci_interface_policy_spine_policy_group + module + - Add aci_l3out_bgp_protocol_profile module + - Add aci_match_community_factor module. + - Add aci_route_control_context and aci_match_rule modules + - Add aci_route_control_profile module + - Add hmac-sha2-224, hmac-sha2-256, hmac-sha2-384, hmac-sha2-512 authentication + types and description to aci_snmp_user module + - Add loopback interface profile as a child class for aci_l3out_logical_node. + - Add missing attributes in aci_interface_policy_leaf_policy_group + - Add missing attributes to aci_l3out_extepg module + - Add missing test cases, fix found issues and add missing attributes for aci_fabric_scheduler, + aci_firmware_group, aci_firmware_group_node, aci_firmware_policy, aci_interface_policy_fc, + aci_interface_policy_lldp, aci_interface_policy_mcp, aci_interface_policy_ospf, + aci_interface_policy_port_channel, aci_maintenance_group, aci_maintenance_group_node, + aci_maintenance_policy and aci_tenant_ep_retention_policy modules (#453) + - Add support for checkmode in aci_rest module + - Add support for configuration of fabric node control with aci_fabric_node_control + module + - Add support for configuration of fabric pod selectors with aci_fabric_pod_selector + module + - Add support for configuration of system banner and alias with aci_system_banner + module + - Add support for configuration of system endpoint controls, ip aging, ep loop + protection and roque endpoint control with aci_system_endpoint_controls module + - Add support for configuration of system fabric wide settings with aci_fabric_wide_settings + module + - Add support for configuration of system global aes passphrase encryption with + aci_system_global_aes_passphrase_encryption module + - Add support for global infra dhcp relay policy configuration in aci_dhcp_relay + - Add support for global infra dhcp relay policy configuration in aci_dhcp_relay_provider + release_summary: + "Release v2.8.0 of the ``ansible-aci`` collection on 2023-11-04. + + This changelog describes all changes made to the modules and plugins included + in this collection since v2.7.0. + + " + release_date: "2023-11-04" diff --git a/ansible_collections/cisco/aci/changelogs/config.yaml b/ansible_collections/cisco/aci/changelogs/config.yaml index 1d6198ea4..6f6f14f81 100644 --- a/ansible_collections/cisco/aci/changelogs/config.yaml +++ b/ansible_collections/cisco/aci/changelogs/config.yaml @@ -10,22 +10,22 @@ notesdir: fragments prelude_section_name: release_summary prelude_section_title: Release Summary sections: -- - major_changes - - Major Changes -- - minor_changes - - Minor Changes -- - breaking_changes - - Breaking Changes / Porting Guide -- - deprecated_features - - Deprecated Features -- - removed_features - - Removed Features (previously deprecated) -- - security_fixes - - Security Fixes -- - bugfixes - - Bugfixes -- - known_issues - - Known Issues + - - major_changes + - Major Changes + - - minor_changes + - Minor Changes + - - breaking_changes + - Breaking Changes / Porting Guide + - - deprecated_features + - Deprecated Features + - - removed_features + - Removed Features (previously deprecated) + - - security_fixes + - Security Fixes + - - bugfixes + - Bugfixes + - - known_issues + - Known Issues title: Cisco ACI Ansible Collection trivial_section_name: trivial use_fqcn: true diff --git a/ansible_collections/cisco/aci/meta/runtime.yml b/ansible_collections/cisco/aci/meta/runtime.yml index e2bed0ea5..9dcf11ce2 100644 --- a/ansible_collections/cisco/aci/meta/runtime.yml +++ b/ansible_collections/cisco/aci/meta/runtime.yml @@ -15,6 +15,9 @@ action_groups: - aci_access_span_dst_group - aci_access_span_filter_group - aci_access_span_filter_group_entry + - aci_access_span_src_group + - aci_access_span_src_group_src + - aci_access_span_src_group_src_path - aci_access_sub_port_block_to_access_port - aci_aep - aci_aep_to_domain @@ -24,8 +27,10 @@ action_groups: - aci_bd_dhcp_label - aci_bd_subnet - aci_bd_to_l3out + - aci_bgp_best_path_policy - aci_bgp_rr_asn - aci_bgp_rr_node + - aci_bgp_timers_policy - aci_bulk_static_binding_to_epg - aci_cloud_ap - aci_cloud_aws_provider @@ -61,6 +66,7 @@ action_groups: - aci_encap_pool_range - aci_epg - aci_epg_monitoring_policy + - aci_epg_subnet - aci_epg_to_contract - aci_epg_to_contract_interface - aci_epg_to_contract_master @@ -70,15 +76,25 @@ action_groups: - aci_esg_epg_selector - aci_esg_ip_subnet_selector - aci_esg_tag_selector + - aci_fabric_interface_policy_group - aci_fabric_leaf_profile - aci_fabric_leaf_switch_assoc - aci_fabric_node + - aci_fabric_node_control - aci_fabric_pod_policy_group + - aci_fabric_pod_selector - aci_fabric_scheduler + - aci_fabric_span_dst_group + - aci_fabric_span_src_group + - aci_fabric_span_src_group_src + - aci_fabric_span_src_group_src_node + - aci_fabric_span_src_group_src_path - aci_fabric_spine_profile - aci_fabric_spine_switch_assoc - aci_fabric_switch_block - aci_fabric_switch_policy_group + - aci_fabric_wide_settings + - aci_file_remote_path - aci_filter - aci_filter_entry - aci_firmware_group @@ -93,6 +109,7 @@ action_groups: - aci_interface_policy_fc - aci_interface_policy_l2 - aci_interface_policy_leaf_breakout_port_group + - aci_interface_policy_leaf_fc_policy_group - aci_interface_policy_leaf_policy_group - aci_interface_policy_leaf_profile - aci_interface_policy_leaf_profile_fex_policy_group @@ -103,6 +120,7 @@ action_groups: - aci_interface_policy_port_channel - aci_interface_policy_port_security - aci_interface_policy_spanning_tree + - aci_interface_policy_spine_policy_group - aci_interface_selector_to_switch_policy_leaf_profile - aci_l2out - aci_l2out_extepg @@ -112,6 +130,7 @@ action_groups: - aci_l2out_logical_node_profile - aci_l3out - aci_l3out_bgp_peer + - aci_l3out_bgp_protocol_profile - aci_l3out_extepg - aci_l3out_extepg_to_contract - aci_l3out_extsubnet @@ -128,10 +147,18 @@ action_groups: - aci_maintenance_group - aci_maintenance_group_node - aci_maintenance_policy + - aci_match_as_path_regex_term + - aci_match_community_factor + - aci_match_community_regex_term + - aci_match_community_term + - aci_match_route_destination + - aci_match_rule - aci_node_mgmt_epg - aci_ntp_policy - aci_ntp_server - aci_rest + - aci_route_control_context + - aci_route_control_profile - aci_snmp_client - aci_snmp_client_group - aci_snmp_community_policy @@ -146,6 +173,9 @@ action_groups: - aci_syslog_remote_dest - aci_syslog_source - aci_system + - aci_system_banner + - aci_system_endpoint_controls + - aci_system_global_aes_passphrase_encryption - aci_taboo_contract - aci_tag - aci_tenant @@ -163,4 +193,5 @@ action_groups: - aci_vmm_uplink_container - aci_vmm_vswitch_policy - aci_vrf + - aci_vrf_leak_internal_subnet - aci_vzany_to_contract diff --git a/ansible_collections/cisco/aci/plugins/doc_fragments/aci.py b/ansible_collections/cisco/aci/plugins/doc_fragments/aci.py index 2bed3dc59..e6b18a289 100644 --- a/ansible_collections/cisco/aci/plugins/doc_fragments/aci.py +++ b/ansible_collections/cisco/aci/plugins/doc_fragments/aci.py @@ -18,7 +18,6 @@ options: - IP Address or hostname of APIC resolvable by Ansible control host. - If the value is not specified in the task, the value of environment variable C(ACI_HOST) will be used instead. type: str - required: true aliases: [ hostname ] port: description: @@ -30,8 +29,8 @@ options: description: - The username to use for authentication. - If the value is not specified in the task, the value of environment variables C(ACI_USERNAME) or C(ANSIBLE_NET_USERNAME) will be used instead. + - The default value is admin. type: str - default: admin aliases: [ user ] password: description: @@ -69,27 +68,27 @@ options: description: - The socket level timeout in seconds. - If the value is not specified in the task, the value of environment variable C(ACI_TIMEOUT) will be used instead. + - The default value is 30. type: int - default: 30 use_proxy: description: - If C(false), it will not use a proxy, even if one is defined in an environment variable on the target hosts. - If the value is not specified in the task, the value of environment variable C(ACI_USE_PROXY) will be used instead. + - The default value is true. type: bool - default: true use_ssl: description: - If C(false), an HTTP connection will be used instead of the default HTTPS connection. - If the value is not specified in the task, the value of environment variable C(ACI_USE_SSL) will be used instead. + - The default value is true when the connection is local. type: bool - default: true validate_certs: description: - If C(false), SSL certificates will not be validated. - This should only set to C(false) when used on personally controlled sites using self-signed certificates. - If the value is not specified in the task, the value of environment variable C(ACI_VALIDATE_CERTS) will be used instead. + - The default value is true. type: bool - default: true output_path: description: - Path to a file that will be used to dump the ACI JSON configuration objects generated by the module. diff --git a/ansible_collections/cisco/aci/plugins/httpapi/aci.py b/ansible_collections/cisco/aci/plugins/httpapi/aci.py new file mode 100644 index 000000000..a0474576a --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/httpapi/aci.py @@ -0,0 +1,340 @@ +# Copyright (c) 2020 Cisco and/or its affiliates. +# Copyright: (c) 2020, Shreyas Srish (@shrsr) <ssrish@cisco.com> +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) +# +# 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 + +DOCUMENTATION = """ +--- +name: aci +author: +- Shreyas Srish (@shrsr) +short_description: Ansible ACI HTTPAPI Plugin. +description: + - This ACI plugin provides the HTTPAPI methods needed to initiate + a connection to the APIC, send API requests and process the + response from the controller. +""" + +import ast +import base64 +import json +import os +import re + +from ansible.module_utils._text import to_text, to_native +from ansible.module_utils.connection import ConnectionError +from ansible.plugins.httpapi import HttpApiBase +from copy import copy, deepcopy + +# Optional, only used for APIC signature-based authentication +try: + from OpenSSL.crypto import FILETYPE_PEM, load_privatekey, sign + + HAS_OPENSSL = True +except ImportError: + HAS_OPENSSL = False + +# Signature-based authentication using cryptography +try: + from cryptography.hazmat.primitives import serialization, hashes + from cryptography.hazmat.primitives.asymmetric import padding + from cryptography.hazmat.backends import default_backend + + HAS_CRYPTOGRAPHY = True +except ImportError: + HAS_CRYPTOGRAPHY = False + +CONNECTION_MAP = {"username": "remote_user", "timeout": "persistent_command_timeout"} +RESET_KEYS = ["username", "password", "port"] +CONNECTION_KEYS = RESET_KEYS + ["timeout", "use_proxy", "use_ssl", "validate_certs"] + + +class HttpApi(HttpApiBase): + def __init__(self, *args, **kwargs): + super(HttpApi, self).__init__(*args, **kwargs) + self.params = None + self.result = {} + self.backup_hosts = None + self.connection_error_check = False + self.connection_parameters = {} + self.current_host = None + self.provided_hosts = None + self.inventory_hosts = None + + def set_params(self, params): + self.params = params + + # Login function is executed until connection to a host is established or until all the hosts in the list are exhausted + def login(self, username, password): + """Log in to APIC""" + # Perform login request + self.connection.queue_message("debug", "Establishing login for {0} to {1}".format(username, self.connection.get_option("host"))) + method = "POST" + path = "/api/aaaLogin.json" + payload = {"aaaUser": {"attributes": {"name": username, "pwd": password}}} + data = json.dumps(payload) + self.connection._connected = True + try: + response, response_data = self.connection.send(path, data, method=method) + response_value = self._get_response_value(response_data) + self.connection._auth = { + "Cookie": "APIC-Cookie={0}".format(self._response_to_json(response_value).get("imdata")[0]["aaaLogin"]["attributes"]["token"]) + } + self.connection.queue_message("debug", "Connection to {0} was successful".format(self.connection.get_option("host"))) + except Exception as exc_login: + self.connection._connected = False + exc_login.path = path + raise + + def logout(self): + method = "POST" + path = "/api/aaaLogout.json" + payload = {"aaaUser": {"attributes": {"name": self.connection.get_option("remote_user")}}} + data = json.dumps(payload) + try: + response, response_data = self.connection.send(path, data, method=method) + except Exception as exc_logout: + msg = "Error on attempt to logout from APIC. {0}".format(exc_logout) + raise ConnectionError(self._return_info("", method, path, msg)) + self.connection._auth = None + self._verify_response(response, method, path, response_data) + + def set_parameters(self): + connection_parameters = {} + for key in CONNECTION_KEYS: + value = self.params.get(key) if self.params.get(key) is not None else self.connection.get_option(CONNECTION_MAP.get(key, key)) + if key == "username" and value is None: + value = "admin" + self.connection.set_option(CONNECTION_MAP.get(key, key), value) + if key == "timeout" and self.connection.get_option("persistent_connect_timeout") <= value: + self.connection.set_option("persistent_connect_timeout", value + 30) + + connection_parameters[key] = value + if self.connection_parameters and value != self.connection_parameters.get(key) and key in RESET_KEYS: + self.connection._connected = False + self.connection.queue_message("debug", "Re-setting connection due to change in the {0}".format(key)) + + if self.params.get("private_key") is not None: + self.connection.set_option("session_key", None) + connection_parameters["certificate_name"] = self.params.get("certificate_name") + connection_parameters["private_key"] = self.params.get("private_key") + elif self.connection.get_option("session_key") is not None and self.params.get("password") is None: + connection_parameters["certificate_name"] = list(self.connection.get_option("session_key").keys())[0] + connection_parameters["private_key"] = list(self.connection.get_option("session_key").values())[0] + else: + if self.connection_parameters.get("private_key") is not None: + self.connection._connected = False + self.connection.queue_message( + "debug", "Re-setting connection due to change from private/session key authentication to password authentication" + ) + self.connection.set_option("session_key", None) + connection_parameters["private_key"] = None + connection_parameters["certificate_name"] = None + + if self.connection_parameters != connection_parameters: + self.connection_parameters = copy(connection_parameters) + + self.set_hosts() + + def set_hosts(self): + if self.params.get("host") is not None: + hosts = ast.literal_eval(self.params.get("host")) if "[" in self.params.get("host") else self.params.get("host").split(",") + else: + if self.inventory_hosts is None: + self.inventory_hosts = re.sub(r"[[\]]", "", self.connection.get_option("host")).split(",") + hosts = self.inventory_hosts + + if self.provided_hosts is None: + self.provided_hosts = deepcopy(hosts) + self.connection.queue_message("debug", "Provided Hosts: {0}".format(self.provided_hosts)) + self.backup_hosts = deepcopy(hosts) + self.current_host = self.backup_hosts.pop(0) + self.connection.queue_message("debug", "Initializing operation on {0}".format(self.current_host)) + elif self.provided_hosts != hosts: + self.provided_hosts = deepcopy(hosts) + self.connection.queue_message("debug", "Provided Hosts have changed: {0}".format(self.provided_hosts)) + self.backup_hosts = deepcopy(hosts) + try: + self.backup_hosts.pop(self.backup_hosts.index(self.current_host)) + self.connection.queue_message("debug", "Connected host {0} found in the provided hosts. Continuing with it.".format(self.current_host)) + except Exception: + self.current_host = self.backup_hosts.pop(0) + self.connection._connected = False + self.connection.queue_message("debug", "Initializing operation on {0}".format(self.current_host)) + self.connection.set_option("host", self.current_host) + + # One API call is made via each call to send_request from aci.py in module_utils + # As long as a host is active in the list the API call will go through + def send_request(self, method, path, data): + """This method handles all APIC REST API requests other than login""" + + self.set_parameters() + + if self.connection_parameters.get("private_key") is not None: + try: + self.connection._auth = {"Cookie": "{0}".format(self.cert_auth(method, path, data).get("Cookie"))} + self.connection._connected = True + except Exception as exc_response: + self.connection._connected = False + return self._return_info("", method, self.validate_url(self.connection._url + path), str(exc_response)) + + try: + if self.connection._connected is False: + self.login(self.connection.get_option("remote_user"), self.connection.get_option("password")) + self.connection.queue_message("debug", "Sending {0} request to {1}".format(method, self.connection._url + path)) + response, response_data = self.connection.send(path, data, method=method) + self.connection.queue_message( + "debug", "Received response from {0} for {1} operation with HTTP: {2}".format(self.connection.get_option("host"), method, response.getcode()) + ) + except Exception as exc_response: + self.connection.queue_message("debug", "Connection to {0} has failed: {1}".format(self.connection.get_option("host"), exc_response)) + if len(self.backup_hosts) == 0: + self.provided_hosts = None + self.connection._connected = False + error = dict( + code=-1, text="No hosts left in the cluster to continue operation! Error on final host {0}".format(self.connection.get_option("host")) + ) + if "path" in dir(exc_response): + path = exc_response.path + return self._return_info("", method, self.validate_url(self.connection._url + path), str(exc_response), error=error) + else: + self.current_host = self.backup_hosts.pop(0) + self.connection.queue_message("debug", "Switching host from {0} to {1}".format(self.connection.get_option("host"), self.current_host)) + self.connection.set_option("host", self.current_host) + # recurse through function for retrying the request + return self.send_request(method, path, data) + # return statement executed upon each successful response from the request function + return self._verify_response(response, method, path, response_data) + + # Built-in-function + def handle_httperror(self, exc): + self.connection.queue_message("debug", "Failed to receive response from {0} with {1}".format(self.connection.get_option("host"), exc)) + if exc.code == 401: + raise ConnectionError(exc) + elif exc.code == 403 and self.connection_parameters.get("private_key") is None: + self.connection._auth = None + self.login(self.connection.get_option("remote_user"), self.connection.get_option("password")) + return True + return exc + + def validate_url(self, url): + validated_url = re.match(r"^.*?\.json|^.*?\.xml", url).group(0) + if self.connection_parameters.get("port") is None: + return validated_url.replace(re.match(r"(https?:\/\/.*)(:\d*)\/?(.*)", url).group(2), "") + else: + return validated_url + + def _verify_response(self, response, method, path, response_data): + """Process the return code and response object from APIC""" + response_value = self._get_response_value(response_data) + response_code = response.getcode() + path = self.validate_url(response.url) + # Response check to remain consistent with fetch_url's response + if str(response) == "HTTP Error 400: Bad Request": + msg = "{0}".format(response) + else: + msg = "{0} ({1} bytes)".format(response.msg, len(response_value)) + return self._return_info(response_code, method, path, msg, respond_data=response_value) + + def _get_response_value(self, response_data): + """Extract string data from response_data returned from APIC""" + return to_text(response_data.getvalue()) + + def _response_to_json(self, response_text): + """Convert response_text to json format""" + try: + return json.loads(response_text) if response_text else {} + # JSONDecodeError only available on Python 3.5+ + except Exception: + return "Invalid JSON response: {0}".format(response_text) + + def _return_info(self, response_code, method, path, msg, respond_data=None, error=None): + """Format success/error data and return with consistent format""" + info = {} + info["status"] = response_code + info["method"] = method + info["url"] = path + info["msg"] = msg + if error is not None: + info["error"] = error + else: + info["error"] = {} + # Response check to trigger key error if response_data is invalid + if respond_data is not None: + info["body"] = respond_data + return info + + def cert_auth(self, method, path, payload=""): + """Perform APIC signature-based authentication, not the expected SSL client certificate authentication.""" + + if payload is None: + payload = "" + + headers = dict() + + try: + if HAS_CRYPTOGRAPHY: + key = self.connection_parameters.get("private_key").encode() + sig_key = serialization.load_pem_private_key( + key, + password=None, + backend=default_backend(), + ) + else: + sig_key = load_privatekey(FILETYPE_PEM, self.connection_parameters.get("private_key")) + except Exception: + if os.path.exists(self.connection_parameters.get("private_key")): + try: + permission = "r" + if HAS_CRYPTOGRAPHY: + permission = "rb" + with open(self.connection_parameters.get("private_key"), permission) as fh: + private_key_content = fh.read() + except Exception: + raise ConnectionError("Cannot open private key file {0}".format(self.connection_parameters.get("private_key"))) + try: + if HAS_CRYPTOGRAPHY: + sig_key = serialization.load_pem_private_key(private_key_content, password=None, backend=default_backend()) + else: + sig_key = load_privatekey(FILETYPE_PEM, private_key_content) + except Exception: + raise ConnectionError("Cannot load private key file {0}".format(self.connection_parameters.get("private_key"))) + if self.connection_parameters.get("certificate_name") is None: + self.connection_parameters["certificate_name"] = os.path.basename(os.path.splitext(self.connection_parameters.get("private_key"))[0]) + else: + raise ConnectionError( + "Provided private key {0} does not appear to be a private key or provided file does not exist.".format( + self.connection_parameters.get("private_key") + ) + ) + if self.connection_parameters.get("certificate_name") is None: + self.connection_parameters["certificate_name"] = self.connection.get_option("remote_user") + sig_request = method + path + payload + if HAS_CRYPTOGRAPHY: + sig_signature = sig_key.sign(sig_request.encode(), padding.PKCS1v15(), hashes.SHA256()) + else: + sig_signature = sign(sig_key, sig_request, "sha256") + sig_dn = "uni/userext/user-{0}/usercert-{1}".format(self.connection.get_option("remote_user"), self.connection_parameters.get("certificate_name")) + headers["Cookie"] = ( + "APIC-Certificate-Algorithm=v1.0; " + + "APIC-Certificate-DN={0}; ".format(sig_dn) + + "APIC-Certificate-Fingerprint=fingerprint; " + + "APIC-Request-Signature={0}".format(to_native(base64.b64encode(sig_signature))) + ) + return headers diff --git a/ansible_collections/cisco/aci/plugins/module_utils/aci.py b/ansible_collections/cisco/aci/plugins/module_utils/aci.py index 5d95e5c1e..9c6e2db2d 100644 --- a/ansible_collections/cisco/aci/plugins/module_utils/aci.py +++ b/ansible_collections/cisco/aci/plugins/module_utils/aci.py @@ -13,6 +13,8 @@ # Copyright: (c) 2019, Rob Huelga (@RobW3LGA) # Copyright: (c) 2020, Lionel Hercot (@lhercot) <lhercot@cisco.com> # Copyright: (c) 2020, Anvitha Jain (@anvitha-jain) <anvjain@cisco.com> +# Copyright: (c) 2023, Gaspard Micol (@gmicol) <gmicol@cisco.com> +# Copyright: (c) 2023, Shreyas Srish (@shrsr) <ssrish@cisco.com> # All rights reserved. # Redistribution and use in source and binary forms, with or without modification, @@ -46,6 +48,7 @@ from copy import deepcopy from ansible.module_utils.urls import fetch_url from ansible.module_utils._text import to_bytes, to_native from ansible.module_utils.basic import env_fallback +from ansible.module_utils.connection import Connection # Optional, only used for APIC signature-based authentication try: @@ -81,19 +84,24 @@ try: except ImportError: HAS_XMLJSON_COBRA = False +try: + from ansible.module_utils.six.moves.urllib.parse import urlparse + + HAS_URLPARSE = True +except Exception: + HAS_URLPARSE = False + def aci_argument_spec(): return dict( host=dict( type="str", - required=True, aliases=["hostname"], fallback=(env_fallback, ["ACI_HOST"]), ), port=dict(type="int", required=False, fallback=(env_fallback, ["ACI_PORT"])), username=dict( type="str", - default="admin", aliases=["user"], fallback=(env_fallback, ["ACI_USERNAME", "ANSIBLE_NET_USERNAME"]), ), @@ -121,10 +129,10 @@ def aci_argument_spec(): choices=["debug", "info", "normal"], fallback=(env_fallback, ["ACI_OUTPUT_LEVEL"]), ), - timeout=dict(type="int", default=30, fallback=(env_fallback, ["ACI_TIMEOUT"])), - use_proxy=dict(type="bool", default=True, fallback=(env_fallback, ["ACI_USE_PROXY"])), - use_ssl=dict(type="bool", default=True, fallback=(env_fallback, ["ACI_USE_SSL"])), - validate_certs=dict(type="bool", default=True, fallback=(env_fallback, ["ACI_VALIDATE_CERTS"])), + timeout=dict(type="int", fallback=(env_fallback, ["ACI_TIMEOUT"])), + use_proxy=dict(type="bool", fallback=(env_fallback, ["ACI_USE_PROXY"])), + use_ssl=dict(type="bool", fallback=(env_fallback, ["ACI_USE_SSL"])), + validate_certs=dict(type="bool", fallback=(env_fallback, ["ACI_VALIDATE_CERTS"])), output_path=dict(type="str", fallback=(env_fallback, ["ACI_OUTPUT_PATH"])), ) @@ -252,6 +260,66 @@ def route_control_profile_spec(): ) +def destination_epg_spec(): + return dict( + tenant=dict(type="str", required=True, aliases=["tenant_name"]), + ap=dict(type="str", required=True, aliases=["ap_name", "app_profile", "app_profile_name"]), + epg=dict(type="str", required=True, aliases=["epg_name"]), + source_ip=dict(type="str", required=True), + destination_ip=dict(type="str", required=True), + span_version=dict(type="str", choices=["version_1", "version_2"]), + version_enforced=dict(type="bool"), + flow_id=dict(type="int"), + ttl=dict(type="int"), + mtu=dict(type="int"), + dscp=dict( + type="str", + choices=[ + "CS0", + "CS1", + "CS2", + "CS3", + "CS4", + "CS5", + "CS6", + "CS7", + "EF", + "VA", + "AF11", + "AF12", + "AF13", + "AF21", + "AF22", + "AF23", + "AF31", + "AF32", + "AF33", + "AF41", + "AF42", + "AF43", + "unspecified", + ], + ), + ) + + +def ospf_spec(): + return dict( + area_cost=dict(type="int"), + area_ctrl=dict(type="list", elements="str", choices=["redistribute", "summary", "suppress-fa", "unspecified"]), + area_id=dict(type="str"), + area_type=dict(type="str", choices=["nssa", "regular", "stub"]), + description=dict(type="str", aliases=["descr"]), + multipod_internal=dict(type="str", choices=["no", "yes"]), + name_alias=dict(type="str"), + ) + + +def integrate_url(httpapi_url, local_path): + parse_url = urlparse(httpapi_url) + return {"protocol": parse_url.scheme, "host": parse_url.netloc, "path": local_path} + + class ACIModule(object): def __init__(self, module): self.module = module @@ -259,6 +327,7 @@ class ACIModule(object): self.result = dict(changed=False) self.headers = dict() self.child_classes = set() + self.connection = None # error output self.error = dict(code=None, text=None) @@ -277,9 +346,11 @@ class ACIModule(object): self.obj_filter = None self.method = None self.path = None + self.parent_path = None self.response = None self.status = None self.url = None + self.httpapi_logs = list() # aci_rest output self.imdata = None @@ -288,21 +359,30 @@ class ACIModule(object): # Ensure protocol is set self.define_protocol() + # Set Connection plugin + self.set_connection() + if self.module._debug: self.module.warn("Enable debug output because ANSIBLE_DEBUG was set.") self.params["output_level"] = "debug" + if self.params.get("port") is not None: + self.base_url = "{protocol}://{host}:{port}".format_map(self.params) + else: + self.base_url = "{protocol}://{host}".format_map(self.params) + if self.params.get("private_key"): # Perform signature-based authentication, no need to log on separately if not HAS_CRYPTOGRAPHY and not HAS_OPENSSL: self.module.fail_json(msg="Cannot use signature-based authentication because cryptography (preferred) or pyopenssl are not available") elif self.params.get("password") is not None: self.module.warn("When doing ACI signatured-based authentication, providing parameter 'password' is not required") - elif self.params.get("password"): - # Perform password-based authentication, log on using password - self.login() - else: - self.module.fail_json(msg="Either parameter 'password' or 'private_key' is required for authentication") + elif self.connection is None: + if self.params.get("password"): + # Perform password-based authentication, log on using password + self.login() + else: + self.module.fail_json(msg="Either parameter 'password' or 'private_key' is required for authentication") def boolean(self, value, true="yes", false="no"): """Return an acceptable value back""" @@ -335,39 +415,26 @@ class ACIModule(object): """Set protocol based on use_ssl parameter""" # Set protocol for further use - self.params["protocol"] = "https" if self.params.get("use_ssl", True) else "http" + self.params["protocol"] = "https" if self.params.get("use_ssl") or self.params.get("use_ssl") is None else "http" - def define_method(self): - """Set method based on state parameter""" - - # Set method for further use - state_map = dict(absent="delete", present="post", query="get") - self.params["method"] = state_map.get(self.params.get("state")) + def set_connection(self): + if self.connection is None and self.module._socket_path: + self.connection = Connection(self.module._socket_path) def login(self): """Log in to APIC""" # Perform login request - if self.params.get("port") is not None: - url = "%(protocol)s://%(host)s:%(port)s/api/aaaLogin.json" % self.params - else: - url = "%(protocol)s://%(host)s/api/aaaLogin.json" % self.params + url = "{0}/api/aaaLogin.json".format(self.base_url) payload = { "aaaUser": { "attributes": { - "name": self.params.get("username"), + "name": "admin" if self.params.get("username") is None else self.params.get("username"), "pwd": self.params.get("password"), } } } - resp, auth = fetch_url( - self.module, - url, - data=json.dumps(payload), - method="POST", - timeout=self.params.get("timeout"), - use_proxy=self.params.get("use_proxy"), - ) + resp, auth = self.api_call("POST", url, data=json.dumps(payload), return_response=True) # Handle APIC response if auth.get("status") != 200: @@ -376,10 +443,10 @@ class ACIModule(object): try: # APIC error self.response_json(auth["body"]) - self.fail_json(msg="Authentication failed: %(code)s %(text)s" % self.error) + self.fail_json(msg="Authentication failed: {code} {text}".format_map(self.error)) except KeyError: # Connection error - self.fail_json(msg="Connection failed for %(url)s. %(msg)s" % auth) + self.fail_json(msg="Connection failed for {url}. {msg}".format_map(auth)) # Retain cookie for later use self.headers["Cookie"] = resp.headers.get("Set-Cookie") @@ -418,7 +485,7 @@ class ACIModule(object): with open(self.params.get("private_key"), permission) as fh: private_key_content = fh.read() except Exception: - self.module.fail_json(msg="Cannot open private key file '%(private_key)s'." % self.params) + self.module.fail_json(msg="Cannot open private key file '{private_key}'.".format_map(self.params)) try: if HAS_CRYPTOGRAPHY: sig_key = serialization.load_pem_private_key( @@ -429,26 +496,28 @@ class ACIModule(object): else: sig_key = load_privatekey(FILETYPE_PEM, private_key_content) except Exception: - self.module.fail_json(msg="Cannot load private key file '%(private_key)s'." % self.params) + self.module.fail_json(msg="Cannot load private key file '{private_key}'.".format_map(self.params)) if self.params.get("certificate_name") is None: self.params["certificate_name"] = os.path.basename(os.path.splitext(self.params.get("private_key"))[0]) else: - self.module.fail_json(msg="Provided private key '%(private_key)s' does not appear to be a private key." % self.params) + self.module.fail_json( + msg="Provided private key {private_key} does not appear to be a private key or provided file does not exist.".format_map(self.params) + ) if self.params.get("certificate_name") is None: - self.params["certificate_name"] = self.params.get("username") + self.params["certificate_name"] = "admin" if self.params.get("username") is None else self.params.get("username") # NOTE: ACI documentation incorrectly adds a space between method and path sig_request = method + path + payload if HAS_CRYPTOGRAPHY: sig_signature = sig_key.sign(sig_request.encode(), padding.PKCS1v15(), hashes.SHA256()) else: sig_signature = sign(sig_key, sig_request, "sha256") - sig_dn = "uni/userext/user-%(username)s/usercert-%(certificate_name)s" % self.params + sig_dn = "uni/userext/user-{username}/usercert-{certificate_name}".format_map(self.params) self.headers["Cookie"] = ( "APIC-Certificate-Algorithm=v1.0; " - + "APIC-Certificate-DN=%s; " % sig_dn + + "APIC-Certificate-DN={0}; ".format(sig_dn) + "APIC-Certificate-Fingerprint=fingerprint; " - + "APIC-Request-Signature=%s" % to_native(base64.b64encode(sig_signature)) + + "APIC-Request-Signature={0}".format(to_native(base64.b64encode(sig_signature))) ) def response_json(self, rawoutput): @@ -457,7 +526,7 @@ class ACIModule(object): jsondata = json.loads(rawoutput) except Exception as e: # Expose RAW output for troubleshooting - self.error = dict(code=-1, text="Unable to parse output as JSON, see 'raw' output. %s" % e) + self.error = dict(code=-1, text="Unable to parse output as JSON, see 'raw' output. {0}".format(e)) self.result["raw"] = rawoutput return @@ -479,7 +548,7 @@ class ACIModule(object): xmldata = cobra.data(xml) except Exception as e: # Expose RAW output for troubleshooting - self.error = dict(code=-1, text="Unable to parse output as XML, see 'raw' output. %s" % e) + self.error = dict(code=-1, text="Unable to parse output as XML, see 'raw' output. {0}".format(e)) self.result["raw"] = rawoutput return @@ -487,7 +556,7 @@ class ACIModule(object): self.imdata = xmldata.get("imdata", {}).get("children") if self.imdata is None: self.imdata = dict() - self.totalCount = int(xmldata.get("imdata", {}).get("attributes", {}).get("totalCount")) + self.totalCount = int(xmldata.get("imdata", {}).get("attributes", {}).get("totalCount", -1)) # Handle possible APIC error information self.response_error() @@ -502,100 +571,6 @@ class ACIModule(object): except (AttributeError, IndexError, KeyError): pass - def request(self, path, payload=None): - """Perform a REST request""" - - # Ensure method is set (only do this once) - self.define_method() - self.path = path - - if self.params.get("port") is not None: - self.url = "%(protocol)s://%(host)s:%(port)s/" % self.params + path.lstrip("/") - else: - self.url = "%(protocol)s://%(host)s/" % self.params + path.lstrip("/") - - # Sign and encode request as to APIC's wishes - if self.params.get("private_key"): - self.cert_auth(path=path, payload=payload) - - # Perform request - resp, info = fetch_url( - self.module, - self.url, - data=payload, - headers=self.headers, - method=self.params.get("method").upper(), - timeout=self.params.get("timeout"), - use_proxy=self.params.get("use_proxy"), - ) - - self.response = info.get("msg") - self.status = info.get("status") - - # Handle APIC response - if info.get("status") != 200: - try: - # APIC error - self.response_json(info["body"]) - self.fail_json(msg="APIC Error %(code)s: %(text)s" % self.error) - except KeyError: - # Connection error - self.fail_json(msg="Connection failed for %(url)s. %(msg)s" % info) - - self.response_json(resp.read()) - - def query(self, path): - """Perform a query with no payload""" - - self.path = path - - if self.params.get("port") is not None: - self.url = "%(protocol)s://%(host)s:%(port)s/" % self.params + path.lstrip("/") - else: - self.url = "%(protocol)s://%(host)s/" % self.params + path.lstrip("/") - - # Sign and encode request as to APIC's wishes - if self.params.get("private_key"): - self.cert_auth(path=path, method="GET") - - # Perform request - resp, query = fetch_url( - self.module, - self.url, - data=None, - headers=self.headers, - method="GET", - timeout=self.params.get("timeout"), - use_proxy=self.params.get("use_proxy"), - ) - - # Handle APIC response - if query.get("status") != 200: - self.response = query.get("msg") - self.status = query.get("status") - try: - # APIC error - self.response_json(query["body"]) - self.fail_json(msg="APIC Error %(code)s: %(text)s" % self.error) - except KeyError: - # Connection error - self.fail_json(msg="Connection failed for %(url)s. %(msg)s" % query) - - query = json.loads(resp.read()) - - return json.dumps(query.get("imdata"), sort_keys=True, indent=2) + "\n" - - def request_diff(self, path, payload=None): - """Perform a request, including a proper diff output""" - self.result["diff"] = dict() - self.result["diff"]["before"] = self.query(path) - self.request(path, payload=payload) - # TODO: Check if we can use the request output for the 'after' diff - self.result["diff"]["after"] = self.query(path) - - if self.result.get("diff", {}).get("before") != self.result.get("diff", {}).get("after"): - self.result["changed"] = True - # TODO: This could be designed to update existing keys def update_qs(self, params): """Append key-value pairs to self.filter_string""" @@ -832,15 +807,7 @@ class ACIModule(object): self.child_classes = set(child_classes) if subclass_5 is not None: - self._construct_url_6( - root_class, - subclass_1, - subclass_2, - subclass_3, - subclass_4, - subclass_5, - config_only, - ) + self._construct_url_6(root_class, subclass_1, subclass_2, subclass_3, subclass_4, subclass_5, config_only) elif subclass_4 is not None: self._construct_url_5(root_class, subclass_1, subclass_2, subclass_3, subclass_4, config_only) elif subclass_3 is not None: @@ -878,6 +845,7 @@ class ACIModule(object): if self.module.params.get("state") in ("absent", "present"): # State is absent or present self.path = "api/mo/uni/{0}.json".format(obj_rn) + self.parent_path = "api/mo/uni.json" if config_only: self.update_qs({"rsp-prop-include": "config-only"}) self.obj_filter = obj_filter @@ -904,6 +872,7 @@ class ACIModule(object): if self.module.params.get("state") in ("absent", "present"): # State is absent or present self.path = "api/mo/uni/{0}/{1}.json".format(parent_rn, obj_rn) + self.parent_path = "api/mo/uni/{0}.json".format(parent_rn) if config_only: self.update_qs({"rsp-prop-include": "config-only"}) self.obj_filter = obj_filter @@ -941,6 +910,7 @@ class ACIModule(object): if self.module.params.get("state") in ("absent", "present"): # State is absent or present self.path = "api/mo/uni/{0}/{1}/{2}.json".format(root_rn, parent_rn, obj_rn) + self.parent_path = "api/mo/uni/{0}/{1}.json".format(root_rn, parent_rn) if config_only: self.update_qs({"rsp-prop-include": "config-only"}) self.obj_filter = obj_filter @@ -1010,6 +980,7 @@ class ACIModule(object): if self.module.params.get("state") in ("absent", "present"): # State is absent or present self.path = "api/mo/uni/{0}/{1}/{2}/{3}.json".format(root_rn, sec_rn, parent_rn, obj_rn) + self.parent_path = "api/mo/uni/{0}/{1}/{2}.json".format(root_rn, sec_rn, parent_rn) if config_only: self.update_qs({"rsp-prop-include": "config-only"}) self.obj_filter = obj_filter @@ -1065,6 +1036,7 @@ class ACIModule(object): if self.module.params.get("state") in ("absent", "present"): # State is absent or present self.path = "api/mo/uni/{0}/{1}/{2}/{3}/{4}.json".format(root_rn, ter_rn, sec_rn, parent_rn, obj_rn) + self.parent_path = "api/mo/uni/{0}/{1}/{2}/{3}.json".format(root_rn, ter_rn, sec_rn, parent_rn) if config_only: self.update_qs({"rsp-prop-include": "config-only"}) self.obj_filter = obj_filter @@ -1182,37 +1154,9 @@ class ACIModule(object): if not self.existing: return - elif not self.module.check_mode: # Sign and encode request as to APIC's wishes - if self.params["private_key"]: - self.cert_auth(method="DELETE") - - resp, info = fetch_url( - self.module, - self.url, - headers=self.headers, - method="DELETE", - timeout=self.params.get("timeout"), - use_proxy=self.params.get("use_proxy"), - ) - - self.response = info.get("msg") - self.status = info.get("status") - self.method = "DELETE" - - # Handle APIC response - if info.get("status") == 200: - self.result["changed"] = True - self.response_json(resp.read()) - else: - try: - # APIC error - self.response_json(info["body"]) - self.fail_json(msg="APIC Error %(code)s: %(text)s" % self.error) - except KeyError: - # Connection error - self.fail_json(msg="Connection failed for %(url)s. %(msg)s" % info) + self.api_call("DELETE", self.url, None, return_response=False) else: self.result["changed"] = True self.method = "DELETE" @@ -1222,7 +1166,6 @@ class ACIModule(object): This method is used to get the difference between the proposed and existing configurations. Each module should call the get_existing method before this method, and add the proposed config to the module results using the module's config parameters. The new config will added to the self.result dictionary. - :param aci_class: Type str. This is the root dictionary key for the MO's configuration body, or the ACI class of the MO. """ @@ -1259,7 +1202,6 @@ class ACIModule(object): """ This method is used to get the difference between a proposed and existing child configs. The get_nested_config() method should be used to return the proposed and existing config portions of child. - :param child_class: Type str. The root class (dict key) for the child dictionary. :param proposed_child: Type dict. @@ -1284,7 +1226,6 @@ class ACIModule(object): """ This method is used to retrieve the updated child configs by comparing the proposed children configs against the objects existing children configs. - :param aci_class: Type str. This is the root dictionary key for the MO's configuration body, or the ACI class of the MO. :return: The list of updated child config dictionaries. None is returned if there are no changes to the child @@ -1338,40 +1279,13 @@ class ACIModule(object): """ uri = self.url + self.filter_string - # Sign and encode request as to APIC's wishes - if self.params.get("private_key"): - self.cert_auth(path=self.path + self.filter_string, method="GET") - - resp, info = fetch_url( - self.module, - uri, - headers=self.headers, - method="GET", - timeout=self.params.get("timeout"), - use_proxy=self.params.get("use_proxy"), - ) - self.response = info.get("msg") - self.status = info.get("status") - self.method = "GET" - - # Handle APIC response - if info.get("status") == 200: - self.existing = json.loads(resp.read())["imdata"] - else: - try: - # APIC error - self.response_json(info["body"]) - self.fail_json(msg="APIC Error %(code)s: %(text)s" % self.error) - except KeyError: - # Connection error - self.fail_json(msg="Connection failed for %(url)s. %(msg)s" % info) + self.api_call("GET", uri, data=None, return_response=False) @staticmethod def get_nested_config(proposed_child, existing_children): """ This method is used for stiping off the outer layers of the child dictionaries so only the configuration key, value pairs are returned. - :param proposed_child: Type dict. The dictionary that represents the child config. :param existing_children: Type list. @@ -1401,7 +1315,6 @@ class ACIModule(object): def get_nested_children(proposed_child, existing_children): """ This method is used for stiping off the outer layers of the child dictionaries so only the children are returned. - :param proposed_child: Type dict. The dictionary that represents the child config. :param existing_children: Type list. @@ -1436,7 +1349,6 @@ class ACIModule(object): This method is used to dynamically build the proposed configuration dictionary from the config related parameters passed into the module. All values that were not passed values from the playbook task will be removed so as to not inadvertently change configurations. - :param aci_class: Type str This is the root dictionary key for the MO's configuration body, or the ACI class of the MO. :param class_config: Type dict @@ -1474,7 +1386,7 @@ class ACIModule(object): if children: self.proposed[aci_class].update(dict(children=children)) - def post_config(self): + def post_config(self, parent_class=None): """ This method is used to handle the logic when the modules state is equal to present. The method only pushes a change if the object has differences than what exists on the APIC, and if check_mode is False. A successful change will mark the @@ -1484,35 +1396,14 @@ class ACIModule(object): return elif not self.module.check_mode: # Sign and encode request as to APIC's wishes - if self.params.get("private_key"): - self.cert_auth(method="POST", payload=json.dumps(self.config)) - - resp, info = fetch_url( - self.module, - self.url, - data=json.dumps(self.config), - headers=self.headers, - method="POST", - timeout=self.params.get("timeout"), - use_proxy=self.params.get("use_proxy"), - ) - - self.response = info.get("msg") - self.status = info.get("status") - self.method = "POST" - - # Handle APIC response - if info.get("status") == 200: - self.result["changed"] = True - self.response_json(resp.read()) - else: - try: - # APIC error - self.response_json(info["body"]) - self.fail_json(msg="APIC Error %(code)s: %(text)s" % self.error) - except KeyError: - # Connection error - self.fail_json(msg="Connection failed for %(url)s. %(msg)s" % info) + url = self.url + if parent_class is not None: + if self.params.get("port") is not None: + url = "{protocol}://{host}:{port}/{path}".format(path=self.parent_path, **self.module.params) + else: + url = "{protocol}://{host}/{path}".format(path=self.parent_path, **self.module.params) + self.config = {parent_class: {"attributes": {}, "children": [self.config]}} + self.api_call("POST", url, json.dumps(self.config), return_response=False) else: self.result["changed"] = True self.method = "POST" @@ -1539,6 +1430,8 @@ class ACIModule(object): self.result["response"] = self.response self.result["status"] = self.status self.result["url"] = self.url + if self.httpapi_logs is not None: + self.result["httpapi_logs"] = self.httpapi_logs if self.stdout: self.result["stdout"] = self.stdout @@ -1567,12 +1460,13 @@ class ACIModule(object): if self.error.get("code") is not None and self.error.get("text") is not None: self.result["error"] = self.error + if self.stdout: + self.result["stdout"] = self.stdout + if "state" in self.params: if self.params.get("state") in ("absent", "present"): if self.params.get("output_level") in ("debug", "info"): self.result["previous"] = self.existing - if self.stdout: - self.result["stdout"] = self.stdout # Return the gory details when we need it if self.params.get("output_level") == "debug": @@ -1589,6 +1483,8 @@ class ACIModule(object): self.result["response"] = self.response self.result["status"] = self.status self.result["url"] = self.url + if self.httpapi_logs is not None: + self.result["httpapi_logs"] = self.httpapi_logs if "state" in self.params: if self.params.get("output_level") in ("debug", "info"): @@ -1600,7 +1496,7 @@ class ACIModule(object): def dump_json(self): if self.params.get("state") in ("absent", "present"): - dn_path = (self.url).split("/mo/")[-1] + dn_path = self.url.rsplit("/mo/", maxsplit=1)[-1] if dn_path[-5:] == ".json": dn_path = dn_path[:-5] mo = {} @@ -1622,3 +1518,63 @@ class ACIModule(object): with open(output_path, "a") as output_file: if self.result.get("changed") is True: json.dump([mo], output_file) + + def parsed_url_path(self, url): + if not HAS_URLPARSE: + self.fail_json(msg="urlparse is not installed") + parse_result = urlparse(url) + if parse_result.query == "": + return parse_result.path + else: + return parse_result.path + "?" + parse_result.query + + def api_call(self, method, url, data=None, return_response=False): + resp = None + if self.connection is not None: + self.connection.set_params(self.params) + info = self.connection.send_request(method, self.parsed_url_path(url), data) + self.url = "{protocol}://{host}/{path}".format_map(integrate_url(info.get("url"), self.path)) + self.error = info.get("error") + self.httpapi_logs.extend(self.connection.pop_messages()) + else: + if self.params.get("private_key"): + self.cert_auth(path=self.parsed_url_path(url), payload=data, method=method) + resp, info = fetch_url( + self.module, + url, + data=data, + headers=self.headers, + method=method, + timeout=30 if self.params.get("timeout") is None else self.params.get("timeout"), + use_proxy=True if self.params.get("use_proxy") is None else self.params.get("use_proxy"), + ) + + self.response = info.get("msg") + self.status = info.get("status") + self.method = method + + if return_response: + return resp, info + else: + # Handle APIC response + if info.get("status") == 200: + if method == "POST" or method == "DELETE": + self.result["changed"] = True + try: + if method == "GET": + self.existing = json.loads(resp.read())["imdata"] + else: + self.response_json(resp.read()) + except AttributeError: + if method == "GET": + self.existing = json.loads(info.get("body"))["imdata"] + else: + self.response_json(info.get("body")) + else: + try: + # APIC error + self.response_json(info["body"]) + self.fail_json(msg="APIC Error {code}: {text}".format_map(self.error)) + except KeyError: + # Connection error + self.fail_json(msg="Connection failed for {url}. {msg}".format_map(info)) diff --git a/ansible_collections/cisco/aci/plugins/module_utils/constants.py b/ansible_collections/cisco/aci/plugins/module_utils/constants.py index 72d7585a5..165b63431 100644 --- a/ansible_collections/cisco/aci/plugins/module_utils/constants.py +++ b/ansible_collections/cisco/aci/plugins/module_utils/constants.py @@ -1,3 +1,100 @@ VALID_IP_PROTOCOLS = ["eigrp", "egp", "icmp", "icmpv6", "igmp", "igp", "l2tp", "ospfigp", "pim", "tcp", "udp", "unspecified"] -FILTER_PORT_MAPPING = {"443": "https", "25": "smtp", "80": "http", "53": "dns", "22": "ssh", "110": "pop3", "554": "rtsp", "20": "ftpData", "ftp": "ftpData"} +FILTER_PORT_MAPPING = {"443": "https", "25": "smtp", "80": "http", "53": "dns", "110": "pop3", "554": "rtsp", "20": "ftpData", "ftp": "ftpData"} + +VALID_ETHER_TYPES = ["arp", "fcoe", "ip", "ipv4", "ipv6", "mac_security", "mpls_ucast", "trill", "unspecified"] + +# mapping dicts are used to normalize the proposed data to what the APIC expects, which will keep diffs accurate +ARP_FLAG_MAPPING = dict(arp_reply="reply", arp_request="req", unspecified="unspecified") + +# ICMPv4 Types Mapping +ICMP4_MAPPING = dict( + dst_unreachable="dst-unreach", echo="echo", echo_reply="echo-rep", src_quench="src-quench", time_exceeded="time-exceeded", unspecified="unspecified" +) + +# ICMPv6 Types Mapping +ICMP6_MAPPING = dict( + dst_unreachable="dst-unreach", + echo_request="echo-req", + echo_reply="echo-rep", + neighbor_advertisement="nbr-advert", + neighbor_solicitation="nbr-solicit", + redirect="redirect", + time_exceeded="time-exceeded", + unspecified="unspecified", +) + +TCP_FLAGS = dict(acknowledgment="ack", established="est", finish="fin", reset="rst", synchronize="syn", unspecified="unspecified") + +SUBNET_CONTROL_MAPPING = {"nd_ra_prefix": "nd", "no_default_gateway": "no-default-gateway", "querier_ip": "querier", "unspecified": ""} +SUBNET_CONTROL_MAPPING_BD_SUBNET = {"nd_ra": "nd", "no_gw": "no-default-gateway", "querier_ip": "querier", "unspecified": ""} + +NODE_TYPE_MAPPING = {"tier_2": "tier-2-leaf", "remote": "remote-leaf-wan", "virtual": "virtual", "unspecified": "unspecified"} + +SPAN_DIRECTION_MAP = {"incoming": "in", "outgoing": "out", "both": "both"} + +MATCH_TYPE_MAPPING = {"all": "All", "at_least_one": "AtleastOne", "at_most_one": "AtmostOne", "none": "None"} + +IPV4_REGEX = r"^((25[0-5]|(2[0-4]|1\d|[1-9]|)\d)\.?\b){4}$" + +VM_PROVIDER_MAPPING = dict( + cloudfoundry="CloudFoundry", + kubernetes="Kubernetes", + microsoft="Microsoft", + openshift="OpenShift", + openstack="OpenStack", + redhat="Redhat", + vmware="VMware", +) + +MATCH_TYPE_GROUP_MAPPING = {"all": "ALL", "all_in_pod": "ALL_IN_POD", "range": "range"} + +MATCH_FC_FILL_PATTERN_MAPPING = {"arbff": "ARBFF", "idle": "IDLE"} + +MATCH_FIRMWARE_NODES_TYPE_MAPPING = { + "c_apic_patch": "cApicPatch", + "catalog": "catalog", + "config": "config", + "controller": "controller", + "controller_patch": "controllerPatch", + "plugin": "plugin", + "plugin_package": "pluginPackage", + "switch": "switch", + "switch_patch": "switchPatch", + "vpod": "vpod", +} + +MATCH_TRIGGER_MAPPING = { + "trigger": "trigger", + "trigger_immediate": "trigger-immediate", + "triggered": "triggered", + "untriggered": "untriggered", +} + +INTERFACE_POLICY_FC_SPEED_LIST = ["auto", "unknown", "2G", "4G", "8G", "16G", "32G"] + +MATCH_RUN_MODE_MAPPING = dict( + pause_always_between_sets="pauseAlwaysBetweenSets", + pause_only_on_failures="pauseOnlyOnFailures", + pause_never="pauseNever", +) + +MATCH_NOTIFY_CONDITION_MAPPING = dict( + notify_always_between_sets="notifyAlwaysBetweenSets", + notify_never="notifyNever", + notify_only_on_failures="notifyOnlyOnFailures", +) + +MATCH_SMU_OPERATION_MAPPING = dict(smu_install="smuInstall", smu_uninstall="smuUninstall") + +MATCH_SMU_OPERATION_FLAGS_MAPPING = dict(smu_reload_immediate="smuReloadImmediate", smu_reload_skip="smuReloadSkip") + +MATCH_BEST_PATH_CONTROL_MAPPING = dict(enable="asPathMultipathRelax", disable="") + +MATCH_GRACEFUL_RESTART_CONTROLS_MAPPING = dict(helper="helper", complete="") + +EP_LOOP_PROTECTION_ACTION_MAPPING = {"bd": "bd-learn-disable", "port": "port-disable"} + +FABRIC_POD_SELECTOR_TYPE_MAPPING = dict(all="ALL", range="range") + +TLS_MAPPING = {"tls_v1.0": "TLSv1", "tls_v1.1": "TLSv1.1", "tls_v1.2": "TLSv1.2"} diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_access_port_to_interface_policy_leaf_profile.py b/ansible_collections/cisco/aci/plugins/modules/aci_access_port_to_interface_policy_leaf_profile.py index 0768e4d0c..7b5c896ad 100644 --- a/ansible_collections/cisco/aci/plugins/modules/aci_access_port_to_interface_policy_leaf_profile.py +++ b/ansible_collections/cisco/aci/plugins/modules/aci_access_port_to_interface_policy_leaf_profile.py @@ -153,7 +153,7 @@ EXAMPLES = r""" state: present delegate_to: localhost -- name: Associate an interface access port selector to an Interface Policy Leaf Profile (w/o policy group) (check if this works) +- name: Associate an interface access port selector to an Interface Policy Leaf Profile (w/o policy group) cisco.aci.aci_access_port_to_interface_policy_leaf_profile: host: apic username: admin @@ -224,6 +224,38 @@ EXAMPLES = r""" fex_id: 105 state: present delegate_to: localhost + +- name: Create and Bind Access Port Selector with PC Policy Group + cisco.aci.aci_access_port_to_interface_policy_leaf_profile: + host: apic + username: admin + password: SomeSecretPassword + interface_profile: leafintprfname + access_port_selector: accessportselectorname + policy_group: pcintprftest + access_port_selector_name: anstest_pc_accessportselector + interface_type: port_channel + port_blk: leafportblkname + from_port: 13 + to_port: 13 + state: present + delegate_to: localhost + +- name: Create and Bind Access Port Selector with VPC Policy Group + cisco.aci.aci_access_port_to_interface_policy_leaf_profile: + host: apic + username: admin + password: SomeSecretPassword + interface_profile: leafintprfname + access_port_selector: accessportselectorname + policy_group: vpcintprftest + access_port_selector_name: anstest_vpc_accessportselector + interface_type: vpc + port_blk: leafportblkname + from_port: 13 + to_port: 13 + state: present + delegate_to: localhost """ RETURN = r""" diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_access_span_dst_group.py b/ansible_collections/cisco/aci/plugins/modules/aci_access_span_dst_group.py index 2da778c75..5b5c10aba 100644 --- a/ansible_collections/cisco/aci/plugins/modules/aci_access_span_dst_group.py +++ b/ansible_collections/cisco/aci/plugins/modules/aci_access_span_dst_group.py @@ -74,11 +74,13 @@ options: - The name of application profile. type: str required: true + aliases: [ ap_name, app_profile, app_profile_name ] epg: description: - The name of the end point group. type: str required: true + aliases: [ epg_name ] span_version: description: - The SPAN version. @@ -214,6 +216,7 @@ EXAMPLES = r""" destination_group: group1 state: query delegate_to: localhost + register: query_result - name: Query all Access SPAN destination groups cisco.aci.aci_access_span_dst_group: @@ -222,6 +225,7 @@ EXAMPLES = r""" password: SomeSecretPassword state: query delegate_to: localhost + register: query_result """ RETURN = r""" @@ -330,50 +334,7 @@ url: """ from ansible.module_utils.basic import AnsibleModule -from ansible_collections.cisco.aci.plugins.module_utils.aci import ACIModule, aci_argument_spec, aci_annotation_spec, aci_owner_spec - - -def destination_epg_spec(): - return dict( - tenant=dict(type="str", required=True, aliases=["tenant_name"]), - ap=dict(type="str", required=True), - epg=dict(type="str", required=True), - source_ip=dict(type="str", required=True), - destination_ip=dict(type="str", required=True), - span_version=dict(type="str", choices=["version_1", "version_2"]), - version_enforced=dict(type="bool"), - flow_id=dict(type="int"), - ttl=dict(type="int"), - mtu=dict(type="int"), - dscp=dict( - type="str", - choices=[ - "CS0", - "CS1", - "CS2", - "CS3", - "CS4", - "CS5", - "CS6", - "CS7", - "EF", - "VA", - "AF11", - "AF12", - "AF13", - "AF21", - "AF22", - "AF23", - "AF31", - "AF32", - "AF33", - "AF41", - "AF42", - "AF43", - "unspecified", - ], - ), - ) +from ansible_collections.cisco.aci.plugins.module_utils.aci import ACIModule, aci_argument_spec, aci_annotation_spec, aci_owner_spec, destination_epg_spec def access_interface_spec(): diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_access_span_filter_group.py b/ansible_collections/cisco/aci/plugins/modules/aci_access_span_filter_group.py index c715027f6..ff716d0ae 100644 --- a/ansible_collections/cisco/aci/plugins/modules/aci_access_span_filter_group.py +++ b/ansible_collections/cisco/aci/plugins/modules/aci_access_span_filter_group.py @@ -72,6 +72,7 @@ EXAMPLES = r""" filter_group: group1 state: query delegate_to: localhost + register: query_result - name: Query all Access SPAN filter groups cisco.aci.aci_access_span_filter_group: @@ -80,6 +81,7 @@ EXAMPLES = r""" password: SomeSecretPassword state: query delegate_to: localhost + register: query_result """ RETURN = r""" diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_access_span_filter_group_entry.py b/ansible_collections/cisco/aci/plugins/modules/aci_access_span_filter_group_entry.py index 5d5516751..ad7151cdf 100644 --- a/ansible_collections/cisco/aci/plugins/modules/aci_access_span_filter_group_entry.py +++ b/ansible_collections/cisco/aci/plugins/modules/aci_access_span_filter_group_entry.py @@ -119,6 +119,7 @@ EXAMPLES = r""" destination_ip: 2.2.2.2 state: query delegate_to: localhost + register: query_result - name: Query all Access SPAN filter groups cisco.aci.aci_access_span_filter_group_entry: @@ -127,6 +128,7 @@ EXAMPLES = r""" password: SomeSecretPassword state: query delegate_to: localhost + register: query_result """ RETURN = r""" diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_access_span_src_group.py b/ansible_collections/cisco/aci/plugins/modules/aci_access_span_src_group.py new file mode 100644 index 000000000..fb6f6a35b --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_access_span_src_group.py @@ -0,0 +1,326 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2023, Akini Ross <akinross@cisco.com> +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported_by": "certified"} + +DOCUMENTATION = r""" +--- +module: aci_access_span_src_group +short_description: Manage Access SPAN source groups (span:SrcGrp) +description: +- Manage SPAN source groups on Cisco ACI fabrics. +options: + source_group: + description: + - The name of the Access SPAN source group. + type: str + aliases: [ name, src_group ] + description: + description: + - The description for Access SPAN source group. + type: str + aliases: [ descr ] + admin_state: + description: + - Enable C(true) or disable C(false) the SPAN sources. + - The APIC defaults to C(true) when unset during creation. + type: bool + filter_group: + description: + - The name of the Access SPAN filter group to associate with the source group. + type: str + destination_group: + description: + - The name of the Access SPAN destination group to associate with the source group. + type: str + aliases: [ dst_group ] + state: + description: + - Use C(present) or C(absent) for adding or removing. + - Use C(query) for listing an object or multiple objects. + type: str + choices: [ absent, present, query ] + default: present + name_alias: + description: + - The alias for the current object. This relates to the nameAlias field in ACI. + type: str +extends_documentation_fragment: +- cisco.aci.aci +- cisco.aci.annotation +- cisco.aci.owner + +notes: +- The I(filter_group) and I(destination_group) must exist before using this module in your playbook. + The M(cisco.aci.aci_access_span_filter_group) and M(cisco.aci.aci_access_span_dst_group) modules can be used for this. +seealso: +- module: cisco.aci.aci_access_span_filter_group +- module: cisco.aci.aci_access_span_dst_group +- name: APIC Management Information Model reference + description: More information about the internal APIC class B(span:SrcGrp). + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Akini Ross (@akinross) +""" + +EXAMPLES = r""" +- name: Create a Access SPAN source group + cisco.aci.aci_access_span_src_group: + host: apic + username: admin + password: SomeSecretPassword + source_group: my_span_source_group + destination_group: my_span_dest_group + state: present + delegate_to: localhost + +- name: Delete a Access SPAN source group + cisco.aci.aci_access_span_src_group: + host: apic + username: admin + password: SomeSecretPassword + source_group: my_span_source_group + state: absent + delegate_to: localhost + +- name: Query all Access SPAN source groups + cisco.aci.aci_access_span_src_group: + host: apic + username: admin + password: SomeSecretPassword + state: query + delegate_to: localhost + register: query_result + +- name: Query a specific Access SPAN source group + cisco.aci.aci_access_span_src_group: + host: apic + username: admin + password: SomeSecretPassword + source_group: my_span_source_group + state: query + delegate_to: localhost + register: query_result +""" + +RETURN = r""" +current: + description: The existing configuration from the APIC after the module has finished + returned: success + type: list + sample: + [ + { + "fvTenant": { + "attributes": { + "descr": "Production environment", + "dn": "uni/tn-production", + "name": "production", + "nameAlias": "", + "ownerKey": "", + "ownerTag": "" + } + } + } + ] +error: + description: The error information as returned from the APIC + returned: failure + type: dict + sample: + { + "code": "122", + "text": "unknown managed object class foo" + } +raw: + description: The raw output returned by the APIC REST API (xml or json) + returned: parse error + type: str + sample: '<?xml version="1.0" encoding="UTF-8"?><imdata totalCount="1"><error code="122" text="unknown managed object class foo"/></imdata>' +sent: + description: The actual/minimal configuration pushed to the APIC + returned: info + type: list + sample: + { + "fvTenant": { + "attributes": { + "descr": "Production environment" + } + } + } +previous: + description: The original configuration from the APIC before the module has started + returned: info + type: list + sample: + [ + { + "fvTenant": { + "attributes": { + "descr": "Production", + "dn": "uni/tn-production", + "name": "production", + "nameAlias": "", + "ownerKey": "", + "ownerTag": "" + } + } + } + ] +proposed: + description: The assembled configuration from the user-provided parameters + returned: info + type: dict + sample: + { + "fvTenant": { + "attributes": { + "descr": "Production environment", + "name": "production" + } + } + } +filter_string: + description: The filter string used for the request + returned: failure or debug + type: str + sample: ?rsp-prop-include=config-only +method: + description: The HTTP method used for the request to the APIC + returned: failure or debug + type: str + sample: POST +response: + description: The HTTP response from the APIC + returned: failure or debug + type: str + sample: OK (30 bytes) +status: + description: The HTTP status from the APIC + returned: failure or debug + type: int + sample: 200 +url: + description: The HTTP url used for the request to the APIC + returned: failure or debug + type: str + sample: https://10.11.12.13/api/mo/uni/tn-production.json +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.cisco.aci.plugins.module_utils.aci import ACIModule, aci_argument_spec, aci_annotation_spec, aci_owner_spec + + +def main(): + argument_spec = aci_argument_spec() + argument_spec.update(aci_annotation_spec()) + argument_spec.update(aci_owner_spec()) + argument_spec.update( + source_group=dict(type="str", aliases=["name", "src_group"]), # Not required for querying all objects + description=dict(type="str", aliases=["descr"]), + admin_state=dict(type="bool"), + filter_group=dict(type="str"), + destination_group=dict(type="str", aliases=["dst_group"]), + state=dict(type="str", default="present", choices=["absent", "present", "query"]), + name_alias=dict(type="str"), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_if=[ + ["state", "absent", ["source_group"]], + ["state", "present", ["source_group", "destination_group"]], + ], + ) + + aci = ACIModule(module) + + source_group = module.params.get("source_group") + description = module.params.get("description") + admin_state = aci.boolean(module.params.get("admin_state"), "enabled", "disabled") + filter_group = module.params.get("filter_group") + destination_group = module.params.get("destination_group") + state = module.params.get("state") + name_alias = module.params.get("name_alias") + + aci.construct_url( + root_class=dict( + aci_class="infra", + aci_rn="infra", + ), + subclass_1=dict( + aci_class="spanSrcGrp", + aci_rn="srcgrp-{0}".format(source_group), + module_object=source_group, + target_filter={"name": source_group}, + ), + child_classes=["spanSpanLbl", "spanRsSrcGrpToFilterGrp"], + ) + + aci.get_existing() + + if state == "present": + # Create new child configs payload + filter_group_tdn = "uni/infra/filtergrp-{0}".format(filter_group) + child_configs = [{"spanSpanLbl": {"attributes": {"name": destination_group}}}] + if filter_group: + child_configs.append({"spanRsSrcGrpToFilterGrp": {"attributes": {"tDn": filter_group_tdn}}}) + + # Validate if existing and remove child objects when do not match provided configuration + if isinstance(aci.existing, list) and len(aci.existing) > 0: + for child in aci.existing[0].get("spanSrcGrp", {}).get("children", {}): + if child.get("spanRsSrcGrpToFilterGrp") and child.get("spanRsSrcGrpToFilterGrp").get("attributes").get("tDn") != filter_group_tdn: + # Appending to child_config list not possible because of APIC Error 103: child (Rn) of class spanRsSrcGrpToFilterGrp is already attached. + # A seperate delete request to dn of the spanRsSrcGrpToFilterGrp is needed to remove the object prior to adding to child_configs. + # Failed child_config is displayed in below: + # + # child_configs.append( + # { + # "spanRsSrcGrpToFilterGrp": { + # "attributes": { + # "dn": "uni/infra/srcgrp-{0}/rssrcGrpToFilterGrp".format(source_group), + # "status": "deleted", + # } + # } + # } + # ) + aci.api_call("DELETE", "/api/mo/uni/infra/srcgrp-{0}/rssrcGrpToFilterGrp.json".format(source_group)) + elif child.get("spanSpanLbl") and child.get("spanSpanLbl").get("attributes").get("name") != destination_group: + child_configs.append( + { + "spanSpanLbl": { + "attributes": { + "dn": "uni/infra/srcgrp-{0}/spanlbl-{1}".format(source_group, child.get("spanSpanLbl").get("attributes").get("name")), + "status": "deleted", + } + } + } + ) + + aci.payload( + aci_class="spanSrcGrp", + class_config=dict(adminSt=admin_state, descr=description, name=source_group, nameAlias=name_alias), + child_configs=child_configs, + ) + + aci.get_diff(aci_class="spanSrcGrp") + + aci.post_config() + + elif state == "absent": + aci.delete_config() + + aci.exit_json() + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_access_span_src_group_src.py b/ansible_collections/cisco/aci/plugins/modules/aci_access_span_src_group_src.py new file mode 100644 index 000000000..f8ff4349e --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_access_span_src_group_src.py @@ -0,0 +1,442 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2023, Akini Ross <akinross@cisco.com> +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported_by": "certified"} + +DOCUMENTATION = r""" +--- +module: aci_access_span_src_group_src +short_description: Manage Access SPAN sources (span:Src) +description: +- Manage Access SPAN sources on Cisco ACI fabrics. +options: + description: + description: + - The description for Access SPAN source. + type: str + aliases: [ descr ] + source_group: + description: + - The name of the Access SPAN source group. + type: str + aliases: [ src_group ] + source: + description: + - The name of the Access SPAN source. + type: str + aliases: [ name, src ] + direction: + description: + - The direction of the SPAN source. + - The APIC defaults to C(both) when unset during creation. + type: str + choices: [ incoming, outgoing, both ] + filter_group: + description: + - The name of the Access SPAN filter group to associate with the source. + type: str + drop_packets: + description: + - Enable SPAN for only dropped packets. + - The APIC defaults to C(false) when unset during creation. + type: bool + epg: + description: + - The SPAN source EPG details. + - The I(epg) and I(routed_outside) cannot be configured simultaneously. + type: dict + suboptions: + tenant: + description: + - The name of the SPAN source Tenant. + type: str + required: true + aliases: [ tenant_name ] + ap: + description: + - The name of the SPAN source AP. + type: str + required: true + aliases: [ ap_name, app_profile, app_profile_name ] + epg: + description: + - The name of the SPAN source EPG. + type: str + required: true + aliases: [ epg_name ] + routed_outside: + description: + - The Routed Outside details. + - The I(epg) and I(routed_outside) cannot be configured simultaneously. + type: dict + suboptions: + tenant: + description: + - The name of the SPAN source Tenant. + type: str + aliases: [ tenant_name ] + l3out: + description: + - The name of the SPAN source L3Out. + type: str + aliases: [ l3out_name ] + encap: + description: + - The VLAN associated with this Routed Outside. + type: int + aliases: [ vlan, vlan_id, encap_id ] + state: + description: + - Use C(present) or C(absent) for adding or removing. + - Use C(query) for listing an object or multiple objects. + type: str + choices: [ absent, present, query ] + default: present + name_alias: + description: + - The alias for the current object. This relates to the nameAlias field in ACI. + type: str +extends_documentation_fragment: +- cisco.aci.aci +- cisco.aci.annotation +- cisco.aci.owner + +notes: +- The I(filter_group) and I(source_group) must exist before using this module in your playbook. + The M(cisco.aci.aci_access_span_filter_group) and M(cisco.aci.aci_access_span_src_group) modules can be used for this. +seealso: +- module: cisco.aci.aci_access_span_filter_group +- module: cisco.aci.aci_access_span_src_group +- module: cisco.aci.aci_tenant +- module: cisco.aci.aci_ap +- module: cisco.aci.aci_epg +- module: cisco.aci.aci_l3out +- name: APIC Management Information Model reference + description: More information about the internal APIC class B(span:Src). + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Akini Ross (@akinross) +""" + +EXAMPLES = r""" +- name: Create a Access SPAN source + cisco.aci.aci_access_span_src_group_src: + host: apic + username: admin + password: SomeSecretPassword + source_group: my_span_source_group + source: my_source + state: present + delegate_to: localhost + +- name: Delete a Access SPAN source + cisco.aci.aci_access_span_src_group_src: + host: apic + username: admin + password: SomeSecretPassword + source_group: my_span_source_group + source: my_source + state: absent + delegate_to: localhost + +- name: Query all Access SPAN sources + cisco.aci.aci_access_span_src_group_src: + host: apic + username: admin + password: SomeSecretPassword + state: query + delegate_to: localhost + register: query_result + +- name: Query a specific Access SPAN source + cisco.aci.aci_access_span_src_group_src: + host: apic + username: admin + password: SomeSecretPassword + source_group: my_span_source_group + source: my_source + state: query + delegate_to: localhost + register: query_result +""" + +RETURN = r""" +current: + description: The existing configuration from the APIC after the module has finished + returned: success + type: list + sample: + [ + { + "fvTenant": { + "attributes": { + "descr": "Production environment", + "dn": "uni/tn-production", + "name": "production", + "nameAlias": "", + "ownerKey": "", + "ownerTag": "" + } + } + } + ] +error: + description: The error information as returned from the APIC + returned: failure + type: dict + sample: + { + "code": "122", + "text": "unknown managed object class foo" + } +raw: + description: The raw output returned by the APIC REST API (xml or json) + returned: parse error + type: str + sample: '<?xml version="1.0" encoding="UTF-8"?><imdata totalCount="1"><error code="122" text="unknown managed object class foo"/></imdata>' +sent: + description: The actual/minimal configuration pushed to the APIC + returned: info + type: list + sample: + { + "fvTenant": { + "attributes": { + "descr": "Production environment" + } + } + } +previous: + description: The original configuration from the APIC before the module has started + returned: info + type: list + sample: + [ + { + "fvTenant": { + "attributes": { + "descr": "Production", + "dn": "uni/tn-production", + "name": "production", + "nameAlias": "", + "ownerKey": "", + "ownerTag": "" + } + } + } + ] +proposed: + description: The assembled configuration from the user-provided parameters + returned: info + type: dict + sample: + { + "fvTenant": { + "attributes": { + "descr": "Production environment", + "name": "production" + } + } + } +filter_string: + description: The filter string used for the request + returned: failure or debug + type: str + sample: ?rsp-prop-include=config-only +method: + description: The HTTP method used for the request to the APIC + returned: failure or debug + type: str + sample: POST +response: + description: The HTTP response from the APIC + returned: failure or debug + type: str + sample: OK (30 bytes) +status: + description: The HTTP status from the APIC + returned: failure or debug + type: int + sample: 200 +url: + description: The HTTP url used for the request to the APIC + returned: failure or debug + type: str + sample: https://10.11.12.13/api/mo/uni/tn-production.json +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.cisco.aci.plugins.module_utils.aci import ACIModule, aci_argument_spec, aci_annotation_spec, aci_owner_spec +from ansible_collections.cisco.aci.plugins.module_utils.constants import SPAN_DIRECTION_MAP + + +def main(): + argument_spec = aci_argument_spec() + argument_spec.update(aci_annotation_spec()) + argument_spec.update(aci_owner_spec()) + argument_spec.update( + source_group=dict(type="str", aliases=["src_group"]), # Not required for querying all objects + description=dict(type="str", aliases=["descr"]), + source=dict(type="str", aliases=["name", "src"]), # Not required for querying all objects + direction=dict(type="str", choices=list(SPAN_DIRECTION_MAP.keys())), + filter_group=dict(type="str"), + drop_packets=dict(type="bool"), + epg=dict( + type="dict", + options=dict( + epg=dict(type="str", required=True, aliases=["epg_name"]), + ap=dict(type="str", required=True, aliases=["ap_name", "app_profile", "app_profile_name"]), + tenant=dict(type="str", required=True, aliases=["tenant_name"]), + ), + ), + routed_outside=dict( + type="dict", + options=dict( + encap=dict(type="int", aliases=["vlan", "vlan_id", "encap_id"]), + l3out=dict(type="str", aliases=["l3out_name"]), + tenant=dict(type="str", aliases=["tenant_name"]), + ), + ), + state=dict(type="str", default="present", choices=["absent", "present", "query"]), + name_alias=dict(type="str"), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_if=[ + ["state", "absent", ["source_group", "source"]], + ["state", "present", ["source_group", "source"]], + ], + mutually_exclusive=[ + ("epg", "routed_outside"), + ], + ) + + aci = ACIModule(module) + + description = module.params.get("description") + source_group = module.params.get("source_group") + source = module.params.get("source") + direction = module.params.get("direction") + filter_group = module.params.get("filter_group") + drop_packets = module.params.get("drop_packets") + epg = module.params.get("epg") + routed_outside = module.params.get("routed_outside") + state = module.params.get("state") + name_alias = module.params.get("name_alias") + + if filter_group and drop_packets: + module.fail_json(msg="Setting 'drop_packets' to 'true' is not allowed when 'filter_group' is configured on the source.") + elif epg and drop_packets: + module.fail_json(msg="Setting 'drop_packets' to 'true' is not allowed when 'epg' is configured on the source.") + elif routed_outside and drop_packets: + module.fail_json(msg="Setting 'drop_packets' to 'true' is not allowed when 'routed_outside' is configured on the source.") + + aci.construct_url( + root_class=dict( + aci_class="infra", + aci_rn="infra", + ), + subclass_1=dict( + aci_class="spanSrcGrp", + aci_rn="srcgrp-{0}".format(source_group), + module_object=source_group, + target_filter={"name": source_group}, + ), + subclass_2=dict( + aci_class="spanSrc", + aci_rn="src-{0}".format(source), + module_object=source, + target_filter={"name": source}, + ), + child_classes=["spanRsSrcToFilterGrp", "spanRsSrcToEpg", "spanRsSrcToL3extOut"], + ) + + aci.get_existing() + + if state == "present": + # Create new child configs payload + child_configs = [] + filter_group_tdn = epg_dn = l3ext_out_dn = None + + if filter_group: + filter_group_tdn = "uni/infra/filtergrp-{0}".format(filter_group) + child_configs.append({"spanRsSrcToFilterGrp": {"attributes": {"tDn": filter_group_tdn}}}) + if epg: + epg_dn = "uni/tn-{0}/ap-{1}/epg-{2}".format(epg.get("tenant"), epg.get("ap"), epg.get("epg")) + child_configs.append({"spanRsSrcToEpg": {"attributes": {"tDn": epg_dn}}}) + elif routed_outside: + # encap is set to unknown when not provided to ensure change is executed and detected properly on update + encap = "vlan-{0}".format(routed_outside.get("encap")) if routed_outside.get("encap") else "unknown" + if routed_outside.get("tenant") and routed_outside.get("l3out"): + l3ext_out_dn = "uni/tn-{0}/out-{1}".format(routed_outside.get("tenant"), routed_outside.get("l3out")) + else: + # tDn is set to "" when not provided to ensure change is executed and detected properly on update + l3ext_out_dn = "" + child_configs.append({"spanRsSrcToL3extOut": {"attributes": {"encap": encap, "tDn": l3ext_out_dn}}}) + + # Validate if existing and remove child objects when do not match provided configuration + if isinstance(aci.existing, list) and len(aci.existing) > 0: + # Commented validate code to avoid making additional API request which is handled by APIC error + # Keeping for informational purposes + # Validate drop_packets are set on parent correctly + # if aci.api_call("GET", "{0}/rssrcGrpToFilterGrp.json".format(source_group_path)) != [] and drop_packets: + # module.fail_json(msg="It is not allowed to configure 'drop_packets: true' when a filter group is configured on the source group.") + + source_path = "/api/mo/uni/infra/srcgrp-{0}/src-{1}".format(source_group, source) + for child in aci.existing[0].get("spanSrc", {}).get("children", {}): + if child.get("spanRsSrcToFilterGrp") and child.get("spanRsSrcToFilterGrp").get("attributes").get("tDn") != filter_group_tdn: + # Appending to child_config list not possible because of APIC Error 103: child (Rn) of class spanRsSrcGrpToFilterGrp is already attached. + # A seperate delete request to dn of the spanRsSrcGrpToFilterGrp is needed to remove the object prior to adding to child_configs. + # Failed child_config is displayed in below: + # + # child_configs.append( + # { + # "spanRsSrcGrpToFilterGrp": { + # "attributes": { + # "dn": "uni/infra/srcgrp-{0}/src-{1}/rssrcGrpToFilterGrp".format(source_group, source), + # "status": "deleted", + # } + # } + # } + # ) + aci.api_call("DELETE", "{0}/rssrcToFilterGrp.json".format(source_path)) + elif child.get("spanRsSrcToEpg") and child.get("spanRsSrcToEpg").get("attributes").get("tDn") != epg_dn: + # Appending to child_config list not possible because of APIC Error 103: child (Rn) of class spanRsSrcToEpg is already attached. + aci.api_call("DELETE", "{0}/rssrcToEpg.json".format(source_path)) + elif child.get("spanRsSrcToL3extOut") and child.get("spanRsSrcToL3extOut").get("attributes").get("tDn") != l3ext_out_dn: + # Appending to child_config list not possible because of APIC Error 103: child (Rn) of class spanRsSrcToL3extOut is already attached. + aci.api_call("DELETE", "{0}/rssrcToL3extOut.json".format(source_path)) + + aci.payload( + aci_class="spanSrc", + class_config=dict( + descr=description, + name=source, + dir=SPAN_DIRECTION_MAP.get(direction), + spanOnDrop=aci.boolean(drop_packets), + nameAlias=name_alias, + ), + child_configs=child_configs, + ) + + aci.get_diff(aci_class="spanSrc") + + aci.post_config() + + elif state == "absent": + aci.delete_config() + + aci.exit_json() + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_access_span_src_group_src_path.py b/ansible_collections/cisco/aci/plugins/modules/aci_access_span_src_group_src_path.py new file mode 100644 index 000000000..e99970905 --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_access_span_src_group_src_path.py @@ -0,0 +1,360 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2023, Akini Ross <akinross@cisco.com> +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported_by": "certified"} + +DOCUMENTATION = r""" +--- +module: aci_access_span_src_group_src_path +short_description: Manage Access SPAN source paths (span:RsSrcToPathEp) +description: +- Manage Access SPAN source paths on Cisco ACI fabrics. +options: + source_group: + description: + - The name of the Access SPAN source group. + type: str + aliases: [ src_group ] + source: + description: + - The name of the Access SPAN source. + type: str + aliases: [ src ] + pod: + description: + - The pod id of the source access path. + type: int + aliases: [ pod_id, pod_number ] + nodes: + description: + - The node of the source access path. + - Provide one node to the list for port, direct_port_channel or vpc_component_pc types. + - Provide two nodes to the list for virtual_port_channel type. + type: list + elements: int + aliases: [ node_ids ] + path_ep: + description: + - The path of the source access path. + - When path is of type port a interface like C(eth1/7) must be provided. + - When path is of type direct_port_channel the name of a pc interface policy group like C(test_PolGrp) must be provided. + - When path is of type virtual_port_channel or vpc_component_pc the name of a vpc interface policy group like C(test_vPC_PolGrp) must be provided. + type: str + state: + description: + - Use C(present) or C(absent) for adding or removing. + - Use C(query) for listing an object or multiple objects. + type: str + choices: [ absent, present, query ] + default: present +extends_documentation_fragment: +- cisco.aci.aci +- cisco.aci.annotation + +notes: +- The I(source_group) and I(source) must exist before using this module in your playbook. + The M(cisco.aci.aci_access_span_src_group) and M(cisco.aci.aci_access_span_src_group_src) modules can be used for this. +seealso: +- module: cisco.aci.aci_access_span_src_group +- module: cisco.aci.aci_access_span_src_group_src +- name: APIC Management Information Model reference + description: More information about the internal APIC class B(span:RsSrcToPathEp). + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Akini Ross (@akinross) +""" + +EXAMPLES = r""" +- name: Create a Access SPAN source path of type Port + cisco.aci.aci_access_span_src_group_src_path: + host: apic + username: admin + password: SomeSecretPassword + source_group: my_span_source_group + source: my_source + pod: 1 + nodes: + - 101 + path_ep: eth1/1 + state: present + delegate_to: localhost + +- name: Create a Access SPAN source path of type Direct Port Channel + cisco.aci.aci_access_span_src_group_src_path: + host: apic + username: admin + password: SomeSecretPassword + source_group: my_span_source_group + source: my_source + pod: 1 + nodes: + - 101 + path_ep: test_PolGrp + state: present + delegate_to: localhost + +- name: Create a Access SPAN source path of type VPC component PC + cisco.aci.aci_access_span_src_group_src_path: + host: apic + username: admin + password: SomeSecretPassword + source_group: my_span_source_group + source: my_source + pod: 1 + nodes: + - 101 + path_ep: test_vPC_PolGrp + state: present + delegate_to: localhost + +- name: Create a Access SPAN source path of type Virtual Port Channel + cisco.aci.aci_access_span_src_group_src_path: + host: apic + username: admin + password: SomeSecretPassword + source_group: my_span_source_group + source: my_source + pod: 1 + nodes: + - 101 + - 102 + path_ep: test_vPC_PolGrp + state: present + delegate_to: localhost + +- name: Delete a Access SPAN source path + cisco.aci.aci_access_span_src_group_src_path: + host: apic + username: admin + password: SomeSecretPassword + source_group: my_span_source_group + source: my_source + pod: 1 + nodes: + - 101 + path_ep: test_vPC_PolGrp + state: absent + delegate_to: localhost + +- name: Query all Access SPAN source paths + cisco.aci.aci_access_span_src_group_src_path: + host: apic + username: admin + password: SomeSecretPassword + state: query + delegate_to: localhost + register: query_result + +- name: Query a specific Access SPAN source path + cisco.aci.aci_access_span_src_group_src_path: + host: apic + username: admin + password: SomeSecretPassword + source_group: my_span_source_group + source: my_source + pod: 1 + nodes: + - 101 + path_ep: test_vPC_PolGrp + state: query + delegate_to: localhost + register: query_result +""" + +RETURN = r""" +current: + description: The existing configuration from the APIC after the module has finished + returned: success + type: list + sample: + [ + { + "fvTenant": { + "attributes": { + "descr": "Production environment", + "dn": "uni/tn-production", + "name": "production", + "nameAlias": "", + "ownerKey": "", + "ownerTag": "" + } + } + } + ] +error: + description: The error information as returned from the APIC + returned: failure + type: dict + sample: + { + "code": "122", + "text": "unknown managed object class foo" + } +raw: + description: The raw output returned by the APIC REST API (xml or json) + returned: parse error + type: str + sample: '<?xml version="1.0" encoding="UTF-8"?><imdata totalCount="1"><error code="122" text="unknown managed object class foo"/></imdata>' +sent: + description: The actual/minimal configuration pushed to the APIC + returned: info + type: list + sample: + { + "fvTenant": { + "attributes": { + "descr": "Production environment" + } + } + } +previous: + description: The original configuration from the APIC before the module has started + returned: info + type: list + sample: + [ + { + "fvTenant": { + "attributes": { + "descr": "Production", + "dn": "uni/tn-production", + "name": "production", + "nameAlias": "", + "ownerKey": "", + "ownerTag": "" + } + } + } + ] +proposed: + description: The assembled configuration from the user-provided parameters + returned: info + type: dict + sample: + { + "fvTenant": { + "attributes": { + "descr": "Production environment", + "name": "production" + } + } + } +filter_string: + description: The filter string used for the request + returned: failure or debug + type: str + sample: ?rsp-prop-include=config-only +method: + description: The HTTP method used for the request to the APIC + returned: failure or debug + type: str + sample: POST +response: + description: The HTTP response from the APIC + returned: failure or debug + type: str + sample: OK (30 bytes) +status: + description: The HTTP status from the APIC + returned: failure or debug + type: int + sample: 200 +url: + description: The HTTP url used for the request to the APIC + returned: failure or debug + type: str + sample: https://10.11.12.13/api/mo/uni/tn-production.json +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.cisco.aci.plugins.module_utils.aci import ACIModule, aci_argument_spec, aci_annotation_spec + + +def main(): + argument_spec = aci_argument_spec() + argument_spec.update(aci_annotation_spec()) + argument_spec.update( + source_group=dict(type="str", aliases=["src_group"]), # Not required for querying all objects + source=dict(type="str", aliases=["src"]), # Not required for querying all objects + pod=dict(type="int", aliases=["pod_id", "pod_number"]), # Not required for querying all objects + nodes=dict(type="list", elements="int", aliases=["node_ids"]), # Not required for querying all objects + path_ep=dict(type="str"), # Not required for querying all objects + state=dict(type="str", default="present", choices=["absent", "present", "query"]), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_if=[ + ["state", "absent", ["source_group", "source", "pod", "nodes", "path_ep"]], + ["state", "present", ["source_group", "source", "pod", "nodes", "path_ep"]], + ], + ) + + aci = ACIModule(module) + + source_group = module.params.get("source_group") + source = module.params.get("source") + pod = module.params.get("pod") + nodes = module.params.get("nodes") + path_ep = module.params.get("path_ep") + state = module.params.get("state") + + tdn = None + if nodes: + if len(nodes) == 1: + tdn = "topology/pod-{0}/paths-{1}/pathep-[{2}]".format(pod, nodes[0], path_ep) + elif len(nodes) == 2: + tdn = "topology/pod-{0}/protpaths-{1}/pathep-[{2}]".format(pod, "-".join([str(node) for node in nodes]), path_ep) + else: + module.fail_json(msg="{0} nodes have been provided, where a maximum of 2 nodes is allowed.".format(len(nodes))) + + aci.construct_url( + root_class=dict( + aci_class="infra", + aci_rn="infra", + ), + subclass_1=dict( + aci_class="spanSrcGrp", + aci_rn="srcgrp-{0}".format(source_group), + module_object=source_group, + target_filter={"name": source_group}, + ), + subclass_2=dict( + aci_class="spanSrc", + aci_rn="src-{0}".format(source), + module_object=source, + target_filter={"name": source}, + ), + subclass_3=dict( + aci_class="spanRsSrcToPathEp", + aci_rn="rssrcToPathEp-[{0}]".format(tdn), + module_object=tdn, + target_filter={"tDn": tdn}, + ), + ) + + aci.get_existing() + + if state == "present": + aci.payload(aci_class="spanRsSrcToPathEp", class_config=dict(tDn=tdn)) + + aci.get_diff(aci_class="spanRsSrcToPathEp") + + aci.post_config() + + elif state == "absent": + aci.delete_config() + + aci.exit_json() + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_aep_to_domain.py b/ansible_collections/cisco/aci/plugins/modules/aci_aep_to_domain.py index d3a8e9e1b..3977e63e3 100644 --- a/ansible_collections/cisco/aci/plugins/modules/aci_aep_to_domain.py +++ b/ansible_collections/cisco/aci/plugins/modules/aci_aep_to_domain.py @@ -216,16 +216,7 @@ url: from ansible.module_utils.basic import AnsibleModule from ansible_collections.cisco.aci.plugins.module_utils.aci import ACIModule, aci_argument_spec, aci_annotation_spec - -VM_PROVIDER_MAPPING = dict( - cloudfoundry="CloudFoundry", - kubernetes="Kubernetes", - microsoft="Microsoft", - openshift="OpenShift", - openstack="OpenStack", - redhat="Redhat", - vmware="VMware", -) +from ansible_collections.cisco.aci.plugins.module_utils.constants import VM_PROVIDER_MAPPING def main(): @@ -236,7 +227,7 @@ def main(): domain=dict(type="str", aliases=["domain_name", "domain_profile"]), # Not required for querying all objects domain_type=dict(type="str", choices=["fc", "l2dom", "l3dom", "phys", "vmm"], aliases=["type"]), # Not required for querying all objects state=dict(type="str", default="present", choices=["absent", "present", "query"]), - vm_provider=dict(type="str", choices=["cloudfoundry", "kubernetes", "microsoft", "openshift", "openstack", "redhat", "vmware"]), + vm_provider=dict(type="str", choices=list(VM_PROVIDER_MAPPING.keys())), ) module = AnsibleModule( diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_bd_subnet.py b/ansible_collections/cisco/aci/plugins/modules/aci_bd_subnet.py index c10db5c8c..1819fe968 100644 --- a/ansible_collections/cisco/aci/plugins/modules/aci_bd_subnet.py +++ b/ansible_collections/cisco/aci/plugins/modules/aci_bd_subnet.py @@ -91,6 +91,13 @@ options: - The name of the Subnet. type: str aliases: [ name ] + ip_data_plane_learning: + description: + - Whether IP data plane learning is enabled or disabled. + - The APIC defaults to C(enabled) when unset during creation. + type: str + choices: [ enabled, disabled ] + aliases: [ ip_dataplane_learning ] tenant: description: - The name of the Tenant. @@ -339,15 +346,10 @@ url: sample: https://10.11.12.13/api/mo/uni/tn-production.json """ +import re from ansible.module_utils.basic import AnsibleModule from ansible_collections.cisco.aci.plugins.module_utils.aci import ACIModule, aci_argument_spec, aci_annotation_spec - -SUBNET_CONTROL_MAPPING = dict( - nd_ra="nd", - no_gw="no-default-gateway", - querier_ip="querier", - unspecified="", -) +from ansible_collections.cisco.aci.plugins.module_utils.constants import SUBNET_CONTROL_MAPPING_BD_SUBNET, IPV4_REGEX def main(): @@ -365,10 +367,11 @@ def main(): route_profile=dict(type="str"), route_profile_l3_out=dict(type="str"), scope=dict(type="list", elements="str", choices=["private", "public", "shared"]), - subnet_control=dict(type="str", choices=["nd_ra", "no_gw", "querier_ip", "unspecified"]), + subnet_control=dict(type="str", choices=list(SUBNET_CONTROL_MAPPING_BD_SUBNET.keys())), state=dict(type="str", default="present", choices=["absent", "present", "query"]), tenant=dict(type="str", aliases=["tenant_name"]), # Not required for querying all objects name_alias=dict(type="str"), + ip_data_plane_learning=dict(type="str", choices=["enabled", "disabled"], aliases=["ip_dataplane_learning"]), ) module = AnsibleModule( @@ -389,9 +392,14 @@ def main(): bd = module.params.get("bd") gateway = module.params.get("gateway") mask = module.params.get("mask") - if mask is not None and mask not in range(0, 129): - # TODO: split checks between IPv4 and IPv6 Addresses - module.fail_json(msg="Valid Subnet Masks are 0 to 32 for IPv4 Addresses and 0 to 128 for IPv6 addresses") + + if mask: + # Assumption for simplicity of code that when a valid IPv4 address is not provided the intend is IPv6. + if re.search(IPV4_REGEX, gateway) and mask not in range(0, 33): + module.fail_json(msg="Valid Subnet Masks are 0 to 32 for IPv4 Addresses") + elif mask not in range(0, 129): + module.fail_json(msg="Valid Subnet Masks are 0 to 128 for IPv6 Addresses") + if gateway is not None: gateway = "{0}/{1}".format(gateway, str(mask)) subnet_name = module.params.get("subnet_name") @@ -408,9 +416,9 @@ def main(): state = module.params.get("state") subnet_control = module.params.get("subnet_control") if subnet_control: - subnet_control = SUBNET_CONTROL_MAPPING[subnet_control] + subnet_control = SUBNET_CONTROL_MAPPING_BD_SUBNET[subnet_control] name_alias = module.params.get("name_alias") - + ip_data_plane_learning = module.params.get("ip_data_plane_learning") aci.construct_url( root_class=dict( aci_class="fvTenant", @@ -436,18 +444,23 @@ def main(): aci.get_existing() if state == "present": + class_config = dict( + ctrl=subnet_control, + descr=description, + ip=gateway, + name=subnet_name, + preferred=preferred, + scope=scope, + virtual=enable_vip, + nameAlias=name_alias, + ) + + if ip_data_plane_learning: + class_config["ipDPLearning"] = ip_data_plane_learning + aci.payload( aci_class="fvSubnet", - class_config=dict( - ctrl=subnet_control, - descr=description, - ip=gateway, - name=subnet_name, - preferred=preferred, - scope=scope, - virtual=enable_vip, - nameAlias=name_alias, - ), + class_config=class_config, child_configs=[ {"fvRsBDSubnetToProfile": {"attributes": {"tnL3extOutName": route_profile_l3_out, "tnRtctrlProfileName": route_profile}}}, {"fvRsNdPfxPol": {"attributes": {"tnNdPfxPolName": nd_prefix_policy}}}, diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_bgp_best_path_policy.py b/ansible_collections/cisco/aci/plugins/modules/aci_bgp_best_path_policy.py new file mode 100644 index 000000000..a8b5c0a6a --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_bgp_best_path_policy.py @@ -0,0 +1,294 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2023, Gaspard Micol (@gmicol) <gmicol@cisco.com> +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported_by": "certified"} + +DOCUMENTATION = r""" +--- +module: aci_bgp_best_path_policy +short_description: Manage BGP Best Path policy (bgp:BestPathCtrlPol) +description: +- Manage BGP Best Path policies for Tenants on Cisco ACI fabrics. +options: + tenant: + description: + - The name of an existing tenant. + type: str + aliases: [ tenant_name ] + bgp_best_path_policy: + description: + - The name of the best path policy. + type: str + aliases: [ bgp_best_path_policy_name, name ] + best_path_control: + description: + - The option to enable/disable to relax AS-Path restriction when choosing multipaths. + - When enabled, allow load sharing across providers with different AS paths. + - The APIC defaults to C(enable) when unset during creation. + type: str + choices: [enable, disable] + aliases: [as_path_control] + description: + description: + - Description for the bgp protocol profile. + type: str + aliases: [ descr ] + state: + description: + - Use C(present) or C(absent) for adding or removing. + - Use C(query) for listing an object or multiple objects. + type: str + choices: [ absent, present, query ] + default: present + name_alias: + description: + - The alias for the current object. This relates to the nameAlias field in ACI. + type: str +extends_documentation_fragment: +- cisco.aci.aci +- cisco.aci.annotation +- cisco.aci.owner + +notes: +- The C(tenant) used must exist before using this module in your playbook. + The M(cisco.aci.aci_tenant) module can be used for this. +seealso: +- module: cisco.aci.aci_tenant +- name: APIC Management Information Model reference + description: More information about the internal APIC class B(bgp:BestPathCtrlPol). + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Gaspard Micol (@gmicol) +""" + +EXAMPLES = r""" +- name: Create a BGP best path policy + cisco.aci.aci_bgp_best_path_policy: + host: apic + username: admin + password: SomeSecretPassword + bgp_protocol_profile: my_bgp_best_path_policy + best_path_control: enable + tenant: production + state: present + delegate_to: localhost + +- name: Delete a BGP best path policy + cisco.aci.aci_bgp_best_path_policy: + host: apic + username: admin + password: SomeSecretPassword + bgp_protocol_profile: my_bgp_best_path_policy + tenant: production + state: absent + delegate_to: localhost + +- name: Query all BGP best path policies + cisco.aci.aci_bgp_best_path_policy: + host: apic + username: admin + password: SomeSecretPassword + state: query + delegate_to: localhost + register: query_result + +- name: Query a specific BGP best path policy + cisco.aci.aci_bgp_best_path_policy: + host: apic + username: admin + password: SomeSecretPassword + bgp_protocol_profile: my_bgp_best_path_policy + tenant: production + state: query + delegate_to: localhost + register: query_result +""" + +RETURN = r""" +current: + description: The existing configuration from the APIC after the module has finished + returned: success + type: list + sample: + [ + { + "fvTenant": { + "attributes": { + "descr": "Production environment", + "dn": "uni/tn-production", + "name": "production", + "nameAlias": "", + "ownerKey": "", + "ownerTag": "" + } + } + } + ] +error: + description: The error information as returned from the APIC + returned: failure + type: dict + sample: + { + "code": "122", + "text": "unknown managed object class foo" + } +raw: + description: The raw output returned by the APIC REST API (xml or json) + returned: parse error + type: str + sample: '<?xml version="1.0" encoding="UTF-8"?><imdata totalCount="1"><error code="122" text="unknown managed object class foo"/></imdata>' +sent: + description: The actual/minimal configuration pushed to the APIC + returned: info + type: list + sample: + { + "fvTenant": { + "attributes": { + "descr": "Production environment" + } + } + } +previous: + description: The original configuration from the APIC before the module has started + returned: info + type: list + sample: + [ + { + "fvTenant": { + "attributes": { + "descr": "Production", + "dn": "uni/tn-production", + "name": "production", + "nameAlias": "", + "ownerKey": "", + "ownerTag": "" + } + } + } + ] +proposed: + description: The assembled configuration from the user-provided parameters + returned: info + type: dict + sample: + { + "fvTenant": { + "attributes": { + "descr": "Production environment", + "name": "production" + } + } + } +filter_string: + description: The filter string used for the request + returned: failure or debug + type: str + sample: ?rsp-prop-include=config-only +method: + description: The HTTP method used for the request to the APIC + returned: failure or debug + type: str + sample: POST +response: + description: The HTTP response from the APIC + returned: failure or debug + type: str + sample: OK (30 bytes) +status: + description: The HTTP status from the APIC + returned: failure or debug + type: int + sample: 200 +url: + description: The HTTP url used for the request to the APIC + returned: failure or debug + type: str + sample: https://10.11.12.13/api/mo/uni/tn-production.json +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.cisco.aci.plugins.module_utils.aci import ACIModule, aci_argument_spec, aci_annotation_spec, aci_owner_spec +from ansible_collections.cisco.aci.plugins.module_utils.constants import MATCH_BEST_PATH_CONTROL_MAPPING + + +def main(): + argument_spec = aci_argument_spec() + argument_spec.update(aci_annotation_spec()) + argument_spec.update(aci_owner_spec()) + argument_spec.update( + tenant=dict(type="str", aliases=["tenant_name"]), # Not required for querying all objects + bgp_best_path_policy=dict(type="str", aliases=["bgp_best_path_policy_name", "name"]), # Not required for querying all objects + best_path_control=dict(type="str", choices=["enable", "disable"], aliases=["as_path_control"]), + description=dict(type="str", aliases=["descr"]), + state=dict(type="str", default="present", choices=["absent", "present", "query"]), + name_alias=dict(type="str"), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_if=[ + ["state", "absent", ["bgp_best_path_policy", "tenant"]], + ["state", "present", ["bgp_best_path_policy", "tenant"]], + ], + ) + + bgp_best_path_policy = module.params.get("bgp_best_path_policy") + best_path_control = MATCH_BEST_PATH_CONTROL_MAPPING.get(module.params.get("best_path_control")) + description = module.params.get("description") + state = module.params.get("state") + tenant = module.params.get("tenant") + name_alias = module.params.get("name_alias") + + aci = ACIModule(module) + + aci.construct_url( + root_class=dict( + aci_class="fvTenant", + aci_rn="tn-{0}".format(tenant), + module_object=tenant, + target_filter={"name": tenant}, + ), + subclass_1=dict( + aci_class="bgpBestPathCtrlPol", + aci_rn="bestpath-{0}".format(bgp_best_path_policy), + module_object=bgp_best_path_policy, + target_filter={"name": bgp_best_path_policy}, + ), + ) + + aci.get_existing() + + if state == "present": + aci.payload( + aci_class="bgpBestPathCtrlPol", + class_config=dict( + name=bgp_best_path_policy, + ctrl=best_path_control, + descr=description, + nameAlias=name_alias, + ), + ) + + aci.get_diff(aci_class="bgpBestPathCtrlPol") + + aci.post_config() + + elif state == "absent": + aci.delete_config() + + aci.exit_json() + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_bgp_timers_policy.py b/ansible_collections/cisco/aci/plugins/modules/aci_bgp_timers_policy.py new file mode 100644 index 000000000..c8d6c54b0 --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_bgp_timers_policy.py @@ -0,0 +1,330 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2023, Gaspard Micol (@gmicol) <gmicol@cisco.com> +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported_by": "certified"} + +DOCUMENTATION = r""" +--- +module: aci_bgp_timers_policy +short_description: Manage BGP timers policy (bgp:CtxPol) +description: +- Manage BGP timers policies for Tenants on Cisco ACI fabrics. +options: + tenant: + description: + - The name of an existing tenant. + type: str + aliases: [ tenant_name ] + bgp_timers_policy: + description: + - The name of the bgp timers policy. + type: str + aliases: [ bgp_timers_policy_name, name ] + graceful_restart_controls: + description: + - The property to determine whether the entity functions only as a graceful restart helper or whether graceful restart is enabled completely. + - The graceful restart helper option configures the local BGP router to support the graceful restart of a remote BGP peer. + - The complete graceful restart option allows BGP graceful restart to be enabled or disable for an individual neighbor. + - The APIC defaults to C(helper) when unset during creation. + type: str + choices: [ helper, complete ] + hold_interval: + description: + - The time period to wait before declaring the neighbor device down. + - The APIC defaults to C(180) when unset during creation. + type: int + keepalive_interval: + description: + - The interval time between sending keepalive messages. + - The APIC defaults to C(60) when unset during creation. + type: int + max_as_limit: + description: + - The maximum AS limit, to discard routes that have excessive AS numbers. + - The APIC defaults to C(0) when unset during creation. + type: int + stale_interval: + description: + - The maximum time that BGP keeps stale routes from the restarting BGP peer. + - The APIC defaults to C(default) which is equal to 300 sec when unset during creation. + type: int + description: + description: + - Description for the bgp protocol profile. + type: str + aliases: [ descr ] + state: + description: + - Use C(present) or C(absent) for adding or removing. + - Use C(query) for listing an object or multiple objects. + type: str + choices: [ absent, present, query ] + default: present + name_alias: + description: + - The alias for the current object. This relates to the nameAlias field in ACI. + type: str +extends_documentation_fragment: +- cisco.aci.aci +- cisco.aci.annotation +- cisco.aci.owner + +notes: +- The C(tenant) used must exist before using this module in your playbook. + The M(cisco.aci.aci_tenant) module can be used for this. +seealso: +- module: cisco.aci.aci_tenant +- name: APIC Management Information Model reference + description: More information about the internal APIC class B(bgp:CtxPol). + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Gaspard Micol (@gmicol) +""" + +EXAMPLES = r""" +- name: Create a BGP timers policy + cisco.aci.aci_bgp_timers_policy: + host: apic + username: admin + password: SomeSecretPassword + bgp_protocol_profile: my_bgp_timers_policy + graceful_restart_controls: complete + hold_interval: 360 + keepalive_interval: 120 + max_as_limit: 1 + stale_interval: 600 + tenant: production + state: present + delegate_to: localhost + +- name: Delete a BGP timers policy + cisco.aci.aci_bgp_timers_policy: + host: apic + username: admin + password: SomeSecretPassword + bgp_protocol_profile: my_bgp_timers_policy + tenant: production + state: absent + delegate_to: localhost + +- name: Query all BGP timers policies + cisco.aci.aci_bgp_timers_policy: + host: apic + username: admin + password: SomeSecretPassword + state: query + delegate_to: localhost + register: query_result + +- name: Query a specific BGP timers policy + cisco.aci.aci_bgp_timers_policy: + host: apic + username: admin + password: SomeSecretPassword + bgp_protocol_profile: my_bgp_timers_policy + tenant: production + state: query + delegate_to: localhost + register: query_result +""" + +RETURN = r""" +current: + description: The existing configuration from the APIC after the module has finished + returned: success + type: list + sample: + [ + { + "fvTenant": { + "attributes": { + "descr": "Production environment", + "dn": "uni/tn-production", + "name": "production", + "nameAlias": "", + "ownerKey": "", + "ownerTag": "" + } + } + } + ] +error: + description: The error information as returned from the APIC + returned: failure + type: dict + sample: + { + "code": "122", + "text": "unknown managed object class foo" + } +raw: + description: The raw output returned by the APIC REST API (xml or json) + returned: parse error + type: str + sample: '<?xml version="1.0" encoding="UTF-8"?><imdata totalCount="1"><error code="122" text="unknown managed object class foo"/></imdata>' +sent: + description: The actual/minimal configuration pushed to the APIC + returned: info + type: list + sample: + { + "fvTenant": { + "attributes": { + "descr": "Production environment" + } + } + } +previous: + description: The original configuration from the APIC before the module has started + returned: info + type: list + sample: + [ + { + "fvTenant": { + "attributes": { + "descr": "Production", + "dn": "uni/tn-production", + "name": "production", + "nameAlias": "", + "ownerKey": "", + "ownerTag": "" + } + } + } + ] +proposed: + description: The assembled configuration from the user-provided parameters + returned: info + type: dict + sample: + { + "fvTenant": { + "attributes": { + "descr": "Production environment", + "name": "production" + } + } + } +filter_string: + description: The filter string used for the request + returned: failure or debug + type: str + sample: ?rsp-prop-include=config-only +method: + description: The HTTP method used for the request to the APIC + returned: failure or debug + type: str + sample: POST +response: + description: The HTTP response from the APIC + returned: failure or debug + type: str + sample: OK (30 bytes) +status: + description: The HTTP status from the APIC + returned: failure or debug + type: int + sample: 200 +url: + description: The HTTP url used for the request to the APIC + returned: failure or debug + type: str + sample: https://10.11.12.13/api/mo/uni/tn-production.json +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.cisco.aci.plugins.module_utils.aci import ACIModule, aci_argument_spec, aci_annotation_spec, aci_owner_spec +from ansible_collections.cisco.aci.plugins.module_utils.constants import MATCH_GRACEFUL_RESTART_CONTROLS_MAPPING + + +def main(): + argument_spec = aci_argument_spec() + argument_spec.update(aci_annotation_spec()) + argument_spec.update(aci_owner_spec()) + argument_spec.update( + tenant=dict(type="str", aliases=["tenant_name"]), # Not required for querying all objects + bgp_timers_policy=dict(type="str", aliases=["bgp_timers_policy_name", "name"]), # Not required for querying all objects + graceful_restart_controls=dict(type="str", choices=["helper", "complete"]), + hold_interval=dict(type="int"), + keepalive_interval=dict(type="int"), + max_as_limit=dict(type="int"), + stale_interval=dict(type="int"), + description=dict(type="str", aliases=["descr"]), + state=dict(type="str", default="present", choices=["absent", "present", "query"]), + name_alias=dict(type="str"), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_if=[ + ["state", "absent", ["bgp_timers_policy", "tenant"]], + ["state", "present", ["bgp_timers_policy", "tenant"]], + ], + ) + + bgp_timers_policy = module.params.get("bgp_timers_policy") + graceful_restart_controls = MATCH_GRACEFUL_RESTART_CONTROLS_MAPPING.get(module.params.get("graceful_restart_controls")) + hold_interval = module.params.get("hold_interval") + keepalive_interval = module.params.get("keepalive_interval") + max_as_limit = module.params.get("max_as_limit") + stale_interval = module.params.get("stale_interval") + description = module.params.get("description") + state = module.params.get("state") + tenant = module.params.get("tenant") + name_alias = module.params.get("name_alias") + + aci = ACIModule(module) + + aci.construct_url( + root_class=dict( + aci_class="fvTenant", + aci_rn="tn-{0}".format(tenant), + module_object=tenant, + target_filter={"name": tenant}, + ), + subclass_1=dict( + aci_class="bgpCtxPol", + aci_rn="bgpCtxP-{0}".format(bgp_timers_policy), + module_object=bgp_timers_policy, + target_filter={"name": bgp_timers_policy}, + ), + ) + + aci.get_existing() + + if state == "present": + aci.payload( + aci_class="bgpCtxPol", + class_config=dict( + name=bgp_timers_policy, + grCtrl=graceful_restart_controls, + holdIntvl=hold_interval, + kaIntvl=keepalive_interval, + maxAsLimit=max_as_limit, + staleIntvl=stale_interval, + descr=description, + nameAlias=name_alias, + ), + ) + + aci.get_diff(aci_class="bgpCtxPol") + + aci.post_config() + + elif state == "absent": + aci.delete_config() + + aci.exit_json() + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_bulk_static_binding_to_epg.py b/ansible_collections/cisco/aci/plugins/modules/aci_bulk_static_binding_to_epg.py index 6ae4d5d55..f2ed66eda 100644 --- a/ansible_collections/cisco/aci/plugins/modules/aci_bulk_static_binding_to_epg.py +++ b/ansible_collections/cisco/aci/plugins/modules/aci_bulk_static_binding_to_epg.py @@ -503,7 +503,6 @@ def main(): if state == "present" or state == "absent": for interface_config in interface_configs: pod_id = interface_config.get("pod_id") - leafs = interface_config.get("leafs") interface = interface_config.get("interface") extpaths = interface_config.get("extpaths") @@ -517,24 +516,21 @@ def main(): if interface_type in ["fex", "fex_vpc", "fex_port_channel"] and extpaths is None: aci.fail_json(msg="extpaths is required when interface_type is: {0}".format(interface_type)) - if leafs is not None: - # Process leafs, and support dash-delimited leafs - leafs = [] - for leaf in interface_config.get("leafs"): - # Users are likely to use integers for leaf IDs, which would raise an exception when using the join method - leafs.extend(str(leaf).split("-")) - if len(leafs) == 1: - if interface_type in ["vpc", "fex_vpc"]: - aci.fail_json(msg='A interface_type of "vpc" requires 2 leafs') - leafs = leafs[0] - elif len(leafs) == 2: - if interface_type not in ["vpc", "fex_vpc"]: - aci.fail_json( - msg='The interface_types "switch_port", "port_channel", and "fex" do not support using multiple leafs for a single binding' - ) - leafs = "-".join(leafs) - else: - aci.fail_json(msg='The "leafs" parameter must not have more than 2 entries') + # Process leafs, and support dash-delimited leafs + leafs = [] + for leaf in interface_config.get("leafs"): + # Users are likely to use integers for leaf IDs, which would raise an exception when using the join method + leafs.extend(str(leaf).split("-")) + if len(leafs) == 1: + if interface_type in ["vpc", "fex_vpc"]: + aci.fail_json(msg='A interface_type of "vpc" requires 2 leafs') + leafs = leafs[0] + elif len(leafs) == 2: + if interface_type not in ["vpc", "fex_vpc"]: + aci.fail_json(msg='The interface_types "switch_port", "port_channel", and "fex" do not support using multiple leafs for a single binding') + leafs = "-".join(leafs) + else: + aci.fail_json(msg='The "leafs" parameter must not have more than 2 entries') if extpaths is not None: # Process extpaths, and support dash-delimited extpaths diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_config_rollback.py b/ansible_collections/cisco/aci/plugins/modules/aci_config_rollback.py index 86ee44d6e..6d14fd46c 100644 --- a/ansible_collections/cisco/aci/plugins/modules/aci_config_rollback.py +++ b/ansible_collections/cisco/aci/plugins/modules/aci_config_rollback.py @@ -72,6 +72,9 @@ extends_documentation_fragment: - cisco.aci.aci - cisco.aci.annotation +notes: +- It is strongly recommended to add a pause task after creating a Snapshot. +- Wait for the Snapshot to be finished before querying, comparing Snapshots or processing to Rollbacks. seealso: - module: cisco.aci.aci_config_snapshot - name: APIC Management Information Model reference @@ -79,6 +82,7 @@ seealso: link: https://developer.cisco.com/docs/apic-mim-ref/ author: - Jacob McGill (@jmcgill298) +- Gaspard Micol (@gmicol) """ EXAMPLES = r""" @@ -92,6 +96,10 @@ EXAMPLES = r""" state: present delegate_to: localhost +- name: Wait for snapshot to be finished before querying + pause: + seconds: 10 + - name: Query Existing Snapshots cisco.aci.aci_config_snapshot: host: apic @@ -189,7 +197,6 @@ url: from ansible_collections.cisco.aci.plugins.module_utils.aci import ACIModule, aci_argument_spec, aci_annotation_spec from ansible.module_utils.basic import AnsibleModule from ansible.module_utils._text import to_bytes -from ansible.module_utils.urls import fetch_url # Optional, only used for rollback preview try: @@ -276,11 +283,17 @@ def main(): aci.post_config() elif state == "preview": - aci.url = "%(protocol)s://%(host)s/mqapi2/snapshots.diff.xml" % module.params + aci.path = "mqapi2/snapshots.diff.xml" + preview_params = {"path": aci.path} + preview_params.update(module.params.items()) + if aci.params.get("port") is not None: + aci.url = "{protocol}://{host}:{port}/{path}".format_map(preview_params) + else: + aci.url = "{protocol}://{host}/{path}".format_map(preview_params) aci.filter_string = ( - "?s1dn=uni/backupst/snapshots-[uni/fabric/configexp-%(export_policy)s]/snapshot-%(snapshot)s&" - "s2dn=uni/backupst/snapshots-[uni/fabric/configexp-%(compare_export_policy)s]/snapshot-%(compare_snapshot)s" - ) % module.params + "?s1dn=uni/backupst/snapshots-[uni/fabric/configexp-{export_policy}]/snapshot-{snapshot}&" + "s2dn=uni/backupst/snapshots-[uni/fabric/configexp-{compare_export_policy}]/snapshot-{compare_snapshot}" + ).format_map(module.params) # Generate rollback comparison get_preview(aci) @@ -293,19 +306,23 @@ def get_preview(aci): This function is used to generate a preview between two snapshots and add the parsed results to the aci module return data. """ uri = aci.url + aci.filter_string - resp, info = fetch_url( - aci.module, uri, headers=aci.headers, method="GET", timeout=aci.module.params.get("timeout"), use_proxy=aci.module.params.get("use_proxy") - ) - aci.method = "GET" - aci.response = info.get("msg") - aci.status = info.get("status") + + resp, info = aci.api_call("GET", uri, data=None, return_response=True) # Handle APIC response if info.get("status") == 200: - xml_to_json(aci, resp.read()) + try: + xml_to_json(aci, resp.read()) + except AttributeError: + xml_to_json(aci, info.get("body")) else: - aci.result["raw"] = resp.read() - aci.fail_json(msg="Request failed: %(code)s %(text)s (see 'raw' output)" % aci.error) + try: + # APIC error + aci.response_xml(info["body"]) + aci.fail_json(msg="APIC Error {code}: {text}".format_map(aci.error)) + except KeyError: + # Connection error + aci.fail_json(msg="Connection failed for {url}. {msg}".format_map(info)) def xml_to_json(aci, response_data): diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_config_snapshot.py b/ansible_collections/cisco/aci/plugins/modules/aci_config_snapshot.py index 020b456b4..bc36a5e62 100644 --- a/ansible_collections/cisco/aci/plugins/modules/aci_config_snapshot.py +++ b/ansible_collections/cisco/aci/plugins/modules/aci_config_snapshot.py @@ -64,6 +64,8 @@ notes: - The APIC does not provide a mechanism for naming the snapshots. - 'Snapshot files use the following naming structure: ce_<config export policy name>-<yyyy>-<mm>-<dd>T<hh>:<mm>:<ss>.<mss>+<hh>:<mm>.' - 'Snapshot objects use the following naming structure: run-<yyyy>-<mm>-<dd>T<hh>-<mm>-<ss>.' +- It is strongly recommended to add a pause task after creating a Snapshot. +- Wait for the Snapshot to be finished before querying, comparing Snapshots or processing to Rollbacks. seealso: - module: cisco.aci.aci_config_rollback - name: APIC Management Information Model reference @@ -71,6 +73,7 @@ seealso: link: https://developer.cisco.com/docs/apic-mim-ref/ author: - Jacob McGill (@jmcgill298) +- Gaspard Micol (@gmicol) """ EXAMPLES = r""" @@ -85,6 +88,10 @@ EXAMPLES = r""" description: Backups taken before new configs are applied. delegate_to: localhost +- name: Wait for snapshot to be finished before querying + pause: + seconds: 10 + - name: Query all Snapshots cisco.aci.aci_config_snapshot: host: apic @@ -272,8 +279,7 @@ def main(): target_filter={"name": export_policy}, ), ) - # Variable set for reuse of correct url in get_existing() triggered from exit_json() after query request - reset_url = aci.url + aci.get_existing() aci.payload( @@ -295,13 +301,9 @@ def main(): aci.post_config() # Query for job information and add to results - # Change state to query else aci.request() will not execute a GET request but POST - aci.params["state"] = "query" - aci.request(path="/api/node/mo/uni/backupst/jobs-[uni/fabric/configexp-{0}].json".format(export_policy)) - aci.result["job_details"] = aci.imdata[0].get("configJobCont", {}) - # Reset state and url to display correct in output and trigger get_existing() function with correct url - aci.url = reset_url - aci.params["state"] = "present" + path = "api/node/mo/uni/backupst/jobs-[uni/fabric/configexp-{0}].json".format(export_policy) + aci.api_call("GET", url="{0}/{1}".format(aci.base_url, path)) + aci.result["job_details"] = aci.existing[0].get("configJobCont", {}) else: # Prefix the proper url to export_policy diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_dhcp_relay.py b/ansible_collections/cisco/aci/plugins/modules/aci_dhcp_relay.py index a1dfc32e9..9bb8267dd 100644 --- a/ansible_collections/cisco/aci/plugins/modules/aci_dhcp_relay.py +++ b/ansible_collections/cisco/aci/plugins/modules/aci_dhcp_relay.py @@ -2,6 +2,7 @@ # -*- coding: utf-8 -*- # Copyright: (c) 2022, Tim Cragg (@timcragg) +# Copyright: (c) 2023, Akini Ross (@akinross) # GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) from __future__ import absolute_import, division, print_function @@ -20,6 +21,7 @@ options: tenant: description: - Name of an existing tenant. + - When tenant is not provided the module will be applied to the global (infra) policy. type: str name: description: @@ -50,6 +52,7 @@ seealso: link: https://developer.cisco.com/docs/apic-mim-ref/ author: - Tim Cragg (@timcragg) +- Akini Ross (@akinross) """ EXAMPLES = r""" @@ -64,6 +67,16 @@ EXAMPLES = r""" state: present delegate_to: localhost +- name: Add a new global (infra) DHCP relay policy + cisco.aci.aci_dhcp_relay: + host: apic + username: admin + password: SomeSecretPassword + name: my_dhcp_relay + description: via Ansible + state: present + delegate_to: localhost + - name: Remove a DHCP relay policy cisco.aci.aci_dhcp_relay: host: apic @@ -74,6 +87,15 @@ EXAMPLES = r""" state: absent delegate_to: localhost +- name: Remove a global (infra) DHCP relay policy + cisco.aci.aci_dhcp_relay: + host: apic + username: admin + password: SomeSecretPassword + name: my_dhcp_relay + state: absent + delegate_to: localhost + - name: Query a DHCP relay policy cisco.aci.aci_dhcp_relay: host: apic @@ -85,6 +107,16 @@ EXAMPLES = r""" delegate_to: localhost register: query_result +- name: Query a global (infra) DHCP relay policy + cisco.aci.aci_dhcp_relay: + host: apic + username: admin + password: SomeSecretPassword + name: my_dhcp_relay + state: query + delegate_to: localhost + register: query_result + - name: Query all DHCP relay policies in a specific tenant cisco.aci.aci_dhcp_relay: host: apic @@ -94,6 +126,16 @@ EXAMPLES = r""" state: query delegate_to: localhost register: query_result + +- name: Query all DHCP relay policies + cisco.aci.aci_dhcp_relay: + host: apic + username: admin + password: SomeSecretPassword + state: query + delegate_to: localhost + register: query_result + """ RETURN = r""" @@ -219,8 +261,8 @@ def main(): argument_spec=argument_spec, supports_check_mode=True, required_if=[ - ["state", "absent", ["name", "tenant"]], - ["state", "present", ["name", "tenant"]], + ["state", "absent", ["name"]], + ["state", "present", ["name"]], ], ) @@ -230,29 +272,40 @@ def main(): tenant = module.params.get("tenant") child_classes = ["dhcpRsProv"] - aci = ACIModule(module) - aci.construct_url( - root_class=dict( + if tenant is None: + root_class = dict( + aci_class="dhcpRelayP", + aci_rn="infra/relayp-{0}".format(name), + module_object=name, + target_filter={"name": name}, + ) + subclass_1 = None + owner = "infra" + else: + root_class = dict( aci_class="fvTenant", aci_rn="tn-{0}".format(tenant), module_object=tenant, target_filter={"name": tenant}, - ), - subclass_1=dict( + ) + subclass_1 = dict( aci_class="dhcpRelayP", aci_rn="relayp-{0}".format(name), module_object=name, target_filter={"name": name}, - ), - child_classes=child_classes, - ) + ) + owner = "tenant" + + aci = ACIModule(module) + + aci.construct_url(root_class=root_class, subclass_1=subclass_1, child_classes=child_classes) aci.get_existing() if state == "present": aci.payload( aci_class="dhcpRelayP", - class_config=dict(name=name, descr=description, owner="tenant"), + class_config=dict(name=name, descr=description, owner=owner), ) aci.get_diff(aci_class="dhcpRelayP") diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_dhcp_relay_provider.py b/ansible_collections/cisco/aci/plugins/modules/aci_dhcp_relay_provider.py index fefebb6ad..efb9e8e54 100644 --- a/ansible_collections/cisco/aci/plugins/modules/aci_dhcp_relay_provider.py +++ b/ansible_collections/cisco/aci/plugins/modules/aci_dhcp_relay_provider.py @@ -2,6 +2,7 @@ # -*- coding: utf-8 -*- # Copyright: (c) 2022, Tim Cragg (@timcragg) +# Copyright: (c) 2023, Akini Ross (@akinross) # GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) from __future__ import absolute_import, division, print_function @@ -20,6 +21,7 @@ options: tenant: description: - Name of the tenant the relay_policy is in. + - When tenant is not provided the module will be applied to the global (infra) policy. type: str relay_policy: description: @@ -29,14 +31,14 @@ options: provider_tenant: description: - Name of the tenant the epg or external_epg is in - - Only required if the epg or external_epg is in a different tenant than the relay_policy + - Required when epg or external_epg is in a different tenant than the relay_policy + - Required when global (infra) relay_policy is configured with epg or external_epg types type: str epg_type: description: - Type of EPG the DHCP server is in type: str choices: [ epg, l2_external, l3_external, dn ] - required: true anp: description: - Application Profile the EPG is in. @@ -93,6 +95,7 @@ seealso: link: https://developer.cisco.com/docs/apic-mim-ref/ author: - Tim Cragg (@timcragg) +- Akini Ross (@akinross) """ EXAMPLES = r""" @@ -110,6 +113,20 @@ EXAMPLES = r""" state: present delegate_to: localhost +- name: Add a new Global (infra) DHCP relay App EPG provider + cisco.aci.aci_dhcp_relay_provider: + host: apic + username: admin + password: SomeSecretPassword + relay_policy: my_dhcp_relay + provider_tenant: Auto-Demo + epg_type: epg + anp: my_anp + epg: my_app_epg + dhcp_server_addr: 10.20.30.40 + state: present + delegate_to: localhost + - name: Add a new DHCP relay L3out provider cisco.aci.aci_dhcp_relay_provider: host: apic @@ -137,6 +154,19 @@ EXAMPLES = r""" state: absent delegate_to: localhost +- name: Remove a Global (infra) DHCP relay provider + cisco.aci.aci_dhcp_relay_provider: + host: apic + username: admin + password: SomeSecretPassword + relay_policy: my_dhcp_relay + provider_tenant: Auto-Demo + epg_type: epg + anp: my_anp + epg: my_app_epg + state: absent + delegate_to: localhost + - name: Query a DHCP relay provider cisco.aci.aci_dhcp_relay_provider: host: apic @@ -150,6 +180,39 @@ EXAMPLES = r""" state: query delegate_to: localhost register: query_result + +- name: Query a Global (infra) DHCP relay provider + cisco.aci.aci_dhcp_relay_provider: + host: apic + username: admin + password: SomeSecretPassword + relay_policy: my_dhcp_relay + provider_tenant: Auto-Demo + epg_type: epg + anp: my_anp + epg: my_app_epg + state: query + delegate_to: localhost + register: query_result + +- name: Query all DHCP relay providers in a specific tenant + cisco.aci.aci_dhcp_relay_provider: + host: apic + username: admin + password: SomeSecretPassword + tenant: Auto-Demo + state: query + delegate_to: localhost + register: query_result + +- name: Query all DHCP relay providers + cisco.aci.aci_dhcp_relay_provider: + host: apic + username: admin + password: SomeSecretPassword + state: query + delegate_to: localhost + register: query_result """ RETURN = r""" @@ -266,7 +329,7 @@ def main(): argument_spec.update(aci_annotation_spec()) argument_spec.update( relay_policy=dict(type="str", aliases=["relay_policy_name"]), - epg_type=dict(type="str", required=True, choices=["epg", "l2_external", "l3_external", "dn"]), + epg_type=dict(type="str", choices=["epg", "l2_external", "l3_external", "dn"]), anp=dict(type="str"), epg=dict(type="str", aliases=["app_epg"]), l2out_name=dict(type="str"), @@ -283,8 +346,8 @@ def main(): argument_spec=argument_spec, supports_check_mode=True, required_if=[ - ["state", "absent", ["relay_policy", "tenant"]], - ["state", "present", ["relay_policy", "tenant"]], + ["state", "absent", ["relay_policy", "epg_type"]], + ["state", "present", ["relay_policy", "epg_type"]], ["epg_type", "epg", ["anp", "epg"]], ["epg_type", "l2_external", ["l2out_name", "external_epg"]], ["epg_type", "l3_external", ["l3out_name", "external_epg"]], @@ -322,6 +385,9 @@ def main(): if provider_tenant is None: provider_tenant = tenant + if epg_type is not None and epg_type != "dn" and provider_tenant is None: + module.fail_json(msg="provider_tenant is required when epg_type is {0}".format(epg_type)) + if epg_type == "epg": tdn = "uni/tn-{0}/ap-{1}/epg-{2}".format(provider_tenant, anp, epg) elif epg_type == "l2_external": @@ -330,28 +396,46 @@ def main(): tdn = "uni/tn-{0}/out-{1}/instP-{2}".format(provider_tenant, l3out_name, external_epg) elif epg_type == "dn": tdn = dn + else: + tdn = None - aci = ACIModule(module) - aci.construct_url( - root_class=dict( + if tenant is None: + root_class = dict( + aci_class="dhcpRelayP", + aci_rn="infra/relayp-{0}".format(relay_policy), + module_object=relay_policy, + target_filter={"name": relay_policy}, + ) + subclass_1 = dict( + aci_class="dhcpRsProv", + aci_rn="rsprov-[{0}]".format(tdn), + module_object=tdn, + target_filter={"tDn": tdn}, + ) + subclass_2 = None + else: + root_class = dict( aci_class="fvTenant", aci_rn="tn-{0}".format(tenant), module_object=tenant, target_filter={"name": tenant}, - ), - subclass_1=dict( + ) + subclass_1 = dict( aci_class="dhcpRelayP", aci_rn="relayp-{0}".format(relay_policy), module_object=relay_policy, target_filter={"name": relay_policy}, - ), - subclass_2=dict( + ) + subclass_2 = dict( aci_class="dhcpRsProv", aci_rn="rsprov-[{0}]".format(tdn), module_object=tdn, target_filter={"tDn": tdn}, - ), - ) + ) + + aci = ACIModule(module) + + aci.construct_url(root_class=root_class, subclass_1=subclass_1, subclass_2=subclass_2) aci.get_existing() diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_domain_to_encap_pool.py b/ansible_collections/cisco/aci/plugins/modules/aci_domain_to_encap_pool.py index 1fe5e8638..bf573d208 100644 --- a/ansible_collections/cisco/aci/plugins/modules/aci_domain_to_encap_pool.py +++ b/ansible_collections/cisco/aci/plugins/modules/aci_domain_to_encap_pool.py @@ -237,16 +237,8 @@ url: from ansible.module_utils.basic import AnsibleModule from ansible_collections.cisco.aci.plugins.module_utils.aci import ACIModule, aci_argument_spec, aci_annotation_spec +from ansible_collections.cisco.aci.plugins.module_utils.constants import VM_PROVIDER_MAPPING -VM_PROVIDER_MAPPING = dict( - cloudfoundry="CloudFoundry", - kubernetes="Kubernetes", - microsoft="Microsoft", - openshift="OpenShift", - openstack="OpenStack", - redhat="Redhat", - vmware="VMware", -) POOL_MAPPING = dict( vlan=dict( @@ -274,7 +266,7 @@ def main(): pool=dict(type="str", aliases=["pool_name"]), # Not required for querying all objects pool_allocation_mode=dict(type="str", aliases=["allocation_mode", "mode"], choices=["dynamic", "static"]), state=dict(type="str", default="present", choices=["absent", "present", "query"]), - vm_provider=dict(type="str", choices=["cloudfoundry", "kubernetes", "microsoft", "openshift", "openstack", "redhat", "vmware"]), + vm_provider=dict(type="str", choices=list(VM_PROVIDER_MAPPING.keys())), ) module = AnsibleModule( diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_domain_to_vlan_pool.py b/ansible_collections/cisco/aci/plugins/modules/aci_domain_to_vlan_pool.py index 28008b712..802f2f3a3 100644 --- a/ansible_collections/cisco/aci/plugins/modules/aci_domain_to_vlan_pool.py +++ b/ansible_collections/cisco/aci/plugins/modules/aci_domain_to_vlan_pool.py @@ -253,16 +253,7 @@ url: from ansible.module_utils.basic import AnsibleModule from ansible_collections.cisco.aci.plugins.module_utils.aci import ACIModule, aci_argument_spec, aci_annotation_spec - -VM_PROVIDER_MAPPING = dict( - cloudfoundry="CloudFoundry", - kubernetes="Kubernetes", - microsoft="Microsoft", - openshift="OpenShift", - openstack="OpenStack", - redhat="Redhat", - vmware="VMware", -) +from ansible_collections.cisco.aci.plugins.module_utils.constants import VM_PROVIDER_MAPPING def main(): @@ -274,7 +265,7 @@ def main(): pool=dict(type="str", aliases=["pool_name", "vlan_pool"]), # Not required for querying all objects pool_allocation_mode=dict(type="str", required=True, aliases=["allocation_mode", "mode"], choices=["dynamic", "static"]), state=dict(type="str", default="present", choices=["absent", "present", "query"]), - vm_provider=dict(type="str", choices=["cloudfoundry", "kubernetes", "microsoft", "openshift", "openstack", "redhat", "vmware"]), + vm_provider=dict(type="str", choices=list(VM_PROVIDER_MAPPING.keys())), ) module = AnsibleModule( @@ -320,7 +311,7 @@ def main(): domain_class = "physDomP" domain_mo = "uni/phys-{0}".format(domain) domain_rn = "phys-{0}".format(domain) - elif domain_type == "vmm": + else: domain_class = "vmmDomP" domain_mo = "uni/vmmp-{0}/dom-{1}".format(VM_PROVIDER_MAPPING[vm_provider], domain) domain_rn = "vmmp-{0}/dom-{1}".format(VM_PROVIDER_MAPPING[vm_provider], domain) diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_encap_pool_range.py b/ansible_collections/cisco/aci/plugins/modules/aci_encap_pool_range.py index 577fc996c..43d34e78d 100644 --- a/ansible_collections/cisco/aci/plugins/modules/aci_encap_pool_range.py +++ b/ansible_collections/cisco/aci/plugins/modules/aci_encap_pool_range.py @@ -380,7 +380,7 @@ def main(): elif pool_type == "vxlan": if not 5000 <= encap_id <= 16777215: module.fail_json(msg='vxlan pools must have "range_start" and "range_end" values between 5000 and 16777215') - elif pool_type == "vsan": + else: if not 1 <= encap_id <= 4093: module.fail_json(msg='vsan pools must have "range_start" and "range_end" values between 1 and 4093') diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_epg_subnet.py b/ansible_collections/cisco/aci/plugins/modules/aci_epg_subnet.py new file mode 100644 index 000000000..67a3dda5d --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_epg_subnet.py @@ -0,0 +1,470 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2023, Gaspard MICOL (@gmicol) <gmicol@cisco.com> + +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported_by": "certified"} + +DOCUMENTATION = r""" +--- +module: aci_epg_subnet +short_description: Manage EPG Subnets (fv:Subnet) +description: +- Manage EPG Subnets on Cisco ACI fabrics. +options: + tenant: + description: + - The name of the Tenant. + type: str + aliases: [ tenant_name ] + ap: + description: + - Name of an existing application network profile, that will contain the EPGs. + type: str + aliases: [ app_profile, app_profile_name ] + epg: + description: + - Name of the end point group. + type: str + aliases: [ epg_name, name ] + description: + description: + - The description for the Subnet. + type: str + aliases: [ descr ] + enable_vip: + description: + - Determines if the Subnet should be treated as a VIP (virtual IP address). + - The APIC defaults to C(false) when unset during creation. + type: bool + gateway: + description: + - The IPv4 or IPv6 gateway address for the Subnet. + type: str + aliases: [ gateway_ip ] + mask: + description: + - The subnet mask for the Subnet. + - This is the number associated with CIDR notation. + - For IPv4 addresses, accepted values range between C(0) and C(32). + - For IPv6 addresses, accepted Values range between C(0) and C(128). + type: int + aliases: [ subnet_mask ] + nd_prefix_policy: + description: + - The IPv6 Neighbor Discovery Prefix Policy to associate with the Subnet. + type: str + preferred: + description: + - Determines if the Subnet is preferred over all available Subnets. Only one Subnet per Address Family (IPv4/IPv6) can be preferred in the Bridge Domain. + - The APIC defaults to C(false) when unset during creation. + type: bool + route_profile: + description: + - The Route Profile to the associate with the Subnet. + type: str + route_profile_l3out: + description: + - The L3Out that contains the associated Route Profile. + type: str + scope: + description: + - Determines the scope of the Subnet. + - The C(private) option only allows communication with hosts in the same VRF. + - The C(public) option allows the Subnet to be advertised outside of the ACI Fabric, and allows communication with hosts in other VRFs. + - The shared option limits communication to hosts in either the same VRF or the shared VRF. + - The value is a list of options, C(private) and C(public) are mutually exclusive, but both can be used with C(shared). + - The APIC defaults to C(private) when unset during creation. + type: list + elements: str + choices: [ private, public, shared ] + subnet_control: + description: + - Determines the Subnet's Control State. + - The C(querier_ip) option is used to treat the gateway_ip as an IGMP querier source IP. + - The C(nd_ra) option is used to treat the gateway_ip address as a Neighbor Discovery Router Advertisement Prefix. + - The C(no_gw) option is used to remove default gateway functionality from the gateway address. + - The APIC defaults to C(nd_ra) when unset during creation. + type: str + choices: [ nd_ra_prefix, no_default_gateway, querier_ip, unspecified ] + subnet_name: + description: + - The name of the Subnet. + type: str + aliases: [ name ] + ip_data_plane_learning: + description: + - Whether IP data plane learning is enabled or disabled. + - The APIC defaults to C(enabled) when unset during creation. + type: str + choices: [ enabled, disabled ] + aliases: [ ip_dataplane_learning ] + state: + description: + - Use C(present) or C(absent) for adding or removing. + - Use C(query) for listing an object or multiple objects. + type: str + choices: [ absent, present, query ] + default: present + name_alias: + description: + - The alias for the current object. This relates to the nameAlias field in ACI. + type: str +extends_documentation_fragment: +- cisco.aci.aci +- cisco.aci.annotation + +notes: +- The C(gateway) parameter is the root key used to access the Subnet (not name), so the C(gateway) + is required when the state is C(absent) or C(present). +- The C(tenant), C(ap) and C(epg) used must exist before using this module in your playbook. + The M(cisco.aci.aci_tenant) module, M(cisco.aci.aci_ap) and M(cisco.aci.aci_epg) can be used for these. +seealso: +- module: cisco.aci.aci_epg +- module: cisco.aci.aci_ap +- module: cisco.aci.aci_tenant +- name: APIC Management Information Model reference + description: More information about the internal APIC class B(fv:Subnet). + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Gaspard MICOL (@gmicol) +""" + +EXAMPLES = r""" +- name: Create a subnet + cisco.aci.aci_epg_subnet: + host: apic + username: admin + password: SomeSecretPassword + tenant: production + ap: intranet + epg: web_epg + gateway: 10.1.1.1 + mask: 24 + state: present + delegate_to: localhost + +- name: Create a subnet with options + cisco.aci.aci_epg_subnet: + host: apic + username: admin + password: SomeSecretPassword + tenant: production + ap: intranet + epg: web_epg + subnet_name: sql + gateway: 10.1.2.1 + mask: 23 + description: SQL Servers + scope: public + route_profile_l3out: corp + route_profile: corp_route_profile + state: present + delegate_to: localhost + +- name: Update a subnets scope to private and shared + cisco.aci.aci_epg_subnet: + host: apic + username: admin + password: SomeSecretPassword + tenant: production + ap: intranet + epg: web_epg + gateway: 10.1.1.1 + mask: 24 + scope: [private, shared] + state: present + delegate_to: localhost + +- name: Get all subnets + cisco.aci.aci_epg_subnet: + host: apic + username: admin + password: SomeSecretPassword + state: query + delegate_to: localhost + +- name: Get all subnets of specific gateway in specified tenant + cisco.aci.aci_epg_subnet: + host: apic + username: admin + password: SomeSecretPassword + tenant: production + gateway: 10.1.1.1 + mask: 24 + state: query + delegate_to: localhost + register: query_result + +- name: Get specific subnet + cisco.aci.aci_epg_subnet: + host: apic + username: admin + password: SomeSecretPassword + tenant: production + ap: intranet + epg: web_epg + gateway: 10.1.1.1 + mask: 24 + state: query + delegate_to: localhost + register: query_result + +- name: Delete a subnet + cisco.aci.aci_epg_subnet: + host: apic + username: admin + password: SomeSecretPassword + tenant: production + ap: intranet + epg: web_epg + gateway: 10.1.1.1 + mask: 24 + state: absent + delegate_to: localhost +""" + +RETURN = r""" +current: + description: The existing configuration from the APIC after the module has finished + returned: success + type: list + sample: + [ + { + "fvTenant": { + "attributes": { + "descr": "Production environment", + "dn": "uni/tn-production", + "name": "production", + "nameAlias": "", + "ownerKey": "", + "ownerTag": "" + } + } + } + ] +error: + description: The error information as returned from the APIC + returned: failure + type: dict + sample: + { + "code": "122", + "text": "unknown managed object class foo" + } +raw: + description: The raw output returned by the APIC REST API (xml or json) + returned: parse error + type: str + sample: '<?xml version="1.0" encoding="UTF-8"?><imdata totalCount="1"><error code="122" text="unknown managed object class foo"/></imdata>' +sent: + description: The actual/minimal configuration pushed to the APIC + returned: info + type: list + sample: + { + "fvTenant": { + "attributes": { + "descr": "Production environment" + } + } + } +previous: + description: The original configuration from the APIC before the module has started + returned: info + type: list + sample: + [ + { + "fvTenant": { + "attributes": { + "descr": "Production", + "dn": "uni/tn-production", + "name": "production", + "nameAlias": "", + "ownerKey": "", + "ownerTag": "" + } + } + } + ] +proposed: + description: The assembled configuration from the user-provided parameters + returned: info + type: dict + sample: + { + "fvTenant": { + "attributes": { + "descr": "Production environment", + "name": "production" + } + } + } +filter_string: + description: The filter string used for the request + returned: failure or debug + type: str + sample: ?rsp-prop-include=config-only +method: + description: The HTTP method used for the request to the APIC + returned: failure or debug + type: str + sample: POST +response: + description: The HTTP response from the APIC + returned: failure or debug + type: str + sample: OK (30 bytes) +status: + description: The HTTP status from the APIC + returned: failure or debug + type: int + sample: 200 +url: + description: The HTTP url used for the request to the APIC + returned: failure or debug + type: str + sample: https://10.11.12.13/api/mo/uni/tn-production.json +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.cisco.aci.plugins.module_utils.constants import SUBNET_CONTROL_MAPPING +from ansible_collections.cisco.aci.plugins.module_utils.aci import ACIModule, aci_argument_spec, aci_annotation_spec + + +def main(): + argument_spec = aci_argument_spec() + argument_spec.update(aci_annotation_spec()) + argument_spec.update( + tenant=dict(type="str", aliases=["tenant_name"]), # Not required for querying all objects + epg=dict(type="str", aliases=["epg_name", "name"]), # Not required for querying all objects + ap=dict(type="str", aliases=["app_profile", "app_profile_name"]), # Not required for querying all objects + description=dict(type="str", aliases=["descr"]), + enable_vip=dict(type="bool"), + gateway=dict(type="str", aliases=["gateway_ip"]), # Not required for querying all objects + mask=dict(type="int", aliases=["subnet_mask"]), # Not required for querying all objects + subnet_name=dict(type="str", aliases=["name"]), + nd_prefix_policy=dict(type="str"), + preferred=dict(type="bool"), + route_profile=dict(type="str"), + route_profile_l3out=dict(type="str"), + scope=dict(type="list", elements="str", choices=["private", "public", "shared"]), + subnet_control=dict(type="str", choices=list(SUBNET_CONTROL_MAPPING.keys())), + state=dict(type="str", default="present", choices=["absent", "present", "query"]), + ip_data_plane_learning=dict(type="str", choices=["enabled", "disabled"], aliases=["ip_dataplane_learning"]), + name_alias=dict(type="str"), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_together=[["gateway", "mask"], ["route_profile_l3out", "route_profile"]], + required_if=[ + ["state", "present", ["epg", "ap", "gateway", "mask", "tenant"]], + ["state", "absent", ["epg", "ap", "gateway", "mask", "tenant"]], + ], + ) + + aci = ACIModule(module) + + description = module.params.get("description") + enable_vip = aci.boolean(module.params.get("enable_vip")) + epg = module.params.get("epg") + ap = module.params.get("ap") + tenant = module.params.get("tenant") + gateway = module.params.get("gateway") + mask = module.params.get("mask") + if mask is not None and mask not in range(0, 129): + # TODO: split checks between IPv4 and IPv6 Addresses + module.fail_json(msg="Valid Subnet Masks are 0 to 32 for IPv4 Addresses and 0 to 128 for IPv6 addresses") + if gateway is not None: + gateway = "{0}/{1}".format(gateway, mask) + subnet_name = module.params.get("subnet_name") + nd_prefix_policy = module.params.get("nd_prefix_policy") + preferred = aci.boolean(module.params.get("preferred")) + route_profile = module.params.get("route_profile") + route_profile_l3out = module.params.get("route_profile_l3out") + scope = module.params.get("scope") + ip_data_plane_learning = module.params.get("ip_data_plane_learning") + + if scope is not None: + if "private" in scope and "public" in scope: + module.fail_json(msg="Parameter 'scope' cannot be both 'private' and 'public', got: %s" % scope) + else: + scope = ",".join(sorted(scope)) + state = module.params.get("state") + subnet_control = module.params.get("subnet_control") + if subnet_control: + subnet_control = SUBNET_CONTROL_MAPPING[subnet_control] + name_alias = module.params.get("name_alias") + + aci.construct_url( + root_class=dict( + aci_class="fvTenant", + aci_rn="tn-{0}".format(tenant), + module_object=tenant, + target_filter={"name": tenant}, + ), + subclass_1=dict( + aci_class="fvAp", + aci_rn="ap-{0}".format(ap), + module_object=ap, + target_filter={"name": ap}, + ), + subclass_2=dict( + aci_class="fvAEPg", + aci_rn="epg-{0}".format(epg), + module_object=epg, + target_filter={"name": epg}, + ), + subclass_3=dict( + aci_class="fvSubnet", + aci_rn="subnet-[{0}]".format(gateway), + module_object=gateway, + target_filter={"ip": gateway}, + ), + child_classes=["fvRsBDSubnetToProfile", "fvRsNdPfxPol"], + ) + + aci.get_existing() + + if state == "present": + aci.payload( + aci_class="fvSubnet", + class_config=dict( + ctrl=subnet_control, + descr=description, + ip=gateway, + name=subnet_name, + preferred=preferred, + scope=scope, + virtual=enable_vip, + nameAlias=name_alias, + ipDPLearning=ip_data_plane_learning, + ), + child_configs=[ + {"fvRsBDSubnetToProfile": {"attributes": {"tnL3extOutName": route_profile_l3out, "tnRtctrlProfileName": route_profile}}}, + {"fvRsNdPfxPol": {"attributes": {"tnNdPfxPolName": nd_prefix_policy}}}, + ], + ) + + aci.get_diff(aci_class="fvSubnet") + + aci.post_config() + + elif state == "absent": + aci.delete_config() + + aci.exit_json() + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_fabric_interface_policy_group.py b/ansible_collections/cisco/aci/plugins/modules/aci_fabric_interface_policy_group.py new file mode 100644 index 000000000..4048187db --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_fabric_interface_policy_group.py @@ -0,0 +1,364 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2023, Sabari Jaganathan <sajagana@cisco.com> +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported_by": "certified"} + +DOCUMENTATION = r""" +--- +module: aci_fabric_interface_policy_group +short_description: Manage Fabric Interface Policy Groups (fabric:LePortPGrp, fabric:SpPortPGrp) +description: +- Manage Fabric Interface Policy Groups on Cisco ACI fabrics. +options: + name: + description: + - The name of the Fabric Leaf or Spine Interface Policy Group. + type: str + aliases: [ policy_group ] + description: + description: + - The description of the Fabric Leaf or Spine Interface Policy Group. + type: str + aliases: [ descr ] + type: + description: + - The type of the Fabric Leaf or Spine Interface Policy Group. + - Use C(leaf) to create a Fabric Leaf Interface Policy Group. + - Use C(spine) to create a Fabric Spine Interface Policy Group. + type: str + aliases: [ policy_group_type ] + choices: [ leaf, spine ] + required: true + dwdm_policy: + description: + - The name of the DWDM policy to bind to the Fabric Leaf or Spine Interface Policy Group. + type: str + link_level_policy: + description: + - The name of the Link Level policy to bind to the Fabric Leaf or Spine Interface Policy Group. + type: str + link_flap_policy: + description: + - The name of the Link Flap policy to bind to the Fabric Leaf or Spine Interface Policy Group. + type: str + l3_interface_policy: + description: + - The name of the L3 Interface policy to bind to the Fabric Leaf or Spine Interface Policy Group. + type: str + macsec_policy: + description: + - The name of the MACSec policy to bind to the Fabric Leaf or Spine Interface Policy Group. + type: str + monitoring_policy: + description: + - The name of the Monitoring policy to bind to the Fabric Leaf or Spine Interface Policy Group. + type: str + transceiver_policy_tdn: + description: + - The target Dn of the Transceiver policy to bind to the Fabric Leaf or Spine Interface Policy Group. + - The Transceiver policy group is only compatible with ACI versions 6.0(2h) and higher. + type: str + state: + description: + - Use C(present) or C(absent) for adding or removing. + - Use C(query) for listing an object or multiple objects. + type: str + choices: [ absent, present, query ] + default: present + name_alias: + description: + - The alias for the current object. This relates to the nameAlias field in ACI. + type: str +extends_documentation_fragment: +- cisco.aci.aci +- cisco.aci.annotation +- cisco.aci.owner + +seealso: +- name: APIC Management Information Model reference + description: More information about the internal APIC class B(fabric:LePortPGrp, fabric:SpPortPGrp). + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Sabari Jaganathan (@sajagana) +""" + +EXAMPLES = r""" +- name: Add a Fabric Leaf Policy Group + cisco.aci.aci_fabric_interface_policy_group: + host: apic + username: admin + password: SomeSecretPassword + name: leaf_policy_group + type: leaf + state: present + delegate_to: localhost + +- name: Query a Fabric Leaf Policy Group with name + cisco.aci.aci_fabric_interface_policy_group: + host: apic + username: admin + password: SomeSecretPassword + name: leaf_policy_group + type: leaf + state: query + delegate_to: localhost + register: query_result + +- name: Query all Fabric Leaf Policy Groups + cisco.aci.aci_fabric_interface_policy_group: + host: apic + username: admin + password: SomeSecretPassword + type: leaf + state: query + delegate_to: localhost + register: query_result + +- name: Remove a Fabric Leaf Policy Group + cisco.aci.aci_fabric_interface_policy_group: + host: apic + username: admin + password: SomeSecretPassword + name: leaf_policy_group + type: leaf + state: absent + delegate_to: localhost +""" + +RETURN = r""" +current: + description: The existing configuration from the APIC after the module has finished + returned: success + type: list + sample: + [ + { + "fvTenant": { + "attributes": { + "descr": "Production environment", + "dn": "uni/tn-production", + "name": "production", + "nameAlias": "", + "ownerKey": "", + "ownerTag": "" + } + } + } + ] +error: + description: The error information as returned from the APIC + returned: failure + type: dict + sample: + { + "code": "122", + "text": "unknown managed object class foo" + } +raw: + description: The raw output returned by the APIC REST API (xml or json) + returned: parse error + type: str + sample: '<?xml version="1.0" encoding="UTF-8"?><imdata totalCount="1"><error code="122" text="unknown managed object class foo"/></imdata>' +sent: + description: The actual/minimal configuration pushed to the APIC + returned: info + type: list + sample: + { + "fvTenant": { + "attributes": { + "descr": "Production environment" + } + } + } +previous: + description: The original configuration from the APIC before the module has started + returned: info + type: list + sample: + [ + { + "fvTenant": { + "attributes": { + "descr": "Production", + "dn": "uni/tn-production", + "name": "production", + "nameAlias": "", + "ownerKey": "", + "ownerTag": "" + } + } + } + ] +proposed: + description: The assembled configuration from the user-provided parameters + returned: info + type: dict + sample: + { + "fvTenant": { + "attributes": { + "descr": "Production environment", + "name": "production" + } + } + } +filter_string: + description: The filter string used for the request + returned: failure or debug + type: str + sample: ?rsp-prop-include=config-only +method: + description: The HTTP method used for the request to the APIC + returned: failure or debug + type: str + sample: POST +response: + description: The HTTP response from the APIC + returned: failure or debug + type: str + sample: OK (30 bytes) +status: + description: The HTTP status from the APIC + returned: failure or debug + type: int + sample: 200 +url: + description: The HTTP url used for the request to the APIC + returned: failure or debug + type: str + sample: https://10.11.12.13/api/mo/uni/tn-production.json +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.cisco.aci.plugins.module_utils.aci import ACIModule, aci_argument_spec, aci_annotation_spec, aci_owner_spec + + +def main(): + argument_spec = aci_argument_spec() + argument_spec.update(aci_annotation_spec()) + argument_spec.update(aci_owner_spec()) + argument_spec.update( + name=dict(type="str", aliases=["policy_group"]), + description=dict(type="str", aliases=["descr"]), + name_alias=dict(type="str"), + type=dict(type="str", aliases=["policy_group_type"], choices=["leaf", "spine"], required=True), + dwdm_policy=dict(type="str"), + link_level_policy=dict(type="str"), + link_flap_policy=dict(type="str"), + l3_interface_policy=dict(type="str"), + macsec_policy=dict(type="str"), + monitoring_policy=dict(type="str"), + transceiver_policy_tdn=dict(type="str"), + state=dict(type="str", default="present", choices=["absent", "present", "query"]), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_if=[ + ["state", "absent", ["name"]], + ["state", "present", ["name"]], + ], + ) + + aci = ACIModule(module) + + name = module.params.get("name") + description = module.params.get("description") + state = module.params.get("state") + name_alias = module.params.get("name_alias") + policy_group_type = module.params.get("type") + dwdm_policy = module.params.get("dwdm_policy") + link_level_policy = module.params.get("link_level_policy") + link_flap_policy = module.params.get("link_flap_policy") + l3_interface_policy = module.params.get("l3_interface_policy") + macsec_policy = module.params.get("macsec_policy") + monitoring_policy = module.params.get("monitoring_policy") + transceiver_policy_tdn = module.params.get("transceiver_policy_tdn") + + if policy_group_type == "leaf": + policy_group_class_name = "fabricLePortPGrp" + policy_group_class_rn = "leportgrp-{0}".format(name) + else: + policy_group_class_name = "fabricSpPortPGrp" + policy_group_class_rn = "spportgrp-{0}".format(name) + + child_classes = [ + "fabricRsDwdmFabIfPol", + "fabricRsFIfPol", + "fabricRsFLinkFlapPol", + "fabricRsL3IfPol", + "fabricRsMacsecFabIfPol", + "fabricRsMonIfFabricPol", + ] + + if transceiver_policy_tdn is not None: + child_classes.append("fabricRsOpticsFabIfPol") + + aci.construct_url( + root_class=dict( + aci_class="fabric", + aci_rn="fabric", + ), + subclass_1=dict( + aci_class="fabricFuncP", + aci_rn="funcprof", + ), + subclass_2=dict( + aci_class=policy_group_class_name, + aci_rn=policy_group_class_rn, + module_object=name, + target_filter={"name": name}, + ), + child_classes=child_classes, + ) + + aci.get_existing() + + if state == "present": + child_configs = [] + if dwdm_policy is not None: + child_configs.append(dict(fabricRsDwdmFabIfPol=dict(attributes=dict(tnDwdmFabIfPolName=dwdm_policy)))) + if link_level_policy is not None: + child_configs.append(dict(fabricRsFIfPol=dict(attributes=dict(tnFabricFIfPolName=link_level_policy)))) + if link_flap_policy is not None: + child_configs.append(dict(fabricRsFLinkFlapPol=dict(attributes=dict(tnFabricFLinkFlapPolName=link_flap_policy)))) + if l3_interface_policy is not None: + child_configs.append(dict(fabricRsL3IfPol=dict(attributes=dict(tnL3IfPolName=l3_interface_policy)))) + if macsec_policy is not None: + child_configs.append(dict(fabricRsMacsecFabIfPol=dict(attributes=dict(tnMacsecFabIfPolName=macsec_policy)))) + if monitoring_policy is not None: + child_configs.append(dict(fabricRsMonIfFabricPol=dict(attributes=dict(tnMonFabricPolName=monitoring_policy)))) + if transceiver_policy_tdn is not None: + child_configs.append(dict(fabricRsOpticsFabIfPol=dict(attributes=dict(tDn=transceiver_policy_tdn)))) + + aci.payload( + aci_class=policy_group_class_name, + class_config=dict( + name=name, + descr=description, + nameAlias=name_alias, + ), + child_configs=child_configs, + ) + + aci.get_diff(aci_class=policy_group_class_name) + + aci.post_config() + + if state == "absent": + aci.delete_config() + + aci.exit_json() + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_fabric_node.py b/ansible_collections/cisco/aci/plugins/modules/aci_fabric_node.py index defa4f783..bd3ac0277 100644 --- a/ansible_collections/cisco/aci/plugins/modules/aci_fabric_node.py +++ b/ansible_collections/cisco/aci/plugins/modules/aci_fabric_node.py @@ -2,6 +2,7 @@ # -*- coding: utf-8 -*- # Copyright: (c) 2017, Bruno Calogero <brunocalogero@hotmail.com> +# Copyright: (c) 2023, Gaspard Micol <gmicol@cisco.com> # GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) from __future__ import absolute_import, division, print_function @@ -46,6 +47,18 @@ options: type: str aliases: [ role_name ] choices: [ leaf, spine, unspecified ] + node_type: + description: + - Type for the new Fabric Node Member. + type: str + choices: [ tier_2, remote, virtual, unspecified ] + remote_leaf_pool_id: + description: + - External Pool Id of the remote leaf. + - I(remote_leaf_pool_id) is incompatible with I(node_type) other than C(remote). + - I(remote_leaf_pool_id) is required if I(node_type) is C(remote). + type: str + aliases: [ pool_id ] state: description: - Use C(present) or C(absent) for adding or removing. @@ -67,6 +80,7 @@ seealso: link: https://developer.cisco.com/docs/apic-mim-ref/ author: - Bruno Calogero (@brunocalogero) +- Gaspard Micol (@gmicol) """ EXAMPLES = r""" @@ -207,6 +221,7 @@ url: """ from ansible.module_utils.basic import AnsibleModule +from ansible_collections.cisco.aci.plugins.module_utils.constants import NODE_TYPE_MAPPING from ansible_collections.cisco.aci.plugins.module_utils.aci import ACIModule, aci_argument_spec, aci_annotation_spec @@ -222,6 +237,8 @@ def main(): node_id=dict(type="int"), # Not required for querying all objects pod_id=dict(type="int"), role=dict(type="str", choices=["leaf", "spine", "unspecified"], aliases=["role_name"]), + node_type=dict(type="str", choices=list(NODE_TYPE_MAPPING.keys())), + remote_leaf_pool_id=dict(type="str", aliases=["pool_id"]), serial=dict(type="str", aliases=["serial_number"]), # Not required for querying all objects switch=dict(type="str", aliases=["name", "switch_name"]), state=dict(type="str", default="present", choices=["absent", "present", "query"]), @@ -234,6 +251,7 @@ def main(): required_if=[ ["state", "absent", ["node_id", "serial"]], ["state", "present", ["node_id", "serial"]], + ["node_type", "remote", ["remote_leaf_pool_id"]], ], ) @@ -243,10 +261,18 @@ def main(): switch = module.params.get("switch") description = module.params.get("description") role = module.params.get("role") + node_type = module.params.get("node_type") + remote_leaf_pool_id = module.params.get("remote_leaf_pool_id") state = module.params.get("state") name_alias = module.params.get("name_alias") aci = ACIModule(module) + + if node_type != "remote" and remote_leaf_pool_id: + module.fail_json(msg="External Pool Id is not compatible with a node type other than 'remote'.") + + node_type = NODE_TYPE_MAPPING.get(node_type) + aci.construct_url( root_class=dict( aci_class="fabricNodeIdentP", @@ -267,10 +293,10 @@ def main(): nodeId=node_id, podId=pod_id, # NOTE: Originally we were sending 'rn', but now we need 'dn' for idempotency - # FIXME: Did this change with ACI version ? dn="uni/controller/nodeidentpol/nodep-{0}".format(serial), - # rn='nodep-{0}'.format(serial), role=role, + nodeType=node_type, + extPoolId=remote_leaf_pool_id, serial=serial, nameAlias=name_alias, ), diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_fabric_node_control.py b/ansible_collections/cisco/aci/plugins/modules/aci_fabric_node_control.py new file mode 100644 index 000000000..eef1d510b --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_fabric_node_control.py @@ -0,0 +1,268 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2023, Tim Cragg (@timcragg) <tcragg@cisco.com> +# Copyright: (c) 2023, Akini Ross (@akinross) <akinross@cisco.com +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported_by": "certified"} + +DOCUMENTATION = r""" +--- +module: aci_fabric_node_control +short_description: Manage Fabric Node Controls (fabric:NodeControl) +description: +- Manage Fabric Node Controls on ACI fabrics. +options: + name: + description: + - The name of the Fabric Node Control. + type: str + aliases: [ fabric_node_control ] + description: + description: + - The description of the Fabric Node Control. + type: str + enable_dom: + description: + - Whether to enable digital optical monitoring (DOM) for the fabric node control. + - The APIC defaults to C(false) when unset during creation. + type: bool + feature_selection: + description: + - The feature selection for the Node Control. + - The APIC defaults to C(telemetry) when unset during creation. + type: str + choices: [ analytics, netflow, telemetry ] + aliases: [ feature ] + state: + description: + - Use C(present) or C(absent) for adding or removing. + - Use C(query) for listing an object or multiple objects. + type: str + choices: [ absent, present, query ] + default: present +extends_documentation_fragment: +- cisco.aci.aci +- cisco.aci.annotation +- cisco.aci.owner + +seealso: +- name: APIC Management Information Model reference + description: More information about the internal APIC class B(fabric:NodeControl). + link: https://developer.cisco.com/docs/apic-mim-ref/ + +author: +- Tim Cragg (@timcragg) +- Akini Ross (@akinross) +""" + +EXAMPLES = r""" +- name: Create Fabric Node Control + cisco.aci.aci_fabric_node_control: + host: apic + username: admin + password: SomeSecretPassword + name: ans_fab_node_control + enable_dom: true + feature_selection: netflow + state: present + delegate_to: localhost + +- name: Delete Fabric Node Control + cisco.aci.aci_fabric_node_control: + host: apic + username: admin + password: SomeSecretPassword + name: ans_fab_node_control + state: absent + delegate_to: localhost + +- name: Query Fabric Node Control + cisco.aci.aci_fabric_node_control: + host: apic + username: admin + password: SomeSecretPassword + name: ans_fab_node_control + state: query + delegate_to: localhost + +- name: Query All Fabric Node Controls + cisco.aci.aci_fabric_node_control: + host: apic + username: admin + password: SomeSecretPassword + state: query + delegate_to: localhost +""" + +RETURN = r""" +current: + description: The existing configuration from the APIC after the module has finished + returned: success + type: list + sample: + [ + { + "fvTenant": { + "attributes": { + "descr": "Production environment", + "dn": "uni/tn-production", + "name": "production", + "nameAlias": "", + "ownerKey": "", + "ownerTag": "" + } + } + } + ] +error: + description: The error information as returned from the APIC + returned: failure + type: dict + sample: + { + "code": "122", + "text": "unknown managed object class foo" + } +raw: + description: The raw output returned by the APIC REST API (xml or json) + returned: parse error + type: str + sample: '<?xml version="1.0" encoding="UTF-8"?><imdata totalCount="1"><error code="122" text="unknown managed object class foo"/></imdata>' +sent: + description: The actual/minimal configuration pushed to the APIC + returned: info + type: list + sample: + { + "fvTenant": { + "attributes": { + "descr": "Production environment" + } + } + } +previous: + description: The original configuration from the APIC before the module has started + returned: info + type: list + sample: + [ + { + "fvTenant": { + "attributes": { + "descr": "Production", + "dn": "uni/tn-production", + "name": "production", + "nameAlias": "", + "ownerKey": "", + "ownerTag": "" + } + } + } + ] +proposed: + description: The assembled configuration from the user-provided parameters + returned: info + type: dict + sample: + { + "fvTenant": { + "attributes": { + "descr": "Production environment", + "name": "production" + } + } + } +filter_string: + description: The filter string used for the request + returned: failure or debug + type: str + sample: ?rsp-prop-include=config-only +method: + description: The HTTP method used for the request to the APIC + returned: failure or debug + type: str + sample: POST +response: + description: The HTTP response from the APIC + returned: failure or debug + type: str + sample: OK (30 bytes) +status: + description: The HTTP status from the APIC + returned: failure or debug + type: int + sample: 200 +url: + description: The HTTP url used for the request to the APIC + returned: failure or debug + type: str + sample: https://10.11.12.13/api/mo/uni/tn-production.json +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.cisco.aci.plugins.module_utils.aci import ACIModule, aci_argument_spec, aci_annotation_spec, aci_owner_spec + + +def main(): + argument_spec = aci_argument_spec() + argument_spec.update(aci_annotation_spec()) + argument_spec.update(aci_owner_spec()) + argument_spec.update( + name=dict(type="str", aliases=["fabric_node_control"]), + description=dict(type="str"), + enable_dom=dict(type="bool"), + feature_selection=dict(type="str", choices=["analytics", "netflow", "telemetry"], aliases=["feature"]), + state=dict(type="str", default="present", choices=["absent", "present", "query"]), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + ) + aci = ACIModule(module) + + name = module.params.get("name") + description = module.params.get("description") + enable_dom = aci.boolean(module.params.get("enable_dom"), "Dom", "None") + feature_selection = module.params.get("feature_selection") + state = module.params.get("state") + + aci.construct_url( + root_class=dict( + aci_class="fabricNodeControl", + aci_rn="fabric/nodecontrol-{0}".format(name), + module_object=name, + target_filter={"name": name}, + ), + ) + aci.get_existing() + + if state == "present": + aci.payload( + aci_class="fabricNodeControl", + class_config=dict( + name=name, + descr=description, + control=enable_dom, + featureSel=feature_selection, + ), + ) + + aci.get_diff(aci_class="fabricNodeControl") + + aci.post_config() + + elif state == "absent": + aci.delete_config() + + aci.exit_json() + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_fabric_pod_selector.py b/ansible_collections/cisco/aci/plugins/modules/aci_fabric_pod_selector.py new file mode 100644 index 000000000..6c95f32df --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_fabric_pod_selector.py @@ -0,0 +1,406 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright: (c) 2023, Tim Cragg (@timcragg) <timcragg@cisco.com> +# Copyright: (c) 2023, Akini Ross (@akinross) <akinross@cisco.com> + +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported_by": "certified"} + +DOCUMENTATION = r""" +--- +module: aci_fabric_pod_selector +short_description: Manage Fabric Pod Selectors (fabric:PodS) +description: +- Manage Fabric Pod Selectors on Cisco ACI fabrics. +options: + pod_profile: + description: + - The name of the Pod Profile that contains the Selector. + type: str + name: + description: + - The name of the Pod Selector. + type: str + aliases: [ selector, pod_selector ] + description: + description: + - The description for the Fabric Pod Selector. + type: str + aliases: [ descr ] + type: + description: + - The type of the Pod Selector. + type: str + choices: [ all, range ] + blocks: + description: + - The pod id(s) associated with the Pod Selector. + - Existing blocks will be removed when they are not matching provided blocks. + - A comma-separated string of pod ids or ranges of pod ids. (ex. 1,3-4) + type: str + aliases: [ pod_id, pod_id_range ] + policy_group: + description: + - The Fabric Policy Group to bind to this Pod Selector. + - Provide an empty string C("") to remove the Fabric Policy Group binding. + type: str + state: + description: + - Use C(present) or C(absent) for adding or removing. + - Use C(query) for listing an object or multiple objects. + type: str + choices: [ absent, present, query ] + default: present + name_alias: + description: + - The alias for the current object. This relates to the nameAlias field in ACI. + type: str +extends_documentation_fragment: +- cisco.aci.aci +- cisco.aci.annotation +- cisco.aci.owner + +notes: +- The C(pod_profile) must exist before using this module in your playbook. +seealso: +- name: APIC Management Information Model reference + description: More information about the internal APIC class B(fabric:PodS). + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Tim Cragg (@timcragg) +- Akini Ross (@akinross) +""" + +# TODO add to notes section when cisco.aci.aci_pod_profile is implemented: +# The M(cisco.aci.aci_pod_profile) module can be used for this. + +EXAMPLES = r""" +- name: Add a new pod selector with type all + cisco.aci.aci_fabric_pod_selector: + host: apic + username: admin + password: SomeSecretPassword + pod_profile: default + name: ans_pod_selector + type: all + policy_group: ansible_policy_group + state: present + delegate_to: localhost + +- name: Add a new pod selector with type range and blocks + cisco.aci.aci_fabric_pod_selector: + host: apic + username: admin + password: SomeSecretPassword + pod_profile: default + name: ans_pod_selector + type: range + blocks: 1,3-4 + policy_group: ansible_policy_group + state: present + delegate_to: localhost + +- name: Remove a policy_group from an existing pod selector + cisco.aci.aci_fabric_pod_selector: + host: apic + username: admin + password: SomeSecretPassword + pod_profile: default + type: all + name: ans_pod_selector + policy_group: "" + state: present + delegate_to: localhost + +- name: Remove a pod selector type all + cisco.aci.aci_fabric_pod_selector: + host: apic + username: admin + password: SomeSecretPassword + pod_profile: default + type: all + name: ans_pod_selector + state: absent + delegate_to: localhost + +- name: Remove a pod selector type range + cisco.aci.aci_fabric_pod_selector: + host: apic + username: admin + password: SomeSecretPassword + pod_profile: default + type: range + name: ans_pod_selector + state: absent + delegate_to: localhost + +- name: Query a pod selector + cisco.aci.aci_fabric_pod_selector: + host: apic + username: admin + password: SomeSecretPassword + pod_profile: default + name: ans_pod_selector + type: all + state: query + delegate_to: localhost + register: query_result + +- name: Query all pod selectors + cisco.aci.aci_fabric_pod_selector: + host: apic + username: admin + password: SomeSecretPassword + state: query + delegate_to: localhost + register: query_result +""" + +RETURN = r""" +current: + description: The existing configuration from the APIC after the module has finished + returned: success + type: list + sample: + [ + { + "fvTenant": { + "attributes": { + "descr": "Production environment", + "dn": "uni/tn-production", + "name": "production", + "nameAlias": "", + "ownerKey": "", + "ownerTag": "" + } + } + } + ] +error: + description: The error information as returned from the APIC + returned: failure + type: dict + sample: + { + "code": "122", + "text": "unknown managed object class foo" + } +raw: + description: The raw output returned by the APIC REST API (xml or json) + returned: parse error + type: str + sample: '<?xml version="1.0" encoding="UTF-8"?><imdata totalCount="1"><error code="122" text="unknown managed object class foo"/></imdata>' +sent: + description: The actual/minimal configuration pushed to the APIC + returned: info + type: list + sample: + { + "fvTenant": { + "attributes": { + "descr": "Production environment" + } + } + } +previous: + description: The original configuration from the APIC before the module has started + returned: info + type: list + sample: + [ + { + "fvTenant": { + "attributes": { + "descr": "Production", + "dn": "uni/tn-production", + "name": "production", + "nameAlias": "", + "ownerKey": "", + "ownerTag": "" + } + } + } + ] +proposed: + description: The assembled configuration from the user-provided parameters + returned: info + type: dict + sample: + { + "fvTenant": { + "attributes": { + "descr": "Production environment", + "name": "production" + } + } + } +filter_string: + description: The filter string used for the request + returned: failure or debug + type: str + sample: ?rsp-prop-include=config-only +method: + description: The HTTP method used for the request to the APIC + returned: failure or debug + type: str + sample: POST +response: + description: The HTTP response from the APIC + returned: failure or debug + type: str + sample: OK (30 bytes) +status: + description: The HTTP status from the APIC + returned: failure or debug + type: int + sample: 200 +url: + description: The HTTP url used for the request to the APIC + returned: failure or debug + type: str + sample: https://10.11.12.13/api/mo/uni/tn-production.json +""" + +import binascii +import os + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.cisco.aci.plugins.module_utils.aci import ACIModule, aci_argument_spec, aci_annotation_spec, aci_owner_spec +from ansible_collections.cisco.aci.plugins.module_utils.constants import FABRIC_POD_SELECTOR_TYPE_MAPPING + + +def main(): + argument_spec = aci_argument_spec() + argument_spec.update(aci_annotation_spec()) + argument_spec.update(aci_owner_spec()) + argument_spec.update( + description=dict(type="str", aliases=["descr"]), + state=dict(type="str", default="present", choices=["absent", "present", "query"]), + name_alias=dict(type="str"), + pod_profile=dict(type="str"), + name=dict(type="str", aliases=["selector", "pod_selector"]), + type=dict(type="str", choices=["all", "range"]), + blocks=dict(type="str", aliases=["pod_id", "pod_id_range"]), + policy_group=dict(type="str"), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_if=[ + ["state", "absent", ["pod_profile", "name", "type"]], + ["state", "present", ["pod_profile", "name", "type"]], + ], + ) + + aci = ACIModule(module) + + name_alias = module.params.get("name_alias") + pod_profile = module.params.get("pod_profile") + name = module.params.get("name") + policy_group = module.params.get("policy_group") + description = module.params.get("description") + selector_type = FABRIC_POD_SELECTOR_TYPE_MAPPING.get(module.params.get("type")) + blocks = [i.strip().split("-") for i in module.params.get("blocks").split(",")] if module.params.get("blocks") else [] + state = module.params.get("state") + + if state == "present" and selector_type == "range" and not blocks: + module.fail_json(msg="The 'blocks' parameter is required when the 'type' parameter is set to 'range' and 'state' parameter is set to 'present'.") + + child_classes = ["fabricRsPodPGrp", "fabricPodBlk"] + + aci.construct_url( + root_class=dict( + aci_class="fabricPodP", + aci_rn="fabric/podprof-{0}".format(pod_profile), + module_object=pod_profile, + target_filter={"name": pod_profile}, + ), + subclass_1=dict( + aci_class="fabricPodS", + aci_rn="pods-{0}-typ-{1}".format(name, selector_type), + module_object=name, + target_filter={"name": name, "type": selector_type}, + ), + child_classes=child_classes, + ) + + aci.get_existing() + + if state == "present": + child_configs = [] + + if policy_group is not None: + child_configs.append( + { + "fabricRsPodPGrp": { + "attributes": {"status": "deleted"} if policy_group == "" else {"tDn": "uni/fabric/funcprof/podpgrp-{0}".format(policy_group)} + } + } + ) + + if blocks: + if isinstance(aci.existing, list) and len(aci.existing) > 0: + for child in aci.existing[0].get("fabricPodS", {}).get("children", {}): + if child.get("fabricPodBlk"): + from_ = child.get("fabricPodBlk").get("attributes").get("from_") + to_ = child.get("fabricPodBlk").get("attributes").get("to_") + if [from_, to_] in blocks: + blocks.remove([from_, to_]) + elif (from_ == to_) and [from_] in blocks: + blocks.remove([from_]) + else: + child_configs.append( + { + "fabricPodBlk": { + "attributes": { + "dn": "uni/fabric/podprof-{0}/pods-{1}-typ-{2}/podblk-{3}".format( + pod_profile, name, selector_type, child.get("fabricPodBlk").get("attributes").get("name") + ), + "status": "deleted", + } + } + } + ) + + for block in blocks: + child_configs.append( + { + "fabricPodBlk": { + "attributes": { + "name": binascii.b2a_hex(os.urandom(8)).decode("utf-8"), + "from_": block[0], + "to_": block[1] if len(block) > 1 else block[0], + } + } + } + ) + + aci.payload( + aci_class="fabricPodS", + class_config=dict( + name=name, + descr=description, + nameAlias=name_alias, + type=selector_type, + ), + child_configs=child_configs, + ) + + aci.get_diff(aci_class="fabricPodS") + + aci.post_config() + + elif state == "absent": + aci.delete_config() + + aci.exit_json() + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_fabric_scheduler.py b/ansible_collections/cisco/aci/plugins/modules/aci_fabric_scheduler.py index c0297d846..364bbc7f5 100644 --- a/ansible_collections/cisco/aci/plugins/modules/aci_fabric_scheduler.py +++ b/ansible_collections/cisco/aci/plugins/modules/aci_fabric_scheduler.py @@ -9,16 +9,12 @@ __metaclass__ = type ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported_by": "community"} -DOCUMENTATION = """ +DOCUMENTATION = r""" --- module: aci_fabric_scheduler - -short_description: This modules creates ACI schedulers. - - +short_description: This module creates ACI schedulers (trig:SchedP) description: - - With the module you can create schedule policies that can be a shell, onetime execution or recurring - +- With the module you can create schedule policies that can be a shell, one-time execution or recurring. options: name: description: @@ -32,44 +28,44 @@ options: aliases: [ descr ] recurring: description: - - If you want to make the Scheduler a recurring it would be a "True" and for a - oneTime execution it would be "False". For a shell just exclude this option from - the task + - If you want to make the scheduler a recurring operation, it should be set C(True) and for a one-time execution it should be C(False). + - For a shell, just exclude this option from the task. type: bool windowname: description: - - This is the name for your what recurring or oneTime execution + - The name of the schedule window. + - This is mandatory for the child class object B(trig:AbsWinddowP) type: str concurCap: description: - - This is the amount of devices that can be executed on at a time + - The amount of devices that can be executed on at a time. type: int maxTime: description: - - This is the amount MAX amount of time a process can be executed + - The maximum amount of time a process can be executed. type: str date: description: - - This is the date and time that the scheduler will execute + - The date and time that the scheduler will execute. type: str hour: description: - - This set the hour of execution + - The number of hours of execution. type: int minute: description: - - This sets the minute of execution, used in conjunction with hour + - The number of minutes of execution, used in conjunction with hour. type: int day: description: - - This sets the day when execution will take place + - The number of days when execution will take place. type: str - default: "every-day" - choices: ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday','Sunday', 'even-day', 'odd-day', 'every-day'] + default: every-day + choices: [ Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday, even-day, odd-day, every-day ] state: description: - - Use C(present) or C(absent) for adding or removing. - - Use C(query) for listing an object or multiple objects. + - Use C(present) or C(absent) for adding or removing. + - Use C(query) for listing an object or multiple objects. type: str default: present choices: [ absent, present, query ] @@ -82,56 +78,63 @@ extends_documentation_fragment: - cisco.aci.annotation - cisco.aci.owner +seealso: +- name: APIC Management Information Model reference + description: More information about the internal APIC class B(trig:SchedP). + link: https://developer.cisco.com/docs/apic-mim-ref/ author: - - Steven Gerhart (@sgerhart) +- Steven Gerhart (@sgerhart) """ EXAMPLES = r""" - - name: Simple Scheduler (Empty) - cisco.aci.aci_fabric_scheduler: - host: "{{ inventory_hostname }}" - username: "{{ user }}" - password: "{{ pass }}" - validate_certs: false - name: simpleScheduler - state: present - - name: Remove Simple Scheduler - cisco.aci.aci_fabric_scheduler: - host: "{{ inventory_hostname }}" - username: "{{ user }}" - password: "{{ pass }}" - validate_certs: false - name: simpleScheduler - state: absent - - name: One Time Scheduler - cisco.aci.aci_fabric_scheduler: - host: "{{ inventory_hostname }}" - username: "{{ user }}" - password: "{{ pass }}" - validate_certs: false - name: OneTime - windowname: OneTime - recurring: False - concurCap: 20 - date: "2018-11-20T24:00:00" - state: present - - name: Recurring Scheduler - cisco.aci.aci_fabric_scheduler: - host: "{{ inventory_hostname }}" - username: "{{ user }}" - password: "{{ pass }}" - validate_certs: false - name: Recurring - windowname: Recurring - recurring: True - concurCap: 20 - hour: 13 - minute: 30 - day: Tuesday - state: present +- name: Simple Scheduler (Empty) + cisco.aci.aci_fabric_scheduler: + host: "{{ inventory_hostname }}" + username: "{{ user }}" + password: "{{ pass }}" + validate_certs: false + name: simpleScheduler + state: present + +- name: Remove Simple Scheduler + cisco.aci.aci_fabric_scheduler: + host: "{{ inventory_hostname }}" + username: "{{ user }}" + password: "{{ pass }}" + validate_certs: false + name: simpleScheduler + state: absent + +- name: One Time Scheduler + cisco.aci.aci_fabric_scheduler: + host: "{{ inventory_hostname }}" + username: "{{ user }}" + password: "{{ pass }}" + validate_certs: false + name: OneTime + windowname: OneTime + recurring: False + concurCap: 20 + date: "2018-11-20T24:00:00" + state: present + +- name: Recurring Scheduler + cisco.aci.aci_fabric_scheduler: + host: "{{ inventory_hostname }}" + username: "{{ user }}" + password: "{{ pass }}" + validate_certs: false + name: Recurring + windowname: Recurring + recurring: True + concurCap: 20 + hour: 13 + minute: 30 + day: Tuesday + state: present """ -RETURN = """ +RETURN = r""" current: description: The existing configuration from the APIC after the module has finished returned: success @@ -285,6 +288,11 @@ def main(): description = module.params.get("description") name_alias = module.params.get("name_alias") + child_classes = [ + "trigRecurrWindowP", + "trigAbsWindowP", + ] + if recurring: child_configs = [ dict( @@ -324,6 +332,7 @@ def main(): target_filter={"name": name}, module_object=name, ), + child_classes=child_classes, ) aci.get_existing() diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_fabric_span_dst_group.py b/ansible_collections/cisco/aci/plugins/modules/aci_fabric_span_dst_group.py new file mode 100644 index 000000000..8ae062c3a --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_fabric_span_dst_group.py @@ -0,0 +1,361 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2023, Akini Ross <akinross@cisco.com> +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported_by": "certified"} + +DOCUMENTATION = r""" +--- +module: aci_fabric_span_dst_group +short_description: Manage Fabric SPAN destination groups (span:DestGrp) +description: +- Manage Fabric SPAN destination groups on Cisco ACI fabrics. +options: + destination_group: + description: + - The name of the Fabric SPAN destination group. + type: str + aliases: [ name, dst_group ] + description: + description: + - The description of the Fabric SPAN destination group. + type: str + aliases: [ descr ] + destination_epg: + description: + - The destination end point group. + type: dict + suboptions: + tenant: + description: + - The name of the tenant. + type: str + required: true + aliases: [ tenant_name ] + ap: + description: + - The name of application profile. + type: str + required: true + aliases: [ ap_name, app_profile, app_profile_name ] + epg: + description: + - The name of the end point group. + type: str + required: true + aliases: [ epg_name ] + span_version: + description: + - The SPAN version. + - The APIC defaults to C(version_2) when unset during creation. + type: str + choices: [ version_1, version_2 ] + version_enforced: + description: + - Enforce SPAN version. + type: bool + source_ip: + description: + - The source IP address or prefix. + type: str + required: true + destination_ip: + description: + - The destination IP address. + type: str + required: true + flow_id: + description: + - The flow ID of the SPAN packet. + - The APIC defaults to C(1) when unset during creation. + type: int + ttl: + description: + - The time to live of the SPAN session packets. + - The APIC defaults to C(64) when unset during creation. + type: int + mtu: + description: + - The MTU truncation size for the packets. + - The APIC defaults to C(1518) when unset during creation. + type: int + dscp: + description: + - The DSCP value for sending the monitored packets using ERSPAN. + - The APIC defaults to C(unspecified) when unset during creation. + type: str + choices: [ CS0, CS1, CS2, CS3, CS4, CS5, CS6, CS7, EF, VA, AF11, AF12, AF13, AF21, AF22, AF23, AF31, AF32, AF33, AF41, AF42, AF43, unspecified ] + state: + description: + - Use C(present) or C(absent) for adding or removing. + - Use C(query) for listing an object or multiple objects. + type: str + choices: [ absent, present, query ] + default: present + name_alias: + description: + - The alias for the current object. This relates to the nameAlias field in ACI. + type: str +extends_documentation_fragment: +- cisco.aci.aci +- cisco.aci.annotation +- cisco.aci.owner + +seealso: +- name: APIC Management Information Model reference + description: More information about the internal APIC class B(span:DestGrp). + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Akini Ross (@akinross) +""" + +EXAMPLES = r""" +- name: Add a Fabric SPAN destination group + cisco.aci.aci_fabric_span_dst_group: + host: apic + username: admin + password: SomeSecretPassword + destination_group: group1 + description: Test span + destination_epg: + tenant: Test1 + ap: ap1 + epg: ep1 + span_version: version_1 + version_enforced: false + destination_ip: 10.0.0.1 + source_ip: 10.0.2.1 + ttl: 2 + mtu: 1500 + flow_id: 1 + dscp: CS1 + state: present + delegate_to: localhost + +- name: Remove a Fabric SPAN destination group + cisco.aci.aci_fabric_span_dst_group: + host: apic + username: admin + password: SomeSecretPassword + destination_group: group1 + state: absent + delegate_to: localhost + +- name: Query a Fabric SPAN destination group + cisco.aci.aci_fabric_span_dst_group: + host: apic + username: admin + password: SomeSecretPassword + destination_group: group1 + state: query + delegate_to: localhost + register: query_result + +- name: Query all Fabric SPAN destination groups + cisco.aci.aci_fabric_span_dst_group: + host: apic + username: admin + password: SomeSecretPassword + state: query + delegate_to: localhost + register: query_result +""" + +RETURN = r""" +current: + description: The existing configuration from the APIC after the module has finished + returned: success + type: list + sample: + [ + { + "fvTenant": { + "attributes": { + "descr": "Production environment", + "dn": "uni/tn-production", + "name": "production", + "nameAlias": "", + "ownerKey": "", + "ownerTag": "" + } + } + } + ] +error: + description: The error information as returned from the APIC + returned: failure + type: dict + sample: + { + "code": "122", + "text": "unknown managed object class foo" + } +raw: + description: The raw output returned by the APIC REST API (xml or json) + returned: parse error + type: str + sample: '<?xml version="1.0" encoding="UTF-8"?><imdata totalCount="1"><error code="122" text="unknown managed object class foo"/></imdata>' +sent: + description: The actual/minimal configuration pushed to the APIC + returned: info + type: list + sample: + { + "fvTenant": { + "attributes": { + "descr": "Production environment" + } + } + } +previous: + description: The original configuration from the APIC before the module has started + returned: info + type: list + sample: + [ + { + "fvTenant": { + "attributes": { + "descr": "Production", + "dn": "uni/tn-production", + "name": "production", + "nameAlias": "", + "ownerKey": "", + "ownerTag": "" + } + } + } + ] +proposed: + description: The assembled configuration from the user-provided parameters + returned: info + type: dict + sample: + { + "fvTenant": { + "attributes": { + "descr": "Production environment", + "name": "production" + } + } + } +filter_string: + description: The filter string used for the request + returned: failure or debug + type: str + sample: ?rsp-prop-include=config-only +method: + description: The HTTP method used for the request to the APIC + returned: failure or debug + type: str + sample: POST +response: + description: The HTTP response from the APIC + returned: failure or debug + type: str + sample: OK (30 bytes) +status: + description: The HTTP status from the APIC + returned: failure or debug + type: int + sample: 200 +url: + description: The HTTP url used for the request to the APIC + returned: failure or debug + type: str + sample: https://10.11.12.13/api/mo/uni/tn-production.json +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.cisco.aci.plugins.module_utils.aci import ACIModule, aci_argument_spec, aci_annotation_spec, aci_owner_spec, destination_epg_spec + + +def main(): + argument_spec = aci_argument_spec() + argument_spec.update(aci_annotation_spec()) + argument_spec.update(aci_owner_spec()) + argument_spec.update( + destination_group=dict(type="str", aliases=["name", "dst_group"]), # Not required for querying all objects + description=dict(type="str", aliases=["descr"]), + destination_epg=dict(type="dict", options=destination_epg_spec()), + name_alias=dict(type="str"), + state=dict(type="str", default="present", choices=["absent", "present", "query"]), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_if=[ + ["state", "absent", ["destination_group"]], + ["state", "present", ["destination_group", "destination_epg"]], + ], + ) + + aci = ACIModule(module) + + destination_group = module.params.get("destination_group") + description = module.params.get("description") + destination_epg = module.params.get("destination_epg") + state = module.params.get("state") + name_alias = module.params.get("name_alias") + + aci.construct_url( + root_class=dict( + aci_class="fabric", + aci_rn="fabric", + ), + subclass_1=dict( + aci_class="spanDestGrp", + aci_rn="destgrp-{0}".format(destination_group), + module_object=destination_group, + target_filter={"name": destination_group}, + ), + child_classes=["spanDest", "spanRsDestEpg", "spanRsDestPathEp"], + ) + + aci.get_existing() + + if state == "present": + attributes = dict( + tDn="uni/tn-{0}/ap-{1}/epg-{2}".format(destination_epg.get("tenant"), destination_epg.get("ap"), destination_epg.get("epg")), + ip=destination_epg.get("destination_ip"), + srcIpPrefix=destination_epg.get("source_ip"), + ) + if destination_epg.get("span_version") is not None: + attributes["ver"] = "ver1" if destination_epg.get("span_version") == "version_1" else "ver2" + if destination_epg.get("version_enforced") is not None: + attributes["verEnforced"] = "yes" if destination_epg.get("version_enforced") else "no" + if destination_epg.get("ttl") is not None: + attributes["ttl"] = str(destination_epg.get("ttl")) + if destination_epg.get("mtu") is not None: + attributes["mtu"] = str(destination_epg.get("mtu")) + if destination_epg.get("flow_id") is not None: + attributes["flowId"] = str(destination_epg.get("flow_id")) + if destination_epg.get("dscp") is not None: + attributes["dscp"] = destination_epg.get("dscp") + span_rs_dest = dict(spanRsDestEpg=dict(attributes=attributes)) + + aci.payload( + aci_class="spanDestGrp", + class_config=dict(name=destination_group, descr=description, nameAlias=name_alias), + child_configs=[dict(spanDest=dict(attributes=dict(name=destination_group), children=[span_rs_dest]))], + ) + + aci.get_diff(aci_class="spanDestGrp") + + aci.post_config() + + elif state == "absent": + aci.delete_config() + + aci.exit_json() + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_fabric_span_src_group.py b/ansible_collections/cisco/aci/plugins/modules/aci_fabric_span_src_group.py new file mode 100644 index 000000000..284ed3677 --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_fabric_span_src_group.py @@ -0,0 +1,300 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2023, Akini Ross <akinross@cisco.com> +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported_by": "certified"} + +DOCUMENTATION = r""" +--- +module: aci_fabric_span_src_group +short_description: Manage Fabric SPAN source groups (span:SrcGrp) +description: +- Manage SPAN source groups on Cisco ACI fabrics. +options: + source_group: + description: + - The name of the Fabric SPAN source group. + type: str + aliases: [ name, src_group ] + description: + description: + - The description for Fabric SPAN source group. + type: str + aliases: [ descr ] + admin_state: + description: + - Enable C(true) or disable C(false) the SPAN sources. + - The APIC defaults to C(true) when unset during creation. + type: bool + destination_group: + description: + - The name of the Fabric SPAN destination group to associate with the source group. + type: str + aliases: [ dst_group ] + state: + description: + - Use C(present) or C(absent) for adding or removing. + - Use C(query) for listing an object or multiple objects. + type: str + choices: [ absent, present, query ] + default: present + name_alias: + description: + - The alias for the current object. This relates to the nameAlias field in ACI. + type: str +extends_documentation_fragment: +- cisco.aci.aci +- cisco.aci.annotation +- cisco.aci.owner + +notes: +- The I(destination_group) must exist before using this module in your playbook. + The M(cisco.aci.aci_fabric_span_dst_group) module can be used for this. +seealso: +- module: cisco.aci.aci_fabric_span_dst_group +- name: APIC Management Information Model reference + description: More information about the internal APIC class B(span:SrcGrp). + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Akini Ross (@akinross) +""" + +EXAMPLES = r""" +- name: Create a Fabric SPAN source group + cisco.aci.aci_fabric_span_src_group: + host: apic + username: admin + password: SomeSecretPassword + source_group: my_span_source_group + destination_group: my_span_dest_group + state: present + delegate_to: localhost + +- name: Delete a Fabric SPAN source group + cisco.aci.aci_fabric_span_src_group: + host: apic + username: admin + password: SomeSecretPassword + source_group: my_span_source_group + state: absent + delegate_to: localhost + +- name: Query all Fabric SPAN source groups + cisco.aci.aci_fabric_span_src_group: + host: apic + username: admin + password: SomeSecretPassword + state: query + delegate_to: localhost + register: query_result + +- name: Query a specific Fabric SPAN source group + cisco.aci.aci_fabric_span_src_group: + host: apic + username: admin + password: SomeSecretPassword + source_group: my_span_source_group + state: query + delegate_to: localhost + register: query_result +""" + +RETURN = r""" +current: + description: The existing configuration from the APIC after the module has finished + returned: success + type: list + sample: + [ + { + "fvTenant": { + "attributes": { + "descr": "Production environment", + "dn": "uni/tn-production", + "name": "production", + "nameAlias": "", + "ownerKey": "", + "ownerTag": "" + } + } + } + ] +error: + description: The error information as returned from the APIC + returned: failure + type: dict + sample: + { + "code": "122", + "text": "unknown managed object class foo" + } +raw: + description: The raw output returned by the APIC REST API (xml or json) + returned: parse error + type: str + sample: '<?xml version="1.0" encoding="UTF-8"?><imdata totalCount="1"><error code="122" text="unknown managed object class foo"/></imdata>' +sent: + description: The actual/minimal configuration pushed to the APIC + returned: info + type: list + sample: + { + "fvTenant": { + "attributes": { + "descr": "Production environment" + } + } + } +previous: + description: The original configuration from the APIC before the module has started + returned: info + type: list + sample: + [ + { + "fvTenant": { + "attributes": { + "descr": "Production", + "dn": "uni/tn-production", + "name": "production", + "nameAlias": "", + "ownerKey": "", + "ownerTag": "" + } + } + } + ] +proposed: + description: The assembled configuration from the user-provided parameters + returned: info + type: dict + sample: + { + "fvTenant": { + "attributes": { + "descr": "Production environment", + "name": "production" + } + } + } +filter_string: + description: The filter string used for the request + returned: failure or debug + type: str + sample: ?rsp-prop-include=config-only +method: + description: The HTTP method used for the request to the APIC + returned: failure or debug + type: str + sample: POST +response: + description: The HTTP response from the APIC + returned: failure or debug + type: str + sample: OK (30 bytes) +status: + description: The HTTP status from the APIC + returned: failure or debug + type: int + sample: 200 +url: + description: The HTTP url used for the request to the APIC + returned: failure or debug + type: str + sample: https://10.11.12.13/api/mo/uni/tn-production.json +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.cisco.aci.plugins.module_utils.aci import ACIModule, aci_argument_spec, aci_annotation_spec, aci_owner_spec + + +def main(): + argument_spec = aci_argument_spec() + argument_spec.update(aci_annotation_spec()) + argument_spec.update(aci_owner_spec()) + argument_spec.update( + source_group=dict(type="str", aliases=["name", "src_group"]), # Not required for querying all objects + description=dict(type="str", aliases=["descr"]), + admin_state=dict(type="bool"), + destination_group=dict(type="str", aliases=["dst_group"]), + state=dict(type="str", default="present", choices=["absent", "present", "query"]), + name_alias=dict(type="str"), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_if=[ + ["state", "absent", ["source_group"]], + ["state", "present", ["source_group", "destination_group"]], + ], + ) + + aci = ACIModule(module) + + source_group = module.params.get("source_group") + description = module.params.get("description") + admin_state = aci.boolean(module.params.get("admin_state"), "enabled", "disabled") + destination_group = module.params.get("destination_group") + state = module.params.get("state") + name_alias = module.params.get("name_alias") + + aci.construct_url( + root_class=dict( + aci_class="fabric", + aci_rn="fabric", + ), + subclass_1=dict( + aci_class="spanSrcGrp", + aci_rn="srcgrp-{0}".format(source_group), + module_object=source_group, + target_filter={"name": source_group}, + ), + child_classes=["spanSpanLbl"], + ) + + aci.get_existing() + + if state == "present": + # Create new child configs payload + child_configs = [{"spanSpanLbl": {"attributes": {"name": destination_group}}}] + + # Validate if existing and remove child object when is does not match provided configuration + if isinstance(aci.existing, list) and len(aci.existing) > 0: + for child in aci.existing[0].get("spanSrcGrp", {}).get("children", {}): + if child.get("spanSpanLbl") and child.get("spanSpanLbl").get("attributes").get("name") != destination_group: + child_configs.append( + { + "spanSpanLbl": { + "attributes": { + "dn": "uni/fabric/srcgrp-{0}/spanlbl-{1}".format(source_group, child.get("spanSpanLbl").get("attributes").get("name")), + "status": "deleted", + } + } + } + ) + + aci.payload( + aci_class="spanSrcGrp", + class_config=dict(adminSt=admin_state, descr=description, name=source_group, nameAlias=name_alias), + child_configs=child_configs, + ) + + aci.get_diff(aci_class="spanSrcGrp") + + aci.post_config() + + elif state == "absent": + aci.delete_config() + + aci.exit_json() + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_fabric_span_src_group_src.py b/ansible_collections/cisco/aci/plugins/modules/aci_fabric_span_src_group_src.py new file mode 100644 index 000000000..b4b722e41 --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_fabric_span_src_group_src.py @@ -0,0 +1,416 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2023, Akini Ross <akinross@cisco.com> +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported_by": "certified"} + +DOCUMENTATION = r""" +--- +module: aci_fabric_span_src_group_src +short_description: Manage Fabric SPAN sources (span:Src) +description: +- Manage Fabric SPAN sources on Cisco ACI fabrics. +options: + description: + description: + - The description for Fabric SPAN source. + type: str + aliases: [ descr ] + source_group: + description: + - The name of the Fabric SPAN source group. + type: str + aliases: [ src_group ] + source: + description: + - The name of the Fabric SPAN source. + type: str + aliases: [ name, src ] + direction: + description: + - The direction of the SPAN source. + - The APIC defaults to C(both) when unset during creation. + type: str + choices: [ incoming, outgoing, both ] + drop_packets: + description: + - Enable SPAN for only dropped packets. + - The APIC defaults to C(false) when unset during creation. + type: bool + vrf: + description: + - The SPAN source VRF details. + - The I(vrf) and I(bd) cannot be configured simultaneously. + type: dict + suboptions: + tenant: + description: + - The name of the SPAN source Tenant. + type: str + required: true + aliases: [ tenant_name ] + vrf: + description: + - The name of the SPAN source VRF. + type: str + required: true + aliases: [ vrf_name ] + bd: + description: + - The SPAN source BD details. + - The I(vrf) and I(bd) cannot be configured simultaneously. + type: dict + suboptions: + tenant: + description: + - The name of the SPAN source Tenant. + type: str + required: true + aliases: [ tenant_name ] + bd: + description: + - The name of the SPAN source BD. + type: str + required: true + aliases: [ bd_name, bridge_domain ] + state: + description: + - Use C(present) or C(absent) for adding or removing. + - Use C(query) for listing an object or multiple objects. + type: str + choices: [ absent, present, query ] + default: present + name_alias: + description: + - The alias for the current object. This relates to the nameAlias field in ACI. + type: str +extends_documentation_fragment: +- cisco.aci.aci +- cisco.aci.annotation +- cisco.aci.owner + +notes: +- The I(source_group) must exist before using this module in your playbook. + The M(cisco.aci.aci_fabric_span_src_group) module can be used for this. +seealso: +- module: cisco.aci.aci_fabric_span_src_group +- module: cisco.aci.aci_tenant +- module: cisco.aci.aci_vrf +- module: cisco.aci.aci_bd +- name: APIC Management Information Model reference + description: More information about the internal APIC class B(span:Src). + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Akini Ross (@akinross) +""" + +EXAMPLES = r""" +- name: Create a Fabric SPAN source + cisco.aci.aci_fabric_span_src_group_src: + host: apic + username: admin + password: SomeSecretPassword + source_group: my_span_source_group + source: my_source + state: present + delegate_to: localhost + +- name: Create a Fabric SPAN source with bd + cisco.aci.aci_fabric_span_src_group_src: + host: apic + username: admin + password: SomeSecretPassword + source_group: my_span_source_group + source: my_source + bd: + tenant: my_tenant + bd: my_bd + state: present + delegate_to: localhost + +- name: Create a Fabric SPAN source with vrf + cisco.aci.aci_fabric_span_src_group_src: + host: apic + username: admin + password: SomeSecretPassword + source_group: my_span_source_group + source: my_source + vrf: + tenant: my_tenant + vrf: my_vrf + state: present + delegate_to: localhost + +- name: Delete a Fabric SPAN source + cisco.aci.aci_fabric_span_src_group_src: + host: apic + username: admin + password: SomeSecretPassword + source_group: my_span_source_group + source: my_source + state: absent + delegate_to: localhost + +- name: Query all Fabric SPAN sources + cisco.aci.aci_fabric_span_src_group_src: + host: apic + username: admin + password: SomeSecretPassword + state: query + delegate_to: localhost + register: query_result + +- name: Query a specific Fabric SPAN source + cisco.aci.aci_fabric_span_src_group_src: + host: apic + username: admin + password: SomeSecretPassword + source_group: my_span_source_group + source: my_source + state: query + delegate_to: localhost + register: query_result +""" + +RETURN = r""" +current: + description: The existing configuration from the APIC after the module has finished + returned: success + type: list + sample: + [ + { + "fvTenant": { + "attributes": { + "descr": "Production environment", + "dn": "uni/tn-production", + "name": "production", + "nameAlias": "", + "ownerKey": "", + "ownerTag": "" + } + } + } + ] +error: + description: The error information as returned from the APIC + returned: failure + type: dict + sample: + { + "code": "122", + "text": "unknown managed object class foo" + } +raw: + description: The raw output returned by the APIC REST API (xml or json) + returned: parse error + type: str + sample: '<?xml version="1.0" encoding="UTF-8"?><imdata totalCount="1"><error code="122" text="unknown managed object class foo"/></imdata>' +sent: + description: The actual/minimal configuration pushed to the APIC + returned: info + type: list + sample: + { + "fvTenant": { + "attributes": { + "descr": "Production environment" + } + } + } +previous: + description: The original configuration from the APIC before the module has started + returned: info + type: list + sample: + [ + { + "fvTenant": { + "attributes": { + "descr": "Production", + "dn": "uni/tn-production", + "name": "production", + "nameAlias": "", + "ownerKey": "", + "ownerTag": "" + } + } + } + ] +proposed: + description: The assembled configuration from the user-provided parameters + returned: info + type: dict + sample: + { + "fvTenant": { + "attributes": { + "descr": "Production environment", + "name": "production" + } + } + } +filter_string: + description: The filter string used for the request + returned: failure or debug + type: str + sample: ?rsp-prop-include=config-only +method: + description: The HTTP method used for the request to the APIC + returned: failure or debug + type: str + sample: POST +response: + description: The HTTP response from the APIC + returned: failure or debug + type: str + sample: OK (30 bytes) +status: + description: The HTTP status from the APIC + returned: failure or debug + type: int + sample: 200 +url: + description: The HTTP url used for the request to the APIC + returned: failure or debug + type: str + sample: https://10.11.12.13/api/mo/uni/tn-production.json +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.cisco.aci.plugins.module_utils.aci import ACIModule, aci_argument_spec, aci_annotation_spec, aci_owner_spec +from ansible_collections.cisco.aci.plugins.module_utils.constants import SPAN_DIRECTION_MAP + + +def main(): + argument_spec = aci_argument_spec() + argument_spec.update(aci_annotation_spec()) + argument_spec.update(aci_owner_spec()) + argument_spec.update( + source_group=dict(type="str", aliases=["src_group"]), # Not required for querying all objects + description=dict(type="str", aliases=["descr"]), + source=dict(type="str", aliases=["name", "src"]), # Not required for querying all objects + direction=dict(type="str", choices=list(SPAN_DIRECTION_MAP.keys())), + drop_packets=dict(type="bool"), + vrf=dict( + type="dict", + options=dict( + vrf=dict(type="str", required=True, aliases=["vrf_name"]), + tenant=dict(type="str", required=True, aliases=["tenant_name"]), + ), + ), + bd=dict( + type="dict", + options=dict( + bd=dict(type="str", required=True, aliases=["bd_name", "bridge_domain"]), + tenant=dict(type="str", required=True, aliases=["tenant_name"]), + ), + ), + state=dict(type="str", default="present", choices=["absent", "present", "query"]), + name_alias=dict(type="str"), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_if=[ + ["state", "absent", ["source_group", "source"]], + ["state", "present", ["source_group", "source"]], + ], + mutually_exclusive=[ + ("vrf", "bd"), + ], + ) + + aci = ACIModule(module) + + description = module.params.get("description") + source_group = module.params.get("source_group") + source = module.params.get("source") + direction = module.params.get("direction") + drop_packets = module.params.get("drop_packets") + vrf = module.params.get("vrf") + bd = module.params.get("bd") + state = module.params.get("state") + name_alias = module.params.get("name_alias") + + if vrf and drop_packets: + module.fail_json(msg="It is not allowed to configure 'drop_packets: true' when 'vrf' is configured on the source.") + elif bd and drop_packets: + module.fail_json(msg="It is not allowed to configure 'drop_packets: true' when 'bd' is configured on the source.") + + aci.construct_url( + root_class=dict( + aci_class="fabric", + aci_rn="fabric", + ), + subclass_1=dict( + aci_class="spanSrcGrp", + aci_rn="srcgrp-{0}".format(source_group), + module_object=source_group, + target_filter={"name": source_group}, + ), + subclass_2=dict( + aci_class="spanSrc", + aci_rn="src-{0}".format(source), + module_object=source, + target_filter={"name": source}, + ), + child_classes=["spanRsSrcToCtx", "spanRsSrcToBD"], + ) + + aci.get_existing() + + if state == "present": + # Create new child configs payload + child_configs = [] + vrf_dn = bd_dn = None + + if vrf: + vrf_dn = "uni/tn-{0}/ctx-{1}".format(vrf.get("tenant"), vrf.get("vrf")) + child_configs.append({"spanRsSrcToCtx": {"attributes": {"tDn": vrf_dn}}}) + elif bd: + bd_dn = "uni/tn-{0}/BD-{1}".format(bd.get("tenant"), bd.get("bd")) + child_configs.append({"spanRsSrcToBD": {"attributes": {"tDn": bd_dn}}}) + + # Validate if existing and remove child objects when do not match provided configuration + if isinstance(aci.existing, list) and len(aci.existing) > 0: + source_path = "/api/mo/uni/fabric/srcgrp-{0}/src-{1}".format(source_group, source) + for child in aci.existing[0].get("spanSrc", {}).get("children", {}): + if child.get("spanRsSrcToCtx") and child.get("spanRsSrcToCtx").get("attributes").get("tDn") != vrf_dn: + # Appending to child_config list not possible because of APIC Error 103: child (Rn) of class spanRsSrcToCtx is already attached. + aci.api_call("DELETE", "{0}/rssrcToCtx.json".format(source_path)) + elif child.get("spanRsSrcToBD") and child.get("spanRsSrcToBD").get("attributes").get("tDn") != bd_dn: + # Appending to child_config list not possible because of APIC Error 103: child (Rn) of class spanRsSrcToBD is already attached. + aci.api_call("DELETE", "{0}/rssrcToBD.json".format(source_path)) + + aci.payload( + aci_class="spanSrc", + class_config=dict( + descr=description, + name=source, + dir=SPAN_DIRECTION_MAP.get(direction), + spanOnDrop=aci.boolean(drop_packets), + nameAlias=name_alias, + ), + child_configs=child_configs, + ) + + aci.get_diff(aci_class="spanSrc") + + aci.post_config() + + elif state == "absent": + aci.delete_config() + + aci.exit_json() + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_fabric_span_src_group_src_node.py b/ansible_collections/cisco/aci/plugins/modules/aci_fabric_span_src_group_src_node.py new file mode 100644 index 000000000..513966c13 --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_fabric_span_src_group_src_node.py @@ -0,0 +1,295 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2023, Akini Ross <akinross@cisco.com> +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported_by": "certified"} + +DOCUMENTATION = r""" +--- +module: aci_fabric_span_src_group_src_node +short_description: Manage Fabric SPAN source nodes (span:RsSrcToNode) +description: +- Manage Fabric SPAN source nodes on Cisco ACI fabrics. +- Source nodes are only configurable when the I(source_group) provided has I(drop_packets) set to C(true). +options: + source_group: + description: + - The name of the Fabric SPAN source group. + type: str + aliases: [ src_group ] + source: + description: + - The name of the Fabric SPAN source. + type: str + aliases: [ src ] + pod: + description: + - The pod id of the source access node. + type: int + aliases: [ pod_id, pod_number ] + node: + description: + - The node id of the source access node. + type: int + aliases: [ node_id ] + state: + description: + - Use C(present) or C(absent) for adding or removing. + - Use C(query) for listing an object or multiple objects. + type: str + choices: [ absent, present, query ] + default: present +extends_documentation_fragment: +- cisco.aci.aci +- cisco.aci.annotation + +notes: +- The I(source_group), and I(source) must exist before using this module in your playbook. + The M(cisco.aci.aci_fabric_span_src_group) and M(cisco.aci.aci_fabric_span_src_group_src) modules can be used for this. +seealso: +- module: cisco.aci.aci_fabric_span_src_group +- module: cisco.aci.aci_fabric_span_src_group_src +- name: APIC Management Information Model reference + description: More information about the internal APIC class B(span:RsSrcToNode). + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Akini Ross (@akinross) +""" + +EXAMPLES = r""" +- name: Create a Fabric SPAN source node + cisco.aci.aci_fabric_span_src_group_src_node: + host: apic + username: admin + password: SomeSecretPassword + source_group: my_span_source_group + source: my_source + pod: 1 + node: 101 + state: present + delegate_to: localhost + +- name: Delete a Fabric SPAN source node + cisco.aci.aci_fabric_span_src_group_src_node: + host: apic + username: admin + password: SomeSecretPassword + source_group: my_span_source_group + source: my_source + pod: 1 + node: 101 + state: absent + delegate_to: localhost + +- name: Query all Fabric SPAN source nodes + cisco.aci.aci_fabric_span_src_group_src_node: + host: apic + username: admin + password: SomeSecretPassword + state: query + delegate_to: localhost + register: query_result + +- name: Query a specific Fabric SPAN source node + cisco.aci.aci_fabric_span_src_group_src_node: + host: apic + username: admin + password: SomeSecretPassword + source_group: my_span_source_group + source: my_source + pod: 1 + nodes: 101 + state: query + delegate_to: localhost + register: query_result +""" + +RETURN = r""" +current: + description: The existing configuration from the APIC after the module has finished + returned: success + type: list + sample: + [ + { + "fvTenant": { + "attributes": { + "descr": "Production environment", + "dn": "uni/tn-production", + "name": "production", + "nameAlias": "", + "ownerKey": "", + "ownerTag": "" + } + } + } + ] +error: + description: The error information as returned from the APIC + returned: failure + type: dict + sample: + { + "code": "122", + "text": "unknown managed object class foo" + } +raw: + description: The raw output returned by the APIC REST API (xml or json) + returned: parse error + type: str + sample: '<?xml version="1.0" encoding="UTF-8"?><imdata totalCount="1"><error code="122" text="unknown managed object class foo"/></imdata>' +sent: + description: The actual/minimal configuration pushed to the APIC + returned: info + type: list + sample: + { + "fvTenant": { + "attributes": { + "descr": "Production environment" + } + } + } +previous: + description: The original configuration from the APIC before the module has started + returned: info + type: list + sample: + [ + { + "fvTenant": { + "attributes": { + "descr": "Production", + "dn": "uni/tn-production", + "name": "production", + "nameAlias": "", + "ownerKey": "", + "ownerTag": "" + } + } + } + ] +proposed: + description: The assembled configuration from the user-provided parameters + returned: info + type: dict + sample: + { + "fvTenant": { + "attributes": { + "descr": "Production environment", + "name": "production" + } + } + } +filter_string: + description: The filter string used for the request + returned: failure or debug + type: str + sample: ?rsp-prop-include=config-only +method: + description: The HTTP method used for the request to the APIC + returned: failure or debug + type: str + sample: POST +response: + description: The HTTP response from the APIC + returned: failure or debug + type: str + sample: OK (30 bytes) +status: + description: The HTTP status from the APIC + returned: failure or debug + type: int + sample: 200 +url: + description: The HTTP url used for the request to the APIC + returned: failure or debug + type: str + sample: https://10.11.12.13/api/mo/uni/tn-production.json +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.cisco.aci.plugins.module_utils.aci import ACIModule, aci_argument_spec, aci_annotation_spec + + +def main(): + argument_spec = aci_argument_spec() + argument_spec.update(aci_annotation_spec()) + argument_spec.update( + source_group=dict(type="str", aliases=["src_group"]), # Not required for querying all objects + source=dict(type="str", aliases=["src"]), # Not required for querying all objects + pod=dict(type="int", aliases=["pod_id", "pod_number"]), # Not required for querying all objects + node=dict(type="int", aliases=["node_id"]), # Not required for querying all objects + state=dict(type="str", default="present", choices=["absent", "present", "query"]), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_if=[ + ["state", "absent", ["source_group", "source", "pod", "node"]], + ["state", "present", ["source_group", "source", "pod", "node"]], + ], + ) + + aci = ACIModule(module) + + source_group = module.params.get("source_group") + source = module.params.get("source") + pod = module.params.get("pod") + node = module.params.get("node") + state = module.params.get("state") + + tdn = None + if pod and node: + tdn = "topology/pod-{0}/node-{1}".format(pod, node) + + aci.construct_url( + root_class=dict( + aci_class="fabric", + aci_rn="fabric", + ), + subclass_1=dict( + aci_class="spanSrcGrp", + aci_rn="srcgrp-{0}".format(source_group), + module_object=source_group, + target_filter={"name": source_group}, + ), + subclass_2=dict( + aci_class="spanSrc", + aci_rn="src-{0}".format(source), + module_object=source, + target_filter={"name": source}, + ), + subclass_3=dict( + aci_class="spanRsSrcToNode", + aci_rn="rssrcToNode-[{0}]".format(tdn), + module_object=tdn, + target_filter={"tDn": tdn}, + ), + ) + + aci.get_existing() + + if state == "present": + aci.payload(aci_class="spanRsSrcToNode", class_config=dict(tDn=tdn)) + + aci.get_diff(aci_class="spanRsSrcToNode") + + aci.post_config() + + elif state == "absent": + aci.delete_config() + + aci.exit_json() + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_fabric_span_src_group_src_path.py b/ansible_collections/cisco/aci/plugins/modules/aci_fabric_span_src_group_src_path.py new file mode 100644 index 000000000..041a807ef --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_fabric_span_src_group_src_path.py @@ -0,0 +1,304 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2023, Akini Ross <akinross@cisco.com> +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported_by": "certified"} + +DOCUMENTATION = r""" +--- +module: aci_fabric_span_src_group_src_path +short_description: Manage Fabric SPAN source paths (span:RsSrcToPathEp) +description: +- Manage Fabric SPAN source paths on Cisco ACI fabrics. +options: + source_group: + description: + - The name of the Fabric SPAN source group. + type: str + aliases: [ src_group ] + source: + description: + - The name of the Fabric SPAN source. + type: str + aliases: [ src ] + pod: + description: + - The pod id of the source access path. + type: int + aliases: [ pod_id, pod_number ] + node: + description: + - The node id of the source access path. + type: int + aliases: [ node_id ] + path_ep: + description: + - The path of the source access path. + - An interface like C(eth1/7) must be provided. + type: str + state: + description: + - Use C(present) or C(absent) for adding or removing. + - Use C(query) for listing an object or multiple objects. + type: str + choices: [ absent, present, query ] + default: present +extends_documentation_fragment: +- cisco.aci.aci +- cisco.aci.annotation + +notes: +- The I(source_group), and I(source) must exist before using this module in your playbook. + The M(cisco.aci.aci_fabric_span_src_group) and M(cisco.aci.aci_fabric_span_src_group_src) modules can be used for this. +seealso: +- module: cisco.aci.aci_fabric_span_src_group +- module: cisco.aci.aci_fabric_span_src_group_src +- name: APIC Management Information Model reference + description: More information about the internal APIC class B(span:RsSrcToPathEp). + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Akini Ross (@akinross) +""" + +EXAMPLES = r""" +- name: Create a Fabric SPAN source path of type path + cisco.aci.aci_fabric_span_src_group_src_path: + host: apic + username: admin + password: SomeSecretPassword + source_group: my_span_source_group + source: my_source + pod: 1 + node: 101 + path_ep: eth1/1 + state: present + delegate_to: localhost + +- name: Delete a Fabric SPAN source path + cisco.aci.aci_fabric_span_src_group_src_path: + host: apic + username: admin + password: SomeSecretPassword + source_group: my_span_source_group + source: my_source + pod: 1 + node: 101 + path_ep: eth1/1 + state: absent + delegate_to: localhost + +- name: Query all Fabric SPAN source paths + cisco.aci.aci_fabric_span_src_group_src_path: + host: apic + username: admin + password: SomeSecretPassword + state: query + delegate_to: localhost + register: query_result + +- name: Query a specific Fabric SPAN source path + cisco.aci.aci_fabric_span_src_group_src_path: + host: apic + username: admin + password: SomeSecretPassword + source_group: my_span_source_group + source: my_source + pod: 1 + node: 101 + path_ep: eth1/1 + state: query + delegate_to: localhost + register: query_result +""" + +RETURN = r""" +current: + description: The existing configuration from the APIC after the module has finished + returned: success + type: list + sample: + [ + { + "fvTenant": { + "attributes": { + "descr": "Production environment", + "dn": "uni/tn-production", + "name": "production", + "nameAlias": "", + "ownerKey": "", + "ownerTag": "" + } + } + } + ] +error: + description: The error information as returned from the APIC + returned: failure + type: dict + sample: + { + "code": "122", + "text": "unknown managed object class foo" + } +raw: + description: The raw output returned by the APIC REST API (xml or json) + returned: parse error + type: str + sample: '<?xml version="1.0" encoding="UTF-8"?><imdata totalCount="1"><error code="122" text="unknown managed object class foo"/></imdata>' +sent: + description: The actual/minimal configuration pushed to the APIC + returned: info + type: list + sample: + { + "fvTenant": { + "attributes": { + "descr": "Production environment" + } + } + } +previous: + description: The original configuration from the APIC before the module has started + returned: info + type: list + sample: + [ + { + "fvTenant": { + "attributes": { + "descr": "Production", + "dn": "uni/tn-production", + "name": "production", + "nameAlias": "", + "ownerKey": "", + "ownerTag": "" + } + } + } + ] +proposed: + description: The assembled configuration from the user-provided parameters + returned: info + type: dict + sample: + { + "fvTenant": { + "attributes": { + "descr": "Production environment", + "name": "production" + } + } + } +filter_string: + description: The filter string used for the request + returned: failure or debug + type: str + sample: ?rsp-prop-include=config-only +method: + description: The HTTP method used for the request to the APIC + returned: failure or debug + type: str + sample: POST +response: + description: The HTTP response from the APIC + returned: failure or debug + type: str + sample: OK (30 bytes) +status: + description: The HTTP status from the APIC + returned: failure or debug + type: int + sample: 200 +url: + description: The HTTP url used for the request to the APIC + returned: failure or debug + type: str + sample: https://10.11.12.13/api/mo/uni/tn-production.json +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.cisco.aci.plugins.module_utils.aci import ACIModule, aci_argument_spec, aci_annotation_spec + + +def main(): + argument_spec = aci_argument_spec() + argument_spec.update(aci_annotation_spec()) + argument_spec.update( + source_group=dict(type="str", aliases=["src_group"]), # Not required for querying all objects + source=dict(type="str", aliases=["src"]), # Not required for querying all objects + pod=dict(type="int", aliases=["pod_id", "pod_number"]), # Not required for querying all objects + node=dict(type="int", aliases=["node_id"]), # Not required for querying all objects + path_ep=dict(type="str"), # Not required for querying all objects + state=dict(type="str", default="present", choices=["absent", "present", "query"]), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_if=[ + ["state", "absent", ["source_group", "source", "pod", "node", "path_ep"]], + ["state", "present", ["source_group", "source", "pod", "node", "path_ep"]], + ], + ) + + aci = ACIModule(module) + + source_group = module.params.get("source_group") + source = module.params.get("source") + pod = module.params.get("pod") + node = module.params.get("node") + path_ep = module.params.get("path_ep") + state = module.params.get("state") + + tdn = None + if pod and node and path_ep: + tdn = "topology/pod-{0}/paths-{1}/pathep-[{2}]".format(pod, node, path_ep) + + aci.construct_url( + root_class=dict( + aci_class="fabric", + aci_rn="fabric", + ), + subclass_1=dict( + aci_class="spanSrcGrp", + aci_rn="srcgrp-{0}".format(source_group), + module_object=source_group, + target_filter={"name": source_group}, + ), + subclass_2=dict( + aci_class="spanSrc", + aci_rn="src-{0}".format(source), + module_object=source, + target_filter={"name": source}, + ), + subclass_3=dict( + aci_class="spanRsSrcToPathEp", + aci_rn="rssrcToPathEp-[{0}]".format(tdn), + module_object=tdn, + target_filter={"tDn": tdn}, + ), + ) + + aci.get_existing() + + if state == "present": + aci.payload(aci_class="spanRsSrcToPathEp", class_config=dict(tDn=tdn)) + + aci.get_diff(aci_class="spanRsSrcToPathEp") + + aci.post_config() + + elif state == "absent": + aci.delete_config() + + aci.exit_json() + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_fabric_wide_settings.py b/ansible_collections/cisco/aci/plugins/modules/aci_fabric_wide_settings.py new file mode 100644 index 000000000..1bc7c28eb --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_fabric_wide_settings.py @@ -0,0 +1,318 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2023, Tim Cragg (@timcragg) <tcragg@cisco.com> +# Copyright: (c) 2023, Akini Ross (@akinross) <akinross@cisco.com> +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported_by": "certified"} + +DOCUMENTATION = r""" +--- +module: aci_fabric_wide_settings +short_description: Manage Fabric Wide Settings (infra:SetPol) +description: +- Manage Fabric Wide Settings on Cisco ACI fabrics. +options: + disable_remote_ep_learning: + description: + - Whether to disable remote endpoint learning in VRFs containing external bridged/routed domains. + - The APIC defaults to C(false) when unset during creation. + type: bool + enforce_subnet_check: + description: + - Whether to disable IP address learning on the outside of subnets configured in a VRF, for all VRFs. + - The APIC defaults to C(false) when unset during creation. + type: bool + enforce_epg_vlan_validation: + description: + - Whether to perform a validation check that prevents overlapping VLAN pools from being associated to an EPG. + - The APIC defaults to C(false) when unset during creation. + type: bool + enforce_domain_validation: + description: + - Whether to perform a validation check if a static path is added but no domain is associated to an EPG. + - Asking for domain validation is a one time operation. Once enabled, it cannot be disabled. + - The APIC defaults to C(false) when unset during creation. + type: bool + spine_opflex_client_auth: + description: + - Whether to enforce Opflex client certificate authentication on spine switches for GOLF and Linux. + - The APIC defaults to C(true) when unset during creation. + type: bool + leaf_opflex_client_auth: + description: + - Whether to enforce Opflex client certificate authentication on leaf switches for GOLF and Linux. + - The APIC defaults to C(true) when unset during creation. + type: bool + spine_ssl_opflex: + description: + - Whether to enable SSL Opflex transport for spine switches. + - The APIC defaults to C(true) when unset during creation. + type: bool + leaf_ssl_opflex: + description: + - Whether to enable SSL Opflex transport for leaf switches. + - The APIC defaults to C(true) when unset during creation. + type: bool + opflex_ssl_versions: + description: + - Which versions of TLS to enable for Opflex. + - When setting any of the TLS versions, you must explicitly set the state for all of them. + type: list + elements: str + choices: [ tls_v1.0, tls_v1.1, tls_v1.2 ] + reallocate_gipo: + description: + - Whether to reallocate some non-stretched BD gipos to make room for stretched BDs. + - Asking for gipo reallocation is a one time operation. Once enabled, it cannot be disabled. + - The APIC defaults to C(false) when unset during creation. + type: bool + restrict_infra_vlan_traffic: + description: + - Whether to restrict infra VLAN traffic to only specified network paths. These enabled network paths are defined by infra security entry policies. + - The APIC defaults to C(false) when unset during creation. + type: bool + state: + description: + - Use C(present) for updating configuration. + - Use C(query) for showing current configuration. + type: str + choices: [ present, query ] + default: present +extends_documentation_fragment: +- cisco.aci.aci +- cisco.aci.annotation +- cisco.aci.owner + +seealso: +- name: APIC Management Information Model reference + description: More information about the internal APIC class B(infra:SetPol). + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Tim Cragg (@timcragg) +- Akini Ross (@akinross) +""" + +EXAMPLES = r""" +- name: Update Fabric Wide Settings + cisco.aci.aci_fabric_wide_settings: + host: apic + username: admin + password: SomeSecretPassword + disable_remote_ep_learning: true + enforce_epg_vlan_validation: true + state: present + delegate_to: localhost + +- name: Update Opflex SSL versions + cisco.aci.aci_fabric_wide_settings: + host: apic + username: admin + password: SomeSecretPassword + opflex_ssl_versions: [ tls_v1.2 ] + state: present + delegate_to: localhost + +- name: Query Fabric Wide Settings + cisco.aci.aci_fabric_wide_settings: + host: apic + username: admin + password: SomeSecretPassword + state: query + delegate_to: localhost + register: query_result +""" + +RETURN = r""" +current: + description: The existing configuration from the APIC after the module has finished + returned: success + type: list + sample: + [ + { + "fvTenant": { + "attributes": { + "descr": "Production environment", + "dn": "uni/tn-production", + "name": "production", + "nameAlias": "", + "ownerKey": "", + "ownerTag": "" + } + } + } + ] +error: + description: The error information as returned from the APIC + returned: failure + type: dict + sample: + { + "code": "122", + "text": "unknown managed object class foo" + } +raw: + description: The raw output returned by the APIC REST API (xml or json) + returned: parse error + type: str + sample: '<?xml version="1.0" encoding="UTF-8"?><imdata totalCount="1"><error code="122" text="unknown managed object class foo"/></imdata>' +sent: + description: The actual/minimal configuration pushed to the APIC + returned: info + type: list + sample: + { + "fvTenant": { + "attributes": { + "descr": "Production environment" + } + } + } +previous: + description: The original configuration from the APIC before the module has started + returned: info + type: list + sample: + [ + { + "fvTenant": { + "attributes": { + "descr": "Production", + "dn": "uni/tn-production", + "name": "production", + "nameAlias": "", + "ownerKey": "", + "ownerTag": "" + } + } + } + ] +proposed: + description: The assembled configuration from the user-provided parameters + returned: info + type: dict + sample: + { + "fvTenant": { + "attributes": { + "descr": "Production environment", + "name": "production" + } + } + } +filter_string: + description: The filter string used for the request + returned: failure or debug + type: str + sample: ?rsp-prop-include=config-only +method: + description: The HTTP method used for the request to the APIC + returned: failure or debug + type: str + sample: POST +response: + description: The HTTP response from the APIC + returned: failure or debug + type: str + sample: OK (30 bytes) +status: + description: The HTTP status from the APIC + returned: failure or debug + type: int + sample: 200 +url: + description: The HTTP url used for the request to the APIC + returned: failure or debug + type: str + sample: https://10.11.12.13/api/mo/uni/tn-production.json +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.cisco.aci.plugins.module_utils.aci import ACIModule, aci_argument_spec, aci_annotation_spec, aci_owner_spec +from ansible_collections.cisco.aci.plugins.module_utils.constants import TLS_MAPPING + + +def main(): + argument_spec = aci_argument_spec() + argument_spec.update(aci_annotation_spec()) + argument_spec.update(aci_owner_spec()) + argument_spec.update( + disable_remote_ep_learning=dict(type="bool"), + enforce_subnet_check=dict(type="bool"), + enforce_epg_vlan_validation=dict(type="bool"), + enforce_domain_validation=dict(type="bool"), + spine_opflex_client_auth=dict(type="bool"), + leaf_opflex_client_auth=dict(type="bool"), + spine_ssl_opflex=dict(type="bool"), + leaf_ssl_opflex=dict(type="bool"), + opflex_ssl_versions=dict(type="list", choices=list(TLS_MAPPING.keys()), elements="str"), + reallocate_gipo=dict(type="bool"), + restrict_infra_vlan_traffic=dict(type="bool"), + state=dict(type="str", default="present", choices=["present", "query"]), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + ) + + aci = ACIModule(module) + + disable_remote_ep_learning = aci.boolean(module.params.get("disable_remote_ep_learning")) + enforce_subnet_check = aci.boolean(module.params.get("enforce_subnet_check")) + enforce_epg_vlan_validation = aci.boolean(module.params.get("enforce_epg_vlan_validation")) + enforce_domain_validation = aci.boolean(module.params.get("enforce_domain_validation")) + spine_opflex_client_auth = aci.boolean(module.params.get("spine_opflex_client_auth")) + leaf_opflex_client_auth = aci.boolean(module.params.get("leaf_opflex_client_auth")) + spine_ssl_opflex = aci.boolean(module.params.get("spine_ssl_opflex")) + leaf_ssl_opflex = aci.boolean(module.params.get("leaf_ssl_opflex")) + opflex_ssl_versions = module.params.get("opflex_ssl_versions") + reallocate_gipo = aci.boolean(module.params.get("reallocate_gipo")) + restrict_infra_vlan_traffic = aci.boolean(module.params.get("restrict_infra_vlan_traffic")) + state = module.params.get("state") + + aci.construct_url( + root_class=dict( + aci_class="infraSetPol", + aci_rn="infra/settings", + ), + ) + + aci.get_existing() + + if state == "present": + class_config = dict( + unicastXrEpLearnDisable=disable_remote_ep_learning, + enforceSubnetCheck=enforce_subnet_check, + validateOverlappingVlans=enforce_epg_vlan_validation, + domainValidation=enforce_domain_validation, + opflexpAuthenticateClients=spine_opflex_client_auth, + leafOpflexpAuthenticateClients=leaf_opflex_client_auth, + opflexpUseSsl=spine_ssl_opflex, + leafOpflexpUseSsl=leaf_ssl_opflex, + reallocateGipo=reallocate_gipo, + restrictInfraVLANTraffic=restrict_infra_vlan_traffic, + ) + if opflex_ssl_versions is not None: + class_config["opflexpSslProtocols"] = ",".join([TLS_MAPPING.get(tls) for tls in sorted(opflex_ssl_versions)]) + + aci.payload( + aci_class="infraSetPol", + class_config=class_config, + ) + + aci.get_diff(aci_class="infraSetPol") + + aci.post_config() + + aci.exit_json() + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_file_remote_path.py b/ansible_collections/cisco/aci/plugins/modules/aci_file_remote_path.py new file mode 100644 index 000000000..37365b769 --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_file_remote_path.py @@ -0,0 +1,357 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2023, Tim Cragg (@timcragg) <tcragg@cisco.com> +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported_by": "certified"} + +DOCUMENTATION = r""" +--- +module: aci_file_remote_path +short_description: Manage Import/Export File Remote Paths (file:RemotePath) +description: +- Manage Import/Export File Remote Paths on Cisco ACI fabrics. +options: + name: + description: + - The name of the File Remote Path. + type: str + description: + description: + - The description of the File Remote Path. + type: str + remote_host: + description: + - The hostname or IP Address of the remote host. + type: str + remote_port: + description: + - The port to access the remote host. + type: int + remote_protocol: + description: + - The protocol to use to connect to the remote host. + type: str + choices: [ ftp, scp, sftp ] + auth_type: + description: + - The authentication type for the remote host. + - Cannot be set to ssh_key if protocol is ftp. + type: str + choices: [ password, ssh_key ] + remote_user: + description: + - The username to access the remote host. + type: str + remote_password: + description: + - The password to access the remote host. + - Only used if auth_type is password. + type: str + remote_ssh_key: + description: + - The private SSH key used to access the remote host. + - Only used if auth_type is ssh_key. + type: str + aliases: [ remote_key ] + remote_ssh_passphrase: + description: + - The Pass phrase used to decode private_key. + - Only used if auth_type is ssh_key. + type: str + aliases: [ passphrase ] + remote_path: + description: + - The path on which the data will reside on the remote host. + type: str + management_epg: + description: + - The management EPG to connect to the remote host on. + type: str + state: + description: + - Use C(present) or C(absent) for adding or removing. + - Use C(query) for listing an object or multiple objects. + type: str + choices: [ absent, present, query ] + default: present +extends_documentation_fragment: +- cisco.aci.aci +- cisco.aci.annotation + +seealso: +- name: APIC Management Information Model reference + description: More information about the internal APIC class B(file:RemotePath). + link: https://developer.cisco.com/docs/apic-mim-ref/ + +author: +- Tim Cragg (@timcragg) +""" + +EXAMPLES = r""" +- name: Add a Remote Path + cisco.aci.aci_file_remote_path: + host: apic + username: admin + password: SomeSecretPassword + name: ans_remote_path + remote_host: test.example.com + remote_port: 22 + remote_protocol: scp + remote_user: test_user + auth_type: password + remote_password: test_pass + remote_path: /tmp + state: present + delegate_to: localhost + +- name: Query a Remote Path + cisco.aci.aci_file_remote_path: + host: apic + username: admin + password: SomeSecretPassword + name: ans_remote_path + state: query + delegate_to: localhost + +- name: Query all Remote Paths + cisco.aci.aci_file_remote_path: + host: apic + username: admin + password: SomeSecretPassword + state: query + delegate_to: localhost + +- name: Remove a Remote Path + cisco.aci.aci_file_remote_path: + host: apic + username: admin + password: SomeSecretPassword + name: ans_remote_path + state: absent + delegate_to: localhost +""" + +RETURN = r""" +current: + description: The existing configuration from the APIC after the module has finished + returned: success + type: list + sample: + [ + { + "fvTenant": { + "attributes": { + "descr": "Production environment", + "dn": "uni/tn-production", + "name": "production", + "nameAlias": "", + "ownerKey": "", + "ownerTag": "" + } + } + } + ] +error: + description: The error information as returned from the APIC + returned: failure + type: dict + sample: + { + "code": "122", + "text": "unknown managed object class foo" + } +raw: + description: The raw output returned by the APIC REST API (xml or json) + returned: parse error + type: str + sample: '<?xml version="1.0" encoding="UTF-8"?><imdata totalCount="1"><error code="122" text="unknown managed object class foo"/></imdata>' +sent: + description: The actual/minimal configuration pushed to the APIC + returned: info + type: list + sample: + { + "fvTenant": { + "attributes": { + "descr": "Production environment" + } + } + } +previous: + description: The original configuration from the APIC before the module has started + returned: info + type: list + sample: + [ + { + "fvTenant": { + "attributes": { + "descr": "Production", + "dn": "uni/tn-production", + "name": "production", + "nameAlias": "", + "ownerKey": "", + "ownerTag": "" + } + } + } + ] +proposed: + description: The assembled configuration from the user-provided parameters + returned: info + type: dict + sample: + { + "fvTenant": { + "attributes": { + "descr": "Production environment", + "name": "production" + } + } + } +filter_string: + description: The filter string used for the request + returned: failure or debug + type: str + sample: ?rsp-prop-include=config-only +method: + description: The HTTP method used for the request to the APIC + returned: failure or debug + type: str + sample: POST +response: + description: The HTTP response from the APIC + returned: failure or debug + type: str + sample: OK (30 bytes) +status: + description: The HTTP status from the APIC + returned: failure or debug + type: int + sample: 200 +url: + description: The HTTP url used for the request to the APIC + returned: failure or debug + type: str + sample: https://10.11.12.13/api/mo/uni/tn-production.json +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.cisco.aci.plugins.module_utils.aci import ACIModule, aci_argument_spec, aci_annotation_spec + + +def main(): + argument_spec = aci_argument_spec() + argument_spec.update(aci_annotation_spec()) + argument_spec.update( + name=dict(type="str"), + description=dict(type="str"), + remote_host=dict(type="str"), + remote_port=dict(type="int"), + remote_protocol=dict(type="str", choices=["ftp", "scp", "sftp"]), + remote_path=dict(type="str"), + auth_type=dict(type="str", choices=["password", "ssh_key"]), + remote_user=dict(type="str"), + remote_password=dict(type="str", no_log=True), + remote_ssh_key=dict(type="str", no_log=True, aliases=["remote_key"]), + remote_ssh_passphrase=dict(type="str", no_log=True, aliases=["passphrase"]), + management_epg=dict(type="str"), + state=dict(type="str", default="present", choices=["absent", "present", "query"]), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_if=[ + ["state", "present", ["name", "auth_type"]], + ["state", "absent", ["name"]], + ], + required_together=[["remote_host", "remote_port"]], + ) + + name = module.params.get("name") + description = module.params.get("description") + remote_host = module.params.get("remote_host") + remote_port = module.params.get("remote_port") + remote_protocol = module.params.get("remote_protocol") + remote_path = module.params.get("remote_path") + auth_type = module.params.get("auth_type") + remote_user = module.params.get("remote_user") + remote_password = module.params.get("remote_password") + remote_key = module.params.get("remote_ssh_key") + passphrase = module.params.get("remote_ssh_passphrase") + management_epg = module.params.get("management_epg") + state = module.params.get("state") + + aci = ACIModule(module) + + if auth_type == "password": + if remote_key is not None: + aci.fail_json(msg="remote_key cannot be set if auth_type is password") + if passphrase is not None: + aci.fail_json(msg="passphrase cannot be set if auth_type is password") + auth = "usePassword" + elif auth_type == "ssh_key": + if remote_password is not None: + aci.fail_json(msg="remote_password cannot be set if auth_type is ssh_key") + auth = "useSshKeyContents" + else: + auth = None + + aci.construct_url( + root_class=dict( + aci_class="fileRemotePath", + aci_rn="fabric/path-{0}".format(name), + module_object=name, + target_filter={"name": name}, + ), + child_classes=["fileRsARemoteHostToEpg"], + ) + aci.get_existing() + + if state == "present": + child_configs = [] + if management_epg is not None: + child_configs.append( + dict( + fileRsARemoteHostToEpg=dict( + attributes=dict(tDn=("uni/tn-mgmt/mgmtp-default/{0}".format(management_epg))), + ) + ) + ) + aci.payload( + aci_class="fileRemotePath", + class_config=dict( + name=name, + descr=description, + authType=auth, + host=remote_host, + protocol=remote_protocol, + remotePath=remote_path, + remotePort=remote_port, + userName=remote_user, + userPasswd=remote_password, + identityPrivateKeyContents=remote_key, + identityPrivateKeyPassphrase=passphrase, + ), + child_configs=child_configs, + ) + + aci.get_diff(aci_class="fileRemotePath") + + aci.post_config() + + elif state == "absent": + aci.delete_config() + + aci.exit_json() + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_filter_entry.py b/ansible_collections/cisco/aci/plugins/modules/aci_filter_entry.py index 50587e198..584bb1012 100644 --- a/ansible_collections/cisco/aci/plugins/modules/aci_filter_entry.py +++ b/ansible_collections/cisco/aci/plugins/modules/aci_filter_entry.py @@ -27,24 +27,63 @@ options: - Description for the Filter Entry. type: str aliases: [ descr ] - dst_port: + destination_port: description: - Used to set both destination start and end ports to the same value when ip_protocol is tcp or udp. - Accepted values are any valid TCP/UDP port range. - The APIC defaults to C(unspecified) when unset during creation. type: str - dst_port_end: + aliases: [ dst_port ] + destination_port_end: description: - Used to set the destination end port when ip_protocol is tcp or udp. - Accepted values are any valid TCP/UDP port range. - The APIC defaults to C(unspecified) when unset during creation. type: str - dst_port_start: + aliases: [ dst_port_end ] + destination_port_start: description: - Used to set the destination start port when ip_protocol is tcp or udp. - Accepted values are any valid TCP/UDP port range. - The APIC defaults to C(unspecified) when unset during creation. type: str + aliases: [ dst_port_start ] + source_port: + description: + - Used to set both source start and end ports to the same value when ip_protocol is tcp or udp. + - Accepted values are any valid TCP/UDP port range. + - The APIC defaults to C(unspecified) when unset during creation. + type: str + aliases: [ src_port ] + source_port_end: + description: + - Used to set the source end port when ip_protocol is tcp or udp. + - Accepted values are any valid TCP/UDP port range. + - The APIC defaults to C(unspecified) when unset during creation. + type: str + aliases: [ src_port_end ] + source_port_start: + description: + - Used to set the source start port when ip_protocol is tcp or udp. + - Accepted values are any valid TCP/UDP port range. + - The APIC defaults to C(unspecified) when unset during creation. + type: str + aliases: [ src_port_start ] + tcp_flags: + description: + - The TCP flags of the filter entry. + - The TCP C(established) cannot be combined with other tcp rules. + - The APIC defaults to C(unspecified) when unset during creation. + type: list + elements: str + choices: [ acknowledgment, established, finish, reset, synchronize, unspecified ] + match_only_fragments: + description: + - The match only packet fragments of the filter entry. + - When enabled C(true) the rule applies to any fragments with offset greater than 0 (all fragments except first). + - When disabled C(false) it applies to all packets (including all fragments) + - The APIC defaults to C(false) when unset during creation. + type: bool entry: description: - Then name of the Filter Entry. @@ -128,6 +167,25 @@ EXAMPLES = r""" ip_protocol: tcp dst_port_start: 443 dst_port_end: 443 + source_port_start: 20 + source_port_end: 22 + tcp_flags: + - acknowledgment + - finish + state: present + delegate_to: localhost + +- name: Create a filter entry with the match only packet fragments enabled + cisco.aci.aci_filter_entry: + host: apic + username: admin + password: SomeSecretPassword + entry: https_allow + filter: web_filter + tenant: prod + ether_type: ip + ip_protocol: tcp + match_only_fragments: true state: present delegate_to: localhost @@ -271,45 +329,14 @@ url: from ansible.module_utils.basic import AnsibleModule from ansible_collections.cisco.aci.plugins.module_utils.aci import ACIModule, aci_argument_spec, aci_annotation_spec -from ansible_collections.cisco.aci.plugins.module_utils.constants import VALID_IP_PROTOCOLS, FILTER_PORT_MAPPING - - -VALID_ARP_FLAGS = ["arp_reply", "arp_request", "unspecified"] -VALID_ETHER_TYPES = ["arp", "fcoe", "ip", "ipv4", "ipv6", "mac_security", "mpls_ucast", "trill", "unspecified"] -VALID_ICMP_TYPES = ["dst_unreachable", "echo", "echo_reply", "src_quench", "time_exceeded", "unspecified"] -VALID_ICMP6_TYPES = [ - "dst_unreachable", - "echo_request", - "echo_reply", - "neighbor_advertisement", - "neighbor_solicitation", - "redirect", - "time_exceeded", - "unspecified", -] - -# mapping dicts are used to normalize the proposed data to what the APIC expects, which will keep diffs accurate -ARP_FLAG_MAPPING = dict(arp_reply="reply", arp_request="req", unspecified=None) - -ICMP_MAPPING = { - "dst_unreachable": "dst-unreach", - "echo": "echo", - "echo_reply": "echo-rep", - "src_quench": "src-quench", - "time_exceeded": "time-exceeded", - "unspecified": "unspecified", - "echo-rep": "echo-rep", - "dst-unreach": "dst-unreach", -} -ICMP6_MAPPING = dict( - dst_unreachable="dst-unreach", - echo_request="echo-req", - echo_reply="echo-rep", - neighbor_advertisement="nbr-advert", - neighbor_solicitation="nbr-solicit", - redirect="redirect", - time_exceeded="time-exceeded", - unspecified="unspecified", +from ansible_collections.cisco.aci.plugins.module_utils.constants import ( + VALID_IP_PROTOCOLS, + FILTER_PORT_MAPPING, + VALID_ETHER_TYPES, + ARP_FLAG_MAPPING, + ICMP4_MAPPING, + ICMP6_MAPPING, + TCP_FLAGS, ) @@ -317,16 +344,21 @@ def main(): argument_spec = aci_argument_spec() argument_spec.update(aci_annotation_spec()) argument_spec.update( - arp_flag=dict(type="str", choices=VALID_ARP_FLAGS), + arp_flag=dict(type="str", choices=list(ARP_FLAG_MAPPING.keys())), description=dict(type="str", aliases=["descr"]), - dst_port=dict(type="str"), - dst_port_end=dict(type="str"), - dst_port_start=dict(type="str"), + destination_port=dict(type="str", aliases=["dst_port"]), + destination_port_end=dict(type="str", aliases=["dst_port_end"]), + destination_port_start=dict(type="str", aliases=["dst_port_start"]), + source_port=dict(type="str", aliases=["src_port"]), + source_port_end=dict(type="str", aliases=["src_port_end"]), + source_port_start=dict(type="str", aliases=["src_port_start"]), + tcp_flags=dict(type="list", elements="str", choices=list(TCP_FLAGS.keys())), + match_only_fragments=dict(type="bool"), entry=dict(type="str", aliases=["entry_name", "filter_entry", "name"]), # Not required for querying all objects ether_type=dict(choices=VALID_ETHER_TYPES, type="str"), filter=dict(type="str", aliases=["filter_name"]), # Not required for querying all objects - icmp_msg_type=dict(type="str", choices=VALID_ICMP_TYPES), - icmp6_msg_type=dict(type="str", choices=VALID_ICMP6_TYPES), + icmp_msg_type=dict(type="str", choices=list(ICMP4_MAPPING.keys())), + icmp6_msg_type=dict(type="str", choices=list(ICMP6_MAPPING.keys())), ip_protocol=dict(choices=VALID_IP_PROTOCOLS, type="str"), state=dict(type="str", default="present", choices=["absent", "present", "query"]), stateful=dict(type="bool"), @@ -349,21 +381,21 @@ def main(): if arp_flag is not None: arp_flag = ARP_FLAG_MAPPING.get(arp_flag) description = module.params.get("description") - dst_port = module.params.get("dst_port") + dst_port = module.params.get("destination_port") if FILTER_PORT_MAPPING.get(dst_port) is not None: dst_port = FILTER_PORT_MAPPING.get(dst_port) - dst_end = module.params.get("dst_port_end") - if FILTER_PORT_MAPPING.get(dst_end) is not None: - dst_end = FILTER_PORT_MAPPING.get(dst_end) - dst_start = module.params.get("dst_port_start") - if FILTER_PORT_MAPPING.get(dst_start) is not None: - dst_start = FILTER_PORT_MAPPING.get(dst_start) + dst_port_end = module.params.get("destination_port_end") + if FILTER_PORT_MAPPING.get(dst_port_end) is not None: + dst_port_end = FILTER_PORT_MAPPING.get(dst_port_end) + dst_port_start = module.params.get("destination_port_start") + if FILTER_PORT_MAPPING.get(dst_port_start) is not None: + dst_port_start = FILTER_PORT_MAPPING.get(dst_port_start) entry = module.params.get("entry") ether_type = module.params.get("ether_type") filter_name = module.params.get("filter") icmp_msg_type = module.params.get("icmp_msg_type") if icmp_msg_type is not None: - icmp_msg_type = ICMP_MAPPING.get(icmp_msg_type) + icmp_msg_type = ICMP4_MAPPING.get(icmp_msg_type) icmp6_msg_type = module.params.get("icmp6_msg_type") if icmp6_msg_type is not None: icmp6_msg_type = ICMP6_MAPPING.get(icmp6_msg_type) @@ -373,12 +405,46 @@ def main(): tenant = module.params.get("tenant") name_alias = module.params.get("name_alias") - # validate that dst_port is not passed with dst_start or dst_end - if dst_port is not None and (dst_end is not None or dst_start is not None): - module.fail_json(msg="Parameter 'dst_port' cannot be used with 'dst_end' and 'dst_start'") + source_port = module.params.get("source_port") + if FILTER_PORT_MAPPING.get(source_port) is not None: + source_port = FILTER_PORT_MAPPING.get(source_port) + source_port_end = module.params.get("source_port_end") + if FILTER_PORT_MAPPING.get(source_port_end) is not None: + source_port_end = FILTER_PORT_MAPPING.get(source_port_end) + source_port_start = module.params.get("source_port_start") + if FILTER_PORT_MAPPING.get(source_port_start) is not None: + source_port_start = FILTER_PORT_MAPPING.get(source_port_start) + + # validate that dst_port is not passed with dst_port_end or dst_port_start + if dst_port is not None and (dst_port_end is not None or dst_port_start is not None): + module.fail_json(msg="Parameter 'dst_port' cannot be used with 'dst_port_end' and 'dst_port_start'") + elif dst_port_end is not None and dst_port_start is None: + module.fail_json(msg="Parameter 'dst_port_end' cannot be configured when the 'dst_port_start' is not defined") elif dst_port is not None: - dst_end = dst_port - dst_start = dst_port + dst_port_end = dst_port + dst_port_start = dst_port + + # validate that source_port is not passed with source_port_end or source_port_start + if source_port is not None and (source_port_end is not None or source_port_start is not None): + module.fail_json(msg="Parameter 'source_port' cannot be used with 'source_port_end' and 'source_port_start'") + elif source_port_end is not None and source_port_start is None: + module.fail_json(msg="Parameter 'source_port_end' cannot be configured when the 'source_port_start' is not defined") + elif source_port is not None: + source_port_end = source_port + source_port_start = source_port + + tcp_flags = module.params.get("tcp_flags") + tcp_flags_list = list() + if tcp_flags is not None: + if len(tcp_flags) >= 2 and "established" in tcp_flags: + module.fail_json(msg="TCP established cannot be combined with other tcp rules") + else: + for tcp_flag in tcp_flags: + tcp_flags_list.append(TCP_FLAGS.get(tcp_flag)) + + match_only_fragments = aci.boolean(module.params.get("match_only_fragments")) + if match_only_fragments == "yes" and (dst_port or source_port or source_port_start or source_port_end or dst_port_start or dst_port_end): + module.fail_json(msg="Parameter 'match_only_fragments' cannot be used with 'Layer 4 Port' value") aci.construct_url( root_class=dict( @@ -409,8 +475,8 @@ def main(): class_config=dict( arpOpc=arp_flag, descr=description, - dFromPort=dst_start, - dToPort=dst_end, + dFromPort=dst_port_start, + dToPort=dst_port_end, etherT=ether_type, icmpv4T=icmp_msg_type, icmpv6T=icmp6_msg_type, @@ -418,6 +484,10 @@ def main(): prot=ip_protocol, stateful=stateful, nameAlias=name_alias, + applyToFrag=match_only_fragments, + sFromPort=source_port_start, + sToPort=source_port_end, + tcpRules=",".join(tcp_flags_list), ), ) diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_firmware_group.py b/ansible_collections/cisco/aci/plugins/modules/aci_firmware_group.py index 1a9db1a6c..9687b05f3 100644 --- a/ansible_collections/cisco/aci/plugins/modules/aci_firmware_group.py +++ b/ansible_collections/cisco/aci/plugins/modules/aci_firmware_group.py @@ -1,5 +1,7 @@ #!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright: (c) 2023, Gaspard Micol (@gmicol) <gmicol@cisco.com> # GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) from __future__ import absolute_import, division, print_function @@ -8,43 +10,61 @@ __metaclass__ = type ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported_by": "community"} -DOCUMENTATION = """ +DOCUMENTATION = r""" --- module: aci_firmware_group - -short_description: This module creates a firmware group - - +short_description: Manage firmware groups (firmware:FwGrp) description: - - This module creates a firmware group, so that you can apply firmware policy to nodes. +- This module creates a firmware group, so that you can apply firmware policy to nodes. options: group: description: - - This the name of the firmware group + - Name of the firmware group. + type: str + policy: + description: + - Name of the firmware policy + - It is important that you use the same name as the policy created with M(cisco.aci.aci_firmware_policy). + type: str + aliases: [ firmwarepol ] + type_group: + description: + - Type of the firmware group. + - The APIC defaults to C(range) when unset during creation. type: str - firmwarepol: + choices: [ all, all_in_pod, range ] + description: description: - - This is the name of the firmware policy, which was create by aci_firmware_policy. It is important that - - you use the same name as the policy created with aci_firmware_policy + - Description of the firmware group. type: str + aliases: [ descr ] state: description: - - Use C(present) or C(absent) for adding or removing. - - Use C(query) for listing an object or multiple objects. + - Use C(present) or C(absent) for adding or removing. + - Use C(query) for listing an object or multiple objects. type: str default: present choices: [ absent, present, query ] name_alias: description: - - The alias for the current object. This relates to the nameAlias field in ACI. + - The alias for the current object. This relates to the nameAlias field in ACI. type: str extends_documentation_fragment: - cisco.aci.aci - cisco.aci.annotation - cisco.aci.owner +notes: +- The C(policy) must exist before using this module in your playbook. +- The M(cisco.aci.aci_firmware_policy) module can be used for this. +seealso: +- module: cisco.aci.aci_firmware_policy +- name: APIC Management Information Model reference + description: More information about the internal APIC class B(firmware:FwGrp). + link: https://developer.cisco.com/docs/apic-mim-ref/ author: - Steven Gerhart (@sgerhart) + - Gaspard Micol (@gmicol) """ @@ -55,7 +75,7 @@ EXAMPLES = r""" username: admin password: SomeSecretPassword group: fmgroup - firmwarepol: fmpolicy1 + policy: fmpolicy1 state: present delegate_to: localhost @@ -88,7 +108,7 @@ EXAMPLES = r""" register: query_result """ -RETURN = """ +RETURN = r""" current: description: The existing configuration from the APIC after the module has finished returned: success @@ -195,6 +215,7 @@ url: from ansible_collections.cisco.aci.plugins.module_utils.aci import ACIModule, aci_argument_spec, aci_annotation_spec, aci_owner_spec from ansible.module_utils.basic import AnsibleModule +from ansible_collections.cisco.aci.plugins.module_utils.constants import MATCH_TYPE_GROUP_MAPPING def main(): @@ -203,7 +224,9 @@ def main(): argument_spec.update(aci_owner_spec()) argument_spec.update( group=dict(type="str"), # Not required for querying all objects - firmwarepol=dict(type="str"), # Not required for querying all objects + policy=dict(type="str", aliases=["firmwarepol"]), # Not required for querying all objects + type_group=dict(type="str", choices=list(MATCH_TYPE_GROUP_MAPPING.keys())), + description=dict(type="str", aliases=["descr"]), state=dict(type="str", default="present", choices=["absent", "present", "query"]), name_alias=dict(type="str"), ) @@ -213,13 +236,15 @@ def main(): supports_check_mode=True, required_if=[ ["state", "absent", ["group"]], - ["state", "present", ["group", "firmwarepol"]], + ["state", "present", ["group", "policy"]], ], ) state = module.params.get("state") group = module.params.get("group") - firmwarepol = module.params.get("firmwarepol") + policy = module.params.get("policy") + type_group = MATCH_TYPE_GROUP_MAPPING.get(module.params.get("type_group")) + description = module.params.get("description") name_alias = module.params.get("name_alias") aci = ACIModule(module) @@ -240,13 +265,15 @@ def main(): aci_class="firmwareFwGrp", class_config=dict( name=group, + descr=description, + type=type_group, nameAlias=name_alias, ), child_configs=[ dict( firmwareRsFwgrpp=dict( attributes=dict( - tnFirmwareFwPName=firmwarepol, + tnFirmwareFwPName=policy, ), ), ), diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_firmware_group_node.py b/ansible_collections/cisco/aci/plugins/modules/aci_firmware_group_node.py index b41a2601b..893eaa867 100644 --- a/ansible_collections/cisco/aci/plugins/modules/aci_firmware_group_node.py +++ b/ansible_collections/cisco/aci/plugins/modules/aci_firmware_group_node.py @@ -1,50 +1,54 @@ #!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright: (c) 2023, Gaspard Micol (@gmicol) <gmicol@cisco.com> # GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) from __future__ import absolute_import, division, print_function __metaclass__ = type - ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported_by": "community"} -DOCUMENTATION = """ +DOCUMENTATION = r""" --- module: aci_firmware_group_node - -short_description: This modules adds and remove nodes from the firmware group - - +short_description: Manage firmware group nodes (fabric:NodeBlk) description: - - This module addes/deletes a node to the firmware group. This modules assigns 1 node at a time. - +- This module adds/deletes a node to the firmware group. options: group: description: - - This is the name of the firmware group + - This is the name of the firmware group. type: str node: description: - - The node to be added to the firmware group - the value equals the NodeID + - The node to be added to the firmware group. + - The value equals the NodeID. type: str state: description: - - Use C(present) or C(absent) for adding or removing. - - Use C(query) for listing an object or multiple objects. + - Use C(present) or C(absent) for adding or removing. + - Use C(query) for listing an object or multiple objects. type: str default: present choices: [ absent, present, query ] name_alias: description: - - The alias for the current object. This relates to the nameAlias field in ACI. + - The alias for the current object. This relates to the nameAlias field in ACI. type: str extends_documentation_fragment: - cisco.aci.aci - cisco.aci.annotation +seealso: +- module: cisco.aci.aci_firmware_group +- name: APIC Management Information Model reference + description: More information about the internal APIC class B(l3ext:Out). + link: https://developer.cisco.com/docs/apic-mim-ref/ author: - Steven Gerhart (@sgerhart) + - Gaspard Micol (@gmicol) """ EXAMPLES = r""" @@ -89,7 +93,7 @@ EXAMPLES = r""" register: query_result """ -RETURN = """ +RETURN = r""" current: description: The existing configuration from the APIC after the module has finished returned: success @@ -221,6 +225,9 @@ def main(): group = module.params.get("group") node = module.params.get("node") name_alias = module.params.get("name_alias") + block_name = None + if node is not None: + block_name = "blk{0}-{0}".format(node) aci = ACIModule(module) aci.construct_url( @@ -233,7 +240,7 @@ def main(): subclass_1=dict( aci_class="fabricNodeBlk", aci_rn="nodeblk-blk{0}-{0}".format(node), - target_filter={"name": node}, + target_filter={"name": block_name}, module_object=node, ), ) diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_firmware_policy.py b/ansible_collections/cisco/aci/plugins/modules/aci_firmware_policy.py index bbe37ecfd..50b41a960 100644 --- a/ansible_collections/cisco/aci/plugins/modules/aci_firmware_policy.py +++ b/ansible_collections/cisco/aci/plugins/modules/aci_firmware_policy.py @@ -1,6 +1,7 @@ #!/usr/bin/python +# -*- coding: utf-8 -*- - +# Copyright: (c) 2023, Gaspard Micol (@gmicol) <gmicol@cisco.com> # GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) from __future__ import absolute_import, division, print_function @@ -10,69 +11,124 @@ __metaclass__ = type ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported_by": "community"} -DOCUMENTATION = """ +DOCUMENTATION = r""" --- module: aci_firmware_policy - -short_description: This creates a firmware policy - - +short_description: Manage firmware policies (firmware:FwP) description: - - This module creates a firmware policy for firmware groups. The firmware policy is create first and then - - referenced by the firmware group. You will assign the firmware and specify if you want to ignore the compatibility - - check +- This module creates a firmware policy for firmware groups. +- The compatibility check can be explicitly ignored while assigning the firmware. options: name: description: - - Name of the firmware policy + - The name of the firmware policy + type: str + effective_on_reboot: + description: + - A property that indicates if the selected firmware version will be active after reboot. + - The firmware must be effective on an unplanned reboot before the scheduled maintenance operation. + type: bool + ignore_compat: + description: + - Check if compatibility checks should be ignored + type: bool + aliases: [ ignoreCompat ] + sr_upgrade: + description: + - The SR firware upgrade. + type: bool + sr_version: + description: + - The SR version of the firmware associated with this policy. type: str version: description: - - The version of the firmware associated with this policy. This value is very import as well as constructing - - it correctly. The syntax for this field is n9000-xx.x. If you look at the firmware repository using the UI - - each version will have a "Full Version" column, this is the value you need to use. So, if the Full Version - - is 13.1(1i), the value for this field would be n9000-13.1(1i) + - The version of the firmware associated with this policy. + - The syntax for this field is n9000-xx.x. + - if the Full Version is 13.1(1i), the value for this field would be n9000-13.1(1i). type: str - ignoreCompat: + version_check_override: description: - - Check if compatibility checks should be ignored - type: bool + - The version check override. + - This is a directive to ignore the version check for the next install. + - The version check, which occurs during a maintenance window, checks to see if the desired version matches the running version. + - If the versions do not match, the install is performed. If the versions do match, the install is not performed. + - The version check override is a one-time override that performs the install whether or not the versions match. + - The APIC defaults to C(untriggered) when unset during creation. + type: str + choices: [ trigger, trigger_immediate, triggered, untriggered ] + description: + description: + - Description for the firmware policy. + type: str + aliases: [ descr ] state: description: - - Use C(present) or C(absent) for adding or removing. - - Use C(query) for listing an object or multiple objects. + - Use C(present) or C(absent) for adding or removing. + - Use C(query) for listing an object or multiple objects. type: str choices: [absent, present, query] default: present name_alias: description: - - The alias for the current object. This relates to the nameAlias field in ACI. + - The alias for the current object. This relates to the nameAlias field in ACI. type: str extends_documentation_fragment: - cisco.aci.aci - cisco.aci.annotation - cisco.aci.owner +seealso: +- name: APIC Management Information Model reference + description: More information about the internal APIC class B(firmware:FwP). + link: https://developer.cisco.com/docs/apic-mim-ref/ author: - Steven Gerhart (@sgerhart) + - Gaspard Micol (@gmicol) """ -# FIXME: Add more, better examples EXAMPLES = r""" - - name: firmware policy - cisco.aci.aci_firmware_policy: - host: "{{ inventory_hostname }}" - username: "{{ user }}" - password: "{{ pass }}" - validate_certs: false - name: test2FrmPol - version: n9000-13.2(1m) - ignoreCompat: False - state: present +- name: Create a firmware policy + cisco.aci.aci_firmware_policy: + host: apic + username: admin + password: SomeSecretPassword + name: my_firmware_policy + version: n9000-13.2(1m) + ignore_compat: False + state: present + delegate_to: localhost +- name: Delete a firmware policy + cisco.aci.aci_firmware_policy: + host: apic + username: admin + password: SomeSecretPassword + name: my_firmware_policy + state: absent + delegate_to: localhost + +- name: Query all maintenance policies + cisco.aci.aci_firmware_policy: + host: apic + username: admin + password: SomeSecretPassword + state: query + delegate_to: localhost + register: query_result + +- name: Query a specific firmware policy + cisco.aci.aci_firmware_policy: + host: apic + username: admin + password: SomeSecretPassword + name: my_firmware_policy + state: query + delegate_to: localhost + register: query_result """ -RETURN = """ +RETURN = r""" current: description: The existing configuration from the APIC after the module has finished returned: success @@ -180,6 +236,7 @@ url: from ansible_collections.cisco.aci.plugins.module_utils.aci import ACIModule, aci_argument_spec, aci_annotation_spec, aci_owner_spec from ansible.module_utils.basic import AnsibleModule +from ansible_collections.cisco.aci.plugins.module_utils.constants import MATCH_TRIGGER_MAPPING def main(): @@ -188,8 +245,13 @@ def main(): argument_spec.update(aci_owner_spec()) argument_spec.update( name=dict(type="str"), # Not required for querying all objects + description=dict(type="str", aliases=["descr"]), version=dict(type="str"), - ignoreCompat=dict(type="bool"), + effective_on_reboot=dict(type="bool"), + ignore_compat=dict(type="bool", aliases=["ignoreCompat"]), + sr_upgrade=dict(type="bool"), + sr_version=dict(type="str"), + version_check_override=dict(type="str", choices=list(MATCH_TRIGGER_MAPPING.keys())), state=dict(type="str", default="present", choices=["absent", "present", "query"]), name_alias=dict(type="str"), ) @@ -202,18 +264,19 @@ def main(): ["state", "present", ["name", "version"]], ], ) + aci = ACIModule(module) state = module.params.get("state") name = module.params.get("name") + description = module.params.get("description") version = module.params.get("version") + effective_on_reboot = aci.boolean(module.params.get("effective_on_reboot"), "yes", "no") + ignore_compat = aci.boolean(module.params.get("ignore_compat"), "yes", "no") + sr_version = module.params.get("sr_version") + sr_upgrade = aci.boolean(module.params.get("sr_upgrade"), "yes", "no") + version_check_override = MATCH_TRIGGER_MAPPING.get(module.params.get("version_check_override")) name_alias = module.params.get("name_alias") - if module.params.get("ignoreCompat"): - ignore = "yes" - else: - ignore = "no" - - aci = ACIModule(module) aci.construct_url( root_class=dict( aci_class="firmwareFwP", @@ -230,8 +293,13 @@ def main(): aci_class="firmwareFwP", class_config=dict( name=name, + descr=description, version=version, - ignoreCompat=ignore, + effectiveOnReboot=effective_on_reboot, + ignoreCompat=ignore_compat, + srUpgrade=sr_upgrade, + srVersion=sr_version, + versionCheckOverride=version_check_override, nameAlias=name_alias, ), ) diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_interface_config.py b/ansible_collections/cisco/aci/plugins/modules/aci_interface_config.py index 7e9b165bd..06e661183 100644 --- a/ansible_collections/cisco/aci/plugins/modules/aci_interface_config.py +++ b/ansible_collections/cisco/aci/plugins/modules/aci_interface_config.py @@ -14,9 +14,9 @@ ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported DOCUMENTATION = r""" --- module: aci_interface_config -short_description: Manage Interface Configuration of Access (infra:PortConfig) and Fabric (fabric:PortConfig) Ports is only supported for ACI 5.2(5)+ +short_description: Manage Interface Configuration of Access (infra:PortConfig) and Fabric (fabric:PortConfig) Ports is only supported for ACI 5.2(7)+ description: -- Manage Interface Configuration of Access (infra:PortConfig) and Fabric (fabric:PortConfig) Ports is only supported for ACI 5.2(5)+ +- Manage Interface Configuration of Access (infra:PortConfig) and Fabric (fabric:PortConfig) Ports is only supported for ACI 5.2(7)+ options: policy_group: description: diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_interface_policy_fc.py b/ansible_collections/cisco/aci/plugins/modules/aci_interface_policy_fc.py index eaf8e1249..2e61a177d 100644 --- a/ansible_collections/cisco/aci/plugins/modules/aci_interface_policy_fc.py +++ b/ansible_collections/cisco/aci/plugins/modules/aci_interface_policy_fc.py @@ -1,6 +1,7 @@ #!/usr/bin/python # -*- coding: utf-8 -*- +# Copyright: (c) 2023, Gaspard Micol (@gmicol) <gmicol@cisco.com> # GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) from __future__ import absolute_import, division, print_function @@ -32,6 +33,35 @@ options: - The APIC defaults to C(f) when unset during creation. type: str choices: [ f, np ] + auto_max_speed: + description: + - The maximum automatic CPU or port speed. + - The APIC defaults to C(32G) when unset during creation. + type: str + choices: [ 2G, 4G, 8G, 16G, 32G ] + fill_pattern: + description: + - Fill Pattern for native FC ports. + - The APIC defaults to C(IDLE) when unset during creation. + type: str + choices: [ arbff, idle ] + buffer_credits: + description: + - Receive buffer credits for native FC ports. + - The APIC defaults to C(64) when unset during creation. + type: int + speed: + description: + - The CPU or port speed. + - The APIC defaults to C(auto) when unset during creation. + type: str + choices: [ auto, unknown, 2G, 4G, 8G, 16G, 32G ] + trunk_mode: + description: + - Trunking on/off for native FC ports. + - The APIC defaults to C(trunk-off) when unset during creation. + type: str + choices: [ auto, trunk-off, trunk-on, un-init ] state: description: - Use C(present) or C(absent) for adding or removing. @@ -54,6 +84,7 @@ seealso: link: https://developer.cisco.com/docs/apic-mim-ref/ author: - Dag Wieers (@dagwieers) +- Gaspard Micol (@gmicol) """ EXAMPLES = r""" @@ -202,6 +233,7 @@ url: from ansible.module_utils.basic import AnsibleModule from ansible_collections.cisco.aci.plugins.module_utils.aci import ACIModule, aci_argument_spec, aci_annotation_spec, aci_owner_spec +from ansible_collections.cisco.aci.plugins.module_utils.constants import MATCH_FC_FILL_PATTERN_MAPPING, INTERFACE_POLICY_FC_SPEED_LIST def main(): @@ -212,6 +244,11 @@ def main(): fc_policy=dict(type="str", aliases=["name"]), # Not required for querying all objects description=dict(type="str", aliases=["descr"]), port_mode=dict(type="str", choices=["f", "np"]), # No default provided on purpose + auto_max_speed=dict(type="str", choices=INTERFACE_POLICY_FC_SPEED_LIST[2:]), + fill_pattern=dict(type="str", choices=list(MATCH_FC_FILL_PATTERN_MAPPING.keys())), + buffer_credits=dict(type="int"), + speed=dict(type="str", choices=INTERFACE_POLICY_FC_SPEED_LIST), + trunk_mode=dict(type="str", choices=["auto", "trunk-off", "trunk-on", "un-init"]), state=dict(type="str", default="present", choices=["absent", "present", "query"]), name_alias=dict(type="str"), ) @@ -227,6 +264,11 @@ def main(): fc_policy = module.params.get("fc_policy") port_mode = module.params.get("port_mode") + auto_max_speed = module.params.get("auto_max_speed") + fill_pattern = MATCH_FC_FILL_PATTERN_MAPPING.get(module.params.get("fill_pattern")) + buffer_credits = module.params.get("buffer_credits") + speed = module.params.get("speed") + trunk_mode = module.params.get("trunk_mode") description = module.params.get("description") state = module.params.get("state") name_alias = module.params.get("name_alias") @@ -250,6 +292,11 @@ def main(): name=fc_policy, descr=description, portMode=port_mode, + automaxspeed=auto_max_speed, + fillPattern=fill_pattern, + rxBBCredit=buffer_credits, + speed=speed, + trunkMode=trunk_mode, nameAlias=name_alias, ), ) diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_interface_policy_leaf_fc_policy_group.py b/ansible_collections/cisco/aci/plugins/modules/aci_interface_policy_leaf_fc_policy_group.py new file mode 100644 index 000000000..6c3436459 --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_interface_policy_leaf_fc_policy_group.py @@ -0,0 +1,366 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2023, Anvitha Jain <anvjain@cisco.com> +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported_by": "certified"} + +DOCUMENTATION = r""" +--- +module: aci_interface_policy_leaf_fc_policy_group +short_description: Manage Fibre Channel (FC) interface policy groups (infra:FcAccBndlGrp, infra:FcAccPortGrp) +description: +- Manage Fibre Channel (FC) interface policy groups on Cisco ACI fabrics. +options: + policy_group: + description: + - The name of the Fibre Channel (FC) interface policy groups. + type: str + aliases: [ name, policy_group_name ] + description: + description: + - The description of the Fibre Channel (FC) interface policy group. + type: str + aliases: [ descr ] + lag_type: + description: + - Selector for the type of Fibre Channel (FC) interface policy group. + - C(port) for Fiber Channel (FC) + - C(port_channel) for Fiber Channel Port Channel (FC PC) + type: str + required: true + choices: [ port, port_channel ] + aliases: [ lag_type_name ] + fibre_channel_interface_policy: + description: + - The name of the fibre channel interface policy used by the Fibre Channel (FC) interface policy group. + type: str + aliases: [ fibre_channel_interface_policy_name ] + port_channel_policy: + description: + - The name of the port channel policy used by the Fibre Channel (FC) interface policy group. + type: str + aliases: [ port_channel_policy_name ] + attached_entity_profile: + description: + - The name of the attached entity profile (AEP) used by the Fibre Channel (FC) interface policy group. + type: str + aliases: [ aep_name, aep ] + state: + description: + - Use C(present) or C(absent) for adding or removing. + - Use C(query) for listing an object or multiple objects. + type: str + choices: [ absent, present, query ] + default: present + name_alias: + description: + - The alias for the current object. This relates to the nameAlias field in ACI. + type: str +extends_documentation_fragment: +- cisco.aci.aci +- cisco.aci.annotation +- cisco.aci.owner + +notes: +- When using the module please select the appropriate link_aggregation_type (lag_type). +- C(port) for Fiber Channel(FC), C(port_channel) for Fiber Channel Port Channel(VPC). +seealso: +- name: APIC Management Information Model reference + description: More information about the internal APIC classes B(infra:FcAccPortGrp) and B(infra:FcAccBndlGrp). + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Anvitha Jain (@anvjain) +""" + +EXAMPLES = r""" +- name: Create a Fiber Channel (FC) Interface Policy Group + cisco.aci.aci_interface_policy_leaf_fc_policy_group: + host: apic + username: admin + password: SomeSecretPassword + lag_type: port + fibre_channel_interface_policy: fcinterfacepolicy + description: policygroupname description + attached_entity_profile: aep + state: present + delegate_to: localhost + +- name: Create a Fiber Channel Port Channel (FC PC) Interface Policy Group + cisco.aci.aci_interface_policy_leaf_fc_policy_group: + host: apic + username: admin + password: SomeSecretPassword + lag_type: port_channel + fibre_channel_interface_policy: fcinterfacepolicy + description: policygroupname description + attached_entity_profile: aep + port_channel_policy: lacppolicy + state: present + delegate_to: localhost + +- name: Query all Leaf Access Port Policy Groups of type link + cisco.aci.aci_interface_policy_leaf_fc_policy_group: + host: apic + username: admin + password: SomeSecretPassword + lag_type: port_channel + state: query + delegate_to: localhost + register: query_result + +- name: Query a specific Lead Access Port Policy Group + cisco.aci.aci_interface_policy_leaf_fc_policy_group: + host: apic + username: admin + password: SomeSecretPassword + lag_type: port + policy_group: policygroupname + state: query + delegate_to: localhost + register: query_result + +- name: Delete an Interface policy Leaf Policy Group + cisco.aci.aci_interface_policy_leaf_fc_policy_group: + host: apic + username: admin + password: SomeSecretPassword + lag_type: port + policy_group: policygroupname + state: absent + delegate_to: localhost +""" + +RETURN = r""" +current: + description: The existing configuration from the APIC after the module has finished + returned: success + type: list + sample: + [ + { + "fvTenant": { + "attributes": { + "descr": "Production environment", + "dn": "uni/tn-production", + "name": "production", + "nameAlias": "", + "ownerKey": "", + "ownerTag": "" + } + } + } + ] +error: + description: The error information as returned from the APIC + returned: failure + type: dict + sample: + { + "code": "122", + "text": "unknown managed object class foo" + } +raw: + description: The raw output returned by the APIC REST API (xml or json) + returned: parse error + type: str + sample: '<?xml version="1.0" encoding="UTF-8"?><imdata totalCount="1"><error code="122" text="unknown managed object class foo"/></imdata>' +sent: + description: The actual/minimal configuration pushed to the APIC + returned: info + type: list + sample: + { + "fvTenant": { + "attributes": { + "descr": "Production environment" + } + } + } +previous: + description: The original configuration from the APIC before the module has started + returned: info + type: list + sample: + [ + { + "fvTenant": { + "attributes": { + "descr": "Production", + "dn": "uni/tn-production", + "name": "production", + "nameAlias": "", + "ownerKey": "", + "ownerTag": "" + } + } + } + ] +proposed: + description: The assembled configuration from the user-provided parameters + returned: info + type: dict + sample: + { + "fvTenant": { + "attributes": { + "descr": "Production environment", + "name": "production" + } + } + } +filter_string: + description: The filter string used for the request + returned: failure or debug + type: str + sample: ?rsp-prop-include=config-only +method: + description: The HTTP method used for the request to the APIC + returned: failure or debug + type: str + sample: POST +response: + description: The HTTP response from the APIC + returned: failure or debug + type: str + sample: OK (30 bytes) +status: + description: The HTTP status from the APIC + returned: failure or debug + type: int + sample: 200 +url: + description: The HTTP url used for the request to the APIC + returned: failure or debug + type: str + sample: https://10.11.12.13/api/mo/uni/tn-production.json +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.cisco.aci.plugins.module_utils.aci import ACIModule, aci_argument_spec, aci_annotation_spec, aci_owner_spec + + +def main(): + argument_spec = aci_argument_spec() + argument_spec.update(aci_annotation_spec()) + argument_spec.update(aci_owner_spec()) + argument_spec.update( + # NOTE: Since this module needs to include both infra:FcAccPortGrp (for FC) and infra:FcAccBndlGrp (for FC PC): + # NOTE: The user(s) can make a choice between (port(FC), port_channel(FC PC)) + lag_type=dict(type="str", required=True, aliases=["lag_type_name"], choices=["port", "port_channel"]), + policy_group=dict(type="str", aliases=["name", "policy_group_name"]), # Not required for querying all objects + description=dict(type="str", aliases=["descr"]), + fibre_channel_interface_policy=dict(type="str", aliases=["fibre_channel_interface_policy_name"]), + port_channel_policy=dict(type="str", aliases=["port_channel_policy_name"]), + attached_entity_profile=dict(type="str", aliases=["aep_name", "aep"]), + state=dict(type="str", default="present", choices=["absent", "present", "query"]), + name_alias=dict(type="str"), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_if=[ + ["state", "absent", ["policy_group"]], + ["state", "present", ["policy_group"]], + ], + ) + + policy_group = module.params.get("policy_group") + description = module.params.get("description") + lag_type = module.params.get("lag_type") + fibre_channel_interface_policy = module.params.get("fibre_channel_interface_policy") + port_channel_policy = module.params.get("port_channel_policy") + attached_entity_profile = module.params.get("attached_entity_profile") + state = module.params.get("state") + name_alias = module.params.get("name_alias") + + aci = ACIModule(module) + + if lag_type == "port": + aci_class_name = "infraFcAccPortGrp" + dn_name = "fcaccportgrp" + elif lag_type == "port_channel": + aci_class_name = "infraFcAccBndlGrp" + dn_name = "fcaccbundle" + + class_config_dict = dict( + name=policy_group, + descr=description, + nameAlias=name_alias, + ) + + child_configs = [] + if fibre_channel_interface_policy is not None: + child_configs.append( + dict( + infraRsFcL2IfPol=dict( + attributes=dict( + tnFcIfPolName=fibre_channel_interface_policy, + ), + ), + ) + ) + if attached_entity_profile is not None: + child_configs.append( + dict( + infraRsFcAttEntP=dict( + attributes=dict( + tDn="uni/infra/attentp-{0}".format(attached_entity_profile), + ), + ), + ) + ) + + # Add infraRsFcLagPol binding only when port_channel_policy is defined + if lag_type == "port_channel" and port_channel_policy is not None: + child_configs.append( + dict( + infraRsFcLagPol=dict( + attributes=dict( + tnLacpLagPolName=port_channel_policy, + ), + ), + ) + ) + + aci.construct_url( + root_class=dict( + aci_class=aci_class_name, + aci_rn="infra/funcprof/{0}-{1}".format(dn_name, policy_group), + module_object=policy_group, + target_filter={"name": policy_group}, + ), + child_classes=[ + "infraRsFcL2IfPol", + "infraRsFcLagPol", + "infraRsFcAttEntP", + ], + ) + + aci.get_existing() + + if state == "present": + aci.payload( + aci_class=aci_class_name, + class_config=class_config_dict, + child_configs=child_configs, + ) + + aci.get_diff(aci_class=aci_class_name) + + aci.post_config() + + elif state == "absent": + aci.delete_config() + + aci.exit_json() + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_interface_policy_leaf_policy_group.py b/ansible_collections/cisco/aci/plugins/modules/aci_interface_policy_leaf_policy_group.py index 1a1ad21b2..da2e90adf 100644 --- a/ansible_collections/cisco/aci/plugins/modules/aci_interface_policy_leaf_policy_group.py +++ b/ansible_collections/cisco/aci/plugins/modules/aci_interface_policy_leaf_policy_group.py @@ -2,6 +2,7 @@ # -*- coding: utf-8 -*- # Copyright: (c) 2017, Bruno Calogero <brunocalogero@hotmail.com> +# Copyright: (c) 2023, Anvitha Jain <anvjain@cisco.com> # GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) from __future__ import absolute_import, division, print_function @@ -19,17 +20,17 @@ description: options: policy_group: description: - - Name of the leaf policy group to be added/deleted. + - The name of the leaf interface policy group. type: str aliases: [ name, policy_group_name ] description: description: - - Description for the leaf policy group to be created. + - The description of the leaf interface policy group. type: str aliases: [ descr ] lag_type: description: - - Selector for the type of leaf policy group we want to create. + - Selector for the type of leaf interface policy group. - C(leaf) for Leaf Access Port Policy Group - C(link) for Port Channel (PC) - C(node) for Virtual Port Channel (VPC) @@ -39,82 +40,142 @@ options: aliases: [ lag_type_name ] link_level_policy: description: - - Choice of link_level_policy to be used as part of the leaf policy group to be created. + - The name of the link level policy used by the leaf interface policy group. type: str aliases: [ link_level_policy_name ] cdp_policy: description: - - Choice of cdp_policy to be used as part of the leaf policy group to be created. + - The name of the cdp policy used by the leaf interface policy group. type: str aliases: [ cdp_policy_name ] mcp_policy: description: - - Choice of mcp_policy to be used as part of the leaf policy group to be created. + - The name of the mcp policy used by the leaf interface policy group. type: str aliases: [ mcp_policy_name ] lldp_policy: description: - - Choice of lldp_policy to be used as part of the leaf policy group to be created. + - The name of the lldp policy used by the leaf interface policy group. type: str aliases: [ lldp_policy_name ] stp_interface_policy: description: - - Choice of stp_interface_policy to be used as part of the leaf policy group to be created. + - The name of the stp interface policy used by the leaf interface policy group. type: str aliases: [ stp_interface_policy_name ] egress_data_plane_policing_policy: description: - - Choice of egress_data_plane_policing_policy to be used as part of the leaf policy group to be created. + - The name of the egress data plane policing policy used by the leaf interface policy group. type: str aliases: [ egress_data_plane_policing_policy_name ] ingress_data_plane_policing_policy: description: - - Choice of ingress_data_plane_policing_policy to be used as part of the leaf policy group to be created. + - The name of the ingress data plane policing policy used by the leaf interface policy group. type: str aliases: [ ingress_data_plane_policing_policy_name ] priority_flow_control_policy: description: - - Choice of priority_flow_control_policy to be used as part of the leaf policy group to be created. + - The name of the priority flow control policy used by the leaf interface policy group. type: str aliases: [ priority_flow_control_policy_name ] fibre_channel_interface_policy: description: - - Choice of fibre_channel_interface_policy to be used as part of the leaf policy group to be created. + - The name of the fibre channel interface policy used by the leaf interface policy group. type: str aliases: [ fibre_channel_interface_policy_name ] slow_drain_policy: description: - - Choice of slow_drain_policy to be used as part of the leaf policy group to be created. + - The name of the slow drain policy used by the leaf interface policy group. type: str aliases: [ slow_drain_policy_name ] port_channel_policy: description: - - Choice of port_channel_policy to be used as part of the leaf policy group to be created. + - The name of the port channel policy used by the leaf interface policy group. type: str aliases: [ port_channel_policy_name ] monitoring_policy: description: - - Choice of monitoring_policy to be used as part of the leaf policy group to be created. + - The name of the monitoring policy used by the leaf interface policy group. type: str aliases: [ monitoring_policy_name ] storm_control_interface_policy: description: - - Choice of storm_control_interface_policy to be used as part of the leaf policy group to be created. + - The name of the storm control interface policy used by the leaf interface policy group. type: str aliases: [ storm_control_interface_policy_name ] l2_interface_policy: description: - - Choice of l2_interface_policy to be used as part of the leaf policy group to be created. + - The name of the l2 interface policy used by the leaf interface policy group. type: str aliases: [ l2_interface_policy_name ] port_security_policy: description: - - Choice of port_security_policy to be used as part of the leaf policy group to be created. + - The name of the port security policy used by the leaf interface policy group. type: str aliases: [ port_security_policy_name ] + link_flap_policy: + description: + - The name of the link flap policy used by the leaf interface policy group. + type: str + aliases: [ link_flap_policy_name ] + link_level_flow_control: + description: + - The name of the link level flow control used by the leaf interface policy group. + type: str + aliases: [ link_level_flow_control_name ] + mac_sec_interface_policy: + description: + - The name of the mac sec interface policy used by the leaf interface policy group. + type: str + aliases: [ mac_sec_interface_policy_name ] + copp_policy: + description: + - The name of the copp policy used by the leaf interface policy group. + type: str + aliases: [ copp_policy_name ] + sync_e_interface_policy: + description: + - The name of the syncE interface policy used by the leaf interface policy group. + - Only availavle in APIC version 5.2 or later. + type: str + aliases: [ sync_e_interface_policy_name ] + port_authentication: + description: + - The name of the port authentication used by the leaf interface policy group. + type: str + aliases: [ port_authentication_name ] + dwdm: + description: + - The name of the dwdm used by the leaf interface policy group. + type: str + aliases: [ dwdm_name ] + poe_interface_policy: + description: + - The name of the poe interface policy used by the leaf interface policy group. + type: str + aliases: [ poe_interface_policy_name ] + transceiver_policy: + description: + - The name of the transceiver policy used by the leaf interface policy group. + - Only availavle in APIC version 6.0(2h) or later. + type: dict + suboptions: + type: + description: + - The type of the transceiver policy. + type: str + required: true + aliases: [ transceiver_policy_type ] + choices: [ zr, zrp] + name: + description: + - The name of the transceiver policy. + type: str + required: true + aliases: [ transceiver_policy_name ] aep: description: - - Choice of attached_entity_profile (AEP) to be used as part of the leaf policy group to be created. + - The name of the attached entity profile (AEP) used by the leaf interface policy group. type: str aliases: [ aep_name ] state: @@ -135,7 +196,7 @@ extends_documentation_fragment: notes: - When using the module please select the appropriate link_aggregation_type (lag_type). - C(link) for Port Channel(PC), C(node) for Virtual Port Channel(VPC) and C(leaf) for Leaf Access Port Policy Group. +- C(link) for Port Channel(PC), C(node) for Virtual Port Channel(VPC) and C(leaf) for Leaf Access Port Policy Group. seealso: - name: APIC Management Information Model reference description: More information about the internal APIC classes B(infra:AccBndlGrp) and B(infra:AccPortGrp). @@ -153,10 +214,10 @@ EXAMPLES = r""" lag_type: link policy_group: policygroupname description: policygroupname description - link_level_policy: whateverlinklevelpolicy - cdp_policy: whatevercdppolicy - lldp_policy: whateverlldppolicy - port_channel_policy: whateverlacppolicy + link_level_policy: linklevelpolicy + cdp_policy: cdppolicy + lldp_policy: lldppolicy + port_channel_policy: lacppolicy state: present delegate_to: localhost @@ -167,10 +228,10 @@ EXAMPLES = r""" password: SomeSecretPassword lag_type: node policy_group: policygroupname - link_level_policy: whateverlinklevelpolicy - cdp_policy: whatevercdppolicy - lldp_policy: whateverlldppolicy - port_channel_policy: whateverlacppolicy + link_level_policy: linklevelpolicy + cdp_policy: cdppolicy + lldp_policy: lldppolicy + port_channel_policy: lacppolicy state: present delegate_to: localhost @@ -181,9 +242,9 @@ EXAMPLES = r""" password: SomeSecretPassword lag_type: leaf policy_group: policygroupname - link_level_policy: whateverlinklevelpolicy - cdp_policy: whatevercdppolicy - lldp_policy: whateverlldppolicy + link_level_policy: linklevelpolicy + cdp_policy: cdppolicy + lldp_policy: lldppolicy state: present delegate_to: localhost @@ -334,7 +395,7 @@ def main(): argument_spec.update(aci_owner_spec()) argument_spec.update( # NOTE: Since this module needs to include both infra:AccBndlGrp (for PC and VPC) and infra:AccPortGrp (for leaf access port policy group): - # NOTE: I'll allow the user to make the choice here (link(PC), node(VPC), leaf(leaf-access port policy group)) + # NOTE: The user(s) can make the choice between (link(PC), node(VPC), leaf(leaf-access port policy group)) lag_type=dict(type="str", required=True, aliases=["lag_type_name"], choices=["leaf", "link", "node"]), policy_group=dict(type="str", aliases=["name", "policy_group_name"]), # Not required for querying all objects description=dict(type="str", aliases=["descr"]), @@ -353,7 +414,22 @@ def main(): storm_control_interface_policy=dict(type="str", aliases=["storm_control_interface_policy_name"]), l2_interface_policy=dict(type="str", aliases=["l2_interface_policy_name"]), port_security_policy=dict(type="str", aliases=["port_security_policy_name"]), + link_flap_policy=dict(type="str", aliases=["link_flap_policy_name"]), + link_level_flow_control=dict(type="str", aliases=["link_level_flow_control_name"]), + mac_sec_interface_policy=dict(type="str", aliases=["mac_sec_interface_policy_name"]), + copp_policy=dict(type="str", aliases=["copp_policy_name"]), aep=dict(type="str", aliases=["aep_name"]), + sync_e_interface_policy=dict(type="str", aliases=["sync_e_interface_policy_name"]), + transceiver_policy=dict( + type="dict", + options=dict( + name=dict(type="str", required=True, aliases=["transceiver_policy_name"]), + type=dict(type="str", required=True, choices=["zr", "zrp"], aliases=["transceiver_policy_type"]), + ), + ), + poe_interface_policy=dict(type="str", aliases=["poe_interface_policy_name"]), + port_authentication=dict(type="str", aliases=["port_authentication_name"]), + dwdm=dict(type="str", aliases=["dwdm_name"]), state=dict(type="str", default="present", choices=["absent", "present", "query"]), name_alias=dict(type="str"), ) @@ -385,7 +461,17 @@ def main(): storm_control_interface_policy = module.params.get("storm_control_interface_policy") l2_interface_policy = module.params.get("l2_interface_policy") port_security_policy = module.params.get("port_security_policy") + link_flap_policy = module.params.get("link_flap_policy") + link_level_flow_control = module.params.get("link_level_flow_control") + mac_sec_interface_policy = module.params.get("mac_sec_interface_policy") + copp_policy = module.params.get("copp_policy") + poe_interface_policy = module.params.get("poe_interface_policy") + port_authentication = module.params.get("port_authentication") + dwdm = module.params.get("dwdm") aep = module.params.get("aep") + transceiver_policy = module.params.get("transceiver_policy") + sync_e_interface_policy = module.params.get("sync_e_interface_policy") + state = module.params.get("state") name_alias = module.params.get("name_alias") @@ -396,6 +482,21 @@ def main(): (leaf access port policy group), if used\ assign null to it (port_channel_policy: null)." ) + invalid_parameters = { + "transceiver_policy": "transceiver_policy is not a valid parameter for link/node (Port Channel, Virtual Port Channel),\ + if used assign null to it (transceiver_policy: null).", + "port_authentication": "port_authentication is not a valid parameter for link/node (Port Channel, Virtual Port Channel),\ + if used assign null to it (port_authentication: null).", + "dwdm": "dwdm is not a valid parameter for link/node (Port Channel, Virtual Port Channel),\ + if used assign null to it (dwdm: null).", + "poe_interface_policy": "poe_interface_policy is not a valid parameter for link/node (Port Channel, Virtual Port Channel),\ + if used assign null to it (poe_interface_policy: null).", + } + + if lag_type in ["link", "node"]: + for param, message in invalid_parameters.items(): + if locals().get(param) is not None: + aci.fail_json(message) if lag_type == "leaf": aci_class_name = "infraAccPortGrp" @@ -407,7 +508,7 @@ def main(): ) # Reset for target_filter lag_type = None - elif lag_type in ("link", "node"): + else: aci_class_name = "infraAccBndlGrp" dn_name = "accbundle" class_config_dict = dict( @@ -523,9 +624,60 @@ def main(): ), ), ), + dict( + infraRsLinkFlapPol=dict( + attributes=dict( + tnFabricLinkFlapPolName=link_flap_policy, + ), + ), + ), + dict( + infraRsQosLlfcIfPol=dict( + attributes=dict( + tnQosLlfcIfPolName=link_level_flow_control, + ), + ), + ), + dict( + infraRsMacsecIfPol=dict( + attributes=dict( + tnMacsecIfPolName=mac_sec_interface_policy, + ), + ), + ), + dict( + infraRsCoppIfPol=dict( + attributes=dict( + tnCoppIfPolName=copp_policy, + ), + ), + ), ] - # Add infraRsattEntP binding only when aep was defined + child_classes = [ + "infraRsAttEntP", + "infraRsCdpIfPol", + "infraRsFcIfPol", + "infraRsHIfPol", + "infraRsL2IfPol", + "infraRsL2PortSecurityPol", + "infraRsLacpPol", + "infraRsLldpIfPol", + "infraRsMcpIfPol", + "infraRsMonIfInfraPol", + "infraRsQosEgressDppIfPol", + "infraRsQosIngressDppIfPol", + "infraRsQosPfcIfPol", + "infraRsQosSdIfPol", + "infraRsStormctrlIfPol", + "infraRsStpIfPol", + "infraRsLinkFlapPol", + "infraRsQosLlfcIfPol", + "infraRsMacsecIfPol", + "infraRsCoppIfPol", + ] + + # Add infraRsattEntP binding only when aep is defined if aep is not None: child_configs.append( dict( @@ -537,6 +689,79 @@ def main(): ) ) + # Add infraRsSynceEthIfPol/infraRsSynceEthIfPolBndlGrp binding only when sync_e_interface_policy is defined + if sync_e_interface_policy is not None: + if lag_type is None: + child_configs.append( + dict( + infraRsSynceEthIfPol=dict( + attributes=dict( + tnSynceEthIfPolName=sync_e_interface_policy, + ), + ), + ) + ) + child_classes.append("infraRsSynceEthIfPol") + elif lag_type == "node" or lag_type == "link": + child_configs.append( + dict( + infraRsSynceEthIfPolBndlGrp=dict( + attributes=dict( + tnSynceEthIfPolName=sync_e_interface_policy, + ), + ), + ) + ) + child_classes.append("infraRsSynceEthIfPolBndlGrp") + + # Add the children only when lag_type == leaf (Leaf Interface specific policies). + if lag_type is None: + # Add infraRsOpticsIfPol binding only when transceiver_policy was defined + if transceiver_policy is not None: + child_configs.append( + dict( + infraRsOpticsIfPol=dict( + attributes=dict( + tDn="uni/infra/{0}-{1}".format(transceiver_policy.get("type"), transceiver_policy.get("name")), + ), + ), + ) + ) + child_classes.append("infraRsOpticsIfPol") + if dwdm is not None: + child_configs.append( + dict( + infraRsDwdmIfPol=dict( + attributes=dict( + tnDwdmIfPolName=dwdm, + ), + ), + ) + ) + child_classes.append("infraRsDwdmIfPol") + if port_authentication is not None: + child_configs.append( + dict( + infraRsL2PortAuthPol=dict( + attributes=dict( + tnL2PortAuthPolName=port_authentication, + ), + ), + ) + ) + child_classes.append("infraRsL2PortAuthPol") + if poe_interface_policy is not None: + child_configs.append( + dict( + infraRsPoeIfPol=dict( + attributes=dict( + tnPoeIfPolName=poe_interface_policy, + ), + ), + ) + ) + child_classes.append("infraRsPoeIfPol") + aci.construct_url( root_class=dict( aci_class=aci_class_name, @@ -544,24 +769,7 @@ def main(): module_object=policy_group, target_filter={"name": policy_group, "lagT": lag_type}, ), - child_classes=[ - "infraRsAttEntP", - "infraRsCdpIfPol", - "infraRsFcIfPol", - "infraRsHIfPol", - "infraRsL2IfPol", - "infraRsL2PortSecurityPol", - "infraRsLacpPol", - "infraRsLldpIfPol", - "infraRsMcpIfPol", - "infraRsMonIfInfraPol", - "infraRsQosEgressDppIfPol", - "infraRsQosIngressDppIfPol", - "infraRsQosPfcIfPol", - "infraRsQosSdIfPol", - "infraRsStormctrlIfPol", - "infraRsStpIfPol", - ], + child_classes=child_classes, ) aci.get_existing() diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_interface_policy_lldp.py b/ansible_collections/cisco/aci/plugins/modules/aci_interface_policy_lldp.py index 8c1299374..12f18c9ee 100644 --- a/ansible_collections/cisco/aci/plugins/modules/aci_interface_policy_lldp.py +++ b/ansible_collections/cisco/aci/plugins/modules/aci_interface_policy_lldp.py @@ -23,7 +23,7 @@ options: aliases: [ name ] description: description: - - The description for the LLDP interface policy name. + - The description of the LLDP interface policy name. type: str aliases: [ descr ] receive_state: diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_interface_policy_mcp.py b/ansible_collections/cisco/aci/plugins/modules/aci_interface_policy_mcp.py index 1a25eced5..41f802e24 100644 --- a/ansible_collections/cisco/aci/plugins/modules/aci_interface_policy_mcp.py +++ b/ansible_collections/cisco/aci/plugins/modules/aci_interface_policy_mcp.py @@ -1,6 +1,7 @@ #!/usr/bin/python # -*- coding: utf-8 -*- +# Copyright: (c) 2023, Gaspard Micol (@gmicol) <gmicol@cisco.com> # GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) from __future__ import absolute_import, division, print_function @@ -23,7 +24,7 @@ options: aliases: [ mcp_interface, name ] description: description: - - The description for the MCP interface. + - The description of the MCP interface. type: str aliases: [ descr ] admin_state: @@ -31,6 +32,38 @@ options: - Enable or disable admin state. - The APIC defaults to C(true) when unset during creation. type: bool + mcp_mode: + description: + - Instance MCP mode + - The APIC defaults to C(non_strict) when unset during creation. + type: str + choices: [ non_strict, strict ] + grace_period: + description: + - For strict mode, grace period timeout in sec during which early loop detection takes place. + type: int + aliases: [ gracePeriod ] + grace_period_millisec: + description: + - For strict mode, grace period timeout in millisec during which early loop detection takes place + type: int + aliases: [ grace_period_msec, gracePeriodMsec ] + init_delay_time: + description: + - For strict mode, delay time in seconds for mcp to wait before sending BPDUs. + - This gives time for STP on the external network to converge. + type: int + aliases: [ strict_init_delay_time, strictInitDelayTime ] + tx_frequence: + description: + - For strict mode, transmission frequency of MCP packets until grace period on each L2 interface in seconds. + type: int + aliases: [ strict_tx_freq, strictTxFreq ] + tx_frequence_millisec: + description: + - For strict mode, transmission frequency of MCP packets until grace period on each L2 interface in milliseconds + type: int + aliases: [strict_tx_freq_msec, strictTxFreqMsec ] state: description: - Use C(present) or C(absent) for adding or removing. @@ -53,6 +86,7 @@ seealso: link: https://developer.cisco.com/docs/apic-mim-ref/ author: - Dag Wieers (@dagwieers) +- Gaspard Micol (@gmicol) """ EXAMPLES = r""" @@ -204,6 +238,9 @@ from ansible.module_utils.basic import AnsibleModule from ansible_collections.cisco.aci.plugins.module_utils.aci import ACIModule, aci_argument_spec, aci_annotation_spec, aci_owner_spec +MATCH_MCP_MODE_MAPPING = {"non_strict": "off", "strict": "on"} + + def main(): argument_spec = aci_argument_spec() argument_spec.update(aci_annotation_spec()) @@ -212,6 +249,12 @@ def main(): mcp=dict(type="str", aliases=["mcp_interface", "name"]), # Not required for querying all objects description=dict(type="str", aliases=["descr"]), admin_state=dict(type="bool"), + mcp_mode=dict(type="str", choices=list(MATCH_MCP_MODE_MAPPING.keys())), + grace_period=dict(type="int", aliases=["gracePeriod"]), + grace_period_millisec=dict(type="int", aliases=["grace_period_msec", "gracePeriodMsec"]), + init_delay_time=dict(type="int", aliases=["strict_init_delay_time", "strictInitDelayTime"]), + tx_frequence=dict(type="int", aliases=["strict_tx_freq", "strictTxFreq"]), + tx_frequence_millisec=dict(type="int", aliases=["strict_tx_freq_msec", "strictTxFreqMsec"]), state=dict(type="str", default="present", choices=["absent", "present", "query"]), name_alias=dict(type="str"), ) @@ -230,6 +273,12 @@ def main(): mcp = module.params.get("mcp") description = module.params.get("description") admin_state = aci.boolean(module.params.get("admin_state"), "enabled", "disabled") + mcp_mode = MATCH_MCP_MODE_MAPPING.get(module.params.get("mcp_mode")) + grace_period = module.params.get("grace_period") + grace_period_millisec = module.params.get("grace_period_millisec") + init_delay_time = module.params.get("init_delay_time") + tx_frequence = module.params.get("tx_frequence") + tx_frequence_millisec = module.params.get("tx_frequence_millisec") state = module.params.get("state") name_alias = module.params.get("name_alias") @@ -251,6 +300,12 @@ def main(): name=mcp, descr=description, adminSt=admin_state, + mcpMode=mcp_mode, + gracePeriod=grace_period, + gracePeriodMsec=grace_period_millisec, + strictInitDelayTime=init_delay_time, + strictTxFreq=tx_frequence, + strictTxFreqMsec=tx_frequence_millisec, nameAlias=name_alias, ), ) diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_interface_policy_ospf.py b/ansible_collections/cisco/aci/plugins/modules/aci_interface_policy_ospf.py index 040e13963..5fce567ae 100644 --- a/ansible_collections/cisco/aci/plugins/modules/aci_interface_policy_ospf.py +++ b/ansible_collections/cisco/aci/plugins/modules/aci_interface_policy_ospf.py @@ -31,7 +31,7 @@ options: aliases: [ ospf_interface, name ] description: description: - - The description for the OSPF interface. + - The description of the OSPF interface. type: str aliases: [ descr ] network_type: @@ -40,7 +40,7 @@ options: - OSPF supports broadcast and point-to-point. - The APIC defaults to C(unspecified) when unset during creation. type: str - choices: [ bcast, p2p ] + choices: [ bcast, p2p, unspecified ] cost: description: - The OSPF cost of the interface. @@ -71,7 +71,7 @@ options: interface is announced as part of the routing network. type: list elements: str - choices: [ advert-subnet, bfd, mtu-ignore, passive ] + choices: [ advert-subnet, bfd, mtu-ignore, passive, unspecified ] dead_interval: description: - The interval between hello packets from a neighbor before the router @@ -95,7 +95,8 @@ options: description: - Whether prefix suppressions is enabled or disabled. - The APIC defaults to C(inherit) when unset during creation. - type: bool + type: str + choices: [ inherit, enable, disable ] priority: description: - The priority for the OSPF interface profile. @@ -302,12 +303,12 @@ def main(): tenant=dict(type="str", aliases=["tenant_name"]), # Not required for querying all objects ospf=dict(type="str", aliases=["ospf_interface", "name"]), # Not required for querying all objects description=dict(type="str", aliases=["descr"]), - network_type=dict(type="str", choices=["bcast", "p2p"]), + network_type=dict(type="str", choices=["bcast", "p2p", "unspecified"]), cost=dict(type="int"), - controls=dict(type="list", elements="str", choices=["advert-subnet", "bfd", "mtu-ignore", "passive"]), + controls=dict(type="list", elements="str", choices=["advert-subnet", "bfd", "mtu-ignore", "passive", "unspecified"]), dead_interval=dict(type="int"), hello_interval=dict(type="int"), - prefix_suppression=dict(type="bool"), + prefix_suppression=dict(type="str", choices=["inherit", "enable", "disable"]), priority=dict(type="int"), retransmit_interval=dict(type="int"), transmit_delay=dict(type="int"), @@ -330,11 +331,9 @@ def main(): ospf = module.params.get("ospf") description = module.params.get("description") name_alias = module.params.get("name_alias") - - if module.params.get("controls") is None: - controls = None - else: - controls = ",".join(module.params.get("controls")) + network_type = module.params.get("network_type") + prefix_suppression = module.params.get("prefix_suppression") + controls = ",".join(module.params.get("controls")) if module.params.get("controls") else None cost = module.params.get("cost") if cost is not None and cost not in range(1, 451): @@ -348,8 +347,6 @@ def main(): if hello_interval is not None and hello_interval not in range(1, 65536): module.fail_json(msg="Parameter 'hello_interval' is only valid in range between 1 and 65536.") - network_type = module.params.get("network_type") - prefix_suppression = aci.boolean(module.params.get("prefix_suppression"), "enabled", "disabled") priority = module.params.get("priority") if priority is not None and priority not in range(0, 256): module.fail_json(msg="Parameter 'priority' is only valid in range between 1 and 255.") diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_interface_policy_port_channel.py b/ansible_collections/cisco/aci/plugins/modules/aci_interface_policy_port_channel.py index 028bd849b..c6422df9c 100644 --- a/ansible_collections/cisco/aci/plugins/modules/aci_interface_policy_port_channel.py +++ b/ansible_collections/cisco/aci/plugins/modules/aci_interface_policy_port_channel.py @@ -23,7 +23,7 @@ options: aliases: [ name ] description: description: - - The description for the port channel. + - The description of the port channel. type: str aliases: [ descr ] max_links: diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_interface_policy_spine_policy_group.py b/ansible_collections/cisco/aci/plugins/modules/aci_interface_policy_spine_policy_group.py new file mode 100644 index 000000000..ab413ce3d --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_interface_policy_spine_policy_group.py @@ -0,0 +1,355 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2023, Anvitha Jain <anvjain@cisco.com> +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported_by": "certified"} + +DOCUMENTATION = r""" +--- +module: aci_interface_policy_spine_policy_group +short_description: Manage spine access interface policy groups (infra:SpAccPortGrp) +description: +- Manage spine access interface policy groups on Cisco ACI fabrics. +options: + policy_group: + description: + - The name of the spine access interface policy group. + type: str + aliases: [ name, policy_group_name, spine_policy_group_name ] + description: + description: + - Description of the spine access interface + type: str + aliases: [ descr ] + name_alias: + description: + - The alias of the current object. This relates to the nameAlias field in ACI. + type: str + link_level_policy: + description: + - The name of the link level policy used by the spine access interface + type: str + aliases: [ link_level_policy_name ] + link_flap_policy: + description: + - The name of the link flap policy used by the spine access interface + type: str + aliases: [ link_flap_policy_name ] + cdp_policy: + description: + - The name of the cdp policy used by the spine access interface + type: str + aliases: [ cdp_policy_name ] + mac_sec_policy: + description: + - The name of the mac sec policy used by the spine access interface + type: str + aliases: [ mac_sec_policy_name ] + attached_entity_profile: + description: + - The name of the attached entity profile used by the spine access interface + type: str + aliases: [ aep_name, aep ] + state: + description: + - Use C(present) or C(absent) for adding or removing. + - Use C(query) for listing an object or multiple objects. + type: str + choices: [ absent, present, query ] + default: present +extends_documentation_fragment: +- cisco.aci.aci +- cisco.aci.annotation +- cisco.aci.owner + +notes: +seealso: +- name: APIC Management Information Model reference + description: More information about the internal APIC classes B(infra:SpAccPortGrp). + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Anvitha Jain (@anvjain) +""" + +EXAMPLES = r""" +- name: Create a Spine Interface Policy Group + cisco.aci.aci_interface_policy_spine_policy_group: + host: apic + username: admin + password: SomeSecretPassword + name: spinepolicygroupname + description: policygroupname description + link_level_policy: somelinklevelpolicy + link_flap_policy: somelinkflappolicy + cdp_policy: somecdppolicy + mac_sec_policy: somemacsecpolicy + attached_entity_profile: someattachedentityprofile + state: present + delegate_to: localhost + +- name: Query all Spine Access Port Policy Groups of type link + cisco.aci.aci_interface_policy_spine_policy_group: + host: apic + username: admin + password: SomeSecretPassword + state: query + delegate_to: localhost + register: query_result + +- name: Query a specific Lead Access Port Policy Group + cisco.aci.aci_interface_policy_spine_policy_group: + host: apic + username: admin + password: SomeSecretPassword + name: spinepolicygroupname + state: query + delegate_to: localhost + register: query_result + +- name: Delete an Interface policy Spine Policy Group + cisco.aci.aci_interface_policy_spine_policy_group: + host: apic + username: admin + password: SomeSecretPassword + name: spinepolicygroupname + state: absent + delegate_to: localhost +""" + +RETURN = r""" +current: + description: The existing configuration from the APIC after the module has finished + returned: success + type: list + sample: + [ + { + "fvTenant": { + "attributes": { + "descr": "Production environment", + "dn": "uni/tn-production", + "name": "production", + "nameAlias": "", + "ownerKey": "", + "ownerTag": "" + } + } + } + ] +error: + description: The error information as returned from the APIC + returned: failure + type: dict + sample: + { + "code": "122", + "text": "unknown managed object class foo" + } +raw: + description: The raw output returned by the APIC REST API (xml or json) + returned: parse error + type: str + sample: '<?xml version="1.0" encoding="UTF-8"?><imdata totalCount="1"><error code="122" text="unknown managed object class foo"/></imdata>' +sent: + description: The actual/minimal configuration pushed to the APIC + returned: info + type: list + sample: + { + "fvTenant": { + "attributes": { + "descr": "Production environment" + } + } + } +previous: + description: The original configuration from the APIC before the module has started + returned: info + type: list + sample: + [ + { + "fvTenant": { + "attributes": { + "descr": "Production", + "dn": "uni/tn-production", + "name": "production", + "nameAlias": "", + "ownerKey": "", + "ownerTag": "" + } + } + } + ] +proposed: + description: The assembled configuration from the user-provided parameters + returned: info + type: dict + sample: + { + "fvTenant": { + "attributes": { + "descr": "Production environment", + "name": "production" + } + } + } +filter_string: + description: The filter string used for the request + returned: failure or debug + type: str + sample: ?rsp-prop-include=config-only +method: + description: The HTTP method used for the request to the APIC + returned: failure or debug + type: str + sample: POST +response: + description: The HTTP response from the APIC + returned: failure or debug + type: str + sample: OK (30 bytes) +status: + description: The HTTP status from the APIC + returned: failure or debug + type: int + sample: 200 +url: + description: The HTTP url used for the request to the APIC + returned: failure or debug + type: str + sample: https://10.11.12.13/api/mo/uni/tn-production.json +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.cisco.aci.plugins.module_utils.aci import ACIModule, aci_argument_spec, aci_annotation_spec, aci_owner_spec + + +def main(): + argument_spec = aci_argument_spec() + argument_spec.update(aci_annotation_spec()) + argument_spec.update(aci_owner_spec()) + argument_spec.update( + policy_group=dict(type="str", aliases=["policy_group_name", "spine_policy_group_name", "name"]), # Not required for querying all objects + description=dict(type="str", aliases=["descr"]), + name_alias=dict(type="str"), + link_level_policy=dict(type="str", aliases=["link_level_policy_name"]), + link_flap_policy=dict(type="str", aliases=["link_flap_policy_name"]), + cdp_policy=dict(type="str", aliases=["cdp_policy_name"]), + mac_sec_policy=dict(type="str", aliases=["mac_sec_policy_name"]), + attached_entity_profile=dict(type="str", aliases=["aep_name", "aep"]), + state=dict(type="str", default="present", choices=["absent", "present", "query"]), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_if=[ + ["state", "absent", ["policy_group"]], + ["state", "present", ["policy_group"]], + ], + ) + + policy_group = module.params.get("policy_group") + description = module.params.get("description") + name_alias = module.params.get("name_alias") + link_level_policy = module.params.get("link_level_policy") + link_flap_policy = module.params.get("link_flap_policy") + cdp_policy = module.params.get("cdp_policy") + mac_sec_policy = module.params.get("mac_sec_policy") + attached_entity_profile = module.params.get("attached_entity_profile") + state = module.params.get("state") + + aci = ACIModule(module) + + class_config = dict( + name=policy_group, + descr=description, + nameAlias=name_alias, + ) + + child_configs = [ + dict( + infraRsHIfPol=dict( + attributes=dict( + tnFabricHIfPolName=link_level_policy, + ), + ), + ), + dict( + infraRsLinkFlapPol=dict( + attributes=dict( + tnFabricLinkFlapPolName=link_flap_policy, + ), + ), + ), + dict( + infraRsCdpIfPol=dict( + attributes=dict( + tnCdpIfPolName=cdp_policy, + ), + ), + ), + dict( + infraRsMacsecIfPol=dict( + attributes=dict( + tnMacsecIfPolName=mac_sec_policy, + ), + ), + ), + ] + + if attached_entity_profile is not None: + child_configs.append( + dict( + infraRsAttEntP=dict( + attributes=dict( + tDn="uni/infra/attentp-{0}".format(attached_entity_profile), + ), + ), + ) + ) + + aci.construct_url( + root_class=dict( + aci_class="infraSpAccPortGrp", + aci_rn="infra/funcprof/spaccportgrp-{0}".format(policy_group), + module_object=policy_group, + target_filter={"name": policy_group}, + ), + child_classes=[ + "infraRsHIfPol", + "infraRsLinkFlapPol", + "infraRsCdpIfPol", + "infraRsMacsecIfPol", + "infraRsAttEntP", + ], + ) + + aci.get_existing() + + if state == "present": + aci.payload( + aci_class="infraSpAccPortGrp", + class_config=class_config, + child_configs=child_configs, + ) + + aci.get_diff(aci_class="infraSpAccPortGrp") + + aci.post_config() + + elif state == "absent": + aci.delete_config() + + aci.exit_json() + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_l3out.py b/ansible_collections/cisco/aci/plugins/modules/aci_l3out.py index 70cd11f8c..56f158b3d 100644 --- a/ansible_collections/cisco/aci/plugins/modules/aci_l3out.py +++ b/ansible_collections/cisco/aci/plugins/modules/aci_l3out.py @@ -1,13 +1,18 @@ #!/usr/bin/python # -*- coding: utf-8 -*- +# Copyright: (c) 2023, Gaspard Micol (@gmicol) <gmicol@cisco.com> # GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) from __future__ import absolute_import, division, print_function __metaclass__ = type -ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported_by": "certified"} +ANSIBLE_METADATA = { + "metadata_version": "1.1", + "status": ["preview"], + "supported_by": "certified", +} DOCUMENTATION = r""" --- @@ -46,20 +51,84 @@ options: route_control: description: - Route Control enforcement direction. The only allowed values are export or import,export. + - The APIC defaults to C(export) when unset during creation. type: list elements: str choices: [ export, import ] aliases: [ route_control_enforcement ] + mpls: + description: + - Indicate whether MPLS (Multi-Protocol Label Switching) is enabled or not. + - The APIC defaults to C(no) when unset during creation. + type: str + choices: [ "no", "yes" ] l3protocol: description: - - Routing protocol for the L3Out + - Routing protocol for the L3Out. + - Protocols already associated with an l3out must be provided again when the l3out is modified if the associated protocols are to be kept. + - The Protocols are otherwise deleted if not provided each time an l3out is modified. + - First example, to add BGP protocol to an l3out with OSPF protocol, the user must enter C([ bgp, ospf ]) even though "ospf" was provided before. + - Second example, to change the protocol from OSPF to EIGRP, the user must simply enter C([ eigrp ]) and the previous OSPF protocol will be deleted. + - To remove all existing protocols, the user must enter C([ static ]). type: list elements: str choices: [ bgp, eigrp, ospf, pim, static ] + ospf: + description: + - Parameters for the OSPF protocol. + type: dict + suboptions: + area_cost: + description: + - The OSPF area cost. + - The APIC defaults to C(1) when unset during creation. + type: int + area_ctrl: + description: + - The controls of redistribution and summary LSA generation into NSSA and Stub areas. + - The APIC defaults to C(redistribute,summary) when unset during creation. + type: list + elements: str + choices: [ redistribute, summary, suppress-fa, unspecified ] + area_id: + description: + - The OSPF Area ID. + - An area is a logical collection of OSPF networks, routers, and links that have the same area identification. + - A router within an area must maintain a topological database for the area to which it belongs. + - The router doesn't have detailed information about network topology outside of its area, thereby reducing the size of its database. + - Areas limit the scope of route information distribution. It is not possible to do route update filtering within an area. + - The link-state database (LSDB) of routers within the same area must be synchronized and be exactly the same. + - However, route summarization and filtering is possible between different areas. + - The main benefit of creating areas is a reduction in the number of routes to propagate-by the filtering and the summarization of routes. + - Areas are identified by an area ID. + - Cisco IOS software supports area IDs expressed in IP address format or decimal format, for example, area 0.0.0.0 is equal to area 0. + - The APIC defaults to C(1) when unset during creation. + type: str + area_type: + description: + - The OSPF area type. + - The APIC defaults to C(nssa) when unset during creation. + type: str + choices: [ nssa, regular, stub ] + description: + description: + - Specifies the description of a policy component. + type: str + aliases: [ descr ] + multipod_internal: + description: + - Start OSPF in WAN instance instead of default. + - The APIC defaults to C(no) when unset during creation. + type: str + choices: [ "no", "yes" ] + name_alias: + description: + - The alias for the current object. This relates to the nameAlias field in ACI. + type: str asn: description: - The AS number for the L3Out. - - Only applicable when using 'eigrp' as the l3protocol + - Only applicable when using 'eigrp' as the l3protocol. type: int aliases: [ as_number ] description: @@ -85,7 +154,7 @@ extends_documentation_fragment: notes: - The C(tenant) and C(domain) and C(vrf) used must exist before using this module in your playbook. - The M(cisco.aci.aci_tenant) and M(cisco.aci.aci_domain) and M(cisco.aci.aci_vrf) modules can be used for this. +- The M(cisco.aci.aci_tenant) and M(cisco.aci.aci_domain) and M(cisco.aci.aci_vrf) modules can be used for this. seealso: - module: cisco.aci.aci_tenant - module: cisco.aci.aci_domain @@ -95,6 +164,7 @@ seealso: link: https://developer.cisco.com/docs/apic-mim-ref/ author: - Rostyslav Davydenko (@rost-d) +- Gaspard Micol (@gmicol) """ EXAMPLES = r""" @@ -109,6 +179,12 @@ EXAMPLES = r""" domain: l3dom_prod vrf: prod l3protocol: ospf + ospf: + area_cost: 1 + area_ctrl: [ summary, redistribute ] + area_id: 0.0.0.1 + area_type: regular + multipod_internal: no state: present delegate_to: localhost @@ -132,6 +208,15 @@ EXAMPLES = r""" state: query delegate_to: localhost register: query_result + +- name: Query all L3Outs + cisco.aci.aci_l3out: + host: apic + username: admin + password: SomeSecretPassword + state: query + delegate_to: localhost + register: query_all_result """ RETURN = r""" @@ -240,7 +325,13 @@ url: """ from ansible.module_utils.basic import AnsibleModule -from ansible_collections.cisco.aci.plugins.module_utils.aci import ACIModule, aci_argument_spec, aci_annotation_spec, aci_owner_spec +from ansible_collections.cisco.aci.plugins.module_utils.aci import ( + ACIModule, + aci_argument_spec, + aci_annotation_spec, + aci_owner_spec, +) +from ansible_collections.cisco.aci.plugins.module_utils.aci import ospf_spec def main(): @@ -253,7 +344,13 @@ def main(): domain=dict(type="str", aliases=["ext_routed_domain_name", "routed_domain"]), vrf=dict(type="str", aliases=["vrf_name"]), description=dict(type="str", aliases=["descr"]), - route_control=dict(type="list", elements="str", choices=["export", "import"], aliases=["route_control_enforcement"]), + route_control=dict( + type="list", + elements="str", + choices=["export", "import"], + aliases=["route_control_enforcement"], + ), + mpls=dict(type="str", choices=["no", "yes"]), dscp=dict( type="str", choices=[ @@ -283,7 +380,12 @@ def main(): ], aliases=["target"], ), - l3protocol=dict(type="list", elements="str", choices=["bgp", "eigrp", "ospf", "pim", "static"]), + l3protocol=dict( + type="list", + elements="str", + choices=["bgp", "eigrp", "ospf", "pim", "static"], + ), + ospf=dict(type="dict", options=ospf_spec()), asn=dict(type="int", aliases=["as_number"]), state=dict(type="str", default="present", choices=["absent", "present", "query"]), name_alias=dict(type="str"), @@ -304,9 +406,11 @@ def main(): domain = module.params.get("domain") dscp = module.params.get("dscp") description = module.params.get("description") - enforceRtctrl = module.params.get("route_control") + route_control = module.params.get("route_control") + mpls = module.params.get("mpls") vrf = module.params.get("vrf") l3protocol = module.params.get("l3protocol") + ospf = module.params.get("ospf") asn = module.params.get("asn") state = module.params.get("state") tenant = module.params.get("tenant") @@ -317,16 +421,62 @@ def main(): module.fail_json(msg="Parameter 'asn' is required when l3protocol is 'eigrp'") if "eigrp" not in l3protocol and asn is not None: module.warn("Parameter 'asn' is only applicable when l3protocol is 'eigrp'. The ASN will be ignored") + if "ospf" not in l3protocol and ospf is not None: + module.warn("Parameter 'ospf' is only applicable when l3protocol is 'ospf'. The OPSF specifications will be ignored") - enforce_ctrl = "" - if enforceRtctrl is not None: - if len(enforceRtctrl) == 1 and enforceRtctrl[0] == "import": + enforce_ctrl = None + if route_control is not None: + if len(route_control) == 1 and route_control[0] == "import": aci.fail_json("The route_control parameter is invalid: allowed options are export or import,export only") - elif len(enforceRtctrl) == 1 and enforceRtctrl[0] == "export": - enforce_ctrl = "export" else: - enforce_ctrl = "export,import" - child_classes = ["l3extRsL3DomAtt", "l3extRsEctx", "bgpExtP", "ospfExtP", "eigrpExtP", "pimExtP"] + enforce_ctrl = ",".join(route_control) + + child_classes = [ + "l3extRsL3DomAtt", + "l3extRsEctx", + "bgpExtP", + "ospfExtP", + "eigrpExtP", + "pimExtP", + ] + + child_configs = [ + dict(l3extRsL3DomAtt=dict(attributes=dict(tDn="uni/l3dom-{0}".format(domain)))), + dict(l3extRsEctx=dict(attributes=dict(tnFvCtxName=vrf))), + ] + if l3protocol is not None: + l3protocol_child_configs = dict( + bgp=dict(bgpExtP=dict(attributes=dict(status="deleted"))), + eigrp=dict(eigrpExtP=dict(attributes=dict(status="deleted"))), + ospf=dict(ospfExtP=dict(attributes=dict(status="deleted"))), + pim=dict(pimExtP=dict(attributes=dict(status="deleted"))), + ) + for protocol in l3protocol: + if protocol == "bgp": + l3protocol_child_configs["bgp"] = dict(bgpExtP=dict(attributes=dict(descr=""))) + elif protocol == "eigrp": + l3protocol_child_configs["eigrp"] = dict(eigrpExtP=dict(attributes=dict(asn=asn))) + elif protocol == "ospf": + if isinstance(ospf, dict): + ospf["area_ctrl"] = ",".join(ospf.get("area_ctrl")) + l3protocol_child_configs["ospf"] = dict( + ospfExtP=dict( + attributes=dict( + areaCost=ospf.get("area_cost"), + areaCtrl=ospf.get("area_ctrl"), + areaId=ospf.get("area_id"), + areaType=ospf.get("area_type"), + descr=ospf.get("description"), + multipodInternal=ospf.get("multipod_internal"), + nameAlias=ospf.get("name_alias"), + ) + ) + ) + else: + l3protocol_child_configs["ospf"] = dict(ospfExtP=dict(attributes=dict(descr=""))) + elif protocol == "pim": + l3protocol_child_configs["pim"] = dict(pimExtP=dict(attributes=dict(descr=""))) + child_configs.extend(list(l3protocol_child_configs.values())) aci.construct_url( root_class=dict( @@ -346,29 +496,14 @@ def main(): aci.get_existing() - child_configs = [ - dict(l3extRsL3DomAtt=dict(attributes=dict(tDn="uni/l3dom-{0}".format(domain)))), - dict(l3extRsEctx=dict(attributes=dict(tnFvCtxName=vrf))), - ] - if l3protocol is not None: - for protocol in l3protocol: - if protocol == "bgp": - child_configs.append(dict(bgpExtP=dict(attributes=dict(descr="", nameAlias="")))) - elif protocol == "eigrp": - child_configs.append(dict(eigrpExtP=dict(attributes=dict(descr="", nameAlias="", asn=asn)))) - elif protocol == "ospf": - child_configs.append(dict(ospfExtP=dict(attributes=dict(descr="", nameAlias="")))) - elif protocol == "pim": - child_configs.append(dict(pimExtP=dict(attributes=dict(descr="", nameAlias="")))) - if state == "present": aci.payload( aci_class="l3extOut", class_config=dict( name=l3out, descr=description, - dn="uni/tn-{0}/out-{1}".format(tenant, l3out), enforceRtctrl=enforce_ctrl, + mplsEnabled=mpls, targetDscp=dscp, nameAlias=name_alias, ), diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_l3out_bgp_peer.py b/ansible_collections/cisco/aci/plugins/modules/aci_l3out_bgp_peer.py index 83bb9ce14..4719c42ca 100644 --- a/ansible_collections/cisco/aci/plugins/modules/aci_l3out_bgp_peer.py +++ b/ansible_collections/cisco/aci/plugins/modules/aci_l3out_bgp_peer.py @@ -130,6 +130,19 @@ options: - Name of the Route Control Profile direction. type: str required: true + local_as_number_config: + description: + - The local Autonomous System Number (ASN) configuration of the L3Out BGP Peer. + - The APIC defaults to C(none) when unset during creation. + type: str + choices: [ dual-as, no-prepend, none, replace-as ] + aliases: [ local_as_num_config ] + local_as_number: + description: + - The local Autonomous System Number (ASN) of the L3Out BGP Peer. + - The APIC defaults to 0 when unset during creation. + type: int + aliases: [ local_as_num ] state: description: - Use C(present) or C(absent) for adding or removing. @@ -439,6 +452,8 @@ def main(): elements="dict", options=route_control_profile_spec(), ), + local_as_number_config=dict(type="str", choices=["dual-as", "no-prepend", "none", "replace-as"], aliases=["local_as_num_config"]), + local_as_number=dict(type="int", aliases=["local_as_num"]), ) module = AnsibleModule( @@ -470,6 +485,8 @@ def main(): admin_state = module.params.get("admin_state") allow_self_as_count = module.params.get("allow_self_as_count") route_control_profiles = module.params.get("route_control_profiles") + local_as_number_config = module.params.get("local_as_number_config") + local_as_number = module.params.get("local_as_number") aci = ACIModule(module) if node_id: @@ -492,6 +509,15 @@ def main(): ) ) + if local_as_number_config or local_as_number: + child_configs.append( + dict( + bgpLocalAsnP=dict( + attributes=dict(asnPropagate=local_as_number_config, localAsn=local_as_number), + ), + ) + ) + if route_control_profiles: child_classes.append("bgpRsPeerToProfile") for profile in route_control_profiles: @@ -514,22 +540,17 @@ def main(): ) ) - subclass_3 = None - subclass_4 = None - subclass_5 = None - - bgp_peer_profile_dict = None - - if peer_ip or state == "query": - bgp_peer_profile_dict = dict( - aci_class="bgpPeerP", - aci_rn="peerP-[{0}]".format(peer_ip), - module_object=peer_ip, - target_filter={"addr": peer_ip}, - ) + bgp_peer_profile_dict = dict( + aci_class="bgpPeerP", + aci_rn="peerP-[{0}]".format(peer_ip), + module_object=peer_ip, + target_filter={"addr": peer_ip}, + ) if interface_profile is None: subclass_3 = bgp_peer_profile_dict + subclass_4 = None + subclass_5 = None else: subclass_3 = dict( aci_class="l3extLIfP", diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_l3out_bgp_protocol_profile.py b/ansible_collections/cisco/aci/plugins/modules/aci_l3out_bgp_protocol_profile.py new file mode 100644 index 000000000..aacf22d3d --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_l3out_bgp_protocol_profile.py @@ -0,0 +1,326 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2023, Gaspard Micol (@gmicol) <gmicol@cisco.com> +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported_by": "certified"} + +DOCUMENTATION = r""" +--- +module: aci_l3out_bgp_protocol_profile +short_description: Manage BGP Protocol Profile (bgp:ProtP) +description: +- Manage BGP Protocol Profile for The Logical Node Profiles on Cisco ACI fabrics. +options: + tenant: + description: + - The name of an existing tenant. + type: str + aliases: [ tenant_name ] + l3out: + description: + - The name of an existing L3Out. + type: str + aliases: [ l3out_name ] + node_profile: + description: + - The name of an existing logical node profile. + type: str + aliases: [ node_profile_name, logical_node ] + bgp_timers_policy: + description: + - The name of an existing bgp timers policy. + type: str + aliases: [ bgp_timers_policy_name ] + bgp_best_path_policy: + description: + - The name of the bgp best path control policy. + type: str + aliases: [ bgp_best_path_policy_name ] + state: + description: + - Use C(present) or C(absent) for adding or removing. + - Use C(query) for listing an object or multiple objects. + type: str + choices: [ absent, present, query ] + default: present + name_alias: + description: + - The alias for the current object. This relates to the nameAlias field in ACI. + type: str +extends_documentation_fragment: +- cisco.aci.aci +- cisco.aci.annotation +- cisco.aci.owner + +notes: +- The C(tenant), C(l3out) and C(node_profile) used must exist before using this module in your playbook. + The M(cisco.aci.aci_tenant), M(cisco.aci.aci_l3out) and M(cisco.aci.aci_l3out_logical_node_profile) modules can be used for this. +- If C(bgp_timers_policy) and/or C(bgp_best_path_policy) are used, they must exist before using this module in your playbook. + The M(cisco.aci.aci_bgp_timers_policy) and M(cisco.aci.aci_bgp_best_path_policy) modules can be used for this. +seealso: +- module: cisco.aci.aci_tenant +- module: cisco.aci.aci_l3out +- module: cisco.aci.aci_l3out_logical_node_profile +- module: cisco.aci.aci_bgp_timers_policy +- module: cisco.aci.aci_bgp_best_path_policy +- name: APIC Management Information Model reference + description: More information about the internal APIC class B(bgp:ProtP). + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Gaspard Micol (@gmicol) +""" + +EXAMPLES = r""" +- name: Create a l3out BGP protocol profile + cisco.aci.l3out_bgp_protocol_profile: + host: apic + username: admin + password: SomeSecretPassword + tenant: production + l3out: prod_l3out + node_profile: prod_node_profile + bgp_timers_policy: prod_bgp_timers_policy + bgp_best_path_policy: prod_bgp_best_path_policy + state: present + delegate_to: localhost + +- name: Delete a l3out BGP protocol profile + cisco.aci.l3out_bgp_protocol_profile: + host: apic + username: admin + password: SomeSecretPassword + tenant: production + l3out: prod_l3out + node_profile: prod_node_profile + state: absent + delegate_to: localhost + +- name: Query all l3out BGP protocol profiles + cisco.aci.l3out_bgp_protocol_profile: + host: apic + username: admin + password: SomeSecretPassword + state: query + delegate_to: localhost + register: query_result + +- name: Query a specific l3out BGP protocol profile + cisco.aci.l3out_bgp_protocol_profile: + host: apic + username: admin + password: SomeSecretPassword + tenant: production + l3out: prod_l3out + node_profile: prod_node_profile + state: query + delegate_to: localhost + register: query_result +""" + +RETURN = r""" +current: + description: The existing configuration from the APIC after the module has finished + returned: success + type: list + sample: + [ + { + "fvTenant": { + "attributes": { + "descr": "Production environment", + "dn": "uni/tn-production", + "name": "production", + "nameAlias": "", + "ownerKey": "", + "ownerTag": "" + } + } + } + ] +error: + description: The error information as returned from the APIC + returned: failure + type: dict + sample: + { + "code": "122", + "text": "unknown managed object class foo" + } +raw: + description: The raw output returned by the APIC REST API (xml or json) + returned: parse error + type: str + sample: '<?xml version="1.0" encoding="UTF-8"?><imdata totalCount="1"><error code="122" text="unknown managed object class foo"/></imdata>' +sent: + description: The actual/minimal configuration pushed to the APIC + returned: info + type: list + sample: + { + "fvTenant": { + "attributes": { + "descr": "Production environment" + } + } + } +previous: + description: The original configuration from the APIC before the module has started + returned: info + type: list + sample: + [ + { + "fvTenant": { + "attributes": { + "descr": "Production", + "dn": "uni/tn-production", + "name": "production", + "nameAlias": "", + "ownerKey": "", + "ownerTag": "" + } + } + } + ] +proposed: + description: The assembled configuration from the user-provided parameters + returned: info + type: dict + sample: + { + "fvTenant": { + "attributes": { + "descr": "Production environment", + "name": "production" + } + } + } +filter_string: + description: The filter string used for the request + returned: failure or debug + type: str + sample: ?rsp-prop-include=config-only +method: + description: The HTTP method used for the request to the APIC + returned: failure or debug + type: str + sample: POST +response: + description: The HTTP response from the APIC + returned: failure or debug + type: str + sample: OK (30 bytes) +status: + description: The HTTP status from the APIC + returned: failure or debug + type: int + sample: 200 +url: + description: The HTTP url used for the request to the APIC + returned: failure or debug + type: str + sample: https://10.11.12.13/api/mo/uni/tn-production.json +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.cisco.aci.plugins.module_utils.aci import ACIModule, aci_argument_spec, aci_annotation_spec, aci_owner_spec + + +def main(): + argument_spec = aci_argument_spec() + argument_spec.update(aci_annotation_spec()) + argument_spec.update(aci_owner_spec()) + argument_spec.update( + tenant=dict(type="str", aliases=["tenant_name"]), # Not required for querying all objects + l3out=dict(type="str", aliases=["l3out_name"]), # Not required for querying all objects + node_profile=dict(type="str", aliases=["node_profile_name", "logical_node"]), # Not required for querying all objects + bgp_timers_policy=dict(type="str", aliases=["bgp_timers_policy_name"]), + bgp_best_path_policy=dict(type="str", aliases=["bgp_best_path_policy_name"]), + state=dict(type="str", default="present", choices=["absent", "present", "query"]), + name_alias=dict(type="str"), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_if=[ + ["state", "absent", ["tenant", "l3out", "node_profile"]], + ["state", "present", ["tenant", "l3out", "node_profile"]], + ], + ) + + bgp_timers_policy = module.params.get("bgp_timers_policy") + bgp_best_path_policy = module.params.get("bgp_best_path_policy") + state = module.params.get("state") + tenant = module.params.get("tenant") + l3out = module.params.get("l3out") + node_profile = module.params.get("node_profile") + name_alias = module.params.get("name_alias") + + aci = ACIModule(module) + + child_classes = ["bgpRsBgpNodeCtxPol", "bgpRsBestPathCtrlPol"] + + aci.construct_url( + root_class=dict( + aci_class="fvTenant", + aci_rn="tn-{0}".format(tenant), + module_object=tenant, + target_filter={"name": tenant}, + ), + subclass_1=dict( + aci_class="l3extOut", + aci_rn="out-{0}".format(l3out), + module_object=l3out, + target_filter={"name": l3out}, + ), + subclass_2=dict( + aci_class="l3extLNodeP", + aci_rn="lnodep-{0}".format(node_profile), + module_object=node_profile, + target_filter={"name": node_profile}, + ), + subclass_3=dict( + aci_class="bgpProtP", + aci_rn="protp", + module_object="", + target_filter={"name": ""}, + ), + child_classes=child_classes, + ) + + aci.get_existing() + + if state == "present": + child_configs = [] + if bgp_timers_policy is not None: + child_configs.append(dict(bgpRsBgpNodeCtxPol=dict(attributes=dict(tnBgpCtxPolName=bgp_timers_policy)))) + if bgp_best_path_policy is not None: + child_configs.append(dict(bgpRsBestPathCtrlPol=dict(attributes=dict(tnBgpBestPathCtrlPolName=bgp_best_path_policy)))) + + aci.payload( + aci_class="bgpProtP", + class_config=dict( + nameAlias=name_alias, + ), + child_configs=child_configs, + ) + + aci.get_diff(aci_class="bgpProtP") + + aci.post_config() + + elif state == "absent": + aci.delete_config() + + aci.exit_json() + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_l3out_extepg.py b/ansible_collections/cisco/aci/plugins/modules/aci_l3out_extepg.py index 514708bd7..acaa7ea9e 100644 --- a/ansible_collections/cisco/aci/plugins/modules/aci_l3out_extepg.py +++ b/ansible_collections/cisco/aci/plugins/modules/aci_l3out_extepg.py @@ -49,6 +49,38 @@ options: type: str choices: [ AF11, AF12, AF13, AF21, AF22, AF23, AF31, AF32, AF33, AF41, AF42, AF43, CS0, CS1, CS2, CS3, CS4, CS5, CS6, CS7, EF, VA, unspecified ] aliases: [ target ] + contract_exception_tag: + description: + - The tag for contract exception. + type: str + qos_class: + description: + - The priority class identifier. + type: str + choices: [ level1, level2, level3, level4, level5, level6, unspecified ] + default: level3 + intra_ext_epg_isolation: + description: + - The preferred policy control. + type: str + choices: [ enforced, unenforced ] + route_control_profiles: + description: + - The route control profiles. + type: dict + suboptions: + import_profile: + description: + - The route control profile whose direction is import. + - To remove a route import policy pass an empty string (see Examples). + type: str + aliases: [ import ] + export_profile: + description: + - The route control profile whose direction is export. + - To remove a route export policy pass an empty string (see Examples). + type: str + aliases: [ export ] state: description: - Use C(present) or C(absent) for adding or removing. @@ -88,6 +120,11 @@ EXAMPLES = r""" l3out: prod_l3out name: prod_extepg description: ExtEpg for Production L3Out + contract_exception_tag: test + qos_class: level6 + route_control_profiles: + import_profile: test1 + export_profile: test2 state: present delegate_to: localhost @@ -102,6 +139,20 @@ EXAMPLES = r""" state: absent delegate_to: localhost +- name: Delete the export route control profile in an ExtEpg + cisco.aci.aci_l3out_extepg: + host: apic + username: admin + password: SomeSecretPassword + tenant: production + l3out: prod_l3out + name: prod_extepg + route_control_profiles: + import_profile: test1 + export_profile: "" + state: present + delegate_to: localhost + - name: Query ExtEpg information cisco.aci.aci_l3out_extepg: host: apic @@ -262,6 +313,13 @@ def main(): ], aliases=["target"], ), + contract_exception_tag=dict(type="str"), + qos_class=dict(type="str", default="level3", choices=["level1", "level2", "level3", "level4", "level5", "level6", "unspecified"]), + intra_ext_epg_isolation=dict(type="str", choices=["enforced", "unenforced"]), + route_control_profiles=dict( + type="dict", + options=dict(import_profile=dict(type="str", aliases=["import"]), export_profile=dict(type="str", aliases=["export"])), + ), state=dict(type="str", default="present", choices=["absent", "present", "query"]), name_alias=dict(type="str"), ) @@ -284,6 +342,10 @@ def main(): preferred_group = aci.boolean(module.params.get("preferred_group"), "include", "exclude") dscp = module.params.get("dscp") state = module.params.get("state") + contract_exception_tag = module.params.get("contract_exception_tag") + qos_class = module.params.get("qos_class") + intra_ext_epg_isolation = module.params.get("intra_ext_epg_isolation") + route_control_profiles = module.params.get("route_control_profiles") name_alias = module.params.get("name_alias") aci.construct_url( @@ -305,22 +367,49 @@ def main(): module_object=extepg, target_filter={"name": extepg}, ), + child_classes=["fvRsSecInherited", "l3extRsInstPToProfile"], ) aci.get_existing() if state == "present": - aci.payload( - aci_class="l3extInstP", - class_config=dict( - name=extepg, - descr=description, - prefGrMemb=preferred_group, - targetDscp=dscp, - nameAlias=name_alias, - ), + child_configs = [] + if route_control_profiles: + for key, name in route_control_profiles.items(): + if "_profile" in key: + direction = key.rstrip("_profile") + if name == "" and isinstance(aci.existing, list) and len(aci.existing) > 0: + for child in aci.existing[0].get("l3extInstP", {}).get("children", {}): + if child.get("l3extRsInstPToProfile") and child.get("l3extRsInstPToProfile").get("attributes").get("direction") == direction: + child_configs.append( + dict( + l3extRsInstPToProfile=dict( + attributes=dict( + status="deleted", + direction=direction, + tnRtctrlProfileName=child.get("l3extRsInstPToProfile").get("attributes").get("tnRtctrlProfileName"), + ) + ) + ) + ) + elif name: + child_configs.append(dict(l3extRsInstPToProfile=dict(attributes=dict(direction=direction, tnRtctrlProfileName=name)))) + + class_config = dict( + name=extepg, + descr=description, + prefGrMemb=preferred_group, + targetDscp=dscp, + nameAlias=name_alias, + prio=qos_class, + exceptionTag=contract_exception_tag, ) + if intra_ext_epg_isolation: + class_config.update(pcEnfPref=intra_ext_epg_isolation) + + aci.payload(aci_class="l3extInstP", class_config=class_config, child_configs=child_configs) + aci.get_diff(aci_class="l3extInstP") aci.post_config() diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_l3out_logical_interface_profile.py b/ansible_collections/cisco/aci/plugins/modules/aci_l3out_logical_interface_profile.py index a87cb664c..db5890205 100644 --- a/ansible_collections/cisco/aci/plugins/modules/aci_l3out_logical_interface_profile.py +++ b/ansible_collections/cisco/aci/plugins/modules/aci_l3out_logical_interface_profile.py @@ -40,14 +40,22 @@ options: description: - Name of the neighbor discovery interface policy. type: str + default: "" egress_dpp_policy: description: - Name of the egress data plane policing policy. type: str + default: "" ingress_dpp_policy: description: - Name of the ingress data plane policing policy. type: str + default: "" + description: + description: + - The description for the logical interface profile. + type: str + aliases: [ descr ] state: description: - Use C(present) or C(absent) for adding or removing. @@ -241,6 +249,7 @@ def main(): egress_dpp_policy=dict(type="str", default=""), ingress_dpp_policy=dict(type="str", default=""), state=dict(type="str", default="present", choices=["absent", "present", "query"]), + description=dict(type="str", aliases=["descr"]), ) module = AnsibleModule( @@ -259,6 +268,7 @@ def main(): nd_policy = module.params.get("nd_policy") egress_dpp_policy = module.params.get("egress_dpp_policy") ingress_dpp_policy = module.params.get("ingress_dpp_policy") + description = module.params.get("description") state = module.params.get("state") aci = ACIModule(module) @@ -299,7 +309,7 @@ def main(): dict(l3extRsIngressQosDppPol=dict(attributes=dict(tnQosDppPolName=ingress_dpp_policy))), dict(l3extRsEgressQosDppPol=dict(attributes=dict(tnQosDppPolName=egress_dpp_policy))), ] - aci.payload(aci_class="l3extLIfP", class_config=dict(name=interface_profile), child_configs=child_configs) + aci.payload(aci_class="l3extLIfP", class_config=dict(name=interface_profile, descr=description), child_configs=child_configs) aci.get_diff(aci_class="l3extLIfP") diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_l3out_logical_interface_profile_ospf_policy.py b/ansible_collections/cisco/aci/plugins/modules/aci_l3out_logical_interface_profile_ospf_policy.py index 92361230f..798a82111 100644 --- a/ansible_collections/cisco/aci/plugins/modules/aci_l3out_logical_interface_profile_ospf_policy.py +++ b/ansible_collections/cisco/aci/plugins/modules/aci_l3out_logical_interface_profile_ospf_policy.py @@ -52,7 +52,6 @@ options: - OSPF authentication key. - When using C(ospf_auth_key) this module will always show as C(changed) as the module cannot know what the currently configured key is. type: str - default: "" state: description: - Use C(present) or C(absent) for adding or removing. diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_l3out_logical_node.py b/ansible_collections/cisco/aci/plugins/modules/aci_l3out_logical_node.py index 2bfc876ad..33c8a22a5 100644 --- a/ansible_collections/cisco/aci/plugins/modules/aci_l3out_logical_node.py +++ b/ansible_collections/cisco/aci/plugins/modules/aci_l3out_logical_node.py @@ -49,6 +49,12 @@ options: type: str choices: [ 'yes', 'no' ] default: 'yes' + loopback_address: + description: + - The loopback IP address. + - A configured loopback address can be removed by passing an empty string (see Examples). + type: str + aliases: [ loopback ] state: description: - Use C(present) or C(absent) for adding or removing. @@ -82,9 +88,23 @@ EXAMPLES = r""" pod_id: 1 node_id: 111 router_id: 111.111.111.111 + loopback_address: 111.111.111.112 state: present delegate_to: localhost +- name: Remove a loopback address from a node in node profile + cisco.aci.aci_l3out_logical_node: + host: apic + username: admin + password: SomeSecretPassword + tenant: my_tenant + l3out: my_l3out + node_profile: my_node_profile + pod_id: 1 + node_id: 111 + loopback_address: "" + delegate_to: localhost + - name: Delete a node from a node profile cisco.aci.aci_l3out_logical_node: host: apic @@ -243,6 +263,7 @@ def main(): node_id=dict(type="int"), router_id=dict(type="str"), router_id_as_loopback=dict(type="str", default="yes", choices=["yes", "no"]), + loopback_address=dict(type="str", aliases=["loopback"]), state=dict(type="str", default="present", choices=["absent", "present", "query"]), ) @@ -262,6 +283,7 @@ def main(): node_id = module.params.get("node_id") router_id = module.params.get("router_id") router_id_as_loopback = module.params.get("router_id_as_loopback") + loopback_address = module.params.get("loopback_address") state = module.params.get("state") tdn = None @@ -270,6 +292,10 @@ def main(): aci = ACIModule(module) + child_classes = ["l3extLoopBackIfP"] + + child_configs = [] + aci.construct_url( root_class=dict( aci_class="fvTenant", @@ -295,12 +321,25 @@ def main(): module_object=tdn, target_filter={"name": tdn}, ), + child_classes=child_classes, ) aci.get_existing() if state == "present": - aci.payload(aci_class="l3extRsNodeL3OutAtt", class_config=dict(rtrId=router_id, rtrIdLoopBack=router_id_as_loopback, tDn=tdn)) + if loopback_address is not None: + if loopback_address == "" and isinstance(aci.existing, list) and len(aci.existing) > 0: + for child in aci.existing[0].get("l3extRsNodeL3OutAtt", {}).get("children", []): + previous_loopback_address = child.get("l3extLoopBackIfP", {}).get("attributes", {}).get("addr") + child_configs.append(dict(l3extLoopBackIfP=dict(attributes=dict(addr=previous_loopback_address, status="deleted")))) + elif loopback_address: + child_configs.append(dict(l3extLoopBackIfP=dict(attributes=dict(addr=loopback_address)))) + + aci.payload( + aci_class="l3extRsNodeL3OutAtt", + class_config=dict(rtrId=router_id, rtrIdLoopBack=router_id_as_loopback, tDn=tdn), + child_configs=child_configs, + ) aci.get_diff(aci_class="l3extRsNodeL3OutAtt") diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_l3out_logical_node_profile.py b/ansible_collections/cisco/aci/plugins/modules/aci_l3out_logical_node_profile.py index d3aa5e8f7..10eefd018 100644 --- a/ansible_collections/cisco/aci/plugins/modules/aci_l3out_logical_node_profile.py +++ b/ansible_collections/cisco/aci/plugins/modules/aci_l3out_logical_node_profile.py @@ -12,7 +12,7 @@ ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported DOCUMENTATION = r""" --- module: aci_l3out_logical_node_profile -short_description: Manage Layer 3 Outside (L3Out) logical node profiles (l3extLNodeP:lnodep) +short_description: Manage Layer 3 Outside (L3Out) logical node profiles (l3ext:LNodeP) description: - Manage Layer 3 Outside (L3Out) logical node profiles on Cisco ACI fabrics. options: diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_l3out_static_routes.py b/ansible_collections/cisco/aci/plugins/modules/aci_l3out_static_routes.py index 09a67b8ee..039593366 100644 --- a/ansible_collections/cisco/aci/plugins/modules/aci_l3out_static_routes.py +++ b/ansible_collections/cisco/aci/plugins/modules/aci_l3out_static_routes.py @@ -61,9 +61,9 @@ options: bfd: description: - Determines if bfd is required for route control. - - The APIC defaults to C(None) when unset during creation. + - The APIC defaults to C(unspecified) when unset during creation. type: str - choices: [ bfd, None ] + choices: [ bfd, unspecified ] state: description: - Use C(present) or C(absent) for adding or removing. @@ -264,7 +264,7 @@ def main(): prefix=dict(type="str", aliases=["route"]), track_policy=dict(type="str"), preference=dict(type="int"), - bfd=dict(type="str", choices=["bfd", None]), + bfd=dict(type="str", choices=["bfd", "unspecified"]), description=dict(type="str", aliases=["descr"]), state=dict(type="str", default="present", choices=["absent", "present", "query"]), name_alias=dict(type="str"), @@ -350,7 +350,7 @@ def main(): tDn = "uni/tn-{0}/tracklist-{1}".format(tenant, track_policy) child_configs.append({"ipRsRouteTrack": {"attributes": {"tDn": tDn}}}) - aci.payload(aci_class="ipRouteP", class_config=class_config, child_configs=child_configs), + aci.payload(aci_class="ipRouteP", class_config=class_config, child_configs=child_configs) aci.get_diff(aci_class="ipRouteP") diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_maintenance_group.py b/ansible_collections/cisco/aci/plugins/modules/aci_maintenance_group.py index 98b0df9f2..d4d249e25 100644 --- a/ansible_collections/cisco/aci/plugins/modules/aci_maintenance_group.py +++ b/ansible_collections/cisco/aci/plugins/modules/aci_maintenance_group.py @@ -1,6 +1,7 @@ #!/usr/bin/python # -*- coding: utf-8 -*- +# Copyright: (c) 2023, Gaspard Micol (@gmicol) <gmicol@cisco.com> # GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) from __future__ import absolute_import, division, print_function @@ -9,40 +10,65 @@ __metaclass__ = type ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported_by": "community"} -DOCUMENTATION = """ +DOCUMENTATION = r""" module: aci_maintenance_group -short_description: This creates an ACI maintenance group -notes: - - a maintenance policy (aci_maintenance_policy must be created prior to creating an aci maintenance group +short_description: This creates an ACI maintenance group (maint:MaintGrp) description: - - This modules creates an ACI maintenance group + - This modules creates an ACI maintenance group. options: group: description: - - This is the name of the group + - The name of the maintenance group. type: str policy: description: - - This is the name of the policy that was created using aci_maintenance_policy + - The name of the maintenance policy. + type: str + aliases: [ maintenancepol ] + firmware_nodes_type: + description: + - The firmware type of nodes in the maintenance group. + - The APIC defaults to C(switch) when unset during creation. + type: str + choices: [ c_apic_patch, catalog, config, controller, controller_patch, plugin, plugin_package, switch, switch_patch, vpod ] + type_group: + description: + - The type of the maintenance group. + - The APIC defaults to C(range) when unset during creation. + type: str + choices: [ all, all_in_pod, range ] + description: + description: + - Description of the maintenance group. type: str + aliases: [ descr ] state: description: - - Use C(present) or C(absent) for adding or removing. - - Use C(query) for listing an object or multiple objects. + - Use C(present) or C(absent) for adding or removing. + - Use C(query) for listing an object or multiple objects. type: str choices: [absent, present, query] default: present name_alias: description: - - The alias for the current object. This relates to the nameAlias field in ACI. + - The alias for the current object. This relates to the nameAlias field in ACI. type: str extends_documentation_fragment: - cisco.aci.aci - cisco.aci.annotation - cisco.aci.owner +notes: +- The C(policy) used must exist before using this module in your playbook. +- The M(cisco.aci.aci_maintenance_policy) module can be used for this. +seealso: +- module: cisco.aci.aci_maintenance_policy +- name: APIC Management Information Model reference + description: More information about the internal APIC class B(maint:MaintGrp). + link: https://developer.cisco.com/docs/apic-mim-ref/ author: - Steven Gerhart (@sgerhart) + - Gaspard Micol (@gmicol) """ EXAMPLES = r""" @@ -85,7 +111,7 @@ EXAMPLES = r""" register: query_result """ -RETURN = """ +RETURN = r""" current: description: The existing configuration from the APIC after the module has finished returned: success @@ -191,6 +217,7 @@ url: """ from ansible_collections.cisco.aci.plugins.module_utils.aci import ACIModule, aci_argument_spec, aci_annotation_spec, aci_owner_spec +from ansible_collections.cisco.aci.plugins.module_utils.constants import MATCH_TYPE_GROUP_MAPPING, MATCH_FIRMWARE_NODES_TYPE_MAPPING from ansible.module_utils.basic import AnsibleModule @@ -200,7 +227,10 @@ def main(): argument_spec.update(aci_owner_spec()) argument_spec.update( group=dict(type="str"), # Not required for querying all objects - policy=dict(type="str"), # Not required for querying all objects + policy=dict(type="str", aliases=["maintenancepol"]), # Not required for querying all objects + firmware_nodes_type=dict(type="str", choices=list(MATCH_FIRMWARE_NODES_TYPE_MAPPING.keys())), + type_group=dict(type="str", choices=list(MATCH_TYPE_GROUP_MAPPING.keys())), + description=dict(type="str", aliases=["descr"]), state=dict(type="str", default="present", choices=["absent", "present", "query"]), name_alias=dict(type="str"), ) @@ -217,6 +247,9 @@ def main(): state = module.params.get("state") group = module.params.get("group") policy = module.params.get("policy") + firmware_nodes_type = MATCH_FIRMWARE_NODES_TYPE_MAPPING.get(module.params.get("firmware_nodes_type")) + type_group = MATCH_TYPE_GROUP_MAPPING.get(module.params.get("type_group")) + description = module.params.get("description") name_alias = module.params.get("name_alias") aci = ACIModule(module) aci.construct_url( @@ -236,6 +269,9 @@ def main(): aci_class="maintMaintGrp", class_config=dict( name=group, + fwtype=firmware_nodes_type, + type=type_group, + descr=description, nameAlias=name_alias, ), child_configs=[ diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_maintenance_group_node.py b/ansible_collections/cisco/aci/plugins/modules/aci_maintenance_group_node.py index 272143410..be97ede40 100644 --- a/ansible_collections/cisco/aci/plugins/modules/aci_maintenance_group_node.py +++ b/ansible_collections/cisco/aci/plugins/modules/aci_maintenance_group_node.py @@ -1,5 +1,7 @@ #!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright: (c) 2023, Gaspard Micol (@gmicol) <gmicol@cisco.com> # GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) from __future__ import absolute_import, division, print_function @@ -11,7 +13,7 @@ ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported DOCUMENTATION = r""" --- module: aci_maintenance_group_node -short_description: Manage maintenance group nodes +short_description: Manage maintenance group nodes (fabric:NodeBlk) description: - Manage maintenance group nodes options: @@ -22,7 +24,7 @@ options: node: description: - The node to be added to the maintenance group. - - The value equals the nodeid. + - The value equals the NodeID. type: str state: description: @@ -39,8 +41,14 @@ extends_documentation_fragment: - cisco.aci.aci - cisco.aci.annotation +seealso: +- module: cisco.aci.aci_maintenance_group +- name: APIC Management Information Model reference + description: More information about the internal APIC class B(fabric:NodeBlk). + link: https://developer.cisco.com/docs/apic-mim-ref/ author: - Steven Gerhart (@sgerhart) +- Gaspard Micol (@gmicol) """ EXAMPLES = r""" @@ -218,6 +226,9 @@ def main(): group = module.params.get("group") node = module.params.get("node") name_alias = module.params.get("name_alias") + block_name = None + if node is not None: + block_name = "blk{0}-{0}".format(node) aci = ACIModule(module) aci.construct_url( @@ -230,7 +241,7 @@ def main(): subclass_1=dict( aci_class="fabricNodeBlk", aci_rn="nodeblk-blk{0}-{0}".format(node), - target_filter={"name": "blk{0}-{0}".format(node)}, + target_filter={"name": block_name}, module_object=node, ), ) diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_maintenance_policy.py b/ansible_collections/cisco/aci/plugins/modules/aci_maintenance_policy.py index ddc62df09..987dc2e35 100644 --- a/ansible_collections/cisco/aci/plugins/modules/aci_maintenance_policy.py +++ b/ansible_collections/cisco/aci/plugins/modules/aci_maintenance_policy.py @@ -1,6 +1,7 @@ #!/usr/bin/python # -*- coding: utf-8 -*- +# Copyright: (c) 2023, Gaspard Micol (@gmicol) <gmicol@cisco.com> # GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) from __future__ import absolute_import, division, print_function @@ -12,7 +13,7 @@ ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported DOCUMENTATION = r""" --- module: aci_maintenance_policy -short_description: Manage firmware maintenance policies +short_description: Manage firmware maintenance policies (maint:MaintP) description: - Manage maintenance policies that defines behavior during an ACI upgrade. options: @@ -21,12 +22,13 @@ options: - The name for the maintenance policy. type: str aliases: [ maintenance_policy ] - runmode: + run_mode: description: - Whether the system pauses on error or just continues through it. + - The APIC defaults to C(pauseOnlyOnFailures) when unset during creation. type: str - choices: [ pauseOnlyOnFailures, pauseNever ] - default: pauseOnlyOnFailures + choices: [ pause_always_between_sets, pause_only_on_failures, pause_never, pauseOnlyOnFailures, pauseNever ] + aliases: [ runmode ] graceful: description: - Whether the system will bring down the nodes gracefully during an upgrade, which reduces traffic lost. @@ -36,17 +38,71 @@ options: description: - The name of scheduler that is applied to the policy. type: str - adminst: + admin_state: description: - - Will trigger an immediate upgrade for nodes if adminst is set to triggered. + - The administrative state of the executable policies. + - Will trigger an immediate upgrade for nodes if C(admin_state) is set to triggered. + - The APIC defaults to C(untriggered) when unset during creation. type: str choices: [ triggered, untriggered ] - default: untriggered - ignoreCompat: + aliases: [ adminst ] + download_state: + description: + - The download state of the executable policies. + - The APIC defaults to C(untriggered) when unset during creation. + type: str + choices: [ triggered, untriggered ] + notify_condition: + description: + - Specifies under what pause condition will admin be notified via email/text as configured. + - This notification mechanism is independent of events/faults. + - The APIC defaults to C(notifyOnlyOnFailures) when unset during creation. + type: str + choices: [ notify_always_between_sets, notify_never, notify_only_on_failures ] + smu_operation: + description: + - Specifies SMU operation. + type: str + choices: [ smu_install, smu_uninstall ] + smu_operation_flags: + description: + - Specifies SMU operation flags + - Indicates if node should be reloaded immediately or skip auto reload on SMU Install/Uninstall. + type: str + choices: [ smu_reload_immediate, smu_reload_skip ] + sr_upgrade: + description: + - The SR firware upgrade. + type: bool + sr_version: + description: + - The SR version of the compatibility catalog. + type: str + version: + description: + - The version of the compatibility catalog. + type: str + version_check_override: + description: + - The version check override. + - This is a directive to ignore the version check for the next install. + - The version check, which occurs during a maintenance window, checks to see if the desired version matches the running version. + - If the versions do not match, the install is performed. If the versions do match, the install is not performed. + - The version check override is a one-time override that performs the install whether or not the versions match. + - The APIC defaults to C(untriggered) when unset during creation. + type: str + choices: [ trigger, trigger_immediate, triggered, untriggered ] + ignore_compat: description: - To check whether compatibility checks should be ignored - The APIC defaults to C(false) when unset during creation. type: bool + aliases: [ ignoreCompat ] + description: + description: + - Description for the maintenance policy. + type: str + aliases: [ descr ] state: description: - Use C(present) or C(absent) for adding or removing. @@ -64,8 +120,14 @@ extends_documentation_fragment: notes: - A scheduler is required for this module, which could have been created using the M(cisco.aci.aci_fabric_scheduler) module or via the UI. +seealso: +- module: cisco.aci.aci_fabric_scheduler +- name: APIC Management Information Model reference + description: More information about the internal APIC class B(maint:MaintP). + link: https://developer.cisco.com/docs/apic-mim-ref/ author: - Steven Gerhart (@sgerhart) +- Gaspard Micol (@gmicol) """ EXAMPLES = r""" @@ -216,18 +278,36 @@ url: from ansible_collections.cisco.aci.plugins.module_utils.aci import ACIModule, aci_argument_spec, aci_annotation_spec from ansible.module_utils.basic import AnsibleModule +from ansible_collections.cisco.aci.plugins.module_utils.constants import ( + MATCH_RUN_MODE_MAPPING, + MATCH_NOTIFY_CONDITION_MAPPING, + MATCH_SMU_OPERATION_MAPPING, + MATCH_SMU_OPERATION_FLAGS_MAPPING, + MATCH_TRIGGER_MAPPING, +) def main(): + list_run_mode_choices = list(MATCH_RUN_MODE_MAPPING.keys()) + list_run_mode_choices.extend(["pauseOnlyOnFailures", "pauseNever"]) argument_spec = aci_argument_spec() argument_spec.update(aci_annotation_spec()) argument_spec.update( name=dict(type="str", aliases=["maintenance_policy"]), # Not required for querying all objects - runmode=dict(type="str", default="pauseOnlyOnFailures", choices=["pauseOnlyOnFailures", "pauseNever"]), + run_mode=dict(type="str", choices=list_run_mode_choices, aliases=["runmode"]), graceful=dict(type="bool"), scheduler=dict(type="str"), - ignoreCompat=dict(type="bool"), - adminst=dict(type="str", default="untriggered", choices=["triggered", "untriggered"]), + ignore_compat=dict(type="bool", aliases=["ignoreCompat"]), + admin_state=dict(type="str", choices=list(MATCH_TRIGGER_MAPPING.keys())[2:], aliases=["adminst"]), + download_state=dict(type="str", choices=list(MATCH_TRIGGER_MAPPING.keys())[2:]), + notify_condition=dict(type="str", choices=list(MATCH_NOTIFY_CONDITION_MAPPING.keys())), + smu_operation=dict(type="str", choices=list(MATCH_SMU_OPERATION_MAPPING.keys())), + smu_operation_flags=dict(type="str", choices=list(MATCH_SMU_OPERATION_FLAGS_MAPPING.keys())), + sr_upgrade=dict(type="bool"), + sr_version=dict(type="str"), + version=dict(type="str"), + version_check_override=dict(type="str", choices=list(MATCH_TRIGGER_MAPPING.keys())), + description=dict(type="str", aliases=["descr"]), state=dict(type="str", default="present", choices=["absent", "present", "query"]), name_alias=dict(type="str"), ) @@ -245,13 +325,25 @@ def main(): state = module.params.get("state") name = module.params.get("name") - runmode = module.params.get("runmode") + run_mode = module.params.get("run_mode") + graceful = aci.boolean(module.params.get("graceful"), "yes", "no") scheduler = module.params.get("scheduler") - adminst = module.params.get("adminst") - graceful = aci.boolean(module.params.get("graceful")) - ignoreCompat = aci.boolean(module.params.get("ignoreCompat")) + admin_state = module.params.get("admin_state") + download_state = module.params.get("download_state") + notify_condition = MATCH_NOTIFY_CONDITION_MAPPING.get(module.params.get("notify_condition")) + smu_operation = MATCH_SMU_OPERATION_MAPPING.get(module.params.get("smu_operation")) + smu_operation_flags = MATCH_SMU_OPERATION_FLAGS_MAPPING.get(module.params.get("smu_operation_flags")) + sr_version = module.params.get("sr_version") + sr_upgrade = module.params.get("sr_upgrade") + version = module.params.get("version") + version_check_override = MATCH_TRIGGER_MAPPING.get(module.params.get("version_check_override")) + ignore_compat = aci.boolean(module.params.get("ignore_compat")) + description = module.params.get("description") name_alias = module.params.get("name_alias") + if run_mode not in ["pauseOnlyOnFailures", "pauseNever"]: + run_mode = MATCH_RUN_MODE_MAPPING.get(run_mode) + aci.construct_url( root_class=dict( aci_class="maintMaintP", @@ -269,10 +361,19 @@ def main(): aci_class="maintMaintP", class_config=dict( name=name, - runMode=runmode, + descr=description, + runMode=run_mode, graceful=graceful, - adminSt=adminst, - ignoreCompat=ignoreCompat, + adminSt=admin_state, + downloadSt=download_state, + notifCond=notify_condition, + smuOperation=smu_operation, + smuOperationFlags=smu_operation_flags, + srUpgrade=sr_upgrade, + srVersion=sr_version, + version=version, + versionCheckOverride=version_check_override, + ignoreCompat=ignore_compat, nameAlias=name_alias, ), child_configs=[ diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_match_as_path_regex_term.py b/ansible_collections/cisco/aci/plugins/modules/aci_match_as_path_regex_term.py new file mode 100644 index 000000000..bb87acaa5 --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_match_as_path_regex_term.py @@ -0,0 +1,306 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2023, Gaspard Micol (@gmicol) <gmicol@cisco.com> +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported_by": "certified"} + +DOCUMENTATION = r""" +--- +module: aci_match_as_path_regex_term +short_description: Manage Match Regular Expression AS-Path Term (rtctrl:MatchAsPathRegexTerm) +description: +- Manage Match Term Based on Route Regular Expression AS-Path for Match Rule Profiles on Cisco ACI fabrics. +options: + tenant: + description: + - The name of an existing tenant. + type: str + aliases: [ tenant_name ] + match_rule: + description: + - The name of an exising match rule profile. + type: str + aliases: [ match_rule_name ] + match_as_path_regex_term: + description: + - The name of the match regex AS-Path term. + type: str + aliases: [ name, match_as_path_regex_term_name ] + regex: + description: + - The Regular Expression. + type: str + description: + description: + - The description for the match regex AS-Path term. + type: str + aliases: [ descr ] + state: + description: + - Use C(present) or C(absent) for adding or removing. + - Use C(query) for listing an object or multiple objects. + type: str + choices: [ absent, present, query ] + default: present + name_alias: + description: + - The alias for the current object. This relates to the nameAlias field in ACI. + type: str +extends_documentation_fragment: +- cisco.aci.aci +- cisco.aci.annotation +- cisco.aci.owner + +notes: +- The C(tenant) and the C(match_rule) used must exist before using this module in your playbook. + The M(cisco.aci.aci_tenant) and the M(cisco.aci.aci_match_rule) modules can be used for this. +seealso: +- module: cisco.aci.aci_tenant +- module: cisco.aci.aci_match_rule +- name: APIC Management Information Model reference + description: More information about the internal APIC class B(rtctrl:MatchAsPathRegexTerm). + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Gaspard Micol (@gmicol) +""" + +EXAMPLES = r""" +- name: Create a match with AS-path regex term + cisco.aci.aci_match_as_path_regex_term: + host: apic + username: admin + password: SomeSecretPassword + match_rule: prod_match_rule + match_as_path_regex_term: prod_match_as_path_regex_term + regex: .* + tenant: production + state: present + delegate_to: localhost + +- name: Delete a match with AS-path regex term + cisco.aci.aci_match_as_path_regex_term: + host: apic + username: admin + password: SomeSecretPassword + match_rule: prod_match_rule + tenant: production + match_as_path_regex_term: prod_match_as_path_regex_term + state: absent + delegate_to: localhost + +- name: Query all match with AS-path regex terms + cisco.aci.aci_match_as_path_regex_term: + host: apic + username: admin + password: SomeSecretPassword + state: query + delegate_to: localhost + register: query_result + +- name: Query a specific match with AS-path regex term + cisco.aci.aci_match_as_path_regex_term: + host: apic + username: admin + password: SomeSecretPassword + match_rule: prod_match_rule + tenant: production + match_as_path_regex_term: prod_match_as_path_regex_term + state: query + delegate_to: localhost + register: query_result +""" + +RETURN = r""" +current: + description: The existing configuration from the APIC after the module has finished + returned: success + type: list + sample: + [ + { + "fvTenant": { + "attributes": { + "descr": "Production environment", + "dn": "uni/tn-production", + "name": "production", + "nameAlias": "", + "ownerKey": "", + "ownerauto_continue": "" + } + } + } + ] +error: + description: The error information as returned from the APIC + returned: failure + type: dict + sample: + { + "code": "122", + "text": "unknown managed object class foo" + } +raw: + description: The raw output returned by the APIC REST API (xml or json) + returned: parse error + type: str + sample: '<?xml version="1.0" encoding="UTF-8"?><imdata totalCount="1"><error code="122" text="unknown managed object class foo"/></imdata>' +sent: + description: The actual/minimal configuration pushed to the APIC + returned: info + type: list + sample: + { + "fvTenant": { + "attributes": { + "descr": "Production environment" + } + } + } +previous: + description: The original configuration from the APIC before the module has started + returned: info + type: list + sample: + [ + { + "fvTenant": { + "attributes": { + "descr": "Production", + "dn": "uni/tn-production", + "name": "production", + "nameAlias": "", + "ownerKey": "", + "ownerauto_continue": "" + } + } + } + ] +proposed: + description: The assembled configuration from the user-provided parameters + returned: info + type: dict + sample: + { + "fvTenant": { + "attributes": { + "descr": "Production environment", + "name": "production" + } + } + } +filter_string: + description: The filter string used for the request + returned: failure or debug + type: str + sample: ?rsp-prop-include=config-only +method: + description: The HTTP method used for the request to the APIC + returned: failure or debug + type: str + sample: POST +response: + description: The HTTP response from the APIC + returned: failure or debug + type: str + sample: OK (30 bytes) +status: + description: The HTTP status from the APIC + returned: failure or debug + type: int + sample: 200 +url: + description: The HTTP url used for the request to the APIC + returned: failure or debug + type: str + sample: https://10.11.12.13/api/mo/uni/tn-production.json +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.cisco.aci.plugins.module_utils.aci import ACIModule, aci_argument_spec, aci_annotation_spec, aci_owner_spec + + +def main(): + argument_spec = aci_argument_spec() + argument_spec.update(aci_annotation_spec()) + argument_spec.update(aci_owner_spec()) + argument_spec.update( + tenant=dict(type="str", aliases=["tenant_name"]), # Not required for querying all objects + match_rule=dict(type="str", aliases=["match_rule_name"]), # Not required for querying all objects + match_as_path_regex_term=dict(type="str", aliases=["name", "match_as_path_regex_term_name"]), + regex=dict(type="str"), + description=dict(type="str", aliases=["descr"]), + name_alias=dict(type="str"), + state=dict(type="str", default="present", choices=["present", "absent", "query"]), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_if=[ + ["state", "absent", ["match_as_path_regex_term", "tenant"]], + ["state", "present", ["match_as_path_regex_term", "tenant"]], + ], + ) + + match_as_path_regex_term = module.params.get("match_as_path_regex_term") + description = module.params.get("description") + regex = module.params.get("regex") + state = module.params.get("state") + tenant = module.params.get("tenant") + match_rule = module.params.get("match_rule") + name_alias = module.params.get("name_alias") + + aci = ACIModule(module) + + aci.construct_url( + root_class=dict( + aci_class="fvTenant", + aci_rn="tn-{0}".format(tenant), + module_object=tenant, + target_filter={"name": tenant}, + ), + subclass_1=dict( + aci_class="rtctrlSubjP", + aci_rn="subj-{0}".format(match_rule), + module_object=match_rule, + target_filter={"name": match_rule}, + ), + subclass_2=dict( + aci_class="rtctrlMatchAsPathRegexTerm", + aci_rn="aspathrxtrm-{0}".format(match_as_path_regex_term), + module_object=match_as_path_regex_term, + target_filter={"name": match_as_path_regex_term}, + ), + ) + + aci.get_existing() + + if state == "present": + aci.payload( + aci_class="rtctrlMatchAsPathRegexTerm", + class_config=dict( + name=match_as_path_regex_term, + regex=regex, + descr=description, + nameAlias=name_alias, + ), + ) + + aci.get_diff(aci_class="rtctrlMatchAsPathRegexTerm") + + aci.post_config() + + elif state == "absent": + aci.delete_config() + + aci.exit_json() + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_match_community_factor.py b/ansible_collections/cisco/aci/plugins/modules/aci_match_community_factor.py new file mode 100644 index 000000000..8db4e6e71 --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_match_community_factor.py @@ -0,0 +1,325 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2023, Gaspard Micol (@gmicol) <gmicol@cisco.com> +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported_by": "certified"} + +DOCUMENTATION = r""" +--- +module: aci_match_community_factor +short_description: Manage Match Community Factor (rtctrl:MatchCommFactor) +description: +- Manage Match Community Factors for Match Terms Based on Community on Cisco ACI fabrics. +options: + tenant: + description: + - The name of an existing tenant. + type: str + aliases: [ tenant_name ] + match_rule: + description: + - The name of an exising match rule profile. + type: str + aliases: [ match_rule_name ] + match_community_term: + description: + - The name of an existing match community term. + type: str + aliases: [ match_community_term_name ] + community: + description: + - The match community value. + type: str + scope: + description: + - The item scope. + - If the scope is transitive, this community may be passed between ASs. + - If the scope is non-transitive, this community should be carried only within the local AS. + type: str + choices: [ transitive, non-transitive ] + description: + description: + - The description for the match community factor. + type: str + aliases: [ descr ] + state: + description: + - Use C(present) or C(absent) for adding or removing. + - Use C(query) for listing an object or multiple objects. + type: str + choices: [ absent, present, query ] + default: present + name_alias: + description: + - The alias for the current object. This relates to the nameAlias field in ACI. + type: str +extends_documentation_fragment: +- cisco.aci.aci +- cisco.aci.annotation +- cisco.aci.owner + +notes: +- The C(tenant), the C(match_rule) and the C(match_community_term) used must exist before using this module in your playbook. + The M(cisco.aci.aci_tenant), the M(cisco.aci.aci_match_rule) and M(cisco.aci.aci_match_community_term) modules can be used for this. +seealso: +- module: cisco.aci.aci_tenant +- module: cisco.aci.aci_match_rule +- module: cisco.aci.aci_match_community_term +- name: APIC Management Information Model reference + description: More information about the internal APIC class B(rtctrl:MatchCommFactor). + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Gaspard Micol (@gmicol) +""" + +EXAMPLES = r""" +- name: Create a match match AS-path regex term + cisco.aci.aci_match_community_factor: + host: apic + username: admin + password: SomeSecretPassword + match_rule: prod_match_rule + match_community_term: prod_match_community_term + community: regular:as2-nn2:4:15 + scope: transitive + tenant: production + state: present + delegate_to: localhost + +- name: Delete a match match AS-path regex term + cisco.aci.aci_match_community_factor: + host: apic + username: admin + password: SomeSecretPassword + match_rule: prod_match_rule + tenant: production + match_community_term: prod_match_community_term + community: regular:as2-nn2:4:15 + state: absent + delegate_to: localhost + +- name: Query all match AS-path regex terms + cisco.aci.aci_match_community_factor: + host: apic + username: admin + password: SomeSecretPassword + state: query + delegate_to: localhost + register: query_result + +- name: Query a specific match match AS-path regex term + cisco.aci.aci_match_community_factor: + host: apic + username: admin + password: SomeSecretPassword + match_rule: prod_match_rule + tenant: production + match_community_term: prod_match_community_term + community: regular:as2-nn2:4:15 + state: query + delegate_to: localhost + register: query_result +""" + +RETURN = r""" +current: + description: The existing configuration from the APIC after the module has finished + returned: success + type: list + sample: + [ + { + "fvTenant": { + "attributes": { + "descr": "Production environment", + "dn": "uni/tn-production", + "name": "production", + "nameAlias": "", + "ownerKey": "", + "ownerauto_continue": "" + } + } + } + ] +error: + description: The error information as returned from the APIC + returned: failure + type: dict + sample: + { + "code": "122", + "text": "unknown managed object class foo" + } +raw: + description: The raw output returned by the APIC REST API (xml or json) + returned: parse error + type: str + sample: '<?xml version="1.0" encoding="UTF-8"?><imdata totalCount="1"><error code="122" text="unknown managed object class foo"/></imdata>' +sent: + description: The actual/minimal configuration pushed to the APIC + returned: info + type: list + sample: + { + "fvTenant": { + "attributes": { + "descr": "Production environment" + } + } + } +previous: + description: The original configuration from the APIC before the module has started + returned: info + type: list + sample: + [ + { + "fvTenant": { + "attributes": { + "descr": "Production", + "dn": "uni/tn-production", + "name": "production", + "nameAlias": "", + "ownerKey": "", + "ownerauto_continue": "" + } + } + } + ] +proposed: + description: The assembled configuration from the user-provided parameters + returned: info + type: dict + sample: + { + "fvTenant": { + "attributes": { + "descr": "Production environment", + "name": "production" + } + } + } +filter_string: + description: The filter string used for the request + returned: failure or debug + type: str + sample: ?rsp-prop-include=config-only +method: + description: The HTTP method used for the request to the APIC + returned: failure or debug + type: str + sample: POST +response: + description: The HTTP response from the APIC + returned: failure or debug + type: str + sample: OK (30 bytes) +status: + description: The HTTP status from the APIC + returned: failure or debug + type: int + sample: 200 +url: + description: The HTTP url used for the request to the APIC + returned: failure or debug + type: str + sample: https://10.11.12.13/api/mo/uni/tn-production.json +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.cisco.aci.plugins.module_utils.aci import ACIModule, aci_argument_spec, aci_annotation_spec, aci_owner_spec + + +def main(): + argument_spec = aci_argument_spec() + argument_spec.update(aci_annotation_spec()) + argument_spec.update(aci_owner_spec()) + argument_spec.update( + tenant=dict(type="str", aliases=["tenant_name"]), # Not required for querying all objects + match_rule=dict(type="str", aliases=["match_rule_name"]), # Not required for querying all objects + match_community_term=dict(type="str", aliases=["match_community_term_name"]), # Not required for querying all objects + community=dict(type="str"), + scope=dict(type="str", choices=["transitive", "non-transitive"]), + description=dict(type="str", aliases=["descr"]), + name_alias=dict(type="str"), + state=dict(type="str", default="present", choices=["present", "absent", "query"]), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_if=[ + ["state", "absent", ["community", "tenant", "match_rule", "match_community_term"]], + ["state", "present", ["community", "tenant", "match_rule", "match_community_term"]], + ], + ) + + community = module.params.get("community") + scope = module.params.get("scope") + description = module.params.get("description") + state = module.params.get("state") + tenant = module.params.get("tenant") + match_rule = module.params.get("match_rule") + match_community_term = module.params.get("match_community_term") + name_alias = module.params.get("name_alias") + + aci = ACIModule(module) + + aci.construct_url( + root_class=dict( + aci_class="fvTenant", + aci_rn="tn-{0}".format(tenant), + module_object=tenant, + target_filter={"name": tenant}, + ), + subclass_1=dict( + aci_class="rtctrlSubjP", + aci_rn="subj-{0}".format(match_rule), + module_object=match_rule, + target_filter={"name": match_rule}, + ), + subclass_2=dict( + aci_class="rtctrlMatchCommTerm", + aci_rn="commtrm-{0}".format(match_community_term), + module_object=match_community_term, + target_filter={"name": match_community_term}, + ), + subclass_3=dict( + aci_class="rtctrlMatchCommFactor", + aci_rn="commfct-{0}".format(community), + module_object=community, + target_filter={"community": community}, + ), + ) + + aci.get_existing() + + if state == "present": + aci.payload( + aci_class="rtctrlMatchCommFactor", + class_config=dict( + community=community, + scope=scope, + descr=description, + nameAlias=name_alias, + ), + ) + + aci.get_diff(aci_class="rtctrlMatchCommFactor") + + aci.post_config() + + elif state == "absent": + aci.delete_config() + + aci.exit_json() + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_match_community_regex_term.py b/ansible_collections/cisco/aci/plugins/modules/aci_match_community_regex_term.py new file mode 100644 index 000000000..1b2d0007a --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_match_community_regex_term.py @@ -0,0 +1,317 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2023, Gaspard Micol (@gmicol) <gmicol@cisco.com> +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported_by": "certified"} + +DOCUMENTATION = r""" +--- +module: aci_match_community_regex_term +short_description: Manage Match Regular Expression Community Term (rtctrl:MatchCommRegexTerm) +description: +- Manage Match Terms Based on Route Regular Expression Community for Match Rule Profiles on Cisco ACI fabrics. +options: + tenant: + description: + - The name of an existing tenant. + type: str + aliases: [ tenant_name ] + match_rule: + description: + - The name of an exising math rule profile. + type: str + aliases: [ match_rule_name ] + match_community_regex_term: + description: + - The name of the match regex community term. + type: str + aliases: [ name, match_community_regex_term_name ] + community_type: + description: + - The community type. + type: str + choices: [ extended, regular ] + default: regular + regex: + description: + - The regular expression. + type: str + description: + description: + - The description for the match regex community term. + type: str + aliases: [ descr ] + state: + description: + - Use C(present) or C(absent) for adding or removing. + - Use C(query) for listing an object or multiple objects. + type: str + choices: [ absent, present, query ] + default: present + name_alias: + description: + - The alias for the current object. This relates to the nameAlias field in ACI. + type: str +extends_documentation_fragment: +- cisco.aci.aci +- cisco.aci.annotation +- cisco.aci.owner + +notes: +- The C(tenant) and the C(match_rule) used must exist before using this module in your playbook. + The M(cisco.aci.aci_tenant) and the M(cisco.aci.aci_match_rule) modules can be used for this. +- Only two match community regex terms can exist at the same time, one of each C(community_type). +seealso: +- module: cisco.aci.aci_tenant +- module: cisco.aci.aci_match_rule +- name: APIC Management Information Model reference + description: More information about the internal APIC class B(rtctrl:MatchCommRegexTerm). + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Gaspard Micol (@gmicol) +""" + +EXAMPLES = r""" +- name: Create a match with comunity regex term + cisco.aci.aci_match_community_regex_term: + host: apic + username: admin + password: SomeSecretPassword + match_rule: prod_match_rule + match_community_regex_term: prod_match_community_regex_term + community_type: regular + regex: .* + tenant: production + state: present + delegate_to: localhost + +- name: Delete a match with comunity regex term + cisco.aci.aci_match_community_regex_term: + host: apic + username: admin + password: SomeSecretPassword + match_rule: prod_match_rule + tenant: production + community_type: regular + state: absent + delegate_to: localhost + +- name: Query all match with commmuntiy regex terms + cisco.aci.aci_match_community_regex_term: + host: apic + username: admin + password: SomeSecretPassword + state: query + delegate_to: localhost + register: query_result + +- name: Query a specific match with comunity regex term + cisco.aci.aci_match_community_regex_term: + host: apic + username: admin + password: SomeSecretPassword + match_rule: prod_match_rule + tenant: production + community_type: regular + state: query + delegate_to: localhost + register: query_result +""" + +RETURN = r""" +current: + description: The existing configuration from the APIC after the module has finished + returned: success + type: list + sample: + [ + { + "fvTenant": { + "attributes": { + "descr": "Production environment", + "dn": "uni/tn-production", + "name": "production", + "nameAlias": "", + "ownerKey": "", + "ownerauto_continue": "" + } + } + } + ] +error: + description: The error information as returned from the APIC + returned: failure + type: dict + sample: + { + "code": "122", + "text": "unknown managed object class foo" + } +raw: + description: The raw output returned by the APIC REST API (xml or json) + returned: parse error + type: str + sample: '<?xml version="1.0" encoding="UTF-8"?><imdata totalCount="1"><error code="122" text="unknown managed object class foo"/></imdata>' +sent: + description: The actual/minimal configuration pushed to the APIC + returned: info + type: list + sample: + { + "fvTenant": { + "attributes": { + "descr": "Production environment" + } + } + } +previous: + description: The original configuration from the APIC before the module has started + returned: info + type: list + sample: + [ + { + "fvTenant": { + "attributes": { + "descr": "Production", + "dn": "uni/tn-production", + "name": "production", + "nameAlias": "", + "ownerKey": "", + "ownerauto_continue": "" + } + } + } + ] +proposed: + description: The assembled configuration from the user-provided parameters + returned: info + type: dict + sample: + { + "fvTenant": { + "attributes": { + "descr": "Production environment", + "name": "production" + } + } + } +filter_string: + description: The filter string used for the request + returned: failure or debug + type: str + sample: ?rsp-prop-include=config-only +method: + description: The HTTP method used for the request to the APIC + returned: failure or debug + type: str + sample: POST +response: + description: The HTTP response from the APIC + returned: failure or debug + type: str + sample: OK (30 bytes) +status: + description: The HTTP status from the APIC + returned: failure or debug + type: int + sample: 200 +url: + description: The HTTP url used for the request to the APIC + returned: failure or debug + type: str + sample: https://10.11.12.13/api/mo/uni/tn-production.json +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.cisco.aci.plugins.module_utils.aci import ACIModule, aci_argument_spec, aci_annotation_spec, aci_owner_spec + + +def main(): + argument_spec = aci_argument_spec() + argument_spec.update(aci_annotation_spec()) + argument_spec.update(aci_owner_spec()) + argument_spec.update( + tenant=dict(type="str", aliases=["tenant_name"]), # Not required for querying all objects + match_rule=dict(type="str", aliases=["match_rule_name"]), # Not required for querying all objects + match_community_regex_term=dict(type="str", aliases=["name", "match_community_regex_term_name"]), + community_type=dict(type="str", default="regular", choices=["extended", "regular"]), + regex=dict(type="str"), + description=dict(type="str", aliases=["descr"]), + name_alias=dict(type="str"), + state=dict(type="str", default="present", choices=["present", "absent", "query"]), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_if=[ + ["state", "absent", ["community_type", "tenant", "match_rule"]], + ["state", "present", ["community_type", "tenant", "match_rule"]], + ], + ) + + match_community_regex_term = module.params.get("match_community_regex_term") + description = module.params.get("description") + community_type = module.params.get("community_type") + regex = module.params.get("regex") + state = module.params.get("state") + tenant = module.params.get("tenant") + match_rule = module.params.get("match_rule") + name_alias = module.params.get("name_alias") + + aci = ACIModule(module) + + aci.construct_url( + root_class=dict( + aci_class="fvTenant", + aci_rn="tn-{0}".format(tenant), + module_object=tenant, + target_filter={"name": tenant}, + ), + subclass_1=dict( + aci_class="rtctrlSubjP", + aci_rn="subj-{0}".format(match_rule), + module_object=match_rule, + target_filter={"name": match_rule}, + ), + subclass_2=dict( + aci_class="rtctrlMatchCommRegexTerm", + aci_rn="commrxtrm-{0}".format(community_type), + module_object=community_type, + target_filter={"commType": community_type}, + ), + ) + + aci.get_existing() + + if state == "present": + aci.payload( + aci_class="rtctrlMatchCommRegexTerm", + class_config=dict( + name=match_community_regex_term, + commType=community_type, + regex=regex, + descr=description, + nameAlias=name_alias, + ), + ) + + aci.get_diff(aci_class="rtctrlMatchCommRegexTerm") + + aci.post_config() + + elif state == "absent": + aci.delete_config() + + aci.exit_json() + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_match_community_term.py b/ansible_collections/cisco/aci/plugins/modules/aci_match_community_term.py new file mode 100644 index 000000000..21a3f35db --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_match_community_term.py @@ -0,0 +1,298 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2023, Gaspard Micol (@gmicol) <gmicol@cisco.com> +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported_by": "certified"} + +DOCUMENTATION = r""" +--- +module: aci_match_community_term +short_description: Manage Match Community Term (rtctrl:MatchCommTerm) +description: +- Manage Match Term Based on Community for Match Rule Profiles on Cisco ACI fabrics. +options: + tenant: + description: + - The name of an existing tenant. + type: str + aliases: [ tenant_name ] + match_rule: + description: + - the name of an exising match rule profile. + type: str + aliases: [ match_rule_name ] + match_community_term: + description: + - the name of the Match Community Term. + type: str + aliases: [ name, match_community_term_name ] + description: + description: + - The description for the Match Community Term. + type: str + aliases: [ descr ] + state: + description: + - Use C(present) or C(absent) for adding or removing. + - Use C(query) for listing an object or multiple objects. + type: str + choices: [ absent, present, query ] + default: present + name_alias: + description: + - The alias for the current object. This relates to the nameAlias field in ACI. + type: str +extends_documentation_fragment: +- cisco.aci.aci +- cisco.aci.annotation +- cisco.aci.owner + +notes: +- The C(tenant) and the C(match_rule) used must exist before using this module in your playbook. + The M(cisco.aci.aci_tenant) and the M(cisco.aci.aci_match_rule) modules can be used for this. +seealso: +- module: cisco.aci.aci_tenant +- module: cisco.aci.aci_match_rule +- name: APIC Management Information Model reference + description: More information about the internal APIC class B(rtctrl:MatchCommTerm). + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Gaspard Micol (@gmicol) +""" + +EXAMPLES = r""" +- name: Create a match with community term + cisco.aci.aci_match_community_term: + host: apic + username: admin + password: SomeSecretPassword + match_rule: prod_match_rule + match_community_term: prod_match_community_term + tenant: production + state: present + delegate_to: localhost + +- name: Delete a match with community term + cisco.aci.aci_match_community_term: + host: apic + username: admin + password: SomeSecretPassword + match_rule: prod_match_rule + tenant: production + match_community_term: prod_match_community_term + state: absent + delegate_to: localhost + +- name: Query all with community terms + cisco.aci.aci_match_community_term: + host: apic + username: admin + password: SomeSecretPassword + state: query + delegate_to: localhost + register: query_result + +- name: Query a specific match with community term + cisco.aci.aci_match_community_term: + host: apic + username: admin + password: SomeSecretPassword + match_rule: prod_match_rule + tenant: production + match_community_term: prod_match_community_term + state: query + delegate_to: localhost + register: query_result +""" + +RETURN = r""" +current: + description: The existing configuration from the APIC after the module has finished + returned: success + type: list + sample: + [ + { + "fvTenant": { + "attributes": { + "descr": "Production environment", + "dn": "uni/tn-production", + "name": "production", + "nameAlias": "", + "ownerKey": "", + "ownerauto_continue": "" + } + } + } + ] +error: + description: The error information as returned from the APIC + returned: failure + type: dict + sample: + { + "code": "122", + "text": "unknown managed object class foo" + } +raw: + description: The raw output returned by the APIC REST API (xml or json) + returned: parse error + type: str + sample: '<?xml version="1.0" encoding="UTF-8"?><imdata totalCount="1"><error code="122" text="unknown managed object class foo"/></imdata>' +sent: + description: The actual/minimal configuration pushed to the APIC + returned: info + type: list + sample: + { + "fvTenant": { + "attributes": { + "descr": "Production environment" + } + } + } +previous: + description: The original configuration from the APIC before the module has started + returned: info + type: list + sample: + [ + { + "fvTenant": { + "attributes": { + "descr": "Production", + "dn": "uni/tn-production", + "name": "production", + "nameAlias": "", + "ownerKey": "", + "ownerauto_continue": "" + } + } + } + ] +proposed: + description: The assembled configuration from the user-provided parameters + returned: info + type: dict + sample: + { + "fvTenant": { + "attributes": { + "descr": "Production environment", + "name": "production" + } + } + } +filter_string: + description: The filter string used for the request + returned: failure or debug + type: str + sample: ?rsp-prop-include=config-only +method: + description: The HTTP method used for the request to the APIC + returned: failure or debug + type: str + sample: POST +response: + description: The HTTP response from the APIC + returned: failure or debug + type: str + sample: OK (30 bytes) +status: + description: The HTTP status from the APIC + returned: failure or debug + type: int + sample: 200 +url: + description: The HTTP url used for the request to the APIC + returned: failure or debug + type: str + sample: https://10.11.12.13/api/mo/uni/tn-production.json +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.cisco.aci.plugins.module_utils.aci import ACIModule, aci_argument_spec, aci_annotation_spec, aci_owner_spec + + +def main(): + argument_spec = aci_argument_spec() + argument_spec.update(aci_annotation_spec()) + argument_spec.update(aci_owner_spec()) + argument_spec.update( + tenant=dict(type="str", aliases=["tenant_name"]), # Not required for querying all objects + match_rule=dict(type="str", aliases=["match_rule_name"]), # Not required for querying all objects + match_community_term=dict(type="str", aliases=["name", "match_community_term_name"]), + description=dict(type="str", aliases=["descr"]), + name_alias=dict(type="str"), + state=dict(type="str", default="present", choices=["present", "absent", "query"]), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_if=[ + ["state", "absent", ["match_community_term", "tenant", "match_rule"]], + ["state", "present", ["match_community_term", "tenant", "match_rule"]], + ], + ) + + match_community_term = module.params.get("match_community_term") + description = module.params.get("description") + state = module.params.get("state") + tenant = module.params.get("tenant") + match_rule = module.params.get("match_rule") + name_alias = module.params.get("name_alias") + + aci = ACIModule(module) + + aci.construct_url( + root_class=dict( + aci_class="fvTenant", + aci_rn="tn-{0}".format(tenant), + module_object=tenant, + target_filter={"name": tenant}, + ), + subclass_1=dict( + aci_class="rtctrlSubjP", + aci_rn="subj-{0}".format(match_rule), + module_object=match_rule, + target_filter={"name": match_rule}, + ), + subclass_2=dict( + aci_class="rtctrlMatchCommTerm", + aci_rn="commtrm-{0}".format(match_community_term), + module_object=match_community_term, + target_filter={"name": match_community_term}, + ), + ) + + aci.get_existing() + + if state == "present": + aci.payload( + aci_class="rtctrlMatchCommTerm", + class_config=dict( + name=match_community_term, + descr=description, + nameAlias=name_alias, + ), + ) + + aci.get_diff(aci_class="rtctrlMatchCommTerm") + + aci.post_config() + + elif state == "absent": + aci.delete_config() + + aci.exit_json() + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_match_route_destination.py b/ansible_collections/cisco/aci/plugins/modules/aci_match_route_destination.py new file mode 100644 index 000000000..973d70561 --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_match_route_destination.py @@ -0,0 +1,325 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2023, Gaspard Micol (@gmicol) <gmicol@cisco.com> +# Copyright: (c) 2023, Tim Cragg (@timcragg) <tcragg@cisco.com> +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported_by": "certified"} + +DOCUMENTATION = r""" +--- +module: aci_match_route_destination +short_description: Manage Match action rule term based on the Route Destination. (rtctrl:MatchRtDest) +description: +- Match action rule terms based on the Route Destination for Subject Profiles on Cisco ACI fabrics. +options: + tenant: + description: + - The name of an existing tenant. + type: str + aliases: [ tenant_name ] + match_rule: + description: + - The name of an exising match rule profile. + type: str + aliases: [ match_rule_name ] + ip: + description: + - The IP address of the route destination. + type: str + aggregate: + description: + - Option to enable/disable aggregated route. + type: str + choices: [ "no", "yes" ] + from_prefix_length: + description: + - The start of the prefix length. + - It corresponds to the lesser Mask if the route is aggregated. + type: int + to_prefix_length: + description: + - The end of the prefix length. + - It corresponds to greater Mask if the route is aggregated. + type: int + description: + description: + - The description for the match action rule term. + type: str + aliases: [ descr ] + state: + description: + - Use C(present) or C(absent) for adding or removing. + - Use C(query) for listing an object or multiple objects. + type: str + choices: [ absent, present, query ] + default: present + name_alias: + description: + - The alias for the current object. This relates to the nameAlias field in ACI. + type: str +extends_documentation_fragment: +- cisco.aci.aci +- cisco.aci.annotation +- cisco.aci.owner + +notes: +- The C(tenant) and the C(match_rule) used must exist before using this module in your playbook. + The M(cisco.aci.aci_tenant) and the M(cisco.aci.aci_match_rule) modules can be used for this. +seealso: +- module: cisco.aci.aci_tenant +- module: cisco.aci.aci_match_rule +- name: APIC Management Information Model reference + description: More information about the internal APIC class B(rtctrl:MatchRtDest). + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Gaspard Micol (@gmicol) +- Tim Cragg (@timcragg) +""" + +EXAMPLES = r""" +- name: Create a match rule destination + cisco.aci.aci_match_rule_destination: + host: apic + username: admin + password: SomeSecretPassword + match_rule: prod_match_rule + tenant: production + ip: 11.11.11.11/24 + aggregate: "yes" + from_prefix_length: 0 + to_prefix_length: 32 + state: present + delegate_to: localhost + +- name: Delete a match rule destination + cisco.aci.aci_match_rule_destination: + host: apic + username: admin + password: SomeSecretPassword + match_rule: prod_match_rule + tenant: production + state: absent + delegate_to: localhost + +- name: Query all match rules destination + cisco.aci.aci_match_rule_destination: + host: apic + username: admin + password: SomeSecretPassword + state: query + delegate_to: localhost + register: query_result + +- name: Query a specific match rule destination + cisco.aci.aci_match_rule_destination: + host: apic + username: admin + password: SomeSecretPassword + match_rule: prod_match_rule + tenant: production + ip: 11.11.11.11/24 + state: query + delegate_to: localhost + register: query_result +""" + +RETURN = r""" +current: + description: The existing configuration from the APIC after the module has finished + returned: success + type: list + sample: + [ + { + "fvTenant": { + "attributes": { + "descr": "Production environment", + "dn": "uni/tn-production", + "name": "production", + "nameAlias": "", + "ownerKey": "", + "ownerauto_continue": "" + } + } + } + ] +error: + description: The error information as returned from the APIC + returned: failure + type: dict + sample: + { + "code": "122", + "text": "unknown managed object class foo" + } +raw: + description: The raw output returned by the APIC REST API (xml or json) + returned: parse error + type: str + sample: '<?xml version="1.0" encoding="UTF-8"?><imdata totalCount="1"><error code="122" text="unknown managed object class foo"/></imdata>' +sent: + description: The actual/minimal configuration pushed to the APIC + returned: info + type: list + sample: + { + "fvTenant": { + "attributes": { + "descr": "Production environment" + } + } + } +previous: + description: The original configuration from the APIC before the module has started + returned: info + type: list + sample: + [ + { + "fvTenant": { + "attributes": { + "descr": "Production", + "dn": "uni/tn-production", + "name": "production", + "nameAlias": "", + "ownerKey": "", + "ownerauto_continue": "" + } + } + } + ] +proposed: + description: The assembled configuration from the user-provided parameters + returned: info + type: dict + sample: + { + "fvTenant": { + "attributes": { + "descr": "Production environment", + "name": "production" + } + } + } +filter_string: + description: The filter string used for the request + returned: failure or debug + type: str + sample: ?rsp-prop-include=config-only +method: + description: The HTTP method used for the request to the APIC + returned: failure or debug + type: str + sample: POST +response: + description: The HTTP response from the APIC + returned: failure or debug + type: str + sample: OK (30 bytes) +status: + description: The HTTP status from the APIC + returned: failure or debug + type: int + sample: 200 +url: + description: The HTTP url used for the request to the APIC + returned: failure or debug + type: str + sample: https://10.11.12.13/api/mo/uni/tn-production.json +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.cisco.aci.plugins.module_utils.aci import ACIModule, aci_argument_spec, aci_annotation_spec, aci_owner_spec + + +def main(): + argument_spec = aci_argument_spec() + argument_spec.update(aci_annotation_spec()) + argument_spec.update(aci_owner_spec()) + argument_spec.update( + tenant=dict(type="str", aliases=["tenant_name"]), # Not required for querying all objects + match_rule=dict(type="str", aliases=["match_rule_name"]), # Not required for querying all objects + ip=dict(type="str"), + aggregate=dict(type="str", choices=["no", "yes"]), + from_prefix_length=dict(type="int"), + to_prefix_length=dict(type="int"), + description=dict(type="str", aliases=["descr"]), + name_alias=dict(type="str"), + state=dict(type="str", default="present", choices=["present", "absent", "query"]), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_if=[ + ["state", "absent", ["ip", "tenant", "match_rule"]], + ["state", "present", ["ip", "tenant", "match_rule"]], + ], + ) + + ip = module.params.get("ip") + description = module.params.get("description") + aggregate = module.params.get("aggregate") + from_prefix_length = module.params.get("from_prefix_length") + to_prefix_length = module.params.get("to_prefix_length") + state = module.params.get("state") + tenant = module.params.get("tenant") + match_rule = module.params.get("match_rule") + name_alias = module.params.get("name_alias") + + aci = ACIModule(module) + + aci.construct_url( + root_class=dict( + aci_class="fvTenant", + aci_rn="tn-{0}".format(tenant), + module_object=tenant, + target_filter={"name": tenant}, + ), + subclass_1=dict( + aci_class="rtctrlSubjP", + aci_rn="subj-{0}".format(match_rule), + module_object=match_rule, + target_filter={"name": match_rule}, + ), + subclass_2=dict( + aci_class="rtctrlMatchRtDest", + aci_rn="dest-[{0}]".format(ip), + module_object=ip, + target_filter={"ip": ip}, + ), + ) + + aci.get_existing() + + if state == "present": + aci.payload( + aci_class="rtctrlMatchRtDest", + class_config=dict( + ip=ip, + aggregate=aggregate, + fromPfxLen=from_prefix_length, + toPfxLen=to_prefix_length, + descr=description, + nameAlias=name_alias, + ), + ) + + aci.get_diff(aci_class="rtctrlMatchRtDest") + + aci.post_config() + + elif state == "absent": + aci.delete_config() + + aci.exit_json() + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_match_rule.py b/ansible_collections/cisco/aci/plugins/modules/aci_match_rule.py new file mode 100644 index 000000000..c412d2458 --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_match_rule.py @@ -0,0 +1,283 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2023, Gaspard Micol (@gmicol) <gmicol@cisco.com> +# Copyright: (c) 2023, Tim Cragg (@timcragg) <tcragg@cisco.com> +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported_by": "certified"} + +DOCUMENTATION = r""" +--- +module: aci_match_rule +short_description: Manage Match Rule (rtcrtl:SubjP) +description: +- Manage Match Rule Profiles for the Tenants on Cisco ACI fabrics. +options: + tenant: + description: + - The name of an existing tenant. + type: str + aliases: [ tenant_name ] + match_rule: + description: + - The name of the match rule profile being created. + type: str + aliases: [ name, match_rule_name ] + description: + description: + - The description for the match rule profile. + type: str + aliases: [ descr ] + state: + description: + - Use C(present) or C(absent) for adding or removing. + - Use C(query) for listing an object or multiple objects. + type: str + choices: [ absent, present, query ] + default: present + name_alias: + description: + - The alias for the current object. This relates to the nameAlias field in ACI. + type: str +extends_documentation_fragment: +- cisco.aci.aci +- cisco.aci.annotation +- cisco.aci.owner + +notes: +- The C(tenant) used must exist before using this module in your playbook. + The M(cisco.aci.aci_tenant) module can be used for this. +seealso: +- module: cisco.aci.aci_tenant +- name: APIC Management Information Model reference + description: More information about the internal APIC class B(rtctrl:SubjP). + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Gaspard Micol (@gmicol) +- Tim Cragg (@timcragg) +""" + +EXAMPLES = r""" +- name: Create a match rule + cisco.aci.aci_match_rule: + host: apic + username: admin + password: SomeSecretPassword + match_rule: prod_match_rule + tenant: production + state: present + delegate_to: localhost + +- name: Delete a match rule + cisco.aci.aci_match_rule: + host: apic + username: admin + password: SomeSecretPassword + match_rule: prod_match_rule + tenant: production + state: absent + delegate_to: localhost + +- name: Query all match rules + cisco.aci.aci_match_rule: + host: apic + username: admin + password: SomeSecretPassword + state: query + delegate_to: localhost + register: query_result + +- name: Query a specific match rule + cisco.aci.aci_match_rule: + host: apic + username: admin + password: SomeSecretPassword + match_rule: prod_match_rule + tenant: production + state: query + delegate_to: localhost + register: query_result +""" + +RETURN = r""" +current: + description: The existing configuration from the APIC after the module has finished + returned: success + type: list + sample: + [ + { + "fvTenant": { + "attributes": { + "descr": "Production environment", + "dn": "uni/tn-production", + "name": "production", + "nameAlias": "", + "ownerKey": "", + "ownerauto_continue": "" + } + } + } + ] +error: + description: The error information as returned from the APIC + returned: failure + type: dict + sample: + { + "code": "122", + "text": "unknown managed object class foo" + } +raw: + description: The raw output returned by the APIC REST API (xml or json) + returned: parse error + type: str + sample: '<?xml version="1.0" encoding="UTF-8"?><imdata totalCount="1"><error code="122" text="unknown managed object class foo"/></imdata>' +sent: + description: The actual/minimal configuration pushed to the APIC + returned: info + type: list + sample: + { + "fvTenant": { + "attributes": { + "descr": "Production environment" + } + } + } +previous: + description: The original configuration from the APIC before the module has started + returned: info + type: list + sample: + [ + { + "fvTenant": { + "attributes": { + "descr": "Production", + "dn": "uni/tn-production", + "name": "production", + "nameAlias": "", + "ownerKey": "", + "ownerauto_continue": "" + } + } + } + ] +proposed: + description: The assembled configuration from the user-provided parameters + returned: info + type: dict + sample: + { + "fvTenant": { + "attributes": { + "descr": "Production environment", + "name": "production" + } + } + } +filter_string: + description: The filter string used for the request + returned: failure or debug + type: str + sample: ?rsp-prop-include=config-only +method: + description: The HTTP method used for the request to the APIC + returned: failure or debug + type: str + sample: POST +response: + description: The HTTP response from the APIC + returned: failure or debug + type: str + sample: OK (30 bytes) +status: + description: The HTTP status from the APIC + returned: failure or debug + type: int + sample: 200 +url: + description: The HTTP url used for the request to the APIC + returned: failure or debug + type: str + sample: https://10.11.12.13/api/mo/uni/tn-production.json +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.cisco.aci.plugins.module_utils.aci import ACIModule, aci_argument_spec, aci_annotation_spec, aci_owner_spec + + +def main(): + argument_spec = aci_argument_spec() + argument_spec.update(aci_annotation_spec()) + argument_spec.update(aci_owner_spec()) + argument_spec.update( + tenant=dict(type="str", aliases=["tenant_name"]), # Not required for querying all objects + match_rule=dict(type="str", aliases=["name", "match_rule_name"]), # Not required for querying all objects + description=dict(type="str", aliases=["descr"]), + name_alias=dict(type="str"), + state=dict(type="str", default="present", choices=["present", "absent", "query"]), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_if=[ + ["state", "absent", ["match_rule", "tenant"]], + ["state", "present", ["match_rule", "tenant"]], + ], + ) + + match_rule = module.params.get("match_rule") + description = module.params.get("description") + state = module.params.get("state") + tenant = module.params.get("tenant") + name_alias = module.params.get("name_alias") + + aci = ACIModule(module) + + aci.construct_url( + root_class=dict( + aci_class="fvTenant", + aci_rn="tn-{0}".format(tenant), + module_object=tenant, + target_filter={"name": tenant}, + ), + subclass_1=dict( + aci_class="rtctrlSubjP", + aci_rn="subj-{0}".format(match_rule), + module_object=match_rule, + target_filter={"name": match_rule}, + ), + ) + + aci.get_existing() + + if state == "present": + aci.payload( + aci_class="rtctrlSubjP", + class_config=dict( + name=match_rule, + descr=description, + nameAlias=name_alias, + ), + ) + + aci.get_diff(aci_class="rtctrlSubjP") + + aci.post_config() + + elif state == "absent": + aci.delete_config() + + aci.exit_json() + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_rest.py b/ansible_collections/cisco/aci/plugins/modules/aci_rest.py index 67428e4f9..ad73cb5bd 100644 --- a/ansible_collections/cisco/aci/plugins/modules/aci_rest.py +++ b/ansible_collections/cisco/aci/plugins/modules/aci_rest.py @@ -55,6 +55,11 @@ options: together with the C(template) lookup plugin, or use C(template). type: path aliases: [ config_file ] + rsp_subtree_preserve: + description: + - Preserve the response for the provided path. + type: bool + default: false extends_documentation_fragment: - cisco.aci.aci @@ -245,6 +250,7 @@ url: import json import os +import re try: from ansible.module_utils.six.moves.urllib.parse import parse_qsl, urlencode, urlparse, urlunparse @@ -279,7 +285,6 @@ except Exception: from ansible.module_utils.basic import AnsibleModule from ansible_collections.cisco.aci.plugins.module_utils.aci import ACIModule, aci_argument_spec -from ansible.module_utils.urls import fetch_url from ansible.module_utils._text import to_text @@ -335,16 +340,19 @@ def main(): method=dict(type="str", default="get", choices=["delete", "get", "post"], aliases=["action"]), src=dict(type="path", aliases=["config_file"]), content=dict(type="raw"), + rsp_subtree_preserve=dict(type="bool", default=False), ) module = AnsibleModule( argument_spec=argument_spec, + supports_check_mode=True, mutually_exclusive=[["content", "src"]], ) content = module.params.get("content") path = module.params.get("path") src = module.params.get("src") + rsp_subtree_preserve = module.params.get("rsp_subtree_preserve") # Report missing file file_exists = False @@ -352,7 +360,7 @@ def main(): if os.path.isfile(src): file_exists = True else: - module.fail_json(msg="Cannot find/access src '%s'" % src) + module.fail_json(msg="Cannot find/access src '{0}'".format(src)) # Find request type if path.find(".xml") != -1: @@ -386,7 +394,7 @@ def main(): # Validate YAML/JSON string payload = json.dumps(yaml.safe_load(payload)) except Exception as e: - module.fail_json(msg="Failed to parse provided JSON/YAML payload: %s" % to_text(e), exception=to_text(e), payload=payload) + module.fail_json(msg="Failed to parse provided JSON/YAML payload: {0}".format(to_text(e)), exception=to_text(e), payload=payload) elif rest_type == "xml" and HAS_LXML_ETREE: if content and isinstance(content, dict) and HAS_XMLJSON_COBRA: # Validate inline YAML/JSON @@ -396,52 +404,62 @@ def main(): # Validate XML string payload = etree.tostring(etree.fromstring(payload), encoding="unicode") except Exception as e: - module.fail_json(msg="Failed to parse provided XML payload: %s" % to_text(e), payload=payload) + module.fail_json(msg="Failed to parse provided XML payload: {0}".format(to_text(e)), payload=payload) # Perform actual request using auth cookie (Same as aci.request(), but also supports XML) - if "port" in aci.params and aci.params.get("port") is not None: - aci.url = "%(protocol)s://%(host)s:%(port)s/" % aci.params + path.lstrip("/") - else: - aci.url = "%(protocol)s://%(host)s/" % aci.params + path.lstrip("/") - if aci.params.get("method") != "get": - path += "?rsp-subtree=modified" + # NOTE By setting aci.path we ensure that Ansible displays accurate URL info when the plugin and the aci_rest module are used. + aci.path = path.lstrip("/") + aci.url = "{0}/{1}".format(aci.base_url, aci.path) + if aci.params.get("method") != "get" and not rsp_subtree_preserve: + aci.path = "{0}?rsp-subtree=modified".format(aci.path) aci.url = update_qsl(aci.url, {"rsp-subtree": "modified"}) - # Sign and encode request as to APIC's wishes - if aci.params.get("private_key") is not None: - aci.cert_auth(path=path, payload=payload) - - aci.method = aci.params.get("method").upper() - + method = aci.params.get("method").upper() # Perform request - resp, info = fetch_url( - module, aci.url, data=payload, headers=aci.headers, method=aci.method, timeout=aci.params.get("timeout"), use_proxy=aci.params.get("use_proxy") - ) - - aci.response = info.get("msg") - aci.status = info.get("status") + if not aci.module.check_mode: + resp, info = aci.api_call(method, aci.url, data=payload, return_response=True) + # Report failure + if info.get("status") != 200: + try: + # APIC error + aci.response_type(info["body"], rest_type) + aci.fail_json(msg="APIC Error {code}: {text}".format_map(aci.error)) + except KeyError: + # Connection error + aci.fail_json(msg="Connection failed for {url}. {msg}".format_map(info)) - # Report failure - if info.get("status") != 200: try: - # APIC error + aci.response_type(resp.read(), rest_type) + except AttributeError: aci.response_type(info.get("body"), rest_type) - aci.fail_json(msg="APIC Error %(code)s: %(text)s" % aci.error) - except KeyError: - # Connection error - aci.fail_json(msg="Connection failed for %(url)s. %(msg)s" % info) - aci.response_type(resp.read(), rest_type) + aci.result["status"] = aci.status + aci.result["imdata"] = aci.imdata + aci.result["totalCount"] = aci.totalCount + + else: + # NOTE A case when aci_rest is used with check mode and the apic host is used directly from the inventory + if aci.connection is not None and aci.params.get("host") is None: + aci.url = urlunparse(urlparse(aci.url)._replace(netloc=re.sub(r"[[\]]", "", aci.connection.get_option("host")).split(",")[0])) + aci.method = method + # Set changed to true so check_mode changed result is behaving similar to non aci_rest modules + aci.result["changed"] = True + + # Only set proposed if we have a payload and thus also only allow output_path if we have a payload + # DELETE and GET do not have a payload + if payload and method == "POST": + if rest_type == "json": + payload = json.loads(payload) - aci.result["status"] = aci.status - aci.result["imdata"] = aci.imdata - aci.result["totalCount"] = aci.totalCount + aci.result["proposed"] = payload - if aci.params.get("method") != "get": output_path = aci.params.get("output_path") if output_path is not None: with open(output_path, "a") as output_file: - output_file.write(str(payload)) + if rest_type == "json": + json.dump([payload], output_file) + else: + output_file.write(str(payload)) # Report success aci.exit_json(**aci.result) diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_route_control_context.py b/ansible_collections/cisco/aci/plugins/modules/aci_route_control_context.py new file mode 100644 index 000000000..19993767a --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_route_control_context.py @@ -0,0 +1,393 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2023, Gaspard Micol (@gmicol) <gmicol@cisco.com> +# Copyright: (c) 2023, Tim Cragg (@timcragg) <tcragg@cisco.com> +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported_by": "certified"} + +DOCUMENTATION = r""" +--- +module: aci_route_control_context +short_description: Manage Route Control Context (rtcrtl:CtxP) +description: +- Manage Route Control Context Policies for the Route Control Profiles on Cisco ACI fabrics. +options: + tenant: + description: + - The name of an existing tenant. + type: str + aliases: [ tenant_name ] + l3out: + description: + - The name of an existing L3Out. + - To use only if the route control profile is linked to an existing L3Out. + type: str + aliases: [ l3out_name ] + route_control_profile: + description: + - The name of an existing route control profile. + type: str + aliases: [ route_control_profile_name ] + route_control_context: + description: + - The name of the route control context being created. + type: str + aliases: [ name, route_control_context_name, context ] + action: + description: + - The action required when the condition is met. + type: str + choices: [ deny, permit ] + action_rule: + description: + - The name of the action rule profile to be associated with this route control context. + - Set the rules for a Route Map. + type: str + aliases: [ action_rule_name ] + match_rule: + description: + - The name of the match rule profile to be associated with this route control context. + - Set the associated Matched rules. + type: str + aliases: [ match_rule_name ] + order: + description: + - The order of the route control context. + - The value range from 0 to 9. + type: int + description: + description: + - The description for the route control context. + type: str + aliases: [ descr ] + state: + description: + - Use C(present) or C(absent) for adding or removing. + - Use C(query) for listing an object or multiple objects. + type: str + choices: [ absent, present, query ] + default: present + name_alias: + description: + - The alias for the current object. This relates to the nameAlias field in ACI. + type: str +extends_documentation_fragment: +- cisco.aci.aci +- cisco.aci.annotation +- cisco.aci.owner + +notes: +- The C(tenant) and the C(route_control_profile) used must exist before using this module in your playbook. + The M(cisco.aci.aci_tenant) and the M(cisco.aci.aci_route_control_profile) modules can be used for this. +- If C(l3out) is used, the C(l3out) used must exist before using this module in your playbook. + The M(cisco.aci.aci_l3out) module can be used for this. +- if C(action_rule) is used, the C(action_rule) used must exist before using this module in your plabook. + The module M(cisco.aci.aci_tenant_action_rule_profile) can be used for this. +- if C(match_rule) is used, the C(match_rule) used must exist before using this module in your plabook. + The module M(cisco.aci.aci_match_rule) can be used for this. +seealso: +- module: cisco.aci.aci_tenant +- module: cisco.aci.aci_route_control_profile +- module: cisco.aci.aci_l3out +- module: cisco.aci.aci_tenant_action_rule_profile +- module: cisco.aci.aci_match_rule +- name: APIC Management Information Model reference + description: More information about the internal APIC class B(rtctrl:CtxP). + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Gaspard Micol (@gmicol) +- Tim Cragg (@timcragg) +""" + +EXAMPLES = r""" +- name: Create a route context policy + cisco.aci.aci_route_control_context: + host: apic + username: admin + password: SomeSecretPassword + route_control_context: prod_route_control_context + route_control_profile: prod_route_control_profile + tenant: production + l3out: prod_l3out + action: permit + order: 0 + action_rule: prod_action_rule_profile + match_rule: prod_match_rule + state: present + delegate_to: localhost + +- name: Delete a route context policy + cisco.aci.aci_route_control_context: + host: apic + username: admin + password: SomeSecretPassword + route_control_context: prod_route_control_context + route_control_profile: prod_route_control_profile + tenant: production + l3out: prod_l3out + state: absent + delegate_to: localhost + +- name: Query all route context policy + cisco.aci.aci_route_control_context: + host: apic + username: admin + password: SomeSecretPassword + state: query + delegate_to: localhost + register: query_result + +- name: Query a specific route context policy + cisco.aci.aci_route_control_profile: + host: apic + username: admin + password: SomeSecretPassword + route_control_context: prod_route_control_context + route_control_profile: prod_route_control_profile + tenant: production + l3out: prod_l3out + state: query + delegate_to: localhost + register: query_result +""" + +RETURN = r""" +current: + description: The existing configuration from the APIC after the module has finished + returned: success + type: list + sample: + [ + { + "fvTenant": { + "attributes": { + "descr": "Production environment", + "dn": "uni/tn-production", + "name": "production", + "nameAlias": "", + "ownerKey": "", + "ownerauto_continue": "" + } + } + } + ] +error: + description: The error information as returned from the APIC + returned: failure + type: dict + sample: + { + "code": "122", + "text": "unknown managed object class foo" + } +raw: + description: The raw output returned by the APIC REST API (xml or json) + returned: parse error + type: str + sample: '<?xml version="1.0" encoding="UTF-8"?><imdata totalCount="1"><error code="122" text="unknown managed object class foo"/></imdata>' +sent: + description: The actual/minimal configuration pushed to the APIC + returned: info + type: list + sample: + { + "fvTenant": { + "attributes": { + "descr": "Production environment" + } + } + } +previous: + description: The original configuration from the APIC before the module has started + returned: info + type: list + sample: + [ + { + "fvTenant": { + "attributes": { + "descr": "Production", + "dn": "uni/tn-production", + "name": "production", + "nameAlias": "", + "ownerKey": "", + "ownerauto_continue": "" + } + } + } + ] +proposed: + description: The assembled configuration from the user-provided parameters + returned: info + type: dict + sample: + { + "fvTenant": { + "attributes": { + "descr": "Production environment", + "name": "production" + } + } + } +filter_string: + description: The filter string used for the request + returned: failure or debug + type: str + sample: ?rsp-prop-include=config-only +method: + description: The HTTP method used for the request to the APIC + returned: failure or debug + type: str + sample: POST +response: + description: The HTTP response from the APIC + returned: failure or debug + type: str + sample: OK (30 bytes) +status: + description: The HTTP status from the APIC + returned: failure or debug + type: int + sample: 200 +url: + description: The HTTP url used for the request to the APIC + returned: failure or debug + type: str + sample: https://10.11.12.13/api/mo/uni/tn-production.json +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.cisco.aci.plugins.module_utils.aci import ACIModule, aci_argument_spec, aci_annotation_spec, aci_owner_spec + + +def main(): + argument_spec = aci_argument_spec() + argument_spec.update(aci_annotation_spec()) + argument_spec.update(aci_owner_spec()) + argument_spec.update( + tenant=dict(type="str", aliases=["tenant_name"]), # Not required for querying all objects + l3out=dict(type="str", aliases=["l3out_name"]), # Not required for querying all objects + route_control_profile=dict(type="str", aliases=["route_control_profile_name"]), # Not required for querying all objects + route_control_context=dict(type="str", aliases=["name", "route_control_context_name", "context"]), # Not required for querying all objects + match_rule=dict(type="str", aliases=["match_rule_name"]), + action_rule=dict(type="str", aliases=["action_rule_name"]), + action=dict(type="str", choices=["deny", "permit"]), + order=dict(type="int"), + description=dict(type="str", aliases=["descr"]), + name_alias=dict(type="str"), + state=dict(type="str", default="present", choices=["present", "absent", "query"]), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_if=[ + ["state", "absent", ["route_control_context", "tenant"]], + ["state", "present", ["route_control_context", "tenant"]], + ], + ) + + route_control_context = module.params.get("route_control_context") + description = module.params.get("description") + action = module.params.get("action") + order = module.params.get("order") + state = module.params.get("state") + tenant = module.params.get("tenant") + l3out = module.params.get("l3out") + route_control_profile = module.params.get("route_control_profile") + match_rule = module.params.get("match_rule") + action_rule = module.params.get("action_rule") + name_alias = module.params.get("name_alias") + + aci = ACIModule(module) + + child_classes = ["rtctrlRsCtxPToSubjP", "rtctrlScope"] + + tenant_url_config = dict( + aci_class="fvTenant", + aci_rn="tn-{0}".format(tenant), + module_object=tenant, + target_filter={"name": tenant}, + ) + + route_control_profile_url_config = dict( + aci_class="rtctrlProfile", + aci_rn="prof-{0}".format(route_control_profile), + module_object=route_control_profile, + target_filter={"name": route_control_profile}, + ) + + route_control_context_url_config = dict( + aci_class="rtctrlCtxP", + aci_rn="ctx-{0}".format(route_control_context), + module_object=route_control_context, + target_filter={"name": route_control_context}, + ) + + if l3out is not None: + aci.construct_url( + root_class=tenant_url_config, + subclass_1=dict( + aci_class="l3extOut", + aci_rn="out-{0}".format(l3out), + module_object=l3out, + target_filter={"name": l3out}, + ), + subclass_2=route_control_profile_url_config, + subclass_3=route_control_context_url_config, + child_classes=child_classes, + ) + else: + aci.construct_url( + root_class=tenant_url_config, + subclass_1=route_control_profile_url_config, + subclass_2=route_control_context_url_config, + child_classes=child_classes, + ) + + aci.get_existing() + + if state == "present": + child_configs = [] + if match_rule is not None: + child_configs.append({"rtctrlRsCtxPToSubjP": {"attributes": {"tnRtctrlSubjPName": match_rule}}}) + if action_rule is not None: + child_configs.append( + { + "rtctrlScope": { + "attributes": {"descr": ""}, + "children": [{"rtctrlRsScopeToAttrP": {"attributes": {"tnRtctrlAttrPName": action_rule}}}], + } + } + ) + + aci.payload( + aci_class="rtctrlCtxP", + class_config=dict( + name=route_control_context, + descr=description, + action=action, + order=order, + nameAlias=name_alias, + ), + child_configs=child_configs, + ) + + aci.get_diff(aci_class="rtctrlCtxP") + + aci.post_config() + + elif state == "absent": + aci.delete_config() + + aci.exit_json() + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_route_control_profile.py b/ansible_collections/cisco/aci/plugins/modules/aci_route_control_profile.py new file mode 100644 index 000000000..496cce9b3 --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_route_control_profile.py @@ -0,0 +1,333 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2023, Gaspard Micol (@gmicol) <gmicol@cisco.com> +# Copyright: (c) 2023, Tim Cragg (@timcragg) <tcragg@cisco.com> +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported_by": "certified"} + +DOCUMENTATION = r""" +--- +module: aci_route_control_profile +short_description: Manage Route Control Profile (rtcrtl:Profile) +description: +- Manage Route Control Profiles on Cisco ACI fabrics. +options: + tenant: + description: + - The name of an existing tenant. + type: str + aliases: [ tenant_name ] + l3out: + description: + - The name of an existing L3Out. + - This will link the created route control profile to the existing L3Out. + type: str + aliases: [ l3out_name ] + route_control_profile: + description: + - The name of the route control profile being created. + type: str + aliases: [ name, route_control_profile_name ] + auto_continue: + description: + - The option to enable/disable auto-continue. + type: str + choices: [ "no", "yes" ] + default: "no" + policy_type: + description: + - Set the policy type to combinable or global. + type: str + choices: [ combinable, global ] + default: combinable + description: + description: + - The description for the route control profile. + type: str + aliases: [ descr ] + state: + description: + - Use C(present) or C(absent) for adding or removing. + - Use C(query) for listing an object or multiple objects. + type: str + choices: [ absent, present, query ] + default: present + name_alias: + description: + - The alias for the current object. This relates to the nameAlias field in ACI. + type: str +extends_documentation_fragment: +- cisco.aci.aci +- cisco.aci.annotation +- cisco.aci.owner + +notes: +- The C(tenant) used must exist before using this module in your playbook. + The M(cisco.aci.aci_tenant) module can be used for this. +- If C(l3out) is used, the C(l3out) used must exist before using this module in your playbook. + The M(cisco.aci.aci_l3out) module can be used for this. +seealso: +- module: cisco.aci.aci_tenant +- module: cisco.aci.aci_l3out +- name: APIC Management Information Model reference + description: More information about the internal APIC class B(rtctrl:Profile). + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Gaspard Micol (@gmicol) +- Tim Cragg (@timcragg) +""" + +EXAMPLES = r""" +- name: Create a route control profile + cisco.aci.aci_route_control_profile: + host: apic + username: admin + password: SomeSecretPassword + route_control_profile: prod_route_control_profile + tenant: production + l3out: prod_l3out + auto_continue: "no" + policy_type: combinable + state: present + delegate_to: localhost + +- name: Delete a route control profile + cisco.aci.aci_route_control_profile: + host: apic + username: admin + password: SomeSecretPassword + route_control_profile: prod_route_control_profile + tenant: production + l3out: prod_l3out + state: absent + delegate_to: localhost + +- name: Query all route control profiles + cisco.aci.aci_route_control_profile: + host: apic + username: admin + password: SomeSecretPassword + state: query + delegate_to: localhost + register: query_result + +- name: Query a specific route control profile + cisco.aci.aci_route_control_profile: + host: apic + username: admin + password: SomeSecretPassword + route_control_profile: prod_route_control_profile + tenant: production + l3out: prod_l3out + state: query + delegate_to: localhost + register: query_result +""" + +RETURN = r""" +current: + description: The existing configuration from the APIC after the module has finished + returned: success + type: list + sample: + [ + { + "fvTenant": { + "attributes": { + "descr": "Production environment", + "dn": "uni/tn-production", + "name": "production", + "nameAlias": "", + "ownerKey": "", + "ownerauto_continue": "" + } + } + } + ] +error: + description: The error information as returned from the APIC + returned: failure + type: dict + sample: + { + "code": "122", + "text": "unknown managed object class foo" + } +raw: + description: The raw output returned by the APIC REST API (xml or json) + returned: parse error + type: str + sample: '<?xml version="1.0" encoding="UTF-8"?><imdata totalCount="1"><error code="122" text="unknown managed object class foo"/></imdata>' +sent: + description: The actual/minimal configuration pushed to the APIC + returned: info + type: list + sample: + { + "fvTenant": { + "attributes": { + "descr": "Production environment" + } + } + } +previous: + description: The original configuration from the APIC before the module has started + returned: info + type: list + sample: + [ + { + "fvTenant": { + "attributes": { + "descr": "Production", + "dn": "uni/tn-production", + "name": "production", + "nameAlias": "", + "ownerKey": "", + "ownerauto_continue": "" + } + } + } + ] +proposed: + description: The assembled configuration from the user-provided parameters + returned: info + type: dict + sample: + { + "fvTenant": { + "attributes": { + "descr": "Production environment", + "name": "production" + } + } + } +filter_string: + description: The filter string used for the request + returned: failure or debug + type: str + sample: ?rsp-prop-include=config-only +method: + description: The HTTP method used for the request to the APIC + returned: failure or debug + type: str + sample: POST +response: + description: The HTTP response from the APIC + returned: failure or debug + type: str + sample: OK (30 bytes) +status: + description: The HTTP status from the APIC + returned: failure or debug + type: int + sample: 200 +url: + description: The HTTP url used for the request to the APIC + returned: failure or debug + type: str + sample: https://10.11.12.13/api/mo/uni/tn-production.json +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.cisco.aci.plugins.module_utils.aci import ACIModule, aci_argument_spec, aci_annotation_spec, aci_owner_spec + + +def main(): + argument_spec = aci_argument_spec() + argument_spec.update(aci_annotation_spec()) + argument_spec.update(aci_owner_spec()) + argument_spec.update( + tenant=dict(type="str", aliases=["tenant_name"]), # Not required for querying all objects + l3out=dict(type="str", aliases=["l3out_name"]), # Not required for querying all objects + route_control_profile=dict(type="str", aliases=["name", "route_control_profile_name"]), # Not required for querying all objects + description=dict(type="str", aliases=["descr"]), + auto_continue=dict(type="str", default="no", choices=["no", "yes"]), + policy_type=dict(type="str", default="combinable", choices=["combinable", "global"]), + name_alias=dict(type="str"), + state=dict(type="str", default="present", choices=["present", "absent", "query"]), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_if=[ + ["state", "absent", ["route_control_profile", "tenant"]], + ["state", "present", ["route_control_profile", "tenant"]], + ], + ) + + route_control_profile = module.params.get("route_control_profile") + description = module.params.get("description") + auto_continue = module.params.get("auto_continue") + policy_type = module.params.get("policy_type") + state = module.params.get("state") + tenant = module.params.get("tenant") + l3out = module.params.get("l3out") + name_alias = module.params.get("name_alias") + + aci = ACIModule(module) + + tenant_url_config = dict( + aci_class="fvTenant", + aci_rn="tn-{0}".format(tenant), + module_object=tenant, + target_filter={"name": tenant}, + ) + + route_control_profile_url_config = dict( + aci_class="rtctrlProfile", + aci_rn="prof-{0}".format(route_control_profile), + module_object=route_control_profile, + target_filter={"name": route_control_profile}, + ) + + if l3out is not None: + aci.construct_url( + root_class=tenant_url_config, + subclass_1=dict( + aci_class="l3extOut", + aci_rn="out-{0}".format(l3out), + module_object=l3out, + target_filter={"name": l3out}, + ), + subclass_2=route_control_profile_url_config, + ) + else: + aci.construct_url( + root_class=tenant_url_config, + subclass_1=route_control_profile_url_config, + ) + + aci.get_existing() + + if state == "present": + aci.payload( + aci_class="rtctrlProfile", + class_config=dict( + name=route_control_profile, + descr=description, + autoContinue=auto_continue, + type=policy_type, + nameAlias=name_alias, + ), + ) + + aci.get_diff(aci_class="rtctrlProfile") + + aci.post_config() + + elif state == "absent": + aci.delete_config() + + aci.exit_json() + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_snmp_user.py b/ansible_collections/cisco/aci/plugins/modules/aci_snmp_user.py index 49a9f5ae5..9e9b6d852 100644 --- a/ansible_collections/cisco/aci/plugins/modules/aci_snmp_user.py +++ b/ansible_collections/cisco/aci/plugins/modules/aci_snmp_user.py @@ -1,6 +1,8 @@ #!/usr/bin/python # -*- coding: utf-8 -*- +# Copyright: (c) 2021, Tim Cragg (@timcragg) +# Copyright: (c) 2023, Akini Ross (@akinross) # GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) from __future__ import absolute_import, division, print_function @@ -21,7 +23,7 @@ options: description: - SNMP authentication method type: str - choices: [ hmac-md5-96, hmac-sha1-96] + choices: [ hmac-md5-96, hmac-sha1-96, hmac-sha2-224, hmac-sha2-256, hmac-sha2-384, hmac-sha2-512 ] auth_key: description: - SNMP authentication key @@ -31,6 +33,11 @@ options: - Name of the SNMP user policy type: str aliases: [ snmp_user_policy ] + description: + description: + - Description of the SNMP user policy + type: str + aliases: [ descr ] policy: description: - Name of an existing SNMP policy @@ -56,12 +63,17 @@ extends_documentation_fragment: - cisco.aci.aci - cisco.aci.annotation +notes: +- The C(policy) used must exist before using this module in your playbook. + The M(cisco.aci.aci_snmp_policy) module can be used for this. seealso: +- module: cisco.aci.aci_snmp_policy - name: APIC Management Information Model reference description: More information about the internal APIC class B(snmp:UserP). link: https://developer.cisco.com/docs/apic-mim-ref/ author: - Tim Cragg (@timcragg) +- Akini Ross (@akinross) """ EXAMPLES = r""" @@ -72,7 +84,7 @@ EXAMPLES = r""" password: SomeSecretPassword policy: my_snmp_policy name: my_snmp_user - auth_type: hmac-sha1-96 + auth_type: hmac-sha2-256 auth_key: "{{ hmac_key }}" state: present delegate_to: localhost @@ -238,7 +250,8 @@ def main(): argument_spec.update( policy=dict(type="str", aliases=["snmp_policy", "snmp_policy_name"]), name=dict(type="str", aliases=["snmp_user_policy"]), - auth_type=dict(type="str", choices=["hmac-md5-96", "hmac-sha1-96"]), + description=dict(type="str", aliases=["descr"]), + auth_type=dict(type="str", choices=["hmac-md5-96", "hmac-sha1-96", "hmac-sha2-224", "hmac-sha2-256", "hmac-sha2-384", "hmac-sha2-512"]), auth_key=dict(type="str", no_log=True), privacy_type=dict(type="str", choices=["aes-128", "des", "none"]), privacy_key=dict(type="str", no_log=True), @@ -258,6 +271,7 @@ def main(): policy = module.params.get("policy") name = module.params.get("name") + description = module.params.get("description") auth_type = module.params.get("auth_type") auth_key = module.params.get("auth_key") privacy_type = module.params.get("privacy_type") @@ -284,7 +298,7 @@ def main(): if state == "present": aci.payload( aci_class="snmpUserP", - class_config=dict(privType=privacy_type, privKey=privacy_key, authType=auth_type, authKey=auth_key, name=name), + class_config=dict(privType=privacy_type, privKey=privacy_key, authType=auth_type, authKey=auth_key, name=name, descr=description), ) aci.get_diff(aci_class="snmpUserP") diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_static_binding_to_epg.py b/ansible_collections/cisco/aci/plugins/modules/aci_static_binding_to_epg.py index b5428449f..051d5ee00 100644 --- a/ansible_collections/cisco/aci/plugins/modules/aci_static_binding_to_epg.py +++ b/ansible_collections/cisco/aci/plugins/modules/aci_static_binding_to_epg.py @@ -48,7 +48,7 @@ options: description: - Determines the primary encapsulation ID associating the C(epg) with the interface path when using micro-segmentation. - - Accepted values are any valid encap ID for specified encap, currently ranges between C(1) and C(4096) and C(unknown. + - Accepted values are any valid encap ID for specified encap, currently ranges between C(1) and C(4096) and C(unknown). - C(unknown) is the default value and using C(unknown) disables the Micro-Segmentation. type: str aliases: [ primary_vlan, primary_vlan_id ] diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_syslog_source.py b/ansible_collections/cisco/aci/plugins/modules/aci_syslog_source.py index 95dcd7f39..b0bb61bb5 100644 --- a/ansible_collections/cisco/aci/plugins/modules/aci_syslog_source.py +++ b/ansible_collections/cisco/aci/plugins/modules/aci_syslog_source.py @@ -25,6 +25,7 @@ options: include: description: - List of message types to include + - The APIC defaults to C(faults) when unset during creation. type: list elements: str choices: [ audit, events, faults, session ] diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_system_banner.py b/ansible_collections/cisco/aci/plugins/modules/aci_system_banner.py new file mode 100644 index 000000000..bbba1d12e --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_system_banner.py @@ -0,0 +1,296 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2023, Tim Cragg (@timcragg) <tcragg@cisco.com> +# Copyright: (c) 2023, Akini Ross (@akinross) <akinross@cisco.com> +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported_by": "certified"} + +DOCUMENTATION = r""" +--- +module: aci_system_banner +short_description: Manages Alias and Banners (aaa:PreLoginBanner) +description: +- Manages Alias and Banners on Cisco ACI fabrics. +options: + description: + description: + - The description of the AAA login banner. + type: str + gui_alias: + description: + - The system GUI alias. + type: str + controller_banner: + description: + - The contents of the CLI informational banner to be displayed before user login authentication. + - The CLI banner is a text based string printed as-is to the console. + type: str + switch_banner: + description: + - The switch banner message. + type: str + application_banner: + description: + - The application banner message. + type: str + severity: + description: + - The application banner severity. + type: str + choices: [ critical, info, major, minor, warning ] + gui_banner: + description: + - The contents of the GUI informational banner to be displayed before user login authentication. + - When I(gui_banner) starts with I(http://) or I(https://) the banner will be of URL type. + - Note that the URL site owner must allow the site to be placed in an iFrame to display the informational banner. + type: str + state: + description: + - Use C(present) for updating. + - Use C(query) for listing an object. + type: str + choices: [ present, query ] + default: present + name_alias: + description: + - The alias for the current object. This relates to the nameAlias field in ACI. + type: str +extends_documentation_fragment: +- cisco.aci.aci +- cisco.aci.annotation +- cisco.aci.owner + +seealso: +- name: APIC Management Information Model reference + description: More information about the internal APIC class B(aaa:PreLoginBanner). + link: https://developer.cisco.com/docs/apic-mim-ref/ + +author: +- Tim Cragg (@timcragg) +- Akini Ross (@akinross) +""" + +EXAMPLES = r""" +- name: Configure banner + cisco.aci.aci_system_banner: + host: apic + username: admin + password: SomeSecretPassword + gui_alias: "Test GUI Alias" + controller_banner: "Test Controller Banner" + application_banner: "Test Application Banner" + severity: critical + switch_banner: "Test Switch Banner" + gui_banner: "Test GUI Banner" + state: present + delegate_to: localhost + +- name: Configure banner with a url + cisco.aci.aci_system_banner: + host: apic + username: admin + password: SomeSecretPassword + gui_alias: "Test GUI Alias" + controller_banner: "Test Controller Banner Message" + application_banner: "Test Application Banner" + severity: critical + switch_banner: "Test Switch Banner Message" + gui_banner: https://www.cisco.com + state: present + delegate_to: localhost + +- name: Query banner + cisco.aci.aci_system_banner: + host: apic + username: admin + password: SomeSecretPassword + state: query + delegate_to: localhost +""" + +RETURN = r""" +current: + description: The existing configuration from the APIC after the module has finished + returned: success + type: list + sample: + [ + { + "fvTenant": { + "attributes": { + "descr": "Production environment", + "dn": "uni/tn-production", + "name": "production", + "nameAlias": "", + "ownerKey": "", + "ownerTag": "" + } + } + } + ] +error: + description: The error information as returned from the APIC + returned: failure + type: dict + sample: + { + "code": "122", + "text": "unknown managed object class foo" + } +raw: + description: The raw output returned by the APIC REST API (xml or json) + returned: parse error + type: str + sample: '<?xml version="1.0" encoding="UTF-8"?><imdata totalCount="1"><error code="122" text="unknown managed object class foo"/></imdata>' +sent: + description: The actual/minimal configuration pushed to the APIC + returned: info + type: list + sample: + { + "fvTenant": { + "attributes": { + "descr": "Production environment" + } + } + } +previous: + description: The original configuration from the APIC before the module has started + returned: info + type: list + sample: + [ + { + "fvTenant": { + "attributes": { + "descr": "Production", + "dn": "uni/tn-production", + "name": "production", + "nameAlias": "", + "ownerKey": "", + "ownerTag": "" + } + } + } + ] +proposed: + description: The assembled configuration from the user-provided parameters + returned: info + type: dict + sample: + { + "fvTenant": { + "attributes": { + "descr": "Production environment", + "name": "production" + } + } + } +filter_string: + description: The filter string used for the request + returned: failure or debug + type: str + sample: ?rsp-prop-include=config-only +method: + description: The HTTP method used for the request to the APIC + returned: failure or debug + type: str + sample: POST +response: + description: The HTTP response from the APIC + returned: failure or debug + type: str + sample: OK (30 bytes) +status: + description: The HTTP status from the APIC + returned: failure or debug + type: int + sample: 200 +url: + description: The HTTP url used for the request to the APIC + returned: failure or debug + type: str + sample: https://10.11.12.13/api/mo/uni/tn-production.json +""" + + +import re +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.cisco.aci.plugins.module_utils.aci import ACIModule, aci_argument_spec, aci_annotation_spec, aci_owner_spec + + +def main(): + argument_spec = aci_argument_spec() + argument_spec.update(aci_annotation_spec()) + argument_spec.update(aci_owner_spec()) + argument_spec.update( + description=dict(type="str"), + gui_alias=dict(type="str"), + controller_banner=dict(type="str"), + switch_banner=dict(type="str"), + application_banner=dict(type="str"), + severity=dict(type="str", choices=["critical", "info", "major", "minor", "warning"]), + gui_banner=dict(type="str"), + name_alias=dict(type="str"), + state=dict(type="str", default="present", choices=["present", "query"]), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_by={"severity": "application_banner"}, + ) + aci = ACIModule(module) + + description = module.params.get("description") + gui_alias = module.params.get("gui_alias") + controller_banner = module.params.get("controller_banner") + switch_banner = module.params.get("switch_banner") + application_banner = module.params.get("application_banner") + severity = module.params.get("severity") + gui_banner = module.params.get("gui_banner") + state = module.params.get("state") + name_alias = module.params.get("name_alias") + + aci.construct_url( + root_class=dict( + aci_class="aaaPreLoginBanner", + aci_rn="userext/preloginbanner", + ), + ) + aci.get_existing() + + if state == "present": + regex_url = "^https?:\\/\\/(?:www\\.)?[-a-zA-Z0-9@:%._\\+~#=]{1,256}\\.[a-zA-Z0-9()]{1,6}\\b(?:[-a-zA-Z0-9()@:%_\\+.~#?&\\/=]*)$" + + aci.payload( + aci_class="aaaPreLoginBanner", + class_config=dict( + descr=description, + bannerMessage=application_banner, + bannerMessageSeverity=severity, + guiMessage=gui_banner, + guiTextMessage=gui_alias, + isGuiMessageText="no" if re.fullmatch(regex_url, gui_banner) else "yes", + message=controller_banner, + showBannerMessage="yes" if application_banner else "no", + switchMessage=switch_banner, + nameAlias=name_alias, + ), + ) + + aci.get_diff(aci_class="aaaPreLoginBanner") + + aci.post_config() + + aci.exit_json() + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_system_endpoint_controls.py b/ansible_collections/cisco/aci/plugins/modules/aci_system_endpoint_controls.py new file mode 100644 index 000000000..7da8afb3f --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_system_endpoint_controls.py @@ -0,0 +1,338 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2023, Tim Cragg (@timcragg) <timcragg@cisco.com> +# Copyright: (c) 2023, Akini Ross (@akinross) <akinross@cisco.com> +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported_by": "certified"} + +DOCUMENTATION = r""" +--- +module: aci_system_endpoint_controls +short_description: Manage System Endpoint Controls (ep:IpAgingP, ep:ControlP, and ep:LoopProtectP) +description: +- Manage System Endpoint Controls on Cisco ACI fabrics. +options: + ip_aging: + description: Configuration container for IP Aging. + type: dict + suboptions: + admin_state: + description: + - Whether to enable IP Aging Controls on the fabric. + type: bool + roque_ep_control: + description: Configuration container for Rogue EP Control. + type: dict + suboptions: + admin_state: + description: + - Whether to enable Rogue EP Control on the fabric. + type: bool + interval: + description: + - The rogue endpoint detection interval in seconds. + type: int + multiplication_factor: + description: + - The rogue endpoint detection multiplication factor. + type: int + hold_interval: + description: + - The rogue endpoint hold interval in seconds. + type: int + ep_loop_protection: + description: Configuration container for EP Loop Protection. + type: dict + suboptions: + admin_state: + description: + - Whether to enable EP Loop Protection on the fabric. + type: bool + interval: + description: + - The loop protection detection interval in seconds. + type: int + multiplication_factor: + description: + - The loop protection detection multiplication factor. + type: int + action: + description: + - The action(s) to take when a loop is detected. + type: list + elements: str + choices: [ bd, port ] + state: + description: + - Use C(present) for updating configuration. + - Use C(query) for showing current configuration. + type: str + choices: [ present, query ] + default: present +extends_documentation_fragment: +- cisco.aci.aci +- cisco.aci.annotation +- cisco.aci.owner + +seealso: +- name: APIC Management Information Model reference + description: More information about the internal APIC class B(ep:IpAgingP), B(ep:ControlP), and B(ep:LoopProtectP). + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Tim Cragg (@timcragg) +- Akini Ross (@akinross) +""" + +EXAMPLES = r""" +- name: Set system endpoint controls settings + cisco.aci.aci_system_endpoint_controls: + host: apic + username: admin + password: SomeSecretPassword + admin_state: true + ip_aging: + admin_state: true + roque_ep_control: + admin_state: true + interval: 50 + multiplication_factor: 10 + hold_interval: 2000 + ep_loop_protection: + admin_state: true + interval: 70 + multiplication_factor: 15 + action: [ bd, port ] + delegate_to: localhost + +- name: Query system endpoint controls settings + cisco.aci.aci_system_endpoint_controls: + host: apic + username: admin + password: SomeSecretPassword + state: query + delegate_to: localhost + register: query_result + +""" + +RETURN = r""" +current: + description: The existing configuration from the APIC after the module has finished + returned: success + type: list + sample: + [ + { + "fvTenant": { + "attributes": { + "descr": "Production environment", + "dn": "uni/tn-production", + "name": "production", + "nameAlias": "", + "ownerKey": "", + "ownerTag": "" + } + } + } + ] +error: + description: The error information as returned from the APIC + returned: failure + type: dict + sample: + { + "code": "122", + "text": "unknown managed object class foo" + } +raw: + description: The raw output returned by the APIC REST API (xml or json) + returned: parse error + type: str + sample: '<?xml version="1.0" encoding="UTF-8"?><imdata totalCount="1"><error code="122" text="unknown managed object class foo"/></imdata>' +sent: + description: The actual/minimal configuration pushed to the APIC + returned: info + type: list + sample: + { + "fvTenant": { + "attributes": { + "descr": "Production environment" + } + } + } +previous: + description: The original configuration from the APIC before the module has started + returned: info + type: list + sample: + [ + { + "fvTenant": { + "attributes": { + "descr": "Production", + "dn": "uni/tn-production", + "name": "production", + "nameAlias": "", + "ownerKey": "", + "ownerTag": "" + } + } + } + ] +proposed: + description: The assembled configuration from the user-provided parameters + returned: info + type: dict + sample: + { + "fvTenant": { + "attributes": { + "descr": "Production environment", + "name": "production" + } + } + } +filter_string: + description: The filter string used for the request + returned: failure or debug + type: str + sample: ?rsp-prop-include=config-only +method: + description: The HTTP method used for the request to the APIC + returned: failure or debug + type: str + sample: POST +response: + description: The HTTP response from the APIC + returned: failure or debug + type: str + sample: OK (30 bytes) +status: + description: The HTTP status from the APIC + returned: failure or debug + type: int + sample: 200 +url: + description: The HTTP url used for the request to the APIC + returned: failure or debug + type: str + sample: https://10.11.12.13/api/mo/uni/tn-production.json +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.cisco.aci.plugins.module_utils.aci import ACIModule, aci_argument_spec, aci_annotation_spec, aci_owner_spec +from ansible_collections.cisco.aci.plugins.module_utils.constants import EP_LOOP_PROTECTION_ACTION_MAPPING + + +def main(): + argument_spec = aci_argument_spec() + argument_spec.update(aci_annotation_spec()) + argument_spec.update(aci_owner_spec()) + argument_spec.update( + ip_aging=dict(type="dict", options=dict(admin_state=dict(type="bool"))), + roque_ep_control=dict( + type="dict", + options=dict( + admin_state=dict(type="bool"), + interval=dict(type="int"), + multiplication_factor=dict(type="int"), + hold_interval=dict(type="int"), + ), + ), + ep_loop_protection=dict( + type="dict", + options=dict( + admin_state=dict(type="bool"), + interval=dict(type="int"), + multiplication_factor=dict(type="int"), + action=dict(type="list", elements="str", choices=["bd", "port"]), + ), + ), + state=dict(type="str", default="present", choices=["present", "query"]), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_if=[["state", "present", ["ip_aging", "roque_ep_control", "ep_loop_protection"], True]], + ) + + aci = ACIModule(module) + state = module.params.get("state") + + aci.construct_url( + root_class=dict( + aci_class="infraInfra", + aci_rn="infra", + ), + child_classes=["epIpAgingP", "epControlP", "epLoopProtectP"], + ) + + aci.get_existing() + + if state == "present": + child_configs = [] + ip_aging = module.params.get("ip_aging") + roque_ep_control = module.params.get("roque_ep_control") + ep_loop_protection = module.params.get("ep_loop_protection") + + if ip_aging: + child_configs.append( + {"epIpAgingP": {"attributes": {"name": "default", "adminSt": aci.boolean(ip_aging.get("admin_state"), "enabled", "disabled")}}} + ) + + if roque_ep_control: + child_configs.append( + { + "epControlP": { + "attributes": { + "name": "default", + "adminSt": aci.boolean(roque_ep_control.get("admin_state"), "enabled", "disabled"), + "rogueEpDetectIntvl": roque_ep_control.get("interval"), + "rogueEpDetectMult": roque_ep_control.get("multiplication_factor"), + "holdIntvl": roque_ep_control.get("hold_interval"), + } + } + } + ) + + if ep_loop_protection: + actions = None + if ep_loop_protection.get("action"): + actions = ",".join(sorted([EP_LOOP_PROTECTION_ACTION_MAPPING.get(action) for action in ep_loop_protection.get("action")])) + child_configs.append( + { + "epLoopProtectP": { + "attributes": { + "name": "default", + "adminSt": aci.boolean(ep_loop_protection.get("admin_state"), "enabled", "disabled"), + "loopDetectIntvl": ep_loop_protection.get("interval"), + "loopDetectMult": ep_loop_protection.get("multiplication_factor"), + "action": actions, + } + } + } + ) + + aci.payload( + aci_class="infraInfra", + class_config=dict(), + child_configs=child_configs, + ) + + aci.get_diff(aci_class="infraInfra") + + aci.post_config() + + aci.exit_json() + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_system_global_aes_passphrase_encryption.py b/ansible_collections/cisco/aci/plugins/modules/aci_system_global_aes_passphrase_encryption.py new file mode 100644 index 000000000..f316a4b22 --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_system_global_aes_passphrase_encryption.py @@ -0,0 +1,236 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2023, Tim Cragg (@timcragg) <tcragg@cisco.com> +# Copyright: (c) 2023, Akini Ross (@akinross) <akinross@cisco.com> +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported_by": "certified"} + +DOCUMENTATION = r""" +--- +module: aci_system_global_aes_passphrase_encryption +short_description: Manage Global AES Passphrase Encryption Settings (pki:ExportEncryptionKey) +description: +- Manage Global AES Passphrase Encryption Settings on Cisco ACI fabrics. +options: + passphrase: + description: + - The AES passphrase to use for configuration export encryption. + - This cannot be modified once in place on the APIC. To modify an existing passphrase, you must delete it by sending a request with state C(absent). + - The value of the passphrase will not be shown in the results of a C(query). + type: str + enable: + description: + - Whether to enable strong encryption. + - The APIC defaults to C(false) when unset during creation. + - Note that this will be set back to False when deleting an existing passphrase. + type: bool + state: + description: + - Use C(present) to create a passphrase or to change the enable setting. + - Use C(absent) to delete the existing passphrase. + - Use C(query) for showing current configuration. + type: str + choices: [ absent, present, query ] + default: present +extends_documentation_fragment: +- cisco.aci.aci +- cisco.aci.annotation +- cisco.aci.owner + +seealso: +- name: APIC Management Information Model reference + description: More information about the internal APIC class B(pki:ExportEncryptionKey). + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Tim Cragg (@timcragg) +- Akini Ross (@akinross) +""" + +EXAMPLES = r""" +- name: Enable encryption with a passphrase + cisco.aci.aci_system_global_aes_passphrase_encryption: + host: apic + username: admin + password: SomeSecretPassword + passphrase: ansible_passphrase + enable: yes + state: present + delegate_to: localhost + +- name: Query passphrase settings + cisco.aci.aci_system_global_aes_passphrase_encryption: + host: apic + username: admin + password: SomeSecretPassword + state: query + delegate_to: localhost + register: query_result + +- name: Clear encryption key + cisco.aci.aci_system_global_aes_passphrase_encryption: + host: apic + username: admin + password: SomeSecretPassword + state: absent + delegate_to: localhost +""" + +RETURN = r""" +current: + description: The existing configuration from the APIC after the module has finished + returned: success + type: list + sample: + [ + { + "fvTenant": { + "attributes": { + "descr": "Production environment", + "dn": "uni/tn-production", + "name": "production", + "nameAlias": "", + "ownerKey": "", + "ownerTag": "" + } + } + } + ] +error: + description: The error information as returned from the APIC + returned: failure + type: dict + sample: + { + "code": "122", + "text": "unknown managed object class foo" + } +raw: + description: The raw output returned by the APIC REST API (xml or json) + returned: parse error + type: str + sample: '<?xml version="1.0" encoding="UTF-8"?><imdata totalCount="1"><error code="122" text="unknown managed object class foo"/></imdata>' +sent: + description: The actual/minimal configuration pushed to the APIC + returned: info + type: list + sample: + { + "fvTenant": { + "attributes": { + "descr": "Production environment" + } + } + } +previous: + description: The original configuration from the APIC before the module has started + returned: info + type: list + sample: + [ + { + "fvTenant": { + "attributes": { + "descr": "Production", + "dn": "uni/tn-production", + "name": "production", + "nameAlias": "", + "ownerKey": "", + "ownerTag": "" + } + } + } + ] +proposed: + description: The assembled configuration from the user-provided parameters + returned: info + type: dict + sample: + { + "fvTenant": { + "attributes": { + "descr": "Production environment", + "name": "production" + } + } + } +filter_string: + description: The filter string used for the request + returned: failure or debug + type: str + sample: ?rsp-prop-include=config-only +method: + description: The HTTP method used for the request to the APIC + returned: failure or debug + type: str + sample: POST +response: + description: The HTTP response from the APIC + returned: failure or debug + type: str + sample: OK (30 bytes) +status: + description: The HTTP status from the APIC + returned: failure or debug + type: int + sample: 200 +url: + description: The HTTP url used for the request to the APIC + returned: failure or debug + type: str + sample: https://10.11.12.13/api/mo/uni/tn-production.json +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.cisco.aci.plugins.module_utils.aci import ACIModule, aci_argument_spec, aci_annotation_spec, aci_owner_spec + + +def main(): + argument_spec = aci_argument_spec() + argument_spec.update(aci_annotation_spec()) + argument_spec.update(aci_owner_spec()) + argument_spec.update( + passphrase=dict(type="str", no_log=True), + enable=dict(type="bool"), + state=dict(type="str", default="present", choices=["absent", "present", "query"]), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + ) + + aci = ACIModule(module) + + passphrase = module.params.get("passphrase") + enable = aci.boolean(module.params.get("enable")) + state = module.params.get("state") + + aci.construct_url( + root_class=dict( + aci_class="pkiExportEncryptionKey", + aci_rn="exportcryptkey", + ), + ) + + aci.get_existing() + + if state in ["present", "absent"]: + class_config = dict(passphrase=passphrase, strongEncryptionEnabled=enable) if state == "present" else dict(clearEncryptionKey="yes") + + aci.payload(aci_class="pkiExportEncryptionKey", class_config=class_config) + + aci.get_diff(aci_class="pkiExportEncryptionKey") + + aci.post_config() + + aci.exit_json() + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_tag.py b/ansible_collections/cisco/aci/plugins/modules/aci_tag.py index 9508f1d6b..9e56ae068 100644 --- a/ansible_collections/cisco/aci/plugins/modules/aci_tag.py +++ b/ansible_collections/cisco/aci/plugins/modules/aci_tag.py @@ -28,6 +28,7 @@ options: description: - Value of the property. type: str + default: "" tag_type: description: - Type of tag object. diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_tenant_ep_retention_policy.py b/ansible_collections/cisco/aci/plugins/modules/aci_tenant_ep_retention_policy.py index c1896bb3a..c499adaaa 100644 --- a/ansible_collections/cisco/aci/plugins/modules/aci_tenant_ep_retention_policy.py +++ b/ansible_collections/cisco/aci/plugins/modules/aci_tenant_ep_retention_policy.py @@ -253,7 +253,7 @@ from ansible_collections.cisco.aci.plugins.module_utils.aci import ACIModule, ac BOUNCE_TRIG_MAPPING = dict( coop="protocol", - rarp="rarp-flood", + flood="rarp-flood", ) @@ -308,7 +308,7 @@ def main(): if move_frequency == 0: move_frequency = "none" remote_ep_interval = module.params.get("remote_ep_interval") - if remote_ep_interval is not None and remote_ep_interval not in range(120, 65536): + if remote_ep_interval is not None and remote_ep_interval != 0 and remote_ep_interval not in range(120, 65536): module.fail_json(msg="The remote_ep_interval must be a value of 0 or between 120 and 65535") if remote_ep_interval == 0: remote_ep_interval = "infinite" diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_tenant_span_dst_group.py b/ansible_collections/cisco/aci/plugins/modules/aci_tenant_span_dst_group.py index 952e8fe28..a9907e983 100644 --- a/ansible_collections/cisco/aci/plugins/modules/aci_tenant_span_dst_group.py +++ b/ansible_collections/cisco/aci/plugins/modules/aci_tenant_span_dst_group.py @@ -155,6 +155,7 @@ EXAMPLES = r""" destination_group: group1 state: query delegate_to: localhost + register: query_result - name: Query all SPAN destination groups cisco.aci.aci_tenant_span_dst_group: @@ -163,6 +164,7 @@ EXAMPLES = r""" password: SomeSecretPassword state: query delegate_to: localhost + register: query_result """ RETURN = r""" diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_tenant_span_src_group.py b/ansible_collections/cisco/aci/plugins/modules/aci_tenant_span_src_group.py index ab3e8b341..260b9b392 100644 --- a/ansible_collections/cisco/aci/plugins/modules/aci_tenant_span_src_group.py +++ b/ansible_collections/cisco/aci/plugins/modules/aci_tenant_span_src_group.py @@ -18,7 +18,7 @@ description: options: admin_state: description: - - Enable or disable the span sources. + - Enable C(true) or disable C(false) the span sources. - The APIC defaults to C(true) when unset during creation. type: bool description: diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_tenant_span_src_group_src.py b/ansible_collections/cisco/aci/plugins/modules/aci_tenant_span_src_group_src.py index 02d118416..0ac97b03c 100644 --- a/ansible_collections/cisco/aci/plugins/modules/aci_tenant_span_src_group_src.py +++ b/ansible_collections/cisco/aci/plugins/modules/aci_tenant_span_src_group_src.py @@ -227,8 +227,7 @@ url: from ansible.module_utils.basic import AnsibleModule from ansible_collections.cisco.aci.plugins.module_utils.aci import ACIModule, aci_argument_spec, aci_annotation_spec, aci_owner_spec - -DIRECTION_MAP = {"incoming": "in", "outgoing": "out", "both": "both"} +from ansible_collections.cisco.aci.plugins.module_utils.constants import SPAN_DIRECTION_MAP def main(): @@ -237,7 +236,7 @@ def main(): argument_spec.update(aci_owner_spec()) argument_spec.update( description=dict(type="str", aliases=["descr"]), - direction=dict(type="str", choices=["incoming", "outgoing", "both"]), + direction=dict(type="str", choices=list(SPAN_DIRECTION_MAP.keys())), name=dict(type="str"), # Not required for querying all objects src_ap=dict(type="str", aliases=["ap"]), src_epg=dict(type="str", aliases=["epg"]), @@ -298,7 +297,7 @@ def main(): aci.payload( aci_class="spanSrc", - class_config=dict(descr=description, name=name, dir=DIRECTION_MAP.get(direction)), + class_config=dict(descr=description, name=name, dir=SPAN_DIRECTION_MAP.get(direction)), child_configs=[{"spanRsSrcToEpg": {"attributes": {"tDn": tdn}}}], ) diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_vlan_pool_encap_block.py b/ansible_collections/cisco/aci/plugins/modules/aci_vlan_pool_encap_block.py index 18a38566a..adec9bb9c 100644 --- a/ansible_collections/cisco/aci/plugins/modules/aci_vlan_pool_encap_block.py +++ b/ansible_collections/cisco/aci/plugins/modules/aci_vlan_pool_encap_block.py @@ -273,6 +273,9 @@ def main(): ["state", "absent", ["pool", "pool_allocation_mode", "block_end", "block_start"]], ["state", "present", ["pool", "pool_allocation_mode", "block_end", "block_start"]], ], + required_together=[ + ["pool", "pool_allocation_mode"], + ], ) allocation_mode = module.params.get("allocation_mode") @@ -316,11 +319,8 @@ def main(): aci_block_mo = None # ACI Pool URL requires the allocation mode (ex: uni/infra/vlanns-[poolname]-static) - if pool is not None: - if pool_allocation_mode is not None: - pool_name = "[{0}]-{1}".format(pool, pool_allocation_mode) - else: - module.fail_json(msg="ACI requires the 'pool_allocation_mode' when 'pool' is provided") + if pool: + pool_name = "[{0}]-{1}".format(pool, pool_allocation_mode) aci = ACIModule(module) aci.construct_url( diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_vmm_controller.py b/ansible_collections/cisco/aci/plugins/modules/aci_vmm_controller.py index b066d8b91..f8a51529c 100644 --- a/ansible_collections/cisco/aci/plugins/modules/aci_vmm_controller.py +++ b/ansible_collections/cisco/aci/plugins/modules/aci_vmm_controller.py @@ -33,7 +33,7 @@ options: - Version of the VMware DVS. type: str aliases: [] - choices: [ 'unmanaged', '5.1', '5.5', '6.0', '6.5', '6.6', '7.0' ] + choices: [ 'unmanaged', '5.1', '5.5', '6.0', '6.5', '6.6', '7.0', '8.0' ] stats_collection: description: - Whether stats collection is enabled. @@ -274,7 +274,7 @@ def main(): argument_spec.update( name=dict(type="str"), controller_hostname=dict(type="str"), - dvs_version=dict(type="str", choices=["unmanaged", "5.1", "5.5", "6.0", "6.5", "6.6", "7.0"]), + dvs_version=dict(type="str", choices=["unmanaged", "5.1", "5.5", "6.0", "6.5", "6.6", "7.0", "8.0"]), stats_collection=dict(type="str", default="disabled", choices=["enabled", "disabled"]), domain=dict(type="str", aliases=["domain_name", "domain_profile"]), state=dict(type="str", default="present", choices=["absent", "present", "query"]), diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_vrf.py b/ansible_collections/cisco/aci/plugins/modules/aci_vrf.py index 49c0c9fc1..1aa87f2a3 100644 --- a/ansible_collections/cisco/aci/plugins/modules/aci_vrf.py +++ b/ansible_collections/cisco/aci/plugins/modules/aci_vrf.py @@ -43,6 +43,13 @@ options: - The description for the VRF. type: str aliases: [ descr ] + ip_data_plane_learning: + description: + - Whether IP data plane learning is enabled or disabled. + - The APIC defaults to C(enabled) when unset during creation. + type: str + choices: [ enabled, disabled ] + aliases: [ ip_dataplane_learning ] state: description: - Use C(present) or C(absent) for adding or removing. @@ -256,6 +263,7 @@ def main(): preferred_group=dict(type="str", choices=["enabled", "disabled"]), match_type=dict(type="str", choices=["all", "at_least_one", "at_most_one", "none"]), name_alias=dict(type="str"), + ip_data_plane_learning=dict(type="str", choices=["enabled", "disabled"], aliases=["ip_dataplane_learning"]), ) module = AnsibleModule( @@ -276,6 +284,7 @@ def main(): name_alias = module.params.get("name_alias") preferred_group = module.params.get("preferred_group") match_type = module.params.get("match_type") + ip_data_plane_learning = module.params.get("ip_data_plane_learning") if match_type is not None: match_type = MATCH_TYPE_MAPPING[match_type] @@ -308,6 +317,7 @@ def main(): pcEnfPref=policy_control_preference, name=vrf, nameAlias=name_alias, + ipDataPlaneLearning=ip_data_plane_learning, ), child_configs=[ dict(vzAny=dict(attributes=dict(prefGrMemb=preferred_group, matchT=match_type))), diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_vrf_leak_internal_subnet.py b/ansible_collections/cisco/aci/plugins/modules/aci_vrf_leak_internal_subnet.py new file mode 100644 index 000000000..67432de61 --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_vrf_leak_internal_subnet.py @@ -0,0 +1,397 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2023, Abraham Mughal (@abmughal) abmughal@cisco.com +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported_by": "certified"} + +DOCUMENTATION = r""" +--- +module: aci_vrf_leak_internal_subnet +short_description: Manage VRF leaking of subnets (fv:leakInternalSubnet) +description: +- Manage the leaking of internal subnets under the VRF. +options: + tenant: + description: + - The name of the Tenant the VRF belongs to. + type: str + aliases: [ tenant_name ] + vrf: + description: + - The name of the VRF. + type: str + aliases: [ context, name, vrf_name ] + description: + description: + - The description for the VRF Leak Internal Subnet. + type: str + aliases: [ descr ] + name_alias: + description: + - The alias for the current object. This relates to the nameAlias field in ACI. + type: str + scope: + description: + - Scope of the object. + type: str + choices: [ public, private, shared ] + default: private + leak_to: + description: + - The VRFs to leak the subnet routes into. + type: list + elements: dict + suboptions: + tenant: + description: + - Name of the tenant. + type: str + aliases: [ tenant_name ] + vrf: + description: + - Name of the VRF. + type: str + aliases: [ vrf_name ] + ip: + description: + - The IP address / subnet used to match routes to be leaked. + type: str + state: + description: + - Use C(present) or C(absent) for adding or removing. + - Use C(query) for listing an object or multiple objects. + type: str + choices: [ absent, present, query ] + default: present +extends_documentation_fragment: +- cisco.aci.aci +- cisco.aci.annotation +- cisco.aci.owner + +notes: +- The C(tenant) and C(vrf) used must exist before using this module in your playbook. + The M(cisco.aci.aci_tenant) and M(cisco.aci.aci_vrf) modules can be used for this. +seealso: +- module: cisco.aci.aci_tenant +- module: cisco.aci.aci_vrf +- name: APIC Management Information Model reference + description: More information about the internal APIC class B(leak:InternalSubnet). + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Abraham Mughal (@abmughal) +""" + +EXAMPLES = r""" +- name: Create leak internal subnet + cisco.aci.aci_vrf_leak_internal_subnet: + host: apic + username: admin + password: SomeSecretPassword + vrf: vrf_lab + tenant: lab_tenant + descr: Lab VRF + state: present + leak_to: + - vrf: "test" + tenant: "lab_tenant" + - vrf: "test2" + tenant: "lab_tenant" + description: Ansible Test + ip: 1.1.1.2 + delegate_to: localhost + +- name: Remove a subnet from leaking + cisco.aci.aci_vrf_leak_internal_subnet: + host: apic + username: admin + password: SomeSecretPassword + vrf: vrf_lab + tenant: lab_tenant + state: absent + leak_to: + - vrf: "test2" + tenant: "lab_tenant" + description: Ansible Test + ip: 1.1.1.2 + delegate_to: localhost + +- name: Delete leak internal subnet + cisco.aci.aci_vrf_leak_internal_subnet: + host: apic + username: admin + password: SomeSecretPassword + vrf: vrf_lab + tenant: lab_tenant + state: absent + description: Ansible Test + ip: 1.1.1.2 + delegate_to: localhost + +- name: Query all leak internal subnet + cisco.aci.aci_vrf_leak_internal_subnet: + host: apic + username: admin + password: SomeSecretPassword + state: query + ip: 1.1.1.2 + delegate_to: localhost + register: query_result + +- name: Query leak internal subnet + cisco.aci.aci_vrf_leak_internal_subnet: + host: apic + username: admin + password: SomeSecretPassword + vrf: vrf_lab + tenant: lab_tenant + state: query + ip: 1.1.1.2 + delegate_to: localhost + register: query_result +""" + +RETURN = r""" +current: + description: The existing configuration from the APIC after the module has finished + returned: success + type: list + sample: + [ + { + "fvTenant": { + "attributes": { + "descr": "Production environment", + "dn": "uni/tn-production", + "name": "production", + "nameAlias": "", + "ownerKey": "", + "ownerTag": "" + } + } + } + ] +error: + description: The error information as returned from the APIC + returned: failure + type: dict + sample: + { + "code": "122", + "text": "unknown managed object class foo" + } +raw: + description: The raw output returned by the APIC REST API (xml or json) + returned: parse error + type: str + sample: '<?xml version="1.0" encoding="UTF-8"?><imdata totalCount="1"><error code="122" text="unknown managed object class foo"/></imdata>' +sent: + description: The actual/minimal configuration pushed to the APIC + returned: info + type: list + sample: + { + "fvTenant": { + "attributes": { + "descr": "Production environment" + } + } + } +previous: + description: The original configuration from the APIC before the module has started + returned: info + type: list + sample: + [ + { + "fvTenant": { + "attributes": { + "descr": "Production", + "dn": "uni/tn-production", + "name": "production", + "nameAlias": "", + "ownerKey": "", + "ownerTag": "" + } + } + } + ] +proposed: + description: The assembled configuration from the user-provided parameters + returned: info + type: dict + sample: + { + "fvTenant": { + "attributes": { + "descr": "Production environment", + "name": "production" + } + } + } +filter_string: + description: The filter string used for the request + returned: failure or debug + type: str + sample: ?rsp-prop-include=config-only +method: + description: The HTTP method used for the request to the APIC + returned: failure or debug + type: str + sample: POST +response: + description: The HTTP response from the APIC + returned: failure or debug + type: str + sample: OK (30 bytes) +status: + description: The HTTP status from the APIC + returned: failure or debug + type: int + sample: 200 +url: + description: The HTTP url used for the request to the APIC + returned: failure or debug + type: str + sample: https://10.11.12.13/api/mo/uni/tn-production.json +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.cisco.aci.plugins.module_utils.aci import ACIModule, aci_argument_spec, aci_annotation_spec, aci_owner_spec + + +def main(): + argument_spec = aci_argument_spec() + argument_spec.update(aci_annotation_spec()) + argument_spec.update(aci_owner_spec()) + argument_spec.update( + tenant=dict(type="str", aliases=["tenant_name"]), # Not required for querying all objects + vrf=dict(type="str", aliases=["context", "name", "vrf_name"]), # Not required for querying all objects + leak_to=dict( + type="list", + elements="dict", + options=dict( + vrf=dict(type="str", aliases=["vrf_name"]), + tenant=dict(type="str", aliases=["tenant_name"]), + ), + ), + description=dict(type="str", aliases=["descr"]), + state=dict(type="str", default="present", choices=["absent", "present", "query"]), + name_alias=dict(type="str"), + scope=dict(type="str", default="private", choices=["public", "private", "shared"]), + ip=dict(type="str"), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_if=[ + ["state", "absent", ["tenant", "vrf"]], + ["state", "present", ["tenant", "vrf", "leak_to"]], + ], + ) + + description = module.params.get("description") + state = module.params.get("state") + tenant = module.params.get("tenant") + vrf = module.params.get("vrf") + leak_to = module.params.get("leak_to") + name_alias = module.params.get("name_alias") + scope = module.params.get("scope") + ip = module.params.get("ip") + + aci = ACIModule(module) + aci.construct_url( + root_class=dict( + aci_class="fvTenant", + aci_rn="tn-{0}".format(tenant), + module_object=tenant, + target_filter={"name": tenant}, + ), + subclass_1=dict( + aci_class="fvCtx", + aci_rn="ctx-{0}".format(vrf), + module_object=vrf, + target_filter={"name": vrf}, + ), + subclass_2=dict( + aci_class="leakRoutes", + aci_rn="leakroutes", + module_object=True, + ), + subclass_3=dict( + aci_class="leakInternalSubnet", + aci_rn="leakintsubnet-[{0}]".format(ip), + module_object=ip, + target_filter={"ip": ip}, + ), + child_classes=["leakTo"], + ) + + aci.get_existing() + + if state == "present": + child_configs = [] + + subnet_rn_list = [] + for subnet in leak_to: + subnet_rn_list.append("to-[{0}]-[{1}]".format(subnet.get("tenant"), subnet.get("vrf"))) + child_configs.append( + dict( + leakTo=dict( + attributes=dict( + ctxName=subnet.get("vrf"), + tenantName=subnet.get("tenant"), + scope=scope, + ) + ) + ) + ) + + if isinstance(aci.existing, list) and len(aci.existing) > 0: + for child in aci.existing[0].get("leakInternalSubnet", {}).get("children", {}): + child_attributes = child.get("leakTo", {}).get("attributes", {}) + if child_attributes and "to-[{0}]-[{1}]".format(child_attributes.get("tenantName"), child_attributes.get("ctxName")) not in subnet_rn_list: + child_configs.append( + dict( + leakTo=dict( + attributes=dict( + ctxName=child_attributes.get("ctxName"), + tenantName=child_attributes.get("tenantName"), + status="deleted", + ) + ) + ) + ) + + aci.payload( + aci_class="leakInternalSubnet", + class_config=dict( + descr=description, + ip=ip, + scope=scope, + nameAlias=name_alias, + ), + child_configs=child_configs, + ) + + aci.get_diff(aci_class="leakInternalSubnet") + + if aci.existing: + aci.post_config() + else: + aci.post_config(parent_class="leakRoutes") + + elif state == "absent": + aci.delete_config() + + aci.exit_json() + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/cisco/aci/tests/integration/inventory.networking b/ansible_collections/cisco/aci/tests/integration/inventory.networking index 16e3d1ee1..357f58758 100644 --- a/ansible_collections/cisco/aci/tests/integration/inventory.networking +++ b/ansible_collections/cisco/aci/tests/integration/inventory.networking @@ -3,11 +3,12 @@ cn-dmz-apic-m1-02-v42 ansible_host=173.36.219.68 aci_hostname=173.36.219.68 cn-dmz-apic-m1-03-v52 ansible_host=173.36.219.69 aci_hostname=173.36.219.69 cn-dmz-apic-m1-04-v60 ansible_host=173.36.219.70 aci_hostname=173.36.219.70 cn-dmz-apic-m1-07-v32 ansible_host=173.36.219.73 aci_hostname=173.36.219.73 -aws_cloud ansible_host=52.52.20.121 aci_hostname=52.52.20.121 aci_password="sJ94G92#8dq2hx*K4qh" cloud_type=aws region=us-east-1 region_2=us-west-1 availability_zone=us-west-1a -azure_cloud ansible_host=20.245.236.136 aci_hostname=20.245.236.136 aci_password="sJ94G92#8dq2hx*K4qh" cloud_type=azure region=westus region_2=westus2 vnet_gateway=true +aws_cloud ansible_host=52.52.20.121 aci_hostname=52.52.20.121 cloud_type=aws region=us-east-1 region_2=us-west-1 availability_zone=us-west-1a +azure_cloud ansible_host=20.245.236.136 aci_hostname=20.245.236.136 cloud_type=azure region=westus region_2=westus2 vnet_gateway=true [aci:vars] aci_username=ansible_github_ci aci_password="sJ94G92#8dq2hx*K4qh" +ansible_network_os=cisco.aci.aci ansible_connection=local ansible_python_interpreter=/usr/bin/python3.9 diff --git a/ansible_collections/cisco/aci/tests/integration/targets/aci_aaa_user/tasks/main.yml b/ansible_collections/cisco/aci/tests/integration/targets/aci_aaa_user/tasks/main.yml index 56de63830..52f3ed0fb 100644 --- a/ansible_collections/cisco/aci/tests/integration/targets/aci_aaa_user/tasks/main.yml +++ b/ansible_collections/cisco/aci/tests/integration/targets/aci_aaa_user/tasks/main.yml @@ -8,23 +8,14 @@ msg: 'Please define the following variables: aci_hostname, aci_username and aci_password.' when: aci_hostname is not defined or aci_username is not defined or aci_password is not defined - -# CLEAN ENVIRONMENT -- name: Remove any pre-existing user - cisco.aci.aci_aaa_user: &user_absent - host: '{{ aci_hostname }}' - username: '{{ aci_username }}' - password: '{{ aci_password }}' - validate_certs: '{{ aci_validate_certs | default(false) }}' - use_ssl: '{{ aci_use_ssl | default(true) }}' - use_proxy: '{{ aci_use_proxy | default(true) }}' - output_level: '{{ aci_output_level | default("info") }}' - aaa_user: ansible +- name: Uninstall dateutil for error checking + ansible.builtin.pip: &dateutil + name: python-dateutil state: absent - + ignore_errors: true # ignore errors to because of multiple executions for hosts at the same time # ADD USER -- name: Add user (check_mode) +- name: Add user without dateutil installed (error) cisco.aci.aci_aaa_user: &user_present host: '{{ aci_hostname }}' username: '{{ aci_username }}' @@ -42,6 +33,36 @@ first_name: Test last_name: User phone: 1-234-555-678 + ignore_errors: true + register: err_no_python_dateutil + +- name: Verify add user + ansible.builtin.assert: + that: + - err_no_python_dateutil.msg == "dateutil required for this module" + +- name: Install dateutil + ansible.builtin.pip: + <<: *dateutil + state: present + ignore_errors: true # ignore errors to because of multiple executions for hosts at the same time + +# CLEAN ENVIRONMENT +- name: Remove any pre-existing user + cisco.aci.aci_aaa_user: &user_absent + host: '{{ aci_hostname }}' + username: '{{ aci_username }}' + password: '{{ aci_password }}' + validate_certs: '{{ aci_validate_certs | default(false) }}' + use_ssl: '{{ aci_use_ssl | default(true) }}' + use_proxy: '{{ aci_use_proxy | default(true) }}' + output_level: '{{ aci_output_level | default("info") }}' + aaa_user: ansible + state: absent + +# ADD USER +- name: Add user (check_mode) + cisco.aci.aci_aaa_user: *user_present check_mode: true register: cm_add_user @@ -49,7 +70,8 @@ - name: Add user (normal mode) cisco.aci.aci_aaa_user: <<: *user_present - aaa_password: 12!Ab:cD!34 + # changed password because password strength check is triggered on the 6.0(2h) version, thus failing testcase + aaa_password: khsaubf@3ijsdn!!2#%nbsh register: nm_add_user - name: Add user again (check mode) @@ -72,7 +94,6 @@ - nm_add_user_again is not changed - nm_add_user_again.current.0.aaaUser.attributes.descr == 'Ansible test user' - # MODIFY USER - name: Modify user (check_mode) cisco.aci.aci_aaa_user: &user_changed @@ -92,6 +113,13 @@ check_mode: true register: cm_modify_user +- name: Error wrong expiration (error) + cisco.aci.aci_aaa_user: + <<: *user_changed + expiration: wrong_expiration + ignore_errors: true + register: err_modify_user_expiration + - name: Modify user (normal mode) cisco.aci.aci_aaa_user: *user_changed register: nm_modify_user @@ -109,6 +137,7 @@ assert: that: - cm_modify_user is changed + - err_modify_user_expiration.msg == "Failed to parse date format 'wrong_expiration', Unknown string format{{":"}} wrong_expiration" - nm_modify_user is changed - nm_modify_user.current.0.aaaUser.attributes.descr == 'Ansible test user for integration tests' - cm_modify_user_again is not changed diff --git a/ansible_collections/cisco/aci/tests/integration/targets/aci_aaa_user_certificate/tasks/main.yml b/ansible_collections/cisco/aci/tests/integration/targets/aci_aaa_user_certificate/tasks/main.yml index 9d300e4d7..3140816e0 100644 --- a/ansible_collections/cisco/aci/tests/integration/targets/aci_aaa_user_certificate/tasks/main.yml +++ b/ansible_collections/cisco/aci/tests/integration/targets/aci_aaa_user_certificate/tasks/main.yml @@ -300,13 +300,13 @@ state: present - name: Remove user ansible_test - aci_aaa_user: - <<: *aci_info - aaa_user: ansible_test - state: absent + cisco.aci.aci_aaa_user: + <<: *aci_info + aaa_user: ansible_test + state: absent - name: Add user ansible_test - aci_aaa_user: + cisco.aci.aci_aaa_user: <<: *aci_info aaa_user: ansible_test aaa_password: ansible_5351 @@ -315,7 +315,7 @@ state: present - name: Add user certificate - aci_aaa_user_certificate: + cisco.aci.aci_aaa_user_certificate: <<: *aci_info aaa_user: ansible_test name: test @@ -323,10 +323,8 @@ state: present - name: Query test certificate - aci_aaa_user_certificate: - host: "{{ aci_hostname }}" - username: "{{ aci_username }}" - validate_certs: '{{ aci_validate_certs | default(false) }}' + cisco.aci.aci_aaa_user_certificate: + <<: *aci_info certificate_name: admin private_key: '{{ role_path }}/pki/admin.key' aaa_user: ansible_test @@ -334,13 +332,75 @@ state: query register: query_test +- name: Query test certificate with a private key file and no certificate name + cisco.aci.aci_aaa_user_certificate: + <<: *aci_info + private_key: '{{ role_path }}/pki/admin.key' + aaa_user: ansible_test + name: test + state: query + register: query_test_pk_file + +- name: Query test certificate with a non existent key + cisco.aci.aci_aaa_user_certificate: + <<: *aci_info + private_key: '{{ role_path }}/pki/non_existent.key' + aaa_user: ansible_test + name: test + state: query + register: query_test_non_existent_key + ignore_errors: true + +- name: Query test certificate with private key content and no certificate name present + cisco.aci.aci_aaa_user_certificate: + <<: *aci_info + private_key: | + -----BEGIN PRIVATE KEY----- + MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBAKIRv+2sbbewm0mj + D+6/tpoUymzYIdFsN+gu02teIr/lZi8ipEB514pyhoaerstzboPteWvniLuwq4KQ + VTEHgoln7J8EaHCnECViGA61pVx8RkJ99cmCkepspROw3I96zBcm58oXs6+Q/BnD + /OWET5sBvR9oTv9GNRVJ1rvSMAEJAgMBAAECgYByu3QO0qF9h7X3JEu0Ld4cKBnB + giQ2uJC/et7KxIJ/LOvw9GopBthyt27KwG1ntBkJpkTuAaQHkyNns7vLkNB0S0IR + +owVFEcKYq9VCHTaiQU8TDp24gN+yPTrpRuH8YhDVq5SfVdVuTMgHVQdj4ya4VlF + Gj+a7+ipxtGiLsVGrQJBAM7p0Fm0xmzi+tBOASUAcVrPLcteFIaTBFwfq16dm/ON + 00Khla8Et5kMBttTbqbukl8mxFjBEEBlhQqb6EdQQ0sCQQDIhHx1a9diG7y/4DQA + 4KvR3FCYwP8PBORlSamegzCo+P1OzxiEo0amX7yQMA5UyiP/kUsZrme2JBZgna8S + p4R7AkEAr7rMhSOPUnMD6V4WgsJ5g1Jp5kqkzBaYoVUUSms5RASz4+cwJVCwTX91 + Y1jcpVIBZmaaY3a0wrx13ajEAa0dOQJBAIpjnb4wqpsEh7VpmJqOdSdGxb1XXfFQ + sA0T1OQYqQnFppWwqrxIL+d9pZdiA1ITnNqyvUFBNETqDSOrUHwwb2cCQGArE+vu + ffPUWQ0j+fiK+covFG8NL7H+26NSGB5+Xsn9uwOGLj7K/YT6CbBtr9hJiuWjM1Al + 0V4ltlTuu2mTMaw= + -----END PRIVATE KEY----- + aaa_user: ansible_test + name: test + state: query + register: query_test_pk_content + - name: Verify query_test assert: that: - query_test is not changed + - query_test_pk_file is not changed + - '"Provided private key ******** does not appear to be a private key or provided file does not exist." in query_test_non_existent_key.msg' + - query_test_pk_content is not changed + +# Cleanup environment +- name: Remove test certificate + cisco.aci.aci_aaa_user_certificate: + <<: *aci_info + aaa_user: ansible_test + name: test + state: absent - name: Remove user to clean environment for next test on ci - aci_aaa_user: + cisco.aci.aci_aaa_user: <<: *aci_info aaa_user: ansible_test state: absent + +- name: Remove test certificate + cisco.aci.aci_aaa_user_certificate: + <<: *aci_info + aaa_user: '{{ aci_username }}' + name: admin + state: absent diff --git a/ansible_collections/cisco/aci/tests/integration/targets/aci_access_span_src_group/aliases b/ansible_collections/cisco/aci/tests/integration/targets/aci_access_span_src_group/aliases new file mode 100644 index 000000000..209b793f9 --- /dev/null +++ b/ansible_collections/cisco/aci/tests/integration/targets/aci_access_span_src_group/aliases @@ -0,0 +1,2 @@ +# No ACI simulator yet, so not enabled +# unsupported diff --git a/ansible_collections/cisco/aci/tests/integration/targets/aci_access_span_src_group/tasks/main.yml b/ansible_collections/cisco/aci/tests/integration/targets/aci_access_span_src_group/tasks/main.yml new file mode 100644 index 000000000..e2d00e525 --- /dev/null +++ b/ansible_collections/cisco/aci/tests/integration/targets/aci_access_span_src_group/tasks/main.yml @@ -0,0 +1,334 @@ +# Test code for the ACI modules +# Copyright: (c) 2023, Akini Ross (@akinross) + +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +- name: Test that we have an ACI APIC host, ACI username and ACI password + fail: + msg: 'Please define the following variables: aci_hostname, aci_username and aci_password.' + when: aci_hostname is not defined or aci_username is not defined or aci_password is not defined + +- name: Set vars + set_fact: + aci_info: &aci_info + host: "{{ aci_hostname }}" + username: "{{ aci_username }}" + password: "{{ aci_password }}" + validate_certs: '{{ aci_validate_certs | default(false) }}' + use_ssl: '{{ aci_use_ssl | default(true) }}' + use_proxy: '{{ aci_use_proxy | default(true) }}' + output_level: '{{ aci_output_level | default("info") }}' + +- name: Verify Cloud and Non-Cloud Sites in use. + include_tasks: ../../../../../../integration/targets/aci_cloud_provider/tasks/main.yml + +# CLEAN TEST ENVIRONMENT BEFORE TESTS + +- name: Query all span source groups ( clean before ) + cisco.aci.aci_access_span_src_group: + <<: *aci_info + state: query + register: query_for_clean + +- name: Clean access span source groups ( clean before ) + cisco.aci.aci_access_span_src_group: + <<: *aci_info + source_group: "{{ item.spanSrcGrp.attributes.name }}" + state: absent + loop: "{{ query_for_clean.current }}" + +- name: Query all span destination groups ( clean before ) + cisco.aci.aci_access_span_dst_group: + <<: *aci_info + state: query + register: query_for_clean + +- name: Clean access span destination groups ( clean before ) + cisco.aci.aci_access_span_dst_group: + <<: *aci_info + destination_group: "{{ item.spanDestGrp.attributes.name }}" + state: absent + loop: "{{ query_for_clean.current }}" + +- name: Execute tasks only for non-cloud sites ( clean before ) + when: + - query_cloud.current == [] + block: + - name: Query all access span filter groups ( clean before ) + cisco.aci.aci_access_span_filter_group: + <<: *aci_info + state: query + register: query_for_clean + + - name: Clean access span filter groups ( clean before ) + cisco.aci.aci_access_span_filter_group: + <<: *aci_info + filter_group: "{{ item.spanFilterGrp.attributes.name }}" + state: absent + loop: "{{ query_for_clean.current }}" + + - name: Add a access span filter groups ( clean before ) + cisco.aci.aci_access_span_filter_group: + <<: *aci_info + filter_group: "{{ item }}" + state: present + loop: + - span_filter_group_1 + - span_filter_group_2 + +- name: Add access span destination group type epg ( clean before ) + cisco.aci.aci_access_span_dst_group: + <<: *aci_info + destination_group: span_dest_1 + description: test span epg + destination_epg: + destination_ip: 10.0.0.1 + source_ip: 10.0.2.1 + tenant: ansible_test + ap: ansible_test + epg: ansible_test + state: present + +- name: Add access span destination group type access interface port ( clean before ) + cisco.aci.aci_access_span_dst_group: + <<: *aci_info + destination_group: span_dest_2 + description: test span access interface port + access_interface: + pod: 1 + node: 101 + path: eth1/1 + mtu: 1500 + state: present + +- name: Add access span destination group type access interface direct port channel ( clean before ) + cisco.aci.aci_access_span_dst_group: + <<: *aci_info + destination_group: span_dest_3 + description: test span access interface port channel + access_interface: + pod: 1 + node: 101 + path: test-PolGrp + state: present + +# TEST CREATE SOURCE GROUPS + +- name: Add access span source group 1 ( checkmode ) + cisco.aci.aci_access_span_src_group: &span_src_1 + <<: *aci_info + source_group: span_src_1 + destination_group: span_dest_1 + filter_group: span_filter_group_1 + state: present + check_mode: true + register: cm_span_src_1 + +- name: Add access span source group 1 + cisco.aci.aci_access_span_src_group: + <<: *span_src_1 + register: nm_span_src_1 + +- name: Add access span source group 1 again + cisco.aci.aci_access_span_src_group: + <<: *span_src_1 + register: nm_span_src_1_again + +- name: Verify add access span source group 1 + ansible.builtin.assert: + that: + - cm_span_src_1 is changed + - cm_span_src_1.previous == [] + - cm_span_src_1.current == [] + - cm_span_src_1.proposed.spanSrcGrp.attributes.name == "span_src_1" + - cm_span_src_1.proposed.spanSrcGrp.children.0.spanSpanLbl.attributes.name == "span_dest_1" + - cm_span_src_1.proposed.spanSrcGrp.children.1.spanRsSrcGrpToFilterGrp.attributes.tDn == "uni/infra/filtergrp-span_filter_group_1" + - nm_span_src_1 is changed + - nm_span_src_1.previous == [] + - nm_span_src_1.current.0.spanSrcGrp.attributes.name == "span_src_1" + - nm_span_src_1.current.0.spanSrcGrp.attributes.descr == "" + - nm_span_src_1.current.0.spanSrcGrp.attributes.adminSt == "enabled" + - nm_span_src_1.current.0.spanSrcGrp.children.1.spanSpanLbl.attributes.name == "span_dest_1" + - nm_span_src_1.current.0.spanSrcGrp.children.0.spanRsSrcGrpToFilterGrp.attributes.tDn == "uni/infra/filtergrp-span_filter_group_1" + - nm_span_src_1_again is not changed + - nm_span_src_1_again.previous.0.spanSrcGrp.attributes.name == "span_src_1" + - nm_span_src_1_again.previous.0.spanSrcGrp.attributes.descr == "" + - nm_span_src_1_again.previous.0.spanSrcGrp.attributes.adminSt == "enabled" + - nm_span_src_1_again.previous.0.spanSrcGrp.children.1.spanSpanLbl.attributes.name == "span_dest_1" + - nm_span_src_1_again.previous.0.spanSrcGrp.children.0.spanRsSrcGrpToFilterGrp.attributes.tDn == "uni/infra/filtergrp-span_filter_group_1" + - nm_span_src_1_again.current.0.spanSrcGrp.attributes.name == "span_src_1" + - nm_span_src_1_again.current.0.spanSrcGrp.attributes.descr == "" + - nm_span_src_1_again.current.0.spanSrcGrp.attributes.adminSt == "enabled" + - nm_span_src_1_again.current.0.spanSrcGrp.children.1.spanSpanLbl.attributes.name == "span_dest_1" + - nm_span_src_1_again.current.0.spanSrcGrp.children.0.spanRsSrcGrpToFilterGrp.attributes.tDn == "uni/infra/filtergrp-span_filter_group_1" + +- name: Change access span source group 1 + cisco.aci.aci_access_span_src_group: &change_span_src_1 + <<: *span_src_1 + destination_group: span_dest_2 + filter_group: span_filter_group_2 + description: "changed description" + admin_state: false + register: nm_changed_span_src_1 + +- name: Change access span source group 1 again + cisco.aci.aci_access_span_src_group: + <<: *change_span_src_1 + register: nm_changed_span_src_1_again + +- name: Change access span source group 1 remove filter group + cisco.aci.aci_access_span_src_group: + <<: *aci_info + source_group: span_src_1 + destination_group: span_dest_2 + register: nm_changed_span_src_1_remove_filter_group + +- name: Verify changed access span source group 1 + ansible.builtin.assert: + that: + - nm_changed_span_src_1 is changed + - nm_changed_span_src_1.previous.0.spanSrcGrp.attributes.descr == "" + - nm_changed_span_src_1.previous.0.spanSrcGrp.attributes.adminSt == "enabled" + - nm_changed_span_src_1.previous.0.spanSrcGrp.children.1.spanSpanLbl.attributes.name == "span_dest_1" + - nm_changed_span_src_1.previous.0.spanSrcGrp.children.0.spanRsSrcGrpToFilterGrp.attributes.tDn == "uni/infra/filtergrp-span_filter_group_1" + - nm_changed_span_src_1.current.0.spanSrcGrp.attributes.descr == "changed description" + - nm_changed_span_src_1.current.0.spanSrcGrp.attributes.adminSt == "disabled" + - nm_changed_span_src_1.current.0.spanSrcGrp.children.1.spanSpanLbl.attributes.name == "span_dest_2" + - nm_changed_span_src_1.current.0.spanSrcGrp.children.0.spanRsSrcGrpToFilterGrp.attributes.tDn == "uni/infra/filtergrp-span_filter_group_2" + - nm_changed_span_src_1_again is not changed + - nm_changed_span_src_1_again.previous.0.spanSrcGrp.attributes.descr == "changed description" + - nm_changed_span_src_1_again.previous.0.spanSrcGrp.attributes.adminSt == "disabled" + - nm_changed_span_src_1_again.previous.0.spanSrcGrp.children.1.spanSpanLbl.attributes.name == "span_dest_2" + - nm_changed_span_src_1_again.previous.0.spanSrcGrp.children.0.spanRsSrcGrpToFilterGrp.attributes.tDn == "uni/infra/filtergrp-span_filter_group_2" + - nm_changed_span_src_1_again.current.0.spanSrcGrp.attributes.descr == "changed description" + - nm_changed_span_src_1_again.current.0.spanSrcGrp.attributes.adminSt == "disabled" + - nm_changed_span_src_1_again.current.0.spanSrcGrp.children.1.spanSpanLbl.attributes.name == "span_dest_2" + - nm_changed_span_src_1_again.current.0.spanSrcGrp.children.0.spanRsSrcGrpToFilterGrp.attributes.tDn == "uni/infra/filtergrp-span_filter_group_2" + - nm_changed_span_src_1_remove_filter_group is changed + - nm_changed_span_src_1_remove_filter_group.current.0.spanSrcGrp.children | length == 1 + - nm_changed_span_src_1_remove_filter_group.current.0.spanSrcGrp.children.0.spanSpanLbl.attributes.name == "span_dest_2" + +- name: Add access span source group 2 with admin_state to false + cisco.aci.aci_access_span_src_group: + <<: *change_span_src_1 + source_group: span_src_2 + destination_group: span_dest_2 + admin_state: false + register: nm_changed_span_src_2_admin_state_false + +- name: Add another access span source group 3 with admin_state to true + cisco.aci.aci_access_span_src_group: + <<: *change_span_src_1 + source_group: span_src_3 + destination_group: span_dest_3 + admin_state: true + register: nm_changed_span_src_3_admin_state_true + +- name: Verify admin_state sets for access span source group 2 and 3 + ansible.builtin.assert: + that: + - nm_changed_span_src_2_admin_state_false is changed + - nm_changed_span_src_2_admin_state_false.current.0.spanSrcGrp.attributes.adminSt == "disabled" + - nm_changed_span_src_3_admin_state_true is changed + - nm_changed_span_src_3_admin_state_true.current.0.spanSrcGrp.attributes.adminSt == "enabled" + +# TEST QUERY SOURCE GROUPS + +- name: Query span source group span_src_1 + cisco.aci.aci_access_span_src_group: + <<: *change_span_src_1 + state: query + register: query_one + +- name: Query all span source groups + cisco.aci.aci_access_span_src_group: + <<: *aci_info + state: query + register: query_all + +- name: Verify querying access span destination groups + ansible.builtin.assert: + that: + - query_one is not changed + - query_one.current | length == 1 + - query_one.current.0.spanSrcGrp.attributes.name == "span_src_1" + - query_all is not changed + - query_all.current | length >= 3 + +# TEST REMOVAL SOURCE GROUPS + +- name: Remove span source group ( checkmode ) + cisco.aci.aci_access_span_src_group: &remove_span_src_1 + <<: *change_span_src_1 + state: absent + check_mode: true + register: cm_remove_span_src_1 + +- name: Remove span source group + cisco.aci.aci_access_span_src_group: + <<: *remove_span_src_1 + register: nm_remove_span_src_1 + +- name: Remove span source group again + cisco.aci.aci_access_span_src_group: + <<: *remove_span_src_1 + register: nm_remove_span_src_1_again + +- name: Verify remove access span destination groups + ansible.builtin.assert: + that: + - cm_remove_span_src_1 is changed + - cm_remove_span_src_1.current | length == 1 + - cm_remove_span_src_1.previous | length == 1 + - cm_remove_span_src_1.proposed == {} + - nm_remove_span_src_1 is changed + - nm_remove_span_src_1.current == [] + - nm_remove_span_src_1.previous | length == 1 + - nm_remove_span_src_1_again is not changed + - nm_remove_span_src_1_again.current == [] + - nm_remove_span_src_1_again.previous == [] + +# CLEAN TEST ENVIRONMENT AFTER TESTS + +- name: Query all span source groups ( clean after ) + cisco.aci.aci_access_span_src_group: + <<: *aci_info + state: query + register: query_for_clean + +- name: Clean access span source groups ( clean after ) + cisco.aci.aci_access_span_src_group: + <<: *aci_info + source_group: "{{ item.spanSrcGrp.attributes.name }}" + state: absent + loop: "{{ query_for_clean.current }}" + +- name: Query all span destination groups ( clean after ) + cisco.aci.aci_access_span_dst_group: + <<: *aci_info + state: query + register: query_for_clean + +- name: Clean access span destination groups ( clean after ) + cisco.aci.aci_access_span_dst_group: + <<: *aci_info + destination_group: "{{ item.spanDestGrp.attributes.name }}" + state: absent + loop: "{{ query_for_clean.current }}" + +- name: Execute tasks only for non-cloud sites ( clean after ) + when: + - query_cloud.current == [] + block: + - name: Query all access span filter groups ( clean after ) + cisco.aci.aci_access_span_filter_group: + <<: *aci_info + state: query + register: query_for_clean + + - name: Clean access span filter groups ( clean after ) + cisco.aci.aci_access_span_filter_group: + <<: *aci_info + filter_group: "{{ item.spanFilterGrp.attributes.name }}" + state: absent + loop: "{{ query_for_clean.current }}" diff --git a/ansible_collections/cisco/aci/tests/integration/targets/aci_access_span_src_group_src/aliases b/ansible_collections/cisco/aci/tests/integration/targets/aci_access_span_src_group_src/aliases new file mode 100644 index 000000000..209b793f9 --- /dev/null +++ b/ansible_collections/cisco/aci/tests/integration/targets/aci_access_span_src_group_src/aliases @@ -0,0 +1,2 @@ +# No ACI simulator yet, so not enabled +# unsupported diff --git a/ansible_collections/cisco/aci/tests/integration/targets/aci_access_span_src_group_src/tasks/main.yml b/ansible_collections/cisco/aci/tests/integration/targets/aci_access_span_src_group_src/tasks/main.yml new file mode 100644 index 000000000..a0fe48f08 --- /dev/null +++ b/ansible_collections/cisco/aci/tests/integration/targets/aci_access_span_src_group_src/tasks/main.yml @@ -0,0 +1,532 @@ +# Test code for the ACI modules +# Copyright: (c) 2023, Akini Ross (@akinross) + +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +- name: Test that we have an ACI APIC host, ACI username and ACI password + fail: + msg: 'Please define the following variables: aci_hostname, aci_username and aci_password.' + when: aci_hostname is not defined or aci_username is not defined or aci_password is not defined + +- name: Set vars + set_fact: + aci_info: &aci_info + host: "{{ aci_hostname }}" + username: "{{ aci_username }}" + password: "{{ aci_password }}" + validate_certs: '{{ aci_validate_certs | default(false) }}' + use_ssl: '{{ aci_use_ssl | default(true) }}' + use_proxy: '{{ aci_use_proxy | default(true) }}' + output_level: '{{ aci_output_level | default("info") }}' + +- name: Verify Cloud and Non-Cloud Sites in use. + include_tasks: ../../../../../../integration/targets/aci_cloud_provider/tasks/main.yml + +# CLEAN TEST ENVIRONMENT BEFORE TESTS + +- name: Query all span source groups ( clean before ) + cisco.aci.aci_access_span_src_group: + <<: *aci_info + state: query + register: query_for_clean + +- name: Clean access span source groups ( clean before ) + cisco.aci.aci_access_span_src_group: + <<: *aci_info + source_group: "{{ item.spanSrcGrp.attributes.name }}" + state: absent + loop: "{{ query_for_clean.current }}" + +- name: Query all span destination groups ( clean before ) + cisco.aci.aci_access_span_dst_group: + <<: *aci_info + state: query + register: query_for_clean + +- name: Clean access span destination groups ( clean before ) + cisco.aci.aci_access_span_dst_group: + <<: *aci_info + destination_group: "{{ item.spanDestGrp.attributes.name }}" + state: absent + loop: "{{ query_for_clean.current }}" + +- name: Execute tasks only for non-cloud sites ( clean before ) + when: + - query_cloud.current == [] + block: + - name: Query all access span filter groups ( clean before ) + cisco.aci.aci_access_span_filter_group: + <<: *aci_info + state: query + register: query_for_clean + + - name: Clean access span filter groups ( clean before ) + cisco.aci.aci_access_span_filter_group: + <<: *aci_info + filter_group: "{{ item.spanFilterGrp.attributes.name }}" + state: absent + loop: "{{ query_for_clean.current }}" + + - name: Add a access span filter groups ( clean before ) + cisco.aci.aci_access_span_filter_group: + <<: *aci_info + filter_group: "{{ item }}" + state: present + loop: + - span_filter_group_1 + - span_filter_group_2 + +- name: Add access span destination group type epg ( clean before ) + cisco.aci.aci_access_span_dst_group: + <<: *aci_info + destination_group: span_dest_1 + description: test span epg + destination_epg: + destination_ip: 10.0.0.1 + source_ip: 10.0.2.1 + tenant: ansible_test + ap: ansible_test + epg: ansible_test + state: present + +- name: Add access span source group 1 ( clean before ) + cisco.aci.aci_access_span_src_group: + <<: *aci_info + source_group: "{{ item }}" + destination_group: span_dest_1 + state: present + loop: + - span_src_group_1 + - span_src_group_2 + - span_src_group_3 + +# TEST CREATE SOURCE + +- name: Add access span source 1 ( checkmode ) + cisco.aci.aci_access_span_src_group_src: &span_src_1 + <<: *aci_info + source_group: span_src_group_1 + filter_group: span_filter_group_1 + source: span_src_1 + state: present + check_mode: true + register: cm_span_src_1 + +- name: Add access span source 1 + cisco.aci.aci_access_span_src_group_src: + <<: *span_src_1 + register: nm_span_src_1 + +- name: Add access span source 1 again + cisco.aci.aci_access_span_src_group_src: + <<: *span_src_1 + register: nm_span_src_1_again + +- name: Verify add access span source group 1 + ansible.builtin.assert: + that: + - cm_span_src_1 is changed + - cm_span_src_1.previous == [] + - cm_span_src_1.current == [] + - cm_span_src_1.proposed.spanSrc.attributes.name == "span_src_1" + - cm_span_src_1.proposed.spanSrc.attributes.dn == "uni/infra/srcgrp-span_src_group_1/src-span_src_1" + - cm_span_src_1.proposed.spanSrc.children.0.spanRsSrcToFilterGrp.attributes.tDn == "uni/infra/filtergrp-span_filter_group_1" + - nm_span_src_1 is changed + - nm_span_src_1.previous == [] + - nm_span_src_1.current.0.spanSrc.attributes.name == "span_src_1" + - nm_span_src_1.current.0.spanSrc.attributes.descr == "" + - nm_span_src_1.current.0.spanSrc.attributes.dir == "both" + - nm_span_src_1.current.0.spanSrc.attributes.dn == "uni/infra/srcgrp-span_src_group_1/src-span_src_1" + - nm_span_src_1.current.0.spanSrc.children.0.spanRsSrcToFilterGrp.attributes.tDn == "uni/infra/filtergrp-span_filter_group_1" + - nm_span_src_1_again is not changed + - nm_span_src_1_again.previous.0.spanSrc.attributes.name == "span_src_1" + - nm_span_src_1_again.previous.0.spanSrc.attributes.descr == "" + - nm_span_src_1_again.previous.0.spanSrc.attributes.dir == "both" + - nm_span_src_1_again.previous.0.spanSrc.attributes.dn == "uni/infra/srcgrp-span_src_group_1/src-span_src_1" + - nm_span_src_1_again.previous.0.spanSrc.children.0.spanRsSrcToFilterGrp.attributes.tDn == "uni/infra/filtergrp-span_filter_group_1" + - nm_span_src_1_again.current.0.spanSrc.attributes.name == "span_src_1" + - nm_span_src_1_again.current.0.spanSrc.attributes.descr == "" + - nm_span_src_1_again.current.0.spanSrc.attributes.dir == "both" + - nm_span_src_1_again.current.0.spanSrc.attributes.dn == "uni/infra/srcgrp-span_src_group_1/src-span_src_1" + - nm_span_src_1_again.current.0.spanSrc.children.0.spanRsSrcToFilterGrp.attributes.tDn == "uni/infra/filtergrp-span_filter_group_1" + +- name: Change access span source group 1 + cisco.aci.aci_access_span_src_group_src: &change_span_src_1 + <<: *span_src_1 + source: span_src_1 + filter_group: span_filter_group_2 + description: "changed description" + direction: both + register: nm_changed_span_src_1 + +- name: Change access span source group 1 direction incoming + cisco.aci.aci_access_span_src_group_src: + <<: *change_span_src_1 + direction: incoming + register: nm_changed_span_src_1_incoming + +- name: Change access span source group 1 direction outgoing + cisco.aci.aci_access_span_src_group_src: &change_span_src_1_again + <<: *change_span_src_1 + direction: outgoing + register: nm_changed_span_src_1_outgoing + +- name: Change access span source group 1 again + cisco.aci.aci_access_span_src_group_src: + <<: *change_span_src_1_again + register: nm_changed_span_src_1_again + +- name: Verify changed access span source group 1 + ansible.builtin.assert: + that: + - nm_changed_span_src_1 is changed + - nm_changed_span_src_1.previous.0.spanSrc.attributes.name == "span_src_1" + - nm_changed_span_src_1.previous.0.spanSrc.attributes.descr == "" + - nm_changed_span_src_1.previous.0.spanSrc.attributes.dir == "both" + - nm_changed_span_src_1.previous.0.spanSrc.attributes.dn == "uni/infra/srcgrp-span_src_group_1/src-span_src_1" + - nm_changed_span_src_1.previous.0.spanSrc.children.0.spanRsSrcToFilterGrp.attributes.tDn == "uni/infra/filtergrp-span_filter_group_1" + - nm_changed_span_src_1.current.0.spanSrc.attributes.name == "span_src_1" + - nm_changed_span_src_1.current.0.spanSrc.attributes.descr == "changed description" + - nm_changed_span_src_1.current.0.spanSrc.attributes.dir == "both" + - nm_changed_span_src_1.current.0.spanSrc.attributes.dn == "uni/infra/srcgrp-span_src_group_1/src-span_src_1" + - nm_changed_span_src_1.current.0.spanSrc.children.0.spanRsSrcToFilterGrp.attributes.tDn == "uni/infra/filtergrp-span_filter_group_2" + - nm_changed_span_src_1_incoming.current.0.spanSrc.attributes.dir == "in" + - nm_changed_span_src_1_outgoing.current.0.spanSrc.attributes.dir == "out" + - nm_changed_span_src_1_again is not changed + +- name: Add access span source 2 of epg type + cisco.aci.aci_access_span_src_group_src: &span_src_epg + <<: *aci_info + source_group: span_src_group_2 + epg: + tenant: tenant1 + ap: ap1 + epg: epg1 + source: span_src_2 + state: present + register: nm_span_src_2 + +- name: Change access span source 2 of epg type + cisco.aci.aci_access_span_src_group_src: + <<: *span_src_epg + epg: + tenant: tenant1 + ap: ap1 + epg: epg2 + register: nm_changed_span_src_2 + +- name: Change access span source 2 of epg type to none type + cisco.aci.aci_access_span_src_group_src: + <<: *aci_info + source_group: span_src_group_2 + source: span_src_2 + state: present + register: nm_changed_span_src_2_type_none + +- name: Add access span source 3 of routed_outside type + cisco.aci.aci_access_span_src_group_src: &span_src_routed_outside + <<: *aci_info + source_group: span_src_group_3 + routed_outside: + tenant: tenant1 + l3out: l3out1 + encap: 1089 + source: span_src_3 + state: present + register: nm_span_src_3 + +- name: Add access span source 3 of routed_outside type without l3out + cisco.aci.aci_access_span_src_group_src: + <<: *span_src_routed_outside + routed_outside: + tenant: tenant1 + encap: 1089 + state: present + register: nm_changed_span_src_3_no_l3out + +- name: Add access span source 3 of routed_outside type without encap + cisco.aci.aci_access_span_src_group_src: + <<: *span_src_routed_outside + routed_outside: + tenant: tenant1 + l3out: l3out1 + state: present + register: nm_changed_span_src_3_no_encap + +- name: Add access span source 3 of routed_outside type without tenant + cisco.aci.aci_access_span_src_group_src: + <<: *span_src_routed_outside + routed_outside: + l3out: l3out1 + encap: 1089 + state: present + register: nm_changed_span_src_3_no_tenant + +- name: Change access span source 3 of routed_outside type + cisco.aci.aci_access_span_src_group_src: + <<: *span_src_routed_outside + routed_outside: + tenant: tenant1 + l3out: l3out2 + encap: 1090 + register: nm_changed_span_src_3 + +- name: Change access span source 3 of routed_outside type to epg type + cisco.aci.aci_access_span_src_group_src: + <<: *aci_info + source_group: span_src_group_3 + source: span_src_3 + epg: + tenant: tenant1 + ap: ap1 + epg: epg3 + state: present + register: nm_changed_span_src_3_type_epg + +- name: Verify changed access span source types group 2 and 3 + ansible.builtin.assert: + that: + - nm_span_src_2 is changed + - nm_span_src_2.current.0.spanSrc.attributes.name == "span_src_2" + - nm_span_src_2.current.0.spanSrc.children.0.spanRsSrcToEpg.attributes.tDn == "uni/tn-tenant1/ap-ap1/epg-epg1" + - nm_changed_span_src_2 is changed + - nm_changed_span_src_2.current.0.spanSrc.attributes.name == "span_src_2" + - nm_changed_span_src_2.current.0.spanSrc.children.0.spanRsSrcToEpg.attributes.tDn == "uni/tn-tenant1/ap-ap1/epg-epg2" + - nm_changed_span_src_2_type_none is changed + - nm_changed_span_src_2_type_none.previous.0.spanSrc.children.0.spanRsSrcToEpg.attributes.tDn == "uni/tn-tenant1/ap-ap1/epg-epg2" + - '"children" not in nm_changed_span_src_2_type_none.current.0.spanSrc' + - nm_span_src_3 is changed + - nm_span_src_3.current.0.spanSrc.attributes.name == "span_src_3" + - nm_span_src_3.current.0.spanSrc.children.0.spanRsSrcToL3extOut.attributes.tDn == "uni/tn-tenant1/out-l3out1" + - nm_span_src_3.current.0.spanSrc.children.0.spanRsSrcToL3extOut.attributes.encap == "vlan-1089" + - nm_changed_span_src_3 is changed + - nm_changed_span_src_3.current.0.spanSrc.attributes.name == "span_src_3" + - nm_changed_span_src_3.current.0.spanSrc.children.0.spanRsSrcToL3extOut.attributes.tDn == "uni/tn-tenant1/out-l3out2" + - nm_changed_span_src_3.current.0.spanSrc.children.0.spanRsSrcToL3extOut.attributes.encap == "vlan-1090" + - nm_changed_span_src_3_type_epg is changed + - nm_changed_span_src_3_type_epg.current.0.spanSrc.attributes.name == "span_src_3" + - nm_changed_span_src_3_type_epg.current.0.spanSrc.children.0.spanRsSrcToEpg.attributes.tDn == "uni/tn-tenant1/ap-ap1/epg-epg3" + - nm_changed_span_src_3_no_l3out is changed + - nm_changed_span_src_3_no_l3out.current.0.spanSrc.attributes.name == "span_src_3" + - nm_changed_span_src_3_no_l3out.current.0.spanSrc.children.0.spanRsSrcToL3extOut.attributes.encap == "vlan-1089" + - nm_changed_span_src_3_no_l3out.current.0.spanSrc.children.0.spanRsSrcToL3extOut.attributes.tDn == "" + - nm_changed_span_src_3_no_tenant is changed + - nm_changed_span_src_3_no_tenant.current.0.spanSrc.attributes.name == "span_src_3" + - nm_changed_span_src_3_no_tenant.current.0.spanSrc.children.0.spanRsSrcToL3extOut.attributes.encap == "vlan-1089" + - nm_changed_span_src_3_no_tenant.current.0.spanSrc.children.0.spanRsSrcToL3extOut.attributes.tDn == "" + - nm_changed_span_src_3_no_encap is changed + - nm_changed_span_src_3_no_encap.current.0.spanSrc.attributes.name == "span_src_3" + - nm_changed_span_src_3_no_encap.current.0.spanSrc.children.0.spanRsSrcToL3extOut.attributes.encap == "unknown" + - nm_changed_span_src_3_no_encap.current.0.spanSrc.children.0.spanRsSrcToL3extOut.attributes.tDn == "uni/tn-tenant1/out-l3out1" + +- name: Change access span source 2 drop_packets true + cisco.aci.aci_access_span_src_group_src: + <<: *aci_info + source_group: span_src_group_2 + source: span_src_2 + drop_packets: true + state: present + register: nm_changed_span_src_2_type_drop_packets_true + +- name: Change access span source 2 drop_packets false + cisco.aci.aci_access_span_src_group_src: + <<: *aci_info + source_group: span_src_group_2 + source: span_src_2 + drop_packets: false + state: present + register: nm_changed_span_src_2_type_drop_packets_false + +- name: Verify changed access span source group 1 drop packets + ansible.builtin.assert: + that: + - nm_changed_span_src_2_type_drop_packets_true is changed + - nm_changed_span_src_2_type_drop_packets_true.current.0.spanSrc.attributes.name == "span_src_2" + - nm_changed_span_src_2_type_drop_packets_true.current.0.spanSrc.attributes.spanOnDrop == "yes" + - nm_changed_span_src_2_type_drop_packets_false is changed + - nm_changed_span_src_2_type_drop_packets_false.current.0.spanSrc.attributes.name == "span_src_2" + - nm_changed_span_src_2_type_drop_packets_false.current.0.spanSrc.attributes.spanOnDrop == "no" + +# TEST QUERY SOURCE + +- name: Query span source group span_src_1 + cisco.aci.aci_access_span_src_group_src: + <<: *aci_info + source: span_src_1 + state: query + register: query_one + +- name: Query all span source groups + cisco.aci.aci_access_span_src_group_src: + <<: *aci_info + state: query + register: query_all + +- name: Verify querying access span sources + ansible.builtin.assert: + that: + - query_one is not changed + - query_one.current | length == 1 + - query_one.current.0.spanSrc.attributes.dn == "uni/infra/srcgrp-span_src_group_1/src-span_src_1" + - query_all is not changed + - query_all.current | length >= 3 + +# TEST ERRORS SOURCE + +- name: Add access span source group 4 + cisco.aci.aci_access_span_src_group: + <<: *aci_info + source_group: span_src_group_4 + destination_group: span_dest_1 + filter_group: span_filter_group_1 + state: present + +- name: Add access span source 4 with epg and routed_outside ( mutually exclusive error ) + cisco.aci.aci_access_span_src_group_src: + <<: *aci_info + source_group: span_src_group_4 + epg: + tenant: tenant1 + ap: ap1 + epg: epg3 + routed_outside: + tenant: tenant1 + l3out: l3out2 + encap: 1090 + source: span_src_4 + state: present + ignore_errors: true + register: err_mutually_exclusive_epg_routed_outside + +- name: Add access span source 4 with filter_group and drop_packets( drop_packets true error ) + cisco.aci.aci_access_span_src_group_src: + <<: *aci_info + source_group: span_src_group_4 + filter_group: span_filter_group_1 + source: span_src_4 + drop_packets: true + ignore_errors: true + register: err_filter_group_drop_packets + +- name: Add access span source 4 with epg and drop_packets ( drop_packets true error ) + cisco.aci.aci_access_span_src_group_src: + <<: *aci_info + source_group: span_src_group_4 + source: span_src_4 + epg: + tenant: tenant1 + ap: ap1 + epg: epg3 + drop_packets: true + ignore_errors: true + register: err_epg_drop_packets + +- name: Add access span source 4 with routed_outside and drop_packets ( drop_packets true error ) + cisco.aci.aci_access_span_src_group_src: + <<: *aci_info + source_group: span_src_group_4 + source: span_src_4 + routed_outside: + tenant: tenant1 + l3out: l3out2 + encap: 1090 + drop_packets: true + ignore_errors: true + register: err_routed_outside_drop_packets + +- name: Add access span source 4 with drop_packets true ( parent filter group config error ) + cisco.aci.aci_access_span_src_group_src: + <<: *aci_info + source_group: span_src_group_4 + source: span_src_4 + drop_packets: true + state: present + ignore_errors: true + register: err_parent_filter_and_drop_packets + +- name: Verify errors on incorrect input + ansible.builtin.assert: + that: + - err_mutually_exclusive_epg_routed_outside.msg == "parameters are mutually exclusive{{':'}} epg|routed_outside" + - err_filter_group_drop_packets.msg == "Setting 'drop_packets' to 'true' is not allowed when 'filter_group' is configured on the source." + - err_epg_drop_packets.msg == "Setting 'drop_packets' to 'true' is not allowed when 'epg' is configured on the source." + - err_routed_outside_drop_packets.msg == "Setting 'drop_packets' to 'true' is not allowed when 'routed_outside' is configured on the source." + - err_parent_filter_and_drop_packets.msg == "APIC Error 105{{':'}} Span on drop not supported for the configuration" + +# TEST REMOVAL SOURCE + +- name: Remove span source group ( checkmode ) + cisco.aci.aci_access_span_src_group_src: &remove_span_src_1 + <<: *change_span_src_1 + state: absent + check_mode: true + register: cm_remove_span_src_1 + +- name: Remove span source group + cisco.aci.aci_access_span_src_group_src: + <<: *remove_span_src_1 + register: nm_remove_span_src_1 + +- name: Remove span source group again + cisco.aci.aci_access_span_src_group_src: + <<: *remove_span_src_1 + register: nm_remove_span_src_1_again + +- name: Verify remove access span destination groups + ansible.builtin.assert: + that: + - cm_remove_span_src_1 is changed + - cm_remove_span_src_1.current | length == 1 + - cm_remove_span_src_1.previous | length == 1 + - cm_remove_span_src_1.proposed == {} + - nm_remove_span_src_1 is changed + - nm_remove_span_src_1.current == [] + - nm_remove_span_src_1.previous | length == 1 + - nm_remove_span_src_1_again is not changed + - nm_remove_span_src_1_again.current == [] + - nm_remove_span_src_1_again.previous == [] + +# CLEAN TEST ENVIRONMENT AFTER TESTS + +- name: Query all span source groups ( clean after ) + cisco.aci.aci_access_span_src_group: + <<: *aci_info + state: query + register: query_for_clean + +- name: Clean access span source groups ( clean after ) + cisco.aci.aci_access_span_src_group: + <<: *aci_info + source_group: "{{ item.spanSrcGrp.attributes.name }}" + state: absent + loop: "{{ query_for_clean.current }}" + +- name: Query all span destination groups ( clean after ) + cisco.aci.aci_access_span_dst_group: + <<: *aci_info + state: query + register: query_for_clean + +- name: Clean access span destination groups ( clean after ) + cisco.aci.aci_access_span_dst_group: + <<: *aci_info + destination_group: "{{ item.spanDestGrp.attributes.name }}" + state: absent + loop: "{{ query_for_clean.current }}" + +- name: Execute tasks only for non-cloud sites ( clean after ) + when: + - query_cloud.current == [] + block: + - name: Query all access span filter groups ( clean after ) + cisco.aci.aci_access_span_filter_group: + <<: *aci_info + state: query + register: query_for_clean + + - name: Clean access span filter groups ( clean after ) + cisco.aci.aci_access_span_filter_group: + <<: *aci_info + filter_group: "{{ item.spanFilterGrp.attributes.name }}" + state: absent + loop: "{{ query_for_clean.current }}" diff --git a/ansible_collections/cisco/aci/tests/integration/targets/aci_access_span_src_group_src_path/aliases b/ansible_collections/cisco/aci/tests/integration/targets/aci_access_span_src_group_src_path/aliases new file mode 100644 index 000000000..209b793f9 --- /dev/null +++ b/ansible_collections/cisco/aci/tests/integration/targets/aci_access_span_src_group_src_path/aliases @@ -0,0 +1,2 @@ +# No ACI simulator yet, so not enabled +# unsupported diff --git a/ansible_collections/cisco/aci/tests/integration/targets/aci_access_span_src_group_src_path/tasks/main.yml b/ansible_collections/cisco/aci/tests/integration/targets/aci_access_span_src_group_src_path/tasks/main.yml new file mode 100644 index 000000000..f370b1323 --- /dev/null +++ b/ansible_collections/cisco/aci/tests/integration/targets/aci_access_span_src_group_src_path/tasks/main.yml @@ -0,0 +1,280 @@ +# Test code for the ACI modules +# Copyright: (c) 2023, Akini Ross (@akinross) + +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +- name: Test that we have an ACI APIC host, ACI username and ACI password + fail: + msg: 'Please define the following variables: aci_hostname, aci_username and aci_password.' + when: aci_hostname is not defined or aci_username is not defined or aci_password is not defined + +- name: Set vars + set_fact: + aci_info: &aci_info + host: "{{ aci_hostname }}" + username: "{{ aci_username }}" + password: "{{ aci_password }}" + validate_certs: '{{ aci_validate_certs | default(false) }}' + use_ssl: '{{ aci_use_ssl | default(true) }}' + use_proxy: '{{ aci_use_proxy | default(true) }}' + output_level: '{{ aci_output_level | default("info") }}' + +# CLEAN TEST ENVIRONMENT BEFORE TESTS + +- name: Query all span source groups ( clean before ) + cisco.aci.aci_access_span_src_group: + <<: *aci_info + state: query + register: query_for_clean + +- name: Clean access span source groups ( clean before ) + cisco.aci.aci_access_span_src_group: + <<: *aci_info + source_group: "{{ item.spanSrcGrp.attributes.name }}" + state: absent + loop: "{{ query_for_clean.current }}" + +- name: Query all span destination groups ( clean before ) + cisco.aci.aci_access_span_dst_group: + <<: *aci_info + state: query + register: query_for_clean + +- name: Clean access span destination groups ( clean before ) + cisco.aci.aci_access_span_dst_group: + <<: *aci_info + destination_group: "{{ item.spanDestGrp.attributes.name }}" + state: absent + loop: "{{ query_for_clean.current }}" + +- name: Add access span destination group type epg ( clean before ) + cisco.aci.aci_access_span_dst_group: + <<: *aci_info + destination_group: span_dest_1 + description: test span epg + destination_epg: + destination_ip: 10.0.0.1 + source_ip: 10.0.2.1 + tenant: ansible_test + ap: ansible_test + epg: ansible_test + state: present + +- name: Add access span source group 1 ( clean before ) + cisco.aci.aci_access_span_src_group: + <<: *aci_info + source_group: span_src_group_1 + destination_group: span_dest_1 + state: present + +- name: Add access span source group source 1 ( clean before ) + cisco.aci.aci_access_span_src_group_src: + <<: *aci_info + source_group: span_src_group_1 + source: span_src_1 + state: present + +# TEST CREATE SOURCE PATHS + +- name: Add access span source path 1 of type port ( checkmode ) + cisco.aci.aci_access_span_src_group_src_path: &span_src_path_1 + <<: *aci_info + source_group: span_src_group_1 + source: span_src_1 + pod: 1 + nodes: + - 101 + path_ep: eth1/1 + state: present + check_mode: true + register: cm_span_src_path_1 + +- name: Add access span source 1 of type port + cisco.aci.aci_access_span_src_group_src_path: + <<: *span_src_path_1 + register: nm_span_src_path_1 + +- name: Add access span source 1 of type port again + cisco.aci.aci_access_span_src_group_src_path: + <<: *span_src_path_1 + register: nm_span_src_path_1_again + +- name: Add access span source path 2 of type Direct Port Channel + cisco.aci.aci_access_span_src_group_src_path: + <<: *aci_info + source_group: span_src_group_1 + source: span_src_1 + pod: 1 + nodes: + - 101 + path_ep: test_pc_pol_group + state: present + register: nm_span_src_path_2 + +- name: Add access span source path 3 of type VPC component PC + cisco.aci.aci_access_span_src_group_src_path: + <<: *aci_info + source_group: span_src_group_1 + source: span_src_1 + pod: 1 + nodes: + - 101 + path_ep: test_vpc_pol_group + state: present + register: nm_span_src_path_3 + +- name: Add access span source path 4 of type Virtual Port Channel + cisco.aci.aci_access_span_src_group_src_path: + <<: *aci_info + source_group: span_src_group_1 + source: span_src_1 + pod: 1 + nodes: + - 101 + - 104 + path_ep: test_vpc_pol_group + state: present + register: nm_span_src_path_4 + +- name: Verify add access span source group 1 of type port + ansible.builtin.assert: + that: + - cm_span_src_path_1 is changed + - cm_span_src_path_1.previous == [] + - cm_span_src_path_1.current == [] + - cm_span_src_path_1.proposed.spanRsSrcToPathEp.attributes.dn == "uni/infra/srcgrp-span_src_group_1/src-span_src_1/rssrcToPathEp-[topology/pod-1/paths-101/pathep-[eth1/1]]" + - cm_span_src_path_1.proposed.spanRsSrcToPathEp.attributes.tDn == "topology/pod-1/paths-101/pathep-[eth1/1]" + - nm_span_src_path_1 is changed + - nm_span_src_path_1.current.0.spanRsSrcToPathEp.attributes.dn == "uni/infra/srcgrp-span_src_group_1/src-span_src_1/rssrcToPathEp-[topology/pod-1/paths-101/pathep-[eth1/1]]" + - nm_span_src_path_1.current.0.spanRsSrcToPathEp.attributes.tDn == "topology/pod-1/paths-101/pathep-[eth1/1]" + - nm_span_src_path_1.previous == [] + - nm_span_src_path_1_again is not changed + - nm_span_src_path_1_again.previous.0.spanRsSrcToPathEp.attributes.dn == "uni/infra/srcgrp-span_src_group_1/src-span_src_1/rssrcToPathEp-[topology/pod-1/paths-101/pathep-[eth1/1]]" + - nm_span_src_path_1_again.previous.0.spanRsSrcToPathEp.attributes.tDn == "topology/pod-1/paths-101/pathep-[eth1/1]" + - nm_span_src_path_1_again.current.0.spanRsSrcToPathEp.attributes.dn == "uni/infra/srcgrp-span_src_group_1/src-span_src_1/rssrcToPathEp-[topology/pod-1/paths-101/pathep-[eth1/1]]" + - nm_span_src_path_1_again.current.0.spanRsSrcToPathEp.attributes.tDn == "topology/pod-1/paths-101/pathep-[eth1/1]" + - nm_span_src_path_2 is changed + - nm_span_src_path_2.previous == [] + - nm_span_src_path_2.current.0.spanRsSrcToPathEp.attributes.dn == "uni/infra/srcgrp-span_src_group_1/src-span_src_1/rssrcToPathEp-[topology/pod-1/paths-101/pathep-[test_pc_pol_group]]" + - nm_span_src_path_2.current.0.spanRsSrcToPathEp.attributes.tDn == "topology/pod-1/paths-101/pathep-[test_pc_pol_group]" + - nm_span_src_path_3 is changed + - nm_span_src_path_3.previous == [] + - nm_span_src_path_3.current.0.spanRsSrcToPathEp.attributes.dn == "uni/infra/srcgrp-span_src_group_1/src-span_src_1/rssrcToPathEp-[topology/pod-1/paths-101/pathep-[test_vpc_pol_group]]" + - nm_span_src_path_3.current.0.spanRsSrcToPathEp.attributes.tDn == "topology/pod-1/paths-101/pathep-[test_vpc_pol_group]" + - nm_span_src_path_4 is changed + - nm_span_src_path_4.previous == [] + - nm_span_src_path_4.current.0.spanRsSrcToPathEp.attributes.dn == "uni/infra/srcgrp-span_src_group_1/src-span_src_1/rssrcToPathEp-[topology/pod-1/protpaths-101-104/pathep-[test_vpc_pol_group]]" + - nm_span_src_path_4.current.0.spanRsSrcToPathEp.attributes.tDn == "topology/pod-1/protpaths-101-104/pathep-[test_vpc_pol_group]" + +# TEST QUERY SOURCE PATHS + +- name: Query span source group path span_src_path_1 + cisco.aci.aci_access_span_src_group_src_path: + <<: *span_src_path_1 + state: query + register: query_one + +- name: Query all span source group paths + cisco.aci.aci_access_span_src_group_src_path: + <<: *aci_info + state: query + register: query_all + +- name: Verify querying access span sources + ansible.builtin.assert: + that: + - query_one is not changed + - query_one.current | length == 1 + - query_one.current.0.spanRsSrcToPathEp.attributes.dn == "uni/infra/srcgrp-span_src_group_1/src-span_src_1/rssrcToPathEp-[topology/pod-1/paths-101/pathep-[eth1/1]]" + - query_one.current.0.spanRsSrcToPathEp.attributes.tDn == "topology/pod-1/paths-101/pathep-[eth1/1]" + - query_all is not changed + - query_all.current | length >= 4 + +# TEST ERRORS SOURCE PATHS + +- name: Add access span source path 5 of type Virtual Port Channel + cisco.aci.aci_access_span_src_group_src_path: + <<: *aci_info + source_group: span_src_group_1 + source: span_src_1 + pod: 1 + nodes: + - 101 + - 102 + - 104 + path_ep: test_vpc_pol_group + state: present + ignore_errors: true + register: err_too_many_nodes + +- name: Verify errors on incorrect input + ansible.builtin.assert: + that: + - err_too_many_nodes.msg == "3 nodes have been provided, where a maximum of 2 nodes is allowed." + +# TEST REMOVAL SOURCE PATHS + +- name: Remove span source group ( checkmode ) + cisco.aci.aci_access_span_src_group_src_path: &remove_span_src_path_1 + <<: *span_src_path_1 + state: absent + check_mode: true + register: cm_remove_span_src_path_1 + +- name: Remove span source group + cisco.aci.aci_access_span_src_group_src_path: + <<: *remove_span_src_path_1 + register: nm_remove_span_src_path_1 + +- name: Remove span source group again + cisco.aci.aci_access_span_src_group_src_path: + <<: *remove_span_src_path_1 + register: nm_remove_span_src_path_1_again + +- name: Verify remove access span destination groups + ansible.builtin.assert: + that: + - cm_remove_span_src_path_1 is changed + - cm_remove_span_src_path_1.current | length == 1 + - cm_remove_span_src_path_1.previous | length == 1 + - cm_remove_span_src_path_1.previous.0.spanRsSrcToPathEp.attributes.dn == "uni/infra/srcgrp-span_src_group_1/src-span_src_1/rssrcToPathEp-[topology/pod-1/paths-101/pathep-[eth1/1]]" + - cm_remove_span_src_path_1.previous.0.spanRsSrcToPathEp.attributes.tDn == "topology/pod-1/paths-101/pathep-[eth1/1]" + - cm_remove_span_src_path_1.current.0.spanRsSrcToPathEp.attributes.dn == "uni/infra/srcgrp-span_src_group_1/src-span_src_1/rssrcToPathEp-[topology/pod-1/paths-101/pathep-[eth1/1]]" + - cm_remove_span_src_path_1.current.0.spanRsSrcToPathEp.attributes.tDn == "topology/pod-1/paths-101/pathep-[eth1/1]" + - cm_remove_span_src_path_1.proposed == {} + - nm_remove_span_src_path_1 is changed + - nm_remove_span_src_path_1.current == [] + - nm_remove_span_src_path_1.previous | length == 1 + - nm_remove_span_src_path_1.previous.0.spanRsSrcToPathEp.attributes.dn == "uni/infra/srcgrp-span_src_group_1/src-span_src_1/rssrcToPathEp-[topology/pod-1/paths-101/pathep-[eth1/1]]" + - nm_remove_span_src_path_1.previous.0.spanRsSrcToPathEp.attributes.tDn == "topology/pod-1/paths-101/pathep-[eth1/1]" + - nm_remove_span_src_path_1_again is not changed + - nm_remove_span_src_path_1_again.current == [] + - nm_remove_span_src_path_1_again.previous == [] + +# CLEAN TEST ENVIRONMENT AFTER TESTS + +- name: Query all span source groups ( clean after ) + cisco.aci.aci_access_span_src_group: + <<: *aci_info + state: query + register: query_for_clean + +- name: Clean access span source groups ( clean after ) + cisco.aci.aci_access_span_src_group: + <<: *aci_info + source_group: "{{ item.spanSrcGrp.attributes.name }}" + state: absent + loop: "{{ query_for_clean.current }}" + +- name: Query all span destination groups ( clean after ) + cisco.aci.aci_access_span_dst_group: + <<: *aci_info + state: query + register: query_for_clean + +- name: Clean access span destination groups ( clean after ) + cisco.aci.aci_access_span_dst_group: + <<: *aci_info + destination_group: "{{ item.spanDestGrp.attributes.name }}" + state: absent + loop: "{{ query_for_clean.current }}" diff --git a/ansible_collections/cisco/aci/tests/integration/targets/aci_aep_to_domain/tasks/main.yml b/ansible_collections/cisco/aci/tests/integration/targets/aci_aep_to_domain/tasks/main.yml index a69d255ff..2a4f6f23e 100644 --- a/ansible_collections/cisco/aci/tests/integration/targets/aci_aep_to_domain/tasks/main.yml +++ b/ansible_collections/cisco/aci/tests/integration/targets/aci_aep_to_domain/tasks/main.yml @@ -8,6 +8,18 @@ msg: 'Please define the following variables: aci_hostname, aci_username and aci_password.' when: aci_hostname is not defined or aci_username is not defined or aci_password is not defined +- name: Set vars + ansible.builtin.set_fact: + aci_info: &aci_info + host: '{{ aci_hostname }}' + username: '{{ aci_username }}' + password: '{{ aci_password }}' + validate_certs: '{{ aci_validate_certs | default(false) }}' + use_ssl: '{{ aci_use_ssl | default(true) }}' + use_proxy: '{{ aci_use_proxy | default(true) }}' + output_level: debug # required debug due to asserts done on url which are only present in debug mode + + - name: Verify Cloud and Non-Cloud Sites in use. include_tasks: ../../../../../../integration/targets/aci_cloud_provider/tasks/main.yml @@ -17,55 +29,43 @@ # CLEAN ENVIRONMENT - name: Remove AEP to domain binding cisco.aci.aci_aep_to_domain: &binding_absent - host: '{{ aci_hostname }}' - username: '{{ aci_username }}' - password: '{{ aci_password }}' - validate_certs: '{{ aci_validate_certs | default(false) }}' - use_ssl: '{{ aci_use_ssl | default(true) }}' - use_proxy: '{{ aci_use_proxy | default(true) }}' - output_level: '{{ aci_output_level | default("info") }}' + <<: *aci_info aep: test_aep domain: phys_dom domain_type: phys state: absent + - name: Delete AEP + cisco.aci.aci_aep: + <<: *aci_info + aep: test_aep + description: Test AEP + state: absent + - name: Create AEP cisco.aci.aci_aep: - host: "{{ aci_hostname }}" - username: "{{ aci_username }}" - password: "{{ aci_password }}" - validate_certs: '{{ aci_validate_certs | default(false) }}' - use_ssl: '{{ aci_use_ssl | default(true) }}' - use_proxy: '{{ aci_use_proxy | default(true) }}' - output_level: '{{ aci_output_level | default("info") }}' + <<: *aci_info aep: test_aep description: Test AEP state: present - name: Create physical domain cisco.aci.aci_domain: - host: "{{ aci_hostname }}" - username: "{{ aci_username }}" - password: "{{ aci_password }}" - validate_certs: '{{ aci_validate_certs | default(false) }}' - use_ssl: '{{ aci_use_ssl | default(true) }}' - use_proxy: '{{ aci_use_proxy | default(true) }}' - output_level: '{{ aci_output_level | default("info") }}' + <<: *aci_info domain: phys_dom domain_type: phys state: present + - name: Ensure vmm domain exist + cisco.aci.aci_rest: + <<: *aci_info + path: api/mo/uni/vmmp-VMware/dom-anstest.json + content: {"vmmDomP": {"attributes": {}}} # ADD BINDING - name: Add AEP to domain binding (check_mode) cisco.aci.aci_aep_to_domain: &binding_present - host: '{{ aci_hostname }}' - username: '{{ aci_username }}' - password: '{{ aci_password }}' - validate_certs: '{{ aci_validate_certs | default(false) }}' - use_ssl: '{{ aci_use_ssl | default(true) }}' - use_proxy: '{{ aci_use_proxy | default(true) }}' - output_level: '{{ aci_output_level | default("info") }}' + <<: *aci_info aep: test_aep domain: phys_dom domain_type: phys @@ -104,17 +104,67 @@ - cm_add_binding_again is not changed - nm_add_binding_again is not changed + - name: Add AEP to domain type not vmm with vm provider (error) + cisco.aci.aci_aep_to_domain: + <<: *aci_info + aep: test_aep + domain: phys_dom + domain_type: phys + vm_provider: vmware + ignore_errors: true + register: err_not_vmm_with_vm_provider + + - name: Add AEP to domain with type fc + cisco.aci.aci_aep_to_domain: + <<: *aci_info + aep: test_aep + domain: fc_dom + domain_type: fc + register: nm_fc_type + + - name: Add AEP to domain with type l2dom + cisco.aci.aci_aep_to_domain: + <<: *aci_info + aep: test_aep + domain: l2dom_dom + domain_type: l2dom + register: nm_l2dom_type + + - name: Add AEP to domain with type l3dom + cisco.aci.aci_aep_to_domain: + <<: *aci_info + aep: test_aep + domain: l3dom_dom + domain_type: l3dom + register: nm_l3dom_type + + - name: Add AEP to domain with type vmm + cisco.aci.aci_aep_to_domain: + <<: *aci_info + aep: test_aep + domain: anstest + domain_type: vmm + vm_provider: vmware + register: nm_vmm_type + + - name: Verify bindings with domain types + assert: + that: + - err_not_vmm_with_vm_provider is not changed + - err_not_vmm_with_vm_provider.msg == "Domain type 'phys' cannot have a 'vm_provider'" + - nm_fc_type is changed + - nm_fc_type.current.0.infraRsDomP.attributes.tDn == "uni/fc-fc_dom" + - nm_l2dom_type is changed + - nm_l2dom_type.current.0.infraRsDomP.attributes.tDn == "uni/l2dom-l2dom_dom" + - nm_l3dom_type is changed + - nm_l3dom_type.current.0.infraRsDomP.attributes.tDn == "uni/l3dom-l3dom_dom" + - nm_vmm_type is changed + - nm_vmm_type.current.0.infraRsDomP.attributes.tDn == "uni/vmmp-VMware/dom-anstest" # QUERY ALL BINDINGS - name: Query all AEP to domain bindings (check_mode) cisco.aci.aci_aep_to_domain: &binding_query - host: '{{ aci_hostname }}' - username: '{{ aci_username }}' - password: '{{ aci_password }}' - validate_certs: '{{ aci_validate_certs | default(false) }}' - use_ssl: '{{ aci_use_ssl | default(true) }}' - use_proxy: '{{ aci_use_proxy | default(true) }}' - output_level: '{{ aci_output_level | default("info") }}' + <<: *aci_info state: query check_mode: true register: cm_query_all_bindings diff --git a/ansible_collections/cisco/aci/tests/integration/targets/aci_bd_subnet/tasks/main.yml b/ansible_collections/cisco/aci/tests/integration/targets/aci_bd_subnet/tasks/main.yml index 10f00990c..4bc8e26c4 100644 --- a/ansible_collections/cisco/aci/tests/integration/targets/aci_bd_subnet/tasks/main.yml +++ b/ansible_collections/cisco/aci/tests/integration/targets/aci_bd_subnet/tasks/main.yml @@ -8,6 +8,24 @@ msg: 'Please define the following variables: aci_hostname, aci_username and aci_password.' when: aci_hostname is not defined or aci_username is not defined or aci_password is not defined +- name: Set vars + set_fact: + aci_info: &aci_info + host: "{{ aci_hostname }}" + username: "{{ aci_username }}" + password: "{{ aci_password }}" + validate_certs: '{{ aci_validate_certs | default(false) }}' + use_ssl: '{{ aci_use_ssl | default(true) }}' + use_proxy: '{{ aci_use_proxy | default(true) }}' + output_level: debug + +- name: Query system information + cisco.aci.aci_system: + <<: *aci_info + id: 1 + state: query + register: version + - name: Verify Cloud and Non-Cloud Sites in use. include_tasks: ../../../../../../integration/targets/aci_cloud_provider/tasks/main.yml @@ -16,17 +34,17 @@ block: # block specifies execution of tasks within, based on conditions - name: ensure tenant exists for tests to kick off cisco.aci.aci_tenant: &aci_tenant_present - host: "{{ aci_hostname }}" - username: "{{ aci_username }}" - password: "{{ aci_password }}" - validate_certs: '{{ aci_validate_certs | default(false) }}' - use_ssl: '{{ aci_use_ssl | default(true) }}' - use_proxy: '{{ aci_use_proxy | default(true) }}' - output_level: debug - state: present + <<: *aci_info tenant: ansible_test register: tenant_present + - name: Query system information + cisco.aci.aci_system: + <<: *aci_info + id: 1 + state: query + register: version + - name: ensure bd exists for tests to kick off cisco.aci.aci_bd: &aci_bd_present <<: *aci_tenant_present @@ -60,7 +78,7 @@ <<: *aci_subnet_present register: create_subnet - - name: create new subnet - creation works + - name: create new subnet - creation works (with no ip_data_plane_learning - APIC version < 5.0) cisco.aci.aci_bd_subnet: &aci_subnet2_present <<: *aci_subnet2_absent state: present @@ -69,6 +87,19 @@ route_profile: default route_profile_l3_out: default register: create_subnet2 + when: version.current.0.topSystem.attributes.version is version('5', '<') + + - name: create new subnet - creation works (with ip_data_plane_learning - APIC version >= 5.0) + cisco.aci.aci_bd_subnet: + <<: *aci_subnet2_present + state: present + descr: Ansible Test + scope: [private, shared] + route_profile: default + route_profile_l3_out: default + ip_data_plane_learning: disabled + register: create_subnet2_5 + when: version.current.0.topSystem.attributes.version is version('5', '>=') - name: create subnet again - idempotency works cisco.aci.aci_bd_subnet: @@ -96,7 +127,7 @@ register: create_incomplete_data ignore_errors: true - - name: asserts for subnet creation tasks + - name: assert for subnet creation tasks assert: that: - create_check_mode is changed @@ -106,11 +137,6 @@ - create_subnet is changed - create_subnet.current.0.fvSubnet.attributes.annotation == 'orchestrator:ansible' - create_subnet.previous == [] - - create_subnet2 is changed - - create_subnet2.sent == create_subnet2.proposed - - create_subnet2.sent.fvSubnet.attributes.scope == "private,shared" - - create_subnet2.sent.fvSubnet.children.0.fvRsBDSubnetToProfile.attributes.tnL3extOutName == 'default' - - create_subnet2.sent.fvSubnet.children.0.fvRsBDSubnetToProfile.attributes.tnRtctrlProfileName == 'default' - create_idempotency is not changed - create_idempotency.previous != [] - modify_subnet is changed @@ -123,6 +149,28 @@ - create_incomplete_data is failed - 'create_incomplete_data.msg == "state is present but all of the following are missing: bd"' + - name: assert for subnet for task with version < 5 + assert: + that: + - create_subnet2 is changed + - create_subnet2.sent == create_subnet2.proposed + - create_subnet2.sent.fvSubnet.attributes.scope == "private,shared" + - create_subnet2.sent.fvSubnet.children.0.fvRsBDSubnetToProfile.attributes.tnL3extOutName == 'default' + - create_subnet2.sent.fvSubnet.children.0.fvRsBDSubnetToProfile.attributes.tnRtctrlProfileName == 'default' + when: version.current.0.topSystem.attributes.version is version('5', '<') + + - name: assert for subnet ip_data_learning for task with version >=5 + assert: + that: + - create_subnet.current.0.fvSubnet.attributes.ipDPLearning == 'enabled' + - create_subnet2_5 is changed + - create_subnet2_5.current.0.fvSubnet.attributes.ipDPLearning == 'disabled' + - create_subnet2_5.sent == create_subnet2_5.proposed + - create_subnet2_5.sent.fvSubnet.attributes.scope == "private,shared" + - create_subnet2_5.sent.fvSubnet.children.0.fvRsBDSubnetToProfile.attributes.tnL3extOutName == 'default' + - create_subnet2_5.sent.fvSubnet.children.0.fvRsBDSubnetToProfile.attributes.tnRtctrlProfileName == 'default' + when: version.current.0.topSystem.attributes.version is version('5', '>=') + - name: get all subnets cisco.aci.aci_bd_subnet: &aci_query <<: *aci_tenant_present @@ -203,6 +251,28 @@ - '"query-target-filter=eq(fvSubnet.ip,\"10.100.100.1/24\")" in get_subnets_gateway.filter_string' - '"class/fvSubnet.json" in get_subnets_gateway.url' + - name: error handling for ipv4 subnet with wrong mask + cisco.aci.aci_bd_subnet: + <<: *aci_bd_present + gateway: 10.100.100.1 + mask: 33 + ignore_errors: true + register: error_mask_ipv4 + + - name: error handling for ipv6 subnet with wrong mask + cisco.aci.aci_bd_subnet: + <<: *aci_bd_present + gateway: "2001:0db8:85a3:0000:0000:8a2e:0370:7334" + mask: 129 + ignore_errors: true + register: error_mask_ipv6 + + - name: asserts for mask error handling + assert: + that: + - error_mask_ipv4.msg == "Valid Subnet Masks are 0 to 32 for IPv4 Addresses" + - error_mask_ipv6.msg == "Valid Subnet Masks are 0 to 128 for IPv6 Addresses" + - name: delete subnet - check mode works cisco.aci.aci_bd_subnet: <<: *aci_subnet_absent diff --git a/ansible_collections/cisco/aci/tests/integration/targets/aci_bd_to_l3out/aliases b/ansible_collections/cisco/aci/tests/integration/targets/aci_bd_to_l3out/aliases new file mode 100644 index 000000000..209b793f9 --- /dev/null +++ b/ansible_collections/cisco/aci/tests/integration/targets/aci_bd_to_l3out/aliases @@ -0,0 +1,2 @@ +# No ACI simulator yet, so not enabled +# unsupported diff --git a/ansible_collections/cisco/aci/tests/integration/targets/aci_bd_to_l3out/tasks/main.yml b/ansible_collections/cisco/aci/tests/integration/targets/aci_bd_to_l3out/tasks/main.yml new file mode 100644 index 000000000..7d1313fb2 --- /dev/null +++ b/ansible_collections/cisco/aci/tests/integration/targets/aci_bd_to_l3out/tasks/main.yml @@ -0,0 +1,202 @@ +# Test code for the ACI modules +# Copyright: (c) 2023, Gaspard Micol (@gmicol) <gmicol@cisco.com> + +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +- name: Test that we have an ACI APIC host, ACI username and ACI password + fail: + msg: 'Please define the following variables: aci_hostname, aci_username and aci_password.' + when: aci_hostname is not defined or aci_username is not defined or aci_password is not defined + +- name: Set vars + set_fact: + aci_info: &aci_info + host: "{{ aci_hostname }}" + username: "{{ aci_username }}" + password: "{{ aci_password }}" + validate_certs: '{{ aci_validate_certs | default(false) }}' + use_ssl: '{{ aci_use_ssl | default(true) }}' + use_proxy: '{{ aci_use_proxy | default(true) }}' + output_level: debug + +# CLEAN ENVIRONMENT +- name: Remove the ansible_tenant + cisco.aci.aci_tenant: &aci_tenant_absent + <<: *aci_info + state: absent + tenant: ansible_test + +- name: Verify Cloud and Non-Cloud Sites in use. + include_tasks: ../../../../../../integration/targets/aci_cloud_provider/tasks/main.yml + +- name: Execute tasks only for non-cloud sites + when: query_cloud.current == [] # This condition will execute only non-cloud sites + block: # block specifies execution of tasks within, based on conditions + - name: Create tenant + cisco.aci.aci_tenant: &aci_tenant_present + <<: *aci_tenant_absent + state: present + + - name: Create vrf + cisco.aci.aci_vrf: &aci_vrf_present + <<: *aci_tenant_present + vrf: anstest + + - name: Create first L3out + cisco.aci.aci_l3out: &aci_l3_out_present + <<: *aci_vrf_present + l3out: ansible_l3out + domain: ansible_dom + state: present + + - name: Create second L3out + cisco.aci.aci_l3out: &aci_l3_out_present_2 + <<: *aci_vrf_present + l3out: ansible_l3out_2 + domain: ansible_dom + state: present + + - name: Create first bd + cisco.aci.aci_bd: &aci_bd_present + <<: *aci_tenant_present + bd: anstest + + - name: Create second bd + cisco.aci.aci_bd: &aci_bd_present_2 + <<: *aci_tenant_present + bd: anstest_2 + + - name: Ensure first binding bd_to_l3out does not exist + cisco.aci.aci_bd_to_l3out: &aci_bd_to_l3out_absent + <<: *aci_tenant_present + bd: anstest + l3out: ansible_l3out + state: absent + + - name: Ensure second binding bd_to_l3out does not exist + cisco.aci.aci_bd_to_l3out: &aci_bd_to_l3out_absent_2 + <<: *aci_tenant_present + bd: anstest_2 + l3out: ansible_l3out_2 + state: absent + + - name: bind bd to L3out - first binding (check_mode) + cisco.aci.aci_bd_to_l3out: &aci_bd_to_l3out_present + <<: *aci_bd_to_l3out_absent + state: present + check_mode: true + register: cm_bd_to_l3out + + - name: bind bd to L3out - first binding (normal_mode) + cisco.aci.aci_bd_to_l3out: + <<: *aci_bd_to_l3out_present + register: nm_bd_to_l3out + + - name: bind bd to L3out again - testing idempotency + cisco.aci.aci_bd_to_l3out: + <<: *aci_bd_to_l3out_present + register: bd_to_l3out_idempotency + + - name: bind bd to L3out - second binding + cisco.aci.aci_bd_to_l3out: &aci_bd_to_l3out_present_2 + <<: *aci_bd_to_l3out_absent_2 + state: present + register: nm_bd_to_l3out_2 + + - name: asserts for creation tasks + assert: + that: + - cm_bd_to_l3out is changed + - cm_bd_to_l3out.previous == [] + - cm_bd_to_l3out.current == [] + - nm_bd_to_l3out is changed + - nm_bd_to_l3out.current.0.fvRsBDToOut.attributes.dn == "uni/tn-ansible_test/BD-anstest/rsBDToOut-ansible_l3out" + - nm_bd_to_l3out.current.0.fvRsBDToOut.attributes.tnL3extOutName == "ansible_l3out" + - bd_to_l3out_idempotency is not changed + - nm_bd_to_l3out_2 is changed + - nm_bd_to_l3out_2.current.0.fvRsBDToOut.attributes.dn == "uni/tn-ansible_test/BD-anstest_2/rsBDToOut-ansible_l3out_2" + - nm_bd_to_l3out_2.current.0.fvRsBDToOut.attributes.tnL3extOutName == "ansible_l3out_2" + + - name: Query all bds bound to l3outs + cisco.aci.aci_bd_to_l3out: + <<: *aci_tenant_present + state: query + register: query_all_bd_to_l3out + + - name: Query first bd bound to first l3out + cisco.aci.aci_bd_to_l3out: + <<: *aci_bd_to_l3out_present + state: query + register: query_first_bd_to_l3out + + - name: asserts query tasks + assert: + that: + - query_all_bd_to_l3out is not changed + - '"fvRsBDToOut" in query_all_bd_to_l3out.filter_string' + - query_all_bd_to_l3out.current.0.fvTenant.children | length >= 2 + - query_all_bd_to_l3out.current.0.fvTenant.children.0.fvBD.attributes.name == "anstest_2" + - query_all_bd_to_l3out.current.0.fvTenant.children.0.fvBD.children.0.fvRsBDToOut.attributes.tRn == "out-ansible_l3out_2" + - query_all_bd_to_l3out.current.0.fvTenant.children.1.fvBD.attributes.name == "anstest" + - query_all_bd_to_l3out.current.0.fvTenant.children.1.fvBD.children.0.fvRsBDToOut.attributes.tRn == "out-ansible_l3out" + - query_first_bd_to_l3out is not changed + - '"tn-ansible_test/BD-anstest/rsBDToOut-ansible_l3out.json" in query_first_bd_to_l3out.url' + + - name: unbind bd to l3out - first binding (check_mode) + cisco.aci.aci_bd_to_l3out: + <<: *aci_bd_to_l3out_present + state: absent + check_mode: true + register: cm_unbind_bd_to_l3out + + - name: unbind bd to l3out - first binding (normal_mode) + cisco.aci.aci_bd_to_l3out: + <<: *aci_bd_to_l3out_present + state: absent + register: nm_unbind_bd_to_l3out + + - name: unbind bd to l3out again - testing idempotency + cisco.aci.aci_bd_to_l3out: + <<: *aci_bd_to_l3out_present + state: absent + register: unbind_bd_to_l3out_idempotency + + - name: unbind bd to l3out - second binding + cisco.aci.aci_bd_to_l3out: + <<: *aci_bd_to_l3out_present_2 + state: absent + register: nm_unbind_bd_to_l3out_2 + + - name: asserts for deletion tasks + assert: + that: + - cm_unbind_bd_to_l3out is changed + - cm_unbind_bd_to_l3out.proposed == {} + - nm_unbind_bd_to_l3out is changed + - nm_unbind_bd_to_l3out.previous != [] + - nm_unbind_bd_to_l3out.method == "DELETE" + - unbind_bd_to_l3out_idempotency is not changed + - unbind_bd_to_l3out_idempotency.previous == [] + - nm_unbind_bd_to_l3out_2 is changed + - nm_unbind_bd_to_l3out_2.previous != [] + - nm_unbind_bd_to_l3out_2.method == "DELETE" + + - name: delete bd - cleanup before ending tests + cisco.aci.aci_bd: + <<: *aci_bd_present + state: absent + + - name: delete l3out - cleanup before ending tests + cisco.aci.aci_l3out: + <<: *aci_l3_out_present + state: absent + + - name: delete vrf - cleanup before ending tests + cisco.aci.aci_vrf: + <<: *aci_vrf_present + state: absent + + - name: delete tenant - cleanup before ending tests + cisco.aci.aci_tenant: + <<: *aci_tenant_present + state: absent diff --git a/ansible_collections/cisco/aci/tests/integration/targets/aci_bgp_best_path_policy/aliases b/ansible_collections/cisco/aci/tests/integration/targets/aci_bgp_best_path_policy/aliases new file mode 100644 index 000000000..209b793f9 --- /dev/null +++ b/ansible_collections/cisco/aci/tests/integration/targets/aci_bgp_best_path_policy/aliases @@ -0,0 +1,2 @@ +# No ACI simulator yet, so not enabled +# unsupported diff --git a/ansible_collections/cisco/aci/tests/integration/targets/aci_bgp_best_path_policy/tasks/main.yml b/ansible_collections/cisco/aci/tests/integration/targets/aci_bgp_best_path_policy/tasks/main.yml new file mode 100644 index 000000000..d0eae2666 --- /dev/null +++ b/ansible_collections/cisco/aci/tests/integration/targets/aci_bgp_best_path_policy/tasks/main.yml @@ -0,0 +1,140 @@ +# Test code for the ACI modules +# Copyright: (c) 2023, Gaspard Micol (@gmicol) <gmicol@cisco.com> + +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +- name: Test that we have an ACI APIC host, ACI username and ACI password + fail: + msg: 'Please define the following variables: aci_hostname, aci_username and aci_password.' + when: aci_hostname is not defined or aci_username is not defined or aci_password is not defined + +- name: Set vars + set_fact: + aci_info: &aci_info + host: "{{ aci_hostname }}" + username: "{{ aci_username }}" + password: "{{ aci_password }}" + validate_certs: '{{ aci_validate_certs | default(false) }}' + use_ssl: '{{ aci_use_ssl | default(true) }}' + use_proxy: '{{ aci_use_proxy | default(true) }}' + output_level: debug + +# CLEAN ENVIRONMENT +- name: Remove the ansible_tenant + aci_tenant: &aci_tenant_absent + <<: *aci_info + tenant: ansible_tenant + state: absent + +- name: Verify Cloud and Non-Cloud Sites in use. + include_tasks: ../../../../../../integration/targets/aci_cloud_provider/tasks/main.yml + +- name: Execute tasks only for non-cloud sites + when: query_cloud.current == [] # This condition will execute only non-cloud sites + block: # block specifies execution of tasks within, based on conditions + - name: Add a new tenant + aci_tenant: &aci_tenant_present + <<: *aci_info + tenant: ansible_tenant + description: Ansible tenant + state: present + + - name: Add a BGP best path policy (check_mode) + aci_bgp_best_path_policy: &aci_bgp_best_path_policy_present + <<: *aci_info + tenant: ansible_tenant + bgp_best_path_policy: ansible_bgp_best_path_policy_1 + best_path_control: enable + description: BGP best path policy 1 for ansible_tenant tenant + state: present + check_mode: true + register: cm_add_bgp_best_path_policy + + - name: Add a BGP best path policy (normal_mode) + aci_bgp_best_path_policy: + <<: *aci_bgp_best_path_policy_present + register: nm_add_bgp_best_path_policy + + - name: Add the first BGP best path policy again - testing idempotency + aci_bgp_best_path_policy: + <<: *aci_bgp_best_path_policy_present + register: nm_add_bgp_best_path_policy_idempotency + + - name: Add a second BGP best path policy (normal_mode) + aci_bgp_best_path_policy: + <<: *aci_info + tenant: ansible_tenant + bgp_best_path_policy: ansible_bgp_best_path_policy_2 + best_path_control: disable + description: BGP best path policy 2 for ansible_tenant tenant + state: present + register: nm_add_bgp_best_path_policy_2 + + - name: Asserts for BGP best path policys creation tasks + assert: + that: + - cm_add_bgp_best_path_policy is changed + - cm_add_bgp_best_path_policy.previous == [] + - cm_add_bgp_best_path_policy.current == [] + - nm_add_bgp_best_path_policy is changed + - nm_add_bgp_best_path_policy.current.0.bgpBestPathCtrlPol.attributes.name == "ansible_bgp_best_path_policy_1" + - nm_add_bgp_best_path_policy.current.0.bgpBestPathCtrlPol.attributes.ctrl == "asPathMultipathRelax" + - nm_add_bgp_best_path_policy_idempotency is not changed + - nm_add_bgp_best_path_policy_2 is changed + - nm_add_bgp_best_path_policy_2.previous == [] + - nm_add_bgp_best_path_policy_2.current.0.bgpBestPathCtrlPol.attributes.name == "ansible_bgp_best_path_policy_2" + - nm_add_bgp_best_path_policy_2.current.0.bgpBestPathCtrlPol.attributes.ctrl == "" + + - name: Query all BGP best path policies + aci_bgp_best_path_policy: + <<: *aci_info + state: query + register: query_all_bgp_best_path_policy + + - name: Query ansible_bgp_best_path_policy_1 + aci_bgp_best_path_policy: + <<: *aci_bgp_best_path_policy_present + state: query + register: query_ansible_bgp_best_path_policy_1 + + - name: Asserts query tasks + assert: + that: + - query_all_bgp_best_path_policy is not changed + - query_all_bgp_best_path_policy.current|length >= 2 + - query_ansible_bgp_best_path_policy_1 is not changed + - query_ansible_bgp_best_path_policy_1.current.0.bgpBestPathCtrlPol.attributes.name == "ansible_bgp_best_path_policy_1" + - query_ansible_bgp_best_path_policy_1.current.0.bgpBestPathCtrlPol.attributes.ctrl == "asPathMultipathRelax" + + - name: Remove BGP best path policy (check_mode) + aci_bgp_best_path_policy: &bgp_best_path_policy_absent + <<: *aci_bgp_best_path_policy_present + state: absent + check_mode: true + register: cm_remove_bgp_best_path_policy + + - name: Remove BGP best path policy (normal_mode) + aci_bgp_best_path_policy: + <<: *bgp_best_path_policy_absent + register: nm_remove_bgp_best_path_policy + + - name: Remove BGP best path policy - testing idempotency + aci_bgp_best_path_policy: + <<: *bgp_best_path_policy_absent + register: nm_remove_bgp_best_path_policy_idempotency + + - name: Asserts deletion tasks + assert: + that: + - cm_remove_bgp_best_path_policy is changed + - cm_remove_bgp_best_path_policy.proposed == {} + - nm_remove_bgp_best_path_policy is changed + - nm_remove_bgp_best_path_policy.previous != [] + - nm_remove_bgp_best_path_policy.method == "DELETE" + - nm_remove_bgp_best_path_policy_idempotency is not changed + - nm_remove_bgp_best_path_policy_idempotency.previous == [] + + - name: Remove the ansible_tenant - cleanup before ending tests + aci_tenant: + <<: *aci_tenant_present + state: absent
\ No newline at end of file diff --git a/ansible_collections/cisco/aci/tests/integration/targets/aci_bgp_timers_policy/aliases b/ansible_collections/cisco/aci/tests/integration/targets/aci_bgp_timers_policy/aliases new file mode 100644 index 000000000..209b793f9 --- /dev/null +++ b/ansible_collections/cisco/aci/tests/integration/targets/aci_bgp_timers_policy/aliases @@ -0,0 +1,2 @@ +# No ACI simulator yet, so not enabled +# unsupported diff --git a/ansible_collections/cisco/aci/tests/integration/targets/aci_bgp_timers_policy/tasks/main.yml b/ansible_collections/cisco/aci/tests/integration/targets/aci_bgp_timers_policy/tasks/main.yml new file mode 100644 index 000000000..0c7cdd77d --- /dev/null +++ b/ansible_collections/cisco/aci/tests/integration/targets/aci_bgp_timers_policy/tasks/main.yml @@ -0,0 +1,155 @@ +# Test code for the ACI modules +# Copyright: (c) 2023, Gaspard Micol (@gmicol) <gmicol@cisco.com> + +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +- name: Test that we have an ACI APIC host, ACI username and ACI password + fail: + msg: 'Please define the following variables: aci_hostname, aci_username and aci_password.' + when: aci_hostname is not defined or aci_username is not defined or aci_password is not defined + +- name: Set vars + set_fact: + aci_info: &aci_info + host: "{{ aci_hostname }}" + username: "{{ aci_username }}" + password: "{{ aci_password }}" + validate_certs: '{{ aci_validate_certs | default(false) }}' + use_ssl: '{{ aci_use_ssl | default(true) }}' + use_proxy: '{{ aci_use_proxy | default(true) }}' + output_level: debug + +# CLEAN ENVIRONMENT +- name: Remove the ansible_tenant + aci_tenant: &aci_tenant_absent + <<: *aci_info + tenant: ansible_tenant + state: absent + +- name: Verify Cloud and Non-Cloud Sites in use. + include_tasks: ../../../../../../integration/targets/aci_cloud_provider/tasks/main.yml + +- name: Execute tasks only for non-cloud sites + when: query_cloud.current == [] # This condition will execute only non-cloud sites + block: # block specifies execution of tasks within, based on conditions + - name: Add a new tenant + aci_tenant: &aci_tenant_present + <<: *aci_info + tenant: ansible_tenant + description: Ansible tenant + state: present + + - name: Add a BGP timers policy (check_mode) + aci_bgp_timers_policy: &aci_bgp_timers_policy_present + <<: *aci_info + tenant: ansible_tenant + bgp_timers_policy: ansible_bgp_timers_policy_1 + graceful_restart_controls: complete + hold_interval: 360 + keepalive_interval: 120 + max_as_limit: 1 + stale_interval: 600 + description: BGP timers policy 1 for ansible_tenant tenant + state: present + check_mode: true + register: cm_add_bgp_timers_policy + + - name: Add a BGP timers policy (normal_mode) + aci_bgp_timers_policy: + <<: *aci_bgp_timers_policy_present + register: nm_add_bgp_timers_policy + + - name: Add the first BGP timers policy again - testing idempotency + aci_bgp_timers_policy: + <<: *aci_bgp_timers_policy_present + register: nm_add_bgp_timers_policy_idempotency + + - name: Add a second BGP timers policy (normal_mode) + aci_bgp_timers_policy: + <<: *aci_info + tenant: ansible_tenant + bgp_timers_policy: ansible_bgp_timers_policy_2 + description: BGP timers policy 2 for ansible_tenant tenant + state: present + register: nm_add_bgp_timers_policy_2 + + - name: Asserts for BGP timers policys creation tasks + assert: + that: + - cm_add_bgp_timers_policy is changed + - cm_add_bgp_timers_policy.previous == [] + - cm_add_bgp_timers_policy.current == [] + - nm_add_bgp_timers_policy is changed + - nm_add_bgp_timers_policy.current.0.bgpCtxPol.attributes.name == "ansible_bgp_timers_policy_1" + - nm_add_bgp_timers_policy.current.0.bgpCtxPol.attributes.grCtrl == "" + - nm_add_bgp_timers_policy.current.0.bgpCtxPol.attributes.holdIntvl == "360" + - nm_add_bgp_timers_policy.current.0.bgpCtxPol.attributes.kaIntvl == "120" + - nm_add_bgp_timers_policy.current.0.bgpCtxPol.attributes.maxAsLimit == "1" + - nm_add_bgp_timers_policy.current.0.bgpCtxPol.attributes.staleIntvl == "600" + - nm_add_bgp_timers_policy_idempotency is not changed + - nm_add_bgp_timers_policy_2 is changed + - nm_add_bgp_timers_policy_2.previous == [] + - nm_add_bgp_timers_policy_2.current.0.bgpCtxPol.attributes.name == "ansible_bgp_timers_policy_2" + - nm_add_bgp_timers_policy_2.current.0.bgpCtxPol.attributes.grCtrl == "helper" + - nm_add_bgp_timers_policy_2.current.0.bgpCtxPol.attributes.holdIntvl == "180" + - nm_add_bgp_timers_policy_2.current.0.bgpCtxPol.attributes.kaIntvl == "60" + - nm_add_bgp_timers_policy_2.current.0.bgpCtxPol.attributes.maxAsLimit == "0" + - nm_add_bgp_timers_policy_2.current.0.bgpCtxPol.attributes.staleIntvl == "default" + + - name: Query all BGP timers policies + aci_bgp_timers_policy: + <<: *aci_info + state: query + register: query_all_bgp_timers_policy + + - name: Query ansible_bgp_timers_policy_1 + aci_bgp_timers_policy: + <<: *aci_bgp_timers_policy_present + state: query + register: query_ansible_bgp_timers_policy_1 + + - name: Asserts query tasks + assert: + that: + - query_all_bgp_timers_policy is not changed + - query_all_bgp_timers_policy.current|length >= 2 + - query_ansible_bgp_timers_policy_1 is not changed + - query_ansible_bgp_timers_policy_1.current.0.bgpCtxPol.attributes.name == "ansible_bgp_timers_policy_1" + - query_ansible_bgp_timers_policy_1.current.0.bgpCtxPol.attributes.grCtrl == "" + - query_ansible_bgp_timers_policy_1.current.0.bgpCtxPol.attributes.holdIntvl == "360" + - query_ansible_bgp_timers_policy_1.current.0.bgpCtxPol.attributes.kaIntvl == "120" + - query_ansible_bgp_timers_policy_1.current.0.bgpCtxPol.attributes.maxAsLimit == "1" + - query_ansible_bgp_timers_policy_1.current.0.bgpCtxPol.attributes.staleIntvl == "600" + + - name: Remove BGP timers policy (check_mode) + aci_bgp_timers_policy: &bgp_timers_policy_absent + <<: *aci_bgp_timers_policy_present + state: absent + check_mode: true + register: cm_remove_bgp_timers_policy + + - name: Remove BGP timers policy (normal_mode) + aci_bgp_timers_policy: + <<: *bgp_timers_policy_absent + register: nm_remove_bgp_timers_policy + + - name: Remove BGP timers policy - testing idempotency + aci_bgp_timers_policy: + <<: *bgp_timers_policy_absent + register: nm_remove_bgp_timers_policy_idempotency + + - name: Asserts deletion tasks + assert: + that: + - cm_remove_bgp_timers_policy is changed + - cm_remove_bgp_timers_policy.proposed == {} + - nm_remove_bgp_timers_policy is changed + - nm_remove_bgp_timers_policy.previous != [] + - nm_remove_bgp_timers_policy.method == "DELETE" + - nm_remove_bgp_timers_policy_idempotency is not changed + - nm_remove_bgp_timers_policy_idempotency.previous == [] + + - name: Remove the ansible_tenant - cleanup before ending tests + aci_tenant: + <<: *aci_tenant_present + state: absent diff --git a/ansible_collections/cisco/aci/tests/integration/targets/aci_config_rollback/tasks/main.yml b/ansible_collections/cisco/aci/tests/integration/targets/aci_config_rollback/tasks/main.yml index df4c64069..9b43c9cfc 100644 --- a/ansible_collections/cisco/aci/tests/integration/targets/aci_config_rollback/tasks/main.yml +++ b/ansible_collections/cisco/aci/tests/integration/targets/aci_config_rollback/tasks/main.yml @@ -20,6 +20,9 @@ state: absent tenant: ansible_test +- name: Verify Cloud and Non-Cloud Sites in use. + include_tasks: ../../../../../../integration/targets/aci_cloud_provider/tasks/main.yml + - name: create a snapshot cisco.aci.aci_config_snapshot: &create_snapshot <<: *aci_tenant_absent @@ -49,62 +52,128 @@ state: query register: snapshots -- name: sort snapshot list - set_fact: - sorted_snapshots: '{{ snapshots.current.0.configSnapshotCont.children | sort(attribute="configSnapshot.attributes.createTime", reverse=True) }}' - -- name: compare snapshots - cisco.aci.aci_config_rollback: &preview_rollback - <<: *create_snapshot - state: preview - compare_export_policy: anstest - compare_snapshot: "{{ sorted_snapshots[0].configSnapshot.attributes.name }}" - snapshot: "{{ sorted_snapshots[1].configSnapshot.attributes.name }}" - register: rollback_preview - -- name: rollback to snapshot with missing parameters - cisco.aci.aci_config_rollback: &aci_rollback - <<: *create_snapshot - state: rollback - snapshot: "{{ sorted_snapshots[1].configSnapshot.attributes.name }}" - ignore_errors: true - register: rollback_missing_param - -- name: rollback to snapshot - cisco.aci.aci_config_rollback: - <<: *aci_rollback - import_policy: anstest - import_type: replace - import_mode: atomic - register: rollback_rollback - -- name: pause execution to let rollback take effect - pause: - seconds: 15 - -- name: ensure tenant doesn't exist after rollback - cisco.aci.aci_tenant: - <<: *aci_tenant_absent - register: tenant_removed - -- debug: - msg: '{{ rollback_preview }}' - -- name: rollback assertions - assert: - that: - - rollback_preview is not changed - - rollback_preview.preview.polUni.children[0].fvTenant.attributes.name == 'ansible_test' - - rollback_preview.preview.polUni.children[0].fvTenant.attributes.status == 'created' - - '"snapshots.diff.xml" in rollback_preview.url' - - rollback_missing_param is failed - - 'rollback_missing_param.msg == "state is rollback but all of the following are missing: import_policy"' - - rollback_rollback is changed - - '"ce2_" in rollback_rollback.sent.configImportP.attributes.fileName' - - '".tar.gz" in rollback_rollback.sent.configImportP.attributes.fileName' - - '"ce2_" in rollback_rollback.proposed.configImportP.attributes.fileName' - - '".tar.gz" in rollback_rollback.proposed.configImportP.attributes.fileName' - - '"fabric/configimp-anstest.json" in rollback_rollback.url' - - tenant_removed is not changed - - tenant_removed.previous == [] - - create_snapshot_annotation_check.current.0.configExportP.attributes.annotation == 'orchestrator:ansible' +- name: Execute tasks only for non-cloud sites + when: query_cloud.current != [] # This condition will execute only non-cloud sites + block: # block specifies execution of tasks within, based on conditions + # Snapshot compare does not work for cloud site since only 1 item is returned in configSnapshotCont + # TODO further investigate why cloud sites behave differently in saving snapshots + - name: verify only one snapshot is returned + ansible.builtin.assert: + that: + - snapshots.current.0.configSnapshotCont.children | length == 1 + +- name: Execute tasks only for non-cloud sites + when: query_cloud.current == [] # This condition will execute only non-cloud sites + block: # block specifies execution of tasks within, based on conditions + # Snapshot compare does not work for cloud site since only 1 item is returned in configSnapshotCont + - name: sort snapshot list + ansible.builtin.set_fact: + sorted_snapshots: '{{ snapshots.current.0.configSnapshotCont.children | sort(attribute="configSnapshot.attributes.createTime", reverse=True) }}' + + - name: Uninstall lxml for error checking + ansible.builtin.pip: &lxml + name: lxml + state: absent + ignore_errors: true # ignore errors to because of multiple executions for hosts at the same time + + - name: Uninstall xmljson for error checking + ansible.builtin.pip: &xmljson + name: xmljson + state: absent + ignore_errors: true # ignore errors to because of multiple executions for hosts at the same time + + - name: compare snapshots + cisco.aci.aci_config_rollback: &preview_rollback + <<: *create_snapshot + state: preview + compare_export_policy: anstest + compare_snapshot: "{{ sorted_snapshots[0].configSnapshot.attributes.name }}" + snapshot: "{{ sorted_snapshots[1].configSnapshot.attributes.name }}" + register: rollback_preview_xml + + - name: Install lxml + ansible.builtin.pip: + <<: *lxml + state: present + ignore_errors: true # ignore errors to because of multiple executions for hosts at the same time + + - name: Install xmljson + ansible.builtin.pip: + <<: *xmljson + state: present + ignore_errors: true # ignore errors to because of multiple executions for hosts at the same time + + - name: compare snapshots + cisco.aci.aci_config_rollback: + <<: *preview_rollback + register: rollback_preview_json + + - name: compare snapshots with wrong compare_snapshot (error) + cisco.aci.aci_config_rollback: + <<: *preview_rollback + compare_snapshot: wrong_snap + ignore_errors: true + register: err_rollback_preview_json + + - name: rollback to snapshot with missing parameters + cisco.aci.aci_config_rollback: &aci_rollback + <<: *create_snapshot + state: rollback + snapshot: "{{ sorted_snapshots[1].configSnapshot.attributes.name }}" + ignore_errors: true + register: rollback_missing_param + + - name: rollback to not existing snapshot for "tar.gz" append and "run-" removal testing + cisco.aci.aci_config_rollback: + <<: *aci_rollback + snapshot: "not-existing-snapshot.tar.gz" + import_policy: anstest + import_type: replace + import_mode: atomic + register: not_existing_rollback + ignore_errors: true + + - name: rollback to snapshot + cisco.aci.aci_config_rollback: + <<: *aci_rollback + import_policy: anstest + import_type: replace + import_mode: atomic + register: rollback_rollback + + - name: pause execution to let rollback take effect + pause: + seconds: 15 + + - name: ensure tenant doesn't exist after rollback + cisco.aci.aci_tenant: + <<: *aci_tenant_absent + register: tenant_removed + + - debug: + msg: '{{ rollback_preview_json }}' + + - name: rollback assertions + assert: + that: + - rollback_preview_xml is not changed + - '"ansible_test" in rollback_preview_xml.preview' + - '"created" in rollback_preview_xml.preview' + - '"snapshots.diff.xml" in rollback_preview_xml.url' + - err_rollback_preview_json.status == 400 + - err_rollback_preview_json.response == "HTTP Error 400{{":"}} Bad Request" + - rollback_preview_json is not changed + - rollback_preview_json.preview.polUni.children[0].fvTenant.attributes.name == 'ansible_test' + - rollback_preview_json.preview.polUni.children[0].fvTenant.attributes.status == 'created' + - '"snapshots.diff.xml" in rollback_preview_json.url' + - rollback_missing_param is failed + - 'rollback_missing_param.msg == "state is rollback but all of the following are missing: import_policy"' + - rollback_rollback is changed + - '"ce2_" in rollback_rollback.sent.configImportP.attributes.fileName' + - '".tar.gz" in rollback_rollback.sent.configImportP.attributes.fileName' + - '"ce2_" in rollback_rollback.proposed.configImportP.attributes.fileName' + - '".tar.gz" in rollback_rollback.proposed.configImportP.attributes.fileName' + - '"fabric/configimp-anstest.json" in rollback_rollback.url' + - tenant_removed is not changed + - tenant_removed.previous == [] + - create_snapshot_annotation_check.current.0.configExportP.attributes.annotation == 'orchestrator:ansible' diff --git a/ansible_collections/cisco/aci/tests/integration/targets/aci_config_snapshot/pki/admin.crt b/ansible_collections/cisco/aci/tests/integration/targets/aci_config_snapshot/pki/admin.crt new file mode 100644 index 000000000..cfac5531e --- /dev/null +++ b/ansible_collections/cisco/aci/tests/integration/targets/aci_config_snapshot/pki/admin.crt @@ -0,0 +1,14 @@ +-----BEGIN CERTIFICATE----- +MIICODCCAaGgAwIBAgIJAIt8XMntue0VMA0GCSqGSIb3DQEBCwUAMDQxDjAMBgNV +BAMMBUFkbWluMRUwEwYDVQQKDAxZb3VyIENvbXBhbnkxCzAJBgNVBAYTAlVTMCAX +DTE4MDEwOTAwNTk0NFoYDzIxMTcxMjE2MDA1OTQ0WjA0MQ4wDAYDVQQDDAVBZG1p +bjEVMBMGA1UECgwMWW91ciBDb21wYW55MQswCQYDVQQGEwJVUzCBnzANBgkqhkiG +9w0BAQEFAAOBjQAwgYkCgYEAohG/7axtt7CbSaMP7r+2mhTKbNgh0Ww36C7Ta14i +v+VmLyKkQHnXinKGhp6uy3Nug+15a+eIu7CrgpBVMQeCiWfsnwRocKcQJWIYDrWl +XHxGQn31yYKR6mylE7Dcj3rMFybnyhezr5D8GcP85YRPmwG9H2hO/0Y1FUnWu9Iw +AQkCAwEAAaNQME4wHQYDVR0OBBYEFD0jLXfpkrU/ChzRvfruRs/fy1VXMB8GA1Ud +IwQYMBaAFD0jLXfpkrU/ChzRvfruRs/fy1VXMAwGA1UdEwQFMAMBAf8wDQYJKoZI +hvcNAQELBQADgYEAOmvre+5tgZ0+F3DgsfxNQqLTrGiBgGCIymPkP/cBXXkNuJyl +3ac7tArHQc7WEA4U2R2rZbEq8FC3UJJm4nUVtCPvEh3G9OhN2xwYev79yt6pIn/l +KU0Td2OpVyo0eLqjoX5u2G90IBWzhyjFbo+CcKMrSVKj1YOdG0E3OuiJf00= +-----END CERTIFICATE----- diff --git a/ansible_collections/cisco/aci/tests/integration/targets/aci_config_snapshot/pki/admin.key b/ansible_collections/cisco/aci/tests/integration/targets/aci_config_snapshot/pki/admin.key new file mode 100644 index 000000000..63bb00cc0 --- /dev/null +++ b/ansible_collections/cisco/aci/tests/integration/targets/aci_config_snapshot/pki/admin.key @@ -0,0 +1,16 @@ +-----BEGIN PRIVATE KEY----- +MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBAKIRv+2sbbewm0mj +D+6/tpoUymzYIdFsN+gu02teIr/lZi8ipEB514pyhoaerstzboPteWvniLuwq4KQ +VTEHgoln7J8EaHCnECViGA61pVx8RkJ99cmCkepspROw3I96zBcm58oXs6+Q/BnD +/OWET5sBvR9oTv9GNRVJ1rvSMAEJAgMBAAECgYByu3QO0qF9h7X3JEu0Ld4cKBnB +giQ2uJC/et7KxIJ/LOvw9GopBthyt27KwG1ntBkJpkTuAaQHkyNns7vLkNB0S0IR ++owVFEcKYq9VCHTaiQU8TDp24gN+yPTrpRuH8YhDVq5SfVdVuTMgHVQdj4ya4VlF +Gj+a7+ipxtGiLsVGrQJBAM7p0Fm0xmzi+tBOASUAcVrPLcteFIaTBFwfq16dm/ON +00Khla8Et5kMBttTbqbukl8mxFjBEEBlhQqb6EdQQ0sCQQDIhHx1a9diG7y/4DQA +4KvR3FCYwP8PBORlSamegzCo+P1OzxiEo0amX7yQMA5UyiP/kUsZrme2JBZgna8S +p4R7AkEAr7rMhSOPUnMD6V4WgsJ5g1Jp5kqkzBaYoVUUSms5RASz4+cwJVCwTX91 +Y1jcpVIBZmaaY3a0wrx13ajEAa0dOQJBAIpjnb4wqpsEh7VpmJqOdSdGxb1XXfFQ +sA0T1OQYqQnFppWwqrxIL+d9pZdiA1ITnNqyvUFBNETqDSOrUHwwb2cCQGArE+vu +ffPUWQ0j+fiK+covFG8NL7H+26NSGB5+Xsn9uwOGLj7K/YT6CbBtr9hJiuWjM1Al +0V4ltlTuu2mTMaw= +-----END PRIVATE KEY----- diff --git a/ansible_collections/cisco/aci/tests/integration/targets/aci_config_snapshot/tasks/main.yml b/ansible_collections/cisco/aci/tests/integration/targets/aci_config_snapshot/tasks/main.yml index 22bcbcb79..c9415a86d 100644 --- a/ansible_collections/cisco/aci/tests/integration/targets/aci_config_snapshot/tasks/main.yml +++ b/ansible_collections/cisco/aci/tests/integration/targets/aci_config_snapshot/tasks/main.yml @@ -146,3 +146,82 @@ - delete_idempotent.previous == [] - delete_missing_param is failed - 'delete_missing_param.msg == "state is absent but all of the following are missing: snapshot"' + +# Create, query and delete snapshot with certificate authentication Ref# 427 +- name: Set vars + set_fact: + aci_info: &aci_info + host: "{{ aci_hostname }}" + username: "{{ aci_username }}" + validate_certs: "{{ aci_validate_certs | default(false) }}" + use_ssl: "{{ aci_use_ssl | default(true) }}" + use_proxy: "{{ aci_use_proxy | default(true) }}" + output_level: debug + +- name: Add a user certificate to be used in the test + cisco.aci.aci_aaa_user_certificate: + <<: *aci_info + password: "{{ aci_password }}" + aaa_user: "{{ aci_username }}" + name: admin + certificate: "{{ lookup('file', 'pki/admin.crt') }}" + state: present + +- name: create a snapshot using the private key + cisco.aci.aci_config_snapshot: + <<: *aci_info + private_key: "{{ lookup('file', 'pki/admin.key') }}" + export_policy: anstest + include_secure: false + format: json + description: ansible test + register: create_private_key + +- name: creation assertion tests with private key + assert: + that: + - create_private_key is not failed + - create_private_key is changed + - create_private_key.sent.configExportP.attributes.adminSt == "triggered" + +- name: query the snapshot using the private key + cisco.aci.aci_config_snapshot: + <<: *aci_info + private_key: "{{ lookup('file', 'pki/admin.key') }}" + export_policy: anstest + state: query + register: query_private_key + +- name: generate snapshot name using query_private_key + set_fact: + test_snapshot_private_key: "{{ query_private_key.current.0.configSnapshotCont.children.0.configSnapshot.attributes.rn.strip('snapshot-') }}" + +- name: query assertion tests with private key + assert: + that: + - query_private_key is not failed + - query_private_key is not changed + - '"snapshots-[uni/fabric/configexp-anstest].json" in query_private_key.url' + +- name: delete using the private key + cisco.aci.aci_config_snapshot: + <<: *aci_info + private_key: "{{ lookup('file', 'pki/admin.key') }}" + export_policy: anstest + snapshot: "{{ test_snapshot_private_key }}" + state: absent + register: delete_snapshot_private_key + +- name: delete assertion tests with the private key + assert: + that: + - delete_snapshot_private_key is not failed + - delete_snapshot_private_key is changed + +- name: Remove the user certificate + cisco.aci.aci_aaa_user_certificate: + <<: *aci_info + password: "{{ aci_password }}" + aaa_user: "{{ aci_username }}" + name: admin + state: absent diff --git a/ansible_collections/cisco/aci/tests/integration/targets/aci_contract_subject_to_filter/tasks/main.yml b/ansible_collections/cisco/aci/tests/integration/targets/aci_contract_subject_to_filter/tasks/main.yml index f7e69e620..d21571692 100644 --- a/ansible_collections/cisco/aci/tests/integration/targets/aci_contract_subject_to_filter/tasks/main.yml +++ b/ansible_collections/cisco/aci/tests/integration/targets/aci_contract_subject_to_filter/tasks/main.yml @@ -129,6 +129,13 @@ state: query register: query_binding + - name: query binding with non_existing subject + cisco.aci.aci_contract_subject_to_filter: + <<: *aci_subject_filter_present + state: query + subject: non_existing + register: query_binding_non_existing_subject + - name: query assertions assert: that: @@ -137,6 +144,7 @@ - query_all.current.0.vzRsSubjFiltAtt is defined - query_binding is not changed - query_binding.current != [] + - query_binding_non_existing_subject.current == [] - name: delete subject filter binding - check mode works cisco.aci.aci_contract_subject_to_filter: &aci_subject_filter_absent diff --git a/ansible_collections/cisco/aci/tests/integration/targets/aci_dhcp_relay/tasks/main.yml b/ansible_collections/cisco/aci/tests/integration/targets/aci_dhcp_relay/tasks/main.yml index 920a24969..da10cc2d8 100644 --- a/ansible_collections/cisco/aci/tests/integration/targets/aci_dhcp_relay/tasks/main.yml +++ b/ansible_collections/cisco/aci/tests/integration/targets/aci_dhcp_relay/tasks/main.yml @@ -1,5 +1,6 @@ # Test code for the ACI modules # Copyright: (c) 2021, Tim Cragg(@timcragg) +# Copyright: (c) 2023, Akini Ross(@akinross) # GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) @@ -26,6 +27,12 @@ tenant: ansible_tenant state: absent +- name: Delete Global DHCP relay policy + cisco.aci.aci_dhcp_relay: + <<: *aci_info + name: ansible_global_dhcp_relay + state: absent + - name: Add a new tenant aci_tenant: <<: *aci_info @@ -91,6 +98,32 @@ - update_dhcp_relay.current.0.dhcpRelayP.attributes.owner == "tenant" - update_dhcp_relay.current.0.dhcpRelayP.attributes.descr == "New Ansible DHCP Relay" +- name: Add another DHCP relay policy for query all + cisco.aci.aci_dhcp_relay: + <<: *aci_info + tenant: ansible_tenant + name: ansible_dhcp_relay_2 + state: present + +# CREATE GLOBAL DHCP RELAY +- name: Add a new Global DHCP relay policy + cisco.aci.aci_dhcp_relay: + <<: *aci_info + name: ansible_global_dhcp_relay + description: Ansible Global DHCP Relay + state: present + register: add_global_dhcp_relay + +- name: Verify Global DHCP Relay creation + assert: + that: + - add_global_dhcp_relay is changed + - add_global_dhcp_relay.current.0.dhcpRelayP.attributes.annotation == 'orchestrator:ansible' + - add_global_dhcp_relay.current.0.dhcpRelayP.attributes.dn == "uni/infra/relayp-ansible_global_dhcp_relay" + - add_global_dhcp_relay.current.0.dhcpRelayP.attributes.name == "ansible_global_dhcp_relay" + - add_global_dhcp_relay.current.0.dhcpRelayP.attributes.owner == "infra" + - add_global_dhcp_relay.current.0.dhcpRelayP.attributes.descr == "Ansible Global DHCP Relay" + # QUERY DHCP RELAY - name: Query DHCP relay policy cisco.aci.aci_dhcp_relay: @@ -120,6 +153,36 @@ assert: that: - query_dhcp_relay_all is not changed + - query_dhcp_relay_all.current.0.fvTenant.children | length >= 2 + +# QUERY GLOBAL DHCP RELAY +- name: Query Global DHCP relay policy + cisco.aci.aci_dhcp_relay: + <<: *aci_info + name: ansible_global_dhcp_relay + state: query + register: query_global_dhcp_relay + +- name: Verify Global DHCP Relay query + assert: + that: + - query_global_dhcp_relay is not changed + - query_global_dhcp_relay.current.0.dhcpRelayP.attributes.dn == "uni/infra/relayp-ansible_global_dhcp_relay" + - query_global_dhcp_relay.current.0.dhcpRelayP.attributes.name == "ansible_global_dhcp_relay" + - query_global_dhcp_relay.current.0.dhcpRelayP.attributes.owner == "infra" + - query_global_dhcp_relay.current.0.dhcpRelayP.attributes.descr == "Ansible Global DHCP Relay" + +- name: Query all DHCP relays + cisco.aci.aci_dhcp_relay: + <<: *aci_info + state: query + register: query_global_dhcp_relay_all + +- name: Verify query idempotence for Global DHCP Relay + assert: + that: + - query_global_dhcp_relay_all is not changed + - query_global_dhcp_relay_all.current | length >= 3 # DELETE DHCP RELAY - name: Delete DHCP relay policy @@ -153,6 +216,24 @@ that: - delete_dhcp_relay_again is not changed +# DELETE GLOBAL DHCP RELAY +- name: Delete Global DHCP relay policy + cisco.aci.aci_dhcp_relay: + <<: *aci_info + name: ansible_global_dhcp_relay + state: absent + register: delete_global_dhcp_relay + +- name: Verify Global DHCP Relay deletion + assert: + that: + - delete_global_dhcp_relay is changed + - delete_global_dhcp_relay.current == [] + - delete_global_dhcp_relay.previous.0.dhcpRelayP.attributes.dn == "uni/infra/relayp-ansible_global_dhcp_relay" + - delete_global_dhcp_relay.previous.0.dhcpRelayP.attributes.name == "ansible_global_dhcp_relay" + - delete_global_dhcp_relay.previous.0.dhcpRelayP.attributes.owner == "infra" + - delete_global_dhcp_relay.previous.0.dhcpRelayP.attributes.descr == "Ansible Global DHCP Relay" + # CLEAN ENVIRONMENT AGAIN - name: Remove the ansible_tenant aci_tenant: diff --git a/ansible_collections/cisco/aci/tests/integration/targets/aci_dhcp_relay_provider/tasks/main.yml b/ansible_collections/cisco/aci/tests/integration/targets/aci_dhcp_relay_provider/tasks/main.yml index 2edc45e51..c133e23ba 100644 --- a/ansible_collections/cisco/aci/tests/integration/targets/aci_dhcp_relay_provider/tasks/main.yml +++ b/ansible_collections/cisco/aci/tests/integration/targets/aci_dhcp_relay_provider/tasks/main.yml @@ -1,5 +1,6 @@ # Test code for the ACI modules # Copyright: (c) 2021, Tim Cragg(@timcragg) +# Copyright: (c) 2023, Akini Ross (@akinross) # GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) @@ -29,6 +30,12 @@ tenant: ansible_tenant state: absent +- name: Delete Global DHCP relay policy + cisco.aci.aci_dhcp_relay: + <<: *aci_info + name: ansible_global_dhcp_relay + state: absent + - name: Remove l2ext domain cisco.aci.aci_domain: <<: *aci_info @@ -153,6 +160,15 @@ description: Ansible DHCP Relay state: present + # CREATE GLOBAL DHCP RELAY + - name: Add a new Global DHCP relay policy + cisco.aci.aci_dhcp_relay: + <<: *aci_info + name: ansible_global_dhcp_relay + description: Ansible Global DHCP Relay + state: present + register: add_global_dhcp_relay + # CREATE DHCP RELAY PROVIDERS - name: Add a new DHCP relay App EPG provider cisco.aci.aci_dhcp_relay_provider: @@ -361,7 +377,6 @@ - update_dn_relay_provider.current.0.dhcpRsProv.attributes.addr == "10.20.30.53" - update_dn_relay_provider.current.0.dhcpRsProv.attributes.tDn == "uni/tn-ansible_tenant/ap-ansible_ap/epg-ansible_epg_2" - # QUERY DHCP RELAY PROVIDERS - name: Query DHCP relay App EPG provider cisco.aci.aci_dhcp_relay_provider: @@ -405,6 +420,13 @@ dn: "uni/tn-ansible_tenant/ap-ansible_ap/epg-ansible_epg_2" state: query register: query_dn_relay_provider + + - name: Query all DHCP relays in ansible_tenant + cisco.aci.aci_dhcp_relay_provider: + <<: *aci_info + tenant: ansible_tenant + state: query + register: query_all_relay_provider - name: Confirm DHCP relay provider query assert: @@ -425,6 +447,161 @@ - query_dn_relay_provider.current.0.dhcpRsProv.attributes.dn == "uni/tn-ansible_tenant/relayp-ansible_dhcp_relay/rsprov-[uni/tn-ansible_tenant/ap-ansible_ap/epg-ansible_epg_2]" - query_dn_relay_provider.current.0.dhcpRsProv.attributes.addr == "10.20.30.53" - query_dn_relay_provider.current.0.dhcpRsProv.attributes.tDn == "uni/tn-ansible_tenant/ap-ansible_ap/epg-ansible_epg_2" + - query_all_relay_provider is not changed + - query_all_relay_provider.current.0.fvTenant.children.0.dhcpRelayP.children | length >= 4 + + # CREATE GLOBAL DHCP RELAY PROVIDERS + - name: Add a new Global DHCP relay App EPG provider without provider tenant (error) + cisco.aci.aci_dhcp_relay_provider: + <<: *aci_info + relay_policy: ansible_global_dhcp_relay + epg_type: epg + anp: ansible_ap + app_epg: ansible_epg + dhcp_server_addr: 10.20.30.40 + state: present + ignore_errors: true + register: err_global_epg_relay_provider + + - name: Add a new Global DHCP relay App EPG provider + cisco.aci.aci_dhcp_relay_provider: + <<: *aci_info + relay_policy: ansible_global_dhcp_relay + epg_type: epg + provider_tenant: ansible_tenant + anp: ansible_ap + app_epg: ansible_epg + dhcp_server_addr: 10.20.30.40 + state: present + register: add_global_epg_relay_provider + + - name: Add a new Global DHCP relay L2out provider + cisco.aci.aci_dhcp_relay_provider: + <<: *aci_info + relay_policy: ansible_global_dhcp_relay + epg_type: l2_external + provider_tenant: ansible_tenant + l2out_name: ansible_l2out + external_net: ansible_l2out_ext_net + dhcp_server_addr: 10.20.30.41 + state: present + register: add_global_l2_relay_provider + + - name: Add a new Global DHCP relay L3out provider + cisco.aci.aci_dhcp_relay_provider: + <<: *aci_info + relay_policy: ansible_global_dhcp_relay + epg_type: l3_external + provider_tenant: ansible_tenant + l3out_name: ansible_l3out + external_net: ansible_l3out_ext_net + dhcp_server_addr: 10.20.30.42 + state: present + register: add_global_l3_relay_provider + + - name: Add a new Global DHCP relay dn provider + cisco.aci.aci_dhcp_relay_provider: + <<: *aci_info + relay_policy: ansible_global_dhcp_relay + epg_type: dn + dn: "uni/tn-ansible_tenant/ap-ansible_ap/epg-ansible_epg_2" + dhcp_server_addr: 10.20.30.43 + state: present + register: add_global_dn_relay_provider + + - name: Confirm Global DHCP relay provider creation + assert: + that: + - err_global_epg_relay_provider is failed + - err_global_epg_relay_provider.msg == "provider_tenant is required when epg_type is epg" + - add_global_epg_relay_provider is changed + - add_global_epg_relay_provider.current.0.dhcpRsProv.attributes.annotation == 'orchestrator:ansible' + - add_global_epg_relay_provider.current.0.dhcpRsProv.attributes.dn == "uni/infra/relayp-ansible_global_dhcp_relay/rsprov-[uni/tn-ansible_tenant/ap-ansible_ap/epg-ansible_epg]" + - add_global_epg_relay_provider.current.0.dhcpRsProv.attributes.addr == "10.20.30.40" + - add_global_epg_relay_provider.current.0.dhcpRsProv.attributes.tDn == "uni/tn-ansible_tenant/ap-ansible_ap/epg-ansible_epg" + - add_global_l2_relay_provider is changed + - add_global_l2_relay_provider.current.0.dhcpRsProv.attributes.dn == "uni/infra/relayp-ansible_global_dhcp_relay/rsprov-[uni/tn-ansible_tenant/l2out-ansible_l2out/instP-ansible_l2out_ext_net]" + - add_global_l2_relay_provider.current.0.dhcpRsProv.attributes.addr == "10.20.30.41" + - add_global_l2_relay_provider.current.0.dhcpRsProv.attributes.tDn == "uni/tn-ansible_tenant/l2out-ansible_l2out/instP-ansible_l2out_ext_net" + - add_global_l3_relay_provider is changed + - add_global_l3_relay_provider.current.0.dhcpRsProv.attributes.dn == "uni/infra/relayp-ansible_global_dhcp_relay/rsprov-[uni/tn-ansible_tenant/out-ansible_l3out/instP-ansible_l3out_ext_net]" + - add_global_l3_relay_provider.current.0.dhcpRsProv.attributes.addr == "10.20.30.42" + - add_global_l3_relay_provider.current.0.dhcpRsProv.attributes.tDn == "uni/tn-ansible_tenant/out-ansible_l3out/instP-ansible_l3out_ext_net" + - add_global_dn_relay_provider is changed + - add_global_dn_relay_provider.current.0.dhcpRsProv.attributes.dn == "uni/infra/relayp-ansible_global_dhcp_relay/rsprov-[uni/tn-ansible_tenant/ap-ansible_ap/epg-ansible_epg_2]" + - add_global_dn_relay_provider.current.0.dhcpRsProv.attributes.addr == "10.20.30.43" + - add_global_dn_relay_provider.current.0.dhcpRsProv.attributes.tDn == "uni/tn-ansible_tenant/ap-ansible_ap/epg-ansible_epg_2" + + # QUERY GLOBAL DHCP RELAY PROVIDERS + - name: Query Global DHCP relay App EPG provider + cisco.aci.aci_dhcp_relay_provider: + <<: *aci_info + relay_policy: ansible_global_dhcp_relay + provider_tenant: ansible_tenant + epg_type: epg + anp: ansible_ap + app_epg: ansible_epg + state: query + register: query_global_epg_relay_provider + + - name: Query Global DHCP relay L2out provider + cisco.aci.aci_dhcp_relay_provider: + <<: *aci_info + relay_policy: ansible_global_dhcp_relay + provider_tenant: ansible_tenant + epg_type: l2_external + l2out_name: ansible_l2out + external_net: ansible_l2out_ext_net + state: query + register: query_global_l2_relay_provider + + - name: Query Global DHCP relay L3out provider + cisco.aci.aci_dhcp_relay_provider: + <<: *aci_info + relay_policy: ansible_global_dhcp_relay + provider_tenant: ansible_tenant + epg_type: l3_external + l3out_name: ansible_l3out + external_net: ansible_l3out_ext_net + state: query + register: query_global_l3_relay_provider + + - name: Query Global DHCP relay dn provider + cisco.aci.aci_dhcp_relay_provider: + <<: *aci_info + relay_policy: ansible_global_dhcp_relay + epg_type: dn + dn: "uni/tn-ansible_tenant/ap-ansible_ap/epg-ansible_epg_2" + state: query + register: query_global_dn_relay_provider + + - name: Query all Global DHCP relay providers + cisco.aci.aci_dhcp_relay_provider: + <<: *aci_info + state: query + register: query_global_all_relay_provider + + - name: Confirm DHCP relay provider query + assert: + that: + - query_global_epg_relay_provider is not changed + - query_global_epg_relay_provider.current.0.dhcpRsProv.attributes.dn == "uni/infra/relayp-ansible_global_dhcp_relay/rsprov-[uni/tn-ansible_tenant/ap-ansible_ap/epg-ansible_epg]" + - query_global_epg_relay_provider.current.0.dhcpRsProv.attributes.addr == "10.20.30.40" + - query_global_epg_relay_provider.current.0.dhcpRsProv.attributes.tDn == "uni/tn-ansible_tenant/ap-ansible_ap/epg-ansible_epg" + - query_global_l2_relay_provider is not changed + - query_global_l2_relay_provider.current.0.dhcpRsProv.attributes.dn == "uni/infra/relayp-ansible_global_dhcp_relay/rsprov-[uni/tn-ansible_tenant/l2out-ansible_l2out/instP-ansible_l2out_ext_net]" + - query_global_l2_relay_provider.current.0.dhcpRsProv.attributes.addr == "10.20.30.41" + - query_global_l2_relay_provider.current.0.dhcpRsProv.attributes.tDn == "uni/tn-ansible_tenant/l2out-ansible_l2out/instP-ansible_l2out_ext_net" + - query_global_l3_relay_provider is not changed + - query_global_l3_relay_provider.current.0.dhcpRsProv.attributes.dn == "uni/infra/relayp-ansible_global_dhcp_relay/rsprov-[uni/tn-ansible_tenant/out-ansible_l3out/instP-ansible_l3out_ext_net]" + - query_global_l3_relay_provider.current.0.dhcpRsProv.attributes.addr == "10.20.30.42" + - query_global_l3_relay_provider.current.0.dhcpRsProv.attributes.tDn == "uni/tn-ansible_tenant/out-ansible_l3out/instP-ansible_l3out_ext_net" + - query_global_dn_relay_provider is not changed + - query_global_dn_relay_provider.current.0.dhcpRsProv.attributes.dn == "uni/infra/relayp-ansible_global_dhcp_relay/rsprov-[uni/tn-ansible_tenant/ap-ansible_ap/epg-ansible_epg_2]" + - query_global_dn_relay_provider.current.0.dhcpRsProv.attributes.addr == "10.20.30.43" + - query_global_dn_relay_provider.current.0.dhcpRsProv.attributes.tDn == "uni/tn-ansible_tenant/ap-ansible_ap/epg-ansible_epg_2" + - query_global_all_relay_provider is not changed + - query_global_all_relay_provider.current | length >= 8 # DELETE DHCP RELAY PROVIDERS - name: Delete DHCP relay App EPG provider @@ -471,7 +648,6 @@ state: absent register: delete_dn_relay_provider - - name: Confirm DHCP relay provider removal assert: that: @@ -548,6 +724,72 @@ - delete_l3_relay_provider_again is not changed - delete_dn_relay_provider_again is not changed + # DELETE GLOBAL DHCP RELAY PROVIDERS + - name: Delete Global DHCP relay App EPG provider + cisco.aci.aci_dhcp_relay_provider: + <<: *aci_info + relay_policy: ansible_global_dhcp_relay + provider_tenant: ansible_tenant + epg_type: epg + anp: ansible_ap + app_epg: ansible_epg + state: absent + register: delete_global_epg_relay_provider + + - name: Delete Global DHCP relay L2out provider + cisco.aci.aci_dhcp_relay_provider: + <<: *aci_info + relay_policy: ansible_global_dhcp_relay + provider_tenant: ansible_tenant + epg_type: l2_external + l2out_name: ansible_l2out + external_net: ansible_l2out_ext_net + state: absent + register: delete_global_l2_relay_provider + + - name: Delete Global DHCP relay L3out provider + cisco.aci.aci_dhcp_relay_provider: + <<: *aci_info + relay_policy: ansible_global_dhcp_relay + provider_tenant: ansible_tenant + epg_type: l3_external + l3out_name: ansible_l3out + external_net: ansible_l3out_ext_net + state: absent + register: delete_global_l3_relay_provider + + - name: Delete Global DHCP relay dn provider + cisco.aci.aci_dhcp_relay_provider: + <<: *aci_info + relay_policy: ansible_global_dhcp_relay + epg_type: dn + dn: "uni/tn-ansible_tenant/ap-ansible_ap/epg-ansible_epg_2" + state: absent + register: delete_global_dn_relay_provider + + - name: Confirm DHCP relay provider removal + assert: + that: + - delete_global_epg_relay_provider is changed + - delete_global_epg_relay_provider.current == [] + - delete_global_epg_relay_provider.previous.0.dhcpRsProv.attributes.dn == "uni/infra/relayp-ansible_global_dhcp_relay/rsprov-[uni/tn-ansible_tenant/ap-ansible_ap/epg-ansible_epg]" + - delete_global_epg_relay_provider.previous.0.dhcpRsProv.attributes.addr == "10.20.30.40" + - delete_global_epg_relay_provider.previous.0.dhcpRsProv.attributes.tDn == "uni/tn-ansible_tenant/ap-ansible_ap/epg-ansible_epg" + - delete_global_l2_relay_provider is changed + - delete_global_l2_relay_provider.current == [] + - delete_global_l2_relay_provider.previous.0.dhcpRsProv.attributes.dn == "uni/infra/relayp-ansible_global_dhcp_relay/rsprov-[uni/tn-ansible_tenant/l2out-ansible_l2out/instP-ansible_l2out_ext_net]" + - delete_global_l2_relay_provider.previous.0.dhcpRsProv.attributes.addr == "10.20.30.41" + - delete_global_l2_relay_provider.previous.0.dhcpRsProv.attributes.tDn == "uni/tn-ansible_tenant/l2out-ansible_l2out/instP-ansible_l2out_ext_net" + - delete_global_l3_relay_provider is changed + - delete_global_l3_relay_provider.current == [] + - delete_global_l3_relay_provider.previous.0.dhcpRsProv.attributes.dn == "uni/infra/relayp-ansible_global_dhcp_relay/rsprov-[uni/tn-ansible_tenant/out-ansible_l3out/instP-ansible_l3out_ext_net]" + - delete_global_l3_relay_provider.previous.0.dhcpRsProv.attributes.addr == "10.20.30.42" + - delete_global_l3_relay_provider.previous.0.dhcpRsProv.attributes.tDn == "uni/tn-ansible_tenant/out-ansible_l3out/instP-ansible_l3out_ext_net" + - delete_global_dn_relay_provider is changed + - delete_global_dn_relay_provider.previous.0.dhcpRsProv.attributes.dn == "uni/infra/relayp-ansible_global_dhcp_relay/rsprov-[uni/tn-ansible_tenant/ap-ansible_ap/epg-ansible_epg_2]" + - delete_global_dn_relay_provider.previous.0.dhcpRsProv.attributes.addr == "10.20.30.43" + - delete_global_dn_relay_provider.previous.0.dhcpRsProv.attributes.tDn == "uni/tn-ansible_tenant/ap-ansible_ap/epg-ansible_epg_2" + # CLEAN ENVIRONMENT AGAIN - name: Remove the ansible_tenant aci_tenant: @@ -555,6 +797,12 @@ tenant: ansible_tenant state: absent + - name: Delete Global DHCP relay policy + cisco.aci.aci_dhcp_relay: + <<: *aci_info + name: ansible_global_dhcp_relay + state: absent + - name: Remove l2ext domain cisco.aci.aci_domain: <<: *aci_info diff --git a/ansible_collections/cisco/aci/tests/integration/targets/aci_dns_domain/tasks/main.yml b/ansible_collections/cisco/aci/tests/integration/targets/aci_dns_domain/tasks/main.yml index 2d3de4dd0..af9c1026b 100644 --- a/ansible_collections/cisco/aci/tests/integration/targets/aci_dns_domain/tasks/main.yml +++ b/ansible_collections/cisco/aci/tests/integration/targets/aci_dns_domain/tasks/main.yml @@ -26,7 +26,6 @@ <<: *aci_info profile_name: ansible_dns_profile state: absent - delegate_to: localhost # ADD DNS PROFILE - name: Add DNS profile @@ -34,7 +33,6 @@ <<: *aci_info profile_name: ansible_dns_profile state: present - delegate_to: localhost # ADD DNS DOMAIN - name: Add a new DNS domain @@ -44,7 +42,6 @@ domain: example.com default: false state: present - delegate_to: localhost register: add_dns_domain - name: Verify DNS domain creation @@ -64,7 +61,6 @@ domain: example.com default: false state: present - delegate_to: localhost register: add_dns_domain_again - name: Verify DNS domain creation idempotence @@ -83,7 +79,6 @@ domain: example.com default: true state: present - delegate_to: localhost register: update_dns_domain - name: Verify DNS domain update @@ -101,7 +96,6 @@ dns_profile: ansible_dns_profile domain: example.com state: query - delegate_to: localhost register: query_dns_domain - name: Verify DNS domain attributes @@ -118,7 +112,6 @@ <<: *aci_info dns_profile: ansible_dns_profile state: query - delegate_to: localhost register: query_dns_domain_all - name: Verify DNS domain query idempotence @@ -133,7 +126,6 @@ dns_profile: ansible_dns_profile domain: example.com state: absent - delegate_to: localhost register: delete_dns_domain - name: Verify DNS domain deletion @@ -152,7 +144,6 @@ dns_profile: ansible_dns_profile domain: example.com state: absent - delegate_to: localhost register: delete_dns_domain_again - name: Verify DNS domain deletion idempotence @@ -166,4 +157,3 @@ <<: *aci_info profile_name: ansible_dns_profile state: absent - delegate_to: localhost diff --git a/ansible_collections/cisco/aci/tests/integration/targets/aci_dns_provider/tasks/main.yml b/ansible_collections/cisco/aci/tests/integration/targets/aci_dns_provider/tasks/main.yml index 8deca651c..3e31d2b52 100644 --- a/ansible_collections/cisco/aci/tests/integration/targets/aci_dns_provider/tasks/main.yml +++ b/ansible_collections/cisco/aci/tests/integration/targets/aci_dns_provider/tasks/main.yml @@ -26,7 +26,6 @@ <<: *aci_info profile_name: ansible_dns_profile state: absent - delegate_to: localhost # ADD DNS PROFILE - name: Add DNS profile @@ -34,7 +33,6 @@ <<: *aci_info profile_name: ansible_dns_profile state: present - delegate_to: localhost # ADD DNS PROVIDER - name: Add a new DNS provider @@ -44,7 +42,6 @@ addr: 10.20.30.40 preferred: false state: present - delegate_to: localhost register: add_dns_provider - name: Verify DNS provider creation @@ -64,7 +61,6 @@ addr: 10.20.30.40 preferred: false state: present - delegate_to: localhost register: add_dns_provider_again - name: Verify DNS provider creation idempotence @@ -83,7 +79,6 @@ addr: 10.20.30.40 preferred: true state: present - delegate_to: localhost register: update_dns_provider - name: Verify DNS provider update @@ -101,7 +96,6 @@ dns_profile: ansible_dns_profile addr: 10.20.30.40 state: query - delegate_to: localhost register: query_dns_provider - name: Verify DNS provider attributes @@ -118,7 +112,6 @@ <<: *aci_info dns_profile: ansible_dns_profile state: query - delegate_to: localhost register: query_dns_provider_all - name: Verify DNS provider query idempotence @@ -133,7 +126,6 @@ dns_profile: ansible_dns_profile addr: 10.20.30.40 state: absent - delegate_to: localhost register: delete_dns_provider - name: Verify DNS provider deletion @@ -152,7 +144,6 @@ dns_profile: ansible_dns_profile addr: 10.20.30.40 state: absent - delegate_to: localhost register: delete_dns_provider_again - name: Verify DNS provider deletion idempotence @@ -166,4 +157,3 @@ <<: *aci_info profile_name: ansible_dns_profile state: absent - delegate_to: localhost diff --git a/ansible_collections/cisco/aci/tests/integration/targets/aci_domain_to_vlan_pool/tasks/main.yml b/ansible_collections/cisco/aci/tests/integration/targets/aci_domain_to_vlan_pool/tasks/main.yml index 8c31fd4cc..ab10856a8 100644 --- a/ansible_collections/cisco/aci/tests/integration/targets/aci_domain_to_vlan_pool/tasks/main.yml +++ b/ansible_collections/cisco/aci/tests/integration/targets/aci_domain_to_vlan_pool/tasks/main.yml @@ -3,6 +3,22 @@ # GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) +- name: Test that we have an ACI APIC host, ACI username and ACI password + fail: + msg: 'Please define the following variables: aci_hostname, aci_username and aci_password.' + when: aci_hostname is not defined or aci_username is not defined or aci_password is not defined + +- name: Set vars + ansible.builtin.set_fact: + aci_info: &aci_info + host: '{{ aci_hostname }}' + username: '{{ aci_username }}' + password: '{{ aci_password }}' + validate_certs: '{{ aci_validate_certs | default(false) }}' + use_ssl: '{{ aci_use_ssl | default(true) }}' + use_proxy: '{{ aci_use_proxy | default(true) }}' + output_level: '{{ aci_output_level | default("info") }}' + - name: Verify Cloud and Non-Cloud Sites in use. include_tasks: ../../../../../../integration/targets/aci_cloud_provider/tasks/main.yml @@ -12,41 +28,36 @@ # CLEAN ENVIRONMENT - name: Remove domain to VLAN pool binding cisco.aci.aci_domain_to_vlan_pool: &binding_absent - host: '{{ aci_hostname }}' - username: '{{ aci_username }}' - password: '{{ aci_password }}' - validate_certs: '{{ aci_validate_certs | default(false) }}' - use_ssl: '{{ aci_use_ssl | default(true) }}' - use_proxy: '{{ aci_use_proxy | default(true) }}' - output_level: '{{ aci_output_level | default("info") }}' + <<: *aci_info domain: phys_dom domain_type: phys pool: test_pool pool_allocation_mode: dynamic state: absent - - name: Remove physical domain + - name: Remove domains cisco.aci.aci_domain: - host: "{{ aci_hostname }}" - username: "{{ aci_username }}" - password: "{{ aci_password }}" - validate_certs: '{{ aci_validate_certs | default(false) }}' - use_ssl: '{{ aci_use_ssl | default(true) }}' - use_proxy: '{{ aci_use_proxy | default(true) }}' - output_level: '{{ aci_output_level | default("info") }}' - domain: phys_dom - domain_type: phys + <<: *aci_info + domain: "{{ item.domain }}" + domain_type: "{{ item.domain_type }}" + state: absent + loop: + - {domain: phys_dom, domain_type: phys} + - {domain: fc_dom, domain_type: fc} + - {domain: l2dom_dom, domain_type: l2dom} + - {domain: l3dom_dom, domain_type: l3dom} + + - name: Remove VMM domain + cisco.aci.aci_domain: + <<: *aci_info + domain: anstest + domain_type: vmm + vm_provider: vmware state: absent - name: Create VLAN pool cisco.aci.aci_vlan_pool: - host: "{{ aci_hostname }}" - username: "{{ aci_username }}" - password: "{{ aci_password }}" - validate_certs: '{{ aci_validate_certs | default(false) }}' - use_ssl: '{{ aci_use_ssl | default(true) }}' - use_proxy: '{{ aci_use_proxy | default(true) }}' - output_level: '{{ aci_output_level | default("info") }}' + <<: *aci_info pool: test_pool pool_allocation_mode: dynamic description: Test VLAN pool @@ -56,13 +67,7 @@ # ADD BINDING - name: Add domain to VLAN pool binding (check_mode) cisco.aci.aci_domain_to_vlan_pool: &binding_present - host: '{{ aci_hostname }}' - username: '{{ aci_username }}' - password: '{{ aci_password }}' - validate_certs: '{{ aci_validate_certs | default(false) }}' - use_ssl: '{{ aci_use_ssl | default(true) }}' - use_proxy: '{{ aci_use_proxy | default(true) }}' - output_level: '{{ aci_output_level | default("info") }}' + <<: *aci_info domain: phys_dom domain_type: phys pool: test_pool @@ -105,17 +110,67 @@ - cm_add_binding_again is not changed - nm_add_binding_again is not changed + - name: Add domain to VLAN pool binding with domain type not vmm and with vm provider (error) + cisco.aci.aci_domain_to_vlan_pool: + <<: *binding_present + domain: phys_dom + domain_type: phys + vm_provider: vmware + ignore_errors: true + register: err_not_vmm_with_vm_provider + + - name: Add domain to VLAN pool binding with domain type fc + cisco.aci.aci_domain_to_vlan_pool: + <<: *binding_present + domain: fc_dom + domain_type: fc + pool_allocation_mode: static + register: nm_fc_type + + - name: Add domain to VLAN pool binding with domain type l2dom + cisco.aci.aci_domain_to_vlan_pool: + <<: *binding_present + domain: l2dom_dom + domain_type: l2dom + register: nm_l2dom_type + + - name: Add domain to VLAN pool binding with domain type l3dom + cisco.aci.aci_domain_to_vlan_pool: + <<: *binding_present + domain: l3dom_dom + domain_type: l3dom + register: nm_l3dom_type + + - name: Add domain to VLAN pool binding with domain type vmm + cisco.aci.aci_domain_to_vlan_pool: + <<: *binding_present + domain: anstest + domain_type: vmm + vm_provider: vmware + register: nm_vmm_type + + - name: Verify bindings with domain types + assert: + that: + - err_not_vmm_with_vm_provider is not changed + - err_not_vmm_with_vm_provider.msg == "Domain type 'phys' cannot have a 'vm_provider'" + - nm_fc_type is changed + - nm_fc_type.current.0.fcDomP.attributes.dn == "uni/fc-fc_dom" + - nm_fc_type.current.0.fcDomP.children.0.infraRsVlanNs.attributes.tDn == "uni/infra/vlanns-[test_pool]-static" + - nm_l2dom_type is changed + - nm_l2dom_type.current.0.l2extDomP.attributes.dn == "uni/l2dom-l2dom_dom" + - nm_l2dom_type.current.0.l2extDomP.children.0.infraRsVlanNs.attributes.tDn == "uni/infra/vlanns-[test_pool]-dynamic" + - nm_l3dom_type is changed + - nm_l3dom_type.current.0.l3extDomP.attributes.dn == "uni/l3dom-l3dom_dom" + - nm_l3dom_type.current.0.l3extDomP.children.0.infraRsVlanNs.attributes.tDn == "uni/infra/vlanns-[test_pool]-dynamic" + - nm_vmm_type is changed + - nm_vmm_type.current.0.vmmDomP.attributes.dn == "uni/vmmp-VMware/dom-anstest" + - nm_vmm_type.current.0.vmmDomP.children.0.infraRsVlanNs.attributes.tDn == "uni/infra/vlanns-[test_pool]-dynamic" # QUERY ALL BINDINGS - name: Query all domain to VLAN pool bindings (check_mode) cisco.aci.aci_domain_to_vlan_pool: &binding_query - host: '{{ aci_hostname }}' - username: '{{ aci_username }}' - password: '{{ aci_password }}' - validate_certs: '{{ aci_validate_certs | default(false) }}' - use_ssl: '{{ aci_use_ssl | default(true) }}' - use_proxy: '{{ aci_use_proxy | default(true) }}' - output_level: '{{ aci_output_level | default("info") }}' + <<: *aci_info domain_type: phys pool_allocation_mode: dynamic state: query diff --git a/ansible_collections/cisco/aci/tests/integration/targets/aci_encap_pool_range/tasks/vsan.yml b/ansible_collections/cisco/aci/tests/integration/targets/aci_encap_pool_range/tasks/vsan.yml index 7bdc332c5..06df26232 100644 --- a/ansible_collections/cisco/aci/tests/integration/targets/aci_encap_pool_range/tasks/vsan.yml +++ b/ansible_collections/cisco/aci/tests/integration/targets/aci_encap_pool_range/tasks/vsan.yml @@ -1,20 +1,97 @@ -- name: ensure vsan pool exists for tests to kick off +- name: Set vars + ansible.builtin.set_fact: + aci_info: &aci_info + host: '{{ aci_hostname }}' + username: '{{ aci_username }}' + password: '{{ aci_password }}' + validate_certs: '{{ aci_validate_certs | default(false) }}' + use_ssl: '{{ aci_use_ssl | default(true) }}' + use_proxy: '{{ aci_use_proxy | default(true) }}' + output_level: '{{ aci_output_level | default("info") }}' + +- name: create vsan pool anstest cisco.aci.aci_encap_pool: &aci_pool_present - host: "{{ aci_hostname }}" - username: "{{ aci_username }}" - password: "{{ aci_password }}" - validate_certs: '{{ aci_validate_certs | default(false) }}' - use_ssl: '{{ aci_use_ssl | default(true) }}' - use_proxy: '{{ aci_use_proxy | default(true) }}' - output_level: '{{ aci_output_level | default("info") }}' + <<: *aci_info + pool: anstest + pool_type: vsan + pool_allocation_mode: static + description: Ansible Test + +- name: add range to vsan pool anstest (check mode) + cisco.aci.aci_encap_pool_range: &aci_vsan_range + <<: *aci_info pool: anstest pool_type: vsan - allocation_mode: static + pool_allocation_mode: static + range_start: 20 + range_end: 40 description: Ansible Test - state: present + check_mode: true + register: cm_vsan_present + +- name: add range to vsan pool anstest (error) + cisco.aci.aci_encap_pool_range: + <<: *aci_vsan_range + range_start: 4092 + range_end: 4099 + register: err_vsan_range + ignore_errors: true + +- name: add range to vsan pool anstest + cisco.aci.aci_encap_pool_range: + <<: *aci_vsan_range + register: nm_vsan_present + +- name: add range to vsan pool anstest again + cisco.aci.aci_encap_pool_range: + <<: *aci_vsan_range + register: nm_vsan_present_again + +- name: delete range from vsan pool anstest + cisco.aci.aci_encap_pool_range: + <<: *aci_vsan_range + state: absent + register: rm_vsan_present + +- name: present assertions + ansible.builtin.assert: + that: + - cm_vsan_present is changed + - cm_vsan_present.current == [] + - cm_vsan_present.proposed.fvnsVsanEncapBlk.attributes.annotation == "orchestrator:ansible" + - cm_vsan_present.proposed.fvnsVsanEncapBlk.attributes.descr == "Ansible Test" + - cm_vsan_present.proposed.fvnsVsanEncapBlk.attributes.dn == "uni/infra/vsanns-[anstest]-static/vsanfrom-[vsan-20]-to-[vsan-40]" + - cm_vsan_present.proposed.fvnsVsanEncapBlk.attributes.from == "vsan-20" + - cm_vsan_present.proposed.fvnsVsanEncapBlk.attributes.to == "vsan-40" + - err_vsan_range is not changed + - err_vsan_range.msg == 'vsan pools must have "range_start" and "range_end" values between 1 and 4093' + - nm_vsan_present is changed + - nm_vsan_present.previous == [] + - nm_vsan_present.current.0.fvnsVsanEncapBlk.attributes.annotation == "orchestrator:ansible" + - nm_vsan_present.current.0.fvnsVsanEncapBlk.attributes.descr == "Ansible Test" + - nm_vsan_present.current.0.fvnsVsanEncapBlk.attributes.dn == "uni/infra/vsanns-[anstest]-static/vsanfrom-[vsan-20]-to-[vsan-40]" + - nm_vsan_present.current.0.fvnsVsanEncapBlk.attributes.from == "vsan-20" + - nm_vsan_present.current.0.fvnsVsanEncapBlk.attributes.to == "vsan-40" + - nm_vsan_present_again is not changed + - nm_vsan_present_again.previous.0.fvnsVsanEncapBlk.attributes.annotation == "orchestrator:ansible" + - nm_vsan_present_again.previous.0.fvnsVsanEncapBlk.attributes.descr == "Ansible Test" + - nm_vsan_present_again.previous.0.fvnsVsanEncapBlk.attributes.dn == "uni/infra/vsanns-[anstest]-static/vsanfrom-[vsan-20]-to-[vsan-40]" + - nm_vsan_present_again.previous.0.fvnsVsanEncapBlk.attributes.from == "vsan-20" + - nm_vsan_present_again.previous.0.fvnsVsanEncapBlk.attributes.to == "vsan-40" + - nm_vsan_present_again.current.0.fvnsVsanEncapBlk.attributes.annotation == "orchestrator:ansible" + - nm_vsan_present_again.current.0.fvnsVsanEncapBlk.attributes.descr == "Ansible Test" + - nm_vsan_present_again.current.0.fvnsVsanEncapBlk.attributes.dn == "uni/infra/vsanns-[anstest]-static/vsanfrom-[vsan-20]-to-[vsan-40]" + - nm_vsan_present_again.current.0.fvnsVsanEncapBlk.attributes.from == "vsan-20" + - nm_vsan_present_again.current.0.fvnsVsanEncapBlk.attributes.to == "vsan-40" + - rm_vsan_present is changed + - rm_vsan_present.previous.0.fvnsVsanEncapBlk.attributes.annotation == "orchestrator:ansible" + - rm_vsan_present.previous.0.fvnsVsanEncapBlk.attributes.descr == "Ansible Test" + - rm_vsan_present.previous.0.fvnsVsanEncapBlk.attributes.dn == "uni/infra/vsanns-[anstest]-static/vsanfrom-[vsan-20]-to-[vsan-40]" + - rm_vsan_present.previous.0.fvnsVsanEncapBlk.attributes.from == "vsan-20" + - rm_vsan_present.previous.0.fvnsVsanEncapBlk.attributes.to == "vsan-40" + - rm_vsan_present.current == [] -- name: cleanup vsan pool +- name: delete vsan pool anstest cisco.aci.aci_encap_pool: <<: *aci_pool_present state: absent - when: pool_present is changed diff --git a/ansible_collections/cisco/aci/tests/integration/targets/aci_encap_pool_range/tasks/vxlan.yml b/ansible_collections/cisco/aci/tests/integration/targets/aci_encap_pool_range/tasks/vxlan.yml index e5e301916..43a4ff788 100644 --- a/ansible_collections/cisco/aci/tests/integration/targets/aci_encap_pool_range/tasks/vxlan.yml +++ b/ansible_collections/cisco/aci/tests/integration/targets/aci_encap_pool_range/tasks/vxlan.yml @@ -1,19 +1,104 @@ -- name: ensure vxlan pool exists for tests to kick off +- name: Set vars + ansible.builtin.set_fact: + aci_info: &aci_info + host: '{{ aci_hostname }}' + username: '{{ aci_username }}' + password: '{{ aci_password }}' + validate_certs: '{{ aci_validate_certs | default(false) }}' + use_ssl: '{{ aci_use_ssl | default(true) }}' + use_proxy: '{{ aci_use_proxy | default(true) }}' + output_level: '{{ aci_output_level | default("info") }}' + +- name: create vxlan pool anstest cisco.aci.aci_encap_pool: &aci_pool_present - host: "{{ aci_hostname }}" - username: "{{ aci_username }}" - password: "{{ aci_password }}" - validate_certs: '{{ aci_validate_certs | default(false) }}' - use_ssl: '{{ aci_use_ssl | default(true) }}' - use_proxy: '{{ aci_use_proxy | default(true) }}' - output_level: '{{ aci_output_level | default("info") }}' + <<: *aci_info + pool: anstest + pool_type: vxlan + description: Ansible Test + +- name: add range to vxlan pool anstest (check mode) + cisco.aci.aci_encap_pool_range: &aci_vxlan_range + <<: *aci_info pool: anstest pool_type: vxlan + range_start: 5000 + range_end: 5010 description: Ansible Test - state: present + check_mode: true + register: cm_vxlan_present + +- name: add range to vxlan pool anstest with pool_allocation_mode (error) + cisco.aci.aci_encap_pool_range: + <<: *aci_vxlan_range + allocation_mode: static + register: err_vxlan_pool_alloc + ignore_errors: true + +- name: add range to vxlan pool anstest (error) + cisco.aci.aci_encap_pool_range: + <<: *aci_vxlan_range + range_start: 4092 + range_end: 4099 + register: err_vxlan_range + ignore_errors: true + +- name: add range to vxlan pool anstest + cisco.aci.aci_encap_pool_range: + <<: *aci_vxlan_range + register: nm_vxlan_present + +- name: add range to vxlan pool anstest again + cisco.aci.aci_encap_pool_range: + <<: *aci_vxlan_range + register: nm_vxlan_present_again + +- name: delete range from vxlan pool anstest + cisco.aci.aci_encap_pool_range: + <<: *aci_vxlan_range + state: absent + register: rm_vxlan_present + +- name: present assertions + ansible.builtin.assert: + that: + - cm_vxlan_present is changed + - cm_vxlan_present.current == [] + - cm_vxlan_present.proposed.fvnsEncapBlk.attributes.annotation == "orchestrator:ansible" + - cm_vxlan_present.proposed.fvnsEncapBlk.attributes.descr == "Ansible Test" + - cm_vxlan_present.proposed.fvnsEncapBlk.attributes.dn == "uni/infra/vxlanns-anstest/from-[vxlan-5000]-to-[vxlan-5010]" + - cm_vxlan_present.proposed.fvnsEncapBlk.attributes.from == "vxlan-5000" + - cm_vxlan_present.proposed.fvnsEncapBlk.attributes.to == "vxlan-5010" + - err_vxlan_pool_alloc is not changed + - err_vxlan_pool_alloc.msg == 'vxlan pools do not support setting the "allocation_mode"; please omit this parameter for vxlan pools' + - err_vxlan_range is not changed + - err_vxlan_range.msg == 'vxlan pools must have "range_start" and "range_end" values between 5000 and 16777215' + - nm_vxlan_present is changed + - nm_vxlan_present.previous == [] + - nm_vxlan_present.current.0.fvnsEncapBlk.attributes.annotation == "orchestrator:ansible" + - nm_vxlan_present.current.0.fvnsEncapBlk.attributes.descr == "Ansible Test" + - nm_vxlan_present.current.0.fvnsEncapBlk.attributes.dn == "uni/infra/vxlanns-anstest/from-[vxlan-5000]-to-[vxlan-5010]" + - nm_vxlan_present.current.0.fvnsEncapBlk.attributes.from == "vxlan-5000" + - nm_vxlan_present.current.0.fvnsEncapBlk.attributes.to == "vxlan-5010" + - nm_vxlan_present_again is not changed + - nm_vxlan_present_again.previous.0.fvnsEncapBlk.attributes.annotation == "orchestrator:ansible" + - nm_vxlan_present_again.previous.0.fvnsEncapBlk.attributes.descr == "Ansible Test" + - nm_vxlan_present_again.previous.0.fvnsEncapBlk.attributes.dn == "uni/infra/vxlanns-anstest/from-[vxlan-5000]-to-[vxlan-5010]" + - nm_vxlan_present_again.previous.0.fvnsEncapBlk.attributes.from == "vxlan-5000" + - nm_vxlan_present_again.previous.0.fvnsEncapBlk.attributes.to == "vxlan-5010" + - nm_vxlan_present_again.current.0.fvnsEncapBlk.attributes.annotation == "orchestrator:ansible" + - nm_vxlan_present_again.current.0.fvnsEncapBlk.attributes.descr == "Ansible Test" + - nm_vxlan_present_again.current.0.fvnsEncapBlk.attributes.dn == "uni/infra/vxlanns-anstest/from-[vxlan-5000]-to-[vxlan-5010]" + - nm_vxlan_present_again.current.0.fvnsEncapBlk.attributes.from == "vxlan-5000" + - nm_vxlan_present_again.current.0.fvnsEncapBlk.attributes.to == "vxlan-5010" + - rm_vxlan_present is changed + - rm_vxlan_present.previous.0.fvnsEncapBlk.attributes.annotation == "orchestrator:ansible" + - rm_vxlan_present.previous.0.fvnsEncapBlk.attributes.descr == "Ansible Test" + - rm_vxlan_present.previous.0.fvnsEncapBlk.attributes.dn == "uni/infra/vxlanns-anstest/from-[vxlan-5000]-to-[vxlan-5010]" + - rm_vxlan_present.previous.0.fvnsEncapBlk.attributes.from == "vxlan-5000" + - rm_vxlan_present.previous.0.fvnsEncapBlk.attributes.to == "vxlan-5010" + - rm_vxlan_present.current == [] -- name: cleanup vxlan pool +- name: delete vxlan pool anstest cisco.aci.aci_encap_pool: <<: *aci_pool_present state: absent - when: pool_present is changed diff --git a/ansible_collections/cisco/aci/tests/integration/targets/aci_epg_monitoring_policy/aliases b/ansible_collections/cisco/aci/tests/integration/targets/aci_epg_monitoring_policy/aliases new file mode 100644 index 000000000..209b793f9 --- /dev/null +++ b/ansible_collections/cisco/aci/tests/integration/targets/aci_epg_monitoring_policy/aliases @@ -0,0 +1,2 @@ +# No ACI simulator yet, so not enabled +# unsupported diff --git a/ansible_collections/cisco/aci/tests/integration/targets/aci_epg_monitoring_policy/tasks/main.yml b/ansible_collections/cisco/aci/tests/integration/targets/aci_epg_monitoring_policy/tasks/main.yml new file mode 100644 index 000000000..1671e252e --- /dev/null +++ b/ansible_collections/cisco/aci/tests/integration/targets/aci_epg_monitoring_policy/tasks/main.yml @@ -0,0 +1,161 @@ +# Test code for the ACI modules +# Copyright: (c) 2023, Akini Ross (akinross@cisco.com) + +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +- name: Test that we have an ACI APIC host, ACI username and ACI password + ansible.builtin.fail: + msg: 'Please define the following variables: aci_hostname, aci_username and aci_password.' + when: aci_hostname is not defined or aci_username is not defined or aci_password is not defined + +- name: Set vars + ansible.builtin.set_fact: + aci_info: &aci_info + host: "{{ aci_hostname }}" + username: "{{ aci_username }}" + password: "{{ aci_password }}" + validate_certs: '{{ aci_validate_certs | default(false) }}' + use_ssl: '{{ aci_use_ssl | default(true) }}' + use_proxy: '{{ aci_use_proxy | default(true) }}' + output_level: '{{ aci_output_level | default("info") }}' + aci_tenant: ansible_test + +- name: Verify Cloud and Non-Cloud Sites in use. + ansible.builtin.include_tasks: ../../../../../../integration/targets/aci_cloud_provider/tasks/main.yml + +- name: Execute tasks only for non-cloud sites + when: + - query_cloud.current == [] + block: + + # CLEAN TEST ENVIRONMENT + + - name: Ensure clean tenant configuration + cisco.aci.aci_tenant: + <<: *aci_info + tenant: "{{ aci_tenant }}" + state: "{{ item }}" + loop: + - absent + - present + + # TEST CREATE EPG MONITORING POLICY + + - name: Add epg monitoring policy 1 ( check mode ) + cisco.aci.aci_epg_monitoring_policy: &add_epg_monitoring_policy_1 + <<: *aci_info + tenant: "{{ aci_tenant }}" + monitoring_policy: monitoring_policy_1 + state: present + check_mode: true + register: cm_add_epg_monitoring_policy_1 + + - name: Add epg monitoring policy 1 + cisco.aci.aci_epg_monitoring_policy: + <<: *add_epg_monitoring_policy_1 + register: nm_add_epg_monitoring_policy_1 + + - name: Add epg monitoring policy 1 again + cisco.aci.aci_epg_monitoring_policy: + <<: *add_epg_monitoring_policy_1 + register: nm_add_epg_monitoring_policy_1_again + + - name: Verify add epg monitoring policy 1 + ansible.builtin.assert: + that: + - cm_add_epg_monitoring_policy_1 is changed + - cm_add_epg_monitoring_policy_1.current == [] + - cm_add_epg_monitoring_policy_1.previous == [] + - cm_add_epg_monitoring_policy_1.proposed.monEPGPol.attributes.name == "monitoring_policy_1" + - nm_add_epg_monitoring_policy_1 is changed + - nm_add_epg_monitoring_policy_1.previous == [] + - nm_add_epg_monitoring_policy_1.current.0.monEPGPol.attributes.name == "monitoring_policy_1" + - nm_add_epg_monitoring_policy_1.current.0.monEPGPol.attributes.descr == "" + - nm_add_epg_monitoring_policy_1_again is not changed + - nm_add_epg_monitoring_policy_1_again.previous.0.monEPGPol.attributes.name == "monitoring_policy_1" + - nm_add_epg_monitoring_policy_1_again.previous.0.monEPGPol.attributes.descr == "" + - nm_add_epg_monitoring_policy_1_again.current.0.monEPGPol.attributes.name == "monitoring_policy_1" + - nm_add_epg_monitoring_policy_1_again.current.0.monEPGPol.attributes.descr == "" + + - name: Change epg monitoring policy 1 + cisco.aci.aci_epg_monitoring_policy: + <<: *add_epg_monitoring_policy_1 + description: "changed description" + register: nm_change_epg_monitoring_policy_1 + + - name: Verify change epg monitoring policy 1 + ansible.builtin.assert: + that: + - nm_change_epg_monitoring_policy_1 is changed + - nm_change_epg_monitoring_policy_1.previous.0.monEPGPol.attributes.name == "monitoring_policy_1" + - nm_change_epg_monitoring_policy_1.previous.0.monEPGPol.attributes.descr == "" + - nm_change_epg_monitoring_policy_1.current.0.monEPGPol.attributes.name == "monitoring_policy_1" + - nm_change_epg_monitoring_policy_1.current.0.monEPGPol.attributes.descr == "changed description" + + - name: Add two more epg monitoring policies + cisco.aci.aci_epg_monitoring_policy: + <<: *aci_info + tenant: "{{ aci_tenant }}" + monitoring_policy: "{{ item }}" + state: present + loop: + - monitoring_policy_2 + - monitoring_policy_3 + + # TEST QUERY EPG MONITORING POLICY + + - name: Query epg monitoring policy 1 + cisco.aci.aci_epg_monitoring_policy: + <<: *add_epg_monitoring_policy_1 + state: query + register: query_one + + - name: Query all epg monitoring policies ( class query ) + cisco.aci.aci_epg_monitoring_policy: + <<: *aci_info + state: query + register: query_all + + - name: Verify query epg monitoring policies + ansible.builtin.assert: + that: + - query_one is not changed + - query_one.current | length == 1 + - query_one.current.0.monEPGPol.attributes.name == "monitoring_policy_1" + - query_all is not changed + - query_all.current | length >= 4 + - query_all.current.0.monEPGPol.attributes.name == "default" + - query_all.current.1.monEPGPol.attributes.name == "monitoring_policy_1" + - query_all.current.2.monEPGPol.attributes.name == "monitoring_policy_2" + - query_all.current.3.monEPGPol.attributes.name == "monitoring_policy_3" + + # TEST REMOVAL EPG MONITORING POLICY + + - name: Remove aepg monitoring policy 1 ( check mode ) + cisco.aci.aci_epg_monitoring_policy: &remove_epg_monitoring_policy_1 + <<: *add_epg_monitoring_policy_1 + state: absent + check_mode: true + register: cm_remove_epg_monitoring_policy_1 + + - name: Remove epg monitoring policy 1 + cisco.aci.aci_epg_monitoring_policy: + <<: *remove_epg_monitoring_policy_1 + register: nm_remove_epg_monitoring_policy_1 + + - name: Remove epg monitoring policy 1 again + cisco.aci.aci_epg_monitoring_policy: + <<: *remove_epg_monitoring_policy_1 + register: nm_remove_epg_monitoring_policy_1_again + + - name: Verify removal epg monitoring policies + ansible.builtin.assert: + that: + - cm_remove_epg_monitoring_policy_1 is changed + - cm_remove_epg_monitoring_policy_1.proposed == {} + - nm_remove_epg_monitoring_policy_1 is changed + - nm_remove_epg_monitoring_policy_1.previous.0.monEPGPol.attributes.name == "monitoring_policy_1" + - nm_remove_epg_monitoring_policy_1.current == [] + - nm_remove_epg_monitoring_policy_1_again is not changed + - nm_remove_epg_monitoring_policy_1_again.previous == [] + - nm_remove_epg_monitoring_policy_1_again.current == [] diff --git a/ansible_collections/cisco/aci/tests/integration/targets/aci_epg_subnet/aliases b/ansible_collections/cisco/aci/tests/integration/targets/aci_epg_subnet/aliases new file mode 100644 index 000000000..209b793f9 --- /dev/null +++ b/ansible_collections/cisco/aci/tests/integration/targets/aci_epg_subnet/aliases @@ -0,0 +1,2 @@ +# No ACI simulator yet, so not enabled +# unsupported diff --git a/ansible_collections/cisco/aci/tests/integration/targets/aci_epg_subnet/tasks/main.yml b/ansible_collections/cisco/aci/tests/integration/targets/aci_epg_subnet/tasks/main.yml new file mode 100644 index 000000000..07fb1d304 --- /dev/null +++ b/ansible_collections/cisco/aci/tests/integration/targets/aci_epg_subnet/tasks/main.yml @@ -0,0 +1,258 @@ +# Test code for the ACI modules + +# Copyright: (c) 2023, Gaspard Micol (@gmicol) <gmicol@cisco.com> + +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +- name: Test that we have an ACI APIC host, ACI username and ACI password + fail: + msg: 'Please define the following variables: aci_hostname, aci_username and aci_password.' + when: aci_hostname is not defined or aci_username is not defined or aci_password is not defined + +- name: Set vars + set_fact: + aci_info: &aci_info + host: "{{ aci_hostname }}" + username: "{{ aci_username }}" + password: "{{ aci_password }}" + validate_certs: '{{ aci_validate_certs | default(false) }}' + use_ssl: '{{ aci_use_ssl | default(true) }}' + use_proxy: '{{ aci_use_proxy | default(true) }}' + output_level: debug + +- name: Query system information + aci_system: + <<: *aci_info + id: 1 + state: query + register: version + +- name: Verify Cloud and Non-Cloud Sites in use. + include_tasks: ../../../../../../integration/targets/aci_cloud_provider/tasks/main.yml + +- name: ensure tenant does not exists + cisco.aci.aci_tenant: &aci_tenant_absent + <<: *aci_info + state: absent + tenant: ansible_test + +- name: Execute tasks only for non-cloud sites + when: query_cloud.current == [] # This condition will execute only non-cloud sites + block: # block specifies execution of tasks within, based on conditions + - name: ensure tenant exists for tests to kick off + cisco.aci.aci_tenant: &aci_tenant_present + <<: *aci_tenant_absent + state: present + tenant: ansible_test + register: tenant_present + + - name: ensure ap exists for tests to kick off + cisco.aci.aci_ap: &aci_ap_present + <<: *aci_tenant_present + ap: anstest + register: ap_present + + - name: ensure epg exists for tests to kick off + cisco.aci.aci_epg: &aci_epg_present + <<: *aci_ap_present + epg: anstest + register: epg_present + + - name: create subnet - check mode works + cisco.aci.aci_epg_subnet: &aci_subnet_present + <<: *aci_epg_present + state: present + subnet_name: anstest + gateway: 10.100.100.1 + mask: 24 + descr: Ansible Test + check_mode: true + register: create_check_mode + + - name: create subnet - creation works + cisco.aci.aci_epg_subnet: + <<: *aci_subnet_present + register: create_subnet + + - name: create new subnet with IpDP_learning disabled - APIC version >= 5.0 + cisco.aci.aci_epg_subnet: + <<: *aci_epg_present + state: present + descr: Ansible Test + gateway: 10.100.101.1 + mask: 32 + scope: [private, shared] + route_profile: default + route_profile_l3out: default + subnet_control: no_default_gateway + ip_data_plane_learning: disabled + register: create_subnet2_5 + when: version.current.0.topSystem.attributes.version is version('5', '>=') + + - name: create new subnet - creation works for APIC version < 5.0 + cisco.aci.aci_epg_subnet: &aci_subnet2_present + <<: *aci_epg_present + state: present + descr: Ansible Test + gateway: 10.100.101.1 + mask: 32 + scope: [private, shared] + route_profile: default + route_profile_l3out: default + subnet_control: no_default_gateway + register: create_subnet2 + when: version.current.0.topSystem.attributes.version is version('5', '<') + + - name: create subnet again with IpDP_learning disabled - idempotency works for APIC version >= 5 + cisco.aci.aci_epg_subnet: + <<: *aci_subnet2_present + ip_data_plane_learning: disabled + register: create_idempotency_5 + when: version.current.0.topSystem.attributes.version is version('5', '>=') + + - name: create subnet again - idempotency works for APIC version < 5 + cisco.aci.aci_epg_subnet: + <<: *aci_subnet2_present + register: create_idempotency + when: version.current.0.topSystem.attributes.version is version('5', '<') + + - name: create subnet with bad scope - failure message works + cisco.aci.aci_epg_subnet: + <<: *aci_subnet_present + scope: [private, public] + register: create_bad_scope + ignore_errors: true + + - name: create subnet without all necessary params - failure message works + cisco.aci.aci_epg_subnet: + <<: *aci_subnet_present + epg: "{{ fake_var | default(omit) }}" + register: create_incomplete_data + ignore_errors: true + + - name: create subnet without valid mask - failure message works + cisco.aci.aci_epg_subnet: + <<: *aci_subnet_present + mask: 130 + register: create_wrong_mask + ignore_errors: true + + - name: asserts for subnet creation tasks + assert: + that: + - create_check_mode is changed + - create_check_mode.sent.fvSubnet.attributes.descr == create_subnet.sent.fvSubnet.attributes.descr == 'Ansible Test' + - create_check_mode.sent.fvSubnet.attributes.ip == create_subnet.sent.fvSubnet.attributes.ip == '10.100.100.1/24' + - create_check_mode.sent.fvSubnet.attributes.name == create_subnet.sent.fvSubnet.attributes.name == 'anstest' + - create_subnet is changed + - create_subnet.current.0.fvSubnet.attributes.annotation == 'orchestrator:ansible' + - create_subnet.previous == [] + - create_bad_scope is failed + - create_bad_scope.msg.startswith("Parameter 'scope' cannot be both 'private' and 'public'") + - create_incomplete_data is failed + - 'create_incomplete_data.msg == "state is present but all of the following are missing: epg"' + - create_wrong_mask is failed + - create_wrong_mask.msg == "Valid Subnet Masks are 0 to 32 for IPv4 Addresses and 0 to 128 for IPv6 addresses" + + - name: assert for subnet IpDPlearning tasks version >=5 + assert: + that: + - create_subnet2_5 is changed + - create_subnet2_5.current.0.fvSubnet.attributes.ctrl == 'no-default-gateway' + - create_subnet2_5.sent == create_subnet2_5.proposed + - create_subnet2_5.sent.fvSubnet.attributes.scope == "private,shared" + - create_subnet2_5.sent.fvSubnet.children.0.fvRsBDSubnetToProfile.attributes.tnL3extOutName == 'default' + - create_subnet2_5.sent.fvSubnet.children.0.fvRsBDSubnetToProfile.attributes.tnRtctrlProfileName == 'default' + - create_idempotency_5 is not changed + - create_idempotency_5.previous != [] + - create_subnet.current.0.fvSubnet.attributes.ipDPLearning == 'enabled' + - create_subnet2_5.current.0.fvSubnet.attributes.ipDPLearning == 'disabled' + when: version.current.0.topSystem.attributes.version is version('5', '>=') + + - name: assert for subnet IpDPlearning tasks version < 5 + assert: + that: + - create_subnet2 is changed + - create_subnet2.current.0.fvSubnet.attributes.ctrl == 'no-default-gateway' + - create_subnet2.sent == create_subnet2.proposed + - create_subnet2.sent.fvSubnet.attributes.scope == "private,shared" + - create_subnet2.sent.fvSubnet.children.0.fvRsBDSubnetToProfile.attributes.tnL3extOutName == 'default' + - create_subnet2.sent.fvSubnet.children.0.fvRsBDSubnetToProfile.attributes.tnRtctrlProfileName == 'default' + - create_idempotency is not changed + - create_idempotency.previous != [] + when: version.current.0.topSystem.attributes.version is version('5', '<') + + - name: get all in epg + cisco.aci.aci_epg_subnet: + <<: *aci_epg_present + state: query + register: get_all_epg + + - name: get specific subnet + cisco.aci.aci_epg_subnet: + <<: *aci_subnet_present + state: query + register: get_subnet + + - name: get all subnets matching gateway + cisco.aci.aci_epg_subnet: + <<: *aci_subnet_present + state: query + tenant: "{{ fake_var | default(omit) }}" + ap: "{{ fake_var | default(omit) }}" + epg: "{{ fake_var | default(omit) }}" + register: get_subnets_gateway + + - name: asserts for query tasks + assert: + that: + - get_all_epg is not changed + - '"rsp-subtree-class=fvRsBDSubnetToProfile,fvRsNdPfxPol,fvSubnet" in get_all_epg.filter_string' + - '"tn-ansible_test/ap-anstest/epg-anstest.json" in get_all_epg.url' + - get_subnet is not changed + - get_subnet.current | length == 1 + - '"tn-ansible_test/ap-anstest/epg-anstest/subnet-[10.100.100.1/24].json" in get_subnet.url' + - get_subnets_gateway is not changed + - '"query-target-filter=eq(fvSubnet.ip,\"10.100.100.1/24\")" in get_subnets_gateway.filter_string' + - '"class/fvSubnet.json" in get_subnets_gateway.url' + + - name: delete subnet - check mode works + cisco.aci.aci_epg_subnet: + <<: *aci_subnet_present + state: absent + check_mode: true + register: delete_check_mode + + - name: delete subnet - delete works + cisco.aci.aci_epg_subnet: + <<: *aci_subnet_present + state: absent + register: delete_subnet + + - name: delete subnet - cleanup + cisco.aci.aci_epg_subnet: + <<: *aci_subnet2_present + state: absent + + - name: delete subnet again - idempotency works + cisco.aci.aci_epg_subnet: + <<: *aci_subnet2_present + state: absent + register: delete_idempotency + + - name: asserts for deletion task + assert: + that: + - delete_check_mode is changed + - delete_check_mode.proposed == {} + - delete_subnet is changed + - delete_subnet.previous != [] + - delete_subnet.method == "DELETE" + - delete_idempotency is not changed + - delete_idempotency.previous == [] + + - name: delete tenant - cleanup before ending tests + cisco.aci.aci_tenant: + <<: *aci_tenant_present + state: absent + when: tenant_present is changed diff --git a/ansible_collections/cisco/aci/tests/integration/targets/aci_fabric_interface_policy_group/aliases b/ansible_collections/cisco/aci/tests/integration/targets/aci_fabric_interface_policy_group/aliases new file mode 100644 index 000000000..209b793f9 --- /dev/null +++ b/ansible_collections/cisco/aci/tests/integration/targets/aci_fabric_interface_policy_group/aliases @@ -0,0 +1,2 @@ +# No ACI simulator yet, so not enabled +# unsupported diff --git a/ansible_collections/cisco/aci/tests/integration/targets/aci_fabric_interface_policy_group/tasks/main.yml b/ansible_collections/cisco/aci/tests/integration/targets/aci_fabric_interface_policy_group/tasks/main.yml new file mode 100644 index 000000000..9f871da6b --- /dev/null +++ b/ansible_collections/cisco/aci/tests/integration/targets/aci_fabric_interface_policy_group/tasks/main.yml @@ -0,0 +1,346 @@ +# Test code for the ACI modules +# Copyright: (c) 2023, Sabari Jaganathan (@sajagana) + +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +- name: Test that we have an ACI APIC host, ACI username and ACI password + fail: + msg: "Please define the following variables: aci_hostname, aci_username and aci_password." + when: aci_hostname is not defined or aci_username is not defined or aci_password is not defined + +# SET VARS +- name: Set vars + set_fact: + aci_info: &aci_info + host: "{{ aci_hostname }}" + username: "{{ aci_username }}" + password: "{{ aci_password }}" + validate_certs: "{{ aci_validate_certs | default(false) }}" + use_ssl: "{{ aci_use_ssl | default(true) }}" + use_proxy: "{{ aci_use_proxy | default(true) }}" + output_level: '{{ aci_output_level | default("info") }}' + +- name: Query system information + cisco.aci.aci_system: + <<: *aci_info + id: 1 + state: query + register: version + +- name: Ensure leaf_policy_group does not exist + aci_fabric_interface_policy_group: + <<: *aci_info + name: leaf_policy_group + type: leaf + state: absent + +- name: Ensure spine_policy_group does not exist + aci_fabric_interface_policy_group: + <<: *aci_info + name: spine_policy_group + type: spine + state: absent + +- name: Add a leaf fabric interface policy group with check mode + aci_fabric_interface_policy_group: &cm_leaf_policy_group_present + <<: *aci_info + name: leaf_policy_group + type: leaf + descr: leaf_policy_group created + state: present + check_mode: true + register: cm_leaf_policy_group_present + +- name: Add a leaf fabric interface policy group with normal mode + aci_fabric_interface_policy_group: &nm_leaf_policy_group_present + <<: *cm_leaf_policy_group_present + dwdm_policy: default + link_level_policy: default + link_flap_policy: default + l3_interface_policy: default + macsec_policy: default + monitoring_policy: default + register: nm_leaf_policy_group_present + +- name: Add a leaf fabric interface policy group with normal mode again + aci_fabric_interface_policy_group: + <<: *nm_leaf_policy_group_present + register: nm_leaf_policy_group_present_again + +- name: Add a spine fabric interface policy group with check mode + aci_fabric_interface_policy_group: &cm_spine_policy_group_present + <<: *aci_info + name: spine_policy_group + type: spine + descr: spine_policy_group created + dwdm_policy: default + link_level_policy: default + link_flap_policy: default + l3_interface_policy: default + macsec_policy: default + monitoring_policy: default + state: present + check_mode: true + register: cm_spine_policy_group_present + +- name: Execute tasks only for ACI v6.0(2h)+ + when: version.current.0.topSystem.attributes.version is version('6.0(2h)', '>=') + block: + - name: Add a spine fabric interface policy group with transceiver_policy_tdn + aci_fabric_interface_policy_group: &tp_spine_policy_group_present + <<: *aci_info + name: spine_policy_group + type: spine + descr: spine_policy_group created + transceiver_policy_tdn: "uni/fabric/zrfab-default" + state: present + register: tp_spine_policy_group_present + + - name: Remove the transceiver_policy from the spine fabric interface policy group + aci_fabric_interface_policy_group: + <<: *aci_info + name: spine_policy_group + type: spine + descr: spine_policy_group created + transceiver_policy_tdn: "" + state: present + register: tp_spine_policy_group_absent + + - name: Assertions check for add fabric interface policy groups + assert: + that: + - tp_spine_policy_group_present is changed + - tp_spine_policy_group_absent is changed + - tp_spine_policy_group_present.current.0.fabricSpPortPGrp.children | selectattr('fabricRsOpticsFabIfPol.attributes.tDn', 'defined') | selectattr('fabricRsOpticsFabIfPol.attributes.tDn', 'equalto', "uni/fabric/zrfab-default") + - tp_spine_policy_group_absent.current.0.fabricSpPortPGrp.children | selectattr('fabricRsOpticsFabIfPol.attributes.tDn', 'defined') | selectattr('fabricRsOpticsFabIfPol.attributes.tDn', 'equalto', "") + +- name: Add a spine fabric interface policy group with normal mode + aci_fabric_interface_policy_group: + <<: *cm_spine_policy_group_present + register: nm_spine_policy_group_present + +- name: Add a spine fabric interface policy group with normal mode again + aci_fabric_interface_policy_group: + <<: *cm_spine_policy_group_present + register: nm_spine_policy_group_present_again + +- name: Update a spine fabric interface policy group with check mode + aci_fabric_interface_policy_group: &cm_spine_policy_group_update + <<: *cm_spine_policy_group_present + descr: spine_policy_group updated + dwdm_policy: "" + link_level_policy: "" + link_flap_policy: "" + l3_interface_policy: "" + macsec_policy: "" + monitoring_policy: "" + check_mode: true + register: cm_spine_policy_group_update + +- name: Update a spine fabric interface policy group with normal mode + aci_fabric_interface_policy_group: + <<: *cm_spine_policy_group_update + register: nm_spine_policy_group_update + +- name: Update a spine fabric interface policy group with normal mode again + aci_fabric_interface_policy_group: + <<: *cm_spine_policy_group_update + register: nm_spine_policy_group_update_again + +- name: Assertions check for add fabric interface policy groups + assert: + that: + - cm_leaf_policy_group_present is changed + - cm_leaf_policy_group_present.current == [] + - cm_leaf_policy_group_present.previous == [] + - cm_leaf_policy_group_present.mo.fabricLePortPGrp.attributes.name == "leaf_policy_group" + - cm_leaf_policy_group_present.mo.fabricLePortPGrp.attributes.descr == "leaf_policy_group created" + - nm_leaf_policy_group_present is changed + - nm_leaf_policy_group_present.current != [] + - nm_leaf_policy_group_present.previous == [] + - nm_leaf_policy_group_present.current.0.fabricLePortPGrp.attributes.name == "leaf_policy_group" + - nm_leaf_policy_group_present.current.0.fabricLePortPGrp.attributes.descr == "leaf_policy_group created" + - nm_leaf_policy_group_present.current.0.fabricLePortPGrp.attributes.dn == "uni/fabric/funcprof/leportgrp-leaf_policy_group" + - nm_leaf_policy_group_present.current.0.fabricLePortPGrp.children | length >= 6 + - nm_leaf_policy_group_present_again is not changed + - nm_leaf_policy_group_present_again.current != [] + - nm_leaf_policy_group_present_again.previous != [] + - nm_leaf_policy_group_present_again.current.0.fabricLePortPGrp.attributes.name == nm_leaf_policy_group_present_again.previous.0.fabricLePortPGrp.attributes.name == "leaf_policy_group" + - nm_leaf_policy_group_present_again.current.0.fabricLePortPGrp.attributes.descr == nm_leaf_policy_group_present_again.previous.0.fabricLePortPGrp.attributes.descr == "leaf_policy_group created" + - nm_leaf_policy_group_present_again.current.0.fabricLePortPGrp.attributes.dn == nm_leaf_policy_group_present_again.previous.0.fabricLePortPGrp.attributes.dn == "uni/fabric/funcprof/leportgrp-leaf_policy_group" + - cm_spine_policy_group_present is changed + - cm_spine_policy_group_present.current == [] + - cm_spine_policy_group_present.previous == [] + - cm_spine_policy_group_present.mo.fabricSpPortPGrp.attributes.name == "spine_policy_group" + - cm_spine_policy_group_present.mo.fabricSpPortPGrp.attributes.descr == "spine_policy_group created" + - nm_spine_policy_group_present is changed + - nm_spine_policy_group_present.current != [] + - nm_spine_policy_group_present.current.0.fabricSpPortPGrp.attributes.name == "spine_policy_group" + - nm_spine_policy_group_present.current.0.fabricSpPortPGrp.attributes.descr == "spine_policy_group created" + - nm_spine_policy_group_present.current.0.fabricSpPortPGrp.attributes.dn == "uni/fabric/funcprof/spportgrp-spine_policy_group" + - nm_spine_policy_group_present.current.0.fabricSpPortPGrp.children | length >= 6 + - nm_spine_policy_group_present.current.0.fabricSpPortPGrp.children | selectattr('fabricRsDwdmFabIfPol.attributes.tnDwdmFabIfPolName', 'defined') | selectattr('fabricRsDwdmFabIfPol.attributes.tnDwdmFabIfPolName', 'equalto', "default") + - nm_spine_policy_group_present.current.0.fabricSpPortPGrp.children | selectattr('fabricRsFIfPol.attributes.tnFabricFIfPolName', 'defined') | selectattr('fabricRsFIfPol.attributes.tnFabricFIfPolName', 'equalto', "default") + - nm_spine_policy_group_present.current.0.fabricSpPortPGrp.children | selectattr('fabricRsFLinkFlapPol.attributes.tnFabricFLinkFlapPolName', 'defined') | selectattr('fabricRsFLinkFlapPol.attributes.tnFabricFLinkFlapPolName', 'equalto', "default") + - nm_spine_policy_group_present.current.0.fabricSpPortPGrp.children | selectattr('fabricRsL3IfPol.attributes.tnL3IfPolName', 'defined') | selectattr('fabricRsL3IfPol.attributes.tnL3IfPolName', 'equalto', "default") + - nm_spine_policy_group_present.current.0.fabricSpPortPGrp.children | selectattr('fabricRsMacsecFabIfPol.attributes.tnMacsecFabIfPolName', 'defined') | selectattr('fabricRsMacsecFabIfPol.attributes.tnMacsecFabIfPolName', 'equalto', "default") + - nm_spine_policy_group_present.current.0.fabricSpPortPGrp.children | selectattr('fabricRsMonIfFabricPol.attributes.tnMonFabricPolName', 'defined') | selectattr('fabricRsMonIfFabricPol.attributes.tnMonFabricPolName', 'equalto', "default") + - nm_spine_policy_group_present_again is not changed + - nm_spine_policy_group_present_again.current != [] + - nm_spine_policy_group_present_again.previous != [] + - nm_spine_policy_group_present_again.current.0.fabricSpPortPGrp.attributes.name == nm_spine_policy_group_present_again.previous.0.fabricSpPortPGrp.attributes.name == "spine_policy_group" + - nm_spine_policy_group_present_again.current.0.fabricSpPortPGrp.attributes.descr == nm_spine_policy_group_present_again.previous.0.fabricSpPortPGrp.attributes.descr == "spine_policy_group created" + - nm_spine_policy_group_present_again.current.0.fabricSpPortPGrp.attributes.dn == nm_spine_policy_group_present_again.previous.0.fabricSpPortPGrp.attributes.dn == "uni/fabric/funcprof/spportgrp-spine_policy_group" + - cm_spine_policy_group_update is changed + - cm_spine_policy_group_update.previous.0.fabricSpPortPGrp.attributes.descr == "spine_policy_group created" + - cm_spine_policy_group_update.current.0.fabricSpPortPGrp.attributes.descr == "spine_policy_group created" + - nm_spine_policy_group_update is changed + - nm_spine_policy_group_update.previous.0.fabricSpPortPGrp.attributes.descr == "spine_policy_group created" + - nm_spine_policy_group_update.current.0.fabricSpPortPGrp.attributes.descr == "spine_policy_group updated" + - nm_spine_policy_group_update.previous.0.fabricSpPortPGrp.children | selectattr('fabricRsDwdmFabIfPol.attributes.tnDwdmFabIfPolName', 'defined') | selectattr('fabricRsDwdmFabIfPol.attributes.tnDwdmFabIfPolName', 'equalto', "default") + - nm_spine_policy_group_update.previous.0.fabricSpPortPGrp.children | selectattr('fabricRsFIfPol.attributes.tnFabricFIfPolName', 'defined') | selectattr('fabricRsFIfPol.attributes.tnFabricFIfPolName', 'equalto', "default") + - nm_spine_policy_group_update.previous.0.fabricSpPortPGrp.children | selectattr('fabricRsFLinkFlapPol.attributes.tnFabricFLinkFlapPolName', 'defined') | selectattr('fabricRsFLinkFlapPol.attributes.tnFabricFLinkFlapPolName', 'equalto', "default") + - nm_spine_policy_group_update.previous.0.fabricSpPortPGrp.children | selectattr('fabricRsL3IfPol.attributes.tnL3IfPolName', 'defined') | selectattr('fabricRsL3IfPol.attributes.tnL3IfPolName', 'equalto', "default") + - nm_spine_policy_group_update.previous.0.fabricSpPortPGrp.children | selectattr('fabricRsMacsecFabIfPol.attributes.tnMacsecFabIfPolName', 'defined') | selectattr('fabricRsMacsecFabIfPol.attributes.tnMacsecFabIfPolName', 'equalto', "default") + - nm_spine_policy_group_update.previous.0.fabricSpPortPGrp.children | selectattr('fabricRsMonIfFabricPol.attributes.tnMonFabricPolName', 'defined') | selectattr('fabricRsMonIfFabricPol.attributes.tnMonFabricPolName', 'equalto', "default") + - nm_spine_policy_group_update.current.0.fabricSpPortPGrp.children | selectattr('fabricRsDwdmFabIfPol.attributes.tnDwdmFabIfPolName', 'defined') | selectattr('fabricRsDwdmFabIfPol.attributes.tnDwdmFabIfPolName', 'equalto', "") + - nm_spine_policy_group_update.current.0.fabricSpPortPGrp.children | selectattr('fabricRsFIfPol.attributes.tnFabricFIfPolName', 'defined') | selectattr('fabricRsFIfPol.attributes.tnFabricFIfPolName', 'equalto', "") + - nm_spine_policy_group_update.current.0.fabricSpPortPGrp.children | selectattr('fabricRsFLinkFlapPol.attributes.tnFabricFLinkFlapPolName', 'defined') | selectattr('fabricRsFLinkFlapPol.attributes.tnFabricFLinkFlapPolName', 'equalto', "") + - nm_spine_policy_group_update.current.0.fabricSpPortPGrp.children | selectattr('fabricRsL3IfPol.attributes.tnL3IfPolName', 'defined') | selectattr('fabricRsL3IfPol.attributes.tnL3IfPolName', 'equalto', "") + - nm_spine_policy_group_update.current.0.fabricSpPortPGrp.children | selectattr('fabricRsMacsecFabIfPol.attributes.tnMacsecFabIfPolName', 'defined') | selectattr('fabricRsMacsecFabIfPol.attributes.tnMacsecFabIfPolName', 'equalto', "") + - nm_spine_policy_group_update.current.0.fabricSpPortPGrp.children | selectattr('fabricRsMonIfFabricPol.attributes.tnMonFabricPolName', 'defined') | selectattr('fabricRsMonIfFabricPol.attributes.tnMonFabricPolName', 'equalto', "") + - nm_spine_policy_group_update_again is not changed + - nm_spine_policy_group_update_again.previous.0.fabricSpPortPGrp.attributes.descr == "spine_policy_group updated" + - nm_spine_policy_group_update_again.current.0.fabricSpPortPGrp.attributes.descr == "spine_policy_group updated" + +- name: Invalid test - add a fabric interface policy group without type + aci_fabric_interface_policy_group: + <<: *aci_info + name: nt_spine_policy_group + descr: negative test nt_spine_policy_group + state: present + register: nt_without_type + ignore_errors: true + +- name: Invalid test - add a fabric interface policy group without name + aci_fabric_interface_policy_group: + <<: *aci_info + type: spine + descr: negative test spine_policy_group + state: present + register: nt_without_name + ignore_errors: true + +- name: Assertions check for invalid test - add fabric interface policy groups + assert: + that: + - nt_without_type is not changed + - nt_without_type.msg == "missing required arguments{{':'}} type" + - nt_without_name is not changed + - nt_without_name.msg == "state is present but all of the following are missing{{':'}} name" + +- name: Query a leaf fabric interface policy group with name + aci_fabric_interface_policy_group: + <<: *aci_info + name: leaf_policy_group + type: leaf + state: query + register: query_leaf_policy_group + +- name: Query all leaf fabric interface policy groups + aci_fabric_interface_policy_group: + <<: *aci_info + type: leaf + state: query + register: query_all_leaf_policy_group + +- name: Query a spine fabric interface policy group with name + aci_fabric_interface_policy_group: + <<: *aci_info + name: spine_policy_group + type: spine + state: query + register: query_a_spine_policy_group + +- name: Query all spine fabric interface policy groups + aci_fabric_interface_policy_group: + <<: *aci_info + type: spine + state: query + register: query_all_spine_policy_group + +- name: Assertions check for query a fabric interface policy groups + assert: + that: + - query_leaf_policy_group is not changed + - query_leaf_policy_group.current != [] + - query_leaf_policy_group.current.0.fabricLePortPGrp.attributes.name == "leaf_policy_group" + - query_leaf_policy_group.current.0.fabricLePortPGrp.attributes.dn == "uni/fabric/funcprof/leportgrp-leaf_policy_group" + - query_leaf_policy_group.current.0.fabricLePortPGrp.attributes.descr == "leaf_policy_group created" + - query_all_leaf_policy_group is not changed + - query_all_leaf_policy_group.current != [] + - query_all_leaf_policy_group.current | length >= 1 + - query_a_spine_policy_group is not changed + - query_a_spine_policy_group.current != [] + - query_a_spine_policy_group.current.0.fabricSpPortPGrp.attributes.name == "spine_policy_group" + - query_a_spine_policy_group.current.0.fabricSpPortPGrp.attributes.dn == "uni/fabric/funcprof/spportgrp-spine_policy_group" + - query_a_spine_policy_group.current.0.fabricSpPortPGrp.attributes.descr == "spine_policy_group updated" + - query_all_spine_policy_group is not changed + - query_all_spine_policy_group.current != [] + - query_all_spine_policy_group.current | length >= 1 + +- name: Remove a leaf fabric interface policy group with check mode + aci_fabric_interface_policy_group: &cm_leaf_policy_group_absent + <<: *cm_leaf_policy_group_present + state: absent + check_mode: true + register: cm_leaf_policy_group_absent + +- name: Remove a leaf fabric interface policy group with normal mode + aci_fabric_interface_policy_group: + <<: *cm_leaf_policy_group_absent + register: nm_leaf_policy_group_absent + +- name: Remove a leaf fabric interface policy group with normal mode again + aci_fabric_interface_policy_group: + <<: *cm_leaf_policy_group_absent + register: nm_leaf_policy_group_absent_again + +- name: Remove a spine fabric interface policy group with check mode + aci_fabric_interface_policy_group: &cm_spine_policy_group_absent + <<: *cm_spine_policy_group_present + state: absent + check_mode: true + register: cm_spine_policy_group_absent + +- name: Remove a spine fabric interface policy group with normal mode + aci_fabric_interface_policy_group: + <<: *cm_spine_policy_group_absent + register: nm_spine_policy_group_absent + +- name: Remove a spine fabric interface policy group with normal mode again + aci_fabric_interface_policy_group: + <<: *cm_spine_policy_group_absent + register: nm_spine_policy_group_absent_again + +- name: Assertions check for remove a fabric interface policy groups + assert: + that: + - cm_leaf_policy_group_absent is changed + - cm_leaf_policy_group_absent.current != [] + - cm_leaf_policy_group_absent.previous != [] + - nm_leaf_policy_group_absent is changed + - nm_leaf_policy_group_absent.current == [] + - nm_leaf_policy_group_absent.previous != [] + - nm_leaf_policy_group_absent_again is not changed + - nm_leaf_policy_group_absent_again.current == [] + - nm_leaf_policy_group_absent_again.previous == [] + - cm_spine_policy_group_absent is changed + - cm_spine_policy_group_absent.current != [] + - cm_spine_policy_group_absent.previous != [] + - nm_spine_policy_group_absent is changed + - nm_spine_policy_group_absent.current == [] + - nm_spine_policy_group_absent.previous != [] + - nm_spine_policy_group_absent_again is not changed + - nm_spine_policy_group_absent_again.current == [] + - nm_spine_policy_group_absent_again.previous == [] diff --git a/ansible_collections/cisco/aci/tests/integration/targets/aci_fabric_node/tasks/main.yml b/ansible_collections/cisco/aci/tests/integration/targets/aci_fabric_node/tasks/main.yml index 36e065e5b..552661556 100644 --- a/ansible_collections/cisco/aci/tests/integration/targets/aci_fabric_node/tasks/main.yml +++ b/ansible_collections/cisco/aci/tests/integration/targets/aci_fabric_node/tasks/main.yml @@ -1,5 +1,6 @@ # Test code for the ACI modules # Copyright: (c) 2017, Bruno Calogero <brunocalogero@hotmail.com> +# Copyright: (c) 2023, Gaspard Micol <gmicol@cisco.com> # GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) @@ -8,38 +9,52 @@ msg: 'Please define the following variables: aci_hostname, aci_username and aci_password.' when: aci_hostname is not defined or aci_username is not defined or aci_password is not defined +- name: Set vars + set_fact: + aci_info: &aci_info + host: "{{ aci_hostname }}" + username: "{{ aci_username }}" + password: "{{ aci_password }}" + validate_certs: '{{ aci_validate_certs | default(false) }}' + use_ssl: '{{ aci_use_ssl | default(true) }}' + use_proxy: '{{ aci_use_proxy | default(true) }}' + output_level: '{{ aci_output_level | default("info") }}' # CLEAN ENVIRONMENT - name: Remove fabric node cisco.aci.aci_fabric_node: &aci_fabric_node_absent - host: '{{ aci_hostname }}' - username: '{{ aci_username }}' - password: '{{ aci_password }}' - validate_certs: '{{ aci_validate_certs | default(false) }}' - use_ssl: '{{ aci_use_ssl | default(true) }}' - use_proxy: '{{ aci_use_proxy | default(true) }}' - output_level: '{{ aci_output_level | default("info") }}' + <<: *aci_info serial: ansible_test node_id: 105 state: absent +- name: Remove fabric node 2 + cisco.aci.aci_fabric_node: &aci_fabric_node_absent_2 + <<: *aci_info + serial: ansible_test_2 + node_id: 106 + state: absent # ADD FABRIC NODE -- name: Add fabric node (check_mode) +- name: Add fabric node with tier-2 leaf (check_mode) cisco.aci.aci_fabric_node: &aci_fabric_node_present - host: '{{ aci_hostname }}' - username: '{{ aci_username }}' - password: '{{ aci_password }}' - validate_certs: '{{ aci_validate_certs | default(false) }}' - use_ssl: '{{ aci_use_ssl | default(true) }}' - use_proxy: '{{ aci_use_proxy | default(true) }}' - output_level: debug - serial: ansible_test - node_id: 105 + <<: *aci_fabric_node_absent switch: anstest + role: leaf + node_type: tier_2 state: present check_mode: true - register: cm_add_fabric_node + register: cm_add_fabric_node_tier_2 + +- name: Add fabric node with remote leaf + cisco.aci.aci_fabric_node: &aci_fabric_node_present_2 + <<: *aci_fabric_node_absent_2 + switch: anstest_2 + role: leaf + node_type: remote + remote_leaf_pool_id: 2 + state: present + register: nm_add_fabric_node_remote - name: Add fabric node (normal mode) cisco.aci.aci_fabric_node: *aci_fabric_node_present @@ -54,15 +69,44 @@ cisco.aci.aci_fabric_node: *aci_fabric_node_present register: nm_add_fabric_node_again +- name: test error - mutually exclusive attributes + cisco.aci.aci_fabric_node: + <<: *aci_fabric_node_present_2 + node_type: tier_2 + register: test_error_mutually_exclusive + ignore_errors: true + +- name: test error - check failure when changing node type + cisco.aci.aci_fabric_node: + <<: *aci_fabric_node_present + node_type: virtual + register: test_error_change__node_type + ignore_errors: true + +- name: test error - check failure when changing Pool Id + cisco.aci.aci_fabric_node: + <<: *aci_fabric_node_present_2 + remote_leaf_pool_id: 3 + register: test_error_change_Pool_Id + ignore_errors: true + - name: Verify add_fabric_node assert: that: - - cm_add_fabric_node is changed + - cm_add_fabric_node_tier_2 is changed - nm_add_fabric_node is changed - nm_add_fabric_node.current.0.fabricNodeIdentP.attributes.annotation == 'orchestrator:ansible' + - nm_add_fabric_node.current.0.fabricNodeIdentP.attributes.role == 'leaf' + - nm_add_fabric_node.current.0.fabricNodeIdentP.attributes.nodeType == 'tier-2-leaf' + - nm_add_fabric_node_remote is changed + - nm_add_fabric_node_remote.current.0.fabricNodeIdentP.attributes.nodeType == 'remote-leaf-wan' + - nm_add_fabric_node_remote.current.0.fabricNodeIdentP.attributes.extPoolId == '2' # FIXME: Module is not idempotent - cm_add_fabric_node_again is not changed - nm_add_fabric_node_again is not changed + - test_error_mutually_exclusive.msg == "External Pool Id is not compatible with a node type other than 'remote'." + - test_error_change__node_type.error.text == "Create-only and naming props cannot be modified after creation, class=fabricNodeIdentP, prop=nodeType" + - test_error_change_Pool_Id.error.text == "Create-only and naming props cannot be modified after creation, class=fabricNodeIdentP, prop=extPoolId" # CHANGE FABRIC NODE @@ -150,7 +194,7 @@ - name: Query our fabric_node cisco.aci.aci_fabric_node: <<: *aci_fabric_node_query - serial: ansible_test # might need node_id too + serial: ansible_test check_mode: true register: cm_query_fabric_node diff --git a/ansible_collections/cisco/aci/tests/integration/targets/aci_fabric_node_control/aliases b/ansible_collections/cisco/aci/tests/integration/targets/aci_fabric_node_control/aliases new file mode 100644 index 000000000..209b793f9 --- /dev/null +++ b/ansible_collections/cisco/aci/tests/integration/targets/aci_fabric_node_control/aliases @@ -0,0 +1,2 @@ +# No ACI simulator yet, so not enabled +# unsupported diff --git a/ansible_collections/cisco/aci/tests/integration/targets/aci_fabric_node_control/tasks/main.yml b/ansible_collections/cisco/aci/tests/integration/targets/aci_fabric_node_control/tasks/main.yml new file mode 100644 index 000000000..f41c69150 --- /dev/null +++ b/ansible_collections/cisco/aci/tests/integration/targets/aci_fabric_node_control/tasks/main.yml @@ -0,0 +1,179 @@ +# Test code for the ACI modules +# Copyright: (c) 2023, Tim Cragg (@timcragg) +# Copyright: (c) 2023, Akini Ross (@akinross) + +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +- name: Test that we have an ACI APIC host, ACI username and ACI password + fail: + msg: 'Please define the following variables: aci_hostname, aci_username and aci_password.' + when: aci_hostname is not defined or aci_username is not defined or aci_password is not defined + +- name: Set vars + set_fact: + aci_info: &aci_info + host: "{{ aci_hostname }}" + username: "{{ aci_username }}" + password: "{{ aci_password }}" + validate_certs: '{{ aci_validate_certs | default(false) }}' + use_ssl: '{{ aci_use_ssl | default(true) }}' + use_proxy: '{{ aci_use_proxy | default(true) }}' + output_level: '{{ aci_output_level | default("info") }}' + +# CLEAN ENVIRONMENT +- name: Pre-Test Clean Environment of Fabric Node Controls + cisco.aci.aci_fabric_node_control: + <<: *aci_info + name: "{{ item }}" + state: absent + loop: + - ansible_fabric_node_control_1 + - ansible_fabric_node_control_2 + - ansible_fabric_node_control_3 + +# CREATE FABRIC NODE CONTROL +- name: Create Fabric Node Control (check mode) + cisco.aci.aci_fabric_node_control: &aci_fabric_node_control + <<: *aci_info + name: ansible_fabric_node_control_1 + check_mode: true + register: cm_create_fabric_node_control + +- name: Create Fabric Node Control (normal mode) + cisco.aci.aci_fabric_node_control: + <<: *aci_fabric_node_control + register: nm_create_fabric_node_control + +- name: Create Fabric Node Control again + cisco.aci.aci_fabric_node_control: + <<: *aci_fabric_node_control + register: nm_create_fabric_node_control_again + +- name: Verify Fabric Node Control creation + ansible.builtin.assert: + that: + - cm_create_fabric_node_control is changed + - cm_create_fabric_node_control.current == [] + - cm_create_fabric_node_control.previous == [] + - cm_create_fabric_node_control.proposed.fabricNodeControl.attributes.name == "ansible_fabric_node_control_1" + - nm_create_fabric_node_control is changed + - nm_create_fabric_node_control.previous == [] + - nm_create_fabric_node_control.current.0.fabricNodeControl.attributes.control == "" + - nm_create_fabric_node_control.current.0.fabricNodeControl.attributes.descr == "" + - nm_create_fabric_node_control.current.0.fabricNodeControl.attributes.featureSel == "telemetry" + - nm_create_fabric_node_control.current.0.fabricNodeControl.attributes.name == "ansible_fabric_node_control_1" + - nm_create_fabric_node_control_again is not changed + - nm_create_fabric_node_control_again.current.0.fabricNodeControl.attributes.control == "" + - nm_create_fabric_node_control_again.current.0.fabricNodeControl.attributes.descr == "" + - nm_create_fabric_node_control_again.current.0.fabricNodeControl.attributes.featureSel == "telemetry" + - nm_create_fabric_node_control_again.current.0.fabricNodeControl.attributes.name == "ansible_fabric_node_control_1" + - nm_create_fabric_node_control_again.previous.0.fabricNodeControl.attributes.control == "" + - nm_create_fabric_node_control_again.previous.0.fabricNodeControl.attributes.descr == "" + - nm_create_fabric_node_control_again.previous.0.fabricNodeControl.attributes.featureSel == "telemetry" + - nm_create_fabric_node_control_again.previous.0.fabricNodeControl.attributes.name == "ansible_fabric_node_control_1" + +# UPDATE FABRIC NODE CONTROL +- name: Update Fabric Node Control + cisco.aci.aci_fabric_node_control: + <<: *aci_fabric_node_control + description: Updated Ansible Test Fabric Node Control + enable_dom: true + feature_selection: analytics + register: update_fabric_node_control + +- name: Verify Fabric Node Control update + ansible.builtin.assert: + that: + - update_fabric_node_control is changed + - update_fabric_node_control.current.0.fabricNodeControl.attributes.control == "Dom" + - update_fabric_node_control.current.0.fabricNodeControl.attributes.descr == "Updated Ansible Test Fabric Node Control" + - update_fabric_node_control.current.0.fabricNodeControl.attributes.featureSel == "analytics" + - update_fabric_node_control.current.0.fabricNodeControl.attributes.name == "ansible_fabric_node_control_1" + - update_fabric_node_control.previous.0.fabricNodeControl.attributes.control == "" + - update_fabric_node_control.previous.0.fabricNodeControl.attributes.descr == "" + - update_fabric_node_control.previous.0.fabricNodeControl.attributes.featureSel == "telemetry" + - update_fabric_node_control.previous.0.fabricNodeControl.attributes.name == "ansible_fabric_node_control_1" + +# QUERY FABRIC NODE CONTROL +- name: Create another Fabric Node Control + cisco.aci.aci_fabric_node_control: + <<: *aci_info + name: "{{ item }}" + feature_selection: netflow + loop: + - ansible_fabric_node_control_2 + - ansible_fabric_node_control_3 + +- name: Query a Fabric Node Control + cisco.aci.aci_fabric_node_control: + <<: *aci_fabric_node_control + state: query + register: query_one + +- name: Query all Fabric Node Controls + cisco.aci.aci_fabric_node_control: + <<: *aci_info + state: query + register: query_all + +- name: Verify Fabric Node Control queries + ansible.builtin.assert: + that: + - query_one is not changed + - query_one.current | length == 1 + - query_one.current.0.fabricNodeControl.attributes.control == "Dom" + - query_one.current.0.fabricNodeControl.attributes.descr == "Updated Ansible Test Fabric Node Control" + - query_one.current.0.fabricNodeControl.attributes.featureSel == "analytics" + - query_one.current.0.fabricNodeControl.attributes.name == "ansible_fabric_node_control_1" + - query_all is not changed + - query_all.current | length > 3 + +# REMOVE FABRIC NODE CONTROL +- name: Remove Fabric Node Control (check mode) + cisco.aci.aci_fabric_node_control: + <<: *aci_fabric_node_control + state: absent + register: cm_remove_fabric_node_control + check_mode: true + +- name: Remove Fabric Node Control + cisco.aci.aci_fabric_node_control: + <<: *aci_fabric_node_control + state: absent + register: nm_remove_fabric_node_control + +- name: Remove Fabric Node Control again + cisco.aci.aci_fabric_node_control: + <<: *aci_fabric_node_control + state: absent + register: nm__remove_fabric_node_control_again + +- name: Verify Fabric Node Control removal + ansible.builtin.assert: + that: + - cm_remove_fabric_node_control is changed + - cm_remove_fabric_node_control.proposed == {} + - cm_remove_fabric_node_control.previous.0.fabricNodeControl.attributes.control == "Dom" + - cm_remove_fabric_node_control.previous.0.fabricNodeControl.attributes.descr == "Updated Ansible Test Fabric Node Control" + - cm_remove_fabric_node_control.previous.0.fabricNodeControl.attributes.featureSel == "analytics" + - cm_remove_fabric_node_control.previous.0.fabricNodeControl.attributes.name == "ansible_fabric_node_control_1" + - nm_remove_fabric_node_control is changed + - nm_remove_fabric_node_control.current == [] + - nm_remove_fabric_node_control.previous.0.fabricNodeControl.attributes.control == "Dom" + - nm_remove_fabric_node_control.previous.0.fabricNodeControl.attributes.descr == "Updated Ansible Test Fabric Node Control" + - nm_remove_fabric_node_control.previous.0.fabricNodeControl.attributes.featureSel == "analytics" + - nm_remove_fabric_node_control.previous.0.fabricNodeControl.attributes.name == "ansible_fabric_node_control_1" + - nm__remove_fabric_node_control_again is not changed + - nm__remove_fabric_node_control_again.current == [] + - nm__remove_fabric_node_control_again.previous == [] + +# CLEAN ENVIRONMENT +- name: PPost-Test Clean Environment of Fabric Node Controls + cisco.aci.aci_fabric_node_control: + <<: *aci_info + name: "{{ item }}" + state: absent + loop: + - ansible_fabric_node_control_1 + - ansible_fabric_node_control_2 + - ansible_fabric_node_control_3 diff --git a/ansible_collections/cisco/aci/tests/integration/targets/aci_fabric_pod_selector/aliases b/ansible_collections/cisco/aci/tests/integration/targets/aci_fabric_pod_selector/aliases new file mode 100644 index 000000000..209b793f9 --- /dev/null +++ b/ansible_collections/cisco/aci/tests/integration/targets/aci_fabric_pod_selector/aliases @@ -0,0 +1,2 @@ +# No ACI simulator yet, so not enabled +# unsupported diff --git a/ansible_collections/cisco/aci/tests/integration/targets/aci_fabric_pod_selector/tasks/main.yml b/ansible_collections/cisco/aci/tests/integration/targets/aci_fabric_pod_selector/tasks/main.yml new file mode 100644 index 000000000..ca9fb8209 --- /dev/null +++ b/ansible_collections/cisco/aci/tests/integration/targets/aci_fabric_pod_selector/tasks/main.yml @@ -0,0 +1,305 @@ +# Test code for the ACI modules +# Copyright: (c) 2023, Tim Cragg (@timcragg) +# Copyright: (c) 2023, Akini Ross (@akinross) + +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +- name: Test that we have an ACI APIC host, ACI username and ACI password + fail: + msg: 'Please define the following variables: aci_hostname, aci_username and aci_password.' + when: aci_hostname is not defined or aci_username is not defined or aci_password is not defined + +- name: Set vars + set_fact: + aci_info: &aci_info + host: "{{ aci_hostname }}" + username: "{{ aci_username }}" + password: "{{ aci_password }}" + validate_certs: '{{ aci_validate_certs | default(false) }}' + use_ssl: '{{ aci_use_ssl | default(true) }}' + use_proxy: '{{ aci_use_proxy | default(true) }}' + output_level: '{{ aci_output_level | default("info") }}' + +# CLEAN EXISTING ENVIRONMENT +- name: Ensure Fabric Pod Selectors are absent before testing + cisco.aci.aci_fabric_pod_selector: + <<: *aci_info + name: "{{ item.name }}" + pod_profile: default + type: "{{ item.type }}" + state: absent + loop: + - {name: ansible_fabric_pod_pol_sel_1, type: all} + - {name: ansible_fabric_pod_pol_sel_2, type: range} + - {name: ansible_fabric_pod_pol_sel_3, type: range} + - {name: default, type: all} + +- name: Ensure a Pod Policy Group exists before testing + cisco.aci.aci_fabric_pod_policy_group: + <<: *aci_info + name: ansible_fabric_pod_pol_grp_1 + +# CREATE FABRIC POD SELECTOR TYPE ALL +- name: Create Fabric Pod Selector without blocks (check mode) + cisco.aci.aci_fabric_pod_selector: &fabric_pod_sel_without_blocks + <<: *aci_info + name: ansible_fabric_pod_pol_sel_1 + type: all + pod_profile: default + description: Created Ansible Pod Selector + check_mode: true + register: cm_create_fabric_pod_sel_without_blocks + +- name: Create Fabric Pod Selector without blocks + cisco.aci.aci_fabric_pod_selector: + <<: *fabric_pod_sel_without_blocks + register: nm_create_fabric_pod_sel_without_blocks + +- name: Create Fabric Pod Selector without blocks again + cisco.aci.aci_fabric_pod_selector: + <<: *fabric_pod_sel_without_blocks + register: nm_create_fabric_pod_sel_without_blocks_again + +- name: Verify create of Pod Selector + ansible.builtin.assert: + that: + - cm_create_fabric_pod_sel_without_blocks is changed + - cm_create_fabric_pod_sel_without_blocks.current == [] + - cm_create_fabric_pod_sel_without_blocks.previous == [] + - cm_create_fabric_pod_sel_without_blocks.proposed.fabricPodS.attributes.annotation == "orchestrator:ansible" + - cm_create_fabric_pod_sel_without_blocks.proposed.fabricPodS.attributes.descr == "Created Ansible Pod Selector" + - cm_create_fabric_pod_sel_without_blocks.proposed.fabricPodS.attributes.dn == "uni/fabric/podprof-default/pods-ansible_fabric_pod_pol_sel_1-typ-ALL" + - cm_create_fabric_pod_sel_without_blocks.proposed.fabricPodS.attributes.name == "ansible_fabric_pod_pol_sel_1" + - cm_create_fabric_pod_sel_without_blocks.proposed.fabricPodS.attributes.type == "ALL" + - nm_create_fabric_pod_sel_without_blocks is changed + - nm_create_fabric_pod_sel_without_blocks.current.0.fabricPodS.attributes.annotation == "orchestrator:ansible" + - nm_create_fabric_pod_sel_without_blocks.current.0.fabricPodS.attributes.descr == "Created Ansible Pod Selector" + - nm_create_fabric_pod_sel_without_blocks.current.0.fabricPodS.attributes.dn == "uni/fabric/podprof-default/pods-ansible_fabric_pod_pol_sel_1-typ-ALL" + - nm_create_fabric_pod_sel_without_blocks.current.0.fabricPodS.attributes.name == "ansible_fabric_pod_pol_sel_1" + - nm_create_fabric_pod_sel_without_blocks.current.0.fabricPodS.attributes.type == "ALL" + - nm_create_fabric_pod_sel_without_blocks.previous == [] + - nm_create_fabric_pod_sel_without_blocks.proposed.fabricPodS.attributes.annotation == "orchestrator:ansible" + - nm_create_fabric_pod_sel_without_blocks.proposed.fabricPodS.attributes.descr == "Created Ansible Pod Selector" + - nm_create_fabric_pod_sel_without_blocks.proposed.fabricPodS.attributes.dn == "uni/fabric/podprof-default/pods-ansible_fabric_pod_pol_sel_1-typ-ALL" + - nm_create_fabric_pod_sel_without_blocks.proposed.fabricPodS.attributes.name == "ansible_fabric_pod_pol_sel_1" + - nm_create_fabric_pod_sel_without_blocks.proposed.fabricPodS.attributes.type == "ALL" + - nm_create_fabric_pod_sel_without_blocks_again is not changed + - nm_create_fabric_pod_sel_without_blocks_again.current.0.fabricPodS.attributes.annotation == "orchestrator:ansible" + - nm_create_fabric_pod_sel_without_blocks_again.current.0.fabricPodS.attributes.descr == "Created Ansible Pod Selector" + - nm_create_fabric_pod_sel_without_blocks_again.current.0.fabricPodS.attributes.dn == "uni/fabric/podprof-default/pods-ansible_fabric_pod_pol_sel_1-typ-ALL" + - nm_create_fabric_pod_sel_without_blocks_again.current.0.fabricPodS.attributes.name == "ansible_fabric_pod_pol_sel_1" + - nm_create_fabric_pod_sel_without_blocks_again.current.0.fabricPodS.attributes.type == "ALL" + - nm_create_fabric_pod_sel_without_blocks_again.previous.0.fabricPodS.attributes.annotation == "orchestrator:ansible" + - nm_create_fabric_pod_sel_without_blocks_again.previous.0.fabricPodS.attributes.descr == "Created Ansible Pod Selector" + - nm_create_fabric_pod_sel_without_blocks_again.previous.0.fabricPodS.attributes.dn == "uni/fabric/podprof-default/pods-ansible_fabric_pod_pol_sel_1-typ-ALL" + - nm_create_fabric_pod_sel_without_blocks_again.previous.0.fabricPodS.attributes.name == "ansible_fabric_pod_pol_sel_1" + - nm_create_fabric_pod_sel_without_blocks_again.previous.0.fabricPodS.attributes.type == "ALL" + +# REMOVE FABRIC POD SELECTOR +- name: Remove Fabric Pod Selector without blocks (check mode) + cisco.aci.aci_fabric_pod_selector: + <<: *fabric_pod_sel_without_blocks + state: absent + check_mode: true + register: cm_remove_fabric_pod_sel_without_blocks + +- name: Remove Fabric Pod Selector without blocks + cisco.aci.aci_fabric_pod_selector: + <<: *fabric_pod_sel_without_blocks + state: absent + register: nm_remove_fabric_pod_sel_without_blocks + +- name: Remove Fabric Pod Selector without blocks again + cisco.aci.aci_fabric_pod_selector: + <<: *fabric_pod_sel_without_blocks + state: absent + register: nm_remove_fabric_pod_sel_without_blocks_again + +- name: Verify removal of Pod Selector + ansible.builtin.assert: + that: + - cm_remove_fabric_pod_sel_without_blocks is changed + - cm_remove_fabric_pod_sel_without_blocks.proposed == {} + - cm_remove_fabric_pod_sel_without_blocks.current.0.fabricPodS.attributes.annotation == "orchestrator:ansible" + - cm_remove_fabric_pod_sel_without_blocks.current.0.fabricPodS.attributes.descr == "Created Ansible Pod Selector" + - cm_remove_fabric_pod_sel_without_blocks.current.0.fabricPodS.attributes.dn == "uni/fabric/podprof-default/pods-ansible_fabric_pod_pol_sel_1-typ-ALL" + - cm_remove_fabric_pod_sel_without_blocks.current.0.fabricPodS.attributes.name == "ansible_fabric_pod_pol_sel_1" + - cm_remove_fabric_pod_sel_without_blocks.current.0.fabricPodS.attributes.type == "ALL" + - cm_remove_fabric_pod_sel_without_blocks.previous.0.fabricPodS.attributes.annotation == "orchestrator:ansible" + - cm_remove_fabric_pod_sel_without_blocks.previous.0.fabricPodS.attributes.descr == "Created Ansible Pod Selector" + - cm_remove_fabric_pod_sel_without_blocks.previous.0.fabricPodS.attributes.dn == "uni/fabric/podprof-default/pods-ansible_fabric_pod_pol_sel_1-typ-ALL" + - cm_remove_fabric_pod_sel_without_blocks.previous.0.fabricPodS.attributes.name == "ansible_fabric_pod_pol_sel_1" + - cm_remove_fabric_pod_sel_without_blocks.previous.0.fabricPodS.attributes.type == "ALL" + - nm_remove_fabric_pod_sel_without_blocks is changed + - nm_remove_fabric_pod_sel_without_blocks.proposed == {} + - nm_remove_fabric_pod_sel_without_blocks.current == [] + - nm_remove_fabric_pod_sel_without_blocks.previous.0.fabricPodS.attributes.annotation == "orchestrator:ansible" + - nm_remove_fabric_pod_sel_without_blocks.previous.0.fabricPodS.attributes.descr == "Created Ansible Pod Selector" + - nm_remove_fabric_pod_sel_without_blocks.previous.0.fabricPodS.attributes.dn == "uni/fabric/podprof-default/pods-ansible_fabric_pod_pol_sel_1-typ-ALL" + - nm_remove_fabric_pod_sel_without_blocks.previous.0.fabricPodS.attributes.name == "ansible_fabric_pod_pol_sel_1" + - nm_remove_fabric_pod_sel_without_blocks.previous.0.fabricPodS.attributes.type == "ALL" + - nm_remove_fabric_pod_sel_without_blocks_again is not changed + - nm_remove_fabric_pod_sel_without_blocks_again.current == [] + - nm_remove_fabric_pod_sel_without_blocks_again.previous == [] + +# CREATE & UPDATE FABRIC POD SELECTOR TYPE RANGE +- name: Create Fabric Pod Selector with blocks and policy + cisco.aci.aci_fabric_pod_selector: &fabric_pod_sel_blocks + <<: *fabric_pod_sel_without_blocks + name: ansible_fabric_pod_pol_sel_2 + type: range + blocks: "2" + policy_group: ansible_fabric_pod_pol_grp + register: nm_update_fabric_pod_sel_with_blocks + +- name: Create Fabric Pod Selector with blocks and policy again + cisco.aci.aci_fabric_pod_selector: + <<: *fabric_pod_sel_blocks + blocks: "2" + register: nm_update_fabric_pod_sel_with_blocks_again + +- name: Update Fabric Pod Selector with blocks and policy again with different notation + cisco.aci.aci_fabric_pod_selector: + <<: *fabric_pod_sel_blocks + blocks: "2-2" + register: nm_update_fabric_pod_sel_with_blocks_again_different_notation + +- name: Update Fabric Pod Selector with changed blocks range + cisco.aci.aci_fabric_pod_selector: &fabric_pod_sel_blocks_range + <<: *fabric_pod_sel_blocks + blocks: "2-3,5,8-11" + register: nm_update_fabric_pod_sel_with_blocks_range + +- name: Update Fabric Pod Selector with removal of policy group + cisco.aci.aci_fabric_pod_selector: + <<: *fabric_pod_sel_blocks_range + policy_group: "" + register: nm_update_fabric_pod_sel_with_blocks_no_policy + +- name: Verify update of Pod Selector + ansible.builtin.assert: + that: + - nm_update_fabric_pod_sel_with_blocks is changed + - nm_update_fabric_pod_sel_with_blocks.current.0.fabricPodS.attributes.annotation == "orchestrator:ansible" + - nm_update_fabric_pod_sel_with_blocks.current.0.fabricPodS.attributes.descr == "Created Ansible Pod Selector" + - nm_update_fabric_pod_sel_with_blocks.current.0.fabricPodS.attributes.dn == "uni/fabric/podprof-default/pods-ansible_fabric_pod_pol_sel_2-typ-range" + - nm_update_fabric_pod_sel_with_blocks.current.0.fabricPodS.attributes.name == "ansible_fabric_pod_pol_sel_2" + - nm_update_fabric_pod_sel_with_blocks.current.0.fabricPodS.attributes.type == "range" + - nm_update_fabric_pod_sel_with_blocks.current.0.fabricPodS.children.0.fabricRsPodPGrp.attributes.tDn == "uni/fabric/funcprof/podpgrp-ansible_fabric_pod_pol_grp" + - nm_update_fabric_pod_sel_with_blocks.current.0.fabricPodS.children.1.fabricPodBlk.attributes.from_ == "2" + - nm_update_fabric_pod_sel_with_blocks.current.0.fabricPodS.children.1.fabricPodBlk.attributes.to_ == "2" + - nm_update_fabric_pod_sel_with_blocks.previous == [] + - nm_update_fabric_pod_sel_with_blocks_again is not changed + - nm_update_fabric_pod_sel_with_blocks_again.current.0.fabricPodS.attributes.annotation == "orchestrator:ansible" + - nm_update_fabric_pod_sel_with_blocks_again.current.0.fabricPodS.attributes.descr == "Created Ansible Pod Selector" + - nm_update_fabric_pod_sel_with_blocks_again.current.0.fabricPodS.attributes.dn == "uni/fabric/podprof-default/pods-ansible_fabric_pod_pol_sel_2-typ-range" + - nm_update_fabric_pod_sel_with_blocks_again.current.0.fabricPodS.attributes.name == "ansible_fabric_pod_pol_sel_2" + - nm_update_fabric_pod_sel_with_blocks_again.current.0.fabricPodS.attributes.type == "range" + - nm_update_fabric_pod_sel_with_blocks_again.current.0.fabricPodS.children.0.fabricRsPodPGrp.attributes.tDn == "uni/fabric/funcprof/podpgrp-ansible_fabric_pod_pol_grp" + - nm_update_fabric_pod_sel_with_blocks_again.current.0.fabricPodS.children.1.fabricPodBlk.attributes.from_ == "2" + - nm_update_fabric_pod_sel_with_blocks_again.current.0.fabricPodS.children.1.fabricPodBlk.attributes.to_ == "2" + - nm_update_fabric_pod_sel_with_blocks_again.previous.0.fabricPodS.attributes.annotation == "orchestrator:ansible" + - nm_update_fabric_pod_sel_with_blocks_again.previous.0.fabricPodS.attributes.descr == "Created Ansible Pod Selector" + - nm_update_fabric_pod_sel_with_blocks_again.previous.0.fabricPodS.attributes.dn == "uni/fabric/podprof-default/pods-ansible_fabric_pod_pol_sel_2-typ-range" + - nm_update_fabric_pod_sel_with_blocks_again.previous.0.fabricPodS.attributes.name == "ansible_fabric_pod_pol_sel_2" + - nm_update_fabric_pod_sel_with_blocks_again.previous.0.fabricPodS.attributes.type == "range" + - nm_update_fabric_pod_sel_with_blocks_again.previous.0.fabricPodS.children.0.fabricRsPodPGrp.attributes.tDn == "uni/fabric/funcprof/podpgrp-ansible_fabric_pod_pol_grp" + - nm_update_fabric_pod_sel_with_blocks_again.previous.0.fabricPodS.children.1.fabricPodBlk.attributes.from_ == "2" + - nm_update_fabric_pod_sel_with_blocks_again.previous.0.fabricPodS.children.1.fabricPodBlk.attributes.to_ == "2" + - nm_update_fabric_pod_sel_with_blocks_again_different_notation is not changed + - nm_update_fabric_pod_sel_with_blocks_again_different_notation.current.0.fabricPodS.attributes.annotation == "orchestrator:ansible" + - nm_update_fabric_pod_sel_with_blocks_again_different_notation.current.0.fabricPodS.attributes.descr == "Created Ansible Pod Selector" + - nm_update_fabric_pod_sel_with_blocks_again_different_notation.current.0.fabricPodS.attributes.dn == "uni/fabric/podprof-default/pods-ansible_fabric_pod_pol_sel_2-typ-range" + - nm_update_fabric_pod_sel_with_blocks_again_different_notation.current.0.fabricPodS.attributes.name == "ansible_fabric_pod_pol_sel_2" + - nm_update_fabric_pod_sel_with_blocks_again_different_notation.current.0.fabricPodS.attributes.type == "range" + - nm_update_fabric_pod_sel_with_blocks_again_different_notation.current.0.fabricPodS.children.0.fabricRsPodPGrp.attributes.tDn == "uni/fabric/funcprof/podpgrp-ansible_fabric_pod_pol_grp" + - nm_update_fabric_pod_sel_with_blocks_again_different_notation.current.0.fabricPodS.children.1.fabricPodBlk.attributes.from_ == "2" + - nm_update_fabric_pod_sel_with_blocks_again_different_notation.current.0.fabricPodS.children.1.fabricPodBlk.attributes.to_ == "2" + - nm_update_fabric_pod_sel_with_blocks_again_different_notation.previous.0.fabricPodS.attributes.annotation == "orchestrator:ansible" + - nm_update_fabric_pod_sel_with_blocks_again_different_notation.previous.0.fabricPodS.attributes.descr == "Created Ansible Pod Selector" + - nm_update_fabric_pod_sel_with_blocks_again_different_notation.previous.0.fabricPodS.attributes.dn == "uni/fabric/podprof-default/pods-ansible_fabric_pod_pol_sel_2-typ-range" + - nm_update_fabric_pod_sel_with_blocks_again_different_notation.previous.0.fabricPodS.attributes.name == "ansible_fabric_pod_pol_sel_2" + - nm_update_fabric_pod_sel_with_blocks_again_different_notation.previous.0.fabricPodS.attributes.type == "range" + - nm_update_fabric_pod_sel_with_blocks_again_different_notation.previous.0.fabricPodS.children.0.fabricRsPodPGrp.attributes.tDn == "uni/fabric/funcprof/podpgrp-ansible_fabric_pod_pol_grp" + - nm_update_fabric_pod_sel_with_blocks_again_different_notation.previous.0.fabricPodS.children.1.fabricPodBlk.attributes.from_ == "2" + - nm_update_fabric_pod_sel_with_blocks_again_different_notation.previous.0.fabricPodS.children.1.fabricPodBlk.attributes.to_ == "2" + - nm_update_fabric_pod_sel_with_blocks_range is changed + - nm_update_fabric_pod_sel_with_blocks_range.current.0.fabricPodS.attributes.annotation == "orchestrator:ansible" + - nm_update_fabric_pod_sel_with_blocks_range.current.0.fabricPodS.attributes.descr == "Created Ansible Pod Selector" + - nm_update_fabric_pod_sel_with_blocks_range.current.0.fabricPodS.attributes.dn == "uni/fabric/podprof-default/pods-ansible_fabric_pod_pol_sel_2-typ-range" + - nm_update_fabric_pod_sel_with_blocks_range.current.0.fabricPodS.attributes.name == "ansible_fabric_pod_pol_sel_2" + - nm_update_fabric_pod_sel_with_blocks_range.current.0.fabricPodS.attributes.type == "range" + - nm_update_fabric_pod_sel_with_blocks_range.current.0.fabricPodS.children.0.fabricRsPodPGrp.attributes.tDn == "uni/fabric/funcprof/podpgrp-ansible_fabric_pod_pol_grp" + - nm_update_fabric_pod_sel_with_blocks_range.current.0.fabricPodS.children | length == 4 + - nm_update_fabric_pod_sel_with_blocks_range.previous.0.fabricPodS.attributes.annotation == "orchestrator:ansible" + - nm_update_fabric_pod_sel_with_blocks_range.previous.0.fabricPodS.attributes.descr == "Created Ansible Pod Selector" + - nm_update_fabric_pod_sel_with_blocks_range.previous.0.fabricPodS.attributes.dn == "uni/fabric/podprof-default/pods-ansible_fabric_pod_pol_sel_2-typ-range" + - nm_update_fabric_pod_sel_with_blocks_range.previous.0.fabricPodS.attributes.name == "ansible_fabric_pod_pol_sel_2" + - nm_update_fabric_pod_sel_with_blocks_range.previous.0.fabricPodS.attributes.type == "range" + - nm_update_fabric_pod_sel_with_blocks_range.previous.0.fabricPodS.children.0.fabricRsPodPGrp.attributes.tDn == "uni/fabric/funcprof/podpgrp-ansible_fabric_pod_pol_grp" + - nm_update_fabric_pod_sel_with_blocks_range.previous.0.fabricPodS.children.1.fabricPodBlk.attributes.from_ == "2" + - nm_update_fabric_pod_sel_with_blocks_range.previous.0.fabricPodS.children.1.fabricPodBlk.attributes.to_ == "2" + - nm_update_fabric_pod_sel_with_blocks_no_policy is changed + - nm_update_fabric_pod_sel_with_blocks_no_policy.current.0.fabricPodS.attributes.annotation == "orchestrator:ansible" + - nm_update_fabric_pod_sel_with_blocks_no_policy.current.0.fabricPodS.attributes.descr == "Created Ansible Pod Selector" + - nm_update_fabric_pod_sel_with_blocks_no_policy.current.0.fabricPodS.attributes.dn == "uni/fabric/podprof-default/pods-ansible_fabric_pod_pol_sel_2-typ-range" + - nm_update_fabric_pod_sel_with_blocks_no_policy.current.0.fabricPodS.attributes.name == "ansible_fabric_pod_pol_sel_2" + - nm_update_fabric_pod_sel_with_blocks_no_policy.current.0.fabricPodS.attributes.type == "range" + - nm_update_fabric_pod_sel_with_blocks_no_policy.current.0.fabricPodS.children | length == 3 + - nm_update_fabric_pod_sel_with_blocks_no_policy.previous.0.fabricPodS.attributes.annotation == "orchestrator:ansible" + - nm_update_fabric_pod_sel_with_blocks_no_policy.previous.0.fabricPodS.attributes.descr == "Created Ansible Pod Selector" + - nm_update_fabric_pod_sel_with_blocks_no_policy.previous.0.fabricPodS.attributes.dn == "uni/fabric/podprof-default/pods-ansible_fabric_pod_pol_sel_2-typ-range" + - nm_update_fabric_pod_sel_with_blocks_no_policy.previous.0.fabricPodS.attributes.name == "ansible_fabric_pod_pol_sel_2" + - nm_update_fabric_pod_sel_with_blocks_no_policy.previous.0.fabricPodS.attributes.type == "range" + - nm_update_fabric_pod_sel_with_blocks_no_policy.previous.0.fabricPodS.children.0.fabricRsPodPGrp.attributes.tDn == "uni/fabric/funcprof/podpgrp-ansible_fabric_pod_pol_grp" + - nm_update_fabric_pod_sel_with_blocks_no_policy.previous.0.fabricPodS.children | length == 4 + +# QUERY FABRIC POD SELECTOR +- name: Create another Fabric Pod Selector + cisco.aci.aci_fabric_pod_selector: + <<: *fabric_pod_sel_blocks + name: ansible_fabric_pod_pol_sel_3 + blocks: "6" + +- name: Query a Fabric Pod Selector + cisco.aci.aci_fabric_pod_selector: + <<: *fabric_pod_sel_blocks + state: query + register: query_one + +- name: Query all Fabric Pod Selectors + cisco.aci.aci_fabric_pod_selector: + <<: *aci_info + state: query + register: query_all + +- name: Verify queries of Fabric Pod Selectors + ansible.builtin.assert: + that: + - query_one is not changed + - query_one.current | length == 1 + - query_one.current.0.fabricPodS.attributes.annotation == "orchestrator:ansible" + - query_one.current.0.fabricPodS.attributes.descr == "Created Ansible Pod Selector" + - query_one.current.0.fabricPodS.attributes.dn == "uni/fabric/podprof-default/pods-ansible_fabric_pod_pol_sel_2-typ-range" + - query_one.current.0.fabricPodS.attributes.name == "ansible_fabric_pod_pol_sel_2" + - query_one.current.0.fabricPodS.attributes.type == "range" + - query_one.current.0.fabricPodS.children | length == 3 + - query_all is not changed + - query_all.current | length >= 2 + +# ERRORS +- name: Create Fabric Pod Selector with type range and without blocks (error) + cisco.aci.aci_fabric_pod_selector: + <<: *aci_info + name: ansible_fabric_pod_pol_sel_2 + type: range + pod_profile: default + register: err_range_without_blocks + ignore_errors: true + +- name: Verify errors of Fabric Pod Selectors + ansible.builtin.assert: + that: + - err_range_without_blocks is not changed + - err_range_without_blocks is failed + - err_range_without_blocks.msg == "The 'blocks' parameter is required when the 'type' parameter is set to 'range' and 'state' parameter is set to 'present'." diff --git a/ansible_collections/cisco/aci/tests/integration/targets/aci_fabric_scheduler/aliases b/ansible_collections/cisco/aci/tests/integration/targets/aci_fabric_scheduler/aliases new file mode 100644 index 000000000..209b793f9 --- /dev/null +++ b/ansible_collections/cisco/aci/tests/integration/targets/aci_fabric_scheduler/aliases @@ -0,0 +1,2 @@ +# No ACI simulator yet, so not enabled +# unsupported diff --git a/ansible_collections/cisco/aci/tests/integration/targets/aci_fabric_scheduler/tasks/main.yml b/ansible_collections/cisco/aci/tests/integration/targets/aci_fabric_scheduler/tasks/main.yml new file mode 100644 index 000000000..daf5e8956 --- /dev/null +++ b/ansible_collections/cisco/aci/tests/integration/targets/aci_fabric_scheduler/tasks/main.yml @@ -0,0 +1,158 @@ +# Test code for the ACI modules +# Copyright: (c) 2023, Gaspard Micol (@gmicol) <gmicol@cisco.com> + +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +- name: Test that we have an ACI APIC host, ACI username and ACI password + fail: + msg: 'Please define the following variables: aci_hostname, aci_username and aci_password.' + when: aci_hostname is not defined or aci_username is not defined or aci_password is not defined + +- name: Set vars + set_fact: + aci_info: &aci_info + host: "{{ aci_hostname }}" + username: "{{ aci_username }}" + password: "{{ aci_password }}" + validate_certs: '{{ aci_validate_certs | default(false) }}' + use_ssl: '{{ aci_use_ssl | default(true) }}' + use_proxy: '{{ aci_use_proxy | default(true) }}' + output_level: debug + +- name: Verify Cloud and Non-Cloud Sites in use. + include_tasks: ../../../../../../integration/targets/aci_cloud_provider/tasks/main.yml + +- name: Execute tasks only for non-cloud sites + when: query_cloud.current == [] # This condition will execute only non-cloud sites + block: # block specifies execution of tasks within, based on conditions + # Clean Environment + - name: Ensure first scheduler does not exist + aci_fabric_scheduler: &aci_fabric_scheduler_absent + <<: *aci_info + name: anstest_scheduler_reccuring + description: ACI scheduler test + windowname: Recurring + recurring: True + concurCap: 20 + hour: 13 + minute: 30 + day: Tuesday + state: absent + + - name: Ensure second scheduler does not exist + aci_fabric_scheduler: &aci_fabric_scheduler_2_absent + <<: *aci_info + name: anstest_scheduler_oneTime + windowname: OneTime + recurring: False + concurCap: 20 + date: "2023-11-20T24:00:00" + state: absent + + - name: Create first scheduler (check_mode) + aci_fabric_scheduler: &aci_fabric_scheduler_present + <<: *aci_fabric_scheduler_absent + state: present + check_mode: true + register: cm_add_fabric_scheduler_1 + + - name: Create first scheduler (normal_mode) + aci_fabric_scheduler: + <<: *aci_fabric_scheduler_present + register: nm_add_fabric_scheduler_1 + + - name: Create first scheduler again - testing idempotency + aci_fabric_scheduler: + <<: *aci_fabric_scheduler_present + register: idempotency_add_fabric_scheduler_1 + + - name: Create second scheduler + aci_fabric_scheduler: &aci_fabric_scheduler_2_present + <<: *aci_fabric_scheduler_2_absent + state: present + register: nm_add_fabric_scheduler_2 + + - name: Asserts for creation tasks + assert: + that: + - cm_add_fabric_scheduler_1 is changed + - cm_add_fabric_scheduler_1.previous == [] + - cm_add_fabric_scheduler_1.current == [] + - nm_add_fabric_scheduler_1 is changed + - nm_add_fabric_scheduler_1.current.0.trigSchedP.attributes.name == "anstest_scheduler_reccuring" + - nm_add_fabric_scheduler_1.current.0.trigSchedP.attributes.descr == "ACI scheduler test" + - nm_add_fabric_scheduler_1.current.0.trigSchedP.children.0.trigRecurrWindowP.attributes.concurCap == "20" + - nm_add_fabric_scheduler_1.current.0.trigSchedP.children.0.trigRecurrWindowP.attributes.day == "Tuesday" + - nm_add_fabric_scheduler_1.current.0.trigSchedP.children.0.trigRecurrWindowP.attributes.hour == "13" + - nm_add_fabric_scheduler_1.current.0.trigSchedP.children.0.trigRecurrWindowP.attributes.minute == "30" + - idempotency_add_fabric_scheduler_1 is not changed + - nm_add_fabric_scheduler_2 is changed + - nm_add_fabric_scheduler_2.current.0.trigSchedP.attributes.name == "anstest_scheduler_oneTime" + - nm_add_fabric_scheduler_2.current.0.trigSchedP.children.0.trigAbsWindowP.attributes.concurCap == "20" + - nm_add_fabric_scheduler_2.current.0.trigSchedP.children.0.trigAbsWindowP.attributes.date == "2023-11-21T00:00:00.000+00:00" + + - name: Query all scheduler + aci_fabric_scheduler: + <<: *aci_info + state: query + register: query_all_fabric_scheduler + + - name: Query first scheduler + aci_fabric_scheduler: + <<: *aci_fabric_scheduler_present + state: query + register: query_first_fabric_scheduler + + - name: Asserts for query tasks + assert: + that: + - query_all_fabric_scheduler is not changed + - query_all_fabric_scheduler.current | length >= 2 + - '"trigAbsWindowP,trigRecurrWindowP" in query_all_fabric_scheduler.filter_string' + - '"class/trigSchedP.json" in query_all_fabric_scheduler.url' + - query_first_fabric_scheduler is not changed + - query_first_fabric_scheduler.current.0.trigSchedP.attributes.name == "anstest_scheduler_reccuring" + - query_first_fabric_scheduler.current.0.trigSchedP.attributes.descr == "ACI scheduler test" + - query_first_fabric_scheduler.current.0.trigSchedP.children.0.trigRecurrWindowP.attributes.concurCap == "20" + - query_first_fabric_scheduler.current.0.trigSchedP.children.0.trigRecurrWindowP.attributes.day == "Tuesday" + - query_first_fabric_scheduler.current.0.trigSchedP.children.0.trigRecurrWindowP.attributes.hour == "13" + - query_first_fabric_scheduler.current.0.trigSchedP.children.0.trigRecurrWindowP.attributes.minute == "30" + + - name: Delete first scheduler (check_mode) + aci_fabric_scheduler: + <<: *aci_fabric_scheduler_present + state: absent + check_mode: true + register: cm_delete_fabric_scheduler_1 + + - name: Delete first scheduler (normal_mode) + aci_fabric_scheduler: + <<: *aci_fabric_scheduler_present + state: absent + register: nm_delete_fabric_scheduler_1 + + - name: Delete first scheduler again - testing idempotency + aci_fabric_scheduler: + <<: *aci_fabric_scheduler_present + state: absent + register: idempotency_delete_maintenance_group_1 + + - name: Delete second scheduler (normal_mode) + aci_fabric_scheduler: + <<: *aci_fabric_scheduler_2_present + state: absent + register: nm_delete_fabric_scheduler_2 + + - name: Asserts for deletion tasks + assert: + that: + - cm_delete_fabric_scheduler_1 is changed + - cm_delete_fabric_scheduler_1.proposed == {} + - nm_delete_fabric_scheduler_1 is changed + - nm_delete_fabric_scheduler_1.previous != [] + - nm_delete_fabric_scheduler_1.current == [] + - idempotency_delete_maintenance_group_1 is not changed + - idempotency_delete_maintenance_group_1.previous == [] + - nm_delete_fabric_scheduler_2 is changed + - nm_delete_fabric_scheduler_2.previous != [] + - nm_delete_fabric_scheduler_2.current == [] diff --git a/ansible_collections/cisco/aci/tests/integration/targets/aci_fabric_span_dst_group/aliases b/ansible_collections/cisco/aci/tests/integration/targets/aci_fabric_span_dst_group/aliases new file mode 100644 index 000000000..209b793f9 --- /dev/null +++ b/ansible_collections/cisco/aci/tests/integration/targets/aci_fabric_span_dst_group/aliases @@ -0,0 +1,2 @@ +# No ACI simulator yet, so not enabled +# unsupported diff --git a/ansible_collections/cisco/aci/tests/integration/targets/aci_fabric_span_dst_group/tasks/main.yml b/ansible_collections/cisco/aci/tests/integration/targets/aci_fabric_span_dst_group/tasks/main.yml new file mode 100644 index 000000000..a67026daa --- /dev/null +++ b/ansible_collections/cisco/aci/tests/integration/targets/aci_fabric_span_dst_group/tasks/main.yml @@ -0,0 +1,264 @@ +# Test code for the ACI modules +# Copyright: (c) 2023, Akini Ross (akinross@cisco.com) + +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +- name: Test that we have an ACI APIC host, ACI username and ACI password + fail: + msg: 'Please define the following variables: aci_hostname, aci_username and aci_password.' + when: aci_hostname is not defined or aci_username is not defined or aci_password is not defined + +- name: Set vars + ansible.builtin.set_fact: + aci_info: &aci_info + host: "{{ aci_hostname }}" + username: "{{ aci_username }}" + password: "{{ aci_password }}" + validate_certs: '{{ aci_validate_certs | default(false) }}' + use_ssl: '{{ aci_use_ssl | default(true) }}' + use_proxy: '{{ aci_use_proxy | default(true) }}' + output_level: '{{ aci_output_level | default("info") }}' + +# CLEAN TEST ENVIRONMENT BEFORE TESTS + +- name: Query all fabric span destination groups ( clean before ) + cisco.aci.aci_fabric_span_dst_group: + <<: *aci_info + state: query + register: query_for_clean + +- name: Clean fabric span destination groups ( clean before ) + cisco.aci.aci_fabric_span_dst_group: + <<: *aci_info + destination_group: "{{ item.spanDestGrp.attributes.name }}" + state: absent + loop: "{{ query_for_clean.current }}" + +# TEST CREATE AND UPDATE DESTINATION GROUPS + +- name: Add fabric span destination group 1 ( check mode ) + cisco.aci.aci_fabric_span_dst_group: &add_ansible_group_1 + <<: *aci_info + destination_group: ansible_group_1 + description: test span epg + destination_epg: + destination_ip: 10.0.0.1 + source_ip: 10.0.2.1 + tenant: ansible_test + ap: ansible_test + epg: ansible_test + state: present + check_mode: true + register: cm_add_ansible_group_1 + +- name: Add fabric span destination group 1 + cisco.aci.aci_fabric_span_dst_group: + <<: *add_ansible_group_1 + register: nm_add_ansible_group_1 + +- name: Add fabric span destination group 1 again + cisco.aci.aci_fabric_span_dst_group: + <<: *add_ansible_group_1 + register: nm_add_ansible_group_1_again + +- name: Verify add fabric span destination group 1 + ansible.builtin.assert: + that: + - cm_add_ansible_group_1 is changed + - cm_add_ansible_group_1.current == [] + - cm_add_ansible_group_1.previous == [] + - cm_add_ansible_group_1.proposed.spanDestGrp.attributes.name == "ansible_group_1" + - cm_add_ansible_group_1.proposed.spanDestGrp.attributes.descr == "test span epg" + - cm_add_ansible_group_1.proposed.spanDestGrp.children.0.spanDest.attributes.name == "ansible_group_1" + - cm_add_ansible_group_1.proposed.spanDestGrp.children.0.spanDest.children.0.spanRsDestEpg.attributes.ip == "10.0.0.1" + - cm_add_ansible_group_1.proposed.spanDestGrp.children.0.spanDest.children.0.spanRsDestEpg.attributes.srcIpPrefix == "10.0.2.1" + - cm_add_ansible_group_1.proposed.spanDestGrp.children.0.spanDest.children.0.spanRsDestEpg.attributes.tDn == "uni/tn-ansible_test/ap-ansible_test/epg-ansible_test" + - nm_add_ansible_group_1 is changed + - nm_add_ansible_group_1.previous == [] + - nm_add_ansible_group_1.current.0.spanDestGrp.attributes.name == "ansible_group_1" + - nm_add_ansible_group_1.current.0.spanDestGrp.attributes.descr == "test span epg" + - nm_add_ansible_group_1.current.0.spanDestGrp.children.0.spanDest.attributes.name == "ansible_group_1" + - nm_add_ansible_group_1.current.0.spanDestGrp.children.0.spanDest.children.0.spanRsDestEpg.attributes.dscp == "unspecified" + - nm_add_ansible_group_1.current.0.spanDestGrp.children.0.spanDest.children.0.spanRsDestEpg.attributes.flowId == "1" + - nm_add_ansible_group_1.current.0.spanDestGrp.children.0.spanDest.children.0.spanRsDestEpg.attributes.ip == "10.0.0.1" + - nm_add_ansible_group_1.current.0.spanDestGrp.children.0.spanDest.children.0.spanRsDestEpg.attributes.mtu == "1518" + - nm_add_ansible_group_1.current.0.spanDestGrp.children.0.spanDest.children.0.spanRsDestEpg.attributes.srcIpPrefix == "10.0.2.1" + - nm_add_ansible_group_1.current.0.spanDestGrp.children.0.spanDest.children.0.spanRsDestEpg.attributes.tDn == "uni/tn-ansible_test/ap-ansible_test/epg-ansible_test" + - nm_add_ansible_group_1.current.0.spanDestGrp.children.0.spanDest.children.0.spanRsDestEpg.attributes.ttl == "64" + - nm_add_ansible_group_1.current.0.spanDestGrp.children.0.spanDest.children.0.spanRsDestEpg.attributes.ver == "ver2" + - nm_add_ansible_group_1.current.0.spanDestGrp.children.0.spanDest.children.0.spanRsDestEpg.attributes.verEnforced == "no" + - nm_add_ansible_group_1_again is not changed + - nm_add_ansible_group_1_again.current.0.spanDestGrp.attributes.name == "ansible_group_1" + - nm_add_ansible_group_1_again.current.0.spanDestGrp.attributes.descr == "test span epg" + - nm_add_ansible_group_1_again.current.0.spanDestGrp.children.0.spanDest.attributes.name == "ansible_group_1" + - nm_add_ansible_group_1_again.current.0.spanDestGrp.children.0.spanDest.children.0.spanRsDestEpg.attributes.dscp == "unspecified" + - nm_add_ansible_group_1_again.current.0.spanDestGrp.children.0.spanDest.children.0.spanRsDestEpg.attributes.flowId == "1" + - nm_add_ansible_group_1_again.current.0.spanDestGrp.children.0.spanDest.children.0.spanRsDestEpg.attributes.ip == "10.0.0.1" + - nm_add_ansible_group_1_again.current.0.spanDestGrp.children.0.spanDest.children.0.spanRsDestEpg.attributes.mtu == "1518" + - nm_add_ansible_group_1_again.current.0.spanDestGrp.children.0.spanDest.children.0.spanRsDestEpg.attributes.srcIpPrefix == "10.0.2.1" + - nm_add_ansible_group_1_again.current.0.spanDestGrp.children.0.spanDest.children.0.spanRsDestEpg.attributes.tDn == "uni/tn-ansible_test/ap-ansible_test/epg-ansible_test" + - nm_add_ansible_group_1_again.current.0.spanDestGrp.children.0.spanDest.children.0.spanRsDestEpg.attributes.ttl == "64" + - nm_add_ansible_group_1_again.current.0.spanDestGrp.children.0.spanDest.children.0.spanRsDestEpg.attributes.ver == "ver2" + - nm_add_ansible_group_1_again.current.0.spanDestGrp.children.0.spanDest.children.0.spanRsDestEpg.attributes.verEnforced == "no" + +- name: Change fabric span destination group 1 + cisco.aci.aci_fabric_span_dst_group: &change_ansible_group_1 + <<: *aci_info + destination_group: ansible_group_1 + description: changed test span epg + destination_epg: + destination_ip: 10.0.0.2 + source_ip: 10.0.2.2 + tenant: ansible_test + ap: ansible_test + epg: ansible_test + version_enforced: true + span_version: version_1 + ttl: 4 + mtu: 1500 + flow_id: 2 + dscp: "VA" + register: change_ansible_group_1 + +- name: Apply same changes to fabric span destination group 1 + cisco.aci.aci_fabric_span_dst_group: + <<: *change_ansible_group_1 + register: change_ansible_group_1_again + +- name: Verify change fabric span destination group 1 + ansible.builtin.assert: + that: + - change_ansible_group_1 is changed + - change_ansible_group_1.previous.0.spanDestGrp.attributes.name == "ansible_group_1" + - change_ansible_group_1.previous.0.spanDestGrp.attributes.descr == "test span epg" + - change_ansible_group_1.previous.0.spanDestGrp.children.0.spanDest.attributes.name == "ansible_group_1" + - change_ansible_group_1.previous.0.spanDestGrp.children.0.spanDest.children.0.spanRsDestEpg.attributes.dscp == "unspecified" + - change_ansible_group_1.previous.0.spanDestGrp.children.0.spanDest.children.0.spanRsDestEpg.attributes.flowId == "1" + - change_ansible_group_1.previous.0.spanDestGrp.children.0.spanDest.children.0.spanRsDestEpg.attributes.ip == "10.0.0.1" + - change_ansible_group_1.previous.0.spanDestGrp.children.0.spanDest.children.0.spanRsDestEpg.attributes.mtu == "1518" + - change_ansible_group_1.previous.0.spanDestGrp.children.0.spanDest.children.0.spanRsDestEpg.attributes.srcIpPrefix == "10.0.2.1" + - change_ansible_group_1.previous.0.spanDestGrp.children.0.spanDest.children.0.spanRsDestEpg.attributes.tDn == "uni/tn-ansible_test/ap-ansible_test/epg-ansible_test" + - change_ansible_group_1.previous.0.spanDestGrp.children.0.spanDest.children.0.spanRsDestEpg.attributes.ttl == "64" + - change_ansible_group_1.previous.0.spanDestGrp.children.0.spanDest.children.0.spanRsDestEpg.attributes.ver == "ver2" + - change_ansible_group_1.previous.0.spanDestGrp.children.0.spanDest.children.0.spanRsDestEpg.attributes.verEnforced == "no" + - change_ansible_group_1.current.0.spanDestGrp.attributes.name == "ansible_group_1" + - change_ansible_group_1.current.0.spanDestGrp.attributes.descr == "changed test span epg" + - change_ansible_group_1.current.0.spanDestGrp.children.0.spanDest.attributes.name == "ansible_group_1" + - change_ansible_group_1.current.0.spanDestGrp.children.0.spanDest.children.0.spanRsDestEpg.attributes.dscp == "VA" + - change_ansible_group_1.current.0.spanDestGrp.children.0.spanDest.children.0.spanRsDestEpg.attributes.flowId == "2" + - change_ansible_group_1.current.0.spanDestGrp.children.0.spanDest.children.0.spanRsDestEpg.attributes.ip == "10.0.0.2" + - change_ansible_group_1.current.0.spanDestGrp.children.0.spanDest.children.0.spanRsDestEpg.attributes.mtu == "1500" + - change_ansible_group_1.current.0.spanDestGrp.children.0.spanDest.children.0.spanRsDestEpg.attributes.srcIpPrefix == "10.0.2.2" + - change_ansible_group_1.current.0.spanDestGrp.children.0.spanDest.children.0.spanRsDestEpg.attributes.tDn == "uni/tn-ansible_test/ap-ansible_test/epg-ansible_test" + - change_ansible_group_1.current.0.spanDestGrp.children.0.spanDest.children.0.spanRsDestEpg.attributes.ttl == "4" + - change_ansible_group_1.current.0.spanDestGrp.children.0.spanDest.children.0.spanRsDestEpg.attributes.ver == "ver1" + - change_ansible_group_1.current.0.spanDestGrp.children.0.spanDest.children.0.spanRsDestEpg.attributes.verEnforced == "yes" + - change_ansible_group_1_again is not changed + - change_ansible_group_1_again.previous.0.spanDestGrp.attributes.name == "ansible_group_1" + - change_ansible_group_1_again.previous.0.spanDestGrp.attributes.descr == "changed test span epg" + - change_ansible_group_1_again.previous.0.spanDestGrp.children.0.spanDest.attributes.name == "ansible_group_1" + - change_ansible_group_1_again.previous.0.spanDestGrp.children.0.spanDest.children.0.spanRsDestEpg.attributes.dscp == "VA" + - change_ansible_group_1_again.previous.0.spanDestGrp.children.0.spanDest.children.0.spanRsDestEpg.attributes.flowId == "2" + - change_ansible_group_1_again.previous.0.spanDestGrp.children.0.spanDest.children.0.spanRsDestEpg.attributes.ip == "10.0.0.2" + - change_ansible_group_1_again.previous.0.spanDestGrp.children.0.spanDest.children.0.spanRsDestEpg.attributes.mtu == "1500" + - change_ansible_group_1_again.previous.0.spanDestGrp.children.0.spanDest.children.0.spanRsDestEpg.attributes.srcIpPrefix == "10.0.2.2" + - change_ansible_group_1_again.previous.0.spanDestGrp.children.0.spanDest.children.0.spanRsDestEpg.attributes.tDn == "uni/tn-ansible_test/ap-ansible_test/epg-ansible_test" + - change_ansible_group_1_again.previous.0.spanDestGrp.children.0.spanDest.children.0.spanRsDestEpg.attributes.ttl == "4" + - change_ansible_group_1_again.previous.0.spanDestGrp.children.0.spanDest.children.0.spanRsDestEpg.attributes.ver == "ver1" + - change_ansible_group_1_again.previous.0.spanDestGrp.children.0.spanDest.children.0.spanRsDestEpg.attributes.verEnforced == "yes" + - change_ansible_group_1_again.current.0.spanDestGrp.attributes.name == "ansible_group_1" + - change_ansible_group_1_again.current.0.spanDestGrp.attributes.descr == "changed test span epg" + - change_ansible_group_1_again.current.0.spanDestGrp.children.0.spanDest.attributes.name == "ansible_group_1" + - change_ansible_group_1_again.current.0.spanDestGrp.children.0.spanDest.children.0.spanRsDestEpg.attributes.dscp == "VA" + - change_ansible_group_1_again.current.0.spanDestGrp.children.0.spanDest.children.0.spanRsDestEpg.attributes.flowId == "2" + - change_ansible_group_1_again.current.0.spanDestGrp.children.0.spanDest.children.0.spanRsDestEpg.attributes.ip == "10.0.0.2" + - change_ansible_group_1_again.current.0.spanDestGrp.children.0.spanDest.children.0.spanRsDestEpg.attributes.mtu == "1500" + - change_ansible_group_1_again.current.0.spanDestGrp.children.0.spanDest.children.0.spanRsDestEpg.attributes.srcIpPrefix == "10.0.2.2" + - change_ansible_group_1_again.current.0.spanDestGrp.children.0.spanDest.children.0.spanRsDestEpg.attributes.tDn == "uni/tn-ansible_test/ap-ansible_test/epg-ansible_test" + - change_ansible_group_1_again.current.0.spanDestGrp.children.0.spanDest.children.0.spanRsDestEpg.attributes.ttl == "4" + - change_ansible_group_1_again.current.0.spanDestGrp.children.0.spanDest.children.0.spanRsDestEpg.attributes.ver == "ver1" + - change_ansible_group_1_again.current.0.spanDestGrp.children.0.spanDest.children.0.spanRsDestEpg.attributes.verEnforced == "yes" + +- name: Add another fabric span destination group 2 for query all + cisco.aci.aci_fabric_span_dst_group: + <<: *aci_info + destination_group: ansible_group_2 + description: test span epg 2 + destination_epg: + destination_ip: 10.0.0.3 + source_ip: 10.0.2.3 + tenant: ansible_test + ap: ansible_test + epg: ansible_test + state: present + +# TEST QUERY DESTINATION GROUPS + +- name: Query one fabric span destination group 1 + cisco.aci.aci_fabric_span_dst_group: + <<: *add_ansible_group_1 + state: query + register: query_one + +- name: Query all fabric span destination groups + cisco.aci.aci_fabric_span_dst_group: + <<: *aci_info + state: query + register: query_all + +- name: Verify querying fabric span destination groups + ansible.builtin.assert: + that: + - query_one is not changed + - query_one.current | length == 1 + - query_one.current.0.spanDestGrp.attributes.name == "ansible_group_1" + - query_all is not changed + - query_all.current | length >= 2 # greater or equal because there could be configuration present that is not fabric span but is returned in ( class based ) query all + +# TEST REMOVAL DESTINATION GROUPS + +- name: Remove fabric span destination group 1 ( check mode ) + cisco.aci.aci_fabric_span_dst_group: &remove_ansible_group_1 + <<: *add_ansible_group_1 + state: absent + check_mode: true + register: cm_remove_ansible_group_1 + +- name: Remove fabric span destination group 1 + cisco.aci.aci_fabric_span_dst_group: + <<: *remove_ansible_group_1 + register: nm_remove_ansible_group_1 + +- name: Remove fabric span destination group 1 again + cisco.aci.aci_fabric_span_dst_group: + <<: *remove_ansible_group_1 + register: nm_remove_ansible_group_1_again + +- name: Verify remove fabric span destination group 1 + ansible.builtin.assert: + that: + - cm_remove_ansible_group_1 is changed + - cm_remove_ansible_group_1.current | length == 1 + - cm_remove_ansible_group_1.previous | length == 1 + - cm_remove_ansible_group_1.proposed == {} + - nm_remove_ansible_group_1 is changed + - nm_remove_ansible_group_1.current == [] + - nm_remove_ansible_group_1.previous | length == 1 + - nm_remove_ansible_group_1_again is not changed + - nm_remove_ansible_group_1_again.current == [] + - nm_remove_ansible_group_1_again.previous == [] + +# CLEAN TEST ENVIRONMENT AFTER TESTS + +- name: Query all fabric span destination groups ( clean after ) + cisco.aci.aci_fabric_span_dst_group: + <<: *aci_info + state: query + register: query_for_clean + +- name: Clean fabric span destination groups ( clean after ) + cisco.aci.aci_fabric_span_dst_group: + <<: *aci_info + destination_group: "{{ item.spanDestGrp.attributes.name }}" + state: absent + loop: "{{ query_for_clean.current }}" diff --git a/ansible_collections/cisco/aci/tests/integration/targets/aci_fabric_span_src_group/aliases b/ansible_collections/cisco/aci/tests/integration/targets/aci_fabric_span_src_group/aliases new file mode 100644 index 000000000..209b793f9 --- /dev/null +++ b/ansible_collections/cisco/aci/tests/integration/targets/aci_fabric_span_src_group/aliases @@ -0,0 +1,2 @@ +# No ACI simulator yet, so not enabled +# unsupported diff --git a/ansible_collections/cisco/aci/tests/integration/targets/aci_fabric_span_src_group/tasks/main.yml b/ansible_collections/cisco/aci/tests/integration/targets/aci_fabric_span_src_group/tasks/main.yml new file mode 100644 index 000000000..c3e4eb55c --- /dev/null +++ b/ansible_collections/cisco/aci/tests/integration/targets/aci_fabric_span_src_group/tasks/main.yml @@ -0,0 +1,245 @@ +# Test code for the ACI modules +# Copyright: (c) 2023, Akini Ross (@akinross) + +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +- name: Test that we have an ACI APIC host, ACI username and ACI password + fail: + msg: 'Please define the following variables: aci_hostname, aci_username and aci_password.' + when: aci_hostname is not defined or aci_username is not defined or aci_password is not defined + +- name: Set vars + set_fact: + aci_info: &aci_info + host: "{{ aci_hostname }}" + username: "{{ aci_username }}" + password: "{{ aci_password }}" + validate_certs: '{{ aci_validate_certs | default(false) }}' + use_ssl: '{{ aci_use_ssl | default(true) }}' + use_proxy: '{{ aci_use_proxy | default(true) }}' + output_level: '{{ aci_output_level | default("info") }}' + +# CLEAN TEST ENVIRONMENT BEFORE TESTS + +- name: Query all fabric span source groups ( clean before ) + cisco.aci.aci_fabric_span_src_group: + <<: *aci_info + state: query + register: query_for_clean + +- name: Clean fabric span source groups ( clean before ) + cisco.aci.aci_fabric_span_src_group: + <<: *aci_info + source_group: "{{ item.spanSrcGrp.attributes.name }}" + state: absent + loop: "{{ query_for_clean.current }}" + +- name: Query all fabric span destination groups ( clean before ) + cisco.aci.aci_fabric_span_dst_group: + <<: *aci_info + state: query + register: query_for_clean + +- name: Clean fabric span destination groups ( clean before ) + cisco.aci.aci_fabric_span_dst_group: + <<: *aci_info + destination_group: "{{ item.spanDestGrp.attributes.name }}" + state: absent + loop: "{{ query_for_clean.current }}" + +- name: Add fabric span destination group 1 ( clean before ) + cisco.aci.aci_fabric_span_dst_group: + <<: *aci_info + destination_group: span_dest_1 + description: test span epg + destination_epg: + destination_ip: 10.0.0.1 + source_ip: 10.0.2.1 + tenant: ansible_test + ap: ansible_test + epg: ansible_test + state: present + +# TEST CREATE AND UPDATE SOURCE GROUPS + +- name: Add fabric span source group 1 ( checkmode ) + cisco.aci.aci_fabric_span_src_group: &span_src_1 + <<: *aci_info + source_group: span_src_1 + destination_group: span_dest_1 + state: present + check_mode: true + register: cm_span_src_1 + +- name: Add fabric span source group 1 + cisco.aci.aci_fabric_span_src_group: + <<: *span_src_1 + register: nm_span_src_1 + +- name: Add fabric span source group 1 again + cisco.aci.aci_fabric_span_src_group: + <<: *span_src_1 + register: nm_span_src_1_again + +- name: Verify add fabric span source group 1 + ansible.builtin.assert: + that: + - cm_span_src_1 is changed + - cm_span_src_1.previous == [] + - cm_span_src_1.current == [] + - cm_span_src_1.proposed.spanSrcGrp.attributes.name == "span_src_1" + - cm_span_src_1.proposed.spanSrcGrp.children.0.spanSpanLbl.attributes.name == "span_dest_1" + - nm_span_src_1 is changed + - nm_span_src_1.previous == [] + - nm_span_src_1.current.0.spanSrcGrp.attributes.name == "span_src_1" + - nm_span_src_1.current.0.spanSrcGrp.attributes.descr == "" + - nm_span_src_1.current.0.spanSrcGrp.attributes.adminSt == "enabled" + - nm_span_src_1.current.0.spanSrcGrp.children.0.spanSpanLbl.attributes.name == "span_dest_1" + - nm_span_src_1_again is not changed + - nm_span_src_1_again.previous.0.spanSrcGrp.attributes.name == "span_src_1" + - nm_span_src_1_again.previous.0.spanSrcGrp.attributes.descr == "" + - nm_span_src_1_again.previous.0.spanSrcGrp.attributes.adminSt == "enabled" + - nm_span_src_1_again.previous.0.spanSrcGrp.children.0.spanSpanLbl.attributes.name == "span_dest_1" + - nm_span_src_1_again.current.0.spanSrcGrp.attributes.name == "span_src_1" + - nm_span_src_1_again.current.0.spanSrcGrp.attributes.descr == "" + - nm_span_src_1_again.current.0.spanSrcGrp.attributes.adminSt == "enabled" + - nm_span_src_1_again.current.0.spanSrcGrp.children.0.spanSpanLbl.attributes.name == "span_dest_1" + +- name: Change fabric span source group 1 + cisco.aci.aci_fabric_span_src_group: &change_span_src_1 + <<: *span_src_1 + destination_group: span_dest_2 + description: "changed description" + admin_state: false + register: nm_changed_span_src_1 + +- name: Change fabric span source group 1 again + cisco.aci.aci_fabric_span_src_group: + <<: *change_span_src_1 + register: nm_changed_span_src_1_again + +- name: Verify changed fabric span source group 1 + ansible.builtin.assert: + that: + - nm_changed_span_src_1 is changed + - nm_changed_span_src_1.previous.0.spanSrcGrp.attributes.descr == "" + - nm_changed_span_src_1.previous.0.spanSrcGrp.attributes.adminSt == "enabled" + - nm_changed_span_src_1.previous.0.spanSrcGrp.children.0.spanSpanLbl.attributes.name == "span_dest_1" + - nm_changed_span_src_1.current.0.spanSrcGrp.attributes.descr == "changed description" + - nm_changed_span_src_1.current.0.spanSrcGrp.attributes.adminSt == "disabled" + - nm_changed_span_src_1.current.0.spanSrcGrp.children.0.spanSpanLbl.attributes.name == "span_dest_2" + - nm_changed_span_src_1_again is not changed + - nm_changed_span_src_1_again.previous.0.spanSrcGrp.attributes.descr == "changed description" + - nm_changed_span_src_1_again.previous.0.spanSrcGrp.attributes.adminSt == "disabled" + - nm_changed_span_src_1_again.previous.0.spanSrcGrp.children.0.spanSpanLbl.attributes.name == "span_dest_2" + - nm_changed_span_src_1_again.current.0.spanSrcGrp.attributes.descr == "changed description" + - nm_changed_span_src_1_again.current.0.spanSrcGrp.attributes.adminSt == "disabled" + - nm_changed_span_src_1_again.current.0.spanSrcGrp.children.0.spanSpanLbl.attributes.name == "span_dest_2" + +- name: Add fabric span source group 2 with admin_state to false + cisco.aci.aci_fabric_span_src_group: + <<: *change_span_src_1 + source_group: span_src_2 + destination_group: span_dest_2 + admin_state: false + register: nm_changed_span_src_2_admin_state_false + +- name: Add fabric span source group 3 with admin_state to true + cisco.aci.aci_fabric_span_src_group: + <<: *change_span_src_1 + source_group: span_src_3 + destination_group: span_dest_3 + admin_state: true + register: nm_changed_span_src_3_admin_state_true + +- name: Verify admin_state sets for fabric span source group 2 and 3 + ansible.builtin.assert: + that: + - nm_changed_span_src_2_admin_state_false is changed + - nm_changed_span_src_2_admin_state_false.current.0.spanSrcGrp.attributes.adminSt == "disabled" + - nm_changed_span_src_3_admin_state_true is changed + - nm_changed_span_src_3_admin_state_true.current.0.spanSrcGrp.attributes.adminSt == "enabled" + +# TEST QUERY SOURCE GROUPS + +- name: Query fabric span source group span_src_1 + cisco.aci.aci_fabric_span_src_group: + <<: *change_span_src_1 + state: query + register: query_one + +- name: Query all span source groups + cisco.aci.aci_fabric_span_src_group: + <<: *aci_info + state: query + register: query_all + +- name: Verify querying fabric span destination groups + ansible.builtin.assert: + that: + - query_one is not changed + - query_one.current | length == 1 + - query_one.current.0.spanSrcGrp.attributes.name == "span_src_1" + - query_all is not changed + - query_all.current | length >= 3 + +# TEST REMOVAL SOURCE GROUPS + +- name: Remove fabric span source group ( checkmode ) + cisco.aci.aci_fabric_span_src_group: &remove_span_src_1 + <<: *change_span_src_1 + state: absent + check_mode: true + register: cm_remove_span_src_1 + +- name: Remove fabric span source group 1 + cisco.aci.aci_fabric_span_src_group: + <<: *remove_span_src_1 + register: nm_remove_span_src_1 + +- name: Remove fabric span source group 1 again + cisco.aci.aci_fabric_span_src_group: + <<: *remove_span_src_1 + register: nm_remove_span_src_1_again + +- name: Verify remove fabric span source group 1 + ansible.builtin.assert: + that: + - cm_remove_span_src_1 is changed + - cm_remove_span_src_1.current | length == 1 + - cm_remove_span_src_1.previous | length == 1 + - cm_remove_span_src_1.proposed == {} + - nm_remove_span_src_1 is changed + - nm_remove_span_src_1.current == [] + - nm_remove_span_src_1.previous | length == 1 + - nm_remove_span_src_1_again is not changed + - nm_remove_span_src_1_again.current == [] + - nm_remove_span_src_1_again.previous == [] + +# CLEAN TEST ENVIRONMENT AFTER TESTS + +- name: Query all fabric span source groups ( clean after ) + cisco.aci.aci_fabric_span_src_group: + <<: *aci_info + state: query + register: query_for_clean + +- name: Clean fabric span source groups ( clean after ) + cisco.aci.aci_fabric_span_src_group: + <<: *aci_info + source_group: "{{ item.spanSrcGrp.attributes.name }}" + state: absent + loop: "{{ query_for_clean.current }}" + +- name: Query all fabric span destination groups ( clean after ) + cisco.aci.aci_fabric_span_dst_group: + <<: *aci_info + state: query + register: query_for_clean + +- name: Clean fabric span destination groups ( clean after ) + cisco.aci.aci_fabric_span_dst_group: + <<: *aci_info + destination_group: "{{ item.spanDestGrp.attributes.name }}" + state: absent + loop: "{{ query_for_clean.current }}" diff --git a/ansible_collections/cisco/aci/tests/integration/targets/aci_fabric_span_src_group_src/aliases b/ansible_collections/cisco/aci/tests/integration/targets/aci_fabric_span_src_group_src/aliases new file mode 100644 index 000000000..209b793f9 --- /dev/null +++ b/ansible_collections/cisco/aci/tests/integration/targets/aci_fabric_span_src_group_src/aliases @@ -0,0 +1,2 @@ +# No ACI simulator yet, so not enabled +# unsupported diff --git a/ansible_collections/cisco/aci/tests/integration/targets/aci_fabric_span_src_group_src/tasks/main.yml b/ansible_collections/cisco/aci/tests/integration/targets/aci_fabric_span_src_group_src/tasks/main.yml new file mode 100644 index 000000000..6767e3e89 --- /dev/null +++ b/ansible_collections/cisco/aci/tests/integration/targets/aci_fabric_span_src_group_src/tasks/main.yml @@ -0,0 +1,396 @@ +# Test code for the ACI modules +# Copyright: (c) 2023, Akini Ross (@akinross) + +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +- name: Test that we have an ACI APIC host, ACI username and ACI password + fail: + msg: 'Please define the following variables: aci_hostname, aci_username and aci_password.' + when: aci_hostname is not defined or aci_username is not defined or aci_password is not defined + +- name: Set vars + set_fact: + aci_info: &aci_info + host: "{{ aci_hostname }}" + username: "{{ aci_username }}" + password: "{{ aci_password }}" + validate_certs: '{{ aci_validate_certs | default(false) }}' + use_ssl: '{{ aci_use_ssl | default(true) }}' + use_proxy: '{{ aci_use_proxy | default(true) }}' + output_level: '{{ aci_output_level | default("info") }}' + +# CLEAN TEST ENVIRONMENT BEFORE TESTS + +- name: Query all fabric span source groups ( clean before ) + cisco.aci.aci_fabric_span_src_group: + <<: *aci_info + state: query + register: query_for_clean + +- name: Clean fabric span source groups ( clean before ) + cisco.aci.aci_fabric_span_src_group: + <<: *aci_info + source_group: "{{ item.spanSrcGrp.attributes.name }}" + state: absent + loop: "{{ query_for_clean.current }}" + +- name: Query all fabric span destination groups ( clean before ) + cisco.aci.aci_fabric_span_dst_group: + <<: *aci_info + state: query + register: query_for_clean + +- name: Clean fabric span destination groups ( clean before ) + cisco.aci.aci_fabric_span_dst_group: + <<: *aci_info + destination_group: "{{ item.spanDestGrp.attributes.name }}" + state: absent + loop: "{{ query_for_clean.current }}" + +- name: Add fabric span destination group type epg ( clean before ) + cisco.aci.aci_fabric_span_dst_group: + <<: *aci_info + destination_group: span_dest_1 + description: test span epg + destination_epg: + destination_ip: 10.0.0.1 + source_ip: 10.0.2.1 + tenant: ansible_test + ap: ansible_test + epg: ansible_test + state: present + +- name: Add fabric span source groups ( clean before ) + cisco.aci.aci_fabric_span_src_group: + <<: *aci_info + source_group: "{{ item }}" + destination_group: span_dest_1 + state: present + loop: + - span_src_group_1 + - span_src_group_2 + +# TEST CREATE AND UPDATE SOURCE + +- name: Add fabric span source 1 ( checkmode ) + cisco.aci.aci_fabric_span_src_group_src: &fabric_span_src_1 + <<: *aci_info + source_group: span_src_group_1 + source: fabric_span_src_1 + state: present + check_mode: true + register: cm_fabric_span_src_1 + +- name: Add fabric span source 1 + cisco.aci.aci_fabric_span_src_group_src: + <<: *fabric_span_src_1 + register: nm_fabric_span_src_1 + +- name: Add fabric span source 1 again + cisco.aci.aci_fabric_span_src_group_src: + <<: *fabric_span_src_1 + register: nm_fabric_span_src_1_again + +- name: Verify add fabric span source 1 + ansible.builtin.assert: + that: + - cm_fabric_span_src_1 is changed + - cm_fabric_span_src_1.previous == [] + - cm_fabric_span_src_1.current == [] + - cm_fabric_span_src_1.proposed.spanSrc.attributes.name == "fabric_span_src_1" + - cm_fabric_span_src_1.proposed.spanSrc.attributes.dn == "uni/fabric/srcgrp-span_src_group_1/src-fabric_span_src_1" + - nm_fabric_span_src_1 is changed + - nm_fabric_span_src_1.previous == [] + - nm_fabric_span_src_1.current.0.spanSrc.attributes.name == "fabric_span_src_1" + - nm_fabric_span_src_1.current.0.spanSrc.attributes.descr == "" + - nm_fabric_span_src_1.current.0.spanSrc.attributes.dir == "both" + - nm_fabric_span_src_1.current.0.spanSrc.attributes.dn == "uni/fabric/srcgrp-span_src_group_1/src-fabric_span_src_1" + - nm_fabric_span_src_1_again is not changed + - nm_fabric_span_src_1_again.previous.0.spanSrc.attributes.name == "fabric_span_src_1" + - nm_fabric_span_src_1_again.previous.0.spanSrc.attributes.descr == "" + - nm_fabric_span_src_1_again.previous.0.spanSrc.attributes.dir == "both" + - nm_fabric_span_src_1_again.previous.0.spanSrc.attributes.dn == "uni/fabric/srcgrp-span_src_group_1/src-fabric_span_src_1" + - nm_fabric_span_src_1_again.current.0.spanSrc.attributes.name == "fabric_span_src_1" + - nm_fabric_span_src_1_again.current.0.spanSrc.attributes.descr == "" + - nm_fabric_span_src_1_again.current.0.spanSrc.attributes.dir == "both" + - nm_fabric_span_src_1_again.current.0.spanSrc.attributes.dn == "uni/fabric/srcgrp-span_src_group_1/src-fabric_span_src_1" + +- name: Change fabric span source 1 + cisco.aci.aci_fabric_span_src_group_src: &change_fabric_span_src_1 + <<: *fabric_span_src_1 + source: fabric_span_src_1 + description: "changed description" + direction: both + register: nm_changed_fabric_span_src_1 + +- name: Change fabric span source 1 direction incoming + cisco.aci.aci_fabric_span_src_group_src: + <<: *change_fabric_span_src_1 + direction: incoming + register: nm_changed_fabric_span_src_1_incoming + +- name: Change fabric span source 1 direction outgoing + cisco.aci.aci_fabric_span_src_group_src: &change_fabric_span_src_1_again + <<: *change_fabric_span_src_1 + direction: outgoing + register: nm_changed_fabric_span_src_1_outgoing + +- name: Change fabric span source 1 again + cisco.aci.aci_fabric_span_src_group_src: + <<: *change_fabric_span_src_1_again + register: nm_changed_fabric_span_src_1_again + +- name: Verify changed fabric span source 1 + ansible.builtin.assert: + that: + - nm_changed_fabric_span_src_1 is changed + - nm_changed_fabric_span_src_1.previous.0.spanSrc.attributes.name == "fabric_span_src_1" + - nm_changed_fabric_span_src_1.previous.0.spanSrc.attributes.descr == "" + - nm_changed_fabric_span_src_1.previous.0.spanSrc.attributes.dir == "both" + - nm_changed_fabric_span_src_1.previous.0.spanSrc.attributes.dn == "uni/fabric/srcgrp-span_src_group_1/src-fabric_span_src_1" + - nm_changed_fabric_span_src_1.current.0.spanSrc.attributes.name == "fabric_span_src_1" + - nm_changed_fabric_span_src_1.current.0.spanSrc.attributes.descr == "changed description" + - nm_changed_fabric_span_src_1.current.0.spanSrc.attributes.dir == "both" + - nm_changed_fabric_span_src_1.current.0.spanSrc.attributes.dn == "uni/fabric/srcgrp-span_src_group_1/src-fabric_span_src_1" + - nm_changed_fabric_span_src_1_incoming.current.0.spanSrc.attributes.dir == "in" + - nm_changed_fabric_span_src_1_outgoing.current.0.spanSrc.attributes.dir == "out" + - nm_changed_fabric_span_src_1_again is not changed + +- name: Add fabric span source 2 of bd type + cisco.aci.aci_fabric_span_src_group_src: &span_src_bd + <<: *aci_info + source_group: span_src_group_1 + bd: + tenant: tenant1 + bd: bd1 + source: fabric_span_src_2 + state: present + register: nm_fabric_span_src_2 + +- name: Change fabric span source 2 of bd type + cisco.aci.aci_fabric_span_src_group_src: + <<: *span_src_bd + bd: + tenant: tenant1 + bd: bd2 + register: nm_changed_fabric_span_src_2 + +- name: Change fabric span source 2 of bd type to none type + cisco.aci.aci_fabric_span_src_group_src: + <<: *aci_info + source_group: span_src_group_1 + source: fabric_span_src_2 + state: present + register: nm_changed_fabric_span_src_2_type_none + +- name: Add fabric span source 3 of vrf type + cisco.aci.aci_fabric_span_src_group_src: &span_src_vrf + <<: *aci_info + source_group: span_src_group_1 + vrf: + tenant: tenant1 + vrf: vrf1 + source: fabric_span_src_3 + state: present + register: nm_fabric_span_src_3 + +- name: Change fabric span source 3 of vrf type + cisco.aci.aci_fabric_span_src_group_src: + <<: *span_src_vrf + vrf: + tenant: tenant1 + vrf: vrf2 + register: nm_changed_fabric_span_src_3 + +- name: Change fabric span source 3 of vrf type + cisco.aci.aci_fabric_span_src_group_src: + <<: *aci_info + source_group: span_src_group_1 + source: fabric_span_src_3 + state: present + register: nm_changed_fabric_span_src_3_type_none + +- name: Verify changed fabric span source types group 2 and 3 + ansible.builtin.assert: + that: + - nm_fabric_span_src_2 is changed + - nm_fabric_span_src_2.current.0.spanSrc.attributes.name == "fabric_span_src_2" + - nm_fabric_span_src_2.current.0.spanSrc.children.0.spanRsSrcToBD.attributes.tDn == "uni/tn-tenant1/BD-bd1" + - nm_changed_fabric_span_src_2 is changed + - nm_changed_fabric_span_src_2.current.0.spanSrc.attributes.name == "fabric_span_src_2" + - nm_changed_fabric_span_src_2.previous.0.spanSrc.children.0.spanRsSrcToBD.attributes.tDn == "uni/tn-tenant1/BD-bd1" + - nm_changed_fabric_span_src_2.current.0.spanSrc.children.0.spanRsSrcToBD.attributes.tDn == "uni/tn-tenant1/BD-bd2" + - nm_changed_fabric_span_src_2_type_none is changed + - nm_changed_fabric_span_src_2_type_none.previous.0.spanSrc.children.0.spanRsSrcToBD.attributes.tDn == "uni/tn-tenant1/BD-bd2" + - '"children" not in nm_changed_fabric_span_src_2_type_none.current.0.spanSrc' + - nm_fabric_span_src_3 is changed + - nm_fabric_span_src_3.current.0.spanSrc.attributes.name == "fabric_span_src_3" + - nm_fabric_span_src_3.current.0.spanSrc.children.0.spanRsSrcToCtx.attributes.tDn == "uni/tn-tenant1/ctx-vrf1" + - nm_changed_fabric_span_src_3 is changed + - nm_changed_fabric_span_src_3.current.0.spanSrc.attributes.name == "fabric_span_src_3" + - nm_changed_fabric_span_src_3.previous.0.spanSrc.children.0.spanRsSrcToCtx.attributes.tDn == "uni/tn-tenant1/ctx-vrf1" + - nm_changed_fabric_span_src_3.current.0.spanSrc.children.0.spanRsSrcToCtx.attributes.tDn == "uni/tn-tenant1/ctx-vrf2" + - nm_changed_fabric_span_src_3_type_none is changed + - nm_changed_fabric_span_src_3_type_none.previous.0.spanSrc.children.0.spanRsSrcToCtx.attributes.tDn == "uni/tn-tenant1/ctx-vrf2" + - '"children" not in nm_changed_fabric_span_src_3_type_none.current.0.spanSrc' + +- name: Change fabric span source 4 drop_packets true + cisco.aci.aci_fabric_span_src_group_src: + <<: *aci_info + source_group: span_src_group_2 + source: span_src_4 + drop_packets: true + state: present + register: nm_changed_span_src_4_type_drop_packets_true + +- name: Change fabric span source 4 drop_packets false + cisco.aci.aci_fabric_span_src_group_src: + <<: *aci_info + source_group: span_src_group_2 + source: span_src_4 + drop_packets: false + state: present + register: nm_changed_span_src_4_type_drop_packets_false + +- name: Verify changed fabric span source group 1 drop packets + ansible.builtin.assert: + that: + - nm_changed_span_src_4_type_drop_packets_true is changed + - nm_changed_span_src_4_type_drop_packets_true.current.0.spanSrc.attributes.name == "span_src_4" + - nm_changed_span_src_4_type_drop_packets_true.current.0.spanSrc.attributes.spanOnDrop == "yes" + - nm_changed_span_src_4_type_drop_packets_false is changed + - nm_changed_span_src_4_type_drop_packets_false.current.0.spanSrc.attributes.name == "span_src_4" + - nm_changed_span_src_4_type_drop_packets_false.current.0.spanSrc.attributes.spanOnDrop == "no" + +# TEST QUERY SOURCE + +- name: Query fabric span source fabric_span_src_1 + cisco.aci.aci_fabric_span_src_group_src: + <<: *aci_info + source: fabric_span_src_1 + state: query + register: query_one + +- name: Query fabric all span sources + cisco.aci.aci_fabric_span_src_group_src: + <<: *aci_info + state: query + register: query_all + +- name: Verify querying fabric span sources + ansible.builtin.assert: + that: + - query_one is not changed + - query_one.current | length == 1 + - query_one.current.0.spanSrc.attributes.dn == "uni/fabric/srcgrp-span_src_group_1/src-fabric_span_src_1" + - query_all is not changed + - query_all.current | length >= 3 + +# TEST ERRORS SOURCE + +- name: Add fabric span source 4 with bd and vrf ( mutually exclusive error ) + cisco.aci.aci_fabric_span_src_group_src: + <<: *aci_info + source_group: span_src_group_4 + bd: + tenant: tenant1 + bd: bd1 + vrf: + tenant: tenant1 + vrf: vrf1 + source: span_src_4 + state: present + ignore_errors: true + register: err_mutually_exclusive_bd_vrf + +- name: Add fabric span source 4 with bd and drop_packets( drop_packets true error ) + cisco.aci.aci_fabric_span_src_group_src: + <<: *aci_info + source_group: span_src_group_4 + source: span_src_4 + bd: + tenant: tenant1 + bd: bd1 + drop_packets: true + ignore_errors: true + register: err_bd_drop_packets + +- name: Add fabric span source 4 with vrf and drop_packets ( drop_packets true error ) + cisco.aci.aci_fabric_span_src_group_src: + <<: *aci_info + source_group: span_src_group_4 + source: span_src_4 + vrf: + tenant: tenant1 + vrf: vrf1 + drop_packets: true + ignore_errors: true + register: err_vrf_drop_packets + +- name: Verify errors on incorrect input + ansible.builtin.assert: + that: + - err_mutually_exclusive_bd_vrf.msg == "parameters are mutually exclusive{{':'}} vrf|bd" + - err_bd_drop_packets.msg == "It is not allowed to configure 'drop_packets{{':'}} true' when 'bd' is configured on the source." + - err_vrf_drop_packets.msg == "It is not allowed to configure 'drop_packets{{':'}} true' when 'vrf' is configured on the source." + +# TEST REMOVAL SOURCE + +- name: Remove fabric span source 1 ( checkmode ) + cisco.aci.aci_fabric_span_src_group_src: &remove_fabric_span_src_1 + <<: *change_fabric_span_src_1 + state: absent + check_mode: true + register: cm_remove_fabric_span_src_1 + +- name: Remove fabric span source 1 + cisco.aci.aci_fabric_span_src_group_src: + <<: *remove_fabric_span_src_1 + register: nm_remove_fabric_span_src_1 + +- name: Remove fabric span source 1 again + cisco.aci.aci_fabric_span_src_group_src: + <<: *remove_fabric_span_src_1 + register: nm_remove_fabric_span_src_1_again + +- name: Verify remove fabric span source 1 + ansible.builtin.assert: + that: + - cm_remove_fabric_span_src_1 is changed + - cm_remove_fabric_span_src_1.current | length == 1 + - cm_remove_fabric_span_src_1.previous | length == 1 + - cm_remove_fabric_span_src_1.proposed == {} + - nm_remove_fabric_span_src_1 is changed + - nm_remove_fabric_span_src_1.current == [] + - nm_remove_fabric_span_src_1.previous | length == 1 + - nm_remove_fabric_span_src_1_again is not changed + - nm_remove_fabric_span_src_1_again.current == [] + - nm_remove_fabric_span_src_1_again.previous == [] + +# CLEAN TEST ENVIRONMENT AFTER TESTS + +- name: Query all fabric span source groups ( clean after ) + cisco.aci.aci_fabric_span_src_group: + <<: *aci_info + state: query + register: query_for_clean + +- name: Clean fabric span source groups ( clean after ) + cisco.aci.aci_fabric_span_src_group: + <<: *aci_info + source_group: "{{ item.spanSrcGrp.attributes.name }}" + state: absent + loop: "{{ query_for_clean.current }}" + +- name: Query all fabric span destination groups ( clean after ) + cisco.aci.aci_fabric_span_dst_group: + <<: *aci_info + state: query + register: query_for_clean + +- name: Clean fabric span destination groups ( clean after ) + cisco.aci.aci_fabric_span_dst_group: + <<: *aci_info + destination_group: "{{ item.spanDestGrp.attributes.name }}" + state: absent + loop: "{{ query_for_clean.current }}" diff --git a/ansible_collections/cisco/aci/tests/integration/targets/aci_fabric_span_src_group_src_node/aliases b/ansible_collections/cisco/aci/tests/integration/targets/aci_fabric_span_src_group_src_node/aliases new file mode 100644 index 000000000..209b793f9 --- /dev/null +++ b/ansible_collections/cisco/aci/tests/integration/targets/aci_fabric_span_src_group_src_node/aliases @@ -0,0 +1,2 @@ +# No ACI simulator yet, so not enabled +# unsupported diff --git a/ansible_collections/cisco/aci/tests/integration/targets/aci_fabric_span_src_group_src_node/tasks/main.yml b/ansible_collections/cisco/aci/tests/integration/targets/aci_fabric_span_src_group_src_node/tasks/main.yml new file mode 100644 index 000000000..0e5ea30b2 --- /dev/null +++ b/ansible_collections/cisco/aci/tests/integration/targets/aci_fabric_span_src_group_src_node/tasks/main.yml @@ -0,0 +1,238 @@ +# Test code for the ACI modules +# Copyright: (c) 2023, Akini Ross (@akinross) + +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +- name: Test that we have an ACI APIC host, ACI username and ACI password + fail: + msg: 'Please define the following variables: aci_hostname, aci_username and aci_password.' + when: aci_hostname is not defined or aci_username is not defined or aci_password is not defined + +- name: Set vars + set_fact: + aci_info: &aci_info + host: "{{ aci_hostname }}" + username: "{{ aci_username }}" + password: "{{ aci_password }}" + validate_certs: '{{ aci_validate_certs | default(false) }}' + use_ssl: '{{ aci_use_ssl | default(true) }}' + use_proxy: '{{ aci_use_proxy | default(true) }}' + output_level: '{{ aci_output_level | default("info") }}' + +# CLEAN TEST ENVIRONMENT BEFORE TESTS + +- name: Query all fabric span source groups ( clean before ) + cisco.aci.aci_fabric_span_src_group: + <<: *aci_info + state: query + register: query_for_clean + +- name: Clean fabric span source groups ( clean before ) + cisco.aci.aci_fabric_span_src_group: + <<: *aci_info + source_group: "{{ item.spanSrcGrp.attributes.name }}" + state: absent + loop: "{{ query_for_clean.current }}" + +- name: Query all fabric span destination groups ( clean before ) + cisco.aci.aci_fabric_span_dst_group: + <<: *aci_info + state: query + register: query_for_clean + +- name: Clean fabric span destination groups ( clean before ) + cisco.aci.aci_fabric_span_dst_group: + <<: *aci_info + destination_group: "{{ item.spanDestGrp.attributes.name }}" + state: absent + loop: "{{ query_for_clean.current }}" + +- name: Add fabric span destination group type epg ( clean before ) + cisco.aci.aci_fabric_span_dst_group: + <<: *aci_info + destination_group: span_dest_1 + description: test span epg + destination_epg: + destination_ip: 10.0.0.1 + source_ip: 10.0.2.1 + tenant: ansible_test + ap: ansible_test + epg: ansible_test + state: present + +- name: Add fabric span source group 1 ( clean before ) + cisco.aci.aci_fabric_span_src_group: + <<: *aci_info + source_group: span_src_group_1 + destination_group: span_dest_1 + state: present + +- name: Add fabric span source group source 1 ( clean before ) + cisco.aci.aci_fabric_span_src_group_src: + <<: *aci_info + source_group: span_src_group_1 + source: span_src_1 + drop_packets: true + state: present + +- name: Add fabric span source group source 2 for error handling ( clean before ) + cisco.aci.aci_fabric_span_src_group_src: + <<: *aci_info + source_group: span_src_group_1 + source: span_src_2 + drop_packets: false + state: present + + +# TEST CREATE SOURCE NODES + +- name: Add fabric span source node 1 ( checkmode ) + cisco.aci.aci_fabric_span_src_group_src_node: &span_src_node_1 + <<: *aci_info + source_group: span_src_group_1 + source: span_src_1 + pod: 1 + node: 101 + state: present + check_mode: true + register: cm_span_src_node_1 + +- name: Add fabric span source node 1 + cisco.aci.aci_fabric_span_src_group_src_node: + <<: *span_src_node_1 + register: nm_span_src_node_1 + +- name: Add fabric span source node 1 again + cisco.aci.aci_fabric_span_src_group_src_node: + <<: *span_src_node_1 + register: nm_span_src_node_1_again + +- name: Verify add fabric span source node 1 + ansible.builtin.assert: + that: + - cm_span_src_node_1 is changed + - cm_span_src_node_1.previous == [] + - cm_span_src_node_1.current == [] + - cm_span_src_node_1.proposed.spanRsSrcToNode.attributes.dn == "uni/fabric/srcgrp-span_src_group_1/src-span_src_1/rssrcToNode-[topology/pod-1/node-101]" + - cm_span_src_node_1.proposed.spanRsSrcToNode.attributes.tDn == "topology/pod-1/node-101" + - nm_span_src_node_1 is changed + - nm_span_src_node_1.current.0.spanRsSrcToNode.attributes.dn == "uni/fabric/srcgrp-span_src_group_1/src-span_src_1/rssrcToNode-[topology/pod-1/node-101]" + - nm_span_src_node_1.current.0.spanRsSrcToNode.attributes.tDn == "topology/pod-1/node-101" + - nm_span_src_node_1.previous == [] + - nm_span_src_node_1_again is not changed + - nm_span_src_node_1_again.previous.0.spanRsSrcToNode.attributes.dn == "uni/fabric/srcgrp-span_src_group_1/src-span_src_1/rssrcToNode-[topology/pod-1/node-101]" + - nm_span_src_node_1_again.previous.0.spanRsSrcToNode.attributes.tDn == "topology/pod-1/node-101" + - nm_span_src_node_1_again.current.0.spanRsSrcToNode.attributes.dn == "uni/fabric/srcgrp-span_src_group_1/src-span_src_1/rssrcToNode-[topology/pod-1/node-101]" + - nm_span_src_node_1_again.current.0.spanRsSrcToNode.attributes.tDn == "topology/pod-1/node-101" + +- name: Add fabric span source node 2 + cisco.aci.aci_fabric_span_src_group_src_node: + <<: *aci_info + source_group: span_src_group_1 + source: span_src_1 + pod: 1 + node: 102 + state: present + +# TEST QUERY SOURCE NODES + +- name: Query fabric span source node 1 + cisco.aci.aci_fabric_span_src_group_src_node: + <<: *span_src_node_1 + state: query + register: query_one + +- name: Query all fabric span source nodes + cisco.aci.aci_fabric_span_src_group_src_node: + <<: *aci_info + state: query + register: query_all + +- name: Verify querying fabric span sources + ansible.builtin.assert: + that: + - query_one is not changed + - query_one.current | length == 1 + - query_one.current.0.spanRsSrcToNode.attributes.dn == "uni/fabric/srcgrp-span_src_group_1/src-span_src_1/rssrcToNode-[topology/pod-1/node-101]" + - query_one.current.0.spanRsSrcToNode.attributes.tDn == "topology/pod-1/node-101" + - query_all is not changed + - query_all.current | length >= 2 + +# TEST REMOVAL SOURCE NODES + +- name: Remove fabric span source node 1 ( checkmode ) + cisco.aci.aci_fabric_span_src_group_src_node: &remove_span_src_node_1 + <<: *span_src_node_1 + state: absent + check_mode: true + register: cm_remove_span_src_node_1 + +- name: Remove fabric span source node 1 + cisco.aci.aci_fabric_span_src_group_src_node: + <<: *remove_span_src_node_1 + register: nm_remove_span_src_node_1 + +- name: Remove fabric span source node 1 again + cisco.aci.aci_fabric_span_src_group_src_node: + <<: *remove_span_src_node_1 + register: nm_remove_span_src_node_1_again + +- name: Verify remove fabric span source node 1 + ansible.builtin.assert: + that: + - cm_remove_span_src_node_1 is changed + - cm_remove_span_src_node_1.current | length == 1 + - cm_remove_span_src_node_1.previous | length == 1 + - cm_remove_span_src_node_1.proposed == {} + - nm_remove_span_src_node_1 is changed + - nm_remove_span_src_node_1.current == [] + - nm_remove_span_src_node_1.previous | length == 1 + - nm_remove_span_src_node_1_again is not changed + - nm_remove_span_src_node_1_again.current == [] + - nm_remove_span_src_node_1_again.previous == [] + +# TEST ERROR SOURCE NODES + +- name: Add fabric span source node 2 (error) + cisco.aci.aci_fabric_span_src_group_src_node: + <<: *aci_info + source_group: span_src_group_1 + source: span_src_2 + pod: 1 + node: 101 + state: present + ignore_errors: true + register: err_drop_packets_on_src + +- name: Verify errors span source node 2 + ansible.builtin.assert: + that: + - err_drop_packets_on_src.msg == "APIC Error 105{{':'}} Node span is supported only with Span on drop configuration" + +# CLEAN TEST ENVIRONMENT AFTER TESTS + +- name: Query all fabric span source groups ( clean after ) + cisco.aci.aci_fabric_span_src_group: + <<: *aci_info + state: query + register: query_for_clean + +- name: Clean fabric span source groups ( clean after ) + cisco.aci.aci_fabric_span_src_group: + <<: *aci_info + source_group: "{{ item.spanSrcGrp.attributes.name }}" + state: absent + loop: "{{ query_for_clean.current }}" + +- name: Query all fabric span destination groups ( clean after ) + cisco.aci.aci_fabric_span_dst_group: + <<: *aci_info + state: query + register: query_for_clean + +- name: Clean fabric span destination groups ( clean after ) + cisco.aci.aci_fabric_span_dst_group: + <<: *aci_info + destination_group: "{{ item.spanDestGrp.attributes.name }}" + state: absent + loop: "{{ query_for_clean.current }}" diff --git a/ansible_collections/cisco/aci/tests/integration/targets/aci_fabric_span_src_group_src_path/aliases b/ansible_collections/cisco/aci/tests/integration/targets/aci_fabric_span_src_group_src_path/aliases new file mode 100644 index 000000000..209b793f9 --- /dev/null +++ b/ansible_collections/cisco/aci/tests/integration/targets/aci_fabric_span_src_group_src_path/aliases @@ -0,0 +1,2 @@ +# No ACI simulator yet, so not enabled +# unsupported diff --git a/ansible_collections/cisco/aci/tests/integration/targets/aci_fabric_span_src_group_src_path/tasks/main.yml b/ansible_collections/cisco/aci/tests/integration/targets/aci_fabric_span_src_group_src_path/tasks/main.yml new file mode 100644 index 000000000..e0c772843 --- /dev/null +++ b/ansible_collections/cisco/aci/tests/integration/targets/aci_fabric_span_src_group_src_path/tasks/main.yml @@ -0,0 +1,218 @@ +# Test code for the ACI modules +# Copyright: (c) 2023, Akini Ross (@akinross) + +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +- name: Test that we have an ACI APIC host, ACI username and ACI password + fail: + msg: 'Please define the following variables: aci_hostname, aci_username and aci_password.' + when: aci_hostname is not defined or aci_username is not defined or aci_password is not defined + +- name: Set vars + set_fact: + aci_info: &aci_info + host: "{{ aci_hostname }}" + username: "{{ aci_username }}" + password: "{{ aci_password }}" + validate_certs: '{{ aci_validate_certs | default(false) }}' + use_ssl: '{{ aci_use_ssl | default(true) }}' + use_proxy: '{{ aci_use_proxy | default(true) }}' + output_level: '{{ aci_output_level | default("debug") }}' + +# CLEAN TEST ENVIRONMENT BEFORE TESTS + +- name: Query all fabric span source groups ( clean before ) + cisco.aci.aci_fabric_span_src_group: + <<: *aci_info + state: query + register: query_for_clean + +- name: Clean fabric span source groups ( clean before ) + cisco.aci.aci_fabric_span_src_group: + <<: *aci_info + source_group: "{{ item.spanSrcGrp.attributes.name }}" + state: absent + loop: "{{ query_for_clean.current }}" + +- name: Query all fabric span destination groups ( clean before ) + cisco.aci.aci_fabric_span_dst_group: + <<: *aci_info + state: query + register: query_for_clean + +- name: Clean fabric span destination groups ( clean before ) + cisco.aci.aci_fabric_span_dst_group: + <<: *aci_info + destination_group: "{{ item.spanDestGrp.attributes.name }}" + state: absent + loop: "{{ query_for_clean.current }}" + +- name: Add fabric span destination group type epg ( clean before ) + cisco.aci.aci_fabric_span_dst_group: + <<: *aci_info + destination_group: span_dest_1 + description: test span epg + destination_epg: + destination_ip: 10.0.0.1 + source_ip: 10.0.2.1 + tenant: ansible_test + ap: ansible_test + epg: ansible_test + state: present + +- name: Add fabric span source group 1 ( clean before ) + cisco.aci.aci_fabric_span_src_group: + <<: *aci_info + source_group: span_src_group_1 + destination_group: span_dest_1 + state: present + +- name: Add fabric span source group source 1 ( clean before ) + cisco.aci.aci_fabric_span_src_group_src: + <<: *aci_info + source_group: span_src_group_1 + source: span_src_1 + state: present + +# TEST CREATE SOURCE PATHS + +- name: Add fabric span source path 1 ( checkmode ) + cisco.aci.aci_fabric_span_src_group_src_path: &span_src_path_1 + <<: *aci_info + source_group: span_src_group_1 + source: span_src_1 + pod: 1 + node: 101 + path_ep: eth1/1 + state: present + check_mode: true + register: cm_span_src_path_1 + +- name: Add fabric span source path 1 + cisco.aci.aci_fabric_span_src_group_src_path: + <<: *span_src_path_1 + register: nm_span_src_path_1 + +- name: Add fabric span source path 1 again + cisco.aci.aci_fabric_span_src_group_src_path: + <<: *span_src_path_1 + register: nm_span_src_path_1_again + +- name: Verify add fabric span source path 1 + ansible.builtin.assert: + that: + - cm_span_src_path_1 is changed + - cm_span_src_path_1.previous == [] + - cm_span_src_path_1.current == [] + - cm_span_src_path_1.proposed.spanRsSrcToPathEp.attributes.dn == "uni/fabric/srcgrp-span_src_group_1/src-span_src_1/rssrcToPathEp-[topology/pod-1/paths-101/pathep-[eth1/1]]" + - cm_span_src_path_1.proposed.spanRsSrcToPathEp.attributes.tDn == "topology/pod-1/paths-101/pathep-[eth1/1]" + - nm_span_src_path_1 is changed + - nm_span_src_path_1.current.0.spanRsSrcToPathEp.attributes.dn == "uni/fabric/srcgrp-span_src_group_1/src-span_src_1/rssrcToPathEp-[topology/pod-1/paths-101/pathep-[eth1/1]]" + - nm_span_src_path_1.current.0.spanRsSrcToPathEp.attributes.tDn == "topology/pod-1/paths-101/pathep-[eth1/1]" + - nm_span_src_path_1.previous == [] + - nm_span_src_path_1_again is not changed + - nm_span_src_path_1_again.previous.0.spanRsSrcToPathEp.attributes.dn == "uni/fabric/srcgrp-span_src_group_1/src-span_src_1/rssrcToPathEp-[topology/pod-1/paths-101/pathep-[eth1/1]]" + - nm_span_src_path_1_again.previous.0.spanRsSrcToPathEp.attributes.tDn == "topology/pod-1/paths-101/pathep-[eth1/1]" + - nm_span_src_path_1_again.current.0.spanRsSrcToPathEp.attributes.dn == "uni/fabric/srcgrp-span_src_group_1/src-span_src_1/rssrcToPathEp-[topology/pod-1/paths-101/pathep-[eth1/1]]" + - nm_span_src_path_1_again.current.0.spanRsSrcToPathEp.attributes.tDn == "topology/pod-1/paths-101/pathep-[eth1/1]" + +- name: Add fabric span source path 2 + cisco.aci.aci_fabric_span_src_group_src_path: + <<: *aci_info + source_group: span_src_group_1 + source: span_src_1 + pod: 1 + node: 102 + path_ep: eth1/2 + state: present + +# TEST QUERY SOURCE PATHS + +- name: Query fabric span source path 1 + cisco.aci.aci_fabric_span_src_group_src_path: + <<: *span_src_path_1 + state: query + register: query_one + +- name: Query all fabric span source paths + cisco.aci.aci_fabric_span_src_group_src_path: + <<: *aci_info + state: query + register: query_all + +- name: Verify querying fabric span sources + ansible.builtin.assert: + that: + - query_one is not changed + - query_one.current | length == 1 + - query_one.current.0.spanRsSrcToPathEp.attributes.dn == "uni/fabric/srcgrp-span_src_group_1/src-span_src_1/rssrcToPathEp-[topology/pod-1/paths-101/pathep-[eth1/1]]" + - query_one.current.0.spanRsSrcToPathEp.attributes.tDn == "topology/pod-1/paths-101/pathep-[eth1/1]" + - query_all is not changed + - query_all.current | length >= 2 + +# TEST REMOVAL SOURCE PATHS + +- name: Remove fabric span source path 1 ( checkmode ) + cisco.aci.aci_fabric_span_src_group_src_path: &remove_span_src_path_1 + <<: *span_src_path_1 + state: absent + check_mode: true + register: cm_remove_span_src_path_1 + +- name: Remove fabric span source path 1 + cisco.aci.aci_fabric_span_src_group_src_path: + <<: *remove_span_src_path_1 + register: nm_remove_span_src_path_1 + +- name: Remove fabric span source path 1 again + cisco.aci.aci_fabric_span_src_group_src_path: + <<: *remove_span_src_path_1 + register: nm_remove_span_src_path_1_again + +- name: Verify remove fabric span source path 1 + ansible.builtin.assert: + that: + - cm_remove_span_src_path_1 is changed + - cm_remove_span_src_path_1.current | length == 1 + - cm_remove_span_src_path_1.previous | length == 1 + - cm_remove_span_src_path_1.previous.0.spanRsSrcToPathEp.attributes.dn == "uni/fabric/srcgrp-span_src_group_1/src-span_src_1/rssrcToPathEp-[topology/pod-1/paths-101/pathep-[eth1/1]]" + - cm_remove_span_src_path_1.previous.0.spanRsSrcToPathEp.attributes.tDn == "topology/pod-1/paths-101/pathep-[eth1/1]" + - cm_remove_span_src_path_1.current.0.spanRsSrcToPathEp.attributes.dn == "uni/fabric/srcgrp-span_src_group_1/src-span_src_1/rssrcToPathEp-[topology/pod-1/paths-101/pathep-[eth1/1]]" + - cm_remove_span_src_path_1.current.0.spanRsSrcToPathEp.attributes.tDn == "topology/pod-1/paths-101/pathep-[eth1/1]" + - cm_remove_span_src_path_1.proposed == {} + - nm_remove_span_src_path_1 is changed + - nm_remove_span_src_path_1.current == [] + - nm_remove_span_src_path_1.previous | length == 1 + - nm_remove_span_src_path_1.previous.0.spanRsSrcToPathEp.attributes.dn == "uni/fabric/srcgrp-span_src_group_1/src-span_src_1/rssrcToPathEp-[topology/pod-1/paths-101/pathep-[eth1/1]]" + - nm_remove_span_src_path_1.previous.0.spanRsSrcToPathEp.attributes.tDn == "topology/pod-1/paths-101/pathep-[eth1/1]" + - nm_remove_span_src_path_1_again is not changed + - nm_remove_span_src_path_1_again.current == [] + - nm_remove_span_src_path_1_again.previous == [] + +# CLEAN TEST ENVIRONMENT AFTER TESTS + +- name: Query all fabric span source groups ( clean after ) + cisco.aci.aci_fabric_span_src_group: + <<: *aci_info + state: query + register: query_for_clean + +- name: Clean fabric span source groups ( clean after ) + cisco.aci.aci_fabric_span_src_group: + <<: *aci_info + source_group: "{{ item.spanSrcGrp.attributes.name }}" + state: absent + loop: "{{ query_for_clean.current }}" + +- name: Query all fabric span destination groups ( clean after ) + cisco.aci.aci_fabric_span_dst_group: + <<: *aci_info + state: query + register: query_for_clean + +- name: Clean fabric span destination groups ( clean after ) + cisco.aci.aci_fabric_span_dst_group: + <<: *aci_info + destination_group: "{{ item.spanDestGrp.attributes.name }}" + state: absent + loop: "{{ query_for_clean.current }}" diff --git a/ansible_collections/cisco/aci/tests/integration/targets/aci_fabric_wide_settings/aliases b/ansible_collections/cisco/aci/tests/integration/targets/aci_fabric_wide_settings/aliases new file mode 100644 index 000000000..209b793f9 --- /dev/null +++ b/ansible_collections/cisco/aci/tests/integration/targets/aci_fabric_wide_settings/aliases @@ -0,0 +1,2 @@ +# No ACI simulator yet, so not enabled +# unsupported diff --git a/ansible_collections/cisco/aci/tests/integration/targets/aci_fabric_wide_settings/tasks/main.yml b/ansible_collections/cisco/aci/tests/integration/targets/aci_fabric_wide_settings/tasks/main.yml new file mode 100644 index 000000000..526c8bee0 --- /dev/null +++ b/ansible_collections/cisco/aci/tests/integration/targets/aci_fabric_wide_settings/tasks/main.yml @@ -0,0 +1,229 @@ +# Test code for the ACI modules +# Copyright: (c) 2023, Tim Cragg (@timcragg) +# Copyright: (c) 2023, Akini Ross (@akinross) + +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +- name: Test that we have an ACI APIC host, ACI username and ACI password + fail: + msg: 'Please define the following variables: aci_hostname, aci_username and aci_password.' + when: aci_hostname is not defined or aci_username is not defined or aci_password is not defined + +- name: Set vars + set_fact: + aci_info: &aci_info + host: "{{ aci_hostname }}" + username: "{{ aci_username }}" + password: "{{ aci_password }}" + validate_certs: '{{ aci_validate_certs | default(false) }}' + use_ssl: '{{ aci_use_ssl | default(true) }}' + use_proxy: '{{ aci_use_proxy | default(true) }}' + output_level: '{{ aci_output_level | default("info") }}' + +- name: Verify Cloud and Non-Cloud Sites in use. + include_tasks: ../../../../../../integration/targets/aci_cloud_provider/tasks/main.yml + +- name: Execute tasks only for non-cloud sites + when: query_cloud.current == [] # This condition will skip execution for cloud sites + block: + + # STORE EXISTING FABRIC WIDE SETTINGS + - name: Query system information + cisco.aci.aci_system: + <<: *aci_info + id: 1 + state: query + register: version + + - name: Capture existing Fabric Wide Settings + cisco.aci.aci_fabric_wide_settings: + <<: *aci_info + state: query + register: previous_settings + + - name: Clear existing settings + cisco.aci.aci_fabric_wide_settings: + <<: *aci_info + disable_remote_ep_learning: false + enforce_subnet_check: false + enforce_epg_vlan_validation: false + spine_opflex_client_auth: false + spine_ssl_opflex: false + + # SET FABRIC WIDE SETTINGS + - name: Execute tests for ACI v5+ + when: version.current.0.topSystem.attributes.version is version('5', '>=') + block: + + - name: Clear existing settings for ACI v5+ + cisco.aci.aci_fabric_wide_settings: + <<: *aci_info + leaf_opflex_client_auth: false + leaf_ssl_opflex: false + restrict_infra_vlan_traffic: false + opflex_ssl_versions: [ tls_v1.2 ] + + - name: Update Fabric Wide Settings for ACI v5+ (check_mode) + cisco.aci.aci_fabric_wide_settings: &aci_fab_settings_5 + <<: *aci_info + disable_remote_ep_learning: true + enforce_subnet_check: true + enforce_epg_vlan_validation: true + spine_opflex_client_auth: true + leaf_opflex_client_auth: true + spine_ssl_opflex: true + leaf_ssl_opflex: true + restrict_infra_vlan_traffic: true + opflex_ssl_versions: [ tls_v1.1, tls_v1.2 ] + check_mode: true + register: cm_update_fab_settings + + - name: Update Fabric Wide Settings for ACI v5+ + cisco.aci.aci_fabric_wide_settings: + <<: *aci_fab_settings_5 + register: nm_update_fab_settings + + - name: Update Fabric Wide Settings Again for ACI v5+ + cisco.aci.aci_fabric_wide_settings: + <<: *aci_fab_settings_5 + register: nm_update_fab_settings_again + + - name: Verify Fabric Wide Settings for ACI v5+ + ansible.builtin.assert: + that: + - cm_update_fab_settings is changed + - nm_update_fab_settings is changed + - nm_update_fab_settings.current.0.infraSetPol.attributes.unicastXrEpLearnDisable == "yes" + - nm_update_fab_settings.current.0.infraSetPol.attributes.enforceSubnetCheck == "yes" + - nm_update_fab_settings.current.0.infraSetPol.attributes.validateOverlappingVlans == "yes" + - nm_update_fab_settings.current.0.infraSetPol.attributes.domainValidation == previous_settings.current.0.infraSetPol.attributes.domainValidation + - nm_update_fab_settings.current.0.infraSetPol.attributes.opflexpAuthenticateClients == "yes" + - nm_update_fab_settings.current.0.infraSetPol.attributes.opflexpUseSsl == "yes" + - nm_update_fab_settings.current.0.infraSetPol.attributes.reallocateGipo == previous_settings.current.0.infraSetPol.attributes.reallocateGipo + - nm_update_fab_settings.current.0.infraSetPol.attributes.leafOpflexpAuthenticateClients == "yes" + - nm_update_fab_settings.current.0.infraSetPol.attributes.leafOpflexpUseSsl == "yes" + - nm_update_fab_settings.current.0.infraSetPol.attributes.restrictInfraVLANTraffic == "yes" + - nm_update_fab_settings.current.0.infraSetPol.attributes.opflexpSslProtocols == "TLSv1.1,TLSv1.2" + - nm_update_fab_settings.previous.0.infraSetPol.attributes.unicastXrEpLearnDisable == "no" + - nm_update_fab_settings.previous.0.infraSetPol.attributes.enforceSubnetCheck == "no" + - nm_update_fab_settings.previous.0.infraSetPol.attributes.validateOverlappingVlans == "no" + - nm_update_fab_settings.previous.0.infraSetPol.attributes.domainValidation == previous_settings.current.0.infraSetPol.attributes.domainValidation + - nm_update_fab_settings.previous.0.infraSetPol.attributes.opflexpAuthenticateClients == "no" + - nm_update_fab_settings.previous.0.infraSetPol.attributes.opflexpUseSsl == "no" + - nm_update_fab_settings.previous.0.infraSetPol.attributes.reallocateGipo == previous_settings.current.0.infraSetPol.attributes.reallocateGipo + - nm_update_fab_settings.previous.0.infraSetPol.attributes.leafOpflexpAuthenticateClients == "no" + - nm_update_fab_settings.previous.0.infraSetPol.attributes.leafOpflexpUseSsl == "no" + - nm_update_fab_settings.previous.0.infraSetPol.attributes.restrictInfraVLANTraffic == "no" + - nm_update_fab_settings.previous.0.infraSetPol.attributes.opflexpSslProtocols == "TLSv1.2" + - nm_update_fab_settings_again is not changed + - nm_update_fab_settings_again.current.0.infraSetPol.attributes.unicastXrEpLearnDisable == "yes" + - nm_update_fab_settings_again.current.0.infraSetPol.attributes.enforceSubnetCheck == "yes" + - nm_update_fab_settings_again.current.0.infraSetPol.attributes.validateOverlappingVlans == "yes" + - nm_update_fab_settings_again.current.0.infraSetPol.attributes.domainValidation == previous_settings.current.0.infraSetPol.attributes.domainValidation + - nm_update_fab_settings_again.current.0.infraSetPol.attributes.opflexpAuthenticateClients == "yes" + - nm_update_fab_settings_again.current.0.infraSetPol.attributes.opflexpUseSsl == "yes" + - nm_update_fab_settings_again.current.0.infraSetPol.attributes.reallocateGipo == previous_settings.current.0.infraSetPol.attributes.reallocateGipo + - nm_update_fab_settings_again.current.0.infraSetPol.attributes.leafOpflexpAuthenticateClients == "yes" + - nm_update_fab_settings_again.current.0.infraSetPol.attributes.leafOpflexpUseSsl == "yes" + - nm_update_fab_settings_again.current.0.infraSetPol.attributes.restrictInfraVLANTraffic == "yes" + - nm_update_fab_settings_again.current.0.infraSetPol.attributes.opflexpSslProtocols == "TLSv1.1,TLSv1.2" + - nm_update_fab_settings_again.previous.0.infraSetPol.attributes.unicastXrEpLearnDisable == "yes" + - nm_update_fab_settings_again.previous.0.infraSetPol.attributes.enforceSubnetCheck == "yes" + - nm_update_fab_settings_again.previous.0.infraSetPol.attributes.validateOverlappingVlans == "yes" + - nm_update_fab_settings_again.previous.0.infraSetPol.attributes.domainValidation == previous_settings.current.0.infraSetPol.attributes.domainValidation + - nm_update_fab_settings_again.previous.0.infraSetPol.attributes.opflexpAuthenticateClients == "yes" + - nm_update_fab_settings_again.previous.0.infraSetPol.attributes.opflexpUseSsl == "yes" + - nm_update_fab_settings_again.previous.0.infraSetPol.attributes.reallocateGipo == previous_settings.current.0.infraSetPol.attributes.reallocateGipo + - nm_update_fab_settings_again.previous.0.infraSetPol.attributes.leafOpflexpAuthenticateClients == "yes" + - nm_update_fab_settings_again.previous.0.infraSetPol.attributes.leafOpflexpUseSsl == "yes" + - nm_update_fab_settings_again.previous.0.infraSetPol.attributes.restrictInfraVLANTraffic == "yes" + - nm_update_fab_settings_again.previous.0.infraSetPol.attributes.opflexpSslProtocols == "TLSv1.1,TLSv1.2" + + - name: Execute tests for ACI + when: version.current.0.topSystem.attributes.version is version('5', '<') + block: + + - name: Update Fabric Wide Settings (check_mode) + cisco.aci.aci_fabric_wide_settings: &aci_fab_settings + <<: *aci_info + disable_remote_ep_learning: true + enforce_subnet_check: true + enforce_epg_vlan_validation: true + spine_opflex_client_auth: true + spine_ssl_opflex: true + check_mode: true + register: cm_update_fab_settings + + - name: Update Fabric Wide Settings + cisco.aci.aci_fabric_wide_settings: + <<: *aci_fab_settings + register: nm_update_fab_settings + + - name: Update Fabric Wide Settings Again + cisco.aci.aci_fabric_wide_settings: + <<: *aci_fab_settings + register: nm_update_fab_settings_again + + - name: Verify Fabric Wide Settings + ansible.builtin.assert: + that: + - cm_update_fab_settings is changed + - nm_update_fab_settings is changed + - nm_update_fab_settings.current.0.infraSetPol.attributes.unicastXrEpLearnDisable == "yes" + - nm_update_fab_settings.current.0.infraSetPol.attributes.enforceSubnetCheck == "yes" + - nm_update_fab_settings.current.0.infraSetPol.attributes.validateOverlappingVlans == "yes" + - nm_update_fab_settings.current.0.infraSetPol.attributes.domainValidation == previous_settings.current.0.infraSetPol.attributes.domainValidation + - nm_update_fab_settings.current.0.infraSetPol.attributes.opflexpAuthenticateClients == "yes" + - nm_update_fab_settings.current.0.infraSetPol.attributes.opflexpUseSsl == "yes" + - nm_update_fab_settings.current.0.infraSetPol.attributes.reallocateGipo == previous_settings.current.0.infraSetPol.attributes.reallocateGipo + - nm_update_fab_settings.previous.0.infraSetPol.attributes.unicastXrEpLearnDisable == "no" + - nm_update_fab_settings.previous.0.infraSetPol.attributes.enforceSubnetCheck == "no" + - nm_update_fab_settings.previous.0.infraSetPol.attributes.validateOverlappingVlans == "no" + - nm_update_fab_settings.previous.0.infraSetPol.attributes.domainValidation == previous_settings.current.0.infraSetPol.attributes.domainValidation + - nm_update_fab_settings.previous.0.infraSetPol.attributes.opflexpAuthenticateClients == "no" + - nm_update_fab_settings.previous.0.infraSetPol.attributes.opflexpUseSsl == "no" + - nm_update_fab_settings.previous.0.infraSetPol.attributes.reallocateGipo == previous_settings.current.0.infraSetPol.attributes.reallocateGipo + - nm_update_fab_settings_again is not changed + - nm_update_fab_settings_again.current.0.infraSetPol.attributes.unicastXrEpLearnDisable == "yes" + - nm_update_fab_settings_again.current.0.infraSetPol.attributes.enforceSubnetCheck == "yes" + - nm_update_fab_settings_again.current.0.infraSetPol.attributes.validateOverlappingVlans == "yes" + - nm_update_fab_settings_again.current.0.infraSetPol.attributes.domainValidation == previous_settings.current.0.infraSetPol.attributes.domainValidation + - nm_update_fab_settings_again.current.0.infraSetPol.attributes.opflexpAuthenticateClients == "yes" + - nm_update_fab_settings_again.current.0.infraSetPol.attributes.opflexpUseSsl == "yes" + - nm_update_fab_settings_again.current.0.infraSetPol.attributes.reallocateGipo == previous_settings.current.0.infraSetPol.attributes.reallocateGipo + - nm_update_fab_settings_again.previous.0.infraSetPol.attributes.unicastXrEpLearnDisable == "yes" + - nm_update_fab_settings_again.previous.0.infraSetPol.attributes.enforceSubnetCheck == "yes" + - nm_update_fab_settings_again.previous.0.infraSetPol.attributes.validateOverlappingVlans == "yes" + - nm_update_fab_settings_again.previous.0.infraSetPol.attributes.domainValidation == previous_settings.current.0.infraSetPol.attributes.domainValidation + - nm_update_fab_settings_again.previous.0.infraSetPol.attributes.opflexpAuthenticateClients == "yes" + - nm_update_fab_settings_again.previous.0.infraSetPol.attributes.opflexpUseSsl == "yes" + - nm_update_fab_settings_again.previous.0.infraSetPol.attributes.reallocateGipo == previous_settings.current.0.infraSetPol.attributes.reallocateGipo + + # QUERY FABRIC WIDE SETTINGS + - name: Query Fabric Wide Settings + cisco.aci.aci_fabric_wide_settings: + <<: *aci_info + state: query + register: query_fab + + - name: Verify Fabric Wide Settings queries + ansible.builtin.assert: + that: + - query_fab is not changed + - query_fab.current.0.infraSetPol.attributes.unicastXrEpLearnDisable == "yes" + - query_fab.current.0.infraSetPol.attributes.enforceSubnetCheck == "yes" + - query_fab.current.0.infraSetPol.attributes.validateOverlappingVlans == "yes" + - query_fab.current.0.infraSetPol.attributes.domainValidation == query_fab.current.0.infraSetPol.attributes.domainValidation + - query_fab.current.0.infraSetPol.attributes.opflexpAuthenticateClients == "yes" + - query_fab.current.0.infraSetPol.attributes.opflexpUseSsl == "yes" + - query_fab.current.0.infraSetPol.attributes.reallocateGipo == query_fab.current.0.infraSetPol.attributes.reallocateGipo + + - name: Verify Fabric Wide Settings queries + ansible.builtin.assert: + that: + - query_fab is not changed + - query_fab.current.0.infraSetPol.attributes.leafOpflexpAuthenticateClients == "yes" + - query_fab.current.0.infraSetPol.attributes.leafOpflexpUseSsl == "yes" + - query_fab.current.0.infraSetPol.attributes.restrictInfraVLANTraffic == "yes" + - query_fab.current.0.infraSetPol.attributes.opflexpSslProtocols == "TLSv1.1,TLSv1.2" + when: version.current.0.topSystem.attributes.version is version('5', '>=') diff --git a/ansible_collections/cisco/aci/tests/integration/targets/aci_file_remote_path/aliases b/ansible_collections/cisco/aci/tests/integration/targets/aci_file_remote_path/aliases new file mode 100644 index 000000000..209b793f9 --- /dev/null +++ b/ansible_collections/cisco/aci/tests/integration/targets/aci_file_remote_path/aliases @@ -0,0 +1,2 @@ +# No ACI simulator yet, so not enabled +# unsupported diff --git a/ansible_collections/cisco/aci/tests/integration/targets/aci_file_remote_path/pki/rsa_ansible.key b/ansible_collections/cisco/aci/tests/integration/targets/aci_file_remote_path/pki/rsa_ansible.key new file mode 100644 index 000000000..ac63a0055 --- /dev/null +++ b/ansible_collections/cisco/aci/tests/integration/targets/aci_file_remote_path/pki/rsa_ansible.key @@ -0,0 +1,15 @@ +-----BEGIN RSA PRIVATE KEY----- +MIICXgIBAAKBgQDVyLS8/ix6QOH7R83B4WuhsliL6nffBvrkHXXsqViit3OZd+/K +fSrNlZysUvHS4hxfRtJrFQfpkogwXEEupBPF3p0xy7wZzvjjWWJk0NQ8PoVlOhUY +emZTfMX+FFNr9pAjjjaVHb9jCuxko7upAyj8POhhETY2zYoJoa8TR6fLZwIDAQAB +AoGBALo5GzeGMThNTIyW/6Tjt94ifP9kPwcIDYSoJRECczNKmmgVEcxRO/fZW6DA +n+YTEKPuDV059KqB+iAmPKFkS9N41kaq+NUAknlFJPV6Vs3gpvJGqWgu++73dhR5 +cKsHTlK2KBsRtsXnOJ9odKWFjiTnZ1Eyvmhw7ct+Fojb/7ABAkEA9+Wwm+HGlYqw +ghuFaBtNuqC/S2vO6SEfdQvTDQKKO5ROei5m+ryjWj6flbCcG+5dLs8l4Zh3sQUL +kc0RQfHSWQJBANzFkdO6wXXPOw7RhAEP2sA2W2VacMbGynjsoDJXmypeJ7Z+odLb +5gNXET9RA77RY/saIBdwR4JNnku2WnoxU78CQQDhYirVP0vu8H6UfHMpeRGNqdLi +vq0LlrrkDxEe1f1aN/e17HRiaZnXVfKABWeZmXmNMndNifLgtiaTtC+JllRZAkEA +ydAdV0SANvaCETC7j9DzcgP+lm8PatYsHlCIvJxS7m71tKCbw0pbQDBmRtADMXrt +/4vJTEPKSrYzfxiqKstOtwJAXkWXaqVhJeKjbMj1buo6s/1qGIfSrZR/AjozvJ03 +JehevfULS3668jOYJZW6BoNhysx6+Hqf5Id8fB4iDWPQhA== +-----END RSA PRIVATE KEY----- diff --git a/ansible_collections/cisco/aci/tests/integration/targets/aci_file_remote_path/tasks/main.yml b/ansible_collections/cisco/aci/tests/integration/targets/aci_file_remote_path/tasks/main.yml new file mode 100644 index 000000000..ab1560ba6 --- /dev/null +++ b/ansible_collections/cisco/aci/tests/integration/targets/aci_file_remote_path/tasks/main.yml @@ -0,0 +1,231 @@ +# Test code for the ACI modules +# Copyright: (c) 2023, Tim Cragg (@timcragg) + +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +- name: Test that we have an ACI APIC host, ACI username and ACI password + fail: + msg: 'Please define the following variables: aci_hostname, aci_username and aci_password.' + when: aci_hostname is not defined or aci_username is not defined or aci_password is not defined + +- name: Set vars + set_fact: + aci_info: &aci_info + host: "{{ aci_hostname }}" + username: "{{ aci_username }}" + password: "{{ aci_password }}" + validate_certs: '{{ aci_validate_certs | default(false) }}' + use_ssl: '{{ aci_use_ssl | default(true) }}' + use_proxy: '{{ aci_use_proxy | default(true) }}' + output_level: '{{ aci_output_level | default("info") }}' + +# CLEAN ENVIRONMENT +- name: Remove the ansible_remote_path + cisco.aci.aci_file_remote_path: + <<: *aci_info + name: ansible_file_path + state: absent + +# CREATE FILE REMOTE PATH +- name: Create a File Remote Path (check mode) + cisco.aci.aci_file_remote_path: &aci_path + <<: *aci_info + name: ansible_file_path + description: Test File Path + remote_host: test.example.com + remote_port: 22 + remote_protocol: scp + remote_user: test_user + auth_type: password + remote_path: /tmp + check_mode: true + register: cm_create_path + +- name: Create a File Remote Path + cisco.aci.aci_file_remote_path: + <<: *aci_path + register: nm_create_path + +- name: Create a File Remote Path Again + cisco.aci.aci_file_remote_path: + <<: *aci_path + register: nm_create_path_again + +- name: Create a second File Remote Path + cisco.aci.aci_file_remote_path: + <<: *aci_info + name: ansible_second_file_path + auth_type: password + remote_host: test2.example.com + remote_port: 22 + remote_protocol: scp + remote_path: /tmp + management_epg: oob-default + register: create_second_path + +- name: Create File Remote Path using SSH Key auth + cisco.aci.aci_file_remote_path: + <<: *aci_info + name: ansible_ssh_auth_file_path + auth_type: ssh_key + remote_key: "{{ lookup('file', 'pki/rsa_ansible.key') }}" + remote_host: test3.example.com + remote_port: 22 + remote_protocol: scp + remote_path: /tmp + register: create_key_auth_path + +- name: Verify creation of File Remote Path + ansible.builtin.assert: + that: + - cm_create_path is changed + - nm_create_path is changed + - nm_create_path_again is not changed + - nm_create_path.current.0.fileRemotePath.attributes.name == "ansible_file_path" + - nm_create_path.current.0.fileRemotePath.attributes.descr == "Test File Path" + - nm_create_path.current.0.fileRemotePath.attributes.protocol == "scp" + - nm_create_path.current.0.fileRemotePath.attributes.remotePort == "22" + - nm_create_path.current.0.fileRemotePath.attributes.authType == "usePassword" + - nm_create_path.current.0.fileRemotePath.attributes.userName == "test_user" + - nm_create_path.current.0.fileRemotePath.attributes.remotePath == "/tmp" + - nm_create_path_again.current.0.fileRemotePath.attributes.name == "ansible_file_path" + - nm_create_path_again.current.0.fileRemotePath.attributes.descr == "Test File Path" + - nm_create_path_again.current.0.fileRemotePath.attributes.protocol == "scp" + - nm_create_path_again.current.0.fileRemotePath.attributes.remotePort == "22" + - nm_create_path_again.current.0.fileRemotePath.attributes.authType == "usePassword" + - nm_create_path_again.current.0.fileRemotePath.attributes.userName == "test_user" + - nm_create_path_again.current.0.fileRemotePath.attributes.remotePath == "/tmp" + - create_second_path.current.0.fileRemotePath.children.0.fileRsARemoteHostToEpg.attributes.tDn == "uni/tn-mgmt/mgmtp-default/oob-default" + - create_key_auth_path.current.0.fileRemotePath.attributes.authType == "useSshKeyContents" + +# UPDATE FILE REMOTE PATH +- name: Update File Remote Path + cisco.aci.aci_file_remote_path: + <<: *aci_path + remote_protocol: sftp + remote_user: new_user + remote_password: new_pass + remote_path: /tmp/dummy + register: update_path + +- name: Verify update of File Remote Path + ansible.builtin.assert: + that: + - update_path is changed + - update_path.current.0.fileRemotePath.attributes.name == "ansible_file_path" + - update_path.current.0.fileRemotePath.attributes.descr == "Test File Path" + - update_path.current.0.fileRemotePath.attributes.protocol == "sftp" + - update_path.current.0.fileRemotePath.attributes.authType == "usePassword" + - update_path.current.0.fileRemotePath.attributes.userName == "new_user" + - update_path.current.0.fileRemotePath.attributes.remotePath == "/tmp/dummy" + +# QUERY FILE REMOTE PATH +- name: Query a File Remote Path + cisco.aci.aci_file_remote_path: + <<: *aci_path + state: query + register: query_one + +- name: Query all File Remote Paths + cisco.aci.aci_file_remote_path: + <<: *aci_info + state: query + register: query_all + +- name: Verify File Remote Path queries + ansible.builtin.assert: + that: + - query_one is not changed + - query_one.current.0.fileRemotePath.attributes.name == "ansible_file_path" + - query_one.current.0.fileRemotePath.attributes.descr == "Test File Path" + - query_one.current.0.fileRemotePath.attributes.protocol == "sftp" + - query_one.current.0.fileRemotePath.attributes.authType == "usePassword" + - query_one.current.0.fileRemotePath.attributes.userName == "new_user" + - query_one.current.0.fileRemotePath.attributes.remotePath == "/tmp/dummy" + - query_all is not changed + - query_all.current | length > 1 + +# VALIDATE ERROR CHECKING +- name: Create Remote Path with remote_key when auth_type is password + cisco.aci.aci_file_remote_path: + <<: *aci_info + name: incorrect_auth_type1 + remote_host: test2.example.com + remote_port: 22 + remote_protocol: scp + remote_user: test_user + auth_type: password + remote_key: "{{ lookup('file', 'pki/rsa_ansible.key') }}" + remote_path: /tmp + ignore_errors: true + register: incorrect_auth_type1 + +- name: Create Remote Path with passphrase when auth_type is password + cisco.aci.aci_file_remote_path: + <<: *aci_info + name: incorrect_auth_type2 + remote_host: test2.example.com + remote_port: 22 + remote_protocol: scp + remote_user: test_user + auth_type: password + passphrase: ansible_passphrase + remote_path: /tmp + ignore_errors: true + register: incorrect_auth_type2 + +- name: Create Remote Path with password when auth_type is ssh_key + cisco.aci.aci_file_remote_path: + <<: *aci_info + name: incorrect_auth_type3 + remote_host: test2.example.com + remote_port: 22 + remote_protocol: scp + remote_user: test_user + auth_type: ssh_key + remote_password: test_password + remote_path: /tmp + ignore_errors: true + register: incorrect_auth_type3 + +- name: Validate error checking + ansible.builtin.assert: + that: + - incorrect_auth_type1 is failed + - incorrect_auth_type2 is failed + - incorrect_auth_type3 is failed + - incorrect_auth_type1.msg == "remote_key cannot be set if auth_type is password" + - incorrect_auth_type2.msg == "passphrase cannot be set if auth_type is password" + - incorrect_auth_type3.msg == "remote_password cannot be set if auth_type is ssh_key" + +# REMOVE FILE REMOTE PATH +- name: Delete a File Remote Path + cisco.aci.aci_file_remote_path: + <<: *aci_path + state: absent + register: delete + +- name: Delete File Remote Path again + cisco.aci.aci_file_remote_path: + <<: *aci_path + state: absent + register: delete_again + +- name: Verify deletion of File Remote Path + ansible.builtin.assert: + that: + - delete is changed + - delete_again is not changed + - delete.current == [] + +- name: Delete Second File Remote Path + cisco.aci.aci_file_remote_path: + <<: *aci_info + name: ansible_second_file_path + state: absent + +- name: Delete SSH Key Auth File Remote Path + cisco.aci.aci_file_remote_path: + <<: *aci_info + name: ansible_ssh_auth_file_path + state: absent
\ No newline at end of file diff --git a/ansible_collections/cisco/aci/tests/integration/targets/aci_filter_entry/tasks/main.yml b/ansible_collections/cisco/aci/tests/integration/targets/aci_filter_entry/tasks/main.yml index 77093a670..e969111a2 100644 --- a/ansible_collections/cisco/aci/tests/integration/targets/aci_filter_entry/tasks/main.yml +++ b/ansible_collections/cisco/aci/tests/integration/targets/aci_filter_entry/tasks/main.yml @@ -20,296 +20,492 @@ use_proxy: '{{ aci_use_proxy | default(true) }}' output_level: '{{ aci_output_level | default("debug") }}' -- name: ensure tenant exists for tests to kick off - cisco.aci.aci_tenant: - <<: *aci_info - tenant: ansible_test - state: absent - -- name: ensure tenant exists for tests to kick off - cisco.aci.aci_tenant: &aci_tenant_present - <<: *aci_info - tenant: ansible_test - state: present - register: tenant_present - -- name: ensure filter exists for tests to kick off - cisco.aci.aci_filter: &aci_filter_present - <<: *aci_tenant_present - filter: anstest - register: filter_present - -- name: create filter entry - check mode works - cisco.aci.aci_filter_entry: &aci_entry_present - <<: *aci_filter_present - entry: anstest - description: Ansible Test - ether_type: ip - ip_protocol: tcp - dst_port_start: 80 - dst_port_end: 88 - check_mode: true - register: entry_present_check_mode - -- name: create filter entry - creation works - cisco.aci.aci_filter_entry: - <<: *aci_entry_present - register: entry_present - -- name: create filter entry - idempotency works - cisco.aci.aci_filter_entry: - <<: *aci_entry_present - register: entry_present_idempotent - -- name: update filter entry - update works - cisco.aci.aci_filter_entry: - <<: *aci_entry_present - description: Ansible Test Update - dst_port_start: 80 - dst_port_end: 90 - register: entry_present_update - - name: Verify Cloud and Non-Cloud Sites in use. include_tasks: ../../../../../../integration/targets/aci_cloud_provider/tasks/main.yml -- name: create filter entry - test different types - cisco.aci.aci_filter_entry: - <<: *aci_filter_present - entry: anstest2 - ether_type: arp - arp_flag: arp_reply - register: entry_present_2 - when: query_cloud.current == [] # This condition will skip execution for cloud sites - -- name: create filter entry - test different types - cisco.aci.aci_filter_entry: - <<: *aci_filter_present - entry: anstest3 - ether_type: ip - ip_protocol: icmp - icmp_msg_type: echo - register: entry_present_3 - -- name: create filter entry - test different types - cisco.aci.aci_filter_entry: - <<: *aci_filter_present - entry: anstest4 - ether_type: ip - ip_protocol: udp - dst_port: 1000 - register: entry_present_4 - -- name: missing param - failure message works - cisco.aci.aci_filter_entry: - <<: *aci_filter_present - ignore_errors: true - register: present_missing_param - -- name: incompatable params - failure message works - cisco.aci.aci_filter_entry: - <<: *aci_entry_present - dst_port: 99 - ignore_errors: true - register: present_incompatible_params - -- name: present assertions - assert: - that: - - entry_present_check_mode is changed - - entry_present_check_mode.previous == [] - - entry_present_check_mode.sent.vzEntry.attributes.dFromPort == 'http' - - entry_present_check_mode.sent.vzEntry.attributes.dToPort == '88' - - entry_present_check_mode.sent.vzEntry.attributes.descr == 'Ansible Test' - - entry_present_check_mode.sent.vzEntry.attributes.etherT == 'ip' - - entry_present_check_mode.sent.vzEntry.attributes.name == 'anstest' - - entry_present_check_mode.sent.vzEntry.attributes.prot == 'tcp' - - entry_present is changed - - entry_present.current.0.vzEntry.attributes.annotation == 'orchestrator:ansible' - - entry_present.previous == [] - - entry_present.sent == entry_present_check_mode.sent - - entry_present_idempotent is not changed - - entry_present_idempotent.previous != [] - - entry_present_idempotent.sent == {} - - entry_present_update is changed - - entry_present_update.previous != [] - - entry_present_update.sent != entry_present_update.proposed - - entry_present_3 is changed - - entry_present_3.sent.vzEntry.attributes.etherT == 'ip' - - entry_present_3.sent.vzEntry.attributes.icmpv4T == 'echo' - - entry_present_3.sent.vzEntry.attributes.name == 'anstest3' - - entry_present_3.sent.vzEntry.attributes.prot == 'icmp' - - entry_present_4 is changed - - entry_present_4.sent.vzEntry.attributes.dFromPort == '1000' - - entry_present_4.sent.vzEntry.attributes.dToPort == '1000' - - entry_present_4.sent.vzEntry.attributes.etherT == 'ip' - - entry_present_4.sent.vzEntry.attributes.name == 'anstest4' - - entry_present_4.sent.vzEntry.attributes.prot == 'udp' - - present_missing_param is failed - - 'present_missing_param.msg == "state is present but all of the following are missing: entry"' - - present_incompatible_params is failed - - present_incompatible_params.msg.startswith("Parameter") - -- name: present assertions for arp - assert: - that: - - entry_present_2 is changed - - entry_present_2.sent.vzEntry.attributes.arpOpc == 'reply' - - entry_present_2.sent.vzEntry.attributes.etherT == 'arp' - - entry_present_2.sent.vzEntry.attributes.name == 'anstest2' - when: query_cloud.current == [] # This condition will skip execution for cloud sites - -- name: query tenant filter entry - cisco.aci.aci_filter_entry: &aci_query_entry - <<: *aci_entry_present - state: query - register: query_tenant_filter_entry - -- name: query filter entry - cisco.aci.aci_filter_entry: - <<: *aci_query_entry - tenant: "{{ fakevar | default(omit) }}" - register: query_filter_entry - -- name: query tenant entry - cisco.aci.aci_filter_entry: - <<: *aci_query_entry - filter: "{{ fakevar | default(omit) }}" - register: query_tenant_entry - -- name: query tenant filter - cisco.aci.aci_filter_entry: - <<: *aci_query_entry - entry: "{{ fakevar | default(omit) }}" - register: query_tenant_filter - -- name: query entry - cisco.aci.aci_filter_entry: &aci_query_entry_2 - <<: *aci_query_entry - tenant: "{{ fakevar | default(omit) }}" - filter: "{{ fakevar | default(omit) }}" - register: query_entry - -- name: query filter - cisco.aci.aci_filter_entry: - <<: *aci_query_entry - tenant: "{{ fakevar | default(omit) }}" - entry: "{{ fakevar | default(omit) }}" - register: query_filter - -- name: query tenant - cisco.aci.aci_filter_entry: - <<: *aci_query_entry - filter: "{{ fakevar | default(omit) }}" - entry: "{{ fakevar | default(omit) }}" - register: query_tenant - -- name: query all - cisco.aci.aci_filter_entry: - <<: *aci_query_entry_2 - entry: "{{ fakevar | default(omit) }}" - register: query_all - -- name: query assertions for all - assert: - that: - - query_tenant_filter_entry is not changed - - query_tenant_filter_entry.current | length == 1 - - query_tenant_filter_entry.current.0.vzEntry.attributes.name == "anstest" - - '"tn-ansible_test/flt-anstest/e-anstest.json" in query_tenant_filter_entry.url' - - query_filter_entry is not changed - - query_filter_entry.current.0.vzFilter.attributes.name == "anstest" - - query_filter_entry.current.0.vzFilter.children | length == 1 - - '"query-target-filter=eq(vzFilter.name,\"anstest\")" in query_filter_entry.filter_string' - - '"rsp-subtree-filter=eq(vzEntry.name,\"anstest\")" in query_filter_entry.filter_string' - - '"class/vzFilter.json" in query_filter_entry.url' - - query_tenant_entry is not changed - - query_tenant_entry.current | length == 1 - - query_tenant_entry.current.0.fvTenant.attributes.name == "ansible_test" - - '"rsp-subtree-filter=eq(vzEntry.name,\"anstest\")" in query_tenant_entry.filter_string' - - '"rsp-subtree-class=vzEntry" in query_tenant_entry.filter_string' - - '"tn-ansible_test.json" in query_tenant_entry.url' - - query_tenant_filter is not changed - - query_tenant_filter.current | length == 1 - - query_tenant_filter.current.0.vzFilter.attributes.name == "anstest" - - '"rsp-subtree-class=vzEntry" in query_tenant_filter.filter_string' - - '"tn-ansible_test/flt-anstest.json" in query_tenant_filter.url' - - query_entry is not changed - - query_entry.current.0.vzEntry.attributes.name == "anstest" - - '"query-target-filter=eq(vzEntry.name,\"anstest\")" in query_entry.filter_string' - - '"class/vzEntry.json" in query_entry.url' - - query_filter is not changed - - query_filter.current.0.vzFilter.attributes.name == "anstest" - - '"query-target-filter=eq(vzFilter.name,\"anstest\")" in query_filter.filter_string' - - '"rsp-subtree-class=vzEntry" in query_filter.filter_string' - - '"class/vzFilter.json" in query_filter.url' - - query_tenant is not changed - - query_tenant.current | length == 1 - - query_tenant.current.0.fvTenant.attributes.name == "ansible_test" - - '"rsp-subtree-class=vzEntry,vzFilter" in query_tenant.filter_string' - - '"tn-ansible_test.json" in query_tenant.url' - - query_all is not changed - - query_all.current | length > 1 - - query_all.current.0.vzEntry is defined - - '"class/vzEntry.json" in query_all.url' - - -- name: query assertions for only Non-Cloud - assert: - that: - - query_tenant_filter.current.0.vzFilter.children | length == 4 - when: query_cloud.current == [] # This condition will skip execution for cloud sites - -- name: delete entry - check mode works - cisco.aci.aci_filter_entry: &aci_entry_absent - <<: *aci_entry_present - state: absent - check_mode: true - register: entry_absent_check_mode - -- name: delete entry - deletion works - cisco.aci.aci_filter_entry: - <<: *aci_entry_absent - register: entry_absent - -- name: delete entry - idempotency works - cisco.aci.aci_filter_entry: - <<: *aci_entry_absent - register: entry_absent_idempotent - -- name: missing param - failure message works - cisco.aci.aci_filter_entry: - <<: *aci_tenant_present - state: absent - ignore_errors: true - register: absent_missing_param - -- name: cleanup remaining entries - cisco.aci.aci_filter_entry: - <<: *aci_entry_absent - entry: "{{ item }}" - with_items: ["anstest2", "anstest3", "anstest4"] - -- name: absent assertions - assert: - that: - - entry_absent_check_mode is changed - - entry_absent_check_mode.previous != [] - - entry_absent is changed - - entry_absent.previous == entry_absent_check_mode.previous - - entry_absent.proposed == {} - - entry_absent_idempotent is not changed - - entry_absent_idempotent.previous == [] - - absent_missing_param is failed - - 'absent_missing_param.msg == "state is absent but all of the following are missing: entry, filter"' - -- name: cleanup filter - cisco.aci.aci_filter: - <<: *aci_filter_present - state: absent - when: filter_present is changed - -- name: cleanup tenant - cisco.aci.aci_tenant: - <<: *aci_tenant_present - state: absent - when: tenant_present is changed +# TODO current module will fail on cloud sites because range is not supported +# APIC Error 1: Invalid Configuration CLOUD_SOURCE_PORT_NOT_SUPPORTED: vz::EntryMo Dn = uni/tn-ansible_test/flt-anstest_fileter_2/e-source_port_start - Source port range is not allowed on CAPIC" +- name: Execute tasks only for non-cloud sites + when: query_cloud.current == [] # This condition will execute only non-cloud sites + block: # block specifies execution of tasks within, based on conditions + + - name: ensure tenant exists for tests to kick off + cisco.aci.aci_tenant: + <<: *aci_info + tenant: ansible_test + state: absent + + - name: ensure tenant exists for tests to kick off + cisco.aci.aci_tenant: &aci_tenant_present + <<: *aci_info + tenant: ansible_test + state: present + register: tenant_present + + - name: ensure filter exists for tests to kick off + cisco.aci.aci_filter: &aci_filter_present + <<: *aci_tenant_present + filter: anstest + register: filter_present + + - name: ensure anstest_2 filter exists for tests to kick off + cisco.aci.aci_filter: &anstest_fileter_2_present + <<: *aci_tenant_present + filter: anstest_fileter_2 + + - name: Create a filter entry with the match_only_fragments - enabled and dst_port values - negative test + cisco.aci.aci_filter_entry: + <<: *anstest_fileter_2_present + entry: nt_match_only_fragments_with_dst_port + ether_type: ip + ip_protocol: tcp + dst_port_start: 80 + dst_port_end: 88 + match_only_fragments: true + register: nt_match_only_fragments_with_dst_port + ignore_errors: true + + - name: Create a filter entry with the match_only_fragments - enabled + cisco.aci.aci_filter_entry: &match_only_fragments_enabled + <<: *anstest_fileter_2_present + entry: match_only_fragments_enabled + ether_type: ip + ip_protocol: tcp + match_only_fragments: true + register: match_only_fragments_enabled + + - name: Disabled the match_only_fragments of an existing filter entry - "match_only_fragments_enabled" + cisco.aci.aci_filter_entry: + <<: *match_only_fragments_enabled + match_only_fragments: false + register: match_only_fragments_disabled + + - name: Create a filter entry with the source_port values - negative test + cisco.aci.aci_filter_entry: + <<: *anstest_fileter_2_present + entry: nt_source_port + ether_type: ip + ip_protocol: tcp + source_port: 20 + source_port_start: 22 + source_port_end: 22 + register: nt_source_port + ignore_errors: true + + - name: Create a filter entry with the only dst_port_end - negative test + cisco.aci.aci_filter_entry: + <<: *anstest_fileter_2_present + entry: nt_dst_port_end + ether_type: ip + ip_protocol: tcp + dst_port_end: 20 + register: nt_dst_port_end + ignore_errors: true + + - name: Create a filter entry with the only source_port_end - negative test + cisco.aci.aci_filter_entry: + <<: *anstest_fileter_2_present + entry: nt_source_port_end + ether_type: ip + ip_protocol: tcp + source_port_end: 20 + register: nt_source_port_end + ignore_errors: true + + - name: Create a filter entry with the only source_port_start + cisco.aci.aci_filter_entry: + <<: *anstest_fileter_2_present + entry: source_port_start + ether_type: ip + ip_protocol: tcp + source_port_start: 20 + register: source_port_start + + - name: Create a filter entry with only source_port_start, source_port_end and valid tcp_flags rules + cisco.aci.aci_filter_entry: + <<: *anstest_fileter_2_present + entry: source_port_values + ether_type: ip + ip_protocol: tcp + source_port_start: 20 + source_port_end: 23 + tcp_flags: + - acknowledgment + - finish + register: source_port_values + + - name: Updated source port and tcp_flags values of an existing filter entry - "source_port_values" + cisco.aci.aci_filter_entry: + <<: *anstest_fileter_2_present + entry: source_port_values + ether_type: ip + ip_protocol: tcp + source_port: 53 + tcp_flags: + - acknowledgment + register: update_source_port_values + + - name: Create a filter entry with the tcp_flags - established and other tcp rules - negative test + cisco.aci.aci_filter_entry: + <<: *anstest_fileter_2_present + entry: nt_tcp_flags + ether_type: ip + ip_protocol: tcp + tcp_flags: + - acknowledgment + - established + - finish + register: nt_tcp_flags + ignore_errors: true + + - name: Create a filter entry with the tcp_flags - established + cisco.aci.aci_filter_entry: + <<: *anstest_fileter_2_present + entry: tcp_flags_est + ether_type: ip + ip_protocol: tcp + tcp_flags: + - established + register: tcp_flags_est + + - name: Create a filter entry with icmp6_msg_type - established + cisco.aci.aci_filter_entry: + <<: *anstest_fileter_2_present + entry: icmp6_msg_type_est + icmp6_msg_type: neighbor_solicitation + register: icmp6_msg_type_est + + - name: Assertion check for the filter entry - match_only_fragments, source_port and tcp_flags attributes + assert: + that: + - nt_match_only_fragments_with_dst_port is not changed + - nt_match_only_fragments_with_dst_port.msg == "Parameter 'match_only_fragments' cannot be used with 'Layer 4 Port' value" + - match_only_fragments_enabled is changed + - match_only_fragments_enabled.current.0.vzEntry.attributes.name == "match_only_fragments_enabled" + - match_only_fragments_enabled.current.0.vzEntry.attributes.tcpRules == match_only_fragments_enabled.current.0.vzEntry.attributes.tcpRules == "" + - match_only_fragments_enabled.current.0.vzEntry.attributes.applyToFrag == match_only_fragments_enabled.sent.vzEntry.attributes.applyToFrag == "yes" + - match_only_fragments_enabled.current.0.vzEntry.attributes.sFromPort == match_only_fragments_enabled.current.0.vzEntry.attributes.sToPort == "unspecified" + - match_only_fragments_enabled.current.0.vzEntry.attributes.dFromPort == match_only_fragments_enabled.current.0.vzEntry.attributes.dToPort == "unspecified" + - match_only_fragments_disabled is changed + - match_only_fragments_disabled.current.0.vzEntry.attributes.applyToFrag == match_only_fragments_disabled.sent.vzEntry.attributes.applyToFrag == "no" + - match_only_fragments_disabled.current.0.vzEntry.attributes.name == "match_only_fragments_enabled" + - match_only_fragments_disabled.current.0.vzEntry.attributes.tcpRules == "" + - match_only_fragments_disabled.current.0.vzEntry.attributes.sFromPort == match_only_fragments_disabled.current.0.vzEntry.attributes.sToPort == "unspecified" + - match_only_fragments_disabled.current.0.vzEntry.attributes.dFromPort == match_only_fragments_disabled.current.0.vzEntry.attributes.dToPort == "unspecified" + - nt_source_port is not changed + - nt_source_port.msg == "Parameter 'source_port' cannot be used with 'source_port_end' and 'source_port_start'" + - nt_dst_port_end is not changed + - nt_dst_port_end.msg == "Parameter 'dst_port_end' cannot be configured when the 'dst_port_start' is not defined" + - nt_source_port_end is not changed + - nt_source_port_end.msg == "Parameter 'source_port_end' cannot be configured when the 'source_port_start' is not defined" + - source_port_start is changed + - source_port_start.current.0.vzEntry.attributes.name == source_port_start.sent.vzEntry.attributes.name == "source_port_start" + - source_port_start.current.0.vzEntry.attributes.sFromPort == source_port_start.sent.vzEntry.attributes.sFromPort == "ftpData" + - source_port_start.current.0.vzEntry.attributes.sToPort == "ftpData" + - source_port_start.current.0.vzEntry.attributes.tcpRules == source_port_start.sent.vzEntry.attributes.tcpRules == "" + - source_port_start.current.0.vzEntry.attributes.applyToFrag == "no" + - source_port_start.current.0.vzEntry.attributes.arpOpc == "unspecified" + - source_port_start.current.0.vzEntry.attributes.etherT == "ip" + - source_port_start.current.0.vzEntry.attributes.prot == "tcp" + - source_port_values is changed + - source_port_values.current.0.vzEntry.attributes.name == source_port_values.sent.vzEntry.attributes.name == "source_port_values" + - source_port_values.current.0.vzEntry.attributes.sFromPort == source_port_values.sent.vzEntry.attributes.sFromPort == "ftpData" + - source_port_values.current.0.vzEntry.attributes.sToPort == source_port_values.sent.vzEntry.attributes.sToPort == "23" + - source_port_values.current.0.vzEntry.attributes.tcpRules == source_port_values.sent.vzEntry.attributes.tcpRules == "ack,fin" + - source_port_values.current.0.vzEntry.attributes.applyToFrag == "no" + - source_port_values.current.0.vzEntry.attributes.arpOpc == "unspecified" + - source_port_values.current.0.vzEntry.attributes.etherT == "ip" + - source_port_values.current.0.vzEntry.attributes.prot == "tcp" + - update_source_port_values is changed + - update_source_port_values.current.0.vzEntry.attributes.name == "source_port_values" + - update_source_port_values.current.0.vzEntry.attributes.applyToFrag == "no" + - update_source_port_values.current.0.vzEntry.attributes.arpOpc == "unspecified" + - update_source_port_values.current.0.vzEntry.attributes.etherT == "ip" + - update_source_port_values.current.0.vzEntry.attributes.prot == "tcp" + - update_source_port_values.current.0.vzEntry.attributes.sFromPort == update_source_port_values.sent.vzEntry.attributes.sFromPort == "dns" + - update_source_port_values.current.0.vzEntry.attributes.sToPort == update_source_port_values.sent.vzEntry.attributes.sToPort == "dns" + - update_source_port_values.current.0.vzEntry.attributes.tcpRules == update_source_port_values.sent.vzEntry.attributes.tcpRules == "ack" + - nt_tcp_flags is not changed + - nt_tcp_flags.msg == "TCP established cannot be combined with other tcp rules" + - tcp_flags_est is changed + - tcp_flags_est.current.0.vzEntry.attributes.applyToFrag == "no" + - tcp_flags_est.current.0.vzEntry.attributes.tcpRules == tcp_flags_est.sent.vzEntry.attributes.tcpRules == "est" + - tcp_flags_est.current.0.vzEntry.attributes.name == tcp_flags_est.sent.vzEntry.attributes.name == "tcp_flags_est" + - tcp_flags_est.current.0.vzEntry.attributes.etherT == tcp_flags_est.sent.vzEntry.attributes.etherT == "ip" + - icmp6_msg_type_est.current.0.vzEntry.attributes.icmpv6T == "nbr-solicit" + + - name: create filter entry - check mode works + cisco.aci.aci_filter_entry: &aci_entry_present + <<: *aci_filter_present + entry: anstest + description: Ansible Test + ether_type: ip + ip_protocol: tcp + dst_port_start: 80 + dst_port_end: 88 + check_mode: true + register: entry_present_check_mode + + - name: create filter entry - creation works + cisco.aci.aci_filter_entry: + <<: *aci_entry_present + register: entry_present + + - name: create filter entry - idempotency works + cisco.aci.aci_filter_entry: + <<: *aci_entry_present + register: entry_present_idempotent + + - name: update filter entry - update works + cisco.aci.aci_filter_entry: + <<: *aci_entry_present + description: Ansible Test Update + dst_port: 80 + dst_port_start: "{{ fake_var | default(omit) }}" + dst_port_end: "{{ fake_var | default(omit) }}" + register: entry_present_update + + - name: Verify Cloud and Non-Cloud Sites in use. + include_tasks: ../../../../../../integration/targets/aci_cloud_provider/tasks/main.yml + + - name: create filter entry - test different types + cisco.aci.aci_filter_entry: + <<: *aci_filter_present + entry: anstest2 + ether_type: arp + arp_flag: arp_reply + register: entry_present_2 + when: query_cloud.current == [] # This condition will skip execution for cloud sites + + - name: create filter entry - test different types + cisco.aci.aci_filter_entry: + <<: *aci_filter_present + entry: anstest3 + ether_type: ip + ip_protocol: icmp + icmp_msg_type: echo + register: entry_present_3 + + - name: create filter entry - test different types + cisco.aci.aci_filter_entry: + <<: *aci_filter_present + entry: anstest4 + ether_type: ip + ip_protocol: udp + dst_port: 1000 + register: entry_present_4 + + - name: missing param - failure message works + cisco.aci.aci_filter_entry: + <<: *aci_filter_present + ignore_errors: true + register: present_missing_param + + - name: incompatable params - failure message works + cisco.aci.aci_filter_entry: + <<: *aci_entry_present + dst_port: 99 + ignore_errors: true + register: present_incompatible_params + + - name: present assertions + assert: + that: + - entry_present_check_mode is changed + - entry_present_check_mode.previous == [] + - entry_present_check_mode.sent.vzEntry.attributes.dFromPort == 'http' + - entry_present_check_mode.sent.vzEntry.attributes.dToPort == '88' + - entry_present_check_mode.sent.vzEntry.attributes.descr == 'Ansible Test' + - entry_present_check_mode.sent.vzEntry.attributes.etherT == 'ip' + - entry_present_check_mode.sent.vzEntry.attributes.name == 'anstest' + - entry_present_check_mode.sent.vzEntry.attributes.prot == 'tcp' + - entry_present is changed + - entry_present.current.0.vzEntry.attributes.annotation == 'orchestrator:ansible' + - entry_present.previous == [] + - entry_present.sent == entry_present_check_mode.sent + - entry_present_idempotent is not changed + - entry_present_idempotent.previous != [] + - entry_present_idempotent.sent == {} + - entry_present_update is changed + - entry_present_update.previous != [] + - entry_present_update.sent != entry_present_update.proposed + - entry_present_3 is changed + - entry_present_3.sent.vzEntry.attributes.etherT == 'ip' + - entry_present_3.sent.vzEntry.attributes.icmpv4T == 'echo' + - entry_present_3.sent.vzEntry.attributes.name == 'anstest3' + - entry_present_3.sent.vzEntry.attributes.prot == 'icmp' + - entry_present_4 is changed + - entry_present_4.sent.vzEntry.attributes.dFromPort == '1000' + - entry_present_4.sent.vzEntry.attributes.dToPort == '1000' + - entry_present_4.sent.vzEntry.attributes.etherT == 'ip' + - entry_present_4.sent.vzEntry.attributes.name == 'anstest4' + - entry_present_4.sent.vzEntry.attributes.prot == 'udp' + - present_missing_param is failed + - 'present_missing_param.msg == "state is present but all of the following are missing: entry"' + - present_incompatible_params is failed + - present_incompatible_params.msg.startswith("Parameter") + + - name: present assertions for arp + assert: + that: + - entry_present_2 is changed + - entry_present_2.sent.vzEntry.attributes.arpOpc == 'reply' + - entry_present_2.sent.vzEntry.attributes.etherT == 'arp' + - entry_present_2.sent.vzEntry.attributes.name == 'anstest2' + when: query_cloud.current == [] # This condition will skip execution for cloud sites + + - name: query tenant filter entry + cisco.aci.aci_filter_entry: &aci_query_entry + <<: *aci_entry_present + state: query + register: query_tenant_filter_entry + + - name: query filter entry + cisco.aci.aci_filter_entry: + <<: *aci_query_entry + tenant: "{{ fakevar | default(omit) }}" + register: query_filter_entry + + - name: query tenant entry + cisco.aci.aci_filter_entry: + <<: *aci_query_entry + filter: "{{ fakevar | default(omit) }}" + register: query_tenant_entry + + - name: query tenant filter + cisco.aci.aci_filter_entry: + <<: *aci_query_entry + entry: "{{ fakevar | default(omit) }}" + register: query_tenant_filter + + - name: query entry + cisco.aci.aci_filter_entry: &aci_query_entry_2 + <<: *aci_query_entry + tenant: "{{ fakevar | default(omit) }}" + filter: "{{ fakevar | default(omit) }}" + register: query_entry + + - name: query filter + cisco.aci.aci_filter_entry: + <<: *aci_query_entry + tenant: "{{ fakevar | default(omit) }}" + entry: "{{ fakevar | default(omit) }}" + register: query_filter + + - name: query tenant + cisco.aci.aci_filter_entry: + <<: *aci_query_entry + filter: "{{ fakevar | default(omit) }}" + entry: "{{ fakevar | default(omit) }}" + register: query_tenant + + - name: query all + cisco.aci.aci_filter_entry: + <<: *aci_query_entry_2 + entry: "{{ fakevar | default(omit) }}" + register: query_all + + - name: query assertions for all + assert: + that: + - query_tenant_filter_entry is not changed + - query_tenant_filter_entry.current | length == 1 + - query_tenant_filter_entry.current.0.vzEntry.attributes.name == "anstest" + - '"tn-ansible_test/flt-anstest/e-anstest.json" in query_tenant_filter_entry.url' + - query_filter_entry is not changed + - query_filter_entry.current.0.vzFilter.attributes.name == "anstest" + - query_filter_entry.current.0.vzFilter.children | length == 1 + - '"query-target-filter=eq(vzFilter.name,\"anstest\")" in query_filter_entry.filter_string' + - '"rsp-subtree-filter=eq(vzEntry.name,\"anstest\")" in query_filter_entry.filter_string' + - '"class/vzFilter.json" in query_filter_entry.url' + - query_tenant_entry is not changed + - query_tenant_entry.current | length == 1 + - query_tenant_entry.current.0.fvTenant.attributes.name == "ansible_test" + - '"rsp-subtree-filter=eq(vzEntry.name,\"anstest\")" in query_tenant_entry.filter_string' + - '"rsp-subtree-class=vzEntry" in query_tenant_entry.filter_string' + - '"tn-ansible_test.json" in query_tenant_entry.url' + - query_tenant_filter is not changed + - query_tenant_filter.current | length == 1 + - query_tenant_filter.current.0.vzFilter.attributes.name == "anstest" + - '"rsp-subtree-class=vzEntry" in query_tenant_filter.filter_string' + - '"tn-ansible_test/flt-anstest.json" in query_tenant_filter.url' + - query_entry is not changed + - query_entry.current.0.vzEntry.attributes.name == "anstest" + - '"query-target-filter=eq(vzEntry.name,\"anstest\")" in query_entry.filter_string' + - '"class/vzEntry.json" in query_entry.url' + - query_filter is not changed + - query_filter.current.0.vzFilter.attributes.name == "anstest" + - '"query-target-filter=eq(vzFilter.name,\"anstest\")" in query_filter.filter_string' + - '"rsp-subtree-class=vzEntry" in query_filter.filter_string' + - '"class/vzFilter.json" in query_filter.url' + - query_tenant is not changed + - query_tenant.current | length == 1 + - query_tenant.current.0.fvTenant.attributes.name == "ansible_test" + - '"rsp-subtree-class=vzEntry,vzFilter" in query_tenant.filter_string' + - '"tn-ansible_test.json" in query_tenant.url' + - query_all is not changed + - query_all.current | length > 1 + - query_all.current.0.vzEntry is defined + - '"class/vzEntry.json" in query_all.url' + + + - name: query assertions for only Non-Cloud + assert: + that: + - query_tenant_filter.current.0.vzFilter.children | length == 4 + when: query_cloud.current == [] # This condition will skip execution for cloud sites + + - name: delete entry - check mode works + cisco.aci.aci_filter_entry: &aci_entry_absent + <<: *aci_entry_present + state: absent + check_mode: true + register: entry_absent_check_mode + + - name: delete entry - deletion works + cisco.aci.aci_filter_entry: + <<: *aci_entry_absent + register: entry_absent + + - name: delete entry - idempotency works + cisco.aci.aci_filter_entry: + <<: *aci_entry_absent + register: entry_absent_idempotent + + - name: missing param - failure message works + cisco.aci.aci_filter_entry: + <<: *aci_tenant_present + state: absent + ignore_errors: true + register: absent_missing_param + + - name: cleanup remaining entries + cisco.aci.aci_filter_entry: + <<: *aci_entry_absent + entry: "{{ item }}" + with_items: ["anstest2", "anstest3", "anstest4"] + + - name: absent assertions + assert: + that: + - entry_absent_check_mode is changed + - entry_absent_check_mode.previous != [] + - entry_absent is changed + - entry_absent.previous == entry_absent_check_mode.previous + - entry_absent.proposed == {} + - entry_absent_idempotent is not changed + - entry_absent_idempotent.previous == [] + - absent_missing_param is failed + - 'absent_missing_param.msg == "state is absent but all of the following are missing: entry, filter"' + + - name: cleanup filter + cisco.aci.aci_filter: + <<: *aci_filter_present + state: absent + when: filter_present is changed + + - name: cleanup tenant + cisco.aci.aci_tenant: + <<: *aci_tenant_present + state: absent + when: tenant_present is changed diff --git a/ansible_collections/cisco/aci/tests/integration/targets/aci_firmware_group/aliases b/ansible_collections/cisco/aci/tests/integration/targets/aci_firmware_group/aliases new file mode 100644 index 000000000..209b793f9 --- /dev/null +++ b/ansible_collections/cisco/aci/tests/integration/targets/aci_firmware_group/aliases @@ -0,0 +1,2 @@ +# No ACI simulator yet, so not enabled +# unsupported diff --git a/ansible_collections/cisco/aci/tests/integration/targets/aci_firmware_group/tasks/main.yml b/ansible_collections/cisco/aci/tests/integration/targets/aci_firmware_group/tasks/main.yml new file mode 100644 index 000000000..5cf88997c --- /dev/null +++ b/ansible_collections/cisco/aci/tests/integration/targets/aci_firmware_group/tasks/main.yml @@ -0,0 +1,154 @@ +# Test code for the ACI modules +# Copyright: (c) 2023, Gaspard Micol (@gmicol) <gmicol@cisco.com> + +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +- name: Test that we have an ACI APIC host, ACI username and ACI password + fail: + msg: 'Please define the following variables: aci_hostname, aci_username and aci_password.' + when: aci_hostname is not defined or aci_username is not defined or aci_password is not defined + +- name: Set vars + set_fact: + aci_info: &aci_info + host: "{{ aci_hostname }}" + username: "{{ aci_username }}" + password: "{{ aci_password }}" + validate_certs: '{{ aci_validate_certs | default(false) }}' + use_ssl: '{{ aci_use_ssl | default(true) }}' + use_proxy: '{{ aci_use_proxy | default(true) }}' + output_level: debug + +- name: Verify Cloud and Non-Cloud Sites in use. + include_tasks: ../../../../../../integration/targets/aci_cloud_provider/tasks/main.yml + +- name: Execute tasks only for non-cloud sites + when: query_cloud.current == [] # This condition will execute only non-cloud sites + block: # block specifies execution of tasks within, based on conditions + # Clean Environment + - name: Create firmware policy + aci_firmware_policy: &aci_firmware_policy_present + <<: *aci_info + name: anstest_policy + version: n9000-15.2(7) + state: present + + - name: Ensure first firmware group does not exist + aci_firmware_group: &aci_firmware_group_absent + <<: *aci_info + group: anstest_group + policy: anstest_policy + type_group: all + description: test firmware group + state: absent + + - name: Ensure second firmware group does not exist + aci_firmware_group: &aci_firmware_group_2_absent + <<: *aci_info + group: anstest_group_2 + policy: anstest_policy + state: absent + + - name: Create first firmware group (check_mode) + aci_firmware_group: &aci_firmware_group_present + <<: *aci_firmware_group_absent + state: present + check_mode: true + register: cm_add_firmware_group_1 + + - name: Create first firmware group (normal_mode) + aci_firmware_group: + <<: *aci_firmware_group_present + register: nm_add_firmware_group_1 + + - name: Create first firmware group again - testing idempotency + aci_firmware_group: + <<: *aci_firmware_group_present + register: idempotency_add_firmware_group_1 + + - name: Create second firmware group + aci_firmware_group: &aci_firmware_group_2_present + <<: *aci_firmware_group_2_absent + state: present + register: nm_add_firmware_group_2 + + - name: Asserts for creation tasks + assert: + that: + - cm_add_firmware_group_1 is changed + - cm_add_firmware_group_1.previous == [] + - cm_add_firmware_group_1.current == [] + - nm_add_firmware_group_1 is changed + - nm_add_firmware_group_1.current.0.firmwareFwGrp.attributes.name == "anstest_group" + - nm_add_firmware_group_1.current.0.firmwareFwGrp.attributes.type == "ALL" + - nm_add_firmware_group_1.current.0.firmwareFwGrp.children.0.firmwareRsFwgrpp.attributes.tnFirmwareFwPName == "anstest_policy" + - idempotency_add_firmware_group_1 is not changed + - nm_add_firmware_group_2 is changed + + - name: Query all firmware groups + aci_firmware_group: + <<: *aci_info + state: query + register: query_all_firmware_group + + - name: Query first firmware group + aci_firmware_group: + <<: *aci_firmware_group_present + state: query + register: query_first_firmware_group + + - name: Asserts for query tasks + assert: + that: + - query_all_firmware_group is not changed + - query_all_firmware_group.current | length >= 2 + - '"firmwareRsFwgrpp" in query_all_firmware_group.filter_string' + - '"class/firmwareFwGrp.json" in query_all_firmware_group.url' + - query_first_firmware_group is not changed + - query_first_firmware_group.current.0.firmwareFwGrp.attributes.name == "anstest_group" + - query_first_firmware_group.current.0.firmwareFwGrp.attributes.type == "ALL" + - query_first_firmware_group.current.0.firmwareFwGrp.children.0.firmwareRsFwgrpp.attributes.tnFirmwareFwPName == "anstest_policy" + + - name: Delete first firmware group (check_mode) + aci_firmware_group: + <<: *aci_firmware_group_present + state: absent + check_mode: true + register: cm_delete_firmware_group_1 + + - name: Delete first firmware group (normal_mode) + aci_firmware_group: + <<: *aci_firmware_group_present + state: absent + register: nm_delete_firmware_group_1 + + - name: Delete first firmware group again - testing idempotency + aci_firmware_group: + <<: *aci_firmware_group_present + state: absent + register: idempotency_delete_firmware_group_1 + + - name: Delete second firmware group (normal_mode) + aci_firmware_group: + <<: *aci_firmware_group_2_present + state: absent + register: nm_delete_firmware_group_2 + + - name: Asserts for deletion tasks + assert: + that: + - cm_delete_firmware_group_1 is changed + - cm_delete_firmware_group_1.proposed == {} + - nm_delete_firmware_group_1 is changed + - nm_delete_firmware_group_1.previous != [] + - nm_delete_firmware_group_1.current == [] + - idempotency_delete_firmware_group_1 is not changed + - idempotency_delete_firmware_group_1.previous == [] + - nm_delete_firmware_group_2 is changed + - nm_delete_firmware_group_2.previous != [] + - nm_delete_firmware_group_2.current == [] + + - name: Delete firmware policy - clean up the environment + aci_firmware_policy: + <<: *aci_firmware_policy_present + state: absent diff --git a/ansible_collections/cisco/aci/tests/integration/targets/aci_firmware_group_node/aliases b/ansible_collections/cisco/aci/tests/integration/targets/aci_firmware_group_node/aliases new file mode 100644 index 000000000..209b793f9 --- /dev/null +++ b/ansible_collections/cisco/aci/tests/integration/targets/aci_firmware_group_node/aliases @@ -0,0 +1,2 @@ +# No ACI simulator yet, so not enabled +# unsupported diff --git a/ansible_collections/cisco/aci/tests/integration/targets/aci_firmware_group_node/tasks/main.yml b/ansible_collections/cisco/aci/tests/integration/targets/aci_firmware_group_node/tasks/main.yml new file mode 100644 index 000000000..8da20a7ca --- /dev/null +++ b/ansible_collections/cisco/aci/tests/integration/targets/aci_firmware_group_node/tasks/main.yml @@ -0,0 +1,164 @@ +# Test code for the ACI modules +# Copyright: (c) 2023, Gaspard Micol (@gmicol) <gmicol@cisco.com> + +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +- name: Test that we have an ACI APIC host, ACI username and ACI password + fail: + msg: 'Please define the following variables: aci_hostname, aci_username and aci_password.' + when: aci_hostname is not defined or aci_username is not defined or aci_password is not defined + +- name: Set vars + set_fact: + aci_info: &aci_info + host: "{{ aci_hostname }}" + username: "{{ aci_username }}" + password: "{{ aci_password }}" + validate_certs: '{{ aci_validate_certs | default(false) }}' + use_ssl: '{{ aci_use_ssl | default(true) }}' + use_proxy: '{{ aci_use_proxy | default(true) }}' + output_level: debug + +- name: Verify Cloud and Non-Cloud Sites in use. + include_tasks: ../../../../../../integration/targets/aci_cloud_provider/tasks/main.yml + +- name: Execute tasks only for non-cloud sites + when: query_cloud.current == [] # This condition will execute only non-cloud sites + block: # block specifies execution of tasks within, based on conditions + # Clean Environment + - name: Create firmware policy exists + aci_firmware_policy: &aci_firmware_policy_present + <<: *aci_info + name: anstest_policy + version: n9000-15.2(7) + state: present + + - name: Create first firmware group + aci_firmware_group: &aci_firmware_group_present + <<: *aci_info + group: anstest_group + firmwarepol: anstest_policy + state: present + + - name: Ensure first firmware group node does not exist + aci_firmware_group_node: &aci_firmware_group_node_absent + <<: *aci_info + group: anstest_group + node: 1001 + state: absent + + - name: Ensure second firmware group node does not exist + aci_firmware_group_node: &aci_firmware_group_node_2_absent + <<: *aci_info + group: anstest_group + node: 1002 + state: absent + + - name: Create first firmware group node (check_mode) + aci_firmware_group_node: &aci_firmware_group_node_present + <<: *aci_firmware_group_node_absent + state: present + check_mode: true + register: cm_add_firmware_group_node_1 + + - name: Create first firmware group node (normal_mode) + aci_firmware_group_node: + <<: *aci_firmware_group_node_present + register: nm_add_firmware_group_node_1 + + - name: Create first firmware group node again - testing idempotency + aci_firmware_group_node: + <<: *aci_firmware_group_node_present + register: idempotency_add_firmware_group_node_1 + + - name: Create second firmware group node + aci_firmware_group_node: &aci_firmware_group_node_2_present + <<: *aci_firmware_group_node_2_absent + state: present + register: nm_add_firmware_group_node_2 + + - name: Asserts for creation tasks + assert: + that: + - cm_add_firmware_group_node_1 is changed + - cm_add_firmware_group_node_1.previous == [] + - cm_add_firmware_group_node_1.current == [] + - nm_add_firmware_group_node_1 is changed + - nm_add_firmware_group_node_1.current.0.fabricNodeBlk.attributes.from_ == "1001" + - nm_add_firmware_group_node_1.current.0.fabricNodeBlk.attributes.to_ == "1001" + - idempotency_add_firmware_group_node_1 is not changed + - nm_add_firmware_group_node_2 is changed + - nm_add_firmware_group_node_2.current.0.fabricNodeBlk.attributes.from_ == "1002" + - nm_add_firmware_group_node_2.current.0.fabricNodeBlk.attributes.to_ == "1002" + + + - name: Query all firmware group nodes + aci_firmware_group_node: + <<: *aci_info + state: query + register: query_all_firmware_group_node + + - name: Query first firmware group nnode + aci_firmware_group_node: + <<: *aci_firmware_group_node_present + state: query + register: query_first_firmware_group_node + + - name: Asserts for query tasks + assert: + that: + - query_all_firmware_group_node is not changed + - query_all_firmware_group_node.current | length >= 2 + - '"class/fabricNodeBlk.json" in query_all_firmware_group_node.url' + - query_first_firmware_group_node is not changed + - query_first_firmware_group_node.current.0.fabricNodeBlk.attributes.from_ == "1001" + - query_first_firmware_group_node.current.0.fabricNodeBlk.attributes.to_ == "1001" + + - name: Delete first firmware group (check_mode) + aci_firmware_group_node: + <<: *aci_firmware_group_node_present + state: absent + check_mode: true + register: cm_delete_firmware_group_node_1 + + - name: Delete first firmware group (normal_mode) + aci_firmware_group_node: + <<: *aci_firmware_group_node_present + state: absent + register: nm_delete_firmware_group_node_1 + + - name: Delete first firmware group again - testing idempotency + aci_firmware_group_node: + <<: *aci_firmware_group_node_present + state: absent + register: idempotency_delete_firmware_group_1 + + - name: Delete second firmware group (normal_mode) + aci_firmware_group_node: + <<: *aci_firmware_group_node_2_present + state: absent + register: nm_delete_firmware_group_node_2 + + - name: Asserts for deletion tasks + assert: + that: + - cm_delete_firmware_group_node_1 is changed + - cm_delete_firmware_group_node_1.proposed == {} + - nm_delete_firmware_group_node_1 is changed + - nm_delete_firmware_group_node_1.previous != [] + - nm_delete_firmware_group_node_1.current == [] + - idempotency_delete_firmware_group_1 is not changed + - idempotency_delete_firmware_group_1.previous == [] + - nm_delete_firmware_group_node_2 is changed + - nm_delete_firmware_group_node_2.previous != [] + - nm_delete_firmware_group_node_2.current == [] + + - name: Delete firmware group - clean up the environment + aci_firmware_group: + <<: *aci_firmware_group_present + state: absent + + - name: Delete firmware policy - clean up the environment + aci_firmware_policy: + <<: *aci_firmware_policy_present + state: absent diff --git a/ansible_collections/cisco/aci/tests/integration/targets/aci_firmware_policy/aliases b/ansible_collections/cisco/aci/tests/integration/targets/aci_firmware_policy/aliases new file mode 100644 index 000000000..209b793f9 --- /dev/null +++ b/ansible_collections/cisco/aci/tests/integration/targets/aci_firmware_policy/aliases @@ -0,0 +1,2 @@ +# No ACI simulator yet, so not enabled +# unsupported diff --git a/ansible_collections/cisco/aci/tests/integration/targets/aci_firmware_policy/tasks/main.yml b/ansible_collections/cisco/aci/tests/integration/targets/aci_firmware_policy/tasks/main.yml new file mode 100644 index 000000000..ec0bb88ae --- /dev/null +++ b/ansible_collections/cisco/aci/tests/integration/targets/aci_firmware_policy/tasks/main.yml @@ -0,0 +1,143 @@ +# Test code for the ACI modules +# Copyright: (c) 2023, Gaspard Micol (@gmicol) <gmicol@cisco.com> + +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +- name: Test that we have an ACI APIC host, ACI username and ACI password + fail: + msg: 'Please define the following variables: aci_hostname, aci_username and aci_password.' + when: aci_hostname is not defined or aci_username is not defined or aci_password is not defined + +- name: Set vars + set_fact: + aci_info: &aci_info + host: "{{ aci_hostname }}" + username: "{{ aci_username }}" + password: "{{ aci_password }}" + validate_certs: '{{ aci_validate_certs | default(false) }}' + use_ssl: '{{ aci_use_ssl | default(true) }}' + use_proxy: '{{ aci_use_proxy | default(true) }}' + output_level: debug + +- name: Verify Cloud and Non-Cloud Sites in use. + include_tasks: ../../../../../../integration/targets/aci_cloud_provider/tasks/main.yml + +- name: Execute tasks only for non-cloud sites + when: query_cloud.current == [] # This condition will execute only non-cloud sites + block: # block specifies execution of tasks within, based on conditions + # Clean Environment + - name: Ensure first firmware policy does not exist + aci_firmware_policy: &aci_firmware_policy_absent + <<: *aci_info + name: anstest_policy + version: n9000-15.2(7) + ignore_compat: true + state: absent + + - name: Ensure second firmware policy does not exist + aci_firmware_policy: &aci_firmware_policy_2_absent + <<: *aci_info + name: anstest_policy_2 + version: n9000-16.0(1) + state: absent + + - name: Create first firmware policy (check_mode) + aci_firmware_policy: &aci_firmware_policy_present + <<: *aci_firmware_policy_absent + state: present + check_mode: true + register: cm_add_firmware_policy_1 + + - name: Create first firmware policy (normal_mode) + aci_firmware_policy: + <<: *aci_firmware_policy_present + register: nm_add_firmware_policy_1 + + - name: Create first firmware policy again - testing idempotency + aci_firmware_policy: + <<: *aci_firmware_policy_present + register: idempotency_add_firmware_policy_1 + + - name: Create second firmware policy + aci_firmware_policy: &aci_firmware_policy_2_present + <<: *aci_firmware_policy_2_absent + state: present + register: nm_add_firmware_policy_2 + + - name: Asserts for creation tasks + assert: + that: + - cm_add_firmware_policy_1 is changed + - cm_add_firmware_policy_1.previous == [] + - cm_add_firmware_policy_1.current == [] + - nm_add_firmware_policy_1 is changed + - nm_add_firmware_policy_1.current.0.firmwareFwP.attributes.name == "anstest_policy" + - nm_add_firmware_policy_1.current.0.firmwareFwP.attributes.ignoreCompat == "yes" + - nm_add_firmware_policy_1.current.0.firmwareFwP.attributes.version == "n9000-15.2(7)" + - idempotency_add_firmware_policy_1 is not changed + - nm_add_firmware_policy_2 is changed + - nm_add_firmware_policy_2.current.0.firmwareFwP.attributes.name == "anstest_policy_2" + - nm_add_firmware_policy_2.current.0.firmwareFwP.attributes.ignoreCompat == "no" + - nm_add_firmware_policy_2.current.0.firmwareFwP.attributes.version == "n9000-16.0(1)" + + - name: Query all firmware policies + aci_firmware_policy: + <<: *aci_info + state: query + register: query_all_firmware_policy + + - name: Query first firmware policy + aci_firmware_policy: + <<: *aci_firmware_policy_present + state: query + register: query_first_firmware_policy + + - name: Asserts for query tasks + assert: + that: + - query_all_firmware_policy is not changed + - query_all_firmware_policy.current | length >= 2 + - '"class/firmwareFwP.json" in query_all_firmware_policy.url' + - query_first_firmware_policy is not changed + - query_first_firmware_policy.current.0.firmwareFwP.attributes.name == "anstest_policy" + - query_first_firmware_policy.current.0.firmwareFwP.attributes.ignoreCompat == "yes" + - query_first_firmware_policy.current.0.firmwareFwP.attributes.version == "n9000-15.2(7)" + + - name: Delete first firmware policy (check_mode) + aci_firmware_policy: + <<: *aci_firmware_policy_present + state: absent + check_mode: true + register: cm_delete_firmware_policy_1 + + - name: Delete first firmware policy (normal_mode) + aci_firmware_policy: + <<: *aci_firmware_policy_present + state: absent + register: nm_delete_firmware_policy_1 + + - name: Delete first firmware policy again - testing idempotency + aci_firmware_policy: + <<: *aci_firmware_policy_present + state: absent + register: idempotency_delete_firmware_policy_1 + + - name: Delete second firmware policy (normal_mode) + aci_firmware_policy: + <<: *aci_firmware_policy_2_present + state: absent + register: nm_delete_firmware_policy_2 + + - name: Asserts for deletion tasks + assert: + that: + - cm_delete_firmware_policy_1 is changed + - cm_delete_firmware_policy_1.proposed == {} + - nm_delete_firmware_policy_1 is changed + - nm_delete_firmware_policy_1.previous != [] + - nm_delete_firmware_policy_1.current == [] + - idempotency_delete_firmware_policy_1 is not changed + - idempotency_delete_firmware_policy_1.previous == [] + - nm_delete_firmware_policy_2 is changed + - nm_delete_firmware_policy_2.previous != [] + - nm_delete_firmware_policy_2.current == [] diff --git a/ansible_collections/cisco/aci/tests/integration/targets/aci_interface_blacklist/tasks/main.yml b/ansible_collections/cisco/aci/tests/integration/targets/aci_interface_blacklist/tasks/main.yml index d7b125267..17e0bb67c 100644 --- a/ansible_collections/cisco/aci/tests/integration/targets/aci_interface_blacklist/tasks/main.yml +++ b/ansible_collections/cisco/aci/tests/integration/targets/aci_interface_blacklist/tasks/main.yml @@ -45,6 +45,40 @@ - name: Execute tasks only for non-cloud sites when: query_cloud.current == [] # This condition will execute only non-cloud sites block: # block specifies execution of tasks within, based on conditions + + - name: Query blacklisted interfaces + cisco.aci.aci_interface_blacklist: + <<: *aci_info + state: query + register: enable_and_clear + + - name: set regex + set_fact: + regexp: '(topology/pod-)(\d)(/paths-)(\d*)(/pathep-\[eth)(.*)(])' + + - name: Save Target DNs + set_fact: + tdn: "{{ item.fabricRsOosPath.attributes.tDn }}" + loop: "{{ enable_and_clear.current }}" + register: enabled_tdn + + - name: Enable interfaces that were blacklisted + cisco.aci.aci_interface_blacklist: + <<: *aci_info + pod_id: "{{ item.ansible_facts.tdn | regex_search(regexp, '\\2') | first }}" + node_id: "{{ item.ansible_facts.tdn | regex_search(regexp, '\\4') | first }}" + interface: "{{ item.ansible_facts.tdn | regex_search(regexp, '\\6') | first }}" + state: absent + loop: "{{ enabled_tdn.results }}" + + - name: Ensure Interfaces are absent - Clean up test environment + cisco.aci.aci_interface_config: + <<: *aci_info + node: "{{ item.ansible_facts.tdn | regex_search(regexp, '\\4') | first }}" + interface: "{{ item.ansible_facts.tdn | regex_search(regexp, '\\6') | first }}" + state: absent + loop: "{{ enabled_tdn.results }}" + - name: Spine - Clean test environment with enabled interface cisco.aci.aci_interface_blacklist: <<: *aci_info diff --git a/ansible_collections/cisco/aci/tests/integration/targets/aci_interface_config/tasks/main.yml b/ansible_collections/cisco/aci/tests/integration/targets/aci_interface_config/tasks/main.yml index 75253bd40..3e8ebcb82 100644 --- a/ansible_collections/cisco/aci/tests/integration/targets/aci_interface_config/tasks/main.yml +++ b/ansible_collections/cisco/aci/tests/integration/targets/aci_interface_config/tasks/main.yml @@ -42,7 +42,7 @@ role: leaf port_type: access interface_type: switch_port - interface: "1/1/1" + interface: "1/1" state: absent register: invalid_interface_absent ignore_errors: true @@ -68,7 +68,7 @@ policy_group: ans_test_switch_port node: 501 breakout: "100g-4x" - interface: "1/1/1" + interface: "1/1" state: present register: invalid_policy_group_with_breakout ignore_errors: true @@ -86,7 +86,7 @@ cisco.aci.aci_interface_config: <<: *aci_info node: 5000 - interface: "1/1/1" + interface: "1/1" state: present register: invalid_node ignore_errors: true @@ -141,7 +141,7 @@ - invalid_sub_port is not changed - invalid_sub_port.msg is match("^Sub Port ID{{':'}} .+? is invalid; it must be in the range of 0 to 16.") - - name: Ensure Interface 501 with leaf access with policy_group - switch_port absent + - name: Ensure Interface 501 with leaf access with policy_group and without sub port - switch_port absent cisco.aci.aci_interface_config: &interface_501_absent <<: *aci_info role: leaf @@ -149,20 +149,20 @@ interface_type: switch_port policy_group: ans_test_switch_port node: 501 - interface: "1/1/1" + interface: "1/1" description: "Interface - 501 added by Ansible" admin_state: "down" state: absent register: interface_501_absent - - name: Ensure Interface 501 with leaf access with policy_group - switch_port present - check mode + - name: Ensure Interface 501 with leaf access with policy_group and without sub port - switch_port present - check mode cisco.aci.aci_interface_config: &cm_interface_501_present <<: *interface_501_absent state: present check_mode: true register: cm_interface_501_present - - name: Ensure Interface 501 with leaf access with policy_group - switch_port present - normal mode + - name: Ensure Interface 501 with leaf access with policy_group and without sub port - switch_port present - normal mode cisco.aci.aci_interface_config: &nm_interface_501_present <<: *interface_501_absent state: present @@ -178,13 +178,13 @@ - nm_interface_501_present.current != [] - nm_interface_501_present.current.0.infraPortConfig.attributes.assocGrp == "uni/infra/funcprof/accportgrp-ans_test_switch_port" - nm_interface_501_present.current.0.infraPortConfig.attributes.brkoutMap == "none" - - nm_interface_501_present.current.0.infraPortConfig.attributes.dn == "uni/infra/portconfnode-501-card-1-port-1-sub-1" + - nm_interface_501_present.current.0.infraPortConfig.attributes.dn == "uni/infra/portconfnode-501-card-1-port-1-sub-0" - nm_interface_501_present.current.0.infraPortConfig.attributes.node == "501" - nm_interface_501_present.current.0.infraPortConfig.attributes.role == "leaf" - nm_interface_501_present.current.0.infraPortConfig.attributes.shutdown == "yes" - nm_interface_501_present.current.0.infraPortConfig.attributes.card == "1" - nm_interface_501_present.current.0.infraPortConfig.attributes.port == "1" - - nm_interface_501_present.current.0.infraPortConfig.attributes.subPort == "1" + - nm_interface_501_present.current.0.infraPortConfig.attributes.subPort == "0" - name: Ensure Interface 502 with leaf access with policy_group - pc_or_vpc absent cisco.aci.aci_interface_config: &interface_502_absent @@ -461,7 +461,7 @@ policy_group: ans_test_switch_port breakout: "100g-4x" node: 501 - interface: "1/1/1" + interface: "1/1" description: "Interface - 501 added by Ansible" state: present register: invalid_breakout_501_present @@ -475,7 +475,7 @@ interface_type: switch_port breakout: "100g-4x" node: 501 - interface: "1/1/1" + interface: "1/1" description: "Interface - 501 added by Ansible" admin_state: "down" state: present @@ -504,14 +504,14 @@ - nm_valid_breakout_501_present.current != [] - nm_valid_breakout_501_present.current.0.infraPortConfig.attributes.brkoutMap == "100g-4x" - nm_valid_breakout_501_present.current.0.infraPortConfig.attributes.description == "Interface - 501 added by Ansible" - - nm_valid_breakout_501_present.current.0.infraPortConfig.attributes.dn == "uni/infra/portconfnode-501-card-1-port-1-sub-1" + - nm_valid_breakout_501_present.current.0.infraPortConfig.attributes.dn == "uni/infra/portconfnode-501-card-1-port-1-sub-0" - nm_valid_breakout_501_present.current.0.infraPortConfig.attributes.role == "leaf" - nm_valid_breakout_501_present.current.0.infraPortConfig.attributes.shutdown == "yes" - idm_breakout_501_present is not changed - idm_breakout_501_present.current != [] - idm_breakout_501_present.current.0.infraPortConfig.attributes.brkoutMap == "100g-4x" - idm_breakout_501_present.current.0.infraPortConfig.attributes.description == "Interface - 501 added by Ansible" - - idm_breakout_501_present.current.0.infraPortConfig.attributes.dn == "uni/infra/portconfnode-501-card-1-port-1-sub-1" + - idm_breakout_501_present.current.0.infraPortConfig.attributes.dn == "uni/infra/portconfnode-501-card-1-port-1-sub-0" - idm_breakout_501_present.current.0.infraPortConfig.attributes.role == "leaf" - idm_breakout_501_present.current.0.infraPortConfig.attributes.shutdown == "yes" # Breakup part ends @@ -527,7 +527,7 @@ assert: that: - query_all_access_interfaces is not changed - - query_all_access_interfaces.current|length >= 6 + - query_all_access_interfaces.current|length >= 5 - name: Query all fabric interfaces cisco.aci.aci_interface_config: @@ -678,3 +678,19 @@ that: - query_interface_509 is not changed - query_interface_509.current == [] + + - name: Ensure Interfaces 50* are absent - Clean up test environment + cisco.aci.aci_interface_config: + <<: *aci_info + node: "{{ item.node }}" + interface: "{{ item.interface }}" + state: absent + with_items: + - { node: "501", interface: "1/1/1" } + - { node: "502", interface: "2/2/2" } + - { node: "503", interface: "1/1/1" } + - { node: "505", interface: "5/5/5" } + - { node: "506", interface: "6/6/6" } + - { node: "507", interface: "7/7/7" } + - { node: "508", interface: "8/8/8" } + - { node: "509", interface: "9/9/9" } diff --git a/ansible_collections/cisco/aci/tests/integration/targets/aci_interface_policy_fc/aliases b/ansible_collections/cisco/aci/tests/integration/targets/aci_interface_policy_fc/aliases new file mode 100644 index 000000000..209b793f9 --- /dev/null +++ b/ansible_collections/cisco/aci/tests/integration/targets/aci_interface_policy_fc/aliases @@ -0,0 +1,2 @@ +# No ACI simulator yet, so not enabled +# unsupported diff --git a/ansible_collections/cisco/aci/tests/integration/targets/aci_interface_policy_fc/tasks/main.yml b/ansible_collections/cisco/aci/tests/integration/targets/aci_interface_policy_fc/tasks/main.yml new file mode 100644 index 000000000..5e039380e --- /dev/null +++ b/ansible_collections/cisco/aci/tests/integration/targets/aci_interface_policy_fc/tasks/main.yml @@ -0,0 +1,160 @@ +# Test code for the ACI modules +# Copyright: (c) 2023, Gaspard Micol (@gmicol) <gmicol@cisco.com> + +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +- name: Test that we have an ACI APIC host, ACI username and ACI password + fail: + msg: 'Please define the following variables: aci_hostname, aci_username and aci_password.' + when: aci_hostname is not defined or aci_username is not defined or aci_password is not defined + +- name: Set vars + set_fact: + aci_info: &aci_info + host: "{{ aci_hostname }}" + username: "{{ aci_username }}" + password: "{{ aci_password }}" + validate_certs: '{{ aci_validate_certs | default(false) }}' + use_ssl: '{{ aci_use_ssl | default(true) }}' + use_proxy: '{{ aci_use_proxy | default(true) }}' + output_level: debug + +- name: Verify Cloud and Non-Cloud Sites in use. + include_tasks: ../../../../../../integration/targets/aci_cloud_provider/tasks/main.yml + +- name: Execute tasks only for non-cloud sites + when: query_cloud.current == [] # This condition will execute only non-cloud sites + block: # block specifies execution of tasks within, based on conditions + # Clean Environment + - name: Ensure first fc interface policy does not exist + aci_interface_policy_fc: &aci_interface_policy_fc_absent + <<: *aci_info + name: anstest_policy + description: test for fc interface policy + port_mode: np + auto_max_speed: 16G + fill_pattern: arbff + buffer_credits: 32 + speed: 8G + trunk_mode: trunk-on + state: absent + + - name: Ensure second fc interface policy does not exist + aci_interface_policy_fc: &aci_interface_policy_fc_2_absent + <<: *aci_info + name: anstest_policy_2 + state: absent + + - name: Create first fc interface policy (check_mode) + aci_interface_policy_fc: &aci_interface_policy_fc_present + <<: *aci_interface_policy_fc_absent + state: present + check_mode: true + register: cm_add_interface_policy_fc_1 + + - name: Create first fc interface policy (normal_mode) + aci_interface_policy_fc: + <<: *aci_interface_policy_fc_present + register: nm_add_interface_policy_fc_1 + + - name: Create first fc interface policy again - testing idempotency + aci_interface_policy_fc: + <<: *aci_interface_policy_fc_present + register: idempotency_add_interface_policy_fc_1 + + - name: Create second fc interface policy + aci_interface_policy_fc: &aci_interface_policy_fc_2_present + <<: *aci_interface_policy_fc_2_absent + state: present + register: nm_add_interface_policy_fc_2 + + - name: Asserts for creation tasks + assert: + that: + - cm_add_interface_policy_fc_1 is changed + - cm_add_interface_policy_fc_1.previous == [] + - cm_add_interface_policy_fc_1.current == [] + - nm_add_interface_policy_fc_1 is changed + - nm_add_interface_policy_fc_1.current.0.fcIfPol.attributes.name == "anstest_policy" + - nm_add_interface_policy_fc_1.current.0.fcIfPol.attributes.portMode == "np" + - nm_add_interface_policy_fc_1.current.0.fcIfPol.attributes.automaxspeed == "16G" + - nm_add_interface_policy_fc_1.current.0.fcIfPol.attributes.fillPattern == "ARBFF" + - nm_add_interface_policy_fc_1.current.0.fcIfPol.attributes.rxBBCredit == "32" + - nm_add_interface_policy_fc_1.current.0.fcIfPol.attributes.speed == "8G" + - nm_add_interface_policy_fc_1.current.0.fcIfPol.attributes.trunkMode == "trunk-on" + - idempotency_add_interface_policy_fc_1 is not changed + - nm_add_interface_policy_fc_2 is changed + - nm_add_interface_policy_fc_2.current.0.fcIfPol.attributes.name == "anstest_policy_2" + - nm_add_interface_policy_fc_2.current.0.fcIfPol.attributes.portMode == "f" + - nm_add_interface_policy_fc_2.current.0.fcIfPol.attributes.automaxspeed == "32G" + - nm_add_interface_policy_fc_2.current.0.fcIfPol.attributes.fillPattern == "IDLE" + - nm_add_interface_policy_fc_2.current.0.fcIfPol.attributes.rxBBCredit == "64" + - nm_add_interface_policy_fc_2.current.0.fcIfPol.attributes.speed == "auto" + - nm_add_interface_policy_fc_2.current.0.fcIfPol.attributes.trunkMode == "trunk-off" + - idempotency_add_interface_policy_fc_1 is not changed + + - name: Query all fc interface policies + aci_interface_policy_fc: + <<: *aci_info + state: query + register: query_all_interface_policy_fc + + - name: Query first fc interface policy + aci_interface_policy_fc: + <<: *aci_interface_policy_fc_present + state: query + register: query_first_interface_policy_fc + + - name: Asserts for query tasks + assert: + that: + - query_all_interface_policy_fc is not changed + - query_all_interface_policy_fc.current | length >= 2 + - '"class/fcIfPol.json" in query_all_interface_policy_fc.url' + - query_first_interface_policy_fc is not changed + - query_first_interface_policy_fc.current.0.fcIfPol.attributes.name == "anstest_policy" + - query_first_interface_policy_fc.current.0.fcIfPol.attributes.portMode == "np" + - query_first_interface_policy_fc.current.0.fcIfPol.attributes.automaxspeed == "16G" + - query_first_interface_policy_fc.current.0.fcIfPol.attributes.fillPattern == "ARBFF" + - query_first_interface_policy_fc.current.0.fcIfPol.attributes.rxBBCredit == "32" + - query_first_interface_policy_fc.current.0.fcIfPol.attributes.speed == "8G" + - query_first_interface_policy_fc.current.0.fcIfPol.attributes.trunkMode == "trunk-on" + + - name: Delete first fc interface policy (check_mode) + aci_interface_policy_fc: + <<: *aci_interface_policy_fc_present + state: absent + check_mode: true + register: cm_delete_interface_policy_fc_1 + + - name: Delete first fc interface policy (normal_mode) + aci_interface_policy_fc: + <<: *aci_interface_policy_fc_present + state: absent + register: nm_delete_interface_policy_fc_1 + + - name: Delete first fc interface policy again - testing idempotency + aci_interface_policy_fc: + <<: *aci_interface_policy_fc_present + state: absent + register: idempotency_delete_interface_policy_fc_1 + + - name: Delete second fc interface policy (normal_mode) + aci_interface_policy_fc: + <<: *aci_interface_policy_fc_2_present + state: absent + register: nm_delete_interface_policy_fc_2 + + - name: Asserts for deletion tasks + assert: + that: + - cm_delete_interface_policy_fc_1 is changed + - cm_delete_interface_policy_fc_1.proposed == {} + - nm_delete_interface_policy_fc_1 is changed + - nm_delete_interface_policy_fc_1.previous != [] + - nm_delete_interface_policy_fc_1.current == [] + - idempotency_delete_interface_policy_fc_1 is not changed + - idempotency_delete_interface_policy_fc_1.previous == [] + - nm_delete_interface_policy_fc_2 is changed + - nm_delete_interface_policy_fc_2.previous != [] + - nm_delete_interface_policy_fc_2.current == [] diff --git a/ansible_collections/cisco/aci/tests/integration/targets/aci_interface_policy_l2/aliases b/ansible_collections/cisco/aci/tests/integration/targets/aci_interface_policy_l2/aliases new file mode 100644 index 000000000..209b793f9 --- /dev/null +++ b/ansible_collections/cisco/aci/tests/integration/targets/aci_interface_policy_l2/aliases @@ -0,0 +1,2 @@ +# No ACI simulator yet, so not enabled +# unsupported diff --git a/ansible_collections/cisco/aci/tests/integration/targets/aci_interface_policy_l2/tasks/main.yml b/ansible_collections/cisco/aci/tests/integration/targets/aci_interface_policy_l2/tasks/main.yml new file mode 100644 index 000000000..f52b74fdb --- /dev/null +++ b/ansible_collections/cisco/aci/tests/integration/targets/aci_interface_policy_l2/tasks/main.yml @@ -0,0 +1,147 @@ +# Test code for the ACI modules +# Copyright: (c) 2023, Gaspard Micol (@gmicol) <gmicol@cisco.com> + +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +- name: Test that we have an ACI APIC host, ACI username and ACI password + fail: + msg: 'Please define the following variables: aci_hostname, aci_username and aci_password.' + when: aci_hostname is not defined or aci_username is not defined or aci_password is not defined + +- name: Set vars + set_fact: + aci_info: &aci_info + host: "{{ aci_hostname }}" + username: "{{ aci_username }}" + password: "{{ aci_password }}" + validate_certs: '{{ aci_validate_certs | default(false) }}' + use_ssl: '{{ aci_use_ssl | default(true) }}' + use_proxy: '{{ aci_use_proxy | default(true) }}' + output_level: debug + +- name: Verify Cloud and Non-Cloud Sites in use. + include_tasks: ../../../../../../integration/targets/aci_cloud_provider/tasks/main.yml + +- name: Execute tasks only for non-cloud sites + when: query_cloud.current == [] # This condition will execute only non-cloud sites + block: # block specifies execution of tasks within, based on conditions + # Clean Environment + - name: Ensure first l2 interface policy does not exist + aci_interface_policy_l2: &aci_interface_policy_l2_absent + <<: *aci_info + name: anstest_policy + description: test for l2 interface policy + vlan_scope: portlocal + qinq: core + vepa: true + state: absent + + - name: Ensure second l2 interface policy does not exist + aci_interface_policy_l2: &aci_interface_policy_l2_2_absent + <<: *aci_info + name: anstest_policy_2 + state: absent + + - name: Create first l2 interface policy (check_mode) + aci_interface_policy_l2: &aci_interface_policy_l2_present + <<: *aci_interface_policy_l2_absent + state: present + check_mode: true + register: cm_add_interface_policy_l2_1 + + - name: Create first l2 interface policy (normal_mode) + aci_interface_policy_l2: + <<: *aci_interface_policy_l2_present + register: nm_add_interface_policy_l2_1 + + - name: Create first l2 interface policy again - testing idempotency + aci_interface_policy_l2: + <<: *aci_interface_policy_l2_present + register: idempotency_add_interface_policy_l2_1 + + - name: Create second l2 interface policy + aci_interface_policy_l2: &aci_interface_policy_l2_2_present + <<: *aci_interface_policy_l2_2_absent + state: present + register: nm_add_interface_policy_l2_2 + + - name: Asserts for creation tasks + assert: + that: + - cm_add_interface_policy_l2_1 is changed + - cm_add_interface_policy_l2_1.previous == [] + - cm_add_interface_policy_l2_1.current == [] + - nm_add_interface_policy_l2_1 is changed + - nm_add_interface_policy_l2_1.current.0.l2IfPol.attributes.name == "anstest_policy" + - nm_add_interface_policy_l2_1.current.0.l2IfPol.attributes.vlanScope == "portlocal" + - nm_add_interface_policy_l2_1.current.0.l2IfPol.attributes.qinq == "corePort" + - nm_add_interface_policy_l2_1.current.0.l2IfPol.attributes.vepa == "enabled" + - idempotency_add_interface_policy_l2_1 is not changed + - nm_add_interface_policy_l2_2 is changed + - nm_add_interface_policy_l2_2.current.0.l2IfPol.attributes.name == "anstest_policy_2" + - nm_add_interface_policy_l2_2.current.0.l2IfPol.attributes.vlanScope == "global" + - nm_add_interface_policy_l2_2.current.0.l2IfPol.attributes.qinq == "disabled" + - nm_add_interface_policy_l2_2.current.0.l2IfPol.attributes.vepa == "disabled" + + - name: Query all l2 interface policies + aci_interface_policy_l2: + <<: *aci_info + state: query + register: query_all_interface_policy_l2 + + - name: Query first l2 interface policy + aci_interface_policy_l2: + <<: *aci_interface_policy_l2_present + state: query + register: query_first_interface_policy_l2 + + - name: Asserts for query tasks + assert: + that: + - query_all_interface_policy_l2 is not changed + - query_all_interface_policy_l2.current | length >= 2 + - '"class/l2IfPol.json" in query_all_interface_policy_l2.url' + - query_first_interface_policy_l2 is not changed + - query_first_interface_policy_l2.current.0.l2IfPol.attributes.name == "anstest_policy" + - query_first_interface_policy_l2.current.0.l2IfPol.attributes.vlanScope == "portlocal" + - query_first_interface_policy_l2.current.0.l2IfPol.attributes.qinq == "corePort" + - query_first_interface_policy_l2.current.0.l2IfPol.attributes.vepa == "enabled" + + - name: Delete first l2 interface policy (check_mode) + aci_interface_policy_l2: + <<: *aci_interface_policy_l2_present + state: absent + check_mode: true + register: cm_delete_interface_policy_l2_1 + + - name: Delete first l2 interface policy (normal_mode) + aci_interface_policy_l2: + <<: *aci_interface_policy_l2_present + state: absent + register: nm_delete_interface_policy_l2_1 + + - name: Delete first l2 interface policy again - testing idempotency + aci_interface_policy_l2: + <<: *aci_interface_policy_l2_present + state: absent + register: idempotency_delete_interface_policy_l2_1 + + - name: Delete second l2 interface policy (normal_mode) + aci_interface_policy_l2: + <<: *aci_interface_policy_l2_2_present + state: absent + register: nm_delete_interface_policy_l2_2 + + - name: Asserts for deletion tasks + assert: + that: + - cm_delete_interface_policy_l2_1 is changed + - cm_delete_interface_policy_l2_1.proposed == {} + - nm_delete_interface_policy_l2_1 is changed + - nm_delete_interface_policy_l2_1.previous != [] + - nm_delete_interface_policy_l2_1.current == [] + - idempotency_delete_interface_policy_l2_1 is not changed + - idempotency_delete_interface_policy_l2_1.previous == [] + - nm_delete_interface_policy_l2_2 is changed + - nm_delete_interface_policy_l2_2.previous != [] + - nm_delete_interface_policy_l2_2.current == [] diff --git a/ansible_collections/cisco/aci/tests/integration/targets/aci_interface_policy_leaf_fc_policy_group/aliases b/ansible_collections/cisco/aci/tests/integration/targets/aci_interface_policy_leaf_fc_policy_group/aliases new file mode 100644 index 000000000..209b793f9 --- /dev/null +++ b/ansible_collections/cisco/aci/tests/integration/targets/aci_interface_policy_leaf_fc_policy_group/aliases @@ -0,0 +1,2 @@ +# No ACI simulator yet, so not enabled +# unsupported diff --git a/ansible_collections/cisco/aci/tests/integration/targets/aci_interface_policy_leaf_fc_policy_group/tasks/main.yml b/ansible_collections/cisco/aci/tests/integration/targets/aci_interface_policy_leaf_fc_policy_group/tasks/main.yml new file mode 100644 index 000000000..5a758bb3d --- /dev/null +++ b/ansible_collections/cisco/aci/tests/integration/targets/aci_interface_policy_leaf_fc_policy_group/tasks/main.yml @@ -0,0 +1,231 @@ +# Test code for the ACI modules +# Copyright: (c) 2023, Anvitha Jain <anvjain@cisco.com> + +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +- name: Test that we have an ACI APIC host, ACI username and ACI password + fail: + msg: 'Please define the following variables: aci_hostname, aci_username and aci_password.' + when: aci_hostname is not defined or aci_username is not defined or aci_password is not defined + +- name: Set vars + set_fact: + aci_info: &aci_info + host: "{{ aci_hostname }}" + username: "{{ aci_username }}" + password: "{{ aci_password }}" + validate_certs: '{{ aci_validate_certs | default(false) }}' + use_ssl: '{{ aci_use_ssl | default(true) }}' + use_proxy: '{{ aci_use_proxy | default(true) }}' + output_level: debug + +- name: Verify Cloud and Non-Cloud Sites in use. + include_tasks: ../../../../../../integration/targets/aci_cloud_provider/tasks/main.yml + +- name: Execute tasks only for non-cloud sites + when: query_cloud.current == [] # This condition will execute only non-cloud sites + block: # block specifies execution of tasks within, based on conditions + + # CLEAN ENVIRONMENT + - name: Making sure FC port policy group doesn't exist at beginning of test + cisco.aci.aci_interface_policy_leaf_fc_policy_group: &fc_absent + <<: *aci_info + lag_type: port + policy_group: fc_port_test + state: absent + + - name: Making sure FC PC policy group doesn't exist at beginning of test + cisco.aci.aci_interface_policy_leaf_fc_policy_group: &fc_pc_absent + <<: *aci_info + lag_type: port_channel + policy_group: fc_pc_test + state: absent + + # ADD Leaf Access FC Port Policy Group + - name: Adding a interface policy Leaf Access FC Port policy group - check mode works + cisco.aci.aci_interface_policy_leaf_fc_policy_group: &fc_present + <<: *aci_info + policy_group: fc_port_test + lag_type: port + fibre_channel_interface_policy: fibre_channel_interface_policy_test + attached_entity_profile: test_aep + state: present + check_mode: yes + register: intf_policy_leaf_access_fc_port_polgrp_check_mode_present + + - name: Adding a interface policy Leaf Access FC Port policy group - creation works + cisco.aci.aci_interface_policy_leaf_fc_policy_group: + <<: *fc_present + register: intf_policy_leaf_access_fc_port_polgrp_present + + - name: Adding a interface policy Leaf Access FC Port policy group - idempotency works + cisco.aci.aci_interface_policy_leaf_fc_policy_group: + <<: *fc_present + register: intf_policy_leaf_access_fc_port_polgrp_idempotent + + # UPDATE Leaf Access FC Port Policy Group + - name: Adding a interface policy Leaf Access FC Port policy group description - update works + cisco.aci.aci_interface_policy_leaf_fc_policy_group: + <<: *fc_present + description: policygroup description + register: intf_policy_leaf_access_fc_port_polgrp_update + + - name: Verify present assertions for FC Port Policy Group + assert: + that: + - intf_policy_leaf_access_fc_port_polgrp_check_mode_present is changed + - intf_policy_leaf_access_fc_port_polgrp_present is changed + - intf_policy_leaf_access_fc_port_polgrp_present.previous == [] + - intf_policy_leaf_access_fc_port_polgrp_present.current[0].infraFcAccPortGrp.attributes.name == 'fc_port_test' + - intf_policy_leaf_access_fc_port_polgrp_present.current[0].infraFcAccPortGrp.children.0.infraRsFcAttEntP.attributes.tDn == 'uni/infra/attentp-test_aep' + - intf_policy_leaf_access_fc_port_polgrp_present.current[0].infraFcAccPortGrp.children.1.infraRsFcL2IfPol.attributes.tnFcIfPolName == 'fibre_channel_interface_policy_test' + - intf_policy_leaf_access_fc_port_polgrp_present.current[0].infraFcAccPortGrp.attributes.annotation == 'orchestrator:ansible' + - intf_policy_leaf_access_fc_port_polgrp_idempotent is not changed + - intf_policy_leaf_access_fc_port_polgrp_idempotent.sent == {} + - intf_policy_leaf_access_fc_port_polgrp_update is changed + - intf_policy_leaf_access_fc_port_polgrp_update.current[0].infraFcAccPortGrp.attributes.descr == 'policygroup description' + + # ADD Leaf Access FC PC Policy Group + - name: Adding a interface policy Leaf Access FC PC policy group - check mode works + cisco.aci.aci_interface_policy_leaf_fc_policy_group: &fc_pc_present + <<: *aci_info + policy_group: fc_pc_test + lag_type: port_channel + fibre_channel_interface_policy: fibre_channel_interface_policy_test + port_channel_policy: port_channel_policy_test + attached_entity_profile: policy_group_aep + state: present + check_mode: yes + register: intf_policy_leaf_access_fc_pc_polgrp_check_mode_present + + - name: Adding a interface policy Leaf Access FC PC policy group - creation works + cisco.aci.aci_interface_policy_leaf_fc_policy_group: + <<: *fc_pc_present + register: intf_policy_leaf_access_fc_pc_polgrp_present + + - name: Adding a interface policy Leaf Access FC PC policy group - idempotency works + cisco.aci.aci_interface_policy_leaf_fc_policy_group: + <<: *fc_pc_present + register: intf_policy_leaf_access_fc_pc_polgrp_idempotent + + # UPDATE Leaf Access FC PC Policy Group + - name: Adding a interface policy Leaf Access FC PC policy group description - update works + cisco.aci.aci_interface_policy_leaf_fc_policy_group: + <<: *fc_pc_present + description: policygroup description + register: intf_policy_leaf_access_fc_pc_polgrp_update + + - name: Verify present assertions for FC PC Policy Group + assert: + that: + - intf_policy_leaf_access_fc_pc_polgrp_check_mode_present is changed + - intf_policy_leaf_access_fc_pc_polgrp_present is changed + - intf_policy_leaf_access_fc_pc_polgrp_present.previous == [] + - intf_policy_leaf_access_fc_pc_polgrp_present.current[0].infraFcAccBndlGrp.attributes.name == 'fc_pc_test' + - intf_policy_leaf_access_fc_pc_polgrp_present.current[0].infraFcAccBndlGrp.children.0.infraRsFcLagPol.attributes.tnLacpLagPolName == 'port_channel_policy_test' + - intf_policy_leaf_access_fc_pc_polgrp_present.current[0].infraFcAccBndlGrp.children.1.infraRsFcAttEntP.attributes.tDn == 'uni/infra/attentp-policy_group_aep' + - intf_policy_leaf_access_fc_pc_polgrp_present.current[0].infraFcAccBndlGrp.children.2.infraRsFcL2IfPol.attributes.tnFcIfPolName == 'fibre_channel_interface_policy_test' + - intf_policy_leaf_access_fc_pc_polgrp_present.current[0].infraFcAccBndlGrp.attributes.annotation == 'orchestrator:ansible' + - intf_policy_leaf_access_fc_pc_polgrp_idempotent is not changed + - intf_policy_leaf_access_fc_pc_polgrp_idempotent.sent == {} + - intf_policy_leaf_access_fc_pc_polgrp_update is changed + - intf_policy_leaf_access_fc_pc_polgrp_update.current[0].infraFcAccBndlGrp.attributes.descr == 'policygroup description' + + + # QUERY Leaf Access FC Port Policy Group + - name: Query all interface policy Leaf Access FC Port policy groups + cisco.aci.aci_interface_policy_leaf_fc_policy_group: + <<: *aci_info + lag_type: port + state: query + register: query_all_leaf_access_fc_port_policy_groups + + - name: Query interface policy Leaf Access FC Port policy group + cisco.aci.aci_interface_policy_leaf_fc_policy_group: + <<: *aci_info + policy_group: fc_port_test + lag_type: port + state: query + register: query_leaf_access_fc_port_policy_group + + - name: Verify query assertions for Leaf Access FC Port Policy Group + assert: + that: + - query_leaf_access_fc_port_policy_group is not changed + - query_leaf_access_fc_port_policy_group.current[0] | length >= 1 + - query_all_leaf_access_fc_port_policy_groups is not changed + + # QUERY Leaf Access FC PC Policy Group + - name: Query all interface policy Leaf Access FC PC policy groups + cisco.aci.aci_interface_policy_leaf_fc_policy_group: + <<: *aci_info + lag_type: port_channel + state: query + register: query_all_leaf_access_fc_pc_policy_groups + + - name: Query interface policy Leaf Access FC PC policy group + cisco.aci.aci_interface_policy_leaf_fc_policy_group: + <<: *aci_info + policy_group: fc_pc_test + lag_type: port_channel + state: query + register: query_leaf_access_fc_pc_policy_group + + - name: Verify query assertions for Leaf Access FC PC Policy Group + assert: + that: + - query_leaf_access_fc_pc_policy_group is not changed + - query_leaf_access_fc_pc_policy_group.current[0] | length >= 1 + - query_all_leaf_access_fc_pc_policy_groups is not changed + + # DELETE Leaf Access FC Port Policy Group + - name: Remove interface policy Leaf Access FC Port policy group - check mode + cisco.aci.aci_interface_policy_leaf_fc_policy_group: + <<: *fc_absent + check_mode: yes + register: intf_policy_leaf_access_fc_port_polgrp_check_mode_absent + + - name: Remove interface policy Leaf Access FC Port policy group - delete works + cisco.aci.aci_interface_policy_leaf_fc_policy_group: + <<: *fc_absent + register: intf_policy_leaf_access_fc_port_polgrp_absent + + - name: Remove interface policy Leaf Access FC Port policy group - idempotency works + cisco.aci.aci_interface_policy_leaf_fc_policy_group: + <<: *fc_absent + register: intf_policy_leaf_access_fc_port_polgrp_absent_idempotent + + - name: Verify absent assertions for FC Port Policy Group + assert: + that: + - intf_policy_leaf_access_fc_port_polgrp_check_mode_absent is changed + - intf_policy_leaf_access_fc_port_polgrp_check_mode_absent.previous != [] + - intf_policy_leaf_access_fc_port_polgrp_absent is changed + - intf_policy_leaf_access_fc_port_polgrp_absent_idempotent is not changed + - intf_policy_leaf_access_fc_port_polgrp_absent_idempotent.previous == [] + + # DELETE Leaf Access FC PC Policy Group + - name: Remove interface policy Leaf Access FC PC policy group - check mode + cisco.aci.aci_interface_policy_leaf_fc_policy_group: + <<: *fc_pc_absent + check_mode: yes + register: intf_policy_leaf_access_fc_pc_polgrp_check_mode_absent + + - name: Remove interface policy Leaf Access FC PC policy group - delete works + cisco.aci.aci_interface_policy_leaf_fc_policy_group: + <<: *fc_pc_absent + register: intf_policy_leaf_access_fc_pc_polgrp_absent + + - name: Remove interface policy Leaf Access FC PC policy group - idempotency works + cisco.aci.aci_interface_policy_leaf_fc_policy_group: + <<: *fc_pc_absent + register: intf_policy_leaf_access_fc_pc_polgrp_absent_idempotent + + - name: Verify absent assertions for FC PC policy group + assert: + that: + - intf_policy_leaf_access_fc_pc_polgrp_check_mode_absent is changed + - intf_policy_leaf_access_fc_pc_polgrp_check_mode_absent.previous != [] + - intf_policy_leaf_access_fc_pc_polgrp_absent is changed + - intf_policy_leaf_access_fc_pc_polgrp_absent_idempotent is not changed + - intf_policy_leaf_access_fc_pc_polgrp_absent_idempotent.previous == [] diff --git a/ansible_collections/cisco/aci/tests/integration/targets/aci_interface_policy_leaf_policy_group/tasks/main.yml b/ansible_collections/cisco/aci/tests/integration/targets/aci_interface_policy_leaf_policy_group/tasks/main.yml index f94ba4e48..3f6e25505 100644 --- a/ansible_collections/cisco/aci/tests/integration/targets/aci_interface_policy_leaf_policy_group/tasks/main.yml +++ b/ansible_collections/cisco/aci/tests/integration/targets/aci_interface_policy_leaf_policy_group/tasks/main.yml @@ -1,4 +1,5 @@ # Test code for the ACI modules +# Copyright: (c) 2023, Anvitha Jain <anvjain@cisco.com> # Copyright: (c) 2017, Bruno Calogero <brunocalogero@hotmail.com> # GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) @@ -8,6 +9,25 @@ msg: 'Please define the following variables: aci_hostname, aci_username and aci_password.' when: aci_hostname is not defined or aci_username is not defined or aci_password is not defined +# GET Credentials from the inventory +- name: Set vars + set_fact: + aci_info: &aci_info + host: "{{ aci_hostname }}" + username: "{{ aci_username }}" + password: "{{ aci_password }}" + validate_certs: "{{ aci_validate_certs | default(false) }}" + use_ssl: "{{ aci_use_ssl | default(true) }}" + use_proxy: "{{ aci_use_proxy | default(true) }}" + output_level: debug + +- name: Query system information + cisco.aci.aci_system: + <<: *aci_info + id: 1 + state: query + register: version + - name: Verify Cloud and Non-Cloud Sites in use. include_tasks: ../../../../../../integration/targets/aci_cloud_provider/tasks/main.yml @@ -16,55 +36,30 @@ block: # block specifies execution of tasks within, based on conditions - name: Making sure interface_policy_leaf_policy_group doesn't exist at beginning of test (PC) cisco.aci.aci_interface_policy_leaf_policy_group: &aci_interface_policy_leaf_policy_group_link_absent - host: '{{ aci_hostname }}' - username: '{{ aci_username }}' - password: '{{ aci_password }}' - validate_certs: '{{ aci_validate_certs | default(false) }}' - use_ssl: '{{ aci_use_ssl | default(true) }}' - use_proxy: '{{ aci_use_proxy | default(true) }}' - output_level: debug + <<: *aci_info policy_group: policygroupname_link lag_type: link state: absent - name: Making sure interface_policy_leaf_policy_group doesn't exist at beginning of test (VPC) cisco.aci.aci_interface_policy_leaf_policy_group: &aci_interface_policy_leaf_policy_group_node_absent - host: '{{ aci_hostname }}' - username: '{{ aci_username }}' - password: '{{ aci_password }}' - validate_certs: '{{ aci_validate_certs | default(false) }}' - use_ssl: '{{ aci_use_ssl | default(true) }}' - use_proxy: '{{ aci_use_proxy | default(true) }}' - output_level: debug + <<: *aci_info policy_group: policygroupname_node lag_type: node state: absent - name: Making sure interface_policy_leaf_policy_group doesn't exist at beginning of test (Leaf Access Port) cisco.aci.aci_interface_policy_leaf_policy_group: &aci_interface_policy_leaf_policy_group_leaf_absent - host: '{{ aci_hostname }}' - username: '{{ aci_username }}' - password: '{{ aci_password }}' - validate_certs: '{{ aci_validate_certs | default(false) }}' - use_ssl: '{{ aci_use_ssl | default(true) }}' - use_proxy: '{{ aci_use_proxy | default(true) }}' - output_level: debug + <<: *aci_info policy_group: policygroupname_leaf lag_type: leaf state: absent - # ==== TESTING Port Channel (PC), lag_type: link ==== - name: Adding a interface policy leaf policy group (PC) - check mode works cisco.aci.aci_interface_policy_leaf_policy_group: &aci_interface_policy_leaf_policy_group_link_present - host: '{{ aci_hostname }}' - username: '{{ aci_username }}' - password: '{{ aci_password }}' - validate_certs: '{{ aci_validate_certs | default(false) }}' - use_ssl: '{{ aci_use_ssl | default(true) }}' - use_proxy: '{{ aci_use_proxy | default(true) }}' - output_level: debug + <<: *aci_info policy_group: policygroupname_link lag_type: link link_level_policy: linklevelpolicy @@ -90,7 +85,7 @@ register: intf_policy_leaf_polgrp_update # TODO: also test for errors - - name: present assertions + - name: present assertions for interface policy leaf policy group (PC) assert: that: - intf_policy_leaf_polgrp_check_mode_present is changed @@ -108,19 +103,13 @@ - name: Query interface policy leaf policy group (PC) cisco.aci.aci_interface_policy_leaf_policy_group: - host: '{{ aci_hostname }}' - username: '{{ aci_username }}' - password: '{{ aci_password }}' - validate_certs: '{{ aci_validate_certs | default(false) }}' - use_ssl: '{{ aci_use_ssl | default(true) }}' - use_proxy: '{{ aci_use_proxy | default(true) }}' - output_level: debug + <<: *aci_info policy_group: policygroupname_link lag_type: link state: query register: binding_query - - name: present assertions + - name: Query assertions for interface policy leaf policy group (PC) assert: that: - binding_query is not changed @@ -145,20 +134,14 @@ - name: Remove interface policy leaf policy group (PC) - check mode cisco.aci.aci_interface_policy_leaf_policy_group: - host: '{{ aci_hostname }}' - username: '{{ aci_username }}' - password: '{{ aci_password }}' - validate_certs: '{{ aci_validate_certs | default(false) }}' - use_ssl: '{{ aci_use_ssl | default(true) }}' - use_proxy: '{{ aci_use_proxy | default(true) }}' - output_level: debug + <<: *aci_info policy_group: policygroupname_link #lag_type: link state: absent ignore_errors: true register: intf_policy_leaf_polgrp_absent_missing_param - - name: absent assertions + - name: absent assertions for interface policy leaf policy group (PC) assert: that: - intf_policy_leaf_polgrp_check_mode_absent is changed @@ -181,13 +164,7 @@ - name: Adding a interface policy leaf policy group (VPC) - check mode works cisco.aci.aci_interface_policy_leaf_policy_group: &aci_interface_policy_leaf_policy_group_node_present - host: '{{ aci_hostname }}' - username: '{{ aci_username }}' - password: '{{ aci_password }}' - validate_certs: '{{ aci_validate_certs | default(false) }}' - use_ssl: '{{ aci_use_ssl | default(true) }}' - use_proxy: '{{ aci_use_proxy | default(true) }}' - output_level: debug + <<: *aci_info policy_group: policygroupname_node lag_type: node link_level_policy: linklevelpolicy @@ -213,7 +190,7 @@ register: intf_policy_leaf_polgrp_update # TODO: also test for errors - - name: present assertions + - name: present assertions for interface policy leaf policy group (VPC) assert: that: - intf_policy_leaf_polgrp_check_mode_present is changed @@ -231,19 +208,13 @@ - name: Query interface policy leaf policy group (VPC) cisco.aci.aci_interface_policy_leaf_policy_group: - host: '{{ aci_hostname }}' - username: '{{ aci_username }}' - password: '{{ aci_password }}' - validate_certs: '{{ aci_validate_certs | default(false) }}' - use_ssl: '{{ aci_use_ssl | default(true) }}' - use_proxy: '{{ aci_use_proxy | default(true) }}' - output_level: debug + <<: *aci_info policy_group: policygroupname_node lag_type: node state: query register: binding_query - - name: present assertions + - name: Query assertions for interface policy leaf policy group (VPC) assert: that: - binding_query is not changed @@ -258,18 +229,12 @@ - name: Query interface policy leaf policy group (VPC) cisco.aci.aci_interface_policy_leaf_policy_group: - host: '{{ aci_hostname }}' - username: '{{ aci_username }}' - password: '{{ aci_password }}' - validate_certs: '{{ aci_validate_certs | default(false) }}' - use_ssl: '{{ aci_use_ssl | default(true) }}' - use_proxy: '{{ aci_use_proxy | default(true) }}' - output_level: debug + <<: *aci_info lag_type: node state: query register: binding_query_node_all - - name: present assertions + - name: Query assertions for interface policy leaf policy group (VPC) assert: that: - binding_query_node_all is not changed @@ -293,22 +258,15 @@ <<: *aci_interface_policy_leaf_policy_group_node_absent register: intf_policy_leaf_polgrp_absent_idempotent - - name: Remove interface policy leaf policy group (VPC) - check mode + - name: Remove interface policy leaf policy group (VPC) - without lag_type cisco.aci.aci_interface_policy_leaf_policy_group: - host: '{{ aci_hostname }}' - username: '{{ aci_username }}' - password: '{{ aci_password }}' - validate_certs: '{{ aci_validate_certs | default(false) }}' - use_ssl: '{{ aci_use_ssl | default(true) }}' - use_proxy: '{{ aci_use_proxy | default(true) }}' - output_level: debug + <<: *aci_info policy_group: policygroupname_node - #lag_type: node state: absent ignore_errors: true register: intf_policy_leaf_polgrp_absent_missing_param - - name: absent assertions + - name: absent assertions for interface policy leaf policy group (VPC) assert: that: - intf_policy_leaf_polgrp_check_mode_absent is changed @@ -320,6 +278,18 @@ - intf_policy_leaf_polgrp_absent_missing_param is failed - 'intf_policy_leaf_polgrp_absent_missing_param.msg == "missing required arguments: lag_type"' + - name: Adding a interface policy leaf policy group (VPC) - port_channel_policy not supported error + cisco.aci.aci_interface_policy_leaf_policy_group: + <<: *aci_interface_policy_leaf_policy_group_link_present + port_authentication: "default" + ignore_errors: true + register: intf_policy_leaf_polgrp_pa_policy_error + + - name: VPC error assertions (adding invalid parameters) + assert: + that: + - intf_policy_leaf_polgrp_pa_policy_error.msg == 'port_authentication is not a valid parameter for link/node (Port Channel, Virtual Port Channel), if used assign null to it (port_authentication{{":"}} null).' + # ==== END TESTING Virtual Port Channel (VPC), lag_type: node ==== @@ -331,17 +301,12 @@ - name: Adding a interface policy leaf policy group (Leaf Access Port) - check mode works cisco.aci.aci_interface_policy_leaf_policy_group: &aci_interface_policy_leaf_policy_group_leaf_present - host: '{{ aci_hostname }}' - username: '{{ aci_username }}' - password: '{{ aci_password }}' - validate_certs: '{{ aci_validate_certs | default(false) }}' - use_ssl: '{{ aci_use_ssl | default(true) }}' - use_proxy: '{{ aci_use_proxy | default(true) }}' - output_level: debug + <<: *aci_info policy_group: policygroupname_leaf lag_type: leaf link_level_policy: linklevelpolicy fibre_channel_interface_policy: fiberchannelpolicy + aep: test_aep state: present check_mode: true register: intf_policy_leaf_polgrp_check_mode_present @@ -377,7 +342,7 @@ register: intf_policy_leaf_polgrp_pc_policy_error # TODO: also test for errors - - name: present assertions + - name: present assertions for interface policy leaf policy group (Leaf Access Port) assert: that: - intf_policy_leaf_polgrp_check_mode_present is changed @@ -386,6 +351,7 @@ - intf_policy_leaf_polgrp_present.sent.infraAccPortGrp.attributes.name == 'policygroupname_leaf' - intf_policy_leaf_polgrp_present.sent.infraAccPortGrp.children.0.infraRsFcIfPol.attributes.tnFcIfPolName == 'fiberchannelpolicy' - intf_policy_leaf_polgrp_present.sent.infraAccPortGrp.children.1.infraRsHIfPol.attributes.tnFabricHIfPolName == 'linklevelpolicy' + - intf_policy_leaf_polgrp_present.sent.infraAccPortGrp.children.2.infraRsAttEntP.attributes.tDn == "uni/infra/attentp-test_aep" - intf_policy_leaf_polgrp_idempotent is not changed - intf_policy_leaf_polgrp_idempotent.sent == {} - intf_policy_leaf_polgrp_update is changed @@ -395,24 +361,19 @@ - name: Query interface policy leaf policy group (Leaf Access Port) cisco.aci.aci_interface_policy_leaf_policy_group: - host: '{{ aci_hostname }}' - username: '{{ aci_username }}' - password: '{{ aci_password }}' - validate_certs: '{{ aci_validate_certs | default(false) }}' - use_ssl: '{{ aci_use_ssl | default(true) }}' - use_proxy: '{{ aci_use_proxy | default(true) }}' - output_level: debug + <<: *aci_info policy_group: policygroupname_leaf lag_type: leaf state: query register: binding_query - - name: present assertions + - name: Query assertions interface policy leaf policy group (Leaf Access Port) assert: that: - binding_query is not changed - binding_query.current | length >= 1 - '"/api/mo/uni/infra/funcprof/accportgrp-policygroupname_leaf.json" in binding_query.url' + - binding_query.current.0.infraAccPortGrp.children.14.infraRsAttEntP.attributes.tDn == "uni/infra/attentp-test_aep" - name: Remove interface policy leaf policy group (Leaf Access Port) - check mode cisco.aci.aci_interface_policy_leaf_policy_group: @@ -430,22 +391,15 @@ <<: *aci_interface_policy_leaf_policy_group_leaf_absent register: intf_policy_leaf_polgrp_absent_idempotent - - name: Remove interface policy leaf policy group (Leaf Access Port) - check mode + - name: Remove interface policy leaf policy group (Leaf Access Port) - without lag_type cisco.aci.aci_interface_policy_leaf_policy_group: - host: '{{ aci_hostname }}' - username: '{{ aci_username }}' - password: '{{ aci_password }}' - validate_certs: '{{ aci_validate_certs | default(false) }}' - use_ssl: '{{ aci_use_ssl | default(true) }}' - use_proxy: '{{ aci_use_proxy | default(true) }}' - output_level: debug + <<: *aci_info policy_group: policygroupname_leaf - #lag_type: leaf state: absent ignore_errors: true register: intf_policy_leaf_polgrp_absent_missing_param - - name: absent assertions + - name: absent assertions for interface policy leaf policy group (Leaf Access Port) assert: that: - intf_policy_leaf_polgrp_check_mode_absent is changed @@ -458,3 +412,129 @@ - 'intf_policy_leaf_polgrp_absent_missing_param.msg == "missing required arguments: lag_type"' # ==== END TESTING Virtual Port Channel (VPC), lag_type: leaf ==== + + + # ==== START TESTING PortCHannel (PC - lag_type: link), Virtual Port Channel (VPC - node: leaf) and Leaf Interface (lag_type: leaf) ==== + # ==== For parameters avaiable only for APIC versions > 5 ==== + + - name: Execute tasks only for the APIC version version > 5 + when: version.current.0.topSystem.attributes.version is version('5', '>') + block: + + # ==== TESTING Port Channel (PC) ==== + - name: Adding a interface policy leaf policy group (PC) - APIC version > 5 + cisco.aci.aci_interface_policy_leaf_policy_group: + <<: *aci_interface_policy_leaf_policy_group_link_present + sync_e_interface_policy: synceinterfacepolicy + state: present + register: intf_policy_leaf_policy_group_link_present_2 + + - name: present assertions (PC) for APIC version > 5 + assert: + that: + - intf_policy_leaf_policy_group_link_present_2 is changed + - intf_policy_leaf_policy_group_link_present_2.previous != [] + - intf_policy_leaf_policy_group_link_present_2.current[0].infraAccBndlGrp.attributes.name == 'policygroupname_link' + - intf_policy_leaf_policy_group_link_present_2.sent.infraAccBndlGrp.children.0.infraRsSynceEthIfPolBndlGrp.attributes.tnSynceEthIfPolName == 'synceinterfacepolicy' + + # ==== TESTING Leaf Interface ==== + - name: Adding a interface policy leaf policy group (Leaf Access Port) - APIC version > 5 + cisco.aci.aci_interface_policy_leaf_policy_group: + <<: *aci_interface_policy_leaf_policy_group_leaf_present + sync_e_interface_policy: synceinterfacepolicy + state: present + register: intf_policy_leaf_polgrp_present_2 + + - name: present assertions (Leaf Access Port) for APIC version > 5 + assert: + that: + - intf_policy_leaf_polgrp_present_2 is changed + - intf_policy_leaf_polgrp_present_2.previous == [] + - intf_policy_leaf_polgrp_present_2.sent.infraAccPortGrp.attributes.name == 'policygroupname_leaf' + - intf_policy_leaf_polgrp_present_2.sent.infraAccPortGrp.children.0.infraRsFcIfPol.attributes.tnFcIfPolName == 'fiberchannelpolicy' + - intf_policy_leaf_polgrp_present_2.sent.infraAccPortGrp.children.1.infraRsHIfPol.attributes.tnFabricHIfPolName == 'linklevelpolicy' + - intf_policy_leaf_polgrp_present_2.sent.infraAccPortGrp.children.2.infraRsAttEntP.attributes.tDn == 'uni/infra/attentp-test_aep' + - intf_policy_leaf_polgrp_present_2.sent.infraAccPortGrp.children.3.infraRsSynceEthIfPol.attributes.tnSynceEthIfPolName == 'synceinterfacepolicy' + + # ==== END TESTING for parameters avaiable only for APIC versions > 5 ==== + + + # ==== START TESTING PortCHannel (PC - lag_type: link), Virtual Port Channel (VPC - node: leaf) and Leaf Interface (lag_type: leaf) ==== + # ==== For parameters avaiable only for APIC versions >= 6 ==== + + - name: Execute tasks only for the APIC version version >= 6 + when: version.current.0.topSystem.attributes.version is version('6', '>=') + block: + # ==== TESTING Virtual Port Channel (VPC) ==== + - name: Adding a interface policy leaf policy group (VPC) - APIC version > 6 + cisco.aci.aci_interface_policy_leaf_policy_group: + <<: *aci_interface_policy_leaf_policy_group_node_present + sync_e_interface_policy: synceinterfacepolicy + state: present + register: intf_policy_leaf_policy_group_node_present_2 + + - name: present assertions (VPC) for APIC version > 6 + assert: + that: + - intf_policy_leaf_policy_group_node_present_2 is changed + - intf_policy_leaf_policy_group_node_present_2.previous == [] + - intf_policy_leaf_policy_group_node_present_2.current[0].infraAccBndlGrp.attributes.name == 'policygroupname_node' + - intf_policy_leaf_policy_group_node_present_2.sent.infraAccBndlGrp.children.0.infraRsFcIfPol.attributes.tnFcIfPolName == 'fiberchannelpolicy' + - intf_policy_leaf_policy_group_node_present_2.sent.infraAccBndlGrp.children.1.infraRsHIfPol.attributes.tnFabricHIfPolName == 'linklevelpolicy' + - intf_policy_leaf_policy_group_node_present_2.sent.infraAccBndlGrp.children.2.infraRsSynceEthIfPolBndlGrp.attributes.tnSynceEthIfPolName == 'synceinterfacepolicy' + + - name: Execute tasks only for the APIC version version >= 6.0.2 + when: version.current.0.topSystem.attributes.version is version('6.0(2h)', '>=') + block: + # ==== TESTING Leaf Interface ==== + - name: Adding a interface policy leaf policy group (Leaf Access Port) - APIC version >= 6.0.2 + cisco.aci.aci_interface_policy_leaf_policy_group: + <<: *aci_interface_policy_leaf_policy_group_leaf_present + transceiver_policy: + name: transceiverpolicy + type: zr + state: present + register: intf_policy_leaf_polgrp_present_3 + + - name: present assertions (Leaf Access Port) for APIC version >= 6.0.2 + assert: + that: + - intf_policy_leaf_polgrp_present_3 is changed + - intf_policy_leaf_polgrp_present_3.previous != [] + - intf_policy_leaf_polgrp_present_3.current[0].infraAccPortGrp.attributes.name == 'policygroupname_leaf' + - intf_policy_leaf_polgrp_present_3.sent.infraAccPortGrp.children.0.infraRsOpticsIfPol.attributes.tDn == 'uni/infra/zr-transceiverpolicy' + + - name: Adding all the policies for leaf policy group (Leaf Access Port) + cisco.aci.aci_interface_policy_leaf_policy_group: + <<: *aci_interface_policy_leaf_policy_group_leaf_present + cdp_policy: cdppolicy + mcp_policy: mcppolicy + lldp_policy: lldppolicy + stp_interface_policy: stppolicy + egress_data_plane_policing_policy: egressdataplanepolicingpolicy + ingress_data_plane_policing_policy: ingressdataplanepolicingpolicy + priority_flow_control_policy: priorityflowcontrolpolicy + slow_drain_policy: slowdrainpolicy + monitoring_policy: monitoringpolicy + storm_control_interface_policy: stormcontrolinterfacepolicy + l2_interface_policy: l2interfacepolicy + port_security_policy: portsecuritypolicy + link_flap_policy: linkflappolicy + link_level_flow_control: linklevelflowcontrol + mac_sec_interface_policy: macsecinterfacepolicy + copp_policy: copppolicy + dwdm: dwdmpolicy + port_authentication: portauthenticationpolicy + poe_interface_policy: poeinterfacepolicy + state: present + register: intf_policy_leaf_polgrp_all_policy_present + + - name: present assertions (Leaf Access Port) for all the policies. + assert: + that: + - intf_policy_leaf_polgrp_all_policy_present is changed + - intf_policy_leaf_polgrp_all_policy_present.previous != [] + - intf_policy_leaf_polgrp_all_policy_present.current[0].infraAccPortGrp.attributes.name == 'policygroupname_leaf' + - intf_policy_leaf_polgrp_all_policy_present.sent.infraAccPortGrp.children.0.infraRsCdpIfPol.attributes.tnCdpIfPolName == 'cdppolicy' + - intf_policy_leaf_polgrp_all_policy_present.sent.infraAccPortGrp.children.1.infraRsL2IfPol.attributes.tnL2IfPolName == 'l2interfacepolicy' + - intf_policy_leaf_polgrp_all_policy_present.sent.infraAccPortGrp.children.2.infraRsL2PortSecurityPol.attributes.tnL2PortSecurityPolName == 'portsecuritypolicy' diff --git a/ansible_collections/cisco/aci/tests/integration/targets/aci_interface_policy_lldp/aliases b/ansible_collections/cisco/aci/tests/integration/targets/aci_interface_policy_lldp/aliases new file mode 100644 index 000000000..209b793f9 --- /dev/null +++ b/ansible_collections/cisco/aci/tests/integration/targets/aci_interface_policy_lldp/aliases @@ -0,0 +1,2 @@ +# No ACI simulator yet, so not enabled +# unsupported diff --git a/ansible_collections/cisco/aci/tests/integration/targets/aci_interface_policy_lldp/tasks/main.yml b/ansible_collections/cisco/aci/tests/integration/targets/aci_interface_policy_lldp/tasks/main.yml new file mode 100644 index 000000000..225e142ba --- /dev/null +++ b/ansible_collections/cisco/aci/tests/integration/targets/aci_interface_policy_lldp/tasks/main.yml @@ -0,0 +1,143 @@ +# Test code for the ACI modules +# Copyright: (c) 2023, Gaspard Micol (@gmicol) <gmicol@cisco.com> + +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +- name: Test that we have an ACI APIC host, ACI username and ACI password + fail: + msg: 'Please define the following variables: aci_hostname, aci_username and aci_password.' + when: aci_hostname is not defined or aci_username is not defined or aci_password is not defined + +- name: Set vars + set_fact: + aci_info: &aci_info + host: "{{ aci_hostname }}" + username: "{{ aci_username }}" + password: "{{ aci_password }}" + validate_certs: '{{ aci_validate_certs | default(false) }}' + use_ssl: '{{ aci_use_ssl | default(true) }}' + use_proxy: '{{ aci_use_proxy | default(true) }}' + output_level: debug + +- name: Verify Cloud and Non-Cloud Sites in use. + include_tasks: ../../../../../../integration/targets/aci_cloud_provider/tasks/main.yml + +- name: Execute tasks only for non-cloud sites + when: query_cloud.current == [] # This condition will execute only non-cloud sites + block: # block specifies execution of tasks within, based on conditions + # Clean Environment + - name: Ensure first lldp interface policy does not exist + aci_interface_policy_lldp: &aci_interface_policy_lldp_absent + <<: *aci_info + name: anstest_policy + description: test for lldp interface policy + receive_state: false + transmit_state: false + state: absent + + - name: Ensure second lldp interface policy does not exist + aci_interface_policy_lldp: &aci_interface_policy_lldp_2_absent + <<: *aci_info + name: anstest_policy_2 + state: absent + + - name: Create first lldp interface policy (check_mode) + aci_interface_policy_lldp: &aci_interface_policy_lldp_present + <<: *aci_interface_policy_lldp_absent + state: present + check_mode: true + register: cm_add_interface_policy_lldp_1 + + - name: Create first lldp interface policy (normal_mode) + aci_interface_policy_lldp: + <<: *aci_interface_policy_lldp_present + register: nm_add_interface_policy_lldp_1 + + - name: Create first lldp interface policy again - testing idempotency + aci_interface_policy_lldp: + <<: *aci_interface_policy_lldp_present + register: idempotency_add_interface_policy_lldp_1 + + - name: Create second lldp interface policy + aci_interface_policy_lldp: &aci_interface_policy_lldp_2_present + <<: *aci_interface_policy_lldp_2_absent + state: present + register: nm_add_interface_policy_lldp_2 + + - name: Asserts for creation tasks + assert: + that: + - cm_add_interface_policy_lldp_1 is changed + - cm_add_interface_policy_lldp_1.previous == [] + - cm_add_interface_policy_lldp_1.current == [] + - nm_add_interface_policy_lldp_1 is changed + - nm_add_interface_policy_lldp_1.current.0.lldpIfPol.attributes.name == "anstest_policy" + - nm_add_interface_policy_lldp_1.current.0.lldpIfPol.attributes.adminRxSt == "disabled" + - nm_add_interface_policy_lldp_1.current.0.lldpIfPol.attributes.adminTxSt == "disabled" + - idempotency_add_interface_policy_lldp_1 is not changed + - nm_add_interface_policy_lldp_2 is changed + - nm_add_interface_policy_lldp_2.current.0.lldpIfPol.attributes.name == "anstest_policy_2" + - nm_add_interface_policy_lldp_2.current.0.lldpIfPol.attributes.adminRxSt == "enabled" + - nm_add_interface_policy_lldp_2.current.0.lldpIfPol.attributes.adminTxSt == "enabled" + + - name: Query all lldp interface policies + aci_interface_policy_lldp: + <<: *aci_info + state: query + register: query_all_interface_policy_lldp + + - name: Query first lldp interface policy + aci_interface_policy_lldp: + <<: *aci_interface_policy_lldp_present + state: query + register: query_first_interface_policy_lldp + + - name: Asserts for query tasks + assert: + that: + - query_all_interface_policy_lldp is not changed + - query_all_interface_policy_lldp.current | length >= 2 + - '"class/lldpIfPol.json" in query_all_interface_policy_lldp.url' + - query_first_interface_policy_lldp is not changed + - query_first_interface_policy_lldp.current.0.lldpIfPol.attributes.name == "anstest_policy" + - query_first_interface_policy_lldp.current.0.lldpIfPol.attributes.adminRxSt == "disabled" + - query_first_interface_policy_lldp.current.0.lldpIfPol.attributes.adminTxSt == "disabled" + + - name: Delete first lldp interface policy (check_mode) + aci_interface_policy_lldp: + <<: *aci_interface_policy_lldp_present + state: absent + check_mode: true + register: cm_delete_interface_policy_lldp_1 + + - name: Delete first lldp interface policy (normal_mode) + aci_interface_policy_lldp: + <<: *aci_interface_policy_lldp_present + state: absent + register: nm_delete_interface_policy_lldp_1 + + - name: Delete first lldp interface policy again - testing idempotency + aci_interface_policy_lldp: + <<: *aci_interface_policy_lldp_present + state: absent + register: idempotency_delete_interface_policy_lldp_1 + + - name: Delete second lldp interface policy (normal_mode) + aci_interface_policy_lldp: + <<: *aci_interface_policy_lldp_2_present + state: absent + register: nm_delete_interface_policy_lldp_2 + + - name: Asserts for deletion tasks + assert: + that: + - cm_delete_interface_policy_lldp_1 is changed + - cm_delete_interface_policy_lldp_1.proposed == {} + - nm_delete_interface_policy_lldp_1 is changed + - nm_delete_interface_policy_lldp_1.previous != [] + - nm_delete_interface_policy_lldp_1.current == [] + - idempotency_delete_interface_policy_lldp_1 is not changed + - idempotency_delete_interface_policy_lldp_1.previous == [] + - nm_delete_interface_policy_lldp_2 is changed + - nm_delete_interface_policy_lldp_2.previous != [] + - nm_delete_interface_policy_lldp_2.current == [] diff --git a/ansible_collections/cisco/aci/tests/integration/targets/aci_interface_policy_mcp/aliases b/ansible_collections/cisco/aci/tests/integration/targets/aci_interface_policy_mcp/aliases new file mode 100644 index 000000000..209b793f9 --- /dev/null +++ b/ansible_collections/cisco/aci/tests/integration/targets/aci_interface_policy_mcp/aliases @@ -0,0 +1,2 @@ +# No ACI simulator yet, so not enabled +# unsupported diff --git a/ansible_collections/cisco/aci/tests/integration/targets/aci_interface_policy_mcp/tasks/main.yml b/ansible_collections/cisco/aci/tests/integration/targets/aci_interface_policy_mcp/tasks/main.yml new file mode 100644 index 000000000..7255c14ad --- /dev/null +++ b/ansible_collections/cisco/aci/tests/integration/targets/aci_interface_policy_mcp/tasks/main.yml @@ -0,0 +1,255 @@ +# Test code for the ACI modules +# Copyright: (c) 2023, Gaspard Micol (@gmicol) <gmicol@cisco.com> + +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +- name: Test that we have an ACI APIC host, ACI username and ACI password + fail: + msg: 'Please define the following variables: aci_hostname, aci_username and aci_password.' + when: aci_hostname is not defined or aci_username is not defined or aci_password is not defined + +- name: Set vars + set_fact: + aci_info: &aci_info + host: "{{ aci_hostname }}" + username: "{{ aci_username }}" + password: "{{ aci_password }}" + validate_certs: '{{ aci_validate_certs | default(false) }}' + use_ssl: '{{ aci_use_ssl | default(true) }}' + use_proxy: '{{ aci_use_proxy | default(true) }}' + output_level: debug + +- name: Verify Cloud and Non-Cloud Sites in use. + include_tasks: ../../../../../../integration/targets/aci_cloud_provider/tasks/main.yml + +- name: Execute tasks only for non-cloud sites + when: query_cloud.current == [] # This condition will execute only non-cloud sites + block: # block specifies execution of tasks within, based on conditions + # Clean Environment + - name: Ensure first mcp interface policy does not exist - APIC version >= 5.0 + aci_interface_policy_mcp: &aci_interface_policy_mcp_absent_5 + <<: *aci_info + name: anstest_policy + description: test for mcp interface policy + mcp_mode: strict + grace_period: 4 + grace_period_millisec: 500 + init_delay_time: 5 + tx_frequence: 2 + tx_frequence_millisec: 500 + admin_state: false + state: absent + when: version.current.0.topSystem.attributes.version is version('5', '>=') + + - name: Ensure first mcp interface policy does not exist + aci_interface_policy_mcp: &aci_interface_policy_mcp_absent + <<: *aci_info + name: anstest_policy + description: test for mcp interface policy + admin_state: false + state: absent + when: version.current.0.topSystem.attributes.version is version('5', '<') + + - name: Ensure second mcp interface policy does not exist + aci_interface_policy_mcp: &aci_interface_policy_mcp_2_absent + <<: *aci_info + name: anstest_policy_2 + state: absent + + - name: Create first mcp interface policy (check_mode) - APIC version >= 5.0 + aci_interface_policy_mcp: &aci_interface_policy_mcp_present_5 + <<: *aci_interface_policy_mcp_absent_5 + state: present + check_mode: true + register: cm_add_interface_policy_mcp_1_5 + when: version.current.0.topSystem.attributes.version is version('5', '>=') + + - name: Create first mcp interface policy (normal_mode) - APIC version >= 5.0 + aci_interface_policy_mcp: + <<: *aci_interface_policy_mcp_present_5 + register: nm_add_interface_policy_mcp_1_5 + when: version.current.0.topSystem.attributes.version is version('5', '>=') + + - name: Create first mcp interface policy again - testing idempotency - APIC version >= 5.0 + aci_interface_policy_mcp: + <<: *aci_interface_policy_mcp_present_5 + register: idempotency_add_interface_policy_mcp_1_5 + when: version.current.0.topSystem.attributes.version is version('5', '>=') + + - name: Create first mcp interface policy (check_mode) - APIC version < 5.0 + aci_interface_policy_mcp: &aci_interface_policy_mcp_present + <<: *aci_interface_policy_mcp_absent + state: present + check_mode: true + register: cm_add_interface_policy_mcp_1 + when: version.current.0.topSystem.attributes.version is version('5', '<') + + - name: Create first mcp interface policy (normal_mode) - APIC version < 5.0 + aci_interface_policy_mcp: + <<: *aci_interface_policy_mcp_present + register: nm_add_interface_policy_mcp_1 + when: version.current.0.topSystem.attributes.version is version('5', '<') + + - name: Create first mcp interface policy again - testing idempotency - APIC version < 5.0 + aci_interface_policy_mcp: + <<: *aci_interface_policy_mcp_present + register: idempotency_add_interface_policy_mcp_1 + when: version.current.0.topSystem.attributes.version is version('5', '<') + + - name: Create second mcp interface policy + aci_interface_policy_mcp: &aci_interface_policy_mcp_2_present + <<: *aci_interface_policy_mcp_2_absent + state: present + register: nm_add_interface_policy_mcp_2 + + - name: Asserts for creation tasks for first mcp interface policy - APIC version >= 5.0 + assert: + that: + - cm_add_interface_policy_mcp_1_5 is changed + - cm_add_interface_policy_mcp_1_5.previous == [] + - cm_add_interface_policy_mcp_1_5.current == [] + - nm_add_interface_policy_mcp_1_5 is changed + - nm_add_interface_policy_mcp_1_5.current.0.mcpIfPol.attributes.name == "anstest_policy" + - nm_add_interface_policy_mcp_1_5.current.0.mcpIfPol.attributes.adminSt == "disabled" + - idempotency_add_interface_policy_mcp_1_5 is not changed + when: version.current.0.topSystem.attributes.version is version('5', '>=') + + - name: Asserts for creation tasks for first mcp interface policy - APIC version < 5.0 + assert: + that: + - cm_add_interface_policy_mcp_1 is changed + - cm_add_interface_policy_mcp_1.previous == [] + - cm_add_interface_policy_mcp_1.current == [] + - nm_add_interface_policy_mcp_1 is changed + - nm_add_interface_policy_mcp_1.current.0.mcpIfPol.attributes.name == "anstest_policy" + - nm_add_interface_policy_mcp_1.current.0.mcpIfPol.attributes.adminSt == "disabled" + - idempotency_add_interface_policy_mcp_1 is not changed + when: version.current.0.topSystem.attributes.version is version('5', '<') + + - name: Asserts for creation tasks for second mcp interface policy + assert: + that: + - nm_add_interface_policy_mcp_2 is changed + - nm_add_interface_policy_mcp_2.current.0.mcpIfPol.attributes.name == "anstest_policy_2" + - nm_add_interface_policy_mcp_2.current.0.mcpIfPol.attributes.adminSt == "enabled" + + - name: Query all mcp interface policies + aci_interface_policy_mcp: + <<: *aci_info + state: query + register: query_all_interface_policy_mcp + + - name: Query first mcp interface policy + aci_interface_policy_mcp: + <<: *aci_interface_policy_mcp_present_5 + state: query + register: query_first_interface_policy_mcp_5 + when: version.current.0.topSystem.attributes.version is version('5', '>=') + + - name: Query first mcp interface policy + aci_interface_policy_mcp: + <<: *aci_interface_policy_mcp_present + state: query + register: query_first_interface_policy_mcp + when: version.current.0.topSystem.attributes.version is version('5', '<') + + - name: Asserts for query tasks + assert: + that: + - query_all_interface_policy_mcp is not changed + - query_all_interface_policy_mcp.current | length >= 2 + - '"class/mcpIfPol.json" in query_all_interface_policy_mcp.url' + + - name: Asserts for individual query tasks - APIC version >= 5.0 + assert: + that: + - query_first_interface_policy_mcp_5.current.0.mcpIfPol.attributes.name == "anstest_policy" + - query_first_interface_policy_mcp_5.current.0.mcpIfPol.attributes.adminSt == "disabled" + when: version.current.0.topSystem.attributes.version is version('5', '>=') + + - name: Asserts for individual query tasks - APIC version < 5.0 + assert: + that: + - query_first_interface_policy_mcp.current.0.mcpIfPol.attributes.name == "anstest_policy" + - query_first_interface_policy_mcp.current.0.mcpIfPol.attributes.adminSt == "disabled" + when: version.current.0.topSystem.attributes.version is version('5', '<') + + - name: Delete first mcp interface policy (check_mode) - APIC version >= 5.0 + aci_interface_policy_mcp: + <<: *aci_interface_policy_mcp_present_5 + state: absent + check_mode: true + register: cm_delete_interface_policy_mcp_1_5 + when: version.current.0.topSystem.attributes.version is version('5', '>=') + + - name: Delete first mcp interface policy (normal_mode) - APIC version >= 5.0 + aci_interface_policy_mcp: + <<: *aci_interface_policy_mcp_present_5 + state: absent + register: nm_delete_interface_policy_mcp_1_5 + when: version.current.0.topSystem.attributes.version is version('5', '>=') + + - name: Delete first mcp interface policy again - testing idempotency - APIC version >= 5.0 + aci_interface_policy_mcp: + <<: *aci_interface_policy_mcp_present_5 + state: absent + register: idempotency_delete_interface_policy_mcp_1_5 + when: version.current.0.topSystem.attributes.version is version('5', '>=') + + - name: Delete first mcp interface policy (check_mode) - APIC version < 5.0 + aci_interface_policy_mcp: + <<: *aci_interface_policy_mcp_present + state: absent + check_mode: true + register: cm_delete_interface_policy_mcp_1 + when: version.current.0.topSystem.attributes.version is version('5', '<') + + - name: Delete first mcp interface policy (normal_mode) - APIC version < 5.0 + aci_interface_policy_mcp: + <<: *aci_interface_policy_mcp_present + state: absent + register: nm_delete_interface_policy_mcp_1 + when: version.current.0.topSystem.attributes.version is version('5', '<') + + - name: Delete first mcp interface policy again - testing idempotency - APIC version < 5.0 + aci_interface_policy_mcp: + <<: *aci_interface_policy_mcp_present + state: absent + register: idempotency_delete_interface_policy_mcp_1 + when: version.current.0.topSystem.attributes.version is version('5', '<') + + - name: Delete second mcp interface policy (normal_mode) + aci_interface_policy_mcp: + <<: *aci_interface_policy_mcp_2_present + state: absent + register: nm_delete_interface_policy_mcp_2 + + - name: Asserts for first mcp interface policy deletion tasks - APIC version >= 5.0 + assert: + that: + - cm_delete_interface_policy_mcp_1_5 is changed + - cm_delete_interface_policy_mcp_1_5.proposed == {} + - nm_delete_interface_policy_mcp_1_5 is changed + - nm_delete_interface_policy_mcp_1_5.previous != [] + - nm_delete_interface_policy_mcp_1_5.current == [] + - idempotency_delete_interface_policy_mcp_1_5 is not changed + - idempotency_delete_interface_policy_mcp_1_5.previous == [] + when: version.current.0.topSystem.attributes.version is version('5', '>=') + + - name: Asserts for first mcp interface policy deletion tasks - APIC version < 5.0 + assert: + that: + - cm_delete_interface_policy_mcp_1 is changed + - cm_delete_interface_policy_mcp_1.proposed == {} + - nm_delete_interface_policy_mcp_1 is changed + - nm_delete_interface_policy_mcp_1.previous != [] + - nm_delete_interface_policy_mcp_1.current == [] + - idempotency_delete_interface_policy_mcp_1 is not changed + - idempotency_delete_interface_policy_mcp_1.previous == [] + when: version.current.0.topSystem.attributes.version is version('5', '<') + + - name: Asserts for second mcp interface policy deletion tasks + assert: + that: + - nm_delete_interface_policy_mcp_2.previous != [] + - nm_delete_interface_policy_mcp_2.current == [] diff --git a/ansible_collections/cisco/aci/tests/integration/targets/aci_interface_policy_ospf/tasks/main.yml b/ansible_collections/cisco/aci/tests/integration/targets/aci_interface_policy_ospf/tasks/main.yml index 96a5472f0..71e096435 100644 --- a/ansible_collections/cisco/aci/tests/integration/targets/aci_interface_policy_ospf/tasks/main.yml +++ b/ansible_collections/cisco/aci/tests/integration/targets/aci_interface_policy_ospf/tasks/main.yml @@ -8,47 +8,33 @@ msg: 'Please define the following variables: aci_hostname, aci_username and aci_password.' when: aci_hostname is not defined or aci_username is not defined or aci_password is not defined +- name: Set vars + set_fact: + aci_info: &aci_info + host: '{{ aci_hostname }}' + username: '{{ aci_username }}' + password: '{{ aci_password }}' + validate_certs: '{{ aci_validate_certs | default(false) }}' + use_ssl: '{{ aci_use_ssl | default(true) }}' + use_proxy: '{{ aci_use_proxy | default(true) }}' + output_level: debug # CLEAN ENVIRONMENT - name: Ensure tenant exists for tests to kick off cisco.aci.aci_tenant: &aci_tenant_present - host: "{{ aci_hostname }}" - username: "{{ aci_username }}" - password: "{{ aci_password }}" - validate_certs: '{{ aci_validate_certs | default(false) }}' - use_ssl: '{{ aci_use_ssl | default(true) }}' - use_proxy: '{{ aci_use_proxy | default(true) }}' - output_level: debug + <<: *aci_info tenant: ansible_test - state: present - register: tenant_present - name: Remove OSPF interface policy cisco.aci.aci_interface_policy_ospf: &interface_policy_ospf_absent - host: '{{ aci_hostname }}' - username: '{{ aci_username }}' - password: '{{ aci_password }}' - validate_certs: '{{ aci_validate_certs | default(false) }}' - use_ssl: '{{ aci_use_ssl | default(true) }}' - use_proxy: '{{ aci_use_proxy | default(true) }}' - output_level: '{{ aci_output_level | default("info") }}' - tenant: ansible_test + <<: *aci_tenant_present ospf: ansible_ospf state: absent - # ADD OSPF INTERFACE POLICY - name: Add ospf interface policy (check_mode) cisco.aci.aci_interface_policy_ospf: &interface_policy_ospf_present - host: '{{ aci_hostname }}' - username: '{{ aci_username }}' - password: '{{ aci_password }}' - validate_certs: '{{ aci_validate_certs | default(false) }}' - use_ssl: '{{ aci_use_ssl | default(true) }}' - use_proxy: '{{ aci_use_proxy | default(true) }}' - output_level: '{{ aci_output_level | default("info") }}' - tenant: ansible_test - ospf: ansible_ospf + <<: *interface_policy_ospf_absent state: present check_mode: true register: cm_add_ospf_interface_policy @@ -70,47 +56,220 @@ assert: that: - cm_add_ospf_interface_policy is changed + - cm_add_ospf_interface_policy.current == [] + - cm_add_ospf_interface_policy.proposed.ospfIfPol.attributes.annotation == "orchestrator:ansible" + - cm_add_ospf_interface_policy.proposed.ospfIfPol.attributes.name == "ansible_ospf" - nm_add_ospf_interface_policy is changed - - nm_add_ospf_interface_policy.current.0.ospfIfPol.attributes.annotation == 'orchestrator:ansible' + - cm_add_ospf_interface_policy.previous == [] + - nm_add_ospf_interface_policy.current.0.ospfIfPol.attributes.annotation == "orchestrator:ansible" + - nm_add_ospf_interface_policy.current.0.ospfIfPol.attributes.descr == "" + - nm_add_ospf_interface_policy.current.0.ospfIfPol.attributes.name == "ansible_ospf" + - nm_add_ospf_interface_policy.current.0.ospfIfPol.attributes.cost == "unspecified" + - nm_add_ospf_interface_policy.current.0.ospfIfPol.attributes.ctrl == "" + - nm_add_ospf_interface_policy.current.0.ospfIfPol.attributes.deadIntvl == "40" + - nm_add_ospf_interface_policy.current.0.ospfIfPol.attributes.helloIntvl == "10" + - nm_add_ospf_interface_policy.current.0.ospfIfPol.attributes.nwT == "unspecified" + - nm_add_ospf_interface_policy.current.0.ospfIfPol.attributes.pfxSuppress == "inherit" + - nm_add_ospf_interface_policy.current.0.ospfIfPol.attributes.prio == "1" + - nm_add_ospf_interface_policy.current.0.ospfIfPol.attributes.rexmitIntvl == "5" + - nm_add_ospf_interface_policy.current.0.ospfIfPol.attributes.xmitDelay == "1" - cm_add_ospf_interface_policy_again is not changed - nm_add_ospf_interface_policy_again is not changed - + - nm_add_ospf_interface_policy_again.previous.0.ospfIfPol.attributes.annotation == "orchestrator:ansible" + - nm_add_ospf_interface_policy_again.previous.0.ospfIfPol.attributes.descr == "" + - nm_add_ospf_interface_policy_again.previous.0.ospfIfPol.attributes.name == "ansible_ospf" + - nm_add_ospf_interface_policy_again.previous.0.ospfIfPol.attributes.cost == "unspecified" + - nm_add_ospf_interface_policy_again.previous.0.ospfIfPol.attributes.ctrl == "" + - nm_add_ospf_interface_policy_again.previous.0.ospfIfPol.attributes.deadIntvl == "40" + - nm_add_ospf_interface_policy_again.previous.0.ospfIfPol.attributes.helloIntvl == "10" + - nm_add_ospf_interface_policy_again.previous.0.ospfIfPol.attributes.nwT == "unspecified" + - nm_add_ospf_interface_policy_again.previous.0.ospfIfPol.attributes.pfxSuppress == "inherit" + - nm_add_ospf_interface_policy_again.previous.0.ospfIfPol.attributes.prio == "1" + - nm_add_ospf_interface_policy_again.previous.0.ospfIfPol.attributes.rexmitIntvl == "5" + - nm_add_ospf_interface_policy_again.previous.0.ospfIfPol.attributes.xmitDelay == "1" + - nm_add_ospf_interface_policy_again.current.0.ospfIfPol.attributes.annotation == "orchestrator:ansible" + - nm_add_ospf_interface_policy_again.current.0.ospfIfPol.attributes.descr == "" + - nm_add_ospf_interface_policy_again.current.0.ospfIfPol.attributes.name == "ansible_ospf" + - nm_add_ospf_interface_policy_again.current.0.ospfIfPol.attributes.cost == "unspecified" + - nm_add_ospf_interface_policy_again.current.0.ospfIfPol.attributes.ctrl == "" + - nm_add_ospf_interface_policy_again.current.0.ospfIfPol.attributes.deadIntvl == "40" + - nm_add_ospf_interface_policy_again.current.0.ospfIfPol.attributes.helloIntvl == "10" + - nm_add_ospf_interface_policy_again.current.0.ospfIfPol.attributes.nwT == "unspecified" + - nm_add_ospf_interface_policy_again.current.0.ospfIfPol.attributes.pfxSuppress == "inherit" + - nm_add_ospf_interface_policy_again.current.0.ospfIfPol.attributes.prio == "1" + - nm_add_ospf_interface_policy_again.current.0.ospfIfPol.attributes.rexmitIntvl == "5" + - nm_add_ospf_interface_policy_again.current.0.ospfIfPol.attributes.xmitDelay == "1" # CHANGE OSPF INTERFACE POLICY - name: Change description of ospf interface policy (check_mode) - cisco.aci.aci_interface_policy_ospf: + cisco.aci.aci_interface_policy_ospf: &change_osp_interface_policy <<: *interface_policy_ospf_present - description: Ansible test ospf interface policy + description: Ansible test ospf interface policy changed + network_type: bcast + cost: 11 + controls: mtu-ignore + dead_interval: 12 + hello_interval: 13 + prefix_suppression: disable + priority: 14 + retransmit_interval: 15 + transmit_delay: 16 check_mode: true register: cm_add_ospf_descr - name: Change description of ospf interface policy (normal mode) cisco.aci.aci_interface_policy_ospf: - <<: *interface_policy_ospf_present - description: Ansible test ospf interface policy + <<: *change_osp_interface_policy register: nm_add_ospf_descr - name: Change description of ospf interface policy again (check_mode) cisco.aci.aci_interface_policy_ospf: - <<: *interface_policy_ospf_present - description: Ansible test ospf interface policy + <<: *change_osp_interface_policy check_mode: true register: cm_add_ospf_descr_again - name: Change description of ospf interface policy again (normal mode) cisco.aci.aci_interface_policy_ospf: - <<: *interface_policy_ospf_present - description: Ansible test ospf interface policy + <<: *change_osp_interface_policy register: nm_add_ospf_descr_again - name: Verify add_ospf_descr assert: that: - cm_add_ospf_descr is changed + - cm_add_ospf_descr.previous.0.ospfIfPol.attributes.annotation == "orchestrator:ansible" + - cm_add_ospf_descr.previous.0.ospfIfPol.attributes.descr == "" + - cm_add_ospf_descr.previous.0.ospfIfPol.attributes.name == "ansible_ospf" + - cm_add_ospf_descr.previous.0.ospfIfPol.attributes.cost == "unspecified" + - cm_add_ospf_descr.previous.0.ospfIfPol.attributes.ctrl == "" + - cm_add_ospf_descr.previous.0.ospfIfPol.attributes.deadIntvl == "40" + - cm_add_ospf_descr.previous.0.ospfIfPol.attributes.helloIntvl == "10" + - cm_add_ospf_descr.previous.0.ospfIfPol.attributes.nwT == "unspecified" + - cm_add_ospf_descr.previous.0.ospfIfPol.attributes.pfxSuppress == "inherit" + - cm_add_ospf_descr.previous.0.ospfIfPol.attributes.prio == "1" + - cm_add_ospf_descr.previous.0.ospfIfPol.attributes.rexmitIntvl == "5" + - cm_add_ospf_descr.previous.0.ospfIfPol.attributes.xmitDelay == "1" + - cm_add_ospf_descr.proposed.ospfIfPol.attributes.annotation == "orchestrator:ansible" + - cm_add_ospf_descr.proposed.ospfIfPol.attributes.descr == "Ansible test ospf interface policy changed" + - cm_add_ospf_descr.proposed.ospfIfPol.attributes.name == "ansible_ospf" + - cm_add_ospf_descr.proposed.ospfIfPol.attributes.cost == "11" + - cm_add_ospf_descr.proposed.ospfIfPol.attributes.ctrl == "mtu-ignore" + - cm_add_ospf_descr.proposed.ospfIfPol.attributes.deadIntvl == "12" + - cm_add_ospf_descr.proposed.ospfIfPol.attributes.helloIntvl == "13" + - cm_add_ospf_descr.proposed.ospfIfPol.attributes.nwT == "bcast" + - cm_add_ospf_descr.proposed.ospfIfPol.attributes.pfxSuppress == "disable" + - cm_add_ospf_descr.proposed.ospfIfPol.attributes.prio == "14" + - cm_add_ospf_descr.proposed.ospfIfPol.attributes.rexmitIntvl == "15" + - cm_add_ospf_descr.proposed.ospfIfPol.attributes.xmitDelay == "16" - nm_add_ospf_descr is changed + - nm_add_ospf_descr.previous.0.ospfIfPol.attributes.annotation == "orchestrator:ansible" + - nm_add_ospf_descr.previous.0.ospfIfPol.attributes.descr == "" + - nm_add_ospf_descr.previous.0.ospfIfPol.attributes.name == "ansible_ospf" + - nm_add_ospf_descr.previous.0.ospfIfPol.attributes.cost == "unspecified" + - nm_add_ospf_descr.previous.0.ospfIfPol.attributes.ctrl == "" + - nm_add_ospf_descr.previous.0.ospfIfPol.attributes.deadIntvl == "40" + - nm_add_ospf_descr.previous.0.ospfIfPol.attributes.helloIntvl == "10" + - nm_add_ospf_descr.previous.0.ospfIfPol.attributes.nwT == "unspecified" + - nm_add_ospf_descr.previous.0.ospfIfPol.attributes.pfxSuppress == "inherit" + - nm_add_ospf_descr.previous.0.ospfIfPol.attributes.prio == "1" + - nm_add_ospf_descr.previous.0.ospfIfPol.attributes.rexmitIntvl == "5" + - nm_add_ospf_descr.previous.0.ospfIfPol.attributes.xmitDelay == "1" + - nm_add_ospf_descr.current.0.ospfIfPol.attributes.annotation == "orchestrator:ansible" + - nm_add_ospf_descr.current.0.ospfIfPol.attributes.descr == "Ansible test ospf interface policy changed" + - nm_add_ospf_descr.current.0.ospfIfPol.attributes.name == "ansible_ospf" + - nm_add_ospf_descr.current.0.ospfIfPol.attributes.cost == "11" + - nm_add_ospf_descr.current.0.ospfIfPol.attributes.ctrl == "mtu-ignore" + - nm_add_ospf_descr.current.0.ospfIfPol.attributes.deadIntvl == "12" + - nm_add_ospf_descr.current.0.ospfIfPol.attributes.helloIntvl == "13" + - nm_add_ospf_descr.current.0.ospfIfPol.attributes.nwT == "bcast" + - nm_add_ospf_descr.current.0.ospfIfPol.attributes.pfxSuppress == "disable" + - nm_add_ospf_descr.current.0.ospfIfPol.attributes.prio == "14" + - nm_add_ospf_descr.current.0.ospfIfPol.attributes.rexmitIntvl == "15" + - nm_add_ospf_descr.current.0.ospfIfPol.attributes.xmitDelay == "16" - cm_add_ospf_descr_again is not changed - nm_add_ospf_descr_again is not changed + - nm_add_ospf_descr_again.previous.0.ospfIfPol.attributes.annotation == "orchestrator:ansible" + - nm_add_ospf_descr_again.previous.0.ospfIfPol.attributes.descr == "Ansible test ospf interface policy changed" + - nm_add_ospf_descr_again.previous.0.ospfIfPol.attributes.name == "ansible_ospf" + - nm_add_ospf_descr_again.previous.0.ospfIfPol.attributes.cost == "11" + - nm_add_ospf_descr_again.previous.0.ospfIfPol.attributes.ctrl == "mtu-ignore" + - nm_add_ospf_descr_again.previous.0.ospfIfPol.attributes.deadIntvl == "12" + - nm_add_ospf_descr_again.previous.0.ospfIfPol.attributes.helloIntvl == "13" + - nm_add_ospf_descr_again.previous.0.ospfIfPol.attributes.nwT == "bcast" + - nm_add_ospf_descr_again.previous.0.ospfIfPol.attributes.pfxSuppress == "disable" + - nm_add_ospf_descr_again.previous.0.ospfIfPol.attributes.prio == "14" + - nm_add_ospf_descr_again.previous.0.ospfIfPol.attributes.rexmitIntvl == "15" + - nm_add_ospf_descr_again.previous.0.ospfIfPol.attributes.xmitDelay == "16" + - nm_add_ospf_descr_again.current.0.ospfIfPol.attributes.annotation == "orchestrator:ansible" + - nm_add_ospf_descr_again.current.0.ospfIfPol.attributes.descr == "Ansible test ospf interface policy changed" + - nm_add_ospf_descr_again.current.0.ospfIfPol.attributes.name == "ansible_ospf" + - nm_add_ospf_descr_again.current.0.ospfIfPol.attributes.cost == "11" + - nm_add_ospf_descr_again.current.0.ospfIfPol.attributes.ctrl == "mtu-ignore" + - nm_add_ospf_descr_again.current.0.ospfIfPol.attributes.deadIntvl == "12" + - nm_add_ospf_descr_again.current.0.ospfIfPol.attributes.helloIntvl == "13" + - nm_add_ospf_descr_again.current.0.ospfIfPol.attributes.nwT == "bcast" + - nm_add_ospf_descr_again.current.0.ospfIfPol.attributes.pfxSuppress == "disable" + - nm_add_ospf_descr_again.current.0.ospfIfPol.attributes.prio == "14" + - nm_add_ospf_descr_again.current.0.ospfIfPol.attributes.rexmitIntvl == "15" + - nm_add_ospf_descr_again.current.0.ospfIfPol.attributes.xmitDelay == "16" + +- name: Change cost of ospf interface policy + cisco.aci.aci_interface_policy_ospf: + <<: *interface_policy_ospf_present + cost: 451 + ignore_errors: true + register: err_change_ospf_cost + +- name: Change dead_interval of ospf interface policy + cisco.aci.aci_interface_policy_ospf: + <<: *interface_policy_ospf_present + dead_interval: 65539 + ignore_errors: true + register: err_change_ospf_dead_interval + +- name: Change hello_interval of ospf interface policy + cisco.aci.aci_interface_policy_ospf: + <<: *interface_policy_ospf_present + hello_interval: 65538 + ignore_errors: true + register: err_change_ospf_hello_interval +- name: Change priority of ospf interface policy + cisco.aci.aci_interface_policy_ospf: + <<: *interface_policy_ospf_present + priority: 256 + ignore_errors: true + register: err_change_ospf_priority + +- name: Change retransmit_interval of ospf interface policy + cisco.aci.aci_interface_policy_ospf: + <<: *interface_policy_ospf_present + retransmit_interval: 65537 + ignore_errors: true + register: err_change_ospf_retransmit_interval + +- name: Change transmit_delay of ospf interface policy + cisco.aci.aci_interface_policy_ospf: + <<: *interface_policy_ospf_present + transmit_delay: 451 + ignore_errors: true + register: err_change_ospf_transmit_delay + +- name: Verify cost change and error input values + assert: + that: + - err_change_ospf_cost is not changed + - err_change_ospf_cost.msg == "Parameter 'cost' is only valid in range between 1 and 450." + - err_change_ospf_dead_interval is not changed + - err_change_ospf_dead_interval.msg == "Parameter 'dead_interval' is only valid in range between 1 and 65536." + - err_change_ospf_hello_interval is not changed + - err_change_ospf_hello_interval.msg == "Parameter 'hello_interval' is only valid in range between 1 and 65536." + - err_change_ospf_priority is not changed + - err_change_ospf_priority.msg == "Parameter 'priority' is only valid in range between 1 and 255." + - err_change_ospf_retransmit_interval is not changed + - err_change_ospf_retransmit_interval.msg == "Parameter 'retransmit_interval' is only valid in range between 1 and 65536." + - err_change_ospf_transmit_delay is not changed + - err_change_ospf_transmit_delay.msg == "Parameter 'transmit_delay' is only valid in range between 1 and 450." # ADD OSPF INTERFACE POLICY AGAIN - name: Add ospf interface policy again with no description (check_mode) diff --git a/ansible_collections/cisco/aci/tests/integration/targets/aci_interface_policy_port_channel/aliases b/ansible_collections/cisco/aci/tests/integration/targets/aci_interface_policy_port_channel/aliases new file mode 100644 index 000000000..209b793f9 --- /dev/null +++ b/ansible_collections/cisco/aci/tests/integration/targets/aci_interface_policy_port_channel/aliases @@ -0,0 +1,2 @@ +# No ACI simulator yet, so not enabled +# unsupported diff --git a/ansible_collections/cisco/aci/tests/integration/targets/aci_interface_policy_port_channel/tasks/main.yml b/ansible_collections/cisco/aci/tests/integration/targets/aci_interface_policy_port_channel/tasks/main.yml new file mode 100644 index 000000000..36c84b30f --- /dev/null +++ b/ansible_collections/cisco/aci/tests/integration/targets/aci_interface_policy_port_channel/tasks/main.yml @@ -0,0 +1,174 @@ +# Test code for the ACI modules +# Copyright: (c) 2023, Gaspard Micol (@gmicol) <gmicol@cisco.com> + +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +- name: Test that we have an ACI APIC host, ACI username and ACI password + fail: + msg: 'Please define the following variables: aci_hostname, aci_username and aci_password.' + when: aci_hostname is not defined or aci_username is not defined or aci_password is not defined + +- name: Set vars + set_fact: + aci_info: &aci_info + host: "{{ aci_hostname }}" + username: "{{ aci_username }}" + password: "{{ aci_password }}" + validate_certs: '{{ aci_validate_certs | default(false) }}' + use_ssl: '{{ aci_use_ssl | default(true) }}' + use_proxy: '{{ aci_use_proxy | default(true) }}' + output_level: debug + +- name: Verify Cloud and Non-Cloud Sites in use. + include_tasks: ../../../../../../integration/targets/aci_cloud_provider/tasks/main.yml + +- name: Execute tasks only for non-cloud sites + when: query_cloud.current == [] # This condition will execute only non-cloud sites + block: # block specifies execution of tasks within, based on conditions + # Clean Environment + - name: Ensure first port channel interface policy does not exist + aci_interface_policy_port_channel: &aci_interface_policy_port_channel_absent + <<: *aci_info + name: anstest_policy + description: test for port channel interface policy + max_links: 8 + min_links: 2 + mode: active + fast_select: false + graceful_convergence: false + load_defer: true + suspend_individual: false + symmetric_hash: true + state: absent + + - name: Ensure second port channel interface policy does not exist + aci_interface_policy_port_channel: &aci_interface_policy_port_channel_2_absent + <<: *aci_info + name: anstest_policy_2 + fast_select: true + graceful_convergence: true + suspend_individual: true + state: absent + + - name: Create first port channel interface policy (check_mode) + aci_interface_policy_port_channel: &aci_interface_policy_port_channel_present + <<: *aci_interface_policy_port_channel_absent + state: present + check_mode: true + register: cm_add_interface_policy_port_channel_1 + + - name: Create first port channel interface policy (normal_mode) + aci_interface_policy_port_channel: + <<: *aci_interface_policy_port_channel_present + register: nm_add_interface_policy_port_channel_1 + + - name: Create first port channel interface policy again - testing idempotency + aci_interface_policy_port_channel: + <<: *aci_interface_policy_port_channel_present + register: idempotency_add_interface_policy_port_channel_1 + + - name: Create second port channel interface policy + aci_interface_policy_port_channel: &aci_interface_policy_port_channel_2_present + <<: *aci_interface_policy_port_channel_2_absent + state: present + register: nm_add_interface_policy_port_channel_2 + + - name: Modify first port channel interface policy with max links above 16 - testing failure message + aci_interface_policy_port_channel: + <<: *aci_interface_policy_port_channel_present + max_links: 17 + ignore_errors: true + register: nm_policy_port_channel_max_links_failure + + - name: Modify first port channel interface policy with min links bellow 1 - testing failure message + aci_interface_policy_port_channel: + <<: *aci_interface_policy_port_channel_present + min_links: 0 + ignore_errors: true + register: nm_policy_port_channel_min_links_failure + + - name: Asserts for creation tasks + assert: + that: + - cm_add_interface_policy_port_channel_1 is changed + - cm_add_interface_policy_port_channel_1.previous == [] + - cm_add_interface_policy_port_channel_1.current == [] + - nm_add_interface_policy_port_channel_1 is changed + - nm_add_interface_policy_port_channel_1.current.0.lacpLagPol.attributes.name == "anstest_policy" + - nm_add_interface_policy_port_channel_1.current.0.lacpLagPol.attributes.ctrl == "load-defer,symmetric-hash" + - nm_add_interface_policy_port_channel_1.current.0.lacpLagPol.attributes.maxLinks == "8" + - nm_add_interface_policy_port_channel_1.current.0.lacpLagPol.attributes.minLinks == "2" + - nm_add_interface_policy_port_channel_1.current.0.lacpLagPol.attributes.mode == "active" + - idempotency_add_interface_policy_port_channel_1 is not changed + - nm_add_interface_policy_port_channel_2 is changed + - nm_add_interface_policy_port_channel_2.current.0.lacpLagPol.attributes.name == "anstest_policy_2" + - nm_add_interface_policy_port_channel_2.current.0.lacpLagPol.attributes.ctrl == "fast-sel-hot-stdby,graceful-conv,susp-individual" + - nm_add_interface_policy_port_channel_2.current.0.lacpLagPol.attributes.maxLinks == "16" + - nm_add_interface_policy_port_channel_2.current.0.lacpLagPol.attributes.minLinks == "1" + - nm_add_interface_policy_port_channel_2.current.0.lacpLagPol.attributes.mode == "off" + - nm_policy_port_channel_max_links_failure.msg == "The \"max_links\" must be a value between 1 and 16" + - nm_policy_port_channel_min_links_failure.msg == "The \"min_links\" must be a value between 1 and 16" + + - name: Query all port channel interface policies + aci_interface_policy_port_channel: + <<: *aci_info + state: query + register: query_all_interface_policy_port_channel + + - name: Query first port channel interface policy + aci_interface_policy_port_channel: + <<: *aci_interface_policy_port_channel_present + state: query + register: query_first_interface_policy_port_channel + + - name: Asserts for query tasks + assert: + that: + - query_all_interface_policy_port_channel is not changed + - query_all_interface_policy_port_channel.current | length >= 2 + - '"class/lacpLagPol.json" in query_all_interface_policy_port_channel.url' + - query_first_interface_policy_port_channel is not changed + - query_first_interface_policy_port_channel.current.0.lacpLagPol.attributes.name == "anstest_policy" + - query_first_interface_policy_port_channel.current.0.lacpLagPol.attributes.ctrl == "load-defer,symmetric-hash" + - query_first_interface_policy_port_channel.current.0.lacpLagPol.attributes.maxLinks == "8" + - query_first_interface_policy_port_channel.current.0.lacpLagPol.attributes.minLinks == "2" + - query_first_interface_policy_port_channel.current.0.lacpLagPol.attributes.mode == "active" + + - name: Delete first port channel interface policy (check_mode) + aci_interface_policy_port_channel: + <<: *aci_interface_policy_port_channel_present + state: absent + check_mode: true + register: cm_delete_interface_policy_port_channel_1 + + - name: Delete first port channel interface policy (normal_mode) + aci_interface_policy_port_channel: + <<: *aci_interface_policy_port_channel_present + state: absent + register: nm_delete_interface_policy_port_channel_1 + + - name: Delete first port channel interface policy again - testing idempotency + aci_interface_policy_port_channel: + <<: *aci_interface_policy_port_channel_present + state: absent + register: idempotency_delete_interface_policy_port_channel_1 + + - name: Delete second port channel interface policy (normal_mode) + aci_interface_policy_port_channel: + <<: *aci_interface_policy_port_channel_2_present + state: absent + register: nm_delete_interface_policy_port_channel_2 + + - name: Asserts for deletion tasks + assert: + that: + - cm_delete_interface_policy_port_channel_1 is changed + - cm_delete_interface_policy_port_channel_1.proposed == {} + - nm_delete_interface_policy_port_channel_1 is changed + - nm_delete_interface_policy_port_channel_1.previous != [] + - nm_delete_interface_policy_port_channel_1.current == [] + - idempotency_delete_interface_policy_port_channel_1 is not changed + - idempotency_delete_interface_policy_port_channel_1.previous == [] + - nm_delete_interface_policy_port_channel_2 is changed + - nm_delete_interface_policy_port_channel_2.previous != [] + - nm_delete_interface_policy_port_channel_2.current == [] diff --git a/ansible_collections/cisco/aci/tests/integration/targets/aci_interface_policy_spine_policy_group/aliases b/ansible_collections/cisco/aci/tests/integration/targets/aci_interface_policy_spine_policy_group/aliases new file mode 100644 index 000000000..209b793f9 --- /dev/null +++ b/ansible_collections/cisco/aci/tests/integration/targets/aci_interface_policy_spine_policy_group/aliases @@ -0,0 +1,2 @@ +# No ACI simulator yet, so not enabled +# unsupported diff --git a/ansible_collections/cisco/aci/tests/integration/targets/aci_interface_policy_spine_policy_group/tasks/main.yml b/ansible_collections/cisco/aci/tests/integration/targets/aci_interface_policy_spine_policy_group/tasks/main.yml new file mode 100644 index 000000000..fec8415a3 --- /dev/null +++ b/ansible_collections/cisco/aci/tests/integration/targets/aci_interface_policy_spine_policy_group/tasks/main.yml @@ -0,0 +1,130 @@ +# Test code for the ACI modules +# Copyright: (c) 2023, Anvitha Jain <anvjain@cisco.com> + +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +- name: Test that we have an ACI APIC host, ACI username and ACI password + fail: + msg: 'Please define the following variables: aci_hostname, aci_username and aci_password.' + when: aci_hostname is not defined or aci_username is not defined or aci_password is not defined + +- name: Set vars + set_fact: + aci_info: &aci_info + host: "{{ aci_hostname }}" + username: "{{ aci_username }}" + password: "{{ aci_password }}" + validate_certs: '{{ aci_validate_certs | default(false) }}' + use_ssl: '{{ aci_use_ssl | default(true) }}' + use_proxy: '{{ aci_use_proxy | default(true) }}' + output_level: debug + +- name: Verify Cloud and Non-Cloud Sites in use. + include_tasks: ../../../../../../integration/targets/aci_cloud_provider/tasks/main.yml + +- name: Execute tasks only for non-cloud sites + when: query_cloud.current == [] # This condition will execute only non-cloud sites + block: # block specifies execution of tasks within, based on conditions + + # CLEAN ENVIRONMENT + - name: Making sure interface_policy_spine_policy_group doesn't exist at beginning of test (Spine Access Port) + cisco.aci.aci_interface_policy_spine_policy_group: &spine_absent + <<: *aci_info + policy_group: spine_pg_test + state: absent + + # ADD Spine Policy Group + - name: Adding a interface policy spine policy group - check mode works + cisco.aci.aci_interface_policy_spine_policy_group: &spine_present + <<: *aci_info + policy_group: spine_pg_test + link_level_policy: link_level_policy_test + link_flap_policy: link_flap_policy_test + cdp_policy: cdp_policy_test + mac_sec_policy: mac_sec_policy_test + attached_entity_profile: policy_group_aep + state: present + check_mode: yes + register: intf_policy_spine_polgrp_check_mode_present + + - name: Adding a interface policy spine policy group - creation works + cisco.aci.aci_interface_policy_spine_policy_group: + <<: *spine_present + register: intf_policy_spine_polgrp_present + + - name: Adding a interface policy spine policy group - idempotency works + cisco.aci.aci_interface_policy_spine_policy_group: + <<: *spine_present + register: intf_policy_spine_polgrp_idempotent + + # UPDATE Spine Policy Group + - name: Adding a interface policy spine policy group description - update works + cisco.aci.aci_interface_policy_spine_policy_group: + <<: *spine_present + description: policygroup description + register: intf_policy_spine_polgrp_update + + - name: Verify present assertions for Spine Policy Group + assert: + that: + - intf_policy_spine_polgrp_check_mode_present is changed + - intf_policy_spine_polgrp_present is changed + - intf_policy_spine_polgrp_present.previous == [] + - intf_policy_spine_polgrp_present.current[0].infraSpAccPortGrp.attributes.name == 'spine_pg_test' + - intf_policy_spine_polgrp_present.current[0].infraSpAccPortGrp.children.0.infraRsLinkFlapPol.attributes.tnFabricLinkFlapPolName == 'link_flap_policy_test' + - intf_policy_spine_polgrp_present.current[0].infraSpAccPortGrp.children.1.infraRsMacsecIfPol.attributes.tnMacsecIfPolName == 'mac_sec_policy_test' + - intf_policy_spine_polgrp_present.current[0].infraSpAccPortGrp.children.2.infraRsAttEntP.attributes.tDn == 'uni/infra/attentp-policy_group_aep' + - intf_policy_spine_polgrp_present.current[0].infraSpAccPortGrp.children.3.infraRsHIfPol.attributes.tnFabricHIfPolName == 'link_level_policy_test' + - intf_policy_spine_polgrp_present.current[0].infraSpAccPortGrp.children.4.infraRsCdpIfPol.attributes.tnCdpIfPolName == 'cdp_policy_test' + - intf_policy_spine_polgrp_present.current[0].infraSpAccPortGrp.attributes.annotation == 'orchestrator:ansible' + - intf_policy_spine_polgrp_idempotent is not changed + - intf_policy_spine_polgrp_idempotent.sent == {} + - intf_policy_spine_polgrp_update is changed + - intf_policy_spine_polgrp_update.current[0].infraSpAccPortGrp.attributes.descr == 'policygroup description' + + # QUERY Spine Policy Group + - name: Query all interface policy spine policy groups + cisco.aci.aci_interface_policy_spine_policy_group: + <<: *aci_info + state: query + register: query_all_spine_policy_groups + + - name: Query interface policy spine policy group + cisco.aci.aci_interface_policy_spine_policy_group: + <<: *aci_info + policy_group: spine_pg_test + state: query + register: query_spine_policy_group + + - name: Verify query assertions for Spine Policy Group + assert: + that: + - query_spine_policy_group is not changed + - query_spine_policy_group.current[0] | length >= 1 + - query_all_spine_policy_groups is not changed + + - name: Remove interface policy spine policy group - check mode + cisco.aci.aci_interface_policy_spine_policy_group: + <<: *spine_absent + check_mode: yes + register: intf_policy_spine_polgrp_check_mode_absent + + - name: Remove interface policy spine policy group - delete works + cisco.aci.aci_interface_policy_spine_policy_group: + <<: *spine_absent + register: intf_policy_spine_polgrp_absent + + - name: Remove interface policy spine policy group - idempotency works + cisco.aci.aci_interface_policy_spine_policy_group: + <<: *spine_absent + register: intf_policy_spine_polgrp_absent_idempotent + + - name: Verify absent assertions for Spine Policy Group + assert: + that: + - intf_policy_spine_polgrp_check_mode_absent is changed + - intf_policy_spine_polgrp_check_mode_absent.previous != [] + - intf_policy_spine_polgrp_absent is changed + - intf_policy_spine_polgrp_absent.previous == intf_policy_spine_polgrp_absent.previous + - intf_policy_spine_polgrp_absent_idempotent is not changed + - intf_policy_spine_polgrp_absent_idempotent.previous == [] diff --git a/ansible_collections/cisco/aci/tests/integration/targets/aci_l3out/aliases b/ansible_collections/cisco/aci/tests/integration/targets/aci_l3out/aliases new file mode 100644 index 000000000..209b793f9 --- /dev/null +++ b/ansible_collections/cisco/aci/tests/integration/targets/aci_l3out/aliases @@ -0,0 +1,2 @@ +# No ACI simulator yet, so not enabled +# unsupported diff --git a/ansible_collections/cisco/aci/tests/integration/targets/aci_l3out/tasks/main.yml b/ansible_collections/cisco/aci/tests/integration/targets/aci_l3out/tasks/main.yml new file mode 100644 index 000000000..65474048c --- /dev/null +++ b/ansible_collections/cisco/aci/tests/integration/targets/aci_l3out/tasks/main.yml @@ -0,0 +1,270 @@ +# Test code for the ACI modules +# Copyright: (c) 2023, Gaspard Micol (@gmicol) <gmicol@cisco.com> + +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +- name: Test that we have an ACI APIC host, ACI username and ACI password + fail: + msg: 'Please define the following variables: aci_hostname, aci_username and aci_password.' + when: aci_hostname is not defined or aci_username is not defined or aci_password is not defined + +- name: Set vars + set_fact: + aci_info: &aci_info + host: "{{ aci_hostname }}" + username: "{{ aci_username }}" + password: "{{ aci_password }}" + validate_certs: '{{ aci_validate_certs | default(false) }}' + use_ssl: '{{ aci_use_ssl | default(true) }}' + use_proxy: '{{ aci_use_proxy | default(true) }}' + output_level: debug + +# CLEAN ENVIRONMENT +- name: Remove the ansible_tenant + aci_tenant: + <<: *aci_info + tenant: ansible_tenant + state: absent + +- name: Verify Cloud and Non-Cloud Sites in use. + include_tasks: ../../../../../../integration/targets/aci_cloud_provider/tasks/main.yml + +- name: Execute tasks only for non-cloud sites + when: query_cloud.current == [] # This condition will execute only non-cloud sites + block: # block specifies execution of tasks within, based on conditions + - name: Add a new tenant + aci_tenant: + <<: *aci_info + tenant: ansible_tenant + description: Ansible tenant + state: present + + - name: Remove the ansible_l3out + aci_l3out: &aci_l3out_absent + <<: *aci_info + tenant: ansible_tenant + name: ansible_l3out + description: L3Out for ansible_tenant tenant + domain: ansible_dom + vrf: ansible_vrf + state: absent + + - name: Remove the second ansible_l3out + aci_l3out: &aci_l3out_2_absent + <<: *aci_info + tenant: ansible_tenant + name: ansible_l3out_2 + description: Second L3Out + domain: ansible_dom + vrf: ansible_vrf + l3protocol: [ ospf, bgp ] + state: absent + + - name: Add a new L3Out (check_mode) + aci_l3out: &aci_l3out_present + <<: *aci_l3out_absent + state: present + check_mode: true + register: cm_add_l3out + + - name: Add a new L3Out (normal_mode) + aci_l3out: + <<: *aci_l3out_present + register: nm_add_l3out + + - name: create L3Out again - testing idempotency + aci_l3out: + <<: *aci_l3out_present + register: create_idempotency + + - name: Add the second ansible_l3out + aci_l3out: + <<: *aci_l3out_2_absent + state: present + register: cm_add_l3out_2 + + - name: asserts for l3out creation tasks + assert: + that: + - cm_add_l3out is changed + - cm_add_l3out.previous == [] + - cm_add_l3out.current == [] + - nm_add_l3out is changed + - nm_add_l3out.current.0.l3extOut.attributes.descr == cm_add_l3out.sent.l3extOut.attributes.descr == "L3Out for ansible_tenant tenant" + - nm_add_l3out.current.0.l3extOut.attributes.name == cm_add_l3out.sent.l3extOut.attributes.name == "ansible_l3out" + - create_idempotency is not changed + + - name: Add export and import to route_control + aci_l3out: + <<: *aci_l3out_present + route_control: [ import, export ] + register: nm_add_l3out_import_export + + - name: Add export to route_control + aci_l3out: + <<: *aci_l3out_present + route_control: export + register: nm_add_l3out_export + + - name: Add just import to route_control - testing failure message + aci_l3out: + <<: *aci_l3out_present + route_control: import + register: nm_add_l3out_import_error + ignore_errors: true + + - name: asserts for l3out route control tasks + assert: + that: + - nm_add_l3out_import_export is changed + - nm_add_l3out_import_export.current.0.l3extOut.attributes.enforceRtctrl == "export,import" + - nm_add_l3out_export is changed + - nm_add_l3out_export.current.0.l3extOut.attributes.enforceRtctrl == "export" + - nm_add_l3out_import_error.msg == "The route_control parameter is invalid{{':'}} allowed options are export or import,export only" + + - name: Add BGP protocol + aci_l3out: + <<: *aci_l3out_present + l3protocol: bgp + register: nm_add_l3out_bgp + + - name: Add BGP protocol again - testing L3protocol changes + aci_l3out: + <<: *aci_l3out_present + l3protocol: bgp + register: nm_add_l3out_bgp_again + + - name: Add PIM protocol, ospf_spec and asn - testing warning message for asn + aci_l3out: + <<: *aci_l3out_present + l3protocol: pim + asn: 1 + register: nm_add_l3out_pim + + - name: Add EIGRP protocol - testing warning message for OSPF spec + aci_l3out: + <<: *aci_l3out_present + l3protocol: eigrp + asn: 1 + ospf: + description: OSPF warnings message works + register: nm_add_l3out_eigrp + + - name: remove asn - testing failure message + aci_l3out: + <<: *aci_l3out_present + l3protocol: eigrp + asn: null + register: add_l3out_without_asn + ignore_errors: true + + - name: remove protocol - testing static protocol + aci_l3out: + <<: *aci_l3out_present + l3protocol: static + register: nm_remove_l3protocol + + - name: asserts for l3out protocols tasks + assert: + that: + - nm_add_l3out_bgp is changed + - nm_add_l3out_bgp_again is changed + - nm_add_l3out_pim is changed + - nm_add_l3out_pim.warnings.0 == "Parameter 'asn' is only applicable when l3protocol is 'eigrp'. The ASN will be ignored" + - nm_add_l3out_eigrp is changed + - nm_add_l3out_eigrp.current.0.l3extOut.children.0.eigrpExtP.attributes.asn == "1" + - nm_add_l3out_eigrp.warnings.0 == "Parameter 'ospf' is only applicable when l3protocol is 'ospf'. The OPSF specifications will be ignored" + - add_l3out_without_asn.msg == "Parameter 'asn' is required when l3protocol is 'eigrp'" + - nm_remove_l3protocol is changed + + - name: Add OSPF protocol + aci_l3out: + <<: *aci_l3out_present + l3protocol: ospf + ospf: + area_cost: 1 + area_ctrl: [ summary, redistribute ] + area_id: 0.0.0.2 + area_type: regular + multipod_internal: no + description: test for ospf protocol + register: nm_add_l3out_ospf + + - name: asserts for l3out OSPF protocol tasks + assert: + that: + - nm_add_l3out_ospf is changed + - nm_add_l3out_ospf.current.0.l3extOut.children.2.ospfExtP.attributes.areaCost == "1" + - nm_add_l3out_ospf.current.0.l3extOut.children.2.ospfExtP.attributes.areaCtrl == "redistribute,summary" + - nm_add_l3out_ospf.current.0.l3extOut.children.2.ospfExtP.attributes.areaId == "0.0.0.2" + - nm_add_l3out_ospf.current.0.l3extOut.children.2.ospfExtP.attributes.areaType == "regular" + - nm_add_l3out_ospf.current.0.l3extOut.children.2.ospfExtP.attributes.descr == "test for ospf protocol" + - nm_add_l3out_ospf.current.0.l3extOut.children.2.ospfExtP.attributes.multipodInternal == "no" + + - name: get ansible_l3out + aci_l3out: + <<: *aci_l3out_present + state: query + register: get_l3out + + - name: get all l3outs in ansible_tenant + aci_l3out: + <<: *aci_info + tenant: ansible_tenant + state: query + register: get_all_l3out_ansible_tenant + + - name: get all l3outs + aci_l3out: + <<: *aci_info + state: query + register: get_all_l3out + + - name: asserts query tasks + assert: + that: + - get_l3out is not changed + - '"rsp-subtree=full&rsp-subtree-class=bgpExtP,eigrpExtP,l3extRsEctx,l3extRsL3DomAtt,ospfExtP,pimExtP" in get_l3out.filter_string' + - '"tn-ansible_tenant/out-ansible_l3out.json" in get_l3out.url' + - get_all_l3out_ansible_tenant is not changed + - '"ospfExtP" in get_all_l3out_ansible_tenant.current.0.fvTenant.children.0.l3extOut.children.2' + - '"ospfExtP" in get_all_l3out_ansible_tenant.current.0.fvTenant.children.1.l3extOut.children.2' + - '"bgpExtP" in get_all_l3out_ansible_tenant.current.0.fvTenant.children.1.l3extOut.children.3' + - get_all_l3out is not changed + - get_all_l3out.current | length >= 2 + + - name: delete l3out (check_mode) + aci_l3out: + <<: *aci_l3out_present + state: absent + check_mode: true + register: delete_cm + + - name: delete l3out (normal_mode) + aci_l3out: + <<: *aci_l3out_present + state: absent + register: delete_l3out + + - name: delete l3out again - testing idempotency + aci_l3out: + <<: *aci_l3out_present + state: absent + register: delete_idempotency + + - name: asserts for deletion tasks + assert: + that: + - delete_cm is changed + - delete_cm.proposed == {} + - delete_l3out is changed + - delete_l3out.previous != [] + - delete_l3out.method == "DELETE" + - delete_idempotency is not changed + - delete_idempotency.previous == [] + + - name: Delete the ansible_tenant - cleanup before ending tests + aci_tenant: + <<: *aci_info + tenant: ansible_tenant + state: absent diff --git a/ansible_collections/cisco/aci/tests/integration/targets/aci_l3out_bgp_peer/tasks/main.yml b/ansible_collections/cisco/aci/tests/integration/targets/aci_l3out_bgp_peer/tasks/main.yml index 4e1784a6a..8ed2daee3 100644 --- a/ansible_collections/cisco/aci/tests/integration/targets/aci_l3out_bgp_peer/tasks/main.yml +++ b/ansible_collections/cisco/aci/tests/integration/targets/aci_l3out_bgp_peer/tasks/main.yml @@ -201,6 +201,7 @@ address_type_controls: - af-ucast ttl: 2 + local_as_number_config: "replace-as" state: present register: add_eth_bgp_peer when: version.current.0.topSystem.attributes.version is version('4', '>=') @@ -239,6 +240,8 @@ - add_eth_bgp_peer.current.0.bgpPeerP.attributes.peerCtrl == "bfd" - add_eth_bgp_peer.current.0.bgpPeerP.attributes.ttl == "2" - add_eth_bgp_peer.current.0.bgpPeerP.attributes.annotation == 'orchestrator:ansible' + - add_eth_bgp_peer.current.0.bgpPeerP.children.0.bgpLocalAsnP.attributes.asnPropagate == "replace-as" + - add_eth_bgp_peer.current.0.bgpPeerP.children.0.bgpLocalAsnP.attributes.localAsn == "0" when: version.current.0.topSystem.attributes.version is version('4', '>=') - name: verify BGP peer has been created with correct attributes (version < 4) @@ -255,7 +258,7 @@ - name: verify remote AS object has been created correctly assert: that: - - add_eth_bgp_peer.current.0.bgpPeerP.children.1.bgpAsP.attributes.asn == "65456" + - add_eth_bgp_peer.current.0.bgpPeerP.children.2.bgpAsP.attributes.asn == "65456" when: version.current.0.topSystem.attributes.version is version('4', '>=') - name: verify remote AS object has been created correctly @@ -340,7 +343,7 @@ - name: verify remote AS object is still correct (version >= 4) assert: that: - - add_eth_bgp_peer_again.current.0.bgpPeerP.children.1.bgpAsP.attributes.asn == "65456" + - add_eth_bgp_peer_again.current.0.bgpPeerP.children.2.bgpAsP.attributes.asn == "65456" when: version.current.0.topSystem.attributes.version is version('4', '>=') - name: verify remote AS object his still correct (version < 4) @@ -376,6 +379,8 @@ allow_self_as_count: 3 ttl: 4 admin_state: disabled + local_as_number: 101 + local_as_number_config: "dual-as" state: present register: update_eth_bgp_peer when: version.current.0.topSystem.attributes.version is version('4', '>=') @@ -419,6 +424,8 @@ - update_eth_bgp_peer.current.0.bgpPeerP.attributes.ttl == "4" - update_eth_bgp_peer.current.0.bgpPeerP.attributes.allowedSelfAsCnt == "3" - update_eth_bgp_peer.current.0.bgpPeerP.attributes.privateASctrl == "remove-exclusive" + - update_eth_bgp_peer.current.0.bgpPeerP.children.0.bgpLocalAsnP.attributes.asnPropagate == "dual-as" + - update_eth_bgp_peer.current.0.bgpPeerP.children.0.bgpLocalAsnP.attributes.localAsn == "101" when: version.current.0.topSystem.attributes.version is version('4', '>=') - name: verify BGP peer has been updated with correct attributes (version < 4) @@ -437,7 +444,7 @@ - name: verify remote AS object has been updated correctly (version >= 4) assert: that: - - update_eth_bgp_peer.current.0.bgpPeerP.children.1.bgpAsP.attributes.asn == "65457" + - update_eth_bgp_peer.current.0.bgpPeerP.children.2.bgpAsP.attributes.asn == "65457" when: version.current.0.topSystem.attributes.version is version('4', '>=') - name: verify remote AS object has been updated correctly (version < 4) @@ -508,7 +515,7 @@ - name: verify BGP remote AS (version >= 4) assert: that: - - query_eth_bgp_peer.current.0.bgpPeerP.children.1.bgpAsP.attributes.asn == "65457" + - query_eth_bgp_peer.current.0.bgpPeerP.children.2.bgpAsP.attributes.asn == "65457" when: version.current.0.topSystem.attributes.version is version('4', '>=') - name: verify BGP remote AS (version < 4) @@ -641,6 +648,8 @@ profile: "anstest_export" direction: "export" l3out: "anstest_l3out" + local_as_number_config: "dual-as" + local_as_number: 100 state: present check_mode: true register: cm_ln_rtctrl_present @@ -668,6 +677,8 @@ - nm_ln_rtctrl_present.previous | length == 0 - nm_ln_rtctrl_present.current.0.bgpPeerP.attributes.addr == "192.168.50.3" - nm_ln_rtctrl_present.current.0.bgpPeerP.children | length >= 2 + - nm_ln_rtctrl_present.current.0.bgpPeerP.children.2.bgpLocalAsnP.attributes.asnPropagate == "dual-as" + - nm_ln_rtctrl_present.current.0.bgpPeerP.children.2.bgpLocalAsnP.attributes.localAsn == "100" - name: Add BGP Peer to the Node Profile level (version >= 4) - normal mode - idempotency works aci_l3out_bgp_peer: diff --git a/ansible_collections/cisco/aci/tests/integration/targets/aci_l3out_bgp_protocol_profile/aliases b/ansible_collections/cisco/aci/tests/integration/targets/aci_l3out_bgp_protocol_profile/aliases new file mode 100644 index 000000000..209b793f9 --- /dev/null +++ b/ansible_collections/cisco/aci/tests/integration/targets/aci_l3out_bgp_protocol_profile/aliases @@ -0,0 +1,2 @@ +# No ACI simulator yet, so not enabled +# unsupported diff --git a/ansible_collections/cisco/aci/tests/integration/targets/aci_l3out_bgp_protocol_profile/tasks/main.yml b/ansible_collections/cisco/aci/tests/integration/targets/aci_l3out_bgp_protocol_profile/tasks/main.yml new file mode 100644 index 000000000..85b43726c --- /dev/null +++ b/ansible_collections/cisco/aci/tests/integration/targets/aci_l3out_bgp_protocol_profile/tasks/main.yml @@ -0,0 +1,184 @@ +# Test code for the ACI modules +# Copyright: (c) 2023, Gaspard Micol (@gmicol) <gmicol@cisco.com> + +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +- name: Test that we have an ACI APIC host, ACI username and ACI password + fail: + msg: 'Please define the following variables: aci_hostname, aci_username and aci_password.' + when: aci_hostname is not defined or aci_username is not defined or aci_password is not defined + +- name: Set vars + set_fact: + aci_info: &aci_info + host: "{{ aci_hostname }}" + username: "{{ aci_username }}" + password: "{{ aci_password }}" + validate_certs: '{{ aci_validate_certs | default(false) }}' + use_ssl: '{{ aci_use_ssl | default(true) }}' + use_proxy: '{{ aci_use_proxy | default(true) }}' + output_level: debug + +# CLEAN ENVIRONMENT +- name: Remove the ansible_tenant + aci_tenant: &aci_tenant_absent + <<: *aci_info + tenant: ansible_tenant + state: absent + +- name: Verify Cloud and Non-Cloud Sites in use. + include_tasks: ../../../../../../integration/targets/aci_cloud_provider/tasks/main.yml + +- name: Execute tasks only for non-cloud sites + when: query_cloud.current == [] # This condition will execute only non-cloud sites + block: # block specifies execution of tasks within, based on conditions + - name: Add a new tenant + aci_tenant: &aci_tenant_present + <<: *aci_info + tenant: ansible_tenant + description: Ansible tenant + state: present + + - name: Add a new l3out + aci_l3out: + <<: *aci_info + tenant: ansible_tenant + l3out: ansible_l3out + description: Ansible L3Out for ansible_tenant tenant + domain: ansible_dom + vrf: ansible_vrf + state: present + + - name: Add a new logical node profile + aci_l3out_logical_node_profile: + <<: *aci_info + tenant: ansible_tenant + l3out: ansible_l3out + node_profile: ansible_node_profile_1 + description: Ansible Logical Node Profile 1 for ansible_l3out L3Out + state: present + + - name: Add a second logical node profile + aci_l3out_logical_node_profile: + <<: *aci_info + tenant: ansible_tenant + l3out: ansible_l3out + node_profile: ansible_node_profile_2 + description: Ansible Logical Node Profile 2 for ansible_l3out L3Out + state: present + + - name: Add a new BGP timers policy + aci_bgp_timers_policy: + <<: *aci_info + tenant: ansible_tenant + bgp_timers_policy: ansible_bgp_timers_policy + description: Ansible BGP timers policy for ansible_tenant tenant + state: present + + - name: Add a new BGP protocol profile + aci_bgp_best_path_policy: + <<: *aci_info + tenant: ansible_tenant + bgp_best_path_policy: ansible_bgp_best_path_policy + description: Ansible BGP protocol profile for ansible_tenant tenant + state: present + + - name: Add a BGP protocol profile (check_mode) + aci_l3out_bgp_protocol_profile: &aci_l3out_bgp_protocol_profile_present + <<: *aci_info + tenant: ansible_tenant + l3out: ansible_l3out + node_profile: ansible_node_profile_1 + bgp_timers_policy: ansible_bgp_timers_policy + bgp_best_path_policy: ansible_bgp_best_path_policy + state: present + check_mode: true + register: cm_add_bgp_protocol_profile + + - name: Add a BGP protocol profile (normal_mode) + aci_l3out_bgp_protocol_profile: + <<: *aci_l3out_bgp_protocol_profile_present + register: nm_add_bgp_protocol_profile + + - name: Add the first BGP protocol profile again - testing idempotency + aci_l3out_bgp_protocol_profile: + <<: *aci_l3out_bgp_protocol_profile_present + register: nm_add_bgp_protocol_profile_idempotency + + - name: Add a second BGP protocol profile (normal_mode) + aci_l3out_bgp_protocol_profile: + <<: *aci_info + tenant: ansible_tenant + l3out: ansible_l3out + node_profile: ansible_node_profile_2 + state: present + register: nm_add_bgp_protocol_profile_2 + + - name: Asserts for BGP protocol profiles creation tasks + assert: + that: + - cm_add_bgp_protocol_profile is changed + - cm_add_bgp_protocol_profile.previous == [] + - cm_add_bgp_protocol_profile.current == [] + - nm_add_bgp_protocol_profile is changed + - nm_add_bgp_protocol_profile.current.0.bgpProtP.children.0.bgpRsBestPathCtrlPol.attributes.tnBgpBestPathCtrlPolName == "ansible_bgp_best_path_policy" + - nm_add_bgp_protocol_profile.current.0.bgpProtP.children.1.bgpRsBgpNodeCtxPol.attributes.tnBgpCtxPolName == "ansible_bgp_timers_policy" + - nm_add_bgp_protocol_profile_idempotency is not changed + - nm_add_bgp_protocol_profile_2 is changed + - nm_add_bgp_protocol_profile_2.previous == [] + + - name: Query all BGP best path policies + aci_l3out_bgp_protocol_profile: + <<: *aci_info + state: query + register: query_all_bgp_protocol_profile + + - name: Query ansible_bgp_protocol_profile_1 + aci_l3out_bgp_protocol_profile: + <<: *aci_l3out_bgp_protocol_profile_present + state: query + register: query_ansible_bgp_protocol_profile_1 + + - name: Asserts query tasks + assert: + that: + - query_all_bgp_protocol_profile is not changed + - query_all_bgp_protocol_profile.current|length >= 2 + - query_ansible_bgp_protocol_profile_1 is not changed + - query_ansible_bgp_protocol_profile_1.current.0.bgpProtP.children.0.bgpRsBestPathCtrlPol.attributes.tDn == "uni/tn-ansible_tenant/bestpath-ansible_bgp_best_path_policy" + - query_ansible_bgp_protocol_profile_1.current.0.bgpProtP.children.0.bgpRsBestPathCtrlPol.attributes.state == "formed" + - query_ansible_bgp_protocol_profile_1.current.0.bgpProtP.children.1.bgpRsBgpNodeCtxPol.attributes.tDn == "uni/tn-ansible_tenant/bgpCtxP-ansible_bgp_timers_policy" + - query_ansible_bgp_protocol_profile_1.current.0.bgpProtP.children.1.bgpRsBgpNodeCtxPol.attributes.state == "formed" + + - name: Remove BGP protocol profile (check_mode) + aci_l3out_bgp_protocol_profile: &bgp_protocol_profile_absent + <<: *aci_l3out_bgp_protocol_profile_present + state: absent + check_mode: true + register: cm_remove_bgp_protocol_profile + + - name: Remove BGP protocol profile (normal_mode) + aci_l3out_bgp_protocol_profile: + <<: *bgp_protocol_profile_absent + register: nm_remove_bgp_protocol_profile + + - name: Remove BGP protocol profile - testing idempotency + aci_l3out_bgp_protocol_profile: + <<: *bgp_protocol_profile_absent + register: nm_remove_bgp_protocol_profile_idempotency + + - name: Asserts deletion tasks + assert: + that: + - cm_remove_bgp_protocol_profile is changed + - cm_remove_bgp_protocol_profile.proposed == {} + - nm_remove_bgp_protocol_profile is changed + - nm_remove_bgp_protocol_profile.previous != [] + - nm_remove_bgp_protocol_profile.method == "DELETE" + - nm_remove_bgp_protocol_profile_idempotency is not changed + - nm_remove_bgp_protocol_profile_idempotency.previous == [] + + - name: Remove the ansible_tenant - cleanup before ending tests + aci_tenant: + <<: *aci_tenant_present + state: absent
\ No newline at end of file diff --git a/ansible_collections/cisco/aci/tests/integration/targets/aci_l3out_extepg/aliases b/ansible_collections/cisco/aci/tests/integration/targets/aci_l3out_extepg/aliases new file mode 100644 index 000000000..209b793f9 --- /dev/null +++ b/ansible_collections/cisco/aci/tests/integration/targets/aci_l3out_extepg/aliases @@ -0,0 +1,2 @@ +# No ACI simulator yet, so not enabled +# unsupported diff --git a/ansible_collections/cisco/aci/tests/integration/targets/aci_l3out_extepg/tasks/main.yml b/ansible_collections/cisco/aci/tests/integration/targets/aci_l3out_extepg/tasks/main.yml new file mode 100644 index 000000000..d52fb5ef4 --- /dev/null +++ b/ansible_collections/cisco/aci/tests/integration/targets/aci_l3out_extepg/tasks/main.yml @@ -0,0 +1,364 @@ +# Test code for the ACI modules +# Copyright: (c) 2023, Akini Ross (akinross@cisco.com) + +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +- name: Test that we have an ACI APIC host, ACI username and ACI password + ansible.builtin.fail: + msg: 'Please define the following variables: aci_hostname, aci_username and aci_password.' + when: aci_hostname is not defined or aci_username is not defined or aci_password is not defined + +- name: Set vars + ansible.builtin.set_fact: + aci_info: &aci_info + host: "{{ aci_hostname }}" + username: "{{ aci_username }}" + password: "{{ aci_password }}" + validate_certs: '{{ aci_validate_certs | default(false) }}' + use_ssl: '{{ aci_use_ssl | default(true) }}' + use_proxy: '{{ aci_use_proxy | default(true) }}' + output_level: '{{ aci_output_level | default("info") }}' + aci_tenant: ansible_test + +- name: Verify Cloud and Non-Cloud Sites in use. + ansible.builtin.include_tasks: ../../../../../../integration/targets/aci_cloud_provider/tasks/main.yml + +- name: Execute tasks only for non-cloud sites + when: + - query_cloud.current == [] + block: + + - name: Query system information + aci_system: + <<: *aci_info + id: 1 + state: query + register: version + + # CLEAN TEST ENVIRONMENT + + - name: Ensure clean tenant configuration + cisco.aci.aci_tenant: + <<: *aci_info + tenant: "{{ aci_tenant }}" + state: "{{ item }}" + loop: + - absent + - present + + - name: Ensure clean vrf configuration + cisco.aci.aci_vrf: + <<: *aci_info + tenant: "{{ aci_tenant }}" + vrf: vrf_1 + + - name: Ensure clean l3out configuration + cisco.aci.aci_l3out: + <<: *aci_info + tenant: "{{ aci_tenant }}" + l3out: "{{ item }}" + vrf: vrf_1 + domain: dom_1 + loop: + - l3out_1 + - l3out_2 + + # TEST CREATE L3OUT EXTERNAL EPG + + - name: Add l3out external epg 1 ( check mode ) + cisco.aci.aci_l3out_extepg: &add_l3out_extepg_1 + <<: *aci_info + tenant: "{{ aci_tenant }}" + l3out: l3out_1 + extepg: extepg_1 + state: present + check_mode: true + register: cm_add_l3out_extepg_1 + + - name: Add l3out external epg 1 + cisco.aci.aci_l3out_extepg: + <<: *add_l3out_extepg_1 + route_control_profiles: + import_profile: "" + register: nm_add_l3out_extepg_1 + + - name: Add l3out external epg 1 again + cisco.aci.aci_l3out_extepg: + <<: *add_l3out_extepg_1 + register: nm_add_l3out_extepg_1_again + + - name: Verify add l3out external epg 1 + ansible.builtin.assert: + that: + - cm_add_l3out_extepg_1 is changed + - cm_add_l3out_extepg_1.current == [] + - cm_add_l3out_extepg_1.previous == [] + - cm_add_l3out_extepg_1.proposed.l3extInstP.attributes.name == "extepg_1" + - nm_add_l3out_extepg_1 is changed + - nm_add_l3out_extepg_1.previous == [] + - nm_add_l3out_extepg_1.current.0.l3extInstP.attributes.name == "extepg_1" + - nm_add_l3out_extepg_1.current.0.l3extInstP.attributes.descr == "" + - nm_add_l3out_extepg_1.current.0.l3extInstP.attributes.prefGrMemb == "exclude" + - nm_add_l3out_extepg_1.current.0.l3extInstP.attributes.targetDscp == "unspecified" + - nm_add_l3out_extepg_1_again is not changed + - nm_add_l3out_extepg_1_again.previous.0.l3extInstP.attributes.name == "extepg_1" + - nm_add_l3out_extepg_1_again.previous.0.l3extInstP.attributes.descr == "" + - nm_add_l3out_extepg_1_again.previous.0.l3extInstP.attributes.prefGrMemb == "exclude" + - nm_add_l3out_extepg_1_again.previous.0.l3extInstP.attributes.targetDscp == "unspecified" + - nm_add_l3out_extepg_1_again.current.0.l3extInstP.attributes.name == "extepg_1" + - nm_add_l3out_extepg_1_again.current.0.l3extInstP.attributes.descr == "" + - nm_add_l3out_extepg_1_again.current.0.l3extInstP.attributes.prefGrMemb == "exclude" + - nm_add_l3out_extepg_1_again.current.0.l3extInstP.attributes.targetDscp == "unspecified" + + - name: Change l3out external epg 1 + cisco.aci.aci_l3out_extepg: + <<: *add_l3out_extepg_1 + description: "changed description" + preferred_group: true + dscp: AF12 + register: nm_change_l3out_extepg_1 + + - name: Verify change l3out external epg 1 + ansible.builtin.assert: + that: + - nm_change_l3out_extepg_1 is changed + - nm_change_l3out_extepg_1.previous.0.l3extInstP.attributes.name == "extepg_1" + - nm_change_l3out_extepg_1.previous.0.l3extInstP.attributes.descr == "" + - nm_change_l3out_extepg_1.previous.0.l3extInstP.attributes.prefGrMemb == "exclude" + - nm_change_l3out_extepg_1.previous.0.l3extInstP.attributes.targetDscp == "unspecified" + - nm_change_l3out_extepg_1.current.0.l3extInstP.attributes.name == "extepg_1" + - nm_change_l3out_extepg_1.current.0.l3extInstP.attributes.descr == "changed description" + - nm_change_l3out_extepg_1.current.0.l3extInstP.attributes.prefGrMemb == "include" + - nm_change_l3out_extepg_1.current.0.l3extInstP.attributes.targetDscp == "AF12" + + - name: Add two more l3out external epgs + cisco.aci.aci_l3out_extepg: + <<: *aci_info + tenant: "{{ aci_tenant }}" + l3out: "{{ item.l3out }}" + extepg: "{{ item.extepg }}" + state: present + loop: + - {l3out: l3out_1, extepg: extepg_2} + - {l3out: l3out_2, extepg: extepg_3} + + - name: Add l3out external epg with the new optional params + cisco.aci.aci_l3out_extepg: + <<: *aci_info + tenant: ansible_test + l3out: l3out_1 + extepg: extepg_params + contract_exception_tag: contract_excepted + qos_class: level6 + route_control_profiles: + import_profile: profile1 + export_profile: profile2 + state: present + register: nm_l3out_extepg_optional_params + + - name: Add l3out external epg with the new optional params again + cisco.aci.aci_l3out_extepg: + <<: *aci_info + tenant: ansible_test + l3out: l3out_1 + extepg: extepg_params + contract_exception_tag: contract_excepted + qos_class: level6 + route_control_profiles: + import_profile: profile1 + export_profile: profile2 + state: present + register: nm_l3out_extepg_optional_params_again + + - name: Add l3out external epg with no export route control profile in the task + cisco.aci.aci_l3out_extepg: + <<: *aci_info + tenant: ansible_test + l3out: l3out_1 + extepg: extepg_params + contract_exception_tag: contract_excepted + qos_class: level6 + route_control_profiles: + import_profile: profile1 + state: present + register: nm_l3out_extepg_non_existent_export_profile + + - name: Add l3out external epg with export route control profile having "" + cisco.aci.aci_l3out_extepg: + <<: *aci_info + tenant: ansible_test + l3out: l3out_1 + extepg: extepg_params + contract_exception_tag: contract_excepted + qos_class: level6 + route_control_profiles: + import_profile: profile1 + export_profile: "" + state: present + register: nm_l3out_extepg_remove_export_profile + + - name: Add l3out external epg with export route control profile again + cisco.aci.aci_l3out_extepg: + <<: *aci_info + tenant: ansible_test + l3out: l3out_1 + extepg: extepg_params + contract_exception_tag: contract_excepted + qos_class: level6 + route_control_profiles: + import_profile: profile1 + export: profile2 + state: present + register: nm_l3out_extepg_add_export_profile + + - name: Add l3out external epg with intra_ext_epg_isolation + cisco.aci.aci_l3out_extepg: + <<: *aci_info + tenant: ansible_test + l3out: l3out_1 + extepg: extepg_isolation + intra_ext_epg_isolation: enforced + state: present + register: nm_l3out_extepg_isolation + when: + - version.current.0.topSystem.attributes.version is version('5.0', '>=') + + # TEST QUERY L3OUT EXTERNAL EPG + + - name: Query l3out external epg 1 + cisco.aci.aci_l3out_extepg: + <<: *add_l3out_extepg_1 + state: query + register: query_one + + - name: Query all l3out external epgs ( class query ) + cisco.aci.aci_l3out_extepg: + <<: *aci_info + state: query + register: query_all + + - name: Verify query l3out external epgs + ansible.builtin.assert: + that: + - query_one is not changed + - query_one.current | length == 1 + - query_one.current.0.l3extInstP.attributes.name == "extepg_1" + - query_all is not changed + - query_all.current | length >= 3 + + - name: Query l3out external epg with the new optional params + cisco.aci.aci_l3out_extepg: + <<: *aci_info + tenant: ansible_test + l3out: l3out_1 + extepg: extepg_params + state: query + register: query_l3out_extepg_optional_params + + - name: Query l3out external epg with intra_ext_epg_isolation + cisco.aci.aci_l3out_extepg: + <<: *aci_info + tenant: ansible_test + l3out: l3out_1 + extepg: extepg_isolation + state: query + register: query_l3out_extepg_isolation + when: + - version.current.0.topSystem.attributes.version is version('5.0', '>=') + + - name: Gather attribute search results using regex + ansible.builtin.set_fact: + profile1_name: "{{ nm_l3out_extepg_optional_params.current.0.l3extInstP.children | regex_search('profile1') }}" + profile1_direction: "{{ nm_l3out_extepg_optional_params.current.0.l3extInstP.children | regex_search('import') }}" + profile2_name: "{{ nm_l3out_extepg_optional_params.current.0.l3extInstP.children | regex_search('profile2') }}" + profile2_direction: "{{ nm_l3out_extepg_optional_params.current.0.l3extInstP.children | regex_search('export') }}" + profile2_exists: "{{ nm_l3out_extepg_non_existent_export_profile.current.0.l3extInstP.children | regex_search('profile2') }}" + profile2_removed: "{{ nm_l3out_extepg_remove_export_profile.current.0.l3extInstP.children | regex_search('profile2') }}" + profile2_direction_removed: "{{ nm_l3out_extepg_remove_export_profile.current.0.l3extInstP.children | regex_search('export') }}" + query_profile1_name: "{{ query_l3out_extepg_optional_params.current.0.l3extInstP.children | regex_search('profile1') }}" + query_profile1_direction: "{{ query_l3out_extepg_optional_params.current.0.l3extInstP.children | regex_search('import') }}" + query_profile2_name: "{{ query_l3out_extepg_optional_params.current.0.l3extInstP.children | regex_search('profile2') }}" + query_profile2_direction: "{{ query_l3out_extepg_optional_params.current.0.l3extInstP.children | regex_search('export') }}" + + - name: Verify l3out external epg with the new optional params + ansible.builtin.assert: + that: + - nm_l3out_extepg_optional_params is changed + - nm_l3out_extepg_optional_params_again is not changed + - nm_l3out_extepg_non_existent_export_profile is not changed + - nm_l3out_extepg_remove_export_profile is changed + - nm_l3out_extepg_add_export_profile is changed + - query_l3out_extepg_optional_params is not changed + - nm_l3out_extepg_optional_params.current.0.l3extInstP.attributes.name == "extepg_params" + - nm_l3out_extepg_optional_params.current.0.l3extInstP.attributes.prio == "level6" + - profile1_name == "profile1" + - profile1_direction == "import" + - profile2_name == "profile2" + - profile2_direction == "export" + - query_profile1_name == "profile1" + - query_profile1_direction == "import" + - query_profile2_name == "profile2" + - query_profile2_direction == "export" + - profile2_exists == "profile2" + - profile2_removed == "" + - profile2_direction_removed == "" + + - name: Verify l3out external epg with intra_ext_epg_isolation + ansible.builtin.assert: + that: + - nm_l3out_extepg_isolation.current.0.l3extInstP.attributes.pcEnfPref == "enforced" + - query_l3out_extepg_isolation.current.0.l3extInstP.attributes.pcEnfPref == "enforced" + when: + - version.current.0.topSystem.attributes.version is version('5.0', '>=') + + # TEST REMOVAL L3OUT EXTERNAL EPG + + - name: Remove l3out external epg 1 ( check mode ) + cisco.aci.aci_l3out_extepg: &remove_l3out_extepg_1 + <<: *add_l3out_extepg_1 + state: absent + check_mode: true + register: cm_remove_l3out_extepg_1 + + - name: Remove l3out external epg 1 + cisco.aci.aci_l3out_extepg: + <<: *remove_l3out_extepg_1 + register: nm_remove_l3out_extepg_1 + + - name: Remove l3out external epg 1 again + cisco.aci.aci_l3out_extepg: + <<: *remove_l3out_extepg_1 + register: nm_remove_l3out_extepg_1_again + + - name: Remove l3out external epg params + cisco.aci.aci_l3out_extepg: + <<: *aci_info + tenant: ansible_test + l3out: l3out_1 + extepg: extepg_params + state: absent + register: remove_l3out_extepg_params + + - name: Remove l3out external epg with intra_ext_epg_isolation + cisco.aci.aci_l3out_extepg: + <<: *aci_info + tenant: ansible_test + l3out: l3out_1 + extepg: extepg_isolation + state: absent + register: remove_l3out_extepg_isolation + + - name: Verify removal epg monitoring policies + ansible.builtin.assert: + that: + - cm_remove_l3out_extepg_1 is changed + - cm_remove_l3out_extepg_1.proposed == {} + - nm_remove_l3out_extepg_1 is changed + - nm_remove_l3out_extepg_1.previous.0.l3extInstP.attributes.name == "extepg_1" + - nm_remove_l3out_extepg_1.current == [] + - nm_remove_l3out_extepg_1_again is not changed + - nm_remove_l3out_extepg_1_again.previous == [] + - nm_remove_l3out_extepg_1_again.current == [] + - remove_l3out_extepg_params is changed + - remove_l3out_extepg_params.current == [] + - remove_l3out_extepg_isolation.current == [] diff --git a/ansible_collections/cisco/aci/tests/integration/targets/aci_l3out_logical_interface_profile/tasks/main.yml b/ansible_collections/cisco/aci/tests/integration/targets/aci_l3out_logical_interface_profile/tasks/main.yml index 4466369d5..b3ad827f1 100644 --- a/ansible_collections/cisco/aci/tests/integration/targets/aci_l3out_logical_interface_profile/tasks/main.yml +++ b/ansible_collections/cisco/aci/tests/integration/targets/aci_l3out_logical_interface_profile/tasks/main.yml @@ -62,6 +62,7 @@ cisco.aci.aci_l3out_logical_interface_profile: &intf_present <<: *np_present interface_profile: INTFS + description: test check_mode: true register: cm_add_intf @@ -77,6 +78,7 @@ - nm_add_intf is changed - cm_add_intf.previous == nm_add_intf.previous == [] - cm_add_intf.sent.l3extLIfP.attributes.name == nm_add_intf.sent.l3extLIfP.attributes.name == 'INTFS' + - cm_add_intf.sent.l3extLIfP.attributes.descr == nm_add_intf.sent.l3extLIfP.attributes.descr == 'test' - nm_add_intf.current.0.l3extLIfP.attributes.annotation == 'orchestrator:ansible' - name: Add profile again, check if idempotency works @@ -94,11 +96,13 @@ cisco.aci.aci_l3out_logical_interface_profile: &intf_update <<: *intf_present nd_policy: NDTEST + description: new test register: update_intf - name: Verify update_intf assert: that: + - update_intf.sent.l3extLIfP.attributes.descr == 'new test' - update_intf is changed - update_intf.previous != [] diff --git a/ansible_collections/cisco/aci/tests/integration/targets/aci_l3out_logical_node/tasks/main.yml b/ansible_collections/cisco/aci/tests/integration/targets/aci_l3out_logical_node/tasks/main.yml index 68bc64b20..1d61ee3e3 100644 --- a/ansible_collections/cisco/aci/tests/integration/targets/aci_l3out_logical_node/tasks/main.yml +++ b/ansible_collections/cisco/aci/tests/integration/targets/aci_l3out_logical_node/tasks/main.yml @@ -1,6 +1,11 @@ # Author: Marcel Zehnder (@maercu) # GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) +- name: Test that we have an ACI APIC host, ACI username and ACI password + fail: + msg: 'Please define the following variables: aci_hostname, aci_username and aci_password.' + when: aci_hostname is not defined or aci_username is not defined or aci_password is not defined + # SET VARS - name: Set vars set_fact: @@ -79,9 +84,9 @@ - cm_add_node is changed - nm_add_node is changed - cm_add_node.previous == nm_add_node.previous == [] - - cm_add_node.sent.l3extRsNodeL3OutAtt.attributes.rtrId == nm_add_node.sent.l3extRsNodeL3OutAtt.attributes.rtrId == '111.111.111.111' - - cm_add_node.sent.l3extRsNodeL3OutAtt.attributes.rtrIdLoopBack == nm_add_node.sent.l3extRsNodeL3OutAtt.attributes.rtrIdLoopBack == 'no' - - cm_add_node.sent.l3extRsNodeL3OutAtt.attributes.tDn == nm_add_node.sent.l3extRsNodeL3OutAtt.attributes.tDn == 'topology/pod-1/node-111' + - cm_add_node.sent.l3extRsNodeL3OutAtt.attributes.rtrId == nm_add_node.current.0.l3extRsNodeL3OutAtt.attributes.rtrId == '111.111.111.111' + - cm_add_node.sent.l3extRsNodeL3OutAtt.attributes.rtrIdLoopBack == nm_add_node.current.0.l3extRsNodeL3OutAtt.attributes.rtrIdLoopBack == 'no' + - cm_add_node.sent.l3extRsNodeL3OutAtt.attributes.tDn == nm_add_node.current.0.l3extRsNodeL3OutAtt.attributes.tDn == 'topology/pod-1/node-111' - nm_add_node.current.0.l3extRsNodeL3OutAtt.attributes.annotation == 'orchestrator:ansible' - name: Add node again, check if idempotency works @@ -101,19 +106,37 @@ router_id: 11.11.11.11 register: update_node + - name: Add loopback address + cisco.aci.aci_l3out_logical_node: &node_add_loopback + <<: *node_update + loopback_address: 11.11.11.12 + register: add_loopback_ip + + - name: Remove loopback address + cisco.aci.aci_l3out_logical_node: &node_remove_loopback + <<: *node_add_loopback + loopback_address: "" + register: remove_loopback_ip + - name: Verify update_node assert: that: - update_node is changed - update_node.previous != [] - - update_node.sent.l3extRsNodeL3OutAtt.attributes.rtrId == '11.11.11.11' + - update_node.current.0.l3extRsNodeL3OutAtt.attributes.rtrId == '11.11.11.11' + - add_loopback_ip is changed + - add_loopback_ip.previous != [] + - add_loopback_ip.current.0.l3extRsNodeL3OutAtt.children.0.l3extLoopBackIfP.attributes.addr == '11.11.11.12' + - remove_loopback_ip is changed + - remove_loopback_ip.previous != [] # ADD ANOTHER NODE - name: Add another node - cisco.aci.aci_l3out_logical_node: + cisco.aci.aci_l3out_logical_node: &second_node_present <<: *node_present node_id: 112 router_id: 12.12.12.12 + loopback_address: 12.12.12.13 # QUERY ALL NODES - name: Query all nodes @@ -131,7 +154,7 @@ # QUERY A SPECIFIC NODE - name: Query a specific node cisco.aci.aci_l3out_logical_node: - <<: *node_update + <<: *second_node_present state: query register: query_spec_node @@ -140,11 +163,13 @@ that: - query_spec_node is not changed - query_spec_node.current|length == 1 + - query_spec_node.current.0.l3extRsNodeL3OutAtt.attributes.rtrId == '12.12.12.12' + - query_spec_node.current.0.l3extRsNodeL3OutAtt.children.0.l3extLoopBackIfP.attributes.addr == '12.12.12.13' # REMOVE NODE - name: Remove node cisco.aci.aci_l3out_logical_node: - <<: *node_update + <<: *node_remove_loopback state: absent register: remove_node @@ -153,3 +178,9 @@ that: - remove_node is changed - remove_node.current == [] + + - name: Remove test tenant - clean-up the environment + cisco.aci.aci_tenant: + <<: *aci_info + tenant: ansible_test + state: absent
\ No newline at end of file diff --git a/ansible_collections/cisco/aci/tests/integration/targets/aci_l3out_route_tag_policy/aliases b/ansible_collections/cisco/aci/tests/integration/targets/aci_l3out_route_tag_policy/aliases new file mode 100644 index 000000000..209b793f9 --- /dev/null +++ b/ansible_collections/cisco/aci/tests/integration/targets/aci_l3out_route_tag_policy/aliases @@ -0,0 +1,2 @@ +# No ACI simulator yet, so not enabled +# unsupported diff --git a/ansible_collections/cisco/aci/tests/integration/targets/aci_l3out_route_tag_policy/tasks/main.yml b/ansible_collections/cisco/aci/tests/integration/targets/aci_l3out_route_tag_policy/tasks/main.yml new file mode 100644 index 000000000..d7ad61c6d --- /dev/null +++ b/ansible_collections/cisco/aci/tests/integration/targets/aci_l3out_route_tag_policy/tasks/main.yml @@ -0,0 +1,149 @@ +# Test code for the ACI modules +# Copyright: (c) 2023, Gaspard Micol (@gmicol) <gmicol@cisco.com> + +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +- name: Test that we have an ACI APIC host, ACI username and ACI password + fail: + msg: 'Please define the following variables: aci_hostname, aci_username and aci_password.' + when: aci_hostname is not defined or aci_username is not defined or aci_password is not defined + +- name: Set vars + set_fact: + aci_info: &aci_info + host: "{{ aci_hostname }}" + username: "{{ aci_username }}" + password: "{{ aci_password }}" + validate_certs: '{{ aci_validate_certs | default(false) }}' + use_ssl: '{{ aci_use_ssl | default(true) }}' + use_proxy: '{{ aci_use_proxy | default(true) }}' + output_level: info + +# CLEAN ENVIRONMENT +- name: Remove the ansible_tenant + cisco.aci.aci_tenant: + <<: *aci_info + tenant: ansible_tenant + state: absent + +- name: Verify Cloud and Non-Cloud Sites in use. + include_tasks: ../../../../../../integration/targets/aci_cloud_provider/tasks/main.yml + +- name: Execute tasks only for non-cloud sites + when: query_cloud.current == [] # This condition will execute only non-cloud sites + block: # block specifies execution of tasks within, based on conditions + - name: Create tenant for tests + cisco.aci.aci_tenant: &aci_tenant_present + <<: *aci_info + state: present + tenant: ansible_tenant + + - name: Create first l3out route tag policy (check_mode) + cisco.aci.aci_l3out_route_tag_policy: &aci_rtp_present + <<: *aci_tenant_present + rtp: anstest + description: rtp test + tag: 1000 + state: present + check_mode: true + register: cm_add_rtp + + - name: Create first l3out route tag policy (normal_mode) + cisco.aci.aci_l3out_route_tag_policy: + <<: *aci_rtp_present + register: nm_add_rtp + + - name: Create first l3out route tag policy again - testing idempotency + cisco.aci.aci_l3out_route_tag_policy: + <<: *aci_rtp_present + register: add_rtp_idempotency + + - name: Create second l3out route tag policy + cisco.aci.aci_l3out_route_tag_policy: &aci_rtp_present_2 + <<: *aci_tenant_present + rtp: anstest_2 + state: present + register: nm_add_rtp_2 + + - name: asserts for creation tasks + assert: + that: + - cm_add_rtp is changed + - cm_add_rtp.previous == [] + - cm_add_rtp.current == [] + - nm_add_rtp is changed + - nm_add_rtp.current.0.l3extRouteTagPol.attributes.name == "anstest" + - nm_add_rtp.current.0.l3extRouteTagPol.attributes.descr == "rtp test" + - nm_add_rtp.current.0.l3extRouteTagPol.attributes.tag == "1000" + - add_rtp_idempotency is not changed + - nm_add_rtp_2 is changed + - nm_add_rtp_2.current.0.l3extRouteTagPol.attributes.name == "anstest_2" + - nm_add_rtp_2.current.0.l3extRouteTagPol.attributes.tag == "4294967295" + + - name: Query all l3out route tag policies + cisco.aci.aci_l3out_route_tag_policy: + <<: *aci_tenant_present + state: query + register: query_all_rtp + + - name: Query first l3out route tag policy + cisco.aci.aci_l3out_route_tag_policy: + <<: *aci_rtp_present + state: query + register: query_first_rtp + + - name: asserts query tasks + assert: + that: + - query_all_rtp is not changed + - query_all_rtp.current.0.fvTenant.children | length >= 2 + - query_all_rtp.current.0.fvTenant.children.0.l3extRouteTagPol.attributes.name == "anstest_2" + - query_all_rtp.current.0.fvTenant.children.1.l3extRouteTagPol.attributes.name == "anstest" + - query_first_rtp is not changed + - query_first_rtp.current.0.l3extRouteTagPol.attributes.name == "anstest" + - query_first_rtp.current.0.l3extRouteTagPol.attributes.descr == "rtp test" + - query_first_rtp.current.0.l3extRouteTagPol.attributes.tag == "1000" + + - name: Delete first l3out route tag policy (check_mode) + cisco.aci.aci_l3out_route_tag_policy: + <<: *aci_rtp_present + state: absent + check_mode: true + register: cm_delete_rtp + + - name: Delete first l3out route tag policy (normal_mode) + cisco.aci.aci_l3out_route_tag_policy: + <<: *aci_rtp_present + state: absent + register: nm_delete_rtp + + - name: Delete first l3out route tag policy agaim - testing idempotency + cisco.aci.aci_l3out_route_tag_policy: + <<: *aci_rtp_present + state: absent + register: delete_rtp_idempotency + + - name: Delete second l3out route tag policy + cisco.aci.aci_l3out_route_tag_policy: + <<: *aci_rtp_present_2 + state: absent + register: nm_delete_rtp_2 + + - name: asserts for deletion tasks + assert: + that: + - cm_delete_rtp is changed + - cm_delete_rtp.proposed == {} + - nm_delete_rtp is changed + - nm_delete_rtp.previous != [] + - nm_delete_rtp.current == [] + - delete_rtp_idempotency is not changed + - delete_rtp_idempotency.previous == [] + - nm_delete_rtp_2 is changed + - nm_delete_rtp_2.previous != [] + - nm_delete_rtp_2.current == [] + + - name: Remove ansible_tenant - cleanup before ending tests + cisco.aci.aci_tenant: + <<: *aci_tenant_present + state: absent diff --git a/ansible_collections/cisco/aci/tests/integration/targets/aci_maintenance_group/aliases b/ansible_collections/cisco/aci/tests/integration/targets/aci_maintenance_group/aliases new file mode 100644 index 000000000..209b793f9 --- /dev/null +++ b/ansible_collections/cisco/aci/tests/integration/targets/aci_maintenance_group/aliases @@ -0,0 +1,2 @@ +# No ACI simulator yet, so not enabled +# unsupported diff --git a/ansible_collections/cisco/aci/tests/integration/targets/aci_maintenance_group/tasks/main.yml b/ansible_collections/cisco/aci/tests/integration/targets/aci_maintenance_group/tasks/main.yml new file mode 100644 index 000000000..12284c8a6 --- /dev/null +++ b/ansible_collections/cisco/aci/tests/integration/targets/aci_maintenance_group/tasks/main.yml @@ -0,0 +1,175 @@ +# Test code for the ACI modules +# Copyright: (c) 2023, Gaspard Micol (@gmicol) <gmicol@cisco.com> + +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +- name: Test that we have an ACI APIC host, ACI username and ACI password + fail: + msg: 'Please define the following variables: aci_hostname, aci_username and aci_password.' + when: aci_hostname is not defined or aci_username is not defined or aci_password is not defined + +- name: Set vars + set_fact: + aci_info: &aci_info + host: "{{ aci_hostname }}" + username: "{{ aci_username }}" + password: "{{ aci_password }}" + validate_certs: '{{ aci_validate_certs | default(false) }}' + use_ssl: '{{ aci_use_ssl | default(true) }}' + use_proxy: '{{ aci_use_proxy | default(true) }}' + output_level: debug + +- name: Verify Cloud and Non-Cloud Sites in use. + include_tasks: ../../../../../../integration/targets/aci_cloud_provider/tasks/main.yml + +- name: Execute tasks only for non-cloud sites + when: query_cloud.current == [] # This condition will execute only non-cloud sites + block: # block specifies execution of tasks within, based on conditions + # Clean Environment + - name: Create Scheduler + aci_fabric_scheduler: &aci_fabric_scheduler_present + <<: *aci_info + name: anstest_scheduler + state: present + + - name: Create maintenance policy + aci_maintenance_policy: &aci_maintenance_policy_present + <<: *aci_info + name: anstest_policy + scheduler: anstest_scheduler + state: present + + - name: Ensure first maintenance group does not exist + aci_maintenance_group: &aci_maintenance_group_absent + <<: *aci_info + group: anstest_group + policy: anstest_policy + firmware_nodes_type: controller + type_group: all + description: test for maintenance group + state: absent + + - name: Ensure second maintenance group does not exist + aci_maintenance_group: &aci_maintenance_group_2_absent + <<: *aci_info + group: anstest_group_2 + policy: anstest_policy_2 + state: absent + + - name: Create first maintenance group (check_mode) + aci_maintenance_group: &aci_maintenance_group_present + <<: *aci_maintenance_group_absent + state: present + check_mode: true + register: cm_add_maintenance_group_1 + + - name: Create first maintenance group (normal_mode) + aci_maintenance_group: + <<: *aci_maintenance_group_present + register: nm_add_maintenance_group_1 + + - name: Create first maintenance group again - testing idempotency + aci_maintenance_group: + <<: *aci_maintenance_group_present + register: idempotency_add_maintenance_group_1 + + - name: Create second maintenance group + aci_maintenance_group: &aci_maintenance_group_2_present + <<: *aci_maintenance_group_2_absent + state: present + register: nm_add_maintenance_group_2 + + - name: Asserts for creation tasks + assert: + that: + - cm_add_maintenance_group_1 is changed + - cm_add_maintenance_group_1.previous == [] + - cm_add_maintenance_group_1.current == [] + - nm_add_maintenance_group_1 is changed + - nm_add_maintenance_group_1.current.0.maintMaintGrp.attributes.name == "anstest_group" + - nm_add_maintenance_group_1.current.0.maintMaintGrp.attributes.fwtype == "controller" + - nm_add_maintenance_group_1.current.0.maintMaintGrp.attributes.type == "ALL" + - nm_add_maintenance_group_1.current.0.maintMaintGrp.children.0.maintRsMgrpp.attributes.tnMaintMaintPName == "anstest_policy" + - idempotency_add_maintenance_group_1 is not changed + - nm_add_maintenance_group_2 is changed + - nm_add_maintenance_group_2.current.0.maintMaintGrp.attributes.name == "anstest_group_2" + - nm_add_maintenance_group_2.current.0.maintMaintGrp.children.0.maintRsMgrpp.attributes.tnMaintMaintPName == "anstest_policy_2" + - nm_add_maintenance_group_2.current.0.maintMaintGrp.attributes.fwtype == "switch" + - nm_add_maintenance_group_2.current.0.maintMaintGrp.attributes.type == "range" + + + - name: Query all maintenance groups + aci_maintenance_group: + <<: *aci_info + state: query + register: query_all_maintenance_group + + - name: Query first maintenance group + aci_maintenance_group: + <<: *aci_maintenance_group_present + state: query + register: query_first_maintenance_group + + - name: Asserts for query tasks + assert: + that: + - query_all_maintenance_group is not changed + - query_all_maintenance_group.current | length >= 2 + - '"maintRsMgrpp" in query_all_maintenance_group.filter_string' + - '"class/maintMaintGrp.json" in query_all_maintenance_group.url' + - query_all_maintenance_group.current.0.maintMaintGrp.attributes.name == "anstest_group" + - query_all_maintenance_group.current.1.maintMaintGrp.attributes.name == "anstest_group_2" + - query_first_maintenance_group is not changed + - query_first_maintenance_group.current.0.maintMaintGrp.attributes.name == "anstest_group" + - query_first_maintenance_group.current.0.maintMaintGrp.children.0.maintRsMgrpp.attributes.tnMaintMaintPName == "anstest_policy" + - query_first_maintenance_group.current.0.maintMaintGrp.attributes.fwtype == "controller" + - query_first_maintenance_group.current.0.maintMaintGrp.attributes.type == "ALL" + + - name: Delete first maintenance group (check_mode) + aci_maintenance_group: + <<: *aci_maintenance_group_present + state: absent + check_mode: true + register: cm_delete_maintenance_group_1 + + - name: Delete first maintenance group (normal_mode) + aci_maintenance_group: + <<: *aci_maintenance_group_present + state: absent + register: nm_delete_maintenance_group_1 + + - name: Delete first maintenance group again - testing idempotency + aci_maintenance_group: + <<: *aci_maintenance_group_present + state: absent + register: idempotency_delete_maintenance_group_1 + + - name: Delete second maintenance group (normal_mode) + aci_maintenance_group: + <<: *aci_maintenance_group_2_present + state: absent + register: nm_delete_maintenance_group_2 + + - name: Asserts for deletion tasks + assert: + that: + - cm_delete_maintenance_group_1 is changed + - cm_delete_maintenance_group_1.proposed == {} + - nm_delete_maintenance_group_1 is changed + - nm_delete_maintenance_group_1.previous != [] + - nm_delete_maintenance_group_1.current == [] + - idempotency_delete_maintenance_group_1 is not changed + - idempotency_delete_maintenance_group_1.previous == [] + - nm_delete_maintenance_group_2 is changed + - nm_delete_maintenance_group_2.previous != [] + - nm_delete_maintenance_group_2.current == [] + + - name: Delete maintenance policy - clean up the environment + aci_maintenance_policy: + <<: *aci_maintenance_policy_present + state: absent + + - name: Delete scheduler - clean up the environment + aci_fabric_scheduler: + <<: *aci_fabric_scheduler_present + state: absent diff --git a/ansible_collections/cisco/aci/tests/integration/targets/aci_maintenance_group_node/aliases b/ansible_collections/cisco/aci/tests/integration/targets/aci_maintenance_group_node/aliases new file mode 100644 index 000000000..209b793f9 --- /dev/null +++ b/ansible_collections/cisco/aci/tests/integration/targets/aci_maintenance_group_node/aliases @@ -0,0 +1,2 @@ +# No ACI simulator yet, so not enabled +# unsupported diff --git a/ansible_collections/cisco/aci/tests/integration/targets/aci_maintenance_group_node/tasks/main.yml b/ansible_collections/cisco/aci/tests/integration/targets/aci_maintenance_group_node/tasks/main.yml new file mode 100644 index 000000000..9be9ecbe8 --- /dev/null +++ b/ansible_collections/cisco/aci/tests/integration/targets/aci_maintenance_group_node/tasks/main.yml @@ -0,0 +1,174 @@ +# Test code for the ACI modules +# Copyright: (c) 2023, Gaspard Micol (@gmicol) <gmicol@cisco.com> + +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +- name: Test that we have an ACI APIC host, ACI username and ACI password + fail: + msg: 'Please define the following variables: aci_hostname, aci_username and aci_password.' + when: aci_hostname is not defined or aci_username is not defined or aci_password is not defined + +- name: Set vars + set_fact: + aci_info: &aci_info + host: "{{ aci_hostname }}" + username: "{{ aci_username }}" + password: "{{ aci_password }}" + validate_certs: '{{ aci_validate_certs | default(false) }}' + use_ssl: '{{ aci_use_ssl | default(true) }}' + use_proxy: '{{ aci_use_proxy | default(true) }}' + output_level: debug + +- name: Verify Cloud and Non-Cloud Sites in use. + include_tasks: ../../../../../../integration/targets/aci_cloud_provider/tasks/main.yml + +- name: Execute tasks only for non-cloud sites + when: query_cloud.current == [] # This condition will execute only non-cloud sites + block: # block specifies execution of tasks within, based on conditions + # Clean Environment + - name: Create Scheduler + aci_fabric_scheduler: &aci_fabric_scheduler_present + <<: *aci_info + name: anstest_scheduler + state: present + + - name: Create maintenance policy + aci_maintenance_policy: &aci_maintenance_policy_present + <<: *aci_info + name: anstest_policy + scheduler: anstest_scheduler + state: present + + - name: Create maintenance groupe + aci_maintenance_group: &aci_maintenance_group_present + <<: *aci_info + group: anstest_group + policy: anstest_policy + state: present + + - name: Ensure first maintenance group node does not exist + aci_maintenance_group_node: &aci_maintenance_group_node_absent + <<: *aci_info + group: anstest_group + node: 1001 + state: absent + + - name: Ensure second maintenance group node does not exist + aci_maintenance_group_node: &aci_maintenance_group_node_2_absent + <<: *aci_info + group: anstest_group + node: 1002 + state: absent + + - name: Create first maintenance group node (check_mode) + aci_maintenance_group_node: &aci_maintenance_group_node_present + <<: *aci_maintenance_group_node_absent + state: present + check_mode: true + register: cm_add_maintenance_group_node_1 + + - name: Create first maintenance group node (normal_mode) + aci_maintenance_group_node: + <<: *aci_maintenance_group_node_present + register: nm_add_maintenance_group_node_1 + + - name: Create first maintenance group node again - testing idempotency + aci_maintenance_group_node: + <<: *aci_maintenance_group_node_present + register: idempotency_add_maintenance_group_node_1 + + - name: Create second maintenance group node + aci_maintenance_group_node: &aci_maintenance_group_node_2_present + <<: *aci_maintenance_group_node_2_absent + state: present + register: nm_add_maintenance_group_node_2 + + - name: Asserts for creation tasks + assert: + that: + - cm_add_maintenance_group_node_1 is changed + - cm_add_maintenance_group_node_1.previous == [] + - cm_add_maintenance_group_node_1.current == [] + - nm_add_maintenance_group_node_1 is changed + - nm_add_maintenance_group_node_1.current.0.fabricNodeBlk.attributes.from_ == "1001" + - nm_add_maintenance_group_node_1.current.0.fabricNodeBlk.attributes.to_ == "1001" + - idempotency_add_maintenance_group_node_1 is not changed + - nm_add_maintenance_group_node_2 is changed + - nm_add_maintenance_group_node_2.current.0.fabricNodeBlk.attributes.from_ == "1002" + - nm_add_maintenance_group_node_2.current.0.fabricNodeBlk.attributes.to_ == "1002" + + + - name: Query all maintenance group nodes + aci_maintenance_group_node: + <<: *aci_info + state: query + register: query_all_maintenance_group_node + + - name: Query first maintenance group nnode + aci_maintenance_group_node: + <<: *aci_maintenance_group_node_present + state: query + register: query_first_maintenance_group_node + + - name: Asserts for query tasks + assert: + that: + - query_all_maintenance_group_node is not changed + - query_all_maintenance_group_node.current | length >= 2 + - query_first_maintenance_group_node is not changed + - query_first_maintenance_group_node.current.0.fabricNodeBlk.attributes.from_ == "1001" + - query_first_maintenance_group_node.current.0.fabricNodeBlk.attributes.to_ == "1001" + + - name: Delete first maintenance group (check_mode) + aci_maintenance_group_node: + <<: *aci_maintenance_group_node_present + state: absent + check_mode: true + register: cm_delete_maintenance_group_node_1 + + - name: Delete first maintenance group (normal_mode) + aci_maintenance_group_node: + <<: *aci_maintenance_group_node_present + state: absent + register: nm_delete_maintenance_group_node_1 + + - name: Delete first maintenance group again - testing idempotency + aci_maintenance_group_node: + <<: *aci_maintenance_group_node_present + state: absent + register: idempotency_delete_maintenance_group_1 + + - name: Delete second maintenance group (normal_mode) + aci_maintenance_group_node: + <<: *aci_maintenance_group_node_2_present + state: absent + register: nm_delete_maintenance_group_node_2 + + - name: Asserts for deletion tasks + assert: + that: + - cm_delete_maintenance_group_node_1 is changed + - cm_delete_maintenance_group_node_1.proposed == {} + - nm_delete_maintenance_group_node_1 is changed + - nm_delete_maintenance_group_node_1.previous != [] + - nm_delete_maintenance_group_node_1.current == [] + - idempotency_delete_maintenance_group_1 is not changed + - idempotency_delete_maintenance_group_1.previous == [] + - nm_delete_maintenance_group_node_2 is changed + - nm_delete_maintenance_group_node_2.previous != [] + - nm_delete_maintenance_group_node_2.current == [] + + - name: Delete maintenance group - clean up the environment + aci_maintenance_group: + <<: *aci_maintenance_group_present + state: absent + + - name: Delete maintenance policy - clean up the environment + aci_maintenance_policy: + <<: *aci_maintenance_policy_present + state: absent + + - name: Delete scheduler - clean up the environment + aci_fabric_scheduler: + <<: *aci_fabric_scheduler_present + state: absent diff --git a/ansible_collections/cisco/aci/tests/integration/targets/aci_maintenance_policy/aliases b/ansible_collections/cisco/aci/tests/integration/targets/aci_maintenance_policy/aliases new file mode 100644 index 000000000..209b793f9 --- /dev/null +++ b/ansible_collections/cisco/aci/tests/integration/targets/aci_maintenance_policy/aliases @@ -0,0 +1,2 @@ +# No ACI simulator yet, so not enabled +# unsupported diff --git a/ansible_collections/cisco/aci/tests/integration/targets/aci_maintenance_policy/tasks/main.yml b/ansible_collections/cisco/aci/tests/integration/targets/aci_maintenance_policy/tasks/main.yml new file mode 100644 index 000000000..8628c2a84 --- /dev/null +++ b/ansible_collections/cisco/aci/tests/integration/targets/aci_maintenance_policy/tasks/main.yml @@ -0,0 +1,184 @@ +# Test code for the ACI modules +# Copyright: (c) 2023, Gaspard Micol (@gmicol) <gmicol@cisco.com> + +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +- name: Test that we have an ACI APIC host, ACI username and ACI password + fail: + msg: 'Please define the following variables: aci_hostname, aci_username and aci_password.' + when: aci_hostname is not defined or aci_username is not defined or aci_password is not defined + +- name: Set vars + set_fact: + aci_info: &aci_info + host: "{{ aci_hostname }}" + username: "{{ aci_username }}" + password: "{{ aci_password }}" + validate_certs: '{{ aci_validate_certs | default(false) }}' + use_ssl: '{{ aci_use_ssl | default(true) }}' + use_proxy: '{{ aci_use_proxy | default(true) }}' + output_level: debug + +- name: Verify Cloud and Non-Cloud Sites in use. + include_tasks: ../../../../../../integration/targets/aci_cloud_provider/tasks/main.yml + +- name: Execute tasks only for non-cloud sites + when: query_cloud.current == [] # This condition will execute only non-cloud sites + block: # block specifies execution of tasks within, based on conditions + # Clean Environment + - name: Create Scheduler + aci_fabric_scheduler: &aci_fabric_scheduler_present + <<: *aci_info + name: anstest_scheduler + state: present + + - name: Ensure first maintenance policy does not exist + aci_maintenance_policy: &aci_maintenance_policy_absent + <<: *aci_info + name: anstest_policy + description: test for maintenance policy + run_mode: pause_never + graceful: True + scheduler: anstest_scheduler + ignore_compat: true + admin_state: untriggered + download_state: untriggered + notify_condition: notify_always_between_sets + smu_operation: smu_install + smu_operation_flags: smu_reload_skip + state: absent + + - name: Ensure second maintenance policy does not exist + aci_maintenance_policy: &aci_maintenance_policy_2_absent + <<: *aci_info + name: anstest_policy_2 + scheduler: anstest_scheduler + state: absent + + - name: Create first maintenance policy (check_mode) + aci_maintenance_policy: &aci_maintenance_policy_present + <<: *aci_maintenance_policy_absent + state: present + check_mode: true + register: cm_add_maintenance_policy_1 + + - name: Create first maintenance policy (normal_mode) + aci_maintenance_policy: + <<: *aci_maintenance_policy_present + register: nm_add_maintenance_policy_1 + + - name: Create first maintenance policy again - testing idempotency + aci_maintenance_policy: + <<: *aci_maintenance_policy_present + register: idempotency_add_maintenance_policy_1 + + - name: Create second maintenance policy + aci_maintenance_policy: &aci_maintenance_policy_2_present + <<: *aci_maintenance_policy_2_absent + state: present + register: nm_add_maintenance_policy_2 + + - name: Asserts for creation tasks + assert: + that: + - cm_add_maintenance_policy_1 is changed + - cm_add_maintenance_policy_1.previous == [] + - cm_add_maintenance_policy_1.current == [] + - nm_add_maintenance_policy_1 is changed + - nm_add_maintenance_policy_1.current.0.maintMaintP.attributes.adminSt == "untriggered" + - nm_add_maintenance_policy_1.current.0.maintMaintP.attributes.downloadSt == "untriggered" + - nm_add_maintenance_policy_1.current.0.maintMaintP.attributes.notifCond == "notifyAlwaysBetweenSets" + - nm_add_maintenance_policy_1.current.0.maintMaintP.attributes.smuOperation == "smuInstall" + - nm_add_maintenance_policy_1.current.0.maintMaintP.attributes.smuOperationFlags == "smuReloadSkip" + - nm_add_maintenance_policy_1.current.0.maintMaintP.attributes.graceful == "yes" + - nm_add_maintenance_policy_1.current.0.maintMaintP.attributes.ignoreCompat == "yes" + - nm_add_maintenance_policy_1.current.0.maintMaintP.attributes.name == "anstest_policy" + - nm_add_maintenance_policy_1.current.0.maintMaintP.attributes.runMode == "pauseNever" + - nm_add_maintenance_policy_1.current.0.maintMaintP.children.0.maintRsPolScheduler.attributes.tnTrigSchedPName == "anstest_scheduler" + - idempotency_add_maintenance_policy_1 is not changed + - nm_add_maintenance_policy_2 is changed + - nm_add_maintenance_policy_2.current.0.maintMaintP.attributes.adminSt == "untriggered" + - nm_add_maintenance_policy_2.current.0.maintMaintP.attributes.graceful == "no" + - nm_add_maintenance_policy_2.current.0.maintMaintP.attributes.ignoreCompat == "no" + - nm_add_maintenance_policy_2.current.0.maintMaintP.attributes.downloadSt == "untriggered" + - nm_add_maintenance_policy_2.current.0.maintMaintP.attributes.notifCond == "notifyOnlyOnFailures" + - nm_add_maintenance_policy_2.current.0.maintMaintP.attributes.smuOperation == "smuInstall" + - nm_add_maintenance_policy_2.current.0.maintMaintP.attributes.smuOperationFlags == "smuReloadImmediate" + - nm_add_maintenance_policy_2.current.0.maintMaintP.attributes.name == "anstest_policy_2" + - nm_add_maintenance_policy_2.current.0.maintMaintP.attributes.runMode == "pauseOnlyOnFailures" + - nm_add_maintenance_policy_2.current.0.maintMaintP.children.0.maintRsPolScheduler.attributes.tnTrigSchedPName == "anstest_scheduler" + + - name: Query all maintenance policies + aci_maintenance_policy: + <<: *aci_info + state: query + register: query_all_maintenance_policy + + - name: Query first maintenance policy + aci_maintenance_policy: + <<: *aci_maintenance_policy_present + state: query + register: query_first_maintenance_policy + + - name: Asserts for query tasks + assert: + that: + - query_all_maintenance_policy is not changed + - query_all_maintenance_policy.current | length >= 2 + - '"maintRsPolScheduler" in query_all_maintenance_policy.filter_string' + - '"class/maintMaintP.json" in query_all_maintenance_policy.url' + - query_first_maintenance_policy is not changed + - query_first_maintenance_policy.current.0.maintMaintP.attributes.adminSt == "untriggered" + - query_first_maintenance_policy.current.0.maintMaintP.attributes.downloadSt == "untriggered" + - query_first_maintenance_policy.current.0.maintMaintP.attributes.notifCond == "notifyAlwaysBetweenSets" + - query_first_maintenance_policy.current.0.maintMaintP.attributes.smuOperation == "smuInstall" + - query_first_maintenance_policy.current.0.maintMaintP.attributes.smuOperationFlags == "smuReloadSkip" + - query_first_maintenance_policy.current.0.maintMaintP.attributes.graceful == "yes" + - query_first_maintenance_policy.current.0.maintMaintP.attributes.ignoreCompat == "yes" + - query_first_maintenance_policy.current.0.maintMaintP.attributes.name == "anstest_policy" + - query_first_maintenance_policy.current.0.maintMaintP.attributes.runMode == "pauseNever" + - query_first_maintenance_policy.current.0.maintMaintP.children.0.maintRsPolScheduler.attributes.tnTrigSchedPName == "anstest_scheduler" + + - name: Delete first maintenance policy (check_mode) + aci_maintenance_policy: + <<: *aci_maintenance_policy_present + state: absent + check_mode: true + register: cm_delete_maintenance_policy_1 + + - name: Delete first maintenance policy (normal_mode) + aci_maintenance_policy: + <<: *aci_maintenance_policy_present + state: absent + register: nm_delete_maintenance_policy_1 + + - name: Delete first maintenance policy again - testing idempotency + aci_maintenance_policy: + <<: *aci_maintenance_policy_present + state: absent + register: idempotency_delete_maintenance_policy_1 + + - name: Delete second maintenance policy (normal_mode) + aci_maintenance_policy: + <<: *aci_maintenance_policy_2_present + state: absent + register: nm_delete_maintenance_policy_2 + + - name: Asserts for deletion tasks + assert: + that: + - cm_delete_maintenance_policy_1 is changed + - cm_delete_maintenance_policy_1.proposed == {} + - nm_delete_maintenance_policy_1 is changed + - nm_delete_maintenance_policy_1.previous != [] + - nm_delete_maintenance_policy_1.current == [] + - idempotency_delete_maintenance_policy_1 is not changed + - idempotency_delete_maintenance_policy_1.previous == [] + - nm_delete_maintenance_policy_2 is changed + - nm_delete_maintenance_policy_2.previous != [] + - nm_delete_maintenance_policy_2.current == [] + + - name: Delete scheduler - clean up the environment + aci_fabric_scheduler: + <<: *aci_fabric_scheduler_present + state: absent diff --git a/ansible_collections/cisco/aci/tests/integration/targets/aci_match_as_path_regex_term/aliases b/ansible_collections/cisco/aci/tests/integration/targets/aci_match_as_path_regex_term/aliases new file mode 100644 index 000000000..209b793f9 --- /dev/null +++ b/ansible_collections/cisco/aci/tests/integration/targets/aci_match_as_path_regex_term/aliases @@ -0,0 +1,2 @@ +# No ACI simulator yet, so not enabled +# unsupported diff --git a/ansible_collections/cisco/aci/tests/integration/targets/aci_match_as_path_regex_term/tasks/main.yml b/ansible_collections/cisco/aci/tests/integration/targets/aci_match_as_path_regex_term/tasks/main.yml new file mode 100644 index 000000000..cdabd8b4e --- /dev/null +++ b/ansible_collections/cisco/aci/tests/integration/targets/aci_match_as_path_regex_term/tasks/main.yml @@ -0,0 +1,150 @@ +# Test code for the ACI modules +# Copyright: (c) 2023, Gaspard Micol (@gmicol) <gmicol@cisco.com> + +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +- name: Test that we have an ACI APIC host, ACI username and ACI password + fail: + msg: 'Please define the following variables: aci_hostname, aci_username and aci_password.' + when: aci_hostname is not defined or aci_username is not defined or aci_password is not defined + +- name: Set vars + set_fact: + aci_info: &aci_info + host: "{{ aci_hostname }}" + username: "{{ aci_username }}" + password: "{{ aci_password }}" + validate_certs: '{{ aci_validate_certs | default(false) }}' + use_ssl: '{{ aci_use_ssl | default(true) }}' + use_proxy: '{{ aci_use_proxy | default(true) }}' + output_level: debug + +# CLEAN ENVIRONMENT +- name: Remove the ansible_tenant + aci_tenant: &aci_tenant_absent + <<: *aci_info + tenant: ansible_tenant + state: absent + +- name: Verify Cloud and Non-Cloud Sites in use. + include_tasks: ../../../../../../integration/targets/aci_cloud_provider/tasks/main.yml + +- name: Execute tasks only for non-cloud sites + when: query_cloud.current == [] # This condition will execute only non-cloud sites + block: # block specifies execution of tasks within, based on conditions + - name: Add a new tenant + aci_tenant: &aci_tenant_present + <<: *aci_info + tenant: ansible_tenant + description: Ansible tenant + state: present + + - name: Add a match rule profile + aci_match_rule: + <<: *aci_info + tenant: ansible_tenant + match_rule: ansible_match_rule + description: match rule profile for ansible_tenant tenant + state: present + register: cm_add_match_rule + + - name: Add a match regex AS-Path term (check_mode) + aci_match_as_path_regex_term: &aci_match_as_path_regex_term_present + <<: *aci_info + tenant: ansible_tenant + match_rule: ansible_match_rule + match_as_path_regex_term: ansible_match_as_path_regex_term_1 + description: match regex AS-Path term 1 for ansible_match_rule match rule profile + regex: .* + state: present + check_mode: true + register: cm_add_match_as_path_regex_term + + - name: Add a match regex AS-Path term (normal_mode) + aci_match_as_path_regex_term: + <<: *aci_match_as_path_regex_term_present + register: nm_add_match_as_path_regex_term + + - name: Add the first match regex AS-Path term again - testing idempotency + aci_match_as_path_regex_term: + <<: *aci_match_as_path_regex_term_present + register: nm_add_match_as_path_regex_term_idempotency + + - name: Add a second match regex AS-Path term (normal_mode) + aci_match_as_path_regex_term: + <<: *aci_info + tenant: ansible_tenant + match_rule: ansible_match_rule + match_as_path_regex_term: ansible_match_as_path_regex_term_2 + description: match regex AS-Path term 2 for ansible_match_rule match rule profile + state: present + register: nm_add_match_as_path_regex_term_2 + + - name: Asserts for match regex AS-Path terms creation tasks + assert: + that: + - cm_add_match_as_path_regex_term is changed + - cm_add_match_as_path_regex_term.previous == [] + - cm_add_match_as_path_regex_term.current == [] + - nm_add_match_as_path_regex_term is changed + - nm_add_match_as_path_regex_term.current.0.rtctrlMatchAsPathRegexTerm.attributes.name == "ansible_match_as_path_regex_term_1" + - nm_add_match_as_path_regex_term.current.0.rtctrlMatchAsPathRegexTerm.attributes.regex == ".*" + - nm_add_match_as_path_regex_term_idempotency is not changed + - nm_add_match_as_path_regex_term_2 is changed + - nm_add_match_as_path_regex_term_2.previous == [] + - nm_add_match_as_path_regex_term_2.current.0.rtctrlMatchAsPathRegexTerm.attributes.name == "ansible_match_as_path_regex_term_2" + - nm_add_match_as_path_regex_term_2.current.0.rtctrlMatchAsPathRegexTerm.attributes.regex == "" + + - name: Query all match regex AS-Path terms + aci_match_as_path_regex_term: + <<: *aci_info + state: query + register: query_all_match_as_path_regex_term + + - name: Query ansible_match_as_path_regex_term_1 + aci_match_as_path_regex_term: + <<: *aci_match_as_path_regex_term_present + state: query + register: query_ansible_match_as_path_regex_term_1 + + - name: Asserts query tasks + assert: + that: + - query_all_match_as_path_regex_term is not changed + - query_all_match_as_path_regex_term.current|length >= 2 + - query_ansible_match_as_path_regex_term_1 is not changed + - query_ansible_match_as_path_regex_term_1.current.0.rtctrlMatchAsPathRegexTerm.attributes.name == "ansible_match_as_path_regex_term_1" + - query_ansible_match_as_path_regex_term_1.current.0.rtctrlMatchAsPathRegexTerm.attributes.regex == ".*" + + - name: Remove match regex AS-Path term for l3out (check_mode) + aci_match_as_path_regex_term: &match_as_path_regex_term_absent + <<: *aci_match_as_path_regex_term_present + state: absent + check_mode: true + register: cm_remove_match_as_path_regex_term + + - name: Remove match regex AS-Path term for l3out (normal_mode) + aci_match_as_path_regex_term: + <<: *match_as_path_regex_term_absent + register: nm_remove_match_as_path_regex_term + + - name: Remove match regex AS-Path term for l3out again - testing previous Removal + aci_match_as_path_regex_term: + <<: *match_as_path_regex_term_absent + register: nm_remove_match_as_path_regex_term_idempotency + + - name: Asserts deletion tasks + assert: + that: + - cm_remove_match_as_path_regex_term is changed + - cm_remove_match_as_path_regex_term.proposed == {} + - nm_remove_match_as_path_regex_term is changed + - nm_remove_match_as_path_regex_term.previous != [] + - nm_remove_match_as_path_regex_term.method == "DELETE" + - nm_remove_match_as_path_regex_term_idempotency is not changed + - nm_remove_match_as_path_regex_term_idempotency.previous == [] + + - name: Remove the ansible_tenant - cleanup before ending tests + aci_tenant: + <<: *aci_tenant_present + state: absent diff --git a/ansible_collections/cisco/aci/tests/integration/targets/aci_match_community_factor/aliases b/ansible_collections/cisco/aci/tests/integration/targets/aci_match_community_factor/aliases new file mode 100644 index 000000000..209b793f9 --- /dev/null +++ b/ansible_collections/cisco/aci/tests/integration/targets/aci_match_community_factor/aliases @@ -0,0 +1,2 @@ +# No ACI simulator yet, so not enabled +# unsupported diff --git a/ansible_collections/cisco/aci/tests/integration/targets/aci_match_community_factor/tasks/main.yml b/ansible_collections/cisco/aci/tests/integration/targets/aci_match_community_factor/tasks/main.yml new file mode 100644 index 000000000..2cc1f3ac1 --- /dev/null +++ b/ansible_collections/cisco/aci/tests/integration/targets/aci_match_community_factor/tasks/main.yml @@ -0,0 +1,157 @@ +# Test code for the ACI modules +# Copyright: (c) 2023, Gaspard Micol (@gmicol) <gmicol@cisco.com> + +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +- name: Test that we have an ACI APIC host, ACI username and ACI password + fail: + msg: 'Please define the following variables: aci_hostname, aci_username and aci_password.' + when: aci_hostname is not defined or aci_username is not defined or aci_password is not defined + +- name: Set vars + set_fact: + aci_info: &aci_info + host: "{{ aci_hostname }}" + username: "{{ aci_username }}" + password: "{{ aci_password }}" + validate_certs: '{{ aci_validate_certs | default(false) }}' + use_ssl: '{{ aci_use_ssl | default(true) }}' + use_proxy: '{{ aci_use_proxy | default(true) }}' + output_level: debug + +# CLEAN ENVIRONMENT +- name: Remove the ansible_tenant + aci_tenant: &aci_tenant_absent + <<: *aci_info + tenant: ansible_tenant + state: absent + +- name: Verify Cloud and Non-Cloud Sites in use. + include_tasks: ../../../../../../integration/targets/aci_cloud_provider/tasks/main.yml + +- name: Execute tasks only for non-cloud sites + when: query_cloud.current == [] # This condition will execute only non-cloud sites + block: # block specifies execution of tasks within, based on conditions + - name: Add a new tenant + aci_tenant: &aci_tenant_present + <<: *aci_info + tenant: ansible_tenant + description: Ansible tenant + state: present + + - name: Add a match rule profile + aci_match_rule: + <<: *aci_info + tenant: ansible_tenant + match_rule: ansible_match_rule + description: match rule profile for ansible_tenant tenant + state: present + + - name: Add a match community term + aci_match_community_term: + <<: *aci_info + tenant: ansible_tenant + match_rule: ansible_match_rule + match_community_term: ansible_match_community_term + description: match community term for ansible_match_rule match rule profile + state: present + + - name: Add a match community factor (check_mode) + aci_match_community_factor: &aci_match_community_factor_present + <<: *aci_info + tenant: ansible_tenant + match_rule: ansible_match_rule + match_community_term: ansible_match_community_term + community: regular:as2-nn2:4:15 + scope: non-transitive + description: match community factor 1 for ansible_match_community_term + state: present + check_mode: true + register: cm_add_match_community_factor + + - name: Add a match community factor (normal_mode) + aci_match_community_factor: + <<: *aci_match_community_factor_present + register: nm_add_match_community_factor + + - name: Add the first match community factor again - testing idempotency + aci_match_community_factor: + <<: *aci_match_community_factor_present + register: nm_add_match_community_factor_idempotency + + - name: Add a second match community factor (normal_mode) + aci_match_community_factor: + <<: *aci_info + tenant: ansible_tenant + match_rule: ansible_match_rule + match_community_term: ansible_match_community_term + community: regular:as2-nn2:4:16 + description: match community factor 2 for ansible_match_community_term + state: present + register: nm_add_match_community_factor_2 + + - name: Asserts for match community factors creation tasks + assert: + that: + - cm_add_match_community_factor is changed + - cm_add_match_community_factor.previous == [] + - cm_add_match_community_factor.current == [] + - nm_add_match_community_factor is changed + - nm_add_match_community_factor.current.0.rtctrlMatchCommFactor.attributes.community == "regular:as2-nn2:4:15" + - nm_add_match_community_factor_idempotency is not changed + - nm_add_match_community_factor_2 is changed + - nm_add_match_community_factor_2.previous == [] + - nm_add_match_community_factor_2.current.0.rtctrlMatchCommFactor.attributes.community == "regular:as2-nn2:4:16" + + - name: Query all match community factors + aci_match_community_factor: + <<: *aci_info + state: query + register: query_all_match_community_factor + + - name: Query a specific match community factor + aci_match_community_factor: + <<: *aci_match_community_factor_present + state: query + register: query_ansible_match_community_factor_1 + + - name: Asserts query tasks + assert: + that: + - query_all_match_community_factor is not changed + - query_all_match_community_factor.current|length >= 2 + - query_ansible_match_community_factor_1 is not changed + - query_ansible_match_community_factor_1.current.0.rtctrlMatchCommFactor.attributes.community == "regular:as2-nn2:4:15" + + - name: Remove match community factor (check_mode) + aci_match_community_factor: &match_community_term_absent + <<: *aci_match_community_factor_present + state: absent + check_mode: true + register: cm_remove_match_community_factor + + - name: Remove match community factor (normal_mode) + aci_match_community_factor: + <<: *match_community_term_absent + register: nm_remove_match_community_factor + + - name: Remove match community factor again - testing previous Removal + aci_match_community_factor: + <<: *match_community_term_absent + register: nm_remove_match_community_factor_idempotency + + - name: Asserts deletion tasks + assert: + that: + - cm_remove_match_community_factor is changed + - cm_remove_match_community_factor.proposed == {} + - nm_remove_match_community_factor is changed + - nm_remove_match_community_factor.previous != [] + - nm_remove_match_community_factor.method == "DELETE" + - nm_remove_match_community_factor_idempotency is not changed + - nm_remove_match_community_factor_idempotency.previous == [] + + - name: Remove the ansible_tenant - cleanup before ending tests + aci_tenant: + <<: *aci_tenant_present + state: absent diff --git a/ansible_collections/cisco/aci/tests/integration/targets/aci_match_community_regex_term/aliases b/ansible_collections/cisco/aci/tests/integration/targets/aci_match_community_regex_term/aliases new file mode 100644 index 000000000..209b793f9 --- /dev/null +++ b/ansible_collections/cisco/aci/tests/integration/targets/aci_match_community_regex_term/aliases @@ -0,0 +1,2 @@ +# No ACI simulator yet, so not enabled +# unsupported diff --git a/ansible_collections/cisco/aci/tests/integration/targets/aci_match_community_regex_term/tasks/main.yml b/ansible_collections/cisco/aci/tests/integration/targets/aci_match_community_regex_term/tasks/main.yml new file mode 100644 index 000000000..c0b12c78c --- /dev/null +++ b/ansible_collections/cisco/aci/tests/integration/targets/aci_match_community_regex_term/tasks/main.yml @@ -0,0 +1,152 @@ +# Test code for the ACI modules +# Copyright: (c) 2023, Gaspard Micol (@gmicol) <gmicol@cisco.com> + +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +- name: Test that we have an ACI APIC host, ACI username and ACI password + fail: + msg: 'Please define the following variables: aci_hostname, aci_username and aci_password.' + when: aci_hostname is not defined or aci_username is not defined or aci_password is not defined + +- name: Set vars + set_fact: + aci_info: &aci_info + host: "{{ aci_hostname }}" + username: "{{ aci_username }}" + password: "{{ aci_password }}" + validate_certs: '{{ aci_validate_certs | default(false) }}' + use_ssl: '{{ aci_use_ssl | default(true) }}' + use_proxy: '{{ aci_use_proxy | default(true) }}' + output_level: debug + +# CLEAN ENVIRONMENT +- name: Remove the ansible_tenant + aci_tenant: &aci_tenant_absent + <<: *aci_info + tenant: ansible_tenant + state: absent + +- name: Verify Cloud and Non-Cloud Sites in use. + include_tasks: ../../../../../../integration/targets/aci_cloud_provider/tasks/main.yml + +- name: Execute tasks only for non-cloud sites + when: query_cloud.current == [] # This condition will execute only non-cloud sites + block: # block specifies execution of tasks within, based on conditions + - name: Add a new tenant + aci_tenant: &aci_tenant_present + <<: *aci_info + tenant: ansible_tenant + description: Ansible tenant + state: present + + - name: Add a match rule profile + aci_match_rule: + <<: *aci_info + tenant: ansible_tenant + match_rule: ansible_match_rule + description: match rule profile for ansible_tenant tenant + state: present + register: cm_add_match_rule + + - name: Add a match community regex term (check_mode) + aci_match_community_regex_term: &aci_match_community_regex_term_present + <<: *aci_info + tenant: ansible_tenant + match_rule: ansible_match_rule + name: ansible_community_regex_extended + description: match extended community regex term for ansible_match_rule match rule profile + community_type: extended + regex: .* + state: present + check_mode: true + register: cm_add_match_community_regex_term + + - name: Add a match community regex term (normal_mode) + aci_match_community_regex_term: + <<: *aci_match_community_regex_term_present + register: nm_add_match_community_regex_term + + - name: Add the first match community regex term again - testing idempotency + aci_match_community_regex_term: + <<: *aci_match_community_regex_term_present + register: nm_add_match_community_regex_term_idempotency + + - name: Add a second match community regex term (normal_mode) + aci_match_community_regex_term: &aci_match_community_regex_term_2_present + <<: *aci_info + tenant: ansible_tenant + match_rule: ansible_match_rule + description: match regular community regex term for ansible_match_rule match rule profile + state: present + register: nm_add_match_community_regex_term_2 + + - name: Asserts for match community regex terms creation tasks + assert: + that: + - cm_add_match_community_regex_term is changed + - cm_add_match_community_regex_term.previous == [] + - cm_add_match_community_regex_term.current == [] + - nm_add_match_community_regex_term is changed + - nm_add_match_community_regex_term.current.0.rtctrlMatchCommRegexTerm.attributes.regex == ".*" + - nm_add_match_community_regex_term.current.0.rtctrlMatchCommRegexTerm.attributes.name == "ansible_community_regex_extended" + - nm_add_match_community_regex_term.current.0.rtctrlMatchCommRegexTerm.attributes.commType == "extended" + - nm_add_match_community_regex_term_idempotency is not changed + - nm_add_match_community_regex_term_2 is changed + - nm_add_match_community_regex_term_2.previous == [] + - nm_add_match_community_regex_term_2.current.0.rtctrlMatchCommRegexTerm.attributes.regex == "" + - nm_add_match_community_regex_term_2.current.0.rtctrlMatchCommRegexTerm.attributes.commType == "regular" + + - name: Query all match community regex terms + aci_match_community_regex_term: + <<: *aci_info + state: query + community_type: extended + register: query_all_match_community_regex_term + + - name: Query ansible_match_community_regex_term_regular + aci_match_community_regex_term: + <<: *aci_match_community_regex_term_2_present + state: query + register: query_ansible_match_community_regex_term_regular + + - name: Asserts query tasks + assert: + that: + - query_all_match_community_regex_term is not changed + - query_all_match_community_regex_term.current.0.rtctrlMatchCommRegexTerm.attributes.commType == "extended" + - query_ansible_match_community_regex_term_regular is not changed + - query_ansible_match_community_regex_term_regular.current.0.rtctrlMatchCommRegexTerm.attributes.regex == "" + - query_ansible_match_community_regex_term_regular.current.0.rtctrlMatchCommRegexTerm.attributes.commType == "regular" + + - name: Remove match community regex term (check_mode) + aci_match_community_regex_term: &match_community_regex_term_absent + <<: *aci_match_community_regex_term_present + state: absent + check_mode: true + register: cm_remove_match_community_regex_term + + - name: Remove match community regex term (normal_mode) + aci_match_community_regex_term: + <<: *match_community_regex_term_absent + register: nm_remove_match_community_regex_term + + - name: Remove match community regex term again - testing previous Removal + aci_match_community_regex_term: + <<: *match_community_regex_term_absent + register: nm_remove_match_community_regex_term_idempotency + + - name: Asserts deletion tasks + assert: + that: + - cm_remove_match_community_regex_term is changed + - cm_remove_match_community_regex_term.proposed == {} + - nm_remove_match_community_regex_term is changed + - nm_remove_match_community_regex_term.previous != [] + - nm_remove_match_community_regex_term.method == "DELETE" + - nm_remove_match_community_regex_term_idempotency is not changed + - nm_remove_match_community_regex_term_idempotency.previous == [] + + - name: Remove the ansible_tenant - cleanup before ending tests + aci_tenant: + <<: *aci_tenant_present + state: absent
\ No newline at end of file diff --git a/ansible_collections/cisco/aci/tests/integration/targets/aci_match_community_term/aliases b/ansible_collections/cisco/aci/tests/integration/targets/aci_match_community_term/aliases new file mode 100644 index 000000000..209b793f9 --- /dev/null +++ b/ansible_collections/cisco/aci/tests/integration/targets/aci_match_community_term/aliases @@ -0,0 +1,2 @@ +# No ACI simulator yet, so not enabled +# unsupported diff --git a/ansible_collections/cisco/aci/tests/integration/targets/aci_match_community_term/tasks/main.yml b/ansible_collections/cisco/aci/tests/integration/targets/aci_match_community_term/tasks/main.yml new file mode 100644 index 000000000..aecf89096 --- /dev/null +++ b/ansible_collections/cisco/aci/tests/integration/targets/aci_match_community_term/tasks/main.yml @@ -0,0 +1,146 @@ +# Test code for the ACI modules +# Copyright: (c) 2023, Gaspard Micol (@gmicol) <gmicol@cisco.com> + +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +- name: Test that we have an ACI APIC host, ACI username and ACI password + fail: + msg: 'Please define the following variables: aci_hostname, aci_username and aci_password.' + when: aci_hostname is not defined or aci_username is not defined or aci_password is not defined + +- name: Set vars + set_fact: + aci_info: &aci_info + host: "{{ aci_hostname }}" + username: "{{ aci_username }}" + password: "{{ aci_password }}" + validate_certs: '{{ aci_validate_certs | default(false) }}' + use_ssl: '{{ aci_use_ssl | default(true) }}' + use_proxy: '{{ aci_use_proxy | default(true) }}' + output_level: debug + +# CLEAN ENVIRONMENT +- name: Remove the ansible_tenant + aci_tenant: &aci_tenant_absent + <<: *aci_info + tenant: ansible_tenant + state: absent + +- name: Verify Cloud and Non-Cloud Sites in use. + include_tasks: ../../../../../../integration/targets/aci_cloud_provider/tasks/main.yml + +- name: Execute tasks only for non-cloud sites + when: query_cloud.current == [] # This condition will execute only non-cloud sites + block: # block specifies execution of tasks within, based on conditions + - name: Add a new tenant + aci_tenant: &aci_tenant_present + <<: *aci_info + tenant: ansible_tenant + description: Ansible tenant + state: present + + - name: Add a match rule profile + aci_match_rule: + <<: *aci_info + tenant: ansible_tenant + match_rule: ansible_match_rule + description: match rule profile for ansible_tenant tenant + state: present + register: cm_add_match_rule + + - name: Add a match community term (check_mode) + aci_match_community_term: &aci_match_community_term_present + <<: *aci_info + tenant: ansible_tenant + match_rule: ansible_match_rule + match_community_term: ansible_match_community_term_1 + description: match community term 1 for ansible_match_rule match rule profile + state: present + check_mode: true + register: cm_add_match_community_term + + - name: Add a match community term (normal_mode) + aci_match_community_term: + <<: *aci_match_community_term_present + register: nm_add_match_community_term + + - name: Add the first match community term again - testing idempotency + aci_match_community_term: + <<: *aci_match_community_term_present + register: nm_add_match_community_term_idempotency + + - name: Add a second match community term (normal_mode) + aci_match_community_term: + <<: *aci_info + tenant: ansible_tenant + match_rule: ansible_match_rule + match_community_term: ansible_match_community_term_2 + description: match community term 2 for ansible_match_rule match rule profile + state: present + register: nm_add_match_community_term_2 + + - name: Asserts for match community terms creation tasks + assert: + that: + - cm_add_match_community_term is changed + - cm_add_match_community_term.previous == [] + - cm_add_match_community_term.current == [] + - nm_add_match_community_term is changed + - nm_add_match_community_term.current.0.rtctrlMatchCommTerm.attributes.name == "ansible_match_community_term_1" + - nm_add_match_community_term_idempotency is not changed + - nm_add_match_community_term_2 is changed + - nm_add_match_community_term_2.previous == [] + - nm_add_match_community_term_2.current.0.rtctrlMatchCommTerm.attributes.name == "ansible_match_community_term_2" + + - name: Query all match community terms + aci_match_community_term: + <<: *aci_info + state: query + register: query_all_match_community_term + + - name: Query ansible_match_community_term_1 + aci_match_community_term: + <<: *aci_match_community_term_present + state: query + register: query_ansible_match_community_term_1 + + - name: Asserts query tasks + assert: + that: + - query_all_match_community_term is not changed + - query_all_match_community_term.current|length >= 2 + - query_ansible_match_community_term_1 is not changed + - query_ansible_match_community_term_1.current.0.rtctrlMatchCommTerm.attributes.name == "ansible_match_community_term_1" + + - name: Remove match community term (check_mode) + aci_match_community_term: &match_community_term_absent + <<: *aci_match_community_term_present + state: absent + check_mode: true + register: cm_remove_match_community_term + + - name: Remove match community term (normal_mode) + aci_match_community_term: + <<: *match_community_term_absent + register: nm_remove_match_community_term + + - name: Remove match community term again - testing previous Removal + aci_match_community_term: + <<: *match_community_term_absent + register: nm_remove_match_community_term_idempotency + + - name: Asserts deletion tasks + assert: + that: + - cm_remove_match_community_term is changed + - cm_remove_match_community_term.proposed == {} + - nm_remove_match_community_term is changed + - nm_remove_match_community_term.previous != [] + - nm_remove_match_community_term.method == "DELETE" + - nm_remove_match_community_term_idempotency is not changed + - nm_remove_match_community_term_idempotency.previous == [] + + - name: Remove the ansible_tenant - cleanup before ending tests + aci_tenant: + <<: *aci_tenant_present + state: absent diff --git a/ansible_collections/cisco/aci/tests/integration/targets/aci_match_route_destination/aliases b/ansible_collections/cisco/aci/tests/integration/targets/aci_match_route_destination/aliases new file mode 100644 index 000000000..209b793f9 --- /dev/null +++ b/ansible_collections/cisco/aci/tests/integration/targets/aci_match_route_destination/aliases @@ -0,0 +1,2 @@ +# No ACI simulator yet, so not enabled +# unsupported diff --git a/ansible_collections/cisco/aci/tests/integration/targets/aci_match_route_destination/tasks/main.yml b/ansible_collections/cisco/aci/tests/integration/targets/aci_match_route_destination/tasks/main.yml new file mode 100644 index 000000000..b4d49c2ff --- /dev/null +++ b/ansible_collections/cisco/aci/tests/integration/targets/aci_match_route_destination/tasks/main.yml @@ -0,0 +1,159 @@ +# Test code for the ACI modules +# Copyright: (c) 2023, Gaspard Micol (@gmicol) <gmicol@cisco.com> +# Copyright: (c) 2023, Tim Cragg (@timcragg) <tcragg@cisco.com> + +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +- name: Test that we have an ACI APIC host, ACI username and ACI password + fail: + msg: 'Please define the following variables: aci_hostname, aci_username and aci_password.' + when: aci_hostname is not defined or aci_username is not defined or aci_password is not defined + +- name: Set vars + set_fact: + aci_info: &aci_info + host: "{{ aci_hostname }}" + username: "{{ aci_username }}" + password: "{{ aci_password }}" + validate_certs: '{{ aci_validate_certs | default(false) }}' + use_ssl: '{{ aci_use_ssl | default(true) }}' + use_proxy: '{{ aci_use_proxy | default(true) }}' + output_level: debug + +# CLEAN ENVIRONMENT +- name: Remove the ansible_tenant + aci_tenant: &aci_tenant_absent + <<: *aci_info + tenant: ansible_tenant + state: absent + +- name: Verify Cloud and Non-Cloud Sites in use. + include_tasks: ../../../../../../integration/targets/aci_cloud_provider/tasks/main.yml + +- name: Execute tasks only for non-cloud sites + when: query_cloud.current == [] # This condition will execute only non-cloud sites + block: # block specifies execution of tasks within, based on conditions + - name: Add a new tenant + aci_tenant: &aci_tenant_present + <<: *aci_info + tenant: ansible_tenant + description: Ansible tenant + state: present + + - name: Add a match rule profile + aci_match_rule: + <<: *aci_info + tenant: ansible_tenant + match_rule: ansible_match_rule + description: subject profile for ansible_tenant tenant + state: present + register: cm_add_match_rule + + - name: Add a match route destination rule (check_mode) + aci_match_route_destination: &aci_match_route_destination_present + <<: *aci_info + tenant: ansible_tenant + match_rule: ansible_match_rule + description: match route destination rule 1 for ansible_match_rule match rule profile + ip: 11.11.11.11/24 + aggregate: yes + from_prefix_length: 0 + to_prefix_length: 32 + state: present + check_mode: true + register: cm_add_match_route_destination + + - name: Add a match route destination rule (normal_mode) + aci_match_route_destination: + <<: *aci_match_route_destination_present + register: nm_add_match_route_destination + + - name: Add the first match route destination rule again - testing idempotency + aci_match_route_destination: + <<: *aci_match_route_destination_present + register: nm_add_match_route_destination_idempotency + + - name: Add a second match route destination rule (normal_mode) + aci_match_route_destination: + <<: *aci_info + tenant: ansible_tenant + match_rule: ansible_match_rule + description: match route destination rule 2 for ansible_match_rule match rule profile + ip: 11.11.11.12/24 + state: present + register: nm_add_match_route_destination_2 + + - name: Asserts for match route destination rule creation tasks + assert: + that: + - cm_add_match_route_destination is changed + - cm_add_match_route_destination.previous == [] + - cm_add_match_route_destination.current == [] + - nm_add_match_route_destination is changed + - nm_add_match_route_destination.current.0.rtctrlMatchRtDest.attributes.ip == "11.11.11.11/24" + - nm_add_match_route_destination.current.0.rtctrlMatchRtDest.attributes.aggregate == "yes" + - nm_add_match_route_destination.current.0.rtctrlMatchRtDest.attributes.fromPfxLen == "0" + - nm_add_match_route_destination.current.0.rtctrlMatchRtDest.attributes.toPfxLen == "32" + - nm_add_match_route_destination_idempotency is not changed + - nm_add_match_route_destination_2 is changed + - nm_add_match_route_destination_2.previous == [] + - nm_add_match_route_destination_2.current.0.rtctrlMatchRtDest.attributes.ip == "11.11.11.12/24" + - nm_add_match_route_destination_2.current.0.rtctrlMatchRtDest.attributes.aggregate == "no" + - nm_add_match_route_destination_2.current.0.rtctrlMatchRtDest.attributes.fromPfxLen == "0" + - nm_add_match_route_destination_2.current.0.rtctrlMatchRtDest.attributes.toPfxLen == "0" + + - name: Query all match regex AS-Path terms + aci_match_route_destination: + <<: *aci_info + state: query + register: query_all_match_route_destination + + - name: Query ansible_match_route_destination_1 + aci_match_route_destination: + <<: *aci_match_route_destination_present + state: query + register: query_ansible_match_route_destination_1 + + - name: Asserts query tasks + assert: + that: + - query_all_match_route_destination is not changed + - query_all_match_route_destination.current|length >= 2 + - query_ansible_match_route_destination_1 is not changed + - query_ansible_match_route_destination_1.current.0.rtctrlMatchRtDest.attributes.ip == "11.11.11.11/24" + - query_ansible_match_route_destination_1.current.0.rtctrlMatchRtDest.attributes.aggregate == "yes" + - query_ansible_match_route_destination_1.current.0.rtctrlMatchRtDest.attributes.fromPfxLen == "0" + - query_ansible_match_route_destination_1.current.0.rtctrlMatchRtDest.attributes.toPfxLen == "32" + + - name: Remove match route destination rule (check_mode) + aci_match_route_destination: &match_route_destination_absent + <<: *aci_match_route_destination_present + state: absent + check_mode: true + register: cm_remove_match_route_destination + + - name: Remove match route destination rule (normal_mode) + aci_match_route_destination: + <<: *match_route_destination_absent + register: nm_remove_match_route_destination + + - name: Remove match route destination rule again - testing previous Removal + aci_match_route_destination: + <<: *match_route_destination_absent + register: nm_remove_match_route_destination_idempotency + + - name: Asserts deletion tasks + assert: + that: + - cm_remove_match_route_destination is changed + - cm_remove_match_route_destination.proposed == {} + - nm_remove_match_route_destination is changed + - nm_remove_match_route_destination.previous != [] + - nm_remove_match_route_destination.method == "DELETE" + - nm_remove_match_route_destination_idempotency is not changed + - nm_remove_match_route_destination_idempotency.previous == [] + + - name: Remove the ansible_tenant - cleanup before ending tests + aci_tenant: + <<: *aci_tenant_present + state: absent
\ No newline at end of file diff --git a/ansible_collections/cisco/aci/tests/integration/targets/aci_match_rule/aliases b/ansible_collections/cisco/aci/tests/integration/targets/aci_match_rule/aliases new file mode 100644 index 000000000..209b793f9 --- /dev/null +++ b/ansible_collections/cisco/aci/tests/integration/targets/aci_match_rule/aliases @@ -0,0 +1,2 @@ +# No ACI simulator yet, so not enabled +# unsupported diff --git a/ansible_collections/cisco/aci/tests/integration/targets/aci_match_rule/tasks/main.yml b/ansible_collections/cisco/aci/tests/integration/targets/aci_match_rule/tasks/main.yml new file mode 100644 index 000000000..23509494c --- /dev/null +++ b/ansible_collections/cisco/aci/tests/integration/targets/aci_match_rule/tasks/main.yml @@ -0,0 +1,136 @@ +# Test code for the ACI modules +# Copyright: (c) 2023, Gaspard Micol (@gmicol) <gmicol@cisco.com> +# Copyright: (c) 2023, Tim Cragg (@timcragg) <tcragg@cisco.com> + +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +- name: Test that we have an ACI APIC host, ACI username and ACI password + fail: + msg: 'Please define the following variables: aci_hostname, aci_username and aci_password.' + when: aci_hostname is not defined or aci_username is not defined or aci_password is not defined + +- name: Set vars + set_fact: + aci_info: &aci_info + host: "{{ aci_hostname }}" + username: "{{ aci_username }}" + password: "{{ aci_password }}" + validate_certs: '{{ aci_validate_certs | default(false) }}' + use_ssl: '{{ aci_use_ssl | default(true) }}' + use_proxy: '{{ aci_use_proxy | default(true) }}' + output_level: debug + +# CLEAN ENVIRONMENT +- name: Remove the ansible_tenant + aci_tenant: &aci_tenant_absent + <<: *aci_info + tenant: ansible_tenant + state: absent + +- name: Verify Cloud and Non-Cloud Sites in use. + include_tasks: ../../../../../../integration/targets/aci_cloud_provider/tasks/main.yml + +- name: Execute tasks only for non-cloud sites + when: query_cloud.current == [] # This condition will execute only non-cloud sites + block: # block specifies execution of tasks within, based on conditions + - name: Add a new tenant + aci_tenant: &aci_tenant_present + <<: *aci_info + tenant: ansible_tenant + description: Ansible tenant + state: present + + - name: Add a match rule profile (check_mode) + aci_match_rule: &aci_match_rule_present + <<: *aci_info + tenant: ansible_tenant + match_rule: ansible_match_rule_1 + description: match rule profile 1 for ansible_tenant tenant + state: present + check_mode: true + register: cm_add_match_rule + + - name: Add a match rule profile (normal_mode) + aci_match_rule: + <<: *aci_match_rule_present + register: nm_add_match_rule + + - name: Add the first match rule profile again - testing idempotency + aci_match_rule: + <<: *aci_match_rule_present + register: nm_add_match_rule_idempotency + + - name: Add a second match rule profile (normal_mode) + aci_match_rule: + <<: *aci_info + tenant: ansible_tenant + match_rule: ansible_match_rule_2 + description: match rule profile 2 for ansible_tenant tenant + state: present + register: nm_add_match_rule_2 + + - name: Asserts for match rule profiles creation tasks + assert: + that: + - cm_add_match_rule is changed + - cm_add_match_rule.previous == [] + - cm_add_match_rule.current == [] + - nm_add_match_rule is changed + - nm_add_match_rule.current.0.rtctrlSubjP.attributes.name == "ansible_match_rule_1" + - nm_add_match_rule_idempotency is not changed + - nm_add_match_rule_2 is changed + - nm_add_match_rule_2.previous == [] + - nm_add_match_rule_2.current.0.rtctrlSubjP.attributes.name == "ansible_match_rule_2" + + - name: Query all match rule profiles + aci_match_rule: + <<: *aci_info + state: query + register: query_all_match_rule + + - name: Query ansible_match_rule_1 + aci_match_rule: + <<: *aci_match_rule_present + state: query + register: query_ansible_match_rule_1 + + - name: Asserts query tasks + assert: + that: + - query_all_match_rule is not changed + - query_all_match_rule.current|length >= 2 + - query_ansible_match_rule_1 is not changed + - query_ansible_match_rule_1.current.0.rtctrlSubjP.attributes.name == "ansible_match_rule_1" + + - name: Remove match rule profile for l3out (check_mode) + aci_match_rule: &match_rule_absent + <<: *aci_match_rule_present + state: absent + check_mode: true + register: cm_remove_match_rule + + - name: Remove match rule profile for l3out (normal_mode) + aci_match_rule: + <<: *match_rule_absent + register: nm_remove_match_rule + + - name: Remove match rule profile for l3out again - testing previous Removal + aci_match_rule: + <<: *match_rule_absent + register: nm_remove_match_rule_idempotency + + - name: Asserts deletion tasks + assert: + that: + - cm_remove_match_rule is changed + - cm_remove_match_rule.proposed == {} + - nm_remove_match_rule is changed + - nm_remove_match_rule.previous != [] + - nm_remove_match_rule.method == "DELETE" + - nm_remove_match_rule_idempotency is not changed + - nm_remove_match_rule_idempotency.previous == [] + + - name: Remove the ansible_tenant - cleanup before ending tests + aci_tenant: + <<: *aci_tenant_present + state: absent diff --git a/ansible_collections/cisco/aci/tests/integration/targets/aci_rest/tasks/error_handling.yml b/ansible_collections/cisco/aci/tests/integration/targets/aci_rest/tasks/error_handling.yml index 1c503c5f2..49c6fca53 100644 --- a/ansible_collections/cisco/aci/tests/integration/targets/aci_rest/tasks/error_handling.yml +++ b/ansible_collections/cisco/aci/tests/integration/targets/aci_rest/tasks/error_handling.yml @@ -27,11 +27,10 @@ assert: that: - error_on_name_resolution is failed - - error_on_name_resolution.msg.startswith("Connection failed for https://foo.bar.cisco.com/api/aaaLogin.json. Request failed:") + - error_on_name_resolution.msg.startswith("Connection failed for https://foo.bar.cisco.com/api/aaaLogin.json.") - "'current' not in error_on_name_resolution" - "'previous' not in error_on_name_resolution" - "'sent' not in error_on_name_resolution" - - "'proposed' not in error_on_name_resolution" - "'filter_string' not in error_on_name_resolution" - name: Error when required parameter is missing @@ -59,7 +58,6 @@ - "'current' not in error_on_missing_required_param" - "'previous' not in error_on_missing_required_param" - "'sent' not in error_on_missing_required_param" - - "'proposed' not in error_on_missing_required_param" - "'filter_string' not in error_on_missing_required_param" - name: Error when attributes are missing @@ -90,7 +88,6 @@ - "'current' not in error_on_missing_attributes" - "'previous' not in error_on_missing_attributes" - "'sent' not in error_on_missing_attributes" - - "'proposed' not in error_on_missing_attributes" - "'filter_string' not in error_on_missing_attributes" - name: Error when input does not validate @@ -123,7 +120,6 @@ - "'current' not in error_on_input_validation" - "'previous' not in error_on_input_validation" - "'sent' not in error_on_input_validation" - - "'proposed' not in error_on_input_validation" - "'filter_string' not in error_on_input_validation" - name: Error when invalid attributes are used @@ -156,7 +152,6 @@ - "'current' not in error_on_invalid_attributes" - "'previous' not in error_on_invalid_attributes" - "'sent' not in error_on_invalid_attributes" - - "'proposed' not in error_on_invalid_attributes" - "'filter_string' not in error_on_invalid_attributes" - name: Error on invalid object @@ -188,7 +183,6 @@ - "'current' not in error_on_invalid_object" - "'previous' not in error_on_invalid_object" - "'sent' not in error_on_invalid_object" - - "'proposed' not in error_on_invalid_object" # Test case for certificate based error issue: https://github.com/CiscoDevNet/ansible-aci/issues/339 # Original error was with ospfCtxPol but same behaviour detected for tenant creation thus simplifying the test case @@ -227,4 +221,104 @@ cisco.aci.aci_rest: <<: *tenant content: - <fvTenant name="test_tenant_cert_auth" status="deleted"/>
\ No newline at end of file + <fvTenant name="test_tenant_cert_auth" status="deleted"/> + +# Test cases for import error handling + +- name: Uninstall lxml for error checking + ansible.builtin.pip: &lxml + name: lxml + state: absent + ignore_errors: true # ignore errors to because of multiple executions for hosts at the same time + +- name: Uninstall xmljson for error checking + ansible.builtin.pip: &xmljson + name: xmljson + state: absent + ignore_errors: true # ignore errors to because of multiple executions for hosts at the same time + +- name: Uninstall pyyaml for error checking + ansible.builtin.pip: &pyyaml + name: pyyaml + state: absent + ignore_errors: true # ignore errors to because of multiple executions for hosts at the same time + when: ansible_connection == "local" + +- name: Create tenant with invalid src attribute + cisco.aci.aci_rest: + <<: *tenant + ignore_errors: true + register: err_missing_lxml + +- name: Install lxml + ansible.builtin.pip: + <<: *lxml + state: present + ignore_errors: true # ignore errors to because of multiple executions for hosts at the same time + +- name: Create tenant with invalid src attribute + cisco.aci.aci_rest: + <<: *tenant + ignore_errors: true + register: err_missing_xmljson + +- name: Install xmljson + ansible.builtin.pip: + <<: *xmljson + state: present + ignore_errors: true # ignore errors to because of multiple executions for hosts at the same time + +- name: Install pyyaml + ansible.builtin.pip: + <<: *pyyaml + state: present + ignore_errors: true # ignore errors to because of multiple executions for hosts at the same time + when: ansible_connection == "local" + +# Test case for invalid src + +- name: Create tenant with invalid src attribute + cisco.aci.aci_rest: + <<: *tenant + src: "./targets/aci_rest/invalid_src.txt" + content: "{{ fakevar | default(omit) }}" + ignore_errors: true + register: err_src_attribute + +# Test case for invalid path extension + +- name: Create tenant with invalid path extension + cisco.aci.aci_rest: + <<: *tenant + path: /api/mo/uni.invalid + ignore_errors: true + register: err_extension + +# Parse failures + +- name: Create tenant with fail to parse xml + cisco.aci.aci_rest: + <<: *tenant + src: "./targets/aci_rest/tasks/xml_files/fail_parse.xml" + content: "{{ fakevar | default(omit) }}" + ignore_errors: true + register: err_fail_parse_xml + +- name: Create tenant with fail to parse json + cisco.aci.aci_rest: + <<: *tenant + path: /api/mo/uni.json + src: "./targets/aci_rest/tasks/xml_files/fail_parse.json" + content: "{{ fakevar | default(omit) }}" + ignore_errors: true + register: err_fail_parse_json + +- name: Assertions checks for import error handling, invalid src, invalid path extension and parse failures + assert: + that: + - err_missing_lxml.msg == "The lxml python library is missing, or lacks etree support." + - err_missing_xmljson.msg == "The xmljson python library is missing, or lacks cobra support." + - err_src_attribute.msg == "Cannot find/access src './targets/aci_rest/invalid_src.txt'" + - err_extension.msg == "Failed to find REST API payload type (neither .xml nor .json)." + - err_fail_parse_xml.msg.startswith("Failed to parse provided XML payload") + - err_fail_parse_json.msg.startswith("Failed to parse provided JSON/YAML payload") diff --git a/ansible_collections/cisco/aci/tests/integration/targets/aci_rest/tasks/json_inline.yml b/ansible_collections/cisco/aci/tests/integration/targets/aci_rest/tasks/json_inline.yml index dd27525f9..3d5c9be48 100644 --- a/ansible_collections/cisco/aci/tests/integration/targets/aci_rest/tasks/json_inline.yml +++ b/ansible_collections/cisco/aci/tests/integration/targets/aci_rest/tasks/json_inline.yml @@ -18,7 +18,7 @@ method: delete # ADD TENANT -- name: Add tenant (normal mode) +- name: Add tenant (check mode) cisco.aci.aci_rest: &tenant_present host: '{{ aci_hostname }}' username: '{{ aci_username }}' @@ -37,12 +37,35 @@ } } } - delegate_to: localhost + register: cm_add_tenant + check_mode: true + +- name: Verify checkmode did not create tenant + cisco.aci.aci_tenant: + host: '{{ aci_hostname }}' + username: '{{ aci_username }}' + password: '{{ aci_password }}' + validate_certs: '{{ aci_validate_certs | default(false) }}' + use_ssl: '{{ aci_use_ssl | default(true) }}' + use_proxy: '{{ aci_use_proxy | default(true) }}' + output_level: '{{ aci_output_level | default("info") }}' + name: ansible_test + state: query + register: cm_verify_checkmode_tenant + +- name: Verify checkmode POST operation + assert: + that: + - cm_add_tenant is changed + - cm_add_tenant.proposed.fvTenant.attributes.name == "ansible_test" + - cm_verify_checkmode_tenant.current == [] + +- name: Add tenant (normal mode) + cisco.aci.aci_rest: *tenant_present register: nm_add_tenant - name: Add tenant again (normal mode) cisco.aci.aci_rest: *tenant_present - delegate_to: localhost register: nm_add_tenant_again - name: Verify add_tenant @@ -72,12 +95,10 @@ } } } - delegate_to: localhost register: nm_add_tenant_descr - name: Change description of tenant again (normal mode) cisco.aci.aci_rest: *tenant_changed - delegate_to: localhost register: nm_add_tenant_descr_again - name: Verify add_tenant_descr @@ -89,7 +110,6 @@ # ADD TENANT AGAIN - name: Add tenant again with no description (normal mode) cisco.aci.aci_rest: *tenant_present - delegate_to: localhost register: nm_add_tenant_again_no_descr - name: Verify add_tenant_again_no_descr @@ -109,7 +129,6 @@ output_level: '{{ aci_output_level | default("info") }}' path: /api/mo/uni/tn-[ansible_test].json method: get - delegate_to: localhost register: nm_query_all_tenants - name: Verify query_all_tenants @@ -129,7 +148,6 @@ output_level: '{{ aci_output_level | default("info") }}' path: /api/mo/uni/tn-[ansible_test].json method: get - delegate_to: localhost register: nm_query_tenant - name: Verify query_tenant @@ -140,12 +158,10 @@ # REMOVE TENANT - name: Remove tenant (normal mode) cisco.aci.aci_rest: *tenant_absent - delegate_to: localhost register: nm_remove_tenant - name: Remove tenant again (normal mode) cisco.aci.aci_rest: *tenant_absent - delegate_to: localhost register: nm_remove_tenant_again - name: Verify remove_tenant @@ -157,7 +173,6 @@ # QUERY NON-EXISTING TENANT - name: Query non-existing tenant (normal mode) cisco.aci.aci_rest: *tenant_query - delegate_to: localhost register: nm_query_non_tenant - name: Verify query_non_tenant diff --git a/ansible_collections/cisco/aci/tests/integration/targets/aci_rest/tasks/json_string.yml b/ansible_collections/cisco/aci/tests/integration/targets/aci_rest/tasks/json_string.yml index bbbd57cd4..7a1dfd8ce 100644 --- a/ansible_collections/cisco/aci/tests/integration/targets/aci_rest/tasks/json_string.yml +++ b/ansible_collections/cisco/aci/tests/integration/targets/aci_rest/tasks/json_string.yml @@ -18,7 +18,7 @@ method: delete # ADD TENANT -- name: Add tenant (normal mode) +- name: Add tenant (check mode) cisco.aci.aci_rest: &tenant_present host: '{{ aci_hostname }}' username: '{{ aci_username }}' @@ -38,6 +38,31 @@ } } output_path: "/tmp/ansible_output_file.log" + register: cm_add_tenant + check_mode: true + +- name: Verify checkmode did not create tenant + cisco.aci.aci_tenant: + host: '{{ aci_hostname }}' + username: '{{ aci_username }}' + password: '{{ aci_password }}' + validate_certs: '{{ aci_validate_certs | default(false) }}' + use_ssl: '{{ aci_use_ssl | default(true) }}' + use_proxy: '{{ aci_use_proxy | default(true) }}' + output_level: '{{ aci_output_level | default("info") }}' + name: ansible_test + state: query + register: cm_verify_checkmode_tenant + +- name: Verify checkmode POST operation + assert: + that: + - cm_add_tenant is changed + - cm_add_tenant.proposed.fvTenant.attributes.name == "ansible_test" + - cm_verify_checkmode_tenant.current == [] + +- name: Add tenant (normal mode) + cisco.aci.aci_rest: *tenant_present register: nm_add_tenant - name: Add tenant again (normal mode) diff --git a/ansible_collections/cisco/aci/tests/integration/targets/aci_rest/tasks/main.yml b/ansible_collections/cisco/aci/tests/integration/targets/aci_rest/tasks/main.yml index 0952dd85b..c06f0cee2 100644 --- a/ansible_collections/cisco/aci/tests/integration/targets/aci_rest/tasks/main.yml +++ b/ansible_collections/cisco/aci/tests/integration/targets/aci_rest/tasks/main.yml @@ -24,8 +24,8 @@ - include_tasks: xml_string.yml tags: xml_string -- include_tasks: error_handling.yml - tags: error_handling - - include_tasks: xml_file.yml tags: xml_file + +- include_tasks: error_handling.yml + tags: error_handling diff --git a/ansible_collections/cisco/aci/tests/integration/targets/aci_rest/tasks/xml_file.yml b/ansible_collections/cisco/aci/tests/integration/targets/aci_rest/tasks/xml_file.yml index cdde037c4..fea63112b 100644 --- a/ansible_collections/cisco/aci/tests/integration/targets/aci_rest/tasks/xml_file.yml +++ b/ansible_collections/cisco/aci/tests/integration/targets/aci_rest/tasks/xml_file.yml @@ -32,10 +32,25 @@ check_mode: true register: cm_add_tenant +- name: Verify checkmode did not create tenant + cisco.aci.aci_tenant: + host: '{{ aci_hostname }}' + username: '{{ aci_username }}' + password: '{{ aci_password }}' + validate_certs: '{{ aci_validate_certs | default(false) }}' + use_ssl: '{{ aci_use_ssl | default(true) }}' + use_proxy: '{{ aci_use_proxy | default(true) }}' + output_level: '{{ aci_output_level | default("info") }}' + name: ans_test_create + state: query + register: cm_verify_checkmode_tenant + - name: Assertions check for add tenant using ans_test_create xml template file with check mode assert: that: - - cm_add_tenant is not changed + - cm_add_tenant is changed + - '"ans_test_create" in cm_add_tenant.proposed' + - cm_verify_checkmode_tenant.current == [] - name: Add tenant using ans_test_create xml template file with normal mode cisco.aci.aci_rest: @@ -95,7 +110,7 @@ - name: Assertions check for update tenant description using ans_test_update xml template file with check mode assert: that: - - cm_update_tenant is not changed + - cm_update_tenant is changed - name: Update tenant description using ans_test_update xml template file with normal mode cisco.aci.aci_rest: @@ -168,7 +183,7 @@ - name: Assertions check for delete tenant using ans_test_delete xml template file with check mode assert: that: - - cm_delete_tenant is not changed + - cm_delete_tenant is changed - name: Delete tenant using ans_test_delete xml template file with normal mode cisco.aci.aci_rest: diff --git a/ansible_collections/cisco/aci/tests/integration/targets/aci_rest/tasks/xml_files/fail_parse.json b/ansible_collections/cisco/aci/tests/integration/targets/aci_rest/tasks/xml_files/fail_parse.json new file mode 100644 index 000000000..b2d10f46a --- /dev/null +++ b/ansible_collections/cisco/aci/tests/integration/targets/aci_rest/tasks/xml_files/fail_parse.json @@ -0,0 +1 @@ +{wrong_payload
\ No newline at end of file diff --git a/ansible_collections/cisco/aci/tests/integration/targets/aci_rest/tasks/xml_files/fail_parse.xml b/ansible_collections/cisco/aci/tests/integration/targets/aci_rest/tasks/xml_files/fail_parse.xml new file mode 100644 index 000000000..96877ed2d --- /dev/null +++ b/ansible_collections/cisco/aci/tests/integration/targets/aci_rest/tasks/xml_files/fail_parse.xml @@ -0,0 +1 @@ +wrong_payload
\ No newline at end of file diff --git a/ansible_collections/cisco/aci/tests/integration/targets/aci_rest/tasks/xml_string.yml b/ansible_collections/cisco/aci/tests/integration/targets/aci_rest/tasks/xml_string.yml index c87291779..d1424bafa 100644 --- a/ansible_collections/cisco/aci/tests/integration/targets/aci_rest/tasks/xml_string.yml +++ b/ansible_collections/cisco/aci/tests/integration/targets/aci_rest/tasks/xml_string.yml @@ -18,6 +18,66 @@ method: delete # ADD TENANT +- name: Add tenant (check mode) + cisco.aci.aci_rest: + host: '{{ aci_hostname }}' + username: '{{ aci_username }}' + password: '{{ aci_password }}' + validate_certs: '{{ aci_validate_certs | default(false) }}' + use_ssl: '{{ aci_use_ssl | default(true) }}' + use_proxy: '{{ aci_use_proxy | default(true) }}' + output_level: '{{ aci_output_level | default("info") }}' + path: /api/mo/uni.xml + method: post + content: + <fvTenant name="ansible_test"/> + register: cm_add_tenant + check_mode: true + +- name: Add tenant 2 (check mode) + cisco.aci.aci_rest: + host: '{{ aci_hostname }}' + username: '{{ aci_username }}' + password: '{{ aci_password }}' + validate_certs: '{{ aci_validate_certs | default(false) }}' + use_ssl: '{{ aci_use_ssl | default(true) }}' + use_proxy: '{{ aci_use_proxy | default(true) }}' + output_level: '{{ aci_output_level | default("info") }}' + path: /api/mo/uni.xml + method: post + content: + { + "fvTenant": { + "attributes": { + "name": "ansible_test" + } + } + } + register: cm_add_tenant_2 + check_mode: true + +- name: Verify checkmode did not create tenant + cisco.aci.aci_tenant: &tenant_cm_query + host: '{{ aci_hostname }}' + username: '{{ aci_username }}' + password: '{{ aci_password }}' + validate_certs: '{{ aci_validate_certs | default(false) }}' + use_ssl: '{{ aci_use_ssl | default(true) }}' + use_proxy: '{{ aci_use_proxy | default(true) }}' + output_level: '{{ aci_output_level | default("info") }}' + name: ansible_test + state: query + register: cm_verify_checkmode_tenant + +- name: Verify checkmode POST operation + assert: + that: + - cm_add_tenant is changed + - '"ansible_test" in cm_add_tenant.proposed' + - cm_add_tenant_2 is changed + - '"ansible_test" in cm_add_tenant.proposed' + - cm_verify_checkmode_tenant.current == [] + - name: Add tenant (normal mode) cisco.aci.aci_rest: &tenant_present host: '{{ aci_hostname }}' diff --git a/ansible_collections/cisco/aci/tests/integration/targets/aci_rest/tasks/yaml_inline.yml b/ansible_collections/cisco/aci/tests/integration/targets/aci_rest/tasks/yaml_inline.yml index d7538f9a6..6cd06afcd 100644 --- a/ansible_collections/cisco/aci/tests/integration/targets/aci_rest/tasks/yaml_inline.yml +++ b/ansible_collections/cisco/aci/tests/integration/targets/aci_rest/tasks/yaml_inline.yml @@ -18,7 +18,7 @@ method: delete # ADD TENANT -- name: Add tenant (normal mode) +- name: Add tenant (check mode) cisco.aci.aci_rest: &tenant_present host: '{{ aci_hostname }}' username: '{{ aci_username }}' @@ -33,6 +33,31 @@ fvTenant: attributes: name: ansible_test + register: cm_add_tenant + check_mode: true + +- name: Verify checkmode did not create tenant + cisco.aci.aci_tenant: + host: '{{ aci_hostname }}' + username: '{{ aci_username }}' + password: '{{ aci_password }}' + validate_certs: '{{ aci_validate_certs | default(false) }}' + use_ssl: '{{ aci_use_ssl | default(true) }}' + use_proxy: '{{ aci_use_proxy | default(true) }}' + output_level: '{{ aci_output_level | default("info") }}' + name: ansible_test + state: query + register: cm_verify_checkmode_tenant + +- name: Verify checkmode POST operation + assert: + that: + - cm_add_tenant is changed + - cm_add_tenant.proposed.fvTenant.attributes.name == "ansible_test" + - cm_verify_checkmode_tenant.current == [] + +- name: Add tenant (normal mode) + cisco.aci.aci_rest: *tenant_present register: nm_add_tenant - name: Add tenant again (normal mode) diff --git a/ansible_collections/cisco/aci/tests/integration/targets/aci_rest/tasks/yaml_string.yml b/ansible_collections/cisco/aci/tests/integration/targets/aci_rest/tasks/yaml_string.yml index d7538f9a6..895045474 100644 --- a/ansible_collections/cisco/aci/tests/integration/targets/aci_rest/tasks/yaml_string.yml +++ b/ansible_collections/cisco/aci/tests/integration/targets/aci_rest/tasks/yaml_string.yml @@ -18,7 +18,7 @@ method: delete # ADD TENANT -- name: Add tenant (normal mode) +- name: Add tenant (check mode) cisco.aci.aci_rest: &tenant_present host: '{{ aci_hostname }}' username: '{{ aci_username }}' @@ -29,10 +29,35 @@ output_level: '{{ aci_output_level | default("info") }}' path: /api/mo/uni.json method: post - content: + content: | fvTenant: attributes: name: ansible_test + register: cm_add_tenant + check_mode: true + +- name: Verify checkmode did not create tenant + cisco.aci.aci_tenant: + host: '{{ aci_hostname }}' + username: '{{ aci_username }}' + password: '{{ aci_password }}' + validate_certs: '{{ aci_validate_certs | default(false) }}' + use_ssl: '{{ aci_use_ssl | default(true) }}' + use_proxy: '{{ aci_use_proxy | default(true) }}' + output_level: '{{ aci_output_level | default("info") }}' + name: ansible_test + state: query + register: cm_verify_checkmode_tenant + +- name: Verify checkmode POST operation + assert: + that: + - cm_add_tenant is changed + - cm_add_tenant.proposed.fvTenant.attributes.name == "ansible_test" + - cm_verify_checkmode_tenant.current == [] + +- name: Add tenant (normal mode) + cisco.aci.aci_rest: *tenant_present register: nm_add_tenant - name: Add tenant again (normal mode) diff --git a/ansible_collections/cisco/aci/tests/integration/targets/aci_route_control_context/aliases b/ansible_collections/cisco/aci/tests/integration/targets/aci_route_control_context/aliases new file mode 100644 index 000000000..209b793f9 --- /dev/null +++ b/ansible_collections/cisco/aci/tests/integration/targets/aci_route_control_context/aliases @@ -0,0 +1,2 @@ +# No ACI simulator yet, so not enabled +# unsupported diff --git a/ansible_collections/cisco/aci/tests/integration/targets/aci_route_control_context/tasks/main.yml b/ansible_collections/cisco/aci/tests/integration/targets/aci_route_control_context/tasks/main.yml new file mode 100644 index 000000000..83f46e4b8 --- /dev/null +++ b/ansible_collections/cisco/aci/tests/integration/targets/aci_route_control_context/tasks/main.yml @@ -0,0 +1,197 @@ +# Test code for the ACI modules +# Copyright: (c) 2023, Gaspard Micol (@gmicol) <gmicol@cisco.com> +# Copyright: (c) 2023, Tim Cragg (@timcragg) <tcragg@cisco.com> + +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +- name: Test that we have an ACI APIC host, ACI username and ACI password + fail: + msg: 'Please define the following variables: aci_hostname, aci_username and aci_password.' + when: aci_hostname is not defined or aci_username is not defined or aci_password is not defined + +- name: Set vars + set_fact: + aci_info: &aci_info + host: "{{ aci_hostname }}" + username: "{{ aci_username }}" + password: "{{ aci_password }}" + validate_certs: '{{ aci_validate_certs | default(false) }}' + use_ssl: '{{ aci_use_ssl | default(true) }}' + use_proxy: '{{ aci_use_proxy | default(true) }}' + output_level: debug + +# CLEAN ENVIRONMENT +- name: Remove the ansible_tenant + aci_tenant: &aci_tenant_absent + <<: *aci_info + tenant: ansible_tenant + state: absent + +- name: Verify Cloud and Non-Cloud Sites in use. + include_tasks: ../../../../../../integration/targets/aci_cloud_provider/tasks/main.yml + +- name: Execute tasks only for non-cloud sites + when: query_cloud.current == [] # This condition will execute only non-cloud sites + block: # block specifies execution of tasks within, based on conditions + - name: Add a new tenant + aci_tenant: &aci_tenant_present + <<: *aci_info + tenant: ansible_tenant + description: Ansible tenant + state: present + + - name: Add a new action rule profile + aci_tenant_action_rule_profile: &aci_action_rule_present + <<: *aci_info + tenant: ansible_tenant + action_rule: ansible_action_rule + description: Ansible action rule profile for ansible_tenant tenant + state: present + + - name: Add a new L3Out + aci_l3out: + <<: *aci_info + tenant: ansible_tenant + l3out: ansible_l3out + description: Ansible l3Out for ansible_tenant tenant + domain: ansible_dom + vrf: ansible_vrf + state: present + + - name: Add a route control profile for l3out + aci_route_control_profile: + <<: *aci_info + tenant: ansible_tenant + l3out: ansible_l3out + route_control_profile: ansible_rtctrl_profile_l3out + description: Ansible Route Control Profile for ansible_l3out l3Out + state: present + + - name: Add a subject profile + aci_match_rule: &aci_match_rule_present + <<: *aci_info + tenant: ansible_tenant + match_rule: ansible_match_rule + description: Ansible Subject Profile for ansible_tenant tenant + state: present + + - name: Add a route control context policy for l3out (check_mode) + aci_route_control_context: &aci_route_control_context_present + <<: *aci_info + tenant: ansible_tenant + l3out: ansible_l3out + route_control_profile: ansible_rtctrl_profile_l3out + route_control_context: ansible_route_control_context_l3out + description: Ansible route control Context Policy for ansible_rtctrl_profile_l3out route control profile + match_rule: ansible_match_rule + action_rule: ansible_action_rule + action: deny + order: 5 + state: present + check_mode: true + register: cm_add_route_control_context_l3out + + - name: Add a route control context policy again (normal_mode) + aci_route_control_context: + <<: *aci_route_control_context_present + register: nm_add_route_control_context_l3out + + - name: Add a route control context policy again - testing idempotency + aci_route_control_context: + <<: *aci_route_control_context_present + register: nm_add_route_control_context_l3out_idempotency + + - name: Add a route control profile for tenant + aci_route_control_profile: + <<: *aci_info + tenant: ansible_tenant + route_control_profile: ansible_rtctrl_profile_tenant + description: Route Control Profile for ansible_tenant tenant + state: present + + - name: Add a route control context policy for tenant + aci_route_control_context: + <<: *aci_info + tenant: ansible_tenant + route_control_profile: ansible_rtctrl_profile_tenant + route_control_context: ansible_route_control_context_tenant + description: Ansible route control Context Policy for ansible_rtctrl_profile_tenant route control profile + state: present + register: nm_add_route_control_context_tenant + + - name: Asserts for route control profiles creation tasks + assert: + that: + - cm_add_route_control_context_l3out is changed + - cm_add_route_control_context_l3out.previous == [] + - cm_add_route_control_context_l3out.current == [] + - nm_add_route_control_context_l3out is changed + - nm_add_route_control_context_l3out.current.0.rtctrlCtxP.attributes.name == "ansible_route_control_context_l3out" + - nm_add_route_control_context_l3out.current.0.rtctrlCtxP.attributes.action == "deny" + - nm_add_route_control_context_l3out.current.0.rtctrlCtxP.attributes.order == "5" + - nm_add_route_control_context_l3out.current.0.rtctrlCtxP.children.0.rtctrlScope.children.0.rtctrlRsScopeToAttrP.attributes.tnRtctrlAttrPName == "ansible_action_rule" + - nm_add_route_control_context_l3out.current.0.rtctrlCtxP.children.1.rtctrlRsCtxPToSubjP.attributes.tnRtctrlSubjPName == "ansible_match_rule" + - nm_add_route_control_context_l3out_idempotency is not changed + - nm_add_route_control_context_tenant is changed + - nm_add_route_control_context_tenant.previous == [] + - nm_add_route_control_context_tenant.current.0.rtctrlCtxP.attributes.name == "ansible_route_control_context_tenant" + - nm_add_route_control_context_tenant.current.0.rtctrlCtxP.attributes.action == "permit" + - nm_add_route_control_context_tenant.current.0.rtctrlCtxP.attributes.order == "0" + + - name: Query all route control context policies + aci_route_control_context: + <<: *aci_info + state: query + register: query_all_route_control_context + + - name: Query ansible_route_control_context_l3out route control context policy + aci_route_control_context: + <<: *aci_info + route_control_context: ansible_route_control_context_l3out + state: query + register: query_route_control_context_l3out + + - name: Asserts query tasks + assert: + that: + - query_all_route_control_context is not changed + - query_all_route_control_context.current|length >= 2 + - query_route_control_context_l3out is not changed + - query_route_control_context_l3out.current.0.rtctrlCtxP.attributes.name == "ansible_route_control_context_l3out" + - query_route_control_context_l3out.current.0.rtctrlCtxP.attributes.action == "deny" + - query_route_control_context_l3out.current.0.rtctrlCtxP.attributes.order == "5" + - query_route_control_context_l3out.current.0.rtctrlCtxP.children.0.rtctrlScope.children.0.rtctrlRsScopeToAttrP.attributes.tDn == "uni/tn-ansible_tenant/attr-ansible_action_rule" + - query_route_control_context_l3out.current.0.rtctrlCtxP.children.1.rtctrlRsCtxPToSubjP.attributes.tDn == "uni/tn-ansible_tenant/subj-ansible_match_rule" + + - name: Remove route control context policy for l3out (check_mode) + aci_route_control_context: &aci_route_control_context_absent + <<: *aci_route_control_context_present + state: absent + check_mode: true + register: cm_remove_route_control_context + + - name: Remove route control context policy for l3out (normal_mode) + aci_route_control_context: + <<: *aci_route_control_context_absent + register: nm_remove_remove_route_control_context + + - name: Remove route control profile for l3out again - testing previous Removal + aci_route_control_context: + <<: *aci_route_control_context_absent + register: nm_remove_route_control_context_idempotency + + - name: Asserts deletion tasks + assert: + that: + - cm_remove_route_control_context is changed + - cm_remove_route_control_context.proposed == {} + - nm_remove_remove_route_control_context is changed + - nm_remove_remove_route_control_context.previous != [] + - nm_remove_remove_route_control_context.method == "DELETE" + - nm_remove_route_control_context_idempotency is not changed + - nm_remove_route_control_context_idempotency.previous == [] + + - name: Remove the ansible_tenant - cleanup before ending tests + aci_tenant: + <<: *aci_tenant_present + state: absent diff --git a/ansible_collections/cisco/aci/tests/integration/targets/aci_route_control_profile/aliases b/ansible_collections/cisco/aci/tests/integration/targets/aci_route_control_profile/aliases new file mode 100644 index 000000000..209b793f9 --- /dev/null +++ b/ansible_collections/cisco/aci/tests/integration/targets/aci_route_control_profile/aliases @@ -0,0 +1,2 @@ +# No ACI simulator yet, so not enabled +# unsupported diff --git a/ansible_collections/cisco/aci/tests/integration/targets/aci_route_control_profile/tasks/main.yml b/ansible_collections/cisco/aci/tests/integration/targets/aci_route_control_profile/tasks/main.yml new file mode 100644 index 000000000..db0022100 --- /dev/null +++ b/ansible_collections/cisco/aci/tests/integration/targets/aci_route_control_profile/tasks/main.yml @@ -0,0 +1,145 @@ +# Test code for the ACI modules +# Copyright: (c) 2023, Gaspard Micol (@gmicol) <gmicol@cisco.com> +# Copyright: (c) 2023, Tim Cragg (@timcragg) <tcragg@cisco.com> + +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +- name: Test that we have an ACI APIC host, ACI username and ACI password + fail: + msg: 'Please define the following variables: aci_hostname, aci_username and aci_password.' + when: aci_hostname is not defined or aci_username is not defined or aci_password is not defined + +- name: Set vars + set_fact: + aci_info: &aci_info + host: "{{ aci_hostname }}" + username: "{{ aci_username }}" + password: "{{ aci_password }}" + validate_certs: '{{ aci_validate_certs | default(false) }}' + use_ssl: '{{ aci_use_ssl | default(true) }}' + use_proxy: '{{ aci_use_proxy | default(true) }}' + output_level: debug + +# CLEAN ENVIRONMENT +- name: Remove the ansible_tenant + aci_tenant: &aci_tenant_absent + <<: *aci_info + tenant: ansible_tenant + state: absent + +- name: Verify Cloud and Non-Cloud Sites in use. + include_tasks: ../../../../../../integration/targets/aci_cloud_provider/tasks/main.yml + +- name: Execute tasks only for non-cloud sites + when: query_cloud.current == [] # This condition will execute only non-cloud sites + block: # block specifies execution of tasks within, based on conditions + - name: Add a new tenant + aci_tenant: &aci_tenant_present + <<: *aci_info + tenant: ansible_tenant + description: Ansible tenant + state: present + + - name: Add a new L3Out + aci_l3out: + <<: *aci_info + tenant: ansible_tenant + l3out: ansible_l3out + description: L3Out for ansible_tenant tenant + domain: ansible_dom + vrf: ansible_vrf + state: present + + - name: Add route control profile for l3out (check_mode) + aci_route_control_profile: &aci_route_control_profile_present + <<: *aci_info + tenant: ansible_tenant + l3out: ansible_l3out + route_control_profile: ansible_rtctrl_profile_l3out + description: Route Control Profile for ansible_l3out L3Out + auto_continue: no + policy_type: combinable + state: present + check_mode: true + register: cm_add_route_control_profile + + - name: Add route control profile for l3out (normal_mode) + aci_route_control_profile: + <<: *aci_route_control_profile_present + register: nm_add_route_control_profile + + - name: Add route control profile for l3out again - testing idempotency + aci_route_control_profile: + <<: *aci_route_control_profile_present + register: nm_add_route_control_profile_idempotency + + - name: Add route control profile for tenant (normal_mode) + aci_route_control_profile: + <<: *aci_info + tenant: ansible_tenant + route_control_profile: ansible_rtctrl_profile_tenant + description: Route Control Profile for ansible_tenant tenant + state: present + register: nm_add_route_control_profile_2 + + - name: Asserts for route control profiles creation tasks + assert: + that: + - cm_add_route_control_profile is changed + - cm_add_route_control_profile.previous == [] + - cm_add_route_control_profile.current == [] + - nm_add_route_control_profile is changed + - nm_add_route_control_profile_idempotency is not changed + - nm_add_route_control_profile_2 is changed + - nm_add_route_control_profile_2.previous == [] + + - name: Query all route control profiles + aci_route_control_profile: + <<: *aci_info + state: query + register: query_all_route_control_profile + + - name: Query ansible_rtctrl_profile_l3out + aci_route_control_profile: + <<: *aci_route_control_profile_present + state: query + register: query_ansible_rtctrl_profile_l3out + + - name: Asserts query tasks + assert: + that: + - query_all_route_control_profile is not changed + - query_all_route_control_profile.current|length >= 2 + + - name: Remove route control profile for l3out (check_mode) + aci_route_control_profile: &route_control_profile_absent + <<: *aci_route_control_profile_present + state: absent + check_mode: true + register: cm_remove_route_control_profile + + - name: Remove route control profile for l3out (normal_mode) + aci_route_control_profile: + <<: *route_control_profile_absent + register: nm_remove_route_control_profile + + - name: Remove route control profile for l3out again - testing previous Removal + aci_route_control_profile: + <<: *route_control_profile_absent + register: nm_remove_route_control_profile_idempotency + + - name: Asserts deletion tasks + assert: + that: + - cm_remove_route_control_profile is changed + - cm_remove_route_control_profile.proposed == {} + - nm_remove_route_control_profile is changed + - nm_remove_route_control_profile.previous != [] + - nm_remove_route_control_profile.method == "DELETE" + - nm_remove_route_control_profile_idempotency is not changed + - nm_remove_route_control_profile_idempotency.previous == [] + + - name: Remove the ansible_tenant - cleanup before ending tests + aci_tenant: + <<: *aci_tenant_present + state: absent diff --git a/ansible_collections/cisco/aci/tests/integration/targets/aci_snmp_user/tasks/main.yml b/ansible_collections/cisco/aci/tests/integration/targets/aci_snmp_user/tasks/main.yml index 1b2608189..aabfd4c67 100644 --- a/ansible_collections/cisco/aci/tests/integration/targets/aci_snmp_user/tasks/main.yml +++ b/ansible_collections/cisco/aci/tests/integration/targets/aci_snmp_user/tasks/main.yml @@ -1,16 +1,16 @@ # Test code for the ACI modules # Copyright: (c) 2021, Tim Cragg (@timcragg) +# Copyright: (c) 2023, Akini Ross (@akinross) # GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) - name: Test that we have an ACI APIC host, ACI username and ACI password - fail: + ansible.builtin.fail: msg: 'Please define the following variables: aci_hostname, aci_username and aci_password.' when: aci_hostname is not defined or aci_username is not defined or aci_password is not defined -# GET Credentials from the inventory - name: Set vars - set_fact: + ansible.builtin.set_fact: aci_info: &aci_info host: "{{ aci_hostname }}" username: "{{ aci_username }}" @@ -18,26 +18,25 @@ validate_certs: '{{ aci_validate_certs | default(false) }}' use_ssl: '{{ aci_use_ssl | default(true) }}' use_proxy: '{{ aci_use_proxy | default(true) }}' - output_level: debug + output_level: '{{ aci_output_level | default("info") }}' # CLEAN ENVIRONMENT - name: Remove ansible_snmp_policy if it already exists - aci_snmp_policy: + cisco.aci.aci_snmp_policy: <<: *aci_info name: ansible_snmp_policy state: absent -# ADD snmp policy -- name: Add snmp policy - aci_snmp_policy: +# CREATE SNMP USER +- name: Create snmp policy + cisco.aci.aci_snmp_policy: <<: *aci_info name: ansible_snmp_policy admin_state: enabled state: present -# ADD snmp user -- name: Add snmp user - aci_snmp_user: +- name: Create a snmp user (checkmode) + cisco.aci.aci_snmp_user: &snmp_user <<: *aci_info policy: ansible_snmp_policy name: ansible_snmp_user @@ -46,66 +45,223 @@ privacy_type: aes-128 privacy_key: "priv-test-key" state: present - register: add_snmp_user + register: cm_create_snmp_user + check_mode: true -- name: Verify that ansible_snmp_community has been created with correct attributes - assert: +- name: Create a snmp user + cisco.aci.aci_snmp_user: + <<: *snmp_user + register: nm_create_snmp_user + +- name: Create a snmp user again without secrets + cisco.aci.aci_snmp_user: + <<: *snmp_user + auth_key: "{{ fake_var | default(omit) }}" + privacy_key: "{{ fake_var | default(omit) }}" + register: nm_create_snmp_user_again + +- name: Verify create of ansible_snmp_user + ansible.builtin.assert: + that: + - cm_create_snmp_user is changed + - cm_create_snmp_user.proposed.snmpUserP.attributes.dn == "uni/fabric/snmppol-ansible_snmp_policy/user-ansible_snmp_user" + - cm_create_snmp_user.proposed.snmpUserP.attributes.name == "ansible_snmp_user" + - cm_create_snmp_user.proposed.snmpUserP.attributes.authType == "hmac-sha1-96" + - cm_create_snmp_user.proposed.snmpUserP.attributes.privType == "aes-128" + - cm_create_snmp_user.proposed.snmpUserP.attributes.annotation == 'orchestrator:ansible' + - cm_create_snmp_user.previous == [] + - nm_create_snmp_user is changed + - nm_create_snmp_user.current.0.snmpUserP.attributes.dn == "uni/fabric/snmppol-ansible_snmp_policy/user-ansible_snmp_user" + - nm_create_snmp_user.current.0.snmpUserP.attributes.name == "ansible_snmp_user" + - nm_create_snmp_user.current.0.snmpUserP.attributes.authType == "hmac-sha1-96" + - nm_create_snmp_user.current.0.snmpUserP.attributes.privType == "aes-128" + - nm_create_snmp_user.current.0.snmpUserP.attributes.annotation == 'orchestrator:ansible' + - nm_create_snmp_user.current.0.snmpUserP.attributes.descr == "" + - nm_create_snmp_user.previous == [] + - nm_create_snmp_user_again is not changed + - nm_create_snmp_user_again.previous.0.snmpUserP.attributes.dn == "uni/fabric/snmppol-ansible_snmp_policy/user-ansible_snmp_user" + - nm_create_snmp_user_again.previous.0.snmpUserP.attributes.name == "ansible_snmp_user" + - nm_create_snmp_user_again.previous.0.snmpUserP.attributes.authType == "hmac-sha1-96" + - nm_create_snmp_user_again.previous.0.snmpUserP.attributes.privType == "aes-128" + - nm_create_snmp_user_again.previous.0.snmpUserP.attributes.annotation == 'orchestrator:ansible' + - nm_create_snmp_user_again.previous.0.snmpUserP.attributes.descr == "" + - nm_create_snmp_user_again.current.0.snmpUserP.attributes.dn == "uni/fabric/snmppol-ansible_snmp_policy/user-ansible_snmp_user" + - nm_create_snmp_user_again.current.0.snmpUserP.attributes.name == "ansible_snmp_user" + - nm_create_snmp_user_again.current.0.snmpUserP.attributes.authType == "hmac-sha1-96" + - nm_create_snmp_user_again.current.0.snmpUserP.attributes.privType == "aes-128" + - nm_create_snmp_user_again.current.0.snmpUserP.attributes.annotation == 'orchestrator:ansible' + - nm_create_snmp_user_again.current.0.snmpUserP.attributes.descr == "" + +# CHANGE SNMP USER +- name: Change a snmp user (checkmode) + cisco.aci.aci_snmp_user: &snmp_user_changed + <<: *snmp_user + descr: description_change + register: cm_change_snmp_user + check_mode: true + +- name: Change a snmp user + cisco.aci.aci_snmp_user: + <<: *snmp_user_changed + register: nm_change_snmp_user + +- name: Change a snmp user again without secrets + cisco.aci.aci_snmp_user: + <<: *snmp_user_changed + auth_key: "{{ fake_var | default(omit) }}" + privacy_key: "{{ fake_var | default(omit) }}" + register: nm_change_snmp_user_again + +- name: Verify change of ansible_snmp_user + ansible.builtin.assert: that: - - add_snmp_user.current.0.snmpUserP.attributes.dn == "uni/fabric/snmppol-ansible_snmp_policy/user-ansible_snmp_user" - - add_snmp_user.current.0.snmpUserP.attributes.name == "ansible_snmp_user" - - add_snmp_user.current.0.snmpUserP.attributes.authType == "hmac-sha1-96" - - add_snmp_user.current.0.snmpUserP.attributes.privType == "aes-128" - - add_snmp_user.current.0.snmpUserP.attributes.annotation == 'orchestrator:ansible' - -# QUERY snmp user -- name: Query snmp user - aci_snmp_user: + - cm_change_snmp_user is changed + - cm_change_snmp_user.previous.0.snmpUserP.attributes.dn == "uni/fabric/snmppol-ansible_snmp_policy/user-ansible_snmp_user" + - cm_change_snmp_user.previous.0.snmpUserP.attributes.name == "ansible_snmp_user" + - cm_change_snmp_user.previous.0.snmpUserP.attributes.authType == "hmac-sha1-96" + - cm_change_snmp_user.previous.0.snmpUserP.attributes.privType == "aes-128" + - cm_change_snmp_user.previous.0.snmpUserP.attributes.annotation == 'orchestrator:ansible' + - cm_change_snmp_user.previous.0.snmpUserP.attributes.descr == "" + - cm_change_snmp_user.proposed.snmpUserP.attributes.dn == "uni/fabric/snmppol-ansible_snmp_policy/user-ansible_snmp_user" + - cm_change_snmp_user.proposed.snmpUserP.attributes.name == "ansible_snmp_user" + - cm_change_snmp_user.proposed.snmpUserP.attributes.authType == "hmac-sha1-96" + - cm_change_snmp_user.proposed.snmpUserP.attributes.privType == "aes-128" + - cm_change_snmp_user.proposed.snmpUserP.attributes.annotation == 'orchestrator:ansible' + - cm_change_snmp_user.proposed.snmpUserP.attributes.descr == "description_change" + - nm_change_snmp_user is changed + - nm_change_snmp_user.previous.0.snmpUserP.attributes.dn == "uni/fabric/snmppol-ansible_snmp_policy/user-ansible_snmp_user" + - nm_change_snmp_user.previous.0.snmpUserP.attributes.name == "ansible_snmp_user" + - nm_change_snmp_user.previous.0.snmpUserP.attributes.authType == "hmac-sha1-96" + - nm_change_snmp_user.previous.0.snmpUserP.attributes.privType == "aes-128" + - nm_change_snmp_user.previous.0.snmpUserP.attributes.annotation == 'orchestrator:ansible' + - nm_change_snmp_user.previous.0.snmpUserP.attributes.descr == "" + - nm_change_snmp_user.current.0.snmpUserP.attributes.dn == "uni/fabric/snmppol-ansible_snmp_policy/user-ansible_snmp_user" + - nm_change_snmp_user.current.0.snmpUserP.attributes.name == "ansible_snmp_user" + - nm_change_snmp_user.current.0.snmpUserP.attributes.authType == "hmac-sha1-96" + - nm_change_snmp_user.current.0.snmpUserP.attributes.privType == "aes-128" + - nm_change_snmp_user.current.0.snmpUserP.attributes.annotation == 'orchestrator:ansible' + - nm_change_snmp_user.current.0.snmpUserP.attributes.descr == "description_change" + - nm_change_snmp_user_again is not changed + - nm_change_snmp_user_again.previous.0.snmpUserP.attributes.dn == "uni/fabric/snmppol-ansible_snmp_policy/user-ansible_snmp_user" + - nm_change_snmp_user_again.previous.0.snmpUserP.attributes.name == "ansible_snmp_user" + - nm_change_snmp_user_again.previous.0.snmpUserP.attributes.authType == "hmac-sha1-96" + - nm_change_snmp_user_again.previous.0.snmpUserP.attributes.privType == "aes-128" + - nm_change_snmp_user_again.previous.0.snmpUserP.attributes.annotation == 'orchestrator:ansible' + - nm_change_snmp_user_again.previous.0.snmpUserP.attributes.descr == "description_change" + - nm_change_snmp_user_again.current.0.snmpUserP.attributes.dn == "uni/fabric/snmppol-ansible_snmp_policy/user-ansible_snmp_user" + - nm_change_snmp_user_again.current.0.snmpUserP.attributes.name == "ansible_snmp_user" + - nm_change_snmp_user_again.current.0.snmpUserP.attributes.authType == "hmac-sha1-96" + - nm_change_snmp_user_again.current.0.snmpUserP.attributes.privType == "aes-128" + - nm_change_snmp_user_again.current.0.snmpUserP.attributes.annotation == 'orchestrator:ansible' + - nm_change_snmp_user_again.current.0.snmpUserP.attributes.descr == "description_change" + +# QUERY SNMP USER +- name: Create another snmp user with auth_type hmac-sha2-224 + cisco.aci.aci_snmp_user: + <<: *snmp_user + name: ansible_snmp_user_2 + auth_type: hmac-sha2-224 + register: nm_create_snmp_user_2 + +- name: Create another snmp user with auth_type hmac-sha2-256 + cisco.aci.aci_snmp_user: + <<: *snmp_user + name: ansible_snmp_user_3 + auth_type: hmac-sha2-256 + register: nm_create_snmp_user_3 + +- name: Create another snmp user with auth_type hmac-sha2-384 + cisco.aci.aci_snmp_user: + <<: *snmp_user + name: ansible_snmp_user_4 + auth_type: hmac-sha2-384 + register: nm_create_snmp_user_4 + +- name: Create another snmp user with auth_type hmac-sha2-512 + cisco.aci.aci_snmp_user: + <<: *snmp_user + name: ansible_snmp_user_5 + auth_type: hmac-sha2-512 + register: nm_create_snmp_user_5 + +- name: Verify change of ansible_snmp_user + ansible.builtin.assert: + that: + - nm_create_snmp_user_2 is changed + - nm_create_snmp_user_2.current.0.snmpUserP.attributes.authType == "hmac-sha2-224" + - nm_create_snmp_user_3 is changed + - nm_create_snmp_user_3.current.0.snmpUserP.attributes.authType == "hmac-sha2-256" + - nm_create_snmp_user_4 is changed + - nm_create_snmp_user_4.current.0.snmpUserP.attributes.authType == "hmac-sha2-384" + - nm_create_snmp_user_5 is changed + - nm_create_snmp_user_5.current.0.snmpUserP.attributes.authType == "hmac-sha2-512" + +- name: Query ansible_snmp_user + cisco.aci.aci_snmp_user: <<: *aci_info policy: ansible_snmp_policy name: ansible_snmp_user state: query register: query_snmp_user -- name: Verify the attributes under query_snmp_client_group - assert: +- name: Verify query of ansible_snmp_user + ansible.builtin.assert: that: - query_snmp_user is not changed + - query_snmp_user.current | length == 1 - query_snmp_user.current.0.snmpUserP.attributes.dn == "uni/fabric/snmppol-ansible_snmp_policy/user-ansible_snmp_user" - query_snmp_user.current.0.snmpUserP.attributes.name == "ansible_snmp_user" - query_snmp_user.current.0.snmpUserP.attributes.authType == "hmac-sha1-96" - query_snmp_user.current.0.snmpUserP.attributes.privType == "aes-128" -- name: Query all snmp communities - aci_snmp_user: +- name: Query all snmp users + cisco.aci.aci_snmp_user: <<: *aci_info state: query register: query_snmp_user_all -- name: Verify query_snmp_user_all - assert: +- name: Verify query of all snmp users + ansible.builtin.assert: that: - query_snmp_user_all is not changed + - query_snmp_user_all.current | length >= 5 -# DELETE snmp user -- name: Remove the snmp user - aci_snmp_user: +# REMOVE SNMP USER +- name: Remove the snmp user (checkmode) + cisco.aci.aci_snmp_user: &snmp_user_remove <<: *aci_info policy: ansible_snmp_policy name: ansible_snmp_user state: absent - register: remove_snmp_user + register: cm_remove_snmp_user + check_mode: true + +- name: Remove the snmp user + cisco.aci.aci_snmp_user: + <<: *snmp_user_remove + register: nm_remove_snmp_user + +- name: Remove the snmp user again + cisco.aci.aci_snmp_user: + <<: *snmp_user_remove + register: nm_remove_snmp_user_again - name: Verify remove_snmp_user - assert: + ansible.builtin.assert: that: - - remove_snmp_user is changed - - remove_snmp_user.current == [] - - remove_snmp_user.previous.0.snmpUserP.attributes.dn == "uni/fabric/snmppol-ansible_snmp_policy/user-ansible_snmp_user" - - remove_snmp_user.previous.0.snmpUserP.attributes.name == "ansible_snmp_user" + - cm_remove_snmp_user is changed + - cm_remove_snmp_user.previous.0.snmpUserP.attributes.name == "ansible_snmp_user" + - cm_remove_snmp_user.proposed == {} + - nm_remove_snmp_user is changed + - nm_remove_snmp_user.current == [] + - nm_remove_snmp_user.previous.0.snmpUserP.attributes.name == "ansible_snmp_user" + - nm_remove_snmp_user_again is not changed + - nm_remove_snmp_user_again.current == [] + - nm_remove_snmp_user_again.previous == [] -# DELETE snmp policy +# CLEAN ENVIRONMENT - name: Remove the snmp policy - aci_snmp_policy: + cisco.aci.aci_snmp_policy: <<: *aci_info name: ansible_snmp_policy state: absent diff --git a/ansible_collections/cisco/aci/tests/integration/targets/aci_static_binding_to_epg/tasks/main.yml b/ansible_collections/cisco/aci/tests/integration/targets/aci_static_binding_to_epg/tasks/main.yml index 180848e28..0e9096b85 100644 --- a/ansible_collections/cisco/aci/tests/integration/targets/aci_static_binding_to_epg/tasks/main.yml +++ b/ansible_collections/cisco/aci/tests/integration/targets/aci_static_binding_to_epg/tasks/main.yml @@ -306,7 +306,7 @@ - 104 - 105 ignore_errors: true - register: fex_vpc_three extpaths + register: fex_vpc_three_extpaths - name: Bind static-binding to epg - fex_port_channel with multiple extpaths (check_mode) cisco.aci.aci_static_binding_to_epg: diff --git a/ansible_collections/cisco/aci/tests/integration/targets/aci_syslog_remote_dest/tasks/main.yml b/ansible_collections/cisco/aci/tests/integration/targets/aci_syslog_remote_dest/tasks/main.yml index 0840aec20..82839ddf7 100644 --- a/ansible_collections/cisco/aci/tests/integration/targets/aci_syslog_remote_dest/tasks/main.yml +++ b/ansible_collections/cisco/aci/tests/integration/targets/aci_syslog_remote_dest/tasks/main.yml @@ -46,7 +46,6 @@ facility: local1 syslog_port: 5678 description: ansible syslog remote destination - mgmt_epg: oob-default format: aci admin_state: enabled severity: warnings @@ -66,11 +65,7 @@ - add_syslog_remote_dest.current.0.syslogRemoteDest.attributes.adminState == "enabled" - add_syslog_remote_dest.current.0.syslogRemoteDest.attributes.severity == "warnings" - add_syslog_remote_dest.current.0.syslogRemoteDest.attributes.annotation == 'orchestrator:ansible' - -- name: Verify that ansible_syslog_remote_dest children have correct attributes - assert: - that: - - add_syslog_remote_dest.current.0.syslogRemoteDest.children.0.fileRsARemoteHostToEpg.attributes.tDn == "uni/tn-mgmt/mgmtp-default/oob-default" + - '"children" not in add_syslog_remote_dest.current.0.syslogRemoteDest' # ADD syslog remote dest again to check idempotency - name: Add syslog remote dest @@ -82,7 +77,6 @@ facility: local1 syslog_port: 5678 description: ansible syslog remote destination - mgmt_epg: oob-default format: aci admin_state: enabled severity: warnings @@ -124,6 +118,11 @@ - update_syslog_remote_dest.current.0.syslogRemoteDest.attributes.adminState == "disabled" - update_syslog_remote_dest.current.0.syslogRemoteDest.attributes.severity == "information" +- name: Verify that ansible_syslog_remote_dest children have correct attributes + assert: + that: + - update_syslog_remote_dest.current.0.syslogRemoteDest.children.0.fileRsARemoteHostToEpg.attributes.tDn == "uni/tn-mgmt/mgmtp-default/oob-default" + # QUERY syslog source - name: Query the syslog source aci_syslog_remote_dest: diff --git a/ansible_collections/cisco/aci/tests/integration/targets/aci_syslog_source/tasks/main.yml b/ansible_collections/cisco/aci/tests/integration/targets/aci_syslog_source/tasks/main.yml index c237d17fe..a7180649f 100644 --- a/ansible_collections/cisco/aci/tests/integration/targets/aci_syslog_source/tasks/main.yml +++ b/ansible_collections/cisco/aci/tests/integration/targets/aci_syslog_source/tasks/main.yml @@ -40,10 +40,6 @@ aci_syslog_source: <<: *aci_info name: ansible_syslog_src - include: - - audit - - events - - faults min_severity: errors destination_group: ansible_syslog_group state: present @@ -54,7 +50,7 @@ that: - add_syslog_src.current.0.syslogSrc.attributes.dn == "uni/fabric/moncommon/slsrc-ansible_syslog_src" - add_syslog_src.current.0.syslogSrc.attributes.name == "ansible_syslog_src" - - add_syslog_src.current.0.syslogSrc.attributes.incl == "audit,events,faults" + - add_syslog_src.current.0.syslogSrc.attributes.incl == "faults" - add_syslog_src.current.0.syslogSrc.attributes.minSev == "errors" - add_syslog_src.current.0.syslogSrc.attributes.annotation == 'orchestrator:ansible' @@ -68,10 +64,6 @@ aci_syslog_source: <<: *aci_info name: ansible_syslog_src - include: - - audit - - events - - faults min_severity: errors destination_group: ansible_syslog_group state: present @@ -88,7 +80,9 @@ <<: *aci_info name: ansible_syslog_src include: - - faults + - audit + - events + - faults min_severity: information destination_group: ansible_syslog_group state: present @@ -99,7 +93,7 @@ that: - update_syslog_src.current.0.syslogSrc.attributes.dn == "uni/fabric/moncommon/slsrc-ansible_syslog_src" - update_syslog_src.current.0.syslogSrc.attributes.name == "ansible_syslog_src" - - update_syslog_src.current.0.syslogSrc.attributes.incl == "faults" + - update_syslog_src.current.0.syslogSrc.attributes.incl == "audit,events,faults" - update_syslog_src.current.0.syslogSrc.attributes.minSev == "information" # QUERY syslog source @@ -116,7 +110,7 @@ - query_syslog_src is not changed - query_syslog_src.current.0.syslogSrc.attributes.dn == "uni/fabric/moncommon/slsrc-ansible_syslog_src" - query_syslog_src.current.0.syslogSrc.attributes.name == "ansible_syslog_src" - - query_syslog_src.current.0.syslogSrc.attributes.incl == "faults" + - query_syslog_src.current.0.syslogSrc.attributes.incl == "audit,events,faults" - query_syslog_src.current.0.syslogSrc.attributes.minSev == "information" - name: Query all syslog sources diff --git a/ansible_collections/cisco/aci/tests/integration/targets/aci_system_banner/aliases b/ansible_collections/cisco/aci/tests/integration/targets/aci_system_banner/aliases new file mode 100644 index 000000000..209b793f9 --- /dev/null +++ b/ansible_collections/cisco/aci/tests/integration/targets/aci_system_banner/aliases @@ -0,0 +1,2 @@ +# No ACI simulator yet, so not enabled +# unsupported diff --git a/ansible_collections/cisco/aci/tests/integration/targets/aci_system_banner/tasks/main.yml b/ansible_collections/cisco/aci/tests/integration/targets/aci_system_banner/tasks/main.yml new file mode 100644 index 000000000..5b9cb96f0 --- /dev/null +++ b/ansible_collections/cisco/aci/tests/integration/targets/aci_system_banner/tasks/main.yml @@ -0,0 +1,221 @@ +# Test code for the ACI modules +# Copyright: (c) 2023, Tim Cragg (@timcragg) +# Copyright: (c) 2023, Akini Ross (@akinross) + +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +- name: Test that we have an ACI APIC host, ACI username and ACI password + fail: + msg: 'Please define the following variables: aci_hostname, aci_username and aci_password.' + when: aci_hostname is not defined or aci_username is not defined or aci_password is not defined + +- name: Set vars + set_fact: + aci_info: &aci_info + host: "{{ aci_hostname }}" + username: "{{ aci_username }}" + password: "{{ aci_password }}" + validate_certs: '{{ aci_validate_certs | default(false) }}' + use_ssl: '{{ aci_use_ssl | default(true) }}' + use_proxy: '{{ aci_use_proxy | default(true) }}' + output_level: '{{ aci_output_level | default("info") }}' + +# CLEANUP ENVIRONMENT +- name: Clear the current alias and banner configuration + cisco.aci.aci_system_banner: &aci_banner_clear + <<: *aci_info + annotation: "" + gui_alias: "" + controller_banner: "" + application_banner: "" + severity: info + switch_banner: "" + gui_banner: "" + +# UPDATE ALIAS AND BANNER CONFIGURATION +- name: Update alias and banner configuration (check mode) + cisco.aci.aci_system_banner: &aci_banner + <<: *aci_info + gui_alias: "Ansible Test GUI Alias" + controller_banner: "Ansible Test Controller Banner" + application_banner: "Ansible Test Application Banner" + severity: critical + switch_banner: "Ansible Test Switch Banner" + gui_banner: "Ansible Test GUI Banner" + check_mode: true + register: cm_update_banner + +- name: Update alias and banner configuration + cisco.aci.aci_system_banner: + <<: *aci_banner + register: nm_update_banner + +- name: Update alias and banner configuration again + cisco.aci.aci_system_banner: + <<: *aci_banner + register: nm_update_banner_again + +- name: Verify alias and banner configuration + ansible.builtin.assert: + that: + - cm_update_banner is changed + - cm_update_banner.proposed.aaaPreLoginBanner.attributes.annotation == "orchestrator:ansible" + - cm_update_banner.proposed.aaaPreLoginBanner.attributes.bannerMessage == "Ansible Test Application Banner" + - cm_update_banner.proposed.aaaPreLoginBanner.attributes.bannerMessageSeverity == "critical" + - cm_update_banner.proposed.aaaPreLoginBanner.attributes.dn == "uni/userext/preloginbanner" + - cm_update_banner.proposed.aaaPreLoginBanner.attributes.guiMessage == "Ansible Test GUI Banner" + - cm_update_banner.proposed.aaaPreLoginBanner.attributes.isGuiMessageText == "yes" + - cm_update_banner.proposed.aaaPreLoginBanner.attributes.guiTextMessage == "Ansible Test GUI Alias" + - cm_update_banner.proposed.aaaPreLoginBanner.attributes.message == "Ansible Test Controller Banner" + - cm_update_banner.proposed.aaaPreLoginBanner.attributes.showBannerMessage == "yes" + - cm_update_banner.proposed.aaaPreLoginBanner.attributes.switchMessage == "Ansible Test Switch Banner" + - cm_update_banner.current.0.aaaPreLoginBanner.attributes.annotation == "" + - cm_update_banner.current.0.aaaPreLoginBanner.attributes.bannerMessage == "" + - cm_update_banner.current.0.aaaPreLoginBanner.attributes.bannerMessageSeverity == "info" + - cm_update_banner.current.0.aaaPreLoginBanner.attributes.dn == "uni/userext/preloginbanner" + - cm_update_banner.current.0.aaaPreLoginBanner.attributes.guiMessage == "" + - cm_update_banner.current.0.aaaPreLoginBanner.attributes.isGuiMessageText == "yes" + - cm_update_banner.current.0.aaaPreLoginBanner.attributes.guiTextMessage == "" + - cm_update_banner.current.0.aaaPreLoginBanner.attributes.message == "" + - cm_update_banner.current.0.aaaPreLoginBanner.attributes.showBannerMessage == "no" + - cm_update_banner.current.0.aaaPreLoginBanner.attributes.switchMessage == "" + - nm_update_banner is changed + - nm_update_banner.current.0.aaaPreLoginBanner.attributes.annotation == "orchestrator:ansible" + - nm_update_banner.current.0.aaaPreLoginBanner.attributes.bannerMessage == "Ansible Test Application Banner" + - nm_update_banner.current.0.aaaPreLoginBanner.attributes.bannerMessageSeverity == "critical" + - nm_update_banner.current.0.aaaPreLoginBanner.attributes.dn == "uni/userext/preloginbanner" + - nm_update_banner.current.0.aaaPreLoginBanner.attributes.guiMessage == "Ansible Test GUI Banner" + - nm_update_banner.current.0.aaaPreLoginBanner.attributes.isGuiMessageText == "yes" + - nm_update_banner.current.0.aaaPreLoginBanner.attributes.guiTextMessage == "Ansible Test GUI Alias" + - nm_update_banner.current.0.aaaPreLoginBanner.attributes.message == "Ansible Test Controller Banner" + - nm_update_banner.current.0.aaaPreLoginBanner.attributes.showBannerMessage == "yes" + - nm_update_banner.current.0.aaaPreLoginBanner.attributes.switchMessage == "Ansible Test Switch Banner" + - nm_update_banner.previous.0.aaaPreLoginBanner.attributes.annotation == "" + - nm_update_banner.previous.0.aaaPreLoginBanner.attributes.bannerMessage == "" + - nm_update_banner.previous.0.aaaPreLoginBanner.attributes.bannerMessageSeverity == "info" + - nm_update_banner.previous.0.aaaPreLoginBanner.attributes.dn == "uni/userext/preloginbanner" + - nm_update_banner.previous.0.aaaPreLoginBanner.attributes.guiMessage == "" + - nm_update_banner.previous.0.aaaPreLoginBanner.attributes.isGuiMessageText == "yes" + - nm_update_banner.previous.0.aaaPreLoginBanner.attributes.guiTextMessage == "" + - nm_update_banner.previous.0.aaaPreLoginBanner.attributes.message == "" + - nm_update_banner.previous.0.aaaPreLoginBanner.attributes.showBannerMessage == "no" + - nm_update_banner.previous.0.aaaPreLoginBanner.attributes.switchMessage == "" + - nm_update_banner_again is not changed + - nm_update_banner_again.current.0.aaaPreLoginBanner.attributes.annotation == "orchestrator:ansible" + - nm_update_banner_again.current.0.aaaPreLoginBanner.attributes.bannerMessage == "Ansible Test Application Banner" + - nm_update_banner_again.current.0.aaaPreLoginBanner.attributes.bannerMessageSeverity == "critical" + - nm_update_banner_again.current.0.aaaPreLoginBanner.attributes.dn == "uni/userext/preloginbanner" + - nm_update_banner_again.current.0.aaaPreLoginBanner.attributes.guiMessage == "Ansible Test GUI Banner" + - nm_update_banner_again.current.0.aaaPreLoginBanner.attributes.isGuiMessageText == "yes" + - nm_update_banner_again.current.0.aaaPreLoginBanner.attributes.guiTextMessage == "Ansible Test GUI Alias" + - nm_update_banner_again.current.0.aaaPreLoginBanner.attributes.message == "Ansible Test Controller Banner" + - nm_update_banner_again.current.0.aaaPreLoginBanner.attributes.showBannerMessage == "yes" + - nm_update_banner_again.current.0.aaaPreLoginBanner.attributes.switchMessage == "Ansible Test Switch Banner" + - nm_update_banner_again.previous.0.aaaPreLoginBanner.attributes.annotation == "orchestrator:ansible" + - nm_update_banner_again.previous.0.aaaPreLoginBanner.attributes.bannerMessage == "Ansible Test Application Banner" + - nm_update_banner_again.previous.0.aaaPreLoginBanner.attributes.bannerMessageSeverity == "critical" + - nm_update_banner_again.previous.0.aaaPreLoginBanner.attributes.dn == "uni/userext/preloginbanner" + - nm_update_banner_again.previous.0.aaaPreLoginBanner.attributes.guiMessage == "Ansible Test GUI Banner" + - nm_update_banner_again.previous.0.aaaPreLoginBanner.attributes.isGuiMessageText == "yes" + - nm_update_banner_again.previous.0.aaaPreLoginBanner.attributes.guiTextMessage == "Ansible Test GUI Alias" + - nm_update_banner_again.previous.0.aaaPreLoginBanner.attributes.message == "Ansible Test Controller Banner" + - nm_update_banner_again.previous.0.aaaPreLoginBanner.attributes.showBannerMessage == "yes" + - nm_update_banner_again.previous.0.aaaPreLoginBanner.attributes.switchMessage == "Ansible Test Switch Banner" + +- name: Update alias and banner configuration with http url + cisco.aci.aci_system_banner: + <<: *aci_banner + gui_banner: http://www.cisco.com + register: nm_update_banner_with_url_http + +- name: Update alias and banner configuration with https url + cisco.aci.aci_system_banner: + <<: *aci_banner + gui_banner: https://www.cisco.com + register: nm_update_banner_with_url_https + +- name: Verify alias and banner configuration with url + ansible.builtin.assert: + that: + - nm_update_banner_with_url_http is changed + - nm_update_banner_with_url_http.current.0.aaaPreLoginBanner.attributes.annotation == "orchestrator:ansible" + - nm_update_banner_with_url_http.current.0.aaaPreLoginBanner.attributes.bannerMessage == "Ansible Test Application Banner" + - nm_update_banner_with_url_http.current.0.aaaPreLoginBanner.attributes.bannerMessageSeverity == "critical" + - nm_update_banner_with_url_http.current.0.aaaPreLoginBanner.attributes.dn == "uni/userext/preloginbanner" + - nm_update_banner_with_url_http.current.0.aaaPreLoginBanner.attributes.guiMessage == "http://www.cisco.com" + - nm_update_banner_with_url_http.current.0.aaaPreLoginBanner.attributes.isGuiMessageText == "no" + - nm_update_banner_with_url_http.current.0.aaaPreLoginBanner.attributes.guiTextMessage == "Ansible Test GUI Alias" + - nm_update_banner_with_url_http.current.0.aaaPreLoginBanner.attributes.message == "Ansible Test Controller Banner" + - nm_update_banner_with_url_http.current.0.aaaPreLoginBanner.attributes.showBannerMessage == "yes" + - nm_update_banner_with_url_http.current.0.aaaPreLoginBanner.attributes.switchMessage == "Ansible Test Switch Banner" + - nm_update_banner_with_url_http.previous.0.aaaPreLoginBanner.attributes.annotation == "orchestrator:ansible" + - nm_update_banner_with_url_http.previous.0.aaaPreLoginBanner.attributes.bannerMessage == "Ansible Test Application Banner" + - nm_update_banner_with_url_http.previous.0.aaaPreLoginBanner.attributes.bannerMessageSeverity == "critical" + - nm_update_banner_with_url_http.previous.0.aaaPreLoginBanner.attributes.dn == "uni/userext/preloginbanner" + - nm_update_banner_with_url_http.previous.0.aaaPreLoginBanner.attributes.guiMessage == "Ansible Test GUI Banner" + - nm_update_banner_with_url_http.previous.0.aaaPreLoginBanner.attributes.isGuiMessageText == "yes" + - nm_update_banner_with_url_http.previous.0.aaaPreLoginBanner.attributes.guiTextMessage == "Ansible Test GUI Alias" + - nm_update_banner_with_url_http.previous.0.aaaPreLoginBanner.attributes.message == "Ansible Test Controller Banner" + - nm_update_banner_with_url_http.previous.0.aaaPreLoginBanner.attributes.showBannerMessage == "yes" + - nm_update_banner_with_url_http.previous.0.aaaPreLoginBanner.attributes.switchMessage == "Ansible Test Switch Banner" + - nm_update_banner_with_url_https is changed + - nm_update_banner_with_url_https.current.0.aaaPreLoginBanner.attributes.annotation == "orchestrator:ansible" + - nm_update_banner_with_url_https.current.0.aaaPreLoginBanner.attributes.bannerMessage == "Ansible Test Application Banner" + - nm_update_banner_with_url_https.current.0.aaaPreLoginBanner.attributes.bannerMessageSeverity == "critical" + - nm_update_banner_with_url_https.current.0.aaaPreLoginBanner.attributes.dn == "uni/userext/preloginbanner" + - nm_update_banner_with_url_https.current.0.aaaPreLoginBanner.attributes.guiMessage == "https://www.cisco.com" + - nm_update_banner_with_url_https.current.0.aaaPreLoginBanner.attributes.isGuiMessageText == "no" + - nm_update_banner_with_url_https.current.0.aaaPreLoginBanner.attributes.guiTextMessage == "Ansible Test GUI Alias" + - nm_update_banner_with_url_https.current.0.aaaPreLoginBanner.attributes.message == "Ansible Test Controller Banner" + - nm_update_banner_with_url_https.current.0.aaaPreLoginBanner.attributes.showBannerMessage == "yes" + - nm_update_banner_with_url_https.current.0.aaaPreLoginBanner.attributes.switchMessage == "Ansible Test Switch Banner" + - nm_update_banner_with_url_https.previous.0.aaaPreLoginBanner.attributes.annotation == "orchestrator:ansible" + - nm_update_banner_with_url_https.previous.0.aaaPreLoginBanner.attributes.bannerMessage == "Ansible Test Application Banner" + - nm_update_banner_with_url_https.previous.0.aaaPreLoginBanner.attributes.bannerMessageSeverity == "critical" + - nm_update_banner_with_url_https.previous.0.aaaPreLoginBanner.attributes.dn == "uni/userext/preloginbanner" + - nm_update_banner_with_url_https.previous.0.aaaPreLoginBanner.attributes.guiMessage == "http://www.cisco.com" + - nm_update_banner_with_url_https.previous.0.aaaPreLoginBanner.attributes.isGuiMessageText == "no" + - nm_update_banner_with_url_https.previous.0.aaaPreLoginBanner.attributes.guiTextMessage == "Ansible Test GUI Alias" + - nm_update_banner_with_url_https.previous.0.aaaPreLoginBanner.attributes.message == "Ansible Test Controller Banner" + - nm_update_banner_with_url_https.previous.0.aaaPreLoginBanner.attributes.showBannerMessage == "yes" + - nm_update_banner_with_url_https.previous.0.aaaPreLoginBanner.attributes.switchMessage == "Ansible Test Switch Banner" + +# QUERY ALIAS AND BANNER CONFIGURATION +- name: Query alias and banner configuration + cisco.aci.aci_system_banner: + <<: *aci_banner + state: query + register: query + +- name: Verify alias and banner configuration query + ansible.builtin.assert: + that: + - query is not changed + - query.current.0.aaaPreLoginBanner.attributes.annotation == "orchestrator:ansible" + - query.current.0.aaaPreLoginBanner.attributes.bannerMessage == "Ansible Test Application Banner" + - query.current.0.aaaPreLoginBanner.attributes.bannerMessageSeverity == "critical" + - query.current.0.aaaPreLoginBanner.attributes.dn == "uni/userext/preloginbanner" + - query.current.0.aaaPreLoginBanner.attributes.guiMessage == "https://www.cisco.com" + - query.current.0.aaaPreLoginBanner.attributes.isGuiMessageText == "no" + - query.current.0.aaaPreLoginBanner.attributes.guiTextMessage == "Ansible Test GUI Alias" + - query.current.0.aaaPreLoginBanner.attributes.message == "Ansible Test Controller Banner" + - query.current.0.aaaPreLoginBanner.attributes.showBannerMessage == "yes" + +# ERROR ALIAS AND BANNER CONFIGURATION INPUT +- name: Update alias and banner configuration (error) + cisco.aci.aci_system_banner: + <<: *aci_info + severity: critical + ignore_errors: true + register: err_update_banner_severity + +- name: Verify alias and banner configuration error + ansible.builtin.assert: + that: + - err_update_banner_severity is failed + - err_update_banner_severity.msg == "missing parameter(s) required by 'severity'{{":"}} application_banner" + +# CLEANUP ENVIRONMENT +- name: Clear the current alias and banner configuration + cisco.aci.aci_system_banner: + <<: *aci_banner_clear diff --git a/ansible_collections/cisco/aci/tests/integration/targets/aci_system_endpoint_controls/aliases b/ansible_collections/cisco/aci/tests/integration/targets/aci_system_endpoint_controls/aliases new file mode 100644 index 000000000..209b793f9 --- /dev/null +++ b/ansible_collections/cisco/aci/tests/integration/targets/aci_system_endpoint_controls/aliases @@ -0,0 +1,2 @@ +# No ACI simulator yet, so not enabled +# unsupported diff --git a/ansible_collections/cisco/aci/tests/integration/targets/aci_system_endpoint_controls/tasks/main.yml b/ansible_collections/cisco/aci/tests/integration/targets/aci_system_endpoint_controls/tasks/main.yml new file mode 100644 index 000000000..fa98657f6 --- /dev/null +++ b/ansible_collections/cisco/aci/tests/integration/targets/aci_system_endpoint_controls/tasks/main.yml @@ -0,0 +1,307 @@ +# Test code for the ACI modules +# Copyright: (c) 2023, Tim Cragg (@timcragg) + +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +- name: Test that we have an ACI APIC host, ACI username and ACI password + fail: + msg: 'Please define the following variables: aci_hostname, aci_username and aci_password.' + when: aci_hostname is not defined or aci_username is not defined or aci_password is not defined + +- name: Set vars + set_fact: + aci_info: &aci_info + host: "{{ aci_hostname }}" + username: "{{ aci_username }}" + password: "{{ aci_password }}" + validate_certs: '{{ aci_validate_certs | default(false) }}' + use_ssl: '{{ aci_use_ssl | default(true) }}' + use_proxy: '{{ aci_use_proxy | default(true) }}' + output_level: '{{ aci_output_level | default("info") }}' + +- name: Verify Cloud and Non-Cloud Sites in use. + include_tasks: ../../../../../../integration/targets/aci_cloud_provider/tasks/main.yml + +- name: Execute tasks only for non-cloud sites + when: query_cloud.current == [] # This condition will skip execution for cloud sites + block: + + # CONFIGURE SYSTEM ENDPOINT CONTROLS SETTINGS TO DEFAULT + - name: Set system endpoint controls settings to default prior to test + cisco.aci.aci_system_endpoint_controls: + <<: *aci_info + ip_aging: + admin_state: false + roque_ep_control: + admin_state: false + interval: 60 + multiplication_factor: 4 + hold_interval: 1800 + ep_loop_protection: + admin_state: false + interval: 60 + multiplication_factor: 4 + action: [ port ] + + # CONFIGURE SYSTEM ENDPOINT CONTROLS SETTINGS + - name: Set system endpoint controls settings (check_mode) + cisco.aci.aci_system_endpoint_controls: &endpoint_controls + <<: *aci_info + ip_aging: + admin_state: true + roque_ep_control: + admin_state: true + interval: 50 + multiplication_factor: 10 + hold_interval: 2000 + ep_loop_protection: + admin_state: true + interval: 70 + multiplication_factor: 15 + action: [ bd, port ] + check_mode: true + register: cm_set_endpoint_controls + + - name: Set system endpoint controls settings + cisco.aci.aci_system_endpoint_controls: + <<: *endpoint_controls + register: nm_set_endpoint_controls + + - name: Set system endpoint controls settings again + cisco.aci.aci_system_endpoint_controls: + <<: *endpoint_controls + register: nm_set_endpoint_controls_again + + - name: Verify set system endpoint controls + ansible.builtin.assert: + that: + - cm_set_endpoint_controls is changed + - cm_set_endpoint_controls.proposed.infraInfra.children.0.epIpAgingP.attributes.adminSt == "enabled" + - cm_set_endpoint_controls.proposed.infraInfra.children.0.epIpAgingP.attributes.name == "default" + - cm_set_endpoint_controls.proposed.infraInfra.children.1.epControlP.attributes.adminSt == "enabled" + - cm_set_endpoint_controls.proposed.infraInfra.children.1.epControlP.attributes.name == "default" + - cm_set_endpoint_controls.proposed.infraInfra.children.1.epControlP.attributes.holdIntvl == "2000" + - cm_set_endpoint_controls.proposed.infraInfra.children.1.epControlP.attributes.rogueEpDetectIntvl == "50" + - cm_set_endpoint_controls.proposed.infraInfra.children.1.epControlP.attributes.rogueEpDetectMult == "10" + - cm_set_endpoint_controls.proposed.infraInfra.children.2.epLoopProtectP.attributes.adminSt == "enabled" + - cm_set_endpoint_controls.proposed.infraInfra.children.2.epLoopProtectP.attributes.name == "default" + - cm_set_endpoint_controls.proposed.infraInfra.children.2.epLoopProtectP.attributes.action == "bd-learn-disable,port-disable" + - cm_set_endpoint_controls.proposed.infraInfra.children.2.epLoopProtectP.attributes.loopDetectIntvl == "70" + - cm_set_endpoint_controls.proposed.infraInfra.children.2.epLoopProtectP.attributes.loopDetectMult == "15" + - nm_set_endpoint_controls is changed + - nm_set_endpoint_controls.current.0.infraInfra.children.0.epIpAgingP.attributes.adminSt == "enabled" + - nm_set_endpoint_controls.current.0.infraInfra.children.0.epIpAgingP.attributes.name == "default" + - nm_set_endpoint_controls.current.0.infraInfra.children.1.epControlP.attributes.adminSt == "enabled" + - nm_set_endpoint_controls.current.0.infraInfra.children.1.epControlP.attributes.name == "default" + - nm_set_endpoint_controls.current.0.infraInfra.children.1.epControlP.attributes.holdIntvl == "2000" + - nm_set_endpoint_controls.current.0.infraInfra.children.1.epControlP.attributes.rogueEpDetectIntvl == "50" + - nm_set_endpoint_controls.current.0.infraInfra.children.1.epControlP.attributes.rogueEpDetectMult == "10" + - nm_set_endpoint_controls.current.0.infraInfra.children.2.epLoopProtectP.attributes.adminSt == "enabled" + - nm_set_endpoint_controls.current.0.infraInfra.children.2.epLoopProtectP.attributes.name == "default" + - nm_set_endpoint_controls.current.0.infraInfra.children.2.epLoopProtectP.attributes.action == "bd-learn-disable,port-disable" + - nm_set_endpoint_controls.current.0.infraInfra.children.2.epLoopProtectP.attributes.loopDetectIntvl == "70" + - nm_set_endpoint_controls.current.0.infraInfra.children.2.epLoopProtectP.attributes.loopDetectMult == "15" + - nm_set_endpoint_controls_again is not changed + - nm_set_endpoint_controls_again.current.0.infraInfra.children.0.epIpAgingP.attributes.adminSt == "enabled" + - nm_set_endpoint_controls_again.current.0.infraInfra.children.0.epIpAgingP.attributes.name == "default" + - nm_set_endpoint_controls_again.current.0.infraInfra.children.1.epControlP.attributes.adminSt == "enabled" + - nm_set_endpoint_controls_again.current.0.infraInfra.children.1.epControlP.attributes.name == "default" + - nm_set_endpoint_controls_again.current.0.infraInfra.children.1.epControlP.attributes.holdIntvl == "2000" + - nm_set_endpoint_controls_again.current.0.infraInfra.children.1.epControlP.attributes.rogueEpDetectIntvl == "50" + - nm_set_endpoint_controls_again.current.0.infraInfra.children.1.epControlP.attributes.rogueEpDetectMult == "10" + - nm_set_endpoint_controls_again.current.0.infraInfra.children.2.epLoopProtectP.attributes.adminSt == "enabled" + - nm_set_endpoint_controls_again.current.0.infraInfra.children.2.epLoopProtectP.attributes.name == "default" + - nm_set_endpoint_controls_again.current.0.infraInfra.children.2.epLoopProtectP.attributes.action == "bd-learn-disable,port-disable" + - nm_set_endpoint_controls_again.current.0.infraInfra.children.2.epLoopProtectP.attributes.loopDetectIntvl == "70" + - nm_set_endpoint_controls_again.current.0.infraInfra.children.2.epLoopProtectP.attributes.loopDetectMult == "15" + - nm_set_endpoint_controls_again.previous.0.infraInfra.children.0.epIpAgingP.attributes.adminSt == "enabled" + - nm_set_endpoint_controls_again.previous.0.infraInfra.children.0.epIpAgingP.attributes.name == "default" + - nm_set_endpoint_controls_again.previous.0.infraInfra.children.1.epControlP.attributes.adminSt == "enabled" + - nm_set_endpoint_controls_again.previous.0.infraInfra.children.1.epControlP.attributes.name == "default" + - nm_set_endpoint_controls_again.previous.0.infraInfra.children.1.epControlP.attributes.holdIntvl == "2000" + - nm_set_endpoint_controls_again.previous.0.infraInfra.children.1.epControlP.attributes.rogueEpDetectIntvl == "50" + - nm_set_endpoint_controls_again.previous.0.infraInfra.children.1.epControlP.attributes.rogueEpDetectMult == "10" + - nm_set_endpoint_controls_again.previous.0.infraInfra.children.2.epLoopProtectP.attributes.adminSt == "enabled" + - nm_set_endpoint_controls_again.previous.0.infraInfra.children.2.epLoopProtectP.attributes.name == "default" + - nm_set_endpoint_controls_again.previous.0.infraInfra.children.2.epLoopProtectP.attributes.action == "bd-learn-disable,port-disable" + - nm_set_endpoint_controls_again.previous.0.infraInfra.children.2.epLoopProtectP.attributes.loopDetectIntvl == "70" + - nm_set_endpoint_controls_again.previous.0.infraInfra.children.2.epLoopProtectP.attributes.loopDetectMult == "15" + + - name: Set ip aging system endpoint controls settings to default + cisco.aci.aci_system_endpoint_controls: + <<: *aci_info + ip_aging: + admin_state: false + register: nm_ip_aging + + - name: Set roque ep control system endpoint controls settings to default + cisco.aci.aci_system_endpoint_controls: + <<: *aci_info + roque_ep_control: + admin_state: false + interval: 60 + multiplication_factor: 4 + hold_interval: 1800 + register: nm_roque_ep_control + + - name: Set ep loop protection system endpoint controls settings without action to default + cisco.aci.aci_system_endpoint_controls: + <<: *aci_info + ep_loop_protection: + admin_state: false + interval: 60 + multiplication_factor: 4 + register: nm_ep_loop_protection_no_action + + - name: Set ep loop protection system endpoint controls settings action to default + cisco.aci.aci_system_endpoint_controls: + <<: *aci_info + ep_loop_protection: + action: port + register: nm_ep_loop_protection_action + + - name: Set system endpoint controls settings without config in config containers + cisco.aci.aci_system_endpoint_controls: + <<: *aci_info + ip_aging: + roque_ep_control: + ep_loop_protection: + register: nm_no_config_in_config_containers + + - name: Set system endpoint controls settings without config (error) + cisco.aci.aci_system_endpoint_controls: + <<: *aci_info + register: err_no_config + ignore_errors: true + + - name: Verify individual set system endpoint controls and error + ansible.builtin.assert: + that: + - nm_ip_aging is changed + - nm_ip_aging.previous.0.infraInfra.children.0.epIpAgingP.attributes.adminSt == "enabled" + - nm_ip_aging.previous.0.infraInfra.children.0.epIpAgingP.attributes.name == "default" + - nm_ip_aging.previous.0.infraInfra.children.1.epControlP.attributes.adminSt == "enabled" + - nm_ip_aging.previous.0.infraInfra.children.1.epControlP.attributes.name == "default" + - nm_ip_aging.previous.0.infraInfra.children.1.epControlP.attributes.holdIntvl == "2000" + - nm_ip_aging.previous.0.infraInfra.children.1.epControlP.attributes.rogueEpDetectIntvl == "50" + - nm_ip_aging.previous.0.infraInfra.children.1.epControlP.attributes.rogueEpDetectMult == "10" + - nm_ip_aging.previous.0.infraInfra.children.2.epLoopProtectP.attributes.adminSt == "enabled" + - nm_ip_aging.previous.0.infraInfra.children.2.epLoopProtectP.attributes.name == "default" + - nm_ip_aging.previous.0.infraInfra.children.2.epLoopProtectP.attributes.action == "bd-learn-disable,port-disable" + - nm_ip_aging.previous.0.infraInfra.children.2.epLoopProtectP.attributes.loopDetectIntvl == "70" + - nm_ip_aging.previous.0.infraInfra.children.2.epLoopProtectP.attributes.loopDetectMult == "15" + - nm_ip_aging.current.0.infraInfra.children.0.epIpAgingP.attributes.adminSt == "disabled" + - nm_ip_aging.current.0.infraInfra.children.0.epIpAgingP.attributes.name == "default" + - nm_ip_aging.current.0.infraInfra.children.1.epControlP.attributes.adminSt == "enabled" + - nm_ip_aging.current.0.infraInfra.children.1.epControlP.attributes.name == "default" + - nm_ip_aging.current.0.infraInfra.children.1.epControlP.attributes.holdIntvl == "2000" + - nm_ip_aging.current.0.infraInfra.children.1.epControlP.attributes.rogueEpDetectIntvl == "50" + - nm_ip_aging.current.0.infraInfra.children.1.epControlP.attributes.rogueEpDetectMult == "10" + - nm_ip_aging.current.0.infraInfra.children.2.epLoopProtectP.attributes.adminSt == "enabled" + - nm_ip_aging.current.0.infraInfra.children.2.epLoopProtectP.attributes.name == "default" + - nm_ip_aging.current.0.infraInfra.children.2.epLoopProtectP.attributes.action == "bd-learn-disable,port-disable" + - nm_ip_aging.current.0.infraInfra.children.2.epLoopProtectP.attributes.loopDetectIntvl == "70" + - nm_ip_aging.current.0.infraInfra.children.2.epLoopProtectP.attributes.loopDetectMult == "15" + - nm_roque_ep_control is changed + - nm_roque_ep_control.previous.0.infraInfra.children.0.epIpAgingP.attributes.adminSt == "disabled" + - nm_roque_ep_control.previous.0.infraInfra.children.0.epIpAgingP.attributes.name == "default" + - nm_roque_ep_control.previous.0.infraInfra.children.1.epControlP.attributes.adminSt == "enabled" + - nm_roque_ep_control.previous.0.infraInfra.children.1.epControlP.attributes.name == "default" + - nm_roque_ep_control.previous.0.infraInfra.children.1.epControlP.attributes.holdIntvl == "2000" + - nm_roque_ep_control.previous.0.infraInfra.children.1.epControlP.attributes.rogueEpDetectIntvl == "50" + - nm_roque_ep_control.previous.0.infraInfra.children.1.epControlP.attributes.rogueEpDetectMult == "10" + - nm_roque_ep_control.previous.0.infraInfra.children.2.epLoopProtectP.attributes.adminSt == "enabled" + - nm_roque_ep_control.previous.0.infraInfra.children.2.epLoopProtectP.attributes.name == "default" + - nm_roque_ep_control.previous.0.infraInfra.children.2.epLoopProtectP.attributes.action == "bd-learn-disable,port-disable" + - nm_roque_ep_control.previous.0.infraInfra.children.2.epLoopProtectP.attributes.loopDetectIntvl == "70" + - nm_roque_ep_control.previous.0.infraInfra.children.2.epLoopProtectP.attributes.loopDetectMult == "15" + - nm_roque_ep_control.current.0.infraInfra.children.0.epIpAgingP.attributes.adminSt == "disabled" + - nm_roque_ep_control.current.0.infraInfra.children.0.epIpAgingP.attributes.name == "default" + - nm_roque_ep_control.current.0.infraInfra.children.1.epControlP.attributes.adminSt == "disabled" + - nm_roque_ep_control.current.0.infraInfra.children.1.epControlP.attributes.name == "default" + - nm_roque_ep_control.current.0.infraInfra.children.1.epControlP.attributes.holdIntvl == "1800" + - nm_roque_ep_control.current.0.infraInfra.children.1.epControlP.attributes.rogueEpDetectIntvl == "60" + - nm_roque_ep_control.current.0.infraInfra.children.1.epControlP.attributes.rogueEpDetectMult == "4" + - nm_roque_ep_control.current.0.infraInfra.children.2.epLoopProtectP.attributes.adminSt == "enabled" + - nm_roque_ep_control.current.0.infraInfra.children.2.epLoopProtectP.attributes.name == "default" + - nm_roque_ep_control.current.0.infraInfra.children.2.epLoopProtectP.attributes.action == "bd-learn-disable,port-disable" + - nm_roque_ep_control.current.0.infraInfra.children.2.epLoopProtectP.attributes.loopDetectIntvl == "70" + - nm_roque_ep_control.current.0.infraInfra.children.2.epLoopProtectP.attributes.loopDetectMult == "15" + - nm_ep_loop_protection_no_action is changed + - nm_ep_loop_protection_no_action.previous.0.infraInfra.children.0.epIpAgingP.attributes.adminSt == "disabled" + - nm_ep_loop_protection_no_action.previous.0.infraInfra.children.0.epIpAgingP.attributes.name == "default" + - nm_ep_loop_protection_no_action.previous.0.infraInfra.children.1.epControlP.attributes.adminSt == "disabled" + - nm_ep_loop_protection_no_action.previous.0.infraInfra.children.1.epControlP.attributes.name == "default" + - nm_ep_loop_protection_no_action.previous.0.infraInfra.children.1.epControlP.attributes.holdIntvl == "1800" + - nm_ep_loop_protection_no_action.previous.0.infraInfra.children.1.epControlP.attributes.rogueEpDetectIntvl == "60" + - nm_ep_loop_protection_no_action.previous.0.infraInfra.children.1.epControlP.attributes.rogueEpDetectMult == "4" + - nm_ep_loop_protection_no_action.previous.0.infraInfra.children.2.epLoopProtectP.attributes.adminSt == "enabled" + - nm_ep_loop_protection_no_action.previous.0.infraInfra.children.2.epLoopProtectP.attributes.name == "default" + - nm_ep_loop_protection_no_action.previous.0.infraInfra.children.2.epLoopProtectP.attributes.action == "bd-learn-disable,port-disable" + - nm_ep_loop_protection_no_action.previous.0.infraInfra.children.2.epLoopProtectP.attributes.loopDetectIntvl == "70" + - nm_ep_loop_protection_no_action.previous.0.infraInfra.children.2.epLoopProtectP.attributes.loopDetectMult == "15" + - nm_ep_loop_protection_no_action.current.0.infraInfra.children.0.epIpAgingP.attributes.adminSt == "disabled" + - nm_ep_loop_protection_no_action.current.0.infraInfra.children.0.epIpAgingP.attributes.name == "default" + - nm_ep_loop_protection_no_action.current.0.infraInfra.children.1.epControlP.attributes.adminSt == "disabled" + - nm_ep_loop_protection_no_action.current.0.infraInfra.children.1.epControlP.attributes.name == "default" + - nm_ep_loop_protection_no_action.current.0.infraInfra.children.1.epControlP.attributes.holdIntvl == "1800" + - nm_ep_loop_protection_no_action.current.0.infraInfra.children.1.epControlP.attributes.rogueEpDetectIntvl == "60" + - nm_ep_loop_protection_no_action.current.0.infraInfra.children.1.epControlP.attributes.rogueEpDetectMult == "4" + - nm_ep_loop_protection_no_action.current.0.infraInfra.children.2.epLoopProtectP.attributes.adminSt == "disabled" + - nm_ep_loop_protection_no_action.current.0.infraInfra.children.2.epLoopProtectP.attributes.name == "default" + - nm_ep_loop_protection_no_action.current.0.infraInfra.children.2.epLoopProtectP.attributes.action == "bd-learn-disable,port-disable" + - nm_ep_loop_protection_no_action.current.0.infraInfra.children.2.epLoopProtectP.attributes.loopDetectIntvl == "60" + - nm_ep_loop_protection_no_action.current.0.infraInfra.children.2.epLoopProtectP.attributes.loopDetectMult == "4" + - nm_ep_loop_protection_action is changed + - nm_ep_loop_protection_action.previous.0.infraInfra.children.0.epIpAgingP.attributes.adminSt == "disabled" + - nm_ep_loop_protection_action.previous.0.infraInfra.children.0.epIpAgingP.attributes.name == "default" + - nm_ep_loop_protection_action.previous.0.infraInfra.children.1.epControlP.attributes.adminSt == "disabled" + - nm_ep_loop_protection_action.previous.0.infraInfra.children.1.epControlP.attributes.name == "default" + - nm_ep_loop_protection_action.previous.0.infraInfra.children.1.epControlP.attributes.holdIntvl == "1800" + - nm_ep_loop_protection_action.previous.0.infraInfra.children.1.epControlP.attributes.rogueEpDetectIntvl == "60" + - nm_ep_loop_protection_action.previous.0.infraInfra.children.1.epControlP.attributes.rogueEpDetectMult == "4" + - nm_ep_loop_protection_action.previous.0.infraInfra.children.2.epLoopProtectP.attributes.adminSt == "disabled" + - nm_ep_loop_protection_action.previous.0.infraInfra.children.2.epLoopProtectP.attributes.name == "default" + - nm_ep_loop_protection_action.previous.0.infraInfra.children.2.epLoopProtectP.attributes.action == "bd-learn-disable,port-disable" + - nm_ep_loop_protection_action.previous.0.infraInfra.children.2.epLoopProtectP.attributes.loopDetectIntvl == "60" + - nm_ep_loop_protection_action.previous.0.infraInfra.children.2.epLoopProtectP.attributes.loopDetectMult == "4" + - nm_ep_loop_protection_action.current.0.infraInfra.children.0.epIpAgingP.attributes.adminSt == "disabled" + - nm_ep_loop_protection_action.current.0.infraInfra.children.0.epIpAgingP.attributes.name == "default" + - nm_ep_loop_protection_action.current.0.infraInfra.children.1.epControlP.attributes.adminSt == "disabled" + - nm_ep_loop_protection_action.current.0.infraInfra.children.1.epControlP.attributes.name == "default" + - nm_ep_loop_protection_action.current.0.infraInfra.children.1.epControlP.attributes.holdIntvl == "1800" + - nm_ep_loop_protection_action.current.0.infraInfra.children.1.epControlP.attributes.rogueEpDetectIntvl == "60" + - nm_ep_loop_protection_action.current.0.infraInfra.children.1.epControlP.attributes.rogueEpDetectMult == "4" + - nm_ep_loop_protection_action.current.0.infraInfra.children.2.epLoopProtectP.attributes.adminSt == "disabled" + - nm_ep_loop_protection_action.current.0.infraInfra.children.2.epLoopProtectP.attributes.name == "default" + - nm_ep_loop_protection_action.current.0.infraInfra.children.2.epLoopProtectP.attributes.action == "port-disable" + - nm_ep_loop_protection_action.current.0.infraInfra.children.2.epLoopProtectP.attributes.loopDetectIntvl == "60" + - nm_ep_loop_protection_action.current.0.infraInfra.children.2.epLoopProtectP.attributes.loopDetectMult == "4" + - nm_no_config_in_config_containers is not changed + - err_no_config is not changed + - err_no_config is failed + - err_no_config.msg == "state is present but any of the following are missing{{":"}} ip_aging, roque_ep_control, ep_loop_protection" + + # QUERY SYSTEM ENDPOINT CONTROLS SETTINGS + - name: Query system endpoint controls settings + cisco.aci.aci_system_endpoint_controls: + <<: *aci_info + state: query + register: query_endpoint_controls + + - name: Verify system endpoint controls query + ansible.builtin.assert: + that: + - query_endpoint_controls is not changed + - query_endpoint_controls.current.0.infraInfra.children.0.epIpAgingP.attributes.adminSt == "disabled" + - query_endpoint_controls.current.0.infraInfra.children.0.epIpAgingP.attributes.name == "default" + - query_endpoint_controls.current.0.infraInfra.children.1.epControlP.attributes.adminSt == "disabled" + - query_endpoint_controls.current.0.infraInfra.children.1.epControlP.attributes.name == "default" + - query_endpoint_controls.current.0.infraInfra.children.1.epControlP.attributes.holdIntvl == "1800" + - query_endpoint_controls.current.0.infraInfra.children.1.epControlP.attributes.rogueEpDetectIntvl == "60" + - query_endpoint_controls.current.0.infraInfra.children.1.epControlP.attributes.rogueEpDetectMult == "4" + - query_endpoint_controls.current.0.infraInfra.children.2.epLoopProtectP.attributes.adminSt == "disabled" + - query_endpoint_controls.current.0.infraInfra.children.2.epLoopProtectP.attributes.name == "default" + - query_endpoint_controls.current.0.infraInfra.children.2.epLoopProtectP.attributes.action == "port-disable" + - query_endpoint_controls.current.0.infraInfra.children.2.epLoopProtectP.attributes.loopDetectIntvl == "60" + - query_endpoint_controls.current.0.infraInfra.children.2.epLoopProtectP.attributes.loopDetectMult == "4" diff --git a/ansible_collections/cisco/aci/tests/integration/targets/aci_system_global_aes_passphrase_encryption/aliases b/ansible_collections/cisco/aci/tests/integration/targets/aci_system_global_aes_passphrase_encryption/aliases new file mode 100644 index 000000000..209b793f9 --- /dev/null +++ b/ansible_collections/cisco/aci/tests/integration/targets/aci_system_global_aes_passphrase_encryption/aliases @@ -0,0 +1,2 @@ +# No ACI simulator yet, so not enabled +# unsupported diff --git a/ansible_collections/cisco/aci/tests/integration/targets/aci_system_global_aes_passphrase_encryption/tasks/main.yml b/ansible_collections/cisco/aci/tests/integration/targets/aci_system_global_aes_passphrase_encryption/tasks/main.yml new file mode 100644 index 000000000..732a4d4f3 --- /dev/null +++ b/ansible_collections/cisco/aci/tests/integration/targets/aci_system_global_aes_passphrase_encryption/tasks/main.yml @@ -0,0 +1,84 @@ +# Test code for the ACI modules +# Copyright: (c) 2023, Tim Cragg (@timcragg) <tcragg@cisco.com> +# Copyright: (c) 2023, Akini Ross (@akinross) <akinross@cisco.com> + +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +- name: Test that we have an ACI APIC host, ACI username and ACI password + fail: + msg: 'Please define the following variables: aci_hostname, aci_username and aci_password.' + when: aci_hostname is not defined or aci_username is not defined or aci_password is not defined + +# SET VARS +- name: Set vars + set_fact: + aci_info: &aci_info + host: '{{ aci_hostname }}' + username: '{{ aci_username }}' + password: '{{ aci_password }}' + validate_certs: '{{ aci_validate_certs | default(false) }}' + use_ssl: '{{ aci_use_ssl | default(true) }}' + use_proxy: '{{ aci_use_proxy | default(true) }}' + output_level: '{{ aci_output_level | default("info") }}' + +- name: Verify Cloud and Non-Cloud Sites in use. + include_tasks: ../../../../../../integration/targets/aci_cloud_provider/tasks/main.yml + +- name: Execute tasks only for non-cloud sites + when: query_cloud.current == [] # This condition will skip execution for cloud sites + block: + + - name: Clear existing passphrase + cisco.aci.aci_system_global_aes_passphrase_encryption: + <<: *aci_info + state: absent + + - name: Set passphrase + cisco.aci.aci_system_global_aes_passphrase_encryption: + <<: *aci_info + passphrase: ansible_passphrase + state: present + + - name: Query key configured + cisco.aci.aci_system_global_aes_passphrase_encryption: + <<: *aci_info + state: query + register: query_passphrase + + - name: Enable encryption + cisco.aci.aci_system_global_aes_passphrase_encryption: + <<: *aci_info + enable: yes + state: present + + - name: Query encryption enabled + cisco.aci.aci_system_global_aes_passphrase_encryption: + <<: *aci_info + state: query + register: query_encryption + + - name: Verify passphrase and encryption settings + ansible.builtin.assert: + that: + - query_passphrase.current.0.pkiExportEncryptionKey.attributes.keyConfigured == "yes" + - query_passphrase.current.0.pkiExportEncryptionKey.attributes.strongEncryptionEnabled == "no" + - query_encryption.current.0.pkiExportEncryptionKey.attributes.keyConfigured == "yes" + - query_encryption.current.0.pkiExportEncryptionKey.attributes.strongEncryptionEnabled == "yes" + + # CLEAR PASSPHRASE + - name: Clear encryption key + cisco.aci.aci_system_global_aes_passphrase_encryption: + <<: *aci_info + state: absent + + - name: Query cleared encryption key + cisco.aci.aci_system_global_aes_passphrase_encryption: + <<: *aci_info + state: query + register: query_deletion + + - name: Verify encryption key is deleted + ansible.builtin.assert: + that: + - query_deletion.current.0.pkiExportEncryptionKey.attributes.keyConfigured == "no" + - query_deletion.current.0.pkiExportEncryptionKey.attributes.strongEncryptionEnabled == "no" diff --git a/ansible_collections/cisco/aci/tests/integration/targets/aci_tenant/filter_plugins/generate_ips.py b/ansible_collections/cisco/aci/tests/integration/targets/aci_tenant/filter_plugins/generate_ips.py new file mode 100644 index 000000000..130fbb786 --- /dev/null +++ b/ansible_collections/cisco/aci/tests/integration/targets/aci_tenant/filter_plugins/generate_ips.py @@ -0,0 +1,28 @@ +# Copyright (c) 2023 Cisco and/or its affiliates. +# Copyright: (c) 2023, Shreyas Srish (@shrsr) <ssrish@cisco.com> + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +from ipaddress import ip_network +import random + +RANGE_IPV4 = list(ip_network("192.0.2.0/24").hosts()) + list(ip_network("198.51.100.0/24").hosts()) + list(ip_network("203.0.113.0/24").hosts()) + + +class FilterModule(object): + def filters(self): + return { + "generate_random_ips": self.generate_random_ips, + } + + def generate_random_ips(self, given_ip, insert_given_ip_at, number_of_ips): + ips = "" + for i in range(number_of_ips): + if i == insert_given_ip_at - 1: + ips += given_ip + else: + ips += str((random.choice(RANGE_IPV4))) + ips += "," + return ips.rstrip(",") diff --git a/ansible_collections/cisco/aci/tests/integration/targets/aci_tenant/pki/admin.crt b/ansible_collections/cisco/aci/tests/integration/targets/aci_tenant/pki/admin.crt new file mode 100644 index 000000000..cfac5531e --- /dev/null +++ b/ansible_collections/cisco/aci/tests/integration/targets/aci_tenant/pki/admin.crt @@ -0,0 +1,14 @@ +-----BEGIN CERTIFICATE----- +MIICODCCAaGgAwIBAgIJAIt8XMntue0VMA0GCSqGSIb3DQEBCwUAMDQxDjAMBgNV +BAMMBUFkbWluMRUwEwYDVQQKDAxZb3VyIENvbXBhbnkxCzAJBgNVBAYTAlVTMCAX +DTE4MDEwOTAwNTk0NFoYDzIxMTcxMjE2MDA1OTQ0WjA0MQ4wDAYDVQQDDAVBZG1p +bjEVMBMGA1UECgwMWW91ciBDb21wYW55MQswCQYDVQQGEwJVUzCBnzANBgkqhkiG +9w0BAQEFAAOBjQAwgYkCgYEAohG/7axtt7CbSaMP7r+2mhTKbNgh0Ww36C7Ta14i +v+VmLyKkQHnXinKGhp6uy3Nug+15a+eIu7CrgpBVMQeCiWfsnwRocKcQJWIYDrWl +XHxGQn31yYKR6mylE7Dcj3rMFybnyhezr5D8GcP85YRPmwG9H2hO/0Y1FUnWu9Iw +AQkCAwEAAaNQME4wHQYDVR0OBBYEFD0jLXfpkrU/ChzRvfruRs/fy1VXMB8GA1Ud +IwQYMBaAFD0jLXfpkrU/ChzRvfruRs/fy1VXMAwGA1UdEwQFMAMBAf8wDQYJKoZI +hvcNAQELBQADgYEAOmvre+5tgZ0+F3DgsfxNQqLTrGiBgGCIymPkP/cBXXkNuJyl +3ac7tArHQc7WEA4U2R2rZbEq8FC3UJJm4nUVtCPvEh3G9OhN2xwYev79yt6pIn/l +KU0Td2OpVyo0eLqjoX5u2G90IBWzhyjFbo+CcKMrSVKj1YOdG0E3OuiJf00= +-----END CERTIFICATE----- diff --git a/ansible_collections/cisco/aci/tests/integration/targets/aci_tenant/pki/admin.key b/ansible_collections/cisco/aci/tests/integration/targets/aci_tenant/pki/admin.key new file mode 100644 index 000000000..63bb00cc0 --- /dev/null +++ b/ansible_collections/cisco/aci/tests/integration/targets/aci_tenant/pki/admin.key @@ -0,0 +1,16 @@ +-----BEGIN PRIVATE KEY----- +MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBAKIRv+2sbbewm0mj +D+6/tpoUymzYIdFsN+gu02teIr/lZi8ipEB514pyhoaerstzboPteWvniLuwq4KQ +VTEHgoln7J8EaHCnECViGA61pVx8RkJ99cmCkepspROw3I96zBcm58oXs6+Q/BnD +/OWET5sBvR9oTv9GNRVJ1rvSMAEJAgMBAAECgYByu3QO0qF9h7X3JEu0Ld4cKBnB +giQ2uJC/et7KxIJ/LOvw9GopBthyt27KwG1ntBkJpkTuAaQHkyNns7vLkNB0S0IR ++owVFEcKYq9VCHTaiQU8TDp24gN+yPTrpRuH8YhDVq5SfVdVuTMgHVQdj4ya4VlF +Gj+a7+ipxtGiLsVGrQJBAM7p0Fm0xmzi+tBOASUAcVrPLcteFIaTBFwfq16dm/ON +00Khla8Et5kMBttTbqbukl8mxFjBEEBlhQqb6EdQQ0sCQQDIhHx1a9diG7y/4DQA +4KvR3FCYwP8PBORlSamegzCo+P1OzxiEo0amX7yQMA5UyiP/kUsZrme2JBZgna8S +p4R7AkEAr7rMhSOPUnMD6V4WgsJ5g1Jp5kqkzBaYoVUUSms5RASz4+cwJVCwTX91 +Y1jcpVIBZmaaY3a0wrx13ajEAa0dOQJBAIpjnb4wqpsEh7VpmJqOdSdGxb1XXfFQ +sA0T1OQYqQnFppWwqrxIL+d9pZdiA1ITnNqyvUFBNETqDSOrUHwwb2cCQGArE+vu +ffPUWQ0j+fiK+covFG8NL7H+26NSGB5+Xsn9uwOGLj7K/YT6CbBtr9hJiuWjM1Al +0V4ltlTuu2mTMaw= +-----END PRIVATE KEY----- diff --git a/ansible_collections/cisco/aci/tests/integration/targets/aci_tenant/pki/admin_invalid.key b/ansible_collections/cisco/aci/tests/integration/targets/aci_tenant/pki/admin_invalid.key new file mode 100644 index 000000000..22f5fae45 --- /dev/null +++ b/ansible_collections/cisco/aci/tests/integration/targets/aci_tenant/pki/admin_invalid.key @@ -0,0 +1,3 @@ +-----BEGIN PRIVATE KEY----- +This is an invalid private key +-----END PRIVATE KEY----- diff --git a/ansible_collections/cisco/aci/tests/integration/targets/aci_tenant/pki/openssh_rsa.key b/ansible_collections/cisco/aci/tests/integration/targets/aci_tenant/pki/openssh_rsa.key new file mode 100644 index 000000000..0c18da5c5 --- /dev/null +++ b/ansible_collections/cisco/aci/tests/integration/targets/aci_tenant/pki/openssh_rsa.key @@ -0,0 +1,16 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAlwAAAAdzc2gtcn +NhAAAAAwEAAQAAAIEA3VnrdPOQbr3DPF5GbC31W7ScloEpU9BSDqPmpyYPUdsWl21UXBB8 +exip3GVOl+7GbB1WkDKYr7uMuBjsfDzMzZkDAFVEpud+IUzZB7aSfSd+L9bdeFG2sGI+Fv +y1QmiMBT5gcvXaM16vRKe4FywM07/Fmd3REm/+wtmFG/C4sYUAAAIQLuIWNS7iFjUAAAAH +c3NoLXJzYQAAAIEA3VnrdPOQbr3DPF5GbC31W7ScloEpU9BSDqPmpyYPUdsWl21UXBB8ex +ip3GVOl+7GbB1WkDKYr7uMuBjsfDzMzZkDAFVEpud+IUzZB7aSfSd+L9bdeFG2sGI+Fvy1 +QmiMBT5gcvXaM16vRKe4FywM07/Fmd3REm/+wtmFG/C4sYUAAAADAQABAAAAgHj5rhALFg +MQP2X8+GwjahemzHYNPXMLRe2ucl8kE/de0CgOnq56bC4yupMz4xJyc4ufNTI2FPDmhfAP +3x+/cwZeYFsipyGdL1IYbfk0QYSP65Btr2yq8+QyN7zWdFXQ8csT0ImZgNiQKehc69ctLH +XcyelsdwNiUCRZYa7kCpf5AAAAQQCo7OSWQUa16xP9KrKm0F3fnaAKewhQNDIwok5PRgoN +03k/IpGOCAjvNuOb7DkXmVvxjO8Rj4L16vL+RTzHg8n7AAAAQQD7tej6gJy3MLcmrQ4aHb +FeLzQ/ZXS2IgdIRC8rcNB1h9Rso7+fySVFwnmwy2Um7wwsjNnr2xyhigwfQCSyRubfAAAA +QQDhH5EX7+hdm/fPLM6Goz9N3ERbIgBq2Mel5CCi/Ns7vDfBQiEla1atdKTV0S2EYfxIw2 +ehkMGbmXl2/9JHxKgbAAAAFGNpemhhb0BDSVpIQU8tTS05MjhRAQIDBAUG +-----END OPENSSH PRIVATE KEY----- diff --git a/ansible_collections/cisco/aci/tests/integration/targets/aci_tenant/pki/rsa_ansible.key b/ansible_collections/cisco/aci/tests/integration/targets/aci_tenant/pki/rsa_ansible.key new file mode 100644 index 000000000..ac63a0055 --- /dev/null +++ b/ansible_collections/cisco/aci/tests/integration/targets/aci_tenant/pki/rsa_ansible.key @@ -0,0 +1,15 @@ +-----BEGIN RSA PRIVATE KEY----- +MIICXgIBAAKBgQDVyLS8/ix6QOH7R83B4WuhsliL6nffBvrkHXXsqViit3OZd+/K +fSrNlZysUvHS4hxfRtJrFQfpkogwXEEupBPF3p0xy7wZzvjjWWJk0NQ8PoVlOhUY +emZTfMX+FFNr9pAjjjaVHb9jCuxko7upAyj8POhhETY2zYoJoa8TR6fLZwIDAQAB +AoGBALo5GzeGMThNTIyW/6Tjt94ifP9kPwcIDYSoJRECczNKmmgVEcxRO/fZW6DA +n+YTEKPuDV059KqB+iAmPKFkS9N41kaq+NUAknlFJPV6Vs3gpvJGqWgu++73dhR5 +cKsHTlK2KBsRtsXnOJ9odKWFjiTnZ1Eyvmhw7ct+Fojb/7ABAkEA9+Wwm+HGlYqw +ghuFaBtNuqC/S2vO6SEfdQvTDQKKO5ROei5m+ryjWj6flbCcG+5dLs8l4Zh3sQUL +kc0RQfHSWQJBANzFkdO6wXXPOw7RhAEP2sA2W2VacMbGynjsoDJXmypeJ7Z+odLb +5gNXET9RA77RY/saIBdwR4JNnku2WnoxU78CQQDhYirVP0vu8H6UfHMpeRGNqdLi +vq0LlrrkDxEe1f1aN/e17HRiaZnXVfKABWeZmXmNMndNifLgtiaTtC+JllRZAkEA +ydAdV0SANvaCETC7j9DzcgP+lm8PatYsHlCIvJxS7m71tKCbw0pbQDBmRtADMXrt +/4vJTEPKSrYzfxiqKstOtwJAXkWXaqVhJeKjbMj1buo6s/1qGIfSrZR/AjozvJ03 +JehevfULS3668jOYJZW6BoNhysx6+Hqf5Id8fB4iDWPQhA== +-----END RSA PRIVATE KEY----- diff --git a/ansible_collections/cisco/aci/tests/integration/targets/aci_tenant/pki/rsa_user.crt b/ansible_collections/cisco/aci/tests/integration/targets/aci_tenant/pki/rsa_user.crt new file mode 100644 index 000000000..de2223500 --- /dev/null +++ b/ansible_collections/cisco/aci/tests/integration/targets/aci_tenant/pki/rsa_user.crt @@ -0,0 +1,14 @@ +-----BEGIN CERTIFICATE----- +MIICODCCAaGgAwIBAgIUNOqiIBh811X/tPWSUgr9rajJ7t4wDQYJKoZIhvcNAQEL +BQAwLTEOMAwGA1UEAwwFQWRtaW4xDjAMBgNVBAoMBWNpc2NvMQswCQYDVQQGEwJV +UzAgFw0yMDEwMjkyMjQ0NTNaGA8yMTIwMTAwNTIyNDQ1M1owLTEOMAwGA1UEAwwF +QWRtaW4xDjAMBgNVBAoMBWNpc2NvMQswCQYDVQQGEwJVUzCBnzANBgkqhkiG9w0B +AQEFAAOBjQAwgYkCgYEAwjb3/W3x/bPX+bylh2PjXbcFPwpdTPJwqTxCdUinJRKv +HXW7rwRiV9TdoNZZ946RvVM6l2LzUJyaK4wZZHf6WKJ2veL6LIrORA32vN+ofmpn +XcTAUQ1JVyHbriy0GaT+1wYClqImWj8HxiskgpD+pKc+kzgl33xwwwqyuF1N7ikC +AwEAAaNTMFEwHQYDVR0OBBYEFAK18YAZAaPQW7bHvqRwselDeGskMB8GA1UdIwQY +MBaAFAK18YAZAaPQW7bHvqRwselDeGskMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZI +hvcNAQELBQADgYEAgIvzyP0t4CjsmUmgG7QP977c3+Uvbt2wlCwe+rrXlqvuSeaW +l4DaTyv8kYyiqIxgrTVI/G+HbpHgTO2yH57njTIAdRjsJgMU9z0oCazOtVD8KpXj +SKFUtJVbY27BQAnbuDOawX96a0UDY44Ia9NaPuq0/mEcdCKSpQP4ZuvvKVc= +-----END CERTIFICATE----- diff --git a/ansible_collections/cisco/aci/tests/integration/targets/aci_tenant/pki/rsa_user.key b/ansible_collections/cisco/aci/tests/integration/targets/aci_tenant/pki/rsa_user.key new file mode 100644 index 000000000..354dbbdbb --- /dev/null +++ b/ansible_collections/cisco/aci/tests/integration/targets/aci_tenant/pki/rsa_user.key @@ -0,0 +1,16 @@ +-----BEGIN PRIVATE KEY----- +MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBAMI29/1t8f2z1/m8 +pYdj4123BT8KXUzycKk8QnVIpyUSrx11u68EYlfU3aDWWfeOkb1TOpdi81CcmiuM +GWR3+liidr3i+iyKzkQN9rzfqH5qZ13EwFENSVch264stBmk/tcGApaiJlo/B8Yr +JIKQ/qSnPpM4Jd98cMMKsrhdTe4pAgMBAAECgYAX8c8BX9zF+rZWA/wkhRwzIa1z +6EM4iWt6cgN/kkWJPJR6fVl2aoP1cDki60qMSveM8AX5RCnbdnNLiypWSLSEogdd +bRWyFeF4ZXvivd+Lds2u5Ni3PiCrIpHfNvid2ERCaKhblQRdhi/dTH9Z+3kGspwc +jpKzWMmGjBpqWjWOQQJBAOB3cS/AxbwJ6Fzvbi6sLiK6Ry8eSIMlce3Yyw89oU+M +DGkIbggICCYKxXYIWtBbyxthdQudKFZYbLpCkLSMBXsCQQDdf5ICNN2R0ptYLhSX +kQ4tiGigi1hq93+25Ov1rI8eIFSYlKNcyA/cvwv5ptlXmy1UAyoAdGCbS47pgCwT +Nz+rAkEAtzHkR5PuDXSMluS2KRNPJ/qdxB/UEGzMGdEYkNy8vX5QVpyRqK5dcCbU +V2ukKm7wSe11KEBgPnA2dKGFFkU85wJAD895Vpr7bdtAp2yyn5cFEg74mO0ZZJlC +DoYMqb6lgJsCLtn9RzQonbMtYaadQPmcpLCNIPctpiggjV5OxxhcfQJBAM1ETm8p +/9beBPTS8cJdWHvCRE149H/ZCUxqjFZriJzFYvi0xor85eK8/3V7xaWtTkK25i3+ +xWk+sA3DYYDPGM8= +-----END PRIVATE KEY----- diff --git a/ansible_collections/cisco/aci/tests/integration/targets/aci_tenant/tasks/httpapi_connection.yml b/ansible_collections/cisco/aci/tests/integration/targets/aci_tenant/tasks/httpapi_connection.yml new file mode 100644 index 000000000..214f55fe2 --- /dev/null +++ b/ansible_collections/cisco/aci/tests/integration/targets/aci_tenant/tasks/httpapi_connection.yml @@ -0,0 +1,653 @@ +## Tests HTTTP Connection when a list of host are provided + +- name: Set vars + set_fact: + aci_info: &aci_info + host: "{{ aci_hostname }}" + username: "{{ aci_username }}" + password: "{{ aci_password }}" + validate_certs: "{{ aci_validate_certs | default(false) }}" + use_ssl: "{{ aci_use_ssl | default(true) }}" + use_proxy: "{{ aci_use_proxy | default(true) }}" + timeout: 5 + output_level: debug + +- name: Set vars with the randomly generated list of hosts for the task level operations + ansible.builtin.set_fact: + last_host_active: "{{ aci_hostname | generate_random_ips(5,5) }}" + second_host_active: "{{aci_hostname|generate_random_ips(2,5)}}" + no_host_active: "{{aci_hostname|generate_random_ips(0,5)}}" + +- name: Set the actual connection type specified in the inventory (this var is used at the end of this test) + ansible.builtin.set_fact: + old_connection: "{{ ansible_connection }}" + +- name: Set the connection to httpapi and set session key used in the inventory + ansible.builtin.set_fact: + ansible_user: "{{ aci_username }}" + ansible_password: "{{ aci_password }}" + ansible_connection: ansible.netcommon.httpapi + ansible_httpapi_session_key: {'admin': "{{ lookup('file', 'pki/admin.key') }}"} + ansible_httpapi_validate_certs: "{{ aci_validate_certs | default(false) }}" + ansible_httpapi_use_ssl: "{{ aci_use_ssl | default(true) }}" + ansible_httpapi_use_proxy: "{{ aci_use_proxy | default(true) }}" + ansible_httpapi_port: 443 + ansible_command_timeout: 5 + +- name: Run aci_aaa_user_certificate through the plugin + include_tasks: ../../../../../../integration/targets/aci_aaa_user_certificate/tasks/main.yml + +- name: Add user certificate to be used later in the test + cisco.aci.aci_aaa_user_certificate: + <<: *aci_info + aaa_user: "{{ aci_username }}" + name: admin + certificate: "{{ lookup('file', 'pki/admin.crt') }}" + state: present + +# XML operation, APIC HTTP Error and Connection reset upon changed parameter tests +- name: Add a tenant using an XML string (Check xml operations through plugin) + cisco.aci.aci_rest: + <<: *aci_info + path: api/mo/uni.xml + method: post + content: '<fvTenant name="Sales" descr="Sales departement"/>' + register: tenant_xml_plugin + +- name: Add an AP (with non existent tenant) + cisco.aci.aci_ap: + <<: *aci_info + tenant: ansible_test_non_existent + ap: ap + description: default ap + state: present + ignore_errors: true + register: ap_non_existent_tenant + +- name: Delete Tenant with the wrong username and password (Check that connection resets) + cisco.aci.aci_tenant: + <<: *aci_info + username: wrong_username + password: wrong_password + tenant: ansible_test + state: absent + ignore_errors: true + register: wrong_credentials + +- name: Set the username to null + ansible.builtin.set_fact: + ansible_user: + +- name: Delete Tenant with no username in the task or inventory (Check that username is set to the default value) + cisco.aci.aci_tenant: + host: "{{ aci_hostname }}" + password: "{{ aci_password }}" + validate_certs: "{{ aci_validate_certs | default(false) }}" + use_ssl: "{{ aci_use_ssl | default(true) }}" + use_proxy: "{{ aci_use_proxy | default(true) }}" + timeout: 5 + output_level: debug + tenant: ansible_test + state: absent + ignore_errors: true + register: no_username + +- name: Revert the username to its original value + ansible.builtin.set_fact: + ansible_user: "{{ aci_username }}" + +- name: Flatten the registered instances + ansible.builtin.set_fact: + ap_non_existent_tenant_flattened: "{{ ap_non_existent_tenant.httpapi_logs | flatten }}" + wrong_credentials_flattened: "{{ wrong_credentials.httpapi_logs | flatten }}" + no_username_flattened: "{{ no_username.httpapi_logs | flatten }}" + +- name: Verify XML operation and HTTP error returned by APIC + assert: + that: + - tenant_xml_plugin.status == 200 + - '"Received response from {{ aci_hostname }} for POST operation with HTTP: 400" in ap_non_existent_tenant_flattened' + - '"Re-setting connection due to change in the username" in wrong_credentials_flattened' + - '"Re-setting connection due to change in the password" in wrong_credentials_flattened' + - '"Connection to {{ aci_hostname }} has failed: HTTP Error 401: Unauthorized" in wrong_credentials_flattened' + - '"Establishing login for admin to {{ aci_hostname }}" in no_username_flattened' + +# Simulate HTTP 403 error test +- name: Delete Tenant with only password in the task (Check for 403) + cisco.aci.aci_tenant: + <<: *aci_info + host: "{{ last_host_active }}" + tenant: ansible_test + state: absent + register: op17_task_pwd_delete_tenant + +- name: Add Tenant with only password in the task (Check for 403) + cisco.aci.aci_tenant: + <<: *aci_info + host: "{{ last_host_active }}" + tenant: ansible_test + state: present + register: op17_task_pwd_add_tenant + +- name: logout to render the token invalid + cisco.aci.aci_rest: + <<: *aci_info + host: "{{ last_host_active }}" + path: /api/aaaLogout.json + method: post + rsp_subtree_preserve: true + content: | + { + "aaaUser": { + "attributes": { + "name": "{{ aci_username }}" + } + } + } + +- name: Add an AP with only password in the task (Ensure re-login on the same host) + cisco.aci.aci_ap: + <<: *aci_info + host: "{{ last_host_active }}" + tenant: ansible_test + ap: ap + description: default ap + state: present + register: op18_task_pwd_add_ap + +- name: Flatten the registered instances + ansible.builtin.set_fact: + op18_flattened_task_pwd_add_ap: "{{ op18_task_pwd_add_ap.httpapi_logs | flatten }}" + +- name: Verify forbidden error 403 + assert: + that: + - op17_task_pwd_add_tenant is changed + - op18_task_pwd_add_ap is changed + - '"Failed to receive response from {{ aci_hostname }} with HTTP Error 403: Forbidden" in op18_flattened_task_pwd_add_ap' + - '"Establishing login for {{ aci_username }} to {{ aci_hostname }}" in op18_flattened_task_pwd_add_ap' + - '"Connection to {{ aci_hostname }} was successful" in op18_flattened_task_pwd_add_ap' + +- name: reset connection to test other scenarios + meta: reset_connection + +# Precedence test +- name: Delete Tenant with password and private key in the task (private_key takes precedence) + cisco.aci.aci_tenant: + <<: *aci_info + private_key: "{{ lookup('file', 'pki/admin.key') }}" + tenant: ansible_test + state: absent + register: op1_task_private_key_delete_tenant + +- name: Add Tenant with password and private key in the task (Check for execution on the provided aci_hostname with no attempts at re-connection) + cisco.aci.aci_tenant: + <<: *aci_info + private_key: "{{ lookup('file', 'pki/admin.key') }}" + tenant: ansible_test + state: present + register: op1_task_private_key_add_tenant + +- name: Delete Tenant with only password in the task (password in the task takes precedence) + cisco.aci.aci_tenant: + <<: *aci_info + tenant: ansible_test + state: absent + register: op2_task_pwd_delete_tenant + +- name: Add Tenant with only password in the task (Check for execution on the provided aci_hostname with no attempts at re-connection) + cisco.aci.aci_tenant: + <<: *aci_info + tenant: ansible_test + state: present + register: op2_task_pwd_add_tenant + +- name: Delete Tenant with password and session key in the inventory (session_key takes precedence) + cisco.aci.aci_tenant: + output_level: debug + tenant: ansible_test + state: absent + register: op3_inventory_session_key_delete_tenant + +- name: Add Tenant with password and session key in the inventory (Check for execution on the provided aci_hostname with no attempts at re-connection) + cisco.aci.aci_tenant: + output_level: debug + tenant: ansible_test + state: present + register: op3_inventory_session_key_add_tenant + +- name: Remove session key used in the inventory + ansible.builtin.set_fact: + ansible_httpapi_session_key: + +- name: Delete Tenant with with only password in the inventory (check for authentication with the password in the inventory) + cisco.aci.aci_tenant: + output_level: debug + tenant: ansible_test + state: absent + register: op4_inventory_pwd_delete_tenant + +- name: Add Tenant with with only password in the inventory (Check for execution on the provided aci_hostname with no attempts at re-connection) + cisco.aci.aci_tenant: + output_level: debug + tenant: ansible_test + state: present + register: op4_inventory_pwd_add_tenant + +- name: Flatten the registered instances + ansible.builtin.set_fact: + op1_flattened_task_private_key_delete_tenant: "{{ op1_task_private_key_delete_tenant.httpapi_logs | flatten }}" + op1_flattened_task_private_key_add_tenant: "{{ op1_task_private_key_add_tenant.httpapi_logs | flatten }}" + op2_flattened_task_pwd_delete_tenant: "{{ op2_task_pwd_delete_tenant.httpapi_logs | flatten }}" + op2_flattened_task_pwd_add_tenant: "{{ op2_task_pwd_add_tenant.httpapi_logs | flatten }}" + op3_flattened_inventory_session_key_delete_tenant: "{{ op3_inventory_session_key_delete_tenant.httpapi_logs | flatten }}" + op3_flattened_inventory_session_key_add_tenant: "{{ op3_inventory_session_key_add_tenant.httpapi_logs | flatten }}" + op4_flattened_inventory_pwd_delete_tenant: "{{ op4_inventory_pwd_delete_tenant.httpapi_logs | flatten }}" + op4_flattened_inventory_pwd_add_tenant: "{{ op4_inventory_pwd_add_tenant.httpapi_logs | flatten }}" + +- name: Verify Precedence + assert: + that: + - '"Provided Hosts: [''{{ aci_hostname }}'']" in op1_flattened_task_private_key_delete_tenant' + - '"Initializing operation on {{ aci_hostname }}" in op1_flattened_task_private_key_delete_tenant' + - op1_task_private_key_add_tenant is changed + - '"Connection to {{ aci_hostname }} was successful" not in op1_flattened_task_private_key_add_tenant' + - op2_task_pwd_delete_tenant is changed + - '"Re-setting connection due to change from private/session key authentication to password authentication" in op2_flattened_task_pwd_delete_tenant' + - '"Establishing login for {{ aci_username }} to {{ aci_hostname }}" in op2_flattened_task_pwd_delete_tenant' + - '"Connection to {{ aci_hostname }} was successful" in op2_flattened_task_pwd_delete_tenant' + - op2_task_pwd_add_tenant is changed + - '"Connection to {{ aci_hostname }} was successful" not in op2_flattened_task_pwd_add_tenant' + - op3_inventory_session_key_delete_tenant is changed + - '"Connection to {{ aci_hostname }} was successful" not in op3_flattened_inventory_session_key_delete_tenant' + - op3_inventory_session_key_add_tenant is changed + - '"Connection to {{ aci_hostname }} was successful" not in op3_flattened_inventory_session_key_add_tenant' + - op4_inventory_pwd_delete_tenant is changed + - '"Re-setting connection due to change from private/session key authentication to password authentication" in op4_flattened_inventory_pwd_delete_tenant' + - '"Establishing login for {{ aci_username }} to {{ aci_hostname }}" in op4_flattened_inventory_pwd_delete_tenant' + - '"Connection to {{ aci_hostname }} was successful" in op4_flattened_inventory_pwd_delete_tenant' + - op4_inventory_pwd_add_tenant is changed + - '"Connection to {{ aci_hostname }} was successful" not in op4_flattened_inventory_pwd_add_tenant' + +- name: reset connection to test other scenarios + meta: reset_connection + +# Switching of hosts test with the password in the task +- name: Delete Tenant with only password in the task (Check for successful operation on the last host) + cisco.aci.aci_tenant: + <<: *aci_info + host: "{{ last_host_active }}" + tenant: ansible_test + state: absent + register: op5_task_pwd_delete_tenant + +- name: Add Tenant with only password in the task (Check for execution on the provided aci_hostname with no attempts at re-connection) + cisco.aci.aci_tenant: + <<: *aci_info + host: "{{ last_host_active }}" + tenant: ansible_test + state: present + register: op5_task_pwd_add_tenant + +- name: Flatten the registered instances + ansible.builtin.set_fact: + op5_flattened_task_pwd_delete_tenant: "{{ op5_task_pwd_delete_tenant.httpapi_logs | flatten }}" + op5_flattened_task_pwd_add_tenant: "{{ op5_task_pwd_add_tenant.httpapi_logs | flatten }}" + +- name: Verify switching of hosts with the password in the task + assert: + that: + - op5_task_pwd_delete_tenant is changed + - op5_flattened_task_pwd_delete_tenant | regex_search('Switching host from [0-9]+(?:\.[0-9]+){3} to {{ aci_hostname }}') is not none + - '"Establishing login for {{ aci_username }} to {{ aci_hostname }}" in op5_flattened_task_pwd_delete_tenant' + - '"Connection to {{ aci_hostname }} was successful" in op5_flattened_task_pwd_delete_tenant' + - op5_task_pwd_add_tenant is changed + - '"Connection to {{ aci_hostname }} was successful" not in op5_flattened_task_pwd_add_tenant' + +# Continuing on the connected host test with the password in the task +- name: Delete Tenant with only password in the task + cisco.aci.aci_tenant: + <<: *aci_info + host: "{{ second_host_active }}" + tenant: ansible_test + state: absent + register: op6_task_pwd_delete_tenant + +- name: Add Tenant with only password in the task (Check for execution on the provided aci_hostname with no attempts at re-connection) + cisco.aci.aci_tenant: + <<: *aci_info + host: "{{ second_host_active }}" + tenant: ansible_test + state: present + register: op6_task_pwd_add_tenant + +- name: Flatten the registered instances + ansible.builtin.set_fact: + op6_flattened_task_pwd_delete_tenant: "{{ op6_task_pwd_delete_tenant.httpapi_logs | flatten }}" + op6_flattened_task_pwd_add_tenant: "{{ op6_task_pwd_add_tenant.httpapi_logs | flatten }}" + +- name: Verify continuation of the operations on the connected host with the password in the task + assert: + that: + - op6_task_pwd_delete_tenant is changed + - '"Connected host {{ aci_hostname }} found in the provided hosts. Continuing with it." in op6_flattened_task_pwd_delete_tenant' + - op6_task_pwd_add_tenant is changed + - '"Connection to {{ aci_hostname }} was successful" not in op6_flattened_task_pwd_add_tenant' + +# Change of hosts and no hosts active test with the password in the task +- name: Delete Tenant with only password in the task (Check for failed operation) + cisco.aci.aci_tenant: + <<: *aci_info + host: "{{ no_host_active }}" + tenant: ansible_test + state: absent + register: op7_task_pwd_delete_tenant + ignore_errors: True + +- name: Add Tenant with only password in the task (Check the reset of the provided list of hosts) + cisco.aci.aci_tenant: + <<: *aci_info + host: "{{ second_host_active }}" + tenant: ansible_test + state: present + register: op7_task_pwd_add_tenant + +- name: Verify failure when no hosts are active + assert: + that: + - op7_task_pwd_delete_tenant.error.text | regex_search('No hosts left in the cluster to continue operation! Error on final host [0-9]+(?:\.[0-9]+){3}') is not none + - op7_task_pwd_add_tenant is not changed + +# Switching of hosts test with the the inventory password +- name: Set list of hosts in the inventory with the last host as active + ansible.builtin.set_fact: + ansible_host: "{{aci_hostname|generate_random_ips(5,5)}}" + +- name: Delete Tenant with only password in the inventory (Check for successful operation on the last host) + cisco.aci.aci_tenant: + output_level: debug + tenant: ansible_test + state: absent + register: op8_inventory_pwd_delete_tenant + +- name: Add Tenant with only password in the inventory (Check for execution on the provided aci_hostname with no attempts at re-connection) + cisco.aci.aci_tenant: + output_level: debug + tenant: ansible_test + state: present + register: op8_inventory_pwd_add_tenant + +- name: Flatten the registered instances + ansible.builtin.set_fact: + op8_flattened_inventory_pwd_delete_tenant: "{{ op8_inventory_pwd_delete_tenant.httpapi_logs | flatten }}" + op8_flattened_inventory_pwd_add_tenant: "{{ op8_inventory_pwd_add_tenant.httpapi_logs | flatten }}" + +- name: Verify switching of hosts with the password in the inventory + assert: + that: + - op8_inventory_pwd_delete_tenant is changed + - op8_flattened_inventory_pwd_delete_tenant | regex_search('Switching host from [0-9]+(?:\.[0-9]+){3} to {{ aci_hostname }}') is not none + - '"Establishing login for {{ aci_username }} to {{ aci_hostname }}" in op8_flattened_inventory_pwd_delete_tenant' + - '"Connection to {{ aci_hostname }} was successful" in op8_flattened_inventory_pwd_delete_tenant' + - op8_inventory_pwd_add_tenant is changed + - '"Connection to {{ aci_hostname }} was successful" not in op8_flattened_inventory_pwd_add_tenant' + +# Continuing on the connected host test with the inventory password +- name: Set list of hosts in the inventory with the second host as active + ansible.builtin.set_fact: + ansible_host: "{{aci_hostname|generate_random_ips(2,5)}}" + +- name: Delete Tenant with only password in the inventory (Check for execution on the previously connected host) + cisco.aci.aci_tenant: + output_level: debug + tenant: ansible_test + state: absent + register: op9_inventory_pwd_delete_tenant + +- name: Add Tenant with only password in the inventory (Check for execution on the provided aci_hostname with no attempts at re-connection) + cisco.aci.aci_tenant: + output_level: debug + tenant: ansible_test + state: present + register: op9_inventory_pwd_add_tenant + +- name: Flatten the registered instances + ansible.builtin.set_fact: + op9_flattened_inventory_pwd_delete_tenant: "{{ op9_inventory_pwd_delete_tenant.httpapi_logs | flatten }}" + op9_flattened_inventory_pwd_add_tenant: "{{ op9_inventory_pwd_add_tenant.httpapi_logs | flatten }}" + +- name: Verify switching of hosts with the password in the inventory + assert: + that: + - op9_inventory_pwd_delete_tenant is changed + - op9_flattened_inventory_pwd_delete_tenant | regex_search('Switching host from [0-9]+(?:\.[0-9]+){3} to {{ aci_hostname }}') is not none + - '"Establishing login for {{ aci_username }} to {{ aci_hostname }}" in op9_flattened_inventory_pwd_delete_tenant' + - '"Connection to {{ aci_hostname }} was successful" in op9_flattened_inventory_pwd_delete_tenant' + - op9_inventory_pwd_add_tenant is changed + - '"Connection to {{ aci_hostname }} was successful" not in op9_flattened_inventory_pwd_add_tenant' + +# Change of hosts and no hosts active test with the inventory password +- name: Set list of hosts in the inventory with no active hosts + ansible.builtin.set_fact: + ansible_host: "{{aci_hostname|generate_random_ips(0,5)}}" + +- name: Delete Tenant with only password in the inventory (Check for failed operation) + cisco.aci.aci_tenant: + output_level: debug + tenant: ansible_test + state: absent + register: op10_inventory_pwd_delete_tenant + ignore_errors: True + +- name: Verify failure when no hosts are active in the inventory + assert: + that: + - op10_inventory_pwd_delete_tenant.error.text | regex_search('No hosts left in the cluster to continue operation! Error on final host [0-9]+(?:\.[0-9]+){3}') is not none + +# Switching of hosts test with the private key in the task +- name: Delete Tenant with only private key in the task (Check for successful operation on the last host) + cisco.aci.aci_tenant: + <<: *aci_info + host: "{{ last_host_active }}" + private_key: "{{ lookup('file', 'pki/admin.key') }}" + tenant: ansible_test + state: absent + register: op11_task_private_key_delete_tenant + +- name: Add Tenant with only private key in the task (Check for execution on the provided aci_hostname with no attempts at re-connection) + cisco.aci.aci_tenant: + <<: *aci_info + host: "{{ last_host_active }}" + private_key: "{{ lookup('file', 'pki/admin.key') }}" + tenant: ansible_test + state: present + register: op11_task_private_key_add_tenant + +- name: Flatten the registered instances + ansible.builtin.set_fact: + op11_flattened_task_private_key_delete_tenant: "{{ op11_task_private_key_delete_tenant.httpapi_logs | flatten }}" + op11_flattened_task_private_key_add_tenant: "{{ op11_task_private_key_add_tenant.httpapi_logs | flatten }}" + +- name: Verify switching of hosts with the private key in the task + assert: + that: + - op11_task_private_key_delete_tenant is changed + - op11_flattened_task_private_key_delete_tenant | regex_search('Switching host from [0-9]+(?:\.[0-9]+){3} to {{ aci_hostname }}') is not none + - '"Establishing login for {{ aci_username }} to {{ aci_hostname }}" not in op11_flattened_task_private_key_delete_tenant' + - '"Connection to {{ aci_hostname }} was successful" not in op11_flattened_task_private_key_delete_tenant' + - op11_task_private_key_add_tenant is changed + - '"Connection to {{ aci_hostname }} was successful" not in op11_flattened_task_private_key_add_tenant' + +# Continuing on the connected host test with the private key in the task +- name: Delete Tenant with only private key in the task (Check for execution on the previously connected host) + cisco.aci.aci_tenant: + <<: *aci_info + host: "{{ second_host_active }}" + private_key: "{{ lookup('file', 'pki/admin.key') }}" + tenant: ansible_test + state: absent + register: op12_task_private_key_delete_tenant + +- name: Add Tenant with only private key in the task (Check for execution on the provided aci_hostname with no attempts at re-connection) + cisco.aci.aci_tenant: + <<: *aci_info + host: "{{ second_host_active }}" + private_key: "{{ lookup('file', 'pki/admin.key') }}" + tenant: ansible_test + state: present + register: op12_task_private_key_add_tenant + +- name: Flatten the registered instances + ansible.builtin.set_fact: + op12_flattened_task_private_key_delete_tenant: "{{ op12_task_private_key_delete_tenant.httpapi_logs | flatten }}" + op12_flattened_task_private_key_add_tenant: "{{ op12_task_private_key_add_tenant.httpapi_logs | flatten }}" + +- name: Verify continuation of the operations on the connected host with the password in the task + assert: + that: + - op12_task_private_key_delete_tenant is changed + - '"Connected host {{ aci_hostname }} found in the provided hosts. Continuing with it." in op12_flattened_task_private_key_delete_tenant' + - op12_task_private_key_add_tenant is changed + - '"Connection to {{ aci_hostname }} was successful" not in op12_flattened_task_private_key_add_tenant' + +# Change of hosts and no hosts active test with the private key in the task +- name: Delete Tenant with only private key in the task (Check for failed operation) + cisco.aci.aci_tenant: + <<: *aci_info + host: "{{ no_host_active }}" + private_key: "{{ lookup('file', 'pki/admin.key') }}" + tenant: ansible_test + state: absent + register: op13_task_private_key_delete_tenant + ignore_errors: True + +- name: Add Tenant with only private key in the task (Check the reset of the provided list of hosts) + cisco.aci.aci_tenant: + <<: *aci_info + host: "{{ second_host_active }}" + private_key: "{{ lookup('file', 'pki/admin.key') }}" + tenant: ansible_test + state: present + register: op13_task_private_key_add_tenant + +- name: Verify failure when no hosts are active in the task + assert: + that: + - op13_task_private_key_delete_tenant.error.text | regex_search('No hosts left in the cluster to continue operation! Error on final host [0-9]+(?:\.[0-9]+){3}') is not none + - op13_task_private_key_add_tenant is not changed + +# Switching of hosts test with the the inventory session key +- name: Set list of hosts in the inventory with the last host as active + ansible.builtin.set_fact: + ansible_host: "{{aci_hostname|generate_random_ips(5,5)}}" + ansible_httpapi_session_key: {'admin': "{{ lookup('file', 'pki/admin.key') }}"} + +- name: Delete Tenant with session key in the inventory (Check for successful operation on the last host) + cisco.aci.aci_tenant: + output_level: debug + tenant: ansible_test + state: absent + register: op14_inventory_session_key_delete_tenant + +- name: Add Tenant with session key in the inventory (Check for execution on the provided aci_hostname with no attempts at re-connection) + cisco.aci.aci_tenant: + output_level: debug + tenant: ansible_test + state: present + register: op14_inventory_session_key_add_tenant + +- name: Flatten the registered instances + ansible.builtin.set_fact: + op14_flattened_inventory_session_key_delete_tenant: "{{ op14_inventory_session_key_delete_tenant.httpapi_logs | flatten }}" + op14_flattened_inventory_session_key_add_tenant: "{{ op14_inventory_session_key_add_tenant.httpapi_logs | flatten }}" + +- name: Verify switching of hosts with the session key in the inventory + assert: + that: + - op14_inventory_session_key_delete_tenant is changed + - op14_flattened_inventory_session_key_delete_tenant | regex_search('Switching host from [0-9]+(?:\.[0-9]+){3} to {{ aci_hostname }}') is not none + - '"Establishing login for {{ aci_username }} to {{ aci_hostname }}" not in op14_flattened_inventory_session_key_delete_tenant' + - '"Connection to {{ aci_hostname }} was successful" not in op14_flattened_inventory_session_key_delete_tenant' + - op14_inventory_session_key_add_tenant is changed + - '"Connection to {{ aci_hostname }} was successful" not in op14_flattened_inventory_session_key_add_tenant' + +# Continuing on the connected host test with the inventory session key +- name: Set list of hosts in the inventory with the second host as active + ansible.builtin.set_fact: + ansible_host: "{{aci_hostname|generate_random_ips(2,5)}}" + +- name: Delete Tenant with session key in the inventory (Check for execution on the previously connected host) + cisco.aci.aci_tenant: + output_level: debug + tenant: ansible_test + state: absent + register: op15_inventory_session_key_delete_tenant + +- name: Add Tenant with session key in the inventory (Check for execution on the provided aci_hostname with no attempts at re-connection) + cisco.aci.aci_tenant: + output_level: debug + tenant: ansible_test + state: present + register: op15_inventory_session_key_add_tenant + +- name: Flatten the registered instances + ansible.builtin.set_fact: + op15_flattened_inventory_session_key_delete_tenant: "{{ op15_inventory_session_key_delete_tenant.httpapi_logs | flatten }}" + op15_flattened_inventory_session_key_add_tenant: "{{ op15_inventory_session_key_add_tenant.httpapi_logs | flatten }}" + +- name: Verify switching of hosts with the session key in the inventory + assert: + that: + - op15_inventory_session_key_delete_tenant is changed + - op15_flattened_inventory_session_key_delete_tenant | regex_search('Switching host from [0-9]+(?:\.[0-9]+){3} to {{ aci_hostname }}') is not none + - '"Establishing login for {{ aci_username }} to {{ aci_hostname }}" not in op15_flattened_inventory_session_key_delete_tenant' + - '"Connection to {{ aci_hostname }} was successful" not in op15_flattened_inventory_session_key_delete_tenant' + - op15_inventory_session_key_add_tenant is changed + - '"Connection to {{ aci_hostname }} was successful" not in op15_flattened_inventory_session_key_add_tenant' + +# Change of hosts and no hosts active test with the inventory session key +- name: Set list of hosts in the inventory with no active hosts + ansible.builtin.set_fact: + ansible_host: "{{aci_hostname|generate_random_ips(0,5)}}" + +- name: Delete Tenant with session key in the inventory (Check for failed operation) + cisco.aci.aci_tenant: + output_level: debug + tenant: ansible_test + state: absent + register: op16_inventory_session_key_delete_tenant + ignore_errors: True + +- name: Verify failure when no hosts are active in the inventory + assert: + that: + - op16_inventory_session_key_delete_tenant.error.text | regex_search('No hosts left in the cluster to continue operation! Error on final host [0-9]+(?:\.[0-9]+){3}') is not none + +# Clean up Environment +- name: Delete a tenant using an XML string + cisco.aci.aci_rest: + <<: *aci_info + path: api/mo/uni/tn-[Sales].xml + method: delete + content: '<fvTenant name="Sales" descr="Sales departement"/>' + +- name: Remove user certificate + cisco.aci.aci_aaa_user_certificate: + <<: *aci_info + aaa_user: "{{ aci_username }}" + name: admin + certificate: "{{ lookup('file', 'pki/admin.crt') }}" + state: absent + +- name: Delete Tenant + cisco.aci.aci_tenant: + <<: *aci_info + port: 443 + tenant: ansible_test + state: absent + +- name: Cleanup facts to continue operation using the inventory file + ansible.builtin.set_fact: + ansible_host: "{{ aci_hostname }}" + ansible_connection: "{{ old_connection }}" + ansible_httpapi_session_key: diff --git a/ansible_collections/cisco/aci/tests/integration/targets/aci_tenant/tasks/main.yml b/ansible_collections/cisco/aci/tests/integration/targets/aci_tenant/tasks/main.yml index f3eba556e..b8a7a310f 100644 --- a/ansible_collections/cisco/aci/tests/integration/targets/aci_tenant/tasks/main.yml +++ b/ansible_collections/cisco/aci/tests/integration/targets/aci_tenant/tasks/main.yml @@ -9,23 +9,26 @@ msg: 'Please define the following variables: aci_hostname, aci_username and aci_password.' when: aci_hostname is not defined or aci_username is not defined or aci_password is not defined +- include_tasks: httpapi_connection.yml + tags: httpapi_connection + - name: Delete old log files to clean test directory file: path: "{{ item }}" state: absent with_items: - - "{{ ansible_host }}_cm_add_tenant.json" - - "{{ ansible_host }}_nm_add_tenant.json" - - "{{ ansible_host }}_cm_add_tenant_again.json" - - "{{ ansible_host }}_nm_add_tenant_again.json" - - "{{ ansible_host }}_cm_add_tenant_descr.json" - - "{{ ansible_host }}_nm_add_tenant_descr.json" - - "{{ ansible_host }}_cm_add_tenant_descr_again.json" - - "{{ ansible_host }}_nm_add_tenant_descr_again.json" - - "{{ ansible_host }}_cm_remove_tenant.json" - - "{{ ansible_host }}_nm_remove_tenant.json" - - "{{ ansible_host }}_cm_remove_tenant_again.json" - - "{{ ansible_host }}_nm_remove_tenant_again.json" + - "{{ aci_hostname }}_cm_add_tenant.json" + - "{{ aci_hostname }}_nm_add_tenant.json" + - "{{ aci_hostname }}_cm_add_tenant_again.json" + - "{{ aci_hostname }}_nm_add_tenant_again.json" + - "{{ aci_hostname }}_cm_add_tenant_descr.json" + - "{{ aci_hostname }}_nm_add_tenant_descr.json" + - "{{ aci_hostname }}_cm_add_tenant_descr_again.json" + - "{{ aci_hostname }}_nm_add_tenant_descr_again.json" + - "{{ aci_hostname }}_cm_remove_tenant.json" + - "{{ aci_hostname }}_nm_remove_tenant.json" + - "{{ aci_hostname }}_cm_remove_tenant_again.json" + - "{{ aci_hostname }}_nm_remove_tenant_again.json" # CLEAN ENVIRONMENT - name: Remove tenant @@ -40,7 +43,6 @@ tenant: ansible_test state: absent - # ADD TENANT - name: Add tenant (check_mode) cisco.aci.aci_tenant: &tenant_present @@ -56,44 +58,44 @@ annotation: ansible_test owner_key: ansible_key owner_tag: ansible_tag - output_path: "{{ ansible_host }}_cm_add_tenant.json" + output_path: "{{ aci_hostname }}_cm_add_tenant.json" check_mode: true register: cm_add_tenant - name: Dump content of files debug: - msg: "{{ lookup('file', ansible_host +'_cm_add_tenant.json')}}" + msg: "{{ lookup('file', aci_hostname +'_cm_add_tenant.json')}}" - name: Add tenant (normal mode) cisco.aci.aci_tenant: <<: *tenant_present - output_path: "{{ ansible_host }}_nm_add_tenant.json" + output_path: "{{ aci_hostname }}_nm_add_tenant.json" register: nm_add_tenant - name: Add tenant again (check_mode) cisco.aci.aci_tenant: <<: *tenant_present - output_path: "{{ ansible_host }}_cm_add_tenant_again.json" + output_path: "{{ aci_hostname }}_cm_add_tenant_again.json" check_mode: true register: cm_add_tenant_again - name: Add tenant again (normal mode) cisco.aci.aci_tenant: <<: *tenant_present - output_path: "{{ ansible_host }}_nm_add_tenant_again.json" + output_path: "{{ aci_hostname }}_nm_add_tenant_again.json" register: nm_add_tenant_again - name: Dump content of files debug: - msg: "{{ lookup('file', ansible_host + '_cm_add_tenant.json')}}" + msg: "{{ lookup('file', aci_hostname + '_cm_add_tenant.json')}}" - name: Store file content on variables for create object set_fact: - fc_cm_add_tenant: "{{ lookup('file', ansible_host + '_cm_add_tenant.json') | from_json }}" - fc_nm_add_tenant: "{{ lookup('file', ansible_host + '_nm_add_tenant.json') | from_json }}" - fc_cm_add_tenant_again: "{{ lookup('file', ansible_host + '_cm_add_tenant_again.json') }}" - fc_nm_add_tenant_again: "{{ lookup('file', ansible_host + '_nm_add_tenant_again.json') }}" + fc_cm_add_tenant: "{{ lookup('file', aci_hostname + '_cm_add_tenant.json') | from_json }}" + fc_nm_add_tenant: "{{ lookup('file', aci_hostname + '_nm_add_tenant.json') | from_json }}" + fc_cm_add_tenant_again: "{{ lookup('file', aci_hostname + '_cm_add_tenant_again.json') }}" + fc_nm_add_tenant_again: "{{ lookup('file', aci_hostname + '_nm_add_tenant_again.json') }}" - name: Log file content verification for create object assert: @@ -119,7 +121,6 @@ - nm_add_tenant.current[0].fvTenant.attributes.ownerTag == 'ansible_tag' - cm_add_tenant.proposed.fvTenant.attributes.ownerTag == 'ansible_tag' - # CHANGE TENANT - name: Change description and annotation/owner_tag/owner_key of tenant (check_mode) cisco.aci.aci_tenant: @@ -128,7 +129,7 @@ annotation: ansible_test_changed owner_key: ansible_key_changed owner_tag: ansible_tag_changed - output_path: "{{ ansible_host }}_cm_add_tenant_descr.json" + output_path: "{{ aci_hostname }}_cm_add_tenant_descr.json" check_mode: true register: cm_add_tenant_descr @@ -139,7 +140,7 @@ annotation: ansible_test_changed owner_key: ansible_key_changed owner_tag: ansible_tag_changed - output_path: "{{ ansible_host }}_nm_add_tenant_descr.json" + output_path: "{{ aci_hostname }}_nm_add_tenant_descr.json" register: nm_add_tenant_descr - name: Change description and annotation/owner_tag/owner_key of tenant again (check_mode) @@ -149,7 +150,7 @@ annotation: ansible_test_changed owner_key: ansible_key_changed owner_tag: ansible_tag_changed - output_path: "{{ ansible_host }}_cm_add_tenant_descr_again.json" + output_path: "{{ aci_hostname }}_cm_add_tenant_descr_again.json" check_mode: true register: cm_add_tenant_descr_again @@ -160,15 +161,15 @@ annotation: ansible_test_changed owner_key: ansible_key_changed owner_tag: ansible_tag_changed - output_path: "{{ ansible_host }}_nm_add_tenant_descr_again.json" + output_path: "{{ aci_hostname }}_nm_add_tenant_descr_again.json" register: nm_add_tenant_descr_again - name: Store file content on variables for update object set_fact: - fc_cm_add_tenant_descr: "{{ lookup('file', ansible_host + '_cm_add_tenant_descr.json') | from_json }}" - fc_nm_add_tenant_descr: "{{ lookup('file', ansible_host + '_nm_add_tenant_descr.json') | from_json }}" - fc_cm_add_tenant_descr_again: "{{ lookup('file', ansible_host + '_cm_add_tenant_descr_again.json') }}" - fc_nm_add_tenant_descr_again: "{{ lookup('file', ansible_host + '_nm_add_tenant_descr_again.json') }}" + fc_cm_add_tenant_descr: "{{ lookup('file', aci_hostname + '_cm_add_tenant_descr.json') | from_json }}" + fc_nm_add_tenant_descr: "{{ lookup('file', aci_hostname + '_nm_add_tenant_descr.json') | from_json }}" + fc_cm_add_tenant_descr_again: "{{ lookup('file', aci_hostname + '_cm_add_tenant_descr_again.json') }}" + fc_nm_add_tenant_descr_again: "{{ lookup('file', aci_hostname + '_nm_add_tenant_descr_again.json') }}" - name: Log file content verification for update object assert: @@ -295,35 +296,35 @@ - name: Remove tenant (check_mode) cisco.aci.aci_tenant: <<: *tenant_absent - output_path: "{{ ansible_host }}_cm_remove_tenant.json" + output_path: "{{ aci_hostname }}_cm_remove_tenant.json" check_mode: true register: cm_remove_tenant - name: Remove tenant (normal mode) cisco.aci.aci_tenant: <<: *tenant_absent - output_path: "{{ ansible_host }}_nm_remove_tenant.json" + output_path: "{{ aci_hostname }}_nm_remove_tenant.json" register: nm_remove_tenant - name: Remove tenant again (check_mode) cisco.aci.aci_tenant: <<: *tenant_absent - output_path: "{{ ansible_host }}_cm_remove_tenant_again.json" + output_path: "{{ aci_hostname }}_cm_remove_tenant_again.json" check_mode: true register: cm_remove_tenant_again - name: Remove tenant again (normal mode) cisco.aci.aci_tenant: <<: *tenant_absent - output_path: "{{ ansible_host }}_nm_remove_tenant_again.json" + output_path: "{{ aci_hostname }}_nm_remove_tenant_again.json" register: nm_remove_tenant_again - name: Store file content on variables for delete object set_fact: - fc_cm_remove_tenant: "{{ lookup('file', ansible_host + '_cm_remove_tenant.json') | from_json }}" - fc_nm_remove_tenant: "{{ lookup('file', ansible_host + '_nm_remove_tenant.json') | from_json }}" - fc_cm_remove_tenant_again: "{{ lookup('file', ansible_host + '_cm_remove_tenant_again.json') }}" - fc_nm_remove_tenant_again: "{{ lookup('file', ansible_host + '_nm_remove_tenant_again.json') }}" + fc_cm_remove_tenant: "{{ lookup('file', aci_hostname + '_cm_remove_tenant.json') | from_json }}" + fc_nm_remove_tenant: "{{ lookup('file', aci_hostname + '_nm_remove_tenant.json') | from_json }}" + fc_cm_remove_tenant_again: "{{ lookup('file', aci_hostname + '_cm_remove_tenant_again.json') }}" + fc_nm_remove_tenant_again: "{{ lookup('file', aci_hostname + '_nm_remove_tenant_again.json') }}" - name: Log file content verification for delete object assert: diff --git a/ansible_collections/cisco/aci/tests/integration/targets/aci_tenant_action_rule_profile/aliases b/ansible_collections/cisco/aci/tests/integration/targets/aci_tenant_action_rule_profile/aliases new file mode 100644 index 000000000..209b793f9 --- /dev/null +++ b/ansible_collections/cisco/aci/tests/integration/targets/aci_tenant_action_rule_profile/aliases @@ -0,0 +1,2 @@ +# No ACI simulator yet, so not enabled +# unsupported diff --git a/ansible_collections/cisco/aci/tests/integration/targets/aci_tenant_action_rule_profile/tasks/main.yml b/ansible_collections/cisco/aci/tests/integration/targets/aci_tenant_action_rule_profile/tasks/main.yml new file mode 100644 index 000000000..b993b0e33 --- /dev/null +++ b/ansible_collections/cisco/aci/tests/integration/targets/aci_tenant_action_rule_profile/tasks/main.yml @@ -0,0 +1,146 @@ +# Test code for the ACI modules +# Copyright: (c) 2023, Gaspard Micol (@gmicol) <gmicol@cisco.com> + +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +- name: Test that we have an ACI APIC host, ACI username and ACI password + fail: + msg: 'Please define the following variables: aci_hostname, aci_username and aci_password.' + when: aci_hostname is not defined or aci_username is not defined or aci_password is not defined + +- name: Set vars + set_fact: + aci_info: &aci_info + host: "{{ aci_hostname }}" + username: "{{ aci_username }}" + password: "{{ aci_password }}" + validate_certs: '{{ aci_validate_certs | default(false) }}' + use_ssl: '{{ aci_use_ssl | default(true) }}' + use_proxy: '{{ aci_use_proxy | default(true) }}' + output_level: debug + +- name: Verify Cloud and Non-Cloud Sites in use. + include_tasks: ../../../../../../integration/targets/aci_cloud_provider/tasks/main.yml + +- name: Execute tasks only for non-cloud sites + when: query_cloud.current == [] # This condition will execute only non-cloud sites + block: # block specifies execution of tasks within, based on conditions + # Clean Environment + - name: create tenant + aci_tenant: &aci_tenant_present + <<: *aci_info + tenant: ansible_test + state: present + + - name: Ensure first action rule profile does not exist + aci_tenant_action_rule_profile: &aci_tenant_action_rule_profile_absent + <<: *aci_tenant_present + name: anstest + description: test for action rule profile + state: absent + + - name: Ensure second action rule profile does not exist + aci_tenant_action_rule_profile: &aci_tenant_action_rule_profile_2_absent + <<: *aci_tenant_present + name: anstest_2 + state: absent + + - name: Create first action rule profile (check_mode) + aci_tenant_action_rule_profile: &aci_tenant_action_rule_profile_present + <<: *aci_tenant_action_rule_profile_absent + state: present + check_mode: true + register: cm_add_tenant_action_rule_profile_1 + + - name: Create first action rule profile (normal_mode) + aci_tenant_action_rule_profile: + <<: *aci_tenant_action_rule_profile_present + register: nm_add_tenant_action_rule_profile_1 + + - name: Create first action rule profile again - testing idempotency + aci_tenant_action_rule_profile: + <<: *aci_tenant_action_rule_profile_present + register: idempotency_add_tenant_action_rule_profile_1 + + - name: Create second action rule profile + aci_tenant_action_rule_profile: &aci_tenant_action_rule_profile_2_present + <<: *aci_tenant_action_rule_profile_2_absent + state: present + register: nm_add_tenant_action_rule_profile_2 + + - name: Asserts for creation tasks + assert: + that: + - cm_add_tenant_action_rule_profile_1 is changed + - cm_add_tenant_action_rule_profile_1.previous == [] + - cm_add_tenant_action_rule_profile_1.current == [] + - nm_add_tenant_action_rule_profile_1 is changed + - nm_add_tenant_action_rule_profile_1.current.0.rtctrlAttrP.attributes.name == "anstest" + - idempotency_add_tenant_action_rule_profile_1 is not changed + - nm_add_tenant_action_rule_profile_2 is changed + - nm_add_tenant_action_rule_profile_2.current.0.rtctrlAttrP.attributes.name == "anstest_2" + + - name: Query all action rule profiles + aci_tenant_action_rule_profile: + <<: *aci_info + state: query + register: query_all_tenant_action_rule_profile + + - name: Query first action rule profile + aci_tenant_action_rule_profile: + <<: *aci_tenant_action_rule_profile_present + state: query + register: query_first_tenant_action_rule_profile + + - name: Asserts for query tasks + assert: + that: + - query_all_tenant_action_rule_profile is not changed + - query_all_tenant_action_rule_profile.current | length >= 2 + - '"class/rtctrlAttrP.json" in query_all_tenant_action_rule_profile.url' + - query_first_tenant_action_rule_profile is not changed + - query_first_tenant_action_rule_profile.current.0.rtctrlAttrP.attributes.name == "anstest" + + - name: Delete first action rule profile (check_mode) + aci_tenant_action_rule_profile: + <<: *aci_tenant_action_rule_profile_present + state: absent + check_mode: true + register: cm_delete_tenant_action_rule_profile_1 + + - name: Delete first action rule profile (normal_mode) + aci_tenant_action_rule_profile: + <<: *aci_tenant_action_rule_profile_present + state: absent + register: nm_delete_tenant_action_rule_profile_1 + + - name: Delete first action rule profile again - testing idempotency + aci_tenant_action_rule_profile: + <<: *aci_tenant_action_rule_profile_present + state: absent + register: idempotency_delete_tenant_action_rule_profile_1 + + - name: Delete second action rule profile (normal_mode) + aci_tenant_action_rule_profile: + <<: *aci_tenant_action_rule_profile_2_present + state: absent + register: nm_delete_tenant_action_rule_profile_2 + + - name: Asserts for deletion tasks + assert: + that: + - cm_delete_tenant_action_rule_profile_1 is changed + - cm_delete_tenant_action_rule_profile_1.proposed == {} + - nm_delete_tenant_action_rule_profile_1 is changed + - nm_delete_tenant_action_rule_profile_1.previous != [] + - nm_delete_tenant_action_rule_profile_1.current == [] + - idempotency_delete_tenant_action_rule_profile_1 is not changed + - idempotency_delete_tenant_action_rule_profile_1.previous == [] + - nm_delete_tenant_action_rule_profile_2 is changed + - nm_delete_tenant_action_rule_profile_2.previous != [] + - nm_delete_tenant_action_rule_profile_2.current == [] + + - name: Delete tenant - clean up the environment + aci_tenant: + <<: *aci_tenant_present + state: absent
\ No newline at end of file diff --git a/ansible_collections/cisco/aci/tests/integration/targets/aci_tenant_ep_retention_policy/aliases b/ansible_collections/cisco/aci/tests/integration/targets/aci_tenant_ep_retention_policy/aliases new file mode 100644 index 000000000..209b793f9 --- /dev/null +++ b/ansible_collections/cisco/aci/tests/integration/targets/aci_tenant_ep_retention_policy/aliases @@ -0,0 +1,2 @@ +# No ACI simulator yet, so not enabled +# unsupported diff --git a/ansible_collections/cisco/aci/tests/integration/targets/aci_tenant_ep_retention_policy/tasks/main.yml b/ansible_collections/cisco/aci/tests/integration/targets/aci_tenant_ep_retention_policy/tasks/main.yml new file mode 100644 index 000000000..96ba34077 --- /dev/null +++ b/ansible_collections/cisco/aci/tests/integration/targets/aci_tenant_ep_retention_policy/tasks/main.yml @@ -0,0 +1,214 @@ +# Test code for the ACI modules +# Copyright: (c) 2023, Gaspard Micol (@gmicol) <gmicol@cisco.com> + +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +- name: Test that we have an ACI APIC host, ACI username and ACI password + fail: + msg: 'Please define the following variables: aci_hostname, aci_username and aci_password.' + when: aci_hostname is not defined or aci_username is not defined or aci_password is not defined + +- name: Set vars + set_fact: + aci_info: &aci_info + host: "{{ aci_hostname }}" + username: "{{ aci_username }}" + password: "{{ aci_password }}" + validate_certs: '{{ aci_validate_certs | default(false) }}' + use_ssl: '{{ aci_use_ssl | default(true) }}' + use_proxy: '{{ aci_use_proxy | default(true) }}' + output_level: debug + +- name: Verify Cloud and Non-Cloud Sites in use. + include_tasks: ../../../../../../integration/targets/aci_cloud_provider/tasks/main.yml + +- name: Execute tasks only for non-cloud sites + when: query_cloud.current == [] # This condition will execute only non-cloud sites + block: # block specifies execution of tasks within, based on conditions + # Clean Environment + - name: create tenant + aci_tenant: &aci_tenant_present + <<: *aci_info + tenant: ansible_test + state: present + + - name: Ensure first EP retention protocol policy does not exist + aci_tenant_ep_retention_policy: &aci_tenant_ep_retention_policy_absent + <<: *aci_tenant_present + name: ep_policy_anstest + description: test for EP retention protocol policy + bounce_age: 300 + bounce_trigger: flood + hold_interval: 600 + local_ep_interval: 1800 + remote_ep_interval: 600 + move_frequency: 512 + state: absent + + - name: Ensure second EP retention protocol policy does not exist + aci_tenant_ep_retention_policy: &aci_tenant_ep_retention_policy_2_absent + <<: *aci_tenant_present + name: ep_policy_anstest_2 + bounce_age: 0 + local_ep_interval: 0 + remote_ep_interval: 0 + move_frequency: 0 + state: absent + + - name: Create first EP retention protocol policy (check_mode) + aci_tenant_ep_retention_policy: &aci_tenant_ep_retention_policy_present + <<: *aci_tenant_ep_retention_policy_absent + state: present + check_mode: true + register: cm_add_tenant_ep_retention_policy_1 + + - name: Create first EP retention protocol policy (normal_mode) + aci_tenant_ep_retention_policy: + <<: *aci_tenant_ep_retention_policy_present + register: nm_add_tenant_ep_retention_policy_1 + + - name: Create first EP retention protocol policy again - testing idempotency + aci_tenant_ep_retention_policy: + <<: *aci_tenant_ep_retention_policy_present + register: idempotency_add_tenant_ep_retention_policy_1 + + - name: Create second EP retention protocol policy + aci_tenant_ep_retention_policy: &aci_tenant_ep_retention_policy_2_present + <<: *aci_tenant_ep_retention_policy_2_absent + state: present + register: nm_add_tenant_ep_retention_policy_2 + + - name: Modify EP retention protocol policy bounce age - testing failure message + aci_tenant_ep_retention_policy: + <<: *aci_tenant_ep_retention_policy_present + bounce_age: 100 + ignore_errors: true + register: Modify_tenant_ep_retention_policy_bounce_age + + - name: Modify EP retention protocol policy hold interval - testing failure message + aci_tenant_ep_retention_policy: + <<: *aci_tenant_ep_retention_policy_present + hold_interval: 4 + ignore_errors: true + register: Modify_tenant_ep_retention_policy_hold_interval + + - name: Modify EP retention protocol policy move frequency - testing failure message + aci_tenant_ep_retention_policy: + <<: *aci_tenant_ep_retention_policy_present + move_frequency: 65540 + ignore_errors: true + register: Modify_tenant_ep_retention_policy_move_frequency + + - name: Modify EP retention protocol policy local ep interval - testing failure message + aci_tenant_ep_retention_policy: + <<: *aci_tenant_ep_retention_policy_present + local_ep_interval: 119 + ignore_errors: true + register: Modify_tenant_ep_retention_policy_local_ep_interval + + - name: Modify EP retention protocol policy remote ep interval - testing failure message + aci_tenant_ep_retention_policy: + <<: *aci_tenant_ep_retention_policy_present + remote_ep_interval: 119 + ignore_errors: true + register: Modify_tenant_ep_retention_policy_remote_ep_interval + + - name: Asserts for creation tasks + assert: + that: + - cm_add_tenant_ep_retention_policy_1 is changed + - cm_add_tenant_ep_retention_policy_1.previous == [] + - cm_add_tenant_ep_retention_policy_1.current == [] + - nm_add_tenant_ep_retention_policy_1 is changed + - nm_add_tenant_ep_retention_policy_1.current.0.fvEpRetPol.attributes.name == "ep_policy_anstest" + - nm_add_tenant_ep_retention_policy_1.current.0.fvEpRetPol.attributes.bounceAgeIntvl == "300" + - nm_add_tenant_ep_retention_policy_1.current.0.fvEpRetPol.attributes.bounceTrig == "rarp-flood" + - nm_add_tenant_ep_retention_policy_1.current.0.fvEpRetPol.attributes.holdIntvl == "600" + - nm_add_tenant_ep_retention_policy_1.current.0.fvEpRetPol.attributes.localEpAgeIntvl == "1800" + - nm_add_tenant_ep_retention_policy_1.current.0.fvEpRetPol.attributes.moveFreq == "512" + - nm_add_tenant_ep_retention_policy_1.current.0.fvEpRetPol.attributes.remoteEpAgeIntvl == "600" + - idempotency_add_tenant_ep_retention_policy_1 is not changed + - nm_add_tenant_ep_retention_policy_2 is changed + - nm_add_tenant_ep_retention_policy_2.current.0.fvEpRetPol.attributes.bounceAgeIntvl == "infinite" + - nm_add_tenant_ep_retention_policy_2.current.0.fvEpRetPol.attributes.bounceTrig == "protocol" + - nm_add_tenant_ep_retention_policy_2.current.0.fvEpRetPol.attributes.holdIntvl == "300" + - nm_add_tenant_ep_retention_policy_2.current.0.fvEpRetPol.attributes.localEpAgeIntvl == "infinite" + - nm_add_tenant_ep_retention_policy_2.current.0.fvEpRetPol.attributes.moveFreq == "none" + - nm_add_tenant_ep_retention_policy_2.current.0.fvEpRetPol.attributes.remoteEpAgeIntvl == "infinite" + - nm_add_tenant_ep_retention_policy_2.current.0.fvEpRetPol.attributes.name == "ep_policy_anstest_2" + - Modify_tenant_ep_retention_policy_bounce_age.msg == "The bounce_age must be a value of 0 or between 150 and 65535" + - Modify_tenant_ep_retention_policy_hold_interval.msg == "The hold_interval must be a value between 5 and 65535" + - Modify_tenant_ep_retention_policy_move_frequency.msg == "The move_frequency must be a value between 0 and 65535" + - Modify_tenant_ep_retention_policy_local_ep_interval.msg == "The local_ep_interval must be a value of 0 or between 120 and 65535" + - Modify_tenant_ep_retention_policy_remote_ep_interval.msg == "The remote_ep_interval must be a value of 0 or between 120 and 65535" + + - name: Query all EP retention protocol policies + aci_tenant_ep_retention_policy: + <<: *aci_info + state: query + register: query_all_tenant_ep_retention_policy + + - name: Query first EP retention protocol policy + aci_tenant_ep_retention_policy: + <<: *aci_tenant_ep_retention_policy_present + state: query + register: query_first_tenant_ep_retention_policy + + - name: Asserts for query tasks + assert: + that: + - query_all_tenant_ep_retention_policy is not changed + - query_all_tenant_ep_retention_policy.current | length >= 2 + - '"class/fvEpRetPol.json" in query_all_tenant_ep_retention_policy.url' + - query_first_tenant_ep_retention_policy is not changed + - query_first_tenant_ep_retention_policy.current.0.fvEpRetPol.attributes.name == "ep_policy_anstest" + - query_first_tenant_ep_retention_policy.current.0.fvEpRetPol.attributes.bounceAgeIntvl == "300" + - query_first_tenant_ep_retention_policy.current.0.fvEpRetPol.attributes.bounceTrig == "rarp-flood" + - query_first_tenant_ep_retention_policy.current.0.fvEpRetPol.attributes.holdIntvl == "600" + - query_first_tenant_ep_retention_policy.current.0.fvEpRetPol.attributes.localEpAgeIntvl == "1800" + - query_first_tenant_ep_retention_policy.current.0.fvEpRetPol.attributes.moveFreq == "512" + - query_first_tenant_ep_retention_policy.current.0.fvEpRetPol.attributes.remoteEpAgeIntvl == "600" + + - name: Delete first EP retention protocol policy (check_mode) + aci_tenant_ep_retention_policy: + <<: *aci_tenant_ep_retention_policy_present + state: absent + check_mode: true + register: cm_delete_tenant_ep_retention_policy_1 + + - name: Delete first EP retention protocol policy (normal_mode) + aci_tenant_ep_retention_policy: + <<: *aci_tenant_ep_retention_policy_present + state: absent + register: nm_delete_tenant_ep_retention_policy_1 + + - name: Delete first EP retention protocol policy again - testing idempotency + aci_tenant_ep_retention_policy: + <<: *aci_tenant_ep_retention_policy_present + state: absent + register: idempotency_delete_tenant_ep_retention_policy_1 + + - name: Delete second EP retention protocol policy (normal_mode) + aci_tenant_ep_retention_policy: + <<: *aci_tenant_ep_retention_policy_2_present + state: absent + register: nm_delete_tenant_ep_retention_policy_2 + + - name: Asserts for deletion tasks + assert: + that: + - cm_delete_tenant_ep_retention_policy_1 is changed + - cm_delete_tenant_ep_retention_policy_1.proposed == {} + - nm_delete_tenant_ep_retention_policy_1 is changed + - nm_delete_tenant_ep_retention_policy_1.previous != [] + - nm_delete_tenant_ep_retention_policy_1.current == [] + - idempotency_delete_tenant_ep_retention_policy_1 is not changed + - idempotency_delete_tenant_ep_retention_policy_1.previous == [] + - nm_delete_tenant_ep_retention_policy_2 is changed + - nm_delete_tenant_ep_retention_policy_2.previous != [] + - nm_delete_tenant_ep_retention_policy_2.current == [] + + - name: Delete tenant - clean up the environment + aci_tenant: + <<: *aci_tenant_present + state: absent
\ No newline at end of file diff --git a/ansible_collections/cisco/aci/tests/integration/targets/aci_tenant_span_dst_group/tasks/main.yml b/ansible_collections/cisco/aci/tests/integration/targets/aci_tenant_span_dst_group/tasks/main.yml index d7d807ad8..543a25cd5 100644 --- a/ansible_collections/cisco/aci/tests/integration/targets/aci_tenant_span_dst_group/tasks/main.yml +++ b/ansible_collections/cisco/aci/tests/integration/targets/aci_tenant_span_dst_group/tasks/main.yml @@ -26,216 +26,230 @@ tenant: ansible_tenant state: absent -- name: Add a new tenant - aci_tenant: - <<: *aci_info - tenant: ansible_tenant - description: Ansible tenant - state: present - -- name: Add span ansible_group - aci_tenant_span_dst_group: - <<: *aci_info - destination_group: ansible_group - description: Test span - destination_ip: 10.0.0.1 - source_ip: 10.0.2.1 - tenant: ansible_tenant - destination_epg: - tenant: Test1 - ap: ap1 - epg: ep1 - version_enforced: false - span_version: version_1 - ttl: 2 - mtu: 1500 - flow_id: 1 - dscp: "CS1" - state: present - register: add_span1 - -- name: Verify add span - assert: - that: - - add_span1 is changed - - add_span1.current.0.spanDestGrp.attributes.name == "ansible_group" - - add_span1.current.0.spanDestGrp.attributes.descr == "Test span" - - add_span1.current.0.spanDestGrp.attributes.dn == "uni/tn-ansible_tenant/destgrp-ansible_group" - - add_span1.current.0.spanDestGrp.children.0.spanDest.attributes.name == "ansible_group" - - add_span1.current.0.spanDestGrp.children.0.spanDest.children.0.spanRsDestEpg.attributes.dscp == "CS1" - - add_span1.current.0.spanDestGrp.children.0.spanDest.children.0.spanRsDestEpg.attributes.srcIpPrefix == "10.0.2.1" - - add_span1.current.0.spanDestGrp.children.0.spanDest.children.0.spanRsDestEpg.attributes.ip == "10.0.0.1" - - add_span1.current.0.spanDestGrp.children.0.spanDest.children.0.spanRsDestEpg.attributes.ver == "ver1" - - add_span1.current.0.spanDestGrp.children.0.spanDest.children.0.spanRsDestEpg.attributes.mtu == "1500" - - add_span1.current.0.spanDestGrp.children.0.spanDest.children.0.spanRsDestEpg.attributes.flowId == "1" - - add_span1.current.0.spanDestGrp.children.0.spanDest.children.0.spanRsDestEpg.attributes.verEnforced == "no" - - add_span1.current.0.spanDestGrp.children.0.spanDest.children.0.spanRsDestEpg.attributes.ttl == "2" - - add_span1.current.0.spanDestGrp.attributes.annotation == 'orchestrator:ansible' - -- name: Add span ansible_group again - aci_tenant_span_dst_group: - <<: *aci_info - destination_group: ansible_group - description: Test span - destination_ip: 10.0.0.1 - source_ip: 10.0.2.1 - tenant: ansible_tenant - destination_epg: - tenant: Test1 - ap: ap1 - epg: ep1 - version_enforced: false - span_version: version_1 - ttl: 2 - mtu: 1500 - flow_id: 1 - dscp: "CS1" - state: present - register: add_span1_again - -- name: Verify add span again - assert: - that: - - add_span1_again is not changed - -- name: Change span ansible_group's src ip - aci_tenant_span_dst_group: - <<: *aci_info - destination_group: ansible_group2 - description: Test span - destination_ip: 10.0.0.2 - source_ip: 10.0.2.1 - tenant: ansible_tenant - destination_epg: - tenant: Test1 - ap: ap1 - epg: ep1 - version_enforced: false - span_version: version_1 - ttl: 2 - mtu: 1500 - flow_id: 1 - dscp: CS1 - state: present - register: change_span1_ip - -- name: Change span ansible_group's dscp - aci_tenant_span_dst_group: - <<: *aci_info - destination_group: ansible_group2 - description: Test span - destination_ip: 10.0.0.2 - source_ip: 10.0.2.1 - tenant: ansible_tenant - destination_epg: - tenant: Test1 - ap: ap1 - epg: ep1 - version_enforced: false - span_version: version_1 - ttl: 2 - mtu: 1500 - flow_id: 1 - dscp: VA - state: present - register: change_span1_dscp - -- name: Verify changes in span - assert: - that: - - change_span1_ip.current.0.spanDestGrp.children.0.spanDest.children.0.spanRsDestEpg.attributes.ip == "10.0.0.2" - - change_span1_dscp.current.0.spanDestGrp.children.0.spanDest.children.0.spanRsDestEpg.attributes.dscp == "VA" - -- name: Add span ansible_group2 - aci_tenant_span_dst_group: - <<: *aci_info - destination_group: ansible_group2 - description: Test span - destination_ip: 10.0.0.1 - source_ip: 10.0.2.1 - tenant: ansible_tenant - destination_epg: - tenant: Test1 - ap: ap1 - epg: ep1 - version_enforced: true - span_version: version_2 - ttl: 2 - mtu: 1500 - flow_id: 1 - dscp: CS1 - state: present - register: add_span2 - -- name: Verify addition of second span - assert: - that: - - add_span2 is changed - - add_span2.current.0.spanDestGrp.attributes.name == "ansible_group2" - - add_span2.current.0.spanDestGrp.attributes.descr == "Test span" - - add_span2.current.0.spanDestGrp.attributes.dn == "uni/tn-ansible_tenant/destgrp-ansible_group2" - - add_span2.current.0.spanDestGrp.children.0.spanDest.attributes.name == "ansible_group2" - - add_span2.current.0.spanDestGrp.children.0.spanDest.children.0.spanRsDestEpg.attributes.dscp == "CS1" - - add_span2.current.0.spanDestGrp.children.0.spanDest.children.0.spanRsDestEpg.attributes.srcIpPrefix == "10.0.2.1" - - add_span2.current.0.spanDestGrp.children.0.spanDest.children.0.spanRsDestEpg.attributes.ip == "10.0.0.1" - - add_span2.current.0.spanDestGrp.children.0.spanDest.children.0.spanRsDestEpg.attributes.ver == "ver2" - - add_span2.current.0.spanDestGrp.children.0.spanDest.children.0.spanRsDestEpg.attributes.mtu == "1500" - - add_span2.current.0.spanDestGrp.children.0.spanDest.children.0.spanRsDestEpg.attributes.flowId == "1" - - add_span2.current.0.spanDestGrp.children.0.spanDest.children.0.spanRsDestEpg.attributes.verEnforced == "yes" - - add_span2.current.0.spanDestGrp.children.0.spanDest.children.0.spanRsDestEpg.attributes.ttl == "2" - -- name: Query span ansible_group - aci_tenant_span_dst_group: - <<: *aci_info - tenant: ansible_tenant - destination_group: ansible_group - state: query - register: query_span_ansible_group - -- name: Query all span dest groups - aci_tenant_span_dst_group: - <<: *aci_info - state: query - register: query_all_span - -- name: Verify Query of span - assert: - that: - - query_span_ansible_group is not changed - - query_span_ansible_group.current.0.spanDestGrp.attributes.name == "ansible_group" - - query_span_ansible_group.current.0.spanDestGrp.attributes.descr == "Test span" - - query_span_ansible_group.current.0.spanDestGrp.attributes.dn == "uni/tn-ansible_tenant/destgrp-ansible_group" - - query_span_ansible_group.current.0.spanDestGrp.children.0.spanDest.attributes.name == "ansible_group" - - query_span_ansible_group.current.0.spanDestGrp.children.0.spanDest.children.0.spanRsDestEpg.attributes.dscp == "CS1" - - query_span_ansible_group.current.0.spanDestGrp.children.0.spanDest.children.0.spanRsDestEpg.attributes.srcIpPrefix == "10.0.2.1" - - query_span_ansible_group.current.0.spanDestGrp.children.0.spanDest.children.0.spanRsDestEpg.attributes.ip == "10.0.0.1" - - query_span_ansible_group.current.0.spanDestGrp.children.0.spanDest.children.0.spanRsDestEpg.attributes.mtu == "1500" - - query_span_ansible_group.current.0.spanDestGrp.children.0.spanDest.children.0.spanRsDestEpg.attributes.flowId == "1" - - query_span_ansible_group.current.0.spanDestGrp.children.0.spanDest.children.0.spanRsDestEpg.attributes.verEnforced == "no" - - query_span_ansible_group.current.0.spanDestGrp.children.0.spanDest.children.0.spanRsDestEpg.attributes.ttl == "2" - - query_all_span is not changed - - query_all_span | length >= 2 - -- name: Remove span ansible_group - aci_tenant_span_dst_group: - <<: *aci_info - tenant: ansible_tenant - destination_group: ansible_group - state: absent - register: remove_span1 - -- name: Remove span ansible_group2 - aci_tenant_span_dst_group: - <<: *aci_info - tenant: ansible_tenant - destination_group: ansible_group2 - state: absent - register: remove_span2 - -- name: Verify Remove of span - assert: - that: - - remove_span1 is changed - - remove_span1.current == [] - - remove_span2 is changed - - remove_span2.current == [] +- name: Verify Cloud and Non-Cloud Sites in use. + include_tasks: ../../../../../../integration/targets/aci_cloud_provider/tasks/main.yml + +- name: Execute tasks only for non-cloud sites + when: query_cloud.current == [] # This condition will execute only non-cloud sites + block: # block specifies execution of tasks within, based on conditions + + - name: Add a new tenant + aci_tenant: + <<: *aci_info + tenant: ansible_tenant + description: Ansible tenant + state: present + + - name: Add span ansible_group + aci_tenant_span_dst_group: + <<: *aci_info + destination_group: ansible_group + description: Test span + destination_ip: 10.0.0.1 + source_ip: 10.0.2.1 + tenant: ansible_tenant + destination_epg: + tenant: Test1 + ap: ap1 + epg: ep1 + version_enforced: false + span_version: version_1 + ttl: 2 + mtu: 1500 + flow_id: 1 + dscp: "CS1" + state: present + register: add_span1 + + - name: Verify add span + assert: + that: + - add_span1 is changed + - add_span1.current.0.spanDestGrp.attributes.name == "ansible_group" + - add_span1.current.0.spanDestGrp.attributes.descr == "Test span" + - add_span1.current.0.spanDestGrp.attributes.dn == "uni/tn-ansible_tenant/destgrp-ansible_group" + - add_span1.current.0.spanDestGrp.children.0.spanDest.attributes.name == "ansible_group" + - add_span1.current.0.spanDestGrp.children.0.spanDest.children.0.spanRsDestEpg.attributes.dscp == "CS1" + - add_span1.current.0.spanDestGrp.children.0.spanDest.children.0.spanRsDestEpg.attributes.srcIpPrefix == "10.0.2.1" + - add_span1.current.0.spanDestGrp.children.0.spanDest.children.0.spanRsDestEpg.attributes.ip == "10.0.0.1" + - add_span1.current.0.spanDestGrp.children.0.spanDest.children.0.spanRsDestEpg.attributes.ver == "ver1" + - add_span1.current.0.spanDestGrp.children.0.spanDest.children.0.spanRsDestEpg.attributes.mtu == "1500" + - add_span1.current.0.spanDestGrp.children.0.spanDest.children.0.spanRsDestEpg.attributes.flowId == "1" + - add_span1.current.0.spanDestGrp.children.0.spanDest.children.0.spanRsDestEpg.attributes.verEnforced == "no" + - add_span1.current.0.spanDestGrp.children.0.spanDest.children.0.spanRsDestEpg.attributes.ttl == "2" + - add_span1.current.0.spanDestGrp.attributes.annotation == 'orchestrator:ansible' + + - name: Add span ansible_group again + aci_tenant_span_dst_group: + <<: *aci_info + destination_group: ansible_group + description: Test span + destination_ip: 10.0.0.1 + source_ip: 10.0.2.1 + tenant: ansible_tenant + destination_epg: + tenant: Test1 + ap: ap1 + epg: ep1 + version_enforced: false + span_version: version_1 + ttl: 2 + mtu: 1500 + flow_id: 1 + dscp: "CS1" + state: present + register: add_span1_again + + - name: Verify add span again + assert: + that: + - add_span1_again is not changed + + - name: Change span ansible_group's src ip + aci_tenant_span_dst_group: + <<: *aci_info + destination_group: ansible_group2 + description: Test span + destination_ip: 10.0.0.2 + source_ip: 10.0.2.1 + tenant: ansible_tenant + destination_epg: + tenant: Test1 + ap: ap1 + epg: ep1 + version_enforced: false + span_version: version_1 + ttl: 2 + mtu: 1500 + flow_id: 1 + dscp: CS1 + state: present + register: change_span1_ip + + - name: Change span ansible_group's dscp + aci_tenant_span_dst_group: + <<: *aci_info + destination_group: ansible_group2 + description: Test span + destination_ip: 10.0.0.2 + source_ip: 10.0.2.1 + tenant: ansible_tenant + destination_epg: + tenant: Test1 + ap: ap1 + epg: ep1 + version_enforced: false + span_version: version_1 + ttl: 2 + mtu: 1500 + flow_id: 1 + dscp: VA + state: present + register: change_span1_dscp + + - name: Verify changes in span + assert: + that: + - change_span1_ip.current.0.spanDestGrp.children.0.spanDest.children.0.spanRsDestEpg.attributes.ip == "10.0.0.2" + - change_span1_dscp.current.0.spanDestGrp.children.0.spanDest.children.0.spanRsDestEpg.attributes.dscp == "VA" + + - name: Add span ansible_group2 + aci_tenant_span_dst_group: + <<: *aci_info + destination_group: ansible_group2 + description: Test span + destination_ip: 10.0.0.1 + source_ip: 10.0.2.1 + tenant: ansible_tenant + destination_epg: + tenant: Test1 + ap: ap1 + epg: ep1 + version_enforced: true + span_version: version_2 + ttl: 2 + mtu: 1500 + flow_id: 1 + dscp: CS1 + state: present + register: add_span2 + + - name: Verify addition of second span + assert: + that: + - add_span2 is changed + - add_span2.current.0.spanDestGrp.attributes.name == "ansible_group2" + - add_span2.current.0.spanDestGrp.attributes.descr == "Test span" + - add_span2.current.0.spanDestGrp.attributes.dn == "uni/tn-ansible_tenant/destgrp-ansible_group2" + - add_span2.current.0.spanDestGrp.children.0.spanDest.attributes.name == "ansible_group2" + - add_span2.current.0.spanDestGrp.children.0.spanDest.children.0.spanRsDestEpg.attributes.dscp == "CS1" + - add_span2.current.0.spanDestGrp.children.0.spanDest.children.0.spanRsDestEpg.attributes.srcIpPrefix == "10.0.2.1" + - add_span2.current.0.spanDestGrp.children.0.spanDest.children.0.spanRsDestEpg.attributes.ip == "10.0.0.1" + - add_span2.current.0.spanDestGrp.children.0.spanDest.children.0.spanRsDestEpg.attributes.ver == "ver2" + - add_span2.current.0.spanDestGrp.children.0.spanDest.children.0.spanRsDestEpg.attributes.mtu == "1500" + - add_span2.current.0.spanDestGrp.children.0.spanDest.children.0.spanRsDestEpg.attributes.flowId == "1" + - add_span2.current.0.spanDestGrp.children.0.spanDest.children.0.spanRsDestEpg.attributes.verEnforced == "yes" + - add_span2.current.0.spanDestGrp.children.0.spanDest.children.0.spanRsDestEpg.attributes.ttl == "2" + + - name: Query span ansible_group + aci_tenant_span_dst_group: + <<: *aci_info + tenant: ansible_tenant + destination_group: ansible_group + state: query + register: query_span_ansible_group + + - name: Query all span dest groups + aci_tenant_span_dst_group: + <<: *aci_info + state: query + register: query_all_span + + - name: Verify Query of span + assert: + that: + - query_span_ansible_group is not changed + - query_span_ansible_group.current.0.spanDestGrp.attributes.name == "ansible_group" + - query_span_ansible_group.current.0.spanDestGrp.attributes.descr == "Test span" + - query_span_ansible_group.current.0.spanDestGrp.attributes.dn == "uni/tn-ansible_tenant/destgrp-ansible_group" + - query_span_ansible_group.current.0.spanDestGrp.children.0.spanDest.attributes.name == "ansible_group" + - query_span_ansible_group.current.0.spanDestGrp.children.0.spanDest.children.0.spanRsDestEpg.attributes.dscp == "CS1" + - query_span_ansible_group.current.0.spanDestGrp.children.0.spanDest.children.0.spanRsDestEpg.attributes.srcIpPrefix == "10.0.2.1" + - query_span_ansible_group.current.0.spanDestGrp.children.0.spanDest.children.0.spanRsDestEpg.attributes.ip == "10.0.0.1" + - query_span_ansible_group.current.0.spanDestGrp.children.0.spanDest.children.0.spanRsDestEpg.attributes.mtu == "1500" + - query_span_ansible_group.current.0.spanDestGrp.children.0.spanDest.children.0.spanRsDestEpg.attributes.flowId == "1" + - query_span_ansible_group.current.0.spanDestGrp.children.0.spanDest.children.0.spanRsDestEpg.attributes.verEnforced == "no" + - query_span_ansible_group.current.0.spanDestGrp.children.0.spanDest.children.0.spanRsDestEpg.attributes.ttl == "2" + - query_all_span is not changed + - query_all_span | length >= 2 + + - name: Remove span ansible_group + aci_tenant_span_dst_group: + <<: *aci_info + tenant: ansible_tenant + destination_group: ansible_group + state: absent + register: remove_span1 + + - name: Remove span ansible_group2 + aci_tenant_span_dst_group: + <<: *aci_info + tenant: ansible_tenant + destination_group: ansible_group2 + state: absent + register: remove_span2 + + - name: Verify Remove of span + assert: + that: + - remove_span1 is changed + - remove_span1.current == [] + - remove_span2 is changed + - remove_span2.current == [] + + # CLEAN ENVIRONMENT + - name: Remove the ansible_tenant + aci_tenant: + <<: *aci_info + tenant: ansible_tenant + state: absent diff --git a/ansible_collections/cisco/aci/tests/integration/targets/aci_vlan_pool_encap_block/tasks/main.yml b/ansible_collections/cisco/aci/tests/integration/targets/aci_vlan_pool_encap_block/tasks/main.yml index c39259593..5ccf8bfbf 100644 --- a/ansible_collections/cisco/aci/tests/integration/targets/aci_vlan_pool_encap_block/tasks/main.yml +++ b/ansible_collections/cisco/aci/tests/integration/targets/aci_vlan_pool_encap_block/tasks/main.yml @@ -215,6 +215,7 @@ cisco.aci.aci_vlan_pool_encap_block: &aci_encap_block_query_filter <<: *aci_encap_block_query pool: "{{ fake_var | default(omit) }}" + pool_allocation_mode: "{{ fake_var | default(omit) }}" register: encap_block_query_from_to_name - name: Query assertions @@ -313,6 +314,7 @@ <<: *aci_pool_present state: query pool: "{{ fake_var | default(omit) }}" + pool_allocation_mode: "{{ fake_var | default(omit) }}" register: encap_block_query_all - name: Query assertions diff --git a/ansible_collections/cisco/aci/tests/integration/targets/aci_vrf/tasks/main.yml b/ansible_collections/cisco/aci/tests/integration/targets/aci_vrf/tasks/main.yml index fa3f1057c..695f40fe7 100644 --- a/ansible_collections/cisco/aci/tests/integration/targets/aci_vrf/tasks/main.yml +++ b/ansible_collections/cisco/aci/tests/integration/targets/aci_vrf/tasks/main.yml @@ -62,6 +62,7 @@ policy_control_direction: egress preferred_group: enabled match_type: all + ip_data_plane_learning: disabled register: vrf_present_2 - name: create vrf without all necessary params - failure message works @@ -80,6 +81,7 @@ - vrf_present is changed - vrf_present.sent == vrf_present_check_mode.sent - vrf_present.current.0.fvCtx.attributes.annotation == 'orchestrator:ansible' + - vrf_present.current.0.fvCtx.attributes.ipDataPlaneLearning == 'enabled' - vrf_present.previous == [] - vrf_present_idempotent is not changed - vrf_present_idempotent.previous != [] @@ -88,9 +90,11 @@ - vrf_update.sent != vrf_update.proposed - vrf_update.sent.fvCtx.attributes.descr == 'Ansible Test Update' - vrf_update.sent.fvCtx.attributes.pcEnfPref == 'unenforced' + - vrf_update.current.0.fvCtx.attributes.ipDataPlaneLearning == 'enabled' - vrf_present_2.sent.fvCtx.attributes.name == 'anstest2' - vrf_present_2.sent.fvCtx.attributes.pcEnfDir == 'egress' - vrf_present_2.sent.fvCtx.attributes.descr == 'Ansible Test' + - vrf_present_2.current.0.fvCtx.attributes.ipDataPlaneLearning == 'disabled' - vrf_present_2.current.0.fvCtx.children.0.vzAny.attributes.matchT == 'All' - vrf_present_2.current.0.fvCtx.children.0.vzAny.attributes.prefGrMemb == 'enabled' - vrf_present_missing_param is failed diff --git a/ansible_collections/cisco/aci/tests/integration/targets/aci_vrf_leak_internal_subnet/aliases b/ansible_collections/cisco/aci/tests/integration/targets/aci_vrf_leak_internal_subnet/aliases new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/ansible_collections/cisco/aci/tests/integration/targets/aci_vrf_leak_internal_subnet/aliases diff --git a/ansible_collections/cisco/aci/tests/integration/targets/aci_vrf_leak_internal_subnet/tasks/main.yml b/ansible_collections/cisco/aci/tests/integration/targets/aci_vrf_leak_internal_subnet/tasks/main.yml new file mode 100644 index 000000000..2455f24e4 --- /dev/null +++ b/ansible_collections/cisco/aci/tests/integration/targets/aci_vrf_leak_internal_subnet/tasks/main.yml @@ -0,0 +1,233 @@ +# Test code for the ACI modules +# Copyright: (c) 2023, Abraham Mughal (@abmughal) + +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +- name: Test that we have an ACI APIC host, ACI username and ACI password + fail: + msg: 'Please define the following variables: aci_hostname, aci_username and aci_password.' + when: aci_hostname is not defined or aci_username is not defined or aci_password is not defined + +- name: Verify Cloud and Non-Cloud Sites in use. + include_tasks: ../../../../../../integration/targets/aci_cloud_provider/tasks/main.yml + +- name: Execute tasks only for non-cloud sites + when: + - query_cloud.current == [] # This condition will execute only non-cloud sites + - version.current.0.topSystem.attributes.version is version('5', '>=') + block: # block specifies execution of tasks within, based on conditions + - name: Set vars + set_fact: + aci_info: &aci_info + host: '{{ aci_hostname }}' + username: '{{ aci_username }}' + password: '{{ aci_password }}' + validate_certs: '{{ aci_validate_certs | default(false) }}' + use_ssl: '{{ aci_use_ssl | default(true) }}' + use_proxy: '{{ aci_use_proxy | default(true) }}' + output_level: '{{ aci_output_level | default("debug") }}' + + - name: delete existing tenant + cisco.aci.aci_tenant: + <<: *aci_info + state: absent + tenant: ansible_test3 + register: tenant_present + + - name: ensure tenant exists for tests to kick off + cisco.aci.aci_tenant: &aci_tenant_present + <<: *aci_info + state: present + tenant: ansible_test3 + register: tenant_present + + - name: create vrf + cisco.aci.aci_vrf: &aci_vrf_present + <<: *aci_tenant_present + vrf: test + description: Ansible Test + register: vrf_present + + - name: create second vrf - creation works + cisco.aci.aci_vrf: + <<: *aci_vrf_present + vrf: test2 + register: vrf_present + + - name: create third vrf - creation works + cisco.aci.aci_vrf: + <<: *aci_vrf_present + vrf: test3 + register: vrf_present + + - name: create leak internal subnet - check_mode + cisco.aci.aci_vrf_leak_internal_subnet: &aci_leak_internal_subnet + <<: *aci_info + state: present + tenant: ansible_test3 + vrf: test + ip: 1.1.1.2 + leak_to: + - vrf: "test2" + tenant: "ansible_test3" + - vrf: "test3" + tenant: "ansible_test3" + description: Ansible Test + check_mode: true + register: leak_sub_check_mode + + - name: create leak internal subnet + cisco.aci.aci_vrf_leak_internal_subnet: + <<: *aci_leak_internal_subnet + register: leak_sub_present + + - name: create leak internal subnet - idempotency check + cisco.aci.aci_vrf_leak_internal_subnet: + <<: *aci_info + state: present + tenant: ansible_test3 + vrf: test + ip: 1.1.1.2 + leak_to: + - vrf: "test2" + tenant: "ansible_test3" + - vrf: "test3" + tenant: "ansible_test3" + description: Ansible Test + register: leak_sub_idempotent + + - name: create second leak internal subnet + cisco.aci.aci_vrf_leak_internal_subnet: + <<: *aci_info + tenant: ansible_test3 + vrf: test2 + leak_to: + - vrf: "test" + tenant: "ansible_test3" + description: Ansible Test + ip: 1.1.1.2 + register: leak_sub_present_2 + + - name: Sort the list of leaked internal subnets for present + ansible.builtin.set_fact: + attributes_list_present: "{{ leak_sub_present.current.0.leakInternalSubnet.children | map(attribute='leakTo.attributes') | list | sort(attribute='ctxName') }}" + + - name: present asserts + assert: + that: + - vrf_present is changed + - leak_sub_check_mode is changed + - leak_sub_check_mode.proposed.leakInternalSubnet.attributes.ip == '1.1.1.2' + - leak_sub_check_mode.proposed.leakInternalSubnet.children.0.leakTo.attributes.ctxName == 'test2' + - leak_sub_check_mode.proposed.leakInternalSubnet.children.0.leakTo.attributes.tenantName == 'ansible_test3' + - leak_sub_check_mode.proposed.leakInternalSubnet.children.1.leakTo.attributes.ctxName == 'test3' + - leak_sub_check_mode.proposed.leakInternalSubnet.children.1.leakTo.attributes.tenantName == 'ansible_test3' + - leak_sub_present.current.0.leakInternalSubnet.attributes.ip == '1.1.1.2' + - attributes_list_present.0.tenantName == 'ansible_test3' + - attributes_list_present.0.ctxName == 'test2' + - attributes_list_present.1.tenantName == 'ansible_test3' + - attributes_list_present.1.ctxName == 'test3' + - leak_sub_idempotent is not changed + - leak_sub_present_2.current.0.leakInternalSubnet.attributes.ip == '1.1.1.2' + - leak_sub_present_2.current.0.leakInternalSubnet.children.0.leakTo.attributes.ctxName == 'test' + - leak_sub_present_2.current.0.leakInternalSubnet.children.0.leakTo.attributes.tenantName == 'ansible_test3' + + - name: query all + cisco.aci.aci_vrf_leak_internal_subnet: &aci_query + <<: *aci_info + state: query + ip: 1.1.1.2 + register: query_all + + - name: query one leak internal subnet + cisco.aci.aci_vrf_leak_internal_subnet: + <<: *aci_tenant_present + state: query + vrf: test + ip: 1.1.1.2 + register: query + + - name: absent case + cisco.aci.aci_vrf_leak_internal_subnet: + <<: *aci_info + tenant: ansible_test3 + vrf: test2 + leak_to: + - vrf: "test" + tenant: "ansible_test3" + description: Ansible Test + ip: 1.1.1.2 + state: absent + register: leak_sub_absent + + - name: Sort the list of leaked internal subnets for query + ansible.builtin.set_fact: + attributes_list_query_all: "{{ query_all.current.0.leakInternalSubnet.children | map(attribute='leakTo.attributes') | list | sort(attribute='ctxName') }}" + attributes_list_query: "{{ query.current.0.leakInternalSubnet.children | map(attribute='leakTo.attributes') | list | sort(attribute='ctxName') }}" + + - name: query asserts + assert: + that: + - query_all is not changed + - query is not changed + - query_all.current.0.leakInternalSubnet.attributes.ip == '1.1.1.2' + - attributes_list_query_all.0.ctxName == 'test2' + - attributes_list_query_all.0.tenantName == 'ansible_test3' + - attributes_list_query_all.1.ctxName == 'test3' + - attributes_list_query_all.1.tenantName == 'ansible_test3' + - query_all.current.1.leakInternalSubnet.attributes.ip == '1.1.1.2' + - query_all.current.1.leakInternalSubnet.children.0.leakTo.attributes.ctxName == 'test' + - query_all.current.1.leakInternalSubnet.children.0.leakTo.attributes.tenantName == 'ansible_test3' + - attributes_list_query.0.ctxName == 'test2' + - attributes_list_query.0.tenantName == 'ansible_test3' + - attributes_list_query.1.ctxName == 'test3' + - attributes_list_query.1.tenantName == 'ansible_test3' + - leak_sub_absent.proposed == {} + + - name: delete leak internal subnet - check_mode + cisco.aci.aci_vrf_leak_internal_subnet: &aci_delete + <<: *aci_vrf_present + vrf: test + leak_to: + - vrf: "test3" + tenant: "ansible_test3" + ip: 1.1.1.2 + register: leak_sub_delete_check_mode + + - name: delete leak internal subnet + cisco.aci.aci_vrf_leak_internal_subnet: + <<: *aci_delete + register: leak_sub_delete + + - name: delete leak internal subnet again + cisco.aci.aci_vrf_leak_internal_subnet: &aci_delete_again + <<: *aci_vrf_present + vrf: test + leak_to: + - vrf: "test2" + tenant: "ansible_test3" + ip: 1.1.1.2 + register: leak_sub_delete_2 + + - name: delete leak internal subnet idempotency check + cisco.aci.aci_vrf_leak_internal_subnet: + <<: *aci_delete_again + register: leak_sub_delete_idempotency + + - name: delete asserts + assert: + that: + - leak_sub_delete_check_mode is changed + - leak_sub_delete_check_mode.current.0.leakInternalSubnet.children.0.leakTo.attributes.ctxName == 'test3' + - leak_sub_delete_check_mode.current.0.leakInternalSubnet.children.0.leakTo.attributes.tenantName == 'ansible_test3' + - leak_sub_delete.previous != [] + - leak_sub_delete.current.0.leakInternalSubnet.children | length == 1 + - leak_sub_delete.current.0.leakInternalSubnet.children.0.leakTo.attributes.ctxName == 'test3' + - leak_sub_delete.current.0.leakInternalSubnet.children.0.leakTo.attributes.tenantName == 'ansible_test3' + - leak_sub_delete_2 is changed + - leak_sub_delete_2.current.0.leakInternalSubnet.children | length == 1 + - leak_sub_delete_2.current.0.leakInternalSubnet.children.0.leakTo.attributes.ctxName == 'test2' + - leak_sub_delete_2.current.0.leakInternalSubnet.children.0.leakTo.attributes.tenantName == 'ansible_test3' + - leak_sub_delete_idempotency is not changed + - leak_sub_delete_idempotency.current.0.leakInternalSubnet.children.0.leakTo.attributes.ctxName == 'test2' + - leak_sub_delete_idempotency.current.0.leakInternalSubnet.children.0.leakTo.attributes.tenantName == 'ansible_test3'
\ No newline at end of file |