1
0
mirror of https://github.com/checktheroads/hyperglass synced 2024-05-11 05:55:08 +00:00

add VRF support

This commit is contained in:
checktheroads
2019-09-30 07:51:17 -07:00
parent ed6770fc0f
commit f756e11615
12 changed files with 340 additions and 219 deletions

View File

@@ -23,7 +23,6 @@ from hyperglass.configuration.models import (
credentials as _credentials,
)
from hyperglass.exceptions import ConfigError, ConfigInvalid, ConfigMissing
from hyperglass.constants import afi_nos_map
# Project Directories
working_dir = Path(__file__).resolve().parent
@@ -97,11 +96,17 @@ except ValidationError as validation_errors:
# Validate that VRFs configured on a device are actually defined
for dev in devices.hostnames:
dev_cls = getattr(devices, dev)
for vrf in getattr(dev_cls, "vrfs"):
display_vrfs = []
for vrf in getattr(dev_cls, "_vrfs"):
if vrf not in vrfs._all:
raise ConfigInvalid(
field=vrf, error_msg=f"{vrf} is not in configured VRFs: {vrfs._all}"
)
vrf_attr = getattr(vrfs, vrf)
display_vrfs.append(vrf_attr.display_name)
devices.routers[dev]["display_vrfs"] = display_vrfs
setattr(dev_cls, "display_vrfs", display_vrfs)
# Logzero Configuration
log_level = 20
@@ -182,7 +187,7 @@ class Networks:
router: {
"location": router_params["location"],
"display_name": router_params["display_name"],
"vrfs": router_params["vrfs"],
"vrfs": router_params["display_vrfs"],
}
}
)
@@ -191,7 +196,7 @@ class Networks:
router: {
"location": router_params["location"],
"display_name": router_params["display_name"],
"vrfs": router_params["vrfs"],
"vrfs": router_params["display_vrfs"],
}
}
if not frontend_dict:

View File

@@ -72,7 +72,7 @@ class Commands(BaseSettings):
class CiscoIOS(BaseSettings):
"""Class model for default cisco_ios commands"""
class VPNv4IPv4(BaseSettings):
class IPv4Vrf(BaseSettings):
"""Default commands for dual afi commands"""
bgp_community: str = "show bgp {afi} unicast vrf {vrf} community {target}"
@@ -80,11 +80,10 @@ class Commands(BaseSettings):
bgp_route: str = "show bgp {afi} unicast vrf {vrf} {target}"
ping: str = "ping vrf {vrf} {target} repeat 5 source {source}"
traceroute: str = (
"traceroute vrf {vrf} {target} timeout 1 probe 2 source {source} "
"| exclude Type escape"
"traceroute vrf {vrf} {target} timeout 1 probe 2 source {source}"
)
class VPNv6IPv6(BaseSettings):
class IPv6Vrf(BaseSettings):
"""Default commands for dual afi commands"""
bgp_community: str = "show bgp {afi} unicast vrf {vrf} community {target}"
@@ -92,40 +91,33 @@ class Commands(BaseSettings):
bgp_route: str = "show bgp {afi} unicast vrf {vrf} {target}"
ping: str = "ping vrf {vrf} {target} repeat 5 source {source}"
traceroute: str = (
"traceroute vrf {vrf} {target} timeout 1 probe 2 source {source} "
"| exclude Type escape"
"traceroute vrf {vrf} {target} timeout 1 probe 2 source {source}"
)
class IPv4(BaseSettings):
class IPv4Default(BaseSettings):
"""Default commands for ipv4 commands"""
bgp_community: str = "show bgp {afi} unicast community {target}"
bgp_aspath: str = 'show bgp {afi} unicast quote-regexp "{target}"'
bgp_route: str = "show bgp {afi} unicast {target} | exclude pathid:|Epoch"
ping: str = "ping {target} repeat 5 source {source} | exclude Type escape"
traceroute: str = (
"traceroute {target} timeout 1 probe 2 source {source} "
"| exclude Type escape"
)
ping: str = "ping {target} repeat 5 source {source}"
traceroute: str = "traceroute {target} timeout 1 probe 2 source {source}"
class IPv6(BaseSettings):
class IPv6Default(BaseSettings):
"""Default commands for ipv6 commands"""
bgp_community: str = "show bgp {afi} unicast community {target}"
bgp_aspath: str = 'show bgp {afi} unicast quote-regexp "{target}"'
bgp_route: str = "show bgp {afi} unicast {target} | exclude pathid:|Epoch"
ping: str = (
"ping {afi} {target} repeat 5 source {source} | exclude Type escape"
)
ping: str = ("ping {afi} {target} repeat 5 source {source}")
traceroute: str = (
"traceroute ipv6 {target} timeout 1 probe 2 source {source} "
"| exclude Type escape"
"traceroute ipv6 {target} timeout 1 probe 2 source {source}"
)
ipv4: IPv4 = IPv4()
ipv6: IPv6 = IPv6()
vpn_ipv4: VPNv4IPv4 = VPNv4IPv4()
vpn_ipv6: VPNv6IPv6 = VPNv6IPv6()
ipv4_default: IPv4Default = IPv4Default()
ipv6_default: IPv6Default = IPv6Default()
ipv4_vrf: IPv4Vrf = IPv4Vrf()
ipv6_vrf: IPv6Vrf = IPv6Vrf()
class CiscoXR(BaseSettings):
"""Class model for default cisco_xr commands"""

View File

@@ -7,22 +7,15 @@ Validates input for overridden parameters.
"""
# Standard Library Imports
from math import ceil
from typing import List
# Third Party Imports
from pydantic import BaseSettings
from pydantic import IPvAnyNetwork
from pydantic import constr
class Features(BaseSettings):
"""Class model for params.features"""
class Vrf(BaseSettings):
"""Class model for params.features.vrf"""
enable: bool = False
class BgpRoute(BaseSettings):
"""Class model for params.features.bgp_route"""
@@ -68,19 +61,6 @@ class Features(BaseSettings):
enable: bool = True
class Blacklist(BaseSettings):
"""Class model for params.features.blacklist"""
enable: bool = True
networks: List[IPvAnyNetwork] = [
"198.18.0.0/15",
"100.64.0.0/10",
"2001:db8::/32",
"10.0.0.0/8",
"192.168.0.0/16",
"172.16.0.0/12",
]
class Cache(BaseSettings):
"""Class model for params.features.cache"""
@@ -138,8 +118,6 @@ class Features(BaseSettings):
bgp_aspath: BgpAsPath = BgpAsPath()
ping: Ping = Ping()
traceroute: Traceroute = Traceroute()
blacklist: Blacklist = Blacklist()
cache: Cache = Cache()
max_prefix: MaxPrefix = MaxPrefix()
rate_limit: RateLimit = RateLimit()
vrf: Vrf = Vrf()

View File

@@ -16,13 +16,15 @@ class Messages(BaseSettings):
no_query_type: str = "A query type must be specified."
no_location: str = "A location must be selected."
no_input: str = "{field} must be specified."
blacklist: str = "{target} a member of {blacklisted_net}, which is not allowed."
acl_denied: str = "{target} is a member of {denied_network}, which is not allowed."
acl_not_allowed: str = "{target} is not allowed."
max_prefix: str = (
"Prefix length must be shorter than /{max_length}. {target} is too specific."
)
requires_ipv6_cidr: str = (
"{device_name} requires IPv6 BGP lookups to be in CIDR notation."
)
feature_not_enabled: str = "{feature} is not enabled for {device_name}."
invalid_input: str = "{target} is not a valid {query_type} target."
invalid_field: str = "{input} is an invalid {field}."
general: str = "Something went wrong."
@@ -31,5 +33,5 @@ class Messages(BaseSettings):
connection_error: str = "Error connecting to {device_name}: {error}"
authentication_error: str = "Authentication error occurred."
noresponse_error: str = "No response."
vrf_not_associated: str = "{vrf} is not associated with {device_name}."
vrf_not_associated: str = "VRF {vrf_name} is not associated with {device_name}."
no_matching_vrfs: str = "No VRFs Match"

View File

@@ -8,34 +8,71 @@ Validates input for overridden parameters.
# Standard Library Imports
from typing import List
from typing import Union
from ipaddress import IPv4Address, IPv6Address
# Third Party Imports
from pydantic import BaseSettings
from pydantic import IPvAnyAddress
from pydantic import validator
from logzero import logger
from logzero import logger as log
# Project Imports
from hyperglass.configuration.models._utils import clean_name
from hyperglass.constants import Supported
from hyperglass.exceptions import UnsupportedDevice
from hyperglass.constants import afi_nos_map
from hyperglass.exceptions import ConfigError
class Afi(BaseSettings):
class DeviceVrf4(BaseSettings):
"""Model for AFI definitions"""
label: str
source: IPvAnyAddress
afi_name: str = ""
vrf_name: str = ""
source_address: IPv4Address
class Config:
"""Pydantic Config"""
validate_assignment = True
validate_all = True
class Afis(BaseSettings):
"""Model for AFI map"""
class DeviceVrf6(BaseSettings):
"""Model for AFI definitions"""
ipv4: Union[Afi, None] = None
ipv6: Union[Afi, None] = None
ipv4_vpn: Union[Afi, None] = None
ipv6_vpn: Union[Afi, None] = None
afi_name: str = ""
vrf_name: str = ""
source_address: IPv6Address
class Config:
"""Pydantic Config"""
validate_assignment = True
validate_all = True
class VrfAfis(BaseSettings):
"""Model for per-AFI dicts of VRF params"""
ipv4: Union[DeviceVrf4, None] = None
ipv6: Union[DeviceVrf6, None] = None
class Config:
"""Pydantic Config"""
validate_assignment = True
validate_all = True
class Vrf(BaseSettings):
default: VrfAfis
class Config:
"""Pydantic Config"""
extra = "allow"
validate_assignment = True
validate_all = True
class Router(BaseSettings):
@@ -50,8 +87,9 @@ class Router(BaseSettings):
port: int
nos: str
commands: Union[str, None] = None
vrfs: List[str] = ["default"]
afis: Afis
vrfs: Vrf
_vrfs: List[str]
display_vrfs: List[str] = []
@validator("nos")
def supported_nos(cls, v): # noqa: N805
@@ -76,23 +114,48 @@ class Router(BaseSettings):
v = values["nos"]
return v
@validator("afis", pre=True)
def validate_afis(cls, v, values): # noqa: N805
@validator("vrfs", pre=True, whole=True, always=True)
def validate_vrfs(cls, v, values): # noqa: N805
"""
If an AFI map is not defined, try to get one based on the
NOS name. If that doesn't exist, use a default.
"""
logger.debug(f"V In: {v}")
for (afi_name, afi_params) in {
afi: params for afi, params in v.items() if params is not None
}.items():
if afi_params.get("label") is None:
label = afi_nos_map.get(values["nos"], None)
if label is None:
label = afi_nos_map["default"][afi_name]
v[afi_name].update({"label": label})
log.debug(f"Start: {v}")
_vrfs = []
for vrf_label, vrf_afis in v.items():
if vrf_label is None:
raise ConfigError(
"The default routing table with source IPs must be defined"
)
vrf_label = clean_name(vrf_label)
_vrfs.append(vrf_label)
if not vrf_afis.get("ipv4"):
vrf_afis.update({"ipv4": None})
if not vrf_afis.get("ipv6"):
vrf_afis.update({"ipv6": None})
for afi, params in {
a: p for a, p in vrf_afis.items() if p is not None
}.items():
if not params.get("source_address"):
raise ConfigError(
'A "source_address" must be defined in {afi}', afi=afi
)
if not params.get("afi_name"):
params.update({"afi_name": afi})
if not params.get("vrf_name"):
params.update({"vrf_name": vrf_label})
setattr(Vrf, vrf_label, VrfAfis(**vrf_afis))
log.debug(_vrfs)
values["_vrfs"] = _vrfs
return v
class Config:
"""Pydantic Config"""
validate_assignment = True
validate_all = True
extra = "allow"
class Routers(BaseSettings):
"""Base model for devices class."""

View File

@@ -7,20 +7,45 @@ Validates input for overridden parameters.
"""
# Standard Library Imports
from typing import List
from typing import Dict
# Third Party Imports
from pydantic import BaseSettings
from pydantic import IPvAnyNetwork
from pydantic import validator
# Project Imports
from hyperglass.configuration.models._utils import clean_name
from hyperglass.exceptions import ConfigInvalid
class Vrf(BaseSettings):
"""Model for per VRF/afi config in devices.yaml"""
display_name: str
label: str
afis: List[str]
ipv4: bool = True
ipv6: bool = True
access_list: List[Dict[str, IPvAnyNetwork]] = [
{"allow": "0.0.0.0/0"},
{"allow": "::/0"},
]
@validator("access_list", whole=True, always=True)
def validate_action(cls, value):
allowed_actions = ("allow", "deny")
for li in value:
for action, network in li.items():
if action not in allowed_actions:
raise ConfigInvalid(
field=action,
error_msg=(
"Access List Entries must be formatted as "
'"- action: network" (list of dictionaries with the action '
"as the key, and the network as the value), e.g. "
'"- deny: 192.0.2.0/24 or "- allow: 2001:db8::/32".'
),
)
return value
class Vrfs(BaseSettings):
@@ -33,29 +58,25 @@ class Vrfs(BaseSettings):
characters from VRF names, dynamically sets attributes for
the Vrfs class.
"""
vrfs: Vrf = {
"default": {
"display_name": "Default",
"label": "default",
"afis": ["ipv4, ipv6"],
}
}
labels: List[str] = ["default"]
_all: List[str] = ["default"]
# Default settings which include the default/global routing table
vrfs: Vrf = {"default": {"display_name": "Global", "ipv4": True, "ipv6": True}}
display_names: List[str] = ["Global"]
_all: List[str] = ["global"]
for (vrf_key, params) in input_params.items():
vrf = clean_name(vrf_key)
vrf_params = Vrf(**params)
vrfs.update({vrf: vrf_params.dict()})
labels.append(params.get("label"))
display_names.append(params.get("display_name"))
_all.append(vrf_key)
for (vrf_key, params) in vrfs.items():
setattr(Vrfs, vrf_key, params)
setattr(Vrfs, vrf_key, Vrf(**params))
labels: List[str] = list(set(labels))
display_names: List[str] = list(set(display_names))
_all: List[str] = list(set(_all))
Vrfs.vrfs = vrfs
Vrfs.labels = labels
Vrfs.display_names = display_names
Vrfs._all = _all
return Vrfs()