diff --git a/lib/ansible/module_utils/network/iosxr/argspec/acl_interfaces/__init__.py b/lib/ansible/module_utils/network/iosxr/argspec/acl_interfaces/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/lib/ansible/module_utils/network/iosxr/argspec/acl_interfaces/acl_interfaces.py b/lib/ansible/module_utils/network/iosxr/argspec/acl_interfaces/acl_interfaces.py new file mode 100644 index 00000000000..ca3035f742b --- /dev/null +++ b/lib/ansible/module_utils/network/iosxr/argspec/acl_interfaces/acl_interfaces.py @@ -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 iosxr_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 iosxr_acl_interfaces module + """ + def __init__(self, **kwargs): + pass + + argument_spec = { + 'running_config': { + 'type': 'str' + }, + 'config': { + 'elements': 'dict', + 'options': { + 'access_groups': { + 'elements': 'dict', + 'options': { + 'acls': { + 'elements': 'dict', + 'options': { + 'direction': { + 'choices': ['in', 'out'], + 'type': 'str', + 'required': True + }, + 'name': { + 'type': 'str', + 'required': True + } + }, + 'type': 'list' + }, + 'afi': { + 'choices': ['ipv4', 'ipv6'], + 'type': 'str', + 'required': True + } + }, + 'type': 'list' + }, + 'name': { + 'type': 'str', + 'required': True + } + }, + 'type': 'list' + }, + 'state': { + 'choices': [ + 'merged', 'replaced', 'overridden', 'deleted', 'gathered', + 'parsed', 'rendered' + ], + 'default': + 'merged', + 'type': + 'str' + } + } # pylint: disable=C0301 diff --git a/lib/ansible/module_utils/network/iosxr/config/acl_interfaces/__init__.py b/lib/ansible/module_utils/network/iosxr/config/acl_interfaces/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/lib/ansible/module_utils/network/iosxr/config/acl_interfaces/acl_interfaces.py b/lib/ansible/module_utils/network/iosxr/config/acl_interfaces/acl_interfaces.py new file mode 100644 index 00000000000..c29a8db28cd --- /dev/null +++ b/lib/ansible/module_utils/network/iosxr/config/acl_interfaces/acl_interfaces.py @@ -0,0 +1,318 @@ +# +# -*- 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 iosxr_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.iosxr.facts.facts import Facts +from ansible.module_utils.network.iosxr.utils.utils import normalize_interface, diff_list_of_dicts, pad_commands +from ansible.module_utils.network.common.utils \ + import ( + to_list, + dict_diff, + search_obj_in_list, + remove_empties + ) + + +class Acl_interfaces(ConfigBase): + """ + The iosxr_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() + + 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) + + 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'] + if want: + for item in want: + item['name'] = normalize_interface(item['name']) + if 'members' in want and want['members']: + for item in want['members']: + item.update({ + 'member': + normalize_interface(item['member']), + 'mode': + item['mode'] + }) + 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 + """ + state = self._module.params['state'] + commands = [] + + 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.extend(self._state_overridden(want, have)) + + elif state == 'deleted': + if not want: + for intf in have: + commands.extend(self._state_deleted({}, intf)) + else: + for item in want: + obj_in_have = search_obj_in_list(item['name'], have) or {} + commands.extend( + self._state_deleted(remove_empties(item), obj_in_have)) + + else: + # Instead of passing entire want and have + # list of dictionaries to the respective + # _state_* methods we are passing the want + # and have dictionaries per interface + for item in want: + name = item['name'] + obj_in_have = search_obj_in_list(name, have) or {} + + if state == 'merged' or state == 'rendered': + commands.extend(self._state_merged(item, obj_in_have)) + + elif state == 'replaced': + commands.extend(self._state_replaced(item, obj_in_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 + """ + commands = [] + + want = remove_empties(want) + + delete_commands = [] + for have_afi in have.get('access_groups', []): + want_afi = search_obj_in_list(have_afi['afi'], + want.get('access_groups', []), + key='afi') or {} + afi = have_afi.get('afi') + + for acl in have_afi.get('acls', []): + if acl not in want_afi.get('acls', []): + delete_commands.extend( + self._compute_commands(afi, [acl], remove=True)) + + if delete_commands: + pad_commands(delete_commands, want['name']) + commands.extend(delete_commands) + + merged_commands = self._state_merged(want, have) + if merged_commands and delete_commands: + del merged_commands[0] + + commands.extend(merged_commands) + + return commands + + 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 + """ + commands = [] + + for have_intf in have: + want_intf = search_obj_in_list(have_intf['name'], want) or {} + if not want_intf: + commands.extend(self._state_deleted(want_intf, have_intf)) + + for want_intf in want: + have_intf = search_obj_in_list(want_intf['name'], have) or {} + commands.extend(self._state_replaced(want_intf, have_intf)) + + return commands + + 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 + """ + commands = [] + + want = remove_empties(want) + + for want_afi in want.get('access_groups', []): + have_afi = search_obj_in_list(want_afi['afi'], + have.get('access_groups', []), + key='afi') or {} + delta = diff_list_of_dicts(want_afi['acls'], + have_afi.get('acls', []), + key='direction') + commands.extend(self._compute_commands(want_afi['afi'], delta)) + + if commands: + pad_commands(commands, want['name']) + + return commands + + 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 + """ + commands = [] + + # This handles deletion for both empty/no config + # and just interface name provided. + if 'access_groups' not in want: + for x in have.get('access_groups', []): + afi = x.get('afi') + for have_acl in x.get('acls', []): + commands.extend( + self._compute_commands(afi, [have_acl], remove=True)) + + else: + for want_afi in want['access_groups']: + have_afi = search_obj_in_list(want_afi['afi'], + have.get('access_groups', []), + key='afi') or {} + afi = have_afi.get('afi') + + # If only the AFI has be specified, we + # delete all the access-groups for that AFI + if 'acls' not in want_afi: + for have_acl in have_afi.get('acls', []): + commands.extend( + self._compute_commands(afi, [have_acl], + remove=True)) + + # If one or more acl has been explicitly specified, we + # delete that and leave the rest untouched + else: + for acl in want_afi['acls']: + if acl in have_afi.get('acls', []): + commands.extend( + self._compute_commands(afi, [acl], + remove=True)) + + if commands: + pad_commands(commands, have['name']) + + return commands + + def _compute_commands(self, afi, delta, remove=False): + updates = [] + map_dir = {'in': 'ingress', 'out': 'egress'} + + for x in delta: + cmd = "{0} access-group {1} {2}".format(afi, x['name'], + map_dir[x['direction']]) + if remove: + cmd = "no " + cmd + updates.append(cmd) + + return updates diff --git a/lib/ansible/module_utils/network/iosxr/facts/acl_interfaces/__init__.py b/lib/ansible/module_utils/network/iosxr/facts/acl_interfaces/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/lib/ansible/module_utils/network/iosxr/facts/acl_interfaces/acl_interfaces.py b/lib/ansible/module_utils/network/iosxr/facts/acl_interfaces/acl_interfaces.py new file mode 100644 index 00000000000..93aa174b169 --- /dev/null +++ b/lib/ansible/module_utils/network/iosxr/facts/acl_interfaces/acl_interfaces.py @@ -0,0 +1,104 @@ +# +# -*- 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 iosxr 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.six import iteritems +from ansible.module_utils.network.common import utils +from ansible.module_utils.network.iosxr.argspec.acl_interfaces.acl_interfaces import Acl_interfacesArgs + + +class Acl_interfacesFacts(object): + """ The iosxr 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 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 = connection.get_config(flags='interface') + + interfaces = data.split('interface ') + + objs = [] + for interface in interfaces: + obj = self.render_config(self.generated_spec, interface) + if obj: + objs.append(obj) + + ansible_facts['ansible_network_resources'].pop('acl_interfaces', None) + facts = {} + 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) + config['access_groups'] = [] + map_dir = {'ingress': 'in', 'egress': 'out'} + + match = re.search(r'(?:preconfigure)*(?:\s*)(\S+)', conf, re.M) + if match: + config['name'] = match.group(1) + acls = {'ipv4': [], 'ipv6': []} + for item in conf.split('\n'): + item = item.strip() + if item.startswith('ipv4 access-group'): + acls['ipv4'].append(item) + elif item.startswith('ipv6 access-group'): + acls['ipv6'].append(item) + + for key, value in iteritems(acls): + if value: + entry = {'afi': key, 'acls': []} + for item in value: + entry['acls'].append({'name': item.split()[2], 'direction': map_dir[item.split()[3]]}) + config['access_groups'].append(entry) + + config['access_groups'] = sorted(config['access_groups'], key=lambda i: i['afi']) + + return utils.remove_empties(config) diff --git a/lib/ansible/module_utils/network/iosxr/facts/facts.py b/lib/ansible/module_utils/network/iosxr/facts/facts.py index 6ecc800dbd6..b8af928a730 100644 --- a/lib/ansible/module_utils/network/iosxr/facts/facts.py +++ b/lib/ansible/module_utils/network/iosxr/facts/facts.py @@ -23,7 +23,7 @@ from ansible.module_utils.network.iosxr.facts.interfaces.interfaces import Inter from ansible.module_utils.network.iosxr.facts.lag_interfaces.lag_interfaces import Lag_interfacesFacts from ansible.module_utils.network.iosxr.facts.l2_interfaces.l2_interfaces import L2_InterfacesFacts from ansible.module_utils.network.iosxr.facts.l3_interfaces.l3_interfaces import L3_InterfacesFacts - +from ansible.module_utils.network.iosxr.facts.acl_interfaces.acl_interfaces import Acl_interfacesFacts FACT_LEGACY_SUBSETS = dict( default=Default, @@ -39,7 +39,8 @@ FACT_RESOURCE_SUBSETS = dict( interfaces=InterfacesFacts, l2_interfaces=L2_InterfacesFacts, lag_interfaces=Lag_interfacesFacts, - l3_interfaces=L3_InterfacesFacts + l3_interfaces=L3_InterfacesFacts, + acl_interfaces=Acl_interfacesFacts ) diff --git a/lib/ansible/module_utils/network/iosxr/utils/utils.py b/lib/ansible/module_utils/network/iosxr/utils/utils.py index b6d0bef5ac8..c90a99260ae 100644 --- a/lib/ansible/module_utils/network/iosxr/utils/utils.py +++ b/lib/ansible/module_utils/network/iosxr/utils/utils.py @@ -98,7 +98,10 @@ def filter_dict_having_none_value(want, have): test_dict.update({'ipv4': test_key_dict}) # Checks if want doesn't have secondary IP but have has secondary IP set elif have.get('ipv4'): - if [True for each_have in have.get('ipv4') if 'secondary' in each_have]: + if [ + True for each_have in have.get('ipv4') + if 'secondary' in each_have + ]: test_dict.update({'ipv4': {'secondary': True}}) if k == 'l2protocol': if want[k] != have.get('l2protocol') and have.get('l2protocol'): @@ -167,7 +170,7 @@ def pad_commands(commands, interface): commands.insert(0, 'interface {0}'.format(interface)) -def diff_list_of_dicts(w, h): +def diff_list_of_dicts(w, h, key='member'): """ Returns a list containing diff between two list of dictionaries @@ -179,11 +182,11 @@ def diff_list_of_dicts(w, h): diff = [] for w_item in w: - h_item = search_obj_in_list(w_item['member'], h, key='member') or {} + h_item = search_obj_in_list(w_item[key], h, key=key) or {} d = dict_diff(h_item, w_item) if d: - if 'member' not in d.keys(): - d['member'] = w_item['member'] + if key not in d.keys(): + d[key] = w_item[key] diff.append(d) return diff @@ -196,7 +199,9 @@ def validate_ipv4(value, module): module.fail_json(msg='address format is /, got invalid format {0}'.format(value)) if not is_masklen(address[1]): - module.fail_json(msg='invalid value for mask: {0}, mask should be in range 0-32'.format(address[1])) + module.fail_json( + msg='invalid value for mask: {0}, mask should be in range 0-32' + .format(address[1])) def validate_ipv6(value, module): diff --git a/lib/ansible/modules/network/iosxr/iosxr_acl_interfaces.py b/lib/ansible/modules/network/iosxr/iosxr_acl_interfaces.py new file mode 100644 index 00000000000..2cfbcf93528 --- /dev/null +++ b/lib/ansible/modules/network/iosxr/iosxr_acl_interfaces.py @@ -0,0 +1,743 @@ +#!/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 iosxr_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: iosxr_acl_interfaces +version_added: "2.10" +short_description: Manage Access Control Lists (ACLs) configuration for interfaces in IOS-XR. +description: + - This module manages adding and removing Access Control Lists (ACLs) from interfaces on devices running IOS-XR software. +author: Nilashish Chakraborty (@NilashishC) +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/IPv6 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 + B(show running-config interface). + 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: +# ------------- +# +# RP/0/RP0/CPU0:ios#sh running-config interface +# Wed Jan 15 12:22:32.911 UTC +# interface MgmtEth0/RP0/CPU0/0 +# ipv4 address dhcp +# ! +# interface GigabitEthernet0/0/0/0 +# shutdown +# ! +# interface GigabitEthernet0/0/0/1 +# shutdown +# ! + +- name: Merge the provided configuration with the existing running configuration + iosxr_acl_interfaces: + config: + - name: GigabitEthernet0/0/0/0 + access_groups: + - afi: ipv4 + acls: + - name: acl_1 + direction: in + - name: acl_2 + direction: out + - afi: ipv6 + acls: + - name: acl6_1 + direction: in + - name: acl6_2 + direction: out + + - name: GigabitEthernet0/0/0/1 + access_groups: + - afi: ipv4 + acls: + - name: acl_1 + direction: out + state: merged + +# After state: +# ------------- +# +# RP/0/RP0/CPU0:ios#sh running-config interface +# Wed Jan 15 12:27:49.378 UTC +# interface MgmtEth0/RP0/CPU0/0 +# ipv4 address dhcp +# ! +# interface GigabitEthernet0/0/0/0 +# shutdown +# ipv4 access-group acl_1 ingress +# ipv4 access-group acl_2 egress +# ipv6 access-group acl6_1 ingress +# ipv6 access-group acl6_2 egress +# ! +# interface GigabitEthernet0/0/0/1 +# shutdown +# ipv4 access-group acl_1 egress +# ! + +# Using merged to update interface ACL configuration + +# Before state: +# ------------- +# +# RP/0/RP0/CPU0:ios#sh running-config interface +# Wed Jan 15 12:27:49.378 UTC +# interface MgmtEth0/RP0/CPU0/0 +# ipv4 address dhcp +# ! +# interface GigabitEthernet0/0/0/0 +# shutdown +# ipv4 access-group acl_1 ingress +# ipv4 access-group acl_2 egress +# ipv6 access-group acl6_1 ingress +# ipv6 access-group acl6_2 egress +# ! +# interface GigabitEthernet0/0/0/1 +# shutdown +# ipv4 access-group acl_1 egress +# ! +# + +- name: Update acl_interfaces configuration using merged + iosxr_acl_interfaces: + config: + - name: GigabitEthernet0/0/0/1 + access_groups: + - afi: ipv4 + acls: + - name: acl_2 + direction: out + - name: acl_1 + direction: in + state: merged + +# After state: +# ------------- +# +# RP/0/RP0/CPU0:ios#sh running-config interface +# Wed Jan 15 12:27:49.378 UTC +# interface MgmtEth0/RP0/CPU0/0 +# ipv4 address dhcp +# ! +# interface GigabitEthernet0/0/0/0 +# shutdown +# ipv4 access-group acl_1 ingress +# ipv4 access-group acl_2 egress +# ipv6 access-group acl6_1 ingress +# ipv6 access-group acl6_2 egress +# ! +# interface GigabitEthernet0/0/0/1 +# shutdown +# ipv4 access-group acl_1 ingress +# ipv4 access-group acl_2 egress +# ! +# + +# Using replaced + +# Before state: +# ------------- +# +# RP/0/RP0/CPU0:ios#sh running-config interface +# Wed Jan 15 12:34:56.689 UTC +# interface MgmtEth0/RP0/CPU0/0 +# ipv4 address dhcp +# ! +# interface GigabitEthernet0/0/0/0 +# shutdown +# ipv4 access-group acl_1 ingress +# ipv4 access-group acl_2 egress +# ipv6 access-group acl6_1 ingress +# ipv6 access-group acl6_2 egress +# ! +# interface GigabitEthernet0/0/0/1 +# shutdown +# ipv4 access-group acl_1 egress +# ! + +- name: Replace device configurations of listed interface with provided configurations + iosxr_acl_interfaces: + config: + - name: GigabitEthernet0/0/0/0 + access_groups: + - afi: ipv6 + acls: + - name: acl6_3 + direction: in + state: replaced + +# After state: +# ------------- +# +# RP/0/RP0/CPU0:ios#sh running-config interface +# Wed Jan 15 12:34:56.689 UTC +# interface MgmtEth0/RP0/CPU0/0 +# ipv4 address dhcp +# ! +# interface GigabitEthernet0/0/0/0 +# shutdown +# ipv6 access-group acl6_3 ingress +# ! +# interface GigabitEthernet0/0/0/1 +# shutdown +# ipv4 access-group acl_1 egress +# ! +# + +# Using overridden + +# Before state: +# ------------- +# +# RP/0/RP0/CPU0:ios#sh running-config interface +# Wed Jan 15 12:34:56.689 UTC +# interface MgmtEth0/RP0/CPU0/0 +# ipv4 address dhcp +# ! +# interface GigabitEthernet0/0/0/0 +# shutdown +# ipv4 access-group acl_1 ingress +# ipv4 access-group acl_2 egress +# ipv6 access-group acl6_1 ingress +# ipv6 access-group acl6_2 egress +# ! +# interface GigabitEthernet0/0/0/1 +# shutdown +# ipv4 access-group acl_1 egress +# ! +# + +- name: Overridde all interface ACL configuration with provided configuration + iosxr_acl_interfaces: + config: + - name: GigabitEthernet0/0/0/1 + access_groups: + - afi: ipv4 + acls: + - name: acl_2 + direction: in + - afi: ipv6 + acls: + - name: acl6_3 + direction: out + state: overridden + +# After state: +# ------------- +# +# RP/0/RP0/CPU0:ios#sh running-config interface +# Wed Jan 15 12:34:56.689 UTC +# interface MgmtEth0/RP0/CPU0/0 +# ipv4 address dhcp +# ! +# interface GigabitEthernet0/0/0/0 +# shutdown +# ! +# interface GigabitEthernet0/0/0/1 +# shutdown +# ipv4 access-group acl_2 ingress +# ipv6 access-group acl6_3 egress +# ! +# + +# Using 'deleted' to delete all ACL attributes of a single interface + +# Before state: +# ------------- +# +# RP/0/RP0/CPU0:ios#sh running-config interface +# Wed Jan 15 12:34:56.689 UTC +# interface MgmtEth0/RP0/CPU0/0 +# ipv4 address dhcp +# ! +# interface GigabitEthernet0/0/0/0 +# shutdown +# ipv4 access-group acl_1 ingress +# ipv4 access-group acl_2 egress +# ipv6 access-group acl6_1 ingress +# ipv6 access-group acl6_2 egress +# ! +# interface GigabitEthernet0/0/0/1 +# shutdown +# ipv4 access-group acl_1 egress +# ! +# + +- name: Delete all ACL attributes of GigabitEthernet0/0/0/1 + iosxr_acl_interfaces: + config: + - name: GigabitEthernet0/0/0/1 + state: deleted + +# After state: +# ------------- +# +# RP/0/RP0/CPU0:ios#sh running-config interface +# Wed Jan 15 12:34:56.689 UTC +# interface MgmtEth0/RP0/CPU0/0 +# ipv4 address dhcp +# ! +# interface GigabitEthernet0/0/0/0 +# shutdown +# ipv4 access-group acl_1 ingress +# ipv4 access-group acl_2 egress +# ipv6 access-group acl6_1 ingress +# ipv6 access-group acl6_2 egress +# ! +# interface GigabitEthernet0/0/0/1 +# shutdown +# ! +# + +# Using 'deleted' to delete a single attached ACL from an interface + +# Before state: +# ------------- +# +# RP/0/RP0/CPU0:ios#sh running-config interface +# Wed Jan 15 12:34:56.689 UTC +# interface MgmtEth0/RP0/CPU0/0 +# ipv4 address dhcp +# ! +# interface GigabitEthernet0/0/0/0 +# shutdown +# ipv4 access-group acl_1 ingress +# ipv4 access-group acl_2 egress +# ipv6 access-group acl6_1 ingress +# ipv6 access-group acl6_2 egress +# ! +# interface GigabitEthernet0/0/0/1 +# shutdown +# ipv4 access-group acl_1 egress +# ! +# + +- name: Delete a single ACL attached to GigabitEthernet0/0/0/0 + iosxr_acl_interfaces: + config: + - name: GigabitEthernet0/0/0/0 + access_groups: + - afi: ipv4 + acls: + - name: acl_2 + direction: out + state: deleted + +# After state: +# ------------- +# +# RP/0/RP0/CPU0:ios#sh running-config interface +# Wed Jan 15 12:34:56.689 UTC +# interface MgmtEth0/RP0/CPU0/0 +# ipv4 address dhcp +# ! +# interface GigabitEthernet0/0/0/0 +# shutdown +# ipv4 access-group acl_1 ingress +# ipv6 access-group acl6_1 ingress +# ipv6 access-group acl6_2 egress +# ! +# interface GigabitEthernet0/0/0/1 +# shutdown +# ! +# + +# Using 'deleted' to delete all ACLs of a particular AFI from an interface + +# Before state: +# ------------- +# +# RP/0/RP0/CPU0:ios#sh running-config interface +# Wed Jan 15 12:34:56.689 UTC +# interface MgmtEth0/RP0/CPU0/0 +# ipv4 address dhcp +# ! +# interface GigabitEthernet0/0/0/0 +# shutdown +# ipv4 access-group acl_1 ingress +# ipv4 access-group acl_2 egress +# ipv6 access-group acl6_1 ingress +# ipv6 access-group acl6_2 egress +# ! +# interface GigabitEthernet0/0/0/1 +# shutdown +# ipv4 access-group acl_1 egress +# ! +# + +- name: Delete all IPv6 ACLs attached to GigabitEthernet0/0/0/0 + iosxr_acl_interfaces: + config: + - name: GigabitEthernet0/0/0/0 + access_groups: + - afi: ipv6 + state: deleted + +# After state: +# ------------- +# +# RP/0/RP0/CPU0:ios#sh running-config interface +# Wed Jan 15 12:34:56.689 UTC +# interface MgmtEth0/RP0/CPU0/0 +# ipv4 address dhcp +# ! +# interface GigabitEthernet0/0/0/0 +# shutdown +# ipv4 access-group acl_1 ingress +# ipv4 access-group acl_2 egress +# ! +# interface GigabitEthernet0/0/0/1 +# shutdown +# ! +# + +# Using 'deleted' to remove all ACLs attached to all the interfaces in the device + +# Before state: +# ------------- +# +# RP/0/RP0/CPU0:ios#sh running-config interface +# Wed Jan 15 12:34:56.689 UTC +# interface MgmtEth0/RP0/CPU0/0 +# ipv4 address dhcp +# ! +# interface GigabitEthernet0/0/0/0 +# shutdown +# ipv4 access-group acl_1 ingress +# ipv4 access-group acl_2 egress +# ipv6 access-group acl6_1 ingress +# ipv6 access-group acl6_2 egress +# ! +# interface GigabitEthernet0/0/0/1 +# shutdown +# ipv4 access-group acl_1 egress +# ! +# + +- name: Delete all ACL interfaces configuration from the device + iosxr_acl_interfaces: + state: deleted + +# After state: +# ------------- +# +# RP/0/RP0/CPU0:ios#sh running-config interface +# Wed Jan 15 12:34:56.689 UTC +# interface MgmtEth0/RP0/CPU0/0 +# ipv4 address dhcp +# ! +# interface GigabitEthernet0/0/0/0 +# shutdown +# ! +# interface GigabitEthernet0/0/0/1 +# shutdown +# ! +# + +# Using parsed + +# parsed.cfg +# ------------ +# +# interface MgmtEth0/RP0/CPU0/0 +# ipv4 address dhcp +# ! +# interface GigabitEthernet0/0/0/0 +# shutdown +# ipv4 access-group acl_1 ingress +# ipv4 access-group acl_2 egress +# ipv6 access-group acl6_1 ingress +# ipv6 access-group acl6_2 egress +# ! +# interface GigabitEthernet0/0/0/1 +# shutdown +# ipv4 access-group acl_1 egress +# ! + +# - name: Convert ACL interfaces config to argspec without connecting to the appliance +# iosxr_acl_interfaces: +# running_config: "{{ lookup('file', './parsed.cfg') }}" +# state: parsed + + +# Task Output (redacted) +# ----------------------- + +# "parsed": [ +# { +# "name": "MgmtEth0/RP0/CPU0/0" +# }, +# { +# "access_groups": [ +# { +# "acls": [ +# { +# "direction": "in", +# "name": "acl_1" +# }, +# { +# "direction": "out", +# "name": "acl_2" +# } +# ], +# "afi": "ipv4" +# }, +# { +# "acls": [ +# { +# "direction": "in", +# "name": "acl6_1" +# }, +# { +# "direction": "out", +# "name": "acl6_2" +# } +# ], +# "afi": "ipv6" +# } +# ], +# "name": "GigabitEthernet0/0/0/0" +# }, +# { +# "access_groups": [ +# { +# "acls": [ +# { +# "direction": "out", +# "name": "acl_1" +# } +# ], +# "afi": "ipv4" +# } +# ], +# "name": "GigabitEthernet0/0/0/1" +# } +# ] +# } + + +# Using gathered + +- name: Gather ACL interfaces facts using gathered state + iosxr_acl_interfaces: + state: gathered + + +# Task Output (redacted) +# ----------------------- +# +# "gathered": [ +# { +# "name": "MgmtEth0/RP0/CPU0/0" +# }, +# { +# "access_groups": [ +# { +# "acls": [ +# { +# "direction": "in", +# "name": "acl_1" +# }, +# { +# "direction": "out", +# "name": "acl_2" +# } +# ], +# "afi": "ipv4" +# } +# "name": "GigabitEthernet0/0/0/0" +# }, +# { +# "access_groups": [ +# { +# "acls": [ +# { +# "direction": "in", +# "name": "acl6_1" +# } +# ], +# "afi": "ipv6" +# } +# "name": "GigabitEthernet0/0/0/1" +# } +# ] + + +# Using rendered + +- name: Render platform specific commands from task input using rendered state + iosxr_acl_interfaces: + config: + - name: GigabitEthernet0/0/0/0 + access_groups: + - afi: ipv4 + acls: + - name: acl_1 + direction: in + - name: acl_2 + direction: out + state: rendered + +# Task Output (redacted) +# ----------------------- + +# "rendered": [ +# "interface GigabitEthernet0/0/0/0", +# "ipv4 access-group acl_1 ingress", +# "ipv4 access-group acl_2 egress" +# ] +""" +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 GigabitEthernet0/0/0/1" + - "ipv4 access-group acl_1 ingress" + - "ipv4 access-group acl_2 egress" + - "ipv6 access-group acl6_1 ingress" + - "interface GigabitEthernet0/0/0/2" + - "no ipv4 access-group acl_3 ingress" + - "ipv4 access-group acl_4 egress" +""" + + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.network.iosxr.argspec.acl_interfaces.acl_interfaces import Acl_interfacesArgs +from ansible.module_utils.network.iosxr.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, required_if=required_if, + supports_check_mode=True) + + result = Acl_interfaces(module).execute_module() + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/test/integration/targets/iosxr_acl_interfaces/defaults/main.yaml b/test/integration/targets/iosxr_acl_interfaces/defaults/main.yaml new file mode 100644 index 00000000000..164afead284 --- /dev/null +++ b/test/integration/targets/iosxr_acl_interfaces/defaults/main.yaml @@ -0,0 +1,3 @@ +--- +testcase: "[^_].*" +test_items: [] diff --git a/test/integration/targets/iosxr_acl_interfaces/tasks/cli.yaml b/test/integration/targets/iosxr_acl_interfaces/tasks/cli.yaml new file mode 100644 index 00000000000..337e34133b0 --- /dev/null +++ b/test/integration/targets/iosxr_acl_interfaces/tasks/cli.yaml @@ -0,0 +1,20 @@ +--- +- 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 diff --git a/test/integration/targets/iosxr_acl_interfaces/tasks/main.yaml b/test/integration/targets/iosxr_acl_interfaces/tasks/main.yaml new file mode 100644 index 00000000000..415c99d8b12 --- /dev/null +++ b/test/integration/targets/iosxr_acl_interfaces/tasks/main.yaml @@ -0,0 +1,2 @@ +--- +- { include: cli.yaml, tags: ['cli'] } diff --git a/test/integration/targets/iosxr_acl_interfaces/tests/cli/_populate.yaml b/test/integration/targets/iosxr_acl_interfaces/tests/cli/_populate.yaml new file mode 100644 index 00000000000..ac996f685f8 --- /dev/null +++ b/test/integration/targets/iosxr_acl_interfaces/tests/cli/_populate.yaml @@ -0,0 +1,31 @@ +--- +- name: Populate the device with ACLs + iosxr_config: + lines: | + ipv4 access-list acl_1 + 10 permit ipv4 any any + ipv4 access-list acl_2 + 10 permit ipv4 any any + ipv4 access-list acl_3 + 10 permit ipv4 any any + ipv6 access-list acl6_1 + 10 permit ipv6 any any + ipv6 access-list acl6_2 + 10 permit ipv6 any any + ipv6 access-list acl6_3 + 10 permit ipv6 any any + +- name: Setup ACL interfaces configuration for GigabitEthernet0/0/0/0 + iosxr_config: + lines: | + ipv4 access-group acl_1 ingress + ipv4 access-group acl_2 egress + ipv6 access-group acl6_1 ingress + ipv6 access-group acl6_2 egress + parents: interface GigabitEthernet0/0/0/0 + +- name: Setup ACL interfaces configuration for GigabitEthernet0/0/0/1 + iosxr_config: + lines: ipv4 access-group acl_1 egress + parents: interface GigabitEthernet0/0/0/1 + diff --git a/test/integration/targets/iosxr_acl_interfaces/tests/cli/_remove_config.yaml b/test/integration/targets/iosxr_acl_interfaces/tests/cli/_remove_config.yaml new file mode 100644 index 00000000000..27d1b3f3955 --- /dev/null +++ b/test/integration/targets/iosxr_acl_interfaces/tests/cli/_remove_config.yaml @@ -0,0 +1,34 @@ +--- +- name: Remove/Default Resources + cli_config: + config: "{{ lines }}" + vars: + lines: | + default interface GigabitEthernet0/0/0/0 + default interface GigabitEthernet0/0/0/1 + no ipv4 access-list acl_1 + no ipv4 access-list acl_2 + no ipv6 access-list acl6_1 + no ipv6 access-list acl6_2 + no ipv6 access-list acl6_3 + +- name: Initialize interfaces + iosxr_config: + lines: shutdown + parents: "{{ item }}" + loop: + - interface GigabitEthernet0/0/0/0 + - interface GigabitEthernet0/0/0/1 + +# To make sure our assertions are not affected by +# spill overs from previous tests +- name: Remove unwanted interfaces from config + iosxr_config: + lines: + - "no interface GigabitEthernet{{ item }}" + loop: + - 0/0/0/2 + - 0/0/0/3 + - 0/0/0/4 + - 0/0/0/5 + ignore_errors: yes diff --git a/test/integration/targets/iosxr_acl_interfaces/tests/cli/deleted.yaml b/test/integration/targets/iosxr_acl_interfaces/tests/cli/deleted.yaml new file mode 100644 index 00000000000..72daf5bf890 --- /dev/null +++ b/test/integration/targets/iosxr_acl_interfaces/tests/cli/deleted.yaml @@ -0,0 +1,85 @@ +--- +- debug: + msg: "Start iosxr_acl_interfaces deleted integration tests ansible_connection={{ ansible_connection }}" + +- include_tasks: _remove_config.yaml + +- include_tasks: _populate.yaml + +- block: + - name: Delete ACL attributes of GigabitEthernet0/0/0/1 + iosxr_acl_interfaces: &deleted + config: + - name: GigabitEthernet0/0/0/1 + state: deleted + register: result + + - assert: + that: + - "'interface GigabitEthernet0/0/0/1' in result.commands" + - "'no ipv4 access-group acl_1 egress' in result.commands" + - "result.commands|length == 2" + + - name: Delete ACL attributes of GigabitEthernet0/0/0/1 (IDEMPOTENT) + iosxr_acl_interfaces: *deleted + register: result + + - assert: + that: + - "result.changed == False" + - "result.commands|length == 0" + + - name: Delete a single ACL attached to GigabitEthernet0/0/0/0 + iosxr_acl_interfaces: &deleted_1 + config: + - name: GigabitEthernet0/0/0/0 + access_groups: + - afi: ipv4 + acls: + - name: acl_2 + direction: out + state: deleted + register: result + + - assert: + that: + - "'interface GigabitEthernet0/0/0/0' in result.commands" + - "'no ipv4 access-group acl_2 egress' in result.commands" + - "result.commands|length == 2" + + - name: Delete a single ACL attached to GigabitEthernet0/0/0/0 (IDEMPOTENT) + iosxr_acl_interfaces: *deleted_1 + register: result + + - assert: + that: + - "result.changed == False" + - "result.commands|length == 0" + + - name: Delete all IPv6 ACLs attached to GigabitEthernet0/0/0/0 + iosxr_acl_interfaces: &deleted_2 + config: + - name: GigabitEthernet0/0/0/0 + access_groups: + - afi: ipv6 + state: deleted + register: result + + - assert: + that: + - "'interface GigabitEthernet0/0/0/0' in result.commands" + - "'no ipv6 access-group acl6_1 ingress' in result.commands" + - "'no ipv6 access-group acl6_2 egress' in result.commands" + - "result.commands|length == 3" + + - name: Delete all IPv6 ACLs attached to GigabitEthernet0/0/0/0 (IDEMPOTENT) + iosxr_acl_interfaces: *deleted_2 + register: result + + - assert: + that: + - "result.changed == False" + - "result.commands|length == 0" + + always: + - include_tasks: _remove_config.yaml diff --git a/test/integration/targets/iosxr_acl_interfaces/tests/cli/deleted_all.yaml b/test/integration/targets/iosxr_acl_interfaces/tests/cli/deleted_all.yaml new file mode 100644 index 00000000000..c591affb9d7 --- /dev/null +++ b/test/integration/targets/iosxr_acl_interfaces/tests/cli/deleted_all.yaml @@ -0,0 +1,46 @@ +--- +- debug: + msg: "Start iosxr_acl_interfaces deleted integration tests ansible_connection={{ ansible_connection }}" + +- include_tasks: _remove_config.yaml + +- include_tasks: _populate.yaml + +- block: + - name: Delete all ACL interfaces configuration from the device + iosxr_acl_interfaces: &deleted_3 + state: deleted + register: result + + - name: Assert that the before dicts were correctly generated + assert: + that: + - "{{ merged['after'] | symmetric_difference(result['before']) |length == 0 }}" + + - name: Assert that the correct set of commands were generated + assert: + that: + - "{{ deleted['commands'] | symmetric_difference(result['commands']) |length == 0 }}" + + - name: Assert that the after dicts were correctly generated + assert: + that: + - "{{ deleted['after'] | symmetric_difference(result['after']) |length == 0 }}" + + - name: Delete ACL attributes of all interfaces (IDEMPOTENT) + iosxr_acl_interfaces: *deleted_3 + register: result + + - name: Assert that the previous task was idempotent + assert: + that: + - "result.changed == false" + - "result.commands|length == 0" + + - name: Assert that the before dicts were correctly generated + assert: + that: + - "{{ deleted['after'] | symmetric_difference(result['before']) |length == 0 }}" + + always: + - include_tasks: _remove_config.yaml diff --git a/test/integration/targets/iosxr_acl_interfaces/tests/cli/empty_config.yaml b/test/integration/targets/iosxr_acl_interfaces/tests/cli/empty_config.yaml new file mode 100644 index 00000000000..3ca3fd8673a --- /dev/null +++ b/test/integration/targets/iosxr_acl_interfaces/tests/cli/empty_config.yaml @@ -0,0 +1,58 @@ +--- +- debug: + msg: "START iosxr_acl_interfaces empty_config integration tests on connection={{ ansible_connection }}" + +- name: Merged with empty config should give appropriate error message + iosxr_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 + iosxr_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 + iosxr_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 + iosxr_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 + iosxr_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' diff --git a/test/integration/targets/iosxr_acl_interfaces/tests/cli/fixtures/parsed.cfg b/test/integration/targets/iosxr_acl_interfaces/tests/cli/fixtures/parsed.cfg new file mode 100644 index 00000000000..818233602cf --- /dev/null +++ b/test/integration/targets/iosxr_acl_interfaces/tests/cli/fixtures/parsed.cfg @@ -0,0 +1,14 @@ +interface MgmtEth0/0/CPU0/0 + ipv4 address dhcp +! +interface GigabitEthernet0/0/0/0 + shutdown + ipv4 access-group acl_1 ingress + ipv4 access-group acl_2 egress + ipv6 access-group acl6_1 ingress + ipv6 access-group acl6_2 egress +! +interface GigabitEthernet0/0/0/1 + shutdown + ipv4 access-group acl_1 egress +! \ No newline at end of file diff --git a/test/integration/targets/iosxr_acl_interfaces/tests/cli/gathered.yaml b/test/integration/targets/iosxr_acl_interfaces/tests/cli/gathered.yaml new file mode 100644 index 00000000000..8d58622e6fd --- /dev/null +++ b/test/integration/targets/iosxr_acl_interfaces/tests/cli/gathered.yaml @@ -0,0 +1,57 @@ +--- +- debug: + msg: "START iosxr_acl_interfaces gathered integration tests on connection={{ ansible_connection }}" + +- include_tasks: _remove_config.yaml + +- name: Populate the device with ACLs + iosxr_config: + lines: | + ipv4 access-list acl_1 + 10 permit ipv4 any any + ipv4 access-list acl_2 + 10 permit ipv4 any any + ipv6 access-list acl6_1 + 10 permit ipv6 any any + ipv6 access-list acl6_2 + 10 permit ipv6 any any + +- block: + - name: Merge the provided configuration with the existing running configuration + iosxr_acl_interfaces: &merged + config: + - name: GigabitEthernet0/0/0/0 + access_groups: + - afi: ipv4 + acls: + - name: acl_1 + direction: in + - name: acl_2 + direction: out + - afi: ipv6 + acls: + - name: acl6_1 + direction: in + - name: acl6_2 + direction: out + + - name: GigabitEthernet0/0/0/1 + access_groups: + - afi: ipv4 + acls: + - name: acl_1 + direction: out + state: merged + register: result + + - name: Gather ACL interfaces facts using gathered state + iosxr_acl_interfaces: + state: gathered + register: result + + - name: Assert that facts were correctly generated + assert: + that: "{{ merged['after'] | symmetric_difference(result['gathered']) |length == 0 }}" + + always: + - include_tasks: _remove_config.yaml diff --git a/test/integration/targets/iosxr_acl_interfaces/tests/cli/merged.yaml b/test/integration/targets/iosxr_acl_interfaces/tests/cli/merged.yaml new file mode 100644 index 00000000000..240243511aa --- /dev/null +++ b/test/integration/targets/iosxr_acl_interfaces/tests/cli/merged.yaml @@ -0,0 +1,115 @@ +--- +- debug: + msg: "START iosxr_acl_interfaces merged integration tests on connection={{ ansible_connection }}" + +- include_tasks: _remove_config.yaml + +- name: Populate the device with ACLs + iosxr_config: + lines: | + ipv4 access-list acl_1 + 10 permit ipv4 any any + ipv4 access-list acl_2 + 10 permit ipv4 any any + ipv6 access-list acl6_1 + 10 permit ipv6 any any + ipv6 access-list acl6_2 + 10 permit ipv6 any any + +- block: + - name: Merge the provided configuration with the existing running configuration + iosxr_acl_interfaces: &merged + config: + - name: GigabitEthernet0/0/0/0 + access_groups: + - afi: ipv4 + acls: + - name: acl_1 + direction: in + - name: acl_2 + direction: out + - afi: ipv6 + acls: + - name: acl6_1 + direction: in + - name: acl6_2 + direction: out + + - name: GigabitEthernet0/0/0/1 + access_groups: + - afi: ipv4 + acls: + - name: acl_1 + direction: out + state: merged + register: result + + - name: Assert that before dicts were correctly generated + assert: + that: "{{ merged['before'] | symmetric_difference(result['before']) |length == 0 }}" + + - name: Assert that correct set of commands were generated + assert: + that: + - "{{ merged['commands'] | symmetric_difference(result['commands']) |length == 0 }}" + + - name: Assert that after dicts was correctly generated + assert: + that: + - "{{ merged['after'] | symmetric_difference(result['after']) |length == 0 }}" + + - name: Merge the provided configuration with the existing running configuration (IDEMPOTENT) + iosxr_acl_interfaces: *merged + register: result + + - name: Assert that the previous task was idempotent + assert: + that: + - "result['changed'] == false" + - "result.commands|length == 0" + + - name: Assert that before dicts were correctly generated + assert: + that: + - "{{ merged['after'] | symmetric_difference(result['before']) |length == 0 }}" + + - name: Update acl_interfaces configuration using merged + iosxr_acl_interfaces: &merged_update + config: + - name: GigabitEthernet0/0/0/1 + access_groups: + - afi: ipv4 + acls: + - name: acl_2 + direction: out + - name: acl_1 + direction: in + state: merged + register: result + + - name: Assert that before dicts were correctly generated + assert: + that: "{{ merged['update_before'] | symmetric_difference(result['before']) |length == 0 }}" + + - name: Assert that correct set of commands were generated + assert: + that: + - "{{ merged['update_commands'] | symmetric_difference(result['commands']) |length == 0 }}" + + - name: Assert that after dicts was correctly generated + assert: + that: + - "{{ merged['update_after'] | symmetric_difference(result['after']) |length == 0 }}" + + - name: Update acl_interfaces configuration using merged (IDEMPOTENT) + iosxr_acl_interfaces: *merged_update + register: result + + - name: Assert that the previous task was idempotent + assert: + that: + - "result['changed'] == false" + - "result.commands|length == 0" + + always: + - include_tasks: _remove_config.yaml diff --git a/test/integration/targets/iosxr_acl_interfaces/tests/cli/overridden.yaml b/test/integration/targets/iosxr_acl_interfaces/tests/cli/overridden.yaml new file mode 100644 index 00000000000..55f3d8a4e0e --- /dev/null +++ b/test/integration/targets/iosxr_acl_interfaces/tests/cli/overridden.yaml @@ -0,0 +1,64 @@ +--- +- debug: + msg: "START iosxr_acl_interfaces overridden integration tests on connection={{ ansible_connection }}" + +- include_tasks: _remove_config.yaml + +- include_tasks: _populate.yaml + +- block: + - name: Overridde all interface ACL configuration with provided configuration + iosxr_acl_interfaces: &overridden + config: + - name: GigabitEthernet0/0/0/0 + access_groups: + - afi: ipv6 + acls: + - name: acl6_3 + direction: in + + - name: GigabitEthernet0/0/0/1 + access_groups: + - afi: ipv4 + acls: + - name: acl_2 + direction: in + - afi: ipv6 + acls: + - name: acl6_3 + direction: out + state: overridden + register: result + + - name: Assert that correct set of commands were generated + assert: + that: + - "{{ overridden['commands'] | symmetric_difference(result['commands']) |length == 0 }}" + + - name: Assert that before dicts are correctly generated + assert: + that: + - "{{ merged['after'] | symmetric_difference(result['before']) |length == 0 }}" + + - name: Assert that after dict is correctly generated + assert: + that: + - "{{ overridden['after'] | symmetric_difference(result['after']) |length == 0 }}" + + - name: Overridde all interface LACP configuration with provided configuration (IDEMPOTENT) + iosxr_acl_interfaces: *overridden + register: result + + - name: Assert that task was idempotent + assert: + that: + - "result['changed'] == false" + - "result.commands|length == 0" + + - name: Assert that before dict is correctly generated + assert: + that: + - "{{ overridden['after'] | symmetric_difference(result['before']) |length == 0 }}" + + always: + - include_tasks: _remove_config.yaml diff --git a/test/integration/targets/iosxr_acl_interfaces/tests/cli/parsed.yaml b/test/integration/targets/iosxr_acl_interfaces/tests/cli/parsed.yaml new file mode 100644 index 00000000000..56ae51eeb46 --- /dev/null +++ b/test/integration/targets/iosxr_acl_interfaces/tests/cli/parsed.yaml @@ -0,0 +1,14 @@ +--- +- debug: + msg: "START iosxr_acl_interfaces parsed integration tests on connection={{ ansible_connection }}" + +- name: Parse externally provided ACL interfaces config to agnostic model + iosxr_acl_interfaces: + running_config: "{{ lookup('file', './fixtures/parsed.cfg') }}" + state: parsed + register: result + +- name: Assert that config was correctly parsed + assert: + that: + - "{{ merged['after'] | symmetric_difference(result['parsed']) |length == 0 }}" \ No newline at end of file diff --git a/test/integration/targets/iosxr_acl_interfaces/tests/cli/rendered.yaml b/test/integration/targets/iosxr_acl_interfaces/tests/cli/rendered.yaml new file mode 100644 index 00000000000..93a0480b451 --- /dev/null +++ b/test/integration/targets/iosxr_acl_interfaces/tests/cli/rendered.yaml @@ -0,0 +1,35 @@ +--- +- debug: + msg: "START iosxr_acl_interfaces rendered integration tests on connection={{ ansible_connection }}" + +- name: Render platform specific commands from task input using rendered state + iosxr_acl_interfaces: + config: + - name: GigabitEthernet0/0/0/0 + access_groups: + - afi: ipv4 + acls: + - name: acl_1 + direction: in + - name: acl_2 + direction: out + - afi: ipv6 + acls: + - name: acl6_1 + direction: in + - name: acl6_2 + direction: out + + - name: GigabitEthernet0/0/0/1 + access_groups: + - afi: ipv4 + acls: + - name: acl_1 + direction: out + state: rendered + register: result + +- name: Assert that correct set of commands were rendered + assert: + that: + - "{{ merged['commands'] | symmetric_difference(result['rendered']) |length == 0 }}" \ No newline at end of file diff --git a/test/integration/targets/iosxr_acl_interfaces/tests/cli/replaced.yaml b/test/integration/targets/iosxr_acl_interfaces/tests/cli/replaced.yaml new file mode 100644 index 00000000000..aeaadac44d0 --- /dev/null +++ b/test/integration/targets/iosxr_acl_interfaces/tests/cli/replaced.yaml @@ -0,0 +1,53 @@ +--- +- debug: + msg: "START iosxr_acl_interfaces replaced integration tests on connection={{ ansible_connection }}" + +- include_tasks: _remove_config.yaml + +- include_tasks: _populate.yaml + +- block: + - name: Replace device configurations of listed interface with provided configurations + iosxr_acl_interfaces: &replaced + config: + - name: GigabitEthernet0/0/0/0 + access_groups: + - afi: ipv6 + acls: + - name: acl6_3 + direction: in + state: replaced + register: result + + - name: Assert that correct set of commands were generated + assert: + that: + - "{{ replaced['commands'] | symmetric_difference(result['commands']) |length == 0 }}" + + - name: Assert that before dicts are correctly generated + assert: + that: + - "{{ merged['after'] | symmetric_difference(result['before']) |length == 0 }}" + + - name: Assert that after dict is correctly generated + assert: + that: + - "{{ replaced['after'] | symmetric_difference(result['after']) |length == 0 }}" + + - name: Replace device configurations of listed interfaces with provided configurarions (IDEMPOTENT) + iosxr_acl_interfaces: *replaced + register: result + + - name: Assert that task was idempotent + assert: + that: + - "result['changed'] == false" + - "result.commands|length == 0" + + - name: Assert that before dict is correctly generated + assert: + that: + - "{{ replaced['after'] | symmetric_difference(result['before']) |length == 0 }}" + + always: + - include_tasks: _remove_config.yaml diff --git a/test/integration/targets/iosxr_acl_interfaces/tests/cli/rtt.yaml b/test/integration/targets/iosxr_acl_interfaces/tests/cli/rtt.yaml new file mode 100644 index 00000000000..f3cc7f44af8 --- /dev/null +++ b/test/integration/targets/iosxr_acl_interfaces/tests/cli/rtt.yaml @@ -0,0 +1,102 @@ +--- +- debug: + msg: "START iosxr_acl_interfaces round trip integration tests on connection={{ ansible_connection }}" + +- block: + - include_tasks: _remove_config.yaml + + - name: Populate the device with ACLs + iosxr_config: + lines: | + ipv4 access-list acl_1 + 10 permit ipv4 any any + ipv4 access-list acl_2 + 10 permit ipv4 any any + ipv4 access-list acl_3 + 10 permit ipv4 any any + ipv6 access-list acl6_1 + 10 permit ipv6 any any + ipv6 access-list acl6_2 + 10 permit ipv6 any any + ipv6 access-list acl6_3 + 10 permit ipv6 any any + + - name: Apply the provided configuration (base config) + iosxr_acl_interfaces: + config: + - name: GigabitEthernet0/0/0/0 + access_groups: + - afi: ipv4 + acls: + - name: acl_1 + direction: in + - name: acl_2 + direction: out + + - afi: ipv6 + acls: + - name: acl6_1 + direction: in + - name: acl6_2 + direction: out + + - name: GigabitEthernet0/0/0/1 + access_groups: + - afi: ipv4 + acls: + - name: acl_1 + direction: out + state: merged + + - name: Gather interfaces facts + iosxr_facts: + gather_subset: + - "!all" + - "!min" + gather_network_resources: + - acl_interfaces + + - name: Apply the provided configuration (config to be reverted) + iosxr_acl_interfaces: + config: + - name: GigabitEthernet0/0/0/1 + access_groups: + - afi: ipv4 + acls: + - name: acl_1 + direction: in + - name: acl_2 + direction: out + + - afi: ipv6 + acls: + - name: acl6_1 + direction: in + - name: acl6_2 + direction: out + + - name: GigabitEthernet0/0/0/0 + access_groups: + - afi: ipv4 + acls: + - name: acl_1 + direction: out + state: overridden + register: result + + - name: Assert that changes were applied + assert: + that: "{{ round_trip['after'] | symmetric_difference(result['after']) |length == 0 }}" + + - name: Revert back to base config using facts round trip + iosxr_acl_interfaces: + config: "{{ ansible_facts['network_resources']['acl_interfaces'] }}" + state: overridden + register: revert + + - name: Assert that config was reverted + assert: + that: "{{ merged['after'] | symmetric_difference(revert['after']) |length == 0 }}" + + always: + - include_tasks: _remove_config.yaml diff --git a/test/integration/targets/iosxr_acl_interfaces/vars/main.yaml b/test/integration/targets/iosxr_acl_interfaces/vars/main.yaml new file mode 100644 index 00000000000..e7933631156 --- /dev/null +++ b/test/integration/targets/iosxr_acl_interfaces/vars/main.yaml @@ -0,0 +1,205 @@ +--- +merged: + before: + - name: MgmtEth0/0/CPU0/0 + + - name: GigabitEthernet0/0/0/0 + + - name: GigabitEthernet0/0/0/1 + + commands: + - interface GigabitEthernet0/0/0/0 + - ipv4 access-group acl_1 ingress + - ipv4 access-group acl_2 egress + - ipv6 access-group acl6_1 ingress + - ipv6 access-group acl6_2 egress + - interface GigabitEthernet0/0/0/1 + - ipv4 access-group acl_1 egress + + update_commands: + - interface GigabitEthernet0/0/0/1 + - ipv4 access-group acl_2 egress + - ipv4 access-group acl_1 ingress + + after: + - name: MgmtEth0/0/CPU0/0 + + - name: GigabitEthernet0/0/0/0 + access_groups: + - acls: + - name: acl_1 + direction: in + - name: acl_2 + direction: out + afi: ipv4 + + - acls: + - name: acl6_1 + direction: in + - name: acl6_2 + direction: out + afi: ipv6 + + - name: GigabitEthernet0/0/0/1 + access_groups: + - acls: + - name: acl_1 + direction: out + afi: ipv4 + + update_before: + - name: MgmtEth0/0/CPU0/0 + + - access_groups: + - acls: + - direction: in + name: acl_1 + - direction: out + name: acl_2 + afi: ipv4 + + - acls: + - direction: in + name: acl6_1 + - direction: out + name: acl6_2 + afi: ipv6 + name: GigabitEthernet0/0/0/0 + + - access_groups: + - acls: + - direction: out + name: acl_1 + afi: ipv4 + name: GigabitEthernet0/0/0/1 + + update_after: + - name: MgmtEth0/0/CPU0/0 + + - access_groups: + - acls: + - direction: in + name: acl_1 + - direction: out + name: acl_2 + afi: ipv4 + - acls: + - direction: in + name: acl6_1 + - direction: out + name: acl6_2 + afi: ipv6 + name: GigabitEthernet0/0/0/0 + + - access_groups: + - acls: + - direction: in + name: acl_1 + - direction: out + name: acl_2 + afi: ipv4 + name: GigabitEthernet0/0/0/1 + + +replaced: + commands: + - interface GigabitEthernet0/0/0/0 + - no ipv4 access-group acl_1 ingress + - no ipv4 access-group acl_2 egress + - no ipv6 access-group acl6_1 ingress + - no ipv6 access-group acl6_2 egress + - ipv6 access-group acl6_3 ingress + + after: + - name: MgmtEth0/0/CPU0/0 + + - name: GigabitEthernet0/0/0/0 + access_groups: + - afi: ipv6 + acls: + - name: acl6_3 + direction: in + + - name: GigabitEthernet0/0/0/1 + access_groups: + - acls: + - name: acl_1 + direction: out + afi: ipv4 + +overridden: + commands: + - interface GigabitEthernet0/0/0/0 + - no ipv4 access-group acl_1 ingress + - no ipv4 access-group acl_2 egress + - no ipv6 access-group acl6_1 ingress + - no ipv6 access-group acl6_2 egress + - ipv6 access-group acl6_3 ingress + - interface GigabitEthernet0/0/0/1 + - no ipv4 access-group acl_1 egress + - ipv4 access-group acl_2 ingress + - ipv6 access-group acl6_3 egress + + after: + - name: MgmtEth0/0/CPU0/0 + + - name: GigabitEthernet0/0/0/0 + access_groups: + - afi: ipv6 + acls: + - name: acl6_3 + direction: in + + - name: GigabitEthernet0/0/0/1 + access_groups: + - acls: + - name: acl_2 + direction: in + afi: ipv4 + + - acls: + - name: acl6_3 + direction: out + afi: ipv6 + + +deleted: + commands: + - interface GigabitEthernet0/0/0/0 + - no ipv4 access-group acl_1 ingress + - no ipv4 access-group acl_2 egress + - no ipv6 access-group acl6_1 ingress + - no ipv6 access-group acl6_2 egress + - interface GigabitEthernet0/0/0/1 + - no ipv4 access-group acl_1 egress + + after: + - name: MgmtEth0/0/CPU0/0 + + - name: GigabitEthernet0/0/0/0 + + - name: GigabitEthernet0/0/0/1 + +round_trip: + after: + - name: MgmtEth0/0/CPU0/0 + - access_groups: + - acls: + - direction: out + name: acl_1 + afi: ipv4 + name: GigabitEthernet0/0/0/0 + - access_groups: + - acls: + - direction: in + name: acl_1 + - direction: out + name: acl_2 + afi: ipv4 + - acls: + - direction: in + name: acl6_1 + - direction: out + name: acl6_2 + afi: ipv6 + name: GigabitEthernet0/0/0/1 \ No newline at end of file