From b3e8c48d4b0928ffb9854ddff94d78e52ccf5c3b Mon Sep 17 00:00:00 2001 From: Ricardo Carrillo Cruz Date: Wed, 7 Jun 2017 23:47:28 +0200 Subject: [PATCH] New module: Add support for Arista EOS vlan (network/eos/eos_vlan) (#25355) * WIP Add eos_vlan module * Fix docstrings * Fix pep8 issues * Add active/suspend states logic * Add integration tests for eos_vlan * Fix map_config_to_obj on EAPI * Sixify iteritems * Add platform agnostic net_vlan integration tests --- lib/ansible/modules/network/eos/eos_vlan.py | 196 ++++++++++++++++++ test/integration/eos.yaml | 1 + test/integration/platform_agnostic.yaml | 1 + .../targets/eos_vlan/defaults/main.yaml | 2 + .../targets/eos_vlan/meta/main.yml | 2 + .../targets/eos_vlan/tasks/cli.yaml | 15 ++ .../targets/eos_vlan/tasks/eapi.yaml | 33 +++ .../targets/eos_vlan/tasks/main.yaml | 3 + .../targets/eos_vlan/tests/cli/basic.yaml | 68 ++++++ .../targets/eos_vlan/tests/eapi/basic.yaml | 68 ++++++ test/integration/targets/net_vlan/aliases | 1 + .../targets/net_vlan/defaults/main.yaml | 2 + .../targets/net_vlan/tasks/cli.yaml | 16 ++ .../targets/net_vlan/tasks/main.yaml | 2 + .../targets/net_vlan/tests/cli/basic.yaml | 4 + .../targets/net_vlan/tests/eos/basic.yaml | 68 ++++++ 16 files changed, 482 insertions(+) create mode 100644 lib/ansible/modules/network/eos/eos_vlan.py create mode 100644 test/integration/targets/eos_vlan/defaults/main.yaml create mode 100644 test/integration/targets/eos_vlan/meta/main.yml create mode 100644 test/integration/targets/eos_vlan/tasks/cli.yaml create mode 100644 test/integration/targets/eos_vlan/tasks/eapi.yaml create mode 100644 test/integration/targets/eos_vlan/tasks/main.yaml create mode 100644 test/integration/targets/eos_vlan/tests/cli/basic.yaml create mode 100644 test/integration/targets/eos_vlan/tests/eapi/basic.yaml create mode 100644 test/integration/targets/net_vlan/aliases create mode 100644 test/integration/targets/net_vlan/defaults/main.yaml create mode 100644 test/integration/targets/net_vlan/tasks/cli.yaml create mode 100644 test/integration/targets/net_vlan/tasks/main.yaml create mode 100644 test/integration/targets/net_vlan/tests/cli/basic.yaml create mode 100644 test/integration/targets/net_vlan/tests/eos/basic.yaml diff --git a/lib/ansible/modules/network/eos/eos_vlan.py b/lib/ansible/modules/network/eos/eos_vlan.py new file mode 100644 index 0000000000..f3f43df1de --- /dev/null +++ b/lib/ansible/modules/network/eos/eos_vlan.py @@ -0,0 +1,196 @@ +#!/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_vlan +version_added: "2.4" +author: "Ricardo Carrillo Cruz (@rcarrillocruz)" +short_description: Manage VLANs on Arista EOS network devices +description: + - This module provides declarative management of VLANs + on Arista EOS network devices. +options: + name: + description: + - Name of the VLAN. + vlan_id: + description: + - ID of the VLAN. + required: true + interfaces: + description: + - List of interfaces to check the VLAN has been + configured correctly. + collection: + description: List of VLANs definitions + purge: + description: + - Purge VLANs not defined in the collections parameter. + default: no + state: + description: + - State of the VLAN configuration. + default: present + choices: ['present', 'absent', 'active', 'suspend'] +""" + +EXAMPLES = """ +""" + +RETURN = """ +commands: + description: The list of configuration mode commands to send to the device + returned: always + type: list + sample: + - vlan 20 + - name test-vlan +""" +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 vlan %s' % want['vlan_id']) + elif state == 'present': + if not have or want['name'] != have['name']: + commands.append('vlan %s' % want['vlan_id']) + commands.append('name %s' % want['name']) + else: + if not have: + commands.append('vlan %s' % want['vlan_id']) + commands.append('name %s' % want['name']) + commands.append('state %s' % want['state']) + elif have['name'] != want['name'] or have['state'] != want['state']: + commands.append('vlan %s' % want['vlan_id']) + + if have['name'] != want['name']: + commands.append('name %s' % want['name']) + + if have['state'] != want['state']: + commands.append('state %s' % want['state']) + + return commands + + +def map_config_to_obj(module): + obj = {} + output = run_commands(module, ['show vlan']) + + if isinstance(output[0], str): + for l in output[0].strip().splitlines()[2:]: + split_line = l.split() + vlan_id = split_line[0] + name = split_line[1] + status = split_line[2] + + if vlan_id == str(module.params['vlan_id']): + obj['vlan_id'] = vlan_id + obj['name'] = name + obj['state'] = status + if obj['state'] == 'suspended': + obj['state'] = 'suspend' + break + else: + for k, v in iteritems(output[0]['vlans']): + vlan_id = k + name = v['name'] + status = v['status'] + + if vlan_id == str(module.params['vlan_id']): + obj['vlan_id'] = vlan_id + obj['name'] = name + obj['state'] = status + if obj['state'] == 'suspended': + obj['state'] = 'suspend' + break + + return obj + + +def map_params_to_obj(module): + return { + 'vlan_id': str(module.params['vlan_id']), + 'name': module.params['name'], + 'state': module.params['state'] + } + + +def main(): + """ main entry point for module execution + """ + argument_spec = dict( + vlan_id=dict(required=True, type='int'), + name=dict(), + interfaces=dict(), + collection=dict(), + purge=dict(default=False, type='bool'), + state=dict(default='present', + choices=['present', 'absent', 'active', 'suspend']) + ) + + 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 21d7d6a6c5..46d60ddf20 100644 --- a/test/integration/eos.yaml +++ b/test/integration/eos.yaml @@ -15,3 +15,4 @@ - { 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']" } diff --git a/test/integration/platform_agnostic.yaml b/test/integration/platform_agnostic.yaml index da11626c07..7f5369f07a 100644 --- a/test/integration/platform_agnostic.yaml +++ b/test/integration/platform_agnostic.yaml @@ -12,3 +12,4 @@ - { role: net_banner, when: "limit_to in ['*', 'net_banner']" } - { role: net_command, when: "limit_to in ['*', 'net_command']" } - { role: net_user, when: "limit_to_in ['*', 'net_user']" } + - { role: net_vlan, when: "limit_to in ['*', 'net_vlan']" } diff --git a/test/integration/targets/eos_vlan/defaults/main.yaml b/test/integration/targets/eos_vlan/defaults/main.yaml new file mode 100644 index 0000000000..5f709c5aac --- /dev/null +++ b/test/integration/targets/eos_vlan/defaults/main.yaml @@ -0,0 +1,2 @@ +--- +testcase: "*" diff --git a/test/integration/targets/eos_vlan/meta/main.yml b/test/integration/targets/eos_vlan/meta/main.yml new file mode 100644 index 0000000000..e5c8cd02f0 --- /dev/null +++ b/test/integration/targets/eos_vlan/meta/main.yml @@ -0,0 +1,2 @@ +dependencies: + - prepare_eos_tests diff --git a/test/integration/targets/eos_vlan/tasks/cli.yaml b/test/integration/targets/eos_vlan/tasks/cli.yaml new file mode 100644 index 0000000000..d675462dd0 --- /dev/null +++ b/test/integration/targets/eos_vlan/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_vlan/tasks/eapi.yaml b/test/integration/targets/eos_vlan/tasks/eapi.yaml new file mode 100644 index 0000000000..994d1a7c7d --- /dev/null +++ b/test/integration/targets/eos_vlan/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_vlan/tasks/main.yaml b/test/integration/targets/eos_vlan/tasks/main.yaml new file mode 100644 index 0000000000..970e74171e --- /dev/null +++ b/test/integration/targets/eos_vlan/tasks/main.yaml @@ -0,0 +1,3 @@ +--- +- { include: cli.yaml, tags: ['cli'] } +- { include: eapi.yaml, tags: ['eapi'] } diff --git a/test/integration/targets/eos_vlan/tests/cli/basic.yaml b/test/integration/targets/eos_vlan/tests/cli/basic.yaml new file mode 100644 index 0000000000..fb44fd5495 --- /dev/null +++ b/test/integration/targets/eos_vlan/tests/cli/basic.yaml @@ -0,0 +1,68 @@ +--- + +- name: setup - remove vlan + eos_vlan: + vlan_id: 4000 + name: test-vlan + state: absent + authorize: yes + provider: "{{ cli }}" + +- name: Create vlan + eos_vlan: + vlan_id: 4000 + name: test-vlan + state: present + authorize: yes + provider: "{{ cli }}" + register: result + +- debug: + msg: "{{ result }}" + +- assert: + that: + - "result.changed == true" + - "'vlan 4000' in result.commands" + - "'name test-vlan' in result.commands" + # Ensure sessions contains epoc. Will fail after 18th May 2033 + - "'ansible_1' in result.session_name" + +- name: Create vlan again (idempotent) + eos_vlan: + vlan_id: 4000 + name: test-vlan + 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: Change vlan name and state + eos_vlan: + vlan_id: 4000 + name: test-vlan2 + state: suspend + authorize: yes + provider: "{{ cli }}" + register: result + +- assert: + that: + - "result.changed == true" + - "'vlan 4000' in result.commands" + - "'name test-vlan2' in result.commands" + - "'state suspend' in result.commands" + # Ensure sessions contains epoc. Will fail after 18th May 2033 + - "'ansible_1' in result.session_name" + + +# FIXME add in tests for everything defined in docs +# FIXME Test state:absent + test: +# FIXME Without powers ensure "privileged mode required" diff --git a/test/integration/targets/eos_vlan/tests/eapi/basic.yaml b/test/integration/targets/eos_vlan/tests/eapi/basic.yaml new file mode 100644 index 0000000000..17193e173e --- /dev/null +++ b/test/integration/targets/eos_vlan/tests/eapi/basic.yaml @@ -0,0 +1,68 @@ +--- + +- name: setup - remove vlan + eos_vlan: + vlan_id: 4000 + name: test-vlan + state: absent + authorize: yes + provider: "{{ eapi }}" + +- name: Create vlan + eos_vlan: + vlan_id: 4000 + name: test-vlan + state: present + authorize: yes + provider: "{{ eapi }}" + register: result + +- debug: + msg: "{{ result }}" + +- assert: + that: + - "result.changed == true" + - "'vlan 4000' in result.commands" + - "'name test-vlan' in result.commands" + # Ensure sessions contains epoc. Will fail after 18th May 2033 + - "'ansible_1' in result.session_name" + +- name: Create vlan again (idempotent) + eos_vlan: + vlan_id: 4000 + name: test-vlan + state: present + authorize: yes + provider: "{{ eapi }}" + 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: Change vlan name and state + eos_vlan: + vlan_id: 4000 + name: test-vlan2 + state: suspend + authorize: yes + provider: "{{ eapi }}" + register: result + +- assert: + that: + - "result.changed == true" + - "'vlan 4000' in result.commands" + - "'name test-vlan2' in result.commands" + - "'state suspend' in result.commands" + # Ensure sessions contains epoc. Will fail after 18th May 2033 + - "'ansible_1' in result.session_name" + + +# FIXME add in tests for everything defined in docs +# FIXME Test state:absent + test: +# FIXME Without powers ensure "privileged mode required" diff --git a/test/integration/targets/net_vlan/aliases b/test/integration/targets/net_vlan/aliases new file mode 100644 index 0000000000..93151a8d9d --- /dev/null +++ b/test/integration/targets/net_vlan/aliases @@ -0,0 +1 @@ +network/ci diff --git a/test/integration/targets/net_vlan/defaults/main.yaml b/test/integration/targets/net_vlan/defaults/main.yaml new file mode 100644 index 0000000000..5f709c5aac --- /dev/null +++ b/test/integration/targets/net_vlan/defaults/main.yaml @@ -0,0 +1,2 @@ +--- +testcase: "*" diff --git a/test/integration/targets/net_vlan/tasks/cli.yaml b/test/integration/targets/net_vlan/tasks/cli.yaml new file mode 100644 index 0000000000..46d86dd698 --- /dev/null +++ b/test/integration/targets/net_vlan/tasks/cli.yaml @@ -0,0 +1,16 @@ +--- +- name: collect all cli test cases + find: + paths: "{{ role_path }}/tests/cli" + patterns: "{{ testcase }}.yaml" + register: test_cases + delegate_to: localhost + +- 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/net_vlan/tasks/main.yaml b/test/integration/targets/net_vlan/tasks/main.yaml new file mode 100644 index 0000000000..415c99d8b1 --- /dev/null +++ b/test/integration/targets/net_vlan/tasks/main.yaml @@ -0,0 +1,2 @@ +--- +- { include: cli.yaml, tags: ['cli'] } diff --git a/test/integration/targets/net_vlan/tests/cli/basic.yaml b/test/integration/targets/net_vlan/tests/cli/basic.yaml new file mode 100644 index 0000000000..b1bec77f7c --- /dev/null +++ b/test/integration/targets/net_vlan/tests/cli/basic.yaml @@ -0,0 +1,4 @@ +--- + +- include: "{{ role_path }}/tests/eos/basic.yaml" + when: hostvars[inventory_hostname]['ansible_network_os'] == 'eos' diff --git a/test/integration/targets/net_vlan/tests/eos/basic.yaml b/test/integration/targets/net_vlan/tests/eos/basic.yaml new file mode 100644 index 0000000000..42442c5f5c --- /dev/null +++ b/test/integration/targets/net_vlan/tests/eos/basic.yaml @@ -0,0 +1,68 @@ +--- + +- name: setup - remove vlan + eos_vlan: + vlan_id: 4000 + name: test-vlan + state: absent + authorize: yes + provider: "{{ cli }}" + +- name: Create vlan + net_vlan: + vlan_id: 4000 + name: test-vlan + state: present + authorize: yes + provider: "{{ cli }}" + register: result + +- debug: + msg: "{{ result }}" + +- assert: + that: + - "result.changed == true" + - "'vlan 4000' in result.commands" + - "'name test-vlan' in result.commands" + # Ensure sessions contains epoc. Will fail after 18th May 2033 + - "'ansible_1' in result.session_name" + +- name: Create vlan again (idempotent) + net_vlan: + vlan_id: 4000 + name: test-vlan + 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: Change vlan name and state + net_vlan: + vlan_id: 4000 + name: test-vlan2 + state: suspend + authorize: yes + provider: "{{ cli }}" + register: result + +- assert: + that: + - "result.changed == true" + - "'vlan 4000' in result.commands" + - "'name test-vlan2' in result.commands" + - "'state suspend' in result.commands" + # Ensure sessions contains epoc. Will fail after 18th May 2033 + - "'ansible_1' in result.session_name" + + +# FIXME add in tests for everything defined in docs +# FIXME Test state:absent + test: +# FIXME Without powers ensure "privileged mode required"