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

Vincent Bernat bernat at moszumanska.debian.org
Tue Nov 14 09:41:43 UTC 2017


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

bernat pushed a commit to annotated tag debian/0.6.1-1
in repository napalm-eos.

commit a046a284029b364c1de71533729fa9e95acbd39a
Author: Vincent Bernat <bernat at debian.org>
Date:   Tue Nov 14 10:39:59 2017 +0100

    New upstream release.
---
 PKG-INFO                                   |   2 +-
 napalm_eos.egg-info/PKG-INFO               |   2 +-
 napalm_eos.egg-info/SOURCES.txt            |   2 +
 napalm_eos.egg-info/requires.txt           |   2 +-
 napalm_eos/eos.py                          | 578 +++++++++++++++++------------
 napalm_eos/templates/delete_snmp_config.j2 |   6 +-
 napalm_eos/templates/delete_users.j2       |  17 +
 napalm_eos/templates/set_users.j2          |  15 +
 napalm_eos/utils/textfsm_templates/vrf.tpl |  17 +-
 requirements.txt                           |   2 +-
 setup.cfg                                  |   2 +-
 setup.py                                   |   2 +-
 12 files changed, 396 insertions(+), 251 deletions(-)

diff --git a/PKG-INFO b/PKG-INFO
index 2a12cec..4617347 100644
--- a/PKG-INFO
+++ b/PKG-INFO
@@ -1,6 +1,6 @@
 Metadata-Version: 1.1
 Name: napalm-eos
-Version: 0.5.1
+Version: 0.6.1
 Summary: Network Automation and Programmability Abstraction Layer with Multivendor support
 Home-page: https://github.com/napalm-automation/napalm-eos
 Author: David Barroso, Mircea Ulinic
diff --git a/napalm_eos.egg-info/PKG-INFO b/napalm_eos.egg-info/PKG-INFO
index 2a12cec..4617347 100644
--- a/napalm_eos.egg-info/PKG-INFO
+++ b/napalm_eos.egg-info/PKG-INFO
@@ -1,6 +1,6 @@
 Metadata-Version: 1.1
 Name: napalm-eos
-Version: 0.5.1
+Version: 0.6.1
 Summary: Network Automation and Programmability Abstraction Layer with Multivendor support
 Home-page: https://github.com/napalm-automation/napalm-eos
 Author: David Barroso, Mircea Ulinic
diff --git a/napalm_eos.egg-info/SOURCES.txt b/napalm_eos.egg-info/SOURCES.txt
index 1d55617..24d3987 100644
--- a/napalm_eos.egg-info/SOURCES.txt
+++ b/napalm_eos.egg-info/SOURCES.txt
@@ -11,7 +11,9 @@ napalm_eos.egg-info/requires.txt
 napalm_eos.egg-info/top_level.txt
 napalm_eos/templates/delete_ntp_servers.j2
 napalm_eos/templates/delete_snmp_config.j2
+napalm_eos/templates/delete_users.j2
 napalm_eos/templates/set_ntp_servers.j2
+napalm_eos/templates/set_users.j2
 napalm_eos/templates/snmp_config.j2
 napalm_eos/utils/__init__.py
 napalm_eos/utils/textfsm_templates/bgp_detail.tpl
diff --git a/napalm_eos.egg-info/requires.txt b/napalm_eos.egg-info/requires.txt
index 9b2ee5b..c3bcd0e 100644
--- a/napalm_eos.egg-info/requires.txt
+++ b/napalm_eos.egg-info/requires.txt
@@ -1,2 +1,2 @@
-napalm-base>=0.19.0
+napalm-base>=0.23.0
 pyeapi
diff --git a/napalm_eos/eos.py b/napalm_eos/eos.py
index e7accf0..59fcd2e 100644
--- a/napalm_eos/eos.py
+++ b/napalm_eos/eos.py
@@ -59,6 +59,9 @@ class EOSDriver(NetworkDriver):
     _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
+    _RE_SNMP_COMM = re.compile(r"""^snmp-server\s+community\s+(?P<community>\S+)
+                                (\s+view\s+(?P<view>\S+))?(\s+(?P<access>ro|rw)?)
+                                (\s+ipv6\s+(?P<v6_acl>\S+))?(\s+(?P<v4_acl>\S+))?$""", re.VERBOSE)
 
     def __init__(self, hostname, username, password, timeout=60, optional_args=None):
         """Constructor."""
@@ -68,11 +71,13 @@ class EOSDriver(NetworkDriver):
         self.password = password
         self.timeout = timeout
         self.config_session = None
+        self.locked = False
 
         if optional_args is None:
             optional_args = {}
 
-        self.transport = optional_args.get('eos_transport', 'https')
+        # eos_transport is there for backwards compatibility, transport is the preferred method
+        self.transport = optional_args.get('transport', optional_args.get('eos_transport', 'https'))
 
         if self.transport == 'https':
             self.port = optional_args.get('port', 443)
@@ -81,6 +86,8 @@ class EOSDriver(NetworkDriver):
 
         self.enablepwd = optional_args.get('enable_password', '')
 
+        self.profile = ["eos"]
+
     def open(self):
         """Implementation of NAPALM method open."""
         try:
@@ -119,15 +126,18 @@ class EOSDriver(NetworkDriver):
             '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')
-        else:
+    def _lock(self):
+        if self.config_session is None:
             self.config_session = 'napalm_{}'.format(datetime.now().microsecond)
+        sess = self.device.run_commands(['show configuration sessions'])[0]['sessions']
+        if [k for k, v in sess.items() if v['state'] == 'pending' and k != self.config_session]:
+            raise SessionLockedException('Session is already in use')
 
-        commands = list()
-        commands.append('configure session {}'.format(self.config_session))
+    def _load_config(self, filename=None, config=None, replace=True):
+        commands = []
 
+        self._lock()
+        commands.append('configure session {}'.format(self.config_session))
         if replace:
             commands.append('rollback clean-config')
 
@@ -152,7 +162,6 @@ class EOSDriver(NetworkDriver):
             self.device.run_commands(commands)
         except pyeapi.eapilib.CommandError as e:
             self.discard_config()
-
             if replace:
                 raise ReplaceConfigException(e.message)
             else:
@@ -180,7 +189,7 @@ class EOSDriver(NetworkDriver):
 
     def commit_config(self):
         """Implementation of NAPALM method commit_config."""
-        commands = list()
+        commands = []
         commands.append('copy startup-config flash:rollback-0')
         commands.append('configure session {}'.format(self.config_session))
         commands.append('commit')
@@ -192,7 +201,7 @@ class EOSDriver(NetworkDriver):
     def discard_config(self):
         """Implementation of NAPALM method discard_config."""
         if self.config_session is not None:
-            commands = list()
+            commands = []
             commands.append('configure session {}'.format(self.config_session))
             commands.append('abort')
             self.device.run_commands(commands)
@@ -200,7 +209,7 @@ class EOSDriver(NetworkDriver):
 
     def rollback(self):
         """Implementation of NAPALM method rollback."""
-        commands = list()
+        commands = []
         commands.append('configure replace flash:rollback-0')
         commands.append('write memory')
         self.device.run_commands(commands)
@@ -235,14 +244,14 @@ class EOSDriver(NetworkDriver):
         }
 
     def get_interfaces(self):
-        commands = list()
+        commands = []
         commands.append('show interfaces')
         output = self.device.run_commands(commands)[0]
 
-        interfaces = dict()
+        interfaces = {}
 
         for interface, values in output['interfaces'].items():
-            interfaces[interface] = dict()
+            interfaces[interface] = {}
 
             if values['lineProtocolStatus'] == 'up':
                 interfaces[interface]['is_up'] = True
@@ -265,15 +274,15 @@ class EOSDriver(NetworkDriver):
         return interfaces
 
     def get_lldp_neighbors(self):
-        commands = list()
+        commands = []
         commands.append('show lldp neighbors')
         output = self.device.run_commands(commands)[0]['lldpNeighbors']
 
-        lldp = dict()
+        lldp = {}
 
         for n in output:
             if n['port'] not in lldp.keys():
-                lldp[n['port']] = list()
+                lldp[n['port']] = []
 
             lldp[n['port']].append(
                 {
@@ -326,7 +335,7 @@ class EOSDriver(NetworkDriver):
             ['show ip ' + NEIGHBOR_FILTER, 'show ipv6 ' + NEIGHBOR_FILTER],
             encoding='text')
 
-        bgp_counters = defaultdict(lambda: dict(peers=dict()))
+        bgp_counters = defaultdict(lambda: dict(peers={}))
         for summary in output_summary_cmds:
             """
             Json output looks as follows
@@ -494,13 +503,13 @@ class EOSDriver(NetworkDriver):
 
     def get_lldp_neighbors_detail(self, interface=''):
 
-        lldp_neighbors_out = dict()
+        lldp_neighbors_out = {}
 
-        filters = list()
+        filters = []
         if interface:
             filters.append(interface)
 
-        commands = list()
+        commands = []
         commands.append(
             'show lldp neighbors {filters} detail'.format(
                 filters=' '.join(filters)
@@ -519,20 +528,23 @@ class EOSDriver(NetworkDriver):
             # it is provided a list of neighbors per interface
             for neighbor in interface_neighbors:
                 if interface not in lldp_neighbors_out.keys():
-                    lldp_neighbors_out[interface] = list()
-                capabilities = neighbor.get('systemCapabilities')
+                    lldp_neighbors_out[interface] = []
+                capabilities = neighbor.get('systemCapabilities', {})
                 capabilities_list = list(capabilities.keys())
                 capabilities_list.sort()
+                remote_chassis_id = neighbor.get('chassisId', u'')
+                if neighbor.get("chassisIdType", u'') == "macAddress":
+                    remote_chassis_id = napalm_base.helpers.mac(remote_chassis_id)
+                neighbor_interface_info = neighbor.get('neighborInterfaceInfo', {})
                 lldp_neighbors_out[interface].append(
                     {
                         'parent_interface': interface,  # no parent interfaces
-                        'remote_port':
-                            neighbor.get('neighborInterfaceInfo', {}).get('interfaceId', u''),
-                        'remote_port_description': u'',
+                        'remote_port': neighbor_interface_info.get('interfaceId', u''),
+                        'remote_port_description':
+                            neighbor_interface_info.get('interfaceDescription', u''),
                         'remote_system_name': neighbor.get('systemName', u''),
                         'remote_system_description': neighbor.get('systemDescription', u''),
-                        'remote_chassis_id': napalm_base.helpers.mac(
-                            neighbor.get('chassisId', u'')),
+                        'remote_chassis_id': remote_chassis_id,
                         '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
@@ -542,7 +554,7 @@ class EOSDriver(NetworkDriver):
         return lldp_neighbors_out
 
     def cli(self, commands):
-        cli_output = dict()
+        cli_output = {}
 
         if type(commands) is not list:
             raise TypeError('Please enter a valid list of commands!')
@@ -629,10 +641,36 @@ class EOSDriver(NetworkDriver):
             list: []
         }
 
+        def default_group_dict(local_as):
+            group_dict = {}
+            group_dict.update({
+                key: _DATATYPE_DEFAULT_.get(_PROPERTY_TYPE_MAP_.get(prop))
+                for prop, key in _GROUP_FIELD_MAP_.items()
+            })
+            group_dict.update({
+                'prefix_limit': {},
+                'neighbors': {},
+                'local_as': local_as
+            })  # few more default values
+            return group_dict
+
+        def default_neighbor_dict(local_as):
+            neighbor_dict = {}
+            neighbor_dict.update({
+                key: _DATATYPE_DEFAULT_.get(_PROPERTY_TYPE_MAP_.get(prop))
+                for prop, key in _PEER_FIELD_MAP_.items()
+            })  # populating with default values
+            neighbor_dict.update({
+                'prefix_limit': {},
+                'local_as': local_as,
+                'authentication_key': u''
+            })  # few more default values
+            return neighbor_dict
+
         def parse_options(options, default_value=False):
 
             if not options:
-                return dict()
+                return {}
 
             config_property = options[0]
             field_name = _PROPERTY_FIELD_MAP_.get(config_property)
@@ -641,11 +679,13 @@ class EOSDriver(NetworkDriver):
 
             if not field_type:
                 # no type specified at all => return empty dictionary
-                return dict()
+                return {}
 
             if not default_value:
                 if len(options) > 1:
-                    field_value = field_type(options[1])
+                    field_value = napalm_base.helpers.convert(field_type,
+                                                              options[1],
+                                                              _DATATYPE_DEFAULT_.get(field_type))
                 else:
                     if field_type is bool:
                         field_value = True
@@ -670,22 +710,22 @@ class EOSDriver(NetworkDriver):
                         field_name = 'export_policy'
                     return {field_name: field_value}
 
-            return dict()
+            return {}
 
-        bgp_config = dict()
+        bgp_config = {}
 
-        commands = list()
+        commands = []
         commands.append('show running-config | section router bgp')
         bgp_conf = self.device.run_commands(commands, encoding='text')[0].get('output', '\n\n')
-        bgp_conf_lines = bgp_conf.splitlines()[2:]
+        bgp_conf_lines = bgp_conf.splitlines()
 
-        bgp_neighbors = dict()
+        bgp_neighbors = {}
 
         if not group:
-            neighbor = ''
+            neighbor = ''  # noqa
 
-        last_peer_group = ''
         local_as = 0
+        bgp_neighbors = {}
         for bgp_conf_line in bgp_conf_lines:
             default_value = False
             bgp_conf_line = bgp_conf_line.strip()
@@ -708,18 +748,10 @@ class EOSDriver(NetworkDriver):
                 IPAddress(group_or_neighbor)
                 # if passes the test => it is an IP Address, thus a Neighbor!
                 peer_address = group_or_neighbor
-
+                if peer_address not in bgp_neighbors:
+                    bgp_neighbors[peer_address] = default_neighbor_dict(local_as)
                 if options[0] == 'peer-group':
-                    last_peer_group = options[1]
-
-                # if looking for a specific group
-                if group and last_peer_group != group:
-                    continue
-
-                # or even more. a specific neighbor within a group
-                if neighbor and peer_address != neighbor:
-                    continue
-                # skip all other except the target
+                    bgp_neighbors[peer_address]['__group'] = options[1]
 
                 # in the config, neighbor details are lister after
                 # the group is specified for the neighbor:
@@ -733,20 +765,8 @@ class EOSDriver(NetworkDriver):
                 # that way we avoid one more loop to
                 # match the neighbors with the group they belong to
                 # directly will apend the neighbor in the neighbor list of the group at the end
-                if last_peer_group not in bgp_neighbors.keys():
-                    bgp_neighbors[last_peer_group] = dict()
-                if peer_address not in bgp_neighbors[last_peer_group]:
-                    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_.items()
-                    })  # populating with default values
-                    bgp_neighbors[last_peer_group][peer_address].update({
-                        'prefix_limit': {},
-                        'local_as': local_as,
-                        'authentication_key': u''
-                    })  # few more default values
-                bgp_neighbors[last_peer_group][peer_address].update(
+
+                bgp_neighbors[peer_address].update(
                     parse_options(options, default_value)
                 )
             except AddrFormatError:
@@ -756,33 +776,24 @@ class EOSDriver(NetworkDriver):
                 if group and group_name != group:
                     continue
                 if group_name not in bgp_config.keys():
-                    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_.items()
-                    })
-                    bgp_config[group_name].update({
-                        'prefix_limit': {},
-                        'neighbors': {},
-                        'local_as': local_as
-                    })  # few more default values
+                    bgp_config[group_name] = default_group_dict(local_as)
                 bgp_config[group_name].update(
                     parse_options(options, default_value)
                 )
-            except Exception:
-                # for other kind of exception pass to next line
-                continue
 
-        for group, peers in bgp_neighbors.items():
-            if group not in bgp_config.keys():
-                continue
-            bgp_config[group]['neighbors'] = peers
+        for peer, peer_details in bgp_neighbors.items():
+            peer_group = peer_details.pop('__group', None)
+            if not peer_group:
+                peer_group = '_'
+            if peer_group not in bgp_config:
+                bgp_config[peer_group] = default_group_dict(local_as)
+            bgp_config[peer_group]['neighbors'][peer] = peer_details
 
         return bgp_config
 
     def get_arp_table(self):
 
-        arp_table = list()
+        arp_table = []
 
         commands = ['show arp']
 
@@ -819,7 +830,7 @@ class EOSDriver(NetworkDriver):
                 for ntp_peer in ntp_config if ntp_peer.get('ntppeer', '')}
 
     def get_ntp_stats(self):
-        ntp_stats = list()
+        ntp_stats = []
 
         REGEX = (
             '^\s?(\+|\*|x|-)?([a-zA-Z0-9\.+-:]+)'
@@ -829,7 +840,7 @@ class EOSDriver(NetworkDriver):
             '\s+([0-9\.]+)\s?$'
         )
 
-        commands = list()
+        commands = []
         commands.append('show ntp associations')
 
         # output = self.device.run_commands(commands)
@@ -866,7 +877,7 @@ class EOSDriver(NetworkDriver):
 
     def get_interfaces_ip(self):
 
-        interfaces_ip = dict()
+        interfaces_ip = {}
 
         interfaces_ipv4_out = self.device.run_commands(['show ip interface'])[0]['interfaces']
         try:
@@ -878,14 +889,14 @@ class EOSDriver(NetworkDriver):
                 raise
 
         for interface_name, interface_details in interfaces_ipv4_out.items():
-            ipv4_list = list()
+            ipv4_list = []
             if interface_name not in interfaces_ip.keys():
-                interfaces_ip[interface_name] = dict()
+                interfaces_ip[interface_name] = {}
 
             if u'ipv4' not in interfaces_ip.get(interface_name):
-                interfaces_ip[interface_name][u'ipv4'] = dict()
+                interfaces_ip[interface_name][u'ipv4'] = {}
             if u'ipv6' not in interfaces_ip.get(interface_name):
-                interfaces_ip[interface_name][u'ipv6'] = dict()
+                interfaces_ip[interface_name][u'ipv6'] = {}
 
             iface_details = interface_details.get('interfaceAddress', {})
             if iface_details.get('primaryIp', {}).get('address') != '0.0.0.0':
@@ -913,14 +924,14 @@ class EOSDriver(NetworkDriver):
                     }
 
         for interface_name, interface_details in interfaces_ipv6_out.items():
-            ipv6_list = list()
+            ipv6_list = []
             if interface_name not in interfaces_ip.keys():
-                interfaces_ip[interface_name] = dict()
+                interfaces_ip[interface_name] = {}
 
             if u'ipv4' not in interfaces_ip.get(interface_name):
-                interfaces_ip[interface_name][u'ipv4'] = dict()
+                interfaces_ip[interface_name][u'ipv4'] = {}
             if u'ipv6' not in interfaces_ip.get(interface_name):
-                interfaces_ip[interface_name][u'ipv6'] = dict()
+                interfaces_ip[interface_name][u'ipv6'] = {}
 
             ipv6_list.append(
                 {
@@ -951,16 +962,12 @@ class EOSDriver(NetworkDriver):
 
     def get_mac_address_table(self):
 
-        mac_table = list()
+        mac_table = []
 
         commands = ['show mac address-table']
 
-        mac_entries = []
-        try:
-            mac_entries = self.device.run_commands(commands)[0].get(
-                'unicastTable', {}).get('tableEntries', [])
-        except Exception:
-            return {}
+        mac_entries = self.device.run_commands(commands)[0].get(
+            'unicastTable', {}).get('tableEntries', [])
 
         for mac_entry in mac_entries:
             vlan = mac_entry.get('vlanId')
@@ -984,7 +991,17 @@ class EOSDriver(NetworkDriver):
         return mac_table
 
     def get_route_to(self, destination='', protocol=''):
-        routes = dict()
+        routes = {}
+
+        # Placeholder for vrf arg
+        vrf = ''
+
+        # Right not iterating through vrfs is necessary
+        # show ipv6 route doesn't support vrf 'all'
+        if vrf == '':
+            vrfs = sorted(self._get_vrfs())
+        else:
+            vrfs = [vrf]
 
         if protocol.lower() == 'direct':
             protocol = 'connected'
@@ -996,129 +1013,155 @@ class EOSDriver(NetworkDriver):
         except AddrFormatError:
             return 'Please specify a valid destination!'
 
-        command = 'show ip{ipv} route {destination} {protocol} detail'.format(
-            ipv=ipv,
-            destination=destination,
-            protocol=protocol,
-        )
-
-        command_output = self.device.run_commands([command])[0]
-        if ipv == 'v6':
-            routes_out = command_output.get('routes', {})
-        else:
-            # 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.items():
-            if prefix not in routes.keys():
-                routes[prefix] = list()
-            route_protocol = route_details.get('routeType').upper()
-            preference = route_details.get('preference', 0)
-
-            route = {
-                'current_active': False,
-                'last_active': False,
-                'age': 0,
-                'next_hop': u'',
-                'protocol': route_protocol,
-                'outgoing_interface': u'',
-                'preference': preference,
-                'inactive_reason': u'',
-                'routing_table': u'default',
-                'selected_next_hop': False,
-                'protocol_attributes': {}
-            }
-            if protocol == 'bgp':
-                metric = route_details.get('metric')
-                command = 'show ip{ipv} bgp {destination} detail'.format(
-                    ipv=ipv,
-                    destination=prefix
-                )
-                default_vrf_details = self.device.run_commands([command])[0].get(
-                    'vrfs', {}).get('default', {})
-                local_as = default_vrf_details.get('asn')
-                bgp_routes = default_vrf_details.get(
-                    'bgpRouteEntries', {}).get(prefix, {}).get('bgpRoutePaths', [])
-                for bgp_route_details in bgp_routes:
-                    bgp_route = route.copy()
-                    as_path = bgp_route_details.get('asPathEntry', {}).get('asPath', u'')
-                    remote_as = int(as_path.split()[-1])
-                    remote_address = napalm_base.helpers.ip(bgp_route_details.get(
-                        'routeDetail', {}).get('peerEntry', {}).get('peerAddr', ''))
-                    local_preference = bgp_route_details.get('localPreference')
-                    next_hop = napalm_base.helpers.ip(bgp_route_details.get('nextHop'))
-                    active_route = bgp_route_details.get('routeType', {}).get('active', False)
-                    last_active = active_route  # should find smth better
-                    communities = bgp_route_details.get('routeDetail', {}).get('communityList', [])
-                    preference2 = bgp_route_details.get('weight')
-                    bgp_route.update({
-                        'current_active': active_route,
-                        'last_active': last_active,
-                        'next_hop': next_hop,
-                        'selected_next_hop': active_route,
-                        'protocol_attributes': {
-                            'metric': metric,
-                            'as_path': as_path,
-                            'local_preference': local_preference,
-                            'local_as': local_as,
-                            'remote_as': remote_as,
-                            'remote_address': remote_address,
-                            'preference2': preference2,
-                            'communities': communities
-                        }
-                    })
-                    routes[prefix].append(bgp_route)
+        commands = []
+        for _vrf in vrfs:
+            commands.append('show ip{ipv} route vrf {_vrf} {destination} {protocol} detail'.format(
+                ipv=ipv,
+                _vrf=_vrf,
+                destination=destination,
+                protocol=protocol,
+            ))
+
+        commands_output = self.device.run_commands(commands)
+
+        for _vrf, command_output in zip(vrfs, commands_output):
+            if ipv == 'v6':
+                routes_out = command_output.get('routes', {})
             else:
-                for next_hop in route_details.get('vias'):
-                    route_next_hop = route.copy()
-                    if next_hop.get('nexthopAddr') is None:
-                        route_next_hop.update(
-                            {
-                                'next_hop': '',
-                                'outgoing_interface': next_hop.get('interface')
-                            }
-                        )
-                    else:
-                        route_next_hop.update(
-                            {
-                                'next_hop': napalm_base.helpers.ip(next_hop.get('nexthopAddr')),
-                                'outgoing_interface': next_hop.get('interface')
+                routes_out = command_output.get('vrfs', {}).get(_vrf, {}).get('routes', {})
+
+            for prefix, route_details in routes_out.items():
+                if prefix not in routes.keys():
+                    routes[prefix] = []
+                route_protocol = route_details.get('routeType')
+                preference = route_details.get('preference', 0)
+
+                route = {
+                    'current_active': True,
+                    'last_active': True,
+                    'age': 0,
+                    'next_hop': u'',
+                    'protocol': route_protocol,
+                    'outgoing_interface': u'',
+                    'preference': preference,
+                    'inactive_reason': u'',
+                    'routing_table': _vrf,
+                    'selected_next_hop': True,
+                    'protocol_attributes': {}
+                }
+                if protocol == 'bgp' or route_protocol.lower() in ('ebgp', 'ibgp'):
+                    nexthop_interface_map = {}
+                    for next_hop in route_details.get('vias'):
+                        nexthop_ip = napalm_base.helpers.ip(next_hop.get('nexthopAddr'))
+                        nexthop_interface_map[nexthop_ip] = next_hop.get('interface')
+                    metric = route_details.get('metric')
+                    command = 'show ip{ipv} bgp {destination} detail vrf {_vrf}'.format(
+                        ipv=ipv,
+                        destination=prefix,
+                        _vrf=_vrf
+                    )
+                    vrf_details = self.device.run_commands([command])[0].get(
+                        'vrfs', {}).get(_vrf, {})
+                    local_as = vrf_details.get('asn')
+                    bgp_routes = vrf_details.get(
+                        'bgpRouteEntries', {}).get(prefix, {}).get('bgpRoutePaths', [])
+                    for bgp_route_details in bgp_routes:
+                        bgp_route = route.copy()
+                        as_path = bgp_route_details.get('asPathEntry', {}).get('asPath', u'')
+                        remote_as = int(as_path.split()[-1])
+                        remote_address = napalm_base.helpers.ip(bgp_route_details.get(
+                            'routeDetail', {}).get('peerEntry', {}).get('peerAddr', ''))
+                        local_preference = bgp_route_details.get('localPreference')
+                        next_hop = napalm_base.helpers.ip(bgp_route_details.get('nextHop'))
+                        active_route = bgp_route_details.get('routeType', {}).get('active', False)
+                        last_active = active_route  # should find smth better
+                        communities = bgp_route_details.get('routeDetail', {}).get(
+                            'communityList', [])
+                        preference2 = bgp_route_details.get('weight')
+                        inactive_reason = bgp_route_details.get('reasonNotBestpath', '')
+                        bgp_route.update({
+                            'current_active': active_route,
+                            'inactive_reason': inactive_reason,
+                            'last_active': last_active,
+                            'next_hop': next_hop,
+                            'outgoing_interface': nexthop_interface_map.get(next_hop),
+                            'selected_next_hop': active_route,
+                            'protocol_attributes': {
+                                'metric': metric,
+                                'as_path': as_path,
+                                'local_preference': local_preference,
+                                'local_as': local_as,
+                                'remote_as': remote_as,
+                                'remote_address': remote_address,
+                                'preference2': preference2,
+                                'communities': communities
                             }
-                        )
-                    routes[prefix].append(route_next_hop)
-
+                        })
+                        routes[prefix].append(bgp_route)
+                else:
+                    if route_details.get('routeAction') in ('drop',):
+                        route['next_hop'] = 'NULL'
+                    if route_details.get('routingDisabled') is True:
+                        route['last_active'] = False
+                        route['current_active'] = False
+                    for next_hop in route_details.get('vias'):
+                        route_next_hop = route.copy()
+                        if next_hop.get('nexthopAddr') is None:
+                            route_next_hop.update(
+                                {
+                                    'next_hop': '',
+                                    'outgoing_interface': next_hop.get('interface')
+                                }
+                            )
+                        else:
+                            route_next_hop.update(
+                                {
+                                    'next_hop': napalm_base.helpers.ip(next_hop.get('nexthopAddr')),
+                                    'outgoing_interface': next_hop.get('interface')
+                                }
+                            )
+                        routes[prefix].append(route_next_hop)
+                    if route_details.get('vias') == []:  # empty list
+                        routes[prefix].append(route)
         return routes
 
     def get_snmp_information(self):
+        """get_snmp_information() for EOS.  Re-written to not use TextFSM"""
 
-        snmp_information = dict()
-
-        commands = list()
-        commands.append('show running-config | section snmp-server')
-        raw_snmp_config = self.device.run_commands(commands, encoding='text')[0].get('output', '')
-
-        snmp_config = napalm_base.helpers.textfsm_extractor(self, 'snmp_config', raw_snmp_config)
-
-        if not snmp_config:
-            return snmp_information
-
-        snmp_information = {
-            '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', '')),
+        # Default values
+        snmp_dict = {
+            'chassis_id': '',
+            'location': '',
+            'contact': '',
             'community': {}
         }
 
-        for snmp_entry in snmp_config:
-            community_name = py23_compat.text_type(snmp_entry.get('community', ''))
-            if not community_name:
-                continue
-            snmp_information['community'][community_name] = {
-                'acl': py23_compat.text_type(snmp_entry.get('acl', '')),
-                'mode': py23_compat.text_type(snmp_entry.get('mode', 'ro').lower())
-            }
+        commands = [
+            'show snmp chassis',
+            'show snmp location',
+            'show snmp contact'
+        ]
+        snmp_config = self.device.run_commands(commands, encoding='json')
+        for line in snmp_config:
+            for k, v in line.items():
+                if k == 'chassisId':
+                    snmp_dict['chassis_id'] = v
+                else:
+                    # Some EOS versions add extra quotes
+                    snmp_dict[k] = v.strip('"')
 
-        return snmp_information
+        commands = ['show running-config | section snmp-server community']
+        raw_snmp_config = self.device.run_commands(commands, encoding='text')[0].get('output', '')
+        for line in raw_snmp_config.splitlines():
+            match = self._RE_SNMP_COMM.search(line)
+            if match:
+                matches = match.groupdict('')
+                snmp_dict['community'][match.group('community')] = {
+                    'acl': py23_compat.text_type(matches['v4_acl']),
+                    'mode': py23_compat.text_type(matches['access'])
+                }
+
+        return snmp_dict
 
     def get_users(self):
 
@@ -1129,7 +1172,7 @@ class EOSDriver(NetworkDriver):
                 return u'ssh_dsa', py23_compat.text_type(sshkey)
             return u'ssh_rsa', u''
 
-        users = dict()
+        users = {}
 
         commands = ['show user-account']
         user_items = self.device.run_commands(commands)[0].get('users', {})
@@ -1138,10 +1181,14 @@ class EOSDriver(NetworkDriver):
             user_details.pop('username', '')
             sshkey_value = user_details.pop('sshAuthorizedKey', '')
             sshkey_type, sshkey_value = _sshkey_type(sshkey_value)
+            if sshkey_value != '':
+                sshkey_list = [sshkey_value]
+            else:
+                sshkey_list = []
             user_details.update({
                 'level': user_details.pop('privLevel', 0),
                 'password': py23_compat.text_type(user_details.pop('secret', '')),
-                'sshkeys': [sshkey_value]
+                'sshkeys': sshkey_list
             })
             users[user] = user_details
 
@@ -1151,7 +1198,8 @@ class EOSDriver(NetworkDriver):
                    destination,
                    source=c.TRACEROUTE_SOURCE,
                    ttl=c.TRACEROUTE_TTL,
-                   timeout=c.TRACEROUTE_TIMEOUT):
+                   timeout=c.TRACEROUTE_TIMEOUT,
+                   vrf=c.TRACEROUTE_VRF):
 
         _HOP_ENTRY_PROBE = [
             '\s+',
@@ -1186,27 +1234,36 @@ class EOSDriver(NetworkDriver):
         probes = 3
         # in case will be added one further param to adjust the number of probes/hop
 
+        commands = []
+
+        if vrf:
+            commands.append('routing-context vrf {vrf}'.format(vrf=vrf))
+
         if source:
             source_opt = '-s {source}'.format(source=source)
         if ttl:
             ttl_opt = '-m {ttl}'.format(ttl=ttl)
         if timeout:
             timeout_opt = '-w {timeout}'.format(timeout=timeout)
-        else:
-            timeout = 5
-
-        command = 'traceroute {destination} {source_opt} {ttl_opt} {timeout_opt}'.format(
-            destination=destination,
-            source_opt=source_opt,
-            ttl_opt=ttl_opt,
-            timeout_opt=timeout_opt
+        total_timeout = timeout * ttl
+        # `ttl`, `source` and `timeout` are not supported by default CLI
+        # so we need to go through the bash and set a specific timeout
+        commands.append(
+            ('bash timeout {total_timeout} traceroute {destination} '
+             '{source_opt} {ttl_opt} {timeout_opt}').format(
+                total_timeout=total_timeout,
+                destination=destination,
+                source_opt=source_opt,
+                ttl_opt=ttl_opt,
+                timeout_opt=timeout_opt
+            )
         )
 
         try:
             traceroute_raw_output = self.device.run_commands(
-                [command], encoding='text')[0].get('output')
+                commands, encoding='text')[-1].get('output')
         except CommandErrorException:
-            return {'error': 'Cannot execute traceroute on the device: {}'.format(command)}
+            return {'error': 'Cannot execute traceroute on the device: {}'.format(commands[0])}
 
         hop_regex = ''.join(_HOP_ENTRY + _HOP_ENTRY_PROBE * probes)
 
@@ -1502,24 +1559,38 @@ class EOSDriver(NetworkDriver):
         else:
             raise Exception("Wrong retrieve filter: {}".format(retrieve))
 
-    def get_network_instances(self, name=''):
-        """get_network_instances implementation for EOS."""
-
+    def _show_vrf(self):
         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()
+
+        return output
+
+    def _get_vrfs(self):
+        output = self._show_vrf()
+
+        vrfs = [py23_compat.text_type(vrf['name']) for vrf in output]
+
+        vrfs.append(u'default')
+
+        return vrfs
+
+    def get_network_instances(self, name=''):
+        """get_network_instances implementation for EOS."""
+
+        output = self._show_vrf()
+        vrfs = {}
+        all_vrf_interfaces = {}
         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()
+            interfaces = {}
             for interface_raw in vrf.get('interfaces', []):
                 interface = interface_raw.split(',')
                 for line in interface:
@@ -1559,7 +1630,7 @@ class EOSDriver(NetworkDriver):
             return vrfs
 
     def ping(self, destination, source=c.PING_SOURCE, ttl=c.PING_TTL, timeout=c.PING_TIMEOUT,
-             size=c.PING_SIZE, count=c.PING_COUNT):
+             size=c.PING_SIZE, count=c.PING_COUNT, vrf=c.PING_VRF):
         """
         Execute ping on the device and returns a dictionary with the result.
         Output dictionary has one of following keys:
@@ -1578,13 +1649,21 @@ class EOSDriver(NetworkDriver):
             * rtt (float)
         """
         ping_dict = {}
+        commands = []
+
+        if vrf:
+            commands.append('routing-context vrf {vrf}'.format(vrf=vrf))
+
         command = 'ping {}'.format(destination)
         command += ' timeout {}'.format(timeout)
         command += ' size {}'.format(size)
         command += ' repeat {}'.format(count)
         if source != '':
             command += ' source {}'.format(source)
-        output = self.device.run_commands([command], encoding='text')[0]['output']
+
+        commands.append(command)
+        output = self.device.run_commands(commands, encoding='text')[-1]['output']
+
         if 'connect:' in output:
             ping_dict['error'] = output
         elif 'PING' in output:
@@ -1602,11 +1681,34 @@ class EOSDriver(NetworkDriver):
                 fields = line.split()
                 if 'icmp' in line:
                     if 'Unreachable' in line:
-                        results_array.append({'ip_address': py23_compat.text_type(fields[1]),
-                                              'rtt': 0.0})
+                        if "(" in fields[2]:
+                            results_array.append(
+                                {
+                                    'ip_address': py23_compat.text_type(fields[2][1:-1]),
+                                    'rtt': 0.0,
+                                }
+                            )
+                        else:
+                            results_array.append({'ip_address': py23_compat.text_type(fields[1]),
+                                                  'rtt': 0.0})
+                    elif 'truncated' in line:
+                        if "(" in fields[4]:
+                            results_array.append(
+                                {
+                                    'ip_address': py23_compat.text_type(fields[4][1:-2]),
+                                    'rtt': 0.0,
+                                }
+                            )
+                        else:
+                            results_array.append(
+                                {
+                                    'ip_address': py23_compat.text_type(fields[3][:-1]),
+                                    'rtt': 0.0,
... 132 lines suppressed ...

-- 
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