From c0ebdf144d4c79b886172ac83326fb2f500c4b64 Mon Sep 17 00:00:00 2001 From: Nathaniel Case Date: Wed, 26 Apr 2017 16:32:36 -0400 Subject: [PATCH] nxos_acl_interface fixes (#23917) * Update nxos_acl_interface * Add basic unit tests to nxos_acl_interface --- .../network/nxos/nxos_acl_interface.py | 151 ++++++------------ test/sanity/pep8/legacy-files.txt | 1 - .../show_ip_access-list_summary.txt | 34 ++++ .../network/nxos/test_nxos_acl_interface.py | 72 +++++++++ 4 files changed, 154 insertions(+), 104 deletions(-) create mode 100644 test/units/modules/network/nxos/fixtures/nxos_acl_interface/show_ip_access-list_summary.txt create mode 100644 test/units/modules/network/nxos/test_nxos_acl_interface.py diff --git a/lib/ansible/modules/network/nxos/nxos_acl_interface.py b/lib/ansible/modules/network/nxos/nxos_acl_interface.py index 9a07f0436fe..b47d796b877 100644 --- a/lib/ansible/modules/network/nxos/nxos_acl_interface.py +++ b/lib/ansible/modules/network/nxos/nxos_acl_interface.py @@ -16,9 +16,11 @@ # along with Ansible. If not, see . # -ANSIBLE_METADATA = {'metadata_version': '1.0', - 'status': ['preview'], - 'supported_by': 'community'} +ANSIBLE_METADATA = { + 'metadata_version': '1.0', + 'status': ['preview'], + 'supported_by': 'community' +} DOCUMENTATION = ''' @@ -28,30 +30,30 @@ extends_documentation_fragment: nxos version_added: "2.2" short_description: Manages applying ACLs to interfaces. description: - - Manages applying ACLs to interfaces. + - Manages applying ACLs to interfaces. author: - - Jason Edelman (@jedelman8) - - Gabriele Gerbino (@GGabriele) + - Jason Edelman (@jedelman8) + - Gabriele Gerbino (@GGabriele) options: - name: - description: - - Case sensitive name of the access list (ACL). - required: true - interface: - description: - - Full name of interface, e.g. I(Ethernet1/1). - required: true - direction: - description: - - Direction ACL to be applied in on the interface. - required: true - choices: ['ingress', 'egress'] - state: - description: - - Specify desired state of the resource. - required: false - default: present - choices: ['present','absent'] + name: + description: + - Case sensitive name of the access list (ACL). + required: true + interface: + description: + - Full name of interface, e.g. I(Ethernet1/1). + required: true + direction: + description: + - Direction ACL to be applied in on the interface. + required: true + choices: ['ingress', 'egress'] + state: + description: + - Specify desired state of the resource. + required: false + default: present + choices: ['present','absent'] ''' EXAMPLES = ''' @@ -61,45 +63,20 @@ EXAMPLES = ''' interface: ethernet1/41 direction: egress state: present - username: "{{ un }}" - password: "{{ pwd }}" - host: "{{ inventory_hostname }}" ''' RETURN = ''' -proposed: - description: k/v pairs of parameters passed into module - returned: always - type: dict - sample: {"direction": "egress", "interface": "ethernet1/41", - "name": "ANSIBLE"} -existing: - description: k/v pairs of existing ACL applied to the interface - returned: always - type: dict - sample: {} -end_state: - description: k/v pairs of interface ACL after module execution - returned: always - type: dict - sample: {"direction": "egress", "interface": "ethernet1/41", - "name": "ANSIBLE"} acl_applied_to: description: list of interfaces the ACL is applied to returned: always type: list sample: [{"acl_type": "Router ACL", "direction": "egress", "interface": "Ethernet1/41", "name": "ANSIBLE"}] -updates: +commands: description: commands sent to the device returned: always type: list sample: ["interface ethernet1/41", "ip access-group ANSIBLE out"] -changed: - description: check to see if a change was made on the device - returned: always - type: boolean - sample: true ''' import re @@ -108,34 +85,21 @@ from ansible.module_utils.nxos import nxos_argument_spec, check_args from ansible.module_utils.basic import AnsibleModule -def execute_show_command(command, module, command_type='cli_show'): - if module.params['transport'] == 'cli': - if 'summary' not in command: - command += ' | json' - cmds = [command] - body = run_commands(module, cmds) - elif module.params['transport'] == 'nxapi': - cmds = [command] - body = run_commands(module, cmds) - - return body +NAME = r'.*IP?\s+access list\s+(?P\S+).*' +INTERFACE = r'.*\s+(?P\w+(\d+)?\/?(\d+)?)\s-\s(?P\w+)\s+\W(?P\w+\s\w+)\W.*' def get_acl_interface(module, acl): - command = 'show ip access-list summary' - name_regex = '.*IPV4\s+ACL\s+(?P\S+).*' - interface_regex = ('.*\s+(?P\w+(\d+)?\/?(\d+)?)\s-\s' - '(?P\w+)\s+\W(?P\w+\s\w+)\W.*') + command = ['show ip access-list summary'] acl_list = [] - body = execute_show_command(command, module, command_type='cli_show_ascii') + body = run_commands(module, command) body_split = body[0].split('Active on interfaces:') for each_acl in body_split: - intf_list = [] temp = {} try: - match_name = re.match(name_regex, each_acl, re.DOTALL) + match_name = re.match(NAME, each_acl, re.DOTALL) name_dict = match_name.groupdict() name = name_dict['name'] except AttributeError: @@ -143,9 +107,8 @@ def get_acl_interface(module, acl): temp['interfaces'] = [] for line in each_acl.split('\n'): - intf_temp = {} try: - match_interface = re.match(interface_regex, line, re.DOTALL) + match_interface = re.match(INTERFACE, line, re.DOTALL) interface_dict = match_interface.groupdict() interface = interface_dict['interface'] direction = interface_dict['direction'] @@ -155,6 +118,7 @@ def get_acl_interface(module, acl): direction = '' acl_type = '' + intf_temp = {} if interface: intf_temp['interface'] = interface if acl_type: @@ -185,21 +149,17 @@ def other_existing_acl(get_existing, interface, direction): # now we'll just get the interface in question # needs to be a list since same acl could be applied in both dirs acls_interface = [] + this = {} + if get_existing: for each in get_existing: if each.get('interface').lower() == interface: acls_interface.append(each) - else: - acls_interface = [] - if acls_interface: - this = {} - for each in acls_interface: - if each.get('direction') == direction: - this = each - else: - acls_interface = [] - this = {} + if acls_interface: + for each in acls_interface: + if each.get('direction') == direction: + this = each return acls_interface, this @@ -247,21 +207,18 @@ def main(): name=dict(required=False, type='str'), interface=dict(required=True), direction=dict(required=True, choices=['egress', 'ingress']), - state=dict(choices=['absent', 'present'], - default='present'), - include_defaults=dict(default=True), - config=dict(), - save=dict(type='bool', default=False) + state=dict(choices=['absent', 'present'], default='present'), ) argument_spec.update(nxos_argument_spec) module = AnsibleModule(argument_spec=argument_spec, - supports_check_mode=True) + supports_check_mode=True) warnings = list() check_args(module, warnings) + results = dict(changed=False, warnings=warnings) state = module.params['state'] name = module.params['name'] @@ -275,12 +232,9 @@ def main(): # interface_acls = includes entries of this ACL on the interface (list) # this_dir_acl_intf = dict - not null if it already exists - interfaces_acls, existing = other_existing_acl( - get_existing, interface, direction) + interfaces_acls, existing = other_existing_acl(get_existing, interface, direction) - end_state = existing end_state_acls = get_existing - changed = False cmds = [] commands = [] @@ -303,23 +257,15 @@ def main(): module.exit_json(changed=True, commands=cmds) else: load_config(module, cmds) - changed = True + results['changed'] = True end_state_acls = get_acl_interface(module, name) - interfaces_acls, this_dir_acl_intf = other_existing_acl( - end_state_acls, interface, direction) - end_state = this_dir_acl_intf + interfaces_acls, this_dir_acl_intf = other_existing_acl(end_state_acls, interface, direction) if 'configure' in cmds: cmds.pop(0) else: cmds = [] - results = {} - results['proposed'] = proposed - results['existing'] = existing - results['updates'] = cmds - results['changed'] = changed - results['warnings'] = warnings - results['end_state'] = end_state + results['commands'] = cmds results['acl_applied_to'] = end_state_acls module.exit_json(**results) @@ -327,4 +273,3 @@ def main(): if __name__ == '__main__': main() - diff --git a/test/sanity/pep8/legacy-files.txt b/test/sanity/pep8/legacy-files.txt index 35aaa0d76da..9f15886d6aa 100644 --- a/test/sanity/pep8/legacy-files.txt +++ b/test/sanity/pep8/legacy-files.txt @@ -541,7 +541,6 @@ lib/ansible/modules/network/nxos/_nxos_mtu.py lib/ansible/modules/network/nxos/_nxos_template.py lib/ansible/modules/network/nxos/nxos_aaa_server.py lib/ansible/modules/network/nxos/nxos_aaa_server_host.py -lib/ansible/modules/network/nxos/nxos_acl_interface.py lib/ansible/modules/network/nxos/nxos_bgp.py lib/ansible/modules/network/nxos/nxos_bgp_af.py lib/ansible/modules/network/nxos/nxos_bgp_neighbor.py diff --git a/test/units/modules/network/nxos/fixtures/nxos_acl_interface/show_ip_access-list_summary.txt b/test/units/modules/network/nxos/fixtures/nxos_acl_interface/show_ip_access-list_summary.txt new file mode 100644 index 00000000000..7c8b16a8b2d --- /dev/null +++ b/test/units/modules/network/nxos/fixtures/nxos_acl_interface/show_ip_access-list_summary.txt @@ -0,0 +1,34 @@ +IP access list __urpf_v4_acl__ + Total ACEs Configured: 1 + Configured on interfaces: + Active on interfaces: +IP access list copp-system-p-acl-bgp + Total ACEs Configured: 2 + Configured on interfaces: + ethernet1/41 - egress (Router ACL) + Active on interfaces: + ethernet1/41 - egress (Router ACL) +IP access list copp-system-p-acl-cts + Total ACEs Configured: 2 + Configured on interfaces: + Active on interfaces: +IP access list copp-system-p-acl-dhcp + Total ACEs Configured: 2 + Configured on interfaces: + Active on interfaces: +IP access list copp-system-p-acl-dhcp-relay-response + Total ACEs Configured: 2 + Configured on interfaces: + Active on interfaces: +IP access list copp-system-p-acl-eigrp + Total ACEs Configured: 1 + Configured on interfaces: + Active on interfaces: +IP access list copp-system-p-acl-ftp + Total ACEs Configured: 4 + Configured on interfaces: + Active on interfaces: +IP access list copp-system-p-acl-glbp + Total ACEs Configured: 1 + Configured on interfaces: + Active on interfaces: diff --git a/test/units/modules/network/nxos/test_nxos_acl_interface.py b/test/units/modules/network/nxos/test_nxos_acl_interface.py new file mode 100644 index 00000000000..0b7e2288211 --- /dev/null +++ b/test/units/modules/network/nxos/test_nxos_acl_interface.py @@ -0,0 +1,72 @@ +# (c) 2016 Red Hat Inc. +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . + +# Make coding more python3-ish +from __future__ import (absolute_import, division, print_function) +__metacl_interfaceass__ = type + +import json + +from ansible.compat.tests.mock import patch +from ansible.modules.network.nxos import nxos_acl_interface +from .nxos_module import TestNxosModule, load_fixture, set_module_args + + +class TestNxosAclInterfaceModule(TestNxosModule): + + module = nxos_acl_interface + + def setUp(self): + self.mock_run_commands = patch('ansible.modules.network.nxos.nxos_acl_interface.run_commands') + self.run_commands = self.mock_run_commands.start() + + self.mock_load_config = patch('ansible.modules.network.nxos.nxos_acl_interface.load_config') + self.load_config = self.mock_load_config.start() + + def tearDown(self): + self.mock_run_commands.stop() + self.mock_load_config.stop() + + def load_fixtures(self, commands=None): + def load_from_file(*args, **kwargs): + module, commands = args + output = list() + + for item in commands: + try: + obj = json.loads(item) + command = obj['command'] + except ValueError: + command = item + filename = str(command).split(' | ')[0].replace(' ', '_') + filename = 'nxos_acl_interface/%s.txt' % filename + output.append(load_fixture(filename)) + return output + + self.run_commands.side_effect = load_from_file + self.load_config.return_value = None + + def test_nxos_acl_interface(self): + set_module_args(dict(name='ANSIBLE', interface='ethernet1/41', direction='egress')) + result = self.execute_module(changed=True) + self.assertEqual(result['commands'], ['interface ethernet1/41', 'ip access-group ANSIBLE out']) + + def test_nxos_acl_interface_remove(self): + set_module_args(dict(name='copp-system-p-acl-bgp', interface='ethernet1/41', + direction='egress', state='absent')) + result = self.execute_module(changed=True) + self.assertEqual(result['commands'], ['interface ethernet1/41', 'no ip access-group copp-system-p-acl-bgp out'])