From 9ff5c15f5798ebd4a661fba45577847250b15da4 Mon Sep 17 00:00:00 2001 From: Jacob McGill Date: Tue, 12 Dec 2017 02:51:19 -0500 Subject: [PATCH] ACI Encap pool: New module for support layer2 encap pools (#33219) * ACI Encap Pool: Add new module to support VLAN, VXLAN, and VSAN Pools * update logic for allocation_mode * update docstring and tests * Update filter_target since url method has been updated --- .../modules/network/aci/aci_encap_pool.py | 192 +++++++++++++ .../targets/aci_encap_pool/aliases | 1 + .../targets/aci_encap_pool/tasks/main.yml | 18 ++ .../targets/aci_encap_pool/tasks/vlan.yml | 261 ++++++++++++++++++ .../targets/aci_encap_pool/tasks/vsan.yml | 11 + .../targets/aci_encap_pool/tasks/vxlan.yml | 164 +++++++++++ 6 files changed, 647 insertions(+) create mode 100644 lib/ansible/modules/network/aci/aci_encap_pool.py create mode 100644 test/integration/targets/aci_encap_pool/aliases create mode 100644 test/integration/targets/aci_encap_pool/tasks/main.yml create mode 100644 test/integration/targets/aci_encap_pool/tasks/vlan.yml create mode 100644 test/integration/targets/aci_encap_pool/tasks/vsan.yml create mode 100644 test/integration/targets/aci_encap_pool/tasks/vxlan.yml diff --git a/lib/ansible/modules/network/aci/aci_encap_pool.py b/lib/ansible/modules/network/aci/aci_encap_pool.py new file mode 100644 index 00000000000..8de5299374f --- /dev/null +++ b/lib/ansible/modules/network/aci/aci_encap_pool.py @@ -0,0 +1,192 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# GNU General Public License v3.0+ (see COPYING 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_encap_pool +short_description: Manage encap pools on Cisco ACI fabrics (fvns:VlanInstP, fvns:VxlanInstP, fvns:VsanInstP) +description: +- Manage vlan, vxlan, and vsan pools on Cisco ACI fabrics. +- More information from the internal APIC class + I(fvns:VlanInstP), I(fvns:VxlanInstP), and I(fvns:VsanInstP) at + U(https://developer.cisco.com/site/aci/docs/apis/apic-mim-ref/). +author: +- Jacob McGill (@jmcgill298) +version_added: '2.5' +options: + allocation_mode: + description: + - The method used for allocating encaps to resources. + - Only vlan and vsan support allocation modes. + aliases: [ mode ] + choices: [ dynamic, static] + description: + description: + - Description for the C(pool). + aliases: [ descr ] + pool: + description: + - The name of the pool. + aliases: [ name, pool_name ] + pool_type: + description: + - The encap type of C(pool). + required: yes + aliases: [ type ] + choices: [ vlan, vxlan, vsan] + state: + description: + - Use C(present) or C(absent) for adding or removing. + - Use C(query) for listing an object or multiple objects. + choices: [ absent, present, query ] + default: present +extends_documentation_fragment: aci +''' + +EXAMPLES = r''' +- name: Add a new vlan pool + aci_encap_pool: + hostname: apic + username: admin + password: SomeSecretPassword + pool: production + pool_type: vlan + description: Production VLANs + state: present + +- name: Remove a vlan pool + aci_encap_pool: + hostname: apic + username: admin + password: SomeSecretPassword + pool: production + pool_type: vlan + state: absent + +- name: Query a vlan pool + aci_encap_pool: + hostname: apic + username: admin + password: SomeSecretPassword + pool: production + pool_type: vlan + state: query + +- name: Query all vlan pools + aci_encap_pool: + hostname: apic + username: admin + password: SomeSecretPassword + pool_type: vlan + state: query +''' + +RETURN = r''' +# +''' + +from ansible.module_utils.network.aci.aci import ACIModule, aci_argument_spec +from ansible.module_utils.basic import AnsibleModule + +ACI_MAPPING = dict( + vlan=dict( + aci_class='fvnsVlanInstP', + aci_mo='infra/vlanns-', + ), + vxlan=dict( + aci_class='fvnsVxlanInstP', + aci_mo='infra/vxlanns-', + ), + vsan=dict( + aci_class='fvnsVsanInstP', + aci_mo='infra/vsanns-', + ), +) + + +def main(): + argument_spec = aci_argument_spec + argument_spec.update( + allocation_mode=dict(type='str', aliases=['mode'], choices=['dynamic', 'static']), + description=dict(type='str', aliases=['descr']), + pool=dict(type='str', aliases=['name', 'pool_name']), + pool_type=dict(type='str', aliases=['type'], choices=['vlan', 'vxlan', 'vsan'], required=True), + state=dict(type='str', default='present', choices=['absent', 'present', 'query']), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_if=[ + ['state', 'absent', ['pool']], + ['state', 'present', ['pool']], + ], + ) + + allocation_mode = module.params['allocation_mode'] + description = module.params['description'] + pool = module.params['pool'] + pool_type = module.params['pool_type'] + state = module.params['state'] + + aci_class = ACI_MAPPING[pool_type]["aci_class"] + aci_mo = ACI_MAPPING[pool_type]["aci_mo"] + pool_name = pool + + # ACI Pool URL requires the allocation mode for vlan and vsan pools (ex: uni/infra/vlanns-[poolname]-static) + if pool_type != 'vxlan' and pool is not None: + if allocation_mode is not None: + pool_name = '[{0}]-{1}'.format(pool, allocation_mode) + else: + module.fail_json(msg='ACI requires the "allocation_mode" for "pool_type" of "vlan" and "vsan" when the "pool" is provided') + + # Vxlan pools do not support allocation modes + if pool_type == 'vxlan' and allocation_mode is not None: + module.fail_json(msg='vxlan pools do not support setting the allocation_mode; please remove this parameter from the task') + + aci = ACIModule(module) + aci.construct_url( + root_class=dict( + aci_class=aci_class, + aci_rn='{0}{1}'.format(aci_mo, pool_name), + filter_target='eq({0}.name, "{1}")'.format(aci_class, pool), + module_object=pool, + ), + ) + + aci.get_existing() + + if state == 'present': + # Filter out module parameters with null values + aci.payload( + aci_class=aci_class, + class_config=dict( + allocMode=allocation_mode, + descr=description, + name=pool, + ) + ) + + # Generate config diff which will be used as POST request body + aci.get_diff(aci_class=aci_class) + + # Submit changes if module not in check_mode and the proposed is different than existing + aci.post_config() + + elif state == 'absent': + aci.delete_config() + + module.exit_json(**aci.result) + + +if __name__ == "__main__": + main() diff --git a/test/integration/targets/aci_encap_pool/aliases b/test/integration/targets/aci_encap_pool/aliases new file mode 100644 index 00000000000..b4e2520c177 --- /dev/null +++ b/test/integration/targets/aci_encap_pool/aliases @@ -0,0 +1 @@ +# No AcI Simulator yet, so not enabled diff --git a/test/integration/targets/aci_encap_pool/tasks/main.yml b/test/integration/targets/aci_encap_pool/tasks/main.yml new file mode 100644 index 00000000000..a086126abd4 --- /dev/null +++ b/test/integration/targets/aci_encap_pool/tasks/main.yml @@ -0,0 +1,18 @@ +# Test code for the ACI modules +# Copyright 2017, Jacob McGill 1 + +- name: get created static vlan pool - get mo works + aci_encap_pool: + <<: *aci_pool_absent_static + state: query + register: get_static_pool + +- name: assertion test - query + assert: + that: + - get_static_pool.changed == false + - get_static_pool.method == "GET" + - get_static_pool.existing | length == 1 + - get_static_pool.existing.0.fvnsVlanInstP.attributes.allocMode == "static" + - get_static_pool.existing.0.fvnsVlanInstP.attributes.name == "anstest" + +- name: get created dynamic vlan pool - get mo works + aci_encap_pool: + <<: *aci_pool_absent_dynamic + state: query + register: get_dynamic_pool + +- name: assertion test - query + assert: + that: + - get_dynamic_pool.changed == false + - get_dynamic_pool.method == "GET" + - get_dynamic_pool.existing | length == 1 + - get_dynamic_pool.existing.0.fvnsVlanInstP.attributes.allocMode == "dynamic" + - get_dynamic_pool.existing.0.fvnsVlanInstP.attributes.name == "anstest" + +- name: get created dynamic vlan pool - get mo works + aci_encap_pool: + <<: *aci_pool_absent_dynamic + state: query + pool_type: "{{ fake_var | default(omit) }}" + ignore_errors: yes + register: vlan_query_pool_type_fail + +- name: assertion test - query + assert: + that: + - vlan_query_pool_type_fail.failed == true + - 'vlan_query_pool_type_fail.msg == "missing required arguments: pool_type"' + +- name: delete static vlan pool - deletion works + aci_encap_pool: + <<: *aci_pool_absent_static + register: delete_static + +- name: assertion test - absent + assert: + that: + - delete_static.changed == true + - delete_static.method == "DELETE" + - delete_static.existing.0.fvnsVlanInstP.attributes.allocMode == "static" + - delete_static.existing.0.fvnsVlanInstP.attributes.name == "anstest" + +- name: delete dynamic vlan pool - check mode works + aci_encap_pool: + <<: *aci_pool_absent_dynamic + check_mode: yes + register: delete_check_mode + +- name: assertion test - absent + assert: + that: + - delete_check_mode.changed == true + +- name: delete dynamic vlan pool - deletion works + aci_encap_pool: + <<: *aci_pool_absent_dynamic + register: delete_dynamic + +- name: assertion test - absent + assert: + that: + - delete_dynamic.changed == true + - delete_dynamic.method == "DELETE" + - delete_dynamic.existing.0.fvnsVlanInstP.attributes.allocMode == "dynamic" + - delete_dynamic.existing.0.fvnsVlanInstP.attributes.name == "anstest" + +- name: delete static vlan pool again - idempotency works + aci_encap_pool: + <<: *aci_pool_absent_static + register: idempotent_delete_static + +- name: assertion test - absent + assert: + that: + - idempotent_delete_static.changed == false + - idempotent_delete_static.existing == [] + +- name: delete dynamic vlan pool again - idempotency works + aci_encap_pool: + <<: *aci_pool_absent_dynamic + register: idempotent_delete_dynamic + +- name: assertion test - absent + assert: + that: + - idempotent_delete_dynamic.changed == false + - idempotent_delete_dynamic.existing == [] diff --git a/test/integration/targets/aci_encap_pool/tasks/vsan.yml b/test/integration/targets/aci_encap_pool/tasks/vsan.yml new file mode 100644 index 00000000000..0e202cbebbe --- /dev/null +++ b/test/integration/targets/aci_encap_pool/tasks/vsan.yml @@ -0,0 +1,11 @@ +--- +- name: ensure vlan pool does not exist for tests to kick off + aci_encap_pool: &aci_pool_absent_static + host: "{{ aci_hostname }}" + username: "{{ aci_username }}" + password: "{{ aci_password }}" + validate_certs: no + state: absent + pool: anstest + pool_type: vsan + allocation_mode: static \ No newline at end of file diff --git a/test/integration/targets/aci_encap_pool/tasks/vxlan.yml b/test/integration/targets/aci_encap_pool/tasks/vxlan.yml new file mode 100644 index 00000000000..ecfd6f3b45c --- /dev/null +++ b/test/integration/targets/aci_encap_pool/tasks/vxlan.yml @@ -0,0 +1,164 @@ +--- +- name: ensure vxlan pool does not exist for tests to kick off + aci_encap_pool: &aci_vxlan_absent + host: "{{ aci_hostname }}" + username: "{{ aci_username }}" + password: "{{ aci_password }}" + validate_certs: no + state: absent + pool: anstest + pool_type: vxlan + +- name: create vxlan pool - check mode works + aci_encap_pool: &aci_vxlan_present + <<: *aci_vxlan_absent + state: present + descr: Ansible Test + check_mode: yes + register: create_vxlan_check_mode + +- name: assertion test - present + assert: + that: + - create_vxlan_check_mode.changed == true + - 'create_vxlan_check_mode.config == {"fvnsVxlanInstP": {"attributes": {"descr": "Ansible Test", "name": "anstest"}}}' + +- name: create vxlan pool - creation works + aci_encap_pool: + <<: *aci_vxlan_present + register: create_vxlan + +- name: assertion test - present + assert: + that: + - create_vxlan.changed == true + - create_vxlan.existing == [] + - create_vxlan.config == create_vxlan_check_mode.config + +- name: create vxlan pool again - idempotency works + aci_encap_pool: + <<: *aci_vxlan_present + register: idempotent_vxlan + +- name: assertion test - present + assert: + that: + - idempotent_vxlan.changed == false + - 'idempotent_vxlan.existing.0.fvnsVxlanInstP.attributes.name == "anstest"' + - idempotent_vxlan.config == {} + +- name: update vxlan pool - update works + aci_encap_pool: + <<: *aci_vxlan_present + descr: Ansible Test Change + register: update_vxlan + +- name: assertion test - present + assert: + that: + - update_vxlan.changed == true + - 'update_vxlan.config == {"fvnsVxlanInstP": {"attributes": {"descr": "Ansible Test Change"}}}' + +- name: create vxlan pool - used for query + aci_encap_pool: + <<: *aci_vxlan_present + name: anstest_2 + register: create_vxlan_2 + +- name: assertion test - present + assert: + that: + - create_vxlan_2.changed == true + +- name: create vxlan pool with allocation mode - failure message works + aci_encap_pool: + <<: *aci_vxlan_present + name: anstest_3 + allocation_mode: dynamic + ignore_errors: yes + register: create_vxlan_alloc_mode + +- name: assertion test - present + assert: + that: + - create_vxlan_alloc_mode.failed == true + - 'create_vxlan_alloc_mode.msg == "vxlan pools do not support setting the allocation_mode; please remove this parameter from the task"' + +- name: get vxlan pool - get object works + aci_encap_pool: &aci_vxlan_query + <<: *aci_vxlan_present + state: query + register: query_vxlan + +- name: assertion test - query + assert: + that: + - query_vxlan.changed == false + - query_vxlan.existing | length == 1 + - '"infra/vxlanns-anstest.json" in query_vxlan.url' + +- name: get created static vlan pool - get class works + aci_encap_pool: + <<: *aci_vxlan_query + pool: "{{ fake_var | default(omit) }}" + register: query_vxlan_all + +- name: assertion test - query + assert: + that: + - query_vxlan_all.changed == false + - query_vxlan_all.existing | length > 1 + - '"class/fvnsVxlanInstP.json" in query_vxlan_all.url' + +- name: delete vxlan pool - check mode works + aci_encap_pool: + <<: *aci_vxlan_absent + check_mode: yes + register: delete_vxlan_check_mode + +- name: assertion test - absent + assert: + that: + - delete_vxlan_check_mode.changed == true + - delete_vxlan_check_mode.existing != [] + +- name: delete vxlan pool - deletion works + aci_encap_pool: + <<: *aci_vxlan_absent + register: delete_vxlan + +- name: assertion test - absent + assert: + that: + - delete_vxlan.changed == true + - delete_vxlan.existing == delete_vxlan_check_mode.existing + - delete_vxlan.existing.0.fvnsVxlanInstP.attributes.name == "anstest" + +- name: delete vxlan pool again - idempotency works + aci_encap_pool: + <<: *aci_vxlan_absent + register: delete_vxlan_idempotent + +- name: missing param - failure message works + aci_encap_pool: + <<: *aci_vxlan_absent + pool: "{{ fake_var | default(omit) }}" + ignore_errors: yes + register: delete_vxlan_pool_fail + +- name: assertion test - absent + assert: + that: + - delete_vxlan_idempotent.changed == false + - delete_vxlan_idempotent.existing == [] + +- name: delete vxlan pool - cleanup + aci_encap_pool: + <<: *aci_vxlan_absent + pool: anstest_2 + +- name: assertion test - absent + assert: + that: + - delete_vxlan_pool_fail.failed == true + - 'delete_vxlan_pool_fail.msg == "state is absent but all of the following are missing: pool"'