1
- import json , fnmatch
1
+ import json
2
+ import fnmatch
2
3
from abc import ABC , abstractmethod
3
4
from dump .helper import verbose_print
4
5
from swsscommon .swsscommon import SonicV2Connector , SonicDBConfig
12
13
"NO_SRC" : "Either one of db or file in the request should be non-empty" ,
13
14
"NO_TABLE" : "No 'table' name provided" ,
14
15
"NO_KEY" : "'key_pattern' cannot be empty" ,
15
- "NO_VALUE" : "Field is provided, but no value is provided to compare with" ,
16
+ "NO_VALUE" : "Field is provided, but no value is provided to compare with" ,
16
17
"SRC_VAGUE" : "Only one of db or file should be provided" ,
17
- "CONN_ERR" : "Connection Error" ,
18
+ "CONN_ERR" : "Connection Error" ,
18
19
"JUST_KEYS_COMPAT" : "When Just_keys is set to False, return_fields should be empty" ,
19
20
"BAD_FORMAT_RE_FIELDS" : "Return Fields should be of list type" ,
20
21
"NO_ENTRIES" : "No Keys found after applying the filtering criteria" ,
21
22
"FILE_R_EXEP" : "Exception Caught While Reading the json cfg file provided" ,
22
23
"INV_NS" : "Namespace is invalid"
23
24
}
24
25
26
+
25
27
class MatchRequest :
26
- """
27
- Request Object which should be passed to the MatchEngine
28
-
28
+ """
29
+ Request Object which should be passed to the MatchEngine
30
+
29
31
Attributes:
30
32
"table" : A Valid Table Name
31
33
"key_pattern" : Pattern of the redis-key to match. Defaults to "*". Eg: "*" will match all the keys.
32
- Supports these glob style patterns. https://redis.io/commands/KEYS
34
+ Supports these glob style patterns. https://redis.io/commands/KEYS
33
35
"field" : Field to check for a match,Defaults to None
34
36
"value" : Value to match, Defaults to None
35
37
"return_fields" : An iterable type, where each element woudld imply a field to return from all the filtered keys
@@ -38,9 +40,10 @@ class MatchRequest:
38
40
Only one of the db/file fields should have a non-empty string.
39
41
"just_keys" : If true, Only Returns the keys matched. Does not return field-value pairs. Defaults to True
40
42
"ns" : namespace argument, if nothing is provided, default namespace is used
41
- "match_entire_list" : When this arg is set to true, entire list is matched incluing the ",".
43
+ "match_entire_list" : When this arg is set to true, entire list is matched incluing the ",".
42
44
When False, the values are split based on "," and individual items are matched with
43
45
"""
46
+
44
47
def __init__ (self , ** kwargs ):
45
48
self .table = kwargs ["table" ] if "table" in kwargs else None
46
49
self .key_pattern = kwargs ["key_pattern" ] if "key_pattern" in kwargs else "*"
@@ -56,16 +59,15 @@ def __init__(self, **kwargs):
56
59
verbose_print (str (err ))
57
60
if err :
58
61
raise Exception ("Static Checks for the MatchRequest Failed, Reason: \n " + err )
59
-
60
-
62
+
61
63
def __static_checks (self ):
62
-
64
+
63
65
if not self .db and not self .file :
64
66
return EXCEP_DICT ["NO_SRC" ]
65
-
67
+
66
68
if self .db and self .file :
67
69
return EXCEP_DICT ["SRC_VAGUE" ]
68
-
70
+
69
71
if not self .db :
70
72
try :
71
73
with open (self .file ) as f :
@@ -75,32 +77,32 @@ def __static_checks(self):
75
77
76
78
if not self .file and self .db not in SonicDBConfig .getDbList ():
77
79
return EXCEP_DICT ["INV_DB" ]
78
-
80
+
79
81
if not self .table :
80
82
return EXCEP_DICT ["NO_TABLE" ]
81
-
83
+
82
84
if not isinstance (self .return_fields , list ):
83
85
return EXCEP_DICT ["BAD_FORMAT_RE_FIELDS" ]
84
-
86
+
85
87
if not self .just_keys and self .return_fields :
86
88
return EXCEP_DICT ["JUST_KEYS_COMPAT" ]
87
-
89
+
88
90
if self .field and not self .value :
89
91
return EXCEP_DICT ["NO_VALUE" ]
90
-
92
+
91
93
if self .ns != DEFAULT_NAMESPACE and self .ns not in multi_asic .get_namespace_list ():
92
94
return EXCEP_DICT ["INV_NS" ] + " Choose From {}" .format (multi_asic .get_namespace_list ())
93
-
95
+
94
96
verbose_print ("MatchRequest Checks Passed" )
95
-
97
+
96
98
return ""
97
-
99
+
98
100
def __str__ (self ):
99
101
str = "----------------------- \n MatchRequest: \n "
100
102
if self .db :
101
103
str += "db:{} , " .format (self .db )
102
104
if self .file :
103
- str += "file:{} , " .format (self .file )
105
+ str += "file:{} , " .format (self .file )
104
106
if self .table :
105
107
str += "table:{} , " .format (self .table )
106
108
if self .key_pattern :
@@ -116,78 +118,76 @@ def __str__(self):
116
118
if len (self .return_fields ) > 0 :
117
119
str += "return_fields: " + "," .join (self .return_fields ) + " "
118
120
if self .ns :
119
- str += "namespace: , " + self .ns
121
+ str += "namespace: , " + self .ns
120
122
if self .match_entire_list :
121
123
str += "match_list: True , "
122
124
else :
123
125
str += "match_list: False , "
124
126
return str
125
-
127
+
128
+
126
129
class SourceAdapter (ABC ):
127
130
""" Source Adaptor offers unified interface to Data Sources """
128
-
131
+
129
132
def __init__ (self ):
130
133
pass
131
-
134
+
132
135
@abstractmethod
133
136
def connect (self , db , ns ):
134
137
""" Return True for Success, False for failure """
135
138
return False
136
-
139
+
137
140
@abstractmethod
138
141
def getKeys (self , db , table , key_pattern ):
139
142
return []
140
-
143
+
141
144
@abstractmethod
142
145
def get (self , db , key ):
143
146
return {}
144
-
147
+
145
148
@abstractmethod
146
149
def hget (self , db , key , field ):
147
150
return ""
148
-
151
+
149
152
@abstractmethod
150
153
def get_separator (self , db ):
151
154
return ""
152
-
155
+
156
+
153
157
class RedisSource (SourceAdapter ):
154
158
""" Concrete Adaptor Class for connecting to Redis Data Sources """
155
-
156
- def __init__ (self ):
157
- self .conn = None
158
-
159
+
160
+ def __init__ (self , conn_pool ):
161
+ self .conn = None
162
+ self .pool = conn_pool
163
+
159
164
def connect (self , db , ns ):
160
165
try :
161
- if not SonicDBConfig .isInit ():
162
- if multi_asic .is_multi_asic ():
163
- SonicDBConfig .load_sonic_global_db_config ()
164
- else :
165
- SonicDBConfig .load_sonic_db_config ()
166
- self .conn = SonicV2Connector (namespace = ns , use_unix_socket_path = True )
167
- self .conn .connect (db )
166
+ self .conn = self .pool .get (db , ns )
168
167
except Exception as e :
169
168
verbose_print ("RedisSource: Connection Failed\n " + str (e ))
170
169
return False
171
170
return True
172
-
171
+
173
172
def get_separator (self , db ):
174
173
return self .conn .get_db_separator (db )
175
-
176
- def getKeys (self , db , table , key_pattern ):
174
+
175
+ def getKeys (self , db , table , key_pattern ):
177
176
return self .conn .keys (db , table + self .get_separator (db ) + key_pattern )
178
-
177
+
179
178
def get (self , db , key ):
180
179
return self .conn .get_all (db , key )
181
-
180
+
182
181
def hget (self , db , key , field ):
183
182
return self .conn .get (db , key , field )
184
183
184
+
185
185
class JsonSource (SourceAdapter ):
186
186
""" Concrete Adaptor Class for connecting to JSON Data Sources """
187
-
187
+
188
188
def __init__ (self ):
189
189
self .json_data = None
190
-
190
+
191
191
def connect (self , db , ns ):
192
192
try :
193
193
with open (db ) as f :
@@ -196,67 +196,114 @@ def connect(self, db, ns):
196
196
verbose_print ("JsonSource: Loading the JSON file failed" + str (e ))
197
197
return False
198
198
return True
199
-
199
+
200
200
def get_separator (self , db ):
201
201
return SonicDBConfig .getSeparator ("CONFIG_DB" )
202
-
202
+
203
203
def getKeys (self , db , table , key_pattern ):
204
204
if table not in self .json_data :
205
205
return []
206
206
# https://docs.python.org/3.7/library/fnmatch.html
207
207
kp = key_pattern .replace ("[^" , "[!" )
208
208
kys = fnmatch .filter (self .json_data [table ].keys (), kp )
209
209
return [table + self .get_separator (db ) + ky for ky in kys ]
210
-
210
+
211
211
def get (self , db , key ):
212
212
sep = self .get_separator (db )
213
213
table , key = key .split (sep , 1 )
214
214
return self .json_data .get (table , {}).get (key , {})
215
-
215
+
216
216
def hget (self , db , key , field ):
217
217
sep = self .get_separator (db )
218
218
table , key = key .split (sep , 1 )
219
219
return self .json_data .get (table , "" ).get (key , "" ).get (field , "" )
220
-
220
+
221
+
222
+ class ConnectionPool :
223
+ """ Caches SonicV2Connector objects for effective reuse """
224
+ def __init__ (self ):
225
+ self .cache = dict () # Pool of SonicV2Connector objects
226
+
227
+ def initialize_connector (self , ns ):
228
+ if not SonicDBConfig .isInit ():
229
+ if multi_asic .is_multi_asic ():
230
+ SonicDBConfig .load_sonic_global_db_config ()
231
+ else :
232
+ SonicDBConfig .load_sonic_db_config ()
233
+ return SonicV2Connector (namespace = ns , use_unix_socket_path = True )
234
+
235
+ def get (self , db_name , ns , update = False ):
236
+ """ Returns a SonicV2Connector Object and caches it for further requests """
237
+ if ns not in self .cache :
238
+ self .cache [ns ] = {}
239
+ self .cache [ns ]["conn" ] = self .initialize_connector (ns )
240
+ self .cache [ns ]["connected_to" ] = set ()
241
+ if update or db_name not in self .cache [ns ]["connected_to" ]:
242
+ self .cache [ns ]["conn" ].connect (db_name )
243
+ self .cache [ns ]["connected_to" ].add (db_name )
244
+ return self .cache [ns ]["conn" ]
245
+
246
+ def clear (self , namespace = None ):
247
+ if not namespace :
248
+ self .cache .clear ()
249
+ elif namespace in self .cache :
250
+ del self .cache [namespace ]
251
+
252
+
221
253
class MatchEngine :
222
- """ Pass in a MatchRequest, to fetch the Matched dump from the Data sources """
223
-
254
+ """
255
+ Provide a MatchRequest to fetch the relevant keys/fv's from the data source
256
+ Usage Guidelines:
257
+ 1) Instantiate the class once for the entire execution,
258
+ to effectively use the caching of redis connection objects
259
+ """
260
+ def __init__ (self , pool = None ):
261
+ if not isinstance (pool , ConnectionPool ):
262
+ self .conn_pool = ConnectionPool ()
263
+ else :
264
+ self .conn_pool = pool
265
+
266
+ def clear_cache (self , ns ):
267
+ self .conn_pool (ns )
268
+
224
269
def __get_source_adapter (self , req ):
225
270
src = None
226
271
d_src = ""
227
272
if req .db :
228
273
d_src = req .db
229
- src = RedisSource ()
274
+ src = RedisSource (self . conn_pool )
230
275
else :
231
276
d_src = req .file
232
277
src = JsonSource ()
233
278
return d_src , src
234
-
279
+
235
280
def __create_template (self ):
236
- return {"error" : "" , "keys" : [], "return_values" : {}}
237
-
281
+ return {"error" : "" , "keys" : [], "return_values" : {}}
282
+
238
283
def __display_error (self , err ):
239
284
template = self .__create_template ()
240
285
template ['error' ] = err
241
286
verbose_print ("MatchEngine: \n " + template ['error' ])
242
287
return template
243
-
288
+
244
289
def __filter_out_keys (self , src , req , all_matched_keys ):
245
290
# TODO: Custom Callbacks for Complex Matching Criteria
246
291
if not req .field :
247
292
return all_matched_keys
248
-
293
+
249
294
filtered_keys = []
250
295
for key in all_matched_keys :
251
296
f_values = src .hget (req .db , key , req .field )
297
+ if not f_values :
298
+ continue
252
299
if "," in f_values and not req .match_entire_list :
253
300
f_value = f_values .split ("," )
254
301
else :
255
302
f_value = [f_values ]
256
303
if req .value in f_value :
257
304
filtered_keys .append (key )
258
305
return filtered_keys
259
-
306
+
260
307
def __fill_template (self , src , req , filtered_keys , template ):
261
308
for key in filtered_keys :
262
309
temp = {}
@@ -266,35 +313,34 @@ def __fill_template(self, src, req, filtered_keys, template):
266
313
elif len (req .return_fields ) > 0 :
267
314
template ["keys" ].append (key )
268
315
template ["return_values" ][key ] = {}
269
- for field in req .return_fields :
316
+ for field in req .return_fields :
270
317
template ["return_values" ][key ][field ] = src .hget (req .db , key , field )
271
318
else :
272
319
template ["keys" ].append (key )
273
320
verbose_print ("Return Values:" + str (template ["return_values" ]))
274
321
return template
275
-
322
+
276
323
def fetch (self , req ):
277
324
""" Given a request obj, find its match in the data source provided """
278
325
if not isinstance (req , MatchRequest ):
279
326
return self .__display_error (EXCEP_DICT ["INV_REQ" ])
280
-
327
+
281
328
verbose_print (str (req ))
282
-
329
+
283
330
if not req .key_pattern :
284
331
return self .__display_error (EXCEP_DICT ["NO_KEY" ])
285
-
332
+
286
333
d_src , src = self .__get_source_adapter (req )
287
334
if not src .connect (d_src , req .ns ):
288
335
return self .__display_error (EXCEP_DICT ["CONN_ERR" ])
289
-
336
+
290
337
template = self .__create_template ()
291
338
all_matched_keys = src .getKeys (req .db , req .table , req .key_pattern )
292
339
if not all_matched_keys :
293
340
return self .__display_error (EXCEP_DICT ["NO_MATCHES" ])
294
-
341
+
295
342
filtered_keys = self .__filter_out_keys (src , req , all_matched_keys )
296
343
verbose_print ("Filtered Keys:" + str (filtered_keys ))
297
344
if not filtered_keys :
298
345
return self .__display_error (EXCEP_DICT ["NO_ENTRIES" ])
299
- return self .__fill_template (src , req , filtered_keys , template )
300
-
346
+ return self .__fill_template (src , req , filtered_keys , template )
0 commit comments