Update standard structured data models

This commit is contained in:
thatmattlove 2021-09-13 02:35:52 -07:00
parent c7292dadd3
commit 98201c1752
5 changed files with 71 additions and 39 deletions

View 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",
)

View file

@ -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

View file

@ -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,
) )

View file

@ -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

View file

@ -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