#!/usr/bin/python
import subprocess
import sys
import re
from collections import defaultdict
help ='''## readable_flows.py:
This script pretty prints ovs flows created by ovsdb. Features include:
1. Where possible, MACs are followed by their corresponding IP (in parenthases) and vice-versa
2. Tunnel ids are followed by the decimal representation used by Open Daylight
3. Counters and stats are removed so that meaningful diffs may be generated
4. Table numbers are given together with descriptive names
5. Flows are grouped together by priority in decending order
### Usage:
This script must be run on the OpenStack controller since it uses the
neutron command to map MACs and IPs.
> sudo ovs-ofctl -OOpenFlow13 dump-flows br-int | python readable_flows.py
'''
if len(sys.argv) > 1 and sys.argv[1] in ['-h', '-?', '--h', '--help']:
print help
sys.exit(0)
DEFAULT_PRIO=32768
TABLE_NAME = { \
0: 'CLASSIFIER',\
20: 'GATEWAY_RESOLVER',\
10: 'DIRECTOR',\
10: 'SFC_CLASSIFIER',\
20: 'ARP_RESPONDER',\
30: 'INBOUND_NAT',\
40: 'EGRESS_ACL',\
50: 'LOAD_BALANCER',\
60: 'ROUTING',\
70: 'ICMP_ECHO',\
70: 'L3_FORWARDING',\
80: 'L2_REWRITE',\
90: 'INGRESS_ACL',\
100: 'OUTBOUND_NAT',\
110: 'L2_FORWARDING'}
PORT_LIST_CMD = 'neutron port-list -c mac_address -c fixed_ips'.split(' ')
LINE_PATTERN = '.*(?P
table=\d*), (?Pn_packets=\d*, n_bytes=\d*), (?Ppriority=\d*)?,?(?P.*)'
MAX_LINE = 30
def print_rules(table, rules_by_prio):
print ''
cut = table.find('=')
print table.upper() + ' (' + TABLE_NAME[int(table[cut+1:])] + ')'
prios = rules_by_prio.keys()
prios.sort()
prios.reverse()
for prio in prios:
print ' priority=%i' % prio,
if DEFAULT_PRIO == prio: print '(DEFAULT_PRIO)'
else: print ''
for rule in rules_by_prio[prio]:
print_flow(' ', re.sub('actions=', 'ACTIONS=', rule))
def tun_match_to_decimal(m):
s = m.group('num')
return 'tun_id=0x%s(%i)' % (s, int(s, 16))
def tun_set_to_decimal(m):
s = m.group('num')
return '0x%s(%i)->tun_id' % (s, int(s, 16))
def print_flow(indent, f):
print indent + f
# WIP
# flow = indent + f
# if len(flow) <= MAX_LINE:
# print flow
# return
#
# cut = flow.find('ACTIONS')
# match = flow[0:cut - 1]
# action = indent + indent + flow[cut:]
# print match
# while action:
# if len(action) <= MAX_LINE:
# print action
# break
# cut = action.rfind(',', 0, MAX_LINE)
# if cut < 2:
# print action
# break
# print action[0:cut + 1]
# action = indent + indent + (' ' * len('ACTIONS=')) + action[cut +1:]
port_list_out = subprocess.check_output(PORT_LIST_CMD)
addresses = []
for line in port_list_out.split('\n')[2:]:
if re.match('^$', line): continue
if '----' in line: continue
line = re.sub('^\| ', '', line)
line = re.sub(' *\|$', '', line)
(mac, fixed_ip) = line.split(' | ')
ip = eval(fixed_ip)['ip_address']
addresses.append((mac, ip))
table = ''
rules_by_prio = defaultdict(list)
for line in sys.stdin:
for (mac, ip) in addresses:
line = re.sub(mac, '%s(%s)' % (mac, ip), line)
line = re.sub('=%s(\D)' % ip, '=%s(%s)\\1' % (ip, mac), line)
line = re.sub('tun_id=0x(?P[0-9a-fA-F]*)', tun_match_to_decimal, line)
line = re.sub('0x(?P[0-9a-fA-F]*)->tun_id', tun_set_to_decimal, line)
match = re.match(LINE_PATTERN, line)
if not match:
print '[Not a flow line?]: ' + line,
continue
if match.group('table') != table:
if table:
print_rules(table, rules_by_prio)
rules_by_prio = defaultdict(list)
table = match.group('table')
prio = DEFAULT_PRIO
prio_str = match.group('prio')
if None != prio_str: prio = int(prio_str[9:])
rules_by_prio[prio].append(match.group('rule'))
print_rules(table, rules_by_prio)