mirror of
				https://github.com/checktheroads/hyperglass
				synced 2024-05-11 05:55:08 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			166 lines
		
	
	
		
			4.7 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			166 lines
		
	
	
		
			4.7 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
"""Data Models for Parsing Arista JSON Response."""
 | 
						|
 | 
						|
# Standard Library
 | 
						|
from typing import Dict, List, Optional
 | 
						|
from datetime import datetime
 | 
						|
 | 
						|
# Project
 | 
						|
from hyperglass.log import log
 | 
						|
 | 
						|
# Local
 | 
						|
from ..main import HyperglassModel
 | 
						|
from .serialized import ParsedRoutes
 | 
						|
 | 
						|
RPKI_STATE_MAP = {
 | 
						|
    "invalid": 0,
 | 
						|
    "valid": 1,
 | 
						|
    "notFound": 2,
 | 
						|
    "notValidated": 3,
 | 
						|
}
 | 
						|
 | 
						|
WINNING_WEIGHT = "high"
 | 
						|
 | 
						|
 | 
						|
def _alias_generator(field: str) -> str:
 | 
						|
    caps = "".join(x for x in field.title() if x.isalnum())
 | 
						|
    return caps[0].lower() + caps[1:]
 | 
						|
 | 
						|
 | 
						|
class _AristaBase(HyperglassModel):
 | 
						|
    """Base Model for Arista validation."""
 | 
						|
 | 
						|
    class Config:
 | 
						|
        extra = "ignore"
 | 
						|
        alias_generator = _alias_generator
 | 
						|
 | 
						|
 | 
						|
class AristaAsPathEntry(_AristaBase):
 | 
						|
    """Validation model for Arista asPathEntry."""
 | 
						|
 | 
						|
    as_path_type: str = "External"
 | 
						|
    as_path: Optional[str] = ""
 | 
						|
 | 
						|
 | 
						|
class AristaPeerEntry(_AristaBase):
 | 
						|
    """Validation model for Arista peerEntry."""
 | 
						|
 | 
						|
    peer_router_id: str
 | 
						|
    peer_addr: str
 | 
						|
 | 
						|
 | 
						|
class AristaRouteType(_AristaBase):
 | 
						|
    """Validation model for Arista routeType."""
 | 
						|
 | 
						|
    origin: str
 | 
						|
    suppressed: bool
 | 
						|
    valid: bool
 | 
						|
    active: bool
 | 
						|
    origin_validity: Optional[str] = "notVerified"
 | 
						|
 | 
						|
 | 
						|
class AristaRouteDetail(_AristaBase):
 | 
						|
    """Validation for Arista routeDetail."""
 | 
						|
 | 
						|
    origin: str
 | 
						|
    label_stack: List = []
 | 
						|
    ext_community_list: List[str] = []
 | 
						|
    ext_community_list_raw: List[str] = []
 | 
						|
    community_list: List[str] = []
 | 
						|
    large_community_list: List[str] = []
 | 
						|
 | 
						|
 | 
						|
class AristaRoutePath(_AristaBase):
 | 
						|
    """Validation model for Arista bgpRoutePaths."""
 | 
						|
 | 
						|
    as_path_entry: AristaAsPathEntry
 | 
						|
    med: int
 | 
						|
    local_preference: int
 | 
						|
    weight: int
 | 
						|
    peer_entry: AristaPeerEntry
 | 
						|
    reason_not_bestpath: str
 | 
						|
    timestamp: int = int(datetime.utcnow().timestamp())
 | 
						|
    next_hop: str
 | 
						|
    route_type: AristaRouteType
 | 
						|
    route_detail: Optional[AristaRouteDetail]
 | 
						|
 | 
						|
 | 
						|
class AristaRouteEntry(_AristaBase):
 | 
						|
    """Validation model for Arista bgpRouteEntries."""
 | 
						|
 | 
						|
    total_paths: int = 0
 | 
						|
    bgp_advertised_peer_groups: Dict = {}
 | 
						|
    mask_length: int
 | 
						|
    bgp_route_paths: List[AristaRoutePath] = []
 | 
						|
 | 
						|
 | 
						|
class AristaRoute(_AristaBase):
 | 
						|
    """Validation model for Arista bgpRouteEntries data."""
 | 
						|
 | 
						|
    router_id: str
 | 
						|
    vrf: str
 | 
						|
    bgp_route_entries: Dict[str, AristaRouteEntry]
 | 
						|
    # The raw value is really a string, but `int` will convert it.
 | 
						|
    asn: int
 | 
						|
 | 
						|
    @staticmethod
 | 
						|
    def _get_route_age(timestamp: int) -> int:
 | 
						|
        now = datetime.utcnow()
 | 
						|
        now_timestamp = int(now.timestamp())
 | 
						|
        return now_timestamp - timestamp
 | 
						|
 | 
						|
    @staticmethod
 | 
						|
    def _get_as_path(as_path: str) -> List[str]:
 | 
						|
        if as_path == "":
 | 
						|
            return []
 | 
						|
        return [int(p) for p in as_path.split() if p.isdecimal()]
 | 
						|
 | 
						|
    def serialize(self):
 | 
						|
        """Convert the Arista-formatted fields to standard parsed data model."""
 | 
						|
        routes = []
 | 
						|
        count = 0
 | 
						|
        for prefix, entries in self.bgp_route_entries.items():
 | 
						|
 | 
						|
            count += entries.total_paths
 | 
						|
 | 
						|
            for route in entries.bgp_route_paths:
 | 
						|
 | 
						|
                as_path = self._get_as_path(route.as_path_entry.as_path)
 | 
						|
                rpki_state = RPKI_STATE_MAP.get(route.route_type.origin_validity, 3)
 | 
						|
 | 
						|
                # BGP AS Path and BGP Community queries do not include the routeDetail
 | 
						|
                # block. Therefore, we must verify it exists before including its data.
 | 
						|
                communities = []
 | 
						|
                if route.route_detail is not None:
 | 
						|
                    communities = route.route_detail.community_list
 | 
						|
 | 
						|
                # iBGP paths contain an empty AS_PATH array. If the AS_PATH is empty, we
 | 
						|
                # set the source_as to the router's local-as.
 | 
						|
                source_as = self.asn
 | 
						|
                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,
 | 
						|
                    }
 | 
						|
                )
 | 
						|
 | 
						|
        serialized = ParsedRoutes(
 | 
						|
            vrf=self.vrf, count=count, routes=routes, winning_weight=WINNING_WEIGHT,
 | 
						|
        )
 | 
						|
 | 
						|
        log.debug("Serialized Arista response: {}", serialized)
 | 
						|
        return serialized
 |