Core documentation¶
Markup Syntax¶
CLI output parsing rules may be specified using a marked-up text input that is
divided into OS
sections and then further subdivided into 3-5 sub-sections.
By default, the supported OS
values are :
IOX
, IOS
, NXOS
and LINUX
.
The supported sub-subsections are CMD
, SHOWCMD
, PREFIX
, ACTUAL
and MARKUP
. The ACTUAL
subsection is optional and is used to simply
store the unmarked up raw show output.
An example of a simple marked-up file is as follows:
OS: NXOS
CMD: SHOW_MRIB_ROUTE
SHOWCMD: show ip mroute
PREFIX: mrib-route
ACTUAL:
(13.13.13.4/32, 232.0.0.1/32), uptime: 00:00:43, igmp pim ip
Incoming interface: Ethernet2/1, RPF nbr: 11.11.11.2^M
Outgoing interface list: (count: 1)^M
Ethernet2/2, uptime: 00:00:43, igmp^M
MARKUP:
(XP<source>X13.13.13.4/32, XP<group>X232.0.0.1/32), uptime: XT<uptime>X00:00:43,
XR<protos>Xigmp pim ip
Incoming interface: XI<ingress-intf>XEthernet2/1, RPF nbr: XA<rpf>X11.11.11.2
Outgoing interface list: (count: XN<egress-count>X1)
XI<egress-intf>XEthernet2/2, uptime: XT<egress-uptime>X00:00:43, XR<egress-protos>Xigmp
The above is used to tell core
the details of how to go
about parsing the show ip mroute
command on a NXOS router.
The OS
Section¶
Multiple OS architectures are supported. The OS
section tells
core
which OS the following show commands (and their
resulting regular expressions
) are for. The currently supported operating
systems are IOS
, IOX
, NXOS
and LINUX
.
The CMD
Sub-Section¶
The CMD
section is a single line that records the symbolic name for the
show command, this name is passed to core
so that it can
look up the correct actual show command for the given OS
. If this section
is not specified then it will be created by uppercasing the SHOWCMD
value
and converting spaces and dashes to underscores.
The SHOWCMD
Sub-Section¶
The SHOWCMD
section is a single line that records the show command that
generated the output that will follow.
The PREFIX
Sub-Section¶
The prefix section indicates the prefix that should be used to identify all
regular expressions when accessing core
parsing classes.
The ACTUAL
Sub-Section¶
One typically copies and pastes the output from a show command into the
ACTUAL
section, which is simply a place to keep an unmodified copy of the
show command output.
The MARKUP
Sub-Section¶
One typically copies the ACTUAL
output into the MARKUP
section and then
uses an XxX
or Xx<name>X
notation to tell core
what type
of value follows. The markup notation allows for instructing
core
what to name the value as well. If the name is omitted then
core
takes a guess as to the name by using text immediately before or after the value. A warning is generated if duplicate names are detected.
The following are the available values for x
in the XxX
notation:
A - IPv4 or IPv6 address.
B - Value terminated with a close brace, bracket, or parenthesis.
C - Value terminated with a comma.
F - Floating point number.
H - Hexidecimal number.
I - Interface name.
M - Mac address.
N - Decimal number.
R - everything else to the newline.
P - IPv4 or IPv6 prefix.
Q - Value terminated by a double quote.
S - Non-space value.
T - Time (00:00:00)
W - A word.
Note
Additionally if one uses lower case rather than upper case for x
then the value is matched with a regular expression but no tag is
created. This allows for specifying a single line multiple times in order
to omit various values that may only optionally be present.
For example consider if the uptime
and protos
values were optional in
the output from show ip mroute
on NXOS
:
(XP<source>X13.13.13.4/32, XP<group>X232.0.0.1/32)
(Xp<source>X13.13.13.4/32, Xp<group>X232.0.0.1/32), uptime: XT<uptime>X00:00:43
(Xp<source>X13.13.13.4/32, Xp<group>X232.0.0.1/32), uptime: Xt<uptime>X00:00:43,
XR<protos>Xigmp pim ip
This generates the following dictionary output:
'mrib-route.source': '\(([A-Fa-f0-9/:\.]+),\s+[A-Fa-f0-9/:\.]+\)',
'mrib-route.group' : '\([A-Fa-f0-9/:\.]+,\s+([A-Fa-f0-9/:\.]+)\)',
'mrib-route.uptime': '\([A-Fa-f0-9/:\.]+,\s+[A-Fa-f0-9/:\.]+\),
uptime:\s+(\d{2}:\d{2}:\d{2})',
'mrib-route.protos': '\([A-Fa-f0-9/:\.]+,\s+[A-Fa-f0-9/:\.]+\),
uptime:\s+\d{2}:\d{2}:\d{2},\s+([^\r\n]+)',
You can see above that uptime
and protos
are not present on the first
and second lines because they do not need to be present for source
or
group
to match.
A second markup notation which allows for specifying the exact regex to parse
the value is allowed for cases where none of the above predefined value types
works. This notation takes the form XXX<regex>XXX
or
XXX<regex><name>XXX
. So using this notation in place of XSX would look like
this:
XXX<\S+>XXXsome-non-space-value-to-parse
Note
Manual regex specification via XXX
does not support the use of the
repetition operators {m} and {m, n}.
Parsing Example¶
The following example shows how a user can specify marked-up text, and then
use core
to parse both tabular and non-tabular elements in
a piece of show command output:
Sample Parsing Script¶
The following script illustrates the parsing of show command output that
contains both tabular and non-tabular content. It also shows the automatic
validation of non-tabular content. Notice the use of a tuple in the
values field to indicate multiple acceptable matches. Notice also that
in the cases where the user does not specify the field name, a name is
automatically chosen based on surrounding text(table-id
):
bgp_summ_actual_output="""
BGP router identifier 192.168.0.12, local AS number 100
BGP generic scan interval 60 secs
BGP table state: Active
Table ID: 0xe0000000 RD version: 8
BGP main routing table version 8
BGP scan interval 60 secs
BGP is operating in STANDALONE mode.
Process RcvTblVer bRIB/RIB LabelVer ImportVer SendTblVer StandbyVer
Speaker 8 8 8 8 8 8
Neighbor Spk AS MsgRcvd MsgSent TblVer InQ OutQ Up/Down St/PfxRcd
50.1.0.2 0 100 63 55 8 0 0 00:51:24 100
50.1.0.3 0 200 63 55 8 0 0 00:40:16 200
"""
bgp_summ_marked_up_output="""
OS: IOX
CMD: BGP_SUMMARY
SHOWCMD: show bgp summary
PREFIX: bgp
MARKUP:
BGP router identifier XA<router-id>X192.168.0.12, local AS number XN<local-as>X100
BGP generic scan interval XN<gen-scan-interval>X60 secs
BGP table state: XW<table-state>XActive
Table ID: XHX0xe0000000 RD version: 8
BGP main routing table version 8
BGP scan interval XN<scan-interval>X60 secs
BGP is operating in XW<oper-mode>XSTANDALONE mode.
Process RcvTblVer bRIB/RIB LabelVer ImportVer SendTblVer StandbyVer
Speaker 8 8 8 8 8 8
Neighbor Spk AS MsgRcvd MsgSent TblVer InQ OutQ Up/Down St/PfxRcd
50.1.0.2 0 100 63 55 8 0 0 00:51:24 100
50.1.0.3 0 200 63 55 8 0 0 00:40:16 200
"""
#
# Parse non-tabular data
#
from genie.parsergen.core import extend_markup
extend_markup(bgp_summ_marked_up_output)
from genie.parsergen.core import _parser_gen_t
from genie.parsergen import core
parse_key = 'rtr1'
parsergen.core.text_to_parse[parse_key] = bgp_summ_actual_output
print ("Parsing CLI show command output: ...")
tags_to_parse = [
'bgp.local-as', (100, 101),
'bgp.router-id', None,
'bgp.gen-scan-interval', None,
'bgp.table-state', None,
'bgp.table-id', None,
'bgp.scan-interval', None,
'bgp.oper-mode', None]
pg = _parser_gen_t('IOX', tags_to_parse[::2], tags_to_parse[1::2], fill = True,
parse_key=parse_key)
(validate, dictio, msg) = pg._parser_validate()
print(" Parser's fill report :\n validate={}," +
" \n dictio={}, \n msg={}\n".
format(validate, dictio, msg))
parsed_output = parsergen.core.ext_dictio[parse_key]
#
# Parse tabular data
#
from genie.parsergen.core import column_table_result_core_t
header = ['Neighbor', 'Spk', 'AS', 'MsgRcvd', 'MsgSent',
'TblVer', 'InQ', 'OutQ', 'Up/Down', 'St/PfxRcd']
labels = ['neighbor', 'spk', 'as', 'msg_rcvd', 'msg_sent' ,
'tbl_ver', 'in_q', 'out_q', 'time', 'prefixes']
tabular_parse_result = column_table_result_core_t(
header, label_fields=labels, index=[0,2],
right_justified=True, parse_key=parse_key);
parsed_output['bgp.neighbor-table'] = tabular_parse_result.entries
print ("CLI parse results for combination tabular/non-tabular data:")
import pprint
pprint.pprint(parsed_output, indent=5)
print ("\n\nTesting COMPARE mode for non-tabular data: ...")
tags_to_parse = [
'bgp.local-as', (100,101),
'bgp.router-id', '192.168.0.12',
'bgp.gen-scan-interval', 60,
'bgp.table-state', 'Active',
'bgp.table-id', '0xe0000000',
'bgp.scan-interval', 60,
'bgp.oper-mode', 'STANDALONE']
pg = _parser_gen_t('IOX', tags_to_parse[::2], tags_to_parse[1::2],
fill = False, parse_key=parse_key)
(validate, dictio, msg) = pg._parser_validate()
print(" Expected parser output :\n {}\n".format(tags_to_parse))
print(" Actual parser output :\n {}\n".format(parsergen.core.ext_dictio[parse_key]))
print(" Parser's comparison report :\n validate={}," +
" \n dictio={}, \n msg={}\n".
format(validate, dictio, msg))
Use of Subclassing with the Tabular Parser¶
A subclass can define a class dictionary variable field_mapping
whose keys are field names and whose value is the type of that
field. When the results are parsed the value is cast to the given
type. Values can also take the form of a generic mapping function.
A subclass can define a class list variable table_title_mapping
which is a list of mapping functions for table title value keys.
If specified, this list has an entry for each matching group present in
table_title_pattern
.
A subclass can also override the function cleanup_entry_field
to
cleanup a field value prior to it being stored. This is called prior
to the mapping being done.
Sub-class Example
:
from genie.parsergen import core
parse_key = ''
core.text_to_parse[parse_key] = '''
RP/0/0/CPU0:iox#show isis database
Wed Dec 16 09:48:55.017 EST
IS-IS ring (Level-1) Link State Database
LSPID LSP Seq Num LSP Checksum LSP Holdtime ATT/P/OL
iox.00-00 * 0x00000008 0xf9fd 1003 0/0/0
ioxbfd.00-00 0x00000004 0x8f36 862 0/0/0
Total Level-1 LSP count: 4 Local Level-1 LSP count: 1
IS-IS ring (Level-2) Link State Database
LSPID LSP Seq Num LSP Checksum LSP Holdtime ATT/P/OL
iox.00-00 * 0x00000009 0x351a 1003 0/0/0
iox.01-00 0x00000002 0x0ead 922 0/0/0
Total Level-2 LSP count: 4 Local Level-2 LSP count: 1
'''
def _hexint (val):
return int(val, 16)
def cleanupLspId (field):
return field
from core import column_table_result_core_t
class my_isis_database_column_parser_t (column_table_result_core_t):
field_mapping = {
'LSPID' : str,
'LSP Seq Num' : _hexint,
'LSP Checksum': _hexint,
'LSP Holdtime': None,
}
table_title_mapping = [ int ]
def __init__ (self):
headers = ["LSPID", "LSP Seq Num", "LSP Checksum",
"LSP Holdtime", "ATT/P/OL"]
labels = headers
column_table_result_core_t.__init__(
self,
headers,
"Total Level-[12] LSP count:",
table_title_pattern =
r"IS-IS (?:[-\w]+ )?\(?Level-([12])\)? Link State Database:?",
label_fields = labels)
def cleanup_entry_field (self, header, field):
if header == "LSPID":
# Strip the "*" off the LSPID for the router's own LSPID.
return cleanupLspId(field)
else:
return field
result = my_isis_database_column_parser_t()
print (result.entries[2]['iox.01-00']['LSP Holdtime'])
922