Skip to content

Commit

Permalink
[SHOW][BGP] support show ip(v6) bgp summary for multi asic platform (s…
Browse files Browse the repository at this point in the history
…onic-net#1064)

Signed-off-by: Arvindsrinivasan Lakshmi Narasimhan <arlakshm@microsoft.com>

The following changes are done
- Support show ip(v6) bgp summary for multi asic platforms
Add the following 2 multi asic options to the cli command
  [-n, --namespace] to allow user to display the information for given namespaces
  If this option is not present the information from all the namespaces will be displayed

  [-d, --display] to allow user to display information related both internal and external interfaces
  If this option is not present only external interfaces/neighbors will be display

- For FRR bgp containers, get the bgp summary in json and display to the user
  add a new file bgp_util.py to have all the common bgp related functions.
  add new functions to get bgp summary from FRR in json, parse the json and render the output
- Add unit test for show ip(v4) bgp summary
  add new unit test to show ip(v4) bgp summary
  • Loading branch information
arlakshm authored Aug 25, 2020
1 parent 8934479 commit 5e661d8
Show file tree
Hide file tree
Showing 12 changed files with 1,912 additions and 199 deletions.
23 changes: 12 additions & 11 deletions show/bgp_frr_v4.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import json

import click

import utilities_common.bgp_util as bgp_util
import utilities_common.cli as clicommon

from show.main import ip, run_command, get_bgp_summary_extended

import utilities_common.constants as constants
import utilities_common.multi_asic as multi_asic_util
from show.main import ip, run_command

###############################################################################
#
Expand All @@ -12,6 +15,7 @@
###############################################################################



@ip.group(cls=clicommon.AliasedGroup)
def bgp():
"""Show IPv4 BGP (Border Gateway Protocol) information"""
Expand All @@ -20,14 +24,11 @@ def bgp():

# 'summary' subcommand ("show ip bgp summary")
@bgp.command()
def summary():
"""Show summarized information of IPv4 BGP state"""
try:
device_output = run_command('sudo vtysh -c "show ip bgp summary"', return_cmd=True)
get_bgp_summary_extended(device_output)
except Exception:
run_command('sudo vtysh -c "show ip bgp summary"')

@multi_asic_util.multi_asic_click_options
def summary(namespace, display):
bgp_summary = bgp_util.get_bgp_summary_from_all_bgp_instances(constants.IPV4, namespace,display)
bgp_util.display_bgp_summary(bgp_summary=bgp_summary, af=constants.IPV4)


# 'neighbors' subcommand ("show ip bgp neighbors")
@bgp.command()
Expand Down
16 changes: 8 additions & 8 deletions show/bgp_frr_v6.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import click

import utilities_common.cli as clicommon
from show.main import ipv6, run_command, get_bgp_summary_extended

from show.main import ipv6, run_command
import utilities_common.multi_asic as multi_asic_util
import utilities_common.bgp_util as bgp_util
import utilities_common.constants as constants

###############################################################################
#
Expand All @@ -19,13 +21,11 @@ def bgp():

# 'summary' subcommand ("show ipv6 bgp summary")
@bgp.command()
def summary():
@multi_asic_util.multi_asic_click_options
def summary(namespace, display):
"""Show summarized information of IPv6 BGP state"""
try:
device_output = run_command('sudo vtysh -c "show bgp ipv6 summary"', return_cmd=True)
get_bgp_summary_extended(device_output)
except Exception:
run_command('sudo vtysh -c "show bgp ipv6 summary"')
bgp_summary = bgp_util.get_bgp_summary_from_all_bgp_instances(constants.IPV6, namespace,display)
bgp_util.display_bgp_summary(bgp_summary=bgp_summary, af=constants.IPV6)


# 'neighbors' subcommand ("show ipv6 bgp neighbors")
Expand Down
3 changes: 2 additions & 1 deletion show/bgp_quagga_v4.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import click
from show.main import AliasedGroup, ip, run_command, get_bgp_summary_extended
from show.main import AliasedGroup, ip, run_command
from utilities_common.bgp_util import get_bgp_summary_extended


###############################################################################
Expand Down
3 changes: 2 additions & 1 deletion show/bgp_quagga_v6.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import click
from show.main import AliasedGroup, ipv6, run_command, get_bgp_summary_extended
from show.main import AliasedGroup, ipv6, run_command
from utilities_common.bgp_util import get_bgp_summary_extended


###############################################################################
Expand Down
134 changes: 2 additions & 132 deletions show/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import re
import subprocess
import sys
import ipaddress


import click
from natsort import natsorted
Expand Down Expand Up @@ -35,7 +35,7 @@
# location (configdb?), so that we prevent the continous execution of this
# bash oneliner. To be revisited once routing-stack info is tracked somewhere.
def get_routing_stack():
command = "sudo docker ps | grep bgp | awk '{print$2}' | cut -d'-' -f3 | cut -d':' -f1"
command = "sudo docker ps | grep bgp | awk '{print$2}' | cut -d'-' -f3 | cut -d':' -f1 | head -n 1"

try:
proc = subprocess.Popen(command,
Expand Down Expand Up @@ -95,33 +95,6 @@ def run_command(command, display_cmd=False, return_cmd=False):
iface_alias_converter = clicommon.InterfaceAliasConverter()


def get_bgp_summary_extended(command_output):
"""
Adds Neighbor name to the show ip[v6] bgp summary command
:param command: command to get bgp summary
"""
static_neighbors, dynamic_neighbors = get_bgp_neighbors_dict()
modified_output = []
my_list = iter(command_output.splitlines())
for element in my_list:
if element.startswith("Neighbor"):
element = "{}\tNeighborName".format(element)
modified_output.append(element)
elif not element or element.startswith("Total number "):
modified_output.append(element)
elif re.match(r"(\*?([0-9A-Fa-f]{1,4}:|\d+.\d+.\d+.\d+))", element.split()[0]):
first_element = element.split()[0]
ip = first_element[1:] if first_element.startswith("*") else first_element
name = get_bgp_neighbor_ip_to_name(ip, static_neighbors, dynamic_neighbors)
if len(element.split()) == 1:
modified_output.append(element)
element = next(my_list)
element = "{}\t{}".format(element, name)
modified_output.append(element)
else:
modified_output.append(element)
click.echo("\n".join(modified_output))


def connect_config_db():
"""
Expand All @@ -132,108 +105,6 @@ def connect_config_db():
return config_db


def get_neighbor_dict_from_table(db,table_name):
"""
returns a dict with bgp neighbor ip as key and neighbor name as value
:param table_name: config db table name
:param db: config_db
"""
neighbor_dict = {}
neighbor_data = db.get_table(table_name)
try:
for entry in neighbor_data.keys():
neighbor_dict[entry] = neighbor_data[entry].get(
'name') if 'name' in neighbor_data[entry].keys() else 'NotAvailable'
return neighbor_dict
except Exception:
return neighbor_dict


def is_ipv4_address(ipaddress):
"""
Checks if given ip is ipv4
:param ipaddress: unicode ipv4
:return: bool
"""
try:
ipaddress.IPv4Address(ipaddress)
return True
except ipaddress.AddressValueError as err:
return False


def is_ipv6_address(ipaddress):
"""
Checks if given ip is ipv6
:param ipaddress: unicode ipv6
:return: bool
"""
try:
ipaddress.IPv6Address(ipaddress)
return True
except ipaddress.AddressValueError as err:
return False


def get_dynamic_neighbor_subnet(db):
"""
Returns dict of description and subnet info from bgp_peer_range table
:param db: config_db
"""
dynamic_neighbor = {}
v4_subnet = {}
v6_subnet = {}
neighbor_data = db.get_table('BGP_PEER_RANGE')
try:
for entry in neighbor_data.keys():
new_key = neighbor_data[entry]['ip_range'][0]
new_value = neighbor_data[entry]['name']
if is_ipv4_address(unicode(neighbor_data[entry]['src_address'])):
v4_subnet[new_key] = new_value
elif is_ipv6_address(unicode(neighbor_data[entry]['src_address'])):
v6_subnet[new_key] = new_value
dynamic_neighbor["v4"] = v4_subnet
dynamic_neighbor["v6"] = v6_subnet
return dynamic_neighbor
except Exception:
return neighbor_data


def get_bgp_neighbors_dict():
"""
Uses config_db to get the bgp neighbors and names in dictionary format
:return:
"""
dynamic_neighbors = {}
config_db = connect_config_db()
static_neighbors = get_neighbor_dict_from_table(config_db, 'BGP_NEIGHBOR')
bgp_monitors = get_neighbor_dict_from_table(config_db, 'BGP_MONITORS')
static_neighbors.update(bgp_monitors)
dynamic_neighbors = get_dynamic_neighbor_subnet(config_db)
return static_neighbors, dynamic_neighbors


def get_bgp_neighbor_ip_to_name(ip, static_neighbors, dynamic_neighbors):
"""
return neighbor name for the ip provided
:param ip: ip address unicode
:param static_neighbors: statically defined bgp neighbors dict
:param dynamic_neighbors: subnet of dynamically defined neighbors dict
:return: name of neighbor
"""
if ip in static_neighbors.keys():
return static_neighbors[ip]
elif is_ipv4_address(unicode(ip)):
for subnet in dynamic_neighbors["v4"].keys():
if ipaddress.IPv4Address(unicode(ip)) in ipaddress.IPv4Network(unicode(subnet)):
return dynamic_neighbors["v4"][subnet]
elif is_ipv6_address(unicode(ip)):
for subnet in dynamic_neighbors["v6"].keys():
if ipaddress.IPv6Address(unicode(ip)) in ipaddress.IPv6Network(unicode(subnet)):
return dynamic_neighbors["v6"][subnet]
else:
return "NotAvailable"


CONTEXT_SETTINGS = dict(help_option_names=['-h', '--help', '-?'])

Expand Down Expand Up @@ -1038,7 +909,6 @@ def protocol(verbose):
cmd = 'sudo vtysh -c "show ipv6 protocol"'
run_command(cmd, display_cmd=verbose)


#
# Inserting BGP functionality into cli's show parse-chain.
# BGP commands are determined by the routing-stack being elected.
Expand Down
137 changes: 137 additions & 0 deletions tests/bgp_commands_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
import os

import pytest

from click.testing import CliRunner

show_bgp_summary_v4 = """\
IPv4 Unicast Summary:
BGP router identifier 10.1.0.32, local AS number 65100 vrf-id 0
BGP table version 12811
RIB entries 12817, using 2358328 bytes of memory
Peers 24, using 502080 KiB of memory
Peer groups 4, using 256 bytes of memory
Neighbhor V AS MsgRcvd MsgSent TblVer InQ OutQ Up/Down State/PfxRcd NeighborName
----------- --- ----- --------- --------- -------- ----- ------ --------- -------------- --------------
10.0.0.1 4 65200 5919 2717 0 0 0 1d21h11m 6402 ARISTA01T2
10.0.0.5 4 65200 5916 2714 0 0 0 1d21h10m 6402 ARISTA03T2
10.0.0.9 4 65200 5915 2713 0 0 0 1d21h09m 6402 ARISTA05T2
10.0.0.13 4 65200 5917 2716 0 0 0 1d21h11m 6402 ARISTA07T2
10.0.0.17 4 65200 5916 2713 0 0 0 1d21h09m 6402 ARISTA09T2
10.0.0.21 4 65200 5917 2716 0 0 0 1d21h11m 6402 ARISTA11T2
10.0.0.25 4 65200 5917 2716 0 0 0 1d21h11m 6402 ARISTA13T2
10.0.0.29 4 65200 5916 2714 0 0 0 1d21h10m 6402 ARISTA15T2
10.0.0.33 4 64001 0 0 0 0 0 never Active ARISTA01T0
10.0.0.35 4 64002 0 0 0 0 0 never Active ARISTA02T0
10.0.0.37 4 64003 0 0 0 0 0 never Active ARISTA03T0
10.0.0.39 4 64004 0 0 0 0 0 never Active ARISTA04T0
10.0.0.41 4 64005 0 0 0 0 0 never Active ARISTA05T0
10.0.0.43 4 64006 0 0 0 0 0 never Active ARISTA06T0
10.0.0.45 4 64007 0 0 0 0 0 never Active ARISTA07T0
10.0.0.47 4 64008 0 0 0 0 0 never Active ARISTA08T0
10.0.0.49 4 64009 0 0 0 0 0 never Active ARISTA09T0
10.0.0.51 4 64010 0 0 0 0 0 never Active ARISTA10T0
10.0.0.53 4 64011 0 0 0 0 0 never Active ARISTA11T0
10.0.0.55 4 64012 0 0 0 0 0 never Active ARISTA12T0
10.0.0.57 4 64013 0 0 0 0 0 never Active ARISTA13T0
10.0.0.59 4 64014 0 0 0 0 0 never Active ARISTA14T0
10.0.0.61 4 64015 0 0 0 0 0 never Active ARISTA15T0
10.0.0.63 4 64016 0 0 0 0 0 never Active ARISTA16T0
"""

show_bgp_summary_v6 = """\
IPv6 Unicast Summary:
BGP router identifier 10.1.0.32, local AS number 65100 vrf-id 0
BGP table version 8972
RIB entries 12817, using 2358328 bytes of memory
Peers 24, using 502080 KiB of memory
Peer groups 4, using 256 bytes of memory
Neighbhor V AS MsgRcvd MsgSent TblVer InQ OutQ Up/Down State/PfxRcd NeighborName
----------- --- ----- --------- --------- -------- ----- ------ --------- -------------- --------------
fc00::1a 4 65200 6665 6672 0 0 0 2d09h39m 6402 ARISTA07T2
fc00::2 4 65200 6666 7913 0 0 0 2d09h39m 6402 ARISTA01T2
fc00::2a 4 65200 6666 7913 0 0 0 2d09h39m 6402 ARISTA11T2
fc00::3a 4 65200 6666 7912 0 0 0 2d09h39m 6402 ARISTA15T2
fc00::4a 4 64003 0 0 0 0 0 never Active ARISTA03T0
fc00::4e 4 64004 0 0 0 0 0 never Active ARISTA04T0
fc00::5a 4 64007 0 0 0 0 0 never Active ARISTA07T0
fc00::5e 4 64008 0 0 0 0 0 never Active ARISTA08T0
fc00::6a 4 64011 0 0 0 0 0 never Connect ARISTA11T0
fc00::6e 4 64012 0 0 0 0 0 never Active ARISTA12T0
fc00::7a 4 64015 0 0 0 0 0 never Active ARISTA15T0
fc00::7e 4 64016 0 0 0 0 0 never Active ARISTA16T0
fc00::12 4 65200 6666 7915 0 0 0 2d09h39m 6402 ARISTA05T2
fc00::22 4 65200 6667 7915 0 0 0 2d09h39m 6402 ARISTA09T2
fc00::32 4 65200 6663 6669 0 0 0 2d09h36m 6402 ARISTA13T2
fc00::42 4 64001 0 0 0 0 0 never Active ARISTA01T0
fc00::46 4 64002 0 0 0 0 0 never Active ARISTA02T0
fc00::52 4 64005 0 0 0 0 0 never Active ARISTA05T0
fc00::56 4 64006 0 0 0 0 0 never Active ARISTA06T0
fc00::62 4 64009 0 0 0 0 0 never Active ARISTA09T0
fc00::66 4 64010 0 0 0 0 0 never Active ARISTA10T0
fc00::72 4 64013 0 0 0 0 0 never Active ARISTA13T0
fc00::76 4 64014 0 0 0 0 0 never Active ARISTA14T0
fc00::a 4 65200 6665 6671 0 0 0 2d09h38m 6402 ARISTA03T2
"""

show_error_invalid_json = """\
Usage: summary [OPTIONS]
Try 'summary --help' for help.
Error: bgp summary from bgp container not in json format
"""


class TestBgpCommands(object):
@classmethod
def setup_class(cls):
print("SETUP")
import mock_tables.dbconnector

@pytest.mark.parametrize('setup_single_bgp_instance',
['v4'], indirect=['setup_single_bgp_instance'])
def test_bgp_summary_v4(
self,
setup_bgp_commands,
setup_single_bgp_instance):
show = setup_bgp_commands
runner = CliRunner()
result = runner.invoke(
show.cli.commands["ip"].commands["bgp"].commands["summary"], [])
print("{}".format(result.output))
assert result.exit_code == 0
assert result.output == show_bgp_summary_v4

@pytest.mark.parametrize('setup_single_bgp_instance',
['v6'], indirect=['setup_single_bgp_instance'])
def test_bgp_summary_v6(
self,
setup_bgp_commands,
setup_single_bgp_instance):
show = setup_bgp_commands
runner = CliRunner()
result = runner.invoke(
show.cli.commands["ipv6"].commands["bgp"].commands["summary"], [])
print("{}".format(result.output))
assert result.exit_code == 0
assert result.output == show_bgp_summary_v6

@pytest.mark.parametrize('setup_single_bgp_instance',
[' '], indirect=['setup_single_bgp_instance'])
def test_bgp_summary_error(
self,
setup_bgp_commands,
setup_single_bgp_instance):
show = setup_bgp_commands
runner = CliRunner()
result = runner.invoke(
show.cli.commands["ipv6"].commands["bgp"].commands["summary"], [])
print("{}".format(result.output))
assert result.exit_code == 2
assert result.output == show_error_invalid_json
Loading

0 comments on commit 5e661d8

Please sign in to comment.