[Python-modules-commits] [napalm-base] 02/04: New upstream release.

Vincent Bernat bernat at moszumanska.debian.org
Tue Nov 14 09:36:53 UTC 2017


This is an automated email from the git hooks/post-receive script.

bernat pushed a commit to annotated tag debian/0.25.0-1
in repository napalm-base.

commit 8c7d30ec0056b7135d4af8cb4d38d3607464c1d3
Author: Vincent Bernat <bernat at debian.org>
Date:   Tue Nov 14 10:28:51 2017 +0100

    New upstream release.
---
 PKG-INFO                                    |   6 +-
 napalm_base.egg-info/PKG-INFO               |   6 +-
 napalm_base.egg-info/SOURCES.txt            |   2 +
 napalm_base.egg-info/entry_points.txt       |   1 +
 napalm_base/__init__.py                     |   7 +-
 napalm_base/base.py                         | 138 ++++++++-----
 napalm_base/clitools/cl_napalm.py           | 289 ++++++++++++++++++++++++++++
 napalm_base/clitools/cl_napalm_configure.py |   3 +
 napalm_base/clitools/cl_napalm_test.py      |   3 +
 napalm_base/clitools/cl_napalm_validate.py  |   3 +
 napalm_base/clitools/helpers.py             |  15 +-
 napalm_base/constants.py                    |   4 +
 napalm_base/exceptions.py                   |  56 ++++++
 napalm_base/helpers.py                      |  59 +++---
 napalm_base/mock.py                         | 199 +++++++++++++++++++
 napalm_base/test/base.py                    |  10 +
 napalm_base/test/conftest.py                |   4 +-
 napalm_base/test/getters.py                 |  44 +++--
 napalm_base/test/helpers.py                 |  11 +-
 napalm_base/utils/string_parsers.py         |  47 +++--
 napalm_base/validate.py                     | 109 +++++++++--
 setup.cfg                                   |  28 ++-
 setup.py                                    |   9 +-
 23 files changed, 910 insertions(+), 143 deletions(-)

diff --git a/PKG-INFO b/PKG-INFO
index e0cb296..9f60a8f 100644
--- a/PKG-INFO
+++ b/PKG-INFO
@@ -1,10 +1,10 @@
 Metadata-Version: 1.1
 Name: napalm-base
-Version: 0.21.0
+Version: 0.25.0
 Summary: Network Automation and Programmability Abstraction Layer with Multivendor support
 Home-page: https://github.com/napalm-automation/napalm-base
-Author: David Barroso
-Author-email: dbarrosop at dravetech.com
+Author: David Barroso, Kirk Byers, Mircea Ulinic
+Author-email: dbarrosop at dravetech.com, ping at mirceaulinic.net, ktbyers at twb-tech.com
 License: UNKNOWN
 Description: UNKNOWN
 Platform: UNKNOWN
diff --git a/napalm_base.egg-info/PKG-INFO b/napalm_base.egg-info/PKG-INFO
index e0cb296..9f60a8f 100644
--- a/napalm_base.egg-info/PKG-INFO
+++ b/napalm_base.egg-info/PKG-INFO
@@ -1,10 +1,10 @@
 Metadata-Version: 1.1
 Name: napalm-base
-Version: 0.21.0
+Version: 0.25.0
 Summary: Network Automation and Programmability Abstraction Layer with Multivendor support
 Home-page: https://github.com/napalm-automation/napalm-base
-Author: David Barroso
-Author-email: dbarrosop at dravetech.com
+Author: David Barroso, Kirk Byers, Mircea Ulinic
+Author-email: dbarrosop at dravetech.com, ping at mirceaulinic.net, ktbyers at twb-tech.com
 License: UNKNOWN
 Description: UNKNOWN
 Platform: UNKNOWN
diff --git a/napalm_base.egg-info/SOURCES.txt b/napalm_base.egg-info/SOURCES.txt
index 590c02d..56aaa63 100644
--- a/napalm_base.egg-info/SOURCES.txt
+++ b/napalm_base.egg-info/SOURCES.txt
@@ -7,6 +7,7 @@ napalm_base/base.py
 napalm_base/constants.py
 napalm_base/exceptions.py
 napalm_base/helpers.py
+napalm_base/mock.py
 napalm_base/validate.py
 napalm_base.egg-info/PKG-INFO
 napalm_base.egg-info/SOURCES.txt
@@ -15,6 +16,7 @@ napalm_base.egg-info/entry_points.txt
 napalm_base.egg-info/requires.txt
 napalm_base.egg-info/top_level.txt
 napalm_base/clitools/__init__.py
+napalm_base/clitools/cl_napalm.py
 napalm_base/clitools/cl_napalm_configure.py
 napalm_base/clitools/cl_napalm_test.py
 napalm_base/clitools/cl_napalm_validate.py
diff --git a/napalm_base.egg-info/entry_points.txt b/napalm_base.egg-info/entry_points.txt
index 2a3eb91..3bcf363 100644
--- a/napalm_base.egg-info/entry_points.txt
+++ b/napalm_base.egg-info/entry_points.txt
@@ -2,4 +2,5 @@
 cl_napalm_configure = napalm_base.clitools.cl_napalm_configure:main
 cl_napalm_test = napalm_base.clitools.cl_napalm_test:main
 cl_napalm_validate = napalm_base.clitools.cl_napalm_validate:main
+napalm = napalm_base.clitools.cl_napalm:main
 
diff --git a/napalm_base/__init__.py b/napalm_base/__init__.py
index 3b09d4d..bce3d11 100644
--- a/napalm_base/__init__.py
+++ b/napalm_base/__init__.py
@@ -35,6 +35,7 @@ except AttributeError:
 # NAPALM base
 from napalm_base.base import NetworkDriver
 from napalm_base.exceptions import ModuleImportError
+from napalm_base.mock import MockDriver
 from napalm_base.utils import py23_compat
 
 try:
@@ -49,7 +50,7 @@ __all__ = [
 ]
 
 
-def get_network_driver(module_name):
+def get_network_driver(module_name, prepend=True):
     """
     Searches for a class derived form the base NAPALM class NetworkDriver in a specific library.
     The library name must repect the following pattern: napalm_[DEVICE_OS].
@@ -81,6 +82,8 @@ def get_network_driver(module_name):
         napalm_base.exceptions.ModuleImportError: Cannot import "napalm_wrong". Is the library \
         installed?
     """
+    if module_name == "mock":
+        return MockDriver
 
     if not (isinstance(module_name, py23_compat.string_types) and len(module_name) > 0):
         raise ModuleImportError('Please provide a valid driver name.')
@@ -91,7 +94,7 @@ def get_network_driver(module_name):
         # Try to not raise error when users requests IOS-XR for e.g.
         module_install_name = module_name.replace('-', '')
         # Can also request using napalm_[SOMETHING]
-        if 'napalm_' not in module_install_name:
+        if 'napalm_' not in module_install_name and prepend is True:
             module_install_name = 'napalm_{name}'.format(name=module_install_name)
         module = importlib.import_module(module_install_name)
     except ImportError:
diff --git a/napalm_base/base.py b/napalm_base/base.py
index 9a512ec..1912f00 100644
--- a/napalm_base/base.py
+++ b/napalm_base/base.py
@@ -16,9 +16,6 @@
 from __future__ import print_function
 from __future__ import unicode_literals
 
-# std libs
-import sys
-
 # local modules
 import napalm_base.exceptions
 import napalm_base.helpers
@@ -47,39 +44,31 @@ class NetworkDriver(object):
         raise NotImplementedError
 
     def __enter__(self):
-        try:
-            self.open()
-        except:
-            exc_info = sys.exc_info()
-            self.__raise_clean_exception(exc_info[0], exc_info[1], exc_info[2])
+        self.open()
         return self
 
     def __exit__(self, exc_type, exc_value, exc_traceback):
         self.close()
-        if exc_type is not None:
-            self.__raise_clean_exception(exc_type, exc_value, exc_traceback)
-
-    @staticmethod
-    def __raise_clean_exception(exc_type, exc_value, exc_traceback):
-        """
-        This method is going to check if the exception exc_type is part of the builtins exceptions
-        or part of the napalm exceptions. If it is not, it will print a message on the screen
-        giving instructions to fill a bug.
-
-        Finally it will raise the original exception.
-
-        :param exc_type: Exception class.
-        :param exc_value: Exception object.
-        :param exc_traceback: Traceback.
-        """
-        if (exc_type.__name__ not in dir(napalm_base.exceptions) and
-                exc_type.__name__ not in __builtins__.keys()):
+        if exc_type is not None and (
+                            exc_type.__name__ not in dir(napalm_base.exceptions) and
+                            exc_type.__name__ not in __builtins__.keys()):
             epilog = ("NAPALM didn't catch this exception. Please, fill a bugfix on "
                       "https://github.com/napalm-automation/napalm/issues\n"
                       "Don't forget to include this traceback.")
             print(epilog)
-        # Traceback should already be attached to exception; no need to re-attach
-        raise exc_value
+            return False
+
+    def __del__(self):
+        """
+        This method is used to cleanup when the program is terminated suddenly.
+        We need to make sure the connection is closed properly and the configuration DB
+        is released (unlocked).
+        """
+        try:
+            if self.is_alive()["is_alive"]:
+                self.close()
+        except Exception:
+            pass
 
     def open(self):
         """
@@ -103,6 +92,30 @@ class NetworkDriver(object):
         """
         raise NotImplementedError
 
+    def pre_connection_tests(self):
+        """
+        This is a helper function used by the cli tool cl_napalm_show_tech. Drivers
+        can override this method to do some tests, show information, enable debugging, etc.
+        before a connection with the device is attempted.
+        """
+        raise NotImplementedError
+
+    def connection_tests(self):
+        """
+        This is a helper function used by the cli tool cl_napalm_show_tech. Drivers
+        can override this method to do some tests, show information, enable debugging, etc.
+        before a connection with the device has been successful.
+        """
+        raise NotImplementedError
+
+    def post_connection_tests(self):
+        """
+        This is a helper function used by the cli tool cl_napalm_show_tech. Drivers
+        can override this method to do some tests, show information, enable debugging, etc.
+        after a connection with the device has been closed successfully.
+        """
+        raise NotImplementedError
+
     def load_template(self, template_name, template_source=None,
                       template_path=None, **template_vars):
         """
@@ -231,37 +244,37 @@ class NetworkDriver(object):
                 {
                 'is_up': False,
                 'is_enabled': False,
-                'description': u'',
+                'description': '',
                 'last_flapped': -1,
                 'speed': 1000,
-                'mac_address': u'dead:beef:dead',
+                'mac_address': 'FA:16:3E:57:33:61',
                 },
             u'Ethernet1':
                 {
                 'is_up': True,
                 'is_enabled': True,
-                'description': u'foo',
+                'description': 'foo',
                 'last_flapped': 1429978575.1554043,
                 'speed': 1000,
-                'mac_address': u'beef:dead:beef',
+                'mac_address': 'FA:16:3E:57:33:62',
                 },
             u'Ethernet2':
                 {
                 'is_up': True,
                 'is_enabled': True,
-                'description': u'bla',
+                'description': 'bla',
                 'last_flapped': 1429978575.1555667,
                 'speed': 1000,
-                'mac_address': u'beef:beef:beef',
+                'mac_address': 'FA:16:3E:57:33:63',
                 },
             u'Ethernet3':
                 {
                 'is_up': False,
                 'is_enabled': True,
-                'description': u'bar',
+                'description': 'bar',
                 'last_flapped': -1,
                 'speed': 1000,
-                'mac_address': u'dead:dead:dead',
+                'mac_address': 'FA:16:3E:57:33:64',
                 }
             }
         """
@@ -333,6 +346,40 @@ class NetworkDriver(object):
                 * received_prefixes (int)
                 * accepted_prefixes (int)
                 * sent_prefixes (int)
+
+            Note, if is_up is False and uptime has a positive value then this indicates the
+            uptime of the last active BGP session.
+
+            Example response:
+            {
+              "global": {
+                "router_id": "10.0.1.1",
+                "peers": {
+                  "10.0.0.2": {
+                    "local_as": 65000,
+                    "remote_as": 65000,
+                    "remote_id": "10.0.1.2",
+                    "is_up": True,
+                    "is_enabled": True,
+                    "description": "internal-2",
+                    "uptime": 4838400,
+                    "address_family": {
+                      "ipv4": {
+                        "sent_prefixes": 637213,
+                        "accepted_prefixes": 3142,
+                        "received_prefixes": 3142
+                      },
+                      "ipv6": {
+                        "sent_prefixes": 36714,
+                        "accepted_prefixes": 148,
+                        "received_prefixes": 148
+                      }
+                    }
+                  }
+                }
+              }
+            }
+
         """
         raise NotImplementedError
 
@@ -471,7 +518,7 @@ class NetworkDriver(object):
         :param neighbor: Returns the configuration of a specific BGP neighbor.
 
         Main dictionary keys represent the group name and the values represent a dictionary having
-        the following keys:
+        the keys below. Neighbors which aren't members of a group will be stored in a key named "_":
             * type (string)
             * description (string)
             * apply_groups (string list)
@@ -694,13 +741,13 @@ class NetworkDriver(object):
             [
                 {
                     'interface' : 'MgmtEth0/RSP0/CPU0/0',
-                    'mac'       : '5c:5e:ab:da:3c:f0',
+                    'mac'       : '5C:5E:AB:DA:3C:F0',
                     'ip'        : '172.17.17.1',
                     'age'       : 1454496274.84
                 },
                 {
                     'interface' : 'MgmtEth0/RSP0/CPU0/0',
-                    'mac'       : '66:0e:94:96:e0:ff',
+                    'mac'       : '5C:5E:AB:DA:3C:FF',
                     'ip'        : '172.17.17.2',
                     'age'       : 1435641582.49
                 }
@@ -859,7 +906,7 @@ class NetworkDriver(object):
 
             [
                 {
-                    'mac'       : '00:1c:58:29:4a:71',
+                    'mac'       : '00:1C:58:29:4A:71',
                     'interface' : 'Ethernet47',
                     'vlan'      : 100,
                     'static'    : False,
@@ -868,7 +915,7 @@ class NetworkDriver(object):
                     'last_move' : 1454417742.58
                 },
                 {
-                    'mac'       : '8c:60:4f:58:e1:c1',
+                    'mac'       : '00:1C:58:29:4A:C1',
                     'interface' : 'xe-1/0/1',
                     'vlan'       : 100,
                     'static'    : False,
@@ -877,7 +924,7 @@ class NetworkDriver(object):
                     'last_move' : 1453191948.11
                 },
                 {
-                    'mac'       : 'f4:b5:2f:56:72:01',
+                    'mac'       : '00:1C:58:29:4A:C2',
                     'interface' : 'ae7.900',
                     'vlan'      : 900,
                     'static'    : False,
@@ -1111,7 +1158,7 @@ class NetworkDriver(object):
         raise NotImplementedError
 
     def ping(self, destination, source=c.PING_SOURCE, ttl=c.PING_TTL, timeout=c.PING_TIMEOUT,
-             size=c.PING_SIZE, count=c.PING_COUNT):
+             size=c.PING_SIZE, count=c.PING_COUNT, vrf=c.PING_VRF):
         """
         Executes ping on the device and returns a dictionary with the result
 
@@ -1178,7 +1225,8 @@ class NetworkDriver(object):
                    destination,
                    source=c.TRACEROUTE_SOURCE,
                    ttl=c.TRACEROUTE_TTL,
-                   timeout=c.TRACEROUTE_TIMEOUT):
+                   timeout=c.TRACEROUTE_TIMEOUT,
+                   vrf=c.TRACEROUTE_VRF):
         """
         Executes traceroute on the device and returns a dictionary with the result.
 
@@ -1496,6 +1544,6 @@ class NetworkDriver(object):
         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.
+        report file. See https://napalm.readthedocs.io/en/latest/validate/index.html.
         """
         return validate.compliance_report(self, validation_file=validation_file)
diff --git a/napalm_base/clitools/cl_napalm.py b/napalm_base/clitools/cl_napalm.py
new file mode 100755
index 0000000..9ca4682
--- /dev/null
+++ b/napalm_base/clitools/cl_napalm.py
@@ -0,0 +1,289 @@
+# Python3 support
+from __future__ import print_function
+from __future__ import unicode_literals
+
+# import helpers
+from napalm_base import get_network_driver
+from napalm_base.clitools import helpers
+
+# stdlib
+import pip
+import json
+import logging
+import argparse
+import getpass
+from functools import wraps
+
+
+def debugging(name):
+    def real_decorator(func):
+        @wraps(func)
+        def wrapper(*args, **kwargs):
+                censor_parameters = ["password"]
+                censored_kwargs = {k: v if k not in censor_parameters else "*******"
+                                   for k, v in kwargs.items()}
+                logger.debug("{} - Calling with args: {}, {}".format(name, args, censored_kwargs))
+                try:
+                    r = func(*args, **kwargs)
+                    logger.debug("{} - Successful".format(name))
+                    return r
+                except NotImplementedError:
+                    if name not in ["pre_connection_tests", "connection_tests",
+                                    "post_connection_tests"]:
+                        logger.debug("{} - Not implemented".format(name))
+                except Exception as e:
+                    logger.error("{} - Failed: {}".format(name, e))
+                    print("\n================= Traceback =================\n")
+                    raise
+        return wrapper
+    return real_decorator
+
+
+logger = logging.getLogger('napalm')
+
+
+def build_help():
+    parser = argparse.ArgumentParser(
+        description='Command line tool to handle configuration on devices using NAPALM.'
+                    'The script will print the diff on the screen',
+        epilog='Automate all the things!!!'
+    )
+    parser.add_argument(
+        dest='hostname',
+        action='store',
+        help='Host where you want to deploy the configuration.'
+    )
+    parser.add_argument(
+        '--user', '-u',
+        dest='user',
+        action='store',
+        default=getpass.getuser(),
+        help='User for authenticating to the host. Default: user running the script.'
+    )
+    parser.add_argument(
+        '--password', '-p',
+        dest='password',
+        action='store',
+        help='Password for authenticating to the host.'
+             'If you do not provide a password in the CLI you will be prompted.',
+    )
+    parser.add_argument(
+        '--vendor', '-v',
+        dest='vendor',
+        action='store',
+        required=True,
+        help='Host Operating System.'
+    )
+    parser.add_argument(
+        '--optional_args', '-o',
+        dest='optional_args',
+        action='store',
+        help='String with comma separated key=value pairs passed via optional_args to the driver.',
+    )
+    parser.add_argument(
+        '--debug',
+        dest='debug',
+        action='store_true',
+        help='Enables debug mode; more verbosity.'
+    )
+    subparser = parser.add_subparsers(title='actions')
+
+    config = subparser.add_parser('configure', help='Perform a configuration operation')
+    config.set_defaults(which='config')
+    config.add_argument(
+        dest='config_file',
+        action='store',
+        help='File containing the configuration you want to deploy.'
+    )
+    config.add_argument(
+        '--strategy', '-s',
+        dest='strategy',
+        action='store',
+        choices=['replace', 'merge'],
+        default='replace',
+        help='Strategy to use to deploy configuration. Default: replace.'
+    )
+    config.add_argument(
+        '--dry-run', '-d',
+        dest='dry_run',
+        action='store_true',
+        default=None,
+        help='Only returns diff, it does not deploy the configuration.',
+    )
+
+    call = subparser.add_parser('call', help='Call a napalm method')
+    call.set_defaults(which='call')
+    call.add_argument(
+        dest='method',
+        action='store',
+        help='Run this method'
+    )
+    call.add_argument(
+        '--method-kwargs', '-k',
+        dest='method_kwargs',
+        action='store',
+        help='kwargs to pass to the method. For example: "destination=1.1.1.1,protocol=bgp"'
+    )
+
+    validate = subparser.add_parser('validate', help='Validate configuration/state')
+    validate.set_defaults(which='validate')
+    validate.add_argument(
+        dest='validation_file',
+        action='store',
+        help='Validation file containing resources derised states'
+    )
+    args = parser.parse_args()
+
+    if args.password is None:
+        password = getpass.getpass('Enter password: ')
+        setattr(args, 'password', password)
+
+    return args
+
+
+def check_installed_packages():
+    logger.debug("Gathering napalm packages")
+    installed_packages = pip.get_installed_distributions()
+    napalm_packages = sorted(["{}=={}".format(i.key, i.version)
+                              for i in installed_packages if i.key.startswith("napalm")])
+    for n in napalm_packages:
+        logger.debug(n)
+
+
+ at debugging("get_network_driver")
+def call_get_network_driver(vendor):
+    return get_network_driver(vendor)
+
+
+ at debugging("__init__")
+def call_instantiating_object(driver, *args, **kwargs):
+    return driver(*args, **kwargs)
+
+
+ at debugging("pre_connection_tests")
+def call_pre_connection(driver):
+    driver.pre_connection_tests()
+
+
+ at debugging("connection_tests")
+def call_connection(device):
+    device.connection_tests()
+
+
+ at debugging("post_connection_tests")
+def call_post_connection(device):
+    device.post_connection_tests()
+
+
+ at debugging("get_facts")
+def call_facts(device):
+    facts = device.get_facts()
+    logger.debug("Gathered facts:\n{}".format(json.dumps(facts, indent=4)))
+    print(json.dumps(facts, indent=4))
+
+
+ at debugging("close")
+def call_close(device):
+    return device.close()
+
+
+ at debugging("open")
+def call_open_device(device):
+    return device.open()
+
+
+ at debugging("load_replace_candidate")
+def call_load_replace_candidate(device, *args, **kwargs):
+    return device.load_replace_candidate(*args, **kwargs)
+
+
+ at debugging("load_merge_candidate")
+def call_load_merge_candidate(device, *args, **kwargs):
+    return device.load_merge_candidate(*args, **kwargs)
+
+
+ at debugging("compare_config")
+def call_compare_config(device, *args, **kwargs):
+    diff = device.compare_config(*args, **kwargs)
+    logger.debug("Gathered diff:")
+    print(diff)
+    return diff
+
+
+ at debugging("commit_config")
+def call_commit_config(device, *args, **kwargs):
+    return device.commit_config(*args, **kwargs)
+
+
+def configuration_change(device, config_file, strategy, dry_run):
+    if strategy == 'replace':
+        strategy_method = call_load_replace_candidate
+    elif strategy == 'merge':
+        strategy_method = call_load_merge_candidate
+
+    strategy_method(device, filename=config_file)
+
+    diff = call_compare_config(device)
+
+    if not dry_run:
+        call_commit_config(device)
+    return diff
+
+
+ at debugging("method")
+def call_getter(device, method, **kwargs):
+    logger.debug("{} - Attempting to resolve method".format(method))
+    func = getattr(device, method)
+    logger.debug("{} - Attempting to call method with kwargs: {}".format(method, kwargs))
+    r = func(**kwargs)
+    logger.debug("{} - Response".format(method))
+    print(json.dumps(r, indent=4))
+
+
+ at debugging("compliance_report")
+def call_compliance_report(device, validation_file):
+    result = device.compliance_report(validation_file)
+    print(json.dumps(result, indent=4))
+    return result
+
+
+def run_tests(args):
+    driver = call_get_network_driver(args.vendor)
+    optional_args = helpers.parse_optional_args(args.optional_args)
+
+    device = call_instantiating_object(driver, args.hostname, args.user, password=args.password,
+                                       timeout=60, optional_args=optional_args)
+
+    if args.debug:
+        call_pre_connection(device)
+
+    call_open_device(device)
+
+    if args.debug:
+        call_connection(device)
+        call_facts(device)
+
+    if args.which == 'call':
+        method_kwargs = helpers.parse_optional_args(args.method_kwargs)
+        call_getter(device, args.method, **method_kwargs)
+    elif args.which == 'config':
+        configuration_change(device, args.config_file, args.strategy, args.dry_run)
+    elif args.which == 'validate':
+        call_compliance_report(device, args.validation_file)
+
+    call_close(device)
+
+    if args.debug:
+        call_post_connection(device)
+
+
+def main():
+    args = build_help()
+    helpers.configure_logging(logger, debug=args.debug)
+    logger.debug("Starting napalm's debugging tool")
+    check_installed_packages()
+    run_tests(args)
+
+
+if __name__ == '__main__':
+    main()
diff --git a/napalm_base/clitools/cl_napalm_configure.py b/napalm_base/clitools/cl_napalm_configure.py
index fce6b32..3924fc4 100644
--- a/napalm_base/clitools/cl_napalm_configure.py
+++ b/napalm_base/clitools/cl_napalm_configure.py
@@ -15,10 +15,13 @@ from napalm_base import get_network_driver
 from napalm_base.clitools.helpers import build_help
 from napalm_base.clitools.helpers import configure_logging
 from napalm_base.clitools.helpers import parse_optional_args
+from napalm_base.clitools.helpers import warning
 
 import sys
 import logging
+
 logger = logging.getLogger('cl-napalm-config.py')
+warning()
 
 
 def run(vendor, hostname, user, password, strategy, optional_args, config_file, dry_run):
diff --git a/napalm_base/clitools/cl_napalm_test.py b/napalm_base/clitools/cl_napalm_test.py
index 5f934c1..7bbe682 100644
--- a/napalm_base/clitools/cl_napalm_test.py
+++ b/napalm_base/clitools/cl_napalm_test.py
@@ -14,11 +14,14 @@ from napalm_base import get_network_driver
 from napalm_base.clitools.helpers import build_help
 from napalm_base.clitools.helpers import configure_logging
 from napalm_base.clitools.helpers import parse_optional_args
+from napalm_base.clitools.helpers import warning
 
 # stdlib
 import sys
 import logging
+
 logger = logging.getLogger('cl_napalm_test.py')
+warning()
 
 
 def main():
diff --git a/napalm_base/clitools/cl_napalm_validate.py b/napalm_base/clitools/cl_napalm_validate.py
index 69b55a0..825d79d 100755
--- a/napalm_base/clitools/cl_napalm_validate.py
+++ b/napalm_base/clitools/cl_napalm_validate.py
@@ -15,12 +15,15 @@ from napalm_base import get_network_driver
 from napalm_base.clitools.helpers import build_help
 from napalm_base.clitools.helpers import configure_logging
 from napalm_base.clitools.helpers import parse_optional_args
+from napalm_base.clitools.helpers import warning
 
 # stdlib
 import sys
 import json
 import logging
+
 logger = logging.getLogger('cl_napalm_validate.py')
+warning()
 
 
 def main():
diff --git a/napalm_base/clitools/helpers.py b/napalm_base/clitools/helpers.py
index 329884b..d3020e5 100644
--- a/napalm_base/clitools/helpers.py
+++ b/napalm_base/clitools/helpers.py
@@ -10,13 +10,21 @@ from __future__ import print_function
 from __future__ import unicode_literals
 
 # stdlib
+import ast
 import sys
 import logging
 import getpass
 import argparse
+import warnings
 
 
-def build_help(connect_test=False, validate=False, configure=False):
+def warning():
+    warnings.simplefilter('always', DeprecationWarning)
+    warnings.warn("This tool has been deprecated, please use `napalm` instead\n",
+                  DeprecationWarning)
+
+
+def build_help(connect_test=False, validate=False, configure=False, napalm_cli=False):
     parser = argparse.ArgumentParser(
         description='Command line tool to handle configuration on devices using NAPALM.'
                     'The script will print the diff on the screen',
@@ -60,6 +68,7 @@ def build_help(connect_test=False, validate=False, configure=False):
         action='store_true',
         help='Enables debug mode; more verbosity.'
     )
+
     if configure:
         parser.add_argument(
             '--strategy', '-s',
@@ -111,7 +120,7 @@ def configure_logging(logger, debug):
 
 
 def parse_optional_args(optional_args):
-
     if optional_args is not None:
-        return {x.split('=')[0]: x.split('=')[1] for x in optional_args.replace(' ', '').split(',')}
+        return {x.split('=')[0]: ast.literal_eval(x.split('=')[1])
+                for x in optional_args.split(',')}
     return {}
diff --git a/napalm_base/constants.py b/napalm_base/constants.py
index c7b7823..bcfbff9 100644
--- a/napalm_base/constants.py
+++ b/napalm_base/constants.py
@@ -7,6 +7,8 @@ TIMEOUT = 60  # seconds
 
 INTERFACE_NULL_SPEED = -1
 
+ACTION_TYPE_METHODS = ('ping', 'traceroute', )
+
 BGP_NEIGHBOR_NULL_COUNTER = -1
 
 SNMP_AUTHORIZATION_MODE_MAP = {
@@ -54,6 +56,7 @@ TRACEROUTE_SOURCE = ''
 TRACEROUTE_TIMEOUT = 2
 TRACEROUTE_NULL_HOST_NAME = '*'
 TRACEROUTE_NULL_IP_ADDRESS = '*'
+TRACEROUTE_VRF = ''
 
 OPTICS_NULL_LEVEL = '-Inf'
 
@@ -62,3 +65,4 @@ PING_TTL = 255
 PING_TIMEOUT = 2
 PING_SIZE = 100
 PING_COUNT = 5
+PING_VRF = ''
diff --git a/napalm_base/exceptions.py b/napalm_base/exceptions.py
index 86dbbd6..5a14228 100644
--- a/napalm_base/exceptions.py
+++ b/napalm_base/exceptions.py
@@ -22,6 +22,40 @@ class ModuleImportError(Exception):
 
 
 class ConnectionException(Exception):
+    '''
+    Unable to connect to the network device.
+    '''
+    pass
+
+
+class ConnectAuthError(ConnectionException):
+    '''
+    Unable to connect to the network device
+    due to invalid credentials.
+    '''
+    pass
+
+
+class ConnectTimeoutError(ConnectionException):
+    '''
+    Exception raised when the connection to the
+    network device takes too long.
+    This may be avoided by adjusting the `timeout`
+    argument.
+    '''
+    pass
+
+
+class ConnectionClosedException(ConnectionException):
+    '''
+    The network device closed the connection.
+    Raised whenever we try to execute a certain
+    function, but we detect that the connection
+    is not usable anymore. This can happen for
+    various reasons: the network device terminates the
+    session or it is dropped by a firewall or
+    the server.
+    '''
     pass
 
 
@@ -33,6 +67,28 @@ class MergeConfigException(Exception):
     pass
 
 
+class CommitError(Exception):
+    '''
+    Raised when unable to commit the candidate config
+    into the running config.
+    '''
+    pass
+
+
+class LockError(Exception):
+    '''
+    Unable to lock the candidate config.
+    '''
+    pass
+
+
+class UnlockError(Exception):
+    '''
+    Unable to unlock the candidate config.
+    '''
+    pass
+
+
 class SessionLockedException(Exception):
     pass
 
diff --git a/napalm_base/helpers.py b/napalm_base/helpers.py
index ed7d63f..8df17ab 100644
--- a/napalm_base/helpers.py
+++ b/napalm_base/helpers.py
@@ -37,29 +37,28 @@ _MACFormat.word_fmt = '%.2X'
 def load_template(cls, template_name, template_source=None, template_path=None,
                   openconfig=False, **template_vars):
     try:
+        search_path = []
         if isinstance(template_source, py23_compat.string_types):
             template = jinja2.Template(template_source)
         else:
-            current_dir = os.path.dirname(os.path.abspath(sys.modules[cls.__module__].__file__))
-            if (isinstance(template_path, py23_compat.string_types) and
-                    os.path.isdir(template_path) and os.path.isabs(template_path)):
-                current_dir = os.path.join(template_path, cls.__module__.split('.')[-1])
-                # append driver name at the end of the custom path
+            if template_path is not None:
+                if (isinstance(template_path, py23_compat.string_types) and
+                        os.path.isdir(template_path) and os.path.isabs(template_path)):
+                    # append driver name at the end of the custom path
+                    search_path.append(os.path.join(template_path, cls.__module__.split('.')[-1]))
+                else:
+                    raise IOError("Template path does not exist: {}".format(template_path))
+            else:
+                # Search modules for template paths
+                search_path = [os.path.dirname(os.path.abspath(sys.modules[c.__module__].__file__))
+                               for c in cls.__class__.mro() if c is not object]
 
             if openconfig:
-                template_dir_path = '{current_dir}/oc_templates'.format(current_dir=current_dir)
+                search_path = ['{}/oc_templates'.format(s) for s in search_path]
             else:
-                template_dir_path = '{current_dir}/templates'.format(current_dir=current_dir)
-
-            if not os.path.isdir(template_dir_path):
-                raise napalm_base.exceptions.DriverTemplateNotImplemented(
-                        '''Config template dir does not exist: {path}.
-                        Please create it and add driver-specific templates.'''.format(
-                            path=template_dir_path
-                        )
-                    )
+                search_path = ['{}/templates'.format(s) for s in search_path]
 
-            loader = jinja2.FileSystemLoader(template_dir_path)
+            loader = jinja2.FileSystemLoader(search_path)
             environment = jinja2.Environment(loader=loader)
 
             for filter_name, filter_function in CustomJinjaFilters.filters().items():
@@ -71,9 +70,9 @@ def load_template(cls, template_name, template_source=None, template_path=None,
         configuration = template.render(**template_vars)
     except jinja2.exceptions.TemplateNotFound:
         raise napalm_base.exceptions.TemplateNotImplemented(
-            "Config template {template_name}.j2 is not defined under {path}".format(
+            "Config template {template_name}.j2 not found in search path: {sp}".format(
                 template_name=template_name,
-                path=template_dir_path
+                sp=search_path
             )
         )
     except (jinja2.exceptions.UndefinedError, jinja2.exceptions.TemplateSyntaxError) as jinjaerr:
@@ -177,7 +176,7 @@ def convert(to, who, default=u''):
         return default
     try:
         return to(who)
-    except:
+    except:  # noqa
         return default
 
 
@@ -219,9 +218,10 @@ def mac(raw):
     return py23_compat.text_type(EUI(raw, dialect=_MACFormat))
 
 
-def ip(addr):
+def ip(addr, version=None):
     """
-    Converts a raw string to a valid IP address.
+    Converts a raw string to a valid IP address. Optional version argument will detect that \
+    object matches specified version.
 
     Motivation: the groups of the IP addreses may contain leading zeros. IPv6 addresses can \
     contain sometimes uppercase characters. E.g.: 2001:0dB8:85a3:0000:0000:8A2e:0370:7334 has \
@@ -229,6 +229,8 @@ def ip(addr):
     not the same.
 
     :param raw: the raw string containing the value of the IP Address
+    :param version: (optional) insist on a specific IP address version.
+    :type version: int.
     :return: a string containing the IP Address in a standard format (no leading zeros, \
     zeros-grouping, lowercase)
 
@@ -239,4 +241,17 @@ def ip(addr):
         >>> ip('2001:0dB8:85a3:0000:0000:8A2e:0370:7334')
         u'2001:db8:85a3::8a2e:370:7334'
     """
-    return py23_compat.text_type(IPAddress(addr))
+    addr_obj = IPAddress(addr)
+    if version and addr_obj.version != version:
... 677 lines suppressed ...

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/python-modules/packages/napalm-base.git



More information about the Python-modules-commits mailing list