Skip to content

Commit

Permalink
Implement ipCidrRouteDest oid (only default routes) (sonic-net#4)
Browse files Browse the repository at this point in the history
* Implement ipCidrRouteDest oid (default routes)

* (doc)

* Fix test cases

* Clean commented code, refine code on default route missing case, fix MockRedis
  • Loading branch information
qiluo-msft authored Feb 2, 2017
1 parent fabc441 commit 5c30a36
Show file tree
Hide file tree
Showing 9 changed files with 210 additions and 14 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
2 changes: 1 addition & 1 deletion src/ax_interface/encodings.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 2 additions & 1 deletion src/sonic_ax_impl/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -24,6 +24,7 @@ class SonicMIB(
rfc1213.InterfacesMIB,
rfc2863.InterfaceMIBObjects,
rfc4363.QBridgeMIBObjects,
rfc4292.IpCidrRouteTable,
ieee802_1ab.LLDPLocPortTable,
ieee802_1ab.LLDPRemTable,
):
Expand Down
64 changes: 64 additions & 0 deletions src/sonic_ax_impl/mibs/ietf/rfc4292.py
Original file line number Diff line number Diff line change
@@ -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)
1 change: 0 additions & 1 deletion src/sonic_ax_impl/mibs/ietf/rfc4363.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
4 changes: 4 additions & 0 deletions tests/mock_tables/appl_db.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
}
26 changes: 24 additions & 2 deletions tests/mock_tables/dbconnector.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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
redis.StrictRedis = SSWSyncClient
9 changes: 0 additions & 9 deletions tests/test_fdb.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -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)
Expand All @@ -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))
Expand All @@ -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)

Expand All @@ -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)

114 changes: 114 additions & 0 deletions tests/test_forward.py
Original file line number Diff line number Diff line change
@@ -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)

0 comments on commit 5c30a36

Please sign in to comment.