junos-eznc-2.1.7/0000755001013500016170000000000013163777613013240 5ustar stacys00000000000000junos-eznc-2.1.7/lib/0000755001013500016170000000000013163777613014006 5ustar stacys00000000000000junos-eznc-2.1.7/lib/jnpr/0000755001013500016170000000000013163777613014757 5ustar stacys00000000000000junos-eznc-2.1.7/lib/jnpr/__init__.py0000644001013500016170000000007013163777563017071 0ustar stacys00000000000000__import__('pkg_resources').declare_namespace(__name__) junos-eznc-2.1.7/lib/jnpr/junos/0000755001013500016170000000000013163777613016115 5ustar stacys00000000000000junos-eznc-2.1.7/lib/jnpr/junos/__init__.py0000644001013500016170000000265313163777563020240 0ustar stacys00000000000000from 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 from jnpr.junos.facts.swver import version_yaml_representer from . import jxml from . import jxml as JXML from . import version from . import exception import json import yaml import logging import sys import warnings if sys.version_info[:2] == (2, 6): warnings.warn( "Python 2.6 is no longer supported by the Python core team, please " "upgrade your Python. A future version of PyEZ will drop " "support for Python 2.6", DeprecationWarning ) __version__ = version.VERSION __date__ = version.DATE # import time # __date__ = time.strftime("%Y-%b-%d") # Set default JSON encoder json._default_encoder = PyEzJSONEncoder() # Disable ignore_aliases for YAML dumper # To support version_info yaml.dumper.SafeDumper.ignore_aliases = lambda self, data: True yaml.dumper.Dumper.ignore_aliases = lambda self, data: True # Add YAML representer for version_info yaml.Dumper.add_multi_representer(version_info, version_yaml_representer) yaml.SafeDumper.add_multi_representer(version_info, version_yaml_representer) # Suppress Paramiko logger warnings plog = logging.getLogger('paramiko') if not plog.handlers: class NullHandler(logging.Handler): def emit(self, record): pass plog.addHandler(NullHandler()) junos-eznc-2.1.7/lib/jnpr/junos/cfg/0000755001013500016170000000000013163777613016654 5ustar stacys00000000000000junos-eznc-2.1.7/lib/jnpr/junos/cfg/__init__.py0000644001013500016170000000005513163777563020771 0ustar stacys00000000000000from jnpr.junos.cfg.resource import Resource junos-eznc-2.1.7/lib/jnpr/junos/cfg/phyport/0000755001013500016170000000000013163777613020361 5ustar stacys00000000000000junos-eznc-2.1.7/lib/jnpr/junos/cfg/phyport/__init__.py0000644001013500016170000000071213163777563022476 0ustar stacys00000000000000from jnpr.junos.cfg import Resource from jnpr.junos.cfg.phyport.classic import PhyPortClassic from jnpr.junos.cfg.phyport.switch import PhyPortSwitch __all__ = ['PhyPort'] class PhyPort(object): def __new__(cls, dev, name=None): supercls = { 'CLASSIC': PhyPortClassic, 'SWITCH': PhyPortSwitch, }.get(dev.facts['ifd_style']) newcls = type(cls.__name__, (supercls,), {}) return newcls(dev, name) junos-eznc-2.1.7/lib/jnpr/junos/cfg/phyport/base.py0000644001013500016170000000500513163777563021651 0ustar stacys00000000000000# 3rd-party from lxml.builder import E # local module from jnpr.junos.cfg.resource import Resource class PhyPortBase(Resource): """ [edit interfaces ] Resource name: str is the interface-name (IFD), e.g. 'ge-0/0/0' """ PROPERTIES = [ 'admin', # True 'description', # str 'speed', # ['10m','100m','1g','10g'] 'duplex', # ['full','half'] 'mtu', # int 'loopback', # True '$unit_count' # number of units defined ] PORT_DUPLEX = { 'full': 'full-duplex', 'half': 'half-duplex' } @classmethod def _set_invert(cls, in_this, item, from_this): from_item = in_this[item] in_this[item] = [ _k for _k, _v in from_this.items() if _v == from_item][0] # ----------------------------------------------------------------------- # XML readers # ----------------------------------------------------------------------- def _xml_at_top(self): return E.interfaces(E.interface( E.name(self._name) )) def _xml_at_res(self, xml): return xml.find('.//interface') def _xml_to_py(self, has_xml, has_py): # common to all subclasses Resource._r_has_xml_status(has_xml, has_py) has_py['admin'] = bool(has_xml.find('disable') is None) Resource.copyifexists(has_xml, 'description', has_py) Resource.copyifexists(has_xml, 'mtu', has_py) has_py['$unit_count'] = len(has_xml.findall('unit')) # ----------------------------------------------------------------------- # XML writers # ----------------------------------------------------------------------- # description handed by Resource def _xml_change_admin(self, xml): xml.append( Resource.xmltag_set_or_del( 'disable', (self.admin is False))) return True def _xml_change_mtu(self, xml): Resource.xml_set_or_delete(xml, 'mtu', self.mtu) return True # ----------------------------------------------------------------------- # Manager List, Catalog # ----------------------------------------------------------------------- def _r_list(self): got = self.R.get_interface_information( media=True, interface_name="[efgx][et]-*") self._rlist = [ name.text.strip() for name in got.xpath('physical-interface/name')] junos-eznc-2.1.7/lib/jnpr/junos/cfg/phyport/classic.py0000644001013500016170000000303513163777563022361 0ustar stacys00000000000000# 3rd-party from lxml.builder import E # local from jnpr.junos.cfg.resource import Resource from jnpr.junos import JXML from jnpr.junos.cfg.phyport.base import PhyPortBase class PhyPortClassic(PhyPortBase): # ----------------------------------------------------------------------- # XML readers # ----------------------------------------------------------------------- def _xml_to_py(self, has_xml, has_py): PhyPortBase._xml_to_py(self, has_xml, has_py) Resource.copyifexists(has_xml, 'speed', has_py) Resource.copyifexists(has_xml, 'link-mode', has_py, 'duplex') if has_xml.find('gigether-options/loopback') is not None: has_py['loopback'] = True has_py['$unit_count'] = len(has_xml.findall('unit')) # normalizers if 'duplex' in has_py: PhyPortBase._set_invert(has_py, 'duplex', self.PORT_DUPLEX) # ----------------------------------------------------------------------- # XML writers # ----------------------------------------------------------------------- def _xml_change_speed(self, xml): Resource.xml_set_or_delete(xml, 'speed', self.speed) return True def _xml_change_duplex(self, xml): value = self.PORT_DUPLEX.get(self.duplex) Resource.xml_set_or_delete(xml, 'link-mode', value) return True def _xml_change_loopback(self, xml): opts = E('gigether-options') opts.append(Resource.xmltag_set_or_del('loopback', self.loopback)) xml.append(opts) return True junos-eznc-2.1.7/lib/jnpr/junos/cfg/phyport/switch.py0000644001013500016170000000445513163777563022250 0ustar stacys00000000000000# 3rd-party from lxml.builder import E # local from jnpr.junos.cfg import Resource from jnpr.junos import JXML from jnpr.junos.cfg.phyport.base import PhyPortBase class PhyPortSwitch(PhyPortBase): PORT_SPEED = { 'auto': 'auto-negotiation', '10m': 'ethernet-10m', '100m': 'ethernet-100m', '1g': 'ethernet-1g' } # ----------------------------------------------------------------------- # XML readers # ----------------------------------------------------------------------- def _xml_to_py(self, has_xml, has_py): PhyPortBase._xml_to_py(self, has_xml, has_py) # speed, duplex, loopback are all under 'ether-options' ethopts = has_xml.find('ether-options') if ethopts is None: return if ethopts.find('loopback') is not None: has_py['loopback'] = True speed = ethopts.find('speed') if speed is not None: # take the first child element has_py['speed'] = speed[0].tag PhyPortBase._set_invert(has_py, 'speed', self.PORT_SPEED) Resource.copyifexists(ethopts, 'link-mode', has_py, 'duplex') if 'duplex' in has_py: PhyPortBase._set_invert(has_py, 'duplex', self.PORT_DUPLEX) # ----------------------------------------------------------------------- # XML writers # ----------------------------------------------------------------------- def _xml_hook_build_change_begin(self, xml): if any([this in self.should for this in ['speed', 'duplex', 'loopback']]): self._ethopts = E('ether-options') xml.append(self._ethopts) def _xml_change_speed(self, xml): speed_tag = self.PORT_SPEED.get(self.speed) add_this = E.speed( JXML.DEL) if speed_tag is None else E.speed( E(speed_tag)) self._ethopts.append(add_this) return True def _xml_change_duplex(self, xml): value = self.PORT_DUPLEX.get(self.duplex) Resource.xml_set_or_delete(self._ethopts, 'link-mode', value) return True def _xml_change_loopback(self, xml): self._ethopts.append( Resource.xmltag_set_or_del( 'loopback', self.loopback)) return True junos-eznc-2.1.7/lib/jnpr/junos/cfg/resource.py0000644001013500016170000006424613163777563021075 0ustar stacys00000000000000# stdlib from pprint import pformat from copy import deepcopy # 3rd-party from lxml.builder import E # package modules from jnpr.junos import jxml as JXML P_JUNOS_EXISTS = '_exists' P_JUNOS_ACTIVE = '_active' class Resource(object): PROPERTIES = [ P_JUNOS_EXISTS, P_JUNOS_ACTIVE ] def __init__(self, junos, namevar=None, **kvargs): """ Resource or Resource-Manager constructor. All managed resources and resource-managers inherit from this class. junos Instance of Device, this is bound to the Resource for device access namevar If not None, identifies a specific resource by 'name'. The format of the name is resource dependent. Most resources take a single string name, while others use tuples for compound names. refer to each resource for the 'namevar' definition If namevar is None, then the instance is a Resource-Manager (RM). The RM is then used to select specific resources by name using the __getitem__ overload. kvargs['P'] or kvargs['parent'] Instance to the resource parent. This is set when resources have hierarchical relationships, like rules within rulesets kvargs['M'] Instance to the resource manager. """ self._junos = junos self._name = namevar self._parent = kvargs.get('parent') or kvargs.get('P') self._opts = kvargs self._manager = kvargs.get('M') if not namevar: # then this is a resource-manager instance. setup the list and # catalog attributes, but do not load them now. when the caller # invokes the properties, they will auto-load when empty. self._rlist = [] self._rcatalog = {} return # otherwise, a resource includes public attributes: self.properties = [] self.properties.extend(Resource.PROPERTIES) if self.__class__ != Resource: self.properties.extend(self.__class__.PROPERTIES) # if this resource manages others, then hook that # into the :manages: list if hasattr(self, 'MANAGES'): self._manages = self.MANAGES.keys() for k, v in self.MANAGES.items(): self.__dict__[k] = v(junos, parent=self) # setup resource cache-attributes self.has = {} self.should = {} self._is_new = False # now load the properties from the device. self.read() # ----------------------------------------------------------------------- # PROPERTIES # ----------------------------------------------------------------------- @property def active(self): """ is this resource configuration active on the Junos device? :RuntimeError: if invoked on a manager object """ if self.is_mgr: raise RuntimeError("Not on a manager!") return self.has[P_JUNOS_ACTIVE] @property def exists(self): """ does this resource configuration exist on the Junos device? :RuntimError: if invoked on a manager """ if self.is_mgr: raise RuntimeError("Not on a manager!") return self.has[P_JUNOS_EXISTS] @property def is_mgr(self): """ is this a resource manager? """ return (self._name is None) @property def is_new(self): """ is this a new resource? that is, it does not exist on the Junos device when it was initally retrieved :RuntimeError: if invoked on a manager """ if self.is_mgr: raise RuntimeError("Not on a manager!") return self._is_new @property def name(self): """ the name of the resource :RuntimeError: if invoked on a manager """ if self.is_mgr: raise RuntimeError("Not on a manager!") return self._name @name.setter def name(self, value): if self.is_mgr: raise RuntimeError("Not on a manager!") raise AttributeError("name is currently read-only") @property def manages(self): """ a resource may contain sub-managers for hierarchical oriented resources. this method will return a list of manager names attached to this resource, or :None: if there are not any """ if hasattr(self, '_manages'): return self._manages return None @manages.setter def manages(self): raise AttributeError("read-only") @property def xml(self): """ for debugging the resource XML configuration that was read from the Junos device """ if self.is_mgr: raise RuntimeError("Not on a manager!") return self._has_xml @property def list(self): """ returns a list of named resources """ if not self.is_mgr: raise RuntimeError("Must be a manager!") if not len(self._rlist): self.list_refresh() return self._rlist @property def catalog(self): """ returns a dictionary of resources """ if not self.is_mgr: raise RuntimeError("Must be a manager!") if not len(self._rcatalog): self.catalog_refresh() return self._rcatalog # ------------------------------------------------------------------------- # shortcuts # ------------------------------------------------------------------------- @property def D(self): """ returns the Device object bound to this resource/manager """ return self._junos @property def R(self): """ returns the Device RPC meta object """ return self._junos.rpc @property def M(self): """ returns the :Resource: manager associated to this resource """ return self._manager @property def P(self): """ returns the parent of the associated Junos object """ return self._parent # ----------------------------------------------------------------------- # PUBLIC METHODS # ----------------------------------------------------------------------- # ------------------------------------------------------------------------- # read # ------------------------------------------------------------------------- def read(self): """ read resource configuration from device """ self._r_has_init() self._has_xml = self._r_config_read_xml() if self._has_xml is None or not len(self._has_xml): self._is_new = True self._r_when_new() return None # the xml_read_parser *MUST* be implement by the # resource subclass. it is used to parse the XML # into native python structures. self._xml_to_py(self._has_xml, self.has) # return the python structure represntation return True # ------------------------------------------------------------------------- # write # ------------------------------------------------------------------------- def write(self, **kvargs): """ write resource configuration stored in :should: back to device kvargs['touch'] if True, then write() will skip the check to see if any items exist in :should: """ if self.is_mgr: raise RuntimeError("Not on a manager!") if not len(self.should) and 'touch' not in kvargs: return False # if this resource did not previously exist, # then mark it now into :should: if P_JUNOS_EXISTS not in self.should: self._r_set_exists(self.should, True) if self.is_new: self._r_set_active(self.should, True) # construct the XML change structure xml_change = self._xml_build_change() if xml_change is None: return False # write these changes to the device self._r_config_write_xml(xml_change) # copy :should: into :has: and then clear :should: self.has.update(self.should) self.should.clear() return True # ------------------------------------------------------------------------- # activate # ------------------------------------------------------------------------- def activate(self): """ activate resource in Junos config the same as the Junos config-mode "activate" command """ # no action needed if it's already active if self.active: return False self._r_set_active(self.should, True) return self.write() # ------------------------------------------------------------------------- # deactivate # ------------------------------------------------------------------------- def deactivate(self): """ activate resource in Junos config the same as the Junos config-mode "deactivate" command """ # no action needed if it's already deactive if not self.active: return False self._r_set_active(self.should, False) return self.write() # ------------------------------------------------------------------------- # delete # ------------------------------------------------------------------------- def delete(self): """ remove configuration from Junos device the same as the Junos config-mode "delete" command """ # cannot delete something that doesn't exist # @@@ should raise? if not self.exists: return False # remove the config from Junos xml = self._xml_edit_at_res() xml.attrib.update(JXML.DEL) self._xml_hook_on_delete(xml) self._r_config_write_xml(xml) # reset the :has: attribute self._r_has_init() return True # ------------------------------------------------------------------------- # rename # ------------------------------------------------------------------------- def rename(self, new_name): """ rename resource in Junos configuration the same as the Junos config-mode "rename" command """ # cannot rename something that doesn't exist # @@@ should raise? if not self.exists: return False xml = self._xml_edit_at_res() xml.attrib.update(JXML.REN) xml.attrib.update(JXML.NAME(new_name)) self._r_config_write_xml(xml) self._name = new_name return True # ------------------------------------------------------------------------- # reorder # ------------------------------------------------------------------------- def reorder(self, **kvargs): """ move the configuration within the Junos hierarcy the same as the Junos config-mode "insert" command :kvargs: after="" before="" """ cmd, name = next(kvargs.iteritems()) if cmd != 'before' and cmd != 'after': raise ValueError("Must be either 'before' or 'after'") xml = self._xml_edit_at_res() xml.attrib.update(JXML.INSERT(cmd)) xml.attrib.update(JXML.NAME(name)) self._r_config_write_xml(xml) return True def list_refresh(self): """ reloads the managed resource list from the Junos device """ if not self.is_mgr: raise RuntimeError("Only on a manager!") del self._rlist[:] self._r_list() # invoke the specific resource method def catalog_refresh(self): """ reloads the resource catalog from the Junos device """ if not self.is_mgr: raise RuntimeError("Only on a manager!") self._rcatalog.clear() self._r_catalog() # invoke the specific resource method def _r_catalog(self): """ provide a 'default' catalog creator method that simply uses the manager list and runs through each resource making a refcopy to the :has: properties """ zone_list = self.list for name in zone_list: r = self[name] self._rcatalog[name] = r.has def refresh(self): if not self.is_mgr: raise RuntimeError("Only on a manager!") self.list_refresh() self.catalog_refresh() def propcopy(self, p_name): """ proptery from :has: to :should: performs a 'deepcopy' of the property; used to make changes to list, dict type properties """ self.should[p_name] = deepcopy(self.has[p_name]) return self.should[p_name] # ----------------------------------------------------------------------- # OVERLOADS # ----------------------------------------------------------------------- # --------------------------------------------------------------------- # ITEMS: for read/write of resource managed properties # --------------------------------------------------------------------- def __getitem__(self, namekey): """ implements [] to obtain property value. value will come from :should: if set or from :has: otherwise. """ if self.is_mgr: # --------------------------------------------------------------- # use is resource-manager accessing specific index/name # to return resource instance # --------------------------------------------------------------- self._opts['M'] = self if isinstance(namekey, int): # index, not name namekey = self.list[namekey] res = self.__class__(self._junos, namekey, **self._opts) return res # --------------------------------------------------------------- # use-case is resource instance for read property # --------------------------------------------------------------- if namekey in self.should: return self.should[namekey] if namekey in self.has: return self.has[namekey] if namekey in self.properties: # it's a valid property, just not set in the resource return None raise ValueError("Unknown property request: %s" % namekey) def __setitem__(self, r_prop, value): """ implements []= to set property value into :should: """ if self.is_mgr: raise RuntimeError("Not on a manager!") if r_prop in self.properties: self.should[r_prop] = value else: raise ValueError("Uknown property request: %s" % r_prop) def __call__(self, **kvargs): """ alternative way to set property values as aggregation of key/value pairs. this will automatically call :write(): when completed. """ if self.is_mgr: raise RuntimeError("Not on a manager!") if not kvargs: return False # validate property names first! for p_name, p_val in kvargs.items(): if p_name not in self.properties: raise ValueError("Unknown property: %s" % p_name) # now cleared to add all the values self.should.update(kvargs) return self.write() # --------------------------------------------------------------------- # ATTRIBUTE: for read/write of resource managed properties # --------------------------------------------------------------------- def __getattr__(self, namekey): """ returns property value, accessed as attribute . only for resource instance """ if self.is_mgr: raise RuntimeError("not on a resource-manager") return self[namekey] def __setattr__(self, name, value): if hasattr(self, 'properties') and name in self.properties: # we can set this name/value in the resource property self[name] = value else: # pass 'up' to standard setattr method object.__setattr__(self, name, value) # --------------------------------------------------------------------- # PRINT/DEBUG # --------------------------------------------------------------------- def __repr__(self): """ stringify for debug/printing this will show the resource manager (class) name, the resource (Junos) name, and the contents of the :has: dict and the contents of the :should: dict """ mgr_name = self.__class__.__name__ return "NAME: %s: %s\nHAS: %s\nSHOULD:%s" % \ (mgr_name, self._name, pformat(self.has), pformat(self.should)) \ if not self.is_mgr \ else "Resource Manager: %s" % mgr_name # --------------------------------------------------------------------- # ITERATOR # --------------------------------------------------------------------- def __iter__(self): """ iterate through each Resource in the Manager list """ for name in self.list: yield self[name] # ----------------------------------------------------------------------- # XML reading # ----------------------------------------------------------------------- def _r_config_read_xml(self): """ read the resource config from the Junos device """ get = self._xml_at_top() self._xml_hook_read_begin(get) got = self._junos.rpc.get_config(get) return self._xml_at_res(got) def _xml_at_top(self): """ ~| WARNING |~ resource subclass *MUST* implement this! Create an XML structure that will be used to retrieve the resource configuration from the device. """ raise RuntimeError("Resource missing method: %s" % self.__class__.__name__) def _xml_at_res(self, xml): """ ~| WARNING |~ resource subclass *MUST* implement this! Return the XML element of the specific resource from the :xml: structure. The :xml: will be the configuration starting at the top of the Junos config, i.e. , and the resource needs to "cursor" at the specific resource within that structure """ raise RuntimeError("Resource missing method: %s" % self.__class__.__name__) # ----------------------------------------------------------------------- # XML writing # ----------------------------------------------------------------------- def _xml_build_change(self): """ iterate through the :should: properties creating the necessary configuration change structure. if there are no changes, then return :None: """ edit_xml = self._xml_edit_at_res() # if there is resource hook to do something # before we build up the xml configuration, # then do that now self._xml_hook_build_change_begin(edit_xml) # if this resource should be deleted then # handle that case and return if not self.should[P_JUNOS_EXISTS]: self._xml_change__exists(edit_xml) return edit_xml # otherwise, this is an update, and we need to # construct the XML for change changed = False for r_prop in self.properties: if r_prop in self.should: edit_fn = "_xml_change_" + r_prop changed |= getattr(self, edit_fn)(edit_xml) # if there is a resource hook to do something # after we've run through all the property methods # then invoke that now changed |= self._xml_hook_build_change_end(edit_xml) return edit_xml if changed else None def _r_config_write_xml(self, xml): """ write the xml change to the Junos device, trapping on exceptions. """ top_xml = xml.getroottree().getroot() try: result = self._junos.rpc.load_config(top_xml, action='replace') except Exception as err: # see if this is OK or just a warning if len(err.rsp.xpath('.//error-severity[. = "error"]')): raise err return err.rsp return result # ------------------------------------------------------------------------ # XML edit cursor methods # ------------------------------------------------------------------------ def _xml_edit_at_res(self): return self._xml_at_res(self._xml_at_top()) # ------------------------------------------------------------------------ # XML standard properties "writers" # ------------------------------------------------------------------------ def _xml_change_description(self, xml): Resource.xml_set_or_delete( xml, 'description', self.should['description']) return True def _xml_change__active(self, xml): if self.should[P_JUNOS_ACTIVE] == self.has[P_JUNOS_ACTIVE]: return False value = 'active' if self.should[P_JUNOS_ACTIVE] else 'inactive' xml.attrib[value] = value return True def _xml_change__exists(self, xml): # if this is a change to create something new, # then invoke the 'on-create' hook and return # the results if self.should[P_JUNOS_EXISTS]: return self._xml_hook_on_new(xml) # otherwise, we are deleting this resource xml.attrib.update(JXML.DEL) # now call the 'on-delete' hook and return # the results return self._xml_hook_on_delete(xml) # ----------------------------------------------------------------------- # XML HOOK methods # ----------------------------------------------------------------------- def _xml_hook_read_begin(self, xml): """ called from :_r_config_read_xml(): after call to :_xml_at_top(): and before the config request is made to the Junos device. This hook allows the subclass to munge the XML get-request with additional items if necessary Returns: :True: when :xml: is changed :False: otherwise """ return False def _xml_hook_build_change_begin(self, xml): """ called from :_xml_build_change(): before the individual property methods are invoked. allows the resource to do anything, like pruning stub elements that were generated as part of :_xml_at_top(): Returns: :True: when :xml: is changed :False: otherwise """ return False def _xml_hook_build_change_end(self, xml): """ called from :_xml_build_change(): after all of the properties methods have been processed. Returns: :True: when :xml: is changed :False: otherwise """ return False def _xml_hook_on_delete(self, xml): """ called when an XML write operation is going to delete the resource. Returns: :True: when :xml: is changed :False: otherwise """ return False def _xml_hook_on_new(self, xml): """ called when an XML write operation is going to create a new resource. Returns: :True: when :xml: is changed :False: otherwise """ return False # ----------------------------------------------------------------------- # Resource HOOK methods # ----------------------------------------------------------------------- def _r_when_new(self): """ called by :read(): when the resource is new; i.e. there is no existing Junos configuration """ pass def _r_when_delete(self): """ ~| not used yet |~ """ pass # ----------------------------------------------------------------------- # ~private~ resource methods # ----------------------------------------------------------------------- def _r_set_active(self, my_props, value): my_props[P_JUNOS_ACTIVE] = value def _r_set_exists(self, my_props, value): my_props[P_JUNOS_EXISTS] = value def _r_has_init(self): self.has.clear() self.has[P_JUNOS_EXISTS] = False self.has[P_JUNOS_ACTIVE] = False @classmethod def _r_has_xml_status(klass, as_xml, as_py): """ set the 'exists' and 'active' :has: values """ as_py[P_JUNOS_ACTIVE] = False if as_xml.attrib.get( 'inactive') else True as_py[P_JUNOS_EXISTS] = True @classmethod def xml_set_or_delete(klass, xml, ele_name, value): """ HELPER function to either set a value or remove the element """ if value is not None and not isinstance(value, str): value = str(value) xml.append(E(ele_name, (value if value else JXML.DEL))) @classmethod def xmltag_set_or_del(klass, ele_name, value): """ HELPER function creates an XML element tag read-only that includes the DEL attribute depending on :value: """ return E(ele_name, ({} if value else JXML.DEL)) @classmethod def copyifexists(klass, xml, ele_name, to_py, py_name=None): ele_val = xml.find(ele_name) if ele_val is not None: to_py[(py_name if py_name else ele_name)] = ele_val.text.strip() @classmethod def diff_list(klass, has_list, should_list): # covert lists to perform differencing should = set(should_list) has = set(has_list) # return lists (added, removed) return (list(should - has), list(has - should)) def _xml_list_property_add_del_names(self, xml, prop_name, element_name): """ utility method use to process :list: properties. this will add/delete items give the propery type and associated XML element name """ (adds, dels) = Resource.diff_list( self.has.get(prop_name, []), self.should[prop_name]) for this in adds: xml.append(E(element_name, E.name(this))) for this in dels: xml.append(E(element_name, JXML.DEL, E.name(this))) junos-eznc-2.1.7/lib/jnpr/junos/cfg/user.py0000644001013500016170000000577713163777563020230 0ustar stacys00000000000000# 3rd-party modules from lxml.builder import E # module packages from jnpr.junos.cfg import Resource from jnpr.junos import jxml as JXML from jnpr.junos.cfg.user_ssh_key import UserSSHKey class User(Resource): """ [edit system login user ] Resource name: str is the user login name Manages resources: sshkey, UserSSHKey """ PROPERTIES = [ 'uid', 'fullname', # the full-name field 'userclass', # user class 'password', # write-only clear-text password, will get crypt'd '$password', # read-only crypt'd password '$sshkeys', # read-only names of ssh-keys ] MANAGES = {'sshkey': UserSSHKey} # ----------------------------------------------------------------------- # XML readers # ----------------------------------------------------------------------- def _xml_at_top(self): return E.system(E.login(E.user(E.name(self._name)))) def _xml_at_res(self, xml): return xml.find('.//user') def _xml_to_py(self, has_xml, has_py): Resource._r_has_xml_status(has_xml, has_py) has_py['userclass'] = has_xml.findtext('class') Resource.copyifexists(has_xml, 'full-name', has_py, 'fullname') Resource.copyifexists(has_xml, 'uid', has_py) if 'uid' in has_py: has_py['uid'] = int(has_py['uid']) auth = has_xml.find('authentication') if auth is not None: # plain-text password Resource.copyifexists( auth, 'encrypted-password', has_py, '$password') # ssh-keys sshkeys = auth.xpath('ssh-rsa | ssh-dsa') if sshkeys is not None: has_py['$sshkeys'] = [(sshkey.tag, sshkey.findtext('name').strip()) for sshkey in sshkeys ] # ----------------------------------------------------------------------- # XML property writers # ----------------------------------------------------------------------- def _xml_change_fullname(self, xml): xml.append(E('full-name', self['fullname'])) return True def _xml_change_userclass(self, xml): xml.append(E('class', self['userclass'])) return True def _xml_change_password(self, xml): xml.append(E.authentication( E('plain-text-password-value', self['password']) )) return True def _xml_change_uid(self, xml): xml.append(E.uid(str(self['uid']))) return True # ----------------------------------------------------------------------- # Manager List, Catalog # ----------------------------------------------------------------------- def _r_list(self): get = E.system(E.login(E.user(JXML.NAMES_ONLY))) got = self.R.get_config(get) self._rlist = [name.text for name in got.xpath('.//user/name')] junos-eznc-2.1.7/lib/jnpr/junos/cfg/user_ssh_key.py0000644001013500016170000000650213163777563021740 0ustar stacys00000000000000# 3rd-party modules from lxml.builder import E # local module from jnpr.junos.cfg import Resource from jnpr.junos import jxml as JXML class UserSSHKey(Resource): """ [edit system login user authentication ] Resource name: tuple(, ) : ['ssh-dsa', 'ssh-rsa'] : SSH public key string (usually something very long) Resource manager utilities: load_key - allows you to load an ssh-key from a file or str """ # there are no properties, since the name constitutes the # actual ssk key data, yo! PROPERTIES = [] # ----------------------------------------------------------------------- # XML readers # ----------------------------------------------------------------------- def _xml_at_top(self): key_t, key_v = self._name return E.system(E.login(E.user( E.name(self.P.name), E.authentication( E(key_t, E.name(key_v) ) )))) def _xml_at_res(self, xml): return xml.find('.//authentication/%s' % self._name[0]) def _xml_to_py(self, has_xml, has_py): Resource._r_has_xml_status(has_xml, has_py) # ----------------------------------------------------------------------- # UTILITY FUNCTIONS # ----------------------------------------------------------------------- def load_key(self, path=None, key_value=None): """ Adds a new ssh-key to the user authentication. You can provide either the path to the ssh-key file, or the contents of they key (useful for loading the same key on many devices) :path: (optional) path to public ssh-key file on the local server, :key_value: (optional) the contents of the ssh public key """ if not self.is_mgr: raise RuntimeError("must be a resource-manager!") if path is None and key_value is None: raise RuntimeError("You must provide either path or key_value") if path is not None: # snarf the file into key_value, yo! with open(path, 'r') as f: key_value = f.read().strip() # extract some data from the key value, this will either # be 'ssh-rsa' or 'ssh-dss'. we need to decode this to set # the type correctly in the RPC. vt = key_value[0:7] key_map = {'ssh-rsa': 'ssh-rsa', 'ssh-dss': 'ssh-dsa'} key_type = key_map.get(vt) if key_type is None: raise RuntimeError("Unknown ssh public key file type: %s" % vt) # at this point we are going to add a new key, so really what we are # doing is accessing a new instance of this class and # doing a write, but just a touch since there are no properties, yo! new_key = self[(key_type, key_value)] return new_key.write(touch=True) # ----------------------------------------------------------------------- # Manager List, Catalog # ----------------------------------------------------------------------- def _r_list(self): # the key list comes from the parent object. self._rlist = self.P['$sshkeys'] def _r_catalog(self): # no catalog but the keys self._rcatalog = dict((k, None) for k in self.list) junos-eznc-2.1.7/lib/jnpr/junos/cfgro/0000755001013500016170000000000013163777613017215 5ustar stacys00000000000000junos-eznc-2.1.7/lib/jnpr/junos/cfgro/__init__.py0000644001013500016170000000000013163777563021320 0ustar stacys00000000000000junos-eznc-2.1.7/lib/jnpr/junos/cfgro/srx.yml0000644001013500016170000000363713163777563020571 0ustar stacys00000000000000--- ##### ========================================================================= ##### SAMPLE SRX config-read-only tables/views ##### ========================================================================= ### --------------------------------------------------------------------------- ### SRX zones and interfaces ### --------------------------------------------------------------------------- ZoneTable: get: security/zones/security-zone ZoneIfsTable: get: security/zones/security-zone/interfaces required_keys: security_zone: name ### --------------------------------------------------------------------------- ### SRX zone address book item table ### --------------------------------------------------------------------------- ABitemTable: get: security/zones/security-zone/address-book/address required_keys: security_zone: name view: abitemView abitemView: fields: ip_prefix: ip-prefix ### --------------------------------------------------------------------------- ### SRX zone-to-zone security policy ### --------------------------------------------------------------------------- PolicyContextTable: get: security/policies/policy key: - from-zone-name - to-zone-name view: policyContextView policyContextView: fields: from_zone: from-zone-name to_zone: to-zone-name ### --------------------------------------------------------------------------- ### SRX zone-to-zone security policy rules ### --------------------------------------------------------------------------- PolicyRuleTable: get: security/policies/policy/policy required_keys: policy: - from-zone-name - to-zone-name view: policyRuleView policyRuleView: groups: match: match then: then fields_match: match_src: source-address match_dst: destination-address match_app: application fields_then: log_init : { log/session-init: flag } action : deny | permit junos-eznc-2.1.7/lib/jnpr/junos/console.py0000644001013500016170000002566213163777563020150 0ustar stacys00000000000000""" This file defines the 'netconifyCmdo' class. Used by the 'netconify' shell utility. """ import traceback import sys import logging import warnings import socket # 3rd-party packages 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.xml_ import NCElement from jnpr.junos.device import _Connection # local modules from jnpr.junos.rpcmeta import _RpcMetaExec from jnpr.junos.factcache import _FactCache from jnpr.junos import jxml as JXML from jnpr.junos.ofacts import * from jnpr.junos.decorators import ignoreWarnDecorator from jnpr.junos.device import _Jinja2ldr logger = logging.getLogger("jnpr.junos.console") class Console(_Connection): def __init__(self, **kvargs): """ NoobDevice object constructor. :param str host: **REQUIRED** host-name or ipaddress of target device :param str user: *OPTIONAL* login user-name, uses root if not provided :param str passwd: *OPTIONAL* in console connection for device at zeroized state password is not required :param int port: *OPTIONAL* port, defaults to '23' for telnet mode and '/dev/ttyUSB0' for serial. :param int baud: *OPTIONAL* baud, default baud rate is 9600 :param str mode: *OPTIONAL* mode, mode of connection (telnet/serial) default is telnet :param int timeout: *OPTIONAL* timeout, default is 0.5 :param int attempts: *OPTIONAL* attempts, default is 10 :param str ssh_config: *OPTIONAL* The path to the SSH configuration file. This can be used to load SSH information from a configuration file. By default ~/.ssh/config is queried it will be used by SCP class. So its assumed ssh is enabled by the time we use SCP functionality. :param bool gather_facts: *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, rpc is sent to the console. If ``True``, after sleep(5), a new-line is sent """ # ---------------------------------------- # setup instance connection/open variables # ---------------------------------------- self._tty = None self._ofacts = {} self.connected = False self._skip_logout = False self.results = dict(changed=False, failed=False, errmsg=None) # hostname is not required in serial mode connection self._hostname = kvargs.get('host') self._auth_user = kvargs.get('user', 'root') self._auth_password = kvargs.get( 'password', '') or kvargs.get( 'passwd', '') self._port = kvargs.get('port', '23') self._baud = kvargs.get('baud', '9600') self._mode = kvargs.get('mode', 'telnet') self._timeout = kvargs.get('timeout', '0.5') self._normalize = kvargs.get('normalize', False) self._attempts = kvargs.get('attempts', 10) 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._j2ldr = _Jinja2ldr if self._fact_style == 'old': self.facts = self.ofacts else: self.facts = _FactCache(self) @property def timeout(self): """ :returns: current console connection timeout value (int) in seconds. """ return self._timeout @timeout.setter def timeout(self, value): """ Used to change the console connection timeout value (default=0.5 sec). :param int value: New timeout value in seconds """ self._timeout = value @property def transform(self): """ :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 """ # --------------------------------------------------------------- # validate device hostname or IP address # --------------------------------------------------------------- if self._mode.upper() == 'TELNET' and self._hostname is None: self.results['failed'] = True self.results[ 'errmsg'] = 'ERROR: Device hostname/IP not specified !!!' return self.results # -------------------- # login to the CONSOLE # -------------------- try: self._tty_login() except RuntimeError as err: logger.error("ERROR: {0}:{1}\n".format('login', str(err))) logger.error( "\nComplete traceback message: {0}".format( traceback.format_exc())) raise err except Exception as ex: logger.error("Exception occurred: {0}:{1}\n".format('login', str(ex))) raise ex self.connected = 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 return self def close(self, skip_logout=False): """ Closes the connection to the device. """ 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 self.connected = False elif self.connected is True: try: self._tty._tty_close() except Exception as err: logger.error("ERROR {0}:{1}\n".format('close', str(err))) logger.error( "\nComplete traceback message: {0}".format( traceback.format_exc())) 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 reply = self._tty.nc.rpc(rpc_cmd) rpc_rsp_e = NCElement(reply, self.junos_dev_handler.transform_reply() )._NCElement__doc return rpc_rsp_e # ------------------------------------------------------------------------- # LOGIN/LOGOUT # ------------------------------------------------------------------------- def _tty_login(self): tty_args = dict() tty_args['user'] = self._auth_user tty_args['passwd'] = self._auth_password tty_args['timeout'] = float(self._timeout) tty_args['attempts'] = int(self._attempts) tty_args['baud'] = self._baud 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': tty_args['port'] = self._port self.console = ('serial', self._port) self._tty = Serial(**tty_args) else: logger.error('Mode should be either telnet or serial') raise AttributeError('Mode to be telnet/serial') self._tty.login() def _tty_logout(self): self._tty.logout() def zeroize(self): """ perform device ZEROIZE actions """ logger.info("zeroize : ZEROIZE device, rebooting") self._tty.nc.zeroize() self._skip_logout = True self.results['changed'] = True # ----------------------------------------------------------------------- # Context Manager # ----------------------------------------------------------------------- def __enter__(self): self._conn = self.open() return self def __exit__(self, exc_type, exc_val, exc_tb): if self.connected: self.close() junos-eznc-2.1.7/lib/jnpr/junos/decorators.py0000644001013500016170000001533413163777563020646 0ustar stacys00000000000000# stdlib from functools import wraps import re import sys 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): @wraps(function) def wrapper(*args, **kwargs): if 'dev_timeout' in kwargs: try: dev = args[0].dev except: dev = args[0] restore_timeout = dev.timeout dev.timeout = kwargs.pop('dev_timeout', None) try: result = function(*args, **kwargs) dev.timeout = restore_timeout return result except Exception: dev.timeout = restore_timeout raise else: try: return function(*args, **kwargs) except Exception: raise return wrapper def normalizeDecorator(function): @wraps(function) def wrapper(*args, **kwargs): if 'normalize' in kwargs: normalize = kwargs.pop('normalize', None) try: dev = args[0].dev except: dev = args[0] if dev._normalize != normalize: restore_transform = dev.transform if normalize is False: try: dev.transform = dev._nc_transform result = function(*args, **kwargs) dev.transform = restore_transform return result except Exception: dev.transform = restore_transform raise else: try: dev.transform = dev._norm_transform result = function(*args, **kwargs) dev.transform = restore_transform return result except Exception: dev.transform = restore_transform raise else: try: return function(*args, **kwargs) except Exception: raise else: try: return function(*args, **kwargs) except Exception: raise return wrapper def ignoreWarnDecorator(function): """ Ignore warnings if all 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 element. # Set rsp to the root 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 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 junos-eznc-2.1.7/lib/jnpr/junos/device.py0000644001013500016170000014533213163777563017742 0ustar stacys00000000000000# stdlib import os import six import types import platform import warnings # stdlib, in support of the the 'probe' method import socket import datetime import time import sys import json import re # 3rd-party packages 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 import jinja2 # local modules from jnpr.junos.rpcmeta import _RpcMetaExec from jnpr.junos import exception as EzErrors 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, \ ignoreWarnDecorator from jnpr.junos.exception import JSONLoadError _MODULEPATH = os.path.dirname(__file__) class _MyTemplateLoader(jinja2.BaseLoader): """ Create a jinja2 template loader class that can be used to load templates from all over the filesystem, but defaults to the CWD and the 'templates' directory of the module """ def __init__(self): self.paths = ['.', os.path.join(_MODULEPATH, 'templates')] def get_source(self, environment, template): def _in_path(dir): return os.path.exists(os.path.join(dir, template)) path = list(filter(_in_path, self.paths)) if not path: raise jinja2.TemplateNotFound(template) path = os.path.join(path[0], template) mtime = os.path.getmtime(path) 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'. source = f.read() return source, path, lambda: mtime == os.path.getmtime(path) _Jinja2ldr = jinja2.Environment(loader=_MyTemplateLoader()) class _Connection(object): # ------------------------------------------------------------------------ # property: hostname # ------------------------------------------------------------------------ @property def hostname(self): """ :returns: the host-name of the Junos device. """ return self._hostname if ( self._hostname != 'localhost') else self.facts.get('hostname') # ------------------------------------------------------------------------ # property: user # ------------------------------------------------------------------------ @property def user(self): """ :returns: the login user (str) accessing the Junos device """ return self._auth_user # ------------------------------------------------------------------------ # property: password # ------------------------------------------------------------------------ @property def password(self): """ :returns: ``None`` - do not provide the password """ return None # read-only @password.setter def password(self, value): """ Change the authentication password value. This is handy in case the calling program needs to attempt different passwords. """ self._auth_password = value # ------------------------------------------------------------------------ # property: logfile # ------------------------------------------------------------------------ @property def logfile(self): """ :returns: exsiting logfile ``file`` object. """ return self._logfile @logfile.setter def logfile(self, value): """ Assigns an opened file object to the device for logging If there is an open logfile, and 'value' is ``None`` or ``False`` then close the existing file. :param file value: An open ``file`` object. :returns: the new logfile ``file`` object :raises ValueError: When **value** is not a ``file`` object """ # got an existing file that we need to close if (not value) and (self._logfile is not None): rc = self._logfile.close() self._logfile = False return rc if sys.version < '3': if not isinstance(value, file): raise ValueError("value must be a file object") else: import io if not isinstance(value, io.TextIOWrapper): raise ValueError("value must be a file object") self._logfile = value return self._logfile # ------------------------------------------------------------------------ # property: timeout # ------------------------------------------------------------------------ @property def timeout(self): """ :returns: the current RPC timeout value (int) in seconds. """ return self._conn.timeout @timeout.setter def timeout(self, value): """ Used to change the RPC timeout value (default=30 sec). :param int value: New timeout value in seconds """ 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 ofacts(self): """ :returns: Device fact dictionary """ 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 @ofacts.setter def ofacts(self, value): """ read-only property """ raise RuntimeError("facts is read-only!") # ------------------------------------------------------------------------ # property: port # ------------------------------------------------------------------------ @property def port(self): """ :returns: the port (str) to connect to the Junos device """ 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) else: home = os.getenv('HOME') if not home: return None sshconf_path = os.path.join(os.getenv('HOME'), '.ssh/config') if not os.path.exists(sshconf_path): return None else: sshconf = paramiko.SSHConfig() with open(sshconf_path, 'r') as fp: sshconf.parse(fp) found = sshconf.lookup(self._hostname) self._hostname = found.get('hostname', self._hostname) self._port = found.get('port', self._port) self._conf_auth_user = found.get('user') self._conf_ssh_private_key_file = found.get('identityfile') return sshconf_path def display_xml_rpc(self, command, format='xml'): """ Executes the CLI command and returns the CLI xml object by default. For example:: print dev.display_xml_rpc('show version').tag or print dev.display_xml_rpc('show version', format='text') :param str command: The CLI command to retrieve XML RPC for, e.g. "show version" :param str format: The return format, by default is XML. You can optionally select "text" to return the XML structure as a string. """ try: command = command + '| display xml rpc' 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) return rsp[0] except: return "invalid command: " + command # ------------------------------------------------------------------------ # Template: retrieves a Jinja2 template # ------------------------------------------------------------------------ def Template(self, filename, parent=None, gvars=None): """ Used to return a Jinja2 :class:`Template`. :param str filename: file-path to Jinja2 template file on local device :returns: Jinja2 :class:`Template` give **filename**. """ return self._j2ldr.get_template(filename, parent, gvars) # ------------------------------------------------------------------------ # property: manages # ------------------------------------------------------------------------ @property def manages(self): """ :returns: ``list`` of Resource Managers/Utilities attached to this instance using the :meth:`bind` method. """ return self._manages # ------------------------------------------------------------------------ # dealing with bind aspects # ------------------------------------------------------------------------ def bind(self, *vargs, **kvargs): """ Used to attach things to this Device instance and make them a property of the :class:Device instance. The most common use for bind is attaching Utility instances to a :class:Device. For example:: from jnpr.junos.utils.config import Config dev.bind( cu=Config ) dev.cu.lock() # ... load some changes dev.cu.commit() dev.cu.unlock() :param list vargs: A list of functions that will get bound as instance methods to this Device instance. .. warning:: Experimental. :param new_property: name/class pairs that will create resource-managers bound as instance attributes to this Device instance. See code example above """ if len(vargs): for fn in vargs: # check for name clashes before binding if hasattr(self, fn.__name__): raise ValueError( "request attribute name %s already exists" % fn.__name__) for fn in vargs: # bind as instance method, majik. if sys.version < '3': self.__dict__[ fn.__name__] = types.MethodType( fn, self, self.__class__) else: self.__dict__[ fn.__name__] = types.MethodType( fn, self.__class__) return # first verify that the names do not conflict with # existing object attribute names for name in kvargs.keys(): # check for name-clashes before binding if hasattr(self, name): raise ValueError( "requested attribute name %s already exists" % name) # now instantiate items and bind to this :Device: for name, thing in kvargs.items(): new_inst = thing(self) self.__dict__[name] = new_inst self._manages.append(name) @property def _sshconf_path(self): return self._sshconf_lkup() # ------------------------------------------------------------------------ # probe # ------------------------------------------------------------------------ def probe(self, timeout=5, intvtimeout=1): """ Probe the device to determine if the Device can accept a remote connection. This method is meant to be called *prior* to :open(): This method will not work with ssh-jumphost environments. :param int timeout: The probe will report ``True``/``False`` if the device report connectivity within this timeout (seconds) :param int intvtimeout: Timeout interval on the socket connection. Generally you should not change this value, but you can if you want to twiddle the frequency of the socket attempts on the connection :returns: ``True`` if probe is successful, ``False`` otherwise """ start = datetime.datetime.now() end = start + datetime.timedelta(seconds=timeout) probe_ok = True while datetime.datetime.now() < end: s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.settimeout(intvtimeout) try: s.connect((self.hostname, int(self._port))) s.shutdown(socket.SHUT_RDWR) s.close() break except: time.sleep(1) pass else: 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 :-) # ------------------------------------------------------------------------ def cli(self, command, format='text', warning=True): """ Executes the CLI command and returns the CLI text output by default. :param str command: The CLI command to execute, e.g. "show version" :param str format: The return format, by default is text. You can optionally select "xml" to return the XML structure. .. 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 ````. .. 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. .. warning:: You cannot use "pipe" filters with **command** such as ``| match`` or ``| count``, etc. The only value use of the "pipe" is for the ``| display xml rpc`` as noted above. """ if 'display xml rpc' not in command and warning is True: # 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=command, format=format) if isinstance(rsp, dict) and format.lower() == 'json': return rsp # rsp returned True means is empty, hence return # empty str as would be the case on cli # ex: # # if rsp is True: return '' if rsp.tag in ['output', 'rpc-reply']: encode = None if sys.version < '3' else 'unicode' return etree.tostring(rsp, method="text", with_tail=False, encoding=encode) if rsp.tag == 'configuration-information': return rsp.findtext('configuration-output') if rsp.tag == 'rpc': return rsp[0] return rsp except EzErrors.ConnectClosedError as ex: raise ex except EzErrors.RpcError as ex: return "invalid command: %s: %s" % (command, ex) except Exception as ex: return "invalid command: " + command # ------------------------------------------------------------------------ # execute # ------------------------------------------------------------------------ @normalizeDecorator @timeoutDecorator def execute(self, rpc_cmd, ignore_warning=False, **kvargs): """ Executes an XML RPC and returns results as either XML or native python :param rpc_cmd: can either be an XML Element or xml-as-string. In either case the command starts with the specific command element, i.e., not the 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 will be passed to this function as well in the form:: to_py( self, rpc_rsp, **kvargs ) :raises ValueError: When the **rpc_cmd** is of unknown origin :raises PermissionError: When the requested RPC command is not allowed due to user-auth class privilege controls on Junos :raises RpcError: 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 that function is called, and return of that function is provided back to the caller; presumably to convert the XML to native python data-types (e.g. ``dict``). """ if self.connected is not True: raise EzErrors.ConnectClosedError(self) if isinstance(rpc_cmd, str): rpc_cmd_e = etree.XML(rpc_cmd) elif isinstance(rpc_cmd, etree._Element): rpc_cmd_e = rpc_cmd else: raise ValueError( "Dont know what to do with rpc of type %s" % rpc_cmd.__class__.__name__) # invoking a bad RPC will cause a connection object exception # will will be raised directly to the caller ... for now ... # @@@ need to trap this and re-raise accordingly. try: 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 ex: if hasattr(ex, 'xml'): rsp = JXML.remove_namespaces(ex.xml) message = rsp.findtext('error-message') # see if this is a permission error if message and message == 'permission denied': raise EzErrors.PermissionError(cmd=rpc_cmd_e, rsp=rsp, errs=ex) else: rsp = None raise EzErrors.RpcError(cmd=rpc_cmd_e, rsp=rsp, errs=ex) # Something unexpected happened - raise it up except Exception as err: warnings.warn("An unknown exception occured - please report.", RuntimeWarning) raise # From 14.2 onward, junos supports JSON, so now code can be written as # dev.rpc.get_route_engine_information({'format': 'json'}) # should not convert rpc response to json when loading json config # as response should be rpc-reply xml object. if (rpc_cmd_e.tag != 'load-configuration' and rpc_cmd_e.attrib.get('format') in ['json', 'JSON']): ver_info = self.facts.get('version_info') if ver_info and ver_info.major[0] >= 15 or \ (ver_info.major[0] == 14 and ver_info.major[1] >= 2): try: return json.loads(rpc_rsp_e.text) except ValueError as ex: # when data is {}{.*} types if str(ex).startswith('Extra data'): return json.loads( re.sub('\s?{\s?}\s?', '', rpc_rsp_e.text)) else: raise JSONLoadError(ex, rpc_rsp_e.text) else: warnings.warn("Native JSON support is only from 14.2 onwards", RuntimeWarning) # This section is here for the possible use of something other than # ncclient for RPCs that have embedded rpc-errors, need to check for # those now. # rpc_errs = rpc_rsp_e.xpath('.//rpc-error') # if len(rpc_errs): # raise EzErrors.RpcError(cmd=rpc_cmd_e, rsp=rpc_errs[0]) # skip the element and pass the caller first child element # generally speaking this is what they really want. If they want to # uplevel they can always call the getparent() method on it. try: ret_rpc_rsp = rpc_rsp_e[0] except IndexError: # For cases where reply are like # # protocol: operation-failed # error: device asdf not found # if rpc_rsp_e.text is not None and rpc_rsp_e.text.strip() is not '': return rpc_rsp_e # no children, so assume it means we are OK return True # if the caller provided a "to Python" conversion function, then invoke # that now and return the results of that function. otherwise just # return the RPC results as XML if kvargs.get('to_py'): return kvargs['to_py'](self, ret_rpc_rsp, **kvargs) else: return ret_rpc_rsp # ------------------------------------------------------------------------ # facts # ------------------------------------------------------------------------ def facts_refresh(self, exception_on_failure=False, warnings_on_failure=None, keys=None): """ Refresh the facts from the Junos device into :attr:`facts` property. See :module:`jnpr.junos.facts` for a complete list of available facts. For old-style facts, this causes all facts to be immediately reloaded. For new-style facts, the current fact value(s) are deleted, and the fact is reloaded on demand. :param bool exception_on_failure: To raise exception when facts gathering errors out. If True when new-style fact gathering is in use, causes all facts to be reloaded rather than being loaded on demand. :param bool warnings_on_failure: To print a warning when fact gathering errors out. The default for old-style facts gathering is warnings_on_failure=True. The default for new-style facts gathering is warnings_on_failure=False. If True when new-style fact gathering is in use, causes all facts to be reloaded rather than being loaded on demand. :param str, set, list, or tuple keys: The set of keys in facts to refresh. Note: Old-style facts gathering does not support gathering individual facts, so this argument can only be specified when new-style fact gathering is in use. In addition, setting exception_on_failure or warnings_on_failure to True causes all facts to be immediately refreshed, rather than being refreshed on demand. For this reason, the keys argument can not be specified if exception_on_failure or warnings_on_failure are True. An example of specifying the keys argument as a string: ``` dev.facts_refresh(keys='hostname') ``` An example of specifying the keys argument as a tuple: ``` dev.facts_refresh(keys=('hostname', 'hostname_info', 'domain', 'fqdn')) ``` or as a list: ``` dev.facts_refresh(keys=['hostname', 'hostname_info', 'domain', 'fqdn']) ``` or as a set: ``` dev.facts_refresh(keys={'hostname', 'hostname_info', 'domain', 'fqdn'}) ``` :raises RuntimeError: If old-style fact gathering is in use and a keys argument is specified. """ if self._fact_style not in ['old', 'new', 'both']: raise RuntimeError("Unknown fact_style: %s" % (self._fact_style)) if self._fact_style == 'old' or self._fact_style == 'both': if warnings_on_failure is None: warnings_on_failure = True if keys is not None: raise RuntimeError("The keys argument can not be specified " "when old-style fact gathering is in use!") should_warn = False for gather in FACT_LIST: try: gather(self, self._ofacts) except: if exception_on_failure: raise should_warn = True if (warnings_on_failure is True and should_warn is True and self._fact_style != 'both'): warnings.warn('Facts gathering is incomplete. ' 'To know the reason call ' '"dev.facts_refresh(exception_on_failure=True)"', RuntimeWarning) if self._fact_style == 'new' or self._fact_style == 'both': if warnings_on_failure is None: warnings_on_failure = False self.facts._refresh(exception_on_failure=exception_on_failure, warnings_on_failure=warnings_on_failure, keys=keys) return # ----------------------------------------------------------------------- # OVERLOADS # ----------------------------------------------------------------------- def __repr__(self): return "Device(%s)" % self.hostname class DeviceSessionListener(SessionListener): """ Listens to Session class of Netconf Transport and detects errors in the transport. """ def __init__(self, device): self._device = device def callback(self, root, raw): """Required by implementation but not used here.""" pass def errback(self, ex): """Called when an error occurs. Set the device's connected status to False. :type ex: :exc:`Exception` """ self._device.connected = False class Device(_Connection): """ Junos Device class. :attr:`ON_JUNOS`: **READ-ONLY** - Auto-set to ``True`` when this code is running on a Junos device, vs. running on a local-server remotely connecting to a device. :attr:`auto_probe`: When non-zero the call to :meth:`open` will probe for NETCONF reachability before proceeding with the NETCONF session establishment. If you want to enable this behavior by default, you could do the following in your code:: from jnpr.junos import Device # set all device open to auto-probe with timeout of 10 sec Device.auto_probe = 10 dev = Device( ... ) dev.open() # this will probe before attempting NETCONF connect """ ON_JUNOS = platform.system().upper() == 'JUNOS' or \ platform.release().startswith('JNPR') auto_probe = 0 # default is no auto-probe # ------------------------------------------------------------------------- # PROPERTIES # ------------------------------------------------------------------------- # ------------------------------------------------------------------------ # property: transform # ------------------------------------------------------------------------ @property def transform(self): """ :returns: the current RPC XML Transformation. """ return self._conn._device_handler.transform_reply @transform.setter def transform(self, func): """ Used to change the RPC XML Transformation. :param lambda value: New transform lambda """ self._conn._device_handler.transform_reply = func # ----------------------------------------------------------------------- # CONSTRUCTOR # ----------------------------------------------------------------------- def __new__(cls, *args, **kwargs): if kwargs.get('port') in [23, '23'] or kwargs.get('mode'): from jnpr.junos.console import Console instance = object.__new__(Console, *args, **kwargs) # Python only calls __init__() if the object returned from # __new__() is an instance of the class in which the __new__() # method is contained (here Device class). Hence calling __init__ # explicitly. kwargs['host'] = args[0] if len(args) else kwargs.get('host') instance.__init__(**kwargs) return instance else: if sys.version < '3': return super(Device, cls).__new__(cls, *args, **kwargs) else: return super().__new__(cls) def __init__(self, *vargs, **kvargs): """ Device object constructor. :param str vargs[0]: host-name or ipaddress. This is an alternative for **host** :param str host: **REQUIRED** host-name or ipaddress of target device :param str user: *OPTIONAL* login user-name, uses $USER if not provided :param str passwd: *OPTIONAL* if not provided, assumed ssh-keys are enforced :param int port: *OPTIONAL* NETCONF port (defaults to 830) :param bool gather_facts: *OPTIONAL* For ssh mode default is ``True``. In case of console connection over telnet/serial it 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 str mode: *OPTIONAL* mode, mode for console connection (telnet/serial) :param int baud: *OPTIONAL* baud, Used during serial console mode, default baud rate is 9600 :param int attempts: *OPTIONAL* attempts, for console connection. default is 10 :param bool auto_probe: *OPTIONAL* if non-zero then this enables auto_probe at time of :meth:`open` and defines the amount of time(sec) for the probe timeout :param str ssh_private_key_file: *OPTIONAL* The path to the SSH private key file. This can be used if you need to provide a private key rather than loading the key into the ssh-key-ring/environment. if your ssh-key requires a password, then you must provide it via **passwd** :param str ssh_config: *OPTIONAL* The path to the SSH configuration file. This can be used to load SSH information from a configuration file. By default ~/.ssh/config is queried. :param bool normalize: *OPTIONAL* default is ``False``. If ``True`` then the XML returned by :meth:`execute` will have whitespace normalized """ # ---------------------------------------- # setup instance connection/open variables # ---------------------------------------- hostname = vargs[0] if len(vargs) else kvargs.get('host') self._port = kvargs.get('port', 830) self._gather_facts = kvargs.get('gather_facts', True) self._normalize = kvargs.get('normalize', False) self._auto_probe = kvargs.get('auto_probe', self.__class__.auto_probe) 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) if self.__class__.ON_JUNOS is True and hostname is None: # --------------------------------- # running on a Junos device locally # --------------------------------- self._auth_user = None self._auth_password = None self._hostname = 'localhost' self._ssh_private_key_file = None self._ssh_config = None else: # -------------------------- # making a remote connection # -------------------------- if hostname is None: raise ValueError("You must provide the 'host' value") self._hostname = hostname # user will default to $USER self._auth_user = os.getenv('USER') self._conf_auth_user = None self._conf_ssh_private_key_file = None # user can get updated by ssh_config self._ssh_config = kvargs.get('ssh_config') self._sshconf_lkup() # but if user or private key is explicit from call, then use it. self._auth_user = kvargs.get('user') or self._conf_auth_user or \ self._auth_user self._ssh_private_key_file = kvargs.get('ssh_private_key_file') \ or self._conf_ssh_private_key_file self._auth_password = kvargs.get( 'password') or kvargs.get('passwd') # ----------------------------- # initialize instance variables # ------------------------------ self._conn = None self._j2ldr = _Jinja2ldr self._manages = [] self._ofacts = {} # public attributes self.connected = False self.rpc = _RpcMetaExec(self) if self._fact_style == 'old': self.facts = self.ofacts else: self.facts = _FactCache(self) # ----------------------------------------------------------------------- # Basic device methods # ----------------------------------------------------------------------- @property def connected(self): return self._connected @connected.setter def connected(self, value): if value in [True, False]: self._connected = value 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 :param bool auto_probe: If non-zero then this enables auto_probe and defines the amount of time/seconds for the probe timeout :param bool normalize: If set to ``True``/``False`` will override the device instance value for only this open process :returns Device: Device instance (*self*). :raises ProbeError: When **auto_probe** is ``True`` and the probe activity exceeds the timeout :raises ConnectAuthError: When provided authentication credentials fail to login :raises ConnectRefusedError: When the device does not have NETCONF enabled :raises ConnectTimeoutError: When the the :meth:`Device.timeout` value is exceeded during the attempt to connect to the remote device :raises ConnectError: When an error, other than the above, occurs. The originating ``Exception`` is assigned as ``err._orig`` and re-raised to the caller. """ auto_probe = kvargs.get('auto_probe', self._auto_probe) if auto_probe is not 0: if not self.probe(auto_probe): raise EzErrors.ProbeError(self) try: ts_start = datetime.datetime.now() # we want to enable the ssh-agent if-and-only-if we are # not given a password or an ssh key file. # in this condition it means we want to query the agent # for available ssh keys allow_agent = bool((self._auth_password is None) and (self._ssh_private_key_file is None)) # open connection using ncclient transport self._conn = netconf_ssh.connect( host=self._hostname, port=self._port, username=self._auth_user, password=self._auth_password, hostkey_verify=False, key_filename=self._ssh_private_key_file, allow_agent=allow_agent, ssh_config=self._sshconf_lkup(), device_params={'name': 'junos', 'local': False}) self._conn._session.add_listener(DeviceSessionListener(self)) except NcErrors.AuthenticationError as err: # bad authentication credentials raise EzErrors.ConnectAuthError(self) except NcErrors.SSHError as err: # this is a bit of a hack for now, since we want to # know if the connection was refused or we simply could # not open a connection due to reachability. so using # a timestamp to differentiate the two conditions for now # if the diff is < 3 sec, then assume the host is # reachable, but NETCONF connection is refushed. ts_err = datetime.datetime.now() diff_ts = ts_err - ts_start if diff_ts.seconds < 3: raise EzErrors.ConnectRefusedError(self) # at this point, we assume that the connection # has timeed out due to ip-reachability issues if str(err).find('not open') > 0: raise EzErrors.ConnectTimeoutError(self) else: # otherwise raise a generic connection # error for now. tag the new exception # with the original for debug cnx = EzErrors.ConnectError(self) cnx._orig = err raise cnx except socket.gaierror: # invalid DNS name, so unreachable raise EzErrors.ConnectUnknownHostError(self) except Exception as err: # anything else, we will re-raise as a # generic ConnectError cnx_err = EzErrors.ConnectError(self) cnx_err._orig = err raise cnx_err self.connected = 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: self.facts_refresh() return self def close(self): """ Closes the connection to the device only if connected. """ if self.connected is True: self._conn.close_session() self.connected = False @ignoreWarnDecorator def _rpc_reply(self, rpc_cmd_e): return self._conn.rpc(rpc_cmd_e)._NCElement__doc # ----------------------------------------------------------------------- # Context Manager # ----------------------------------------------------------------------- def __enter__(self): self.open() return self def __exit__(self, exc_type, exc_val, exc_tb): if self._conn.connected and \ not isinstance(exc_val, EzErrors.ConnectError): self.close() junos-eznc-2.1.7/lib/jnpr/junos/exception.py0000644001013500016170000002167113163777563020500 0ustar stacys00000000000000import re from jnpr.junos import jxml from jnpr.junos import jxml as JXML from lxml.etree import _Element from ncclient.operations.rpc import RPCError class FactLoopError(RuntimeError): """ Generated when there is a loop in fact gathering. """ pass class RpcError(Exception): """ Parent class for all junos-pyez RPC Exceptions """ def __init__(self, cmd=None, rsp=None, errs=None, dev=None, timeout=None, re=None): """ :cmd: is the rpc command :rsp: is the rpc response (after ) :errs: is a list of dictionaries of extracted elements. :dev: is the device rpc was executed on :timeout: is the timeout value of the device :re: is the RE or member exception occured on """ self.cmd = cmd self.rsp = rsp self.dev = dev self.timeout = timeout self.re = re self.rpc_error = None self.xml = rsp # To handle errors coming from ncclient, Here errs is list of RPCError if isinstance(errs, RPCError) and hasattr(errs, 'errors'): self.errs = [JXML.rpc_error(error.xml) for error in errs.errors] for error in errs.errors: if error.severity == 'error': self.rsp = JXML.remove_namespaces(error.xml) break else: if errs.severity == 'warning': for error in errs.errors: if error.severity == 'warning': self.rsp = JXML.remove_namespaces(error.xml) break self.message = errs.message else: self.errs = errs self.message = "\n".join(["%s: %s" % (err['severity'].strip(), err['message'].strip()) for err in errs if err['message'] is not None and err['severity'] is not None]) \ if isinstance(errs, list) else '' if isinstance(self.rsp, _Element): self.rpc_error = jxml.rpc_error(self.rsp) self.message = self.message or self.rpc_error['message'] if self.errs is None or not isinstance(self.errs, list): self.errs = [self.rpc_error] def __repr__(self): """ pprints the response XML attribute """ if self.rpc_error is not None: return "{0}(severity: {1}, bad_element: {2}, message: {3})"\ .format(self.__class__.__name__, self.rpc_error['severity'], self.rpc_error['bad_element'], self.message) else: return self.__class__.__name__ __str__ = __repr__ class CommitError(RpcError): """ Generated in response to a commit-check or a commit action. """ def __init__(self, rsp, cmd=None, errs=None): RpcError.__init__(self, cmd, rsp, errs) def __repr__(self): return "{0}(edit_path: {1}, bad_element: {2}, message: {3})"\ .format(self.__class__.__name__, self.rpc_error['edit_path'], self.rpc_error['bad_element'], self.message) __str__ = __repr__ class ConfigLoadError(RpcError): """ Generated in response to a failure when loading a configuration. """ def __init__(self, rsp, cmd=None, errs=None): RpcError.__init__(self, cmd, rsp, errs) def __repr__(self): return "{0}(severity: {1}, bad_element: {2}, message: {3})"\ .format(self.__class__.__name__, self.rpc_error['severity'], self.rpc_error['bad_element'], self.message) __str__ = __repr__ class LockError(RpcError): """ Generated in response to attempting to take an exclusive lock on the configuration database. """ def __init__(self, rsp): RpcError.__init__(self, rsp=rsp) class UnlockError(RpcError): """ Generated in response to attempting to unlock the configuration database. """ def __init__(self, rsp): RpcError.__init__(self, rsp=rsp) class PermissionError(RpcError): """ Generated in response to invoking an RPC for which the auth user does not have user-class permissions. PermissionError.message gives you the specific RPC that cause the exceptions """ def __init__(self, rsp, cmd=None, errs=None): RpcError.__init__(self, cmd=cmd, rsp=rsp, errs=errs) self.message = rsp.findtext('.//bad-element') class RpcTimeoutError(RpcError): """ Generated in response to a RPC execution timeout. """ def __init__(self, dev, cmd, timeout): RpcError.__init__(self, dev=dev, cmd=cmd, timeout=timeout) def __repr__(self): return "{0}(host: {1}, cmd: {2}, timeout: {3})"\ .format(self.__class__.__name__, self.dev.hostname, self.cmd, self.timeout) __str__ = __repr__ class SwRollbackError(RpcError): """ Generated in response to a SW rollback error. """ def __init__(self, rsp, re=None): RpcError.__init__(self, re=re, rsp=rsp) def __repr__(self): if self.re: return "{0}(re: {1}, output: {2})"\ .format(self.__class__.__name__, self.re, self.rsp) else: return "{0}(output: {1})".format(self.__class__.__name__, self.rsp) __str__ = __repr__ # ================================================================ # ================================================================ # Connection Exceptions # ================================================================ # ================================================================ class ConnectError(Exception): """ Parent class for all connection related exceptions """ def __init__(self, dev, msg=None): self.dev = dev self._orig = msg @property def user(self): """ login user-name """ return self.dev.user @property def host(self): """ login host name/ipaddr """ return self.dev.hostname @property def port(self): """ login SSH port """ return self.dev._port @property def msg(self): """ login SSH port """ return self._orig def __repr__(self): if self._orig: return "{0}(host: {1}, msg: {2})".format(self.__class__.__name__, self.dev.hostname, self._orig) else: return "{0}({1})".format(self.__class__.__name__, self.dev.hostname) __str__ = __repr__ class ProbeError(ConnectError): """ Generated if auto_probe is enabled and the probe action fails """ pass class ConnectAuthError(ConnectError): """ Generated if the user-name, password is invalid """ pass class ConnectTimeoutError(ConnectError): """ Generated if the NETCONF session fails to connect, could be due to the fact the device is not ip reachable; bad ipaddr or just due to routing """ pass class ConnectUnknownHostError(ConnectError): """ Generated if the specific hostname does not DNS resolve """ pass class ConnectRefusedError(ConnectError): """ Generated if the specified host denies the NETCONF; could be that the services is not enabled, or the host has too many connections already. """ pass class ConnectNotMasterError(ConnectError): """ Generated if the connection is made to a non-master routing-engine. This could be a backup RE on an MX device, or a virtual-chassis member (linecard), for example """ pass class ConnectClosedError(ConnectError): """ Generated if connection unexpectedly closed """ def __init__(self, dev): ConnectError.__init__(self, dev=dev) dev.connected = False class JSONLoadError(Exception): """ Generated if json content of rpc reply fails to load """ def __init__(self, exception, rpc_content): self.ex_msg = str(exception) self.rpc_content = rpc_content self.offending_line = '' obj = re.search('line (\d+)', self.ex_msg) if obj: line_no = int(obj.group(1)) rpc_lines = rpc_content.splitlines() for line in range(line_no-3, line_no+2): self.offending_line += '%s: %s\n' % (line+1, rpc_lines[line]) def __repr__(self): if self.offending_line: return "{0}(reason: {1}, \nThe offending config appears " \ "to be: \n{2})".format(self.__class__.__name__, self.ex_msg, self.offending_line) else: return "{0}(reason: {1})" \ .format(self.__class__.__name__, self.ex_msg) __str__ = __repr__ junos-eznc-2.1.7/lib/jnpr/junos/factcache.py0000644001013500016170000003071313163777563020400 0ustar stacys00000000000000import collections import warnings from pprint import pformat import jnpr.junos.facts from jnpr.junos.facts import __doc__ as facts_doc import jnpr.junos.exception class _FactCache(collections.MutableMapping): """ A dictionary-like object which performs on-demand fact gathering. This class should not be used directly. An instance of this class is available as the :attr:`facts` attribute of a Device object. **Dictionary magic methods:** * :meth:`__getitem__`: Gets the value of a given key in the dict. * :meth:`__delitem__`: Called when a key is deleted from the dict. * :meth:`__setitem__`: Called when a key is set on the dict. * :meth:`__iter__`: Called when iterating over the keys of the dict. * :meth:`__len__`: Called when getting the length of the dict. * :meth:`__repr__`: Called when representing the dict as a string. **Additional methods:** * :meth:`_refresh`: Refreshes the fact cache. """ def __init__(self, device): """ _FactCache object constructor. :param device: The device object for which fact gathering will be performed. """ self._device = device self._cache = dict() self._call_stack = list() self._callbacks = jnpr.junos.facts._callbacks self._exception_on_failure = False self._warnings_on_failure = False self._should_warn = False def __getitem__(self, key): """ Return the value of a particular key in the dictionary. If the fact has already been cached, the value is simply returned from the cache. If the value has not been cached, then the appropriate callback function is invoked to gather the fact from the device. The value is cached, and then returned. If _warnings_on_failure is True, then a warning is logged if there is an error gathering a fact from the device. :param key: The key who's value is returned. :returns value: The value of the key fact. If key is a known fact, but there is an error gathering the fact, then the value None is returned. :raises KeyError: When key is not a known fact (there is no callback present to gather the fact.) :raises jnpr.junos.exception.FactLoopError: When there is a loop attempting to gather the fact. :raises other exceptions as defined by the fact gathering modules: When an error is encountered and _exception_on_failure is True. """ if key not in self._callbacks: # Not a fact that we know how to provide. raise KeyError('%s: There is no function to gather the %s fact' % (key, key)) if key not in self._cache: # A known fact, but not yet cached. Go get it and cache it. if self._callbacks[key] in self._call_stack: raise jnpr.junos.exception.FactLoopError( "A loop was detected while gathering the %s fact. The %s " "module has already been called. Please report this error." % (key, self._callbacks[key].__module__)) else: # Add the callback we are about to invoke to the _call_stack in # order to detect loops in fact gathering. self._call_stack.append(self._callbacks[key]) try: # Invoke the callback new_facts = self._callbacks[key](self._device) except jnpr.junos.exception.FactLoopError: raise except Exception: # An exception was raised. No facts were returned. # Raise the exception to the user? if self._exception_on_failure: raise # Warn the user? if self._warnings_on_failure: self._should_warn = True # Set all of the facts which should have been returned # by this callback to the default value of None. for new_key in self._callbacks: if self._callbacks[key] is self._callbacks[new_key]: self._cache[new_key] = None else: # No exception for new_key in new_facts: if (new_key not in self._callbacks or self._callbacks[key] is not self._callbacks[new_key]): # The callback returned a fact it didn't advertise raise RuntimeError("The %s module returned the %s " "fact, but does not list %s as a " "provided fact. Please report this " "error." % (self._callbacks[key].__module__, new_key, new_key)) else: # Cache the returned fact self._cache[new_key] = new_facts[new_key] finally: # Always pop the current callback from _call_stack, # regardless of whether or not an exception was raised. self._call_stack.pop() if key in self._cache: # key fact is cached. Return it. if self._device._fact_style == 'both': # Compare old and new-style values. if key in self._device._ofacts: # Skip key comparisons for certain keys. # # The old facts gathering code has an up_time key. # The new facts gathering code maintains this key for RE0 # and RE1 facts, but it's not comparable (because it # depends on when the fact was gathered and is therefore # not really a "fact".) The new re_info fact omits the # up_time field for this reason. # # The old facts gathering code didn't return a correct # value for the master fact when the system was a VC. # The new fact gathering code still returns the master fact # but returns a correct value for VCs. It also returns a # new re_master fact which is much more useful. if key not in ['RE0', 'RE1', 'master']: if self._cache[key] != self._device._ofacts[key]: warnings.warn('New and old-style facts do not ' 'match for the %s fact.\n' ' New-style value: %s\n' ' Old-style value: %s\n' % (key, self._cache[key], self._device._ofacts[key]), RuntimeWarning) return self._cache[key] else: # key fact was not returned by callback raise RuntimeError("The %s module claims to provide the %s " "fact, but failed to return it. Please report " "this error." % (self._callbacks[key].__module__, key)) def __delitem__(self, key): """ Facts are read-only. Don't allow deleting an item. """ raise RuntimeError("facts are read-only!") def __setitem__(self, key, value): """ Facts are read-only. Don't allow setting an item. """ raise RuntimeError("facts are read-only!") def __iter__(self): """ An iterator of known facts. :returns iterator: of all of the 'non-hidden' facts we know how to gather, regardless of whether or not they've already been cached. Fact names which are hidden start with an underscore and are not returned. """ callbacks = {} for key in self._callbacks: if not key.startswith('_'): callbacks[key] = self._callbacks[key] return iter(callbacks) def __len__(self): """ The length of all known facts. :returns length: of all of the facts we know how to gather, regardless of whether or not they've already been cached. """ return len(self._callbacks) def __str__(self): """ A string representation of the facts dictionary. :returns string: a string representation of the dictionary. Because this returns the value of every fact, it has the side-effect of causing any ungathered facts to be gathered and then cached. """ string = '' for key in sorted(self): if not key.startswith('_'): current = "'%s': %s" % (key, repr(self.get(key))) if string: string = ', '.join([string, current]) else: string = current return '{' + string + '}' def __repr__(self): """ A formated string representation of the facts dictionary. :returns string: a formated string representation of the dictionary. Because this returns the value of every fact, it has the side-effect of causing any ungathered facts to be gathered and then cached. """ return pformat(dict(self)) def _refresh(self, exception_on_failure=False, warnings_on_failure=False, keys=None): """ Empty the cache to force a refresh of one or more facts. Empties the fact gathering cache for all keys (if keys == None) or a set of keys. This causes the fact to be gathered and cached upon next access. If either eception_on_failure or warnings_on_failure is true, then all facts are accessed by getting the string representation of the facts. This causes all facts to immediately be populated so that any exceptions or warnings are generated during the call to _refresh(). :param exception_on_failure: A boolean which indicates if an exception should be raised upon a failure gathering facts. :param warnings_on_failure: A boolean which indicates if an warning should be logged upon a failure gathering facts. :param keys: A single key as a string, or an iterable of keys (such as a list, set, or or tuple.) The specified keys are emptied from the cache. If None, all keys are emptied from the cache. :raises RuntimeError: When keys contains an unknown fact. """ refresh_keys = None if keys is not None: if isinstance('str', type(keys)): refresh_keys = (keys,) else: refresh_keys = keys if refresh_keys is not None: for key in refresh_keys: if key in self._callbacks: if key in self._cache: del self._cache[key] else: raise RuntimeError('The %s fact can not be refreshed. %s ' 'is not a known fact.' % (key, key)) else: self._cache = dict() if exception_on_failure or warnings_on_failure: self._exception_on_failure = exception_on_failure self._warnings_on_failure = warnings_on_failure try: str(self._device.facts) except Exception: if exception_on_failure: raise finally: if warnings_on_failure and self._should_warn: warnings.warn('Facts gathering is incomplete. ' 'To know the reason call ' '"dev.facts_refresh(' 'exception_on_failure=True)"', RuntimeWarning) self._exception_on_failure = False self._warnings_on_failure = False self._should_warn = False # Precede the class's documentation with the documentation on the specific # facts from the jnpr.junos.facts package. __doc__ = (facts_doc + "Implementation details on the _FactCache class:" + __doc__) junos-eznc-2.1.7/lib/jnpr/junos/factory/0000755001013500016170000000000013163777613017564 5ustar stacys00000000000000junos-eznc-2.1.7/lib/jnpr/junos/factory/__init__.py0000644001013500016170000000172213163777563021703 0ustar stacys00000000000000import yaml import os.path from jnpr.junos.factory.factory_loader import FactoryLoader __all__ = ['loadyaml', 'FactoryLoader'] def loadyaml(path): """ Load a YAML file at :path: that contains Table and View definitions. Returns a of item-name anditem-class definition. If you want to import these definitions directly into your namespace, (like a module) you would do the following: globals().update( loadyaml( )) If you did not want to do this, you can access the items as the . For example, if your YAML file contained a Table called MyTable, then you could do something like: catalog = loadyaml( ) MyTable = catalog['MyTable'] table = MyTable(dev) table.get() ... """ # if no extension is given, default to '.yml' if os.path.splitext(path)[1] == '': path += '.yml' return FactoryLoader().load(yaml.load(open(path, 'r'))) junos-eznc-2.1.7/lib/jnpr/junos/factory/cfgtable.py0000644001013500016170000006522513163777563021723 0ustar stacys00000000000000from 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 from jnpr.junos.utils.config import Config class CfgTable(Table): __isfrozen = False # ----------------------------------------------------------------------- # CONSTRUCTOR # ----------------------------------------------------------------------- def __init__(self, dev=None, xml=None, path=None, mode=None): Table.__init__(self, dev, xml, path) # call parent constructor self._init_get() self._data_dict = self.DEFINE # crutch self.ITEM_NAME_XPATH = self._data_dict.get('key', 'name') self.view = self._data_dict.get('view') self._options = self._data_dict.get('options') self.mode = mode if 'set' in self._data_dict: Config.__init__(self, dev, mode) # call parent constructor self._init_set() if self._view: self.fields = self._view.FIELDS.copy() else: raise ValueError( "%s set table view is not defined.\n" % (self.__class__.__name__) ) if 'key-field' in self._data_dict: key_name = self._data_dict.get('key-field', None) if isinstance(key_name, list): self.key_field = key_name elif isinstance(key_name, str): self.key_field = [key_name] else: raise TypeError( "Key-field %s is of invalid type %s.\n" % (key_name, type(key_name)) ) else: raise ValueError( "Table should have key-field attribute defined\n" ) self._type = 'set' self._init_field() else: self._type = 'get' self.ITEM_XPATH = self._data_dict[self._type] # no new attributes. self._freeze() # ----------------------------------------------------------------------- # 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 _init_get(self): self._get_xpath = None # for debug purposes self._get_cmd = None self._get_opt = None def _init_set(self): self._insert_node = None # lxml object of configuration xml self._config_xml_req = None # To check if field value is set. self._is_field_set = False # for debug purposes self._load_rsp = None self._commit_rsp = None def _buildxml(self, namesonly=False): """ Return an lxml Element starting with and comprised of the elements specified in the xpath expression. For example, and xpath = 'interfaces/interface' would produce: 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[self._type] 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 _build_config_xml(self, top): """ used to encode the field values into the configuration XML for set table,each of the field= pairs are defined by user: """ for field_name, opt in self.fields.items(): dot = top # create an XML element with the key/value field_value = getattr(self, field_name, None) # If field value is not set ignore it if field_value is None: continue if isinstance(field_value, (list, tuple, set)): [self._validate_value(field_name, v, opt) for v in field_value] else: self._validate_value(field_name, field_value, opt) field_dict = self.fields[field_name] if 'group' in field_dict: group_xpath = self._view.GROUPS[field_dict['group']] dot = self._encode_xpath(top, group_xpath.split('/')) lxpath = field_dict['xpath'].split('/') if len(lxpath) > 1: dot = self._encode_xpath(top, lxpath[0:len(lxpath) - 1]) add_field = self._grindfield(lxpath[-1], field_value) for _add in add_field: if len(_add.attrib) > 0: for i in dot.getiterator(): if i.tag == _add.tag: i.attrib.update(_add.attrib) break else: dot.append(_add) elif field_name in self.key_field: dot.insert(0, _add) else: dot.append(_add) def _validate_value(self, field_name, value, opt): """ Validate value set for field against the constraints and data type check define in yml table/view defination. :param field_name: Name of field as mentioned in yaml table/view :param value: Value set by user for field_name. :param opt: Dictionary of data type and constraint check. :return: """ def _get_field_type(ftype): ft = { 'str': str, 'int': int, 'float': float, 'bool': bool, }.get(ftype, None) if ft is None: raise TypeError("Unsupported type %s\n" % (ftype)) return ft def _validate_enum_value(field_name, value, enum_value): if isinstance(enum_value, list): if value not in enum_value: raise ValueError('Invalid value %s assigned ' 'to field %s' % (value, field_name)) elif isinstance(enum_value, str): if not value == enum_value: raise ValueError('Invalid value %s assigned ' 'to field %s' % (value, field_name)) else: raise TypeError('Value of enum should ' 'be either a string or list of strings.\n') def _validate_type(field_name, value, opt): if isinstance(opt['type'], dict): if 'enum' in opt['type']: _validate_enum_value(field_name, value, opt['type']['enum']) else: # More user defined type check can be added in future. # raise execption for now. raise TypeError("Unsupported type %s\n" % (opt['type'])) elif isinstance(opt['type'], str): field_type = _get_field_type(opt['type']) if not isinstance(value, field_type): raise TypeError( 'Invalid value %s asigned to field %s,' ' value should be of type %s\n' % (value, field_name, field_type) ) else: raise TypeError( 'Invalid value %s, should be either of' ' type string or dictionary.\n' % (opt['type']) ) def _validate_min_max_value(field_name, value, opt): if isinstance(value, (int, float)): if value < opt['minValue'] or value > opt['maxValue']: raise ValueError( 'Invalid value %s assigned ' 'to field %s.\n' % (value, field_name) ) elif isinstance(value, str): if len(value) < opt['minValue'] or \ len(value) > opt['maxValue']: raise ValueError( 'Invalid value %s assigned ' 'to field %s.\n' % (value, field_name) ) if isinstance(value, dict) and 'operation' in value: # in case user want to pass operation attr for ex: # pass elif isinstance(value, (list, tuple, dict, set)): raise ValueError("%s value is invalid %s\n" % (field_name, value)) else: if 'type' in opt: _validate_type(field_name, value, opt) if ('minValue' or 'maxValue') in opt: _validate_min_max_value(field_name, value, opt) 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 _grindfield(self, xpath, value): """ returns list of xml elements for field name-value pairs """ lst = [] if isinstance(value, (list, tuple, set)): for v in value: lst.append(E(xpath.replace('_', '-'), str(v))) elif isinstance(value, bool): if value is True: lst.append(E(xpath.replace('_', '-'))) elif value is False: lst.append(E(xpath.replace('_', '-'), {'operation': 'delete'})) elif isinstance(value, dict) and 'operation' in value: lst.append(E(xpath.replace('_', '-'), value)) else: lst.append(E(xpath.replace('_', '-'), str(value))) return lst def _encode_requiredkeys(self, get_cmd, kvargs): """ used to encode the required_keys values into the XML get-command. each of the required_key= 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 _encode_xpath(self, top, lst): """ Create xml element hierarchy for given field. Return container node to which field and its value will appended as child elements. """ dot = top for index in range(1, len(lst) + 1): xp = '/'.join(lst[0:index]) if not len(top.xpath(xp)): dot.append(E(lst[index - 1])) dot = dot.find(lst[index - 1]) return dot def _keyspec(self): """ returns tuple (keyname-xpath, item-xpath) """ return (self._data_dict.get('key', 'name'), self._data_dict[self._type]) def _init_field(self): """ Initialize fields of set table to it's default value (if mentioned in yml Table/View) else set to None. """ for fname, opt in self.fields.items(): self.__dict__[fname] = opt['default'] \ if 'default' in opt else None def _mandatory_check(self): """ Mandatory checks for set table/view """ for key in self.key_field: value = getattr(self, key) if value is None: raise ValueError("%s key-field value is not set.\n" % (key)) def _freeze(self): """ Freeze class object so that user cannot add new attributes (fields). """ self.__isfrozen = True def _unfreeze(self): """ Unfreeze class object, should be called from within class only. """ self.__isfrozen = False # ---------------------------------------------------------------------- # reset - Assign 'set' Table field values to default or None # ---------------------------------------------------------------------- def reset(self): """ Initialize fields of set table to it's default value (if mentioned in Table/View) else set to None. """ return self._init_field() # ---------------------------------------------------------------------- # get_table_xml - retrieve lxml configuration object for set table # ---------------------------------------------------------------------- def get_table_xml(self): """ It returns lxml object of configuration xml that is generated from table data (field=value) pairs. To get a valid xml this method should be used after append() is called. """ return self._config_xml_req # ---------------------------------------------------------------------- # append - append Table data to lxml configuration object # ---------------------------------------------------------------------- def append(self): """ It creates lxml nodes with field name as xml tag and its value given by user as text of xml node. The generated xml nodes are appended to configuration xml at appropriate hierarchy. .. warning:: xml node that are appended cannot be changed later hence care should be taken to assign correct value to table fields before calling append. """ # mandatory check for 'set' table fields self._mandatory_check() set_cmd = self._buildxml() top = set_cmd.find(self._data_dict[self._type]) self._build_config_xml(top) if self._config_xml_req is None: self._config_xml_req = set_cmd self._insert_node = top.getparent() else: self._insert_node.extend(top.getparent()) self.reset() # Reset field values self._is_field_set = False # ---------------------------------------------------------------------- # get - retrieve Table data # ---------------------------------------------------------------------- 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[self._type]) 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 # ----------------------------------------------------------------------- # set - configure Table data in running configuration. # ----------------------------------------------------------------------- def set(self, **kvargs): """ Load configuration data in running db. It performs following operation in sequence. * lock(): Locks candidate configuration db. * load(): Load structured configuration xml in candidate db. * commit(): Commit configuration to runnning db. * unlock(): Unlock candidate db. This method should be used after append() is called to get the desired results. :param bool overwrite: Determines if the contents completely replace the existing configuration. Default is ``False``. :param bool merge: If set to ``True`` will set the load-config action to merge. the default load-config action is 'replace' :param str comment: If provided logs this comment with the commit. :param int confirm: If provided activates confirm safeguard with provided value as timeout (minutes). :param int timeout: If provided the command will wait for completion using the provided value as timeout (seconds). By default the device timeout is used. :param bool sync: On dual control plane systems, requests that the candidate configuration on one control plane be copied to the other control plane, checked for correct syntax, and committed on both Routing Engines. :param bool force_sync: On dual control plane systems, forces the candidate configuration on one control plane to be copied to the other control plane. :param bool full: When true requires all the daemons to check and evaluate the new configuration. :param bool detail: When true return commit detail as XML :returns: Class object: :raises: ConfigLoadError: When errors detected while loading configuration. You can use the Exception errs variable to identify the specific problems CommitError: When errors detected in candidate configuration. You can use the Exception errs variable to identify the specific problems RuntimeError: If field value is set and append() is not invoked before calling this method, it will raise an exception with appropriate error message. .. warning:: If the function does not receive a reply prior to the timeout a RpcTimeoutError will be raised. It is possible the commit was successful. Manual verification may be required. """ if self._is_field_set: raise RuntimeError("Field value is changed, append() " "must be called before set()") self.lock() try: # Invoke config class load() api, with xml object. self._load_rsp = super(CfgTable, self).load(self._config_xml_req, **kvargs) self._commit_rsp = self.commit(**kvargs) finally: self.unlock() return self # ----------------------------------------------------------------------- # OVERLOADS # ----------------------------------------------------------------------- def __setitem__(self, t_field, value): """ implements []= to set Field value """ if t_field in self.fields: # pass 'up' to standard setattr method self.__setattr__(t_field, value) else: raise ValueError("Unknown field: %s" % (t_field)) def __setattr__(self, attribute, value): if self.__isfrozen and not hasattr(self, attribute): raise ValueError("Unknown field: %s" % (attribute)) else: # pass 'up' to standard setattr method object.__setattr__(self, attribute, value) if hasattr(self, 'fields') and attribute in self.fields: object.__setattr__(self, '_is_field_set', True) def __enter__(self): return super(CfgTable, self).__enter__() def __exit__(self, exc_type, exc_val, exc_tb): return super(CfgTable, self).__exit__(exc_type, exc_val, exc_tb) # ----------------------------------------------------------------------- # load - configure Table data in candidate configuration. # ----------------------------------------------------------------------- def load(self, **kvargs): """ Load configuration xml having table data (field=value) in candidate db. This method should be used after append() is called to get the desired results. :param bool overwrite: Determines if the contents completely replace the existing configuration. Default is ``False``. :param bool merge: If set to ``True`` will set the load-config action to merge. the default load-config action is 'replace' :returns: Class object. :raises: ConfigLoadError: When errors detected while loading configuration. You can use the Exception errs variable to identify the specific problems RuntimeError: If field value is set and append() is not invoked before calling this method, it will raise an exception with appropriate error message. """ if self._is_field_set: raise RuntimeError("Field value is changed, append() " "must be called before load()") # pass up to config class load() api, with xml object as vargs[0]. self._load_rsp = super(CfgTable, self).load(self._config_xml_req, **kvargs) return self junos-eznc-2.1.7/lib/jnpr/junos/factory/factory_cls.py0000644001013500016170000000562413163777563022461 0ustar stacys00000000000000# stdlib from copy import deepcopy # local from jnpr.junos.factory.cfgtable import CfgTable from jnpr.junos.factory.optable import OpTable from jnpr.junos.factory.table import Table from jnpr.junos.factory.view import View from jnpr.junos.factory.viewfields import ViewFields from jnpr.junos.utils.config import Config def FactoryCfgTable(table_name=None, data_dict={}): if table_name is None: table_name = "CfgTable" if 'set' in data_dict.keys(): new_cls = type(table_name, (CfgTable, Config), {}) else: new_cls = type(table_name, (CfgTable,), {}) new_cls.DEFINE = deepcopy(data_dict) new_cls.__module__ = __name__.replace('factory_cls', 'CfgTable') return new_cls def FactoryOpTable(cmd, args=None, args_key=None, item=None, key=OpTable.ITEM_NAME_XPATH, view=None, table_name=None): if table_name is None: table_name = "OpTable." + cmd new_cls = type(table_name, (OpTable,), {}) new_cls.GET_RPC = cmd new_cls.GET_ARGS = args or {} if args_key is not None: new_cls.GET_KEY = args_key new_cls.ITEM_XPATH = item new_cls.ITEM_NAME_XPATH = key new_cls.VIEW = view new_cls.__module__ = __name__.replace('factory_cls', 'OpTable') return new_cls def FactoryTable(item, key=Table.ITEM_NAME_XPATH, view=None, table_name=None): if table_name is None: table_name = 'Table.' + item new_cls = type(table_name, (Table,), {}) new_cls.ITEM_XPATH = item new_cls.ITEM_NAME_XPATH = key new_cls.VIEW = view new_cls.__module__ = __name__.replace('factory_cls', 'Table') return new_cls def FactoryView(fields, **kvargs): """ :fields: dictionary of fields, structure of which is ~internal~ and should not be defined explicitly. use the RunstatMaker.Fields() mechanism to create theserather than hardcoding the dictionary structures; since they might change over time. :kvargs: 'view_name' to name the class. this could be useful for debug or eventual callback mechanisms. 'groups' is a dict of name/xpath assocaited to fields this technique would be used to extract fields from node-set elements like port . 'extends' names the base View class to extend. using this technique you can add to existing defined Views. """ view_name = kvargs.get('view_name', 'RunstatView') new_cls = type(view_name, (View,), {}) if 'extends' in kvargs: base_cls = kvargs['extends'] new_cls.FIELDS = deepcopy(base_cls.FIELDS) new_cls.FIELDS.update(fields) if 'groups' in kvargs: new_cls.GROUPS = deepcopy(base_cls.GROUPS) new_cls.GROUPS.update(kvargs['groups']) else: new_cls.FIELDS = fields new_cls.GROUPS = kvargs['groups'] if 'groups' in kvargs else None new_cls.__module__ = __name__.replace('factory_cls', 'View') return new_cls junos-eznc-2.1.7/lib/jnpr/junos/factory/factory_loader.py0000644001013500016170000002360613163777563023146 0ustar stacys00000000000000""" This file contains the FactoryLoader class that is used to dynamically create Runstat Table and View objects from a of data. The can originate from any kind of source: YAML, JSON, program. For examples of YAML refer to the .yml files in this jnpr.junos.op directory. """ # stdlib from copy import deepcopy import re # locally from jnpr.junos.factory.factory_cls import * from jnpr.junos.factory.viewfields import * __all__ = ['FactoryLoader'] # internally used shortcuts _VIEW = FactoryView _FIELDS = ViewFields _GET = FactoryOpTable _TABLE = FactoryTable _CFGTBL = FactoryCfgTable class FactoryLoader(object): """ Used to load a of data that contains Table and View definitions. The primary method is :load(): which will return a of item-name and item-class definitions. If you want to import these definitions directly into your namespace, (like a module) you would do the following: loader = FactoryLoader() catalog = loader.load( ) globals().update( catalog ) If you did not want to do this, you can access the items as the catalog. For example, if your contained a Table called MyTable, then you could do something like: MyTable = catalog['MyTable'] table = MyTable(dev) table.get() ... """ def __init__(self): self._catalog_dict = None # YAML data self._item_optables = [] # list of the get/op-tables self._item_cfgtables = [] # list of get/cfg-tables self._item_views = [] # list of views to build self._item_tables = [] # list of tables to build self.catalog = {} # catalog of built classes # ----------------------------------------------------------------------- # Create a View class from YAML definition # ----------------------------------------------------------------------- def _fieldfunc_True(self, value_rhs): def true_test(x): if value_rhs.startswith('regex('): return True if bool(re.search(value_rhs.strip('regex()'), x)) else False return x == value_rhs return true_test def _fieldfunc_False(self, value_rhs): def false_test(x): if value_rhs.startswith('regex('): return False if bool(re.search(value_rhs.strip('regex()'), x)) else True return x != value_rhs return false_test def _add_dictfield(self, fields, f_name, f_dict, kvargs): """ add a field based on its associated dictionary """ # at present if a field is a then there is **one # item** - { the xpath value : the option control }. typically # the option would be a bultin class type like 'int' # however, as this framework expands in capability, this # will be enhaced, yo! xpath, opt = list(f_dict.items())[0] # get first/only key,value if opt == 'group': fields.group(f_name, xpath) return if 'flag' == opt: opt = 'bool' # flag is alias for bool # first check to see if the option is a built-in Python # type, most commonly would be 'int' for numbers, like counters if isinstance(opt, dict): kvargs.update(opt) fields.str(f_name, xpath, **kvargs) return astype = __builtins__.get(opt) or globals().get(opt) if astype is not None: kvargs['astype'] = astype fields.astype(f_name, xpath, **kvargs) return # next check to see if this is a "field-function" # operator in the form "func=value", like "True=enabled" if isinstance(opt, str) and opt.find('=') > 0: field_cmd, value_rhs = opt.split('=') fn_field = '_fieldfunc_' + field_cmd if not hasattr(self, fn_field): raise ValueError("Unknown field-func: '%'" % field_cmd) kvargs['astype'] = getattr(self, fn_field)(value_rhs) fields.astype(f_name, xpath, **kvargs) return raise RuntimeError("Dont know what to do with field: '%s'" % f_name) # ---[ END: _add_dictfield ] --------------------------------------------- def _add_view_fields(self, view_dict, fields_name, fields): """ add a group of fields to the view """ fields_dict = view_dict[fields_name] try: # see if this is a 'fields_' collection, and if so # then we automatically setup using the group mechanism mark = fields_name.index('_') group = {'group': fields_name[mark + 1:]} except: # otherwise, no group, just standard 'fields' group = {} for f_name, f_data in fields_dict.items(): # each field could have its own unique set of properties # so create a kvargs each time. but copy in the # groups (single item) generically. kvargs = {} kvargs.update(group) if isinstance(f_data, dict): self._add_dictfield(fields, f_name, f_data, kvargs) continue if f_data in self._catalog_dict: # f_data is the table name cls_tbl = self.catalog.get(f_data, self._build_table(f_data)) fields.table(f_name, cls_tbl) continue # if we are here, then it means that the field is a string value xpath = f_name if f_data is True else f_data fields.str(f_name, xpath, **kvargs) # ------------------------------------------------------------------------- def _build_view(self, view_name): """ build a new View definition """ if view_name in self.catalog: return self.catalog[view_name] view_dict = self._catalog_dict[view_name] kvargs = {'view_name': view_name} # if there are field groups, then get that now. if 'groups' in view_dict: kvargs['groups'] = view_dict['groups'] # if this view extends another ... if 'extends' in view_dict: base_cls = self.catalog.get(view_dict['extends']) # @@@ should check for base_cls is None! kvargs['extends'] = base_cls fields = _FIELDS() fg_list = [name for name in view_dict if name.startswith('fields')] for fg_name in fg_list: self._add_view_fields(view_dict, fg_name, fields) cls = _VIEW(fields.end, **kvargs) self.catalog[view_name] = cls return cls # ----------------------------------------------------------------------- # Create a Get-Table from YAML definition # ----------------------------------------------------------------------- def _build_optable(self, table_name): """ build a new Get-Table definition """ if table_name in self.catalog: return self.catalog[table_name] tbl_dict = self._catalog_dict[table_name] kvargs = deepcopy(tbl_dict) rpc = kvargs.pop('rpc') kvargs['table_name'] = table_name if 'view' in tbl_dict: view_name = tbl_dict['view'] cls_view = self.catalog.get(view_name, self._build_view(view_name)) kvargs['view'] = cls_view cls = _GET(rpc, **kvargs) self.catalog[table_name] = cls return cls # ----------------------------------------------------------------------- # Create a Table class from YAML definition # ----------------------------------------------------------------------- def _build_table(self, table_name): """ build a new Table definition """ if table_name in self.catalog: return self.catalog[table_name] tbl_dict = self._catalog_dict[table_name] table_item = tbl_dict.pop('item') kvargs = deepcopy(tbl_dict) kvargs['table_name'] = table_name if 'view' in tbl_dict: view_name = tbl_dict['view'] cls_view = self.catalog.get(view_name, self._build_view(view_name)) kvargs['view'] = cls_view cls = _TABLE(table_item, **kvargs) self.catalog[table_name] = cls return cls def _build_cfgtable(self, table_name): """ build a new Config-Table definition """ if table_name in self.catalog: return self.catalog[table_name] tbl_dict = self._catalog_dict[table_name] if 'view' in tbl_dict: # transpose name to class view_name = tbl_dict['view'] tbl_dict['view'] = self.catalog.get( view_name, self._build_view(view_name)) cls = _CFGTBL(table_name, tbl_dict) self.catalog[table_name] = cls return cls # ----------------------------------------------------------------------- # Primary builders ... # ----------------------------------------------------------------------- def _sortitems(self): for k, v in self._catalog_dict.items(): if 'rpc' in v: self._item_optables.append(k) elif 'get' in v: self._item_cfgtables.append(k) elif 'set' in v: self._item_cfgtables.append(k) elif 'view' in v: self._item_tables.append(k) else: self._item_views.append(k) def load(self, catalog_dict, envrion={}): # load the yaml data and extract the item names. these names will # become the new class definitions self._catalog_dict = catalog_dict self._sortitems() list(map(self._build_optable, self._item_optables)) list(map(self._build_cfgtable, self._item_cfgtables)) list(map(self._build_table, self._item_tables)) list(map(self._build_view, self._item_views)) return self.catalog junos-eznc-2.1.7/lib/jnpr/junos/factory/optable.py0000644001013500016170000000446613163777564021603 0ustar stacys00000000000000# 3rd-party from lxml import etree # local from jnpr.junos.factory.table import Table from jnpr.junos.jxml import remove_namespaces class OpTable(Table): # ------------------------------------------------------------------------- # PUBLIC METHODS # ------------------------------------------------------------------------- def get(self, *vargs, **kvargs): """ Retrieve the XML table data from the Device instance and returns back the Table instance - for call-chaining purposes. If the Table was created with a :path: rather than a Device, then this method will load the XML from that file. In this case, the \*vargs, and \**kvargs are not used. ALIAS: __call__ :vargs: [0] is the table :arg_key: value. This is used so that the caller can retrieve just one item from the table without having to know the Junos RPC argument. :kvargs: these are the name/value pairs relating to the specific Junos XML command attached to the table. For example, if the RPC is 'get-route-information', there are parameters such as 'table' and 'destination'. Any valid RPC argument can be passed to :kvargs: to further filter the results of the :get(): operation. neato! NOTES: If you need to create a 'stub' for unit-testing purposes, you want to create a subclass of your table and overload this methods. """ self._clearkeys() if self._path is not None: # for loading from local file-path self.xml = remove_namespaces(etree.parse(self._path).getroot()) return self if self._lxml is not None: return self argkey = vargs[0] if len(vargs) else None rpc_args = {'normalize': True} # create default rpc_args.update(self.GET_ARGS) # copy default args rpc_args.update(kvargs) # copy caller provided args if hasattr(self, 'GET_KEY') and argkey is not None: rpc_args.update({self.GET_KEY: argkey}) # execute the Junos RPC to retrieve the table self.xml = getattr(self.RPC, self.GET_RPC)(**rpc_args) # returning self for call-chaining purposes, yo! return self junos-eznc-2.1.7/lib/jnpr/junos/factory/table.py0000644001013500016170000002546713163777564021250 0ustar stacys00000000000000# stdlib from inspect import isclass from time import time from datetime import datetime import os # 3rd-party from lxml import etree import json from jnpr.junos.factory.to_json import TableJSONEncoder _TSFMT = "%Y%m%d%H%M%S" class Table(object): ITEM_XPATH = None ITEM_NAME_XPATH = 'name' VIEW = None def __init__(self, dev=None, xml=None, path=None): """ :dev: Device instance :xml: lxml Element instance :path: file path to XML, to be used rather than :dev: """ self._dev = dev self.xml = xml self.view = self.VIEW self._key_list = [] self._path = path self._lxml = xml # ------------------------------------------------------------------------- # PROPERTIES # ------------------------------------------------------------------------- @property def D(self): """ the Device instance """ return self._dev @property def RPC(self): """ the Device.rpc instance """ return self.D.rpc @property def view(self): """ returns the current view assigned to this table """ return self._view @view.setter def view(self, cls): """ assigns a new view to the table """ if cls is None: self._view = None return if not isclass(cls): raise ValueError("Must be given RunstatView class") self._view = cls @property def hostname(self): return self.D.hostname @property def is_container(self): """ True if this table does not have records, but is a container of fields False otherwise """ return self.ITEM_XPATH is None @property def key_list(self): """ the list of keys, as property for caching """ return self._key_list # ------------------------------------------------------------------------- # PRIVATE METHODS # ------------------------------------------------------------------------- def _assert_data(self): if self.xml is None: raise RuntimeError("Table is empty, use get()") def _tkey(self, this, key_list): """ keys with missing XPATH nodes are set to None """ keys = [] for k in key_list: try: keys.append(this.xpath(k)[0].text) except: keys.append(None) return tuple(keys) def _keys_composite(self, xpath, key_list): """ composite keys return a tuple of key-items """ return [self._tkey(item, key_list) for item in self.xml.xpath(xpath)] def _keys_simple(self, xpath): return [x.text.strip() for x in self.xml.xpath(xpath)] def _keyspec(self): """ returns tuple (keyname-xpath, item-xpath) """ return (self.ITEM_NAME_XPATH, self.ITEM_XPATH) def _clearkeys(self): self._key_list = [] # ------------------------------------------------------------------------- # PUBLIC METHODS # ------------------------------------------------------------------------- # ------------------------------------------------------------------------ # keys # ------------------------------------------------------------------------ def _keys(self): """ return a list of data item keys from the Table XML """ self._assert_data() key_value, xpath = self._keyspec() if isinstance(key_value, str): # Check if pipe is in the key_value, if so append xpath # to each value if ' | ' in key_value: return self._keys_simple(' | '.join([xpath + '/' + x for x in key_value.split(' | ')])) return self._keys_simple(xpath + '/' + key_value) if not isinstance(key_value, list): raise RuntimeError( "What to do with key, table:'%s'" % self.__class__.__name__) # ok, so it's a list, which means we need to extract tuple values return self._keys_composite(xpath, key_value) def keys(self): # if the key_list has been cached, then use it if len(self.key_list): return self.key_list # otherwise, build the list of keys into the cache self._key_list = self._keys() return self._key_list # ------------------------------------------------------------------------ # values # ------------------------------------------------------------------------ def values(self): """ returns list of table entry items() """ self._assert_data() if self.view is None: # no View, so provide XML for each item return [this for this in self] else: # view object for each item return [list(this.items()) for this in self] # ------------------------------------------------------------------------ # items # ------------------------------------------------------------------------ def items(self): """ returns list of tuple(name,values) for each table entry """ return list(zip(self.keys(), self.values())) # ------------------------------------------------------------------------ # get - loads the data from source # ------------------------------------------------------------------------ def get(self, *vargs, **kvargs): # implemented by either OpTable or CfgTable # @@@ perhaps this should raise an exception rather than just 'pass',?? pass # ------------------------------------------------------------------------ # savexml - saves the table XML to a local file # ------------------------------------------------------------------------ def savexml(self, path, hostname=False, timestamp=False, append=None): """ Save a copy of the table XML data to a local file. The name of the output file (:path:) can include the name of the Device host, the timestamp of this action, as well as any user-defined appended value. These 'add-ons' will be added to the :path: value prior to the file extension in the order (hostname,timestamp,append), separated by underscore (_). For example, if both hostname=True and append='BAZ1', then when :path: = '/var/tmp/foo.xml' and the Device.hostname is "srx123", the final file-path will be "/var/tmp/foo_srx123_BAZ1.xml" :path: file-path to write the XML file on the local filesystem :hostname: if True, will append the hostname to the :path: :timestamp: if True, will append the timestamp to the :path: using the default timestamp format if the timestamp will use the value as the timestamp format as defied by strftime() :append: any value that you'd like appended to the :path: value preceding the filename extension. """ fname, fext = os.path.splitext(path) if hostname is True: fname += "_%s" % self.D.hostname if timestamp is not False: tsfmt = _TSFMT if timestamp is True else timestamp tsfmt_val = datetime.fromtimestamp(time()).strftime(tsfmt) fname += "_%s" % tsfmt_val if append is not None: fname += "_%s" % append path = fname + fext return etree.ElementTree(self.xml).write(open(path, 'w')) def to_json(self): """ :returns: JSON encoded string of entire Table contents """ return json.dumps(self, cls=TableJSONEncoder) # ------------------------------------------------------------------------- # OVERLOADS # ------------------------------------------------------------------------- __call__ = get def __repr__(self): cls_name = self.__class__.__name__ source = self.D.hostname if self.D is not None else self._path if self.xml is None: return "%s:%s - Table empty" % (cls_name, source) else: n_items = len(self.keys()) return "%s:%s: %s items" % (cls_name, source, n_items) def __len__(self): self._assert_data() return len(self.keys()) def __iter__(self): """ iterate over each time in the table """ self._assert_data() as_xml = lambda table, view_xml: view_xml view_as = self.view or as_xml for this in self.xml.xpath(self.ITEM_XPATH): yield view_as(self, this) def __getitem__(self, value): """ returns a table item. If a table view is set (should be by default) then the item will be converted to the view upon return. if there is no table view, then the XML object will be returned. :value: for , this will perform a select based on key-name for , this will perform a select based on compsite key-name for , this will perform a select based by position, like [0] is the first item [-1] is the last item when it is a then this will return a of View widgets """ self._assert_data() keys = self.keys() if isinstance(value, int): # if selection by index, then grab the key at this index and # recursively call this method using that key, yo! return self.__getitem__(keys[value]) if isinstance(value, slice): # implements the 'slice' mechanism return [self.__getitem__(key) for key in keys[value]] # ---[ get_xpath ] ---------------------------------------------------- def get_xpath(find_value): namekey_xpath, item_xpath = self._keyspec() xnkv = '[{0}="{1}"]' if isinstance(find_value, str): # find by name, simple key return item_xpath + xnkv.format(namekey_xpath, find_value) if isinstance(find_value, tuple): # composite key (value1, value2, ...) will create an # iterative xpath of the fmt statement for each key/value pair # skip over missing keys kv = [] for k, v in zip(namekey_xpath, find_value): if v is not None: kv.append(xnkv.format(k.replace('_', '-'), v)) xpf = ''.join(kv) return item_xpath + xpf # ---[END: get_xpath ] ------------------------------------------------ found = self.xml.xpath(get_xpath(value)) if not len(found): return None as_xml = lambda table, view_xml: view_xml use_view = self.view or as_xml return use_view(table=self, view_xml=found[0]) def __contains__(self, key): """ membership for use with 'in' """ return bool(key in self.keys()) junos-eznc-2.1.7/lib/jnpr/junos/factory/to_json.py0000644001013500016170000000363313163777564021623 0ustar stacys00000000000000from jnpr.junos.jxml import strip_comments_transform import json from lxml import etree from copy import deepcopy class TableJSONEncoder(json.JSONEncoder): """ Used to encode Table/View instances into JSON. See :meth:`Table.to_json`. """ def default(self, obj): from jnpr.junos.factory.view import View from jnpr.junos.factory.table import Table if isinstance(obj, View): obj = dict(obj.items()) elif isinstance(obj, Table): obj = dict((str(item.name), item) for item in obj) else: obj = super(TableJSONEncoder, self).default(obj) return obj class TableViewJSONEncoder(json.JSONEncoder): """ Used to encode Table/View instances into JSON. See :meth:`Table.to_json`. """ def default(self, obj): from jnpr.junos.factory.view import View from jnpr.junos.factory.table import Table if isinstance(obj, View): obj = {str(obj.name): dict(obj.items())} elif isinstance(obj, Table): obj = dict((str(item.name), dict(item.items())) for item in obj) else: obj = super(TableViewJSONEncoder, self).default(obj) return obj class PyEzJSONEncoder(json.JSONEncoder): """ Used to encode facts and rpc instances into JSON.`. """ def default(self, obj): from jnpr.junos.facts.swver import version_info if isinstance(obj, version_info): obj = obj.v_dict elif isinstance(obj, etree._Element): def recursive_dict(element): return (element.tag, dict(map(recursive_dict, element)) or element.text) # JSON does not support comments - strip them obj = strip_comments_transform(deepcopy(obj)).getroot() _, obj = recursive_dict(obj) else: obj = super(PyEzJSONEncoder, self).default(obj) return obj junos-eznc-2.1.7/lib/jnpr/junos/factory/view.py0000644001013500016170000002254213163777564021122 0ustar stacys00000000000000import warnings from contextlib import contextmanager from copy import deepcopy from lxml import etree import json import sys from jnpr.junos.factory.viewfields import ViewFields from jnpr.junos.factory.to_json import TableViewJSONEncoder class View(object): """ View is the base-class that makes extracting values from XML data appear as objects with attributes. """ ITEM_NAME_XPATH = 'name' FIELDS = {} GROUPS = None # ------------------------------------------------------------------------- # CONSTRUCTOR # ------------------------------------------------------------------------- def __init__(self, table, view_xml): """ :table: instance of the RunstatTable :view_xml: this should be an lxml etree Elemenet object. This constructor also accepts a list with a single item/XML """ # if as_xml is passed as a list, make sure it only has # a single item, common response from an xpath search if isinstance(view_xml, list): if 1 == len(view_xml): view_xml = view_xml[0] else: raise ValueError("constructor only accepts a single item") # now ensure that the thing provided is an lxml etree Element if not isinstance(view_xml, etree._Element): raise ValueError("constructor only accecpts lxml.etree._Element") self._table = table self.ITEM_NAME_XPATH = table.ITEM_NAME_XPATH self._init_xml(view_xml) def _init_xml(self, given_xml): self._xml = given_xml if self.GROUPS is not None: self._groups = {} for xg_name, xg_xpath in self.GROUPS.items(): xg_xml = self._xml.xpath(xg_xpath) # @@@ this is technically an error; need to trap it if not len(xg_xml): continue self._groups[xg_name] = xg_xml[0] # ------------------------------------------------------------------------- # PROPERTIES # ------------------------------------------------------------------------- @property def T(self): """ return the Table instance for the View """ return self._table @property def D(self): """ return the Device instance for this View """ return self.T.D @property def name(self): """ return the name of view item """ if self.ITEM_NAME_XPATH is None: return self._table.D.hostname if isinstance(self.ITEM_NAME_XPATH, str): # xpath union key if ' | ' in self.ITEM_NAME_XPATH: return self._xml.xpath(self.ITEM_NAME_XPATH)[0].text.strip() # simple key return self._xml.findtext(self.ITEM_NAME_XPATH).strip() else: # composite key # keys with missing XPATH nodes are set to None keys = [] for i in self.ITEM_NAME_XPATH: try: keys.append(self.xml.xpath(i)[0].text.strip()) except: keys.append(None) return tuple(keys) # ALIAS key <=> name key = name @property def xml(self): """ returns the XML associated to the item """ return self._xml # ------------------------------------------------------------------------- # METHODS # ------------------------------------------------------------------------- def keys(self): """ list of view keys, i.e. field names """ return self.FIELDS.keys() def values(self): """ list of view values """ return [getattr(self, field) for field in self.keys()] def items(self): """ list of tuple(key,value) """ return zip(self.keys(), self.values()) def _updater_instance(self, more): """ called from extend """ if hasattr(more, 'fields'): self.FIELDS = deepcopy(self.__class__.FIELDS) self.FIELDS.update(more.fields.end) if hasattr(more, 'groups'): self.GROUPS = deepcopy(self.__class__.GROUPS) self.GROUPS.update(more.groups) def _updater_class(self, more): """ called from extend """ if hasattr(more, 'fields'): self.FIELDS.update(more.fields.end) if hasattr(more, 'groups'): self.GROUPS.update(more.groups) @contextmanager def updater(self, fields=True, groups=False, all=True, **kvargs): """ provide the ability for subclassing objects to extend the definitions of the fields. this is implemented as a context manager with the form called from the subclass constructor: with self.extend() as more: more.fields = more.groups = # optional """ # --------------------------------------------------------------------- # create a new object class so we can attach stuff to it arbitrarily. # then pass that object to the caller, yo! # --------------------------------------------------------------------- more = type('RunstatViewMore', (object,), {})() if fields is True: more.fields = ViewFields() # --------------------------------------------------------------------- # callback through context manager # --------------------------------------------------------------------- yield more updater = self._updater_class if all is True else \ self._updater_instance updater(more) def asview(self, view_cls): """ create a new View object for this item """ return view_cls(self._table, self._xml) def refresh(self): """ ~~~ EXPERIMENTAL ~~~ refresh the data from the Junos device. this only works if the table provides an "args_key", does not update the original table, just this specific view/item """ warnings.warn("Experimental method: refresh") if self._table.can_refresh is not True: raise RuntimeError("table does not support this feature") # create a new table instance that gets only the specific named # value of this view tbl_xml = self._table._rpc_get(self.name) new_xml = tbl_xml.xpath(self._table.ITEM_XPATH)[0] self._init_xml(new_xml) return self def to_json(self): """ :returns: JSON encoded string of entire View contents """ return json.dumps(self, cls=TableViewJSONEncoder) # ------------------------------------------------------------------------- # OVERLOADS # ------------------------------------------------------------------------- def __repr__(self): """ returns the name of the View with the associate item name """ return "%s:%s" % (self.__class__.__name__, self.name) def __getattr__(self, name): """ returns a view item value, called as :obj.name: """ item = self.FIELDS.get(name) if item is None: raise ValueError("Unknown field: '%s'" % name) if 'table' in item: # if this is a sub-table, then return that now return item['table'](self.D, self._xml) # otherwise, not a sub-table, and handle the field astype = item.get('astype', str) if 'group' in item: if item['group'] in self._groups: found = self._groups[item['group']].xpath(item['xpath']) else: return else: found = self._xml.xpath(item['xpath']) len_found = len(found) if astype is bool: # handle the boolean flag case separately return bool(len_found) if not len_found: # even for the case of numbers, do not set the value. we # want to detect "does not exist" vs. defaulting to 0 # -- 2013-nov-19, JLS. return None try: # added exception handler to catch malformed xpath expressesion # -- 2013-nov-19, JLS. # added support to handle multiple xpath values, i.e. a list of # things that have the same xpath expression (common in configs) # -- 2031-dec-06, JLS # added support to use the element tag if the text is empty def _munch(x): if sys.version < '3': as_str = x if isinstance(x, str) else x.text if isinstance(as_str, unicode): as_str = as_str.encode('ascii', 'replace') else: as_str = x if isinstance(x, str) else x.text if as_str is not None: as_str = as_str.strip() if not as_str: as_str = x.tag # use 'not' to test for empty return astype(as_str) if 1 == len_found: return _munch(found[0]) return [_munch(this) for this in found] except: raise RuntimeError("Unable to handle field:'%s'" % name) # and if we are here, then we didn't handle the field. raise RuntimeError("Unable to handle field:'%s'" % name) def __getitem__(self, name): """ allow the caller to extract field values using :obj['name']: the same way they would do :obj.name: """ return getattr(self, name) junos-eznc-2.1.7/lib/jnpr/junos/factory/viewfields.py0000644001013500016170000000426213163777564022310 0ustar stacys00000000000000class ViewFields(object): """ Used to dynamically create a field dictionary used with the RunstatView class """ def __init__(self): self._fields = dict() def _prockvargs(self, field, name, **kvargs): if not len(kvargs): return field[name].update(kvargs) @property def end(self): return self._fields def str(self, name, xpath=None, **kvargs): """ field is a string """ if xpath is None: xpath = name field = {name: {'xpath': xpath}} self._prockvargs(field, name, **kvargs) self._fields.update(field) return self def astype(self, name, xpath=None, astype=int, **kvargs): """ field string value will be passed to function :astype: This is typically used to do simple type conversions, but also works really well if you set :astype: to a function that does a basic converstion like look at the value and change it to a True/False. For example: astype=lambda x: True if x == 'enabled' else False """ if xpath is None: xpath = name field = { name: {'xpath': xpath, 'astype': astype} } self._prockvargs(field, name, **kvargs) self._fields.update(field) return self def int(self, name, xpath=None, **kvargs): """ field is an integer """ return self.astype(name, xpath, int, **kvargs) def flag(self, name, xpath=None, **kvargs): """ field is a flag, results in True/False if the xpath element exists or not. Model this as a boolean type """ return self.astype(name, xpath, bool, **kvargs) def group(self, name, xpath=None, **kvargs): """ field is an apply group, results in value of group attr if the xpath element has the associated group attribute. """ xpath = './{0}/@group'.format(xpath) return self.astype(name, xpath, str, **kvargs) def table(self, name, table): """ field is a RunstatTable """ self._fields.update({ name: {'table': table} }) return self junos-eznc-2.1.7/lib/jnpr/junos/facts/0000755001013500016170000000000013163777613017215 5ustar stacys00000000000000junos-eznc-2.1.7/lib/jnpr/junos/facts/__example.py0000644001013500016170000000776213163777564021541 0ustar stacys00000000000000# Import any exceptions raised in this module. from jnpr.junos.exception import RpcError # In general, a "fact" should be a piece of information that does not change # over the life of a PyEZ connection. While things like mastership state can # change, they also result in the PyEZ connection being closed, so it can # be considered a fact. Things like number of routes in the route table or # system uptime do not maintain the same value over the life of a PyEZ # connection and should therefore not be considered facts. # The name of this file should be based on the RPCs which are invoked by this # file. This avoids accidentally invoking the same RPC multiple times when we # could invoke it just once and gather multiple facts from the output. # An import for each fact file must be present in # lib/jnpr/junos/facts/__init__.py # The file must include a provide_facts() function # The provide_facts() function must return a dictionary. The keys of the # dictionary are each fact that is handled/returned by this module. The value # of each item in the dictionary is the documentation string describing the # particular fact. def provides_facts(): """ Returns a dictionary keyed on the facts provided by this module. The value of each key is the doc string describing the fact. """ return {'foo': 'The foo information.', 'bar': 'The bar information.', } # The file must include a get_facts(device) function. The get_facts(device) # function takes a single mandatory device argument which is the Device object # on which the fact is discovered. # The get_facts(device) function must return a dictionary with a key for each # fact that is handled/returned by this module. # The get_facts(device) function should raise an appropriate exception if the # response from the device prevents getting value(s) for ALL of the facts # provided by the module. The example gets the 'foo' and 'bar' facts. # An exception should only be raised if the error prevents getting the value # for the 'foo' fact AND prevents getting the value for the 'bar' fact. If # unable to determine the value for a given fact, then set the value for that # fact to None. def get_facts(device): """ Gathers facts from the RPC. """ # Invoke an RPC on device using device.rpc.rpc_name() methods. # Avoid using the cli() or shell() methods. # Avoid any RPC which requires more than view privileges. # Avoid any RPC which doesn't run on non-master REs. # Avoid any RPC which takes more than a couple of seconds to execute. # Avoid any RPC which MIGHT take longer than the default PyEZ RPC timeout # (currently 30 seconds). # If there are different ways of gathering the info depending on # Junos version, platform, model, or other things, then try the most common # way first, catch exceptions, try the second most common, etc. # Note that executing an RPC might itself raise an exception. There is no # need to catch that exception. It is handled within the FactCache() class. # Always pass the normalize=True argument when invoking an RPC to avoid any # potential white-space problems in the RPC response. rsp = device.rpc.get_foo_bar_information(normalize=True) # Handle any exceptional situations unique to this RPC which prevent # getting values for ALL of the facts provided by this module. # These are examples which MAY or MAY NOT apply to your particular RPC. # Don't just blindly leave these in. if rsp.tag == 'error': raise RpcError() # An example of a boolean fact. False if the top-level tag is not 'foo'. foo = False if rsp.tag == 'foo': foo = True # An example of a string value which might be found at various levels or # locations within the response hierarchy. bar = ( rsp.findtext('.//chassis[1]/bar') or rsp.findtext('.//chassis-module[name="Backplane"]/bar') or rsp.findtext('.//chassis-module[name="Midplane"]/bar')) return {'foo': foo, 'bar': bar, } junos-eznc-2.1.7/lib/jnpr/junos/facts/__init__.py0000644001013500016170000000655613163777564021347 0ustar stacys00000000000000""" A dictionary-like object of read-only facts about the Junos device. These facts are accessed as the `facts` attribute of a `Device` object instance. For example, if `dev` is an instance of a `Device` object, the hostname of the device can be accessed with:: dev.facts['hostname'] Force a refresh of all facts with:: dev.facts_refresh() Force a refresh of a single fact with:: dev.facts_refresh(keys='hostname') Force a refresh of a set of facts with:: dev.facts_refresh(keys=('hostname','domain','fqdn')) NOTE: The dictionary key for each available fact is guaranteed to exist. If there is a problem gathering the value of a specific fact/key, or if the fact is not supported on a given platform, then the fact/key will have the value None (the None object, not a string.) Accessing a dictionary key which does not correspond to an available fact will raise a KeyError (the same behavior as accessing a non-existent key of a normal dict.) The following dictionary keys represent the available facts and their meaning: """ import sys import jnpr.junos.facts.current_re import jnpr.junos.facts.domain import jnpr.junos.facts.ethernet_mac_table import jnpr.junos.facts.file_list import jnpr.junos.facts.get_chassis_cluster_status import jnpr.junos.facts.get_chassis_inventory import jnpr.junos.facts.get_route_engine_information import jnpr.junos.facts.get_software_information import jnpr.junos.facts.get_virtual_chassis_information import jnpr.junos.facts.ifd_style import jnpr.junos.facts.iri_mapping import jnpr.junos.facts.personality import jnpr.junos.facts.swver def _build_fact_callbacks_and_doc_strings(): """ Imports the fact modules and returns callbacks and doc_strings. :returns: A tuple of callbacks and doc_strings. callbacks - a dict of the callback function to invoke for each fact. doc_strings - a dict of the doc string for each fact. :raises: RuntimeError if more than one module claims to provide the same fact. This is an indication of incorrectly written fact module(s). In order to remain deterministic, each fact must be provided by a single module. """ callbacks = {} doc_strings = {} for (name, module) in sys.modules.items(): if name.startswith('jnpr.junos.facts.') and module is not None: new_doc_strings = module.provides_facts() for key in new_doc_strings: if key not in callbacks: callbacks[key] = module.get_facts doc_strings[key] = new_doc_strings[key] else: raise RuntimeError('Both the %s module and the %s module ' 'claim to provide the %s fact. Please ' 'report this error.' % (callbacks[key].__module__, module.__name__, key)) return (callbacks, doc_strings) # Import all of the fact modules and build the callbacks and doc strings (_callbacks, _doc_strings) = _build_fact_callbacks_and_doc_strings() # Append the doc string (__doc__) with the documentation for each fact. for key in sorted(_doc_strings, key=lambda s: s.lower()): __doc__ += ':%s:\n %s\n' % (key, _doc_strings[key]) junos-eznc-2.1.7/lib/jnpr/junos/facts/current_re.py0000644001013500016170000001017113163777564021744 0ustar stacys00000000000000from jnpr.junos.exception import RpcError import re def provides_facts(): """ Returns a dictionary keyed on the facts provided by this module. The value of each key is the doc string describing the fact. """ return {'current_re': "A list of internal routing instance hostnames " "for the current RE. These hostnames identify " "things like the RE's slot ('re0' or 're1'), the " "RE's mastership state ('master' or 'backup'), " "and node in a VC ('member0' or 'member1')", } def get_facts(device): """ The RPC-equivalent of show interfaces terse on private routing instance. """ current_re = [] try: rsp = device.rpc.get_interface_information( normalize=True, routing_instance='__juniper_private1__', terse=True, ) # Get the local IPv4 addresses from the response. for ifa in rsp.iterfind( ".//address-family[address-family-name='inet']/" "interface-address/ifa-local"): ifa_text = ifa.text if ifa_text is not None: # Separate the IP from the mask (ip, _, _) = ifa.text.partition('/') if ip is not None: # Use the _iri_hostname fact to map the IP address to # an internal routing instance hostname. if ip in device.facts['_iri_hostname']: for host in device.facts['_iri_hostname'][ip]: if host not in current_re: current_re.append(host) # An SRX platform in an HA cluster uses a different # algorithm for assigning IRI IP addresses elif device.facts['srx_cluster_id'] is not None: try: # Split the IRI IP into a list of 4 octets octets = ip.split('.', 3) # The 2nd octet will be cluster-id << 4 cluster_id_octet = str( int(device.facts['srx_cluster_id']) << 4) # node0 will have an IP of # 129..0.1 # node1 will have an IP of # 130..0.1 # primary will have an IP of # 143..0.1 if (octets[1] == cluster_id_octet and octets[2] == '0' and octets[3] == '1'): host = None if octets[0] == '129': host = 'node0' elif octets[0] == '130': host = 'node1' elif octets[0] == '143': host = 'primary' if host is not None and host not in current_re: current_re.append(host) # Problem splitting IP into octets and indexing them. # Keep looping to check the other IRI IPs. except IndexError: pass except RpcError: # Check to see if it's JDM of Junos Node slicing try: current_re_sw = device.rpc.get_software_information() if current_re_sw is not None: server_slot = current_re_sw.findtext( './package-information[name="Server ' 'slot"]/comment' ) slot_num = re.findall(r'Server slot : (\d+)', server_slot)[0] current_re = ['server' + slot_num] except Exception: pass # An empty list indicates a problem finding any current_re info. # Return None. if len(current_re) == 0: current_re = None return {'current_re': current_re, } junos-eznc-2.1.7/lib/jnpr/junos/facts/domain.py0000644001013500016170000000362513163777564021051 0ustar stacys00000000000000from lxml import etree from jnpr.junos.exception import PermissionError from jnpr.junos.utils.fs import FS def provides_facts(): """ Returns a dictionary keyed on the facts provided by this module. The value of each key is the doc string describing the fact. """ return {'domain': "The domain name configured at the [edit system " "domain-name] configuration hierarchy.", 'fqdn': "The device's hostname + domain", } def get_facts(device): """ Gathers domain facts from the configuration or /etc/resolv.conf. """ domain_config = """ """ domain = None fqdn = None # Try to read the domain-name from the config. # This might fail due to lack of permissions. try: rsp = device.rpc.get_config(filter_xml=etree.XML(domain_config), options={'database': 'committed', 'inherit': 'inherit', 'commit-scripts': 'apply', }) domain = rsp.findtext('.//domain-name') # Ignore if user can't view the configuration. except PermissionError: pass # Try to read the domain from the resolv.conf file. This only requires # view permissions. if domain is None: fs = FS(device) file_content = (fs.cat('/etc/resolv.conf') or fs.cat('/var/etc/resolv.conf')) words = file_content.split() if file_content is not None else [] if 'domain' in words: idx = words.index('domain') + 1 domain = words[idx] # Set the fqdn fqdn = device.facts['hostname'] if fqdn is not None and domain is not None: fqdn = fqdn + '.' + domain return {'domain': domain, 'fqdn': fqdn, } junos-eznc-2.1.7/lib/jnpr/junos/facts/ethernet_mac_table.py0000644001013500016170000000471513163777564023410 0ustar stacys00000000000000from jnpr.junos.exception import RpcError def provides_facts(): """ Returns a dictionary keyed on the facts provided by this module. The value of each key is the doc string describing the fact. """ return {'switch_style': "A string which indicates the Ethernet " "switching syntax style supported by the device. " "Possible values are: 'BRIDGE_DOMAIN', 'VLAN', " "'VLAN_L2NG', or 'NONE'.", } def get_facts(device): """ Gathers facts about the Ethernet switching configuration syntax style. """ switch_style = None try: # RPC if VLAN style (Older EX, QFX, and SRX) # or if VLAN_L2NG (aka ELS) style (Newer EX and QFX) rsp = device.rpc.get_ethernet_switching_table_information(summary=True) if rsp.tag == 'l2ng-l2ald-rtb-macdb': switch_style = 'VLAN_L2NG' elif rsp.tag == 'ethernet-switching-table-information': switch_style = 'VLAN' except RpcError: pass if switch_style is None: try: # CLI command for MX style. Using the CLI command instead of the # RPC because PTX lies. It returns for the # RPC, but doesn't really support bridging. It raises an RPC # error for the CLI command (via RPC), so we use # the CLI command to make sure the device really does support # bridge domains. In this case, an RpcError is raised with a # bad_element of 'bridge'. # # However, on the backup RE for devices that really do support # bridge domains, we get an RpcError stating: # 'the l2-learning subsystem is not running' rsp = device.rpc.cli('show bridge mac-table count', format='xml', normalize=True) if rsp.tag == 'l2ald-rtb-mac-count': switch_style = 'BRIDGE_DOMAIN' else: switch_style = 'NONE' except RpcError as err: # Probably a PTX. if err.rpc_error['bad_element'] == 'bridge': switch_style = 'NONE' # Probably a non-master RE on an MX. elif (err.rpc_error['message'] == 'the l2-learning subsystem is not running'): switch_style = 'BRIDGE_DOMAIN' return {'switch_style': switch_style, } junos-eznc-2.1.7/lib/jnpr/junos/facts/file_list.py0000644001013500016170000000136013163777564021546 0ustar stacys00000000000000def provides_facts(): """ Returns a dictionary keyed on the facts provided by this module. The value of each key is the doc string describing the fact. """ return {'HOME': "A string indicating the home directory of the current " "user.", } def get_facts(device): """ Gathers facts from the RPC. """ home = None rsp = device.rpc.file_list(normalize=True, path='~') if rsp.tag == 'directory-list': dir_list_element = rsp else: dir_list_element = rsp.find('.//directory-list') if dir_list_element is not None: home = dir_list_element.get('root-path') if home is not None: home = home.rstrip('/') return {'HOME': home, } junos-eznc-2.1.7/lib/jnpr/junos/facts/get_chassis_cluster_status.py0000644001013500016170000000727713163777564025251 0ustar stacys00000000000000from jnpr.junos.exception import RpcError def provides_facts(): """ Returns a dictionary keyed on the facts provided by this module. The value of each key is the doc string describing the fact. """ return {'srx_cluster': "A boolean indicating if the device is part of an " "SRX cluster.", 'srx_cluster_id': "A string containing the configured cluster id", 'srx_cluster_redundancy_group': "A multi-level dictionary of " "information about the SRX " "cluster redundancy groups " "on the device. The first-level " "key is the redundancy group id. " "The second-level keys are: " "cluster_id, failover_count, " "node0, and node1. The node0 and " "node1 keys have third-level keys " "of priority, preempt, status, " "and failover_mode. The values " "for this fact correspond to the " "values of the 'show chassis " "cluster status' CLI command."} def get_facts(device): """ Gathers facts from the RPC. """ srx_cluster = None srx_cluster_id = None redundancy_group = None try: rsp = device.rpc.get_chassis_cluster_status(normalize=True) if rsp is not None: if rsp.tag == 'error': srx_cluster = False else: srx_cluster = True srx_cluster_id = rsp.findtext('cluster-id') groups = rsp.findall('redundancy-group') if groups is not None: redundancy_group = {} for group in groups: group_id = group.findtext('redundancy-group-id') redundancy_group[group_id] = { 'cluster_id': group.findtext('cluster-id'), 'failover_count': group.findtext( 'redundancy-group-failover-count'), } # Iterate over the broken XML in for stats in group.findall('./device-stats'): for node in zip(stats.findall('device-name'), stats.findall('device-priority'), stats.findall( 'redundancy-group-status'), stats.findall('preempt'), stats.findall('failover-mode')): redundancy_group[group_id][node[0].text] = { 'priority': node[1].text, 'status': node[2].text, 'preempt': node[3].text, 'failover_mode': node[4].text, } except RpcError: # Likely a device that doesn't implement the # RPC. # That's OK. Just ignore it and leave srx_cluster = None. pass return {'srx_cluster': srx_cluster, 'srx_cluster_id': srx_cluster_id, 'srx_cluster_redundancy_group': redundancy_group} junos-eznc-2.1.7/lib/jnpr/junos/facts/get_chassis_inventory.py0000644001013500016170000000306013163777564024204 0ustar stacys00000000000000from jnpr.junos.exception import ConnectNotMasterError from jnpr.junos.exception import RpcError def provides_facts(): """ Returns a dictionary keyed on the facts provided by this module. The value of each key is the doc string describing the fact. """ return {'RE_hw_mi': "(Routing Engine hardware multi-instance) A boolean " "indicating if this is a multi-chassis system.", 'serialnumber': "A string containing the serial number of the " "device's chassis. If there is no chassis serial " "number, the serial number of the backplane or " "midplane is returned.", } def get_facts(device): """ Gathers facts from the RPC. """ rsp = device.rpc.get_chassis_inventory(normalize=True) if rsp.tag == 'error': raise RpcError() if (rsp.tag == 'output' and rsp.text.find('can only be used on the master routing engine') != -1): # An error; due to the fact that this RPC can only be executed on the # master Routing Engine raise ConnectNotMasterError() RE_hw_mi = False if rsp.tag == 'multi-routing-engine-results': RE_hw_mi = True serialnumber = ( rsp.findtext('.//chassis[1]/serial-number') or rsp.findtext('.//chassis-module[name="Backplane"]/serial-number') or rsp.findtext('.//chassis-module[name="Midplane"]/serial-number')) return {'RE_hw_mi': RE_hw_mi, 'serialnumber': serialnumber, } junos-eznc-2.1.7/lib/jnpr/junos/facts/get_route_engine_information.py0000644001013500016170000001441013163777564025523 0ustar stacys00000000000000def provides_facts(): """ Returns a dictionary keyed on the facts provided by this module. The value of each key is the doc string describing the fact. """ return {'2RE': "A boolean indicating if the device has more than one " "Routing Engine installed.", 'master': "On a single chassis/node system, a string value of " "'RE0' or 'RE1' indicating which RE is master. On a " "multi-chassis or multi-node system, the value is a " "list of these strings indicating whether RE0 or RE1 " "is master. There is one entry in the list for each " "chassis/node in the system.", 'RE0': "A dictionary with information about RE0 (if present). The " "keys of the dictionary are: mastership_state, status, " "model, up_time, and last_reboot_reason.", 'RE1': "A dictionary with information about RE1 (if present). The " "keys of the dictionary are: mastership_state, status, " "model, up_time, and last_reboot_reason.", 're_info': "A three-level dictionary with information about " "the Routing Engines in the device. The first-level " "key is the chassis or node name. The second-level key " "is the slot number, the third-level keys are: " "mastership_state, status, model, and " "last_reboot_reason. A first-level key with a value " "of 'default' will always be present and represents " "the first chassis/node of the system (Note: the first " "chasis/node of the system is not necessarily the " "'master' node in a VC.) A second-level key with a " "value of 'default' will always be present " "for the default chassis/node and represents the " "first Routing Engine on the first node/chassis. " "(Note: the first RE of a chassis/node is not " "necessarily the 'master' RE of the chassis/node. See " "the RE_master fact for info on the 'master' RE of " "each chassis/node.)", 're_master': "A dictionary indicating which RE slot is master for " "each chassis/node in the system. The dictionary key " "is the chassis or node name. A key with a value " "of 'default' will always be present and represents " "the first node/chassis of the system. (Note: the " "first chassis/node of the system is not necessarily " "the 'master' node in a VC. See the vc_master fact " "to determine which chassis/node is the master of " "a VC.)", } def get_facts(device): """ Gathers facts from the RPC. """ multi_re = None master = None RE0 = None RE1 = None re_info = None re_master = None rsp = device.rpc.get_route_engine_information(normalize=True) re_list = rsp.findall('.//route-engine') if len(re_list) > 1: multi_re = True else: multi_re = False first_node = None first_slot = None master_list = [] node_masters = {} for current_re in re_list: node = current_re.findtext('../../re-name', 'default') slot = current_re.findtext('slot', '0') info = {'mastership_state': current_re.findtext('mastership-state', 'master'), 'status': current_re.findtext('status'), 'model': current_re.findtext('model'), 'last_reboot_reason': current_re.findtext('last-reboot-reason'), # This key is only returned in the RE0 and RE1 facts in order # to maintain backwards compatibility with the old fact # gathering system. Since the up_time value changes, it's not # really a "fact" and is therefore omitted from the new re_info # fact. 'up_time': current_re.findtext('up-time'), } if first_node is None: first_node = node first_slot = slot if node == first_node: if slot == '0' and RE0 is None: # Copy the dictionary RE0 = dict(info) if slot == '1' and RE1 is None: # Copy the dictionary RE1 = dict(info) # Don't want the up_time key in the new re_info fact. if 'up_time' in info: del info['up_time'] if re_info is None: re_info = {} if node not in re_info: re_info[node] = {} # Save with second-level key as a string. re_info[node][slot] = info # If it's a master RE, then save in node_masters and master_list if ('mastership_state' in info and info['mastership_state'].lower().startswith('master')): node_masters[node] = slot master_list.append('RE' + slot) # Fill in the 'default' first-level key if multi-chassis/node system if first_node is not None and first_node != 'default': re_info['default'] = re_info[first_node] if first_node in node_masters: node_masters['default'] = node_masters[first_node] # Fill in the 'default' second-level key if at least one RE was found. if first_slot is not None: re_info['default']['default'] = re_info['default'][first_slot] # Set the 'master' fact to a string or list based on the number of members. master_list_len = len(master_list) if master_list_len == 1: master = master_list[0] elif master_list_len > 1: master = master_list # If any info in the node_masters dict, then return it as fact 're_master' if node_masters: re_master = node_masters return {'2RE': multi_re, 'master': master, 'RE0': RE0, 'RE1': RE1, 're_info': re_info, 're_master': re_master} junos-eznc-2.1.7/lib/jnpr/junos/facts/get_software_information.py0000644001013500016170000002210413163777564024671 0ustar stacys00000000000000import re from jnpr.junos.facts.swver import version_info from jnpr.junos.exception import RpcError def _get_software_information(device): # See if device understands "invoke-on all-routing-engines" try: return device.rpc.cli("show version invoke-on all-routing-engines", format='xml', normalize=True) except RpcError: # See if device is VC Capable if device.facts['vc_capable'] is True: try: return device.rpc.cli("show version all-members", format='xml', normalize=True) except Exception: pass try: # JDM for Junos Node Slicing return device.rpc.get_software_information(all_servers=True, format='xml', normalize=True) except Exception: pass try: sw_info = device.rpc.get_software_information(normalize=True) if sw_info is True: # Possibly an NFX which requires 'local' and 'detail' args. sw_info = device.rpc.get_software_information(local=True, detail=True, normalize=True) return sw_info except Exception: pass def provides_facts(): """ Returns a dictionary keyed on the facts provided by this module. The value of each key is the doc string describing the fact. """ return {'junos_info': "A two-level dictionary providing Junos software " "version information for each RE in the system. " "The first-level key is the name of the RE. The " "second level key is 'text' for the version as a " "string and 'object' for the version as a " "version_info object.", 'hostname': 'A string containing the hostname of the current ' 'Routing Engine.', 'hostname_info': 'A dictionary keyed on Routing Engine name. The ' 'value of each key is the hostname of the ' 'Routing Engine.', 'model': 'An uppercase string containing the model of the chassis ' 'in which the current Routing Engine resides.', 'model_info': 'A dictionary keyed on Routing Engine name. The ' 'value of each key is an uppercase string ' 'containing the model of the chassis in which the ' 'Routing Engine resides.', 'version': 'A string containing the Junos version of the current ' 'Routing Engine.', 'version_info': 'The Junos version of the current Routing Engine ' 'as a version_info object.', 'version_RE0': "A string containing the Junos version of the " "RE in slot 0. (Assuming the system contains an " "RE0.)", 'version_RE1': "A string containing the Junos version of the " "RE in slot 1. (Assuming the system contains an " "RE1)", } def get_facts(device): """ Gathers facts from the RPC. """ junos_info = None hostname = None hostname_info = None model = None model_info = None version = None ver_info = None version_RE0 = None version_RE1 = None rsp = _get_software_information(device) si_rsp = None if rsp.tag == 'software-information': si_rsp = [rsp] else: si_rsp = rsp.findall('.//software-information') for re_sw_info in si_rsp: re_name = re_sw_info.findtext('../re-name', 're0') re_model = re_sw_info.findtext('./product-model') re_hostname = re_sw_info.findtext('./host-name') # First try the tag present in >= 15.1 re_version = re_sw_info.findtext('./junos-version') if re_version is None: # For < 15.1, get version from the "junos" package. try: re_pkg_info = re_sw_info.findtext( './package-information[name="junos"]/comment' ) if re_pkg_info is not None: re_version = re.findall(r'\[(.*)\]', re_pkg_info)[0] else: # Junos Node Slicing JDM case re_pkg_info = re_sw_info.findtext( './package-information[name="JUNOS version"]/comment' ) if re_pkg_info is not None: # In this case, re_pkg_info might look like this: # JUNOS version : 17.4-20170703_dev_common.0-secure # Match everything from last space until the end. re_version = re.findall(r'.*\s+(.*)', re_pkg_info)[0] else: # NFX JDM case re_pkg_info = re_sw_info.findtext( './version-information[component="MGD"]/release' ) re_version = re.findall(r'(.*\d+)', re_pkg_info)[0] except Exception: re_version = None if model_info is None and re_model is not None: model_info = {} if re_model is not None: model_info[re_name] = re_model.upper() if hostname_info is None and re_hostname is not None: hostname_info = {} if re_hostname is not None: hostname_info[re_name] = re_hostname if junos_info is None and re_version is not None: junos_info = {} if re_version is not None: junos_info[re_name] = {'text': re_version, 'object': version_info(re_version), } # Check to see if re_name is the RE we are currently connected to. # There are at least five cases to handle. this_re = False # 1) this device doesn't support the current_re fact and there's only # one RE. if device.facts['current_re'] is None and len(si_rsp) == 1: this_re = True # 2) re_name is in the current_re fact. The easy case. elif re_name in device.facts['current_re']: this_re = True # 3) Some single-RE devices (discovered on EX2200 running 11.4R1) # don't include 'reX' in the current_re list. Check for this # condition and still set default hostname, model, and version elif (re_name == 're0' and 're1' not in device.facts['current_re'] and 'master' in device.facts['current_re']): this_re = True # 4) For an lcc in a TX(P) re_name is 're0' or 're1', but the # current_re fact is ['lcc1-re0', 'member1-re0', ...]. Check to see # if any iri_name endswith the re_name. if this_re is False: for iri_name in device.facts['current_re']: if iri_name.endswith('-' + re_name): this_re = True break # 5) For an MX configured with Node Virtualization then, re_name is # bsys-reX, but the iri_name is still just reX. if this_re is False: for iri_name in device.facts['current_re']: if re_name == 'bsys-' + iri_name: this_re = True break # Set hostname, model, and version facts if we've found the RE to # which we are currently connected. if this_re is True: if hostname is None: hostname = re_hostname if model is None: model = re_model.upper() if version is None: version = re_version if version is None: version = '0.0I0.0' ver_info = version_info(version) if junos_info is not None: if 're0' in junos_info: version_RE0 = junos_info['re0']['text'] elif 'node0' in junos_info: version_RE0 = junos_info['node0']['text'] elif 'bsys-re0' in junos_info: version_RE0 = junos_info['bsys-re0']['text'] elif 'server0' in junos_info: version_RE0 = junos_info['server0']['text'] if 're1' in junos_info: version_RE1 = junos_info['re1']['text'] elif 'node1' in junos_info: version_RE1 = junos_info['node1']['text'] elif 'bsys-re1' in junos_info: version_RE1 = junos_info['bsys-re1']['text'] elif 'server1' in junos_info: version_RE1 = junos_info['server1']['text'] return {'junos_info': junos_info, 'hostname': hostname, 'hostname_info': hostname_info, 'model': model, 'model_info': model_info, 'version': version, 'version_info': ver_info, 'version_RE0': version_RE0, 'version_RE1': version_RE1, } junos-eznc-2.1.7/lib/jnpr/junos/facts/get_virtual_chassis_information.py0000644001013500016170000000521713163777564026250 0ustar stacys00000000000000from jnpr.junos.exception import RpcError def provides_facts(): """ Returns a dictionary keyed on the facts provided by this module. The value of each key is the doc string describing the fact. """ return {'vc_capable': "A boolean indicating if the device is currently " "configured in a virtual chassis. In spite of the " "name, this fact does NOT indicate whether or not " "the device is CAPABLE of joining a VC.", 'vc_mode': "A string indicating the current virtual chassis " "mode of the device.", 'vc_fabric': "A boolean indicating if the device is currently in " "fabric mode.", 'vc_master': "A string indicating the chassis/node which is " "currently the master of the VC.", } def get_facts(device): """ Gathers facts from the RPC. """ vc_capable = None vc_mode = None vc_fabric = None vc_master = None try: rsp = device.rpc.get_virtual_chassis_information(normalize=True) # MX issue where command returns, but without content. In this case, # rsp is set to True. if rsp is not True: vc_capable = True if rsp is not None: vc_mode = rsp.findtext('.//virtual-chassis-mode') vc_id_info = rsp.find('.//virtual-chassis-id-information') if vc_id_info is not None: if vc_id_info.get('style') == 'fabric': vc_fabric = True else: vc_fabric = False for member_id in rsp.xpath( ".//member-role[starts-with(.,'Master')]" "/preceding-sibling::member-id"): if vc_master is None: vc_master = member_id.text else: old_vc_master = vc_master vc_master = None raise ValueError("Member %s and member %s both claim " "to be master of the VC." % (old_vc_master, member_id.text)) else: vc_capable = False except RpcError: # Likely a device that doesn't implement the # RPC. # That's OK. Set vc_capable = False. vc_capable = False return {'vc_capable': vc_capable, 'vc_mode': vc_mode, 'vc_fabric': vc_fabric, 'vc_master': vc_master, } junos-eznc-2.1.7/lib/jnpr/junos/facts/ifd_style.py0000644001013500016170000000117713163777564021564 0ustar stacys00000000000000def provides_facts(): """ Returns a dictionary keyed on the facts provided by this module. The value of each key is the doc string describing the fact. """ return {'ifd_style': "The type of physical interface (ifd) supported by " "the device. Choices are 'CLASSIC' or 'SWITCH'.", } def get_facts(device): """ Determines ifd_style fact based on the personality. """ ifd_style = 'CLASSIC' if device.facts['personality'] == 'SWITCH': ifd_style = 'SWITCH' elif device.facts['personality'] == 'JDM': ifd_style = None return {'ifd_style': ifd_style, } junos-eznc-2.1.7/lib/jnpr/junos/facts/iri_mapping.py0000644001013500016170000000533213163777564022075 0ustar stacys00000000000000def provides_facts(): """ Returns a dictionary keyed on the facts provided by this module. The value of each key is the doc string describing the fact. """ return {'_iri_hostname': 'A dictionary keyed by internal routing instance ' 'ip addresses. The value of each key is the ' 'internal routing instance hostname for the ip', '_iri_ip': 'A dictionary keyed by internal routing instance ' 'hostnames. The value of each key is the internal ' 'routing instance ip for the hostname', } def get_facts(device): """ Gathers facts from a RPC on the '/etc/hosts.junos' file. """ iri_hostname = None iri_ip = None rsp = device.rpc.file_show(filename='/etc/hosts.junos', normalize=False) if rsp is not None: hosts_file_content = rsp.findtext('.', default='') if hosts_file_content is not None: for line in hosts_file_content.splitlines(): (line, _, _) = line.partition('#') components = line.split(None) if len(components) > 1: ip = components[0] hosts = components[1:] if iri_hostname is None: iri_hostname = {} if iri_ip is None: iri_ip = {} if ip in iri_hostname: iri_hostname[ip] += hosts else: iri_hostname[ip] = hosts for host in hosts: if host in iri_ip: iri_ip[host].append(ip) else: iri_ip[host] = [ip] for host in hosts: # Handle templates with %d if '%d' in host: octets = ip.split('.', 3) for count in range(255): t_ip = (octets[0] + '.' + octets[1] + '.' + str(count) + '.' + octets[3]) t_host = host.replace('%d', str(count)) if t_ip in iri_hostname: iri_hostname[t_ip].append(t_host) else: iri_hostname[t_ip] = [t_host] if t_host in iri_ip: iri_ip[t_host].append(t_ip) else: iri_ip[t_host] = [t_ip] return {'_iri_hostname': iri_hostname, '_iri_ip': iri_ip, } junos-eznc-2.1.7/lib/jnpr/junos/facts/personality.py0000644001013500016170000000522113163777564022145 0ustar stacys00000000000000import re def provides_facts(): """ Returns a dictionary keyed on the facts provided by this module. The value of each key is the doc string describing the fact. """ return {'personality': 'A string which is generally based on the ' 'platform and indicates the behavior of the ' 'device.', 'virtual': 'A boolean indicating if the device is virtual.', } def get_facts(device): """ Determines personality fact based on the model. """ personality = None virtual = None model = device.facts['model'] if model == 'Virtual Chassis': # Set model to the model of the first RE in the multi-chassis system. model = device.facts['re_info']['default']['default']['model'] if re.match('^(EX)|(QFX)', model): personality = 'SWITCH' virtual = False elif model.startswith('MX'): re_type = device.facts['re_info']['default']['default']['model'] # The VMX has an RE type of 'RE-VMX' if re_type == 'RE-VMX': personality = 'MX' virtual = True # An MX GNF has an RE type that includes the letters 'GNF' elif 'GNF' in re_type: personality = 'MX-GNF' virtual = True else: personality = 'MX' virtual = False elif model.startswith('VMX'): personality = 'MX' virtual = True elif model.startswith('VJX'): personality = 'SRX_BRANCH' virtual = True elif 'VRR' == model: personality = 'MX' virtual = True elif model.startswith('M'): personality = 'M' virtual = False elif model.startswith('T'): personality = 'T' virtual = False elif model.startswith('PTX'): personality = 'PTX' # The vPTX has an RE type of 'RE-VIRTUAL' if (device.facts['re_info']['default']['default']['model'] == 'RE-VIRTUAL'): virtual = True else: virtual = False elif re.match('SRX\s?(\d){4}', model): personality = 'SRX_HIGHEND' virtual = False elif re.match('SRX\s?(\d){3}', model): personality = 'SRX_BRANCH' virtual = False elif re.search('firefly', model, re.IGNORECASE): personality = 'SRX_BRANCH' virtual = True elif 'OLIVE' == model: personality = 'OLIVE' virtual = True elif model.startswith('NFX'): personality = 'NFX' virtual = False elif 'JUNOS_NODE_SLICING' == model: personality = 'JDM' virtual = True return {'personality': personality, 'virtual': virtual, } junos-eznc-2.1.7/lib/jnpr/junos/facts/swver.py0000644001013500016170000000636313163777564020752 0ustar stacys00000000000000import re class version_info(object): def __init__(self, verstr): """verstr - version string""" m1 = re.match('(.*?)([RBIXSF-])(.*)', verstr) self.type = m1.group(2) self.major = tuple(map(int, m1.group(1).split('.'))) # creates tuyple after_type = m1.group(3).split('.') self.minor = after_type[0] if 'X' == self.type: # assumes form similar to "45-D10", so extract the bits from this xm = re.match("(\d+)-(\w)(\d+)", self.minor) if xm is not None: self.minor = tuple( [int(xm.group(1)), xm.group(2), int(xm.group(3))]) if len(after_type) < 2: self.build = None else: self.build = int(after_type[1]) # X type not hyphen format, perhaps "11.4X12.1", just extract # build rev or set None else: if len(after_type) < 2: self.build = None else: self.build = int(after_type[1]) elif ('I' == self.type) or ('-' == self.type): self.type = 'I' try: # assumes that we have a build/spin, but not numeric self.build = after_type[1] except: self.build = None else: try: self.build = int(after_type[1]) # assumes numeric build/spin except: self.build = after_type[0] # non-numeric self.as_tuple = self.major + tuple([self.type, self.minor, self.build]) self.v_dict = {'major': self.major, 'type': self.type, 'minor': self.minor, 'build': self.build} def __iter__(self): for key in self.v_dict: yield key, self.v_dict[key] def __repr__(self): retstr = "junos.version_info(major={major}, type={type}," \ " minor={minor}, build={build})".format( major=self.major, type=self.type, minor=self.minor, build=self.build ) return retstr def _cmp_tuple(self, other): length = len(self) if len(self) < len(other) else len(other) return self.as_tuple[0:length] def __len__(self): length = 0 for component in self.as_tuple: if component is None: return length else: length += 1 return length def __lt__(self, other): return self._cmp_tuple(other) < other def __le__(self, other): return self._cmp_tuple(other) <= other def __gt__(self, other): return self._cmp_tuple(other) > other def __ge__(self, other): return self._cmp_tuple(other) >= other def __eq__(self, other): return self._cmp_tuple(other) == other def __ne__(self, other): return self._cmp_tuple(other) != other def version_yaml_representer(dumper, version): return dumper.represent_mapping(u'tag:yaml.org,2002:map', version.v_dict) def provides_facts(): """ Doesn't really provide any facts. """ return {} def get_facts(device): """ Doesn't get any facts. """ return {} junos-eznc-2.1.7/lib/jnpr/junos/jxml.py0000644001013500016170000001472713163777564017461 0ustar stacys00000000000000from ncclient import manager from ncclient.xml_ import NCElement from lxml import etree import six """ These are Junos XML 'helper' definitions use for generic XML processing .DEL to delete an item .REN to rename an item, requires the use of NAME() .INSERT(<'before'|'after'>) to reorder an item, requires the use of NAME() .BEFORE to reorder an item before another, requires the use of NAME() .AFTER to reorder an item after another, requires the use of NAME() .NAME(name) to assign the name attribute """ DEL = {'delete': 'delete'} # Junos XML resource delete REN = {'rename': 'rename'} # Junos XML resource rename ACTIVATE = {'active': 'active'} # activate resource DEACTIVATE = {'inactive': 'inactive'} # deactivate resource REPLACE = {'replace': 'replace'} # replace elements def NAME(name): return {'name': name} def INSERT(cmd): return {'insert': cmd} BEFORE = {'insert': 'before'} AFTER = {'insert': 'after'} # used with to load only the object identifiers and # not all the subsequent configuration NAMES_ONLY = {'recurse': "false"} # for , attributes to retrieve from apply-groups INHERIT = {'inherit': 'inherit'} INHERIT_GROUPS = {'inherit': 'inherit', 'groups': 'groups'} INHERIT_DEFAULTS = {'inherit': 'defaults', 'groups': 'groups'} # XSLT for on-box commit script conf_xslt = '''\ ''' conf_xslt_root = etree.XML(conf_xslt) conf_transform = etree.XSLT(conf_xslt_root) normalize_xslt = '''\ ''' # XSLT to strip comments strip_comments_xslt = '''\ ''' strip_xslt_root = etree.XML(strip_comments_xslt) strip_comments_transform = etree.XSLT(strip_xslt_root) # XSLT to strip elements strip_rpc_error_xslt = ''' ''' strip_rpc_error_root = etree.XML(strip_rpc_error_xslt) strip_rpc_error_transform = etree.XSLT(strip_rpc_error_root) def remove_namespaces(xml): for elem in xml.getiterator(): if elem.tag is etree.Comment: continue i = elem.tag.find('}') if i > 0: elem.tag = elem.tag[i + 1:] return xml def rpc_error(rpc_xml): """ extract the various bits from an element into a dictionary """ remove_namespaces(rpc_xml) if 'rpc-reply' == rpc_xml.tag: rpc_xml = rpc_xml[0] def find_strip(x): ele = rpc_xml.find(x) return ele.text.strip() if ele is not None and ele.text is not None \ else None this_err = {} this_err['severity'] = find_strip('error-severity') this_err['source'] = find_strip('source-daemon') this_err['edit_path'] = find_strip('error-path') this_err['bad_element'] = find_strip('error-info/bad-element') this_err['message'] = find_strip('error-message') return this_err def cscript_conf(reply): try: device_params = {'name': 'junos'} device_handler = manager.make_device_handler(device_params) transform_reply = device_handler.transform_reply() return NCElement(reply, transform_reply)._NCElement__doc except: return None # xslt to remove prefix like junos:ns strip_namespaces_prefix = six.b(""" """) junos-eznc-2.1.7/lib/jnpr/junos/ofacts/0000755001013500016170000000000013163777613017374 5ustar stacys00000000000000junos-eznc-2.1.7/lib/jnpr/junos/ofacts/__init__.py0000644001013500016170000000153713163777564021520 0ustar stacys00000000000000from jnpr.junos.ofacts.chassis import facts_chassis from jnpr.junos.ofacts.routing_engines import facts_routing_engines from jnpr.junos.ofacts.personality import facts_personality from jnpr.junos.ofacts.swver import facts_software_version from jnpr.junos.ofacts.ifd_style import facts_ifd_style from jnpr.junos.ofacts.switch_style import facts_switch_style from jnpr.junos.ofacts.session import facts_session from jnpr.junos.ofacts.srx_cluster import facts_srx_cluster from jnpr.junos.ofacts.domain import facts_domain FACT_LIST = [ facts_chassis, # first facts_routing_engines, # second facts_personality, # third facts_srx_cluster, # four facts_software_version, # fifth facts_domain, facts_ifd_style, facts_switch_style, facts_session ] __all__ = ['FACT_LIST'] junos-eznc-2.1.7/lib/jnpr/junos/ofacts/chassis.py0000644001013500016170000000342213163777564021411 0ustar stacys00000000000000from jnpr.junos.exception import ConnectNotMasterError from jnpr.junos.exception import RpcError def facts_chassis(junos, facts): """ The following facts are assigned: facts['2RE'] : designates if the device can support two RE, not that it has them facts['RE_hw_mi'] : designates if the device is multi-instance-routing-engine facts['model'] : product model facts['serialnumber'] : serial number NOTES: (1) if in a 2RE system, this routine will only load the information from the first chassis item. (2) hostname, domain, and fqdn are retrieved from configuration data; inherited configs are checked. """ # Set default values. facts['2RE'] = False facts['RE_hw_mi'] = False facts['model'] = 'UNKNOWN' facts['serialnumber'] = 'UNKNOWN' rsp = junos.rpc.get_chassis_inventory() if rsp.tag == 'error': raise RuntimeError() if rsp.tag == 'output': # this means that there was an error; due to the # fact that this connection is not on the master # @@@ need to validate on VC-member raise ConnectNotMasterError(junos) if rsp.tag == 'multi-routing-engine-results': facts['2RE'] = True facts['RE_hw_mi'] = True else: facts['2RE'] = False facts['model'] = rsp.findtext('.//chassis[1]/description', 'UNKNOWN') facts['serialnumber'] = ( rsp.findtext('.//chassis[1]/serial-number') or rsp.findtext('.//chassis-module[name="Backplane"]/serial-number') or rsp.findtext('.//chassis-module[name="Midplane"]/serial-number', 'UNKNOWN')) if facts['model'] == 'UNKNOWN' or facts['serialnumber'] == 'UNKNOWN': raise RpcError() junos-eznc-2.1.7/lib/jnpr/junos/ofacts/domain.py0000644001013500016170000000232213163777564021221 0ustar stacys00000000000000from jnpr.junos.utils.fs import FS from jnpr.junos.exception import RpcError from jnpr.junos.jxml import INHERIT from lxml.builder import E def facts_domain(junos, facts): """ The following facts are required: facts['hostname'] The following facts are assigned: facts['domain'] facts['fqdn'] """ try: domain_filter_xml = E('configuration', E('system', E('domain-name'))) domain = junos.rpc.get_config(filter_xml=domain_filter_xml, options=INHERIT) domain_name = domain.xpath('.//domain-name') if len(domain_name) > 0: facts['domain'] = domain_name[0].text facts['fqdn'] = facts['hostname'] + '.' + facts['domain'] return except RpcError: pass fs = FS(junos) file_content = fs.cat('/etc/resolv.conf') or fs.cat('/var/etc/resolv.conf') words = file_content.split() if file_content is not None else '' if 'domain' not in words: facts['domain'] = None facts['fqdn'] = facts['hostname'] else: idx = words.index('domain') + 1 facts['domain'] = words[idx] facts['fqdn'] = facts['hostname'] + '.' + facts['domain'] junos-eznc-2.1.7/lib/jnpr/junos/ofacts/ifd_style.py0000644001013500016170000000027113163777564021735 0ustar stacys00000000000000def facts_ifd_style(junos, facts): persona = facts['personality'] if persona == 'SWITCH': facts['ifd_style'] = 'SWITCH' else: facts['ifd_style'] = 'CLASSIC' junos-eznc-2.1.7/lib/jnpr/junos/ofacts/personality.py0000644001013500016170000000244513163777564022331 0ustar stacys00000000000000import re def facts_personality(junos, facts): model = facts['model'] if model != 'Virtual Chassis': examine = model else: for fact in facts: if re.match("^RE\d", fact): examine = facts[fact]['model'] break examine = examine.upper() if re.match("^(EX)|(QFX)", examine): persona = 'SWITCH' elif examine.startswith("MX"): persona = 'MX' elif examine.startswith('VMX'): facts['virtual'] = True persona = 'MX' elif examine.startswith("VJX"): facts['virtual'] = True persona = 'SRX_BRANCH' elif 'VRR' == examine: persona = "MX" facts['virtual'] = True elif examine.startswith("M"): persona = "M" elif examine.startswith("T"): persona = "T" elif examine.startswith("PTX"): persona = "PTX" elif re.match("SRX\s?(\d){4}", examine): persona = 'SRX_HIGHEND' elif re.match("SRX\s?(\d){3}", examine): persona = 'SRX_BRANCH' elif re.search("firefly", examine, re.IGNORECASE): facts['virtual'] = True persona = 'SRX_BRANCH' elif 'OLIVE' == examine: facts['virtual'] = True persona = 'OLIVE' else: persona = "UNKNOWN" facts['personality'] = persona junos-eznc-2.1.7/lib/jnpr/junos/ofacts/routing_engines.py0000644001013500016170000000572113163777564023157 0ustar stacys00000000000000import re as RE def _get_vc_status(dev, facts): try: rsp = dev.rpc.get_virtual_chassis_information() # MX issue where command returns, but without content if rsp is not True: facts['vc_capable'] = True return rsp else: facts['vc_capable'] = False return None except: facts['vc_capable'] = False return None def facts_routing_engines(junos, facts): re_facts = [ 'mastership-state', 'status', 'model', 'up-time', 'last-reboot-reason'] master = [] vc_info = _get_vc_status(junos, facts) if vc_info is not None: facts['vc_mode'] = vc_info.findtext('.//virtual-chassis-mode') if len(vc_info.xpath(".//virtual-chassis-id-information" "[@style='fabric']")) > 0: facts['vc_fabric'] = True vc_list = vc_info.xpath(".//member-role[starts-with(.,'Master') " "or starts-with(.,'Backup')]") if len(vc_list) > 1: facts['2RE'] = True for member_id in vc_info.xpath( ".//member-role[starts-with(.,'Master')]" "/preceding-sibling::member-id"): master.append("RE{0}".format(member_id.text)) try: re_info = junos.rpc.get_route_engine_information() except: # this means that the RPC failed. this should "never" # happen, but we will trap it cleanly for now return re_list = re_info.xpath('.//route-engine') if len(re_list) > 1: facts['2RE'] = True for re in re_list: x_re_name = re.xpath('ancestor::multi-routing-engine-item/re-name') if not x_re_name: # not a multi-instance routing engine platform, but could # have multiple RE slots re_name = "RE" x_slot = re.find('slot') slot_id = x_slot.text if x_slot is not None else "0" re_name = re_name + slot_id else: # multi-instance routing platform m = RE.search('(\d)', x_re_name[0].text) if vc_info is not None: # => RE0-RE0 | RE0-RE1 re_name = "RE{0}-RE{1}".format(m.group(0), re.find('slot').text) else: re_name = "RE" + m.group(0) # => RE0 | RE1 re_fd = {} facts[re_name] = re_fd for factoid in re_facts: x_f = re.find(factoid) if x_f is not None: re_fd[factoid.replace('-', '_')] = x_f.text if vc_info is None and 'mastership_state' in re_fd: if facts[re_name]['mastership_state'] == 'master': master.append(re_name) # --[ end for-each 're' ]------------------------------------------------- len_master = len(master) if len_master > 1: facts['master'] = master elif len_master == 1: facts['master'] = master[0] junos-eznc-2.1.7/lib/jnpr/junos/ofacts/session.py0000644001013500016170000000032113163777564021432 0ustar stacys00000000000000""" facts['HOME'] = login home directory """ from lxml.builder import E def facts_session(dev, facts): facts['HOME'] = dev.rpc( E.command("show cli directory")).findtext('./working-directory') junos-eznc-2.1.7/lib/jnpr/junos/ofacts/srx_cluster.py0000644001013500016170000000174313163777564022335 0ustar stacys00000000000000def facts_srx_cluster(junos, facts): # we should check the 'cluster status' on redundancy group 0 to see who is # master. we use a try/except block for cases when SRX is not clustered try: cluster_st = junos.rpc.get_chassis_cluster_status(redundancy_group="0") if 'error' == cluster_st.tag: facts['srx_cluster'] = False return primary = cluster_st.xpath( './/redundancy-group-status[.="primary"]')[0] node = primary.xpath( 'preceding-sibling::device-name[1]')[0].text.replace('node', 'RE') if not facts.get('master'): facts['master'] = node elif node not in facts['master']: facts['master'].append(node) facts['srx_cluster'] = True except: # this device doesn't support SRX chassis clustering; i.e. # since we arbitrarily execute the RPC on all devices, if we # hit this exception we just ignore, A-OK, yo! pass junos-eznc-2.1.7/lib/jnpr/junos/ofacts/switch_style.py0000644001013500016170000000077113163777564022501 0ustar stacys00000000000000import re def facts_switch_style(junos, facts): persona = facts['personality'] if persona in ['MX', 'SRX_HIGHEND']: style = 'BRIDGE_DOMAIN' elif persona in ['SWITCH', 'SRX_BRANCH']: model = facts['model'] if re.match('firefly', model, re.IGNORECASE): style = 'NONE' elif re.match('^(EX9)|(EX43)', model): style = 'VLAN_L2NG' else: style = 'VLAN' else: style = 'NONE' facts['switch_style'] = style junos-eznc-2.1.7/lib/jnpr/junos/ofacts/swver.py0000644001013500016170000001074313163777564021126 0ustar stacys00000000000000import re from jnpr.junos.facts.swver import version_info def _get_swver(dev, facts): # See if we're VC Capable if facts['vc_capable'] is True: try: return dev.rpc.cli("show version all-members", format='xml') except: pass try: return dev.rpc.cli("show version invoke-on all-routing-engines", format='xml') except: return dev.rpc.get_software_information() def facts_software_version(junos, facts): """ The following facts are required: facts['master'] The following facts are assigned: facts['hostname'] facts['version'] facts['version_'] for each RE in dual-RE, cluster or VC system facts['version_info'] for master RE """ x_swver = _get_swver(junos, facts) if not facts.get('model'): # try to extract the model from the version information facts['model'] = x_swver.findtext('.//product-model') # ------------------------------------------------------------------------ # extract the version information out of the RPC response # ------------------------------------------------------------------------ f_master = facts.get('master', 'RE0') if x_swver.tag == 'multi-routing-engine-results': # we need to find/identify each of the routing-engine (CPU) versions. if len(x_swver.xpath('./multi-routing-engine-item')) > 1: facts['2RE'] = True versions = [] if isinstance(f_master, list): xpath = './multi-routing-engine-item[re-name="{0}"]/software-' \ 'information/host-name'.format(f_master[0].lower()) else: xpath = './multi-routing-engine-item[re-name="{0}"' \ ']/software-information/host-name'.format(f_master.lower()) facts['hostname'] = x_swver.findtext(xpath) if facts['hostname'] is None: # then there the re-name is not what we are expecting; we should # handle this better, eh? For now, just assume there is one # software-information element and take that host-name. @@@ hack. facts['hostname'] = x_swver.findtext( './/software-information/host-name') for re_sw in x_swver.xpath('.//software-information'): re_name = re_sw.xpath('preceding-sibling::re-name')[0].text # handle the cases where the "RE name" could be things like # "FPC" or "ndoe", and normalize to "RE". re_name = re.sub(r'(\w+)(\d+)', 'RE\\2', re_name) # First try the tag present in >= 15.1 swinfo = re_sw.findtext('junos-version', default=None) if not swinfo: # For < 15.1, get version from the "junos" package. pkginfo = re_sw.xpath( 'package-information[normalize-space(name)=' '"junos"]/comment')[0].text try: swinfo = re.findall(r'\[(.*)\]', pkginfo)[0] except: swinfo = "0.0I0.0" versions.append((re_name.upper(), swinfo)) # now add the versions to the facts for re_ver in versions: facts['version_' + re_ver[0]] = re_ver[1] if f_master is not None: master = f_master[0] if isinstance(f_master, list) else f_master if 'version_' + master in facts: facts['version'] = facts['version_' + master] else: facts['version'] = versions[0][1] else: facts['version'] = versions[0][1] else: # single-RE facts['hostname'] = x_swver.findtext('host-name') # First try the tag present in >= 15.1 swinfo = x_swver.findtext('.//junos-version', default=None) if not swinfo: # For < 15.1, get version from the "junos" package. pkginfo = x_swver.xpath( './/package-information[normalize-space(name)="junos"]/comment' )[0].text try: swinfo = re.findall(r'\[(.*)\]', pkginfo)[0] except: swinfo = "0.0I0.0" facts['version'] = swinfo # ------------------------------------------------------------------------ # create a 'version_info' object based on the master version # ------------------------------------------------------------------------ facts['version_info'] = version_info(facts['version']) junos-eznc-2.1.7/lib/jnpr/junos/op/0000755001013500016170000000000013163777613016533 5ustar stacys00000000000000junos-eznc-2.1.7/lib/jnpr/junos/op/__init__.py0000644001013500016170000000000013163777564020637 0ustar stacys00000000000000junos-eznc-2.1.7/lib/jnpr/junos/op/arp.py0000644001013500016170000000026713163777564017701 0ustar stacys00000000000000""" Pythonifier for ARP Table/View """ from jnpr.junos.factory import loadyaml from os.path import splitext _YAML_ = splitext(__file__)[0] + '.yml' globals().update(loadyaml(_YAML_)) junos-eznc-2.1.7/lib/jnpr/junos/op/arp.yml0000644001013500016170000000036713163777564020053 0ustar stacys00000000000000--- ArpTable: rpc: get-arp-table-information args: no-resolve: True item: arp-table-entry key: mac-address view: ArpView ArpView: fields: mac_address: mac-address ip_address: ip-address interface_name: interface-name junos-eznc-2.1.7/lib/jnpr/junos/op/bfd.py0000644001013500016170000000026713163777564017652 0ustar stacys00000000000000""" Pythonifier for BFD Table/View """ from jnpr.junos.factory import loadyaml from os.path import splitext _YAML_ = splitext(__file__)[0] + '.yml' globals().update(loadyaml(_YAML_)) junos-eznc-2.1.7/lib/jnpr/junos/op/bfd.yml0000644001013500016170000000334113163777564020017 0ustar stacys00000000000000BfdSessionTable: rpc: get-bfd-session-information args: extensive: True item: bfd-session key: session-neighbor view: BfdSessionView BfdSessionView: fields: neighbor: session-neighbor state: session-state interface: session-interface detection_time: session-detection-time transmission_interval: session-transmission-interval adaptive_multiplier: session-adaptive-multiplier local_diagnostic: local-diagnostic remote_diagnostic: remote-diagnostic version: session-version remote_state: remote-state minimum_asynchronous_interval: minimum-asynchronous-interval minimum_slow_interval: minimum-slow-interval adaptive_asynchronous_transmission_interval: adaptive-asynchronous-transmission_interval adaptive_reception_interval: adaptive-reception-interval minimum_transmission_interval: minimum-transmission-interval minimum_reception_interval: minimum-reception-interval detection_multiplier: detection-multiplier neighbor_minimum_transmission_interval: neighbor-minimum-transmission_interval neighbor_minimum_reception_interval: neighbor-minimum-reception_interval neighbor_session_multiplier: neighbor-session-multiplier local_discriminator: local-discriminator remote_discriminator: remote-discriminator echo_mode_desired: echo-mode-desired echo_mode_state: echo-mode-state no_absorb: { no-absorb: True=regex(no-absorb) } no_refresh: { no-refresh: True=no-refresh } bfd_client: _BfdSessionClientTable _BfdSessionClientTable: item: bfd-client view: _BfdSessionClientView _BfdSessionClientView: fields: name: client-name transmission_interval: client-transmission-interval reception_interval: client-reception-interval junos-eznc-2.1.7/lib/jnpr/junos/op/bgp.py0000644001013500016170000000026713163777564017667 0ustar stacys00000000000000""" Pythonifier for BGP Table/View """ from jnpr.junos.factory import loadyaml from os.path import splitext _YAML_ = splitext(__file__)[0] + '.yml' globals().update(loadyaml(_YAML_)) junos-eznc-2.1.7/lib/jnpr/junos/op/bgp.yml0000644001013500016170000000042013163777564020027 0ustar stacys00000000000000--- bgpTable: rpc: get-bgp-neighbor-information item: bgp-peer view: bgpView key: peer-id bgpView: fields: local_as: local-as peer_as: peer-as local_address: local-address peer_id: peer-id local_id: local-id route_received: bgp-rib/received-prefix-count junos-eznc-2.1.7/lib/jnpr/junos/op/ccc.py0000644001013500016170000000031713163777564017643 0ustar stacys00000000000000""" Pythonifier for Circuit Cross Connect Table/View (ccc) """ from jnpr.junos.factory import loadyaml from os.path import splitext _YAML_ = splitext(__file__)[0] + '.yml' globals().update(loadyaml(_YAML_)) junos-eznc-2.1.7/lib/jnpr/junos/op/ccc.yml0000644001013500016170000000063713163777564020021 0ustar stacys00000000000000--- CCCTable: rpc: get-ccc-information args: status: True interface-switch: True item: ccc-connection key: ccc-connection-name view: CCCView CCCView: fields: status: ccc-connection-status ports: _CCCPorts _CCCPorts: item: ccc-connection-circuit key: - ccc-circuit-name view: _CCCPortsView _CCCPortsView: fields: type: ccc-circuit-type status: ccc-circuit-status junos-eznc-2.1.7/lib/jnpr/junos/op/ethernetswitchingtable.py0000644001013500016170000000030613163777564023657 0ustar stacys00000000000000""" Pythonifier for EthernetSwitchingTable/View """ from jnpr.junos.factory import loadyaml from os.path import splitext _YAML_ = splitext(__file__)[0] + '.yml' globals().update(loadyaml(_YAML_)) junos-eznc-2.1.7/lib/jnpr/junos/op/ethernetswitchingtable.yml0000644001013500016170000000173513163777564024037 0ustar stacys00000000000000--- EthernetSwitchingTable: rpc: get-ethernet-switching-table-information args: detail: True item: ethernet-switching-table view: EthernetSwitchingView EthernetSwitchingView: fields: count: mac-table-count learned: mac-table-learned persistent: mac-table-persistent entries: _MacTableEntriesTable _MacTableEntriesTable: item: mac-table-entry key: mac-vlan view: _MacTableEntriesView _MacTableEntriesView: fields: vlan: mac-vlan vlan_tag: mac-vlan-tag mac_address: mac-address type: mac-type age: mac-age learned_time: mac-learned-time action: mac-action next_hop: mac-nexthop interface: mac-interface interface-list: _MacTableInterfacesTable _MacTableInterfacesTable: item: mac-interfaces-list key: mac-interfaces view: _MacTableInterfacesView _MacTableInterfacesView: fields: interfaces: mac-interfaces junos-eznc-2.1.7/lib/jnpr/junos/op/ethport.py0000644001013500016170000000027313163777564020601 0ustar stacys00000000000000""" Pythonifier for EthPort Table/View """ from jnpr.junos.factory import loadyaml from os.path import splitext _YAML_ = splitext(__file__)[0] + '.yml' globals().update(loadyaml(_YAML_)) junos-eznc-2.1.7/lib/jnpr/junos/op/ethport.yml0000644001013500016170000000123513163777564020751 0ustar stacys00000000000000--- EthPortTable: rpc: get-interface-information args: media: True interface_name: '[afgxe][et]-*' args_key: interface_name item: physical-interface view: EthPortView EthPortView: groups: mac_stats: ethernet-mac-statistics flags: if-device-flags fields: oper: oper-status admin: admin-status description: description mtu: { mtu : int } link_mode: link-mode macaddr: current-physical-address fields_mac_stats: rx_bytes: input-bytes rx_packets: input-packets tx_bytes: output-bytes tx_packets: output-packets fields_flags: running: { ifdf-running: flag } present: { ifdf-present: flag } junos-eznc-2.1.7/lib/jnpr/junos/op/fpc.py0000644001013500016170000000027113163777564017662 0ustar stacys00000000000000""" Pythonifier for Route Table/View """ from jnpr.junos.factory import loadyaml from os.path import splitext _YAML_ = splitext(__file__)[0] + '.yml' globals().update(loadyaml(_YAML_)) junos-eznc-2.1.7/lib/jnpr/junos/op/fpc.yml0000644001013500016170000000343213163777564020035 0ustar stacys00000000000000--- # ------------------------------------------------------------------- # Table # ------------------------------------------------------------------- # retrieve the chassis hardware (inventory) and extract the FPC # items. # ------------------------------------------------------------------- FpcHwTable: rpc: get-chassis-inventory item: .//name[starts-with(.,'FPC')]/parent::* view: _fpc_hw_view FpcMiReHwTable: rpc: get-chassis-inventory item: .//name[starts-with(.,'FPC')]/parent::* key: - ancestor::multi-routing-engine-item/re-name - name view: _fpc_hw_view # ------------------------------------------------------------------- # View # ------------------------------------------------------------------- # use the underscore (_) so this definition is not # imported into the glboal namespace. We want to extract various # bits of information from the FPC items # ------------------------------------------------------------------- _fpc_hw_view: fields: sn: serial-number pn: part-number desc: description ver: version model: model-number # ------------------------------------------------------------------- # Table # ------------------------------------------------------------------- # retrieve the FPC status information; corresponds to: # > show chassis fpc # ------------------------------------------------------------------- FpcInfoTable: rpc: get-fpc-information item: .//fpc key: slot view: _fpc_info_view FpcMiReInfoTable: rpc: get-fpc-information item: .//fpc key: - ancestor::multi-routing-engine-item/re-name - slot view: _fpc_info_view _fpc_info_view: fields: state: state memory: memory-heap-utilization cpu: cpu-total junos-eznc-2.1.7/lib/jnpr/junos/op/idpattacks.py0000644001013500016170000000026713163777564021246 0ustar stacys00000000000000""" Pythonifier for IDP Table/View """ from jnpr.junos.factory import loadyaml from os.path import splitext _YAML_ = splitext(__file__)[0] + '.yml' globals().update(loadyaml(_YAML_)) junos-eznc-2.1.7/lib/jnpr/junos/op/idpattacks.yml0000644001013500016170000000027513163777564021416 0ustar stacys00000000000000--- IDPAttackTable: rpc: get-idp-attack-table-information item: idp-attack-statistics key: name view: IDPAttackView IDPAttackView: fields: attack_name: name count: value junos-eznc-2.1.7/lib/jnpr/junos/op/intopticdiag.py0000644001013500016170000000030013163777564021561 0ustar stacys00000000000000""" Pythonifier for IntOpticDiag Table/View """ from jnpr.junos.factory import loadyaml from os.path import splitext _YAML_ = splitext(__file__)[0] + '.yml' globals().update(loadyaml(_YAML_)) junos-eznc-2.1.7/lib/jnpr/junos/op/intopticdiag.yml0000644001013500016170000000100613163777564021736 0ustar stacys00000000000000--- PhyPortDiagTable: rpc: get-interface-optics-diagnostics-information args: interface_name: '[efgx][et]-*' args_key: interface_name item: physical-interface view: PhyPortDiagView PhyPortDiagView: groups: diag: optics-diagnostics # fields that are part of groups are called # "fields_" fields_diag: rx_optic_power : rx-signal-avg-optical-power-dbm tx_optic_power : laser-output-power-dbm module_temperature : module-temperature module_voltage : module-voltage junos-eznc-2.1.7/lib/jnpr/junos/op/inventory.py0000644001013500016170000000027513163777564021153 0ustar stacys00000000000000""" Pythonifier for Inventory Table/View """ from jnpr.junos.factory import loadyaml from os.path import splitext _YAML_ = splitext(__file__)[0] + '.yml' globals().update(loadyaml(_YAML_)) junos-eznc-2.1.7/lib/jnpr/junos/op/inventory.yml0000644001013500016170000000044113163777564021317 0ustar stacys00000000000000--- ModuleTable: rpc: get-chassis-inventory item: .//chassis-sub-module|.//chassis-module|.//chassis-sub-sub-module key: - name view: ModuleTableView ModuleTableView: fields: jname: name sn: serial-number pn: part-number ver: version type: description junos-eznc-2.1.7/lib/jnpr/junos/op/isis.py0000644001013500016170000000027013163777564020060 0ustar stacys00000000000000""" Pythonifier for ISIS Table/View """ from jnpr.junos.factory import loadyaml from os.path import splitext _YAML_ = splitext(__file__)[0] + '.yml' globals().update(loadyaml(_YAML_)) junos-eznc-2.1.7/lib/jnpr/junos/op/isis.yml0000644001013500016170000000164513163777564020240 0ustar stacys00000000000000IsisAdjacencyTable: rpc: get-isis-adjacency-information args: extensive: True item: isis-adjacency key: - interface-name - system-name view: IsisAdjacencyView IsisAdjacencyView: fields: interface_name: interface-name system_name: system-name level: level adjacency_state: adjacency-state holdtime: holdtime circuit_type: circuit-type ip_address: ip-address interface_priority: interface-priority adjacency_flag: adjacency-flag adjacency_topologies: adjacency-topologies adjacency_restart_capable: adjacency-restart-capable adjacency_advertisement: adjacency-advertisement adjacency_log: _IsisAdjacencyLogTable _IsisAdjacencyLogTable: item: isis-adjacency-log key: - adjacency-when - adjacency-state view: _IsisAdjacencyLogView _IsisAdjacencyLogView: fields: when: adjacency-when state: adjacency-state event: adjacency-event junos-eznc-2.1.7/lib/jnpr/junos/op/l2circuit.py0000644001013500016170000000027513163777564021016 0ustar stacys00000000000000""" Pythonifier for Route L2circuit/View """ from jnpr.junos.factory import loadyaml from os.path import splitext _YAML_ = splitext(__file__)[0] + '.yml' globals().update(loadyaml(_YAML_)) junos-eznc-2.1.7/lib/jnpr/junos/op/l2circuit.yml0000644001013500016170000000162213163777564021164 0ustar stacys00000000000000--- ### ------------------------------------------------------ ### show l2circuit connections ### ------------------------------------------------------ L2CircuitConnectionTable: rpc: get-l2ckt-connection-information item: l2circuit-neighbor/connection key: - ancestor::l2circuit-neighbor/neighbor-address - connection-id view: L2CircuitConnectionView L2CircuitConnectionView: fields: connection_id: connection-id connection_type: connection-type connection_status: connection-status remote_pe: remote-pe control_word: control-word inboud_label: inbound-label outbound_label: outbound-label pw_status_tlv: pw-status-tlv local_interface: local-interface/interface-name interface_status: local-interface/interface-status interface_encapsulation: local-interface/interface-encapsulation interface_description: local-interface/interface-description junos-eznc-2.1.7/lib/jnpr/junos/op/lacp.py0000644001013500016170000000027013163777564020030 0ustar stacys00000000000000""" Pythonifier for LACP Table/View """ from jnpr.junos.factory import loadyaml from os.path import splitext _YAML_ = splitext(__file__)[0] + '.yml' globals().update(loadyaml(_YAML_)) junos-eznc-2.1.7/lib/jnpr/junos/op/lacp.yml0000644001013500016170000000340113163777564020200 0ustar stacys00000000000000--- # ----------------------------------------------------------------------------- # LACP ports are aggregating-ethernet interfaces, e.g. 'ae0' # ----------------------------------------------------------------------------- LacpPortTable: rpc: get-lacp-interface-information args_key: interface_name item: lacp-interface-information key: lag-lacp-header/aggregate-name view: LacpPortView # the view provides access to two tables states and protocols LacpPortView: fields: state: _LacpPortStateTable proto: _LacpPortProtoTable # ----------------------------------------------------------------------------- # Each LACP port maintains a "state" table that ha a composite key of the # interface name and it's role (Actor/Partner). name with the "_" so this # item does not get exported to the global namespace # ----------------------------------------------------------------------------- _LacpPortStateTable: item: lag-lacp-state key: - name - lacp-role view: _LacpPortStateView _LacpPortStateView: fields: activity: lacp-activity timeout: lacp-timeout expired: { lacp-expired: True=Yes } defaulted: { lacp-defaulted: True=Yes } distributing: { lacp-distributing: True=Yes } collecting: { lacp-collecting: True=Yes } sync : { lacp-synchronization: True=Yes } aggregation: { lacp-aggregation: True=Yes } # ----------------------------------------------------------------------------- # Each LACP port maintains a "protocol" table # ----------------------------------------------------------------------------- _LacpPortProtoTable: item: lag-lacp-protocol view: _LacpPortProtoView _LacpPortProtoView: fields: rx_state: lacp-receive-state tx_state: lacp-transmit-state mx_state: lacp-mux-state junos-eznc-2.1.7/lib/jnpr/junos/op/ldp.py0000644001013500016170000000026713163777564017676 0ustar stacys00000000000000""" Pythonifier for LDP Table/View """ from jnpr.junos.factory import loadyaml from os.path import splitext _YAML_ = splitext(__file__)[0] + '.yml' globals().update(loadyaml(_YAML_)) junos-eznc-2.1.7/lib/jnpr/junos/op/ldp.yml0000644001013500016170000000217713163777564020051 0ustar stacys00000000000000--- LdpNeighborTable: rpc: get-ldp-neighbor-information args: extensive: True item: ldp-neighbor key: - interface-name - ldp-neighbor-address view: LdpNeighborView LdpNeighborView: fields: interface_name: interface-name neighbor_address: ldp-neighbor-address label_space_id: ldp-label-space-id remaining_time: ldp-remaining-time transport_address: ldp-transport-address config_sequence: ldp-config-sequence up_time: ldp-up-time reference_count: ldp-reference-count holdtime: ldp-holdtime proposed_local_holdtime: ldp-proposed-local-holdtime proposed_peer_holdtime: ldp-proposed-peer-holdtime hello_interval: ldp-hello-interval hello_flags: _LdpNeighborHelloFlagsTable types: _LdpNeighborTypesTable _LdpNeighborHelloFlagsTable: item: ldp-neighbor-hello-flags key: ldp-neighbor-hello-flag view: _LdpNeighborHelloFlagsView _LdpNeighborHelloFlagsView: fields: flag: ldp-neighbor-hello-flag _LdpNeighborTypesTable: item: ldp-neighbor-types key: ldp-neighbor-type view: _LdpNeighborTypesView _LdpNeighborTypesView: fields: type: ldp-neighbor-type junos-eznc-2.1.7/lib/jnpr/junos/op/lldp.py0000644001013500016170000000027013163777565020045 0ustar stacys00000000000000""" Pythonifier for LLDP Table/View """ from jnpr.junos.factory import loadyaml from os.path import splitext _YAML_ = splitext(__file__)[0] + '.yml' globals().update(loadyaml(_YAML_)) junos-eznc-2.1.7/lib/jnpr/junos/op/lldp.yml0000644001013500016170000000102713163777565020217 0ustar stacys00000000000000--- LLDPNeighborTable: rpc: get-lldp-neighbors-information item: lldp-neighbor-information key: lldp-local-interface | lldp-local-port-id view: LLDPNeighborView LLDPNeighborView: fields: local_int: lldp-local-interface | lldp-local-port-id local_parent: lldp-local-parent-interface-name remote_type: lldp-remote-chassis-id-subtype remote_chassis_id: lldp-remote-chassis-id remote_port_desc: lldp-remote-port-description remote_port_id: lldp-remote-port-id remote_sysname: lldp-remote-system-name junos-eznc-2.1.7/lib/jnpr/junos/op/nd.py0000644001013500016170000000031313163777565017511 0ustar stacys00000000000000""" Pythonifier for IPv6 Neighbor Discovery Table/View """ from jnpr.junos.factory import loadyaml from os.path import splitext _YAML_ = splitext(__file__)[0] + '.yml' globals().update(loadyaml(_YAML_)) junos-eznc-2.1.7/lib/jnpr/junos/op/nd.yml0000644001013500016170000000044513163777565017670 0ustar stacys00000000000000--- NdTable: rpc: get-ipv6-nd-information item: ipv6-nd-entry key: ipv6-nd-neighbor-l2-address view: NdView NdView: fields: mac_address: ipv6-nd-neighbor-l2-address ip_address: ipv6-nd-neighbor-address interface_name: ipv6-nd-interface-name router: ipv6-nd-isrouter junos-eznc-2.1.7/lib/jnpr/junos/op/ospf.py0000644001013500016170000000027013163777565020061 0ustar stacys00000000000000""" Pythonifier for OSPF Table/View """ from jnpr.junos.factory import loadyaml from os.path import splitext _YAML_ = splitext(__file__)[0] + '.yml' globals().update(loadyaml(_YAML_)) junos-eznc-2.1.7/lib/jnpr/junos/op/ospf.yml0000644001013500016170000000256113163777565020237 0ustar stacys00000000000000OspfNeighborTable: rpc: get-ospf-neighbor-information args: extensive: True item: ospf-neighbor key: - interface-name - neighbor-id view: OspfNeighborView OspfNeighborView: fields: neighbor_address: neighbor-address interface_name: interface-name ospf_neighbor_state: ospf-neighbor-state neighbor_id: neighbor-id activity_timer: activity-timer ospf_area: ospf-area dr_address: dr-address bdr_address: bdr-address neighbor_up_time: neighbor-up-time neighbor_adjacency_time: neighbor-adjacency-time OspfInterfaceTable: rpc: get-ospf-interface-information item: ospf-interface key: interface-name view: OspfInterfaceView OspfInterfaceView: fields: interface_name: interface-name ospf_interface_state: ospf-interface-state neighbor_count: neighbor-count ospfTable: rpc: get-ospf-overview-information item: ospf-overview key: ospf-area-overview/ospf-area view: ospfView ospfView: fields: router_id: ospf-router-id area: ospf-area-overview/ospf-area neighbors: ospf-area-overview/ospf-nbr-up-count OspfRoutesTable: rpc: get-ospf-route-information item: ospf-topology-route-table/ospf-route key: ospf-route-entry/address-prefix view: OspfRoutesView OspfRoutesView: fields: prefix: ospf-route-entry/address-prefix interface: ospf-route-entry/ospf-next-hop/next-hop-name/interface-name junos-eznc-2.1.7/lib/jnpr/junos/op/phyport.py0000644001013500016170000000027313163777565020622 0ustar stacys00000000000000""" Pythonifier for PhyPort Table/View """ from jnpr.junos.factory import loadyaml from os.path import splitext _YAML_ = splitext(__file__)[0] + '.yml' globals().update(loadyaml(_YAML_)) junos-eznc-2.1.7/lib/jnpr/junos/op/phyport.yml0000644001013500016170000000516613163777565021001 0ustar stacys00000000000000--- PhyPortTable: rpc: get-interface-information args: interface_name: '[efgx][et]-*' args_key: interface_name item: physical-interface view: PhyPortView PhyPortView: fields: oper : oper-status admin : admin-status description: description mtu: { mtu : int } link_mode: link-mode speed: speed macaddr: current-physical-address flapped: interface-flapped ### --------------------------------------------------------------------------- ### get extensive information ### --------------------------------------------------------------------------- PhyPortStatsTable: rpc: get-interface-information args: extensive: True interface_name: '[efgx][et]-*' args_key: interface_name item: physical-interface view: PhyPortStatsView PhyPortStatsView: groups: ts: traffic-statistics rxerrs: input-error-list # fields that are part of groups are called # "fields_" fields_ts: rx_bytes: { input-bytes: int } rx_packets: { input-packets: int } tx_bytes: { output-bytes: int } tx_packets: { output-packets: int } fields_rxerrs: rx_err_input: { input-errors: int } rx_err_drops: { input-drops: int } PhyPortErrorTable: rpc: get-interface-information args: extensive: True interface_name: '[efgx][et]-*' args_key: interface_name item: physical-interface view: PhyPortErrorView PhyPortErrorView: groups: ts: traffic-statistics rxerrs: input-error-list txerrs: output-error-list # fields that are part of groups are called # "fields_" fields_ts: rx_bytes: { input-bytes: int } rx_packets: { input-packets: int } tx_bytes: { output-bytes: int } tx_packets: { output-packets: int } fields_rxerrs: rx_err_input: { input-errors: int } rx_err_drops: { input-drops: int } rx_err_frame: { framing-errors: int } rx_err_runts: { input-runts: int } rx_err_discards: { input-discards: int } rx_err_l3-incompletes: { input-l3-incompletes: int } rx_err_l2-channel: { input-l2-channel-errors: int } rx_err_l2-mismatch: { input-l2-mismatch-timeouts: int } rx_err_fifo: { input-fifo-errors: int } rx_err_resource: { input-resource-errors: int } fields_txerrs: tx_err_carrier-transitions: { carrier-transitions: int } tx_err_output: { output-errors: int } tx_err_collisions: { output-collisions: int } tx_err_drops: { output-drops: int } tx_err_aged: { aged-packets: int } tx_err_mtu: { mtu-errors: int } tx_err_hs-crc: { hs-link-crc-errors: int } tx_err_fifo: { output-fifo-errors: int } tx_err_resource: { output-resource-errors: int } junos-eznc-2.1.7/lib/jnpr/junos/op/routes.py0000644001013500016170000000027113163777565020434 0ustar stacys00000000000000""" Pythonifier for Route Table/View """ from jnpr.junos.factory import loadyaml from os.path import splitext _YAML_ = splitext(__file__)[0] + '.yml' globals().update(loadyaml(_YAML_)) junos-eznc-2.1.7/lib/jnpr/junos/op/routes.yml0000644001013500016170000000226013163777565020605 0ustar stacys00000000000000--- ### ------------------------------------------------------ ### show route ### ------------------------------------------------------ RouteTable: rpc: get-route-information args_key: destination item: route-table/rt key: rt-destination view: RouteTableView RouteTableView: groups: entry: rt-entry fields_entry: # fields taken from the group 'entry' protocol: protocol-name via: nh/via | nh/nh-local-interface age: { age/@seconds : int } nexthop: nh/to ### ------------------------------------------------------ ### show route summary ### ------------------------------------------------------ RouteSummaryTable: rpc: get-route-summary-information item: route-table key: table-name view: RouteSummaryView RouteSummaryView: fields: dests: { destination-count : int } total: { total-route-count : int } active: { active-route-count : int } holddown: { holddown-route-count : int } hidden: { hidden-route-count : int } proto: _rspTable _rspTable: item: protocols key: protocol-name view: _rspView _rspView: fields: count: { protocol-route-count: int } active: { active-route-count : int } junos-eznc-2.1.7/lib/jnpr/junos/op/teddb.py0000644001013500016170000000026713163777565020202 0ustar stacys00000000000000""" Pythonifier for ted database Table/View """ from ..factory import loadyaml from os.path import splitext _YAML_ = splitext(__file__)[0] + '.yml' globals().update(loadyaml(_YAML_)) junos-eznc-2.1.7/lib/jnpr/junos/op/teddb.yml0000644001013500016170000000205613163777565020351 0ustar stacys00000000000000### ------------------------------------------------------ #### show ted database #### ------------------------------------------------------ TedTable: rpc: get-ted-database-information args: extensive: True item: ted-database key: ted-database-id view: TedView TedView: fields: type: ted-database-type age: { ted-database-age : int } link-in: { ted-database-link-in : int } link-out: { ted-database-link-out : int } protocol: ted-database-protocol link: _linkTable _linkTable: item: ted-link key: ted-link-local-ifindex view: _linkView _linkView: fields: remoteRTR: ted-link-to remoteIfI: { ted-link-remote-ifindex : int } remoteADD: ted-link-remote-address localIfI: { ted-link-local-ifindex : int } localADD: ted-link-local-address metric: { ted-link-metric : int } color: admin-groups/color TedSummaryTable: rpc: get-ted-database-information item: ted-database-summary view: TedSummaryView TedSummaryView: fields: iso-count: { ted-database-iso-count : int } inet-count: { ted-database-inet-count : int } junos-eznc-2.1.7/lib/jnpr/junos/op/vlan.py0000644001013500016170000000027013163777565020052 0ustar stacys00000000000000""" Pythonifier for Vlan Table/View """ from jnpr.junos.factory import loadyaml from os.path import splitext _YAML_ = splitext(__file__)[0] + '.yml' globals().update(loadyaml(_YAML_)) junos-eznc-2.1.7/lib/jnpr/junos/op/vlan.yml0000644001013500016170000000044513163777565020227 0ustar stacys00000000000000--- VlanTable: rpc: get-vlan-information item: vlan key: vlan-name view: VlanView VlanView: fields: instance: vlan-instance name: vlan-name created: vlan-create-time status: vlan-status owner: vlan-owner tag: vlan-tag members: .//vlan-member-interface junos-eznc-2.1.7/lib/jnpr/junos/op/xcvr.py0000644001013500016170000000027013163777565020074 0ustar stacys00000000000000""" Pythonifier for Xcvr Table/View """ from jnpr.junos.factory import loadyaml from os.path import splitext _YAML_ = splitext(__file__)[0] + '.yml' globals().update(loadyaml(_YAML_)) junos-eznc-2.1.7/lib/jnpr/junos/op/xcvr.yml0000644001013500016170000000050513163777565020246 0ustar stacys00000000000000--- XcvrTable: rpc: get-chassis-inventory item: //*[starts-with(name,"Xcvr")] key: - ancestor::*[starts-with(name,'FPC')]/name - ancestor::*[starts-with(name,'PIC')]/name - name view: XcvrTableView XcvrTableView: fields: sn: serial-number pn: part-number ver: version type: description junos-eznc-2.1.7/lib/jnpr/junos/resources/0000755001013500016170000000000013163777613020127 5ustar stacys00000000000000junos-eznc-2.1.7/lib/jnpr/junos/resources/__init__.py0000644001013500016170000000000013163777565022234 0ustar stacys00000000000000junos-eznc-2.1.7/lib/jnpr/junos/resources/autosys.py0000644001013500016170000000027313163777565022220 0ustar stacys00000000000000""" Pythonifier for AutoSys Table/View """ from jnpr.junos.factory import loadyaml from os.path import splitext _YAML_ = splitext(__file__)[0] + '.yml' globals().update(loadyaml(_YAML_)) junos-eznc-2.1.7/lib/jnpr/junos/resources/autosys.yml0000644001013500016170000000047213163777565022372 0ustar stacys00000000000000--- ### ------------------------------------------------------ ### Table-view to configure Autonomous system. ### ------------------------------------------------------ AutoSysTable: set: routing-options/autonomous-system key-field: - as_num view: AutoSysView AutoSysView: fields: as_num: as-numberjunos-eznc-2.1.7/lib/jnpr/junos/resources/bgp.py0000644001013500016170000000026713163777565021264 0ustar stacys00000000000000""" Pythonifier for BGP Table/View """ from jnpr.junos.factory import loadyaml from os.path import splitext _YAML_ = splitext(__file__)[0] + '.yml' globals().update(loadyaml(_YAML_)) junos-eznc-2.1.7/lib/jnpr/junos/resources/bgp.yml0000644001013500016170000000106513163777565021432 0ustar stacys00000000000000--- ### ------------------------------------------------------ ### Table-view to configure BGP. ### ------------------------------------------------------ BgpTable: set: protocols/bgp/group key-field: - bgp_name view: BgpView BgpView: groups: neigh : neighbor fields: bgp_name : { 'name' : { 'type' : 'str', 'minValue' : 1, 'maxValue' : 255} } bgp_type : {'type' : {'type': { 'enum': ['external', 'internal'] } } } local_addr : local-address peer : { 'peer-as' : { 'type' : 'int' } } fields_neigh: neigh_addr : namejunos-eznc-2.1.7/lib/jnpr/junos/resources/interface.py0000644001013500016170000000027513163777565022453 0ustar stacys00000000000000""" Pythonifier for interface table/view """ from jnpr.junos.factory import loadyaml from os.path import splitext _YAML_ = splitext(__file__)[0] + '.yml' globals().update(loadyaml(_YAML_)) junos-eznc-2.1.7/lib/jnpr/junos/resources/interface.yml0000644001013500016170000000067213163777565022625 0ustar stacys00000000000000InterfaceTable: set: interfaces/interface key-field: - name - unit_name view: InterfaceView InterfaceView: groups: unit: unit bridge: unit/family/bridge fields: name: name disable: { 'disable' : { 'type': 'bool' } } enc : encapsulation native_vlan: native-vlan-id fields_unit: unit_name : name desc : description fields_bridge: mode: interface-mode vlan_list: vlan-id-listjunos-eznc-2.1.7/lib/jnpr/junos/resources/staticroutes.py0000644001013500016170000000030013163777565023231 0ustar stacys00000000000000""" Pythonifier for Static route Table/View """ from jnpr.junos.factory import loadyaml from os.path import splitext _YAML_ = splitext(__file__)[0] + '.yml' globals().update(loadyaml(_YAML_)) junos-eznc-2.1.7/lib/jnpr/junos/resources/staticroutes.yml0000644001013500016170000000052313163777565023411 0ustar stacys00000000000000--- ### ------------------------------------------------------ ### Table-view to configure Static routes. ### ------------------------------------------------------ StaticRouteTable: set: routing-options/static/route key-field: - route_name view: StaticRouteView StaticRouteView: fields: route_name: name hop: next-hopjunos-eznc-2.1.7/lib/jnpr/junos/resources/syslog.py0000644001013500016170000000027213163777565022030 0ustar stacys00000000000000""" Pythonifier for Syslog Table/View """ from jnpr.junos.factory import loadyaml from os.path import splitext _YAML_ = splitext(__file__)[0] + '.yml' globals().update(loadyaml(_YAML_)) junos-eznc-2.1.7/lib/jnpr/junos/resources/syslog.yml0000644001013500016170000000071713163777565022205 0ustar stacys00000000000000--- ### ------------------------------------------------------ ### Table-view to configure Syslog. ### ------------------------------------------------------ SyslogTable: set: system/syslog/file key-field: - name - contents_name view: SyslogView SyslogView: groups: contents: contents fields: name : { 'name' : { 'default' : 'messages' } } fields_contents: info : info contents_name : name error : errorjunos-eznc-2.1.7/lib/jnpr/junos/resources/user.py0000644001013500016170000000027013163777565021464 0ustar stacys00000000000000""" Pythonifier for User Table/View """ from jnpr.junos.factory import loadyaml from os.path import splitext _YAML_ = splitext(__file__)[0] + '.yml' globals().update(loadyaml(_YAML_)) junos-eznc-2.1.7/lib/jnpr/junos/resources/user.yml0000644001013500016170000000101113163777565021627 0ustar stacys00000000000000--- ### ------------------------------------------------------ ### Table-view to configure login user. ### ------------------------------------------------------ UserTable: set: system/login/user key-field: user view: userView userView: groups: auth: authentication fields: user: name uid: { uid : { 'type' : 'int', 'minValue' : 100, 'maxValue' : 64000 } } class_name: { class : { 'type' : { 'enum' : ['operator', 'read-only', 'super-user'] } } } fields_auth: password: encrypted-passwordjunos-eznc-2.1.7/lib/jnpr/junos/rpcmeta.py0000644001013500016170000003710213163777565020133 0ustar stacys00000000000000import re import sys from lxml import etree from lxml.builder import E from jnpr.junos import jxml as JXML class _RpcMetaExec(object): # ----------------------------------------------------------------------- # CONSTRUCTOR # ----------------------------------------------------------------------- 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 # ----------------------------------------------------------------------- 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='') dev.rpc.get_config(filter_xml='system/services') dev.rpc.get_config( filter_xml=etree.XML(''), 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='', 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(''' ''')) :options: is a dictionary of XML attributes to set within the 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(''' '''), 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 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 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 # ----------------------------------------------------------------------- def get(self, filter_select=None, ignore_warning=False, **kwargs): """ Retrieve running configuration and device state information using 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 # ----------------------------------------------------------------------- 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 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 :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 # ----------------------------------------------------------------------- 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) junos-eznc-2.1.7/lib/jnpr/junos/transport/0000755001013500016170000000000013163777613020151 5ustar stacys00000000000000junos-eznc-2.1.7/lib/jnpr/junos/transport/__init__.py0000644001013500016170000000000013163777565022256 0ustar stacys00000000000000junos-eznc-2.1.7/lib/jnpr/junos/transport/tty.py0000644001013500016170000002100013163777565021342 0ustar stacys00000000000000from time import sleep import logging from jnpr.junos import exception as EzErrors from jnpr.junos.transport.tty_netconf import tty_netconf logger = logging.getLogger("jnpr.junos.tty") __all__ = ['Terminal'] # ========================================================================= # Terminal class # ========================================================================= class Terminal(object): """ Terminal is used to bootstrap Junos New Out of the Box (NOOB) device over the CONSOLE port. The general use-case is to setup the minimal configuration so that the device is IP reachable using SSH and NETCONF for remote management. Serial is needed for Junos devices that do not support the DHCP 'auto-installation' or 'ZTP' feature; i.e. you *MUST* do the NOOB configuration via the CONSOLE. Serial is also useful for situations even when the Junos device supports auto-DHCP, but is not an option due to the specific situation """ TIMEOUT = 0.2 # serial readline timeout, seconds EXPECT_TIMEOUT = 10 # total read timeout, seconds LOGIN_RETRY = 20 # total number of passes thru login state-machine _ST_INIT = 0 _ST_LOADER = 1 _ST_LOGIN = 2 _ST_PASSWD = 3 _ST_DONE = 4 _ST_BAD_PASSWD = 5 _ST_TTY_NOLOGIN = 6 _ST_TTY_OPTION = 7 _ST_TTY_HOTKEY = 8 _re_pat_login = '(?Pogin:\s*$)' _RE_PAT = [ '(?Poader>\s*$)', _re_pat_login, '(?Password:\s*$)', '(?Pogin incorrect)', '(?P\s*)', '(?P%|#\s*$)', '(?P[^\\-"]>\s*$)', '(?P