From e494db5337f5b41d47792908932ba64bc46ba826 Mon Sep 17 00:00:00 2001 From: thatmattlove Date: Mon, 26 Dec 2022 11:15:20 -0500 Subject: [PATCH] remove legacy parsers --- hyperglass/parsing/__init__.py | 1 - hyperglass/parsing/arista.py | 57 ------------------ hyperglass/parsing/juniper.py | 103 --------------------------------- hyperglass/parsing/linux.py | 79 ------------------------- hyperglass/parsing/nos.py | 18 ------ 5 files changed, 258 deletions(-) delete mode 100644 hyperglass/parsing/__init__.py delete mode 100644 hyperglass/parsing/arista.py delete mode 100644 hyperglass/parsing/juniper.py delete mode 100644 hyperglass/parsing/linux.py delete mode 100644 hyperglass/parsing/nos.py diff --git a/hyperglass/parsing/__init__.py b/hyperglass/parsing/__init__.py deleted file mode 100644 index a3270f0..0000000 --- a/hyperglass/parsing/__init__.py +++ /dev/null @@ -1 +0,0 @@ -"""Parsing utilities for command output.""" diff --git a/hyperglass/parsing/arista.py b/hyperglass/parsing/arista.py deleted file mode 100644 index e1ad50c..0000000 --- a/hyperglass/parsing/arista.py +++ /dev/null @@ -1,57 +0,0 @@ -"""Parse Arista JSON Response to Structured Data.""" - -# Standard Library -import json -from typing import Dict, Sequence - -# Third Party -from pydantic import ValidationError - -# Project -from hyperglass.log import log -from hyperglass.exceptions.private import ParsingError -from hyperglass.models.parsing.arista_eos import AristaRoute - - -def parse_arista(output: Sequence[str]) -> Dict: # noqa: C901 - """Parse a Arista BGP JSON response.""" - data = {} - - for i, response in enumerate(output): - - try: - data: Dict = json.loads(response) - - log.debug("Pre-parsed data: {}", data) - - vrf = list(data["vrfs"].keys())[0] - routes = data["vrfs"][vrf] - - log.debug("Pre-validated data: {}", routes) - - validated = AristaRoute(**routes) - serialized = validated.serialize().export_dict() - - if i == 0: - data.update(serialized) - else: - data["routes"].extend(serialized["routes"]) - - except json.JSONDecodeError as err: - log.critical("Error decoding JSON: {}", str(err)) - raise ParsingError("Error parsing response data") from err - - except KeyError as err: - log.critical("'{}' was not found in the response", str(err)) - raise ParsingError("Error parsing response data") from err - - except IndexError as err: - log.critical(str(err)) - raise ParsingError("Error parsing response data") from err - - except ValidationError as err: - log.critical(str(err)) - raise ParsingError(err.errors()) from err - - log.debug("Serialized: {}", data) - return data diff --git a/hyperglass/parsing/juniper.py b/hyperglass/parsing/juniper.py deleted file mode 100644 index e9766d7..0000000 --- a/hyperglass/parsing/juniper.py +++ /dev/null @@ -1,103 +0,0 @@ -"""Parse Juniper XML Response to Structured Data.""" - -# Standard Library -import re -from typing import Dict, List, Sequence, Generator - -# Third Party -import xmltodict # type:ignore -from pydantic import ValidationError - -# Project -from hyperglass.log import log -from hyperglass.exceptions.private import ParsingError -from hyperglass.models.parsing.juniper import JuniperRoute - -REMOVE_PATTERNS = ( - # The XML response can a CLI banner appended to the end of the XML - # string. For example: - # ``` - # - # ... - # - # {master} - # - # - # - # {master} noqa: E800 - # ``` - # - # This pattern will remove anything inside braces, including the braces. - r"\{.+\}", -) - - -def clean_xml_output(output: str) -> str: - """Remove Juniper-specific patterns from output.""" - - def scrub(lines: List[str]) -> Generator[str, None, None]: - """Clean & remove each pattern from each line.""" - for pattern in REMOVE_PATTERNS: - for line in lines: - # Remove the pattern & strip extra newlines - scrubbed = re.sub(pattern, "", line.strip()) - # Only return non-empty and non-newline lines - if scrubbed and scrubbed != "\n": - yield scrubbed - - lines = scrub(output.splitlines()) - - return "\n".join(lines) - - -def parse_juniper(output: Sequence) -> Dict: # noqa: C901 - """Parse a Juniper BGP XML response.""" - data = {} - - for i, response in enumerate(output): - cleaned = clean_xml_output(response) - - try: - parsed = xmltodict.parse(cleaned, force_list=("rt", "rt-entry", "community")) - - log.debug("Initially Parsed Response: \n{}", parsed) - - if "rpc-reply" in parsed.keys(): - if "xnm:error" in parsed["rpc-reply"]: - if "message" in parsed["rpc-reply"]["xnm:error"]: - err = parsed["rpc-reply"]["xnm:error"]["message"] - raise ParsingError('Error from device: "{}"', err) - - parsed_base = parsed["rpc-reply"]["route-information"] - elif "route-information" in parsed.keys(): - parsed_base = parsed["route-information"] - - if "route-table" not in parsed_base: - return data - - if "rt" not in parsed_base["route-table"]: - return data - - parsed = parsed_base["route-table"] - - validated = JuniperRoute(**parsed) - serialized = validated.serialize().export_dict() - - if i == 0: - data.update(serialized) - else: - data["routes"].extend(serialized["routes"]) - - except xmltodict.expat.ExpatError as err: - log.critical(str(err)) - raise ParsingError("Error parsing response data") from err - - except KeyError as err: - log.critical("{} was not found in the response", str(err)) - raise ParsingError("Error parsing response data") from err - - except ValidationError as err: - log.critical(str(err)) - raise ParsingError(err.errors()) from err - - return data diff --git a/hyperglass/parsing/linux.py b/hyperglass/parsing/linux.py deleted file mode 100644 index 3e65103..0000000 --- a/hyperglass/parsing/linux.py +++ /dev/null @@ -1,79 +0,0 @@ -# flake8: noqa -# WORK IN PROGRESS - -"""Linux-style parsers for ping & traceroute.""" - -# Standard Library -import re - -# Project -from hyperglass.exceptions.private import ParsingError - - -def _process_numbers(numbers): - """Convert string to float or int.""" - for num in numbers: - num = float(num) - if num.is_integer(): - num = int(num) - yield num - - -def parse_linux_ping(output): - """Parse standard Linux-style ping output to structured data. - - Example: - 64 bytes from 1.1.1.1: icmp_seq=0 ttl=59 time=1.151 ms - 64 bytes from 1.1.1.1: icmp_seq=1 ttl=59 time=1.180 ms - 64 bytes from 1.1.1.1: icmp_seq=2 ttl=59 time=1.170 ms - 64 bytes from 1.1.1.1: icmp_seq=3 ttl=59 time=1.338 ms - 64 bytes from 1.1.1.1: icmp_seq=4 ttl=59 time=4.913 ms - - --- 1.1.1.1 ping statistics --- - 5 packets transmitted, 5 packets received, 0% packet loss - round-trip min/avg/max/stddev = 1.151/1.950/4.913/1.483 ms - """ - try: - # Extract target host - host = re.findall(r"^PING (.+) \(.+\): \d+ data bytes", output)[0] - - # Separate echo replies from summary info - replies, _stats = re.split(r"--- .+ ---", output) - replies = [l for l in replies.splitlines()[1:] if l] - - reply_stats = [] - for line in replies: - # Extract the numerical values from each echo reply line - bytes_seq_ttl_rtt = re.findall( - r"(\d+) bytes.+ icmp_seq=(\d+) ttl=(\d+) time=(\d+\.\d+).*", line - )[0] - - _bytes, seq, ttl, rtt = _process_numbers(bytes_seq_ttl_rtt) - - reply_stats.append({"bytes": _bytes, "sequence": seq, "ttl": ttl, "rtt": rtt}) - - stats = [l for l in _stats.splitlines() if l] - - # Extract the top summary line numbers & process - tx_rx_loss = re.findall(r"(\d+) .+, (\d+) .+, (\d+)\%.+", stats[0])[0] - tx, rx, loss = _process_numbers(tx_rx_loss) - - # Extract the bottom summary line numbers & process - rt = stats[1].split(" = ")[1] - min_max_avg = rt.split("/")[:-1] - _min, _max, _avg = _process_numbers(min_max_avg) - - return { - "host": host, - "transmitted": tx, - "received": rx, - "loss_percent": loss, - "min_rtt": _min, - "max_rtt": _max, - "avg_rtt": _avg, - "replies": reply_stats, - } - - except (KeyError, ValueError) as err: - # KeyError for empty findalls, ValueError for regex errors - raise ParsingError("Error parsing ping response: {e}", e=str(err)) diff --git a/hyperglass/parsing/nos.py b/hyperglass/parsing/nos.py deleted file mode 100644 index 1362142..0000000 --- a/hyperglass/parsing/nos.py +++ /dev/null @@ -1,18 +0,0 @@ -"""Map NOS and Commands to Parsing Functions.""" - -# Local -from .arista import parse_arista -from .juniper import parse_juniper - -structured_parsers = { - "juniper": { - "bgp_route": parse_juniper, - "bgp_aspath": parse_juniper, - "bgp_community": parse_juniper, - }, - "arista_eos": { - "bgp_route": parse_arista, - "bgp_aspath": parse_arista, - "bgp_community": parse_arista, - }, -}