From d025630b83d89dfda18cf9c4d54af6cd52d8afa7 Mon Sep 17 00:00:00 2001 From: Wilhelm Schonfeldt Date: Fri, 26 Sep 2025 16:37:51 +0200 Subject: [PATCH 1/3] Fix: Make Huawei BGP parser conform to MikroTik approach - Remove custom __init__ from HuaweiBGPRouteTable class - Use standard BGPRouteTable inheritance like MikroTik does - Add BGPRoute import and instantiate BGPRoute objects in bgp_table() - Enable proper Pydantic validation for both table and routes - Ensure consistent behavior across all BGP parsers - Update .gitignore to exclude dev-build.sh and dev-docker/ --- .gitignore | 2 ++ hyperglass/models/parsing/huawei.py | 14 ++++---------- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/.gitignore b/.gitignore index dff8f8c..2a0786d 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,8 @@ hyperglass/hyperglass/static TODO* .env +dev-build.sh +dev-docker/ test.py .DS_Store diff --git a/hyperglass/models/parsing/huawei.py b/hyperglass/models/parsing/huawei.py index 5b7671e..c93c1d0 100644 --- a/hyperglass/models/parsing/huawei.py +++ b/hyperglass/models/parsing/huawei.py @@ -9,7 +9,7 @@ from pydantic import ConfigDict, field_validator, model_validator # Project from hyperglass.log import log -from hyperglass.models.data.bgp_route import BGPRouteTable +from hyperglass.models.data.bgp_route import BGPRoute, BGPRouteTable # Local from ..main import HyperglassModel @@ -318,15 +318,9 @@ def _extract_route_entries(lines: t.List[str]) -> t.List[HuaweiRouteEntry]: class HuaweiBGPRouteTable(BGPRouteTable): - """Custom BGP Route Table for Huawei that bypasses validation.""" + """Canonical Huawei BGP Route Table.""" - def __init__(self, **kwargs): - """Initialize without calling parent validation.""" - # Set attributes directly without validation using object.__setattr__ - object.__setattr__(self, "vrf", kwargs.get("vrf", "default")) - object.__setattr__(self, "count", kwargs.get("count", 0)) - object.__setattr__(self, "routes", kwargs.get("routes", [])) - object.__setattr__(self, "winning_weight", kwargs.get("winning_weight", "low")) + # No custom __init__ needed; inherit from BGPRouteTable (which should be a Pydantic model) class HuaweiBGPTable(HuaweiBase): @@ -388,7 +382,7 @@ class HuaweiBGPTable(HuaweiBase): RPKI_STATE_MAP.get("unknown") if route.is_valid else RPKI_STATE_MAP.get("valid") ), } - routes.append(route_data) + routes.append(BGPRoute(**route_data)) return HuaweiBGPRouteTable( vrf="default", From 6af39fb5be204e343854679200c5a2c22433d2d3 Mon Sep 17 00:00:00 2001 From: Wilhelm Schonfeldt Date: Fri, 26 Sep 2025 16:53:36 +0200 Subject: [PATCH 2/3] Fix: Standardize BGP parsers to use BGPRoute objects and harmonize variable naming - Add BGPRoute import to Juniper, Arista, and FRR parsers - Change all parsers to instantiate BGPRoute objects instead of raw dictionaries - Harmonize variable naming across all BGP parsers: - Use 'route_data' for dictionary variable - Use 'routes' for output list - Use 'route' for loop variable - Ensure consistent validation behavior across MikroTik, Huawei, Juniper, Arista, and FRR - All parsers now properly invoke BGP table class validation --- hyperglass/models/parsing/arista_eos.py | 35 +++++++++++----------- hyperglass/models/parsing/frr.py | 39 ++++++++++++------------- hyperglass/models/parsing/juniper.py | 35 +++++++++++----------- hyperglass/models/parsing/mikrotik.py | 38 ++++++++++++------------ 4 files changed, 72 insertions(+), 75 deletions(-) diff --git a/hyperglass/models/parsing/arista_eos.py b/hyperglass/models/parsing/arista_eos.py index 6f41ad1..6a9d388 100644 --- a/hyperglass/models/parsing/arista_eos.py +++ b/hyperglass/models/parsing/arista_eos.py @@ -9,7 +9,7 @@ from pydantic import ConfigDict # Project from hyperglass.log import log -from hyperglass.models.data import BGPRouteTable +from hyperglass.models.data import BGPRoute, BGPRouteTable # Local from ..main import HyperglassModel @@ -138,23 +138,22 @@ class AristaBGPTable(_AristaBase): if len(as_path) != 0: source_as = as_path[0] - routes.append( - { - "prefix": prefix, - "active": route.route_type.active, - "age": self._get_route_age(route.timestamp), - "weight": route.weight, - "med": route.med, - "local_preference": route.local_preference, - "as_path": as_path, - "communities": communities, - "next_hop": route.next_hop, - "source_as": source_as, - "source_rid": route.peer_entry.peer_router_id, - "peer_rid": route.peer_entry.peer_router_id, - "rpki_state": rpki_state, - } - ) + route_data = { + "prefix": prefix, + "active": route.route_type.active, + "age": self._get_route_age(route.timestamp), + "weight": route.weight, + "med": route.med, + "local_preference": route.local_preference, + "as_path": as_path, + "communities": communities, + "next_hop": route.next_hop, + "source_as": source_as, + "source_rid": route.peer_entry.peer_router_id, + "peer_rid": route.peer_entry.peer_router_id, + "rpki_state": rpki_state, + } + routes.append(BGPRoute(**route_data)) serialized = BGPRouteTable( vrf=self.vrf, diff --git a/hyperglass/models/parsing/frr.py b/hyperglass/models/parsing/frr.py index dacd82c..4525a84 100644 --- a/hyperglass/models/parsing/frr.py +++ b/hyperglass/models/parsing/frr.py @@ -9,7 +9,7 @@ from pydantic import ConfigDict, model_validator # Project from hyperglass.log import log -from hyperglass.models.data import BGPRouteTable +from hyperglass.models.data import BGPRoute, BGPRouteTable # Local from ..main import HyperglassModel @@ -91,25 +91,24 @@ class FRRBGPTable(_FRRBase): now = datetime.utcnow().timestamp() then = datetime.utcfromtimestamp(route.last_update).timestamp() age = int(now - then) - routes.append( - { - "prefix": self.prefix, - "active": route.bestpath, - "age": age, - "weight": route.weight, - "med": route.med, - "local_preference": route.loc_prf, - "as_path": route.aspath, - "communities": route.community, - "next_hop": route.nexthops[0].ip, - "source_as": route.aggregator_as, - "source_rid": route.aggregator_id, - "peer_rid": route.peer.peer_id, - # TODO: somehow, get the actual RPKI state - # This depends on whether or not the RPKI module is enabled in FRR - "rpki_state": 3, - } - ) + route_data = { + "prefix": self.prefix, + "active": route.bestpath, + "age": age, + "weight": route.weight, + "med": route.med, + "local_preference": route.loc_prf, + "as_path": route.aspath, + "communities": route.community, + "next_hop": route.nexthops[0].ip, + "source_as": route.aggregator_as, + "source_rid": route.aggregator_id, + "peer_rid": route.peer.peer_id, + # TODO: somehow, get the actual RPKI state + # This depends on whether or not the RPKI module is enabled in FRR + "rpki_state": 3, + } + routes.append(BGPRoute(**route_data)) serialized = BGPRouteTable( vrf=vrf, diff --git a/hyperglass/models/parsing/juniper.py b/hyperglass/models/parsing/juniper.py index f4f999c..f73f01a 100644 --- a/hyperglass/models/parsing/juniper.py +++ b/hyperglass/models/parsing/juniper.py @@ -9,7 +9,7 @@ from pydantic import ConfigDict, field_validator, model_validator # Project from hyperglass.log import log from hyperglass.util import deep_convert_keys -from hyperglass.models.data.bgp_route import BGPRouteTable +from hyperglass.models.data.bgp_route import BGPRoute, BGPRouteTable # Local from ..main import HyperglassModel @@ -176,23 +176,22 @@ class JuniperBGPTable(JuniperBase): count += table.rt_entry_count prefix = "/".join(str(i) for i in (table.rt_destination, table.rt_prefix_length)) for route in table.rt_entry: - routes.append( - { - "prefix": prefix, - "active": route.active_tag, - "age": route.age, - "weight": route.preference, - "med": route.metric, - "local_preference": route.local_preference, - "as_path": route.as_path, - "communities": route.communities, - "next_hop": route.next_hop, - "source_as": route.source_as, - "source_rid": route.source_rid, - "peer_rid": route.peer_rid, - "rpki_state": route.validation_state, - } - ) + route_data = { + "prefix": prefix, + "active": route.active_tag, + "age": route.age, + "weight": route.preference, + "med": route.metric, + "local_preference": route.local_preference, + "as_path": route.as_path, + "communities": route.communities, + "next_hop": route.next_hop, + "source_as": route.source_as, + "source_rid": route.source_rid, + "peer_rid": route.peer_rid, + "rpki_state": route.validation_state, + } + routes.append(BGPRoute(**route_data)) serialized = BGPRouteTable(vrf=vrf, count=count, routes=routes, winning_weight="low") log.bind(platform="juniper", response=repr(serialized)).debug("Serialized response") diff --git a/hyperglass/models/parsing/mikrotik.py b/hyperglass/models/parsing/mikrotik.py index cc48dcb..941cf5a 100644 --- a/hyperglass/models/parsing/mikrotik.py +++ b/hyperglass/models/parsing/mikrotik.py @@ -282,28 +282,28 @@ class MikrotikBGPTable(MikrotikBase): return inst def bgp_table(self) -> BGPRouteTable: - out = [] - for r in self.routes: - route_dict = { - "prefix": r.prefix, - "active": r.active, - "age": r.age, - "weight": r.weight, - "med": r.med, - "local_preference": r.local_preference, - "as_path": r.as_path, - "communities": r.all_communities, - "next_hop": r.next_hop, - "source_as": r.source_as, - "source_rid": r.source_rid, - "peer_rid": r.peer_rid, - "rpki_state": r.rpki_state, + routes = [] + for route in self.routes: + route_data = { + "prefix": route.prefix, + "active": route.active, + "age": route.age, + "weight": route.weight, + "med": route.med, + "local_preference": route.local_preference, + "as_path": route.as_path, + "communities": route.all_communities, + "next_hop": route.next_hop, + "source_as": route.source_as, + "source_rid": route.source_rid, + "peer_rid": route.peer_rid, + "rpki_state": route.rpki_state, } # Instantiate BGPRoute to trigger validation (including external RPKI) - out.append(BGPRoute(**route_dict)) + routes.append(BGPRoute(**route_data)) return MikrotikBGPRouteTable( vrf="default", - count=len(out), - routes=out, + count=len(routes), + routes=routes, winning_weight="low", ) From c394d93326a00d6cd669dddb787ca54050b8248b Mon Sep 17 00:00:00 2001 From: Wilhelm Schonfeldt Date: Fri, 26 Sep 2025 16:59:10 +0200 Subject: [PATCH 3/3] Fix: Export BGPRoute class in data models __init__.py - Add BGPRoute import and export in hyperglass.models.data.__init__.py - This allows BGP parsers to properly import BGPRoute for validation - Resolves ImportError when starting hyperglass application --- hyperglass/models/data/__init__.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/hyperglass/models/data/__init__.py b/hyperglass/models/data/__init__.py index 02d8f27..d19d5e8 100644 --- a/hyperglass/models/data/__init__.py +++ b/hyperglass/models/data/__init__.py @@ -4,11 +4,12 @@ from typing import Union # Local -from .bgp_route import BGPRouteTable +from .bgp_route import BGPRoute, BGPRouteTable OutputDataModel = Union[BGPRouteTable] __all__ = ( + "BGPRoute", "BGPRouteTable", "OutputDataModel", )