1
0
Fork 1
mirror of https://github.com/thatmattlove/hyperglass.git synced 2026-04-17 21:38:27 +00:00

Refactor code for improved readability and consistency across multiple files using black

This commit is contained in:
Wilhelm Schonfeldt 2025-09-26 09:43:06 +02:00
parent 22c8f8310e
commit ae6a1a0bb8
18 changed files with 184 additions and 109 deletions

View file

@ -272,7 +272,9 @@ class _ForwardServer(socketserver.TCPServer): # Not Threading
def handle_error(self, request, client_address): def handle_error(self, request, client_address):
(exc_class, exc, tb) = sys.exc_info() (exc_class, exc, tb) = sys.exc_info()
self.logger.bind(source=request.getsockname()).error("Could not establish connection to remote side of the tunnel") self.logger.bind(source=request.getsockname()).error(
"Could not establish connection to remote side of the tunnel"
)
self.tunnel_ok.put(False) self.tunnel_ok.put(False)
@property @property
@ -1023,7 +1025,7 @@ class SSHTunnelForwarder:
msg = template.format(self.ssh_host, self.ssh_port, e.args[0]) msg = template.format(self.ssh_host, self.ssh_port, e.args[0])
self.logger.error(msg) self.logger.error(msg)
return return
for (rem, loc) in zip(self._remote_binds, self._local_binds): for rem, loc in zip(self._remote_binds, self._local_binds):
try: try:
self._make_ssh_forward_server(rem, loc) self._make_ssh_forward_server(rem, loc)
except BaseSSHTunnelForwarderError as e: except BaseSSHTunnelForwarderError as e:
@ -1053,7 +1055,7 @@ class SSHTunnelForwarder:
bind_addresses = [bind_address] bind_addresses = [bind_address]
if not is_remote: if not is_remote:
# Add random port if missing in local bind # Add random port if missing in local bind
for (i, local_bind) in enumerate(bind_addresses): for i, local_bind in enumerate(bind_addresses):
if isinstance(local_bind, tuple) and len(local_bind) == 1: if isinstance(local_bind, tuple) and len(local_bind) == 1:
bind_addresses[i] = (local_bind[0], 0) bind_addresses[i] = (local_bind[0], 0)
check_addresses(bind_addresses, is_remote) check_addresses(bind_addresses, is_remote)
@ -1400,9 +1402,11 @@ class SSHTunnelForwarder:
def __str__(self) -> str: def __str__(self) -> str:
credentials = { credentials = {
"password": self.ssh_password, "password": self.ssh_password,
"pkeys": [(key.get_name(), hexlify(key.get_fingerprint())) for key in self.ssh_pkeys] "pkeys": (
if any(self.ssh_pkeys) [(key.get_name(), hexlify(key.get_fingerprint())) for key in self.ssh_pkeys]
else None, if any(self.ssh_pkeys)
else None
),
} }
_remove_none_values(credentials) _remove_none_values(credentials)
template = os.linesep.join( template = os.linesep.join(

View file

@ -19,7 +19,14 @@ TARGET_FORMAT_SPACE = ("huawei", "huawei_vrpv8")
TARGET_JUNIPER_ASPATH = ("juniper", "juniper_junos") TARGET_JUNIPER_ASPATH = ("juniper", "juniper_junos")
SUPPORTED_STRUCTURED_OUTPUT = ("frr", "juniper", "arista_eos", "huawei", "mikrotik_routeros", "mikrotik_switchos") SUPPORTED_STRUCTURED_OUTPUT = (
"frr",
"juniper",
"arista_eos",
"huawei",
"mikrotik_routeros",
"mikrotik_switchos",
)
CONFIG_EXTENSIONS = ("py", "yaml", "yml", "json", "toml") CONFIG_EXTENSIONS = ("py", "yaml", "yml", "json", "toml")

View file

@ -177,13 +177,13 @@ HuaweiBGPRouteTable = BuiltinDirective(
command="", command="",
), ),
# Regra DENY AS PREFIXO # Regra DENY AS PREFIXO
#RuleWithIPv4( # RuleWithIPv4(
# condition="x.x.x.x/xx", # condition="x.x.x.x/xx",
# ge="xx", # ge="xx",
# le="32", # le="32",
# action="deny", # action="deny",
# command="", # command="",
#), # ),
RuleWithIPv4( RuleWithIPv4(
condition="0.0.0.0/0", condition="0.0.0.0/0",
ge="8", ge="8",
@ -232,13 +232,13 @@ HuaweiBGPRouteTable = BuiltinDirective(
command="", command="",
), ),
# REGRA DENY AS PREFIXO # REGRA DENY AS PREFIXO
#RuleWithIPv6( # RuleWithIPv6(
# condition="x.x.x.x/xx", # condition="x.x.x.x/xx",
# ge="XX", # ge="XX",
# le="128", # le="128",
# action="deny", # action="deny",
# command="", # command="",
#), # ),
RuleWithIPv6( RuleWithIPv6(
condition="::/0", condition="::/0",
ge="10", ge="10",

View file

@ -35,7 +35,7 @@ Mikrotik_BGPRoute = BuiltinDirective(
# v7 # v7
command="routing route print detail without-paging where {target} in dst-address bgp and dst-address !=0.0.0.0/0", command="routing route print detail without-paging where {target} in dst-address bgp and dst-address !=0.0.0.0/0",
# v6 # v6
#command="ip route print detail without-paging where {target} in dst-address bgp and dst-address !=0.0.0.0/0", # command="ip route print detail without-paging where {target} in dst-address bgp and dst-address !=0.0.0.0/0",
), ),
RuleWithIPv6( RuleWithIPv6(
condition="::/0", condition="::/0",
@ -43,7 +43,7 @@ Mikrotik_BGPRoute = BuiltinDirective(
# v7 # v7
command="routing route print detail without-paging where {target} in dst-address bgp and dst-address !=::/0", command="routing route print detail without-paging where {target} in dst-address bgp and dst-address !=::/0",
# v6 # v6
#command="ipv6 route print detail without-paging where {target} in dst-address bgp and dst-address !=::/0", # command="ipv6 route print detail without-paging where {target} in dst-address bgp and dst-address !=::/0",
), ),
], ],
field=Text(description="IP Address, Prefix, or Hostname"), field=Text(description="IP Address, Prefix, or Hostname"),
@ -66,7 +66,7 @@ Mikrotik_BGPASPath = BuiltinDirective(
) )
], ],
field=Text(description="AS Path Regular Expression"), field=Text(description="AS Path Regular Expression"),
plugins=["mikrotik_normalize_input","mikrotik_garbage_output", "bgp_routestr_mikrotik"], plugins=["mikrotik_normalize_input", "mikrotik_garbage_output", "bgp_routestr_mikrotik"],
table_output="__hyperglass_mikrotik_bgp_aspath_table__", table_output="__hyperglass_mikrotik_bgp_aspath_table__",
platforms=PLATFORMS, platforms=PLATFORMS,
) )
@ -85,7 +85,7 @@ Mikrotik_BGPCommunity = BuiltinDirective(
) )
], ],
field=Text(description="BGP Community String"), field=Text(description="BGP Community String"),
plugins=["mikrotik_normalize_input","mikrotik_garbage_output", "bgp_routestr_mikrotik"], plugins=["mikrotik_normalize_input", "mikrotik_garbage_output", "bgp_routestr_mikrotik"],
table_output="__hyperglass_mikrotik_bgp_community_table__", table_output="__hyperglass_mikrotik_bgp_community_table__",
platforms=PLATFORMS, platforms=PLATFORMS,
) )
@ -183,13 +183,13 @@ MikrotikBGPRouteTable = BuiltinDirective(
command="", command="",
), ),
# Regra DENY AS PREFIXO # Regra DENY AS PREFIXO
#RuleWithIPv4( # RuleWithIPv4(
# condition="x.x.x.x/x", # condition="x.x.x.x/x",
# ge="xx", # ge="xx",
# le="32", # le="32",
# action="deny", # action="deny",
# command="", # command="",
#), # ),
RuleWithIPv4( RuleWithIPv4(
condition="0.0.0.0/0", condition="0.0.0.0/0",
ge="8", ge="8",
@ -198,7 +198,7 @@ MikrotikBGPRouteTable = BuiltinDirective(
# v7 # v7
command="routing route print detail without-paging where {target} in dst-address bgp and dst-address !=0.0.0.0/0", command="routing route print detail without-paging where {target} in dst-address bgp and dst-address !=0.0.0.0/0",
# v6 # v6
#command="ip route print detail without-paging where {target} in dst-address bgp and dst-address !=0.0.0.0/0", # command="ip route print detail without-paging where {target} in dst-address bgp and dst-address !=0.0.0.0/0",
), ),
# REGRA DENY SITE LOCAL DEPRECIADO RFC 3879 # REGRA DENY SITE LOCAL DEPRECIADO RFC 3879
RuleWithIPv6( RuleWithIPv6(
@ -241,20 +241,20 @@ MikrotikBGPRouteTable = BuiltinDirective(
command="", command="",
), ),
# REGRA DENY AS PREFIXO # REGRA DENY AS PREFIXO
#RuleWithIPv6( # RuleWithIPv6(
# condition="xxxx:xxxx::/xx", # condition="xxxx:xxxx::/xx",
# ge="xx", # ge="xx",
# le="128", # le="128",
# action="deny", # action="deny",
# command="", # command="",
#), # ),
RuleWithIPv6( RuleWithIPv6(
condition="::/0", condition="::/0",
action="permit", action="permit",
# v7 # v7
command="routing route print detail without-paging where {target} in dst-address bgp and dst-address !=::/0", command="routing route print detail without-paging where {target} in dst-address bgp and dst-address !=::/0",
# v6 # v6
#command="ipv6 route print detail without-paging where {target} in dst-address bgp and dst-address !=::/0", # command="ipv6 route print detail without-paging where {target} in dst-address bgp and dst-address !=::/0",
), ),
], ],
field=Text(description="IP Address, Prefix, or Hostname"), field=Text(description="IP Address, Prefix, or Hostname"),

View file

@ -208,7 +208,9 @@ class BaseExternal:
data, data,
timeout, timeout,
response_required, response_required,
) = itemgetter(*kwargs.keys())(kwargs) ) = itemgetter(
*kwargs.keys()
)(kwargs)
if method.upper() not in supported_methods: if method.upper() not in supported_methods:
raise self._exception( raise self._exception(

View file

@ -13,20 +13,27 @@ if t.TYPE_CHECKING:
from ipaddress import IPv4Address, IPv6Address from ipaddress import IPv4Address, IPv6Address
RPKI_STATE_MAP = { RPKI_STATE_MAP = {
"Invalid": 0, "invalid": 0, "Invalid": 0,
"Valid": 1, "valid": 1, "invalid": 0,
"NotFound": 2, "notfound": 2, "not_found": 2, "not-found": 2, "Valid": 1,
"Unknown": 2, "unknown": 2, "valid": 1,
"DEFAULT": 3 "NotFound": 2,
"notfound": 2,
"not_found": 2,
"not-found": 2,
"Unknown": 2,
"unknown": 2,
"DEFAULT": 3,
} }
RPKI_NAME_MAP = {v: k for k, v in RPKI_STATE_MAP.items()} RPKI_NAME_MAP = {v: k for k, v in RPKI_STATE_MAP.items()}
CACHE_KEY = "hyperglass.external.rpki" CACHE_KEY = "hyperglass.external.rpki"
def rpki_state( def rpki_state(
prefix: t.Union["IPv4Address", "IPv6Address", str], prefix: t.Union["IPv4Address", "IPv6Address", str],
asn: t.Union[int, str], asn: t.Union[int, str],
backend: str = "cloudflare", backend: str = "cloudflare",
rpki_server_url: str = "" rpki_server_url: str = "",
) -> int: ) -> int:
"""Get RPKI state and map to expected integer.""" """Get RPKI state and map to expected integer."""
_log = log.bind(prefix=prefix, asn=asn) _log = log.bind(prefix=prefix, asn=asn)

View file

@ -19,8 +19,8 @@ def test_rpki():
result = rpki_state(prefix, asn) result = rpki_state(prefix, asn)
result_name = RPKI_NAME_MAP.get(result, "No Name") result_name = RPKI_NAME_MAP.get(result, "No Name")
expected_name = RPKI_NAME_MAP.get(expected, "No Name") expected_name = RPKI_NAME_MAP.get(expected, "No Name")
assert result == expected, ( assert (
"RPKI State for '{}' via AS{!s} '{}' ({}) instead of '{}' ({})".format( result == expected
prefix, asn, result, result_name, expected, expected_name ), "RPKI State for '{}' via AS{!s} '{}' ({}) instead of '{}' ({})".format(
) prefix, asn, result, result_name, expected, expected_name
) )

View file

@ -357,9 +357,9 @@ class Devices(MultiModel, model=Device, unique_by="id"):
"group": group, "group": group,
"id": device.id, "id": device.id,
"name": device.name, "name": device.name,
"avatar": f"/images/{device.avatar.name}" "avatar": (
if device.avatar is not None f"/images/{device.avatar.name}" if device.avatar is not None else None
else None, ),
"description": device.description, "description": device.description,
"directives": [d.frontend() for d in device.directives], "directives": [d.frontend() for d in device.directives],
} }

View file

@ -24,6 +24,7 @@ class StructuredRpki(HyperglassModel):
backend: str = "cloudflare" backend: str = "cloudflare"
rpki_server_url: str = "" rpki_server_url: str = ""
class Structured(HyperglassModel): class Structured(HyperglassModel):
"""Control structured data responses.""" """Control structured data responses."""

View file

@ -21,10 +21,11 @@ RPKI_STATE_MAP = {
"unverified": 3, "unverified": 3,
} }
def remove_prefix(text: str, prefix: str) -> str: def remove_prefix(text: str, prefix: str) -> str:
"""Remove prefix from text if it exists.""" """Remove prefix from text if it exists."""
if text.startswith(prefix): if text.startswith(prefix):
return text[len(prefix):] return text[len(prefix) :]
return text return text
@ -38,6 +39,7 @@ class HuaweiBase(HyperglassModel, extra="ignore"):
class HuaweiPaths(HuaweiBase): class HuaweiPaths(HuaweiBase):
"""BGP paths information.""" """BGP paths information."""
available: int = 0 available: int = 0
best: int = 0 best: int = 0
select: int = 0 select: int = 0
@ -118,6 +120,7 @@ class HuaweiRouteEntry(HuaweiBase):
"""Get peer router ID.""" """Get peer router ID."""
return self.from_addr return self.from_addr
def _extract_paths(line: str) -> HuaweiPaths: def _extract_paths(line: str) -> HuaweiPaths:
"""Extract paths information from line like 'Paths: 3 available, 1 best, 1 select, 0 best-external, 0 add-path'.""" """Extract paths information from line like 'Paths: 3 available, 1 best, 1 select, 0 best-external, 0 add-path'."""
paths_data = { paths_data = {
@ -150,7 +153,14 @@ def _extract_route_entries(lines: t.List[str]) -> t.List[HuaweiRouteEntry]:
# Split lines into route blocks using empty lines as separators # Split lines into route blocks using empty lines as separators
size = len(lines) size = len(lines)
idx_list = [idx + 1 for idx, val in enumerate(lines) if val.strip() == ""] idx_list = [idx + 1 for idx, val in enumerate(lines) if val.strip() == ""]
entries = [lines[i:j] for i, j in zip([0] + idx_list, idx_list + ([size] if idx_list[-1] != size else []))] if idx_list else [lines] entries = (
[
lines[i:j]
for i, j in zip([0] + idx_list, idx_list + ([size] if idx_list[-1] != size else []))
]
if idx_list
else [lines]
)
for route_entry in entries: for route_entry in entries:
if not route_entry: if not route_entry:
@ -188,7 +198,9 @@ def _extract_route_entries(lines: t.List[str]) -> t.List[HuaweiRouteEntry]:
continue continue
if info.startswith("BGP routing table entry information of"): if info.startswith("BGP routing table entry information of"):
route_data["prefix"] = remove_prefix(info, "BGP routing table entry information of ").rstrip(":") route_data["prefix"] = remove_prefix(
info, "BGP routing table entry information of "
).rstrip(":")
elif info.startswith("From:"): elif info.startswith("From:"):
route_data["from_addr"] = remove_prefix(info, "From: ").split(" (")[0] route_data["from_addr"] = remove_prefix(info, "From: ").split(" (")[0]
elif info.startswith("Route Duration:"): elif info.startswith("Route Duration:"):
@ -205,7 +217,9 @@ def _extract_route_entries(lines: t.List[str]) -> t.List[HuaweiRouteEntry]:
minutes = int(m_match.group(1)) if m_match else 0 minutes = int(m_match.group(1)) if m_match else 0
seconds = int(s_match.group(1)) if s_match else 0 seconds = int(s_match.group(1)) if s_match else 0
route_data["duration"] = days * 24 * 60 * 60 + hours * 60 * 60 + minutes * 60 + seconds route_data["duration"] = (
days * 24 * 60 * 60 + hours * 60 * 60 + minutes * 60 + seconds
)
except: except:
route_data["duration"] = 0 route_data["duration"] = 0
elif info.startswith("Direct Out-interface:"): elif info.startswith("Direct Out-interface:"):
@ -215,23 +229,34 @@ def _extract_route_entries(lines: t.List[str]) -> t.List[HuaweiRouteEntry]:
elif info.startswith("Relay IP Nexthop:"): elif info.startswith("Relay IP Nexthop:"):
route_data["relay_ip_next_hop"] = remove_prefix(info, "Relay IP Nexthop: ") route_data["relay_ip_next_hop"] = remove_prefix(info, "Relay IP Nexthop: ")
elif info.startswith("Relay IP Out-Interface:"): elif info.startswith("Relay IP Out-Interface:"):
route_data["relay_ip_out_interface"] = remove_prefix(info, "Relay IP Out-Interface: ") route_data["relay_ip_out_interface"] = remove_prefix(
info, "Relay IP Out-Interface: "
)
elif info.startswith("Qos information :"): elif info.startswith("Qos information :"):
route_data["qos"] = remove_prefix(info, "Qos information : ") route_data["qos"] = remove_prefix(info, "Qos information : ")
elif info.startswith("Community:"): elif info.startswith("Community:"):
communities_str = remove_prefix(info, "Community: ") communities_str = remove_prefix(info, "Community: ")
if communities_str and communities_str.lower() != "none": if communities_str and communities_str.lower() != "none":
communities = [c.strip().replace("<", "").replace(">", "") for c in communities_str.split(", ")] communities = [
c.strip().replace("<", "").replace(">", "")
for c in communities_str.split(", ")
]
route_data["communities"] = [c for c in communities if c] route_data["communities"] = [c for c in communities if c]
elif info.startswith("Large-Community:"): elif info.startswith("Large-Community:"):
large_communities_str = remove_prefix(info, "Large-Community: ") large_communities_str = remove_prefix(info, "Large-Community: ")
if large_communities_str and large_communities_str.lower() != "none": if large_communities_str and large_communities_str.lower() != "none":
large_communities = [c.strip().replace("<", "").replace(">", "") for c in large_communities_str.split(", ")] large_communities = [
c.strip().replace("<", "").replace(">", "")
for c in large_communities_str.split(", ")
]
route_data["large_communities"] = [c for c in large_communities if c] route_data["large_communities"] = [c for c in large_communities if c]
elif info.startswith("Ext-Community:"): elif info.startswith("Ext-Community:"):
ext_communities_str = remove_prefix(info, "Ext-Community: ") ext_communities_str = remove_prefix(info, "Ext-Community: ")
if ext_communities_str and ext_communities_str.lower() != "none": if ext_communities_str and ext_communities_str.lower() != "none":
ext_communities = [c.strip().replace("<", "").replace(">", "") for c in ext_communities_str.split(", ")] ext_communities = [
c.strip().replace("<", "").replace(">", "")
for c in ext_communities_str.split(", ")
]
route_data["ext_communities"] = [c for c in ext_communities if c] route_data["ext_communities"] = [c for c in ext_communities if c]
elif info.startswith("AS-path"): elif info.startswith("AS-path"):
values = info.split(",") values = info.split(",")
@ -240,7 +265,9 @@ def _extract_route_entries(lines: t.List[str]) -> t.List[HuaweiRouteEntry]:
if v.startswith("AS-path"): if v.startswith("AS-path"):
as_path_str = remove_prefix(v, "AS-path ") as_path_str = remove_prefix(v, "AS-path ")
try: try:
route_data["as_path"] = [int(a) for a in as_path_str.split() if a.isdigit()] route_data["as_path"] = [
int(a) for a in as_path_str.split() if a.isdigit()
]
except ValueError: except ValueError:
route_data["as_path"] = [] route_data["as_path"] = []
elif v.startswith("origin"): elif v.startswith("origin"):
@ -282,7 +309,9 @@ def _extract_route_entries(lines: t.List[str]) -> t.List[HuaweiRouteEntry]:
route = HuaweiRouteEntry(**route_data) route = HuaweiRouteEntry(**route_data)
routes.append(route) routes.append(route)
except Exception as e: except Exception as e:
log.warning(f'Failed to create route entry for prefix {{route_data.get("prefix", "unknown")}}: {{e}}') log.warning(
f'Failed to create route entry for prefix {{route_data.get("prefix", "unknown")}}: {{e}}'
)
continue continue
return routes return routes
@ -323,7 +352,9 @@ class HuaweiBGPTable(HuaweiBase):
instance.local_router_id = remove_prefix(line, "BGP local router ID : ").strip() instance.local_router_id = remove_prefix(line, "BGP local router ID : ").strip()
elif "Local AS number" in line: elif "Local AS number" in line:
try: try:
instance.local_as_number = int(remove_prefix(line, "Local AS number : ").strip()) instance.local_as_number = int(
remove_prefix(line, "Local AS number : ").strip()
)
except ValueError: except ValueError:
instance.local_as_number = 0 instance.local_as_number = 0
elif line.strip().startswith("Paths:"): elif line.strip().startswith("Paths:"):
@ -353,7 +384,9 @@ class HuaweiBGPTable(HuaweiBase):
"source_as": route.source_as, "source_as": route.source_as,
"source_rid": route.source_rid, "source_rid": route.source_rid,
"peer_rid": route.peer_rid, "peer_rid": route.peer_rid,
"rpki_state": RPKI_STATE_MAP.get("unknown") if route.is_valid else RPKI_STATE_MAP.get("valid"), "rpki_state": (
RPKI_STATE_MAP.get("unknown") if route.is_valid else RPKI_STATE_MAP.get("valid")
),
} }
routes.append(route_data) routes.append(route_data)
@ -363,4 +396,3 @@ class HuaweiBGPTable(HuaweiBase):
routes=routes, routes=routes,
winning_weight="high", winning_weight="high",
) )

View file

@ -21,22 +21,26 @@ RPKI_STATE_MAP = {
"unverified": 3, "unverified": 3,
} }
def remove_prefix(text: str, prefix: str) -> str: def remove_prefix(text: str, prefix: str) -> str:
if text.startswith(prefix): if text.startswith(prefix):
return text[len(prefix):] return text[len(prefix) :]
return text return text
# Regex to find key=value pairs. The key can contain dots and hyphens. # Regex to find key=value pairs. The key can contain dots and hyphens.
# The value can be quoted or a single word. # The value can be quoted or a single word.
TOKEN_RE = re.compile(r'([a-zA-Z0-9_.-]+)=(".*?"|\S+)') TOKEN_RE = re.compile(r'([a-zA-Z0-9_.-]+)=(".*?"|\S+)')
# Regex to find flags at the beginning of a line (e.g., "Ab dst-address=...") # Regex to find flags at the beginning of a line (e.g., "Ab dst-address=...")
FLAGS_RE = re.compile(r'^\s*([DXIAcmsroivmyH\+b]+)\s+') FLAGS_RE = re.compile(r"^\s*([DXIAcmsroivmyH\+b]+)\s+")
class MikrotikBase(HyperglassModel, extra="ignore"): class MikrotikBase(HyperglassModel, extra="ignore"):
def __init__(self, **kwargs: t.Any) -> None: def __init__(self, **kwargs: t.Any) -> None:
super().__init__(**kwargs) super().__init__(**kwargs)
class MikrotikPaths(MikrotikBase): class MikrotikPaths(MikrotikBase):
available: int = 0 available: int = 0
best: int = 0 best: int = 0
@ -44,8 +48,10 @@ class MikrotikPaths(MikrotikBase):
best_external: int = 0 best_external: int = 0
add_path: int = 0 add_path: int = 0
class MikrotikRouteEntry(MikrotikBase): class MikrotikRouteEntry(MikrotikBase):
"""MikroTik Route Entry.""" """MikroTik Route Entry."""
model_config = ConfigDict(validate_assignment=False) model_config = ConfigDict(validate_assignment=False)
prefix: str prefix: str
@ -102,6 +108,7 @@ class MikrotikRouteEntry(MikrotikBase):
def peer_rid(self) -> str: def peer_rid(self) -> str:
return self.gateway return self.gateway
def _extract_paths(lines: t.List[str]) -> MikrotikPaths: def _extract_paths(lines: t.List[str]) -> MikrotikPaths:
"""Simple count based on lines with dst/dst-address and 'A' flag.""" """Simple count based on lines with dst/dst-address and 'A' flag."""
available = 0 available = 0
@ -114,6 +121,7 @@ def _extract_paths(lines: t.List[str]) -> MikrotikPaths:
best += 1 best += 1
return MikrotikPaths(available=available, best=best, select=best) return MikrotikPaths(available=available, best=best, select=best)
def _process_kv(route: dict, key: str, val: str): def _process_kv(route: dict, key: str, val: str):
_log = log.bind(parser="MikrotikBGPTable") _log = log.bind(parser="MikrotikBGPTable")
"""Process a key-value pair and update the route dictionary.""" """Process a key-value pair and update the route dictionary."""
@ -125,7 +133,7 @@ def _process_kv(route: dict, key: str, val: str):
route["prefix"] = val route["prefix"] = val
elif key in ("gateway", "nexthop"): elif key in ("gateway", "nexthop"):
# Extract only the IP from gateway (e.g., 168.254.0.2%vlan-2000) # Extract only the IP from gateway (e.g., 168.254.0.2%vlan-2000)
route["gateway"] = val.split('%')[0] route["gateway"] = val.split("%")[0]
elif key == "distance": elif key == "distance":
route["distance"] = int(val) if val.isdigit() else route.get("distance", 0) route["distance"] = int(val) if val.isdigit() else route.get("distance", 0)
elif key == "scope": elif key == "scope":
@ -155,10 +163,11 @@ def _process_kv(route: dict, key: str, val: str):
if val and val.lower() != "none": if val and val.lower() != "none":
route["ext_communities"] = [c.strip() for c in val.split(",") if c.strip()] route["ext_communities"] = [c.strip() for c in val.split(",") if c.strip()]
elif key == "rpki": elif key == "rpki":
#_log.debug(f"RPKI raw value: {val!r}") # _log.debug(f"RPKI raw value: {val!r}")
clean_val = val.strip().strip('"').lower() clean_val = val.strip().strip('"').lower()
route["rpki_state"] = RPKI_STATE_MAP.get(clean_val, 2) route["rpki_state"] = RPKI_STATE_MAP.get(clean_val, 2)
def _extract_route_entries(lines: t.List[str]) -> t.List[MikrotikRouteEntry]: def _extract_route_entries(lines: t.List[str]) -> t.List[MikrotikRouteEntry]:
"""Extract route entries from a list of lines.""" """Extract route entries from a list of lines."""
routes: t.List[MikrotikRouteEntry] = [] routes: t.List[MikrotikRouteEntry] = []
@ -192,6 +201,7 @@ def _extract_route_entries(lines: t.List[str]) -> t.List[MikrotikRouteEntry]:
return routes return routes
def _parse_route_block(block: t.List[str]) -> t.Optional[MikrotikRouteEntry]: def _parse_route_block(block: t.List[str]) -> t.Optional[MikrotikRouteEntry]:
"""Parse a single route block and return a MikrotikRouteEntry.""" """Parse a single route block and return a MikrotikRouteEntry."""
if not block: if not block:
@ -202,10 +212,21 @@ def _parse_route_block(block: t.List[str]) -> t.Optional[MikrotikRouteEntry]:
return None return None
rd = { rd = {
"prefix": "", "gateway": "", "distance": 20, "scope": 30, "target_scope": 10, "prefix": "",
"as_path": [], "communities": [], "large_communities": [], "ext_communities": [], "gateway": "",
"local_preference": 100, "metric": 0, "origin": "", "distance": 20,
"is_active": False, "is_best": False, "is_valid": False, "scope": 30,
"target_scope": 10,
"as_path": [],
"communities": [],
"large_communities": [],
"ext_communities": [],
"local_preference": 100,
"metric": 0,
"origin": "",
"is_active": False,
"is_best": False,
"is_valid": False,
"rpki_state": RPKI_STATE_MAP.get("unknown", 2), "rpki_state": RPKI_STATE_MAP.get("unknown", 2),
} }
@ -229,12 +250,14 @@ def _parse_route_block(block: t.List[str]) -> t.Optional[MikrotikRouteEntry]:
class MikrotikBGPRouteTable(BGPRouteTable): class MikrotikBGPRouteTable(BGPRouteTable):
"""Bypass validation to align with Huawei parser.""" """Bypass validation to align with Huawei parser."""
def __init__(self, **kwargs): def __init__(self, **kwargs):
object.__setattr__(self, "vrf", kwargs.get("vrf", "default")) object.__setattr__(self, "vrf", kwargs.get("vrf", "default"))
object.__setattr__(self, "count", kwargs.get("count", 0)) object.__setattr__(self, "count", kwargs.get("count", 0))
object.__setattr__(self, "routes", kwargs.get("routes", [])) object.__setattr__(self, "routes", kwargs.get("routes", []))
object.__setattr__(self, "winning_weight", kwargs.get("winning_weight", "low")) object.__setattr__(self, "winning_weight", kwargs.get("winning_weight", "low"))
class MikrotikBGPTable(MikrotikBase): class MikrotikBGPTable(MikrotikBase):
"""MikroTik BGP Table in canonical format.""" """MikroTik BGP Table in canonical format."""
@ -253,10 +276,7 @@ class MikrotikBGPTable(MikrotikBase):
return inst return inst
# Filter out command echoes and header lines # Filter out command echoes and header lines
lines = [ lines = [ln for ln in lines if not ln.strip().startswith((">", "Flags:", "[", "#"))]
ln for ln in lines
if not ln.strip().startswith((">", "Flags:", "[", "#"))
]
inst.paths = _extract_paths(lines) inst.paths = _extract_paths(lines)
inst.routes = _extract_route_entries(lines) inst.routes = _extract_route_entries(lines)

View file

@ -19,9 +19,9 @@ def test_check_legacy_fields():
test1_expected.keys() test1_expected.keys()
), "legacy field not replaced" ), "legacy field not replaced"
assert set(check_legacy_fields(model="Device", data=test2).keys()) == set(test2.keys()), ( assert set(check_legacy_fields(model="Device", data=test2).keys()) == set(
"new field not left unmodified" test2.keys()
) ), "new field not left unmodified"
with pytest.raises(ValueError): with pytest.raises(ValueError):
check_legacy_fields(model="Device", data=test3) check_legacy_fields(model="Device", data=test3)

View file

@ -35,7 +35,7 @@ def parse_huawei(output: Sequence[str]) -> "OutputDataModel":
_log.debug(f"Combined output length: {len(combined_output)}") _log.debug(f"Combined output length: {len(combined_output)}")
# Debug: log the first few lines to understand the format # Debug: log the first few lines to understand the format
lines = combined_output.split('\n')[:10] lines = combined_output.split("\n")[:10]
_log.debug(f"First 10 lines: {lines}") _log.debug(f"First 10 lines: {lines}")
for response in output: for response in output:

View file

@ -70,7 +70,7 @@ class MikrotikGarbageOutput(OutputPlugin):
# Iniciar a detecção da seção de Flags # Iniciar a detecção da seção de Flags
if stripped_line.startswith("Flags:"): if stripped_line.startswith("Flags:"):
in_flags_section = True in_flags_section = True
continue # Pula a própria linha "Flags:" continue # Pula a própria linha "Flags:"
# Se estivermos na seção de flags, verificar se a linha ainda é parte dela. # Se estivermos na seção de flags, verificar se a linha ainda é parte dela.
# Uma linha de dados de rota real geralmente começa com flags (ex: "Ab") ou é indentada. # Uma linha de dados de rota real geralmente começa com flags (ex: "Ab") ou é indentada.
@ -84,7 +84,7 @@ class MikrotikGarbageOutput(OutputPlugin):
if "=" in stripped_line: if "=" in stripped_line:
in_flags_section = False in_flags_section = False
else: else:
continue # Pula as linhas da legenda de flags continue # Pula as linhas da legenda de flags
filtered_lines.append(line) filtered_lines.append(line)
@ -94,4 +94,3 @@ class MikrotikGarbageOutput(OutputPlugin):
log.debug(f"MikrotikGarbageOutput cleaned {len(output)} output blocks.") log.debug(f"MikrotikGarbageOutput cleaned {len(output)} output blocks.")
return tuple(cleaned_outputs) return tuple(cleaned_outputs)

View file

@ -7,6 +7,7 @@ from pydantic import PrivateAttr
# Project # Project
from hyperglass.log import log from hyperglass.log import log
# REMOVIDA a importação direta de Query para evitar o erro circular # REMOVIDA a importação direta de Query para evitar o erro circular
# from hyperglass.models.api.query import Query # from hyperglass.models.api.query import Query
@ -20,6 +21,7 @@ class MikrotikTargetNormalizerInput(InputPlugin):
This ensures that queries for different IPs within the same subnet This ensures that queries for different IPs within the same subnet
resolve to the same cache key. resolve to the same cache key.
""" """
_hyperglass_builtin: bool = PrivateAttr(False) _hyperglass_builtin: bool = PrivateAttr(False)
name: str = "mikrotik_normalizer" name: str = "mikrotik_normalizer"
platforms: t.Sequence[str] = ("mikrotik_routeros", "mikrotik_switchos", "mikrotik") platforms: t.Sequence[str] = ("mikrotik_routeros", "mikrotik_switchos", "mikrotik")
@ -28,9 +30,9 @@ class MikrotikTargetNormalizerInput(InputPlugin):
# INÍCIO DA MODIFICAÇÃO: Usar 't.Any' em vez de 'Query' # INÍCIO DA MODIFICAÇÃO: Usar 't.Any' em vez de 'Query'
# ############################################################# # #############################################################
def validate(self, query: t.Any) -> InputPluginValidationReturn: def validate(self, query: t.Any) -> InputPluginValidationReturn:
# ############################################################# # #############################################################
# FIM DA MODIFICAÇÃO # FIM DA MODIFICAÇÃO
# ############################################################# # #############################################################
""" """
Takes the query object and modifies the target if it's a BGP Route query. Takes the query object and modifies the target if it's a BGP Route query.
""" """

View file

@ -96,7 +96,7 @@ def test_use_state_caching(state):
instance = use_state(attr) instance = use_state(attr)
if i == 0: if i == 0:
first = instance first = instance
assert isinstance(instance, model), ( assert isinstance(
f"{instance!r} is not an instance of '{model.__name__}'" instance, model
) ), f"{instance!r} is not an instance of '{model.__name__}'"
assert instance == first, f"{instance!r} is not equal to {first!r}" assert instance == first, f"{instance!r} is not equal to {first!r}"

View file

@ -1,4 +1,5 @@
"""Test typing utilities.""" """Test typing utilities."""
# flake8: noqa # flake8: noqa
# Standard Library # Standard Library