diff --git a/lib/ansible/modules/network/nxos/nxos_static_route.py b/lib/ansible/modules/network/nxos/nxos_static_route.py index 3c474371f6..9b1f3c3911 100644 --- a/lib/ansible/modules/network/nxos/nxos_static_route.py +++ b/lib/ansible/modules/network/nxos/nxos_static_route.py @@ -15,9 +15,11 @@ # You should have received a copy of the GNU General Public License # 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 = ''' @@ -27,47 +29,47 @@ extends_documentation_fragment: nxos version_added: "2.2" short_description: Manages static route configuration description: - - Manages static route configuration + - Manages static route configuration author: Gabriele Gerbino (@GGabriele) notes: - - If no vrf is supplied, vrf is set to default. - - If C(state=absent), the route will be removed, regardless of the - non-required parameters. + - If no vrf is supplied, vrf is set to default. + - If C(state=absent), the route will be removed, regardless of the + non-required parameters. options: - prefix: - description: - - Destination prefix of static route. - required: true - next_hop: - description: - - Next hop address or interface of static route. - If interface, it must be the fully-qualified interface name. - required: true - vrf: - description: - - VRF for static route. - required: false - default: default - tag: - description: - - Route tag value (numeric). - required: false - default: null - route_name: - description: - - Name of the route. Used with the name parameter on the CLI. - required: false - default: null - pref: - description: - - Preference or administrative difference of route (range 1-255). - required: false - default: null - state: - description: - - Manage the state of the resource. - required: true - choices: ['present','absent'] + prefix: + description: + - Destination prefix of static route. + required: true + next_hop: + description: + - Next hop address or interface of static route. + If interface, it must be the fully-qualified interface name. + required: true + vrf: + description: + - VRF for static route. + required: false + default: default + tag: + description: + - Route tag value (numeric). + required: false + default: null + route_name: + description: + - Name of the route. Used with the name parameter on the CLI. + required: false + default: null + pref: + description: + - Preference or administrative difference of route (range 1-255). + required: false + default: null + state: + description: + - Manage the state of the resource. + required: true + choices: ['present','absent'] ''' EXAMPLES = ''' @@ -76,86 +78,55 @@ EXAMPLES = ''' next_hop: "3.3.3.3" route_name: testing pref: 100 - username: "{{ un }}" - password: "{{ pwd }}" - host: "{{ inventory_hostname }}" ''' RETURN = ''' -proposed: - description: k/v pairs of parameters passed into module - returned: verbose mode - type: dict - sample: {"next_hop": "3.3.3.3", "pref": "100", - "prefix": "192.168.20.64/24", "route_name": "testing", - "vrf": "default"} -existing: - description: k/v pairs of existing configuration - returned: verbose mode - type: dict - sample: {} -end_state: - description: k/v pairs of configuration after module execution - returned: verbose mode - type: dict - sample: {"next_hop": "3.3.3.3", "pref": "100", - "prefix": "192.168.20.0/24", "route_name": "testing", - "tag": null} -updates: +commands: description: commands sent to the device returned: always type: list sample: ["ip route 192.168.20.0/24 3.3.3.3 name testing 100"] -changed: - description: check to see if a change was made on the device - returned: always - type: boolean - sample: true ''' import re -from ansible.module_utils.nxos import get_config, load_config, run_commands +from ansible.module_utils.nxos import get_config, load_config 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 -def invoke(name, *args, **kwargs): - func = globals().get(name) - if func: - return func(*args, **kwargs) - -def state_present(module, candidate, prefix): - commands = list() - invoke('set_route', module, commands, prefix) - if commands: - if module.params['vrf'] == 'default': - candidate.add(commands, parents=[]) - else: - candidate.add(commands, parents=['vrf context {0}'.format(module.params['vrf'])]) - - -def state_absent(module, candidate, prefix): +def reconcile_candidate(module, candidate, prefix): netcfg = CustomNetworkConfig(indent=2, contents=get_config(module)) - commands = list() - parents = 'vrf context {0}'.format(module.params['vrf']) - invoke('set_route', module, commands, prefix) + state = module.params['state'] + + set_command = set_route_command(module, prefix) + remove_command = remove_route_command(module, prefix) + + parents = [] + commands = [] if module.params['vrf'] == 'default': - config = netcfg.get_section(commands[0]) - if config: - invoke('remove_route', module, commands, config, prefix) - candidate.add(commands, parents=[]) + config = netcfg.get_section(set_command) + if config and state == 'absent': + commands = [remove_command] + elif not config and state == 'present': + commands = [set_command] else: + parents = ['vrf context {0}'.format(module.params['vrf'])] config = netcfg.get_section(parents) - splitted_config = config.split('\n') - splitted_config = map(str.strip, splitted_config) - if commands[0] in splitted_config: - invoke('remove_route', module, commands, config, prefix) - candidate.add(commands, parents=[parents]) + if not isinstance(config, list): + config = config.split('\n') + config = [line.strip() for line in config] + if set_command in config and state == 'absent': + commands = [remove_command] + elif set_command not in config and state == 'present': + commands = [set_command] + + if commands: + candidate.add(commands, parents=parents) def fix_prefix_to_regex(prefix): - prefix = prefix.replace('.', '\.').replace('/', '\/') + prefix = prefix.replace('.', r'\.').replace('/', r'\/') return prefix @@ -165,8 +136,7 @@ def get_existing(module, prefix, warnings): parents = 'vrf context {0}'.format(module.params['vrf']) prefix_to_regex = fix_prefix_to_regex(prefix) - route_regex = ('.*ip\sroute\s{0}\s(?P\S+)(\sname\s(?P\S+))?' - '(\stag\s(?P\d+))?(\s(?P\d+)).*'.format(prefix_to_regex)) + route_regex = r'.*ip\sroute\s{0}\s(?P\S+)(\sname\s(?P\S+))?(\stag\s(?P\d+))?(\s(?P\d+))?.*'.format(prefix_to_regex) if module.params['vrf'] == 'default': config = str(netcfg) @@ -194,11 +164,11 @@ def get_existing(module, prefix, warnings): return group_route -def remove_route(module, commands, config, prefix): - commands.append('no ip route {0} {1}'.format(prefix, module.params['next_hop'])) +def remove_route_command(module, prefix): + return 'no ip route {0} {1}'.format(prefix, module.params['next_hop']) -def set_route(module, commands, prefix): +def set_route_command(module, prefix): route_cmd = 'ip route {0} {1}'.format(prefix, module.params['next_hop']) if module.params['route_name']: @@ -207,15 +177,15 @@ def set_route(module, commands, prefix): route_cmd += ' tag {0}'.format(module.params['tag']) if module.params['pref']: route_cmd += ' {0}'.format(module.params['pref']) - commands.append(route_cmd) + + return route_cmd def get_dotted_mask(mask): bits = 0 - for i in xrange(32-mask,32): + for i in range(32-mask, 32): bits |= (1 << i) - mask = ("%d.%d.%d.%d" % ((bits & 0xff000000) >> 24, - (bits & 0xff0000) >> 16, (bits & 0xff00) >> 8 , (bits & 0xff))) + mask = ("%d.%d.%d.%d" % ((bits & 0xff000000) >> 24, (bits & 0xff0000) >> 16, (bits & 0xff00) >> 8, (bits & 0xff))) return mask @@ -275,53 +245,35 @@ def main(): tag=dict(type='str'), route_name=dict(type='str'), pref=dict(type='str'), - 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) + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + ) warnings = list() check_args(module, warnings) + result = dict(changed=False, warnings=warnings) - state = module.params['state'] + prefix = normalize_prefix(module, module.params['prefix']) - result = dict(changed=False) - warnings = list() - prefix = invoke('normalize_prefix', module, module.params['prefix']) - - existing = invoke('get_existing', module, prefix, warnings) - end_state = existing - - args = ['route_name', 'vrf', 'pref', 'tag', 'next_hop', 'prefix'] - proposed = dict((k, v) for k, v in module.params.items() if v is not None and k in args) - - if state == 'present' or (state == 'absent' and existing): - candidate = CustomNetworkConfig(indent=3) - invoke('state_%s' % state, module, candidate, prefix) + candidate = CustomNetworkConfig(indent=3) + reconcile_candidate(module, candidate, prefix) + if candidate: + candidate = candidate.items_text() load_config(module, candidate) + result['commands'] = candidate + result['changed'] = True else: - result['updates'] = [] - - result['warnings'] = warnings - - if module._verbosity > 0: - end_state = invoke('get_existing', module, prefix, warnings) - result['end_state'] = end_state - result['existing'] = existing - result['proposed'] = proposed + result['commands'] = [] module.exit_json(**result) if __name__ == '__main__': main() - diff --git a/test/units/modules/network/nxos/fixtures/nxos_static_route.cfg b/test/units/modules/network/nxos/fixtures/nxos_static_route.cfg new file mode 100644 index 0000000000..8b1a6fe863 --- /dev/null +++ b/test/units/modules/network/nxos/fixtures/nxos_static_route.cfg @@ -0,0 +1,3 @@ +ip route 10.10.30.0/24 1.2.4.8 +vrf context test + ip route 10.8.0.0/14 15.16.17.18 diff --git a/test/units/modules/network/nxos/test_nxos_static_route.py b/test/units/modules/network/nxos/test_nxos_static_route.py new file mode 100644 index 0000000000..c4876d4a36 --- /dev/null +++ b/test/units/modules/network/nxos/test_nxos_static_route.py @@ -0,0 +1,78 @@ +# (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_static_route +from .nxos_module import TestNxosModule, load_fixture, set_module_args + + +class TestNxosStaticRouteModule(TestNxosModule): + + module = nxos_static_route + + def setUp(self): + self.mock_load_config = patch('ansible.modules.network.nxos.nxos_static_route.load_config') + self.load_config = self.mock_load_config.start() + + self.mock_get_config = patch('ansible.modules.network.nxos.nxos_static_route.get_config') + self.get_config = self.mock_get_config.start() + + def tearDown(self): + self.mock_load_config.stop() + self.mock_get_config.stop() + + def load_fixtures(self, commands=None): + self.get_config.return_value = load_fixture('nxos_static_route.cfg') + self.load_config.return_value = None + + def test_nxos_static_route_present(self): + set_module_args(dict(prefix='192.168.20.64/24', next_hop='3.3.3.3')) + self.execute_module(changed=True, commands=['ip route 192.168.20.0/24 3.3.3.3']) + + def test_nxos_static_route_present_no_defaults(self): + set_module_args(dict(prefix='192.168.20.64/24', next_hop='3.3.3.3', + route_name='testing', pref=100)) + self.execute_module(changed=True, commands=['ip route 192.168.20.0/24 3.3.3.3 name testing 100']) + + def test_nxos_static_route_present_vrf(self): + set_module_args(dict(prefix='192.168.20.64/24', next_hop='3.3.3.3', vrf='test')) + self.execute_module(changed=True, sort=False, commands=['vrf context test', 'ip route 192.168.20.0/24 3.3.3.3']) + + def test_nxos_static_route_no_change(self): + set_module_args(dict(prefix='10.10.30.64/24', next_hop='1.2.4.8')) + self.execute_module(changed=False, commands=[]) + + def test_nxos_static_route_absent(self): + set_module_args(dict(prefix='10.10.30.12/24', next_hop='1.2.4.8', state='absent')) + self.execute_module(changed=True, commands=['no ip route 10.10.30.0/24 1.2.4.8']) + + def test_nxos_static_route_absent_no_change(self): + set_module_args(dict(prefix='192.168.20.6/24', next_hop='3.3.3.3', state='absent')) + self.execute_module(changed=False, commands=[]) + + def test_nxos_static_route_absent_vrf(self): + set_module_args(dict(prefix='10.11.12.13/14', next_hop='15.16.17.18', vrf='test', state='absent')) + self.execute_module( + changed=True, sort=False, + commands=['vrf context test', 'no ip route 10.8.0.0/14 15.16.17.18'] + )