diff --git a/plugins/modules/aci_l3out_logical_node.py b/plugins/modules/aci_l3out_logical_node.py index 2bfc876ad..33c8a22a5 100644 --- a/plugins/modules/aci_l3out_logical_node.py +++ b/plugins/modules/aci_l3out_logical_node.py @@ -49,6 +49,12 @@ type: str choices: [ 'yes', 'no' ] default: 'yes' + loopback_address: + description: + - The loopback IP address. + - A configured loopback address can be removed by passing an empty string (see Examples). + type: str + aliases: [ loopback ] state: description: - Use C(present) or C(absent) for adding or removing. @@ -82,9 +88,23 @@ pod_id: 1 node_id: 111 router_id: 111.111.111.111 + loopback_address: 111.111.111.112 state: present delegate_to: localhost +- name: Remove a loopback address from a node in node profile + cisco.aci.aci_l3out_logical_node: + host: apic + username: admin + password: SomeSecretPassword + tenant: my_tenant + l3out: my_l3out + node_profile: my_node_profile + pod_id: 1 + node_id: 111 + loopback_address: "" + delegate_to: localhost + - name: Delete a node from a node profile cisco.aci.aci_l3out_logical_node: host: apic @@ -243,6 +263,7 @@ def main(): node_id=dict(type="int"), router_id=dict(type="str"), router_id_as_loopback=dict(type="str", default="yes", choices=["yes", "no"]), + loopback_address=dict(type="str", aliases=["loopback"]), state=dict(type="str", default="present", choices=["absent", "present", "query"]), ) @@ -262,6 +283,7 @@ def main(): node_id = module.params.get("node_id") router_id = module.params.get("router_id") router_id_as_loopback = module.params.get("router_id_as_loopback") + loopback_address = module.params.get("loopback_address") state = module.params.get("state") tdn = None @@ -270,6 +292,10 @@ def main(): aci = ACIModule(module) + child_classes = ["l3extLoopBackIfP"] + + child_configs = [] + aci.construct_url( root_class=dict( aci_class="fvTenant", @@ -295,12 +321,25 @@ def main(): module_object=tdn, target_filter={"name": tdn}, ), + child_classes=child_classes, ) aci.get_existing() if state == "present": - aci.payload(aci_class="l3extRsNodeL3OutAtt", class_config=dict(rtrId=router_id, rtrIdLoopBack=router_id_as_loopback, tDn=tdn)) + if loopback_address is not None: + if loopback_address == "" and isinstance(aci.existing, list) and len(aci.existing) > 0: + for child in aci.existing[0].get("l3extRsNodeL3OutAtt", {}).get("children", []): + previous_loopback_address = child.get("l3extLoopBackIfP", {}).get("attributes", {}).get("addr") + child_configs.append(dict(l3extLoopBackIfP=dict(attributes=dict(addr=previous_loopback_address, status="deleted")))) + elif loopback_address: + child_configs.append(dict(l3extLoopBackIfP=dict(attributes=dict(addr=loopback_address)))) + + aci.payload( + aci_class="l3extRsNodeL3OutAtt", + class_config=dict(rtrId=router_id, rtrIdLoopBack=router_id_as_loopback, tDn=tdn), + child_configs=child_configs, + ) aci.get_diff(aci_class="l3extRsNodeL3OutAtt") diff --git a/tests/integration/targets/aci_l3out_logical_node/tasks/main.yml b/tests/integration/targets/aci_l3out_logical_node/tasks/main.yml index 68bc64b20..1d61ee3e3 100644 --- a/tests/integration/targets/aci_l3out_logical_node/tasks/main.yml +++ b/tests/integration/targets/aci_l3out_logical_node/tasks/main.yml @@ -1,6 +1,11 @@ # Author: Marcel Zehnder (@maercu) # GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) +- name: Test that we have an ACI APIC host, ACI username and ACI password + fail: + msg: 'Please define the following variables: aci_hostname, aci_username and aci_password.' + when: aci_hostname is not defined or aci_username is not defined or aci_password is not defined + # SET VARS - name: Set vars set_fact: @@ -79,9 +84,9 @@ - cm_add_node is changed - nm_add_node is changed - cm_add_node.previous == nm_add_node.previous == [] - - cm_add_node.sent.l3extRsNodeL3OutAtt.attributes.rtrId == nm_add_node.sent.l3extRsNodeL3OutAtt.attributes.rtrId == '111.111.111.111' - - cm_add_node.sent.l3extRsNodeL3OutAtt.attributes.rtrIdLoopBack == nm_add_node.sent.l3extRsNodeL3OutAtt.attributes.rtrIdLoopBack == 'no' - - cm_add_node.sent.l3extRsNodeL3OutAtt.attributes.tDn == nm_add_node.sent.l3extRsNodeL3OutAtt.attributes.tDn == 'topology/pod-1/node-111' + - cm_add_node.sent.l3extRsNodeL3OutAtt.attributes.rtrId == nm_add_node.current.0.l3extRsNodeL3OutAtt.attributes.rtrId == '111.111.111.111' + - cm_add_node.sent.l3extRsNodeL3OutAtt.attributes.rtrIdLoopBack == nm_add_node.current.0.l3extRsNodeL3OutAtt.attributes.rtrIdLoopBack == 'no' + - cm_add_node.sent.l3extRsNodeL3OutAtt.attributes.tDn == nm_add_node.current.0.l3extRsNodeL3OutAtt.attributes.tDn == 'topology/pod-1/node-111' - nm_add_node.current.0.l3extRsNodeL3OutAtt.attributes.annotation == 'orchestrator:ansible' - name: Add node again, check if idempotency works @@ -101,19 +106,37 @@ router_id: 11.11.11.11 register: update_node + - name: Add loopback address + cisco.aci.aci_l3out_logical_node: &node_add_loopback + <<: *node_update + loopback_address: 11.11.11.12 + register: add_loopback_ip + + - name: Remove loopback address + cisco.aci.aci_l3out_logical_node: &node_remove_loopback + <<: *node_add_loopback + loopback_address: "" + register: remove_loopback_ip + - name: Verify update_node assert: that: - update_node is changed - update_node.previous != [] - - update_node.sent.l3extRsNodeL3OutAtt.attributes.rtrId == '11.11.11.11' + - update_node.current.0.l3extRsNodeL3OutAtt.attributes.rtrId == '11.11.11.11' + - add_loopback_ip is changed + - add_loopback_ip.previous != [] + - add_loopback_ip.current.0.l3extRsNodeL3OutAtt.children.0.l3extLoopBackIfP.attributes.addr == '11.11.11.12' + - remove_loopback_ip is changed + - remove_loopback_ip.previous != [] # ADD ANOTHER NODE - name: Add another node - cisco.aci.aci_l3out_logical_node: + cisco.aci.aci_l3out_logical_node: &second_node_present <<: *node_present node_id: 112 router_id: 12.12.12.12 + loopback_address: 12.12.12.13 # QUERY ALL NODES - name: Query all nodes @@ -131,7 +154,7 @@ # QUERY A SPECIFIC NODE - name: Query a specific node cisco.aci.aci_l3out_logical_node: - <<: *node_update + <<: *second_node_present state: query register: query_spec_node @@ -140,11 +163,13 @@ that: - query_spec_node is not changed - query_spec_node.current|length == 1 + - query_spec_node.current.0.l3extRsNodeL3OutAtt.attributes.rtrId == '12.12.12.12' + - query_spec_node.current.0.l3extRsNodeL3OutAtt.children.0.l3extLoopBackIfP.attributes.addr == '12.12.12.13' # REMOVE NODE - name: Remove node cisco.aci.aci_l3out_logical_node: - <<: *node_update + <<: *node_remove_loopback state: absent register: remove_node @@ -153,3 +178,9 @@ that: - remove_node is changed - remove_node.current == [] + + - name: Remove test tenant - clean-up the environment + cisco.aci.aci_tenant: + <<: *aci_info + tenant: ansible_test + state: absent \ No newline at end of file