[Python-modules-commits] [napalm-junos] 01/03: New upstream release.

Vincent Bernat bernat at moszumanska.debian.org
Tue Nov 14 09:54:40 UTC 2017


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

bernat pushed a commit to annotated tag debian/0.12.1-1
in repository napalm-junos.

commit 1d5ed8e892164de27549ab32093b5d8999212a25
Author: Vincent Bernat <bernat at debian.org>
Date:   Tue Nov 14 10:52:40 2017 +0100

    New upstream release.
---
 PKG-INFO                               |   2 +-
 napalm_junos.egg-info/PKG-INFO         |   2 +-
 napalm_junos.egg-info/SOURCES.txt      |   2 +
 napalm_junos.egg-info/requires.txt     |   4 +-
 napalm_junos/junos.py                  | 717 +++++++++++++++++++++++++++------
 napalm_junos/templates/delete_users.j2 |  26 ++
 napalm_junos/templates/set_users.j2    |  34 ++
 napalm_junos/utils/junos_views.yml     |  70 +++-
 requirements.txt                       |   4 +-
 setup.cfg                              |   1 -
 setup.py                               |   2 +-
 11 files changed, 724 insertions(+), 140 deletions(-)

diff --git a/PKG-INFO b/PKG-INFO
index 2f79b12..454f55e 100644
--- a/PKG-INFO
+++ b/PKG-INFO
@@ -1,6 +1,6 @@
 Metadata-Version: 1.1
 Name: napalm-junos
-Version: 0.6.0
+Version: 0.12.1
 Summary: Network Automation and Programmability Abstraction Layer with Multivendor support
 Home-page: https://github.com/napalm-automation/napalm-junos
 Author: David Barroso, Mircea Ulinic
diff --git a/napalm_junos.egg-info/PKG-INFO b/napalm_junos.egg-info/PKG-INFO
index 2f79b12..454f55e 100644
--- a/napalm_junos.egg-info/PKG-INFO
+++ b/napalm_junos.egg-info/PKG-INFO
@@ -1,6 +1,6 @@
 Metadata-Version: 1.1
 Name: napalm-junos
-Version: 0.6.0
+Version: 0.12.1
 Summary: Network Automation and Programmability Abstraction Layer with Multivendor support
 Home-page: https://github.com/napalm-automation/napalm-junos
 Author: David Barroso, Mircea Ulinic
diff --git a/napalm_junos.egg-info/SOURCES.txt b/napalm_junos.egg-info/SOURCES.txt
index affcf58..33e9330 100644
--- a/napalm_junos.egg-info/SOURCES.txt
+++ b/napalm_junos.egg-info/SOURCES.txt
@@ -14,11 +14,13 @@ napalm_junos/templates/delete_ntp_peers.j2
 napalm_junos/templates/delete_ntp_servers.j2
 napalm_junos/templates/delete_probes.j2
 napalm_junos/templates/delete_snmp_config.j2
+napalm_junos/templates/delete_users.j2
 napalm_junos/templates/schedule_probes.j2
 napalm_junos/templates/set_hostname.j2
 napalm_junos/templates/set_ntp_peers.j2
 napalm_junos/templates/set_ntp_servers.j2
 napalm_junos/templates/set_probes.j2
+napalm_junos/templates/set_users.j2
 napalm_junos/templates/snmp_config.j2
 napalm_junos/utils/__init__.py
 napalm_junos/utils/junos_views.py
diff --git a/napalm_junos.egg-info/requires.txt b/napalm_junos.egg-info/requires.txt
index 724db5e..cc42db9 100644
--- a/napalm_junos.egg-info/requires.txt
+++ b/napalm_junos.egg-info/requires.txt
@@ -1,2 +1,2 @@
-napalm-base>=0.20.3
-junos-eznc
+napalm-base>=0.24.1
+junos-eznc>=2.1.5
diff --git a/napalm_junos/junos.py b/napalm_junos/junos.py
index 0fadf31..363192d 100644
--- a/napalm_junos/junos.py
+++ b/napalm_junos/junos.py
@@ -19,11 +19,15 @@ from __future__ import unicode_literals
 
 # import stdlib
 import re
+import json
+import logging
 import collections
 from copy import deepcopy
+from collections import OrderedDict
 
 # import third party lib
 from lxml.builder import E
+from lxml import etree
 
 from jnpr.junos import Device
 from jnpr.junos.utils.config import Config
@@ -35,7 +39,6 @@ from jnpr.junos.exception import ConnectTimeoutError
 # import NAPALM Base
 import napalm_base.helpers
 from napalm_base.base import NetworkDriver
-from napalm_base.utils import string_parsers
 from napalm_base.utils import py23_compat
 import napalm_junos.constants as C
 from napalm_base.exceptions import ConnectionException
@@ -47,6 +50,8 @@ from napalm_base.exceptions import CommandTimeoutException
 # import local modules
 from napalm_junos.utils import junos_views
 
+log = logging.getLogger(__file__)
+
 
 class JunOSDriver(NetworkDriver):
     """JunOSDriver class - inherits NetworkDriver from napalm_base."""
@@ -56,8 +61,11 @@ class JunOSDriver(NetworkDriver):
         Initialise JunOS driver.
 
         Optional args:
-            * port (int): custom port
             * config_lock (True/False): lock configuration DB after the connection is established.
+            * port (int): custom port
+            * key_file (string): SSH key file path
+            * keepalive (int): Keepalive interval
+            * ignore_warning (boolean): not generate warning exceptions
         """
         self.hostname = hostname
         self.username = username
@@ -66,12 +74,32 @@ class JunOSDriver(NetworkDriver):
         self.config_replace = False
         self.locked = False
 
+        # Get optional arguments
         if optional_args is None:
             optional_args = {}
+
+        self.config_lock = optional_args.get('config_lock', False)
         self.port = optional_args.get('port', 22)
-        self.config_lock = optional_args.get('config_lock', True)
+        self.key_file = optional_args.get('key_file', None)
+        self.keepalive = optional_args.get('keepalive', 30)
+        self.ssh_config_file = optional_args.get('ssh_config_file', None)
+        self.ignore_warning = optional_args.get('ignore_warning', False)
+
+        if self.key_file:
+            self.device = Device(hostname,
+                                 user=username,
+                                 password=password,
+                                 ssh_private_key_file=self.key_file,
+                                 ssh_config=self.ssh_config_file,
+                                 port=self.port)
+        else:
+            self.device = Device(hostname,
+                                 user=username,
+                                 password=password,
+                                 port=self.port,
+                                 ssh_config=self.ssh_config_file)
 
-        self.device = Device(hostname, user=username, password=password, port=self.port)
+        self.profile = ["junos"]
 
     def open(self):
         """Open the connection wit the device."""
@@ -80,6 +108,7 @@ class JunOSDriver(NetworkDriver):
         except ConnectTimeoutError as cte:
             raise ConnectionException(cte.message)
         self.device.timeout = self.timeout
+        self.device._conn._session.transport.set_keepalive(self.keepalive)
         if hasattr(self.device, "cu"):
             # make sure to remove the cu attr from previous session
             # ValueError: requested attribute name cu already exists
@@ -106,6 +135,23 @@ class JunOSDriver(NetworkDriver):
             self.device.cu.unlock()
             self.locked = False
 
+    def _rpc(self, get, child=None, **kwargs):
+        """
+        This allows you to construct an arbitrary RPC call to retreive common stuff. For example:
+        Configuration:  get: "<get-configuration/>"
+        Interface information:  get: "<get-interface-information/>"
+        A particular interfacece information:
+              get: "<get-interface-information/>"
+              child: "<interface-name>ge-0/0/0</interface-name>"
+        """
+        rpc = etree.fromstring(get)
+
+        if child:
+            rpc.append(etree.fromstring(child))
+
+        response = self.device.execute(rpc)
+        return etree.tostring(response)
+
     def is_alive(self):
         # evaluate the state of the underlying SSH connection
         # and also the NETCONF status from PyEZ
@@ -113,6 +159,36 @@ class JunOSDriver(NetworkDriver):
             'is_alive': self.device._conn._session.transport.is_active() and self.device.connected
         }
 
+    @staticmethod
+    def _is_json_format(config):
+        try:
+            _ = json.loads(config)  # noqa
+        except (TypeError, ValueError):
+            return False
+        return True
+
+    def _detect_config_format(self, config):
+        fmt = 'text'
+        set_action_matches = [
+            'set',
+            'activate',
+            'deactivate',
+            'annotate',
+            'copy',
+            'delete',
+            'insert',
+            'protect',
+            'rename',
+            'unprotect',
+        ]
+        if config.strip().startswith('<'):
+            return 'xml'
+        elif config.strip().split(' ')[0] in set_action_matches:
+            return 'set'
+        elif self._is_json_format(config):
+            return 'json'
+        return fmt
+
     def _load_candidate(self, filename, config, overwrite):
         if filename is None:
             configuration = config
@@ -127,12 +203,18 @@ class JunOSDriver(NetworkDriver):
             # and the device will be locked till first commit/rollback
 
         try:
-            self.device.cu.load(configuration, format='text', overwrite=overwrite)
+            fmt = self._detect_config_format(configuration)
+
+            if fmt == "xml":
+                configuration = etree.XML(configuration)
+
+            self.device.cu.load(configuration, format=fmt, overwrite=overwrite,
+                                ignore_warning=self.ignore_warning)
         except ConfigLoadError as e:
             if self.config_replace:
-                raise ReplaceConfigException(e.message)
+                raise ReplaceConfigException(e.errs)
             else:
-                raise MergeConfigException(e.message)
+                raise MergeConfigException(e.errs)
 
     def load_replace_candidate(self, filename=None, config=None):
         """Open the candidate config and merge."""
@@ -155,7 +237,7 @@ class JunOSDriver(NetworkDriver):
 
     def commit_config(self):
         """Commit configuration."""
-        self.device.cu.commit()
+        self.device.cu.commit(ignore_warning=self.ignore_warning)
         if not self.config_lock:
             self._unlock()
 
@@ -174,9 +256,7 @@ class JunOSDriver(NetworkDriver):
         """Return facts of the device."""
         output = self.device.facts
 
-        uptime = '0'
-        if 'RE0' in output:
-            uptime = output['RE0']['up_time']
+        uptime = self.device.uptime or -1
 
         interfaces = junos_views.junos_iface_table(self.device)
         interfaces.get()
@@ -189,7 +269,7 @@ class JunOSDriver(NetworkDriver):
             'os_version': py23_compat.text_type(output['version']),
             'hostname': py23_compat.text_type(output['hostname']),
             'fqdn': py23_compat.text_type(output['fqdn']),
-            'uptime': string_parsers.convert_uptime_string_seconds(uptime),
+            'uptime': uptime,
             'interface_list': interface_list
         }
 
@@ -242,14 +322,25 @@ class JunOSDriver(NetworkDriver):
         environment = junos_views.junos_enviroment_table(self.device)
         routing_engine = junos_views.junos_routing_engine_table(self.device)
         temperature_thresholds = junos_views.junos_temperature_thresholds(self.device)
+        power_supplies = junos_views.junos_pem_table(self.device)
         environment.get()
         routing_engine.get()
         temperature_thresholds.get()
         environment_data = {}
+        current_class = None
 
         for sensor_object, object_data in environment.items():
             structured_object_data = {k: v for k, v in object_data}
 
+            if structured_object_data['class']:
+                # If current object has a 'class' defined, store it for use
+                # on subsequent unlabeled lines.
+                current_class = structured_object_data['class']
+            else:
+                # Juniper doesn't label the 2nd+ lines of a given class with a
+                # class name.  In that case, we use the most recent class seen.
+                structured_object_data['class'] = current_class
+
             if structured_object_data['class'] == 'Power':
                 # Create a dict for the 'power' key
                 try:
@@ -258,7 +349,6 @@ class JunOSDriver(NetworkDriver):
                     environment_data['power'] = {}
                     environment_data['power'][sensor_object] = {}
 
-                # Set these values to -1, because Junos does not provide them
                 environment_data['power'][sensor_object]['capacity'] = -1.0
                 environment_data['power'][sensor_object]['output'] = -1.0
 
@@ -310,6 +400,21 @@ class JunOSDriver(NetworkDriver):
                     elif structured_temperature_data['yellow-alarm'] <= temp:
                         environment_data['temperature'][sensor_object]['is_alert'] = True
 
+        # Try to correct Power Supply information
+        pem_table = dict()
+        try:
+            power_supplies.get()
+        except RpcError:
+            # Not all platforms have support for this
+            pass
+        else:
+            # Format PEM information and correct capacity and output values
+            for pem in power_supplies.items():
+                pem_name = pem[0].replace("PEM", "Power Supply")
+                pem_table[pem_name] = dict(pem[1])
+                environment_data['power'][pem_name]['capacity'] = pem_table[pem_name]['capacity']
+                environment_data['power'][pem_name]['output'] = pem_table[pem_name]['output']
+
         for routing_engine_object, routing_engine_data in routing_engine.items():
             structured_routing_engine_data = {k: v for k, v in routing_engine_data}
             # Create dicts for 'cpu' and 'memory'.
@@ -363,16 +468,34 @@ class JunOSDriver(NetworkDriver):
         return address_family
 
     def _parse_route_stats(self, neighbor):
-        data = {}
+        data = {
+            'ipv4': {
+                'received_prefixes': -1,
+                'accepted_prefixes': -1,
+                'sent_prefixes': -1
+            },
+            'ipv6': {
+                'received_prefixes': -1,
+                'accepted_prefixes': -1,
+                'sent_prefixes': -1
+            }
+        }
         if not neighbor['is_up']:
-            pass
+            return data
         elif isinstance(neighbor['tables'], list):
+            if isinstance(neighbor['sent_prefixes'], int):
+                # We expect sent_prefixes to be a list, but sometimes it
+                # is of type int. Therefore convert attribute to list
+                neighbor['sent_prefixes'] = [neighbor['sent_prefixes']]
             for idx, table in enumerate(neighbor['tables']):
                 family = self._get_address_family(table)
                 data[family] = {}
                 data[family]['received_prefixes'] = neighbor['received_prefixes'][idx]
                 data[family]['accepted_prefixes'] = neighbor['accepted_prefixes'][idx]
-                data[family]['sent_prefixes'] = neighbor['sent_prefixes'][idx]
+                if 'in sync' in neighbor['send-state'][idx]:
+                    data[family]['sent_prefixes'] = neighbor['sent_prefixes'].pop(0)
+                else:
+                    data[family]['sent_prefixes'] = 0
         else:
             family = self._get_address_family(neighbor['tables'])
             data[family] = {}
@@ -392,33 +515,112 @@ class JunOSDriver(NetworkDriver):
 
     def get_bgp_neighbors(self):
         """Return BGP neighbors details."""
-        instances = junos_views.junos_route_instance_table(self.device)
-        uptime_table = junos_views.junos_bgp_uptime_table(self.device)
-        bgp_neighbors = junos_views.junos_bgp_table(self.device)
-        keys = ['local_as', 'remote_as', 'is_up', 'is_enabled', 'description', 'remote_id']
         bgp_neighbor_data = {}
-        for instance, instance_data in instances.get().items():
-            if instance.startswith('__'):
-                # junos internal instances
-                continue
-            instance_name = "global" if instance == 'master' else instance
-            bgp_neighbor_data[instance_name] = {'peers': {}}
-            for neighbor, data in bgp_neighbors.get(instance=instance).items():
-                neighbor_data = {k: v for k, v in data}
-                peer_ip = napalm_base.helpers.ip(neighbor.split('+')[0])
+        default_neighbor_details = {
+            'local_as': 0,
+            'remote_as': 0,
+            'remote_id': '',
+            'is_up': False,
+            'is_enabled': False,
+            'description': '',
+            'uptime': 0,
+            'address_family': {}
+        }
+        keys = default_neighbor_details.keys()
+
+        uptime_table = junos_views.junos_bgp_uptime_table(self.device)
+        bgp_neighbors_table = junos_views.junos_bgp_table(self.device)
+
+        uptime_table_lookup = {}
+
+        def _get_uptime_table(instance):
+            if instance not in uptime_table_lookup:
+                uptime_table_lookup[instance] = uptime_table.get(instance=instance).items()
+            return uptime_table_lookup[instance]
+
+        def _get_bgp_neighbors_core(neighbor_data, instance=None, uptime_table_items=None):
+            '''
+            Make sure to execute a simple request whenever using
+            junos > 13. This is a helper used to avoid code redundancy
+            and reuse the function also when iterating through the list
+            BGP neighbors under a specific routing instance,
+            also when the device is capable to return the routing
+            instance name at the BGP neighbor level.
+            '''
+            for bgp_neighbor in neighbor_data:
+                peer_ip = napalm_base.helpers.ip(bgp_neighbor[0].split('+')[0])
+                neighbor_details = deepcopy(default_neighbor_details)
+                neighbor_details.update(
+                    {elem[0]: elem[1] for elem in bgp_neighbor[1] if elem[1] is not None}
+                )
+                if not instance:
+                    # not instance, means newer Junos version,
+                    # as we request everything in a single request
+                    peer_fwd_rti = neighbor_details.pop('peer_fwd_rti')
+                    instance = peer_fwd_rti
+                else:
+                    # instance is explicitly requests,
+                    # thus it's an old Junos, so we retrieve the BGP neighbors
+                    # under a certain routing instance
+                    peer_fwd_rti = neighbor_details.pop('peer_fwd_rti', '')
+                instance_name = 'global' if instance == 'master' else instance
+                if instance_name not in bgp_neighbor_data:
+                    bgp_neighbor_data[instance_name] = {}
                 if 'router_id' not in bgp_neighbor_data[instance_name]:
                     # we only need to set this once
                     bgp_neighbor_data[instance_name]['router_id'] = \
-                        py23_compat.text_type(neighbor_data['local_id'])
+                        py23_compat.text_type(neighbor_details.get('local_id', ''))
                 peer = {
                     key: self._parse_value(value)
-                    for key, value in neighbor_data.items()
+                    for key, value in neighbor_details.items()
                     if key in keys
                 }
-                peer['address_family'] = self._parse_route_stats(neighbor_data)
+                peer['local_as'] = napalm_base.helpers.as_number(peer['local_as'])
+                peer['remote_as'] = napalm_base.helpers.as_number(peer['remote_as'])
+                peer['address_family'] = self._parse_route_stats(neighbor_details)
+                if 'peers' not in bgp_neighbor_data[instance_name]:
+                    bgp_neighbor_data[instance_name]['peers'] = {}
                 bgp_neighbor_data[instance_name]['peers'][peer_ip] = peer
-            for neighbor, uptime in uptime_table.get(instance=instance).items():
-                bgp_neighbor_data[instance_name]['peers'][neighbor]['uptime'] = uptime[0][1]
+                if not uptime_table_items:
+                    uptime_table_items = _get_uptime_table(instance)
+                for neighbor, uptime in uptime_table_items:
+                    if neighbor not in bgp_neighbor_data[instance_name]['peers']:
+                        bgp_neighbor_data[instance_name]['peers'][neighbor] = {}
+                    bgp_neighbor_data[instance_name]['peers'][neighbor]['uptime'] = uptime[0][1]
+
+        # Commenting out the following sections, till Junos
+        #   will provide a way to identify the routing instance name
+        #   from the details of the BGP neighbor
+        #   currently, there are Junos 15 version having a field called `peer_fwd_rti`
+        #   but unfortunately, this is not consistent.
+        # Junos 17 might have this fixed, but this needs to be revisited later.
+        # In the definition below, `old_junos` means a version that does not provide
+        #   the forwarding RTI information.
+        #
+        # old_junos = napalm_base.helpers.convert(
+        #     int, self.device.facts.get('version', '0.0').split('.')[0], 0) < 15
+
+        # if old_junos:
+        instances = junos_views.junos_route_instance_table(self.device).get()
+        for instance, instance_data in instances.items():
+            if instance.startswith('__'):
+                # junos internal instances
+                continue
+            bgp_neighbor_data[instance] = {'peers': {}}
+            instance_neighbors = bgp_neighbors_table.get(instance=instance).items()
+            uptime_table_items = uptime_table.get(instance=instance).items()
+            _get_bgp_neighbors_core(instance_neighbors,
+                                    instance=instance,
+                                    uptime_table_items=uptime_table_items)
+        # If the OS provides the `peer_fwd_rti` or any way to identify the
+        #   rotuing instance name (see above), the performances of this getter
+        #   can be significantly improved, as we won't execute one request
+        #   for each an every RT.
+        # However, this improvement would only be beneficial for multi-VRF envs.
+        #
+        # else:
+        #     instance_neighbors = bgp_neighbors_table.get().items()
+        #     _get_bgp_neighbors_core(instance_neighbors)
         bgp_tmp_dict = {}
         for k, v in bgp_neighbor_data.items():
             if bgp_neighbor_data[k]['peers']:
@@ -428,8 +630,15 @@ class JunOSDriver(NetworkDriver):
     def get_lldp_neighbors(self):
         """Return LLDP neighbors details."""
         lldp = junos_views.junos_lldp_table(self.device)
-        lldp.get()
-
+        try:
+            lldp.get()
+        except RpcError as rpcerr:
+            # this assumes the library runs in an environment
+            # able to handle logs
+            # otherwise, the user just won't see this happening
+            log.error('Unable to retrieve the LLDP neighbors information:')
+            log.error(rpcerr.message)
+            return {}
         result = lldp.items()
 
         neighbors = {}
@@ -445,18 +654,28 @@ class JunOSDriver(NetworkDriver):
         lldp_neighbors = {}
 
         lldp_table = junos_views.junos_lldp_neighbors_detail_table(self.device)
-        lldp_table.get()
+        try:
+            lldp_table.get()
+        except RpcError as rpcerr:
+            # this assumes the library runs in an environment
+            # able to handle logs
+            # otherwise, the user just won't see this happening
+            log.error('Unable to retrieve the LLDP neighbors information:')
+            log.error(rpcerr.message)
+            return {}
         interfaces = lldp_table.get().keys()
 
-        old_junos = napalm_base.helpers.convert(
-            int, self.device.facts.get('version', '0.0').split('.')[0], '0') < 13
-
+        # get lldp neighbor by interface rpc for EX Series, QFX Series, J Series
+        # and SRX Series is get-lldp-interface-neighbors-information,
+        # and rpc for M, MX, and T Series is get-lldp-interface-neighbors
+        # ref1: https://apps.juniper.net/xmlapi/operTags.jsp  (Junos 13.1 and later)
+        # ref2: https://www.juniper.net/documentation/en_US/junos12.3/information-products/topic-collections/junos-xml-ref-oper/index.html  (Junos 12.3) # noqa
         lldp_table.GET_RPC = 'get-lldp-interface-neighbors'
-        if old_junos:
+        if self.device.facts.get('personality') not in ('MX', 'M', 'T'):
             lldp_table.GET_RPC = 'get-lldp-interface-neighbors-information'
 
         for interface in interfaces:
-            if old_junos:
+            if self.device.facts.get('personality') not in ('MX', 'M', 'T'):
                 lldp_table.get(interface_name=interface)
             else:
                 lldp_table.get(interface_device=interface)
@@ -482,13 +701,124 @@ class JunOSDriver(NetworkDriver):
         """Execute raw CLI commands and returns their output."""
         cli_output = {}
 
+        def _count(txt, none):  # Second arg for consistency only. noqa
+            '''
+            Return the exact output, as Junos displays
+            e.g.:
+            > show system processes extensive | match root | count
+            Count: 113 lines
+            '''
+            count = len(txt.splitlines())
+            return 'Count: {count} lines'.format(count=count)
+
+        def _trim(txt, length):
+            '''
+            Trim specified number of columns from start of line.
+            '''
+            try:
+                newlines = []
+                for line in txt.splitlines():
+                    newlines.append(line[int(length):])
+                return '\n'.join(newlines)
+            except ValueError:
+                return txt
+
+        def _except(txt, pattern):
+            '''
+            Show only text that does not match a pattern.
+            '''
+            rgx = '^.*({pattern}).*$'.format(pattern=pattern)
+            unmatched = [
+                line for line in txt.splitlines()
+                if not re.search(rgx, line, re.I)
+            ]
+            return '\n'.join(unmatched)
+
+        def _last(txt, length):
+            '''
+            Display end of output only.
+            '''
+            try:
+                return '\n'.join(
+                    txt.splitlines()[(-1)*int(length):]
+                )
+            except ValueError:
+                return txt
+
+        def _match(txt, pattern):
+            '''
+            Show only text that matches a pattern.
+            '''
+            rgx = '^.*({pattern}).*$'.format(pattern=pattern)
+            matched = [
+                line for line in txt.splitlines()
+                if re.search(rgx, line, re.I)
+            ]
+            return '\n'.join(matched)
+
+        def _find(txt, pattern):
+            '''
+            Search for first occurrence of pattern.
+            '''
+            rgx = '^.*({pattern})(.*)$'.format(pattern=pattern)
+            match = re.search(rgx, txt, re.I | re.M | re.DOTALL)
+            if match:
+                return '{pattern}{rest}'.format(pattern=pattern, rest=match.group(2))
+            else:
+                return '\nPattern not found'
+
+        def _process_pipe(cmd, txt):
+            '''
+            Process CLI output from Juniper device that
+            doesn't allow piping the output.
+            '''
+            if txt is not None:
+                return txt
+            _OF_MAP = OrderedDict()
+            _OF_MAP['except'] = _except
+            _OF_MAP['match'] = _match
+            _OF_MAP['last'] = _last
+            _OF_MAP['trim'] = _trim
+            _OF_MAP['count'] = _count
+            _OF_MAP['find'] = _find
+            # the operations order matter in this case!
+            exploded_cmd = cmd.split('|')
+            pipe_oper_args = {}
+            for pipe in exploded_cmd[1:]:
+                exploded_pipe = pipe.split()
+                pipe_oper = exploded_pipe[0]  # always there
+                pipe_args = ''.join(exploded_pipe[1:2])
+                # will not throw error when there's no arg
+                pipe_oper_args[pipe_oper] = pipe_args
+            for oper in _OF_MAP.keys():
+                # to make sure the operation sequence is correct
+                if oper not in pipe_oper_args.keys():
+                    continue
+                txt = _OF_MAP[oper](txt, pipe_oper_args[oper])
+            return txt
+
         if not isinstance(commands, list):
             raise TypeError('Please enter a valid list of commands!')
-
+        _PIPE_BLACKLIST = ['save']
+        # Preprocessing to avoid forbidden commands
         for command in commands:
+            exploded_cmd = command.split('|')
+            command_safe_parts = []
+            for pipe in exploded_cmd[1:]:
+                exploded_pipe = pipe.split()
+                pipe_oper = exploded_pipe[0]  # always there
+                if pipe_oper in _PIPE_BLACKLIST:
+                    continue
+                pipe_args = ''.join(exploded_pipe[1:2])
+                safe_pipe = pipe_oper if not pipe_args else '{fun} {args}'.format(fun=pipe_oper,
+                                                                                  args=pipe_args)
+                command_safe_parts.append(safe_pipe)
+            safe_command = exploded_cmd[0] if not command_safe_parts else\
+                '{base} | {pipes}'.format(base=exploded_cmd[0],
+                                          pipes=' | '.join(command_safe_parts))
+            raw_txt = self.device.cli(safe_command, warning=False)
             cli_output[py23_compat.text_type(command)] = py23_compat.text_type(
-                self.device.cli(command))
-
+                _process_pipe(command, raw_txt))
         return cli_output
 
     def get_bgp_config(self, group='', neighbor=''):
@@ -648,6 +978,12 @@ class JunOSDriver(NetworkDriver):
                             napalm_base.helpers.convert(datatype, elem[1], default)
                     })
             bgp_config[bgp_group_name]['prefix_limit'] = build_prefix_limit(**prefix_limit_fields)
+            if 'multihop' in bgp_config[bgp_group_name].keys():
+                # Delete 'multihop' key from the output
+                del bgp_config[bgp_group_name]['multihop']
+                if bgp_config[bgp_group_name]['multihop_ttl'] == 0:
+                    # Set ttl to default value 64
+                    bgp_config[bgp_group_name]['multihop_ttl'] = 64
 
             bgp_config[bgp_group_name]['neighbors'] = {}
             for bgp_group_neighbor in bgp_group_peers.items():
@@ -676,6 +1012,17 @@ class JunOSDriver(NetworkDriver):
                     bgp_peer_details.update({
                         key: napalm_base.helpers.convert(datatype, value, default)
                     })
+                    bgp_peer_details['local_as'] = napalm_base.helpers.as_number(
+                        bgp_peer_details['local_as'])
+                    bgp_peer_details['remote_as'] = napalm_base.helpers.as_number(
+                        bgp_peer_details['remote_as'])
+                    if key == 'cluster':
+                        bgp_peer_details['route_reflector_client'] = True
+                        # we do not want cluster in the output
+                        del bgp_peer_details['cluster']
+
+                if 'cluster' in bgp_config[bgp_group_name].keys():
+                    bgp_peer_details['route_reflector_client'] = True
                 prefix_limit_fields = {}
                 for elem in bgp_group_details:
                     if '_prefix_limit' in elem[0] and elem[1] is not None:
@@ -690,19 +1037,15 @@ class JunOSDriver(NetworkDriver):
                 if neighbor and bgp_peer_address == neighbor_ip:
                     break  # found the desired neighbor
 
+            if 'cluster' in bgp_config[bgp_group_name].keys():
+                # we do not want cluster in the output
+                del bgp_config[bgp_group_name]['cluster']
+
         return bgp_config
 
     def get_bgp_neighbors_detail(self, neighbor_address=''):
         """Detailed view of the BGP neighbors operational data."""
         bgp_neighbors = {}
-
-        bgp_neighbors_table = junos_views.junos_bgp_neighbors_table(self.device)
-
-        bgp_neighbors_table.get(
-            neighbor_address=neighbor_address
-        )
-        bgp_neighbors_items = bgp_neighbors_table.items()
-
         default_neighbor_details = {
             'up': False,
             'local_as': 0,
@@ -740,7 +1083,6 @@ class JunOSDriver(NetworkDriver):
             'advertised_prefix_count': -1,
             'flap_count': 0
         }
-
         OPTION_KEY_MAP = {
             'RemovePrivateAS': 'remove_private_as',
             'Multipath': 'multipath',
@@ -752,52 +1094,105 @@ class JunOSDriver(NetworkDriver):
             # Preference, HoldTime, Ttl, LogUpDown, Refresh
         }
 
-        for bgp_neighbor in bgp_neighbors_items:
-            remote_as = int(bgp_neighbor[0])
-            neighbor_details = deepcopy(default_neighbor_details)
-            neighbor_details.update(
-                {elem[0]: elem[1] for elem in bgp_neighbor[1] if elem[1] is not None}
-            )
-            options = neighbor_details.pop('options', '')
-            if isinstance(options, str):
-                options_list = options.split()
-                for option in options_list:
-                    key = OPTION_KEY_MAP.get(option)
-                    if key is not None:
-                        neighbor_details[key] = True
-            four_byte_as = neighbor_details.pop('4byte_as', 0)
-            local_address = neighbor_details.pop('local_address', '')
-            local_details = local_address.split('+')
-            neighbor_details['local_address'] = napalm_base.helpers.convert(
-                napalm_base.helpers.ip, local_details[0], local_details[0])
-            if len(local_details) == 2:
-                neighbor_details['local_port'] = int(local_details[1])
-            else:
-                neighbor_details['local_port'] = 179
-            neighbor_details['suppress_4byte_as'] = (remote_as != four_byte_as)
-            peer_address = neighbor_details.pop('peer_address', '')
-            remote_details = peer_address.split('+')
-            neighbor_details['remote_address'] = napalm_base.helpers.convert(
-                napalm_base.helpers.ip, remote_details[0], remote_details[0])
-            if len(remote_details) == 2:
-                neighbor_details['remote_port'] = int(remote_details[1])
-            else:
-                neighbor_details['remote_port'] = 179
-            neighbors_rib = neighbor_details.pop('rib')
-            neighbors_rib_items = neighbors_rib.items()
-            for rib_entry in neighbors_rib_items:
-                _table = py23_compat.text_type(rib_entry[0])
-                if _table not in bgp_neighbors.keys():
-                    bgp_neighbors[_table] = {}
-                if remote_as not in bgp_neighbors[_table].keys():
-                    bgp_neighbors[_table][remote_as] = []
-                neighbor_rib_details = deepcopy(neighbor_details)
-                neighbor_rib_details.update({
-                    elem[0]: elem[1] for elem in rib_entry[1]
-                })
-                neighbor_rib_details['routing_table'] = py23_compat.text_type(_table)
-                bgp_neighbors[_table][remote_as].append(neighbor_rib_details)
+        def _bgp_iter_core(neighbor_data, instance=None):
+            '''
+            Iterate over a list of neighbors.
+            For older junos, the routing instance is not specified inside the
+            BGP neighbors XML, therefore we need to use a super sub-optimal structure
+            as in get_bgp_neighbors: iterate through the list of network instances
+            then execute one request for each and every routing instance.
+            For newer junos, this is not necessary as the routing instance is available
+            and we can get everything solve in a single request.
+            '''
+            for bgp_neighbor in neighbor_data:
+                remote_as = int(bgp_neighbor[0])
+                neighbor_details = deepcopy(default_neighbor_details)
+                neighbor_details.update(
+                    {elem[0]: elem[1] for elem in bgp_neighbor[1] if elem[1] is not None}
+                )
+                if not instance:
+                    peer_fwd_rti = neighbor_details.pop('peer_fwd_rti')
+                    instance = peer_fwd_rti
+                else:
+                    peer_fwd_rti = neighbor_details.pop('peer_fwd_rti', '')
+                instance_name = 'global' if instance == 'master' else instance
+                options = neighbor_details.pop('options', '')
+                if isinstance(options, str):
+                    options_list = options.split()
+                    for option in options_list:
+                        key = OPTION_KEY_MAP.get(option)
+                        if key is not None:
+                            neighbor_details[key] = True
+                four_byte_as = neighbor_details.pop('4byte_as', 0)
+                local_address = neighbor_details.pop('local_address', '')
+                local_details = local_address.split('+')
+                neighbor_details['local_address'] = napalm_base.helpers.convert(
+                    napalm_base.helpers.ip, local_details[0], local_details[0])
+                if len(local_details) == 2:
+                    neighbor_details['local_port'] = int(local_details[1])
+                else:
+                    neighbor_details['local_port'] = 179
+                neighbor_details['suppress_4byte_as'] = (remote_as != four_byte_as)
+                peer_address = neighbor_details.pop('peer_address', '')
+                remote_details = peer_address.split('+')
+                neighbor_details['remote_address'] = napalm_base.helpers.convert(
+                    napalm_base.helpers.ip, remote_details[0], remote_details[0])
+                if len(remote_details) == 2:
+                    neighbor_details['remote_port'] = int(remote_details[1])
+                else:
+                    neighbor_details['remote_port'] = 179
+                neighbor_details['routing_table'] = instance_name
+                neighbor_details['local_as'] = napalm_base.helpers.as_number(
+                    neighbor_details['local_as'])
+                neighbor_details['remote_as'] = napalm_base.helpers.as_number(
+                    neighbor_details['remote_as'])
+                neighbors_rib = neighbor_details.pop('rib')
+                neighbors_queue = neighbor_details.pop('queue')
+                messages_queued_out = 0
+                for queue_entry in neighbors_queue.items():
+                    messages_queued_out += queue_entry[1][0][1]
+                neighbor_details['messages_queued_out'] = messages_queued_out
+                if instance_name not in bgp_neighbors.keys():
+                    bgp_neighbors[instance_name] = {}
+                if remote_as not in bgp_neighbors[instance_name].keys():
+                    bgp_neighbors[instance_name][remote_as] = []
+                neighbor_rib_stats = neighbors_rib.items()
+                if not neighbor_rib_stats:
+                    bgp_neighbors[instance_name][remote_as].append(neighbor_details)
+                    continue  # no RIBs available, pass default details
+                neighbor_rib_details = {
+                    'active_prefix_count': 0,
+                    'received_prefix_count': 0,
+                    'accepted_prefix_count': 0,
+                    'suppressed_prefix_count': 0,
+                    'advertised_prefix_count': 0
+                }
+                for rib_entry in neighbor_rib_stats:
+                    for elem in rib_entry[1]:
+                        if elem[1] is None:
+                            neighbor_rib_details[elem[0]] += 0
+                        else:
+                            neighbor_rib_details[elem[0]] += elem[1]
+                neighbor_details.update(neighbor_rib_details)
+                bgp_neighbors[instance_name][remote_as].append(neighbor_details)
+
+        # old_junos = napalm_base.helpers.convert(
+        #     int, self.device.facts.get('version', '0.0').split('.')[0], 0) < 15
+        bgp_neighbors_table = junos_views.junos_bgp_neighbors_table(self.device)
 
+        # if old_junos:
+        instances = junos_views.junos_route_instance_table(self.device)
+        for instance, instance_data in instances.get().items():
+            if instance.startswith('__'):
+                # junos internal instances
+                continue
+            neighbor_data = bgp_neighbors_table.get(instance=instance,
+                                                    neighbor_address=str(neighbor_address)).items()
+            _bgp_iter_core(neighbor_data, instance=instance)
+        # else:
+        #     bgp_neighbors_table = junos_views.junos_bgp_neighbors_table(self.device)
+        #     neighbor_data = bgp_neighbors_table.get(neighbor_address=neighbor_address).items()
+        #     _bgp_iter_core(neighbor_data)
         return bgp_neighbors
 
     def get_arp_table(self):
@@ -907,19 +1302,25 @@ class JunOSDriver(NetworkDriver):
             'inet6': u'ipv6'
             # can add more mappings
         }
+        _FAMILY_MAX_PREFIXLEN = {
+            'inet': 32,
+            'inet6': 128
+        }
 
         for interface_details in interface_table_items:
             ip_network = interface_details[0]
             ip_address = ip_network.split('/')[0]
             address = napalm_base.helpers.convert(
                 napalm_base.helpers.ip, ip_address, ip_address)
-            prefix = napalm_base.helpers.convert(int, ip_network.split('/')[-1], 0)
             try:
                 interface_details_dict = dict(interface_details[1])
                 family_raw = interface_details_dict.get('family')
                 interface = py23_compat.text_type(interface_details_dict.get('interface'))
             except ValueError:
                 continue
+            prefix = napalm_base.helpers.convert(int,
+                                                 ip_network.split('/')[-1],
+                                                 _FAMILY_MAX_PREFIXLEN.get(family_raw))
             family = _FAMILY_VMAP_.get(family_raw)
             if not family or not interface:
                 continue
@@ -938,7 +1339,10 @@ class JunOSDriver(NetworkDriver):
         mac_address_table = []
 
         if self.device.facts.get('personality', '') in ['SWITCH']:  # for EX & QFX devices
-            mac_table = junos_views.junos_mac_address_table_switch(self.device)
+            if self.device.facts.get('switch_style', '') in ['VLAN_L2NG']:  # for L2NG devices
+                mac_table = junos_views.junos_mac_address_table_switch_l2ng(self.device)
+            else:
+                mac_table = junos_views.junos_mac_address_table_switch(self.device)
         else:
             mac_table = junos_views.junos_mac_address_table(self.device)
 
@@ -961,6 +1365,11 @@ class JunOSDriver(NetworkDriver):
                 {elem[0]: elem[1] for elem in mac_table_entry[1]}
             )
             mac = mac_entry.get('mac')
+
+            # JUNOS returns '*' for Type = Flood
+            if mac == '*':
+                continue
+
             mac_entry['mac'] = napalm_base.helpers.mac(mac)
             mac_address_table.append(mac_entry)
 
@@ -1195,7 +1604,8 @@ class JunOSDriver(NetworkDriver):
                    destination,
                    source=C.TRACEROUTE_SOURCE,
                    ttl=C.TRACEROUTE_TTL,
-                   timeout=C.TRACEROUTE_TIMEOUT):
+                   timeout=C.TRACEROUTE_TIMEOUT,
+                   vrf=C.TRACEROUTE_VRF):
         """Execute traceroute and return results."""
         traceroute_result = {}
 
@@ -1205,19 +1615,23 @@ class JunOSDriver(NetworkDriver):
         source_str = ''
         maxttl_str = ''
         wait_str = ''
+        vrf_str = ''
 
         if source:
-            source_str = 'source {source}'.format(source=source)
+            source_str = ' source {source}'.format(source=source)
         if ttl:
-            maxttl_str = 'ttl {ttl}'.format(ttl=ttl)
+            maxttl_str = ' ttl {ttl}'.format(ttl=ttl)
         if timeout:
-            wait_str = 'wait {timeout}'.format(timeout=timeout)
+            wait_str = ' wait {timeout}'.format(timeout=timeout)
+        if vrf:
+            vrf_str = ' routing-instance {vrf}'.format(vrf=vrf)
 
-        traceroute_command = 'traceroute {destination} {source} {maxttl} {wait}'.format(
+        traceroute_command = 'traceroute {destination}{source}{maxttl}{wait}{vrf}'.format(
             destination=destination,
             source=source_str,
             maxttl=maxttl_str,
-            wait=wait_str
+            wait=wait_str,
+            vrf=vrf_str
         )
 
         traceroute_rpc = E('command', traceroute_command)
@@ -1257,7 +1671,7 @@ class JunOSDriver(NetworkDriver):
         return traceroute_result
 
     def ping(self, destination, source=C.PING_SOURCE, ttl=C.PING_TTL,
-             timeout=C.PING_TIMEOUT, size=C.PING_SIZE, count=C.PING_COUNT):
+             timeout=C.PING_TIMEOUT, size=C.PING_SIZE, count=C.PING_COUNT, vrf=C.PING_VRF):
 
         ping_dict = {}
 
@@ -1266,25 +1680,29 @@ class JunOSDriver(NetworkDriver):
         timeout_str = ''
         size_str = ''
         count_str = ''
+        vrf_str = ''
 
... 419 lines suppressed ...

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



More information about the Python-modules-commits mailing list