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