New module for BGP configuration management in Arista EOS (#52722)

* New module for BGP in EOS

Signed-off-by: NilashishC <nilashishchakraborty8@gmail.com>

* Add function to validate input

Signed-off-by: NilashishC <nilashishchakraborty8@gmail.com>

* Fix line indentation

Signed-off-by: NilashishC <nilashishchakraborty8@gmail.com>

* Add integration tests

Signed-off-by: NilashishC <nilashishchakraborty8@gmail.com>

* Fix CI

Signed-off-by: NilashishC <nilashishchakraborty8@gmail.com>

* Fix sanity test failure

Signed-off-by: NilashishC <nilashishchakraborty8@gmail.com>

* Remove unused code

Signed-off-by: NilashishC <nilashishchakraborty8@gmail.com>
This commit is contained in:
Nilashish Chakraborty 2019-03-12 17:31:58 +05:30 committed by GitHub
commit 9365c0f468
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 1657 additions and 0 deletions

View file

@ -0,0 +1,130 @@
#
# (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.eos.providers.providers import CliProvider
from ansible.module_utils.network.eos.providers.cli.config.bgp.neighbors import AFNeighbors
from ansible.module_utils.common.network import to_netmask
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' % item['afi']
context_commands = list()
if config:
context_path = [router_context, context]
context_config = self.get_config_context(config, context_path, indent=2)
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')
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['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'])

View file

@ -0,0 +1,173 @@
#
# (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.eos.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_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:
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_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_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_maximum_prefix(self, item, config=None):
cmd = 'neighbor %s maximum-routes %s' % (item['neighbor'], item['maximum_prefix'])
if 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_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
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_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_default_originate(self, item, config=None):
cmd = 'neighbor %s default-originate' % 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_graceful_restart(self, item, config=None):
cmd = 'neighbor %s graceful-restart' % 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_weight(self, item, config=None):
cmd = 'neighbor %s weight %s' % (item['neighbor'], item['weight'])
if not config or cmd not in config:
return cmd

View file

@ -0,0 +1,162 @@
#
# (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.eos.providers.providers import register_provider
from ansible.module_utils.network.eos.providers.providers import CliProvider
from ansible.module_utils.network.eos.providers.cli.config.bgp.neighbors import Neighbors
from ansible.module_utils.network.eos.providers.cli.config.bgp.address_family import AddressFamily
REDISTRIBUTE_PROTOCOLS = frozenset(['ospf', 'ospf3', 'rip', 'isis', 'static', 'connected'])
@register_provider('eos', 'eos_bgp')
class Provider(CliProvider):
def render(self, config=None):
commands = list()
existing_as = None
if config:
match = re.search(r'router bgp (\d+)', config, re.M)
existing_as = match.group(1)
operation = self.params['operation']
context = None
if self.params['config']:
context = 'router bgp %s' % self.get_value('config.bgp_as')
if operation == 'delete':
if existing_as:
commands.append('no router bgp %s' % existing_as)
elif context:
commands.append('no %s' % context)
else:
self._validate_input(config)
if operation == 'replace':
if existing_as and int(existing_as) != self.get_value('config.bgp_as'):
commands.append('no router bgp %s' % existing_as)
config = None
elif operation == 'override':
if existing_as:
commands.append('no router bgp %s' % existing_as)
config = None
context_commands = list()
for key, value in iteritems(self.get_value('config')):
if value is not None:
meth = getattr(self, '_render_%s' % key, None)
if meth:
resp = meth(config)
if resp:
context_commands.extend(to_list(resp))
if context and context_commands:
commands.append(context)
commands.extend(context_commands)
commands.append('exit')
return commands
def _render_router_id(self, config=None):
cmd = 'router-id %s' % self.get_value('config.router_id')
if not config or cmd not in config:
return cmd
def _render_log_neighbor_changes(self, config=None):
cmd = 'bgp log-neighbor-changes'
log_neighbor_changes = self.get_value('config.log_neighbor_changes')
if log_neighbor_changes is True:
if not config or cmd not in config:
return cmd
elif log_neighbor_changes is False:
if config and cmd in config:
return 'no %s' % cmd
def _render_networks(self, config=None):
commands = list()
safe_list = list()
for entry in self.get_value('config.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, config=None):
commands = list()
safe_list = list()
for entry in self.get_value('config.redistribute'):
option = entry['protocol']
cmd = 'redistribute %s' % entry['protocol']
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, config):
""" generate bgp neighbor configuration
"""
return Neighbors(self.params).render(config)
def _render_address_family(self, config):
""" generate address-family configuration
"""
return AddressFamily(self.params).render(config)
def _validate_input(self, config):
def device_has_AF(config):
return re.search(r'address-family (?:.*)', config)
address_family = self.get_value('config.address_family')
root_networks = self.get_value('config.networks')
operation = self.params['operation']
if operation == 'replace' and root_networks:
if address_family:
for item in address_family:
if item['networks']:
raise ValueError('operation is replace but provided both root level networks and networks under %s address family'
% item['afi'])
if config and device_has_AF(config):
raise ValueError('operation is replace and device has one or more address family activated but root level network(s) provided')