Freeze upstream versions
[netvirt.git] / resources / commons / showOvsdbMdsal.py
1 #!/usr/bin/env python
2
3 import urllib2, base64, json, sys, optparse
4
5 # globals
6 CONST_DEFAULT_DEBUG=0
7 options = None
8 state = None
9 jsonTopologyNodes = []
10 jsonInventoryNodes = []
11 flowInfoNodes = {}
12 nodeIdToDpidCache = {}
13
14 CONST_OPERATIONAL = 'operational'
15 CONST_CONFIG = 'config'
16 CONST_NET_TOPOLOGY = 'network-topology'
17 CONST_TOPOLOGY = 'topology'
18 CONST_TP_OF_INTERNAL = 65534
19 CONST_ALIASES = ['alpha', 'bravo', 'charlie', 'delta', 'echo', 'foxtrot', 'golf', 'hotel', 'india', 'juliet',
20                  'kilo', 'lima', 'mike', 'november', 'oscar', 'papa', 'quebec', 'romeo', 'sierra', 'tango',
21                  'uniform', 'victor', 'whiskey', 'xray', 'yankee', 'zulu']
22
23
24 class State:
25     def __init__(self):
26         self.nextAliasIndex = 0
27         self.nextAliasWrap = 0
28         self.nodeIdToAlias = {}
29
30         self.bridgeNodes = {}
31         self.ovsdbNodes = {}
32         self.ofLinks = {}
33
34     def __repr__(self):
35         return 'State {}:{} {}:{} {}:{} {}:{} {}:{}'.format(
36             'nextAliasIndex', self.nextAliasIndex,
37             'nextAliasWrap', self.nextAliasWrap,
38             'bridgeNodes_ids', self.bridgeNodes.keys(),
39             'ovsdbNodes_ids', self.ovsdbNodes.keys(),
40             'nodeIdToAlias', self.nodeIdToAlias)
41
42     def registerBridgeNode(self, bridgeNode):
43         self.bridgeNodes[bridgeNode.nodeId] = bridgeNode
44
45     def registerOvsdbNode(self, ovsdbNode):
46         self.ovsdbNodes[ovsdbNode.nodeId] = ovsdbNode
47
48     def getNextAlias(self, nodeId):
49         result = CONST_ALIASES[ self.nextAliasIndex ]
50         if self.nextAliasWrap > 0:
51             result += '_' + str(self.nextAliasWrap)
52
53         if CONST_ALIASES[ self.nextAliasIndex ] == CONST_ALIASES[-1]:
54             self.nextAliasIndex = 0
55             self.nextAliasWrap += 1
56         else:
57             self.nextAliasIndex += 1
58
59         self.nodeIdToAlias[ nodeId ] = result
60         return result
61
62 # --
63
64 class TerminationPoint:
65     def __init__(self, name, ofPort, tpType, mac='', ifaceId=''):
66         self.name = name
67         self.ofPort = ofPort
68         self.tpType = tpType
69         self.mac = mac
70         self.ifaceId = ifaceId
71
72     def __repr__(self):
73         result = '{} {}:{}'.format(self.name, 'of', self.ofPort)
74
75         if self.tpType != '':
76             result += ' {}:{}'.format('type', self.tpType)
77         if self.mac != '':
78             result += ' {}:{}'.format('mac', self.mac)
79         if self.ifaceId != '':
80             result += ' {}:{}'.format('ifaceId', self.ifaceId)
81
82         return '{' + result + '}'
83
84 # --
85
86 class BridgeNode:
87     def __init__(self, nodeId, dpId, name, controllerTarget, controllerConnected):
88         global state
89         self.alias = state.getNextAlias(nodeId)
90         self.nodeId = nodeId
91         self.dpId = dpId
92         self.name = name
93         self.controllerTarget = controllerTarget
94         self.controllerConnected = controllerConnected
95         self.tps = []
96
97     def getOpenflowName(self):
98         if self.dpId is None:
99             return self.nodeId
100         return dataPathIdToOfFormat(self.dpId)
101
102     def addTerminationPoint(self, terminationPoint):
103         self.tps.append(terminationPoint)
104
105     def __repr__(self):
106         return 'BridgeNode {}:{} {}:{} {}:{} {}:{} {}:{} {}:{} {}:{} {}:{}'.format(
107             'alias', self.alias,
108             'nodeId', self.nodeId,
109             'dpId', self.dpId,
110             'openflowName', self.getOpenflowName(),
111             'name', self.name,
112             'controllerTarget', self.controllerTarget,
113             'controllerConnected', self.controllerConnected,
114             'tps', self.tps)
115
116 # --
117
118 class OvsdbNode:
119     def __init__(self, nodeId, inetMgr, inetNode, otherLocalIp, ovsVersion):
120         global state
121         if inetNode != '':
122             self.alias = inetNode
123         else:
124             self.alias = nodeId
125         self.nodeId = nodeId
126         self.inetMgr = inetMgr
127         self.inetNode = inetNode
128         self.otherLocalIp = otherLocalIp
129         self.ovsVersion = ovsVersion
130
131     def __repr__(self):
132         return 'OvsdbNode {}:{} {}:{} {}:{} {}:{} {}:{} {}:{}'.format(
133             'alias', self.alias,
134             'nodeId', self.nodeId,
135             'inetMgr', self.inetMgr,
136             'inetNode', self.inetNode,
137             'otherLocalIp', self.otherLocalIp,
138             'ovsVersion', self.ovsVersion)
139
140 # ======================================================================
141
142 def make_it_a_string(param):
143     result = ""
144     try:
145         result = str( param )
146     except:
147         pass
148     return result
149
150 # ======================================================================
151
152 def printError(msg):
153     sys.stderr.write(msg)
154
155 # ======================================================================
156
157 def prt(msg, logLevel=0):
158     prtCommon(msg, logLevel)
159 def prtLn(msg, logLevel=0):
160     prtCommon('{}\n'.format(msg), logLevel)
161 def prtCommon(msg, logLevel):
162     if options.debug >= logLevel:
163         sys.stdout.write(msg)
164
165 # ======================================================================
166
167 def getMdsalTreeType():
168     if options.useConfigTree:
169         return CONST_CONFIG
170     return CONST_OPERATIONAL
171
172 # --
173
174 def grabJson(url):
175
176     try:
177         request = urllib2.Request(url)
178         # You need the replace to handle encodestring adding a trailing newline
179         # (https://docs.python.org/2/library/base64.html#base64.encodestring)
180         base64string = base64.encodestring('{}:{}'.format(options.odlUsername, options.odlPassword)).replace('\n', '')
181         request.add_header('Authorization', 'Basic {}'.format(base64string))
182         result = urllib2.urlopen(request)
183     except urllib2.URLError, e:
184         printError('Unable to send request: {}\n'.format(e))
185         sys.exit(1)
186
187     if (result.code != 200):
188         printError( '{}\n{}\n\nError: unexpected code: {}\n'.format(result.info(), result.read(), result.code) )
189         sys.exit(1)
190
191     data = json.load(result)
192     prtLn(data, 4)
193     return data
194
195 # --
196
197 def grabInventoryJson(mdsalTreeType):
198     global jsonInventoryNodes
199
200     url = 'http://{}:{}/restconf/{}/opendaylight-inventory:nodes/'.format(options.odlIp, options.odlPort, mdsalTreeType)
201     data = grabJson(url)
202
203     if not 'nodes' in data:
204         printError( '{}\n\nError: did not find nodes in {}'.format(data, url) )
205         sys.exit(1)
206
207     data2 = data['nodes']
208     if not 'node' in data2:
209         printError( '{}\n\nError: did not find node in {}'.format(data2, url) )
210         sys.exit(1)
211
212     jsonInventoryNodes = data2['node']
213
214 # --
215
216 def parseInventoryJson(mdsalTreeType):
217     global jsonInventoryNodes
218     global flowInfoNodes
219
220     for nodeDict in jsonInventoryNodes:
221         if not 'id' in nodeDict:
222             continue
223
224         bridgeOfId = nodeDict.get('id')
225         prtLn('inventory node {} has keys {}'.format(bridgeOfId, nodeDict.keys()), 3)
226
227         # locate bridge Node
228         bridgeNodeId = None
229         bridgeNode = None
230         for currNodeId in state.bridgeNodes.keys():
231             if state.bridgeNodes[currNodeId].getOpenflowName() == bridgeOfId:
232                 bridgeNodeId = currNodeId
233                 bridgeNode = state.bridgeNodes[currNodeId]
234                 break
235
236         if bridgeNodeId is None:
237             prtLn('inventory node {}'.format(bridgeOfId), 1)
238         else:
239             prtLn('inventory node {}, aka {}, aka {}'.format(bridgeOfId, bridgeNodeId, showPrettyName(bridgeNodeId)), 1)
240
241         flowInfoNode = {}
242
243         indent = ' ' * 2
244         prtLn('{}features: {}'.format(indent, nodeDict.get('flow-node-inventory:switch-features', {})), 2)
245         prt('{}sw: {}'.format(indent, nodeDict.get('flow-node-inventory:software')), 2)
246         prt('{}hw: {}'.format(indent, nodeDict.get('flow-node-inventory:hardware')), 2)
247         prt('{}manuf: {}'.format(indent, nodeDict.get('flow-node-inventory:manufacturer')), 2)
248         prtLn('{}ip: {}'.format(indent, nodeDict.get('flow-node-inventory:ip-address')), 2)
249
250
251         for inventoryEntry in nodeDict.get('flow-node-inventory:table', []):
252             if 'id' in inventoryEntry:
253                 currTableId = inventoryEntry.get('id')
254                 for currFlow in inventoryEntry.get('flow', []):
255                     prtLn('{}table {}: {}'.format(indent, currTableId, currFlow.get('id')), 1)
256                     prtLn('{}{}'.format(indent * 2, currFlow), 2)
257
258                     if currTableId in flowInfoNode:
259                         flowInfoNode[ currTableId ].append( currFlow.get('id') )
260                     else:
261                         flowInfoNode[ currTableId ] = [ currFlow.get('id') ]
262
263         prtLn('', 1)
264
265         for currTableId in flowInfoNode.keys():
266             flowInfoNode[currTableId].sort()
267
268         # store info collected in flowInfoNodes
269         flowInfoNodes[bridgeOfId] = flowInfoNode
270
271 # --
272
273 def grabTopologyJson(mdsalTreeType):
274     global jsonTopologyNodes
275
276     url = 'http://{}:{}/restconf/{}/network-topology:network-topology/'.format(options.odlIp, options.odlPort, mdsalTreeType)
277     data = grabJson(url)
278
279     if not CONST_NET_TOPOLOGY in data:
280         printError( '{}\n\nError: did not find {} in data'.format(data, CONST_NET_TOPOLOGY) )
281         sys.exit(1)
282
283     data2 = data[CONST_NET_TOPOLOGY]
284     if not CONST_TOPOLOGY in data2:
285         printError( '{}\n\nError: did not find {} in data2'.format(data2, CONST_TOPOLOGY) )
286         sys.exit(1)
287
288     jsonTopologyNodes = data2[CONST_TOPOLOGY]
289
290 # --
291
292 def buildDpidCache():
293     global jsonTopologyNodes
294     global nodeIdToDpidCache
295
296     # only needed if not parsing operational tree
297     if getMdsalTreeType() == CONST_OPERATIONAL:
298         return
299
300     jsonTopologyNodesSave = jsonTopologyNodes
301     grabTopologyJson(CONST_OPERATIONAL)
302     jsonTopologyNodesLocal = jsonTopologyNodes
303     jsonTopologyNodes = jsonTopologyNodesSave
304
305     for nodeDict in jsonTopologyNodesLocal:
306         if nodeDict.get('topology-id') != 'ovsdb:1':
307             continue
308         for node in nodeDict.get('node', []):
309             if node.get('node-id') is None or node.get('ovsdb:datapath-id') is None:
310                 continue
311             nodeIdToDpidCache[ node.get('node-id') ] = node.get('ovsdb:datapath-id')
312
313 # --
314
315 def parseTopologyJson(mdsalTreeType):
316     for nodeDict in jsonTopologyNodes:
317         if not 'topology-id' in nodeDict:
318             continue
319         prtLn('{} {} keys are: {}'.format(mdsalTreeType, nodeDict['topology-id'], nodeDict.keys()), 3)
320         if 'node' in nodeDict:
321             nodeIndex = 0
322             for currNode in nodeDict['node']:
323                 parseTopologyJsonNode('', mdsalTreeType, nodeDict['topology-id'], nodeIndex, currNode)
324                 nodeIndex += 1
325             prtLn('', 2)
326         if (mdsalTreeType == CONST_OPERATIONAL) and (nodeDict['topology-id'] == 'flow:1') and ('link' in nodeDict):
327             parseTopologyJsonFlowLink(nodeDict['link'])
328
329     prtLn('', 1)
330
331 # --
332
333 def parseTopologyJsonNode(indent, mdsalTreeType, topologyId, nodeIndex, node):
334     if node.get('node-id') is None:
335         printError( 'Warning: unexpected node: {}\n'.format(node) )
336         return
337     prt('{} {} node[{}] {} '.format(indent + mdsalTreeType, topologyId, nodeIndex, node.get('node-id')), 2)
338     if 'ovsdb:bridge-name' in node:
339         prtLn('', 2)
340         parseTopologyJsonNodeBridge(indent + '  ', mdsalTreeType, topologyId, nodeIndex, node)
341     elif 'ovsdb:connection-info' in node:
342         prtLn('', 2)
343         parseTopologyJsonNodeOvsdb(indent + '  ', mdsalTreeType, topologyId, nodeIndex, node)
344     else:
345         prtLn('keys: {}'.format(node.keys()), 2)
346
347 # --
348
349 def parseTopologyJsonNodeOvsdb(indent, mdsalTreeType, topologyId, nodeIndex, node):
350     keys = node.keys()
351     keys.sort()
352     for k in keys:
353         prtLn('{}{} : {}'.format(indent, k, node[k]), 2)
354
355     connectionInfoRaw = node.get('ovsdb:connection-info')
356     connectionInfo = {}
357     if type(connectionInfoRaw) is dict:
358         connectionInfo['inetMgr'] = make_it_a_string(connectionInfoRaw.get('local-ip')) + ':' + make_it_a_string(connectionInfoRaw.get('local-port'))
359         connectionInfo['inetNode'] = make_it_a_string(connectionInfoRaw.get('remote-ip')) + ':' + make_it_a_string(connectionInfoRaw.get('remote-port'))
360     otherConfigsRaw = node.get('ovsdb:openvswitch-other-configs')
361     otherLocalIp = ''
362     if type(otherConfigsRaw) is list:
363         for currOtherConfig in otherConfigsRaw:
364             if type(currOtherConfig) is dict and \
365                     currOtherConfig.get('other-config-key') == 'local_ip':
366                 otherLocalIp = currOtherConfig.get('other-config-value')
367                 break
368
369     ovsdbNode = OvsdbNode(node.get('node-id'), connectionInfo.get('inetMgr'), connectionInfo.get('inetNode'), otherLocalIp, node.get('ovsdb:ovs-version'))
370     state.registerOvsdbNode(ovsdbNode)
371     prtLn('Added {}'.format(ovsdbNode), 1)
372
373 # --
374
375 def parseTopologyJsonNodeBridge(indent, mdsalTreeType, topologyId, nodeIndex, node):
376
377     controllerTarget = None
378     controllerConnected = None
379     controllerEntries = node.get('ovsdb:controller-entry')
380     if type(controllerEntries) is list:
381         for currControllerEntry in controllerEntries:
382             if type(currControllerEntry) is dict:
383                 controllerTarget = currControllerEntry.get('target')
384                 controllerConnected = currControllerEntry.get('is-connected')
385                 break
386
387     nodeId = node.get('node-id')
388     dpId = node.get('ovsdb:datapath-id', nodeIdToDpidCache.get(nodeId))
389     bridgeNode = BridgeNode(nodeId, dpId, node.get('ovsdb:bridge-name'), controllerTarget, controllerConnected)
390
391     keys = node.keys()
392     keys.sort()
393     for k in keys:
394         if k == 'termination-point' and len(node[k]) > 0:
395             tpIndex = 0
396             for tp in node[k]:
397                 terminationPoint = parseTopologyJsonNodeBridgeTerminationPoint('%stermination-point[%d] :' % (indent, tpIndex), mdsalTreeType, topologyId, nodeIndex, node, tp)
398
399                 # skip boring tps
400                 if terminationPoint.ofPort == CONST_TP_OF_INTERNAL and \
401                         (terminationPoint.name == 'br-ex' or terminationPoint.name == 'br-int'):
402                     pass
403                 else:
404                     bridgeNode.addTerminationPoint(terminationPoint)
405
406                 tpIndex += 1
407         else:
408             prtLn('{}{} : {}'.format(indent, k, node[k]), 2)
409
410     state.registerBridgeNode(bridgeNode)
411     prtLn('Added {}'.format(bridgeNode), 1)
412
413
414 # --
415
416 def parseTopologyJsonNodeBridgeTerminationPoint(indent, mdsalTreeType, topologyId, nodeIndex, node, tp):
417     attachedMac = ''
418     ifaceId = ''
419
420     keys = tp.keys()
421     keys.sort()
422     for k in keys:
423         if (k == 'ovsdb:port-external-ids' or k == 'ovsdb:interface-external-ids') and len(tp[k]) > 0:
424             extIdIndex = 0
425             for extId in tp[k]:
426                 prtLn('{} {}[{}] {} : {}'.format(indent, k, extIdIndex, extId.get('external-id-key'), extId.get('external-id-value')), 2)
427                 extIdIndex += 1
428
429                 if extId.get('external-id-key') == 'attached-mac':
430                     attachedMac = extId.get('external-id-value')
431                 if extId.get('external-id-key') == 'iface-id':
432                     ifaceId = extId.get('external-id-value')
433         else:
434             prtLn('{} {} : {}'.format(indent, k, tp[k]), 2)
435
436     return TerminationPoint(tp.get('ovsdb:name'),
437                             tp.get('ovsdb:ofport'),
438                             tp.get('ovsdb:interface-type', '').split('-')[-1],
439                             attachedMac, ifaceId)
440
441 # --
442
443 def parseTopologyJsonFlowLink(link):
444     linkCount = 0
445     spc = ' ' * 2
446     for currLinkDict in link:
447         linkCount += 1
448         linkId = currLinkDict.get('link-id')
449         linkDest = currLinkDict.get('destination', {}).get('dest-tp')
450         linkSrc = currLinkDict.get('source', {}).get('source-tp')
451
452         linkDestNode = currLinkDict.get('destination', {}).get('dest-node')
453         linkSrcNode = currLinkDict.get('source', {}).get('source-node')
454         prtLn('{} {} {} => {}:{} -> {}:{}'.format(spc, linkCount, linkId, linkSrcNode, linkSrc.split(':')[-1], linkDestNode, linkDest.split(':')[-1]), 3)
455
456         if linkId != linkSrc:
457             printError('Warning: ignoring link with unexpected id: %s != %s\n' % (linkId, linkSrc))
458             continue
459         else:
460             state.ofLinks[linkSrc] = linkDest
461
462 # --
463
464 def showPrettyNamesMap():
465     spc = ' ' * 2
466     if not options.useAlias or len(state.bridgeNodes) == 0:
467         return
468
469     prtLn('aliasMap:', 0)
470     resultMap = {}
471     for bridge in state.bridgeNodes.values():
472         resultMap[ bridge.alias ] = '{0: <25} {1: <7} {2}'.format(bridge.getOpenflowName(), bridge.name, bridge.dpId)
473
474     for resultMapKey in sorted(resultMap):
475         prtLn('{0}{1: <10} ->  {2}'.format(spc, resultMapKey, resultMap[resultMapKey]), 0)
476     prtLn('', 0)
477
478 # --
479
480 def showNodesPretty():
481     if len(state.ovsdbNodes) == 0:
482         showBridgeOnlyNodes()
483         return
484
485     aliasDict = { state.ovsdbNodes[nodeId].alias : nodeId for nodeId in state.ovsdbNodes.keys() }
486     aliasDictKeys = aliasDict.keys()
487     aliasDictKeys.sort()
488     for ovsAlias in aliasDictKeys:
489         ovsdbNode = state.ovsdbNodes[ aliasDict[ovsAlias] ]
490
491         prt('ovsdbNode:{} mgr:{} version:{}'.format(ovsAlias, ovsdbNode.inetMgr, ovsdbNode.ovsVersion), 0)
492         if ovsdbNode.inetNode.split(':')[0] != ovsdbNode.otherLocalIp:
493             prt(' **localIp:{}'.format(ovsdbNode.otherLocalIp), 0)
494         prtLn('', 0)
495         showPrettyBridgeNodes('  ', getNodeBridgeIds(ovsdbNode.nodeId), ovsdbNode)
496     showBridgeOnlyNodes(True)
497     prtLn('', 0)
498
499 # --
500
501 def showFlowInfoPretty():
502     global flowInfoNodes
503     spc = ' ' * 2
504
505     if not options.showFlows:
506         return
507
508     if len(flowInfoNodes) == 0:
509         prtLn('no flowInfo found\n', 0)
510         return
511
512     # translate flowKeys (openflow:123124) into their alias format
513     # then sort it and translate back, so we list them in the order
514     flowInfoNodeKeysDict = {}
515     for flowInfoNodeKey in flowInfoNodes.keys():
516         flowInfoNodeKeysDict[ showPrettyName(flowInfoNodeKey) ] = flowInfoNodeKey
517     flowInfoNodeKeysKeys = flowInfoNodeKeysDict.keys()
518     flowInfoNodeKeysKeys.sort()
519
520     flowInfoNodesKeys = [ flowInfoNodeKeysDict[ x ] for x in flowInfoNodeKeysKeys ]
521
522     nodeIdToDpidCacheReverse = {dataPathIdToOfFormat(v): k for k, v in nodeIdToDpidCache.items()}
523     nodesVisited = 0
524     for flowInfoNodeKey in flowInfoNodesKeys:
525         if nodesVisited > 0: prtLn('', 0)
526
527         nodeName = showPrettyName(flowInfoNodeKey)
528         if nodeName == flowInfoNodeKey:
529             nodeName += '  ( {} )'.format( nodeIdToDpidCacheReverse.get(flowInfoNodeKey, 'node_not_in_topology') )
530
531         prtLn('{} tree flows at {}'.format(getMdsalTreeType(), nodeName), 0)
532         flowInfoNode = flowInfoNodes[flowInfoNodeKey]
533         flowInfoTables = flowInfoNode.keys()
534         flowInfoTables.sort()
535         for flowInfoTable in flowInfoTables:
536             for rule in flowInfoNode[flowInfoTable]:
537                 prtLn('{}table {}: {}'.format(spc, flowInfoTable, rule), 0)
538         nodesVisited += 1
539
540     prtLn('', 0)
541
542 # --
543
544 def getNodeBridgeIds(nodeIdFilter = None):
545     resultMap = {}
546     for bridge in state.bridgeNodes.values():
547         if nodeIdFilter is None or nodeIdFilter in bridge.nodeId:
548             resultMap[ bridge.alias ] = bridge.nodeId
549     resultMapKeys = resultMap.keys()
550     resultMapKeys.sort()
551     return [ resultMap[x] for x in resultMapKeys ]
552
553 # --
554
555 def showPrettyBridgeNodes(indent, bridgeNodeIds, ovsdbNode = None):
556     if bridgeNodeIds is None:
557         return
558
559     for nodeId in bridgeNodeIds:
560         bridgeNode = state.bridgeNodes[nodeId]
561         prt('{}{}:{}'.format(indent, showPrettyName(nodeId), bridgeNode.name), 0)
562
563         if ovsdbNode is None or \
564                 bridgeNode.controllerTarget is None or \
565                 bridgeNode.controllerTarget == '' or \
566                 ovsdbNode.inetMgr.split(':')[0] != bridgeNode.controllerTarget.split(':')[-2] or \
567                 bridgeNode.controllerConnected != True:
568             prt(' controller:{}'.format(bridgeNode.controllerTarget), 0)
569             prt(' connected:{}'.format(bridgeNode.controllerConnected), 0)
570         prtLn('', 0)
571         showPrettyTerminationPoints(indent + '  ', bridgeNode.tps)
572
573 # --
574
575 def showBridgeOnlyNodes(showOrphansOnly = False):
576     if len(state.bridgeNodes) == 0:
577         return
578
579     # group bridges by nodeId prefix
580     resultMap = {}
581     for bridge in state.bridgeNodes.values():
582         nodePrefix = bridge.nodeId.split('/bridge/')[0]
583
584         if showOrphansOnly and nodePrefix in state.ovsdbNodes:
585             continue
586
587         if nodePrefix in resultMap:
588             resultMap[nodePrefix][bridge.alias] = bridge.nodeId
589         else:
590             resultMap[nodePrefix] = { bridge.alias: bridge.nodeId }
591     resultMapKeys = resultMap.keys()
592     resultMapKeys.sort()
593
594     if len(resultMapKeys) == 0:
595         return  #noop
596
597     for nodePrefix in resultMapKeys:
598         nodePrefixEntriesKeys = resultMap[nodePrefix].keys()
599         nodePrefixEntriesKeys.sort()
600         # prtLn('Bridges in {}: {}'.format(nodePrefix, nodePrefixEntriesKeys), 0)
601         prtLn('Bridges in {}'.format(nodePrefix), 0)
602         nodeIds = [ resultMap[nodePrefix][nodePrefixEntry] for nodePrefixEntry in nodePrefixEntriesKeys ]
603         showPrettyBridgeNodes('  ', nodeIds)
604
605     prtLn('', 0)
606
607 # --
608
609 def showPrettyTerminationPoints(indent, tps):
610
611     tpsDict = {}
612     for tp in tps:
613         tpsDict[ tp.ofPort ] = tp
614
615     tpDictKeys = tpsDict.keys()
616     tpDictKeys.sort()
617     for tpKey in tpDictKeys:
618         tp = tpsDict[tpKey]
619         prt('{}of:{} {}'.format(indent, tp.ofPort, tp.name), 0)
620         if tp.mac != '':
621             prt(' {}:{}'.format('mac', tp.mac), 0)
622         if tp.ifaceId != '':
623             prt(' {}:{}'.format('ifaceId', tp.ifaceId), 0)
624
625         prtLn('', 0)
626
627 # --
628
629 def dataPathIdToOfFormat(dpId):
630     return 'openflow:' + str( int('0x' + dpId.replace(':',''), 16) )
631
632 # --
633
634 def showPrettyName(name):
635     if not options.useAlias:
636         return name
637
638     # handle both openflow:138604958315853:2 and openflow:138604958315853 (aka dpid)
639     # also handle ovsdb://uuid/5c72ec51-1e71-4a04-ab0b-b044fb5f4dc0/bridge/br-int  (aka nodeId)
640     #
641     nameSplit = name.split(':')
642     ofName = ':'.join(nameSplit[:2])
643     ofPart = ''
644     if len(nameSplit) > 2:
645         ofPart = ':' + ':'.join(nameSplit[2:])
646
647     for bridge in state.bridgeNodes.values():
648         if bridge.getOpenflowName() == ofName or bridge.nodeId == name:
649             return '{}{}'.format(bridge.alias, ofPart)
650
651     # not found, return paramIn
652     return name
653
654 # --
655
656 def showOfLinks():
657     spc = ' ' * 2
658     ofLinksKeys = state.ofLinks.keys()
659     ofLinksKeys.sort()
660     ofLinksKeysVisited = set()
661
662     if len(ofLinksKeys) == 0:
663         # prtLn('no ofLinks found\n', 0)
664         return
665
666     prtLn('ofLinks (discover via lldp):', 0)
667     for ofLinkKey in ofLinksKeys:
668         if ofLinkKey in ofLinksKeysVisited:
669             continue
670         if state.ofLinks.get( state.ofLinks[ofLinkKey] ) == ofLinkKey:
671             prtLn('{}{} <-> {}'.format(spc, showPrettyName(ofLinkKey), showPrettyName(state.ofLinks[ofLinkKey])), 0)
672             ofLinksKeysVisited.add(state.ofLinks[ofLinkKey])
673         else:
674             prtLn('{}{} -> {}'.format(spc, showPrettyName(ofLinkKey), showPrettyName(state.ofLinks[ofLinkKey])), 0)
675         ofLinksKeysVisited.add(ofLinkKey)
676     prtLn('', 0)
677
678 # --
679
680 def parseArgv():
681     global options
682
683     parser = optparse.OptionParser(version="0.1")
684     parser.add_option("-d", "--debug", action="count", dest="debug", default=CONST_DEFAULT_DEBUG,
685                       help="Verbosity. Can be provided multiple times for more debug.")
686     parser.add_option("-n", "--noalias", action="store_false", dest="useAlias", default=True,
687                       help="Do not map nodeId of bridges to an alias")
688     parser.add_option("-i", "--ip", action="store", type="string", dest="odlIp", default="localhost",
689                       help="opendaylights ip address")
690     parser.add_option("-t", "--port", action="store", type="string", dest="odlPort", default="8080",
691                       help="opendaylights listening tcp port on restconf northbound")
692     parser.add_option("-u", "--user", action="store", type="string", dest="odlUsername", default="admin",
693                       help="opendaylight restconf username")
694     parser.add_option("-p", "--password", action="store", type="string", dest="odlPassword", default="admin",
695                       help="opendaylight restconf password")
696     parser.add_option("-c", "--config", action="store_true", dest="useConfigTree", default=False,
697                       help="parse mdsal restconf config tree instead of operational tree")
698     parser.add_option("-f", "--hide-flows", action="store_false", dest="showFlows", default=True,
699                       help="hide flows")
700
701     (options, args) = parser.parse_args(sys.argv)
702     prtLn('argv options:{} args:{}'.format(options, args), 2)
703
704 # --
705
706 def doMain():
707     global state
708
709     state = State()
710     parseArgv()
711     buildDpidCache()
712     grabTopologyJson(getMdsalTreeType())
713     grabInventoryJson(getMdsalTreeType())
714     parseTopologyJson(getMdsalTreeType())
715     parseInventoryJson(getMdsalTreeType())
716     showPrettyNamesMap()
717     showNodesPretty()
718     showFlowInfoPretty()
719     showOfLinks()
720
721 # --
722
723 if __name__ == "__main__":
724     doMain()
725     sys.exit(0)