Vyos static route module added (#62193)

* Vyos static route module added

Signed-off-by: rohitthakur2590 <rohitthakur2590@outlook.com>

* sanity fixes

Signed-off-by: rohitthakur2590 <rohitthakur2590@outlook.com>

* empty config traceback fix

Signed-off-by: rohitthakur2590 <rohitthakur2590@outlook.com>

* sanity check fix

* model specific changes and SI test cases updated

Signed-off-by: rohitthakur2590 <rohitthakur2590@outlook.com>

* new state changes and SI test cases updated

Signed-off-by: rohitthakur2590 <rohitthakur2590@outlook.com>

* sanity fixes

Signed-off-by: rohitthakur2590 <rohitthakur2590@outlook.com>

* UT cases added

Signed-off-by: rohitthakur2590 <rohitthakur2590@outlook.com>

* replaced operation fix

Signed-off-by: rohitthakur2590 <rohitthakur2590@outlook.com>

* review comments incorporated

Signed-off-by: rohitthakur2590 <rohitthakur2590@outlook.com>

* shippable fix

Signed-off-by: rohitthakur2590 <rohitthakur2590@outlook.com>

* sanity fix

Signed-off-by: rohitthakur2590 <rohitthakur2590@outlook.com>

* delete opr updated

Signed-off-by: rohitthakur2590 <rohitthakur2590@outlook.com>

* comments incorporated

Signed-off-by: rohitthakur2590 <rohitthakur2590@outlook.com>
This commit is contained in:
Rohit 2020-02-18 07:32:26 -05:00 committed by GitHub
parent b6753b46a9
commit 9eb7709c61
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
36 changed files with 3045 additions and 17 deletions

View file

@ -54,6 +54,7 @@ Deprecation notices
The following modules will be removed in Ansible 2.14. Please update your playbooks accordingly. The following modules will be removed in Ansible 2.14. Please update your playbooks accordingly.
* ldap_attr use :ref:`ldap_attrs <ldap_attrs_module>` instead. * ldap_attr use :ref:`ldap_attrs <ldap_attrs_module>` instead.
* vyos_static_route use :ref:`vyos_static_routes <vyos_static_routes_module>` instead.
The following functionality will be removed in Ansible 2.14. Please update update your playbooks accordingly. The following functionality will be removed in Ansible 2.14. Please update update your playbooks accordingly.

View file

@ -0,0 +1,107 @@
#
# -*- 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 vyos_static_routes module
"""
from __future__ import absolute_import, division, print_function
__metaclass__ = type
class Static_routesArgs(object): # pylint: disable=R0903
"""The arg spec for the vyos_static_routes module
"""
def __init__(self, **kwargs):
pass
argument_spec = {
'config': {
'elements': 'dict',
'options': {
'address_families': {
'elements': 'dict',
'options': {
'afi': {
'choices': ['ipv4', 'ipv6'],
'required': True,
'type': 'str'
},
'routes': {
'elements': 'dict',
'options': {
'blackhole_config': {
'options': {
'distance': {
'type': 'int'
},
'type': {
'type': 'str'
}
},
'type': 'dict'
},
'dest': {
'required': True,
'type': 'str'
},
'next_hops': {
'elements': 'dict',
'options': {
'admin_distance': {
'type': 'int'
},
'enabled': {
'type': 'bool'
},
'forward_router_address': {
'required': True,
'type': 'str'
},
'interface': {
'type': 'str'
}
},
'type': 'list'
}
},
'type': 'list'
}
},
'type': 'list'
}
},
'type': 'list'
},
'running_config': {'type': 'str'},
'state': {
'choices': [
'merged', 'replaced', 'overridden', 'deleted', 'gathered',
'rendered', 'parsed'
],
'default':
'merged',
'type':
'str'
}
} # pylint: disable=C0301

View file

@ -0,0 +1,523 @@
#
# -*- 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 vyos_static_routes 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 copy import deepcopy
from ansible.module_utils.network.common.cfg.base import ConfigBase
from ansible.module_utils.network.common.utils import to_list, dict_diff, remove_empties
from ansible.module_utils.network.vyos.facts.facts import Facts
from ansible.module_utils.six import iteritems
from ansible.module_utils.network. vyos.utils.utils import get_route_type, \
diff_list_of_dicts, get_lst_diff_for_dicts, get_lst_same_for_dicts, dict_delete
class Static_routes(ConfigBase):
"""
The vyos_static_routes class
"""
gather_subset = [
'!all',
'!min',
]
gather_network_resources = [
'static_routes',
]
def __init__(self, module):
super(Static_routes, self).__init__(module)
def get_static_routes_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)
static_routes_facts = facts['ansible_network_resources'].get('static_routes')
if not static_routes_facts:
return []
return static_routes_facts
def execute_module(self):
""" Execute the module
:rtype: A dictionary
:returns: The result from module execution
"""
result = {'changed': False}
warnings = list()
commands = list()
if self.state in self.ACTION_STATES:
existing_static_routes_facts = self.get_static_routes_facts()
else:
existing_static_routes_facts = []
if self.state in self.ACTION_STATES or self.state == 'rendered':
commands.extend(self.set_config(existing_static_routes_facts))
if commands and self.state in self.ACTION_STATES:
if not self._module.check_mode:
self._connection.edit_config(commands)
result['changed'] = True
if self.state in self.ACTION_STATES:
result['commands'] = commands
if self.state in self.ACTION_STATES or self.state == 'gathered':
changed_static_routes_facts = self.get_static_routes_facts()
elif self.state == 'rendered':
result['rendered'] = commands
elif self.state == 'parsed':
running_config = self._module.params['running_config']
if not running_config:
self._module.fail_json(
msg="value of running_config parameter must not be empty for state parsed"
)
result['parsed'] = self.get_static_routes_facts(data=running_config)
else:
changed_static_routes_facts = []
if self.state in self.ACTION_STATES:
result['before'] = existing_static_routes_facts
if result['changed']:
result['after'] = changed_static_routes_facts
elif self.state == 'gathered':
result['gathered'] = changed_static_routes_facts
result['warnings'] = warnings
return result
def set_config(self, existing_static_routes_facts):
""" Collect the configuration from the args passed to the module,
collect the current configuration (as a dict from facts)
:rtype: A list
:returns: the commands necessary to migrate the current configuration
to the desired configuration
"""
want = self._module.params['config']
have = existing_static_routes_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
"""
commands = []
if self.state in ('merged', 'replaced', 'overridden', 'rendered') and not want:
self._module.fail_json(msg='value of config parameter must not be empty for state {0}'.format(self.state))
if self.state == 'overridden':
commands.extend(self._state_overridden(want=want, have=have))
elif self.state == 'deleted':
commands.extend(self._state_deleted(want=want, have=have))
elif want:
routes = self._get_routes(want)
for r in routes:
h_item = self.search_route_in_have(have, r['dest'])
if self.state == 'merged' or self.state == 'rendered':
commands.extend(self._state_merged(want=r, have=h_item))
elif self.state == 'replaced':
commands.extend(self._state_replaced(want=r, have=h_item))
return commands
def search_route_in_have(self, have, want_dest):
"""
This function returns the route if its found in
have config.
:param have:
:param dest:
:return: the matched route
"""
routes = self._get_routes(have)
for r in routes:
if r['dest'] == want_dest:
return r
return None
def _state_replaced(self, want, have):
""" The command generator when state is replaced
:rtype: A list
:returns: the commands necessary to migrate the current configuration
to the desired configuration
"""
commands = []
if have:
for key, value in iteritems(want):
if value:
if key == 'next_hops':
commands.extend(self._update_next_hop(want, have))
elif key == 'blackhole_config':
commands.extend(self._update_blackhole(key, want, have))
commands.extend(self._state_merged(want, have))
return commands
def _state_overridden(self, want, have):
""" The command generator when state is overridden
:rtype: A list
:returns: the commands necessary to migrate the current configuration
to the desired configuration
"""
commands = []
routes = self._get_routes(have)
for r in routes:
route_in_want = self.search_route_in_have(want, r['dest'])
if not route_in_want:
commands.append(self._compute_command(r['dest'], remove=True))
routes = self._get_routes(want)
for r in routes:
route_in_have = self.search_route_in_have(have, r['dest'])
commands.extend(self._state_replaced(r, route_in_have))
return commands
def _state_merged(self, want, have, opr=True):
""" The command generator when state is merged
:rtype: A list
:returns: the commands necessary to merge the provided into
the current configuration
"""
commands = []
if have:
commands.extend(self._render_updates(want, have))
else:
commands.extend(self._render_set_commands(want))
return commands
def _state_deleted(self, want, have):
""" The command generator when state is deleted
:rtype: A list
:returns: the commands necessary to remove the current configuration
of the provided objects
"""
commands = []
if want:
routes = self._get_routes(want)
if not routes:
for w in want:
af = w['address_families']
for item in af:
if self.afi_in_have(have, item):
commands.append(self._compute_command(afi=item['afi'], remove=True))
for r in routes:
h_route = self.search_route_in_have(have, r['dest'])
if h_route:
commands.extend(self._render_updates(r, h_route, opr=False))
else:
routes = self._get_routes(have)
if self._is_ip_route_exist(routes):
commands.append(self._compute_command(afi='ipv4', remove=True))
if self._is_ip_route_exist(routes, 'route6'):
commands.append(self._compute_command(afi='ipv6', remove=True))
return commands
def _render_set_commands(self, want):
"""
This function returns the list of commands to add attributes which are
present in want
:param want:
:return: list of commands.
"""
commands = []
have = {}
for key, value in iteritems(want):
if value:
if key == 'dest':
commands.append(
self._compute_command(dest=want['dest'])
)
elif key == 'blackhole_config':
commands.extend(self._add_blackhole(key, want, have))
elif key == 'next_hops':
commands.extend(self._add_next_hop(want, have))
return commands
def _add_blackhole(self, key, want, have):
"""
This function gets the diff for blackhole config specific attributes
and form the commands for attributes which are present in want but not in have.
:param key:
:param want:
:param have:
:return: list of commands
"""
commands = []
want_copy = deepcopy(remove_empties(want))
have_copy = deepcopy(remove_empties(have))
want_blackhole = want_copy.get(key) or {}
have_blackhole = have_copy.get(key) or {}
updates = dict_delete(want_blackhole, have_blackhole)
if updates:
for attrib, value in iteritems(updates):
if value:
if attrib == 'distance':
commands.append(
self._compute_command(dest=want['dest'], key='blackhole',
attrib=attrib, remove=False, value=str(value))
)
elif attrib == 'type':
commands.append(
self._compute_command(dest=want['dest'], key='blackhole')
)
return commands
def _add_next_hop(self, want, have, opr=True):
"""
This function gets the diff for next hop specific attributes
and form the commands to add attributes which are present in want but not in have.
:param want:
:param have:
:return: list of commands.
"""
commands = []
want_copy = deepcopy(remove_empties(want))
have_copy = deepcopy(remove_empties(have))
if not opr:
diff_next_hops = get_lst_same_for_dicts(want_copy, have_copy, 'next_hops')
else:
diff_next_hops = get_lst_diff_for_dicts(want_copy, have_copy, 'next_hops')
if diff_next_hops:
for hop in diff_next_hops:
for element in hop:
if element == 'forward_router_address':
commands.append(
self._compute_command(dest=want['dest'],
key='next-hop',
value=hop[element],
opr=opr)
)
elif element == 'enabled' and not hop[element]:
commands.append(
self._compute_command(dest=want['dest'],
key='next-hop',
attrib=hop['forward_router_address'],
value='disable',
opr=opr)
)
elif element == 'admin_distance':
commands.append(
self._compute_command(dest=want['dest'],
key='next-hop',
attrib=hop['forward_router_address'] + " " + element,
value=str(hop[element]),
opr=opr)
)
elif element == 'interface':
commands.append(
self._compute_command(dest=want['dest'],
key='next-hop',
attrib=hop['forward_router_address'] + " " + element,
value=hop[element],
opr=opr)
)
return commands
def _update_blackhole(self, key, want, have):
"""
This function gets the difference for blackhole dict and
form the commands to delete the attributes which are present in have but not in want.
:param want:
:param have:
:return: list of commands
:param key:
:param want:
:param have:
:return: list of commands
"""
commands = []
want_copy = deepcopy(remove_empties(want))
have_copy = deepcopy(remove_empties(have))
want_blackhole = want_copy.get(key) or {}
have_blackhole = have_copy.get(key) or {}
updates = dict_delete(have_blackhole, want_blackhole)
if updates:
for attrib, value in iteritems(updates):
if value:
if attrib == 'distance':
commands.append(
self._compute_command(dest=want['dest'], key='blackhole',
attrib=attrib, remove=True, value=str(value))
)
elif attrib == 'type' and 'distance' not in want_blackhole.keys():
commands.append(
self._compute_command(dest=want['dest'], key='blackhole', remove=True)
)
return commands
def _update_next_hop(self, want, have, opr=True):
"""
This function gets the difference for next_hops list and
form the commands to delete the attributes which are present in have but not in want.
:param want:
:param have:
:return: list of commands
"""
commands = []
want_copy = deepcopy(remove_empties(want))
have_copy = deepcopy(remove_empties(have))
diff_next_hops = get_lst_diff_for_dicts(have_copy, want_copy, 'next_hops')
if diff_next_hops:
for hop in diff_next_hops:
for element in hop:
if element == 'forward_router_address':
commands.append(
self._compute_command(dest=want['dest'], key='next-hop', value=hop[element], remove=True)
)
elif element == 'enabled':
commands.append(
self._compute_command(dest=want['dest'],
key='next-hop', attrib=hop['forward_router_address'], value='disable', remove=True)
)
elif element == 'admin_distance':
commands.append(
self._compute_command(dest=want['dest'], key='next-hop',
attrib=hop['forward_router_address'] + " " + element, value=str(hop[element]), remove=True)
)
elif element == 'interface':
commands.append(
self._compute_command(dest=want['dest'], key='next-hop',
attrib=hop['forward_router_address'] + " " + element, value=hop[element], remove=True)
)
return commands
def _render_updates(self, want, have, opr=True):
"""
This function takes the diff between want and have and
invokes the appropriate functions to create the commands
to update the attributes.
:param want:
:param have:
:return: list of commands
"""
commands = []
want_nh = want.get('next_hops') or []
# delete static route operation per destination
if not opr and not want_nh:
commands.append(self._compute_command(dest=want['dest'], remove=True))
else:
temp_have_next_hops = have.pop('next_hops', None)
temp_want_next_hops = want.pop('next_hops', None)
updates = dict_diff(have, want)
if temp_have_next_hops:
have['next_hops'] = temp_have_next_hops
if temp_want_next_hops:
want['next_hops'] = temp_want_next_hops
commands.extend(self._add_next_hop(want, have, opr=opr))
if opr and updates:
for key, value in iteritems(updates):
if value:
if key == 'blackhole_config':
commands.extend(self._add_blackhole(key, want, have))
return commands
def _compute_command(self, dest=None, key=None, attrib=None, value=None, remove=False, afi=None, opr=True):
"""
This functions construct the required command based on the passed arguments.
:param dest:
:param key:
:param attrib:
:param value:
:param remove:
:return: constructed command
"""
if remove or not opr:
cmd = 'delete protocols static ' + self.get_route_type(dest, afi)
else:
cmd = 'set protocols static ' + self.get_route_type(dest, afi)
if dest:
cmd += (' ' + dest)
if key:
cmd += (' ' + key)
if attrib:
cmd += (' ' + attrib)
if value:
cmd += (" '" + value + "'")
return cmd
def afi_in_have(self, have, w_item):
"""
This functions checks for the afi
list in have
:param have:
:param w_item:
:return:
"""
if have:
for h in have:
af = h.get('address_families') or []
for item in af:
if w_item['afi'] == item['afi']:
return True
return False
def get_route_type(self, dest=None, afi=None):
"""
This function returns the route type based on
destination ip address or afi
:param address:
:return:
"""
if dest:
return get_route_type(dest)
elif afi == 'ipv4':
return 'route'
elif afi == 'ipv6':
return 'route6'
def _is_ip_route_exist(self, routes, type='route'):
"""
This functions checks for the type of route.
:param routes:
:param type:
:return: True/False
"""
for r in routes:
if type == self.get_route_type(r['dest']):
return True
return False
def _get_routes(self, lst):
"""
This function returns the list of routes
:param lst: list of address families
:return: list of routes
"""
r_list = []
for item in lst:
af = item['address_families']
for element in af:
routes = element.get('routes') or []
for r in routes:
r_list.append(r)
return r_list

View file

@ -15,6 +15,7 @@ from ansible.module_utils.network.vyos.facts.lag_interfaces.lag_interfaces impor
from ansible.module_utils.network.vyos.facts.lldp_global.lldp_global import Lldp_globalFacts from ansible.module_utils.network.vyos.facts.lldp_global.lldp_global import Lldp_globalFacts
from ansible.module_utils.network.vyos.facts.lldp_interfaces.lldp_interfaces import Lldp_interfacesFacts from ansible.module_utils.network.vyos.facts.lldp_interfaces.lldp_interfaces import Lldp_interfacesFacts
from ansible.module_utils.network.vyos.facts.firewall_rules.firewall_rules import Firewall_rulesFacts from ansible.module_utils.network.vyos.facts.firewall_rules.firewall_rules import Firewall_rulesFacts
from ansible.module_utils.network.vyos.facts.static_routes.static_routes import Static_routesFacts
from ansible.module_utils.network.vyos.facts.legacy.base import Default, Neighbors, Config from ansible.module_utils.network.vyos.facts.legacy.base import Default, Neighbors, Config
@ -29,6 +30,7 @@ FACT_RESOURCE_SUBSETS = dict(
lag_interfaces=Lag_interfacesFacts, lag_interfaces=Lag_interfacesFacts,
lldp_global=Lldp_globalFacts, lldp_global=Lldp_globalFacts,
lldp_interfaces=Lldp_interfacesFacts, lldp_interfaces=Lldp_interfacesFacts,
static_routes=Static_routesFacts,
firewall_rules=Firewall_rulesFacts firewall_rules=Firewall_rulesFacts
) )
@ -55,5 +57,4 @@ class Facts(FactsBase):
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: 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 return self.ansible_facts, self._warnings

View file

@ -0,0 +1,161 @@
#
# -*- 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 vyos static_routes 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
from re import findall, search, M
from copy import deepcopy
from ansible.module_utils.network.common import utils
from ansible.module_utils.network.vyos.argspec.static_routes.static_routes import Static_routesArgs
from ansible.module_utils.network. vyos.utils.utils import get_route_type
class Static_routesFacts(object):
""" The vyos static_routes fact class
"""
def __init__(self, module, subspec='config', options='options'):
self._module = module
self.argument_spec = Static_routesArgs.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_config()
def populate_facts(self, connection, ansible_facts, data=None):
""" Populate the facts for static_routes
: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)
# typically data is populated from the current device configuration
# data = connection.get('show running-config | section ^interface')
# using mock data instead
objs = []
r_v4 = []
r_v6 = []
af = []
static_routes = findall(r'set protocols static route(6)? (\S+)', data, M)
if static_routes:
for route in set(static_routes):
route_regex = r' %s .+$' % route[1]
cfg = findall(route_regex, data, M)
sr = self.render_config(cfg)
sr['dest'] = route[1].strip("'")
afi = self.get_afi(sr['dest'])
if afi == 'ipv4':
r_v4.append(sr)
else:
r_v6.append(sr)
if r_v4:
afi_v4 = {'afi': 'ipv4', 'routes': r_v4}
af.append(afi_v4)
if r_v6:
afi_v6 = {'afi': 'ipv6', 'routes': r_v6}
af.append(afi_v6)
config = {'address_families': af}
if config:
objs.append(config)
ansible_facts['ansible_network_resources'].pop('static_routes', None)
facts = {}
if objs:
facts['static_routes'] = []
params = utils.validate_config(self.argument_spec, {'config': objs})
for cfg in params['config']:
facts['static_routes'].append(utils.remove_empties(cfg))
ansible_facts['ansible_network_resources'].update(facts)
return ansible_facts
def render_config(self, 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
"""
next_hops_conf = '\n'.join(filter(lambda x: ('next-hop' in x), conf))
blackhole_conf = '\n'.join(filter(lambda x: ('blackhole' in x), conf))
routes_dict = {'blackhole_config': self.parse_blackhole(blackhole_conf),
'next_hops': self.parse_next_hop(next_hops_conf)}
return routes_dict
def parse_blackhole(self, conf):
blackhole = None
if conf:
distance = search(r'^.*blackhole distance (.\S+)', conf, M)
bh = conf.find('blackhole')
if distance is not None:
blackhole = {}
value = distance.group(1).strip("'")
blackhole['distance'] = int(value)
elif bh:
blackhole = {}
blackhole['type'] = 'blackhole'
return blackhole
def get_afi(self, address):
route_type = get_route_type(address)
if route_type == 'route':
return 'ipv4'
elif route_type == 'route6':
return 'ipv6'
def parse_next_hop(self, conf):
nh_list = None
if conf:
nh_list = []
hop_list = findall(r"^.*next-hop (.+)", conf, M)
if hop_list:
for hop in hop_list:
distance = search(r'^.*distance (.\S+)', hop, M)
interface = search(r'^.*interface (.\S+)', hop, M)
dis = hop.find('disable')
hop_info = hop.split(' ')
nh_info = {'forward_router_address': hop_info[0].strip("'")}
if interface:
nh_info['interface'] = interface.group(1).strip("'")
if distance:
value = distance.group(1).strip("'")
nh_info['admin_distance'] = int(value)
elif dis >= 1:
nh_info['enabled'] = False
for element in nh_list:
if element['forward_router_address'] == nh_info['forward_router_address']:
if 'interface' in nh_info.keys():
element['interface'] = nh_info['interface']
if 'admin_distance' in nh_info.keys():
element['admin_distance'] = nh_info['admin_distance']
if 'enabled' in nh_info.keys():
element['enabled'] = nh_info['enabled']
nh_info = None
if nh_info is not None:
nh_list.append(nh_info)
return nh_list

View file

@ -7,6 +7,7 @@
from __future__ import absolute_import, division, print_function from __future__ import absolute_import, division, print_function
__metaclass__ = type __metaclass__ = type
from ansible.module_utils.six import iteritems from ansible.module_utils.six import iteritems
from ansible.module_utils.compat import ipaddress
def search_obj_in_list(name, lst, key='name'): def search_obj_in_list(name, lst, key='name'):
@ -86,6 +87,23 @@ def get_lst_diff_for_dicts(want, have, lst):
return diff return diff
def get_lst_same_for_dicts(want, have, lst):
"""
This function generates a list containing values
that are common for list in want and list in have dict
:param want: dict object to want
:param have: dict object to have
:param lst: list the comparison on
:return: new list object with values which are common in want and have.
"""
diff = None
if want and have:
want_list = want.get(lst) or {}
have_list = have.get(lst) or {}
diff = [i for i in want_list and have_list if i in have_list and i in want_list]
return diff
def list_diff_have_only(want_list, have_list): def list_diff_have_only(want_list, have_list):
""" """
This function generated the list containing values This function generated the list containing values
@ -162,3 +180,30 @@ def is_dict_element_present(dict, key):
if item == key: if item == key:
return True return True
return False return False
def get_ip_address_version(address):
"""
This function returns the version of IP address
:param address: IP address
:return:
"""
try:
address = unicode(address)
except NameError:
address = str(address)
version = ipaddress.ip_address(address.split("/")[0]).version
return version
def get_route_type(address):
"""
This function returns the route type based on IP address
:param address:
:return:
"""
version = get_ip_address_version(address)
if version == 6:
return 'route6'
elif version == 4:
return 'route'

View file

@ -20,7 +20,7 @@
# #
ANSIBLE_METADATA = {'metadata_version': '1.1', ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'], 'status': ['deprecated'],
'supported_by': 'network'} 'supported_by': 'network'}
@ -33,6 +33,10 @@ short_description: Manage static IP routes on Vyatta VyOS network devices
description: description:
- This module provides declarative management of static - This module provides declarative management of static
IP routes on Vyatta VyOS network devices. IP routes on Vyatta VyOS network devices.
deprecated:
removed_in: '2.13'
alternative: vyos_static_routes
why: Updated modules released with more functionality.
notes: notes:
- Tested against VyOS 1.1.8 (helium). - Tested against VyOS 1.1.8 (helium).
- This module works with connection C(network_cli). See L(the VyOS OS Platform Options,../network/user_guide/platform_vyos.html). - This module works with connection C(network_cli). See L(the VyOS OS Platform Options,../network/user_guide/platform_vyos.html).
@ -42,22 +46,28 @@ options:
- Network prefix of the static route. - Network prefix of the static route.
C(mask) param should be ignored if C(prefix) is provided C(mask) param should be ignored if C(prefix) is provided
with C(mask) value C(prefix/mask). with C(mask) value C(prefix/mask).
type: str
mask: mask:
description: description:
- Network prefix mask of the static route. - Network prefix mask of the static route.
type: str
next_hop: next_hop:
description: description:
- Next hop IP of the static route. - Next hop IP of the static route.
type: str
admin_distance: admin_distance:
description: description:
- Admin distance of the static route. - Admin distance of the static route.
type: int
aggregate: aggregate:
description: List of static route definitions description: List of static route definitions
type: list
state: state:
description: description:
- State of the static route configuration. - State of the static route configuration.
default: present default: present
choices: ['present', 'absent'] choices: ['present', 'absent']
type: str
extends_documentation_fragment: vyos extends_documentation_fragment: vyos
""" """

View file

@ -52,7 +52,7 @@ options:
can also be used with an initial C(M(!)) to specify that a can also be used with an initial C(M(!)) to specify that a
specific subset should not be collected. specific subset should not be collected.
Valid subsets are 'all', 'interfaces', 'l3_interfaces', 'lag_interfaces', Valid subsets are 'all', 'interfaces', 'l3_interfaces', 'lag_interfaces',
'lldp_global', 'lldp_interfaces', 'firewall_rules'. 'lldp_global', 'lldp_interfaces', 'static_routes', 'firewall_rules'.
required: false required: false
version_added: "2.9" version_added: "2.9"
""" """

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,3 @@
---
testcase: "[^_].*"
test_items: []

View file

@ -0,0 +1,19 @@
---
- name: Collect all cli test cases
find:
paths: "{{ role_path }}/tests/cli"
patterns: "{{ testcase }}.yaml"
use_regex: true
register: test_cases
delegate_to: localhost
- name: Set test_items
set_fact: test_items="{{ test_cases.files | map(attribute='path') | list }}"
- name: Run test case (connection=network_cli)
include: "{{ test_case_to_run }}"
vars:
ansible_connection: network_cli
with_items: "{{ test_items }}"
loop_control:
loop_var: test_case_to_run

View file

@ -0,0 +1,2 @@
---
- { include: cli.yaml, tags: ['cli'] }

View file

@ -0,0 +1,6 @@
set protocols static route 192.0.2.32/28 next-hop '192.0.2.9'
set protocols static route 192.0.2.32/28 next-hop '192.0.2.10'
set protocols static route 192.0.2.32/28 blackhole
set protocols static route6 2001:db8:1000::/36 next-hop '2001:db8:2000:2::1'
set protocols static route6 2001:db8:1000::/36 next-hop '2001:db8:2000:2::2'
set protocols static route6 2001:db8:1000::/36 blackhole distance '2'

View file

@ -0,0 +1,14 @@
---
- name: Setup
cli_config:
config: "{{ lines }}"
vars:
lines: |
set protocols static route 192.0.2.32/28 next-hop '192.0.2.10'
set protocols static route 192.0.2.32/28 next-hop '192.0.2.9'
set protocols static route 192.0.2.32/28 blackhole
set protocols static route 192.0.2.32/28
set protocols static route6 2001:db8:1000::/36 next-hop '2001:db8:2000:2::1'
set protocols static route6 2001:db8:1000::/36 next-hop '2001:db8:2000:2::2'
set protocols static route6 2001:db8:1000::/36 blackhole distance '2'
set protocols static route6 2001:db8:1000::/36

View file

@ -0,0 +1,8 @@
---
- name: Remove Config
cli_config:
config: "{{ lines }}"
vars:
lines: |
delete protocols static route
delete protocols static route6

View file

@ -0,0 +1,51 @@
---
- debug:
msg: "Start vyos_static_routes deleted integration tests ansible_connection={{ ansible_connection }}"
- include_tasks: _populate.yaml
- block:
- name: Delete static route based on destiation.
vyos_static_routes: &deleted_dest
config:
- address_families:
- afi: 'ipv4'
routes:
- dest: '192.0.2.32/28'
- afi: 'ipv6'
routes:
- dest: '2001:db8:1000::/36'
state: deleted
register: result
- name: Assert that the before dicts were correctly generated
assert:
that:
- "{{ populate | symmetric_difference(result['before']) |length == 0 }}"
- name: Assert that the correct set of commands were generated
assert:
that:
- "{{ deleted_dest['commands'] | symmetric_difference(result['commands']) |length == 0 }}"
- name: Assert that the after dicts were correctly generated
assert:
that:
- "{{ deleted_dest['after'] | symmetric_difference(result['after']) |length == 0 }}"
- name: Delete attributes of given interfaces (IDEMPOTENT)
vyos_static_routes: *deleted_dest
register: result
- name: Assert that the previous task was idempotent
assert:
that:
- "result.changed == false"
- "result.commands|length == 0"
- name: Assert that the before dicts were correctly generated
assert:
that:
- "{{ deleted_dest['after'] | symmetric_difference(result['before']) |length == 0 }}"
always:
- include_tasks: _remove_config.yaml

View file

@ -0,0 +1,47 @@
---
- debug:
msg: "Start vyos_static_routes deleted integration tests ansible_connection={{ ansible_connection }}"
- include_tasks: _populate.yaml
- block:
- name: Delete static route based on afi.
vyos_static_routes: &deleted_afi
config:
- address_families:
- afi: 'ipv4'
- afi: 'ipv6'
state: deleted
register: result
- name: Assert that the before dicts were correctly generated
assert:
that:
- "{{ populate | symmetric_difference(result['before']) |length == 0 }}"
- name: Assert that the correct set of commands were generated
assert:
that:
- "{{ deleted_afi_all['commands'] | symmetric_difference(result['commands']) |length == 0 }}"
- name: Assert that the after dicts were correctly generated
assert:
that:
- "{{ deleted_afi_all['after'] | symmetric_difference(result['after']) |length == 0 }}"
- name: Delete attributes of given interfaces (IDEMPOTENT)
vyos_static_routes: *deleted_afi
register: result
- name: Assert that the previous task was idempotent
assert:
that:
- "result.changed == false"
- "result.commands|length == 0"
- name: Assert that the before dicts were correctly generated
assert:
that:
- "{{ deleted_afi_all['after'] | symmetric_difference(result['before']) |length == 0 }}"
always:
- include_tasks: _remove_config.yaml

View file

@ -0,0 +1,44 @@
---
- debug:
msg: "Start vyos_static_routes deleted integration tests ansible_connection={{ ansible_connection }}"
- include_tasks: _populate.yaml
- block:
- name: Delete all the static routes.
vyos_static_routes: &deleted_all
config:
state: deleted
register: result
- name: Assert that the before dicts were correctly generated
assert:
that:
- "{{ populate | symmetric_difference(result['before']) |length == 0 }}"
- name: Assert that the correct set of commands were generated
assert:
that:
- "{{ deleted_afi_all['commands'] | symmetric_difference(result['commands']) |length == 0 }}"
- name: Assert that the after dicts were correctly generated
assert:
that:
- "{{ deleted_afi_all['after'] | symmetric_difference(result['after']) |length == 0 }}"
- name: Delete attributes of given interfaces (IDEMPOTENT)
vyos_static_routes: *deleted_all
register: result
- name: Assert that the previous task was idempotent
assert:
that:
- "result.changed == false"
- "result.commands|length == 0"
- name: Assert that the before dicts were correctly generated
assert:
that:
- "{{ deleted_afi_all['after'] | symmetric_difference(result['before']) |length == 0 }}"
always:
- include_tasks: _remove_config.yaml

View file

@ -0,0 +1,55 @@
---
- debug:
msg: "Start vyos_static_routes deleted integration tests ansible_connection={{ ansible_connection }}"
- include_tasks: _populate.yaml
- block:
- name: Delete static route based on next_hop.
vyos_static_routes: &deleted_nh
config:
- address_families:
- afi: 'ipv4'
routes:
- dest: '192.0.2.32/28'
next_hops:
- forward_router_address: '192.0.2.9'
- afi: 'ipv6'
routes:
- dest: '2001:db8:1000::/36'
next_hops:
- forward_router_address: '2001:db8:2000:2::1'
state: deleted
register: result
- name: Assert that the before dicts were correctly generated
assert:
that:
- "{{ populate | symmetric_difference(result['before']) |length == 0 }}"
- name: Assert that the correct set of commands were generated
assert:
that:
- "{{ deleted_nh['commands'] | symmetric_difference(result['commands']) |length == 0 }}"
- name: Assert that the after dicts were correctly generated
assert:
that:
- "{{ deleted_nh['after'] | symmetric_difference(result['after']) |length == 0 }}"
- name: Delete attributes of given interfaces (IDEMPOTENT)
vyos_static_routes: *deleted_nh
register: result
- name: Assert that the previous task was idempotent
assert:
that:
- "result.changed == false"
- "result.commands|length == 0"
- name: Assert that the before dicts were correctly generated
assert:
that:
- "{{ deleted_nh['after'] | symmetric_difference(result['before']) |length == 0 }}"
always:
- include_tasks: _remove_config.yaml

View file

@ -0,0 +1,58 @@
---
- debug:
msg: "START vyos_static_routes empty_config integration tests on connection={{ ansible_connection }}"
- name: Merged with empty config should give appropriate error message
vyos_static_routes:
config:
state: merged
register: result
ignore_errors: true
- assert:
that:
- result.msg == 'value of config parameter must not be empty for state merged'
- name: Replaced with empty config should give appropriate error message
vyos_static_routes:
config:
state: replaced
register: result
ignore_errors: true
- assert:
that:
- result.msg == 'value of config parameter must not be empty for state replaced'
- name: Overridden with empty config should give appropriate error message
vyos_static_routes:
config:
state: overridden
register: result
ignore_errors: true
- assert:
that:
- result.msg == 'value of config parameter must not be empty for state overridden'
- name: Parsed with empty running_config should give appropriate error message
vyos_static_routes:
running_config:
state: parsed
register: result
ignore_errors: true
- assert:
that:
- result.msg == 'value of running_config parameter must not be empty for state parsed'
- name: Rendered with empty config should give appropriate error message
vyos_static_routes:
config:
state: rendered
register: result
ignore_errors: true
- assert:
that:
- result.msg == 'value of config parameter must not be empty for state rendered'

View file

@ -0,0 +1,31 @@
---
- debug:
msg: "START vyos_static_routes gathered integration tests on connection={{ ansible_connection }}"
- include_tasks: _remove_config.yaml
- include_tasks: _populate.yaml
- block:
- name: Merge the provided configuration with the exisiting running configuration
vyos_static_routes: &gathered
config:
state: gathered
register: result
- name: Assert that gathered dicts was correctly generated
assert:
that:
- "{{ populate | symmetric_difference(result['gathered']) |length == 0 }}"
- name: Gather the existing running configuration (IDEMPOTENT)
vyos_static_routes: *gathered
register: result
- name: Assert that the previous task was idempotent
assert:
that:
- "result['changed'] == false"
always:
- include_tasks: _remove_config.yaml

View file

@ -0,0 +1,61 @@
---
- debug:
msg: "START vyos_static_routes merged integration tests on connection={{ ansible_connection }}"
- include_tasks: _remove_config.yaml
- block:
- name: Merge the provided configuration with the exisiting running configuration
vyos_static_routes: &merged
config:
- address_families:
- afi: 'ipv4'
routes:
- dest: 192.0.2.32/28
blackhole_config:
type: 'blackhole'
next_hops:
- forward_router_address: 192.0.2.10
- forward_router_address: 192.0.2.9
- address_families:
- afi: 'ipv6'
routes:
- dest: 2001:db8:1000::/36
blackhole_config:
distance: 2
next_hops:
- forward_router_address: 2001:db8:2000:2::1
- forward_router_address: 2001:db8:2000:2::2
state: merged
register: result
- name: Assert that before dicts were correctly generated
assert:
that: "{{ merged['before'] | symmetric_difference(result['before']) |length == 0 }}"
- name: Assert that correct set of commands were generated
assert:
that:
- "{{ merged['commands'] | symmetric_difference(result['commands']) |length == 0 }}"
- name: Assert that after dicts was correctly generated
assert:
that:
- "{{ merged['after'] | symmetric_difference(result['after']) |length == 0 }}"
- name: Merge the provided configuration with the existing running configuration (IDEMPOTENT)
vyos_static_routes: *merged
register: result
- name: Assert that the previous task was idempotent
assert:
that:
- "result['changed'] == false"
- name: Assert that before dicts were correctly generated
assert:
that:
- "{{ merged['after'] | symmetric_difference(result['before']) |length == 0 }}"
always:
- include_tasks: _remove_config.yaml

View file

@ -0,0 +1,52 @@
---
- debug:
msg: "START vyos_static_routes overridden integration tests on connection={{ ansible_connection }}"
- include_tasks: _remove_config.yaml
- include_tasks: _populate.yaml
- block:
- name: Overrides all device configuration with provided configuration
vyos_static_routes: &overridden
config:
- address_families:
- afi: 'ipv4'
routes:
- dest: 198.0.2.48/28
next_hops:
- forward_router_address: 192.0.2.18
state: overridden
register: result
- name: Assert that before dicts were correctly generated
assert:
that:
- "{{ populate | symmetric_difference(result['before']) |length == 0 }}"
- name: Assert that correct commands were generated
assert:
that:
- "{{ overridden['commands'] | symmetric_difference(result['commands']) |length == 0 }}"
- name: Assert that after dicts were correctly generated
assert:
that:
- "{{ overridden['after'] | symmetric_difference(result['after']) |length == 0 }}"
- name: Overrides all device configuration with provided configurations (IDEMPOTENT)
vyos_static_routes: *overridden
register: result
- name: Assert that the previous task was idempotent
assert:
that:
- "result['changed'] == false"
- name: Assert that before dicts were correctly generated
assert:
that:
- "{{ overridden['after'] | symmetric_difference(result['before']) |length == 0 }}"
always:
- include_tasks: _remove_config.yaml

View file

@ -0,0 +1,39 @@
---
- debug:
msg: "START vyos_static_routes parsed integration tests on connection={{ ansible_connection }}"
- include_tasks: _remove_config.yaml
- include_tasks: _populate.yaml
- block:
- name: Gather static_routes facts
vyos_facts:
gather_subset:
- default
gather_network_resources:
- static_routes
register: static_routes_facts
- name: Provide the running configuration for parsing (config to be parsed)
vyos_static_routes: &parsed
running_config:
"{{ lookup('file', '_parsed_config.cfg') }}"
state: parsed
register: result
- name: Assert that correct parsing done
assert:
that: "{{ ansible_facts['network_resources']['static_routes'] | symmetric_difference(result['parsed']) |length == 0 }}"
- name: Gather the existing running configuration (IDEMPOTENT)
vyos_static_routes: *parsed
register: result
- name: Assert that the previous task was idempotent
assert:
that:
- "result['changed'] == false"
always:
- include_tasks: _remove_config.yaml

View file

@ -0,0 +1,49 @@
---
- debug:
msg: "START vyos_static_routes rendered integration tests on connection={{ ansible_connection }}"
- include_tasks: _remove_config.yaml
- include_tasks: _populate.yaml
- block:
- name: Structure provided configuration into device specific commands
vyos_static_routes: &rendered
config:
- address_families:
- afi: 'ipv4'
routes:
- dest: 192.0.2.32/28
blackhole_config:
type: 'blackhole'
next_hops:
- forward_router_address: 192.0.2.10
- forward_router_address: 192.0.2.9
- address_families:
- afi: 'ipv6'
routes:
- dest: 2001:db8:1000::/36
blackhole_config:
distance: 2
next_hops:
- forward_router_address: 2001:db8:2000:2::1
- forward_router_address: 2001:db8:2000:2::2
state: rendered
register: result
- name: Assert that correct set of commands were generated
assert:
that:
- "{{ rendered['commands'] | symmetric_difference(result['rendered']) |length == 0 }}"
- name: Structure provided configuration into device specific commands (IDEMPOTENT)
vyos_static_routes: *rendered
register: result
- name: Assert that the previous task was idempotent
assert:
that:
- "result['changed'] == false"
always:
- include_tasks: _remove_config.yaml

View file

@ -0,0 +1,56 @@
---
- debug:
msg: "START vyos_static_routes replaced integration tests on connection={{ ansible_connection }}"
- include_tasks: _remove_config.yaml
- include_tasks: _populate.yaml
- block:
- name: Replace device configurations of listed static routes with provided configurations
vyos_static_routes: &replaced
config:
- address_families:
- afi: 'ipv4'
routes:
- dest: 192.0.2.32/28
blackhole_config:
distance: 2
next_hops:
- forward_router_address: 192.0.2.7
- forward_router_address: 192.0.2.8
- forward_router_address: 192.0.2.9
state: replaced
register: result
- name: Assert that correct set of commands were generated
assert:
that:
- "{{ replaced['commands'] | symmetric_difference(result['commands']) |length == 0 }}"
- name: Assert that before dicts are correctly generated
assert:
that:
- "{{ populate | symmetric_difference(result['before']) |length == 0 }}"
- name: Assert that after dict is correctly generated
assert:
that:
- "{{ replaced['after'] | symmetric_difference(result['after']) |length == 0 }}"
- name: Replace device configurations of listed static routes with provided configurarions (IDEMPOTENT)
vyos_static_routes: *replaced
register: result
- name: Assert that task was idempotent
assert:
that:
- "result['changed'] == false"
- name: Assert that before dict is correctly generated
assert:
that:
- "{{ replaced['after'] | symmetric_difference(result['before']) |length == 0 }}"
always:
- include_tasks: _remove_config.yaml

View file

@ -0,0 +1,71 @@
---
- debug:
msg: "START vyos_static_routes round trip integration tests on connection={{ ansible_connection }}"
- include_tasks: _remove_config.yaml
- block:
- name: Apply the provided configuration (base config)
vyos_static_routes:
config:
- address_families:
- afi: 'ipv4'
routes:
- dest: 192.0.2.32/28
blackhole_config:
type: 'blackhole'
next_hops:
- forward_router_address: 192.0.2.10
- forward_router_address: 192.0.2.9
- address_families:
- afi: 'ipv6'
routes:
- dest: 2001:db8:1000::/36
blackhole_config:
distance: 2
next_hops:
- forward_router_address: 2001:db8:2000:2::1
- forward_router_address: 2001:db8:2000:2::2
state: merged
register: base_config
- name: Gather static_routes facts
vyos_facts:
gather_subset:
- default
gather_network_resources:
- static_routes
- name: Apply the provided configuration (config to be reverted)
vyos_static_routes:
config:
- address_families:
- afi: 'ipv4'
routes:
- dest: 192.0.2.32/28
blackhole_config:
distance: 2
next_hops:
- forward_router_address: 192.0.2.7
- forward_router_address: 192.0.2.8
- forward_router_address: 192.0.2.9
state: merged
register: result
- name: Assert that changes were applied
assert:
that: "{{ round_trip['after'] | symmetric_difference(result['after']) |length == 0 }}"
- name: Revert back to base config using facts round trip
vyos_static_routes:
config: "{{ ansible_facts['network_resources']['static_routes'] }}"
state: overridden
register: revert
- name: Assert that config was reverted
assert:
that: "{{ base_config['after'] | symmetric_difference(revert['after']) |length == 0 }}"
always:
- include_tasks: _remove_config.yaml

View file

@ -0,0 +1,163 @@
---
merged:
before: []
commands:
- "set protocols static route 192.0.2.32/28 next-hop '192.0.2.10'"
- "set protocols static route 192.0.2.32/28 next-hop '192.0.2.9'"
- "set protocols static route 192.0.2.32/28 blackhole"
- "set protocols static route 192.0.2.32/28"
- "set protocols static route6 2001:db8:1000::/36 next-hop '2001:db8:2000:2::1'"
- "set protocols static route6 2001:db8:1000::/36 next-hop '2001:db8:2000:2::2'"
- "set protocols static route6 2001:db8:1000::/36 blackhole distance '2'"
- "set protocols static route6 2001:db8:1000::/36"
after:
- address_families:
- afi: 'ipv4'
routes:
- dest: '192.0.2.32/28'
blackhole_config:
type: 'blackhole'
next_hops:
- forward_router_address: '192.0.2.9'
- forward_router_address: '192.0.2.10'
- afi: 'ipv6'
routes:
- dest: '2001:db8:1000::/36'
blackhole_config:
distance: 2
next_hops:
- forward_router_address: '2001:db8:2000:2::1'
- forward_router_address: '2001:db8:2000:2::2'
populate:
- address_families:
- afi: 'ipv4'
routes:
- dest: '192.0.2.32/28'
blackhole_config:
type: 'blackhole'
next_hops:
- forward_router_address: '192.0.2.9'
- forward_router_address: '192.0.2.10'
- afi: 'ipv6'
routes:
- dest: '2001:db8:1000::/36'
blackhole_config:
distance: 2
next_hops:
- forward_router_address: '2001:db8:2000:2::1'
- forward_router_address: '2001:db8:2000:2::2'
replaced:
commands:
- "delete protocols static route 192.0.2.32/28 next-hop '192.0.2.10'"
- "set protocols static route 192.0.2.32/28 next-hop '192.0.2.7'"
- "set protocols static route 192.0.2.32/28 next-hop '192.0.2.8'"
- "set protocols static route 192.0.2.32/28 blackhole distance '2'"
after:
- address_families:
- afi: 'ipv4'
routes:
- dest: 192.0.2.32/28
blackhole_config:
distance: 2
next_hops:
- forward_router_address: 192.0.2.7
- forward_router_address: 192.0.2.8
- forward_router_address: 192.0.2.9
- afi: 'ipv6'
routes:
- dest: '2001:db8:1000::/36'
blackhole_config:
distance: 2
next_hops:
- forward_router_address: '2001:db8:2000:2::1'
- forward_router_address: '2001:db8:2000:2::2'
overridden:
commands:
- "delete protocols static route 192.0.2.32/28"
- "delete protocols static route6 2001:db8:1000::/36"
- "set protocols static route 198.0.2.48/28 next-hop '192.0.2.18'"
- "set protocols static route 198.0.2.48/28"
after:
- address_families:
- afi: 'ipv4'
routes:
- dest: 198.0.2.48/28
next_hops:
- forward_router_address: '192.0.2.18'
rendered:
commands:
- "set protocols static route 192.0.2.32/28 next-hop '192.0.2.10'"
- "set protocols static route 192.0.2.32/28 next-hop '192.0.2.9'"
- "set protocols static route 192.0.2.32/28 blackhole"
- "set protocols static route 192.0.2.32/28"
- "set protocols static route6 2001:db8:1000::/36 next-hop '2001:db8:2000:2::1'"
- "set protocols static route6 2001:db8:1000::/36 next-hop '2001:db8:2000:2::2'"
- "set protocols static route6 2001:db8:1000::/36 blackhole distance '2'"
- "set protocols static route6 2001:db8:1000::/36"
deleted_dest:
commands:
- "delete protocols static route 192.0.2.32/28"
- "delete protocols static route6 2001:db8:1000::/36"
after: []
deleted_nh:
commands:
- "delete protocols static route 192.0.2.32/28 next-hop '192.0.2.9'"
- "delete protocols static route6 2001:db8:1000::/36 next-hop '2001:db8:2000:2::1'"
after:
- address_families:
- afi: 'ipv4'
routes:
- dest: '192.0.2.32/28'
blackhole_config:
type: 'blackhole'
next_hops:
- forward_router_address: '192.0.2.10'
- afi: 'ipv6'
routes:
- dest: '2001:db8:1000::/36'
blackhole_config:
distance: 2
next_hops:
- forward_router_address: '2001:db8:2000:2::2'
deleted_afi_all:
commands:
- "delete protocols static route"
- "delete protocols static route6"
after: []
round_trip:
after:
- address_families:
- afi: 'ipv4'
routes:
- dest: 192.0.2.32/28
blackhole_config:
distance: 2
next_hops:
- forward_router_address: '192.0.2.7'
- forward_router_address: '192.0.2.8'
- forward_router_address: '192.0.2.9'
- forward_router_address: '192.0.2.10'
- afi: 'ipv6'
routes:
- dest: '2001:db8:1000::/36'
blackhole_config:
distance: 2
next_hops:
- forward_router_address: '2001:db8:2000:2::1'
- forward_router_address: '2001:db8:2000:2::2'

View file

@ -6436,16 +6436,16 @@ lib/ansible/modules/network/vyos/vyos_logging.py validate-modules:undocumented-p
lib/ansible/modules/network/vyos/vyos_ping.py validate-modules:doc-default-does-not-match-spec lib/ansible/modules/network/vyos/vyos_ping.py validate-modules:doc-default-does-not-match-spec
lib/ansible/modules/network/vyos/vyos_ping.py validate-modules:doc-required-mismatch lib/ansible/modules/network/vyos/vyos_ping.py validate-modules:doc-required-mismatch
lib/ansible/modules/network/vyos/vyos_ping.py validate-modules:parameter-type-not-in-doc lib/ansible/modules/network/vyos/vyos_ping.py validate-modules:parameter-type-not-in-doc
lib/ansible/modules/network/vyos/vyos_static_route.py future-import-boilerplate lib/ansible/modules/network/vyos/_vyos_static_route.py future-import-boilerplate
lib/ansible/modules/network/vyos/vyos_static_route.py metaclass-boilerplate lib/ansible/modules/network/vyos/_vyos_static_route.py metaclass-boilerplate
lib/ansible/modules/network/vyos/vyos_static_route.py validate-modules:doc-choices-do-not-match-spec lib/ansible/modules/network/vyos/_vyos_static_route.py validate-modules:undocumented-parameter
lib/ansible/modules/network/vyos/vyos_static_route.py validate-modules:doc-default-does-not-match-spec lib/ansible/modules/network/vyos/_vyos_static_route.py validate-modules:doc-default-does-not-match-spec
lib/ansible/modules/network/vyos/vyos_static_route.py validate-modules:doc-elements-mismatch lib/ansible/modules/network/vyos/_vyos_static_route.py validate-modules:doc-choices-do-not-match-spec
lib/ansible/modules/network/vyos/vyos_static_route.py validate-modules:doc-missing-type lib/ansible/modules/network/vyos/_vyos_static_route.py validate-modules:doc-required-mismatch
lib/ansible/modules/network/vyos/vyos_static_route.py validate-modules:doc-required-mismatch lib/ansible/modules/network/vyos/_vyos_static_route.py validate-modules:parameter-type-not-in-doc
lib/ansible/modules/network/vyos/vyos_static_route.py validate-modules:missing-suboption-docs lib/ansible/modules/network/vyos/_vyos_static_route.py validate-modules:doc-missing-type
lib/ansible/modules/network/vyos/vyos_static_route.py validate-modules:parameter-type-not-in-doc lib/ansible/modules/network/vyos/_vyos_static_route.py validate-modules:doc-elements-mismatch
lib/ansible/modules/network/vyos/vyos_static_route.py validate-modules:undocumented-parameter lib/ansible/modules/network/vyos/_vyos_static_route.py validate-modules:missing-suboption-docs
lib/ansible/modules/network/vyos/vyos_system.py future-import-boilerplate lib/ansible/modules/network/vyos/vyos_system.py future-import-boilerplate
lib/ansible/modules/network/vyos/vyos_system.py metaclass-boilerplate lib/ansible/modules/network/vyos/vyos_system.py metaclass-boilerplate
lib/ansible/modules/network/vyos/vyos_system.py validate-modules:doc-default-does-not-match-spec lib/ansible/modules/network/vyos/vyos_system.py validate-modules:doc-default-does-not-match-spec

View file

@ -0,0 +1,2 @@
'set protocols static route 192.0.2.32/28 next-hop 192.0.2.9'
'set protocols static route 192.0.2.32/28 next-hop 192.0.2.10'

View file

@ -20,22 +20,22 @@ from __future__ import (absolute_import, division, print_function)
__metaclass__ = type __metaclass__ = type
from units.compat.mock import patch from units.compat.mock import patch
from ansible.modules.network.vyos import vyos_static_route from ansible.modules.network.vyos import _vyos_static_route
from units.modules.utils import set_module_args from units.modules.utils import set_module_args
from .vyos_module import TestVyosModule from .vyos_module import TestVyosModule
class TestVyosStaticRouteModule(TestVyosModule): class TestVyosStaticRouteModule(TestVyosModule):
module = vyos_static_route module = _vyos_static_route
def setUp(self): def setUp(self):
super(TestVyosStaticRouteModule, self).setUp() super(TestVyosStaticRouteModule, self).setUp()
self.mock_get_config = patch('ansible.modules.network.vyos.vyos_static_route.get_config') self.mock_get_config = patch('ansible.modules.network.vyos._vyos_static_route.get_config')
self.get_config = self.mock_get_config.start() self.get_config = self.mock_get_config.start()
self.mock_load_config = patch('ansible.modules.network.vyos.vyos_static_route.load_config') self.mock_load_config = patch('ansible.modules.network.vyos._vyos_static_route.load_config')
self.load_config = self.mock_load_config.start() self.load_config = self.mock_load_config.start()
def tearDown(self): def tearDown(self):

View file

@ -0,0 +1,202 @@
# (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 <http://www.gnu.org/licenses/>.
# Make coding more python3-ish
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
from units.compat.mock import patch, MagicMock
from ansible.modules.network.vyos import vyos_static_routes
from units.modules.utils import set_module_args
from .vyos_module import TestVyosModule, load_fixture
class TestVyosStaticRoutesModule(TestVyosModule):
module = vyos_static_routes
def setUp(self):
super(TestVyosStaticRoutesModule, 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_execute_show_command = patch('ansible.module_utils.network.vyos.facts.static_routes.static_routes.Static_routesFacts.get_device_data')
self.execute_show_command = self.mock_execute_show_command.start()
def tearDown(self):
super(TestVyosStaticRoutesModule, self).tearDown()
self.mock_get_resource_connection_config.stop()
self.mock_get_resource_connection_facts.stop()
self.mock_get_config.stop()
self.mock_load_config.stop()
self.mock_execute_show_command.stop()
def load_fixtures(self, commands=None):
def load_from_file(*args, **kwargs):
return load_fixture('vyos_static_routes_config.cfg')
self.execute_show_command.side_effect = load_from_file
def test_vyos_static_routes_merged(self):
set_module_args(
dict(config=[
dict(address_families=[
dict(
afi='ipv4',
routes=[
dict(
dest='192.0.2.48/28',
next_hops=[
dict(
forward_router_address='192.0.2.9'),
dict(forward_router_address='192.0.2.10')
])
])
])
],
state="merged"))
commands = ['set protocols static route 192.0.2.48/28',
"set protocols static route 192.0.2.48/28 next-hop '192.0.2.9'",
"set protocols static route 192.0.2.48/28 next-hop '192.0.2.10'"]
self.execute_module(changed=True, commands=commands)
def test_vyos_static_routes_merged_idempotent(self):
set_module_args(
dict(config=[
dict(address_families=[
dict(
afi='ipv4',
routes=[
dict(
dest='192.0.2.32/28',
next_hops=[
dict(
forward_router_address='192.0.2.9'),
dict(forward_router_address='192.0.2.10')
])
])
])
],
state="merged"))
self.execute_module(changed=False, commands=[])
def test_vyos_static_routes_replaced(self):
set_module_args(
dict(config=[
dict(address_families=[
dict(
afi='ipv4',
routes=[
dict(
dest='192.0.2.48/28',
next_hops=[
dict(
forward_router_address='192.0.2.9'),
dict(forward_router_address='192.0.2.10')
])
])
])
],
state="replaced"))
commands = ["set protocols static route 192.0.2.48/28",
"set protocols static route 192.0.2.48/28 next-hop '192.0.2.9'",
"set protocols static route 192.0.2.48/28 next-hop '192.0.2.10'"]
self.execute_module(changed=True, commands=commands)
def test_vyos_static_routes_replaced_idempotent(self):
set_module_args(
dict(config=[
dict(address_families=[
dict(
afi='ipv4',
routes=[
dict(
dest='192.0.2.32/28',
next_hops=[
dict(
forward_router_address='192.0.2.9'),
dict(forward_router_address='192.0.2.10')
])
])
])
],
state="replaced"))
self.execute_module(changed=False, commands=[])
def test_vyos_static_routes_overridden(self):
set_module_args(
dict(config=[
dict(address_families=[
dict(
afi='ipv4',
routes=[
dict(
dest='192.0.2.48/28',
next_hops=[
dict(
forward_router_address='192.0.2.9'),
dict(forward_router_address='192.0.2.10')
])
])
])
],
state="overridden"))
commands = ['delete protocols static route 192.0.2.32/28',
'set protocols static route 192.0.2.48/28',
"set protocols static route 192.0.2.48/28 next-hop '192.0.2.9'",
"set protocols static route 192.0.2.48/28 next-hop '192.0.2.10'"]
self.execute_module(changed=True, commands=commands)
def test_vyos_static_routes_overridden_idempotent(self):
set_module_args(
dict(config=[
dict(address_families=[
dict(
afi='ipv4',
routes=[
dict(
dest='192.0.2.32/28',
next_hops=[
dict(
forward_router_address='192.0.2.9'),
dict(forward_router_address='192.0.2.10')
])
])
])
],
state="overridden"))
self.execute_module(changed=False, commands=[])
def test_vyos_static_routes_deleted(self):
set_module_args(
dict(config=[
dict(address_families=[
dict(afi='ipv4', routes=[dict(dest='192.0.2.32/28')])
])
],
state="deleted"))
commands = ['delete protocols static route 192.0.2.32/28']
self.execute_module(changed=True, commands=commands)