Tools to troubleshoot netvirt 28/63128/9
authorVishal Thapar <vishal.thapar@ericsson.com>
Tue, 22 Aug 2017 16:09:35 +0000 (21:39 +0530)
committerSam Hague <shague@redhat.com>
Thu, 14 Dec 2017 21:07:03 +0000 (21:07 +0000)
Change-Id: Ie008f8f67dbff3ca980e3b48af121275c98e2014
Signed-off-by: Vishal Thapar <vishal.thapar@ericsson.com>
.gitignore
resources/tools/odl/__init__.py [new file with mode: 0644]
resources/tools/odl/netvirt/__init__.py [new file with mode: 0644]
resources/tools/odl/netvirt/constants.py [new file with mode: 0644]
resources/tools/odl/netvirt/ds_analyze.py [new file with mode: 0644]
resources/tools/odl/netvirt/ds_get_data.py [new file with mode: 0644]
resources/tools/odl/netvirt/flow_parser.py [new file with mode: 0644]
resources/tools/odl/netvirt/netvirt_utils.py [new file with mode: 0644]
resources/tools/odl/netvirt/ovs_get_data.py [new file with mode: 0644]
resources/tools/odl/netvirt/showOvsdbMdsal.py [new file with mode: 0644]

index a40da0d9144f704ed4c88937b02dbe78fb87b33d..464b6ec974510deb9b7ae7d20168161f4794644f 100644 (file)
@@ -17,6 +17,7 @@ opendaylight/northbound/integrationtest/logs/*
 *.iml
 *.iws
 .idea
+.pydevproject
 *.pyc
 log.html
 output.xml
diff --git a/resources/tools/odl/__init__.py b/resources/tools/odl/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/resources/tools/odl/netvirt/__init__.py b/resources/tools/odl/netvirt/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/resources/tools/odl/netvirt/constants.py b/resources/tools/odl/netvirt/constants.py
new file mode 100644 (file)
index 0000000..f5c44d0
--- /dev/null
@@ -0,0 +1,74 @@
+VIF_TYPE_TO_PREFIX = {
+                      'ovs':'tap',
+                      'vhost_user':'vhu'
+                      }
+
+VIF_TYPE = 'neutron-binding:vif-type'
+IFACE_PARENT = 'odl-interface:parent-interface'
+VIF_TYPE = 'neutron-binding:vif-type'
+IFTYPE_VLAN = 'iana-if-type:l2vlan'
+IFTYPE_TUNNEL = 'iana-if-type:tunnel'
+
+NODE_GROUP = 'flow-node-inventory:group'
+NODE_TABLE = 'flow-node-inventory:table'
+
+DSM_FILE = 0
+DSM_DSTYPE = 1
+DSM_PATH = 2
+DSM_ROOT1 = 3
+DSM_ROOT2 = 4
+"""
+   source map to fetch data from data store.
+   Format is:
+   resource-key-name:[filename,datastore_type,resource-url,container-name,list-title]
+"""
+DSMAP = {
+    'bindings': ['service-bindings.log', 'config',
+                 'interface-service-bindings:service-bindings',
+                 'service-bindings', 'services-info'],
+    'dpnendpoints': ['dpn-endpoints.log', 'config', 'itm-state:dpn-endpoints',
+                     'dpn-endpoints', 'DPN-TEPs-info'],
+    'elaninstances': ['elan-instances.log', 'config', 'elan:elan-instances',
+                      'elan-instances', 'elan-instance'],
+    'elaninterfaces': ['elan-interfaces.log', 'config', 'elan:elan-interfaces',
+                       'elan-interfaces', 'elan-interface'],
+    'fibentries': ['fibentries.log', 'config', 'odl-fib:fibEntries',
+                   'fibEntries', 'vrfTables'],
+    'ifconfig': ['iface-config.log', 'config', 'ietf-interfaces:interfaces',
+                 'interfaces', 'interface'],
+    'ifindexes': ['ifindexes.log', 'operational',
+                  'odl-interface-meta:if-indexes-interface-map',
+                  'if-indexes-interface-map', 'if-index-interface'],
+    'ifstate': ['ifstate.log', 'operational',
+                'ietf-interfaces:interfaces-state',
+                'interfaces-state', 'interface'],
+    'inventory': ['inventory-config.log', 'config',
+                  'opendaylight-inventory:nodes', 'nodes', 'node'],
+    'l3vpn': ['l3vpn-config.log', 'config', 'l3vpn:vpn-interfaces',
+              'vpn-interfaces', 'vpn-interface'],
+    'neutronports': ['neutron-ports.log', 'config', 'neutron:neutron/ports',
+                     'ports', 'port'],
+    'neutronvpn-portip': ['neutronvpn-portip-port.log', 'config',
+                          'neutronvpn:neutron-vpn-portip-port-data',
+                          'neutron-vpn-portip-port-data',
+                          'vpn-portip-to-port'],
+    'tunconfig': ['tunnel-config.log', 'config', 'itm-state:tunnel-list',
+                  'tunnel-list', 'internal-tunnel'],
+    'tunconfig-external': ['tunnel-config-external.log', 'config',
+                           'itm-state:external-tunnel-list',
+                           'external-tunnel-list', 'external-tunnel'],
+    'tunstate': ['tunnel-state.log', 'operational', 'itm-state:tunnels_state',
+                 'tunnels_state', 'state-tunnel-list'],
+    'vpninstance-to-vpnid': ['vpninstance-to-vpnid.log', 'config',
+                             'odl-l3vpn:vpn-instance-to-vpn-id',
+                             'vpn-instance-to-vpn-id', 'vpn-instance'],
+    'vpninterfaces': ['vpn-interfaces.log', 'config', 'l3vpn:vpn-interfaces',
+                      'vpn-interfaces', 'vpn-interface']
+}
+
+TABLE_MAP = {
+    'ifm': [0, 17, 220],
+    'l3vpn': [19, 20, 21, 22, 36, 81],
+    'elan': [50, 51, 52, 55],
+    'acl': [211, 212, 213, 214, 215, 241, 242, 243, 244, 245]
+}
diff --git a/resources/tools/odl/netvirt/ds_analyze.py b/resources/tools/odl/netvirt/ds_analyze.py
new file mode 100644 (file)
index 0000000..2f2dcf1
--- /dev/null
@@ -0,0 +1,414 @@
+import collections
+import constants as const
+import ds_get_data as dsg
+import flow_parser as fp
+import json
+import netvirt_utils as utils
+import ovs_get_data as og
+
+
+# Required
+ifaces = None
+ifstates = None
+
+#Optional
+ports = {}
+tunnels = {}
+confNodes = {}
+operNodes = {}
+
+
+def by_ifname(ifname):
+    print ifname
+    ifstate = ifstates.get(ifname)
+    iface = ifaces.get(ifname)
+    port = None
+    tunnel = None
+    tunState = None
+    if iface.get('type') == const.IFTYPE_VLAN:
+        ports = dsg.get_neutron_ports()
+        port = ports.get(ifname)
+    elif iface.get('type') == const.IFTYPE_TUNNEL:
+        tunnels = dsg.get_config_tunnels()
+        tunnel = tunnels.get(ifname)
+        tunStates = dsg.get_tunnel_states()
+        tunState = tunStates.get(ifname)
+    else:
+        print "UNSUPPORTED IfType"
+    return iface,ifstate,port,tunnel,tunState
+
+
+def print_keys():
+    print "InterfaceNames: ", ifaces.keys()
+    print
+    print "IfStateNames: ", ifstates.keys()
+
+
+def analyze_interface(ifname=None):
+    global ifaces,ifstates
+    ifaces = dsg.get_config_interfaces()
+    ifstates = dsg.get_interface_states()
+    if not ifname:
+        print_keys()
+        exit(1)
+    ifname = ifname[1]
+    iface,ifstate,port,tunnel,tunState = by_ifname(ifname)
+    print "InterfaceConfig: "
+    utils.pretty_print(iface)
+    print "InterfaceState: "
+    utils.pretty_print(ifstate)
+    if port:
+        print "NeutronPort: "
+        utils.pretty_print(port)
+        analyze_neutron_port(port, iface, ifstate)
+        return
+    if tunnel:
+        print "Tunnel: "
+        utils.pretty_print(tunnel)
+    if tunState:
+        print "TunState: "
+        utils.pretty_print(tunState)
+    if ifstate:
+        ncId = ifstate.get('lower-layer-if')[0]
+        nodeId = ncId[:ncId.rindex(':')]
+        analyze_inventory(nodeId, True, ncId, ifname)
+        #analyze_inventory(nodeId, False, ncId, ifname)
+
+def analyze_neutron_port(port, iface, ifstate):
+    for flow in utils.sort(get_all_flows(['all']), 'table'):
+        if ((flow.get('ifname') == port['uuid']) or
+            (flow.get('lport') and flow['lport'] == ifstate.get('if-index')) or
+            (iface['name'] == flow.get('ifname'))):
+                result = 'Table:{},FlowId:{}{}'.format(
+                flow['table'], flow['id'],
+                utils.show_optionals(flow))
+                print result
+                print 'Flow:', json.dumps(parse_flow(flow.get('flow')))
+
+
+def analyze_inventory(nodeId, isConfig=True, ncId=None, ifName=None):
+    if isConfig:
+        nodes = dsg.get_inventory_config()
+        print "Inventory Config:"
+    else:
+        print "Inventory Operational:"
+        nodes = dsg.get_inventory_oper()
+    node = nodes.get(nodeId)
+    tables = node.get(const.NODE_TABLE)
+    groups = node.get(const.NODE_GROUP)
+    flow_list = []
+    print "Flows:"
+    for table in tables:
+        for flow in table.get('flow'):
+            if not ifName or ifName in utils.nstr(flow.get('flow-name')):
+                flow_dict = {}
+                flow_dict['table'] = table['id']
+                flow_dict['id'] = flow['id']
+                flow_dict['name'] = flow.get('flow-name')
+                flow_dict['flow'] = flow
+                flow_list.append(flow_dict)
+    flows = sorted(flow_list, key=lambda x: x['table'])
+    for flow in flows:
+        print 'Table:', flow['table']
+        print 'FlowId:', flow['id'], 'FlowName:', flow.get('name')
+
+
+def get_dpn_host_mapping(oper_nodes=None):
+        nodes_dict = {}
+        nodes = oper_nodes or dsg.get_inventory_oper()
+        for node in nodes.itervalues():
+            dpnid = utils.get_dpn_from_ofnodeid(node['id'])
+            nodes_dict[dpnid] = node.get('flow-node-inventory:description', '')
+        return nodes_dict
+
+
+def get_groups(ofnodes=None):
+    of_nodes = ofnodes or dsg.get_inventory_config()
+    key ='group-id'
+    group_dict = collections.defaultdict(dict)
+    for node in of_nodes.itervalues():
+        dpnid = utils.get_dpn_from_ofnodeid(node['id'])
+        for group in node[const.NODE_GROUP]:
+            if group_dict.get(dpnid) and group_dict.get(dpnid).get(group[key]):
+                print 'Duplicate:', dpnid, group[key]
+            group_dict[dpnid][group[key]] = group
+    return dict(group_dict)
+
+
+def get_stale_flows(modules=['ifm']):
+    if not modules:
+        return 'No modules specified'
+    ifaces = {}
+    ifstates = {}
+    ifindexes = {}
+    bindings = {}
+    einsts = {}
+    eifaces = {}
+    fibentries = {}
+    vpnids = {}
+    vpninterfaces = {}
+    groups = {}
+    table_list = list(set([table for module in modules for table in const.TABLE_MAP[module]]))
+    ##table_list = [214, 244]
+    of_nodes = dsg.get_inventory_config()
+    if 'ifm' in modules:
+        ifaces = dsg.get_config_interfaces()
+        ifstates = dsg.get_interface_states()
+    if 'l3vpn' in modules:
+        ifaces = ifaces or dsg.get_config_interfaces()
+        ifindexes = ifindexes or dsg.get_ifindexes()
+        fibentries = fibentries or dsg.get_fibentries_by_label()
+        vpnids = vpnids or dsg.get_vpnids()
+        vpninterfaces = vpninterfaces or dsg.get_vpninterfaces()
+        groups = groups or get_groups(of_nodes)
+    if 'acl' in modules:
+        ifaces = ifaces or dsg.get_config_interfaces()
+        ifindexes = ifindexes or dsg.get_ifindexes()
+        einsts = einsts or dsg.get_elan_instances()
+        eifaces = eifaces or dsg.get_elan_interfaces()
+    if 'elan' in modules:
+        ifaces = ifaces or dsg.get_config_interfaces()
+        einsts = einsts or dsg.get_elan_instances()
+        eifaces = eifaces or dsg.get_elan_interfaces()
+        ifindexes = ifindexes or dsg.get_ifindexes()
+    stale_flows = []
+    for node in of_nodes.itervalues():
+        tables = [x for x in node[const.NODE_TABLE] if x['id'] in table_list]
+        for table in tables:
+            for flow in table.get('flow', []):
+                flow_dict = None
+                flow_info = {}
+                flow_info['dpnid'] = utils.get_dpn_from_ofnodeid(node['id'])
+                if 'ifm' in modules and table['id'] in const.TABLE_MAP['ifm']:
+                    flow_dict = fp.stale_ifm_flow(flow, flow_info, ifaces, ifstates)
+                if 'l3vpn' in modules and table['id'] in const.TABLE_MAP['l3vpn']:
+                    flow_dict = fp.stale_l3vpn_flow(flow, flow_info, groups, ifaces, ifindexes, vpnids, vpninterfaces, fibentries)
+                if 'elan' in modules and table['id'] in const.TABLE_MAP['elan']:
+                    flow_dict = fp.stale_elan_flow(flow, flow_info, ifaces, ifindexes, einsts, eifaces)
+                if 'acl' in modules and table['id'] in const.TABLE_MAP['acl']:
+                    flow_dict = fp.stale_acl_flow(flow, flow_info, ifaces, ifindexes, einsts, eifaces)
+                if flow_dict is not None:
+                    stale_flows.append(flow_dict)
+
+    return stale_flows
+
+
+def show_stale_bindings():
+    stale_ids, bindings = get_stale_bindings()
+    for iface_id in sorted(stale_ids):
+        for binding in bindings[iface_id].itervalues():
+            #if binding.get('bound-services'):
+            path = get_data_path('bindings', binding)
+            print json.dumps(bindings[iface_id])
+            print('http://192.168.2.32:8383/restconf/config/{}'.format(path))
+
+
+def get_stale_bindings():
+    ifaces = dsg.get_config_interfaces()
+    bindings, orphans = dsg.get_service_bindings()
+    return set(bindings.keys()) - set(ifaces.keys()), bindings
+
+
+def get_ips_for_iface(nports, ifname):
+    ips = []
+    port = nports.get(ifname) if ifname else None
+    fixed_ips = port.get('fixed-ips', []) if port else []
+    for fixed_ip in fixed_ips:
+        ips.append(fixed_ip['ip-address'])
+    return ips
+
+
+def show_link_flow_binding():
+    stale_ids, bindings = get_stale_bindings()
+    flows = get_stale_flows()
+    print len(stale_ids), len(flows)
+    for flow in flows:
+        if flow['ifname'] in stale_ids and 'bound-services' in bindings[flow['ifname']]:
+            print 'Flow with binding: ', flow['ifname']
+        else:
+            print 'Flow without binding: ', flow['ifname']
+
+
+def show_stale_flows(sort_by='table'):
+    compute_map = get_dpn_host_mapping()
+    nports = dsg.get_neutron_ports()
+    for flow in utils.sort(get_stale_flows(['acl']), sort_by):
+        host = compute_map.get(flow.get('dpnid'), flow.get('dpnid'))
+        ip_list = get_ips_for_iface(nports, flow.get('ifname'))
+        if ip_list:
+            flow['iface-ips'] = ip_list
+        result = 'Table:{},Host:{},FlowId:{}{}'.format(
+            flow['table'], host, flow['id'],
+            utils.show_optionals(flow))
+        print result
+        ##path = get_data_path('flows', flow)
+        #print('http://192.168.2.32:8383/restconf/config/{}'.format(path))
+        #print 'Flow:', json.dumps(parse_flow(flow['flow']))
+
+
+def get_all_flows(modules=['ifm']):
+    if not modules:
+        return 'No modules specified'
+    ifaces = {}
+    ifstates = {}
+    ifindexes = {}
+    bindings = {}
+    einsts = {}
+    eifaces = {}
+    fibentries = {}
+    vpnids = {}
+    vpninterfaces = {}
+    groups = {}
+    if 'all' in modules:
+        table_list = list(range(0, 255))
+    else:
+        table_list = list(set([table for module in modules for table in const.TABLE_MAP[module]]))
+    ##table_list = [214, 244]
+    of_nodes = dsg.get_inventory_config()
+    if 'ifm' in modules:
+        ifaces = dsg.get_config_interfaces()
+        ifstates = dsg.get_interface_states()
+    if 'l3vpn' in modules:
+        ifaces = ifaces or dsg.get_config_interfaces()
+        ifindexes = ifindexes or dsg.get_ifindexes()
+        fibentries = fibentries or dsg.get_fibentries_by_label()
+        vpnids = vpnids or dsg.get_vpnids()
+        vpninterfaces = vpninterfaces or dsg.get_vpninterfaces()
+        groups = groups or get_groups(of_nodes)
+    if 'acl' in modules:
+        ifaces = ifaces or dsg.get_config_interfaces()
+        ifindexes = ifindexes or dsg.get_ifindexes()
+        einsts = einsts or dsg.get_elan_instances()
+        eifaces = eifaces or dsg.get_elan_interfaces()
+    if 'elan' in modules:
+        ifaces = ifaces or dsg.get_config_interfaces()
+        einsts = einsts or dsg.get_elan_instances()
+        eifaces = eifaces or dsg.get_elan_interfaces()
+        ifindexes = ifindexes or dsg.get_ifindexes()
+    if 'all' in modules:
+        groups = groups or get_groups(of_nodes)
+        ifaces = ifaces or dsg.get_config_interfaces()
+        ifstates = ifstates or dsg.get_interface_states()
+        ifindexes = ifindexes or dsg.get_ifindexes()
+        fibentries = fibentries or dsg.get_fibentries_by_label()
+        vpnids = vpnids or dsg.get_vpnids()
+        vpninterfaces = vpninterfaces or dsg.get_vpninterfaces()
+        einsts = einsts or dsg.get_elan_instances()
+        eifaces = eifaces or dsg.get_elan_interfaces()
+    flows = []
+    for node in of_nodes.itervalues():
+        tables = [x for x in node[const.NODE_TABLE] if x['id'] in table_list]
+        for table in tables:
+            for flow in table.get('flow', []):
+                flow_dict = None
+                flow_info = {}
+                flow_info['dpnid'] = utils.get_dpn_from_ofnodeid(node['id'])
+                flow_dict = fp.get_any_flow(flow, flow_info, groups,
+                                        ifaces, ifstates, ifindexes,
+                                        fibentries, vpnids, vpninterfaces,
+                                        einsts, eifaces)
+                if flow_dict is not None:
+                    flows.append(flow_dict)
+    return flows
+
+
+def show_all_flows():
+    compute_map = get_dpn_host_mapping()
+    nports = dsg.get_neutron_ports()
+    for flow in utils.sort(get_all_flows(['all']), 'table'):
+        host = compute_map.get(flow.get('dpnid'), flow.get('dpnid'))
+        ip_list = get_ips_for_iface(nports, flow.get('ifname'))
+        if ip_list:
+            flow['iface-ips'] = ip_list
+        result = 'Table:{},Host:{},FlowId:{}{}'.format(
+            flow['table'], host, flow['id'],
+            utils.show_optionals(flow))
+        print result
+        print 'Flow:', json.dumps(parse_flow(flow['flow']))
+
+
+def show_elan_flows():
+    for flow in utils.sort(get_stale_flows(['elan']), 'table'):
+        print 'Table:', flow['table'], 'FlowId:', flow['id'], utils.show_optionals(flow)
+        print 'Flow:', json.dumps(parse_flow(flow['flow']))
+
+
+def show_elan_instances():
+    insts = dsg.get_elan_instances()
+    json.dumps(insts)
+
+
+def parse_flow(flow):
+    #parse flow fields
+    #hex(int(mask, 16) & int(data, 16))
+    if flow['cookie']:
+        utils.to_hex(flow, 'cookie')
+    # parse instructions
+    for instruction in flow['instructions'].get('instruction', []):
+        if 'write-metadata' in instruction:
+            utils.to_hex(instruction['write-metadata'],'metadata')
+            utils.to_hex(instruction['write-metadata'],'metadata-mask')
+        if 'apply-actions' in instruction:
+            for action in instruction['apply-actions'].get('action', []):
+                if 'openflowplugin-extension-nicira-action:nx-reg-load' in action:
+                    utils.to_hex(action['openflowplugin-extension-nicira-action:nx-reg-load'], 'value')
+    # parse matches
+    if 'metadata' in flow['match']:
+        metadata = flow['match']['metadata']
+        utils.to_hex(metadata,'metadata')
+        utils.to_hex(metadata,'metadata-mask')
+
+    for ofex in flow['match'].get('openflowplugin-extension-general:extension-list', []):
+        if ofex['extension-key'] == 'openflowplugin-extension-nicira-match:nxm-nx-reg6-key':
+            utils.to_hex(ofex['extension']['openflowplugin-extension-nicira-match:nxm-nx-reg'], 'value')
+
+    return flow
+
+
+def get_data_path(res_type, data):
+    if res_type == 'bindings':
+        return 'interface-service-bindings:service-bindings/services-info/{}/{}'.format(data['interface-name'],data['service-mode'])
+    elif res_type == 'flows':
+        return 'opendaylight-inventory:nodes/node/openflow:{}/flow-node-inventory:table/{}/flow/{}'.format(data['dpnid'],data['table'],data['id'])
+
+
+# Sample method that shows how to use
+def show_all_tables():
+    of_nodes = dsg.get_inventory_config()
+    tables = set()
+    for node in of_nodes.itervalues():
+        for table in node[const.NODE_TABLE]:
+            if table.get('flow'):
+                tables.add(table['id'])
+    print list(tables)
+
+
+def show_all_groups():
+    of_nodes = dsg.get_inventory_config()
+    groups = get_groups(of_nodes)
+    for dpn in groups:
+        for group_key in groups[dpn]:
+            print 'Dpn:', dpn, 'ID:', group_key, 'Group:', json.dumps(groups[dpn][group_key])
+
+
+def main(args=None):
+    options, args = utils.parse_args()
+    if options.callMethod:
+        if args[1:]:
+            eval(options.callMethod)(args[1:])
+            return
+        else:
+            eval(options.callMethod)()
+            return
+    #print json.dumps(dsg.get_vpninterfaces())
+    #show_all_tables()
+    #analyze_inventory('openflow:165862438671169',ifName='tunf94333cc491')
+    #show_stale_flows()
+    #show_stale_bindings()
+    analyze_interface(args)
+    #og.print_flow_dict(og.get_ofctl_flows())
+
+
+if __name__ == '__main__':
+    import sys
+    main()
diff --git a/resources/tools/odl/netvirt/ds_get_data.py b/resources/tools/odl/netvirt/ds_get_data.py
new file mode 100644 (file)
index 0000000..10156ff
--- /dev/null
@@ -0,0 +1,176 @@
+import collections
+import json
+import netvirt_utils as utils
+import constants as const
+
+
+def get_ds_data(name, file_name=None, ds_type=None):
+    res = const.DSMAP[name]
+    filename = file_name or res[const.DSM_FILE]
+    dstype = ds_type or res[const.DSM_DSTYPE]
+    path = res[const.DSM_PATH]
+    root1 = res[const.DSM_ROOT1]
+    root2 = res[const.DSM_ROOT2]
+    data = {}
+    try:
+        with open(filename) as data_file:
+            data = json.load(data_file)[root1][root2]
+    except IOError:
+        url = utils.create_url(dstype, path)
+        result = utils.grabJson(url)
+        if result:
+            data = result[root1][root2]
+    return data
+
+
+def get_config_interfaces(file_name=None):
+    # Returns dict of ifaces, key is iface name
+    if_dict = {}
+    ifaces = get_ds_data('ifconfig',file_name)
+    for iface in ifaces:
+        if_dict[iface['name']] = iface
+    return if_dict
+
+
+def get_neutron_ports(file_name=None):
+    port_dict = {}
+    ports = get_ds_data('neutronports',file_name)
+    for port in ports:
+        port_dict[port['uuid']] = port
+    return port_dict
+
+
+def get_interface_states(file_name=None):
+    ifs_dict = {}
+    ifstates = get_ds_data('ifstate',file_name)
+    for ifstate in ifstates:
+        ifs_dict[ifstate['name']] = ifstate
+    return ifs_dict
+
+
+def get_config_tunnels(file_name='tunnel-config.log'):
+    tun_dict = {}
+    tunnels = {}
+    try:
+        with open(file_name) as tunconfig_file:
+            tunnels = json.load(tunconfig_file)['tunnel-list']['internal-tunnel']
+    except Exception:
+        pass
+    for tunnel in tunnels:
+        for tun_name in tunnel['tunnel-interface-name']:
+            tun_dict[tun_name] = tunnel
+    return tun_dict
+
+
+def get_tunnel_states(file_name=None):
+    tun_dict = {}
+    tunnels = get_ds_data('tunstate',file_name)
+    for tunnel in tunnels:
+        tun_dict[tunnel['tunnel-interface-name']] = tunnel
+    return tun_dict
+
+
+def get_topology_nodes(file_name, type = 'ovsdb:1', dsType = 'config'):
+    nodes_dict = {}
+    topologies = {}
+    nodes = {}
+    try:
+        with open(file_name) as topology_file:
+            topologies = json.load(topology_file)['topology']
+            nodes = [topology['node'] for topology in topologies if topology['topology-id'] == type][0]
+    except IOError:
+        url = utils.create_url(dsType, "network-topology:network-topology/{}".format(type))
+        result = utils.grabJson(url)
+        if result:
+            nodes = result['node']
+    for node in nodes:
+        nodes_dict[node['node-id']] = node
+    return nodes_dict
+
+
+def get_topology_config(file_name='topology-config.log', type = 'ovsdb:1'):
+    return get_topology_nodes(file_name)
+
+
+def get_topology_oper(file_name='topology-oper.log', type = 'ovsdb:1', dsType = 'operational'):
+    return get_topology_nodes(file_name, type)
+
+
+def get_inventory_nodes(file_name, dsType = 'config'):
+    nodes_dict = {}
+    nodes = get_ds_data('inventory', file_name, dsType)
+    for node in nodes:
+        nodes_dict[node['id']] = node
+    return nodes_dict
+
+
+def get_inventory_config(file_name=None):
+    return get_inventory_nodes(file_name)
+
+
+def get_inventory_oper(file_name='inventory-oper.log'):
+    return get_inventory_nodes(file_name, 'operational')
+
+
+def get_service_bindings(file_name=None):
+    sb_dict = collections.defaultdict(dict)
+    orphans_dict = collections.defaultdict(dict)
+    sb_infos = get_ds_data('bindings')
+    for sb_info in sb_infos:
+        service_mode = sb_info['service-mode'][len('interface-service-bindings:'):]
+        if sb_info.get('bound-services'):
+            sb_dict[sb_info['interface-name']][service_mode] =  sb_info
+        else:
+            orphans_dict[sb_info['interface-name']][service_mode] =  sb_info
+    return dict(sb_dict), dict(orphans_dict)
+
+
+def get_elan_instances(file_name=None):
+    einstances_dict = {}
+    einstances = get_ds_data('elaninstances')
+    for einstance in einstances:
+        einstances_dict[einstance['elan-instance-name']] = einstance
+    return einstances_dict
+
+
+def get_elan_interfaces(file_name=None):
+    eifaces_dict = {}
+    eifaces = get_ds_data('elaninterfaces')
+    for eiface in eifaces:
+        eifaces_dict[eiface['name']] = eiface
+    return eifaces_dict
+
+
+def get_ifindexes(file_name=None):
+    ifindexes_dict = {}
+    ifindexes = get_ds_data('ifindexes')
+    for ifindex in ifindexes:
+        ifindexes_dict[ifindex['if-index']] = ifindex
+    return ifindexes_dict
+
+
+def get_fibentries_by_label(file_name=None):
+    fibs_dict = {}
+    fibs = get_ds_data('fibentries')
+    for vrftable in fibs:
+        for vrfEntry in vrftable.get('vrfentry', []):
+            if vrfEntry.get('label'):
+                vrfEntry['rd'] = vrftable['routeDistinguisher']
+                fibs_dict[vrfEntry['label']] = vrfEntry
+    return fibs_dict
+
+
+def get_vpnids(filename=None):
+    vpnids_dict = {}
+    vpninstances = get_ds_data('vpninstance-to-vpnid')
+    for vpninstance in vpninstances:
+        vpnids_dict[vpninstance['vpn-id']] = vpninstance
+    return vpnids_dict
+
+
+def get_vpninterfaces(filename=None):
+    vpninterfaces_dict = {}
+    vpninterfaces = get_ds_data('vpninterfaces')
+    for vpninterface in vpninterfaces:
+        vpninterfaces_dict[vpninterface['name']] = vpninterface
+    return vpninterfaces_dict
diff --git a/resources/tools/odl/netvirt/flow_parser.py b/resources/tools/odl/netvirt/flow_parser.py
new file mode 100644 (file)
index 0000000..8fc095f
--- /dev/null
@@ -0,0 +1,618 @@
+import netvirt_utils as utils
+import constants as const
+
+
+OPTIONALS = ['ifname', 'lport', 'elan-tag', 'mpls', 'vpnid', 'reason']
+MAC_LEN = 17
+
+# Flow table constants
+
+PREFIX_211_GOTO = 'Egress_Fixed_Goto_Classifier_'
+PREFIX_211_DHCPSv4 = 'Egress_DHCP_Server_v4'
+PREFIX_211_DHCPSv6 = 'Egress_DHCP_Server_v6_'
+PREFIX_211_DHCPCv4 = 'Egress_DHCP_Client_v4'
+PREFIX_211_DHCPCv6 = 'Egress_DHCP_Client_v6_'
+PREFIX_211_ARP = 'Egress_ARP_'
+PREFIX_211_L2BCAST = 'Egress_L2Broadcast_'
+PREFIX_211_ICMPv6 = 'Egress_ICMPv6_'
+
+PREFIX_213 = 'Egress_Fixed_Conntrk_'
+PREFIX_214 = 'Egress_Fixed_Conntrk_Drop'
+PREFIX_215 = 'Egress_Fixed_NonConntrk_Drop'
+PREFIX_241_DHCPv4 = 'Ingress_DHCP_Server_v4'
+PREFIX_241_DHCPv6 = 'Ingress_DHCP_Server_v6_'
+PREFIX_241_ICMPv6 = 'Ingress_ICMPv6_'
+PREFIX_241_ARP = 'Ingress_ARP_'
+PREFIX_241_BCASTv4 = 'Ingress_v4_Broadcast_'
+PREFIX_241_GOTO = 'Ingress_Fixed_Goto_Classifier_'
+PREFIX_243 = 'Ingress_Fixed_Conntrk_'
+PREFIX_244 = 'Ingress_Fixed_Conntrk_Drop'
+PREFIX_245 = 'Ingress_Fixed_NonConntrk_Drop'
+
+
+PREFIX_FOR_LPORT = {211: [PREFIX_211_GOTO, PREFIX_211_DHCPSv4,
+                          PREFIX_211_DHCPSv6, PREFIX_211_DHCPCv4,
+                          PREFIX_211_DHCPCv6, PREFIX_211_ARP,
+                          PREFIX_211_L2BCAST, PREFIX_211_ICMPv6],
+                    212: [],
+                    213: [PREFIX_213],
+                    214: [PREFIX_214],
+                    215: [PREFIX_215],
+                    241: [PREFIX_241_DHCPv4, PREFIX_241_DHCPv6,
+                          PREFIX_241_ICMPv6, PREFIX_241_ARP,
+                          PREFIX_241_BCASTv4, PREFIX_241_GOTO],
+                    242: [],
+                    243: [PREFIX_243],
+                    244: [PREFIX_244],
+                    245: [PREFIX_245]}
+
+PREFIX_SGR_ETHER = 'ETHERnull_'
+PREFIX_SGR_ICMP = 'ICMP_'
+PREFIX_SGR_TCP = 'TCP_'
+PREFIX_SGR_UDP = 'UDP_'
+PREFIX_SGR_OTHER = 'OTHER_PROTO'
+
+PREFIX_LPORT_SGR = {211: [], 212: [], 213: [],
+                    214: [PREFIX_SGR_ETHER, PREFIX_SGR_ICMP, PREFIX_SGR_TCP,
+                          PREFIX_SGR_UDP, PREFIX_SGR_OTHER],
+                    215: [PREFIX_SGR_ETHER, PREFIX_SGR_ICMP, PREFIX_SGR_TCP,
+                          PREFIX_SGR_UDP, PREFIX_SGR_OTHER],
+                    241: [], 242: [], 243: [],
+                    244: [PREFIX_SGR_ETHER, PREFIX_SGR_ICMP, PREFIX_SGR_TCP,
+                          PREFIX_SGR_UDP, PREFIX_SGR_OTHER],
+                    245: [PREFIX_SGR_ETHER, PREFIX_SGR_ICMP, PREFIX_SGR_TCP,
+                          PREFIX_SGR_UDP, PREFIX_SGR_OTHER]
+                    }
+
+# Metadata consts
+LPORT_MASK = 0x1fffff0000000000
+LPORT_MASK_ZLEN = 10  # no. of trailing 0s in lport mask
+ELAN_TAG_MASK = 0x000000ffff000000
+ELAN_HEX_LEN = 4
+LPORT_REG6_MASK = 0x1fffff00
+LPORT_REG6_MASK_ZLEN = 2
+VRFID_MASK = 0x00000000fffffffe
+
+
+def create_flow_dict(flow_info, flow):
+    flow_dict = {}
+    flow_dict['table'] = flow['table_id']
+    flow_dict['id'] = flow['id']
+    flow_dict['name'] = flow.get('flow-name')
+    flow_dict['flow'] = flow
+    flow_dict['dpnid'] = flow_info['dpnid']
+    for opt in OPTIONALS:
+        if flow_info.get(opt):
+            flow_dict[opt] = flow_info.get(opt)
+    return flow_dict
+
+
+def get_any_flow(flow, flow_info, groups, ifaces, ifstates, ifindexes,
+                 fibentries, vpnids, vpninterfaces, einsts, eifaces):
+    table = flow['table_id']
+    if table in const.TABLE_MAP['ifm']:
+        stale_ifm = stale_ifm_flow(flow, flow_info, ifaces, ifstates)
+        flow_info = stale_ifm if stale_ifm else get_flow_info_from_ifm_table(flow_info, flow)
+    elif table in const.TABLE_MAP['acl']:
+        stale_acl = stale_acl_flow(flow, flow_info, ifaces, ifindexes, einsts, eifaces)
+        flow_info = stale_acl if stale_acl else get_flow_info_from_acl_table(flow_info, flow)
+    elif table in const.TABLE_MAP['elan']:
+        stale_elan = stale_elan_flow(flow, flow_info, ifaces, ifindexes, einsts, eifaces)
+        flow_info = stale_elan if stale_elan else get_flow_info_from_elan_table(flow_info, flow)
+    elif table in const.TABLE_MAP['l3vpn']:
+        stale_l3vpn = stale_l3vpn_flow(flow, flow_info, groups, ifaces, ifindexes, vpnids, vpninterfaces, fibentries)
+        flow_info = stale_l3vpn if stale_l3vpn else get_flow_info_from_l3vpn_table(flow_info, flow)
+    else:
+        flow_info = get_flow_info_from_any(flow_info, flow)
+        iface = (get_iface_for_lport(ifaces, ifindexes, flow_info.get('lport'))
+                 if flow_info.get('lport') else None)
+        if iface and iface.get('name'):
+            flow_info['ifname'] = iface['name']
+    return create_flow_dict(flow_info, flow)
+
+
+def stale_ifm_flow(flow, flow_info, ifaces, ifstates):
+    get_flow_info_from_ifm_table(flow_info, flow)
+    flow_ifname = flow_info['ifname']
+    if flow_ifname is not None and not ifaces.get(flow_ifname):
+        flow_info['reason'] = 'Interface doesnt exist'
+        return create_flow_dict(flow_info, flow)
+    elif flow_ifname and ifstates.get(flow_ifname):
+        ifstate = ifstates.get(flow_ifname)
+        ncid_list = ifstate.get('lower-layer-if')
+        ncid = ncid_list[0] if ncid_list else None
+        dpn = utils.get_dpn_from_ofnodeid(ncid)
+        if dpn and dpn != flow_info['dpnid']:
+            flow_info['reason'] = 'DpnId mismatch for flow and Interface'
+            return create_flow_dict(flow_info, flow)
+        if (flow_info.get('lport') and ifstate.get('if-index')
+                and flow_info['lport'] != ifstate['if-index']):
+            flow_info['reason'] = 'Lport and IfIndex mismatch'
+            return create_flow_dict(flow_info, flow)
+    return None
+    # return create_flow_dict(flow_info, flow)
+
+
+def stale_l3vpn_flow(flow, flow_info, groups, ifaces, ifindexes,
+                     vpnids, vpninterfaces, fibentries):
+    get_flow_info_from_l3vpn_table(flow_info, flow)
+    lport = flow_info.get('lport')
+    iface = get_iface_for_lport(ifaces, ifindexes, lport)
+    if lport and not iface:
+        flow_info['reason'] = 'Interface for lport not found'
+        return create_flow_dict(flow_info, flow)
+    if iface:
+        flow_info['ifname'] = iface['name']
+    vpninterface = vpninterfaces.get(iface.get('name')) if iface else None
+    if not vpninterfaces:
+        flow_info['reason'] = 'VpnInterface for Lport not found'
+        return create_flow_dict(flow_info, flow)
+    vpnid = flow_info.get('vpnid')
+    if vpnid and not vpnids.get(vpnid):
+        flow_info['reason'] = 'VpnInstance for VpnId not found'
+        return create_flow_dict(flow_info, flow)
+    if vpnid and vpninterface and vpnids.get(vpnid):
+        if (vpninterface.get('vpn-instance-name') !=
+                vpnids[vpnid]['vpn-instance-name']):
+            flow_info['reason'] = 'Lport VpnId mismatch'
+            return create_flow_dict(flow_info, flow)
+    label = flow_info.get('label')
+    fibentry = fibentries.get(label) if label else None
+    if label and not fibentry:
+        flow_info['reason'] = 'Fibentry for MplsLabel not found'
+        return create_flow_dict(flow_info, flow)
+    # Label check for group
+    prefix = fibentry.get('destPrefix') if fibentry else None
+    if prefix and flow_info.get('group-id'):
+        gid = flow_info.get('group-id')
+        if groups.get(gid) and (
+                groups.get(gid).get('group-name', '') != prefix):
+            flow_info['reason'] = 'DestPrefix mismatch for label and group'
+            return create_flow_dict(flow_info, flow)
+    return None
+
+
+def stale_elan_flow(flow, flow_info, ifaces, ifindexes, einsts, eifaces):
+    # hex(int(mask, 16) & int(hexa, 16))
+    get_flow_info_from_elan_table(flow_info, flow)
+    lport = flow_info.get('lport')
+    eltag = flow_info.get('elan-tag')
+    iface = get_iface_for_lport(ifaces, ifindexes, lport)
+    if lport and not iface:
+        flow_info['reason'] = 'Interface for lport not found'
+        return create_flow_dict(flow_info, flow)
+    if iface:
+        flow_info['ifname'] = iface['name']
+    if not is_elantag_valid(eltag, eifaces, einsts, iface):
+        flow_info['reason'] = 'Lport Elantag mismatch'
+        return create_flow_dict(flow_info, flow)
+    return None
+
+
+def stale_acl_flow(flow, flow_info, ifaces, ifindexes, einsts, eifaces):
+    get_flow_info_from_acl_table(flow_info, flow)
+    lport = flow_info.get('lport')
+    eltag = flow_info.get('elan-tag')
+    iface = get_iface_for_lport(ifaces, ifindexes, lport)
+    if lport and not iface:
+            flow_info['reason'] = 'Interface for lport not found'
+            #return create_flow_dict(flow_info, flow)
+    if iface:
+        flow_info['ifname'] = iface['name']
+    if not is_elantag_valid(eltag, eifaces, einsts, iface):
+        flow_info['reason'] = 'Lport Elantag mismatch'
+        #return create_flow_dict(flow_info, flow)
+    return create_flow_dict(flow_info, flow)
+    #return None
+
+
+def is_elantag_valid(eltag, eifaces, einsts, iface):
+    if (iface and eltag
+            and eltag != get_eltag_for_iface(eifaces, einsts, iface)):
+        return False
+    return True
+
+
+def get_iface_for_lport(ifaces, ifindexes, lport):
+    if lport:
+        if ifindexes.get(lport):
+            ifname = ifindexes.get(lport).get('interface-name')
+            if ifname and ifaces.get(ifname):
+                return ifaces.get(ifname)
+    return None
+
+
+def get_eltag_for_iface(eifaces, einsts, iface):
+    ifname = iface.get('name') if iface else None
+    eiface = eifaces.get(ifname) if ifname else None
+    einst_name = eiface.get('elan-instance-name') if eiface else None
+    einst = einsts.get(einst_name) if einst_name else None
+    return einst.get('elan-tag') if einst else None
+
+
+# Methods to extract flow fields
+def get_instruction_writemeta(flow):
+    for instruction in flow['instructions'].get('instruction', []):
+        if 'write-metadata' in instruction:
+            return instruction['write-metadata']
+    return None
+
+
+def get_act_reg6load(flow):
+    for instruction in flow['instructions'].get('instruction', []):
+        if 'apply-actions' in instruction:
+            for action in instruction['apply-actions'].get('action', []):
+                if ('openflowplugin-extension-nicira-action:nx-reg-load'
+                        in action):
+                    return action[
+                        'openflowplugin-extension-nicira-action:nx-reg-load']
+    return None
+
+
+def get_act_conntrack(flow):
+    for instruction in flow['instructions'].get('instruction', []):
+        if 'apply-actions' in instruction:
+            for action in instruction['apply-actions'].get('action', []):
+                if ('openflowplugin-extension-nicira-action:nx-conntrack'
+                        in action):
+                    return action[
+                        'openflowplugin-extension-nicira-action:nx-conntrack']
+
+
+def get_act_group(flow):
+    for instruction in flow['instructions'].get('instruction', []):
+        if 'apply-actions' in instruction:
+            for action in instruction['apply-actions'].get('action', []):
+                if 'group-action' in action:
+                    return action['group-action']
+
+
+def get_match_metadata(flow):
+    return flow['match'].get('metadata')
+
+
+def get_match_reg6(flow):
+    for ofex in (
+            flow['match'].get(
+                'openflowplugin-extension-general:extension-list', [])):
+        if (ofex['extension-key']
+                == 'openflowplugin-extension-nicira-match:nxm-nx-reg6-key'):
+            return (
+                ofex['extension']
+                ['openflowplugin-extension-nicira-match:nxm-nx-reg'])
+    return None
+
+
+def get_match_mpls(flow):
+    if flow['match'].get('protocol-match-fields'):
+        return flow['match'].get('protocol-match-fields').get('mpls-label')
+    return None
+
+
+def get_match_tunnelid(flow):
+    if flow['match'].get('tunnel'):
+        return flow['match'].get('tunnel').get('tunnel-id')
+    return None
+
+
+def get_flow_info_from_any(flow_info, flow):
+    w_mdata = get_instruction_writemeta(flow)
+    if w_mdata:
+        metadata = w_mdata['metadata']
+        mask = w_mdata['metadata-mask']
+        if (mask & LPORT_MASK):
+            lport = ('%x' % (metadata & LPORT_MASK))[:-LPORT_MASK_ZLEN]
+            if lport:
+                flow_info['lport'] = int(lport, 16)
+    m_metadata = get_match_metadata(flow)
+    if m_metadata:
+        metadata = m_metadata['metadata']
+        mask = m_metadata['metadata-mask']
+        if (mask & ELAN_TAG_MASK):
+            elan = ('%x' % (metadata & ELAN_TAG_MASK))[:ELAN_HEX_LEN]
+            if elan:
+                flow_info['elan-tag'] = int(elan, 16)
+        if not lport and (mask & LPORT_MASK):
+            lport = ('%x' % (metadata & LPORT_MASK))[:-LPORT_MASK_ZLEN]
+            if lport:
+                flow_info['lport'] = int(lport, 16)
+    return flow_info
+
+# Table specific parsing
+
+def get_ifname_from_flowid(flow_id, table):
+    splitter = ':' if table == 0 else '.'
+    i = 2 if table == 0 else 1
+    # i = 2
+    ifname = None
+    try:
+        ifname = flow_id.split(splitter)[i]
+    except IndexError:
+        tun_index = flow_id.find('tun')
+        if tun_index > -1:
+            ifname = flow_id[tun_index:]
+    return ifname
+
+
+def get_flow_info_from_ifm_table(flow_info, flow):
+    flow_info['ifname'] = get_ifname_from_flowid(flow['id'], flow['table_id'])
+    w_mdata = get_instruction_writemeta(flow)
+    if w_mdata:
+        metadata = w_mdata['metadata']
+        mask = w_mdata['metadata-mask']
+        if (mask & LPORT_MASK):
+            lport = ('%x' % (metadata & LPORT_MASK))[:-LPORT_MASK_ZLEN]
+            flow_info['lport'] = int(lport, 16)
+            return flow_info
+    m_reg6 = get_match_reg6(flow)
+    if m_reg6 and m_reg6.get('value'):
+        lport = (('%x' % (m_reg6.get('value') & LPORT_REG6_MASK))
+                 [:-LPORT_REG6_MASK_ZLEN])
+        flow_info['lport'] = int(lport, 16)
+    return flow_info
+
+
+def get_flow_info_from_l3vpn_table(flow_info, flow):
+    label = get_match_mpls(flow)
+    if not label and flow['table_id'] == 36:
+        label = get_match_tunnelid(flow)
+    if label:
+        flow_info['mpls'] = label
+    a_group = get_act_group(flow)
+    if a_group and a_group.get('group-id'):
+        flow_info['group-id'] = a_group.get('group-id')
+    m_metadata = get_match_metadata(flow)
+    if m_metadata:
+        metadata = m_metadata['metadata']
+        mask = m_metadata['metadata-mask']
+        if (mask & LPORT_MASK):
+            lport = ('%x' % (metadata & LPORT_MASK))[:-LPORT_MASK_ZLEN]
+            flow_info['lport'] = int(lport, 16)
+        if (mask & VRFID_MASK):
+            flow_info['vpnid'] = (metadata & VRFID_MASK) / 2
+    return flow_info
+
+
+def get_lport_elan_tags_from_flowid(flowid, dpnid):
+    res = flowid[:-MAC_LEN].split(dpnid)
+    lport = res[1]
+    elan = res[0][2:]
+    return lport, elan
+
+
+def get_flow_info_from_elan_table(flow_info, flow):
+    m_metadata = get_match_metadata(flow)
+    if m_metadata:
+        metadata = m_metadata['metadata']
+        mask = m_metadata['metadata-mask']
+        if (mask & ELAN_TAG_MASK):
+            elan = ('%x' % (metadata & ELAN_TAG_MASK))[:ELAN_HEX_LEN]
+            flow_info['elan-tag'] = int(elan, 16)
+        if (mask & LPORT_MASK):
+            lport = ('%x' % (metadata & LPORT_MASK))[:-LPORT_MASK_ZLEN]
+            flow_info['lport'] = int(lport, 16)
+
+    if not flow_info.get('lport'):
+        reg6_load = get_act_reg6load(flow)
+        if reg6_load and reg6_load.get('value'):
+            # reg6load value is lport lft-shit by 8 bits.
+            lport = ('%x' % reg6_load.get('value'))[:-2]
+            flow_info['lport'] = int(lport, 16)
+    return flow_info
+
+
+def get_flow_info_from_acl_table(flow_info, flow):
+    m_metadata = get_match_metadata(flow)
+    if m_metadata:
+        metadata = m_metadata['metadata']
+        mask = m_metadata['metadata-mask']
+        if (mask & LPORT_MASK):
+            lport = ('%x' % (metadata & LPORT_MASK))[:-LPORT_MASK_ZLEN]
+            flow_info['lport'] = int(lport, 16)
+    a_conntrk = get_act_conntrack(flow)
+    if a_conntrk and a_conntrk.get('conntrack-zone'):
+        flow_info['elan-tag'] = a_conntrk.get('conntrack-zone')
+    return flow_info
+
+
+def get_flow_info_from_acl_table_flowid(flow_info, flow):
+    """
+        Format for ACL flow ids is as follows:
+        211:Egress_Fixed_Goto_Classifier_<dpId>_<lportTag>_<attachMac>_<attachIp>,
+            Egress_DHCP_Server_v4<dpId>_<lportTag>__Drop_,
+            Egress_DHCP_Server_v6_<dpId>_<lportTag>__Drop_,
+            Egress_DHCP_Client_v4<dpId>_<lportTag>_<macAddress>_Permit_,
+            Egress_DHCP_Client_v6_<dpId>_<lportTag>_<macAddress>_Permit_,
+            Egress_ARP_<dpId>_<lportTag>_<allowedAddressMac><allowedAddressIp>,
+            Egress_L2Broadcast_<dpId>_<lportTag>_<attachMac>,
+            Egress_ICMPv6_<dpId>_<lportTag>_134_Drop_,
+            Egress_ICMPv6_<dpId>_<lportTag>_<icmpv6Type>_<allowedAddressMac>_Permit_,
+            Egress_Fixed_Goto_Classifier_<dpId>_<lportTag>_<attachMac>_<attachIp>
+
+        212:Fixed_Conntrk_Classifier_<dpId>_212_<etherType>_<protocol>
+        213:Egress_Fixed_Conntrk_<dpId>_<lportTag>_<etherType>_Recirc
+        214:Fixed_Conntrk_Trk_<dpId>_Tracked_Established17,
+            Fixed_Conntrk_Trk_<dpId>_Tracked_Related17,
+            Egress_Fixed_Conntrk_Drop<dpId>_<lportTag>_Tracked_New,
+            Egress_Fixed_Conntrk_Drop<dpId>_<lportTag>_Tracked_Invalid,
+            ETHERnull_Egress_<lportTag>_<sgRuleId>,
+            ETHERnull_ipv6_remoteACL_interface_aap_<remoteIp>_Egress_<lportTag>_<sgRuleId>,
+            ICMP__Egress_<lportTag>_<sgRuleId>,
+            ICMP_V4_DESTINATION_<type><code>__Egress_<lportTag>_<sgRuleId>,
+            ICMP_V6_DESTINATION_<type><code>__Egress_<lportTag>_<sgRuleId>,
+            ICMP__ipv4_remoteACL_interface_aap_<remoteIp>_Egress_<lportTag>_<sgRuleId>,
+            ICMP__ipv6_remoteACL_interface_aap_<remoteIp>_Egress_<lportTag>_<sgRuleId>,
+            ICMP_V4_DESTINATION_<type><code>__ipv4_remoteACL_interface_aap_<remoteIp>_Egress_<lportTag>_<sgRuleId>,
+            ICMP_V6_DESTINATION_<type><code>__ipv6_remoteACL_interface_aap_<remoteIp>_Egress_<lportTag>_<sgRuleId>,
+            TCP_DESTINATION_<port>_<portMask>_Egress_<lportTag>_<sgRuleId>,
+            TCP_DESTINATION_<port>_<portMask>_ipv4_remoteACL_interface_aap_<remoteIp>_Egress_<lportTag>_<sgRuleId>,
+            TCP_DESTINATION_<port>_<portMask>_ipv6_remoteACL_interface_aap_<remoteIp>_Egress_<lportTag>_<sgRuleId>,
+            TCP_SOURCE_<port>_<portMask>_Egress_<lportTag>_<sgRuleId>,
+            TCP_SOURCE_<port>_<portMask>_ipv4_remoteACL_interface_aap_<remoteIp>_Egress_<lportTag>_<sgRuleId>,
+            TCP_SOURCE_ALL__Egress_<lportTag>_<sgRuleId>,
+            TCP_SOURCE_ALL__ipv4_remoteACL_interface_aap_<remoteIp>_Egress_<lportTag>_<sgRuleId>,
+            TCP_SOURCE_ALL__ipv6_remoteACL_interface_aap_<remoteIp>_Egress_<lportTag>_<sgRuleId>,
+            UDP_DESTINATION_<port>_<portMask>_Egress_<lportTag>_<sgRuleId>,
+            UDP_DESTINATION_<port>_<portMask>_ipv4_remoteACL_interface_aap_<remoteIp>_Egress_<lportTag>_<sgRuleId>,
+            UDP_DESTINATION_<port>_<portMask>_ipv6_remoteACL_interface_aap_<remoteIp>_Egress_<lportTag>_<sgRuleId>,
+            UDP_SOURCE_<port>_<portMask>_Egress_<lportTag>_<sgRuleId>,
+            UDP_SOURCE_<port>_<portMask>_ipv4_remoteACL_interface_aap_<remoteIp>_Egress_<lportTag>_<sgRuleId>,
+            UDP_SOURCE_<port>_<portMask>_ipv6_remoteACL_interface_aap_<remoteIp>_Egress_<lportTag>_<sgRuleId>,
+            UDP_SOURCE_ALL__Egress_<lportTag>_<sgRuleId>,
+            UDP_SOURCE_ALL__ipv4_remoteACL_interface_aap_<remoteIp>_Egress_<lportTag>_<sgRuleId>,
+            UDP_SOURCE_ALL__ipv6_remoteACL_interface_aap_<remoteIp>_Egress_<lportTag>_<sgRuleId>,
+            OTHER_PROTO<protocolNumber>_Egress_<lportTag>_<sgRuleId>,
+            OTHER_PROTO<protocolNumber>_ipv4_remoteACL_interface_aap_<remoteIp>_Egress_<lportTag>_<sgRuleId>,
+            OTHER_PROTO<protocolNumber>_ipv6_remoteACL_interface_aap_<remoteIp>_Egress_<lportTag>_<sgRuleId>,
+
+        215:Egress_Fixed_NonConntrk_Drop<dpId>_<lportTag>_ACL_Rule_Miss,
+            ETHERnull_Egress_<lportTag>_<sgRuleId>,
+            ETHERnull_ipv4_remoteACL_interface_aap_<remoteIp>_Egress_<lportTag>_<sgRuleId>,
+            ETHERnull_ipv6_remoteACL_interface_aap_<remoteIp>_Egress_<lportTag>_<sgRuleId>,
+            ICMP__Egress_<lportTag>_<sgRuleId>,
+            ICMP_V4_DESTINATION_<type><code>__Egress_<lportTag>_<sgRuleId>,
+            ICMP_V6_DESTINATION_<type><code>__Egress_<lportTag>_<sgRuleId>,
+            ICMP__ipv4_remoteACL_interface_aap_<remoteIp>_Egress_<lportTag>_<sgRuleId>,
+            ICMP__ipv6_remoteACL_interface_aap_<remoteIp>_Egress_<lportTag>_<sgRuleId>,
+            ICMP_V4_DESTINATION_<type><code>__ipv4_remoteACL_interface_aap_<remoteIp>_Egress_<lportTag>_<sgRuleId>,
+            ICMP_V6_DESTINATION_<type><code>__ipv6_remoteACL_interface_aap_<remoteIp>_Egress_<lportTag>_<sgRuleId>,
+            TCP_DESTINATION_<port>_<portMask>_Egress_<lportTag>_<sgRuleId>,
+            TCP_DESTINATION_<port>_<portMask>_ipv4_remoteACL_interface_aap_<remoteIp>_Egress_<lportTag>_<sgRuleId>,
+            TCP_DESTINATION_<port>_<portMask>_ipv6_remoteACL_interface_aap_<remoteIp>_Egress_<lportTag>_<sgRuleId>,
+            TCP_SOURCE_<port>_<portMask>_Egress_<lportTag>_<sgRuleId>,
+            TCP_SOURCE_<port>_<portMask>_ipv4_remoteACL_interface_aap_<remoteIp>_Egress_<lportTag>_<sgRuleId>,
+            TCP_SOURCE_ALL__Egress_<lportTag>_<sgRuleId>,
+            TCP_SOURCE_ALL__ipv4_remoteACL_interface_aap_<remoteIp>_Egress_<lportTag>_<sgRuleId>,
+            TCP_SOURCE_ALL__ipv6_remoteACL_interface_aap_<remoteIp>_Egress_<lportTag>_<sgRuleId>,
+            UDP_DESTINATION_<port>_<portMask>_Egress_<lportTag>_<sgRuleId>,
+            UDP_DESTINATION_<port>_<portMask>_ipv4_remoteACL_interface_aap_<remoteIp>_Egress_<lportTag>_<sgRuleId>,
+            UDP_DESTINATION_<port>_<portMask>_ipv6_remoteACL_interface_aap_<remoteIp>_Egress_<lportTag>_<sgRuleId>,
+            UDP_SOURCE_<port>_<portMask>_Egress_<lportTag>_<sgRuleId>,
+            UDP_SOURCE_<port>_<portMask>_ipv4_remoteACL_interface_aap_<remoteIp>_Egress_<lportTag>_<sgRuleId>,
+            UDP_SOURCE_<port>_<portMask>_ipv6_remoteACL_interface_aap_<remoteIp>_Egress_<lportTag>_<sgRuleId>,
+            UDP_SOURCE_ALL__Egress_<lportTag>_<sgRuleId>,
+            UDP_SOURCE_ALL__ipv4_remoteACL_interface_aap_<remoteIp>_Egress_<lportTag>_<sgRuleId>,
+            UDP_SOURCE_ALL__ipv6_remoteACL_interface_aap_<remoteIp>_Egress_<lportTag>_<sgRuleId>,
+            OTHER_PROTO<protocolNumber>_Egress_<lportTag>_<sgRuleId>,
+            OTHER_PROTO<protocolNumber>_ipv4_remoteACL_interface_aap_<remoteIp>_Egress_<lportTag>_<sgRuleId>,
+            OTHER_PROTO<protocolNumber>_ipv6_remoteACL_interface_aap_<remoteIp>_Egress_<lportTag>_<sgRuleId>
+
+        241:Ingress_v4_Broadcast_<dpId>_Permit,
+            Ingress_L2_Broadcast_<dpId>_Permit,
+            Ingress_DHCP_Server_v4<dpId>_<lportTag>__Permit_,
+            Ingress_DHCP_Server_v6_<dpId>_<lportTag>___Permit_,
+            Ingress_ICMPv6_<dpId>_<lportTag>_130_Permit_,
+            Ingress_ICMPv6_<dpId>_<lportTag>_134_LinkLocal_Permit_,
+            Ingress_ICMPv6_<dpId>_<lportTag>_135_Permit_,
+            Ingress_ICMPv6_<dpId>_<lportTag>_136_Permit_,
+            Ingress_ARP_<dpId>_<lportTag>,
+            Ingress_v4_Broadcast_<dpId>_<lportTag>_<broadcastAddress>_Permit,
+            Ingress_Fixed_Goto_Classifier_<dpId>_<lportTag>_<attachMac>_<attachIp>
+
+        242:Fixed_Conntrk_Classifier_<dpId>_242_<etherType>_<protocol>
+
+        243:Ingress_Fixed_Conntrk_<dpId>_<lportTag>_<etherType>_Recirc
+
+        244:Fixed_Conntrk_Trk_<dpId>_Tracked_Established220
+            Fixed_Conntrk_Trk_<dpId>_Tracked_Related220,
+            Ingress_Fixed_Conntrk_Drop<dpId>_<lportTag>_Tracked_New,
+            Ingress_Fixed_Conntrk_Drop<dpId>_<lportTag>_Tracked_Invalid,
+            ETHERnull_Ingress_<lportTag>_<sgRuleId>,
+            ETHERnull_ipv4_remoteACL_interface_aap_<remoteIp>_Ingress_<lportTag>_<sgRuleId>,
+            ETHERnull_ipv6_remoteACL_interface_aap_<remoteIp>_Ingress_<lportTag>_<sgRuleId>,
+            ICMP__Ingress_<lportTag>_<sgRuleId>,
+            ICMP_V4_DESTINATION_<type><code>__Ingress_<lportTag>_<sgRuleId>,
+            ICMP_V6_DESTINATION_<type><code>__Ingress_<lportTag>_<sgRuleId>,
+            ICMP__ipv4_remoteACL_interface_aap_<remoteIp>_Ingress_<lportTag>_<sgRuleId>,
+            ICMP__ipv6_remoteACL_interface_aap_<remoteIp>_Ingress_<lportTag>_<sgRuleId>,
+            ICMP_V4_DESTINATION_<type><code>__ipv4_remoteACL_interface_aap_<remoteIp>_Ingress_<lportTag>_<sgRuleId>,
+            ICMP_V6_DESTINATION_<type><code>__ipv6_remoteACL_interface_aap_<remoteIp>_Ingress_<lportTag>_<sgRuleId>,
+            TCP_DESTINATION_<port>_<portMask>_Ingress_<lportTag>_<sgRuleId>,
+            TCP_DESTINATION_<port>_<portMask>_ipv4_remoteACL_interface_aap_<remoteIp>_Ingress_<lportTag>_<sgRuleId>,
+            TCP_DESTINATION_<port>_<portMask>_ipv6_remoteACL_interface_aap_<remoteIp>_Ingress_<lportTag>_<sgRuleId>,
+            TCP_SOURCE_<port>_<portMask>_Ingress_<lportTag>_<sgRuleId>,
+            TCP_SOURCE_<port>_<portMask>_ipv4_remoteACL_interface_aap_<remoteIp>_Ingress_<lportTag>_<sgRuleId>,
+            TCP_SOURCE_ALL__Ingress_<lportTag>_<sgRuleId>,
+            TCP_SOURCE_ALL__ipv4_remoteACL_interface_aap_<remoteIp>_Ingress_<lportTag>_<sgRuleId>,
+            TCP_SOURCE_ALL__ipv6_remoteACL_interface_aap_<remoteIp>_Ingress_<lportTag>_<sgRuleId>,
+            UDP_DESTINATION_<port>_<portMask>_Ingress_<lportTag>_<sgRuleId>,
+            UDP_DESTINATION_<port>_<portMask>_ipv4_remoteACL_interface_aap_<remoteIp>_Ingress_<lportTag>_<sgRuleId>,
+            UDP_DESTINATION_<port>_<portMask>_ipv6_remoteACL_interface_aap_<remoteIp>_Ingress_<lportTag>_<sgRuleId>,
+            UDP_SOURCE_<port>_<portMask>_Ingress_<lportTag>_<sgRuleId>,
+            UDP_SOURCE_<port>_<portMask>_ipv4_remoteACL_interface_aap_<remoteIp>_Ingress_<lportTag>_<sgRuleId>,
+            UDP_SOURCE_<port>_<portMask>_ipv6_remoteACL_interface_aap_<remoteIp>_Ingress_<lportTag>_<sgRuleId>,
+            UDP_SOURCE_ALL__Ingress_<lportTag>_<sgRuleId>,
+            UDP_SOURCE_ALL__ipv4_remoteACL_interface_aap_<remoteIp>_Ingress_<lportTag>_<sgRuleId>,
+            UDP_SOURCE_ALL__ipv6_remoteACL_interface_aap_<remoteIp>_Ingress_<lportTag>_<sgRuleId>,
+            OTHER_PROTO<protocolNumber>_Ingress_<lportTag>_<sgRuleId>,
+            OTHER_PROTO<protocolNumber>_ipv4_remoteACL_interface_aap_<remoteIp>_Ingress_<lportTag>_<sgRuleId>,
+            OTHER_PROTO<protocolNumber>_ipv6_remoteACL_interface_aap_<remoteIp>_Ingress_<lportTag>_<sgRuleId>
+
+        245:Ingress_Fixed_NonConntrk_Drop<dpId>_<lportTag>_ACL_Rule_Miss,
+            ETHERnull_Ingress_<lportTag>_<sgRuleId>,
+            ETHERnull_ipv4_remoteACL_interface_aap_<remoteIp>_Ingress_<lportTag>_<sgRuleId>,
+            ETHERnull_ipv6_remoteACL_interface_aap_<remoteIp>_Ingress_<lportTag>_<sgRuleId>,
+            ICMP__Ingress_<lportTag>_<sgRuleId>,
+            ICMP_V4_DESTINATION_<type><code>__Ingress_<lportTag>_<sgRuleId>,
+            ICMP_V6_DESTINATION_<type><code>__Ingress_<lportTag>_<sgRuleId>,
+            ICMP__ipv4_remoteACL_interface_aap_<remoteIp>_Ingress_<lportTag>_<sgRuleId>,
+            ICMP__ipv6_remoteACL_interface_aap_<remoteIp>_Ingress_<lportTag>_<sgRuleId>,
+            ICMP_V4_DESTINATION_<type><code>__ipv4_remoteACL_interface_aap_<remoteIp>_Ingress_<lportTag>_<sgRuleId>,
+            ICMP_V6_DESTINATION_<type><code>__ipv6_remoteACL_interface_aap_<remoteIp>_Ingress_<lportTag>_<sgRuleId>,
+            TCP_DESTINATION_<port>_<portMask>_Ingress_<lportTag>_<sgRuleId>,
+            TCP_DESTINATION_<port>_<portMask>_ipv4_remoteACL_interface_aap_<remoteIp>_Ingress_<lportTag>_<sgRuleId>,
+            TCP_DESTINATION_<port>_<portMask>_ipv6_remoteACL_interface_aap_<remoteIp>_Ingress_<lportTag>_<sgRuleId>,
+            TCP_SOURCE_<port>_<portMask>_Ingress_<lportTag>_<sgRuleId>,
+            TCP_SOURCE_<port>_<portMask>_ipv4_remoteACL_interface_aap_<remoteIp>_Ingress_<lportTag>_<sgRuleId>,
+            TCP_SOURCE_ALL__Ingress_<lportTag>_<sgRuleId>,
+            TCP_SOURCE_ALL__ipv4_remoteACL_interface_aap_<remoteIp>_Ingress_<lportTag>_<sgRuleId>,
+            TCP_SOURCE_ALL__ipv6_remoteACL_interface_aap_<remoteIp>_Ingress_<lportTag>_<sgRuleId>,
+            UDP_DESTINATION_<port>_<portMask>_Ingress_<lportTag>_<sgRuleId>,
+            UDP_DESTINATION_<port>_<portMask>_ipv4_remoteACL_interface_aap_<remoteIp>_Ingress_<lportTag>_<sgRuleId>,
+            UDP_DESTINATION_<port>_<portMask>_ipv6_remoteACL_interface_aap_<remoteIp>_Ingress_<lportTag>_<sgRuleId>,
+            UDP_SOURCE_<port>_<portMask>_Ingress_<lportTag>_<sgRuleId>,
+            UDP_SOURCE_<port>_<portMask>_ipv4_remoteACL_interface_aap_<remoteIp>_Ingress_<lportTag>_<sgRuleId>,
+            UDP_SOURCE_<port>_<portMask>_ipv6_remoteACL_interface_aap_<remoteIp>_Ingress_<lportTag>_<sgRuleId>,
+            UDP_SOURCE_ALL__Ingress_<lportTag>_<sgRuleId>,
+            UDP_SOURCE_ALL__ipv4_remoteACL_interface_aap_<remoteIp>_Ingress_<lportTag>_<sgRuleId>,
+            UDP_SOURCE_ALL__ipv6_remoteACL_interface_aap_<remoteIp>_Ingress_<lportTag>_<sgRuleId>,
+            OTHER_PROTO<protocolNumber>_Ingress_<lportTag>_<sgRuleId>,
+            OTHER_PROTO<protocolNumber>_ipv4_remoteACL_interface_aap_<remoteIp>_Ingress_<lportTag>_<sgRuleId>,
+            OTHER_PROTO<protocolNumber>_ipv6_remoteACL_interface_aap_<remoteIp>_Ingress_<lportTag>_<sgRuleId>
+
+    """
+    flowid = flow['id']
+    """
+        This captures flows with following format:
+            *_<dpnid>_<lport>_*
+    """
+    for prefix in PREFIX_FOR_LPORT[flow['table_id']]:
+        if flowid.startswith(prefix):
+            res = flowid[len(prefix):].split('_')
+            try:
+                flow_info['lport'] = int(res[1])
+                return flow_info
+            except ValueError:
+                """ Possible cases, ignore:
+                    241:Ingress_v4_Broadcast_<dpId>_Permit
+                """
+                pass
+    """
+        This captures flows with following format:
+            *_<lport>_<sgRuleId>
+    """
+    for prefix in PREFIX_LPORT_SGR[flow['table_id']]:
+        if flowid.startswith(prefix):
+            res = flowid[len(prefix):].split('_')
+            try:
+                flow_info['lport'] = int(res[-2])
+                return flow_info
+            except ValueError:
+                """ Possible cases, ignore:
+                    Unexpected, log?
+                """
+                pass
+            except IndexError:
+                # Unknown flow type. Log???
+                pass
+    return flow_info
diff --git a/resources/tools/odl/netvirt/netvirt_utils.py b/resources/tools/odl/netvirt/netvirt_utils.py
new file mode 100644 (file)
index 0000000..f9befa5
--- /dev/null
@@ -0,0 +1,122 @@
+import constants as const
+import json, pprint, urllib2, base64, sys
+import optparse
+
+
+options = None
+args = None
+
+def parse_args():
+    global options, args
+    parser = optparse.OptionParser(version="0.1")
+    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("-m", "--method", action="store", type="string", dest="callMethod", default=None,
+                      help="method to call")
+    (options, args) = parser.parse_args(sys.argv)
+    return options, args
+
+
+def get_options():
+    return options
+
+
+def get_args():
+    return args
+
+
+def create_url(dsType, path):
+    return 'http://{}:{}/restconf/{}/{}/'.format(options.odlIp, options.odlPort, dsType, path)
+
+
+def grabJson(url):
+    data = None
+    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))
+        return data
+
+    if (result.code != 200):
+        printError( '{}\n{}\n\nError: unexpected code: {}\n'.format(result.info(), result.read(), result.code) )
+        return data
+
+    data = json.load(result)
+    return data
+
+
+def nstr(s):
+    if not s:
+        return ''
+    return str(s)
+
+
+def pretty_print(arg):
+    pp = pprint.PrettyPrinter(indent=2)
+    pp.pprint(arg)
+    print
+
+
+def printError(msg):
+    sys.stderr.write(msg)
+
+
+def get_port_name(port):
+    prefix = const.VIF_TYPE_TO_PREFIX.get(port[const.VIF_TYPE])
+    if prefix is None:
+        return None
+    else:
+        return prefix + port['uuid'][:11]
+
+
+def get_dpn_from_ofnodeid(node_id):
+    return node_id.split(':')[1]
+
+
+def to_hex(data, ele=None):
+    if not ele:
+        data = ("0x%x" % data) if data else None
+        return data
+    elif data.get(ele):
+        data[ele] = "0x%x" % data[ele]
+        return data[ele]
+    else:
+        return data
+
+
+def sort(data, field):
+    return sorted(data, key=lambda x: x[field])
+
+
+
+def show_optionals(flow):
+    result = ''
+    lport = flow.get('lport')
+    elantag = flow.get('elan-tag')
+    label = flow.get('mpls')
+    vpnid = flow.get('vpnid')
+    ip = flow.get('iface-ips')
+    if lport:
+        result = '{},LportTag:{}/{}'.format(result, lport, to_hex(lport))
+    if vpnid:
+        result = '{},VpnId:{}/{}'.format(result, vpnid, to_hex(vpnid*2))
+    if label:
+        result = '{},MplsLabel:{}'.format(result, label)
+    if elantag:
+        result = '{},ElanTag:{}/{}'.format(result, elantag, to_hex(elantag))
+    if ip:
+        result = '{},LportIp:{}'.format(result, json.dumps(ip))
+    result = '{},Reason:{}'.format(result, flow.get('reason'))
+    return result
+
diff --git a/resources/tools/odl/netvirt/ovs_get_data.py b/resources/tools/odl/netvirt/ovs_get_data.py
new file mode 100644 (file)
index 0000000..6508486
--- /dev/null
@@ -0,0 +1,92 @@
+import sys
+import re
+
+from datetime import datetime as dt
+from datetime import timedelta as td
+
+base_time = '2017-07-31 20:19:00,698'
+#base_time = None
+TIME_FORMAT = '%Y-%m-%d %H:%M:%S,%f'
+
+def get_ofctl_flows(file_name='ofctl-flows.log'):
+    flow_list = []
+    with open(file_name, 'rb') as f:
+        data = f.read().splitlines()
+        for row in data:
+            meta = row.split(', ')
+            matches, actions = meta.pop().split()
+            flow_list.append(get_flow_dict(meta, matches, actions))
+    return flow_list
+
+
+def get_flow_dict(meta, matches, actions):
+    actions = actions.strip('actions=')
+    flow_dict = {}
+    flow_dict['meta'] = get_meta_dict(meta)
+    flow_dict['matches'] = get_matches_dict(matches)
+    flow_dict['actions'] = get_actions_dict(actions)
+    return flow_dict
+
+
+def get_meta_dict(meta_str):
+    meta = {}
+    for field in meta_str:
+        k, v = field.split('=')
+        if base_time and k == 'duration':
+            v = get_flow_time(v)
+            k = 'installed'
+        meta[k] = v
+    return meta
+
+
+def get_matches_dict(matches_str):
+    return matches_str
+''' Note: This mostly works requires bit of rework for matches like:
+    arp,arp_sa=abc etc.
+    matches = {}
+    for match in matches_str.split(','):
+        if '=' in match:
+            k, v = match.split('=')
+        else:
+            k, v = match, ""
+        matches[k] = v
+    return matches
+'''
+
+
+def get_flow_time(duration):
+    duration = duration.strip('s')
+    time = dt.strptime(base_time, TIME_FORMAT) + td(0, float(duration))
+    time_str = time.strftime(TIME_FORMAT)
+    return time_str
+
+
+def get_actions_dict(action_str):
+    return action_str
+''' todo: Action parsing is WiP and requires much complex logic
+    actions_dict = {}
+    actions = action_str.split(',')
+    actions = iter(actions)
+    for action in actions:
+        if ':' in action:
+            k, v = action.split(':')
+            actions_dict[k] = v
+        elif action.startswith('resubmit'):
+            k = 'resubmit'
+            port = action.strip('resubmit(')
+            try:
+                next_action = next(actions)
+                if next_action.endswith(')'):
+                    table = next_action.strip(')')
+                else:
+                    table = action
+            except StopIteration:
+                break;
+            actions_dict[k] = '('+port+','+table+')'
+    return actions_dict
+'''
+
+
+def print_flow_dict(flows):
+    for flow in flows:
+        print flow['meta'], flow['matches'], flow['actions']
\ No newline at end of file
diff --git a/resources/tools/odl/netvirt/showOvsdbMdsal.py b/resources/tools/odl/netvirt/showOvsdbMdsal.py
new file mode 100644 (file)
index 0000000..3d566eb
--- /dev/null
@@ -0,0 +1,725 @@
+#!/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 make_it_a_string(param):
+    result = ""
+    try:
+        result = str( param )
+    except:
+        pass
+    return result
+
+# ======================================================================
+
+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'] = make_it_a_string(connectionInfoRaw.get('local-ip')) + ':' + make_it_a_string(connectionInfoRaw.get('local-port'))
+        connectionInfo['inetNode'] = make_it_a_string(connectionInfoRaw.get('remote-ip')) + ':' + make_it_a_string(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 ] = '{0: <25} {1: <7} {2}'.format(bridge.getOpenflowName(), bridge.name, bridge.dpId)
+
+    for resultMapKey in sorted(resultMap):
+        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)