eos : Add resource module eos_acl_interfaces (#67137)

* Added fix for bug # 54400

* Adding files for RM static_routes

* Added Integration tests

* Revert "Added fix for bug # 54400"

This reverts commit bf42db4269.

* Revert "Adding files for RM static_routes"

This reverts commit dafdd92d43.

* Revert "Added Integration tests"

This reverts commit 129dc87682.

* Adding files for RM static_routes

* Added Integration tests

* Corrected lint errors

* Added fix for bug # 54400

* Revert "Added fix for bug # 54400"

This reverts commit bf42db4269.

* Revert "Adding files for RM static_routes"

This reverts commit dafdd92d43.

* Revert "Added Integration tests"

This reverts commit 129dc87682.

* acl_interfaces

* acl_interfaces in progress

* Adding unit testcases

* Addresses Paul's review comments

* worked on shippable errors

* indentation errors

* Modified unit tests

* indent issues

* indent issues

* eos specfic changes

* making the integration tcs skip the zuul run

* addressed review comments

* added required:True documentaiton
pull/67500/head
GomathiselviS 4 years ago committed by GitHub
parent 195ded6527
commit 600d6278f9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -0,0 +1,88 @@
#
# -*- 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 eos_acl_interfaces module
"""
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
class Acl_interfacesArgs(object): # pylint: disable=R0903
"""The arg spec for the eos_acl_interfaces module
"""
def __init__(self, **kwargs):
pass
argument_spec = {
'config': {
'elements': 'dict',
'options': {
'access_groups': {
'elements': 'dict',
'options': {
'acls': {
'elements': 'dict',
'options': {
'direction': {
'required': True,
'choices': ['in', 'out'],
'type': 'str'
},
'name': {
'required': True,
'type': 'str'
}
},
'type': 'list'
},
'afi': {
'required': True,
'choices': ['ipv4', 'ipv6'],
'type': 'str'
}
},
'type': 'list'
},
'name': {
'required': True,
'type': 'str'
}
},
'type': 'list'
},
'running_config': {
'type': 'str'
},
'state': {
'choices': [
'merged', 'replaced', 'overridden', 'deleted', 'gathered',
'parsed', 'rendered'
],
'default':
'merged',
'type':
'str'
}
} # pylint: disable=C0301

@ -0,0 +1,396 @@
#
# -*- 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 eos_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
import re
import itertools
from ansible.module_utils.network.common.cfg.base import ConfigBase
from ansible.module_utils.network.common.utils import to_list, dict_diff, search_obj_in_list
from ansible.module_utils.network.common.utils import remove_empties
from ansible.module_utils.network.eos.facts.facts import Facts
class Acl_interfaces(ConfigBase):
"""
The eos_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 module execution
"""
result = {'changed': False}
warnings = list()
commands = list()
changed = False
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)
changed = True
if changed:
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':
if not self._module.params['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=self._module.params['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 desired configuration
"""
want = self._module.params['config']
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 desired configuration
"""
commands = []
if self.state in ('merged', 'replaced', 'overridden') and not want:
self._module.fail_json(msg='value of config parameter must not be empty for state {0}'.format(self.state))
state = self._module.params['state']
if state == 'overridden':
commands = self._state_overridden(want, have)
elif state == 'deleted':
commands = self._state_deleted(want, have)
elif state == 'merged' or self.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
:rtype: A list
:returns: the commands necessary to migrate the current configuration
to the desired configuration
"""
commandset = []
want_interface = []
for w in want:
commands = []
diff_access_group = []
want_interface.append(w['name'])
obj_in_have = search_obj_in_list(w['name'], have, 'name')
if not obj_in_have or 'access_groups' not in obj_in_have.keys():
commands.append(add_commands(w['access_groups'], w['name']))
else:
if 'access_groups' in obj_in_have.keys():
obj = self.get_acl_diff(obj_in_have, w)
if obj[0]:
to_delete = {'access_groups': [{'acls': obj[0], 'afi': 'ipv4'}]}
commands.append(remove_commands(to_delete, w['name']))
if obj[1]:
to_delete = {'access_groups': [{'acls': obj[1], 'afi': 'ipv6'}]}
commands.append(remove_commands(to_delete, w['name']))
diff = self.get_acl_diff(w, obj_in_have)
if diff[0]:
diff_access_group.append({'afi': 'ipv4', 'acls': diff[0]})
if diff[1]:
diff_access_group.append({'afi': 'ipv6', 'acls': diff[1]})
if diff_access_group:
commands.append(add_commands(diff_access_group, w['name']))
if commands:
intf_command = ["interface " + w['name']]
commands = list(itertools.chain(*commands))
commandset.append(intf_command)
commandset.append(commands)
if commandset:
commandset = list(itertools.chain(*commandset))
return commandset
def _state_overridden(self, want, have):
""" The command generator when state is overridden
:rtype: A list
:returns: the commands necessary to migrate the current configuration
to the desired configuration
"""
commandset = []
want_interface = []
for w in want:
commands = []
diff_access_group = []
want_interface.append(w['name'])
obj_in_have = search_obj_in_list(w['name'], have, 'name')
if not obj_in_have or 'access_groups' not in obj_in_have.keys():
commands.append(add_commands(w['access_groups'], w['name']))
else:
if 'access_groups' in obj_in_have.keys():
obj = self.get_acl_diff(obj_in_have, w)
if obj[0]:
to_delete = {'access_groups': [{'acls': obj[0], 'afi': 'ipv4'}]}
commands.append(remove_commands(to_delete, w['name']))
if obj[1]:
to_delete = {'access_groups': [{'acls': obj[1], 'afi': 'ipv6'}]}
commands.append(remove_commands(to_delete, w['name']))
diff = self.get_acl_diff(w, obj_in_have)
if diff[0]:
diff_access_group.append({'afi': 'ipv4', 'acls': diff[0]})
if diff[1]:
diff_access_group.append({'afi': 'ipv6', 'acls': diff[1]})
if diff_access_group:
commands.append(add_commands(diff_access_group, w['name']))
if commands:
intf_command = ["interface " + w['name']]
commands = list(itertools.chain(*commands))
commandset.append(intf_command)
commandset.append(commands)
for h in have:
commands = []
if 'access_groups' in h.keys() and h['access_groups']:
if h['name'] not in want_interface:
for h_group in h['access_groups']:
to_delete = {'access_groups': [h_group]}
commands.append(remove_commands(to_delete, h['name']))
if commands:
intf_command = ["interface " + h['name']]
commands = list(itertools.chain(*commands))
commandset.append(intf_command)
commandset.append(commands)
if commandset:
commandset = list(itertools.chain(*commandset))
return commandset
def _state_merged(self, want, have):
""" The command generator when state is merged
:rtype: A list
:returns: the commands necessary to merge the provided into
the current configuration
"""
commandset = []
for w in want:
commands = []
diff_access_group = []
obj_in_have = search_obj_in_list(w['name'], have, 'name')
if not obj_in_have:
commands = add_commands(w['access_groups'], w['name'])
else:
if 'access_groups' in obj_in_have.keys():
diff = self.get_acl_diff(w, obj_in_have)
if diff[0]:
diff_access_group.append({'afi': 'ipv4', 'acls': diff[0]})
if diff[1]:
diff_access_group.append({'afi': 'ipv6', 'acls': diff[1]})
if diff_access_group:
commands = add_commands(diff_access_group, w['name'])
else:
commands = add_commands(w['access_groups'], w['name'])
if commands:
intf_command = ["interface " + w['name']]
commandset.append(intf_command)
commandset.append(commands)
if commandset:
commandset = list(itertools.chain(*commandset))
return commandset
def _state_deleted(self, want, have):
""" The command generator when state is deleted
:rtype: A list
:returns: the commands necessary to remove the current configuration
of the provided objects
"""
commandset = []
for w in want:
commands = []
intf_command = ["interface " + w['name']]
obj_in_have = search_obj_in_list(w['name'], have, 'name')
if 'access_groups' not in w.keys() or not w['access_groups']:
commands = remove_commands(obj_in_have, w['name'])
if w['access_groups']:
for w_grp in w['access_groups']:
if 'acls' not in w_grp.keys() or not w_grp['acls']:
obj = self.get_acls_from_afi(w['name'], w_grp['afi'], have)
to_delete = {'access_groups': [{'acls': obj, 'afi': w_grp['afi']}]}
commands = remove_commands(to_delete, w['name'])
else:
if 'access_groups' not in obj_in_have.keys() or not obj_in_have['access_groups']:
continue
group = {'access_groups': [w_grp]}
obj = self.get_acl_diff(group, obj_in_have, True)
if obj[0]:
to_delete = {'access_groups': [{'acls': obj[0], 'afi': 'ipv4'}]}
commands.append(remove_commands(to_delete, w['name']))
if obj[1]:
to_delete = {'access_groups': [{'acls': obj[1], 'afi': 'ipv6'}]}
commands.append(remove_commands(to_delete, w['name']))
if commands:
commands = list(itertools.chain(*commands))
if commands:
commandset.append(intf_command)
commandset.append(commands)
if commandset:
commandset = list(itertools.chain(*commandset))
return commandset
def get_acl_diff(self, w, h, intersection=False):
diff_v4 = []
diff_v6 = []
w_acls_v4 = []
w_acls_v6 = []
h_acls_v4 = []
h_acls_v6 = []
for w_group in w['access_groups']:
if w_group['afi'] == 'ipv4':
w_acls_v4 = w_group['acls']
if w_group['afi'] == 'ipv6':
w_acls_v6 = w_group['acls']
for h_group in h['access_groups']:
if h_group['afi'] == 'ipv4':
h_acls_v4 = h_group['acls']
if h_group['afi'] == 'ipv6':
h_acls_v6 = h_group['acls']
for item in w_acls_v4:
match = list(filter(lambda x: x['name'] == item['name'], h_acls_v4))
if match:
if item['direction'] == match[0]['direction']:
if intersection:
diff_v4.append(item)
else:
if not intersection:
diff_v4.append(item)
else:
if not intersection:
diff_v4.append(item)
for item in w_acls_v6:
match = list(filter(lambda x: x['name'] == item['name'], h_acls_v6))
if match:
if item['direction'] == match[0]['direction']:
if intersection:
diff_v6.append(item)
else:
if not intersection:
diff_v6.append(item)
else:
if not intersection:
diff_v6.append(item)
return diff_v4, diff_v6
def get_acls_from_afi(self, interface, afi, have):
config = []
for h in have:
if h['name'] == interface:
if 'access_groups' not in h.keys() or not h['access_groups']:
continue
if h['access_groups']:
for h_grp in h['access_groups']:
if h_grp['afi'] == afi:
config = h_grp['acls']
return config
def add_commands(want, interface):
commands = []
for w in want:
# This module was verified on an ios device since vEOS doesnot support
# acl_interfaces cnfiguration. In ios, ipv6 acl is configured as
# traffic-filter and in eos it is access-group
# a_cmd = "traffic-filter" if w['afi'] == 'ipv6' else "access-group"
a_cmd = "access-group"
afi = 'ip' if w['afi'] == 'ipv4' else w['afi']
if 'acls' in w.keys():
for acl in w['acls']:
commands.append(afi + " " + a_cmd + " " + acl['name'] + " " + acl['direction'])
return commands
def remove_commands(want, interface):
commands = []
if 'access_groups' not in want.keys() or not want['access_groups']:
return commands
for w in want['access_groups']:
# This module was verified on an ios device since vEOS doesnot support
# acl_interfaces cnfiguration. In ios, ipv6 acl is configured as
# traffic-filter and in eos it is access-group
# a_cmd = "traffic-filter" if w['afi'] == 'ipv6' else "access-group"
a_cmd = "access-group"
afi = 'ip' if w['afi'] == 'ipv4' else w['afi']
if 'acls' in w.keys():
for acl in w['acls']:
commands.append("no " + afi + " " + a_cmd + " " + acl['name'] + " " + acl['direction'])
return commands

@ -0,0 +1,133 @@
#
# -*- 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 eos 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.eos.argspec.acl_interfaces.acl_interfaces import Acl_interfacesArgs
class Acl_interfacesFacts(object):
""" The eos 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_device_data(self, connection):
return connection.get('show running-config | include interface | access-group | traffic-filter')
def populate_facts(self, connection, ansible_facts, data=None):
""" Populate the facts for acl_interfaces
:param connection: the device connection
:param ansible_facts: Facts dictionary
:param data: previously collected conf
:rtype: dictionary
:returns: facts
"""
if not data:
data = self.get_device_data(connection)
# split the config into instances of the resource
resource_delim = 'interface'
find_pattern = r'(?:^|\n)%s.*?(?=(?:^|\n)%s|$)' % (resource_delim,
resource_delim)
resources = [p.strip() for p in re.findall(find_pattern,
data,
re.DOTALL)]
objs = []
for resource in resources:
if resource:
obj = self.render_config(self.generated_spec, resource)
if obj:
objs.append(obj)
ansible_facts['ansible_network_resources'].pop('acl_interfaces', None)
facts = {}
if objs:
params = utils.validate_config(self.argument_spec, {'config': objs})
facts['acl_interfaces'] = [utils.remove_empties(cfg) for cfg in params['config']]
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)
access_group_list = []
access_group_v6_list = []
acls_list = []
group_list = []
group_dict = {}
config['name'] = utils.parse_conf_arg(conf, 'interface')
conf_lines = conf.split('\n')
for line in conf_lines:
if config['name'] in line:
continue
access_group = utils.parse_conf_arg(line, 'ip access-group')
# This module was verified on an ios device since vEOS doesnot support
# acl_interfaces cnfiguration. In ios, ipv6 acl is configured as
# traffic-filter and in eos it is access-group
# access_group_v6 = utils.parse_conf_arg(line, 'ipv6 traffic-filter')
access_group_v6 = utils.parse_conf_arg(line, 'ipv6 access-group')
if access_group:
access_group_list.append(access_group)
if access_group_v6:
access_group_v6_list.append(access_group_v6)
if access_group_list:
for acl in access_group_list:
a_name = acl.split()[0]
a_dir = acl.split()[1]
acls_dict = {"name": a_name, "direction": a_dir}
acls_list.append(acls_dict)
group_dict = {"afi": "ipv4", "acls": acls_list}
group_list.append(group_dict)
acls_list = []
if group_list:
config['access_groups'] = group_list
if access_group_v6_list:
for acl in access_group_v6_list:
a_name = acl.split()[0]
a_dir = acl.split()[1]
acls_dict = {"name": a_name, "direction": a_dir}
acls_list.append(acls_dict)
group_dict = {"acls": acls_list, "afi": "ipv6"}
group_list.append(group_dict)
acls_list = []
if group_list:
config['access_groups'] = group_list
return utils.remove_empties(config)

@ -21,6 +21,7 @@ from ansible.module_utils.network.eos.facts.lldp_global.lldp_global import Lldp_
from ansible.module_utils.network.eos.facts.lldp_interfaces.lldp_interfaces import Lldp_interfacesFacts
from ansible.module_utils.network.eos.facts.vlans.vlans import VlansFacts
from ansible.module_utils.network.eos.facts.legacy.base import Default, Hardware, Config, Interfaces
from ansible.module_utils.network.eos.facts.acl_interfaces.acl_interfaces import Acl_interfacesFacts
FACT_LEGACY_SUBSETS = dict(
@ -39,6 +40,7 @@ FACT_RESOURCE_SUBSETS = dict(
lldp_global=Lldp_globalFacts,
lldp_interfaces=Lldp_interfacesFacts,
vlans=VlansFacts,
acl_interfaces=Acl_interfacesFacts,
)

@ -0,0 +1,421 @@
#!/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 eos_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: eos_acl_interfaces
version_added: "2.10"
short_description: Manage adding and removing Access Control Lists (ACLs) from interfaces on devices running EOS software.
description:
- This module manages adding and removing Access Control Lists (ACLs) from interfaces on devices running EOS software.
author: GomathiSelvi S (@GomathiselviS)
options:
config:
description: A dictionary of ACL options for interfaces.
type: list
elements: dict
suboptions:
name:
description:
- Name/Identifier for the interface.
type: str
required: True
access_groups:
type: list
elements: dict
description:
- Specifies ACLs attached to the interfaces.
suboptions:
afi:
description:
- Specifies the AFI for the ACL(s) to be configured on this interface.
type: str
choices: ['ipv4', 'ipv6']
required: True
acls:
type: list
description:
- Specifies the ACLs for the provided AFI.
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.
type: str
choices: ['in', 'out']
required: True
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
version_added: "2.10"
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:
# -------------
#
# eos#sh running-config | include interface|access-group
# interface Ethernet1
# interface Ethernet2
# interface Ethernet3
- name: "Merge module attributes of given access-groups"
eos_acl_interfaces:
config:
- name: Ethernet2
access_groups:
- afi: ipv4
acls:
name: acl01
direction: in
- afi: ipv6
acls:
name: acl03
direction: out
state: merged
# Commands Fired:
# ---------------
#
# interface Ethernet2
# ip access-group acl01 in
# ipv6 access-group acl03 out
# After state:
# -------------
#
# eos#sh running-config | include interface| access-group
# interface Loopback888
# interface Ethernet1
# interface Ethernet2
# ip access-group acl01 in
# ipv6 access-group acl03 out
# interface Ethernet3
# Using Replaced
# Before state:
# -------------
#
# eos#sh running-config | include interface|access-group
# interface Ethernet1
# interface Ethernet2
# ip access-group acl01 in
# ipv6 access-group acl03 out
# interface Ethernet3
# ip access-group acl01 in
- name: "Replace module attributes of given access-groups"
eos_acl_interfaces:
config:
- name: Ethernet2
access_groups:
- afi: ipv4
acls:
name: acl01
direction: out
state: replaced
# Commands Fired:
# ---------------
#
# interface Ethernet2
# no ip access-group acl01 in
# no ipv6 access-group acl03 out
# ip access-group acl01 out
# After state:
# -------------
#
# eos#sh running-config | include interface| access-group
# interface Loopback888
# interface Ethernet1
# interface Ethernet2
# ip access-group acl01 out
# interface Ethernet3
# ip access-group acl01 in
# Using Overridden
# Before state:
# -------------
#
# eos#sh running-config | include interface|access-group
# interface Ethernet1
# interface Ethernet2
# ip access-group acl01 in
# ipv6 access-group acl03 out
# interface Ethernet3
# ip access-group acl01 in
- name: "Override module attributes of given access-groups"
eos_acl_interfaces:
config:
- name: Ethernet2
access_groups:
- afi: ipv4
acls:
name: acl01
direction: out
state: overridden
# Commands Fired:
# ---------------
#
# interface Ethernet2
# no ip access-group acl01 in
# no ipv6 access-group acl03 out
# ip access-group acl01 out
# interface Ethernet3
# no ip access-group acl01 in
# After state:
# -------------
#
# eos#sh running-config | include interface| access-group
# interface Loopback888
# interface Ethernet1
# interface Ethernet2
# ip access-group acl01 out
# interface Ethernet3
# Using Deleted
# Before state:
# -------------
#
# eos#sh running-config | include interface|access-group
# interface Ethernet1
# interface Ethernet2
# ip access-group acl01 in
# ipv6 access-group acl03 out
# interface Ethernet3
# ip access-group acl01 out
- name: "Delete module attributes of given access-groups"
eos_acl_interfaces:
config:
- name: Ethernet2
access_groups:
- afi: ipv4
acls:
name: acl01
direction: in
- afi: ipv6
acls:
name: acl03
direction: out
state: deleted
# Commands Fired:
# ---------------
#
# interface Ethernet2
# no ip access-group acl01 in
# no ipv6 access-group acl03 out
# After state:
# -------------
#
# eos#sh running-config | include interface| access-group
# interface Loopback888
# interface Ethernet1
# interface Ethernet2
# interface Ethernet3
# ip access-group acl01 out
# Before state:
# -------------
#
# eos#sh running-config | include interface| access-group
# interface Ethernet1
# interface Ethernet2
# ip access-group acl01 in
# ipv6 access-group acl03 out
# interface Ethernet3
# ip access-group acl01 out
- name: "Delete module attributes of given access-groups from ALL Interfaces"
eos_acl_interfaces:
config:
state: deleted
# Commands Fired:
# ---------------
#
# interface Ethernet2
# no ip access-group acl01 in
# no ipv6 access-group acl03 out
# interface Ethernet3
# no ip access-group acl01 out
# After state:
# -------------
#
# eos#sh running-config | include interface| access-group
# interface Loopback888
# interface Ethernet1
# interface Ethernet2
# interface Ethernet3
# Before state:
# -------------
#
# eos#sh running-config | include interface| access-group
# interface Ethernet1
# interface Ethernet2
# ip access-group acl01 in
# ipv6 access-group acl03 out
# interface Ethernet3
# ip access-group acl01 out
- name: "Delete acls under afi"
eos_acl_interfaces:
config:
- name: Ethernet3
access_groups:
- afi: "ipv4"
- name: Ethernet2
access_groups:
- afi: "ipv6"
state: deleted
# Commands Fired:
# ---------------
#
# interface Ethernet2
# no ipv6 access-group acl03 out
# interface Ethernet3
# no ip access-group acl01 out
# After state:
# -------------
#
# eos#sh running-config | include interface| access-group
# interface Loopback888
# interface Ethernet1
# interface Ethernet2
# ip access-group acl01 in
# interface Ethernet3
"""
RETURN = """
before:
description: The configuration prior to the model invocation.
returned: always
type: list
sample: >
The configuration returned will always be in the same format
of the parameters above.
after:
description: The resulting configuration model invocation.
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 Ethernet2
- ip access-group acl01 in
- ipv6 access-group acl03 out
- interface Ethernet3
- ip access-group acl01 out
"""
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.network.eos.argspec.acl_interfaces.acl_interfaces import Acl_interfacesArgs
from ansible.module_utils.network.eos.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',))]
module = AnsibleModule(argument_spec=Acl_interfacesArgs.argument_spec,
supports_check_mode=True)
result = Acl_interfaces(module).execute_module()
module.exit_json(**result)
if __name__ == '__main__':
main()

@ -268,7 +268,6 @@ def map_obj_to_commands(updates, module, warnings):
add('no protocol unix-socket')
else:
add('protocol unix-socket')
if needs_update('state'):
if want['state'] == 'stopped':
add('shutdown')

@ -0,0 +1,5 @@
---
# testcase: "[^_].*"
# eos_acl_interfaces will not run on vEOS
testcase: ""
test_items: []

@ -0,0 +1,2 @@
dependencies:
- prepare_eos_tests

@ -0,0 +1,17 @@
---
- 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 }}"
- 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

@ -0,0 +1,16 @@
---
- name: collect all eapi test cases
find:
paths: "{{ role_path }}/tests/eapi"
patterns: "{{ testcase }}.yaml"
delegate_to: localhost
register: test_cases
- name: set test_items
set_fact: test_items="{{ test_cases.files | map(attribute='path') | list }}"
- name: run test cases (connection=httpapi)
include: "{{ test_case_to_run }} ansible_connection=httpapi"
with_items: "{{ test_items }}"
loop_control:
loop_var: test_case_to_run

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

@ -0,0 +1,6 @@
interface GigabitEthernet0/0
ip access-group aclv401 in
ip access-group aclv402 out
ipv6 traffic-filter aclv601 out
interface GigabitEthernet0/1
ipv6 traffic-filter aclv601 in

@ -0,0 +1,24 @@
---
- name: Setup
eos_acl_interfaces:
config:
- name: "{{ Interfaces['int1'] }}"
access_groups:
- afi: ipv4
acls:
- name: aclv401
direction: in
- name: aclv402
direction: out
- afi: ipv6
acls:
- name: aclv601
direction: out
- name: "{{ Interfaces['int2'] }}"
access_groups:
- afi: ipv6
acls:
- name: aclv601
direction: in
state: merged
become: yes

@ -0,0 +1,8 @@
---
- name: Setup
eos_acl_interfaces:
config:
- name: "{{ Interfaces['int1'] }}"
- name: "{{ Interfaces['int2'] }}"
state: deleted
become: yes

@ -0,0 +1,57 @@
---
- debug:
msg: "Start eos_acl_interfaces deleted integration tests ansible_connection={{ ansible_connection }}"
- include_tasks: _populate.yaml
- block:
- name: Delete attributes of given acl_interfaces of an afi.
eos_acl_interfaces:
config:
- name: "{{ Interfaces['int1'] }}"
access_groups:
- afi: ipv6
state: deleted
register: result
- assert:
that:
- "result.commands|length == 2"
- "result.changed == true"
- "'no ipv6 traffic-filter aclv601 out' in result.commands"
- name: Delete attributes of given acl_interfaces configuration.
eos_acl_interfaces:
config:
- name: "{{ Interfaces['int1'] }}"
access_groups:
- afi: ipv4
acls:
- name: aclv401
direction: in
state: deleted
register: result
- assert:
that:
- "result.commands|length == 2"
- "result.changed == true"
- "'no ip access-group aclv401 in' in result.commands"
- name: Delete attributes of given acl_interfaces of an interface.
eos_acl_interfaces:
config:
- name: "{{ Interfaces['int1'] }}"
state: deleted
register: result
- assert:
that:
- "result.commands|length == 2"
- "result.changed == true"
- "'no ip access-group aclv402 out' in result.commands"
always:
- include_tasks: _remove_config.yaml

@ -0,0 +1,57 @@
- debug:
msg: "START eos_acl_interfaces empty_config integration tests on connection={{ ansible_connection }}"
- name: Merged with empty config should give appropriate error message
eos_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
eos_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
eos_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
eos_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
eos_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'

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

@ -0,0 +1,51 @@
---
- debug:
msg: "Start eos_acl_interfaces merged integration tests ansible_connection={{ ansible_connection }}"
- block:
- name: merge given acl interfaces configuration
eos_acl_interfaces: &merged
config:
- name: "{{ Interfaces['int1'] }}"
access_groups:
- afi: ipv4
acls:
- name: aclv401
direction: in
- name: aclv402
direction: out
- afi: ipv6
acls:
- name: aclv601
direction: out
- name: "{{ Interfaces['int2'] }}"
access_groups:
- afi: ipv6
acls:
- name: aclv601
direction: in
state: merged
become: yes
register: result
- assert:
that:
- "result.commands|length == 6"
- "result.changed == true"
- "result.commands|symmetric_difference(merged.commands) == []"
- name: Idempotency check
eos_acl_interfaces: *merged
become: yes
register: result
- assert:
that:
- "result.commands|length == 0"
- "result.changed == false"
always:
- include_tasks: _remove_config.yaml

@ -0,0 +1,40 @@
---
- debug:
msg: "Start eos_acl_interfaces overridden integration tests ansible_connection={{ ansible_connection }}"
- include_tasks: _populate.yaml
- block:
- name: override given acl interfaces configuration
eos_acl_interfaces: &overridden
config:
- name: "{{ Interfaces['int2'] }}"
access_groups:
- afi: ipv4
acls:
- name: aclv401
direction: in
state: overridden
become: yes
register: result
- assert:
that:
- "result.commands|length == 7"
- "result.changed == true"
- "result.commands|symmetric_difference(overridden.commands) == []"
- name: Idempotency check
eos_acl_interfaces: *overridden
become: yes
register: result
- assert:
that:
- "result.commands|length == 0"
- "result.changed == false"
always:
- include_tasks: _remove_config.yaml

@ -0,0 +1,16 @@
---
- debug:
msg: "START eos_acl_interfaces parsed integration tests on connection={{ ansible_connection }}"
- name: Provide the running configuration for parsing (config to be parsed)
eos_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) == []"

@ -0,0 +1,36 @@
---
- debug:
msg: "Start eos_acl_interfaces rendered integration tests ansible_connection={{ ansible_connection }}"
- block:
- name: render given acl interfaces configuration
eos_acl_interfaces:
config:
- name: "{{ Interfaces['int1'] }}"
access_groups:
- afi: ipv4
acls:
- name: aclv401
direction: in
- name: aclv402
direction: out
- afi: ipv6
acls:
- name: aclv601
direction: out
- name: "{{ Interfaces['int2'] }}"
access_groups:
- afi: ipv6
acls:
- name: aclv601
direction: in
state: rendered
become: yes
register: result
- assert:
that:
- "result.changed == false"
- "result.rendered|symmetric_difference(merged.commands) == []"

@ -0,0 +1,40 @@
---
- debug:
msg: "Start eos_acl_interfaces replced integration tests ansible_connection={{ ansible_connection }}"
- include_tasks: _populate.yaml
- block:
- name: replace given acl interfaces configuration
eos_acl_interfaces: &replaced
config:
- name: "{{ Interfaces['int2'] }}"
access_groups:
- afi: ipv4
acls:
- name: aclv401
direction: in
state: replaced
become: yes
register: result
- assert:
that:
- "result.commands|length == 3"
- "result.changed == true"
- "result.commands|symmetric_difference(replaced.commands) == []"
- name: Idempotency check
eos_acl_interfaces: *replaced
become: yes
register: result
- assert:
that:
- "result.commands|length == 0"
- "result.changed == false"
always:
- include_tasks: _remove_config.yaml

@ -0,0 +1,83 @@
Interfaces:
int1:
GigabitEthernet0/0
int2:
GigabitEthernet0/1
deleted1:
commands:
- interface GigabitEthernet0/0
- no ip access-group aclv401 in
- no ip access-group aclv402 out
- no ipv6 traffic-filter aclv601 out
merged:
commands:
- interface GigabitEthernet0/0
- ip access-group aclv401 in
- ip access-group aclv402 out
- ipv6 traffic-filter aclv601 out
- interface GigabitEthernet0/1
- ipv6 traffic-filter aclv601 in
replaced:
commands:
- interface GigabitEthernet0/1
- no ipv6 traffic-filter aclv601 in
- ip access-group aclv401 in
overridden:
commands:
- interface GigabitEthernet0/0
- no ip access-group aclv401 in
- no ip access-group aclv402 out
- no ipv6 traffic-filter aclv601 out
- interface GigabitEthernet0/1
- no ipv6 traffic-filter aclv601 in
- ip access-group aclv401 in
# Interface names will changed based on the platform used. Following configs are wrt ios.
gathered:
config:
- name: Loopback888
- access_groups:
- acls:
- direction: in
name: aclv401
- direction: out
name: aclv402
afi: ipv4
- acls:
- direction: out
name: aclv601
afi: ipv6
name: GigabitEthernet0/0
- access_groups:
- acls:
- direction: in
name: aclv601
afi: ipv6
name: GigabitEthernet0/1
- name: GigabitEthernet0/2
- name: GigabitEthernet0/3
parsed:
config:
- access_groups:
- acls:
- direction: in
name: aclv401
- direction: out
name: aclv402
afi: ipv4
- acls:
- direction: out
name: aclv601
afi: ipv6
name: GigabitEthernet0/0
- access_groups:
- acls:
- direction: in
name: aclv601
afi: ipv6
name: GigabitEthernet0/1

@ -49,9 +49,12 @@ def load_fixture(name):
class TestEosModule(ModuleTestCase):
def execute_module(self, failed=False, changed=False, commands=None, inputs=None, sort=True, defaults=False, transport='cli'):
def execute_module(self, failed=False, changed=False, commands=None, inputs=None, sort=True, defaults=False, transport='cli', filename=None):
self.load_fixtures(commands, transport=transport)
if filename is None:
self.load_fixtures(commands, transport=transport)
else:
self.load_fixtures(commands, transport=transport, filename=filename)
if failed:
result = self.failed()
@ -98,5 +101,5 @@ class TestEosModule(ModuleTestCase):
self.assertEqual(result['changed'], changed, result)
return result
def load_fixtures(self, commands=None, transport='cli'):
def load_fixtures(self, commands=None, transport='cli', filename=None):
pass

@ -0,0 +1,6 @@
interface GigabitEthernet0/0
ip access-group aclv404 in
ipv6 access-group aclv601 out
interface GigabitEthernet0/1
ipv6 access-group aclv601 in

@ -0,0 +1,288 @@
#
# (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.eos import eos_acl_interfaces
from ansible.module_utils.network.eos.config.acl_interfaces.acl_interfaces import add_commands
from units.modules.utils import set_module_args
from .eos_module import TestEosModule, load_fixture
import itertools
class TestEosAclInterfacesModule(TestEosModule):
module = eos_acl_interfaces
def setUp(self):
super(TestEosAclInterfacesModule, 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.eos.providers.providers.CliProvider.edit_config'
)
self.edit_config = self.mock_edit_config.start()
self.mock_execute_show_command = patch(
'ansible.module_utils.network.eos.facts.acl_interfaces.acl_interfaces.Acl_interfacesFacts.get_device_data'
)
self.execute_show_command = self.mock_execute_show_command.start()
def tearDown(self):
super(TestEosAclInterfacesModule, 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, transport='cli', filename=None):
if filename is None:
filename = 'eos_acl_interfaces_config.cfg'
def load_from_file(*args, **kwargs):
output = load_fixture(filename)
return output
self.execute_show_command.side_effect = load_from_file
def test_eos_acl_interfaces_merged(self):
set_module_args(
dict(config=[
dict(name="GigabitEthernet0/0",
access_groups=[
dict(afi="ipv4",
acls=[
dict(name="aclv401",
direction="in"),
dict(name="aclv402",
direction="out")
]),
dict(afi="ipv6",
acls=[
dict(name="aclv601",
direction="in")
])
]),
dict(name="GigabitEthernet0/1",
access_groups=[
dict(afi="ipv4",
acls=[
dict(name="aclv401",
direction="in")
])
])
], state="merged"))
commands = ['interface GigabitEthernet0/0', 'ip access-group aclv401 in',
'ip access-group aclv402 out', 'ipv6 access-group aclv601 in', 'interface GigabitEthernet0/1',
'ip access-group aclv401 in']
result = self.execute_module(changed=True, commands=commands)
def test_eos_acl_interfaces_merged_idempotent(self):
set_module_args(
dict(config=[
dict(name="GigabitEthernet0/0",
access_groups=[
dict(afi="ipv4",
acls=[
dict(name="aclv404",
direction="in"),
]),
dict(afi="ipv6",
acls=[dict(name="aclv601", direction="out")])
]),
dict(name="GigabitEthernet0/1",
access_groups=[
dict(afi="ipv6",
acls=[dict(name="aclv601", direction="in")])
])
], state="merged"))
result = self.execute_module(changed=False, commands=[])
def test_eos_acl_interfaces_replaced(self):
set_module_args(
dict(config=[
dict(name="GigabitEthernet0/0",
access_groups=[
dict(afi="ipv4",
acls=[
dict(name="aclv401",
direction="in"),
dict(name="aclv402",
direction="out")
]),
])
], state="replaced"))
commands = ['interface GigabitEthernet0/0', 'no ip access-group aclv404 in',
'no ipv6 access-group aclv601 out', 'ip access-group aclv402 out',
'ip access-group aclv401 in']
result = self.execute_module(changed=True, commands=commands)
def test_eos_acl_interfaces_replaced_idempotent(self):
set_module_args(
dict(config=[
dict(name="GigabitEthernet0/0",
access_groups=[
dict(afi="ipv4",
acls=[
dict(name="aclv404",
direction="in"),
]),
dict(afi="ipv6",
acls=[dict(name="aclv601", direction="out")])
]),
], state="replaced"))
result = self.execute_module(changed=False, commands=[])
def test_eos_acl_interfaces_overridden(self):
set_module_args(
dict(config=[
dict(name="GigabitEthernet0/0",
access_groups=[
dict(afi="ipv4",
acls=[
dict(name="aclv401",
direction="in"),
dict(name="aclv402",
direction="out")
]),
])
], state="overridden"))
commands = ['interface GigabitEthernet0/0', 'no ip access-group aclv404 in',
'no ipv6 access-group aclv601 out', 'ip access-group aclv402 out',
'ip access-group aclv401 in', 'interface GigabitEthernet0/1',
'no ipv6 access-group aclv601 in']
result = self.execute_module(changed=True, commands=commands)
def test_eos_acl_interfaces_overridden_idempotent(self):
set_module_args(
dict(config=[
dict(name="GigabitEthernet0/0",
access_groups=[
dict(afi="ipv4",
acls=[
dict(name="aclv404",
direction="in"),
]),
dict(afi="ipv6",
acls=[dict(name="aclv601", direction="out")])
]),
dict(name="GigabitEthernet0/1",
access_groups=[
dict(afi="ipv6",
acls=[
dict(name="aclv601",
direction="in")
])
])
], state="overridden"))
result = self.execute_module(changed=False, commands=[])
def test_eos_acl_interfaces_deletedafi(self):
set_module_args(
dict(config=[
dict(name="GigabitEthernet0/0",
access_groups=[
dict(afi="ipv6")])
], state="deleted"))
commands = ['interface GigabitEthernet0/0', 'no ipv6 access-group aclv601 out']
result = self.execute_module(changed=True, commands=commands)
def test_eos_acl_interfaces_deletedint(self):
set_module_args(
dict(config=[
dict(name="GigabitEthernet0/0")
], state="deleted"))
commands = ['interface GigabitEthernet0/0', 'no ipv6 access-group aclv601 out',
'no ip access-group aclv404 in']
result = self.execute_module(changed=True, commands=commands)
def test_eos_acl_interfaces_deletedacl(self):
set_module_args(
dict(config=[
dict(name="GigabitEthernet0/0",
access_groups=[
dict(afi="ipv4",
acls=[
dict(name="aclv404",
direction="in"),
]),
])
], state="deleted"))
commands = ['interface GigabitEthernet0/0', 'no ip access-group aclv404 in']
result = self.execute_module(changed=True, commands=commands)
def test_eos_acl_interfaces_gathered(self):
set_module_args(
dict(state="gathered"))
result = self.execute_module(changed=False, filename='eos_acl_interfaces_config.cfg')
commands = []
gathered_list = [{'access_groups': [{'acls': [{'direction': 'in', 'name': 'aclv404'}], 'afi': 'ipv4'},
{'acls': [{'direction': 'out', 'name': 'aclv601'}], 'afi': 'ipv6'}],
'name': 'GigabitEthernet0/0'},
{'access_groups': [{'acls': [{'direction': 'in', 'name': 'aclv601'}], 'afi': 'ipv6'}],
'name': 'GigabitEthernet0/1'}]
self.assertEqual(gathered_list, result['gathered'])
def test_eos_acl_interfaces_parsed(self):
set_module_args(
dict(running_config="interface GigabitEthernet0/0\nipv6 access-group aclv601 out\nip access-group aclv404 in",
state="parsed"))
result = self.execute_module(changed=False)
parsed_list = [{'access_groups': [{'acls': [{'direction': 'in', 'name': 'aclv404'}], 'afi': 'ipv4'},
{'acls': [{'direction': 'out', 'name': 'aclv601'}], 'afi': 'ipv6'}],
'name': 'GigabitEthernet0/0'}]
self.assertEqual(parsed_list, result['parsed'])
def test_eos_acl_interfaces_rendered(self):
set_module_args(
dict(config=[
dict(name="GigabitEthernet0/0",
access_groups=[
dict(afi="ipv4",
acls=[
dict(name="aclv401",
direction="in"),
dict(name="aclv402",
direction="out")
]),
dict(afi="ipv6",
acls=[dict(name="aclv601", direction="in")])
]),
dict(name="GigabitEthernet0/1",
access_groups=[
dict(afi="ipv4",
acls=[
dict(name="aclv401",
direction="in")
])
])
], state="rendered"))
commands = ['interface GigabitEthernet0/0', 'ip access-group aclv401 in',
'ip access-group aclv402 out', 'ipv6 access-group aclv601 in', 'interface GigabitEthernet0/1',
'ip access-group aclv401 in']
result = self.execute_module(changed=False)
self.assertEqual(sorted(result['rendered']), sorted(commands), result['rendered'])
Loading…
Cancel
Save