Skip to content

Commit 02a98ef

Browse files
authored
[debug dump util] Route Module added (#1913)
* Added the Route Module to the Debug Dump Utility Signed-off-by: Vivek Reddy Karri <[email protected]>
1 parent ac8382f commit 02a98ef

File tree

6 files changed

+857
-1
lines changed

6 files changed

+857
-1
lines changed

dump/match_infra.py

+84-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import json
22
import fnmatch
3+
import copy
34
from abc import ABC, abstractmethod
45
from dump.helper import verbose_print
56
from swsscommon.swsscommon import SonicV2Connector, SonicDBConfig
@@ -343,4 +344,86 @@ def fetch(self, req):
343344
verbose_print("Filtered Keys:" + str(filtered_keys))
344345
if not filtered_keys:
345346
return self.__display_error(EXCEP_DICT["NO_ENTRIES"])
346-
return self.__fill_template(src, req, filtered_keys, template)
347+
return self.__fill_template(src, req, filtered_keys, template)
348+
349+
350+
class MatchRequestOptimizer():
351+
"""
352+
A Stateful Wrapper which reduces the number of calls to redis by caching the keys
353+
The Cache saves all the fv-pairs for a key
354+
Caching would only happen when the "key_pattern" is an absolute key and is not a glob-style pattern
355+
"""
356+
357+
def __init__(self, m_engine):
358+
self.__key_cache = {}
359+
self.m_engine = m_engine
360+
361+
def __mutate_request(self, req):
362+
"""
363+
Mutate the Request to fetch all the fv pairs, regardless of the orignal request
364+
Save the return_fields and just_keys args of original request
365+
"""
366+
fv_requested = []
367+
ret_just_keys = req.just_keys
368+
fv_requested = copy.deepcopy(req.return_fields)
369+
if ret_just_keys:
370+
req.just_keys = False
371+
req.return_fields = []
372+
return req, fv_requested, ret_just_keys
373+
374+
def __mutate_response(self, ret, fv_requested, ret_just_keys):
375+
"""
376+
Mutate the Response based on what was originally asked.
377+
"""
378+
if not ret_just_keys:
379+
return ret
380+
new_ret = {"error": "", "keys": [], "return_values": {}}
381+
for key_fv in ret["keys"]:
382+
if isinstance(key_fv, dict):
383+
keys = key_fv.keys()
384+
new_ret["keys"].extend(keys)
385+
for key in keys:
386+
new_ret["return_values"][key] = {}
387+
for field in fv_requested:
388+
new_ret["return_values"][key][field] = key_fv[key][field]
389+
return new_ret
390+
391+
def __fill_cache(self, ret):
392+
"""
393+
Fill the cache with all the fv-pairs
394+
"""
395+
for key_fv in ret["keys"]:
396+
keys = key_fv.keys()
397+
for key in keys:
398+
self.__key_cache[key] = key_fv[key]
399+
400+
def __fetch_from_cache(self, key, req):
401+
"""
402+
Cache will have all the fv-pairs of the requested key
403+
Response will be tailored based on what was asked
404+
"""
405+
new_ret = {"error": "", "keys": [], "return_values": {}}
406+
if not req.just_keys:
407+
new_ret["keys"].append(self.__key_cache[key])
408+
else:
409+
new_ret["keys"].append(key)
410+
if req.return_fields:
411+
new_ret["return_values"][key] = {}
412+
for field in req.return_fields:
413+
new_ret["return_values"][key][field] = self.__key_cache[key][field]
414+
return new_ret
415+
416+
def fetch(self, req_orig):
417+
req = copy.deepcopy(req_orig)
418+
key = req.table + ":" + req.key_pattern
419+
if key in self.__key_cache:
420+
verbose_print("Cache Hit for Key: {}".format(key))
421+
return self.__fetch_from_cache(key, req)
422+
else:
423+
verbose_print("Cache Miss for Key: {}".format(key))
424+
req, fv_requested, ret_just_keys = self.__mutate_request(req)
425+
ret = self.m_engine.fetch(req)
426+
if ret["error"]:
427+
return ret
428+
self.__fill_cache(ret)
429+
return self.__mutate_response(ret, fv_requested, ret_just_keys)

dump/plugins/route.py

+271
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,271 @@
1+
import json
2+
import re
3+
from dump.match_infra import MatchRequest, MatchRequestOptimizer
4+
from dump.helper import create_template_dict
5+
from .executor import Executor
6+
7+
NH = "ASIC_STATE:SAI_OBJECT_TYPE_NEXT_HOP"
8+
NH_GRP = "ASIC_STATE:SAI_OBJECT_TYPE_NEXT_HOP_GROUP"
9+
RIF = "ASIC_STATE:SAI_OBJECT_TYPE_ROUTER_INTERFACE"
10+
CPU_PORT = "ASIC_STATE:SAI_OBJECT_TYPE_PORT"
11+
12+
OID_HEADERS = {
13+
NH: "0x40",
14+
NH_GRP: "0x50",
15+
RIF: "0x60",
16+
CPU_PORT: "0x10"
17+
}
18+
19+
20+
def get_route_pattern(dest):
21+
return "*\"dest\":\"" + dest + "\"*"
22+
23+
24+
def get_vr_oid(asic_route_entry):
25+
"""
26+
Route Entry Format: ASIC_STATE:SAI_OBJECT_TYPE_ROUTE_ENTRY:
27+
{'dest':'::0','switch_id':'oid:0x21000000000000','vr':'oid:0x3000000000002'}
28+
"""
29+
matches = re.findall(r"\{.*\}", asic_route_entry)
30+
key_dict = {}
31+
if matches:
32+
try:
33+
key_dict = json.loads(matches[0])
34+
except Exception as e:
35+
pass
36+
return key_dict.get("vr", "")
37+
38+
39+
class Route(Executor):
40+
"""
41+
Debug Dump Plugin for Route Module
42+
"""
43+
ARG_NAME = "destination_network"
44+
45+
def __init__(self, match_engine=None):
46+
super().__init__(match_engine)
47+
self.nhgrp_match_engine = MatchRequestOptimizer(self.match_engine)
48+
"""
49+
MatchRequestOptimizer will be used for the keys related to these tables
50+
1) SAI_OBJECT_TYPE_NEXT_HOP_GROUP_MEMBER
51+
2) SAI_OBJECT_TYPE_NEXT_HOP
52+
3) SAI_OBJECT_TYPE_ROUTER_INTERFACE
53+
4) CLASS_BASED_NEXT_HOP_GROUP_TABLE
54+
5) NEXTHOP_GROUP_TABLE
55+
"""
56+
self.ret_temp = {}
57+
self.ns = ''
58+
self.dest_net = ''
59+
self.nh_id = ''
60+
self.nh_type = ''
61+
62+
def get_all_args(self, ns=""):
63+
req = MatchRequest(db="APPL_DB", table="ROUTE_TABLE", key_pattern="*", ns=self.ns)
64+
ret = self.match_engine.fetch(req)
65+
all_routes = ret.get("keys", [])
66+
return [key[len("ROUTE_TABLE:"):] for key in all_routes]
67+
68+
def execute(self, params):
69+
self.ret_temp = create_template_dict(dbs=["CONFIG_DB", "APPL_DB", "ASIC_DB"])
70+
self.dest_net = params[Route.ARG_NAME]
71+
self.ns = params["namespace"]
72+
# CONFIG DB
73+
if not self.init_route_config_info():
74+
del self.ret_temp["CONFIG_DB"]
75+
# APPL DB
76+
nhgrp_field = self.init_route_appl_info()
77+
self.init_nhgrp_cbf_appl_info(nhgrp_field)
78+
# ASIC DB - ROUTE ENTRY
79+
self.nh_id, vr = self.init_asic_route_entry_info()
80+
# ASIC DB - VIRTUAL ROUTER
81+
self.init_asic_vr_info(vr)
82+
# ASIC DB - KEYS dependent on NEXT HOP ID
83+
self.init_asic_nh()
84+
return self.ret_temp
85+
86+
def add_to_ret_template(self, table, db, keys, err, add_to_tables_not_found=True):
87+
if not err and keys:
88+
self.ret_temp[db]["keys"].extend(keys)
89+
return keys
90+
elif add_to_tables_not_found:
91+
self.ret_temp[db]["tables_not_found"].extend([table])
92+
return []
93+
94+
def init_route_config_info(self):
95+
req = MatchRequest(db="CONFIG_DB", table="STATIC_ROUTE", key_pattern=self.dest_net, ns=self.ns)
96+
ret = self.match_engine.fetch(req)
97+
return self.add_to_ret_template(req.table, req.db, ret["keys"], ret["error"])
98+
99+
def init_route_appl_info(self):
100+
req = MatchRequest(db="APPL_DB", table="ROUTE_TABLE", key_pattern=self.dest_net,
101+
ns=self.ns, return_fields=["nexthop_group"])
102+
ret = self.match_engine.fetch(req)
103+
self.add_to_ret_template(req.table, req.db, ret["keys"], ret["error"])
104+
if ret["keys"]:
105+
return ret["return_values"].get(ret["keys"][0], {}).get("nexthop_group", "")
106+
return ""
107+
108+
def init_nhgrp_cbf_appl_info(self, nhgrp_field):
109+
if not nhgrp_field:
110+
return
111+
112+
# Verify if the nhgrp field in the route table refers to class based next_hop_group
113+
req = MatchRequest(db="APPL_DB", table="CLASS_BASED_NEXT_HOP_GROUP_TABLE", key_pattern=nhgrp_field,
114+
ns=self.ns, return_fields=["members"])
115+
ret = self.nhgrp_match_engine.fetch(req)
116+
self.add_to_ret_template(req.table, req.db, ret["keys"], ret["error"], False)
117+
118+
nggrp_table_key = ""
119+
if not ret["keys"]:
120+
nggrp_table_key = nhgrp_field
121+
else:
122+
nggrp_table_key = ret["return_values"].get(ret["keys"][0], {}).get("members", "")
123+
124+
if nggrp_table_key:
125+
# Retrieve the next_hop_group key
126+
req = MatchRequest(db="APPL_DB", table="NEXTHOP_GROUP_TABLE", key_pattern=nggrp_table_key, ns=self.ns)
127+
ret = self.nhgrp_match_engine.fetch(req)
128+
self.add_to_ret_template(req.table, req.db, ret["keys"], ret["error"], False)
129+
130+
def init_asic_route_entry_info(self):
131+
nh_id_field = "SAI_ROUTE_ENTRY_ATTR_NEXT_HOP_ID"
132+
req = MatchRequest(db="ASIC_DB", table="ASIC_STATE:SAI_OBJECT_TYPE_ROUTE_ENTRY", key_pattern=get_route_pattern(self.dest_net),
133+
ns=self.ns, return_fields=[nh_id_field])
134+
ret = self.match_engine.fetch(req)
135+
keys = self.add_to_ret_template(req.table, req.db, ret["keys"], ret["error"])
136+
asic_route_entry = keys[0] if keys else ""
137+
vr = get_vr_oid(asic_route_entry)
138+
nh_id = ret["return_values"].get(asic_route_entry, {}).get(nh_id_field, "")
139+
return nh_id, vr
140+
141+
def init_asic_vr_info(self, vr):
142+
ret = {}
143+
if vr:
144+
req = MatchRequest(db="ASIC_DB", table="ASIC_STATE:SAI_OBJECT_TYPE_VIRTUAL_ROUTER", key_pattern=vr, ns=self.ns)
145+
ret = self.nhgrp_match_engine.fetch(req)
146+
self.add_to_ret_template("ASIC_STATE:SAI_OBJECT_TYPE_VIRTUAL_ROUTER", "ASIC_DB", ret.get("keys", []), ret.get("error", ""))
147+
148+
def init_asic_nh(self):
149+
self.nh_type = self.get_nh_type()
150+
nh_ex = NHExtractor.initialize(self)
151+
nh_ex.collect()
152+
153+
def get_nh_type(self):
154+
"""
155+
Figure out the nh_type using OID Header
156+
"""
157+
if not self.nh_id:
158+
return "DROP"
159+
oid = self.nh_id.split(":")[-1]
160+
for nh_type in [NH_GRP, NH, RIF, CPU_PORT]:
161+
if oid.startswith(OID_HEADERS.get(nh_type, "")):
162+
return nh_type
163+
return "DROP"
164+
165+
166+
class NHExtractor(object):
167+
"""
168+
Base Class for NH_ID Type
169+
"""
170+
@staticmethod
171+
def initialize(route_obj):
172+
if route_obj.nh_type == NH:
173+
return SingleNextHop(route_obj)
174+
elif route_obj.nh_type == NH_GRP:
175+
return MultipleNextHop(route_obj)
176+
elif route_obj.nh_type == RIF:
177+
return DirecAttachedRt(route_obj)
178+
elif route_obj.nh_type == CPU_PORT:
179+
return CPUPort(route_obj)
180+
return NHExtractor(route_obj)
181+
182+
def __init__(self, route_obj):
183+
self.rt = route_obj
184+
185+
def collect(self):
186+
pass
187+
188+
def init_asic_rif_info(self, oid, add_to_tables_not_found=True):
189+
ret = {}
190+
if oid:
191+
req = MatchRequest(db="ASIC_DB", table=RIF, key_pattern=oid, ns=self.rt.ns)
192+
ret = self.rt.nhgrp_match_engine.fetch(req)
193+
return self.rt.add_to_ret_template(RIF, "ASIC_DB", ret.get("keys", []), ret.get("error", ""), add_to_tables_not_found)
194+
195+
def init_asic_next_hop_info(self, oid, add_to_tables_not_found=True):
196+
ret = {}
197+
if oid:
198+
req = MatchRequest(db="ASIC_DB", table=NH, key_pattern=oid, ns=self.rt.ns,
199+
return_fields=["SAI_NEXT_HOP_ATTR_ROUTER_INTERFACE_ID"])
200+
ret = self.rt.nhgrp_match_engine.fetch(req)
201+
keys = self.rt.add_to_ret_template(NH, "ASIC_DB", ret.get("keys", []), ret.get("error", ""), add_to_tables_not_found)
202+
nh_key = keys[0] if keys else ""
203+
return ret.get("return_values", {}).get(nh_key, {}).get("SAI_NEXT_HOP_ATTR_ROUTER_INTERFACE_ID", "")
204+
205+
206+
class CPUPort(NHExtractor):
207+
def collect(self):
208+
req = MatchRequest(db="ASIC_DB", table=CPU_PORT, key_pattern=self.rt.nh_id, ns=self.rt.ns)
209+
ret = self.rt.nhgrp_match_engine.fetch(req)
210+
self.rt.add_to_ret_template(req.table, req.db, ret["keys"], ret["error"])
211+
212+
213+
class DirecAttachedRt(NHExtractor):
214+
def collect(self):
215+
self.init_asic_rif_info(self.rt.nh_id)
216+
217+
218+
class SingleNextHop(NHExtractor):
219+
def collect(self):
220+
rif_oid = self.init_asic_next_hop_info(self.rt.nh_id)
221+
self.init_asic_rif_info(rif_oid)
222+
223+
224+
class MultipleNextHop(NHExtractor):
225+
def collect(self):
226+
# Save nh_grp related keys
227+
self.init_asic_nh_group_info(self.rt.nh_id)
228+
# Save nh_grp_members info and fetch nh_oids
229+
nh_oids = self.init_asic_nh_group_members_info(self.rt.nh_id)
230+
# Save the actual next_hop using the nh_oids retrieved, fetch rif oid's if any
231+
rif_oids = self.init_asic_next_hops_info(nh_oids)
232+
# Save the rif_oid related ASIC keys
233+
self.init_asic_rifs_info(rif_oids)
234+
235+
def init_asic_nh_group_info(self, oid):
236+
ret = {}
237+
if oid:
238+
req = MatchRequest(db="ASIC_DB", table=NH_GRP, key_pattern=oid, ns=self.rt.ns)
239+
ret = self.rt.nhgrp_match_engine.fetch(req)
240+
self.rt.add_to_ret_template(NH_GRP, "ASIC_DB", ret.get("keys", []), ret.get("error", ""))
241+
242+
def init_asic_nh_group_members_info(self, oid):
243+
ret = {}
244+
if oid:
245+
req = MatchRequest(db="ASIC_DB", table="ASIC_STATE:SAI_OBJECT_TYPE_NEXT_HOP_GROUP_MEMBER",
246+
field="SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_GROUP_ID", value=oid, ns=self.rt.ns,
247+
return_fields=["SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_ID"])
248+
ret = self.rt.nhgrp_match_engine.fetch(req)
249+
keys = self.rt.add_to_ret_template("ASIC_STATE:SAI_OBJECT_TYPE_NEXT_HOP_GROUP_MEMBER", "ASIC_DB",
250+
ret.get("keys", []), ret.get("error", ""), False)
251+
if not keys:
252+
self.rt.ret_temp["ASIC_DB"]["tables_not_found"].append("ASIC_STATE:SAI_OBJECT_TYPE_NEXT_HOP_GROUP_MEMBER")
253+
return [ret.get("return_values", {}).get(key, {}).get("SAI_NEXT_HOP_GROUP_MEMBER_ATTR_NEXT_HOP_ID", "") for key in keys]
254+
255+
def init_asic_next_hops_info(self, nh_oids):
256+
rif_oids = []
257+
for oid in nh_oids:
258+
rif_oid = self.init_asic_next_hop_info(oid, False)
259+
if rif_oid:
260+
rif_oids.append(rif_oid)
261+
if not rif_oids:
262+
self.rt.ret_temp["ASIC_DB"]["tables_not_found"].append("ASIC_STATE:SAI_OBJECT_TYPE_NEXT_HOP")
263+
return rif_oids
264+
265+
def init_asic_rifs_info(self, rif_oids):
266+
nothing_found = True
267+
for oid in rif_oids:
268+
if self.init_asic_rif_info(oid, False):
269+
nothing_found = False
270+
if nothing_found:
271+
self.rt.ret_temp["ASIC_DB"]["tables_not_found"].append("ASIC_STATE:SAI_OBJECT_TYPE_ROUTER_INTERFACE")

0 commit comments

Comments
 (0)