[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