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')