forked from mirrors/thatmattlove-hyperglass
Update standard structured data models
This commit is contained in:
parent
c7292dadd3
commit
98201c1752
5 changed files with 71 additions and 39 deletions
14
hyperglass/models/data/__init__.py
Normal file
14
hyperglass/models/data/__init__.py
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
"""Data structure models."""
|
||||||
|
|
||||||
|
# Standard Library
|
||||||
|
from typing import Union
|
||||||
|
|
||||||
|
# Local
|
||||||
|
from .bgp_route import BGPRouteTable
|
||||||
|
|
||||||
|
OutputDataModel = Union["BGPRouteTable"]
|
||||||
|
|
||||||
|
__all__ = (
|
||||||
|
"BGPRouteTable",
|
||||||
|
"OutputDataModel",
|
||||||
|
)
|
||||||
|
|
@ -2,11 +2,11 @@
|
||||||
|
|
||||||
# Standard Library
|
# Standard Library
|
||||||
import re
|
import re
|
||||||
from typing import List
|
from typing import List, Literal
|
||||||
from ipaddress import ip_network
|
from ipaddress import ip_network
|
||||||
|
|
||||||
# Third Party
|
# Third Party
|
||||||
from pydantic import StrictInt, StrictStr, StrictBool, constr, validator
|
from pydantic import StrictInt, StrictStr, StrictBool, validator
|
||||||
|
|
||||||
# Project
|
# Project
|
||||||
from hyperglass.configuration import params
|
from hyperglass.configuration import params
|
||||||
|
|
@ -15,11 +15,11 @@ from hyperglass.external.rpki import rpki_state
|
||||||
# Local
|
# Local
|
||||||
from ..main import HyperglassModel
|
from ..main import HyperglassModel
|
||||||
|
|
||||||
WinningWeight = constr(regex=r"(low|high)")
|
WinningWeight = Literal["low", "high"]
|
||||||
|
|
||||||
|
|
||||||
class ParsedRouteEntry(HyperglassModel):
|
class BGPRoute(HyperglassModel):
|
||||||
"""Per-Route Response Model."""
|
"""Post-parsed BGP route."""
|
||||||
|
|
||||||
prefix: StrictStr
|
prefix: StrictStr
|
||||||
active: StrictBool
|
active: StrictBool
|
||||||
|
|
@ -100,10 +100,22 @@ class ParsedRouteEntry(HyperglassModel):
|
||||||
return value
|
return value
|
||||||
|
|
||||||
|
|
||||||
class ParsedRoutes(HyperglassModel):
|
class BGPRouteTable(HyperglassModel):
|
||||||
"""Parsed Response Model."""
|
"""Post-parsed BGP route table."""
|
||||||
|
|
||||||
vrf: StrictStr
|
vrf: StrictStr
|
||||||
count: StrictInt = 0
|
count: StrictInt = 0
|
||||||
routes: List[ParsedRouteEntry]
|
routes: List[BGPRoute]
|
||||||
winning_weight: WinningWeight
|
winning_weight: WinningWeight
|
||||||
|
|
||||||
|
def __init__(self, **kwargs):
|
||||||
|
"""Sort routes by prefix after validation."""
|
||||||
|
super().__init__(**kwargs)
|
||||||
|
self.routes = sorted(self.routes, key=lambda r: r.prefix)
|
||||||
|
|
||||||
|
def __add__(self: "BGPRouteTable", other: "BGPRouteTable") -> "BGPRouteTable":
|
||||||
|
"""Merge another BGP table instance with this instance."""
|
||||||
|
if isinstance(other, BGPRouteTable):
|
||||||
|
self.routes = sorted([*self.routes, *other.routes], key=lambda r: r.prefix)
|
||||||
|
self.count = len(self.routes)
|
||||||
|
return self
|
||||||
|
|
@ -6,10 +6,10 @@ from datetime import datetime
|
||||||
|
|
||||||
# Project
|
# Project
|
||||||
from hyperglass.log import log
|
from hyperglass.log import log
|
||||||
|
from hyperglass.models.data import BGPRouteTable
|
||||||
|
|
||||||
# Local
|
# Local
|
||||||
from ..main import HyperglassModel
|
from ..main import HyperglassModel
|
||||||
from .serialized import ParsedRoutes
|
|
||||||
|
|
||||||
RPKI_STATE_MAP = {
|
RPKI_STATE_MAP = {
|
||||||
"invalid": 0,
|
"invalid": 0,
|
||||||
|
|
@ -157,7 +157,7 @@ class AristaRoute(_AristaBase):
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
serialized = ParsedRoutes(
|
serialized = BGPRouteTable(
|
||||||
vrf=self.vrf, count=count, routes=routes, winning_weight=WINNING_WEIGHT,
|
vrf=self.vrf, count=count, routes=routes, winning_weight=WINNING_WEIGHT,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,10 +9,10 @@ from pydantic import StrictInt, StrictStr, StrictBool, constr, root_validator
|
||||||
|
|
||||||
# Project
|
# Project
|
||||||
from hyperglass.log import log
|
from hyperglass.log import log
|
||||||
|
from hyperglass.models.data import BGPRouteTable
|
||||||
|
|
||||||
# Local
|
# Local
|
||||||
from ..main import HyperglassModel
|
from ..main import HyperglassModel
|
||||||
from .serialized import ParsedRoutes
|
|
||||||
|
|
||||||
FRRPeerType = constr(regex=r"(internal|external)")
|
FRRPeerType = constr(regex=r"(internal|external)")
|
||||||
|
|
||||||
|
|
@ -110,7 +110,9 @@ class FRRRoute(_FRRBase):
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
serialized = ParsedRoutes(vrf=vrf, count=len(routes), routes=routes, winning_weight="high",)
|
serialized = BGPRouteTable(
|
||||||
|
vrf=vrf, count=len(routes), routes=routes, winning_weight="high",
|
||||||
|
)
|
||||||
|
|
||||||
log.info("Serialized FRR response: {}", serialized)
|
log.info("Serialized FRR response: {}", serialized)
|
||||||
return serialized
|
return serialized
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,19 @@
|
||||||
"""Data Models for Parsing Juniper XML Response."""
|
"""Data Models for Parsing Juniper XML Response."""
|
||||||
|
|
||||||
# Standard Library
|
# Standard Library
|
||||||
from typing import Dict, List
|
from typing import Any, Dict, List
|
||||||
|
|
||||||
# Third Party
|
# Third Party
|
||||||
from pydantic import StrictInt, StrictStr, StrictBool, validator, root_validator
|
from pydantic import validator, root_validator
|
||||||
|
from pydantic.types import StrictInt, StrictStr, StrictBool
|
||||||
|
|
||||||
# Project
|
# Project
|
||||||
from hyperglass.log import log
|
from hyperglass.log import log
|
||||||
|
from hyperglass.util import deep_convert_keys
|
||||||
|
from hyperglass.models.data.bgp_route import BGPRouteTable
|
||||||
|
|
||||||
# Local
|
# Local
|
||||||
from ..main import HyperglassModel
|
from ..main import HyperglassModel
|
||||||
from .serialized import ParsedRoutes
|
|
||||||
|
|
||||||
RPKI_STATE_MAP = {
|
RPKI_STATE_MAP = {
|
||||||
"invalid": 0,
|
"invalid": 0,
|
||||||
|
|
@ -21,17 +23,19 @@ RPKI_STATE_MAP = {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def _alias_generator(field):
|
class JuniperBase(HyperglassModel, extra="ignore"):
|
||||||
return field.replace("_", "-")
|
"""Base Juniper model."""
|
||||||
|
|
||||||
|
def __init__(self, **kwargs: Any) -> None:
|
||||||
|
"""Convert all `-` keys to `_`.
|
||||||
|
|
||||||
|
Default camelCase alias generator will still be used.
|
||||||
|
"""
|
||||||
|
rebuilt = deep_convert_keys(kwargs, lambda k: k.replace("-", "_"))
|
||||||
|
super().__init__(**rebuilt)
|
||||||
|
|
||||||
|
|
||||||
class _JuniperBase(HyperglassModel):
|
class JuniperRouteTableEntry(JuniperBase):
|
||||||
class Config:
|
|
||||||
alias_generator = _alias_generator
|
|
||||||
extra = "ignore"
|
|
||||||
|
|
||||||
|
|
||||||
class JuniperRouteTableEntry(_JuniperBase):
|
|
||||||
"""Parse Juniper rt-entry data."""
|
"""Parse Juniper rt-entry data."""
|
||||||
|
|
||||||
active_tag: StrictBool
|
active_tag: StrictBool
|
||||||
|
|
@ -59,8 +63,8 @@ class JuniperRouteTableEntry(_JuniperBase):
|
||||||
nh = values.pop("nh")
|
nh = values.pop("nh")
|
||||||
|
|
||||||
# Handle Juniper's 'Indirect' Next Hop Type
|
# Handle Juniper's 'Indirect' Next Hop Type
|
||||||
if "protocol-nh" in values:
|
if "protocol_nh" in values:
|
||||||
nh = values.pop("protocol-nh")
|
nh = values.pop("protocol_nh")
|
||||||
|
|
||||||
# Force the next hops to be a list
|
# Force the next hops to be a list
|
||||||
if isinstance(nh, Dict):
|
if isinstance(nh, Dict):
|
||||||
|
|
@ -72,21 +76,21 @@ class JuniperRouteTableEntry(_JuniperBase):
|
||||||
# Extract the 'to:' value from the next-hop
|
# Extract the 'to:' value from the next-hop
|
||||||
selected_next_hop = ""
|
selected_next_hop = ""
|
||||||
for hop in next_hops:
|
for hop in next_hops:
|
||||||
if "selected-next-hop" in hop:
|
if "selected_next_hop" in hop:
|
||||||
selected_next_hop = hop.get("to", "")
|
selected_next_hop = hop.get("to", "")
|
||||||
break
|
break
|
||||||
elif hop.get("to") is not None:
|
elif hop.get("to") is not None:
|
||||||
selected_next_hop = hop["to"]
|
selected_next_hop = hop["to"]
|
||||||
break
|
break
|
||||||
|
|
||||||
values["next-hop"] = selected_next_hop
|
values["next_hop"] = selected_next_hop
|
||||||
|
|
||||||
_path_attr = values.get("bgp-path-attributes", {})
|
_path_attr = values.get("bgp_path_attributes", {})
|
||||||
_path_attr_agg = _path_attr.get("attr-aggregator", {}).get("attr-value", {})
|
_path_attr_agg = _path_attr.get("attr_aggregator", {}).get("attr_value", {})
|
||||||
values["as-path"] = _path_attr.get("attr-as-path-effective", {}).get("attr-value", "")
|
values["as_path"] = _path_attr.get("attr_as_path_effective", {}).get("attr_value", "")
|
||||||
values["source-as"] = _path_attr_agg.get("aggr-as-number", 0)
|
values["source_as"] = _path_attr_agg.get("aggr_as_number", 0)
|
||||||
values["source-rid"] = _path_attr_agg.get("aggr-router-id", "")
|
values["source_rid"] = _path_attr_agg.get("aggr_router_id", "")
|
||||||
values["peer-rid"] = values["peer-id"]
|
values["peer_rid"] = values.get("peer_id", "")
|
||||||
|
|
||||||
return values
|
return values
|
||||||
|
|
||||||
|
|
@ -132,7 +136,7 @@ class JuniperRouteTableEntry(_JuniperBase):
|
||||||
return flat
|
return flat
|
||||||
|
|
||||||
|
|
||||||
class JuniperRouteTable(_JuniperBase):
|
class JuniperRouteTable(JuniperBase):
|
||||||
"""Validation model for Juniper rt data."""
|
"""Validation model for Juniper rt data."""
|
||||||
|
|
||||||
rt_destination: StrictStr
|
rt_destination: StrictStr
|
||||||
|
|
@ -147,7 +151,7 @@ class JuniperRouteTable(_JuniperBase):
|
||||||
return int(value.get("#text"))
|
return int(value.get("#text"))
|
||||||
|
|
||||||
|
|
||||||
class JuniperRoute(_JuniperBase):
|
class JuniperBGPTable(JuniperBase):
|
||||||
"""Validation model for route-table data."""
|
"""Validation model for route-table data."""
|
||||||
|
|
||||||
table_name: StrictStr
|
table_name: StrictStr
|
||||||
|
|
@ -157,7 +161,7 @@ class JuniperRoute(_JuniperBase):
|
||||||
hidden_route_count: int
|
hidden_route_count: int
|
||||||
rt: List[JuniperRouteTable]
|
rt: List[JuniperRouteTable]
|
||||||
|
|
||||||
def serialize(self):
|
def bgp_table(self: "JuniperBGPTable") -> "BGPRouteTable":
|
||||||
"""Convert the Juniper-specific fields to standard parsed data model."""
|
"""Convert the Juniper-specific fields to standard parsed data model."""
|
||||||
vrf_parts = self.table_name.split(".")
|
vrf_parts = self.table_name.split(".")
|
||||||
if len(vrf_parts) == 2:
|
if len(vrf_parts) == 2:
|
||||||
|
|
@ -189,7 +193,7 @@ class JuniperRoute(_JuniperBase):
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
serialized = ParsedRoutes(vrf=vrf, count=count, routes=routes, winning_weight="low",)
|
serialized = BGPRouteTable(vrf=vrf, count=count, routes=routes, winning_weight="low")
|
||||||
|
|
||||||
log.debug("Serialized Juniper response: {}", serialized)
|
log.debug("Serialized Juniper response: {}", repr(serialized))
|
||||||
return serialized
|
return serialized
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue