[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