diff --git a/lib/ansible/modules/network/nxos/nxos_portchannel.py b/lib/ansible/modules/network/nxos/nxos_portchannel.py index a0637b7c9eb..2acb8bada40 100644 --- a/lib/ansible/modules/network/nxos/nxos_portchannel.py +++ b/lib/ansible/modules/network/nxos/nxos_portchannel.py @@ -16,10 +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,52 +29,53 @@ extends_documentation_fragment: nxos version_added: "2.2" short_description: Manages port-channel interfaces. description: - - Manages port-channel specific configuration parameters. + - Manages port-channel specific configuration parameters. author: - - Jason Edelman (@jedelman8) - - Gabriele Gerbino (@GGabriele) + - Jason Edelman (@jedelman8) + - Gabriele Gerbino (@GGabriele) notes: - - C(state=absent) removes the portchannel config and interface if it - already exists. If members to be removed are not explicitly - passed, all existing members (if any), are removed. - - Members must be a list. - - LACP needs to be enabled first if active/passive modes are used. + - C(state=absent) removes the portchannel config and interface if it + already exists. If members to be removed are not explicitly + passed, all existing members (if any), are removed. + - Members must be a list. + - LACP needs to be enabled first if active/passive modes are used. options: - group: - description: - - Channel-group number for the port-channel. - required: true - mode: - description: - - Mode for the port-channel, i.e. on, active, passive. - required: false - default: on - choices: ['active','passive','on'] - min_links: - description: - - Min links required to keep portchannel up. - required: false - default: null - members: - description: - - List of interfaces that will be managed in a given portchannel. - required: false - default: null - force: - description: - - When true it forces port-channel members to match what is - declared in the members param. This can be used to remove - members. - required: false - choices: ['true', 'false'] - default: false - state: - description: - - Manage the state of the resource. - required: false - default: present - choices: ['present','absent'] + group: + description: + - Channel-group number for the port-channel. + required: true + mode: + description: + - Mode for the port-channel, i.e. on, active, passive. + required: false + default: on + choices: ['active','passive','on'] + min_links: + description: + - Min links required to keep portchannel up. + required: false + default: null + members: + description: + - List of interfaces that will be managed in a given portchannel. + required: false + default: null + force: + description: + - When true it forces port-channel members to match what is + declared in the members param. This can be used to remove + members. + required: false + choices: ['true', 'false'] + default: false + state: + description: + - Manage the state of the resource. + required: false + default: present + choices: ['present','absent'] ''' + EXAMPLES = ''' # Ensure port-channel99 is created, add two members, and set to mode on - nxos_portchannel: @@ -81,38 +83,10 @@ EXAMPLES = ''' members: ['Ethernet1/1','Ethernet1/2'] mode: 'active' 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: {"group": "12", "members": ["Ethernet2/5", - "Ethernet2/6"], "mode": "on"} -existing: - description: - - k/v pairs of existing portchannel - returned: always - type: dict - sample: {"group": "12", "members": ["Ethernet2/5", - "Ethernet2/6"], "members_detail": { - "Ethernet2/5": {"mode": "active", "status": "D"}, - "Ethernet2/6": {"mode": "active", "status": "D"}}, - "min_links": null, "mode": "active"} -end_state: - description: k/v pairs of portchannel info after module execution - returned: always - type: dict - sample: {"group": "12", "members": ["Ethernet2/5", - "Ethernet2/6"], "members_detail": { - "Ethernet2/5": {"mode": "on", "status": "D"}, - "Ethernet2/6": {"mode": "on", "status": "D"}}, - "min_links": null, "mode": "on"} -updates: +commands: description: command sent to the device returned: always type: list @@ -120,38 +94,25 @@ updates: "interface Ethernet2/5", "no channel-group 12", "interface Ethernet2/6", "channel-group 12 mode on", "interface Ethernet2/5", "channel-group 12 mode on"] -changed: - description: check to see if a change was made on the device - returned: always - type: boolean - sample: true ''' +import collections +import re + from ansible.module_utils.nxos import get_config, load_config, run_commands from ansible.module_utils.nxos import nxos_argument_spec, check_args from ansible.module_utils.basic import AnsibleModule from ansible.module_utils.netcfg import CustomNetworkConfig -import collections - -import re -import re -WARNINGS = [] -PARAM_TO_COMMAND_KEYMAP = { - 'min_links': 'lacp min-links' -} - - -def invoke(name, *args, **kwargs): - func = globals().get(name) - if func: - return func(*args, **kwargs) - def get_value(arg, config, module): - REGEX = re.compile(r'(?:{0}\s)(?P.*)$'.format(PARAM_TO_COMMAND_KEYMAP[arg]), re.M) + param_to_command_keymap = { + 'min_links': 'lacp min-links' + } + + REGEX = re.compile(r'(?:{0}\s)(?P.*)$'.format(param_to_command_keymap[arg]), re.M) value = '' - if PARAM_TO_COMMAND_KEYMAP[arg] in config: + if param_to_command_keymap[arg] in config: value = REGEX.search(config).group('value') return value @@ -181,7 +142,7 @@ def get_custom_value(arg, config, module): return value -def execute_show_command(command, module, command_type='cli_show'): +def execute_show_command(command, module): if module.params['transport'] == 'cli': if 'show port-channel summary' in command: command += ' | json' @@ -232,10 +193,9 @@ def get_portchannel(module, netcfg=None): portchannel_table = {} members = [] - body = execute_show_command(command, module) - try: - pc_table = body[0]['TABLE_channel']['ROW_channel'] + body = execute_show_command(command, module)[0] + pc_table = body['TABLE_channel']['ROW_channel'] if isinstance(pc_table, dict): pc_table = [pc_table] @@ -295,19 +255,6 @@ def get_existing(module, args): return existing, interface_exist -def apply_key_map(key_map, table): - new_dict = {} - for key, value in table.items(): - new_key = key_map.get(key) - if new_key: - value = table.get(key) - if value: - new_dict[new_key] = value - else: - new_dict[new_key] = value - return new_dict - - def config_portchannel(proposed, mode, group): commands = [] config_args = { @@ -434,17 +381,53 @@ def flatten_list(command_lists): return flat_command_list +def state_present(module, existing, proposed, interface_exist, force, warnings): + commands = [] + group = str(module.params['group']) + mode = module.params['mode'] + min_links = module.params['min_links'] + + if not interface_exist: + command = config_portchannel(proposed, mode, group) + commands.append(command) + commands.insert(0, 'interface port-channel{0}'.format(group)) + warnings.append("The proposed port-channel interface did not " + "exist. It's recommended to use nxos_interface to " + "create all logical interfaces.") + + elif existing and interface_exist: + if force: + command = get_commands_to_remove_members(proposed, existing, module) + commands.append(command) + + command = get_commands_to_add_members(proposed, existing, module) + commands.append(command) + + mode_command = get_commands_if_mode_change(proposed, existing, group, mode, module) + commands.insert(0, mode_command) + + if min_links: + command = get_commands_min_links(existing, proposed, group, min_links, module) + commands.append(command) + + return commands + + +def state_absent(module, existing, proposed): + commands = [] + group = str(module.params['group']) + commands.append(['no interface port-channel{0}'.format(group)]) + return commands + + def main(): argument_spec = dict( group=dict(required=True, type='str'), - mode=dict(required=False, choices=['on', 'active', 'passive'], - default='on', type='str'), + mode=dict(required=False, choices=['on', 'active', 'passive'], default='on', type='str'), min_links=dict(required=False, default=None, type='str'), members=dict(required=False, default=None, type='list'), - force=dict(required=False, default='false', type='str', - choices=['true', 'false']), - state=dict(required=False, choices=['absent', 'present'], - default='present'), + force=dict(required=False, default='false', type='str', choices=['true', 'false']), + state=dict(required=False, choices=['absent', 'present'], default='present'), include_defaults=dict(default=False), config=dict(), save=dict(type='bool', default=False) @@ -452,12 +435,11 @@ def main(): argument_spec.update(nxos_argument_spec) - module = AnsibleModule(argument_spec=argument_spec, - supports_check_mode=True) + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True) warnings = list() check_args(module, warnings) - + results = dict(changed=False, warnings=warnings) group = str(module.params['group']) mode = module.params['mode'] @@ -475,76 +457,37 @@ def main(): module.fail_json(msg='"members" is required when state=present and ' '"min_links" or "mode" are provided') - changed = False - args = [ + args = [ 'group', 'members', 'min_links', 'mode' ] - existing, interface_exist = invoke('get_existing', module, args) - end_state = existing + existing, interface_exist = get_existing(module, args) proposed = dict((k, v) for k, v in module.params.items() if v is not None and k in args) - result = {} commands = [] - if state == 'absent': - if existing: - commands.append(['no interface port-channel{0}'.format(group)]) + + if state == 'absent' and existing: + commands = state_absent(module, existing, proposed) elif state == 'present': - if not interface_exist: - command = config_portchannel(proposed, mode, group) - commands.append(command) - commands.insert(0, 'interface port-channel{0}'.format(group)) - WARNINGS.append("The proposed port-channel interface did not " - "exist. It's recommended to use nxos_interface to " - "create all logical interfaces.") - - elif existing and interface_exist: - if force: - command = get_commands_to_remove_members(proposed, existing, module) - commands.append(command) - - command = get_commands_to_add_members(proposed, existing, module) - commands.append(command) - - mode_command = get_commands_if_mode_change(proposed, existing, - group, mode, module) - - commands.insert(0, mode_command) - - if min_links: - command = get_commands_min_links(existing, proposed, - group, min_links, module) - commands.append(command) + commands = state_present(module, existing, proposed, interface_exist, force, warnings) cmds = flatten_list(commands) if cmds: if module.check_mode: - module.exit_json(changed=True, commands=cmds) + module.exit_json(**results) else: load_config(module, cmds) - changed = True - end_state, interface_exist = get_existing(module, args) + results['changed'] = True if 'configure' in cmds: cmds.pop(0) - results = {} - results['proposed'] = proposed - results['existing'] = existing - results['end_state'] = end_state - results['updates'] = cmds - results['changed'] = changed - results['warnings'] = warnings - - if WARNINGS: - results['warnings'] = WARNINGS - + results['commands'] = cmds module.exit_json(**results) if __name__ == '__main__': main() - diff --git a/test/sanity/pep8/legacy-files.txt b/test/sanity/pep8/legacy-files.txt index ff5711ac330..81eea886be8 100644 --- a/test/sanity/pep8/legacy-files.txt +++ b/test/sanity/pep8/legacy-files.txt @@ -474,7 +474,6 @@ lib/ansible/modules/network/nxos/nxos_pim.py lib/ansible/modules/network/nxos/nxos_pim_interface.py lib/ansible/modules/network/nxos/nxos_pim_rp_address.py lib/ansible/modules/network/nxos/nxos_ping.py -lib/ansible/modules/network/nxos/nxos_portchannel.py lib/ansible/modules/network/nxos/nxos_smu.py lib/ansible/modules/network/nxos/nxos_snapshot.py lib/ansible/modules/network/nxos/nxos_snmp_community.py diff --git a/test/units/modules/network/nxos/test_nxos_portchannel.py b/test/units/modules/network/nxos/test_nxos_portchannel.py new file mode 100644 index 00000000000..684805e6a60 --- /dev/null +++ b/test/units/modules/network/nxos/test_nxos_portchannel.py @@ -0,0 +1,61 @@ +# (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) +__metaclass__ = type + +import json + +from ansible.compat.tests.mock import patch +from ansible.modules.network.nxos import nxos_portchannel +from .nxos_module import TestNxosModule, load_fixture, set_module_args + + +class TestNxosPortchannelModule(TestNxosModule): + + module = nxos_portchannel + + def setUp(self): + self.mock_run_commands = patch('ansible.modules.network.nxos.nxos_portchannel.run_commands') + self.run_commands = self.mock_run_commands.start() + + self.mock_load_config = patch('ansible.modules.network.nxos.nxos_portchannel.load_config') + self.load_config = self.mock_load_config.start() + + self.mock_get_config = patch('ansible.modules.network.nxos.nxos_portchannel.get_config') + self.get_config = self.mock_get_config.start() + + def tearDown(self): + self.mock_run_commands.stop() + self.mock_load_config.stop() + self.mock_get_config.stop() + + def load_fixtures(self, commands=None): + self.load_config.return_value = None + + def test_nxos_portchannel(self): + set_module_args(dict(group='99', + members=['Ethernet2/1', 'Ethernet2/2'], + mode='active', + state='present')) + result = self.execute_module(changed=True) + self.assertEqual(result['commands'], ['interface port-channel99', + 'interface Ethernet2/1', + 'channel-group 99 mode active', + 'interface Ethernet2/2', + 'channel-group 99 mode active'])