Select Git revision
juniper.py 10.55 KiB
"""
# rpc: 'get-interface-information' (child elem: <extensive />)
# cli: 'show interfaces extensive'
"""
import contextlib
import logging
import time
from lxml import etree
import ncclient.manager
from ncclient.devices.junos import JunosDeviceHandler
from ncclient.xml_ import NCElement
logger = logging.getLogger(__name__)
def _remove_ns(rpc_response):
junos_dev_handler = JunosDeviceHandler(
device_params={'name': 'junos', 'local': False})
return NCElement(rpc_response, junos_dev_handler.transform_reply()) \
.remove_namespaces(rpc_response.xml)
def nc_connection(hostname, host_verify=True):
conn = ncclient.manager.connect(
host=hostname,
port=830,
ssh_config='~/.ssh/config.d/routers-jump',
hostkey_verify=host_verify
# device_params={'name': 'junos'}
)
conn.async_mode = True
return conn
def rpc(router, command):
obj = router.rpc(command)
logger.info(f'sent rpc command: {etree.tostring(command)}')
# just wait for the result
while not obj.event.is_set():
logging.info('waiting for rpc reply ...')
time.sleep(.3)
return obj.reply
def netconf_rpc(router_name, command, host_verify=True):
with contextlib.closing(nc_connection(hostname=router_name, host_verify=host_verify)) as router:
reply = rpc(router, command)
return _remove_ns(reply)
def get_interface_info_ncrpc(router_name, host_verify=True):
request = etree.Element('get-interface-information')
request.append(etree.Element('extensive'))
return netconf_rpc(router_name=router_name, command=request, host_verify=host_verify)
def _elem_int(node, name):
_subelems = node.xpath(f'./{name}/text()')
if not _subelems:
assert False, f"can't find {name} in {node} [{_subelems}]"
return int(_subelems[0])
def _counter_dict(node, paths_and_keys):
"""
:param node: a node element
:param paths_and_keys: list of dicts {'path': str, 'key': str, 'required': boolean (optional)}
:return:
"""
result = {}
for pk in paths_and_keys:
_elems = node.xpath(pk['path'] + '/text()')
if _elems:
if len(_elems) > 1:
logger.warning(f'found more than one {pk["path"]} in {node}')
result[pk['key']] = int(_elems[0])
else:
if pk.get('required', False):
assert False, f'path {pk["path"]} not found in {node}'
return result
def _ifc_name(ifc_node):
return ifc_node.xpath('./name/text()')[0].strip()
def physical_interface_counters(ifc_doc):
def _is_logical(ifc_node):
name = _ifc_name(ifc_node)
log_prefixes = ['ae', 'lt', 'irb']
return any(map(name.startswith, log_prefixes))
def _brian_counters(ifc_node):
ingress_fields = [
{'path': './traffic-statistics/input-bytes', 'key': 'bytes', 'required': True},
{'path': './traffic-statistics/input-packets', 'key': 'packets', 'required': True},
]
egress_fields = [
{'path': './traffic-statistics/output-bytes', 'key': 'bytes', 'required': True},
{'path': './traffic-statistics/output-packets', 'key': 'packets', 'required': True},
]
baseline_stats = {
'ingress': _counter_dict(ifc_node, ingress_fields),
'egress': _counter_dict(ifc_node, egress_fields)
}
v6_ingress_fields = [
{'path': './traffic-statistics/ipv6-transit-statistics/input-bytes', 'key': 'bytes'},
{'path': './traffic-statistics/ipv6-transit-statistics/input-packets', 'key': 'packets'},
]
v6_egress_fields = [
{'path': './traffic-statistics/ipv6-transit-statistics/output-bytes', 'key': 'bytes'},
{'path': './traffic-statistics/ipv6-transit-statistics/output-packets', 'key': 'packets'},
]
v6 = _counter_dict(ifc_node, v6_ingress_fields)
if v6:
baseline_stats['ingress']['v6'] = v6
v6 = _counter_dict(ifc_node, v6_egress_fields)
if v6:
baseline_stats['egress']['v6'] = v6
ingress_error_fields = [
{'path': './input-error-list/input-errors', 'key': 'errors'},
{'path': './input-error-list/input-drops', 'key': 'drops'},
{'path': './input-error-list/input-resource-errors', 'key': 'resource'},
{'path': './input-error-list/input-discards', 'key': 'discards'},
{'path': './input-error-list/input-fifo-errors', 'key': 'fifo'},
]
egress_error_fields = [
{'path': './output-error-list/output-errors', 'key': 'errors'},
{'path': './output-error-list/output-drops', 'key': 'drops'},
{'path': './output-error-list/output-resource-errors', 'key': 'resource'},
{'path': './output-error-list/output-fifo-errors', 'key': 'fifo'},
{'path': './output-error-list/output-collisions', 'key': 'collisions'},
]
errors = _counter_dict(ifc_node, ingress_error_fields)
if errors:
baseline_stats['ingress']['errors'] = errors
errors = _counter_dict(ifc_node, egress_error_fields)
if errors:
baseline_stats['egress']['errors'] = errors
return baseline_stats
def _l2_counters(ifc_node):
ingress_fields = [
{'path': '././ethernet-mac-statistics/input-broadcasts', 'key': 'broadcast', 'required': True},
{'path': '././ethernet-mac-statistics/input-multicasts', 'key': 'multicast', 'required': True},
{'path': '././ethernet-mac-statistics/input-bytes', 'key': 'bytes'},
{'path': '././ethernet-mac-statistics/input-packets', 'key': 'packets'},
{'path': '././ethernet-mac-statistics/input-unicasts', 'key': 'unicast'},
]
egress_fields = [
{'path': '././ethernet-mac-statistics/output-broadcasts', 'key': 'broadcast', 'required': True},
{'path': '././ethernet-mac-statistics/output-multicasts', 'key': 'multicast', 'required': True},
{'path': '././ethernet-mac-statistics/output-bytes', 'key': 'bytes'},
{'path': '././ethernet-mac-statistics/output-packets', 'key': 'packets'},
{'path': '././ethernet-mac-statistics/output-unicasts', 'key': 'unicast'},
]
baseline_stats = {
'ingress': _counter_dict(ifc_node, ingress_fields),
'egress': _counter_dict(ifc_node, egress_fields)
}
pcs_stats_fields = [
{'path': '././ethernet-pcs-statistics/bit-error-seconds', 'key': 'bit-error-seconds'},
{'path': '././ethernet-pcs-statistics/errored-blocks-seconds', 'key': 'errored-blocks-seconds'},
]
pcs = _counter_dict(ifc_node, pcs_stats_fields)
if pcs:
baseline_stats['pcs'] = pcs
ingress_error_fields = [
# this one isn't available on qfx's
# 'total': _elem_int(mac_stats, 'input-total-errors'),
{'path': '././ethernet-mac-statistics/input-crc-errors', 'key': 'crc'},
{'path': '././ethernet-mac-statistics/input-fifo-errors', 'key': 'fifo'},
]
egress_error_fields = [
# 'total': _elem_int(mac_stats, 'input-total-errors'),
{'path': '././ethernet-mac-statistics/output-crc-errors', 'key': 'crc'},
{'path': '././ethernet-mac-statistics/output-fifo-errors', 'key': 'fifo'},
]
errors = _counter_dict(ifc_node, ingress_error_fields)
if errors:
baseline_stats['ingress']['errors'] = errors
errors = _counter_dict(ifc_node, egress_error_fields)
if errors:
baseline_stats['egress']['errors'] = errors
return baseline_stats
for phy in ifc_doc.xpath(
'//interface-information/physical-interface['
'(name[(starts-with(normalize-space(), "xe-"))]'
' or name[(starts-with(normalize-space(), "ge-"))]'
' or name[(starts-with(normalize-space(), "et-"))]'
' or name[(starts-with(normalize-space(), "ae"))]'
' or name[(starts-with(normalize-space(), "irb"))])'
' and normalize-space(admin-status)="up"'
' and normalize-space(oper-status)="up"'
']'):
counters = {
'name': _ifc_name(phy),
'brian': _brian_counters(phy)
}
if phy.xpath('./ethernet-mac-statistics'):
counters['l2'] = _l2_counters(phy)
yield counters
def logical_interface_counters(ifc_doc):
def _brian_counters(ifc_node):
traffic = 'traffic-statistics' \
if ifc_node.xpath('./traffic-statistics') \
else 'lag-traffic-statistics/lag-bundle'
ingress_fields = [
{'path': f'./{traffic}/input-bytes', 'key': 'bytes', 'required': True},
{'path': f'./{traffic}/input-packets', 'key': 'packets', 'required': True},
]
egress_fields = [
{'path': f'./{traffic}/output-bytes', 'key': 'bytes', 'required': True},
{'path': f'./{traffic}/output-packets', 'key': 'packets', 'required': True},
]
baseline_stats = {
'ingress': _counter_dict(ifc_node, ingress_fields),
'egress': _counter_dict(ifc_node, egress_fields)
}
v6_ingress_fields = [
{'path': './traffic-statistics/ipv6-transit-statistics/input-bytes', 'key': 'bytes'},
{'path': './traffic-statistics/ipv6-transit-statistics/input-packets', 'key': 'packets'},
]
v6_egress_fields = [
{'path': './traffic-statistics/ipv6-transit-statistics/output-bytes', 'key': 'bytes'},
{'path': './traffic-statistics/ipv6-transit-statistics/output-packets', 'key': 'packets'},
]
v6 = _counter_dict(ifc_node, v6_ingress_fields)
if v6:
baseline_stats['ingress']['v6'] = v6
v6 = _counter_dict(ifc_node, v6_egress_fields)
if v6:
baseline_stats['egress']['v6'] = v6
return baseline_stats
for logical in ifc_doc.xpath(
'//interface-information/physical-interface['
'(name[(starts-with(normalize-space(), "xe-"))]'
' or name[(starts-with(normalize-space(), "ge-"))]'
' or name[(starts-with(normalize-space(), "et-"))]'
' or name[(starts-with(normalize-space(), "ae"))]'
' or name[(starts-with(normalize-space(), "irb"))])'
' and normalize-space(admin-status)="up"'
' and normalize-space(oper-status)="up"'
']/logical-interface'):
yield {
'name': _ifc_name(logical),
'brian': _brian_counters(logical)
}