diff --git a/brian_polling_manager/interface_stats/cli.py b/brian_polling_manager/interface_stats/cli.py index 3b56fdd88432815e4e7a4e79ef841979481f59b0..8783169ad64bc46b088f21fb91e2fb515cd36d0e 100644 --- a/brian_polling_manager/interface_stats/cli.py +++ b/brian_polling_manager/interface_stats/cli.py @@ -10,11 +10,10 @@ from typing import Iterable, List, Sequence import click import jsonschema from brian_polling_manager.influx import influx_client -from brian_polling_manager.interface_stats import vendors +from brian_polling_manager.interface_stats import vendors, config from brian_polling_manager.interface_stats.vendors import Vendor, juniper, nokia from brian_polling_manager.inventory import load_interfaces - -from . import config +from lxml import etree logger = logging.getLogger(__file__) @@ -104,14 +103,10 @@ def write_points_to_stdout(points, influx_params, stream=sys.stdout, **_): def get_netconf(router_name, vendor: Vendor, ssh_params: dict, **kwargs): - if vendor == Vendor.JUNIPER: - return juniper.get_netconf_interface_info( - router_name, ssh_params=ssh_params, **kwargs - ) - else: - return nokia.get_netconf_interface_info( - router_name, ssh_params=ssh_params, **kwargs - ) + module = juniper if vendor == Vendor.JUNIPER else nokia + return module.get_netconf_interface_info( + router_name, ssh_params=ssh_params, **kwargs + ) def validate_router_hosts( @@ -123,13 +118,14 @@ def validate_router_hosts( if inprov_hosts is None: return True + if vendor == Vendor.NOKIA: + logger.info(f"Skipping validation for Nokia host(s) {' '.join(hostnames)}") + return True + logger.info( f"Validating hosts {' '.join(hostnames)} using providers {inprov_hosts}" ) - if vendor == Vendor.NOKIA: - all_fqdns = [] - else: - all_fqdns = {ifc["router"] for ifc in load_interfaces_(inprov_hosts)} + all_fqdns = {ifc["router"] for ifc in load_interfaces_(inprov_hosts)} extra_fqdns = set(hostnames) - set(all_fqdns) if extra_fqdns: @@ -141,34 +137,24 @@ def validate_router_hosts( def process_router( - router_fqdn: str, - vendor: Vendor, - app_config_params: dict, - output: OutputMethod, + router_fqdn: str, vendor: Vendor, app_config_params: dict, output: OutputMethod ): - if vendor == Vendor.JUNIPER: - return process_juniper_router(router_fqdn, app_config_params, output) - else: - return process_nokia_router(router_fqdn, app_config_params, output) + logger.info(f"Processing {vendor.value.capitalize()} router {router_fqdn}") -def process_juniper_router( - router_fqdn: str, app_config_params: dict, output: OutputMethod -): - logger.info(f"Processing Juniper router {router_fqdn}") - - ssh_params = app_config_params[Vendor.JUNIPER.value] - document = get_netconf(router_fqdn, vendor=Vendor.JUNIPER, ssh_params=ssh_params) + ssh_params = app_config_params[vendor.value] + document = get_netconf(router_fqdn, vendor=vendor, ssh_params=ssh_params) timestamp = datetime.now() influx_params = app_config_params["influx"]["brian-counters"] logger.info("Processing Brian points...") points = list( - _juniper_brian_points( + _brian_points( router_fqdn=router_fqdn, netconf_doc=document, timestamp=timestamp, measurement_name=influx_params["measurement"], + vendor=vendor, ) ) _log_interface_points_sorted(points) @@ -177,11 +163,12 @@ def process_juniper_router( influx_params = app_config_params["influx"]["error-counters"] logger.info("Processing Error points...") points = list( - _juniper_error_points( + _error_points( router_fqdn=router_fqdn, netconf_doc=document, timestamp=timestamp, measurement_name=influx_params["measurement"], + vendor=vendor, ) ) @@ -189,6 +176,34 @@ def process_juniper_router( write_points(points, influx_params=influx_params, output=output) +def _brian_points( + router_fqdn: str, + netconf_doc: etree.Element, + timestamp: datetime, + measurement_name: str, + vendor: Vendor, +): + module = juniper if vendor == Vendor.JUNIPER else nokia + interfaces = module.interface_counters(netconf_doc) + yield from vendors.brian_points( + router_fqdn, interfaces, timestamp, measurement_name + ) + + +def _error_points( + router_fqdn: str, + netconf_doc: etree.Element, + timestamp: datetime, + measurement_name: str, + vendor: Vendor, +): + module = juniper if vendor == Vendor.JUNIPER else nokia + interfaces = module.interface_counters(netconf_doc) + yield from vendors.error_points( + router_fqdn, interfaces, timestamp, measurement_name + ) + + def _log_interface_points_sorted(points: Sequence[dict], point_kind=""): N_COLUMNS = 5 num_points = len(points) @@ -201,31 +216,12 @@ def _log_interface_points_sorted(points: Sequence[dict], point_kind=""): interfaces = sorted(p["tags"]["interface_name"] for p in points) longest_ifc = max(len(i) for i in interfaces) - for n in range(len(interfaces) // N_COLUMNS + 1): + ifc_count = len(interfaces) + for n in range(ifc_count // N_COLUMNS + (ifc_count % N_COLUMNS > 0)): ifc_slice = interfaces[n * N_COLUMNS : (n + 1) * N_COLUMNS] logger.info(" ".join(i.ljust(longest_ifc) for i in ifc_slice)) -def _juniper_brian_points(router_fqdn, netconf_doc, timestamp, measurement_name): - interfaces = juniper.interface_counters(netconf_doc) - yield from vendors.brian_points( - router_fqdn, interfaces, timestamp, measurement_name - ) - - -def _juniper_error_points(router_fqdn, netconf_doc, timestamp, measurement_name): - interfaces = juniper.interface_counters(netconf_doc) - yield from vendors.error_points( - router_fqdn, interfaces, timestamp, measurement_name - ) - - -def process_nokia_router( - router_fqdn: str, app_config_params: dict, output: OutputMethod -): - logger.warning(f"skipping Nokia router {router_fqdn}") - - def main( app_config_params: dict, router_fqdns: List[str], @@ -250,9 +246,9 @@ def main( app_config_params=app_config_params, output=output, ) - except Exception as e: + except Exception: logger.exception( - f"Error while processing {vendor_str} {router}", exc_info=e + f"Error while processing {vendor_str.capitalize()} router {router}" ) error_count += 1 diff --git a/brian_polling_manager/interface_stats/vendors/__init__.py b/brian_polling_manager/interface_stats/vendors/__init__.py index 1b7f72da24c0a5f9f4f4aef66769ab834dd94652..801e635060f1bb795eafaccc73ec55203c058c88 100644 --- a/brian_polling_manager/interface_stats/vendors/__init__.py +++ b/brian_polling_manager/interface_stats/vendors/__init__.py @@ -5,8 +5,7 @@ from .common import ( error_points, BRIAN_POINT_FIELDS_SCHEMA, ERROR_POINT_FIELDS_SCHEMA, - PHYSICAL_INTERFACE_COUNTER_SCHEMA, - LOGICAL_INTERFACE_COUNTER_SCHEMA, + INTERFACE_COUNTER_SCHEMA, ) @@ -18,8 +17,7 @@ class Vendor(enum.Enum): __all__ = [ "BRIAN_POINT_FIELDS_SCHEMA", "ERROR_POINT_FIELDS_SCHEMA", - "PHYSICAL_INTERFACE_COUNTER_SCHEMA", - "LOGICAL_INTERFACE_COUNTER_SCHEMA", + "INTERFACE_COUNTER_SCHEMA", "brian_points", "error_points", "Vendor", diff --git a/brian_polling_manager/interface_stats/vendors/common.py b/brian_polling_manager/interface_stats/vendors/common.py index 8f85efc37090ece3968e8b03c064477e45351f1f..91e08dcd3ba0489aaf15ab6677a8b02256b27c53 100644 --- a/brian_polling_manager/interface_stats/vendors/common.py +++ b/brian_polling_manager/interface_stats/vendors/common.py @@ -1,95 +1,14 @@ +import contextlib import logging from functools import partial +import ncclient.manager +from lxml import etree logger = logging.getLogger(__name__) -PHYSICAL_INTERFACE_COUNTER_SCHEMA = { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "definitions": { - "brian-counters": { - "type": "object", - "properties": { - "ingressOctets": {"type": "integer"}, - "ingressPackets": {"type": "integer"}, - "egressOctets": {"type": "integer"}, - "egressPackets": {"type": "integer"}, - "ingressOctetsv6": {"type": "integer"}, - "ingressPacketsv6": {"type": "integer"}, - "egressOctetsv6": {"type": "integer"}, - "egressPacketsv6": {"type": "integer"}, - "ingressErrors": {"type": "integer"}, - "egressErrors": {"type": "integer"}, - "ingressDiscards": {"type": "integer"}, - }, - "required": [ - "ingressOctets", - "ingressPackets", - "egressOctets", - "egressPackets", - ], - "additionalProperties": False, - }, - "error-counters": { - "type": "object", - "properties": { - "bit_error_seconds": {"type": "integer"}, - "errored_blocks_seconds": {"type": "integer"}, - "input_crc_errors": {"type": "integer"}, - "input_fifo_errors": {"type": "integer"}, - "input_total_errors": {"type": "integer"}, - "input_discards": {"type": "integer"}, - "input_drops": {"type": "integer"}, - "output_drops": {"type": "integer"}, - "output_crc_errors": {"type": "integer"}, - "output_fifo_errors": {"type": "integer"}, - "output_total_errors": {"type": "integer"}, - "input_framing_errors": {"type": "integer"}, - "output_resource_errors": {"type": "integer"}, - "input_resource_errors": {"type": "integer"}, - "output_collisions": {"type": "integer"}, - }, - "additionalProperties": False, - }, - }, - "type": "object", - "properties": { - "name": {"type": "string"}, - "brian": {"$ref": "#/definitions/brian-counters"}, - "errors": {"$ref": "#/definitions/error-counters"}, - }, - "required": ["name", "brian"], - "additionalProperties": False, -} +DEFAULT_NETCONF_PORT = 830 + -LOGICAL_INTERFACE_COUNTER_SCHEMA = { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "type": "object", - "properties": { - "name": {"type": "string"}, - "brian": { - "type": "object", - "properties": { - "ingressOctets": {"type": "integer"}, - "ingressPackets": {"type": "integer"}, - "egressOctets": {"type": "integer"}, - "egressPackets": {"type": "integer"}, - "ingressOctetsv6": {"type": "integer"}, - "ingressPacketsv6": {"type": "integer"}, - "egressOctetsv6": {"type": "integer"}, - "egressPacketsv6": {"type": "integer"}, - }, - "required": [ - "ingressOctets", - "ingressPackets", - "egressOctets", - "egressPackets", - ], - "additionalProperties": False, - }, - }, - "required": ["name", "brian"], - "additionalProperties": False, -} BRIAN_POINT_FIELDS_SCHEMA = { "$schema": "https://json-schema.org/draft/2020-12/schema", "type": "object", @@ -115,61 +34,6 @@ BRIAN_POINT_FIELDS_SCHEMA = { "additionalProperties": False, } -# TODO (POL1-798): unify the counter schemas -NOKIA_INTERFACE_COUNTER_SCHEMA = { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "definitions": { - "brian-counters": { - "type": "object", - "properties": { - "ingressOctets": {"type": "integer"}, - "ingressPackets": {"type": "integer"}, - "egressOctets": {"type": "integer"}, - "egressPackets": {"type": "integer"}, - "ingressErrors": {"type": "integer"}, - "egressErrors": {"type": "integer"}, - "ingressDiscards": {"type": "integer"}, - "egressDiscards": {"type": "integer"}, - }, - "required": [ - "ingressOctets", - "ingressPackets", - "egressOctets", - "egressPackets", - "ingressErrors", - "egressErrors", - "ingressDiscards", - "egressDiscards", - ], - "additionalProperties": False, - }, - "error-counters": { - "type": "object", - "properties": { - "input_discards": {"type": "integer"}, - "output_discards": {"type": "integer"}, - "output_total_errors": {"type": "integer"}, - }, - "required": [ - "input_discards", - "output_discards", - "output_total_errors", - ], - "additionalProperties": False, - - "additionalProperties": False, - }, - }, - "type": "object", - "properties": { - "name": {"type": "string"}, - "brian": {"$ref": "#/definitions/brian-counters"}, - "errors": {"$ref": "#/definitions/error-counters"}, - }, - "required": ["name", "brian"], - "additionalProperties": False, -} - ERROR_POINT_FIELDS_SCHEMA = { "$schema": "https://json-schema.org/draft/2020-12/schema", @@ -178,19 +42,36 @@ ERROR_POINT_FIELDS_SCHEMA = { "bit_error_seconds": {"type": "integer"}, "errored_blocks_seconds": {"type": "integer"}, "input_crc_errors": {"type": "integer"}, - "input_fifo_errors": {"type": "integer"}, - "input_total_errors": {"type": "integer"}, "input_discards": {"type": "integer"}, "input_drops": {"type": "integer"}, - "output_drops": {"type": "integer"}, - "output_crc_errors": {"type": "integer"}, - "output_fifo_errors": {"type": "integer"}, - "output_total_errors": {"type": "integer"}, + "input_fifo_errors": {"type": "integer"}, "input_framing_errors": {"type": "integer"}, - "output_resource_errors": {"type": "integer"}, "input_resource_errors": {"type": "integer"}, + "input_total_errors": {"type": "integer"}, "output_collisions": {"type": "integer"}, + "output_crc_errors": {"type": "integer"}, + "output_discards": {"type": "integer"}, + "output_drops": {"type": "integer"}, + "output_fifo_errors": {"type": "integer"}, + "output_resource_errors": {"type": "integer"}, + "output_total_errors": {"type": "integer"}, + }, + "additionalProperties": False, +} + +INTERFACE_COUNTER_SCHEMA = { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "definitions": { + "brian-counters": BRIAN_POINT_FIELDS_SCHEMA, + "error-counters": ERROR_POINT_FIELDS_SCHEMA, + }, + "type": "object", + "properties": { + "name": {"type": "string"}, + "brian": {"$ref": "#/definitions/brian-counters"}, + "errors": {"$ref": "#/definitions/error-counters"}, }, + "required": ["name", "brian"], "additionalProperties": False, } @@ -199,8 +80,7 @@ def _counters_to_point( interface, router_fqdn, timestamp, measurement, counters_name, required=False ): """ - :param interface: either a PHYSICAL_INTERFACE_COUNTER_SCHEMA or - LOGICAL_INTERFACE_COUNTER_SCHEMA object + :param interface: an instance of INTERFACE_COUNTER_SCHEMA :param router_fqdn: ``hostname`` tag to be set in the generated points :param measurement: the measurement where the point will be written :param counter_name: the desired set of counters (either ``brian`` or ``errors``) @@ -225,8 +105,7 @@ def brian_points(router_fqdn, interfaces, timestamp, measurement_name): returns an interable of points that can be written to influxdb :param router_fqdn: 'hostname' tag to be set in the generated points - :param interfaces: an iterable of PHYSICAL_INTERFACE_COUNTER_SCHEMA or - LOGICAL_INTERFACE_COUNTER_SCHEMA + :param interfaces: an iterable of INTERFACE_COUNTER_SCHEMA :param timestamp: timestamp to put in the generated points :param measurement_name: measurement name to put in the generated points :return: @@ -247,8 +126,7 @@ def error_points(router_fqdn, interfaces, timestamp, measurement_name): returns an interable of points that can be written to influxdb :param router_fqdn: 'hostname' tag to be set in the generated points - :param interfaces: an iterable of PHYSICAL_INTERFACE_COUNTER_SCHEMA or - LOGICAL_INTERFACE_COUNTER_SCHEMA + :param interfaces: an iterable of INTERFACE_COUNTER_SCHEMA :param timestamp: timestamp to put in the generated points :param measurement_name: measurement name to put in the generated points :return: @@ -264,20 +142,6 @@ def error_points(router_fqdn, interfaces, timestamp, measurement_name): return filter(lambda r: r is not None, map(point_creator, interfaces)) -def _read_counter(node, path, transform, required=False): - if isinstance(path, str): - path = [path] - for p in path: - _elems = node.xpath(p + "/text()") - if _elems: - if len(_elems) > 1: - logger.warning(f"found more than one {p} in {node}") - return transform(_elems[0]) - - if required: - logger.error(f"required path {p} not found in {node}") - - def parse_interface_xml(node, struct: dict, defaults=None): """ struct should be one of: @@ -304,3 +168,34 @@ def parse_interface_xml(node, struct: dict, defaults=None): continue result[key] = parsed return result or None + + +def _read_counter(node, path, transform, required=False): + if isinstance(path, str): + path = [path] + for p in path: + _elems = node.xpath(p + "/text()") + if _elems: + if len(_elems) > 1: + logger.warning(f"found more than one {p} in {node}") + return transform(_elems[0]) + + if required: + logger.error(f"required path {p} not found in {node}") + + +@contextlib.contextmanager +def netconf_connect(hostname: str, ssh_params: dict, **kwargs): + params = {"host": hostname, **kwargs, **ssh_params} + if "port" not in params: + params["port"] = DEFAULT_NETCONF_PORT + conn = ncclient.manager.connect(**params) + yield conn + + +def remove_xml_namespaces(xml: str): + etree_doc = etree.fromstring(xml) + for elem in etree_doc.iter(): + elem.tag = etree.QName(elem).localname + etree.cleanup_namespaces(etree_doc) + return etree_doc diff --git a/brian_polling_manager/interface_stats/vendors/juniper.py b/brian_polling_manager/interface_stats/vendors/juniper.py index 73b7a9e55cf571ff05923b370193875e8a88c219..2dd61410dbd5e1e81b9e0f35805608c9ad855e26 100644 --- a/brian_polling_manager/interface_stats/vendors/juniper.py +++ b/brian_polling_manager/interface_stats/vendors/juniper.py @@ -1,17 +1,18 @@ import logging import pathlib +from brian_polling_manager.interface_stats.vendors.common import ( + netconf_connect, + parse_interface_xml, + remove_xml_namespaces, +) from lxml import etree -import ncclient.manager -from ncclient.devices.junos import JunosDeviceHandler -from ncclient.xml_ import NCElement - -from brian_polling_manager.interface_stats.vendors.common import parse_interface_xml logger = logging.getLogger(__name__) -DEFAULT_NETCONF_PORT = 830 -DEFAULT_NETCONF_CONNECT_TIMEOUT = 5 # seconds +NCCLIENT_PARAMS = { + "timeout": 5, +} PHYSICAL_INTERFACE_COUNTERS = { "__defaults__": {"transform": int, "required": False}, @@ -42,7 +43,7 @@ PHYSICAL_INTERFACE_COUNTERS = { "ingressErrors": {"path": "./input-error-list/input-errors"}, "egressErrors": {"path": "./output-error-list/output-errors"}, "ingressDiscards": {"path": "./input-error-list/input-discards"}, - # These error counters are unused, but perhaps in the future we want to output + # These counters are unused, but perhaps in the future we want to output # them as well # "l2_input_broadcast": {"path": "./ethernet-mac-statistics/input-broadcasts"}, # "l2_input_multicast": {"path": "./ethernet-mac-statistics/input-multicasts"}, @@ -65,6 +66,7 @@ PHYSICAL_INTERFACE_COUNTERS = { "output_resource_errors": { "path": "./output-error-list/output-resource-errors" }, + "output_discards": {"path": "./output-error-list/output-discards"}, "output_fifo_errors": {"path": "./output-error-list/output-fifo-errors"}, "output_collisions": {"path": "./output-error-list/output-collisions"}, "input_crc_errors": {"path": "./ethernet-mac-statistics/input-crc-errors"}, @@ -171,67 +173,21 @@ def _logical_interface_counters(ifc_doc): def interface_counters(ifc_doc): yield from _physical_interface_counters(ifc_doc) - - # [2024-03-21] We currently have no definition for error points on logical - # interfaces, so returning these interfaces when writing error points - # is essentially a no-op. Perhaps in the future we will - # get a definition for errors on logical interfaces yield from _logical_interface_counters(ifc_doc) -def get_netconf_interface_info(router_name, ssh_params, timeout=10): +def get_netconf_interface_info(router_name, ssh_params): request = etree.Element("get-interface-information") request.append(etree.Element("extensive")) - response = _rpc( - router_name, - ssh_params=ssh_params, - command=request, - manager_params={"timeout": timeout}, - ) - return _remove_ns(response) - - -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 _rpc( - router_name: str, - ssh_params: dict, - manager_params: dict, - command: etree.Element, - port=DEFAULT_NETCONF_PORT, - timeout=DEFAULT_NETCONF_CONNECT_TIMEOUT, -): - """ - :param router_name: router fqdn - :param ssh_params: 'juniper' element from app config - :param command: an lxml etree element - :param port: the port to connect to - :param timeout: a timeout in seconds - :return: the router netconf rpc reponse as an lxml.Element - """ - params = { - "host": router_name, - "port": port, - "timeout": timeout, - **ssh_params, - **manager_params, - } - - with ncclient.manager.connect(**params) as router: - logger.debug(f"sending rpc command: {etree.tostring(command)}") - return router.rpc(command) - - -def get_netconf_interface_info_from_source_dir( - router_name: str, source_dir: str, *_, **__ -): + + with netconf_connect( + hostname=router_name, ssh_params=ssh_params, **NCCLIENT_PARAMS + ) as conn: + response = conn.rpc(request) + return remove_xml_namespaces(response.xml) + + +def get_netconf_interface_info_from_source_dir(router_name: str, source_dir: str): file = pathlib.Path(source_dir) / f"{router_name}-interface-info.xml" if not file.is_file(): raise ValueError(f"file {file} is not a valid file") diff --git a/brian_polling_manager/interface_stats/vendors/nokia.py b/brian_polling_manager/interface_stats/vendors/nokia.py index b4834e02163df75fa6b2b1306ff36c4267ac0e78..a4179adc006008644c33254df4b5c49e243baf0e 100644 --- a/brian_polling_manager/interface_stats/vendors/nokia.py +++ b/brian_polling_manager/interface_stats/vendors/nokia.py @@ -1,151 +1,112 @@ -""" - -""" -import contextlib import logging import pathlib -import time -import typing - +from brian_polling_manager.interface_stats.vendors.common import ( + netconf_connect, + parse_interface_xml, + remove_xml_namespaces, +) from lxml import etree -import ncclient.manager - -from brian_polling_manager.interface_stats.vendors.common import parse_interface_xml - -if typing.TYPE_CHECKING: - from ncclient.lxml_ import NCElement logger = logging.getLogger(__name__) -STATE_NS = 'urn:nokia.com:sros:ns:yang:sr:state' - -# TODO: decide if these are really global -DEFAULT_NETCONF_PORT = 830 -# cf. POL1-799 -DEFAULT_NETCONF_CONNECT_TIMEOUT = 60 # seconds -# DEFAULT_NETCONF_CONNECT_TIMEOUT = 5 # seconds -ENDPOINT_TYPES = {'port', 'lag'} - -@contextlib.contextmanager -def _connection(hostname: str, ssh_params: dict, port: int = DEFAULT_NETCONF_PORT): - params = { - 'host': hostname, - 'port': port, - 'device_params': {'name': 'sros'}, - 'nc_params': {'capabilities': ['urn:nokia.com:nc:pysros:pc']}, - 'timeout': DEFAULT_NETCONF_CONNECT_TIMEOUT - } - params.update(ssh_params) - - conn = ncclient.manager.connect(**params) - conn.async_mode = False - yield conn - - -def _remove_ns(nc_doc: 'NCElement'): - # convert to etree - etree_doc = etree.fromstring(nc_doc.tostring.decode('utf-8')) - for elem in etree_doc.getiterator(): - elem.tag = etree.QName(elem).localname - etree.cleanup_namespaces(etree_doc) - return etree_doc +NCCLIENT_PARAMS = { + "device_params": {"name": "sros"}, + "nc_params": {"capabilities": ["urn:nokia.com:nc:pysros:pc"]}, + "timeout": 60, +} + +INTERFACE_COUNTERS = { + "__defaults__": {"transform": int, "required": False}, + "name": { + "path": ["./lag-name", "./port-id"], + "transform": lambda v: str(v).strip(), + }, + "brian": { + "ingressOctets": {"path": "./statistics/in-octets", "required": True}, + "ingressPackets": { + "path": "./statistics/in-packets", + "required": True, + }, + "egressOctets": {"path": "./statistics/out-octets", "required": True}, + "egressPackets": {"path": "./statistics/out-packets", "required": True}, + "ingressErrors": {"path": "./statistics/in-errors"}, + "egressErrors": {"path": "./statistics/out-errors"}, + "ingressDiscards": {"path": "./statistics/in-discards"}, + }, + "errors": { + "output_total_errors": {"path": "./statistics/out-errors"}, + "input_total_errors": {"path": "./statistics/in-errors"}, + "input_discards": {"path": "./statistics/in-discards"}, + "output_discards": {"path": "./statistics/out-discards"}, + }, +} + +INTERFACE_COUNTERS_ALT = { + "__defaults__": {"transform": int, "required": False}, + "name": { + "path": "./interface-name", + "transform": lambda v: str(v).strip(), + }, + "brian": { + "ingressOctets": {"path": "./statistics/ip/in-octets", "required": True}, + "ingressPackets": { + "path": "./statistics/ip/in-packets", + "required": True, + }, + "egressOctets": {"path": "./statistics/ip/out-octets", "required": True}, + "egressPackets": {"path": "./statistics/ip/out-packets", "required": True}, + "ingressOctetsv6": {"path": "./ipv6/statistics/in-octets"}, + "ingressPacketsv6": {"path": "./ipv6/statistics/in-packets"}, + "egressOctetsv6": {"path": "./ipv6/statistics/out-octets"}, + "egressPacketsv6": {"path": "./ipv6/statistics/out-packets"}, + }, + "errors": {"output_discards": {"path": "./statistics/ip/out-discard-packets"}}, +} def get_netconf_interface_info(router_name: str, ssh_params: dict): + STATE_NS = "urn:nokia.com:sros:ns:yang:sr:state" - def _filter(endpoint_type: str): + def query(*path: str): # https://github.com/nokia/pysros/blob/main/examples/show_port_counters.py # https://netdevops.me/2020/nokia-yang-tree-and-path-browser/ # https://github.com/hellt/nokia-yangtree - _f = etree.Element('filter') - state = etree.SubElement( - _f, - f'{{{STATE_NS}}}state', - nsmap={'nokia-state': STATE_NS}) - _ = etree.SubElement(state, f'{{{STATE_NS}}}{endpoint_type}') - # cf. POL1-799 - # _ = etree.SubElement(_state, f'{{{STATE_NS}}}statistics') - return _f - - start = time.time() - with _connection(hostname=router_name, ssh_params=ssh_params) as router: - # return {_ept: _remove_ns(router.get(filter=_filter(_ept))) for _ept in ENDPOINT_TYPES} - now = time.time() - logger.debug(f"seconds to connect ({router_name}: {now - start}") - - rsp = {} - for _ept in ENDPOINT_TYPES: - start = now - logger.debug(f"Getting {router_name} {_ept} info") - rsp[_ept] = _remove_ns(router.get(filter=_filter(_ept))) - now = time.time() - logger.debug(f" seconds to get {_ept} info: {now - start}") - - return rsp - - -def get_netconf_interface_info_from_source_dir( - router_name: str, source_dir: str, *_, **__ -): - file = pathlib.Path(source_dir) / f"{router_name}-ports.xml" - if not file.is_file(): - raise ValueError(f"file {file} is not a valid file") - ports_doc = etree.fromstring(file.read_text()) - - file = pathlib.Path(source_dir) / f"{router_name}-lags.xml" - if not file.is_file(): - raise ValueError(f"file {file} is not a valid file") - lags_doc = etree.fromstring(file.read_text()) - return { - 'port': ports_doc, - 'lag': lags_doc + root = etree.Element("filter") + sub_elem = etree.SubElement( + root, f"{{{STATE_NS}}}state", nsmap={"nokia-state": STATE_NS} + ) + for p in path: + sub_elem = etree.SubElement(sub_elem, f"{{{STATE_NS}}}{p}") + return root + + def get_xml(conn, query_): + response = conn.get(filter=query_) + return remove_xml_namespaces(response.tostring.decode("utf-8")) + + queries = { + "port": query("port"), + "lag": query("lag"), + "router-interface": query("router", "interface"), } + with netconf_connect( + hostname=router_name, ssh_params=ssh_params, **NCCLIENT_PARAMS + ) as conn: + return {ept: get_xml(conn, q) for ept, q in queries.items()} -def _counter_parse_config(name_tag: str): - """ - the stats info for both port and lag are the same, only the - name we use for reference is different, so the parsing is nearly - identical +def get_netconf_interface_info_from_source_dir(router_name: str, source_dir: str): + def read_doc_or_raise(name: str): + file = pathlib.Path(source_dir) / name + if not file.is_file(): + raise ValueError(f"file {file} is not a valid file") - :param name_tag: either 'port-id' or 'lag-name' - :return: - """ - # sanity: special-purpose method for only these 2 cases - assert name_tag in ('port-id', 'lag-name') + return etree.fromstring(file.read_text()) return { - "__defaults__": {"transform": int, "required": False}, - "name": {"path": f"./{name_tag}", "transform": lambda v: str(v).strip()}, - "brian": { - "ingressOctets": { - "path": "./statistics/in-octets", - "required": True - }, - "ingressPackets": { - "path": "./statistics/in-packets", - "required": True, - }, - "egressOctets": { - "path": "./statistics/out-octets", - "required": True - }, - "egressPackets": { - "path": "./statistics/out-packets", - "required": True - }, - "ingressErrors": {"path": "./statistics/in-errors"}, - "egressErrors": {"path": "./statistics/out-errors"}, - "ingressDiscards": {"path": "./statistics/in-discards"}, - "egressDiscards": {"path": "./statistics/out-discards"}, - }, - "errors": { - "output_total_errors": {"path": "./statistics/out-errors"}, - "input_discards": {"path": "./statistics/in-discards"}, - "output_discards": {"path": "./statistics/out-discards"}, - }, + key: read_doc_or_raise(f"{router_name}-{key}s.xml") + for key in ["port", "lag", "router-interface"] } @@ -155,16 +116,8 @@ def _port_counters(state_doc: etree.Element): :return: counters for all monitored ports """ - parse_config = _counter_parse_config('port-id') - - for port in state_doc.xpath( - "//data/state/port[" - ' normalize-space(oper-state)="up"' - "]" - ): - result = parse_interface_xml(port, parse_config) - assert result - yield result + for elem in state_doc.xpath("//data/state/port"): + yield parse_interface_xml(elem, INTERFACE_COUNTERS) def _lag_counters(state_doc: etree.Element): @@ -173,16 +126,18 @@ def _lag_counters(state_doc: etree.Element): :return: counters for all monitored lags """ - parse_config = _counter_parse_config('lag-name') + for elem in state_doc.xpath("//data/state/lag"): + yield parse_interface_xml(elem, INTERFACE_COUNTERS) + + +def _router_interface_counters(state_doc: etree.Element): + """ + :param state_doc: /nokia-state:state/router/interface yang node + :return: counters for all monitored "router interfaces" + """ - for port in state_doc.xpath( - "//data/state/lag[" - ' normalize-space(oper-state)="up"' - "]" - ): - result = parse_interface_xml(port, parse_config) - assert result - yield result + for elem in state_doc.xpath("//data/state/router/interface"): + yield parse_interface_xml(elem, INTERFACE_COUNTERS_ALT) def interface_counters(raw_counter_docs: dict): @@ -191,27 +146,6 @@ def interface_counters(raw_counter_docs: dict): :param raw_counter_docs: output of a call to get_netconf_interface_info :return: iterable of interface counters """ - yield from _port_counters(raw_counter_docs['port']) - yield from _lag_counters(raw_counter_docs['lag']) - - -if __name__ == '__main__': - from brian_polling_manager.interface_stats import config - import os - config_filename = os.path.join( - os.path.dirname(__file__), - '..', 'config-test.json') - with open(config_filename) as f: - params = config.load(f) - - ROUTERS = [ - 'rt0.lon.uk.lab.office.geant.net', - 'rt0.ams.nl.lab.office.geant.net' - ] - - # rsp = get_interface_info_ncrpc( - raw_counter_docs = get_netconf_interface_info( - router_name=ROUTERS[0], - ssh_params=params['nokia']) - - print(interface_counters(raw_counter_docs)) + yield from _port_counters(raw_counter_docs["port"]) + yield from _lag_counters(raw_counter_docs["lag"]) + yield from _router_interface_counters(raw_counter_docs["router-interface"]) diff --git a/test/interface_stats/conftest.py b/test/interface_stats/conftest.py index bb9159cd43fc55dcb834dcc1cc27ccee7ad1f522..ed0a766c2d78f5600b04a79682f44d0ac4e594fc 100644 --- a/test/interface_stats/conftest.py +++ b/test/interface_stats/conftest.py @@ -1,8 +1,8 @@ -import functools import json import pathlib from unittest.mock import patch -from brian_polling_manager.interface_stats.vendors import juniper, nokia +from brian_polling_manager.interface_stats import cli +from brian_polling_manager.interface_stats.vendors import Vendor, juniper, nokia import pytest @@ -30,38 +30,21 @@ def data_dir(): return DATA_DIR -@pytest.fixture(autouse=True) -def mocked_juniper_get_netconf_interface_info(data_dir): - patcher = patch.object( - juniper, - "get_netconf_interface_info", - functools.partial( - juniper.get_netconf_interface_info_from_source_dir, source_dir=data_dir - ), - ) - patcher.start() - yield patcher - try: - patcher.stop() - except RuntimeError: # already stopped - pass - - -@pytest.fixture(autouse=True) -def mocked_nokia_get_netconf_interface_info(data_dir): - patcher = patch.object( - nokia, - "get_netconf_interface_info", - functools.partial( - nokia.get_netconf_interface_info_from_source_dir, source_dir=data_dir - ), - ) - patcher.start() - yield patcher - try: - patcher.stop() - except RuntimeError: # already stopped - pass +@pytest.fixture +def mocked_get_netconf(data_dir): + def get_netconf(router_name, vendor, **_): + if vendor == Vendor.JUNIPER: + return juniper.get_netconf_interface_info_from_source_dir( + router_name, source_dir=data_dir + ) + + else: + return nokia.get_netconf_interface_info_from_source_dir( + router_name, source_dir=data_dir + ) + + with patch.object(cli, "get_netconf", side_effect=get_netconf) as mock: + yield mock def poller_interfaces(): diff --git a/test/interface_stats/data/capture-test-data-nokia.py b/test/interface_stats/data/capture-test-data-nokia.py index 406d1b3e4d42cc4f616ff66a487ea02bf93a4793..783efe07a67bb04059a6ba09f2ac15557f991889 100644 --- a/test/interface_stats/data/capture-test-data-nokia.py +++ b/test/interface_stats/data/capture-test-data-nokia.py @@ -24,11 +24,10 @@ def load_estate_interface_info(): logger.info(fqdn) raw_counter_docs = nokia.get_netconf_interface_info(router_name=fqdn, ssh_params=SSH_PARAMS) - file = pathlib.Path(__file__).parent / f"{fqdn}-ports.xml" - file.write_bytes(etree.tostring(raw_counter_docs['port'])) - - file = pathlib.Path(__file__).parent / f"{fqdn}-lags.xml" - file.write_bytes(etree.tostring(raw_counter_docs['lag'])) + assert set(raw_counter_docs.keys()) == {"port", "lag", "router-interface"} + for key,doc in raw_counter_docs.items(): + file = pathlib.Path(__file__).parent / f"{fqdn}-{key}s.xml" + file.write_bytes(etree.tostring(doc)) if __name__ == "__main__": diff --git a/test/interface_stats/data/capture-test-data.py b/test/interface_stats/data/capture-test-data.py index 6f152cc68fa0af59ea8e85bc4e62b589adb7a851..fa85b905b6be96dc46bec55a197b16b4545425c3 100644 --- a/test/interface_stats/data/capture-test-data.py +++ b/test/interface_stats/data/capture-test-data.py @@ -1,7 +1,8 @@ import concurrent.futures import pathlib -from brian_polling_manager.interface_stats.services.netconf import ( - JuniperNetconfProvider, + +from brian_polling_manager.interface_stats.vendors.juniper import ( + get_netconf_interface_info, ) from lxml import etree @@ -66,20 +67,21 @@ ROUTERS = [ ] -def save_router_info(fqdn, provider: JuniperNetconfProvider): +def save_router_info(fqdn, ssh_params: dict): print(fqdn) file = pathlib.Path(__file__).parent / f"{fqdn}-interface-info.xml" - doc = provider.get(fqdn) + doc = get_netconf_interface_info(router_name=fqdn, ssh_params=ssh_params) file.write_text(etree.tostring(doc)) def load_estate_interface_info(): - provider = JuniperNetconfProvider( - ssh_params={"host_verify": False, "ssh_config": "~/.ssh/config"} - ) with concurrent.futures.ThreadPoolExecutor(max_workers=20) as executor: for r in ROUTERS: - executor.submit(save_router_info, r, provider) + executor.submit( + save_router_info, + r, + ssh_params={"host_verify": False, "ssh_config": "~/.ssh/config"}, + ) executor.shutdown(wait=True) diff --git a/test/interface_stats/data/raw-response-sample.xml b/test/interface_stats/data/raw-response-juniper-sample.xml similarity index 100% rename from test/interface_stats/data/raw-response-sample.xml rename to test/interface_stats/data/raw-response-juniper-sample.xml diff --git a/test/interface_stats/data/raw-response-nokia-sample.xml b/test/interface_stats/data/raw-response-nokia-sample.xml new file mode 100644 index 0000000000000000000000000000000000000000..ecd5d4a6235775485d11b8fe3ea27979791935f3 --- /dev/null +++ b/test/interface_stats/data/raw-response-nokia-sample.xml @@ -0,0 +1,333 @@ +<rpc-reply xmlns="urn:ietf:params:xml:ns:netconf:base:1.0" message-id="urn:uuid:e6f0895c-876f-4bec-a018-8ad25e3f2605"> + <data> + <state xmlns="urn:nokia.com:sros:ns:yang:sr:state"> + <port> + <port-id>1/1/c1</port-id> + <down-reason/> + <far-end-port-id/> + <fp-number>1</fp-number> + <hardware-mac-address>00:00:00:00:00:00</hardware-mac-address> + <interface-group-handler-index>0</interface-group-handler-index> + <mac-chip-number>1</mac-chip-number> + <number-of-channels>0</number-of-channels> + <oper-state>down</oper-state> + <oper-state-last-changed>2024-04-04T09:57:28.2Z</oper-state-last-changed> + <otu-capable>false</otu-capable> + <physical-link>false</physical-link> + <port-class>connector</port-class> + <port-state>link-down</port-state> + <previous-state>link-down</previous-state> + <protocols-supported/> + <type>qsfp-dd-connector</type> + <user-assigned-mac>false</user-assigned-mac> + <rs-fec-oper-mode>none</rs-fec-oper-mode> + <licensed>true</licensed> + <if-index>1610899520</if-index> + <transceiver> + <equipped>false</equipped> + </transceiver> + <statistics> + <in-discards>0</in-discards> + <in-errors>0</in-errors> + <in-octets>0</in-octets> + <in-packets>0</in-packets> + <in-unknown-protocol-discards>0</in-unknown-protocol-discards> + <in-broadcast-packets>0</in-broadcast-packets> + <in-multicast-packets>0</in-multicast-packets> + <in-unicast-packets>0</in-unicast-packets> + <out-discards>0</out-discards> + <out-errors>0</out-errors> + <out-octets>0</out-octets> + <out-packets>0</out-packets> + <out-broadcast-packets>0</out-broadcast-packets> + <out-multicast-packets>0</out-multicast-packets> + <out-unicast-packets>0</out-unicast-packets> + </statistics> + <subscriber-management> + <statistics> + <ipv4-hosts> + <counter>ipcp</counter> + <current-value>0</current-value> + </ipv4-hosts> + <ipv4-hosts> + <counter>dhcp</counter> + <current-value>0</current-value> + </ipv4-hosts> + <ipv4-hosts> + <counter>arp</counter> + <current-value>0</current-value> + </ipv4-hosts> + <ipv4-hosts> + <counter>static</counter> + <current-value>0</current-value> + </ipv4-hosts> + <ipv4-hosts> + <counter>data-triggered</counter> + <current-value>0</current-value> + </ipv4-hosts> + <ipv4-hosts> + <counter>aaa</counter> + <current-value>0</current-value> + </ipv4-hosts> + <ipv4-hosts> + <counter>gtp</counter> + <current-value>0</current-value> + </ipv4-hosts> + <ipv4-hosts> + <counter>bonding</counter> + <current-value>0</current-value> + </ipv4-hosts> + <ipv4-hosts> + <counter>dhcp-bsm</counter> + <current-value>0</current-value> + </ipv4-hosts> + <ipv4-hosts> + <counter>static-bsm</counter> + <current-value>0</current-value> + </ipv4-hosts> + <ipv4-hosts> + <counter>dhcp-bsm-antispoof</counter> + <current-value>0</current-value> + </ipv4-hosts> + <ipv4-hosts> + <counter>static-bsm-antispoof</counter> + <current-value>0</current-value> + </ipv4-hosts> + <ipv4-hosts> + <counter>pfcp-ppp</counter> + <current-value>0</current-value> + </ipv4-hosts> + <ipv4-hosts> + <counter>pfcp-ipoe</counter> + <current-value>0</current-value> + </ipv4-hosts> + <ipv4-hosts> + <counter>pfcp-fwa</counter> + <current-value>0</current-value> + </ipv4-hosts> + <ipv6-hosts> + <counter>ppp-slaac</counter> + <current-value>0</current-value> + </ipv6-hosts> + <ipv6-hosts> + <counter>ppp-dhcp6-na</counter> + <current-value>0</current-value> + </ipv6-hosts> + <ipv6-hosts> + <counter>ppp-dhcp6-pd</counter> + <current-value>0</current-value> + </ipv6-hosts> + <ipv6-hosts> + <counter>ppp-dhcp6-pd-mngdrt</counter> + <current-value>0</current-value> + </ipv6-hosts> + <ipv6-hosts> + <counter>ipoe-slaac</counter> + <current-value>0</current-value> + </ipv6-hosts> + <ipv6-hosts> + <counter>ipoe-dhcp6-na</counter> + <current-value>0</current-value> + </ipv6-hosts> + <ipv6-hosts> + <counter>ipoe-dhcp6-pd</counter> + <current-value>0</current-value> + </ipv6-hosts> + <ipv6-hosts> + <counter>ipoe-dhcp6-pd-mngdrt</counter> + <current-value>0</current-value> + </ipv6-hosts> + <ipv6-hosts> + <counter>ipoe-static-wan</counter> + <current-value>0</current-value> + </ipv6-hosts> + <ipv6-hosts> + <counter>ipoe-static-pfx</counter> + <current-value>0</current-value> + </ipv6-hosts> + <ipv6-hosts> + <counter>ipoe-mngd-data-wan</counter> + <current-value>0</current-value> + </ipv6-hosts> + <ipv6-hosts> + <counter>ipoe-mngd-data-pfx</counter> + <current-value>0</current-value> + </ipv6-hosts> + <ipv6-hosts> + <counter>ipoe-mngd-data-pfx-mngdrt</counter> + <current-value>0</current-value> + </ipv6-hosts> + <ipv6-hosts> + <counter>ipoe-mngd-aaa</counter> + <current-value>0</current-value> + </ipv6-hosts> + <ipv6-hosts> + <counter>ipoe-mngd-gtp</counter> + <current-value>0</current-value> + </ipv6-hosts> + <ipv6-hosts> + <counter>ipoe-mngd-bonding</counter> + <current-value>0</current-value> + </ipv6-hosts> + <ipv6-hosts> + <counter>ipoe-dhcp6-na-bsm</counter> + <current-value>0</current-value> + </ipv6-hosts> + <ipv6-hosts> + <counter>ipoe-dhcp6-pd-bsm</counter> + <current-value>0</current-value> + </ipv6-hosts> + <ipv6-hosts> + <counter>pfcp-ppp-slaac</counter> + <current-value>0</current-value> + </ipv6-hosts> + <ipv6-hosts> + <counter>pfcp-ppp-na</counter> + <current-value>0</current-value> + </ipv6-hosts> + <ipv6-hosts> + <counter>pfcp-ppp-pd</counter> + <current-value>0</current-value> + </ipv6-hosts> + <ipv6-hosts> + <counter>pfcp-ipoe-slaac</counter> + <current-value>0</current-value> + </ipv6-hosts> + <ipv6-hosts> + <counter>pfcp-ipoe-na</counter> + <current-value>0</current-value> + </ipv6-hosts> + <ipv6-hosts> + <counter>pfcp-ipoe-pd</counter> + <current-value>0</current-value> + </ipv6-hosts> + <ipv6-hosts> + <counter>pfcp-fwa-slaac</counter> + <current-value>0</current-value> + </ipv6-hosts> + <ipv6-hosts> + <counter>pfcp-fwa-na</counter> + <current-value>0</current-value> + </ipv6-hosts> + <ipv6-hosts> + <counter>pfcp-fwa-pd</counter> + <current-value>0</current-value> + </ipv6-hosts> + <total-hosts> + <counter>subhosts</counter> + <current-value>0</current-value> + </total-hosts> + <total-hosts> + <counter>ppp</counter> + <current-value>0</current-value> + </total-hosts> + <total-hosts> + <counter>ipoe</counter> + <current-value>0</current-value> + </total-hosts> + <total-hosts> + <counter>ipv4</counter> + <current-value>0</current-value> + </total-hosts> + <total-hosts> + <counter>ipv6</counter> + <current-value>0</current-value> + </total-hosts> + <total-hosts> + <counter>ipv6-pd-mngdrt</counter> + <current-value>0</current-value> + </total-hosts> + <total-hosts> + <counter>l2tp-lac</counter> + <current-value>0</current-value> + </total-hosts> + <total-hosts> + <counter>l2tp-lns</counter> + <current-value>0</current-value> + </total-hosts> + <total-hosts> + <counter>internal</counter> + <current-value>0</current-value> + </total-hosts> + <total-hosts> + <counter>nonsub-traffic</counter> + <current-value>0</current-value> + </total-hosts> + <total-hosts> + <counter>dhcp-leases</counter> + <current-value>0</current-value> + </total-hosts> + <total-hosts> + <counter>dhcp6-leases</counter> + <current-value>0</current-value> + </total-hosts> + <total-hosts> + <counter>pfcp</counter> + <current-value>0</current-value> + </total-hosts> + <sessions> + <counter>ppp-sessions-total-established</counter> + <current-value>0</current-value> + </sessions> + <sessions> + <counter>ppp-sessions-total-in-setup</counter> + <current-value>0</current-value> + </sessions> + <sessions> + <counter>ppp-sessions-local-pppoe</counter> + <current-value>0</current-value> + </sessions> + <sessions> + <counter>ppp-sessions-local-pppoeoa</counter> + <current-value>0</current-value> + </sessions> + <sessions> + <counter>ppp-sessions-local-pppoa</counter> + <current-value>0</current-value> + </sessions> + <sessions> + <counter>ppp-sessions-local-l2tp-lns</counter> + <current-value>0</current-value> + </sessions> + <sessions> + <counter>ppp-sessions-local-total</counter> + <current-value>0</current-value> + </sessions> + <sessions> + <counter>ppp-sessions-lac-pppoe</counter> + <current-value>0</current-value> + </sessions> + <sessions> + <counter>ppp-sessions-lac-pppoeoa</counter> + <current-value>0</current-value> + </sessions> + <sessions> + <counter>ppp-sessions-lac-pppoa</counter> + <current-value>0</current-value> + </sessions> + <sessions> + <counter>ppp-sessions-lac-l2tp-lts</counter> + <current-value>0</current-value> + </sessions> + <sessions> + <counter>ppp-sessions-lac-total</counter> + <current-value>0</current-value> + </sessions> + <sessions> + <counter>ipoe-sessions-total-established</counter> + <current-value>0</current-value> + </sessions> + <sessions> + <counter>ipoe-sessions-total-in-setup</counter> + <current-value>0</current-value> + </sessions> + <sessions> + <counter>subscribers-total</counter> + <current-value>0</current-value> + </sessions> + </statistics> + </subscriber-management> + </port> + </state> + </data> +</rpc-reply> diff --git a/test/interface_stats/data/rt0.ams.nl.lab.office.geant.net-router-interfaces.xml b/test/interface_stats/data/rt0.ams.nl.lab.office.geant.net-router-interfaces.xml new file mode 100644 index 0000000000000000000000000000000000000000..3907cfe5d7b6f3b6214ec1b717a04c74039d4841 --- /dev/null +++ b/test/interface_stats/data/rt0.ams.nl.lab.office.geant.net-router-interfaces.xml @@ -0,0 +1,661 @@ +<rpc-reply message-id="urn:uuid:5a05fb39-aaca-48e4-979c-2d6db6ba2c01"> + <data> + <state> + <router> + <router-name>Base</router-name> + <interface> + <interface-name>system</interface-name> + <if-index>1</if-index> + <system-if-index>256</system-if-index> + <oper-state>up</oper-state> + <protocol>isis mpls rsvp pim</protocol> + <oper-ip-mtu>1500</oper-ip-mtu> + <creation-origin>manual</creation-origin> + <last-oper-change>2024-04-04T07:53:41.8Z</last-oper-change> + <statistics> + <ip> + <out-packets>0</out-packets> + <out-octets>0</out-octets> + <out-discard-packets>0</out-discard-packets> + <out-discard-octets>0</out-discard-octets> + <in-packets>0</in-packets> + <in-octets>0</in-octets> + <urpf-check-fail-packets>0</urpf-check-fail-packets> + <urpf-check-fail-octets>0</urpf-check-fail-octets> + </ip> + <mpls> + <out-packets>0</out-packets> + <out-octets>0</out-octets> + <in-packets>0</in-packets> + <in-octets>0</in-octets> + </mpls> + </statistics> + <ipv4> + <oper-state>up</oper-state> + <icmp> + <statistics> + <icmp-in-msgs>0</icmp-in-msgs> + <icmp-in-errors>0</icmp-in-errors> + <icmp-in-dest-unreachables>0</icmp-in-dest-unreachables> + <icmp-in-redirects>0</icmp-in-redirects> + <icmp-in-echos>0</icmp-in-echos> + <icmp-in-echo-replies>0</icmp-in-echo-replies> + <icmp-in-time-exceeds>0</icmp-in-time-exceeds> + <icmp-in-src-quenches>0</icmp-in-src-quenches> + <icmp-in-timestamps>0</icmp-in-timestamps> + <icmp-in-timestamp-replies>0</icmp-in-timestamp-replies> + <icmp-in-address-masks>0</icmp-in-address-masks> + <icmp-in-address-mask-replies>0</icmp-in-address-mask-replies> + <icmp-in-parm-problems>0</icmp-in-parm-problems> + <icmp-out-msgs>0</icmp-out-msgs> + <icmp-out-errors>0</icmp-out-errors> + <icmp-out-dest-unreachables>0</icmp-out-dest-unreachables> + <icmp-out-redirects>0</icmp-out-redirects> + <icmp-out-echos>0</icmp-out-echos> + <icmp-out-echo-replies>0</icmp-out-echo-replies> + <icmp-out-time-exceeds>0</icmp-out-time-exceeds> + <icmp-out-src-quenches>0</icmp-out-src-quenches> + <icmp-out-timestamps>0</icmp-out-timestamps> + <icmp-out-timestamp-replies>0</icmp-out-timestamp-replies> + <icmp-out-address-masks>0</icmp-out-address-masks> + <icmp-out-address-mask-replies>0</icmp-out-address-mask-replies> + <icmp-out-parm-problems>0</icmp-out-parm-problems> + <icmp-out-discards>0</icmp-out-discards> + </statistics> + </icmp> + <dhcp> + <statistics> + <total-rx-packets> + <received>0</received> + <malformed>0</malformed> + <untrusted>0</untrusted> + </total-rx-packets> + <total-tx-packets> + <transmitted>0</transmitted> + </total-tx-packets> + <client-packets> + <dropped>0</dropped> + <relayed>0</relayed> + <snooped>0</snooped> + </client-packets> + <server-packets> + <dropped>0</dropped> + <relayed>0</relayed> + <snooped>0</snooped> + </server-packets> + </statistics> + </dhcp> + <statistics> + <out-packets>0</out-packets> + <out-octets>0</out-octets> + <out-discard-packets>0</out-discard-packets> + <out-discard-octets>0</out-discard-octets> + <in-packets>0</in-packets> + <in-octets>0</in-octets> + <urpf-check-fail-packets>0</urpf-check-fail-packets> + <urpf-check-fail-octets>0</urpf-check-fail-octets> + <out-discard-dbcast-packets>0</out-discard-dbcast-packets> + <out-discard-dbcast-octets>0</out-discard-dbcast-octets> + <in-ip-helper-redirects-packets>0</in-ip-helper-redirects-packets> + <in-ip-helper-redirects-octets>0</in-ip-helper-redirects-octets> + </statistics> + <primary> + <oper-address>62.40.119.6</oper-address> + <creation-origin>manual</creation-origin> + </primary> + </ipv4> + <ipv6> + <oper-state>up</oper-state> + <icmp6> + <statistics> + <icmp6-in-msgs>0</icmp6-in-msgs> + <icmp6-in-errors>0</icmp6-in-errors> + <icmp6-in-dest-unreachables>0</icmp6-in-dest-unreachables> + <icmp6-in-admin-prohibs>0</icmp6-in-admin-prohibs> + <icmp6-in-time-exceeds>0</icmp6-in-time-exceeds> + <icmp6-in-parm-problems>0</icmp6-in-parm-problems> + <icmp6-in-pkt-too-bigs>0</icmp6-in-pkt-too-bigs> + <icmp6-in-echos>0</icmp6-in-echos> + <icmp6-in-echo-replies>0</icmp6-in-echo-replies> + <icmp6-in-rtr-solicits>0</icmp6-in-rtr-solicits> + <icmp6-in-rtr-advertisements>0</icmp6-in-rtr-advertisements> + <icmp6-in-nbr-solicits>0</icmp6-in-nbr-solicits> + <icmp6-in-nbr-advertisements>0</icmp6-in-nbr-advertisements> + <icmp6-in-redirects>0</icmp6-in-redirects> + <icmp6-in-grp-memb-queries>0</icmp6-in-grp-memb-queries> + <icmp6-in-grp-memb-repsonses>0</icmp6-in-grp-memb-repsonses> + <icmp6-in-grp-memb-reductions>0</icmp6-in-grp-memb-reductions> + <icmp6-out-msgs>0</icmp6-out-msgs> + <icmp6-out-errors>0</icmp6-out-errors> + <icmp6-out-dest-unreachables>0</icmp6-out-dest-unreachables> + <icmp6-out-admin-prohibs>0</icmp6-out-admin-prohibs> + <icmp6-out-time-exceeds>0</icmp6-out-time-exceeds> + <icmp6-out-parm-problems>0</icmp6-out-parm-problems> + <icmp6-out-pkt-too-bigs>0</icmp6-out-pkt-too-bigs> + <icmp6-out-echos>0</icmp6-out-echos> + <icmp6-out-echo-replies>0</icmp6-out-echo-replies> + <icmp6-out-rtr-solicits>0</icmp6-out-rtr-solicits> + <icmp6-out-rtr-advertisements>0</icmp6-out-rtr-advertisements> + <icmp6-out-nbr-solicits>0</icmp6-out-nbr-solicits> + <icmp6-out-nbr-advertisements>0</icmp6-out-nbr-advertisements> + <icmp6-out-redirects>0</icmp6-out-redirects> + <icmp6-out-grp-memb-queries>0</icmp6-out-grp-memb-queries> + <icmp6-out-grp-memb-responses>0</icmp6-out-grp-memb-responses> + <icmp6-out-grp-memb-reductions>0</icmp6-out-grp-memb-reductions> + <icmp6-out-discards>0</icmp6-out-discards> + </statistics> + </icmp6> + <statistics> + <out-packets>0</out-packets> + <out-octets>0</out-octets> + <out-discard-packets>0</out-discard-packets> + <out-discard-octets>0</out-discard-octets> + <in-packets>0</in-packets> + <in-octets>0</in-octets> + <urpf-check-fail-packets>0</urpf-check-fail-packets> + <urpf-check-fail-octets>0</urpf-check-fail-octets> + </statistics> + <address> + <ipv6-address>2001:799:1ab::6</ipv6-address> + <address-state>preferred</address-state> + <oper-address>2001:799:1ab::6</oper-address> + <creation-origin>manual</creation-origin> + <primary-preferred>true</primary-preferred> + </address> + </ipv6> + </interface> + <interface> + <interface-name>lag-1.0</interface-name> + <if-index>2</if-index> + <system-if-index>1</system-if-index> + <oper-state>up</oper-state> + <protocol>isis mpls rsvp pim</protocol> + <oper-ip-mtu>9000</oper-ip-mtu> + <creation-origin>manual</creation-origin> + <last-oper-change>2024-04-04T08:00:22.0Z</last-oper-change> + <distributed-cpu-protection> + <static-policer> + <name>ICMP_LIMIT</name> + <card>1</card> + <fp-number>1</fp-number> + <state>conform</state> + <exceed-count>0</exceed-count> + <hold-down-remain>none</hold-down-remain> + <detection-time-remain>0</detection-time-remain> + <total-exceed-count>0</total-exceed-count> + <exit-conform-state-count>0</exit-conform-state-count> + </static-policer> + </distributed-cpu-protection> + <statistics> + <ip> + <out-packets>3437402</out-packets> + <out-octets>1000435620</out-octets> + <out-discard-packets>0</out-discard-packets> + <out-discard-octets>0</out-discard-octets> + <in-packets>2891465</in-packets> + <in-octets>321980620</in-octets> + <urpf-check-fail-packets>0</urpf-check-fail-packets> + <urpf-check-fail-octets>0</urpf-check-fail-octets> + </ip> + <mpls> + <out-packets>6054</out-packets> + <out-octets>2634617</out-octets> + <in-packets>6280</in-packets> + <in-octets>3101782</in-octets> + </mpls> + </statistics> + <ipv4> + <oper-state>up</oper-state> + <icmp> + <statistics> + <icmp-in-msgs>81402</icmp-in-msgs> + <icmp-in-errors>47768</icmp-in-errors> + <icmp-in-dest-unreachables>47768</icmp-in-dest-unreachables> + <icmp-in-redirects>0</icmp-in-redirects> + <icmp-in-echos>33634</icmp-in-echos> + <icmp-in-echo-replies>0</icmp-in-echo-replies> + <icmp-in-time-exceeds>0</icmp-in-time-exceeds> + <icmp-in-src-quenches>0</icmp-in-src-quenches> + <icmp-in-timestamps>0</icmp-in-timestamps> + <icmp-in-timestamp-replies>0</icmp-in-timestamp-replies> + <icmp-in-address-masks>0</icmp-in-address-masks> + <icmp-in-address-mask-replies>0</icmp-in-address-mask-replies> + <icmp-in-parm-problems>0</icmp-in-parm-problems> + <icmp-out-msgs>33633</icmp-out-msgs> + <icmp-out-errors>0</icmp-out-errors> + <icmp-out-dest-unreachables>0</icmp-out-dest-unreachables> + <icmp-out-redirects>0</icmp-out-redirects> + <icmp-out-echos>0</icmp-out-echos> + <icmp-out-echo-replies>33633</icmp-out-echo-replies> + <icmp-out-time-exceeds>0</icmp-out-time-exceeds> + <icmp-out-src-quenches>0</icmp-out-src-quenches> + <icmp-out-timestamps>0</icmp-out-timestamps> + <icmp-out-timestamp-replies>0</icmp-out-timestamp-replies> + <icmp-out-address-masks>0</icmp-out-address-masks> + <icmp-out-address-mask-replies>0</icmp-out-address-mask-replies> + <icmp-out-parm-problems>0</icmp-out-parm-problems> + <icmp-out-discards>0</icmp-out-discards> + </statistics> + </icmp> + <dhcp> + <statistics> + <total-rx-packets> + <received>0</received> + <malformed>0</malformed> + <untrusted>0</untrusted> + </total-rx-packets> + <total-tx-packets> + <transmitted>0</transmitted> + </total-tx-packets> + <client-packets> + <dropped>0</dropped> + <relayed>0</relayed> + <snooped>0</snooped> + </client-packets> + <server-packets> + <dropped>0</dropped> + <relayed>0</relayed> + <snooped>0</snooped> + </server-packets> + </statistics> + </dhcp> + <statistics> + <out-packets>3177195</out-packets> + <out-octets>970038344</out-octets> + <out-discard-packets>0</out-discard-packets> + <out-discard-octets>0</out-discard-octets> + <in-packets>0</in-packets> + <in-octets>0</in-octets> + <urpf-check-fail-packets>0</urpf-check-fail-packets> + <urpf-check-fail-octets>0</urpf-check-fail-octets> + <out-discard-dbcast-packets>0</out-discard-dbcast-packets> + <out-discard-dbcast-octets>0</out-discard-dbcast-octets> + <in-ip-helper-redirects-packets>0</in-ip-helper-redirects-packets> + <in-ip-helper-redirects-octets>0</in-ip-helper-redirects-octets> + </statistics> + <primary> + <oper-address>62.40.119.88</oper-address> + <creation-origin>manual</creation-origin> + </primary> + <neighbor-discovery> + <neighbor> + <ipv4-address>62.40.119.89</ipv4-address> + <oper-state>up</oper-state> + <mac-address>b2:a8:6e:3e:f4:b8</mac-address> + <type>dynamic</type> + <timer>13974</timer> + </neighbor> + </neighbor-discovery> + </ipv4> + <ipv6> + <oper-state>up</oper-state> + <icmp6> + <statistics> + <icmp6-in-msgs>18601</icmp6-in-msgs> + <icmp6-in-errors>403</icmp6-in-errors> + <icmp6-in-dest-unreachables>403</icmp6-in-dest-unreachables> + <icmp6-in-admin-prohibs>0</icmp6-in-admin-prohibs> + <icmp6-in-time-exceeds>0</icmp6-in-time-exceeds> + <icmp6-in-parm-problems>0</icmp6-in-parm-problems> + <icmp6-in-pkt-too-bigs>0</icmp6-in-pkt-too-bigs> + <icmp6-in-echos>0</icmp6-in-echos> + <icmp6-in-echo-replies>0</icmp6-in-echo-replies> + <icmp6-in-rtr-solicits>0</icmp6-in-rtr-solicits> + <icmp6-in-rtr-advertisements>0</icmp6-in-rtr-advertisements> + <icmp6-in-nbr-solicits>1605</icmp6-in-nbr-solicits> + <icmp6-in-nbr-advertisements>16593</icmp6-in-nbr-advertisements> + <icmp6-in-redirects>0</icmp6-in-redirects> + <icmp6-in-grp-memb-queries>0</icmp6-in-grp-memb-queries> + <icmp6-in-grp-memb-repsonses>0</icmp6-in-grp-memb-repsonses> + <icmp6-in-grp-memb-reductions>0</icmp6-in-grp-memb-reductions> + <icmp6-out-msgs>18198</icmp6-out-msgs> + <icmp6-out-errors>0</icmp6-out-errors> + <icmp6-out-dest-unreachables>0</icmp6-out-dest-unreachables> + <icmp6-out-admin-prohibs>0</icmp6-out-admin-prohibs> + <icmp6-out-time-exceeds>0</icmp6-out-time-exceeds> + <icmp6-out-parm-problems>0</icmp6-out-parm-problems> + <icmp6-out-pkt-too-bigs>0</icmp6-out-pkt-too-bigs> + <icmp6-out-echos>0</icmp6-out-echos> + <icmp6-out-echo-replies>0</icmp6-out-echo-replies> + <icmp6-out-rtr-solicits>0</icmp6-out-rtr-solicits> + <icmp6-out-rtr-advertisements>0</icmp6-out-rtr-advertisements> + <icmp6-out-nbr-solicits>16593</icmp6-out-nbr-solicits> + <icmp6-out-nbr-advertisements>1605</icmp6-out-nbr-advertisements> + <icmp6-out-redirects>0</icmp6-out-redirects> + <icmp6-out-grp-memb-queries>0</icmp6-out-grp-memb-queries> + <icmp6-out-grp-memb-responses>0</icmp6-out-grp-memb-responses> + <icmp6-out-grp-memb-reductions>0</icmp6-out-grp-memb-reductions> + <icmp6-out-discards>0</icmp6-out-discards> + </statistics> + </icmp6> + <link-local-address> + <oper-address>fe80::1ac3:ff:fe91:fe59</oper-address> + <address-state>preferred</address-state> + </link-local-address> + <statistics> + <out-packets>260207</out-packets> + <out-octets>30397276</out-octets> + <out-discard-packets>0</out-discard-packets> + <out-discard-octets>0</out-discard-octets> + <in-packets>0</in-packets> + <in-octets>0</in-octets> + <urpf-check-fail-packets>0</urpf-check-fail-packets> + <urpf-check-fail-octets>0</urpf-check-fail-octets> + </statistics> + <address> + <ipv6-address>2001:799:1ab:2::31</ipv6-address> + <address-state>preferred</address-state> + <oper-address>2001:799:1ab:2::31</oper-address> + <creation-origin>manual</creation-origin> + <primary-preferred>true</primary-preferred> + </address> + <neighbor-discovery> + <neighbor> + <ipv6-address>fe80::b2a8:6eff:fe3e:f4b8</ipv6-address> + <state>delay</state> + <is-router>true</is-router> + <mtu>9000</mtu> + <mac-address>b2:a8:6e:3e:f4:b8</mac-address> + <type>dynamic</type> + <timer>4</timer> + </neighbor> + </neighbor-discovery> + </ipv6> + </interface> + <interface> + <interface-name>lag-2.0</interface-name> + <if-index>3</if-index> + <system-if-index>2</system-if-index> + <oper-state>up</oper-state> + <protocol>isis mpls rsvp pim</protocol> + <oper-ip-mtu>9000</oper-ip-mtu> + <creation-origin>manual</creation-origin> + <last-oper-change>2024-04-04T08:00:32.1Z</last-oper-change> + <distributed-cpu-protection> + <static-policer> + <name>ICMP_LIMIT</name> + <card>1</card> + <fp-number>1</fp-number> + <state>conform</state> + <exceed-count>0</exceed-count> + <hold-down-remain>none</hold-down-remain> + <detection-time-remain>0</detection-time-remain> + <total-exceed-count>0</total-exceed-count> + <exit-conform-state-count>0</exit-conform-state-count> + </static-policer> + </distributed-cpu-protection> + <statistics> + <ip> + <out-packets>174521</out-packets> + <out-octets>14224224</out-octets> + <out-discard-packets>0</out-discard-packets> + <out-discard-octets>0</out-discard-octets> + <in-packets>172336</in-packets> + <in-octets>14081274</in-octets> + <urpf-check-fail-packets>0</urpf-check-fail-packets> + <urpf-check-fail-octets>0</urpf-check-fail-octets> + </ip> + <mpls> + <out-packets>2014</out-packets> + <out-octets>1703454</out-octets> + <in-packets>1788</in-packets> + <in-octets>1242133</in-octets> + </mpls> + </statistics> + <ipv4> + <oper-state>up</oper-state> + <icmp> + <statistics> + <icmp-in-msgs>0</icmp-in-msgs> + <icmp-in-errors>0</icmp-in-errors> + <icmp-in-dest-unreachables>0</icmp-in-dest-unreachables> + <icmp-in-redirects>0</icmp-in-redirects> + <icmp-in-echos>0</icmp-in-echos> + <icmp-in-echo-replies>0</icmp-in-echo-replies> + <icmp-in-time-exceeds>0</icmp-in-time-exceeds> + <icmp-in-src-quenches>0</icmp-in-src-quenches> + <icmp-in-timestamps>0</icmp-in-timestamps> + <icmp-in-timestamp-replies>0</icmp-in-timestamp-replies> + <icmp-in-address-masks>0</icmp-in-address-masks> + <icmp-in-address-mask-replies>0</icmp-in-address-mask-replies> + <icmp-in-parm-problems>0</icmp-in-parm-problems> + <icmp-out-msgs>1</icmp-out-msgs> + <icmp-out-errors>0</icmp-out-errors> + <icmp-out-dest-unreachables>0</icmp-out-dest-unreachables> + <icmp-out-redirects>0</icmp-out-redirects> + <icmp-out-echos>0</icmp-out-echos> + <icmp-out-echo-replies>1</icmp-out-echo-replies> + <icmp-out-time-exceeds>0</icmp-out-time-exceeds> + <icmp-out-src-quenches>0</icmp-out-src-quenches> + <icmp-out-timestamps>0</icmp-out-timestamps> + <icmp-out-timestamp-replies>0</icmp-out-timestamp-replies> + <icmp-out-address-masks>0</icmp-out-address-masks> + <icmp-out-address-mask-replies>0</icmp-out-address-mask-replies> + <icmp-out-parm-problems>0</icmp-out-parm-problems> + <icmp-out-discards>0</icmp-out-discards> + </statistics> + </icmp> + <dhcp> + <statistics> + <total-rx-packets> + <received>0</received> + <malformed>0</malformed> + <untrusted>0</untrusted> + </total-rx-packets> + <total-tx-packets> + <transmitted>0</transmitted> + </total-tx-packets> + <client-packets> + <dropped>0</dropped> + <relayed>0</relayed> + <snooped>0</snooped> + </client-packets> + <server-packets> + <dropped>0</dropped> + <relayed>0</relayed> + <snooped>0</snooped> + </server-packets> + </statistics> + </dhcp> + <statistics> + <out-packets>153219</out-packets> + <out-octets>11793614</out-octets> + <out-discard-packets>0</out-discard-packets> + <out-discard-octets>0</out-discard-octets> + <in-packets>0</in-packets> + <in-octets>0</in-octets> + <urpf-check-fail-packets>0</urpf-check-fail-packets> + <urpf-check-fail-octets>0</urpf-check-fail-octets> + <out-discard-dbcast-packets>0</out-discard-dbcast-packets> + <out-discard-dbcast-octets>0</out-discard-dbcast-octets> + <in-ip-helper-redirects-packets>0</in-ip-helper-redirects-packets> + <in-ip-helper-redirects-octets>0</in-ip-helper-redirects-octets> + </statistics> + <primary> + <oper-address>62.40.119.90</oper-address> + <creation-origin>manual</creation-origin> + </primary> + <neighbor-discovery> + <neighbor> + <ipv4-address>62.40.119.91</ipv4-address> + <oper-state>up</oper-state> + <mac-address>9c:e0:41:60:f9:2a</mac-address> + <type>dynamic</type> + <timer>3715</timer> + </neighbor> + </neighbor-discovery> + </ipv4> + <ipv6> + <oper-state>up</oper-state> + <icmp6> + <statistics> + <icmp6-in-msgs>30</icmp6-in-msgs> + <icmp6-in-errors>0</icmp6-in-errors> + <icmp6-in-dest-unreachables>0</icmp6-in-dest-unreachables> + <icmp6-in-admin-prohibs>0</icmp6-in-admin-prohibs> + <icmp6-in-time-exceeds>0</icmp6-in-time-exceeds> + <icmp6-in-parm-problems>0</icmp6-in-parm-problems> + <icmp6-in-pkt-too-bigs>0</icmp6-in-pkt-too-bigs> + <icmp6-in-echos>0</icmp6-in-echos> + <icmp6-in-echo-replies>0</icmp6-in-echo-replies> + <icmp6-in-rtr-solicits>0</icmp6-in-rtr-solicits> + <icmp6-in-rtr-advertisements>0</icmp6-in-rtr-advertisements> + <icmp6-in-nbr-solicits>14</icmp6-in-nbr-solicits> + <icmp6-in-nbr-advertisements>16</icmp6-in-nbr-advertisements> + <icmp6-in-redirects>0</icmp6-in-redirects> + <icmp6-in-grp-memb-queries>0</icmp6-in-grp-memb-queries> + <icmp6-in-grp-memb-repsonses>0</icmp6-in-grp-memb-repsonses> + <icmp6-in-grp-memb-reductions>0</icmp6-in-grp-memb-reductions> + <icmp6-out-msgs>32</icmp6-out-msgs> + <icmp6-out-errors>0</icmp6-out-errors> + <icmp6-out-dest-unreachables>0</icmp6-out-dest-unreachables> + <icmp6-out-admin-prohibs>0</icmp6-out-admin-prohibs> + <icmp6-out-time-exceeds>0</icmp6-out-time-exceeds> + <icmp6-out-parm-problems>0</icmp6-out-parm-problems> + <icmp6-out-pkt-too-bigs>0</icmp6-out-pkt-too-bigs> + <icmp6-out-echos>0</icmp6-out-echos> + <icmp6-out-echo-replies>0</icmp6-out-echo-replies> + <icmp6-out-rtr-solicits>0</icmp6-out-rtr-solicits> + <icmp6-out-rtr-advertisements>0</icmp6-out-rtr-advertisements> + <icmp6-out-nbr-solicits>18</icmp6-out-nbr-solicits> + <icmp6-out-nbr-advertisements>14</icmp6-out-nbr-advertisements> + <icmp6-out-redirects>0</icmp6-out-redirects> + <icmp6-out-grp-memb-queries>0</icmp6-out-grp-memb-queries> + <icmp6-out-grp-memb-responses>0</icmp6-out-grp-memb-responses> + <icmp6-out-grp-memb-reductions>0</icmp6-out-grp-memb-reductions> + <icmp6-out-discards>0</icmp6-out-discards> + </statistics> + </icmp6> + <link-local-address> + <oper-address>fe80::1ac3:ff:fe91:fe5a</oper-address> + <address-state>preferred</address-state> + </link-local-address> + <statistics> + <out-packets>21302</out-packets> + <out-octets>2430610</out-octets> + <out-discard-packets>0</out-discard-packets> + <out-discard-octets>0</out-discard-octets> + <in-packets>0</in-packets> + <in-octets>0</in-octets> + <urpf-check-fail-packets>0</urpf-check-fail-packets> + <urpf-check-fail-octets>0</urpf-check-fail-octets> + </statistics> + <address> + <ipv6-address>2001:799:1ab:2::41</ipv6-address> + <address-state>preferred</address-state> + <oper-address>2001:799:1ab:2::41</oper-address> + <creation-origin>manual</creation-origin> + <primary-preferred>true</primary-preferred> + </address> + </ipv6> + </interface> + </router> + <router> + <router-name>management</router-name> + <interface> + <interface-name>management</interface-name> + <if-index>1280</if-index> + <system-if-index>32768</system-if-index> + <oper-state>up</oper-state> + <protocol/> + <oper-ip-mtu>1500</oper-ip-mtu> + <creation-origin>manual</creation-origin> + <last-oper-change>2024-04-04T07:53:27.0Z</last-oper-change> + <statistics> + <ip> + <out-packets>0</out-packets> + <out-octets>0</out-octets> + <out-discard-packets>0</out-discard-packets> + <out-discard-octets>0</out-discard-octets> + <in-packets>7379</in-packets> + <in-octets>472036</in-octets> + <urpf-check-fail-packets>0</urpf-check-fail-packets> + <urpf-check-fail-octets>0</urpf-check-fail-octets> + </ip> + </statistics> + <ipv4> + <oper-state>up</oper-state> + <icmp> + <statistics> + <icmp-in-msgs>0</icmp-in-msgs> + <icmp-in-errors>0</icmp-in-errors> + <icmp-in-dest-unreachables>0</icmp-in-dest-unreachables> + <icmp-in-redirects>0</icmp-in-redirects> + <icmp-in-echos>0</icmp-in-echos> + <icmp-in-echo-replies>0</icmp-in-echo-replies> + <icmp-in-time-exceeds>0</icmp-in-time-exceeds> + <icmp-in-src-quenches>0</icmp-in-src-quenches> + <icmp-in-timestamps>0</icmp-in-timestamps> + <icmp-in-timestamp-replies>0</icmp-in-timestamp-replies> + <icmp-in-address-masks>0</icmp-in-address-masks> + <icmp-in-address-mask-replies>0</icmp-in-address-mask-replies> + <icmp-in-parm-problems>0</icmp-in-parm-problems> + <icmp-out-msgs>0</icmp-out-msgs> + <icmp-out-errors>0</icmp-out-errors> + <icmp-out-dest-unreachables>0</icmp-out-dest-unreachables> + <icmp-out-redirects>0</icmp-out-redirects> + <icmp-out-echos>0</icmp-out-echos> + <icmp-out-echo-replies>0</icmp-out-echo-replies> + <icmp-out-time-exceeds>0</icmp-out-time-exceeds> + <icmp-out-src-quenches>0</icmp-out-src-quenches> + <icmp-out-timestamps>0</icmp-out-timestamps> + <icmp-out-timestamp-replies>0</icmp-out-timestamp-replies> + <icmp-out-address-masks>0</icmp-out-address-masks> + <icmp-out-address-mask-replies>0</icmp-out-address-mask-replies> + <icmp-out-parm-problems>0</icmp-out-parm-problems> + <icmp-out-discards>0</icmp-out-discards> + </statistics> + </icmp> + <primary> + <oper-address>172.16.254.30</oper-address> + <creation-origin>manual</creation-origin> + </primary> + </ipv4> + <ipv6> + <oper-state>down</oper-state> + <down-reason>protocol-down</down-reason> + <icmp6> + <statistics> + <icmp6-in-msgs>0</icmp6-in-msgs> + <icmp6-in-errors>0</icmp6-in-errors> + <icmp6-in-dest-unreachables>0</icmp6-in-dest-unreachables> + <icmp6-in-admin-prohibs>0</icmp6-in-admin-prohibs> + <icmp6-in-time-exceeds>0</icmp6-in-time-exceeds> + <icmp6-in-parm-problems>0</icmp6-in-parm-problems> + <icmp6-in-pkt-too-bigs>0</icmp6-in-pkt-too-bigs> + <icmp6-in-echos>0</icmp6-in-echos> + <icmp6-in-echo-replies>0</icmp6-in-echo-replies> + <icmp6-in-rtr-solicits>0</icmp6-in-rtr-solicits> + <icmp6-in-rtr-advertisements>0</icmp6-in-rtr-advertisements> + <icmp6-in-nbr-solicits>0</icmp6-in-nbr-solicits> + <icmp6-in-nbr-advertisements>0</icmp6-in-nbr-advertisements> + <icmp6-in-redirects>0</icmp6-in-redirects> + <icmp6-in-grp-memb-queries>0</icmp6-in-grp-memb-queries> + <icmp6-in-grp-memb-repsonses>0</icmp6-in-grp-memb-repsonses> + <icmp6-in-grp-memb-reductions>0</icmp6-in-grp-memb-reductions> + <icmp6-out-msgs>0</icmp6-out-msgs> + <icmp6-out-errors>0</icmp6-out-errors> + <icmp6-out-dest-unreachables>0</icmp6-out-dest-unreachables> + <icmp6-out-admin-prohibs>0</icmp6-out-admin-prohibs> + <icmp6-out-time-exceeds>0</icmp6-out-time-exceeds> + <icmp6-out-parm-problems>0</icmp6-out-parm-problems> + <icmp6-out-pkt-too-bigs>0</icmp6-out-pkt-too-bigs> + <icmp6-out-echos>0</icmp6-out-echos> + <icmp6-out-echo-replies>0</icmp6-out-echo-replies> + <icmp6-out-rtr-solicits>0</icmp6-out-rtr-solicits> + <icmp6-out-rtr-advertisements>0</icmp6-out-rtr-advertisements> + <icmp6-out-nbr-solicits>0</icmp6-out-nbr-solicits> + <icmp6-out-nbr-advertisements>0</icmp6-out-nbr-advertisements> + <icmp6-out-redirects>0</icmp6-out-redirects> + <icmp6-out-grp-memb-queries>0</icmp6-out-grp-memb-queries> + <icmp6-out-grp-memb-responses>0</icmp6-out-grp-memb-responses> + <icmp6-out-grp-memb-reductions>0</icmp6-out-grp-memb-reductions> + <icmp6-out-discards>0</icmp6-out-discards> + </statistics> + </icmp6> + </ipv6> + </interface> + </router> + </state> + </data> +</rpc-reply> diff --git a/test/interface_stats/data/rt0.lon.uk.lab.office.geant.net-router-interfaces.xml b/test/interface_stats/data/rt0.lon.uk.lab.office.geant.net-router-interfaces.xml new file mode 100644 index 0000000000000000000000000000000000000000..de697cb3e214c70ac8a97848febcf5d0ffcf946a --- /dev/null +++ b/test/interface_stats/data/rt0.lon.uk.lab.office.geant.net-router-interfaces.xml @@ -0,0 +1,1028 @@ +<rpc-reply message-id="urn:uuid:726313e0-3658-472f-8ad1-6f9f287e65cd"> + <data> + <state> + <router> + <router-name>Base</router-name> + <interface> + <interface-name>system</interface-name> + <if-index>1</if-index> + <system-if-index>256</system-if-index> + <oper-state>up</oper-state> + <protocol>isis mpls rsvp pim</protocol> + <oper-ip-mtu>1500</oper-ip-mtu> + <creation-origin>manual</creation-origin> + <last-oper-change>2024-04-04T09:57:28.3Z</last-oper-change> + <statistics> + <ip> + <out-packets>0</out-packets> + <out-octets>0</out-octets> + <out-discard-packets>0</out-discard-packets> + <out-discard-octets>0</out-discard-octets> + <in-packets>0</in-packets> + <in-octets>0</in-octets> + <urpf-check-fail-packets>0</urpf-check-fail-packets> + <urpf-check-fail-octets>0</urpf-check-fail-octets> + </ip> + <mpls> + <out-packets>0</out-packets> + <out-octets>0</out-octets> + <in-packets>0</in-packets> + <in-octets>0</in-octets> + </mpls> + </statistics> + <ipv4> + <oper-state>up</oper-state> + <icmp> + <statistics> + <icmp-in-msgs>0</icmp-in-msgs> + <icmp-in-errors>0</icmp-in-errors> + <icmp-in-dest-unreachables>0</icmp-in-dest-unreachables> + <icmp-in-redirects>0</icmp-in-redirects> + <icmp-in-echos>0</icmp-in-echos> + <icmp-in-echo-replies>0</icmp-in-echo-replies> + <icmp-in-time-exceeds>0</icmp-in-time-exceeds> + <icmp-in-src-quenches>0</icmp-in-src-quenches> + <icmp-in-timestamps>0</icmp-in-timestamps> + <icmp-in-timestamp-replies>0</icmp-in-timestamp-replies> + <icmp-in-address-masks>0</icmp-in-address-masks> + <icmp-in-address-mask-replies>0</icmp-in-address-mask-replies> + <icmp-in-parm-problems>0</icmp-in-parm-problems> + <icmp-out-msgs>0</icmp-out-msgs> + <icmp-out-errors>0</icmp-out-errors> + <icmp-out-dest-unreachables>0</icmp-out-dest-unreachables> + <icmp-out-redirects>0</icmp-out-redirects> + <icmp-out-echos>0</icmp-out-echos> + <icmp-out-echo-replies>0</icmp-out-echo-replies> + <icmp-out-time-exceeds>0</icmp-out-time-exceeds> + <icmp-out-src-quenches>0</icmp-out-src-quenches> + <icmp-out-timestamps>0</icmp-out-timestamps> + <icmp-out-timestamp-replies>0</icmp-out-timestamp-replies> + <icmp-out-address-masks>0</icmp-out-address-masks> + <icmp-out-address-mask-replies>0</icmp-out-address-mask-replies> + <icmp-out-parm-problems>0</icmp-out-parm-problems> + <icmp-out-discards>0</icmp-out-discards> + </statistics> + </icmp> + <dhcp> + <statistics> + <total-rx-packets> + <received>0</received> + <malformed>0</malformed> + <untrusted>0</untrusted> + </total-rx-packets> + <total-tx-packets> + <transmitted>0</transmitted> + </total-tx-packets> + <client-packets> + <dropped>0</dropped> + <relayed>0</relayed> + <snooped>0</snooped> + </client-packets> + <server-packets> + <dropped>0</dropped> + <relayed>0</relayed> + <snooped>0</snooped> + </server-packets> + </statistics> + </dhcp> + <statistics> + <out-packets>0</out-packets> + <out-octets>0</out-octets> + <out-discard-packets>0</out-discard-packets> + <out-discard-octets>0</out-discard-octets> + <in-packets>0</in-packets> + <in-octets>0</in-octets> + <urpf-check-fail-packets>0</urpf-check-fail-packets> + <urpf-check-fail-octets>0</urpf-check-fail-octets> + <out-discard-dbcast-packets>0</out-discard-dbcast-packets> + <out-discard-dbcast-octets>0</out-discard-dbcast-octets> + <in-ip-helper-redirects-packets>0</in-ip-helper-redirects-packets> + <in-ip-helper-redirects-octets>0</in-ip-helper-redirects-octets> + </statistics> + <primary> + <oper-address>62.40.119.7</oper-address> + <creation-origin>manual</creation-origin> + </primary> + </ipv4> + <ipv6> + <oper-state>up</oper-state> + <icmp6> + <statistics> + <icmp6-in-msgs>0</icmp6-in-msgs> + <icmp6-in-errors>0</icmp6-in-errors> + <icmp6-in-dest-unreachables>0</icmp6-in-dest-unreachables> + <icmp6-in-admin-prohibs>0</icmp6-in-admin-prohibs> + <icmp6-in-time-exceeds>0</icmp6-in-time-exceeds> + <icmp6-in-parm-problems>0</icmp6-in-parm-problems> + <icmp6-in-pkt-too-bigs>0</icmp6-in-pkt-too-bigs> + <icmp6-in-echos>0</icmp6-in-echos> + <icmp6-in-echo-replies>0</icmp6-in-echo-replies> + <icmp6-in-rtr-solicits>0</icmp6-in-rtr-solicits> + <icmp6-in-rtr-advertisements>0</icmp6-in-rtr-advertisements> + <icmp6-in-nbr-solicits>0</icmp6-in-nbr-solicits> + <icmp6-in-nbr-advertisements>0</icmp6-in-nbr-advertisements> + <icmp6-in-redirects>0</icmp6-in-redirects> + <icmp6-in-grp-memb-queries>0</icmp6-in-grp-memb-queries> + <icmp6-in-grp-memb-repsonses>0</icmp6-in-grp-memb-repsonses> + <icmp6-in-grp-memb-reductions>0</icmp6-in-grp-memb-reductions> + <icmp6-out-msgs>0</icmp6-out-msgs> + <icmp6-out-errors>0</icmp6-out-errors> + <icmp6-out-dest-unreachables>0</icmp6-out-dest-unreachables> + <icmp6-out-admin-prohibs>0</icmp6-out-admin-prohibs> + <icmp6-out-time-exceeds>0</icmp6-out-time-exceeds> + <icmp6-out-parm-problems>0</icmp6-out-parm-problems> + <icmp6-out-pkt-too-bigs>0</icmp6-out-pkt-too-bigs> + <icmp6-out-echos>0</icmp6-out-echos> + <icmp6-out-echo-replies>0</icmp6-out-echo-replies> + <icmp6-out-rtr-solicits>0</icmp6-out-rtr-solicits> + <icmp6-out-rtr-advertisements>0</icmp6-out-rtr-advertisements> + <icmp6-out-nbr-solicits>0</icmp6-out-nbr-solicits> + <icmp6-out-nbr-advertisements>0</icmp6-out-nbr-advertisements> + <icmp6-out-redirects>0</icmp6-out-redirects> + <icmp6-out-grp-memb-queries>0</icmp6-out-grp-memb-queries> + <icmp6-out-grp-memb-responses>0</icmp6-out-grp-memb-responses> + <icmp6-out-grp-memb-reductions>0</icmp6-out-grp-memb-reductions> + <icmp6-out-discards>0</icmp6-out-discards> + </statistics> + </icmp6> + <statistics> + <out-packets>0</out-packets> + <out-octets>0</out-octets> + <out-discard-packets>0</out-discard-packets> + <out-discard-octets>0</out-discard-octets> + <in-packets>0</in-packets> + <in-octets>0</in-octets> + <urpf-check-fail-packets>0</urpf-check-fail-packets> + <urpf-check-fail-octets>0</urpf-check-fail-octets> + </statistics> + <address> + <ipv6-address>2001:799:1ab::7</ipv6-address> + <address-state>preferred</address-state> + <oper-address>2001:799:1ab::7</oper-address> + <creation-origin>manual</creation-origin> + <primary-preferred>true</primary-preferred> + </address> + </ipv6> + </interface> + <interface> + <interface-name>lag-1.0</interface-name> + <if-index>2</if-index> + <system-if-index>2</system-if-index> + <oper-state>up</oper-state> + <protocol>isis mpls rsvp pim</protocol> + <oper-ip-mtu>9000</oper-ip-mtu> + <creation-origin>manual</creation-origin> + <last-oper-change>2024-04-04T09:57:28.3Z</last-oper-change> + <distributed-cpu-protection> + <static-policer> + <name>ICMP_LIMIT</name> + <card>1</card> + <fp-number>1</fp-number> + <state>conform</state> + <exceed-count>0</exceed-count> + <hold-down-remain>none</hold-down-remain> + <detection-time-remain>0</detection-time-remain> + <total-exceed-count>0</total-exceed-count> + <exit-conform-state-count>0</exit-conform-state-count> + </static-policer> + </distributed-cpu-protection> + <statistics> + <ip> + <out-packets>17459420</out-packets> + <out-octets>3407393702</out-octets> + <out-discard-packets>0</out-discard-packets> + <out-discard-octets>0</out-discard-octets> + <in-packets>2958598</in-packets> + <in-octets>335785704</in-octets> + <urpf-check-fail-packets>0</urpf-check-fail-packets> + <urpf-check-fail-octets>0</urpf-check-fail-octets> + </ip> + <mpls> + <out-packets>2045</out-packets> + <out-octets>1700296</out-octets> + <in-packets>1616</in-packets> + <in-octets>1249297</in-octets> + </mpls> + </statistics> + <ipv4> + <oper-state>up</oper-state> + <icmp> + <statistics> + <icmp-in-msgs>52904</icmp-in-msgs> + <icmp-in-errors>46694</icmp-in-errors> + <icmp-in-dest-unreachables>46694</icmp-in-dest-unreachables> + <icmp-in-redirects>0</icmp-in-redirects> + <icmp-in-echos>6210</icmp-in-echos> + <icmp-in-echo-replies>0</icmp-in-echo-replies> + <icmp-in-time-exceeds>0</icmp-in-time-exceeds> + <icmp-in-src-quenches>0</icmp-in-src-quenches> + <icmp-in-timestamps>0</icmp-in-timestamps> + <icmp-in-timestamp-replies>0</icmp-in-timestamp-replies> + <icmp-in-address-masks>0</icmp-in-address-masks> + <icmp-in-address-mask-replies>0</icmp-in-address-mask-replies> + <icmp-in-parm-problems>0</icmp-in-parm-problems> + <icmp-out-msgs>6211</icmp-out-msgs> + <icmp-out-errors>1</icmp-out-errors> + <icmp-out-dest-unreachables>0</icmp-out-dest-unreachables> + <icmp-out-redirects>0</icmp-out-redirects> + <icmp-out-echos>0</icmp-out-echos> + <icmp-out-echo-replies>6210</icmp-out-echo-replies> + <icmp-out-time-exceeds>1</icmp-out-time-exceeds> + <icmp-out-src-quenches>0</icmp-out-src-quenches> + <icmp-out-timestamps>0</icmp-out-timestamps> + <icmp-out-timestamp-replies>0</icmp-out-timestamp-replies> + <icmp-out-address-masks>0</icmp-out-address-masks> + <icmp-out-address-mask-replies>0</icmp-out-address-mask-replies> + <icmp-out-parm-problems>0</icmp-out-parm-problems> + <icmp-out-discards>0</icmp-out-discards> + </statistics> + </icmp> + <dhcp> + <statistics> + <total-rx-packets> + <received>0</received> + <malformed>0</malformed> + <untrusted>0</untrusted> + </total-rx-packets> + <total-tx-packets> + <transmitted>0</transmitted> + </total-tx-packets> + <client-packets> + <dropped>0</dropped> + <relayed>0</relayed> + <snooped>0</snooped> + </client-packets> + <server-packets> + <dropped>0</dropped> + <relayed>0</relayed> + <snooped>0</snooped> + </server-packets> + </statistics> + </dhcp> + <statistics> + <out-packets>16615399</out-packets> + <out-octets>3300321263</out-octets> + <out-discard-packets>0</out-discard-packets> + <out-discard-octets>0</out-discard-octets> + <in-packets>0</in-packets> + <in-octets>0</in-octets> + <urpf-check-fail-packets>0</urpf-check-fail-packets> + <urpf-check-fail-octets>0</urpf-check-fail-octets> + <out-discard-dbcast-packets>0</out-discard-dbcast-packets> + <out-discard-dbcast-octets>0</out-discard-dbcast-octets> + <in-ip-helper-redirects-packets>0</in-ip-helper-redirects-packets> + <in-ip-helper-redirects-octets>0</in-ip-helper-redirects-octets> + </statistics> + <primary> + <oper-address>62.40.119.86</oper-address> + <creation-origin>manual</creation-origin> + </primary> + <neighbor-discovery> + <neighbor> + <ipv4-address>62.40.119.87</ipv4-address> + <oper-state>up</oper-state> + <mac-address>ae:4b:c8:46:29:72</mac-address> + <type>dynamic</type> + <timer>13163</timer> + </neighbor> + </neighbor-discovery> + </ipv4> + <ipv6> + <oper-state>up</oper-state> + <icmp6> + <statistics> + <icmp6-in-msgs>17788</icmp6-in-msgs> + <icmp6-in-errors>166</icmp6-in-errors> + <icmp6-in-dest-unreachables>166</icmp6-in-dest-unreachables> + <icmp6-in-admin-prohibs>0</icmp6-in-admin-prohibs> + <icmp6-in-time-exceeds>0</icmp6-in-time-exceeds> + <icmp6-in-parm-problems>0</icmp6-in-parm-problems> + <icmp6-in-pkt-too-bigs>0</icmp6-in-pkt-too-bigs> + <icmp6-in-echos>0</icmp6-in-echos> + <icmp6-in-echo-replies>0</icmp6-in-echo-replies> + <icmp6-in-rtr-solicits>0</icmp6-in-rtr-solicits> + <icmp6-in-rtr-advertisements>0</icmp6-in-rtr-advertisements> + <icmp6-in-nbr-solicits>1848</icmp6-in-nbr-solicits> + <icmp6-in-nbr-advertisements>15774</icmp6-in-nbr-advertisements> + <icmp6-in-redirects>0</icmp6-in-redirects> + <icmp6-in-grp-memb-queries>0</icmp6-in-grp-memb-queries> + <icmp6-in-grp-memb-repsonses>0</icmp6-in-grp-memb-repsonses> + <icmp6-in-grp-memb-reductions>0</icmp6-in-grp-memb-reductions> + <icmp6-out-msgs>17622</icmp6-out-msgs> + <icmp6-out-errors>0</icmp6-out-errors> + <icmp6-out-dest-unreachables>0</icmp6-out-dest-unreachables> + <icmp6-out-admin-prohibs>0</icmp6-out-admin-prohibs> + <icmp6-out-time-exceeds>0</icmp6-out-time-exceeds> + <icmp6-out-parm-problems>0</icmp6-out-parm-problems> + <icmp6-out-pkt-too-bigs>0</icmp6-out-pkt-too-bigs> + <icmp6-out-echos>0</icmp6-out-echos> + <icmp6-out-echo-replies>0</icmp6-out-echo-replies> + <icmp6-out-rtr-solicits>0</icmp6-out-rtr-solicits> + <icmp6-out-rtr-advertisements>0</icmp6-out-rtr-advertisements> + <icmp6-out-nbr-solicits>15774</icmp6-out-nbr-solicits> + <icmp6-out-nbr-advertisements>1848</icmp6-out-nbr-advertisements> + <icmp6-out-redirects>0</icmp6-out-redirects> + <icmp6-out-grp-memb-queries>0</icmp6-out-grp-memb-queries> + <icmp6-out-grp-memb-responses>0</icmp6-out-grp-memb-responses> + <icmp6-out-grp-memb-reductions>0</icmp6-out-grp-memb-reductions> + <icmp6-out-discards>0</icmp6-out-discards> + </statistics> + </icmp6> + <link-local-address> + <oper-address>fe80::9ee0:41ff:fe60:f929</oper-address> + <address-state>preferred</address-state> + </link-local-address> + <statistics> + <out-packets>844021</out-packets> + <out-octets>107072439</out-octets> + <out-discard-packets>0</out-discard-packets> + <out-discard-octets>0</out-discard-octets> + <in-packets>0</in-packets> + <in-octets>0</in-octets> + <urpf-check-fail-packets>0</urpf-check-fail-packets> + <urpf-check-fail-octets>0</urpf-check-fail-octets> + </statistics> + <address> + <ipv6-address>2001:799:1ab:2::3d</ipv6-address> + <address-state>preferred</address-state> + <oper-address>2001:799:1ab:2::3d</oper-address> + <creation-origin>manual</creation-origin> + <primary-preferred>true</primary-preferred> + </address> + <neighbor-discovery> + <neighbor> + <ipv6-address>fe80::ae4b:c8ff:fe46:2972</ipv6-address> + <state>reachable</state> + <is-router>true</is-router> + <mtu>9000</mtu> + <mac-address>ae:4b:c8:46:29:72</mac-address> + <type>dynamic</type> + <timer>13</timer> + </neighbor> + </neighbor-discovery> + </ipv6> + </interface> + <interface> + <interface-name>lag-2.0</interface-name> + <if-index>3</if-index> + <system-if-index>3</system-if-index> + <oper-state>up</oper-state> + <protocol>isis mpls rsvp pim</protocol> + <oper-ip-mtu>9000</oper-ip-mtu> + <creation-origin>manual</creation-origin> + <last-oper-change>2024-04-04T09:57:28.3Z</last-oper-change> + <distributed-cpu-protection> + <static-policer> + <name>ICMP_LIMIT</name> + <card>1</card> + <fp-number>1</fp-number> + <state>conform</state> + <exceed-count>0</exceed-count> + <hold-down-remain>none</hold-down-remain> + <detection-time-remain>0</detection-time-remain> + <total-exceed-count>0</total-exceed-count> + <exit-conform-state-count>0</exit-conform-state-count> + </static-policer> + </distributed-cpu-protection> + <statistics> + <ip> + <out-packets>186418</out-packets> + <out-octets>17279891</out-octets> + <out-discard-packets>0</out-discard-packets> + <out-discard-octets>0</out-discard-octets> + <in-packets>171833</in-packets> + <in-octets>13246928</in-octets> + <urpf-check-fail-packets>0</urpf-check-fail-packets> + <urpf-check-fail-octets>0</urpf-check-fail-octets> + </ip> + <mpls> + <out-packets>1788</out-packets> + <out-octets>1242133</out-octets> + <in-packets>2014</in-packets> + <in-octets>1703454</in-octets> + </mpls> + </statistics> + <ipv4> + <oper-state>up</oper-state> + <icmp> + <statistics> + <icmp-in-msgs>10</icmp-in-msgs> + <icmp-in-errors>10</icmp-in-errors> + <icmp-in-dest-unreachables>10</icmp-in-dest-unreachables> + <icmp-in-redirects>0</icmp-in-redirects> + <icmp-in-echos>0</icmp-in-echos> + <icmp-in-echo-replies>0</icmp-in-echo-replies> + <icmp-in-time-exceeds>0</icmp-in-time-exceeds> + <icmp-in-src-quenches>0</icmp-in-src-quenches> + <icmp-in-timestamps>0</icmp-in-timestamps> + <icmp-in-timestamp-replies>0</icmp-in-timestamp-replies> + <icmp-in-address-masks>0</icmp-in-address-masks> + <icmp-in-address-mask-replies>0</icmp-in-address-mask-replies> + <icmp-in-parm-problems>0</icmp-in-parm-problems> + <icmp-out-msgs>0</icmp-out-msgs> + <icmp-out-errors>0</icmp-out-errors> + <icmp-out-dest-unreachables>0</icmp-out-dest-unreachables> + <icmp-out-redirects>0</icmp-out-redirects> + <icmp-out-echos>0</icmp-out-echos> + <icmp-out-echo-replies>0</icmp-out-echo-replies> + <icmp-out-time-exceeds>0</icmp-out-time-exceeds> + <icmp-out-src-quenches>0</icmp-out-src-quenches> + <icmp-out-timestamps>0</icmp-out-timestamps> + <icmp-out-timestamp-replies>0</icmp-out-timestamp-replies> + <icmp-out-address-masks>0</icmp-out-address-masks> + <icmp-out-address-mask-replies>0</icmp-out-address-mask-replies> + <icmp-out-parm-problems>0</icmp-out-parm-problems> + <icmp-out-discards>0</icmp-out-discards> + </statistics> + </icmp> + <dhcp> + <statistics> + <total-rx-packets> + <received>0</received> + <malformed>0</malformed> + <untrusted>0</untrusted> + </total-rx-packets> + <total-tx-packets> + <transmitted>0</transmitted> + </total-tx-packets> + <client-packets> + <dropped>0</dropped> + <relayed>0</relayed> + <snooped>0</snooped> + </client-packets> + <server-packets> + <dropped>0</dropped> + <relayed>0</relayed> + <snooped>0</snooped> + </server-packets> + </statistics> + </dhcp> + <statistics> + <out-packets>164711</out-packets> + <out-octets>14799044</out-octets> + <out-discard-packets>0</out-discard-packets> + <out-discard-octets>0</out-discard-octets> + <in-packets>0</in-packets> + <in-octets>0</in-octets> + <urpf-check-fail-packets>0</urpf-check-fail-packets> + <urpf-check-fail-octets>0</urpf-check-fail-octets> + <out-discard-dbcast-packets>0</out-discard-dbcast-packets> + <out-discard-dbcast-octets>0</out-discard-dbcast-octets> + <in-ip-helper-redirects-packets>0</in-ip-helper-redirects-packets> + <in-ip-helper-redirects-octets>0</in-ip-helper-redirects-octets> + </statistics> + <primary> + <oper-address>62.40.119.91</oper-address> + <creation-origin>manual</creation-origin> + </primary> + <neighbor-discovery> + <neighbor> + <ipv4-address>62.40.119.90</ipv4-address> + <oper-state>up</oper-state> + <mac-address>18:c3:00:91:fe:5a</mac-address> + <type>dynamic</type> + <timer>2976</timer> + </neighbor> + </neighbor-discovery> + </ipv4> + <ipv6> + <oper-state>up</oper-state> + <icmp6> + <statistics> + <icmp6-in-msgs>28</icmp6-in-msgs> + <icmp6-in-errors>0</icmp6-in-errors> + <icmp6-in-dest-unreachables>0</icmp6-in-dest-unreachables> + <icmp6-in-admin-prohibs>0</icmp6-in-admin-prohibs> + <icmp6-in-time-exceeds>0</icmp6-in-time-exceeds> + <icmp6-in-parm-problems>0</icmp6-in-parm-problems> + <icmp6-in-pkt-too-bigs>0</icmp6-in-pkt-too-bigs> + <icmp6-in-echos>0</icmp6-in-echos> + <icmp6-in-echo-replies>0</icmp6-in-echo-replies> + <icmp6-in-rtr-solicits>0</icmp6-in-rtr-solicits> + <icmp6-in-rtr-advertisements>0</icmp6-in-rtr-advertisements> + <icmp6-in-nbr-solicits>15</icmp6-in-nbr-solicits> + <icmp6-in-nbr-advertisements>13</icmp6-in-nbr-advertisements> + <icmp6-in-redirects>0</icmp6-in-redirects> + <icmp6-in-grp-memb-queries>0</icmp6-in-grp-memb-queries> + <icmp6-in-grp-memb-repsonses>0</icmp6-in-grp-memb-repsonses> + <icmp6-in-grp-memb-reductions>0</icmp6-in-grp-memb-reductions> + <icmp6-out-msgs>28</icmp6-out-msgs> + <icmp6-out-errors>0</icmp6-out-errors> + <icmp6-out-dest-unreachables>0</icmp6-out-dest-unreachables> + <icmp6-out-admin-prohibs>0</icmp6-out-admin-prohibs> + <icmp6-out-time-exceeds>0</icmp6-out-time-exceeds> + <icmp6-out-parm-problems>0</icmp6-out-parm-problems> + <icmp6-out-pkt-too-bigs>0</icmp6-out-pkt-too-bigs> + <icmp6-out-echos>0</icmp6-out-echos> + <icmp6-out-echo-replies>0</icmp6-out-echo-replies> + <icmp6-out-rtr-solicits>0</icmp6-out-rtr-solicits> + <icmp6-out-rtr-advertisements>0</icmp6-out-rtr-advertisements> + <icmp6-out-nbr-solicits>13</icmp6-out-nbr-solicits> + <icmp6-out-nbr-advertisements>15</icmp6-out-nbr-advertisements> + <icmp6-out-redirects>0</icmp6-out-redirects> + <icmp6-out-grp-memb-queries>0</icmp6-out-grp-memb-queries> + <icmp6-out-grp-memb-responses>0</icmp6-out-grp-memb-responses> + <icmp6-out-grp-memb-reductions>0</icmp6-out-grp-memb-reductions> + <icmp6-out-discards>0</icmp6-out-discards> + </statistics> + </icmp6> + <link-local-address> + <oper-address>fe80::9ee0:41ff:fe60:f92a</oper-address> + <address-state>preferred</address-state> + </link-local-address> + <statistics> + <out-packets>21707</out-packets> + <out-octets>2480847</out-octets> + <out-discard-packets>0</out-discard-packets> + <out-discard-octets>0</out-discard-octets> + <in-packets>0</in-packets> + <in-octets>0</in-octets> + <urpf-check-fail-packets>0</urpf-check-fail-packets> + <urpf-check-fail-octets>0</urpf-check-fail-octets> + </statistics> + <address> + <ipv6-address>2001:799:1ab:2::42</ipv6-address> + <address-state>preferred</address-state> + <oper-address>2001:799:1ab:2::42</oper-address> + <creation-origin>manual</creation-origin> + <primary-preferred>true</primary-preferred> + </address> + </ipv6> + </interface> + <interface> + <interface-name>lag-3.0</interface-name> + <if-index>4</if-index> + <system-if-index>4</system-if-index> + <oper-state>up</oper-state> + <protocol>isis mpls rsvp pim</protocol> + <oper-ip-mtu>9000</oper-ip-mtu> + <creation-origin>manual</creation-origin> + <last-oper-change>2024-04-04T09:57:28.3Z</last-oper-change> + <distributed-cpu-protection> + <static-policer> + <name>ICMP_LIMIT</name> + <card>2</card> + <fp-number>1</fp-number> + <state>conform</state> + <exceed-count>0</exceed-count> + <hold-down-remain>none</hold-down-remain> + <detection-time-remain>0</detection-time-remain> + <total-exceed-count>0</total-exceed-count> + <exit-conform-state-count>0</exit-conform-state-count> + </static-policer> + </distributed-cpu-protection> + <statistics> + <ip> + <out-packets>373018</out-packets> + <out-octets>27092765</out-octets> + <out-discard-packets>0</out-discard-packets> + <out-discard-octets>0</out-discard-octets> + <in-packets>362600</in-packets> + <in-octets>30254556</in-octets> + <urpf-check-fail-packets>0</urpf-check-fail-packets> + <urpf-check-fail-octets>0</urpf-check-fail-octets> + </ip> + <mpls> + <out-packets>0</out-packets> + <out-octets>0</out-octets> + <in-packets>228</in-packets> + <in-octets>35044</in-octets> + </mpls> + </statistics> + <ipv4> + <oper-state>up</oper-state> + <icmp> + <statistics> + <icmp-in-msgs>0</icmp-in-msgs> + <icmp-in-errors>0</icmp-in-errors> + <icmp-in-dest-unreachables>0</icmp-in-dest-unreachables> + <icmp-in-redirects>0</icmp-in-redirects> + <icmp-in-echos>0</icmp-in-echos> + <icmp-in-echo-replies>0</icmp-in-echo-replies> + <icmp-in-time-exceeds>0</icmp-in-time-exceeds> + <icmp-in-src-quenches>0</icmp-in-src-quenches> + <icmp-in-timestamps>0</icmp-in-timestamps> + <icmp-in-timestamp-replies>0</icmp-in-timestamp-replies> + <icmp-in-address-masks>0</icmp-in-address-masks> + <icmp-in-address-mask-replies>0</icmp-in-address-mask-replies> + <icmp-in-parm-problems>0</icmp-in-parm-problems> + <icmp-out-msgs>0</icmp-out-msgs> + <icmp-out-errors>0</icmp-out-errors> + <icmp-out-dest-unreachables>0</icmp-out-dest-unreachables> + <icmp-out-redirects>0</icmp-out-redirects> + <icmp-out-echos>0</icmp-out-echos> + <icmp-out-echo-replies>0</icmp-out-echo-replies> + <icmp-out-time-exceeds>0</icmp-out-time-exceeds> + <icmp-out-src-quenches>0</icmp-out-src-quenches> + <icmp-out-timestamps>0</icmp-out-timestamps> + <icmp-out-timestamp-replies>0</icmp-out-timestamp-replies> + <icmp-out-address-masks>0</icmp-out-address-masks> + <icmp-out-address-mask-replies>0</icmp-out-address-mask-replies> + <icmp-out-parm-problems>0</icmp-out-parm-problems> + <icmp-out-discards>0</icmp-out-discards> + </statistics> + </icmp> + <dhcp> + <statistics> + <total-rx-packets> + <received>0</received> + <malformed>0</malformed> + <untrusted>0</untrusted> + </total-rx-packets> + <total-tx-packets> + <transmitted>0</transmitted> + </total-tx-packets> + <client-packets> + <dropped>0</dropped> + <relayed>0</relayed> + <snooped>0</snooped> + </client-packets> + <server-packets> + <dropped>0</dropped> + <relayed>0</relayed> + <snooped>0</snooped> + </server-packets> + </statistics> + </dhcp> + <statistics> + <out-packets>350593</out-packets> + <out-octets>24561192</out-octets> + <out-discard-packets>0</out-discard-packets> + <out-discard-octets>0</out-discard-octets> + <in-packets>0</in-packets> + <in-octets>0</in-octets> + <urpf-check-fail-packets>0</urpf-check-fail-packets> + <urpf-check-fail-octets>0</urpf-check-fail-octets> + <out-discard-dbcast-packets>0</out-discard-dbcast-packets> + <out-discard-dbcast-octets>0</out-discard-dbcast-octets> + <in-ip-helper-redirects-packets>0</in-ip-helper-redirects-packets> + <in-ip-helper-redirects-octets>0</in-ip-helper-redirects-octets> + </statistics> + <primary> + <oper-address>62.40.119.84</oper-address> + <creation-origin>manual</creation-origin> + </primary> + <neighbor-discovery> + <neighbor> + <ipv4-address>62.40.119.85</ipv4-address> + <oper-state>up</oper-state> + <mac-address>ae:4b:c8:46:29:98</mac-address> + <type>dynamic</type> + <timer>13206</timer> + </neighbor> + </neighbor-discovery> + </ipv4> + <ipv6> + <oper-state>up</oper-state> + <icmp6> + <statistics> + <icmp6-in-msgs>1000</icmp6-in-msgs> + <icmp6-in-errors>0</icmp6-in-errors> + <icmp6-in-dest-unreachables>0</icmp6-in-dest-unreachables> + <icmp6-in-admin-prohibs>0</icmp6-in-admin-prohibs> + <icmp6-in-time-exceeds>0</icmp6-in-time-exceeds> + <icmp6-in-parm-problems>0</icmp6-in-parm-problems> + <icmp6-in-pkt-too-bigs>0</icmp6-in-pkt-too-bigs> + <icmp6-in-echos>0</icmp6-in-echos> + <icmp6-in-echo-replies>0</icmp6-in-echo-replies> + <icmp6-in-rtr-solicits>0</icmp6-in-rtr-solicits> + <icmp6-in-rtr-advertisements>0</icmp6-in-rtr-advertisements> + <icmp6-in-nbr-solicits>501</icmp6-in-nbr-solicits> + <icmp6-in-nbr-advertisements>499</icmp6-in-nbr-advertisements> + <icmp6-in-redirects>0</icmp6-in-redirects> + <icmp6-in-grp-memb-queries>0</icmp6-in-grp-memb-queries> + <icmp6-in-grp-memb-repsonses>0</icmp6-in-grp-memb-repsonses> + <icmp6-in-grp-memb-reductions>0</icmp6-in-grp-memb-reductions> + <icmp6-out-msgs>1000</icmp6-out-msgs> + <icmp6-out-errors>0</icmp6-out-errors> + <icmp6-out-dest-unreachables>0</icmp6-out-dest-unreachables> + <icmp6-out-admin-prohibs>0</icmp6-out-admin-prohibs> + <icmp6-out-time-exceeds>0</icmp6-out-time-exceeds> + <icmp6-out-parm-problems>0</icmp6-out-parm-problems> + <icmp6-out-pkt-too-bigs>0</icmp6-out-pkt-too-bigs> + <icmp6-out-echos>0</icmp6-out-echos> + <icmp6-out-echo-replies>0</icmp6-out-echo-replies> + <icmp6-out-rtr-solicits>0</icmp6-out-rtr-solicits> + <icmp6-out-rtr-advertisements>0</icmp6-out-rtr-advertisements> + <icmp6-out-nbr-solicits>499</icmp6-out-nbr-solicits> + <icmp6-out-nbr-advertisements>501</icmp6-out-nbr-advertisements> + <icmp6-out-redirects>0</icmp6-out-redirects> + <icmp6-out-grp-memb-queries>0</icmp6-out-grp-memb-queries> + <icmp6-out-grp-memb-responses>0</icmp6-out-grp-memb-responses> + <icmp6-out-grp-memb-reductions>0</icmp6-out-grp-memb-reductions> + <icmp6-out-discards>0</icmp6-out-discards> + </statistics> + </icmp6> + <link-local-address> + <oper-address>fe80::9ee0:41ff:fe60:f92b</oper-address> + <address-state>preferred</address-state> + </link-local-address> + <statistics> + <out-packets>22425</out-packets> + <out-octets>2531573</out-octets> + <out-discard-packets>0</out-discard-packets> + <out-discard-octets>0</out-discard-octets> + <in-packets>0</in-packets> + <in-octets>0</in-octets> + <urpf-check-fail-packets>0</urpf-check-fail-packets> + <urpf-check-fail-octets>0</urpf-check-fail-octets> + </statistics> + <address> + <ipv6-address>2001:799:1ab:2::45</ipv6-address> + <address-state>preferred</address-state> + <oper-address>2001:799:1ab:2::45</oper-address> + <creation-origin>manual</creation-origin> + <primary-preferred>true</primary-preferred> + </address> + <neighbor-discovery> + <neighbor> + <ipv6-address>fe80::ae4b:c8ff:fe46:2998</ipv6-address> + <state>stale</state> + <is-router>true</is-router> + <mtu>9000</mtu> + <mac-address>ae:4b:c8:46:29:98</mac-address> + <type>dynamic</type> + <timer>13879</timer> + </neighbor> + </neighbor-discovery> + </ipv6> + </interface> + <interface> + <interface-name>guy</interface-name> + <if-index>6</if-index> + <system-if-index>1</system-if-index> + <oper-state>lower-layer-down</oper-state> + <protocol/> + <oper-ip-mtu>1500</oper-ip-mtu> + <creation-origin>manual</creation-origin> + <last-oper-change>2024-04-04T04:49:43.2Z</last-oper-change> + <distributed-cpu-protection> + <static-policer> + <name>ICMP_LIMIT</name> + <card>1</card> + <fp-number>2</fp-number> + <state>conform</state> + <exceed-count>0</exceed-count> + <hold-down-remain>none</hold-down-remain> + <detection-time-remain>0</detection-time-remain> + <total-exceed-count>0</total-exceed-count> + <exit-conform-state-count>0</exit-conform-state-count> + </static-policer> + </distributed-cpu-protection> + <statistics> + <ip> + <out-packets>0</out-packets> + <out-octets>0</out-octets> + <out-discard-packets>0</out-discard-packets> + <out-discard-octets>0</out-discard-octets> + <in-packets>0</in-packets> + <in-octets>0</in-octets> + <urpf-check-fail-packets>0</urpf-check-fail-packets> + <urpf-check-fail-octets>0</urpf-check-fail-octets> + </ip> + <mpls> + <out-packets>0</out-packets> + <out-octets>0</out-octets> + <in-packets>0</in-packets> + <in-octets>0</in-octets> + </mpls> + </statistics> + <ipv4> + <oper-state>down</oper-state> + <down-reason>port-down</down-reason> + <icmp> + <statistics> + <icmp-in-msgs>0</icmp-in-msgs> + <icmp-in-errors>0</icmp-in-errors> + <icmp-in-dest-unreachables>0</icmp-in-dest-unreachables> + <icmp-in-redirects>0</icmp-in-redirects> + <icmp-in-echos>0</icmp-in-echos> + <icmp-in-echo-replies>0</icmp-in-echo-replies> + <icmp-in-time-exceeds>0</icmp-in-time-exceeds> + <icmp-in-src-quenches>0</icmp-in-src-quenches> + <icmp-in-timestamps>0</icmp-in-timestamps> + <icmp-in-timestamp-replies>0</icmp-in-timestamp-replies> + <icmp-in-address-masks>0</icmp-in-address-masks> + <icmp-in-address-mask-replies>0</icmp-in-address-mask-replies> + <icmp-in-parm-problems>0</icmp-in-parm-problems> + <icmp-out-msgs>0</icmp-out-msgs> + <icmp-out-errors>0</icmp-out-errors> + <icmp-out-dest-unreachables>0</icmp-out-dest-unreachables> + <icmp-out-redirects>0</icmp-out-redirects> + <icmp-out-echos>0</icmp-out-echos> + <icmp-out-echo-replies>0</icmp-out-echo-replies> + <icmp-out-time-exceeds>0</icmp-out-time-exceeds> + <icmp-out-src-quenches>0</icmp-out-src-quenches> + <icmp-out-timestamps>0</icmp-out-timestamps> + <icmp-out-timestamp-replies>0</icmp-out-timestamp-replies> + <icmp-out-address-masks>0</icmp-out-address-masks> + <icmp-out-address-mask-replies>0</icmp-out-address-mask-replies> + <icmp-out-parm-problems>0</icmp-out-parm-problems> + <icmp-out-discards>0</icmp-out-discards> + </statistics> + </icmp> + <dhcp> + <statistics> + <total-rx-packets> + <received>0</received> + <malformed>0</malformed> + <untrusted>0</untrusted> + </total-rx-packets> + <total-tx-packets> + <transmitted>0</transmitted> + </total-tx-packets> + <client-packets> + <dropped>0</dropped> + <relayed>0</relayed> + <snooped>0</snooped> + </client-packets> + <server-packets> + <dropped>0</dropped> + <relayed>0</relayed> + <snooped>0</snooped> + </server-packets> + </statistics> + </dhcp> + <statistics> + <out-packets>0</out-packets> + <out-octets>0</out-octets> + <out-discard-packets>0</out-discard-packets> + <out-discard-octets>0</out-discard-octets> + <in-packets>0</in-packets> + <in-octets>0</in-octets> + <urpf-check-fail-packets>0</urpf-check-fail-packets> + <urpf-check-fail-octets>0</urpf-check-fail-octets> + <out-discard-dbcast-packets>0</out-discard-dbcast-packets> + <out-discard-dbcast-octets>0</out-discard-dbcast-octets> + <in-ip-helper-redirects-packets>0</in-ip-helper-redirects-packets> + <in-ip-helper-redirects-octets>0</in-ip-helper-redirects-octets> + </statistics> + <primary> + <oper-address>10.211.101.1</oper-address> + <creation-origin>manual</creation-origin> + </primary> + </ipv4> + <ipv6> + <oper-state>down</oper-state> + <down-reason>protocol-down</down-reason> + <icmp6> + <statistics> + <icmp6-in-msgs>0</icmp6-in-msgs> + <icmp6-in-errors>0</icmp6-in-errors> + <icmp6-in-dest-unreachables>0</icmp6-in-dest-unreachables> + <icmp6-in-admin-prohibs>0</icmp6-in-admin-prohibs> + <icmp6-in-time-exceeds>0</icmp6-in-time-exceeds> + <icmp6-in-parm-problems>0</icmp6-in-parm-problems> + <icmp6-in-pkt-too-bigs>0</icmp6-in-pkt-too-bigs> + <icmp6-in-echos>0</icmp6-in-echos> + <icmp6-in-echo-replies>0</icmp6-in-echo-replies> + <icmp6-in-rtr-solicits>0</icmp6-in-rtr-solicits> + <icmp6-in-rtr-advertisements>0</icmp6-in-rtr-advertisements> + <icmp6-in-nbr-solicits>0</icmp6-in-nbr-solicits> + <icmp6-in-nbr-advertisements>0</icmp6-in-nbr-advertisements> + <icmp6-in-redirects>0</icmp6-in-redirects> + <icmp6-in-grp-memb-queries>0</icmp6-in-grp-memb-queries> + <icmp6-in-grp-memb-repsonses>0</icmp6-in-grp-memb-repsonses> + <icmp6-in-grp-memb-reductions>0</icmp6-in-grp-memb-reductions> + <icmp6-out-msgs>0</icmp6-out-msgs> + <icmp6-out-errors>0</icmp6-out-errors> + <icmp6-out-dest-unreachables>0</icmp6-out-dest-unreachables> + <icmp6-out-admin-prohibs>0</icmp6-out-admin-prohibs> + <icmp6-out-time-exceeds>0</icmp6-out-time-exceeds> + <icmp6-out-parm-problems>0</icmp6-out-parm-problems> + <icmp6-out-pkt-too-bigs>0</icmp6-out-pkt-too-bigs> + <icmp6-out-echos>0</icmp6-out-echos> + <icmp6-out-echo-replies>0</icmp6-out-echo-replies> + <icmp6-out-rtr-solicits>0</icmp6-out-rtr-solicits> + <icmp6-out-rtr-advertisements>0</icmp6-out-rtr-advertisements> + <icmp6-out-nbr-solicits>0</icmp6-out-nbr-solicits> + <icmp6-out-nbr-advertisements>0</icmp6-out-nbr-advertisements> + <icmp6-out-redirects>0</icmp6-out-redirects> + <icmp6-out-grp-memb-queries>0</icmp6-out-grp-memb-queries> + <icmp6-out-grp-memb-responses>0</icmp6-out-grp-memb-responses> + <icmp6-out-grp-memb-reductions>0</icmp6-out-grp-memb-reductions> + <icmp6-out-discards>0</icmp6-out-discards> + </statistics> + </icmp6> + <statistics> + <out-packets>0</out-packets> + <out-octets>0</out-octets> + <out-discard-packets>0</out-discard-packets> + <out-discard-octets>0</out-discard-octets> + <in-packets>0</in-packets> + <in-octets>0</in-octets> + <urpf-check-fail-packets>0</urpf-check-fail-packets> + <urpf-check-fail-octets>0</urpf-check-fail-octets> + </statistics> + </ipv6> + </interface> + </router> + <router> + <router-name>management</router-name> + <interface> + <interface-name>management</interface-name> + <if-index>1280</if-index> + <system-if-index>32768</system-if-index> + <oper-state>up</oper-state> + <protocol/> + <oper-ip-mtu>1500</oper-ip-mtu> + <creation-origin>manual</creation-origin> + <last-oper-change>2024-04-04T12:54:59.1Z</last-oper-change> + <statistics> + <ip> + <out-packets>0</out-packets> + <out-octets>0</out-octets> + <out-discard-packets>0</out-discard-packets> + <out-discard-octets>0</out-discard-octets> + <in-packets>7475</in-packets> + <in-octets>493198</in-octets> + <urpf-check-fail-packets>0</urpf-check-fail-packets> + <urpf-check-fail-octets>0</urpf-check-fail-octets> + </ip> + </statistics> + <ipv4> + <oper-state>up</oper-state> + <icmp> + <statistics> + <icmp-in-msgs>0</icmp-in-msgs> + <icmp-in-errors>0</icmp-in-errors> + <icmp-in-dest-unreachables>0</icmp-in-dest-unreachables> + <icmp-in-redirects>0</icmp-in-redirects> + <icmp-in-echos>0</icmp-in-echos> + <icmp-in-echo-replies>0</icmp-in-echo-replies> + <icmp-in-time-exceeds>0</icmp-in-time-exceeds> + <icmp-in-src-quenches>0</icmp-in-src-quenches> + <icmp-in-timestamps>0</icmp-in-timestamps> + <icmp-in-timestamp-replies>0</icmp-in-timestamp-replies> + <icmp-in-address-masks>0</icmp-in-address-masks> + <icmp-in-address-mask-replies>0</icmp-in-address-mask-replies> + <icmp-in-parm-problems>0</icmp-in-parm-problems> + <icmp-out-msgs>0</icmp-out-msgs> + <icmp-out-errors>0</icmp-out-errors> + <icmp-out-dest-unreachables>0</icmp-out-dest-unreachables> + <icmp-out-redirects>0</icmp-out-redirects> + <icmp-out-echos>0</icmp-out-echos> + <icmp-out-echo-replies>0</icmp-out-echo-replies> + <icmp-out-time-exceeds>0</icmp-out-time-exceeds> + <icmp-out-src-quenches>0</icmp-out-src-quenches> + <icmp-out-timestamps>0</icmp-out-timestamps> + <icmp-out-timestamp-replies>0</icmp-out-timestamp-replies> + <icmp-out-address-masks>0</icmp-out-address-masks> + <icmp-out-address-mask-replies>0</icmp-out-address-mask-replies> + <icmp-out-parm-problems>0</icmp-out-parm-problems> + <icmp-out-discards>0</icmp-out-discards> + </statistics> + </icmp> + <primary> + <oper-address>172.16.254.32</oper-address> + <creation-origin>manual</creation-origin> + </primary> + </ipv4> + <ipv6> + <oper-state>down</oper-state> + <down-reason>protocol-down</down-reason> + <icmp6> + <statistics> + <icmp6-in-msgs>0</icmp6-in-msgs> + <icmp6-in-errors>0</icmp6-in-errors> + <icmp6-in-dest-unreachables>0</icmp6-in-dest-unreachables> + <icmp6-in-admin-prohibs>0</icmp6-in-admin-prohibs> + <icmp6-in-time-exceeds>0</icmp6-in-time-exceeds> + <icmp6-in-parm-problems>0</icmp6-in-parm-problems> + <icmp6-in-pkt-too-bigs>0</icmp6-in-pkt-too-bigs> + <icmp6-in-echos>0</icmp6-in-echos> + <icmp6-in-echo-replies>0</icmp6-in-echo-replies> + <icmp6-in-rtr-solicits>0</icmp6-in-rtr-solicits> + <icmp6-in-rtr-advertisements>0</icmp6-in-rtr-advertisements> + <icmp6-in-nbr-solicits>0</icmp6-in-nbr-solicits> + <icmp6-in-nbr-advertisements>0</icmp6-in-nbr-advertisements> + <icmp6-in-redirects>0</icmp6-in-redirects> + <icmp6-in-grp-memb-queries>0</icmp6-in-grp-memb-queries> + <icmp6-in-grp-memb-repsonses>0</icmp6-in-grp-memb-repsonses> + <icmp6-in-grp-memb-reductions>0</icmp6-in-grp-memb-reductions> + <icmp6-out-msgs>0</icmp6-out-msgs> + <icmp6-out-errors>0</icmp6-out-errors> + <icmp6-out-dest-unreachables>0</icmp6-out-dest-unreachables> + <icmp6-out-admin-prohibs>0</icmp6-out-admin-prohibs> + <icmp6-out-time-exceeds>0</icmp6-out-time-exceeds> + <icmp6-out-parm-problems>0</icmp6-out-parm-problems> + <icmp6-out-pkt-too-bigs>0</icmp6-out-pkt-too-bigs> + <icmp6-out-echos>0</icmp6-out-echos> + <icmp6-out-echo-replies>0</icmp6-out-echo-replies> + <icmp6-out-rtr-solicits>0</icmp6-out-rtr-solicits> + <icmp6-out-rtr-advertisements>0</icmp6-out-rtr-advertisements> + <icmp6-out-nbr-solicits>0</icmp6-out-nbr-solicits> + <icmp6-out-nbr-advertisements>0</icmp6-out-nbr-advertisements> + <icmp6-out-redirects>0</icmp6-out-redirects> + <icmp6-out-grp-memb-queries>0</icmp6-out-grp-memb-queries> + <icmp6-out-grp-memb-responses>0</icmp6-out-grp-memb-responses> + <icmp6-out-grp-memb-reductions>0</icmp6-out-grp-memb-reductions> + <icmp6-out-discards>0</icmp6-out-discards> + </statistics> + </icmp6> + </ipv6> + </interface> + </router> + </state> + </data> +</rpc-reply> diff --git a/test/interface_stats/test_interface_stats.py b/test/interface_stats/test_interface_stats.py index bb620af89de628eb483032171a0af2e89a897db0..d87cfc2c7fed7fba65fafccaa2bf6735241148f0 100644 --- a/test/interface_stats/test_interface_stats.py +++ b/test/interface_stats/test_interface_stats.py @@ -1,5 +1,3 @@ -import itertools -import re from datetime import datetime from unittest.mock import Mock, call, patch @@ -7,12 +5,12 @@ from brian_polling_manager import influx import jsonschema import pytest from brian_polling_manager.interface_stats import cli -from brian_polling_manager.interface_stats.vendors import Vendor, common, juniper, nokia +from brian_polling_manager.interface_stats.vendors import Vendor, common from lxml import etree -from ncclient.operations.rpc import RPCReply +import ncclient.manager -def test_sanity_check_snapshot_data(polled_interfaces, all_juniper_routers): +def test_sanity_check_juniper_snapshot_data(polled_interfaces, all_juniper_routers): """ verify that all routers with interfaces to be polled are in the test data set @@ -22,37 +20,12 @@ def test_sanity_check_snapshot_data(polled_interfaces, all_juniper_routers): assert len(missing_routers) == 0 -def test_verify_all_interfaces_present(juniper_router_fqdn, polled_interfaces): - """ - verify that all the interfaces we expect to poll - are available in the netconf data - compares a snapshot of all netconf docs with a - a snapshot of inventory /poller/interfaces - (the snapshots were all taken around the same time) - """ - - def _is_enabled(ifc_name, ifc_doc): - m = re.match(r"^([^\.]+)\.?.*", ifc_name) - assert m # sanity: should never fail - phy = ifc_doc.xpath( - f'//interface-information/physical-interface[normalize-space(name)="{m.group(1)}"]' - )[0] - admin_status = phy.xpath("./admin-status/text()")[0].strip() - oper_status = phy.xpath("./oper-status/text()")[0].strip() - - return admin_status == "up" and oper_status == "up" - - if juniper_router_fqdn not in polled_interfaces: - pytest.skip(f"{juniper_router_fqdn} has no expected polled interfaces") - - doc = cli.get_netconf(juniper_router_fqdn, vendor=Vendor.JUNIPER, ssh_params={}) - phy = juniper._physical_interface_counters(doc) - log = juniper._logical_interface_counters(doc) - interfaces = set(x["name"] for x in itertools.chain(phy, log)) - missing_interfaces = polled_interfaces[juniper_router_fqdn] - interfaces - for ifc_name in missing_interfaces: - # verify that any missing interfaces are admin/oper disabled - assert not _is_enabled(ifc_name, doc) +def test_sanity_check_nokia_snapshot_data(all_nokia_routers): + # TODO: update this once we have Nokia routers in inventory provider + assert set(all_nokia_routers) == { + "rt0.ams.nl.lab.office.geant.net", + "rt0.lon.uk.lab.office.geant.net", + } class TestParseCounters: @@ -106,199 +79,6 @@ class TestParseCounters: assert "found more than one ./a" in record.message -@pytest.fixture -def juniper_router_doc_containing_every_field(): - doc = """\ -<interface-information> - <physical-interface> - <name>ae12</name> - <admin-status>up</admin-status> - <oper-status>up</oper-status> - <traffic-statistics> - <input-bytes>1</input-bytes> - <input-packets>2</input-packets> - <output-bytes>3</output-bytes> - <output-packets>4</output-packets> - <ipv6-transit-statistics> - <input-bytes>5</input-bytes> - <input-packets>6</input-packets> - <output-bytes>7</output-bytes> - <output-packets>8</output-packets> - </ipv6-transit-statistics> - </traffic-statistics> - <input-error-list> - <input-errors>11</input-errors> - <input-discards>12</input-discards> - <input-fifo-errors>13</input-fifo-errors> - <input-drops>14</input-drops> - <framing-errors>15</framing-errors> - <input-resource-errors>16</input-resource-errors> - </input-error-list> - <output-error-list> - <output-errors>21</output-errors> - <output-drops>22</output-drops> - <output-resource-errors>23</output-resource-errors> - <output-fifo-errors>24</output-fifo-errors> - <output-collisions>25</output-collisions> - </output-error-list> - <ethernet-mac-statistics> - <input-crc-errors>31</input-crc-errors> - <output-crc-errors>32</output-crc-errors> - <input-total-errors>33</input-total-errors> - <output-total-errors>34</output-total-errors> - </ethernet-mac-statistics> - <ethernet-pcs-statistics> - <bit-error-seconds>41</bit-error-seconds> - <errored-blocks-seconds>42</errored-blocks-seconds> - </ethernet-pcs-statistics> - <logical-interface> - <name>ae12.1</name> - <traffic-statistics> - <input-bytes>51</input-bytes> - <input-packets>52</input-packets> - <output-bytes>53</output-bytes> - <output-packets>54</output-packets> - <ipv6-transit-statistics> - <input-bytes>55</input-bytes> - <input-packets>56</input-packets> - <output-bytes>57</output-bytes> - <output-packets>58</output-packets> - </ipv6-transit-statistics> - </traffic-statistics> - </logical-interface> - </physical-interface> -</interface-information> -""" - return etree.fromstring(doc) - - -def test_physical_interface_counters(juniper_router_doc_containing_every_field): - result = list( - juniper._physical_interface_counters(juniper_router_doc_containing_every_field) - ) - assert len(result) == 1 - assert result[0] == { - "name": "ae12", - "brian": { - "ingressOctets": 1, - "ingressPackets": 2, - "egressOctets": 3, - "egressPackets": 4, - "ingressOctetsv6": 5, - "ingressPacketsv6": 6, - "egressOctetsv6": 7, - "egressPacketsv6": 8, - "ingressErrors": 11, - "ingressDiscards": 12, - "egressErrors": 21, - }, - "errors": { - "input_discards": 12, - "input_fifo_errors": 13, - "input_drops": 14, - "input_framing_errors": 15, - "input_resource_errors": 16, - "output_drops": 22, - "output_resource_errors": 23, - "output_fifo_errors": 24, - "output_collisions": 25, - "input_crc_errors": 31, - "output_crc_errors": 32, - "input_total_errors": 33, - "output_total_errors": 34, - "bit_error_seconds": 41, - "errored_blocks_seconds": 42, - }, - } - - -def test_logical_interface_counters(juniper_router_doc_containing_every_field): - result = list( - juniper._logical_interface_counters(juniper_router_doc_containing_every_field) - ) - assert len(result) == 1 - assert result[0] == { - "name": "ae12.1", - "brian": { - "ingressOctets": 51, - "ingressPackets": 52, - "egressOctets": 53, - "egressPackets": 54, - "ingressOctetsv6": 55, - "ingressPacketsv6": 56, - "egressOctetsv6": 57, - "egressPacketsv6": 58, - }, - } - - -def test_juniper_router_docs_do_not_generate_errors(juniper_router_fqdn, caplog): - doc = cli.get_netconf(juniper_router_fqdn, ssh_params=None, vendor=Vendor.JUNIPER) - counters = list(juniper.interface_counters(doc)) - assert counters - assert not [r for r in caplog.records if r.levelname in ("ERROR", "WARNING")] - - -def test_nokia_router_docs_do_not_generate_errors(nokia_router_fqdn, caplog): - doc = cli.get_netconf(nokia_router_fqdn, ssh_params=None, vendor=Vendor.NOKIA) - counters = list(nokia.interface_counters(doc)) - assert counters - assert not [r for r in caplog.records if r.levelname in ("ERROR", "WARNING")] - - -def test_validate_interface_schema_for_all_juniper_routers( - juniper_router_fqdn, data_dir -): - doc = juniper.get_netconf_interface_info_from_source_dir( - juniper_router_fqdn, data_dir - ) - interfaces = list(juniper._physical_interface_counters(doc)) - assert interfaces - for ifc in interfaces: - jsonschema.validate(ifc, common.PHYSICAL_INTERFACE_COUNTER_SCHEMA) - - interfaces = list(juniper._logical_interface_counters(doc)) - assert interfaces - for ifc in interfaces: - jsonschema.validate(ifc, common.LOGICAL_INTERFACE_COUNTER_SCHEMA) - - -def test_validate_generate_points_for_all_juniper_routers( - juniper_router_fqdn, data_dir -): - doc = juniper.get_netconf_interface_info_from_source_dir( - juniper_router_fqdn, data_dir - ) - interfaces = list(juniper.interface_counters(doc)) - assert interfaces - - bpoints = list( - common.brian_points( - juniper_router_fqdn, - interfaces, - timestamp=datetime.now(), - measurement_name="blah", - ) - ) - assert bpoints - for point in bpoints: - jsonschema.validate(point, influx.INFLUX_POINT) - jsonschema.validate(point["fields"], common.BRIAN_POINT_FIELDS_SCHEMA) - - epoints = list( - common.error_points( - juniper_router_fqdn, - interfaces, - timestamp=datetime.now(), - measurement_name="blah", - ) - ) - assert epoints - for point in epoints: - jsonschema.validate(point, influx.INFLUX_POINT) - jsonschema.validate(point["fields"], common.ERROR_POINT_FIELDS_SCHEMA) - - def test_brian_point_counters(): now = datetime.now() points = list( @@ -379,7 +159,9 @@ def test_no_error_point_counters(): @patch.object(cli, "write_points") -def test_main_for_all_juniper_routers(write_points, all_juniper_routers): +def test_main_for_all_juniper_routers( + write_points, mocked_get_netconf, all_juniper_routers +): config = { "juniper": {"some": "params"}, "influx": { @@ -413,40 +195,6 @@ def test_main_for_all_juniper_routers(write_points, all_juniper_routers): assert total_points > 0 -class TestGetJuniperNetConf: - RAW_RESPONSE_FILE = "raw-response-sample.xml" - - @pytest.fixture(autouse=True) - def mocked_rpc(self, data_dir): - raw_response = data_dir.joinpath(self.RAW_RESPONSE_FILE).read_text() - with patch.object(juniper, "_rpc", return_value=RPCReply(raw_response)) as mock: - yield mock - - @pytest.fixture(autouse=True) - def unmock_get_netconf(self, mocked_juniper_get_netconf_interface_info): - mocked_juniper_get_netconf_interface_info.stop() - - def test_calls_rpc_with_params(self, mocked_rpc): - router_name = "some-router" - cli.get_netconf( - router_name, vendor=Vendor.JUNIPER, ssh_params={"some": "param"} - ) - - call_args = mocked_rpc.call_args - assert call_args[0][0] == router_name - assert call_args[1]["ssh_params"] == {"some": "param"} - assert call_args[1]["command"].tag == "get-interface-information" - - def test_converts_rpc_response_to_xml(self): - router_name = "some-router" - - doc = cli.get_netconf( - router_name, vendor=Vendor.JUNIPER, ssh_params={"some": "param"} - ) - - assert doc.tag == "rpc-reply" - - @pytest.fixture def load_interfaces(): return Mock( @@ -515,17 +263,13 @@ def test_write_points_to_stdout(): ] -@pytest.mark.parametrize( - "hostname", ["rt0.lon.uk.lab.office.geant.net", "rt0.ams.nl.lab.office.geant.net"] -) -def test_nokia_counters(hostname): - """ - quick poc - :return: - """ - doc = cli.get_netconf(hostname, vendor=Vendor.NOKIA, ssh_params={}) - counters = list(nokia.interface_counters(doc)) - assert counters - for counter in counters: - jsonschema.validate(counter, common.NOKIA_INTERFACE_COUNTER_SCHEMA) - print(len(counters)) +@patch.object(ncclient.manager, "connect") +def test_netconf_connect(connect): + with common.netconf_connect( + hostname="some.router", ssh_params={"ssh": "param"}, other="more_params" + ): + pass + + assert connect.call_args == call( + host="some.router", ssh="param", other="more_params", port=830 + ) diff --git a/test/interface_stats/test_interface_stats_e2e.py b/test/interface_stats/test_interface_stats_e2e.py index fa82c5ad0309d3b2498eb85e75f427796a3e8930..26c526f5ff2a3563cbb9a4dd8a0d7f36b3757f72 100644 --- a/test/interface_stats/test_interface_stats_e2e.py +++ b/test/interface_stats/test_interface_stats_e2e.py @@ -11,7 +11,7 @@ from unittest.mock import patch from brian_polling_manager.influx import influx_client from brian_polling_manager.interface_stats.vendors import common from click.testing import CliRunner -from typing import Any, Dict +from typing import Any, Dict, Optional, Set from brian_polling_manager.interface_stats import cli import concurrent.futures import pytest @@ -31,10 +31,29 @@ def free_host_port(): @pytest.fixture -def testenv_containers(testenv_compose_params: Dict[str, Any]): +def influx_params(free_host_port): + return { + "hostname": "localhost", + "port": free_host_port, + "database": "testdb", + "username": "bogus-user", + "password": "bogus-password", + "ssl": False, + } + + +@pytest.fixture +def influx_container(influx_compose_params: Dict[str, Any]): + # PK: we may want to make this a session scoped fixture to reduce test setup time. + # However, we then have to make sure to clean up the database after every test + # and I couldn't quickly get the admin user properly authorized to do this (which is + # weird). Together with the fact that we only have two tests running against a + # container and the expected speed-up is limited, I decided to let this fixture be + # function scoped for now + with run_compose( - compose_filename=testenv_compose_params["compose_filename"], - container_names=[testenv_compose_params["container"]], + compose_filename=influx_compose_params["compose_filename"], + container_names=[influx_compose_params["container"]], ): # wait here until the caller context finishes yield @@ -66,50 +85,32 @@ def app_config_filename(app_config_params: Dict[str, Any]): @pytest.fixture -def brian_influx_container_params(app_config_params: Dict[str, Any]): - +def brian_influx_container_params(influx_params: Dict[str, Any]): container_name = f"brian-influxdb-{random.randint(1000, 10000)}" - - # sanity (using same container for both) - assert ( - app_config_params["influx"]["brian-counters"]["port"] - == app_config_params["influx"]["error-counters"]["port"] - ) - assert ( - app_config_params["influx"]["brian-counters"]["hostname"] - == app_config_params["influx"]["error-counters"]["hostname"] - ) - - influx_params = app_config_params["influx"]["brian-counters"] - - with tempfile.TemporaryDirectory() as influx_data_dirname: - os.chmod(influx_data_dirname, 0o777) - yield { - "image": "bitnami/influxdb:1.8.5-debian-10-r197", - "user": f"{os.getuid()}", - "container_name": container_name, - # this image serves on 2222 - "ports": [f'{influx_params["port"]}:8086'], - "environment": { - "INFLUXDB_HTTP_AUTH_ENABLED": "true", - "INFLUXDB_ADMIN_USER": "admin123", - "INFLUXDB_ADMIN_USER_PASSWORD": "secret", - "INFLUXDB_USER": influx_params["username"], - "INFLUXDB_USER_PASSWORD": influx_params["password"], - "INFLUXDB_DB": influx_params["database"], # no dashes!! - "INFLUXDB_LOGGING_LEVEL": "debug", - }, - "healthcheck": { - "interval": "2s", - "timeout": "2s", - "retries": 2, - "test": ["CMD", "curl", "-f", "http://localhost:8086/ping"], - }, - } + return { + "image": "bitnami/influxdb:1.8.5-debian-10-r197", + "container_name": container_name, + "ports": [f'{influx_params["port"]}:8086'], + "environment": { + "INFLUXDB_HTTP_AUTH_ENABLED": "true", + "INFLUXDB_ADMIN_USER": "admin123", + "INFLUXDB_ADMIN_USER_PASSWORD": "secret", + "INFLUXDB_USER": influx_params["username"], + "INFLUXDB_USER_PASSWORD": influx_params["password"], + "INFLUXDB_DB": influx_params["database"], # no dashes!! + "INFLUXDB_LOGGING_LEVEL": "debug", + }, + "healthcheck": { + "interval": "2s", + "timeout": "2s", + "retries": 2, + "test": ["CMD", "curl", "-f", "http://localhost:8086/ping"], + }, + } @pytest.fixture -def testenv_compose_params(brian_influx_container_params: Dict[str, Any]): +def influx_compose_params(brian_influx_container_params: Dict[str, Any]): compose_config = { "version": "3.8", "services": { @@ -212,12 +213,7 @@ def test_config_file_validation(app_config_filename: str): @pytest.fixture -def app_config_params(free_host_port, data_dir): - - testenv_dbname = "testdb" # can't contain dashes - testenv_username = "bogus-user" - testenv_password = "bogus-password" - +def app_config_params(influx_params): with tempfile.NamedTemporaryFile() as f: yield { "juniper": { @@ -238,31 +234,18 @@ def app_config_params(free_host_port, data_dir): }, "influx": { "brian-counters": { - "hostname": "localhost", - "port": free_host_port, - "database": testenv_dbname, + **influx_params, "measurement": "testenv_brian_counters", - "username": testenv_username, - "password": testenv_password, - "ssl": False, }, "error-counters": { - "hostname": "localhost", - "port": free_host_port, - "database": testenv_dbname, + **influx_params, "measurement": "testenv_error_counters", - "username": testenv_username, - "password": testenv_password, - "ssl": False, }, }, } -@pytest.mark.parametrize( - "output", - iter(cli.OutputMethod), -) +@pytest.mark.parametrize("output", iter(cli.OutputMethod)) @patch.object(cli, "setup_logging") @patch.object(cli, "main", return_value=0) def test_cli_output_option( @@ -282,15 +265,65 @@ def test_cli_output_option( assert main.call_args[1]["output"] == output +def verify_influx_content( + influx_config: dict, + schema: dict, + only: Optional[Set[str]] = None, + exclude: Optional[Set[str]] = None, +): + """For each field in the ``schema``, verify there is at least one point with that + field populated in influx. + + :param influx_config: an influx config with connection info and a ``measurement`` + key + :param schema: either common.ERROR_POINT_FIELDS_SCHEMA or + common.BRIAN_POINT_FIELDS_SCHEMA + :param only: Optional. Only test for a subset of the keys in the schema. Do not use + together with ``exclude`` + :param exclude: Optional. Exclude a subset from the keys in the schema to test for. + Do not use together with ``only`` + """ + # for each expected field, verify that there's + # at least one point with that field populated + + assert ( + schema is common.ERROR_POINT_FIELDS_SCHEMA + or schema is common.BRIAN_POINT_FIELDS_SCHEMA + ) + expected_fields = schema["properties"].keys() + + if only is not None and exclude is not None: + raise ValueError("do not supply both 'only' and 'exclude' argument") + + if only is not None: + assert only.issubset(expected_fields) + expected_fields = only + elif exclude is not None: + assert exclude.issubset(expected_fields) + expected_fields -= exclude + + with contextlib.closing(influx_client(influx_config)) as client: + query = f'select count(*) from {influx_config["measurement"]}' + counts = next( + client.query(query).get_points(measurement=influx_config["measurement"]) + ) + + expected_count_fields = {f"count_{n}" for n in expected_fields} + + assert expected_count_fields == set(counts.keys()) - {"time"} + assert all(counts[_f] > 0 for _f in expected_count_fields) + + @pytest.mark.skipif( not _use_docker_compose(), reason="docker compose not found or disabled" ) @patch.object(cli, "setup_logging") -def test_e2e( +def test_e2e_juniper( unused_setup_logging, + mocked_get_netconf, app_config_params: Dict[str, Any], app_config_filename: str, - testenv_containers: None, + influx_container: None, all_juniper_routers, ): """ @@ -308,34 +341,57 @@ def test_e2e( result = runner.invoke(cli.cli, cli_args) assert result.exit_code == 0, str(result) - def verify_influx_content(influx_config, expected_fields): - # for each expected field, verify that there's - # at least one point with that field populated + verify_influx_content( + influx_config=app_config_params["influx"]["brian-counters"], + schema=common.BRIAN_POINT_FIELDS_SCHEMA, + ) - with contextlib.closing(influx_client(influx_config)) as client: - query = f'select count(*) from {influx_config["measurement"]}' - counts = next( - client.query(query).get_points(measurement=influx_config["measurement"]) - ) + verify_influx_content( + influx_config=app_config_params["influx"]["error-counters"], + schema=common.ERROR_POINT_FIELDS_SCHEMA, + exclude={"output_discards"}, + ) - expected_count_fields = {f"count_{n}" for n in expected_fields} - assert expected_count_fields.issubset(counts.keys()) - assert all(counts[_f] > 0 for _f in expected_count_fields) +@pytest.mark.skipif( + not _use_docker_compose(), reason="docker compose not found or disabled" +) +@patch.object(cli, "setup_logging") +def test_e2e_nokia( + unused_setup_logging, + mocked_get_netconf, + app_config_params: Dict[str, Any], + app_config_filename: str, + influx_container: None, + all_nokia_routers, +): + """ + load all router interfaces into a tmp influx container, check that + all potential counter fields are populated + """ - def _expected_fields_from_schema(schema): - assert ( - schema == common.ERROR_POINT_FIELDS_SCHEMA - or schema == common.BRIAN_POINT_FIELDS_SCHEMA - ) - return schema["properties"].keys() + cli_args = [ + "--config", + app_config_filename, + "--nokia", + *all_nokia_routers, + ] + runner = CliRunner() + result = runner.invoke(cli.cli, cli_args) + assert result.exit_code == 0, str(result) verify_influx_content( influx_config=app_config_params["influx"]["brian-counters"], - expected_fields=_expected_fields_from_schema(common.BRIAN_POINT_FIELDS_SCHEMA), + schema=common.BRIAN_POINT_FIELDS_SCHEMA, ) verify_influx_content( influx_config=app_config_params["influx"]["error-counters"], - expected_fields=_expected_fields_from_schema(common.ERROR_POINT_FIELDS_SCHEMA), + schema=common.ERROR_POINT_FIELDS_SCHEMA, + only={ + "output_total_errors", + "input_total_errors", + "input_discards", + "output_discards", + }, ) diff --git a/test/interface_stats/test_juniper.py b/test/interface_stats/test_juniper.py new file mode 100644 index 0000000000000000000000000000000000000000..90567e5abf9b5251a137a93b2d29449dcd74c80a --- /dev/null +++ b/test/interface_stats/test_juniper.py @@ -0,0 +1,261 @@ +from datetime import datetime +import itertools +import re +from unittest.mock import call, patch + +import jsonschema +import pytest +from brian_polling_manager import influx +from brian_polling_manager.interface_stats.vendors import common, juniper +from lxml import etree +from ncclient.operations.rpc import RPCReply + + +@pytest.fixture +def get_netconf(data_dir): + def _get_netconf(router_name): + return juniper.get_netconf_interface_info_from_source_dir( + router_name, source_dir=data_dir + ) + + return _get_netconf + + +@pytest.fixture +def juniper_router_doc_containing_every_field(): + doc = """\ +<interface-information> + <physical-interface> + <name>ae12</name> + <admin-status>up</admin-status> + <oper-status>up</oper-status> + <traffic-statistics> + <input-bytes>1</input-bytes> + <input-packets>2</input-packets> + <output-bytes>3</output-bytes> + <output-packets>4</output-packets> + <ipv6-transit-statistics> + <input-bytes>5</input-bytes> + <input-packets>6</input-packets> + <output-bytes>7</output-bytes> + <output-packets>8</output-packets> + </ipv6-transit-statistics> + </traffic-statistics> + <input-error-list> + <input-errors>11</input-errors> + <input-discards>12</input-discards> + <input-fifo-errors>13</input-fifo-errors> + <input-drops>14</input-drops> + <framing-errors>15</framing-errors> + <input-resource-errors>16</input-resource-errors> + </input-error-list> + <output-error-list> + <output-errors>21</output-errors> + <output-drops>22</output-drops> + <output-resource-errors>23</output-resource-errors> + <output-fifo-errors>24</output-fifo-errors> + <output-collisions>25</output-collisions> + <output-discards>26</output-discards> + </output-error-list> + <ethernet-mac-statistics> + <input-crc-errors>31</input-crc-errors> + <output-crc-errors>32</output-crc-errors> + <input-total-errors>33</input-total-errors> + <output-total-errors>34</output-total-errors> + </ethernet-mac-statistics> + <ethernet-pcs-statistics> + <bit-error-seconds>41</bit-error-seconds> + <errored-blocks-seconds>42</errored-blocks-seconds> + </ethernet-pcs-statistics> + <logical-interface> + <name>ae12.1</name> + <traffic-statistics> + <input-bytes>51</input-bytes> + <input-packets>52</input-packets> + <output-bytes>53</output-bytes> + <output-packets>54</output-packets> + <ipv6-transit-statistics> + <input-bytes>55</input-bytes> + <input-packets>56</input-packets> + <output-bytes>57</output-bytes> + <output-packets>58</output-packets> + </ipv6-transit-statistics> + </traffic-statistics> + </logical-interface> + </physical-interface> +</interface-information> +""" + return etree.fromstring(doc) + + +def test_verify_all_interfaces_present( + juniper_router_fqdn, polled_interfaces, get_netconf +): + """ + verify that all the interfaces we expect to poll + are available in the netconf data + compares a snapshot of all netconf docs with a + a snapshot of inventory /poller/interfaces + (the snapshots were all taken around the same time) + """ + + def _is_enabled(ifc_name, ifc_doc): + m = re.match(r"^([^\.]+)\.?.*", ifc_name) + assert m # sanity: should never fail + phy = ifc_doc.xpath( + f'//interface-information/physical-interface[normalize-space(name)="{m.group(1)}"]' + )[0] + admin_status = phy.xpath("./admin-status/text()")[0].strip() + oper_status = phy.xpath("./oper-status/text()")[0].strip() + + return admin_status == "up" and oper_status == "up" + + if juniper_router_fqdn not in polled_interfaces: + pytest.skip(f"{juniper_router_fqdn} has no expected polled interfaces") + + doc = get_netconf(juniper_router_fqdn) + phy = juniper._physical_interface_counters(doc) + log = juniper._logical_interface_counters(doc) + interfaces = set(x["name"] for x in itertools.chain(phy, log)) + missing_interfaces = polled_interfaces[juniper_router_fqdn] - interfaces + for ifc_name in missing_interfaces: + # verify that any missing interfaces are admin/oper disabled + assert not _is_enabled(ifc_name, doc) + + +def test_physical_interface_counters(juniper_router_doc_containing_every_field): + result = list( + juniper._physical_interface_counters(juniper_router_doc_containing_every_field) + ) + assert len(result) == 1 + assert result[0] == { + "name": "ae12", + "brian": { + "ingressOctets": 1, + "ingressPackets": 2, + "egressOctets": 3, + "egressPackets": 4, + "ingressOctetsv6": 5, + "ingressPacketsv6": 6, + "egressOctetsv6": 7, + "egressPacketsv6": 8, + "ingressErrors": 11, + "ingressDiscards": 12, + "egressErrors": 21, + }, + "errors": { + "input_discards": 12, + "input_fifo_errors": 13, + "input_drops": 14, + "input_framing_errors": 15, + "input_resource_errors": 16, + "output_drops": 22, + "output_resource_errors": 23, + "output_fifo_errors": 24, + "output_collisions": 25, + "output_discards": 26, + "input_crc_errors": 31, + "output_crc_errors": 32, + "input_total_errors": 33, + "output_total_errors": 34, + "bit_error_seconds": 41, + "errored_blocks_seconds": 42, + }, + } + + +def test_logical_interface_counters(juniper_router_doc_containing_every_field): + result = list( + juniper._logical_interface_counters(juniper_router_doc_containing_every_field) + ) + assert len(result) == 1 + assert result[0] == { + "name": "ae12.1", + "brian": { + "ingressOctets": 51, + "ingressPackets": 52, + "egressOctets": 53, + "egressPackets": 54, + "ingressOctetsv6": 55, + "ingressPacketsv6": 56, + "egressOctetsv6": 57, + "egressPacketsv6": 58, + }, + } + + +def test_juniper_router_docs_do_not_generate_errors( + get_netconf, juniper_router_fqdn, caplog +): + doc = get_netconf(juniper_router_fqdn) + counters = list(juniper.interface_counters(doc)) + assert counters + assert not [r for r in caplog.records if r.levelname in ("ERROR", "WARNING")] + + +def test_validate_interface_counters_and_influx_points_for_all_juniper_routers( + juniper_router_fqdn, get_netconf +): + doc = get_netconf(juniper_router_fqdn) + interfaces = list(juniper.interface_counters(doc)) + assert interfaces + for ifc in interfaces: + jsonschema.validate(ifc, common.INTERFACE_COUNTER_SCHEMA) + + bpoints = list( + common.brian_points( + juniper_router_fqdn, + interfaces, + timestamp=datetime.now(), + measurement_name="blah", + ) + ) + assert bpoints + for point in bpoints: + jsonschema.validate(point, influx.INFLUX_POINT) + jsonschema.validate(point["fields"], common.BRIAN_POINT_FIELDS_SCHEMA) + + epoints = list( + common.error_points( + juniper_router_fqdn, + interfaces, + timestamp=datetime.now(), + measurement_name="blah", + ) + ) + assert epoints + for point in epoints: + jsonschema.validate(point, influx.INFLUX_POINT) + jsonschema.validate(point["fields"], common.ERROR_POINT_FIELDS_SCHEMA) + + +class TestGetJuniperNetConf: + RAW_RESPONSE_FILE = "raw-response-juniper-sample.xml" + + @pytest.fixture(autouse=True) + def mocked_connect(self, data_dir): + raw_response = data_dir.joinpath(self.RAW_RESPONSE_FILE).read_text() + with patch.object(juniper, "netconf_connect") as mock: + mock().__enter__().rpc.return_value = RPCReply(raw_response) + mock.reset_mock() + yield mock + + def test_calls_ncclient_with_params(self, mocked_connect): + router_name = "some-router" + juniper.get_netconf_interface_info(router_name, ssh_params={"ssh": "param"}) + + mocked_connect.call_args = call(hostname="some-router", ssh="param", timeout=5) + + assert ( + mocked_connect().__enter__().rpc.call_args[0][0].tag + == "get-interface-information" + ) + + def test_converts_rpc_response_to_xml(self): + router_name = "some-router" + + doc = juniper.get_netconf_interface_info( + router_name, ssh_params={"some": "param"} + ) + + assert doc.tag == "rpc-reply" diff --git a/test/interface_stats/test_nokia.py b/test/interface_stats/test_nokia.py new file mode 100644 index 0000000000000000000000000000000000000000..fbe8a1f998d73fbaae4e1c44ef52466f6d6cea32 --- /dev/null +++ b/test/interface_stats/test_nokia.py @@ -0,0 +1,260 @@ +from datetime import datetime +from unittest.mock import call, patch +from brian_polling_manager import influx +from brian_polling_manager.interface_stats.vendors import common, nokia +import jsonschema +import pytest +from lxml import etree + + +@pytest.fixture +def get_netconf(data_dir): + def _get_netconf(router_name): + return nokia.get_netconf_interface_info_from_source_dir( + router_name, source_dir=data_dir + ) + + return _get_netconf + + +NOKIA_PORT_XML = """\ +<rpc-reply> + <data> + <state> + <port> + <port-id>1/1/c1</port-id> + <oper-state>up</oper-state> + <statistics> + <in-octets>1</in-octets> + <in-packets>2</in-packets> + <out-octets>3</out-octets> + <out-packets>4</out-packets> + <in-errors>5</in-errors> + <in-discards>6</in-discards> + <out-errors>7</out-errors> + <out-discards>8</out-discards> + </statistics> + </port> + </state> + </data> +</rpc-reply>""" + +NOKIA_ROUTER_INTERFACE_XML = """ +<rpc-reply> + <data> + <state> + <router> + <interface> + <interface-name>lag-1.0</interface-name> + <statistics> + <ip> + <in-octets>11</in-octets> + <in-packets>12</in-packets> + <out-octets>13</out-octets> + <out-packets>14</out-packets> + <out-discard-packets>15</out-discard-packets> + </ip> + </statistics> + <ipv6> + <statistics> + <in-octets>16</in-octets> + <in-packets>17</in-packets> + <out-octets>18</out-octets> + <out-packets>19</out-packets> + </statistics> + </ipv6> + </interface> + </router> + </state> + </data> +</rpc-reply> +""" + + +@pytest.fixture +def nokia_port_doc_containing_every_field(): + return etree.fromstring(NOKIA_PORT_XML) + + +@pytest.fixture +def nokia_lag_doc_containing_every_field(): + return etree.fromstring( + NOKIA_PORT_XML.replace("port>", "lag>").replace( + "<port-id>1/1/c1</port-id>", "<lag-name>lag-1</lag-name>" + ) + ) + + +@pytest.fixture +def nokia_router_interface_doc_containing_every_field(): + return etree.fromstring(NOKIA_ROUTER_INTERFACE_XML) + + +def test_port_counters(nokia_port_doc_containing_every_field): + result = list(nokia._port_counters(nokia_port_doc_containing_every_field)) + assert len(result) == 1 + assert result[0] == { + "name": "1/1/c1", + "brian": { + "ingressOctets": 1, + "ingressPackets": 2, + "egressOctets": 3, + "egressPackets": 4, + "ingressErrors": 5, + "ingressDiscards": 6, + "egressErrors": 7, + }, + "errors": { + "input_total_errors": 5, + "input_discards": 6, + "output_total_errors": 7, + "output_discards": 8, + }, + } + + +def test_lag_counters(nokia_lag_doc_containing_every_field): + result = list(nokia._lag_counters(nokia_lag_doc_containing_every_field)) + assert len(result) == 1 + assert result[0] == { + "name": "lag-1", + "brian": { + "ingressOctets": 1, + "ingressPackets": 2, + "egressOctets": 3, + "egressPackets": 4, + "ingressErrors": 5, + "ingressDiscards": 6, + "egressErrors": 7, + }, + "errors": { + "input_total_errors": 5, + "input_discards": 6, + "output_total_errors": 7, + "output_discards": 8, + }, + } + + +def test_router_interface_counters(nokia_router_interface_doc_containing_every_field): + result = list( + nokia._router_interface_counters( + nokia_router_interface_doc_containing_every_field + ) + ) + assert len(result) == 1 + assert result[0] == { + "name": "lag-1.0", + "brian": { + "ingressOctets": 11, + "ingressPackets": 12, + "egressOctets": 13, + "egressPackets": 14, + "ingressOctetsv6": 16, + "ingressPacketsv6": 17, + "egressOctetsv6": 18, + "egressPacketsv6": 19, + }, + "errors": { + "output_discards": 15, + }, + } + + +def test_nokia_router_docs_do_not_generate_errors( + nokia_router_fqdn, caplog, get_netconf +): + doc = get_netconf(nokia_router_fqdn) + counters = list(nokia.interface_counters(doc)) + assert counters + assert not [r for r in caplog.records if r.levelname in ("ERROR", "WARNING")] + + +def test_validate_interface_counters_and_influx_points_for_all_nokia_routers( + nokia_router_fqdn, get_netconf +): + doc = get_netconf(nokia_router_fqdn) + interfaces = list(nokia.interface_counters(doc)) + assert interfaces + for ifc in interfaces: + jsonschema.validate(ifc, common.INTERFACE_COUNTER_SCHEMA) + + bpoints = list( + common.brian_points( + nokia_router_fqdn, + interfaces, + timestamp=datetime.now(), + measurement_name="blah", + ) + ) + assert bpoints + for point in bpoints: + jsonschema.validate(point, influx.INFLUX_POINT) + jsonschema.validate(point["fields"], common.BRIAN_POINT_FIELDS_SCHEMA) + + epoints = list( + common.error_points( + nokia_router_fqdn, + interfaces, + timestamp=datetime.now(), + measurement_name="blah", + ) + ) + assert epoints + for point in epoints: + jsonschema.validate(point, influx.INFLUX_POINT) + jsonschema.validate(point["fields"], common.ERROR_POINT_FIELDS_SCHEMA) + + +class TestGetNokiaNetconf: + RAW_RESPONSE_FILE = "raw-response-nokia-sample.xml" + + @pytest.fixture(autouse=True) + def mocked_connection(self, data_dir): + raw_response = (data_dir / self.RAW_RESPONSE_FILE).read_bytes() + with patch.object(nokia, "netconf_connect") as mock: + mock().__enter__().get().tostring = raw_response + mock.reset_mock() + yield mock + + def test_connect_with_params(self, mocked_connection): + nokia.get_netconf_interface_info("some.router", ssh_params={"some": "param"}) + + assert mocked_connection.call_args == call( + hostname="some.router", + ssh_params={"some": "param"}, + device_params={"name": "sros"}, + nc_params={"capabilities": ["urn:nokia.com:nc:pysros:pc"]}, + timeout=60, + ) + + def test_calls_get_with_request_filter(self, mocked_connection): + router_name = "some-router" + nokia.get_netconf_interface_info(router_name, ssh_params={"some": "param"}) + + calls = mocked_connection().__enter__().get.call_args_list + assert len(calls) == 3 + for i, kind in enumerate(["port", "lag"]): + elems = calls[i][1]["filter"].iter() + assert [e.tag for e in elems] == [ + "filter", + "{urn:nokia.com:sros:ns:yang:sr:state}state", + "{urn:nokia.com:sros:ns:yang:sr:state}" + kind, + ] + elems = calls[2][1]["filter"].iter() + assert [e.tag for e in elems] == [ + "filter", + "{urn:nokia.com:sros:ns:yang:sr:state}state", + "{urn:nokia.com:sros:ns:yang:sr:state}router", + "{urn:nokia.com:sros:ns:yang:sr:state}interface", + ] + + def test_converts_rpc_response_to_xml(self): + router_name = "some-router" + + doc = nokia.get_netconf_interface_info( + router_name, ssh_params={"some": "param"} + ) + + assert doc["port"].tag == "rpc-reply" + assert doc["lag"].tag == "rpc-reply"