[Python-modules-commits] [napalm-ios] 01/04: Imported Upstream version 0.1.7
Vincent Bernat
bernat at moszumanska.debian.org
Fri May 27 19:23:40 UTC 2016
This is an automated email from the git hooks/post-receive script.
bernat pushed a commit to branch master
in repository napalm-ios.
commit 4b2162c7ec84615d73c5217522af819391d21867
Author: Vincent Bernat <bernat at debian.org>
Date: Fri May 27 21:20:15 2016 +0200
Imported Upstream version 0.1.7
---
MANIFEST.in | 2 +
PKG-INFO | 14 +
napalm_ios.egg-info/PKG-INFO | 14 +
napalm_ios.egg-info/SOURCES.txt | 13 +
napalm_ios.egg-info/dependency_links.txt | 1 +
napalm_ios.egg-info/requires.txt | 2 +
napalm_ios.egg-info/top_level.txt | 1 +
napalm_ios/__init__.py | 16 +
napalm_ios/ios.py | 1304 ++++++++++++++++++++++++++++++
napalm_ios/templates/delete_ntp_peers.j2 | 3 +
napalm_ios/templates/set_hostname.j2 | 1 +
napalm_ios/templates/set_ntp_peers.j2 | 3 +
requirements.txt | 2 +
setup.cfg | 5 +
setup.py | 29 +
15 files changed, 1410 insertions(+)
diff --git a/MANIFEST.in b/MANIFEST.in
new file mode 100644
index 0000000..cd663b8
--- /dev/null
+++ b/MANIFEST.in
@@ -0,0 +1,2 @@
+include requirements.txt
+include napalm_ios/templates/*.j2
diff --git a/PKG-INFO b/PKG-INFO
new file mode 100644
index 0000000..50bbb69
--- /dev/null
+++ b/PKG-INFO
@@ -0,0 +1,14 @@
+Metadata-Version: 1.1
+Name: napalm-ios
+Version: 0.1.7
+Summary: Network Automation and Programmability Abstraction Layer with Multivendor support
+Home-page: https://github.com/napalm-automation/napalm-ios
+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_ios.egg-info/PKG-INFO b/napalm_ios.egg-info/PKG-INFO
new file mode 100644
index 0000000..50bbb69
--- /dev/null
+++ b/napalm_ios.egg-info/PKG-INFO
@@ -0,0 +1,14 @@
+Metadata-Version: 1.1
+Name: napalm-ios
+Version: 0.1.7
+Summary: Network Automation and Programmability Abstraction Layer with Multivendor support
+Home-page: https://github.com/napalm-automation/napalm-ios
+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_ios.egg-info/SOURCES.txt b/napalm_ios.egg-info/SOURCES.txt
new file mode 100644
index 0000000..76839b0
--- /dev/null
+++ b/napalm_ios.egg-info/SOURCES.txt
@@ -0,0 +1,13 @@
+MANIFEST.in
+requirements.txt
+setup.py
+napalm_ios/__init__.py
+napalm_ios/ios.py
+napalm_ios.egg-info/PKG-INFO
+napalm_ios.egg-info/SOURCES.txt
+napalm_ios.egg-info/dependency_links.txt
+napalm_ios.egg-info/requires.txt
+napalm_ios.egg-info/top_level.txt
+napalm_ios/templates/delete_ntp_peers.j2
+napalm_ios/templates/set_hostname.j2
+napalm_ios/templates/set_ntp_peers.j2
\ No newline at end of file
diff --git a/napalm_ios.egg-info/dependency_links.txt b/napalm_ios.egg-info/dependency_links.txt
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/napalm_ios.egg-info/dependency_links.txt
@@ -0,0 +1 @@
+
diff --git a/napalm_ios.egg-info/requires.txt b/napalm_ios.egg-info/requires.txt
new file mode 100644
index 0000000..ec5f5f1
--- /dev/null
+++ b/napalm_ios.egg-info/requires.txt
@@ -0,0 +1,2 @@
+napalm-base
+netmiko>=0.5.0
diff --git a/napalm_ios.egg-info/top_level.txt b/napalm_ios.egg-info/top_level.txt
new file mode 100644
index 0000000..72f2e17
--- /dev/null
+++ b/napalm_ios.egg-info/top_level.txt
@@ -0,0 +1 @@
+napalm_ios
diff --git a/napalm_ios/__init__.py b/napalm_ios/__init__.py
new file mode 100644
index 0000000..b268bc6
--- /dev/null
+++ b/napalm_ios/__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_ios package."""
+from ios import IOSDriver
diff --git a/napalm_ios/ios.py b/napalm_ios/ios.py
new file mode 100644
index 0000000..c7bd093
--- /dev/null
+++ b/napalm_ios/ios.py
@@ -0,0 +1,1304 @@
+"""NAPALM Cisco IOS Handler."""
+
+# 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.
+
+from __future__ import print_function
+
+import re
+from datetime import datetime
+
+from netmiko import ConnectHandler, FileTransfer
+from napalm_base.base import NetworkDriver
+from napalm_base.exceptions import ReplaceConfigException, MergeConfigException
+
+# Easier to store these as constants
+HOUR_SECONDS = 3600
+DAY_SECONDS = 24 * HOUR_SECONDS
+WEEK_SECONDS = 7 * DAY_SECONDS
+YEAR_SECONDS = 365 * DAY_SECONDS
+
+
+class IOSDriver(NetworkDriver):
+ """NAPALM Cisco IOS Handler."""
+
+ def __init__(self, hostname, username, password, timeout=60, optional_args=None):
+ """NAPALM Cisco IOS Handler."""
+ if optional_args is None:
+ optional_args = {}
+ self.hostname = hostname
+ self.username = username
+ self.password = password
+ self.timeout = timeout
+ self.candidate_cfg = optional_args.get('candidate_cfg', 'candidate_config.txt')
+ self.merge_cfg = optional_args.get('merge_cfg', 'merge_config.txt')
+ self.rollback_cfg = optional_args.get('rollback_cfg', 'rollback_config.txt')
+
+ # None will cause autodetection of dest_file_system
+ self.dest_file_system = optional_args.get('dest_file_system', None)
+ self.global_delay_factor = optional_args.get('global_delay_factor', .5)
+ self.port = optional_args.get('port', 22)
+ self.auto_rollback_on_error = optional_args.get('auto_rollback_on_error', True)
+ self.device = None
+ self.config_replace = False
+ self.interface_map = {}
+
+ def open(self):
+ """Open a connection to the device."""
+ self.device = ConnectHandler(device_type='cisco_ios',
+ ip=self.hostname,
+ port=self.port,
+ username=self.username,
+ password=self.password,
+ global_delay_factor=self.global_delay_factor,
+ verbose=False)
+ if not self.dest_file_system:
+ try:
+ self.dest_file_system = self.device._autodetect_fs()
+ except AttributeError:
+ raise AttributeError("Netmiko _autodetect_fs not found please upgrade Netmiko or "
+ "specify dest_file_system in optional_args.")
+
+ def close(self):
+ """Close the connection to the device."""
+ self.device.disconnect()
+
+ def load_replace_candidate(self, filename=None, config=None):
+ """
+ SCP file to device filesystem, defaults to candidate_config.
+
+ Return None or raise exception
+ """
+ self.config_replace = True
+ if config:
+ raise NotImplementedError
+ if filename:
+ (return_status, msg) = self.scp_file(source_file=filename,
+ dest_file=self.candidate_cfg,
+ file_system=self.dest_file_system)
+ if not return_status:
+ if msg == '':
+ msg = "SCP transfer to remote device failed"
+ raise ReplaceConfigException(msg)
+
+ def load_merge_candidate(self, filename=None, config=None):
+ """
+ SCP file to remote device.
+
+ Merge configuration in: copy <file> running-config
+ """
+ self.config_replace = False
+ if config:
+ raise NotImplementedError
+ if filename:
+ (return_status, msg) = self.scp_file(source_file=filename,
+ dest_file=self.merge_cfg,
+ file_system=self.dest_file_system)
+ if not return_status:
+ if msg == '':
+ msg = "SCP transfer to remote device failed"
+ raise MergeConfigException(msg)
+
+ @staticmethod
+ def normalize_compare_config(diff):
+ """Filter out strings that should not show up in the diff."""
+ ignore_strings = ['Contextual Config Diffs', 'No changes were found', 'file prompt quiet', 'ntp clock-period']
+
+ new_list = []
+ for line in diff.splitlines():
+ for ignore in ignore_strings:
+ if ignore in line:
+ break
+ else: # nobreak
+ new_list.append(line)
+ return "\n".join(new_list)
+
+ @staticmethod
+ def _normalize_merge_diff(diff):
+ """Make compare_config() for merge look similar to replace config diff."""
+ new_diff = []
+ for line in diff.splitlines():
+ # Filter blank lines and prepend +sign
+ if line.strip():
+ new_diff.append('+' + line)
+ if new_diff:
+ new_diff.insert(0, '! Cisco IOS does not support true compare_config() for merge: '
+ 'echo merge file.')
+ else:
+ new_diff.append('! No changes specified in merge file.')
+ return "\n".join(new_diff)
+
+ def compare_config(self,
+ base_file='running-config',
+ new_file=None,
+ base_file_system='system:',
+ new_file_system=None):
+ """
+ show archive config differences <base_file> <new_file>.
+
+ Default operation is to compare system:running-config to self.candidate_cfg
+ """
+ # Set defaults if not passed as arguments
+ if new_file is None:
+ if self.config_replace:
+ new_file = self.candidate_cfg
+ else:
+ new_file = self.merge_cfg
+ if new_file_system is None:
+ new_file_system = self.dest_file_system
+ base_file_full = self.gen_full_path(filename=base_file, file_system=base_file_system)
+ new_file_full = self.gen_full_path(filename=new_file, file_system=new_file_system)
+
+ if self.config_replace:
+ cmd = 'show archive config differences {} {}'.format(base_file_full, new_file_full)
+ diff = self.device.send_command_expect(cmd)
+ diff = self.normalize_compare_config(diff)
+ else:
+ cmd = 'more {}'.format(new_file_full)
+ diff = self.device.send_command_expect(cmd)
+ diff = self._normalize_merge_diff(diff)
+ return diff.strip()
+
+ def _commit_hostname_handler(self, cmd):
+ """Special handler for hostname change on commit operation."""
+ try:
+ current_prompt = self.device.find_prompt()
+ # Wait 12 seconds for output to come back (.2 * 60)
+ output = self.device.send_command_expect(cmd, delay_factor=.2, max_loops=60)
+ except IOError:
+ # Check if hostname change
+ if current_prompt == self.device.find_prompt():
+ raise
+ else:
+ self.device.set_base_prompt()
+ output = ''
+ return output
+
+ def commit_config(self, filename=None):
+ """
+ If replacement operation, perform 'configure replace' for the entire config.
+
+ If merge operation, perform copy <file> running-config.
+ """
+ debug = False
+ # Always generate a rollback config on commit
+ self._gen_rollback_cfg()
+
+ if self.config_replace:
+ # Replace operation
+ if filename is None:
+ filename = self.candidate_cfg
+ cfg_file = self.gen_full_path(filename)
+ if not self._check_file_exists(cfg_file):
+ raise ReplaceConfigException("Candidate config file does not exist")
+ if self.auto_rollback_on_error:
+ cmd = 'configure replace {} force revert trigger error'.format(cfg_file)
+ else:
+ cmd = 'configure replace {} force'.format(cfg_file)
+ output = self._commit_hostname_handler(cmd)
+ if ('Failed to apply command' in output) or \
+ ('original configuration has been successfully restored' in output):
+ raise ReplaceConfigException("Candidate config could not be applied")
+ else:
+ # Merge operation
+ if filename is None:
+ filename = self.merge_cfg
+ cfg_file = self.gen_full_path(filename)
+ if not self._check_file_exists(cfg_file):
+ raise MergeConfigException("Merge source config file does not exist")
+ cmd = 'copy {} running-config'.format(cfg_file)
+ self._disable_confirm()
+ output = self._commit_hostname_handler(cmd)
+ self._enable_confirm()
+ if 'Invalid input detected' in output:
+ self.rollback()
+ merge_error = "Configuration merge failed; automatic rollback attempted"
+ raise MergeConfigException(merge_error)
+
+ # Save config to startup (both replace and merge)
+ output += self.device.send_command_expect("write mem")
+
+ def discard_config(self):
+ """Set candidate_cfg to current running-config. Erase the merge_cfg file."""
+ discard_candidate = 'copy running-config {}'.format(self.gen_full_path(self.candidate_cfg))
+ discard_merge = 'copy null: {}'.format(self.gen_full_path(self.merge_cfg))
+ self._disable_confirm()
+ self.device.send_command_expect(discard_candidate)
+ self.device.send_command_expect(discard_merge)
+ self._enable_confirm()
+
+ def rollback(self, filename=None):
+ """Rollback configuration to filename or to self.rollback_cfg file."""
+ if filename is None:
+ filename = self.rollback_cfg
+ cfg_file = self.gen_full_path(filename)
+ if not self._check_file_exists(cfg_file):
+ raise ReplaceConfigException("Rollback config file does not exist")
+ cmd = 'configure replace {} force'.format(cfg_file)
+ self.device.send_command_expect(cmd)
+
+ def scp_file(self, source_file, dest_file, file_system):
+ """
+ SCP file to remote device.
+
+ Return (status, msg)
+ status = boolean
+ msg = details on what happened
+ """
+ # Will automaticall enable SCP on remote device
+ enable_scp = True
+ debug = False
+
+ with FileTransfer(self.device,
+ source_file=source_file,
+ dest_file=dest_file,
+ file_system=file_system) as scp_transfer:
+
+ # Check if file already exists and has correct MD5
+ if scp_transfer.check_file_exists() and scp_transfer.compare_md5():
+ msg = "File already exists and has correct MD5: no SCP needed"
+ return (True, msg)
+ if not scp_transfer.verify_space_available():
+ msg = "Insufficient space available on remote device"
+ return (False, msg)
+
+ if enable_scp:
+ scp_transfer.enable_scp()
+
+ # Transfer file
+ scp_transfer.transfer_file()
+
+ # Compares MD5 between local-remote files
+ if scp_transfer.verify_file():
+ msg = "File successfully transferred to remote device"
+ return (True, msg)
+ else:
+ msg = "File transfer to remote device failed"
+ return (False, msg)
+ return (False, '')
+
+ def _enable_confirm(self):
+ """Enable IOS confirmations on file operations (global config command)."""
+ cmd = 'no file prompt quiet'
+ self.device.send_config_set([cmd])
+
+ def _disable_confirm(self):
+ """Disable IOS confirmations on file operations (global config command)."""
+ cmd = 'file prompt quiet'
+ self.device.send_config_set([cmd])
+
+ def gen_full_path(self, filename, file_system=None):
+ """Generate full file path on remote device."""
+ if file_system is None:
+ return '{}/{}'.format(self.dest_file_system, filename)
+ else:
+ if ":" not in file_system:
+ raise ValueError("Invalid file_system specified: {}".format(file_system))
+ return '{}/{}'.format(file_system, filename)
+
+ def _gen_rollback_cfg(self):
+ """Save a configuration that can be used for rollback."""
+ cfg_file = self.gen_full_path(self.rollback_cfg)
+ cmd = 'copy running-config {}'.format(cfg_file)
+ self._disable_confirm()
+ self.device.send_command_expect(cmd)
+ self._enable_confirm()
+
+ def _check_file_exists(self, cfg_file):
+ """
+ Check that the file exists on remote device using full path.
+
+ cfg_file is full path i.e. flash:/file_name
+
+ For example
+ # dir flash:/candidate_config.txt
+ Directory of flash:/candidate_config.txt
+
+ 33 -rw- 5592 Dec 18 2015 10:50:22 -08:00 candidate_config.txt
+
+ return boolean
+ """
+ cmd = 'dir {}'.format(cfg_file)
+ success_pattern = 'Directory of {}'.format(cfg_file)
+ output = self.device.send_command_expect(cmd)
+ if 'Error opening' in output:
+ return False
+ elif success_pattern in output:
+ return True
+ return False
+
+ def _expand_interface_name(self, interface_brief):
+ """
+ Obtain the full interface name from the abbreviated name.
+
+ Cache mappings in self.interface_map.
+ """
+ if self.interface_map.get(interface_brief):
+ return self.interface_map.get(interface_brief)
+ command = 'show int {}'.format(interface_brief)
+ output = self.device.send_command(command)
+ output = output.strip()
+ first_line = output.splitlines()[0]
+ if 'line protocol' in first_line:
+ full_int_name = first_line.split()[0]
+ self.interface_map[interface_brief] = full_int_name
+ return self.interface_map.get(interface_brief)
+ else:
+ return interface_brief
+
+ def get_lldp_neighbors(self):
+ """IOS implementation of get_lldp_neighbors."""
+ lldp = {}
+ command = 'show lldp neighbors'
+ output = self.device.send_command(command)
+
+ # Check if router supports the command
+ if '% Invalid input' in output:
+ return {}
+
+ # Process the output to obtain just the LLDP entries
+ try:
+ split_output = re.split(r'^Device ID.*$', output, flags=re.M)[1]
+ split_output = re.split(r'^Total entries displayed.*$', split_output, flags=re.M)[0]
+ except IndexError:
+ return {}
+
+ split_output = split_output.strip()
+
+ for lldp_entry in split_output.splitlines():
+ # Example, twb-sf-hpsw1 Fa4 120 B 17
+ device_id, local_int_brief, hold_time, capability, remote_port = lldp_entry.split()
+ local_port = self._expand_interface_name(local_int_brief)
+
+ entry = {'port': remote_port, 'hostname': device_id}
+ lldp.setdefault(local_port, [])
+ lldp[local_port].append(entry)
+
+ return lldp
+
+ def get_lldp_neighbors_detail(self):
+ """
+ IOS implementation of get_lldp_neighbors_detail.
+
+ Calls get_lldp_neighbors.
+ """
+ def pad_list_entries(my_list, list_length):
+ """Normalize the length of all the LLDP fields."""
+ if len(my_list) < list_length:
+ for i in range(list_length):
+ try:
+ my_list[i]
+ except IndexError:
+ my_list[i] = u"N/A"
+ return my_list
+
+ lldp = {}
+ lldp_neighbors = self.get_lldp_neighbors()
+
+ for interface in lldp_neighbors:
+ command = "show lldp neighbors {} detail".format(interface)
+ output = self.device.send_command(command)
+
+ # Check if router supports the command
+ if '% Invalid input' in output:
+ return {}
+
+ local_port = interface
+ port_id = re.findall(r"Port id: (.+)", output)
+ port_description = re.findall(r"Port Description: (.+)", output)
+ chassis_id = re.findall(r"Chassis id: (.+)", output)
+ system_name = re.findall(r"System Name: (.+)", output)
+ system_description = re.findall(r"System Description: \n(.+)", output)
+ system_capabilities = re.findall(r"System Capabilities: (.+)", output)
+ enabled_capabilities = re.findall(r"Enabled Capabilities: (.+)", output)
+ remote_address = re.findall(r"Management Addresses:\n IP: (.+)", output)
+
+ number_entries = len(port_id)
+ lldp_fields = [port_id, port_description, chassis_id, system_name, system_description,
+ system_capabilities, enabled_capabilities, remote_address]
+ # Check length of each list
+ for test_list in lldp_fields:
+ if len(test_list) > number_entries:
+ raise ValueError("Failure processing show lldp neighbors detail")
+
+ # Pad any missing entries with "N/A"
+ lldp_fields = [pad_list_entries(field, number_entries) for field in lldp_fields]
+
+ # Standardize the fields
+ port_id, port_description, chassis_id, system_name, system_description, \
+ system_capabilities, enabled_capabilities, remote_address = lldp_fields
+ standardized_fields = zip(port_id, port_description, chassis_id, system_name, system_description,
+ system_capabilities, enabled_capabilities, remote_address)
+
+ lldp.setdefault(local_port, [])
+ for entry in standardized_fields:
+ remote_port_id, remote_port_description, remote_chassis_id, remote_system_name, \
+ remote_system_description, remote_system_capab, remote_enabled_capab, \
+ remote_mgmt_address = entry
+
+ lldp[local_port].append({
+ 'parent_interface': u'N/A',
+ 'remote_port': remote_port_id,
+ 'remote_port_description': remote_port_description,
+ 'remote_chassis_id': remote_chassis_id,
+ 'remote_system_name': remote_system_name,
+ 'remote_system_description': remote_system_description,
+ 'remote_system_capab': remote_system_capab,
+ 'remote_system_enable_capab': remote_enabled_capab})
+
+ return lldp
+
+ @staticmethod
+ def parse_uptime(uptime_str):
+ """
+ Extract the uptime string from the given Cisco IOS Device.
+
+ Return the uptime in seconds as an integer
+ """
+ # Initialize to zero
+ (years, weeks, days, hours, minutes) = (0, 0, 0, 0, 0)
+
+ uptime_str = uptime_str.strip()
+ time_list = uptime_str.split(',')
+ for element in time_list:
+ if re.search("year", element):
+ years = int(element.split()[0])
+ elif re.search("week", element):
+ weeks = int(element.split()[0])
+ elif re.search("day", element):
+ days = int(element.split()[0])
+ elif re.search("hour", element):
+ hours = int(element.split()[0])
+ elif re.search("minute", element):
+ minutes = int(element.split()[0])
+
+ uptime_sec = (years * YEAR_SECONDS) + (weeks * WEEK_SECONDS) + (days * DAY_SECONDS) + \
+ (hours * 3600) + (minutes * 60)
+ return uptime_sec
+
+ def get_facts(self):
+ """Return a set of facts from the devices."""
+ # default values.
+ vendor = u'Cisco'
+ uptime = -1
+ serial_number, fqdn, os_version, hostname = (u'Unknown', u'Unknown', u'Unknown', u'Unknown')
+
+ # obtain output from device
+ show_ver = self.device.send_command('show version')
+ show_hosts = self.device.send_command('show hosts')
+ show_ip_int_br = self.device.send_command('show ip interface brief')
+
+ # uptime/serial_number/IOS version
+ for line in show_ver.splitlines():
+ if ' uptime is ' in line:
+ hostname, uptime_str = line.split(' uptime is ')
+ uptime = self.parse_uptime(uptime_str)
+ hostname = hostname.strip()
+
+ if 'Processor board ID' in line:
+ _, serial_number = line.split("Processor board ID ")
+ serial_number = serial_number.strip()
+
+ if re.search(r"Cisco IOS Software", line):
+ _, os_version = line.split("Cisco IOS Software, ")
+ os_version = os_version.strip()
+ elif re.search(r"IOS (tm).+Software", line):
+ _, os_version = line.split("IOS (tm) ")
+ os_version = os_version.strip()
+
+ # Determine domain_name and fqdn
+ for line in show_hosts.splitlines():
+ if 'Default domain' in line:
+ _, domain_name = line.split("Default domain is ")
+ domain_name = domain_name.strip()
+ break
+ if domain_name != 'Unknown' and hostname != 'Unknown':
+ fqdn = u'{}.{}'.format(hostname, domain_name)
+
+ # model filter
+ try:
+ match_model = re.search(r"Cisco (.+?) .+bytes of", show_ver, flags=re.IGNORECASE)
+ model = match_model.group(1)
+ except AttributeError:
+ model = u'Unknown'
+
+ # interface_list filter
+ interface_list = []
+ show_ip_int_br = show_ip_int_br.strip()
+ for line in show_ip_int_br.splitlines():
+ if 'Interface ' in line:
+ continue
+ interface = line.split()[0]
+ interface_list.append(interface)
+
+ return {
+ 'uptime': uptime,
+ 'vendor': vendor,
+ 'os_version': unicode(os_version),
+ 'serial_number': unicode(serial_number),
+ 'model': unicode(model),
+ 'hostname': unicode(hostname),
+ 'fqdn': fqdn,
+ 'interface_list': interface_list
+ }
+
+ def get_interfaces(self):
+ """
+ Get interface details.
+
+ last_flapped is not implemented
+
+ Example Output:
+
+ { u'Vlan1': { 'description': u'N/A',
+ 'is_enabled': True,
+ 'is_up': True,
+ 'last_flapped': -1.0,
+ 'mac_address': u'a493.4cc1.67a7',
+ 'speed': 100},
+ u'Vlan100': { 'description': u'Data Network',
+ 'is_enabled': True,
+ 'is_up': True,
+ 'last_flapped': -1.0,
+ 'mac_address': u'a493.4cc1.67a7',
+ 'speed': 100},
+ u'Vlan200': { 'description': u'Voice Network',
+ 'is_enabled': True,
+ 'is_up': True,
+ 'last_flapped': -1.0,
+ 'mac_address': u'a493.4cc1.67a7',
+ 'speed': 100}}
+ """
+ interface_list = {}
+
+ # default values.
+ last_flapped = -1.0
+
+ # creating the parsing regex.
+ mac_regex = r".*,\saddress\sis\s(?P<mac_address>\S+).*"
+ speed_regex = r".*BW\s(?P<speed>\d+)\s(?P<speed_format>\S+).*"
+
+ command = 'show ip interface brief'
+ output = self.device.send_command(command)
+ for line in output.splitlines():
+ if 'Interface' in line and 'Status' in line:
+ continue
+ fields = line.split()
+ """
+ router#sh ip interface brief
+ Interface IP-Address OK? Method Status Protocol
+ FastEthernet8 10.65.43.169 YES DHCP up up
+ GigabitEthernet0 unassigned YES NVRAM administratively down down
+ Loopback234 unassigned YES unset up up
+ Loopback555 unassigned YES unset up up
+ NVI0 unassigned YES unset administratively down down
+ Tunnel0 10.63.100.9 YES NVRAM up up
+ Tunnel1 10.63.101.9 YES NVRAM up up
+ Vlan1 unassigned YES unset up up
+ Vlan100 10.40.0.1 YES NVRAM up up
+ Vlan200 10.63.176.57 YES NVRAM up up
+ Wlan-GigabitEthernet0 unassigned YES unset up up
+ wlan-ap0 10.40.0.1 YES unset up up
+ """
+
+ # Check for administratively down
+ if len(fields) == 6:
+ interface, ip_address, ok, method, status, protocol = fields
+ elif len(fields) == 7:
+ # Administratively down is two fields in the output for status
+ interface, ip_address, ok, method, status, status2, protocol = fields
+ else:
+ raise ValueError(u"Unexpected Response from the device")
+
+ status = status.lower()
+ protocol = protocol.lower()
+ if 'admin' in status:
+ is_enabled = False
+ else:
+ is_enabled = True
+ is_up = bool('up' in protocol)
+ interface_list[interface] = {
+ 'is_up': is_up,
+ 'is_enabled': is_enabled,
+ 'last_flapped': last_flapped,
+ }
+
+ for interface in interface_list:
+ show_command = "show interface {0}".format(interface)
+ interface_output = self.device.send_command(show_command)
+ try:
+ # description filter
+ description = re.search(r" Description: (.+)", interface_output)
+ interface_list[interface]['description'] = description.group(1).strip('\r')
+ except AttributeError:
+ interface_list[interface]['description'] = u'N/A'
+
+ try:
+ # mac_address filter.
+ match_mac = re.match(mac_regex, interface_output, flags=re.DOTALL)
+ group_mac = match_mac.groupdict()
+ mac_address = group_mac["mac_address"]
+ interface_list[interface]['mac_address'] = unicode(mac_address)
+ except AttributeError:
+ interface_list[interface]['mac_address'] = u'N/A'
+ try:
+ # BW filter.
+ match_speed = re.match(speed_regex, interface_output, flags=re.DOTALL)
+ group_speed = match_speed.groupdict()
+ speed = group_speed["speed"]
+ speed_format = group_speed["speed_format"]
+ if speed_format == 'Mbit':
+ interface_list[interface]['speed'] = int(speed)
+ else:
+ speed = int(speed) / 1000
+ interface_list[interface]['speed'] = int(speed)
+ except AttributeError:
+ interface_list[interface]['speed'] = -1
+ except ValueError:
+ interface_list[interface]['speed'] = -1
+
+ return interface_list
+
+ def get_interfaces_ip(self):
+ """
+ Get interface ip details.
+
+ Returns a dict of dicts
+
+ Example Output:
+
+ { u'FastEthernet8': { 'ipv4': { u'10.66.43.169': { 'prefix_length': 22}}},
+ u'Loopback555': { 'ipv4': { u'192.168.1.1': { 'prefix_length': 24}},
+ 'ipv6': { u'1::1': { 'prefix_length': 64},
+ u'2001:DB8:1::1': { 'prefix_length': 64},
+ u'2::': { 'prefix_length': 64},
+ u'FE80::3': { 'prefix_length': u'N/A'}}},
+ u'Tunnel0': { 'ipv4': { u'10.63.100.9': { 'prefix_length': 24}}},
+ u'Tunnel1': { 'ipv4': { u'10.63.101.9': { 'prefix_length': 24}}},
+ u'Vlan100': { 'ipv4': { u'10.40.0.1': { 'prefix_length': 24},
+ u'10.41.0.1': { 'prefix_length': 24},
+ u'10.65.0.1': { 'prefix_length': 24}}},
+ u'Vlan200': { 'ipv4': { u'10.63.176.57': { 'prefix_length': 29}}}}
+ """
+ interfaces = {}
+
+ command = 'show ip interface brief'
+ output = self.device.send_command(command)
+ for line in output.splitlines():
+ if 'Interface' in line and 'Status' in line:
+ continue
+ fields = line.split()
+ if len(fields) >= 3:
+ interface = fields[0]
+ else:
+ raise ValueError("Unexpected response from the router")
+ interfaces.update({interface: {}})
+
+ # Parse IP Address and Subnet Mask from Interfaces
+ for interface in interfaces:
+ show_command = "show run interface {0}".format(interface)
+ interface_output = self.device.send_command(show_command)
+ for line in interface_output.splitlines():
+ if 'ip address ' in line and 'no ip address' not in line:
+ fields = line.split()
+ if len(fields) == 3:
+ # Check for 'ip address dhcp', convert to ip address and mask
+ if fields[2] == 'dhcp':
+ show_command = "show interface {0} | in Internet address is".format(interface)
+ show_int = self.device.send_command(show_command)
+ int_fields = show_int.split()
+ ip_address, subnet = int_fields[3].split(r'/')
+ interfaces[interface]['ipv4'] = {ip_address: {}}
+ try:
+ interfaces[interface]['ipv4'][ip_address] = {'prefix_length': int(subnet)}
+ except ValueError:
+ interfaces[interface]['ipv4'][ip_address] = {'prefix_length': u'N/A'}
+ elif len(fields) in [4, 5]:
+ # Check for 'ip address 10.10.10.1 255.255.255.0'
+ # Check for 'ip address 10.10.11.1 255.255.255.0 secondary'
+ if 'ipv4' not in interfaces[interface].keys():
+ interfaces[interface].update({'ipv4': {}})
+ ip_address = fields[2]
+
+ try:
+ subnet = sum([bin(int(x)).count('1') for x in fields[3].split('.')])
+ except ValueError:
+ subnet = u'N/A'
+
+ ip_dict = {'prefix_length': subnet}
+ interfaces[interface]['ipv4'].update({ip_address: {}})
+ interfaces[interface]['ipv4'][ip_address].update(ip_dict)
+ else:
+ raise ValueError(u"Unexpected Response from the device")
+
+ # Check IPv6
+ if 'ipv6 address ' in line:
+ fields = line.split()
+ ip_address = fields[2]
+ if 'ipv6' not in interfaces[interface].keys():
+ interfaces[interface].update({'ipv6': {}})
+
+ try:
+ if r'/' in ip_address:
+ # check for 'ipv6 address 1::1/64'
+ ip_address, subnet = ip_address.split(r'/')
+ interfaces[interface]['ipv6'].update({ip_address: {}})
+ ip_dict = {'prefix_length': int(subnet)}
+ else:
+ # check for 'ipv6 address FE80::3 link-local'
+ interfaces[interface]['ipv6'].update({ip_address: {}})
+ ip_dict = {'prefix_length': u'N/A'}
+
+ interfaces[interface]['ipv6'][ip_address].update(ip_dict)
+ except AttributeError:
+ raise ValueError(u"Unexpected Response from the device")
+
+ # remove keys with no data
+ for key in interfaces.keys():
+ if not bool(interfaces[key]):
+ del interfaces[key]
+
+ return interfaces
+
+ @staticmethod
+ def bgp_time_conversion(bgp_uptime):
+ """
+ Convert string time to seconds.
+
+ Examples
+ 00:14:23
+ 00:13:40
+ 00:00:21
+ 00:00:13
+ 00:00:49
+ 1d11h
+ 1d17h
+ 1w0d
+ 8w5d
+ 1y28w
+ never
+ """
+ bgp_uptime = bgp_uptime.strip()
+ uptime_letters = set(['w', 'h', 'd'])
+
+ if 'never' in bgp_uptime:
+ return -1
+ elif ':' in bgp_uptime:
+ times = bgp_uptime.split(":")
+ times = [int(x) for x in times]
+ hours, minutes, seconds = times
+ return (hours * 3600) + (minutes * 60) + seconds
+ # Check if any letters 'w', 'h', 'd' are in the time string
+ elif uptime_letters & set(bgp_uptime):
+ form1 = r'(\d+)d(\d+)h' # 1d17h
+ form2 = r'(\d+)w(\d+)d' # 8w5d
+ form3 = r'(\d+)y(\d+)w' # 1y28w
+ match = re.search(form1, bgp_uptime)
+ if match:
+ days = int(match.group(1))
+ hours = int(match.group(2))
+ return (days * DAY_SECONDS) + (hours * 3600)
+ match = re.search(form2, bgp_uptime)
+ if match:
+ weeks = int(match.group(1))
+ days = int(match.group(2))
+ return (weeks * WEEK_SECONDS) + (days * DAY_SECONDS)
+ match = re.search(form3, bgp_uptime)
+ if match:
+ years = int(match.group(1))
+ weeks = int(match.group(2))
+ return (years * YEAR_SECONDS) + (weeks * WEEK_SECONDS)
+ raise ValueError("Unexpected value for BGP uptime string: {}".format(bgp_uptime))
+
+ def get_bgp_neighbors(self):
+ """
+ BGP neighbor information.
+
+ Currently, no VRF support
+ Not tested with IPv6
+
+ Example output of 'show ip bgp summary' only peer table
+ Neighbor V AS MsgRcvd MsgSent TblVer InQ OutQ Up/Down State/PfxRcd
+ 10.100.1.1 4 200 26 22 199 0 0 00:14:23 23
+ 10.200.1.1 4 300 21 51 199 0 0 00:13:40 0
+ 192.168.1.2 4 200 19 17 0 0 0 00:00:21 2
+ 1.1.1.1 4 1 0 0 0 0 0 never Active
+ 3.3.3.3 4 2 0 0 0 0 0 never Idle
+ 1.1.1.2 4 1 11 9 0 0 0 00:00:13 Idle (Admin)
+ 1.1.1.3 4 27506 256642 11327 2527 0 0 1w0d 519
+ 1.1.1.4 4 46887 1015641 19982 2527 0 0 1w0d 365
+ 192.168.1.237 4 60000 2139 2355 13683280 0 0 1d11h 4 (SE)
+ 10.90.1.4 4 65015 2508 2502 170 0 0 1d17h 163
+ 172.30.155.20 4 111 0 0 0 0 0 never Active
+ 1.1.1.5 4 6500 54 28 0 0 0 00:00:49 Idle (PfxCt)
+ 10.1.4.46 4 3979 95244 98874 267067 0 0 8w5d 254
+ 10.1.4.58 4 3979 2715 3045 267067 0 0 1d21h 2
+ 10.1.1.85 4 65417 8344303 8343570 235 0 0 1y28w 2
+ """
+ cmd_bgp_summary = 'show ip bgp summary'
+ bgp_neighbor_data = {}
+ bgp_neighbor_data['global'] = {}
+
+ output = self.device.send_command(cmd_bgp_summary).strip()
+ if 'Neighbor' not in output:
+ return {}
+ for line in output.splitlines():
+ if 'router identifier' in line:
+ # BGP router identifier 172.16.1.1, local AS number 100
+ rid_regex = r'^.* router identifier (\d+\.\d+\.\d+\.\d+), local AS number (\d+)'
... 524 lines suppressed ...
--
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/python-modules/packages/napalm-ios.git
More information about the Python-modules-commits
mailing list