diff --git a/plugins/module_utils/aci.py b/plugins/module_utils/aci.py index 73f34b271..d567089c6 100644 --- a/plugins/module_utils/aci.py +++ b/plugins/module_utils/aci.py @@ -1732,3 +1732,19 @@ def api_call(self, method, url, data=None, return_response=False): except KeyError: # Connection error self.fail_json(msg="Connection failed for {url}. {msg}".format_map(info)) + + def delete_config_request(self, path): + self._config_request(path, "absent") + self.result["changed"] = True + + def get_config_request(self, path): + self._config_request(path, "query") + return self.imdata + + def _config_request(self, path, state): + reset_url = self.url + reset_state = self.params["state"] + self.params["state"] = state + self.request(path) + self.url = reset_url + self.params["state"] = reset_state diff --git a/plugins/module_utils/constants.py b/plugins/module_utils/constants.py index ee94fc5e3..6ca9ff3b8 100644 --- a/plugins/module_utils/constants.py +++ b/plugins/module_utils/constants.py @@ -432,3 +432,13 @@ "yellow", "yellow_green", ] + +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"} + +HTTP_VERSIONS_MAPPING = {"1.0": "HTTP10", "1.1": "HTTP11"} + +L4L7_FUNC_TYPES_MAPPING = {"go_to": "GoTo", "go_through": "GoThrough", "l1": "L1", "l2": "L2"} + +L4L7_HASH_ALGORITHMS_MAPPING = {"source_ip": "sip", "destination_ip": "dip", "ip_and_protocol": "sip-dip-prototype"} diff --git a/plugins/modules/aci_ip_sla_monitoring_policy.py b/plugins/modules/aci_ip_sla_monitoring_policy.py new file mode 100644 index 000000000..764235f46 --- /dev/null +++ b/plugins/modules/aci_ip_sla_monitoring_policy.py @@ -0,0 +1,379 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# 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 = r""" +--- +module: aci_ip_sla_monitoring_policy +short_description: Manage IP SLA Monitoring Policies (fv:IPSLAMonitoringPol) +description: +- Manage IP SLA Monitoring Policies used for L4-L7 Policy Based Redirection +options: + tenant: + description: + - The name of an existing tenant. + type: str + aliases: [ tenant_name ] + name: + description: + - The SLA Policy name. + type: str + aliases: [ sla_policy ] + sla_type: + description: + - The type of monitoring. + - The APIC defaults to C(icmp) when unset during creation. + type: str + choices: [ icmp, tcp, l2ping, http ] + sla_port: + description: + - The Port to monitor for TCP SLAs. + type: int + frequency: + description: + - How often to probe. + - The APIC defaults to C(60) when unset during creation. + type: int + multiplier: + description: + - How many probes must fail for the SLA to be down. + - The APIC defaults to C(3) when unset during creation. + type: int + request_data_size: + description: + - The number of bytes to send in the request. + - Only used if I(sla_type) is set to C(http). + - The APIC defaults to C(28) when unset during creation. + type: int + type_of_service: + description: + - The Type of Service (ToS) value to set in the IPv4 header. + - The APIC defaults to C(0) when unset during creation. + type: int + aliases: [ tos ] + operation_timeout: + description: + - The amount of time in milliseconds that the IP SLA operation waits for a response from its request packet. + - The APIC defaults to C(900) when unset during creation. + type: int + threshold: + description: + - The upper threshold value in milliseconds for calculating network monitoring statistics created by the IP SLA operation. + - The value specified for this property must not exceed the value specified for operation_timeout. + - The APIC defaults to C(900) when unset during creation. + type: int + traffic_class: + description: + - The Traffic Class value to set in the IPv6 header. + - The APIC defaults to C(0) when unset during creation. + type: int + http_version: + description: + - The HTTP version to use. + - The APIC defaults to C(1.0) when unset during creation. + type: str + choices: [ "1.0", "1.1" ] + http_uri: + description: + - The HTTP URI to use as the SLA destination. + - The APIC defaults to C(/) when unset during creation. + 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 I(tenant) must exist before using this module in your playbook. + The M(cisco.aci.aci_tenant) modules can be used for this. +seealso: +- name: APIC Management Information Model reference + description: More information about the internal APIC class B(fv:IPSLAMonitoringPol) + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Tim Cragg (@timcragg) +""" + +EXAMPLES = r""" +- name: Add a new ICMP SLA monitoring policy + cisco.aci.aci_ip_sla_monitoring_policy: + host: apic + username: admin + password: SomeSecretPassword + tenant: my_tenant + name: my_policy + sla_type: icmp + frequency: 40 + multiplier: 6 + state: present + delegate_to: localhost + +- name: Add a new TCP SLA monitoring policy + cisco.aci.aci_ip_sla_monitoring_policy: + host: apic + username: admin + password: SomeSecretPassword + tenant: my_tenant + name: my_policy + sla_type: tcp + sla_port: 2345 + frequency: 45 + multiplier: 5 + state: present + delegate_to: localhost + +- name: Delete an SLA monitoring policy + cisco.aci.aci_ip_sla_monitoring_policy: + host: apic + username: admin + password: SomeSecretPassword + tenant: my_tenant + name: my_policy + state: absent + delegate_to: localhost + +- name: Query an SLA monitoring policy + cisco.aci.aci_ip_sla_monitoring_policy: + host: apic + username: admin + password: SomeSecretPassword + tenant: my_tenant + name: my_policy + state: query + delegate_to: localhost + register: query_result + +- name: Query all SLA monitoring policies + cisco.aci.aci_ip_sla_monitoring_policy: + 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: '' +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 HTTP_VERSIONS_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"]), + state=dict(type="str", default="present", choices=["absent", "present", "query"]), + name=dict(type="str", aliases=["sla_policy"]), + sla_type=dict(type="str", choices=["icmp", "tcp", "l2ping", "http"]), + sla_port=dict(type="int"), + frequency=dict(type="int"), + multiplier=dict(type="int"), + request_data_size=dict(type="int"), + type_of_service=dict(type="int", aliases=["tos"]), + operation_timeout=dict(type="int"), + threshold=dict(type="int"), + traffic_class=dict(type="int"), + http_version=dict(type="str", choices=["1.0", "1.1"]), + http_uri=dict(type="str"), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_if=[ + ["state", "absent", ["tenant", "name"]], + ["state", "present", ["tenant", "name"]], + ], + ) + + tenant = module.params.get("tenant") + state = module.params.get("state") + name = module.params.get("name") + sla_type = module.params.get("sla_type") + sla_port = module.params.get("sla_port") + frequency = module.params.get("frequency") + multiplier = module.params.get("multiplier") + request_data_size = module.params.get("request_data_size") + type_of_service = module.params.get("type_of_service") + operation_timeout = module.params.get("operation_timeout") + threshold = module.params.get("threshold") + traffic_class = module.params.get("traffic_class") + http_version = HTTP_VERSIONS_MAPPING.get(module.params.get("http_version")) + http_uri = module.params.get("http_uri") + + 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="fvIPSLAMonitoringPol", aci_rn="ipslaMonitoringPol-{0}".format(name), module_object=name, target_filter={"name": name}), + ) + aci.get_existing() + + if state == "present": + if sla_port is not None and sla_type != "tcp": + aci.fail_json("Setting 'sla_port' is not allowed when 'sla_type' is not set to 'tcp'.") + if sla_type != "http" and request_data_size is not None: + aci.fail_json("Setting 'request_data_size' is not allowed when 'sla_type' is not set to 'http.") + + if sla_type == "http": + sla_port = 80 + elif sla_type != "tcp": + sla_port = 0 + + aci.payload( + aci_class="fvIPSLAMonitoringPol", + class_config=dict( + name=name, + slaType=sla_type, + slaPort=sla_port, + slaFrequency=frequency, + slaDetectMultiplier=multiplier, + reqDataSize=request_data_size, + ipv4Tos=type_of_service, + timeout=operation_timeout, + threshold=threshold, + ipv6TrfClass=traffic_class, + httpVersion=http_version, + httpUri=http_uri, + ), + ) + aci.get_diff(aci_class="fvIPSLAMonitoringPol") + + aci.post_config() + + elif state == "absent": + aci.delete_config() + + aci.exit_json() + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/aci_l4l7_concrete_device.py b/plugins/modules/aci_l4l7_concrete_device.py new file mode 100644 index 000000000..10c0fa208 --- /dev/null +++ b/plugins/modules/aci_l4l7_concrete_device.py @@ -0,0 +1,295 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# 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 = r""" +--- +module: aci_l4l7_concrete_device +short_description: Manage L4-L7 Concrete Devices (vns:CDev) +description: +- Manage L4-L7 Concrete Devices. +options: + tenant: + description: + - The name of an existing tenant. + type: str + aliases: [ tenant_name ] + device: + description: + - The name of the logical device (vns:lDevVip) the concrete device is attached to. + - The logical device can be configured using the M(cisco.aci.aci_l4l7_device) module. + type: str + aliases: [ device_name, logical_device_name ] + name: + description: + - The name of the concrete device. + type: str + aliases: [ concrete_device, concrete_device_name ] + vcenter_name: + description: + - The virtual center name on which the device is hosted in the L4-L7 device cluster. + type: str + vm_name: + description: + - The virtual center VM name on which the device is hosted in the L4-L7 device cluster. + 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(tenant) and I(device) must exist before using this module in your playbook. + The M(cisco.aci.aci_tenant) and M(cisco.aci.aci_l4l7_device) modules can be used for this. +seealso: +- module: aci_l4l7_device +- name: APIC Management Information Model reference + description: More information about the internal APIC class B(vns:CDev) + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Tim Cragg (@timcragg) +""" + +EXAMPLES = r""" +- name: Add a new concrete device + cisco.aci.aci_l4l7_concrete_device: + host: apic + username: admin + password: SomeSecretPassword + tenant: my_tenant + device: my_device + concrete_device: my_concrete_device + state: present + delegate_to: localhost + +- name: Delete a concrete device + cisco.aci.aci_l4l7_concrete_device: + host: apic + username: admin + password: SomeSecretPassword + tenant: my_tenant + device: my_device + concrete_device: my_concrete_device + state: absent + delegate_to: localhost + +- name: Query a concrete device + cisco.aci.aci_l4l7_concrete_device: + host: apic + username: admin + password: SomeSecretPassword + tenant: my_tenant + device: my_device + concrete_device: my_concrete_device + state: query + delegate_to: localhost + register: query_result + +- name: Query all concrete devices + cisco.aci.aci_l4l7_concrete_device: + 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: '' +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( + tenant=dict(type="str", aliases=["tenant_name"]), + device=dict(type="str", aliases=["device_name", "logical_device_name"]), + name=dict(type="str", aliases=["concrete_device", "concrete_device_name"]), + vcenter_name=dict(type="str"), + vm_name=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", ["tenant", "device", "name"]], + ["state", "present", ["tenant", "device", "name"]], + ], + ) + + tenant = module.params.get("tenant") + state = module.params.get("state") + device = module.params.get("device") + name = module.params.get("name") + vcenter_name = module.params.get("vcenter_name") + vm_name = module.params.get("vm_name") + + 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="vnsLDevVip", + aci_rn="lDevVip-{0}".format(device), + module_object=device, + target_filter={"name": device}, + ), + subclass_2=dict( + aci_class="vnsCDev", + aci_rn="cDev-{0}".format(name), + module_object=name, + target_filter={"name": name}, + ), + ) + + aci.get_existing() + + if state == "present": + aci.payload( + aci_class="vnsCDev", + class_config=dict( + name=name, + vcenterName=vcenter_name, + vmName=vm_name, + ), + ) + aci.get_diff(aci_class="vnsCDev") + + aci.post_config() + + elif state == "absent": + aci.delete_config() + + aci.exit_json() + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/aci_l4l7_concrete_interface.py b/plugins/modules/aci_l4l7_concrete_interface.py new file mode 100644 index 000000000..557b48743 --- /dev/null +++ b/plugins/modules/aci_l4l7_concrete_interface.py @@ -0,0 +1,352 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# 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 = r""" +--- +module: aci_l4l7_concrete_interface +short_description: Manage L4-L7 Concrete Interfaces (vns:CIf) +description: +- Manage L4-L7 Concrete Interfaces. +options: + tenant: + description: + - The name of an existing tenant. + type: str + aliases: [ tenant_name ] + device: + description: + - The name of an existing logical device. + type: str + concrete_device: + description: + - The name of an existing concrete device. + type: str + name: + description: + - The name of the concrete interface. + type: str + aliases: [ concrete_interface ] + pod_id: + description: + - The pod ID the concrete interface exists on. + type: int + node_id: + description: + - The node ID the concrete interface exists on. + - For Ports and Port-channels this is a single node-id. + - For vPCs this is a hyphen separated pair of node-ids, e.g. "201-202". + type: str + path_ep: + description: + - The path to the physical interface. + - For single ports, this is the port name, e.g. "eth1/15". + - For Port-channels and vPCs, this is the Interface Policy Group name. + type: str + vnic_name: + description: + - The concrete interface vNIC name. + 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: +- module: aci_l4l7_device +- module: aci_l4l7_concrete_device +- name: APIC Management Information Model reference + description: More information about the internal APIC class B(vns:CIf) + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Tim Cragg (@timcragg) +""" + +EXAMPLES = r""" +- name: Add a new concrete interface on a single port + cisco.aci.aci_l4l7_concrete_interface: + host: apic + username: admin + password: SomeSecretPassword + tenant: my_tenant + device: my_device + concrete_device: my_concrete_device + name: my_concrete_interface + pod_id: 1 + node_id: 201 + path_ep: eth1/16 + state: present + delegate_to: localhost + +- name: Add a new concrete interface on a vPC + cisco.aci.aci_l4l7_concrete_interface: + host: apic + username: admin + password: SomeSecretPassword + tenant: my_tenant + device: my_device + concrete_device: my_concrete_device + name: my_concrete_interface + pod_id: 1 + node_id: 201-202 + path_ep: my_vpc_ipg + state: present + delegate_to: localhost + +- name: Delete a concrete interface + cisco.aci.aci_l4l7_concrete_interface: + host: apic + username: admin + password: SomeSecretPassword + tenant: my_tenant + device: my_device + concrete_device: my_concrete_device + name: my_concrete_interface + state: absent + delegate_to: localhost + +- name: Query a concrete interface + cisco.aci.aci_l4l7_service_graph_template: + host: apic + username: admin + password: SomeSecretPassword + tenant: my_tenant + device: my_device + concrete_device: my_concrete_device + name: my_concrete_interface + pod_id: 1 + node_id: 201-202 + path_ep: my_vpc_ipg + state: query + delegate_to: localhost + register: query_result + +- name: Query all concrete interfaces + cisco.aci.aci_l4l7_service_graph_template: + 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: '' +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 + + +def main(): + argument_spec = aci_argument_spec() + argument_spec.update(aci_annotation_spec()) + argument_spec.update( + tenant=dict(type="str", aliases=["tenant_name"]), + device=dict(type="str"), + concrete_device=dict(type="str"), + state=dict(type="str", default="present", choices=["absent", "present", "query"]), + name=dict(type="str", aliases=["concrete_interface"]), + pod_id=dict(type="int"), + node_id=dict(type="str"), + path_ep=dict(type="str"), + vnic_name=dict(type="str"), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_if=[ + ["state", "absent", ["tenant", "device", "concrete_device", "name"]], + ["state", "present", ["tenant", "device", "concrete_device", "name", "pod_id", "node_id", "path_ep"]], + ], + ) + + tenant = module.params.get("tenant") + state = module.params.get("state") + device = module.params.get("device") + concrete_device = module.params.get("concrete_device") + name = module.params.get("name") + pod_id = module.params.get("pod_id") + node_id = module.params.get("node_id") + path_ep = module.params.get("path_ep") + vnic_name = module.params.get("vnic_name") + + 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="vnsLDevVip", + aci_rn="lDevVip-{0}".format(device), + module_object=device, + target_filter={"name": device}, + ), + subclass_2=dict( + aci_class="vnsCDev", + aci_rn="cDev-{0}".format(concrete_device), + module_object=concrete_device, + target_filter={"name": concrete_device}, + ), + subclass_3=dict( + aci_class="vnsCIf", + aci_rn="cIf-[{0}]".format(name), + module_object=name, + target_filter={"name": name}, + ), + child_classes=["vnsRsCIfPathAtt"], + ) + + aci.get_existing() + + if state == "present": + path_dn = "topology/pod-{0}/{1}-{2}/pathep-[{3}]".format(pod_id, "protpaths" if "-" in node_id else "paths", node_id, path_ep) + aci.payload( + aci_class="vnsCIf", + class_config=dict( + name=name, + vnicName=vnic_name, + ), + child_configs=[ + dict( + vnsRsCIfPathAtt=dict( + attributes=dict(tDn=path_dn), + ), + ), + ], + ) + aci.get_diff(aci_class="vnsCIf") + + aci.post_config() + + elif state == "absent": + aci.delete_config() + + aci.exit_json() + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/aci_l4l7_concrete_interface_attach.py b/plugins/modules/aci_l4l7_concrete_interface_attach.py new file mode 100644 index 000000000..fa9694fcc --- /dev/null +++ b/plugins/modules/aci_l4l7_concrete_interface_attach.py @@ -0,0 +1,302 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# 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 = r""" +--- +module: aci_l4l7_concrete_interface_attach +short_description: Manage L4-L7 Concrete Interface Attach (vns:RsCIfAttN) +description: +- Manage L4-L7 Concrete Interface Attachment to Logical Interfaces. +options: + tenant: + description: + - The name of an existing tenant. + type: str + aliases: [ tenant_name ] + device: + description: + - The name of an existing Logical Device. + type: str + logical_interface: + description: + - The name of an existing Logical Interface. + type: str + concrete_device: + description: + - The name of an existing Concrete Device. + type: str + concrete_interface: + description: + - The name of an existing Concrete Interface. + 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: +- module: aci_l4l7_logical_interface +- module: aci_l4l7_concrete_device +- module: aci_l4l7_concrete_interface +- name: APIC Management Information Model reference + description: More information about the internal APIC class B(vns:RsCIfAttN) + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Tim Cragg (@timcragg) +""" + +EXAMPLES = r""" +- name: Add a new concrete interface attachment + cisco.aci.aci_l4l7_concrete_interface_attach: + host: apic + username: admin + password: SomeSecretPassword + tenant: my_tenant + device: my_log_device + logical_interface: my_log_intf + concrete_device: my_conc_device + concrete_interface: my_conc_intf + state: present + delegate_to: localhost + +- name: Delete a concrete interface attachment + cisco.aci.aci_l4l7_concrete_interface_attach: + host: apic + username: admin + password: SomeSecretPassword + tenant: my_tenant + device: my_log_device + logical_interface: my_log_intf + concrete_device: my_conc_device + concrete_interface: my_conc_intf + state: absent + delegate_to: localhost + +- name: Query a concrete interface attachment + cisco.aci.aci_l4l7_concrete_interface_attach: + host: apic + username: admin + password: SomeSecretPassword + tenant: my_tenant + device: my_log_device + logical_interface: my_log_intf + concrete_device: my_conc_device + concrete_interface: my_conc_intf + state: query + delegate_to: localhost + register: query_result + +- name: Query all concrete interface attachments + cisco.aci.aci_l4l7_concrete_interface_attach: + host: apic + username: admin + password: SomeSecretPassword + 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: '' +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( + tenant=dict(type="str", aliases=["tenant_name"]), + device=dict(type="str"), + logical_interface=dict(type="str"), + state=dict(type="str", default="present", choices=["absent", "present", "query"]), + concrete_device=dict(type="str"), + concrete_interface=dict(type="str"), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_if=[ + ["state", "absent", ["tenant", "device", "logical_interface", "concrete_device", "concrete_interface"]], + ["state", "present", ["tenant", "device", "logical_interface", "concrete_device", "concrete_interface"]], + ], + required_together=[ + ["tenant", "device", "concrete_device", "concrete_interface"], + ], + ) + + tenant = module.params.get("tenant") + state = module.params.get("state") + device = module.params.get("device") + logical_interface = module.params.get("logical_interface") + concrete_device = module.params.get("concrete_device") + concrete_interface = module.params.get("concrete_interface") + + aci = ACIModule(module) + + tdn = "uni/tn-{0}/lDevVip-{1}/cDev-{2}/cIf-[{3}]".format(tenant, device, concrete_device, concrete_interface) if concrete_interface is not None else None + + 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="vnsLDevVip", + aci_rn="lDevVip-{0}".format(device), + module_object=device, + target_filter={"name": device}, + ), + subclass_2=dict( + aci_class="vnsLIf", + aci_rn="lIf-{0}".format(logical_interface), + module_object=logical_interface, + target_filter={"name": logical_interface}, + ), + subclass_3=dict( + aci_class="vnsRsCIfAttN", + aci_rn="rscIfAttN-[{0}]".format(tdn), + module_object=tdn, + target_filter={"tDn": tdn}, + ), + ) + + aci.get_existing() + + if state == "present": + aci.payload( + aci_class="vnsRsCIfAttN", + class_config=dict(tDn=tdn), + ) + aci.get_diff(aci_class="vnsRsCIfAttN") + + aci.post_config() + + elif state == "absent": + aci.delete_config() + + aci.exit_json() + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/aci_l4l7_device.py b/plugins/modules/aci_l4l7_device.py new file mode 100644 index 000000000..abdf4d1c8 --- /dev/null +++ b/plugins/modules/aci_l4l7_device.py @@ -0,0 +1,351 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# 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 = r""" +--- +module: aci_l4l7_device +short_description: Manage L4-L7 Devices (vns:LDevVip) +description: +- Manage L4-L7 Devices. +options: + tenant: + description: + - The name of an existing tenant. + type: str + aliases: [ tenant_name ] + name: + description: + - The name of the L4-L7 device. + type: str + aliases: [ device, device_name, logical_device_name ] + context_aware: + description: + - Is device Single or Multi context aware. + - The APIC defaults to C(single) when unset during creation. + type: str + choices: [ multi, single ] + dev_type: + description: + - The type of the device. + - The APIC defaults to C(physical) when unset during creation. + type: str + choices: [ physical, virtual ] + func_type: + description: + - The function type of the device. + - The APIC defaults to C(go_to) when unset during creation. + type: str + choices: [ go_to, go_through, l1, l2 ] + managed: + description: + - Is the device a managed device. + - The APIC defaults to C(true) when unset during creation. + type: bool + prom_mode: + description: + - Enable promiscuous mode. + - The APIC defaults to C(false) when unset during creation. + type: bool + svc_type: + description: + - The service type running on the device. + - The APIC defaults to C(others) when unset during creation. + type: str + choices: [ adc, fw, others ] + trunking: + description: + - Enable trunking. + - The APIC defaults to C(false) when unset during creation. + type: bool + domain: + description: + - The domain to bind to the device. + - The type of domain is controlled by the dev_type setting. + 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(tenant) must exist before using this module in your playbook. + The M(cisco.aci.aci_tenant) modules can be used for this. +seealso: +- name: APIC Management Information Model reference + description: More information about the internal APIC class B(vns:LDevVip) + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Tim Cragg (@timcragg) +""" + +EXAMPLES = r""" +- name: Add a new L4-L7 device + cisco.aci.aci_l4l7_device: + host: apic + username: admin + password: SomeSecretPassword + tenant: my_tenant + name: my_device + state: present + domain: phys + func_type: go_to + context_aware: single + managed: no + dev_type: physical + svc_type: adc + trunking: no + prom_mode: yes + delegate_to: localhost + +- name: Delete an existing L4-L7 device + cisco.aci.aci_l4l7_device: + host: apic + username: admin + password: SomeSecretPassword + tenant: my_tenant + name: my_device + state: absent + delegate_to: localhost + +- name: Query an L4-L7 device + cisco.aci.aci_l4l7_device: + host: apic + username: admin + password: SomeSecretPassword + tenant: my_tenant + name: my_device + state: query + delegate_to: localhost + register: query_result + +- name: Query all L4-L7 devices + cisco.aci.aci_l4l7_device: + 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: '' +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 +from ansible_collections.cisco.aci.plugins.module_utils.constants import L4L7_FUNC_TYPES_MAPPING + + +def main(): + argument_spec = aci_argument_spec() + argument_spec.update(aci_annotation_spec()) + argument_spec.update( + tenant=dict(type="str", aliases=["tenant_name"]), + name=dict(type="str", aliases=["device", "device_name", "logical_device_name"]), + state=dict(type="str", default="present", choices=["absent", "present", "query"]), + context_aware=dict(type="str", choices=["single", "multi"]), + dev_type=dict(type="str", choices=["physical", "virtual"]), + func_type=dict(type="str", choices=["go_to", "go_through", "l1", "l2"]), + managed=dict(type="bool"), + prom_mode=dict(type="bool"), + svc_type=dict(type="str", choices=["adc", "fw", "others"]), + trunking=dict(type="bool"), + domain=dict(type="str"), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_if=[ + ["state", "absent", ["tenant", "name"]], + ["state", "present", ["tenant", "name", "dev_type", "domain"]], + ], + ) + + aci = ACIModule(module) + + tenant = module.params.get("tenant") + state = module.params.get("state") + name = module.params.get("name") + context_aware = module.params.get("context_aware") + dev_type = module.params.get("dev_type") + func_type = L4L7_FUNC_TYPES_MAPPING.get(module.params.get("func_type")) + managed = aci.boolean(module.params.get("managed")) + prom_mode = aci.boolean(module.params.get("prom_mode")) + svc_type = module.params.get("svc_type") + trunking = aci.boolean(module.params.get("trunking")) + domain = module.params.get("domain") + + 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="vnsLDevVip", + aci_rn="lDevVip-{0}".format(name), + module_object=name, + target_filter={"name": name}, + ), + child_classes=["vnsRsALDevToPhysDomP", "vnsCDev", "vnsRsALDevToDomP"], + ) + + aci.get_existing() + + if state == "present": + child_configs = [] + if dev_type == "virtual": + dom_class = "vnsRsALDevToDomP" + tdn = "uni/vmmp-VMware/dom-{0}".format(domain) + else: + dom_class = "vnsRsALDevToPhysDomP" + tdn = "uni/phys-{0}".format(domain) + child_configs.append({dom_class: {"attributes": {"tDn": tdn}}}) + + aci.payload( + aci_class="vnsLDevVip", + class_config=dict( + name=name, + contextAware="{0}-Context".format(context_aware), + devtype=dev_type.upper(), + funcType=func_type, + managed=managed, + promMode=prom_mode, + svcType=svc_type.upper(), + trunking=trunking, + ), + child_configs=child_configs, + ) + + aci.get_diff(aci_class="vnsLDevVip") + + aci.post_config() + + elif state == "absent": + aci.delete_config() + + aci.exit_json() + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/aci_l4l7_device_selection_if_context.py b/plugins/modules/aci_l4l7_device_selection_if_context.py new file mode 100644 index 000000000..308533568 --- /dev/null +++ b/plugins/modules/aci_l4l7_device_selection_if_context.py @@ -0,0 +1,408 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# 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 = r""" +--- +module: aci_l4l7_device_selection_if_context +short_description: Manage L4-L7 Device Selection Policy Logical Interface Contexts (vns:LIfCtx) +description: +- Manage L4-L7 Device Selection Policy Logical Interface Contexts +options: + tenant: + description: + - The name of an existing tenant. + type: str + aliases: [ tenant_name ] + contract: + description: + - The name of an existing contract. + type: str + aliases: [ contract_name ] + graph: + description: + - The name of an existing Service Graph Template. + type: str + aliases: [ service_graph, service_graph_name ] + node: + description: + - The name of an existing Service Graph Node. + type: str + aliases: [ node_name ] + context: + description: + - The name of the logical interface context. + type: str + l3_dest: + description: + - Whether the context is a Layer3 destination. + - The APIC defaults to C(true) when unset during creation. + type: bool + permit_log: + description: + - Whether to log permitted traffic. + - The APIC defaults to C(false) when unset during creation. + type: bool + bridge_domain: + description: + - The Bridge Domain to bind to the Context. + type: str + aliases: [ bd, bd_name ] + bridge_domain_tenant: + description: + - The tenant the Bridge Domain resides in. + - Omit this variable if both context and Bridge Domain are in the same tenant. + - Intended use case is for when the Bridge Domain is in the common tenant, but the context is not. + type: str + aliases: [ bd_tenant ] + logical_device: + description: + - The Logical Device to bind the context to. + type: str + logical_interface: + description: + - The Logical Interface to bind the context to. + type: str + redirect_policy: + description: + - The Redirect Policy to bind the context to. + 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(tenant), I(graph), I(contract) and I(node) must exist before using this module in your playbook. + The M(cisco.aci.aci_tenant), M(cisco.aci.aci_l4l7_service_graph_template), M(cisco.aci.aci_contract) + and M(cisco.aci.aci_l4l7_service_graph_template_node) modules can be used for this. +seealso: +- module: aci_l3out +- module: aci_l3out_logical_node_profile +- name: APIC Management Information Model reference + description: More information about the internal APIC class, B(vns:LIfCtx) + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Tim Cragg (@timcragg) +""" + +EXAMPLES = r""" +- name: Add a new interface context + cisco.aci.aci_l4l7_device_selection_if_context: + host: apic + username: admin + password: SomeSecretPassword + tenant: my_tenant + contract: my_contract + graph: my_graph + node: my_node + context: provider + state: present + delegate_to: localhost + +- name: Delete an interface context + cisco.aci.aci_l4l7_device_selection_if_context: + host: apic + username: admin + password: SomeSecretPassword + tenant: my_tenant + contract: my_contract + graph: my_graph + node: my_node + context: provider + state: absent + delegate_to: localhost + +- name: Query an interface context + cisco.aci.aci_l4l7_device_selection_if_context: + host: apic + username: admin + password: SomeSecretPassword + tenant: my_tenant + contract: my_contract + graph: my_graph + node: my_node + context: consumer + state: query + delegate_to: localhost + register: query_result + +- name: Query all interface contexts + cisco.aci.aci_l4l7_device_selection_if_context: + 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: '' +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( + tenant=dict(type="str", aliases=["tenant_name"]), + contract=dict(type="str", aliases=["contract_name"]), + graph=dict(type="str", aliases=["service_graph", "service_graph_name"]), + node=dict(type="str", aliases=["node_name"]), + state=dict(type="str", default="present", choices=["absent", "present", "query"]), + context=dict(type="str"), + l3_dest=dict(type="bool"), + permit_log=dict(type="bool"), + bridge_domain=dict(type="str", aliases=["bd", "bd_name"]), + bridge_domain_tenant=dict(type="str", aliases=["bd_tenant"]), + logical_device=dict(type="str"), + logical_interface=dict(type="str"), + redirect_policy=dict(type="str"), + ) + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_if=[ + ["state", "absent", ["tenant", "contract", "graph", "node", "context"]], + ["state", "present", ["tenant", "contract", "graph", "node", "context"]], + ], + ) + + aci = ACIModule(module) + + tenant = module.params.get("tenant") + state = module.params.get("state") + contract = module.params.get("contract") + graph = module.params.get("graph") + node = module.params.get("node") + context = module.params.get("context") + l3_dest = aci.boolean(module.params.get("l3_dest")) + permit_log = aci.boolean(module.params.get("permit_log")) + bridge_domain = module.params.get("bridge_domain") + bridge_domain_tenant = module.params.get("bridge_domain_tenant") + logical_device = module.params.get("logical_device") + logical_interface = module.params.get("logical_interface") + redirect_policy = module.params.get("redirect_policy") + + ldev_ctx_rn = "ldevCtx-c-{0}-g-{1}-n-{2}".format(contract, graph, node) if (contract, graph, node) != (None, None, None) else None + + 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="vnsLDevCtx", + aci_rn=ldev_ctx_rn, + module_object=ldev_ctx_rn, + target_filter={"dn": ldev_ctx_rn}, + ), + subclass_2=dict( + aci_class="vnsLIfCtx", + aci_rn="lIfCtx-c-{0}".format(context), + module_object=context, + target_filter={"connNameOrLbl": context}, + ), + child_classes=["vnsRsLIfCtxToBD", "vnsRsLIfCtxToLIf", "vnsRsLIfCtxToSvcRedirectPol"], + ) + + aci.get_existing() + + if state == "present": + child_configs = [] + if bridge_domain is not None: + if bridge_domain_tenant is None: + bridge_domain_tenant = tenant + bd_tdn = "uni/tn-{0}/BD-{1}".format(bridge_domain_tenant, bridge_domain) + child_configs.append({"vnsRsLIfCtxToBD": {"attributes": {"tDn": bd_tdn}}}) + else: + bd_tdn = None + if logical_interface is not None: + log_intf_tdn = "uni/tn-{0}/lDevVip-{1}/lIf-{2}".format(tenant, logical_device, logical_interface) + child_configs.append({"vnsRsLIfCtxToLIf": {"attributes": {"tDn": log_intf_tdn}}}) + else: + log_intf_tdn = None + if redirect_policy is not None: + redir_pol_tdn = "uni/tn-{0}/svcCont/svcRedirectPol-{1}".format(tenant, redirect_policy) + child_configs.append({"vnsRsLIfCtxToSvcRedirectPol": {"attributes": {"tDn": redir_pol_tdn}}}) + else: + redir_pol_tdn = None + # 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("vnsLIfCtx", {}).get("children", {}): + if child.get("vnsRsLIfCtxToBD") and child.get("vnsRsLIfCtxToBD").get("attributes").get("tDn") != bd_tdn: + # Appending to child_config list not possible because of APIC Error 103: child (Rn) of class vnsRsLIfCtxToBD is already attached. + # A seperate delete request to dn of the vnsRsLIfCtxToBD is needed to remove the object prior to adding to child_configs. + # child_configs.append( + # { + # "vnsRsLIfCtxToBD": { + # "attributes": { + # "dn": child.get("vnsRsLIfCtxToBD").get("attributes").get("dn"), + # "status": "deleted", + # } + # } + # } + # ) + aci.delete_config_request( + "/api/mo/uni/tn-{0}/ldevCtx-c-{1}-g-{2}-n-{3}/lIfCtx-c-{4}/rsLIfCtxToBD.json".format(tenant, contract, graph, node, context) + ) + elif child.get("vnsRsLIfCtxToLIf") and child.get("vnsRsLIfCtxToLIf").get("attributes").get("tDn") != log_intf_tdn: + child_configs.append( + { + "vnsRsLIfCtxToLIf": { + "attributes": { + "dn": child.get("vnsRsLIfCtxToLIf").get("attributes").get("dn"), + "status": "deleted", + } + } + } + ) + elif child.get("vnsRsLIfCtxToSvcRedirectPol") and child.get("vnsRsLIfCtxToSvcRedirectPol").get("attributes").get("tDn") != redir_pol_tdn: + child_configs.append( + { + "vnsRsLIfCtxToSvcRedirectPol": { + "attributes": { + "dn": child.get("vnsRsLIfCtxToSvcRedirectPol").get("attributes").get("dn"), + "status": "deleted", + } + } + } + ) + aci.payload( + aci_class="vnsLIfCtx", + class_config=dict(connNameOrLbl=context, l3Dest=l3_dest, permitLog=permit_log), + child_configs=child_configs, + ) + aci.get_diff(aci_class="vnsLIfCtx") + + aci.post_config() + + elif state == "absent": + aci.delete_config() + + aci.exit_json() + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/aci_l4l7_device_selection_policy.py b/plugins/modules/aci_l4l7_device_selection_policy.py new file mode 100644 index 000000000..cfe087cbc --- /dev/null +++ b/plugins/modules/aci_l4l7_device_selection_policy.py @@ -0,0 +1,314 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# 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 = r""" +--- +module: aci_l4l7_device_selection_policy +short_description: Manage L4-L7 Device Selection Policies (vns:LDevCtx) +description: +- Manage L4-L7 Device Selection Policies +options: + tenant: + description: + - The name of an existing tenant. + type: str + aliases: [ tenant_name ] + contract: + description: + - The name of an existing contract. + - The APIC defaults to C(any) when unset during creation. + type: str + aliases: [ contract_name ] + graph: + description: + - The name of an existing service graph. + - The APIC defaults to C(any) when unset during creation. + type: str + aliases: [ service_graph, service_graph_name ] + node: + description: + - The name of an existing L4-L7 node. + - The APIC defaults to C(any) when unset during creation. + type: str + aliases: [ node_name ] + device: + description: + - The name of the L4-L7 Device to bind to the policy. + 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(tenant), I(contract), I(graph) and I(node) must exist before using this module in your playbook. + The M(cisco.aci.aci_tenant), M(cisco.aci.aci_contract), M(cisco.aci.aci_l4l7_service_graph) and M(cisco.aci.aci_l4l7_device) modules can be used for this. + +seealso: +- name: APIC Management Information Model reference + description: More information about the internal APIC class B(vns:LDevCtx) + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Tim Cragg (@timcragg) +""" + +EXAMPLES = r""" +- name: Add a new device selection policy + cisco.aci.aci_l4l7_device_selection_policy: + host: apic + username: admin + password: SomeSecretPassword + tenant: my_tenant + contract: my_contract + graph: my_graph + node: my_node + state: present + delegate_to: localhost + +- name: Delete a device selection policy + cisco.aci.aci_l4l7_device_selection_policy: + host: apic + username: admin + password: SomeSecretPassword + tenant: my_tenant + contract: my_contract + graph: my_graph + node: my_node + state: absent + delegate_to: localhost + +- name: Query a device selection policy + cisco.aci.aci_l4l7_device_selection_policy: + host: apic + username: admin + password: SomeSecretPassword + tenant: my_tenant + contract: my_contract + graph: my_graph + node: my_node + state: query + delegate_to: localhost + register: query_result + +- name: Query all device selection policies + cisco.aci.aci_l4l7_device_selection_policy: + 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: '' +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( + tenant=dict(type="str", aliases=["tenant_name"]), + contract=dict(type="str", aliases=["contract_name"]), + graph=dict(type="str", aliases=["service_graph", "service_graph_name"]), + node=dict(type="str", aliases=["node_name"]), + device=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", ["tenant", "contract", "graph", "node"]], + ["state", "present", ["tenant", "contract", "graph", "node"]], + ], + ) + + tenant = module.params.get("tenant") + state = module.params.get("state") + contract = module.params.get("contract") + graph = module.params.get("graph") + node = module.params.get("node") + device = module.params.get("device") + + policy_dn = "ldevCtx-c-{0}-g-{1}-n-{2}".format(contract, graph, node) if (contract, graph, node) != (None, None, None) else None + + 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="vnsLDevCtx", + aci_rn=policy_dn, + module_object=policy_dn, + target_filter={"dn": policy_dn}, + ), + child_classes=["vnsRsLDevCtxToLDev"], + ) + + aci.get_existing() + + if state == "present": + child_configs = [] + if device is not None: + device_tdn = "uni/tn-{0}/lDevVip-{1}".format(tenant, device) + child_configs.append({"vnsRsLDevCtxToLDev": {"attributes": {"tDn": device_tdn}}}) + else: + device_tdn = None + # 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("vnsLDevCtx", {}).get("children", {}): + if child.get("vnsRsLDevCtxToLDev") and child.get("vnsRsLDevCtxToLDev").get("attributes").get("tDn") != device_tdn: + child_configs.append( + { + "vnsRsLDevCtxToLDev": { + "attributes": { + "dn": child.get("vnsRsLDevCtxToLDev").get("attributes").get("dn"), + "status": "deleted", + } + } + } + ) + aci.payload( + aci_class="vnsLDevCtx", + class_config=dict(ctrctNameOrLbl=contract, graphNameOrLbl=graph, nodeNameOrLbl=node), + child_configs=child_configs, + ) + aci.get_diff(aci_class="vnsLDevCtx") + + aci.post_config() + + elif state == "absent": + aci.delete_config() + + aci.exit_json() + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/aci_l4l7_logical_interface.py b/plugins/modules/aci_l4l7_logical_interface.py new file mode 100644 index 000000000..d641d5153 --- /dev/null +++ b/plugins/modules/aci_l4l7_logical_interface.py @@ -0,0 +1,283 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# 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 = r""" +--- +module: aci_l4l7_logical_interface +short_description: Manage L4-L7 Logical Interface (vns:LIf) +description: +- Manage L4-L7 Logical Interfaces. +options: + tenant: + description: + - The name of an existing tenant. + type: str + aliases: [ tenant_name ] + device: + description: + - The name of an existing Logical Device. + type: str + logical_interface: + description: + - The name of an existing Logical Interface. + type: str + encap: + description: + - The encapsulation of the Logical Interface. + 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(tenant) and I(device) must exist before using this module in your playbook. + The M(cisco.aci.aci_tenant) and M(cisco.aci.aci_l4l7_device) modules can be used for this. +seealso: +- module: aci_l4l7_device +- name: APIC Management Information Model reference + description: More information about the internal APIC class B(vns:LIf) + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Tim Cragg (@timcragg) +""" + +EXAMPLES = r""" +- name: Add a new logical interface + cisco.aci.aci_l4l7_logical_interface: + host: apic + username: admin + password: SomeSecretPassword + tenant: my_tenant + device: my_device + logical_interface: my_log_intf + encap: vlan-987 + state: present + delegate_to: localhost + +- name: Delete a logical interface + cisco.aci.aci_l4l7_logical_interface: + host: apic + username: admin + password: SomeSecretPassword + tenant: my_tenant + device: my_device + logical_interface: my_log_intf + state: absent + delegate_to: localhost + +- name: Query a logical interface + cisco.aci.aci_l4l7_logical_interface: + host: apic + username: admin + password: SomeSecretPassword + tenant: my_tenant + device: my_device + logical_interface: my_log_intf + state: query + delegate_to: localhost + register: query_result + +- name: Query all logical interfaces + cisco.aci.aci_l4l7_logical_interface: + 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: '' +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( + tenant=dict(type="str", aliases=["tenant_name"]), + device=dict(type="str"), + logical_interface=dict(type="str"), + state=dict(type="str", default="present", choices=["absent", "present", "query"]), + encap=dict(type="str"), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_if=[ + ["state", "absent", ["tenant", "device", "logical_interface"]], + ["state", "present", ["tenant", "device", "logical_interface"]], + ], + ) + + tenant = module.params.get("tenant") + state = module.params.get("state") + device = module.params.get("device") + logical_interface = module.params.get("logical_interface") + encap = module.params.get("encap") + + 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="vnsLDevVip", + aci_rn="lDevVip-{0}".format(device), + module_object=device, + target_filter={"name": device}, + ), + subclass_2=dict( + aci_class="vnsLIf", + aci_rn="lIf-{0}".format(logical_interface), + module_object=logical_interface, + target_filter={"name": logical_interface}, + ), + ) + + aci.get_existing() + + if state == "present": + aci.payload( + aci_class="vnsLIf", + class_config=dict(name=logical_interface, encap=encap), + ) + aci.get_diff(aci_class="vnsLIf") + + aci.post_config() + + elif state == "absent": + aci.delete_config() + + aci.exit_json() + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/aci_l4l7_policy_based_redirect.py b/plugins/modules/aci_l4l7_policy_based_redirect.py new file mode 100644 index 000000000..ce5c2c0ea --- /dev/null +++ b/plugins/modules/aci_l4l7_policy_based_redirect.py @@ -0,0 +1,387 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# 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 = r""" +--- +module: aci_l4l7_policy_based_redirect +short_description: Manage L4-L7 Policy Based Redirection (vns:SvcRedirectPol) +description: +- Manage L4-L7 Policy Based Redirection +options: + tenant: + description: + - The name of an existing tenant. + type: str + aliases: [ tenant_name ] + policy_name: + description: + - The name of the Policy Based Redirection Policy. + type: str + description: + description: + - The description of the Policy Based Redirection Policy. + type: str + dest_type: + description: + - The destination type. + - The APIC defaults to C(l3) when unset during creation. + type: str + choices: [ l1, l2, l3 ] + hash_algorithm: + description: + - The hashing algorithm. + - The APIC defaults to C(ip_and_protocol) when unset during creation. + type: str + choices: [ source_ip, destination_ip, ip_and_protocol ] + threshold_enable: + description: + - Whether to enable the threshold for the policy. + - The APIC defaults to C(false) when unset during creation. + type: bool + max_threshold: + description: + - The maximum percent when threshold is enabled. + - The APIC defaults to C(0) when unset during creation. + type: int + min_threshold: + description: + - The minimum percent when threshold is enabled. + - The APIC defaults to C(0) when unset during creation. + type: int + threshold_down_action: + description: + - The action to take when threshold is breached. + - The APIC defaults to C(permit) when unset during creation. + type: str + choices: [ deny, permit ] + resilient_hash: + description: + - Whether to enable resilient hashing. + - The APIC defaults to C(false) when unset during creation. + type: bool + pod_aware: + description: + - Whether to enable Pod ID aware redirection. + - The APIC defaults to C(false) when unset during creation. + type: bool + anycast_enabled: + description: + - Whether to enable anycast services. + - The APIC defaults to C(false) when unset during creation. + - Only available when I(dest_type=l3) + type: bool + monitor_policy: + description: + - The name of the IP SLA Monitoring Policy to bind to the L4-L7 Redirect Policy. + - To remove an existing binding to an IP SLA Monitoring Policy, submit a request with I(state=present) and no I(monitor_policy) value. + type: str + aliases: [ sla, sla_policy ] + rewrite_source_mac: + description: + - Whether to rewrite the source MAC address of forwarded traffic. + - The APIC defaults to C(false) when unset during creation. + type: bool + 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 I(tenant) must exist before using this module in your playbook. + The M(cisco.aci.aci_tenant) module can be used for this. +seealso: +- name: APIC Management Information Model reference + description: More information about the internal APIC class, B(vns:SvcRedirectPol) + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Tim Cragg (@timcragg) +""" + +EXAMPLES = r""" +- name: Add a new Policy Based Redirect + cisco.aci.aci_l4l7_policy_based_redirect: + host: apic + username: admin + password: SomeSecretPassword + tenant: my_tenant + policy_name: my_pbr_policy + dest_type: l3 + hash_algorithm: destination_ip + resilient_hash: yes + state: present + delegate_to: localhost + +- name: Delete a Policy Based Redirect + cisco.aci.aci_l4l7_policy_based_redirect: + host: apic + username: admin + password: SomeSecretPassword + tenant: my_tenant + policy_name: my_pbr_policy + state: absent + delegate_to: localhost + +- name: Query a Policy Based Redirect + cisco.aci.aci_l4l7_policy_based_redirect: + host: apic + username: admin + password: SomeSecretPassword + tenant: my_tenant + policy_name: my_pbr_policy + state: query + delegate_to: localhost + register: query_result + +- name: Query all Policy Based Redirects + cisco.aci.aci_l4l7_policy_based_redirect: + 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: '' +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 L4L7_HASH_ALGORITHMS_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"]), + policy_name=dict(type="str"), + description=dict(type="str"), + dest_type=dict(type="str", choices=["l1", "l2", "l3"]), + hash_algorithm=dict(type="str", choices=["source_ip", "destination_ip", "ip_and_protocol"]), + threshold_enable=dict(type="bool"), + max_threshold=dict(type="int"), + min_threshold=dict(type="int"), + threshold_down_action=dict(type="str", choices=["permit", "deny"]), + resilient_hash=dict(type="bool"), + pod_aware=dict(type="bool"), + anycast_enabled=dict(type="bool"), + monitor_policy=dict(type="str", aliases=["sla", "sla_policy"]), + rewrite_source_mac=dict(type="bool"), + state=dict(type="str", default="present", choices=["absent", "present", "query"]), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_if=[ + ["state", "absent", ["tenant", "policy_name"]], + ["state", "present", ["tenant", "policy_name"]], + ], + ) + aci = ACIModule(module) + + tenant = module.params.get("tenant") + state = module.params.get("state") + policy_name = module.params.get("policy_name") + description = module.params.get("description") + dest_type = module.params.get("dest_type").upper() if module.params.get("dest_type") is not None else None + hash_algorithm = L4L7_HASH_ALGORITHMS_MAPPING.get(module.params.get("hash_algorithm")) + threshold_enable = aci.boolean(module.params.get("threshold_enable")) + max_threshold = module.params.get("max_threshold") + min_threshold = module.params.get("min_threshold") + threshold_down_action = module.params.get("threshold_down_action") + resilient_hash = aci.boolean(module.params.get("resilient_hash")) + pod_aware = aci.boolean(module.params.get("pod_aware")) + anycast_enabled = aci.boolean(module.params.get("anycast_enabled")) + monitor_policy = module.params.get("monitor_policy") + rewrite_source_mac = aci.boolean(module.params.get("rewrite_source_mac")) + + 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="vnsSvcRedirectPol", + aci_rn="svcCont/svcRedirectPol-{0}".format(policy_name), + module_object=policy_name, + target_filter={"name": policy_name}, + ), + child_classes=["vnsRsIPSLAMonitoringPol"], + ) + + aci.get_existing() + + if state == "present": + child_configs = [] + if monitor_policy is not None: + monitor_tdn = "uni/tn-{0}/ipslaMonitoringPol-{1}".format(tenant, monitor_policy) + child_configs.append({"vnsRsIPSLAMonitoringPol": {"attributes": {"tDn": monitor_tdn}}}) + else: + monitor_tdn = None + if isinstance(aci.existing, list) and len(aci.existing) > 0: + for child in aci.existing[0].get("vnsSvcRedirectPol", {}).get("children", {}): + if child.get("vnsRsIPSLAMonitoringPol") and child.get("vnsRsIPSLAMonitoringPol").get("attributes").get("tDn") != monitor_tdn: + child_configs.append( + { + "vnsRsIPSLAMonitoringPol": { + "attributes": { + "dn": child.get("vnsRsIPSLAMonitoringPol").get("attributes").get("dn"), + "status": "deleted", + } + } + } + ) + aci.payload( + aci_class="vnsSvcRedirectPol", + class_config=dict( + name=policy_name, + descr=description, + destType=dest_type, + hashingAlgorithm=hash_algorithm, + maxThresholdPercent=max_threshold, + minThresholdPercent=min_threshold, + programLocalPodOnly=pod_aware, + resilientHashEnabled=resilient_hash, + thresholdDownAction=threshold_down_action, + thresholdEnable=threshold_enable, + AnycastEnabled=anycast_enabled, + srcMacRewriteEnabled=rewrite_source_mac, + ), + child_configs=child_configs, + ) + aci.get_diff(aci_class="vnsSvcRedirectPol") + + aci.post_config() + + elif state == "absent": + aci.delete_config() + + aci.exit_json() + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/aci_l4l7_policy_based_redirect_dest.py b/plugins/modules/aci_l4l7_policy_based_redirect_dest.py new file mode 100644 index 000000000..8661b6ff9 --- /dev/null +++ b/plugins/modules/aci_l4l7_policy_based_redirect_dest.py @@ -0,0 +1,390 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# 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 = r""" +--- +module: aci_l4l7_policy_based_redirect_dest +short_description: Manage L4-L7 Policy Based Redirect Destinations (vns:RedirectDest and vns:L1L2RedirectDest) +description: +- Manage L4-L7 Policy Based Redirect Destinations +options: + tenant: + description: + - The name of an existing tenant. + type: str + aliases: [ tenant_name ] + policy: + description: + - The name of an existing Policy Based Redirect Policy. + type: str + aliases: [ policy_name ] + ip: + description: + - The destination IP for redirection. + - Only used if I(dest_type=l3) + aliases: [ redirect_ip ] + type: str + additional_ip: + description: + - The Additional IP Address for the Destination. + - Only used if I(dest_type=l3) + type: str + logical_dev: + description: + - The destination Logical Device for redirection. + - Only used if I(dest_type=l1/l2) + type: str + concrete_dev: + description: + - The destination Concrete Device for redirection. + - Only used if I(dest_type=l1/l2) + type: str + concrete_intf: + description: + - The destination Concrete Interface for redirection. + - Only used if I(dest_type=l1/l2) + type: str + mac: + description: + - The destination MAC address for redirection. + type: str + aliases: [ redirect_mac ] + dest_name: + description: + - The name for Policy Based Redirect destination. + type: str + dest_type: + description: + - The destination type. + type: str + choices: [ l1/l2, l3 ] + default: l3 + pod_id: + description: + - The Pod ID to deploy Policy Based Redirect destination on. + - The APIC defaults to C(1) when unset during creation. + type: int + health_group: + description: + - The Health Group to bind the Policy Based Redirection Destination to. + - To remove an existing binding from a Health Group, submit a request with I(state=present) and no I(health_group) value. + 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(tenant) and I(policy) must exist before using this module in your playbook. + The M(cisco.aci.aci_tenant) and M(cisco.aci.aci_l4l7_policy_based_redirect) modules can be used for this. +seealso: +- module: aci_l4l7_policy_based_redirect +- name: APIC Management Information Model reference + description: More information about the internal APIC class B(vns:RedirectDest) + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Tim Cragg (@timcragg) +""" + +EXAMPLES = r""" +- name: Add destination to a Policy Based Redirect Policy + cisco.aci.aci_l4l7_policy_based_redirect_dest: + host: apic + username: admin + password: SomeSecretPassword + tenant: my_tenant + policy: my_pbr_policy + dest_type: l3 + ip: 192.168.10.1 + mac: AB:CD:EF:12:34:56 + dest_name: redirect_dest + pod_id: 1 + state: present + delegate_to: localhost + +- name: Remove destination from a Policy Based Redirect Policy + cisco.aci.aci_l4l7_policy_based_redirect_dest: + host: apic + username: admin + password: SomeSecretPassword + tenant: my_tenant + policy: my_pbr_policy + state: absent + delegate_to: localhost + +- name: Query destinations for a Policy Based Redirect Policy + cisco.aci.aci_l4l7_policy_based_redirect_dest: + host: apic + username: admin + password: SomeSecretPassword + tenant: my_tenant + policy: my_pbr_policy + state: query + delegate_to: localhost + register: query_result + +- name: Query destinations for all Policy Based Redirect Policies + cisco.aci.aci_l4l7_policy_based_redirect_dest: + 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: '' +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( + tenant=dict(type="str", aliases=["tenant_name"]), + policy=dict(type="str", aliases=["policy_name"]), + ip=dict(type="str", aliases=["redirect_ip"]), + additional_ip=dict(type="str"), + mac=dict(type="str", aliases=["redirect_mac"]), + logical_dev=dict(type="str"), + concrete_dev=dict(type="str"), + concrete_intf=dict(type="str"), + dest_name=dict(type="str"), + dest_type=dict(type="str", default="l3", choices=["l1/l2", "l3"]), + health_group=dict(type="str"), + state=dict(type="str", default="present", choices=["absent", "present", "query"]), + pod_id=dict(type="int"), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_if=[["state", "absent", ["tenant", "policy"]], ["state", "present", ["tenant", "policy"]]], + ) + + aci = ACIModule(module) + + tenant = module.params.get("tenant") + state = module.params.get("state") + policy = module.params.get("policy") + ip = module.params.get("ip") + additional_ip = module.params.get("additional_ip") + mac = module.params.get("mac") + logical_dev = module.params.get("logical_dev") + concrete_dev = module.params.get("concrete_dev") + concrete_intf = module.params.get("concrete_intf") + dest_name = module.params.get("dest_name") + dest_type = module.params.get("dest_type") + health_group = module.params.get("health_group") + state = module.params.get("state") + pod_id = module.params.get("pod_id") + + if dest_type == "l3": + aci_class = "vnsRedirectDest" + aci_rn = "RedirectDest_ip-[{0}]".format(ip) + module_object = ip + target_filter = {"ip": ip} + child_classes = ["vnsRsRedirectHealthGroup"] + redirect_hg_class = "vnsRsRedirectHealthGroup" + elif dest_type == "l1/l2": + aci_class = "vnsL1L2RedirectDest" + aci_rn = "L1L2RedirectDest-[{0}]".format(dest_name) + module_object = dest_name + target_filter = {"destName": dest_name} + child_classes = ["vnsRsL1L2RedirectHealthGroup", "vnsRsToCIf"] + redirect_hg_class = "vnsRsL1L2RedirectHealthGroup" + + 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="vnsSvcRedirectPol", + aci_rn="svcCont/svcRedirectPol-{0}".format(policy), + module_object=policy, + target_filter={"name": policy}, + ), + subclass_2=dict( + aci_class=aci_class, + aci_rn=aci_rn, + module_object=module_object, + target_filter=target_filter, + ), + child_classes=child_classes, + ) + aci.get_existing() + + if state == "present": + if dest_type == "l1/l2" and additional_ip is not None: + aci.fail_json(msg="You cannot provide an additional_ip when configuring an l1/l2 destination") + elif dest_type == "l3" and (logical_dev, concrete_dev, concrete_intf) != (None, None, None): + aci.fail_json(msg="You cannot provide a logical_dev, concrete_dev or concrete_intf when configuring an l3 destination") + elif dest_type == "l1/l2" and (logical_dev, concrete_dev, concrete_intf) == (None, None, None): + aci.fail_json(msg="You must provide a logical_dev, concrete_dev and concrete_intf when configuring an l1/l2 destination") + elif dest_type == "l1/l2" and ip is not None: + aci.fail_json(msg="You cannot provide an ip when configuring an l1/l2 destination") + if dest_type == "l3": + child_configs = [] + elif dest_type == "l1/l2": + child_configs = [ + {"vnsRsToCIf": {"attributes": {"tDn": "uni/tn-{0}/lDevVip-{1}/cDev-{2}/cIf-[{3}]".format(tenant, logical_dev, concrete_dev, concrete_intf)}}} + ] + if health_group is not None: + health_group_tdn = "uni/tn-{0}/svcCont/redirectHealthGroup-{1}".format(tenant, health_group) + child_configs.append({redirect_hg_class: {"attributes": {"tDn": health_group_tdn}}}) + else: + health_group_tdn = None + if isinstance(aci.existing, list) and len(aci.existing) > 0: + for child in aci.existing[0].get(aci_class, {}).get("children", {}): + if child.get(redirect_hg_class) and child.get(redirect_hg_class).get("attributes").get("tDn") != health_group_tdn: + child_configs.append( + { + redirect_hg_class: { + "attributes": { + "dn": child.get(redirect_hg_class).get("attributes").get("dn"), + "status": "deleted", + } + } + } + ) + aci.payload( + aci_class=aci_class, + class_config=dict(ip=ip, mac=mac, destName=dest_name, podId=pod_id, ip2=additional_ip), + child_configs=child_configs, + ) + aci.get_diff(aci_class=aci_class) + + aci.post_config() + + elif state == "absent": + aci.delete_config() + + aci.exit_json() + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/aci_l4l7_redirect_health_group.py b/plugins/modules/aci_l4l7_redirect_health_group.py new file mode 100644 index 000000000..9cc8cff46 --- /dev/null +++ b/plugins/modules/aci_l4l7_redirect_health_group.py @@ -0,0 +1,260 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# 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 = r""" +--- +module: aci_l4l7_redirect_health_group +short_description: Manage L4-L7 Redirect Health Groups (vns:RedirectHealthGroup) +description: +- Manage L4-L7 Redirect Health Groups. +options: + tenant: + description: + - The name of an existing tenant. + type: str + aliases: [ tenant_name ] + health_group: + description: + - The name of the Health Group. + 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 I(tenant) must exist before using this module in your playbook. + The M(cisco.aci.aci_tenant) module can be used for this. +seealso: +- name: APIC Management Information Model reference + description: More information about the internal APIC class B(vns:RedirectHealthGroup) + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Tim Cragg (@timcragg) +""" + +EXAMPLES = r""" +- name: Add a new Redirect Health Group + cisco.aci.aci_l4l7_redirect_health_group: + host: apic + username: admin + password: SomeSecretPassword + tenant: my_tenant + health_group: my_health_group + state: present + delegate_to: localhost + +- name: Delete a Redirect Health Group + cisco.aci.aci_l4l7_redirect_health_group: + host: apic + username: admin + password: SomeSecretPassword + tenant: my_tenant + health_group: my_health_group + state: absent + delegate_to: localhost + +- name: Query a Redirect Health Group + cisco.aci.aci_l4l7_redirect_health_group: + host: apic + username: admin + password: SomeSecretPassword + tenant: my_tenant + health_group: my_health_group + state: query + delegate_to: localhost + register: query_result + +- name: Query all Redirect Health Groups + cisco.aci.aci_l4l7_redirect_health_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: '' +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"]), + state=dict(type="str", default="present", choices=["absent", "present", "query"]), + health_group=dict(type="str"), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_if=[ + ["state", "absent", ["tenant", "health_group"]], + ["state", "present", ["tenant", "health_group"]], + ], + ) + + tenant = module.params.get("tenant") + state = module.params.get("state") + health_group = module.params.get("health_group") + + 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="vnsRedirectHealthGroup", + aci_rn="svcCont/redirectHealthGroup-{0}".format(health_group), + module_object=health_group, + target_filter={"name": health_group}, + ), + ) + aci.get_existing() + + if state == "present": + aci.payload( + aci_class="vnsRedirectHealthGroup", + class_config=dict(name=health_group), + ) + aci.get_diff(aci_class="vnsRedirectHealthGroup") + + aci.post_config() + + elif state == "absent": + aci.delete_config() + + aci.exit_json() + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/aci_l4l7_service_graph_template.py b/plugins/modules/aci_l4l7_service_graph_template.py new file mode 100644 index 000000000..f2d7ad822 --- /dev/null +++ b/plugins/modules/aci_l4l7_service_graph_template.py @@ -0,0 +1,266 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# 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 = r""" +--- +module: aci_l4l7_service_graph_template +short_description: Manage L4-L7 Service Graph Templates (vns:AbsGraph) +description: +- Manage L4-L7 Service Graph Templates on Cisco ACI fabrics. +options: + tenant: + description: + - The name of an existing tenant. + type: str + aliases: [ tenant_name ] + service_graph: + description: + - The name of Service Graph Template. + type: str + ui_template_type: + description: + - The UI Template Type. + 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 + +seealso: +- name: APIC Management Information Model reference + description: More information about the internal APIC class B(vns:AbsGraph) + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Tim Cragg (@timcragg) +""" + +EXAMPLES = r""" +- name: Add a new L4-L7 Service Graph Template + cisco.aci.aci_l4l7_service_graph_template: + host: apic + username: admin + password: SomeSecretPassword + tenant: my_tenant + service_graph: my_service_graph + state: present + delegate_to: localhost + +- name: Delete a Service Graph Template + cisco.aci.aci_l4l7_service_graph_template: + host: apic + username: admin + password: SomeSecretPassword + tenant: my_tenant + service_graph: my_service_graph + state: absent + delegate_to: localhost + +- name: Query a Service Graph Template + cisco.aci.aci_l4l7_service_graph_template: + host: apic + username: admin + password: SomeSecretPassword + tenant: my_tenant + service_graph: my_service_graph + state: query + register: query_result + delegate_to: localhost + +- name: Query all Service Graph Templates + cisco.aci.aci_l4l7_service_graph_template: + 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: '' +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"]), + service_graph=dict(type="str"), + state=dict(type="str", default="present", choices=["absent", "present", "query"]), + ui_template_type=dict(type="str"), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_if=[ + ["state", "absent", ["tenant", "service_graph"]], + ["state", "present", ["tenant", "service_graph"]], + ], + ) + + tenant = module.params.get("tenant") + service_graph = module.params.get("service_graph") + state = module.params.get("state") + ui_template_type = module.params.get("ui_template_type") + + 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="vnsAbsGraph", + aci_rn="AbsGraph-{0}".format(service_graph), + module_object=service_graph, + target_filter={"name": service_graph}, + ), + child_classes=["vnsAbsTermNodeProv", "vnsAbsTermNodeCon"], + ) + + aci.get_existing() + + if state == "present": + aci.payload( + aci_class="vnsAbsGraph", + class_config=dict(name=service_graph, uiTemplateType=ui_template_type), + ) + aci.get_diff(aci_class="vnsAbsGraph") + + aci.post_config() + + elif state == "absent": + aci.delete_config() + + aci.exit_json() + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/aci_l4l7_service_graph_template_abs_conn.py b/plugins/modules/aci_l4l7_service_graph_template_abs_conn.py new file mode 100644 index 000000000..2c9131999 --- /dev/null +++ b/plugins/modules/aci_l4l7_service_graph_template_abs_conn.py @@ -0,0 +1,301 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# 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 = r""" +--- +module: aci_l4l7_service_graph_template_abs_conn +short_description: Manage L4-L7 Service Graph Template Abs Connections (vns:AbsConnection) +description: +- Manage Manage L4-L7 Service Graph Template Connections. +options: + tenant: + description: + - The name of an existing tenant. + type: str + aliases: [ tenant_name ] + service_graph: + description: + - The name of an existing Service Graph. + type: str + connection_name: + description: + - The name of the connection, e.g C1 or C2. + type: str + direct_connect: + description: + - Whether to enable direct connections. + - The APIC defaults to C(false) when unset during creation. + type: bool + unicast_route: + description: + - Whether to enable unicast routing. + - The APIC defaults to C(true) when unset during creation. + type: bool + adjacency_type: + description: + - Whether the adjacecncy Layer2 or Layer3. + - The APIC defaults to C(l2) when unset during creation. + type: str + choices: [ l2, l3 ] + 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 I(tenant) and I(service_graph) must exist before using this module in your playbook. + The M(cisco.aci.aci_tenant) and M(cisco.aci.aci_l4l7_service_graph_template_node) modules can be used for this. + +seealso: +- module: aci_l4l7_service_graph_template +- name: APIC Management Information Model reference + description: More information about the internal APIC class, B(vns:AbsConnection) + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Tim Cragg (@timcragg) +""" + +EXAMPLES = r""" +- name: Add a new connection + cisco.aci.aci_l4l7_service_graph_template_abs_conn: + host: apic + username: admin + password: SomeSecretPassword + tenant: my_tenant + service_graph: my_graph + connection_name: C1 + state: present + delegate_to: localhost + +- name: Delete a connection + cisco.aci.aci_l4l7_service_graph_template_abs_conn: + host: apic + username: admin + password: SomeSecretPassword + tenant: my_tenant + service_graph: my_graph + connection_name: C1 + state: absent + delegate_to: localhost + +- name: Query a connection + cisco.aci.aci_l4l7_service_graph_template_abs_conn: + host: apic + username: admin + password: SomeSecretPassword + tenant: my_tenant + service_graph: my_graph + connection_name: C1 + state: query + delegate_to: localhost + register: query_result + +- name: Query all connections + cisco.aci.aci_l4l7_service_graph_template_abs_conn: + 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: '' +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"]), + service_graph=dict(type="str"), + state=dict(type="str", default="present", choices=["absent", "present", "query"]), + connection_name=dict(type="str"), + direct_connect=dict(type="bool"), + unicast_route=dict(type="bool"), + adjacency_type=dict(type="str", choices=["l2", "l3"]), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_if=[ + ["state", "absent", ["tenant", "service_graph", "connection_name"]], + ["state", "present", ["tenant", "service_graph", "connection_name"]], + ], + ) + + aci = ACIModule(module) + + tenant = module.params.get("tenant") + service_graph = module.params.get("service_graph") + state = module.params.get("state") + connection_name = module.params.get("connection_name") + unicast_route = aci.boolean(module.params.get("unicast_route")) + adjacency_type = module.params.get("adjacency_type").upper() if module.params.get("adjacency_type") is not None else None + direct_connect = aci.boolean(module.params.get("direct_connect")) + + 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="vnsAbsGraph", + aci_rn="AbsGraph-{0}".format(service_graph), + module_object=service_graph, + target_filter={"name": service_graph}, + ), + subclass_2=dict( + aci_class="vnsAbsConnection", + aci_rn="AbsConnection-{0}".format(connection_name), + module_object=connection_name, + target_filter={"name": connection_name}, + ), + ) + + aci.get_existing() + + if state == "present": + aci.payload( + aci_class="vnsAbsConnection", + class_config=dict(name=connection_name, adjType=adjacency_type, directConnect=direct_connect, unicastRoute=unicast_route), + ) + aci.get_diff(aci_class="vnsAbsConnection") + + aci.post_config() + + elif state == "absent": + aci.delete_config() + + aci.exit_json() + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/aci_l4l7_service_graph_template_abs_connection_conns.py b/plugins/modules/aci_l4l7_service_graph_template_abs_connection_conns.py new file mode 100644 index 000000000..34e96e7ff --- /dev/null +++ b/plugins/modules/aci_l4l7_service_graph_template_abs_connection_conns.py @@ -0,0 +1,315 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# 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 = r""" +--- +module: aci_l4l7_service_graph_template_abs_connection_conns +short_description: Manage L4-L7 Service Graph Template Connections (vns:RsAbsConnectionConns) +description: +- Manage Manage L4-L7 Service Graph Template Connections. +options: + tenant: + description: + - The name of an existing tenant. + type: str + aliases: [ tenant_name ] + service_graph: + description: + - The name of an existing Service Graph. + type: str + connection_name: + description: + - The name of an existing vns:AbsConnection object. + type: str + direction: + description: + - The direction of the connection. + - If this links to a terminal node, both vns:RsAbsConnectionConns will use the same direction. + - Otherwise one vns:RsAbsConnectionConns will be consumer, and the other will be provider. + type: str + choices: [ consumer, provider ] + connected_node: + description: + - The name of an existing node. + - Omit this variable for connections to terminal nodes. + 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(tenant), I(service_graph) and I(connection_name) must exist before using this module in your playbook. + The M(cisco.aci.aci_tenant), M(cisco.aci.aci_l4l7_service_graph_template_node) + and M(cisco.aci.aci_l4l7_service_graph_template_abs_conn) modules can be used for this. + +seealso: +- module: aci_l4l7_service_graph_template +- module: aci_l4l7_service_graph_template_abs_connection +- module: aci_l4l7_service_graph_template_node +- name: APIC Management Information Model reference + description: More information about the internal APIC class, B(vns:RsAbsConnectionConns) + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Tim Cragg (@timcragg) +""" + +EXAMPLES = r""" +- name: Add a new connection to a node + cisco.aci.aci_l4l7_service_graph_template_abs_connection_conns: + host: apic + username: admin + password: SomeSecretPassword + tenant: my_tenant + service_graph: test-graph + direction: provider + connection_name: C1 + connected_node: my_node + state: present + delegate_to: localhost + +- name: Add a new connection to a terminal node + cisco.aci.aci_l4l7_service_graph_template_abs_connection_conns: + host: apic + username: admin + password: SomeSecretPassword + tenant: my_tenant + service_graph: test-graph + direction: provider + connection_name: C1 + state: present + delegate_to: localhost + +- name: Delete a connection + cisco.aci.aci_l4l7_service_graph_template_abs_connection_conns: + host: apic + username: admin + password: SomeSecretPassword + tenant: my_tenant + service_graph: test-graph + direction: provider + connection_name: C1 + state: absent + delegate_to: localhost + +- name: Query a connection + cisco.aci.aci_l4l7_service_graph_template_abs_connection_conns: + host: apic + username: admin + password: SomeSecretPassword + tenant: my_tenant + service_graph: test-graph + direction: provider + connection_name: C1 + 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: '' +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( + tenant=dict(type="str", aliases=["tenant_name"]), + service_graph=dict(type="str"), + state=dict(type="str", default="present", choices=["absent", "present", "query"]), + connection_name=dict(type="str"), + direction=dict(type="str", choices=["consumer", "provider"]), + connected_node=dict(type="str"), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_if=[ + ["state", "absent", ["tenant", "service_graph", "connection_name", "direction"]], + ["state", "present", ["tenant", "service_graph", "connection_name", "direction"]], + ], + ) + + tenant = module.params.get("tenant") + service_graph = module.params.get("service_graph") + state = module.params.get("state") + connection_name = module.params.get("connection_name") + direction = module.params.get("direction") + connected_node = module.params.get("connected_node") + + aci = ACIModule(module) + if connected_node: + tdn = "uni/tn-{0}/AbsGraph-{1}/AbsNode-{2}/AbsFConn-{3}".format(tenant, service_graph, connected_node, direction) + elif direction == "consumer": + tdn = "uni/tn-{0}/AbsGraph-{1}/AbsTermNodeCon-T1/AbsTConn".format(tenant, service_graph) + elif direction == "provider": + tdn = "uni/tn-{0}/AbsGraph-{1}/AbsTermNodeProv-T2/AbsTConn".format(tenant, service_graph) + + 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="vnsAbsGraph", + aci_rn="AbsGraph-{0}".format(service_graph), + module_object=service_graph, + target_filter={"name": service_graph}, + ), + subclass_2=dict( + aci_class="vnsAbsConnection", + aci_rn="AbsConnection-{0}".format(connection_name), + module_object=connection_name, + target_filter={"name": connection_name}, + ), + subclass_3=dict( + aci_class="vnsRsAbsConnectionConns", + aci_rn="rsabsConnectionConns-[{0}]".format(tdn), + module_object=tdn, + target_filter={"tDn": tdn}, + ), + ) + + aci.get_existing() + + if state == "present": + aci.payload( + aci_class="vnsRsAbsConnectionConns", + class_config=dict(tDn=tdn), + ) + aci.get_diff(aci_class="vnsRsAbsConnectionConns") + + aci.post_config() + + elif state == "absent": + aci.delete_config() + + aci.exit_json() + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/aci_l4l7_service_graph_template_func_conn.py b/plugins/modules/aci_l4l7_service_graph_template_func_conn.py new file mode 100644 index 000000000..89aabe308 --- /dev/null +++ b/plugins/modules/aci_l4l7_service_graph_template_func_conn.py @@ -0,0 +1,296 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# 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 = r""" +--- +module: aci_l4l7_service_graph_template_func_conn +short_description: Manage L4-L7 Service Graph Templates Functional Connections (vns:AbsFuncConn) +description: +- Manage Manage L4-L7 Service Graph Templates Functional Connections. +options: + tenant: + description: + - The name of an existing tenant. + type: str + aliases: [ tenant_name ] + service_graph: + description: + - The name of an existing Service Graph. + type: str + node: + description: + - The name an existing Service Graph Node. + type: str + connection_name: + description: + - Whether this Functional Connection is the consumer or provider. + type: str + choices: [ consumer, provider ] + 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 I(tenant), I(service_graph) and I(node) must exist before using this module in your playbook. + The M(cisco.aci.aci_tenant), M(cisco.aci.aci_l4l7_service_graph_template) and M(cisco.aci.aci_l4l7_service_graph_template_node) modules can be used for this. + +seealso: +- module: aci_l4l7_service_graph_template +- module: aci_l4l7_service_graph_template_node +- name: APIC Management Information Model reference + description: More information about the internal APIC classes, B(vns:AbsFuncConn) + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Tim Cragg (@timcragg) +""" + +EXAMPLES = r""" +- name: Add a new Consumer Functional Connection + cisco.aci.aci_l4l7_service_graph_template_func_conn: + host: apic + username: admin + password: SomeSecretPassword + tenant: my_tenant + service_graph: test-graph + node: test-node + connection_name: consumer + state: present + delegate_to: localhost + +- name: Delete a Functional Connection + cisco.aci.aci_l4l7_service_graph_template_func_conn: + host: apic + username: admin + password: SomeSecretPassword + tenant: my_tenant + service_graph: test-graph + node: test-node + connection_name: consumer + state: absent + delegate_to: localhost + +- name: Query a Functional Connection + cisco.aci.aci_l4l7_service_graph_template_func_conn: + host: apic + username: admin + password: SomeSecretPassword + tenant: my_tenant + service_graph: test-graph + node: test-node + connection_name: consumer + state: query + delegate_to: localhost + register: query_result + +- name: Query all Functional Connections + cisco.aci.aci_l4l7_service_graph_template_func_conn: + 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: '' +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"]), + service_graph=dict(type="str"), + node=dict(type="str"), + state=dict(type="str", default="present", choices=["absent", "present", "query"]), + connection_name=dict(type="str", choices=["consumer", "provider"]), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_if=[ + ["state", "absent", ["tenant", "service_graph", "node", "connection_name"]], + ["state", "present", ["tenant", "service_graph", "node", "connection_name"]], + ], + ) + + tenant = module.params.get("tenant") + service_graph = module.params.get("service_graph") + node = module.params.get("node") + state = module.params.get("state") + connection_name = module.params.get("connection_name") + + 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="vnsAbsGraph", + aci_rn="AbsGraph-{0}".format(service_graph), + module_object=service_graph, + target_filter={"name": service_graph}, + ), + subclass_2=dict( + aci_class="vnsAbsNode", + aci_rn="AbsNode-{0}".format(node), + module_object=node, + target_filter={"name": node}, + ), + subclass_3=dict( + aci_class="vnsAbsFuncConn", + aci_rn="AbsFConn-{0}".format(connection_name), + module_object=connection_name, + target_filter={"name": connection_name}, + ), + ) + + aci.get_existing() + + if state == "present": + aci.payload( + aci_class="vnsAbsFuncConn", + class_config=dict(name=connection_name), + ) + aci.get_diff(aci_class="vnsAbsFuncConn") + + aci.post_config() + + elif state == "absent": + aci.delete_config() + + aci.exit_json() + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/aci_l4l7_service_graph_template_node.py b/plugins/modules/aci_l4l7_service_graph_template_node.py new file mode 100644 index 000000000..1ad7bf129 --- /dev/null +++ b/plugins/modules/aci_l4l7_service_graph_template_node.py @@ -0,0 +1,344 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# 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 = r""" +--- +module: aci_l4l7_service_graph_template_node +short_description: Manage L4-L7 Service Graph Templates Nodes (vns:AbsNode) +description: +- Manage Manage L4-L7 Service Graph Templates Nodes. +options: + tenant: + description: + - The name of an existing tenant. + type: str + aliases: [ tenant_name ] + service_graph: + description: + - The name of an existing Service Graph. + type: str + node: + description: + - The name of the Service Graph Template Node. + type: str + func_template_type: + description: + - The functional template type for the node. + - The APIC defaults to C(other) when unset during creation. + type: str + choices: [ fw_trans, fw_routed, adc_one_arm, adc_two_arm, other ] + func_type: + description: + - The type of connection. + - The APIC defaults to C(go_to) when unset during creation. + type: str + choices: [ go_to, go_through, l1, l2 ] + device: + description: + - The name of an existing logical device. + type: str + device_tenant: + description: + - The tenant the logical device exists under. + - This variable is only used if logical device and node exist within different tenants. + - Intended use case is when the device is in the C(common) tenant but the node is not. + type: str + managed: + description: + - Whether this device managed by the apic. + - The APIC defaults to C(true) when unset during creation. + type: bool + routing_mode: + description: + - The routing mode for the node. + 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 I(tenant) and I(service_graph) must exist before using this module in your playbook. + The M(cisco.aci.aci_tenant) and M(cisco.aci.aci_l4l7_service_graph_template_node) modules can be used for this. +seealso: +- module: aci_l4l7_service_graph_template +- module: aci_l4l7_device +- name: APIC Management Information Model reference + description: More information about the internal APIC classes B(vnsAbsNode) + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Tim Cragg (@timcragg) +""" + +EXAMPLES = r""" +- name: Add a new Service Graph Template Node + cisco.aci.aci_l4l7_service_graph_template_node: + host: apic + username: admin + password: SomeSecretPassword + tenant: my_tenant + service_graph: test-graph + node: test-node + func_template_type: adc_one_arm + func_type: GoTo + device: test-device + managed: no + routing_mode: Redirect + state: present + delegate_to: localhost + +- name: Delete a Service Graph Template Node + cisco.aci.aci_l4l7_service_graph_template_node: + host: apic + username: admin + password: SomeSecretPassword + tenant: my_tenant + service_graph: test-graph + node: test-node + state: absent + delegate_to: localhost + +- name: Query a Service Graph Template Node + cisco.aci.aci_l4l7_service_graph_template_node: + host: apic + username: admin + password: SomeSecretPassword + tenant: my_tenant + service_graph: test-graph + node: test-node + state: query + delegate_to: localhost + register: query_result + +- name: Query all Service Graph Template Nodes + cisco.aci.aci_l4l7_service_graph_template_node: + 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: '' +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 L4L7_FUNC_TYPES_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"]), + service_graph=dict(type="str"), + node=dict(type="str"), + state=dict(type="str", default="present", choices=["absent", "present", "query"]), + func_template_type=dict(type="str", choices=["fw_trans", "fw_routed", "adc_one_arm", "adc_two_arm", "other"]), + func_type=dict(type="str", choices=["go_to", "go_through", "l1", "l2"]), + device=dict(type="str"), + device_tenant=dict(type="str"), + managed=dict(type="bool"), + routing_mode=dict(type="str"), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_if=[ + ["state", "absent", ["tenant", "service_graph", "node"]], + ["state", "present", ["tenant", "service_graph", "node", "device"]], + ], + ) + + aci = ACIModule(module) + + tenant = module.params.get("tenant") + service_graph = module.params.get("service_graph") + node = module.params.get("node") + state = module.params.get("state") + func_template_type = module.params.get("func_template_type") + func_type = L4L7_FUNC_TYPES_MAPPING.get(module.params.get("func_type")) + device = module.params.get("device") + device_tenant = module.params.get("device_tenant") + managed = aci.boolean(module.params.get("managed")) + routing_mode = module.params.get("routing_mode") + + if func_template_type: + func_template_upper = func_template_type.upper() + else: + func_template_upper = None + + 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="vnsAbsGraph", + aci_rn="AbsGraph-{0}".format(service_graph), + module_object=service_graph, + target_filter={"name": service_graph}, + ), + subclass_2=dict( + aci_class="vnsAbsNode", + aci_rn="AbsNode-{0}".format(node), + module_object=node, + target_filter={"name": node}, + ), + child_classes=["vnsRsNodeToLDev"], + ) + + aci.get_existing() + if not device_tenant: + device_tenant = tenant + dev_tdn = "uni/tn-{0}/lDevVip-{1}".format(device_tenant, device) + + if state == "present": + aci.payload( + aci_class="vnsAbsNode", + class_config=dict(name=node, funcTemplateType=func_template_upper, funcType=func_type, managed=managed, routingMode=routing_mode), + child_configs=[ + dict( + vnsRsNodeToLDev=dict( + attributes=dict(tDn=dev_tdn), + ), + ), + ], + ) + aci.get_diff(aci_class="vnsAbsNode") + + aci.post_config() + + elif state == "absent": + aci.delete_config() + + aci.exit_json() + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/aci_l4l7_service_graph_template_term_node.py b/plugins/modules/aci_l4l7_service_graph_template_term_node.py new file mode 100644 index 000000000..de4ab3987 --- /dev/null +++ b/plugins/modules/aci_l4l7_service_graph_template_term_node.py @@ -0,0 +1,292 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# 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 = r""" +--- +module: aci_l4l7_service_graph_template_term_node +short_description: Manage L4-L7 SGT Term Nodes (vns:AbsTermNodeCon, vns:AbsTermNodeProv and vns:AbsTermConn) +description: +- Manage L4-L7 Service Graph Template Term Nodes on Cisco ACI fabrics. +options: + tenant: + description: + - The name of an existing tenant. + type: str + aliases: [ tenant_name ] + service_graph: + description: + - The name of an existing Service graph. + type: str + node_name: + description: + - The name of the Term Node. + type: str + choices: [ T1, T2 ] + 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 I(tenant) and I(service_graph) must exist before using this module in your playbook. + The M(cisco.aci.aci_tenant) and M(cisco.aci.aci_l4l7_service_graph_template_node) modules can be used for this. + +seealso: +- module: aci_l4l7_service_graph_template +- name: APIC Management Information Model reference + description: More information about the internal APIC classes, B(vns:AbsTermNodeCon), B(vns:AbsTermNodeProv), B(vns:AbsTermConn) + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Tim Cragg (@timcragg) +""" + +EXAMPLES = r""" +- name: Add a new Term Node + cisco.aci.aci_l4l7_service_graph_template_term_node: + host: apic + username: admin + password: SomeSecretPassword + tenant: my_tenant + service_graph: my_service_graph + node_name: T1 + state: present + delegate_to: localhost + +- name: Delete a Term Node + cisco.aci.aci_l4l7_service_graph_template_term_node: + host: apic + username: admin + password: SomeSecretPassword + tenant: my_tenant + service_graph: my_service_graph + node_name: T1 + state: absent + delegate_to: localhost + +- name: Query a Term Node + cisco.aci.aci_l4l7_service_graph_template_term_node: + host: apic + username: admin + password: SomeSecretPassword + tenant: my_tenant + service_graph: my_service_graph + node_name: T1 + 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: '' +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"]), + service_graph=dict(type="str"), + state=dict(type="str", default="present", choices=["absent", "present", "query"]), + node_name=dict(type="str", choices=["T1", "T2"]), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_if=[ + ["state", "absent", ["tenant", "service_graph", "node_name"]], + ["state", "present", ["tenant", "service_graph", "node_name"]], + ], + ) + + tenant = module.params.get("tenant") + service_graph = module.params.get("service_graph") + state = module.params.get("state") + node_name = module.params.get("node_name") + + aci = ACIModule(module) + + if node_name == "T1": + term_class = "vnsAbsTermNodeCon" + term_rn = "AbsTermNodeCon-T1" + term_module_object = "T1" + term_target_filter = {"name": "T1"} + name = "T1" + elif node_name == "T2": + term_class = "vnsAbsTermNodeProv" + term_rn = "AbsTermNodeProv-T2" + term_module_object = "T2" + term_target_filter = {"name": "T2"} + name = "T2" + + 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="vnsAbsGraph", + aci_rn="AbsGraph-{0}".format(service_graph), + module_object=service_graph, + target_filter={"name": service_graph}, + ), + subclass_2=dict( + aci_class=term_class, + aci_rn=term_rn, + module_object=term_module_object, + target_filter=term_target_filter, + ), + child_classes=["vnsAbsTermConn"], + ) + + aci.get_existing() + + if state == "present": + aci.payload( + aci_class=term_class, + class_config=dict(name=name), + child_configs=[ + dict( + vnsAbsTermConn=dict( + attributes=dict(name="1"), + ), + ), + ], + ) + aci.get_diff(aci_class=term_class) + + aci.post_config() + + elif state == "absent": + aci.delete_config() + + aci.exit_json() + + +if __name__ == "__main__": + main() diff --git a/tests/integration/targets/aci_ip_sla_monitoring_policy/aliases b/tests/integration/targets/aci_ip_sla_monitoring_policy/aliases new file mode 100644 index 000000000..209b793f9 --- /dev/null +++ b/tests/integration/targets/aci_ip_sla_monitoring_policy/aliases @@ -0,0 +1,2 @@ +# No ACI simulator yet, so not enabled +# unsupported diff --git a/tests/integration/targets/aci_ip_sla_monitoring_policy/tasks/main.yml b/tests/integration/targets/aci_ip_sla_monitoring_policy/tasks/main.yml new file mode 100644 index 000000000..a898b05bc --- /dev/null +++ b/tests/integration/targets/aci_ip_sla_monitoring_policy/tasks/main.yml @@ -0,0 +1,252 @@ +# Test code for the ACI modules +# Copyright: (c) 2021, 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 + +# 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: '{{ aci_output_level | default("info") }}' + +- name: Query system information + cisco.aci.aci_system: + <<: *aci_info + id: 1 + state: query + register: version + +# CLEAN ENVIRONMENT +- name: Remove ansible_tenant if it already exists + cisco.aci.aci_tenant: + <<: *aci_info + name: ansible_tenant + state: absent + +# CREATE TENANT +- name: Create ansible_tenant + cisco.aci.aci_tenant: + <<: *aci_info + name: ansible_tenant + state: present + +# CREATE ICMP SLA POLICY +- name: Create ICMP SLA policy + cisco.aci.aci_ip_sla_monitoring_policy: + <<: *aci_info + tenant: ansible_tenant + sla_policy: ansible_sla + sla_type: icmp + frequency: 40 + multiplier: 6 + state: present + register: create_icmp_sla + +- name: Verify SLA creation + ansible.builtin.assert: + that: + - create_icmp_sla.current.0.fvIPSLAMonitoringPol.attributes.dn == "uni/tn-ansible_tenant/ipslaMonitoringPol-ansible_sla" + - create_icmp_sla.current.0.fvIPSLAMonitoringPol.attributes.name == "ansible_sla" + - create_icmp_sla.current.0.fvIPSLAMonitoringPol.attributes.slaDetectMultiplier == "6" + - create_icmp_sla.current.0.fvIPSLAMonitoringPol.attributes.slaFrequency == "40" + - create_icmp_sla.current.0.fvIPSLAMonitoringPol.attributes.slaPort == "0" + - create_icmp_sla.current.0.fvIPSLAMonitoringPol.attributes.slaType == "icmp" + +# CREATE ICMP SLA POLICY AGAIN TO TEST IDEMPOTENCE +- name: Create ICMP SLA policy again + cisco.aci.aci_ip_sla_monitoring_policy: + <<: *aci_info + tenant: ansible_tenant + sla_policy: ansible_sla + sla_type: icmp + frequency: 40 + multiplier: 6 + state: present + register: create_icmp_sla_again + +- name: Verify SLA creation idempotence + ansible.builtin.assert: + that: + - create_icmp_sla_again is not changed + +# CREATE SECOND SLA POLICY +- name: Create Second SLA policy again + cisco.aci.aci_ip_sla_monitoring_policy: + <<: *aci_info + tenant: ansible_tenant + sla_policy: second_ansible_sla + sla_type: icmp + frequency: 40 + multiplier: 6 + state: present + +# CREATE HTTP SLA POLICY +- name: Create HTTP SLA policy + cisco.aci.aci_ip_sla_monitoring_policy: + <<: *aci_info + tenant: ansible_tenant + sla_policy: ansible_http_sla + sla_type: http + frequency: 40 + multiplier: 6 + http_version: 1.1 + http_uri: /test + request_data_size: 100 + operation_timeout: 2100 + threshold: 2000 + state: present + when: version.current.0.topSystem.attributes.version is version('5.2', '>=') + register: http_sla + +- name: Verify HTTP SLA creation + ansible.builtin.assert: + that: + - http_sla.current.0.fvIPSLAMonitoringPol.attributes.dn == "uni/tn-ansible_tenant/ipslaMonitoringPol-ansible_http_sla" + - http_sla.current.0.fvIPSLAMonitoringPol.attributes.name == "ansible_http_sla" + - http_sla.current.0.fvIPSLAMonitoringPol.attributes.slaDetectMultiplier == "6" + - http_sla.current.0.fvIPSLAMonitoringPol.attributes.slaFrequency == "40" + - http_sla.current.0.fvIPSLAMonitoringPol.attributes.slaPort == "80" + - http_sla.current.0.fvIPSLAMonitoringPol.attributes.slaType == "http" + - http_sla.current.0.fvIPSLAMonitoringPol.attributes.reqDataSize == "100" + - http_sla.current.0.fvIPSLAMonitoringPol.attributes.timeout == "2100" + - http_sla.current.0.fvIPSLAMonitoringPol.attributes.threshold == "2000" + - http_sla.current.0.fvIPSLAMonitoringPol.attributes.httpVersion == "HTTP11" + - http_sla.current.0.fvIPSLAMonitoringPol.attributes.httpUri == "/test" + - http_sla.current.0.fvIPSLAMonitoringPol.attributes.httpMethod == "get" + when: version.current.0.topSystem.attributes.version is version('5.2', '>=') + +# TEST ERROR CHECKING +- name: Create ICMP Monitoring Policy with sla_port + cisco.aci.aci_ip_sla_monitoring_policy: + <<: *aci_info + tenant: ansible_tenant + sla_policy: ansible_http_sla + sla_type: icmp + sla_port: 8080 + state: present + ignore_errors: true + register: icmp_with_sla_port + +- name: Create ICMP Monitoring Policy with request_data_size + cisco.aci.aci_ip_sla_monitoring_policy: + <<: *aci_info + tenant: ansible_tenant + sla_policy: ansible_http_sla + sla_type: icmp + request_data_size: 2000 + state: present + ignore_errors: true + register: icmp_with_request_data_size + +- name: Verify Errors + ansible.builtin.assert: + that: + - icmp_with_sla_port is failed + - icmp_with_request_data_size is failed + - icmp_with_sla_port.msg == "Setting 'sla_port' is not allowed when 'sla_type' is not set to 'tcp'." + - icmp_with_request_data_size.msg == "Setting 'request_data_size' is not allowed when 'sla_type' is not set to 'http." + +# MODIFY SLA POLICY +- name: Convert ICMP SLA policy to TCP + cisco.aci.aci_ip_sla_monitoring_policy: + <<: *aci_info + tenant: ansible_tenant + sla_policy: ansible_sla + sla_type: tcp + sla_port: 8080 + frequency: 20 + multiplier: 5 + state: present + register: update_tcp_sla + +- name: Verify SLA update + ansible.builtin.assert: + that: + - update_tcp_sla.current.0.fvIPSLAMonitoringPol.attributes.dn == "uni/tn-ansible_tenant/ipslaMonitoringPol-ansible_sla" + - update_tcp_sla.current.0.fvIPSLAMonitoringPol.attributes.name == "ansible_sla" + - update_tcp_sla.current.0.fvIPSLAMonitoringPol.attributes.slaDetectMultiplier == "5" + - update_tcp_sla.current.0.fvIPSLAMonitoringPol.attributes.slaFrequency == "20" + - update_tcp_sla.current.0.fvIPSLAMonitoringPol.attributes.slaPort == "8080" + - update_tcp_sla.current.0.fvIPSLAMonitoringPol.attributes.slaType == "tcp" + +# QUERY IP SLA POLICY +- name: Query IP SLA monitor + cisco.aci.aci_ip_sla_monitoring_policy: + <<: *aci_info + tenant: ansible_tenant + sla_policy: ansible_sla + state: query + register: query_tcp_sla + +- name: Verify SLA query + ansible.builtin.assert: + that: + - query_tcp_sla is not changed + - query_tcp_sla.current.0.fvIPSLAMonitoringPol.attributes.dn == "uni/tn-ansible_tenant/ipslaMonitoringPol-ansible_sla" + - query_tcp_sla.current.0.fvIPSLAMonitoringPol.attributes.name == "ansible_sla" + - query_tcp_sla.current.0.fvIPSLAMonitoringPol.attributes.slaDetectMultiplier == "5" + - query_tcp_sla.current.0.fvIPSLAMonitoringPol.attributes.slaFrequency == "20" + - query_tcp_sla.current.0.fvIPSLAMonitoringPol.attributes.slaPort == "8080" + - query_tcp_sla.current.0.fvIPSLAMonitoringPol.attributes.slaType == "tcp" + +# QUERY ALL SLA POLICIES +- name: Query all IP SLA monitor policies + cisco.aci.aci_ip_sla_monitoring_policy: + <<: *aci_info + state: query + register: query_sla_all + +- name: Verify all SLA query + ansible.builtin.assert: + that: + - query_sla_all is not changed + - query_sla_all.current | length > 1 + +# DELETE SLA POLICY +- name: Remove IP SLA monitor + cisco.aci.aci_ip_sla_monitoring_policy: + <<: *aci_info + tenant: ansible_tenant + sla_policy: ansible_sla + state: absent + register: remove_tcp_sla + +- name: Verify IP SLA deletion + ansible.builtin.assert: + that: + - remove_tcp_sla is changed + - remove_tcp_sla.current == [] + - remove_tcp_sla.previous.0.fvIPSLAMonitoringPol.attributes.dn == "uni/tn-ansible_tenant/ipslaMonitoringPol-ansible_sla" + - remove_tcp_sla.previous.0.fvIPSLAMonitoringPol.attributes.name == "ansible_sla" + +# DELETE IP SLA POLICY AGAIN TO TEST IDEMPOTENCE +- name: Remove IP SLA monitor again + cisco.aci.aci_ip_sla_monitoring_policy: + <<: *aci_info + tenant: ansible_tenant + sla_policy: ansible_sla + state: absent + register: remove_tcp_sla_again + +- name: Verify IP SLA deletion + ansible.builtin.assert: + that: + - remove_tcp_sla_again is not changed + +# CLEAN UP +- name: remove ansible_tenant + cisco.aci.aci_tenant: + <<: *aci_info + name: ansible_tenant + state: absent diff --git a/tests/integration/targets/aci_l4l7_concrete_device/aliases b/tests/integration/targets/aci_l4l7_concrete_device/aliases new file mode 100644 index 000000000..209b793f9 --- /dev/null +++ b/tests/integration/targets/aci_l4l7_concrete_device/aliases @@ -0,0 +1,2 @@ +# No ACI simulator yet, so not enabled +# unsupported diff --git a/tests/integration/targets/aci_l4l7_concrete_device/tasks/main.yml b/tests/integration/targets/aci_l4l7_concrete_device/tasks/main.yml new file mode 100644 index 000000000..e8e112454 --- /dev/null +++ b/tests/integration/targets/aci_l4l7_concrete_device/tasks/main.yml @@ -0,0 +1,175 @@ +# Test code for the ACI modules +# Copyright: (c) 2021, 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 + +# 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: '{{ aci_output_level | default("info") }}' + +# CLEAN ENVIRONMENT +- name: Remove ansible_tenant if it already exists + cisco.aci.aci_tenant: + <<: *aci_info + name: ansible_tenant + state: absent + +- name: Remove ansible_phys_domain if it already exists + cisco.aci.aci_domain: + <<: *aci_info + domain: ansible_phys_dom + domain_type: phys + state: absent + +# CREATE TENANT +- name: Create ansible_tenant + cisco.aci.aci_tenant: + <<: *aci_info + name: ansible_tenant + state: present + +# CREATE DOMAIN +- name: Create ansible_phys_domain + cisco.aci.aci_domain: + <<: *aci_info + domain: ansible_phys_dom + domain_type: phys + state: present + +# CREATE L4-L7 LOGICAL DEVICE +- name: Create L4-L7 Device + cisco.aci.aci_l4l7_device: + <<: *aci_info + tenant: ansible_tenant + device: ansible_device + domain: ansible_phys_dom + func_type: go_to + context_aware: single + managed: false + dev_type: physical + svc_type: adc + trunking: false + prom_mode: true + state: present + +# ADD L4-L7 CONCRETE DEVICE +- name: Create L4-L7 Concrete Device + cisco.aci.aci_l4l7_concrete_device: + <<: *aci_info + tenant: ansible_tenant + device: ansible_device + concrete_device: ansible_concrete_device + state: present + register: add_l4l7_concrete_device + +- name: Verify L4-L7 Concrete Device Attributes + ansible.builtin.assert: + that: + - add_l4l7_concrete_device.current.0.vnsCDev.attributes.dn == "uni/tn-ansible_tenant/lDevVip-ansible_device/cDev-ansible_concrete_device" + - add_l4l7_concrete_device.current.0.vnsCDev.attributes.name == "ansible_concrete_device" + +# ADD L4-L7 CONCRETE DEVICE AGAIN TO CHECK IDEMPOTENCE +- name: Add L4-L7 Concrete Device again + cisco.aci.aci_l4l7_concrete_device: + <<: *aci_info + tenant: ansible_tenant + device: ansible_device + concrete_device: ansible_concrete_device + state: present + register: add_l4l7_concrete_device_again + +- name: Verify L4-L7 Concrete Device Attributes + ansible.builtin.assert: + that: + - add_l4l7_concrete_device_again is not changed + - add_l4l7_concrete_device_again.current.0.vnsCDev.attributes.dn == "uni/tn-ansible_tenant/lDevVip-ansible_device/cDev-ansible_concrete_device" + - add_l4l7_concrete_device_again.current.0.vnsCDev.attributes.name == "ansible_concrete_device" + +# QUERY L4-L7 CONCRETE DEVICE +- name: Query L4-L7 Concrete Device + cisco.aci.aci_l4l7_concrete_device: + <<: *aci_info + tenant: ansible_tenant + device: ansible_device + concrete_device: ansible_concrete_device + state: query + register: query_l4l7_concrete_device + +- name: Verify L4-L7 Concrete Device Attributes + ansible.builtin.assert: + that: + - query_l4l7_concrete_device is not changed + - query_l4l7_concrete_device.current.0.vnsCDev.attributes.dn == "uni/tn-ansible_tenant/lDevVip-ansible_device/cDev-ansible_concrete_device" + - query_l4l7_concrete_device.current.0.vnsCDev.attributes.name == "ansible_concrete_device" + +- name: Query All L4-L7 Concrete Devices + cisco.aci.aci_l4l7_concrete_device: + <<: *aci_info + state: query + register: query_l4l7_concrete_device_all + +- name: Verify L4-L7 Concrete Device Attributes + ansible.builtin.assert: + that: + - query_l4l7_concrete_device_all is not changed + +# DELETE L4-L7 CONCRETE DEVICE +- name: Remove L4-L7 Concrete Device + cisco.aci.aci_l4l7_concrete_device: + <<: *aci_info + tenant: ansible_tenant + device: ansible_device + concrete_device: ansible_concrete_device + state: absent + register: delete_l4l7_concrete_device + +- name: Verify L4-L7 Concrete Device Deletion + ansible.builtin.assert: + that: + - delete_l4l7_concrete_device is changed + - delete_l4l7_concrete_device.current == [] + - delete_l4l7_concrete_device.previous.0.vnsCDev.attributes.dn == "uni/tn-ansible_tenant/lDevVip-ansible_device/cDev-ansible_concrete_device" + - delete_l4l7_concrete_device.previous.0.vnsCDev.attributes.name == "ansible_concrete_device" + +# DELETE L4-L7 CONCRETE DEVICE AGAIN TO TEST IDEMPOTENCE +- name: Remove L4-L7 Concrete Device + cisco.aci.aci_l4l7_concrete_device: + <<: *aci_info + tenant: ansible_tenant + device: ansible_device + concrete_device: ansible_concrete_device + state: absent + register: delete_l4l7_concrete_device_again + +- name: Verify L4-L7 Concrete Device Deletion idempotence + ansible.builtin.assert: + that: + - delete_l4l7_concrete_device_again is not changed + - delete_l4l7_concrete_device_again.current == [] + +# CLEAN UP +- name: Remove ansible_tenant + cisco.aci.aci_tenant: + <<: *aci_info + name: ansible_tenant + state: absent + +- name: Remove ansible_phys_domain + cisco.aci.aci_domain: + <<: *aci_info + domain: ansible_phys_dom + domain_type: phys + state: absent diff --git a/tests/integration/targets/aci_l4l7_concrete_interface/aliases b/tests/integration/targets/aci_l4l7_concrete_interface/aliases new file mode 100644 index 000000000..209b793f9 --- /dev/null +++ b/tests/integration/targets/aci_l4l7_concrete_interface/aliases @@ -0,0 +1,2 @@ +# No ACI simulator yet, so not enabled +# unsupported diff --git a/tests/integration/targets/aci_l4l7_concrete_interface/tasks/main.yml b/tests/integration/targets/aci_l4l7_concrete_interface/tasks/main.yml new file mode 100644 index 000000000..776e61336 --- /dev/null +++ b/tests/integration/targets/aci_l4l7_concrete_interface/tasks/main.yml @@ -0,0 +1,220 @@ +# Test code for the ACI modules +# Copyright: (c) 2021, 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 + +# 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: '{{ aci_output_level | default("info") }}' + +# CLEAN ENVIRONMENT +- name: Remove ansible_tenant if it already exists + cisco.aci.aci_tenant: + <<: *aci_info + name: ansible_tenant + state: absent + +- name: Remove ansible_phys_domain if it already exists + cisco.aci.aci_domain: + <<: *aci_info + domain: ansible_phys_dom + domain_type: phys + state: absent + +# CREATE TENANT +- name: Create ansible_tenant + cisco.aci.aci_tenant: + <<: *aci_info + name: ansible_tenant + state: present + +# CREATE DOMAIN +- name: Create ansible_phys_domain + cisco.aci.aci_domain: + <<: *aci_info + domain: ansible_phys_dom + domain_type: phys + state: present + +# CREATE L4-L7 LOGICAL DEVICE +- name: Create L4-L7 Device + cisco.aci.aci_l4l7_device: + <<: *aci_info + tenant: ansible_tenant + device: ansible_device + domain: ansible_phys_dom + func_type: go_to + context_aware: single + managed: false + dev_type: physical + svc_type: adc + trunking: false + prom_mode: true + state: present + +# ADD L4-L7 CONCRETE DEVICE +- name: Create L4-L7 Concrete Device + cisco.aci.aci_l4l7_concrete_device: + <<: *aci_info + tenant: ansible_tenant + device: ansible_device + concrete_device: ansible_concrete_device + state: present + +# ADD L4-L7 CONCRETE INTERFACE +- name: Create L4-L7 Concrete Interface + cisco.aci.aci_l4l7_concrete_interface: + <<: *aci_info + tenant: ansible_tenant + device: ansible_device + concrete_device: ansible_concrete_device + concrete_interface: ansible_concrete_interface + pod_id: 1 + node_id: 201 + path_ep: eth1/16 + state: present + register: add_concrete_interface + +- name: Verify L4-L7 Concrete Interface Attributes + ansible.builtin.assert: + that: + - add_concrete_interface.current.0.vnsCIf.attributes.dn == "uni/tn-ansible_tenant/lDevVip-ansible_device/cDev-ansible_concrete_device/cIf-[ansible_concrete_interface]" + - add_concrete_interface.current.0.vnsCIf.attributes.name == "ansible_concrete_interface" + +- name: Verify Path Attribute + ansible.builtin.assert: + that: + - add_concrete_interface.current.0.vnsCIf.children.0.vnsRsCIfPathAtt.attributes.tDn == "topology/pod-1/paths-201/pathep-[eth1/16]" + +# ADD L4-L7 CONCRETE INTERFACE AGAIN TO CHECK IDEMPOTENCE +- name: Create L4-L7 Concrete Interface again + cisco.aci.aci_l4l7_concrete_interface: + <<: *aci_info + tenant: ansible_tenant + device: ansible_device + concrete_device: ansible_concrete_device + concrete_interface: ansible_concrete_interface + pod_id: 1 + node_id: 201 + path_ep: eth1/16 + state: present + register: add_concrete_interface_again + +- name: Verify L4-L7 Concrete Interface Attributes + ansible.builtin.assert: + that: + - add_concrete_interface_again is not changed + - add_concrete_interface_again.current.0.vnsCIf.attributes.dn == "uni/tn-ansible_tenant/lDevVip-ansible_device/cDev-ansible_concrete_device/cIf-[ansible_concrete_interface]" + - add_concrete_interface_again.current.0.vnsCIf.attributes.name == "ansible_concrete_interface" + +- name: Verify Path Attribute + ansible.builtin.assert: + that: + - add_concrete_interface_again.current.0.vnsCIf.children.0.vnsRsCIfPathAtt.attributes.tDn == "topology/pod-1/paths-201/pathep-[eth1/16]" + +# QUERY L4-L7 CONCRETE INTERFACE +- name: Query L4-L7 Concrete Interface + cisco.aci.aci_l4l7_concrete_interface: + <<: *aci_info + tenant: ansible_tenant + device: ansible_device + concrete_device: ansible_concrete_device + concrete_interface: ansible_concrete_interface + pod_id: 1 + node_id: 201 + path_ep: eth1/16 + state: query + register: query_concrete_interface + +- name: Verify L4-L7 Concrete Interface Attributes + ansible.builtin.assert: + that: + - query_concrete_interface is not changed + - query_concrete_interface.current.0.vnsCIf.attributes.dn == "uni/tn-ansible_tenant/lDevVip-ansible_device/cDev-ansible_concrete_device/cIf-[ansible_concrete_interface]" + - query_concrete_interface.current.0.vnsCIf.attributes.name == "ansible_concrete_interface" + +- name: Verify Path Attribute + ansible.builtin.assert: + that: + - query_concrete_interface.current.0.vnsCIf.children.0.vnsRsCIfPathAtt.attributes.tDn == "topology/pod-1/paths-201/pathep-[eth1/16]" + +# QUERY ALL L4-L7 CONCRETE INTERFACES +- name: Query all L4-L7 Concrete Interfaces + cisco.aci.aci_l4l7_concrete_interface: + <<: *aci_info + state: query + register: query_concrete_interface_all + +- name: Verify L4-L7 Concrete Interface Attributes + ansible.builtin.assert: + that: + - query_concrete_interface is not changed + +# DELETE L4-L7 CONCRETE INTERFACE +- name: Remove L4-L7 Concrete Interface + cisco.aci.aci_l4l7_concrete_interface: + <<: *aci_info + tenant: ansible_tenant + device: ansible_device + concrete_device: ansible_concrete_device + concrete_interface: ansible_concrete_interface + pod_id: 1 + node_id: 201 + path_ep: eth1/16 + state: absent + register: delete_concrete_interface + +- name: Verify L4-L7 Concrete Interface Deletion + ansible.builtin.assert: + that: + - delete_concrete_interface is changed + - delete_concrete_interface.current == [] + - delete_concrete_interface.previous.0.vnsCIf.attributes.dn == "uni/tn-ansible_tenant/lDevVip-ansible_device/cDev-ansible_concrete_device/cIf-[ansible_concrete_interface]" + - delete_concrete_interface.previous.0.vnsCIf.attributes.name == "ansible_concrete_interface" + +# DELETE L4-L7 CONCRETE INTERFACE AGAIN TO TEST IDEMPOTENCE +- name: Remove L4-L7 Concrete Interface + cisco.aci.aci_l4l7_concrete_interface: + <<: *aci_info + tenant: ansible_tenant + device: ansible_device + concrete_device: ansible_concrete_device + concrete_interface: ansible_concrete_interface + pod_id: 1 + node_id: 201 + path_ep: eth1/16 + state: absent + register: delete_concrete_interface_again + +- name: Verify L4-L7 Concrete Interface Deletion idempotence + ansible.builtin.assert: + that: + - delete_concrete_interface_again is not changed + - delete_concrete_interface_again.current == [] + +# CLEAN UP +- name: Remove ansible_tenant + cisco.aci.aci_tenant: + <<: *aci_info + name: ansible_tenant + state: absent + +- name: Remove ansible_phys_domain + cisco.aci.aci_domain: + <<: *aci_info + domain: ansible_phys_dom + domain_type: phys + state: absent diff --git a/tests/integration/targets/aci_l4l7_concrete_interface_attach/aliases b/tests/integration/targets/aci_l4l7_concrete_interface_attach/aliases new file mode 100644 index 000000000..209b793f9 --- /dev/null +++ b/tests/integration/targets/aci_l4l7_concrete_interface_attach/aliases @@ -0,0 +1,2 @@ +# No ACI simulator yet, so not enabled +# unsupported diff --git a/tests/integration/targets/aci_l4l7_concrete_interface_attach/tasks/main.yml b/tests/integration/targets/aci_l4l7_concrete_interface_attach/tasks/main.yml new file mode 100644 index 000000000..3833c3635 --- /dev/null +++ b/tests/integration/targets/aci_l4l7_concrete_interface_attach/tasks/main.yml @@ -0,0 +1,248 @@ +# Test code for the ACI modules +# Copyright: (c) 2021, 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 + +# 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: '{{ aci_output_level | default("info") }}' + +# CLEAN ENVIRONMENT +- name: Remove ansible_tenant if it already exists + cisco.aci.aci_tenant: + <<: *aci_info + name: ansible_tenant + state: absent + +- name: Remove ansible_phys_domain if it already exists + cisco.aci.aci_domain: + <<: *aci_info + domain: ansible_phys_dom + domain_type: phys + state: absent + +# CREATE TENANT +- name: Create ansible_tenant + cisco.aci.aci_tenant: + <<: *aci_info + name: ansible_tenant + state: present + +# CREATE DOMAIN +- name: Create ansible_phys_domain + cisco.aci.aci_domain: + <<: *aci_info + domain: ansible_phys_dom + domain_type: phys + state: present + +# CREATE L4-L7 LOGICAL DEVICE +- name: Create L4-L7 Device + cisco.aci.aci_l4l7_device: + <<: *aci_info + tenant: ansible_tenant + device: ansible_device + domain: ansible_phys_dom + func_type: go_to + context_aware: single + managed: false + dev_type: physical + svc_type: adc + trunking: false + prom_mode: true + state: present + +# CREATE L4-L7 LOGICAL INTERFACE +- name: Add Logical Interface + cisco.aci.aci_l4l7_logical_interface: + <<: *aci_info + tenant: ansible_tenant + device: ansible_device + logical_interface: ansible_log_intf + encap: vlan-987 + state: present + +- name: Add Second Logical Interface + cisco.aci.aci_l4l7_logical_interface: + <<: *aci_info + tenant: ansible_tenant + device: ansible_device + logical_interface: second_log_intf + encap: vlan-988 + state: present + +# CREATE L4-L7 CONCRETE INTERFACE +- name: Create L4-L7 Concrete Device + cisco.aci.aci_l4l7_concrete_device: + <<: *aci_info + tenant: ansible_tenant + device: ansible_device + concrete_device: ansible_concrete_device + state: present + +# CREATE L4-L7 CONCRETE INTERFACE +- name: Create L4-L7 Concrete Interface + cisco.aci.aci_l4l7_concrete_interface: + <<: *aci_info + tenant: ansible_tenant + device: ansible_device + concrete_device: ansible_concrete_device + concrete_interface: ansible_concrete_interface + pod_id: 1 + node_id: 201 + path_ep: eth1/16 + state: present + +- name: Create Second L4-L7 Concrete Interface + cisco.aci.aci_l4l7_concrete_interface: + <<: *aci_info + tenant: ansible_tenant + device: ansible_device + concrete_device: ansible_concrete_device + concrete_interface: ansible_second_concrete_interface + pod_id: 1 + node_id: 201 + path_ep: eth1/17 + state: present + +- name: Add a new concrete interface attachment + cisco.aci.aci_l4l7_concrete_interface_attach: + <<: *aci_info + tenant: ansible_tenant + device: ansible_device + logical_interface: ansible_log_intf + concrete_device: ansible_concrete_device + concrete_interface: ansible_concrete_interface + state: present + register: add_concrete_intf_attach + +- name: Add a second concrete interface attachment + cisco.aci.aci_l4l7_concrete_interface_attach: + <<: *aci_info + tenant: ansible_tenant + device: ansible_device + logical_interface: second_log_intf + concrete_device: ansible_concrete_device + concrete_interface: ansible_second_concrete_interface + state: present + +- name: Verify interface attachment + ansible.builtin.assert: + that: + - add_concrete_intf_attach.current.0.vnsRsCIfAttN.attributes.dn == "uni/tn-ansible_tenant/lDevVip-ansible_device/lIf-ansible_log_intf/rscIfAttN-[uni/tn-ansible_tenant/lDevVip-ansible_device/cDev-ansible_concrete_device/cIf-[ansible_concrete_interface]]" + - add_concrete_intf_attach.current.0.vnsRsCIfAttN.attributes.tDn == "uni/tn-ansible_tenant/lDevVip-ansible_device/cDev-ansible_concrete_device/cIf-[ansible_concrete_interface]" + +# ADD CONCRETE INTERFACE ATTACHMENT AGAIN TO TEST IDEMPOTENCE +- name: Add concrete interface attachment again + cisco.aci.aci_l4l7_concrete_interface_attach: + <<: *aci_info + tenant: ansible_tenant + device: ansible_device + logical_interface: ansible_log_intf + concrete_device: ansible_concrete_device + concrete_interface: ansible_concrete_interface + state: present + register: add_concrete_intf_attach_again + +- name: Verify interface attachment + ansible.builtin.assert: + that: + - add_concrete_intf_attach_again is not changed + - add_concrete_intf_attach_again.current.0.vnsRsCIfAttN.attributes.dn == "uni/tn-ansible_tenant/lDevVip-ansible_device/lIf-ansible_log_intf/rscIfAttN-[uni/tn-ansible_tenant/lDevVip-ansible_device/cDev-ansible_concrete_device/cIf-[ansible_concrete_interface]]" + - add_concrete_intf_attach_again.current.0.vnsRsCIfAttN.attributes.tDn == "uni/tn-ansible_tenant/lDevVip-ansible_device/cDev-ansible_concrete_device/cIf-[ansible_concrete_interface]" + +# QUERY CONCRETE INTERFACE ATTACHMENT +- name: Query concrete interface attachment + cisco.aci.aci_l4l7_concrete_interface_attach: + <<: *aci_info + tenant: ansible_tenant + device: ansible_device + logical_interface: ansible_log_intf + concrete_device: ansible_concrete_device + concrete_interface: ansible_concrete_interface + state: query + register: query_concrete_intf_attach + +- name: Verify interface attachment + ansible.builtin.assert: + that: + - query_concrete_intf_attach is not changed + - query_concrete_intf_attach.current.0.vnsRsCIfAttN.attributes.dn == "uni/tn-ansible_tenant/lDevVip-ansible_device/lIf-ansible_log_intf/rscIfAttN-[uni/tn-ansible_tenant/lDevVip-ansible_device/cDev-ansible_concrete_device/cIf-[ansible_concrete_interface]]" + - query_concrete_intf_attach.current.0.vnsRsCIfAttN.attributes.tDn == "uni/tn-ansible_tenant/lDevVip-ansible_device/cDev-ansible_concrete_device/cIf-[ansible_concrete_interface]" + +- name: Query all concrete interface attachments + cisco.aci.aci_l4l7_concrete_interface_attach: + <<: *aci_info + state: query + register: query_all_attachments + +- name: Verify query all + ansible.builtin.assert: + that: + - query_all_attachments is not changed + - query_all_attachments.current | length > 1 + +# DELETE CONCRETE INTERFACE ATTACHMENT +- name: Remove concrete interface attachment + cisco.aci.aci_l4l7_concrete_interface_attach: + <<: *aci_info + tenant: ansible_tenant + device: ansible_device + logical_interface: ansible_log_intf + concrete_device: ansible_concrete_device + concrete_interface: ansible_concrete_interface + state: absent + register: delete_concrete_intf_attach + +- name: Verify interface attachment removal + ansible.builtin.assert: + that: + - delete_concrete_intf_attach is changed + - delete_concrete_intf_attach.current == [] + - delete_concrete_intf_attach.previous.0.vnsRsCIfAttN.attributes.dn == "uni/tn-ansible_tenant/lDevVip-ansible_device/lIf-ansible_log_intf/rscIfAttN-[uni/tn-ansible_tenant/lDevVip-ansible_device/cDev-ansible_concrete_device/cIf-[ansible_concrete_interface]]" + - delete_concrete_intf_attach.previous.0.vnsRsCIfAttN.attributes.tDn == "uni/tn-ansible_tenant/lDevVip-ansible_device/cDev-ansible_concrete_device/cIf-[ansible_concrete_interface]" + +# DELETE CONCRETE INTERFACE ATTACHMENT AGAIN TO TEST IDEMPOTENCE +- name: Remove concrete interface attachment again + cisco.aci.aci_l4l7_concrete_interface_attach: + <<: *aci_info + tenant: ansible_tenant + device: ansible_device + logical_interface: ansible_log_intf + concrete_device: ansible_concrete_device + concrete_interface: ansible_concrete_interface + state: absent + register: delete_concrete_intf_attach_again + +- name: Verify interface attachment removal idemptence + ansible.builtin.assert: + that: + - delete_concrete_intf_attach_again is not changed + - delete_concrete_intf_attach_again.current == [] + +# CLEAN UP +- name: Remove ansible_tenant + cisco.aci.aci_tenant: + <<: *aci_info + name: ansible_tenant + state: absent + +- name: Remove ansible_phys_domain + cisco.aci.aci_domain: + <<: *aci_info + domain: ansible_phys_dom + domain_type: phys + state: absent diff --git a/tests/integration/targets/aci_l4l7_device/aliases b/tests/integration/targets/aci_l4l7_device/aliases new file mode 100644 index 000000000..209b793f9 --- /dev/null +++ b/tests/integration/targets/aci_l4l7_device/aliases @@ -0,0 +1,2 @@ +# No ACI simulator yet, so not enabled +# unsupported diff --git a/tests/integration/targets/aci_l4l7_device/tasks/main.yml b/tests/integration/targets/aci_l4l7_device/tasks/main.yml new file mode 100644 index 000000000..c9129bc95 --- /dev/null +++ b/tests/integration/targets/aci_l4l7_device/tasks/main.yml @@ -0,0 +1,320 @@ +# Test code for the ACI modules +# Copyright: (c) 2021, 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 + +# 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: '{{ aci_output_level | default("info") }}' + +# CLEAN ENVIRONMENT +- name: Remove ansible_tenant if it already exists + cisco.aci.aci_tenant: + <<: *aci_info + name: ansible_tenant + state: absent + +- name: Remove ansible_phys_domain if it already exists + cisco.aci.aci_domain: + <<: *aci_info + domain: ansible_phys_dom + domain_type: phys + state: absent + +- name: Remove ansible_vmm_domain if it already exists + cisco.aci.aci_domain: + <<: *aci_info + domain: ansible_vmm_dom + domain_type: vmm + vm_provider: vmware + state: absent + +# CREATE TENANT +- name: Create ansible_tenant + cisco.aci.aci_tenant: + <<: *aci_info + name: ansible_tenant + state: present + +# CREATE DOMAINS +- name: Create ansible_phys_domain + cisco.aci.aci_domain: + <<: *aci_info + domain: ansible_phys_dom + domain_type: phys + state: present + +- name: Create ansible_vmm_domain + cisco.aci.aci_domain: + <<: *aci_info + domain: ansible_vmm_dom + domain_type: vmm + vm_provider: vmware + state: present + +# ADD L4-L7 DEVICE +- name: Create L4-L7 Physical Device + cisco.aci.aci_l4l7_device: + <<: *aci_info + tenant: ansible_tenant + device: ansible_device + domain: ansible_phys_dom + func_type: go_to + context_aware: single + managed: false + dev_type: physical + svc_type: adc + trunking: false + prom_mode: true + state: present + register: create_l4l7_device + +- name: Create L4-L7 Virtual Device + cisco.aci.aci_l4l7_device: + <<: *aci_info + tenant: ansible_tenant + device: ansible_virt_device + domain: ansible_vmm_dom + func_type: go_to + context_aware: single + managed: false + dev_type: virtual + svc_type: adc + trunking: false + prom_mode: true + state: present + register: create_virt_l4l7_device + +- name: Verify L4-L7 device has been created + ansible.builtin.assert: + that: + - create_l4l7_device.current.0.vnsLDevVip.attributes.dn == "uni/tn-ansible_tenant/lDevVip-ansible_device" + - create_l4l7_device.current.0.vnsLDevVip.attributes.name == "ansible_device" + - create_l4l7_device.current.0.vnsLDevVip.attributes.contextAware == "single-Context" + - create_l4l7_device.current.0.vnsLDevVip.attributes.devtype == "PHYSICAL" + - create_l4l7_device.current.0.vnsLDevVip.attributes.funcType == "GoTo" + - create_l4l7_device.current.0.vnsLDevVip.attributes.isCopy == "no" + - create_l4l7_device.current.0.vnsLDevVip.attributes.managed == "no" + - create_l4l7_device.current.0.vnsLDevVip.attributes.promMode == "yes" + - create_l4l7_device.current.0.vnsLDevVip.attributes.svcType == "ADC" + - create_l4l7_device.current.0.vnsLDevVip.attributes.trunking == "no" + - create_virt_l4l7_device.current.0.vnsLDevVip.attributes.dn == "uni/tn-ansible_tenant/lDevVip-ansible_virt_device" + - create_virt_l4l7_device.current.0.vnsLDevVip.attributes.name == "ansible_virt_device" + - create_virt_l4l7_device.current.0.vnsLDevVip.attributes.contextAware == "single-Context" + - create_virt_l4l7_device.current.0.vnsLDevVip.attributes.devtype == "VIRTUAL" + - create_virt_l4l7_device.current.0.vnsLDevVip.attributes.funcType == "GoTo" + - create_virt_l4l7_device.current.0.vnsLDevVip.attributes.isCopy == "no" + - create_virt_l4l7_device.current.0.vnsLDevVip.attributes.managed == "no" + - create_virt_l4l7_device.current.0.vnsLDevVip.attributes.promMode == "yes" + - create_virt_l4l7_device.current.0.vnsLDevVip.attributes.svcType == "ADC" + - create_virt_l4l7_device.current.0.vnsLDevVip.attributes.trunking == "no" + +- name: Verify domain binding object has been created + ansible.builtin.assert: + that: + - create_l4l7_device.current.0.vnsLDevVip.children.0.vnsRsALDevToPhysDomP.attributes.tDn == "uni/phys-ansible_phys_dom" + - create_virt_l4l7_device.current.0.vnsLDevVip.children.0.vnsRsALDevToDomP.attributes.tDn == "uni/vmmp-VMware/dom-ansible_vmm_dom" + +# ADD device again to check idempotence +- name: Create L4-L7 device again + cisco.aci.aci_l4l7_device: + <<: *aci_info + tenant: ansible_tenant + device: ansible_device + domain: ansible_phys_dom + func_type: go_to + context_aware: single + managed: false + dev_type: physical + svc_type: adc + trunking: false + prom_mode: true + state: present + register: create_l4l7_device_again + +- name: Create L4-L7 Virtual Device again + cisco.aci.aci_l4l7_device: + <<: *aci_info + tenant: ansible_tenant + device: ansible_virt_device + domain: ansible_vmm_dom + func_type: go_to + context_aware: single + managed: false + dev_type: virtual + svc_type: adc + trunking: false + prom_mode: true + state: present + register: create_virt_l4l7_device_again + +- name: Verify L4-L7 device idempotence + ansible.builtin.assert: + that: + - create_l4l7_device_again is not changed + - create_virt_l4l7_device_again is not changed + - create_l4l7_device_again.current.0.vnsLDevVip.attributes.dn == "uni/tn-ansible_tenant/lDevVip-ansible_device" + - create_l4l7_device_again.current.0.vnsLDevVip.attributes.name == "ansible_device" + - create_l4l7_device_again.current.0.vnsLDevVip.attributes.contextAware == "single-Context" + - create_l4l7_device_again.current.0.vnsLDevVip.attributes.devtype == "PHYSICAL" + - create_l4l7_device_again.current.0.vnsLDevVip.attributes.funcType == "GoTo" + - create_l4l7_device_again.current.0.vnsLDevVip.attributes.isCopy == "no" + - create_l4l7_device_again.current.0.vnsLDevVip.attributes.managed == "no" + - create_l4l7_device_again.current.0.vnsLDevVip.attributes.promMode == "yes" + - create_l4l7_device_again.current.0.vnsLDevVip.attributes.svcType == "ADC" + - create_l4l7_device_again.current.0.vnsLDevVip.attributes.trunking == "no" + - create_virt_l4l7_device_again.current.0.vnsLDevVip.attributes.dn == "uni/tn-ansible_tenant/lDevVip-ansible_virt_device" + - create_virt_l4l7_device_again.current.0.vnsLDevVip.attributes.name == "ansible_virt_device" + - create_virt_l4l7_device_again.current.0.vnsLDevVip.attributes.contextAware == "single-Context" + - create_virt_l4l7_device_again.current.0.vnsLDevVip.attributes.devtype == "VIRTUAL" + - create_virt_l4l7_device_again.current.0.vnsLDevVip.attributes.funcType == "GoTo" + - create_virt_l4l7_device_again.current.0.vnsLDevVip.attributes.isCopy == "no" + - create_virt_l4l7_device_again.current.0.vnsLDevVip.attributes.managed == "no" + - create_virt_l4l7_device_again.current.0.vnsLDevVip.attributes.promMode == "yes" + - create_virt_l4l7_device_again.current.0.vnsLDevVip.attributes.svcType == "ADC" + - create_virt_l4l7_device_again.current.0.vnsLDevVip.attributes.trunking == "no" + +- name: Verify domain binding object is still correct + ansible.builtin.assert: + that: + - create_l4l7_device_again.current.0.vnsLDevVip.children.0.vnsRsALDevToPhysDomP.attributes.tDn == "uni/phys-ansible_phys_dom" + - create_virt_l4l7_device_again.current.0.vnsLDevVip.children.0.vnsRsALDevToDomP.attributes.tDn == "uni/vmmp-VMware/dom-ansible_vmm_dom" + +# MODIFY L4-L7 Device +- name: Update L4-L7 device + cisco.aci.aci_l4l7_device: + <<: *aci_info + tenant: ansible_tenant + device: ansible_device + domain: ansible_phys_dom + func_type: go_through + context_aware: multi + managed: false + dev_type: physical + svc_type: fw + trunking: true + prom_mode: false + state: present + register: update_l4l7_device + +- name: Verify L4-L7 device has been updated + ansible.builtin.assert: + that: + - update_l4l7_device is changed + - update_l4l7_device.current.0.vnsLDevVip.attributes.dn == "uni/tn-ansible_tenant/lDevVip-ansible_device" + - update_l4l7_device.current.0.vnsLDevVip.attributes.name == "ansible_device" + - update_l4l7_device.current.0.vnsLDevVip.attributes.contextAware == "multi-Context" + - update_l4l7_device.current.0.vnsLDevVip.attributes.devtype == "PHYSICAL" + - update_l4l7_device.current.0.vnsLDevVip.attributes.funcType == "GoThrough" + - update_l4l7_device.current.0.vnsLDevVip.attributes.isCopy == "no" + - update_l4l7_device.current.0.vnsLDevVip.attributes.managed == "no" + - update_l4l7_device.current.0.vnsLDevVip.attributes.promMode == "no" + - update_l4l7_device.current.0.vnsLDevVip.attributes.svcType == "FW" + - update_l4l7_device.current.0.vnsLDevVip.attributes.trunking == "yes" + +- name: Verify domain binding object + ansible.builtin.assert: + that: + - update_l4l7_device.current.0.vnsLDevVip.children.0.vnsRsALDevToPhysDomP.attributes.tDn == "uni/phys-ansible_phys_dom" + +# QUERY DEVICE +- name: Query L4-L7 Device + cisco.aci.aci_l4l7_device: + <<: *aci_info + tenant: ansible_tenant + device: ansible_device + state: query + register: query_l4l7_device + +- name: Verify L4-L7 Device attributes + ansible.builtin.assert: + that: + - query_l4l7_device is not changed + - query_l4l7_device.current.0.vnsLDevVip.attributes.dn == "uni/tn-ansible_tenant/lDevVip-ansible_device" + - query_l4l7_device.current.0.vnsLDevVip.attributes.name == "ansible_device" + - query_l4l7_device.current.0.vnsLDevVip.attributes.contextAware == "multi-Context" + - query_l4l7_device.current.0.vnsLDevVip.attributes.devtype == "PHYSICAL" + - query_l4l7_device.current.0.vnsLDevVip.attributes.funcType == "GoThrough" + - query_l4l7_device.current.0.vnsLDevVip.attributes.isCopy == "no" + - query_l4l7_device.current.0.vnsLDevVip.attributes.managed == "no" + - query_l4l7_device.current.0.vnsLDevVip.attributes.promMode == "no" + - query_l4l7_device.current.0.vnsLDevVip.attributes.svcType == "FW" + - query_l4l7_device.current.0.vnsLDevVip.attributes.trunking == "yes" + +- name: Query all L4-L7 Devices + cisco.aci.aci_l4l7_device: + <<: *aci_info + state: query + register: query_l4l7_device_all + +- name: Verify L4-L7 Device query idempotence + ansible.builtin.assert: + that: + query_l4l7_device_all is not changed + +# DELETE DEVICE +- name: Delete L4-L7 Device + cisco.aci.aci_l4l7_device: + <<: *aci_info + tenant: ansible_tenant + device: ansible_device + state: absent + register: remove_l4l7_device + +- name: Verify L4-L7 Device deletion + ansible.builtin.assert: + that: + - remove_l4l7_device is changed + - remove_l4l7_device.current == [] + - remove_l4l7_device.previous.0.vnsLDevVip.attributes.dn == "uni/tn-ansible_tenant/lDevVip-ansible_device" + - remove_l4l7_device.previous.0.vnsLDevVip.attributes.name == "ansible_device" + +# DELETE DEVICE AGAIN TO TEST IDEMPOTENCE +- name: Delete L4-L7 Device again + cisco.aci.aci_l4l7_device: + <<: *aci_info + tenant: ansible_tenant + device: ansible_device + state: absent + register: remove_l4l7_device_again + +- name: Verify L4-L7 Device deletion idempotence + ansible.builtin.assert: + that: + - remove_l4l7_device_again is not changed + - remove_l4l7_device_again.current == [] + +# CLEAN UP +- name: Remove ansible_tenant + cisco.aci.aci_tenant: + <<: *aci_info + name: ansible_tenant + state: absent + +- name: Remove ansible_phys_domain + cisco.aci.aci_domain: + <<: *aci_info + domain: ansible_phys_dom + domain_type: phys + state: absent + +- name: Remove ansible_vmm_domain + cisco.aci.aci_domain: + <<: *aci_info + domain: ansible_vmm_dom + domain_type: vmm + vm_provider: vmware + state: absent \ No newline at end of file diff --git a/tests/integration/targets/aci_l4l7_device_selection_if_context/aliases b/tests/integration/targets/aci_l4l7_device_selection_if_context/aliases new file mode 100644 index 000000000..209b793f9 --- /dev/null +++ b/tests/integration/targets/aci_l4l7_device_selection_if_context/aliases @@ -0,0 +1,2 @@ +# No ACI simulator yet, so not enabled +# unsupported diff --git a/tests/integration/targets/aci_l4l7_device_selection_if_context/tasks/main.yml b/tests/integration/targets/aci_l4l7_device_selection_if_context/tasks/main.yml new file mode 100644 index 000000000..29f9b13e6 --- /dev/null +++ b/tests/integration/targets/aci_l4l7_device_selection_if_context/tasks/main.yml @@ -0,0 +1,341 @@ +# Test code for the ACI modules +# Copyright: (c) 2021, 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 + + # 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: '{{ aci_output_level | default("info") }}' + +# CLEAN ENVIRONMENT +- name: Remove ansible_tenant if it already exists + cisco.aci.aci_tenant: + <<: *aci_info + name: ansible_tenant + state: absent + +- name: Remove ansible_phys_domain if it already exists + cisco.aci.aci_domain: + <<: *aci_info + domain: ansible_phys_dom + domain_type: phys + state: absent + +# CREATE TENANT +- name: Create ansible_tenant + cisco.aci.aci_tenant: + <<: *aci_info + name: ansible_tenant + state: present + +# CREATE DOMAIN +- name: Create ansible_phys_domain + cisco.aci.aci_domain: + <<: *aci_info + domain: ansible_phys_dom + domain_type: phys + state: present + +# CREATE CONTRACT +- name: Create contract + cisco.aci.aci_contract: + <<: *aci_info + tenant: ansible_tenant + contract: ansible_contract + scope: application-profile + state: present + +# CREATE L4-L7 DEVICE +- name: Create L4-L7 Device + cisco.aci.aci_l4l7_device: + <<: *aci_info + tenant: ansible_tenant + device: ansible_device + domain: ansible_phys_dom + func_type: go_to + context_aware: single + managed: false + dev_type: physical + svc_type: adc + trunking: false + prom_mode: true + state: present + +# CREATE L4-L7 SERVICE GRAPH +- name: Create L4-L7 Service Graph + cisco.aci.aci_l4l7_service_graph_template: + <<: *aci_info + tenant: ansible_tenant + service_graph: ansible_graph + state: present + +# CREATE L4-L7 SERVICE GRAPH NODE +- name: Add a new Service Graph Template Node + cisco.aci.aci_l4l7_service_graph_template_node: + <<: *aci_info + tenant: ansible_tenant + service_graph: ansible_graph + node: ansible_node + func_template_type: adc_one_arm + func_type: go_to + device: ansible_device + managed: false + routing_mode: Redirect + state: present + +# CREATE L4-L7 DEVICE SELECTION POLICY +- name: Add a new device selection policy + cisco.aci.aci_l4l7_device_selection_policy: + <<: *aci_info + tenant: ansible_tenant + contract: ansible_contract + graph: ansible_graph + node: ansible_node + state: present + +# CREATE BRIDGE DOMAINS +- name: Add a Bridge Domain in ansible_tenant + cisco.aci.aci_bd: + <<: *aci_info + tenant: ansible_tenant + bd: ansible_bd + state: present + +- name: Add a Bridge Domain in the common tenant + cisco.aci.aci_bd: + <<: *aci_info + tenant: common + bd: ansible_if_context_bd + state: present + +# CREATE LOGICAL INTERFACE +- name: Add a new logical interface + cisco.aci.aci_l4l7_logical_interface: + <<: *aci_info + tenant: ansible_tenant + device: ansible_device + logical_interface: ansible_log_intf + encap: vlan-987 + state: present + delegate_to: localhost + +# CREATE L4-L7 REDIRECT POLICY +- name: Add a new Policy Based Redirect + cisco.aci.aci_l4l7_policy_based_redirect: + <<: *aci_info + tenant: ansible_tenant + policy_name: ansible_pbr_policy + dest_type: l3 + hash_algorithm: destination_ip + resilient_hash: yes + state: present + delegate_to: localhost + +# CREATE L4-L7 DEVICE SELECTION LOGICAL INTERFACE CONTEXT +- name: Create L4-L7 Device Selection Logical Interface Context + cisco.aci.aci_l4l7_device_selection_if_context: + <<: *aci_info + tenant: ansible_tenant + contract: ansible_contract + graph: ansible_graph + node: ansible_node + context: provider + bridge_domain: ansible_bd + logical_device: ansible_device + logical_interface: ansible_log_intf + redirect_policy: ansible_pbr_policy + state: present + register: add_context + +- name: Verify context creation + ansible.builtin.assert: + that: + - add_context.current.0.vnsLIfCtx.attributes.dn == "uni/tn-ansible_tenant/ldevCtx-c-ansible_contract-g-ansible_graph-n-ansible_node/lIfCtx-c-provider" + - add_context.current.0.vnsLIfCtx.attributes.connNameOrLbl == "provider" + - add_context.current.0.vnsLIfCtx.children.1.vnsRsLIfCtxToBD.attributes.tDn == "uni/tn-ansible_tenant/BD-ansible_bd" + - add_context.current.0.vnsLIfCtx.children.2.vnsRsLIfCtxToLIf.attributes.tDn == "uni/tn-ansible_tenant/lDevVip-ansible_device/lIf-ansible_log_intf" + - add_context.current.0.vnsLIfCtx.children.0.vnsRsLIfCtxToSvcRedirectPol.attributes.tDn == "uni/tn-ansible_tenant/svcCont/svcRedirectPol-ansible_pbr_policy" + +# CREATE L4-L7 DEVICE SELECTION LOGICAL INTERFACE CONTEXT AGAIN TO TEST IDEMPOTENCE +- name: Create L4-L7 Device Selection Logical Interface Context again + cisco.aci.aci_l4l7_device_selection_if_context: + <<: *aci_info + tenant: ansible_tenant + contract: ansible_contract + graph: ansible_graph + node: ansible_node + context: provider + bridge_domain: ansible_bd + logical_device: ansible_device + logical_interface: ansible_log_intf + redirect_policy: ansible_pbr_policy + state: present + register: add_context_again + +- name: Verify context creation idempotence + ansible.builtin.assert: + that: + - add_context_again is not changed + - add_context_again.current.0.vnsLIfCtx.attributes.dn == "uni/tn-ansible_tenant/ldevCtx-c-ansible_contract-g-ansible_graph-n-ansible_node/lIfCtx-c-provider" + - add_context_again.current.0.vnsLIfCtx.attributes.connNameOrLbl == "provider" + - add_context_again.current.0.vnsLIfCtx.children.1.vnsRsLIfCtxToBD.attributes.tDn == "uni/tn-ansible_tenant/BD-ansible_bd" + - add_context_again.current.0.vnsLIfCtx.children.2.vnsRsLIfCtxToLIf.attributes.tDn == "uni/tn-ansible_tenant/lDevVip-ansible_device/lIf-ansible_log_intf" + - add_context_again.current.0.vnsLIfCtx.children.0.vnsRsLIfCtxToSvcRedirectPol.attributes.tDn == "uni/tn-ansible_tenant/svcCont/svcRedirectPol-ansible_pbr_policy" + +# UPDATE L4-L7 DEVICE SELECTION LOGICAL INTERFACE CONTEXT +- name: Modify L4-L7 Device Selection Logical Interface Context + cisco.aci.aci_l4l7_device_selection_if_context: + <<: *aci_info + tenant: ansible_tenant + contract: ansible_contract + graph: ansible_graph + node: ansible_node + context: provider + bridge_domain: ansible_if_context_bd + bridge_domain_tenant: common + state: present + register: update_context + +- name: Verify context update + ansible.builtin.assert: + that: + - update_context is changed + - update_context.current.0.vnsLIfCtx.attributes.dn == "uni/tn-ansible_tenant/ldevCtx-c-ansible_contract-g-ansible_graph-n-ansible_node/lIfCtx-c-provider" + - update_context.current.0.vnsLIfCtx.attributes.connNameOrLbl == "provider" + - update_context.current.0.vnsLIfCtx.children.0.vnsRsLIfCtxToBD.attributes.tDn == "uni/tn-common/BD-ansible_if_context_bd" + - update_context.current.0.vnsLIfCtx.children | length == 1 + +- name: Remove L4-L7 Device Selection Logical Interface Context BD + cisco.aci.aci_l4l7_device_selection_if_context: + <<: *aci_info + tenant: ansible_tenant + contract: ansible_contract + graph: ansible_graph + node: ansible_node + context: provider + state: present + register: update_context + +- name: Verify BD binding removal + ansible.builtin.assert: + that: + - update_context is changed + - update_context.current.0.vnsLIfCtx.attributes.dn == "uni/tn-ansible_tenant/ldevCtx-c-ansible_contract-g-ansible_graph-n-ansible_node/lIfCtx-c-provider" + - update_context.current.0.vnsLIfCtx.attributes.connNameOrLbl == "provider" + - update_context.current.0.vnsLIfCtx.children is not defined + +- name: Re-add L4-L7 Device Selection Logical Interface Context BD binding + cisco.aci.aci_l4l7_device_selection_if_context: + <<: *aci_info + tenant: ansible_tenant + contract: ansible_contract + graph: ansible_graph + node: ansible_node + context: provider + bridge_domain: ansible_if_context_bd + bridge_domain_tenant: common + state: present + +# QUERY L4-L7 DEVICE SELECTION INTERFACE CONTEXT +- name: Query L4-L7 Device Selection Logical Interface Context + cisco.aci.aci_l4l7_device_selection_if_context: + <<: *aci_info + tenant: ansible_tenant + contract: ansible_contract + graph: ansible_graph + node: ansible_node + context: provider + state: query + register: query_context + +- name: Verify context query + ansible.builtin.assert: + that: + - query_context is not changed + - query_context.current.0.vnsLIfCtx.attributes.dn == "uni/tn-ansible_tenant/ldevCtx-c-ansible_contract-g-ansible_graph-n-ansible_node/lIfCtx-c-provider" + - query_context.current.0.vnsLIfCtx.attributes.connNameOrLbl == "provider" + - query_context.current.0.vnsLIfCtx.children.0.vnsRsLIfCtxToBD.attributes.tDn == "uni/tn-common/BD-ansible_if_context_bd" + +# QUERY ALL L4-L7 DEVICE SELECTION INTERFACE CONTEXTS +- name: Query all L4-L7 Device Selection Logical Interface Contexts + cisco.aci.aci_l4l7_device_selection_if_context: + <<: *aci_info + state: query + register: query_all_contexts + +- name: Verify Query All + ansible.builtin.assert: + that: + - query_all_contexts is not changed + +# DELETE L4-L7 DEVICE SELECTION INTERFACE CONTEXT +- name: Delete L4-L7 Device Selection Logical Interface Context + cisco.aci.aci_l4l7_device_selection_if_context: + <<: *aci_info + tenant: ansible_tenant + contract: ansible_contract + graph: ansible_graph + node: ansible_node + context: provider + state: absent + register: delete_context + +- name: Verify context deletion + ansible.builtin.assert: + that: + - delete_context is changed + - delete_context.current == [] + - delete_context.previous.0.vnsLIfCtx.attributes.dn == "uni/tn-ansible_tenant/ldevCtx-c-ansible_contract-g-ansible_graph-n-ansible_node/lIfCtx-c-provider" + - delete_context.previous.0.vnsLIfCtx.attributes.connNameOrLbl == "provider" + +# DELETE L4-L7 DEVICE SELECTION INTERFACE CONTEXT AGAIN TO TEST IDEMPOTENCE +- name: Delete L4-L7 Device Selection Logical Interface Context again + cisco.aci.aci_l4l7_device_selection_if_context: + <<: *aci_info + tenant: ansible_tenant + contract: ansible_contract + graph: ansible_graph + node: ansible_node + context: provider + state: absent + register: delete_context_again + +- name: Verify context deletion idempotence + ansible.builtin.assert: + that: + - delete_context_again is not changed + - delete_context_again.current == [] + +# CLEAN UP +- name: Remove ansible_tenant + cisco.aci.aci_tenant: + <<: *aci_info + name: ansible_tenant + state: absent + +- name: Remove ansible_phys_domain + cisco.aci.aci_domain: + <<: *aci_info + domain: ansible_phys_dom + domain_type: phys + state: absent + +- name: Remove ansible_if_context_bd + cisco.aci.aci_bd: + <<: *aci_info + tenant: common + bd: ansible_if_context_bd + state: absent \ No newline at end of file diff --git a/tests/integration/targets/aci_l4l7_device_selection_policy/aliases b/tests/integration/targets/aci_l4l7_device_selection_policy/aliases new file mode 100644 index 000000000..209b793f9 --- /dev/null +++ b/tests/integration/targets/aci_l4l7_device_selection_policy/aliases @@ -0,0 +1,2 @@ +# No ACI simulator yet, so not enabled +# unsupported diff --git a/tests/integration/targets/aci_l4l7_device_selection_policy/tasks/main.yml b/tests/integration/targets/aci_l4l7_device_selection_policy/tasks/main.yml new file mode 100644 index 000000000..11935f18b --- /dev/null +++ b/tests/integration/targets/aci_l4l7_device_selection_policy/tasks/main.yml @@ -0,0 +1,233 @@ +# Test code for the ACI modules +# Copyright: (c) 2021, 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 + +# 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: '{{ aci_output_level | default("info") }}' + +# CLEAN ENVIRONMENT +- name: Remove ansible_tenant if it already exists + cisco.aci.aci_tenant: + <<: *aci_info + name: ansible_tenant + state: absent + +- name: Remove ansible_phys_domain if it already exists + cisco.aci.aci_domain: + <<: *aci_info + domain: ansible_phys_dom + domain_type: phys + state: absent + +# CREATE TENANT +- name: Create ansible_tenant + cisco.aci.aci_tenant: + <<: *aci_info + name: ansible_tenant + state: present + +# CREATE DOMAIN +- name: Create ansible_phys_domain + cisco.aci.aci_domain: + <<: *aci_info + domain: ansible_phys_dom + domain_type: phys + state: present + +# CREATE L4-L7 LOGICAL DEVICE +- name: Create L4-L7 Device + cisco.aci.aci_l4l7_device: + <<: *aci_info + tenant: ansible_tenant + device: ansible_device + domain: ansible_phys_dom + func_type: go_to + context_aware: single + managed: false + dev_type: physical + svc_type: adc + trunking: false + prom_mode: true + state: present + +# CREATE CONTRACT +- name: Create contract + cisco.aci.aci_contract: + <<: *aci_info + tenant: ansible_tenant + contract: ansible_contract + scope: application-profile + state: present + +# CREATE L4-L7 SERVICE GRAPH +- name: Create L4-L7 Service Graph + cisco.aci.aci_l4l7_service_graph_template: + <<: *aci_info + tenant: ansible_tenant + service_graph: ansible_service_graph + state: present + +# CREATE L4-L7 SERVICE GRAPH NODE +- name: Create Service Graph Template Node + cisco.aci.aci_l4l7_service_graph_template_node: + <<: *aci_info + tenant: ansible_tenant + service_graph: ansible_service_graph + node: ansible_node + func_template_type: adc_one_arm + func_type: go_to + device: ansible_device + managed: false + routing_mode: Redirect + state: present + +# CREATE L4-L7 DEVICE SELECTION POLICY +- name: Create L4-L7 Device Selection Policy + cisco.aci.aci_l4l7_device_selection_policy: + <<: *aci_info + tenant: ansible_tenant + contract: ansible_contract + graph: ansible_graph + node: ansible_node + device: ansible_device + state: present + register: add_l4l7_device_selection_policy + +- name: Verify L4-L7 Device Selection Policy attributes + ansible.builtin.assert: + that: + - add_l4l7_device_selection_policy.current.0.vnsLDevCtx.attributes.dn == "uni/tn-ansible_tenant/ldevCtx-c-ansible_contract-g-ansible_graph-n-ansible_node" + - add_l4l7_device_selection_policy.current.0.vnsLDevCtx.attributes.ctrctNameOrLbl == "ansible_contract" + - add_l4l7_device_selection_policy.current.0.vnsLDevCtx.attributes.graphNameOrLbl == "ansible_graph" + - add_l4l7_device_selection_policy.current.0.vnsLDevCtx.attributes.nodeNameOrLbl == "ansible_node" + - add_l4l7_device_selection_policy.current.0.vnsLDevCtx.children.0.vnsRsLDevCtxToLDev.attributes.tDn == "uni/tn-ansible_tenant/lDevVip-ansible_device" + +# ADD L4-L7 DEVICE SELECTION POLICY AGAIN TO CHECK IDEMPOTENCE +- name: Add L4-L7 Device Selection Policy again + cisco.aci.aci_l4l7_device_selection_policy: + <<: *aci_info + tenant: ansible_tenant + contract: ansible_contract + graph: ansible_graph + node: ansible_node + device: ansible_device + state: present + register: add_l4l7_device_selection_policy_again + +- name: Verify L4-L7 Device Selection Policy attributes are unchanged + ansible.builtin.assert: + that: + - add_l4l7_device_selection_policy_again is not changed + - add_l4l7_device_selection_policy_again.current.0.vnsLDevCtx.attributes.dn == "uni/tn-ansible_tenant/ldevCtx-c-ansible_contract-g-ansible_graph-n-ansible_node" + - add_l4l7_device_selection_policy_again.current.0.vnsLDevCtx.attributes.ctrctNameOrLbl == "ansible_contract" + - add_l4l7_device_selection_policy_again.current.0.vnsLDevCtx.attributes.graphNameOrLbl == "ansible_graph" + - add_l4l7_device_selection_policy_again.current.0.vnsLDevCtx.attributes.nodeNameOrLbl == "ansible_node" + - add_l4l7_device_selection_policy_again.current.0.vnsLDevCtx.children.0.vnsRsLDevCtxToLDev.attributes.tDn == "uni/tn-ansible_tenant/lDevVip-ansible_device" + +# REMOVE LOGICAL DEVICE BINDING +- name: Remove L4-L7 Device Selection Policy Device Binding + cisco.aci.aci_l4l7_device_selection_policy: + <<: *aci_info + tenant: ansible_tenant + contract: ansible_contract + graph: ansible_graph + node: ansible_node + state: present + register: remove_l4l7_device_selection_policy_device_binding + +- name: Verify L4-L7 Device Selection Policy Device Binding removal + ansible.builtin.assert: + that: + - remove_l4l7_device_selection_policy_device_binding is changed + - remove_l4l7_device_selection_policy_device_binding.current.0.vnsLDevCtx.attributes.dn == "uni/tn-ansible_tenant/ldevCtx-c-ansible_contract-g-ansible_graph-n-ansible_node" + - remove_l4l7_device_selection_policy_device_binding.current.0.vnsLDevCtx.attributes.ctrctNameOrLbl == "ansible_contract" + - remove_l4l7_device_selection_policy_device_binding.current.0.vnsLDevCtx.attributes.graphNameOrLbl == "ansible_graph" + - remove_l4l7_device_selection_policy_device_binding.current.0.vnsLDevCtx.attributes.nodeNameOrLbl == "ansible_node" + - remove_l4l7_device_selection_policy_device_binding.current.0.vnsLDevCtx.children is not defined + +# QUERY L4-L7 DEVICE SELECTION POLICY +- name: Query L4-L7 Device Selection Policy + cisco.aci.aci_l4l7_device_selection_policy: + <<: *aci_info + tenant: ansible_tenant + contract: ansible_contract + graph: ansible_graph + node: ansible_node + state: query + register: query_l4l7_device_selection_policy + +- name: Verify L4-L7 Device Selection Policy attributes + ansible.builtin.assert: + that: + - query_l4l7_device_selection_policy is not changed + - query_l4l7_device_selection_policy.current.0.vnsLDevCtx.attributes.dn == "uni/tn-ansible_tenant/ldevCtx-c-ansible_contract-g-ansible_graph-n-ansible_node" + - query_l4l7_device_selection_policy.current.0.vnsLDevCtx.attributes.ctrctNameOrLbl == "ansible_contract" + - query_l4l7_device_selection_policy.current.0.vnsLDevCtx.attributes.graphNameOrLbl == "ansible_graph" + - query_l4l7_device_selection_policy.current.0.vnsLDevCtx.attributes.nodeNameOrLbl == "ansible_node" + +# DELETE L4-L7 DEVICE SELECTION POLICY +- name: Delete L4-L7 Device Selection Policy + cisco.aci.aci_l4l7_device_selection_policy: + <<: *aci_info + tenant: ansible_tenant + contract: ansible_contract + graph: ansible_graph + node: ansible_node + state: absent + register: delete_l4l7_device_selection_policy + +- name: Verify L4-L7 Device Selection Policy removal + ansible.builtin.assert: + that: + - delete_l4l7_device_selection_policy is changed + - delete_l4l7_device_selection_policy.current == [] + - delete_l4l7_device_selection_policy.previous.0.vnsLDevCtx.attributes.dn == "uni/tn-ansible_tenant/ldevCtx-c-ansible_contract-g-ansible_graph-n-ansible_node" + - delete_l4l7_device_selection_policy.previous.0.vnsLDevCtx.attributes.ctrctNameOrLbl == "ansible_contract" + - delete_l4l7_device_selection_policy.previous.0.vnsLDevCtx.attributes.graphNameOrLbl == "ansible_graph" + - delete_l4l7_device_selection_policy.previous.0.vnsLDevCtx.attributes.nodeNameOrLbl == "ansible_node" + +# DELETE L4-L7 DEVICE SELECTION POLICY AGAIN TO TEST IDEMPOTENCE +- name: Delete L4-L7 Device Selection Policy + cisco.aci.aci_l4l7_device_selection_policy: + <<: *aci_info + tenant: ansible_tenant + contract: ansible_contract + graph: ansible_graph + node: ansible_node + state: absent + register: delete_l4l7_device_selection_policy_again + +- name: Verify L4-L7 Device Selection Policy removal + ansible.builtin.assert: + that: + - delete_l4l7_device_selection_policy_again is not changed + - delete_l4l7_device_selection_policy_again.current == [] + +# CLEAN UP +- name: Remove ansible_tenant + cisco.aci.aci_tenant: + <<: *aci_info + name: ansible_tenant + state: absent + +- name: Remove ansible_phys_domain + cisco.aci.aci_domain: + <<: *aci_info + domain: ansible_phys_dom + domain_type: phys + state: absent diff --git a/tests/integration/targets/aci_l4l7_logical_interface/aliases b/tests/integration/targets/aci_l4l7_logical_interface/aliases new file mode 100644 index 000000000..209b793f9 --- /dev/null +++ b/tests/integration/targets/aci_l4l7_logical_interface/aliases @@ -0,0 +1,2 @@ +# No ACI simulator yet, so not enabled +# unsupported diff --git a/tests/integration/targets/aci_l4l7_logical_interface/tasks/main.yml b/tests/integration/targets/aci_l4l7_logical_interface/tasks/main.yml new file mode 100644 index 000000000..4f0d42fcf --- /dev/null +++ b/tests/integration/targets/aci_l4l7_logical_interface/tasks/main.yml @@ -0,0 +1,201 @@ +# Test code for the ACI modules +# Copyright: (c) 2021, 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 + +# 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: '{{ aci_output_level | default("info") }}' + +# CLEAN ENVIRONMENT +- name: Remove ansible_tenant if it already exists + cisco.aci.aci_tenant: + <<: *aci_info + name: ansible_tenant + state: absent + +- name: Remove ansible_phys_domain if it already exists + cisco.aci.aci_domain: + <<: *aci_info + domain: ansible_phys_dom + domain_type: phys + state: absent + +# CREATE TENANT +- name: Create ansible_tenant + cisco.aci.aci_tenant: + <<: *aci_info + name: ansible_tenant + state: present + +# CREATE DOMAIN +- name: Create ansible_phys_domain + cisco.aci.aci_domain: + <<: *aci_info + domain: ansible_phys_dom + domain_type: phys + state: present + +# CREATE L4-L7 LOGICAL DEVICE +- name: Create L4-L7 Device + cisco.aci.aci_l4l7_device: + <<: *aci_info + tenant: ansible_tenant + device: ansible_device + domain: ansible_phys_dom + func_type: go_to + context_aware: single + managed: false + dev_type: physical + svc_type: adc + trunking: false + prom_mode: true + state: present + +# CREATE L4-L7 LOGICAL INTERFACE +- name: Add Logical Interface + cisco.aci.aci_l4l7_logical_interface: + <<: *aci_info + tenant: ansible_tenant + device: ansible_device + logical_interface: ansible_log_intf + encap: vlan-987 + state: present + register: add_logical_interface + +- name: Verify Logical Interface Attributes + ansible.builtin.assert: + that: + - add_logical_interface.current.0.vnsLIf.attributes.dn == "uni/tn-ansible_tenant/lDevVip-ansible_device/lIf-ansible_log_intf" + - add_logical_interface.current.0.vnsLIf.attributes.name == "ansible_log_intf" + - add_logical_interface.current.0.vnsLIf.attributes.encap == "vlan-987" + +# ADD L4-L7 LOGICAL INTERFACE AGAIN TO CHECK IDEMPOTENCE +- name: Add Logical Interface again + cisco.aci.aci_l4l7_logical_interface: + <<: *aci_info + tenant: ansible_tenant + device: ansible_device + logical_interface: ansible_log_intf + encap: vlan-987 + state: present + register: add_logical_interface_again + +- name: Verify Logical Interface Attributes have not changed + ansible.builtin.assert: + that: + - add_logical_interface_again is not changed + - add_logical_interface_again.current.0.vnsLIf.attributes.dn == "uni/tn-ansible_tenant/lDevVip-ansible_device/lIf-ansible_log_intf" + - add_logical_interface_again.current.0.vnsLIf.attributes.name == "ansible_log_intf" + - add_logical_interface_again.current.0.vnsLIf.attributes.encap == "vlan-987" + +# MODIFY L4-L7 LOGICAL INTERFACE +- name: Update L4-L7 Logical Interface + cisco.aci.aci_l4l7_logical_interface: + <<: *aci_info + tenant: ansible_tenant + device: ansible_device + logical_interface: ansible_log_intf + encap: vlan-988 + state: present + register: update_logical_interface + +- name: Verify Logical Interface update + ansible.builtin.assert: + that: + - update_logical_interface is changed + - update_logical_interface.current.0.vnsLIf.attributes.dn == "uni/tn-ansible_tenant/lDevVip-ansible_device/lIf-ansible_log_intf" + - update_logical_interface.current.0.vnsLIf.attributes.name == "ansible_log_intf" + - update_logical_interface.current.0.vnsLIf.attributes.encap == "vlan-988" + +# QUERY L4-L7 LOGICAL INTERFACE +- name: Query L4-L7 Logical Interface + cisco.aci.aci_l4l7_logical_interface: + <<: *aci_info + tenant: ansible_tenant + device: ansible_device + logical_interface: ansible_log_intf + state: query + register: query_logical_interface + +- name: Verify Logical Interface Attributes have not changed + ansible.builtin.assert: + that: + - query_logical_interface is not changed + - query_logical_interface.current.0.vnsLIf.attributes.dn == "uni/tn-ansible_tenant/lDevVip-ansible_device/lIf-ansible_log_intf" + - query_logical_interface.current.0.vnsLIf.attributes.name == "ansible_log_intf" + - query_logical_interface.current.0.vnsLIf.attributes.encap == "vlan-988" + +# QUERY ALL L4-L7 LOGICAL INTERFACES +- name: Query All L4-L7 Logical Interfaces + cisco.aci.aci_l4l7_logical_interface: + <<: *aci_info + state: query + register: query_logical_interface_all + +- name: Verify Logical Interface Attributes have not changed + ansible.builtin.assert: + that: + - query_logical_interface_all is not changed + +# DELETE L4-L7 LOGICAL INTERFACE +- name: Remove L4-L7 Logical Interface + cisco.aci.aci_l4l7_logical_interface: + <<: *aci_info + tenant: ansible_tenant + device: ansible_device + logical_interface: ansible_log_intf + state: absent + register: delete_logical_interface + +- name: Verify Logical Interface Deletion + ansible.builtin.assert: + that: + - delete_logical_interface is changed + - delete_logical_interface.current == [] + - delete_logical_interface.previous.0.vnsLIf.attributes.dn == "uni/tn-ansible_tenant/lDevVip-ansible_device/lIf-ansible_log_intf" + - delete_logical_interface.previous.0.vnsLIf.attributes.name == "ansible_log_intf" + - delete_logical_interface.previous.0.vnsLIf.attributes.encap == "vlan-988" + +# DELETE L4-L7 LOGICAL INTERFACE AGAIN TO TEST IDEMPOTENCE +- name: Remove L4-L7 Logical Interface + cisco.aci.aci_l4l7_logical_interface: + <<: *aci_info + tenant: ansible_tenant + device: ansible_device + logical_interface: ansible_log_intf + state: absent + register: delete_logical_interface_again + +- name: Verify Logical Interface Deletion + ansible.builtin.assert: + that: + - delete_logical_interface_again is not changed + - delete_logical_interface_again.current == [] + +# CLEAN UP +- name: Remove ansible_tenant + cisco.aci.aci_tenant: + <<: *aci_info + name: ansible_tenant + state: absent + +- name: Remove ansible_phys_domain + cisco.aci.aci_domain: + <<: *aci_info + domain: ansible_phys_dom + domain_type: phys + state: absent diff --git a/tests/integration/targets/aci_l4l7_policy_based_redirect/aliases b/tests/integration/targets/aci_l4l7_policy_based_redirect/aliases new file mode 100644 index 000000000..209b793f9 --- /dev/null +++ b/tests/integration/targets/aci_l4l7_policy_based_redirect/aliases @@ -0,0 +1,2 @@ +# No ACI simulator yet, so not enabled +# unsupported diff --git a/tests/integration/targets/aci_l4l7_policy_based_redirect/tasks/main.yml b/tests/integration/targets/aci_l4l7_policy_based_redirect/tasks/main.yml new file mode 100644 index 000000000..d34c0399a --- /dev/null +++ b/tests/integration/targets/aci_l4l7_policy_based_redirect/tasks/main.yml @@ -0,0 +1,382 @@ +# Test code for the ACI modules +# Copyright: (c) 2021, 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 + +# 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: '{{ aci_output_level | default("info") }}' + +# CLEAN ENVIRONMENT +- name: Remove ansible_tenant if it already exists + cisco.aci.aci_tenant: + <<: *aci_info + name: ansible_tenant + state: absent + +# CREATE TENANT +- name: Create ansible_tenant + cisco.aci.aci_tenant: + <<: *aci_info + name: ansible_tenant + state: present + +- name: Query system information + cisco.aci.aci_system: + <<: *aci_info + id: 1 + state: query + register: version + +- name: Create IP SLA Monitoring Policy + cisco.aci.aci_ip_sla_monitoring_policy: + <<: *aci_info + tenant: ansible_tenant + name: ansible_ip_sla_mon_policy + sla_type: icmp + state: present + +# CREATE L4-L7 POLICY BASED REDIRECT +- name: Create L4-L7 Policy Based Redirect (v4) + cisco.aci.aci_l4l7_policy_based_redirect: + <<: *aci_info + tenant: ansible_tenant + policy_name: ansible_pbr_policy + dest_type: l3 + hash_algorithm: destination_ip + resilient_hash: true + min_threshold: 60 + max_threshold: 90 + threshold_enable: true + threshold_down_action: permit + pod_aware: true + anycast_enabled: false + state: present + register: add_pbr_policy_v4 + when: version.current.0.topSystem.attributes.version is version('4.2', '>=') + +- name: Create L4-L7 Policy Based Redirect (v3) + cisco.aci.aci_l4l7_policy_based_redirect: + <<: *aci_info + tenant: ansible_tenant + policy_name: ansible_pbr_policy + hash_algorithm: destination_ip + resilient_hash: true + min_threshold: 60 + max_threshold: 90 + threshold_enable: true + threshold_down_action: permit + pod_aware: true + anycast_enabled: false + state: present + register: add_pbr_policy_v3 + when: version.current.0.topSystem.attributes.version is version('4.2', '<') + +- name: Verify PBR Attributes v4 + ansible.builtin.assert: + that: + - add_pbr_policy_v4.current.0.vnsSvcRedirectPol.attributes.dn == "uni/tn-ansible_tenant/svcCont/svcRedirectPol-ansible_pbr_policy" + - add_pbr_policy_v4.current.0.vnsSvcRedirectPol.attributes.name == "ansible_pbr_policy" + - add_pbr_policy_v4.current.0.vnsSvcRedirectPol.attributes.AnycastEnabled == "no" + - add_pbr_policy_v4.current.0.vnsSvcRedirectPol.attributes.destType == "L3" + - add_pbr_policy_v4.current.0.vnsSvcRedirectPol.attributes.hashingAlgorithm == "dip" + - add_pbr_policy_v4.current.0.vnsSvcRedirectPol.attributes.maxThresholdPercent == "90" + - add_pbr_policy_v4.current.0.vnsSvcRedirectPol.attributes.minThresholdPercent == "60" + - add_pbr_policy_v4.current.0.vnsSvcRedirectPol.attributes.programLocalPodOnly == "yes" + - add_pbr_policy_v4.current.0.vnsSvcRedirectPol.attributes.resilientHashEnabled == "yes" + - add_pbr_policy_v4.current.0.vnsSvcRedirectPol.attributes.thresholdDownAction == "permit" + - add_pbr_policy_v4.current.0.vnsSvcRedirectPol.attributes.thresholdEnable == "yes" + when: version.current.0.topSystem.attributes.version is version('4.2', '>=') + +- name: Verify PBR Attributes v3 + ansible.builtin.assert: + that: + - add_pbr_policy_v3.current.0.vnsSvcRedirectPol.attributes.dn == "uni/tn-ansible_tenant/svcCont/svcRedirectPol-ansible_pbr_policy" + - add_pbr_policy_v3.current.0.vnsSvcRedirectPol.attributes.name == "ansible_pbr_policy" + - add_pbr_policy_v3.current.0.vnsSvcRedirectPol.attributes.AnycastEnabled == "no" + - add_pbr_policy_v3.current.0.vnsSvcRedirectPol.attributes.hashingAlgorithm == "dip" + - add_pbr_policy_v3.current.0.vnsSvcRedirectPol.attributes.maxThresholdPercent == "90" + - add_pbr_policy_v3.current.0.vnsSvcRedirectPol.attributes.minThresholdPercent == "60" + - add_pbr_policy_v3.current.0.vnsSvcRedirectPol.attributes.programLocalPodOnly == "yes" + - add_pbr_policy_v3.current.0.vnsSvcRedirectPol.attributes.resilientHashEnabled == "yes" + - add_pbr_policy_v3.current.0.vnsSvcRedirectPol.attributes.thresholdDownAction == "permit" + - add_pbr_policy_v3.current.0.vnsSvcRedirectPol.attributes.thresholdEnable == "yes" + when: version.current.0.topSystem.attributes.version is version('4.2', '<') + +# ADD L4-L7 PBR AGAIN TO CHECK IDEMPOTENCY +- name: Add L4-L7 Policy Based Redirect again (v4) + cisco.aci.aci_l4l7_policy_based_redirect: + <<: *aci_info + tenant: ansible_tenant + policy_name: ansible_pbr_policy + dest_type: l3 + hash_algorithm: destination_ip + resilient_hash: true + min_threshold: 60 + max_threshold: 90 + threshold_enable: true + threshold_down_action: permit + pod_aware: true + anycast_enabled: false + state: present + register: add_pbr_policy_v4_again + when: version.current.0.topSystem.attributes.version is version('4.2', '>=') + +- name: Add L4-L7 Policy Based Redirect again (v3) + cisco.aci.aci_l4l7_policy_based_redirect: + <<: *aci_info + tenant: ansible_tenant + policy_name: ansible_pbr_policy + hash_algorithm: destination_ip + resilient_hash: true + min_threshold: 60 + max_threshold: 90 + threshold_enable: true + threshold_down_action: permit + pod_aware: true + anycast_enabled: false + state: present + register: add_pbr_policy_v3_again + when: version.current.0.topSystem.attributes.version is version('4.2', '<') + +- name: Verify PBR Attributes (v4) + ansible.builtin.assert: + that: + - add_pbr_policy_v4_again is not changed + - add_pbr_policy_v4_again.current.0.vnsSvcRedirectPol.attributes.dn == "uni/tn-ansible_tenant/svcCont/svcRedirectPol-ansible_pbr_policy" + - add_pbr_policy_v4_again.current.0.vnsSvcRedirectPol.attributes.name == "ansible_pbr_policy" + - add_pbr_policy_v4_again.current.0.vnsSvcRedirectPol.attributes.AnycastEnabled == "no" + - add_pbr_policy_v4_again.current.0.vnsSvcRedirectPol.attributes.destType == "L3" + - add_pbr_policy_v4_again.current.0.vnsSvcRedirectPol.attributes.hashingAlgorithm == "dip" + - add_pbr_policy_v4_again.current.0.vnsSvcRedirectPol.attributes.maxThresholdPercent == "90" + - add_pbr_policy_v4_again.current.0.vnsSvcRedirectPol.attributes.minThresholdPercent == "60" + - add_pbr_policy_v4_again.current.0.vnsSvcRedirectPol.attributes.programLocalPodOnly == "yes" + - add_pbr_policy_v4_again.current.0.vnsSvcRedirectPol.attributes.resilientHashEnabled == "yes" + - add_pbr_policy_v4_again.current.0.vnsSvcRedirectPol.attributes.thresholdDownAction == "permit" + - add_pbr_policy_v4_again.current.0.vnsSvcRedirectPol.attributes.thresholdEnable == "yes" + when: version.current.0.topSystem.attributes.version is version('4.2', '>=') + +- name: Verify PBR Attributes (v3) + ansible.builtin.assert: + that: + - add_pbr_policy_v3_again is not changed + - add_pbr_policy_v3_again.current.0.vnsSvcRedirectPol.attributes.dn == "uni/tn-ansible_tenant/svcCont/svcRedirectPol-ansible_pbr_policy" + - add_pbr_policy_v3_again.current.0.vnsSvcRedirectPol.attributes.name == "ansible_pbr_policy" + - add_pbr_policy_v3_again.current.0.vnsSvcRedirectPol.attributes.AnycastEnabled == "no" + - add_pbr_policy_v3_again.current.0.vnsSvcRedirectPol.attributes.hashingAlgorithm == "dip" + - add_pbr_policy_v3_again.current.0.vnsSvcRedirectPol.attributes.maxThresholdPercent == "90" + - add_pbr_policy_v3_again.current.0.vnsSvcRedirectPol.attributes.minThresholdPercent == "60" + - add_pbr_policy_v3_again.current.0.vnsSvcRedirectPol.attributes.programLocalPodOnly == "yes" + - add_pbr_policy_v3_again.current.0.vnsSvcRedirectPol.attributes.resilientHashEnabled == "yes" + - add_pbr_policy_v3_again.current.0.vnsSvcRedirectPol.attributes.thresholdDownAction == "permit" + - add_pbr_policy_v3_again.current.0.vnsSvcRedirectPol.attributes.thresholdEnable == "yes" + when: version.current.0.topSystem.attributes.version is version('4.2', '<') + +# MODIFY L4-L7 PBR POLICY +- name: Update L4-L7 Policy Based Redirect (v4) + cisco.aci.aci_l4l7_policy_based_redirect: + <<: *aci_info + tenant: ansible_tenant + policy_name: ansible_pbr_policy + dest_type: l2 + hash_algorithm: ip_and_protocol + resilient_hash: false + min_threshold: 50 + max_threshold: 80 + threshold_enable: false + threshold_down_action: deny + pod_aware: false + monitor_policy: ansible_ip_sla_mon_policy + anycast_enabled: false + state: present + register: update_pbr_policy_v4 + when: version.current.0.topSystem.attributes.version is version('4.2', '>=') + +- name: Update L4-L7 Policy Based Redirect (v3) + cisco.aci.aci_l4l7_policy_based_redirect: + <<: *aci_info + tenant: ansible_tenant + policy_name: ansible_pbr_policy + hash_algorithm: ip_and_protocol + resilient_hash: false + min_threshold: 50 + max_threshold: 80 + threshold_enable: false + threshold_down_action: deny + pod_aware: false + anycast_enabled: false + state: present + register: update_pbr_policy_v3 + when: version.current.0.topSystem.attributes.version is version('4.2', '<') + +- name: Verify PBR Attributes (v4) + ansible.builtin.assert: + that: + - update_pbr_policy_v4 is changed + - update_pbr_policy_v4.current.0.vnsSvcRedirectPol.attributes.dn == "uni/tn-ansible_tenant/svcCont/svcRedirectPol-ansible_pbr_policy" + - update_pbr_policy_v4.current.0.vnsSvcRedirectPol.attributes.name == "ansible_pbr_policy" + - update_pbr_policy_v4.current.0.vnsSvcRedirectPol.attributes.AnycastEnabled == "no" + - update_pbr_policy_v4.current.0.vnsSvcRedirectPol.attributes.destType == "L2" + - update_pbr_policy_v4.current.0.vnsSvcRedirectPol.attributes.hashingAlgorithm == "sip-dip-prototype" + - update_pbr_policy_v4.current.0.vnsSvcRedirectPol.attributes.maxThresholdPercent == "80" + - update_pbr_policy_v4.current.0.vnsSvcRedirectPol.attributes.minThresholdPercent == "50" + - update_pbr_policy_v4.current.0.vnsSvcRedirectPol.attributes.programLocalPodOnly == "no" + - update_pbr_policy_v4.current.0.vnsSvcRedirectPol.attributes.resilientHashEnabled == "no" + - update_pbr_policy_v4.current.0.vnsSvcRedirectPol.attributes.thresholdDownAction == "deny" + - update_pbr_policy_v4.current.0.vnsSvcRedirectPol.attributes.thresholdEnable == "no" + - update_pbr_policy_v4.current.0.vnsSvcRedirectPol.children.0.vnsRsIPSLAMonitoringPol.attributes.tDn == "uni/tn-ansible_tenant/ipslaMonitoringPol-ansible_ip_sla_mon_policy" + when: version.current.0.topSystem.attributes.version is version('4.2', '>=') + +- name: Verify PBR Attributes (v3) + ansible.builtin.assert: + that: + - update_pbr_policy_v3 is changed + - update_pbr_policy_v3.current.0.vnsSvcRedirectPol.attributes.dn == "uni/tn-ansible_tenant/svcCont/svcRedirectPol-ansible_pbr_policy" + - update_pbr_policy_v3.current.0.vnsSvcRedirectPol.attributes.name == "ansible_pbr_policy" + - update_pbr_policy_v3.current.0.vnsSvcRedirectPol.attributes.AnycastEnabled == "no" + - update_pbr_policy_v3.current.0.vnsSvcRedirectPol.attributes.hashingAlgorithm == "sip-dip-prototype" + - update_pbr_policy_v3.current.0.vnsSvcRedirectPol.attributes.maxThresholdPercent == "80" + - update_pbr_policy_v3.current.0.vnsSvcRedirectPol.attributes.minThresholdPercent == "50" + - update_pbr_policy_v3.current.0.vnsSvcRedirectPol.attributes.programLocalPodOnly == "no" + - update_pbr_policy_v3.current.0.vnsSvcRedirectPol.attributes.resilientHashEnabled == "no" + - update_pbr_policy_v3.current.0.vnsSvcRedirectPol.attributes.thresholdDownAction == "deny" + - update_pbr_policy_v3.current.0.vnsSvcRedirectPol.attributes.thresholdEnable == "no" + when: version.current.0.topSystem.attributes.version is version('4.2', '<') + +- name: Remove Monitoring Policy + cisco.aci.aci_l4l7_policy_based_redirect: + <<: *aci_info + tenant: ansible_tenant + policy_name: ansible_pbr_policy + dest_type: l2 + state: present + register: remove_monitoring_policy + when: version.current.0.topSystem.attributes.version is version('4.2', '>=') + +- name: Verify Monitoring Policy Removal + ansible.builtin.assert: + that: + - remove_monitoring_policy.current.0.children is not defined + +# QUERY L4-L7 PBR POLICY +- name: Query L4-L7 Policy Based Redirect (v4) + cisco.aci.aci_l4l7_policy_based_redirect: + <<: *aci_info + tenant: ansible_tenant + policy_name: ansible_pbr_policy + state: query + register: query_pbr_policy_v4 + when: version.current.0.topSystem.attributes.version is version('4.2', '>=') + +- name: Query L4-L7 Policy Based Redirect (v3) + cisco.aci.aci_l4l7_policy_based_redirect: + <<: *aci_info + tenant: ansible_tenant + policy_name: ansible_pbr_policy + state: query + register: query_pbr_policy_v3 + when: version.current.0.topSystem.attributes.version is version('4.2', '<') + +- name: Verify PBR Attributes (v4) + ansible.builtin.assert: + that: + - query_pbr_policy_v4 is not changed + - query_pbr_policy_v4.current.0.vnsSvcRedirectPol.attributes.dn == "uni/tn-ansible_tenant/svcCont/svcRedirectPol-ansible_pbr_policy" + - query_pbr_policy_v4.current.0.vnsSvcRedirectPol.attributes.name == "ansible_pbr_policy" + - query_pbr_policy_v4.current.0.vnsSvcRedirectPol.attributes.AnycastEnabled == "no" + - query_pbr_policy_v4.current.0.vnsSvcRedirectPol.attributes.destType == "L2" + - query_pbr_policy_v4.current.0.vnsSvcRedirectPol.attributes.hashingAlgorithm == "sip-dip-prototype" + - query_pbr_policy_v4.current.0.vnsSvcRedirectPol.attributes.maxThresholdPercent == "80" + - query_pbr_policy_v4.current.0.vnsSvcRedirectPol.attributes.minThresholdPercent == "50" + - query_pbr_policy_v4.current.0.vnsSvcRedirectPol.attributes.programLocalPodOnly == "no" + - query_pbr_policy_v4.current.0.vnsSvcRedirectPol.attributes.resilientHashEnabled == "no" + - query_pbr_policy_v4.current.0.vnsSvcRedirectPol.attributes.thresholdDownAction == "deny" + - query_pbr_policy_v4.current.0.vnsSvcRedirectPol.attributes.thresholdEnable == "no" + when: version.current.0.topSystem.attributes.version is version('4.2', '>=') + +- name: Verify PBR Attributes (v3) + ansible.builtin.assert: + that: + - query_pbr_policy_v3 is not changed + - query_pbr_policy_v3.current.0.vnsSvcRedirectPol.attributes.dn == "uni/tn-ansible_tenant/svcCont/svcRedirectPol-ansible_pbr_policy" + - query_pbr_policy_v3.current.0.vnsSvcRedirectPol.attributes.name == "ansible_pbr_policy" + - query_pbr_policy_v3.current.0.vnsSvcRedirectPol.attributes.AnycastEnabled == "no" + - query_pbr_policy_v3.current.0.vnsSvcRedirectPol.attributes.hashingAlgorithm == "sip-dip-prototype" + - query_pbr_policy_v3.current.0.vnsSvcRedirectPol.attributes.maxThresholdPercent == "80" + - query_pbr_policy_v3.current.0.vnsSvcRedirectPol.attributes.minThresholdPercent == "50" + - query_pbr_policy_v3.current.0.vnsSvcRedirectPol.attributes.programLocalPodOnly == "no" + - query_pbr_policy_v3.current.0.vnsSvcRedirectPol.attributes.resilientHashEnabled == "no" + - query_pbr_policy_v3.current.0.vnsSvcRedirectPol.attributes.thresholdDownAction == "deny" + - query_pbr_policy_v3.current.0.vnsSvcRedirectPol.attributes.thresholdEnable == "no" + when: version.current.0.topSystem.attributes.version is version('4.2', '<') + +- name: Query all L4-L7 Policy Based Redirect Policies + cisco.aci.aci_l4l7_policy_based_redirect: + <<: *aci_info + state: query + register: query_pbr_policy_all + +- name: Verify PBR Attributes are unchanged + ansible.builtin.assert: + that: + - query_pbr_policy_all is not changed + +# DELETE L4-L7 POLICY BASED REDIRECT POLICY +- name: Delete L4-L7 Policy Based Redirect + cisco.aci.aci_l4l7_policy_based_redirect: + <<: *aci_info + tenant: ansible_tenant + policy_name: ansible_pbr_policy + state: absent + register: delete_pbr_policy + +- name: Confirm PBR Policy Removal + ansible.builtin.assert: + that: + - delete_pbr_policy is changed + - delete_pbr_policy.current == [] + - delete_pbr_policy.previous.0.vnsSvcRedirectPol.attributes.dn == "uni/tn-ansible_tenant/svcCont/svcRedirectPol-ansible_pbr_policy" + - delete_pbr_policy.previous.0.vnsSvcRedirectPol.attributes.name == "ansible_pbr_policy" + - delete_pbr_policy.previous.0.vnsSvcRedirectPol.attributes.AnycastEnabled == "no" + - delete_pbr_policy.previous.0.vnsSvcRedirectPol.attributes.hashingAlgorithm == "sip-dip-prototype" + - delete_pbr_policy.previous.0.vnsSvcRedirectPol.attributes.maxThresholdPercent == "80" + - delete_pbr_policy.previous.0.vnsSvcRedirectPol.attributes.minThresholdPercent == "50" + - delete_pbr_policy.previous.0.vnsSvcRedirectPol.attributes.programLocalPodOnly == "no" + - delete_pbr_policy.previous.0.vnsSvcRedirectPol.attributes.resilientHashEnabled == "no" + - delete_pbr_policy.previous.0.vnsSvcRedirectPol.attributes.thresholdDownAction == "deny" + - delete_pbr_policy.previous.0.vnsSvcRedirectPol.attributes.thresholdEnable == "no" + +# DELETE L4-L7 POLICY BASED REDIRECT POLICY AGAIN TO TEST IDEMPOTENCE +- name: Delete L4-L7 Policy Based Redirect again + cisco.aci.aci_l4l7_policy_based_redirect: + <<: *aci_info + tenant: ansible_tenant + policy_name: ansible_pbr_policy + state: absent + register: delete_pbr_policy_again + +- name: Confirm PBR Policy Removal idempotence + ansible.builtin.assert: + that: + - delete_pbr_policy_again is not changed + - delete_pbr_policy_again.current == [] + +# CLEAN UP +- name: Remove ansible_tenant + cisco.aci.aci_tenant: + <<: *aci_info + name: ansible_tenant + state: absent diff --git a/tests/integration/targets/aci_l4l7_policy_based_redirect_dest/aliases b/tests/integration/targets/aci_l4l7_policy_based_redirect_dest/aliases new file mode 100644 index 000000000..209b793f9 --- /dev/null +++ b/tests/integration/targets/aci_l4l7_policy_based_redirect_dest/aliases @@ -0,0 +1,2 @@ +# No ACI simulator yet, so not enabled +# unsupported diff --git a/tests/integration/targets/aci_l4l7_policy_based_redirect_dest/tasks/main.yml b/tests/integration/targets/aci_l4l7_policy_based_redirect_dest/tasks/main.yml new file mode 100644 index 000000000..e6b949f92 --- /dev/null +++ b/tests/integration/targets/aci_l4l7_policy_based_redirect_dest/tasks/main.yml @@ -0,0 +1,519 @@ +# Test code for the ACI modules +# Copyright: (c) 2021, 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 + +# 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: '{{ aci_output_level | default("info") }}' + +# CLEAN ENVIRONMENT +- name: Remove ansible_tenant if it already exists + cisco.aci.aci_tenant: + <<: *aci_info + name: ansible_tenant + state: absent + +# CREATE TENANT +- name: Create ansible_tenant + cisco.aci.aci_tenant: + <<: *aci_info + name: ansible_tenant + state: present + +# CREATE DOMAIN +- name: Create ansible_phys_domain + cisco.aci.aci_domain: + <<: *aci_info + domain: ansible_phys_dom + domain_type: phys + state: present + +# CREATE L4-L7 LOGICAL DEVICE +- name: Create L4-L7 Device + cisco.aci.aci_l4l7_device: + <<: *aci_info + tenant: ansible_tenant + device: ansible_device + domain: ansible_phys_dom + func_type: go_to + context_aware: single + managed: false + dev_type: physical + svc_type: adc + trunking: false + prom_mode: true + state: present + +# ADD L4-L7 CONCRETE DEVICE +- name: Create L4-L7 Concrete Device + cisco.aci.aci_l4l7_concrete_device: + <<: *aci_info + tenant: ansible_tenant + device: ansible_device + concrete_device: ansible_concrete_device + state: present + +# ADD L4-L7 CONCRETE INTERFACE +- name: Create L4-L7 Concrete Interface + cisco.aci.aci_l4l7_concrete_interface: + <<: *aci_info + tenant: ansible_tenant + device: ansible_device + concrete_device: ansible_concrete_device + concrete_interface: ansible_concrete_interface + pod_id: 1 + node_id: 201 + path_ep: eth1/12 + state: present + +- name: Create second L4-L7 Concrete Interface + cisco.aci.aci_l4l7_concrete_interface: + <<: *aci_info + tenant: ansible_tenant + device: ansible_device + concrete_device: ansible_concrete_device + concrete_interface: ansible_second_concrete_interface + pod_id: 1 + node_id: 201 + path_ep: eth1/13 + state: present + +# CREATE L4-L7 POLICY BASED REDIRECT +- name: Create L4-L7 Policy Based Redirect + cisco.aci.aci_l4l7_policy_based_redirect: + <<: *aci_info + tenant: ansible_tenant + policy_name: ansible_pbr_policy + hash_algorithm: destination_ip + resilient_hash: true + min_threshold: 60 + max_threshold: 90 + threshold_enable: true + threshold_down_action: permit + pod_aware: true + anycast_enabled: false + state: present + +# CREATE L4-L7 L2 POLICY BASED REDIRECT +- name: Create L4-L7 L2 Policy Based Redirect + cisco.aci.aci_l4l7_policy_based_redirect: + <<: *aci_info + tenant: ansible_tenant + policy_name: ansible_pbr_l2_policy + hash_algorithm: destination_ip + dest_type: l2 + resilient_hash: true + min_threshold: 60 + max_threshold: 90 + threshold_enable: true + threshold_down_action: permit + pod_aware: true + anycast_enabled: false + state: present + +# CREATE HEALTH GROUP +- name: Add a new Redirect Health Group + cisco.aci.aci_l4l7_redirect_health_group: + <<: *aci_info + tenant: ansible_tenant + health_group: ansible_health_group + state: present + +# CREATE L4-L7 PBR DESTINATION +- name: Create L4-L7 Policy Based Redirect Destination + cisco.aci.aci_l4l7_policy_based_redirect_dest: + <<: *aci_info + tenant: ansible_tenant + policy: ansible_pbr_policy + redirect_ip: 192.168.10.1 + additional_ip: 192.168.50.1 + redirect_mac: AB:CD:EF:12:34:56 + dest_name: redirect_dest + health_group: ansible_health_group + pod_id: 1 + state: present + register: add_pbr_dest + +- name: Create L4-L7 L1/L2 Policy Based Redirect Destination + cisco.aci.aci_l4l7_policy_based_redirect_dest: + <<: *aci_info + tenant: ansible_tenant + policy: ansible_pbr_l2_policy + redirect_mac: AB:CD:EF:12:34:57 + dest_name: l1l2_redirect_dest + dest_type: l1/l2 + logical_dev: ansible_device + concrete_dev: ansible_concrete_device + concrete_intf: ansible_concrete_interface + health_group: ansible_health_group + pod_id: 1 + state: present + register: add_pbr_l1l2_dest + +- name: Create Second L4-L7 Policy Based Redirect Destination + cisco.aci.aci_l4l7_policy_based_redirect_dest: + <<: *aci_info + tenant: ansible_tenant + policy: ansible_pbr_policy + redirect_ip: 192.168.30.1 + redirect_mac: AB:CD:EF:12:34:60 + dest_name: second_redirect_dest + pod_id: 1 + state: present + +- name: Create Second L4-L7 L1/L2 Policy Based Redirect Destination + cisco.aci.aci_l4l7_policy_based_redirect_dest: + <<: *aci_info + tenant: ansible_tenant + policy: ansible_pbr_l2_policy + redirect_mac: AB:CD:EF:12:34:61 + dest_name: second_l1l2_redirect_dest + dest_type: l1/l2 + logical_dev: ansible_device + concrete_dev: ansible_concrete_device + concrete_intf: ansible_second_concrete_interface + pod_id: 1 + state: present + +- name: Verify L4-L7 Policy Based Redirect Destination creation + ansible.builtin.assert: + that: + - add_pbr_dest.current.0.vnsRedirectDest.attributes.dn == "uni/tn-ansible_tenant/svcCont/svcRedirectPol-ansible_pbr_policy/RedirectDest_ip-[192.168.10.1]" + - add_pbr_dest.current.0.vnsRedirectDest.attributes.ip == "192.168.10.1" + - add_pbr_dest.current.0.vnsRedirectDest.attributes.ip2 == "192.168.50.1" + - add_pbr_dest.current.0.vnsRedirectDest.attributes.mac == "AB:CD:EF:12:34:56" + - add_pbr_dest.current.0.vnsRedirectDest.attributes.destName == "redirect_dest" + - add_pbr_dest.current.0.vnsRedirectDest.attributes.podId == "1" + - add_pbr_dest.current.0.vnsRedirectDest.children.0.vnsRsRedirectHealthGroup.attributes.tDn == "uni/tn-ansible_tenant/svcCont/redirectHealthGroup-ansible_health_group" + - add_pbr_l1l2_dest.current.0.vnsL1L2RedirectDest.attributes.dn == "uni/tn-ansible_tenant/svcCont/svcRedirectPol-ansible_pbr_l2_policy/L1L2RedirectDest-l1l2_redirect_dest" + - add_pbr_l1l2_dest.current.0.vnsL1L2RedirectDest.attributes.mac == "AB:CD:EF:12:34:57" + - add_pbr_l1l2_dest.current.0.vnsL1L2RedirectDest.attributes.destName == "l1l2_redirect_dest" + - add_pbr_l1l2_dest.current.0.vnsL1L2RedirectDest.attributes.podId == "1" + - add_pbr_l1l2_dest.current.0.vnsL1L2RedirectDest.children.0.vnsRsToCIf.attributes.tDn == "uni/tn-ansible_tenant/lDevVip-ansible_device/cDev-ansible_concrete_device/cIf-[ansible_concrete_interface]" + - add_pbr_l1l2_dest.current.0.vnsL1L2RedirectDest.children.1.vnsRsL1L2RedirectHealthGroup.attributes.tDn == "uni/tn-ansible_tenant/svcCont/redirectHealthGroup-ansible_health_group" + +# ADD L4-L7 PBR DESTINATION AGAIN TO TEST IDEMPOTENCE +- name: Add L4-L7 Policy Based Redirect Destination again + cisco.aci.aci_l4l7_policy_based_redirect_dest: + <<: *aci_info + tenant: ansible_tenant + policy: ansible_pbr_policy + redirect_ip: 192.168.10.1 + additional_ip: 192.168.50.1 + redirect_mac: AB:CD:EF:12:34:56 + dest_name: redirect_dest + health_group: ansible_health_group + pod_id: 1 + state: present + register: add_pbr_dest_again + +- name: Add L4-L7 L1/L2 Policy Based Redirect Destination again + cisco.aci.aci_l4l7_policy_based_redirect_dest: + <<: *aci_info + tenant: ansible_tenant + policy: ansible_pbr_l2_policy + redirect_mac: AB:CD:EF:12:34:57 + dest_name: l1l2_redirect_dest + dest_type: l1/l2 + logical_dev: ansible_device + concrete_dev: ansible_concrete_device + concrete_intf: ansible_concrete_interface + health_group: ansible_health_group + pod_id: 1 + state: present + register: add_pbr_l1l2_dest_again + +- name: Verify L4-L7 Policy Based Redirect Destination + ansible.builtin.assert: + that: + - add_pbr_dest_again is not changed + - add_pbr_l1l2_dest_again is not changed + - add_pbr_dest_again.current.0.vnsRedirectDest.attributes.dn == "uni/tn-ansible_tenant/svcCont/svcRedirectPol-ansible_pbr_policy/RedirectDest_ip-[192.168.10.1]" + - add_pbr_dest_again.current.0.vnsRedirectDest.attributes.ip == "192.168.10.1" + - add_pbr_dest_again.current.0.vnsRedirectDest.attributes.ip2 == "192.168.50.1" + - add_pbr_dest_again.current.0.vnsRedirectDest.attributes.mac == "AB:CD:EF:12:34:56" + - add_pbr_dest_again.current.0.vnsRedirectDest.attributes.destName == "redirect_dest" + - add_pbr_dest_again.current.0.vnsRedirectDest.attributes.podId == "1" + - add_pbr_dest_again.current.0.vnsRedirectDest.children.0.vnsRsRedirectHealthGroup.attributes.tDn == "uni/tn-ansible_tenant/svcCont/redirectHealthGroup-ansible_health_group" + - add_pbr_l1l2_dest_again.current.0.vnsL1L2RedirectDest.attributes.dn == "uni/tn-ansible_tenant/svcCont/svcRedirectPol-ansible_pbr_l2_policy/L1L2RedirectDest-l1l2_redirect_dest" + - add_pbr_l1l2_dest_again.current.0.vnsL1L2RedirectDest.attributes.mac == "AB:CD:EF:12:34:57" + - add_pbr_l1l2_dest_again.current.0.vnsL1L2RedirectDest.attributes.destName == "l1l2_redirect_dest" + - add_pbr_l1l2_dest_again.current.0.vnsL1L2RedirectDest.attributes.podId == "1" + - add_pbr_l1l2_dest_again.current.0.vnsL1L2RedirectDest.children.0.vnsRsToCIf.attributes.tDn == "uni/tn-ansible_tenant/lDevVip-ansible_device/cDev-ansible_concrete_device/cIf-[ansible_concrete_interface]" + - add_pbr_l1l2_dest_again.current.0.vnsL1L2RedirectDest.children.1.vnsRsL1L2RedirectHealthGroup.attributes.tDn == "uni/tn-ansible_tenant/svcCont/redirectHealthGroup-ansible_health_group" + +# TEST ERROR CHECKING +- name: Add L4-L7 L1/L2 Policy Based Redirect Destination with ip + cisco.aci.aci_l4l7_policy_based_redirect_dest: + <<: *aci_info + tenant: ansible_tenant + policy: ansible_pbr_l2_policy + redirect_mac: AB:CD:EF:12:34:57 + dest_name: l1l2_redirect_dest + dest_type: l1/l2 + logical_dev: ansible_device + concrete_dev: ansible_concrete_device + concrete_intf: ansible_concrete_interface + health_group: ansible_health_group + pod_id: 1 + ip: 10.20.30.40 + state: present + register: add_pbr_l1l2_ip + ignore_errors: true + +- name: Add L4-L7 L1/L2 Policy Based Redirect Destination with additional ip + cisco.aci.aci_l4l7_policy_based_redirect_dest: + <<: *aci_info + tenant: ansible_tenant + policy: ansible_pbr_l2_policy + redirect_mac: AB:CD:EF:12:34:57 + dest_name: l1l2_redirect_dest + dest_type: l1/l2 + logical_dev: ansible_device + concrete_dev: ansible_concrete_device + concrete_intf: ansible_concrete_interface + health_group: ansible_health_group + pod_id: 1 + additional_ip: 10.20.30.40 + state: present + register: add_pbr_l1l2_add_ip + ignore_errors: true + +- name: Add L4-L7 L3 Policy Based Redirect Destination with redirect interface + cisco.aci.aci_l4l7_policy_based_redirect_dest: + <<: *aci_info + tenant: ansible_tenant + policy: ansible_pbr_l2_policy + redirect_mac: AB:CD:EF:12:34:57 + dest_name: l3_redirect_dest + dest_type: l3 + logical_dev: ansible_device + concrete_dev: ansible_concrete_device + concrete_intf: ansible_concrete_interface + health_group: ansible_health_group + pod_id: 1 + ip: 10.20.30.40 + state: present + register: add_pbr_l3_redirect_intf + ignore_errors: true + +- name: Add L4-L7 L1/L2 Policy Based Redirect Destination with no redirect interface + cisco.aci.aci_l4l7_policy_based_redirect_dest: + <<: *aci_info + tenant: ansible_tenant + policy: ansible_pbr_l2_policy + redirect_mac: AB:CD:EF:12:34:57 + dest_name: l1l2_redirect_dest + dest_type: l1/l2 + health_group: ansible_health_group + pod_id: 1 + state: present + register: add_pbr_l1l2_no_redirect_intf + ignore_errors: true + +- name: Validate error messages + ansible.builtin.assert: + that: + - add_pbr_l1l2_ip is failed + - add_pbr_l1l2_ip.msg == "You cannot provide an ip when configuring an l1/l2 destination" + - add_pbr_l1l2_add_ip is failed + - add_pbr_l1l2_add_ip.msg == "You cannot provide an additional_ip when configuring an l1/l2 destination" + - add_pbr_l3_redirect_intf is failed + - add_pbr_l3_redirect_intf.msg == "You cannot provide a logical_dev, concrete_dev or concrete_intf when configuring an l3 destination" + - add_pbr_l1l2_no_redirect_intf is failed + - add_pbr_l1l2_no_redirect_intf.msg == "You must provide a logical_dev, concrete_dev and concrete_intf when configuring an l1/l2 destination" + +# MODIFY L4-L7 PBR DESTINATION +- name: Modify L4-L7 Policy Based Redirect Destination + cisco.aci.aci_l4l7_policy_based_redirect_dest: + <<: *aci_info + tenant: ansible_tenant + policy: ansible_pbr_policy + redirect_ip: 192.168.10.1 + redirect_mac: AB:CD:EF:12:34:57 + dest_name: updated_redirect_dest + pod_id: 1 + state: present + register: update_pbr_dest + +- name: Modify L4-L7 L1/L2 Policy Based Redirect Destination + cisco.aci.aci_l4l7_policy_based_redirect_dest: + <<: *aci_info + tenant: ansible_tenant + policy: ansible_pbr_l2_policy + redirect_mac: AB:CD:EF:12:34:58 + dest_name: l1l2_redirect_dest + dest_type: l1/l2 + logical_dev: ansible_device + concrete_dev: ansible_concrete_device + concrete_intf: ansible_concrete_interface + pod_id: 1 + state: present + register: update_pbr_l1l2_dest + +- name: Verify L4-L7 Policy Based Redirect Destination update, including removal of health group bindings + ansible.builtin.assert: + that: + - update_pbr_dest is changed + - update_pbr_dest.current.0.vnsRedirectDest.attributes.dn == "uni/tn-ansible_tenant/svcCont/svcRedirectPol-ansible_pbr_policy/RedirectDest_ip-[192.168.10.1]" + - update_pbr_dest.current.0.vnsRedirectDest.attributes.ip == "192.168.10.1" + - update_pbr_dest.current.0.vnsRedirectDest.attributes.mac == "AB:CD:EF:12:34:57" + - update_pbr_dest.current.0.vnsRedirectDest.attributes.destName == "updated_redirect_dest" + - update_pbr_dest.current.0.vnsRedirectDest.attributes.podId == "1" + - update_pbr_dest.current.0.vnsRedirectDest.children is not defined + - update_pbr_l1l2_dest.current.0.vnsL1L2RedirectDest.attributes.dn == "uni/tn-ansible_tenant/svcCont/svcRedirectPol-ansible_pbr_l2_policy/L1L2RedirectDest-l1l2_redirect_dest" + - update_pbr_l1l2_dest.current.0.vnsL1L2RedirectDest.attributes.mac == "AB:CD:EF:12:34:58" + - update_pbr_l1l2_dest.current.0.vnsL1L2RedirectDest.attributes.destName == "l1l2_redirect_dest" + - update_pbr_l1l2_dest.current.0.vnsL1L2RedirectDest.attributes.podId == "1" + - update_pbr_l1l2_dest.current.0.vnsL1L2RedirectDest.children.0.vnsRsToCIf.attributes.tDn == "uni/tn-ansible_tenant/lDevVip-ansible_device/cDev-ansible_concrete_device/cIf-[ansible_concrete_interface]" + - update_pbr_l1l2_dest.current.0.vnsL1L2RedirectDest.children | length == 1 + +# QUERY L4-L7 PBR DESTINATION +- name: Query L4-L7 Policy Based Redirect Destination + cisco.aci.aci_l4l7_policy_based_redirect_dest: + <<: *aci_info + tenant: ansible_tenant + policy: ansible_pbr_policy + redirect_ip: 192.168.10.1 + state: query + register: query_pbr_dest + +- name: Query L4-L7 Policy Based Redirect L1/L2 Destination + cisco.aci.aci_l4l7_policy_based_redirect_dest: + <<: *aci_info + tenant: ansible_tenant + policy: ansible_pbr_l2_policy + dest_name: l1l2_redirect_dest + dest_type: l1/l2 + state: query + register: query_l1l2_pbr_dest + +- name: Verify L4-L7 Policy Based Redirect Destination query + ansible.builtin.assert: + that: + - query_pbr_dest is not changed + - query_l1l2_pbr_dest is not changed + - query_pbr_dest.current.0.vnsRedirectDest.attributes.dn == "uni/tn-ansible_tenant/svcCont/svcRedirectPol-ansible_pbr_policy/RedirectDest_ip-[192.168.10.1]" + - query_pbr_dest.current.0.vnsRedirectDest.attributes.ip == "192.168.10.1" + - query_pbr_dest.current.0.vnsRedirectDest.attributes.mac == "AB:CD:EF:12:34:57" + - query_pbr_dest.current.0.vnsRedirectDest.attributes.destName == "updated_redirect_dest" + - query_pbr_dest.current.0.vnsRedirectDest.attributes.podId == "1" + - query_l1l2_pbr_dest.current.0.vnsL1L2RedirectDest.attributes.dn == "uni/tn-ansible_tenant/svcCont/svcRedirectPol-ansible_pbr_l2_policy/L1L2RedirectDest-l1l2_redirect_dest" + - query_l1l2_pbr_dest.current.0.vnsL1L2RedirectDest.attributes.mac == "AB:CD:EF:12:34:58" + - query_l1l2_pbr_dest.current.0.vnsL1L2RedirectDest.attributes.destName == "l1l2_redirect_dest" + - query_l1l2_pbr_dest.current.0.vnsL1L2RedirectDest.attributes.podId == "1" + - query_l1l2_pbr_dest.current.0.vnsL1L2RedirectDest.children.0.vnsRsToCIf.attributes.tDn == "uni/tn-ansible_tenant/lDevVip-ansible_device/cDev-ansible_concrete_device/cIf-[ansible_concrete_interface]" + +- name: Query All L4-L7 Policy Based Redirect L3 Destinations + cisco.aci.aci_l4l7_policy_based_redirect_dest: + <<: *aci_info + state: query + register: query_pbr_dest_all + +- name: Query All L4-L7 Policy Based Redirect L1/L2 Destinations + cisco.aci.aci_l4l7_policy_based_redirect_dest: + <<: *aci_info + state: query + register: query_pbr_l1l2_dest_all + +- name: Verify query_pbr_dest_all + ansible.builtin.assert: + that: + - query_pbr_dest_all is not changed + - query_pbr_l1l2_dest_all is not changed + - query_pbr_dest_all.current | length > 1 + - query_pbr_l1l2_dest_all.current | length > 1 + +# DELETE L4-L7 PBR DESTINATION +- name: Delete L4-L7 Policy Based Redirect Destination + cisco.aci.aci_l4l7_policy_based_redirect_dest: + <<: *aci_info + tenant: ansible_tenant + policy: ansible_pbr_policy + redirect_ip: 192.168.10.1 + state: absent + register: delete_pbr_dest + +- name: Delete L4-L7 L1/L2 Policy Based Redirect Destination + cisco.aci.aci_l4l7_policy_based_redirect_dest: + <<: *aci_info + tenant: ansible_tenant + policy: ansible_pbr_l2_policy + dest_name: l1l2_redirect_dest + dest_type: l1/l2 + state: absent + register: delete_pbr_l1l2_dest + +- name: Verify L4-L7 Policy Based Redirect Destination deletion + ansible.builtin.assert: + that: + - delete_pbr_dest is changed + - delete_pbr_dest.current == [] + - delete_pbr_l1l2_dest is changed + - delete_pbr_l1l2_dest.current == [] + - delete_pbr_dest.previous.0.vnsRedirectDest.attributes.dn == "uni/tn-ansible_tenant/svcCont/svcRedirectPol-ansible_pbr_policy/RedirectDest_ip-[192.168.10.1]" + - delete_pbr_dest.previous.0.vnsRedirectDest.attributes.ip == "192.168.10.1" + - delete_pbr_dest.previous.0.vnsRedirectDest.attributes.mac == "AB:CD:EF:12:34:57" + - delete_pbr_dest.previous.0.vnsRedirectDest.attributes.destName == "updated_redirect_dest" + - delete_pbr_dest.previous.0.vnsRedirectDest.attributes.podId == "1" + - delete_pbr_l1l2_dest.previous.0.vnsL1L2RedirectDest.attributes.dn == "uni/tn-ansible_tenant/svcCont/svcRedirectPol-ansible_pbr_l2_policy/L1L2RedirectDest-l1l2_redirect_dest" + - delete_pbr_l1l2_dest.previous.0.vnsL1L2RedirectDest.attributes.mac == "AB:CD:EF:12:34:58" + - delete_pbr_l1l2_dest.previous.0.vnsL1L2RedirectDest.attributes.destName == "l1l2_redirect_dest" + - delete_pbr_l1l2_dest.previous.0.vnsL1L2RedirectDest.attributes.podId == "1" + + +# DELETE L4-L7 PBR DESTINATION AGAIN TO TEST IDEMPOTENCE +- name: Delete L4-L7 Policy Based Redirect Destination again + cisco.aci.aci_l4l7_policy_based_redirect_dest: + <<: *aci_info + tenant: ansible_tenant + policy: ansible_pbr_policy + redirect_ip: 192.168.10.1 + state: absent + register: delete_pbr_dest_again + +- name: Delete L4-L7 L1/L2 Policy Based Redirect Destination again + cisco.aci.aci_l4l7_policy_based_redirect_dest: + <<: *aci_info + tenant: ansible_tenant + policy: ansible_pbr_l2_policy + dest_name: l1l2_redirect_dest + dest_type: l1/l2 + state: absent + register: delete_pbr_l1l2_dest_again + +- name: Verify L4-L7 Policy Based Redirect Destination deletion idempotence + ansible.builtin.assert: + that: + - delete_pbr_dest_again is not changed + - delete_pbr_dest_again.current == [] + - delete_pbr_l1l2_dest_again is not changed + - delete_pbr_l1l2_dest_again.current == [] + +# CLEAN UP +- name: Remove ansible_tenant + cisco.aci.aci_tenant: + <<: *aci_info + name: ansible_tenant + state: absent + +- name: Remove ansible_phys_domain + cisco.aci.aci_domain: + <<: *aci_info + domain: ansible_phys_dom + domain_type: phys + state: absent \ No newline at end of file diff --git a/tests/integration/targets/aci_l4l7_redirect_health_group/aliases b/tests/integration/targets/aci_l4l7_redirect_health_group/aliases new file mode 100644 index 000000000..209b793f9 --- /dev/null +++ b/tests/integration/targets/aci_l4l7_redirect_health_group/aliases @@ -0,0 +1,2 @@ +# No ACI simulator yet, so not enabled +# unsupported diff --git a/tests/integration/targets/aci_l4l7_redirect_health_group/tasks/main.yml b/tests/integration/targets/aci_l4l7_redirect_health_group/tasks/main.yml new file mode 100644 index 000000000..e7c21c039 --- /dev/null +++ b/tests/integration/targets/aci_l4l7_redirect_health_group/tasks/main.yml @@ -0,0 +1,121 @@ +# Test code for the ACI modules +# Copyright: (c) 2021, 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 + + # 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: '{{ aci_output_level | default("info") }}' + +# CLEAN ENVIRONMENT +- name: Remove ansible_tenant if it already exists + cisco.aci.aci_tenant: + <<: *aci_info + name: ansible_tenant + state: absent + +# CREATE TENANT +- name: Remove ansible_tenant if it already exists + cisco.aci.aci_tenant: + <<: *aci_info + name: ansible_tenant + state: present + +# CREATE REDIRECT HEALTH GROUP +- name: Add a new Redirect Health Group + cisco.aci.aci_l4l7_redirect_health_group: + <<: *aci_info + tenant: ansible_tenant + health_group: ansible_health_group + state: present + register: add_health_group + +- name: Verify L4-L7 Health Group + ansible.builtin.assert: + that: + - add_health_group.current.0.vnsRedirectHealthGroup.attributes.dn == "uni/tn-ansible_tenant/svcCont/redirectHealthGroup-ansible_health_group" + - add_health_group.current.0.vnsRedirectHealthGroup.attributes.name == "ansible_health_group" + +# CREATE REDIRECT HEALTH GROUP AGAIN TO TEST IDEMPOTENCE +- name: Add a new Redirect Health Group again + cisco.aci.aci_l4l7_redirect_health_group: + <<: *aci_info + tenant: ansible_tenant + health_group: ansible_health_group + state: present + register: add_health_group_again + +- name: Verify L4-L7 Health Group idempotence + ansible.builtin.assert: + that: + - add_health_group_again is not changed + - add_health_group_again.current.0.vnsRedirectHealthGroup.attributes.dn == "uni/tn-ansible_tenant/svcCont/redirectHealthGroup-ansible_health_group" + - add_health_group_again.current.0.vnsRedirectHealthGroup.attributes.name == "ansible_health_group" + +# QUERY REDIRECT HEALTH GROUP +- name: Query Redirect Health Group + cisco.aci.aci_l4l7_redirect_health_group: + <<: *aci_info + tenant: ansible_tenant + health_group: ansible_health_group + state: query + register: query_health_group + +- name: Verify L4-L7 Health Group query + ansible.builtin.assert: + that: + - query_health_group is not changed + - query_health_group.current.0.vnsRedirectHealthGroup.attributes.dn == "uni/tn-ansible_tenant/svcCont/redirectHealthGroup-ansible_health_group" + - query_health_group.current.0.vnsRedirectHealthGroup.attributes.name == "ansible_health_group" + +# REMOVE L4-L7 HEALTH GROUP +- name: Remove Redirect Health Group + cisco.aci.aci_l4l7_redirect_health_group: + <<: *aci_info + tenant: ansible_tenant + health_group: ansible_health_group + state: absent + register: delete_health_group + +- name: Verify L4-L7 Health Group removal + ansible.builtin.assert: + that: + - delete_health_group is changed + - delete_health_group.current == [] + - delete_health_group.previous.0.vnsRedirectHealthGroup.attributes.dn == "uni/tn-ansible_tenant/svcCont/redirectHealthGroup-ansible_health_group" + - delete_health_group.previous.0.vnsRedirectHealthGroup.attributes.name == "ansible_health_group" + +# REMOVE L4-L7 HEALTH GROUP AGAIN TO TEST IDEMPOTENCE +- name: Remove Redirect Health Group again + cisco.aci.aci_l4l7_redirect_health_group: + <<: *aci_info + tenant: ansible_tenant + health_group: ansible_health_group + state: absent + register: delete_health_group_again + +- name: Verify L4-L7 Health Group removal idempotence + ansible.builtin.assert: + that: + - delete_health_group_again is not changed + - delete_health_group_again.current == [] + +# CLEAN UP +- name: Remove ansible_tenant + cisco.aci.aci_tenant: + <<: *aci_info + name: ansible_tenant + state: absent diff --git a/tests/integration/targets/aci_l4l7_service_graph_template/aliases b/tests/integration/targets/aci_l4l7_service_graph_template/aliases new file mode 100644 index 000000000..209b793f9 --- /dev/null +++ b/tests/integration/targets/aci_l4l7_service_graph_template/aliases @@ -0,0 +1,2 @@ +# No ACI simulator yet, so not enabled +# unsupported diff --git a/tests/integration/targets/aci_l4l7_service_graph_template/tasks/main.yml b/tests/integration/targets/aci_l4l7_service_graph_template/tasks/main.yml new file mode 100644 index 000000000..77a81efe5 --- /dev/null +++ b/tests/integration/targets/aci_l4l7_service_graph_template/tasks/main.yml @@ -0,0 +1,132 @@ +# Test code for the ACI modules +# Copyright: (c) 2021, 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 + +# 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: '{{ aci_output_level | default("info") }}' + +# CLEAN ENVIRONMENT +- name: Remove ansible_tenant if it already exists + cisco.aci.aci_tenant: + <<: *aci_info + name: ansible_tenant + state: absent + +# CREATE TENANT +- name: Create ansible_tenant + cisco.aci.aci_tenant: + <<: *aci_info + name: ansible_tenant + state: present + +# ADD service graph template +- name: Create L4-L7 Service Graph Template + cisco.aci.aci_l4l7_service_graph_template: + <<: *aci_info + tenant: ansible_tenant + service_graph: ansible_service_graph + state: present + register: create_sgt + +- name: Verify L4-L7 Service Graph Template has been created + ansible.builtin.assert: + that: + - create_sgt.current.0.vnsAbsGraph.attributes.dn == "uni/tn-ansible_tenant/AbsGraph-ansible_service_graph" + - create_sgt.current.0.vnsAbsGraph.attributes.name == "ansible_service_graph" + +# ADD service graph template again to check idempotence +- name: Create L4-L7 Service Graph Template again + cisco.aci.aci_l4l7_service_graph_template: + <<: *aci_info + tenant: ansible_tenant + service_graph: ansible_service_graph + state: present + register: create_sgt_again + +- name: Verify L4-L7 Service Graph Template attributes + ansible.builtin.assert: + that: + - create_sgt_again is not changed + - create_sgt_again.current.0.vnsAbsGraph.attributes.dn == "uni/tn-ansible_tenant/AbsGraph-ansible_service_graph" + - create_sgt_again.current.0.vnsAbsGraph.attributes.name == "ansible_service_graph" + +# QUERY service graph template +- name: Query L4-L7 Service Graph Template + cisco.aci.aci_l4l7_service_graph_template: + <<: *aci_info + tenant: ansible_tenant + service_graph: ansible_service_graph + state: query + register: query_sgt + +- name: Verify L4-L7 Service Graph Template attributes + ansible.builtin.assert: + that: + - query_sgt is not changed + - query_sgt.current.0.vnsAbsGraph.attributes.dn == "uni/tn-ansible_tenant/AbsGraph-ansible_service_graph" + - query_sgt.current.0.vnsAbsGraph.attributes.name == "ansible_service_graph" + +- name: Query all L4-L7 Service Graph Templates + cisco.aci.aci_l4l7_service_graph_template: + <<: *aci_info + state: query + register: query_sgt_all + +- name: Verify L4-L7 Service Graph Template attributes + ansible.builtin.assert: + that: + query_sgt_all is not changed + +# DELETE service graph template +- name: Remove L4-L7 Service Graph Template + cisco.aci.aci_l4l7_service_graph_template: + <<: *aci_info + tenant: ansible_tenant + service_graph: ansible_service_graph + state: absent + register: remove_sgt + +- name: Verify L4-L7 Service Graph Template deletion + ansible.builtin.assert: + that: + - remove_sgt is changed + - remove_sgt.current == [] + - remove_sgt.previous.0.vnsAbsGraph.attributes.dn == "uni/tn-ansible_tenant/AbsGraph-ansible_service_graph" + - remove_sgt.previous.0.vnsAbsGraph.attributes.name == "ansible_service_graph" + +# DELETE SERVICE GRAPH TEMPLATE AGAIN TO TEST IDEMPOTENCE +- name: Remove L4-L7 Service Graph Template + cisco.aci.aci_l4l7_service_graph_template: + <<: *aci_info + tenant: ansible_tenant + service_graph: ansible_service_graph + state: absent + register: remove_sgt_again + +- name: Verify L4-L7 Service Graph Template deletion idempotence + ansible.builtin.assert: + that: + - remove_sgt_again is not changed + - remove_sgt_again.current == [] + +# CLEAN UP +- name: Remove ansible_tenant + cisco.aci.aci_tenant: + <<: *aci_info + name: ansible_tenant + state: absent diff --git a/tests/integration/targets/aci_l4l7_service_graph_template_abs_conn/aliases b/tests/integration/targets/aci_l4l7_service_graph_template_abs_conn/aliases new file mode 100644 index 000000000..209b793f9 --- /dev/null +++ b/tests/integration/targets/aci_l4l7_service_graph_template_abs_conn/aliases @@ -0,0 +1,2 @@ +# No ACI simulator yet, so not enabled +# unsupported diff --git a/tests/integration/targets/aci_l4l7_service_graph_template_abs_conn/tasks/main.yml b/tests/integration/targets/aci_l4l7_service_graph_template_abs_conn/tasks/main.yml new file mode 100644 index 000000000..993f60eb0 --- /dev/null +++ b/tests/integration/targets/aci_l4l7_service_graph_template_abs_conn/tasks/main.yml @@ -0,0 +1,203 @@ +# Test code for the ACI modules +# Copyright: (c) 2021, 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 + +# 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: '{{ aci_output_level | default("info") }}' + +# CLEAN ENVIRONMENT +- name: Remove ansible_tenant if it already exists + cisco.aci.aci_tenant: + <<: *aci_info + name: ansible_tenant + state: absent + +# CREATE TENANT +- name: Create ansible_tenant + cisco.aci.aci_tenant: + <<: *aci_info + name: ansible_tenant + state: present + +# ADD SERVICE GRAPH TEMPLATE +- name: Create L4-L7 Service Graph Template + cisco.aci.aci_l4l7_service_graph_template: + <<: *aci_info + tenant: ansible_tenant + service_graph: ansible_graph + state: present + +# ADD SERVICE GRAPH ABS CONNECTION +- name: Create L4-L7 Service Graph Abs Connection + cisco.aci.aci_l4l7_service_graph_template_abs_conn: + <<: *aci_info + tenant: ansible_tenant + service_graph: ansible_graph + connection_name: C1 + direct_connect: true + unicast_route: true + adjacency_type: l3 + state: present + register: add_l4l7_abs_conn + +# VERIFY SERVICE GRAPH ABS CONNECTION +- name: Verify Connection Attributes + ansible.builtin.assert: + that: + - add_l4l7_abs_conn.current.0.vnsAbsConnection.attributes.dn == "uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsConnection-C1" + - add_l4l7_abs_conn.current.0.vnsAbsConnection.attributes.name == "C1" + - add_l4l7_abs_conn.current.0.vnsAbsConnection.attributes.adjType == "L3" + - add_l4l7_abs_conn.current.0.vnsAbsConnection.attributes.connDir == "provider" + - add_l4l7_abs_conn.current.0.vnsAbsConnection.attributes.connType == "external" + - add_l4l7_abs_conn.current.0.vnsAbsConnection.attributes.directConnect == "yes" + - add_l4l7_abs_conn.current.0.vnsAbsConnection.attributes.unicastRoute == "yes" + +# ADD SERVICE GRAPH ABS CONNECTION AGAIN TO TEST IDEMPOTENCE +- name: Create L4-L7 Service Graph Abs Connection again + cisco.aci.aci_l4l7_service_graph_template_abs_conn: + <<: *aci_info + tenant: ansible_tenant + service_graph: ansible_graph + connection_name: C1 + direct_connect: true + unicast_route: true + adjacency_type: l3 + state: present + register: add_l4l7_abs_conn_again + +# VERIFY SERVICE GRAPH ABS CONNECTION ARE UNCHANGED +- name: Verify Connection Attributes are unchanged + ansible.builtin.assert: + that: + - add_l4l7_abs_conn_again is not changed + - add_l4l7_abs_conn_again.current.0.vnsAbsConnection.attributes.dn == "uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsConnection-C1" + - add_l4l7_abs_conn_again.current.0.vnsAbsConnection.attributes.name == "C1" + - add_l4l7_abs_conn_again.current.0.vnsAbsConnection.attributes.adjType == "L3" + - add_l4l7_abs_conn_again.current.0.vnsAbsConnection.attributes.connDir == "provider" + - add_l4l7_abs_conn_again.current.0.vnsAbsConnection.attributes.connType == "external" + - add_l4l7_abs_conn_again.current.0.vnsAbsConnection.attributes.directConnect == "yes" + - add_l4l7_abs_conn_again.current.0.vnsAbsConnection.attributes.unicastRoute == "yes" + +# MODIFY SERVICE GRAPH ABS CONNECTION +- name: Modify L4-L7 Service Graph Abs Connection + cisco.aci.aci_l4l7_service_graph_template_abs_conn: + <<: *aci_info + tenant: ansible_tenant + service_graph: ansible_graph + connection_name: C1 + direct_connect: false + unicast_route: false + adjacency_type: l2 + state: present + register: update_l4l7_abs_conn + +# VERIFY SERVICE GRAPH ABS CONNECTION +- name: Verify Connection Attributes + ansible.builtin.assert: + that: + - update_l4l7_abs_conn is changed + - update_l4l7_abs_conn.current.0.vnsAbsConnection.attributes.dn == "uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsConnection-C1" + - update_l4l7_abs_conn.current.0.vnsAbsConnection.attributes.name == "C1" + - update_l4l7_abs_conn.current.0.vnsAbsConnection.attributes.adjType == "L2" + - update_l4l7_abs_conn.current.0.vnsAbsConnection.attributes.connDir == "provider" + - update_l4l7_abs_conn.current.0.vnsAbsConnection.attributes.connType == "external" + - update_l4l7_abs_conn.current.0.vnsAbsConnection.attributes.directConnect == "no" + - update_l4l7_abs_conn.current.0.vnsAbsConnection.attributes.unicastRoute == "no" + +# QUERY SERVICE GRAPH ABS CONNECTION +- name: Query L4-L7 Service Graph Abs Connection + cisco.aci.aci_l4l7_service_graph_template_abs_conn: + <<: *aci_info + tenant: ansible_tenant + service_graph: ansible_graph + connection_name: C1 + state: query + register: query_l4l7_abs_conn + +# VERIFY SERVICE GRAPH ABS CONNECTION +- name: Verify Connection Attributes + ansible.builtin.assert: + that: + - query_l4l7_abs_conn is not changed + - query_l4l7_abs_conn.current.0.vnsAbsConnection.attributes.dn == "uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsConnection-C1" + - query_l4l7_abs_conn.current.0.vnsAbsConnection.attributes.name == "C1" + - query_l4l7_abs_conn.current.0.vnsAbsConnection.attributes.adjType == "L2" + - query_l4l7_abs_conn.current.0.vnsAbsConnection.attributes.connDir == "provider" + - query_l4l7_abs_conn.current.0.vnsAbsConnection.attributes.connType == "external" + - query_l4l7_abs_conn.current.0.vnsAbsConnection.attributes.directConnect == "no" + - query_l4l7_abs_conn.current.0.vnsAbsConnection.attributes.unicastRoute == "no" + +# QUERY ALL SERVICE GRAPH ABS CONNECTIONS +- name: Query all L4-L7 Service Graph Abs Connections + cisco.aci.aci_l4l7_service_graph_template_abs_conn: + <<: *aci_info + state: query + register: query_l4l7_abs_conn_all + +# VERIFY SERVICE GRAPH ABS CONNECTION +- name: Verify Connection Attributes + ansible.builtin.assert: + that: + - query_l4l7_abs_conn_all is not changed + +# DELETE SERVICE GRAPH ABS CONNECTION +- name: Delete L4-L7 Service Graph Abs Connection + cisco.aci.aci_l4l7_service_graph_template_abs_conn: + <<: *aci_info + tenant: ansible_tenant + service_graph: ansible_graph + connection_name: C1 + state: absent + register: delete_l4l7_abs_conn + +# VERIFY SERVICE GRAPH ABS CONNECTION DELETION +- name: Verify Connection Deletion + ansible.builtin.assert: + that: + - delete_l4l7_abs_conn is changed + - delete_l4l7_abs_conn.current == [] + - delete_l4l7_abs_conn.previous.0.vnsAbsConnection.attributes.dn == "uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsConnection-C1" + - delete_l4l7_abs_conn.previous.0.vnsAbsConnection.attributes.name == "C1" + - delete_l4l7_abs_conn.previous.0.vnsAbsConnection.attributes.adjType == "L2" + - delete_l4l7_abs_conn.previous.0.vnsAbsConnection.attributes.connDir == "provider" + - delete_l4l7_abs_conn.previous.0.vnsAbsConnection.attributes.connType == "external" + - delete_l4l7_abs_conn.previous.0.vnsAbsConnection.attributes.directConnect == "no" + - delete_l4l7_abs_conn.previous.0.vnsAbsConnection.attributes.unicastRoute == "no" + +# DELETE ABS CONN AGAIN TO TEST IDEMPOTENCE +- name: Delete L4-L7 Service Graph Abs Connection again + cisco.aci.aci_l4l7_service_graph_template_abs_conn: + <<: *aci_info + tenant: ansible_tenant + service_graph: ansible_graph + connection_name: C1 + state: absent + register: delete_l4l7_abs_conn_again + +- name: Verify Connection Deletion idempotence + ansible.builtin.assert: + that: + - delete_l4l7_abs_conn_again is not changed + - delete_l4l7_abs_conn_again.current == [] + +# CLEAN UP +- name: Remove ansible_tenant + cisco.aci.aci_tenant: + <<: *aci_info + name: ansible_tenant + state: absent diff --git a/tests/integration/targets/aci_l4l7_service_graph_template_abs_connection_conns/aliases b/tests/integration/targets/aci_l4l7_service_graph_template_abs_connection_conns/aliases new file mode 100644 index 000000000..209b793f9 --- /dev/null +++ b/tests/integration/targets/aci_l4l7_service_graph_template_abs_connection_conns/aliases @@ -0,0 +1,2 @@ +# No ACI simulator yet, so not enabled +# unsupported diff --git a/tests/integration/targets/aci_l4l7_service_graph_template_abs_connection_conns/tasks/main.yml b/tests/integration/targets/aci_l4l7_service_graph_template_abs_connection_conns/tasks/main.yml new file mode 100644 index 000000000..8b27fe4aa --- /dev/null +++ b/tests/integration/targets/aci_l4l7_service_graph_template_abs_connection_conns/tasks/main.yml @@ -0,0 +1,695 @@ +# Test code for the ACI modules +# Copyright: (c) 2021, 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 + +# 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: '{{ aci_output_level | default("info") }}' + +# CLEAN ENVIRONMENT +- name: Remove ansible_tenant if it already exists + cisco.aci.aci_tenant: + <<: *aci_info + name: ansible_tenant + state: absent + +- name: Remove ansible_phys_domain if it already exists + cisco.aci.aci_domain: + <<: *aci_info + domain: ansible_phys_dom + domain_type: phys + state: absent + +# CREATE TENANT +- name: Create ansible_tenant + cisco.aci.aci_tenant: + <<: *aci_info + name: ansible_tenant + state: present + +# CREATE DOMAIN +- name: Create ansible_phys_domain + cisco.aci.aci_domain: + <<: *aci_info + domain: ansible_phys_dom + domain_type: phys + state: present + +# ADD SERVICE GRAPH TEMPLATE +- name: Create L4-L7 Service Graph Template + cisco.aci.aci_l4l7_service_graph_template: + <<: *aci_info + tenant: ansible_tenant + service_graph: ansible_graph + state: present + +# ADD L4-L7 LOGICAL DEVICES +- name: Create PBR Device 1 + cisco.aci.aci_l4l7_device: + <<: *aci_info + tenant: ansible_tenant + device: ansible_device1 + domain: ansible_phys_dom + func_type: go_to + context_aware: single + managed: false + dev_type: physical + svc_type: adc + trunking: false + prom_mode: false + state: present + +- name: Create PBR Device 2 + cisco.aci.aci_l4l7_device: + <<: *aci_info + tenant: ansible_tenant + device: ansible_device2 + domain: ansible_phys_dom + func_type: go_to + context_aware: single + managed: false + dev_type: physical + svc_type: adc + trunking: false + prom_mode: false + state: present + +# ADD SERVICE GRAPH NODES +- name: Add Service Graph Template Node 1 + cisco.aci.aci_l4l7_service_graph_template_node: + <<: *aci_info + tenant: ansible_tenant + service_graph: ansible_graph + node: ansible_node1 + func_template_type: adc_one_arm + func_type: go_to + device: ansible_device1 + managed: false + routing_mode: Redirect + state: present + +- name: Add Service Graph Template Node 2 + cisco.aci.aci_l4l7_service_graph_template_node: + <<: *aci_info + tenant: ansible_tenant + service_graph: ansible_graph + node: ansible_node2 + func_template_type: adc_one_arm + func_type: go_to + device: ansible_device2 + managed: false + routing_mode: Redirect + state: present + +# ADD SERVICE GRAPH ABS CONNECTIONS +- name: Create L4-L7 Service Graph Abs Connection C1 + cisco.aci.aci_l4l7_service_graph_template_abs_conn: + <<: *aci_info + tenant: ansible_tenant + service_graph: ansible_graph + connection_name: C1 + direct_connect: true + unicast_route: true + adjacency_type: l3 + state: present + +- name: Create L4-L7 Service Graph Abs Connection C2 + cisco.aci.aci_l4l7_service_graph_template_abs_conn: + <<: *aci_info + tenant: ansible_tenant + service_graph: ansible_graph + connection_name: C2 + direct_connect: true + unicast_route: true + adjacency_type: l3 + state: present + +- name: Create L4-L7 Service Graph Abs Connection C3 + cisco.aci.aci_l4l7_service_graph_template_abs_conn: + <<: *aci_info + tenant: ansible_tenant + service_graph: ansible_graph + connection_name: C3 + direct_connect: true + unicast_route: true + adjacency_type: l3 + state: present + +# CREATE CONNECTION CONNS +- name: Add C1 node connection + cisco.aci.aci_l4l7_service_graph_template_abs_connection_conns: + <<: *aci_info + tenant: ansible_tenant + service_graph: ansible_graph + direction: consumer + connection_name: C1 + connected_node: ansible_node1 + state: present + register: add_c1_node_conn + +- name: Add C1 term connection + cisco.aci.aci_l4l7_service_graph_template_abs_connection_conns: + <<: *aci_info + tenant: ansible_tenant + service_graph: ansible_graph + direction: consumer + connection_name: C1 + state: present + register: add_c1_term_conn + +- name: Add C2 provider connection + cisco.aci.aci_l4l7_service_graph_template_abs_connection_conns: + <<: *aci_info + tenant: ansible_tenant + service_graph: ansible_graph + direction: provider + connection_name: C2 + connected_node: ansible_node1 + state: present + register: add_c2_prov_conn + +- name: Add C2 consumer connection + cisco.aci.aci_l4l7_service_graph_template_abs_connection_conns: + <<: *aci_info + tenant: ansible_tenant + service_graph: ansible_graph + direction: consumer + connection_name: C2 + connected_node: ansible_node2 + state: present + register: add_c2_cons_conn + +- name: Add C3 node connection + cisco.aci.aci_l4l7_service_graph_template_abs_connection_conns: + <<: *aci_info + tenant: ansible_tenant + service_graph: ansible_graph + direction: provider + connection_name: C3 + connected_node: ansible_node2 + state: present + register: add_c3_node_conn + +- name: Add C3 term connection + cisco.aci.aci_l4l7_service_graph_template_abs_connection_conns: + <<: *aci_info + tenant: ansible_tenant + service_graph: ansible_graph + direction: provider + connection_name: C3 + state: present + register: add_c3_term_conn + +# VERIFY CONNECTION ATTRIBUTES +- name: Verify C1 Node Connection + ansible.builtin.assert: + that: + - add_c1_node_conn.current.0.vnsRsAbsConnectionConns.attributes.dn == "uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsConnection-C1/rsabsConnectionConns-[uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsNode-ansible_node1/AbsFConn-consumer]" + - add_c1_node_conn.current.0.vnsRsAbsConnectionConns.attributes.tDn == "uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsNode-ansible_node1/AbsFConn-consumer" + +- name: Verify C1 Term Node Connection + ansible.builtin.assert: + that: + - add_c1_term_conn.current.0.vnsRsAbsConnectionConns.attributes.dn == "uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsConnection-C1/rsabsConnectionConns-[uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsTermNodeCon-T1/AbsTConn]" + - add_c1_term_conn.current.0.vnsRsAbsConnectionConns.attributes.tDn == "uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsTermNodeCon-T1/AbsTConn" + +- name: Verify C2 Provider Connection + ansible.builtin.assert: + that: + - add_c2_prov_conn.current.0.vnsRsAbsConnectionConns.attributes.dn == "uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsConnection-C2/rsabsConnectionConns-[uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsNode-ansible_node1/AbsFConn-provider]" + - add_c2_prov_conn.current.0.vnsRsAbsConnectionConns.attributes.tDn == "uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsNode-ansible_node1/AbsFConn-provider" + +- name: Verify C2 Consumer Connection + ansible.builtin.assert: + that: + - add_c2_cons_conn.current.0.vnsRsAbsConnectionConns.attributes.dn == "uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsConnection-C2/rsabsConnectionConns-[uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsNode-ansible_node2/AbsFConn-consumer]" + - add_c2_cons_conn.current.0.vnsRsAbsConnectionConns.attributes.tDn == "uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsNode-ansible_node2/AbsFConn-consumer" + +- name: Verify C3 Node Connection + ansible.builtin.assert: + that: + - add_c3_node_conn.current.0.vnsRsAbsConnectionConns.attributes.dn == "uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsConnection-C3/rsabsConnectionConns-[uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsNode-ansible_node2/AbsFConn-provider]" + - add_c3_node_conn.current.0.vnsRsAbsConnectionConns.attributes.tDn == "uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsNode-ansible_node2/AbsFConn-provider" + +- name: Verify C3 Term Node Connection + ansible.builtin.assert: + that: + - add_c3_term_conn.current.0.vnsRsAbsConnectionConns.attributes.dn == "uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsConnection-C3/rsabsConnectionConns-[uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsTermNodeProv-T2/AbsTConn]" + - add_c3_term_conn.current.0.vnsRsAbsConnectionConns.attributes.tDn == "uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsTermNodeProv-T2/AbsTConn" + +# ADD CONNECTIONS AGAIN TO TEST IDEMPOTENCE +- name: Add C1 node connection again + cisco.aci.aci_l4l7_service_graph_template_abs_connection_conns: + <<: *aci_info + tenant: ansible_tenant + service_graph: ansible_graph + direction: consumer + connection_name: C1 + connected_node: ansible_node1 + state: present + register: add_c1_node_conn_again + +- name: Add C1 term connection again + cisco.aci.aci_l4l7_service_graph_template_abs_connection_conns: + <<: *aci_info + tenant: ansible_tenant + service_graph: ansible_graph + direction: consumer + connection_name: C1 + state: present + register: add_c1_term_conn_again + +- name: Add C2 provider connection again + cisco.aci.aci_l4l7_service_graph_template_abs_connection_conns: + <<: *aci_info + tenant: ansible_tenant + service_graph: ansible_graph + direction: provider + connection_name: C2 + connected_node: ansible_node1 + state: present + register: add_c2_prov_conn_again + +- name: Add C2 consumer connection again + cisco.aci.aci_l4l7_service_graph_template_abs_connection_conns: + <<: *aci_info + tenant: ansible_tenant + service_graph: ansible_graph + direction: consumer + connection_name: C2 + connected_node: ansible_node2 + state: present + register: add_c2_cons_conn_again + +- name: Add C3 node connection again + cisco.aci.aci_l4l7_service_graph_template_abs_connection_conns: + <<: *aci_info + tenant: ansible_tenant + service_graph: ansible_graph + direction: provider + connection_name: C3 + connected_node: ansible_node2 + state: present + register: add_c3_node_conn_again + +- name: Add C3 term connection again + cisco.aci.aci_l4l7_service_graph_template_abs_connection_conns: + <<: *aci_info + tenant: ansible_tenant + service_graph: ansible_graph + direction: provider + connection_name: C3 + state: present + register: add_c3_term_conn_again + +# VERIFY CONNECTION ATTRIBUTES +- name: Verify C1 Node Connection is unchanged + ansible.builtin.assert: + that: + - add_c1_node_conn_again is not changed + - add_c1_node_conn_again.current.0.vnsRsAbsConnectionConns.attributes.dn == "uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsConnection-C1/rsabsConnectionConns-[uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsNode-ansible_node1/AbsFConn-consumer]" + - add_c1_node_conn_again.current.0.vnsRsAbsConnectionConns.attributes.tDn == "uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsNode-ansible_node1/AbsFConn-consumer" + +- name: Verify C1 Term Node Connection is unchanged + ansible.builtin.assert: + that: + - add_c1_term_conn_again is not changed + - add_c1_term_conn_again.current.0.vnsRsAbsConnectionConns.attributes.dn == "uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsConnection-C1/rsabsConnectionConns-[uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsTermNodeCon-T1/AbsTConn]" + - add_c1_term_conn_again.current.0.vnsRsAbsConnectionConns.attributes.tDn == "uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsTermNodeCon-T1/AbsTConn" + +- name: Verify C2 Provider Connection is unchanged + ansible.builtin.assert: + that: + - add_c2_prov_conn_again is not changed + - add_c2_prov_conn_again.current.0.vnsRsAbsConnectionConns.attributes.dn == "uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsConnection-C2/rsabsConnectionConns-[uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsNode-ansible_node1/AbsFConn-provider]" + - add_c2_prov_conn_again.current.0.vnsRsAbsConnectionConns.attributes.tDn == "uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsNode-ansible_node1/AbsFConn-provider" + +- name: Verify C2 Consumer Connection is unchanged + ansible.builtin.assert: + that: + - add_c2_cons_conn_again is not changed + - add_c2_cons_conn_again.current.0.vnsRsAbsConnectionConns.attributes.dn == "uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsConnection-C2/rsabsConnectionConns-[uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsNode-ansible_node2/AbsFConn-consumer]" + - add_c2_cons_conn_again.current.0.vnsRsAbsConnectionConns.attributes.tDn == "uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsNode-ansible_node2/AbsFConn-consumer" + +- name: Verify C3 Node Connection is unchanged + ansible.builtin.assert: + that: + - add_c3_node_conn_again is not changed + - add_c3_node_conn_again.current.0.vnsRsAbsConnectionConns.attributes.dn == "uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsConnection-C3/rsabsConnectionConns-[uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsNode-ansible_node2/AbsFConn-provider]" + - add_c3_node_conn_again.current.0.vnsRsAbsConnectionConns.attributes.tDn == "uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsNode-ansible_node2/AbsFConn-provider" + +- name: Verify C3 Term Node Connection is unchanged + ansible.builtin.assert: + that: + - add_c3_term_conn_again is not changed + - add_c3_term_conn_again.current.0.vnsRsAbsConnectionConns.attributes.dn == "uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsConnection-C3/rsabsConnectionConns-[uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsTermNodeProv-T2/AbsTConn]" + - add_c3_term_conn_again.current.0.vnsRsAbsConnectionConns.attributes.tDn == "uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsTermNodeProv-T2/AbsTConn" + +# QUERY CONNECTION ATTRIBUTES +- name: Query C1 node connection + cisco.aci.aci_l4l7_service_graph_template_abs_connection_conns: + <<: *aci_info + tenant: ansible_tenant + service_graph: ansible_graph + direction: consumer + connection_name: C1 + connected_node: ansible_node1 + state: query + register: query_c1_node_conn + +- name: Query C1 term connection + cisco.aci.aci_l4l7_service_graph_template_abs_connection_conns: + <<: *aci_info + tenant: ansible_tenant + service_graph: ansible_graph + direction: consumer + connection_name: C1 + state: query + register: query_c1_term_conn + +- name: Query C2 provider connection + cisco.aci.aci_l4l7_service_graph_template_abs_connection_conns: + <<: *aci_info + tenant: ansible_tenant + service_graph: ansible_graph + direction: provider + connection_name: C2 + connected_node: ansible_node1 + state: query + register: query_c2_prov_conn + +- name: Query C2 consumer connection + cisco.aci.aci_l4l7_service_graph_template_abs_connection_conns: + <<: *aci_info + tenant: ansible_tenant + service_graph: ansible_graph + direction: consumer + connection_name: C2 + connected_node: ansible_node2 + state: query + register: query_c2_cons_conn + +- name: Query C3 node connection + cisco.aci.aci_l4l7_service_graph_template_abs_connection_conns: + <<: *aci_info + tenant: ansible_tenant + service_graph: ansible_graph + direction: provider + connection_name: C3 + connected_node: ansible_node2 + state: query + register: query_c3_node_conn + +- name: Query C3 term connection + cisco.aci.aci_l4l7_service_graph_template_abs_connection_conns: + <<: *aci_info + tenant: ansible_tenant + service_graph: ansible_graph + direction: provider + connection_name: C3 + state: query + register: query_c3_term_conn + +- name: Verify C1 Node Connection + ansible.builtin.assert: + that: + - query_c1_node_conn is not changed + - query_c1_node_conn.current.0.vnsRsAbsConnectionConns.attributes.dn == "uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsConnection-C1/rsabsConnectionConns-[uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsNode-ansible_node1/AbsFConn-consumer]" + - query_c1_node_conn.current.0.vnsRsAbsConnectionConns.attributes.tDn == "uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsNode-ansible_node1/AbsFConn-consumer" + +- name: Verify C1 Term Node Connection + ansible.builtin.assert: + that: + - query_c1_term_conn is not changed + - query_c1_term_conn.current.0.vnsRsAbsConnectionConns.attributes.dn == "uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsConnection-C1/rsabsConnectionConns-[uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsTermNodeCon-T1/AbsTConn]" + - query_c1_term_conn.current.0.vnsRsAbsConnectionConns.attributes.tDn == "uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsTermNodeCon-T1/AbsTConn" + +- name: Verify C2 Provider Connection + ansible.builtin.assert: + that: + - query_c2_prov_conn is not changed + - query_c2_prov_conn.current.0.vnsRsAbsConnectionConns.attributes.dn == "uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsConnection-C2/rsabsConnectionConns-[uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsNode-ansible_node1/AbsFConn-provider]" + - query_c2_prov_conn.current.0.vnsRsAbsConnectionConns.attributes.tDn == "uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsNode-ansible_node1/AbsFConn-provider" + +- name: Verify C2 Consumer Connection + ansible.builtin.assert: + that: + - query_c2_cons_conn is not changed + - query_c2_cons_conn.current.0.vnsRsAbsConnectionConns.attributes.dn == "uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsConnection-C2/rsabsConnectionConns-[uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsNode-ansible_node2/AbsFConn-consumer]" + - query_c2_cons_conn.current.0.vnsRsAbsConnectionConns.attributes.tDn == "uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsNode-ansible_node2/AbsFConn-consumer" + +- name: Verify C3 Node Connection + ansible.builtin.assert: + that: + - query_c3_node_conn is not changed + - query_c3_node_conn.current.0.vnsRsAbsConnectionConns.attributes.dn == "uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsConnection-C3/rsabsConnectionConns-[uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsNode-ansible_node2/AbsFConn-provider]" + - query_c3_node_conn.current.0.vnsRsAbsConnectionConns.attributes.tDn == "uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsNode-ansible_node2/AbsFConn-provider" + +- name: Verify C3 Term Node Connection + ansible.builtin.assert: + that: + - query_c3_term_conn is not changed + - query_c3_term_conn.current.0.vnsRsAbsConnectionConns.attributes.dn == "uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsConnection-C3/rsabsConnectionConns-[uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsTermNodeProv-T2/AbsTConn]" + - query_c3_term_conn.current.0.vnsRsAbsConnectionConns.attributes.tDn == "uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsTermNodeProv-T2/AbsTConn" + +# DELETE CONNECTIONS +- name: Delete C1 node connection + cisco.aci.aci_l4l7_service_graph_template_abs_connection_conns: + <<: *aci_info + tenant: ansible_tenant + service_graph: ansible_graph + direction: consumer + connection_name: C1 + connected_node: ansible_node1 + state: absent + register: delete_c1_node_conn + +- name: Delete C1 term connection + cisco.aci.aci_l4l7_service_graph_template_abs_connection_conns: + <<: *aci_info + tenant: ansible_tenant + service_graph: ansible_graph + direction: consumer + connection_name: C1 + state: absent + register: delete_c1_term_conn + +- name: Delete C2 provider connection + cisco.aci.aci_l4l7_service_graph_template_abs_connection_conns: + <<: *aci_info + tenant: ansible_tenant + service_graph: ansible_graph + direction: provider + connection_name: C2 + connected_node: ansible_node1 + state: absent + register: delete_c2_prov_conn + +- name: Delete C2 consumer connection + cisco.aci.aci_l4l7_service_graph_template_abs_connection_conns: + <<: *aci_info + tenant: ansible_tenant + service_graph: ansible_graph + direction: consumer + connection_name: C2 + connected_node: ansible_node2 + state: absent + register: delete_c2_cons_conn + +- name: Delete C3 node connection + cisco.aci.aci_l4l7_service_graph_template_abs_connection_conns: + <<: *aci_info + tenant: ansible_tenant + service_graph: ansible_graph + direction: provider + connection_name: C3 + connected_node: ansible_node2 + state: absent + register: delete_c3_node_conn + +- name: Delete C3 term connection + cisco.aci.aci_l4l7_service_graph_template_abs_connection_conns: + <<: *aci_info + tenant: ansible_tenant + service_graph: ansible_graph + direction: provider + connection_name: C3 + state: absent + register: delete_c3_term_conn + +- name: Verify C1 Node Connection Removal + ansible.builtin.assert: + that: + - delete_c1_node_conn is changed + - delete_c1_node_conn.current == [] + - delete_c1_node_conn.previous.0.vnsRsAbsConnectionConns.attributes.dn == "uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsConnection-C1/rsabsConnectionConns-[uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsNode-ansible_node1/AbsFConn-consumer]" + - delete_c1_node_conn.previous.0.vnsRsAbsConnectionConns.attributes.tDn == "uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsNode-ansible_node1/AbsFConn-consumer" + +- name: Verify C1 Term Node Connection Removal + ansible.builtin.assert: + that: + - delete_c1_term_conn is changed + - delete_c1_term_conn.current == [] + - delete_c1_term_conn.previous.0.vnsRsAbsConnectionConns.attributes.dn == "uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsConnection-C1/rsabsConnectionConns-[uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsTermNodeCon-T1/AbsTConn]" + - delete_c1_term_conn.previous.0.vnsRsAbsConnectionConns.attributes.tDn == "uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsTermNodeCon-T1/AbsTConn" + +- name: Verify C2 Provider Connection Removal + ansible.builtin.assert: + that: + - delete_c2_prov_conn is changed + - delete_c2_prov_conn.current == [] + - delete_c2_prov_conn.previous.0.vnsRsAbsConnectionConns.attributes.dn == "uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsConnection-C2/rsabsConnectionConns-[uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsNode-ansible_node1/AbsFConn-provider]" + - delete_c2_prov_conn.previous.0.vnsRsAbsConnectionConns.attributes.tDn == "uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsNode-ansible_node1/AbsFConn-provider" + +- name: Verify C2 Consumer Connection Removal + ansible.builtin.assert: + that: + - delete_c2_cons_conn is changed + - delete_c2_cons_conn.current == [] + - delete_c2_cons_conn.previous.0.vnsRsAbsConnectionConns.attributes.dn == "uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsConnection-C2/rsabsConnectionConns-[uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsNode-ansible_node2/AbsFConn-consumer]" + - delete_c2_cons_conn.previous.0.vnsRsAbsConnectionConns.attributes.tDn == "uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsNode-ansible_node2/AbsFConn-consumer" + +- name: Verify C3 Node Connection Removal + ansible.builtin.assert: + that: + - delete_c3_node_conn is changed + - delete_c3_node_conn.current == [] + - delete_c3_node_conn.previous.0.vnsRsAbsConnectionConns.attributes.dn == "uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsConnection-C3/rsabsConnectionConns-[uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsNode-ansible_node2/AbsFConn-provider]" + - delete_c3_node_conn.previous.0.vnsRsAbsConnectionConns.attributes.tDn == "uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsNode-ansible_node2/AbsFConn-provider" + +- name: Verify C3 Term Node Connection Removal + ansible.builtin.assert: + that: + - delete_c3_term_conn is changed + - delete_c3_term_conn.current == [] + - delete_c3_term_conn.previous.0.vnsRsAbsConnectionConns.attributes.dn == "uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsConnection-C3/rsabsConnectionConns-[uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsTermNodeProv-T2/AbsTConn]" + - delete_c3_term_conn.previous.0.vnsRsAbsConnectionConns.attributes.tDn == "uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsTermNodeProv-T2/AbsTConn" + +# REMOVE CONNECTIONS AGAIN TO TEST IDEMPOTENCE +- name: Delete C1 node connection again + cisco.aci.aci_l4l7_service_graph_template_abs_connection_conns: + <<: *aci_info + tenant: ansible_tenant + service_graph: ansible_graph + direction: consumer + connection_name: C1 + connected_node: ansible_node1 + state: absent + register: delete_c1_node_conn_again + +- name: Delete C1 term connection again + cisco.aci.aci_l4l7_service_graph_template_abs_connection_conns: + <<: *aci_info + tenant: ansible_tenant + service_graph: ansible_graph + direction: consumer + connection_name: C1 + state: absent + register: delete_c1_term_conn_again + +- name: Delete C2 provider connection again + cisco.aci.aci_l4l7_service_graph_template_abs_connection_conns: + <<: *aci_info + tenant: ansible_tenant + service_graph: ansible_graph + direction: provider + connection_name: C2 + connected_node: ansible_node1 + state: absent + register: delete_c2_prov_conn_again + +- name: Delete C2 consumer connection again + cisco.aci.aci_l4l7_service_graph_template_abs_connection_conns: + <<: *aci_info + tenant: ansible_tenant + service_graph: ansible_graph + direction: consumer + connection_name: C2 + connected_node: ansible_node2 + state: absent + register: delete_c2_cons_conn_again + +- name: Delete C3 node connection again + cisco.aci.aci_l4l7_service_graph_template_abs_connection_conns: + <<: *aci_info + tenant: ansible_tenant + service_graph: ansible_graph + direction: provider + connection_name: C3 + connected_node: ansible_node2 + state: absent + register: delete_c3_node_conn_again + +- name: Delete C3 term connection again + cisco.aci.aci_l4l7_service_graph_template_abs_connection_conns: + <<: *aci_info + tenant: ansible_tenant + service_graph: ansible_graph + direction: provider + connection_name: C3 + state: absent + register: delete_c3_term_conn_again + +- name: Verify C1 Node Connection Removal idempotence + ansible.builtin.assert: + that: + - delete_c1_node_conn_again is not changed + - delete_c1_node_conn_again.current == [] + +- name: Verify C1 Term Node Connection Removal idempotence + ansible.builtin.assert: + that: + - delete_c1_term_conn_again is not changed + - delete_c1_term_conn_again.current == [] + +- name: Verify C2 Provider Connection Removal idempotence + ansible.builtin.assert: + that: + - delete_c2_prov_conn_again is not changed + - delete_c2_prov_conn_again.current == [] + +- name: Verify C2 Consumer Connection Removal idempotence + ansible.builtin.assert: + that: + - delete_c2_cons_conn_again is not changed + - delete_c2_cons_conn_again.current == [] + +- name: Verify C3 Node Connection Removal idempotence + ansible.builtin.assert: + that: + - delete_c3_node_conn_again is not changed + - delete_c3_node_conn_again.current == [] + +- name: Verify C3 Term Node Connection Removal idempotence + ansible.builtin.assert: + that: + - delete_c3_term_conn_again is not changed + - delete_c3_term_conn_again.current == [] + +# CLEAN UP +- name: Remove ansible_tenant + cisco.aci.aci_tenant: + <<: *aci_info + name: ansible_tenant + state: absent + +- name: Remove ansible_phys_domain + cisco.aci.aci_domain: + <<: *aci_info + domain: ansible_phys_dom + domain_type: phys + state: absent diff --git a/tests/integration/targets/aci_l4l7_service_graph_template_func_conn/aliases b/tests/integration/targets/aci_l4l7_service_graph_template_func_conn/aliases new file mode 100644 index 000000000..209b793f9 --- /dev/null +++ b/tests/integration/targets/aci_l4l7_service_graph_template_func_conn/aliases @@ -0,0 +1,2 @@ +# No ACI simulator yet, so not enabled +# unsupported diff --git a/tests/integration/targets/aci_l4l7_service_graph_template_func_conn/tasks/main.yml b/tests/integration/targets/aci_l4l7_service_graph_template_func_conn/tasks/main.yml new file mode 100644 index 000000000..507e7bd1a --- /dev/null +++ b/tests/integration/targets/aci_l4l7_service_graph_template_func_conn/tasks/main.yml @@ -0,0 +1,262 @@ +# Test code for the ACI modules +# Copyright: (c) 2021, 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 + +# 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: '{{ aci_output_level | default("info") }}' + +# CLEAN ENVIRONMENT +- name: Remove ansible_tenant if it already exists + cisco.aci.aci_tenant: + <<: *aci_info + name: ansible_tenant + state: absent + +# CREATE TENANT +- name: Create ansible_tenant + cisco.aci.aci_tenant: + <<: *aci_info + name: ansible_tenant + state: present + +# CREATE DOMAIN +- name: Create ansible_phys_domain + cisco.aci.aci_domain: + <<: *aci_info + domain: ansible_phys_dom + domain_type: phys + state: present + +# ADD DEVICE +- name: Create L4-L7 Device + cisco.aci.aci_l4l7_device: + <<: *aci_info + tenant: ansible_tenant + device: ansible_device + domain: ansible_phys_dom + func_type: go_to + context_aware: single + managed: false + dev_type: physical + svc_type: adc + trunking: false + prom_mode: true + state: present + +# ADD SERVICE GRAPH TEMPLATE +- name: Create L4-L7 Service Graph Template + cisco.aci.aci_l4l7_service_graph_template: + <<: *aci_info + tenant: ansible_tenant + service_graph: ansible_graph + state: present + +# ADD SERVICE GRAPH NODE +- name: Create L4-L7 Service Graph Node + cisco.aci.aci_l4l7_service_graph_template_node: + <<: *aci_info + tenant: ansible_tenant + service_graph: ansible_graph + node: ansible_node + func_template_type: adc_one_arm + func_type: go_to + device: ansible_device + managed: false + routing_mode: Redirect + state: present + +# ADD FUNCTIONAL CONNECTIONS +- name: Create Consumer Func Conn + cisco.aci.aci_l4l7_service_graph_template_func_conn: + <<: *aci_info + tenant: ansible_tenant + service_graph: ansible_graph + node: ansible_node + connection_name: consumer + state: present + register: add_cons_func_conn + +- name: Create Provider Func Conn + cisco.aci.aci_l4l7_service_graph_template_func_conn: + <<: *aci_info + tenant: ansible_tenant + service_graph: ansible_graph + node: ansible_node + connection_name: provider + state: present + register: add_prov_func_conn + +# VERIFY ATTRIBUTES +- name: Verify Consumer Func Conn Creation + ansible.builtin.assert: + that: + - add_cons_func_conn.current.0.vnsAbsFuncConn.attributes.dn == "uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsNode-ansible_node/AbsFConn-consumer" + - add_cons_func_conn.current.0.vnsAbsFuncConn.attributes.name == "consumer" + +- name: Verify Provider Func Conn Creation + ansible.builtin.assert: + that: + - add_prov_func_conn.current.0.vnsAbsFuncConn.attributes.dn == "uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsNode-ansible_node/AbsFConn-provider" + - add_prov_func_conn.current.0.vnsAbsFuncConn.attributes.name == "provider" + +# ADD FUNC CONNS AGAIN TO TEST IDEMPOTENCE +- name: Add Consumer Func Conn again + cisco.aci.aci_l4l7_service_graph_template_func_conn: + <<: *aci_info + tenant: ansible_tenant + service_graph: ansible_graph + node: ansible_node + connection_name: consumer + state: present + register: add_cons_func_conn_again + +- name: Add Provider Func Conn again + cisco.aci.aci_l4l7_service_graph_template_func_conn: + <<: *aci_info + tenant: ansible_tenant + service_graph: ansible_graph + node: ansible_node + connection_name: provider + state: present + register: add_prov_func_conn_again + +- name: Verify Consumer Func Conn is not changed + ansible.builtin.assert: + that: + - add_cons_func_conn_again is not changed + - add_cons_func_conn_again.current.0.vnsAbsFuncConn.attributes.dn == "uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsNode-ansible_node/AbsFConn-consumer" + - add_cons_func_conn_again.current.0.vnsAbsFuncConn.attributes.name == "consumer" + +- name: Verify Provider Func Conn is not changed + ansible.builtin.assert: + that: + - add_prov_func_conn_again is not changed + - add_prov_func_conn_again.current.0.vnsAbsFuncConn.attributes.dn == "uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsNode-ansible_node/AbsFConn-provider" + - add_prov_func_conn_again.current.0.vnsAbsFuncConn.attributes.name == "provider" + +# QUERY FUNC CONNS +- name: Query Consumer Func Conn + cisco.aci.aci_l4l7_service_graph_template_func_conn: + <<: *aci_info + tenant: ansible_tenant + service_graph: ansible_graph + node: ansible_node + connection_name: consumer + state: query + register: query_cons_func_conn + +- name: Query Provider Func Conn + cisco.aci.aci_l4l7_service_graph_template_func_conn: + <<: *aci_info + tenant: ansible_tenant + service_graph: ansible_graph + node: ansible_node + connection_name: provider + state: query + register: query_prov_func_conn + +# VERIFY ATTRIBUTES +- name: Verify Consumer Func Conn Attributes + ansible.builtin.assert: + that: + - query_cons_func_conn is not changed + - query_cons_func_conn.current.0.vnsAbsFuncConn.attributes.dn == "uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsNode-ansible_node/AbsFConn-consumer" + - query_cons_func_conn.current.0.vnsAbsFuncConn.attributes.name == "consumer" + +- name: Verify Provider Func Conn Attributes + ansible.builtin.assert: + that: + - query_prov_func_conn is not changed + - query_prov_func_conn.current.0.vnsAbsFuncConn.attributes.dn == "uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsNode-ansible_node/AbsFConn-provider" + - query_prov_func_conn.current.0.vnsAbsFuncConn.attributes.name == "provider" + +# QUERY ALL FUNC CONNS +- name: Query All Func Conns + cisco.aci.aci_l4l7_service_graph_template_func_conn: + <<: *aci_info + state: query + register: query_func_conn_all + +# DELETE FUNC CONNS +- name: Delete Consumer Func Conn + cisco.aci.aci_l4l7_service_graph_template_func_conn: + <<: *aci_info + tenant: ansible_tenant + service_graph: ansible_graph + node: ansible_node + connection_name: consumer + state: absent + register: delete_cons_func_conn + +- name: Delete Provider Func Conn + cisco.aci.aci_l4l7_service_graph_template_func_conn: + <<: *aci_info + tenant: ansible_tenant + service_graph: ansible_graph + node: ansible_node + connection_name: provider + state: absent + register: delete_prov_func_conn + +# VERIFY ATTRIBUTES +- name: Verify Consumer Func Conn Removal + ansible.builtin.assert: + that: + - delete_cons_func_conn is changed + - delete_cons_func_conn.current == [] + - delete_cons_func_conn.previous.0.vnsAbsFuncConn.attributes.dn == "uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsNode-ansible_node/AbsFConn-consumer" + - delete_cons_func_conn.previous.0.vnsAbsFuncConn.attributes.name == "consumer" + +- name: Verify Provider Func Conn Removal + ansible.builtin.assert: + that: + - delete_prov_func_conn is changed + - delete_prov_func_conn.current == [] + - delete_prov_func_conn.previous.0.vnsAbsFuncConn.attributes.dn == "uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsNode-ansible_node/AbsFConn-provider" + - delete_prov_func_conn.previous.0.vnsAbsFuncConn.attributes.name == "provider" + +# DELETE FUNC CONN AGAIN TO TEST IDEMPOTENCE +- name: Delete Consumer Func Conn again + cisco.aci.aci_l4l7_service_graph_template_func_conn: + <<: *aci_info + tenant: ansible_tenant + service_graph: ansible_graph + node: ansible_node + connection_name: consumer + state: absent + register: delete_cons_func_conn_again + +- name: Verify Consumer Func Conn Removal idempotence + ansible.builtin.assert: + that: + - delete_cons_func_conn_again is not changed + - delete_cons_func_conn_again.current == [] + +# CLEAN UP +- name: Remove ansible_tenant + cisco.aci.aci_tenant: + <<: *aci_info + name: ansible_tenant + state: absent + +- name: Remove ansible_phys_dom + cisco.aci.aci_domain: + <<: *aci_info + domain: ansible_phys_dom + domain_type: phys + state: absent diff --git a/tests/integration/targets/aci_l4l7_service_graph_template_node/aliases b/tests/integration/targets/aci_l4l7_service_graph_template_node/aliases new file mode 100644 index 000000000..209b793f9 --- /dev/null +++ b/tests/integration/targets/aci_l4l7_service_graph_template_node/aliases @@ -0,0 +1,2 @@ +# No ACI simulator yet, so not enabled +# unsupported diff --git a/tests/integration/targets/aci_l4l7_service_graph_template_node/tasks/main.yml b/tests/integration/targets/aci_l4l7_service_graph_template_node/tasks/main.yml new file mode 100644 index 000000000..56cd79132 --- /dev/null +++ b/tests/integration/targets/aci_l4l7_service_graph_template_node/tasks/main.yml @@ -0,0 +1,250 @@ +# Test code for the ACI modules +# Copyright: (c) 2021, 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 + + # 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: '{{ aci_output_level | default("info") }}' + +# CLEAN ENVIRONMENT +- name: Remove ansible_tenant if it already exists + cisco.aci.aci_tenant: + <<: *aci_info + name: ansible_tenant + state: absent + +# CREATE TENANT +- name: Create ansible_tenant + cisco.aci.aci_tenant: + <<: *aci_info + name: ansible_tenant + state: present + +# CREATE DOMAIN +- name: Create ansible_phys_domain + cisco.aci.aci_domain: + <<: *aci_info + domain: ansible_phys_dom + domain_type: phys + state: present + +# ADD DEVICE +- name: Create L4-L7 Device + cisco.aci.aci_l4l7_device: + <<: *aci_info + tenant: ansible_tenant + device: ansible_device + domain: ansible_phys_dom + func_type: go_to + context_aware: single + managed: false + dev_type: physical + svc_type: adc + trunking: false + prom_mode: true + state: present + +# ADD SERVICE GRAPH TEMPLATE +- name: Create L4-L7 Service Graph Template + cisco.aci.aci_l4l7_service_graph_template: + <<: *aci_info + tenant: ansible_tenant + service_graph: ansible_graph + state: present + +# ADD SERVICE GRAPH NODE +- name: Create L4-L7 Service Graph Node + cisco.aci.aci_l4l7_service_graph_template_node: + <<: *aci_info + tenant: ansible_tenant + service_graph: ansible_graph + node: ansible_node + func_template_type: adc_one_arm + func_type: go_to + device: ansible_device + managed: false + routing_mode: Redirect + state: present + register: add_l4l7_node + +# VERIFY NODE CREATION +- name: Verify Node has been created correctly + ansible.builtin.assert: + that: + - add_l4l7_node.current.0.vnsAbsNode.attributes.dn == "uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsNode-ansible_node" + - add_l4l7_node.current.0.vnsAbsNode.attributes.name == "ansible_node" + - add_l4l7_node.current.0.vnsAbsNode.attributes.funcType == "GoTo" + - add_l4l7_node.current.0.vnsAbsNode.attributes.funcTemplateType == "ADC_ONE_ARM" + - add_l4l7_node.current.0.vnsAbsNode.attributes.managed == "no" + - add_l4l7_node.current.0.vnsAbsNode.attributes.routingMode == "Redirect" + +# VERIFY NODE BINDING TO LOGICAL DEVICE +- name: Verify Node Binding to Logical Device + ansible.builtin.assert: + that: + - add_l4l7_node.current.0.vnsAbsNode.children.0.vnsRsNodeToLDev.attributes.tDn == "uni/tn-ansible_tenant/lDevVip-ansible_device" + +# ADD NODE AGAIN TO TEST IDEMPOTENCE +- name: Add Node again to check idempotence + cisco.aci.aci_l4l7_service_graph_template_node: + <<: *aci_info + tenant: ansible_tenant + service_graph: ansible_graph + node: ansible_node + func_template_type: adc_one_arm + func_type: go_to + device: ansible_device + managed: false + routing_mode: Redirect + state: present + register: add_l4l7_node_again + +# VERIFY NODE IS NOT MODIFIED +- name: Verify Node has not changed + ansible.builtin.assert: + that: + - add_l4l7_node_again is not changed + - add_l4l7_node_again.current.0.vnsAbsNode.attributes.dn == "uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsNode-ansible_node" + - add_l4l7_node_again.current.0.vnsAbsNode.attributes.name == "ansible_node" + - add_l4l7_node_again.current.0.vnsAbsNode.attributes.funcType == "GoTo" + - add_l4l7_node_again.current.0.vnsAbsNode.attributes.funcTemplateType == "ADC_ONE_ARM" + - add_l4l7_node_again.current.0.vnsAbsNode.attributes.managed == "no" + - add_l4l7_node_again.current.0.vnsAbsNode.attributes.routingMode == "Redirect" + +# VERIFY NODE BINDING TO LOGICAL DEVICE +- name: Verify Node Binding to Logical Device is not changed + ansible.builtin.assert: + that: + - add_l4l7_node_again is not changed + - add_l4l7_node_again.current.0.vnsAbsNode.children.0.vnsRsNodeToLDev.attributes.tDn == "uni/tn-ansible_tenant/lDevVip-ansible_device" + +# MODIFY L4-L7 NODE +- name: Update L4-L7 Service Graph Node + cisco.aci.aci_l4l7_service_graph_template_node: + <<: *aci_info + tenant: ansible_tenant + service_graph: ansible_graph + node: ansible_node + func_template_type: adc_two_arm + func_type: go_through + device: ansible_device + managed: true + routing_mode: Redirect + state: present + register: update_l4l7_node + +# VERIFY NODE ATTRIBUTES +- name: Verify Node has not changed + ansible.builtin.assert: + that: + - update_l4l7_node is changed + - update_l4l7_node.current.0.vnsAbsNode.attributes.dn == "uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsNode-ansible_node" + - update_l4l7_node.current.0.vnsAbsNode.attributes.name == "ansible_node" + - update_l4l7_node.current.0.vnsAbsNode.attributes.funcType == "GoThrough" + - update_l4l7_node.current.0.vnsAbsNode.attributes.funcTemplateType == "ADC_TWO_ARM" + - update_l4l7_node.current.0.vnsAbsNode.attributes.managed == "yes" + - update_l4l7_node.current.0.vnsAbsNode.attributes.routingMode == "Redirect" + +# VERIFY NODE BINDING TO LOGICAL DEVICE +- name: Verify Node Binding to Logical Device + ansible.builtin.assert: + that: + - add_l4l7_node_again is not changed + - add_l4l7_node_again.current.0.vnsAbsNode.children.0.vnsRsNodeToLDev.attributes.tDn == "uni/tn-ansible_tenant/lDevVip-ansible_device" + +# QUERY L4-L7 NODE +- name: Query L4-L7 Service Graph Node + cisco.aci.aci_l4l7_service_graph_template_node: + <<: *aci_info + tenant: ansible_tenant + service_graph: ansible_graph + node: ansible_node + state: query + register: query_l4l7_node + +# VERIFY NODE ATTRIBUTES +- name: Verify Node has not changed + ansible.builtin.assert: + that: + - query_l4l7_node is not changed + - query_l4l7_node.current.0.vnsAbsNode.attributes.dn == "uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsNode-ansible_node" + - query_l4l7_node.current.0.vnsAbsNode.attributes.name == "ansible_node" + - query_l4l7_node.current.0.vnsAbsNode.attributes.funcType == "GoThrough" + - query_l4l7_node.current.0.vnsAbsNode.attributes.funcTemplateType == "ADC_TWO_ARM" + - query_l4l7_node.current.0.vnsAbsNode.attributes.managed == "yes" + - query_l4l7_node.current.0.vnsAbsNode.attributes.routingMode == "Redirect" + +# VERIFY NODE BINDING TO LOGICAL DEVICE +- name: Verify Node Binding to Logical Device + ansible.builtin.assert: + that: + - query_l4l7_node is not changed + - query_l4l7_node.current.0.vnsAbsNode.children.0.vnsRsNodeToLDev.attributes.tDn == "uni/tn-ansible_tenant/lDevVip-ansible_device" + +# DELETE L4-L7 NODE +- name: Remove L4-L7 Service Graph Node + cisco.aci.aci_l4l7_service_graph_template_node: + <<: *aci_info + tenant: ansible_tenant + service_graph: ansible_graph + node: ansible_node + state: absent + register: delete_l4l7_node + +# VERIFY NODE REMOVAL +- name: Verify Node removal + ansible.builtin.assert: + that: + - delete_l4l7_node is changed + - delete_l4l7_node.current == [] + - delete_l4l7_node.previous.0.vnsAbsNode.attributes.dn == "uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsNode-ansible_node" + - delete_l4l7_node.previous.0.vnsAbsNode.attributes.name == "ansible_node" + - delete_l4l7_node.previous.0.vnsAbsNode.attributes.funcType == "GoThrough" + - delete_l4l7_node.previous.0.vnsAbsNode.attributes.funcTemplateType == "ADC_TWO_ARM" + - delete_l4l7_node.previous.0.vnsAbsNode.attributes.managed == "yes" + - delete_l4l7_node.previous.0.vnsAbsNode.attributes.routingMode == "Redirect" + +# DELETE L4-L7 NODE AGAIN TO TEST IDEMPOTENCE +- name: Remove L4-L7 Service Graph Node again + cisco.aci.aci_l4l7_service_graph_template_node: + <<: *aci_info + tenant: ansible_tenant + service_graph: ansible_graph + node: ansible_node + state: absent + register: delete_l4l7_node_again + +# VERIFY NODE REMOVAL IDEMPOTENCE +- name: Verify Node removal idempotence + ansible.builtin.assert: + that: + - delete_l4l7_node_again is not changed + - delete_l4l7_node_again.current == [] + +# CLEAN UP +- name: Remove ansible_tenant + cisco.aci.aci_tenant: + <<: *aci_info + name: ansible_tenant + state: absent + +- name: Remove ansible_phys_dom + cisco.aci.aci_domain: + <<: *aci_info + domain: ansible_phys_dom + domain_type: phys + state: absent diff --git a/tests/integration/targets/aci_l4l7_service_graph_template_term_node/aliases b/tests/integration/targets/aci_l4l7_service_graph_template_term_node/aliases new file mode 100644 index 000000000..209b793f9 --- /dev/null +++ b/tests/integration/targets/aci_l4l7_service_graph_template_term_node/aliases @@ -0,0 +1,2 @@ +# No ACI simulator yet, so not enabled +# unsupported diff --git a/tests/integration/targets/aci_l4l7_service_graph_template_term_node/tasks/main.yml b/tests/integration/targets/aci_l4l7_service_graph_template_term_node/tasks/main.yml new file mode 100644 index 000000000..aab769e76 --- /dev/null +++ b/tests/integration/targets/aci_l4l7_service_graph_template_term_node/tasks/main.yml @@ -0,0 +1,211 @@ +# Test code for the ACI modules +# Copyright: (c) 2021, 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 + +# 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: '{{ aci_output_level | default("info") }}' + +# CLEAN ENVIRONMENT +- name: Remove ansible_tenant if it already exists + cisco.aci.aci_tenant: + <<: *aci_info + name: ansible_tenant + state: absent + +# CREATE TENANT +- name: Create ansible_tenant + cisco.aci.aci_tenant: + <<: *aci_info + name: ansible_tenant + state: present + +# ADD SERVICE GRAPH TEMPLATE +- name: Create L4-L7 Service Graph Template + cisco.aci.aci_l4l7_service_graph_template: + <<: *aci_info + tenant: ansible_tenant + service_graph: ansible_graph + state: present + +# ADD SERVICE GRAPH TERM NODE +- name: Create L4-L7 T1 Service Graph Node + cisco.aci.aci_l4l7_service_graph_template_term_node: + <<: *aci_info + tenant: ansible_tenant + service_graph: ansible_graph + node_name: T1 + state: present + register: add_l4l7_term_node + +# VERIFY TERM NODE CREATION +- name: Verify Term Node Creation + ansible.builtin.assert: + that: + - add_l4l7_term_node.current.0.vnsAbsTermNodeCon.attributes.dn == "uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsTermNodeCon-T1" + - add_l4l7_term_node.current.0.vnsAbsTermNodeCon.attributes.name == "T1" + +# ADD SERVICE GRAPH TERM NODE AGAIN TO CHECK IDEMPOTENCE +- name: Create L4-L7 T1 Service Graph Node again + cisco.aci.aci_l4l7_service_graph_template_term_node: + <<: *aci_info + tenant: ansible_tenant + service_graph: ansible_graph + node_name: T1 + state: present + register: add_l4l7_term_node_again + +# VERIFY TERM NODE UNCHANGED +- name: Verify Term Node is not changed + ansible.builtin.assert: + that: + - add_l4l7_term_node_again is not changed + - add_l4l7_term_node_again.current.0.vnsAbsTermNodeCon.attributes.dn == "uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsTermNodeCon-T1" + - add_l4l7_term_node_again.current.0.vnsAbsTermNodeCon.attributes.name == "T1" + +# QUERY TERM NODE +- name: Query L4-L7 T1 Service Graph Node + cisco.aci.aci_l4l7_service_graph_template_term_node: + <<: *aci_info + tenant: ansible_tenant + service_graph: ansible_graph + node_name: T1 + state: query + register: query_l4l7_term_node + +# VERIFY TERM NODE QUERY +- name: Verify Term Node attributes + ansible.builtin.assert: + that: + - query_l4l7_term_node is not changed + - query_l4l7_term_node.current.0.vnsAbsTermNodeCon.attributes.dn == "uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsTermNodeCon-T1" + - query_l4l7_term_node.current.0.vnsAbsTermNodeCon.attributes.name == "T1" + +# DELETE TERM NODE T1 +- name: Remove L4-L7 T1 Service Graph Node + cisco.aci.aci_l4l7_service_graph_template_term_node: + <<: *aci_info + tenant: ansible_tenant + service_graph: ansible_graph + node_name: T1 + state: absent + register: delete_l4l7_term_node + +# VERIFY DELETION +- name: Verify Term Node attributes + ansible.builtin.assert: + that: + - delete_l4l7_term_node is changed + - delete_l4l7_term_node.current == [] + - delete_l4l7_term_node.previous.0.vnsAbsTermNodeCon.attributes.dn == "uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsTermNodeCon-T1" + - delete_l4l7_term_node.previous.0.vnsAbsTermNodeCon.attributes.name == "T1" + +# ADD SERVICE GRAPH TERM NODE +- name: Create L4-L7 T2 Service Graph Node + cisco.aci.aci_l4l7_service_graph_template_term_node: + <<: *aci_info + tenant: ansible_tenant + service_graph: ansible_graph + node_name: T2 + state: present + register: add_l4l7_term_node2 + +# VERIFY TERM NODE CREATION +- name: Verify Term Node Creation + ansible.builtin.assert: + that: + - add_l4l7_term_node2.current.0.vnsAbsTermNodeProv.attributes.dn == "uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsTermNodeProv-T2" + - add_l4l7_term_node2.current.0.vnsAbsTermNodeProv.attributes.name == "T2" + +# ADD SERVICE GRAPH TERM NODE AGAIN TO CHECK IDEMPOTENCE +- name: Create L4-L7 T2 Service Graph Node again + cisco.aci.aci_l4l7_service_graph_template_term_node: + <<: *aci_info + tenant: ansible_tenant + service_graph: ansible_graph + node_name: T2 + state: present + register: add_l4l7_term_node2_again + +# VERIFY TERM NODE UNCHANGED +- name: Verify Term Node is not changed + ansible.builtin.assert: + that: + - add_l4l7_term_node2_again is not changed + - add_l4l7_term_node2_again.current.0.vnsAbsTermNodeProv.attributes.dn == "uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsTermNodeProv-T2" + - add_l4l7_term_node2_again.current.0.vnsAbsTermNodeProv.attributes.name == "T2" + +# QUERY TERM NODE +- name: Query L4-L7 T2 Service Graph Node + cisco.aci.aci_l4l7_service_graph_template_term_node: + <<: *aci_info + tenant: ansible_tenant + service_graph: ansible_graph + node_name: T2 + state: query + register: query_l4l7_term_node2 + +# VERIFY TERM NODE QUERY +- name: Verify Term Node attributes + ansible.builtin.assert: + that: + - query_l4l7_term_node2 is not changed + - query_l4l7_term_node2.current.0.vnsAbsTermNodeProv.attributes.dn == "uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsTermNodeProv-T2" + - query_l4l7_term_node2.current.0.vnsAbsTermNodeProv.attributes.name == "T2" + +# DELETE TERM NODE T2 +- name: Remove L4-L7 T2 Service Graph Node + cisco.aci.aci_l4l7_service_graph_template_term_node: + <<: *aci_info + tenant: ansible_tenant + service_graph: ansible_graph + node_name: T2 + state: absent + register: delete_l4l7_term_node2 + +# VERIFY DELETION +- name: Verify Term Node T2 deletion + ansible.builtin.assert: + that: + - delete_l4l7_term_node2 is changed + - delete_l4l7_term_node2.current == [] + - delete_l4l7_term_node2.previous.0.vnsAbsTermNodeProv.attributes.dn == "uni/tn-ansible_tenant/AbsGraph-ansible_graph/AbsTermNodeProv-T2" + - delete_l4l7_term_node2.previous.0.vnsAbsTermNodeProv.attributes.name == "T2" + +# DELETE TERM NODE T2 AGAIN TO TEST IDEMPOTENCE +- name: Remove L4-L7 T2 Service Graph Node + cisco.aci.aci_l4l7_service_graph_template_term_node: + <<: *aci_info + tenant: ansible_tenant + service_graph: ansible_graph + node_name: T2 + state: absent + register: delete_l4l7_term_node2_again + +# VERIFY DELETION IDEMPOTENCE +- name: Verify Term Node T2 deletion idempotence + ansible.builtin.assert: + that: + - delete_l4l7_term_node2_again is not changed + - delete_l4l7_term_node2_again.current == [] + +# CLEAN UP +- name: Remove ansible_tenant + cisco.aci.aci_tenant: + <<: *aci_info + name: ansible_tenant + state: absent