Skip to content

Commit

Permalink
[debug dump util] Route Module added (sonic-net#1913)
Browse files Browse the repository at this point in the history
* Added the Route Module to the Debug Dump Utility
Signed-off-by: Vivek Reddy Karri <[email protected]>
  • Loading branch information
vivekrnv authored Nov 22, 2021
1 parent ac8382f commit 02a98ef
Show file tree
Hide file tree
Showing 6 changed files with 857 additions and 1 deletion.
85 changes: 84 additions & 1 deletion dump/match_infra.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import json
import fnmatch
import copy
from abc import ABC, abstractmethod
from dump.helper import verbose_print
from swsscommon.swsscommon import SonicV2Connector, SonicDBConfig
Expand Down Expand Up @@ -343,4 +344,86 @@ def fetch(self, req):
verbose_print("Filtered Keys:" + str(filtered_keys))
if not filtered_keys:
return self.__display_error(EXCEP_DICT["NO_ENTRIES"])
return self.__fill_template(src, req, filtered_keys, template)
return self.__fill_template(src, req, filtered_keys, template)


class MatchRequestOptimizer():
"""
A Stateful Wrapper which reduces the number of calls to redis by caching the keys
The Cache saves all the fv-pairs for a key
Caching would only happen when the "key_pattern" is an absolute key and is not a glob-style pattern
"""

def __init__(self, m_engine):
self.__key_cache = {}
self.m_engine = m_engine

def __mutate_request(self, req):
"""
Mutate the Request to fetch all the fv pairs, regardless of the orignal request
Save the return_fields and just_keys args of original request
"""
fv_requested = []
ret_just_keys = req.just_keys
fv_requested = copy.deepcopy(req.return_fields)
if ret_just_keys:
req.just_keys = False
req.return_fields = []
return req, fv_requested, ret_just_keys

def __mutate_response(self, ret, fv_requested, ret_just_keys):
"""
Mutate the Response based on what was originally asked.
"""
if not ret_just_keys:
return ret
new_ret = {"error": "", "keys": [], "return_values": {}}
for key_fv in ret["keys"]:
if isinstance(key_fv, dict):
keys = key_fv.keys()
new_ret["keys"].extend(keys)
for key in keys:
new_ret["return_values"][key] = {}
for field in fv_requested:
new_ret["return_values"][key][field] = key_fv[key][field]
return new_ret

def __fill_cache(self, ret):
"""
Fill the cache with all the fv-pairs
"""
for key_fv in ret["keys"]:
keys = key_fv.keys()
for key in keys:
self.__key_cache[key] = key_fv[key]

def __fetch_from_cache(self, key, req):
"""
Cache will have all the fv-pairs of the requested key
Response will be tailored based on what was asked
"""
new_ret = {"error": "", "keys": [], "return_values": {}}
if not req.just_keys:
new_ret["keys"].append(self.__key_cache[key])
else:
new_ret["keys"].append(key)
if req.return_fields:
new_ret["return_values"][key] = {}
for field in req.return_fields:
new_ret["return_values"][key][field] = self.__key_cache[key][field]
return new_ret

def fetch(self, req_orig):
req = copy.deepcopy(req_orig)
key = req.table + ":" + req.key_pattern
if key in self.__key_cache:
verbose_print("Cache Hit for Key: {}".format(key))
return self.__fetch_from_cache(key, req)
else:
verbose_print("Cache Miss for Key: {}".format(key))
req, fv_requested, ret_just_keys = self.__mutate_request(req)
ret = self.m_engine.fetch(req)
if ret["error"]:
return ret
self.__fill_cache(ret)
return self.__mutate_response(ret, fv_requested, ret_just_keys)
271 changes: 271 additions & 0 deletions dump/plugins/route.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,271 @@
import json
import re
from dump.match_infra import MatchRequest, MatchRequestOptimizer
from dump.helper import create_template_dict
from .executor import Executor

NH = "ASIC_STATE:SAI_OBJECT_TYPE_NEXT_HOP"
NH_GRP = "ASIC_STATE:SAI_OBJECT_TYPE_NEXT_HOP_GROUP"
RIF = "ASIC_STATE:SAI_OBJECT_TYPE_ROUTER_INTERFACE"
CPU_PORT = "ASIC_STATE:SAI_OBJECT_TYPE_PORT"

OID_HEADERS = {
NH: "0x40",
NH_GRP: "0x50",
RIF: "0x60",
CPU_PORT: "0x10"
}


def get_route_pattern(dest):
return "*\"dest\":\"" + dest + "\"*"


def get_vr_oid(asic_route_entry):
"""
Route Entry Format: ASIC_STATE:SAI_OBJECT_TYPE_ROUTE_ENTRY:
{'dest':'::0','switch_id':'oid:0x21000000000000','vr':'oid:0x3000000000002'}
"""
matches = re.findall(r"\{.*\}", asic_route_entry)
key_dict = {}
if matches:
try:
key_dict = json.loads(matches[0])
except Exception as e:
pass
return key_dict.get("vr", "")


class Route(Executor):
"""
Debug Dump Plugin for Route Module
"""
ARG_NAME = "destination_network"

def __init__(self, match_engine=None):
super().__init__(match_engine)
self.nhgrp_match_engine = MatchRequestOptimizer(self.match_engine)
"""
MatchRequestOptimizer will be used for the keys related to these tables
1) SAI_OBJECT_TYPE_NEXT_HOP_GROUP_MEMBER
2) SAI_OBJECT_TYPE_NEXT_HOP
3) SAI_OBJECT_TYPE_ROUTER_INTERFACE
4) CLASS_BASED_NEXT_HOP_GROUP_TABLE
5) NEXTHOP_GROUP_TABLE
"""
self.ret_temp = {}
self.ns = ''
self.dest_net = ''
self.nh_id = ''
self.nh_type = ''

def get_all_args(self, ns=""):
req = MatchRequest(db="APPL_DB", table="ROUTE_TABLE", key_pattern="*", ns=self.ns)
ret = self.match_engine.fetch(req)
all_routes = ret.get("keys", [])
return [key[len("ROUTE_TABLE:"):] for key in all_routes]

def execute(self, params):
self.ret_temp = create_template_dict(dbs=["CONFIG_DB", "APPL_DB", "ASIC_DB"])
self.dest_net = params[Route.ARG_NAME]
self.ns = params["namespace"]
# CONFIG DB
if not self.init_route_config_info():
del self.ret_temp["CONFIG_DB"]
# APPL DB
nhgrp_field = self.init_route_appl_info()
self.init_nhgrp_cbf_appl_info(nhgrp_field)
# ASIC DB - ROUTE ENTRY
self.nh_id, vr = self.init_asic_route_entry_info()
# ASIC DB - VIRTUAL ROUTER
self.init_asic_vr_info(vr)
# ASIC DB - KEYS dependent on NEXT HOP ID
self.init_asic_nh()
return self.ret_temp

def add_to_ret_template(self, table, db, keys, err, add_to_tables_not_found=True):
if not err and keys:
self.ret_temp[db]["keys"].extend(keys)
return keys
elif add_to_tables_not_found:
self.ret_temp[db]["tables_not_found"].extend([table])
return []

def init_route_config_info(self):
req = MatchRequest(db="CONFIG_DB", table="STATIC_ROUTE", key_pattern=self.dest_net, ns=self.ns)
ret = self.match_engine.fetch(req)
return self.add_to_ret_template(req.table, req.db, ret["keys"], ret["error"])

def init_route_appl_info(self):
req = MatchRequest(db="APPL_DB", table="ROUTE_TABLE", key_pattern=self.dest_net,
ns=self.ns, return_fields=["nexthop_group"])
ret = self.match_engine.fetch(req)
self.add_to_ret_template(req.table, req.db, ret["keys"], ret["error"])
if ret["keys"]:
return ret["return_values"].get(ret["keys"][0], {}).get("nexthop_group", "")
return ""

def init_nhgrp_cbf_appl_info(self, nhgrp_field):
if not nhgrp_field:
return

# Verify if the nhgrp field in the route table refers to class based next_hop_group
req = MatchRequest(db="APPL_DB", table="CLASS_BASED_NEXT_HOP_GROUP_TABLE", key_pattern=nhgrp_field,
ns=self.ns, return_fields=["members"])
ret = self.nhgrp_match_engine.fetch(req)
self.add_to_ret_template(req.table, req.db, ret["keys"], ret["error"], False)

nggrp_table_key = ""
if not ret["keys"]:
nggrp_table_key = nhgrp_field
else:
nggrp_table_key = ret["return_values"].get(ret["keys"][0], {}).get("members", "")

if nggrp_table_key:
# Retrieve the next_hop_group key
req = MatchRequest(db="APPL_DB", table="NEXTHOP_GROUP_TABLE", key_pattern=nggrp_table_key, ns=self.ns)
ret = self.nhgrp_match_engine.fetch(req)
self.add_to_ret_template(req.table, req.db, ret["keys"], ret["error"], False)

def init_asic_route_entry_info(self):
nh_id_field = "SAI_ROUTE_ENTRY_ATTR_NEXT_HOP_ID"
req = MatchRequest(db="ASIC_DB", table="ASIC_STATE:SAI_OBJECT_TYPE_ROUTE_ENTRY", key_pattern=get_route_pattern(self.dest_net),
ns=self.ns, return_fields=[nh_id_field])
ret = self.match_engine.fetch(req)
keys = self.add_to_ret_template(req.table, req.db, ret["keys"], ret["error"])
asic_route_entry = keys[0] if keys else ""
vr = get_vr_oid(asic_route_entry)
nh_id = ret["return_values"].get(asic_route_entry, {}).get(nh_id_field, "")
return nh_id, vr

def init_asic_vr_info(self, vr):
ret = {}
if vr:
req = MatchRequest(db="ASIC_DB", table="ASIC_STATE:SAI_OBJECT_TYPE_VIRTUAL_ROUTER", key_pattern=vr, ns=self.ns)
ret = self.nhgrp_match_engine.fetch(req)
self.add_to_ret_template("ASIC_STATE:SAI_OBJECT_TYPE_VIRTUAL_ROUTER", "ASIC_DB", ret.get("keys", []), ret.get("error", ""))

def init_asic_nh(self):
self.nh_type = self.get_nh_type()
nh_ex = NHExtractor.initialize(self)
nh_ex.collect()

def get_nh_type(self):
"""
Figure out the nh_type using OID Header
"""
if not self.nh_id:
return "DROP"
oid = self.nh_id.split(":")[-1]
for nh_type in [NH_GRP, NH, RIF, CPU_PORT]:
if oid.startswith(OID_HEADERS.get(nh_type, "")):
return nh_type
return "DROP"


class NHExtractor(object):
"""
Base Class for NH_ID Type
"""
@staticmethod
def initialize(route_obj):
if route_obj.nh_type == NH:
return SingleNextHop(route_obj)
elif route_obj.nh_type == NH_GRP:
return MultipleNextHop(route_obj)
elif route_obj.nh_type == RIF:
return DirecAttachedRt(route_obj)
elif route_obj.nh_type == CPU_PORT:
return CPUPort(route_obj)
return NHExtractor(route_obj)

def __init__(self, route_obj):
self.rt = route_obj

def collect(self):
pass

def init_asic_rif_info(self, oid, add_to_tables_not_found=True):
ret = {}
if oid:
req = MatchRequest(db="ASIC_DB", table=RIF, key_pattern=oid, ns=self.rt.ns)
ret = self.rt.nhgrp_match_engine.fetch(req)
return self.rt.add_to_ret_template(RIF, "ASIC_DB", ret.get("keys", []), ret.get("error", ""), add_to_tables_not_found)

def init_asic_next_hop_info(self, oid, add_to_tables_not_found=True):
ret = {}
if oid:
req = MatchRequest(db="ASIC_DB", table=NH, key_pattern=oid, ns=self.rt.ns,
return_fields=["SAI_NEXT_HOP_ATTR_ROUTER_INTERFACE_ID"])
ret = self.rt.nhgrp_match_engine.fetch(req)
keys = self.rt.add_to_ret_template(NH, "ASIC_DB", ret.get("keys", []), ret.get("error", ""), add_to_tables_not_found)
nh_key = keys[0] if keys else ""
return ret.get("return_values", {}).get(nh_key, {}).get("SAI_NEXT_HOP_ATTR_ROUTER_INTERFACE_ID", "")


class CPUPort(NHExtractor):
def collect(self):
req = MatchRequest(db="ASIC_DB", table=CPU_PORT, key_pattern=self.rt.nh_id, ns=self.rt.ns)
ret = self.rt.nhgrp_match_engine.fetch(req)
self.rt.add_to_ret_template(req.table, req.db, ret["keys"], ret["error"])


class DirecAttachedRt(NHExtractor):
def collect(self):
self.init_asic_rif_info(self.rt.nh_id)


class SingleNextHop(NHExtractor):
def collect(self):
rif_oid = self.init_asic_next_hop_info(self.rt.nh_id)
self.init_asic_rif_info(rif_oid)


class MultipleNextHop(NHExtractor):
def collect(self):
# Save nh_grp related keys
self.init_asic_nh_group_info(self.rt.nh_id)
# Save nh_grp_members info and fetch nh_oids
nh_oids = self.init_asic_nh_group_members_info(self.rt.nh_id)
# Save the actual next_hop using the nh_oids retrieved, fetch rif oid's if any
rif_oids = self.init_asic_next_hops_info(nh_oids)
# Save the rif_oid related ASIC keys
self.init_asic_rifs_info(rif_oids)

def init_asic_nh_group_info(self, oid):
ret = {}
if oid:
req = MatchRequest(db="ASIC_DB", table=NH_GRP, key_pattern=oid, ns=self.rt.ns)
ret = self.rt.nhgrp_match_engine.fetch(req)
self.rt.add_to_ret_template(NH_GRP, "ASIC_DB", ret.get("keys", []), ret.get("error", ""))

def init_asic_nh_group_members_info(self, oid):
ret = {}
if oid:
req = MatchRequest(db="ASIC_DB", table="ASIC_STATE:SAI_OBJECT_TYPE_NEXT_HOP_GROUP_MEMBER",
field="SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_GROUP_ID", value=oid, ns=self.rt.ns,
return_fields=["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_ID"])
ret = self.rt.nhgrp_match_engine.fetch(req)
keys = self.rt.add_to_ret_template("ASIC_STATE:SAI_OBJECT_TYPE_NEXT_HOP_GROUP_MEMBER", "ASIC_DB",
ret.get("keys", []), ret.get("error", ""), False)
if not keys:
self.rt.ret_temp["ASIC_DB"]["tables_not_found"].append("ASIC_STATE:SAI_OBJECT_TYPE_NEXT_HOP_GROUP_MEMBER")
return [ret.get("return_values", {}).get(key, {}).get("SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_ID", "") for key in keys]

def init_asic_next_hops_info(self, nh_oids):
rif_oids = []
for oid in nh_oids:
rif_oid = self.init_asic_next_hop_info(oid, False)
if rif_oid:
rif_oids.append(rif_oid)
if not rif_oids:
self.rt.ret_temp["ASIC_DB"]["tables_not_found"].append("ASIC_STATE:SAI_OBJECT_TYPE_NEXT_HOP")
return rif_oids

def init_asic_rifs_info(self, rif_oids):
nothing_found = True
for oid in rif_oids:
if self.init_asic_rif_info(oid, False):
nothing_found = False
if nothing_found:
self.rt.ret_temp["ASIC_DB"]["tables_not_found"].append("ASIC_STATE:SAI_OBJECT_TYPE_ROUTER_INTERFACE")
Loading

0 comments on commit 02a98ef

Please sign in to comment.