#!/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',\ 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 = '.*(?Ptable=\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)