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:
parent
22c8f8310e
commit
ae6a1a0bb8
18 changed files with 184 additions and 109 deletions
|
|
@ -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(
|
||||||
|
|
|
||||||
|
|
@ -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")
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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",
|
||||||
|
|
|
||||||
|
|
@ -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"),
|
||||||
|
|
|
||||||
4
hyperglass/external/_base.py
vendored
4
hyperglass/external/_base.py
vendored
|
|
@ -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(
|
||||||
|
|
|
||||||
19
hyperglass/external/rpki.py
vendored
19
hyperglass/external/rpki.py
vendored
|
|
@ -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)
|
||||||
|
|
|
||||||
8
hyperglass/external/tests/test_rpki.py
vendored
8
hyperglass/external/tests/test_rpki.py
vendored
|
|
@ -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
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -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],
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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."""
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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:
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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.
|
||||||
"""
|
"""
|
||||||
|
|
|
||||||
|
|
@ -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}"
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
"""Test typing utilities."""
|
"""Test typing utilities."""
|
||||||
|
|
||||||
# flake8: noqa
|
# flake8: noqa
|
||||||
|
|
||||||
# Standard Library
|
# Standard Library
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue