From bcc751a72ca05d33230106940a9ff76da28cebfd Mon Sep 17 00:00:00 2001 From: SuvarnaMeenakshi Date: Mon, 24 Aug 2020 09:41:42 -0700 Subject: [PATCH 1/5] [BGP]: Update CiscoBgp4MIB implementation to get data from STATE_DB instead of getting data from vtysh. Update unit-test mock table to add new table with BGP Neighbour information. Signed-off-by: SuvarnaMeenakshi --- src/sonic_ax_impl/mibs/vendor/cisco/bgp4.py | 70 +++++++++++---------- tests/mock_tables/state_db.json | 50 ++++++++++++++- tests/test_vtysh.py | 1 + 3 files changed, 87 insertions(+), 34 deletions(-) diff --git a/src/sonic_ax_impl/mibs/vendor/cisco/bgp4.py b/src/sonic_ax_impl/mibs/vendor/cisco/bgp4.py index d2aa5d3ca..64c58a1f2 100644 --- a/src/sonic_ax_impl/mibs/vendor/cisco/bgp4.py +++ b/src/sonic_ax_impl/mibs/vendor/cisco/bgp4.py @@ -5,55 +5,59 @@ from sonic_ax_impl.lib.quaggaclient import QuaggaClient, bgp_peer_tuple from ax_interface import MIBMeta, ValueType, MIBUpdater, SubtreeMIBEntry from ax_interface.mib import MIBEntry +from sonic_ax_impl.mibs import Namespace +import ipaddress + +STATE_CODE = { + "Idle": 1, + "Idle (Admin)": 1, + "Connect": 2, + "Active": 3, + "OpenSent": 4, + "OpenConfirm": 5, + "Established": 6 +}; + class BgpSessionUpdater(MIBUpdater): def __init__(self): super().__init__() - self.sock = PerseverantSocket(socket.AF_INET, socket.SOCK_STREAM - , address_tuple=(QuaggaClient.HOST, QuaggaClient.PORT)) - self.QuaggaClient = QuaggaClient(self.sock) + self.db_conn = Namespace.init_namespace_dbs() + self.neigh_state_map = {} self.session_status_map = {} self.session_status_list = [] def reinit_data(self): - if not self.sock.connected: - try: - self.sock.reconnect() - mibs.logger.info('Connected quagga socket') - except (ConnectionRefusedError, socket.timeout) as e: - mibs.logger.debug('Failed to connect quagga socket. Retry later...: {}.'.format(e)) - return - self.QuaggaClient.auth() - mibs.logger.info('Authed quagga socket') + Namespace.connect_all_dbs(self.db_conn, mibs.STATE_DB) + self.neigh_state_map = Namespace.dbs_keys_namespace(self.db_conn, mibs.STATE_DB, "NEIGH_STATE_TABLE|*") def update_data(self): - self.session_status_map = {} - self.session_status_list = [] + for neigh_key, db_index in self.neigh_state_map.items(): + neigh_str = neigh_key.decode() + neigh_str = neigh_str.split('|')[1] + neigh_info = self.db_conn[db_index].get_all(mibs.STATE_DB, neigh_key, blocking=False) + if neigh_info is None: + continue + state = neigh_info[b'state'].decode() + ip = ipaddress.ip_address(neigh_str) + if type(ip) is ipaddress.IPv4Address: + oid_head = (1, 4) + else: + oid_head = (2, 16) + oid_ip = tuple(i for i in ip.packed) - try: - if not self.sock.connected: - return + if state.isdigit(): + status = 6 + elif state in STATE_CODE: + status = STATE_CODE[state] + else: + continue - sessions = self.QuaggaClient.union_bgp_sessions() - - except (socket.error, socket.timeout) as e: - self.sock.close() - mibs.logger.error('Failed to talk with quagga socket. Reconnect later...: {}.'.format(e)) - return - except ValueError as e: - self.sock.close() - mibs.logger.error('Receive unexpected data from quagga socket. Reconnect later...: {}.'.format(e)) - return - - for nei, ses in sessions.items(): - oid, status = bgp_peer_tuple(ses) - if oid is None: continue + oid = oid_head + oid_ip self.session_status_list.append(oid) self.session_status_map[oid] = status - self.session_status_list.sort() - def sessionstatus(self, sub_id): return self.session_status_map.get(sub_id, None) diff --git a/tests/mock_tables/state_db.json b/tests/mock_tables/state_db.json index 302bdd880..28eb4a0bc 100644 --- a/tests/mock_tables/state_db.json +++ b/tests/mock_tables/state_db.json @@ -45,5 +45,53 @@ }, "MGMT_PORT_TABLE|eth1": { "oper_status": "up" - } + }, + "NEIGH_STATE_TABLE|10.0.0.57": { + "state" : "6402" + }, + "NEIGH_STATE_TABLE|10.0.0.59": { + "state" : "6402" + }, + "NEIGH_STATE_TABLE|10.0.0.61": { + "state" : "6402" + }, + "NEIGH_STATE_TABLE|10.0.0.63": { + "state" : "6402" + }, + "NEIGH_STATE_TABLE|10.0.0.65": { + "state" : "Idle" + }, + "NEIGH_STATE_TABLE|10.0.0.67": { + "state" : "Idle (Admin)" + }, + "NEIGH_STATE_TABLE|fc00::72": { + "state" : "0" + }, + "NEIGH_STATE_TABLE|fc00::76": { + "state" : "0" + }, + "NEIGH_STATE_TABLE|fc00::7a": { + "state" : "0" + }, + "NEIGH_STATE_TABLE|fc00::7e": { + "state" : "0" + }, + "NEIGH_STATE_TABLE|fc00::2": { + "state" : "Active" + }, + "NEIGH_STATE_TABLE|fc00::4": { + "state" : "Connect" + }, + "NEIGH_STATE_TABLE|fc00::6": { + "state" : "OpenSent" + }, + "NEIGH_STATE_TABLE|fc00::8": { + "state" : "OpenConfirm" + }, + "NEIGH_STATE_TABLE|fc00::10": { + "state" : "Clearing" + }, + "NEIGH_STATE_TABLE|fc00::12": { + "state" : "Deleted" + } } diff --git a/tests/test_vtysh.py b/tests/test_vtysh.py index 995c9f97c..31f3f23f2 100644 --- a/tests/test_vtysh.py +++ b/tests/test_vtysh.py @@ -20,6 +20,7 @@ from sonic_ax_impl.main import SonicMIB from sonic_ax_impl.lib.quaggaclient import parse_bgp_summary from mock_tables.socket import MockGetHostname +from sonic_ax_impl.mibs.vendor.cisco.bgp4 import CiscoBgp4MIB class TestSonicMIB(TestCase): @classmethod From 4666a76da194985a6e0f71e135b87b61076d0310 Mon Sep 17 00:00:00 2001 From: SuvarnaMeenakshi Date: Wed, 26 Aug 2020 17:29:58 -0700 Subject: [PATCH 2/5] Re-init data structure before populating in update_data. Sort oid list. Signed-off-by: SuvarnaMeenakshi --- src/sonic_ax_impl/mibs/vendor/cisco/bgp4.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/sonic_ax_impl/mibs/vendor/cisco/bgp4.py b/src/sonic_ax_impl/mibs/vendor/cisco/bgp4.py index 64c58a1f2..d0b9f8f96 100644 --- a/src/sonic_ax_impl/mibs/vendor/cisco/bgp4.py +++ b/src/sonic_ax_impl/mibs/vendor/cisco/bgp4.py @@ -33,6 +33,9 @@ def reinit_data(self): self.neigh_state_map = Namespace.dbs_keys_namespace(self.db_conn, mibs.STATE_DB, "NEIGH_STATE_TABLE|*") def update_data(self): + self.session_status_map = {} + self.session_status_list = [] + for neigh_key, db_index in self.neigh_state_map.items(): neigh_str = neigh_key.decode() neigh_str = neigh_str.split('|')[1] @@ -58,6 +61,8 @@ def update_data(self): self.session_status_list.append(oid) self.session_status_map[oid] = status + self.session_status_list.sort() + def sessionstatus(self, sub_id): return self.session_status_map.get(sub_id, None) From 113e4e51ebb8639e174530ae1b1640ee09bae8bd Mon Sep 17 00:00:00 2001 From: SuvarnaMeenakshi Date: Thu, 27 Aug 2020 11:11:29 -0700 Subject: [PATCH 3/5] Add namespace related unit-test for CiscoBgp4MIB. Remove usage of mock vtysh socket and vtysh dummy output. Signed-off-by: SuvarnaMeenakshi --- tests/__init__.py | 1 - tests/mock_tables/asic0/state_db.json | 12 ++ tests/mock_tables/asic1/state_db.json | 9 ++ tests/mock_tables/asic2/appl_db.json | 2 +- tests/mock_tables/asic2/state_db.json | 6 + tests/mock_tables/bgpsummary_ipv6.txt | 19 ---- tests/mock_tables/bgpsummary_ipv6_nobgp.txt | 1 - tests/mock_tables/socket.py | 73 ------------- tests/namespace/test_bgp.py | 115 ++++++++++++++++++++ tests/{test_vtysh.py => test_bgp.py} | 14 --- 10 files changed, 143 insertions(+), 109 deletions(-) delete mode 100644 tests/mock_tables/bgpsummary_ipv6.txt delete mode 100644 tests/mock_tables/bgpsummary_ipv6_nobgp.txt delete mode 100644 tests/mock_tables/socket.py create mode 100644 tests/namespace/test_bgp.py rename tests/{test_vtysh.py => test_bgp.py} (87%) diff --git a/tests/__init__.py b/tests/__init__.py index 5feedc0e9..e69de29bb 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -1 +0,0 @@ -import tests.mock_tables.socket diff --git a/tests/mock_tables/asic0/state_db.json b/tests/mock_tables/asic0/state_db.json index 0428a0665..0793070eb 100644 --- a/tests/mock_tables/asic0/state_db.json +++ b/tests/mock_tables/asic0/state_db.json @@ -21,5 +21,17 @@ "tx2power": -5.4, "tx3power": -5.4, "tx4power": -5.4 + }, + "NEIGH_STATE_TABLE|10.0.0.0": { + "state": "Connect" + }, + "NEIGH_STATE_TABLE|10.0.0.2": { + "state": "Active" + }, + "NEIGH_STATE_TABLE|10.10.0.0": { + "state": "Established" + }, + "NEIGH_STATE_TABLE|fec0::ffff:afa:07": { + "state": "Active" } } diff --git a/tests/mock_tables/asic1/state_db.json b/tests/mock_tables/asic1/state_db.json index ac4d2247b..f68a58aa0 100644 --- a/tests/mock_tables/asic1/state_db.json +++ b/tests/mock_tables/asic1/state_db.json @@ -21,5 +21,14 @@ "tx2power": -5.4, "tx3power": -5.4, "tx4power": -5.4 + }, + "NEIGH_STATE_TABLE|10.0.0.6": { + "state": "Idle" + }, + "NEIGH_STATE_TABLE|10.0.0.8": { + "state": "Active" + }, + "NEIGH_STATE_TABLE|10.10.0.4": { + "state": "Established" } } diff --git a/tests/mock_tables/asic2/appl_db.json b/tests/mock_tables/asic2/appl_db.json index 9900490cc..37a72e5e8 100644 --- a/tests/mock_tables/asic2/appl_db.json +++ b/tests/mock_tables/asic2/appl_db.json @@ -75,7 +75,7 @@ "scope": "global", "family": "IPv4" }, - "INTF_TABLE:PortChannel04:10.10.0.5/31": { + "INTF_TABLE:PortChannel04:10.10.0.4/31": { "scope": "global", "family": "IPv4" }, diff --git a/tests/mock_tables/asic2/state_db.json b/tests/mock_tables/asic2/state_db.json index 2c63c0851..87ec6d4dd 100644 --- a/tests/mock_tables/asic2/state_db.json +++ b/tests/mock_tables/asic2/state_db.json @@ -1,2 +1,8 @@ { + "NEIGH_STATE_TABLE|10.10.0.1": { + "state": "Established" + }, + "NEIGH_STATE_TABLE|10.10.0.5": { + "state": "Established" + } } diff --git a/tests/mock_tables/bgpsummary_ipv6.txt b/tests/mock_tables/bgpsummary_ipv6.txt deleted file mode 100644 index d66b912db..000000000 --- a/tests/mock_tables/bgpsummary_ipv6.txt +++ /dev/null @@ -1,19 +0,0 @@ -BGP router identifier 10.1.0.32, local AS number 65100 -RIB entries 13025, using 1425 KiB of memory -Peers 11, using 291 KiB of memory - -Neighbor V AS MsgRcvd MsgSent TblVer InQ OutQ Up/Down State/PfxRcd -fc00::72 4 64600 3253 3255 0 0 0 00:48:54 Idle (Admin) -fc00::76 4 64600 3253 3255 0 0 0 00:48:54 0 -fc00::7a 4 64600 3253 3255 0 0 0 00:48:55 0 -fc00::7e 4 64600 3253 54 0 0 0 00:48:55 0 -fc00::2 4 65200 6608 6790 0 0 0 13:21:22 Active -fc00::4 4 65200 6608 6790 0 0 0 13:21:22 Connect -fc00::6 4 65200 6608 6790 0 0 0 13:21:22 OpenSent -fc00::8 4 65200 6608 6790 0 0 0 13:21:22 OpenConfirm -fc00::10 4 65200 6608 6790 0 0 0 13:21:22 Clearing -fc00::12 4 65200 6608 6790 0 0 0 13:21:22 Deleted -2603:10b0:2800:cc1::2e - 4 64611 2919 92 0 0 0 01:18:59 0 - -Total number of neighbors 11 diff --git a/tests/mock_tables/bgpsummary_ipv6_nobgp.txt b/tests/mock_tables/bgpsummary_ipv6_nobgp.txt deleted file mode 100644 index d1e85d041..000000000 --- a/tests/mock_tables/bgpsummary_ipv6_nobgp.txt +++ /dev/null @@ -1 +0,0 @@ -show ip bgp ipv6 summary diff --git a/tests/mock_tables/socket.py b/tests/mock_tables/socket.py deleted file mode 100644 index 3288fa4b3..000000000 --- a/tests/mock_tables/socket.py +++ /dev/null @@ -1,73 +0,0 @@ -import os -import sys -from collections import namedtuple -import unittest -from unittest import TestCase, mock -from unittest.mock import patch, mock_open, MagicMock -from enum import Enum - -INPUT_DIR = os.path.dirname(os.path.abspath(__file__)) -modules_path = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) -sys.path.insert(0, os.path.join(modules_path, 'src')) - -import socket - -# Backup original class -_socket_class = socket.socket -_socket_gethostname = socket.gethostname - -# Monkey patch -def MockGetHostname(): - return 'str-msn2700-05' - -class State(Enum): - CLOSED = 0 - BANNER = 1 - INTERACTIVE = 2 - -class MockSocket(_socket_class): - def __init__(self, *args, **kwargs): - super(MockSocket, self).__init__(*args, **kwargs) - self.prompt_hostname = (MockGetHostname() + '> ').encode() - self.state = State.CLOSED - - def connect(self, *args, **kwargs): - self.state = State.BANNER - self._string_sent = b'' - - def send(self, *args, **kwargs): - string = args[0] - self._string_sent = string - pass - - def recv(self, *args, **kwargs): - if self.state == State.CLOSED: - raise OSError("Transport endpoint is not connected") - - if self.state == State.BANNER: - self.state = State.INTERACTIVE - return b'\r\nHello, this is Quagga (version 0.99.24.1).\r\nCopyright 1996-2005 Kunihiro Ishiguro, et al.\r\n\r\n\r\nUser Access Verification\r\n\r\n\xff\xfb\x01\xff\xfb\x03\xff\xfe"\xff\xfd\x1fPassword: ' - - if not self._string_sent or b'\n' not in self._string_sent: - raise socket.timeout - - try: - if self._string_sent == b'zebra\n': - return self.prompt_hostname - elif b'show ip bgp summary\n' in self._string_sent: - filename = INPUT_DIR + '/bgpsummary_ipv4.txt' - elif b'show ipv6 bgp summary\n' in self._string_sent: - filename = INPUT_DIR + '/bgpsummary_ipv6.txt' - else: - return self.prompt_hostname - - with open(filename, 'rb') as f: - ret = f.read() - return ret + b'\r\n' + self.prompt_hostname - finally: - self._string_sent = b'' - -# Replace the function with mocked one -socket.socket = MockSocket -socket.gethostname = MockGetHostname - diff --git a/tests/namespace/test_bgp.py b/tests/namespace/test_bgp.py new file mode 100644 index 000000000..7cd51abf3 --- /dev/null +++ b/tests/namespace/test_bgp.py @@ -0,0 +1,115 @@ +import os +import sys +import importlib +# noinspection PyUnresolvedReferences +import tests.mock_tables.dbconnector + +modules_path = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +sys.path.insert(0, os.path.join(modules_path, 'src')) + +from unittest import TestCase +from unittest.mock import patch, mock_open + +import socket +from ax_interface.mib import MIBTable +from ax_interface.pdu import PDUHeader +from ax_interface.pdu_implementations import GetPDU, GetNextPDU +from ax_interface import ValueType +from ax_interface.encodings import ObjectIdentifier +from ax_interface.constants import PduTypes +from sonic_ax_impl.main import SonicMIB +from sonic_ax_impl.mibs.vendor.cisco import bgp4 + +class TestSonicMIB(TestCase): + @classmethod + def setUpClass(cls): + tests.mock_tables.dbconnector.load_namespace_config() + importlib.reload(bgp4) + cls.lut = MIBTable(bgp4.CiscoBgp4MIB) + for updater in cls.lut.updater_instances: + updater.reinit_data() + updater.update_data() + + def test_getpdu_established(self): + print("hello") + print(bgp4.CiscoBgp4MIB.bgpsession_updater.session_status_list) + oid = ObjectIdentifier(20, 0, 0, 0, (1, 3, 6, 1, 4, 1, 9, 9, 187, 1, 2, 5, 1, 3, 1, 4, 10, 10, 0, 0)) + get_pdu = GetPDU( + header=PDUHeader(1, PduTypes.GET, 16, 0, 42, 0, 0, 0), + oids=[oid] + ) + + encoded = get_pdu.encode() + response = get_pdu.make_response(self.lut) + print(response) + + value0 = response.values[0] + self.assertEqual(value0.type_, ValueType.INTEGER) + self.assertEqual(str(value0.name), str(oid)) + self.assertEqual(value0.data, 6) + + def test_getpdu_idle(self): + oid = ObjectIdentifier(20, 0, 0, 0, (1, 3, 6, 1, 4, 1, 9, 9, 187, 1, 2, 5, 1, 3, 1, 4, 10, 0, 0, 6)) + get_pdu = GetPDU( + header=PDUHeader(1, PduTypes.GET, 16, 0, 42, 0, 0, 0), + oids=[oid] + ) + + encoded = get_pdu.encode() + response = get_pdu.make_response(self.lut) + print(response) + + value0 = response.values[0] + self.assertEqual(value0.type_, ValueType.INTEGER) + self.assertEqual(str(value0.name), str(oid)) + self.assertEqual(value0.data, 1) + + def test_getpdu_active(self): + oid = ObjectIdentifier(20, 0, 0, 0, (1, 3, 6, 1, 4, 1, 9, 9, 187, 1, 2, 5, 1, 3, 2, 16, 254, 192, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 10, 250, 0, 7)) + get_pdu = GetPDU( + header=PDUHeader(1, PduTypes.GET, 16, 0, 42, 0, 0, 0), + oids=[oid] + ) + + encoded = get_pdu.encode() + response = get_pdu.make_response(self.lut) + print(response) + + value0 = response.values[0] + self.assertEqual(value0.type_, ValueType.INTEGER) + self.assertEqual(str(value0.name), str(oid)) + self.assertEqual(value0.data, 3) + + def test_getpdu_disappeared(self): + oid = ObjectIdentifier(20, 0, 0, 0, (1, 3, 6, 1, 4, 1, 9, 9, 187, 1, 2, 5, 1, 3, 2, 16, 252, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x10)) + get_pdu = GetPDU( + header=PDUHeader(1, PduTypes.GET, 16, 0, 42, 0, 0, 0), + oids=[oid] + ) + + encoded = get_pdu.encode() + response = get_pdu.make_response(self.lut) + print(response) + + value0 = response.values[0] + self.assertEqual(value0.type_, ValueType.NO_SUCH_INSTANCE) + + def test_getpdu_established_asic2(self): + oid = ObjectIdentifier(20, 0, 0, 0, (1, 3, 6, 1, 4, 1, 9, 9, 187, 1, 2, 5, 1, 3, 1, 4, 10, 10, 0, 5)) + get_pdu = GetPDU( + header=PDUHeader(1, PduTypes.GET, 16, 0, 42, 0, 0, 0), + oids=[oid] + ) + + encoded = get_pdu.encode() + response = get_pdu.make_response(self.lut) + print(response) + + value0 = response.values[0] + self.assertEqual(value0.type_, ValueType.INTEGER) + self.assertEqual(str(value0.name), str(oid)) + self.assertEqual(value0.data, 6) + + @classmethod + def tearDownClass(cls): + tests.mock_tables.dbconnector.clean_up_config() diff --git a/tests/test_vtysh.py b/tests/test_bgp.py similarity index 87% rename from tests/test_vtysh.py rename to tests/test_bgp.py index 31f3f23f2..27931ae71 100644 --- a/tests/test_vtysh.py +++ b/tests/test_bgp.py @@ -9,7 +9,6 @@ from unittest import TestCase from unittest.mock import patch, mock_open -import socket from ax_interface.mib import MIBTable from ax_interface.pdu import PDUHeader from ax_interface.pdu_implementations import GetPDU, GetNextPDU @@ -18,8 +17,6 @@ from ax_interface.constants import PduTypes from sonic_ax_impl.mibs.ietf import rfc4363 from sonic_ax_impl.main import SonicMIB -from sonic_ax_impl.lib.quaggaclient import parse_bgp_summary -from mock_tables.socket import MockGetHostname from sonic_ax_impl.mibs.vendor.cisco.bgp4 import CiscoBgp4MIB class TestSonicMIB(TestCase): @@ -106,14 +103,3 @@ def test_getpdu_ipv4_overwite_ipv6(self): self.assertEqual(value0.type_, ValueType.INTEGER) self.assertEqual(str(value0.name), str(oid)) self.assertEqual(value0.data, 6) - - def test_parse_no_bgp(self): - filename = INPUT_DIR + '/mock_tables/bgpsummary_ipv6_nobgp.txt' - with open(filename, 'rb') as f: - bgpsu = f.read() - hostname = MockGetHostname() - prompt_hostname = ('\r\n' + hostname + '> ').encode() - bgpsu += prompt_hostname - bgpsu = bgpsu.decode('ascii', 'ignore') - bgpsumm_ipv6 = parse_bgp_summary(bgpsu) - self.assertEqual(bgpsumm_ipv6, []) From ade4e384c34d19d959d5726e14d830225dcf4b18 Mon Sep 17 00:00:00 2001 From: SuvarnaMeenakshi Date: Thu, 27 Aug 2020 11:14:29 -0700 Subject: [PATCH 4/5] Remove perserverant socket and quaggaclient library as it is not required for CiscoBgp4MIB. Signed-off-by: SuvarnaMeenakshi --- src/sonic_ax_impl/lib/perseverantsocket.py | 44 ------ src/sonic_ax_impl/lib/quaggaclient.py | 165 -------------------- src/sonic_ax_impl/mibs/vendor/cisco/bgp4.py | 2 - 3 files changed, 211 deletions(-) delete mode 100644 src/sonic_ax_impl/lib/perseverantsocket.py delete mode 100644 src/sonic_ax_impl/lib/quaggaclient.py diff --git a/src/sonic_ax_impl/lib/perseverantsocket.py b/src/sonic_ax_impl/lib/perseverantsocket.py deleted file mode 100644 index 642533208..000000000 --- a/src/sonic_ax_impl/lib/perseverantsocket.py +++ /dev/null @@ -1,44 +0,0 @@ -import socket - -class PerseverantSocket: - def __init__(self, family=socket.AF_INET, type=socket.SOCK_STREAM, proto=0, fileno=None, address_tuple=None, *args, **kwargs): - self._connected = False - self.address_tuple = address_tuple - self.family = family - self.type = type - self.proto = proto - self.fileno = fileno - self.args = args - self.kwargs = kwargs - self._initsock() - - def _initsock(self): - self.sock = socket.socket(family=self.family, type=self.type, proto=self.proto, fileno=self.fileno, *self.args, **self.kwargs) - self.sock.settimeout(1) - - @property - def connected(self): - return self._connected - - def connect(self, address_tuple): - self.address_tuple = address_tuple - self.sock.connect(self.address_tuple) - - def reconnect(self): - assert self.address_tuple is not None - if self._connected: - self.close() - self.sock.connect(self.address_tuple) - self._connected = True - - def close(self): - self._connected = False - self.sock.close() - self._initsock() - - ## TODO: override __getattr__ to implement auto function call forwarding if not implemented - def send(self, *args, **kwargs): - return self.sock.send(*args, **kwargs) - - def recv(self, *args, **kwargs): - return self.sock.recv(*args, **kwargs) diff --git a/src/sonic_ax_impl/lib/quaggaclient.py b/src/sonic_ax_impl/lib/quaggaclient.py deleted file mode 100644 index a19adc237..000000000 --- a/src/sonic_ax_impl/lib/quaggaclient.py +++ /dev/null @@ -1,165 +0,0 @@ -import re -import ipaddress -import socket - -STATE_CODE = { - "Idle": 1, - "Idle (Admin)": 1, - "Connect": 2, - "Active": 3, - "OpenSent": 4, - "OpenConfirm": 5, - "Established": 6 -}; - -def parse_bgp_summary(summ): - ls = summ.splitlines() - bgpinfo = [] - - ## Read until the table header - n = len(ls) - li = 0 - while li < n: - l = ls[li] - if l.startswith('Neighbor '): - break - if l.startswith('No IPv'): # eg. No IPv6 neighbor is configured, in Quagga (version 0.99.24.1) - return bgpinfo - if l.startswith('% No BGP neighbors found'): # in FRRouting (version 7.2) - return bgpinfo - if (l.endswith('> ') or l.endswith('# ')) and li == n - 1: # empty output followed by prompt, in FRRouting (version 4.0) - return bgpinfo - li += 1 - - ## Read and store the table header - if li >= n: - raise ValueError('No table header found: ' + summ) - hl = ls[li] - li += 1 - ht = re.split('\s+', hl.rstrip()) - hn = len(ht) - - ## Read rows in the table - while li < n: - l = ls[li] - li += 1 - if l == '': break - - ## Handle line wrap - ## ref: bgp_show_summary in https://github.com/Azure/sonic-quagga/blob/debian/0.99.24.1/bgpd/bgp_vty.c - if ' ' not in l: - ## Read next line - if li >= n: - raise ValueError('Unexpected line wrap') - l += ls[li] - li += 1 - - ## Note: State/PfxRcd field may be 'Idle (Admin)' - lt = re.split('\s+', l.rstrip(), maxsplit = hn - 1) - if len(lt) != hn: - raise ValueError('Unexpected row in the table') - dic = dict(zip(ht, lt)) - bgpinfo.append(dic) - return bgpinfo - -def bgp_peer_tuple(dic): - nei = dic['Neighbor'] - ver = dic['V'] - sta = dic['State/PfxRcd'] - - # prefix '*' appears if the entry is a dynamic neighbor - nei = nei[1:] if nei[0] == '*' else nei - ip = ipaddress.ip_address(nei) - if type(ip) is ipaddress.IPv4Address: - oid_head = (1, 4) - else: - oid_head = (2, 16) - - oid_ip = tuple(i for i in ip.packed) - - if sta.isdigit(): - status = 6 - elif sta in STATE_CODE: - status = STATE_CODE[sta] - else: - return None, None - - return oid_head + oid_ip, status - -class QuaggaClient: - HOST = '127.0.0.1' - PORT = 2605 - PROMPT_PASSWORD = b'\x1fPassword: ' - - def __init__(self, sock): - self.sock = sock - self.bgp_provider = 'Quagga' - - def union_bgp_sessions(self): - bgpsumm_ipv4 = self.show_bgp_summary('ip') - sessions_ipv4 = parse_bgp_summary(bgpsumm_ipv4) - - bgpsumm_ipv6 = self.show_bgp_summary('ipv6') - sessions_ipv6 = parse_bgp_summary(bgpsumm_ipv6) - - ## Note: sessions_ipv4 will overwrite sessions_ipv6 if key is the same - neighbor_sessions = {} - for ses in sessions_ipv6 + sessions_ipv4: - nei = ses['Neighbor'] - neighbor_sessions[nei] = ses - return neighbor_sessions - - def auth(self): - ## Nowadays we see 2 BGP stacks - ## 1. Quagga (version 0.99.24.1) - ## 2. FRRouting (version 7.2-sonic) - banner = self.vtysh_recv() - if 'Quagga' in banner: - self.bgp_provider = 'Quagga' - elif 'FRRouting' in banner: - self.bgp_provider = 'FRRouting' - else: - raise ValueError('Unexpected data recv for banner: {0}'.format(banner)) - - ## Send default user credential and receive the prompt - passwd = "zebra" - self.vtysh_run(passwd) - return banner - - def vtysh_run(self, command): - cmd = command.encode() + b'\n' - self.sock.send(cmd) - return self.vtysh_recv() - - def vtysh_recv(self): - acc = b"" - while True: - try: - data = self.sock.recv(1024) - except socket.timeout as e: - raise ValueError('Timeout recv acc=: {0}'.format(acc)) from e - if not data: - raise ValueError('Unexpected data recv acc=: {0}'.format(acc)) - acc += data - ## 1. To match hostname - ## RFC 1123 Section 2.1 - ## First char of hostname must be a letter or a digit - ## Hostname length <= 255 - ## Hostname contains no whitespace characters - ## 2. To match the prompt line - ## The buffer may containers only prompt without return char - ## Or the buffer container some output followed by return char and prompt - if re.search(b'(^|\r\n)[a-zA-Z0-9][\\S]{0,254}[#>] $', acc): - break - if acc.endswith(QuaggaClient.PROMPT_PASSWORD): - break - - return acc.decode('ascii', 'ignore') - - def show_bgp_summary(self, ipver): - assert(ipver in ['ip', 'ipv6']) - if self.bgp_provider == 'Quagga' or ipver == 'ip': - result = self.vtysh_run('show %s bgp summary' % ipver) - elif self.bgp_provider == 'FRRouting' and ipver == 'ipv6': - result = self.vtysh_run('show ip bgp ipv6 summary') - return result diff --git a/src/sonic_ax_impl/mibs/vendor/cisco/bgp4.py b/src/sonic_ax_impl/mibs/vendor/cisco/bgp4.py index d0b9f8f96..b0ff8a7df 100644 --- a/src/sonic_ax_impl/mibs/vendor/cisco/bgp4.py +++ b/src/sonic_ax_impl/mibs/vendor/cisco/bgp4.py @@ -1,8 +1,6 @@ import socket from bisect import bisect_right from sonic_ax_impl import mibs -from sonic_ax_impl.lib.perseverantsocket import PerseverantSocket -from sonic_ax_impl.lib.quaggaclient import QuaggaClient, bgp_peer_tuple from ax_interface import MIBMeta, ValueType, MIBUpdater, SubtreeMIBEntry from ax_interface.mib import MIBEntry from sonic_ax_impl.mibs import Namespace From 99f3b76ee1b37754de5cb2d7224841c18d2de3a8 Mon Sep 17 00:00:00 2001 From: SuvarnaMeenakshi Date: Tue, 8 Sep 2020 07:51:17 -0700 Subject: [PATCH 5/5] Fix as per review comment. Signed-off-by: SuvarnaMeenakshi --- src/sonic_ax_impl/mibs/vendor/cisco/bgp4.py | 39 ++++++++++----------- 1 file changed, 19 insertions(+), 20 deletions(-) diff --git a/src/sonic_ax_impl/mibs/vendor/cisco/bgp4.py b/src/sonic_ax_impl/mibs/vendor/cisco/bgp4.py index b0ff8a7df..33c1806ba 100644 --- a/src/sonic_ax_impl/mibs/vendor/cisco/bgp4.py +++ b/src/sonic_ax_impl/mibs/vendor/cisco/bgp4.py @@ -38,26 +38,25 @@ def update_data(self): neigh_str = neigh_key.decode() neigh_str = neigh_str.split('|')[1] neigh_info = self.db_conn[db_index].get_all(mibs.STATE_DB, neigh_key, blocking=False) - if neigh_info is None: - continue - state = neigh_info[b'state'].decode() - ip = ipaddress.ip_address(neigh_str) - if type(ip) is ipaddress.IPv4Address: - oid_head = (1, 4) - else: - oid_head = (2, 16) - oid_ip = tuple(i for i in ip.packed) - - if state.isdigit(): - status = 6 - elif state in STATE_CODE: - status = STATE_CODE[state] - else: - continue - - oid = oid_head + oid_ip - self.session_status_list.append(oid) - self.session_status_map[oid] = status + if neigh_info is not None: + state = neigh_info[b'state'].decode() + ip = ipaddress.ip_address(neigh_str) + if type(ip) is ipaddress.IPv4Address: + oid_head = (1, 4) + else: + oid_head = (2, 16) + oid_ip = tuple(i for i in ip.packed) + + if state.isdigit(): + status = 6 + elif state in STATE_CODE: + status = STATE_CODE[state] + else: + continue + + oid = oid_head + oid_ip + self.session_status_list.append(oid) + self.session_status_map[oid] = status self.session_status_list.sort()