diff options
author | Michal Cmarada <mcmarada@cisco.com> | 2019-01-23 11:41:26 +0100 |
---|---|---|
committer | Michal Cmarada <mcmarada@cisco.com> | 2019-01-23 11:43:40 +0100 |
commit | 498be1107120dd7a9a8df5443b74689fbe95f61a (patch) | |
tree | 4060cc8491006bfb898776d4a8eeea29aac88428 /examples/ncclient/diff_xml.py | |
parent | 7645a98e4c92a20d45a8a1417db498db1b075080 (diff) |
Update ncclient examples
- added optional parameter host for copy_config, get_config
and edit-config.
- added basic tests for interfaces:
- loopback interface
- af-packet interface
- tap interface
- added diff_xml.py to find differences between two sets of config.
It supports both full xml comparison and comparison based on xPath
- added example diff between running and candidate config (interfaces)
- fixed formatting and namespaces
Change-Id: If6dd7a76fab538735ab92c67f9457326fbcba7ec
Signed-off-by: Michal Cmarada <mcmarada@cisco.com>
Diffstat (limited to 'examples/ncclient/diff_xml.py')
-rwxr-xr-x | examples/ncclient/diff_xml.py | 139 |
1 files changed, 139 insertions, 0 deletions
diff --git a/examples/ncclient/diff_xml.py b/examples/ncclient/diff_xml.py new file mode 100755 index 000000000..e21773425 --- /dev/null +++ b/examples/ncclient/diff_xml.py @@ -0,0 +1,139 @@ +#!/usr/bin/env python +# +# Copyright (c) 2019 Cisco and/or its affiliates. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +""" +Usage: {prog} [OPTION] FILE1 FILE2 xPath + +Compare two XML files, while sorting elements. + +xPath optional parameter +example: + ./{{urn:ietf:params:xml:ns:yang:ietf-interfaces}}interfaces/ + {{urn:ietf:params:xml:ns:yang:ietf-interfaces}}interface[ + {{urn:ietf:params:xml:ns:yang:ietf-interfaces}}name='loop0'] +""" +import os +import re +import subprocess +import sys +from tempfile import NamedTemporaryFile +from xml.etree import ElementTree +from xml.etree.ElementTree import Element + +TAG_PATTERN = re.compile(r"""({(?P<namespace>[a-zA-Z0-9.:@\-/]+)}(?P<tag>[0-9a-zA-Z\-]+))|(?P<name>[0-9a-zA-Z\-]+)""", + re.VERBOSE) +PREFIX_PATTERN = re.compile(r"""(((urn)|(.+://))[a-zA-Z0-9.:@\-/]+[:/](?P<prefix>[0-9a-zA-Z\-]+))""", re.VERBOSE) + +if sys.version_info < (3, 0): + # Python 2 + import codecs + + def unicode_writer(fp): + return codecs.getwriter('utf-8')(fp) +else: + # Python 3 + def unicode_writer(fp): + return fp + + +def print_usage(program): + print(__doc__.format(prog=program).strip()) + + +def parse_namespace(namespace): + """ + Extracts module name fom namespace. + example: + urn:ietf:params:xml:ns:yang:ietf-nat -> ietf-nat + http://fd.io/hc2vpp/yang/vpp-fib-table-management -> vpp-fib-table-management + https://fd.io/hc2vpp/yang/vpp-fib-table-management -> vpp-fib-table-management + :param namespace: namespace in urn or http format + :return: module name from namespace + """ + matcher = PREFIX_PATTERN.match(namespace) + if matcher.group("prefix"): + return matcher.group("prefix").lower() + + +def sort(xml_element): + """ + Sorts elements in xml in alphabetical order. + :param xml_element: root element of xml + """ + if not isinstance(xml_element, Element): + exit(-1) + xml_element[:] = sorted(xml_element, key=lambda child: child.tag) + + mo = TAG_PATTERN.match(xml_element.tag) + if mo.group("namespace") and mo.group("tag") and not mo.group("namespace").__contains__( + "urn:ietf:params:xml:ns:netconf:base:1.0"): + ElementTree.register_namespace(parse_namespace(mo.group("namespace")), mo.group("namespace")) + + for element in xml_element: + sort(element) + + +def diff_xml(xml1, xml2, xpath): + """ + Resolves differences between two xml files. + :param xml1: input file left side (original) + :param xml2: input file right side (to compare with) + :param xpath: xpath of element to read. example: + ./{urn:ietf:params:xml:ns:yang:ietf-interfaces}interfaces/{ \ + urn:ietf:params:xml:ns:yang:ietf-interfaces}interface[{ \ + urn:ietf:params:xml:ns:yang:ietf-interfaces}name='loop0'] + :return: diff of input files + """ + ElementTree.register_namespace("nc", "urn:ietf:params:xml:ns:netconf:base:1.0") + tmp1 = unicode_writer(NamedTemporaryFile('w')) + normalize_xml(tmp1, ElementTree.parse(xml1), xpath) + + tmp2 = unicode_writer(NamedTemporaryFile('w')) + normalize_xml(tmp2, ElementTree.parse(xml2), xpath) + + return subprocess.call(["diff", "-u", "-s", "-d", "--label", xml1, "--label", xml2, tmp1.name, tmp2.name]) + + +def normalize_xml(tmp, tree, xpath): + root = tree.getroot() if (xpath == "*") else tree.getroot().find(xPath) + sort(root) + xml_str = ElementTree.tostring(root, encoding="utf-8", method="xml") + tmp.write(xml_str.decode("utf-8")) + tmp.flush() + # format and normalize output using xmllint + # cmd: xmllint --exc-c14n [file_name] -o [file_name] + subprocess.call(["xmllint", "--format", tmp.name, "-o", tmp.name]) + + +if __name__ == '__main__': + args = sys.argv + prog = os.path.basename(args.pop(0)) + + if '-h' in args or '--help' in args: + print_usage(prog) + exit(0) + + if len(args) < 2: + print_usage(prog) + exit(1) + args.reverse() + file1 = args.pop(-1) + file2 = args.pop(-1) + if len(args) > 2: + xPath = args.pop(-1) + else: + xPath = "*" + + exit(diff_xml(file1, file2, xPath)) |