[Python-modules-commits] [napalm-eos] 01/03: Import napalm-eos_0.5.0.orig.tar.gz

Vincent Bernat bernat at moszumanska.debian.org
Sun Dec 25 10:55:35 UTC 2016


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

bernat pushed a commit to branch master
in repository napalm-eos.

commit 1d9df5bf4b8fe0050e75358cab5cd7c922bc4c78
Author: Vincent Bernat <bernat at debian.org>
Date:   Sun Dec 25 11:53:44 2016 +0100

    Import napalm-eos_0.5.0.orig.tar.gz
---
 PKG-INFO                                   |   6 +-
 napalm_eos.egg-info/PKG-INFO               |   6 +-
 napalm_eos.egg-info/SOURCES.txt            |   3 +-
 napalm_eos.egg-info/requires.txt           |   2 +-
 napalm_eos/__init__.py                     |   2 +-
 napalm_eos/eos.py                          | 481 ++++++++++++++++-------------
 napalm_eos/utils/textfsm_templates/vrf.tpl |  12 +
 requirements.txt                           |   2 +-
 setup.py                                   |   6 +-
 9 files changed, 287 insertions(+), 233 deletions(-)

diff --git a/PKG-INFO b/PKG-INFO
index 2f87f6c..de91264 100644
--- a/PKG-INFO
+++ b/PKG-INFO
@@ -1,10 +1,10 @@
 Metadata-Version: 1.1
 Name: napalm-eos
-Version: 0.4.2
+Version: 0.5.0
 Summary: Network Automation and Programmability Abstraction Layer with Multivendor support
 Home-page: https://github.com/napalm-automation/napalm-eos
-Author: David Barroso
-Author-email: dbarrosop at dravetech.com
+Author: David Barroso, Mircea Ulinic
+Author-email: dbarrosop at dravetech.com, mircea at cloudflare.com
 License: UNKNOWN
 Description: UNKNOWN
 Platform: UNKNOWN
diff --git a/napalm_eos.egg-info/PKG-INFO b/napalm_eos.egg-info/PKG-INFO
index 2f87f6c..de91264 100644
--- a/napalm_eos.egg-info/PKG-INFO
+++ b/napalm_eos.egg-info/PKG-INFO
@@ -1,10 +1,10 @@
 Metadata-Version: 1.1
 Name: napalm-eos
-Version: 0.4.2
+Version: 0.5.0
 Summary: Network Automation and Programmability Abstraction Layer with Multivendor support
 Home-page: https://github.com/napalm-automation/napalm-eos
-Author: David Barroso
-Author-email: dbarrosop at dravetech.com
+Author: David Barroso, Mircea Ulinic
+Author-email: dbarrosop at dravetech.com, mircea at cloudflare.com
 License: UNKNOWN
 Description: UNKNOWN
 Platform: UNKNOWN
diff --git a/napalm_eos.egg-info/SOURCES.txt b/napalm_eos.egg-info/SOURCES.txt
index 3385ad8..cdd05dd 100644
--- a/napalm_eos.egg-info/SOURCES.txt
+++ b/napalm_eos.egg-info/SOURCES.txt
@@ -14,4 +14,5 @@ napalm_eos/templates/set_ntp_peers.j2
 napalm_eos/utils/__init__.py
 napalm_eos/utils/textfsm_templates/bgp_detail.tpl
 napalm_eos/utils/textfsm_templates/ntp_peers.tpl
-napalm_eos/utils/textfsm_templates/snmp_config.tpl
\ No newline at end of file
+napalm_eos/utils/textfsm_templates/snmp_config.tpl
+napalm_eos/utils/textfsm_templates/vrf.tpl
\ No newline at end of file
diff --git a/napalm_eos.egg-info/requires.txt b/napalm_eos.egg-info/requires.txt
index 64f2b11..9b2ee5b 100644
--- a/napalm_eos.egg-info/requires.txt
+++ b/napalm_eos.egg-info/requires.txt
@@ -1,2 +1,2 @@
-napalm-base>=0.18.0
+napalm-base>=0.19.0
 pyeapi
diff --git a/napalm_eos/__init__.py b/napalm_eos/__init__.py
index c398f99..da3b5c1 100644
--- a/napalm_eos/__init__.py
+++ b/napalm_eos/__init__.py
@@ -13,7 +13,7 @@
 # the License.
 
 """napalm_eos package."""
-from eos import EOSDriver
+from napalm_eos.eos import EOSDriver
 import pkg_resources
 
 try:
diff --git a/napalm_eos/eos.py b/napalm_eos/eos.py
index 5ffc224..a0c1c43 100644
--- a/napalm_eos/eos.py
+++ b/napalm_eos/eos.py
@@ -17,13 +17,15 @@ Napalm driver for Arista EOS.
 
 Read napalm.readthedocs.org for more information.
 """
+from __future__ import print_function
+from __future__ import unicode_literals
 
 # std libs
 import re
 import time
 
 from datetime import datetime
-
+from collections import defaultdict
 from netaddr import IPAddress
 from netaddr import IPNetwork
 
@@ -37,6 +39,7 @@ from pyeapi.eapilib import ConnectionError
 import napalm_base.helpers
 from napalm_base.base import NetworkDriver
 from napalm_base.utils import string_parsers
+from napalm_base.utils import py23_compat
 from napalm_base.exceptions import ConnectionException, MergeConfigException, \
                         ReplaceConfigException, SessionLockedException, CommandErrorException
 
@@ -50,6 +53,12 @@ class EOSDriver(NetworkDriver):
 
     SUPPORTED_OC_MODELS = []
 
+    _RE_BGP_INFO = re.compile('BGP neighbor is (?P<neighbor>.*?), remote AS (?P<as>.*?), .*') # noqa
+    _RE_BGP_RID_INFO = re.compile('.*BGP version 4, remote router ID (?P<rid>.*?), VRF (?P<vrf>.*?)$') # noqa
+    _RE_BGP_DESC = re.compile('\s+Description: (?P<description>.*?)')
+    _RE_BGP_LOCAL = re.compile('Local AS is (?P<as>.*?),.*')
+    _RE_BGP_PREFIX = re.compile('(\s*?)(?P<af>IPv[46]) Unicast:\s*(?P<sent>\d+)\s*(?P<received>\d+)') # noqa
+
     def __init__(self, hostname, username, password, timeout=60, optional_args=None):
         """Constructor."""
         self.device = None
@@ -66,7 +75,7 @@ class EOSDriver(NetworkDriver):
 
         if self.transport == 'https':
             self.port = optional_args.get('port', 443)
-        elif self.transrpot == 'http':
+        elif self.transport == 'http':
             self.port = optional_args.get('port', 80)
 
         self.enablepwd = optional_args.get('enable_password', '')
@@ -76,7 +85,7 @@ class EOSDriver(NetworkDriver):
         try:
             if self.transport in ('http', 'https'):
                 connection = pyeapi.client.connect(
-                    transport='https',
+                    transport=self.transport,
                     host=self.hostname,
                     username=self.username,
                     password=self.password,
@@ -104,6 +113,11 @@ class EOSDriver(NetworkDriver):
         """Implementation of NAPALM method close."""
         self.discard_config()
 
+    def is_alive(self):
+        return {
+            'is_alive': True  # always true as eAPI is HTTP-based
+        }
+
     def _load_config(self, filename=None, config=None, replace=True):
         if self.config_session is not None:
             raise SessionLockedException('Session is already in use by napalm')
@@ -192,7 +206,7 @@ class EOSDriver(NetworkDriver):
 
     def get_facts(self):
         """Implementation of NAPALM method get_facts."""
-        commands = list()
+        commands = []
         commands.append('show version')
         commands.append('show hostname')
         commands.append('show interfaces')
@@ -226,7 +240,7 @@ class EOSDriver(NetworkDriver):
 
         interfaces = dict()
 
-        for interface, values in output['interfaces'].iteritems():
+        for interface, values in output['interfaces'].items():
             interfaces[interface] = dict()
 
             if values['lineProtocolStatus'] == 'up':
@@ -270,70 +284,39 @@ class EOSDriver(NetworkDriver):
         return lldp
 
     def get_interfaces_counters(self):
-        commands = list()
-
-        commands.append('show interfaces counters')
-        commands.append('show interfaces counters errors')
-
+        commands = ['show interfaces']
         output = self.device.run_commands(commands)
-
-        interface_counters = dict()
-
-        for interface, counters in output[0]['interfaces'].iteritems():
-            interface_counters[interface] = dict()
-
-            interface_counters[interface]['tx_octets'] = counters['outOctets']
-            interface_counters[interface]['rx_octets'] = counters['inOctets']
-            interface_counters[interface]['tx_unicast_packets'] = counters['outUcastPkts']
-            interface_counters[interface]['rx_unicast_packets'] = counters['inUcastPkts']
-            interface_counters[interface]['tx_multicast_packets'] = counters['outMulticastPkts']
-            interface_counters[interface]['rx_multicast_packets'] = counters['inMulticastPkts']
-            interface_counters[interface]['tx_broadcast_packets'] = counters['outBroadcastPkts']
-            interface_counters[interface]['rx_broadcast_packets'] = counters['inBroadcastPkts']
-            interface_counters[interface]['tx_discards'] = counters['outDiscards']
-            interface_counters[interface]['rx_discards'] = counters['inDiscards']
-
-            # Errors come from a different command
-            errors = output[1]['interfaceErrorCounters'][interface]
-            interface_counters[interface]['tx_errors'] = errors['outErrors']
-            interface_counters[interface]['rx_errors'] = errors['inErrors']
-
+        interface_counters = defaultdict(dict)
+        for interface, data in output[0]['interfaces'].items():
+            if data['hardware'] == 'subinterface':
+                # Subinterfaces will never have counters so no point in parsing them at all
+                continue
+            counters = data.get('interfaceCounters', {})
+            interface_counters[interface].update(
+                tx_octets=counters.get('outOctets', -1),
+                rx_octets=counters.get('inOctets', -1),
+                tx_unicast_packets=counters.get('outUcastPkts', -1),
+                rx_unicast_packets=counters.get('inUcastPkts', -1),
+                tx_multicast_packets=counters.get('outMulticastPkts', -1),
+                rx_multicast_packets=counters.get('inMulticastPkts', -1),
+                tx_broadcast_packets=counters.get('outBroadcastPkts', -1),
+                rx_broadcast_packets=counters.get('inBroadcastPkts', -1),
+                tx_discards=counters.get('outDiscards', -1),
+                rx_discards=counters.get('inDiscards', -1),
+                tx_errors=counters.get('totalOutErrors', -1),
+                rx_errors=counters.get('totalInErrors', -1)
+            )
         return interface_counters
 
-    @staticmethod
-    def _parse_neigbor_info(line):
-        m = re.match('BGP neighbor is (?P<neighbor>.*?), remote AS (?P<as>.*?), .*', line)
-        return m.group('neighbor'), m.group('as')
-
-    @staticmethod
-    def _parse_rid_info(line):
-        m = re.match('.*BGP version 4, remote router ID (?P<rid>.*?), VRF (?P<vrf>.*?)$', line)
-        return m.group('rid'), m.group('vrf')
-
-    @staticmethod
-    def _parse_desc(line):
-        m = re.match('\s+Description: (?P<description>.*?)', line)
-        if m:
-            return m.group('description')
-        else:
-            return None
-
-    @staticmethod
-    def _parse_local_info(line):
-        m = re.match('Local AS is (?P<as>.*?),.*', line)
-        return m.group('as')
-
-    @staticmethod
-    def _bgp_neighbor_enabled(line):
-        m = re.match('\s+BGP\s+state\s+is\s+.*,\s+Administratively\s+shut\s+down', line)
-        return m is None
+    def get_bgp_neighbors(self):
 
-    @staticmethod
-    def _parse_prefix_info(line):
-        m = re.match('(\s*?)(?P<af>IPv[46]) Unicast:\s*(?P<sent>\d+)\s*(?P<received>\d+)', line)
-        return m.group('sent'), m.group('received')
+        def get_re_group(res, key, default=None):
+            """ Small helper to retrive data from re match groups"""
+            try:
+                return res.group(key)
+            except KeyError:
+                return default
 
-    def get_bgp_neighbors(self):
         NEIGHBOR_FILTER = 'bgp neighbors vrf all | include remote AS | remote router ID |IPv[46] Unicast:.*[0-9]+|^Local AS|Desc|BGP state'  # noqa
         output_summary_cmds = self.device.run_commands(
             ['show ipv6 bgp summary vrf all', 'show ip bgp summary vrf all'],
@@ -342,7 +325,7 @@ class EOSDriver(NetworkDriver):
             ['show ip ' + NEIGHBOR_FILTER, 'show ipv6 ' + NEIGHBOR_FILTER],
             encoding='text')
 
-        bgp_counters = {}
+        bgp_counters = defaultdict(lambda: dict(peers=dict()))
         for summary in output_summary_cmds:
             """
             Json output looks as follows
@@ -368,23 +351,22 @@ class EOSDriver(NetworkDriver):
                 }
             }
             """
-            for vrf, vrf_data in summary['vrfs'].iteritems():
-                if vrf not in bgp_counters.keys():
-                    bgp_counters[vrf] = {
-                        'peers': {}
-                    }
+            for vrf, vrf_data in summary['vrfs'].items():
                 bgp_counters[vrf]['router_id'] = vrf_data['routerId']
-                for peer, peer_data in vrf_data['peers'].iteritems():
+                for peer, peer_data in vrf_data['peers'].items():
+                    if peer_data['peerState'] == 'Idle':
+                        is_enabled = True if peer_data['peerStateIdleReason'] != 'Admin' else False
+                    else:
+                        is_enabled = True
                     peer_info = {
                         'is_up': peer_data['peerState'] == 'Established',
-                        'is_enabled': peer_data['peerState'] == 'Established' or
-                        peer_data['peerState'] == 'Active',
-                        'uptime': int(peer_data['upDownTime'])
+                        'is_enabled': is_enabled,
+                        'uptime': int(time.time() - peer_data['upDownTime'])
                     }
                     bgp_counters[vrf]['peers'][napalm_base.helpers.ip(peer)] = peer_info
         lines = []
         [lines.extend(x['output'].splitlines()) for x in output_neighbor_cmds]
-        for line in lines:
+        while lines:
             """
             Raw output from the command looks like the following:
 
@@ -396,115 +378,117 @@ class EOSDriver(NetworkDriver):
                  IPv6 Unicast:           0         0
               Local AS is 2, local router ID 2.2.2.2
             """
-            if line is '':
-                continue
-            neighbor, r_as = self._parse_neigbor_info(lines.pop(0))
+            neighbor_info = re.match(self._RE_BGP_INFO, lines.pop(0))
             # this line can be either description or rid info
             next_line = lines.pop(0)
-            desc = self._parse_desc(next_line)
+            desc = re.match(self._RE_BGP_DESC, next_line)
             if desc is None:
-                rid, vrf = self._parse_rid_info(next_line)
+                rid_info = re.match(self._RE_BGP_RID_INFO, next_line)
                 desc = ''
             else:
-                rid, vrf = self._parse_rid_info(lines.pop(0))
-
-            is_enabled = self._bgp_neighbor_enabled(lines.pop(0))
-            v4_sent, v4_recv = self._parse_prefix_info(lines.pop(0))
-            v6_sent, v6_recv = self._parse_prefix_info(lines.pop(0))
-            local_as = self._parse_local_info(lines.pop(0))
+                rid_info = re.match(self._RE_BGP_RID_INFO, lines.pop(0))
+                desc = desc.group('description')
+            lines.pop(0)
+            v4_stats = re.match(self._RE_BGP_PREFIX, lines.pop(0))
+            v6_stats = re.match(self._RE_BGP_PREFIX, lines.pop(0))
+            local_as = re.match(self._RE_BGP_LOCAL, lines.pop(0))
             data = {
-                'remote_as': int(r_as),
-                'remote_id': napalm_base.helpers.ip(rid),
-                'local_as': int(local_as),
-                'description': unicode(desc),
+                'remote_as': int(neighbor_info.group('as')),
+                'remote_id': napalm_base.helpers.ip(get_re_group(rid_info, 'rid', '0.0.0.0')),
+                'local_as': int(local_as.group('as')),
+                'description': py23_compat.text_type(desc),
                 'address_family': {
                     'ipv4': {
-                        'sent_prefixes': int(v4_sent),
-                        'received_prefixes': int(v4_recv),
+                        'sent_prefixes': int(get_re_group(v4_stats, 'sent', -1)),
+                        'received_prefixes': int(get_re_group(v4_stats, 'received', -1)),
                         'accepted_prefixes': -1
                     },
                     'ipv6': {
-                        'sent_prefixes': int(v6_sent),
-                        'received_prefixes': int(v6_recv),
+                        'sent_prefixes': int(get_re_group(v6_stats, 'sent', -1)),
+                        'received_prefixes': int(get_re_group(v6_stats, 'received', -1)),
                         'accepted_prefixes': -1
                     }
                 }
             }
-            peer_addr = napalm_base.helpers.ip(neighbor)
-            if peer_addr not in bgp_counters[vrf]['peers'].keys():
+            peer_addr = napalm_base.helpers.ip(neighbor_info.group('neighbor'))
+            vrf = rid_info.group('vrf')
+            if peer_addr not in bgp_counters[vrf]['peers']:
                 bgp_counters[vrf]['peers'][peer_addr] = {
                     'is_up': False,  # if not found, means it was not found in the oper stats
-                    # i.e. neighbor down,
+                                     # i.e. neighbor down,
                     'uptime': 0,
-                    'is_enabled': is_enabled
+                    'is_enabled': True
                 }
             bgp_counters[vrf]['peers'][peer_addr].update(data)
-
-        if 'default' in bgp_counters.keys():
+        if 'default' in bgp_counters:
             bgp_counters['global'] = bgp_counters.pop('default')
-        return bgp_counters
+        return dict(bgp_counters)
 
     def get_environment(self):
-        """Impplementation of get_environment"""
-        command = list()
-        command.append('show environment cooling')
-        command.append('show environment temperature')
-        command.append('show environment power')
-        output = self.device.run_commands(command)
-
-        environment_counters = dict()
-        environment_counters['fans'] = dict()
-        environment_counters['temperature'] = dict()
-        environment_counters['power'] = dict()
-        environment_counters['cpu'] = dict()
-        environment_counters['available_ram'] = ''
-        environment_counters['used_ram'] = ''
-
-        fans_output = output[0]
-        temp_output = output[1]
-        power_output = output[2]
+        def extract_temperature_data(data):
+            for s in data:
+                temp = s['currentTemperature']
+                name = s['name']
+                values = {
+                   'temperature': temp,
+                   'is_alert': temp > s['overheatThreshold'],
+                   'is_critical': temp > s['criticalThreshold']
+                }
+                yield name, values
+
+        sh_version_out = self.device.run_commands(['show version'])
+        is_veos = sh_version_out[0]['modelName'].lower() == 'veos'
+        commands = [
+            'show environment cooling',
+            'show environment temperature'
+        ]
+        if not is_veos:
+            commands.append('show environment power')
+            fans_output, temp_output, power_output = self.device.run_commands(commands)
+        else:
+            fans_output, temp_output = self.device.run_commands(commands)
+        environment_counters = {
+            'fans': {},
+            'temperature': {},
+            'power': {},
+            'cpu': {}
+        }
         cpu_output = self.device.run_commands(['show processes top once'],
                                               encoding='text')[0]['output']
-
-        ''' Get fans counters '''
         for slot in fans_output['fanTraySlots']:
-            environment_counters['fans'][slot['label']] = dict()
-            environment_counters['fans'][slot['label']]['status'] = slot['status'] == 'ok'
-
-        ''' Get temp counters '''
-        for slot in temp_output:
-            try:
-                for sensorsgroup in temp_output[slot]:
-                    for sensor in sensorsgroup['tempSensors']:
-                        environment_counters['temperature'][sensor['name']] = {
-                           'temperature': sensor['currentTemperature'],
-                           'is_alert': sensor['currentTemperature'] > sensor['overheatThreshold'],
-                           'is_critical': sensor['currentTemperature'] > sensor['criticalThreshold']
-                        }
-            except:
-                pass
-
-        ''' Get power counters '''
-        for _, item in power_output.iteritems():
-            for id, ps in item.iteritems():
-                environment_counters['power'][id] = {
-                    'status': ps['state'] == 'ok',
-                    'capacity': ps['capacity'],
-                    'output': ps['outputPower']
+            environment_counters['fans'][slot['label']] = {'status': slot['status'] == 'ok'}
+        # First check FRU's
+        for fru_type in ['cardSlots', 'powerSupplySlots']:
+            for fru in temp_output[fru_type]:
+                t = {name: value for name, value in extract_temperature_data(fru['tempSensors'])}
+                environment_counters['temperature'].update(t)
+        # On board sensors
+        parsed = {n: v for n, v in extract_temperature_data(temp_output['tempSensors'])}
+        environment_counters['temperature'].update(parsed)
+        if not is_veos:
+            for psu, data in power_output['powerSupplies'].items():
+                environment_counters['power'][psu] = {
+                    'status': data['state'] == 'ok',
+                    'capacity': data['capacity'],
+                    'output': data['outputPower']
                 }
-
-        ''' Get CPU counters '''
-        m = re.search('(\d+.\d+)\%', cpu_output.splitlines()[2])
+        cpu_lines = cpu_output.splitlines()
+        # Matches either of
+        # Cpu(s):  5.2%us,  1.4%sy,  0.0%ni, 92.2%id,  0.6%wa,  0.3%hi,  0.4%si,  0.0%st ( 4.16 > )
+        # %Cpu(s):  4.2 us,  0.9 sy,  0.0 ni, 94.6 id,  0.0 wa,  0.1 hi,  0.2 si,  0.0 st ( 4.16 < )
+        m = re.match('.*ni, (?P<idle>.*).id.*', cpu_lines[2])
         environment_counters['cpu'][0] = {
-            '%usage': float(m.group(1))
+            '%usage': round(100 - float(m.group('idle')), 1)
         }
-        m = re.search('(\d+)k\W+total\W+(\d+)k\W+used\W+(\d+)k\W+free', cpu_output.splitlines()[3])
-
+        # Matches either of
+        # Mem:   3844356k total,  3763184k used,    81172k free,    16732k buffers ( 4.16 > )
+        # KiB Mem:  32472080 total,  5697604 used, 26774476 free,   372052 buffers ( 4.16 < )
+        mem_regex = '.*total,\s+ (?P<used>\d+)[k\s]+used,\s+(?P<free>\d+)[k\s]+free,.*'
+        m = re.match(mem_regex, cpu_lines[3])
         environment_counters['memory'] = {
-            'available_ram': int(m.group(1)),
-            'used_ram': int(m.group(2))
+            'available_ram': int(m.group('free')),
+            'used_ram': int(m.group('used'))
         }
-
         return environment_counters
 
     def get_lldp_neighbors_detail(self, interface=''):
@@ -536,6 +520,8 @@ class EOSDriver(NetworkDriver):
                 if interface not in lldp_neighbors_out.keys():
                     lldp_neighbors_out[interface] = list()
                 capabilities = neighbor.get('systemCapabilities')
+                capabilities_list = list(capabilities.keys())
+                capabilities_list.sort()
                 lldp_neighbors_out[interface].append(
                     {
                         'parent_interface': interface,  # no parent interfaces
@@ -546,13 +532,12 @@ class EOSDriver(NetworkDriver):
                         'remote_system_description': neighbor.get('systemDescription', u''),
                         'remote_chassis_id': napalm_base.helpers.mac(
                             neighbor.get('chassisId', u'')),
-                        'remote_system_capab': unicode(', '.join(capabilities)),
-                        'remote_system_enable_capab': unicode(', '.join(
-                            [capability for capability in capabilities.keys()
+                        'remote_system_capab': py23_compat.text_type(', '.join(capabilities_list)),
+                        'remote_system_enable_capab': py23_compat.text_type(', '.join(
+                            [capability for capability in capabilities_list
                              if capabilities[capability]]))
                     }
                 )
-
         return lldp_neighbors_out
 
     def cli(self, commands=None):
@@ -563,22 +548,20 @@ class EOSDriver(NetworkDriver):
 
         for command in commands:
             try:
-                cli_output[unicode(command)] = self.device.run_commands(
+                cli_output[py23_compat.text_type(command)] = self.device.run_commands(
                     [command], encoding='text')[0].get('output')
                 # not quite fair to not exploit rum_commands
                 # but at least can have better control to point to wrong command in case of failure
             except pyeapi.eapilib.CommandError:
                 # for sure this command failed
-                cli_output[unicode(command)] = 'Invalid command: "{cmd}"'.format(
+                cli_output[py23_compat.text_type(command)] = 'Invalid command: "{cmd}"'.format(
                     cmd=command
                 )
                 raise CommandErrorException(str(cli_output))
             except Exception as e:
                 # something bad happened
-                cli_output[unicode(command)] = 'Unable to execute command "{cmd}": {err}'.format(
-                    cmd=command,
-                    err=e
-                )
+                msg = 'Unable to execute command "{cmd}": {err}'.format(cmd=command, err=e)
+                cli_output[py23_compat.text_type(command)] = msg
                 raise CommandErrorException(str(cli_output))
 
         return cli_output
@@ -622,24 +605,24 @@ class EOSDriver(NetworkDriver):
             # and cast the values
             'remote-as': int,
             'ebgp-multihop': int,
-            'local-v4-addr': unicode,
-            'local-v6-addr': unicode,
+            'local-v4-addr': py23_compat.text_type,
+            'local-v6-addr': py23_compat.text_type,
             'local-as': int,
             'remove-private-as': bool,
             'next-hop-self': bool,
-            'description': unicode,
+            'description': py23_compat.text_type,
             'route-reflector-client': bool,
-            'password': unicode,
-            'route-map': unicode,
+            'password': py23_compat.text_type,
+            'route-map': py23_compat.text_type,
             'apply-groups': list,
-            'type': unicode,
-            'import-policy': unicode,
-            'export-policy': unicode,
+            'type': py23_compat.text_type,
+            'import-policy': py23_compat.text_type,
+            'export-policy': py23_compat.text_type,
             'multipath': bool
         }
 
         _DATATYPE_DEFAULT_ = {
-            unicode: u'',
+            py23_compat.text_type: '',
             int: 0,
             bool: False,
             list: []
@@ -671,7 +654,7 @@ class EOSDriver(NetworkDriver):
                 # do not respect the pattern neighbor [IP_ADDRESS] [PROPERTY] [VALUE]
                 # or need special output (e.g.: maximum-routes)
                 if config_property == 'password':
-                    return {'authentication_key': unicode(options[2])}
+                    return {'authentication_key': py23_compat.text_type(options[2])}
                     # returns the MD5 password
                 if config_property == 'route-map':
                     direction = None
@@ -715,7 +698,7 @@ class EOSDriver(NetworkDriver):
                 default_value = True
             bgp_conf_line = bgp_conf_line.replace('no neighbor ', '').replace('neighbor ', '')
             bgp_conf_line_details = bgp_conf_line.split()
-            group_or_neighbor = unicode(bgp_conf_line_details[0])
+            group_or_neighbor = py23_compat.text_type(bgp_conf_line_details[0])
             options = bgp_conf_line_details[1:]
             try:
                 # will try to parse the neighbor name
@@ -755,7 +738,7 @@ class EOSDriver(NetworkDriver):
                     bgp_neighbors[last_peer_group][peer_address] = dict()
                     bgp_neighbors[last_peer_group][peer_address].update({
                         key: _DATATYPE_DEFAULT_.get(_PROPERTY_TYPE_MAP_.get(prop))
-                        for prop, key in _PEER_FIELD_MAP_.iteritems()
+                        for prop, key in _PEER_FIELD_MAP_.items()
                     })  # populating with default values
                     bgp_neighbors[last_peer_group][peer_address].update({
                         'prefix_limit': {},
@@ -775,7 +758,7 @@ class EOSDriver(NetworkDriver):
                     bgp_config[group_name] = dict()
                     bgp_config[group_name].update({
                         key: _DATATYPE_DEFAULT_.get(_PROPERTY_TYPE_MAP_.get(prop))
-                        for prop, key in _GROUP_FIELD_MAP_.iteritems()
+                        for prop, key in _GROUP_FIELD_MAP_.items()
                     })
                     bgp_config[group_name].update({
                         'prefix_limit': {},
@@ -789,7 +772,7 @@ class EOSDriver(NetworkDriver):
                 # for other kind of exception pass to next line
                 continue
 
-        for group, peers in bgp_neighbors.iteritems():
+        for group, peers in bgp_neighbors.items():
             if group not in bgp_config.keys():
                 continue
             bgp_config[group]['neighbors'] = peers
@@ -809,9 +792,9 @@ class EOSDriver(NetworkDriver):
             return []
 
         for neighbor in ipv4_neighbors:
-            interface = unicode(neighbor.get('interface'))
+            interface = py23_compat.text_type(neighbor.get('interface'))
             mac_raw = neighbor.get('hwAddress')
-            ip = unicode(neighbor.get('address'))
+            ip = py23_compat.text_type(neighbor.get('address'))
             age = float(neighbor.get('age'))
             arp_table.append(
                 {
@@ -831,7 +814,7 @@ class EOSDriver(NetworkDriver):
 
         ntp_config = napalm_base.helpers.textfsm_extractor(self, 'ntp_peers', raw_ntp_config)
 
-        return {unicode(ntp_peer.get('ntppeer')): {}
+        return {py23_compat.text_type(ntp_peer.get('ntppeer')): {}
                 for ntp_peer in ntp_config if ntp_peer.get('ntppeer', '')}
 
     def get_ntp_stats(self):
@@ -863,12 +846,12 @@ class EOSDriver(NetworkDriver):
             line_groups = line_search.groups()
             try:
                 ntp_stats.append({
-                    'remote': unicode(line_groups[1]),
+                    'remote': py23_compat.text_type(line_groups[1]),
                     'synchronized': (line_groups[0] == '*'),
-                    'referenceid': unicode(line_groups[2]),
+                    'referenceid': py23_compat.text_type(line_groups[2]),
                     'stratum': int(line_groups[3]),
-                    'type': unicode(line_groups[4]),
-                    'when': unicode(line_groups[5]),
+                    'type': py23_compat.text_type(line_groups[4]),
+                    'when': py23_compat.text_type(line_groups[5]),
                     'hostpoll': int(line_groups[6]),
                     'reachability': int(line_groups[7]),
                     'delay': float(line_groups[8]),
@@ -893,7 +876,7 @@ class EOSDriver(NetworkDriver):
             else:
                 raise
 
-        for interface_name, interface_details in interfaces_ipv4_out.iteritems():
+        for interface_name, interface_details in interfaces_ipv4_out.items():
             ipv4_list = list()
             if interface_name not in interfaces_ip.keys():
                 interfaces_ip[interface_name] = dict()
@@ -928,7 +911,7 @@ class EOSDriver(NetworkDriver):
                         u'prefix_length': ip.get('masklen')
                     }
 
-        for interface_name, interface_details in interfaces_ipv6_out.iteritems():
+        for interface_name, interface_details in interfaces_ipv6_out.items():
             ipv6_list = list()
             if interface_name not in interfaces_ip.keys():
                 interfaces_ip[interface_name] = dict()
@@ -1021,7 +1004,7 @@ class EOSDriver(NetworkDriver):
             # on a multi-VRF configured device need to go through a loop and get for each instance
             routes_out = command_output.get('vrfs', {}).get('default', {}).get('routes', {})
 
-        for prefix, route_details in routes_out.iteritems():
+        for prefix, route_details in routes_out.items():
             if prefix not in routes.keys():
                 routes[prefix] = list()
             route_protocol = route_details.get('routeType').upper()
@@ -1107,19 +1090,19 @@ class EOSDriver(NetworkDriver):
             return snmp_information
 
         snmp_information = {
-            'contact': unicode(snmp_config[0].get('contact', '')),
-            'location': unicode(snmp_config[0].get('location', '')),
-            'chassis_id': unicode(snmp_config[0].get('chassis_id', '')),
+            'contact': py23_compat.text_type(snmp_config[0].get('contact', '')),
+            'location': py23_compat.text_type(snmp_config[0].get('location', '')),
+            'chassis_id': py23_compat.text_type(snmp_config[0].get('chassis_id', '')),
             'community': {}
         }
 
         for snmp_entry in snmp_config:
-            community_name = unicode(snmp_entry.get('community', ''))
+            community_name = py23_compat.text_type(snmp_entry.get('community', ''))
             if not community_name:
                 continue
             snmp_information['community'][community_name] = {
-                'acl': unicode(snmp_entry.get('acl', '')),
-                'mode': unicode(snmp_entry.get('mode', 'ro').lower())
+                'acl': py23_compat.text_type(snmp_entry.get('acl', '')),
+                'mode': py23_compat.text_type(snmp_entry.get('mode', 'ro').lower())
             }
 
         return snmp_information
@@ -1128,9 +1111,9 @@ class EOSDriver(NetworkDriver):
 
         def _sshkey_type(sshkey):
             if sshkey.startswith('ssh-rsa'):
-                return u'ssh_rsa', unicode(sshkey)
+                return u'ssh_rsa', py23_compat.text_type(sshkey)
             elif sshkey.startswith('ssh-dss'):
-                return u'ssh_dsa', unicode(sshkey)
+                return u'ssh_dsa', py23_compat.text_type(sshkey)
             return u'ssh_rsa', u''
 
         users = dict()
@@ -1138,13 +1121,13 @@ class EOSDriver(NetworkDriver):
         commands = ['show user-account']
         user_items = self.device.run_commands(commands)[0].get('users', {})
 
-        for user, user_details in user_items.iteritems():
+        for user, user_details in user_items.items():
             user_details.pop('username', '')
             sshkey_value = user_details.pop('sshAuthorizedKey', '')
             sshkey_type, sshkey_value = _sshkey_type(sshkey_value)
             user_details.update({
                 'level': user_details.pop('privLevel', 0),
-                'password': unicode(user_details.pop('secret', '')),
+                'password': py23_compat.text_type(user_details.pop('secret', '')),
                 'sshkeys': [sshkey_value]
             })
             users[user] = user_details
@@ -1239,8 +1222,8 @@ class EOSDriver(NetworkDriver):
                     host_name = '*'
                     ip_address = '*'
                 traceroute_result['success'][hop_index]['probes'][probe_index+1] = {
-                    'host_name': unicode(host_name),
-                    'ip_address': unicode(ip_address),
+                    'host_name': py23_compat.text_type(host_name),
+                    'ip_address': py23_compat.text_type(ip_address),
                     'rtt': rtt
                 }
                 previous_probe_host_name = host_name
@@ -1298,23 +1281,23 @@ class EOSDriver(NetworkDriver):
                 # Conforming with the datatypes defined by the base class
                 item['export_policy'] = (
                     napalm_base.helpers.convert(
-                        unicode, item['export_policy']))
+                        py23_compat.text_type, item['export_policy']))
                 item['last_event'] = (
                     napalm_base.helpers.convert(
-                        unicode, item['last_event']))
+                        py23_compat.text_type, item['last_event']))
                 item['remote_address'] = napalm_base.helpers.ip(item['remote_address'])
                 item['previous_connection_state'] = (
                     napalm_base.helpers.convert(
-                        unicode, item['previous_connection_state']))
+                        py23_compat.text_type, item['previous_connection_state']))
                 item['import_policy'] = (
                     napalm_base.helpers.convert(
-                        unicode, item['import_policy']))
+                        py23_compat.text_type, item['import_policy']))
                 item['connection_state'] = (
                     napalm_base.helpers.convert(
-                        unicode, item['connection_state']))
+                        py23_compat.text_type, item['connection_state']))
                 item['routing_table'] = (
                     napalm_base.helpers.convert(
-                        unicode, item['routing_table']))
+                        py23_compat.text_type, item['routing_table']))
                 item['router_id'] = napalm_base.helpers.ip(item['router_id'])
                 item['local_address'] = napalm_base.helpers.convert(
                     napalm_base.helpers.ip, item['local_address'])
@@ -1417,7 +1400,7 @@ class EOSDriver(NetworkDriver):
         # Formatting data into return data structure
         optics_detail = {}
 
-        for port, port_values in output.iteritems():
+        for port, port_values in output.items():
             port_detail = {}
 
             port_detail['physical_channels'] = {}
@@ -1472,36 +1455,92 @@ class EOSDriver(NetworkDriver):
 
             output = self.device.run_commands(commands, encoding="text")
             return {
-                'startup': unicode(output[0]['output']) if get_startup else u"",
-                'running': unicode(output[1]['output']) if get_running else u"",
-                'candidate': unicode(output[2]['output']) if get_candidate else u"",
+                'startup': py23_compat.text_type(output[0]['output']) if get_startup else u"",
+                'running': py23_compat.text_type(output[1]['output']) if get_running else u"",
+                'candidate': py23_compat.text_type(output[2]['output']) if get_candidate else u"",
             }
         elif get_startup or get_running:
             commands = ['show {}-config'.format(retrieve)]
             output = self.device.run_commands(commands, encoding="text")
             return {
-                'startup': unicode(output[0]['output']) if get_startup else u"",
-                'running': unicode(output[0]['output']) if get_running else u"",
-                'candidate': u"",
+                'startup': py23_compat.text_type(output[0]['output']) if get_startup else u"",
+                'running': py23_compat.text_type(output[0]['output']) if get_running else u"",
+                'candidate': "",
             }
         elif get_candidate:
             commands = ['show session-config named {}'.format(self.config_session)]
             output = self.device.run_commands(commands, encoding="text")
             return {
-                'startup': u"",
-                'running': u"",
-                'candidate': unicode(output[0]['output']),
+                'startup': "",
+                'running': "",
+                'candidate': py23_compat.text_type(output[0]['output']),
             }
         elif retrieve == "candidate":
             # If we get here it means that we want the candidate but there is none.
             return {
-                'startup': u"",
-                'running': u"",
-                'candidate': u"",
+                'startup': "",
+                'running': "",
+                'candidate': "",
             }
         else:
             raise Exception("Wrong retrieve filter: {}".format(retrieve))
 
+    def get_network_instances(self, name=''):
+        """get_network_instances implementation for EOS."""
+
+        commands = ['show vrf']
+
+        # This command has no JSON yet
+        raw_output = self.device.run_commands(commands, encoding='text')[0].get('output', '')
+
+        output = napalm_base.helpers.textfsm_extractor(self, 'vrf', raw_output)
+        vrfs = dict()
+        all_vrf_interfaces = dict()
+        for vrf in output:
+            if (vrf.get('route_distinguisher', '') == "<not set>" or
+                    vrf.get('route_distinguisher', '') == 'None'):
+                vrf['route_distinguisher'] = u''
+            else:
+                vrf['route_distinguisher'] = py23_compat.text_type(vrf['route_distinguisher'])
+            interfaces = dict()
+            for interface_raw in vrf.get('interfaces', []):
+                interface = interface_raw.split(',')
+                for line in interface:
+                    if line.strip() != '':
+                        interfaces[py23_compat.text_type(line.strip())] = {}
+                        all_vrf_interfaces[py23_compat.text_type(line.strip())] = {}
+
+            vrfs[py23_compat.text_type(vrf['name'])] = {
+                          u'name': py23_compat.text_type(vrf['name']),
+                          u'type': u'L3VRF',
+                          u'state': {
+                              u'route_distinguisher': vrf['route_distinguisher'],
+                          },
+                          u'interfaces': {
+                              u'interface': interfaces,
+                          },
+            }
+        all_interfaces = self.get_interfaces_ip().keys()
+        vrfs[u'default'] = {
+            u'name': u'default',
+            u'type': u'DEFAULT_INSTANCE',
+            u'state': {
+                u'route_distinguisher': u'',
+            },
+            u'interfaces': {
+                u'interface': {
+                    k: {} for k in all_interfaces if k not in all_vrf_interfaces.keys()
+                },
+            },
+        }
+
+        if name:
+            if name in vrfs:
+                return {py23_compat.text_type(name): vrfs[name]}
+            return {}
+        else:
+            return vrfs
+
     def ping(self, destination, source='', ttl=255, timeout=2, size=100, count=5):
         """
         Execute ping on the device and returns a dictionary with the result.
@@ -1545,10 +1584,12 @@ class EOSDriver(NetworkDriver):
                 fields = line.split()
                 if 'icmp' in line:
                     if 'Unreachable' in line:
-                        results_array.append({'ip_address': unicode(fields[1]), 'rtt': 0.0})
+                        results_array.append({'ip_address': py23_compat.text_type(fields[1]),
+                                              'rtt': 0.0})
                     elif fields[1] == 'bytes':
                         m = fields[6][5:]
-                        results_array.append({'ip_address': unicode(fields[3]), 'rtt': float(m)})
+                        results_array.append({'ip_address': py23_compat.text_type(fields[3]),
+                                              'rtt': float(m)})
                 elif 'packets transmitted' in line:
                     ping_dict['success']['probes_sent'] = int(fields[0])
                     ping_dict['success']['packet_loss'] = int(fields[0]) - int(fields[3])
diff --git a/napalm_eos/utils/textfsm_templates/vrf.tpl b/napalm_eos/utils/textfsm_templates/vrf.tpl
new file mode 100644
index 0000000..110b992
--- /dev/null
+++ b/napalm_eos/utils/textfsm_templates/vrf.tpl
@@ -0,0 +1,12 @@
+Value Required Name (\S+)
+Value Route_Distinguisher (\d+:\d+|<not set>)
+Value List Interfaces (\S.+)
+
+
+Start
+  ^\s\S+\s+(\d|<) -> Continue.Record
+  ^\s+${Name}\s+${Route_Distinguisher}\s+(ipv4,ipv6\s+)?v4:(incomplete|(no )?routing(; multicast)?),\s+$Interfaces
+  ^\s+${Name}\s+${Route_Distinguisher}\s+(ipv4,ipv6\s+)?v4:(incomplete|(no )?routing(; multicast)?),
+  ^\s+v6:(incomplete|(no )?routing)\s+$Interfaces
+  ^\s+v6:(incomplete|(no )?routing) -> Record
+  ^\s+$Interfaces
diff --git a/requirements.txt b/requirements.txt
index 8f5ec73..d0a46f6 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,2 +1,2 @@
-napalm_base>=0.18.0
+napalm_base>=0.19.0
 pyeapi
diff --git a/setup.py b/setup.py
index c83c605..f40af5d 100644
--- a/setup.py
+++ b/setup.py
@@ -12,10 +12,10 @@ reqs = [str(ir.req) for ir in install_reqs]
 
 setup(
     name="napalm-eos",
-    version="0.4.2",
+    version="0.5.0",
     packages=find_packages(),
-    author="David Barroso",
-    author_email="dbarrosop at dravetech.com",
+    author="David Barroso, Mircea Ulinic",
+    author_email="dbarrosop at dravetech.com, mircea at cloudflare.com",
     description="Network Automation and Programmability Abstraction Layer with Multivendor support",
     classifiers=[
         'Topic :: Utilities',

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



More information about the Python-modules-commits mailing list