From f3ddf1bc95af28a140f23b2b3f85078f8bd864c6 Mon Sep 17 00:00:00 2001 From: Adharsh Srivats R Date: Sun, 1 Mar 2020 23:22:32 -0500 Subject: [PATCH] NX-OS ACL interfaces module (#67505) * Rebase * Completed integration tests * Added unit tests * Added warning detection * Updated tests * Completed tests * Linting Linting II YAML Lint Linting * Updated review changes * Updated examples, fixed reviews * Added failure condition * Resolved merge conflict --- .../nxos/argspec/acl_interfaces/__init__.py | 0 .../argspec/acl_interfaces/acl_interfaces.py | 91 ++++ .../nxos/config/acl_interfaces/__init__.py | 0 .../config/acl_interfaces/acl_interfaces.py | 303 +++++++++++++ .../nxos/facts/acl_interfaces/__init__.py | 0 .../facts/acl_interfaces/acl_interfaces.py | 119 +++++ .../module_utils/network/nxos/facts/facts.py | 8 +- .../network/nxos/nxos_acl_interfaces.py | 408 ++++++++++++++++++ lib/ansible/plugins/terminal/nxos.py | 22 +- .../nxos_acl_interfaces/defaults/main.yaml | 2 + .../targets/nxos_acl_interfaces/meta/main.yml | 2 + .../nxos_acl_interfaces/tasks/cli.yaml | 20 + .../nxos_acl_interfaces/tasks/main.yaml | 2 + .../nxos_acl_interfaces/tests/cli/deleted.yml | 90 ++++ .../tests/cli/gathered.yml | 34 ++ .../nxos_acl_interfaces/tests/cli/merged.yml | 63 +++ .../tests/cli/overridden.yml | 68 +++ .../nxos_acl_interfaces/tests/cli/parsed.yml | 40 ++ .../tests/cli/populate_acl.yaml | 9 + .../tests/cli/populate_config.yaml | 15 + .../tests/cli/remove_config.yaml | 21 + .../tests/cli/rendered.yml | 48 +++ .../tests/cli/replaced.yml | 60 +++ .../nxos_acl_interfaces/tests/cli/rtt.yml | 99 +++++ .../targets/nxos_acl_interfaces/vars/main.yml | 21 + .../nxos_acl_interfaces.cfg | 2 + .../units/modules/network/nxos/nxos_module.py | 13 +- .../network/nxos/test_nxos_acl_interfaces.py | 303 +++++++++++++ 28 files changed, 1849 insertions(+), 14 deletions(-) create mode 100644 lib/ansible/module_utils/network/nxos/argspec/acl_interfaces/__init__.py create mode 100644 lib/ansible/module_utils/network/nxos/argspec/acl_interfaces/acl_interfaces.py create mode 100644 lib/ansible/module_utils/network/nxos/config/acl_interfaces/__init__.py create mode 100644 lib/ansible/module_utils/network/nxos/config/acl_interfaces/acl_interfaces.py create mode 100644 lib/ansible/module_utils/network/nxos/facts/acl_interfaces/__init__.py create mode 100644 lib/ansible/module_utils/network/nxos/facts/acl_interfaces/acl_interfaces.py create mode 100644 lib/ansible/modules/network/nxos/nxos_acl_interfaces.py create mode 100644 test/integration/targets/nxos_acl_interfaces/defaults/main.yaml create mode 100644 test/integration/targets/nxos_acl_interfaces/meta/main.yml create mode 100644 test/integration/targets/nxos_acl_interfaces/tasks/cli.yaml create mode 100644 test/integration/targets/nxos_acl_interfaces/tasks/main.yaml create mode 100644 test/integration/targets/nxos_acl_interfaces/tests/cli/deleted.yml create mode 100644 test/integration/targets/nxos_acl_interfaces/tests/cli/gathered.yml create mode 100644 test/integration/targets/nxos_acl_interfaces/tests/cli/merged.yml create mode 100644 test/integration/targets/nxos_acl_interfaces/tests/cli/overridden.yml create mode 100644 test/integration/targets/nxos_acl_interfaces/tests/cli/parsed.yml create mode 100644 test/integration/targets/nxos_acl_interfaces/tests/cli/populate_acl.yaml create mode 100644 test/integration/targets/nxos_acl_interfaces/tests/cli/populate_config.yaml create mode 100644 test/integration/targets/nxos_acl_interfaces/tests/cli/remove_config.yaml create mode 100644 test/integration/targets/nxos_acl_interfaces/tests/cli/rendered.yml create mode 100644 test/integration/targets/nxos_acl_interfaces/tests/cli/replaced.yml create mode 100644 test/integration/targets/nxos_acl_interfaces/tests/cli/rtt.yml create mode 100644 test/integration/targets/nxos_acl_interfaces/vars/main.yml create mode 100644 test/units/modules/network/nxos/fixtures/nxos_acl_interfaces/nxos_acl_interfaces.cfg create mode 100644 test/units/modules/network/nxos/test_nxos_acl_interfaces.py diff --git a/lib/ansible/module_utils/network/nxos/argspec/acl_interfaces/__init__.py b/lib/ansible/module_utils/network/nxos/argspec/acl_interfaces/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/lib/ansible/module_utils/network/nxos/argspec/acl_interfaces/acl_interfaces.py b/lib/ansible/module_utils/network/nxos/argspec/acl_interfaces/acl_interfaces.py new file mode 100644 index 00000000000..baa1a70a285 --- /dev/null +++ b/lib/ansible/module_utils/network/nxos/argspec/acl_interfaces/acl_interfaces.py @@ -0,0 +1,91 @@ +# +# -*- 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 nxos_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 nxos_acl_interfaces module + """ + + def __init__(self, **kwargs): + pass + + argument_spec = { + 'config': { + 'elements': 'dict', + 'options': { + 'access_groups': { + 'elements': 'dict', + 'options': { + 'acls': { + 'elements': 'dict', + 'options': { + 'direction': { + 'choices': ['in', 'out'], + 'required': True, + 'type': 'str' + }, + 'name': { + 'required': True, + 'type': 'str' + }, + 'port': { + 'type': 'bool' + } + }, + 'type': 'list' + }, + 'afi': { + 'choices': ['ipv4', 'ipv6'], + 'required': True, + 'type': 'str' + } + }, + 'type': 'list' + }, + 'name': { + 'required': True, + 'type': 'str' + } + }, + 'type': 'list' + }, + 'running_config': { + 'type': 'str' + }, + 'state': { + 'choices': [ + 'deleted', 'gathered', 'merged', 'overridden', 'rendered', + 'replaced', 'parsed' + ], + 'default': + 'merged', + 'type': + 'str' + } + } # pylint: disable=C0301 diff --git a/lib/ansible/module_utils/network/nxos/config/acl_interfaces/__init__.py b/lib/ansible/module_utils/network/nxos/config/acl_interfaces/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/lib/ansible/module_utils/network/nxos/config/acl_interfaces/acl_interfaces.py b/lib/ansible/module_utils/network/nxos/config/acl_interfaces/acl_interfaces.py new file mode 100644 index 00000000000..e5b56f82b19 --- /dev/null +++ b/lib/ansible/module_utils/network/nxos/config/acl_interfaces/acl_interfaces.py @@ -0,0 +1,303 @@ +# +# -*- 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 nxos_acl_interfaces class +It is in this file where the current configuration (as dict) +is compared to the provided configuration (as dict) and the command set +necessary to bring the current configuration to it's desired end-state is +created +""" +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +from ansible.module_utils.network.common.cfg.base import ConfigBase +from ansible.module_utils.network.common.utils import to_list, remove_empties, dict_diff +from ansible.module_utils.network.nxos.facts.facts import Facts +from ansible.module_utils.network.nxos.utils.utils import flatten_dict, search_obj_in_list, get_interface_type, normalize_interface + + +class Acl_interfaces(ConfigBase): + """ + The nxos_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 edit_config(self, commands): + """Wrapper method for `_connection.edit_config()` + This exists solely to allow the unit test framework to mock device connection calls. + """ + return self._connection.edit_config(commands) + + def execute_module(self): + """ Execute the module + + :rtype: A dictionary + :returns: The result from module execution + """ + result = {'changed': False} + warnings = list() + commands = list() + state = self._module.params['state'] + action_states = ['merged', 'replaced', 'deleted', 'overridden'] + + if state == 'gathered': + result['gathered'] = self.get_acl_interfaces_facts() + elif state == 'rendered': + result['rendered'] = self.set_config({}) + # no need to fetch facts for rendered + elif state == 'parsed': + result['parsed'] = self.set_config({}) + # no need to fetch facts for parsed + else: + existing_acl_interfaces_facts = self.get_acl_interfaces_facts() + commands.extend(self.set_config(existing_acl_interfaces_facts)) + if commands and state in action_states: + if not self._module.check_mode: + self._connection.edit_config(commands) + result['changed'] = True + result['before'] = existing_acl_interfaces_facts + result['commands'] = commands + + changed_acl_interfaces_facts = self.get_acl_interfaces_facts() + if result['changed']: + result['after'] = 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 + """ + config = self._module.params['config'] + want = [] + if config: + for w in config: + if get_interface_type(w['name']) == 'loopback': + self._module.fail_json( + msg='This module works with ethernet, management or port-channe') + w.update({'name': normalize_interface(w['name'])}) + want.append(remove_empties(w)) + 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 == 'overridden': + commands = (self._state_overridden(want, have)) + elif state == 'deleted': + commands = (self._state_deleted(want, have)) + elif state == 'rendered': + commands = self._state_rendered(want) + elif state == 'parsed': + want = self._module.params['running_config'] + commands = self._state_parsed(want) + else: + for w in want: + if state == 'merged': + commands.extend(self._state_merged(w, have)) + elif state == 'replaced': + commands.extend(self._state_replaced(w, have)) + return commands + + def _state_parsed(self, want): + return self.get_acl_interfaces_facts(want) + + def _state_rendered(self, want): + commands = [] + for w in want: + commands.extend(self.set_commands(w, {})) + 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 + """ + new_commands = [] + del_dict = {'name': want['name'], 'access_groups': []} + obj_in_have = search_obj_in_list(want['name'], have, 'name') + if obj_in_have != want: + commands = [] + if obj_in_have and 'access_groups' in obj_in_have.keys(): + for ag in obj_in_have['access_groups']: + want_afi = [] + if want.get('access_groups'): + want_afi = search_obj_in_list( + ag['afi'], want['access_groups'], 'afi') + if not want_afi: + # whatever in have is not in want + del_dict['access_groups'].append(ag) + else: + del_acl = [] + for acl in ag['acls']: + if want_afi.get('acls'): + if acl not in want_afi['acls']: + del_acl.append(acl) + else: + del_acl.append(acl) + afi = want_afi['afi'] + del_dict['access_groups'].append( + {'afi': afi, 'acls': del_acl}) + + commands.extend(self._state_deleted([del_dict], have)) + commands.extend(self._state_merged(want, have)) + new_commands.append(commands[0]) + commands = [commands[i] + for i in range(1, len(commands)) if commands[i] != commands[0]] + new_commands.extend(commands) + return new_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 = [] + want_intf = [w['name'] for w in want] + for h in have: + if h['name'] not in want_intf: + commands.extend(self._state_deleted([h], have)) + for w in want: + commands.extend(self._state_replaced(w, have)) + 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 + """ + return self.set_commands(want, have) + + def set_commands(self, want, have, deleted=False): + commands = [] + have_name = search_obj_in_list(want['name'], have, 'name') + if have_name and have_name.get('access_groups'): + if want.get('access_groups'): + for w_afi in want['access_groups']: + ip = 'ipv6' + if w_afi['afi'] == 'ipv4': + ip = 'ip' + have_afi = search_obj_in_list( + w_afi['afi'], have_name['access_groups'], 'afi') + if have_afi: + new_acls = [] + if deleted: + if w_afi.get('acls') and have_afi.get('acls'): + new_acls = [ + acl for acl in w_afi.get('acls') if acl in have_afi.get('acls')] + elif 'acls' not in w_afi.keys(): + new_acls = have_afi.get('acls') + else: + if w_afi.get('acls'): + new_acls = [ + acl for acl in w_afi['acls'] if acl not in have_afi['acls']] + commands.extend(self.process_acl( + new_acls, ip, deleted)) + else: + if not deleted: + if w_afi.get('acls'): + commands.extend( + self.process_acl(w_afi['acls'], ip)) + else: + # only name is given to delete + if deleted and 'access_groups' in have_name.keys(): + commands.extend(self.process_access_group(have_name, True)) + else: + if not deleted: # and 'access_groups' in have_name.keys(): + commands.extend(self.process_access_group(want)) + + if len(commands) > 0: + commands.insert(0, 'interface ' + want['name']) + return commands + + def process_access_group(self, item, deleted=False): + commands = [] + for ag in item['access_groups']: + ip = 'ipv6' + if ag['afi'] == 'ipv4': + ip = 'ip' + if ag.get('acls'): + commands.extend(self.process_acl( + ag['acls'], ip, deleted)) + return commands + + def process_acl(self, acls, ip, deleted=False): + commands = [] + no = '' + if deleted: + no = 'no ' + for acl in acls: + port = '' + if acl.get('port'): + port = ' port' + ag = ' access-group ' + if ip == 'ipv6': + ag = ' traffic-filter ' + commands.append(no + ip + port + ag + + acl['name'] + ' ' + acl['direction']) + return commands + + def _state_deleted(self, main_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 = [] + if main_want: + for want in main_want: + commands.extend(self.set_commands(want, have, deleted=True)) + else: + for h in have: + commands.extend(self.set_commands(h, have, deleted=True)) + + return commands diff --git a/lib/ansible/module_utils/network/nxos/facts/acl_interfaces/__init__.py b/lib/ansible/module_utils/network/nxos/facts/acl_interfaces/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/lib/ansible/module_utils/network/nxos/facts/acl_interfaces/acl_interfaces.py b/lib/ansible/module_utils/network/nxos/facts/acl_interfaces/acl_interfaces.py new file mode 100644 index 00000000000..87d522df670 --- /dev/null +++ b/lib/ansible/module_utils/network/nxos/facts/acl_interfaces/acl_interfaces.py @@ -0,0 +1,119 @@ +# +# -*- 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 nxos 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.nxos.argspec.acl_interfaces.acl_interfaces import Acl_interfacesArgs +from ansible.module_utils.network.nxos.utils.utils import normalize_interface + + +class Acl_interfacesFacts(object): + """ The nxos 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 | section interface') + + 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) + data = data.split('interface') + + resources = [] + for i in range(len(data)): + intf = data[i].split('\n') + for l in range(1, len(intf)): + if not re.search('ip(v6)?( port)? (access-group|traffic-filter)', intf[l]): + intf[l] = '' + intf = list(filter(None, intf)) + resources.append(intf) + + 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}) + params = utils.remove_empties(params) + facts['acl_interfaces'] = 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) + name = conf[0].strip() + config['name'] = normalize_interface(name) + config['access_groups'] = [] + v4 = {'afi': 'ipv4', 'acls': []} + v6 = {'afi': 'ipv6', 'acls': []} + for c in conf[1:]: + if c: + acl4 = re.search(r'ip( port)? access-group (\w*) (\w*)', c) + acl6 = re.search(r'ipv6( port)? traffic-filter (\w*) (\w*)', c) + if acl4: + acl = {'name': acl4.group(2).strip( + ), 'direction': acl4.group(3).strip()} + if acl4.group(1): + acl.update({'port': True}) + v4['acls'].append(acl) + elif acl6: + acl = {'name': acl6.group(2), 'direction': acl6.group(3)} + if acl6.group(1): + acl.update({'port': True}) + v6['acls'].append(acl) + + if len(v4['acls']) > 0: + config['access_groups'].append(v4) + if len(v6['acls']) > 0: + config['access_groups'].append(v6) + + return utils.remove_empties(config) diff --git a/lib/ansible/module_utils/network/nxos/facts/facts.py b/lib/ansible/module_utils/network/nxos/facts/facts.py index e36d7b3357f..9fab13c9ee3 100644 --- a/lib/ansible/module_utils/network/nxos/facts/facts.py +++ b/lib/ansible/module_utils/network/nxos/facts/facts.py @@ -23,6 +23,7 @@ from ansible.module_utils.network.nxos.facts.vlans.vlans import VlansFacts from ansible.module_utils.network.nxos.facts.lacp_interfaces.lacp_interfaces import Lacp_interfacesFacts from ansible.module_utils.network.nxos.facts.lldp_global.lldp_global import Lldp_globalFacts from ansible.module_utils.network.nxos.facts.lldp_interfaces.lldp_interfaces import Lldp_interfacesFacts +from ansible.module_utils.network.nxos.facts.acl_interfaces.acl_interfaces import Acl_interfacesFacts FACT_LEGACY_SUBSETS = dict( @@ -46,6 +47,7 @@ FACT_RESOURCE_SUBSETS = dict( l3_interfaces=L3_interfacesFacts, l2_interfaces=L2_interfacesFacts, lldp_interfaces=Lldp_interfacesFacts, + acl_interfaces=Acl_interfacesFacts, ) @@ -68,9 +70,11 @@ class Facts(FactsBase): :return: the facts gathered """ if self.VALID_RESOURCE_SUBSETS: - self.get_network_resources_facts(FACT_RESOURCE_SUBSETS, resource_facts_type, data) + self.get_network_resources_facts( + FACT_RESOURCE_SUBSETS, resource_facts_type, data) if self.VALID_LEGACY_GATHER_SUBSETS: - self.get_network_legacy_facts(FACT_LEGACY_SUBSETS, legacy_facts_type) + self.get_network_legacy_facts( + FACT_LEGACY_SUBSETS, legacy_facts_type) return self.ansible_facts, self._warnings diff --git a/lib/ansible/modules/network/nxos/nxos_acl_interfaces.py b/lib/ansible/modules/network/nxos/nxos_acl_interfaces.py new file mode 100644 index 00000000000..1b151c63c25 --- /dev/null +++ b/lib/ansible/modules/network/nxos/nxos_acl_interfaces.py @@ -0,0 +1,408 @@ +#!/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 nxos_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: nxos_acl_interfaces +version_added: '2.10' +short_description: Add and remove Access Control Lists on interfaces in NX-OS platform +description: Add and remove Access Control Lists on interfaces in NX-OS platform +author: Adharsh Srivats Rangarajan (@adharshsrivatsr) +notes: + - Tested against NX-OS 7.3.(0)D1(1) on VIRL +options: + running_config: + description: + - Used to parse given commands into structured format, only in parsed state + type: str + config: + description: A list of interfaces to be configured with ACLs + type: list + elements: dict + suboptions: + name: + description: Name of the interface + type: str + required: true + access_groups: + description: List of address family indicators with ACLs to be configured on the interface + type: list + elements: dict + suboptions: + afi: + description: Address Family Indicator of the ACLs to be configured + type: str + required: true + choices: ['ipv4','ipv6'] + acls: + description: List of Access Control Lists for the interface + type: list + elements: dict + suboptions: + name: + description: Name of the ACL to be added/removed + type: str + required: true + direction: + description: Direction to be applied for the ACL + type: str + required: true + choices: ['in','out'] + port: + description: Use ACL as port policy. + type: bool + state: + description: The state the configuration should be left in + type: str + choices: + - deleted + - gathered + - merged + - overridden + - rendered + - replaced + - parsed + default: merged +""" +EXAMPLES = """# Using merged + +# Before state: +# ------------ +# + +- name: Merge ACL interfaces configuration + nxos_acl_interfaces: + config: + - name: Ethernet1/2 + access_groups: + - afi: ipv6 + acls: + - name: ACL1v6 + direction: in + + - name: Eth1/5 + access_groups: + - afi: ipv4 + acls: + - name: PortACL + direction: in + port: true + + - name: ACL1v4 + direction: out + + - afi: ipv6 + acls: + - name: ACL1v6 + direction: in + state: merged + +# After state: +# ------------ +# interface Ethernet1/2 +# ipv6 traffic-filter ACL1v6 in +# interface Ethernet1/5 +# ip port access-group PortACL in +# ip access-group ACL1v4 out +# ipv6 traffic-filter ACL1v6 in + +# Using replaced + +# Before state: +# ------------ +# interface Ethernet1/2 +# ipv6 traffic-filter ACL1v6 in +# interface Ethernet1/5 +# ip port access-group PortACL in +# ip access-group ACL1v4 out +# ipv6 traffic-filter ACL1v6 in + +- name: Replace interface configuration with given configuration + nxos_acl_interfaces: + config: + - name: Eth1/5 + access_groups: + - afi: ipv4 + acls: + - name: NewACLv4 + direction: out + + - name: Ethernet1/3 + access_groups: + - afi: ipv6 + acls: + - name: NewACLv6 + direction: in + port: true + state: replaced + +# After state: +# ------------ +# interface Ethernet1/2 +# ipv6 traffic-filter ACL1v6 in +# interface Ethernet1/3 +# ipv6 port traffic-filter NewACLv6 in +# interface Ethernet1/5 +# ip access-group NewACLv4 out + +# Using overridden + +# Before state: +# ------------ +# interface Ethernet1/2 +# ipv6 traffic-filter ACL1v6 in +# interface Ethernet1/5 +# ip port access-group PortACL in +# ip access-group ACL1v4 out +# ipv6 traffic-filter ACL1v6 in + +- name: Override interface configuration with given configuration + nxos_acl_interfaces: + config: + - name: Ethernet1/3 + access_groups: + - afi: ipv4 + acls: + - name: ACL1v4 + direction: out + + - name: PortACL + port: true + direction: in + - afi: ipv6 + acls: + - name: NewACLv6 + direction: in + port: true + state: overridden + +# After state: +# ------------ +# interface Ethernet1/3 +# ip access-group ACL1v4 out +# ip port access-group PortACL in +# ipv6 port traffic-filter NewACLv6 in + +# Using deleted + +# Before state: +# ------------- +# interface Ethernet1/2 +# ipv6 traffic-filter ACL1v6 in +# interface Ethernet1/5 +# ip port access-group PortACL in +# ip access-group ACL1v4 out +# ipv6 traffic-filter ACL1v6 in + +- name: Delete ACL configuration on interfaces + nxos_acl_interfaces: + config: + - name: Ethernet1/5 + access_groups: + - afi: ipv6 + + - afi: ipv4 + acls: + - name: ACL1v4 + direction: out + + - name: Ethernet1/2 + state: deleted + +# After state: +# ------------- +# interface Ethernet1/2 +# interface Ethernet1/5 +# ip port access-group PortACL in +# ip access-group ACL1v4 out +# ipv6 traffic-filter ACL1v6 in + +# Using parsed + +- name: Parse given configuration into structured format + nxos_acl_interfaces: + running_config: | + interface Ethernet1/2 + ipv6 traffic-filter ACL1v6 in + interface Ethernet1/5 + ipv6 traffic-filter ACL1v6 in + ip access-group ACL1v4 out + ip port access-group PortACL in + state: parsed + +# returns +# parsed: +# - name: Ethernet1/2 +# access_groups: +# - afi: ipv6 +# acls: +# - name: ACL1v6 +# direction: in +# - name: Ethernet1/5 +# access_groups: +# - afi: ipv4 +# acls: +# - name: PortACL +# direction: in +# port: True +# - name: ACL1v4 +# direction: out +# - afi: ipv6 +# acls: +# - name: ACL1v6 +# direction: in + + +# Using gathered: + +# Before state: +# ------------ +# interface Ethernet1/2 +# ipv6 traffic-filter ACL1v6 in +# interface Ethernet1/5 +# ipv6 traffic-filter ACL1v6 in +# ip access-group ACL1v4 out +# ip port access-group PortACL in + +- name: Gather existing configuration from device + nxos_acl_interfaces: + config: + state: gathered + +# returns +# gathered: +# - name: Ethernet1/2 +# access_groups: +# - afi: ipv6 +# acls: +# - name: ACL1v6 +# direction: in +# - name: Ethernet1/5 +# access_groups: +# - afi: ipv4 +# acls: +# - name: PortACL +# direction: in +# port: True +# - name: ACL1v4 +# direction: out +# - afi: ipv6 +# acls: +# - name: ACL1v6 +# direction: in + + +# Using rendered + +- name: Render required configuration to be pushed to the device + nxos_acl_interfaces: + config: + - name: Ethernet1/2 + access_groups: + - afi: ipv6 + acls: + - name: ACL1v6 + direction: in + + - name: Ethernet1/5 + access_groups: + - afi: ipv4 + acls: + - name: PortACL + direction: in + port: true + - name: ACL1v4 + direction: out + - afi: ipv6 + acls: + - name: ACL1v6 + direction: in + state: rendered + +# returns +# rendered: +# interface Ethernet1/2 +# ipv6 traffic-filter ACL1v6 in +# interface Ethernet1/5 +# ipv6 traffic-filter ACL1v6 in +# ip access-group ACL1v4 out +# ip port access-group PortACL in + +""" +RETURN = """ +before: + description: The configuration prior to the model invocation. + returned: always + type: dict + 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: dict + 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 Ethernet1/2', 'ipv6 traffic-filter ACL1v6 out', 'ip port access-group PortACL in'] +""" + + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.network.nxos.argspec.acl_interfaces.acl_interfaces import Acl_interfacesArgs +from ansible.module_utils.network.nxos.config.acl_interfaces.acl_interfaces import Acl_interfaces + + +def main(): + """ + Main entry point for module execution + + :returns: the result form module invocation + """ + 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() diff --git a/lib/ansible/plugins/terminal/nxos.py b/lib/ansible/plugins/terminal/nxos.py index a832f94264a..42437c9e628 100644 --- a/lib/ansible/plugins/terminal/nxos.py +++ b/lib/ansible/plugins/terminal/nxos.py @@ -30,7 +30,8 @@ from ansible.module_utils._text import to_bytes, to_text class TerminalModule(TerminalBase): terminal_stdout_re = [ - re.compile(br'[\r\n](?!\s*<)?(\x1b\S+)*[a-zA-Z_0-9]{1}[a-zA-Z0-9-_.]*[>|#](?:\s*)(\x1b\S+)*$'), + re.compile( + br'[\r\n](?!\s*<)?(\x1b\S+)*[a-zA-Z_0-9]{1}[a-zA-Z0-9-_.]*[>|#](?:\s*)(\x1b\S+)*$'), re.compile(br'[\r\n]?[a-zA-Z0-9]{1}[a-zA-Z0-9-_.]*\(.+\)#(?:\s*)$') ] @@ -49,7 +50,12 @@ class TerminalModule(TerminalBase): re.compile(br"user not present"), re.compile(br"invalid (.+?)at '\^' marker", re.I), re.compile(br"configuration not allowed .+ at '\^' marker"), - re.compile(br"[B|b]aud rate of console should be.* (\d*) to increase [a-z]* level", re.I), + re.compile( + br"[B|b]aud rate of console should be.* (\d*) to increase [a-z]* level", re.I), + re.compile(br"cannot apply non-existing acl policy to interface", re.I), + re.compile(br"Duplicate sequence number", re.I), + re.compile( + br"Cannot apply ACL to an interface that is a port-channel member", re.I) ] def on_become(self, passwd=None): @@ -70,18 +76,22 @@ class TerminalModule(TerminalBase): cmd = {u'command': u'enable'} if passwd: - cmd[u'prompt'] = to_text(r"(?i)[\r\n]?Password: $", errors='surrogate_or_strict') + cmd[u'prompt'] = to_text( + r"(?i)[\r\n]?Password: $", errors='surrogate_or_strict') cmd[u'answer'] = passwd cmd[u'prompt_retry_check'] = True try: - self._exec_cli_command(to_bytes(json.dumps(cmd), errors='surrogate_or_strict')) + self._exec_cli_command( + to_bytes(json.dumps(cmd), errors='surrogate_or_strict')) prompt = self._get_prompt() if prompt is None or not prompt.strip().endswith(b'enable#'): - raise AnsibleConnectionFailure('failed to elevate privilege to enable mode still at prompt [%s]' % prompt) + raise AnsibleConnectionFailure( + 'failed to elevate privilege to enable mode still at prompt [%s]' % prompt) except AnsibleConnectionFailure as e: prompt = self._get_prompt() - raise AnsibleConnectionFailure('unable to elevate privilege to enable mode, at prompt [%s] with error: %s' % (prompt, e.message)) + raise AnsibleConnectionFailure( + 'unable to elevate privilege to enable mode, at prompt [%s] with error: %s' % (prompt, e.message)) def on_unbecome(self): prompt = self._get_prompt() diff --git a/test/integration/targets/nxos_acl_interfaces/defaults/main.yaml b/test/integration/targets/nxos_acl_interfaces/defaults/main.yaml new file mode 100644 index 00000000000..5f709c5aac1 --- /dev/null +++ b/test/integration/targets/nxos_acl_interfaces/defaults/main.yaml @@ -0,0 +1,2 @@ +--- +testcase: "*" diff --git a/test/integration/targets/nxos_acl_interfaces/meta/main.yml b/test/integration/targets/nxos_acl_interfaces/meta/main.yml new file mode 100644 index 00000000000..ae741cbdc71 --- /dev/null +++ b/test/integration/targets/nxos_acl_interfaces/meta/main.yml @@ -0,0 +1,2 @@ +dependencies: + - prepare_nxos_tests diff --git a/test/integration/targets/nxos_acl_interfaces/tasks/cli.yaml b/test/integration/targets/nxos_acl_interfaces/tasks/cli.yaml new file mode 100644 index 00000000000..f1c20c1b78e --- /dev/null +++ b/test/integration/targets/nxos_acl_interfaces/tasks/cli.yaml @@ -0,0 +1,20 @@ +--- +- name: collect cli test cases + find: + paths: "{{ role_path }}/tests/cli" + patterns: "{{ testcase }}.yml" + connection: local + register: test_cases + +- set_fact: + test_cases: + files: "{{ test_cases.files }}" + +- 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 connection={{ cli }}" + with_items: "{{ test_items }}" + loop_control: + loop_var: test_case_to_run diff --git a/test/integration/targets/nxos_acl_interfaces/tasks/main.yaml b/test/integration/targets/nxos_acl_interfaces/tasks/main.yaml new file mode 100644 index 00000000000..415c99d8b12 --- /dev/null +++ b/test/integration/targets/nxos_acl_interfaces/tasks/main.yaml @@ -0,0 +1,2 @@ +--- +- { include: cli.yaml, tags: ['cli'] } diff --git a/test/integration/targets/nxos_acl_interfaces/tests/cli/deleted.yml b/test/integration/targets/nxos_acl_interfaces/tests/cli/deleted.yml new file mode 100644 index 00000000000..b935df541e2 --- /dev/null +++ b/test/integration/targets/nxos_acl_interfaces/tests/cli/deleted.yml @@ -0,0 +1,90 @@ +--- +- debug: + msg: "Start nxos_acl_interfaces deleted integration tests connection = {{ansible_connection}}" + +- include_tasks: populate_config.yaml + +- block: + - name: Delete single ACL from an interface + nxos_acl_interfaces: + config: + - name: Ethernet1/5 + access_groups: + - afi: ipv6 + acls: + - name: ACL1v6 + direction: in + state: deleted + register: result + + - assert: + that: + - "result.changed == true" + - "'interface Ethernet1/5' in result.commands" + - "'no ipv6 traffic-filter ACL1v6 in' in result.commands" + - "result.commands | length ==2" + + - name: Delete all ACLs of given AFI from an interface + nxos_acl_interfaces: + config: + - name: Ethernet1/5 + access_groups: + - afi: ipv4 + state: deleted + register: result + + - assert: + that: + - "result.changed == true" + - "'interface Ethernet1/5' in result.commands" + - "'no ip port access-group PortACL in' in result.commands" + - "'no ip access-group ACL1v4 out' in result.commands" + - "result.commands | length ==3" + + - name: Delete all ACLs configuration from given interface + nxos_acl_interfaces: &deleted + config: + - name: Ethernet1/2 + state: deleted + register: result + + - assert: + that: + - "result.changed == True" + - "'interface Ethernet1/2' in result.commands" + - "'no ipv6 traffic-filter ACL1v6 in' in result.commands" + - "result.commands | length == 2" + + - include_tasks: populate_config.yaml + + - name: Delete all ACLs from all interfaces (from all interfaces) + nxos_acl_interfaces: + config: + state: deleted + register: result + + - name: Gather acl interfaces facts + nxos_facts: &facts + gather_subset: + - "!all" + - "!min" + gather_network_resources: acl_interfaces + + - assert: + that: + - "result.changed == True" + - "ansible_facts.network_resources.acl_interfaces == result.after" + + - name: Gather acls facts + nxos_facts: *facts + + - name: Idempotence - deleted + nxos_acl_interfaces: *deleted + register: result + + - assert: + that: + - "result.changed == false" + + always: + - include_tasks: remove_config.yaml diff --git a/test/integration/targets/nxos_acl_interfaces/tests/cli/gathered.yml b/test/integration/targets/nxos_acl_interfaces/tests/cli/gathered.yml new file mode 100644 index 00000000000..b3ae8b36c8e --- /dev/null +++ b/test/integration/targets/nxos_acl_interfaces/tests/cli/gathered.yml @@ -0,0 +1,34 @@ +--- +- debug: + msg: Start nxos_acl_interfaces gathered integration tests connection={{ansible_connection}}" + +- include_tasks: populate_config.yaml + +- block: + - name: Gather acl interfaces facts + nxos_facts: &facts + gather_subset: + - "!all" + - "!min" + gather_network_resources: acl_interfaces + + - name: Gathered + nxos_acl_interfaces: &gathered + state: gathered + register: result + + - assert: + that: + - "result.changed == false" + - "ansible_facts.network_resources.acl_interfaces == result.gathered" + + - name: Idempotence - Gathered + nxos_acl_interfaces: *gathered + register: result + + - assert: + that: + - "result.changed == false" + + always: + - include_tasks: remove_config.yaml diff --git a/test/integration/targets/nxos_acl_interfaces/tests/cli/merged.yml b/test/integration/targets/nxos_acl_interfaces/tests/cli/merged.yml new file mode 100644 index 00000000000..b468e47d7c1 --- /dev/null +++ b/test/integration/targets/nxos_acl_interfaces/tests/cli/merged.yml @@ -0,0 +1,63 @@ +--- +- debug: + msg: "Start nxos_acl_interfaces merged integration tests connection = {{ansible_connection}}" + +- include_tasks: populate_acl.yaml + +- block: + - name: Gather acl interfaces facts + nxos_facts: + gather_subset: + - "!all" + - "!min" + gather_network_resources: acl_interfaces + + - name: Merged + nxos_acl_interfaces: &merged + config: + - name: Ethernet1/2 + access_groups: + - afi: ipv6 + acls: + - name: ACL1v6 + direction: in + + - name: Eth1/5 + access_groups: + - afi: ipv4 + acls: + - name: PortACL + direction: in + port: True + + - name: ACL1v4 + direction: out + + - afi: ipv6 + acls: + - name: ACL1v6 + direction: in + state: merged + register: result + + - assert: + that: + - "result.changed == True" + - "'interface Ethernet1/2' in result.commands" + - "'ipv6 traffic-filter ACL1v6 in' in result.commands" + - "'interface Ethernet1/5' in result.commands" + - "'ip port access-group PortACL in' in result.commands" + - "'ip access-group ACL1v4 out' in result.commands" + - "'ipv6 traffic-filter ACL1v6 in' in result.commands" + - "result.commands | length == 6 " + + - name: Idempotence - Merged + nxos_acl_interfaces: *merged + register: result + + - assert: + that: + - "result.changed == false" + + always: + - include_tasks: remove_config.yaml diff --git a/test/integration/targets/nxos_acl_interfaces/tests/cli/overridden.yml b/test/integration/targets/nxos_acl_interfaces/tests/cli/overridden.yml new file mode 100644 index 00000000000..d990c2a0986 --- /dev/null +++ b/test/integration/targets/nxos_acl_interfaces/tests/cli/overridden.yml @@ -0,0 +1,68 @@ +--- +- debug: + msg: "Start nxos_acl_interfaces overridden integration tests connection = {{ansible_connection}}" + +- include_tasks: populate_config.yaml + +- block: + - name: Overridden + nxos_acl_interfaces: &overridden + config: + - name: Ethernet1/3 + access_groups: + - afi: ipv4 + acls: + - name: ACL1v4 + direction: out + + - name: PortACL + port: true + direction: in + + - afi: ipv6 + acls: + - name: NewACLv6 + direction: in + port: true + state: overridden + register: result + + - assert: + that: + - "result.changed == True" + - "'interface Ethernet1/2' in result.commands" + - "'no ipv6 traffic-filter ACL1v6 in' in result.commands" + - "'interface Ethernet1/5' in result.commands" + - "'no ip access-group ACL1v4 out' in result.commands" + - "'no ip port access-group PortACL in' in result.commands" + - "'no ipv6 traffic-filter ACL1v6 in' in result.commands" + - "'interface Ethernet1/3' in result.commands" + - "'ip access-group ACL1v4 out' in result.commands" + - "'ip port access-group PortACL in' in result.commands" + - "'ipv6 port traffic-filter NewACLv6 in' in result.commands" + - "result.commands | length == 10" + + - name: Gather acl_interfaces post facts + nxos_facts: &facts + gather_subset: + - "!all" + - "!min" + gather_network_resources: acl_interfaces + + - name: Gather acls post facts + nxos_facts: *facts + + - assert: + that: + - "ansible_facts.network_resources.acl_interfaces == result.after" + + - name: Idempotence - overridden + nxos_acl_interfaces: *overridden + register: result + + - assert: + that: + - "result.changed == false" + + always: + - include_tasks: remove_config.yaml diff --git a/test/integration/targets/nxos_acl_interfaces/tests/cli/parsed.yml b/test/integration/targets/nxos_acl_interfaces/tests/cli/parsed.yml new file mode 100644 index 00000000000..a54fc83ab0e --- /dev/null +++ b/test/integration/targets/nxos_acl_interfaces/tests/cli/parsed.yml @@ -0,0 +1,40 @@ +--- +- debug: + msg: Start nxos_acl_interfaces parsed integration tests connection={{ansible_connection}}" + +- include_tasks: populate_config.yaml + +- block: + - name: Gather acl interfaces facts + nxos_facts: &facts + gather_subset: + - "!all" + - "!min" + gather_network_resources: acl_interfaces + + - name: Parsed + nxos_acl_interfaces: &parsed + running_config: | + interface Ethernet1/2 + ipv6 traffic-filter ACL1v6 in + interface Ethernet1/5 + ipv6 traffic-filter ACL1v6 in + ip access-group ACL1v4 out + ip port access-group PortACL in + state: parsed + register: result + + - assert: + that: + - "result.changed == false" + - "result.parsed == parsed" + + - name: Idempotence - Parsed + nxos_acl_interfaces: *parsed + register: result + + - assert: + that: "result.changed == false" + + always: + - include_tasks: remove_config.yaml diff --git a/test/integration/targets/nxos_acl_interfaces/tests/cli/populate_acl.yaml b/test/integration/targets/nxos_acl_interfaces/tests/cli/populate_acl.yaml new file mode 100644 index 00000000000..157781558bb --- /dev/null +++ b/test/integration/targets/nxos_acl_interfaces/tests/cli/populate_acl.yaml @@ -0,0 +1,9 @@ +--- +- name: Adding base configuration + cli_config: + config: | + ip access-list ACL1v4 + ip access-list NewACLv4 + ipv6 access-list ACL1v6 + ipv6 access-list NewACLv6 + ip access-list PortACL diff --git a/test/integration/targets/nxos_acl_interfaces/tests/cli/populate_config.yaml b/test/integration/targets/nxos_acl_interfaces/tests/cli/populate_config.yaml new file mode 100644 index 00000000000..9ce0295eebd --- /dev/null +++ b/test/integration/targets/nxos_acl_interfaces/tests/cli/populate_config.yaml @@ -0,0 +1,15 @@ +--- +- name: Adding base configuration + cli_config: + config: | + ip access-list ACL1v4 + ip access-list NewACLv4 + ipv6 access-list ACL1v6 + ipv6 access-list NewACLv6 + ip access-list PortACL + interface Ethernet1/2 + ipv6 traffic-filter ACL1v6 in + interface Ethernet1/5 + ip port access-group PortACL in + ip access-group ACL1v4 out + ipv6 traffic-filter ACL1v6 in diff --git a/test/integration/targets/nxos_acl_interfaces/tests/cli/remove_config.yaml b/test/integration/targets/nxos_acl_interfaces/tests/cli/remove_config.yaml new file mode 100644 index 00000000000..d0a9f9f9bbf --- /dev/null +++ b/test/integration/targets/nxos_acl_interfaces/tests/cli/remove_config.yaml @@ -0,0 +1,21 @@ +--- +- name: Remove config + cli_config: + config: | + no ip access-list ACL1v4 + no ip access-list NewACLv4 + no ip access-list PortACL + no ipv6 access-list ACL1v6 + no ipv6 access-list ACL1v6 + interface Ethernet1/2 + no ipv6 traffic-filter ACL1v6 in + interface Ethernet1/5 + no ip access-group ACL1v4 out + no ip port access-group PortACL in + no ipv6 traffic-filter ACL1v6 in + interface Ethernet1/3 + no ipv6 port traffic-filter NewACLv6 in + no ip access-group ACL1v4 out + no ip port access-group PortACL in + interface Ethernet1/4 + no ip access-group NewACLv4 out diff --git a/test/integration/targets/nxos_acl_interfaces/tests/cli/rendered.yml b/test/integration/targets/nxos_acl_interfaces/tests/cli/rendered.yml new file mode 100644 index 00000000000..125cc601924 --- /dev/null +++ b/test/integration/targets/nxos_acl_interfaces/tests/cli/rendered.yml @@ -0,0 +1,48 @@ +--- +- debug: + msg: "Start nxos_acl_interfaces rendered tests connection={{ ansible_connection }}" + +- name: Rendered + nxos_acl_interfaces: &rendered + config: + - name: Ethernet1/2 + access_groups: + - afi: ipv6 + acls: + - name: ACL1v6 + direction: in + + - name: Ethernet1/5 + access_groups: + - afi: ipv4 + acls: + - name: PortACL + direction: in + port: True + - name: ACL1v4 + direction: out + - afi: ipv6 + acls: + - name: ACL1v6 + direction: in + state: rendered + register: result + +- assert: + that: + - "result.changed == false" + - "'interface Ethernet1/2' in result.rendered" + - "'ipv6 traffic-filter ACL1v6 in' in result.rendered" + - "'interface Ethernet1/5' in result.rendered" + - "'ipv6 traffic-filter ACL1v6 in' in result.rendered" + - "'ip access-group ACL1v4 out' in result.rendered" + - "'ip port access-group PortACL in' in result.rendered" + - "result.rendered | length == 6" + +- name: Idempotence - Rendered + nxos_acl_interfaces: *rendered + register: result + +- assert: + that: + - "result.changed == false" diff --git a/test/integration/targets/nxos_acl_interfaces/tests/cli/replaced.yml b/test/integration/targets/nxos_acl_interfaces/tests/cli/replaced.yml new file mode 100644 index 00000000000..b2844e119d3 --- /dev/null +++ b/test/integration/targets/nxos_acl_interfaces/tests/cli/replaced.yml @@ -0,0 +1,60 @@ +--- +- debug: + msg: "Start nxos_acl_interfaces replaced integration tests connection = {{ansible_connection}}" + +- include_tasks: populate_config.yaml + +- block: + - name: Replaced + nxos_acl_interfaces: &replaced + config: + - name: Eth1/5 + access_groups: + - afi: ipv4 + acls: + - name: NewACLv4 + direction: out + + - name: Ethernet1/3 + access_groups: + - afi: ipv6 + acls: + - name: NewACLv6 + direction: in + port: true + state: replaced + register: result + + - assert: + that: + - "result.changed==True" + - "'interface Ethernet1/5' in result.commands" + - "'no ip access-group ACL1v4 out' in result.commands" + - "'no ip port access-group PortACL in' in result.commands" + - "'no ipv6 traffic-filter ACL1v6 in' in result.commands" + - "'ip access-group NewACLv4 out' in result.commands" + - "'interface Ethernet1/3' in result.commands" + - "'ipv6 port traffic-filter NewACLv6 in' in result.commands" + - "result.commands|length==7" + + - name: Gather acl_interfaces post facts + nxos_facts: + gather_subset: + - "!all" + - "!min" + gather_network_resources: acl_interfaces + + - assert: + that: + - "ansible_facts.network_resources.acl_interfaces == result.after" + + - name: Idempotence - Replaced + nxos_acl_interfaces: *replaced + register: result + + - assert: + that: + - "result.changed == false" + + always: + - include_tasks: remove_config.yaml diff --git a/test/integration/targets/nxos_acl_interfaces/tests/cli/rtt.yml b/test/integration/targets/nxos_acl_interfaces/tests/cli/rtt.yml new file mode 100644 index 00000000000..0cd7a1c236f --- /dev/null +++ b/test/integration/targets/nxos_acl_interfaces/tests/cli/rtt.yml @@ -0,0 +1,99 @@ +--- +- debug: + msg: "Start nxos_acl_interfaces round trip integration tests connection = {{ansible_connection}}" + +- include_tasks: populate_config.yaml + +- block: + - name: RTT- Apply provided configuration + nxos_acl_interfaces: + config: + - name: Ethernet1/2 + access_groups: + - afi: ipv6 + acls: + - name: ACL1v6 + direction: in + + - name: Eth1/5 + access_groups: + - afi: ipv4 + acls: + - name: PortACL + direction: in + port: True + + - name: ACL1v4 + direction: out + + - afi: ipv6 + acls: + - name: ACL1v6 + direction: in + state: merged + + - name: Gather interfaces facts + nxos_facts: + gather_subset: + - "!all" + - "!min" + gather_network_resources: + - acl_interfaces + + - name: Apply configuration to be reverted + nxos_acl_interfaces: + config: + - name: Eth1/4 + access_groups: + - afi: ipv4 + acls: + - name: NewACLv4 + direction: out + + - name: Ethernet1/3 + access_groups: + - afi: ipv6 + acls: + - name: NewACLv6 + direction: in + port: true + state: overridden + register: result + + - name: Assert that changes were applied + assert: + that: + - "result.changed==True" + - "'interface Ethernet1/2' in result.commands" + - "'no ipv6 traffic-filter ACL1v6 in' in result.commands" + - "'interface Ethernet1/5' in result.commands" + - "'no ip access-group ACL1v4 out' in result.commands" + - "'no ip port access-group PortACL in' in result.commands" + - "'no ipv6 traffic-filter ACL1v6 in' in result.commands" + - "'interface Ethernet1/4' in result.commands" + - "'ip access-group NewACLv4 out' in result.commands" + - "'interface Ethernet1/3' in result.commands" + - "'ipv6 port traffic-filter NewACLv6 in' in result.commands" + + - name: Revert back to base configuration using facts round trip + nxos_acl_interfaces: + config: "{{ ansible_facts['network_resources']['acl_interfaces'] }}" + state: overridden + register: result + + - name: Assert that config was reverted + assert: + that: + - "result.changed==True" + - "'interface Ethernet1/2' in result.commands" + - "'ipv6 traffic-filter ACL1v6 in' in result.commands" + - "'interface Ethernet1/3' in result.commands" + - "'no ipv6 port traffic-filter NewACLv6 in' in result.commands" + - "'interface Ethernet1/4' in result.commands" + - "'no ip access-group NewACLv4 out' in result.commands" + - "'interface Ethernet1/5' in result.commands" + - "'ip access-group ACL1v4 out' in result.commands" + - "'ip port access-group PortACL in' in result.commands" + - "'ipv6 traffic-filter ACL1v6 in' in result.commands" + always: + - include_tasks: remove_config.yaml diff --git a/test/integration/targets/nxos_acl_interfaces/vars/main.yml b/test/integration/targets/nxos_acl_interfaces/vars/main.yml new file mode 100644 index 00000000000..0d9ed68e9fc --- /dev/null +++ b/test/integration/targets/nxos_acl_interfaces/vars/main.yml @@ -0,0 +1,21 @@ +--- +parsed: + - access_groups: + - acls: + - direction: in + name: ACL1v6 + afi: ipv6 + name: Ethernet1/2 + - access_groups: + - acls: + - direction: out + name: ACL1v4 + - direction: in + name: PortACL + port: true + afi: ipv4 + - acls: + - direction: in + name: ACL1v6 + afi: ipv6 + name: Ethernet1/5 \ No newline at end of file diff --git a/test/units/modules/network/nxos/fixtures/nxos_acl_interfaces/nxos_acl_interfaces.cfg b/test/units/modules/network/nxos/fixtures/nxos_acl_interfaces/nxos_acl_interfaces.cfg new file mode 100644 index 00000000000..199244e784a --- /dev/null +++ b/test/units/modules/network/nxos/fixtures/nxos_acl_interfaces/nxos_acl_interfaces.cfg @@ -0,0 +1,2 @@ +interface Ethernet1/2 + ip access-group ACL1v4 out \ No newline at end of file diff --git a/test/units/modules/network/nxos/nxos_module.py b/test/units/modules/network/nxos/nxos_module.py index 47871986eeb..07670a282ce 100644 --- a/test/units/modules/network/nxos/nxos_module.py +++ b/test/units/modules/network/nxos/nxos_module.py @@ -73,7 +73,8 @@ class TestNxosModule(ModuleTestCase): retvals = {} for model in models: - retvals[model] = self.execute_module(failed, changed, commands, sort, device=model) + retvals[model] = self.execute_module( + failed, changed, commands, sort, device=model) return retvals @@ -87,19 +88,19 @@ class TestNxosModule(ModuleTestCase): else: result = self.changed(changed) self.assertEqual(result['changed'], changed, result) - - if commands is not None: + if commands is not None and len(commands) > 0: if sort: - self.assertEqual(sorted(commands), sorted(result['commands']), result['commands']) + self.assertEqual(sorted(commands), sorted( + result['commands']), result['commands']) else: - self.assertEqual(commands, result['commands'], result['commands']) + self.assertEqual( + commands, result['commands'], result['commands']) return result def failed(self): with self.assertRaises(AnsibleFailJson) as exc: self.module.main() - result = exc.exception.args[0] self.assertTrue(result['failed'], result) return result diff --git a/test/units/modules/network/nxos/test_nxos_acl_interfaces.py b/test/units/modules/network/nxos/test_nxos_acl_interfaces.py new file mode 100644 index 00000000000..8c55cd94807 --- /dev/null +++ b/test/units/modules/network/nxos/test_nxos_acl_interfaces.py @@ -0,0 +1,303 @@ +# +# (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 ansible.modules.network.nxos import nxos_acl_interfaces +from units.compat.mock import patch, MagicMock +from units.modules.utils import set_module_args +from .nxos_module import TestNxosModule, load_fixture + + +class TestNxosAclInterfacesModule(TestNxosModule): + + module = nxos_acl_interfaces + + def setUp(self): + super(TestNxosAclInterfacesModule, 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.nxos.config.acl_interfaces.acl_interfaces.Acl_interfaces.edit_config' + ) + self.edit_config = self.mock_edit_config.start() + + self.mock_execute_show_command = patch( + 'ansible.module_utils.network.nxos.facts.acl_interfaces.acl_interfaces.Acl_interfacesFacts.get_device_data' + ) + self.execute_show_command = self.mock_execute_show_command.start() + + def tearDown(self): + super(TestNxosAclInterfacesModule, 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, device=''): + def load_from_file(*args, **kwargs): + output = '''interface Ethernet1/2\n ip access-group ACL1v4 out\n interface Ethernet1/4\n ipv6 port traffic-filter ACL2v6 in\n''' + return output + + self.execute_show_command.side_effect = load_from_file + + def test_nxos_acl_interfaces_merged(self): + set_module_args( + dict(config=[ + dict(name="Ethernet1/3", + access_groups=[ + dict(afi="ipv4", + acls=[ + dict( + name="ACL1v4", + direction="in", + ) + ] + ) + ] + ) + ], state="merged")) + commands = ['interface Ethernet1/3', + 'ip access-group ACL1v4 in'] + self.execute_module(changed=True, commands=commands) + + def test_nxos_acl_interfaces_merged_idempotent(self): + set_module_args( + dict(config=[ + dict(name="Ethernet1/2", + access_groups=[ + dict(afi="ipv4", + acls=[ + dict( + name="ACL1v4", + direction="out", + ) + ] + ) + ] + ), + dict(name="Ethernet1/4", + access_groups=[ + dict(afi="ipv6", + acls=[ + dict( + name="ACL2v6", + direction="in", + port=True + ) + ] + ) + ] + ), + + ], state="merged")) + self.execute_module(changed=False, commands=[]) + + def test_nxos_acl_interfaces_replaced(self): + set_module_args( + dict(config=[ + dict(name="Ethernet1/2", + access_groups=[ + dict(afi="ipv6", + acls=[ + dict( + name="ACL1v6", + direction="in", + port=True + ) + ] + ) + ] + ), + dict(name="Ethernet1/5", + access_groups=[ + dict(afi="ipv4", + acls=[ + dict( + name="ACL2v4", + direction="in", + port=True + ) + ] + ) + ] + ) + ], state="replaced")) + commands = ['interface Ethernet1/2', 'no ip access-group ACL1v4 out', + 'ipv6 port traffic-filter ACL1v6 in', 'interface Ethernet1/5', 'ip port access-group ACL2v4 in'] + self.execute_module(changed=True, commands=commands) + + def test_nxos_acl_interfaces_replaced_idempotent(self): + set_module_args( + dict(config=[ + dict(name="Ethernet1/2", + access_groups=[ + dict(afi="ipv4", + acls=[ + dict( + name="ACL1v4", + direction="out", + ) + ] + ) + ] + )], state="replaced")) + self.execute_module(changed=False, commands=[]) + + def test_nxos_acl_interfaces_overridden(self): + set_module_args( + dict(config=[ + dict(name="Ethernet1/3", + access_groups=[ + dict(afi="ipv4", + acls=[ + dict( + name="ACL2v4", + direction="out" + ), + dict( + name="PortACL", + direction="in", + port=True + ), + ] + ) + ] + )], state="overridden")) + commands = ['interface Ethernet1/2', 'no ip access-group ACL1v4 out', 'interface Ethernet1/4', + 'no ipv6 port traffic-filter ACL2v6 in', 'interface Ethernet1/3', 'ip access-group ACL2v4 out', 'ip port access-group PortACL in'] + self.execute_module(changed=True, commands=commands) + + def test_nxos_acl_interfaces_overridden_idempotent(self): + set_module_args( + dict(config=[ + dict(name="Ethernet1/2", + access_groups=[ + dict(afi="ipv4", + acls=[ + dict( + name="ACL1v4", + direction="out", + ) + ] + ) + ] + ), + dict(name="Ethernet1/4", + access_groups=[ + dict(afi="ipv6", + acls=[ + dict( + name="ACL2v6", + direction="in", + port=True + ) + ] + ) + ] + ), + ], state="overridden")) + self.execute_module(changed=False, commands=[]) + + def test_nxos_acl_interfaces_deletedname(self): + set_module_args( + dict(config=[dict(name="Ethernet1/2")], state="deleted")) + commands = ['interface Ethernet1/2', 'no ip access-group ACL1v4 out'] + self.execute_module(changed=True, commands=commands) + + def test_nxos_acl_interfaces_deletedafi(self): + set_module_args( + dict(config=[dict(name="Ethernet1/2", access_groups=[ + dict(afi="ipv4") + ])], state="deleted")) + commands = ['interface Ethernet1/2', 'no ip access-group ACL1v4 out'] + self.execute_module(changed=True, commands=commands) + + def test_nxos_acl_interfaces_deletedacl(self): + set_module_args( + dict(config=[dict(name="Ethernet1/2", access_groups=[ + dict(afi="ipv4", acls=[ + dict( + name="ACL1v4", + direction="out" + ) + ]) + ])], state="deleted")) + commands = ['interface Ethernet1/2', 'no ip access-group ACL1v4 out'] + self.execute_module(changed=True, commands=commands) + + def test_nxos_acl_interfaces_rendered(self): + set_module_args( + dict(config=[ + dict(name="Ethernet1/2", + access_groups=[ + dict(afi="ipv4", + acls=[ + dict( + name="ACL1v4", + direction="out", + ) + ] + ) + ] + ), + dict(name="Ethernet1/4", + access_groups=[ + dict(afi="ipv6", + acls=[ + dict( + name="ACL2v6", + direction="in", + port=True + ) + ] + ) + ] + ), + ], state="rendered")) + commands = ['interface Ethernet1/2', 'ip access-group ACL1v4 out', + 'interface Ethernet1/4', 'ipv6 port traffic-filter ACL2v6 in'] + result = self.execute_module(changed=False) + self.assertEqual(sorted(result['rendered']), sorted( + commands), result['rendered']) + + def test_nxos_acl_interfaces_parsed(self): + set_module_args(dict(running_config='''interface Ethernet1/2\n ip access-group ACL1v4 out\n interface Ethernet1/4\n \ + ipv6 port traffic-filter ACL2v6 in''', + state="parsed")) + result = self.execute_module(changed=False) + compare_list = [{'access_groups': [{'acls': [{'direction': 'out', 'name': 'ACL1v4'}], 'afi': 'ipv4'}], 'name': 'Ethernet1/2'}, + {'access_groups': [{'acls': [{'direction': 'in', 'name': 'ACL2v6', 'port': True}], 'afi': 'ipv6'}], 'name': 'Ethernet1/4'}] + self.assertEqual(result['parsed'], + compare_list, result['parsed']) + + def test_nxos_acl_interfaces_gathered(self): + set_module_args(dict(config=[], state="gathered")) + result = self.execute_module(changed=False) + compare_list = [{'access_groups': [{'acls': [{'direction': 'out', 'name': 'ACL1v4'}], 'afi': 'ipv4'}], 'name': 'Ethernet1/2'}, + {'access_groups': [{'acls': [{'direction': 'in', 'name': 'ACL2v6', 'port': True}], 'afi': 'ipv6'}], 'name': 'Ethernet1/4'}] + self.assertEqual(result['gathered'], + compare_list, result['gathered'])