[Python-modules-commits] [junos-eznc] 01/07: New upstream release.

Vincent Bernat bernat at moszumanska.debian.org
Sun Nov 12 18:41:07 UTC 2017


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

bernat pushed a commit to annotated tag debian/2.1.7-1
in repository junos-eznc.

commit 14f3f3821dcbdd61cdb1569583bcc80a2d20bd8c
Author: Vincent Bernat <bernat at debian.org>
Date:   Sun Nov 12 19:14:51 2017 +0100

    New upstream release.
---
 PKG-INFO                                           |   6 +-
 lib/jnpr/junos/__init__.py                         |   7 +-
 lib/jnpr/junos/cfg/phyport/base.py                 |   4 +-
 lib/jnpr/junos/cfg/resource.py                     |  13 +-
 lib/jnpr/junos/console.py                          | 121 +++-
 lib/jnpr/junos/decorators.py                       |  99 ++-
 lib/jnpr/junos/device.py                           | 520 ++++++++++++--
 lib/jnpr/junos/exception.py                        |  58 +-
 lib/jnpr/junos/factcache.py                        | 291 ++++++++
 lib/jnpr/junos/factory/factory_cls.py              |   1 +
 lib/jnpr/junos/factory/factory_loader.py           |   6 +-
 lib/jnpr/junos/factory/table.py                    |  10 +-
 lib/jnpr/junos/factory/to_json.py                  |   3 +-
 lib/jnpr/junos/factory/view.py                     |   4 +-
 lib/jnpr/junos/facts/__example.py                  |  85 +++
 lib/jnpr/junos/facts/__init__.py                   | 114 ++-
 lib/jnpr/junos/facts/chassis.py                    |  52 --
 lib/jnpr/junos/facts/current_re.py                 |  93 +++
 lib/jnpr/junos/facts/domain.py                     |  77 ++-
 lib/jnpr/junos/facts/ethernet_mac_table.py         |  61 ++
 lib/jnpr/junos/facts/file_list.py                  |  24 +
 lib/jnpr/junos/facts/get_chassis_cluster_status.py |  75 ++
 lib/jnpr/junos/facts/get_chassis_inventory.py      |  42 ++
 .../junos/facts/get_route_engine_information.py    | 134 ++++
 lib/jnpr/junos/facts/get_software_information.py   | 210 ++++++
 .../junos/facts/get_virtual_chassis_information.py |  66 ++
 lib/jnpr/junos/facts/ifd_style.py                  |  27 +-
 lib/jnpr/junos/facts/iri_mapping.py                |  63 ++
 lib/jnpr/junos/facts/personality.py                | 132 ++--
 lib/jnpr/junos/facts/swver.py                      | 144 +---
 lib/jnpr/junos/jxml.py                             |  92 ++-
 lib/jnpr/junos/ofacts/__init__.py                  |  24 +
 lib/jnpr/junos/ofacts/chassis.py                   |  51 ++
 lib/jnpr/junos/{facts => ofacts}/domain.py         |   3 +-
 lib/jnpr/junos/{facts => ofacts}/ifd_style.py      |   0
 lib/jnpr/junos/{facts => ofacts}/personality.py    |   1 +
 .../junos/{facts => ofacts}/routing_engines.py     |  14 +-
 lib/jnpr/junos/{facts => ofacts}/session.py        |   1 +
 lib/jnpr/junos/{facts => ofacts}/srx_cluster.py    |   2 +-
 lib/jnpr/junos/{facts => ofacts}/switch_style.py   |   1 +
 lib/jnpr/junos/{facts => ofacts}/swver.py          |  98 +--
 lib/jnpr/junos/op/arp.yml                          |   2 +
 lib/jnpr/junos/op/bgp.py                           |   7 +
 lib/jnpr/junos/op/bgp.yml                          |  15 +
 lib/jnpr/junos/op/intopticdiag.yml                 |   3 +-
 lib/jnpr/junos/op/inventory.py                     |   7 +
 lib/jnpr/junos/op/inventory.yml                    |  17 +
 lib/jnpr/junos/op/isis.yml                         |   3 +
 lib/jnpr/junos/op/ldp.yml                          |   2 +
 lib/jnpr/junos/op/lldp.yml                         |   3 +-
 lib/jnpr/junos/op/ospf.yml                         |  24 +
 lib/jnpr/junos/op/phyport.yml                      |   9 +-
 lib/jnpr/junos/rpcmeta.py                          | 277 ++++++--
 lib/jnpr/junos/transport/tty.py                    |  40 +-
 lib/jnpr/junos/transport/tty_netconf.py            |  15 +-
 lib/jnpr/junos/transport/tty_telnet.py             |   6 +-
 lib/jnpr/junos/utils/config.py                     | 257 +++++--
 lib/jnpr/junos/utils/fs.py                         |  66 +-
 lib/jnpr/junos/utils/ftp.py                        |   9 +-
 lib/jnpr/junos/utils/scp.py                        |  10 +-
 lib/jnpr/junos/utils/start_shell.py                |  27 +-
 lib/jnpr/junos/utils/sw.py                         | 768 ++++++++++++++++-----
 lib/jnpr/junos/version.py                          |  12 +-
 lib/junos_eznc.egg-info/PKG-INFO                   |   6 +-
 lib/junos_eznc.egg-info/SOURCES.txt                |  31 +-
 lib/junos_eznc.egg-info/requires.txt               |   2 +-
 requirements.txt                                   |   2 +-
 setup.cfg                                          |   3 -
 setup.py                                           |   6 +-
 69 files changed, 3590 insertions(+), 868 deletions(-)

diff --git a/PKG-INFO b/PKG-INFO
index f68c966..b49675d 100644
--- a/PKG-INFO
+++ b/PKG-INFO
@@ -1,9 +1,9 @@
 Metadata-Version: 1.1
 Name: junos-eznc
-Version: 2.0.1
+Version: 2.1.7
 Summary: Junos 'EZ' automation for non-programmers
 Home-page: http://www.github.com/Juniper/py-junos-eznc
-Author: Jeremy Schulman, Nitin Kumar
+Author: Jeremy Schulman, Nitin Kumar, Rick Sherman, Stacy Smith
 Author-email: jnpr-community-netdev at juniper.net
 License: Apache 2.0
 Description: UNKNOWN
@@ -21,6 +21,8 @@ Classifier: Programming Language :: Python
 Classifier: Programming Language :: Python :: 2.6
 Classifier: Programming Language :: Python :: 2.7
 Classifier: Programming Language :: Python :: 3.4
+Classifier: Programming Language :: Python :: 3.5
+Classifier: Programming Language :: Python :: 3.6
 Classifier: Topic :: Software Development :: Libraries
 Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
 Classifier: Topic :: Software Development :: Libraries :: Python Modules
diff --git a/lib/jnpr/junos/__init__.py b/lib/jnpr/junos/__init__.py
index 40b27c8..e95e876 100644
--- a/lib/jnpr/junos/__init__.py
+++ b/lib/jnpr/junos/__init__.py
@@ -1,7 +1,8 @@
 from jnpr.junos.device import Device
 from jnpr.junos.console import Console
 from jnpr.junos.factory.to_json import PyEzJSONEncoder
-from jnpr.junos.facts.swver import version_info, version_yaml_representer
+from jnpr.junos.facts.swver import version_info
+from jnpr.junos.facts.swver import version_yaml_representer
 from . import jxml
 from . import jxml as JXML
 from . import version
@@ -25,8 +26,8 @@ if sys.version_info[:2] == (2, 6):
 __version__ = version.VERSION
 __date__ = version.DATE
 
-#import time
-#__date__ = time.strftime("%Y-%b-%d")
+# import time
+# __date__ = time.strftime("%Y-%b-%d")
 
 # Set default JSON encoder
 json._default_encoder = PyEzJSONEncoder()
diff --git a/lib/jnpr/junos/cfg/phyport/base.py b/lib/jnpr/junos/cfg/phyport/base.py
index 8dc52db..bc8bbb2 100644
--- a/lib/jnpr/junos/cfg/phyport/base.py
+++ b/lib/jnpr/junos/cfg/phyport/base.py
@@ -66,7 +66,7 @@ class PhyPortBase(Resource):
         xml.append(
             Resource.xmltag_set_or_del(
                 'disable',
-                (self.admin == False)))
+                (self.admin is False)))
         return True
 
     def _xml_change_mtu(self, xml):
@@ -80,6 +80,6 @@ class PhyPortBase(Resource):
     def _r_list(self):
         got = self.R.get_interface_information(
             media=True,
-            interface_name="[xgf]e*")
+            interface_name="[efgx][et]-*")
         self._rlist = [
             name.text.strip() for name in got.xpath('physical-interface/name')]
diff --git a/lib/jnpr/junos/cfg/resource.py b/lib/jnpr/junos/cfg/resource.py
index 6c89330..d540eac 100644
--- a/lib/jnpr/junos/cfg/resource.py
+++ b/lib/jnpr/junos/cfg/resource.py
@@ -1,5 +1,4 @@
 # stdlib
-import warnings
 from pprint import pformat
 from copy import deepcopy
 
@@ -234,7 +233,7 @@ class Resource(object):
         self._r_has_init()
         self._has_xml = self._r_config_read_xml()
 
-        if None == self._has_xml or not len(self._has_xml):
+        if self._has_xml is None or not len(self._has_xml):
             self._is_new = True
             self._r_when_new()
             return None
@@ -277,11 +276,11 @@ class Resource(object):
 
         # construct the XML change structure
         xml_change = self._xml_build_change()
-        if None == xml_change:
+        if xml_change is None:
             return False
 
         # write these changes to the device
-        rsp = self._r_config_write_xml(xml_change)
+        self._r_config_write_xml(xml_change)
 
         # copy :should: into :has: and then clear :should:
         self.has.update(self.should)
@@ -338,7 +337,7 @@ class Resource(object):
         xml = self._xml_edit_at_res()
         xml.attrib.update(JXML.DEL)
         self._xml_hook_on_delete(xml)
-        rsp = self._r_config_write_xml(xml)
+        self._r_config_write_xml(xml)
 
         # reset the :has: attribute
         self._r_has_init()
@@ -363,7 +362,7 @@ class Resource(object):
         xml.attrib.update(JXML.REN)
         xml.attrib.update(JXML.NAME(new_name))
 
-        rsp = self._r_config_write_xml(xml)
+        self._r_config_write_xml(xml)
         self._name = new_name
 
         return True
@@ -389,7 +388,7 @@ class Resource(object):
         xml.attrib.update(JXML.INSERT(cmd))
         xml.attrib.update(JXML.NAME(name))
 
-        rsp = self._r_config_write_xml(xml)
+        self._r_config_write_xml(xml)
         return True
 
     def list_refresh(self):
diff --git a/lib/jnpr/junos/console.py b/lib/jnpr/junos/console.py
index bacb5f1..97838c2 100644
--- a/lib/jnpr/junos/console.py
+++ b/lib/jnpr/junos/console.py
@@ -6,28 +6,23 @@ import traceback
 import sys
 import logging
 import warnings
+import socket
 
 # 3rd-party packages
-import ncclient.transport.errors as NcErrors
-import ncclient.operations.errors as NcOpErrors
 from ncclient.devices.junos import JunosDeviceHandler
 from lxml import etree
 from jnpr.junos.transport.tty_telnet import Telnet
 from jnpr.junos.transport.tty_serial import Serial
-from ncclient.operations.rpc import RPCReply, RPCError
 from ncclient.xml_ import NCElement
 from jnpr.junos.device import _Connection
 
 # local modules
 from jnpr.junos.rpcmeta import _RpcMetaExec
-from jnpr.junos import exception as EzErrors
-from jnpr.junos.facts import *
+from jnpr.junos.factcache import _FactCache
 from jnpr.junos import jxml as JXML
-from jnpr.junos.decorators import timeoutDecorator, normalizeDecorator
-
-QFX_MODEL_LIST = ['QFX3500', 'QFX3600', 'VIRTUAL CHASSIS']
-QFX_MODE_NODE = 'NODE'
-QFX_MODE_SWITCH = 'SWITCH'
+from jnpr.junos.ofacts import *
+from jnpr.junos.decorators import ignoreWarnDecorator
+from jnpr.junos.device import _Jinja2ldr
 
 logger = logging.getLogger("jnpr.junos.console")
 
@@ -72,8 +67,22 @@ class Console(_Connection):
             So its assumed ssh is enabled by the time we use SCP functionality.
 
         :param bool gather_facts:
-            *OPTIONAL* default is ``False``.  If ``False`` then the
-            facts are not gathered on call to :meth:`open`
+            *OPTIONAL* Defaults to ``False``. If ``False`` and old-style fact
+            gathering is in use then facts are not gathered on call to
+            :meth:`open`. This argument is a no-op when new-style fact
+            gathering is in use (the default.)
+
+        :param str fact_style:
+            *OPTIONAL*  The style of fact gathering to use. Valid values are:
+            'new', 'old', or 'both'. The default is 'new'. The value 'both' is
+            only present for debugging purposes. It will be removed in a future
+            release. The value 'old' is only present to workaround bugs in
+            new-style fact gathering. It will be removed in a future release.
+
+        :param bool console_has_banner:
+            *OPTIONAL* default is ``False``.  If ``False`` then in case of a
+            hung state, <close-session/> rpc is sent to the console.
+            If ``True``, after sleep(5), a new-line is sent
 
         """
 
@@ -82,7 +91,7 @@ class Console(_Connection):
         # ----------------------------------------
 
         self._tty = None
-        self._facts = {}
+        self._ofacts = {}
         self.connected = False
         self._skip_logout = False
         self.results = dict(changed=False, failed=False, errmsg=None)
@@ -100,18 +109,25 @@ class Console(_Connection):
         self._mode = kvargs.get('mode', 'telnet')
         self._timeout = kvargs.get('timeout', '0.5')
         self._normalize = kvargs.get('normalize', False)
-        self._norm_transform = lambda: JXML.normalize_xslt.encode('UTF-8')
-        self.transform = self._norm_transform
-        # self.timeout needed by PyEZ utils
-        #self.timeout = self._timeout
         self._attempts = kvargs.get('attempts', 10)
-        self.gather_facts = kvargs.get('gather_facts', False)
+        self._gather_facts = kvargs.get('gather_facts', False)
+        self._fact_style = kvargs.get('fact_style', 'new')
+        if self._fact_style != 'new':
+            warnings.warn('fact-style %s will be removed in '
+                          'a future release.' %
+                          (self._fact_style), RuntimeWarning)
+        self.console_has_banner = kvargs.get('console_has_banner', False)
         self.rpc = _RpcMetaExec(self)
         self._ssh_config = kvargs.get('ssh_config')
         self._manages = []
-        self.junos_dev_handler = JunosDeviceHandler(device_params=
-                                                    {'name': 'junos',
-                                                     'local': False})
+        self.junos_dev_handler = JunosDeviceHandler(
+                                     device_params={'name': 'junos',
+                                                    'local': False})
+        self._j2ldr = _Jinja2ldr
+        if self._fact_style == 'old':
+            self.facts = self.ofacts
+        else:
+            self.facts = _FactCache(self)
 
     @property
     def timeout(self):
@@ -130,9 +146,31 @@ class Console(_Connection):
         """
         self._timeout = value
 
-    def open(self):
+    @property
+    def transform(self):
         """
-        open the connection to the device
+        :returns: the current RPC XML Transformation.
+        """
+        return self.junos_dev_handler.transform_reply
+
+    @transform.setter
+    def transform(self, func):
+        """
+        Used to change the RPC XML Transformation.
+
+        :param lambda value:
+            New transform lambda
+        """
+        self.junos_dev_handler.transform_reply = func
+
+    def open(self, *vargs, **kvargs):
+        """
+        Opens a connection to the device using existing login/auth
+        information.
+
+        :param bool gather_facts:
+            If set to ``True``/``False`` will override the device
+            instance value for only this open process
         """
 
         # ---------------------------------------------------------------
@@ -156,13 +194,26 @@ class Console(_Connection):
                     traceback.format_exc()))
             raise err
         except Exception as ex:
-            logger.error("Exception occurred: {0}:{1}\n".format('login', str(ex)))
+            logger.error("Exception occurred: {0}:{1}\n".format('login',
+                                                                str(ex)))
             raise ex
         self.connected = True
-        if self.gather_facts is True:
+
+        self._nc_transform = self.transform
+        self._norm_transform = lambda: JXML.normalize_xslt.encode('UTF-8')
+
+        # normalize argument to open() overrides normalize argument value
+        # to __init__(). Save value to self._normalize where it is used by
+        # normalizeDecorator()
+        self._normalize = kvargs.get('normalize', self._normalize)
+        if self._normalize is True:
+            self.transform = self._norm_transform
+
+        gather_facts = kvargs.get('gather_facts', self._gather_facts)
+        if gather_facts is True:
             logger.info('facts: retrieving device facts...')
             self.facts_refresh()
-            self.results['facts'] = self._facts
+            self.results['facts'] = self.facts
         return self
 
     def close(self, skip_logout=False):
@@ -172,6 +223,14 @@ class Console(_Connection):
         if skip_logout is False and self.connected is True:
             try:
                 self._tty_logout()
+            except socket.error as err:
+                # if err contains "Connection reset by peer" connection to the
+                # device got closed
+                if "Connection reset by peer" not in str(err):
+                    raise err
+            except EOFError as err:
+                if "telnet connection closed" not in str(err):
+                    raise err
             except Exception as err:
                 logger.error("ERROR {0}:{1}\n".format('logout', str(err)))
                 raise err
@@ -187,12 +246,15 @@ class Console(_Connection):
                 raise err
             self.connected = False
 
+    @ignoreWarnDecorator
     def _rpc_reply(self, rpc_cmd_e):
         encode = None if sys.version < '3' else 'unicode'
-        rpc_cmd = etree.tostring(rpc_cmd_e, encoding=encode) if \
-                isinstance(rpc_cmd_e, etree._Element) else rpc_cmd_e
+        rpc_cmd = etree.tostring(rpc_cmd_e, encoding=encode) \
+            if isinstance(rpc_cmd_e, etree._Element) else rpc_cmd_e
         reply = self._tty.nc.rpc(rpc_cmd)
-        rpc_rsp_e = NCElement(reply, self.junos_dev_handler.transform_reply())._NCElement__doc
+        rpc_rsp_e = NCElement(reply,
+                              self.junos_dev_handler.transform_reply()
+                              )._NCElement__doc
         return rpc_rsp_e
 
     # -------------------------------------------------------------------------
@@ -209,6 +271,7 @@ class Console(_Connection):
         if self._mode.upper() == 'TELNET':
             tty_args['host'] = self._hostname
             tty_args['port'] = self._port
+            tty_args['console_has_banner'] = self.console_has_banner
             self.console = ('telnet', self._hostname, self.port)
             self._tty = Telnet(**tty_args)
         elif self._mode.upper() == 'SERIAL':
diff --git a/lib/jnpr/junos/decorators.py b/lib/jnpr/junos/decorators.py
index 70ee098..9a7c9e6 100644
--- a/lib/jnpr/junos/decorators.py
+++ b/lib/jnpr/junos/decorators.py
@@ -1,7 +1,12 @@
 # stdlib
 from functools import wraps
+import re
+import sys
 
-from jnpr.junos.jxml import normalize_xslt
+from lxml import etree
+from ncclient.operations.rpc import RPCError
+from ncclient.xml_ import NCElement
+from jnpr.junos import jxml as JXML
 
 
 def timeoutDecorator(function):
@@ -73,3 +78,95 @@ def normalizeDecorator(function):
                 raise
 
     return wrapper
+
+
+def ignoreWarnDecorator(function):
+    """
+    Ignore warnings if all <rpc-error> elements are at severity 'warning' and
+    match one of the values of the ignore_warning argument.
+
+    For example::
+        dev.rpc.get(ignore_warning=True)
+        dev.rpc.get(ignore_warning='vrrp subsystem not running')
+        dev.rpc.get(ignore_warning=['vrrp subsystem not running',
+                                    'statement not found'])
+        cu.load(cnf, ignore_warning='statement not found')
+
+    :ignore_warning: A boolean, string or list of string.
+        If the value is True, it will ignore all warnings regarldess of the
+        warning message. If the value is a string, it will ignore warning(s) if
+        the message of each warning matches the string. If the value is a list
+        of strings, ignore warning(s) if the message of each warning matches at
+        least one of the strings in the list.
+
+    .. note::
+            When the value of ignore_warning is a string, or list of strings,
+            the string is actually used as a case-insensitive regular
+            expression pattern. If the string contains only alpha-numeric
+            characters, as shown in the above examples, this results in a
+            case-insensitive substring match. However, any regular expression
+            pattern supported by the re library may be used for more
+            complicated match conditions.
+    """
+    @wraps(function)
+    def wrapper(self, *args, **kwargs):
+        ignore_warning = kwargs.pop('ignore_warning', False)
+        rsp = None
+        try:
+            rsp = function(self, *args, **kwargs)
+        except RPCError as ex:
+            if hasattr(ex, 'xml') and ignore_warning:
+                if hasattr(ex, 'errors'):
+                    errors = ex.errors
+                else:
+                    errors = [ex]
+                for err in errors:
+                    if err.severity == 'warning':
+                        if ((sys.version < '3' and
+                           isinstance(ignore_warning,
+                                      (str, unicode))) or
+                           (sys.version >= '3' and
+                           isinstance(ignore_warning, str))):
+                            if not re.search(ignore_warning,
+                                             err.message,
+                                             re.I):
+                                # Message did not match.
+                                raise ex
+                        elif isinstance(ignore_warning, list):
+                            for warn_msg in ignore_warning:
+                                if re.search(warn_msg,
+                                             err.message,
+                                             re.I):
+                                    # Warning matches.
+                                    # Break skips else.
+                                    break
+                            else:
+                                # Message didn't match any of the
+                                # ignore_warn pattern values.
+                                raise ex
+                    else:
+                        # Not a warning (probably an error).
+                        raise ex
+                # Every err was a warning that matched ignore_warning.
+                # Prepare the response which will get returned.
+                # ex.xml contains the raw xml response which was
+                # received, but might be rooted at an <rpc-error> element.
+                # Set rsp to the root <rpc-reply> element.
+                rsp = ex.xml.getroottree().getroot()
+                # 1) A normal response has been run through the XSLT
+                #    transformation, but ex.xml has not. Do that now.
+                encode = None if sys.version < '3' else 'unicode'
+                rsp = NCElement(etree.tostring(rsp, encoding=encode),
+                                self.transform())._NCElement__doc
+                # 2) Now remove all of the <rpc-error> elements from
+                #    the response. We've already confirmed they are
+                #    all warnings
+                rsp = etree.fromstring(
+                          str(JXML.strip_rpc_error_transform(rsp)))
+            else:
+                # ignore_warning was false, or an RPCError which doesn't have
+                #  an XML attribute. Raise it up for the caller to deal with.
+                raise ex
+        return rsp
+
+    return wrapper
diff --git a/lib/jnpr/junos/device.py b/lib/jnpr/junos/device.py
index b89d630..09ac26e 100644
--- a/lib/jnpr/junos/device.py
+++ b/lib/jnpr/junos/device.py
@@ -1,9 +1,9 @@
 # stdlib
 import os
+import six
 import types
 import platform
 import warnings
-import traceback
 
 # stdlib, in support of the the 'probe' method
 import socket
@@ -17,6 +17,7 @@ import re
 from lxml import etree
 from ncclient import manager as netconf_ssh
 import ncclient.transport.errors as NcErrors
+from ncclient.transport.session import SessionListener
 import ncclient.operations.errors as NcOpErrors
 from ncclient.operations import RPCError
 import paramiko
@@ -25,9 +26,12 @@ import jinja2
 # local modules
 from jnpr.junos.rpcmeta import _RpcMetaExec
 from jnpr.junos import exception as EzErrors
-from jnpr.junos.facts import *
+from jnpr.junos.factcache import _FactCache
+from jnpr.junos.ofacts import *
 from jnpr.junos import jxml as JXML
-from jnpr.junos.decorators import timeoutDecorator, normalizeDecorator
+from jnpr.junos.decorators import timeoutDecorator, normalizeDecorator, \
+    ignoreWarnDecorator
+from jnpr.junos.exception import JSONLoadError
 
 
 _MODULEPATH = os.path.dirname(__file__)
@@ -57,9 +61,9 @@ class _MyTemplateLoader(jinja2.BaseLoader):
         with open(path) as f:
             # You are trying to decode an object that is already decoded.
             # You have a str, there is no need to decode from UTF-8 anymore.
-            # open already decodes to Unicode in Python 3 if you open in text mode.
-            # If you want to open it as bytes, so that you can then decode,
-            # you need to open with mode 'rb'.
+            # open already decodes to Unicode in Python 3 if you open in text
+            # mode. If you want to open it as bytes, so that you can then
+            # decode, you need to open with mode 'rb'.
             source = f.read()
         return source, path, lambda: mtime == os.path.getmtime(path)
 
@@ -136,7 +140,7 @@ class _Connection(object):
             When **value** is not a ``file`` object
         """
         # got an existing file that we need to close
-        if (not value) and (None != self._logfile):
+        if (not value) and (self._logfile is not None):
             rc = self._logfile.close()
             self._logfile = False
             return rc
@@ -171,21 +175,29 @@ class _Connection(object):
         :param int value:
             New timeout value in seconds
         """
-        self._conn.timeout = value
+        try:
+            self._conn.timeout = int(value)
+        except (ValueError, TypeError):
+            raise RuntimeError("could not convert timeout value of %s to an "
+                               "integer" % (value))
 
     # ------------------------------------------------------------------------
     # property: facts
     # ------------------------------------------------------------------------
 
     @property
-    def facts(self):
+    def ofacts(self):
         """
         :returns: Device fact dictionary
         """
-        return self._facts
+        if self._fact_style != 'old' and self._fact_style != 'both':
+            raise RuntimeError("Old-style facts gathering is not in use!")
+        if self._ofacts == {} and self.connected:
+            self.facts_refresh()
+        return self._ofacts
 
-    @facts.setter
-    def facts(self, value):
+    @ofacts.setter
+    def ofacts(self, value):
         """ read-only property """
         raise RuntimeError("facts is read-only!")
 
@@ -200,6 +212,180 @@ class _Connection(object):
         """
         return self._port
 
+    # ------------------------------------------------------------------------
+    # property: master
+    # ------------------------------------------------------------------------
+
+    @property
+    def master(self):
+        """
+        The mastership state of the current Routing Engine.
+
+        The current Routing Engine is the RE to which the NETCONF session is
+        connected.
+
+        .. note::
+            This property is based on new-style fact gathering and the
+            value of currently cached facts. If there is a chance the
+            mastership state may have changed since the facts were cached,
+            then dev.facts_refresh() should be invoked prior to checking
+            this property. If old-style fact gathering is in use,
+            this property will return None.
+
+        :returns: True if the current RE is the master Routing Engine. False if
+                  the current RE is not the master Routing Engine. None if
+                  unable to determine the state of the current Routing Engine.
+        """
+        master = None
+
+        # Make sure the 'current_re' fact has a value
+        if self.facts.get('current_re') is not None:
+            # Typical master case
+            if 'master' in self.facts['current_re']:
+                master = True
+            # Typical backup case
+            elif 'backup' in self.facts['current_re']:
+                master = False
+            # Some single chassis and single RE platforms don't have
+            # 'master' in the 'current_re' fact. It's best to check if it's a
+            #  single chassis and single RE platform based on the
+            # 'RE_hw_mi' and '2RE' facts, not the 'current_re' fact.
+            elif (self.facts.get('2RE') is False and
+                  self.facts.get('RE_hw_mi') is False and
+                  're0' in self.facts['current_re']):
+                master = True
+            # Is it an SRX cluster?
+            # If so, the cluster's "primary" is the "master"
+            elif self.facts.get('srx_cluster') is True:
+                if 'primary' in self.facts['current_re']:
+                    master = True
+                else:
+                    master = False
+            else:
+                # Might be a GNF case.
+                if (self.re_name is not None and
+                        'gnf' in self.re_name and
+                        '-re' in self.re_name):
+                    # Get the name of the GNF from re_name/
+                    # re_name will be in the format gnfX-reY
+                    (gnf, _) = self.re_name.split('-re', 1)
+                    if gnf + '-master' in self.facts.get('current_re'):
+                        master = True
+                    elif gnf + '-backup' in self.facts.get('current_re'):
+                        master = False
+                else:
+                    # Might be a multi-chassis case where this RE is neither
+                    # the master or the backup for the entire system. In that
+                    # case, it's either a chassis master or a chassis backup.
+                    for re_state in self.facts['current_re']:
+                        # Multi-chassis case. A chassis master/backup, but
+                        # not the system master/backup.
+                        if '-backup' in re_state or '-master' in re_state:
+                            master = False
+                            break
+        return master
+
+    @master.setter
+    def master(self, value):
+        """ read-only property """
+        raise RuntimeError("master is read-only!")
+
+    # ------------------------------------------------------------------------
+    # property: uptime
+    # ------------------------------------------------------------------------
+
+    @property
+    def uptime(self):
+        """
+        The uptime of the current Routing Engine.
+
+        The current Routing Engine is the RE to which the NETCONF session is
+        connected.
+
+        :returns: The number of seconds (int) since the current Routing Engine
+                  was booted. If there is a problem gathering or parsing the
+                  uptime information, None is returned.
+        :raises: May raise a specific jnpr.junos.RpcError or
+                 jnpr.junos.ConnectError subclass if there is a problem
+                 communicating with the device.
+        """
+        uptime = None
+        rsp = self.rpc.get_system_uptime_information(normalize=True)
+        if rsp is not None:
+            element = rsp.find('.//system-booted-time/time-length')
+            if element is not None:
+                uptime_string = element.get('seconds')
+                if uptime_string is not None:
+                    uptime = int(uptime_string)
+        return uptime
+
+    @uptime.setter
+    def uptime(self, value):
+        """ read-only property """
+        raise RuntimeError("uptime is read-only!")
+
+    # ------------------------------------------------------------------------
+    # property: re_name
+    # ------------------------------------------------------------------------
+
+    @property
+    def re_name(self):
+        """
+        The name of the current Routing Engine.
+
+        The current Routing Engine is the RE to which the NETCONF session is
+        connected.
+
+        .. note::
+            This property is based on new-style fact gathering. If
+            old-style fact gathering is in use, this property will return None.
+
+        :returns: A string containing the name of the current Routing Engine or
+                  None if unable to determine the state of the current
+                  Routing Engine.
+        """
+        re_name = None
+
+        # Make sure the 'current_re' and 'hostname_info' facts have values
+        if (self.facts.get('current_re') is not None and
+           self.facts.get('hostname_info') is not None):
+            # re_name should be the intersection of the values in the
+            # 'current_re' fact and the keys in the 'hostname_info' fact.
+            intersect = (set(self.facts['current_re']) &
+                         set(self.facts['hostname_info'].keys()))
+            # intersect should usually contain a single element (the RE's
+            # name) if things worked correctly.
+            if len(intersect) == 1:
+                re_name = list(intersect)[0]
+            # If intersect contains no elements
+            elif len(intersect) == 0:
+                # Look for the first value
+                # in 'current_re' which contains '-re'.
+                for re_state in self.facts['current_re']:
+                    if '-re' in re_state:
+                        re_name = re_state
+                        break
+                if re_name is None:
+                    # Still haven't figured it out, if there's only one key
+                    # in 'hostname_info', assume that.
+                    all_re_names = list(self.facts['hostname_info'].keys())
+                    if len(all_re_names) == 1:
+                        re_name = all_re_names[0]
+                if re_name is None:
+                    # Still haven't figured it out. Is this a bsys?
+                    for re_state in self.facts['current_re']:
+                        match = re.search('^re\d+$', re_state)
+                        if match:
+                            re_string = 'bsys-' + match.group(0)
+                            if re_string in self.facts['hostname_info'].keys():
+                                re_name = re_string
+        return re_name
+
+    @re_name.setter
+    def re_name(self, value):
+        """ read-only property """
+        raise RuntimeError("re_name is read-only!")
+
     def _sshconf_lkup(self):
         if self._ssh_config:
             sshconf_path = os.path.expanduser(self._ssh_config)
@@ -239,7 +425,8 @@ class _Connection(object):
         """
         try:
             command = command + '| display xml rpc'
-            rsp = self.rpc.cli(command)
+            rsp = self.rpc.cli(command, format="xml")
+            rsp = rsp.getparent().find('.//rpc')
             if format == 'text':
                 encode = None if sys.version < '3' else 'unicode'
                 return etree.tostring(rsp[0], encoding=encode)
@@ -385,11 +572,53 @@ class _Connection(object):
                 time.sleep(1)
                 pass
         else:
-            elapsed = datetime.datetime.now() - start
             probe_ok = False
 
         return probe_ok
 
+    def cli_to_rpc_string(self, command):
+        """
+        Translate a CLI command string into the equivalent RPC method call.
+
+        Translates a CLI command string into a string which represents the
+        equivalent line of code using an RPC instead of a CLI command. Handles
+        RPCs with arguments.
+
+        .. note::
+            This method does NOT actually invoke the RPC equivalent.
+
+        :param str command:
+          The CLI command to translate, e.g. "show version"
+
+        :returns: (str) representing the RPC meta-method (including
+                  attributes and arguments) which could be invoked instead of
+                  cli(command). Returns None if there is no equivalent RPC for
+                  command or if command is not a valid CLI command.
+        """
+
+        # Strip off any pipe modifiers
+        (command, _, _) = command.partition('|')
+        # Strip any leading or trailing whitespace
+        command = command.strip()
+        # Get the equivalent RPC
+        rpc = self.display_xml_rpc(command)
+        if isinstance(rpc, six.string_types):
+            # No RPC is available.
+            return None
+        rpc_string = "rpc.%s(" % (rpc.tag.replace('-', '_'))
+        arguments = []
+        for child in rpc:
+            key = child.tag.replace('-', '_')
+            if child.text:
+                value = "'" + child.text + "'"
+            else:
+                value = "True"
+            arguments.append("%s=%s" % (key, value))
+        if arguments:
+            rpc_string += ', '.join(arguments)
+        rpc_string += ")"
+        return rpc_string
+
     # ------------------------------------------------------------------------
     # cli - for cheating commands :-)
     # ------------------------------------------------------------------------
@@ -408,16 +637,18 @@ class _Connection(object):
         .. note::
             You can also use this method to obtain the XML RPC command for a
             given CLI command by using the pipe filter ``| display xml rpc``.
-            When you do this, the return value is the XML RPC command. For 
-            example if you provide as the command ``show version | display xml rpc``,
-            you will get back the XML Element ``<get-software-information>``.
+            When you do this, the return value is the XML RPC command. For
+            example if you provide as the command
+            ``show version | display xml rpc``, you will get back the XML
+            Element ``<get-software-information>``.
 
         .. warning::
             This function is provided for **DEBUG** purposes only!
             **DO NOT** use this method for general automation purposes as
-            that puts you in the realm of "screen-scraping the CLI".  The purpose of
-            the PyEZ framework is to migrate away from that tooling pattern.
-            Interaction with the device should be done via the RPC function.
+            that puts you in the realm of "screen-scraping the CLI".
+            The purpose of the PyEZ framework is to migrate away from that
+            tooling pattern. Interaction with the device should be done via
+            the RPC function.
 
         .. warning::
             You cannot use "pipe" filters with **command** such as ``| match``
@@ -425,12 +656,18 @@ class _Connection(object):
             ``| display xml rpc`` as noted above.
         """
         if 'display xml rpc' not in command and warning is True:
-            warnings.simplefilter("always")
-            warnings.warn("CLI command is for debug use only!", RuntimeWarning)
-            warnings.resetwarnings()
+            # Get the equivalent rpc metamethod
+            rpc_string = self.cli_to_rpc_string(command)
+            if rpc_string is not None:
+                warning_string = "\nCLI command is for debug use only!\n"
+                warning_string += "Instead of:\ncli('%s')\n" % (command)
+                warning_string += "Use:\n%s\n" % (rpc_string)
+                warnings.simplefilter("always")
+                warnings.warn(warning_string, RuntimeWarning)
+                warnings.resetwarnings()
 
         try:
-            rsp = self.rpc.cli(command, format)
+            rsp = self.rpc.cli(command=command, format=format)
             if isinstance(rsp, dict) and format.lower() == 'json':
                 return rsp
             # rsp returned True means <rpc-reply> is empty, hence return
@@ -449,11 +686,10 @@ class _Connection(object):
             if rsp.tag == 'rpc':
                 return rsp[0]
             return rsp
+        except EzErrors.ConnectClosedError as ex:
+            raise ex
         except EzErrors.RpcError as ex:
-            if str(ex) is not '':
-                return "%s: %s" % (str(ex), command)
-            else:
-                return "invalid command: " + command
+            return "invalid command: %s: %s" % (command, ex)
         except Exception as ex:
             return "invalid command: " + command
 
@@ -463,7 +699,7 @@ class _Connection(object):
 
     @normalizeDecorator
     @timeoutDecorator
-    def execute(self, rpc_cmd, **kvargs):
+    def execute(self, rpc_cmd, ignore_warning=False, **kvargs):
         """
         Executes an XML RPC and returns results as either XML or native python
 
@@ -472,6 +708,22 @@ class _Connection(object):
           the command starts with the specific command element, i.e., not the
           <rpc> element itself
 
+        :param ignore_warning: A boolean, string or list of string.
+          If the value is True, it will ignore all warnings regardless of the
+          warning message. If the value is a string, it will ignore
+          warning(s) if the message of each warning matches the string. If
+          the value is a list of strings, ignore warning(s) if the message of
+          each warning matches at least one of the strings in the list.
+
+          .. note::
+            When the value of ignore_warning is a string, or list of strings,
+            the string is actually used as a case-insensitive regular
+            expression pattern. If the string contains only alpha-numeric
+            characters, as shown in the above examples, this results in a
+            case-insensitive substring match. However, any regular expression
+            pattern supported by the re library may be used for more
+            complicated match conditions.
+
         :param func to_py:
           Is a caller provided function that takes the response and
           will convert the results to native python types.  all kvargs
@@ -487,7 +739,8 @@ class _Connection(object):
             user-auth class privilege controls on Junos
 
         :raises RpcError:
-            When an ``rpc-error`` element is contained in the RPC-reply
+            When an ``rpc-error`` element is contained in the RPC-reply and the
+            ``rpc-error`` element does not match the **ignore_warning** value.
 
         :returns:
             RPC-reply as XML object.  If **to_py** is provided, then
@@ -513,20 +766,28 @@ class _Connection(object):
         # @@@ need to trap this and re-raise accordingly.
 
         try:
-            rpc_rsp_e = self._rpc_reply(rpc_cmd_e)
+            rpc_rsp_e = self._rpc_reply(rpc_cmd_e,
+                                        ignore_warning=ignore_warning)
         except NcOpErrors.TimeoutExpiredError:
             # err is a TimeoutExpiredError from ncclient,
             # which has no such attribute as xml.
             raise EzErrors.RpcTimeoutError(self, rpc_cmd_e.tag, self.timeout)
         except NcErrors.TransportError:
             raise EzErrors.ConnectClosedError(self)
-        except RPCError as err:
-            rsp = JXML.remove_namespaces(err.xml)
-            # see if this is a permission error
-            e = EzErrors.PermissionError if rsp.findtext('error-message') == \
-                'permission denied' \
-                else EzErrors.RpcError
-            raise e(cmd=rpc_cmd_e, rsp=rsp, errs=err)
... 5556 lines suppressed ...

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



More information about the Python-modules-commits mailing list