Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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):
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment to document expectations of route entry format

"""
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):
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could this be made a method on the Executor class, to avoid needing to redefine each time?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's another such refactoring request here: #1853 (comment),

I'll also do this after once the Route and VxLan modules are merged. It'll be cleaner that way

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