From 61004cb2b368113163716fffe4fa4ec34460054b Mon Sep 17 00:00:00 2001 From: David Barroso Date: Thu, 30 Mar 2017 16:41:40 +0200 Subject: [PATCH 1/3] Load dict now update even on defaults and added compliance_report to Root class --- interactive_demo/tutorial.ipynb | 226 +++++++++++++++++- interactive_demo/validate.yaml | 13 + napalm_yang/base.py | 132 +++++++++- napalm_yang/utils.py | 2 +- .../openconfig-interfaces/default/replace.txt | 7 + .../default/translation.txt | 7 + test/integration/test_validate.py | 71 ++++++ .../validate/ios/default/candidate.json | 180 ++++++++++++++ .../validate/ios/default/validate.yaml | 22 ++ .../validate/ios/default_fail/candidate.json | 180 ++++++++++++++ .../validate/ios/default_fail/validate.yaml | 22 ++ 11 files changed, 849 insertions(+), 13 deletions(-) create mode 100644 interactive_demo/validate.yaml create mode 100644 test/integration/test_validate.py create mode 100644 test/integration/validate/ios/default/candidate.json create mode 100644 test/integration/validate/ios/default/validate.yaml create mode 100644 test/integration/validate/ios/default_fail/candidate.json create mode 100644 test/integration/validate/ios/default_fail/validate.yaml diff --git a/interactive_demo/tutorial.ipynb b/interactive_demo/tutorial.ipynb index 305b0ca5..d2e0dbf7 100644 --- a/interactive_demo/tutorial.ipynb +++ b/interactive_demo/tutorial.ipynb @@ -2454,7 +2454,7 @@ " }, \n", " \"enabled\": True, \n", " \"ifindex\": 531, \n", - " \"last-change\": 254456, \n", + " \"last-change\": 255203, \n", " \"mtu\": 1518, \n", " \"oper-status\": \"DOWN\"\n", " }, \n", @@ -2518,16 +2518,16 @@ " \"in-discards\": 0, \n", " \"in-errors\": 0, \n", " \"in-multicast-pkts\": 0, \n", - " \"in-unicast-pkts\": 15740, \n", + " \"in-unicast-pkts\": 16877, \n", " \"out-broadcast-pkts\": 0, \n", " \"out-errors\": 0, \n", " \"out-multicast-pkts\": 0, \n", - " \"out-unicast-pkts\": 14704\n", + " \"out-unicast-pkts\": 15742\n", " }, \n", " \"description\": \"management interface\", \n", " \"enabled\": True, \n", " \"ifindex\": 507, \n", - " \"last-change\": 257720, \n", + " \"last-change\": 258467, \n", " \"mtu\": 1400, \n", " \"oper-status\": \"UP\"\n", " }, \n", @@ -2562,7 +2562,7 @@ " \"description\": \"ge-0/0/1\", \n", " \"enabled\": False, \n", " \"ifindex\": 508, \n", - " \"last-change\": 254454, \n", + " \"last-change\": 255201, \n", " \"mtu\": 1514, \n", " \"oper-status\": \"DOWN\"\n", " }\n", @@ -2584,7 +2584,7 @@ " }, \n", " \"enabled\": True, \n", " \"ifindex\": 509, \n", - " \"last-change\": 257720, \n", + " \"last-change\": 258467, \n", " \"mtu\": 1514, \n", " \"oper-status\": \"UP\"\n", " }\n", @@ -2718,7 +2718,7 @@ " \"admin-status\": \"UP\", \n", " \"enabled\": True, \n", " \"ifindex\": 518, \n", - " \"last-change\": 257721, \n", + " \"last-change\": 258468, \n", " \"mtu\": 1504, \n", " \"oper-status\": \"UP\"\n", " }\n", @@ -2821,7 +2821,7 @@ " }, \n", " \"enabled\": True, \n", " \"ifindex\": 515, \n", - " \"last-change\": 257721, \n", + " \"last-change\": 258468, \n", " \"mtu\": 9192, \n", " \"oper-status\": \"UP\"\n", " }, \n", @@ -2881,7 +2881,7 @@ " }, \n", " \"enabled\": True, \n", " \"ifindex\": 506, \n", - " \"last-change\": 257729, \n", + " \"last-change\": 258476, \n", " \"mtu\": 1518, \n", " \"oper-status\": \"DOWN\", \n", " \"type\": \"l2vlan\"\n", @@ -2990,6 +2990,214 @@ "source": [ "Diff'ing models with state is also supported." ] + }, + { + "cell_type": "markdown", + "metadata": { + "deletable": true, + "editable": true + }, + "source": [ + "# Compliance Report\n", + "\n", + "This feature also works with YANG models. Let's assume we want to verify we have set the MTU of all of our interfaces to 9000." + ] + }, + { + "cell_type": "code", + "execution_count": 28, + "metadata": { + "collapsed": false, + "deletable": true, + "editable": true + }, + "outputs": [], + "source": [ + "data = {\n", + " \"interfaces\": {\n", + " \"interface\":{\n", + " \"Et1\": {\n", + " \"config\": {\n", + " \"mtu\": 9000\n", + " },\n", + " },\n", + " \"Et2\": {\n", + " \"config\": {\n", + " \"mtu\": 1500\n", + " }\n", + " }\n", + " }\n", + " }\n", + "}\n", + "# We load a dict for convenience, any source will do\n", + "config = napalm_yang.base.Root()\n", + "config.add_model(napalm_yang.models.openconfig_interfaces())\n", + "config.load_dict(data)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "collapsed": true, + "deletable": true, + "editable": true, + "slideshow": { + "slide_type": "-" + } + }, + "source": [ + "Now we can load the validation file. Here is the content for reference:\n", + "\n", + "```\n", + "---\n", + "- to_dict:\n", + " _kwargs:\n", + " filter: true\n", + " interfaces:\n", + " interface:\n", + " Et1:\n", + " config:\n", + " mtu: 9000\n", + " Et2:\n", + " config:\n", + " mtu: 9000\n", + " _mode: strict\n", + "```\n", + "\n", + "Note that there is a major difference between using the `compliance_report` method on getters and on `YANG` objects. With the former you have to specify how to get the data, with the later you have to get the data yourself by any means and then specify you want to convert the data into a `dict` with the `to_dict` method." + ] + }, + { + "cell_type": "code", + "execution_count": 29, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{\n", + " \"complies\": false, \n", + " \"skipped\": [], \n", + " \"to_dict\": {\n", + " \"complies\": false, \n", + " \"extra\": [], \n", + " \"missing\": [], \n", + " \"present\": {\n", + " \"interfaces\": {\n", + " \"complies\": false, \n", + " \"diff\": {\n", + " \"complies\": false, \n", + " \"extra\": [], \n", + " \"missing\": [], \n", + " \"present\": {\n", + " \"interface\": {\n", + " \"complies\": false, \n", + " \"diff\": {\n", + " \"complies\": false, \n", + " \"extra\": [], \n", + " \"missing\": [], \n", + " \"present\": {\n", + " \"Et1\": {\n", + " \"complies\": true, \n", + " \"nested\": true\n", + " }, \n", + " \"Et2\": {\n", + " \"complies\": false, \n", + " \"diff\": {\n", + " \"complies\": false, \n", + " \"extra\": [], \n", + " \"missing\": [], \n", + " \"present\": {\n", + " \"config\": {\n", + " \"complies\": false, \n", + " \"diff\": {\n", + " \"complies\": false, \n", + " \"extra\": [], \n", + " \"missing\": [], \n", + " \"present\": {\n", + " \"mtu\": {\n", + " \"actual_value\": 1500, \n", + " \"complies\": false, \n", + " \"nested\": false\n", + " }\n", + " }\n", + " }, \n", + " \"nested\": true\n", + " }\n", + " }\n", + " }, \n", + " \"nested\": true\n", + " }\n", + " }\n", + " }, \n", + " \"nested\": true\n", + " }\n", + " }\n", + " }, \n", + " \"nested\": true\n", + " }\n", + " }\n", + " }\n", + "}\n" + ] + } + ], + "source": [ + "report = config.compliance_report(\"validate.yaml\")\n", + "pretty_print(report)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can see it's complaining that the value of `Et2`'s MTU is 1500. Let's fix it and try again:" + ] + }, + { + "cell_type": "code", + "execution_count": 30, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{\n", + " \"complies\": true, \n", + " \"skipped\": [], \n", + " \"to_dict\": {\n", + " \"complies\": true, \n", + " \"extra\": [], \n", + " \"missing\": [], \n", + " \"present\": {\n", + " \"interfaces\": {\n", + " \"complies\": true, \n", + " \"nested\": true\n", + " }\n", + " }\n", + " }\n", + "}\n" + ] + } + ], + "source": [ + "config.interfaces.interface[\"Et2\"].config.mtu = 9000\n", + "report = config.compliance_report(\"validate.yaml\")\n", + "pretty_print(report)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now we can see in the first `complies` element of the report that we are complying. This works for state as the rest of the features too." + ] } ], "metadata": { diff --git a/interactive_demo/validate.yaml b/interactive_demo/validate.yaml new file mode 100644 index 00000000..4db9fcba --- /dev/null +++ b/interactive_demo/validate.yaml @@ -0,0 +1,13 @@ +--- +- to_dict: + _kwargs: + filter: true + interfaces: + interface: + Et1: + config: + mtu: 9000 + Et2: + config: + mtu: 9000 + _mode: strict \ No newline at end of file diff --git a/napalm_yang/base.py b/napalm_yang/base.py index 3f79fa37..505dfb38 100644 --- a/napalm_yang/base.py +++ b/napalm_yang/base.py @@ -1,8 +1,14 @@ -from pyangbind.lib.serialise import pybindJSONDecoder +import ast + from napalm_yang.parser import Parser from napalm_yang.translator import Translator +from napalm_base import validate + + +from pyangbind.lib import yangtypes + class Root(object): """ @@ -55,7 +61,8 @@ def add_model(self, model): def get(self, filter=False): """ - Returns a dictionary with the values of the model. + Returns a dictionary with the values of the model. Note that the values + of the leafs are YANG classes. Args: filter (bool): If set to ``True``, show only values that have been set. @@ -131,7 +138,50 @@ def load_dict(self, data, overwrite=False): if k not in self._elements.keys(): raise AttributeError("Model {} is not loaded".format(k)) attr = getattr(self, k) - pybindJSONDecoder.load_json(v, None, None, obj=attr, overwrite=overwrite) + _load_dict(attr, v) + + def to_dict(self, filter=True): + """ + Returns a dictionary with the values of the model. Note that the values + of the leafs are evaluated to python types. + + Args: + filter (bool): If set to ``True``, show only values that have been set. + + Returns: + dict: A dictionary with the values of the model. + + Example: + + >>> pretty_print(config.to_dict(filter=True)) + >>> { + >>> "interfaces": { + >>> "interface": { + >>> "et1": { + >>> "config": { + >>> "description": "My description", + >>> "mtu": 1500 + >>> }, + >>> "name": "et1" + >>> }, + >>> "et2": { + >>> "config": { + >>> "description": "Another description", + >>> "mtu": 9000 + >>> }, + >>> "name": "et2" + >>> } + >>> } + >>> } + >>> } + + """ + result = {} + for k, v in self: + r = _to_dict(v, filter) + if r: + result[k] = r + return result def parse_config(self, device=None, profile=None, native=None): """ @@ -229,3 +279,79 @@ def translate_config(self, profile, merge=None, replace=None): result.append(translator.translate()) return "\n".join(result) + + def compliance_report(self, validation_file='validate.yml'): + """ + Return a compliance report. + Verify that the device complies with the given validation file and writes a compliance + report file. See https://napalm.readthedocs.io/en/latest/validate.html. + """ + return validate.compliance_report(self, validation_file=validation_file) + + +def _load_dict(cls, data): + for k, v in data.items(): + if cls._yang_type == "list": + try: + attr = cls[k] + except KeyError: + attr = cls.add(k) + _load_dict(attr, v) + elif isinstance(v, dict): + attr = getattr(cls, yangtypes.safe_name(k)) + _load_dict(attr, v) + else: + model = getattr(cls, yangtypes.safe_name(k)) + + # We can't set attributes that are keys + if model._is_keyval: + continue + + setter = getattr(cls, "_set_{}".format(yangtypes.safe_name(k))) + setter(v) + + model = getattr(model._parent, yangtypes.safe_name(k)) + model._mchanged = True + + +def _to_dict(element, filter): + if isinstance(element, Root) or element._yang_type in ("container", None): + result = _to_dict_container(element, filter) + elif element._yang_type in ("list", ): + result = _to_dict_list(element, filter) + else: + result = _to_dict_leaf(element, filter) + + return result + + +def _to_dict_container(element, filter): + result = {} + + for k in element.elements(): + v = getattr(element, k) + r = _to_dict(v, filter) + if r not in [None, {}]: + result[k] = r + return result + + +def _to_dict_list(element, filter): + result = {} + + for k, v in element.items(): + r = _to_dict(v, filter) + if r not in [None, {}]: + result[k] = r + return result + + +def _to_dict_leaf(element, filter): + value = None + if element._changed() or not filter: + try: + value = ast.literal_eval(element.__repr__()) + except Exception: + value = element.__repr__() + + return value diff --git a/napalm_yang/utils.py b/napalm_yang/utils.py index f6e57ad7..62d67209 100644 --- a/napalm_yang/utils.py +++ b/napalm_yang/utils.py @@ -66,7 +66,7 @@ def _diff_root(f, s): r = diff(v, w) if r: - result[k] = diff(v, w) + result[k] = r return result diff --git a/test/integration/profiles_data/ios/openconfig-interfaces/default/replace.txt b/test/integration/profiles_data/ios/openconfig-interfaces/default/replace.txt index 7c2c8ad3..2357de7f 100644 --- a/test/integration/profiles_data/ios/openconfig-interfaces/default/replace.txt +++ b/test/integration/profiles_data/ios/openconfig-interfaces/default/replace.txt @@ -1,5 +1,7 @@ no interface Port-channel1 interface Port-channel1 + no switchport + no switchport description blah mtu 9000 no interface Port-channel1.1 @@ -9,10 +11,14 @@ interface Loopback1 description a loopback default interface GigabitEthernet1 interface GigabitEthernet1 + no switchport mtu 1500 default interface GigabitEthernet2 interface GigabitEthernet2 + no switchport ip address 192.168.0.1 255.255.255.0 + no switchport + no switchport shutdown description so much oc mtu 9000 @@ -28,5 +34,6 @@ interface GigabitEthernet2.2 description asdasdasd default interface GigabitEthernet3 interface GigabitEthernet3 + no switchport shutdown mtu 1500 diff --git a/test/integration/profiles_data/ios/openconfig-interfaces/default/translation.txt b/test/integration/profiles_data/ios/openconfig-interfaces/default/translation.txt index b5936b74..0a7186a2 100644 --- a/test/integration/profiles_data/ios/openconfig-interfaces/default/translation.txt +++ b/test/integration/profiles_data/ios/openconfig-interfaces/default/translation.txt @@ -1,14 +1,20 @@ interface Port-channel1 + no switchport + no switchport description blah mtu 9000 interface Port-channel1.1 interface Loopback1 description a loopback interface GigabitEthernet1 + no switchport description This is a description mtu 1500 interface GigabitEthernet2 + no switchport ip address 192.168.0.1 255.255.255.0 + no switchport + no switchport shutdown description so much oc mtu 1500 @@ -22,5 +28,6 @@ interface GigabitEthernet2.2 ip address 192.168.2.1 255.255.255.0 description asdasdasd interface GigabitEthernet3 + no switchport shutdown mtu 1500 diff --git a/test/integration/test_validate.py b/test/integration/test_validate.py new file mode 100644 index 00000000..59800a94 --- /dev/null +++ b/test/integration/test_validate.py @@ -0,0 +1,71 @@ +from __future__ import unicode_literals + +import pytest + + +import napalm_yang + +import json +import os +import sys + + +import logging +logger = logging.getLogger("napalm-yang") + + +def config_logging(): + logger.setLevel(logging.DEBUG) + ch = logging.StreamHandler(sys.stdout) + formatter = logging.Formatter('%(name)s - %(levelname)s - %(message)s') + ch.setFormatter(formatter) + logger.addHandler(ch) + + +# config_logging() + + +def pretty_json(dictionary): + return json.dumps(dictionary, sort_keys=True, indent=4) + + +BASE_PATH = os.path.dirname(__file__) + + +test_validate = [ + ["ios", napalm_yang.models.openconfig_interfaces, "default", True], + ["ios", napalm_yang.models.openconfig_interfaces, "default_fail", False], +] + + +def read_file_content(profile, model, case, filename): + full_path = os.path.join(BASE_PATH, "validate", + profile, case, filename) + with open(full_path, "r") as f: + return f.read() + + +def read_json(profile, model, case, filename): + return json.loads(read_file_content(profile, model, case, filename)) + + +class Tests(object): + + @pytest.mark.parametrize("profile, model, case, result", test_validate) + def test_validate(self, profile, model, case, result): + data_file = "candidate.json" + validate_file = "validate.yaml" + + data = read_json(profile, model, case, data_file) + + config = napalm_yang.base.Root() + config.add_model(napalm_yang.models.openconfig_interfaces) + config.load_dict(data) + + # print(pretty_json(config.to_dict(filter=True))) + + validation_file = os.path.join(BASE_PATH, "validate", profile, case, validate_file) + + report = config.compliance_report(validation_file) + + assert report["complies"] == result, report diff --git a/test/integration/validate/ios/default/candidate.json b/test/integration/validate/ios/default/candidate.json new file mode 100644 index 00000000..b2f7f2e1 --- /dev/null +++ b/test/integration/validate/ios/default/candidate.json @@ -0,0 +1,180 @@ +{ + "interfaces": { + "interface": { + "GigabitEthernet1": { + "config": { + "enabled": true, + "mtu": 1500, + "type": "ethernetCsmacd" + }, + "name": "GigabitEthernet1", + "routed-vlan": { + "ipv4": { + "config": { + "enabled": true + } + } + } + }, + "GigabitEthernet2": { + "config": { + "description": "so much oc", + "enabled": false, + "mtu": 9000, + "type": "ethernetCsmacd" + }, + "name": "GigabitEthernet2", + "routed-vlan": { + "ipv4": { + "addresses": { + "address": { + "192.168.0.1": { + "config": { + "ip": "192.168.0.1", + "prefix-length": 24, + "secondary": false + }, + "ip": "192.168.0.1" + } + } + }, + "config": { + "enabled": true + } + } + }, + "subinterfaces": { + "subinterface": { + "1": { + "config": { + "description": "another subiface", + "enabled": true, + "name": "GigabitEthernet2.1" + }, + "index": "1", + "ipv4": { + "addresses": { + "address": { + "192.168.20.1": { + "config": { + "ip": "192.168.20.1", + "prefix-length": 24, + "secondary": true + }, + "ip": "192.168.20.1" + }, + "192.168.1.1": { + "config": { + "ip": "192.168.1.1", + "prefix-length": 24, + "secondary": false + }, + "ip": "192.168.1.1" + } + } + }, + "config": { + "enabled": true + } + } + }, + "2": { + "config": { + "description": "asdasdasd", + "enabled": true, + "name": "GigabitEthernet2.2" + }, + "index": "2", + "ipv4": { + "addresses": { + "address": { + "192.168.2.1": { + "config": { + "ip": "192.168.2.1", + "prefix-length": 24, + "secondary": false + }, + "ip": "192.168.2.1" + } + } + }, + "config": { + "enabled": true + } + }, + "vlan": { + "config": { + "vlan-id": 2 + } + } + } + } + } + }, + "GigabitEthernet3": { + "config": { + "enabled": false, + "mtu": 1500, + "type": "ethernetCsmacd" + }, + "name": "GigabitEthernet3", + "routed-vlan": { + "ipv4": { + "config": { + "enabled": true + } + } + } + }, + "Loopback1": { + "config": { + "description": "a loopback", + "enabled": true, + "mtu": 1514, + "type": "softwareLoopback" + }, + "name": "Loopback1", + "routed-vlan": { + "ipv4": { + "config": { + "enabled": true + } + } + } + }, + "Port-channel1": { + "config": { + "description": "blah", + "enabled": true, + "mtu": 9000, + "type": "ieee8023adLag" + }, + "name": "Port-channel1", + "routed-vlan": { + "ipv4": { + "config": { + "enabled": true + } + } + }, + "subinterfaces": { + "subinterface": { + "1": { + "config": { + "enabled": true, + "name": "Port-channel1.1" + }, + "index": "1", + "ipv4": { + "config": { + "enabled": true + } + } + } + } + } + } + } + } +} + diff --git a/test/integration/validate/ios/default/validate.yaml b/test/integration/validate/ios/default/validate.yaml new file mode 100644 index 00000000..4440979d --- /dev/null +++ b/test/integration/validate/ios/default/validate.yaml @@ -0,0 +1,22 @@ +--- +- to_dict: + _kwargs: + filter: true + interfaces: + interface: + GigabitEthernet1: + config: + enabled: true + GigabitEthernet2: + config: + enabled: false + GigabitEthernet3: + config: + enabled: false + Port-channel1: + config: + enabled: true + Loopback1: + config: + enabled: true + _mode: strict diff --git a/test/integration/validate/ios/default_fail/candidate.json b/test/integration/validate/ios/default_fail/candidate.json new file mode 100644 index 00000000..b2f7f2e1 --- /dev/null +++ b/test/integration/validate/ios/default_fail/candidate.json @@ -0,0 +1,180 @@ +{ + "interfaces": { + "interface": { + "GigabitEthernet1": { + "config": { + "enabled": true, + "mtu": 1500, + "type": "ethernetCsmacd" + }, + "name": "GigabitEthernet1", + "routed-vlan": { + "ipv4": { + "config": { + "enabled": true + } + } + } + }, + "GigabitEthernet2": { + "config": { + "description": "so much oc", + "enabled": false, + "mtu": 9000, + "type": "ethernetCsmacd" + }, + "name": "GigabitEthernet2", + "routed-vlan": { + "ipv4": { + "addresses": { + "address": { + "192.168.0.1": { + "config": { + "ip": "192.168.0.1", + "prefix-length": 24, + "secondary": false + }, + "ip": "192.168.0.1" + } + } + }, + "config": { + "enabled": true + } + } + }, + "subinterfaces": { + "subinterface": { + "1": { + "config": { + "description": "another subiface", + "enabled": true, + "name": "GigabitEthernet2.1" + }, + "index": "1", + "ipv4": { + "addresses": { + "address": { + "192.168.20.1": { + "config": { + "ip": "192.168.20.1", + "prefix-length": 24, + "secondary": true + }, + "ip": "192.168.20.1" + }, + "192.168.1.1": { + "config": { + "ip": "192.168.1.1", + "prefix-length": 24, + "secondary": false + }, + "ip": "192.168.1.1" + } + } + }, + "config": { + "enabled": true + } + } + }, + "2": { + "config": { + "description": "asdasdasd", + "enabled": true, + "name": "GigabitEthernet2.2" + }, + "index": "2", + "ipv4": { + "addresses": { + "address": { + "192.168.2.1": { + "config": { + "ip": "192.168.2.1", + "prefix-length": 24, + "secondary": false + }, + "ip": "192.168.2.1" + } + } + }, + "config": { + "enabled": true + } + }, + "vlan": { + "config": { + "vlan-id": 2 + } + } + } + } + } + }, + "GigabitEthernet3": { + "config": { + "enabled": false, + "mtu": 1500, + "type": "ethernetCsmacd" + }, + "name": "GigabitEthernet3", + "routed-vlan": { + "ipv4": { + "config": { + "enabled": true + } + } + } + }, + "Loopback1": { + "config": { + "description": "a loopback", + "enabled": true, + "mtu": 1514, + "type": "softwareLoopback" + }, + "name": "Loopback1", + "routed-vlan": { + "ipv4": { + "config": { + "enabled": true + } + } + } + }, + "Port-channel1": { + "config": { + "description": "blah", + "enabled": true, + "mtu": 9000, + "type": "ieee8023adLag" + }, + "name": "Port-channel1", + "routed-vlan": { + "ipv4": { + "config": { + "enabled": true + } + } + }, + "subinterfaces": { + "subinterface": { + "1": { + "config": { + "enabled": true, + "name": "Port-channel1.1" + }, + "index": "1", + "ipv4": { + "config": { + "enabled": true + } + } + } + } + } + } + } + } +} + diff --git a/test/integration/validate/ios/default_fail/validate.yaml b/test/integration/validate/ios/default_fail/validate.yaml new file mode 100644 index 00000000..2a3b12d6 --- /dev/null +++ b/test/integration/validate/ios/default_fail/validate.yaml @@ -0,0 +1,22 @@ +--- +- to_dict: + _kwargs: + filter: true + interfaces: + interface: + GigabitEthernet1: + config: + enabled: false + GigabitEthernet2: + config: + enabled: false + GigabitEthernet3: + config: + enabled: false + Port-channel1: + config: + enabled: true + Loopback1: + config: + enabled: true + _mode: strict From 1258d5f60604a6f6076daa9638fc1f5f9a7952c6 Mon Sep 17 00:00:00 2001 From: David Barroso Date: Thu, 30 Mar 2017 16:48:55 +0200 Subject: [PATCH 2/3] Updating requirements --- 1 | 21 +++++++++++++++++++++ requirements.txt | 1 + 2 files changed, 22 insertions(+) create mode 100644 1 diff --git a/1 b/1 new file mode 100644 index 00000000..593751c2 --- /dev/null +++ b/1 @@ -0,0 +1,21 @@ +r 609e192 Load dict now update even on defaults and added compliance_report to Root class +f 90f6a83 Added docstring + +# Rebase c9cdf79..90f6a83 onto c9cdf79 (2 commands) +# +# Commands: +# p, pick = use commit +# r, reword = use commit, but edit the commit message +# e, edit = use commit, but stop for amending +# s, squash = use commit, but meld into previous commit +# f, fixup = like "squash", but discard this commit's log message +# x, exec = run command (the rest of the line) using shell +# d, drop = remove commit +# +# These lines can be re-ordered; they are executed from top to bottom. +# +# If you remove a line here THAT COMMIT WILL BE LOST. +# +# However, if you remove everything, the rebase will be aborted. +# +# Note that empty commits are commented out diff --git a/requirements.txt b/requirements.txt index 79fbd12e..ff4ce162 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,4 +3,5 @@ netaddr lxml jinja2 PyYAML +napalm-base -e git://github.com/napalm-automation/pyangbind.git@napalm_custom#egg=pyangbind From 3dded32d1b99a5cbface6ccd3e32abc4eced7322 Mon Sep 17 00:00:00 2001 From: David Barroso Date: Thu, 30 Mar 2017 16:49:15 +0200 Subject: [PATCH 3/3] Bump version --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index c80bd3bf..0b5be1e2 100644 --- a/setup.py +++ b/setup.py @@ -12,7 +12,7 @@ setup( name="napalm-yang", - version="0.0.5", + version="0.0.6", packages=find_packages(), author="David Barroso", author_email="dbarrosop@dravetech.com",