2019-12-29 23:57:39 -07:00
|
|
|
"""Validate router configuration variables."""
|
2019-09-11 01:35:31 -07:00
|
|
|
|
|
|
|
# Standard Library Imports
|
2019-10-09 03:10:52 -07:00
|
|
|
import re
|
2019-09-11 01:35:31 -07:00
|
|
|
from typing import List
|
|
|
|
from typing import Union
|
2019-10-04 16:54:32 -07:00
|
|
|
|
2019-09-11 01:35:31 -07:00
|
|
|
# Third Party Imports
|
2019-12-31 18:29:43 -07:00
|
|
|
from pydantic import StrictInt
|
|
|
|
from pydantic import StrictStr
|
2019-09-11 01:35:31 -07:00
|
|
|
from pydantic import validator
|
|
|
|
|
|
|
|
# Project Imports
|
2019-10-04 16:54:32 -07:00
|
|
|
from hyperglass.configuration.models._utils import HyperglassModel
|
2019-10-09 03:10:52 -07:00
|
|
|
from hyperglass.configuration.models._utils import HyperglassModelExtra
|
|
|
|
from hyperglass.configuration.models._utils import clean_name
|
|
|
|
from hyperglass.configuration.models.commands import Command
|
|
|
|
from hyperglass.configuration.models.credentials import Credential
|
|
|
|
from hyperglass.configuration.models.networks import Network
|
|
|
|
from hyperglass.configuration.models.proxies import Proxy
|
2019-12-30 09:47:31 -07:00
|
|
|
from hyperglass.configuration.models.vrfs import DefaultVrf
|
|
|
|
from hyperglass.configuration.models.vrfs import Vrf
|
2019-09-11 01:35:31 -07:00
|
|
|
from hyperglass.constants import Supported
|
2019-09-30 07:51:17 -07:00
|
|
|
from hyperglass.exceptions import ConfigError
|
2019-10-09 03:10:52 -07:00
|
|
|
from hyperglass.exceptions import UnsupportedDevice
|
2019-12-29 23:57:39 -07:00
|
|
|
from hyperglass.util import log
|
2019-09-13 00:36:58 -07:00
|
|
|
|
2019-09-11 01:35:31 -07:00
|
|
|
|
2019-10-04 16:54:32 -07:00
|
|
|
class Router(HyperglassModel):
|
2019-12-29 23:57:39 -07:00
|
|
|
"""Validation model for per-router config in devices.yaml."""
|
2019-09-11 01:35:31 -07:00
|
|
|
|
2019-12-31 18:29:43 -07:00
|
|
|
name: StrictStr
|
|
|
|
address: StrictStr
|
2019-10-09 03:10:52 -07:00
|
|
|
network: Network
|
|
|
|
credential: Credential
|
|
|
|
proxy: Union[Proxy, None] = None
|
2019-12-31 18:29:43 -07:00
|
|
|
location: StrictStr
|
|
|
|
display_name: StrictStr
|
|
|
|
port: StrictInt
|
|
|
|
nos: StrictStr
|
2019-10-09 03:10:52 -07:00
|
|
|
commands: Union[Command, None] = None
|
|
|
|
vrfs: List[Vrf] = [DefaultVrf()]
|
2019-12-31 18:29:43 -07:00
|
|
|
display_vrfs: List[StrictStr] = []
|
|
|
|
vrf_names: List[StrictStr] = []
|
2019-09-11 01:35:31 -07:00
|
|
|
|
|
|
|
@validator("nos")
|
2019-12-31 18:29:43 -07:00
|
|
|
def supported_nos(cls, value):
|
|
|
|
"""Validate that nos is supported by hyperglass.
|
|
|
|
|
|
|
|
Raises:
|
|
|
|
UnsupportedDevice: Raised if nos is unsupported.
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
{str} -- Valid NOS
|
2019-09-13 00:36:58 -07:00
|
|
|
"""
|
2019-12-31 18:29:43 -07:00
|
|
|
if not Supported.is_supported(value):
|
|
|
|
raise UnsupportedDevice(f'"{value}" device type is not supported.')
|
|
|
|
return value
|
2019-09-11 01:35:31 -07:00
|
|
|
|
2019-10-09 03:10:52 -07:00
|
|
|
@validator("name", "location")
|
2019-12-31 18:29:43 -07:00
|
|
|
def clean_name(cls, value):
|
|
|
|
"""Remove or replace unsupported characters from field values.
|
2019-09-11 01:35:31 -07:00
|
|
|
|
2019-12-31 18:29:43 -07:00
|
|
|
Arguments:
|
|
|
|
value {str} -- Raw name/location
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
{} -- Valid name/location
|
2019-09-13 00:36:58 -07:00
|
|
|
"""
|
2019-12-31 18:29:43 -07:00
|
|
|
return clean_name(value)
|
|
|
|
|
|
|
|
@validator("commands", always=True)
|
|
|
|
def validate_commands(cls, value, values):
|
|
|
|
"""If a named command profile is not defined, use the NOS name.
|
|
|
|
|
|
|
|
Arguments:
|
|
|
|
value {str} -- Reference to command profile
|
|
|
|
values {dict} -- Other already-validated fields
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
{str} -- Command profile or NOS name
|
2019-09-13 00:36:58 -07:00
|
|
|
"""
|
2019-12-31 18:29:43 -07:00
|
|
|
if value is None:
|
|
|
|
value = values["nos"]
|
|
|
|
return value
|
2019-09-11 01:35:31 -07:00
|
|
|
|
2019-12-30 01:44:19 -07:00
|
|
|
@validator("vrfs", pre=True)
|
2019-10-09 03:10:52 -07:00
|
|
|
def validate_vrfs(cls, value, values):
|
2019-12-31 18:29:43 -07:00
|
|
|
"""Validate VRF definitions.
|
|
|
|
|
2019-10-09 03:10:52 -07:00
|
|
|
- Ensures source IP addresses are set for the default VRF
|
|
|
|
(global routing table).
|
|
|
|
- Initializes the default VRF with the DefaultVRF() class so
|
|
|
|
that specific defaults can be set for the global routing
|
|
|
|
table.
|
|
|
|
- If the 'display_name' is not set for a non-default VRF, try
|
|
|
|
to make one that looks pretty based on the 'name'.
|
2019-12-31 18:29:43 -07:00
|
|
|
|
|
|
|
Arguments:
|
|
|
|
value {list} -- List of VRFs
|
|
|
|
values {dict} -- Other already-validated fields
|
|
|
|
|
|
|
|
Raises:
|
|
|
|
ConfigError: Raised if the VRF is missing a source address
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
{list} -- List of valid VRFs
|
2019-09-13 00:36:58 -07:00
|
|
|
"""
|
2019-10-09 03:10:52 -07:00
|
|
|
vrfs = []
|
|
|
|
for vrf in value:
|
|
|
|
vrf_name = vrf.get("name")
|
|
|
|
|
|
|
|
for afi in ("ipv4", "ipv6"):
|
|
|
|
vrf_afi = vrf.get(afi)
|
|
|
|
|
|
|
|
if vrf_afi is not None and vrf_afi.get("source_address") is None:
|
|
|
|
|
|
|
|
# If AFI is actually defined (enabled), and if the
|
|
|
|
# source_address field is not set, raise an error
|
2019-09-30 07:51:17 -07:00
|
|
|
raise ConfigError(
|
2019-10-09 03:10:52 -07:00
|
|
|
(
|
|
|
|
"VRF '{vrf}' in router '{router}' is missing a source "
|
|
|
|
"{afi} address."
|
|
|
|
),
|
|
|
|
vrf=vrf.get("name"),
|
|
|
|
router=values.get("name"),
|
|
|
|
afi=afi.replace("ip", "IP"),
|
2019-09-30 07:51:17 -07:00
|
|
|
)
|
2019-10-09 03:10:52 -07:00
|
|
|
if vrf_name == "default":
|
|
|
|
|
|
|
|
# Validate the default VRF against the DefaultVrf()
|
|
|
|
# class. (See vrfs.py)
|
|
|
|
vrf = DefaultVrf(**vrf)
|
|
|
|
|
2019-12-31 18:29:43 -07:00
|
|
|
elif vrf_name != "default" and not isinstance(
|
|
|
|
vrf.get("display_name"), StrictStr
|
|
|
|
):
|
2019-10-09 03:10:52 -07:00
|
|
|
|
|
|
|
# If no display_name is set for a non-default VRF, try
|
|
|
|
# to make one by replacing non-alphanumeric characters
|
|
|
|
# with whitespaces and using str.title() to make each
|
|
|
|
# word look "pretty".
|
|
|
|
new_name = vrf["name"]
|
|
|
|
new_name = re.sub(r"[^a-zA-Z0-9]", " ", new_name)
|
|
|
|
new_name = re.split(" ", new_name)
|
|
|
|
vrf["display_name"] = " ".join([w.title() for w in new_name])
|
|
|
|
|
|
|
|
log.debug(
|
|
|
|
f'Field "display_name" for VRF "{vrf["name"]}" was not set. '
|
|
|
|
f'Generated "display_name" {vrf["display_name"]}'
|
|
|
|
)
|
2019-12-30 09:47:31 -07:00
|
|
|
|
2019-10-09 03:10:52 -07:00
|
|
|
# Validate the non-default VRF against the standard
|
|
|
|
# Vrf() class.
|
|
|
|
vrf = Vrf(**vrf)
|
2019-09-30 07:51:17 -07:00
|
|
|
|
2019-10-09 03:10:52 -07:00
|
|
|
vrfs.append(vrf)
|
|
|
|
return vrfs
|
2019-09-30 07:51:17 -07:00
|
|
|
|
2019-09-13 00:36:58 -07:00
|
|
|
|
2019-10-09 03:10:52 -07:00
|
|
|
class Routers(HyperglassModelExtra):
|
2019-12-29 23:57:39 -07:00
|
|
|
"""Validation model for device configurations."""
|
2019-09-11 01:35:31 -07:00
|
|
|
|
2019-12-31 18:29:43 -07:00
|
|
|
hostnames: List[StrictStr] = []
|
|
|
|
vrfs: List[StrictStr] = []
|
|
|
|
display_vrfs: List[StrictStr] = []
|
2019-10-09 03:10:52 -07:00
|
|
|
routers: List[Router] = []
|
|
|
|
|
2019-09-11 01:35:31 -07:00
|
|
|
@classmethod
|
2019-10-09 03:10:52 -07:00
|
|
|
def _import(cls, input_params):
|
2019-12-31 18:29:43 -07:00
|
|
|
"""Import loaded YAML, initialize per-network definitions.
|
|
|
|
|
|
|
|
Remove unsupported characters from device names, dynamically
|
|
|
|
set attributes for the devices class. Builds lists of common
|
|
|
|
attributes for easy access in other modules.
|
|
|
|
|
|
|
|
Arguments:
|
|
|
|
input_params {dict} -- Unvalidated router definitions
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
{object} -- Validated routers object
|
2019-09-11 01:35:31 -07:00
|
|
|
"""
|
|
|
|
vrfs = set()
|
2019-10-09 03:10:52 -07:00
|
|
|
display_vrfs = set()
|
2019-12-31 18:29:43 -07:00
|
|
|
routers = Routers()
|
|
|
|
routers.routers = []
|
|
|
|
routers.hostnames = []
|
|
|
|
routers.vrfs = []
|
|
|
|
routers.display_vrfs = []
|
2019-10-09 03:10:52 -07:00
|
|
|
|
|
|
|
for definition in input_params:
|
|
|
|
# Validate each router config against Router() model/schema
|
|
|
|
router = Router(**definition)
|
|
|
|
|
|
|
|
# Set a class attribute for each router so each router's
|
|
|
|
# attributes can be accessed with `devices.router_hostname`
|
2019-12-31 18:29:43 -07:00
|
|
|
setattr(routers, router.name, router)
|
2019-10-09 03:10:52 -07:00
|
|
|
|
|
|
|
# Add router-level attributes (assumed to be unique) to
|
|
|
|
# class lists, e.g. so all hostnames can be accessed as a
|
|
|
|
# list with `devices.hostnames`, same for all router
|
|
|
|
# classes, for when iteration over all routers is required.
|
2019-12-31 18:29:43 -07:00
|
|
|
routers.hostnames.append(router.name)
|
|
|
|
routers.routers.append(router)
|
2019-10-09 03:10:52 -07:00
|
|
|
|
|
|
|
for vrf in router.vrfs:
|
|
|
|
# For each configured router VRF, add its name and
|
|
|
|
# display_name to a class set (for automatic de-duping).
|
|
|
|
vrfs.add(vrf.name)
|
|
|
|
display_vrfs.add(vrf.display_name)
|
|
|
|
|
|
|
|
# Also add the names to a router-level list so each
|
|
|
|
# router's VRFs and display VRFs can be easily accessed.
|
|
|
|
router.display_vrfs.append(vrf.display_name)
|
|
|
|
router.vrf_names.append(vrf.name)
|
|
|
|
|
|
|
|
# Add a 'default_vrf' attribute to the devices class
|
|
|
|
# which contains the configured default VRF display name
|
|
|
|
if vrf.name == "default" and not hasattr(cls, "default_vrf"):
|
2019-12-31 18:29:43 -07:00
|
|
|
routers.default_vrf = {
|
|
|
|
"name": vrf.name,
|
|
|
|
"display_name": vrf.display_name,
|
|
|
|
}
|
2019-10-04 16:54:32 -07:00
|
|
|
|
2019-10-09 03:10:52 -07:00
|
|
|
# Convert the de-duplicated sets to a standard list, add lists
|
|
|
|
# as class attributes
|
2019-12-31 18:29:43 -07:00
|
|
|
routers.vrfs = list(vrfs)
|
|
|
|
routers.display_vrfs = list(display_vrfs)
|
2019-09-11 01:35:31 -07:00
|
|
|
|
2019-12-31 18:29:43 -07:00
|
|
|
return routers
|