Commit cfb580c6 authored by Tomas Hlavacek's avatar Tomas Hlavacek

Cisco parsing improved. Various bugfixes added.

parent 3f9caa80
......@@ -14,20 +14,22 @@
<py:if test="defined('before') and (before)">
${before}
</py:if>
<table border="1">
<tr>
<py:for each="thidx,th in enumerate(table_header)">
<th bgcolor="#C0C0C0">${th}</th>
</py:for>
</tr>
<py:for each="tridx,tr in enumerate(table)">
<py:if test="defined('table_header') and defined('table') and len(table_header) > 0 and len(table) > 0">
<table border="1">
<tr>
<py:for each="tdidx,td in enumerate(tr)">
<td bgcolor="${td[1]}">${td[0]}</td>
<py:for each="thidx,th in enumerate(table_header)">
<th bgcolor="#C0C0C0">${th}</th>
</py:for>
</tr>
</py:for>
</table>
<py:for each="tridx,tr in enumerate(table)">
<tr>
<py:for each="tdidx,td in enumerate(tr)">
<td bgcolor="${td[1]}">${td[0]}</td>
</py:for>
</tr>
</py:for>
</table>
</py:if>
<py:if test="defined('after') and (after)">
${after}
</py:if>
......
......@@ -27,6 +27,7 @@ import defaults
import ulgmodel
import ulg
import ulgcisco
import sys
import re
......@@ -199,6 +200,67 @@ def testULGPersistentStorage():
print traceback.format_exc()
return False
def testULGCiscoParser(header,cisco_input,expect):
try:
res = ulgcisco.matchCiscoBGPLines(header,cisco_input)
# print "DEBUG: Cisco parser input: " + str(cisco_input)
# print "DEBUG: Cisco parser output: " + str(res)
if(res == expect):
print "OK: Test Cisco parser."
return True
else:
print "DEBUG: Cisco parser input: " + str(cisco_input)
print "DEBUG: Cisco parser output: " + str(res)
print "FAIL: Test Cisco parser. Wrong output."
return False
except Exception as e:
print "FAIL: Test Cisco parser.\n Exception="+str(e)
print traceback.format_exc()
return False
def testULGCiscoParser1():
header = "Stat Network Next_Hop Metric LocPrf Weight Path"
cisco_input = """ *> 62.109.128.0/19 0.0.0.0 0 32768 i
*> 91.199.207.0/24 217.31.48.86 1024 0 44672 i
*> 91.224.58.0/23 217.31.48.70 100 1024 0 50833 i
*> 93.170.16.0/21 217.31.48.70 100 1024 0 50833 i
*> 188.227.128.0/19 0.0.0.0 0 32768 i
*> 217.31.48.0/20 0.0.0.0 0 32768 i"""
expect = [['*>', '62.109.128.0/19', '0.0.0.0', '0', '', '32768', 'i'],
['*>', '91.199.207.0/24', '217.31.48.86', '', '1024', '0', '44672 i'],
['*>', '91.224.58.0/23', '217.31.48.70', '100', '1024', '0', '50833 i'],
['*>', '93.170.16.0/21', '217.31.48.70', '100', '1024', '0', '50833 i'],
['*>', '188.227.128.0/19', '0.0.0.0', '0', '', '32768', 'i'],
['*>', '217.31.48.0/20', '0.0.0.0', '0', '', '32768', 'i']]
return testULGCiscoParser(header,cisco_input.splitlines(),expect)
def testULGCiscoParser2():
header = "Stat Network Next_Hop Metric LocPrf Weight Path"
cisco_input = " * 195.226.217.0 62.109.128.9 0 51278 i"
expect = [['*', '195.226.217.0', '62.109.128.9', '', '', '0', '51278 i']]
return testULGCiscoParser(header,cisco_input.splitlines(),expect)
def testULGCiscoParser3():
header = "Stat Network Next_Hop Metric LocPrf Weight Path"
cisco_input = """ *> 2001:67C:278::/48
2001:1AB0:7E1F:1:230:48FF:FE8C:A192
1024 0 51278 i
*> 2001:1AB0::/32 :: 0 32768 i
*> 2A01:8C00::/32 :: 0 32768 i"""
expect = [['*>', '2001:67C:278::/48', '2001:1AB0:7E1F:1:230:48FF:FE8C:A192', '', '1024', '0', '51278 i'],
['*>', '2001:1AB0::/32', '::', '0', '', '32768', 'i'],
['*>', '2A01:8C00::/32', '::', '0', '', '32768', 'i']]
return testULGCiscoParser(header,cisco_input.splitlines(),expect)
#####################################
......@@ -222,23 +284,17 @@ Tests failed: %d/%d""" % (len(passed),tests,len(failed),tests)
# main
if __name__=="__main__":
runTest(testRouterCommand(router=0,command=0,params=[]))
runTest(testULGRunParameter(router=0,command=4,params=['91.210.16.1']))
runTest(testULGIndex(routerid=0,commandid=0,sessionid=None))
runTest(testULGAction(routerid=0,commandid=0,sessionid=None,maxtimes=10,interval=5,**{}))
runTest(testULGSessions())
runTest(testULGLock())
runTest(testULGLog())
runTest(testULGRescan())
runTest(testULGPersistentStorage())
runTest(testULGCiscoParser1())
runTest(testULGCiscoParser2())
runTest(testULGCiscoParser3())
reportResults()
......@@ -225,7 +225,7 @@ class ULGCgi:
# Acquire lock
fcntl.lockf(lf, fcntl.LOCK_EX)
except IOError,ValueError:
except (IOError,ValueError) as e:
ulgmodel.log("Locking mechanism failure: "+str(e))
return False
......
......@@ -22,6 +22,8 @@
import os
import pexpect
import re
import sys
import string
import defaults
......@@ -31,12 +33,14 @@ import ulgmodel
STRING_EXPECT_SSH_NEWKEY='Are you sure you want to continue connecting'
STRING_EXPECT_PASSWORD='(P|p)assword:'
STRING_EXPECT_SHELL_PROMPT_REGEXP = '\n[a-zA-Z0-9\._-]+>'
BGP_IPV6_SUM_TABLE_SPLITLINE_REGEXP='^\s*[0-9a-fA-F:]+\s*$'
BGP_IPV6_TABLE_SPLITLINE_REGEXP='^\s*[0-9a-fA-F:]+\s*$'
BGP_IPV6_TABLE_HEADER_REGEXP='^\s*(Neighbor)\s+(V)\s+(AS)\s+(MsgRcvd)\s+(MsgSent)\s+(TblVer)\s+(InQ)\s+(OutQ)\s+(Up/Down)\s+(State/PfxRcd)\s*$'
BGP_IPV6_TABLE_LINE_REGEXP='^\s*([0-9a-fA-F:]+)\s+([0-9]+)\s+([0-9]+)\s+([0-9]+)\s+([0-9]+)\s+([0-9]+)\s+([0-9]+)\s+([0-9]+)\s+([a-zA-Z0-9:]+)\s+([a-zA-Z0-9]+|[a-zA-Z0-9]+\s\(Admin\))\s*$'
BGP_IPV4_TABLE_LINE_REGEXP='^\s*([0-9\.]+)\s+([0-9]+)\s+([0-9]+)\s+([0-9]+)\s+([0-9]+)\s+([0-9]+)\s+([0-9]+)\s+([0-9]+)\s+([a-zA-Z0-9:]+)\s+([a-zA-Z0-9]+|[a-zA-Z0-9]+\s\(Admin\))\s*$'
BGP_IPV6_TABLE_LINE_REGEXP='^\s*([0-9a-fA-F:]+)\s+([0-9]+)\s+([0-9]+)\s+([0-9]+)\s+([0-9]+)\s+([0-9]+)\s+([0-9]+)\s+([0-9]+)\s+([a-zA-Z0-9:]+)\s+([a-zA-Z0-9\(\)]+|[a-zA-Z0-9]+\s\(Admin\))\s*$'
BGP_IPV4_TABLE_HEADER_REGEXP='^\s*(Neighbor)\s+(V)\s+(AS)\s+(MsgRcvd)\s+(MsgSent)\s+(TblVer)\s+(InQ)\s+(OutQ)\s+(Up/Down)\s+(State/PfxRcd)\s*$'
BGP_IPV4_TABLE_LINE_REGEXP='^\s*([0-9\.]+)\s+([0-9]+)\s+([0-9]+)\s+([0-9]+)\s+([0-9]+)\s+([0-9]+)\s+([0-9]+)\s+([0-9]+)\s+([a-zA-Z0-9:]+)\s+([a-zA-Z0-9\(\)]+|[a-zA-Z0-9]+\s\(Admin\))\s*$'
BGP_PREFIX_TABLE_HEADER='^(\s)\s+(Network)\s+(Next Hop)\s+(Metric)\s+(LocPrf)\s+(Weight)\s+(Path)\s*$'
RESCAN_BGP_IPv4_COMMAND='show bgp ipv4 unicast summary'
RESCAN_BGP_IPv6_COMMAND='show bgp ipv6 unicast summary'
......@@ -47,12 +51,129 @@ IPV6_SUBNET_REGEXP = '^[0-9a-fA-F:]+(/[0-9]{1,2}){0,1}$'
IPV6_ADDRESS_REGEXP = '^[0-9a-fA-F:]+$'
MAC_ADDRESS_REGEXP = '^[0-9a-fA-F]{4}\.[0-9a-fA-F]{4}\.[0-9a-fA-F]{4}$'
BGP_RED_STATES = ['Idle', 'Active', '(NoNeg)']
BGP_YELLOW_STATES = ['Idle (Admin)',]
def matchCiscoBGPLines(header,lines):
# TODO
# Match cisco lines formatted to be aligned to columns. Like:
# Network Next Hop Metric LocPrf Weight Path
# * 79.170.248.0/21 91.210.16.6 300 0 20723 i
# *>i91.199.207.0/24 217.31.48.123 0 1024 0 44672 i
# 0 1 2 3 4 5 6
#
# Network Next Hop Metric LocPrf Weight Path
# *>i2001:67C:278::/48
# 2001:1AB0:B0F4:FFFF::2
# 0 1024 0 51278 i
# *> 2001:1AB0::/32 :: 0 32768 i
# 0 1 2 3 4 5 6
#
# First find boundaries, second section text (if it overflow next boundary
# line wrap is expected) and then remove white spaces and return sections.
#
# ?1 hat happens when last element is line-wrapped? It looks like it does
# not happen in my settings.
def divideGroups(line,max_index_start=sys.maxint,table_line=False):
# divide groups starting before max_index_start
result = []
# when parsing table_line (not the header and not the continuation line)
# cut off first three charactes and use them as a group
if(table_line and
(not re.match('^\s*$',line[0:3]))):
result.append([0,3])
line = ' '+line[3:]
last_group = False
for r in re.compile('[^\s]+').finditer(line):
if(not last_group):
result.append([r.start(),r.end()])
if(r.start() >= max_index_start):
last_group = True
# shortcut for empty lines / no results
if(len(result)==0):
return None
# add tail to last groups
result[-1][1] = len(line)
return result
def matchGroup(header_groups_indexes,line_group_indexes,last_element):
if(len(header_groups_indexes) == 1):
return 0
# beginning of the second group is right of the beginning of the tested
if((len(header_groups_indexes) > 1) and
(header_groups_indexes[1][0] > line_group_indexes[0])):
return 0
# beginning of the last (and the only possible) group is left
# the beginning of the tested
if(header_groups_indexes[-1][0] <= line_group_indexes[0]):
return (len(header_groups_indexes)-1)
# linear algorithm !!!
# rewrite to tree(?)
for hgipos,hgi in enumerate(header_groups_indexes):
if((hgipos >= 1) and
(hgipos < (len(header_groups_indexes)-1)) and
(header_groups_indexes[hgipos-1][1] <= line_group_indexes[0]) and
(header_groups_indexes[hgipos+1][0] >= line_group_indexes[1])):
return hgipos
if((last_element) and
(hgipos >= 1) and
(hgi[0] <= line_group_indexes[0]) and
(len(header_groups_indexes)-1 > hgipos) and
(header_groups_indexes[hgipos+1][0] > line_group_indexes[0])):
return hgipos
return None
def normalize(instr):
return instr.strip()
hgidxs = divideGroups(header)
result = [[]]
for l in lines:
# divide groups (leave the last group in one part)
lgps = divideGroups(l,hgidxs[-1][0],True)
if(lgps==None):
continue
for lgpidx,lgp in enumerate(lgps):
gidx = matchGroup(hgidxs,lgp,(lgpidx == (len(lgps)-1)))
if(gidx == None):
raise Exception("No group matched for line indexes. line="+l+" header_group_indexes="+
str(hgidxs)+" line_group_index="+str(lgp))
def normalizeBGPIPv6SplitLines(lines):
if(gidx < len(result[-1])):
result.append([])
while(gidx > len(result[-1])):
result[-1].append('')
result[-1].append(normalize(l[lgp[0]:lgp[1]]))
return result
def normalizeBGPIPv6SumSplitLines(lines):
"""This function concatenates lines with longer IPv6 addresses that
router splits (for no obvious reason)."""
router splits on the header boundary."""
result = []
slr = re.compile(BGP_IPV6_TABLE_SPLITLINE_REGEXP)
slr = re.compile(BGP_IPV6_SUM_TABLE_SPLITLINE_REGEXP)
b = None
for l in lines:
......@@ -71,6 +192,9 @@ def normalizeBGPIPv6SplitLines(lines):
class CiscoCommandBgpIPv46Sum(ulgmodel.TextCommand):
""" Abstract class, baseline for IPv4 and IPv6 versions. """
RED_STATES = BGP_RED_STATES
YELLOW_STATES = BGP_YELLOW_STATES
def __init__(self,name=None,peer_address_command=None,peer_received_command=None):
self.command=self.COMMAND_TEXT
self.param_specs=[]
......@@ -122,9 +246,8 @@ class CiscoCommandBgpIPv46Sum(ulgmodel.TextCommand):
else:
return received
def _decorateTableLine(self,line,decorator_helper,router):
lrm = re.compile(self.table_line_regexp).match(line)
lrm = re.compile(self.TABLE_LINE_REGEXP).match(line)
if(lrm):
# color selection
if(lrm.group(10) in self.YELLOW_STATES):
......@@ -148,7 +271,7 @@ class CiscoCommandBgpIPv46Sum(ulgmodel.TextCommand):
(self._getReceivedTableCell(decorator_helper,router,lrm.group(1),lrm.group(10)),color),
]
else:
raise Exception("Can not parse line: "+l)
raise Exception("Can not parse line: "+line)
def decorateResult(self,result,router=None,decorator_helper=None):
if((not router) or (not decorator_helper)):
......@@ -162,8 +285,8 @@ class CiscoCommandBgpIPv46Sum(ulgmodel.TextCommand):
table_header=[]
tb = False
header_regexp = re.compile(self.table_header_regexp)
line_regexp = re.compile(self.table_line_regexp)
header_regexp = re.compile(self.TABLE_HEADER_REGEXP)
line_regexp = re.compile(self.TABLE_LINE_REGEXP)
for l in lines:
if(tb):
# inside table body
......@@ -184,11 +307,8 @@ class CiscoCommandBgpIPv46Sum(ulgmodel.TextCommand):
class CiscoCommandBgpIPv4Sum(CiscoCommandBgpIPv46Sum):
COMMAND_TEXT='show bgp ipv4 unicast summary'
table_line_regexp=BGP_IPV4_TABLE_LINE_REGEXP
table_header_regexp=BGP_IPV4_TABLE_HEADER_REGEXP
RED_STATES = ['Idle', 'Active']
YELLOW_STATES = ['Idle (Admin)',]
TABLE_LINE_REGEXP=BGP_IPV4_TABLE_LINE_REGEXP
TABLE_HEADER_REGEXP=BGP_IPV4_TABLE_HEADER_REGEXP
def __init__(self,name=None,peer_address_command=None,peer_received_command=None):
return CiscoCommandBgpIPv46Sum.__init__(self,name,peer_address_command,peer_received_command)
......@@ -196,36 +316,108 @@ class CiscoCommandBgpIPv4Sum(CiscoCommandBgpIPv46Sum):
class CiscoCommandBgpIPv6Sum(CiscoCommandBgpIPv46Sum):
COMMAND_TEXT='show bgp ipv6 unicast summary'
table_line_regexp=BGP_IPV6_TABLE_LINE_REGEXP
table_header_regexp=BGP_IPV6_TABLE_HEADER_REGEXP
RED_STATES = ['Idle', 'Active']
YELLOW_STATES = ['Idle (Admin)',]
TABLE_LINE_REGEXP=BGP_IPV6_TABLE_LINE_REGEXP
TABLE_HEADER_REGEXP=BGP_IPV6_TABLE_HEADER_REGEXP
def __init__(self,name=None,peer_address_command=None,peer_received_command=None):
return CiscoCommandBgpIPv46Sum.__init__(self,name,peer_address_command,peer_received_command)
def decorateResult(self,result,router=None,decorator_helper=None):
res=''
for l in normalizeBGPIPv6SplitLines(str.splitlines(result)):
for l in normalizeBGPIPv6SumSplitLines(str.splitlines(result)):
res = res + "\n" + l
return super(CiscoCommandBgpIPv6Sum,self).decorateResult(res,router,decorator_helper)
class CiscoCommandShowBgpIPv4Neigh(ulgmodel.TextCommand):
COMMAND_TEXT='show bgp ipv4 unicast neighbor %s'
def __init__(self,peers,name=None):
peer_param = ulgmodel.SelectionParameter([tuple((p,p,)) for p in peers],
name=defaults.STRING_IPADDRESS)
ulgmodel.TextCommand.__init__(self,self.COMMAND_TEXT,param_specs=[peer_param],name=name)
class CiscoCommandShowBgpIPv6Neigh(ulgmodel.TextCommand):
COMMAND_TEXT='show bgp ipv6 unicast neighbor %s'
def __init__(self,peers,name=None):
peer_param = ulgmodel.SelectionParameter([tuple((p,p,)) for p in peers],
name=defaults.STRING_IPADDRESS)
ulgmodel.TextCommand.__init__(self,self.COMMAND_TEXT,param_specs=[peer_param],name=name)
class CiscoCommandShowBgpIPv46Select(ulgmodel.TextCommand):
TABLE_HEADER_REGEXP=BGP_PREFIX_TABLE_HEADER
LASTLINE_REGEXP='^\s*Total number of prefixes [0-9]+\s*$'
def __init__(self,peers,name=None):
peer_param = ulgmodel.SelectionParameter([tuple((p,p,)) for p in peers],
name=defaults.STRING_IPADDRESS)
ulgmodel.TextCommand.__init__(self,self.COMMAND_TEXT,param_specs=[peer_param],name=name)
class CiscoCommandShowBgpIPv4Neigh(CiscoCommandShowBgpIPv46Select):
COMMAND_TEXT='show bgp ipv4 unicast neighbor %s'
def _genTable(self,table_lines,decorator_helper,router):
mls = matchCiscoBGPLines(self.table_header,table_lines)
class CiscoCommandShowBgpIPv6Neigh(CiscoCommandShowBgpIPv46Select):
COMMAND_TEXT='show bgp ipv6 unicast neighbor %s'
result = []
for ml in mls:
# generate table content
result.append([
(ml[0],),
(ml[1],),
(ml[2],),
(ml[3],),
(ml[4],),
(ml[5],),
(ml[6],),
])
return result
def decorateResult(self,result,router=None,decorator_helper=None):
if((not router) or (not decorator_helper)):
return "<pre>\n%s\n</pre>" % result
lines = str.splitlines(result)
before=''
after=None
table=[]
table_header_descr=[]
tb = False
header_regexp = re.compile(self.TABLE_HEADER_REGEXP)
lastline_regexp = re.compile(self.LASTLINE_REGEXP)
table_lines = []
for l in lines:
if(tb):
# inside table body
if(lastline_regexp.match(l)):
after = l
else:
table_lines.append(l)
else:
# should we switch to table body?
thrm = header_regexp.match(l)
if(thrm):
# set header accoring to the local router alignment
# include (unnamed) states (=S)
self.table_header = 'S'+(l[1:].replace('Next Hop','Next_Hop',1))
tb = True
table_header_descr = [g for g in thrm.groups()]
else:
# not yet in the table body, append before-table section
before = before + l + '\n'
if(table_lines):
table = self._genTable(table_lines,decorator_helper,router)
if(after):
after=decorator_helper.pre(after)
return ulgmodel.TableDecorator(table,table_header_descr,before=decorator_helper.pre(before),
after=after).decorate()
class CiscoCommandShowBgpIPv4NeighAdv(CiscoCommandShowBgpIPv46Select):
COMMAND_TEXT='show bgp ipv4 unicast neighbor %s advertised'
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment