--- /dev/null
+#!/usr/bin/env python
+
+import urllib2, base64, json, sys, optparse
+
+# globals
+CONST_DEFAULT_DEBUG=0
+options = None
+state = None
+jsonTopologyNodes = []
+jsonInventoryNodes = []
+flowInfoNodes = {}
+nodeIdToDpidCache = {}
+
+CONST_OPERATIONAL = 'operational'
+CONST_CONFIG = 'config'
+CONST_NET_TOPOLOGY = 'network-topology'
+CONST_TOPOLOGY = 'topology'
+CONST_TP_OF_INTERNAL = 65534
+CONST_ALIASES = ['alpha', 'bravo', 'charlie', 'delta', 'echo', 'foxtrot', 'golf', 'hotel', 'india', 'juliet',
+ 'kilo', 'lima', 'mike', 'november', 'oscar', 'papa', 'quebec', 'romeo', 'sierra', 'tango',
+ 'uniform', 'victor', 'whiskey', 'xray', 'yankee', 'zulu']
+
+
+class State:
+ def __init__(self):
+ self.nextAliasIndex = 0
+ self.nextAliasWrap = 0
+ self.nodeIdToAlias = {}
+
+ self.bridgeNodes = {}
+ self.ovsdbNodes = {}
+ self.ofLinks = {}
+
+ def __repr__(self):
+ return 'State {}:{} {}:{} {}:{} {}:{} {}:{}'.format(
+ 'nextAliasIndex', self.nextAliasIndex,
+ 'nextAliasWrap', self.nextAliasWrap,
+ 'bridgeNodes_ids', self.bridgeNodes.keys(),
+ 'ovsdbNodes_ids', self.ovsdbNodes.keys(),
+ 'nodeIdToAlias', self.nodeIdToAlias)
+
+ def registerBridgeNode(self, bridgeNode):
+ self.bridgeNodes[bridgeNode.nodeId] = bridgeNode
+
+ def registerOvsdbNode(self, ovsdbNode):
+ self.ovsdbNodes[ovsdbNode.nodeId] = ovsdbNode
+
+ def getNextAlias(self, nodeId):
+ result = CONST_ALIASES[ self.nextAliasIndex ]
+ if self.nextAliasWrap > 0:
+ result += '_' + str(self.nextAliasWrap)
+
+ if CONST_ALIASES[ self.nextAliasIndex ] == CONST_ALIASES[-1]:
+ self.nextAliasIndex = 0
+ self.nextAliasWrap += 1
+ else:
+ self.nextAliasIndex += 1
+
+ self.nodeIdToAlias[ nodeId ] = result
+ return result
+
+# --
+
+class TerminationPoint:
+ def __init__(self, name, ofPort, tpType, mac='', ifaceId=''):
+ self.name = name
+ self.ofPort = ofPort
+ self.tpType = tpType
+ self.mac = mac
+ self.ifaceId = ifaceId
+
+ def __repr__(self):
+ result = '{} {}:{}'.format(self.name, 'of', self.ofPort)
+
+ if self.tpType != '':
+ result += ' {}:{}'.format('type', self.tpType)
+ if self.mac != '':
+ result += ' {}:{}'.format('mac', self.mac)
+ if self.ifaceId != '':
+ result += ' {}:{}'.format('ifaceId', self.ifaceId)
+
+ return '{' + result + '}'
+
+# --
+
+class BridgeNode:
+ def __init__(self, nodeId, dpId, name, controllerTarget, controllerConnected):
+ global state
+ self.alias = state.getNextAlias(nodeId)
+ self.nodeId = nodeId
+ self.dpId = dpId
+ self.name = name
+ self.controllerTarget = controllerTarget
+ self.controllerConnected = controllerConnected
+ self.tps = []
+
+ def getOpenflowName(self):
+ if self.dpId is None:
+ return self.nodeId
+ return dataPathIdToOfFormat(self.dpId)
+
+ def addTerminationPoint(self, terminationPoint):
+ self.tps.append(terminationPoint)
+
+ def __repr__(self):
+ return 'BridgeNode {}:{} {}:{} {}:{} {}:{} {}:{} {}:{} {}:{} {}:{}'.format(
+ 'alias', self.alias,
+ 'nodeId', self.nodeId,
+ 'dpId', self.dpId,
+ 'openflowName', self.getOpenflowName(),
+ 'name', self.name,
+ 'controllerTarget', self.controllerTarget,
+ 'controllerConnected', self.controllerConnected,
+ 'tps', self.tps)
+
+# --
+
+class OvsdbNode:
+ def __init__(self, nodeId, inetMgr, inetNode, otherLocalIp, ovsVersion):
+ global state
+ if inetNode != '':
+ self.alias = inetNode
+ else:
+ self.alias = nodeId
+ self.nodeId = nodeId
+ self.inetMgr = inetMgr
+ self.inetNode = inetNode
+ self.otherLocalIp = otherLocalIp
+ self.ovsVersion = ovsVersion
+
+ def __repr__(self):
+ return 'OvsdbNode {}:{} {}:{} {}:{} {}:{} {}:{} {}:{}'.format(
+ 'alias', self.alias,
+ 'nodeId', self.nodeId,
+ 'inetMgr', self.inetMgr,
+ 'inetNode', self.inetNode,
+ 'otherLocalIp', self.otherLocalIp,
+ 'ovsVersion', self.ovsVersion)
+
+# ======================================================================
+
+def printError(msg):
+ sys.stderr.write(msg)
+
+# ======================================================================
+
+def prt(msg, logLevel=0):
+ prtCommon(msg, logLevel)
+def prtLn(msg, logLevel=0):
+ prtCommon('{}\n'.format(msg), logLevel)
+def prtCommon(msg, logLevel):
+ if options.debug >= logLevel:
+ sys.stdout.write(msg)
+
+# ======================================================================
+
+def getMdsalTreeType():
+ if options.useConfigTree:
+ return CONST_CONFIG
+ return CONST_OPERATIONAL
+
+# --
+
+def grabJson(url):
+
+ try:
+ request = urllib2.Request(url)
+ # You need the replace to handle encodestring adding a trailing newline
+ # (https://docs.python.org/2/library/base64.html#base64.encodestring)
+ base64string = base64.encodestring('{}:{}'.format(options.odlUsername, options.odlPassword)).replace('\n', '')
+ request.add_header('Authorization', 'Basic {}'.format(base64string))
+ result = urllib2.urlopen(request)
+ except urllib2.URLError, e:
+ printError('Unable to send request: {}\n'.format(e))
+ sys.exit(1)
+
+ if (result.code != 200):
+ printError( '{}\n{}\n\nError: unexpected code: {}\n'.format(result.info(), result.read(), result.code) )
+ sys.exit(1)
+
+ data = json.load(result)
+ prtLn(data, 4)
+ return data
+
+# --
+
+def grabInventoryJson(mdsalTreeType):
+ global jsonInventoryNodes
+
+ url = 'http://{}:{}/restconf/{}/opendaylight-inventory:nodes/'.format(options.odlIp, options.odlPort, mdsalTreeType)
+ data = grabJson(url)
+
+ if not 'nodes' in data:
+ printError( '{}\n\nError: did not find nodes in {}'.format(data, url) )
+ sys.exit(1)
+
+ data2 = data['nodes']
+ if not 'node' in data2:
+ printError( '{}\n\nError: did not find node in {}'.format(data2, url) )
+ sys.exit(1)
+
+ jsonInventoryNodes = data2['node']
+
+# --
+
+def parseInventoryJson(mdsalTreeType):
+ global jsonInventoryNodes
+ global flowInfoNodes
+
+ for nodeDict in jsonInventoryNodes:
+ if not 'id' in nodeDict:
+ continue
+
+ bridgeOfId = nodeDict.get('id')
+ prtLn('inventory node {} has keys {}'.format(bridgeOfId, nodeDict.keys()), 3)
+
+ # locate bridge Node
+ bridgeNodeId = None
+ bridgeNode = None
+ for currNodeId in state.bridgeNodes.keys():
+ if state.bridgeNodes[currNodeId].getOpenflowName() == bridgeOfId:
+ bridgeNodeId = currNodeId
+ bridgeNode = state.bridgeNodes[currNodeId]
+ break
+
+ if bridgeNodeId is None:
+ prtLn('inventory node {}'.format(bridgeOfId), 1)
+ else:
+ prtLn('inventory node {}, aka {}, aka {}'.format(bridgeOfId, bridgeNodeId, showPrettyName(bridgeNodeId)), 1)
+
+ flowInfoNode = {}
+
+ indent = ' ' * 2
+ prtLn('{}features: {}'.format(indent, nodeDict.get('flow-node-inventory:switch-features', {})), 2)
+ prt('{}sw: {}'.format(indent, nodeDict.get('flow-node-inventory:software')), 2)
+ prt('{}hw: {}'.format(indent, nodeDict.get('flow-node-inventory:hardware')), 2)
+ prt('{}manuf: {}'.format(indent, nodeDict.get('flow-node-inventory:manufacturer')), 2)
+ prtLn('{}ip: {}'.format(indent, nodeDict.get('flow-node-inventory:ip-address')), 2)
+
+
+ for inventoryEntry in nodeDict.get('flow-node-inventory:table', []):
+ if 'id' in inventoryEntry:
+ currTableId = inventoryEntry.get('id')
+ for currFlow in inventoryEntry.get('flow', []):
+ prtLn('{}table {}: {}'.format(indent, currTableId, currFlow.get('id')), 1)
+ prtLn('{}{}'.format(indent * 2, currFlow), 2)
+
+ if currTableId in flowInfoNode:
+ flowInfoNode[ currTableId ].append( currFlow.get('id') )
+ else:
+ flowInfoNode[ currTableId ] = [ currFlow.get('id') ]
+
+ prtLn('', 1)
+
+ for currTableId in flowInfoNode.keys():
+ flowInfoNode[currTableId].sort()
+
+ # store info collected in flowInfoNodes
+ flowInfoNodes[bridgeOfId] = flowInfoNode
+
+# --
+
+def grabTopologyJson(mdsalTreeType):
+ global jsonTopologyNodes
+
+ url = 'http://{}:{}/restconf/{}/network-topology:network-topology/'.format(options.odlIp, options.odlPort, mdsalTreeType)
+ data = grabJson(url)
+
+ if not CONST_NET_TOPOLOGY in data:
+ printError( '{}\n\nError: did not find {} in data'.format(data, CONST_NET_TOPOLOGY) )
+ sys.exit(1)
+
+ data2 = data[CONST_NET_TOPOLOGY]
+ if not CONST_TOPOLOGY in data2:
+ printError( '{}\n\nError: did not find {} in data2'.format(data2, CONST_TOPOLOGY) )
+ sys.exit(1)
+
+ jsonTopologyNodes = data2[CONST_TOPOLOGY]
+
+# --
+
+def buildDpidCache():
+ global jsonTopologyNodes
+ global nodeIdToDpidCache
+
+ # only needed if not parsing operational tree
+ if getMdsalTreeType() == CONST_OPERATIONAL:
+ return
+
+ jsonTopologyNodesSave = jsonTopologyNodes
+ grabTopologyJson(CONST_OPERATIONAL)
+ jsonTopologyNodesLocal = jsonTopologyNodes
+ jsonTopologyNodes = jsonTopologyNodesSave
+
+ for nodeDict in jsonTopologyNodesLocal:
+ if nodeDict.get('topology-id') != 'ovsdb:1':
+ continue
+ for node in nodeDict.get('node', []):
+ if node.get('node-id') is None or node.get('ovsdb:datapath-id') is None:
+ continue
+ nodeIdToDpidCache[ node.get('node-id') ] = node.get('ovsdb:datapath-id')
+
+# --
+
+def parseTopologyJson(mdsalTreeType):
+ for nodeDict in jsonTopologyNodes:
+ if not 'topology-id' in nodeDict:
+ continue
+ prtLn('{} {} keys are: {}'.format(mdsalTreeType, nodeDict['topology-id'], nodeDict.keys()), 3)
+ if 'node' in nodeDict:
+ nodeIndex = 0
+ for currNode in nodeDict['node']:
+ parseTopologyJsonNode('', mdsalTreeType, nodeDict['topology-id'], nodeIndex, currNode)
+ nodeIndex += 1
+ prtLn('', 2)
+ if (mdsalTreeType == CONST_OPERATIONAL) and (nodeDict['topology-id'] == 'flow:1') and ('link' in nodeDict):
+ parseTopologyJsonFlowLink(nodeDict['link'])
+
+ prtLn('', 1)
+
+# --
+
+def parseTopologyJsonNode(indent, mdsalTreeType, topologyId, nodeIndex, node):
+ if node.get('node-id') is None:
+ printError( 'Warning: unexpected node: {}\n'.format(node) )
+ return
+ prt('{} {} node[{}] {} '.format(indent + mdsalTreeType, topologyId, nodeIndex, node.get('node-id')), 2)
+ if 'ovsdb:bridge-name' in node:
+ prtLn('', 2)
+ parseTopologyJsonNodeBridge(indent + ' ', mdsalTreeType, topologyId, nodeIndex, node)
+ elif 'ovsdb:connection-info' in node:
+ prtLn('', 2)
+ parseTopologyJsonNodeOvsdb(indent + ' ', mdsalTreeType, topologyId, nodeIndex, node)
+ else:
+ prtLn('keys: {}'.format(node.keys()), 2)
+
+# --
+
+def parseTopologyJsonNodeOvsdb(indent, mdsalTreeType, topologyId, nodeIndex, node):
+ keys = node.keys()
+ keys.sort()
+ for k in keys:
+ prtLn('{}{} : {}'.format(indent, k, node[k]), 2)
+
+ connectionInfoRaw = node.get('ovsdb:connection-info')
+ connectionInfo = {}
+ if type(connectionInfoRaw) is dict:
+ connectionInfo['inetMgr'] = connectionInfoRaw.get('local-ip') + ':' + str( connectionInfoRaw.get('local-port') )
+ connectionInfo['inetNode'] = connectionInfoRaw.get('remote-ip') + ':' + str( connectionInfoRaw.get('remote-port') )
+ otherConfigsRaw = node.get('ovsdb:openvswitch-other-configs')
+ otherLocalIp = ''
+ if type(otherConfigsRaw) is list:
+ for currOtherConfig in otherConfigsRaw:
+ if type(currOtherConfig) is dict and \
+ currOtherConfig.get('other-config-key') == 'local_ip':
+ otherLocalIp = currOtherConfig.get('other-config-value')
+ break
+
+ ovsdbNode = OvsdbNode(node.get('node-id'), connectionInfo.get('inetMgr'), connectionInfo.get('inetNode'), otherLocalIp, node.get('ovsdb:ovs-version'))
+ state.registerOvsdbNode(ovsdbNode)
+ prtLn('Added {}'.format(ovsdbNode), 1)
+
+# --
+
+def parseTopologyJsonNodeBridge(indent, mdsalTreeType, topologyId, nodeIndex, node):
+
+ controllerTarget = None
+ controllerConnected = None
+ controllerEntries = node.get('ovsdb:controller-entry')
+ if type(controllerEntries) is list:
+ for currControllerEntry in controllerEntries:
+ if type(currControllerEntry) is dict:
+ controllerTarget = currControllerEntry.get('target')
+ controllerConnected = currControllerEntry.get('is-connected')
+ break
+
+ nodeId = node.get('node-id')
+ dpId = node.get('ovsdb:datapath-id', nodeIdToDpidCache.get(nodeId))
+ bridgeNode = BridgeNode(nodeId, dpId, node.get('ovsdb:bridge-name'), controllerTarget, controllerConnected)
+
+ keys = node.keys()
+ keys.sort()
+ for k in keys:
+ if k == 'termination-point' and len(node[k]) > 0:
+ tpIndex = 0
+ for tp in node[k]:
+ terminationPoint = parseTopologyJsonNodeBridgeTerminationPoint('%stermination-point[%d] :' % (indent, tpIndex), mdsalTreeType, topologyId, nodeIndex, node, tp)
+
+ # skip boring tps
+ if terminationPoint.ofPort == CONST_TP_OF_INTERNAL and \
+ (terminationPoint.name == 'br-ex' or terminationPoint.name == 'br-int'):
+ pass
+ else:
+ bridgeNode.addTerminationPoint(terminationPoint)
+
+ tpIndex += 1
+ else:
+ prtLn('{}{} : {}'.format(indent, k, node[k]), 2)
+
+ state.registerBridgeNode(bridgeNode)
+ prtLn('Added {}'.format(bridgeNode), 1)
+
+
+# --
+
+def parseTopologyJsonNodeBridgeTerminationPoint(indent, mdsalTreeType, topologyId, nodeIndex, node, tp):
+ attachedMac = ''
+ ifaceId = ''
+
+ keys = tp.keys()
+ keys.sort()
+ for k in keys:
+ if (k == 'ovsdb:port-external-ids' or k == 'ovsdb:interface-external-ids') and len(tp[k]) > 0:
+ extIdIndex = 0
+ for extId in tp[k]:
+ prtLn('{} {}[{}] {} : {}'.format(indent, k, extIdIndex, extId.get('external-id-key'), extId.get('external-id-value')), 2)
+ extIdIndex += 1
+
+ if extId.get('external-id-key') == 'attached-mac':
+ attachedMac = extId.get('external-id-value')
+ if extId.get('external-id-key') == 'iface-id':
+ ifaceId = extId.get('external-id-value')
+ else:
+ prtLn('{} {} : {}'.format(indent, k, tp[k]), 2)
+
+ return TerminationPoint(tp.get('ovsdb:name'),
+ tp.get('ovsdb:ofport'),
+ tp.get('ovsdb:interface-type', '').split('-')[-1],
+ attachedMac, ifaceId)
+
+# --
+
+def parseTopologyJsonFlowLink(link):
+ linkCount = 0
+ spc = ' ' * 2
+ for currLinkDict in link:
+ linkCount += 1
+ linkId = currLinkDict.get('link-id')
+ linkDest = currLinkDict.get('destination', {}).get('dest-tp')
+ linkSrc = currLinkDict.get('source', {}).get('source-tp')
+
+ linkDestNode = currLinkDict.get('destination', {}).get('dest-node')
+ linkSrcNode = currLinkDict.get('source', {}).get('source-node')
+ prtLn('{} {} {} => {}:{} -> {}:{}'.format(spc, linkCount, linkId, linkSrcNode, linkSrc.split(':')[-1], linkDestNode, linkDest.split(':')[-1]), 3)
+
+ if linkId != linkSrc:
+ printError('Warning: ignoring link with unexpected id: %s != %s\n' % (linkId, linkSrc))
+ continue
+ else:
+ state.ofLinks[linkSrc] = linkDest
+
+# --
+
+def showPrettyNamesMap():
+ spc = ' ' * 2
+ if not options.useAlias or len(state.bridgeNodes) == 0:
+ return
+
+ prtLn('aliasMap:', 0)
+ resultMap = {}
+ for bridge in state.bridgeNodes.values():
+ resultMap[ bridge.alias ] = bridge.getOpenflowName()
+
+ resultMapKeys = resultMap.keys()
+ resultMapKeys.sort()
+
+ for resultMapKey in resultMapKeys:
+ prtLn('{0}{1: <10} -> {2}'.format(spc, resultMapKey, resultMap[resultMapKey]), 0)
+ prtLn('', 0)
+
+# --
+
+def showNodesPretty():
+ if len(state.ovsdbNodes) == 0:
+ showBridgeOnlyNodes()
+ return
+
+ aliasDict = { state.ovsdbNodes[nodeId].alias : nodeId for nodeId in state.ovsdbNodes.keys() }
+ aliasDictKeys = aliasDict.keys()
+ aliasDictKeys.sort()
+ for ovsAlias in aliasDictKeys:
+ ovsdbNode = state.ovsdbNodes[ aliasDict[ovsAlias] ]
+
+ prt('ovsdbNode:{} mgr:{} version:{}'.format(ovsAlias, ovsdbNode.inetMgr, ovsdbNode.ovsVersion), 0)
+ if ovsdbNode.inetNode.split(':')[0] != ovsdbNode.otherLocalIp:
+ prt(' **localIp:{}'.format(ovsdbNode.otherLocalIp), 0)
+ prtLn('', 0)
+ showPrettyBridgeNodes(' ', getNodeBridgeIds(ovsdbNode.nodeId), ovsdbNode)
+ showBridgeOnlyNodes(True)
+ prtLn('', 0)
+
+# --
+
+def showFlowInfoPretty():
+ global flowInfoNodes
+ spc = ' ' * 2
+
+ if not options.showFlows:
+ return
+
+ if len(flowInfoNodes) == 0:
+ prtLn('no flowInfo found\n', 0)
+ return
+
+ # translate flowKeys (openflow:123124) into their alias format
+ # then sort it and translate back, so we list them in the order
+ flowInfoNodeKeysDict = {}
+ for flowInfoNodeKey in flowInfoNodes.keys():
+ flowInfoNodeKeysDict[ showPrettyName(flowInfoNodeKey) ] = flowInfoNodeKey
+ flowInfoNodeKeysKeys = flowInfoNodeKeysDict.keys()
+ flowInfoNodeKeysKeys.sort()
+
+ flowInfoNodesKeys = [ flowInfoNodeKeysDict[ x ] for x in flowInfoNodeKeysKeys ]
+
+ nodeIdToDpidCacheReverse = {dataPathIdToOfFormat(v): k for k, v in nodeIdToDpidCache.items()}
+ nodesVisited = 0
+ for flowInfoNodeKey in flowInfoNodesKeys:
+ if nodesVisited > 0: prtLn('', 0)
+
+ nodeName = showPrettyName(flowInfoNodeKey)
+ if nodeName == flowInfoNodeKey:
+ nodeName += ' ( {} )'.format( nodeIdToDpidCacheReverse.get(flowInfoNodeKey, 'node_not_in_topology') )
+
+ prtLn('{} tree flows at {}'.format(getMdsalTreeType(), nodeName), 0)
+ flowInfoNode = flowInfoNodes[flowInfoNodeKey]
+ flowInfoTables = flowInfoNode.keys()
+ flowInfoTables.sort()
+ for flowInfoTable in flowInfoTables:
+ for rule in flowInfoNode[flowInfoTable]:
+ prtLn('{}table {}: {}'.format(spc, flowInfoTable, rule), 0)
+ nodesVisited += 1
+
+ prtLn('', 0)
+
+# --
+
+def getNodeBridgeIds(nodeIdFilter = None):
+ resultMap = {}
+ for bridge in state.bridgeNodes.values():
+ if nodeIdFilter is None or nodeIdFilter in bridge.nodeId:
+ resultMap[ bridge.alias ] = bridge.nodeId
+ resultMapKeys = resultMap.keys()
+ resultMapKeys.sort()
+ return [ resultMap[x] for x in resultMapKeys ]
+
+# --
+
+def showPrettyBridgeNodes(indent, bridgeNodeIds, ovsdbNode = None):
+ if bridgeNodeIds is None:
+ return
+
+ for nodeId in bridgeNodeIds:
+ bridgeNode = state.bridgeNodes[nodeId]
+ prt('{}{}:{}'.format(indent, showPrettyName(nodeId), bridgeNode.name), 0)
+
+ if ovsdbNode is None or \
+ bridgeNode.controllerTarget is None or \
+ bridgeNode.controllerTarget == '' or \
+ ovsdbNode.inetMgr.split(':')[0] != bridgeNode.controllerTarget.split(':')[-2] or \
+ bridgeNode.controllerConnected != True:
+ prt(' controller:{}'.format(bridgeNode.controllerTarget), 0)
+ prt(' connected:{}'.format(bridgeNode.controllerConnected), 0)
+ prtLn('', 0)
+ showPrettyTerminationPoints(indent + ' ', bridgeNode.tps)
+
+# --
+
+def showBridgeOnlyNodes(showOrphansOnly = False):
+ if len(state.bridgeNodes) == 0:
+ return
+
+ # group bridges by nodeId prefix
+ resultMap = {}
+ for bridge in state.bridgeNodes.values():
+ nodePrefix = bridge.nodeId.split('/bridge/')[0]
+
+ if showOrphansOnly and nodePrefix in state.ovsdbNodes:
+ continue
+
+ if nodePrefix in resultMap:
+ resultMap[nodePrefix][bridge.alias] = bridge.nodeId
+ else:
+ resultMap[nodePrefix] = { bridge.alias: bridge.nodeId }
+ resultMapKeys = resultMap.keys()
+ resultMapKeys.sort()
+
+ if len(resultMapKeys) == 0:
+ return #noop
+
+ for nodePrefix in resultMapKeys:
+ nodePrefixEntriesKeys = resultMap[nodePrefix].keys()
+ nodePrefixEntriesKeys.sort()
+ # prtLn('Bridges in {}: {}'.format(nodePrefix, nodePrefixEntriesKeys), 0)
+ prtLn('Bridges in {}'.format(nodePrefix), 0)
+ nodeIds = [ resultMap[nodePrefix][nodePrefixEntry] for nodePrefixEntry in nodePrefixEntriesKeys ]
+ showPrettyBridgeNodes(' ', nodeIds)
+
+ prtLn('', 0)
+
+# --
+
+def showPrettyTerminationPoints(indent, tps):
+
+ tpsDict = {}
+ for tp in tps:
+ tpsDict[ tp.ofPort ] = tp
+
+ tpDictKeys = tpsDict.keys()
+ tpDictKeys.sort()
+ for tpKey in tpDictKeys:
+ tp = tpsDict[tpKey]
+ prt('{}of:{} {}'.format(indent, tp.ofPort, tp.name), 0)
+ if tp.mac != '':
+ prt(' {}:{}'.format('mac', tp.mac), 0)
+ if tp.ifaceId != '':
+ prt(' {}:{}'.format('ifaceId', tp.ifaceId), 0)
+
+ prtLn('', 0)
+
+# --
+
+def dataPathIdToOfFormat(dpId):
+ return 'openflow:' + str( int('0x' + dpId.replace(':',''), 16) )
+
+# --
+
+def showPrettyName(name):
+ if not options.useAlias:
+ return name
+
+ # handle both openflow:138604958315853:2 and openflow:138604958315853 (aka dpid)
+ # also handle ovsdb://uuid/5c72ec51-1e71-4a04-ab0b-b044fb5f4dc0/bridge/br-int (aka nodeId)
+ #
+ nameSplit = name.split(':')
+ ofName = ':'.join(nameSplit[:2])
+ ofPart = ''
+ if len(nameSplit) > 2:
+ ofPart = ':' + ':'.join(nameSplit[2:])
+
+ for bridge in state.bridgeNodes.values():
+ if bridge.getOpenflowName() == ofName or bridge.nodeId == name:
+ return '{}{}'.format(bridge.alias, ofPart)
+
+ # not found, return paramIn
+ return name
+
+# --
+
+def showOfLinks():
+ spc = ' ' * 2
+ ofLinksKeys = state.ofLinks.keys()
+ ofLinksKeys.sort()
+ ofLinksKeysVisited = set()
+
+ if len(ofLinksKeys) == 0:
+ # prtLn('no ofLinks found\n', 0)
+ return
+
+ prtLn('ofLinks (discover via lldp):', 0)
+ for ofLinkKey in ofLinksKeys:
+ if ofLinkKey in ofLinksKeysVisited:
+ continue
+ if state.ofLinks.get( state.ofLinks[ofLinkKey] ) == ofLinkKey:
+ prtLn('{}{} <-> {}'.format(spc, showPrettyName(ofLinkKey), showPrettyName(state.ofLinks[ofLinkKey])), 0)
+ ofLinksKeysVisited.add(state.ofLinks[ofLinkKey])
+ else:
+ prtLn('{}{} -> {}'.format(spc, showPrettyName(ofLinkKey), showPrettyName(state.ofLinks[ofLinkKey])), 0)
+ ofLinksKeysVisited.add(ofLinkKey)
+ prtLn('', 0)
+
+# --
+
+def parseArgv():
+ global options
+
+ parser = optparse.OptionParser(version="0.1")
+ parser.add_option("-d", "--debug", action="count", dest="debug", default=CONST_DEFAULT_DEBUG,
+ help="Verbosity. Can be provided multiple times for more debug.")
+ parser.add_option("-n", "--noalias", action="store_false", dest="useAlias", default=True,
+ help="Do not map nodeId of bridges to an alias")
+ parser.add_option("-i", "--ip", action="store", type="string", dest="odlIp", default="localhost",
+ help="opendaylights ip address")
+ parser.add_option("-t", "--port", action="store", type="string", dest="odlPort", default="8080",
+ help="opendaylights listening tcp port on restconf northbound")
+ parser.add_option("-u", "--user", action="store", type="string", dest="odlUsername", default="admin",
+ help="opendaylight restconf username")
+ parser.add_option("-p", "--password", action="store", type="string", dest="odlPassword", default="admin",
+ help="opendaylight restconf password")
+ parser.add_option("-c", "--config", action="store_true", dest="useConfigTree", default=False,
+ help="parse mdsal restconf config tree instead of operational tree")
+ parser.add_option("-f", "--hide-flows", action="store_false", dest="showFlows", default=True,
+ help="hide flows")
+
+ (options, args) = parser.parse_args(sys.argv)
+ prtLn('argv options:{} args:{}'.format(options, args), 2)
+
+# --
+
+def doMain():
+ global state
+
+ state = State()
+ parseArgv()
+ buildDpidCache()
+ grabTopologyJson(getMdsalTreeType())
+ grabInventoryJson(getMdsalTreeType())
+ parseTopologyJson(getMdsalTreeType())
+ parseInventoryJson(getMdsalTreeType())
+ showPrettyNamesMap()
+ showNodesPretty()
+ showFlowInfoPretty()
+ showOfLinks()
+
+# --
+
+if __name__ == "__main__":
+ doMain()
+ sys.exit(0)