Define Views for Junos PyEZ Operational Tables that Parse Unstructured Output
Junos PyEZ operational (op) Tables for unstructured output extract data from the text output of a
CLI command executed on a Junos device or a vty command executed on a given Flexible PIC
Concentrator (FPC). A Table is associated with a View, which is used to access fields
in the Table items and map them to user-defined Python variables. You associate a Table with a
particular View by including the view
property in the Table definition, which
takes the View name as its argument.
A View maps your user-defined variables to data in the selected Table items. A View enables you to access specific fields in the output as variables with properties that can be manipulated in Python. Junos PyEZ handles the extraction of the data into Python as well as any type conversion or data normalization. The keys defined in the View must be valid Python variable names.
This topic discusses the different components of the View.
Summary of Parameters in Views for Parsing Unstructured Output
Junos PyEZ Views, like Tables, are formatted using YAML. Views that parse unstructured output can include a number of parameters, which are summarized in Table 1.
View Parameter |
Description |
---|---|
View Name |
User-defined identifier for the View. |
|
(Optional) List of column titles in the command output. |
|
(Optional) Associative array, or dictionary, of one or
more key-value pairs that map a user-defined key to a string containing
a mathematical expression. For each iteration of the data, the expression
is evaluated using the Python |
|
(Optional) Associative array, or dictionary, of key-value pairs that map a user-defined key to a string. If the string is present in the output, the variable is set to True, otherwise, the variable is set to False. |
|
(Optional) Associative array, or dictionary, of key-value pairs that map a user-defined key to the name of a nested Table that parses a specific section of the command output. |
|
(Optional) List of one or more keys defined under |
|
(Optional) List of regular expressions to match desired content. |
View Name
The View name is a user-defined identifier for the View. You
associate a Table with a particular View by including the view
property in the Table definition and providing
the View name as its argument.
The following example defines a View named ChassisFanView
, which is referenced by the Table’s view
parameter:
--- ChassisFanTable: command: show chassis fan key: fan-name view: ChassisFanView ChassisFanView: columns: fan-name: Item fan-status: Status fan-rpm: RPM fan-measurement: Measurement
columns
You can use the columns
parameter
in a View to extract and parse command output that is formatted in
rows and columns.
Consider the following show ospf neighbor
command output:
Address Interface State ID Pri Dead 198.51.100.2 ge-0/0/0.0 Full 192.168.0.2 128 37 198.51.100.6 ge-0/0/1.0 Full 192.168.0.3 128 34
To extract the data, include the columns
parameter in the View, and map your Python variable name to the
column name. The application stores the key and the value extracted
from the command output for that column as a key-value pair in the
dictionary for the given item.
The following View extracts the data from each column
in the show ospf neighbor
command output:
--- OspfNeighborTable: command: show ospf neighbor key: Address view: OspfNeighborView OspfNeighborView: columns: neighbor_address: Address interface: Interface neighbor_state: State neighbor_id: ID neighbor_priority: Pri activity_timer: Dead
When you retrieve and print the data in the Junos PyEZ application, the data for each neighbor includes the column keys and corresponding data.
from jnpr.junos import Device from jnpr.junos.command.ospf_neighbor import OspfNeighborTable from pprint import pprint import json with Device(host='router1.example.com') as dev: stats = OspfNeighborTable(dev) stats.get() pprint(json.loads(stats.to_json()))
user@host:~$ python3 junos-pyez-ospf-neighbors.py {'198.51.100.2': {'activity_timer': 39, 'interface': 'ge-0/0/0.0', 'neighbor_address': '198.51.100.2', 'neighbor_id': '192.168.0.2', 'neighbor_priority': 128, 'neighbor_state': 'Full'}, '198.51.100.6': {'activity_timer': 36, 'interface': 'ge-0/0/1.0', 'neighbor_address': '198.51.100.6', 'neighbor_id': '192.168.0.3', 'neighbor_priority': 128, 'neighbor_state': 'Full'}}
To filter the data to include only data from selected columns, include the
filters
parameter in the View. For more information, see filters.
Some command output includes column titles that span multiple lines, for example:
FI interrupt statistics ----------------------- -------------------------------------------------------------------------------- Stream Total RLIM Total Cell timeout Total Reorder Total cell Total number request PT/MALLOC Ignored cell timeout drops in of times counter Usemeter errors secure mode entered into saturation Drops secure mode -------------------------------------------------------------------------------- 36 0 0 1 1 0 0 128 0 0 1 49 0 0 142 0 0 1 53 0 0 -------------------------------------------------------------------------------- ...
To define a multiline column title in a View, set the column key element equal to a list of the words in each row for that title. The following View defines the columns for the previous command output:
CChipFiStatsTable: command: show mqss {{ chip_instance }} fi interrupt-stats target: fpc8 args: chip_instance: 0 key: Stream view: CChipFiStatsView CChipFiStatsView: columns: stream: Stream req_sat: - Total RLIM - request - counter - saturation cchip_fi_malloc_drops: - Total - PT/MALLOC - Usemeter - Drops cell_timeout_ignored: - Cell timeout - Ignored cchip_fi_cell_timeout: - Total Reorder - cell timeout - errors drops_in_secure: - Total cell - drops in - secure mode times_in_secure: - Total number - of times - entered into - secure mode
Eval Expression (eval)
You can use the optional eval
parameter
to add or modify key-value pairs in the final data returned by the
Table and View. The eval
parameter maps
a key name to a string containing a mathematical expression that gets
evaluated by the Python eval
function.
You can include the eval
parameter in both
Tables and Views, and eval
can define and
calculate multiple values.
When you use eval
in a Table, it
references the full data dictionary for the calculation, and the key
and calculated value are added as a single additional item to the
dictionary. When you use eval
in a View,
the expression is calculated on each iteration of the data, and the
calculated value is added to the data for that iteration. If the eval
key name matches a key defined in the View, eval
replaces the value of that key with the calculated
value. If the eval
key name does not match
a key defined in the View, eval
adds the
new key and calculated value to the data.
The eval
expression can reference
the data
dictionary returned by the View.
The expression must reference data
using
a Jinja template variable, so that Junos PyEZ can replace the variable
with the dictionary when the expression is evaluated.
The following example uses eval
in the View definition. The cpu
entry
modifies the existing value of the cpu
field
for each item in the data dictionary, and the max
entry creates a new key-value pair for each item in the data dictionary.
--- FPCThread: command: show threads target: Null key: Name view: FPCThreadView FPCThreadView: columns: pid-pr: PID PR state: State name: Name stack: Stack Use time: Time (Last/Max/Total) cpu: cpu eval: cpu: "'{{ cpu }}'[:-1]" max: "('{{ time }}'.split('/'))[1]"
Consider the following sample output for the show
threads
vty command:
PID PR State Name Stack Use Time (Last/Max/Total) cpu --- -- ------- --------------------- --------- --------------------- 1 H asleep Maintenance 680/32768 0/5/5 ms 0% 2 L running Idle 1452/32768 0/22/565623960 ms 80% 3 H asleep Timer Services 1452/32768 0/7/1966 ms 0% ...
The View’s eval
parameter
modifies each cpu
entry to omit the percent
sign (%). As a result, the data includes '0' instead of '0%'. In addition,
it adds a new key, max
, and its calculated
value for each item.
'Maintenance': {'cpu': '0', 'max': '5', 'name': 'Maintenance', 'pid-pr': '1 H', 'stack': '680/32768', 'state': 'asleep', 'time': '0/5/5 ms'}, 'Timer Services': {'cpu': '0', 'max': '7', 'name': 'Timer Services', 'pid-pr': '3 H', 'stack': '1452/32768', 'state': 'asleep', 'time': '0/7/1966 ms'}, ...
For examples that use eval
in the Table definition, see Eval Expression (eval).
exists
You can use the optional exists
parameter
in a View to indicate if a string is present in the command output. exists
is a dictionary of key-value pairs that map
a user-defined Python variable name to the string to match in the
command output. If the string is present in the output, the variable
is set to True
. Otherwise, the variable
is set to False
.
Consider the show host_loopback status-summary
vty command output.
SENT: Ukern command: show host_loopback status-summary Host Loopback Toolkit Status Summary: No detected wedges No toolkit errors
The following Table defines exists
to test if the command output includes a No detected
wedges
string or a No toolkit errors
string:
--- HostlbStatusSummaryTable: command: show host_loopback status-summary target: fpc1 view: HostlbStatusSummaryView HostlbStatusSummaryView: exists: no_detected_wedges: No detected wedges no_toolkit_errors: No toolkit errors
When you use the Table and View to test for the strings
and print the resulting values in your Junos PyEZ application, both
variables are set to True
in this case.
{'no_detected_wedges': True, 'no_toolkit_errors': True}
fields
Command output can be lengthy and complex, and you might need
different logic to parse different sections of the output. In some
cases, you cannot adequately parse the command output using a single
Table and View. To parse this type of output, you can include the
optional fields
parameter in the View. fields
is a dictionary of key-value pairs that map
a user-defined key to the name of a nested Table that selects a specific
section of the command output. Each nested Table can reference its
own View, which is used to parse the data selected by that Table.
Consider the show xmchip 0 pt stats
vty command
output, which has two different sections of data:
SENT: Ukern command: show xmchip 0 pt stats WAN PT statistics (Index 0) --------------------------- PCT entries used by all WI-1 streams : 0 PCT entries used by all WI-0 streams : 0 PCT entries used by all LI streams : 0 CPT entries used by all multicast packets : 0 CPT entries used by all WI-1 streams : 0 CPT entries used by all WI-0 streams : 0 CPT entries used by all LI streams : 0 Fabric PT statistics (Index 1) ------------------------------ PCT entries used by all FI streams : 0 PCT entries used by all WI (Unused) streams : 0 PCT entries used by all LI streams : 0 CPT entries used by all multicast packets : 0 CPT entries used by all FI streams : 0 CPT entries used by all WI (Unused) streams : 0 CPT entries used by all LI streams : 0
The following XMChipStatsView
View uses the fields
parameter to define
two additional Tables, which are used to parse the two different sections
of the command output. The _WANPTStatTable
and _FabricPTStatTable
Tables extract
the data from the WAN PT statistics
and the Fabric PT statistics
sections,
respectively. In this case, the Tables use the delimiter
parameter to extract and split the data, so they do not need to
reference a separate View.
XMChipStatsTable: command: show xmchip 0 pt stats target: fpc1 view: XMChipStatsView XMChipStatsView: fields: wan_pt_stats: _WANPTStatTable fabric_pt_stats: _FabricPTStatTable _WANPTStatTable: title: WAN PT statistics (Index 0) delimiter: ":" _FabricPTStatTable: title: Fabric PT statistics (Index 1) delimiter: ":"
When you retrieve and print the data in the Junos PyEZ
application, each key defined under fields
contains the data selected and parsed by the corresponding Table.
{'fabric_pt_stats': {'CPT entries used by all FI streams': 0, 'CPT entries used by all LI streams': 0, 'CPT entries used by all WI (Unused) streams': 0, 'CPT entries used by all multicast packets': 0, 'PCT entries used by all FI streams': 0, 'PCT entries used by all LI streams': 0, 'PCT entries used by all WI (Unused) streams': 0}, 'wan_pt_stats': {'CPT entries used by all LI streams': 0, 'CPT entries used by all WI-0 streams': 0, 'CPT entries used by all WI-1 streams': 0, 'CPT entries used by all multicast packets': 0, 'PCT entries used by all LI streams': 0, 'PCT entries used by all WI-0 streams': 0, 'PCT entries used by all WI-1 streams': 0}}
As another example, consider the show ttp statistics
vty command output:
TTP Statistics: Receive Transmit ---------- ---------- L2 Packets 4292 1093544 L3 Packets 542638 0 Drops 0 0 Netwk Fail 0 0 Queue Drops 0 0 Unknown 0 0 Coalesce 0 0 Coalesce Fail 0 0 TTP Transmit Statistics: Queue 0 Queue 1 Queue 2 Queue 3 ---------- ---------- ---------- ---------- L2 Packets 1093544 0 0 0 L3 Packets 0 0 0 0 TTP Receive Statistics: Control High Medium Low Discard ---------- ---------- ---------- ---------- ---------- L2 Packets 0 0 4292 0 0 L3 Packets 0 539172 3466 0 0 Drops 0 0 0 0 0 Queue Drops 0 0 0 0 0 Unknown 0 0 0 0 0 Coalesce 0 0 0 0 0 Coalesce Fail 0 0 0 0 0 TTP Receive Queue Sizes: Control Plane : 0 (max is 4473) High : 0 (max is 4473) Medium : 0 (max is 4473) Low : 0 (max is 2236) TTP Transmit Queue Size: 0 (max is 6710)
The FPCTTPStatsView
View uses
the fields
parameter to reference multiple
nested Tables, which extract the data in the different sections of
the output. Each Table references its own View or uses the delimiter
parameter to parse the data in that section.
--- FPCTTPStatsTable: command: show ttp statistics target: fpc2 view: FPCTTPStatsView FPCTTPStatsView: fields: TTPStatistics: _FPCTTPStatisticsTable TTPTransmitStatistics: _FPCTTPTransmitStatisticsTable TTPReceiveStatistics: _FPCTTPReceiveStatisticsTable TTPQueueSizes: _FPCTTPQueueSizesTable _FPCTTPStatisticsTable: title: TTP Statistics view: _FPCTTPStatisticsView _FPCTTPStatisticsView: columns: rcvd: Receive tras: Transmit _FPCTTPTransmitStatisticsTable: title: TTP Transmit Statistics view: _FPCTTPTransmitStatisticsView _FPCTTPTransmitStatisticsView: columns: queue0: Queue 0 queue1: Queue 1 queue2: Queue 2 queue3: Queue 3 filters: - queue2 _FPCTTPReceiveStatisticsTable: title: TTP Receive Statistics key: name key_items: - Coalesce view: _FPCTTPReceiveStatisticsView _FPCTTPReceiveStatisticsView: columns: control: Control high: High medium: Medium low: Low discard: Discard _FPCTTPQueueSizesTable: title: TTP Receive Queue Sizes delimiter: ":"
When you retrieve and print the data in the Junos PyEZ
application, each fields
key contains the
data that was extracted and parsed by the corresponding Table.
{'TTPQueueSizes': {'Control Plane': '0 (max is 4473)', 'High': '0 (max is 4473)', 'Low': '0 (max is 2236)', 'Medium': '0 (max is 4473)'}, 'TTPReceiveStatistics': {'Coalesce': {'control': 0, 'discard': 0, 'high': 0, 'low': 0, 'medium': 0, 'name': 'Coalesce'}}, 'TTPStatistics': {'Coalesce': {'name': 'Coalesce', 'rcvd': 0, 'tras': 0}, 'Coalesce Fail': {'name': 'Coalesce Fail', 'rcvd': 0, 'tras': 0}, 'Drops': {'name': 'Drops', 'rcvd': 0, 'tras': 0}, 'L2 Packets': {'name': 'L2 Packets', 'rcvd': 4292, 'tras': 1093544}, 'L3 Packets': {'name': 'L3 Packets', 'rcvd': 542638, 'tras': 0}, 'Netwk Fail': {'name': 'Netwk Fail', 'rcvd': 0, 'tras': 173}, 'Queue Drops': {'name': 'Queue Drops', 'rcvd': 0, 'tras': 0}, 'Unknown': {'name': 'Unknown', 'rcvd': 0, 'tras': 0}}, 'TTPTransmitStatistics': {'L2 Packets': {'queue2': 0}, 'L3 Packets': {'queue2': 0}}}
filters
The columns
parameter extracts data from command output that is formatted in
rows and columns. When you include the columns
parameter in a View, you
can optionally include the filters
parameter to filter which column data
is included in the final output. The filters
parameter defines a list of
one or more keys defined under columns
. The final set of data includes
only data from the selected columns. You can provide default filters in the View
definition, and you can also define or override filter values in the Junos PyEZ
application.
Consider the show ospf neighbor
command output:
Address Interface State ID Pri Dead 198.51.100.2 ge-0/0/0.0 Full 192.168.0.2 128 37 198.51.100.6 ge-0/0/1.0 Full 192.168.0.3 128 34
In the following View, the columns
parameter defines keys for all of the columns in the corresponding
command output, but the filters
parameter
only includes the data from the Address
and State
columns in the data dictionary.
--- OspfNeighborFiltersTable: command: show ospf neighbor key: Address view: OspfNeighborFiltersView OspfNeighborFiltersView: columns: neighbor_address: Address interface: Interface neighbor_state: State neighbor_id: ID neighbor_priority: Pri activity_timer: Dead filters: - neighbor_address - neighbor_state
The following Junos PyEZ code first calls get()
without any arguments, which retrieves the data
using the default list of filters defined in the View. The second
call to get()
includes the filters
argument, which overrides the filter list defined
in the View.
from jnpr.junos import Device from Tables.show_ospf_neighbor_filter import OspfNeighborFiltersTable from pprint import pprint import json with Device(host='router1.example.com') as dev: stats = OspfNeighborFiltersTable(dev) stats.get() pprint(json.loads(stats.to_json())) print('\n') stats.get(filters=['neighbor_address', 'neighbor_id', 'neighbor_state']) pprint(json.loads(stats.to_json()))
When you execute the application, the first call to get()
uses the filters defined in the View, and the
second call uses the filters defined in the call, which override those
defined in the View.
user@host:~$ python3 junos-pyez-ospf-filters.py {'198.51.100.2': {'neighbor_address': '198.51.100.2', 'neighbor_state': 'Full'}, '198.51.100.6': {'neighbor_address': '198.51.100.6', 'neighbor_state': 'Full'}} {'198.51.100.2': {'neighbor_address': '198.51.100.2', 'neighbor_id': '192.168.0.2', 'neighbor_state': 'Full'}, '198.51.100.6': {'neighbor_address': '198.51.100.6', 'neighbor_id': '192.168.0.3', 'neighbor_state': 'Full'}}
regex
You can use the optional regex
parameter
in a View to match and extract specific fields in the command output. regex
is a dictionary that maps keys to regular expressions.
If the corresponding Table does not define item: '*'
, Junos PyEZ combines the regular expressions and matches the result
against each line of output. However, if the Table defines item: '*'
to extract the data as a single text string,
Junos PyEZ instead matches each individual regular expression against
the entire text string.
The capturing group defined in the regular expression determines
the data that is extracted from the field and stored in the data dictionary.
If you define a capturing group, only the value for that group is
stored in the data. Otherwise, Junos PyEZ stores the value that matches
the full expression. For example, (d+.d+)
retrieves and stores a float value from the string search expression,
whereas (d+).d+
only stores the integer
portion of the data. If you define multiple groups, only the value
for the first group is stored.
Junos PyEZ leverages the pyparsing
module to define a number of built-in keywords that you can use
in place of a regular expression. Table 2 lists
the keyword, a brief description, and the corresponding expression,
where pp
is derived from import
pyparsing as pp
.
Keyword |
Description |
Expression |
---|---|---|
|
Word containing only hexadecimal characters |
hex_numbers = pp.OneOrMore(pp.Word(pp.nums, min=1)) & pp.OneOrMore(pp.Word('abcdefABCDEF', min=1)) |
|
Word consisting of an integer or float value |
numbers = (pp.Word(pp.nums) + pp.Optional(pp.Literal('.') + pp.Word(pp.nums))).setParseAction(lambda i: ''.join(i)) |
|
Word composed of digits and a trailing percentage sign (%) |
percentage = pp.Word(pp.nums) + pp.Literal('%') |
|
One or more words composed of printable characters (any non-whitespace characters) |
printables = pp.OneOrMore(pp.Word(pp.printables)) |
|
Word composed of alpha or alphanumeric characters |
word = pp.Word(pp.alphanums) | pp.Word(pp.alphas) |
|
One or more |
words = (pp.OneOrMore(word)).setParseAction(lambda i: ' '.join(i)) |
Consider the following show icmp statistics
command output. Each section of output is under a specific section
title, and the data consists of a number and one or more words.
ICMP Statistics: 0 requests 0 throttled 0 network unreachables 0 ttl expired 0 redirects 0 mtu exceeded 0 source route denials 0 filter prohibited 0 other unreachables 0 parameter problems 0 ttl captured 0 icmp/option handoffs 0 igmp v1 handoffs 0 tag te requests 0 tag te to RE ICMP Errors: 0 unknown unreachables 0 unsupported ICMP type 0 unprocessed redirects 0 invalid ICMP type 0 invalid protocol 0 bad input interface 0 bad route lookup 0 bad nh lookup 0 bad cf mtu 0 runts ICMP Discards: 0 multicasts 0 bad source addresses 0 bad dest addresses 0 IP fragments 0 ICMP errors 0 unknown originators ICMP Debug Messages: 0 throttled ICMP Rate Limit Settings: 500 pps per iff 1000 pps total
To parse the previous output, the main View defines fields
, which references nested Tables and Views that
parse each section of output. The nested Views define the regex
parameter to match against the data extracted
by the corresponding Table.
--- ICMPStatsTable: command: show icmp statistics target: fpc1 view: ICMPStatsView ICMPStatsView: fields: discards: _ICMPDiscardsTable errors: _ICMPErrorsTable rate: _ICMPRateTable _ICMPDiscardsTable: title: ICMP Discards key: name view: _ICMPDiscardsView _ICMPDiscardsView: regex: value: \d+ name: '(\w+(\s\w+)*)' _ICMPErrorsTable: title: ICMP Errors key: name view: _ICMPErrorsView _ICMPErrorsView: regex: error: numbers name: words _ICMPRateTable: title: ICMP Rate Limit Settings key: name view: _ICMPRateView _ICMPRateView: regex: rate: numbers name: words
For example, the _ICMPDiscardsTable
Table selects the data under the ICMP Discards
section in the command output. The _ICMPDiscardsView
View defines two keys, value
and name
, which map to regular expressions. value
matches one or more digits, and name
matches one or more words. Because the Table does
not define item: '*'
, the regular expressions
are combined and matched against each line of data in that section.
_ICMPDiscardsTable: title: ICMP Discards key: name view: _ICMPDiscardsView _ICMPDiscardsView: regex: value: \d+ name: '(\w+(\s\w+)*)'
The _ICMPErrorsTable
Table
selects the data under the ICMP Errors
section in the command output. The _ICMPErrorsView
View defines the error
and name
keys and uses the built-in keywords numbers
and words
in place
of explicitly defining regular expressions.
_ICMPErrorsTable: title: ICMP Errors key: name view: _ICMPErrorsView _ICMPErrorsView: regex: error: numbers name: words
If the Table defines item: '*'
, the
extracted data is considered to be one text string. In this case,
each regular expression in the corresponding view matches against
the entire string.
Consider the show ithrottle id 0
command
output.
SENT: Ukern command: show ithrottle id 0 ID Usage % Cfg State Oper State Name -- ------- --------- ---------- -------- 0 50.0 1 1 TOE ithrottle Throttle Times: In hptime ticks In ms --------------- ------ Timer Interval 333333 5.000 Allowed time 166666 2.500 Allowed excess 8333 0.125 Start time 488655082 n/a Run time this interval 0 0.000 Deficit 0 0.000 Run time max 17712 0.266 Run time total 144154525761 2162317 Min Usage Perc: 25.0 Max Usage Perc: 50.0 AdjustUsageEnable: 1 Throttle Stats: Starts : 65708652 Stops : 65708652 Checks : 124149442 Enables : 0 Disables : 0 AdjUp : 6 AdjDown : 4
The following Table uses item: '*'
to extract the data as a single string. The View’s regex
parameter defines three regular expressions.
Each regex pattern is matched against the entire string. Because the
regular expressions define capturing groups, Junos PyEZ only stores
the data that matches the group.
IthrottleIDTable: command: show ithrottle id {{ id }} args: id: 0 item: '*' target: fpc1 view: IthrottleIDView IthrottleIDView: regex: min_usage: 'Min Usage Perc: (\d+\.\d+)' max_usage: 'Max Usage Perc: (\d+\.\d+)' usg_enable: 'AdjustUsageEnable: (\d)' fields: throttle_stats: _ThrottleStatsTable _ThrottleStatsTable: title: Throttle Stats delimiter: ":"
When you retrieve and print the data in the Junos PyEZ application,
the data includes the three regex
items,
which contain the value matched by the capturing group for that expression.
{'max_usage': 50.0, 'min_usage': 25.0, 'throttle_stats': {'AdjDown': 4, 'AdjUp': 6, 'Checks': 124149442, 'Disables': 0, 'Enables': 0, 'Starts': 65708652, 'Stops': 65708652}, 'usg_enable': 1}