mirror of
https://github.com/ansible-collections/community.general.git
synced 2025-07-22 21:00:22 -07:00
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:
parent
3bef1dbfd2
commit
9365c0f468
17 changed files with 1657 additions and 0 deletions
|
@ -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'])
|
|
@ -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
|
|
@ -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')
|
Loading…
Add table
Add a link
Reference in a new issue