[Python-modules-commits] [napalm-eos] 01/03: Imported Upstream version 0.2.0
Vincent Bernat
bernat at moszumanska.debian.org
Tue Jun 7 14:52:17 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 642e50b2ab380bd69840c6268680720c934243d4
Author: Vincent Bernat <bernat at debian.org>
Date: Fri Jun 3 20:42:11 2016 +0200
Imported Upstream version 0.2.0
---
MANIFEST.in | 3 +
PKG-INFO | 14 +
napalm_eos.egg-info/PKG-INFO | 14 +
napalm_eos.egg-info/SOURCES.txt | 15 +
napalm_eos.egg-info/dependency_links.txt | 1 +
napalm_eos.egg-info/requires.txt | 2 +
napalm_eos.egg-info/top_level.txt | 1 +
napalm_eos/__init__.py | 16 +
napalm_eos/eos.py | 1203 ++++++++++++++++++++
napalm_eos/templates/delete_ntp_peers.j2 | 3 +
napalm_eos/templates/set_ntp_peers.j2 | 3 +
napalm_eos/utils/__init__.py | 1 +
napalm_eos/utils/textfsm_templates/ntp_peers.tpl | 6 +
napalm_eos/utils/textfsm_templates/snmp_config.tpl | 14 +
requirements.txt | 2 +
setup.cfg | 5 +
setup.py | 29 +
17 files changed, 1332 insertions(+)
diff --git a/MANIFEST.in b/MANIFEST.in
new file mode 100644
index 0000000..382c66b
--- /dev/null
+++ b/MANIFEST.in
@@ -0,0 +1,3 @@
+include requirements.txt
+include napalm_eos/templates/*.j2
+include napalm_eos/utils/textfsm_templates/*.tpl
diff --git a/PKG-INFO b/PKG-INFO
new file mode 100644
index 0000000..da0ed1e
--- /dev/null
+++ b/PKG-INFO
@@ -0,0 +1,14 @@
+Metadata-Version: 1.1
+Name: napalm-eos
+Version: 0.2.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
+License: UNKNOWN
+Description: UNKNOWN
+Platform: UNKNOWN
+Classifier: Topic :: Utilities
+Classifier: Programming Language :: Python
+Classifier: Operating System :: POSIX :: Linux
+Classifier: Operating System :: MacOS
diff --git a/napalm_eos.egg-info/PKG-INFO b/napalm_eos.egg-info/PKG-INFO
new file mode 100644
index 0000000..da0ed1e
--- /dev/null
+++ b/napalm_eos.egg-info/PKG-INFO
@@ -0,0 +1,14 @@
+Metadata-Version: 1.1
+Name: napalm-eos
+Version: 0.2.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
+License: UNKNOWN
+Description: UNKNOWN
+Platform: UNKNOWN
+Classifier: Topic :: Utilities
+Classifier: Programming Language :: Python
+Classifier: Operating System :: POSIX :: Linux
+Classifier: Operating System :: MacOS
diff --git a/napalm_eos.egg-info/SOURCES.txt b/napalm_eos.egg-info/SOURCES.txt
new file mode 100644
index 0000000..59f4055
--- /dev/null
+++ b/napalm_eos.egg-info/SOURCES.txt
@@ -0,0 +1,15 @@
+MANIFEST.in
+requirements.txt
+setup.py
+napalm_eos/__init__.py
+napalm_eos/eos.py
+napalm_eos.egg-info/PKG-INFO
+napalm_eos.egg-info/SOURCES.txt
+napalm_eos.egg-info/dependency_links.txt
+napalm_eos.egg-info/requires.txt
+napalm_eos.egg-info/top_level.txt
+napalm_eos/templates/delete_ntp_peers.j2
+napalm_eos/templates/set_ntp_peers.j2
+napalm_eos/utils/__init__.py
+napalm_eos/utils/textfsm_templates/ntp_peers.tpl
+napalm_eos/utils/textfsm_templates/snmp_config.tpl
\ No newline at end of file
diff --git a/napalm_eos.egg-info/dependency_links.txt b/napalm_eos.egg-info/dependency_links.txt
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/napalm_eos.egg-info/dependency_links.txt
@@ -0,0 +1 @@
+
diff --git a/napalm_eos.egg-info/requires.txt b/napalm_eos.egg-info/requires.txt
new file mode 100644
index 0000000..5e773d3
--- /dev/null
+++ b/napalm_eos.egg-info/requires.txt
@@ -0,0 +1,2 @@
+napalm-base
+pyeapi
diff --git a/napalm_eos.egg-info/top_level.txt b/napalm_eos.egg-info/top_level.txt
new file mode 100644
index 0000000..390f28f
--- /dev/null
+++ b/napalm_eos.egg-info/top_level.txt
@@ -0,0 +1 @@
+napalm_eos
diff --git a/napalm_eos/__init__.py b/napalm_eos/__init__.py
new file mode 100644
index 0000000..7015fd1
--- /dev/null
+++ b/napalm_eos/__init__.py
@@ -0,0 +1,16 @@
+# Copyright 2016 Dravetech AB. All rights reserved.
+#
+# The contents of this file are licensed under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with the
+# License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations under
+# the License.
+
+"""napalm_eos package."""
+from eos import EOSDriver
diff --git a/napalm_eos/eos.py b/napalm_eos/eos.py
new file mode 100644
index 0000000..0ac698b
--- /dev/null
+++ b/napalm_eos/eos.py
@@ -0,0 +1,1203 @@
+# Copyright 2015 Spotify AB. All rights reserved.
+#
+# The contents of this file are licensed under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with the
+# License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations under
+# the License.
+
+"""
+Napalm driver for Arista EOS.
+
+Read napalm.readthedocs.org for more information.
+"""
+
+# std libs
+import re
+import time
+from datetime import datetime
+from netaddr import IPAddress
+from netaddr import IPNetwork
+from collections import defaultdict
+from netaddr.core import AddrFormatError
+
+# third party libs
+import pyeapi
+from pyeapi.eapilib import ConnectionError
+
+# NAPALM base
+import napalm_base.helpers
+from napalm_base.base import NetworkDriver
+from napalm_base.utils import string_parsers
+from napalm_base.exceptions import ConnectionException, MergeConfigException, ReplaceConfigException,\
+ SessionLockedException, CommandErrorException
+
+# local modules
+# here add local imports
+# e.g. import napalm_eos.helpers etc.
+
+
+class EOSDriver(NetworkDriver):
+ """Napalm driver for Arista EOS."""
+
+ def __init__(self, hostname, username, password, timeout=60, optional_args=None):
+ """Constructor."""
+ self.device = None
+ self.hostname = hostname
+ self.username = username
+ self.password = password
+ self.timeout = timeout
+ self.config_session = None
+
+ if optional_args is None:
+ optional_args = {}
+ self.port = optional_args.get('port', 443)
+
+ def open(self):
+ """Implemantation of NAPALM method open."""
+ try:
+ connection = pyeapi.client.connect(
+ transport='https',
+ host=self.hostname,
+ username=self.username,
+ password=self.password,
+ port=self.port,
+ timeout=self.timeout
+ )
+ self.device = pyeapi.client.Node(connection)
+ # does not raise an Exception if unusable
+
+ # let's try to run a very simple command
+ self.device.run_commands(['show clock'], encoding='text')
+ except ConnectionError as ce:
+ # and this is raised either if device not avaiable
+ # either if HTTP(S) agent is not enabled
+ # show management api http-commands
+ raise ConnectionException(ce.message)
+
+ def close(self):
+ """Implemantation of NAPALM method close."""
+ self.discard_config()
+
+ 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:
+ self.config_session = 'napalm_{}'.format(datetime.now().microsecond)
+
+ commands = list()
+ commands.append('configure session {}'.format(self.config_session))
+
+ if replace:
+ commands.append('rollback clean-config')
+
+ if filename is not None:
+ with open(filename, 'r') as f:
+ lines = f.readlines()
+ else:
+ if isinstance(config, list):
+ lines = config
+ else:
+ lines = config.splitlines()
+
+ for line in lines:
+ line = line.strip()
+ if line == '':
+ continue
+ if line.startswith('!'):
+ continue
+ commands.append(line)
+
+ try:
+ self.device.run_commands(commands)
+ except pyeapi.eapilib.CommandError as e:
+ self.discard_config()
+
+ if replace:
+ raise ReplaceConfigException(e.message)
+ else:
+ raise MergeConfigException(e.message)
+
+ def load_replace_candidate(self, filename=None, config=None):
+ """Implemantation of NAPALM method load_replace_candidate."""
+ self._load_config(filename, config, True)
+
+ def load_merge_candidate(self, filename=None, config=None):
+ """Implemantation of NAPALM method load_merge_candidate."""
+ self._load_config(filename, config, False)
+
+ def compare_config(self):
+ """Implemantation of NAPALM method compare_config."""
+ if self.config_session is None:
+ return ''
+ else:
+ commands = ['show session-config named %s diffs' % self.config_session]
+ result = self.device.run_commands(commands, encoding='text')[0]['output']
+
+ result = '\n'.join(result.splitlines()[2:])
+
+ return result.strip()
+
+ def commit_config(self):
+ """Implemantation of NAPALM method commit_config."""
+ commands = list()
+ commands.append('copy startup-config flash:rollback-0')
+ commands.append('configure session {}'.format(self.config_session))
+ commands.append('commit')
+ commands.append('write memory')
+
+ self.device.run_commands(commands)
+ self.config_session = None
+
+ def discard_config(self):
+ """Implemantation of NAPALM method discard_config."""
+ if self.config_session is not None:
+ commands = list()
+ commands.append('configure session {}'.format(self.config_session))
+ commands.append('abort')
+ self.device.run_commands(commands)
+ self.config_session = None
+
+ def rollback(self):
+ """Implemantation of NAPALM method rollback."""
+ commands = list()
+ commands.append('configure replace flash:rollback-0')
+ commands.append('write memory')
+ self.device.run_commands(commands)
+
+ def get_facts(self):
+ """Implemantation of NAPALM method get_facts."""
+ commands = list()
+ commands.append('show version')
+ commands.append('show hostname')
+ commands.append('show interfaces status')
+
+ result = self.device.run_commands(commands)
+
+ version = result[0]
+ hostname = result[1]
+ interfaces_dict = result[2]['interfaceStatuses']
+
+ uptime = time.time() - version['bootupTimestamp']
+
+ interfaces = [i for i in interfaces_dict.keys() if '.' not in i]
+ interfaces = string_parsers.sorted_nicely(interfaces)
+
+ return {
+ 'hostname': hostname['hostname'],
+ 'fqdn': hostname['fqdn'],
+ 'vendor': u'Arista',
+ 'model': version['modelName'],
+ 'serial_number': version['serialNumber'],
+ 'os_version': version['internalVersion'],
+ 'uptime': int(uptime),
+ 'interface_list': interfaces,
+ }
+
+ def get_interfaces(self):
+ commands = list()
+ commands.append('show interfaces')
+ output = self.device.run_commands(commands)[0]
+
+ interfaces = dict()
+
+ for interface, values in output['interfaces'].iteritems():
+ interfaces[interface] = dict()
+
+ if values['lineProtocolStatus'] == 'up':
+ interfaces[interface]['is_up'] = True
+ interfaces[interface]['is_enabled'] = True
+ else:
+ interfaces[interface]['is_up'] = False
+ if values['interfaceStatus'] == 'disabled':
+ interfaces[interface]['is_enabled'] = False
+ else:
+ interfaces[interface]['is_enabled'] = True
+
+ interfaces[interface]['description'] = values['description']
+
+ interfaces[interface]['last_flapped'] = values.pop('lastStatusChangeTimestamp', None)
+
+ interfaces[interface]['speed'] = int(values['bandwidth'] * 1e-6)
+ interfaces[interface]['mac_address'] = values.pop('physicalAddress', u'')
+
+ return interfaces
+
+ def get_lldp_neighbors(self):
+ commands = list()
+ commands.append('show lldp neighbors')
+ output = self.device.run_commands(commands)[0]['lldpNeighbors']
+
+ lldp = dict()
+
+ for n in output:
+ if n['port'] not in lldp.keys():
+ lldp[n['port']] = list()
+
+ lldp[n['port']].append(
+ {
+ 'hostname': n['neighborDevice'],
+ 'port': n['neighborPort'],
+ }
+ )
+
+ return lldp
+
+ def get_interfaces_counters(self):
+ commands = list()
+
+ commands.append('show interfaces counters')
+ commands.append('show interfaces counters errors')
+
+ 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']
+
+ 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 _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_bgp_neighbors(self):
+ NEIGHBOR_FILTER = 'bgp neighbors vrf all | include remote AS | remote router ID |^\s*IPv[46] Unicast:.*[0-9]+|^Local AS|Desc'
+ output_summary_cmds = self.device.run_commands(
+ ['show ipv6 bgp summary vrf all', 'show ip bgp summary vrf all'],
+ encoding='json')
+ output_neighbor_cmds = self.device.run_commands(
+ ['show ip ' + NEIGHBOR_FILTER, 'show ipv6 ' + NEIGHBOR_FILTER],
+ encoding='text')
+
+ bgp_counters = defaultdict(lambda: dict(peers=dict()))
+ for summary in output_summary_cmds:
+ """
+ Json output looks as follows
+ "vrfs": {
+ "default": {
+ "routerId": 1,
+ "asn": 1,
+ "peers": {
+ "1.1.1.1": {
+ "msgSent": 1,
+ "inMsgQueue": 0,
+ "prefixReceived": 3926,
+ "upDownTime": 1449501378.418644,
+ "version": 4,
+ "msgReceived": 59616,
+ "prefixAccepted": 3926,
+ "peerState": "Established",
+ "outMsgQueue": 0,
+ "underMaintenance": false,
+ "asn": 1
+ }
+ }
+ }
+ }
+ """
+ for vrf, vrf_data in summary['vrfs'].iteritems():
+ bgp_counters[vrf]['router_id'] = vrf_data['routerId']
+ for peer, peer_data in vrf_data['peers'].iteritems():
+ peer_info = {
+ 'is_up': peer_data['peerState'] == 'Established',
+ 'is_enabled': peer_data['peerState'] == 'Established' or peer_data['peerState'] == 'Active',
+ 'uptime': int(peer_data['upDownTime'])
+ }
+ bgp_counters[vrf]['peers'][peer] = peer_info
+ lines = []
+ [lines.extend(x['output'].splitlines()) for x in output_neighbor_cmds]
+ for line in lines:
+ """
+ Raw output from the command looks like the following:
+
+ BGP neighbor is 1.1.1.1, remote AS 1, external link
+ Description: Very info such descriptive
+ BGP version 4, remote router ID 1.1.1.1, VRF my_vrf
+ IPv4 Unicast: 683 78
+ 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))
+ # this line can be either description or rid info
+ next_line = lines.pop(0)
+ desc = self._parse_desc(next_line)
+ if desc is None:
+ rid, vrf = self._parse_rid_info(next_line)
+ desc = ''
+ else:
+ rid, vrf = self._parse_rid_info(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))
+ data = {
+ 'remote_as': int(r_as),
+ 'remote_id': unicode(rid),
+ 'local_as': int(local_as),
+ 'description': unicode(desc),
+ 'address_family': {
+ 'ipv4': {
+ 'sent_prefixes': int(v4_sent),
+ 'received_prefixes': int(v4_recv),
+ 'accepted_prefixes': -1
+ },
+ 'ipv6': {
+ 'sent_prefixes': int(v6_sent),
+ 'received_prefixes': int(v6_recv),
+ 'accepted_prefixes': -1
+ }
+ }
+ }
+ bgp_counters[vrf]['peers'][neighbor].update(data)
+
+ if 'default' in bgp_counters.keys():
+ bgp_counters['global'] = bgp_counters.pop('default')
+ return bgp_counters
+
+ def get_environment(self):
+ """
+ Returns a dictionary where:
+ * fans is a dictionary of dictionaries where the key is the location and the values:
+ * status (boolean) - True if it's ok, false if it's broken
+ * temperature is a dictionary of dictionaries where the key is the location and the values:
+ * temperature (int) - Temperature in celsius the sensor is reporting.
+ * is_alert (boolean) - True if the temperature is above the alert threshold
+ * is_critical (boolean) - True if the temperature is above the critical threshold
+ * power is a dictionary of dictionaries where the key is the PSU id and the values:
+ * status (boolean) - True if it's ok, false if it's broken
+ * capacity (int) - Capacity in W that the power supply can support
+ * output (int) - Watts drawn by the system
+ * cpu is a dictionary of dictionaries where the key is the ID and the values
+ * %usage
+ * available_ram (int) - Total amount of RAM installed in the device
+ * used_ram (int) - RAM that is still free in the device
+ """
+ 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]
+ 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']
+ }
+
+ ''' Get CPU counters '''
+ m = re.search('(\d+.\d+)\%', cpu_output.splitlines()[2])
+ environment_counters['cpu'][0] = {
+ '%usage': float(m.group(1))
+ }
+ m = re.search('(\d+)k\W+total\W+(\d+)k\W+used\W+(\d+)k\W+free', cpu_output.splitlines()[3])
+
+ environment_counters['memory'] = {
+ 'available_ram': int(m.group(1)),
+ 'used_ram': int(m.group(2))
+ }
+
+ return environment_counters
+
+ def get_lldp_neighbors_detail(self, interface = ''):
+
+ lldp_neighbors_out = dict()
+
+ filters = list()
+ if interface:
+ filters.append(interface)
+
+ commands = list()
+ commands.append(
+ 'show lldp neighbors {filters} detail'.format(
+ filters = ' '.join(filters)
+ )
+ )
+
+ lldp_neighbors_in = {}
+ try:
+ lldp_neighbors_in = self.device.run_commands(commands)[0].get('lldpNeighbors', {})
+ except Exception:
+ return {}
+
+ for interface in lldp_neighbors_in:
+ interface_neighbors = lldp_neighbors_in.get(interface).get('lldpNeighborInfo', {})
+ if not interface_neighbors:
+ # in case of empty infos
+ continue
+ for neighbor in interface_neighbors: # it is provided a list of neighbors per interface
+ if interface not in lldp_neighbors_out.keys():
+ lldp_neighbors_out[interface] = list()
+ capabilities = neighbor.get('systemCapabilities')
+ lldp_neighbors_out[interface].append(
+ {
+ 'parent_interface' : interface, # no parent interfaces
+ 'remote_port' : neighbor.get('neighborInterfaceInfo', {}).get('interfaceId', u''),
+ 'remote_port_description' : u'',
+ 'remote_system_name' : neighbor.get('systemName', u''),
+ 'remote_system_description' : neighbor.get('systemDescription', u''),
+ 'remote_chassis_id' : neighbor.get('chassisId', u''),
+ 'remote_system_capab' : unicode(', '.join(capabilities)),
+ 'remote_system_enable_capab' : unicode(', '.join([capability for capability in capabilities.keys() if capabilities[capability]]))
+ }
+ )
+
+ return lldp_neighbors_out
+
+ def cli(self, commands = None):
+
+ cli_output = dict()
+
+ if type(commands) is not list:
+ raise TypeError('Please enter a valid list of commands!')
+
+ for command in commands:
+ try:
+ cli_output[unicode(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(
+ 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
+ )
+ raise CommandErrorException(str(cli_output))
+
+ return cli_output
+
+ def get_bgp_config(self, group='', neighbor=''):
+ """Implemantation of NAPALM method get_bgp_config."""
+ _GROUP_FIELD_MAP_ = {
+ 'type': 'type',
+ 'multipath': 'multipath',
+ 'apply-groups': 'apply_groups',
+ 'remove-private-as': 'remove_private_as',
+ 'ebgp-multihop': 'multihop_ttl',
+ 'remote-as': 'remote_as',
+ 'local-v4-addr': 'local_address',
+ 'local-v6-addr': 'local_address',
+ 'local-as': 'local_as',
+ 'description': 'description',
+ 'import-policy': 'import_policy',
+ 'export-policy': 'export_policy'
+ }
+
+ _PEER_FIELD_MAP_ = {
+ 'description': 'description',
+ 'remote-as': 'remote_as',
+ 'local-v4-addr': 'local_address',
+ 'local-v6-addr': 'local_address',
+ 'local-as': 'local_as',
+ 'next-hop-self': 'nhs',
+ 'route-reflector-client': 'route_reflector_client',
+ 'description': 'description',
+ 'import-policy': 'import_policy',
+ 'export-policy': 'export_policy',
+ 'passwd': 'authentication_key'
+ }
+
+ _PROPERTY_FIELD_MAP_ = _GROUP_FIELD_MAP_.copy()
+ _PROPERTY_FIELD_MAP_.update(_PEER_FIELD_MAP_)
+
+ _PROPERTY_TYPE_MAP_ = {
+ # used to determine the default value
+ # and cast the values
+ 'remote-as' : int,
+ 'ebgp-multihop' : int,
+ 'local-v4-addr' : unicode,
+ 'local-v6-addr' : unicode,
+ 'local-as' : int,
+ 'remove-private-as' : bool,
+ 'next-hop-self' : bool,
+ 'description' : unicode,
+ 'route-reflector-client': bool,
+ 'password' : unicode,
+ 'route-map' : unicode,
+ 'apply-groups' : list,
+ 'type' : unicode,
+ 'import-policy' : unicode,
+ 'export-policy' : unicode,
+ 'multipath' : bool
+ }
+
+ _DATATYPE_DEFAULT_ = {
+ unicode : u'',
+ int : 0,
+ bool : False,
+ list : []
+ }
+
+ def parse_options(options, default_value = False):
+
+ if not options:
+ return dict()
+
+ config_property = options[0]
+ field_name = _PROPERTY_FIELD_MAP_.get(config_property)
+ field_type = _PROPERTY_TYPE_MAP_.get(config_property)
+ field_value = _DATATYPE_DEFAULT_.get(field_type) # to get the default value
+
+ if not field_type:
+ # no type specified at all => return empty dictionary
+ return dict()
+
+ if not default_value:
+ if len(options) > 1:
+ field_value = field_type(options[1])
+ else:
+ if field_type is bool:
+ field_value = True
+ if field_name is not None:
+ return {field_name: field_value}
+ elif config_property in ['route-map', 'password']:
+ # 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])}
+ # returns the MD5 password
+ if config_property == 'route-map':
+ direction = None
+ if len(options) == 3:
+ direction = options[2]
+ field_value = field_type(options[1]) # the name of the policy
+ elif len(options) == 2:
+ direction = options[1]
+ if direction == 'in':
+ field_name = 'import_policy'
+ else:
+ field_name = 'export_policy'
+ return {field_name: field_value}
+
+ return dict()
+
+ bgp_config = dict()
+
+ commands = list()
+ 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_neighbors = dict()
+
+ if not group:
+ neighbor = ''
+
+ last_peer_group = ''
+ local_as = 0
+ for bgp_conf_line in bgp_conf_lines:
+ raw_line = bgp_conf_line
+ default_value = False
+ bgp_conf_line = bgp_conf_line.strip()
+ if bgp_conf_line.startswith('router bgp'):
+ local_as = int(bgp_conf_line.replace('router bgp', '').strip())
+ continue
+ if not (bgp_conf_line.startswith('neighbor') or bgp_conf_line.startswith('no neighbor')):
+ continue
+ if bgp_conf_line.startswith('no'):
+ 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])
+ options = bgp_conf_line_details[1:]
+ try:
+ # will try to parse the neighbor name
+ # which sometimes is the IP Address of the neigbor
+ # or the name of the BGP group
+ IPAddress(group_or_neighbor)
+ # if passes the test => it is an IP Address, thus a Neighbor!
+ peer_address = group_or_neighbor
+
+ 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
+
+ # in the config, neighbor details are lister after
+ # the group is specified for the neighbor:
+ #
+ # neighbor 192.168.172.36 peer-group 4-public-anycast-peers
+ # neighbor 192.168.172.36 remote-as 12392
+ # neighbor 192.168.172.36 maximum-routes 200
+ #
+ # because the lines are parsed sequentially
+ # can use the last group detected
+ # 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_.iteritems()
+ }) # 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(
+ parse_options(options, default_value)
+ )
+ except AddrFormatError:
+ # exception trying to parse group name
+ # group_or_neighbor represents the name of the group
+ group_name = group_or_neighbor
+ 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_.iteritems()
+ })
+ bgp_config[group_name].update({
+ 'prefix_limit' : {},
+ 'neighbors' : {},
+ 'local_as' : local_as
+ }) # few more default values
+ 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.iteritems():
+ if group not in bgp_config.keys():
+ continue
+ bgp_config[group]['neighbors'] = peers
+
+ return bgp_config
+
+ def get_arp_table(self):
+
+ arp_table = list()
+
+ commands = ['show arp']
+
+ ipv4_neighbors = []
+ try:
+ ipv4_neighbors = self.device.run_commands(commands)[0].get('ipV4Neighbors', [])
+ except pyeapi.eapilib.CommandError:
+ return []
+
+ for neighbor in ipv4_neighbors:
+ interface = unicode(neighbor.get('interface'))
+ mac_raw = neighbor.get('hwAddress')
+ mac_all = mac_raw.replace('.', '').replace(':', '')
+ mac_format = unicode(':'.join([mac_all[i:i+2] for i in range(12)[::2]]))
+ ip = unicode(neighbor.get('address'))
+ age = float(neighbor.get('age'))
+ arp_table.append(
+ {
+ 'interface' : interface,
+ 'mac' : mac_format,
+ 'ip' : ip,
+ 'age' : age
+ }
+ )
+
+ return arp_table
+
+
+ def get_ntp_peers(self):
+
+ commands = ['show running-config | section ntp']
+
+ raw_ntp_config = self.device.run_commands(commands, encoding='text')[0].get('output', '')
+
+ ntp_config = napalm_base.helpers.textfsm_extractor(self, 'ntp_peers', raw_ntp_config)
+
+ return {unicode(ntp_peer.get('ntppeer')):{} for ntp_peer in ntp_config if ntp_peer.get('ntppeer', '')}
+
+
+ def get_ntp_stats(self):
+
+ ntp_stats = list()
+
+ REGEX = (
+ '^\s?(\+|\*|x|-)?([a-zA-Z0-9\.+-:]+)'
+ '\s+([a-zA-Z0-9\.]+)\s+([0-9]{1,2})'
+ '\s+(-|u)\s+([0-9h-]+)\s+([0-9]+)'
+ '\s+([0-9]+)\s+([0-9\.]+)\s+([0-9\.-]+)'
+ '\s+([0-9\.]+)\s?$'
+ )
+
+ commands = list()
+ commands.append('show ntp associations')
+
+ # output = self.device.run_commands(commands)
+ # pyeapi.eapilib.CommandError: CLI command 2 of 2 'show ntp associations' failed: unconverted command
+ # JSON output not yet implemented...
+
+ ntp_assoc = self.device.run_commands(commands, encoding = 'text')[0].get('output', '\n\n')
+ ntp_assoc_lines = ntp_assoc.splitlines()[2:]
+
+ for ntp_assoc in ntp_assoc_lines:
+ line_search = re.search(REGEX, ntp_assoc, re.I)
+ if not line_search:
+ continue # pattern not found
+ line_groups = line_search.groups()
+ try:
+ ntp_stats.append({
+ 'remote' : unicode(line_groups[1]),
+ 'synchronized' : (line_groups[0] == '*'),
+ 'referenceid' : unicode(line_groups[2]),
+ 'stratum' : int(line_groups[3]),
+ 'type' : unicode(line_groups[4]),
+ 'when' : unicode(line_groups[5]),
+ 'hostpoll' : int(line_groups[6]),
+ 'reachability' : int(line_groups[7]),
+ 'delay' : float(line_groups[8]),
+ 'offset' : float(line_groups[9]),
+ 'jitter' : float(line_groups[10])
+ })
+ except Exception:
+ continue # jump to next line
+
+ return ntp_stats
... 460 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