diff --git a/lib/ansible/modules/network/aci/aci_vlan_pool.py b/lib/ansible/modules/network/aci/aci_vlan_pool.py new file mode 100644 index 0000000000..7a4bf2c09c --- /dev/null +++ b/lib/ansible/modules/network/aci/aci_vlan_pool.py @@ -0,0 +1,160 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2017, Jacob McGill (@jmcgill298) +# Copyright: (c) 2018, Dag Wieers (@dagwieers) +# 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_vlan_pool +short_description: Manage VLAN pools on Cisco ACI fabrics (fvns:VlanInstP) +description: +- Manage VLAN pools on Cisco ACI fabrics. +- More information from the internal APIC class I(fvns:VlanInstP) at + U(https://developer.cisco.com/site/aci/docs/apis/apic-mim-ref/). +author: +- Jacob McGill (@jmcgill298) +- Dag Wieers (@dagwieers) +version_added: '2.5' +options: + allocation_mode: + description: + - The method used for allocating VLANs to resources. + 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 ] + 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_vlan_pool: + hostname: apic + username: admin + password: SomeSecretPassword + pool: production + description: Production VLANs + state: present + +- name: Remove a VLAN pool + aci_vlan_pool: + hostname: apic + username: admin + password: SomeSecretPassword + pool: production + state: absent + +- name: Query a VLAN pool + aci_vlan_pool: + hostname: apic + username: admin + password: SomeSecretPassword + pool: production + state: query + +- name: Query all VLAN pools + aci_vlan_pool: + hostname: apic + username: admin + password: SomeSecretPassword + state: query +''' + +RETURN = r''' +# +''' + +from ansible.module_utils.network.aci.aci import ACIModule, aci_argument_spec +from ansible.module_utils.basic import AnsibleModule + + +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']), + 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'] + state = module.params['state'] + + pool_name = pool + + # ACI Pool URL requires the allocation mode for vlan and vsan pools (ex: uni/infra/vlanns-[poolname]-static) + if 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' when 'pool' is provided") + + aci = ACIModule(module) + aci.construct_url( + root_class=dict( + aci_class='fvnsVlanInstP', + aci_rn='infra/vlanns-{0}'.format(pool_name), + filter_target='eq(fvnsVlanInstP.name, "{0}")'.format(pool), + module_object=pool, + ), + ) + + aci.get_existing() + + if state == 'present': + # Filter out module parameters with null values + aci.payload( + aci_class='fvnsVlanInstP', + 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='fvnsVlanInstP') + + # 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_vlan_pool/aliases b/test/integration/targets/aci_vlan_pool/aliases new file mode 100644 index 0000000000..b4e2520c17 --- /dev/null +++ b/test/integration/targets/aci_vlan_pool/aliases @@ -0,0 +1 @@ +# No AcI Simulator yet, so not enabled diff --git a/test/integration/targets/aci_vlan_pool/tasks/main.yml b/test/integration/targets/aci_vlan_pool/tasks/main.yml new file mode 100644 index 0000000000..79ba2b53ae --- /dev/null +++ b/test/integration/targets/aci_vlan_pool/tasks/main.yml @@ -0,0 +1,243 @@ +# Test code for the ACI modules + +# Copyright: (c) 2017, Jacob McGill (jmcgill298) +# Copyright: (c) 2018, Dag Wieers (dagwieers) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +- name: Test that we have an aci apic host, aci username and aci password + fail: + msg: 'Please define the following variables: aci_hostname, aci_username and aci_password.' + when: aci_hostname is not defined or aci_username is not defined or aci_password is not defined + +- name: Ensure vlan pool does not exist for tests to kick off + aci_vlan_pool: &aci_pool_absent_static + host: "{{ aci_hostname }}" + username: "{{ aci_username }}" + password: "{{ aci_password }}" + validate_certs: no + state: absent + pool: anstest + allocation_mode: static + +- name: Ensure vlan pool does not exist for tests to kick off + aci_vlan_pool: &aci_pool_absent_dynamic + <<: *aci_pool_absent_static + allocation_mode: dynamic + +- name: Create static vlan pool - check mode works + aci_vlan_pool: &aci_pool_present_static + <<: *aci_pool_absent_static + state: present + descr: Ansible Test + check_mode: yes + register: create_check_mode + +- name: Assertion test - present + assert: + that: + - create_check_mode.changed == true + - 'create_check_mode.config == {"fvnsVlanInstP": {"attributes": {"allocMode": "static", "descr": "Ansible Test", "name": "anstest"}}}' + +- name: Create static vlan pool - creation works + aci_vlan_pool: + <<: *aci_pool_present_static + register: create_static + +- name: Assertion test - present + assert: + that: + - create_static.changed == true + - create_static.existing == [] + - create_static.config == create_check_mode.config + +- name: Create dynamic vlan pool - creation works + aci_vlan_pool: &aci_pool_present_dynamic + <<: *aci_pool_absent_dynamic + state: present + descr: Ansible Test + register: create_dynamic + +- name: Assertion test - present + assert: + that: + - create_dynamic.changed == true + - create_dynamic.existing == [] + - 'create_dynamic.config == {"fvnsVlanInstP": {"attributes": {"allocMode": "dynamic", "descr": "Ansible Test", "name": "anstest"}}}' + +- name: Create static vlan pool again - idempotency works + aci_vlan_pool: + <<: *aci_pool_present_static + register: idempotent_static + +- name: Assertion test - present + assert: + that: + - idempotent_static.changed == false + - 'idempotent_static.existing == [{"fvnsVlanInstP": {"attributes": {"allocMode": "static", "descr": "Ansible Test", "dn": "uni/infra/vlanns-[anstest]-static", "name": "anstest", "nameAlias": "", "ownerKey": "", "ownerTag": ""}}}]' + - idempotent_static.config == {} + +- name: Create dynamic vlan pool again - idempotency works + aci_vlan_pool: + <<: *aci_pool_present_dynamic + register: idempotent_dynamic + +- name: Assertion test - present + assert: + that: + - idempotent_dynamic.changed == false + - 'idempotent_dynamic.existing == [{"fvnsVlanInstP": {"attributes": {"allocMode": "dynamic", "descr": "Ansible Test", "dn": "uni/infra/vlanns-[anstest]-dynamic", "name": "anstest", "nameAlias": "", "ownerKey": "", "ownerTag": ""}}}]' + - idempotent_dynamic.config == {} + +- name: Update static vlan pool - update works + aci_vlan_pool: + <<: *aci_pool_present_static + descr: Ansible Test Change + register: update_static + +- name: Assertion test - present + assert: + that: + - update_static.changed == true + - 'update_static.config == {"fvnsVlanInstP": {"attributes": {"descr": "Ansible Test Change"}}}' + +- name: Update dynamic vlan pool - update works + aci_vlan_pool: + <<: *aci_pool_present_dynamic + descr: Ansible Test Change + register: update_dynamic + +- name: Assertion test - present + assert: + that: + - update_dynamic.changed == true + - 'update_dynamic.config == {"fvnsVlanInstP": {"attributes": {"descr": "Ansible Test Change"}}}' + +- name: Missing param - failure message works + aci_vlan_pool: + <<: *aci_pool_present_dynamic + allocation_mode: "{{ fake_var | default(omit) }}" + ignore_errors: yes + register: vlan_alloc_fail + +- name: Assertion test - present + assert: + that: + - vlan_alloc_fail.failed == true + - vlan_alloc_fail.msg == "ACI requires the 'allocation_mode' when 'pool' is provided" + +- name: Missing param - failure message works + aci_vlan_pool: + <<: *aci_pool_present_dynamic + pool: "{{ fake_var | default(omit) }}" + ignore_errors: yes + register: vlan_pool_fail + +- name: Assertion test - present + assert: + that: + - vlan_pool_fail.failed == true + - 'vlan_pool_fail.msg == "state is present but all of the following are missing: pool"' + +- name: Get all vlan pools - get class works + aci_vlan_pool: + <<: *aci_pool_absent_static + state: query + pool: "{{ fake_var | default(omit) }}" + allocation_mode: "{{ fake_var | default(omit) }}" + register: get_all_pools + +- name: Assertion test - query + assert: + that: + - get_all_pools.changed == false + - get_all_pools.method == "GET" + - get_all_pools.existing | length > 1 + +- name: Get created static vlan pool - get mo works + aci_vlan_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_vlan_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: Delete static vlan pool - deletion works + aci_vlan_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_vlan_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_vlan_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_vlan_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_vlan_pool: + <<: *aci_pool_absent_dynamic + register: idempotent_delete_dynamic + +- name: Assertion test - absent + assert: + that: + - idempotent_delete_dynamic.changed == false + - idempotent_delete_dynamic.existing == []