Source code for jnpr.junos.factory.cfgtable

from copy import deepcopy
import re

from lxml import etree
from lxml.builder import E
from jnpr.junos.factory.table import Table
from jnpr.junos import jxml


[docs]class CfgTable(Table): # ----------------------------------------------------------------------- # CONSTRUCTOR # -----------------------------------------------------------------------
[docs] def __init__(self, dev=None, xml=None, path=None): Table.__init__(self, dev, xml, path) # call parent constructor self._data_dict = self.DEFINE # crutch self.ITEM_NAME_XPATH = self._data_dict.get('key', 'name') self.ITEM_XPATH = self._data_dict['get'] self.view = self._data_dict.get('view') self._options = self._data_dict.get('options') # ----------------------------------------------------------------------- # PROPERTIES # -----------------------------------------------------------------------
@property def required_keys(self): """ return a list of the keys required when invoking :get(): and :get_keys(): """ return self._data_dict.get('required_keys') @property def keys_required(self): """ True/False - if this Table requires keys """ return self.required_keys is not None # ----------------------------------------------------------------------- # PRIVATE METHODS # ----------------------------------------------------------------------- def _buildxml(self, namesonly=False): """ Return an lxml Element starting with <configuration> and comprised of the elements specified in the xpath expression. For example, and xpath = 'interfaces/interface' would produce: <configuration> <interfaces> <interface/> </interfaces> </configuration> If :namesonly: is True, then the XML will be encoded only to retrieve the XML name keys at the 'end' of the XPath expression. This return value can then be passed to dev.rpc.get_config() to retrieve the specifc data """ xpath = self._data_dict['get'] self._get_xpath = '//configuration/' + xpath top = E('configuration') dot = top for name in xpath.split('/'): dot.append(E(name)) dot = dot[0] if namesonly is True: dot.attrib['recurse'] = 'false' return top def _grindkey(self, key_xpath, key_value): """ returns list of XML elements for key values """ simple = lambda: [E(key_xpath.replace('_', '-'), key_value)] composite = lambda: [E(xp.replace('_', '-'), xv) for xp, xv in zip(key_xpath, key_value)] return simple() if isinstance(key_xpath, str) else composite() def _grindxpath(self, key_xpath, key_value): """ returns xpath elements for key values """ simple = lambda: "[{0}='{1}']".format(key_xpath.replace('_', '-'), key_value) composite = lambda: "[{0}]".format(' and '.join(["{0}='{1}'".format(xp.replace('_', '-'), xv) for xp, xv in zip(key_xpath, key_value)])) return simple() if isinstance(key_xpath, str) else composite() def _encode_requiredkeys(self, get_cmd, kvargs): """ used to encode the required_keys values into the XML get-command. each of the required_key=<value> pairs are defined in :kvargs: """ rqkeys = self._data_dict['required_keys'] for key_name in self.required_keys: # create an XML element with the key/value key_value = kvargs.get(key_name) if key_value is None: raise ValueError("Missing required-key: '%s'" % key_name) key_xpath = rqkeys[key_name] add_keylist_xml = self._grindkey(key_xpath, key_value) # now link this item into the XML command, where key_name # designates the XML parent element key_name = key_name.replace('_', '-') dot = get_cmd.find('.//' + key_name) if dot is None: raise RuntimeError( "Unable to find parent XML for key: '%s'" % key_name) for _at, _add in enumerate(add_keylist_xml): dot.insert(_at, _add) # Add required key values to _get_xpath xid = re.search(r"\b{0}\b".format(key_name), self._get_xpath).start() + len(key_name) self._get_xpath = self._get_xpath[:xid] + self._grindxpath(key_xpath, key_value) + self._get_xpath[xid:] def _encode_namekey(self, get_cmd, dot, namekey_value): """ encodes the specific namekey_value into the get command so that the returned XML configuration is the complete hierarchy of data. """ namekey_xpath = self._data_dict.get('key', 'name') keylist_xml = self._grindkey(namekey_xpath, namekey_value) for _add in keylist_xml: dot.append(_add) def _encode_getfields(self, get_cmd, dot): for field_xpath in self._data_dict['get_fields']: dot.append(E(field_xpath)) def _keyspec(self): """ returns tuple (keyname-xpath, item-xpath) """ return (self._data_dict.get('key', 'name'), self._data_dict['get']) # ------------------------------------------------------------------------- # get - retrieve Table data # -------------------------------------------------------------------------
[docs] def get(self, *vargs, **kvargs): """ Retrieve configuration data for this table. By default all child keys of the table are loaded. This behavior can be overridden by with kvargs['nameonly']=True :param str vargs[0]: identifies a unique item in the table, same as calling with :kvargs['key']: value :param str namesonly: *OPTIONAL* True/False*, when set to True will cause only the the name-keys to be retrieved. :param str key: *OPTIONAL* identifies a unique item in the table :param dict options: *OPTIONAL* options to pass to get-configuration. By default {'inherit': 'inherit', 'groups': 'groups'} is sent. """ if self._lxml is not None: return self if self._path is not None: # for loading from local file-path self.xml = etree.parse(self._path).getroot() return self if self.keys_required is True and not len(kvargs): raise ValueError( "This table has required-keys\n", self.required_keys) self._clearkeys() # determine if we need to get only the names of keys, or all of the # hierarchical data from the config. The caller can explicitly set # :namesonly: in the call. if 'namesonly' in kvargs: namesonly = kvargs.get('namesonly') else: namesonly = False get_cmd = self._buildxml(namesonly=namesonly) # if this table requires additional keys, for the hierarchical # use-cases then make sure these are provided by the caller. Then # encode them into the 'get-cmd' XML if self.keys_required is True: self._encode_requiredkeys(get_cmd, kvargs) try: # see if the caller provided a named item. this must # be an actual name of a thing, and not an index number. # ... at least for now ... named_item = kvargs.get('key') or vargs[0] dot = get_cmd.find(self._data_dict['get']) self._encode_namekey(get_cmd, dot, named_item) if 'get_fields' in self._data_dict: self._encode_getfields(get_cmd, dot) except: # caller not requesting a specific table item pass # Check for options in get if 'options' in kvargs: options = kvargs.get('options') or {} else: if self._options is not None: options = self._options else: options = jxml.INHERIT_GROUPS # for debug purposes self._get_cmd = get_cmd self._get_opt = options # retrieve the XML configuration # Check to see if running on box if self._dev.ON_JUNOS: try: from junos import Junos_Configuration # If part of commit script use the context if Junos_Configuration is not None: # Convert the onbox XML to ncclient reply config = jxml.conf_transform(deepcopy(jxml.cscript_conf(Junos_Configuration)), subSelectionXPath=self._get_xpath) self.xml = config.getroot() else: self.xml = self.RPC.get_config(get_cmd, options=options) # If onbox import missing fallback to RPC - possibly raise exception in future except ImportError: self.xml = self.RPC.get_config(get_cmd, options=options) else: self.xml = self.RPC.get_config(get_cmd, options=options) # return self for call-chaining, yo! return self