Add ip_interface() validator

This commit is contained in:
Pim van Pelt
2022-03-13 09:55:01 +00:00
parent 9862129ab0
commit be102d5b6d
3 changed files with 65 additions and 19 deletions

View File

@ -1,17 +1,38 @@
## Design ## Design
### Validators ### YAML Configuration
Validators are functions which are passed the fully formed YAML configuration and are meant to The main file that is handled by this program is the **Configuration File**.
check it for syntax and semantic validity. A validator has a unique name and takes the (yaml)
configuration as the only argument. Validators are expected to return a tuple of (bool,[string])
where the boolean signals success (False meaning the validator rejected the configuration file,
True meaning it is known to be correct), and a list of zero or more strings which contain messages
meant for human consumption. They can contain INFO, WARN or ERROR messages, and are meant to help
the caller understand why the validator rejected the configuration.
Validators can be disabled with the --skip-validator <name> [<name>] flag. It is not ## Validation
advised to skip validators. The purpose of the validators is to ensure the configuration is sane
and semantically correct.
Validators can be registered as follows: There are three types of validation: _schema_ which ensures that the input YAML has the correct
fields of well known types, _semantic_ which ensures that the configuration doesn't violate
semantic constraints and _runtime_ which ensures that the configuration can be applied to the
VPP daemon.
### Schema Validators
First the configuration file is held against a structural validator, provided by [Yamale](https://github.com/23andMe/Yamale/).
Based on a validation schema in `schema.yaml`, the input file is checked for syntax correctness.
For example, a `dot1q` field must be an integer between 1 and 4095, wile an `lcp` string must
match a certain regular expression. After this first pass of syntax validation, I'm certain that
_if_ a field is set, it is of the right type (ie. string, int, enum).
### Semantic Validators
A set of semantic validators, each with a unique name, ensure that the _semantics_ of the YAML
are correct. For example, a physical interface cannot have an LCP, addresses or sub-interfaces,
if it is to be a member of a BondEthernet.
Validators are expected to return a tuple of (bool,[string]) where the boolean signals success
(False meaning the validator rejected the configuration file, True meaning it is known to be
correct), and a list of zero or more strings which contain messages meant for human consumption.
### Runtime Validators
After the configuration file is considered syntax and semanticly valid, there is one more set of
checks to perform -- runtime validators ensure that the configuration elements such as physical
network devices (ie. `HundredGigabitEthernet12/0/0` or plugin `lcpng` are present on the system.
It does this by connecting to VPP and querying the runtime state to ensure that what is modeled
in the configuration file is able to be committed.

View File

@ -9,17 +9,13 @@ interface:
description: str(exclude='\'"',required=False) description: str(exclude='\'"',required=False)
lcp: str(max=8,matches='[a-z]+[a-z0-9-]{,7}',required=False) lcp: str(max=8,matches='[a-z]+[a-z0-9-]{,7}',required=False)
mac: mac(required=False) mac: mac(required=False)
addresses: list(include('v4'),include('v6'),min=1,max=6,required=False) addresses: list(ip_interface(),min=1,max=6,required=False)
sub-interfaces: map(include('sub-interface'),key=int(min=1,max=4294967295),required=False) sub-interfaces: map(include('sub-interface'),key=int(min=1,max=4294967295),required=False)
--- ---
v4: str(matches='[0-9\.]+/[0-9]+')
---
v6: str(matches='[0-9a-f:]+/[0-9]+',ignore_case=True)
---
sub-interface: sub-interface:
description: str(exclude='\'"',required=False) description: str(exclude='\'"',required=False)
lcp: str(max=8,matches='[a-z]+[a-z0-9-]{,7}',required=False) lcp: str(max=8,matches='[a-z]+[a-z0-9-]{,7}',required=False)
addresses: list(ip(),required=False) addresses: list(ip_interface(),required=False)
encapsulation: include('encapsulation',required=False) encapsulation: include('encapsulation',required=False)
--- ---
encapsulation: encapsulation:

View File

@ -17,6 +17,33 @@ from validator.bondethernet import bondethernet
from validator.interface import interface from validator.interface import interface
from validator.bridgedomain import bridgedomain from validator.bridgedomain import bridgedomain
from yamale.validators import DefaultValidators, Validator
import ipaddress
class IPInterfaceWithPrefixLength(Validator):
""" Custom IPAddress validator - takes IP/prefixlen as input:
192.0.2.1/29 or 2001:db8::1/64 are correct. The PrefixLength
is required, and must be a number (0-32 for IPv4 and 0-128 for
IPv6).
"""
tag = 'ip_interface'
def _is_valid(self, value):
try:
network = ipaddress.ip_interface(value)
except:
return False
if not '/' in value:
return False
e = value.split('/')
if not len(e) == 2:
return False
if not e[1].isnumeric():
return False
return True
class NullHandler(logging.Handler): class NullHandler(logging.Handler):
def emit(self, record): def emit(self, record):
pass pass
@ -35,7 +62,9 @@ class Validator(object):
if self.args.schema: if self.args.schema:
try: try:
self.logger.info("Validating against schema %s" % self.args.schema) self.logger.info("Validating against schema %s" % self.args.schema)
schema = yamale.make_schema(self.args.schema) validators = DefaultValidators.copy()
validators[IPInterfaceWithPrefixLength.tag] = IPInterfaceWithPrefixLength
schema = yamale.make_schema(self.args.schema, validators=validators)
data = yamale.make_data(content=str(yaml)) data = yamale.make_data(content=str(yaml))
yamale.validate(schema, data) yamale.validate(schema, data)
self.logger.debug("Schema correctly validated by yamale") self.logger.debug("Schema correctly validated by yamale")