diff --git a/lib/ansible/modules/network/eos/eos_vrf.py b/lib/ansible/modules/network/eos/eos_vrf.py new file mode 100644 index 00000000000..054b118d563 --- /dev/null +++ b/lib/ansible/modules/network/eos/eos_vrf.py @@ -0,0 +1,192 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# (c) 2017, Ansible by Red Hat, inc +# +# This file is part of Ansible by Red Hat +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# + +ANSIBLE_METADATA = {'metadata_version': '1.0', + 'status': ['preview'], + 'supported_by': 'core'} + + +DOCUMENTATION = """ +--- +module: eos_vrf +version_added: "2.4" +author: "Ricardo Carrillo Cruz (@rcarrillocruz)" +short_description: Manage VRFs on Arista EOS network devices +description: + - This module provides declarative management of VRFs + on Arista EOS network devices. +options: + name: + description: + - Name of the VRF. + required: true + rd: + description: + - Route distinguisher of the VRF + interfaces: + description: + - List of interfaces to check the VRF has been + configured correctly. + collection: + description: List of VRFs definitions + purge: + description: + - Purge VRFs not defined in the collections parameter. + default: no + state: + description: + - State of the VRF configuration. + default: present + choices: ['present', 'absent'] +""" + +EXAMPLES = """ +""" + +RETURN = """ +commands: + description: The list of configuration mode commands to send to the device + returned: always + type: list + sample: + - vrf definition test + - rd 1:100 + - interface Ethernet1 + - vrf forwarding test +""" +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.eos import load_config, run_commands +from ansible.module_utils.eos import eos_argument_spec, check_args +from ansible.module_utils.six import iteritems + +import re + + +def map_obj_to_commands(updates, module): + commands = list() + want, have = updates + state = module.params['state'] + + if state == 'absent': + if have: + commands.append('no vrf definition %s' % want['name']) + elif state == 'present': + if not have: + commands.append('vrf definition %s' % want['name']) + + if want['rd'] is not None: + commands.append('rd %s' % want['rd']) + + if want['interfaces']: + for i in want['interfaces']: + commands.append('interface %s' % i) + commands.append('vrf forwarding %s' % want['name']) + else: + if want['rd'] is not None and want['rd'] != have['rd']: + commands.append('vrf definition %s' % want['name']) + commands.append('rd %s' % want['rd']) + + if want['interfaces']: + if not have['interfaces']: + for i in want['interfaces']: + commands.append('interface %s' % i) + commands.append('vrf forwarding %s' % want['name']) + elif set(want['interfaces']) != have['interfaces']: + missing_interfaces = list(set(want['interfaces']) - set(have['interfaces'])) + + for i in missing_interfaces: + commands.append('interface %s' % i) + commands.append('vrf forwarding %s' % want['name']) + + return commands + + +def map_config_to_obj(module): + obj = {} + output = run_commands(module, ['show vrf %s' % module.params['name']]) + lines = output[0].strip().splitlines() + + if len(lines) > 2: + splitted_line = re.split(r'\s{2,}', lines[2].strip()) + obj['name'] = splitted_line[0] + obj['rd'] = splitted_line[1] + obj['interfaces'] = None + + if len(splitted_line) > 4: + obj['interfaces'] = [] + for i in splitted_line[4].split(','): + obj['interfaces'].append(i.strip()) + + return obj + + +def map_params_to_obj(module): + return { + 'name': module.params['name'], + 'state': module.params['state'], + 'rd': module.params['rd'], + 'interfaces': module.params['interfaces'] + } + + +def main(): + """ main entry point for module execution + """ + argument_spec = dict( + name=dict(required=True), + interfaces=dict(type='list'), + rd=dict(), + collection=dict(), + purge=dict(default=False, type='bool'), + state=dict(default='present', choices=['present', 'absent']) + ) + + argument_spec.update(eos_argument_spec) + + module = AnsibleModule(argument_spec=argument_spec, + supports_check_mode=True) + + warnings = list() + check_args(module, warnings) + + result = {'changed': False} + + if warnings: + result['warnings'] = warnings + + want = map_params_to_obj(module) + have = map_config_to_obj(module) + + commands = map_obj_to_commands((want, have), module) + result['commands'] = commands + + if commands: + commit = not module.check_mode + response = load_config(module, commands, commit=commit) + if response.get('diff') and module._diff: + result['diff'] = {'prepared': response.get('diff')} + result['session_name'] = response.get('session') + result['changed'] = True + + module.exit_json(**result) + +if __name__ == '__main__': + main() diff --git a/test/integration/eos.yaml b/test/integration/eos.yaml index b1406644e1f..bb0a8d888e6 100644 --- a/test/integration/eos.yaml +++ b/test/integration/eos.yaml @@ -15,5 +15,6 @@ - { role: eos_facts, when: "limit_to in ['*', 'eos_facts']" } - { role: eos_eapi, debug: yes, when: "limit_to in ['*', 'eos_eapi']" } - { role: eos_system, debug: yes, when: "limit_to in ['*', 'eos_system']" } - - { role: eos_vlan, debug: yes, when: "limit_to in ['*', 'eos_vlan']" } - { role: eos_user, when: "limit_to in ['*', eos_user']" } + - { role: eos_vlan, debug: yes, when: "limit_to in ['*', 'eos_vlan']" } + - { role: eos_vrf, debug: yes, when: "limit_to in ['*', 'eos_vrf']" } diff --git a/test/integration/targets/eos_vrf/aliases b/test/integration/targets/eos_vrf/aliases new file mode 100644 index 00000000000..93151a8d9df --- /dev/null +++ b/test/integration/targets/eos_vrf/aliases @@ -0,0 +1 @@ +network/ci diff --git a/test/integration/targets/eos_vrf/defaults/main.yaml b/test/integration/targets/eos_vrf/defaults/main.yaml new file mode 100644 index 00000000000..5f709c5aac1 --- /dev/null +++ b/test/integration/targets/eos_vrf/defaults/main.yaml @@ -0,0 +1,2 @@ +--- +testcase: "*" diff --git a/test/integration/targets/eos_vrf/meta/main.yml b/test/integration/targets/eos_vrf/meta/main.yml new file mode 100644 index 00000000000..e5c8cd02f04 --- /dev/null +++ b/test/integration/targets/eos_vrf/meta/main.yml @@ -0,0 +1,2 @@ +dependencies: + - prepare_eos_tests diff --git a/test/integration/targets/eos_vrf/tasks/cli.yaml b/test/integration/targets/eos_vrf/tasks/cli.yaml new file mode 100644 index 00000000000..d675462dd02 --- /dev/null +++ b/test/integration/targets/eos_vrf/tasks/cli.yaml @@ -0,0 +1,15 @@ +--- +- name: collect all cli test cases + find: + paths: "{{ role_path }}/tests/cli" + patterns: "{{ testcase }}.yaml" + register: test_cases + +- name: set test_items + set_fact: test_items="{{ test_cases.files | map(attribute='path') | list }}" + +- name: run test case + include: "{{ test_case_to_run }}" + with_items: "{{ test_items }}" + loop_control: + loop_var: test_case_to_run diff --git a/test/integration/targets/eos_vrf/tasks/eapi.yaml b/test/integration/targets/eos_vrf/tasks/eapi.yaml new file mode 100644 index 00000000000..994d1a7c7da --- /dev/null +++ b/test/integration/targets/eos_vrf/tasks/eapi.yaml @@ -0,0 +1,33 @@ +--- +- name: collect all eapi test cases + find: + paths: "{{ role_path }}/tests/eapi" + patterns: "{{ testcase }}.yaml" + register: test_cases + +- name: set test_items + set_fact: test_items="{{ test_cases.files | map(attribute='path') | list }}" #" + +- name: enable eapi + eos_eapi: + enable_http: yes + enable_https: yes + enable_local_http: yes + enable_socket: yes + provider: "{{ cli }}" +# authorize: yes + +- name: run test case + include: "{{ test_case_to_run }}" + with_items: "{{ test_items }}" + loop_control: + loop_var: test_case_to_run + +- name: disable eapi + eos_eapi: + enable_http: no + enable_https: no + enable_local_http: no + enable_socket: no + provider: "{{ cli }}" +# authorize: yes diff --git a/test/integration/targets/eos_vrf/tasks/main.yaml b/test/integration/targets/eos_vrf/tasks/main.yaml new file mode 100644 index 00000000000..970e74171ea --- /dev/null +++ b/test/integration/targets/eos_vrf/tasks/main.yaml @@ -0,0 +1,3 @@ +--- +- { include: cli.yaml, tags: ['cli'] } +- { include: eapi.yaml, tags: ['eapi'] } diff --git a/test/integration/targets/eos_vrf/tests/cli/basic.yaml b/test/integration/targets/eos_vrf/tests/cli/basic.yaml new file mode 100644 index 00000000000..cedd930759e --- /dev/null +++ b/test/integration/targets/eos_vrf/tests/cli/basic.yaml @@ -0,0 +1,116 @@ +--- + +- name: setup - remove vrf + eos_vrf: + name: test + state: absent + authorize: yes + provider: "{{ cli }}" + +- name: Create vrf + eos_vrf: + name: test + rd: 1:200 + state: present + authorize: yes + provider: "{{ cli }}" + register: result + +- assert: + that: + - "result.changed == true" + - "'vrf definition test' in result.commands" + - "'rd 1:200' in result.commands" + # Ensure sessions contains epoc. Will fail after 18th May 2033 + - "'ansible_1' in result.session_name" + +- name: Create vrf again (idempotent) + eos_vrf: + name: test + rd: 1:200 + state: present + authorize: yes + provider: "{{ cli }}" + register: result + +- assert: + that: + - "result.changed == false" + - "result.commands | length == 0" + # Ensure sessions contains epoc. Will fail after 18th May 2033 + - "result.session_name is not defined" + +- name: Modify rd + eos_vrf: + name: test + rd: 1:201 + state: present + authorize: yes + provider: "{{ cli }}" + register: result + +- assert: + that: + - "result.changed == true" + - "'vrf definition test' in result.commands" + - "'rd 1:201' in result.commands" + # Ensure sessions contains epoc. Will fail after 18th May 2033 + - "'ansible_1' in result.session_name" + +- name: Modify rd again (idempotent) + eos_vrf: + name: test + rd: 1:201 + state: present + authorize: yes + provider: "{{ cli }}" + register: result + +- assert: + that: + - "result.changed == false" + - "result.commands | length == 0" + # Ensure sessions contains epoc. Will fail after 18th May 2033 + - "result.session_name is not defined" + +- name: Add Ethernet2 to vrf + eos_vrf: + name: test + rd: 1:201 + state: present + authorize: yes + interfaces: + - Ethernet2 + provider: "{{ cli }}" + register: result + +- assert: + that: + - "result.changed == true" + - "'interface Ethernet2' in result.commands" + - "'vrf forwarding test' in result.commands" + # Ensure sessions contains epoc. Will fail after 18th May 2033 + - "'ansible_1' in result.session_name" + +- name: Add Ethernet2 to vrf again (idempotent) + eos_vrf: + name: test + rd: 1:201 + state: present + authorize: yes + interfaces: + - Ethernet2 + provider: "{{ cli }}" + register: result + +- assert: + that: + - "result.changed == false" + - "result.commands | length == 0" + # Ensure sessions contains epoc. Will fail after 18th May 2033 + - "result.session_name is not defined" + + +# FIXME add in tests for everything defined in docs +# FIXME Test state:absent + test: +# FIXME Without powers ensure "privileged mode required"