Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

frr: New module for BGP configuration management #51281

Merged
merged 18 commits into from
Mar 4, 2019
Merged
Empty file.
Empty file.
Empty file.
77 changes: 77 additions & 0 deletions lib/ansible/module_utils/network/frr/providers/cli/config/base.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
#
# (c) 2019, Ansible by Red Hat, inc
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
#
from ansible.module_utils.six import iteritems
from ansible.module_utils.network.common.utils import to_list
from ansible.module_utils.network.common.config import NetworkConfig


class ConfigBase(object):

argument_spec = {}

mutually_exclusive = []

identifier = ()

def __init__(self, **kwargs):
self.values = {}
self._rendered_configuration = {}
self.active_configuration = None

for item in self.identifier:
self.values[item] = kwargs.pop(item)

for key, value in iteritems(kwargs):
if key in self.argument_spec:
setattr(self, key, value)

for key, value in iteritems(self.argument_spec):
if value.get('default'):
if not getattr(self, key, None):
setattr(self, key, value.get('default'))

def __getattr__(self, key):
if key in self.argument_spec:
return self.values.get(key)

def __setattr__(self, key, value):
if key in self.argument_spec:
if key in self.identifier:
raise TypeError('cannot set value')
elif value is not None:
self.values[key] = value
else:
super(ConfigBase, self).__setattr__(key, value)

def context_config(self, cmd):
if 'context' not in self._rendered_configuration:
self._rendered_configuration['context'] = list()
self._rendered_configuration['context'].extend(to_list(cmd))

def global_config(self, cmd):
if 'global' not in self._rendered_configuration:
self._rendered_configuration['global'] = list()
self._rendered_configuration['global'].extend(to_list(cmd))

def get_rendered_configuration(self):
config = list()
for section in ('context', 'global'):
config.extend(self._rendered_configuration.get(section, []))
return config

def set_active_configuration(self, config):
self.active_configuration = config

def render(self, config=None):
raise NotImplementedError

def get_section(self, config, section):
if config is not None:
netcfg = NetworkConfig(indent=1, contents=config)
try:
config = netcfg.get_block_config(to_list(section))
except ValueError:
config = None
return config
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
#
# (c) 2019, Ansible by Red Hat, inc
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
#
import re

from ansible.module_utils.six import iteritems
from ansible.module_utils.network.common.utils import to_list
from ansible.module_utils.network.frr.providers.providers import CliProvider
from ansible.module_utils.network.frr.providers.cli.config.bgp.neighbors import AFNeighbors


class AddressFamily(CliProvider):

def render(self, config=None):
commands = list()
safe_list = list()

router_context = 'router bgp %s' % self.get_value('config.bgp_as')
context_config = None

for item in self.get_value('config.address_family'):
context = 'address-family %s %s' % (item['afi'], item['safi'])
context_commands = list()

if config:
context_path = [router_context, context]
context_config = self.get_config_context(config, context_path, indent=1)

for key, value in iteritems(item):
if value is not None:
meth = getattr(self, '_render_%s' % key, None)
if meth:
resp = meth(item, context_config)
if resp:
context_commands.extend(to_list(resp))

if context_commands:
commands.append(context)
commands.extend(context_commands)
commands.append('exit-address-family')

safe_list.append(context)

if self.params['operation'] == 'replace':
if config:
resp = self._negate_config(config, safe_list)
commands.extend(resp)

return commands

def _negate_config(self, config, safe_list=None):
commands = list()
matches = re.findall(r'(address-family .+)$', config, re.M)
for item in set(matches).difference(safe_list):
commands.append('no %s' % item)
return commands

def _render_auto_summary(self, item, config=None):
cmd = 'auto-summary'
if item['auto_summary'] is False:
cmd = 'no %s' % cmd
if not config or cmd not in config:
return cmd

def _render_synchronization(self, item, config=None):
cmd = 'synchronization'
if item['synchronization'] is False:
cmd = 'no %s' % cmd
if not config or cmd not in config:
return cmd

def _render_networks(self, item, config=None):
commands = list()
safe_list = list()

for entry in item['networks']:
network = entry['prefix']
if entry['masklen']:
network = '%s/%s' % (entry['prefix'], entry['masklen'])
safe_list.append(network)

cmd = 'network %s' % network

if entry['route_map']:
cmd += ' route-map %s' % entry['route_map']

if not config or cmd not in config:
commands.append(cmd)

if self.params['operation'] == 'replace':
if config:
matches = re.findall(r'network (\S+)', config, re.M)
for entry in set(matches).difference(safe_list):
commands.append('no network %s' % entry)

return commands

def _render_redistribute(self, item, config=None):
commands = list()
safe_list = list()

for entry in item['redistribute']:
option = entry['protocol']

cmd = 'redistribute %s' % entry['protocol']

if entry['id'] and entry['protocol'] in ('ospf', 'table'):
cmd += ' %s' % entry['id']
option += ' %s' % entry['id']

if entry['metric']:
cmd += ' metric %s' % entry['metric']

if entry['route_map']:
cmd += ' route-map %s' % entry['route_map']

if not config or cmd not in config:
commands.append(cmd)

safe_list.append(option)

if self.params['operation'] == 'replace':
if config:
matches = re.findall(r'redistribute (\S+)(?:\s*)(\d*)', config, re.M)
for i in range(0, len(matches)):
matches[i] = ' '.join(matches[i]).strip()
for entry in set(matches).difference(safe_list):
commands.append('no redistribute %s' % entry)

return commands

def _render_neighbors(self, item, config):
""" generate bgp neighbor configuration
"""
return AFNeighbors(self.params).render(config, nbr_list=item['neighbors'])
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
#
# (c) 2019, Ansible by Red Hat, inc
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
#
import re

from ansible.module_utils.six import iteritems
from ansible.module_utils.network.common.utils import to_list
from ansible.module_utils.network.frr.providers.providers import CliProvider


class Neighbors(CliProvider):

def render(self, config=None, nbr_list=None):
commands = list()
safe_list = list()
if not nbr_list:
nbr_list = self.get_value('config.neighbors')

for item in nbr_list:
neighbor_commands = list()
context = 'neighbor %s' % item['neighbor']
cmd = '%s remote-as %s' % (context, item['remote_as'])

if not config or cmd not in config:
neighbor_commands.append(cmd)

for key, value in iteritems(item):
if value is not None:
meth = getattr(self, '_render_%s' % key, None)
if meth:
resp = meth(item, config)
if resp:
neighbor_commands.extend(to_list(resp))

commands.extend(neighbor_commands)
safe_list.append(context)

if self.params['operation'] == 'replace':
if config and safe_list:
commands.extend(self._negate_config(config, safe_list))

return commands

def _negate_config(self, config, safe_list=None):
commands = list()
matches = re.findall(r'(neighbor \S+)', config, re.M)
for item in set(matches).difference(safe_list):
commands.append('no %s' % item)
return commands

def _render_advertisement_interval(self, item, config=None):
cmd = 'neighbor %s advertisement-interval %s' % (item['neighbor'], item['advertisement_interval'])
if not config or cmd not in config:
return cmd

def _render_local_as(self, item, config=None):
cmd = 'neighbor %s local-as %s' % (item['neighbor'], item['local_as'])
if not config or cmd not in config:
return cmd

def _render_port(self, item, config=None):
cmd = 'neighbor %s port %s' % (item['neighbor'], item['port'])
if not config or cmd not in config:
return cmd

def _render_description(self, item, config=None):
cmd = 'neighbor %s description %s' % (item['neighbor'], item['description'])
if not config or cmd not in config:
return cmd

def _render_enabled(self, item, config=None):
cmd = 'neighbor %s shutdown' % item['neighbor']
if item['enabled'] is True:
cmd = 'no %s' % cmd
if not config or cmd not in config:
return cmd

def _render_update_source(self, item, config=None):
cmd = 'neighbor %s update-source %s' % (item['neighbor'], item['update_source'])
if not config or cmd not in config:
return cmd

def _render_password(self, item, config=None):
cmd = 'neighbor %s password %s' % (item['neighbor'], item['password'])
if not config or cmd not in config:
return cmd

def _render_ebgp_multihop(self, item, config=None):
cmd = 'neighbor %s ebgp-multihop %s' % (item['neighbor'], item['ebgp_multihop'])
if not config or cmd not in config:
return cmd

def _render_peer_group(self, item, config=None):
cmd = 'neighbor %s peer-group %s' % (item['neighbor'], item['peer_group'])
if not config or cmd not in config:
return cmd

def _render_timers(self, item, config):
"""generate bgp timer related configuration
"""
keepalive = item['timers']['keepalive']
holdtime = item['timers']['holdtime']
neighbor = item['neighbor']

if keepalive and holdtime:
cmd = 'neighbor %s timers %s %s' % (neighbor, keepalive, holdtime)
if not config or cmd not in config:
return cmd
else:
raise ValueError("required both options for timers: keepalive and holdtime")


class AFNeighbors(CliProvider):

def render(self, config=None, nbr_list=None):
commands = list()
if not nbr_list:
return

for item in nbr_list:
neighbor_commands = list()
for key, value in iteritems(item):
if value is not None:
meth = getattr(self, '_render_%s' % key, None)
if meth:
resp = meth(item, config)
if resp:
neighbor_commands.extend(to_list(resp))

commands.extend(neighbor_commands)

return commands

def _render_route_reflector_client(self, item, config=None):
cmd = 'neighbor %s route-reflector-client' % item['neighbor']
if item['route_reflector_client'] is False:
if not config or cmd in config:
cmd = 'no %s' % cmd
return cmd
elif not config or cmd not in config:
return cmd

def _render_route_server_client(self, item, config=None):
cmd = 'neighbor %s route-server-client' % item['neighbor']
if item['route_server_client'] is False:
if not config or cmd in config:
cmd = 'no %s' % cmd
return cmd
elif not config or cmd not in config:
return cmd

def _render_remove_private_as(self, item, config=None):
cmd = 'neighbor %s remove-private-AS' % item['neighbor']
if item['remove_private_as'] is False:
if not config or cmd in config:
cmd = 'no %s' % cmd
return cmd
elif not config or cmd not in config:
return cmd

def _render_next_hop_self(self, item, config=None):
cmd = 'neighbor %s activate' % item['neighbor']
if item['next_hop_self'] is False:
if not config or cmd in config:
cmd = 'no %s' % cmd
return cmd
elif not config or cmd not in config:
return cmd

def _render_activate(self, item, config=None):
cmd = 'neighbor %s activate' % item['neighbor']
if item['activate'] is False:
if not config or cmd in config:
cmd = 'no %s' % cmd
return cmd
elif not config or cmd not in config:
return cmd

def _render_maximum_prefix(self, item, config=None):
cmd = 'neighbor %s maximum-prefix %s' % (item['neighbor'], item['maximum_prefix'])
if not config or cmd not in config:
return cmd