From 5c30a369a94b9312a8858c3bc9a3db083d835900 Mon Sep 17 00:00:00 2001 From: Qi Luo Date: Thu, 2 Feb 2017 12:00:25 -0800 Subject: [PATCH] Implement ipCidrRouteDest oid (only default routes) (#4) * Implement ipCidrRouteDest oid (default routes) * (doc) * Fix test cases * Clean commented code, refine code on default route missing case, fix MockRedis --- README.md | 1 + src/ax_interface/encodings.py | 2 +- src/sonic_ax_impl/main.py | 3 +- src/sonic_ax_impl/mibs/ietf/rfc4292.py | 64 ++++++++++++++ src/sonic_ax_impl/mibs/ietf/rfc4363.py | 1 - tests/mock_tables/appl_db.json | 4 + tests/mock_tables/dbconnector.py | 26 +++++- tests/test_fdb.py | 9 -- tests/test_forward.py | 114 +++++++++++++++++++++++++ 9 files changed, 210 insertions(+), 14 deletions(-) create mode 100644 src/sonic_ax_impl/mibs/ietf/rfc4292.py create mode 100644 tests/test_forward.py diff --git a/README.md b/README.md index f643501450cf..76b1efb58471 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,7 @@ MIB implementations included: * [RFC 1213](https://www.ietf.org/rfc/rfc1213.txt) MIB-II * [RFC 2863](https://www.ietf.org/rfc/rfc2863.txt) Interfaces MIB +* [RFC 4292](https://tools.ietf.org/html/rfc4292) ipCidrRouteDest table in IP Forwarding Table MIB * [RFC 4363](https://tools.ietf.org/html/rfc4363) dot1qTpFdbPort in Q-BRIDGE-MIB * [IEEE 802.1 AB](http://www.ieee802.org/1/files/public/MIBs/LLDP-MIB-200505060000Z.txt) LLDP-MIB diff --git a/src/ax_interface/encodings.py b/src/ax_interface/encodings.py index a45e5ff85f09..0d6b616d21d8 100644 --- a/src/ax_interface/encodings.py +++ b/src/ax_interface/encodings.py @@ -241,7 +241,7 @@ def size(self): def from_typecast(cls, type_, oid_iter_or_obj, data): oid = ObjectIdentifier.from_iterable(oid_iter_or_obj) \ if type(oid_iter_or_obj) is not ObjectIdentifier else oid_iter_or_obj - if type_ == constants.ValueType.OCTET_STRING: + if type_ in cls.OCTET_STRINGS: _data = OctetString.from_string(data) elif type_ == constants.ValueType.OBJECT_IDENTIFIER: _data = ObjectIdentifier.from_iterable(data) if type(data) is not ObjectIdentifier else data diff --git a/src/sonic_ax_impl/main.py b/src/sonic_ax_impl/main.py index 33bd842b9fe9..b01e90a3bcdc 100644 --- a/src/sonic_ax_impl/main.py +++ b/src/sonic_ax_impl/main.py @@ -11,7 +11,7 @@ import ax_interface from sonic_ax_impl.mibs import ieee802_1ab from . import logger -from .mibs.ietf import rfc1213, rfc2863, rfc4363 +from .mibs.ietf import rfc1213, rfc2863, rfc4292, rfc4363 # Background task update frequency ( in seconds ) DEFAULT_UPDATE_FREQUENCY = 5 @@ -24,6 +24,7 @@ class SonicMIB( rfc1213.InterfacesMIB, rfc2863.InterfaceMIBObjects, rfc4363.QBridgeMIBObjects, + rfc4292.IpCidrRouteTable, ieee802_1ab.LLDPLocPortTable, ieee802_1ab.LLDPRemTable, ): diff --git a/src/sonic_ax_impl/mibs/ietf/rfc4292.py b/src/sonic_ax_impl/mibs/ietf/rfc4292.py new file mode 100644 index 000000000000..d5e655491649 --- /dev/null +++ b/src/sonic_ax_impl/mibs/ietf/rfc4292.py @@ -0,0 +1,64 @@ +import json +import ipaddress +from enum import unique, Enum + +from sonic_ax_impl import mibs +from ax_interface import MIBMeta, ValueType, MIBUpdater, ContextualMIBEntry, SubtreeMIBEntry +from ax_interface.encodings import OctetString +from ax_interface.util import mac_decimals +from bisect import bisect_right + +def ip2tuple(ip): + return tuple(int(bs) for bs in str(ip).split('.')) + +class RouteUpdater(MIBUpdater): + def __init__(self): + super().__init__() + self.db_conn, _, _, _, _, _ = mibs.init_sync_d_interface_tables() + # call our update method once to "seed" data before the "Agent" starts accepting requests. + self.update_data() + + def update_data(self): + """ + Update redis (caches config) + Pulls the table references for each interface. + """ + self.route_dest_map = {} + self.route_dest_list = [] + + self.db_conn.connect(mibs.APPL_DB) + route_entries = self.db_conn.keys(mibs.APPL_DB, "ROUTE_TABLE:*") + + for route_entry in route_entries: + routestr = route_entry.decode() + ipnstr = routestr[len("ROUTE_TABLE:"):] + if ipnstr == "0.0.0.0/0": + ipn = ipaddress.ip_network(ipnstr) + ent = self.db_conn.get_all(mibs.APPL_DB, routestr, blocking=True) + nexthops = ent[b"nexthop"].decode() + for nh in nexthops.split(','): + sub_id = ip2tuple(ipn.network_address) + ip2tuple(ipn.netmask) + ip2tuple(nh) + self.route_dest_list.append(sub_id) + self.route_dest_map[sub_id] = ipn.network_address.packed + + self.route_dest_list.sort() + + def route_dest(self, sub_id): + return self.route_dest_map.get(sub_id, None) + + def get_next(self, sub_id): + right = bisect_right(self.route_dest_list, sub_id) + if right >= len(self.route_dest_list): + return None + + return self.route_dest_list[right] + +class IpCidrRouteTable(metaclass=MIBMeta, prefix='.1.3.6.1.2.1.4.24.4'): + """ + 'ipCidrRouteDest table in IP Forwarding Table MIB' https://tools.ietf.org/html/rfc4292 + """ + + route_updater = RouteUpdater() + + ipCidrRouteDest = \ + SubtreeMIBEntry('1.1', route_updater, ValueType.IP_ADDRESS, route_updater.route_dest) diff --git a/src/sonic_ax_impl/mibs/ietf/rfc4363.py b/src/sonic_ax_impl/mibs/ietf/rfc4363.py index aa65545225e2..d7cf781531f3 100644 --- a/src/sonic_ax_impl/mibs/ietf/rfc4363.py +++ b/src/sonic_ax_impl/mibs/ietf/rfc4363.py @@ -38,7 +38,6 @@ def update_data(self): fdb = json.loads(fdb_str.split(":", maxsplit=2)[-1]) except ValueError as e: # includes simplejson.decoder.JSONDecodeError mibs.logger.error("SyncD 'ASIC_DB' includes invalid FDB_ENTRY '{}': {}.".format(fdb_str, e)) - self.vlanmac_ifindex_map = {} break ent = self.db_conn.get_all(mibs.ASIC_DB, s, blocking=True) diff --git a/tests/mock_tables/appl_db.json b/tests/mock_tables/appl_db.json index 40e21c9d7737..893f3534e7bb 100644 --- a/tests/mock_tables/appl_db.json +++ b/tests/mock_tables/appl_db.json @@ -328,5 +328,9 @@ "lldp_rem_chassis_id_subtype": 4, "lldp_rem_sys_name": "switch13", "lldp_rem_port_id": "Ethernet4" + }, + "ROUTE_TABLE:0.0.0.0/0": { + "ifname": "Ethernet0,Ethernet4,Ethernet8,Ethernet12,Ethernet16,Ethernet20,Ethernet24,Ethernet28,Ethernet32,Ethernet36,Ethernet40,Ethernet44,Ethernet48,Ethernet52", + "nexthop": "10.0.0.1,10.0.0.3,10.0.0.5,10.0.0.7,10.0.0.9,10.0.0.11,10.0.0.13,10.0.0.15,10.0.0.17,10.0.0.19,10.0.0.21,10.0.0.23,10.0.0.25,10.0.0.27" } } diff --git a/tests/mock_tables/dbconnector.py b/tests/mock_tables/dbconnector.py index 0e01d5604813..4100afeb9115 100644 --- a/tests/mock_tables/dbconnector.py +++ b/tests/mock_tables/dbconnector.py @@ -17,7 +17,6 @@ def config_set(self, *args): INPUT_DIR = os.path.dirname(os.path.abspath(__file__)) - class SSWSyncClient(mockredis.MockRedis): def __init__(self, *args, **kwargs): super(SSWSyncClient, self).__init__(strict=True, *args, **kwargs) @@ -41,7 +40,30 @@ def __init__(self, *args, **kwargs): for k, v in table.items(): self.hset(h, k, v) + # Patch mockredis/mockredis/client.py + # The official implementation will filter out keys with a slash '/' + # ref: https://github.com/locationlabs/mockredis/blob/master/mockredis/client.py + def keys(self, pattern='*'): + """Emulate keys.""" + import fnmatch + import re + + # making sure the pattern is unicode/str. + try: + pattern = pattern.decode('utf-8') + # This throws an AttributeError in python 3, or an + # UnicodeEncodeError in python 2 + except (AttributeError, UnicodeEncodeError): + pass + + # Make regex out of glob styled pattern. + regex = fnmatch.translate(pattern) + regex = re.compile(regex) + + # Find every key that matches the pattern + return [key for key in self.redis.keys() if regex.match(key.decode('utf-8'))] + sswsdk.interface.DBInterface._subscribe_keyspace_notification = _subscribe_keyspace_notification mockredis.MockRedis.config_set = config_set -redis.StrictRedis = SSWSyncClient \ No newline at end of file +redis.StrictRedis = SSWSyncClient diff --git a/tests/test_fdb.py b/tests/test_fdb.py index a043f4ab4937..76b999ad5a0a 100644 --- a/tests/test_fdb.py +++ b/tests/test_fdb.py @@ -23,11 +23,6 @@ class TestSonicMIB(TestCase): def setUpClass(cls): cls.lut = MIBTable(SonicMIB) - # def test_print_oids(self): - # for k in self.lut.keys(): - # print(k) - #mib_entry = self.lut[(1, 3, 6, 1, 2, 1, 17, 7, 1, 2, 2, 1, 2, 1000, 124, 254, 144, 128, 159, 92)] - def test_getpdu(self): oid = ObjectIdentifier(20, 0, 0, 0, (1, 3, 6, 1, 2, 1, 17, 7, 1, 2, 2, 1, 2, 1000, 124, 254, 144, 128, 159, 92)) get_pdu = GetPDU( @@ -57,7 +52,6 @@ def test_getnextpdu(self): print(response) n = len(response.values) - # self.assertEqual(n, 7) value0 = response.values[0] self.assertEqual(value0.type_, ValueType.INTEGER) self.assertEqual(value0.data, 49) @@ -75,7 +69,6 @@ def test_getnextpdu_exactmatch(self): print(response) n = len(response.values) - # self.assertEqual(n, 7) value0 = response.values[0] self.assertEqual(value0.type_, ValueType.INTEGER) print("test_getnextpdu_exactmatch: ", str(oid)) @@ -95,7 +88,6 @@ def test_getpdu_noinstance(self): print(response) n = len(response.values) - # self.assertEqual(n, 7) value0 = response.values[0] self.assertEqual(value0.type_, ValueType.NO_SUCH_INSTANCE) @@ -112,7 +104,6 @@ def test_getnextpdu_empty(self): print(response) n = len(response.values) - # self.assertEqual(n, 7) value0 = response.values[0] self.assertEqual(value0.type_, ValueType.END_OF_MIB_VIEW) diff --git a/tests/test_forward.py b/tests/test_forward.py new file mode 100644 index 000000000000..4219dc934bb7 --- /dev/null +++ b/tests/test_forward.py @@ -0,0 +1,114 @@ +import os +import sys +import ipaddress + +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 + +import tests.mock_tables.dbconnector + +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.mibs.ietf import rfc4363 +from sonic_ax_impl.main import SonicMIB + +class TestForwardMIB(TestCase): + @classmethod + def setUpClass(cls): + cls.lut = MIBTable(SonicMIB) + + def test_network_order(self): + ip = ipaddress.ip_address("0.1.2.3") + ipb = ip.packed + ips = ".".join(str(int(x)) for x in list(ipb)) + self.assertEqual(ips, "0.1.2.3") + + def test_getpdu(self): + oid = ObjectIdentifier(23, 0, 1, 0, (1, 3, 6, 1, 2, 1, 4, 24, 4, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 10, 0, 0, 15)) + 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.IP_ADDRESS) + self.assertEqual(str(value0.name), str(oid)) + self.assertEqual(str(value0.data), ipaddress.ip_address("0.0.0.0").packed.decode()) + + def test_getnextpdu(self): + get_pdu = GetNextPDU( + header=PDUHeader(1, PduTypes.GET, 16, 0, 42, 0, 0, 0), + oids=( + ObjectIdentifier(20, 0, 0, 0, (1, 3, 6, 1, 2, 1, 4, 24, 4, 1, 1)), + ) + ) + + encoded = get_pdu.encode() + response = get_pdu.make_response(self.lut) + print(response) + + n = len(response.values) + value0 = response.values[0] + self.assertEqual(value0.type_, ValueType.IP_ADDRESS) + self.assertEqual(str(value0.data), ipaddress.ip_address("0.0.0.0").packed.decode()) + + def test_getnextpdu_exactmatch(self): + oid = ObjectIdentifier(23, 0, 1, 0, (1, 3, 6, 1, 2, 1, 4, 24, 4, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 10, 0, 0, 17)) + get_pdu = GetNextPDU( + 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) + + n = len(response.values) + value0 = response.values[0] + self.assertEqual(value0.type_, ValueType.IP_ADDRESS) + print("test_getnextpdu_exactmatch: ", str(oid)) + self.assertEqual(str(value0.name), str(oid)) + self.assertEqual(str(value0.data), ipaddress.ip_address("0.0.0.0").packed.decode()) + + def test_getpdu_noinstance(self): + get_pdu = GetPDU( + header=PDUHeader(1, PduTypes.GET, 16, 0, 42, 0, 0, 0), + oids=( + ObjectIdentifier(19, 0, 0, 0, (1, 3, 6, 1, 2, 1, 4, 24, 4, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1)), + ) + ) + + encoded = get_pdu.encode() + response = get_pdu.make_response(self.lut) + print(response) + + n = len(response.values) + value0 = response.values[0] + self.assertEqual(value0.type_, ValueType.NO_SUCH_INSTANCE) + + def test_getnextpdu_empty(self): + get_pdu = GetNextPDU( + header=PDUHeader(1, PduTypes.GET, 16, 0, 42, 0, 0, 0), + oids=( + ObjectIdentifier(11, 0, 0, 0, (1, 3, 6, 1, 2, 1, 4, 24, 4, 1, 2)), + ) + ) + + encoded = get_pdu.encode() + response = get_pdu.make_response(self.lut) + print(response) + + n = len(response.values) + value0 = response.values[0] + self.assertEqual(value0.type_, ValueType.END_OF_MIB_VIEW) +