10
10
11
11
from sonic_syncd import SonicSyncDaemon
12
12
from . import logger
13
- from .conventions import LldpPortIdSubtype , LldpChassisIdSubtype
13
+ from .conventions import LldpPortIdSubtype , LldpChassisIdSubtype , LldpSystemCapabilitiesMap
14
14
15
15
LLDPD_TIME_FORMAT = '%H:%M:%S'
16
16
@@ -44,7 +44,8 @@ def parse_time(time_str):
44
44
"""
45
45
days , hour_min_secs = re .split (LLDPD_UPTIME_RE_SPLIT_PATTERN , time_str )
46
46
struct_time = time .strptime (hour_min_secs , LLDPD_TIME_FORMAT )
47
- time_delta = datetime .timedelta (days = int (days ), hours = struct_time .tm_hour , minutes = struct_time .tm_min ,
47
+ time_delta = datetime .timedelta (days = int (days ), hours = struct_time .tm_hour ,
48
+ minutes = struct_time .tm_min ,
48
49
seconds = struct_time .tm_sec )
49
50
return int (time_delta .total_seconds ())
50
51
@@ -56,6 +57,7 @@ class LldpSyncDaemon(SonicSyncDaemon):
56
57
within the same Redis instance on a switch
57
58
"""
58
59
LLDP_ENTRY_TABLE = 'LLDP_ENTRY_TABLE'
60
+ LLDP_LOC_CHASSIS_TABLE = 'LLDP_LOC_CHASSIS'
59
61
60
62
@unique
61
63
class PortIdSubtypeMap (int , Enum ):
@@ -109,19 +111,54 @@ class ChassisIdSubtypeMap(int, Enum):
109
111
# chassis = int(LldpChassisIdSubtype.chassisComponent) # (unsupported by lldpd)
110
112
local = int (LldpPortIdSubtype .local )
111
113
114
+ def get_sys_capability_list (self , if_attributes ):
115
+ """
116
+ Get a list of capabilities from interface attributes dictionary.
117
+ :param if_attributes: interface attributes
118
+ :return: list of capabilities
119
+ """
120
+ try :
121
+ # [{'enabled': ..., 'type': 'capability1'}, {'enabled': ..., 'type': 'capability2'}]
122
+ capability_list = if_attributes ['chassis' ].values ()[0 ]['capability' ]
123
+ # {'enabled': ..., 'type': 'capability'}
124
+ if isinstance (capability_list , dict ):
125
+ capability_list = [capability_list ]
126
+ except KeyError :
127
+ logger .error ("Failed to get system capabilities" )
128
+ return []
129
+ return capability_list
130
+
131
+ def parse_sys_capabilities (self , capability_list , enabled = False ):
132
+ """
133
+ Get a bit map of capabilities, accoding to textual convention.
134
+ :param capability_list: list of capabilities
135
+ :param enabled: if true, consider only the enabled capabilities
136
+ :return: string representing a bit map
137
+ """
138
+ # chassis is incomplete, missing capabilities
139
+ if not capability_list :
140
+ return ""
141
+
142
+ sys_cap = 0x00
143
+ for capability in capability_list :
144
+ try :
145
+ if (not enabled ) or capability ["enabled" ]:
146
+ sys_cap |= 128 >> LldpSystemCapabilitiesMap [capability ["type" ].lower ()]
147
+ except KeyError :
148
+ logger .warning ("Unknown capability {}" .format (capability ["type" ]))
149
+ return "%0.2X 00" % sys_cap
150
+
112
151
def __init__ (self , update_interval = None ):
113
152
super (LldpSyncDaemon , self ).__init__ ()
114
153
self ._update_interval = update_interval or DEFAULT_UPDATE_INTERVAL
115
154
self .db_connector = SonicV2Connector ()
116
155
self .db_connector .connect (self .db_connector .APPL_DB )
117
156
118
- def source_update (self ):
119
- """
120
- Invoke lldpctl and format as JSON
121
- """
122
- cmd = ['/usr/sbin/lldpctl' , '-f' , 'json' ]
123
- logger .debug ("Invoking lldpctl with: {}" .format (cmd ))
157
+ self .chassis_cache = {}
158
+ self .interfaces_cache = {}
124
159
160
+ @staticmethod
161
+ def _scrap_output (cmd ):
125
162
try :
126
163
# execute the subprocess command
127
164
lldpctl_output = subprocess .check_output (cmd )
@@ -135,8 +172,22 @@ def source_update(self):
135
172
except ValueError :
136
173
logger .exception ("Failed to parse lldpctl output" )
137
174
return None
138
- else :
139
- return lldpctl_json
175
+
176
+ return lldpctl_json
177
+
178
+ def source_update (self ):
179
+ """
180
+ Invoke lldpctl and format as JSON
181
+ """
182
+ cmd = ['/usr/sbin/lldpctl' , '-f' , 'json' ]
183
+ logger .debug ("Invoking lldpctl with: {}" .format (cmd ))
184
+ cmd_local = ['/usr/sbin/lldpcli' , '-f' , 'json' , 'show' , 'chassis' ]
185
+ logger .debug ("Invoking lldpcli with: {}" .format (cmd_local ))
186
+
187
+ lldp_json = self ._scrap_output (cmd )
188
+ lldp_json ['lldp_loc_chassis' ] = self ._scrap_output (cmd_local )
189
+
190
+ return lldp_json
140
191
141
192
def parse_update (self , lldp_json ):
142
193
"""
@@ -148,20 +199,19 @@ def parse_update(self, lldp_json):
148
199
149
200
LldpRemEntry ::= SEQUENCE {
150
201
lldpRemTimeMark TimeFilter,
151
- * lldpRemLocalPortNum LldpPortNumber,
152
- * lldpRemIndex Integer32,
202
+ lldpRemLocalPortNum LldpPortNumber,
203
+ lldpRemIndex Integer32,
153
204
lldpRemChassisIdSubtype LldpChassisIdSubtype,
154
205
lldpRemChassisId LldpChassisId,
155
206
lldpRemPortIdSubtype LldpPortIdSubtype,
156
207
lldpRemPortId LldpPortId,
157
208
lldpRemPortDesc SnmpAdminString,
158
209
lldpRemSysName SnmpAdminString,
159
210
lldpRemSysDesc SnmpAdminString,
160
- * lldpRemSysCapSupported LldpSystemCapabilitiesMap,
161
- * lldpRemSysCapEnabled LldpSystemCapabilitiesMap
211
+ lldpRemSysCapSupported LldpSystemCapabilitiesMap,
212
+ lldpRemSysCapEnabled LldpSystemCapabilitiesMap
162
213
}
163
214
"""
164
- # TODO: *Implement
165
215
try :
166
216
interface_list = lldp_json ['lldp' ].get ('interface' ) or []
167
217
parsed_interfaces = defaultdict (dict )
@@ -175,12 +225,45 @@ def parse_update(self, lldp_json):
175
225
if_attributes = interface_list [if_name ]
176
226
177
227
if 'port' in if_attributes :
178
- parsed_interfaces [if_name ].update (self .parse_port (if_attributes ['port' ]))
228
+ rem_port_keys = ('lldp_rem_port_id_subtype' ,
229
+ 'lldp_rem_port_id' ,
230
+ 'lldp_rem_port_desc' )
231
+ parsed_port = zip (rem_port_keys , self .parse_port (if_attributes ['port' ]))
232
+ parsed_interfaces [if_name ].update (parsed_port )
233
+
179
234
if 'chassis' in if_attributes :
180
- parsed_interfaces [if_name ].update (self .parse_chassis (if_attributes ['chassis' ]))
235
+ rem_chassis_keys = ('lldp_rem_chassis_id_subtype' ,
236
+ 'lldp_rem_chassis_id' ,
237
+ 'lldp_rem_sys_name' ,
238
+ 'lldp_rem_sys_desc' )
239
+ parsed_chassis = zip (rem_chassis_keys ,
240
+ self .parse_chassis (if_attributes ['chassis' ]))
241
+ parsed_interfaces [if_name ].update (parsed_chassis )
181
242
182
243
# lldpRemTimeMark TimeFilter,
183
- parsed_interfaces [if_name ].update ({'lldp_rem_time_mark' : str (parse_time (if_attributes .get ('age' )))})
244
+ parsed_interfaces [if_name ].update ({'lldp_rem_time_mark' :
245
+ str (parse_time (if_attributes .get ('age' )))})
246
+
247
+ # lldpRemIndex
248
+ parsed_interfaces [if_name ].update ({'lldp_rem_index' : str (if_attributes .get ('rid' ))})
249
+
250
+ capability_list = self .get_sys_capability_list (if_attributes )
251
+ # lldpSysCapSupported
252
+ parsed_interfaces [if_name ].update ({'lldp_rem_sys_cap_supported' :
253
+ self .parse_sys_capabilities (capability_list )})
254
+ # lldpSysCapEnabled
255
+ parsed_interfaces [if_name ].update ({'lldp_rem_sys_cap_enabled' :
256
+ self .parse_sys_capabilities (
257
+ capability_list , enabled = True )})
258
+ if lldp_json ['lldp_loc_chassis' ]:
259
+ loc_chassis_keys = ('lldp_loc_chassis_id_subtype' ,
260
+ 'lldp_loc_chassis_id' ,
261
+ 'lldp_loc_sys_name' ,
262
+ 'lldp_loc_sys_desc' )
263
+ parsed_chassis = zip (loc_chassis_keys ,
264
+ self .parse_chassis (lldp_json ['lldp_loc_chassis' ]
265
+ ['local-chassis' ]['chassis' ]))
266
+ parsed_interfaces ['local-chassis' ].update (parsed_chassis )
184
267
185
268
return parsed_interfaces
186
269
except (KeyError , ValueError ):
@@ -190,29 +273,25 @@ def parse_chassis(self, chassis_attributes):
190
273
try :
191
274
if 'id' in chassis_attributes and 'id' not in chassis_attributes ['id' ]:
192
275
sys_name = ''
193
- rem_attributes = chassis_attributes
276
+ attributes = chassis_attributes
194
277
id_attributes = chassis_attributes ['id' ]
195
278
else :
196
- (sys_name , rem_attributes ) = chassis_attributes .items ()[0 ]
197
- id_attributes = rem_attributes .get ('id' , '' )
279
+ (sys_name , attributes ) = chassis_attributes .items ()[0 ]
280
+ id_attributes = attributes .get ('id' , '' )
198
281
199
282
chassis_id_subtype = str (self .ChassisIdSubtypeMap [id_attributes ['type' ]].value )
200
283
chassis_id = id_attributes .get ('value' , '' )
201
- rem_desc = rem_attributes .get ('descr' , '' )
284
+ descr = attributes .get ('descr' , '' )
202
285
except (KeyError , ValueError ):
203
- logger .exception ("Could not infer system information from: {}" .format (chassis_attributes ))
204
- chassis_id_subtype = chassis_id = sys_name = rem_desc = ''
205
-
206
- return {
207
- # lldpRemChassisIdSubtype LldpChassisIdSubtype,
208
- 'lldp_rem_chassis_id_subtype' : chassis_id_subtype ,
209
- # lldpRemChassisId LldpChassisId,
210
- 'lldp_rem_chassis_id' : chassis_id ,
211
- # lldpRemSysName SnmpAdminString,
212
- 'lldp_rem_sys_name' : sys_name ,
213
- # lldpRemSysDesc SnmpAdminString,
214
- 'lldp_rem_sys_desc' : rem_desc ,
215
- }
286
+ logger .exception ("Could not infer system information from: {}"
287
+ .format (chassis_attributes ))
288
+ chassis_id_subtype = chassis_id = sys_name = descr = ''
289
+
290
+ return (chassis_id_subtype ,
291
+ chassis_id ,
292
+ sys_name ,
293
+ descr ,
294
+ )
216
295
217
296
def parse_port (self , port_attributes ):
218
297
port_identifiers = port_attributes .get ('id' )
@@ -224,33 +303,52 @@ def parse_port(self, port_attributes):
224
303
logger .exception ("Could not infer chassis subtype from: {}" .format (port_attributes ))
225
304
subtype , value = None
226
305
227
- return {
228
- # lldpRemPortIdSubtype LldpPortIdSubtype,
229
- 'lldp_rem_port_id_subtype' : subtype ,
230
- # lldpRemPortId LldpPortId,
231
- 'lldp_rem_port_id' : value ,
232
- # lldpRemSysDesc SnmpAdminString,
233
- 'lldp_rem_port_desc' : port_attributes .get ('descr' , '' )
234
- }
306
+ return (subtype ,
307
+ value ,
308
+ port_attributes .get ('descr' , '' ),
309
+ )
310
+
311
+ def cache_diff (self , cache , update ):
312
+ """
313
+ Find difference in keys between update and local cache dicts
314
+ :param cache: Local cache dict
315
+ :param update: Update dict
316
+ :return: new, changed, deleted keys tuple
317
+ """
318
+ new_keys = [key for key in update .keys () if key not in cache .keys ()]
319
+ changed_keys = list (set (key for key in update .keys () + cache .keys ()
320
+ if update [key ] != cache .get (key )))
321
+ deleted_keys = [key for key in cache .keys () if key not in update .keys ()]
322
+ return new_keys , changed_keys , deleted_keys
235
323
236
324
def sync (self , parsed_update ):
237
325
"""
238
326
Sync LLDP information to redis DB.
239
327
"""
240
328
logger .debug ("Initiating LLDPd sync to Redis..." )
241
329
242
- # First, delete all entries from the LLDP_ENTRY_TABLE
243
- client = self .db_connector .redis_clients [self .db_connector .APPL_DB ]
244
- pattern = '{}:*' .format (LldpSyncDaemon .LLDP_ENTRY_TABLE )
245
- self .db_connector .delete_all_by_pattern (self .db_connector .APPL_DB , pattern )
330
+ # push local chassis data to APP DB
331
+ chassis_update = parsed_update .pop ('local-chassis' )
332
+ if chassis_update != self .chassis_cache :
333
+ self .db_connector .delete (self .db_connector .APPL_DB ,
334
+ LldpSyncDaemon .LLDP_LOC_CHASSIS_TABLE )
335
+ for k , v in chassis_update .items ():
336
+ self .db_connector .set (self .db_connector .APPL_DB ,
337
+ LldpSyncDaemon .LLDP_LOC_CHASSIS_TABLE , k , v , blocking = True )
338
+ logger .debug ("sync'd: {}" .format (json .dumps (chassis_update , indent = 3 )))
246
339
247
- # Repopulate LLDP_ENTRY_TABLE by adding all elements from parsed_update
248
- for interface , if_attributes in parsed_update .items ():
340
+ new , changed , deleted = self .cache_diff (self .interfaces_cache , parsed_update )
341
+ # Delete LLDP_ENTRIES which were modified or are missing
342
+ for interface in changed + deleted :
343
+ table_key = ':' .join ([LldpSyncDaemon .LLDP_ENTRY_TABLE , interface ])
344
+ self .db_connector .delete (self .db_connector .APPL_DB , table_key )
345
+ # Repopulate LLDP_ENTRY_TABLE by adding all changed elements
346
+ for interface in changed + new :
249
347
if re .match (SONIC_ETHERNET_RE_PATTERN , interface ) is None :
250
348
logger .warning ("Ignoring interface '{}'" .format (interface ))
251
349
continue
252
- for k , v in if_attributes . items ():
253
- # port_table_key = LLDP_ENTRY_TABLE:INTERFACE_NAME;
254
- table_key = ':' . join ([ LldpSyncDaemon . LLDP_ENTRY_TABLE , interface ])
350
+ # port_table_key = LLDP_ENTRY_TABLE:INTERFACE_NAME;
351
+ table_key = ':' . join ([ LldpSyncDaemon . LLDP_ENTRY_TABLE , interface ])
352
+ for k , v in parsed_update [ interface ]. items ():
255
353
self .db_connector .set (self .db_connector .APPL_DB , table_key , k , v , blocking = True )
256
- logger .debug ("sync'd: \n {}" .format (json .dumps (if_attributes , indent = 3 )))
354
+ logger .debug ("sync'd: \n {}" .format (json .dumps (parsed_update [ interface ] , indent = 3 )))
0 commit comments