From 82d26c8c933612d7a27e7b2fcedd74b2599dcd4d Mon Sep 17 00:00:00 2001 From: Anil Kumar Muraleedharan Date: Thu, 28 Mar 2019 19:19:37 +0530 Subject: [PATCH] Lenovo cnos vrf (#54188) * Adding module cnos_vrf to manage VRF Configurations. * Update cnos_vrf.py * Adding Functional Tests, Unit Tests and Bug Fixes. * Fixing discrepancy in description against sample * Review comments incorporated * Review comments 2 Done * Update basic.yaml * Update test_cnos_vrf.py * Review comments 3 --- lib/ansible/modules/network/cnos/cnos_vrf.py | 334 ++++++++++++++++++ .../cnos_command/cnos_command_sample_hosts | 2 +- .../cnos_config/cnos_config_sample_hosts | 2 +- .../cnos_facts/cnos_facts_sample_hosts | 2 +- .../targets/cnos_lldp/cnos_lldp_sample_hosts | 14 + .../cnos_logging/cnos_logging_sample_hosts | 2 +- .../cnos_showrun/cnos_showrun_sample_hosts | 2 +- .../targets/cnos_user/cnos_user_sample_hosts | 2 +- .../targets/cnos_vlan/cnos_vlan_sample_hosts | 2 +- test/integration/targets/cnos_vrf/aliases | 2 + .../targets/cnos_vrf/cnos_vrf_sample_hosts | 14 + .../targets/cnos_vrf/defaults/main.yaml | 3 + .../targets/cnos_vrf/tasks/cli.yaml | 16 + .../targets/cnos_vrf/tasks/main.yaml | 2 + .../targets/cnos_vrf/tests/cli/basic.yaml | 250 +++++++++++++ .../network/cnos/fixtures/cnos_vrf_config.cfg | 176 +++++++++ .../modules/network/cnos/test_cnos_vrf.py | 71 ++++ 17 files changed, 889 insertions(+), 7 deletions(-) create mode 100644 lib/ansible/modules/network/cnos/cnos_vrf.py create mode 100644 test/integration/targets/cnos_lldp/cnos_lldp_sample_hosts create mode 100644 test/integration/targets/cnos_vrf/aliases create mode 100644 test/integration/targets/cnos_vrf/cnos_vrf_sample_hosts create mode 100644 test/integration/targets/cnos_vrf/defaults/main.yaml create mode 100644 test/integration/targets/cnos_vrf/tasks/cli.yaml create mode 100644 test/integration/targets/cnos_vrf/tasks/main.yaml create mode 100644 test/integration/targets/cnos_vrf/tests/cli/basic.yaml create mode 100644 test/units/modules/network/cnos/fixtures/cnos_vrf_config.cfg create mode 100644 test/units/modules/network/cnos/test_cnos_vrf.py diff --git a/lib/ansible/modules/network/cnos/cnos_vrf.py b/lib/ansible/modules/network/cnos/cnos_vrf.py new file mode 100644 index 00000000000..b3daf645f7d --- /dev/null +++ b/lib/ansible/modules/network/cnos/cnos_vrf.py @@ -0,0 +1,334 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type +# +# Copyright (C) 2019 Lenovo. +# (c) 2017, Ansible by Red Hat, inc +# This file is part of Ansible +# +# 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 . +# +# Module to work on management of local users on Lenovo CNOS Switches +# Lenovo Networking +# +ANSIBLE_METADATA = {'metadata_version': '1.1', + 'status': ['preview'], + 'supported_by': 'community'} + +DOCUMENTATION = """ +--- +module: cnos_vrf +version_added: "2.8" +author: "Anil Kumar Muraleedharan (@amuraleedhar)" +short_description: Manage VRFs on Lenovo CNOS network devices +description: + - This module provides declarative management of VRFs + on Lenovo CNOS network devices. +notes: + - Tested against CNOS 10.9.1 +options: + name: + description: + - Name of the VRF. + required: true + rd: + description: + - Route distinguisher of the VRF + interfaces: + description: + - Identifies the set of interfaces that + should be configured in the VRF. Interfaces must be routed + interfaces in order to be placed into a VRF. The name of interface + should be in expanded format and not abbreviated. + associated_interfaces: + description: + - This is a intent option and checks the operational state of the for + given vrf C(name) for associated interfaces. If the value in the + C(associated_interfaces) does not match with the operational state of + vrf interfaces on device it will result in failure. + aggregate: + description: List of VRFs contexts + purge: + description: + - Purge VRFs not defined in the I(aggregate) parameter. + default: no + type: bool + delay: + description: + - Time in seconds to wait before checking for the operational state on + remote device. This wait is applicable for operational state arguments. + default: 10 + state: + description: + - State of the VRF configuration. + default: present + choices: ['present', 'absent'] +""" + +EXAMPLES = """ +- name: Create vrf + cnos_vrf: + name: test + rd: 1:200 + interfaces: + - Ethernet1/33 + state: present + +- name: Delete VRFs + cnos_vrf: + name: test + state: absent + +- name: Create aggregate of VRFs with purge + cnos_vrf: + aggregate: + - { name: test4, rd: "1:204" } + - { name: test5, rd: "1:205" } + state: present + purge: yes + +- name: Delete aggregate of VRFs + cnos_vrf: + aggregate: + - name: test2 + - name: test3 + - name: test4 + - name: test5 + state: absent +""" + +RETURN = """ +commands: + description: The list of configuration mode commands to send to the device + returned: always + type: list + sample: + - vrf context test + - rd 1:100 + - interface Ethernet1/44 + - vrf member test +""" +import re +import time + +from copy import deepcopy + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.network.common.utils import remove_default_spec +from ansible.module_utils.network.cnos.cnos import load_config, run_commands +from ansible.module_utils.network.cnos.cnos import cnos_argument_spec, check_args + + +def search_obj_in_list(name, lst): + for o in lst: + if o['name'] == name: + return o + + +def map_obj_to_commands(updates, module): + commands = list() + want, have = updates + state = module.params['state'] + purge = module.params['purge'] + + for w in want: + name = w['name'] + rd = w['rd'] + interfaces = w['interfaces'] + + obj_in_have = search_obj_in_list(name, have) + + if name == 'default': + module.fail_json(msg='VRF context default is reserved') + elif len(name) > 63: + module.fail_json(msg='VRF name is too long') + if state == 'absent': + if name == 'management': + module.fail_json(msg='Management VRF context cannot be deleted') + if obj_in_have: + commands.append('no vrf context %s' % name) + elif state == 'present': + if not obj_in_have: + commands.append('vrf context %s' % name) + + if rd is not None: + commands.append('rd %s' % rd) + + if w['interfaces']: + for i in w['interfaces']: + commands.append('interface %s' % i) + commands.append('vrf member %s' % w['name']) + else: + if w['rd'] is not None and w['rd'] != obj_in_have['rd']: + commands.append('vrf context %s' % w['name']) + commands.append('rd %s' % w['rd']) + + if w['interfaces']: + if not obj_in_have['interfaces']: + for i in w['interfaces']: + commands.append('interface %s' % i) + commands.append('vrf member %s' % w['name']) + elif set(w['interfaces']) != obj_in_have['interfaces']: + missing_interfaces = list(set(w['interfaces']) - set(obj_in_have['interfaces'])) + + for i in missing_interfaces: + commands.append('interface %s' % i) + commands.append('vrf member %s' % w['name']) + + if purge: + for h in have: + obj_in_want = search_obj_in_list(h['name'], want) + if not obj_in_want: + commands.append('no vrf context %s' % h['name']) + + return commands + + +def map_config_to_obj(module): + objs = [] + output = run_commands(module, {'command': 'show vrf'}) + if output is None: + module.fail_json(msg='Could not fetch VRF details from device') + vrfText = output[0].strip() + vrfList = vrfText.split('VRF') + for vrfItem in vrfList: + if 'FIB ID' in vrfItem: + obj = dict() + list_of_words = vrfItem.split() + vrfName = list_of_words[0] + obj['name'] = vrfName[:-1] + obj['rd'] = list_of_words[list_of_words.index('RD') + 1] + start = False + obj['interfaces'] = [] + for intName in list_of_words: + if 'Interfaces' in intName: + start = True + if start is True: + if '!' not in intName and 'Interfaces' not in intName: + obj['interfaces'].append(intName.strip().lower()) + objs.append(obj) + + return objs + + +def map_params_to_obj(module): + obj = [] + aggregate = module.params.get('aggregate') + if aggregate: + for item in aggregate: + for key in item: + if item.get(key) is None: + item[key] = module.params[key] + + if item.get('interfaces'): + item['interfaces'] = [intf.replace(" ", "").lower() for intf in item.get('interfaces') if intf] + + if item.get('associated_interfaces'): + item['associated_interfaces'] = [intf.replace(" ", "").lower() for intf in item.get('associated_interfaces') if intf] + + obj.append(item.copy()) + else: + obj.append({ + 'name': module.params['name'], + 'state': module.params['state'], + 'rd': module.params['rd'], + 'interfaces': [intf.replace(" ", "").lower() for intf in module.params['interfaces']] if module.params['interfaces'] else [], + 'associated_interfaces': [intf.replace(" ", "").lower() for intf in + module.params['associated_interfaces']] if module.params['associated_interfaces'] else [] + + }) + + return obj + + +def check_declarative_intent_params(want, module, result): + have = None + is_delay = False + + for w in want: + if w.get('associated_interfaces') is None: + continue + + if result['changed'] and not is_delay: + time.sleep(module.params['delay']) + is_delay = True + + if have is None: + have = map_config_to_obj(module) + + for i in w['associated_interfaces']: + obj_in_have = search_obj_in_list(w['name'], have) + + if obj_in_have: + interfaces = obj_in_have.get('interfaces') + if interfaces is not None and i not in interfaces: + module.fail_json(msg="Interface %s not configured on vrf %s" % (i, w['name'])) + + +def main(): + """ main entry point for module execution + """ + element_spec = dict( + name=dict(), + interfaces=dict(type='list'), + associated_interfaces=dict(type='list'), + delay=dict(default=10, type='int'), + rd=dict(), + state=dict(default='present', choices=['present', 'absent']) + ) + + aggregate_spec = deepcopy(element_spec) + + # remove default in aggregate spec, to handle common arguments + remove_default_spec(aggregate_spec) + + argument_spec = dict( + aggregate=dict(type='list', elements='dict', options=aggregate_spec), + purge=dict(default=False, type='bool') + ) + + argument_spec.update(element_spec) + + required_one_of = [['name', 'aggregate']] + mutually_exclusive = [['name', 'aggregate']] + module = AnsibleModule(argument_spec=argument_spec, + required_one_of=required_one_of, + mutually_exclusive=mutually_exclusive, + 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: + if not module.check_mode: + load_config(module, commands) + result['changed'] = True + check_declarative_intent_params(want, module, result) + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/test/integration/targets/cnos_command/cnos_command_sample_hosts b/test/integration/targets/cnos_command/cnos_command_sample_hosts index c47d67b7d06..05c50f2567e 100644 --- a/test/integration/targets/cnos_command/cnos_command_sample_hosts +++ b/test/integration/targets/cnos_command/cnos_command_sample_hosts @@ -10,5 +10,5 @@ # Following you should specify IP Addresses details # Please change and with appropriate value for your switch. -[cnos_command] +[cnos_command_sample] 10.241.107.39 ansible_network_os=cnos ansible_ssh_user= ansible_ssh_pass= deviceType=g8272_cnos diff --git a/test/integration/targets/cnos_config/cnos_config_sample_hosts b/test/integration/targets/cnos_config/cnos_config_sample_hosts index 5e1f0d6ef34..4e61a815810 100644 --- a/test/integration/targets/cnos_config/cnos_config_sample_hosts +++ b/test/integration/targets/cnos_config/cnos_config_sample_hosts @@ -10,5 +10,5 @@ # Following you should specify IP Addresses details # Please change and with appropriate value for your switch. -[cnos_config] +[cnos_config_sample] 10.241.107.39 ansible_network_os=cnos ansible_ssh_user= ansible_ssh_pass= deviceType=g8272_cnos diff --git a/test/integration/targets/cnos_facts/cnos_facts_sample_hosts b/test/integration/targets/cnos_facts/cnos_facts_sample_hosts index 1f881f94dad..a1356ed7c46 100644 --- a/test/integration/targets/cnos_facts/cnos_facts_sample_hosts +++ b/test/integration/targets/cnos_facts/cnos_facts_sample_hosts @@ -10,5 +10,5 @@ # Following you should specify IP Addresses details # Please change and with appropriate value for your switch. -[cnos_facts] +[cnos_facts_sample] 10.241.107.39 ansible_network_os=cnos ansible_ssh_user= ansible_ssh_pass= deviceType=g8272_cnos diff --git a/test/integration/targets/cnos_lldp/cnos_lldp_sample_hosts b/test/integration/targets/cnos_lldp/cnos_lldp_sample_hosts new file mode 100644 index 00000000000..5ce92379181 --- /dev/null +++ b/test/integration/targets/cnos_lldp/cnos_lldp_sample_hosts @@ -0,0 +1,14 @@ +# You have to paste this dummy information in /etc/ansible/hosts +# Notes: +# - Comments begin with the '#' character +# - Blank lines are ignored +# - Groups of hosts are delimited by [header] elements +# - You can enter hostnames or ip Addresses +# - A hostname/ip can be a member of multiple groups +# +# In the /etc/ansible/hosts file u have to enter [cnos_lldp_sample] tag +# Following you should specify IP Addresses details +# Please change and with appropriate value for your switch. + +[cnos_lldp_sample] +10.241.107.39 ansible_network_os=cnos ansible_ssh_user= ansible_ssh_pass= deviceType=g8272_cnos diff --git a/test/integration/targets/cnos_logging/cnos_logging_sample_hosts b/test/integration/targets/cnos_logging/cnos_logging_sample_hosts index 6cf095132ac..e966047ae02 100644 --- a/test/integration/targets/cnos_logging/cnos_logging_sample_hosts +++ b/test/integration/targets/cnos_logging/cnos_logging_sample_hosts @@ -6,7 +6,7 @@ # - You can enter hostnames or ip Addresses # - A hostname/ip can be a member of multiple groups # -# In the /etc/ansible/hosts file u have to enter [cnos_portchannel_sample] tag +# In the /etc/ansible/hosts file u have to enter [cnos_logging_sample] tag # Following you should specify IP Addresses details # Please change and with appropriate value for your switch. diff --git a/test/integration/targets/cnos_showrun/cnos_showrun_sample_hosts b/test/integration/targets/cnos_showrun/cnos_showrun_sample_hosts index 5203e097529..8257765dc9b 100644 --- a/test/integration/targets/cnos_showrun/cnos_showrun_sample_hosts +++ b/test/integration/targets/cnos_showrun/cnos_showrun_sample_hosts @@ -6,7 +6,7 @@ # - You can enter hostnames or ip Addresses # - A hostname/ip can be a member of multiple groups # -# In the /etc/ansible/hosts file u have to enter [cnos_facts_sample] tag +# In the /etc/ansible/hosts file u have to enter [cnos_showrun_sample] tag # Following you should specify IP Addresses details # Please change and with appropriate value for your switch. diff --git a/test/integration/targets/cnos_user/cnos_user_sample_hosts b/test/integration/targets/cnos_user/cnos_user_sample_hosts index 7274107e49e..0d18ec3063d 100644 --- a/test/integration/targets/cnos_user/cnos_user_sample_hosts +++ b/test/integration/targets/cnos_user/cnos_user_sample_hosts @@ -10,5 +10,5 @@ # Following you should specify IP Addresses details # Please change and with appropriate value for your switch. -[cnos_sample_sample] +[cnos_user_sample] 10.241.107.39 ansible_network_os=cnos ansible_ssh_user=admin ansible_ssh_pass=admin diff --git a/test/integration/targets/cnos_vlan/cnos_vlan_sample_hosts b/test/integration/targets/cnos_vlan/cnos_vlan_sample_hosts index 5d42a551740..be57bc951b6 100644 --- a/test/integration/targets/cnos_vlan/cnos_vlan_sample_hosts +++ b/test/integration/targets/cnos_vlan/cnos_vlan_sample_hosts @@ -10,5 +10,5 @@ # Following you should specify IP Addresses details # Please change and with appropriate value for your switch. -[cnos] +[cnos_vlan_sample] 10.241.107.39 ansible_network_os=cnos ansible_ssh_user= ansible_ssh_pass= diff --git a/test/integration/targets/cnos_vrf/aliases b/test/integration/targets/cnos_vrf/aliases new file mode 100644 index 00000000000..be010d923f4 --- /dev/null +++ b/test/integration/targets/cnos_vrf/aliases @@ -0,0 +1,2 @@ +# No Lenovo Switch simulator yet, so not enabled +unsupported diff --git a/test/integration/targets/cnos_vrf/cnos_vrf_sample_hosts b/test/integration/targets/cnos_vrf/cnos_vrf_sample_hosts new file mode 100644 index 00000000000..696911deca4 --- /dev/null +++ b/test/integration/targets/cnos_vrf/cnos_vrf_sample_hosts @@ -0,0 +1,14 @@ +# You have to paste this dummy information in /etc/ansible/hosts +# Notes: +# - Comments begin with the '#' character +# - Blank lines are ignored +# - Groups of hosts are delimited by [header] elements +# - You can enter hostnames or ip Addresses +# - A hostname/ip can be a member of multiple groups +# +# In the /etc/ansible/hosts file u have to enter [cnos_vrf_sample] tag +# Following you should specify IP Addresses details +# Please change and with appropriate value for your switch. + +[cnos_vrf_sample] +10.241.107.39 ansible_network_os=cnos ansible_ssh_user= ansible_ssh_pass= test_interface=ethernet1/33 test_interface2=ethernet1/44 diff --git a/test/integration/targets/cnos_vrf/defaults/main.yaml b/test/integration/targets/cnos_vrf/defaults/main.yaml new file mode 100644 index 00000000000..9ef5ba51651 --- /dev/null +++ b/test/integration/targets/cnos_vrf/defaults/main.yaml @@ -0,0 +1,3 @@ +--- +testcase: "*" +test_items: [] diff --git a/test/integration/targets/cnos_vrf/tasks/cli.yaml b/test/integration/targets/cnos_vrf/tasks/cli.yaml new file mode 100644 index 00000000000..87a42971bbc --- /dev/null +++ b/test/integration/targets/cnos_vrf/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 cases (connection=network_cli) + include: "{{ test_case_to_run }} ansible_connection=network_cli" + with_items: "{{ test_items }}" + loop_control: + loop_var: test_case_to_run diff --git a/test/integration/targets/cnos_vrf/tasks/main.yaml b/test/integration/targets/cnos_vrf/tasks/main.yaml new file mode 100644 index 00000000000..415c99d8b12 --- /dev/null +++ b/test/integration/targets/cnos_vrf/tasks/main.yaml @@ -0,0 +1,2 @@ +--- +- { include: cli.yaml, tags: ['cli'] } diff --git a/test/integration/targets/cnos_vrf/tests/cli/basic.yaml b/test/integration/targets/cnos_vrf/tests/cli/basic.yaml new file mode 100644 index 00000000000..81d051137a2 --- /dev/null +++ b/test/integration/targets/cnos_vrf/tests/cli/basic.yaml @@ -0,0 +1,250 @@ +--- +- name: setup - remove vrf + cnos_vrf: + name: "{{ item }}" + state: absent + become: yes + with_items: + - test + - test1 + - test2 + - test3 + - test4 + - test5 + +- name: Create vrf + cnos_vrf: + name: test + rd: 1:200 + state: present + become: yes + register: result + +- assert: + that: + - "result.changed == true" + - "'vrf context test' in result.commands" + - "'rd 1:200' in result.commands" + +- name: Create vrf again (idempotent) + cnos_vrf: + name: test + rd: 1:200 + state: present + become: yes + register: result + +- assert: + that: + - "result.changed == false" + - "result.commands | length == 0" + +- name: Modify rd + cnos_vrf: + name: test + rd: 1:201 + state: present + become: yes + register: result + +- assert: + that: + - "result.changed == true" + - "'vrf context test' in result.commands" + - "'rd 1:201' in result.commands" + +- name: Modify rd again (idempotent) + cnos_vrf: + name: test + rd: 1:201 + state: present + become: yes + register: result + +- assert: + that: + - "result.changed == false" + - "result.commands | length == 0" + +- name: Add Ethernet1/33 to vrf and check interface assigned state + cnos_vrf: + name: test + rd: 1:201 + state: present + interfaces: + - Ethernet1/33 + associated_interfaces: + - Ethernet1/33 + become: yes + register: result + +- assert: + that: + - "result.changed == true" + - "'interface ethernet1/33' in result.commands" + - "'vrf member test' in result.commands" + +- name: Add Ethernet1/33 to vrf again (idempotent) + cnos_vrf: + name: test + rd: 1:201 + state: present + interfaces: + - ethernet 1/33 # interface name modified to test case insensitive and space scenario + become: yes + register: result + +- assert: + that: + - "result.changed == false" + - "result.commands | length == 0" + +- name: Add multiple interfaces to vrf + cnos_vrf: + name: test1 + rd: 1:202 + state: present + interfaces: + - loopback 1 + - loopback 2 + - loopback 3 + - loopback 4 + - loopback 5 + - loopback 6 + become: yes + register: result + +- assert: + that: + - "result.changed == true" + - "'interface loopback1' in result.commands" + - "'vrf member test1' in result.commands" + - "'interface loopback2' in result.commands" + - "'vrf member test1' in result.commands" + - "'interface loopback3' in result.commands" + - "'vrf member test1' in result.commands" + - "'interface loopback4' in result.commands" + - "'vrf member test1' in result.commands" + - "'interface loopback5' in result.commands" + - "'vrf member test1' in result.commands" + - "'interface loopback6' in result.commands" + - "'vrf member test1' in result.commands" + +- name: Add multiple interfaces to vrf (idempotent) + cnos_vrf: + name: test1 + rd: 1:202 + state: present + interfaces: + - loopback 1 + - loopback 2 + - loopback 3 + - loopback 4 + - loopback 5 + - loopback 6 + become: yes + register: result + +- assert: + that: + - "result.changed == false" + - "result.commands | length == 0" + +- name: setup - remove vrf + cnos_vrf: + name: "{{ item }}" + state: absent + become: yes + with_items: + - test1 + - test2 + - test3 + +- name: Create aggregate of VRFs + cnos_vrf: + aggregate: + - { name: test2, rd: "1:202" } + - { name: test3, rd: "1:203" } + state: present + register: result + +- assert: + that: + - "result.changed == true" + - "'vrf context test2' in result.commands" + - "'rd 1:202' in result.commands" + - "'vrf context test3' in result.commands" + - "'rd 1:203' in result.commands" + +- name: Create aggregate of VRFs again (idempotent) + cnos_vrf: + aggregate: + - { name: test2, rd: "1:202" } + - { name: test3, rd: "1:203" } + state: present + become: yes + register: result + +- assert: + that: + - "result.changed == false" + - "result.commands | length == 0" + +- name: Create aggregate of VRFs with purge + cnos_vrf: + aggregate: + - { name: test4, rd: "1:204" } + - { name: test5, rd: "1:205" } + state: present + purge: yes + become: yes + register: result + +- assert: + that: + - "result.changed == true" + - "'vrf context test4' in result.commands" + - "'rd 1:204' in result.commands" + - "'vrf context test5' in result.commands" + - "'rd 1:205' in result.commands" + - "'no vrf context test' in result.commands" + - "'no vrf context test2' in result.commands" + - "'no vrf context test3' in result.commands" + +- name: Delete VRFs + cnos_vrf: + name: test + state: absent + become: yes + +- name: Delete VRFs again (idempotent) + cnos_vrf: + name: test + state: absent + become: yes + +- name: Delete aggregate of VRFs + cnos_vrf: + aggregate: + - { name: test1 } + - { name: test2 } + - { name: test3 } + - { name: test4 } + - { name: test5 } + state: absent + become: yes + +- name: Delete VRFs again (idempotent) + cnos_vrf: + aggregate: + - { name: test1 } + - { name: test2 } + - { name: test3 } + - { name: test4 } + - { name: test5 } + state: absent + become: yes + +- assert: + that: + - "result.changed == true" diff --git a/test/units/modules/network/cnos/fixtures/cnos_vrf_config.cfg b/test/units/modules/network/cnos/fixtures/cnos_vrf_config.cfg new file mode 100644 index 00000000000..78ce6c370f1 --- /dev/null +++ b/test/units/modules/network/cnos/fixtures/cnos_vrf_config.cfg @@ -0,0 +1,176 @@ +Maximum number of vrfs allowed: 65 +VRF default, FIB ID 0 +Router ID: 20.141.2.1 (automatic) +RD 0:0 +Interfaces: + Vlan1 + Vlan2 + Vlan13 + loopback0 + Ethernet1/5 + Ethernet1/6 + Ethernet1/7 + Ethernet1/8 + Ethernet1/9 + Ethernet1/11 + Ethernet1/12 + Ethernet1/13 + Ethernet1/44 + po1 + po2 + po3 + po4 + po6 + po7 + po8 + po9 + po10 + po11 + po12 + po13 + po14 + po15 + po16 + po17 + po18 + po19 + po21 + po22 + po23 + po24 + po25 + po26 + po27 + po28 + po29 + po30 + po31 + po32 + po33 + po34 + po35 + po36 + po37 + po38 + po39 + po40 + po41 + po42 + po43 + po44 + po45 + po46 + po47 + po48 + po49 + po50 + po51 + po52 + po53 + po54 + po55 + po56 + po57 + po58 + po59 + po60 + po61 + po62 + po63 + po64 + po65 + po66 + po67 + po1001 + po1002 + po1003 + po1004 + Ethernet1/1 + Ethernet1/2 + Ethernet1/3 + Ethernet1/4 + Ethernet1/10 + Ethernet1/14 + Ethernet1/15 + Ethernet1/16 + Ethernet1/17 + Ethernet1/18 + Ethernet1/19 + Ethernet1/20 + Ethernet1/21 + Ethernet1/22 + Ethernet1/23 + Ethernet1/24 + Ethernet1/25 + Ethernet1/26 + Ethernet1/27 + Ethernet1/28 + Ethernet1/29 + Ethernet1/30 + Ethernet1/31 + Ethernet1/32 + Ethernet1/34 + Ethernet1/35 + Ethernet1/36 + Ethernet1/37 + Ethernet1/38 + Ethernet1/39 + Ethernet1/40 + Ethernet1/41 + Ethernet1/42 + Ethernet1/43 + Ethernet1/45 + Ethernet1/46 + Ethernet1/47 + Ethernet1/48 + Ethernet1/49 + Ethernet1/50 + Ethernet1/51 + Ethernet1/52 + Ethernet1/53 + Ethernet1/54 +! +VRF management, FIB ID 1 +Router ID: 10.241.107.39 (automatic) +RD 0:0 +Interfaces: + mgmt0 +! +VRF test, FIB ID 2 +Router ID is not set +RD 1:201 +Interfaces: + Ethernet1/33 +! +VRF test1, FIB ID 3 +Router ID is not set +RD 1:202 +Interfaces: + loopback1 + loopback2 + loopback3 + loopback4 + loopback5 + loopback6 +! +VRF test2, FIB ID 4 +Router ID is not set +RD 0:0 +Interfaces: +! +VRF test3, FIB ID 5 +Router ID is not set +RD 1:203 +Interfaces: +! +VRF test4, FIB ID 6 +Router ID is not set +RD 1:204 +Interfaces: +! +VRF test5, FIB ID 7 +Router ID is not set +RD 1:205 +Interfaces: +! + diff --git a/test/units/modules/network/cnos/test_cnos_vrf.py b/test/units/modules/network/cnos/test_cnos_vrf.py new file mode 100644 index 00000000000..7cfa8a02542 --- /dev/null +++ b/test/units/modules/network/cnos/test_cnos_vrf.py @@ -0,0 +1,71 @@ +# +# (c) 2019 Lenovo. +# +# This file is part of Ansible +# +# 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 . +# +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +from units.compat.mock import patch +from ansible.modules.network.cnos import cnos_vrf +from units.modules.utils import set_module_args +from .cnos_module import TestCnosModule, load_fixture + + +class TestCnosVrfModule(TestCnosModule): + + module = cnos_vrf + + def setUp(self): + super(TestCnosVrfModule, self).setUp() + + self.mock_load_config = patch('ansible.modules.network.cnos.cnos_vrf.load_config') + self.load_config = self.mock_load_config.start() + + self.mock_run_commands = patch('ansible.modules.network.cnos.cnos_vrf.run_commands') + self.run_commands = self.mock_run_commands.start() + + def tearDown(self): + super(TestCnosVrfModule, self).tearDown() + self.mock_load_config.stop() + self.mock_run_commands.stop() + + def load_fixtures(self, commands=None): + config_file = 'cnos_vrf_config.cfg' + self.load_config.return_value = load_fixture(config_file) + self.run_commands.return_value = load_fixture(config_file) + + def test_cnos_vrf_present(self): + set_module_args(dict(name='test1', state='present')) + self.execute_module(changed=True, commands=['vrf context test1']) + + def test_cnos_vrf_present_management(self): + set_module_args(dict(name='management', state='present')) + self.execute_module(changed=True, commands=['vrf context management']) + + def test_cnos_vrf_absent_management(self): + set_module_args(dict(name='management', state='absent')) + result = self.execute_module(failed=True) + self.assertEqual(result['msg'], 'Management VRF context cannot be deleted') + + def test_cnos_vrf_absent_no_change(self): + set_module_args(dict(name='test1', state='absent')) + self.execute_module(changed=False, commands=[]) + + def test_cnos_vrf_default(self): + set_module_args(dict(name='default', state='present')) + result = self.execute_module(failed=True) + self.assertEqual(result['msg'], 'VRF context default is reserved')