From a50dc31eaef7033444311f0917e8ffde373bea1b Mon Sep 17 00:00:00 2001 From: Erik Reid <erik.reid@geant.org> Date: Sun, 23 Dec 2018 19:00:12 +0100 Subject: [PATCH] added validation to netconf input --- inventory_provider/netconf.py | 112 +++++++++++++++++++++++++++++++++- test/test_netconf_data.py | 1 - 2 files changed, 109 insertions(+), 4 deletions(-) diff --git a/inventory_provider/netconf.py b/inventory_provider/netconf.py index e20edbea..a93c693e 100644 --- a/inventory_provider/netconf.py +++ b/inventory_provider/netconf.py @@ -1,8 +1,94 @@ -# import os -from jnpr.junos import Device +import logging +from jnpr.junos import Device from lxml import etree +CONFIG_SCHEMA = """<?xml version="1.1" encoding="UTF-8" ?> +<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"> + + <xs:complexType name="generic-sequence"> + <xs:sequence> + <xs:any processContents="lax" minOccurs="0" maxOccurs="unbounded"/> + </xs:sequence> + <xs:anyAttribute processContents="skip" /> + </xs:complexType> + + <!-- NOTE: 'unit' content isn't validated --> + <xs:complexType name="juniper-interface"> + <xs:sequence> + <xs:choice minOccurs="1" maxOccurs="unbounded"> + <xs:element name="name" minOccurs="1" maxOccurs="1" type="xs:string" /> + <xs:element name="description" minOccurs="0" maxOccurs="1" type="xs:string" /> + <xs:any processContents="lax" minOccurs="0" maxOccurs="unbounded" /> + </xs:choice> + </xs:sequence> + <xs:attribute name="inactive" type="xs:string" /> + </xs:complexType> + + <xs:element name="configuration"> + <xs:complexType> + <xs:sequence> + <xs:choice minOccurs="1" maxOccurs="unbounded"> + <xs:element name="version" minOccurs="0" type="xs:string" /> + <xs:element name="groups" minOccurs="0" type="generic-sequence" /> + <xs:element name="apply-groups" minOccurs="0" type="xs:string" /> + <xs:element name="system" minOccurs="0" type="generic-sequence" /> + <xs:element name="logical-systems" minOccurs="0" type="generic-sequence" /> + <xs:element name="chassis" minOccurs="0" type="generic-sequence" /> + <xs:element name="services" minOccurs="0" type="generic-sequence" /> + <xs:element name="interfaces" minOccurs="0"> + <xs:complexType> + <xs:sequence> + <xs:choice minOccurs="1" maxOccurs="unbounded"> + <xs:element name="apply-groups" minOccurs="0" type="xs:string" /> + <xs:element name="interface" minOccurs="1" maxOccurs="unbounded" type="juniper-interface" /> + </xs:choice> + </xs:sequence> + </xs:complexType> + </xs:element> + <xs:element name="snmp" minOccurs="0" type="generic-sequence" /> + <xs:element name="forwarding-options" minOccurs="0" type="generic-sequence" /> + <xs:element name="routing-options" minOccurs="0" type="generic-sequence" /> + <xs:element name="protocols" minOccurs="0" type="generic-sequence" /> + <xs:element name="policy-options" minOccurs="0" type="generic-sequence" /> + <xs:element name="class-of-service" minOccurs="0" type="generic-sequence" /> + <xs:element name="firewall" minOccurs="0" type="generic-sequence" /> + <xs:element name="routing-instances" minOccurs="0" type="generic-sequence" /> + <xs:element name="bridge-domains" minOccurs="0" type="generic-sequence" /> + </xs:choice> + </xs:sequence> + <xs:attribute name="changed-seconds" type="xs:string" /> + <xs:attribute name="changed-localtime" type="xs:string" /> + </xs:complexType> + </xs:element> + +</xs:schema> +""" + +UNIT_SCHEMA = """<?xml version="1.1" encoding="UTF-8" ?> +<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"> + + <xs:complexType name="generic-sequence"> + <xs:sequence> + <xs:any processContents="lax" minOccurs="0" maxOccurs="unbounded"/> + </xs:sequence> + <xs:anyAttribute processContents="skip" /> + </xs:complexType> + + <xs:element name="unit"> + <xs:complexType> + <xs:sequence> + <xs:element name="name" minOccurs="1" maxOccurs="1" type="xs:int" /> + <xs:element name="description" minOccurs="0" maxOccurs="1" type="xs:string" /> + <xs:any processContents="lax" minOccurs="0" maxOccurs="unbounded"/> + </xs:sequence> + <xs:attribute name="inactive" type="xs:string" /> + </xs:complexType> + </xs:element> + +</xs:schema> +""" + def _rpc(hostname, ssh): dev = Device( @@ -15,7 +101,27 @@ def _rpc(hostname, ssh): def load_config(hostname, ssh_params): # data = dev.rpc.get_config(options={'format': 'json'}) - return _rpc(hostname, ssh_params).get_config() + config = _rpc(hostname, ssh_params).get_config() + + def _validate(schema, doc): + if schema.validate(doc): + return + for e in schema.error_log: + logging.debug("%d.%d: %s" % (e.line, e.column, e.message)) + assert False + + schema_doc = etree.XML(CONFIG_SCHEMA.encode('utf-8')) + config_schema = etree.XMLSchema(schema_doc) + _validate(config_schema, config) + + # validate interfaces/interface/unit elements ... + schema_doc = etree.XML(UNIT_SCHEMA.encode('utf-8')) + unit_schema = etree.XMLSchema(schema_doc) + for i in config.xpath('//configuration/interfaces/interface'): + for u in i.xpath('./unit'): + _validate(unit_schema, u) + + return config def load_interfaces(hostname, ssh_params): diff --git a/test/test_netconf_data.py b/test/test_netconf_data.py index 6a20a272..5e628ce1 100644 --- a/test/test_netconf_data.py +++ b/test/test_netconf_data.py @@ -28,7 +28,6 @@ def test_query_doc_and_validate(mocker, data_config): 'inventory_provider.netconf.Device', MockedJunosDevice) - etree.parse(SCHEMA_FILENAME) for r in data_config['routers']: doc = netconf.load_config(r['hostname'], data_config['ssh']) -- GitLab