import re
import sys
from lxml import etree
from lxml.builder import E
from jnpr.junos import jxml as JXML
[docs]class _RpcMetaExec(object):
# -----------------------------------------------------------------------
# CONSTRUCTOR
# -----------------------------------------------------------------------
[docs] def __init__(self, junos):
"""
~PRIVATE CLASS~
creates an RPC meta-executor object bound to the provided
ez-netconf :junos: object
"""
self._junos = junos
# -----------------------------------------------------------------------
# get_config
# -----------------------------------------------------------------------
[docs] def get_config(self, filter_xml=None, options={}, model=None,
namespace=None, remove_ns=True, **kwargs):
"""
retrieve configuration from the Junos device
.. code-block:: python
dev.rpc.get_config()
dev.rpc.get_config(filter_xml='<system><services/></system>')
dev.rpc.get_config(filter_xml='system/services')
dev.rpc.get_config(
filter_xml=etree.XML('<system><services/></system>'),
options={'format': 'json'})
# to fetch junos as well as yang model configs
dev.rpc.get_config(model=True)
# openconfig yang example
dev.rpc.get_config(filter_xml='bgp', model='openconfig')
dev.rpc.get_config(filter_xml='<bgp><neighbors></neighbors></bgp>',
model='openconfig')
# custom yang example
dev.rpc.get_config(filter_xml='l2vpn', model='custom',
namespace="http://yang.juniper.net/customyang/l2vpn")
# ietf yang example
dev.rpc.get_config(filter_xml='interfaces', model='ietf')
# ietf-softwire yang example
dev.rpc.get_config(filter_xml='softwire-config', model='ietf',
namespace="urn:ietf:params:xml:ns:yang:ietf-softwire",
options={'format': 'json'})
:filter_xml: fully XML formatted tag which defines what to retrieve,
when omitted the entire configuration is returned;
the following returns the device host-name configured
with "set system host-name"
.. code-block:: python
config = dev.rpc.get_config(filter_xml=etree.XML('''
<configuration>
<system>
<host-name/>
</system>
</configuration>'''))
:options: is a dictionary of XML attributes to set within the
<get-configuration> RPC; the following returns the device
host-name either configured with "set system host-name"
and if unconfigured, the value inherited from
apply-group re0|re1, typical for multi-RE systems
.. code-block:: python
config = dev.rpc.get_config(filter_xml=etree.XML('''
<configuration>
<system>
<host-name/>
</system>
</configuration>'''),
options={'database':'committed','inherit':'inherit'})
:param str model: Can provide yang model openconfig/custom/ietf. When
model is True and filter_xml is None, xml is enclosed under
<data> so that we get junos as well as other model
configurations
:param str namespace: User can have their own defined namespace in the
custom yang models, In such cases they need to provide that
namespace so that it can be used to fetch yang modeled configs
:param bool remove_ns: remove namespaces, if value assigned is False,
function will return xml with namespaces. The same xml
returned can be loaded back to devices. This comes handy in
case of yang based configs
.. code-block:: python
dev.rpc.get_config(filter_xml='bgp', model='openconfig',
remove_ns=False)
"""
nmspaces = {'openconfig': "http://openconfig.net/yang/",
'ietf': "urn:ietf:params:xml:ns:yang:ietf-"}
rpc = E('get-configuration', options)
if filter_xml is not None:
if not isinstance(filter_xml, etree._Element):
if re.search("^<.*>$", filter_xml):
filter_xml = etree.XML(filter_xml)
else:
filter_data = None
for tag in filter_xml.split('/')[::-1]:
filter_data = E(tag) if filter_data is None else E(
tag,
filter_data)
filter_xml = filter_data
# wrap the provided filter with toplevel <configuration> if
# it does not already have one (not in case of yang model config)
if (filter_xml.tag != 'configuration' and model is None and
namespace is None):
etree.SubElement(rpc, 'configuration').append(filter_xml)
else:
if model is not None or namespace is not None:
if model == 'custom' and namespace is None:
raise AttributeError('For "custom" model, '
'explicitly provide "namespace"')
ns = namespace or (nmspaces.get(model.lower()) +
filter_xml.tag)
filter_xml.attrib['xmlns'] = ns
rpc.append(filter_xml)
transform = self._junos.transform
if remove_ns is False:
self._junos.transform = lambda: JXML.strip_namespaces_prefix
try:
response = self._junos.execute(rpc, **kwargs)
finally:
self._junos.transform = transform
# in case of model provided top level should be data
# return response
if model and filter_xml is None and options.get('format') \
is not 'json':
response = response.getparent()
response.tag = 'data'
return response
# -----------------------------------------------------------------------
# get
# -----------------------------------------------------------------------
[docs] def get(self, filter_select=None, ignore_warning=False, **kwargs):
"""
Retrieve running configuration and device state information using
<get> rpc
.. code-block:: python
dev.rpc.get()
dev.rpc.get(ignore_warning=True)
dev.rpc.get(filter_select='bgp') or dev.rpc.get('bgp')
dev.rpc.get(filter_select='bgp/neighbors')
dev.rpc.get("/bgp/neighbors/neighbor[neighbor-address='10.10.0.1']"
"/timers/state/hold-time")
dev.rpc.get('mpls', ignore_warning=True)
:param str filter_select:
The select attribute will be treated as an XPath expression and
used to filter the returned data.
: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.
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'])
.. 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.
:returns: xml object
"""
# junos only support filter type to be xpath
filter_params = {'type': 'xpath'}
if filter_select is not None:
filter_params['source'] = filter_select
rpc = E('get', E('filter', filter_params))
return self._junos.execute(rpc,
ignore_warning=ignore_warning,
**kwargs)
# -----------------------------------------------------------------------
# load_config
# -----------------------------------------------------------------------
[docs] def load_config(self, contents, ignore_warning=False, **options):
"""
loads :contents: onto the Junos device, does not commit the change.
: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.
For example::
dev.rpc.load_config(cnf, ignore_warning=True)
dev.rpc.load_config(cnf,
ignore_warning='vrrp subsystem not running')
dev.rpc.load_config(cnf,
ignore_warning=['vrrp subsystem not running',
'statement not found'])
dev.rpc.load_config(cnf, ignore_warning='statement not found')
.. 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.
:options: is a dictionary of XML attributes to set within the
<load-configuration> RPC.
The :contents: are interpreted by the :options: as follows:
format='text' and action='set', then :contents: is a string containing
a series of "set" commands
format='text', then :contents: is a string containing Junos
configuration in curly-brace/text format
format='json', then :contents: is a string containing Junos
configuration in json format
url='path', then :contents: is a None
<otherwise> :contents: is XML structure
"""
rpc = E('load-configuration', options)
if contents is None and 'url' in options:
pass
elif ('action' in options) and (options['action'] == 'set'):
rpc.append(E('configuration-set', contents))
elif ('format' in options) and (options['format'] == 'text'):
rpc.append(E('configuration-text', contents))
elif ('format' in options) and (options['format'] == 'json'):
rpc.append(E('configuration-json', contents))
else:
# otherwise, it's just XML Element
if contents.tag != 'configuration':
etree.SubElement(rpc, 'configuration').append(contents)
else:
rpc.append(contents)
return self._junos.execute(rpc, ignore_warning=ignore_warning)
# -----------------------------------------------------------------------
# cli
# -----------------------------------------------------------------------
[docs] def cli(self, command, format='text', normalize=False):
rpc = E('command', command)
if format.lower() in ['text', 'json']:
rpc.attrib['format'] = format
return self._junos.execute(rpc, normalize=normalize)
# -----------------------------------------------------------------------
# method missing
# -----------------------------------------------------------------------
def __getattr__(self, rpc_cmd_name):
"""
metaprograms a function to execute the :rpc_cmd_name:
the caller will be passing (*vargs, **kvargs) on
execution of the meta function; these are the specific
rpc command arguments(**kvargs) and options bound
as XML attributes (*vargs)
"""
rpc_cmd = re.sub('_', '-', rpc_cmd_name)
def _exec_rpc(*vargs, **kvargs):
# create the rpc as XML command
rpc = etree.Element(rpc_cmd)
# Gather decorator keywords into dec_args and remove from kvargs
dec_arg_keywords = ['dev_timeout', 'normalize', 'ignore_warning']
dec_args = {}
for keyword in dec_arg_keywords:
if keyword in kvargs:
dec_args[keyword] = kvargs.pop(keyword)
# kvargs are the command parameter/values
if kvargs:
for arg_name, arg_value in kvargs.items():
arg_name = re.sub('_', '-', arg_name)
if not isinstance(arg_value, (tuple, list)):
arg_value = [arg_value]
for a in arg_value:
if not isinstance(a, (bool, str, unicode)
if sys.version < '3' else (bool, str)):
raise TypeError("The value %s for argument %s"
" is of %s. Argument "
"values must be a string, "
"boolean, or list/tuple of "
"strings and booleans." %
(a, arg_name, str(type(a))))
if a is not False:
arg = etree.SubElement(rpc, arg_name)
if not isinstance(a, bool):
arg.text = a
# vargs[0] is a dict, command options like format='text'
if vargs:
for k, v in vargs[0].items():
if v is not True:
rpc.attrib[k] = v
# now invoke the command against the
# associated :junos: device and return
# the results per :junos:execute()
return self._junos.execute(rpc, **dec_args)
# metabind help() and the function name to the :rpc_cmd_name:
# provided by the caller ... that's about all we can do, yo!
_exec_rpc.__doc__ = rpc_cmd
_exec_rpc.__name__ = rpc_cmd_name
# return the metafunction that the caller will in-turn invoke
return _exec_rpc
# -----------------------------------------------------------------------
# callable
# -----------------------------------------------------------------------
def __call__(self, rpc_cmd, **kvargs):
"""
callable will execute the provided :rpc_cmd: against the
attached :junos: object and return the RPC response per
:junos:execute()
kvargs is simply passed 'as-is' to :junos:execute()
"""
return self._junos.execute(rpc_cmd, **kvargs)