Resource module for IOS ACL_Interfaces (#66746)

* acl_interfaces
This commit is contained in:
Sumit Jaiswal 2020-02-28 16:29:18 +05:30 committed by GitHub
parent 08fc9f63b6
commit 6629b9feef
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
28 changed files with 2146 additions and 2 deletions

View file

@ -0,0 +1,66 @@
#
# -*- coding: utf-8 -*-
# Copyright 2019 Red Hat
# GNU General Public License v3.0+
# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
#############################################
# WARNING #
#############################################
#
# This file is auto generated by the resource
# module builder playbook.
#
# Do not edit this file manually.
#
# Changes to this file will be over written
# by the resource module builder.
#
# Changes should be made in the model used to
# generate this file or in the resource module
# builder template.
#
#############################################
"""
The arg spec for the ios_acl_interfaces module
"""
from __future__ import absolute_import, division, print_function
__metaclass__ = type
class Acl_InterfacesArgs(object):
"""The arg spec for the ios_acl_interfaces module
"""
argument_spec = {
'config': {
'elements': 'dict',
'options': {
'name': {'required': True, 'type': 'str'},
'access_groups': {
'type': 'list',
'elements': 'dict',
'options': {
'afi': {'required': True, 'choices': ['ipv4', 'ipv6'], 'type': 'str'},
'acls': {
'type': 'list',
'elements': 'dict',
'options': {
'name': {'required': True, 'type': 'str'},
'direction': {'required': True, 'choices': ['in', 'out'], 'type': 'str'}
}
}
}
}
},
'type': 'list'
},
'running_config': {'type': 'str'},
'state': {
'choices': ['merged', 'replaced', 'overridden', 'deleted', 'gathered', 'rendered', 'parsed'],
'default': 'merged',
'type': 'str'
}
}

View file

@ -0,0 +1,405 @@
#
# -*- coding: utf-8 -*-
# Copyright 2019 Red Hat Inc.
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
"""
The ios_acl_interfaces class
It is in this file where the current configuration (as dict)
is compared to the provided configuration (as dict) and the command set
necessary to bring the current configuration to it's desired end-state is
created
"""
from __future__ import absolute_import, division, print_function
__metaclass__ = type
from ansible.module_utils.network.common.cfg.base import ConfigBase
from ansible.module_utils.network.common.utils import to_list
from ansible.module_utils.network.ios.facts.facts import Facts
from ansible.module_utils.six import iteritems
from ansible.module_utils.network.ios.utils.utils import remove_duplicate_interface, normalize_interface
class Acl_Interfaces(ConfigBase):
"""
The ios_acl_interfaces class
"""
gather_subset = [
'!all',
'!min',
]
gather_network_resources = [
'acl_interfaces',
]
def __init__(self, module):
super(Acl_Interfaces, self).__init__(module)
def get_acl_interfaces_facts(self, data=None):
""" Get the 'facts' (the current configuration)
:rtype: A dictionary
:returns: The current configuration as a dictionary
"""
facts, _warnings = Facts(self._module).get_facts(self.gather_subset, self.gather_network_resources, data=data)
acl_interfaces_facts = facts['ansible_network_resources'].get('acl_interfaces')
if not acl_interfaces_facts:
return []
return acl_interfaces_facts
def execute_module(self):
""" Execute the module
:rtype: A dictionary
:returns: The result from moduel execution
"""
result = {'changed': False}
commands = list()
warnings = list()
if self.state in self.ACTION_STATES:
existing_acl_interfaces_facts = self.get_acl_interfaces_facts()
else:
existing_acl_interfaces_facts = []
if self.state in self.ACTION_STATES or self.state == 'rendered':
commands.extend(self.set_config(existing_acl_interfaces_facts))
if commands and self.state in self.ACTION_STATES:
if not self._module.check_mode:
self._connection.edit_config(commands)
result['changed'] = True
if self.state in self.ACTION_STATES:
result['commands'] = commands
if self.state in self.ACTION_STATES or self.state == 'gathered':
changed_acl_interfaces_facts = self.get_acl_interfaces_facts()
elif self.state == 'rendered':
result['rendered'] = commands
elif self.state == 'parsed':
running_config = self._module.params['running_config']
if not running_config:
self._module.fail_json(
msg="value of running_config parameter must not be empty for state parsed"
)
result['parsed'] = self.get_acl_interfaces_facts(data=running_config)
else:
changed_acl_interfaces_facts = []
if self.state in self.ACTION_STATES:
result['before'] = existing_acl_interfaces_facts
if result['changed']:
result['after'] = changed_acl_interfaces_facts
elif self.state == 'gathered':
result['gathered'] = changed_acl_interfaces_facts
result['warnings'] = warnings
return result
def set_config(self, existing_acl_interfaces_facts):
""" Collect the configuration from the args passed to the module,
collect the current configuration (as a dict from facts)
:rtype: A list
:returns: the commands necessary to migrate the current configuration
to the deisred configuration
"""
want = self._module.params['config']
if want:
for item in want:
item['name'] = normalize_interface(item['name'])
have = existing_acl_interfaces_facts
resp = self.set_state(want, have)
return to_list(resp)
def set_state(self, want, have):
""" Select the appropriate function based on the state provided
:param want: the desired configuration as a dictionary
:param have: the current configuration as a dictionary
:rtype: A list
:returns: the commands necessary to migrate the current configuration
to the deisred configuration
"""
commands = []
state = self._module.params['state']
if state in ('overridden', 'merged', 'replaced', 'rendered') and not want:
self._module.fail_json(msg='value of config parameter must not be empty for state {0}'.format(state))
if state == 'overridden':
commands = self._state_overridden(want, have)
elif state == 'deleted':
commands = self._state_deleted(want, have)
elif state == 'merged' or state == 'rendered':
commands = self._state_merged(want, have)
elif state == 'replaced':
commands = self._state_replaced(want, have)
return commands
def _state_replaced(self, want, have):
""" The command generator when state is replaced
:param want: the desired configuration as a dictionary
:param have: the current configuration as a dictionary
:rtype: A list
:returns: the commands necessary to migrate the current configuration
to the deisred configuration
"""
commands = []
for interface in want:
for each in have:
if each['name'] == interface['name']:
break
else:
continue
commands.extend(self._clear_config(interface, each, 'replaced'))
commands.extend(self._set_config(interface, each))
# Remove the duplicate interface call
commands = remove_duplicate_interface(commands)
return commands
def _state_overridden(self, want, have):
""" The command generator when state is overridden
:param want: the desired configuration as a dictionary
:param have: the current configuration as a dictionary
:rtype: A list
:returns: the commands necessary to migrate the current configuration
to the desired configuration
"""
commands = []
for each in have:
for interface in want:
if each['name'] == interface['name']:
break
else:
# We didn't find a matching desired state, which means we can
# pretend we recieved an empty desired state.
interface = dict(name=each['name'])
commands.extend(self._clear_config(interface, each))
continue
commands.extend(self._clear_config(interface, each, 'overridden'))
commands.extend(self._set_config(interface, each))
# Remove the duplicate interface call
commands = remove_duplicate_interface(commands)
return commands
def _state_merged(self, want, have):
""" The command generator when state is merged
:param want: the additive configuration as a dictionary
:param have: the current configuration as a dictionary
:rtype: A list
:returns: the commands necessary to merge the provided into
the current configuration
"""
commands = []
for interface in want:
for each in have:
if each['name'] == interface['name']:
break
else:
# configuring non-existing interface
commands.extend(self._set_config(interface, dict()))
continue
commands.extend(self._set_config(interface, each))
return commands
def _state_deleted(self, want, have):
""" The command generator when state is deleted
:param want: the objects from which the configuration should be removed
:param have: the current configuration as a dictionary
:rtype: A list
:returns: the commands necessary to remove the current configuration
of the provided objects
"""
commands = []
if want:
for interface in want:
for each in have:
if each['name'] == interface['name']:
break
else:
continue
commands.extend(self._clear_config(interface, each))
else:
for each in have:
commands.extend(self._clear_config(dict(), each))
return commands
def dict_to_set(self, input_dict, test_set, final_set, count=0):
# recursive function to convert input dict to set for comparision
test_dict = dict()
if isinstance(input_dict, dict):
input_dict_len = len(input_dict)
for k, v in sorted(iteritems(input_dict)):
count += 1
if isinstance(v, list):
for each in v:
if isinstance(each, dict):
input_dict_len = len(each)
if [True for i in each.values() if type(i) == list]:
self.dict_to_set(each, set(), final_set, count)
else:
self.dict_to_set(each, test_set, final_set, 0)
else:
if v is not None:
test_dict.update({k: v})
if tuple(iteritems(test_dict)) not in test_set and count == input_dict_len:
test_set.add(tuple(iteritems(test_dict)))
count = 0
if count == input_dict_len + 1:
test_set.update(tuple(iteritems(test_dict)))
final_set.add(tuple(test_set))
def _set_config(self, want, have):
""" Function that sets the acls config based on the want and have config
:param want: want config
:param have: have config
:param acl_want: want acl config
:param afi: acl afi type
:rtype: A list
:returns: the commands generated based on input want/have params
"""
commands = []
want_set = set()
have_set = set()
self.dict_to_set(want, set(), want_set)
self.dict_to_set(have, set(), have_set)
for w in want_set:
want_afi = dict(w).get('afi')
if have_set:
def common_diff_config_code(diff_list, cmd, commands):
for each in diff_list:
try:
temp = dict(each)
temp_cmd = cmd + ' {0} {1}'.format(temp['name'], temp['direction'])
if temp_cmd not in commands:
commands.append(temp_cmd)
except ValueError:
continue
for h in have_set:
have_afi = dict(h).get('afi')
if have_afi == want_afi:
if want_afi == 'ipv4':
diff = set(w) - set(h)
if diff:
cmd = 'ip access-group'
common_diff_config_code(diff, cmd, commands)
if want_afi == 'ipv6':
diff = set(w) - set(h)
if diff:
cmd = 'ipv6 traffic-filter'
common_diff_config_code(diff, cmd, commands)
break
else:
if want_afi == 'ipv4':
diff = set(w) - set(h)
if diff:
cmd = 'ip access-group'
common_diff_config_code(diff, cmd, commands)
if want_afi == 'ipv6':
diff = set(w) - set(h)
if diff:
cmd = 'ipv6 traffic-filter'
common_diff_config_code(diff, cmd, commands)
else:
def common_want_config_code(want, cmd, commands):
for each in want:
if each[0] == 'afi':
continue
temp = dict(each)
temp_cmd = cmd + ' {0} {1}'.format(temp['name'], temp['direction'])
commands.append(temp_cmd)
if want_afi == 'ipv4':
cmd = 'ip access-group'
common_want_config_code(w, cmd, commands)
if want_afi == 'ipv6':
cmd = 'ipv6 traffic-filter'
common_want_config_code(w, cmd, commands)
commands.sort()
if commands:
interface = want.get('name')
commands.insert(0, 'interface {0}'.format(interface))
return commands
def _clear_config(self, want, have, state=''):
""" Function that deletes the acl config based on the want and have config
:param acl: acl config
:param config: config
:rtype: A list
:returns: the commands generated based on input acl/config params
"""
commands = []
if want.get('name'):
interface = 'interface ' + want['name']
else:
interface = 'interface ' + have['name']
w_access_group = want.get('access_groups')
temp_want_afi = []
temp_want_acl_name = []
if w_access_group:
# get the user input afi and acls
for each in w_access_group:
want_afi = each.get('afi')
want_acls = each.get('acls')
if want_afi:
temp_want_afi.append(want_afi)
if want_acls:
for each in want_acls:
temp_want_acl_name.append(each.get('name'))
h_access_group = have.get('access_groups')
if h_access_group:
for access_grp in h_access_group:
for acl in access_grp.get('acls'):
have_afi = access_grp.get('afi')
acl_name = acl.get('name')
acl_direction = acl.get('direction')
if temp_want_afi and state not in ['replaced', 'overridden']:
# if user want to delete acls based on afi
if 'ipv4' in temp_want_afi and have_afi == 'ipv4':
if acl_name in temp_want_acl_name:
continue
cmd = 'no ip access-group'
cmd += ' {0} {1}'.format(acl_name, acl_direction)
commands.append(cmd)
if 'ipv6' in temp_want_afi and have_afi == 'ipv6':
if acl_name in temp_want_acl_name:
continue
cmd = 'no ipv6 traffic-filter'
cmd += ' {0} {1}'.format(acl_name, acl_direction)
commands.append(cmd)
else:
# if user want to delete acls based on interface
if access_grp.get('afi') == 'ipv4':
if acl_name in temp_want_acl_name:
continue
cmd = 'no ip access-group'
cmd += ' {0} {1}'.format(acl_name, acl_direction)
commands.append(cmd)
elif access_grp.get('afi') == 'ipv6':
if acl_name in temp_want_acl_name:
continue
cmd = 'no ipv6 traffic-filter'
cmd += ' {0} {1}'.format(acl_name, acl_direction)
commands.append(cmd)
if commands:
# inserting the interface at first
commands.insert(0, interface)
return commands

View file

@ -0,0 +1,122 @@
#
# -*- coding: utf-8 -*-
# Copyright 2019 Red Hat
# GNU General Public License v3.0+
# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
"""
The ios_acl_interfaces fact class
It is in this file the configuration is collected from the device
for a given resource, parsed, and the facts tree is populated
based on the configuration.
"""
from __future__ import absolute_import, division, print_function
__metaclass__ = type
import re
from copy import deepcopy
from ansible.module_utils.network.common import utils
from ansible.module_utils.network.ios.utils.utils import get_interface_type
from ansible.module_utils.network.ios.argspec.acl_interfaces.acl_interfaces import Acl_InterfacesArgs
class Acl_InterfacesFacts(object):
""" The ios_acl_interfaces fact class
"""
def __init__(self, module, subspec='config', options='options'):
self._module = module
self.argument_spec = Acl_InterfacesArgs.argument_spec
spec = deepcopy(self.argument_spec)
if subspec:
if options:
facts_argument_spec = spec[subspec][options]
else:
facts_argument_spec = spec[subspec]
else:
facts_argument_spec = spec
self.generated_spec = utils.generate_dict(facts_argument_spec)
def get_acl_interfaces_data(self, connection):
return connection.get('sh running-config | include interface|ip access-group|ipv6 traffic-filter')
def populate_facts(self, connection, ansible_facts, data=None):
""" Populate the facts for interfaces
:param connection: the device connection
:param ansible_facts: Facts dictionary
:param data: previously collected conf
:rtype: dictionary
:returns: facts
"""
objs = []
if not data:
data = self.get_acl_interfaces_data(connection)
# operate on a collection of resource x
config = data.split('interface ')
for conf in config:
if conf:
obj = self.render_config(self.generated_spec, conf)
if obj:
objs.append(obj)
facts = {}
if objs:
facts['acl_interfaces'] = []
params = utils.validate_config(self.argument_spec, {'config': objs})
for cfg in params['config']:
facts['acl_interfaces'].append(utils.remove_empties(cfg))
ansible_facts['ansible_network_resources'].update(facts)
return ansible_facts
def render_config(self, spec, conf):
"""
Render config as dictionary structure and delete keys
from spec for null values
:param spec: The facts tree, generated from the argspec
:param conf: The configuration
:rtype: dictionary
:returns: The generated config
"""
config = deepcopy(spec)
match = re.search(r'^(\S+)', conf)
intf = match.group(1)
if get_interface_type(intf) == 'unknown':
return {}
config['name'] = intf
config['access_groups'] = []
acl_v4_config = {}
acl_v6_config = {}
def common_iter_code(cmd, conf):
# Common code for IPV4 and IPV6 config parsing
acls = []
re_cmd = cmd + ' (\\S+.*)'
ip_all = re.findall(re_cmd, conf)
for each in ip_all:
acl = {}
access_grp_config = each.split(' ')
acl['name'] = access_grp_config[0]
acl['direction'] = access_grp_config[1]
acls.append(acl)
return acls
if 'ip' in conf:
acls = common_iter_code('ip access-group', conf)
acl_v4_config['afi'] = 'ipv4'
acl_v4_config['acls'] = acls
config['access_groups'].append(acl_v4_config)
if 'ipv6' in conf:
acls = common_iter_code('ipv6 traffic-filter', conf)
acl_v6_config['afi'] = 'ipv6'
acl_v6_config['acls'] = acls
config['access_groups'].append(acl_v6_config)
return utils.remove_empties(config)

View file

@ -23,6 +23,7 @@ from ansible.module_utils.network.ios.facts.lacp_interfaces.lacp_interfaces impo
from ansible.module_utils.network.ios.facts.lldp_global.lldp_global import Lldp_globalFacts from ansible.module_utils.network.ios.facts.lldp_global.lldp_global import Lldp_globalFacts
from ansible.module_utils.network.ios.facts.lldp_interfaces.lldp_interfaces import Lldp_InterfacesFacts from ansible.module_utils.network.ios.facts.lldp_interfaces.lldp_interfaces import Lldp_InterfacesFacts
from ansible.module_utils.network.ios.facts.l3_interfaces.l3_interfaces import L3_InterfacesFacts from ansible.module_utils.network.ios.facts.l3_interfaces.l3_interfaces import L3_InterfacesFacts
from ansible.module_utils.network.ios.facts.acl_interfaces.acl_interfaces import Acl_InterfacesFacts
from ansible.module_utils.network.ios.facts.legacy.base import Default, Hardware, Interfaces, Config from ansible.module_utils.network.ios.facts.legacy.base import Default, Hardware, Interfaces, Config
@ -43,6 +44,7 @@ FACT_RESOURCE_SUBSETS = dict(
lldp_global=Lldp_globalFacts, lldp_global=Lldp_globalFacts,
lldp_interfaces=Lldp_InterfacesFacts, lldp_interfaces=Lldp_InterfacesFacts,
l3_interfaces=L3_InterfacesFacts, l3_interfaces=L3_InterfacesFacts,
acl_interfaces=Acl_InterfacesFacts,
) )

View file

@ -0,0 +1,633 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright 2019 Red Hat
# GNU General Public License v3.0+
# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
#############################################
# WARNING #
#############################################
#
# This file is auto generated by the resource
# module builder playbook.
#
# Do not edit this file manually.
#
# Changes to this file will be over written
# by the resource module builder.
#
# Changes should be made in the model used to
# generate this file or in the resource module
# builder template.
#
#############################################
"""
The module file for ios_acl_interfaces
"""
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'network'}
DOCUMENTATION = """
---
module: ios_acl_interfaces
version_added: '2.10'
short_description: Configure and manage access-control (ACL) attributes of interfaces on IOS devices.
description: This module configures and manages the access-control (ACL) attributes of interfaces on IOS platforms.
author: Sumit Jaiswal (@justjais)
notes:
- Tested against Cisco IOSv Version 15.2 on VIRL
- This module works with connection C(network_cli).
See L(IOS Platform Options,../network/user_guide/platform_ios.html).
options:
config:
description: A dictionary of ACL interfaces options
type: list
elements: dict
suboptions:
name:
description: Full name of the interface excluding any logical unit number, i.e. GigabitEthernet0/1.
type: str
required: True
access_groups:
description: Specify access-group for IP access list (standard or extended).
type: list
elements: dict
suboptions:
afi:
description: Specifies the AFI for the ACLs to be configured on this interface.
type: str
required: True
choices:
- ipv4
- ipv6
acls:
description: Specifies the ACLs for the provided AFI.
type: list
elements: dict
suboptions:
name:
description: Specifies the name of the IPv4/IPv4 ACL for the interface.
type: str
required: True
direction:
description:
- Specifies the direction of packets that the ACL will be applied on.
- With one direction already assigned, other acl direction cannot be same.
type: str
required: True
choices:
- in
- out
running_config:
description:
- The module, by default, will connect to the remote device and retrieve the current
running-config to use as a base for comparing against the contents of source.
There are times when it is not desirable to have the task get the current running-config
for every task in a playbook. The I(running_config) argument allows the implementer to
pass in the configuration to use as the base config for comparison. This value of this
option should be the output received from device by executing command.
type: str
state:
description:
- The state the configuration should be left in
type: str
choices:
- merged
- replaced
- overridden
- deleted
- gathered
- parsed
- rendered
default: merged
"""
EXAMPLES = """
---
# Using Merged
# Before state:
# -------------
#
# vios#sh running-config | include interface|ip access-group|ipv6 traffic-filter
# interface Loopback888
# interface GigabitEthernet0/0
# interface GigabitEthernet0/1
# interface GigabitEthernet0/2
# ip access-group 123 out
- name: "Merge module attributes of given access-groups"
ios_acl_interfaces:
config:
- name: GigabitEthernet0/1
access_groups:
- afi: ipv4
acls:
- name: 110
direction: in
- name: 123
direction: out
- afi: ipv6
acls:
- name: test_v6
direction: out
- name: temp_v6
direction: in
- name: GigabitEthernet0/2
access_groups:
- afi: ipv4
acls:
- name: 100
direction: in
state: merged
# Commands Fired:
# ---------------
#
# interface GigabitEthernet0/1
# ip access-group 110 in
# ip access-group 123 out
# ipv6 traffic-filter test_v6 out
# ipv6 traffic-filter temp_v6 in
# interface GigabitEthernet0/2
# ip access-group 100 in
# ip access-group 123 out
# After state:
# -------------
#
# vios#sh running-config | include interface|ip access-group|ipv6 traffic-filter
# interface Loopback888
# interface GigabitEthernet0/0
# interface GigabitEthernet0/1
# ip access-group 110 in
# ip access-group 123 out
# ipv6 traffic-filter test_v6 out
# ipv6 traffic-filter temp_v6 in
# interface GigabitEthernet0/2
# ip access-group 110 in
# ip access-group 123 out
# Using Replaced
# Before state:
# -------------
#
# vios#sh running-config | include interface|ip access-group|ipv6 traffic-filter
# interface Loopback888
# interface GigabitEthernet0/0
# interface GigabitEthernet0/1
# ip access-group 110 in
# ip access-group 123 out
# ipv6 traffic-filter test_v6 out
# ipv6 traffic-filter temp_v6 in
# interface GigabitEthernet0/2
# ip access-group 110 in
# ip access-group 123 out
- name: "Replace module attributes of given access-groups"
ios_acl_interfaces:
config:
- name: GigabitEthernet0/1
access_groups:
- afi: ipv4
acls:
- name: 100
direction: out
- name: 110
direction: in
state: replaced
# Commands Fired:
# ---------------
#
# interface GigabitEthernet0/1
# no ip access-group 123 out
# no ipv6 traffic-filter temp_v6 in
# no ipv6 traffic-filter test_v6 out
# ip access-group 100 out
# After state:
# -------------
#
# vios#sh running-config | include interface|ip access-group|ipv6 traffic-filter
# interface Loopback888
# interface GigabitEthernet0/0
# interface GigabitEthernet0/1
# ip access-group 100 out
# ip access-group 110 in
# interface GigabitEthernet0/2
# ip access-group 110 in
# ip access-group 123 out
# Using Overridden
# Before state:
# -------------
#
# vios#sh running-config | include interface|ip access-group|ipv6 traffic-filter
# interface Loopback888
# interface GigabitEthernet0/0
# interface GigabitEthernet0/1
# ip access-group 110 in
# ip access-group 123 out
# ipv6 traffic-filter test_v6 out
# ipv6 traffic-filter temp_v6 in
# interface GigabitEthernet0/2
# ip access-group 110 in
# ip access-group 123 out
- name: "Overridden module attributes of given access-groups"
ios_acl_interfaces:
config:
- name: GigabitEthernet0/1
access_groups:
- afi: ipv4
acls:
- name: 100
direction: out
- name: 110
direction: in
state: overridden
# Commands Fired:
# ---------------
#
# interface GigabitEthernet0/1
# no ip access-group 123 out
# no ipv6 traffic-filter test_v6 out
# no ipv6 traffic-filter temp_v6 in
# ip access-group 100 out
# interface GigabitEthernet0/2
# no ip access-group 110 in
# no ip access-group 123 out
# After state:
# -------------
#
# vios#sh running-config | include interface|ip access-group|ipv6 traffic-filter
# interface Loopback888
# interface GigabitEthernet0/0
# interface GigabitEthernet0/1
# ip access-group 100 out
# ip access-group 110 in
# interface GigabitEthernet0/2
# Using Deleted
# Before state:
# -------------
#
# vios#sh running-config | include interface|ip access-group|ipv6 traffic-filter
# interface Loopback888
# interface GigabitEthernet0/0
# interface GigabitEthernet0/1
# ip access-group 110 in
# ip access-group 123 out
# ipv6 traffic-filter test_v6 out
# ipv6 traffic-filter temp_v6 in
# interface GigabitEthernet0/2
# ip access-group 110 in
# ip access-group 123 out
- name: "Delete module attributes of given Interface"
ios_acl_interfaces:
config:
- name: GigabitEthernet0/1
state: deleted
# Commands Fired:
# ---------------
#
# interface GigabitEthernet0/1
# no ip access-group 110 in
# no ip access-group 123 out
# no ipv6 traffic-filter test_v6 out
# no ipv6 traffic-filter temp_v6 in
# After state:
# -------------
#
# vios#sh running-config | include interface|ip access-group|ipv6 traffic-filter
# interface Loopback888
# interface GigabitEthernet0/0
# interface GigabitEthernet0/1
# interface GigabitEthernet0/2
# ip access-group 110 in
# ip access-group 123 out
# Before state:
# -------------
#
# vios#sh running-config | include interface|ip access-group|ipv6 traffic-filter
# interface Loopback888
# interface GigabitEthernet0/0
# interface GigabitEthernet0/1
# ip access-group 110 in
# ip access-group 123 out
# ipv6 traffic-filter test_v6 out
# ipv6 traffic-filter temp_v6 in
# interface GigabitEthernet0/2
# ip access-group 110 in
# ip access-group 123 out
- name: "Delete module attributes of given Interface based on AFI"
ios_acl_interfaces:
config:
- name: GigabitEthernet0/1
access_groups:
- afi: ipv4
state: deleted
# Commands Fired:
# ---------------
#
# interface GigabitEthernet0/1
# no ip access-group 110 in
# no ip access-group 123 out
# After state:
# -------------
#
# vios#sh running-config | include interface|ip access-group|ipv6 traffic-filter
# interface Loopback888
# interface GigabitEthernet0/0
# interface GigabitEthernet0/1
# ipv6 traffic-filter test_v6 out
# ipv6 traffic-filter temp_v6 in
# interface GigabitEthernet0/2
# ip access-group 110 in
# ip access-group 123 out
# Using DELETED without any config passed
#"(NOTE: This will delete all of configured resource module attributes from each configured interface)"
# Before state:
# -------------
#
# vios#sh running-config | include interface|ip access-group|ipv6 traffic-filter
# interface Loopback888
# interface GigabitEthernet0/0
# interface GigabitEthernet0/1
# ip access-group 110 in
# ip access-group 123 out
# ipv6 traffic-filter test_v6 out
# ipv6 traffic-filter temp_v6 in
# interface GigabitEthernet0/2
# ip access-group 110 in
# ip access-group 123 out
- name: "Delete module attributes of given access-groups from ALL Interfaces"
ios_acl_interfaces:
config:
state: deleted
# Commands Fired:
# ---------------
#
# interface GigabitEthernet0/1
# no ip access-group 110 in
# no ip access-group 123 out
# no ipv6 traffic-filter test_v6 out
# no ipv6 traffic-filter temp_v6 in
# interface GigabitEthernet0/2
# no ip access-group 110 out
# no ip access-group 123 out
# After state:
# -------------
#
# vios#sh running-config | include interface|ip access-group|ipv6 traffic-filter
# interface Loopback888
# interface GigabitEthernet0/0
# interface GigabitEthernet0/1
# interface GigabitEthernet0/2
# Using Gathered
# Before state:
# -------------
#
# vios#sh running-config | include interface|ip access-group|ipv6 traffic-filter
# interface Loopback888
# interface GigabitEthernet0/0
# interface GigabitEthernet0/1
# ip access-group 110 in
# ip access-group 123 out
# ipv6 traffic-filter test_v6 out
# ipv6 traffic-filter temp_v6 in
# interface GigabitEthernet0/2
# ip access-group 110 in
# ip access-group 123 out
- name: Gather listed acl interfaces with provided configurations
ios_acl_interfaces:
config:
state: gathered
# Module Execution Result:
# ------------------------
#
# "gathered": [
# {
# "name": "Loopback888"
# },
# {
# "name": "GigabitEthernet0/0"
# },
# {
# "access_groups": [
# {
# "acls": [
# {
# "direction": "in",
# "name": "110"
# },
# {
# "direction": "out",
# "name": "123"
# }
# ],
# "afi": "ipv4"
# },
# {
# "acls": [
# {
# "direction": "in",
# "name": "temp_v6"
# },
# {
# "direction": "out",
# "name": "test_v6"
# }
# ],
# "afi": "ipv6"
# }
# ],
# "name": "GigabitEthernet0/1"
# },
# {
# "access_groups": [
# {
# "acls": [
# {
# "direction": "in",
# "name": "100"
# },
# {
# "direction": "out",
# "name": "123"
# }
# ],
# "afi": "ipv4"
# }
# ],
# "name": "GigabitEthernet0/2"
# }
# ]
# After state:
# ------------
#
# vios#sh running-config | include interface|ip access-group|ipv6 traffic-filter
# interface Loopback888
# interface GigabitEthernet0/0
# interface GigabitEthernet0/1
# ip access-group 110 in
# ip access-group 123 out
# ipv6 traffic-filter test_v6 out
# ipv6 traffic-filter temp_v6 in
# interface GigabitEthernet0/2
# ip access-group 110 in
# ip access-group 123 out
# Using Rendered
- name: Render the commands for provided configuration
ios_acl_interfaces:
config:
- name: GigabitEthernet0/1
access_groups:
- afi: ipv4
acls:
- name: 110
direction: in
- name: 123
direction: out
- afi: ipv6
acls:
- name: test_v6
direction: out
- name: temp_v6
direction: in
state: rendered
# Module Execution Result:
# ------------------------
#
# "rendered": [
# "interface GigabitEthernet0/1",
# "ip access-group 110 in",
# "ip access-group 123 out",
# "ipv6 traffic-filter temp_v6 in",
# "ipv6 traffic-filter test_v6 out"
# ]
# Using Parsed
- name: Parse the commands for provided configuration
ios_acl_interfaces:
running_config:
"interface GigabitEthernet0/1
ip access-group 110 in
ip access-group 123 out
ipv6 traffic-filter temp_v6 in
ipv6 traffic-filter test_v6 out"
state: parsed
# Module Execution Result:
# ------------------------
#
# "parsed": [
# {
# "access_groups": [
# {
# "acls": [
# {
# "direction": "in",
# "name": "110"
# }
# ],
# "afi": "ipv4"
# },
# {
# "acls": [
# {
# "direction": "in",
# "name": "temp_v6"
# }
# ],
# "afi": "ipv6"
# }
# ],
# "name": "GigabitEthernet0/1"
# }
# ]
"""
RETURN = """
before:
description: The configuration as structured data prior to module invocation.
returned: always
type: list
sample: The configuration returned will always be in the same format of the parameters above.
after:
description: The configuration as structured data after module completion.
returned: when changed
type: list
sample: The configuration returned will always be in the same format of the parameters above.
commands:
description: The set of commands pushed to the remote device
returned: always
type: list
sample: ['interface GigabitEthernet0/1', 'ip access-group 110 in', 'ipv6 traffic-filter test_v6 out']
"""
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.network.ios.argspec.acl_interfaces.acl_interfaces import Acl_InterfacesArgs
from ansible.module_utils.network.ios.config.acl_interfaces.acl_interfaces import Acl_Interfaces
def main():
"""
Main entry point for module execution
:returns: the result form module invocation
"""
required_if = [('state', 'merged', ('config',)),
('state', 'replaced', ('config',)),
('state', 'overridden', ('config',)),
('state', 'rendered', ('config',)),
('state', 'parsed', ('running_config',))]
mutually_exclusive = [('config', 'running_config')]
module = AnsibleModule(argument_spec=Acl_InterfacesArgs.argument_spec,
required_if=required_if,
mutually_exclusive=mutually_exclusive,
supports_check_mode=True)
result = Acl_Interfaces(module).execute_module()
module.exit_json(**result)
if __name__ == '__main__':
main()

View file

@ -58,7 +58,7 @@ options:
a specific subset should not be collected. a specific subset should not be collected.
Valid subsets are 'all', 'interfaces', 'l2_interfaces', 'vlans', Valid subsets are 'all', 'interfaces', 'l2_interfaces', 'vlans',
'lag_interfaces', 'lacp', 'lacp_interfaces', 'lldp_global', 'lag_interfaces', 'lacp', 'lacp_interfaces', 'lldp_global',
'lldp_interfaces', 'l3_interfaces'. 'lldp_interfaces', 'l3_interfaces', 'acl_interfaces'.
version_added: "2.9" version_added: "2.9"
""" """

View file

@ -0,0 +1,3 @@
---
testcase: "[^_].*"
test_items: []

View file

@ -0,0 +1,21 @@
---
- name: Collect all cli test cases
find:
paths: "{{ role_path }}/tests/cli"
patterns: "{{ testcase }}.yaml"
use_regex: true
register: test_cases
delegate_to: localhost
- name: Set test_items
set_fact: test_items="{{ test_cases.files | map(attribute='path') | list }}"
delegate_to: localhost
- name: Run test case (connection=network_cli)
include: "{{ test_case_to_run }}"
vars:
ansible_connection: network_cli
with_items: "{{ test_items }}"
loop_control:
loop_var: test_case_to_run
tags: connection_network_cli

View file

@ -0,0 +1,2 @@
---
- { include: cli.yaml, tags: ['cli'] }

View file

@ -0,0 +1,8 @@
interface GigabitEthernet0/1
ip access-group 110 in
ip access-group 123 out
ipv6 traffic-filter temp_v6 in
ipv6 traffic-filter test_v6 out
interface GigabitEthernet0/2
ip access-group 110 in
ip access-group 123 out

View file

@ -0,0 +1,14 @@
---
- name: Populate Config
cli_config:
config: "{{ lines }}"
vars:
lines: |
interface GigabitEthernet 0/1
ip access-group 110 in
ip access-group 123 out
ipv6 traffic-filter temp_v6 in
ipv6 traffic-filter test_v6 out
interface GigabitEthernet 0/2
ip access-group 110 in
ip access-group 123 out

View file

@ -0,0 +1,15 @@
---
- name: Remove Config
cli_config:
config: "{{ lines }}"
vars:
lines: |
interface GigabitEthernet 0/1
no ip access-group 110 in
no ip access-group 100 out
no ip access-group 123 out
no ipv6 traffic-filter temp_v6 in
no ipv6 traffic-filter test_v6 out
interface GigabitEthernet 0/2
no ip access-group 110 in
no ip access-group 123 out

View file

@ -0,0 +1,65 @@
---
- debug:
msg: "Start ios_acl_interfaces deleted integration tests ansible_connection={{ ansible_connection }}"
- include_tasks: _remove_config.yaml
- include_tasks: _populate_config.yaml
- block:
- name: Delete module attributes of given Interface based on AFI
ios_acl_interfaces:
config:
- name: GigabitEthernet0/1
access_groups:
- afi: ipv6
state: deleted
register: result
- assert:
that:
- "result.commands|length == 3"
- "result.changed == true"
- "'no ipv6 traffic-filter temp_v6 in' in result.commands"
- "'no ipv6 traffic-filter test_v6 out' in result.commands"
- name: Delete module attributes of given Interface based on AFI (IDEMPOTENT)
ios_acl_interfaces:
config:
- name: GigabitEthernet0/1
access_groups:
- afi: ipv6
state: deleted
register: result
- assert:
that:
- "result.changed == false"
- name: Delete module attributes of given Interface.
ios_acl_interfaces:
config:
- name: GigabitEthernet0/1
state: deleted
register: result
- assert:
that:
- "result.commands|length == 3"
- "result.changed == true"
- "'no ip access-group 110 in' in result.commands"
- "'no ip access-group 123 out' in result.commands"
- name: Delete module attributes of given Interface (IDEMPOTENT)
ios_acl_interfaces:
config:
- name: GigabitEthernet0/1
state: deleted
register: result
- assert:
that:
- "result.changed == false"
always:
- include_tasks: _remove_config.yaml

View file

@ -0,0 +1,58 @@
---
- debug:
msg: "START ios_acl_interfaces empty_config.yaml integration tests on connection={{ ansible_connection }}"
- name: Merged with empty config should give appropriate error message
ios_acl_interfaces:
config:
state: merged
register: result
ignore_errors: True
- assert:
that:
- result.msg == 'value of config parameter must not be empty for state merged'
- name: Replaced with empty config should give appropriate error message
ios_acl_interfaces:
config:
state: replaced
register: result
ignore_errors: True
- assert:
that:
- result.msg == 'value of config parameter must not be empty for state replaced'
- name: Overridden with empty config should give appropriate error message
ios_acl_interfaces:
config:
state: overridden
register: result
ignore_errors: True
- assert:
that:
- result.msg == 'value of config parameter must not be empty for state overridden'
- name: Rendered with empty config should give appropriate error message
ios_acl_interfaces:
config:
state: rendered
register: result
ignore_errors: True
- assert:
that:
- result.msg == 'value of config parameter must not be empty for state rendered'
- name: Parsed with empty config should give appropriate error message
ios_acl_interfaces:
running_config:
state: parsed
register: result
ignore_errors: True
- assert:
that:
- result.msg == 'value of running_config parameter must not be empty for state parsed'

View file

@ -0,0 +1,21 @@
---
- debug:
msg: "START ios_acl_interfaces gathered integration tests on connection={{ ansible_connection }}"
- include_tasks: _populate_config.yaml
- block:
- name: Gather the provided configuration with the exisiting running configuration
ios_acl_interfaces: &gathered
config:
state: gathered
register: result
- name: Assert
assert:
that:
- "gathered['config'] | symmetric_difference(result.gathered) == []"
- "result['changed'] == false"
always:
- include_tasks: _remove_config.yaml

View file

@ -0,0 +1,53 @@
---
- debug:
msg: "Start ios_acl_interfaces merged integration tests ansible_connection={{ ansible_connection }}"
- include_tasks: _remove_config.yaml
- block:
- name: Merge the provided configuration with the exisiting running configuration
ios_acl_interfaces: &merged
config:
- name: GigabitEthernet0/1
access_groups:
- afi: ipv4
acls:
- name: 110
direction: in
- name: 123
direction: out
- afi: ipv6
acls:
- name: temp_v6
direction: in
- name: test_v6
direction: out
- name: GigabitEthernet0/2
access_groups:
- afi: ipv4
acls:
- name: 110
direction: in
- name: 123
direction: out
state: merged
register: result
- assert:
that:
- "result.commands|length == 8"
- "result.changed == true"
- "result.commands|symmetric_difference(merged.commands) == []"
- name: Merge the provided configuration with the exisiting running configuration (IDEMPOTENT)
ios_acl_interfaces: *merged
register: result
- assert:
that:
- "result.commands|length == 0"
- "result.changed == false"
always:
- include_tasks: _remove_config.yaml

View file

@ -0,0 +1,41 @@
---
- debug:
msg: "Start ios_acl_interfaces overridden integration tests ansible_connection={{ ansible_connection }}"
- include_tasks: _populate_config.yaml
- block:
- name: Override device configuration of all acl_interfaces with provided configuration
ios_acl_interfaces: &overridden
config:
- name: GigabitEthernet0/1
access_groups:
- afi: ipv4
acls:
- name: 100
direction: out
- name: 110
direction: in
state: overridden
become: yes
register: result
- assert:
that:
- "result.commands|length == 8"
- "result.changed == true"
- "result.commands|symmetric_difference(overridden.commands) == []"
- name: Override device configuration of all acl_interfaces with provided configuration (IDEMPOTENT)
ios_acl_interfaces: *overridden
become: yes
register: result
- assert:
that:
- "result.commands|length == 0"
- "result.changed == false"
always:
- include_tasks: _remove_config.yaml

View file

@ -0,0 +1,16 @@
---
- debug:
msg: "START ios_acl_interfaces parsed integration tests on connection={{ ansible_connection }}"
- name: Parse the commands for provided configuration
ios_acl_interfaces: &parsed
running_config:
"{{ lookup('file', '_parsed.cfg') }}"
state: parsed
become: yes
register: result
- assert:
that:
- "result.changed == false"
- "parsed['config']|symmetric_difference(result.parsed) == []"

View file

@ -0,0 +1,39 @@
---
- debug:
msg: "Start ios_acl_interfaces rendered integration tests ansible_connection={{ ansible_connection }}"
- block:
- name: Render the commands for provided configuration
ios_acl_interfaces:
config:
- name: GigabitEthernet0/1
access_groups:
- afi: ipv4
acls:
- name: 110
direction: in
- name: 123
direction: out
- afi: ipv6
acls:
- name: test_v6
direction: out
- name: temp_v6
direction: in
- name: GigabitEthernet0/2
access_groups:
- afi: ipv4
acls:
- name: 110
direction: in
- name: 123
direction: out
state: rendered
become: yes
register: result
- assert:
that:
- "result.changed == false"
- "result.rendered|symmetric_difference(merged.commands) == []"

View file

@ -0,0 +1,41 @@
---
- debug:
msg: "Start ios_acl_interfaces replced integration tests ansible_connection={{ ansible_connection }}"
- include_tasks: _populate_config.yaml
- block:
- name: Replaces device configuration of listed acl_interfaces with provided configuration
ios_acl_interfaces: &replaced
config:
- name: GigabitEthernet0/1
access_groups:
- afi: ipv4
acls:
- name: 100
direction: out
- name: 110
direction: in
state: replaced
become: yes
register: result
- assert:
that:
- "result.commands|length == 5"
- "result.changed == true"
- "result.commands|symmetric_difference(replaced.commands) == []"
- name: Replaces device configuration of listed acl_interfaces with provided configuration (IDEMPOTENT)
ios_acl_interfaces: *replaced
become: yes
register: result
- assert:
that:
- "result.commands|length == 0"
- "result.changed == false"
always:
- include_tasks: _remove_config.yaml

View file

@ -0,0 +1,78 @@
---
- debug:
msg: "START ios_acl_interfaces round trip integration tests on connection={{ ansible_connection }}"
- include_tasks: _remove_config.yaml
- block:
- name: Apply the provided configuration (Base config)
ios_acl_interfaces:
config:
- name: GigabitEthernet0/1
access_groups:
- afi: ipv4
acls:
- name: 110
direction: in
- name: 123
direction: out
- afi: ipv6
acls:
- name: temp_v6
direction: in
- name: test_v6
direction: out
- name: GigabitEthernet0/2
access_groups:
- afi: ipv4
acls:
- name: 110
direction: in
- name: 123
direction: out
state: merged
register: base_config
- name: Gather acl interfaces facts
ios_facts:
gather_subset:
- "!all"
- "!min"
gather_network_resources:
- acl_interfaces
- name: Apply the provided configuration (config to be reverted)
ios_acl_interfaces:
config:
- name: GigabitEthernet0/1
access_groups:
- afi: ipv4
acls:
- name: 100
direction: out
- name: 110
direction: in
state: overridden
register: result
- assert:
that:
- "result.commands|length == 8"
- "result.changed == true"
- "result.commands|symmetric_difference(overridden.commands) == []"
- name: Revert back to base config using facts round trip
ios_acl_interfaces:
config: "{{ ansible_facts['network_resources']['acl_interfaces'] }}"
state: overridden
register: revert
- assert:
that:
- "revert.commands|length == 8"
- "revert.changed == true"
- "revert.commands|symmetric_difference(rtt.commands) == []"
always:
- include_tasks: _remove_config.yaml

View file

@ -0,0 +1,99 @@
---
interfaces:
int1:
GigabitEthernet0/1
int2:
GigabitEthernet0/2
merged:
commands:
- interface GigabitEthernet0/1
- ip access-group 110 in
- ip access-group 123 out
- ipv6 traffic-filter temp_v6 in
- ipv6 traffic-filter test_v6 out
- interface GigabitEthernet0/2
- ip access-group 110 in
- ip access-group 123 out
replaced:
commands:
- interface GigabitEthernet0/1
- no ip access-group 123 out
- no ipv6 traffic-filter temp_v6 in
- no ipv6 traffic-filter test_v6 out
- ip access-group 100 out
overridden:
commands:
- interface GigabitEthernet0/1
- no ip access-group 123 out
- no ipv6 traffic-filter test_v6 out
- no ipv6 traffic-filter temp_v6 in
- ip access-group 100 out
- interface GigabitEthernet0/2
- no ip access-group 110 in
- no ip access-group 123 out
gathered:
config:
- name: GigabitEthernet0/0
- access_groups:
- acls:
- direction: 'in'
name: '110'
- direction: 'out'
name: '123'
afi: 'ipv4'
- acls:
- direction: 'in'
name: 'temp_v6'
- direction: 'out'
name: 'test_v6'
afi: 'ipv6'
name: GigabitEthernet0/1
- access_groups:
- acls:
- direction: 'in'
name: '110'
- direction: 'out'
name: '123'
afi: ipv4
name: GigabitEthernet0/2
parsed:
config:
- access_groups:
- acls:
- direction: in
name: '110'
- direction: out
name: '123'
afi: ipv4
- acls:
- direction: in
name: temp_v6
- direction: out
name: test_v6
afi: ipv6
name: GigabitEthernet0/1
- access_groups:
- acls:
- direction: in
name: '110'
- direction: out
name: '123'
afi: ipv4
name: GigabitEthernet0/2
rtt:
commands:
- interface GigabitEthernet0/1
- no ip access-group 100 out
- ip access-group 123 out
- ipv6 traffic-filter temp_v6 in
- ipv6 traffic-filter test_v6 out
- interface GigabitEthernet0/2
- ip access-group 110 in
- ip access-group 123 out

View file

@ -0,0 +1,8 @@
interface GigabitEthernet0/1
ip access-group 110 in
ip access-group 123 out
ipv6 traffic-filter temp_v6 in
ipv6 traffic-filter test_v6 out
interface GigabitEthernet0/2
ip access-group 110 in
ip access-group 123 out

View file

@ -50,7 +50,6 @@ def load_fixture(name):
class TestIosModule(ModuleTestCase): class TestIosModule(ModuleTestCase):
def execute_module(self, failed=False, changed=False, commands=None, sort=True, defaults=False): def execute_module(self, failed=False, changed=False, commands=None, sort=True, defaults=False):
self.load_fixtures(commands) self.load_fixtures(commands)
if failed: if failed:

View file

@ -0,0 +1,335 @@
#
# (c) 2019, Ansible by Red Hat, inc
# 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
from units.compat.mock import patch
from ansible.modules.network.ios import ios_acl_interfaces
from units.modules.utils import set_module_args
from .ios_module import TestIosModule, load_fixture
class TestIosAclInterfacesModule(TestIosModule):
module = ios_acl_interfaces
def setUp(self):
super(TestIosAclInterfacesModule, self).setUp()
self.mock_get_config = patch('ansible.module_utils.network.common.network.Config.get_config')
self.get_config = self.mock_get_config.start()
self.mock_load_config = patch('ansible.module_utils.network.common.network.Config.load_config')
self.load_config = self.mock_load_config.start()
self.mock_get_resource_connection_config = patch('ansible.module_utils.network.common.cfg.base.'
'get_resource_connection')
self.get_resource_connection_config = self.mock_get_resource_connection_config.start()
self.mock_get_resource_connection_facts = patch('ansible.module_utils.network.common.facts.facts.'
'get_resource_connection')
self.get_resource_connection_facts = self.mock_get_resource_connection_facts.start()
self.mock_edit_config = patch('ansible.module_utils.network.ios.providers.providers.CliProvider.edit_config')
self.edit_config = self.mock_edit_config.start()
self.mock_execute_show_command = patch('ansible.module_utils.network.ios.facts.acl_interfaces.acl_interfaces.'
'Acl_InterfacesFacts.get_acl_interfaces_data')
self.execute_show_command = self.mock_execute_show_command.start()
def tearDown(self):
super(TestIosAclInterfacesModule, self).tearDown()
self.mock_get_resource_connection_config.stop()
self.mock_get_resource_connection_facts.stop()
self.mock_edit_config.stop()
self.mock_get_config.stop()
self.mock_load_config.stop()
self.mock_execute_show_command.stop()
def load_fixtures(self, commands=None):
def load_from_file(*args, **kwargs):
return load_fixture('ios_acl_interfaces.cfg')
self.execute_show_command.side_effect = load_from_file
def test_ios_acl_interfaces_merged(self):
set_module_args(
dict(config=[
dict(name="GigabitEthernet0/1",
access_groups=[
dict(afi="ipv4",
acls=[
dict(name="merge_110",
direction="in"),
dict(name="merge_123",
direction="out")
]),
dict(afi="ipv6",
acls=[
dict(name="merge_temp_v6",
direction="in"),
dict(name="merge_test_v6",
direction="out")
])
]),
dict(name="GigabitEthernet0/2",
access_groups=[
dict(afi="ipv4",
acls=[
dict(name="merge_110",
direction="in"),
dict(name="merge_123",
direction="out")
])
])
], state="merged"))
commands = ['interface GigabitEthernet0/1',
'ip access-group merge_110 in',
'ip access-group merge_123 out',
'ipv6 traffic-filter merge_temp_v6 in',
'ipv6 traffic-filter merge_test_v6 out',
'interface GigabitEthernet0/2',
'ip access-group merge_110 in',
'ip access-group merge_123 out'
]
result = self.execute_module(changed=True)
self.assertEqual(result['commands'], commands)
def test_ios_acl_interfaces_merged_idempotent(self):
set_module_args(dict(
config=[dict(
name="GigabitEthernet0/1",
access_groups=[dict(
afi="ipv4",
acls=[dict(
name="110",
direction="in"
), dict(
name="123",
direction="out"
)]
), dict(
afi="ipv6",
acls=[dict(
name="test_v6",
direction="out"
), dict(
name="temp_v6",
direction="in"
)]
)]
), dict(
name="GigabitEthernet0/2",
access_groups=[dict(
afi="ipv4",
acls=[dict(
name="110",
direction="in"
), dict(
name="123",
direction="out"
)]
)]
)], state="merged"
))
self.execute_module(changed=False, commands=[])
def test_ios_acl_interfaces_replaced(self):
set_module_args(dict(
config=[dict(
name="GigabitEthernet0/1",
access_groups=[dict(
afi="ipv4",
acls=[dict(
name="replace_100",
direction="out"
), dict(
name="110",
direction="in"
)]
)]
)], state="replaced"
))
commands = ['interface GigabitEthernet0/1',
'no ip access-group 123 out',
'no ipv6 traffic-filter temp_v6 in',
'no ipv6 traffic-filter test_v6 out',
'ip access-group replace_100 out'
]
result = self.execute_module(changed=True)
self.assertEqual(result['commands'], commands)
def test_ios_acl_interfaces_replaced_idempotent(self):
set_module_args(dict(
config=[dict(
name="GigabitEthernet0/1",
access_groups=[dict(
afi="ipv4",
acls=[dict(
name="110",
direction="in"
), dict(
name="123",
direction="out"
)]
), dict(
afi="ipv6",
acls=[dict(
name="test_v6",
direction="out"
), dict(
name="temp_v6",
direction="in"
)]
)]
)], state="replaced"
))
self.execute_module(changed=False, commands=[])
def test_ios_acl_interfaces_overridden(self):
set_module_args(dict(
config=[dict(
name="GigabitEthernet0/1",
access_groups=[dict(
afi="ipv4",
acls=[dict(
name="100",
direction="out"
), dict(
name="110",
direction="in"
)]
)]
)], state="overridden"
))
commands = [
'interface GigabitEthernet0/1',
'no ip access-group 123 out',
'no ipv6 traffic-filter test_v6 out',
'no ipv6 traffic-filter temp_v6 in',
'ip access-group 100 out',
'interface GigabitEthernet0/2',
'no ip access-group 110 in',
'no ip access-group 123 out'
]
self.execute_module(changed=True, commands=commands)
def test_ios_acl_interfaces_overridden_idempotent(self):
set_module_args(dict(
config=[dict(
name="GigabitEthernet0/1",
access_groups=[dict(
afi="ipv4",
acls=[dict(
name="110",
direction="in"
), dict(
name="123",
direction="out"
)]
), dict(
afi="ipv6",
acls=[dict(
name="test_v6",
direction="out"
), dict(
name="temp_v6",
direction="in"
)]
)]
), dict(
name="GigabitEthernet0/2",
access_groups=[dict(
afi="ipv4",
acls=[dict(
name="110",
direction="in"
), dict(
name="123",
direction="out"
)]
)]
)], state="overridden"
))
self.execute_module(changed=False, commands=[])
def test_ios_acl_interfaces_deleted_interface(self):
set_module_args(
dict(config=[
dict(name="GigabitEthernet0/1")
], state="deleted"))
commands = ['interface GigabitEthernet0/1',
'no ip access-group 110 in',
'no ip access-group 123 out',
'no ipv6 traffic-filter test_v6 out',
'no ipv6 traffic-filter temp_v6 in',
]
self.execute_module(changed=True, commands=commands)
def test_ios_acl_interfaces_deleted_afi(self):
set_module_args(
dict(config=[
dict(name="GigabitEthernet0/1",
access_groups=[
dict(afi="ipv6")])
], state="deleted"))
commands = ['interface GigabitEthernet0/1',
'no ipv6 traffic-filter test_v6 out',
'no ipv6 traffic-filter temp_v6 in',
]
self.execute_module(changed=True, commands=commands)
def test_ios_acl_interfaces_parsed(self):
set_module_args(
dict(
running_config="interface GigabitEthernet0/1\nip access-group 110 in\nipv6 traffic-filter test_v6 out",
state="parsed"
)
)
result = self.execute_module(changed=False)
parsed_list = [
{'access_groups':
[
{'acls':
[
{'direction': 'in', 'name': '110'}
], 'afi': 'ipv4'},
{'acls':
[
{'direction': 'out', 'name': 'test_v6'}
],
'afi': 'ipv6'}
],
'name': 'GigabitEthernet0/1'}]
self.assertEqual(parsed_list, result['parsed'])
def test_ios_acl_interfaces_rendered(self):
set_module_args(
dict(config=[
dict(name="GigabitEthernet0/1",
access_groups=[
dict(afi="ipv4",
acls=[
dict(name="110",
direction="in"),
dict(name="123",
direction="out")
]),
dict(afi="ipv6",
acls=[
dict(name="temp_v6", direction="in"),
dict(name="test_v6", direction="out")
])
])
], state="rendered"))
commands = ['interface GigabitEthernet0/1',
'ip access-group 110 in',
'ip access-group 123 out',
'ipv6 traffic-filter temp_v6 in',
'ipv6 traffic-filter test_v6 out'
]
result = self.execute_module(changed=False)
self.assertEqual(sorted(result['rendered']), commands)