2019-12-29 23:57:39 -07:00
"""Validate VRF configuration variables."""
2019-09-11 01:35:31 -07:00
2020-02-03 02:35:11 -07:00
# Standard Library
2021-02-25 23:38:57 -07:00
import re
2021-02-26 23:21:48 -07:00
from typing import Dict , List , Union , Optional
2020-02-03 02:35:11 -07:00
from ipaddress import IPv4Address , IPv4Network , IPv6Address , IPv6Network
2019-09-11 01:35:31 -07:00
2020-02-03 02:35:11 -07:00
# Third Party
from pydantic import (
Field ,
FilePath ,
StrictStr ,
StrictBool ,
2021-02-25 23:38:57 -07:00
PrivateAttr ,
2020-02-03 02:35:11 -07:00
conint ,
constr ,
validator ,
root_validator ,
)
2021-02-26 23:21:48 -07:00
from typing_extensions import Literal
2019-09-11 01:35:31 -07:00
2021-02-25 23:38:57 -07:00
# Project
from hyperglass.log import log
2020-10-11 13:14:07 -07:00
# Local
from ..main import HyperglassModel , HyperglassModelExtra
2020-01-17 02:50:57 -07:00
2020-07-13 02:18:26 -07:00
ACLAction = constr ( regex = r "permit|deny" )
2021-02-26 23:21:48 -07:00
AddressFamily = Union [ Literal [ 4 ], Literal [ 6 ]]
2020-07-13 02:18:26 -07:00
2020-01-17 02:50:57 -07:00
2021-02-25 23:38:57 -07:00
def find_vrf_id ( values : Dict ) -> str :
"""Generate (private) VRF ID."""
def generate_id ( name : str ) -> str :
scrubbed = re . sub ( r "[^A-Za-z0-9\_\-\s]" , "" , name )
return "_" . join ( scrubbed . split ()) . lower ()
display_name = values . get ( "display_name" )
if display_name is None :
raise ValueError ( "display_name is required." )
2021-02-26 23:21:48 -07:00
return generate_id ( display_name )
2021-02-25 23:38:57 -07:00
2020-01-31 02:06:27 -10:00
class AccessList4 ( HyperglassModel ):
"""Validation model for IPv4 access-lists."""
2020-02-01 16:11:01 -10:00
network : IPv4Network = Field (
"0.0.0.0/0" ,
title = "Network" ,
description = "IPv4 Network/Prefix that should be permitted or denied. " ,
)
2020-07-13 02:18:26 -07:00
action : ACLAction = Field (
2020-02-01 16:11:01 -10:00
"permit" ,
title = "Action" ,
description = "Permit or deny any networks contained within the prefix." ,
)
ge : conint ( ge = 0 , le = 32 ) = Field (
0 ,
title = "Greater Than or Equal To" ,
description = "Similar to `ge` in a Cisco prefix-list, the `ge` field defines the **bottom** threshold for prefix size. For example, a value of `24` would result in a query for `192.0.2.0/23` being denied, but a query for `192.0.2.0/32` would be permitted. If this field is set to a value smaller than the `network` field's prefix length, this field's value will be overwritten to the prefix length of the prefix in the `network` field." ,
)
le : conint ( ge = 0 , le = 32 ) = Field (
32 ,
title = "Less Than or Equal To" ,
description = "Similar to `le` in a Cisco prefix-list, the `le` field defines the **top** threshold for prefix size. For example, a value of `24` would result in a query for `192.0.2.0/23` being permitted, but a query for `192.0.2.0/32` would be denied." ,
)
2020-01-31 02:06:27 -10:00
@validator ( "ge" )
def validate_model ( cls , value , values ):
"""Ensure ge is at least the size of the input prefix.
Arguments:
value {int} -- Initial ge value
values {dict} -- Other post-validation fields
Returns:
{int} -- Validated ge value
"""
net_len = values [ "network" ] . prefixlen
if net_len > value :
value = net_len
return value
class AccessList6 ( HyperglassModel ):
"""Validation model for IPv6 access-lists."""
2020-02-01 16:11:01 -10:00
network : IPv6Network = Field (
"::/0" ,
title = "Network" ,
description = "IPv6 Network/Prefix that should be permitted or denied. " ,
)
2020-07-13 02:18:26 -07:00
action : ACLAction = Field (
2020-02-01 16:11:01 -10:00
"permit" ,
title = "Action" ,
description = "Permit or deny any networks contained within the prefix." ,
)
ge : conint ( ge = 0 , le = 128 ) = Field (
0 ,
title = "Greater Than or Equal To" ,
description = "Similar to `ge` in a Cisco prefix-list, the `ge` field defines the **bottom** threshold for prefix size. For example, a value of `64` would result in a query for `2001:db8::/48` being denied, but a query for `2001:db8::1/128` would be permitted. If this field is set to a value smaller than the `network` field's prefix length, this field's value will be overwritten to the prefix length of the prefix in the `network` field." ,
)
le : conint ( ge = 0 , le = 128 ) = Field (
128 ,
title = "Less Than or Equal To" ,
description = "Similar to `le` in a Cisco prefix-list, the `le` field defines the **top** threshold for prefix size. For example, a value of `64` would result in a query for `2001:db8::/48` being permitted, but a query for `2001:db8::1/128` would be denied." ,
)
2020-01-31 02:06:27 -10:00
@validator ( "ge" )
def validate_model ( cls , value , values ):
"""Ensure ge is at least the size of the input prefix.
Arguments:
value {int} -- Initial ge value
values {dict} -- Other post-validation fields
Returns:
{int} -- Validated ge value
"""
net_len = values [ "network" ] . prefixlen
if net_len > value :
value = net_len
return value
2020-01-17 02:50:57 -07:00
class InfoConfigParams ( HyperglassModelExtra ):
"""Validation model for per-help params."""
title : Optional [ StrictStr ]
2020-02-01 16:11:01 -10:00
class Config :
"""Pydantic model configuration."""
title = "Help Parameters"
description = "Set dynamic or reusable values which may be used in the help/information content. Params my be access by using Python string formatting syntax, e.g. ` {param_name} `. Any arbitrary values may be added."
2020-01-17 02:50:57 -07:00
class InfoConfig ( HyperglassModel ):
"""Validation model for help configuration."""
enable : StrictBool = True
file : Optional [ FilePath ]
params : InfoConfigParams = InfoConfigParams ()
2019-10-09 03:10:52 -07:00
2020-02-01 16:11:01 -10:00
class Config :
"""Pydantic model configuration."""
fields = {
"enable" : {
"title" : "Enable" ,
"description" : "Enable or disable the display of help/information content for this query type." ,
},
"file" : {
"title" : "File Name" ,
"description" : "Path to a valid text or Markdown file containing custom content." ,
},
}
2019-10-09 03:10:52 -07:00
2020-01-16 02:52:00 -07:00
class Info ( HyperglassModel ):
2020-01-17 02:50:57 -07:00
"""Validation model for per-VRF, per-Command help."""
2020-01-16 02:52:00 -07:00
2020-01-17 02:50:57 -07:00
bgp_aspath : InfoConfig = InfoConfig ()
bgp_community : InfoConfig = InfoConfig ()
bgp_route : InfoConfig = InfoConfig ()
ping : InfoConfig = InfoConfig ()
traceroute : InfoConfig = InfoConfig ()
2020-01-16 02:52:00 -07:00
2020-02-01 16:11:01 -10:00
class Config :
"""Pydantic model configuration."""
title = "VRF Information"
description = "Per-VRF help & information content."
fields = {
"bgp_aspath" : {
"title" : "BGP AS Path" ,
"description" : "Show information about bgp_aspath queries when selected." ,
},
"bgp_community" : {
"title" : "BGP Community" ,
"description" : "Show information about bgp_community queries when selected." ,
},
"bgp_route" : {
"title" : "BGP Route" ,
"description" : "Show information about bgp_route queries when selected." ,
},
"ping" : {
"title" : "Ping" ,
"description" : "Show information about ping queries when selected." ,
},
"traceroute" : {
"title" : "Traceroute" ,
"description" : "Show information about traceroute queries when selected." ,
},
}
2020-01-16 02:52:00 -07:00
2020-01-31 02:06:27 -10:00
class DeviceVrf4 ( HyperglassModelExtra ):
2019-12-29 23:57:39 -07:00
"""Validation model for IPv4 AFI definitions."""
2019-10-09 03:10:52 -07:00
source_address : IPv4Address
2020-01-31 02:06:27 -10:00
access_list : List [ AccessList4 ] = [ AccessList4 ()]
2020-03-22 22:08:29 -07:00
force_cidr : StrictBool = True
2019-10-09 03:10:52 -07:00
2020-01-31 02:06:27 -10:00
class DeviceVrf6 ( HyperglassModelExtra ):
2019-12-29 23:57:39 -07:00
"""Validation model for IPv6 AFI definitions."""
2019-10-09 03:10:52 -07:00
source_address : IPv6Address
2020-01-31 02:06:27 -10:00
access_list : List [ AccessList6 ] = [ AccessList6 ()]
2020-03-22 22:08:29 -07:00
force_cidr : StrictBool = True
2019-10-09 03:10:52 -07:00
2019-10-04 16:54:32 -07:00
class Vrf ( HyperglassModel ):
2019-12-29 23:57:39 -07:00
"""Validation model for per VRF/afi config in devices.yaml."""
2019-09-11 01:35:31 -07:00
2021-02-25 23:38:57 -07:00
_id : StrictStr = PrivateAttr ()
2020-01-16 02:52:00 -07:00
name : StrictStr
display_name : StrictStr
info : Info = Info ()
2019-12-30 01:44:19 -07:00
ipv4 : Optional [ DeviceVrf4 ]
ipv6 : Optional [ DeviceVrf6 ]
2021-02-25 23:38:57 -07:00
default : StrictBool = False
2019-09-30 07:51:17 -07:00
2021-02-25 23:38:57 -07:00
def __init__ ( self , ** kwargs ) -> None :
"""Set the VRF ID."""
_id = find_vrf_id ( kwargs )
super () . __init__ ( ** kwargs )
self . _id = _id
2020-01-31 02:06:27 -10:00
2021-02-25 23:38:57 -07:00
@root_validator
def set_dynamic ( cls , values : Dict ) -> Dict :
"""Set dynamic attributes before VRF initialization."""
2019-12-31 18:29:43 -07:00
2020-01-31 02:06:27 -10:00
if values [ "name" ] == "default" :
2021-02-25 23:38:57 -07:00
log . warning (
"""You have set the VRF name to 'default'. This is no longer the way to
designate a VRF as the default (or global routing table) VRF. Instead,
add 'default: true' to the VRF definition.
"""
)
if values . get ( "default" , False ) is True :
2020-01-31 02:06:27 -10:00
protocol4 = "ipv4_default"
protocol6 = "ipv6_default"
else :
protocol4 = "ipv4_vpn"
protocol6 = "ipv6_vpn"
2019-10-09 03:10:52 -07:00
2020-01-31 02:06:27 -10:00
if values . get ( "ipv4" ) is not None :
values [ "ipv4" ] . protocol = protocol4
values [ "ipv4" ] . version = 4
if values . get ( "ipv6" ) is not None :
values [ "ipv6" ] . protocol = protocol6
values [ "ipv6" ] . version = 6
2021-02-25 23:38:57 -07:00
if values . get ( "default" , False ) and values . get ( "display_name" ) is None :
2020-03-19 09:25:57 -07:00
values [ "display_name" ] = "Global"
2020-01-31 02:06:27 -10:00
return values
2021-02-26 23:21:48 -07:00
def __getitem__ ( self , i : AddressFamily ) -> Union [ DeviceVrf4 , DeviceVrf6 ]:
"""Access the VRF's AFI by IP protocol number."""
2020-01-31 02:06:27 -10:00
if i not in ( 4 , 6 ):
2021-02-25 23:38:57 -07:00
raise AttributeError ( f "Must be 4 or 6, got ' { i } '" )
2020-01-31 02:06:27 -10:00
return getattr ( self , f "ipv { i } " )
2019-09-11 01:35:31 -07:00
2021-02-26 23:21:48 -07:00
def __hash__ ( self ) -> int :
"""Make VRF object hashable so the object can be deduplicated with set()."""
2020-01-16 02:52:00 -07:00
return hash (( self . name ,))
2021-02-26 23:21:48 -07:00
def __eq__ ( self , other : object ) -> bool :
"""Make VRF object comparable so the object can be deduplicated with set()."""
2020-01-31 02:06:27 -10:00
result = False
if isinstance ( other , HyperglassModel ):
result = self . name == other . name
return result
2020-01-16 02:52:00 -07:00
2020-02-01 16:11:01 -10:00
class Config :
"""Pydantic model configuration."""
title = "VRF"
description = "Per-VRF configuration."
fields = {
"name" : {
"title" : "Name" ,
"description" : "VRF name as configured on the router/device." ,
},
"display_name" : {
"title" : "Display Name" ,
"description" : "Display name of VRF for use in the hyperglass UI. If none is specified, hyperglass will attempt to generate one." ,
},
}